Snap for 8730993 from 65cef9d54c66476f09cd530f8e515958bec375c5 to mainline-tzdata3-release

Change-Id: I1e9cbe7b0814c60955f2e0376faa41664f3e48f3
diff --git a/.bazelrc b/.bazelrc
index e70ca8c..21d2703 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -1,54 +1 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-# Required for new toolchain resolution API.
-build --incompatible_enable_cc_toolchain_resolution
-
-# Required for combined code coverage reports.
-coverage --experimental_generate_llvm_lcov
-coverage --combined_report=lcov
-
-# Enforces consistent action environment variables. This is important to
-# address Protobuf's rebuild sensitivity on changes to the environment
-# variables.
-build --incompatible_strict_action_env
-
-# This leaks the PATH variable into the Bazel build environment, which
-# enables the Python pw_protobuf plugins to find the Python version
-# via CIPD and pw_env_setup. This is a partial fix for pwbug/437,
-# however this does not provide a fix for downstream projects that
-# use system Python < 3.6. This approach is problematic because of the
-# Protobuf rebuild sensitivity to environment variable changes.
-# TODO(pwbug/437): Remove this once pwbug/437 is completely resolved.
-build --action_env=PATH
-
-# Define the --config=asan-libfuzzer configuration.
-build:asan-libfuzzer \
-    --@rules_fuzzing//fuzzing:cc_engine=@rules_fuzzing//fuzzing/engines:libfuzzer
-build:asan-libfuzzer \
-    --@rules_fuzzing//fuzzing:cc_engine_instrumentation=libfuzzer
-build:asan-libfuzzer --@rules_fuzzing//fuzzing:cc_engine_sanitizer=asan
-
-# Configures toolchain with polyfill headers.
-build --@rules_cc_toolchain_config//:user_defined=//pw_polyfill:toolchain_polyfill_overrides
-
-# Specifies desired output mode for running tests.
-# Valid values are
-#   'summary' to output only test status summary
-#   'errors' to also print test logs for failed tests
-#   'all' to print logs for all tests
-#   'streamed' to output logs for all tests in real time
-#     (this will force tests to be executed locally one at a time regardless
-#     of --test_strategy value).
-test --test_output=errors
+build --incompatible_enable_cc_toolchain_resolution
\ No newline at end of file
diff --git a/.clang-tidy b/.clang-tidy
deleted file mode 100644
index 10f27f3..0000000
--- a/.clang-tidy
+++ /dev/null
@@ -1,115 +0,0 @@
----
-Checks: >
-    bugprone-argument-comment,
-    bugprone-assert-side-effect,
-    bugprone-bool-pointer-implicit-conversion,
-    bugprone-dangling-handle,
-    bugprone-fold-init-type,
-    bugprone-forward-declaration-namespace,
-    bugprone-inaccurate-erase,
-    bugprone-macro-repeated-side-effects,
-    bugprone-move-forwarding-reference,
-    bugprone-multiple-statement-macro,
-    bugprone-string-constructor,
-    bugprone-suspicious-memset-usage,
-    bugprone-swapped-arguments,
-    bugprone-undefined-memory-manipulation,
-    bugprone-undelegated-constructor,
-    bugprone-unused-raii,
-    bugprone-use-after-move,
-    clang-diagnostic-*,
-    -clang-analyzer-*,
-    darwin-avoid-spinlock,
-    google-build-explicit-make-pair,
-    google-build-namespaces,
-    google-default-arguments,
-    google-global-names-in-headers,
-    google-readability-function-size,
-    google-readability-namespace-comments,
-    google-runtime-operator,
-    misc-static-assert,
-    misc-unconventional-assign-operator,
-    misc-unused-using-decls,
-    modernize-avoid-bind,
-    modernize-deprecated-ios-base-aliases,
-    modernize-make-shared,
-    modernize-make-unique,
-    modernize-replace-auto-ptr,
-    modernize-replace-disallow-copy-and-assign-macro,
-    modernize-replace-random-shuffle,
-    modernize-shrink-to-fit,
-    modernize-use-bool-literals,
-    modernize-use-equals-delete,
-    modernize-use-noexcept,
-    modernize-use-nullptr,
-    modernize-use-transparent-functors,
-    modernize-use-uncaught-exceptions,
-    performance-faster-string-find,
-    performance-for-range-copy,
-    performance-implicit-conversion-in-loop,
-    performance-inefficient-algorithm,
-    performance-inefficient-vector-operation,
-    performance-move-constructor-init,
-    readability-container-size-empty,
-    readability-inconsistent-declaration-parameter-name,
-    readability-misleading-indentation,
-    readability-redundant-control-flow,
-    readability-redundant-smartptr-get,
-    readability-string-compare,
-WarningsAsErrors: >
-    *,
-    -clang-diagnostic-unused-command-line-argument
-HeaderFilterRegex: '.*'
-...
-
-# Disabled checks:
-#
-# clang-analyzer-*:
-#
-# performance-*
-# bugprone-*
-# cert-*
-# misc-*
-# readability-*
-#
-# Checks marked with @ should be reenabled first
-# (the effort is minimal).
-#
-# modernize-avoid-c-arrays:
-# @ modernize-concat-nested-namespaces:
-#   Note: added in c++17
-# modernize-deprecated-headers:
-#   Advises to use <cheader> instead of <header.h> for
-#   legacy headers
-# modernize-loop-convert:
-# @ modernize-pass-by-value:
-# @ modernize-raw-string-literal:
-#   Note: added in c++11
-# @ modernize-redundant-void-arg:
-# modernize-return-braced-init-list:
-# @ modernize-unary-static-assert:
-#   Note: added in c++17
-#   The message argument can be omitted when it is empty
-# @ modernize-use-auto:
-#   Advises to use auto when initializing with a cast to
-#   avoid duplicating the type name
-# modernize-use-default-member-init:
-#   Note: added in c++11
-#   Advises to use a default initializer in
-#   member declarations
-# @ modernize-use-emplace:
-# @ modernize-use-equals-default:
-#   Note: added in c++11
-#   Advises to use '= default' for empty constructors or
-#   destructors '{}'
-# modernize-use-nodiscard:
-#   Note: added in c++17
-# @ modernize-use-override:
-# modernize-use-trailing-return-type:
-#   Note: added in c++11
-#   Used to delay the writing of the return type
-#   to after the function parameters; does not make
-#   sense to generalise its use.
-# modernize-use-using:
-#   Note: added in c++11
-#   Advises to use 'using' instead of 'typedef'
diff --git a/.gitignore b/.gitignore
index f66dbc9..33e6059 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,23 +3,17 @@
 out/
 bazel-*
 .presubmit/
-docs/_build
 
 # Editors
 .idea/
 .project
 .cproject
 .vscode
-# Clangd directories
 .clangd/
-/.cache/clangd/
-# Vim
 *.swp
 *.swo
-# Emacs
 *flycheck_*
 *_flymake.*
-.#*
 
 # Python
 *.pyc
@@ -55,7 +49,6 @@
 # Env Setup
 environment
 .environment
-build_overrides/pigweed_environment.gni
 # TODO(pwbug/216) Remove following lines in this section.
 # Maybe find a way to delete these files before these lines are removed.
 python*-env/
diff --git a/.gn b/.gn
index fc3984a..60ce6bd 100644
--- a/.gn
+++ b/.gn
@@ -13,25 +13,3 @@
 # the License.
 
 buildconfig = "//BUILDCONFIG.gn"
-
-default_args = {
-  pw_build_PIP_CONSTRAINTS =
-      [ "//pw_env_setup/py/pw_env_setup/virtualenv_setup/constraint.list" ]
-
-  # Exclude third-party headers from static analysis.
-  pw_toolchain_STATIC_ANALYSIS_SKIP_SOURCES_RES = [
-    "third_party/.*",
-    ".*packages/mbedtls.*",
-    ".*packages/boringssl.*",
-  ]
-
-  pw_toolchain_STATIC_ANALYSIS_SKIP_INCLUDE_PATHS = [
-    "mbedtls/include",
-    "mbedtls",
-    "boringssl/src/include",
-    "boringssl",
-
-    # Code generated by third-party tool.
-    "pw_tls_client/generate_test_data",
-  ]
-}
diff --git a/.pylintrc b/.pylintrc
index 1d46aee..c259400 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -1,9 +1,10 @@
-[MASTER]  # inclusive-language: ignore
+[MASTER]
 
 # A comma-separated list of package or module names from where C extensions may
 # be loaded. Extensions are loading into the active Python interpreter and may
 # run arbitrary code.
-extension-pkg-allowlist=mypy
+# TODO(pwbug/280) Change "whitelist" to "allowlist". (Blocked on pylint.)
+extension-pkg-whitelist=mypy
 
 # Add files or directories to the blocklist. They should be base names, not
 # paths.
@@ -61,7 +62,6 @@
 # no Warning level messages displayed, use "--disable=all --enable=classes
 # --disable=W".
 disable=bad-continuation,  # Rely on yapf for formatting
-        consider-using-with,
         fixme,
         subprocess-run-check,
         raise-missing-from,
@@ -161,8 +161,8 @@
 callbacks=cb_,
           _cb
 
-# A regular expression matching the name of placeholder variables (i.e. 
-# expected to not be used). # inclusive-language: ignore
+# A regular expression matching the name of dummy variables (i.e. expected to
+# not be used).
 dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
 
 # Argument names that match this expression will be ignored. Default to name
diff --git a/Android.bp b/Android.bp
deleted file mode 100644
index 2780420..0000000
--- a/Android.bp
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 {
-    default_applicable_licenses: ["external_pigweed_license"],
-}
-
-license {
-    name: "external_pigweed_license",
-    visibility: [":__subpackages__"],
-    license_kinds: ["SPDX-license-identifier-Apache-2.0"],
-    license_text: ["LICENSE"],
-}
diff --git a/BUILD b/BUILD
new file mode 100644
index 0000000..31c2571
--- /dev/null
+++ b/BUILD
@@ -0,0 +1,16 @@
+# Copyright 2019 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+licenses(["notice"])  # Apache License 2.0
+exports_files(["tsconfig.json"], visibility = ["//:__subpackages__"])
diff --git a/BUILD.bazel b/BUILD.bazel
deleted file mode 100644
index 9d4cb4f..0000000
--- a/BUILD.bazel
+++ /dev/null
@@ -1,61 +0,0 @@
-# Copyright 2019 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "@com_github_bazelbuild_buildtools//buildifier:def.bzl",
-    "buildifier",
-    "buildifier_test",
-)
-
-licenses(["notice"])
-
-exports_files(
-    ["tsconfig.json"],
-    visibility = [":__subpackages__"],
-)
-
-# Fix all Bazel relevant files.
-buildifier(
-    name = "buildifier",
-    # Ignore gn and CIPD outputs in formatting.
-    # NOTE: These globs are not Bazel native and are passed directly
-    # through to the buildifier binary.
-    # TODO: Remove these globs when
-    # https://github.com/bazelbuild/buildtools/issues/623 is addressed.
-    exclude_patterns = [
-        "./.environment/**/*",
-        "./.presubmit/**/*",
-        "./.out/**/*",
-    ],
-)
-
-# Test to ensure all Bazel build files are properly formatted.
-buildifier_test(
-    name = "buildifier_test",
-    srcs = glob(
-        [
-            "**/*.bazel",
-            "**/*.bzl",
-            "**/*.oss",
-            "**/*.sky",
-            "**/BUILD",
-        ],
-        # Node modules do not play nice with buildifier. Exclude these
-        # generated Bazel files from format testing.
-        exclude = ["**/node_modules/**/*"],
-    ) + ["WORKSPACE"],
-    diff_command = "diff -u",
-    mode = "diff",
-    verbose = True,
-)
diff --git a/BUILD.gn b/BUILD.gn
index 4f11d5b..c16c6ba 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -12,15 +12,12 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
-import("//build_overrides/pi_pico.gni")
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_arduino_build/arduino.gni")
 import("$dir_pw_build/host_tool.gni")
 import("$dir_pw_build/python.gni")
 import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_rpc/config.gni")
-import("$dir_pw_third_party/mcuxpresso/mcuxpresso.gni")
 import("$dir_pw_toolchain/generate_toolchain.gni")
 import("$dir_pw_unit_test/test.gni")
 
@@ -37,10 +34,6 @@
 
   # List of application image GN targets specific to the Pigweed target.
   pw_TARGET_APPLICATIONS = []
-
-  # Whether to run integration tests, which may involve multiple processes
-  # communicating over a socket. Integration tests require RPC synchronization.
-  pw_RUN_INTEGRATION_TESTS = host_os == "linux"
 }
 
 # Enumerate all of the different targets that upstream Pigweed will build by
@@ -48,70 +41,18 @@
 # exclusively to facilitate easy upstream development and testing.
 group("default") {
   deps = [
-    ":check_modules",
     ":docs",
+    ":fuzzers",
     ":host",
-    ":pi_pico",
-    ":python.lint",
-    ":python.tests",
-    ":static_analysis",
     ":stm32f429i",
-    "pw_rpc:test_protos.python.install",
-  ]
-
-  if (pw_RUN_INTEGRATION_TESTS) {
-    deps += [ ":integration_tests" ]
-  }
-}
-
-# Verify that this BUILD.gn file is only used by Pigweed itself.
-assert(get_path_info("//", "abspath") == get_path_info(".", "abspath"),
-       "Pigweed's top-level BUILD.gn may only be used when building upstream " +
-           "Pigweed. To pull all Pigweed code into your build, import " +
-           "\$dir_pigweed/modules.gni and create a top-level pw_test_group " +
-           "that depends on the tests in pw_modules_tests. See " +
-           "https://pigweed.dev/build_system.html for details.")
-
-_update_or_check_modules_lists = {
-  script = "$dir_pw_build/py/pw_build/generate_modules_lists.py"
-  args = [
-    rebase_path(".", root_build_dir),
-    rebase_path("PIGWEED_MODULES", root_build_dir),
-    rebase_path("$dir_pw_build/generated_pigweed_modules_lists.gni",
-                root_build_dir),
-  ]
-  inputs = [
-    "$dir_pw_build/generated_pigweed_modules_lists.gni",
-    "PIGWEED_MODULES",
+    "$dir_pw_env_setup:python.install",
+    "$dir_pw_env_setup:python.lint",
+    "$dir_pw_env_setup:python.tests",
+    "$dir_pw_env_setup:target_support_packages.lint",
+    "$dir_pw_env_setup:target_support_packages.tests",
   ]
 }
 
-# Check that PIGWEED_MODULES is up-to-date and sorted.
-action("check_modules") {
-  forward_variables_from(_update_or_check_modules_lists, "*")
-  outputs = [ "$target_gen_dir/$target_name.passed" ]
-  args += [ "--warn-only" ] + rebase_path(outputs, root_build_dir)
-}
-
-# Run this command after adding an item to PIGWEED_MODULES to update the
-# generated .gni with Pigweed modules lists.
-action("update_modules") {
-  forward_variables_from(_update_or_check_modules_lists, "*")
-  outputs = [ "$target_gen_dir/$target_name.ALWAYS_RERUN" ]  # Never created
-}
-
-group("pw_system_demo") {
-  deps = [ "$dir_pw_system:system_examples" ]
-}
-
-group("pi_pico") {
-  if (PICO_SRC_DIR != "") {
-    deps = [ ":pw_module_tests(targets/rp2040)" ]
-  }
-}
-
-_internal_toolchains = "$dir_pigweed/targets/host/pigweed_internal"
-
 # This template generates a group that builds pigweed_default with a particular
 # toolchain.
 template("_build_pigweed_default_at_all_optimization_levels") {
@@ -137,11 +78,11 @@
 
 # Select a default toolchain based on host OS.
 if (host_os == "linux") {
-  _default_toolchain_prefix = "$_internal_toolchains:pw_strict_host_clang_"
+  _default_toolchain_prefix = "$dir_pigweed/targets/host:host_clang_"
 } else if (host_os == "mac") {
-  _default_toolchain_prefix = "$_internal_toolchains:pw_strict_host_clang_"
+  _default_toolchain_prefix = "$dir_pigweed/targets/host:host_clang_"
 } else if (host_os == "win") {
-  _default_toolchain_prefix = "$_internal_toolchains:pw_strict_host_gcc_"
+  _default_toolchain_prefix = "$dir_pigweed/targets/host:host_gcc_"
 } else {
   assert(false, "Please define a host config for your system: $host_os")
 }
@@ -153,21 +94,15 @@
 }
 
 _build_pigweed_default_at_all_optimization_levels("host_clang") {
-  toolchain_prefix = "$_internal_toolchains:pw_strict_host_clang_"
+  toolchain_prefix = "$dir_pigweed/targets/host:host_clang_"
 }
 
 _build_pigweed_default_at_all_optimization_levels("host_gcc") {
-  toolchain_prefix = "$_internal_toolchains:pw_strict_host_gcc_"
-}
-
-if (pw_third_party_mcuxpresso_SDK != "") {
-  _build_pigweed_default_at_all_optimization_levels("mimxrt595") {
-    toolchain_prefix = "$dir_pigweed/targets/mimxrt595_evk:mimxrt595_evk_"
-  }
+  toolchain_prefix = "$dir_pigweed/targets/host:host_gcc_"
 }
 
 _build_pigweed_default_at_all_optimization_levels("stm32f429i") {
-  toolchain_prefix = "$dir_pigweed/targets/stm32f429i_disc1:stm32f429i_disc1_"
+  toolchain_prefix = "$dir_pigweed/targets/stm32f429i-disc1:stm32f429i_disc1_"
 }
 
 if (pw_arduino_build_CORE_PATH != "") {
@@ -178,39 +113,18 @@
 
 _build_pigweed_default_at_all_optimization_levels("qemu_gcc") {
   toolchain_prefix =
-      "$dir_pigweed/targets/lm3s6965evb_qemu:lm3s6965evb_qemu_gcc_"
+      "$dir_pigweed/targets/lm3s6965evb-qemu:lm3s6965evb_qemu_gcc_"
 }
 
 _build_pigweed_default_at_all_optimization_levels("qemu_clang") {
   toolchain_prefix =
-      "$dir_pigweed/targets/lm3s6965evb_qemu:lm3s6965evb_qemu_clang_"
-}
-
-# Run clang-tidy on pigweed_default with pw_strict_host_clang_debug toolchain options.
-# Make sure to invoke gn clean out when any relevant .clang-tidy
-# file is updated.
-group("static_analysis") {
-  _toolchain = "$_internal_toolchains:pw_strict_host_clang_debug"
-  deps = [ ":pigweed_default($_toolchain.static_analysis)" ]
+      "$dir_pigweed/targets/lm3s6965evb-qemu:lm3s6965evb_qemu_clang_"
 }
 
 group("docs") {
   deps = [ "$dir_pigweed/docs($dir_pigweed/targets/docs)" ]
 }
 
-# Tests larger than unit tests, typically run in a specific configuration. Only
-# added if pw_RUN_INTEGRATION_TESTS is true.
-group("integration_tests") {
-  _default_tc = _default_toolchain_prefix + pw_default_optimization_level
-  deps = [
-    "$dir_pw_rpc:cpp_client_server_integration_test($_default_tc)",
-    "$dir_pw_rpc/py:python_client_cpp_server_test.action($_default_tc)",
-    "$dir_pw_transfer:cpp_client_integration_test($_default_tc)",
-    "$dir_pw_transfer/py:python_cpp_transfer_test.action($_default_tc)",
-    "$dir_pw_unit_test/py:rpc_service_test.action($_default_tc)",
-  ]
-}
-
 # OSS-Fuzz uses this target to build fuzzers alone.
 group("fuzzers") {
   # Fuzzing is only supported on Linux and MacOS using clang.
@@ -219,64 +133,14 @@
   }
 }
 
-group("asan") {
-  if (host_os != "win") {
-    deps = [ ":pw_module_tests.run($dir_pigweed/targets/host:host_clang_asan)" ]
-  }
-}
-
-group("msan") {
-  if (host_os != "win") {
-    deps = [ ":pw_module_tests.run($dir_pigweed/targets/host:host_clang_msan)" ]
-  }
-}
-
-group("tsan") {
-  if (host_os != "win") {
-    deps = [ ":pw_module_tests.run($dir_pigweed/targets/host:host_clang_tsan)" ]
-  }
-}
-
-group("ubsan") {
-  if (host_os != "win") {
-    deps =
-        [ ":pw_module_tests.run($dir_pigweed/targets/host:host_clang_ubsan)" ]
-  }
-}
-
-group("ubsan_heuristic") {
-  if (host_os != "win") {
-    deps = [ ":pw_module_tests.run($dir_pigweed/targets/host:host_clang_ubsan_heuristic)" ]
-  }
-}
-
-group("runtime_sanitizers") {
-  if (host_os != "win") {
-    deps = [
-      ":asan",
-      ":msan",
-      ":tsan",
-      ":ubsan",
-
-      # No ubsan_heuristic, which may have false positives.
-    ]
-  }
-}
-
+# TODO(pwbug/325) Delete this target.
 pw_python_group("python") {
-  python_deps = [
-    "$dir_pigweed/docs:sphinx_themes",
-    "$dir_pw_env_setup:python",
-    "$dir_pw_env_setup:target_support_packages",
-  ]
+  python_deps = [ "$dir_pw_env_setup:python" ]
 }
 
-# Build host-only tooling.
-group("host_tools") {
-  deps = [
-    "$dir_pw_target_runner/go:simple_client",
-    "$dir_pw_target_runner/go:simple_server",
-  ]
+# TODO(pwbug/325) Delete this target.
+pw_python_group("target_support_packages") {
+  python_deps = [ "$dir_pw_env_setup:target_support_packages" ]
 }
 
 # By default, Pigweed will build this target when invoking ninja.
@@ -295,6 +159,10 @@
       deps += [ ":pw_module_tests.run" ]
     }
   }
+  if (defined(pw_toolchain_SCOPE.is_host_toolchain) &&
+      pw_toolchain_SCOPE.is_host_toolchain && pw_build_HOST_TOOLS) {
+    deps += [ ":host_tools" ]
+  }
 
   # Trace examples currently only support running on non-windows host
   if (defined(pw_toolchain_SCOPE.is_host_toolchain) &&
@@ -325,13 +193,121 @@
     }
   }
 
+  group("host_tools") {
+    deps = [
+      "$dir_pw_target_runner/go:simple_client",
+      "$dir_pw_target_runner/go:simple_server",
+    ]
+  }
+
   # All Pigweed modules that can be built using gn. This is not built by default.
   group("pw_modules") {
-    deps = pw_modules
+    deps = [
+      "$dir_pigweed/docs",
+      "$dir_pw_allocator",
+      "$dir_pw_base64",
+      "$dir_pw_blob_store",
+      "$dir_pw_bytes",
+      "$dir_pw_checksum",
+      "$dir_pw_chrono",
+      "$dir_pw_cpu_exception",
+      "$dir_pw_hdlc",
+      "$dir_pw_i2c",
+      "$dir_pw_metric",
+      "$dir_pw_persistent_ram",
+      "$dir_pw_polyfill",
+      "$dir_pw_preprocessor",
+      "$dir_pw_protobuf",
+      "$dir_pw_result",
+      "$dir_pw_status",
+      "$dir_pw_stream",
+      "$dir_pw_string",
+      "$dir_pw_sync",
+      "$dir_pw_sys_io",
+      "$dir_pw_thread",
+      "$dir_pw_tool",
+      "$dir_pw_trace",
+      "$dir_pw_unit_test",
+      "$dir_pw_varint",
+    ]
+
+    if (host_os != "win") {
+      deps += [
+        # TODO(frolv): Remove these two when new KVS is ready.
+        "$dir_pw_kvs",
+        "$dir_pw_minimal_cpp_stdlib",
+
+        # TODO(pwbug/111): Remove this when building successfully on Windows.
+        "$dir_pw_tokenizer",
+      ]
+    }
   }
 
   # Targets for all module unit test groups.
   pw_test_group("pw_module_tests") {
-    group_deps = pw_module_tests
+    group_deps = [
+      "$dir_pw_allocator:tests",
+      "$dir_pw_assert:tests",
+      "$dir_pw_base64:tests",
+      "$dir_pw_blob_store:tests",
+      "$dir_pw_bytes:tests",
+      "$dir_pw_checksum:tests",
+      "$dir_pw_chrono:tests",
+      "$dir_pw_containers:tests",
+      "$dir_pw_cpu_exception_cortex_m:tests",
+      "$dir_pw_fuzzer:tests",
+      "$dir_pw_hdlc:tests",
+      "$dir_pw_hex_dump:tests",
+      "$dir_pw_i2c:tests",
+      "$dir_pw_log:tests",
+      "$dir_pw_log_multisink:tests",
+      "$dir_pw_log_null:tests",
+      "$dir_pw_log_rpc:tests",
+      "$dir_pw_log_sink:tests",
+      "$dir_pw_log_tokenized:tests",
+      "$dir_pw_malloc_freelist:tests",
+      "$dir_pw_metric:tests",
+      "$dir_pw_multisink:tests",
+      "$dir_pw_persistent_ram:tests",
+      "$dir_pw_polyfill:tests",
+      "$dir_pw_preprocessor:tests",
+      "$dir_pw_protobuf:tests",
+      "$dir_pw_protobuf_compiler:tests",
+      "$dir_pw_random:tests",
+      "$dir_pw_result:tests",
+      "$dir_pw_ring_buffer:tests",
+      "$dir_pw_router:tests",
+      "$dir_pw_rpc:tests",
+      "$dir_pw_span:tests",
+      "$dir_pw_status:tests",
+      "$dir_pw_stream:tests",
+      "$dir_pw_string:tests",
+      "$dir_pw_sync:tests",
+      "$dir_pw_thread:tests",
+      "$dir_pw_thread_stl:tests",
+      "$dir_pw_tokenizer:tests",
+      "$dir_pw_trace:tests",
+      "$dir_pw_trace_tokenized:tests",
+      "$dir_pw_unit_test:tests",
+      "$dir_pw_varint:tests",
+    ]
+
+    if (defined(pw_toolchain_SCOPE.is_host_toolchain) &&
+        pw_toolchain_SCOPE.is_host_toolchain) {
+      # TODO(pwbug/196): KVS tests are not compatible with device builds as they
+      # use features such as std::map and are computationally expensive. Solving
+      # this requires a more complex capabilities-based build and configuration
+      # system which allowing enabling specific tests for targets that support
+      # them and modifying test parameters for different targets.
+      #
+      # Checking for pw_build_host_tools (which is only set by the host) is a
+      # temporary fix until the problem can be properly solved.
+      group_deps += [ "$dir_pw_kvs:tests" ]
+    }
+
+    if (host_os != "win") {
+      # TODO(amontanez): pw_minimal_cpp_stdlib tests do not build on windows.
+      group_deps += [ "$dir_pw_minimal_cpp_stdlib:tests" ]
+    }
   }
 }
diff --git a/BUILDCONFIG.gn b/BUILDCONFIG.gn
index 1dbd4c5..dbf7ed4 100644
--- a/BUILDCONFIG.gn
+++ b/BUILDCONFIG.gn
@@ -22,7 +22,7 @@
   import("//build_overrides/pigweed.gni")
 }
 
-# The default toolchain is not used in Pigweed builds, so it is set to a
-# toolchain that cannot compile C/C++ code. The top-level BUILD.gn should stamp
-# a group with all of the build targets and their toolchains.
-set_default_toolchain("${_pigweed_directory.dir_pw_toolchain}/default")
+# The default toolchain is not used in Pigweed builds, so it is set to a dummy
+# toolchain. The top-level BUILD.gn should stamp a group with all of the build
+# targets and their toolchains.
+set_default_toolchain("${_pigweed_directory.dir_pw_toolchain}/dummy")
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a6c922c..9447bda 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -16,81 +16,56 @@
 
 cmake_minimum_required(VERSION 3.16)
 
-# Do not rely on the PW_ROOT environment variable being set through bootstrap.
-# Regardless of whether it's set or not the following include will ensure it is.
-include(pw_build/pigweed.cmake)
+# The PW_ROOT environment variable should be set in bootstrap. If it is not set,
+# set it to this directory.
+if("$ENV{PW_ROOT}" STREQUAL "")
+  message("The PW_ROOT environment variable is not set; "
+          "using ${CMAKE_CURRENT_LIST_DIR} within CMake")
+  set(ENV{PW_ROOT} "${CMAKE_CURRENT_LIST_DIR}")
+endif()
 
 add_subdirectory(pw_assert EXCLUDE_FROM_ALL)
 add_subdirectory(pw_assert_basic EXCLUDE_FROM_ALL)
 add_subdirectory(pw_assert_log EXCLUDE_FROM_ALL)
-add_subdirectory(pw_assert_zephyr EXCLUDE_FROM_ALL)
 add_subdirectory(pw_base64 EXCLUDE_FROM_ALL)
 add_subdirectory(pw_blob_store EXCLUDE_FROM_ALL)
 add_subdirectory(pw_build EXCLUDE_FROM_ALL)
-add_subdirectory(pw_build_info EXCLUDE_FROM_ALL)
 add_subdirectory(pw_bytes EXCLUDE_FROM_ALL)
 add_subdirectory(pw_checksum EXCLUDE_FROM_ALL)
 add_subdirectory(pw_chrono EXCLUDE_FROM_ALL)
-add_subdirectory(pw_chrono_freertos EXCLUDE_FROM_ALL)
 add_subdirectory(pw_chrono_stl EXCLUDE_FROM_ALL)
-add_subdirectory(pw_chrono_zephyr EXCLUDE_FROM_ALL)
 add_subdirectory(pw_containers EXCLUDE_FROM_ALL)
 add_subdirectory(pw_cpu_exception EXCLUDE_FROM_ALL)
 add_subdirectory(pw_cpu_exception_cortex_m EXCLUDE_FROM_ALL)
-add_subdirectory(pw_file EXCLUDE_FROM_ALL)
-add_subdirectory(pw_function EXCLUDE_FROM_ALL)
 add_subdirectory(pw_hdlc EXCLUDE_FROM_ALL)
-add_subdirectory(pw_interrupt EXCLUDE_FROM_ALL)
-add_subdirectory(pw_interrupt_cortex_m EXCLUDE_FROM_ALL)
-add_subdirectory(pw_interrupt_zephyr EXCLUDE_FROM_ALL)
 add_subdirectory(pw_kvs EXCLUDE_FROM_ALL)
 add_subdirectory(pw_log EXCLUDE_FROM_ALL)
 add_subdirectory(pw_log_basic EXCLUDE_FROM_ALL)
-add_subdirectory(pw_log_null EXCLUDE_FROM_ALL)
-add_subdirectory(pw_log_string EXCLUDE_FROM_ALL)
 add_subdirectory(pw_log_tokenized EXCLUDE_FROM_ALL)
-add_subdirectory(pw_log_zephyr EXCLUDE_FROM_ALL)
 add_subdirectory(pw_minimal_cpp_stdlib EXCLUDE_FROM_ALL)
-add_subdirectory(pw_multisink EXCLUDE_FROM_ALL)
-add_subdirectory(pw_persistent_ram EXCLUDE_FROM_ALL)
 add_subdirectory(pw_polyfill EXCLUDE_FROM_ALL)
-add_subdirectory(pw_preprocessor EXCLUDE_FROM_ALL)
 add_subdirectory(pw_protobuf EXCLUDE_FROM_ALL)
-add_subdirectory(pw_protobuf_compiler EXCLUDE_FROM_ALL)
+add_subdirectory(pw_preprocessor EXCLUDE_FROM_ALL)
 add_subdirectory(pw_random EXCLUDE_FROM_ALL)
 add_subdirectory(pw_result EXCLUDE_FROM_ALL)
-add_subdirectory(pw_ring_buffer EXCLUDE_FROM_ALL)
 add_subdirectory(pw_router EXCLUDE_FROM_ALL)
 add_subdirectory(pw_rpc EXCLUDE_FROM_ALL)
-add_subdirectory(pw_snapshot EXCLUDE_FROM_ALL)
 add_subdirectory(pw_span EXCLUDE_FROM_ALL)
 add_subdirectory(pw_status EXCLUDE_FROM_ALL)
 add_subdirectory(pw_stream EXCLUDE_FROM_ALL)
 add_subdirectory(pw_string EXCLUDE_FROM_ALL)
 add_subdirectory(pw_sync EXCLUDE_FROM_ALL)
-add_subdirectory(pw_sync_freertos EXCLUDE_FROM_ALL)
 add_subdirectory(pw_sync_stl EXCLUDE_FROM_ALL)
-add_subdirectory(pw_sync_zephyr EXCLUDE_FROM_ALL)
 add_subdirectory(pw_sys_io EXCLUDE_FROM_ALL)
 add_subdirectory(pw_sys_io_stdio EXCLUDE_FROM_ALL)
-add_subdirectory(pw_sys_io_zephyr EXCLUDE_FROM_ALL)
-add_subdirectory(pw_system EXCLUDE_FROM_ALL)
-add_subdirectory(pw_thread EXCLUDE_FROM_ALL)
-add_subdirectory(pw_thread_freertos EXCLUDE_FROM_ALL)
-add_subdirectory(pw_thread_stl EXCLUDE_FROM_ALL)
 add_subdirectory(pw_tokenizer EXCLUDE_FROM_ALL)
 add_subdirectory(pw_trace EXCLUDE_FROM_ALL)
-add_subdirectory(pw_trace_tokenized EXCLUDE_FROM_ALL)
-add_subdirectory(pw_transfer EXCLUDE_FROM_ALL)
 add_subdirectory(pw_unit_test EXCLUDE_FROM_ALL)
 add_subdirectory(pw_varint EXCLUDE_FROM_ALL)
 
+add_subdirectory(targets/host EXCLUDE_FROM_ALL)
+
 add_subdirectory(third_party/nanopb EXCLUDE_FROM_ALL)
-add_subdirectory(third_party/freertos EXCLUDE_FROM_ALL)
 
-if(NOT ZEPHYR_PIGWEED_MODULE_DIR)
-  add_subdirectory(targets/host EXCLUDE_FROM_ALL)
-
-  add_custom_target(pw_apps)
-  add_dependencies(pw_apps pw_hdlc.rpc_example)
-endif()
+add_custom_target(pw_apps)
+add_dependencies(pw_apps pw_hdlc.rpc_example)
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..551d804
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,93 @@
+# Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of
+experience, education, socio-economic status, nationality, personal appearance,
+race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+*   Using welcoming and inclusive language
+*   Being respectful of differing viewpoints and experiences
+*   Gracefully accepting constructive criticism
+*   Focusing on what is best for the community
+*   Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+*   The use of sexualized language or imagery and unwelcome sexual attention or
+    advances
+*   Trolling, insulting/derogatory comments, and personal or political attacks
+*   Public or private harassment
+*   Publishing others' private information, such as a physical or electronic
+    address, without explicit permission
+*   Other conduct which could reasonably be considered inappropriate in a
+    professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, or to ban temporarily or permanently any
+contributor for other behaviors that they deem inappropriate, threatening,
+offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+This Code of Conduct also applies outside the project spaces when the Project
+Steward has a reasonable belief that an individual's behavior may have a
+negative impact on the project or its community.
+
+## Conflict Resolution
+
+We do not believe that all conflict is bad; healthy debate and disagreement
+often yield positive results. However, it is never okay to be disrespectful or
+to engage in behavior that violates the project’s code of conduct.
+
+If you see someone violating the code of conduct, you are encouraged to address
+the behavior directly with those involved. Many issues can be resolved quickly
+and easily, and this gives people more control over the outcome of their
+dispute. If you are unable to resolve the matter for any reason, or if the
+behavior is threatening or harassing, report it. We are dedicated to providing
+an environment where participants feel welcome and safe.
+
+Reports should be directed to Pigweed Community Managers at
+pigweed-community-managers@google.com, the Project Steward(s) for Pigweed. It
+is the Project Steward’s duty to receive and address reported violations of the
+code of conduct. They will then work with a committee consisting of
+representatives from the Open Source Programs Office and the Google Open Source
+Strategy team. If for any reason you are uncomfortable reaching out the Project
+Steward, please email opensource@google.com.
+
+We will investigate every complaint, but you may not receive a direct response.
+We will use our discretion in determining when and how to follow up on reported
+incidents, which may range from not taking action to permanent expulsion from
+the project and project-sponsored spaces. We will notify the accused of the
+report and provide them an opportunity to discuss it before any action is taken.
+The identity of the reporter will be omitted from the details of the report
+supplied to the accused. In potentially harmful situations, such as ongoing
+harassment or threats to anyone's safety, we may take action without notice.
+
+## Attribution
+
+This Code of Conduct is adapted from the Contributor Covenant, version 1.4,
+available at
+https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..d9e325f
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,211 @@
+# Contributing
+
+We'd love to accept your patches and contributions to Pigweed. There are just a
+few small guidelines you need to follow. Before making or sending major changes,
+please reach out on the [mailing list](mailto:pigweed@googlegroups.com) first to
+ensure the changes make sense for upstream. We generally go through a design
+phase before making large changes.
+
+Before participating in our community, please take a moment to review our [code
+of conduct](CODE_OF_CONDUCT.md). We expect everyone who interacts with the
+project to respect these guidelines.
+
+Pigweed contribution overview:
+ 1. One-time contributor setup:
+   * Sign the [Contributor License Agreement](https://cla.developers.google.com/).
+   * Verify that Git user email (git config user.email) is either Google Account
+     email or an Alternate email for the Google account used to sign the CLA (Manage
+     Google account->Personal Info->email).
+   * Install the [Gerrit commit hook](CONTRIBUTING.md#gerrit-commit-hook) to
+     automatically add a `Change-Id: ...` line to your commit.
+   * Install the Pigweed presubmit check hook (`pw presubmit --install`).
+     (recommended).
+ 1. Ensure all files include a correct [copyright and license header](CONTRIBUTING.md#source-code-headers).
+ 1. Include any necessary changes to
+    [documentation](CONTRIBUTING.md#documentation).
+ 1. Run `pw presubmit` (see below) to detect style or compilation issues before
+    uploading.
+ 1. Upload the change with `git push origin HEAD:refs/for/master`.
+ 1. Address any reviewer feedback by amending the commit (`git commit --amend`).
+ 1. Submit change to CI builders to merge. If you are not part of Pigweed's
+    core team, you can ask the reviewer to add the `+2 CQ` vote, which will
+    trigger a rebase and submit once the builders pass.
+
+## Contributor License Agreement
+
+Contributions to this project must be accompanied by a Contributor License
+Agreement. You (or your employer) retain the copyright to your contribution;
+this simply gives us permission to use and redistribute your contributions as
+part of the project. Head over to <https://cla.developers.google.com/> to see
+your current agreements on file or to sign a new one.
+
+You generally only need to submit a CLA once, so if you've already submitted one
+(even if it was for a different project), you probably don't need to do it
+again.
+
+## Gerrit Commit Hook
+
+Gerrit requires all changes to have a `Change-Id` tag at the bottom of each
+commit message. You should set this up to be done automatically using the
+instructions below.
+
+**Linux/macOS**<br/>
+```bash
+$ f=`git rev-parse --git-dir`/hooks/commit-msg ; mkdir -p $(dirname $f) ; curl -Lo $f https://gerrit-review.googlesource.com/tools/hooks/commit-msg ; chmod +x $f
+```
+
+**Windows**<br/>
+Download [the Gerrit commit hook](https://gerrit-review.googlesource.com/tools/hooks/commit-msg)
+and then copy it to the `.git\hooks` directory in the Pigweed repository.
+```batch
+copy %HOMEPATH%\Downloads\commit-msg %HOMEPATH%\pigweed\.git\hooks\commit-msg
+```
+
+## Documentation
+
+All Pigweed changes must either
+
+ 1. Include updates to documentation, or
+ 1. Include `No-Docs-Update-Reason: <reason>` in the commit message or a Gerrit
+    comment on the CL. Potential reasons might include
+    * "minor code formatting change",
+    * "internal cleanup of pw_modulename, no changes to API"
+
+It's acceptable to only document new changes in an otherwise underdocumented
+module, but it's not acceptable to not document new changes because the module
+doesn't have any other documentation.
+
+## Code Reviews
+
+All Pigweed development happens on Gerrit, following the [typical Gerrit
+development workflow](http://ceres-solver.org/contributing.html). Consult
+[Gerrit User Guide](https://gerrit-documentation.storage.googleapis.com/Documentation/2.12.3/intro-user.html)
+for more information on using Gerrit.
+
+In the future we may support GitHub pull requests, but until that time we will
+close GitHub pull requests and ask that the changes be uploaded to Gerrit
+instead.
+
+## Community Guidelines
+
+This project follows [Google's Open Source Community
+Guidelines](https://opensource.google/conduct/) and the [Pigweed Code of
+Conduct](CODE_OF_CONDUCT.md).
+
+## Source Code Headers
+
+Every Pigweed file containing source code must include copyright and license
+information. This includes any JS/CSS files that you might be serving out to
+browsers.
+
+Apache header for C and C++ files:
+
+```javascript
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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.
+```
+
+Apache header for Python and GN files:
+
+```python
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+```
+
+## Presubmit Checks and Continuous Integration
+
+All Pigweed change lists (CLs) must adhere to Pigweed's style guide and pass a
+suite of automated builds, tests, and style checks to be merged upstream. Much
+of this checking is done using Pigweed's `pw_presubmit` module by automated
+builders. These builders run before each Pigweed CL is submitted and in our
+continuous integration infrastructure (see
+https://ci.chromium.org/p/pigweed/g/pigweed/console).
+
+### Running Presubmit Checks
+
+To run automated presubmit checks on a pending CL, click the `CQ DRY RUN` button
+in the Gerrit UI. The results appear in the Tryjobs section, below the source
+listing. Jobs that passed are green; jobs that failed are red.
+
+If all checks pass, you will see a ``Dry run: This CL passed the CQ dry run.``
+comment on your change. If any checks fail, you will see a ``Dry run: Failed
+builds:`` message. All failures must be addressed before submitting.
+
+In addition to the publicly visible presubmit checks, Pigweed runs internal
+presubmit checks that are only visible within Google. If any these checks fail,
+external developers will see a ``Dry run: Failed builds:`` comment on the CL,
+even if all visible checks passed. Reach out to the Pigweed team for help
+addressing these issues.
+
+### Project Presubmit Checks
+
+In addition to Pigweed's presubmit checks, some projects that use Pigweed run
+their presubmit checks in Pigweed's infrastructure. This supports a development
+flow where projects automatically update their Pigweed submodule if their tests
+pass. If a project cannot build against Pigweed's tip-of-tree, it will stay on a
+fixed Pigweed revision until the issues are fixed. See the
+[sample project](https://pigweed.googlesource.com/pigweed/sample_project/) for
+an example of this.
+
+Pigweed does its best to keep builds passing for dependent projects. In some
+circumstances, the Pigweed maintainers may choose to merge changes that break
+dependent projects. This will only be done if
+
+  * a feature or fix is needed urgently in Pigweed or for a different project,
+    and
+  * the project broken by the change does not imminently need Pigweed updates.
+
+The downstream project will continue to build against their last working
+revision of Pigweed until the incompatibilities are fixed.
+
+In these situations, Pigweed's commit queue submission process will fail for all
+changes. If a change passes all presubmit checks except for known failures, the
+Pigweed team may permit manual submission of the CL. Contact the Pigweed team
+for submission approval.
+
+### Running local presubmits
+
+To speed up the review process, consider adding `pw presubmit` as a git push
+hook using the following command:
+
+**Linux/macOS**<br/>
+```bash
+$ pw presubmit --install
+```
+
+This will be effectively the same as running the following command before every
+`git push`:
+```bash
+$ pw presubmit
+```
+
+![pigweed presubmit demonstration](pw_presubmit/docs/pw_presubmit_demo.gif)
+
+If you ever need to bypass the presubmit hook (due to it being broken, for
+example) you may push using this command:
+
+```bash
+$ git push origin HEAD:refs/for/master --no-verify
+```
diff --git a/Kconfig.zephyr b/Kconfig.zephyr
deleted file mode 100644
index d0c43d0..0000000
--- a/Kconfig.zephyr
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-config ZEPHYR_PIGWEED_MODULE
-    select LIB_CPLUSPLUS
-    depends on STD_CPP17 || STD_CPP2A || STD_CPP20 || STD_CPP2B
-
-if ZEPHYR_PIGWEED_MODULE
-
-rsource "pw_assert_zephyr/Kconfig"
-rsource "pw_bytes/Kconfig"
-rsource "pw_checksum/Kconfig"
-rsource "pw_chrono_zephyr/Kconfig"
-rsource "pw_containers/Kconfig"
-rsource "pw_function/Kconfig"
-rsource "pw_hdlc/Kconfig"
-rsource "pw_interrupt_zephyr/Kconfig"
-rsource "pw_log_zephyr/Kconfig"
-rsource "pw_polyfill/Kconfig"
-rsource "pw_preprocessor/Kconfig"
-rsource "pw_result/Kconfig"
-rsource "pw_router/Kconfig"
-rsource "pw_rpc/Kconfig"
-rsource "pw_span/Kconfig"
-rsource "pw_status/Kconfig"
-rsource "pw_stream/Kconfig"
-rsource "pw_string/Kconfig"
-rsource "pw_sync_zephyr/Kconfig"
-rsource "pw_sys_io_zephyr/Kconfig"
-rsource "pw_varint/Kconfig"
-
-endif # ZEPHYR_PIGWEED_MODULE
diff --git a/OWNERS b/OWNERS
index c0c23f8..f584bdc 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,4 +1,8 @@
-# CHRE team maintainers in AOSP
-bduddie@google.com
-karthikmb@google.com
-stange@google.com
+amontanez@google.com
+davidrogers@google.com
+ewout@google.com
+frolv@google.com
+hepler@google.com
+jethier@google.com
+keir@google.com
+mohrr@google.com
diff --git a/PIGWEED_MODULES b/PIGWEED_MODULES
deleted file mode 100644
index dfb7f27..0000000
--- a/PIGWEED_MODULES
+++ /dev/null
@@ -1,120 +0,0 @@
-docker
-pw_allocator
-pw_analog
-pw_android_toolchain
-pw_arduino_build
-pw_assert
-pw_assert_basic
-pw_assert_log
-pw_assert_tokenized
-pw_assert_zephyr
-pw_base64
-pw_bloat
-pw_blob_store
-pw_bluetooth_hci
-pw_boot
-pw_boot_cortex_m
-pw_build
-pw_build_info
-pw_build_mcuxpresso
-pw_bytes
-pw_checksum
-pw_chrono
-pw_chrono_embos
-pw_chrono_freertos
-pw_chrono_stl
-pw_chrono_threadx
-pw_chrono_zephyr
-pw_cli
-pw_console
-pw_containers
-pw_cpu_exception
-pw_cpu_exception_cortex_m
-pw_crypto
-pw_docgen
-pw_doctor
-pw_env_setup
-pw_file
-pw_function
-pw_fuzzer
-pw_hdlc
-pw_hex_dump
-pw_i2c
-pw_i2c_mcuxpresso
-pw_interrupt
-pw_interrupt_cortex_m
-pw_interrupt_zephyr
-pw_kvs
-pw_libc
-pw_log
-pw_log_android
-pw_log_basic
-pw_log_null
-pw_log_rpc
-pw_log_string
-pw_log_tokenized
-pw_log_zephyr
-pw_malloc
-pw_malloc_freelist
-pw_metric
-pw_minimal_cpp_stdlib
-pw_module
-pw_multisink
-pw_package
-pw_persistent_ram
-pw_polyfill
-pw_preprocessor
-pw_presubmit
-pw_protobuf
-pw_protobuf_compiler
-pw_random
-pw_result
-pw_ring_buffer
-pw_router
-pw_rpc
-pw_snapshot
-pw_software_update
-pw_span
-pw_spi
-pw_status
-pw_stm32cube_build
-pw_stream
-pw_string
-pw_symbolizer
-pw_sync
-pw_sync_baremetal
-pw_sync_embos
-pw_sync_freertos
-pw_sync_stl
-pw_sync_threadx
-pw_sync_zephyr
-pw_sys_io
-pw_sys_io_arduino
-pw_sys_io_baremetal_lm3s6965evb
-pw_sys_io_baremetal_stm32f429
-pw_sys_io_emcraft_sf2
-pw_sys_io_mcuxpresso
-pw_sys_io_stdio
-pw_sys_io_stm32cube
-pw_sys_io_zephyr
-pw_system
-pw_target_runner
-pw_thread
-pw_thread_embos
-pw_thread_freertos
-pw_thread_stl
-pw_thread_threadx
-pw_tls_client
-pw_tls_client_boringssl
-pw_tls_client_mbedtls
-pw_tokenizer
-pw_tool
-pw_toolchain
-pw_trace
-pw_trace_tokenized
-pw_transfer
-pw_unit_test
-pw_varint
-pw_watch
-pw_web_ui
-pw_work_queue
diff --git a/PW_PLUGINS b/PW_PLUGINS
index a3047ae..b7e8fe4 100644
--- a/PW_PLUGINS
+++ b/PW_PLUGINS
@@ -16,4 +16,3 @@
 presubmit pw_presubmit.pigweed_presubmit main
 requires pw_cli.requires main
 rpc pw_hdlc.rpc_console main
-console pw_console.__main__ main
diff --git a/README.md b/README.md
index 8548ab4..9f73d55 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,142 @@
 # Pigweed
 
-Pigweed is an open source collection of embedded-targeted libraries–or as we
+Pigweed is an open source collection of embedded-targeted libraries--or as we
 like to call them, modules. These modules are building blocks and infrastructure
 that enable faster and more reliable development on small-footprint MMU-less
 32-bit microcontrollers like the STMicroelectronics STM32L452 or the Nordic
 nRF52832.
 
-For more information please see our website: https://pigweed.dev/
+Pigweed is in the early stages of development, **and should be considered
+experimental**. We’re continuing to evolve the platform and add new modules. We
+value developer feedback along the way.
 
-## Links
+# Quick links
 
-- [Documentation](https://pigweed.dev/)
-- [Source Code](https://cs.opensource.google/pigweed/pigweed)
-- [Code Reviews](https://pigweed-review.googlesource.com)
-- [Mailing List](https://groups.google.com/forum/#!forum/pigweed)
-- [Chat Room](https://discord.gg/M9NSeTA)
-- [Issue Tracker](https://bugs.pigweed.dev/)
+ - [Getting started guide](docs/getting_started.md)
+ - [Documentation](https://pigweed.dev)
+ - [Source code](https://cs.opensource.google/pigweed/pigweed)
+ - [Code reviews](https://pigweed-review.googlesource.com/)
+ - [Issue tracker](https://bugs.pigweed.dev/)
+ - [Mailing list](https://groups.google.com/forum/#!forum/pigweed)
+ - [Chat room (Discord)](https://discord.gg/M9NSeTA)
+ - [Open Source blog post](https://opensource.googleblog.com/2020/03/pigweed-collection-of-embedded-libraries.html)
 
+Get the code: `git clone https://pigweed.googlesource.com/pigweed/pigweed` (or
+[fork us on GitHub](https://github.com/google/pigweed)).
+
+# Getting Started
+
+If you'd like to get set up with Pigweed, please visit the
+[getting started guide](docs/getting_started.md).
+
+# What does Pigweed offer?
+
+There are many modules in Pigweed, and this section only showcases a small
+selection that happen to produce visual output. For more information about the
+different Pigweed module offerings, refer to "Module Guides" section in the full
+documentation.
+
+## `pw_watch` - Build, flash, run, & test on save
+
+In the web development space, file system watchers are prevalent. These watchers
+trigger a web server reload on source change, making development much faster. In
+the embedded space, file system watchers are less prevalent; however, they are
+no less useful! The Pigweed watcher module makes it easy to instantly compile,
+flash, and run tests upon save. Combined with the GN-based build which expresses
+the full dependency tree, only the exact tests affected by a file change are run
+on saves.
+
+The demo below shows `pw_watch` building for a STMicroelectronics
+STM32F429I-DISC1 development board, flashing the board with the affected test,
+and verifying the test runs as expected. Once this is set up, you can attach
+multiple devices to run tests in a distributed manner to reduce the time it
+takes to run tests.
+
+![pw watch running on-device tests](docs/images/pw_watch_on_device_demo.gif)
+
+## `pw_presubmit` - Vacuum code lint on every commit
+
+Presubmit checks are essential tools, but they take work to set up, and projects
+don’t always get around to it. The `pw_presubmit` module provides tools for
+setting up high quality presubmit checks for any project. We use this framework
+to run Pigweed’s presubmit on our workstations and in our automated building
+tools.
+
+The `pw_presubmit` module includes `pw format` command, a tool that provides a
+unified interface for automatically formatting code in a variety of languages.
+With `pw format`, you can format C, C++, Python, GN, and Go code according to
+configurations defined by your project. `pw format` leverages existing tools
+like `clang-format`, and it’s simple to add support for new languages.
+
+![pw presubmit demo](pw_presubmit/docs/pw_presubmit_demo.gif)
+
+## `pw_env_setup` - Cross platform embedded compiler setup
+
+A classic problem in the embedded space is reducing the time from git clone to
+having a binary executing on a device. The issue is that an entire suite of
+tools is needed for non-trivial production embedded projects. For example:
+
+ - A C++ compiler for your target device, and also for your host
+ - A build system or three; for example, GN, Ninja, CMake, Bazel
+ - A code formatting program like clang-format
+ - A debugger like OpenOCD to flash and debug your embedded device
+ - A known Python version with known modules installed for scripting
+ - A Go compiler for the Go-based command line tools
+ - ... and so on
+
+In the server space, container solutions like Docker or Podman solve this;
+however, in our experience container solutions are a mixed bag for embedded
+systems development where one frequently needs access to native system resources
+like USB devices, or must operate on Windows.
+
+`pw_env_setup` is our compromise solution for this problem that works on Mac,
+Windows, and Linux. It leverages the Chrome packaging system CIPD to bootstrap a
+Python installation, which in turn inflates a virtual environment. The tooling
+is installed into your workspace, and makes no changes to your system. This
+tooling is designed to be reused by any project.
+
+![pw environment setup demo](docs/images/pw_env_setup_demo.gif)
+
+## `pw_unit_test` - Embedded testing for MCUs
+
+Unit testing is important, and Pigweed offers a portable library that’s broadly
+compatible with [Google Test](https://github.com/google/googletest). Unlike
+Google Test, `pw_unit_test` is built on top of embedded friendly primitives; for
+example, it does not use dynamic memory allocation. Additionally, it is easy to
+port to new target platforms by implementing the
+[test event handler interface](https://pigweed.googlesource.com/pigweed/pigweed/+/refs/heads/master/pw_unit_test/public/pw_unit_test/event_handler.h).
+
+Like other modules in Pigweed, `pw_unit_test` is designed for use in
+established codebases with their own build system, without the rest of Pigweed
+or the Pigweed integrated GN build. However, when combined with Pigweed's
+build, the result is a flexible and powerful setup that enables easily
+developing code on your desktop (with tests), then running the same tests
+on-device.
+
+![pw_status test run natively on host](docs/images/pw_status_test.png)
+
+## And more!
+
+See the "Module Guides" in the documentation for the complete list and
+documentation for each, but is a selection of some others:
+
+ - `pw_cpu_exception_cortex_m`: Robust low level hardware fault handler for ARM
+   Cortex-M; the handler even has unit tests written in assembly to verify
+   nested-hardware-fault handling!
+
+ - `pw_polyfill`: Similar to JavaScript “polyfill” libraries, this module
+   provides selected C++17 standard library components that are compatible with
+   C++11 and C++14.
+
+ - `pw_tokenizer`: Replace string literals from log statements with 32-bit
+   tokens, to reduce flash use, reduce logging bandwidth, and save formatting
+   cycles from log statements at runtime.
+
+ - `pw_kvs`: A key-value-store implementation for flash-backed persistent
+   storage with integrated wear levelling. This is a lightweight alternative to
+   a file system for embedded devices.
+
+ - `pw_protobuf`: An early preview of our wire-format-oriented protocol buffer
+   implementation. This protobuf compiler makes a different set of
+   implementation tradeoffs than the most popular protocol buffer library in
+   this space, nanopb.
diff --git a/WORKSPACE b/WORKSPACE
index e95aa1b..573d3f2 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -17,163 +17,14 @@
     managed_directories = {"@npm": ["node_modules"]},
 )
 
+# Set up build_bazel_rules_nodejs.
 load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
 load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
-load("//pw_env_setup/bazel/cipd_setup:cipd_rules.bzl", "pigweed_deps")
 
-# Setup CIPD client and packages.
-# Required by: pigweed.
-# Used by modules: all.
-pigweed_deps()
-
-load("@cipd_deps//:cipd_init.bzl", "cipd_init")
-
-cipd_init()
-
-# Set up Python support.
-# Required by: rules_fuzzing, com_github_nanopb_nanopb.
-# Used in modules: None.
-http_archive(
-    name = "rules_python",
-    sha256 = "a30abdfc7126d497a7698c29c46ea9901c6392d6ed315171a6df5ce433aa4502",
-    strip_prefix = "rules_python-0.6.0",
-    url = "https://github.com/bazelbuild/rules_python/archive/0.6.0.tar.gz",
-)
-
-# Set up Starlark library.
-# Required by: io_bazel_rules_go, com_google_protobuf.
-# Used in modules: None.
-# This must be instantiated before com_google_protobuf as protobuf_deps() pulls
-# in an older version of bazel_skylib. However io_bazel_rules_go requires a
-# newer version.
-http_archive(
-    name = "bazel_skylib",
-    sha256 = "1c531376ac7e5a180e0237938a2536de0c54d93f5c278634818e0efc952dd56c",
-    urls = [
-        "https://github.com/bazelbuild/bazel-skylib/releases/download/1.0.3/bazel-skylib-1.0.3.tar.gz",
-        "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.0.3/bazel-skylib-1.0.3.tar.gz",
-    ],
-)
-
-load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
-
-bazel_skylib_workspace()
-
-# Set up upstream googletest and googlemock.
-# Required by: Pigweed.
-# Used in modules: //pw_analog, //pw_i2c.
-http_archive(
-    name = "com_google_googletest",
-    sha256 = "9dc9157a9a1551ec7a7e43daea9a694a0bb5fb8bec81235d8a1e6ef64c716dcb",
-    strip_prefix = "googletest-release-1.10.0",
-    urls = [
-        "https://mirror.bazel.build/github.com/google/googletest/archive/release-1.10.0.tar.gz",
-        "https://github.com/google/googletest/archive/release-1.10.0.tar.gz",
-    ],
-)
-
-# Set up host hermetic host toolchain.
-# Required by: All cc targets.
-# Used in modules: All cc targets.
-git_repository(
-    name = "rules_cc_toolchain",
-    commit = "80b51ba81f14eebe06684efa25261f6dc46e9b29",
-    remote = "https://github.com/bazelembedded/rules_cc_toolchain.git",
-)
-
-load("@rules_cc_toolchain//:rules_cc_toolchain_deps.bzl", "rules_cc_toolchain_deps")
-
-rules_cc_toolchain_deps()
-
-load("@rules_cc_toolchain//cc_toolchain:cc_toolchain.bzl", "register_cc_toolchains")
-
-register_cc_toolchains()
-
-# Set up Protobuf rules.
-# Required by: pigweed, com_github_bazelbuild_buildtools.
-# Used in modules: //pw_protobuf.
-http_archive(
-    name = "com_google_protobuf",
-    sha256 = "c6003e1d2e7fefa78a3039f19f383b4f3a61e81be8c19356f85b6461998ad3db",
-    strip_prefix = "protobuf-3.17.3",
-    url = "https://github.com/protocolbuffers/protobuf/archive/v3.17.3.tar.gz",
-)
-
-load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")
-
-protobuf_deps()
-
-# Set up tools to build custom GRPC rules.
-# Required by: pigweed.
-# Used in modules: //pw_protobuf.
-http_archive(
-    name = "rules_proto_grpc",
-    sha256 = "7954abbb6898830cd10ac9714fbcacf092299fda00ed2baf781172f545120419",
-    strip_prefix = "rules_proto_grpc-3.1.1",
-    urls = ["https://github.com/rules-proto-grpc/rules_proto_grpc/archive/3.1.1.tar.gz"],
-)
-
-load(
-    "@rules_proto_grpc//:repositories.bzl",
-    "rules_proto_grpc_repos",
-    "rules_proto_grpc_toolchains",
-)
-
-rules_proto_grpc_toolchains()
-
-rules_proto_grpc_repos()
-
-# Set up Protobuf rules.
-# Required by: pigweed, com_github_bazelbuild_buildtools.
-# Used in modules: //pw_protobuf.
-http_archive(
-    name = "com_google_protobuf",
-    sha256 = "c6003e1d2e7fefa78a3039f19f383b4f3a61e81be8c19356f85b6461998ad3db",
-    strip_prefix = "protobuf-3.17.3",
-    url = "https://github.com/protocolbuffers/protobuf/archive/v3.17.3.tar.gz",
-)
-
-load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")
-
-protobuf_deps()
-
-# Setup Nanopb protoc plugin.
-# Required by: Pigweed.
-# Used in modules: pw_protobuf.
-git_repository(
-    name = "com_github_nanopb_nanopb",
-    commit = "e601fca6d9ed7fb5c09e2732452753b2989f128b",
-    remote = "https://github.com/nanopb/nanopb.git",
-)
-
-load("@com_github_nanopb_nanopb//:nanopb_deps.bzl", "nanopb_deps")
-
-nanopb_deps()
-
-load("@com_github_nanopb_nanopb//:python_deps.bzl", "nanopb_python_deps")
-
-nanopb_python_deps()
-
-load("@com_github_nanopb_nanopb//:nanopb_workspace.bzl", "nanopb_workspace")
-
-nanopb_workspace()
-
-# Set up Bazel platforms.
-# Required by: pigweed.
-# Used in modules: //pw_build, (Assorted modules via select statements).
-git_repository(
-    name = "platforms",
-    commit = "d4c9d7f51a7c403814b60f66d20eeb425fbaaacb",
-    remote = "https://github.com/bazelbuild/platforms.git",
-)
-
-# Set up NodeJs rules.
-# Required by: pigweed.
-# Used in modules: //pw_web_ui.
 http_archive(
     name = "build_bazel_rules_nodejs",
-    sha256 = "b32a4713b45095e9e1921a7fcb1adf584bc05959f3336e7351bcf77f015a2d7c",
-    urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/4.1.0/rules_nodejs-4.1.0.tar.gz"],
+    sha256 = "4952ef879704ab4ad6729a29007e7094aef213ea79e9f2e94cbe1c9a753e63ef",
+    urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/2.2.0/rules_nodejs-2.2.0.tar.gz"],
 )
 
 # Get the latest LTS version of Node.
@@ -191,16 +42,15 @@
     yarn_lock = "//:yarn.lock",
 )
 
-# Set up web-testing rules.
-# Required by: pigweed.
-# Used in modules: //pw_web_ui.
-http_archive(
-    name = "io_bazel_rules_webtesting",
-    sha256 = "9bb461d5ef08e850025480bab185fd269242d4e533bca75bfb748001ceb343c3",
-    urls = ["https://github.com/bazelbuild/rules_webtesting/releases/download/0.3.3/rules_webtesting.tar.gz"],
-)
+# Set up Karma.
+load("@npm//@bazel/karma:package.bzl", "npm_bazel_karma_dependencies")
 
-load("@io_bazel_rules_webtesting//web:repositories.bzl", "web_test_repositories")
+npm_bazel_karma_dependencies()
+
+load(
+    "@io_bazel_rules_webtesting//web:repositories.bzl",
+    "web_test_repositories",
+)
 
 web_test_repositories()
 
@@ -214,13 +64,11 @@
     firefox = True,
 )
 
-# Set up embedded C/C++ toolchains.
-# Required by: pigweed.
-# Used in modules: //pw_polyfill, //pw_build (all pw_cc* targets).
+# Setup embedded C/C++ toolchains.
 git_repository(
     name = "bazel_embedded",
-    commit = "17c93d5fa52c4c78860b8bbd327325fba6c85555",
-    remote = "https://github.com/bazelembedded/bazel-embedded.git",
+    commit = "89c05fa415218abd2e24fa7016cb7903317d606b",
+    remote = "https://github.com/silvergasp/bazel-embedded.git",
 )
 
 # Instantiate Pigweed configuration for embedded toolchain,
@@ -263,116 +111,21 @@
 
 register_gcc_arm_none_toolchain()
 
-# Registers platforms for use with toolchain resolution
-register_execution_platforms("//pw_build/platforms:all")
+# Fetch LLVM/Clang compiler and register for toolchain resolution.
+load(
+    "@bazel_embedded//toolchains/compilers/llvm:llvm_repository.bzl",
+    "llvm_repository",
+)
 
-# Set up Golang toolchain rules.
-# Required by: bazel_gazelle, com_github_bazelbuild_buildtools.
-# Used in modules: None.
-http_archive(
-    name = "io_bazel_rules_go",
-    sha256 = "d1ffd055969c8f8d431e2d439813e42326961d0942bdf734d2c95dc30c369566",
-    urls = [
-        "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.24.5/rules_go-v0.24.5.tar.gz",
-        "https://github.com/bazelbuild/rules_go/releases/download/v0.24.5/rules_go-v0.24.5.tar.gz",
-    ],
+llvm_repository(
+    name = "com_llvm_compiler",
 )
 
 load(
-    "@io_bazel_rules_go//go:deps.bzl",
-    "go_register_toolchains",
-    "go_rules_dependencies",
+    "@bazel_embedded//toolchains/clang:clang_toolchain.bzl",
+    "register_clang_toolchain",
 )
 
-go_rules_dependencies()
+register_clang_toolchain()
 
-go_register_toolchains()
-
-# Set up bazel package manager for golang.
-# Required by: com_github_bazelbuild_buildtools.
-# Used in modules: None.
-http_archive(
-    name = "bazel_gazelle",
-    sha256 = "b85f48fa105c4403326e9525ad2b2cc437babaa6e15a3fc0b1dbab0ab064bc7c",
-    urls = [
-        "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.22.2/bazel-gazelle-v0.22.2.tar.gz",
-        "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.22.2/bazel-gazelle-v0.22.2.tar.gz",
-    ],
-)
-
-load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies")
-
-gazelle_dependencies()
-
-# Set up bazel buildtools (bazel linter and formatter).
-# Required by: pigweed.
-# Used in modules: //:all (bazel specific tools).
-http_archive(
-    name = "com_github_bazelbuild_buildtools",
-    sha256 = "c28eef4d30ba1a195c6837acf6c75a4034981f5b4002dda3c5aa6e48ce023cf1",
-    strip_prefix = "buildtools-4.0.1",
-    url = "https://github.com/bazelbuild/buildtools/archive/4.0.1.tar.gz",
-)
-
-load("//pw_build:target_config.bzl", "pigweed_config")
-
-# Configure Pigweeds backend.
-pigweed_config(
-    name = "pigweed_config",
-    build_file = "//targets:default_config.BUILD",
-)
-
-# Set up rules for fuzz testing.
-# Required by: pigweed.
-# Used in modules: //pw_protobuf, //pw_tokenizer, //pw_fuzzer.
-http_archive(
-    name = "rules_fuzzing",
-    sha256 = "94f25c7a18db0502ace26a3ef7d0a25fd7c195c4e9770ddd1b1ec718e8936091",
-    strip_prefix = "rules_fuzzing-0.1.3",
-    urls = ["https://github.com/bazelbuild/rules_fuzzing/archive/v0.1.3.zip"],
-)
-
-load("@rules_fuzzing//fuzzing:repositories.bzl", "rules_fuzzing_dependencies")
-
-rules_fuzzing_dependencies()
-
-load("@rules_fuzzing//fuzzing:init.bzl", "rules_fuzzing_init")
-
-rules_fuzzing_init()
-
-# esbuild setup
-load("@build_bazel_rules_nodejs//toolchains/esbuild:esbuild_repositories.bzl", "esbuild_repositories")
-
-esbuild_repositories(npm_repository = "npm")  # Note, npm is the default value for npm_repository
-
-RULES_JVM_EXTERNAL_TAG = "2.8"
-
-RULES_JVM_EXTERNAL_SHA = "79c9850690d7614ecdb72d68394f994fef7534b292c4867ce5e7dec0aa7bdfad"
-
-http_archive(
-    name = "rules_jvm_external",
-    sha256 = RULES_JVM_EXTERNAL_SHA,
-    strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG,
-    url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG,
-)
-
-load("@rules_jvm_external//:defs.bzl", "maven_install")
-
-# Pull in packages for the pw_rpc Java client with Maven.
-maven_install(
-    artifacts = [
-        "com.google.auto.value:auto-value:1.8.2",
-        "com.google.auto.value:auto-value-annotations:1.8.2",
-        "com.google.code.findbugs:jsr305:3.0.2",
-        "com.google.flogger:flogger:0.7.1",
-        "com.google.flogger:flogger-system-backend:0.7.1",
-        "com.google.guava:guava:31.0.1-jre",
-        "com.google.truth:truth:1.1.3",
-        "org.mockito:mockito-core:4.1.0",
-    ],
-    repositories = [
-        "https://maven.google.com/",
-        "https://jcenter.bintray.com/",
-        "https://repo1.maven.org/maven2",
-    ],
-)
+register_execution_platforms("//pw_build/platforms:all")
diff --git a/bootstrap.sh b/bootstrap.sh
index 75da8d9..5f75d90 100644
--- a/bootstrap.sh
+++ b/bootstrap.sh
@@ -15,11 +15,23 @@
 # This script must be tested on bash, zsh, and dash.
 
 _bootstrap_abspath () {
-  $(command -v python3 || command -v python2 || command -v python) -c "import os.path; print(os.path.abspath('$@'))"
+  $(which python || which python3 || which python2) -c "import os.path; print(os.path.abspath('$@'))"
 }
 
+# Users are not expected to set PW_CHECKOUT_ROOT, it's only used because it
+# seems to be impossible to reliably determine the path to a sourced file in
+# dash when sourced from a dash script instead of a dash interactive prompt.
+# To reinforce that users should not be using PW_CHECKOUT_ROOT, it is cleared
+# here after it is used, and other pw tools will complain if they see that
+# variable set.
+# TODO(mohrr) find out a way to do this without PW_CHECKOUT_ROOT.
+if test -n "$PW_CHECKOUT_ROOT"; then
+  _PW_BOOTSTRAP_PATH="$(_bootstrap_abspath "$PW_CHECKOUT_ROOT/bootstrap.sh")"
+  # Downstream projects need to set PW_CHECKOUT_ROOT to point to Pigweed if
+  # they're using Pigweed's CI/CQ system.
+  unset PW_CHECKOUT_ROOT
 # Shell: bash.
-if test -n "$BASH"; then
+elif test -n "$BASH"; then
   _PW_BOOTSTRAP_PATH="$(_bootstrap_abspath "$BASH_SOURCE")"
 # Shell: zsh.
 elif test -n "$ZSH_NAME"; then
@@ -77,7 +89,7 @@
 if [ "$(basename "$_PW_BOOTSTRAP_PATH")" = "bootstrap.sh" ] || \
   [ ! -f "$SETUP_SH" ] || \
   [ ! -s "$SETUP_SH" ]; then
-  pw_bootstrap --shell-file "$SETUP_SH" --install-dir "$_PW_ACTUAL_ENVIRONMENT_ROOT" --config-file "$PW_ROOT/pw_env_setup/config.json"
+  pw_bootstrap --shell-file "$SETUP_SH" --install-dir "$_PW_ACTUAL_ENVIRONMENT_ROOT" --json-file "$_PW_ACTUAL_ENVIRONMENT_ROOT/actions.json" --config-file "$PW_ROOT/pw_env_setup/config.json"
   pw_finalize bootstrap "$SETUP_SH"
 else
   pw_activate
diff --git a/build_overrides/pi_pico.gni b/build_overrides/pi_pico.gni
deleted file mode 100644
index e6343f6..0000000
--- a/build_overrides/pi_pico.gni
+++ /dev/null
@@ -1,23 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-declare_args() {
-  # Since the GN build lives in Pigweed for now, PICO_ROOT is
-  # Always relative to Pigweed's root.
-  PICO_ROOT = "${dir_pw_third_party}/pico_sdk"
-}
-
-import("${PICO_ROOT}/pi_pico.gni")
diff --git a/build_overrides/pigweed.gni b/build_overrides/pigweed.gni
index 04ef3b1..2c05787 100644
--- a/build_overrides/pigweed.gni
+++ b/build_overrides/pigweed.gni
@@ -17,5 +17,4 @@
   dir_pigweed = "//"
 }
 
-# Import modules.gni, which generates the modules list.
 import("$dir_pigweed/modules.gni")
diff --git a/docs/BUILD.bazel b/docs/BUILD
similarity index 100%
rename from docs/BUILD.bazel
rename to docs/BUILD
diff --git a/docs/BUILD.gn b/docs/BUILD.gn
index bd29906..3c1baae 100644
--- a/docs/BUILD.gn
+++ b/docs/BUILD.gn
@@ -14,11 +14,8 @@
 
 import("//build_overrides/pigweed.gni")
 
-import("$dir_pw_build/python.gni")
 import("$dir_pw_docgen/docs.gni")
 
-# Note: These may be useful for downstream projects, which is why they are
-# split out from the overall docgen target below.
 pw_doc_group("core_docs") {
   inputs = [
     "images/pw_env_setup_demo.gif",
@@ -27,89 +24,131 @@
     "images/pw_watch_on_device_demo.gif",
     "images/pw_watch_test_demo.gif",
     "images/stm32f429i-disc1_connected.jpg",
-
-    # TODO(pwbug/368): This should be in the pw_doc_gen target instead of here.
-    "_static/css/pigweed.css",
   ]
   sources = [
-    "code_of_conduct.rst",
-    "concepts/index.rst",
-    "contributing.rst",
     "embedded_cpp_guide.rst",
     "faq.rst",
-    "getting_started.rst",
+    "getting_started.md",
     "module_structure.rst",
-    "os_abstraction_layers.rst",
-    "size_optimizations.rst",
     "style_guide.rst",
   ]
 }
 
-pw_doc_group("release_notes") {
-  sources = [
-    "release_notes/2022_jan.rst",
-    "release_notes/index.rst",
-  ]
-}
-
 # Documentation for upstream Pigweed targets.
 group("target_docs") {
   deps = [
-    "$dir_pigweed/targets/android:target_docs",
     "$dir_pigweed/targets/arduino:target_docs",
     "$dir_pigweed/targets/docs:target_docs",
-    "$dir_pigweed/targets/emcraft_sf2_som:docs",
     "$dir_pigweed/targets/host:target_docs",
-    "$dir_pigweed/targets/host_device_simulator:target_docs",
-    "$dir_pigweed/targets/lm3s6965evb_qemu:target_docs",
-    "$dir_pigweed/targets/mimxrt595_evk:target_docs",
-    "$dir_pigweed/targets/rp2040:target_docs",
-    "$dir_pigweed/targets/stm32f429i_disc1:target_docs",
-    "$dir_pigweed/targets/stm32f429i_disc1_stm32cube:target_docs",
+    "$dir_pigweed/targets/lm3s6965evb-qemu:target_docs",
+    "$dir_pigweed/targets/stm32f429i-disc1:target_docs",
   ]
 }
 
 group("module_docs") {
-  deps = pw_module_docs
-}
-
-group("third_party_docs") {
   deps = [
-    "$dir_pigweed/third_party/freertos:docs",
-    "$dir_pigweed/third_party/nanopb:docs",
-    "$dir_pigweed/third_party/tinyusb:docs",
+    "$dir_docker:docs",
+    "$dir_pw_allocator:docs",
+    "$dir_pw_arduino_build:docs",
+    "$dir_pw_assert:docs",
+    "$dir_pw_assert_basic:docs",
+    "$dir_pw_assert_log:docs",
+    "$dir_pw_base64:docs",
+    "$dir_pw_bloat:docs",
+    "$dir_pw_blob_store:docs",
+    "$dir_pw_boot_armv7m:docs",
+    "$dir_pw_build:docs",
+    "$dir_pw_bytes:docs",
+    "$dir_pw_checksum:docs",
+    "$dir_pw_chrono:docs",
+    "$dir_pw_chrono_embos:docs",
+    "$dir_pw_chrono_freertos:docs",
+    "$dir_pw_chrono_stl:docs",
+    "$dir_pw_chrono_threadx:docs",
+    "$dir_pw_cli:docs",
+    "$dir_pw_containers:docs",
+    "$dir_pw_cpu_exception:docs",
+    "$dir_pw_cpu_exception_cortex_m:docs",
+    "$dir_pw_docgen:docs",
+    "$dir_pw_doctor:docs",
+    "$dir_pw_env_setup:docs",
+    "$dir_pw_fuzzer:docs",
+    "$dir_pw_hdlc:docs",
+    "$dir_pw_hex_dump:docs",
+    "$dir_pw_interrupt:docs",
+    "$dir_pw_interrupt_cortex_m:docs",
+    "$dir_pw_kvs:docs",
+    "$dir_pw_log:docs",
+    "$dir_pw_log_basic:docs",
+    "$dir_pw_log_multisink:docs",
+    "$dir_pw_log_null:docs",
+    "$dir_pw_log_rpc:docs",
+    "$dir_pw_log_sink:docs",
+    "$dir_pw_log_tokenized:docs",
+    "$dir_pw_metric:docs",
+    "$dir_pw_minimal_cpp_stdlib:docs",
+    "$dir_pw_module:docs",
+    "$dir_pw_multisink:docs",
+    "$dir_pw_package:docs",
+    "$dir_pw_persistent_ram:docs",
+    "$dir_pw_polyfill:docs",
+    "$dir_pw_preprocessor:docs",
+    "$dir_pw_presubmit:docs",
+    "$dir_pw_protobuf:docs",
+    "$dir_pw_protobuf_compiler:docs",
+    "$dir_pw_random:docs",
+    "$dir_pw_result:docs",
+    "$dir_pw_ring_buffer:docs",
+    "$dir_pw_router:docs",
+    "$dir_pw_rpc:docs",
+    "$dir_pw_span:docs",
+    "$dir_pw_status:docs",
+    "$dir_pw_stream:docs",
+    "$dir_pw_string:docs",
+    "$dir_pw_sync:docs",
+    "$dir_pw_sync_baremetal:docs",
+    "$dir_pw_sync_embos:docs",
+    "$dir_pw_sync_freertos:docs",
+    "$dir_pw_sync_stl:docs",
+    "$dir_pw_sync_threadx:docs",
+    "$dir_pw_sys_io:docs",
+    "$dir_pw_sys_io_arduino:docs",
+    "$dir_pw_sys_io_baremetal_stm32f429:docs",
+    "$dir_pw_sys_io_stdio:docs",
+    "$dir_pw_target_runner:docs",
+    "$dir_pw_thread:docs",
+    "$dir_pw_thread_embos:docs",
+    "$dir_pw_thread_freertos:docs",
+    "$dir_pw_thread_stl:docs",
+    "$dir_pw_thread_threadx:docs",
+    "$dir_pw_tokenizer:docs",
+    "$dir_pw_toolchain:docs",
+    "$dir_pw_trace:docs",
+    "$dir_pw_trace_tokenized:docs",
+    "$dir_pw_unit_test:docs",
+    "$dir_pw_varint:docs",
+    "$dir_pw_watch:docs",
+    "$dir_pw_web_ui:docs",
   ]
 }
 
 pw_doc_gen("docs") {
   conf = "conf.py"
   sources = [
-    # Note: These must use the "docs" prefix for links and image references. In
-    # contrast, the pw_doc_group above should not use the docs prefix.
-    "automated_analysis.rst",
+    "../CODE_OF_CONDUCT.md",
+    "../CONTRIBUTING.md",
+    "../README.md",
     "build_system.rst",
     "index.rst",
     "module_guides.rst",
     "python_build.rst",
     "targets.rst",
-    "third_party_support.rst",
   ]
   output_directory = target_gen_dir
   deps = [
     ":core_docs",
     ":module_docs",
-    ":release_notes",
-    ":sphinx_themes.install",
     ":target_docs",
-    ":third_party_docs",
     "$dir_pw_env_setup:python.install",
   ]
 }
-
-# Install Pigweed specific sphinx themes.
-pw_python_requirements("sphinx_themes") {
-  requirements = [
-    "furo",
-    "sphinx_design",
-  ]
-}
diff --git a/docs/_static/css/pigweed.css b/docs/_static/css/pigweed.css
deleted file mode 100644
index 5e87caf..0000000
--- a/docs/_static/css/pigweed.css
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright 2021 The Pigweed Authors
- *
- * 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
- *
- *     https://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.
- */
-
-/********** Top left logo & search bar ***********/
-
-/* Make the "Pigweed" logo text. One day, this will be an image. */
-.sidebar-brand {
-  font-size: 2em;
-  font-family: 'Inconsolata', monospace;
-  font-weight: bold;
-  letter-spacing: 0.1em;
-  text-transform: uppercase;
-}
-.sidebar-brand-text {
-    font-size: 2.5rem;
-}
-
-/********** General document coloring ***********/
-
-/* Code blocks inside block quotes end up getting italicized. */
-blockquote {
-  font-style: normal;
-}
-
-/* Update description list (dl) > description titles (dt) styles for autodoc */
-
-/* Function signatures are gray */
-dl.c>dt, dl.cpp>dt, dl.js>dt, dl.py>dt {
-  border: none;
-  border-left: 3px solid var(--color-api-function-border);
-  background: var(--color-api-function-background);
-  padding-top: 6px;
-  padding-bottom: 6px;
-  padding-left: 0.5em;
-  font-family: var(--font-stack--monospace);
-}
-
-/* Adjust indentation for all list types */
-dl.class>dt, dl.enum-class>dt, dl.enum-struct>dt, dl.enum>dt, dl.exception>dt, dl.function>dt, dl.method>dt, dl.type>dt {
-  padding-left: 3em;
-  text-indent: -2.5em;
-}
-
-/* Class and describe signatures are blue */
-dl.class>dt, dl.describe>dt {
-  background: var(--color-api-class-background);
-  color: var(--color-api-class-foreground);
-  border: none;
-  border-top: 3px solid var(--color-api-class-border);
-  font-family: var(--font-stack--monospace);
-}
-
-/* Adjust .. describe:: block padding */
-dl.describe>dt {
-  display: table;
-  padding-top: 6px;
-  padding-bottom: 6px;
-  padding-left: 0.5em;
-  padding-right: 0.5em;
-}
-
-/* Function signature unindent */
-.sig {
-    text-indent: 0em;
-}
-
-/* Diagram backgrounds should always be light */
-div>svg {
-  background: #ffffff;
-}
-
-/* Make inline code standout more */
-code.literal {
-  border: 1px solid var(--color-inline-code-border);
-}
-
-/* Make sure text selection colors are readable */
-::selection {
-  color: var(--color-text-selection-foreground);
-  background: var(--color-text-selection-background);
-}
-
-/* Use normal mixed-case for h4, h5, and h6 */
-h4, h5, h6 {
-    text-transform: none;
-}
-
-:root {
-  /* SVGs from: https://octicons-git-v2-primer.vercel.app/octicons/ */
-  --icon--check-circle: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0zM0 8a8 8 0 1116 0A8 8 0 010 8zm11.78-1.72a.75.75 0 00-1.06-1.06L6.75 9.19 5.28 7.72a.75.75 0 00-1.06 1.06l2 2a.75.75 0 001.06 0l4.5-4.5z"></path></svg>');
-  --icon--check-circle-fill: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 16A8 8 0 108 0a8 8 0 000 16zm3.78-9.72a.75.75 0 00-1.06-1.06L6.75 9.19 5.28 7.72a.75.75 0 00-1.06 1.06l2 2a.75.75 0 001.06 0l4.5-4.5z"></path></svg>');
-}
-
-.admonition.checkmark {
-  border-color: var(--color-admonition-title--tip);
-}
-.admonition.checkmark > .admonition-title {
-  background-color: var(--color-admonition-title-background--tip);
-}
-.admonition.checkmark > .admonition-title::before {
-  background-color: var(--color-admonition-title--tip);
-  -webkit-mask-image: var(--icon--check-circle-fill);
-          mask-image: var(--icon--check-circle-fill);
-}
diff --git a/docs/automated_analysis.rst b/docs/automated_analysis.rst
deleted file mode 100644
index 23a3178..0000000
--- a/docs/automated_analysis.rst
+++ /dev/null
@@ -1,218 +0,0 @@
-.. _docs-automated-analysis:
-
-==================
-Automated analysis
-==================
-
-The correctness and style of Pigweed's source code is continuously verified
-using a suite of automated tools. We also make it easy to use the same tools
-to verify the code of projects using Pigweed.
-
--------
-Summary
--------
-On presubmit or in CI we verify Pigweed using:
-
-* pylint
-* mypy
-* clang-tidy
-* AddressSanitizer (asan)
-* ThreadSanitizer (tsan)
-* UndefinedBehaviorSanitizer (ubsan)
-* OSS-Fuzz
-
-The rest of this document discusses these tools and their configuration in
-greater detail, and how to use them in your own project.
-
---------------
-Analysis tools
---------------
-
-Static analysis
-===============
-
-PyLint
-------
-`PyLint`_ is a customizable Python linter. Pigweed complies with almost all
-the default checks; see `.pylintrc`_ for details. PyLint detects problems such
-as overly broad catch statements, unused arguments/variables, and mutable
-default parameter values.
-
-For upstream Pigweed, PyLint can be run with ``ninja python.lint.pylint`` or
-``ninja python.lint``.  It's also included in a variety of presubmit steps,
-like ``static_analysis`` and ``python_checks.gn_python_check``.  See the
-`Enabling analysis for your project`_ section to learn how to run PyLint on
-your Pigweed-based project.
-
-.. _PyLint: https://pylint.org/
-.. _.pylintrc: https://cs.opensource.google/pigweed/pigweed/+/main:.pylintrc
-
-Mypy
-----
-Python 3 allows for `type annotations`_ for variables, function arguments, and
-return values. Most, but not all, of Pigweed's Python code has type
-annotations, and these annotations have caught real bugs in code that didn't
-yet have unit tests. `Mypy`_ is an analysis tool that enforces these
-annotations.
-
-Mypy helps find bugs like when a string is passed into a function that expects
-a list of strings---since both are iterables this bug might otherwise be hard
-to track down.
-
-Mypy can be run with ``ninja python.lint.mypy`` or ``ninja python.lint``. It's
-also included in a variety of presubmit steps, like ``static_analysis`` and
-``python_checks.gn_python_check``.
-
-.. _type annotations: https://docs.python.org/3/library/typing.html
-.. _Mypy: http://mypy-lang.org/
-
-clang-tidy
-----------
-`clang-tidy`_ is a C++ "linter" and static analysis tool. It identifies
-bug-prone patterns (e.g., use after move), non-idiomatic usage (e.g., creating
-``std::unique_ptr`` with ``new`` rather than ``std::make_unique``), and
-performance issues (e.g., unnecessary copies of loop variables).
-
-While powerful, clang-tidy defines a very large number of checks, many of which
-are special-purpose (e.g., only applicable to FPGA HLS code, or code using the
-`Abseil`_ library) or have high false positive rates. Pigweed enables over 50
-checks which are relevant to an embedded C/C++ library and have good
-signal-to-noise ratios. The full list of Pigweed's checks is in `.clang-tidy`_.
-
-We do not currently enable the `Clang Static Analyzers`_ because they suffer
-from false positives, and their findings are time-consuming to manually verify.
-
-clang-tidy can be run with ``ninja static_analysis`` or ``pw presubmit --step
-static_analysis``. Note that as a static analysis tool, clang-tidy will not
-produce any runnable binaries: it simply analyzes the source files.
-
-.. _clang-tidy: https://clang.llvm.org/extra/clang-tidy/
-.. _Abseil: https://abseil.io/
-.. _.clang-tidy: https://cs.opensource.google/pigweed/pigweed/+/main:.clang-tidy
-.. _Clang Static Analyzers: https://clang-analyzer.llvm.org/available_checks.html
-
-
-Clang sanitizers
-================
-We run all of Pigweed's unit tests with the additional instrumentation
-described in this section. For more detail about these sanitizers, see the
-`Github documentation`_.
-
-* asan: `AddressSanitizer`_ detects memory errors such as out-of-bounds access
-  and use-after-free.
-* msan: `MemorySanitizer`_ detects reads of uninitialized memory.
-* tsan: `ThreadSanitizer`_ detects data races.
-* ubsan: `UndefinedBehaviorSanitizer`_ is a fast undefined behavior detector.
-  We use the default ``-fsanitize=undefined`` option.
-
-.. note::
-   Pigweed does not currently support msan. See https://bugs.pigweed.dev/560
-   for details.
-
-The exact configurations we use for these sanitizers are in
-`pw_toolchain/host_clang/BUILD.gn <https://cs.opensource.google/pigweed/pigweed/+/main:pw_toolchain/host_clang/BUILD.gn>`_.
-You can see the current status of the sanitizer builds in the `Pigweed CI
-console`_, as ``pigweed-linux-san-*``.
-
-Unlike clang-tidy, the clang sanitizers are runtime instrumentation: the
-instrumented binary needs to be run for issues to be detected.
-
-.. _Github documentation: https://github.com/google/sanitizers
-.. _AddressSanitizer: https://clang.llvm.org/docs/AddressSanitizer.html
-.. _MemorySanitizer: https://clang.llvm.org/docs/MemorySanitizer.html
-.. _Pigweed CI console: https://ci.chromium.org/p/pigweed/g/pigweed/console
-.. _ThreadSanitizer: https://clang.llvm.org/docs/ThreadSanitizer.html
-.. _UndefinedBehaviorSanitizer: https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html
-
-
-Fuzzers
-=======
-`Fuzz testing`_ detects errors in software by providing it with randomly
-generated inputs.  We use `OSS-fuzz`_ to continuously uncover potential
-vulnerabilities in Pigweed.  `Dashboard with Pigweed's latest results`_. See
-the :ref:`module-pw_fuzzer` module documentation for more details.
-
-.. _Dashboard with Pigweed's latest results: https://oss-fuzz-build-logs.storage.googleapis.com/index.html#pigweed
-.. _Fuzz testing: https://en.wikipedia.org/wiki/Fuzzing
-.. _OSS-fuzz: https://github.com/google/oss-fuzz
-
-.. _Enabling analysis for your project:
-
-----------------------------------
-Enabling analysis for your project
-----------------------------------
-
-PyLint and Mypy
-===============
-PyLint and Mypy can be configured to run every time your project is built by
-adding ``python.lint`` to your default build group. (You can also add one or both
-individually using ``python.lint.mypy`` and ``python.lint.pylint``.) Likewise,
-these can be added to individual presubmit steps (`examples`_). You can also
-directly include the `python_checks.gn_python_lint`_ presubmit step.
-
-.. _examples: https://cs.opensource.google/search?q=file:pigweed_presubmit.py%20%22python.lint%22&sq=&ss=pigweed%2Fpigweed
-.. _python_checks.gn_python_lint: https://cs.opensource.google/pigweed/pigweed/+/main:pw_presubmit/py/pw_presubmit/python_checks.py?q=file:python_checks.py%20gn_python_lint&ss=pigweed%2Fpigweed
-
-clang-tidy
-==========
-`pw_toolchain/static_analysis_toolchain.gni`_ provides the
-``pw_static_analysis_toolchain`` template that can be used to create a build
-group performing static analysis. See :ref:`module-pw_toolchain` documentation
-for more details. This group can then be added as a presubmit step using
-pw_presubmit.
-
-You can place a ``.clang-tidy`` file at the root of your repository to control
-which checks are executed. See the `clang documentation`_ for a discussion of how
-the tool chooses which ``.clang-tidy`` files to apply when run on a particular
-source file.
-
-.. _pw_toolchain/static_analysis_toolchain.gni: https://cs.opensource.google/pigweed/pigweed/+/main:pw_toolchain/static_analysis_toolchain.gni
-.. _clang documentation: https://clang.llvm.org/extra/clang-tidy/
-
-Clang sanitizers
-================
-There are two ways to enable sanitizers for your build.
-
-GN args on debug toolchains
----------------------------
-If you are already building your tests with one of the following toolchains (or
-a toolchain derived from one of them):
-
-* ``pw_toolchain_host_clang.debug``
-* ``pw_toolchain_host_clang.speed_optimized``
-* ``pw_toolchain_host_clang.size_optimized``
-
-you can enable the clang sanitizers simply by setting the gn arg
-``pw_toolchain_SANITIZERS`` to the desired subset of
-``["address", "thread", "undefined"]``.
-
-Example
-^^^^^^^
-If your project defines a toolchain ``host_clang_debug`` that is derived from
-one of the above toolchains, and you'd like to run the ``pw_executable`` target
-``sample_binary`` defined in the ``BUILD.gn`` file in ``examples/sample`` with
-asan, you would run,
-
-.. code-block:: bash
-
-    gn gen out --args='pw_toolchain_SANITIZERS=["address"]'
-    ninja -C out host_clang_debug/obj/example/sample/bin/sample_binary
-    out/host_clang_debug/obj/example/sample/bin/sample_binary
-
-Sanitizer toolchains
---------------------
-Otherwise, instead of using ``gn args`` you can build your tests with the
-appropriate toolchain from the following list (or a toolchain derived from one
-of them):
-
-* ``pw_toolchain_host_clang.asan``
-* ``pw_toolchain_host_clang.ubsan``
-* ``pw_toolchain_host_clang.tsan``
-
-See the :ref:`module-pw_toolchain` module documentation for more
-about Pigweed toolchains.
-
-Fuzzers
-=======
-See the :ref:`module-pw_fuzzer` module documentation.
-
diff --git a/docs/build_system.rst b/docs/build_system.rst
index 6cd32a5..59c638b 100644
--- a/docs/build_system.rst
+++ b/docs/build_system.rst
@@ -154,14 +154,19 @@
 Of the supported build systems, GN is the most full-featured, followed by CMake,
 and finally Bazel.
 
-.. note::
-  A quick note on terminology: the word "target" is overloaded within GN/Bazel (and
-  Pigweed)---it can refer to either a GN/Bazel build target, such as a ``source_set``
-  or ``executable``, or to an output platform (e.g. a specific board, device, or
-  system).
+CMake
+-----
+A well-known name in C/C++ development, `CMake`_ is widely used by all kinds of
+projects, including embedded devices. Pigweed's CMake support is provided
+primarily for projects that have an existing CMake build and wish to integrate
+Pigweed modules.
 
-  To avoid confusing the two, we refer to the former as "GN/Bazel targets" and the
-  latter as "Pigweed targets".
+Bazel
+-----
+The open source version of Google's internal build system. `Bazel`_ has been
+growing in popularity within the open source world, as well as being adopted by
+various proprietary projects. Its modular structure makes it a great fit for
+à la carte usage.
 
 GN
 --
@@ -185,7 +190,16 @@
 intended to be a guide on how to use GN. To learn more about the tool itself,
 refer to the official `GN reference`_.
 
-.. _GN reference: https://gn.googlesource.com/gn/+/HEAD/docs/reference.md
+.. _GN reference: https://gn.googlesource.com/gn/+/master/docs/reference.md
+
+.. note::
+  A quick note on terminology: the word "target" is overloaded within GN (and
+  Pigweed)---it can refer to either a GN build target, such as a ``source_set``
+  or ``executable``, or to an output platform (e.g. a specific board, device, or
+  system).
+
+  To avoid confusing the two, we refer to the former as "GN targets" and the
+  latter as "Pigweed targets".
 
 Entrypoint: .gn
 ---------------
@@ -289,7 +303,7 @@
   }
 
 .. warning::
-  Pigweed's default toolchain is never used, so it is set to an empty toolchain
+  Pigweed's default toolchain is never used, so it is set to a dummy toolchain
   which doesn't define any tools. ``//BUILD.gn`` contains conditions which check
   that the current toolchain is not the default before declaring any GN target
   dependencies to prevent the default toolchain from evaluating any other BUILD
@@ -328,13 +342,6 @@
 which are described below. In order to build a GN target, it *must* be listed in
 one of the groups in this file.
 
-.. important::
-
-  Pigweed's top-level ``BUILD.gn`` file should not be used by downstream
-  projects. Projects that wish to pull all of Pigweed's code into their build
-  may use the ``pw_modules`` and ``pw_module_tests`` variables in
-  ``modules.gni``.
-
 apps
 ~~~~
 This group defines the application images built in Pigweed. It lists all of the
@@ -347,48 +354,14 @@
 ~~~~~~~~~~
 This group defines host-side tooling binaries built for Pigweed.
 
-runtime_sanitizers
-~~~~~~~~~~~~~~~~~~
-This group defines host-side build targets for Clang runtime sanitizers.
-Next runtime sanitizers supported:
-
-* ``asan`` -- `AddressSanitizer`_ is a fast memory error detector.
-* ``msan`` -- `MemorySanitizer`_ is a detector of uninitialized reads.
-* ``ubsan`` -- `UndefinedBehaviorSanitizer`_ is a fast undefined behavior detector.
-* ``ubsan_heuristic`` -- `UndefinedBehaviorSanitizer`_ with the following
-  additional checks enabled:
-
-   * ``integer``: Checks for undefined or suspicious integer behavior.
-   * ``float-divide-by-zero``: Checks for floating point division by zero.
-   * ``implicit-conversion"``: Checks for suspicious behavior of implicit conversions.
-   * ``nullability``: Checks for null as function arg, lvalue and return type.
-
-  These additional checks are heuristic and may not correspond to undefined
-  behavior.
-* ``tsan`` -- `ThreadSanitizer`_ is a tool that detects data races.
-
-Results of building this group are ``host_clang_<sanitizer>`` build directories
-with ``pw_module_tests`` per supported sanitizer.
-
-.. _AddressSanitizer: https://clang.llvm.org/docs/AddressSanitizer.html
-.. _MemorySanitizer: https://clang.llvm.org/docs/MemorySanitizer.html
-.. _UndefinedBehaviorSanitizer: https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html
-.. _ThreadSanitizer: https://clang.llvm.org/docs/ThreadSanitizer.html
-
 pw_modules
 ~~~~~~~~~~
 This group lists the main libraries for all of Pigweed's modules.
 
-The modules in the ``pw_modules`` group are listed in the ``pw_modules``
-variable, which is provided by ``modules.gni``.
-
 pw_module_tests
 ~~~~~~~~~~~~~~~
 All modules' unit tests are collected here, so that they can all be run at once.
 
-The test groups in ``pw_module_tests`` group are listed in the
-``pw_module_tests`` variable, which is provided by ``modules.gni``.
-
 pigweed_default
 ~~~~~~~~~~~~~~~
 This group defines everything built in a Pigweed build invocation by collecting
@@ -452,15 +425,15 @@
 build. For information on Pigweed's target system, refer to
 :ref:`docs-targets`.
 
-The empty toolchain
+The dummy toolchain
 -------------------
-Pigweed's ``BUILDCONFIG.gn`` sets the project's default toolchain to a "empty"
+Pigweed's ``BUILDCONFIG.gn`` sets the project's default toolchain to a "dummy"
 toolchain which does not specify any compilers or override any build arguments.
 Downstream projects are recommended to do the same, following the steps
 described in :ref:`top-level-build` to configure builds for each of their
 Pigweed targets.
 
-.. admonition:: Why use an empty toolchain?
+.. admonition:: Why use a dummy?
 
   To support some of its advanced (and useful!) build features, Pigweed requires
   the ability to generate new toolchains on the fly. This requires having
@@ -550,527 +523,3 @@
    .. code::
 
      out/stm32f429i_disc1_debug/obj/foo/bin/foo
-
-CMake
------
-A well-known name in C/C++ development, `CMake`_ is widely used by all kinds of
-projects, including embedded devices. Pigweed's CMake support is provided
-primarily for projects that have an existing CMake build and wish to integrate
-Pigweed modules.
-
-Bazel
------
-The open source version of Google's internal build system. `Bazel`_ has been
-growing in popularity within the open source world, as well as being adopted by
-various proprietary projects. Its modular structure makes it a great fit for
-à la carte usage.
-
-.. note::
-  Bazel support is experimental and only for the brave for now. If you are
-  looking for stable set of build API's please use GN.
-
-The Bazel build
-===============
-This section describes Pigweed's Bazel build structure, how it is used upstream,
-build conventions, and recommendations for Pigweed-based projects. While
-containing some details about how Bazel works in general, this section is not
-intended to be a guide on how to use Bazel. To learn more about the tool itself,
-refer to the official `Bazel reference`_.
-
-.. _Bazel reference: https://www.bazel.build/
-
-General usage
--------------
-While described in more detail in the Bazel docs there a few Bazel features that
-are of particular importance when targeting embedded platforms. The most
-commonly used commands used in bazel are;
-
-.. code:: sh
-
-  bazel build //your:target
-  bazel test //your:target
-  bazel coverage //your:target
-
-.. note:: Code coverage support is only available on the host for now.
-
-Building
-^^^^^^^^
-When it comes to building/testing your Bazel target for a specific Pigweed
-target (e.g. stm32f429i-discovery) a slight variation is required.
-
-.. code:: sh
-
-  bazel build //your:target \
-    --platforms=@pigweed//pw_build/platforms:stm32f429i-disc1
-
-For more information on how to create your own platforms refer to the official
-`Bazel platforms reference`_. You may also find helpful examples of constraints
-and platforms in the '//pw_build/platforms' and '//pw_build/constraints'
-directories.
-
-.. _Bazel platforms reference: https://docs.bazel.build/versions/main/platforms.html
-
-Testing
-^^^^^^^
-Running tests on an embedded target with Bazel is possible although support for
-this is experimental. The easiest way of achieving this at the moment is to use
-Bazel's '--run_under' flag. To make this work create a Bazel target
-('//your_handler') that;
-
-1. Takes a single argument (the path to the elf) and uploads the elf to your
-   Pigweed target.
-2. Connects with your target using serial or other communication method.
-3. Listens to the communication transport for the keywords ("PASSED", "FAIL")
-   and returns (0, 1) respectively if one of the keywords is intercepted. (This
-   step assumes you are using the pw_unit_test package and it is configured for
-   your target).
-4. Run;
-
-   .. code:: sh
-
-    bazel test //your:test --platforms=//your/platform --run_under=//your_handler
-
-Code Coverage
-^^^^^^^^^^^^^
-Making use of the code coverage functionality in Bazel is straightforward.
-
-1. Add the following lines to your '.bazelrc'.
-
-  .. code:: sh
-
-    coverage --experimental_generate_llvm_lcov
-    coverage --combined_report=lcov
-
-2. Generate a combined lcov coverage report. This will produce a combined lcov
-   coverage report at the path 'bazel-out/_coverage/_coverage_report.dat'. e.g.
-
-  .. code:: sh
-
-    bazel coverage //pw_log/...
-
-3. View the results using the command line utility 'lcov'.
-
-  .. code:: sh
-
-    lcov --list bazel-out/_coverage/_coverage_report.dat
-
-Configuration
--------------
-Generally speaking there are three primary concepts that make up Bazel's
-configuration API.
-
-1. Selects
-2. Compatibility lists
-3. Flags/Build settings
-
-Selects
-^^^^^^^
-Selects are useful for specifying different dependencies/source depending on the
-platform that is currently being targeted. For more information on this please
-see the `Bazel selects reference`_. e.g.
-
-.. code:: py
-
-  pw_cc_library(
-    name = "some_platform_dependant_library",
-    deps = select({
-      "@platforms//cpu:armv7e-m": [":arm_libs"],
-      "//conditions:default": [":host_libs"],
-    }),
-  )
-
-Compatibility lists
-^^^^^^^^^^^^^^^^^^^
-Compatibility lists allow you to specify which platforms your targets are
-compatible with. Consider an example where you want to specify that a target is
-compatible with only a host os;
-
-.. code:: py
-
-  pw_cc_library(
-    name = "some_host_only_lib",
-    srcs = ["host.cc"],
-    target_compatible_with = select({
-      "@platforms//os:windows": [],
-      "@platforms//os:linux": [],
-      "@platforms//os:macos": [],
-      "//conditions:default": ["@platforms//:incompatible"],
-    }),
-  )
-
-In this case building from or for either Windows/Linux/Mac will be supported, but
-other OS's will fail if this target is explicitly depended on. However if
-building with a wild card for a non-host platform this target will be skipped
-and the build will continue. e.g.
-
-.. code:: sh
-
-  bazel build //... --platforms=@pigweed//pw_build/platforms:cortex_m0
-
-This allows for you to easily create compatibility matricies without adversely
-affecting your ability build your entire repo for a given Pigweed target.
-For more detailed information on how to use the target_compatible_with attribute
-please see `Bazel target_compatible_with reference`_.
-
-Flags/build settings
-^^^^^^^^^^^^^^^^^^^^
-Flags/build settings are particularly useful in scenarios where you may want to
-be able to quickly inject a dependency from the command line but don't
-necessarily want to create an entirely new set of constraints to use with a
-select statement.
-
-.. note::
-  The scope for what is possible with build flags/settings goes well beyond
-  what will be described here. For more detailed information on flags/settings
-  please see `Bazel config reference`_.
-
-A simple example of when it is useful to use a label_flag is when you want to
-swap out a single dependency from the command line. e.g.
-
-.. code:: py
-
-  pw_cc_library(
-    name = "some_default_io",
-    srcs = ["default_io.cc"],
-  )
-
-  pw_cc_library(
-    name = "some_other_io",
-    srcs = ["other_io.cc"],
-  )
-
-  label_flag(
-    name = "io",
-    default_build_setting = ":some_default_io",
-  )
-
-  pw_cc_library(
-    name = "some_target_that_needs_io",
-    deps = [":io"],
-  )
-
-From here the label_flag by default redirects to the target ":some_default_io",
-however it is possible to override this from the command line. e.g.
-
-.. code:: sh
-
-  bazel build //:some_target_that_needs_io --//:io=//:some_other_io
-
-
-
-.. _Bazel selects reference: https://docs.bazel.build/versions/main/configurable-attributes.html#select-and-dependencies
-
-.. _Bazel target_compatible_with reference: https://docs.bazel.build/versions/main/platforms.html#skipping-incompatible-targets
-
-.. _Bazel config reference: https://docs.bazel.build/versions/main/skylark/config.html
-
-
-Pigweeds configuration
-^^^^^^^^^^^^^^^^^^^^^^
-Pigweeds Bazel configuration API is designed to be distributed across the
-Pigweed repository and/or your downstream repository. If you are coming from
-GN's centralized configuration API it might be useful to think about
-Pigweed+Bazel's configuration as the transpose of GN's configuration. The
-configuration interface that is supported by Pigweed is designed to start simple
-and then grow with your project.
-
-.. note::
-  There are plans to extend the configuration API for Bazel. However,
-  currently the only configurations that are available under the Bazel+Pigweed
-  configuration API is the ability to switch facade backends. For more
-  information on what this is please see the
-  :ref:`docs-module-structure-facades` section of :ref:`docs-module-structure`.
-
-Consider a scenario that you are building a flight controller for a
-spacecraft. But have very little experience with Pigweed and you have just
-landed here. First things first you would;
-
-1. Set up your WORKSPACE to fetch the Pigweeds repository. Then add the
-   dependencies that you need from Pigweeds WORKSPACE.
-
-2. Add a pigweed_config rule to your WORKSPACE, using Pigweed's default
-   configuration.
-
-  .. code:: py
-
-    # WORKSPACE ...
-    load("//pw_build:target_config.bzl", "pigweed_config")
-
-    # Configure Pigweeds backend.
-    pigweed_config(
-        name = "pigweed_config",
-        build_file = "@pigweed//targets:default_config.BUILD",
-    )
-
-.. note::
-  We are aware, that the experience of setting up your WORKSPACE file to work
-  with pigweed is less than ideal. This API is under construction, but we are
-  working on this!
-
-..
-  TODO: Add in a better way to pull down WORKSPACE dependencies in Bazel then
-  add docs in here.
-
-Now to explain what is going on here. Housed under the "pigweed_config" remote
-repository is a set of configuration flags. These can be used to inject
-dependencies into facades to override the default backend.
-
-Continuing on with our scenario, consider that you maybe want to try using the
-'//pw_chrono' module. So you create a target in your repository like so;
-
-.. code::
-
-  # BUILD
-  pw_cc_library(
-    name = "time_is_relative",
-    srcs = ["relative_time_on_earth.cc"],
-    deps = ["@pigweed//pw_chrono"],
-  )
-
-Now this should work out of the box for any host operating system. e.g. Running;
-
-.. code::
-
-  bazel build //:time_is_relative
-
-will produce a working library. But as your probably here because Pigweed offers
-a set of embedded libraries you might be interested in running your code on some
-random micro-controller/FPGA combined with an RTOS. For now let's assume that by
-some coincidence you are using FreeRTOS and are happy to make use
-of our default '//pw_chrono' backend for FreeRTOS. You could build the following
-with;
-
-.. code:: sh
-
-  bazel build //:time_is_relative \
-  --platforms=@pigweed//pw_build/platforms:freertos
-
-There is a fair bit to unpack here in terms of how our configuration system
-is determining which dependencies to choose for your build. The dependency
-tree (that is important for configuration) in a project such as this would
-look like.
-
-.. code::
-
-  @pigweed//pw_chrono:pw_chrono_facade <-----------.
-   ^                                               |
-   |                            @pigweed//pw_chrono_freertos:system_clock
-   |                            (Actual backend)
-   |                                               ^
-   |                                               |
-   |                            @pigweed//pw_chrono:system_clock_backend_multiplexer
-   |                            Select backend based on OS:
-   |                            [FreeRTOS (X), Embos ( ), STL ( ), Threadx ( )]
-   |                                               ^
-   |                                               |
-  @pigweed//pw_chrono  -------> @pigweed_config//:pw_chrono_system_clock_backend
-   ^                            (Injectable)
-   |
-  //:time_is_relative
-
-So when evaluating this setup Bazel checks the dependencies for '//pw_chrono'
-and finds that it depends on "@pigweed_config//:pw_chrono_system_clock_backend" which looks
-like this;
-
-.. code:: py
-
-  # pw_chrono config.
-  label_flag(
-      name = "pw_chrono_system_clock_backend",
-      build_setting_default = "@pigweed//pw_chrono:system_clock_backend_multiplexer",
-  )
-
-Looking at the 'build_setting_default' we can see that by default it depends
-back on the target "@pigweed//pw_chrono:system_clock_backend_multiplexer". If
-you only had one backend you could actually just change the
-'build_setting_default' to point directly to your backend. However because we
-have four different backends we have to use the select semantics to choose the
-right one. In this case it looks like;
-
-.. code:: py
-
-  pw_cc_library(
-    name = "system_clock_backend_multiplexer",
-    visibility = ["@pigweed_config//:__pkg__"],
-    deps = select({
-        "@pigweed//pw_build/constraints/rtos:freertos":
-            ["//pw_chrono_freertos:system_clock"],
-        "@pigweed//pw_build/constraints/rtos:embos":
-            ["//pw_chrono_embos:system_clock"],
-        "@pigweed//pw_build/constraints/rtos:threadx":
-            ["//pw_chrono_threadx:system_clock"],
-        "//conditions:default": ["//pw_chrono_stl:system_clock"],
-    }),
-  )
-
-Intuitively you can see that the first option was selected, which terminates
-the configuration chain.
-
-Continuing on with our scenario let's say that you have read
-:ref:`docs-module-structure` and now want to implement your own backend for
-'//pw_chrono' using a hardware RTC. In this case you would create a new
-directory 'pw_chrono_my_hardware_rtc'. To ensure that your new backend compiles
-with the facade an easy and temporary way to override the dependency tree is
-to override the label flag in '@pigweed_config'. For example;
-
-.. code:: sh
-
-  bazel build //:time_is_relative \
-    --@pigweed_config//pw_chrono_system_clock_backend=//pw_chrono_my_hardware_rtc:system_clock
-
-This temporarily modifies the build graph to look something like this;
-
-.. code::
-
-  @pigweed//pw_chrono:pw_chrono_facade <-----.
-   ^                                         |
-   |                      @your_workspace//pw_chrono_my_hardware_rtc:system_clock
-   |                      (Actual backend)
-   |                                         ^
-   |                                         |
-  @pigweed//pw_chrono  -> @pigweed_config//:pw_chrono_system_clock_backend
-   ^                      (Injectable)
-   |
-  //:time_is_relative
-
-Now while this is a nice temporary change, but you might find yourself in need
-of a more permanent configuration. Particularly if you want to override multiple
-different backends. In other words if you had several backends to override, that
-would translate to several different command line flags (one for each override).
-This problem further compounds as you have multiple Pigweed targets all
-requiring different combinations of different backends as you can't even reuse
-your command line entries. Instead you would have to memorize the correct
-combination of backends for each of your targets.
-
-So continuing on with our scenario, let's say we add a backup micro-controller,
-to our spacecraft. But this backup computer doesn't have a hardware RTC. We
-still want to share the bulk of the code between the two computers but now we
-need two separate implementations for our pw_chrono facade. Let's say we choose
-to keep the primary flight computer using the hardware RTC and switch the backup
-computer over to use Pigweeds default FreeRTOS backend. In this case we might,
-want to do something similar to
-'@pigweed//pw_chrono:system_clock_backend_multiplexer' and create selectable
-dependencies for the two different computers. Now because there are no default
-constraint_setting's that meet our requirements we are going to have to;
-
-1. Create a constraint_setting and a set of constraint_value's for the flight
-   computer. For example;
-
-  .. code:: py
-
-    # //platforms/flight_computer/BUILD
-    constraint_setting(
-      name = "flight_computer",
-    )
-
-    constraint_value(
-      name = "primary",
-      constraint_setting = ":flight_computer",
-    )
-
-    constraint_value(
-      name = "backup",
-      constraint_setting = ":flight_computer",
-    )
-
-2. Create a set of platforms that can be used to switch constraint_value's.
-   For example;
-
-  .. code:: py
-
-    # //platforms/BUILD
-    platform(
-      name = "primary_computer",
-      constraint_values = ["//platforms/flight_computer:primary"],
-    )
-
-    platform(
-      name = "backup_computer",
-      constraint_values = ["//platforms/flight_computer:backup"],
-    )
-
-3. Create a target multiplexer that will select the right backend depending on
-   which computer you are using. For example;
-
-  .. code:: py
-
-    # //pw_chrono/BUILD
-    load("//pw_build:pigweed.bzl", "pw_cc_library")
-
-    pw_cc_library(
-      name = "system_clock_backend_multiplexer",
-      deps = select({
-        "//platforms/flight_computer:primary": [
-          "//pw_chrono_my_hardware_rtc:system_clock",
-        ],
-        "//platforms/flight_computer:backup": [
-          "@pigweed//pw_chrono_freertos:system_clock",
-        ],
-        "//conditions:default": [
-          "@pigweed//pw_chrono_stl:system_clock",
-        ],
-      }),
-    )
-
-4. Copy and paste across the target/default_config.BUILD across from the
-   Pigweed repository and modifying the build_setting_default for the target
-   'pw_chrono_system_clock_backend' to point to your new system_clock_backend_multiplexer
-   target. For example;
-
-   This;
-
-  .. code:: py
-
-    # @pigweed//target:default_config.BUILD
-    label_flag(
-        name = "pw_chrono_system_clock_backend",
-        build_setting_default = "@pigweed//pw_chrono:system_clock_backend_multiplexer",
-    )
-
-  Becomes this;
-
-  .. code:: py
-
-    # @your_workspace//target:your_config.BUILD
-    label_flag(
-      name = "pw_chrono_system_clock_backend",
-      build_setting_default =
-        "@your_workspace//pw_chrono:system_clock_backend_multiplexer",
-    )
-
-5. Switch your workspace 'pigweed_config' rule over to use your custom config.
-
-  .. code:: py
-
-    # WORKSPACE
-    pigweed_config(
-      name = "pigweed_config",
-      build_file = "//target/your_config.BUILD",
-    )
-
-Building your target now will result in slightly different build graph. For
-example, running;
-
-.. code:: sh
-
-  bazel build //:time_is_relative --platforms=//platforms:primary_computer
-
-Will result in a build graph that looks like;
-
-.. code::
-
-  @pigweed//pw_chrono:pw_chrono_facade <---.
-   ^                                        |
-   |                     @your_workspace//pw_chrono_my_hardware_rtc:system_clock
-   |                     (Actual backend)
-   |                                        ^
-   |                                        |
-   |                     @your_workspace//pw_chrono:system_clock_backend_multiplexer
-   |                     Select backend based on OS:
-   |                     [Primary (X), Backup ( ), Host only default ( )]
-   |                                        ^
-   |                                        |
-  @pigweed//pw_chrono -> @pigweed_config//:pw_chrono_system_clock_backend
-   ^                     (Injectable)
-   |
-  //:time_is_relative
diff --git a/docs/code_of_conduct.rst b/docs/code_of_conduct.rst
deleted file mode 100644
index 79436db..0000000
--- a/docs/code_of_conduct.rst
+++ /dev/null
@@ -1,98 +0,0 @@
-.. _docs-code-of-conduct:
-
-===============
-Code of Conduct
-===============
-
-Our Pledge
-----------
-
-In the interest of fostering an open and welcoming environment, we as
-contributors and maintainers pledge to making participation in our project and
-our community a harassment-free experience for everyone, regardless of age, body
-size, disability, ethnicity, gender identity and expression, level of
-experience, education, socio-economic status, nationality, personal appearance,
-race, religion, or sexual identity and orientation.
-
-Our Standards
--------------
-Examples of behavior that contributes to creating a positive environment
-include:
-
-* Using welcoming and inclusive language
-* Being respectful of differing viewpoints and experiences
-* Gracefully accepting constructive criticism
-* Focusing on what is best for the community
-* Showing empathy towards other community members
-
-Examples of unacceptable behavior by participants include:
-
-* The use of sexualized language or imagery and unwelcome sexual attention or
-  advances
-* Trolling, insulting/derogatory comments, and personal or political attacks
-* Public or private harassment
-* Publishing others' private information, such as a physical or electronic
-  address, without explicit permission
-* Other conduct which could reasonably be considered inappropriate in a
-  professional setting
-
-Our Responsibilities
---------------------
-Project maintainers are responsible for clarifying the standards of acceptable
-behavior and are expected to take appropriate and fair corrective action in
-response to any instances of unacceptable behavior.
-
-Project maintainers have the right and responsibility to remove, edit, or reject
-comments, commits, code, wiki edits, issues, and other contributions that are
-not aligned to this Code of Conduct, or to ban temporarily or permanently any
-contributor for other behaviors that they deem inappropriate, threatening,
-offensive, or harmful.
-
-Scope
------
-This Code of Conduct applies both within project spaces and in public spaces
-when an individual is representing the project or its community. Examples of
-representing a project or community include using an official project e-mail
-address, posting via an official social media account, or acting as an
-appointed representative at an online or offline event. Representation of a
-project may be further defined and clarified by project maintainers.
-
-This Code of Conduct also applies outside the project spaces when the Project
-Steward has a reasonable belief that an individual's behavior may have a
-negative impact on the project or its community.
-
-Conflict Resolution
--------------------
-We do not believe that all conflict is bad; healthy debate and disagreement
-often yield positive results. However, it is never okay to be disrespectful or
-to engage in behavior that violates the project’s code of conduct.
-
-If you see someone violating the code of conduct, you are encouraged to address
-the behavior directly with those involved. Many issues can be resolved quickly
-and easily, and this gives people more control over the outcome of their
-dispute. If you are unable to resolve the matter for any reason, or if the
-behavior is threatening or harassing, report it. We are dedicated to providing
-an environment where participants feel welcome and safe.
-
-Reports should be directed to Pigweed Community Managers at
-pigweed-community-managers@google.com, the Project Steward(s) for Pigweed. It
-is the Project Steward’s duty to receive and address reported violations of the
-code of conduct. They will then work with a committee consisting of
-representatives from the Open Source Programs Office and the Google Open Source
-Strategy team. If for any reason you are uncomfortable reaching out the Project
-Steward, please email opensource@google.com.
-
-**We will investigate every complaint**, but you may not receive a direct
-response. We will use our discretion in determining when and how to follow up
-on reported incidents, which may range from not taking action to permanent
-expulsion from the project and project-sponsored spaces. We will notify the
-accused of the report and provide them an opportunity to discuss it before any
-action is taken. The identity of the reporter will be omitted from the details
-of the report supplied to the accused. In potentially harmful situations, such
-as ongoing harassment or threats to anyone's safety, we may take action without
-notice.
-
-Attribution
------------
-This Code of Conduct is adapted from `Contributor Covenant version 1.4
-<https://www.contributor-covenant.org/version/1/4/code-of-conduct.html>`_
diff --git a/docs/concepts/index.rst b/docs/concepts/index.rst
deleted file mode 100644
index 1caf7b5..0000000
--- a/docs/concepts/index.rst
+++ /dev/null
@@ -1,111 +0,0 @@
-.. _docs-concepts:
-
-=============
-About Pigweed
-=============
-
-Why Build Pigweed?
-==================
-Our goal is to make embedded software development efficient, robust, and
-heck, even delightful, for projects ranging from weekend Arduino experiements
-to commercial products selling in the millions.
-
-Embedded software development is notoriously arcane. Developers often have to
-track down vendor toolchains specific to the hardware they're targeting, write
-their code against hardware-specfic SDKs/HALs, and limit themselves to a small
-subset of C. Project teams are on their own to figure out how to set up a build
-system, automated testing, serial communication, and many other embedded
-project fundamentals. This is error prone and takes effort away from developing
-the actual product!
-
-There are solutions on the market that promise to solve all of these problems
-with a monolithic framework—just write your code against the framework and use
-hardware the framework supports, and you get an efficient embedded development
-environment. But this approach doesn't work well for existing projects that
-weren't built on the framework from the beginning or for projects that have
-specific needs the framework wasn't designed for. We know from experience that
-this approach alone doesn't meet our goal.
-
-So we have set out to build a platform that supports successful embedded
-developers at every scale by allowing them to adopt as much or as little of
-what Pigweed provides as they need, in the way that works best for their
-project.
-
-How Pigweed Works
-=================
-Pigweed provides four foundational pillars to support your embedded development:
-
-* :ref:`A comprehensive set of libraries for embedded development<docs-concepts-embedded-development-libraries>`
-* :ref:`A hermetic and replicable development environment<docs-concepts-development-environment>`
-* :ref:`A system for building, testing, and linting your project<docs-concepts-build-system>`
-* :ref:`A full framework for new projects that want a turn-key solution<docs-concepts-full-framework>`
-
-.. _docs-concepts-embedded-development-libraries:
-
-Embedded Development Libraries
-------------------------------
-Pigweed enables you to use modern C++ and software development best practices in
-your embedded project without compromising performance or increasing memory use
-compared to conventional embedded C.
-
-We provide libraries (modules) for :ref:`strings<module-pw_string>`,
-:ref:`time<module-pw_chrono>`, :ref:`assertions<module-pw_assert>`,
-:ref:`logging<module-pw_log>`, :ref:`serial communication<module-pw_spi>`,
-:ref:`remote procedure calls (RPC)<module-pw_rpc>`, and
-:ref:`much more<docs-module-guides>`.
-
-These modules are designed to work both on your host machine and on a wide
-variety of target devices. We achieve this by writing them in an inherently
-portable way, or through the facade/backend pattern. As a result, you can write
-most or all of your code to run transparently on your host machine and targets.
-
-.. _docs-concepts-development-environment:
-
-Development Environment
------------------------
-Managing toolchains, build systems, and other software needed for a project is
-complex. Pigweed provides all of this out of the box for Linux, Mac, and
-Windows systems in a sealed environment that leaves the rest of your system
-untouched. Getting new developers started is as simple as cloning your project
-repository and activating the Pigweed environment.
-
-.. _docs-concepts-build-system:
-
-Build System
-------------
-Pigweed modules are built to integrate seamlessly into projects using GN. We
-are rapidly expanding our good support for CMake and nascent support for Bazel
-so you can use your build system of choice. For new projects, Pigweed provides a
-build system you can integrate your own code into that works out of the box.
-
-.. _docs-concepts-full-framework:
-
-Full Framework (coming in 2022)
--------------------------------
-For those who want a fully-integrated solution that provides everything Pigweed
-has to offer with an opinionated project structure, we are working diligently
-on a :ref:`Pigweed framework<module-pw_system>`. Stay tuned for more news to
-come! In the meantime, we invite you to discuss this and collaborate with us
-on `Discord <https://discord.gg/M9NSeTA>`_.
-
-.. _docs-concepts-right-for-my-project:
-
-Is Pigweed Right for My Project?
-================================
-Pigweed is still in its early stages, and while we have ambitious plans for it,
-Pigweed might not be the right fit for your project today. Here are some things
-to keep in mind:
-
-* Many individual modules are stable and are running on shipped devices today.
-  If any of those modules meet your needs, you should feel safe bringing them
-  into your project.
-
-* Some modules are in very early and active stages of development. They likely
-  have unstable APIs and may not work on all supported targets. If this is the
-  case, it will be indicated in the module's documentation. If you're interested
-  in contributing to the development of one of these modules, we encourage you
-  to experiment with them. Otherwise they aren't ready for use in most projects.
-
-* Setting up new projects to use Pigweed is currently not very easy, but we are
-  working to address that. In the meantime, join the Pigweed community on
-  `Discord <https://discord.gg/M9NSeTA>`_ to get help.
diff --git a/docs/conf.py b/docs/conf.py
index 519b87b..7eaf374 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -13,18 +13,17 @@
 # the License.
 """Pigweed's Sphinx configuration."""
 
-from datetime import date
-import sphinx
+import sphinx_rtd_theme
 
 # The suffix of source filenames.
-source_suffix = ['.rst']
+source_suffix = ['.rst', '.md']
 
-# The master toctree document.  # inclusive-language: ignore
+# The master toctree document.
 master_doc = 'index'
 
 # General information about the project.
 project = 'Pigweed'
-copyright = f'{date.today().year} The Pigweed Authors'  # pylint: disable=redefined-builtin
+copyright = '2020 The Pigweed Authors'  # pylint: disable=redefined-builtin
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
@@ -36,15 +35,20 @@
 release = '0.1.0'
 
 # The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'pigweed-code-light'
-pygments_dark_style = 'pigweed-code'
+pygm = 'sphinx'
 
 extensions = [
-    'pw_docgen.sphinx.google_analytics',  # Enables optional Google Analytics
     'sphinx.ext.autodoc',  # Automatic documentation for Python code
     'sphinx.ext.napoleon',  # Parses Google-style docstrings
-    'sphinxcontrib.mermaid',
-    'sphinx_design',
+    'm2r',  # Converts Markdown to reStructuredText
+
+    # Blockdiag suite of diagram generators.
+    'sphinxcontrib.blockdiag',
+    'sphinxcontrib.nwdiag',
+    'sphinxcontrib.seqdiag',
+    'sphinxcontrib.actdiag',
+    'sphinxcontrib.rackdiag',
+    'sphinxcontrib.packetdiag',
 ]
 
 _DIAG_HTML_IMAGE_FORMAT = 'SVG'
@@ -60,7 +64,14 @@
 
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
-html_theme = 'furo'
+html_theme = 'sphinx_rtd_theme'
+
+# Add any paths that contain custom themes here, relative to this directory.
+html_theme_path = [
+    '_themes',
+]
+
+html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
 
 # The name for this set of Sphinx documents.  If None, it defaults to
 # "<project> v<release> documentation".
@@ -85,68 +96,6 @@
 # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
 html_show_sphinx = False
 
-# These folders are copied to the documentation's HTML output
-html_static_path = ['docs/_static']
-
-# These paths are either relative to html_static_path
-# or fully qualified paths (eg. https://...)
-html_css_files = [
-    'css/pigweed.css',
-
-    # Needed for Inconsolata font.
-    'https://fonts.googleapis.com/css2?family=Inconsolata&display=swap',
-]
-
-html_theme_options = {
-    'light_css_variables': {
-        # Make the logo text more amaranth-like
-        'color-sidebar-brand-text': '#b529aa',
-        'color-sidebar-search-border': '#b529aa',
-        'color-sidebar-link-text--top-level': '#85004d',
-        'color-sidebar-link-text': '#016074',
-        'color-sidebar-item-background--current': '#f0f0f0',
-        'color-sidebar-item-background--hover': '#ffe2f3',
-        'color-sidebar-item-expander-background--hover': '#ffe2f3',
-        # Function signature colors
-        'color-api-function-border': '#cccccc',
-        'color-api-function-background': '#f0f0f0',
-        'color-api-class-background': '#e7f2fa',
-        'color-api-class-foreground': '#2980b9',
-        'color-api-class-border': '#6ab0de',
-        # Namespace::
-        'color-api-pre-name': '#2980b9',
-        # Function name
-        'color-api-name': '#2980b9',
-        'color-inline-code-background': '#fafafa',
-        'color-inline-code-border': '#cccccc',
-        'color-text-selection-background': '#1d5fad',
-        'color-text-selection-foreground': '#ffffff',
-    },
-    'dark_css_variables': {
-        'color-sidebar-brand-text': '#e815a5',
-        'color-sidebar-search-border': '#e815a5',
-        'color-sidebar-link-text--top-level': '#ff79c6',
-        'color-sidebar-link-text': '#8be9fd',
-        'color-sidebar-item-background--current': '#575757',
-        'color-sidebar-item-background--hover': '#4c333f',
-        'color-sidebar-item-expander-background--hover': '#4c333f',
-        # Function signature colors
-        'color-api-function-border': '#575757',
-        'color-api-function-background': '#2b2b2b',
-        'color-api-class-background': '#222c35',
-        'color-api-class-foreground': '#87c1e5',
-        'color-api-class-border': '#5288be',
-        # Namespace::
-        'color-api-pre-name': '#87c1e5',
-        # Function name
-        'color-api-name': '#87c1e5',
-        'color-inline-code-background': '#2b2b2b',
-        'color-inline-code-border': '#575757',
-        'color-text-selection-background': '#2674bf',
-        'color-text-selection-foreground': '#ffffff',
-    },
-}
-
 # Output file base name for HTML help builder.
 htmlhelp_basename = 'Pigweeddoc'
 
@@ -162,20 +111,17 @@
      'Miscellaneous'),
 ]
 
+# Markdown files imported using m2r aren't marked as "referenced," so exclude
+# them from the error reference checking.
+exclude_patterns = ['README.md']
+
 
 def do_not_skip_init(app, what, name, obj, would_skip, options):
     if name == "__init__":
         return False  # never skip __init__ functions
+
     return would_skip
 
 
-# Problem: CSS files aren't copied after modifying them. Solution:
-# https://github.com/sphinx-doc/sphinx/issues/2090#issuecomment-572902572
-def env_get_outdated(app, env, added, changed, removed):
-    return ['index']
-
-
 def setup(app):
-    app.add_css_file('css/pigweed.css')
-    app.connect('env-get-outdated', env_get_outdated)
     app.connect("autodoc-skip-member", do_not_skip_init)
diff --git a/docs/contributing.rst b/docs/contributing.rst
deleted file mode 100644
index beb43f1..0000000
--- a/docs/contributing.rst
+++ /dev/null
@@ -1,420 +0,0 @@
-.. _docs-contributing:
-
-============
-Contributing
-============
-We'd love to accept your patches and contributions to Pigweed. There are just a
-few small guidelines you need to follow. Before making or sending major
-changes, please reach out on the `mailing list
-<https://groups.google.com/forum/#!forum/pigweed>`_ first to ensure the changes
-make sense for upstream. We generally go through a design phase before making
-large changes.
-
-Before participating in our community, please take a moment to review our
-:ref:`docs-code-of-conduct`. We expect everyone who interacts with the project
-to respect these guidelines.
-
-Pigweed contribution overview
------------------------------
-
-#. One-time contributor setup:
-
-  - Sign the `Contributor License Agreement <https://cla.developers.google.com/>`_.
-  - Verify that your Git user email (git config user.email) is either Google
-    Account email or an Alternate email for the Google account used to sign
-    the CLA (Manage Google account → Personal Info → email)
-  - Sign in to `Gerrit <https://pigweed-review.googlesource.com/>`_ to create
-    an account using the same Google account you used above.
-  - Obtain a login cookie from Gerrit's `new-password <https://pigweed-review.googlesource.com/new-password>`_ page
-  - Install the Gerrit commit hook to automatically add a ``Change-Id: ...``
-    line to your commit
-  - Install the Pigweed presubmit check hook with ``pw presubmit --install``
-
-#. Ensure all files include the correct copyright and license headers
-#. Include any necessary changes to the documentation
-#. Run :ref:`module-pw_presubmit` to detect style or compilation issues before
-   uploading
-#. Upload the change with ``git push origin HEAD:refs/for/main``
-#. Address any reviewer feedback by amending the commit (``git commit --amend``)
-#. Submit change to CI builders to merge. If you are not part of Pigweed's
-   core team, you can ask the reviewer to add the `+2 CQ` vote, which will
-   trigger a rebase and submit once the builders pass
-
-.. note::
-
-  If you have any trouble with this flow, reach out in our `chat room
-  <https://discord.gg/M9NSeTA>`_ or on the `mailing list
-  <https://groups.google.com/forum/#!forum/pigweed>`_ for help.
-
-Contributor License Agreement
------------------------------
-Contributions to this project must be accompanied by a Contributor License
-Agreement. You (or your employer) retain the copyright to your contribution;
-this simply gives us permission to use and redistribute your contributions as
-part of the project. Head over to <https://cla.developers.google.com/> to see
-your current agreements on file or to sign a new one.
-
-You generally only need to submit a CLA once, so if you've already submitted one
-(even if it was for a different project), you probably don't need to do it
-again.
-
-Gerrit Commit Hook
-------------------
-Gerrit requires all changes to have a ``Change-Id`` tag at the bottom of each
-commit message. You should set this up to be done automatically using the
-instructions below.
-
-**Linux/macOS**
-
-.. code:: bash
-
-  $ f=`git rev-parse --git-dir`/hooks/commit-msg ; mkdir -p $(dirname $f) ; curl -Lo $f https://gerrit-review.googlesource.com/tools/hooks/commit-msg ; chmod +x $f
-
-**Windows**
-
-Download `the Gerrit commit hook
-<https://gerrit-review.googlesource.com/tools/hooks/commit-msg>`_ and then copy
-it to the ``.git\hooks`` directory in the Pigweed repository.
-
-.. code::
-
-  copy %HOMEPATH%\Downloads\commit-msg %HOMEPATH%\pigweed\.git\hooks\commit-msg
-
-Commit message
---------------
-Consider the following when writing a commit message:
-
-#. **Documentation and comments are better** - Consider whether the commit
-   message contents would be better expressed in the documentation or code
-   comments. Docs and code comments are durable and readable later; commit
-   messages are rarely read after the change lands.
-#. **Include why the change is made, not just what the change is** - It is
-   important to include a "why" component in most commits. Sometimes, why is
-   evident - for example, reducing memory usage, or optimizing. But it is often
-   not. Err on the side of over-explaining why, not under-explaining why.
-
-Pigweed commit messages should conform to the following style:
-
-**Yes:**
-
-.. code:: none
-
-   pw_some_module: Short capitalized description
-
-   Details about the change here. Include a summary of the what, and a clear
-   description of why the change is needed for future maintainers.
-
-   Consider what parts of the commit message are better suited for
-   documentation.
-
-**Yes**: Small number of modules affected; use {} syntax.
-
-.. code:: none
-
-   pw_{foo, bar, baz}: Change something in a few places
-
-   When changes cross a few modules, include them with the syntax shown above.
-
-
-**Yes**: targets are effectively modules, even though they're nested, so they get a
-``/`` character.
-
-.. code:: none
-
-   targets/xyz123: Tweak support for XYZ's PQR
-
-**Yes**: Uses imperative style for subject and text.
-
-.. code:: none
-
-   pw_something: Add foo and bar functions
-
-   This commit correctly uses imperative present-tense style.
-
-**No**: Uses non-imperative style for subject and text.
-
-.. code:: none
-
-   pw_something: Adds more things
-
-   Use present tense imperative style for subjects and commit. The above
-   subject has a plural "Adds" which is incorrect; should be "Add".
-
-**Yes**: Use bulleted lists when multiple changes are in a single CL. Prefer
-smaller CLs, but larger CLs are a practical reality.
-
-.. code:: none
-
-   pw_complicated_module: Pre-work for refactor
-
-   Prepare for a bigger refactor by reworking some arguments before the larger
-   change. This change must land in downstream projects before the refactor to
-   enable a smooth transition to the new API.
-
-   - Add arguments to MyImportantClass::MyFunction
-   - Update MyImportantClass to handle precondition Y
-   - Add stub functions to be used during the transition
-
-**No**: Run on paragraph instead of bulleted list
-
-.. code:: none
-
-   pw_foo: Many things in a giant BWOT
-
-   This CL does A, B, and C. The commit message is a Big Wall Of Text (BWOT),
-   which we try to discourage in Pigweed. Also changes X and Y, because Z and
-   Q. Furthermore, in some cases, adds a new Foo (with Bar, because we want
-   to). Also refactors qux and quz.
-
-**No**: Doesn't capitalize the subject
-
-.. code:: none
-
-   pw_foo: do a thing
-
-   Above subject is incorrect, since it is a sentence style subject.
-
-**Yes**: Doesn't capitalize the subject when subject's first word is a
-lowercase identifier.
-
-.. code:: none
-
-   pw_foo: std::unique_lock cleanup
-
-   This commit message demonstrates the subject when the subject has an
-   identifier for the first word. In that case, follow the identifier casing
-   instead of capitalizing.
-
-   However, imperative style subjects often have the identifier elsewhere in
-   the subject; for example:
-
-     pw_foo: Improve use of std::unique_lock
-
-**No**: Uses a non-standard ``[]`` to indicate moduule:
-
-.. code:: none
-
-   [pw_foo]: Do a thing
-
-**No**: Has a period at the end of the subject
-
-.. code:: none
-
-   pw_bar: Do somehthing great.
-
-**No**: Puts extra stuff after the module which isn't a module.
-
-.. code:: none
-
-   pw_bar/byte_builder: Add more stuff to builder
-
-Footer
-^^^^^^
-We support a number of `git footers`_ in the commit message, such as ``Bug:
-123`` in the message below:
-
-.. code:: none
-
-   pw_something: Add foo and bar functions
-
-   Bug: 123
-
-You are encouraged to use the following footers when appropriate:
-
-* ``Bug``: Associates this commit with a bug (issue in our `bug tracker`_). The
-  bug will be automatically updated when the change is submitted. When a change
-  is relevant to more than one bug, include multiple ``Bug`` lines, like so:
-
-  .. code:: none
-
-      pw_something: Add foo and bar functions
-
-      Bug: 123
-      Bug: 456
-
-* ``Fixed``: Like ``Bug``, but automatically closes the bug when submitted.
-
-In addition, we support all of the `Chromium CQ footers`_, but those are
-relatively rarely useful.
-
-.. _bug tracker: https://bugs.chromium.org/p/pigweed/issues/list
-.. _Chromium CQ footers: https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/infra/cq.md#options
-.. _git footers: https://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/git-footers.html
-
-
-Documentation
--------------
-All Pigweed changes must either
-
-#. Include updates to documentation, or
-#. Include ``No-Docs-Update-Reason: <reason>`` in a Gerrit comment on the CL.
-   For example:
-
-   * ``No-Docs-Update-Reason: formatting tweaks``
-   * ``No-Docs-Update-Reason: internal cleanups``
-   * ``No-Docs-Update-Reason: bugfix``
-
-It's acceptable to only document new changes in an otherwise underdocumented
-module, but it's not acceptable to not document new changes because the module
-doesn't have any other documentation.
-
-Code Reviews
-------------
-All Pigweed development happens on Gerrit, following the `typical Gerrit
-development workflow <http://ceres-solver.org/contributing.html>`_. Consult the
-`Gerrit User Guide
-<https://gerrit-documentation.storage.googleapis.com/Documentation/2.12.3/intro-user.html>`_
-for more information on using Gerrit.
-
-In the future we may support GitHub pull requests, but until that time we will
-close GitHub pull requests and ask that the changes be uploaded to Gerrit
-instead.
-
-Community Guidelines
---------------------
-This project follows `Google's Open Source Community Guidelines
-<https://opensource.google/conduct/>`_ and the :ref:`docs-code-of-conduct`.
-
-Source Code Headers
--------------------
-Every Pigweed file containing source code must include copyright and license
-information. This includes any JS/CSS files that you might be serving out to
-browsers.
-
-Apache header for C and C++ files:
-
-.. code:: none
-
-  // Copyright 2021 The Pigweed Authors
-  //
-  // 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
-  //
-  //     https://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.
-
-Apache header for Python and GN files:
-
-.. code:: none
-
-  # Copyright 2020 The Pigweed Authors
-  #
-  # 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
-  #
-  #     https://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.
-
-Presubmit Checks and Continuous Integration
--------------------------------------------
-All Pigweed change lists (CLs) must adhere to Pigweed's style guide and pass a
-suite of automated builds, tests, and style checks to be merged upstream. Much
-of this checking is done using Pigweed's ``pw_presubmit`` module by automated
-builders. These builders run before each Pigweed CL is submitted and in our
-continuous integration infrastructure (see `Pigweed's build console
-<https://ci.chromium.org/p/pigweed/g/pigweed/console>`_).
-
-Running Presubmit Checks
-------------------------
-To run automated presubmit checks on a pending CL, click the ``CQ DRY RUN``
-button in the Gerrit UI. The results appear in the Tryjobs section, below the
-source listing. Jobs that passed are green; jobs that failed are red.
-
-If all checks pass, you will see a ``Dry run: This CL passed the CQ dry run.``
-comment on your change. If any checks fail, you will see a ``Dry run: Failed
-builds:`` message. All failures must be addressed before submitting.
-
-In addition to the publicly visible presubmit checks, Pigweed runs internal
-presubmit checks that are only visible within Google. If any these checks fail,
-external developers will see a ``Dry run: Failed builds:`` comment on the CL,
-even if all visible checks passed. Reach out to the Pigweed team for help
-addressing these issues.
-
-Project Presubmit Checks
-------------------------
-In addition to Pigweed's presubmit checks, some projects that use Pigweed run
-their presubmit checks in Pigweed's infrastructure. This supports a development
-flow where projects automatically update their Pigweed submodule if their tests
-pass. If a project cannot build against Pigweed's tip-of-tree, it will stay on
-a fixed Pigweed revision until the issues are fixed. See the `sample project
-<https://pigweed.googlesource.com/pigweed/sample_project/>`_ for an example of
-this.
-
-Pigweed does its best to keep builds passing for dependent projects. In some
-circumstances, the Pigweed maintainers may choose to merge changes that break
-dependent projects. This will only be done if
-
-* a feature or fix is needed urgently in Pigweed or for a different project,
-  and
-* the project broken by the change does not imminently need Pigweed updates.
-
-The downstream project will continue to build against their last working
-revision of Pigweed until the incompatibilities are fixed.
-
-In these situations, Pigweed's commit queue submission process will fail for all
-changes. If a change passes all presubmit checks except for known failures, the
-Pigweed team may permit manual submission of the CL. Contact the Pigweed team
-for submission approval.
-
-Running local presubmits
-------------------------
-To speed up the review process, consider adding :ref:`module-pw_presubmit` as a
-git push hook using the following command:
-
-Linux/macOS
-^^^^^^^^^^^
-.. code:: bash
-
-  $ pw presubmit --install
-
-This will be effectively the same as running the following command before every
-``git push``:
-
-.. code:: bash
-
-  $ pw presubmit
-
-
-.. image:: ../pw_presubmit/docs/pw_presubmit_demo.gif
-  :width: 800
-  :alt: pw presubmit demo
-
-If you ever need to bypass the presubmit hook (due to it being broken, for
-example) you may push using this command:
-
-.. code:: bash
-
-  $ git push origin HEAD:refs/for/main --no-verify
-
-Presubmit and branch management
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-When creating new feature branches, make sure to specify the upstream branch to
-track, e.g.
-
-.. code:: bash
-
-  $ git checkout -b myfeature origin/main
-
-When tracking an upstream branch, ``pw presubmit`` will only run checks on the
-modified files, rather than the entire repository.
-
-.. _Sphinx: https://www.sphinx-doc.org/
-
-.. inclusive-language: disable
-
-.. _reStructuredText Primer: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html
-
-.. inclusive-language: enable
-
diff --git a/docs/faq.rst b/docs/faq.rst
index 7a6f248..3b51b4a 100644
--- a/docs/faq.rst
+++ b/docs/faq.rst
@@ -144,7 +144,6 @@
 Host platforms that we are likely to support in the future
 ..........................................................
 
-- **Mac on ARM (M1)** - This is currently supported through Rosetta.
 - **Linux on ARM** - At time of writing (mid 2020), we do not support ARM-based
   host platforms.  However, we would like to support this eventually.
 - **Windows on WSL2 x86-64** - There are some minor issues preventing WSL2 on
diff --git a/docs/getting_started.md b/docs/getting_started.md
new file mode 100644
index 0000000..5423d31
--- /dev/null
+++ b/docs/getting_started.md
@@ -0,0 +1,334 @@
+# Getting Started
+
+This guide will walk you through setup and general use of Pigweed.
+We hope to make the setup process as smooth as possible. If any of this doesn't
+work, please [let us know](mailto:pigweed@googlegroups.com).
+
+## Express setup
+
+If you'd like to skip the detailed explanations, below is the shorter version of
+getting setup for Pigweed. If you run into trouble, look at the more in-depth
+guide below, starting at [Prerequisites](getting_started.md#prerequisites). The
+express setup configures Pigweed's watcher for three targets to give a taste of
+Pigweed:
+
+1. **Host** - Mac, Linux, or Windows. Builds and runs tests
+2. **Device/STM32F429** - Build only; Optionally, the STM32F429I-DISC1 kit to
+   follow along later in the guide to run tests directly on said device(s)
+3. **Docs** - Builds the Pigweed docs
+
+To get setup:
+
+(1) Make sure you have Git and Python installed and on your path.
+
+(2) Clone Pigweed and bootstrap the environment (compiler setup & more). **Be
+    patient, this step downloads ~1GB of LLVM, GCC, and other tooling**.
+
+```bash
+$ cd ~
+$ git clone https://pigweed.googlesource.com/pigweed/pigweed
+...
+$ cd pigweed
+$ source ./bootstrap.sh
+...
+```
+
+(3) Configure the GN build.
+
+```bash
+$ gn gen out
+Done. Made 1047 targets from 91 files in 114ms
+```
+
+(4) Start the watcher. The watcher will invoke Ninja to build all the targets
+
+```bash
+$ pw watch
+
+ ▒█████▄   █▓  ▄███▒  ▒█    ▒█ ░▓████▒ ░▓████▒ ▒▓████▄
+  ▒█░  █░ ░█▒ ██▒ ▀█▒ ▒█░ █ ▒█  ▒█   ▀  ▒█   ▀  ▒█  ▀█▌
+  ▒█▄▄▄█░ ░█▒ █▓░ ▄▄░ ▒█░ █ ▒█  ▒███    ▒███    ░█   █▌
+  ▒█▀     ░█░ ▓█   █▓ ░█░ █ ▒█  ▒█   ▄  ▒█   ▄  ░█  ▄█▌
+  ▒█      ░█░ ░▓███▀   ▒█▓▀▓█░ ░▓████▒ ░▓████▒ ▒▓████▀
+
+20200707 17:24:06 INF Starting Pigweed build watcher
+20200707 17:24:06 INF Will build [1/1]: out
+20200707 17:24:06 INF Attaching filesystem watcher to $HOME/wrk/pigweed/...
+20200707 17:24:06 INF Triggering initial build...
+...
+```
+
+(5) **Congratulations, you're ready to go!** Now take Pigweed for a spin with
+    the below steps.
+
+(6) With the watcher running in a separate window, edit
+    `pw_status/status_test.cc` to make an expectation fail; for example, add
+    `EXPECT_EQ(0, 1);` in a test.
+
+(7) Save the file. Observe the watcher rebuild & retest, and fail. Restore the
+    test if you feel like it.
+
+(8) Open the generated docs in `out/docs/gen/docs/html/index.html` in your
+    browser.
+
+(9) Edit `docs/getting_started.md` (this file!) and make any change. Save. See
+    the watcher rebuild the docs. Reload your browser, and see the changes.
+
+See below for equivalent Windows commands, and for more details on what each
+part does.
+
+**Note:** After running bootstrap once, use `source ./activate.sh` (or
+`activate.bat` on Windows) to re-activate the environment without
+re-bootstrapping.
+
+## Prerequisites
+
+**Linux**<br/>
+Most Linux installations should work out of box, and not require any manual
+installation of prerequisites beyond basics like `git` and `build-essential`.
+Make sure gcc is set to gcc-8.
+
+**macOS**<br/>
+To start using Pigweed on MacOS, you'll need to install XCode. Download it
+via the App Store, then install the relevant tools from the command line.
+
+```bash
+xcode-select --install
+```
+
+On macOS you may get SSL certificate errors with the system Python
+installation. Run `sudo pip install certifi` to fix this. If you get SSL
+errors with the Python from [Homebrew](https://brew.sh) try running the
+following commands to ensure Python knows how to use OpenSSL.
+
+```bash
+brew install openssl
+brew uninstall python
+brew install python
+```
+
+To flash firmware to a STM32 Discovery development board (and run `pw test`)
+from macOS, you will need to install OpenOCD. Install
+[Homebrew](https://brew.sh), then install OpenOCD with `brew install openocd`.
+
+**Windows**<br/>
+To start using Pigweed on Windows, you'll need to install
+[Git](https://git-scm.com/download/win) and
+[Python](https://www.python.org/downloads/windows/) (2.7 or above). We recommend
+you install Git to run from the command line and third party software.
+
+If you plan to flash devices with firmware, you'll need to install OpenOCD and
+ensure it's on your system path.
+
+## Bootstrap
+
+Once you satisfied the prerequisites, you will be able to clone Pigweed and
+run the bootstrap that initializes the Pigweed virtual environment. The
+bootstrap may take several minutes to complete, so please be patient.
+
+**Linux/macOS**
+```bash
+$ git clone https://pigweed.googlesource.com/pigweed/pigweed ~/pigweed
+$ cd ~/pigweed
+$ source ./bootstrap.sh
+```
+
+**Windows**
+```batch
+:: Run git commands from the shell you set up to use with Git during install.
+> git clone https://pigweed.googlesource.com/pigweed/pigweed %HOMEPATH%\pigweed
+> cd %HOMEPATH%\pigweed
+> bootstrap.bat
+```
+
+Below is a real-time demo with roughly what you should expect to see as output:
+
+![build example using pw watch](images/pw_env_setup_demo.gif)
+
+Congratulations, you are now set up to start using Pigweed!
+
+## Pigweed Environment
+
+After going through the initial setup process, your current terminal will be in
+the Pigweed development environment that provides all the tools you should need
+to develop on Pigweed. If you leave that session, you can activate the
+environment in a new session with the following command:
+
+**Linux/macOS**
+```bash
+$ source ./activate.sh
+```
+
+**Windows**
+```batch
+> activate.bat
+```
+
+Some major changes may require triggering the bootstrap again, so if you run
+into host tooling changes after a pull it may be worth re-running bootstrap.
+
+## Build Pigweed for Host
+
+Pigweed's primary build system is GN/Ninja based. There are CMake and Bazel
+builds in-development, but they are incomplete and don't have feature parity
+with the GN build. We strongly recommend you stick to the GN build system.
+
+GN (Generate Ninja) just does what it says on the tin; GN generates
+[Ninja](https://ninja-build.org/) build files.
+
+The default GN configuration generates build files that allow you to build host
+binaries, device binaries, and upstream documentation all in one Ninja
+invocation.
+
+Run GN as seen below:
+
+```bash
+$ gn gen out
+```
+
+Note that `out` is simply the directory the build files are saved to. Unless
+this directory is deleted or you desire to do a clean build, there's no need to
+run GN again; just rebuild using Ninja directly.
+
+Now that we have build files, it's time to build Pigweed!
+
+Now you *could* manually invoke the host build using `ninja -C out` every
+time you make a change, but that's tedious. Instead, let's use `pw_watch`.
+
+Go ahead and start `pw_watch`:
+
+```bash
+$ pw watch
+```
+
+When `pw_watch` starts up, it will automatically build the directory we
+generated in `out`. Additionally, `pw_watch` watches source code files for
+changes, and triggers a Ninja build whenever it notices a file has been saved.
+You might be surprised how much time it can save you!
+
+With `pw watch` running, try modifying `pw_status/public/pw_status/status.h` and
+watch the build re-trigger when you save the file.
+
+See below for a demo of this in action:
+
+![build example using pw watch](images/pw_watch_build_demo.gif)
+
+## Running Unit Tests
+
+Fun fact, you've been running the unit tests already! Ninja builds targeting the
+host automatically build and run the unit tests. Unit tests err on the side of
+being quiet in the success case, and only output test results when there's a
+failure.
+
+To see the a test failure, you can modify `pw_status/status_test.cc` to fail
+by changing one of the strings in the "KnownString" test.
+
+![example test failure using pw watch](images/pw_watch_test_demo.gif)
+
+Running tests as part of the build isn't particularly expensive because GN
+caches passing tests. Each time you build, only the tests that are affected
+(whether directly or transitively) by the code changes since the last build will
+be re-built and re-run.
+
+Try running the `pw_status` test manually:
+```bash
+$ ./out/host_{clang,gcc}_debug/obj/pw_status/test/status_test
+```
+
+Depending on your host OS, the compiler will default to either `clang` or `gcc`.
+
+## Building for a Device
+
+A Pigweed "target" is a build configuration that includes a toolchain, default
+library configurations, and more to result in binaries that run natively on the
+target. With the default build invocation, you're already building for a device
+target (the STMicroelectronics STM32F429I-DISC1) in parallel with the host
+build!
+
+If you want to build JUST for the device, you can kick of watch with:
+
+```bash
+$ pw watch stm32f429i
+```
+
+This is equivalent to the following Ninja invocation:
+
+```bash
+$ ninja -C out stm32f429i
+```
+
+
+## Running Tests on a Device
+
+While tests run automatically on the host, it takes a few more steps to get
+tests to run automatically on a device, too. Even though we've verified tests
+pass on the host, it's crucial to verify the same with on-device testing. We've
+encountered some unexpected bugs that can only be found by running the unit
+tests directly on the device.
+
+### 1. Connect Device(s)
+Connect any number of STM32F429I-DISC1 boards to your computer using the mini
+USB port on the board (**not** the micro USB). Pigweed will automatically detect
+the boards and distribute the tests across the devices. More boards = faster
+tests! Keep in mind that you may have to make some environment specific updates
+to ensure you have permissions to use the USB device. For example, on Linux you
+may need to update your udev rules and ensure you're in the plugdev and dialout
+groups.
+
+![development boards connected via USB](images/stm32f429i-disc1_connected.jpg)
+
+### 2. Launch Test Server
+To allow Ninja to run tests on an arbitrary number of devices, Ninja will send
+test requests to a server running in the background. Launch the server in
+another window using the command below (remember, you'll need to activate the
+Pigweed environment first).
+
+```shell
+  $ stm32f429i_disc1_test_server
+```
+
+**Note:** If you attach or detach any more boards to your workstation you'll
+need to relaunch this server.
+
+### 3. Configure GN
+
+We can tell GN to use the testing server by enabling a build arg specific to
+the stm32f429i-disc1 target.
+
+```shell
+$ gn args out
+# Append this line to the file that opens in your editor to tell GN to run
+# on-device unit tests.
+pw_use_test_server = true
+```
+
+### Done!
+
+Whenever you make code changes and trigger a build, all the affected unit tests
+will be run across the attached boards!
+
+See the demo below for an example of what this all looks like put together:
+
+![pw watch running on-device tests](images/pw_watch_on_device_demo.gif)
+
+## Building the Documentation
+
+In addition to the markdown documentation, Pigweed has a collection of
+information-rich RST files that are used to generate HTML documentation. All the
+docs are hosted at https://pigweed.dev/, and are built as a part of the default
+build invocation. This makes it easier to make changes and see how they turn
+out. Once built, you can find the rendered HTML documentation at
+`out/docs/gen/docs/html`.
+
+You can explicitly build just the documentation with the command below.
+
+```shell
+$ ninja -C out docs
+```
+
+## Next steps
+
+This concludes the introduction to developing with Pigweed. If you'd like to see
+more of what Pigweed has to offer, feel free to dive into the per-module
+documentation. If you run into snags along the way, please [let us
+know](mailto:pigweed@googlegroups.com)!
diff --git a/docs/getting_started.rst b/docs/getting_started.rst
deleted file mode 100644
index dc9af55..0000000
--- a/docs/getting_started.rst
+++ /dev/null
@@ -1,412 +0,0 @@
-.. _docs-getting-started:
-
-===============
-Getting Started
-===============
-This guide will walk you through the typical upstream development workflow.
-
-.. note::
-
-  This documentation and the `sample project
-  <https://pigweed.googlesource.com/pigweed/sample_project/+/main/README.md>`_
-  show how to use Pigweed as a library in your existing project. Using Pigweed
-  as the foundation for *new* projects is our intended use case, but you may
-  need more guidance than this documentation provides to do that right now.
-
-  We're happy to help you get your project setup; just drop in our `chat room
-  <https://discord.gg/M9NSeTA>`_ or send a note to the `mailing list
-  <https://groups.google.com/forum/#!forum/pigweed>`_.
-
-Express setup
-=============
-If you'd like to skip the detailed explanations, below is the shorter version
-of getting setup for Pigweed. If you run into trouble, look at the more
-in-depth guide below, starting at :ref:`prerequisites`. The express setup
-configures Pigweed's watcher for three targets to give a taste of Pigweed:
-
-#. **Host** - Mac, Linux, or Windows. Builds and runs tests
-#. **Device/STM32F429** - Build only; Optionally, the STM32F429I-DISC1 kit to
-   follow along later in the guide to run tests directly on said device(s)
-#. **Docs** - Builds the Pigweed docs
-
-To get setup:
-
-#. Make sure you have Git and Python installed and on your path.
-
-#. Clone Pigweed and bootstrap the environment (compiler setup & more). **Be
-   patient, this step downloads ~1GB of LLVM, GCC, and other tooling**.
-
-   .. code:: bash
-
-     $ cd ~
-     $ git clone https://pigweed.googlesource.com/pigweed/pigweed
-     ...
-     $ cd pigweed
-     $ source ./bootstrap.sh (On Linux & Mac)
-     $ bootstrap.bat         (On Windows)
-     ...
-
-#. Configure the GN build.
-
-   .. code:: bash
-
-     $ gn gen out
-     Done. Made 1047 targets from 91 files in 114ms
-
-#. Start the watcher. The watcher will invoke Ninja to build all the targets
-
-   .. code:: bash
-
-     $ pw watch
-
-      ▒█████▄   █▓  ▄███▒  ▒█    ▒█ ░▓████▒ ░▓████▒ ▒▓████▄
-       ▒█░  █░ ░█▒ ██▒ ▀█▒ ▒█░ █ ▒█  ▒█   ▀  ▒█   ▀  ▒█  ▀█▌
-       ▒█▄▄▄█░ ░█▒ █▓░ ▄▄░ ▒█░ █ ▒█  ▒███    ▒███    ░█   █▌
-       ▒█▀     ░█░ ▓█   █▓ ░█░ █ ▒█  ▒█   ▄  ▒█   ▄  ░█  ▄█▌
-       ▒█      ░█░ ░▓███▀   ▒█▓▀▓█░ ░▓████▒ ░▓████▒ ▒▓████▀
-
-     20200707 17:24:06 INF Starting Pigweed build watcher
-     20200707 17:24:06 INF Will build [1/1]: out
-     20200707 17:24:06 INF Attaching filesystem watcher to $HOME/wrk/pigweed/...
-     20200707 17:24:06 INF Triggering initial build...
-     ...
-
-#. **Congratulations, you're ready to go!** Now take Pigweed for a spin by
-   making a test fail.
-
-#. With the watcher running in a separate window, edit
-   ``pw_status/status_test.cc`` to make an expectation fail; for example, add
-   ``EXPECT_EQ(0, 1);`` in a test.
-
-#. Save the file. Observe the watcher rebuild & retest, and fail. Restore the
-   test if you feel like it.
-
-#. Open the generated docs in ``out/docs/gen/docs/html/index.html`` in your
-   browser.
-
-#. Edit ``docs/getting_started.rst`` (this file!) and make any change. Save.
-   See the watcher rebuild the docs. Reload your browser, and see the changes.
-
-See below for equivalent Windows commands, and for more details on what each
-part does.
-
-**Note:** After running bootstrap once, use ``source ./activate.sh`` (or
-``activate.bat`` on Windows) to re-activate the environment without
-re-bootstrapping.
-
-.. _prerequisites:
-
-Prerequisites
--------------
-**Linux**
-
-Most Linux installations should work out of box, and not require any manual
-installation of prerequisites beyond basics like ``git`` and
-``build-essential`` (or the equivalent for your distro).
-
-**macOS**
-
-To start using Pigweed on MacOS, you'll need to install XCode. Download it
-via the App Store, then install the relevant tools from the command line.
-
-.. code:: none
-
-  $ xcode-select --install
-
-On macOS you may get SSL certificate errors with the system Python
-installation. Run ``/Applications/Python <default_py_version>/Install Certificates.command``
-to fix this. If you get SSL
-errors with the Python from `Homebrew <https://brew.sh>`_ try running the
-following commands to ensure Python knows how to use OpenSSL.
-
-.. code:: none
-
-  $ brew install openssl
-  $ brew uninstall python
-  $ brew install python
-
-To flash firmware to a STM32 Discovery development board (and run ``pw test``)
-from macOS, you will need to install OpenOCD. Install
-[Homebrew](https://brew.sh), then install OpenOCD with `brew install openocd`.
-
-**Windows**
-
-To start using Pigweed on Windows, you'll need to do the following:
-
-* Install `Git <https://git-scm.com/download/win>`_. Git must be installed to
-  run from the command line and third-party software or be added to ``PATH``.
-  Also, ensure that the **Enable symbolic links** option is selected.
-* Install `Python <https://www.python.org/downloads/windows/>`_.
-* Ensure that `Developer Mode
-  <https://docs.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development>`_
-  is enabled.
-
-If you plan to flash devices with firmware, you'll need to install OpenOCD and
-ensure it's on your system path.
-
-Bootstrap
-=========
-
-Once you satisfied the prerequisites, you will be able to clone Pigweed and
-run the bootstrap that initializes the Pigweed virtual environment. The
-bootstrap may take several minutes to complete, so please be patient.
-
-**Linux & macOS**
-
-.. code:: bash
-
-  $ git clone https://pigweed.googlesource.com/pigweed/pigweed ~/pigweed
-  $ cd ~/pigweed
-  $ source ./bootstrap.sh
-
-**Windows**
-
-.. code:: batch
-
-  :: Run git commands from the shell you set up to use with Git during install.
-  > git clone https://pigweed.googlesource.com/pigweed/pigweed %HOMEPATH%\pigweed
-  > cd %HOMEPATH%\pigweed
-  > bootstrap.bat
-
-Below is a real-time demo with roughly what you should expect to see as output:
-
-.. image:: images/pw_env_setup_demo.gif
-  :width: 800
-  :alt: build example using pw watch
-
-Congratulations, you are now set up to start using Pigweed!
-
-Pigweed Environment
-===================
-After going through the initial setup process, your current terminal will be in
-the Pigweed development environment that provides all the tools you should need
-to develop on Pigweed. If you leave that session, you can activate the
-environment in a new session with the following command:
-
-**Linux & macOS**
-
-.. code:: bash
-
-  $ source ./activate.sh
-
-**Windows**
-
-.. code:: batch
-
-  > activate.bat
-
-Some major changes may require triggering the bootstrap again, so if you run
-into host tooling changes after a pull it may be worth re-running bootstrap.
-
-Build Pigweed for Host
-======================
-Pigweed's primary build system is GN/Ninja based. There are CMake and Bazel
-builds in-development, but they are incomplete and don't have feature parity
-with the GN build. We strongly recommend you stick to the GN build system.
-
-GN (Generate Ninja) just does what it says on the tin; GN generates
-`Ninja <https://ninja-build.org/>`_ build files.
-
-The default GN configuration generates build files that allow you to build host
-binaries, device binaries, and upstream documentation all in one Ninja
-invocation.
-
-Run GN as seen below:
-
-.. code:: bash
-
-  $ gn gen out
-
-Note that ``out`` is simply the directory the build files are saved to. Unless
-this directory is deleted or you desire to do a clean build, there's no need to
-run GN again; just rebuild using Ninja directly.
-
-Now that we have build files, it's time to build Pigweed!
-
-Now you *could* manually invoke the host build using ``ninja -C out`` every
-time you make a change, but that's tedious. Instead, let's use ``pw_watch``.
-
-Go ahead and start ``pw_watch``:
-
-.. code:: bash
-
-  $ pw watch
-
-When ``pw_watch`` starts up, it will automatically build the directory we
-generated in ``out``. Additionally, ``pw_watch`` watches source code files for
-changes, and triggers a Ninja build whenever it notices a file has been saved.
-You might be surprised how much time it can save you!
-
-With ``pw watch`` running, try modifying
-``pw_status/public/pw_status/status.h`` and watch the build re-trigger when you
-save the file.
-
-See below for a demo of this in action:
-
-.. image:: images/pw_watch_build_demo.gif
-  :width: 800
-  :alt: build example using pw watch
-
-Running Unit Tests
-==================
-Fun fact, you've been running the unit tests already! Ninja builds targeting
-the host automatically build and run the unit tests. Unit tests err on the side
-of being quiet in the success case, and only output test results when there's a
-failure.
-
-To see the a test failure, modify ``pw_status/status_test.cc`` to fail by
-changing one of the strings in the "KnownString" test.
-
-.. image:: images/pw_watch_test_demo.gif
-  :width: 800
-  :alt: example test failure using pw watch
-
-Running tests as part of the build isn't particularly expensive because GN
-caches passing tests. Each time you build, only the tests that are affected
-(whether directly or transitively) by the code changes since the last build
-will be re-built and re-run.
-
-Try running the ``pw_status`` test manually:
-
-.. code:: bash
-
-  $ ./out/host_{clang,gcc}_debug/obj/pw_status/test/status_test
-
-Depending on your host OS, the compiler will default to either ``clang`` or
-``gcc``.
-
-Building for a Device
-=====================
-A Pigweed "target" is a build configuration that includes a toolchain, default
-library configurations, and more to result in binaries that run natively on the
-target. With the default build invocation, you're already building for a device
-target (the STMicroelectronics STM32F429I-DISC1) in parallel with the host
-build!
-
-If you want to build JUST for the device, you can kick of watch with:
-
-.. code:: bash
-
-  $ pw watch stm32f429i
-
-This is equivalent to the following Ninja invocation:
-
-.. code:: bash
-
-  $ ninja -C out stm32f429i
-
-Running Tests on a Device
-=========================
-While tests run automatically on the host, it takes a few more steps to get
-tests to run automatically on a device, too. Even though we've verified tests
-pass on the host, it's crucial to verify the same with on-device testing. We've
-encountered some unexpected bugs that can only be found by running the unit
-tests directly on the device.
-
-1. Connect Device(s)
---------------------
-Connect any number of STM32F429I-DISC1 boards to your computer using the mini
-USB port on the board (**not** the micro USB). Pigweed will automatically
-detect the boards and distribute the tests across the devices. More boards =
-faster tests! Keep in mind that you may have to make some environment specific
-updates to ensure you have permissions to use the USB device. For example, on
-Linux you may need to update your udev rules and ensure you're in the plugdev
-and dialout groups.
-
-.. image:: images/stm32f429i-disc1_connected.jpg
-  :width: 800
-  :alt: development boards connected via USB
-
-2. Launch Test Server
----------------------
-To allow Ninja to run tests on an arbitrary number of devices, Ninja will send
-test requests to a server running in the background. Launch the server in
-another window using the command below (remember, you'll need to activate the
-Pigweed environment first).
-
-.. code:: bash
-
-  $ stm32f429i_disc1_test_server
-
-**Note:** If you attach or detach any more boards to your workstation you'll
-need to relaunch this server.
-
-3. Configure GN
----------------
-Tell GN to use the testing server by enabling a build arg specific to the
-stm32f429i-disc1 target.
-
-.. code:: bash
-
-  $ gn args out
-  # Append this line to the file that opens in your editor to tell GN to run
-  # on-device unit tests.
-  pw_use_test_server = true
-
-**Note:** There are several additional dependencies required to test on device:
-libusb-compat, libftdi, and hidapi at the time of writing. On MacOS, these
-dependencies should be installed to the default homebrew location:
-``/usr/local/opt/``.
-
-Done!
------
-Whenever you make code changes and trigger a build, all the affected unit tests
-will be run across the attached boards!
-
-See the demo below for an example of what this all looks like put together:
-
-.. image:: images/pw_watch_on_device_demo.gif
-  :width: 800
-  :alt: pw watch running on-device tests
-
-Building the Documentation
-==========================
-In addition to the markdown documentation, Pigweed has a collection of
-information-rich RST files that are used to generate HTML documentation. All
-the docs are hosted at https://pigweed.dev/, and are built as a part of the
-default build invocation. This makes it easier to make changes and see how they
-turn out. Once built, you can find the rendered HTML documentation at
-``out/docs/gen/docs/html``.
-
-You can explicitly build just the documentation with the command below.
-
-.. code:: bash
-
-  $ ninja -C out docs
-
-This concludes the introduction to developing for upstream Pigweed.
-
-Next steps
-==========
-
-Check out other modules
------------------------
-If you'd like to see more of what Pigweed has to offer, dive into the
-:ref:`docs-module-guides`.
-
-Check out the sample project
-----------------------------
-We have a `sample project
-<https://pigweed.googlesource.com/pigweed/sample_project/+/main/README.md>`_
-that demonstrates how to use Pigweed in your own project. Note that there are
-many ways to leverage Pigweed and the sample project is one approach.
-
-Check out the Hackaday Supercon talk about Pigweed
---------------------------------------------------
-We gave a talk at Hackaday's 2021 supercon, `Give Pigweed a Whirl
-<https://hackaday.com/2021/01/13/remoticon-video-pigweed-brings-embedded-unit-testing-library-integration-to-commandline/>`_
-
-We've made improvements since we gave the talk; for example, we now have RTOS
-primitives.
-
-Set up Pigweed for your own project
-------------------------------------
-We don't yet have thorough documentation for leveraging Pigweed in a separate
-project (our intended use case!). The `sample project
-<https://pigweed.googlesource.com/pigweed/sample_project/+/main/README.md>`_
-shows how to use Pigweed as a library in your broader project, but you may need
-further guidance.
-
-Dropping into our `chat room <https://discord.gg/M9NSeTA>`_ is the most
-immediate way to get help. Alternatively, you can send a note to the `mailing
-list <https://groups.google.com/forum/#!forum/pigweed>`_.
diff --git a/docs/index.rst b/docs/index.rst
index 0b5d901..de7c142 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,173 +1,24 @@
-.. _docs-root:
 .. highlight:: sh
 
+.. mdinclude:: README.md
+
 .. toctree::
   :maxdepth: 1
   :hidden:
 
   Home <self>
-  docs/getting_started
-  docs/concepts/index
-  docs/release_notes/index
+  docs/getting_started.md
   Source Code <https://cs.opensource.google/pigweed/pigweed>
   Code Reviews <https://pigweed-review.googlesource.com>
   Mailing List <https://groups.google.com/forum/#!forum/pigweed>
   Chat Room <https://discord.gg/M9NSeTA>
   Issue Tracker <https://bugs.pigweed.dev/>
-  docs/contributing
-  docs/code_of_conduct
+  CONTRIBUTING.md
+  CODE_OF_CONDUCT.md
   docs/embedded_cpp_guide
-  Style Guide <docs/style_guide>
-  Automated Analysis <automated_analysis>
-  docs/os_abstraction_layers
+  Code Style <docs/style_guide>
   targets
   Build System <build_system>
-  docs/size_optimizations
   FAQ <docs/faq>
   docs/module_structure
   module_guides
-  third_party_support
-
-=======
-Pigweed
-=======
-Pigweed is an open source collection of embedded-targeted libraries--or as we
-like to call them, modules. These modules are building blocks and
-infrastructure that enable faster and more reliable development on
-small-footprint MMU-less 32-bit microcontrollers like the STMicroelectronics
-STM32L452 or the Nordic nRF52832.
-
-.. attention::
-
-  Pigweed is in **early access**; though many modules are shipping in
-  production already. If you're interested in using Pigweed, please reach out
-  in our `chat room <https://discord.gg/M9NSeTA>`_ or on the `mailing list
-  <https://groups.google.com/forum/#!forum/pigweed>`_.
-
-Getting Started
----------------
-If you'd like to get set up with Pigweed, please visit the
-:ref:`docs-getting-started` guide.
-
-What does Pigweed offer?
-------------------------
-There are many modules in Pigweed; this section showcases a selection that
-produce visual output. For more information about the different Pigweed module
-offerings, refer to :ref:`docs-module-guides` section.
-
-``pw_watch`` - Build, flash, run, & test on save
-------------------------------------------------
-In the web development space, file system watchers are prevalent. These
-watchers trigger a web server reload on source change, making development much
-faster. In the embedded space, file system watchers are less prevalent;
-however, they are no less useful! The Pigweed watcher module makes it easy to
-instantly compile, flash, and run tests upon save. Combined with the GN-based
-build which expresses the full dependency tree, only the exact tests affected
-by a file change are run on saves.
-
-The demo below shows :ref:`module-pw_watch` building for a STMicroelectronics
-STM32F429I-DISC1 development board, flashing the board with the affected test,
-and verifying the test runs as expected. Once this is set up, you can attach
-multiple devices to run tests in a distributed manner to reduce the time it
-takes to run tests.
-
-.. image:: docs/images/pw_watch_on_device_demo.gif
-  :width: 800
-  :alt: pw watch running on-device tests
-
-``pw_presubmit`` - Vacuum lint on every commit
-----------------------------------------------
-Presubmit checks are essential tools, but they take work to set up, and
-projects don’t always get around to it. The :ref:`module-pw_presubmit` module
-provides tools for setting up high quality presubmit checks for any project. We
-use this framework to run Pigweed’s presubmit on our workstations and in our
-automated building tools.
-
-The ``pw_presubmit`` module includes ``pw format``, a tool that provides a
-unified interface for automatically formatting code in a variety of languages.
-With ``pw format``, you can format C, C++, Python, GN, and Go code according to
-configurations defined by your project. ``pw format`` leverages existing tools
-like ``clang-format``, and it’s simple to add support for new languages.
-
-.. image:: pw_presubmit/docs/pw_presubmit_demo.gif
-  :width: 800
-  :alt: pw presubmit demo
-
-``pw_env_setup`` - Cross platform embedded compiler setup
----------------------------------------------------------
-A classic problem in the embedded space is reducing the **time from git clone
-to having a binary executing on a device**. An entire suite of tools is needed
-for building non-trivial production embedded projects, and need to be
-installed. For example:
-
-- A C++ compiler for your target device, and also for your host
-- A build system or three; for example, GN, Ninja, CMake, Bazel
-- A code formatting program like clang-format
-- A debugger like OpenOCD to flash and debug your embedded device
-- A known Python version with known modules installed for scripting
-- A Go compiler for the Go-based command line tools
-- ... and so on
-
-In the server space, container solutions like Docker or Podman solve this;
-however, container solutions are a mixed bag for embedded systems development
-where one frequently needs access to native system resources like USB devices,
-or must operate on Windows.
-
-:ref:`module-pw_env_setup` is our compromise solution for this problem that
-works on Mac, Windows, and Linux. It leverages the Chrome Infrastructure
-Packaging Deployment system (CIPD) to bootstrap a Python installation, which in
-turn inflates a virtual environment. The tooling is installed into your
-workspace, and makes no changes to your system. This tooling is designed to be
-reused by any project.
-
-.. image:: docs/images/pw_env_setup_demo.gif
-  :width: 800
-  :alt: pw environment setup demo
-
-``pw_unit_test`` - Embedded testing for MCUs
---------------------------------------------
-Unit testing is important, and Pigweed offers a portable library that’s broadly
-compatible with `Google Test <https://github.com/google/googletest>`_. Unlike
-Google Test, :ref:`module-pw_unit_test` is built on top of embedded friendly
-primitives; for example, it does not use dynamic memory allocation.
-Additionally, it is easy to port to new target platforms by implementing the
-`test event handler interface <https://cs.opensource.google/pigweed/pigweed/+/main:pw_unit_test/public/pw_unit_test/event_handler.h>`_.
-
-Like other modules in Pigweed, ``pw_unit_test`` is designed for use in
-established codebases with their own build system, without the rest of Pigweed
-or the Pigweed integrated GN build. However, when combined with Pigweed's
-build, the result is a flexible and powerful setup that enables easily
-developing code on your desktop (with tests), then running the same tests
-on-device.
-
-.. image:: docs/images/pw_status_test.png
-  :width: 800
-  :alt: pw_status test run natively on host
-
-And more!
----------
-Here is a selection of interesting modules:
-
- - :ref:`module-pw_cpu_exception_cortex_m` - Robust low level hardware fault
-   handler for ARM Cortex-M; the handler even has unit tests written in
-   assembly to verify nested-hardware-fault handling!
-
- - :ref:`module-pw_polyfill` - Similar to JavaScript “polyfill” libraries, this
-   module provides selected C++17 standard library components that are
-   compatible with C++14.
-
- - :ref:`module-pw_tokenizer` - Replace string literals from log statements
-   with 32-bit tokens, to reduce flash use, reduce logging bandwidth, and save
-   formatting cycles from log statements at runtime.
-
- - :ref:`module-pw_kvs` - A key-value-store implementation for flash-backed
-   persistent storage with integrated wear levelling. This is a lightweight
-   alternative to a file system for embedded devices.
-
- - :ref:`module-pw_protobuf` - An early preview of our wire-format-oriented
-   protocol buffer implementation. This protobuf compiler makes a different set
-   of implementation tradeoffs than the most popular protocol buffer library in
-   this space, nanopb.
-
-See the :ref:`docs-module-guides` for the complete list of modules and their
-documentation.
diff --git a/docs/module_guides.rst b/docs/module_guides.rst
index 936a244..f45131e 100644
--- a/docs/module_guides.rst
+++ b/docs/module_guides.rst
@@ -8,7 +8,7 @@
 Shell, Batch and Python.
 
 .. toctree::
-  :titlesonly:
+  :maxdepth: 1
   :glob:
 
   */docs
diff --git a/docs/module_structure.rst b/docs/module_structure.rst
index f97654f..17d3a07 100644
--- a/docs/module_structure.rst
+++ b/docs/module_structure.rst
@@ -188,8 +188,6 @@
     BUILD.gn
     README.md
 
-.. _module-structure-compile-time-configuration:
-
 Compile-time configuration
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 Pigweed modules are intended to be used in a wide variety of environments.
@@ -320,7 +318,7 @@
   config("set_options_in_header_file") {
     cflags = [
       "-include",
-      rebase_path("my_config_overrides.h", root_build_dir),
+      rebase_path("my_config_overrides.h"),
     ]
   }
 
@@ -427,39 +425,39 @@
   accidentally duplicating work, or avoiding writing code that won't get
   accepted.
 
-1. Create module folder following `Module name`_ guidelines.
+1. Create module folder following `Module name`_ guidelines
 2. Add `C++ public headers`_ files in
    ``{pw_module_dir}/public/{pw_module_name}/``
 3. Add `C++ implementation files`_ files in ``{pw_module_dir}/``
 4. Add module documentation
 
-   - Add ``{pw_module_dir}/README.md`` that has a module summary
-   - Add ``{pw_module_dir}/docs.rst`` that contains the main module
-     documentation
+    - Add ``{pw_module_dir}/README.md`` that has a module summary
+    - Add ``{pw_module_dir}/docs.rst`` that contains the main module
+      documentation
 
-5. Add GN build support in ``{pw_module_dir}/BUILD.gn``
+5. Add build support inside of new module
 
-   - Declare tests in ``pw_test_group("tests")``
-   - Declare docs in ``pw_docs_group("docs")``
+    - Add GN with ``{pw_module_dir}/BUILD.gn``
+    - Add Bazel with ``{pw_module_dir}/BUILD``
+    - Add CMake with ``{pw_module_dir}/CMakeLists.txt``
 
-6. Add Bazel build support in ``{pw_module_dir}/BUILD.bazel``
+6. Add folder alias for new module variable in ``/modules.gni``
 
-7. Add CMake build support in ``{pw_module_dir}/CMakeLists.txt``
+    - ``dir_pw_new = get_path_info("pw_new", "abspath")``
 
-8. Add the new module to the ``/PIGWEED_MODULES`` list
+7. Add new module to main GN build
 
-   Modules must be listed one per line with no extra spaces or comments. This
-   automatically adds the new module, its tests, and its docs, to the GN build.
+    - in ``/BUILD.gn`` to ``group("pw_modules")`` using folder alias variable
 
-9. Update the generated Pigweed modules lists file
+8. Add test target for new module in ``/BUILD.gn`` to
+   ``pw_test_group("pw_module_tests")``
+9. Add new module to CMake build
 
-   .. code-block:: bash
+    - In ``/CMakeLists.txt`` add ``add_subdirectory(pw_new)``
 
-     ninja -C out update_modules
+10. Add the new module to docs module
 
-10. Add the new module to CMake build
-
-   - In ``/CMakeLists.txt`` add ``add_subdirectory(pw_new)``
+    - Add in ``docs/BUILD.gn`` to ``group("module_docs")``
 
 11. Run :ref:`module-pw_module-module-check`
 
diff --git a/docs/os_abstraction_layers.rst b/docs/os_abstraction_layers.rst
deleted file mode 100644
index 94969d5..0000000
--- a/docs/os_abstraction_layers.rst
+++ /dev/null
@@ -1,495 +0,0 @@
-.. _docs-os_abstraction_layers:
-
-=====================
-OS Abstraction Layers
-=====================
-Pigweed’s operating system abstraction layers are portable and configurable
-building blocks, giving users full control while maintaining high performance
-and low overhead.
-
-Although we primarily target smaller-footprint MMU-less 32-bit microcontrollers,
-the OS abstraction layers are written to work on everything from single-core
-bare metal low end microcontrollers to asymmetric multiprocessing (AMP) and
-symmetric multiprocessing (SMP) embedded systems using Real Time Operating
-Systems (RTOS). They even fully work on your developer workstation on Linux,
-Windows, or MacOS!
-
-Pigweed has ports for the following systems:
-
-.. list-table::
-
-  * - **Environment**
-    - **Status**
-  * - STL (Mac, Window, & Linux)
-    - **✔ Supported**
-  * - `FreeRTOS <https://www.freertos.org/>`_
-    - **✔ Supported**
-  * - `Azure RTOS (formerly ThreadX) <https://azure.microsoft.com/en-us/services/rtos/>`_
-    - **✔ Supported**
-  * - `SEGGER embOS <https://www.segger.com/products/rtos/embos/>`_
-    - **✔ Supported**
-  * - Baremetal
-    - *In Progress*
-  * - `Zephyr <https://www.zephyrproject.org/>`_
-    - Planned
-  * - `CMSIS-RTOS API v2 & RTX5 <https://www.keil.com/pack/doc/CMSIS/RTOS2/html/index.html>`_
-    - Planned
-
-Pigweed's OS abstraction layers are divided by the **functional grouping of the
-primitives**. Many of our APIs are similar or **nearly identical to C++'s
-Standard Template Library (STL)** with the notable exception that we do not
-support exceptions. We opted to follow the STL's APIs partially because they
-are relatively well thought out and many developers are already familiar with
-them, but also because this means they are compatible with existing helpers in
-the STL; for example, ``std::lock_guard``.
-
----------------
-Time Primitives
----------------
-The :ref:`module-pw_chrono` module provides the building blocks for expressing
-durations, timestamps, and acquiring the current time. This in turn is used by
-other modules, including  :ref:`module-pw_sync` and :ref:`module-pw_thread` as
-the basis for any time bound APIs (i.e. with timeouts and/or deadlines). Note
-that this module is optional and bare metal targets may opt not to use this.
-
-.. list-table::
-
-  * - **Supported On**
-    - **SystemClock**
-  * - FreeRTOS
-    - :ref:`module-pw_chrono_freertos`
-  * - ThreadX
-    - :ref:`module-pw_chrono_threadx`
-  * - embOS
-    - :ref:`module-pw_chrono_embos`
-  * - STL
-    - :ref:`module-pw_chrono_stl`
-  * - Zephyr
-    - Planned
-  * - CMSIS-RTOS API v2 & RTX5
-    - Planned
-  * - Baremetal
-    - Planned
-
-
-System Clock
-============
-For RTOS and HAL interactions, we provide a ``pw::chrono::SystemClock`` facade
-which provides 64 bit timestamps and duration support along with a C API. For
-C++ there is an optional virtual wrapper, ``pw::chrono::VirtualSystemClock``,
-around the singleton clock facade to enable dependency injection.
-
-.. code-block:: cpp
-
-  #include <chrono>
-
-  #include "pw_thread/sleep.h"
-
-  using namespace std::literals::chrono_literals;
-
-  void ThisSleeps() {
-    pw::thread::sleep_for(42ms);
-  }
-
-Unlike the STL's time bound templated APIs which are not specific to a
-particular clock, Pigweed's time bound APIs are strongly typed to use the
-``pw::chrono::SystemClock``'s ``duration`` and ``time_points`` directly.
-
-.. code-block:: cpp
-
-  #include "pw_chrono/system_clock.h"
-
-  bool HasThisPointInTimePassed(const SystemClock::time_point timestamp) {
-    return SystemClock::now() > timestamp;
-  }
-
---------------------------
-Synchronization Primitives
---------------------------
-The :ref:`module-pw_sync` provides the building blocks for synchronizing between
-threads and/or interrupts through signaling primitives and critical section lock
-primitives.
-
-Critical Section Lock Primitives
-================================
-Pigweed's locks support Clang's thread safety lock annotations and the STL's
-RAII helpers.
-
-.. list-table::
-
-  * - **Supported On**
-    - **Mutex**
-    - **TimedMutex**
-    - **InterruptSpinLock**
-  * - FreeRTOS
-    - :ref:`module-pw_sync_freertos`
-    - :ref:`module-pw_sync_freertos`
-    - :ref:`module-pw_sync_freertos`
-  * - ThreadX
-    - :ref:`module-pw_sync_threadx`
-    - :ref:`module-pw_sync_threadx`
-    - :ref:`module-pw_sync_threadx`
-  * - embOS
-    - :ref:`module-pw_sync_embos`
-    - :ref:`module-pw_sync_embos`
-    - :ref:`module-pw_sync_embos`
-  * - STL
-    - :ref:`module-pw_sync_stl`
-    - :ref:`module-pw_sync_stl`
-    - :ref:`module-pw_sync_stl`
-  * - Zephyr
-    - Planned
-    - Planned
-    - Planned
-  * - CMSIS-RTOS API v2 & RTX5
-    - Planned
-    - Planned
-    - Planned
-  * - Baremetal
-    - Planned, not ready for use
-    - ✗
-    - Planned, not ready for use
-
-
-Thread Safe Mutex
------------------
-The ``pw::sync::Mutex`` protects shared data from being simultaneously accessed
-by multiple threads. Optionally, the ``pw::sync::TimedMutex`` can be used as an
-extension with timeout and deadline based semantics.
-
-.. code-block:: cpp
-
-  #include <mutex>
-
-  #include "pw_sync/mutex.h"
-
-  pw::sync::Mutex mutex;
-
-  void ThreadSafeCriticalSection() {
-    std::lock_guard lock(mutex);
-    NotThreadSafeCriticalSection();
-  }
-
-Interrupt Safe InterruptSpinLock
---------------------------------
-The ``pw::sync::InterruptSpinLock`` protects shared data from being
-simultaneously accessed by multiple threads and/or interrupts as a targeted
-global lock, with the exception of Non-Maskable Interrupts (NMIs). Unlike global
-interrupt locks, this also works safely and efficiently on SMP systems.
-
-.. code-block:: cpp
-
-  #include <mutex>
-
-  #include "pw_sync/interrupt_spin_lock.h"
-
-  pw::sync::InterruptSpinLock interrupt_spin_lock;
-
-  void InterruptSafeCriticalSection() {
-    std::lock_guard lock(interrupt_spin_lock);
-    NotThreadSafeCriticalSection();
-  }
-
-Signaling Primitives
-====================
-Native signaling primitives tend to vary more compared to critical section locks
-across different platforms. For example, although common signaling primitives
-like semaphores are in most if not all RTOSes and even POSIX, it was not in the
-STL before C++20. Likewise many C++ developers are surprised that conditional
-variables tend to not be natively supported on RTOSes. Although you can usually
-build any signaling primitive based on other native signaling primitives,
-this may come with non-trivial added overhead in ROM, RAM, and execution
-efficiency.
-
-For this reason, Pigweed intends to provide some simpler signaling primitives
-which exist to solve a narrow programming need but can be implemented as
-efficiently as possible for the platform that it is used on. This simpler but
-highly portable class of signaling primitives is intended to ensure that a
-portability efficiency tradeoff does not have to be made up front.
-
-.. list-table::
-
-  * - **Supported On**
-    - **ThreadNotification**
-    - **TimedThreadNotification**
-    - **CountingSemaphore**
-    - **BinarySemaphore**
-  * - FreeRTOS
-    - :ref:`module-pw_sync_freertos`
-    - :ref:`module-pw_sync_freertos`
-    - :ref:`module-pw_sync_freertos`
-    - :ref:`module-pw_sync_freertos`
-  * - ThreadX
-    - :ref:`module-pw_sync_threadx`
-    - :ref:`module-pw_sync_threadx`
-    - :ref:`module-pw_sync_threadx`
-    - :ref:`module-pw_sync_threadx`
-  * - embOS
-    - :ref:`module-pw_sync_embos`
-    - :ref:`module-pw_sync_embos`
-    - :ref:`module-pw_sync_embos`
-    - :ref:`module-pw_sync_embos`
-  * - STL
-    - :ref:`module-pw_sync_stl`
-    - :ref:`module-pw_sync_stl`
-    - :ref:`module-pw_sync_stl`
-    - :ref:`module-pw_sync_stl`
-  * - Zephyr
-    - Planned
-    - Planned
-    - Planned
-    - Planned
-  * - CMSIS-RTOS API v2 & RTX5
-    - Planned
-    - Planned
-    - Planned
-    - Planned
-  * - Baremetal
-    - Planned
-    - ✗
-    - TBD
-    - TBD
-
-Thread Notification
--------------------
-Pigweed intends to provide the ``pw::sync::ThreadNotification`` and
-``pw::sync::TimedThreadNotification`` facades which permit a singler consumer to
-block until an event occurs. This should be backed by the most efficient native
-primitive for a target, regardless of whether that is a semaphore, event flag
-group, condition variable, or direct task notification with a critical section
-something else.
-
-Counting Semaphore
-------------------
-The ``pw::sync::CountingSemaphore`` is a synchronization primitive that can be
-used for counting events and/or resource management where receiver(s) can block
-on acquire until notifier(s) signal by invoking release.
-
-.. code-block:: cpp
-
-  #include "pw_sync/counting_semaphore.h"
-
-  pw::sync::CountingSemaphore event_semaphore;
-
-  void NotifyEventOccurred() {
-    event_semaphore.release();
-  }
-
-  void HandleEventsForever() {
-    while (true) {
-      event_semaphore.acquire();
-      HandleEvent();
-    }
-  }
-
-Binary Semaphore
-----------------
-The ``pw::sync::BinarySemaphore`` is a specialization of the counting semaphore
-with an arbitrary token limit of 1, meaning it's either full or empty.
-
-.. code-block:: cpp
-
-  #include "pw_sync/binary_semaphore.h"
-
-  pw::sync::BinarySemaphore do_foo_semaphore;
-
-  void NotifyResultReady() {
-    result_ready_semaphore.release();
-  }
-
-  void BlockUntilResultReady() {
-    result_ready_semaphore.acquire();
-  }
-
---------------------
-Threading Primitives
---------------------
-The :ref:`module-pw_thread` module provides the building blocks for creating and
-using threads including yielding and sleeping.
-
-.. list-table::
-
-  * - **Supported On**
-    - **Thread Creation**
-    - **Thread Id/Sleep/Yield**
-  * - FreeRTOS
-    - :ref:`module-pw_thread_freertos`
-    - :ref:`module-pw_thread_freertos`
-  * - ThreadX
-    - :ref:`module-pw_thread_threadx`
-    - :ref:`module-pw_thread_threadx`
-  * - embOS
-    - :ref:`module-pw_thread_embos`
-    - :ref:`module-pw_thread_embos`
-  * - STL
-    - :ref:`module-pw_thread_stl`
-    - :ref:`module-pw_thread_stl`
-  * - Zephyr
-    - Planned
-    - Planned
-  * - CMSIS-RTOS API v2 & RTX5
-    - Planned
-    - Planned
-  * - Baremetal
-    - ✗
-    - ✗
-
-Thread Creation
-===============
-The ``pw::thread::Thread``’s API is C++11 STL ``std::thread`` like. Unlike
-``std::thread``, the Pigweed's API requires ``pw::thread::Options`` as an
-argument for creating a thread. This is used to give the user full control over
-the native OS's threading options without getting in your way.
-
-.. code-block:: cpp
-
-  #include "pw_thread/detached_thread.h"
-  #include "pw_thread_freertos/context.h"
-  #include "pw_thread_freertos/options.h"
-
-  pw::thread::freertos::ContextWithStack<42> example_thread_context;
-
-  void StartDetachedExampleThread() {
-     pw::thread::DetachedThread(
-       pw::thread::freertos::Options()
-           .set_name("static_example_thread")
-           .set_priority(kFooPriority)
-           .set_static_context(example_thread_context),
-       example_thread_function);
-  }
-
-Controlling the current thread
-==============================
-Beyond thread creation, Pigweed offers support for sleeping, identifying, and
-yielding the current thread.
-
-.. code-block:: cpp
-
-  #include "pw_thread/yield.h"
-
-  void CooperativeBusyLooper() {
-    while (true) {
-      DoChunkOfWork();
-      pw::this_thread::yield();
-    }
-  }
-
-------------------
-Execution Contexts
-------------------
-Code runs in *execution contexts*. Common examples of execution contexts on
-microcontrollers are **thread context** and **interrupt context**, though there
-are others. Since OS abstactions deal with concurrency, it's important to
-understand what API primitives are safe to call in what contexts.  Since the
-number of execution contexts is too large for Pigweed to cover exhaustively,
-Pigweed has the following classes of APIs:
-
-**Thread Safe APIs** - These APIs are safe to use in any execution context where
-one can use blocking or yielding APIs such as sleeping, blocking on a mutex
-waiting on a semaphore.
-
-**Interrupt (IRQ) Safe APIs** - These APIs can be used in any execution context
-which cannot use blocking and yielding APIs. These APIs must protect themselves
-from preemption from maskable interrupts, etc. This includes critical section
-thread contexts in addition to "real" interrupt contexts. Our definition
-explicitly excludes any interrupts which are not masked when holding a SpinLock,
-those are all considered non-maskable interrupts. An interrupt safe API may
-always be safely used in a context which permits thread safe APIs.
-
-**Non-Maskable Interrupt (NMI) Safe APIs** - Like the Interrupt Safe APIs, these
-can be used in any execution context which cannot use blocking or yielding APIs.
-In addition, these may be used by interrupts which are not masked when for
-example holding a SpinLock like CPU exceptions or C++/POSIX signals. These tend
-to come with significant overhead and restrictions compared to regular interrupt
-safe APIs as they **cannot rely on critical sections**, instead
-only atomic signaling can be used. An interrupt safe API may always be
-used in a context which permits interrupt safe and thread safe APIs.
-
-On naming
-=========
-Instead of having context specific APIs like FreeRTOS's ``...FromISR()``,
-Pigweed has a single API which validates the context requirements through
-``DASSERT`` and ``DCHECK`` in the backends (user configurable). We did this for
-a few reasons:
-
-#. **Too many contexts** - Since there are contexts beyond just thread,
-   interrupt, and NMI, having context-specefic APIs would be a hard to
-   maintain. The proliferation of postfixed APIs (``...FromISR``,
-   ``...FromNMI``, ``...FromThreadCriticalSection``, and so on) would also be
-   confusing for users.
-
-#. **Must verify context anyway** - Backends are requried to enforce context
-   requirements with ``DHCECK`` or related calls, so we chose a simple API
-   which happens to match both the C++'s STL and Google's Abseil.
-
-#. **Multi-context code** - Code running in multiple contexts would need to be
-   duplicated for each context if the APIs were postfixed, or duplicated with
-   macros. The authors chose the duplication/macro route in previous projects
-   and found it clunky and hard to maintain.
-
------------------------------
-Construction & Initialization
------------------------------
-**TL;DR: Pigweed OS primitives are initialized through C++ construction.**
-
-We have chosen to go with a model which initializes the synchronization
-primitive during C++ object construction. This means that there is a requirement
-in order for static instantiation to be safe that the user ensures that any
-necessary kernel and/or platform initialization is done before the global static
-constructors are run which would include construction of the C++ synchronization
-primitives.
-
-In addition this model for now assumes that Pigweed code will always be used to
-construct synchronization primitives used with Pigweed modules. Note that with
-this model the backend provider can decide if they want to statically
-preallocate space for the primitives or rely on dynamic allocation strategies.
-If we discover at a later point that this is not sufficiently portable than we
-can either produce an optional constructor that takes in a reference to an
-existing native synchronization type and wastes a little bit RAM or we can
-refactor the existing class into two layers where one is a StaticMutex for
-example and the other is a Mutex which only holds a handle to the native mutex
-type. This would then permit users who cannot construct their synchronization
-primitives to skip the optional static layer.
-
-Kernel / Platform Initialization Before C++ Global Static Constructors
-======================================================================
-What is this kernel and/or platform initialization that must be done first?
-
-It's not uncommon for an RTOS to require some initialization functions to be
-invoked before more of its API can be safely used. For example for CMSIS RTOSv2
-``osKernelInitialize()`` must be invoked before anything but two basic getters
-are called. Similarly, Segger's embOS requires ``OS_Init()`` to be invoked first
-before any other embOS API.
-
-.. Note::
-  To get around this one should invoke these initialization functions earlier
-  and/or delay the static C++ constructors to meet this ordering requirement. As
-  an example if you were using :ref:`module-pw_boot_cortex_m`, then
-  ``pw_boot_PreStaticConstructorInit()`` would be a great place to invoke kernel
-  initialization.
-
--------
-Roadmap
--------
-Pigweed is still actively expanding and improving its OS Abstraction Layers.
-That being said, the following concrete areas are being worked on and can be
-expected to land at some point in the future:
-
-1. We'd like to offer a system clock based timer abstraction facade which can be
-   used on either an RTOS or a hardware timer.
-2. We are evaluating a less-portable but very useful portability facade for
-   event flags / groups. This would make it even easier to ensure all firmware
-   can be fully executed on the host.
-3. Cooperative cancellation thread joining along with a ``std::jthread`` like
-   wrapper is in progress.
-4. We'd like to add support for queues, message queues, and similar channel
-   abstractions which also support interprocessor communication in a transparent
-   manner.
-5. We're interested in supporting asynchronous worker queues and worker queue
-   pools.
-6. Migrate HAL and similar APIs to use deadlines for the backend virtual
-   interfaces to permit a smaller vtable which supports both public timeout and
-   deadline semantics.
-7. Baremetal support is partially in place today, but it's not ready for use.
-8. Most of our APIs today are focused around synchronous blocking APIs, however
-   we would love to extend this to include asynchronous APIs.
diff --git a/docs/python_build.rst b/docs/python_build.rst
index d032613..b89c788 100644
--- a/docs/python_build.rst
+++ b/docs/python_build.rst
@@ -176,60 +176,25 @@
 :ref:`docs-module-structure`).
 
 .. code-block::
-  :caption: Example layout of a Pigweed Python package.
-  :name: python-file-tree
 
   module_name/
   ├── py/
-  │   ├── BUILD.gn
-  │   ├── setup.cfg
-  │   ├── setup.py
-  │   ├── pyproject.toml
-  │   ├── package_name/
-  │   │   ├── module_a.py
-  │   │   ├── module_b.py
-  │   │   ├── py.typed
-  │   │   └── nested_package/
-  │   │       ├── py.typed
-  │   │       └── module_c.py
-  │   ├── module_a_test.py
-  │   └── module_c_test.py
+  │   ├── BUILD.gn
+  │   ├── setup.py
+  │   ├── package_name/
+  │   │   ├── module_a.py
+  │   │   ├── module_b.py
+  │   │   ├── py.typed
+  │   │   └── nested_package/
+  │   │       ├── py.typed
+  │   │       └── module_c.py
+  │   ├── module_a_test.py
+  │   └── module_c_test.py
   └── ...
 
 The ``BUILD.gn`` declares this package in GN. For upstream Pigweed, a presubmit
 check in ensures that all Python files are listed in a ``BUILD.gn``.
 
-Pigweed prefers to define Python packages using ``setup.cfg`` files. In the
-above file tree ``setup.py`` and ``pyproject.toml`` files are stubs with the
-following content:
-
-.. code-block::
-  :caption: setup.py
-  :name: setup-py-stub
-
-  import setuptools  # type: ignore
-  setuptools.setup()  # Package definition in setup.cfg
-
-.. code-block::
-  :caption: pyproject.toml
-  :name: pyproject-toml-stub
-
-  [build-system]
-  requires = ['setuptools', 'wheel']
-  build-backend = 'setuptools.build_meta'
-
-The stub ``setup.py`` file is there to support running ``pip install --editable``.
-
-Each ``pyproject.toml`` file is required to specify which build system should be
-used for the given Python package. In Pigweed's case it always specifies using
-setuptools.
-
-.. seealso::
-
-   - ``setup.cfg`` examples at `Configuring setup() using setup.cfg files`_
-   - ``pyproject.toml`` background at `Build System Support - How to use it?`_
-
-
 .. _module-pw_build-python-target:
 
 pw_python_package targets
@@ -317,10 +282,10 @@
 
 The ``.wheel`` subtarget of ``pw_python_package`` records the location of
 the generated wheel with `GN metadata
-<https://gn.googlesource.com/gn/+/HEAD/docs/reference.md#var_metadata>`_.
+<https://gn.googlesource.com/gn/+/master/docs/reference.md#var_metadata>`_.
 Wheels for a Python package and its transitive dependencies can be collected
 from the ``pw_python_package_wheels`` key. See
-:ref:`module-pw_build-python-dist`.
+:ref:`module-pw_build-python-wheels`.
 
 Protocol buffers
 ^^^^^^^^^^^^^^^^
@@ -349,6 +314,3 @@
   - :ref:`module-pw_build-python`
   - :ref:`module-pw_build`
   - :ref:`docs-build-system`
-
-.. _Configuring setup() using setup.cfg files: https://ipython.readthedocs.io/en/stable/interactive/reference.html#embedding
-.. _Build System Support - How to use it?: https://setuptools.readthedocs.io/en/latest/build_meta.html?highlight=pyproject.toml#how-to-use-it
diff --git a/docs/release_notes/2022_jan.rst b/docs/release_notes/2022_jan.rst
deleted file mode 100644
index f7244e5..0000000
--- a/docs/release_notes/2022_jan.rst
+++ /dev/null
@@ -1,192 +0,0 @@
-.. _docs-release-notes-2022-jan:
-
-===================================
-Pigweed: What's New in January 2022
-===================================
-Happy new year from the Pigweed team! We’re excited to share what we’ve been up
-to this month and we’re really looking forward to what 2022 will bring to the
-Pigweed community.
-
-:ref:`Pigweed<docs-root>` is a collection of libraries and tools for building
-robust embedded software, efficiently. Pigweed allows you to write code that
-runs transparently on both your host machine and tiny 32-bit microcontrollers
-like those in the :ref:`STM32<target-stm32f429i-disc1>` and
-:ref:`Arduino<target-arduino>` families, while giving you the comforts of modern
-software development traditionally lacking in embedded systems, like
-:ref:`easy unit testing<module-pw_unit_test>`,
-:ref:`powerful build systems<docs-build-system>`,
-:ref:`flexible logging<module-pw_log>`, and
-:ref:`reliable communication<module-pw_rpc>`.
-
-.. admonition:: Note
-   :class: warning
-
-   Many Pigweed modules are already shipping in commercial products, but it is
-   still an early access project. Find out if
-   :ref:`Pigweed is right for you<docs-concepts-right-for-my-project>`.
-
-Pigweed is a free and open source project and we welcome contributions! Join us
-on `Discord <https://discord.gg/M9NSeTA>`_ to share feedback, ask questions, and
-get involved with the Pigweed community!
-
-------------------------------
-Experimental Pigweed framework
-------------------------------
-.. admonition:: tl;dr
-   :class: checkmark
-
-   We’re starting the “whole OS” framework version of Pigweed! It’s not ready
-   for use yet but you might want to take a peek.
-
-Pigweed is designed to be highly modular—you can use as many or as few of the
-Pigweed modules as you need for your project, and those modules will work
-flexibly in any project structure. This works great when you want to add Pigweed
-super powers like hybrid host/target unit testing or RPC communication to an
-existing project. While Pigweed gives you nearly all of the tools you need to
-efficiently build a robust, reliable embedded project, until now we haven’t had
-a great solution for building a new project on Pigweed.
-
-The Pigweed framework assembles an opinionated project structure, build system,
-and development environment that does three key things:
-
-* Takes care of difficult but unproductive project plumbing issues like setting
-  up a target toolchain and providing support for
-  :ref:`OS abstractions<docs-os_abstraction_layers>`.
-
-* Configures Pigweed module backends that give you logging, asserts, threads,
-  dynamic memory allocation, and more, that work transparently both on host and
-  on target
-
-* Sets up a productive development environment with rich code analysis and
-  powerful device interaction tools
-
-You can experiment with this right now by checking out the ``pw_system``
-:ref:`documentation<module-pw_system>`. The experimental configuration leverages
-FreeRTOS and runs on the STM32F429I Discovery board. With a
-:ref:`few simple commands<target-stm32f429i-disc1-stm32cube>`, you can have a
-complete embedded development environment set up and focus on building your
-product.
-
-.. warning::
-
-   The Pigweed framework is still in very active development and you should
-   expect breaking changes in the future. If you’re experimenting with it, we
-   would love to hear from you! Join us on
-   `Discord <https://discord.gg/M9NSeTA>`_!
-
--------------------------------------
-Support for plugins in ``pw_console``
--------------------------------------
-Teams that use Pigweed quickly come to rely on the
-:ref:`console<module-pw_console>` as a vital tool for interacting with their
-devices via RPC. It’s now possible to tailor the console to meet your project’s
-specific needs through a new :ref:`plugin interface<module-pw_console-plugins>`.
-You can build your own menus, window panes, keybindings, and clickable buttons
-to truly make ``pw_console`` your own.
-
-How are you using the Pigweed console in your project? Let us know on
-`Discord <https://discord.gg/M9NSeTA>`_!
-
-------------------------------------
-Expanded support for Bazel and CMake
-------------------------------------
-Pigweed’s primary build system is
-`GN (Generate Ninja) <https://gn.googlesource.com/gn>`_, but to make it easier
-to use Pigweed modules in existing projects, we have been expanding support for
-the `Bazel <https://bazel.build/>`_ and `CMake <https://cmake.org/>`_ build
-systems. Right now, the best way to determine which build systems a module
-supports is to look out for ``BUILD.gn``, ``BUILD.bazel`` and ``CMakeLists.txt``
-files (respectively) in module directories. While we work on improving build
-system support and documentation, check out the
-:ref:`build system documentation<docs-build-system>` for more detailed
-information and join us on Discord for support.
-
-----------------------------------------
-Changes to the RPC ``ChannelOutput`` API
-----------------------------------------
-RPC endpoints use :ref:`ChannelOutput<module-pw_rpc-ChannelOutput>` instances to
-send packets encoding RPC data. To send an encoded RPC packet, we need a buffer
-containing the packet’s data. In the past, we could request a buffer by doing
-something like this:
-
-.. code-block:: cpp
-
-   auto buffer = pw::rpc::ChannelOutput::AcquireBuffer(buffer_size)
-   // fill in the buffer here
-   pw::rpc::ChannelOutput::SendAndReleaseBuffer(buffer)
-
-The ``ChannelOutput::AcquireBuffer`` and ``ChannelOutput::SendAndReleaseBuffer``
-methods are no longer part of ``ChannelOutput``’s public API, making its
-internal buffer private. Now, we create our own buffer and ``ChannelOutput`` is
-simply responsible for sending it:
-
-.. code-block:: cpp
-
-   auto buffer = ... // create your own local buffer with RPC packet data
-   pw::rpc::ChannelOutput::Send(buffer)
-
-This approach avoids several tricky concurrency issues related to buffer
-lifetimes, and simplifies the ``ChannelOutput`` API. It also opens up the
-possibility of projects managing RPC buffers in more flexible ways, e.g. via
-dynamically-allocated memory or separate shared memory mechanisms.
-
-.. warning::
-
-   This is a breaking change if you update pw_rpc, but one that can be fixed
-   quickly.
-
-We’re actively reviewing the RPC API with a view towards significantly improving
-it in the future. Share your input with us on
-`Discord <https://discord.gg/M9NSeTA>`_!
-
-------------
-More Updates
-------------
-* It’s now possible to generate a token database from a list of strings in a
-  JSON file for ``pw_tokenizer``. This can be useful when you need to tokenize
-  strings that can’t be parsed from compiled binaries.
-
-* ``pw_assert``‘s new ``pw_assert_tokenized`` backend provides a much more
-  space-efficient implementation compared to using ``pw_assert_log`` with
-  ``pw_log_tokenized``. However, there are trade offs to consider, so check out
-  the :ref:`documentation<module-pw_assert_tokenized>`.
-
-* CMake builds now support compile-time module configuration similar to GN
-  through the use of the ``pw_add_module_config`` and ``pw_set_module_config``
-  functions.
-
-* In ``pw_build``, it is now possible to set a specific working directory for
-  :ref:`pw_exec<module-pw_build-pw_exec>` actions.
-
-* ``pw_cpu_exception`` now supports the ARMv8M Mainline architecture in
-  ``pw_cpu_exception_cortex_m``. This allows us to take advantage of stack limit
-  boundary features in microcontrollers using that architecture, like Cortex M33
-  and M35P.
-
-------------
-Get Involved
-------------
-.. tip::
-
-   We welcome contributions from the community! Here are just a few
-   opportunities to get involved.
-
-* Pigweed now includes GN build files for
-  `TinyUSB <https://github.com/hathach/tinyusb>`_, a popular USB library for
-  embedded systems. Projects can now include it by cloning the TinyUSB
-  repository and configuring GN to build it. But right now, we lack interfaces
-  between TinyUSB and Pigweed abstractions like pw_stream. This is a great
-  opportunity to help get very useful functionality across the finish line.
-
-* We’re very interested in supporting the
-  `Raspberry Pi Pico <https://www.raspberrypi.com/products/raspberry-pi-pico/>`_
-  and the ecosystem of devices using the RP2040 microcontroller. We will be
-  working in earnest on this in the coming months and welcome anyone who wants
-  to lend a helping hand!
-
-* Evolving the Pigweed framework from its current experimental state to a
-  relatively complete embedded project platform is one of our major focuses this
-  year, and we want your help. That help can range from providing input on what
-  you’re looking for in a framework, to building small projects with it and
-  providing feedback, up to contributing directly to its development. Join us to
-  talk about it on `Discord <https://discord.gg/M9NSeTA>`_!
diff --git a/docs/release_notes/index.rst b/docs/release_notes/index.rst
deleted file mode 100644
index 4340b00..0000000
--- a/docs/release_notes/index.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-.. _docs-release-notes:
-
-=============
-Release Notes
-=============
-
-.. toctree::
-   :titlesonly:
-
-   January 2022 <2022_jan>
diff --git a/docs/size_optimizations.rst b/docs/size_optimizations.rst
deleted file mode 100644
index 5c409fe..0000000
--- a/docs/size_optimizations.rst
+++ /dev/null
@@ -1,637 +0,0 @@
-.. _docs-size-optimizations:
-
-==================
-Size Optimizations
-==================
-This page contains recommendations for optimizing the size of embedded software
-including its memory and code footprints.
-
-These recommendations are subject to change as the C++ standard and compilers
-evolve, and as the authors continue to gain more knowledge and experience in
-this area. If you disagree with recommendations, please discuss them with the
-Pigweed team, as we're always looking to improve the guide or correct any
-inaccuracies.
-
----------------------------------
-Compile Time Constant Expressions
----------------------------------
-The use of `constexpr <https://en.cppreference.com/w/cpp/language/constexpr>`_
-and soon with C++20
-`consteval <https://en.cppreference.com/w/cpp/language/consteval>`_ can enable
-you to evaluate the value of a function or variable more at compile-time rather
-than only at run-time. This can often not only result in smaller sizes but also
-often times more efficient, faster execution.
-
-We highly encourage using this aspect of C++, however there is one caveat: be
-careful in marking functions constexpr in APIs which cannot be easily changed
-in the future unless you can prove that for all time and all platforms, the
-computation can actually be done at compile time. This is because there is no
-"mutable" escape hatch for constexpr.
-
-See the :doc:`embedded_cpp_guide` for more detail.
-
----------
-Templates
----------
-The compiler implements templates by generating a separate version of the
-function for each set of types it is instantiated with. This can increase code
-size significantly.
-
-Be careful when instantiating non-trivial template functions with multiple
-types.
-
-Consider splitting templated interfaces into multiple layers so that more of the
-implementation can be shared between different instantiations. A more advanced
-form is to share common logic internally by using default sentinel template
-argument value and ergo instantation such as ``pw::Vector``'s
-``size_t kMaxSize = vector_impl::kGeneric`` or ``std::span``'s
-``size_t Extent = dynamic_extent``.
-
------------------
-Virtual Functions
------------------
-Virtual functions provide for runtime polymorphism. Unless runtime polymorphism
-is required, virtual functions should be avoided. Virtual functions require a
-virtual table and a pointer to it in each instance, which all increases RAM
-usage and requires extra instructions at each call site. Virtual functions can
-also inhibit compiler optimizations, since the compiler may not be able to tell
-which functions will actually be invoked. This can prevent linker garbage
-collection, resulting in unused functions being linked into a binary.
-
-When runtime polymorphism is required, virtual functions should be considered.
-C alternatives, such as a struct of function pointers, could be used instead,
-but these approaches may offer no performance advantage while sacrificing
-flexibility and ease of use.
-
-Only use virtual functions when runtime polymorphism is needed. Lastly try to
-avoid templated virtual interfaces which can compound the cost by instantiating
-many virtual tables.
-
-Devirtualization
-================
-When you do use virtual functions, try to keep devirtualization in mind. You can
-make it easier on the compiler and linker by declaring class definitions as
-``final`` to improve the odds. This can help significantly depending on your
-toolchain.
-
-If you're interested in more details,
-`this is an interesting deep dive <https://quuxplusone.github.io/blog/2021/02/15/devirtualization/>`_.
-
----------------------------------------------------------
-Initialization, Constructors, Finalizers, and Destructors
----------------------------------------------------------
-Constructors
-============
-Where possible consider making your constructors constexpr to reduce their
-costs. This also enables global instances to be eligible for ``.data`` or if
-all zeros for ``.bss`` section placement.
-
-Static Destructors And Finalizers
-=================================
-For many embedded projects, cleaning up after the program is not a requiement,
-meaning the exit functions including any finalizers registered through
-``atexit``, ``at_quick_exit``, and static destructors can all be removed to
-reduce the size.
-
-The exact mechanics for disabling static destructors depends on your toolchain.
-
-See the `Ignored Finalizer and Destructor Registration`_ section below for
-further details regarding disabling registration of functions to be run at exit
-via ``atexit`` and ``at_quick_exit``.
-
-Clang
------
-With modern versions of Clang you can simply use ``-fno-C++-static-destructors``
-and you are done.
-
-GCC with newlib-nano
---------------------
-With GCC this is more complicated. For example with GCC for ARM Cortex M devices
-using ``newlib-nano`` you are forced to tackle the problem in two stages.
-
-First, there are the destructors for the static global objects. These can be
-placed in the ``.fini_array`` and ``.fini`` input sections through the use of
-the ``-fno-use-cxa-atexit`` GCC flag, assuming ``newlib-nano`` was configured
-with ``HAVE_INITFINI_ARAY_SUPPORT``. The two input sections can then be
-explicitly discarded in the linker script through the use of the special
-``/DISCARD/`` output section:
-
-.. code-block:: text
-
-      /DISCARD/ : {
-      /* The finalizers are never invoked when the target shuts down and ergo
-       * can be discarded. These include C++ global static destructors and C
-       * designated finalizers. */
-      *(.fini_array);
-      *(.fini);
-
-Second, there are the destructors for the scoped static objects, frequently
-referred to as Meyer's Singletons. With the Itanium ABI these use
-``__cxa_atexit`` to register destruction on the fly. However, if
-``-fno-use-cxa-atexit`` is used with GCC and ``newlib-nano`` these will appear
-as ``__tcf_`` prefixed symbols, for example ``__tcf_0``.
-
-There's `an interesting proposal (P1247R0) <http://wg21.link/p1247r0>`_ to
-enable ``[[no_destroy]]`` attributes to C++ which would be tempting to use here.
-Alas this is not an option yet. As mentioned in the proposal one way to remove
-the destructors from these scoped statics is to wrap it in a templated wrapper
-which uses placement new.
-
-.. code-block:: cpp
-
-  #include <type_traits>
-
-  template <class T>
-  class NoDestroy {
-   public:
-    template <class... Ts>
-    NoDestroy(Ts&&... ts) {
-      new (&static_) T(std::forward<Ts>(ts)...);
-    }
-
-    T& get() { return reinterpret_cast<T&>(static_); }
-
-   private:
-    std::aligned_storage_t<sizeof(T), alignof(T)> static_;
-  };
-
-This can then be used as follows to instantiate scoped statics where the
-destructor will never be invoked and ergo will not be linked in.
-
-.. code-block:: cpp
-
-  Foo& GetFoo() {
-    static NoDestroy<Foo> foo(foo_args);
-    return foo.get();
-  }
-
--------
-Strings
--------
-
-Tokenization
-============
-Instead of directly using strings and printf, consider using
-:ref:`module-pw_tokenizer` to replace strings and printf-style formatted strings
-with binary tokens during compilation. This can reduce the code size, memory
-usage, I/O traffic, and even CPU utilization by replacing snprintf calls with
-simple tokenization code.
-
-Be careful when using string arguments with tokenization as these still result
-in a string in your binary which is appended to your token at run time.
-
-String Formatting
-=================
-The formatted output family of printf functions in ``<cstdio>`` are quite
-expensive from a code size point of view and they often rely on malloc. Instead,
-where tokenization cannot be used, consider using :ref:`module-pw_string`'s
-utilities.
-
-Removing all printf functions often saves more than 5KiB of code size on ARM
-Cortex M devices using ``newlib-nano``.
-
-Logging & Asserting
-===================
-Using tokenized backends for logging and asserting such as
-:ref:`module-pw_log_tokenized` coupled with :ref:`module-pw_assert_log` can
-drastically reduce the costs. However, even with this approach there remains a
-callsite cost which can add up due to arguments and including metadata.
-
-Try to avoid string arguments and reduce unnecessary extra arguments where
-possible. And consider adjusting log levels to compile out debug or even info
-logs as code stabilizes and matures.
-
-Future Plans
-------------
-Going forward Pigweed is evaluating extra configuration options to do things
-such as dropping log arguments for certain log levels and modules to give users
-finer grained control in trading off diagnostic value and the size cost.
-
-----------------------------------
-Threading and Synchronization Cost
-----------------------------------
-
-Lighterweight Signaling Primatives
-==================================
-Consider using ``pw::sync::ThreadNotification`` instead of semaphores as they
-can be implemented using more efficient RTOS specific signaling primitives. For
-example on FreeRTOS they can be backed by direct task notifications which are
-more than 10x smaller than semaphores while also being faster.
-
-Threads and their stack sizes
-=============================
-Although synchronous APIs are incredibly portable and often easier to reason
-about, it is often easy to forget the large stack cost this design paradigm
-comes with. We highly recommend watermarking your stacks to reduce wasted
-memory.
-
-Our snapshot integration for RTOSes such as :ref:`module-pw_thread_freertos` and
-:ref:`module-pw_thread_embos` come with built in support to report stack
-watermarks for threads if enabled in the kernel.
-
-In addition, consider using asynchronous design patterns such as Active Objects
-which can use :ref:`module-pw_work_queue` or similar asynchronous dispatch work
-queues to effectively permit the sharing of stack allocations.
-
-Buffer Sizing
-=============
-We'd be remiss not to mention the sizing of the various buffers that may exist
-in your application. You could consider watermarking them with
-:ref:`module-pw_metric`. You may also be able toadjust their servicing interval
-and priority, but do not forget to keep the ingress burst sizes and scheduling
-jitter into account.
-
-----------------------------
-Standard C and C++ libraries
-----------------------------
-Toolchains are typically distrubted with their preferred standard C library and
-standard C++ library of choice for the target platform.
-
-Although you do not always have a choice in what standard C library and what
-what standard C++ library is used or even how it's compiled, we recommend always
-keeping an eye out for common sources of bloat.
-
-Assert
-======
-The standard C library should provides the ``assert`` function or macro which
-may be internally used even if your application does not invoke it directly.
-Although this can be disabled through ``NDEBUG`` there typically is not a
-portable way of replacing the ``assert(condition)`` implementation without
-configuring and recompiling your standard C library.
-
-However, you can consider replacing the implementation at link time with a
-cheaper implementation. For example ``newlib-nano``, which comes with the
-``GNU Arm Embedded Toolchain``, often has an expensive ``__assert_func``
-implementation which uses ``fiprintf`` to print to ``stderr`` before invoking
-``abort()``. This can be replaced with a simple ``PW_CRASH`` invocation which
-can save several kilobytes in case ``fiprintf`` isn't used elsewhere.
-
-One option to remove this bloat is to use ``--wrap`` at link time to replace
-these implementations. As an example in GN you could replace it with the
-following ``BUILD.gn`` file:
-
-.. code-block:: text
-
-  import("//build_overrides/pigweed.gni")
-
-  import("$dir_pw_build/target_types.gni")
-
-  # Wraps the function called by newlib's implementation of assert from stdlib.h.
-  #
-  # When using this, we suggest injecting :newlib_assert via pw_build_LINK_DEPS.
-  config("wrap_newlib_assert") {
-    ldflags = [ "-Wl,--wrap=__assert_func" ]
-  }
-
-  # Implements the function called by newlib's implementation of assert from
-  # stdlib.h which invokes __assert_func unless NDEBUG is defined.
-  pw_source_set("wrapped_newlib_assert") {
-    sources = [ "wrapped_newlib_assert.cc" ]
-    deps = [
-      "$dir_pw_assert:check",
-      "$dir_pw_preprocessor",
-    ]
-  }
-
-And a ``wrapped_newlib_assert.cc`` source file implementing the wrapped assert
-function:
-
-.. code-block:: cpp
-
-  #include "pw_assert/check.h"
-  #include "pw_preprocessor/compiler.h"
-
-  // This is defined by <cassert>
-  extern "C" PW_NO_RETURN void __wrap___assert_func(const char*,
-                                                    int,
-                                                    const char*,
-                                                    const char*) {
-    PW_CRASH("libc assert() failure");
-  }
-
-
-Ignored Finalizer and Destructor Registration
-=============================================
-Even if no cleanup is done during shutdown for your target, shutdown functions
-such as ``atexit``, ``at_quick_exit``, and ``__cxa_atexit`` can sometimes not be
-linked out. This may be due to vendor code or perhaps using scoped statics, also
-known as Meyer's Singletons.
-
-The registration of these destructors and finalizers may include locks, malloc,
-and more depending on your standard C library and its configuration.
-
-One option to remove this bloat is to use ``--wrap`` at link time to replace
-these implementations with ones which do nothing. As an example in GN you could
-replace it with the following ``BUILD.gn`` file:
-
-.. code-block:: text
-
-  import("//build_overrides/pigweed.gni")
-
-  import("$dir_pw_build/target_types.gni")
-
-  config("wrap_atexit") {
-    ldflags = [
-      "-Wl,--wrap=atexit",
-      "-Wl,--wrap=at_quick_exit",
-      "-Wl,--wrap=__cxa_atexit",
-    ]
-  }
-
-  # Implements atexit, at_quick_exit, and __cxa_atexit from stdlib.h with noop
-  # versions for targets which do not cleanup during exit and quick_exit.
-  #
-  # This removes any dependencies which may exist in your existing libc.
-  # Although this removes the ability for things such as Meyer's Singletons,
-  # i.e. non-global statics, to register destruction function it does not permit
-  # them to be garbage collected by the linker.
-  pw_source_set("wrapped_noop_atexit") {
-    sources = [ "wrapped_noop_atexit.cc" ]
-  }
-
-And a ``wrapped_noop_atexit.cc`` source file implementing the noop functions:
-
-.. code-block:: cpp
-
-  // These two are defined by <cstdlib>.
-  extern "C" int __wrap_atexit(void (*)(void)) { return 0; }
-  extern "C" int __wrap_at_quick_exit(void (*)(void)) { return 0; }
-
-  // This function is part of the Itanium C++ ABI, there is no header which
-  // provides this.
-  extern "C" int __wrap___cxa_atexit(void (*)(void*), void*, void*) { return 0; }
-
-Unexpected Bloat in Disabled STL Exceptions
-===========================================
-The GCC
-`manual <https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_exceptions.html>`_
-recommends using ``-fno-exceptions`` along with ``-fno-unwind-tables`` to
-disable exceptions and any associated overhead. This should replace all throw
-statements with calls to ``abort()``.
-
-However, what we've noticed with the GCC and ``libstdc++`` is that there is a
-risk that the STL will still throw exceptions when the application is compiled
-with ``-fno-exceptions`` and there is no way for you to catch them. In theory,
-this is not unsafe because the unhandled exception will invoke ``abort()`` via
-``std::terminate()``. This can occur because the libraries such as
-``libstdc++.a`` may not have been compiled with ``-fno-exceptions`` even though
-your application is linked against it.
-
-See
-`this <https://blog.mozilla.org/nnethercote/2011/01/18/the-dangers-of-fno-exceptions/>`_
-for more information.
-
-Unfortunately there can be significant overhead surrounding these throw call
-sites in the ``std::__throw_*`` helper functions. These implementations such as
-``std::__throw_out_of_range_fmt(const char*, ...)`` and
-their snprintf and ergo malloc dependencies can very quickly add up to many
-kilobytes of unnecessary overhead.
-
-One option to remove this bloat while also making sure that the exceptions will
-actually result in an effective ``abort()`` is to use ``--wrap`` at link time to
-replace these implementations with ones which simply call ``PW_CRASH``.
-
-As an example in GN you could replace it with the following ``BUILD.gn`` file,
-note that the mangled names must be used:
-
-.. code-block:: text
-
-  import("//build_overrides/pigweed.gni")
-
-  import("$dir_pw_build/target_types.gni")
-
-  # Wraps the std::__throw_* functions called by GNU ISO C++ Library regardless
-  # of whether "-fno-exceptions" is specified.
-  #
-  # When using this, we suggest injecting :wrapped_libstdc++_functexcept via
-  # pw_build_LINK_DEPS.
-  config("wrap_libstdc++_functexcept") {
-    ldflags = [
-      "-Wl,--wrap=_ZSt21__throw_bad_exceptionv",
-      "-Wl,--wrap=_ZSt17__throw_bad_allocv",
-      "-Wl,--wrap=_ZSt16__throw_bad_castv",
-      "-Wl,--wrap=_ZSt18__throw_bad_typeidv",
-      "-Wl,--wrap=_ZSt19__throw_logic_errorPKc",
-      "-Wl,--wrap=_ZSt20__throw_domain_errorPKc",
-      "-Wl,--wrap=_ZSt24__throw_invalid_argumentPKc",
-      "-Wl,--wrap=_ZSt20__throw_length_errorPKc",
-      "-Wl,--wrap=_ZSt20__throw_out_of_rangePKc",
-      "-Wl,--wrap=_ZSt24__throw_out_of_range_fmtPKcz",
-      "-Wl,--wrap=_ZSt21__throw_runtime_errorPKc",
-      "-Wl,--wrap=_ZSt19__throw_range_errorPKc",
-      "-Wl,--wrap=_ZSt22__throw_overflow_errorPKc",
-      "-Wl,--wrap=_ZSt23__throw_underflow_errorPKc",
-      "-Wl,--wrap=_ZSt19__throw_ios_failurePKc",
-      "-Wl,--wrap=_ZSt19__throw_ios_failurePKci",
-      "-Wl,--wrap=_ZSt20__throw_system_errori",
-      "-Wl,--wrap=_ZSt20__throw_future_errori",
-      "-Wl,--wrap=_ZSt25__throw_bad_function_callv",
-    ]
-  }
-
-  # Implements the std::__throw_* functions called by GNU ISO C++ Library
-  # regardless of whether "-fno-exceptions" is specified with PW_CRASH.
-  pw_source_set("wrapped_libstdc++_functexcept") {
-    sources = [ "wrapped_libstdc++_functexcept.cc" ]
-    deps = [
-      "$dir_pw_assert:check",
-      "$dir_pw_preprocessor",
-    ]
-  }
-
-And a ``wrapped_libstdc++_functexcept.cc`` source file implementing each
-wrapped and mangled ``std::__throw_*`` function:
-
-.. code-block:: cpp
-
- #include "pw_assert/check.h"
- #include "pw_preprocessor/compiler.h"
-
- // These are all wrapped implementations of the throw functions provided by
- // libstdc++'s bits/functexcept.h which are not needed when "-fno-exceptions"
- // is used.
-
- // std::__throw_bad_exception(void)
- extern "C" PW_NO_RETURN void __wrap__ZSt21__throw_bad_exceptionv() {
-   PW_CRASH("std::throw_bad_exception");
- }
-
- // std::__throw_bad_alloc(void)
- extern "C" PW_NO_RETURN void __wrap__ZSt17__throw_bad_allocv() {
-   PW_CRASH("std::throw_bad_alloc");
- }
-
- // std::__throw_bad_cast(void)
- extern "C" PW_NO_RETURN void __wrap__ZSt16__throw_bad_castv() {
-   PW_CRASH("std::throw_bad_cast");
- }
-
- // std::__throw_bad_typeid(void)
- extern "C" PW_NO_RETURN void __wrap__ZSt18__throw_bad_typeidv() {
-   PW_CRASH("std::throw_bad_typeid");
- }
-
- // std::__throw_logic_error(const char*)
- extern "C" PW_NO_RETURN void __wrap__ZSt19__throw_logic_errorPKc(const char*) {
-   PW_CRASH("std::throw_logic_error");
- }
-
- // std::__throw_domain_error(const char*)
- extern "C" PW_NO_RETURN void __wrap__ZSt20__throw_domain_errorPKc(const char*) {
-   PW_CRASH("std::throw_domain_error");
- }
-
- // std::__throw_invalid_argument(const char*)
- extern "C" PW_NO_RETURN void __wrap__ZSt24__throw_invalid_argumentPKc(
-     const char*) {
-   PW_CRASH("std::throw_invalid_argument");
- }
-
- // std::__throw_length_error(const char*)
- extern "C" PW_NO_RETURN void __wrap__ZSt20__throw_length_errorPKc(const char*) {
-   PW_CRASH("std::throw_length_error");
- }
-
- // std::__throw_out_of_range(const char*)
- extern "C" PW_NO_RETURN void __wrap__ZSt20__throw_out_of_rangePKc(const char*) {
-   PW_CRASH("std::throw_out_of_range");
- }
-
- // std::__throw_out_of_range_fmt(const char*, ...)
- extern "C" PW_NO_RETURN void __wrap__ZSt24__throw_out_of_range_fmtPKcz(
-     const char*, ...) {
-   PW_CRASH("std::throw_out_of_range");
- }
-
- // std::__throw_runtime_error(const char*)
- extern "C" PW_NO_RETURN void __wrap__ZSt21__throw_runtime_errorPKc(
-     const char*) {
-   PW_CRASH("std::throw_runtime_error");
- }
-
- // std::__throw_range_error(const char*)
- extern "C" PW_NO_RETURN void __wrap__ZSt19__throw_range_errorPKc(const char*) {
-   PW_CRASH("std::throw_range_error");
- }
-
- // std::__throw_overflow_error(const char*)
- extern "C" PW_NO_RETURN void __wrap__ZSt22__throw_overflow_errorPKc(
-     const char*) {
-   PW_CRASH("std::throw_overflow_error");
- }
-
- // std::__throw_underflow_error(const char*)
- extern "C" PW_NO_RETURN void __wrap__ZSt23__throw_underflow_errorPKc(
-     const char*) {
-   PW_CRASH("std::throw_underflow_error");
- }
-
- // std::__throw_ios_failure(const char*)
- extern "C" PW_NO_RETURN void __wrap__ZSt19__throw_ios_failurePKc(const char*) {
-   PW_CRASH("std::throw_ios_failure");
- }
-
- // std::__throw_ios_failure(const char*, int)
- extern "C" PW_NO_RETURN void __wrap__ZSt19__throw_ios_failurePKci(const char*,
-                                                                   int) {
-   PW_CRASH("std::throw_ios_failure");
- }
-
- // std::__throw_system_error(int)
- extern "C" PW_NO_RETURN void __wrap__ZSt20__throw_system_errori(int) {
-   PW_CRASH("std::throw_system_error");
- }
-
- // std::__throw_future_error(int)
- extern "C" PW_NO_RETURN void __wrap__ZSt20__throw_future_errori(int) {
-   PW_CRASH("std::throw_future_error");
- }
-
- // std::__throw_bad_function_call(void)
- extern "C" PW_NO_RETURN void __wrap__ZSt25__throw_bad_function_callv() {
-   PW_CRASH("std::throw_bad_function_call");
- }
-
----------------------------------
-Compiler and Linker Optimizations
----------------------------------
-
-Compiler Optimization Options
-=============================
-Don't forget to configure your compiler to optimize for size if needed. With
-Clang this is ``-Oz`` and with GCC this can be done via ``-Os``. The GN
-toolchains provided through :ref:`module-pw_toolchain` which are optimize for
-size are suffixed with ``*_size_optimized``.
-
-Garbage collect function and data sections
-==========================================
-By default the linker will place all functions in an object within the same
-linker "section" (e.g. ``.text``). With Clang and GCC you can use
-``-ffunction-sections`` and ``-fdata-sections`` to use a unique "section" for
-each object (e.g. ``.text.do_foo_function``). This permits you to pass
-``--gc-sections`` to the linker to cull any unused sections which were not
-referenced.
-
-To see what sections were garbage collected you can pass ``--print-gc-sections``
-to the linker so it prints out what was removed.
-
-The GN toolchains provided through :ref:`module-pw_toolchain` are configured to
-do this by default.
-
-Function Inlining
-=================
-Don't forget to expose trivial functions such as member accessors as inline
-definitions in the header. The compiler and linker can make the trade-off on
-whether the function should be actually inlined or not based on your
-optimization settings, however this at least gives it the option. Note that LTO
-can inline functions which are not defined in headers.
-
-We stand by the
-`Google style guide <https://google.github.io/styleguide/cppguide.html#Inline_Functions>`_
-to recommend considering this for simple functions which are 10 lines or less.
-
-Link Time Optimization (LTO)
-============================
-Consider using LTO to further reduce the size of the binary if needed. This
-tends to be very effective at devirtualization.
-
-Unfortunately, the aggressive inlining done by LTO can make it much more
-difficult to triage bugs. In addition, it can significantly increase the total
-build time.
-
-On GCC and Clang this can be enabled by passing ``-flto`` to both the compiler
-and the linker. In addition, on GCC ``-fdevirtualize-at-ltrans`` can be
-optionally used to devirtualize more aggressively.
-
-Disabling Scoped Static Initialization Locks
-============================================
-C++11 requires that scoped static objects are initialized in a thread-safe
-manner. This also means that scoped statics, i.e. Meyer's Singletons, be
-thread-safe. Unfortunately this rarely is the case on embedded targets. For
-example with GCC on an ARM Cortex M device if you test for this you will
-discover that instead the program crashes as reentrant initialization is
-detected through the use of guard variables.
-
-With GCC and Clang, ``-fno-threadsafe-statics`` can be used to remove the global
-lock which often does not work for embedded targets. Note that this leaves the
-guard variables in place which ensure that reentrant initialization continues to
-crash.
-
-Be careful when using this option in case you are relying on threadsafe
-initialization of statics and the global locks were functional for your target.
-
-Triaging Unexpectedly Linked In Functions
-=========================================
-Lastly as a tip if you cannot figure out why a function is being linked in you
-can consider:
-
-* Using ``--wrap`` with the linker to remove the implementation, resulting in a
-  link failure which typically calls out which calling function can no longer be
-  linked.
-* With GCC, you can use ``-fcallgraph-info`` to visualize or otherwise inspect
-  the callgraph to figure out who is calling what.
-* Sometimes symbolizing the address can resolve what a function is for. For
-  example if you are using ``newlib-nano`` along with ``-fno-use-cxa-atexit``,
-  scoped static destructors are prefixed ``__tcf_*``. To figure out object these
-  destructor functions are associated with, you can use ``llvm-symbolizer`` or
-  ``addr2line`` and these will often print out the related object's name.
diff --git a/docs/style_guide.rst b/docs/style_guide.rst
index 0cb1aa9..e05bde9 100644
--- a/docs/style_guide.rst
+++ b/docs/style_guide.rst
@@ -1,8 +1,9 @@
 .. _docs-pw-style:
 
-===========
-Style Guide
-===========
+===========================
+Style Guide and Conventions
+===========================
+
 .. tip::
   Pigweed runs ``pw format`` as part of ``pw presubmit`` to perform some code
   formatting checks. To speed up the review process, consider adding ``pw
@@ -12,6 +13,7 @@
 ---------
 C++ style
 ---------
+
 The Pigweed C++ style guide is closely based on Google's external C++ Style
 Guide, which is found on the web at
 https://google.github.io/styleguide/cppguide.html. The Google C++ Style Guide
@@ -26,20 +28,6 @@
 Pigweed style guide, but are separated out since it covers more general
 embedded development beyond just C++ style.
 
-C++ standard
-============
-Pigweed primarily uses the C++17 standard. A few modules maintain support for
-C++14, however (e.g. :ref:`module-pw_kvs` and its dependencies).
-
-All Pigweed C++ code must compile with ``-std=C++17`` in Clang and GCC. C++20
-features may be used as long as the code still compiles unmodified with C++17.
-See ``pw_polyfill/language_feature_macros.h`` for macros that provide C++20
-features when supported.
-
-Compiler extensions should not be used unless wrapped in a macro or properly
-guarded in the preprocessor. See ``pw_processor/compiler.h`` for macros that
-wrap compiler-specific features.
-
 Automatic formatting
 ====================
 Pigweed uses `clang-format <https://clang.llvm.org/docs/ClangFormat.html>`_ to
@@ -100,51 +88,46 @@
 must be avoided. Care must be exercised when using multiple instantiations of a
 template function, which can lead to code bloat.
 
-Permitted Headers
------------------
-.. admonition:: The following C++ Standard Library headers are always permitted:
-   :class: checkmark
+The following C++ Standard Library headers are always permitted:
 
-   * ``<array>``
-   * ``<complex>``
-   * ``<initializer_list>``
-   * ``<iterator>``
-   * ``<limits>``
-   * ``<optional>``
-   * ``<random>``
-   * ``<ratio>``
-   * ``<span>``
-   * ``<string_view>``
-   * ``<tuple>``
-   * ``<type_traits>``
-   * ``<utility>``
-   * ``<variant>``
-   * C Standard Library headers (``<c*>``)
+  * ``<array>``
+  * ``<complex>``
+  * ``<initializer_list>``
+  * ``<iterator>``
+  * ``<limits>``
+  * ``<optional>``
+  * ``<random>``
+  * ``<ratio>``
+  * ``<span>``
+  * ``<string_view>``
+  * ``<tuple>``
+  * ``<type_traits>``
+  * ``<utility>``
+  * ``<variant>``
+  * C Standard Library headers (``<c*>``)
 
-.. admonition:: With caution, parts of the following headers can be used:
-   :class: warning
+With caution, parts of the following headers can be used:
 
-   * ``<algorithm>`` -- be wary of potential memory allocation
-   * ``<atomic>`` -- not all MCUs natively support atomic operations
-   * ``<bitset>`` -- conversions to or from strings are disallowed
-   * ``<functional>`` -- do **not** use ``std::function``
-   * ``<new>`` -- for placement new
-   * ``<numeric>`` -- be wary of code size with multiple template instantiations
+  * ``<algorithm>`` -- be wary of potential memory allocation
+  * ``<atomic>`` -- not all MCUs natively support atomic operations
+  * ``<bitset>`` -- conversions to or from strings are disallowed
+  * ``<functional>`` -- do **not** use ``std::function``
+  * ``<new>`` -- for placement new
+  * ``<numeric>`` -- be wary of code size with multiple template instantiations
 
-.. admonition:: Never use any of these headers:
-   :class: error
+Never use any of these headers:
 
-   * Dynamic containers (``<list>``, ``<map>``, ``<set>``, ``<vector>``, etc.)
-   * Streams (``<iostream>``, ``<ostream>``, ``<fstream>``, etc.)
-   * ``<exception>``
-   * ``<future>``, ``<mutex>``, ``<thread>``
-   * ``<memory>``
-   * ``<regex>``
-   * ``<scoped_allocator>``
-   * ``<sstream>``
-   * ``<stdexcept>``
-   * ``<string>``
-   * ``<valarray>``
+  * Dynamic containers (``<list>``, ``<map>``, ``<set>``, ``<vector>``, etc.)
+  * Streams (``<iostream>``, ``<ostream>``, ``<fstream>``, etc.)
+  * ``<exception>``
+  * ``<future>``, ``<mutex>``, ``<thread>``
+  * ``<memory>``
+  * ``<regex>``
+  * ``<scoped_allocator>``
+  * ``<sstream>``
+  * ``<stdexcept>``
+  * ``<string>``
+  * ``<valarray>``
 
 Headers not listed here should be carefully evaluated before they are used.
 
@@ -221,7 +204,7 @@
 
 .. code-block:: cpp
 
-  // Copyright 2021 The Pigweed Authors
+  // Copyright 2020 The Pigweed Authors
   //
   // 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
@@ -258,57 +241,60 @@
 <https://google.github.io/styleguide/cppguide.html>`_, with the following
 additional requirements.
 
-C++ code
---------
-* All Pigweed C++ code must be in the ``pw`` namespace. Namespaces for modules
-  should be nested under ``pw``. For example, ``pw::string::Format()``.
-* Whenever possible, private code should be in a source (.cc) file and placed in
-  anonymous namespace nested under ``pw``.
-* If private code must be exposed in a header file, it must be in a namespace
-  nested under ``pw``. The namespace may be named for its subsystem or use a
-  name that designates it as private, such as ``internal``.
-* Template arguments for non-type names (e.g. ``template <int kFooBar>``) should
-  follow the constexpr and const variable Google naming convention, which means
-  k prefixed camel case (e.g.  ``kCamelCase``). This matches the Google C++
-  style for variable naming, however the wording in the official style guide
-  isn't explicit for template arguments and could be interpreted to use
-  ``foo_bar`` style naming.  For consistency with other variables whose value is
-  always fixed for the duration of the program, the naming convention is
-  ``kCamelCase``, and so that is the style we use in Pigweed.
+**C++ code**
+  * All Pigweed C++ code must be in the ``pw`` namespace. Namespaces for
+    modules should be nested under ``pw``. For example,
+    ``pw::string::Format()``.
+  * Whenever possible, private code should be in a source (.cc) file and placed
+    in anonymous namespace nested under ``pw``.
+  * If private code must be exposed in a header file, it must be in a namespace
+    nested under ``pw``. The namespace may be named for its subsystem or use a
+    name that designates it as private, such as ``internal``.
+  * Template arguments for non-type names (e.g. ``template <int kFooBar>``)
+    should follow the constexpr and const variable Google naming convention,
+    which means k prefixed camel case (e.g.
+    ``kCamelCase``). This matches the Google C++ style for variable naming,
+    however the wording in the official style guide isn't explicit for template
+    arguments and could be interpreted to use ``foo_bar`` style naming.
+    For consistency with other variables whose value is always fixed for the
+    duration of the program, the naming convention is ``kCamelCase``, and so
+    that is the style we use in Pigweed.
 
-C code
-------
+    **Note:** At time of writing much of Pigweed incorrectly follows the
+    ``snake_case`` naming for non-type template arguments. This is a bug that
+    will be fixed eventually.
+
+**C code**
 In general, C symbols should be prefixed with the module name. If the symbol is
 not associated with a module, use just ``pw`` as the module name. Facade
 backends may chose to prefix symbols with the facade's name to help reduce the
 length of the prefix.
 
-* Public names used by C code must be prefixed with the module name (e.g.
-  ``pw_tokenizer_*``).
-* If private code must be exposed in a header, private names used by C code must
-  be prefixed with an underscore followed by the module name (e.g.
-  ``_pw_assert_*``).
-* Avoid writing C source (.c) files in Pigweed. Prefer to write C++ code with C
-  linkage using ``extern "C"``. Within C source, private C functions and
-  variables must be named with the ``_pw_my_module_*`` prefix and should be
-  declared ``static`` whenever possible; for example,
-  ``_pw_my_module_MyPrivateFunction``.
-* The C prefix rules apply to
+  * Public names used by C code must be prefixed with the module name (e.g.
+    ``pw_tokenizer_*``).
+  * If private code must be exposed in a header, private names used by C code
+    must be prefixed with an underscore followed by the module name (e.g.
+    ``_pw_assert_*``).
+  * Avoid writing C source (.c) files in Pigweed. Prefer to write C++ code with
+    C linkage using ``extern "C"``. Within C source, private C functions and
+    variables must be named with the ``_pw_my_module_*`` prefix and should be
+    declared ``static`` whenever possible; for example,
+    ``_pw_my_module_MyPrivateFunction``.
+  * The C prefix rules apply to
 
-  * C functions (``int pw_foo_FunctionName(void);``),
-  * variables used by C code (``int pw_foo_variable_name;``),
-  * constant variables used by C code (``int pw_foo_kConstantName;``),
-  * structs used by C code (``typedef struct {} pw_foo_StructName;``), and
-  * all of the above for ``extern "C"`` names in C++ code.
+    * C functions (``int pw_foo_FunctionName(void);``),
+    * variables used by C code (``int pw_foo_variable_name;``),
+    * constant variables used by C code (``int pw_foo_kConstantName;``),
+    * structs used by C code (``typedef struct {} pw_foo_StructName;``), and
+    * all of the above for ``extern "C"`` names in C++ code.
 
-  The prefix does not apply to struct members, which use normal Google style.
+    The prefix does not apply to struct members, which use normal Google style.
 
-Preprocessor macros
--------------------
-* Public Pigweed macros must be prefixed with the module name (e.g.
-  ``PW_MY_MODULE_*``).
-* Private Pigweed macros must be prefixed with an underscore followed by the
-  module name (e.g. ``_PW_MY_MODULE_*``).
+**Preprocessor macros**
+  * Public Pigweed macros must be prefixed with the module name (e.g.
+    ``PW_MY_MODULE_*``).
+  * Private Pigweed macros must be prefixed with an underscore followed by the
+    module name (e.g. ``_PW_MY_MODULE_*``).
 
 **Example**
 
@@ -421,7 +407,9 @@
 
 Prefer storing references over storing pointers. Pointers are required when the
 pointer can change its target or may be ``nullptr``. Otherwise, a reference or
-const reference should be used.
+const reference should be used. In accordance with the Google C++ style guide,
+only const references are permitted as function arguments; pointers must be used
+in place of mutable references when passed as function arguments.
 
 Preprocessor macros
 ===================
@@ -520,14 +508,14 @@
 When using macros for conditional compilation, prefer to use ``#if`` over
 ``#ifdef``. This checks the value of the macro rather than whether it exists.
 
-* ``#if`` handles undefined macros equivalently to ``#ifdef``. Undefined
-  macros expand to 0 in preprocessor conditional statements.
-* ``#if`` evaluates false for macros defined as 0, while ``#ifdef`` evaluates
-  true.
-* Macros defined using compiler flags have a default value of 1 in GCC and
-  Clang, so they work equivalently for ``#if`` and ``#ifdef``.
-* Macros defined to an empty statement cause compile-time errors in ``#if``
-  statements, which avoids ambiguity about how the macro should be used.
+ * ``#if`` handles undefined macros equivalently to ``#ifdef``. Undefined
+   macros expand to 0 in preprocessor conditional statements.
+ * ``#if`` evaluates false for macros defined as 0, while ``#ifdef`` evaluates
+   true.
+ * Macros defined using compiler flags have a default value of 1 in GCC and
+   Clang, so they work equivalently for ``#if`` and ``#ifdef``.
+ * Macros defined to an empty statement cause compile-time errors in ``#if``
+   statements, which avoids ambiguity about how the macro should be used.
 
 All ``#endif`` statements should be commented with the expression from their
 corresponding ``#if``. Do not indent within preprocessor conditional statements.
@@ -546,16 +534,6 @@
 code and the C++ Standard Library. Be very careful mixing signed and unsigned
 integers.
 
-Features not in the C++ standard
-================================
-Avoid features not available in standard C++. This includes compiler extensions
-and features from other standards like POSIX.
-
-For example, use ``ptrdiff_t`` instead of POSIX's ``ssize_t``, unless
-interacting with a POSIX API in intentionally non-portable code. Never use
-POSIX functions with suitable standard or Pigweed alternatives, such as
-``strnlen`` (use ``pw::string::NullTerminatedLength`` instead).
-
 ------------
 Python style
 ------------
@@ -571,23 +549,24 @@
 ---------------
 Build files: GN
 ---------------
-Each Pigweed source module requires a GN build file named BUILD.gn. This
+
+Each Pigweed source module will require a build file named BUILD.gn which
 encapsulates the build targets and specifies their sources and dependencies.
-GN build files use a format similar to `Bazel's BUILD files
-<https://docs.bazel.build/versions/main/build-ref.html>`_
-(see the `Bazel style guide
-<https://docs.bazel.build/versions/main/skylark/build-style.html>`_).
+The format of this file is similar in structure to the
+`Bazel/Blaze format <https://docs.bazel.build/versions/3.2.0/build-ref.html>`_
+(Googlers may also review `go/build-style <go/build-style>`_), but with
+nomenclature specific to Pigweed. For each target specified within the build
+file there are a list of dependency fields. Those fields, in their expected
+order, are:
 
-C/C++ build targets include a list of fields. The primary fields are:
+  * ``<public_config>`` -- external build configuration
+  * ``<public_deps>`` -- necessary public dependencies (ie: Pigweed headers)
+  * ``<public>`` -- exposed package public interface header files
+  * ``<config>`` -- package build configuration
+  * ``<sources>`` -- package source code
+  * ``<deps>`` -- package necessary local dependencies
 
-* ``<public>`` -- public header files
-* ``<sources>`` -- source files and private header files
-* ``<public_configs>`` -- public build configuration
-* ``<configs>`` -- private build configuration
-* ``<public_deps>`` -- public dependencies
-* ``<deps>`` -- private dependencies
-
-Assets within each field must be listed in alphabetical order.
+Assets within each field must be listed in alphabetical order
 
 .. code-block:: cpp
 
@@ -595,20 +574,19 @@
 
   import("$dir_pw_unit_test/test.gni")
 
-  config("public_include_path") {
+  config("default_config") {
     include_dirs = [ "public" ]
-    visibility = [":*"]
   }
 
-  pw_source_set("pw_sample_module") {
+  source_set("pw_sample_module") {
+    public_configs = [ ":default_config" ]
+    public_deps = [ dir_pw_status ]
     public = [ "public/pw_sample_module/sample_module.h" ]
     sources = [
-      "public/pw_sample_module/internal/secret_header.h",
+      "public/pw_sample_module/internal/sample_module.h",
       "sample_module.cc",
       "used_by_sample_module.cc",
     ]
-    public_configs = [ ":public_include_path" ]
-    public_deps = [ dir_pw_status ]
     deps = [ dir_pw_varint ]
   }
 
@@ -624,203 +602,3 @@
   pw_doc_group("docs") {
     sources = [ "docs.rst" ]
   }
-
-------------------
-Build files: Bazel
-------------------
-Build files for the Bazel build system must be named ``BUILD.bazel``. Bazel can
-interpret files named just ``BUILD``, but Pigweed uses ``BUILD.bazel`` to avoid
-ambiguity with other build systems or tooling.
-
-Pigweed's Bazel files follow the `Bazel style guide
-<https://docs.bazel.build/versions/main/skylark/build-style.html>`_.
-
--------------
-Documentation
--------------
-.. note::
-
-   Pigweed's documentation style guide came after much of the documentation was
-   written, so Pigweed's docs don't yet 100% conform to this style guide. When
-   updating docs, please update them to match the style guide.
-
-Pigweed documentation is written using the `reStructuredText
-<https://docutils.sourceforge.io/rst.html>`_ markup language and processed by
-`Sphinx`_. We use the `Furo theme <https://github.com/pradyunsg/furo>`_ along
-with the `sphinx-design <https://sphinx-design.readthedocs.io/en/furo-theme/>`_
-extension.
-
-Syntax Reference Links
-======================
-.. admonition:: See also
-   :class: seealso
-
-   - `reStructuredText Primer`_
-
-   - `reStructuredText Directives <https://docutils.sourceforge.io/docs/ref/rst/directives.html>`_
-
-   - `Furo Reference <https://pradyunsg.me/furo/reference/>`_
-
-   - `Sphinx-design Reference <https://sphinx-design.readthedocs.io/en/furo-theme/>`_
-
-ReST is flexible, supporting formatting the same logical document in a few ways
-(for example headings, blank lines). Pigweed has the following restrictions to
-make our documentation consistent.
-
-Headings
-========
-Use headings according to the following hierarchy, with the shown characters
-for the ReST heading syntax.
-
-.. code:: rst
-
-   ==================================
-   Document Title: Two Bars of Equals
-   ==================================
-   Document titles use equals ("====="), above and below. Capitalize the words
-   in the title, except for 'of' and 'the'.
-
-   ---------------------------
-   Major Sections Within a Doc
-   ---------------------------
-   Major sections use hypens ("----"), above and below. Capitalize the words in
-   the title, except for 'of' and 'the'.
-
-   Heading 1 - For Sections Within a Doc
-   =====================================
-   These should be title cased. Use a single equals bar ("====").
-
-   Heading 2 - for subsections
-   ---------------------------
-   Subsections use hypens ("----"). In many cases, these headings may be
-   sentence-like. In those cases, only the first letter should be capitalized.
-   For example, FAQ subsections would have a title with "Why does the X do the
-   Y?"; note the sentence capitalization (but not title capitalization).
-
-   Heading 3 - for subsubsections
-   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   Use the caret symbol ("^^^^") for subsubsections.
-
-   Note: Generally don't go beyond heading 3.
-
-   Heading 4 - for subsubsubsections
-   .................................
-   Don't use this heading level, but if you must, use period characters
-   ("....") for the heading.
-
-Do not put blank lines after headings.
---------------------------------------
-.. admonition:: **Yes**: No blank after heading
-   :class: checkmark
-
-   .. code:: rst
-
-      Here is a heading
-      -----------------
-      Note that there is no blank line after the heading separator!
-
-.. admonition:: **No**: Unnecessary blank line
-   :class: error
-
-   .. code:: rst
-
-      Here is a heading
-      -----------------
-
-      There is a totally unnecessary blank line above this one. Don't do this.
-
-Do not put multiple blank lines before a heading.
--------------------------------------------------
-.. admonition:: **Yes**: Just one blank after section content before the next heading
-   :class: checkmark
-
-   .. code:: rst
-
-      There is some text here in the section before the next. It's just here to
-      illustrate the spacing standard. Note that there is just one blank line
-      after this paragraph.
-
-      Just one blank!
-      ---------------
-      There is just one blank line before the heading.
-
-.. admonition:: **No**: Extra blank lines
-   :class: error
-
-   .. code:: rst
-
-      There is some text here in the section before the next. It's just here to
-      illustrate the spacing standard. Note that there are too many blank lines
-      after this paragraph; there should be just one.
-
-
-
-      Too many blanks
-      ---------------
-      There are too many blanks before the heading for this section.
-
-Directives
-==========
-Indent directives 3 spaces; and put a blank line between the directive and the
-content. This aligns the directive content with the directive name.
-
-.. admonition:: **Yes**: Three space indent for directives; and nested
-   :class: checkmark
-
-   .. code:: none
-
-      Here is a paragraph that has some content. After this content is a
-      directive.
-
-      .. my_directive::
-
-         Note that this line's start aligns with the "m" above. The 3-space
-         alignment accounts for the ".. " prefix for directives, to vertically
-         align the directive name with the content.
-
-         This indentation must continue for nested directives.
-
-         .. nested_directive::
-
-            Here is some nested directive content.
-
-.. admonition:: **No**: One space, two spaces, four spaces, or other indents
-   for directives
-   :class: error
-
-   .. code:: none
-
-      Here is a paragraph with some content.
-
-      .. my_directive::
-
-        The indentation here is incorrect! It's one space short; doesn't align
-        with the directive name above.
-
-        .. nested_directive::
-
-            This isn't indented correctly either; it's too much (4 spaces).
-
-.. admonition:: **No**: Missing blank between directive and content.
-   :class: error
-
-   .. code:: none
-
-      Here is a paragraph with some content.
-
-      .. my_directive::
-         Note the lack of blank line above here.
-
-Tables
-======
-Consider using ``.. list-table::`` syntax, which is more maintainable and
-easier to edit for complex tables (`details
-<https://docutils.sourceforge.io/docs/ref/rst/directives.html#list-table>`_).
-
-.. _Sphinx: https://www.sphinx-doc.org/
-
-.. inclusive-language: disable
-
-.. _reStructuredText Primer: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html
-
-.. inclusive-language: enable
diff --git a/docs/targets.rst b/docs/targets.rst
index 7c13097..a72ae4a 100644
--- a/docs/targets.rst
+++ b/docs/targets.rst
@@ -25,14 +25,24 @@
 these as a base for board-specific toolchains, then creates its final targets on
 top of those.
 
-.. mermaid::
+.. blockdiag::
 
-  graph TD
-    arm_gcc --> arm_gcc_cortex_m4
-    arm_gcc --> arm_gcc_cortex_m4f
-    arm_gcc_cortex_m4f --> arm_gcc_cortex_m4f_debug
-    arm_gcc_cortex_m4f --> arm_gcc_cortex_m4f_size_optimized
-    arm_gcc_cortex_m4f_debug --> stm32f429i_disc1_debug
+  blockdiag {
+    default_fontsize = 14;
+    orientation = portrait;
+
+    arm_gcc  [label = "arm_gcc"];
+    arm_gcc_cortex_m4  [label = "cortex_m4"];
+    arm_gcc_cortex_m4f  [label = "cortex_m4f"];
+    arm_gcc_cortex_m4f_debug  [label = "cortex_m4f_debug"];
+    arm_gcc_cortex_m4f_size_optimized  [label = "cortex_m4f_size_optimized"];
+    stm32f429i_disc1_debug  [label = "stm32f429i_disc1_debug"];
+    arm_gcc -> arm_gcc_cortex_m4
+    arm_gcc -> arm_gcc_cortex_m4f
+    arm_gcc_cortex_m4f -> arm_gcc_cortex_m4f_debug
+    arm_gcc_cortex_m4f -> arm_gcc_cortex_m4f_size_optimized
+    arm_gcc_cortex_m4f_debug -> stm32f429i_disc1_debug
+  }
 
 Toolchain target variables
 --------------------------
@@ -51,7 +61,7 @@
 * ``default_public_deps``: List of GN targets which are added as a dependency
   to all ``pw_*`` GN targets. This is used to add global module dependencies;
   for example, in upstream, ``pw_polyfill`` is added here to provide C++17
-  features in C++14 code.
+  features in C++11/C++14 code.
 * Facade backends: Pigweed defines facades to provide a common interface for
   core system features such as logging without assuming an implementation.
   When building a Pigweed target, the implementations for each of these must be
@@ -67,8 +77,9 @@
 The code below demonstrates how a project might configure one of its Pigweed
 targets.
 
-.. code-block::
+.. code::
 
+  # Prevent gn format from reordering this import.
   import("//build_overrides/pigweed.gni")
 
   import("$dir_pw_toolchain/arm_gcc/toolchains.gni")
diff --git a/docs/third_party_support.rst b/docs/third_party_support.rst
deleted file mode 100644
index 9fedae1..0000000
--- a/docs/third_party_support.rst
+++ /dev/null
@@ -1,14 +0,0 @@
-.. _docs-third-party-support:
-
-===================
-Third Party Support
-===================
-Pigweed is a collection of embedded-focused and embedded-related modules where
-some may have third party dependencies. To facilitate this Pigweed provides a
-set of third party modules with toolchain support and other helpers.
-
-.. toctree::
-  :maxdepth: 1
-  :glob:
-
-  third_party/*/docs
diff --git a/modules.gni b/modules.gni
index fc6f47b..7da2072 100644
--- a/modules.gni
+++ b/modules.gni
@@ -1,4 +1,4 @@
-# Copyright 2022 The Pigweed Authors
+# Copyright 2020 The Pigweed Authors
 #
 # 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
@@ -12,15 +12,99 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
-# Generate a .gni file with a GN arg for each module and lists of modules and
-# tests. Then, import the generated .gni file.
-#
-# To avoid generating it multiple times, the modules list is only generated by
-# the default toolchain. The default toolchain runs before any other toolchains,
-# so the module list will be created before it is used by other toolchains.
-
-import("pw_build/generated_pigweed_modules_lists.gni")
-
 declare_args() {
+  # This file defines a directory variable for each of Pigweed's modules. This
+  # allows modules to be moved or swapped out without breaking existing builds.
+  # All module variables are prefixed with dir_.
+  dir_docker = get_path_info("docker", "abspath")
+  dir_pw_allocator = get_path_info("pw_allocator", "abspath")
+  dir_pw_arduino_build = get_path_info("pw_arduino_build", "abspath")
+  dir_pw_assert = get_path_info("pw_assert", "abspath")
+  dir_pw_assert_basic = get_path_info("pw_assert_basic", "abspath")
+  dir_pw_assert_log = get_path_info("pw_assert_log", "abspath")
+  dir_pw_base64 = get_path_info("pw_base64", "abspath")
+  dir_pw_bloat = get_path_info("pw_bloat", "abspath")
+  dir_pw_blob_store = get_path_info("pw_blob_store", "abspath")
+  dir_pw_boot_armv7m = get_path_info("pw_boot_armv7m", "abspath")
+  dir_pw_build = get_path_info("pw_build", "abspath")
+  dir_pw_bytes = get_path_info("pw_bytes", "abspath")
+  dir_pw_checksum = get_path_info("pw_checksum", "abspath")
+  dir_pw_chrono = get_path_info("pw_chrono", "abspath")
+  dir_pw_chrono_embos = get_path_info("pw_chrono_embos", "abspath")
+  dir_pw_chrono_freertos = get_path_info("pw_chrono_freertos", "abspath")
+  dir_pw_chrono_stl = get_path_info("pw_chrono_stl", "abspath")
+  dir_pw_chrono_threadx = get_path_info("pw_chrono_threadx", "abspath")
+  dir_pw_cli = get_path_info("pw_cli", "abspath")
+  dir_pw_containers = get_path_info("pw_containers", "abspath")
+  dir_pw_cpu_exception = get_path_info("pw_cpu_exception", "abspath")
+  dir_pw_cpu_exception_cortex_m =
+      get_path_info("pw_cpu_exception_cortex_m", "abspath")
+  dir_pw_docgen = get_path_info("pw_docgen", "abspath")
+  dir_pw_doctor = get_path_info("pw_doctor", "abspath")
+  dir_pw_hex_dump = get_path_info("pw_hex_dump", "abspath")
+  dir_pw_env_setup = get_path_info("pw_env_setup", "abspath")
+  dir_pw_hdlc = get_path_info("pw_hdlc", "abspath")
+  dir_pw_i2c = get_path_info("pw_i2c", "abspath")
+  dir_pw_interrupt = get_path_info("pw_interrupt", "abspath")
+  dir_pw_interrupt_cortex_m = get_path_info("pw_interrupt_cortex_m", "abspath")
+  dir_pw_kvs = get_path_info("pw_kvs", "abspath")
+  dir_pw_log = get_path_info("pw_log", "abspath")
+  dir_pw_log_basic = get_path_info("pw_log_basic", "abspath")
+  dir_pw_log_multisink = get_path_info("pw_log_multisink", "abspath")
+  dir_pw_log_null = get_path_info("pw_log_null", "abspath")
+  dir_pw_log_rpc = get_path_info("pw_log_rpc", "abspath")
+  dir_pw_log_sink = get_path_info("pw_log_sink", "abspath")
+  dir_pw_log_tokenized = get_path_info("pw_log_tokenized", "abspath")
+  dir_pw_malloc = get_path_info("pw_malloc", "abspath")
+  dir_pw_malloc_freelist = get_path_info("pw_malloc_freelist", "abspath")
+  dir_pw_metric = get_path_info("pw_metric", "abspath")
+  dir_pw_minimal_cpp_stdlib = get_path_info("pw_minimal_cpp_stdlib", "abspath")
+  dir_pw_module = get_path_info("pw_module", "abspath")
+  dir_pw_multisink = get_path_info("pw_multisink", "abspath")
+  dir_pw_fuzzer = get_path_info("pw_fuzzer", "abspath")
+  dir_pw_package = get_path_info("pw_package", "abspath")
+  dir_pw_persistent_ram = get_path_info("pw_persistent_ram", "abspath")
+  dir_pw_polyfill = get_path_info("pw_polyfill", "abspath")
+  dir_pw_preprocessor = get_path_info("pw_preprocessor", "abspath")
+  dir_pw_presubmit = get_path_info("pw_presubmit", "abspath")
+  dir_pw_protobuf = get_path_info("pw_protobuf", "abspath")
+  dir_pw_protobuf_compiler = get_path_info("pw_protobuf_compiler", "abspath")
+  dir_pw_random = get_path_info("pw_random", "abspath")
+  dir_pw_result = get_path_info("pw_result", "abspath")
+  dir_pw_ring_buffer = get_path_info("pw_ring_buffer", "abspath")
+  dir_pw_router = get_path_info("pw_router", "abspath")
+  dir_pw_rpc = get_path_info("pw_rpc", "abspath")
+  dir_pw_span = get_path_info("pw_span", "abspath")
+  dir_pw_status = get_path_info("pw_status", "abspath")
+  dir_pw_stream = get_path_info("pw_stream", "abspath")
+  dir_pw_string = get_path_info("pw_string", "abspath")
+  dir_pw_sync = get_path_info("pw_sync", "abspath")
+  dir_pw_sync_embos = get_path_info("pw_sync_embos", "abspath")
+  dir_pw_sync_freertos = get_path_info("pw_sync_freertos", "abspath")
+  dir_pw_sync_baremetal = get_path_info("pw_sync_baremetal", "abspath")
+  dir_pw_sync_stl = get_path_info("pw_sync_stl", "abspath")
+  dir_pw_sync_threadx = get_path_info("pw_sync_threadx", "abspath")
+  dir_pw_sys_io = get_path_info("pw_sys_io", "abspath")
+  dir_pw_sys_io_baremetal_lm3s6965evb =
+      get_path_info("pw_sys_io_baremetal_lm3s6965evb", "abspath")
+  dir_pw_sys_io_baremetal_stm32f429 =
+      get_path_info("pw_sys_io_baremetal_stm32f429", "abspath")
+  dir_pw_sys_io_arduino = get_path_info("pw_sys_io_arduino", "abspath")
+  dir_pw_sys_io_stdio = get_path_info("pw_sys_io_stdio", "abspath")
+  dir_pw_target_runner = get_path_info("pw_target_runner", "abspath")
+  dir_pw_thread = get_path_info("pw_thread", "abspath")
+  dir_pw_thread_stl = get_path_info("pw_thread_stl", "abspath")
+  dir_pw_thread_embos = get_path_info("pw_thread_embos", "abspath")
+  dir_pw_thread_freertos = get_path_info("pw_thread_freertos", "abspath")
+  dir_pw_thread_threadx = get_path_info("pw_thread_threadx", "abspath")
   dir_pw_third_party = get_path_info("third_party", "abspath")
+  dir_pw_tokenizer = get_path_info("pw_tokenizer", "abspath")
+  dir_pw_tool = get_path_info("pw_tool", "abspath")
+  dir_pw_toolchain = get_path_info("pw_toolchain", "abspath")
+  dir_pw_trace = get_path_info("pw_trace", "abspath")
+  dir_pw_trace_tokenized = get_path_info("pw_trace_tokenized", "abspath")
+  dir_pw_unit_test = get_path_info("pw_unit_test", "abspath")
+  dir_pw_varint = get_path_info("pw_varint", "abspath")
+  dir_pw_watch = get_path_info("pw_watch", "abspath")
+  dir_pw_web_ui = get_path_info("pw_web_ui", "abspath")
 }
diff --git a/package.json b/package.json
index f3a9852..a37e1c1 100644
--- a/package.json
+++ b/package.json
@@ -5,65 +5,40 @@
   "author": "",
   "license": "Apache-2.0",
   "devDependencies": {
-    "@bazel/concatjs": "4.1.0",
-    "@bazel/esbuild": "4.1.0",
-    "@bazel/ibazel": "^0.15.10",
-    "@bazel/jasmine": "4.1.0",
-    "@bazel/rollup": "4.1.0",
-    "@bazel/typescript": "4.1.0",
-    "@grpc/grpc-js": "^1.3.7",
-    "@material-ui/core": "^4.12.1",
-    "@material-ui/lab": "^4.0.0-alpha.60",
-    "@rollup/plugin-commonjs": "^19.0.0",
-    "@rollup/plugin-node-resolve": "^13.0.0",
-    "@types/argparse": "^2.0.10",
-    "@types/crc": "^3.4.0",
-    "@types/google-protobuf": "^3.15.5",
-    "@types/jasmine": "^3.7.8",
-    "@types/node": "^16.0.1",
-    "@types/react": "^17.0.14",
-    "@types/react-dom": "^17.0.9",
-    "argparse": "^2.0.1",
-    "base64-js": "^1.5.1",
-    "buffer": "^6.0.3",
-    "crc": "^3.8.0",
-    "debug": "^4.3.2",
-    "eslint": "^7.30.0",
-    "eslint-plugin-react": "^7.24.0",
-    "google-protobuf": "^3.17.3",
-    "grpc-tools": "^1.11.2",
-    "grpc-web": "^1.2.1",
-    "gts": "^3.1.0",
-    "http-server": "^13.0.2",
-    "install-peers": "^1.0.3",
-    "jasmine": "^3.8.0",
-    "jasmine-core": "^3.8.0",
-    "karma": "6.3.4",
+    "@bazel/jasmine": "^2.2.0",
+    "@bazel/karma": "^2.2.0",
+    "@bazel/rollup": "^2.2.0",
+    "@bazel/typescript": "^2.2.0",
+    "@material-ui/core": "^4.10.2",
+    "@rollup/plugin-commonjs": "^13.0.0",
+    "@rollup/plugin-node-resolve": "^8.0.1",
+    "@types/jasmine": "^3.5.10",
+    "@types/node": "^13.11.1",
+    "@types/react": "^16.9.36",
+    "@types/react-dom": "^16.9.8",
+    "eslint-plugin-react": "^7.20.0",
+    "gts": "^2.0.2",
+    "jasmine": "^3.5.0",
+    "jasmine-core": "^3.5.0",
+    "karma": "5.0.2",
     "karma-chrome-launcher": "^3.1.0",
-    "karma-firefox-launcher": "^2.1.1",
-    "karma-jasmine": "^4.0.1",
-    "karma-junit-reporter": "^2.0.1",
+    "karma-firefox-launcher": "^1.3.0",
+    "karma-jasmine": "^3.3.1",
     "karma-requirejs": "^1.1.0",
-    "karma-sourcemap-loader": "^0.3.8",
-    "react": "^17.0.2",
-    "react-dom": "^17.0.2",
+    "karma-sourcemap-loader": "^0.3.7",
+    "react": "^16.13.1",
+    "react-dom": "^16.13.1",
     "requirejs": "^2.3.6",
-    "rollup": "^2.52.8",
+    "rollup": "^2.15.0",
     "rollup-plugin-node-builtins": "^2.1.2",
     "rollup-plugin-node-globals": "^1.4.0",
-    "rollup-plugin-sourcemaps": "^0.6.3",
-    "rxjs": "^7.2.0",
-    "tmp": "0.2.1",
-    "ts-protoc-gen": "^0.15.0",
-    "typescript": "^4.3.5",
-    "wait-queue": "^1.1.4"
+    "rollup-plugin-sourcemaps": "^0.6.2",
+    "rxjs": "^6.5.5",
+    "tmp": "0.1.0",
+    "typescript": "^3.9.3"
   },
   "scripts": {
     "check": "gts check",
     "fix": "gts fix"
-  },
-  "dependencies": {
-    "ansi_up": "^5.1.0",
-    "html-react-parser": "^1.4.0"
   }
 }
diff --git a/pw_allocator/BUILD b/pw_allocator/BUILD
new file mode 100644
index 0000000..8492751
--- /dev/null
+++ b/pw_allocator/BUILD
@@ -0,0 +1,103 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "block",
+    srcs = [
+        "block.cc",
+    ],
+    hdrs = [
+        "public/pw_allocator/block.h",
+    ],
+    includes = ["public"],
+    deps = [
+        "//pw_span",
+        "//pw_status",
+    ],
+)
+
+pw_cc_library(
+    name = "freelist",
+    srcs = [
+        "freelist.cc",
+    ],
+    hdrs = [
+        "public/pw_allocator/freelist.h",
+    ],
+    includes = ["public"],
+    deps = [
+        "//pw_containers",
+        "//pw_span",
+        "//pw_status",
+    ],
+)
+
+pw_cc_library(
+    name = "freelist_heap",
+    srcs = [
+        "freelist_heap.cc",
+    ],
+    hdrs = [
+        "public/pw_allocator/freelist_heap.h",
+    ],
+    deps = [
+        ":block",
+        ":freelist",
+    ],
+)
+
+pw_cc_test(
+    name = "block_test",
+    srcs = [
+        "block_test.cc",
+    ],
+    deps = [
+        ":block",
+        "//pw_span",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "freelist_test",
+    srcs = [
+        "freelist_test.cc",
+    ],
+    deps = [
+        ":freelist",
+        "//pw_span",
+        "//pw_status",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "freelist_heap_test",
+    srcs = [
+        "freelist_heap_test.cc",
+    ],
+    deps = [
+        ":freelist_heap",
+    ],
+)
diff --git a/pw_allocator/BUILD.bazel b/pw_allocator/BUILD.bazel
deleted file mode 100644
index 71f255c..0000000
--- a/pw_allocator/BUILD.bazel
+++ /dev/null
@@ -1,106 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "block",
-    srcs = [
-        "block.cc",
-    ],
-    hdrs = [
-        "public/pw_allocator/block.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_assert",
-        "//pw_span",
-        "//pw_status",
-    ],
-)
-
-pw_cc_library(
-    name = "freelist",
-    srcs = [
-        "freelist.cc",
-    ],
-    hdrs = [
-        "public/pw_allocator/freelist.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_containers",
-        "//pw_span",
-        "//pw_status",
-    ],
-)
-
-pw_cc_library(
-    name = "freelist_heap",
-    srcs = [
-        "freelist_heap.cc",
-    ],
-    hdrs = [
-        "public/pw_allocator/freelist_heap.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":block",
-        ":freelist",
-        "//pw_log",
-    ],
-)
-
-pw_cc_test(
-    name = "block_test",
-    srcs = [
-        "block_test.cc",
-    ],
-    deps = [
-        ":block",
-        "//pw_span",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "freelist_test",
-    srcs = [
-        "freelist_test.cc",
-    ],
-    deps = [
-        ":freelist",
-        "//pw_span",
-        "//pw_status",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "freelist_heap_test",
-    srcs = [
-        "freelist_heap_test.cc",
-    ],
-    deps = [
-        ":freelist_heap",
-    ],
-)
diff --git a/pw_allocator/OWNERS b/pw_allocator/OWNERS
deleted file mode 100644
index 3afb926..0000000
--- a/pw_allocator/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-frolv@google.com
diff --git a/pw_allocator/block.cc b/pw_allocator/block.cc
index c673eb5..7390419 100644
--- a/pw_allocator/block.cc
+++ b/pw_allocator/block.cc
@@ -16,8 +16,6 @@
 
 #include <cstring>
 
-#include "pw_assert/check.h"
-
 namespace pw::allocator {
 
 Status Block::Init(const std::span<std::byte> region, Block** block) {
diff --git a/pw_allocator/block_test.cc b/pw_allocator/block_test.cc
index e397314..f70c826 100644
--- a/pw_allocator/block_test.cc
+++ b/pw_allocator/block_test.cc
@@ -136,12 +136,10 @@
   EXPECT_EQ(Block::Init(std::span(bytes, kN), &block), OkStatus());
 
   Block* block2 = nullptr;
-  block->Split(kSplit1, &block2)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  block->Split(kSplit1, &block2);
 
   Block* block3 = nullptr;
-  block->Split(kSplit2, &block3)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  block->Split(kSplit2, &block3);
 
   EXPECT_EQ(block->Next(), block3);
   EXPECT_EQ(block3->Next(), block2);
@@ -284,12 +282,10 @@
   EXPECT_EQ(Block::Init(std::span(bytes, kN), &block), OkStatus());
 
   Block* block2 = nullptr;
-  block->Split(kSplit1, &block2)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  block->Split(kSplit1, &block2);
 
   Block* block3 = nullptr;
-  block->Split(kSplit2, &block3)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  block->Split(kSplit2, &block3);
 
   EXPECT_EQ(block3->MergeNext(), OkStatus());
 
@@ -308,14 +304,13 @@
   constexpr size_t kN = 1024;
   alignas(Block*) byte bytes[kN];
 
-  // Do a split, just to check that the checks on Next/Prev are
+  // Do a split, just to sanity check that the checks on Next/Prev are
   // different...
   Block* block = nullptr;
   EXPECT_EQ(Block::Init(std::span(bytes, kN), &block), OkStatus());
 
   Block* next_block = nullptr;
-  block->Split(512, &next_block)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  block->Split(512, &next_block);
 
   EXPECT_EQ(next_block->MergeNext(), Status::OutOfRange());
   EXPECT_EQ(block->MergePrev(), Status::OutOfRange());
@@ -325,14 +320,13 @@
   constexpr size_t kN = 1024;
   alignas(Block*) byte bytes[kN];
 
-  // Do a split, just to check that the checks on Next/Prev are
+  // Do a split, just to sanity check that the checks on Next/Prev are
   // different...
   Block* block = nullptr;
   EXPECT_EQ(Block::Init(std::span(bytes, kN), &block), OkStatus());
 
   Block* next_block = nullptr;
-  block->Split(512, &next_block)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  block->Split(512, &next_block);
 
   block->MarkUsed();
   EXPECT_EQ(block->MergeNext(), Status::FailedPrecondition());
@@ -347,12 +341,10 @@
   EXPECT_EQ(Block::Init(std::span(bytes, kN), &first_block), OkStatus());
 
   Block* second_block = nullptr;
-  first_block->Split(512, &second_block)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  first_block->Split(512, &second_block);
 
   Block* third_block = nullptr;
-  second_block->Split(256, &third_block)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  second_block->Split(256, &third_block);
 
   EXPECT_EQ(first_block->IsValid(), true);
   EXPECT_EQ(second_block->IsValid(), true);
@@ -367,16 +359,13 @@
   EXPECT_EQ(Block::Init(std::span(bytes, kN), &first_block), OkStatus());
 
   Block* second_block = nullptr;
-  first_block->Split(512, &second_block)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  first_block->Split(512, &second_block);
 
   Block* third_block = nullptr;
-  second_block->Split(256, &third_block)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  second_block->Split(256, &third_block);
 
   Block* fourth_block = nullptr;
-  third_block->Split(128, &fourth_block)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  third_block->Split(128, &fourth_block);
 
   std::byte* next_ptr = reinterpret_cast<std::byte*>(first_block);
   memcpy(next_ptr, second_block, sizeof(void*));
@@ -408,12 +397,10 @@
   EXPECT_EQ(Block::Init(std::span(bytes, kN), &first_block), OkStatus());
 
   Block* second_block = nullptr;
-  first_block->Split(512, &second_block)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  first_block->Split(512, &second_block);
 
   Block* third_block = nullptr;
-  second_block->Split(256, &third_block)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  second_block->Split(256, &third_block);
 
   EXPECT_EQ(first_block->IsValid(), true);
   EXPECT_EQ(second_block->IsValid(), true);
diff --git a/pw_allocator/docs.rst b/pw_allocator/docs.rst
index 81dcb06..853cb08 100644
--- a/pw_allocator/docs.rst
+++ b/pw_allocator/docs.rst
@@ -7,19 +7,19 @@
 This module provides various building blocks
 for a dynamic allocator. This is composed of the following parts:
 
-- ``block``: An implementation of a linked list of memory blocks, supporting
-  splitting and merging of blocks.
-- ``freelist``: A freelist, suitable for fast lookups of available memory chunks
-  (i.e. ``block`` s).
+ - ``block``: An implementation of a linked list of memory blocks, supporting
+   splitting and merging of blocks.
+ - ``freelist``: A freelist, suitable for fast lookups of available memory
+   chunks (i.e. ``block`` s).
 
 Heap Integrity Check
 ====================
-The ``Block`` class provides two check functions:
+The ``Block`` class provides two sanity check functions:
 
-- ``bool Block::IsValid()``: Returns ``true`` is the given block is valid and
-  ``false`` otherwise.
-- ``void Block::CrashIfInvalid()``: Crash the program and output the reason why
-  the check fails using ``PW_DCHECK``.
+  - ``bool Block::IsValid()``: Returns ``true`` is the given block is valid
+    and ``false`` otherwise.
+  - ``void Block::CrashIfInvalid()``: Crash the program and output the reason
+    why sanity check fails using ``PW_DCHECK``.
 
 Heap Poisoning
 ==============
@@ -36,7 +36,7 @@
 
 When heap poisoning is enabled, ``pw_allocator`` will add ``sizeof(void*)``
 bytes before and after the usable space of each ``Block``, and paint the space
-with a hard-coded randomized pattern. During each check, ``pw_allocator``
+with a hard-coded randomized pattern. During each sanity check, ``pw_allocator``
 will check if the painted space still remains the pattern, and return ``false``
 if the pattern is damaged.
 
@@ -65,41 +65,41 @@
 
 The required arguments are:
 
-- ``--dump-file`` is the path of a file that contains ``malloc/free``
-  information. Each line in the dump file represents a ``malloc/free`` call.
-  ``malloc`` is represented as ``m <size> <memory address>`` and ``free`` is
-  represented as ``f <memory address>``. For example, a dump file should look
-  like:
+  - ``--dump-file`` is the path of a file that contains ``malloc/free``
+    information. Each line in the dump file represents a ``malloc/free`` call.
+    ``malloc`` is represented as ``m <size> <memory address>`` and ``free`` is
+    represented as ``f <memory address>``. For example, a dump file should look
+    like:
 
-  .. code:: sh
+      .. code:: sh
 
-    m 20 0x20004450  # malloc 20 bytes, the pointer is 0x20004450
-    m 8 0x2000447c   # malloc 8 bytes, the pointer is 0x2000447c
-    f 0x2000447c     # free the pointer at 0x2000447c
-    ...
+        m 20 0x20004450  # malloc 20 bytes, the pointer is 0x20004450
+        m 8 0x2000447c   # malloc 8 bytes, the pointer is 0x2000447c
+        f 0x2000447c     # free the pointer at 0x2000447c
+        ...
 
-  Any line not formatted as the above will be ignored.
+      Any line not formatted as the above will be ignored.
 
-- ``--heap-low-address`` is the start of the heap. For example:
+  - ``--heap-low-address`` is the start of the heap. For example:
 
-  .. code:: sh
+      .. code:: sh
 
-    --heap-low-address 0x20004440
+        --heap-low-address 0x20004440
 
-- ``--heap-high-address`` is the end of the heap. For example:
+  - ``--heap-high-address`` is the end of the heap. For example:
 
-  .. code:: sh
+      .. code:: sh
 
-    --heap-high-address 0x20006040
+        --heap-high-address 0x20006040
 
 Options include the following:
 
-- ``--poison-enable``: If heap poisoning is enabled during the
-  allocation or not. The value is ``False`` if the option is not specified and
-  ``True`` otherwise.
+  - ``--poison-enable``: If heap poisoning is enabled during the
+    allocation or not. The value is ``False`` if the option is not specified and
+    ``True`` otherwise.
 
-- ``--pointer-size <integer of pointer size>``: The size of a pointer on the
-  machine where ``malloc/free`` is called. The default value is ``4``.
+  - ``--pointer-size <integer of pointer size>``: The size of a pointer on the
+    machine where ``malloc/free`` is called. The default value is ``4``.
 
 Note, this module, and its documentation, is currently incomplete and
 experimental.
diff --git a/pw_allocator/freelist_heap.cc b/pw_allocator/freelist_heap.cc
index 1dacf2d..750a857 100644
--- a/pw_allocator/freelist_heap.cc
+++ b/pw_allocator/freelist_heap.cc
@@ -16,7 +16,7 @@
 
 #include <cstring>
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_log/log.h"
 
 namespace pw::allocator {
@@ -28,8 +28,7 @@
       Block::Init(region, &block),
       "Failed to initialize FreeListHeap region; misaligned or too small");
 
-  freelist_.AddChunk(BlockToSpan(block))
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  freelist_.AddChunk(BlockToSpan(block));
 
   region_ = region;
   heap_stats_.total_bytes = region.size();
@@ -43,8 +42,7 @@
   if (chunk.data() == nullptr) {
     return nullptr;
   }
-  freelist_.RemoveChunk(chunk)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  freelist_.RemoveChunk(chunk);
 
   Block* chunk_block = Block::FromUsableSpace(chunk.data());
 
@@ -54,8 +52,7 @@
   Block* leftover;
   auto status = chunk_block->Split(size, &leftover);
   if (status == PW_STATUS_OK) {
-    freelist_.AddChunk(BlockToSpan(leftover))
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    freelist_.AddChunk(BlockToSpan(leftover));
   }
 
   chunk_block->MarkUsed();
@@ -95,24 +92,19 @@
 
   if (prev != nullptr && !prev->Used()) {
     // Remove from freelist and merge
-    freelist_.RemoveChunk(BlockToSpan(prev))
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    chunk_block->MergePrev()
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    freelist_.RemoveChunk(BlockToSpan(prev));
+    chunk_block->MergePrev();
 
     // chunk_block is now invalid; prev now encompasses it.
     chunk_block = prev;
   }
 
   if (next != nullptr && !next->Used()) {
-    freelist_.RemoveChunk(BlockToSpan(next))
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    chunk_block->MergeNext()
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    freelist_.RemoveChunk(BlockToSpan(next));
+    chunk_block->MergeNext();
   }
   // Add back to the freelist
-  freelist_.AddChunk(BlockToSpan(chunk_block))
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  freelist_.AddChunk(BlockToSpan(chunk_block));
 
   heap_stats_.bytes_allocated -= size_freed;
   heap_stats_.cumulative_freed += size_freed;
diff --git a/pw_allocator/freelist_test.cc b/pw_allocator/freelist_test.cc
index 45d1d35..8e0e145 100644
--- a/pw_allocator/freelist_test.cc
+++ b/pw_allocator/freelist_test.cc
@@ -56,8 +56,7 @@
 
   byte data[kN] = {std::byte(0)};
 
-  list.AddChunk(std::span(data, kN))
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  list.AddChunk(std::span(data, kN));
   auto item = list.FindChunk(kN / 2);
   EXPECT_EQ(item.size(), kN);
   EXPECT_EQ(item.data(), data);
@@ -69,8 +68,7 @@
 
   byte data[kN] = {std::byte(0)};
 
-  list.AddChunk(std::span(data, kN))
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  list.AddChunk(std::span(data, kN));
   auto status = list.RemoveChunk(std::span(data, kN));
   EXPECT_EQ(status, OkStatus());
 
@@ -86,10 +84,8 @@
   byte data1[kN1] = {std::byte(0)};
   byte data2[kN2] = {std::byte(0)};
 
-  list.AddChunk(std::span(data1, kN1))
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  list.AddChunk(std::span(data2, kN2))
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  list.AddChunk(std::span(data1, kN1));
+  list.AddChunk(std::span(data2, kN2));
 
   auto chunk = list.FindChunk(kN1 / 2);
   EXPECT_EQ(chunk.size(), kN1);
@@ -115,10 +111,8 @@
   byte data2[kN2] = {std::byte(0)};
 
   // List should now be 257 -> 512 -> NULL
-  list.AddChunk(std::span(data1, kN1))
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  list.AddChunk(std::span(data2, kN2))
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  list.AddChunk(std::span(data1, kN1));
+  list.AddChunk(std::span(data2, kN2));
 
   auto chunk = list.FindChunk(kN2 + 1);
   EXPECT_EQ(chunk.size(), kN1);
@@ -137,10 +131,8 @@
   // List should now be:
   // bkt[3] (257 bytes up to 512 bytes) -> 257 -> NULL
   // bkt[4] (513 bytes up to 1024 bytes) -> 513 -> NULL
-  list.AddChunk(std::span(data1, kN1))
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  list.AddChunk(std::span(data2, kN2))
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  list.AddChunk(std::span(data1, kN1));
+  list.AddChunk(std::span(data2, kN2));
 
   // Request a 300 byte chunk. This should return the 513 byte one
   auto chunk = list.FindChunk(kN1 + 1);
@@ -154,8 +146,7 @@
   byte data[kN] = {std::byte(0)};
   byte data2[kN] = {std::byte(0)};
 
-  list.AddChunk(std::span(data, kN))
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  list.AddChunk(std::span(data, kN));
   auto status = list.RemoveChunk(std::span(data2, kN));
   EXPECT_EQ(status, Status::NotFound());
 }
@@ -167,17 +158,13 @@
   byte data1[kN] = {std::byte(0)};
   byte data2[kN] = {std::byte(0)};
 
-  list.AddChunk(std::span(data1, kN))
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  list.AddChunk(std::span(data2, kN))
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  list.AddChunk(std::span(data1, kN));
+  list.AddChunk(std::span(data2, kN));
 
   auto chunk1 = list.FindChunk(kN);
-  list.RemoveChunk(chunk1)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  list.RemoveChunk(chunk1);
   auto chunk2 = list.FindChunk(kN);
-  list.RemoveChunk(chunk2)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  list.RemoveChunk(chunk2);
 
   // Ordering of the chunks doesn't matter
   EXPECT_TRUE(chunk1.data() != chunk2.data());
diff --git a/pw_allocator/public/pw_allocator/block.h b/pw_allocator/public/pw_allocator/block.h
index 3edc92c..fcea0e0 100644
--- a/pw_allocator/public/pw_allocator/block.h
+++ b/pw_allocator/public/pw_allocator/block.h
@@ -18,6 +18,7 @@
 
 #include <span>
 
+#include "pw_assert/assert.h"
 #include "pw_status/status.h"
 
 namespace pw::allocator {
@@ -98,7 +99,7 @@
   // reality, this method just subtracts the appropriate amount from
   // usable_space to point to the start of the owning block.
   //
-  // Be aware that this method does not do any checking; passing a random
+  // Be aware that this method does not do any sanity checking; passing a random
   // pointer will return a non-null pointer.
   static Block* FromUsableSpace(std::byte* usable_space) {
     return reinterpret_cast<Block*>(usable_space - sizeof(Block) -
@@ -210,7 +211,7 @@
   // return false to indicate this block is corrupted.
   bool IsValid() const { return CheckStatus() == BlockStatus::VALID; }
 
-  // Uses PW_DCHECK to log information about the reason if a block is invalid.
+  // Uses PW_DCHECK to log information about the reason if a blcok is invalid.
   // This function will do nothing if the block is valid.
   void CrashIfInvalid();
 
diff --git a/pw_allocator/py/BUILD.gn b/pw_allocator/py/BUILD.gn
index 8b4696a..0322cb3 100644
--- a/pw_allocator/py/BUILD.gn
+++ b/pw_allocator/py/BUILD.gn
@@ -17,11 +17,7 @@
 import("$dir_pw_build/python.gni")
 
 pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
+  setup = [ "setup.py" ]
   sources = [
     "pw_allocator/__init__.py",
     "pw_allocator/heap_viewer.py",
diff --git a/pw_allocator/py/pyproject.toml b/pw_allocator/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/pw_allocator/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/pw_allocator/py/setup.cfg b/pw_allocator/py/setup.cfg
deleted file mode 100644
index 4f1a6fd..0000000
--- a/pw_allocator/py/setup.cfg
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pw_allocator
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Pigweed heap allocator
-
-[options]
-packages = find:
-zip_safe = False
-install_requires = pw_cli
-
-[options.package_data]
-pw_allocator = py.typed
diff --git a/pw_allocator/py/setup.py b/pw_allocator/py/setup.py
index 7a82dcf..1031fc7 100644
--- a/pw_allocator/py/setup.py
+++ b/pw_allocator/py/setup.py
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2020 The Pigweed Authors
 #
 # 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
@@ -15,4 +15,16 @@
 
 import setuptools  # type: ignore
 
-setuptools.setup()  # Package definition in setup.cfg
+setuptools.setup(
+    name='pw_allocator',
+    version='0.0.1',
+    author='Pigweed Authors',
+    author_email='pigweed-developers@googlegroups.com',
+    description='Pigweed heap allocator',
+    packages=setuptools.find_packages(),
+    package_data={'pw_allocator': ['py.typed']},
+    zip_safe=False,
+    install_requires=[
+        'pw_cli',
+    ],
+)
diff --git a/pw_analog/BUILD.bazel b/pw_analog/BUILD.bazel
deleted file mode 100644
index 5c699c3..0000000
--- a/pw_analog/BUILD.bazel
+++ /dev/null
@@ -1,95 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "analog_input",
-    hdrs = [
-        "public/pw_analog/analog_input.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_chrono:system_clock",
-        "//pw_result",
-    ],
-)
-
-pw_cc_library(
-    name = "microvolt_input",
-    hdrs = [
-        "public/pw_analog/microvolt_input.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":analog_input",
-        "//pw_chrono:system_clock",
-        "//pw_result",
-        "//pw_status",
-    ],
-)
-
-pw_cc_library(
-    name = "microvolt_input_gmock",
-    hdrs = [
-        "public/pw_analog/microvolt_input_gmock.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":microvolt_input",
-        "@com_google_googletest//:gtest",
-    ],
-)
-
-pw_cc_library(
-    name = "analog_input_gmock",
-    hdrs = [
-        "public/pw_analog/analog_input_gmock.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":analog_input",
-        "@com_google_googletest//:gtest",
-    ],
-)
-
-pw_cc_test(
-    name = "analog_input_test",
-    srcs = [
-        "analog_input_test.cc",
-    ],
-    deps = [
-        ":analog_input",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "microvolt_input_test",
-    srcs = [
-        "microvolt_input_test.cc",
-    ],
-    deps = [
-        ":microvolt_input",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_analog/BUILD.gn b/pw_analog/BUILD.gn
deleted file mode 100644
index dd2761a..0000000
--- a/pw_analog/BUILD.gn
+++ /dev/null
@@ -1,92 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_chrono/backend.gni")
-import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_unit_test/test.gni")
-
-config("public_include_path") {
-  include_dirs = [ "public" ]
-}
-
-group("pw_analog") {
-  public_deps = [
-    ":analog_input",
-    ":microvolt_input",
-  ]
-}
-
-pw_source_set("analog_input") {
-  public_configs = [ ":public_include_path" ]
-  public_deps = [
-    "$dir_pw_chrono:system_clock",
-    "$dir_pw_result",
-  ]
-  public = [ "public/pw_analog/analog_input.h" ]
-}
-
-pw_source_set("microvolt_input") {
-  public_configs = [ ":public_include_path" ]
-  public_deps = [
-    ":analog_input",
-    "$dir_pw_chrono:system_clock",
-    "$dir_pw_result",
-    "$dir_pw_status",
-  ]
-  public = [ "public/pw_analog/microvolt_input.h" ]
-}
-
-pw_source_set("analog_input_gmock") {
-  public_configs = [ ":public_include_path" ]
-  public_deps = [
-    ":analog_input",
-    "$dir_pw_third_party/googletest",
-  ]
-  public = [ "public/pw_analog/analog_input_gmock.h" ]
-}
-
-pw_source_set("microvolt_input_gmock") {
-  public_configs = [ ":public_include_path" ]
-  public_deps = [
-    ":microvolt_input",
-    "$dir_pw_third_party/googletest",
-  ]
-  public = [ "public/pw_analog/microvolt_input_gmock.h" ]
-}
-
-pw_test_group("tests") {
-  tests = [
-    ":analog_input_test",
-    ":microvolt_input_test",
-  ]
-}
-
-pw_test("analog_input_test") {
-  enable_if = pw_chrono_SYSTEM_CLOCK_BACKEND != ""
-  sources = [ "analog_input_test.cc" ]
-  deps = [ ":pw_analog" ]
-}
-
-pw_test("microvolt_input_test") {
-  enable_if = pw_chrono_SYSTEM_CLOCK_BACKEND != ""
-  sources = [ "microvolt_input_test.cc" ]
-  deps = [ ":pw_analog" ]
-}
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
diff --git a/pw_analog/analog_input_test.cc b/pw_analog/analog_input_test.cc
deleted file mode 100644
index 6e0fbac..0000000
--- a/pw_analog/analog_input_test.cc
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_analog/analog_input.h"
-
-#include "gtest/gtest.h"
-
-namespace pw {
-namespace analog {
-namespace {
-
-constexpr int32_t kLimitsMax = 4096;
-constexpr int32_t kLimitsMin = 0;
-
-// Fake test analog input that's used for testing.
-class TestAnalogInput : public AnalogInput {
- public:
-  TestAnalogInput()
-      : limits_({
-            .min = kLimitsMin,
-            .max = kLimitsMax,
-        }) {}
-
-  Result<int32_t> TryReadUntil(chrono::SystemClock::time_point) override {
-    return Status::Unimplemented();
-  }
-
-  Limits GetLimits() const override { return limits_; }
-
- private:
-  const Limits limits_;
-};
-
-TEST(AnalogInputTest, Construction) {
-  TestAnalogInput analog_input = TestAnalogInput();
-}
-
-TEST(AnalogInputTest, GetLimits) {
-  TestAnalogInput analog_input = TestAnalogInput();
-  AnalogInput::Limits limits = analog_input.GetLimits();
-  EXPECT_EQ(limits.min, kLimitsMin);
-  EXPECT_EQ(limits.max, kLimitsMax);
-}
-
-}  // namespace
-}  // namespace analog
-}  // namespace pw
diff --git a/pw_analog/docs.rst b/pw_analog/docs.rst
deleted file mode 100644
index 19285e0..0000000
--- a/pw_analog/docs.rst
+++ /dev/null
@@ -1,38 +0,0 @@
-.. _module-pw_analog:
-
----------
-pw_analog
----------
-
-.. warning::
-  This module is under construction and may not be ready for use.
-
-pw_analog contains interfaces and utility functions for using the ADC.
-
-Features
-========
-
-pw::analog::AnalogInput
------------------------
-The common interface for obtaining ADC samples. This interface represents
-a single analog input or channel. Users will need to supply their own ADC
-driver implementation in order to configure and enable the ADC peripheral.
-Users are responsible for managing multithreaded access to the ADC driver if the
-ADC services multiple channels.
-
-pw::analog::MicrovoltInput
---------------------------
-The common interface for obtaining voltage samples in microvolts. This interface
-represents a single voltage input or channel. Users will need to supply their
-own ADC driver implementation in order to configure and enable the ADC
-peripheral in order to provide the reference voltages and to configure and
-enable the ADC peripheral where needed. Users are responsible for managing
-multithreaded access to the ADC driver if the ADC services multiple channels.
-
-pw::analog::GmockAnalogInput
--------------------------------
-gMock of AnalogInput used for testing and mocking out the AnalogInput.
-
-pw::analog::GmockMicrovoltInput
--------------------------------
-gMock of MicrovoltInput used for testing and mocking out the MicrovoltInput.
diff --git a/pw_analog/microvolt_input_test.cc b/pw_analog/microvolt_input_test.cc
deleted file mode 100644
index 710a367..0000000
--- a/pw_analog/microvolt_input_test.cc
+++ /dev/null
@@ -1,280 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_analog/microvolt_input.h"
-
-#include "gtest/gtest.h"
-
-namespace pw {
-namespace analog {
-namespace {
-
-using namespace std::chrono_literals;
-
-constexpr int32_t kLimitsMax = 4096;
-constexpr int32_t kLimitsMin = 0;
-constexpr int32_t kReferenceMaxVoltageUv = 1800000;
-constexpr int32_t kReferenceMinVoltageUv = 0;
-constexpr chrono::SystemClock::duration kTimeout = 1ms;
-
-constexpr int32_t kBipolarLimitsMax = 4096;
-constexpr int32_t kBipolarLimitsMin = -4096;
-constexpr int32_t kBipolarReferenceMaxVoltageUv = 1800000;
-constexpr int32_t kBipolarReferenceMinVoltageUv = -1800000;
-
-constexpr int32_t kCornerLimitsMax = std::numeric_limits<int32_t>::max();
-constexpr int32_t kCornerLimitsMin = std::numeric_limits<int32_t>::min();
-constexpr int32_t kCornerReferenceMaxVoltageUv =
-    std::numeric_limits<int32_t>::max();
-constexpr int32_t kCornerReferenceMinVoltageUv =
-    std::numeric_limits<int32_t>::min();
-
-constexpr int32_t kInvertedLimitsMax = std::numeric_limits<int32_t>::min();
-constexpr int32_t kInvertedLimitsMin = std::numeric_limits<int32_t>::max();
-constexpr int32_t kInvertedReferenceMaxVoltageUv =
-    std::numeric_limits<int32_t>::min();
-constexpr int32_t kInvertedReferenceMinVoltageUv =
-    std::numeric_limits<int32_t>::max();
-
-// Fake voltage input that's used for testing.
-class TestMicrovoltInput : public MicrovoltInput {
- public:
-  constexpr explicit TestMicrovoltInput(AnalogInput::Limits limits,
-                                        MicrovoltInput::References reference)
-      : sample_(0), limits_(limits), reference_(reference) {}
-
-  void SetSampleValue(int32_t sample) { sample_ = sample; }
-
- private:
-  Result<int32_t> TryReadUntil(chrono::SystemClock::time_point) override {
-    return sample_;
-  }
-
-  Limits GetLimits() const override { return limits_; }
-  References GetReferences() const override { return reference_; }
-
-  uint32_t sample_;
-  const Limits limits_;
-  const References reference_;
-};
-
-TEST(MicrovoltInputTest, Construction) {
-  TestMicrovoltInput voltage_input =
-      TestMicrovoltInput({.min = kLimitsMin, .max = kLimitsMax},
-                         {.max_voltage_uv = kReferenceMaxVoltageUv,
-                          .min_voltage_uv = kReferenceMinVoltageUv});
-}
-
-TEST(MicrovoltInputTest, ReadMicrovoltsWithSampleAtMin) {
-  TestMicrovoltInput voltage_input =
-      TestMicrovoltInput({.min = kLimitsMin, .max = kLimitsMax},
-                         {.max_voltage_uv = kReferenceMaxVoltageUv,
-                          .min_voltage_uv = kReferenceMinVoltageUv});
-  voltage_input.SetSampleValue(kLimitsMin);
-
-  Result<int32_t> result = voltage_input.TryReadMicrovoltsFor(kTimeout);
-  ASSERT_TRUE(result.status().ok());
-
-  EXPECT_EQ(result.value(), 0);
-}
-
-TEST(MicrovoltInputTest, ReadMicrovoltsWithSampleAtMax) {
-  TestMicrovoltInput voltage_input =
-      TestMicrovoltInput({.min = kLimitsMin, .max = kLimitsMax},
-                         {.max_voltage_uv = kReferenceMaxVoltageUv,
-                          .min_voltage_uv = kReferenceMinVoltageUv});
-  voltage_input.SetSampleValue(kLimitsMax);
-
-  Result<int32_t> result = voltage_input.TryReadMicrovoltsFor(kTimeout);
-  ASSERT_TRUE(result.status().ok());
-
-  EXPECT_EQ(result.value(), kReferenceMaxVoltageUv);
-}
-
-TEST(MicrovoltInputTest, ReadMicrovoltsWithSampleAtHalf) {
-  TestMicrovoltInput voltage_input =
-      TestMicrovoltInput({.min = kLimitsMin, .max = kLimitsMax},
-                         {.max_voltage_uv = kReferenceMaxVoltageUv,
-                          .min_voltage_uv = kReferenceMinVoltageUv});
-  voltage_input.SetSampleValue(kLimitsMax / 2);
-
-  Result<int32_t> result = voltage_input.TryReadMicrovoltsFor(kTimeout);
-  ASSERT_TRUE(result.status().ok());
-
-  EXPECT_EQ(result.value(), kReferenceMaxVoltageUv / 2);
-}
-
-TEST(MicrovoltInputTest, ReadMicrovoltsWithBipolarAdcAtZero) {
-  TestMicrovoltInput voltage_input =
-      TestMicrovoltInput({.min = kBipolarLimitsMin, .max = kBipolarLimitsMax},
-                         {.max_voltage_uv = kBipolarReferenceMaxVoltageUv,
-                          .min_voltage_uv = kBipolarReferenceMinVoltageUv});
-  voltage_input.SetSampleValue(0);
-
-  Result<int32_t> result = voltage_input.TryReadMicrovoltsFor(kTimeout);
-  ASSERT_TRUE(result.status().ok());
-
-  EXPECT_EQ(result.value(), 0);
-}
-
-TEST(MicrovoltInputTest, ReadMicrovoltsWithBipolarAdcAtMin) {
-  TestMicrovoltInput voltage_input =
-      TestMicrovoltInput({.min = kBipolarLimitsMin, .max = kBipolarLimitsMax},
-                         {.max_voltage_uv = kBipolarReferenceMaxVoltageUv,
-                          .min_voltage_uv = kBipolarReferenceMinVoltageUv});
-  voltage_input.SetSampleValue(kBipolarLimitsMin);
-
-  Result<int32_t> result = voltage_input.TryReadMicrovoltsFor(kTimeout);
-  ASSERT_TRUE(result.status().ok());
-
-  EXPECT_EQ(result.value(), kBipolarReferenceMinVoltageUv);
-}
-
-TEST(MicrovoltInputTest, ReadMicrovoltsWithBipolarAdcAtMax) {
-  TestMicrovoltInput voltage_input =
-      TestMicrovoltInput({.min = kBipolarLimitsMin, .max = kBipolarLimitsMax},
-                         {.max_voltage_uv = kBipolarReferenceMaxVoltageUv,
-                          .min_voltage_uv = kBipolarReferenceMinVoltageUv});
-  voltage_input.SetSampleValue(kBipolarLimitsMax);
-
-  Result<int32_t> result = voltage_input.TryReadMicrovoltsFor(kTimeout);
-  ASSERT_TRUE(result.status().ok());
-
-  EXPECT_EQ(result.value(), kBipolarReferenceMaxVoltageUv);
-}
-
-TEST(MicrovoltInputTest, ReadMicrovoltsWithBipolarAdcAtUpperHalf) {
-  TestMicrovoltInput voltage_input =
-      TestMicrovoltInput({.min = kBipolarLimitsMin, .max = kBipolarLimitsMax},
-                         {.max_voltage_uv = kBipolarReferenceMaxVoltageUv,
-                          .min_voltage_uv = kBipolarReferenceMinVoltageUv});
-  voltage_input.SetSampleValue(kBipolarLimitsMax / 2);
-
-  Result<int32_t> result = voltage_input.TryReadMicrovoltsFor(kTimeout);
-  ASSERT_TRUE(result.status().ok());
-
-  EXPECT_EQ(result.value(), kBipolarReferenceMaxVoltageUv / 2);
-}
-
-TEST(MicrovoltInputTest, ReadMicrovoltsWithBipolarAdcAtLowerHalf) {
-  TestMicrovoltInput voltage_input =
-      TestMicrovoltInput({.min = kBipolarLimitsMin, .max = kBipolarLimitsMax},
-                         {.max_voltage_uv = kBipolarReferenceMaxVoltageUv,
-                          .min_voltage_uv = kBipolarReferenceMinVoltageUv});
-  voltage_input.SetSampleValue(kBipolarLimitsMin / 2);
-
-  Result<int32_t> result = voltage_input.TryReadMicrovoltsFor(kTimeout);
-  ASSERT_TRUE(result.status().ok());
-
-  EXPECT_EQ(result.value(), kBipolarReferenceMinVoltageUv / 2);
-}
-
-TEST(MicrovoltInputTest, ReadMicrovoltsWithBipolarReferenceAtZero) {
-  TestMicrovoltInput voltage_input =
-      TestMicrovoltInput({.min = kLimitsMin, .max = kLimitsMax},
-                         {.max_voltage_uv = kBipolarReferenceMaxVoltageUv,
-                          .min_voltage_uv = kBipolarReferenceMinVoltageUv});
-  voltage_input.SetSampleValue(0);
-
-  Result<int32_t> result = voltage_input.TryReadMicrovoltsFor(kTimeout);
-  ASSERT_TRUE(result.status().ok());
-
-  EXPECT_EQ(result.value(), kBipolarReferenceMinVoltageUv);
-}
-
-TEST(MicrovoltInputTest, ReadMicrovoltsWithBipolarReferenceAtMin) {
-  TestMicrovoltInput voltage_input =
-      TestMicrovoltInput({.min = kLimitsMin, .max = kLimitsMax},
-                         {.max_voltage_uv = kBipolarReferenceMaxVoltageUv,
-                          .min_voltage_uv = kBipolarReferenceMinVoltageUv});
-  voltage_input.SetSampleValue(kLimitsMin);
-
-  Result<int32_t> result = voltage_input.TryReadMicrovoltsFor(kTimeout);
-  ASSERT_TRUE(result.status().ok());
-
-  EXPECT_EQ(result.value(), kBipolarReferenceMinVoltageUv);
-}
-
-TEST(MicrovoltInputTest, ReadMicrovoltsWithBipolarReferenceAtMax) {
-  TestMicrovoltInput voltage_input =
-      TestMicrovoltInput({.min = kLimitsMin, .max = kLimitsMax},
-                         {.max_voltage_uv = kBipolarReferenceMaxVoltageUv,
-                          .min_voltage_uv = kBipolarReferenceMinVoltageUv});
-  voltage_input.SetSampleValue(kLimitsMax);
-
-  Result<int32_t> result = voltage_input.TryReadMicrovoltsFor(kTimeout);
-  ASSERT_TRUE(result.status().ok());
-
-  EXPECT_EQ(result.value(), kBipolarReferenceMaxVoltageUv);
-}
-
-TEST(MicrovoltInputTest, ReadMicrovoltsWithBipolarReferenceAtHalf) {
-  TestMicrovoltInput voltage_input =
-      TestMicrovoltInput({.min = kLimitsMin, .max = kLimitsMax},
-                         {.max_voltage_uv = kBipolarReferenceMaxVoltageUv,
-                          .min_voltage_uv = kBipolarReferenceMinVoltageUv});
-  voltage_input.SetSampleValue(kLimitsMax / 2);
-
-  Result<int32_t> result = voltage_input.TryReadMicrovoltsFor(kTimeout);
-  ASSERT_TRUE(result.status().ok());
-
-  EXPECT_EQ(result.value(), 0);
-}
-
-TEST(MicrovoltInputTest, ReadMicrovoltsWithSampleAtMinCornerCase) {
-  TestMicrovoltInput voltage_input =
-      TestMicrovoltInput({.min = kCornerLimitsMin, .max = kCornerLimitsMax},
-                         {.max_voltage_uv = kCornerReferenceMaxVoltageUv,
-                          .min_voltage_uv = kCornerReferenceMinVoltageUv});
-  voltage_input.SetSampleValue(kCornerLimitsMin);
-
-  Result<int32_t> result = voltage_input.TryReadMicrovoltsFor(kTimeout);
-  ASSERT_EQ(result.status(), pw::Status::Internal());
-}
-
-TEST(MicrovoltInputTest, ReadMicrovoltsWithSampleAtMaxCornerCase) {
-  TestMicrovoltInput voltage_input =
-      TestMicrovoltInput({.min = kCornerLimitsMin, .max = kCornerLimitsMax},
-                         {.max_voltage_uv = kCornerReferenceMaxVoltageUv,
-                          .min_voltage_uv = kCornerReferenceMinVoltageUv});
-  voltage_input.SetSampleValue(kCornerLimitsMax);
-
-  Result<int32_t> result = voltage_input.TryReadMicrovoltsFor(kTimeout);
-  ASSERT_EQ(result.status(), pw::Status::Internal());
-}
-
-TEST(MicrovoltInputTest, ReadMicrovoltsWithInvertedReferenceAtMax) {
-  TestMicrovoltInput voltage_input =
-      TestMicrovoltInput({.min = kInvertedLimitsMin, .max = kInvertedLimitsMax},
-                         {.max_voltage_uv = kInvertedReferenceMaxVoltageUv,
-                          .min_voltage_uv = kInvertedReferenceMinVoltageUv});
-  voltage_input.SetSampleValue(kInvertedLimitsMax);
-
-  Result<int32_t> result = voltage_input.TryReadMicrovoltsFor(kTimeout);
-  ASSERT_EQ(result.status(), pw::Status::Internal());
-}
-
-TEST(MicrovoltInputTest, ReadMicrovoltsWithInvertedReferenceAtMin) {
-  TestMicrovoltInput voltage_input =
-      TestMicrovoltInput({.min = kInvertedLimitsMin, .max = kInvertedLimitsMax},
-                         {.max_voltage_uv = kInvertedReferenceMaxVoltageUv,
-                          .min_voltage_uv = kInvertedReferenceMinVoltageUv});
-  voltage_input.SetSampleValue(kInvertedLimitsMin);
-
-  Result<int32_t> result = voltage_input.TryReadMicrovoltsFor(kTimeout);
-  ASSERT_EQ(result.status(), pw::Status::Internal());
-}
-
-}  // namespace
-}  // namespace analog
-}  // namespace pw
diff --git a/pw_analog/public/pw_analog/analog_input.h b/pw_analog/public/pw_analog/analog_input.h
deleted file mode 100644
index 39024a0..0000000
--- a/pw_analog/public/pw_analog/analog_input.h
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_chrono/system_clock.h"
-#include "pw_result/result.h"
-
-namespace pw::analog {
-
-// Base interface for getting ADC samples from one ADC channel in a thread
-// safe manner.
-//
-// The ADC backend interface is up to the user to define and implement for now.
-// This gives the flexibility for the ADC driver implementation.
-//
-// AnalogInput controls a specific input/channel where the ADC peripheral may be
-// shared across multiple channels that may be controlled by multiple threads.
-// The implementer of this pure virtual interface is responsible for ensuring
-// thread safety and access at the driver level.
-class AnalogInput {
- public:
-  // Limits struct that specifies the min and max of the sample range.
-  // These values do not change at run time.
-  struct Limits {
-    int32_t min;
-    int32_t max;
-  };
-
-  virtual ~AnalogInput() = default;
-
-  // Blocks until the specified timeout duration has elapsed or the ADC sample
-  // has been returned, whichever comes first.
-  //
-  // This method is thread safe.
-  //
-  // Returns:
-  //   Sample.
-  //   ResourceExhuasted: ADC peripheral in use.
-  //   DeadlineExceedded: Timed out waiting for a sample.
-  //   Other statuses left up to the implementer.
-  Result<int32_t> TryReadFor(chrono::SystemClock::duration timeout) {
-    return TryReadUntil(chrono::SystemClock::TimePointAfterAtLeast(timeout));
-  }
-
-  // Blocks until the deadline time has been reached or the ADC sample
-  // has been returned, whichever comes first.
-  //
-  // This method is thread safe.
-  //
-  // Returns:
-  //   Sample.
-  //   ResourceExhuasted: ADC peripheral in use.
-  //   DeadlineExceedded: Timed out waiting for a sample.
-  //   Other statuses left up to the implementer.
-  virtual Result<int32_t> TryReadUntil(
-      chrono::SystemClock::time_point deadline) = 0;
-
-  // Returns the range of the ADC sample.
-  // These values do not change at run time.
-  virtual Limits GetLimits() const = 0;
-};
-
-}  // namespace pw::analog
diff --git a/pw_analog/public/pw_analog/analog_input_gmock.h b/pw_analog/public/pw_analog/analog_input_gmock.h
deleted file mode 100644
index f328c93..0000000
--- a/pw_analog/public/pw_analog/analog_input_gmock.h
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "gmock/gmock.h"
-#include "pw_analog/analog_input.h"
-
-namespace pw::analog {
-
-class GmockAnalogInput : public AnalogInput {
- public:
-  MOCK_METHOD(pw::Result<int32_t>,
-              TryReadUntil,
-              (pw::chrono::SystemClock::time_point deadline),
-              (override));
-
-  MOCK_METHOD(Limits, GetLimits, (), (const, override));
-};
-
-}  // namespace pw::analog
diff --git a/pw_analog/public/pw_analog/microvolt_input.h b/pw_analog/public/pw_analog/microvolt_input.h
deleted file mode 100644
index 6a3a562..0000000
--- a/pw_analog/public/pw_analog/microvolt_input.h
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_analog/analog_input.h"
-#include "pw_chrono/system_clock.h"
-#include "pw_result/result.h"
-#include "pw_status/try.h"
-
-namespace pw::analog {
-
-// The common interface for obtaining voltage samples in microvolts. This
-// interface represents a single voltage input or channel. Users will need to
-// supply their own ADC driver implementation in order to configure and enable
-// the ADC peripheral in order to provide the reference voltages and to
-// configure and enable the ADC peripheral where needed. Users are responsible
-// for managing multithreaded access to the ADC driver if the ADC services
-// multiple channels.
-class MicrovoltInput : public AnalogInput {
- public:
-  // Specifies the max and min microvolt range the analog input can measure.
-  // The reference voltage difference cannot be bigger than sizeof(int32_t)
-  // which should be just above 2000V.
-  // * These values do not change at run time.
-  // * Inversion of min/max is supported.
-  struct References {
-    int32_t max_voltage_uv;  // Microvolts at AnalogInput::Limits::max
-    int32_t min_voltage_uv;  // Microvolts at AnalogInput::Limits::min.
-  };
-
-  virtual ~MicrovoltInput() = default;
-
-  // Blocks until the specified timeout duration has elapsed or the voltage
-  // sample has been returned, whichever comes first.
-  //
-  // This method is thread safe.
-  //
-  // Returns:
-  //   Microvolts (uV).
-  //   ResourceExhuasted: ADC peripheral in use.
-  //   DeadlineExceedded: Timed out waiting for a sample.
-  //   Other statuses left up to the implementer.
-  Result<int32_t> TryReadMicrovoltsFor(chrono::SystemClock::duration timeout) {
-    return TryReadMicrovoltsUntil(
-        chrono::SystemClock::TimePointAfterAtLeast(timeout));
-  }
-
-  // Blocks until the deadline time has been reached or the voltage sample has
-  // been returned, whichever comes first.
-  //
-  // This method is thread safe.
-  //
-  // Returns:
-  //   Microvolts (uV).
-  //   ResourceExhuasted: ADC peripheral in use.
-  //   DeadlineExceedded: Timed out waiting for a sample.
-  //   Other statuses left up to the implementer.
-  Result<int32_t> TryReadMicrovoltsUntil(
-      chrono::SystemClock::time_point deadline) {
-    PW_TRY_ASSIGN(const int32_t sample, TryReadUntil(deadline));
-
-    const References reference = GetReferences();
-    const AnalogInput::Limits limits = GetLimits();
-
-    constexpr int64_t kMaxReferenceDiffUv = std::numeric_limits<int32_t>::max();
-
-    if (std::abs(static_cast<int64_t>(reference.max_voltage_uv) -
-                 static_cast<int64_t>(reference.min_voltage_uv)) >
-        kMaxReferenceDiffUv) {
-      return pw::Status::Internal();
-    }
-
-    return (((static_cast<int64_t>(sample) - static_cast<int64_t>(limits.min)) *
-             (reference.max_voltage_uv - reference.min_voltage_uv)) /
-            (limits.max - limits.min)) +
-           reference.min_voltage_uv;
-  }
-
- private:
-  // Returns the reference voltage needed to calculate the voltage.
-  // These values do not change at run time.
-  virtual References GetReferences() const = 0;
-};
-
-}  // namespace pw::analog
diff --git a/pw_analog/public/pw_analog/microvolt_input_gmock.h b/pw_analog/public/pw_analog/microvolt_input_gmock.h
deleted file mode 100644
index a6ce922..0000000
--- a/pw_analog/public/pw_analog/microvolt_input_gmock.h
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "gmock/gmock.h"
-#include "pw_analog/microvolt_input.h"
-
-namespace pw::analog {
-
-class GmockMicrovoltInput : public MicrovoltInput {
- public:
-  MOCK_METHOD(pw::Result<int32_t>,
-              TryReadUntil,
-              (pw::chrono::SystemClock::time_point deadline),
-              (override));
-
-  MOCK_METHOD(Limits, GetLimits, (), (const, override));
-
-  MOCK_METHOD(References, GetReferences, (), (const, override));
-};
-
-}  // namespace pw::analog
diff --git a/pw_android_toolchain/BUILD.gn b/pw_android_toolchain/BUILD.gn
deleted file mode 100644
index 8cd2719..0000000
--- a/pw_android_toolchain/BUILD.gn
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_docgen/docs.gni")
-
-config("static_libstdc") {
-  # Compile against the static libstdc++ and libc++, since we don't have an
-  # APK to include the shared version in.
-  ldflags = [ "-static-libstdc++" ]
-}
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
diff --git a/pw_android_toolchain/OWNERS b/pw_android_toolchain/OWNERS
deleted file mode 100644
index c224618..0000000
--- a/pw_android_toolchain/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-keybuk@google.com
diff --git a/pw_android_toolchain/README.md b/pw_android_toolchain/README.md
deleted file mode 100644
index 141cc90..0000000
--- a/pw_android_toolchain/README.md
+++ /dev/null
@@ -1 +0,0 @@
-# pw\_android_toolchain: Android NDK toolchains
diff --git a/pw_android_toolchain/android.gni b/pw_android_toolchain/android.gni
deleted file mode 100644
index e2186e2..0000000
--- a/pw_android_toolchain/android.gni
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-declare_args() {
-  # Enable building with an Android toolchain by setting to the full path of
-  # an Android NDK.
-  pw_android_toolchain_NDK_PATH = ""
-
-  # Set to the default minimum version of the Android SDK to compile support
-  # for. This will change which compiler from the NDK is used, and can be
-  # overriden on a per-toolchain basis using `api_level` within a toolchain's
-  # defaults.
-  pw_android_toolchain_API_LEVEL = 21
-}
-
-# Android NDKs contain toolchains for the following multiple targets.
-# `pw_generate_android_toolchains` will create toolchains for each of these
-# targets if a toolchain scope's defaults does not contain `current_cpu`.
-pw_android_toolchain_cpu_targets = [
-  "arm",
-  "arm64",
-  "x64",
-  "x86",
-]
diff --git a/pw_android_toolchain/docs.rst b/pw_android_toolchain/docs.rst
deleted file mode 100644
index e810fb6..0000000
--- a/pw_android_toolchain/docs.rst
+++ /dev/null
@@ -1,99 +0,0 @@
-.. _module-pw_android_toolchain:
-
---------------------
-pw_android_toolchain
---------------------
-Android toolchains differ from ``pw_toolchain`` in that the latter defines the
-tool names and paths at the lowest level, with customisation added at higher
-levels, while in ``pw_android_toolchain`` the tool names and paths are derived
-from build args and defaults so are defined last by calling
-``pw_generate_android_toolchain``.
-
-Setup
-=====
-You must first download and unpack a copy of the `Android NDK`_ and let Pigweed
-know where that is located using the ``pw_android_toolchain_NDK_PATH`` build
-arg.
-
-.. _Android NDK: https://developer.android.com/ndk
-
-You can set Pigweed build options using ``gn args out``.
-
-Toolchains
-==========
-``pw_android_toolchain`` provides GN toolchains that may be used to build
-Pigweed against an Android NDK. The following toolchains are defined:
-
- - arm_android_debug
- - arm_android_size_optimized
- - arm_android_speed_optimized
- - arm64_android_debug
- - arm64_android_size_optimized
- - arm64_android_speed_optimized
- - x64_android_debug
- - x64_android_size_optimized
- - x64_android_speed_optimized
- - x86_android_debug
- - x86_android_size_optimized
- - x86_android_speed_optimized
-
-.. note::
-  The documentation for this module is currently incomplete.
-
-Defining Toolchains
-===================
-Defining Android NDK toolchains is similar to ``pw_toolchain`` except that
-instead of using ``generate_toolchain`` use ``pw_generate_android_toolchain``,
-and ensure that ``current_cpu`` is set in the toolchain ``defaults``.
-
-For example:
-
-.. code::
-
-  import("//build_overrides/pigweed.gni")
-
-  import("$dir_pw_android_toolchain/toolchains.gni")
-  import("$dir_pw_android_toolchain/generate_toolchain.gni")
-
-  my_target_scope = {
-    # Use Pigweed's Android toolchain as a base.
-    _toolchain_base = pw_toolchain_android.debug
-
-    # Forward everything except the defaults scope from that toolchain.
-    forward_variables_from(_toolchain_base, "*", [ "defaults" ])
-
-    defaults = {
-      # Forward everything from the base toolchain's defaults.
-      forward_variables_from(_toolchain_base.defaults, "*")
-
-      # Build for 64-bit AArch64 Android devices.
-      current_cpu = "arm64"
-
-      # Extend with custom build arguments for the target.
-      pw_log_BACKEND = dir_pw_log_tokenized
-    }
-  }
-
-  # Create the actual GN toolchain from the scope.
-  pw_generate_android_toolchain("my_target") {
-    forward_variables_from(my_target_scope, "*")
-  }
-
-Since Android NDKs contain toolchains for all supported targets, as a
-convenience, ``pw_generate_android_toolchains`` does not require that
-``current_cpu`` is set. If any toolchain scope in the list does not set it, a
-toolchain for each supported target will be generated.
-
-.. code::
-
-  # Generate arm_*, arm64_*, x64_*, and x86_* for each scope in the list.
-  pw_generate_android_toolchains("target_toolchains) {
-    toolchains = pw_toolchain_android_list
-  }
-
-Customization
--------------
-The Android SDK target version defaults to the value of the
-``pw_android_toolchain_API_LEVEL`` build arg. You can override this on global
-level, or on a per-toolchain level by setting ``api_level`` in the toolchain
-defaults.
diff --git a/pw_android_toolchain/generate_toolchain.gni b/pw_android_toolchain/generate_toolchain.gni
deleted file mode 100644
index 55952e1..0000000
--- a/pw_android_toolchain/generate_toolchain.gni
+++ /dev/null
@@ -1,134 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_android_toolchain/android.gni")
-import("$dir_pw_toolchain/generate_toolchain.gni")
-
-# Creates an Android toolchain target.
-#
-# Arguments are forwarded to $generate_toolchain.
-template("pw_generate_android_toolchain") {
-  assert(pw_android_toolchain_NDK_PATH != "",
-         "pw_android_toolchain_NDK_PATH is not set")
-  assert(defined(invoker.defaults), "toolchain is missing 'defaults'")
-  invoker_toolchain_args = invoker.defaults
-
-  # Build _clang_prefix from "host_os" and "host_cpu".
-  _host_os = ""
-  if (host_os == "linux") {
-    _host_os = "linux"
-  } else if (host_os == "mac") {
-    _host_os = "darwin"
-  } else if (host_os == "win") {
-    _host_os = "windows"
-  }
-
-  _host_cpu = ""
-  if (host_cpu == "x64") {
-    _host_cpu = "-x86_64"
-  }
-
-  _clang_prefix = "${pw_android_toolchain_NDK_PATH}/toolchains/llvm/prebuilt/${_host_os}${_host_cpu}/bin/"
-
-  # Build _tool_name_root from "_ndk_cpu" and "api_level".
-  if (defined(invoker_toolchain_args.api_level)) {
-    _api_level = invoker_toolchain_args.api_level
-  } else {
-    _api_level = pw_android_toolchain_API_LEVEL
-  }
-
-  assert(defined(invoker_toolchain_args.current_cpu),
-         "toolchain.defaults is missing 'current_cpu'")
-  if (invoker_toolchain_args.current_cpu == "arm") {
-    _tool_name_root = "armv7a-linux-androideabi${_api_level}-"
-  } else if (invoker_toolchain_args.current_cpu == "arm64") {
-    _tool_name_root = "aarch64-linux-android${_api_level}-"
-  } else if (invoker_toolchain_args.current_cpu == "x86") {
-    _tool_name_root = "i686-linux-android${_api_level}-"
-  } else if (invoker_toolchain_args.current_cpu == "x64") {
-    _tool_name_root = "x86_64-linux-android${_api_level}-"
-  } else {
-    assert(false, "toolchain.defaults.current_cpu unknown or invalid")
-  }
-
-  generate_toolchain(target_name) {
-    ar = _clang_prefix + "llvm-ar"
-    cc = _clang_prefix + _tool_name_root + "clang"
-    cxx = _clang_prefix + _tool_name_root + "clang++"
-
-    forward_variables_from(invoker,
-                           "*",
-                           [
-                             "defaults",
-                             "name",
-                           ])
-    defaults = {
-      current_os = "android"
-      forward_variables_from(invoker_toolchain_args, "*")
-    }
-  }
-}
-
-# Creates a series of Android toolchain targets with common compiler options.
-#
-# Args:
-#   toolchains: List of scopes defining each of the desired tolchains.
-#   The scope must contain a "name" variable; other variables are forwared to
-#   $generate_toolchain.
-template("pw_generate_android_toolchains") {
-  not_needed([ "target_name" ])
-  assert(
-      defined(invoker.toolchains),
-      "pw_generate_android_toolchains must be called with a list of toolchains")
-
-  # Create a target for each of the desired toolchains.
-  foreach(_toolchain, invoker.toolchains) {
-    # If the toolchain defines a CPU, use that, otherwise expand to all of the
-    # CPU targets an NDK may contain and prepend the CPU name.
-    _current_cpu = ""
-    if (defined(_toolchain.defaults)) {
-      invoker_toolchain_args = {
-      }
-      invoker_toolchain_args = _toolchain.defaults
-      if (defined(invoker_toolchain_args.current_cpu)) {
-        _current_cpu = invoker_toolchain_args.current_cpu
-      }
-    }
-
-    if (_current_cpu != "") {
-      pw_generate_android_toolchain(_toolchain.name) {
-        forward_variables_from(_toolchain, "*", [ "name" ])
-      }
-    } else {
-      foreach(_current_cpu, pw_android_toolchain_cpu_targets) {
-        pw_generate_android_toolchain("${_current_cpu}_${_toolchain.name}") {
-          forward_variables_from(_toolchain,
-                                 "*",
-                                 [
-                                   "defaults",
-                                   "name",
-                                 ])
-          defaults = {
-            current_cpu = _current_cpu
-            if (defined(_toolchain.defaults)) {
-              forward_variables_from(_toolchain.defaults, "*")
-            }
-          }
-        }
-      }
-    }
-  }
-}
diff --git a/pw_android_toolchain/toolchains.gni b/pw_android_toolchain/toolchains.gni
deleted file mode 100644
index 2199328..0000000
--- a/pw_android_toolchain/toolchains.gni
+++ /dev/null
@@ -1,52 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-# Common configs shared by all Android toolchains.
-_android = [ "$dir_pw_android_toolchain:static_libstdc" ]
-
-# Describe base Android toolchains.
-pw_toolchain_android = {
-  debug = {
-    name = "android_debug"
-    defaults = {
-      default_configs = _android + [ "$dir_pw_build:optimize_debugging" ]
-      remove_default_configs = [ "$dir_pw_build:relative_paths" ]
-    }
-  }
-  speed_optimized = {
-    name = "android_speed_optimized"
-    defaults = {
-      default_configs = _android + [ "$dir_pw_build:optimize_speed" ]
-      remove_default_configs = [ "$dir_pw_build:relative_paths" ]
-    }
-  }
-  size_optimized = {
-    name = "android_size_optimized"
-    defaults = {
-      default_configs = _android + [ "$dir_pw_build:optimize_size" ]
-      remove_default_configs = [ "$dir_pw_build:relative_paths" ]
-    }
-  }
-}
-
-# This list just contains the members of the above scope for convenience to make
-# it trivial to generate all the toolchains in this file via a
-# `pw_generate_android_toolchains` target.
-pw_toolchain_android_list = [
-  pw_toolchain_android.debug,
-  pw_toolchain_android.speed_optimized,
-  pw_toolchain_android.size_optimized,
-]
diff --git a/pw_arduino_build/BUILD b/pw_arduino_build/BUILD
new file mode 100644
index 0000000..a66ce62
--- /dev/null
+++ b/pw_arduino_build/BUILD
@@ -0,0 +1,31 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_arduino_build",
+    srcs = ["arduino_main_wrapper.cc"],
+    hdrs = ["public/pw_arduino_build/init.h"],
+    deps = [
+        "//pw_sys_io",
+    ],
+)
diff --git a/pw_arduino_build/BUILD.bazel b/pw_arduino_build/BUILD.bazel
deleted file mode 100644
index 29e210a..0000000
--- a/pw_arduino_build/BUILD.bazel
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_arduino_build",
-    srcs = ["arduino_main_wrapper.cc"],
-    hdrs = ["public/pw_arduino_build/init.h"],
-    deps = [
-        "//pw_sys_io",
-    ],
-)
diff --git a/pw_arduino_build/OWNERS b/pw_arduino_build/OWNERS
deleted file mode 100644
index ca011e8..0000000
--- a/pw_arduino_build/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-tonymd@google.com
diff --git a/pw_arduino_build/arduino.gni b/pw_arduino_build/arduino.gni
index 3ba0000..0d1704c 100644
--- a/pw_arduino_build/arduino.gni
+++ b/pw_arduino_build/arduino.gni
@@ -13,7 +13,6 @@
 # the License.
 
 import("//build_overrides/pigweed.gni")
-import("//build_overrides/pigweed_environment.gni")
 
 declare_args() {
   # Enable/disable Arduino builds via group("arduino").
@@ -47,8 +46,7 @@
              _required_args_message)
 
   _arduino_selected_core_path =
-      rebase_path("$pw_arduino_build_CORE_PATH/$pw_arduino_build_CORE_NAME",
-                  root_build_dir)
+      rebase_path("$pw_arduino_build_CORE_PATH/$pw_arduino_build_CORE_NAME")
 
   arduino_builder_script =
       get_path_info("py/pw_arduino_build/__main__.py", "abspath")
@@ -62,7 +60,7 @@
              pw_arduino_build_PACKAGE_NAME + " list-boards")
 
   _compiler_path_override =
-      rebase_path(dir_cipd_pigweed + "/bin", root_build_dir)
+      rebase_path(getenv("_PW_ACTUAL_ENVIRONMENT_ROOT") + "/cipd/pigweed/bin")
 
   arduino_core_library_path = "$_arduino_selected_core_path/hardware/" +
                               "$pw_arduino_build_PACKAGE_NAME/libraries"
@@ -77,13 +75,13 @@
 
     # Save config files to "out/arduino_debug/gen/arduino_builder_config.json"
     "--config-file",
-    rebase_path(root_gen_dir, root_build_dir) + "/arduino_builder_config.json",
+    rebase_path(root_gen_dir) + "/arduino_builder_config.json",
     "--save-config",
   ]
 
   arduino_board_args = [
     "--build-path",
-    ".",
+    rebase_path(root_build_dir),
     "--board",
     pw_arduino_build_BOARD,
   ]
diff --git a/pw_arduino_build/py/BUILD.gn b/pw_arduino_build/py/BUILD.gn
index 731b345..990f4db 100644
--- a/pw_arduino_build/py/BUILD.gn
+++ b/pw_arduino_build/py/BUILD.gn
@@ -17,11 +17,7 @@
 import("$dir_pw_build/python.gni")
 
 pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
+  setup = [ "setup.py" ]
   sources = [
     "pw_arduino_build/__init__.py",
     "pw_arduino_build/__main__.py",
diff --git a/pw_arduino_build/py/pw_arduino_build/builder.py b/pw_arduino_build/py/pw_arduino_build/builder.py
index 2a9dcc3..b19946e 100755
--- a/pw_arduino_build/py/pw_arduino_build/builder.py
+++ b/pw_arduino_build/py/pw_arduino_build/builder.py
@@ -274,36 +274,43 @@
         # TODO(tonymd): Make sure these variables are replaced in recipe lines
         # even if they are None: build_path, project_path, project_source_path,
         # build_project_name
-        for current_board in self.board.values():
+        for current_board_name in self.board.keys():
             if self.build_path:
-                current_board["build.path"] = self.build_path
+                self.board[current_board_name]["build.path"] = self.build_path
             if self.build_project_name:
-                current_board["build.project_name"] = self.build_project_name
+                self.board[current_board_name][
+                    "build.project_name"] = self.build_project_name
                 # {archive_file} is the final *.elf
                 archive_file = "{}.elf".format(self.build_project_name)
-                current_board["archive_file"] = archive_file
+                self.board[current_board_name]["archive_file"] = archive_file
                 # {archive_file_path} is the final core.a archive
                 if self.build_path:
-                    current_board["archive_file_path"] = os.path.join(
-                        self.build_path, "core.a")
+                    self.board[current_board_name][
+                        "archive_file_path"] = os.path.join(
+                            self.build_path, "core.a")
             if self.project_source_path:
-                current_board["build.source.path"] = self.project_source_path
+                self.board[current_board_name][
+                    "build.source.path"] = self.project_source_path
 
-            current_board["extra.time.local"] = str(int(time.time()))
-            current_board["runtime.ide.version"] = "10812"
-            current_board["runtime.hardware.path"] = self.hardware_path
+            self.board[current_board_name]["extra.time.local"] = str(
+                int(time.time()))
+            self.board[current_board_name]["runtime.ide.version"] = "10812"
+            self.board[current_board_name][
+                "runtime.hardware.path"] = self.hardware_path
 
             # Copy {runtime.tools.TOOL_NAME.path} vars
-            self._set_tools_variables(current_board)
+            self._set_tools_variables(self.board[current_board_name])
 
-            current_board["runtime.platform.path"] = self.package_path
+            self.board[current_board_name][
+                "runtime.platform.path"] = self.package_path
             if self.platform["name"] == "Teensyduino":
                 # Teensyduino is installed into the arduino IDE folder
                 # rather than ~/.arduino15/packages/
-                current_board["runtime.hardware.path"] = os.path.join(
-                    self.hardware_path, "teensy")
+                self.board[current_board_name][
+                    "runtime.hardware.path"] = os.path.join(
+                        self.hardware_path, "teensy")
 
-            current_board["build.system.path"] = os.path.join(
+            self.board[current_board_name]["build.system.path"] = os.path.join(
                 self.package_path, "system")
 
             # Set the {build.core.path} variable that pointing to a sub-core
@@ -312,15 +319,16 @@
             # it's typically just the 'arduino' folder. For example:
             # 'arduino-samd/hardware/samd/1.8.8/cores/arduino'
             core_path = Path(self.package_path) / "cores"
-            core_path /= current_board.get("build.core",
-                                           self.sub_core_folders[0])
-            current_board["build.core.path"] = core_path.as_posix()
+            core_path /= self.board[current_board_name].get(
+                "build.core", self.sub_core_folders[0])
+            self.board[current_board_name][
+                "build.core.path"] = core_path.as_posix()
 
-            current_board["build.arch"] = self.build_arch
+            self.board[current_board_name]["build.arch"] = self.build_arch
 
-            for name, var in current_board.items():
-                current_board[name] = var.replace("{build.core.path}",
-                                                  core_path.as_posix())
+            for name, var in self.board[current_board_name].items():
+                self.board[current_board_name][name] = var.replace(
+                    "{build.core.path}", core_path.as_posix())
 
     def load_board_definitions(self):
         """Loads Arduino boards.txt and platform.txt files into dictionaries.
@@ -355,7 +363,7 @@
                     b_match["name"]] = OrderedDict()
 
             # Get all board variables, e.g. teensy40.*
-            for current_board_name, current_board in self.board.items():
+            for current_board_name in self.board.keys():
                 board_line_matches = re.finditer(
                     fr"^\s*{current_board_name}\."
                     fr"(?P<key>[^#=]+)=(?P<value>.*)$", board_file,
@@ -367,7 +375,8 @@
                     ArduinoBuilder.save_default_menu_option(
                         current_board_name, b_match["key"], b_match["value"],
                         self.menu_options)
-                    current_board[b_match["key"]] = b_match["value"].strip()
+                    self.board[current_board_name][
+                        b_match["key"]] = b_match["value"].strip()
 
             self._set_global_arduino_variables()
 
diff --git a/pw_arduino_build/py/pw_arduino_build/core_installer.py b/pw_arduino_build/py/pw_arduino_build/core_installer.py
index 662f90e..2d3dbf7 100644
--- a/pw_arduino_build/py/pw_arduino_build/core_installer.py
+++ b/pw_arduino_build/py/pw_arduino_build/core_installer.py
@@ -27,7 +27,7 @@
 from pathlib import Path
 from typing import Dict, List
 
-from pw_arduino_build import file_operations
+import pw_arduino_build.file_operations as file_operations
 
 _LOG = logging.getLogger(__name__)
 
@@ -414,6 +414,6 @@
     original_working_dir = os.getcwd()
     os.chdir(install_prefix)
     # TODO(tonymd): Fetch platform specific tools as specified by:
-    # https://github.com/stm32duino/BoardManagerFiles/raw/HEAD/STM32/package_stm_index.json
+    # https://github.com/stm32duino/BoardManagerFiles/raw/master/STM32/package_stm_index.json
     os.chdir(original_working_dir)
     return True
diff --git a/pw_arduino_build/py/pw_arduino_build/unit_test_server.py b/pw_arduino_build/py/pw_arduino_build/unit_test_server.py
index 947b2ae..edbae1d 100644
--- a/pw_arduino_build/py/pw_arduino_build/unit_test_server.py
+++ b/pw_arduino_build/py/pw_arduino_build/unit_test_server.py
@@ -12,7 +12,7 @@
 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 # License for the specific language governing permissions and limitations under
 # the License.
-"""Launch a pw_target_runner server to use for multi-device testing."""
+"""Launch a pw_test_server server to use for multi-device testing."""
 
 import argparse
 import logging
diff --git a/pw_arduino_build/py/pyproject.toml b/pw_arduino_build/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/pw_arduino_build/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/pw_arduino_build/py/setup.cfg b/pw_arduino_build/py/setup.cfg
deleted file mode 100644
index 05f6d2a..0000000
--- a/pw_arduino_build/py/setup.cfg
+++ /dev/null
@@ -1,41 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pw_arduino_build
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Target-specific python scripts for the arduino target
-
-[options]
-packages = find:
-zip_safe = False
-install_requires =
-    pyserial>=3.5,<4.0
-    coloredlogs
-    parameterized
-
-[options.entry_points]
-console_scripts =
-    arduino_builder = pw_arduino_build.__main__:main
-    teensy_detector = pw_arduino_build.teensy_detector:main
-    arduino_unit_test_runner = pw_arduino_build.unit_test_runner:main
-    arduino_test_server = pw_arduino_build.unit_test_server:main
-    arduino_test_client = pw_arduino_build.unit_test_client:main
-
-[options.package_data]
-pw_arduino_build =
-    core_patches/teensy/01-teensyduino_1.53-cpp17.diff
-    core_patches/teensy/02-teensy4_nonstatic_flash_functions.diff
-    py.typed
diff --git a/pw_arduino_build/py/setup.py b/pw_arduino_build/py/setup.py
index 484362f..528ca95 100644
--- a/pw_arduino_build/py/setup.py
+++ b/pw_arduino_build/py/setup.py
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2019 The Pigweed Authors
 #
 # 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
@@ -15,4 +15,35 @@
 
 import setuptools  # type: ignore
 
-setuptools.setup()  # Package definition in setup.cfg
+setuptools.setup(
+    name='pw_arduino_build',
+    version='0.0.1',
+    author='Pigweed Authors',
+    author_email='pigweed-developers@googlegroups.com',
+    description='Target-specific python scripts for the arduino target',
+    packages=setuptools.find_packages(),
+    package_data={
+        'pw_arduino_build': [
+            'core_patches/teensy/01-teensyduino_1.53-cpp17.diff',
+            'core_patches/teensy/02-teensy4_nonstatic_flash_functions.diff',
+            'py.typed',
+        ]
+    },
+    zip_safe=False,
+    entry_points={
+        'console_scripts': [
+            'arduino_builder = pw_arduino_build.__main__:main',
+            'teensy_detector = pw_arduino_build.teensy_detector:main',
+            'arduino_unit_test_runner = '
+            '    pw_arduino_build.unit_test_runner:main',
+            'arduino_test_server = '
+            '    pw_arduino_build.unit_test_server:main',
+            'arduino_test_client = '
+            '    pw_arduino_build.unit_test_client:main',
+        ]
+    },
+    install_requires=[
+        'pyserial>=3.5,<4.0',
+        'coloredlogs',
+        'parameterized',
+    ])
diff --git a/pw_assert/BUILD b/pw_assert/BUILD
new file mode 100644
index 0000000..58ce601
--- /dev/null
+++ b/pw_assert/BUILD
@@ -0,0 +1,92 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+# TODO(pwbug/101): Need to add support for facades/backends to Bazel.
+PW_ASSERT_BACKEND = "//pw_assert_basic"
+
+pw_cc_library(
+    name = "facade",
+    hdrs = [
+        "public/pw_assert/assert.h",
+        "public/pw_assert/check.h",
+        "public/pw_assert/internal/check_impl.h",
+        "public/pw_assert/light.h",
+        "public/pw_assert/options.h",
+        "public/pw_assert/short.h",
+    ],
+    includes = ["public"],
+    deps = [
+        PW_ASSERT_BACKEND + ":headers",
+        "//pw_preprocessor",
+    ],
+)
+
+pw_cc_library(
+    name = "pw_assert",
+    deps = [
+        ":facade",
+        PW_ASSERT_BACKEND + ":headers",
+        PW_ASSERT_BACKEND,
+    ],
+)
+
+pw_cc_library(
+    name = "backend",
+    deps = [
+        PW_ASSERT_BACKEND,
+    ],
+)
+
+pw_cc_test(
+    name = "assert_facade_test",
+    srcs = [
+        "assert_facade_test.cc",
+        "fake_backend.cc",
+        "light_test.cc",
+        "public/pw_assert/internal/assert_impl.h",
+        "pw_assert_test/fake_backend.h",
+    ],
+    deps = [
+        ":facade",
+        "//pw_preprocessor",
+        "//pw_span",
+        "//pw_string",
+        "//pw_unit_test",
+        PW_ASSERT_BACKEND,
+    ],
+)
+
+pw_cc_test(
+    name = "assert_backend_compile_test",
+    srcs = [
+        "assert_backend_compile_test.cc",
+        "assert_backend_compile_test_c.c",
+    ],
+    deps = [
+        ":backend",
+        ":facade",
+        ":pw_assert",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_assert/BUILD.bazel b/pw_assert/BUILD.bazel
deleted file mode 100644
index 080fc99..0000000
--- a/pw_assert/BUILD.bazel
+++ /dev/null
@@ -1,90 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_facade",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_facade(
-    name = "facade",
-    hdrs = [
-        "assert_lite_public_overrides/pw_assert_backend/assert_lite_backend.h",
-        "print_and_abort_public_overrides/pw_assert_backend/assert_lite_backend.h",
-        "public/pw_assert/assert.h",
-        "public/pw_assert/check.h",
-        "public/pw_assert/config.h",
-        "public/pw_assert/internal/check_impl.h",
-        "public/pw_assert/internal/print_and_abort.h",
-        "public/pw_assert/short.h",
-    ],
-    includes = [
-        "assert_lite_public_overrides",
-        "public",
-    ],
-    deps = [
-        "//pw_preprocessor",
-    ],
-)
-
-pw_cc_library(
-    name = "pw_assert",
-    deps = [
-        ":facade",
-        "@pigweed_config//:pw_assert_backend",
-    ],
-)
-
-pw_cc_library(
-    name = "backend_multiplexer",
-    visibility = ["@pigweed_config//:__pkg__"],
-    deps = ["@pigweed//pw_assert_basic"],
-)
-
-pw_cc_test(
-    name = "assert_facade_test",
-    srcs = [
-        "assert_facade_test.cc",
-        "assert_test.cc",
-        "fake_backend.cc",
-        "public/pw_assert/internal/check_impl.h",
-        "pw_assert_test/fake_backend.h",
-    ],
-    deps = [
-        ":facade",
-        "//pw_assert",
-        "//pw_preprocessor",
-        "//pw_span",
-        "//pw_string",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "assert_backend_compile_test",
-    srcs = [
-        "assert_backend_compile_test.cc",
-        "assert_backend_compile_test_c.c",
-    ],
-    deps = [
-        "//pw_assert",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_assert/BUILD.gn b/pw_assert/BUILD.gn
index 1588210..5e83da1 100644
--- a/pw_assert/BUILD.gn
+++ b/pw_assert/BUILD.gn
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2020 The Pigweed Authors
 #
 # 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
@@ -15,162 +15,72 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_build/facade.gni")
-import("$dir_pw_build/module_config.gni")
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_unit_test/test.gni")
-import("backend.gni")
 
 declare_args() {
-  # The build target that overrides the default configuration options for this
-  # module. This should point to a source set that provides defines through a
-  # public config (which may -include a file or add defines directly).
-  pw_assert_CONFIG = pw_build_DEFAULT_MODULE_CONFIG
+  # Backend for the pw_assert module.
+  pw_assert_BACKEND = ""
 }
 
-config("public_include_path") {
+config("default_config") {
   include_dirs = [ "public" ]
-  visibility = [ ":*" ]
 }
 
-config("lite_backend_overrides") {
-  include_dirs = [ "assert_lite_public_overrides" ]
-  visibility = [ ":*" ]
-}
-
-config("print_and_abort_backend_overrides") {
-  include_dirs = [ "print_and_abort_public_overrides" ]
-  visibility = [ ":*" ]
-}
-
-pw_source_set("config") {
-  public = [ "public/pw_assert/config.h" ]
-  public_configs = [ ":public_include_path" ]
-  public_deps = [ pw_assert_CONFIG ]
-}
-
-# Depending on dir_pw_assert provides both assert and check.
-group("pw_assert") {
-  public_deps = [
-    ":assert",
-    ":check",
-    ":config",
-  ]
-}
-
-# Wrap :pw_assert with facade-style targets, so it can be used as if it were
-# created with pw_facade.
-group("facade") {
-  public_deps = [
-    ":assert",
-    ":check.facade",
-  ]
-}
-
-group("pw_assert.facade") {
-  public_deps = [ ":facade" ]
-}
-
-# Provides the rich PW_CHECK macros.
-pw_facade("check") {
+pw_facade("pw_assert") {
   backend = pw_assert_BACKEND
-  public_configs = [ ":public_include_path" ]
+  public_configs = [ ":default_config" ]
   public = [
     "public/pw_assert/check.h",
     "public/pw_assert/internal/check_impl.h",
     "public/pw_assert/short.h",
   ]
   public_deps = [
-    ":config",
     dir_pw_preprocessor,
+
+    # Also expose assert.h to all users of pw_assert.
+    ":light",
   ]
 
-  require_link_deps = [ ":impl" ]
+  # TODO(pwbug/350): Allow assert.h to include check.h for backwards
+  #     compatibility. Remove this when projects have migrated.
+  allow_circular_includes_from = [ ":light" ]
+  deps = [ ":light" ]
 }
 
-# Provide "pw_assert/assert.h" in its own source set, so it can be used without
-# depending on pw_assert_BACKEND.
-pw_facade("assert") {
-  backend = pw_assert_LITE_BACKEND
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_assert/assert.h" ]
-  public_deps = [ ":config" ]
-}
-
-# This backend to pw_assert's PW_ASSERT()/PW_DASSERT() macros provides backwards
-# compatibility with pw_assert's previous C-symbol based API.
+# Provide a way include "pw_assert/light.h" without depending on the full
+# assert facade. This enables relying on light asserts from low-level headers
+# like polyfill or span that might trigger circular includes due to the
+# backend.
 #
-# Warning: The "lite" naming is transitional. assert_lite_backend.h headers
-# will be renamed as the pw_assert API is reassessed. (pwbug/246)
-pw_source_set("lite_compatibility_backend") {
-  public_configs = [ ":lite_backend_overrides" ]
-  public_deps = [ dir_pw_preprocessor ]
-  public =
-      [ "assert_lite_public_overrides/pw_assert_backend/assert_lite_backend.h" ]
-}
-
-group("lite_compatibility_backend.impl") {
-}
-
-# This backend to pw_assert's PW_ASSERT()/PW_DASSERT() macros prints the assert
-# expression, file/line number, and function with printf, then aborts. It is
-# intended for use with host builds.
-#
-# Warning: The "lite" naming is transitional. assert_lite_backend.h headers
-# will be renamed as the pw_assert API is reassessed. (pwbug/246)
-pw_source_set("print_and_abort") {
-  public_configs = [
-    ":print_and_abort_backend_overrides",
-    ":public_include_path",
-  ]
-  public_deps = [
-    ":config",
-    dir_pw_preprocessor,
-  ]
+# See the docs for more discussion around where to use which assert system.
+pw_source_set("light") {
+  public_configs = [ ":default_config" ]
   public = [
-    "print_and_abort_public_overrides/pw_assert_backend/assert_lite_backend.h",
+    "public/pw_assert/assert.h",
+    "public/pw_assert/light.h",
+
+    # Needed for PW_ASSERT_ENABLE_DEBUG. Note that depending on :pw_assert to
+    # get options.h won't work here since it will trigger the circular include
+    # problem that light asserts are designed to solve.
+    "public/pw_assert/options.h",
   ]
-  sources = [ "public/pw_assert/internal/print_and_abort.h" ]
-}
-
-group("print_and_abort.impl") {
-}
-
-# pw_assert is low-level and ubiquitous. Because of this, it can often cause
-# circular dependencies. This target collects dependencies from the backend that
-# cannot be used because they would cause circular deps.
-#
-# This group ("$dir_pw_assert:impl") must listed in pw_build_LINK_DEPS if
-# pw_assert_BACKEND is set.
-#
-# pw_assert backends must provide their own "impl" target that collects their
-# actual dependencies. The backend "impl" group may be empty if everything can
-# go directly in the backend target without causing circular dependencies.
-group("impl") {
-  public_deps = []
-
-  if (pw_assert_BACKEND != "") {
-    public_deps +=
-        [ get_label_info(pw_assert_BACKEND, "label_no_toolchain") + ".impl" ]
-  }
-  if (pw_assert_LITE_BACKEND != "") {
-    public_deps += [ get_label_info(pw_assert_LITE_BACKEND,
-                                    "label_no_toolchain") + ".impl" ]
-  }
+  public_deps = [ dir_pw_preprocessor ]
 }
 
 # Note: While this is technically a test, doesn't verify any of the output and
 # is more of a compile test. The results can be visually verified if desired.
-pw_test("assert_test") {
-  configs = [ ":public_include_path" ]
-  sources = [ "assert_test.cc" ]
+pw_test("light_test") {
+  configs = [ ":default_config" ]
+  sources = [ "light_test.cc" ]
   deps = [ ":pw_assert" ]
 }
 
 pw_test_group("tests") {
   tests = [
-    ":assert_test",
     ":assert_backend_compile_test",
     ":assert_facade_test",
+    ":light_test",
   ]
 }
 
@@ -178,7 +88,7 @@
 # provided. However, since this doesn't depend on the backend it re-includes
 # the facade headers.
 pw_test("assert_facade_test") {
-  configs = [ ":public_include_path" ]  # For internal/assert_impl.h
+  configs = [ ":default_config" ]  # For internal/assert_impl.h
   sources = [
     "assert_facade_test.cc",
     "fake_backend.cc",
@@ -186,7 +96,7 @@
     "pw_assert_test/fake_backend.h",
   ]
   deps = [
-    ":assert",
+    ":light",
     dir_pw_status,
   ]
 
diff --git a/pw_assert/CMakeLists.txt b/pw_assert/CMakeLists.txt
index b58bb27..9d9a259 100644
--- a/pw_assert/CMakeLists.txt
+++ b/pw_assert/CMakeLists.txt
@@ -14,11 +14,7 @@
 
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
 
-pw_add_module_config(pw_assert_CONFIG)
-
 pw_add_facade(pw_assert
   PUBLIC_DEPS
     pw_preprocessor
-    ${pw_assert_CONFIG}
 )
-target_include_directories(pw_assert INTERFACE assert_lite_public_overrides)
diff --git a/pw_assert/OWNERS b/pw_assert/OWNERS
deleted file mode 100644
index 2529495..0000000
--- a/pw_assert/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-ewout@google.com
-keir@google.com
diff --git a/pw_assert/assert_backend_compile_test.cc b/pw_assert/assert_backend_compile_test.cc
index 96f17e4..5a98b15 100644
--- a/pw_assert/assert_backend_compile_test.cc
+++ b/pw_assert/assert_backend_compile_test.cc
@@ -35,9 +35,13 @@
 //
 // TODO(pwbug/88): Add verification of the actually recorded asserts statements.
 
-#include "gtest/gtest.h"
-#include "pw_assert/short.h"
+// clang-format off
+#define PW_ASSERT_USE_SHORT_NAMES 1
+#include "pw_assert/assert.h"
+// clang-format on
+
 #include "pw_status/status.h"
+#include "gtest/gtest.h"
 
 // This is a global constant to feed into the formatter for tests.
 // Intended to pair with FAIL_IF_DISPLAYED_ARGS or FAIL_IF_HIDDEN_ARGS.
@@ -167,6 +171,7 @@
   PW_CHECK_INT_LE(x_int, y_int, "INT: " FAIL_IF_DISPLAYED_ARGS, z);
 }
 
+// Note: This requires enabling PW_ASSERT_USE_SHORT_NAMES 1 above.
 TEST(Check, ShortNamesWork) {
   MAYBE_SKIP_TEST;
 
diff --git a/pw_assert/assert_backend_compile_test_c.c b/pw_assert/assert_backend_compile_test_c.c
index f0c9d05..a4aaced 100644
--- a/pw_assert/assert_backend_compile_test_c.c
+++ b/pw_assert/assert_backend_compile_test_c.c
@@ -19,7 +19,11 @@
 // will abort. However, the assert_basic backend supports non-aborting assert;
 // see the note in assert_backend_compile_test.cc.
 
-#include "pw_assert/check.h"
+// The compile tests verifies that the short macros compile, so enable them.
+#undef PW_ASSERT_USE_SHORT_NAMES
+#define PW_ASSERT_USE_SHORT_NAMES 1
+
+#include "pw_assert/assert.h"
 
 static void EnsureNullIsIncluded(void) {
   // This is a compile check to ensure NULL is defined. It comes before the
@@ -30,8 +34,7 @@
 
 #include <stdbool.h>
 
-#include "pw_assert/assert.h"
-#include "pw_assert/short.h"
+#include "pw_assert/light.h"
 #include "pw_status/status.h"
 
 #ifdef __cplusplus
@@ -168,6 +171,7 @@
     PW_CHECK_INT_LE(x_int, y_int, "INT: " FAIL_IF_DISPLAYED_ARGS, z);
   }
 
+  // Note: This requires enabling PW_ASSERT_USE_SHORT_NAMES 1 above.
   {  // TEST(Check, ShortNamesWork) {
     MAYBE_SKIP_TEST;
 
diff --git a/pw_assert/assert_facade_test.cc b/pw_assert/assert_facade_test.cc
index a2a6fdd..088f725 100644
--- a/pw_assert/assert_facade_test.cc
+++ b/pw_assert/assert_facade_test.cc
@@ -26,7 +26,6 @@
 // clang-format on
 
 #include "gtest/gtest.h"
-#include "pw_status/status.h"
 
 namespace {
 
@@ -194,91 +193,38 @@
 // Test comparison boundaries.
 
 // PTR <
-TEST_F(AssertPass, PtrLt1) {
-  PW_CHECK_PTR_LT(reinterpret_cast<void*>(0xa), reinterpret_cast<void*>(0xb));
-}
-TEST_F(AssertFail, PtrLt2) {
-  PW_CHECK_PTR_LT(reinterpret_cast<void*>(0xb), reinterpret_cast<void*>(0xb));
-}
-TEST_F(AssertFail, PtrLt3) {
-  PW_CHECK_PTR_LT(reinterpret_cast<void*>(0xb), reinterpret_cast<void*>(0xa));
-}
+TEST_F(AssertPass, PtrLt1) { PW_CHECK_PTR_LT(0xa, 0xb); }
+TEST_F(AssertFail, PtrLt2) { PW_CHECK_PTR_LT(0xb, 0xb); }
+TEST_F(AssertFail, PtrLt3) { PW_CHECK_PTR_LT(0xb, 0xa); }
 
 // PTR <=
-TEST_F(AssertPass, PtrLe1) {
-  PW_CHECK_PTR_LE(reinterpret_cast<void*>(0xa), reinterpret_cast<void*>(0xb));
-}
-TEST_F(AssertPass, PtrLe2) {
-  PW_CHECK_PTR_LE(reinterpret_cast<void*>(0xb), reinterpret_cast<void*>(0xb));
-}
-TEST_F(AssertFail, PtrLe3) {
-  PW_CHECK_PTR_LE(reinterpret_cast<void*>(0xb), reinterpret_cast<void*>(0xa));
-}
+TEST_F(AssertPass, PtrLe1) { PW_CHECK_PTR_LE(0xa, 0xb); }
+TEST_F(AssertPass, PtrLe2) { PW_CHECK_PTR_LE(0xb, 0xb); }
+TEST_F(AssertFail, PtrLe3) { PW_CHECK_PTR_LE(0xb, 0xa); }
 
 // PTR ==
-TEST_F(AssertFail, PtrEq1) {
-  PW_CHECK_PTR_EQ(reinterpret_cast<void*>(0xa), reinterpret_cast<void*>(0xb));
-}
-TEST_F(AssertPass, PtrEq2) {
-  PW_CHECK_PTR_EQ(reinterpret_cast<void*>(0xb), reinterpret_cast<void*>(0xb));
-}
-TEST_F(AssertFail, PtrEq3) {
-  PW_CHECK_PTR_EQ(reinterpret_cast<void*>(0xb), reinterpret_cast<void*>(0xa));
-}
+TEST_F(AssertFail, PtrEq1) { PW_CHECK_PTR_EQ(0xa, 0xb); }
+TEST_F(AssertPass, PtrEq2) { PW_CHECK_PTR_EQ(0xb, 0xb); }
+TEST_F(AssertFail, PtrEq3) { PW_CHECK_PTR_EQ(0xb, 0xa); }
 
 // PTR !=
-TEST_F(AssertPass, PtrNe1) {
-  PW_CHECK_PTR_NE(reinterpret_cast<void*>(0xa), reinterpret_cast<void*>(0xb));
-}
-TEST_F(AssertFail, PtrNe2) {
-  PW_CHECK_PTR_NE(reinterpret_cast<void*>(0xb), reinterpret_cast<void*>(0xb));
-}
-TEST_F(AssertPass, PtrNe3) {
-  PW_CHECK_PTR_NE(reinterpret_cast<void*>(0xb), reinterpret_cast<void*>(0xa));
-}
+TEST_F(AssertPass, PtrNe1) { PW_CHECK_PTR_NE(0xa, 0xb); }
+TEST_F(AssertFail, PtrNe2) { PW_CHECK_PTR_NE(0xb, 0xb); }
+TEST_F(AssertPass, PtrNe3) { PW_CHECK_PTR_NE(0xb, 0xa); }
 
 // PTR >
-TEST_F(AssertFail, PtrGt1) {
-  PW_CHECK_PTR_GT(reinterpret_cast<void*>(0xa), reinterpret_cast<void*>(0xb));
-}
-TEST_F(AssertFail, PtrGt2) {
-  PW_CHECK_PTR_GT(reinterpret_cast<void*>(0xb), reinterpret_cast<void*>(0xb));
-}
-TEST_F(AssertPass, PtrGt3) {
-  PW_CHECK_PTR_GT(reinterpret_cast<void*>(0xb), reinterpret_cast<void*>(0xa));
-}
+TEST_F(AssertFail, PtrGt1) { PW_CHECK_PTR_GT(0xa, 0xb); }
+TEST_F(AssertFail, PtrGt2) { PW_CHECK_PTR_GT(0xb, 0xb); }
+TEST_F(AssertPass, PtrGt3) { PW_CHECK_PTR_GT(0xb, 0xa); }
 
 // PTR >=
-TEST_F(AssertFail, PtrGe1) {
-  PW_CHECK_PTR_GE(reinterpret_cast<void*>(0xa), reinterpret_cast<void*>(0xb));
-}
-TEST_F(AssertPass, PtrGe2) {
-  PW_CHECK_PTR_GE(reinterpret_cast<void*>(0xb), reinterpret_cast<void*>(0xb));
-}
-TEST_F(AssertPass, PtrGe3) {
-  PW_CHECK_PTR_GE(reinterpret_cast<void*>(0xb), reinterpret_cast<void*>(0xa));
-}
+TEST_F(AssertFail, PtrGe1) { PW_CHECK_PTR_GE(0xa, 0xb); }
+TEST_F(AssertPass, PtrGe2) { PW_CHECK_PTR_GE(0xb, 0xb); }
+TEST_F(AssertPass, PtrGe3) { PW_CHECK_PTR_GE(0xb, 0xa); }
 
 // NOTNULL
-TEST_F(AssertPass, PtrNotNull) {
-  PW_CHECK_NOTNULL(reinterpret_cast<void*>(0xa));
-}
-TEST_F(AssertFail, PtrNotNull) {
-  PW_CHECK_NOTNULL(reinterpret_cast<void*>(0x0));
-}
-
-[[maybe_unused]] void Function1() {}
-[[maybe_unused]] bool Function2(int) { return false; }
-
-// NOTNULL for function poionters
-TEST_F(AssertPass, FunctionPtrNotNull) {
-  PW_CHECK_NOTNULL(&Function1);
-  PW_CHECK_NOTNULL(&Function2);
-}
-TEST_F(AssertFail, FunctionPtrNotNull) {
-  void (*const function)() = nullptr;
-  PW_CHECK_NOTNULL(function);
-}
+TEST_F(AssertPass, PtrNotNull) { PW_CHECK_NOTNULL(0xa); }
+TEST_F(AssertFail, PtrNotNull) { PW_CHECK_NOTNULL(0x0); }
 
 // Note: Due to platform inconsistencies, the below test for the NOTNULL
 // message doesn't work. Some platforms print NULL formatted as %p as "(nil)",
@@ -398,107 +344,113 @@
 }
 
 // Verify that the CHECK_*(x,y) macros only evaluate their arguments once.
-struct MultiEvaluateTestContext {
-  int IncrementAndReturnZero() {
-    counter += 1;
-    return 0;
-  }
-  int counter = 0;
-};
+static int global_state_for_multi_evaluate_test;
+static int IncrementsGlobal() {
+  global_state_for_multi_evaluate_test++;
+  return 0;
+}
 
 TEST(AssertPass, CheckSingleSideEffectingCall) {
-  MultiEvaluateTestContext ctx;
-  PW_CHECK(ctx.IncrementAndReturnZero() == 0);
-  EXPECT_EQ(ctx.counter, 1);
+  global_state_for_multi_evaluate_test = 0;
+  PW_CHECK(IncrementsGlobal() == 0);
+  EXPECT_EQ(global_state_for_multi_evaluate_test, 1);
 }
 TEST(AssertFail, CheckSingleSideEffectingCall) {
-  MultiEvaluateTestContext ctx;
-  PW_CHECK(ctx.IncrementAndReturnZero() == 1);
-  EXPECT_EQ(ctx.counter, 1);
+  global_state_for_multi_evaluate_test = 0;
+  PW_CHECK(IncrementsGlobal() == 1);
+  EXPECT_EQ(global_state_for_multi_evaluate_test, 1);
 }
 TEST(AssertPass, BinaryOpSingleSideEffectingCall) {
-  MultiEvaluateTestContext ctx;
-  PW_CHECK_INT_EQ(0, ctx.IncrementAndReturnZero());
-  EXPECT_EQ(ctx.counter, 1);
+  global_state_for_multi_evaluate_test = 0;
+  PW_CHECK_INT_EQ(0, IncrementsGlobal());
+  EXPECT_EQ(global_state_for_multi_evaluate_test, 1);
 }
 TEST(AssertPass, BinaryOpTwoSideEffectingCalls) {
-  MultiEvaluateTestContext ctx;
-  PW_CHECK_INT_EQ(ctx.IncrementAndReturnZero(), ctx.IncrementAndReturnZero());
-  EXPECT_EQ(ctx.counter, 2);
+  global_state_for_multi_evaluate_test = 0;
+  PW_CHECK_INT_EQ(IncrementsGlobal(), IncrementsGlobal());
+  EXPECT_EQ(global_state_for_multi_evaluate_test, 2);
 }
 TEST(AssertFail, BinaryOpSingleSideEffectingCall) {
-  MultiEvaluateTestContext ctx;
-  PW_CHECK_INT_EQ(12314, ctx.IncrementAndReturnZero());
-  EXPECT_EQ(ctx.counter, 1);
+  global_state_for_multi_evaluate_test = 0;
+  PW_CHECK_INT_EQ(12314, IncrementsGlobal());
+  EXPECT_EQ(global_state_for_multi_evaluate_test, 1);
 }
 TEST(AssertFail, BinaryOpTwoSideEffectingCalls) {
-  MultiEvaluateTestContext ctx;
-  PW_CHECK_INT_EQ(ctx.IncrementAndReturnZero() + 10,
-                  ctx.IncrementAndReturnZero());
-  EXPECT_EQ(ctx.counter, 2);
-}
-TEST(AssertPass, CheckOkSingleSideEffectingCall) {
-  MultiEvaluateTestContext ctx;
-  PW_CHECK_OK(ctx.IncrementAndReturnZero() ? pw::OkStatus() : pw::OkStatus());
-  EXPECT_EQ(ctx.counter, 1);
-}
-TEST(AssertFail, CheckOkSingleSideEffectingCall) {
-  MultiEvaluateTestContext ctx;
-  PW_CHECK_OK(ctx.IncrementAndReturnZero() ? pw::Status::NotFound()
-                                           : pw::Status::NotFound());
-  EXPECT_EQ(ctx.counter, 1);
+  global_state_for_multi_evaluate_test = 0;
+  PW_CHECK_INT_EQ(IncrementsGlobal() + 10, IncrementsGlobal());
+  EXPECT_EQ(global_state_for_multi_evaluate_test, 2);
 }
 
 // Verify side effects of debug checks work as expected.
 // Only check a couple of cases, since the logic is all the same.
-
+#if PW_ASSERT_ENABLE_DEBUG
 // When DCHECKs are enabled, they behave the same as normal checks.
-// When DCHECKs are disabled, they should not trip, and their arguments
-// shouldn't be evaluated.
-constexpr int kExpectedSideEffects = PW_ASSERT_ENABLE_DEBUG;
-
 TEST(AssertPass, DCheckEnabledSingleSideEffectingCall) {
-  MultiEvaluateTestContext ctx;
-  PW_DCHECK(ctx.IncrementAndReturnZero() == 0);
-  EXPECT_EQ(ctx.counter, kExpectedSideEffects);
+  global_state_for_multi_evaluate_test = 0;
+  PW_DCHECK(IncrementsGlobal() == 0);
+  EXPECT_EQ(global_state_for_multi_evaluate_test, 1);
 }
 TEST(AssertFail, DCheckEnabledSingleSideEffectingCall) {
-  MultiEvaluateTestContext ctx;
-  PW_DCHECK(ctx.IncrementAndReturnZero() == 1);
-  EXPECT_EQ(ctx.counter, kExpectedSideEffects);
+  global_state_for_multi_evaluate_test = 0;
+  PW_DCHECK(IncrementsGlobal() == 1);
+  EXPECT_EQ(global_state_for_multi_evaluate_test, 1);
 }
 TEST(AssertPass, DCheckEnabledBinaryOpSingleSideEffectingCall) {
-  MultiEvaluateTestContext ctx;
-  PW_DCHECK_INT_EQ(0, ctx.IncrementAndReturnZero());
-  EXPECT_EQ(ctx.counter, kExpectedSideEffects);
+  global_state_for_multi_evaluate_test = 0;
+  PW_DCHECK_INT_EQ(0, IncrementsGlobal());
+  EXPECT_EQ(global_state_for_multi_evaluate_test, 1);
 }
 TEST(AssertPass, DCheckEnabledBinaryOpTwoSideEffectingCalls) {
-  MultiEvaluateTestContext ctx;
-  PW_DCHECK_INT_EQ(ctx.IncrementAndReturnZero(), ctx.IncrementAndReturnZero());
-  EXPECT_EQ(ctx.counter, 2 * kExpectedSideEffects);
+  global_state_for_multi_evaluate_test = 0;
+  PW_DCHECK_INT_EQ(IncrementsGlobal(), IncrementsGlobal());
+  EXPECT_EQ(global_state_for_multi_evaluate_test, 2);
 }
 TEST(AssertFail, DCheckEnabledBinaryOpSingleSideEffectingCall) {
-  MultiEvaluateTestContext ctx;
-  PW_DCHECK_INT_EQ(12314, ctx.IncrementAndReturnZero());
-  EXPECT_EQ(ctx.counter, kExpectedSideEffects);
+  global_state_for_multi_evaluate_test = 0;
+  PW_DCHECK_INT_EQ(12314, IncrementsGlobal());
+  EXPECT_EQ(global_state_for_multi_evaluate_test, 1);
 }
 TEST(AssertFail, DCheckEnabledBinaryOpTwoSideEffectingCalls) {
-  MultiEvaluateTestContext ctx;
-  PW_DCHECK_INT_EQ(ctx.IncrementAndReturnZero() + 10,
-                   ctx.IncrementAndReturnZero());
-  EXPECT_EQ(ctx.counter, 2 * kExpectedSideEffects);
+  global_state_for_multi_evaluate_test = 0;
+  PW_DCHECK_INT_EQ(IncrementsGlobal() + 10, IncrementsGlobal());
+  EXPECT_EQ(global_state_for_multi_evaluate_test, 2);
 }
-TEST(AssertPass, DCheckOkSingleSideEffectingCall) {
-  MultiEvaluateTestContext ctx;
-  PW_DCHECK_OK(ctx.IncrementAndReturnZero() ? pw::OkStatus() : pw::OkStatus());
-  EXPECT_EQ(ctx.counter, kExpectedSideEffects);
+
+#else  // PW_ASSERT_ENABLE_DEBUG
+
+// When DCHECKs are disabled, they should not trip, and their arguments
+// shouldn't be evaluated.
+TEST(AssertPass, DCheckDisabledSingleSideEffectingCall_1) {
+  global_state_for_multi_evaluate_test = 0;
+  PW_DCHECK(IncrementsGlobal() == 0);
+  EXPECT_EQ(global_state_for_multi_evaluate_test, 0);
 }
-TEST(AssertFail, DCheckOkSingleSideEffectingCall) {
-  MultiEvaluateTestContext ctx;
-  PW_DCHECK_OK(ctx.IncrementAndReturnZero() ? pw::Status::NotFound()
-                                            : pw::Status::NotFound());
-  EXPECT_EQ(ctx.counter, kExpectedSideEffects);
+TEST(AssertPass, DCheckDisabledSingleSideEffectingCall_2) {
+  global_state_for_multi_evaluate_test = 0;
+  PW_DCHECK(IncrementsGlobal() == 1);
+  EXPECT_EQ(global_state_for_multi_evaluate_test, 0);
 }
+TEST(AssertPass, DCheckDisabledBinaryOpSingleSideEffectingCall_1) {
+  global_state_for_multi_evaluate_test = 0;
+  PW_DCHECK_INT_EQ(0, IncrementsGlobal());
+  EXPECT_EQ(global_state_for_multi_evaluate_test, 0);
+}
+TEST(AssertPass, DCheckDisabledBinaryOpTwoSideEffectingCalls_1) {
+  global_state_for_multi_evaluate_test = 0;
+  PW_DCHECK_INT_EQ(IncrementsGlobal(), IncrementsGlobal());
+  EXPECT_EQ(global_state_for_multi_evaluate_test, 0);
+}
+TEST(AssertPass, DCheckDisabledBinaryOpSingleSideEffectingCall_2) {
+  global_state_for_multi_evaluate_test = 0;
+  PW_DCHECK_INT_EQ(12314, IncrementsGlobal());
+  EXPECT_EQ(global_state_for_multi_evaluate_test, 0);
+}
+TEST(AssertPass, DCheckDisabledBinaryOpTwoSideEffectingCalls_2) {
+  global_state_for_multi_evaluate_test = 0;
+  PW_DCHECK_INT_EQ(IncrementsGlobal() + 10, IncrementsGlobal());
+  EXPECT_EQ(global_state_for_multi_evaluate_test, 0);
+}
+#endif  // PW_ASSERT_ENABLE_DEBUG
 
 // Verify PW_CHECK_OK, including message handling.
 TEST_F(AssertFail, StatusNotOK) {
@@ -543,7 +495,7 @@
 TEST_F(AssertPass, DCheckEnum) { PW_DCHECK_OK(PW_STATUS_OK); }
 TEST_F(AssertFail, DCheckFunction) { PW_DCHECK_OK(pw::Status::Unknown()); }
 TEST_F(AssertFail, DCheckEnum) { PW_DCHECK_OK(PW_STATUS_UNKNOWN); }
-#else   // PW_ASSERT_ENABLE_DEBUG
+#else  // PW_ASSERT_ENABLE_DEBUG
 
 // In release mode, all the asserts should pass.
 TEST_F(AssertPass, DCheckFunction_Ok) { PW_DCHECK_OK(pw::OkStatus()); }
diff --git a/pw_assert/assert_lite_public_overrides/pw_assert_backend/assert_lite_backend.h b/pw_assert/assert_lite_public_overrides/pw_assert_backend/assert_lite_backend.h
deleted file mode 100644
index 367c39d..0000000
--- a/pw_assert/assert_lite_public_overrides/pw_assert_backend/assert_lite_backend.h
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_preprocessor/compiler.h"
-#include "pw_preprocessor/util.h"
-
-PW_EXTERN_C_START
-
-PW_NO_RETURN void pw_assert_HandleFailure(void);
-
-PW_EXTERN_C_END
-
-#define PW_ASSERT_HANDLE_FAILURE(condition_string) pw_assert_HandleFailure()
diff --git a/pw_assert/assert_test.cc b/pw_assert/assert_test.cc
deleted file mode 100644
index ecbb0c1..0000000
--- a/pw_assert/assert_test.cc
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_assert/assert.h"
-
-#include "gtest/gtest.h"
-
-// PW_ASSERT() should always be enabled, and always evaluate the expression.
-TEST(Assert, AssertTrue) {
-  int evaluated = 1;
-  PW_ASSERT(++evaluated);
-  EXPECT_EQ(evaluated, 2);
-}
-
-// PW_DASSERT() might be disabled sometimes.
-TEST(Assert, DebugAssertTrue) {
-  int evaluated = 1;
-  PW_DASSERT(++evaluated);
-  if (PW_ASSERT_ENABLE_DEBUG == 1) {
-    EXPECT_EQ(evaluated, 2);
-  } else {
-    EXPECT_EQ(evaluated, 1);
-  }
-}
-
-// Unfortunately, we don't have the infrastructure to test failure handling
-// automatically, since the harness crashes in the process of running this
-// test. The unsatisfying alternative is to test the functionality manually,
-// then disable the test.
-
-TEST(Assert, AssertFalse) {
-  if (false) {
-    PW_ASSERT(false);
-  }
-}
-
-TEST(Assert, DebugAssertFalse) {
-  if (false) {
-    PW_DASSERT(false);
-  }
-}
diff --git a/pw_assert/backend.gni b/pw_assert/backend.gni
deleted file mode 100644
index c4876cf..0000000
--- a/pw_assert/backend.gni
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-declare_args() {
-  # Backend for the pw_assert module's CHECK facade.
-  pw_assert_BACKEND = ""
-
-  # Backend for the pw_assert module's ASSERT facade.
-  #
-  # Warning: This naming is transitional. Modifying this build argument WILL
-  #     result in future breakages. (pwbug/246)
-  pw_assert_LITE_BACKEND = "${dir_pw_assert}:lite_compatibility_backend"
-}
diff --git a/pw_assert/docs.rst b/pw_assert/docs.rst
index 03c4fda..0cd72cf 100644
--- a/pw_assert/docs.rst
+++ b/pw_assert/docs.rst
@@ -12,7 +12,7 @@
 defensive programming that can lead to more reliable and less buggy code.
 
 The assert API facilitates flexible crash handling through Pigweed's facade
-mechanism. The API is designed to enable features like:
+mechanism. The API is desigend to enable features like:
 
 - Optional ancillary printf-style messages along assertions
 - Capturing actual values of binary operator assertions like ``a < b``
@@ -37,11 +37,8 @@
     PW_CHECK_INT_LE(ItemCount(), 100);
     PW_CHECK_INT_LE(ItemCount(), 100, "System state: %s", GetStateStr());
 
-  To ensure compatibility with :ref:`module-pw_assert_log` and
-  :ref:`module-pw_log_tokenized`, the message must be a string literal.
-
 Example
-=======
+-------
 
 .. code-block:: cpp
 
@@ -69,7 +66,7 @@
     // This assert is always enabled, even in production.
     PW_CHECK_INT_LE(ItemCount(), 100);
 
-    // This assert is enabled based on ``PW_ASSERT_ENABLE_DEBUG``.
+    // This assert disabled for release builds, where NDEBUG is defined.
     // The functions ItemCount() and GetStateStr() are never called.
     PW_DCHECK_INT_LE(ItemCount(), 100, "System state: %s", GetStateStr());
 
@@ -78,8 +75,8 @@
   Use ``PW_ASSERT`` from ``pw_assert/assert.h`` for asserts in headers or
   asserting in ``constexpr`` contexts.
 
-Structure of Assert Modules
-===========================
+Structure of assert modules
+---------------------------
 The module is split into two components:
 
 1. The **facade** (this module) which is only a macro interface layer, and
@@ -91,10 +88,14 @@
    backend. This is also where application or product specific crash handling
    would go.
 
-.. mermaid::
+.. blockdiag::
 
-  graph LR
-    facade --> backend
+  blockdiag {
+    default_fontsize = 16;
+    facade  [label = "facade"];
+    backend [label = "backend"];
+    facade -> backend
+  }
 
 See the Backend API section below for more details.
 
@@ -127,8 +128,8 @@
   Assert that a condition is true, optionally including a message with
   arguments to report if the codition is false.
 
-  The ``DCHECK`` variants only run if ``PW_ASSERT_ENABLE_DEBUG`` is enabled;
-  otherwise, the entire statement is removed (and the expression not evaluated).
+  The ``DCHECK`` variants only run if ``NDEBUG`` is defined; otherwise, the
+  entire statement is removed (and the expression not evaluated).
 
   Example:
 
@@ -172,8 +173,8 @@
   Assert that the given pointer is not ``NULL``, optionally including a message
   with arguments to report if the pointer is ``NULL``.
 
-  The ``DCHECK`` variants only run if ``PW_ASSERT_ENABLE_DEBUG`` is enabled;
-  otherwise, the entire statement is removed (and the expression not evaluated).
+  The ``DCHECK`` variants only run if ``NDEBUG`` is defined; otherwise, the
+  entire statement is removed (and the expression not evaluated).
 
   .. code-block:: cpp
 
@@ -194,8 +195,8 @@
   If present, the optional format message is reported on failure. Depending on
   the backend, values of ``a`` and ``b`` will also be reported.
 
-  The ``DCHECK`` variants only run if ``PW_ASSERT_ENABLE_DEBUG`` is enabled;
-  otherwise, the entire statement is removed (and the expression not evaluated).
+  The ``DCHECK`` variants only run if ``NDEBUG`` is defined; otherwise, the
+  entire statement is removed (and the expression not evaluated).
 
   Example, with no message:
 
@@ -268,8 +269,7 @@
   +-------------------------+--------------+-----------+-----------------------+
 
   The above ``CHECK_*_*()`` are also available in DCHECK variants, which will
-  only evaluate their arguments and trigger if the ``PW_ASSERT_ENABLE_DEBUG``
-  macro is enabled.
+  only evaluate their arguments and trigger if the ``NDEBUG`` macro is defined.
 
   +--------------------------+--------------+-----------+----------------------+
   | Macro                    | a, b type    | condition | a, b format          |
@@ -344,8 +344,8 @@
   .. note::
     This also asserts that ``abs_tolerance >= 0``.
 
-  The ``DCHECK`` variants only run if ``PW_ASSERT_ENABLE_DEBUG`` is enabled;
-  otherwise, the entire statement is removed (and the expression not evaluated).
+  The ``DCHECK`` variants only run if ``NDEBUG`` is defined; otherwise, the
+  entire statement is removed (and the expression not evaluated).
 
   Example, with no message:
 
@@ -369,8 +369,8 @@
   ``PW_STATUS_OK`` (in C). Optionally include a message with arguments to
   report.
 
-  The ``DCHECK`` variants only run if ``PW_ASSERT_ENABLE_DEBUG`` is defined;
-  otherwise, the entire statement is removed (and the expression not evaluated).
+  The ``DCHECK`` variants only run if ``NDEBUG`` is defined; otherwise, the
+  entire statement is removed (and the expression not evaluated).
 
   .. code-block:: cpp
 
@@ -391,8 +391,6 @@
     code; for example ``status == RESOURCE_EXHAUSTED`` instead of ``status ==
     5``.
 
-.. _module-pw_assert-assert-api:
-
 ----------
 Assert API
 ----------
@@ -419,7 +417,7 @@
 expression, or anything other than a binary indication of failure**.
 
 Example
-=======
+-------
 
 .. code-block:: cpp
 
@@ -441,8 +439,8 @@
     }
   };
 
-PW_ASSERT API Reference
-=======================
+PW_ASSERT API reference
+-----------------------
 .. cpp:function:: PW_ASSERT(condition)
 
   A header- and constexpr-safe version of ``PW_CHECK()``.
@@ -455,7 +453,7 @@
 
   A header- and constexpr-safe version of ``PW_DCHECK()``.
 
-  Same as ``PW_ASSERT()``, except that if ``PW_ASSERT_ENABLE_DEBUG == 0``, the
+  Same as ``PW_ASSERT()``, except that if ``PW_ASSERT_ENABLE_DEBUG == 1``, the
   assert is disabled and condition is not evaluated.
 
 .. attention::
@@ -468,42 +466,16 @@
 
   Use ``PW_CHECK_*()`` whenever possible.
 
-PW_ASSERT API Backend
-=====================
+PW_ASSERT API backend
+---------------------
 The ``PW_ASSERT`` API ultimately calls the C function
 ``pw_assert_HandleFailure()``, which must be provided by the ``pw_assert``
-backend. The ``pw_assert_HandleFailure()`` function must not return.
-
-.. _module-pw_assert-circular-deps:
-
-Avoiding Circular Dependencies With ``PW_ASSERT``
-=================================================
-Because asserts are so widely used, including in low-level libraries, it is
-common for the ``pw_assert`` backend to cause circular dependencies. Because of
-this, assert backends may avoid declaring explicit dependencies, instead relying
-on include paths to access header files.
-
-In GN, the ``pw_assert`` backend's full implementation with true dependencies is
-made available through the ``$dir_pw_assert:impl`` group. When
-``pw_assert_BACKEND`` is set, ``$dir_pw_assert:impl`` must be listed in the
-``pw_build_LINK_DEPS`` variable. See :ref:`module-pw_build-link-deps`.
-
-In the ``pw_assert``, the backend's full implementation is placed in the
-``$pw_assert_BACKEND.impl`` target. ``$dir_pw_assert:impl`` depends on this
-backend target. The ``$pw_assert_BACKEND.impl`` target may be an empty group if
-the backend target can use its dependencies directly without causing circular
-dependencies.
-
-In order to break dependency cycles, the ``pw_assert_BACKEND`` target may need
-to directly provide dependencies through include paths only, rather than GN
-``public_deps``. In this case, GN header checking can be disabled with
-``check_includes = false``.
-
-.. _module-pw_assert-backend_api:
+backend.
 
 -----------
 Backend API
 -----------
+
 The backend controls what to do in the case of an assertion failure. In the
 most basic cases, the backend could display the assertion failure on something
 like sys_io and halt in a while loop waiting for a debugger. In other cases,
@@ -515,8 +487,8 @@
 .. attention::
 
   The facade macros (``PW_CRASH`` and related) are expected to behave like they
-  have the ``[[noreturn]]`` attribute set. This implies that the backend handler
-  functions, ``PW_HANDLE_*`` defined by the backend, must not return.
+  have the ``[[ noreturn ]]`` attribute set. This implies that the backend
+  handler functions, ``PW_HANDLE_*`` defined by the backend, must not return.
 
   In other words, the device must reboot.
 
@@ -585,52 +557,12 @@
   file, expression, or other rich assert information. Backends should do
   something reasonable in this case; typically, capturing the stack is useful.
 
-Backend Build Targets
-=====================
-In GN, the backend must provide a ``pw_assert.impl`` build target in the same
-directory as the backend target. If the main backend target's dependencies would
-cause dependency cycles, the actual backend implementation with its full
-dependencies is placed in the ``pw_assert.impl`` target. If this is not
-necessary, ``pw_assert.impl`` can be an empty group. Circular dependencies are a
-common problem with ``pw_assert`` because it is so widely used. See
-:ref:`module-pw_assert-circular-deps`.
-
-Macro-based PW_ASSERT()/PW_DASSERT() backend
-============================================
-The pw_assert API is being re-assessed to provide more helpful information in
-contexts where ``PW_CHECK_*()`` macros cannot be used. A first step towards this
-is providing a macro-based backend API for the ``PW_ASSERT()`` and
-``PW_DASSERT()`` macros.
-
-.. warning::
-  This part of ``pw_assert``'s API is transitional, and any project-specific
-  reliance on any of the API mentioned here will likely experience breakages.
-  In particular, ``PW_ASSERT_HANDLE_FAILURE`` and ``PW_HANDLE_ASSERT_FAILURE``
-  are extremely confusingly similar and are NOT interchangeable.
-
-A macro-based backend for the ``PW_ASSERT()`` macros must provide the following
-macro in a header at ``pw_assert_backend/assert_lite_backend.h``.
-
-.. cpp:function:: PW_ASSERT_HANDLE_FAILURE(expression)
-
-  Handle a low-level crash. This crash entry happens through
-  ``pw_assert/assert.h``. Backends must ensure their implementation is safe for
-  usage in headers, constexpr contexts, and templates. This macro should expand
-  to an expression that does not return.
-
-Similar to the ``PW_CHECK_*()`` facade, the header backend that provides an
-expansion for the ``PW_ASSERT_HANDLE_FAILURE()`` macro can be controlled in the
-GN build using the ``pw_assert_LITE_BACKEND`` build argument. In addition to
-the header-based target at ``${pw_assert_LITE_BACKEND}``, a source set at
-``${pw_assert_LITE_BACKEND}.impl`` is also required as a way to reduce the
-impact of :ref:`circular dependencies <module-pw_assert-circular-deps>`.
-
 --------------------------
-Frequently Asked Questions
+Frequently asked questions
 --------------------------
 
 When should DCHECK_* be used instead of CHECK_* and vice versa?
-===============================================================
+---------------------------------------------------------------
 There is no hard and fast rule for when to use one or the other.
 
 In theory, ``DCHECK_*`` macros should never be used and all the asserts should
@@ -679,13 +611,13 @@
   mistake; so use error codes in those cases instead.
 
 How should objects be asserted against or compared?
-===================================================
-Unfortunately, there is no native mechanism for this, and instead the way to
+---------------------------------------------------
+Unfortunatly, there is no native mechanism for this, and instead the way to
 assert object states or comparisons is with the normal ``PW_CHECK_*`` macros
 that operate on booleans, ints, and floats.
 
 This is due to the requirement of supporting C and also tokenization. It may be
-possible support rich object comparisons by defining a convention for
+possible support rich object comparions by defining a convention for
 stringifying objects; however, this hasn't been added yet. Additionally, such a
 mechanism would not work well with tokenization. In particular, it would
 require runtime stringifying arguments and rendering them with ``%s``, which
@@ -693,7 +625,7 @@
 object assert API won't be added.
 
 Why was the assert facade designed this way?
-============================================
+--------------------------------------------
 The Pigweed assert API was designed taking into account the needs of several
 past projects the team members were involved with. Based on those experiences,
 the following were key requirements for the API:
@@ -726,7 +658,7 @@
 asserting in that case, rather than terminating at a C-style API.
 
 Why isn't there a ``PW_CHECK_LE``? Why is the type (e.g. ``INT``) needed?
-=========================================================================
+-------------------------------------------------------------------------
 The problem with asserts like ``PW_CHECK_LE(a, b)`` instead of
 ``PW_CHECK_INT_LE(a, b)`` or ``PW_CHECK_FLOAT_EXACT_LE(a, b)`` is that to
 capture the arguments with the tokenizer, we need to know the types. Using the
@@ -734,29 +666,6 @@
 ``b``, so unfortunately having a separate macro for each of the types commonly
 asserted on is necessary.
 
-----------------------------
-Module Configuration Options
-----------------------------
-The following configurations can be adjusted via compile-time configuration of
-this module, see the
-:ref:`module documentation <module-structure-compile-time-configuration>` for
-more details.
-
-.. c:macro:: PW_ASSERT_ENABLE_DEBUG
-
-  Controls whether ``DCHECK`` and ``DASSERT`` are enabled.
-
-  This defaults to being disabled if ``NDEBUG`` is defined, else it is enabled
-  by default.
-
-.. c:macro:: PW_ASSERT_CAPTURE_VALUES
-
-  Controls whether the evaluated values of a CHECK statement are captured as
-  arguments to the final string. Disabling this will reduce code size at CHECK
-  callsites, but slightly reduces debugability.
-
-  This defaults to enabled.
-
 -------------
 Compatibility
 -------------
@@ -773,8 +682,8 @@
 
 Below is a brief summary of what modules are ready for use:
 
-Available Assert Backends
-=========================
+Available assert backends
+-------------------------
 - ``pw_assert`` - **Stable** - The assert facade (this module). This module is
   stable, and in production use. The documentation is comprehensive and covers
   the functionality. There are (a) tests for the facade macro processing logic,
@@ -794,8 +703,8 @@
 ``pw_assert_log`` in combination with ``pw_log_null``. This will direct asserts
 to logs, then the logs are removed due to the null backend.
 
-Missing Functionality
-=====================
+Missing functionality
+---------------------
 - **Stack traces** - Pigweed doesn't have a reliable stack walker, which makes
   displaying a stack trace on crash harder. We plan to add this eventually.
 - **Snapshot integration** - Pigweed doesn't yet have a rich system state
diff --git a/pw_assert/light_test.cc b/pw_assert/light_test.cc
new file mode 100644
index 0000000..1710e4f
--- /dev/null
+++ b/pw_assert/light_test.cc
@@ -0,0 +1,53 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_assert/light.h"
+
+#include "gtest/gtest.h"
+#include "pw_assert/assert.h"
+
+// PW_ASSERT() should always be enabled, and always evaluate the expression.
+TEST(Light, AssertTrue) {
+  int evaluated = 1;
+  PW_ASSERT(++evaluated);
+  EXPECT_EQ(evaluated, 2);
+}
+
+// PW_DASSERT() might be disabled sometimes.
+TEST(Light, DebugAssertTrue) {
+  int evaluated = 1;
+  PW_DASSERT(++evaluated);
+  if (PW_ASSERT_ENABLE_DEBUG == 1) {
+    EXPECT_EQ(evaluated, 2);
+  } else {
+    EXPECT_EQ(evaluated, 1);
+  }
+}
+
+// Unfortunately, we don't have the infrastructure to test failure handling
+// automatically, since the harness crashes in the process of running this
+// test. The unsatisfying alternative is to test the functionality manually,
+// then disable the test.
+
+TEST(Light, AssertFalse) {
+  if (0) {
+    PW_ASSERT(false);
+  }
+}
+
+TEST(Light, DebugAssertFalse) {
+  if (0) {
+    PW_DASSERT(false);
+  }
+}
diff --git a/pw_assert/print_and_abort_public_overrides/pw_assert_backend/assert_lite_backend.h b/pw_assert/print_and_abort_public_overrides/pw_assert_backend/assert_lite_backend.h
deleted file mode 100644
index 02b81df..0000000
--- a/pw_assert/print_and_abort_public_overrides/pw_assert_backend/assert_lite_backend.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_assert/internal/print_and_abort.h"
diff --git a/pw_assert/public/pw_assert/assert.h b/pw_assert/public/pw_assert/assert.h
index ec4995f..a6756a6 100644
--- a/pw_assert/public/pw_assert/assert.h
+++ b/pw_assert/public/pw_assert/assert.h
@@ -13,8 +13,18 @@
 // the License.
 #pragma once
 
-#include "pw_assert/config.h"  // For PW_ASSERT_ENABLE_DEBUG
-#include "pw_assert_backend/assert_lite_backend.h"
+#include "pw_assert/options.h"  // For PW_ASSERT_ENABLE_DEBUG
+#include "pw_preprocessor/util.h"
+
+// For backwards compatibility, include check.h from assert.h.
+// TODO(pwbug/350): Remove this include when projects have migrated.
+#include "pw_assert/check.h"
+
+PW_EXTERN_C_START
+
+void pw_assert_HandleFailure(void);
+
+PW_EXTERN_C_END
 
 // A header- and constexpr-safe version of PW_CHECK().
 //
@@ -27,11 +37,11 @@
 // stringified expression. Use these macros only when absolutely necessary --
 // in headers, constexr contexts, or in rare cases where the call site overhead
 // of a full PW_CHECK must be avoided. Use PW_CHECK_*() whenever possible.
-#define PW_ASSERT(condition)                \
-  do {                                      \
-    if (!(condition)) {                     \
-      PW_ASSERT_HANDLE_FAILURE(#condition); \
-    }                                       \
+#define PW_ASSERT(condition)     \
+  do {                           \
+    if (!(condition)) {          \
+      pw_assert_HandleFailure(); \
+    }                            \
   } while (0)
 
 // A header- and constexpr-safe version of PW_DCHECK().
@@ -47,6 +57,6 @@
 #define PW_DASSERT(condition)                            \
   do {                                                   \
     if ((PW_ASSERT_ENABLE_DEBUG == 1) && !(condition)) { \
-      PW_ASSERT_HANDLE_FAILURE(#condition);              \
+      pw_assert_HandleFailure();                         \
     }                                                    \
   } while (0)
diff --git a/pw_assert/public/pw_assert/check.h b/pw_assert/public/pw_assert/check.h
index 31ffca7..1cb4eb7 100644
--- a/pw_assert/public/pw_assert/check.h
+++ b/pw_assert/public/pw_assert/check.h
@@ -97,6 +97,13 @@
 // build facilities.
 #include "pw_assert/internal/check_impl.h"
 
+// For compatibility, include short.h if PW_ASSERT_USE_SHORT_NAMES is set.
+// TODO(pwbug/350): Remove this include and the PW_ASSERT_USE_SHORT_NAMES macro
+//     when projects have migrated to including the short.h header.
+#if defined(PW_ASSERT_USE_SHORT_NAMES) && PW_ASSERT_USE_SHORT_NAMES == 1
+#include "pw_assert/short.h"
+#endif  // defined(PW_ASSERT_USE_SHORT_NAMES) && PW_ASSERT_USE_SHORT_NAMES == 1
+
 // The pw_assert_backend must provide these macros:
 //
 //   PW_HANDLE_CRASH(msg, ...)
diff --git a/pw_assert/public/pw_assert/config.h b/pw_assert/public/pw_assert/config.h
deleted file mode 100644
index 8bcb0ee..0000000
--- a/pw_assert/public/pw_assert/config.h
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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
-
-// PW_ASSERT_ENABLE_DEBUG controls whether DCHECKs and DASSERTs are enabled.
-//
-// This block defines PW_ASSERT_ENABLE_DEBUG if it is not already, taking into
-// account traditional NDEBUG macro.
-#if !defined(PW_ASSERT_ENABLE_DEBUG)
-#if defined(NDEBUG)
-// Release mode; remove all DCHECK*() and DASSERT() asserts.
-#define PW_ASSERT_ENABLE_DEBUG 0
-#else
-// Debug mode; keep all DCHECK*() and DASSERT() asserts.
-#define PW_ASSERT_ENABLE_DEBUG 1
-#endif  // defined (NDEBUG)
-#endif  // !defined(PW_ASSERT_ENABLE_DEBUG)
-
-// PW_ASSERT_CAPTURE_VALUES controls whether the evaluated values of a CHECK are
-// captured in the final string. Disabling this will reduce codesize at CHECK
-// callsites.
-#if !defined(PW_ASSERT_CAPTURE_VALUES)
-#define PW_ASSERT_CAPTURE_VALUES 1
-#endif  // !defined(PW_ASSERT_CAPTURE_VALUES)
diff --git a/pw_assert/public/pw_assert/internal/check_impl.h b/pw_assert/public/pw_assert/internal/check_impl.h
index bb7c7f3..584dd0f 100644
--- a/pw_assert/public/pw_assert/internal/check_impl.h
+++ b/pw_assert/public/pw_assert/internal/check_impl.h
@@ -13,26 +13,26 @@
 // the License.
 #pragma once
 
-#ifdef __cplusplus
-#include <type_traits>
-#else
+#ifndef __cplusplus
 #include <stddef.h>
 #endif  // __cplusplus
 
 // Note: This file depends on the backend header already being included.
 
-#include "pw_assert/config.h"
+#include "pw_assert/options.h"
+#include "pw_preprocessor/arguments.h"
 #include "pw_preprocessor/compiler.h"
 
 // PW_CRASH - Crash the system, with a message.
 #define PW_CRASH PW_HANDLE_CRASH
 
 // PW_CHECK - If condition evaluates to false, crash. Message optional.
-#define PW_CHECK(condition, ...)                            \
-  do {                                                      \
-    if (!(condition)) {                                     \
-      PW_HANDLE_ASSERT_FAILURE(#condition, "" __VA_ARGS__); \
-    }                                                       \
+#define PW_CHECK(condition, ...)                              \
+  do {                                                        \
+    if (!(condition)) {                                       \
+      _PW_CHECK_SELECT_MACRO(                                 \
+          #condition, PW_HAS_ARGS(__VA_ARGS__), __VA_ARGS__); \
+    }                                                         \
   } while (0)
 
 #define PW_DCHECK(...)            \
@@ -132,61 +132,114 @@
 
 // clang-format on
 
-// PW_CHECK_OK - If condition does not evaluate to PW_STATUS_OK, crash. Message
-// optional.
-#define PW_CHECK_OK(expression, ...)                                     \
-  do {                                                                   \
-    const _PW_CHECK_OK_STATUS _pw_assert_check_ok_status = (expression); \
-    if (_pw_assert_check_ok_status != PW_STATUS_OK) {                    \
-      _PW_CHECK_BINARY_ARG_HANDLER(                                      \
-          #expression,                                                   \
-          pw_StatusString(_pw_assert_check_ok_status),                   \
-          "==",                                                          \
-          "OkStatus()",                                                  \
-          "OK",                                                          \
-          "%s",                                                          \
-          "" __VA_ARGS__);                                               \
-    }                                                                    \
+// PW_CHECK - If condition evaluates to false, crash. Message optional.
+#define PW_CHECK_OK(status, ...)                          \
+  do {                                                    \
+    if (status != PW_STATUS_OK) {                         \
+      _PW_CHECK_OK_SELECT_MACRO(#status,                  \
+                                pw_StatusString(status),  \
+                                PW_HAS_ARGS(__VA_ARGS__), \
+                                __VA_ARGS__);             \
+    }                                                     \
   } while (0)
 
-#ifdef __cplusplus
-#define _PW_CHECK_OK_STATUS ::pw::Status
-#else
-#define _PW_CHECK_OK_STATUS pw_Status
-#endif  // __cplusplus
-
 #define PW_DCHECK_OK(...)          \
   if (!(PW_ASSERT_ENABLE_DEBUG)) { \
   } else                           \
     PW_CHECK_OK(__VA_ARGS__)
 
-// Use a static_cast in C++ to avoid accidental comparisons between e.g. an
-// integer and the CHECK message const char*.
-#if defined(__cplusplus) && __cplusplus >= 201703L
+// =========================================================================
+// Implementation for PW_CHECK
 
-namespace pw::assert::internal {
+// Two layers of select macros are used to enable the preprocessor to expand
+// macros in the arguments to ultimately token paste the final macro name based
+// on whether there are printf-style arguments.
+#define _PW_CHECK_SELECT_MACRO(condition, has_args, ...) \
+  _PW_CHECK_SELECT_MACRO_EXPANDED(condition, has_args, __VA_ARGS__)
 
-template <typename T, typename U>
-constexpr const void* ConvertToType(U* value) {
-  if constexpr (std::is_function<U>()) {
-    return reinterpret_cast<const void*>(value);
-  } else {
-    return static_cast<const void*>(value);
-  }
-}
+// Delegate to the macro
+#define _PW_CHECK_SELECT_MACRO_EXPANDED(condition, has_args, ...) \
+  _PW_CHECK_HAS_MSG_##has_args(condition, __VA_ARGS__)
 
-template <typename T, typename U>
-constexpr T ConvertToType(const U& value) {
-  return static_cast<T>(value);
-}
+// PW_CHECK version 1: No message or args
+#define _PW_CHECK_HAS_MSG_0(condition, ignored_arg) \
+  PW_HANDLE_ASSERT_FAILURE(condition, "")
 
-}  // namespace pw::assert::internal
+// PW_CHECK version 2: With message (and maybe args)
+#define _PW_CHECK_HAS_MSG_1(condition, ...) \
+  PW_HANDLE_ASSERT_FAILURE(condition, __VA_ARGS__)
 
-#define _PW_CHECK_CONVERT(type, name, arg) \
-  type name = ::pw::assert::internal::ConvertToType<type>(arg)
-#else
-#define _PW_CHECK_CONVERT(type, name, arg) type name = (type)(arg)
-#endif  // __cplusplus
+// =========================================================================
+// Implementation for PW_CHECK_<type>_<comparison>
+
+// Two layers of select macros are used to enable the preprocessor to expand
+// macros in the arguments to ultimately token paste the final macro name based
+// on whether there are printf-style arguments.
+#define _PW_CHECK_BINARY_COMPARISON_SELECT_MACRO(argument_a_str,       \
+                                                 argument_a_val,       \
+                                                 comparison_op_str,    \
+                                                 argument_b_str,       \
+                                                 argument_b_val,       \
+                                                 type_fmt,             \
+                                                 has_args,             \
+                                                 ...)                  \
+  _PW_CHECK_SELECT_BINARY_COMPARISON_MACRO_EXPANDED(argument_a_str,    \
+                                                    argument_a_val,    \
+                                                    comparison_op_str, \
+                                                    argument_b_str,    \
+                                                    argument_b_val,    \
+                                                    type_fmt,          \
+                                                    has_args,          \
+                                                    __VA_ARGS__)
+
+// Delegate to the macro
+#define _PW_CHECK_SELECT_BINARY_COMPARISON_MACRO_EXPANDED(argument_a_str,    \
+                                                          argument_a_val,    \
+                                                          comparison_op_str, \
+                                                          argument_b_str,    \
+                                                          argument_b_val,    \
+                                                          type_fmt,          \
+                                                          has_args,          \
+                                                          ...)               \
+  _PW_CHECK_BINARY_COMPARISON_HAS_MSG_##has_args(argument_a_str,             \
+                                                 argument_a_val,             \
+                                                 comparison_op_str,          \
+                                                 argument_b_str,             \
+                                                 argument_b_val,             \
+                                                 type_fmt,                   \
+                                                 __VA_ARGS__)
+
+// PW_CHECK_BINARY_COMPARISON version 1: No message or args
+#define _PW_CHECK_BINARY_COMPARISON_HAS_MSG_0(argument_a_str,    \
+                                              argument_a_val,    \
+                                              comparison_op_str, \
+                                              argument_b_str,    \
+                                              argument_b_val,    \
+                                              type_fmt,          \
+                                              ignored_arg)       \
+  PW_HANDLE_ASSERT_BINARY_COMPARE_FAILURE(argument_a_str,        \
+                                          argument_a_val,        \
+                                          comparison_op_str,     \
+                                          argument_b_str,        \
+                                          argument_b_val,        \
+                                          type_fmt,              \
+                                          "")
+
+// PW_CHECK_BINARY_COMPARISON version 2: With message (and maybe args)
+#define _PW_CHECK_BINARY_COMPARISON_HAS_MSG_1(argument_a_str,    \
+                                              argument_a_val,    \
+                                              comparison_op_str, \
+                                              argument_b_str,    \
+                                              argument_b_val,    \
+                                              type_fmt,          \
+                                              ...)               \
+  PW_HANDLE_ASSERT_BINARY_COMPARE_FAILURE(argument_a_str,        \
+                                          argument_a_val,        \
+                                          comparison_op_str,     \
+                                          argument_b_str,        \
+                                          argument_b_val,        \
+                                          type_fmt,              \
+                                          __VA_ARGS__)
 
 // For the binary assertions, this private macro is re-used for almost all of
 // the variants. Due to limitations of C formatting, it is necessary to have
@@ -194,79 +247,81 @@
 //
 // The macro avoids evaluating the arguments multiple times at the cost of some
 // macro complexity.
-#define _PW_CHECK_BINARY_CMP_IMPL(                                    \
-    arg_a, comparison_op, arg_b, type_decl, type_fmt, ...)            \
-  do {                                                                \
-    _PW_CHECK_CONVERT(type_decl, evaluated_argument_a, arg_a);        \
-    _PW_CHECK_CONVERT(type_decl, evaluated_argument_b, arg_b);        \
-    if (!(evaluated_argument_a comparison_op evaluated_argument_b)) { \
-      _PW_CHECK_BINARY_ARG_HANDLER(#arg_a,                            \
-                                   evaluated_argument_a,              \
-                                   #comparison_op,                    \
-                                   #arg_b,                            \
-                                   evaluated_argument_b,              \
-                                   type_fmt,                          \
-                                   "" __VA_ARGS__);                   \
-    }                                                                 \
+#define _PW_CHECK_BINARY_CMP_IMPL(                                       \
+    argument_a, comparison_op, argument_b, type_decl, type_fmt, ...)     \
+  do {                                                                   \
+    type_decl evaluated_argument_a = (type_decl)(argument_a);            \
+    type_decl evaluated_argument_b = (type_decl)(argument_b);            \
+    if (!(evaluated_argument_a comparison_op evaluated_argument_b)) {    \
+      _PW_CHECK_BINARY_COMPARISON_SELECT_MACRO(#argument_a,              \
+                                               evaluated_argument_a,     \
+                                               #comparison_op,           \
+                                               #argument_b,              \
+                                               evaluated_argument_b,     \
+                                               type_fmt,                 \
+                                               PW_HAS_ARGS(__VA_ARGS__), \
+                                               __VA_ARGS__);             \
+    }                                                                    \
   } while (0)
 
-// All binary comparison CHECK macros are directed to this handler before
-// hitting the CHECK backend. This controls whether evaluated values are
-// captured.
-#if PW_ASSERT_CAPTURE_VALUES
-#define _PW_CHECK_BINARY_ARG_HANDLER(arg_a_str,              \
-                                     arg_a_val,              \
-                                     comparison_op_str,      \
-                                     arg_b_str,              \
-                                     arg_b_val,              \
-                                     type_fmt,               \
-                                     message,                \
-                                     ...)                    \
-  PW_HANDLE_ASSERT_BINARY_COMPARE_FAILURE(arg_a_str,         \
-                                          arg_a_val,         \
-                                          comparison_op_str, \
-                                          arg_b_str,         \
-                                          arg_b_val,         \
-                                          type_fmt,          \
-                                          message,           \
-                                          __VA_ARGS__)
-#else
-#define _PW_CHECK_BINARY_ARG_HANDLER(arg_a_str,         \
-                                     arg_a_val,         \
-                                     comparison_op_str, \
-                                     arg_b_str,         \
-                                     arg_b_val,         \
-                                     type_fmt,          \
-                                     message,           \
-                                     ...)               \
-  PW_HANDLE_ASSERT_FAILURE(                             \
-      arg_a_str " " comparison_op_str " " arg_b_str, message, __VA_ARGS__)
-#endif  // PW_ASSERT_CAPTURE_VALUES
-
 // Custom implementation for FLOAT_NEAR which is implemented through two
 // underlying checks which are not trivially replaced through the use of
 // FLOAT_EXACT_LE & FLOAT_EXACT_GE.
-#define _PW_CHECK_FLOAT_NEAR(argument_a, argument_b, abs_tolerance, ...)  \
-  do {                                                                    \
-    PW_CHECK_FLOAT_EXACT_GE(abs_tolerance, 0.0f);                         \
-    float evaluated_argument_a = (float)(argument_a);                     \
-    float evaluated_argument_b_min = (float)(argument_b)-abs_tolerance;   \
-    float evaluated_argument_b_max = (float)(argument_b) + abs_tolerance; \
-    if (!(evaluated_argument_a >= evaluated_argument_b_min)) {            \
-      _PW_CHECK_BINARY_ARG_HANDLER(#argument_a,                           \
-                                   evaluated_argument_a,                  \
-                                   ">=",                                  \
-                                   #argument_b " - abs_tolerance",        \
-                                   evaluated_argument_b_min,              \
-                                   "%f",                                  \
-                                   "" __VA_ARGS__);                       \
-    } else if (!(evaluated_argument_a <= evaluated_argument_b_max)) {     \
-      _PW_CHECK_BINARY_ARG_HANDLER(#argument_a,                           \
-                                   evaluated_argument_a,                  \
-                                   "<=",                                  \
-                                   #argument_b " + abs_tolerance",        \
-                                   evaluated_argument_b_max,              \
-                                   "%f",                                  \
-                                   "" __VA_ARGS__);                       \
-    }                                                                     \
+#define _PW_CHECK_FLOAT_NEAR(argument_a, argument_b, abs_tolerance, ...)       \
+  do {                                                                         \
+    PW_CHECK_FLOAT_EXACT_GE(abs_tolerance, 0.0f);                              \
+    float evaluated_argument_a = (float)(argument_a);                          \
+    float evaluated_argument_b_min = (float)(argument_b)-abs_tolerance;        \
+    float evaluated_argument_b_max = (float)(argument_b) + abs_tolerance;      \
+    if (!(evaluated_argument_a >= evaluated_argument_b_min)) {                 \
+      _PW_CHECK_BINARY_COMPARISON_SELECT_MACRO(#argument_a,                    \
+                                               evaluated_argument_a,           \
+                                               ">=",                           \
+                                               #argument_b " - abs_tolerance", \
+                                               evaluated_argument_b_min,       \
+                                               "%f",                           \
+                                               PW_HAS_ARGS(__VA_ARGS__),       \
+                                               __VA_ARGS__);                   \
+    } else if (!(evaluated_argument_a <= evaluated_argument_b_max)) {          \
+      _PW_CHECK_BINARY_COMPARISON_SELECT_MACRO(#argument_a,                    \
+                                               evaluated_argument_a,           \
+                                               "<=",                           \
+                                               #argument_b " + abs_tolerance", \
+                                               evaluated_argument_b_max,       \
+                                               "%f",                           \
+                                               PW_HAS_ARGS(__VA_ARGS__),       \
+                                               __VA_ARGS__);                   \
+    }                                                                          \
   } while (0)
+
+// =========================================================================
+// Implementation for PW_CHECK_OK
+
+// Two layers of select macros are used to enable the preprocessor to expand
+// macros in the arguments to ultimately token paste the final macro name based
+// on whether there are printf-style arguments.
+#define _PW_CHECK_OK_SELECT_MACRO(                    \
+    status_expr_str, status_value_str, has_args, ...) \
+  _PW_CHECK_OK_SELECT_MACRO_EXPANDED(                 \
+      status_expr_str, status_value_str, has_args, __VA_ARGS__)
+
+// Delegate to the macro
+#define _PW_CHECK_OK_SELECT_MACRO_EXPANDED(           \
+    status_expr_str, status_value_str, has_args, ...) \
+  _PW_CHECK_OK_HAS_MSG_##has_args(                    \
+      status_expr_str, status_value_str, __VA_ARGS__)
+
+// PW_CHECK_OK version 1: No message or args
+#define _PW_CHECK_OK_HAS_MSG_0(status_expr_str, status_value_str, ignored_arg) \
+  PW_HANDLE_ASSERT_BINARY_COMPARE_FAILURE(                                     \
+      status_expr_str, status_value_str, "==", "OkStatus()", "OK", "%s", "")
+
+// PW_CHECK_OK version 2: With message (and maybe args)
+#define _PW_CHECK_OK_HAS_MSG_1(status_expr_str, status_value_str, ...) \
+  PW_HANDLE_ASSERT_BINARY_COMPARE_FAILURE(status_expr_str,             \
+                                          status_value_str,            \
+                                          "==",                        \
+                                          "OkStatus()",                \
+                                          "OK",                        \
+                                          "%s",                        \
+                                          __VA_ARGS__)
diff --git a/pw_assert/public/pw_assert/internal/print_and_abort.h b/pw_assert/public/pw_assert/internal/print_and_abort.h
deleted file mode 100644
index 3efdf09..0000000
--- a/pw_assert/public/pw_assert/internal/print_and_abort.h
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <stdio.h>
-#include <stdlib.h>
-
-#include "pw_assert/config.h"
-
-#if PW_ASSERT_ENABLE_DEBUG
-#define _PW_ASSERT_DEBUG_MACRO "or PW_DASSERT() "
-#else
-#define _PW_ASSERT_DEBUG_MACRO
-#endif  // PW_ASSERT_ENABLE_DEBUG
-
-#ifdef __GNUC__
-#define _PW_ASSERT_ABORT_FUNCTION __PRETTY_FUNCTION__
-#else
-#define _PW_ASSERT_ABORT_FUNCTION __func__
-#endif  // __GNUC__
-
-// This assert implementation prints the file path, line number, and assert
-// expression using printf. Uses ANSI escape codes for colors.
-//
-// This is done with single printf to work better in multithreaded enironments.
-#define PW_ASSERT_HANDLE_FAILURE(expression)           \
-  fflush(stdout);                                      \
-  fprintf(stderr,                                      \
-          "\033[41m\033[37m\033[1m%s:%d:\033[0m "      \
-          "\033[1mPW_ASSERT() " _PW_ASSERT_DEBUG_MACRO \
-          "\033[31mFAILED!\033[0m\n\n"                 \
-          "  FAILED ASSERTION\n\n"                     \
-          "    %s\n\n"                                 \
-          "  FILE & LINE\n\n"                          \
-          "    %s:%d\n\n"                              \
-          "  FUNCTION\n\n"                             \
-          "    %s\n\n",                                \
-          __FILE__,                                    \
-          __LINE__,                                    \
-          expression,                                  \
-          __FILE__,                                    \
-          __LINE__,                                    \
-          _PW_ASSERT_ABORT_FUNCTION);                  \
-  fflush(stderr);                                      \
-  abort()
diff --git a/pw_assert/public/pw_assert/light.h b/pw_assert/public/pw_assert/light.h
new file mode 100644
index 0000000..635bb3f
--- /dev/null
+++ b/pw_assert/public/pw_assert/light.h
@@ -0,0 +1,17 @@
+// Copyright 2021 The Pigweed Authors
+//
+// 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
+//
+//     https://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(pwbug/350): Remove the deprecated light.h header in favor of assert.h.
+#include "pw_assert/assert.h"
diff --git a/pw_assert/public/pw_assert/options.h b/pw_assert/public/pw_assert/options.h
new file mode 100644
index 0000000..e7ea02f
--- /dev/null
+++ b/pw_assert/public/pw_assert/options.h
@@ -0,0 +1,28 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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
+
+// PW_ASSERT_ENABLE_DEBUG controls whether DCHECKs and DASSERTs are enabled.
+//
+// This block defines PW_ASSERT_ENABLE_DEBUG if it is not already, taking into
+// account traditional NDEBUG macro.
+#if !defined(PW_ASSERT_ENABLE_DEBUG)
+#if defined(NDEBUG)
+// Release mode; remove all DCHECK*() and DASSERT() asserts.
+#define PW_ASSERT_ENABLE_DEBUG 0
+#else
+// Debug mode; keep all DCHECK*() and DASSERT() asserts.
+#define PW_ASSERT_ENABLE_DEBUG 1
+#endif  // defined (NDEBUG)
+#endif  // !defined(PW_ASSERT_ENABLE_DEBUG)
diff --git a/pw_assert/pw_assert_test/fake_backend.h b/pw_assert/pw_assert_test/fake_backend.h
index 869d224..ee7f3be 100644
--- a/pw_assert/pw_assert_test/fake_backend.h
+++ b/pw_assert/pw_assert_test/fake_backend.h
@@ -15,7 +15,6 @@
 
 #include <stdbool.h>
 
-#include "pw_preprocessor/arguments.h"
 #include "pw_preprocessor/compiler.h"
 #include "pw_preprocessor/util.h"
 
diff --git a/pw_assert_basic/BUILD b/pw_assert_basic/BUILD
new file mode 100644
index 0000000..9992d77
--- /dev/null
+++ b/pw_assert_basic/BUILD
@@ -0,0 +1,75 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "headers",
+    hdrs = [
+        "public/pw_assert_basic/assert_basic.h",
+        "public_overrides/pw_assert_backend/assert_backend.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        "//pw_preprocessor",
+    ],
+)
+
+pw_cc_library(
+    name = "pw_assert_basic",
+    srcs = [
+        "assert_basic.cc",
+    ],
+    deps = [
+        ":headers",
+        ":pw_assert_basic_handler",
+        "//pw_assert:facade",
+        "//pw_preprocessor",
+    ],
+)
+
+pw_cc_library(
+    name = "handler_facade",
+    hdrs = [
+        "public/pw_assert_basic/handler.h",
+    ],
+    deps = [
+        "//pw_preprocessor",
+    ],
+)
+
+pw_cc_library(
+    name = "pw_assert_basic_handler",
+    srcs = [
+        "basic_handler.cc",
+    ],
+    deps = [
+        ":handler_facade",
+        ":headers",
+        "//pw_assert:facade",
+        "//pw_preprocessor",
+        "//pw_string",
+        "//pw_sys_io",
+    ],
+)
diff --git a/pw_assert_basic/BUILD.bazel b/pw_assert_basic/BUILD.bazel
deleted file mode 100644
index 95f5a5b..0000000
--- a/pw_assert_basic/BUILD.bazel
+++ /dev/null
@@ -1,75 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "headers",
-    hdrs = [
-        "public/pw_assert_basic/assert_basic.h",
-        "public_overrides/pw_assert_backend/assert_backend.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    deps = [
-        "//pw_preprocessor",
-    ],
-)
-
-pw_cc_library(
-    name = "pw_assert_basic",
-    srcs = [
-        "assert_basic.cc",
-    ],
-    deps = [
-        ":headers",
-        ":pw_assert_basic_handler",
-        "//pw_assert:facade",
-        "//pw_preprocessor",
-    ],
-)
-
-pw_cc_library(
-    name = "handler_facade",
-    hdrs = [
-        "public/pw_assert_basic/handler.h",
-    ],
-    deps = [
-        "//pw_preprocessor",
-    ],
-)
-
-pw_cc_library(
-    name = "pw_assert_basic_handler",
-    srcs = [
-        "basic_handler.cc",
-    ],
-    deps = [
-        ":handler_facade",
-        ":headers",
-        "//pw_assert:facade",
-        "//pw_preprocessor",
-        "//pw_string",
-        "//pw_sys_io",
-    ],
-)
diff --git a/pw_assert_basic/BUILD.gn b/pw_assert_basic/BUILD.gn
index 72c22f1..a8682f1 100644
--- a/pw_assert_basic/BUILD.gn
+++ b/pw_assert_basic/BUILD.gn
@@ -19,74 +19,47 @@
 import("$dir_pw_docgen/docs.gni")
 import("backend.gni")
 
-config("public_include_path") {
+config("default_config") {
   include_dirs = [ "public" ]
-  visibility = [ ":*" ]
 }
 
 config("backend_config") {
   include_dirs = [ "public_overrides" ]
-  visibility = [ ":*" ]
 }
 
 pw_facade("handler") {
   backend = pw_assert_basic_HANDLER_BACKEND
-  public_configs = [ ":public_include_path" ]
+  public_configs = [ ":default_config" ]
   public_deps = [ "$dir_pw_preprocessor" ]
   public = [ "public/pw_assert_basic/handler.h" ]
 }
 
-# pw_assert_basic only provides the backend's interface. The implementation is
-# pulled in through pw_build_LINK_DEPS.
 pw_source_set("pw_assert_basic") {
   public_configs = [
     ":backend_config",
-    ":public_include_path",
+    ":default_config",
+  ]
+  deps = [
+    "$dir_pw_assert:facade",
+    "$dir_pw_preprocessor",
+    pw_assert_basic_HANDLER_BACKEND,
   ]
   public = [
     "public/pw_assert_basic/assert_basic.h",
     "public/pw_assert_basic/handler.h",
     "public_overrides/pw_assert_backend/assert_backend.h",
   ]
-}
-
-# The assert backend deps that might cause circular dependencies, since
-# pw_assert is so ubiquitous. These deps are kept separate so they can be
-# depended on from elsewhere.
-pw_source_set("pw_assert_basic.impl") {
-  public_deps = [
-    dir_pw_result,
-    dir_pw_string,
-    dir_pw_sys_io,
-  ]
-  deps = [
-    ":pw_assert_basic",
-    "$dir_pw_assert:config",
-    "$dir_pw_assert:facade",
-    "$dir_pw_preprocessor",
-    pw_assert_basic_HANDLER_BACKEND,
-  ]
   sources = [ "assert_basic.cc" ]
 }
 
-# A basic handler backend using pw_sys_io.
+# A basic handler backend using sysio.
 pw_source_set("basic_handler") {
-  # Turn off GN check since this target intentionally leaves out deps to avoid
-  # circular dependencies.
-  check_includes = false
-
-  # Depend on the include path instead of the library to avoid circular deps.
-  configs = [
-    "$dir_pw_string:public_include_path",
-    "$dir_pw_result:public_include_path",
-  ]
   deps = [
     ":handler.facade",
-    "$dir_pw_assert:config",
     "$dir_pw_assert:facade",
     "$dir_pw_preprocessor",
-    "$dir_pw_sys_io:facade",  # Only pull in the facade to avoid circular deps
-    dir_pw_status,
+    "$dir_pw_string",
+    "$dir_pw_sys_io",
   ]
   sources = [ "basic_handler.cc" ]
 }
diff --git a/pw_assert_basic/OWNERS b/pw_assert_basic/OWNERS
deleted file mode 100644
index 2529495..0000000
--- a/pw_assert_basic/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-ewout@google.com
-keir@google.com
diff --git a/pw_assert_basic/assert_basic.cc b/pw_assert_basic/assert_basic.cc
index fcc20cc..72f2e8c 100644
--- a/pw_assert_basic/assert_basic.cc
+++ b/pw_assert_basic/assert_basic.cc
@@ -12,7 +12,7 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-#include "pw_assert/config.h"
+#include "pw_assert/options.h"
 #include "pw_assert_basic/handler.h"
 
 extern "C" void pw_assert_HandleFailure(void) {
diff --git a/pw_assert_basic/basic_handler.cc b/pw_assert_basic/basic_handler.cc
index 6a6e734..29d44e3 100644
--- a/pw_assert_basic/basic_handler.cc
+++ b/pw_assert_basic/basic_handler.cc
@@ -17,10 +17,9 @@
 //#define PW_LOG_MODULE_NAME "ASRT"
 //#include "pw_log/log.h"
 
-#include <cstdio>
 #include <cstring>
 
-#include "pw_assert/config.h"
+#include "pw_assert/options.h"
 #include "pw_assert_basic/handler.h"
 #include "pw_preprocessor/util.h"
 #include "pw_string/string_builder.h"
@@ -79,10 +78,7 @@
     " ",
 };
 
-static void WriteLine(const std::string_view& s) {
-  pw::sys_io::WriteLine(s)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-}
+using pw::sys_io::WriteLine;
 
 typedef pw::StringBuffer<150> Buffer;
 
@@ -142,18 +138,23 @@
   // now this is acceptable since no one is using this basic backend.
   if (!PW_ASSERT_BASIC_DISABLE_NORETURN) {
     if (PW_ASSERT_BASIC_ABORT) {
-      // abort() doesn't flush stderr/stdout, so manually flush them before
-      // aborting. abort() is preferred to exit(1) because debuggers catch it.
-      std::fflush(stderr);
-      std::fflush(stdout);
-      std::abort();
+      // Using exit() instead of abort() here because exit() allows for the
+      // destructors for the stdout buffers to be called. This addresses an
+      // issue that occurs when Bazel's execution wrapper binds stdout. This
+      // results in stdout going from a synchronized to a buffered file
+      // descriptor. In this case when abort() is called in a Bazel test the
+      // program exits before the stdout buffer can be synchronized with Bazel's
+      // execution wrapper, the resulting output from a test is an empty output
+      // buffer. Using exit() here allows the destructors to synchronized the
+      // stdout buffer before exiting.
+      exit(1);
     } else {
       WriteLine("");
       WriteLine(MAGENTA "  HANG TIME" RESET);
       WriteLine("");
       WriteLine(
           "     ... until a debugger joins. System is waiting in a while(1)");
-      while (true) {
+      while (1) {
       }
     }
     PW_UNREACHABLE;
diff --git a/pw_assert_basic/docs.rst b/pw_assert_basic/docs.rst
index a06627f..2ddc367 100644
--- a/pw_assert_basic/docs.rst
+++ b/pw_assert_basic/docs.rst
@@ -20,8 +20,6 @@
   intended mostly for ease of initial bringup. We encourage teams to use
   tokenized asserts since they are much smaller both in terms of ROM and RAM.
 
-.. _module-pw_assert_basic-custom_handler:
-
 Custom handler backend example
 ------------------------------
 Here is a typical usage example implementing a simple handler backend which uses
diff --git a/pw_assert_basic/public/pw_assert_basic/assert_basic.h b/pw_assert_basic/public/pw_assert_basic/assert_basic.h
index 88405f8..cc933d0 100644
--- a/pw_assert_basic/public/pw_assert_basic/assert_basic.h
+++ b/pw_assert_basic/public/pw_assert_basic/assert_basic.h
@@ -14,7 +14,6 @@
 #pragma once
 
 #include "pw_assert_basic/handler.h"
-#include "pw_preprocessor/arguments.h"
 #include "pw_preprocessor/compiler.h"
 #include "pw_preprocessor/util.h"
 
diff --git a/pw_assert_basic/public_overrides/pw_assert_backend/assert_backend.h b/pw_assert_basic/public_overrides/pw_assert_backend/assert_backend.h
index 5cdee4f..e7e1840 100644
--- a/pw_assert_basic/public_overrides/pw_assert_backend/assert_backend.h
+++ b/pw_assert_basic/public_overrides/pw_assert_backend/assert_backend.h
@@ -14,7 +14,7 @@
 
 // This override header merely points to the true backend, in this case the
 // basic one. The reason to redirect is to permit the use of multiple backends
-// (though only pw_assert/check.h can only point to 1 backend).
+// (though only pw_assert/assert.h can only point to 1 backend).
 #pragma once
 
 #include "pw_assert_basic/assert_basic.h"
diff --git a/pw_assert_log/BUILD b/pw_assert_log/BUILD
new file mode 100644
index 0000000..25768d1
--- /dev/null
+++ b/pw_assert_log/BUILD
@@ -0,0 +1,49 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "headers",
+    hdrs = [
+        "public/pw_assert_log/assert_log.h",
+        "public_overrides/pw_assert_backend/assert_backend.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    srcs = [
+        "assert_log.cc",
+    ],
+    deps = [
+        "//pw_preprocessor",
+    ],
+)
+
+pw_cc_library(
+    name = "pw_assert_log",
+    deps = [
+        ":headers",
+        "//pw_assert:facade",
+        "//pw_preprocessor",
+    ],
+)
diff --git a/pw_assert_log/BUILD.bazel b/pw_assert_log/BUILD.bazel
deleted file mode 100644
index 3f21931..0000000
--- a/pw_assert_log/BUILD.bazel
+++ /dev/null
@@ -1,57 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_assert_log",
-    srcs = [
-        "assert_log.cc",
-    ],
-    hdrs = [
-        "public/pw_assert_log/assert_log.h",
-        "public_overrides/pw_assert_backend/assert_backend.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    deps = [
-        "//pw_assert:facade",
-        "//pw_log",
-        "//pw_preprocessor",
-    ],
-)
-
-pw_cc_library(
-    name = "lite_backend",
-    hdrs = [
-        "assert_lite_public_overrides/pw_assert_backend/assert_lite_backend.h",
-        "public/pw_assert_log/assert_lite_log.h",
-    ],
-    includes = [
-        "assert_lite_public_overrides",
-        "public",
-    ],
-    deps = [
-        "//pw_preprocessor",
-    ],
-)
diff --git a/pw_assert_log/BUILD.gn b/pw_assert_log/BUILD.gn
index addc888..083c5e8 100644
--- a/pw_assert_log/BUILD.gn
+++ b/pw_assert_log/BUILD.gn
@@ -25,52 +25,25 @@
   include_dirs = [ "public_overrides" ]
 }
 
-config("lite_backend_overrides") {
-  include_dirs = [ "assert_lite_public_overrides" ]
-}
-
-# This backend to pw_assert's PW_CHECK()/PW_CRASH() macros via PW_LOG.
 pw_source_set("pw_assert_log") {
   public_configs = [
     ":backend_config",
     ":default_config",
   ]
+  deps = [ ":core" ]
   public_deps = [ "$dir_pw_log" ]
   public = [ "public_overrides/pw_assert_backend/assert_backend.h" ]
+}
+
+pw_source_set("core") {
+  public_configs = [ ":default_config" ]
+  public_deps = [ "$dir_pw_log" ]
   deps = [
-    "$dir_pw_assert:config",
     "$dir_pw_assert:facade",
     "$dir_pw_preprocessor",
   ]
-  sources = [
-    "assert_log.cc",
-    "public/pw_assert_log/assert_log.h",
-  ]
-}
-
-# This backend to pw_assert's PW_ASSERT() macros via PW_LOG. It is intended only
-# for use with PW_LOG backends which are constexpr compatible such as
-# pw_log_android.
-#
-# Warning: The "lite" naming is transitional. assert_lite_backend.h headers
-# will be renamed as the pw_assert API is reassessed. (pwbug/246)
-pw_source_set("lite_backend") {
-  public_configs = [
-    ":lite_backend_overrides",
-    ":default_config",
-  ]
-  public_deps = [ dir_pw_preprocessor ]
-  public =
-      [ "assert_lite_public_overrides/pw_assert_backend/assert_lite_backend.h" ]
-  sources = [ "public/pw_assert_log/assert_lite_log.h" ]
-}
-
-group("lite_compatibility_backend.impl") {
-}
-
-# pw_assert_log doesn't have deps with potential circular dependencies, so this
-# impl group can be empty.
-group("pw_assert_log.impl") {
+  public = [ "public/pw_assert_log/assert_log.h" ]
+  sources = [ "assert_log.cc" ]
 }
 
 pw_doc_group("docs") {
diff --git a/pw_assert_log/OWNERS b/pw_assert_log/OWNERS
deleted file mode 100644
index 2529495..0000000
--- a/pw_assert_log/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-ewout@google.com
-keir@google.com
diff --git a/pw_assert_log/assert_lite_public_overrides/pw_assert_backend/assert_lite_backend.h b/pw_assert_log/assert_lite_public_overrides/pw_assert_backend/assert_lite_backend.h
deleted file mode 100644
index 91a6ff6..0000000
--- a/pw_assert_log/assert_lite_public_overrides/pw_assert_backend/assert_lite_backend.h
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// This override header merely points to the true backend, in this case the
-// basic one. The reason to redirect is to permit the use of multiple backends
-// (though only pw_assert/check.h can only point to 1 backend).
-#pragma once
-
-#include "pw_assert_log/assert_lite_log.h"
diff --git a/pw_assert_log/assert_log.cc b/pw_assert_log/assert_log.cc
index 0e8be18..9d8b9ba 100644
--- a/pw_assert_log/assert_log.cc
+++ b/pw_assert_log/assert_log.cc
@@ -14,16 +14,16 @@
 
 #include "pw_assert_log/assert_log.h"
 
-#include "pw_assert/config.h"
+#include "pw_assert/options.h"
 
 extern "C" void pw_assert_HandleFailure(void) {
 #if PW_ASSERT_ENABLE_DEBUG
-  PW_LOG(PW_LOG_LEVEL_FATAL,
-         PW_LOG_FLAGS,
+  PW_LOG(PW_LOG_LEVEL_CRITICAL,
+         PW_LOG_ASSERT_FAILED_FLAG,
          "Crash: PW_ASSERT() or PW_DASSERT() failure");
 #else
-  PW_LOG(PW_LOG_LEVEL_FATAL,
-         PW_LOG_FLAGS,
+  PW_LOG(PW_LOG_LEVEL_CRITICAL,
+         PW_LOG_ASSERT_FAILED_FLAG,
          "Crash: PW_ASSERT() failure. Note: PW_DASSERT disabled");
 #endif  // PW_ASSERT_ENABLE_DEBUG
   PW_UNREACHABLE;
diff --git a/pw_assert_log/docs.rst b/pw_assert_log/docs.rst
index 3fa0d3a..012e1c8 100644
--- a/pw_assert_log/docs.rst
+++ b/pw_assert_log/docs.rst
@@ -4,25 +4,16 @@
 pw_assert_log
 =============
 
------------------
-pw_assert_BACKEND
------------------
-This assert backend implements the ``pw_assert:check`` facade, by routing the
-``PW_CHECK()``/``PW_CRASH()`` macros into ``PW_LOG``  with the
-``PW_LOG_LEVEL_FATAL`` log level. This is an easy way to tokenize your assert
-messages, by using the ``pw_log_tokenized`` log backend for logging, then using
-``pw_assert_log`` to route the tokenized messages into the tokenized log
-handler.
+--------
+Overview
+--------
+This assert backend implements the ``pw_assert`` facade, by routing the assert
+message into the logger with the ``PW_LOG_ASSERT_FAILED`` flag set. This is an
+easy way to tokenize your assert messages, by using the ``pw_log_tokenized``
+log backend for logging, then using ``pw_assert_log`` to route the tokenized
+messages into the tokenized log handler.
 
 To use this module:
 
 1. Set your assert backend: ``pw_assert_BACKEND = dir_pw_assert_log``
 2. Ensure your logging backend knows how to handle the assert failure flag
-
-----------------------
-pw_assert_LITE_BACKEND
-----------------------
-This assert backend implements the ``pw_assert:assert`` facade, by routing the
-``PW_ASSERT()`` macros into ``PW_LOG`` with the ``PW_LOG_LEVEL_FATAL`` log
-level. This is an easy way to forward your asserts to a native macro assert
-API if it is already constexpr safe such as on Android.
diff --git a/pw_assert_log/public/pw_assert_log/assert_lite_log.h b/pw_assert_log/public/pw_assert_log/assert_lite_log.h
deleted file mode 100644
index 08ddcbc..0000000
--- a/pw_assert_log/public/pw_assert_log/assert_lite_log.h
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_log/levels.h"
-#include "pw_log/log.h"
-#include "pw_log/options.h"
-#include "pw_preprocessor/compiler.h"
-
-#define PW_ASSERT_HANDLE_FAILURE(condition_string)                             \
-  do {                                                                         \
-    PW_LOG(                                                                    \
-        PW_LOG_LEVEL_FATAL, PW_LOG_FLAGS, "Assert failed: " condition_string); \
-    PW_UNREACHABLE;                                                            \
-  } while (0)
diff --git a/pw_assert_log/public/pw_assert_log/assert_log.h b/pw_assert_log/public/pw_assert_log/assert_log.h
index d238e57..d26d339 100644
--- a/pw_assert_log/public/pw_assert_log/assert_log.h
+++ b/pw_assert_log/public/pw_assert_log/assert_log.h
@@ -19,30 +19,38 @@
 #include "pw_preprocessor/compiler.h"
 #include "pw_preprocessor/util.h"
 
+// Use the highest available log flag to indicate an assert failure.
+#define PW_LOG_ASSERT_FAILED_FLAG (1u << (PW_LOG_FLAG_BITS - 1u))
+
 // Die with a message with several attributes included. This crash frontend
 // funnels everything into the logger, which must then handle the true crash
 // behaviour.
-#define PW_HANDLE_CRASH(message, ...)                                         \
-  do {                                                                        \
-    PW_LOG(PW_LOG_LEVEL_FATAL, PW_LOG_FLAGS, "Crash: " message, __VA_ARGS__); \
-    PW_UNREACHABLE;                                                           \
+#define PW_HANDLE_CRASH(message, ...)       \
+  do {                                      \
+    PW_LOG(PW_LOG_LEVEL_CRITICAL,           \
+           PW_LOG_ASSERT_FAILED_FLAG,       \
+           __FILE__ ":%d: Crash: " message, \
+           __LINE__,                        \
+           __VA_ARGS__);                    \
+    PW_UNREACHABLE;                         \
   } while (0)
 
 // Die with a message with several attributes included. This assert frontend
 // funnels everything into the logger, which is responsible for displaying the
 // log, then crashing/rebooting the device.
-#define PW_HANDLE_ASSERT_FAILURE(condition_string, message, ...) \
-  do {                                                           \
-    PW_LOG(PW_LOG_LEVEL_FATAL,                                   \
-           PW_LOG_FLAGS,                                         \
-           "Check failed: " condition_string ". " message,       \
-           __VA_ARGS__);                                         \
-    PW_UNREACHABLE;                                              \
+#define PW_HANDLE_ASSERT_FAILURE(condition_string, message, ...)         \
+  do {                                                                   \
+    PW_LOG(PW_LOG_LEVEL_CRITICAL,                                        \
+           PW_LOG_ASSERT_FAILED_FLAG,                                    \
+           __FILE__ ":%d: Check failed: " condition_string ". " message, \
+           __LINE__,                                                     \
+           __VA_ARGS__);                                                 \
+    PW_UNREACHABLE;                                                      \
   } while (0)
 
 // Sample assert failure message produced by the below implementation:
 //
-//   Check failed: old_x (=610) < new_x (=50). Details: foo=10, bar.
+//   foo.cc:25: Check failed: old_x (=610) < new_x (=50). Details: foo=10, bar.
 //
 // Putting the value next to the operand makes the string easier to read.
 
@@ -56,14 +64,14 @@
                                                 type_fmt,                 \
                                                 message, ...)             \
   do {                                                                    \
-    PW_LOG(PW_LOG_LEVEL_FATAL,                                            \
-           PW_LOG_FLAGS,                                                  \
-           "Check failed: "                                               \
+    PW_LOG(PW_LOG_LEVEL_CRITICAL,                                         \
+           PW_LOG_ASSERT_FAILED_FLAG,                                     \
+           __FILE__ ":%d: Check failed: "                                 \
                  arg_a_str " (=" type_fmt ") "                            \
                  comparison_op_str " "                                    \
                  arg_b_str " (=" type_fmt ")"                             \
                  ". " message,                                            \
-              arg_a_val, arg_b_val, __VA_ARGS__);                         \
+              __LINE__, arg_a_val, arg_b_val, __VA_ARGS__);               \
     PW_UNREACHABLE;                                                       \
   } while(0)
 // clang-format on
diff --git a/pw_assert_log/public_overrides/pw_assert_backend/assert_backend.h b/pw_assert_log/public_overrides/pw_assert_backend/assert_backend.h
index 8522926..376f51e 100644
--- a/pw_assert_log/public_overrides/pw_assert_backend/assert_backend.h
+++ b/pw_assert_log/public_overrides/pw_assert_backend/assert_backend.h
@@ -14,7 +14,7 @@
 
 // This override header merely points to the true backend, in this case the
 // basic one. The reason to redirect is to permit the use of multiple backends
-// (though only pw_assert/check.h can only point to 1 backend).
+// (though only pw_assert/assert.h can only point to 1 backend).
 #pragma once
 
 #include "pw_assert_log/assert_log.h"
diff --git a/pw_assert_tokenized/BUILD.bazel b/pw_assert_tokenized/BUILD.bazel
deleted file mode 100644
index bd2fe86..0000000
--- a/pw_assert_tokenized/BUILD.bazel
+++ /dev/null
@@ -1,47 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_assert_tokenized",
-    srcs = [
-        "log_handler.cc",
-    ],
-    hdrs = [
-        "assert_public_overrides/pw_assert_backend/assert_lite_backend.h",
-        "check_public_overrides/pw_assert_backend/assert_backend.h",
-        "public/pw_assert_tokenized/assert_tokenized.h",
-        "public/pw_assert_tokenized/check_tokenized.h",
-        "public/pw_assert_tokenized/handler.h",
-    ],
-    includes = [
-        "assert_public_overrides",
-        "check_public_overrides",
-        "public",
-    ],
-    deps = [
-        "//pw_assert",
-        "//pw_log_tokenized,",
-        "//pw_preprocessor",
-        "//pw_tokenizer",
-    ],
-)
diff --git a/pw_assert_tokenized/BUILD.gn b/pw_assert_tokenized/BUILD.gn
deleted file mode 100644
index 42561a4..0000000
--- a/pw_assert_tokenized/BUILD.gn
+++ /dev/null
@@ -1,110 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_docgen/docs.gni")
-
-declare_args() {
-  pw_assert_tokenized_HANDLER_BACKEND = "$dir_pw_assert_tokenized:log_handler"
-}
-
-config("public_include_path") {
-  include_dirs = [ "public" ]
-  visibility = [ ":*" ]
-}
-
-config("assert_backend_config") {
-  include_dirs = [ "assert_public_overrides" ]
-  visibility = [ ":*" ]
-}
-
-config("check_backend_config") {
-  include_dirs = [ "check_public_overrides" ]
-  visibility = [ ":*" ]
-}
-
-pw_source_set("handler") {
-  public_configs = [ ":public_include_path" ]
-  public_deps = [ "$dir_pw_preprocessor" ]
-  public = [ "public/pw_assert_tokenized/handler.h" ]
-}
-
-pw_source_set("assert_backend") {
-  public_configs = [
-    ":public_include_path",
-    ":assert_backend_config",
-  ]
-  public_deps = [
-    ":handler",
-    "$dir_pw_tokenizer",
-  ]
-  public = [
-    "assert_public_overrides/pw_assert_backend/assert_lite_backend.h",
-    "public/pw_assert_tokenized/assert_tokenized.h",
-  ]
-}
-
-pw_source_set("assert_backend.impl") {
-  public_deps = [ pw_assert_tokenized_HANDLER_BACKEND ]
-}
-
-pw_source_set("check_backend") {
-  public_configs = [
-    ":public_include_path",
-    ":check_backend_config",
-  ]
-  public_deps = [
-    ":handler",
-    "$dir_pw_tokenizer",
-  ]
-  public = [
-    "check_public_overrides/pw_assert_backend/assert_backend.h",
-    "public/pw_assert_tokenized/check_tokenized.h",
-  ]
-}
-
-pw_source_set("check_backend.impl") {
-  public_deps = [ pw_assert_tokenized_HANDLER_BACKEND ]
-}
-
-pw_source_set("pw_assert_tokenized") {
-  public_deps = [
-    ":assert_backend",
-    ":check_backend",
-  ]
-}
-
-pw_source_set("pw_assert_tokenized.impl") {
-  deps = [
-    ":assert_backend.impl",
-    ":check_backend.impl",
-  ]
-}
-
-pw_source_set("log_handler") {
-  deps = [
-    ":handler",
-    "$dir_pw_assert:config",
-    "$dir_pw_base64",
-    "$dir_pw_log",
-    "$dir_pw_log_tokenized",
-  ]
-  sources = [ "log_handler.cc" ]
-}
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
diff --git a/pw_assert_tokenized/assert_public_overrides/pw_assert_backend/assert_lite_backend.h b/pw_assert_tokenized/assert_public_overrides/pw_assert_backend/assert_lite_backend.h
deleted file mode 100644
index 561f521..0000000
--- a/pw_assert_tokenized/assert_public_overrides/pw_assert_backend/assert_lite_backend.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_assert_tokenized/assert_tokenized.h"
diff --git a/pw_assert_tokenized/check_public_overrides/pw_assert_backend/assert_backend.h b/pw_assert_tokenized/check_public_overrides/pw_assert_backend/assert_backend.h
deleted file mode 100644
index 4a1e3ae..0000000
--- a/pw_assert_tokenized/check_public_overrides/pw_assert_backend/assert_backend.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_assert_tokenized/check_tokenized.h"
diff --git a/pw_assert_tokenized/docs.rst b/pw_assert_tokenized/docs.rst
deleted file mode 100644
index 0a0aeeb..0000000
--- a/pw_assert_tokenized/docs.rst
+++ /dev/null
@@ -1,95 +0,0 @@
-.. _module-pw_assert_tokenized:
-
-===================
-pw_assert_tokenized
-===================
-
---------
-Overview
---------
-The ``pw_assert_tokenized`` module provides ``PW_ASSERT()`` and ``PW_CHECK_*()``
-backends for the ``pw_assert`` module. These backends are much more space
-efficient than using ``pw_assert_log`` with ``pw_log_tokenized`` The tradeoff,
-however, is that ``PW_CHECK_*()`` macros are much more limited as all argument
-values are discarded. This means only constant string information is captured in
-the reported tokens.
-
-* **PW_ASSERT()**: The ``PW_ASSERT()`` macro will capture the file name and line
-  number of the assert statement. By default, it is passed to the logging system
-  to produce a string like this:
-
-    PW_ASSERT() or PW_DASSERT() failure at
-    pw_result/public/pw_result/result.h:63
-
-* **PW_CHECK_\*()**: The ``PW_CHECK_*()`` macros work in contexts where
-  tokenization is fully supported, so they are able to capture the CHECK
-  statement expression and any provided string literal in addition to the file
-  name:
-
-    Check failure in pw_metric/size_report/base.cc: \*unoptimizable >= 0,
-    Ensure this CHECK logic stays.
-
-  Evaluated values of ``PW_CHECK_*()`` statements are not captured, and any
-  string formatting arguments are also not captured. This minimizes call-site
-  cost as only two arguments are ever passed to the handler (the calculated
-  token, and the line number of the statement).
-
-  Note that the line number is passed to the tokenized logging system as
-  metadata, but is not part of the tokenized string. This is to ensure the
-  CHECK callsite maximizes efficiency by only passing two arguments to the
-  handler.
-
-In both cases, the assert handler is only called with two arguments: a 32-bit
-token to represent a string, and the integer line number of the callsite.
-
------
-Setup
------
-
-#. Set ``pw_assert_BACKEND = "$dir_pw_assert_tokenized:check_backend"`` and
-   ``pw_assert_LITE_BACKEND = "$dir_pw_assert_tokenized:assert_backend"`` in
-   your target configuration.
-#. Ensure your target provides ``pw_tokenizer_GLOBAL_HANDLER_BACKEND``. By
-   default, pw_assert_tokenized will forward assert failures to the tokenizer
-   handler as logs. The tokenizer handler should check for ``LOG_LEVEL_FATAL``
-   and properly divert to a crash handler.
-#. Add file name tokens to your token database. pw_assert_tokenized can't create
-   file name tokens that can be parsed out of the final compiled binary. The
-   ``pw_relative_source_file_names``
-   :ref:`GN template<module-pw_build-relative-source-file-names>` can be used to
-   collect the names of all source files used in your final executable into a
-   JSON file, which can then be included in the creation of a tokenizer
-   database.
-
-Example file name token database setup
---------------------------------------
-
-.. code-block::
-
-  pw_executable("main") {
-    deps = [
-      # ...
-    ]
-    sources = [ "main.cc" ]
-  }
-
-  pw_tokenizer_database("log_tokens") {
-    database = "tools/tokenized_logs.csv"
-    deps = [
-      ":source_file_names",
-      ":main",
-    ]
-    optional_paths = [ "$root_build_dir/**/*.elf" ]
-    input_databases = [ "$target_gen_dir/source_file_names.json" ]
-  }
-
-  # Extracts all source/header file names from "main" and its transitive
-  # dependencies for tokenization.
-  pw_relative_source_file_names("source_file_names") {
-    deps = [ ":main" ]
-    outputs = [ "$target_gen_dir/source_file_names.json" ]
-  }
-
-
-.. warning::
-  This module is experimental and does not provide a stable API.
diff --git a/pw_assert_tokenized/log_handler.cc b/pw_assert_tokenized/log_handler.cc
deleted file mode 100644
index ea559ea..0000000
--- a/pw_assert_tokenized/log_handler.cc
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstdint>
-#include <cstring>
-#include <memory>
-#include <span>
-
-#include "pw_assert/config.h"
-#include "pw_assert_tokenized/handler.h"
-#include "pw_base64/base64.h"
-#include "pw_log/log.h"
-#include "pw_log_tokenized/log_tokenized.h"
-
-extern "C" void pw_assert_tokenized_HandleAssertFailure(
-    uint32_t tokenized_file_name, int line_number) {
-  // Buffer size for binary->base64 conversion with a null terminator.
-  constexpr size_t kBufferSize =
-      pw::base64::EncodedSize(sizeof(tokenized_file_name)) + 1;
-  std::byte* hash_buffer = reinterpret_cast<std::byte*>(&tokenized_file_name);
-  char base64_buffer[kBufferSize];
-
-  size_t len =
-      pw::base64::Encode(std::span(hash_buffer, sizeof(tokenized_file_name)),
-                         std::span(base64_buffer));
-  base64_buffer[len] = '\0';
-#if PW_ASSERT_ENABLE_DEBUG
-  PW_LOG(PW_LOG_LEVEL_FATAL,
-         PW_LOG_FLAGS,
-         "PW_ASSERT() or PW_DASSERT() failure at $%s:%d",
-         base64_buffer,
-         line_number);
-#else
-  PW_LOG(PW_LOG_LEVEL_FATAL,
-         PW_LOG_FLAGS,
-         "PW_ASSERT() failure. Note: PW_DASSERT disabled $%s:%d",
-         base64_buffer,
-         line_number);
-#endif  // PW_ASSERT_ENABLE_DEBUG
-  PW_UNREACHABLE;
-}
-
-extern "C" void pw_assert_tokenized_HandleCheckFailure(
-    uint32_t tokenized_message, int line_number) {
-  // TODO(amontanez): There should be a less-hacky way to assemble this.
-  const uint32_t payload = _PW_LOG_TOKENIZED_LEVEL(PW_LOG_LEVEL_FATAL) |
-                           _PW_LOG_TOKENIZED_FLAGS(PW_LOG_FLAGS) |
-                           _PW_LOG_TOKENIZED_LINE(line_number);
-  uint8_t token_buffer[sizeof(tokenized_message)];
-  memcpy(token_buffer, &tokenized_message, sizeof(tokenized_message));
-
-  pw_tokenizer_HandleEncodedMessageWithPayload(
-      payload, token_buffer, sizeof(token_buffer));
-  PW_UNREACHABLE;
-}
diff --git a/pw_assert_tokenized/public/pw_assert_tokenized/assert_tokenized.h b/pw_assert_tokenized/public/pw_assert_tokenized/assert_tokenized.h
deleted file mode 100644
index 46ae933..0000000
--- a/pw_assert_tokenized/public/pw_assert_tokenized/assert_tokenized.h
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_assert_tokenized/handler.h"
-#include "pw_tokenizer/tokenize.h"
-
-// The tokens generated by this expression are not stored in the ELF in a way
-// that the pw_tokenizer tooling can extract. Tokenized file names must be
-// generated offline separately.
-#define PW_ASSERT_HANDLE_FAILURE(expression)                                   \
-  pw_assert_tokenized_HandleAssertFailure(PW_TOKENIZER_STRING_TOKEN(__FILE__), \
-                                          __LINE__)
diff --git a/pw_assert_tokenized/public/pw_assert_tokenized/check_tokenized.h b/pw_assert_tokenized/public/pw_assert_tokenized/check_tokenized.h
deleted file mode 100644
index bda3a1a..0000000
--- a/pw_assert_tokenized/public/pw_assert_tokenized/check_tokenized.h
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_assert_tokenized/handler.h"
-#include "pw_tokenizer/tokenize.h"
-
-#define _PW_ASSERT_TOKENIZED_TO_HANDLER(str)                       \
-  do {                                                             \
-    const uint32_t token =                                         \
-        PW_TOKENIZE_STRING("Check failure in " __FILE__ ": " str); \
-    pw_assert_tokenized_HandleCheckFailure(token, __LINE__);       \
-  } while (0)
-
-#define PW_HANDLE_CRASH(...) _PW_ASSERT_TOKENIZED_TO_HANDLER(#__VA_ARGS__)
-
-#define PW_HANDLE_ASSERT_FAILURE(condition_string, message, ...) \
-  _PW_ASSERT_TOKENIZED_TO_HANDLER(condition_string ", " message)
-
-#define PW_HANDLE_ASSERT_BINARY_COMPARE_FAILURE(arg_a_str,         \
-                                                arg_a_val,         \
-                                                comparison_op_str, \
-                                                arg_b_str,         \
-                                                arg_b_val,         \
-                                                type_fmt,          \
-                                                message,           \
-                                                ...)               \
-  _PW_ASSERT_TOKENIZED_TO_HANDLER(                                 \
-      arg_a_str " " comparison_op_str " " arg_b_str ", " message #__VA_ARGS__)
diff --git a/pw_assert_tokenized/public/pw_assert_tokenized/handler.h b/pw_assert_tokenized/public/pw_assert_tokenized/handler.h
deleted file mode 100644
index dcdba50..0000000
--- a/pw_assert_tokenized/public/pw_assert_tokenized/handler.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <stdint.h>
-
-#include "pw_preprocessor/compiler.h"
-#include "pw_preprocessor/util.h"
-
-PW_EXTERN_C_START
-
-PW_NO_RETURN void pw_assert_tokenized_HandleAssertFailure(
-    uint32_t tokenized_file_name, int line_number);
-
-PW_NO_RETURN void pw_assert_tokenized_HandleCheckFailure(
-    uint32_t tokenized_message, int line_number);
-
-PW_EXTERN_C_END
diff --git a/pw_assert_zephyr/BUILD.gn b/pw_assert_zephyr/BUILD.gn
deleted file mode 100644
index 9a6699a..0000000
--- a/pw_assert_zephyr/BUILD.gn
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_docgen/docs.gni")
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
diff --git a/pw_assert_zephyr/CMakeLists.txt b/pw_assert_zephyr/CMakeLists.txt
deleted file mode 100644
index 1d55f65..0000000
--- a/pw_assert_zephyr/CMakeLists.txt
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-
-if(NOT CONFIG_PIGWEED_ASSERT)
-  return()
-endif()
-
-pw_auto_add_simple_module(pw_assert_zephyr
-  IMPLEMENTS_FACADE
-    pw_assert
-)
-pw_set_backend(pw_assert pw_assert_zephyr)
-target_link_libraries(pw_assert_zephyr PUBLIC zephyr_interface)
-zephyr_link_libraries(pw_assert_zephyr)
diff --git a/pw_assert_zephyr/Kconfig b/pw_assert_zephyr/Kconfig
deleted file mode 100644
index de2290c..0000000
--- a/pw_assert_zephyr/Kconfig
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-config PIGWEED_ASSERT
-    bool "Enable Pigweed assert library (pw_assert)"
-    select PIGWEED_PREPROCESSOR
diff --git a/pw_assert_zephyr/OWNERS b/pw_assert_zephyr/OWNERS
deleted file mode 100644
index 2230ec2..0000000
--- a/pw_assert_zephyr/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-peress@google.com
diff --git a/pw_assert_zephyr/assert_zephyr.cc b/pw_assert_zephyr/assert_zephyr.cc
deleted file mode 100644
index 1deae5d..0000000
--- a/pw_assert_zephyr/assert_zephyr.cc
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <kernel.h>
-
-#include "pw_assert/assert.h"
-#include "pw_preprocessor/compiler.h"
-
-extern "C" void pw_assert_HandleFailure(void) {
-  k_panic();
-  PW_UNREACHABLE;
-}
diff --git a/pw_assert_zephyr/docs.rst b/pw_assert_zephyr/docs.rst
deleted file mode 100644
index a9313fd..0000000
--- a/pw_assert_zephyr/docs.rst
+++ /dev/null
@@ -1,17 +0,0 @@
-.. _module-pw_assert_zephyr:
-
-================
-pw_assert_zephyr
-================
-
---------
-Overview
---------
-This assert backend implements the ``pw_assert`` facade, by routing the assert
-message to the Zephyr assert subsystem. Failed asserts will call ``k_panic()``.
-To enable the assert module, set ``CONFIG_PIGWEED_ASSERT=y``. After that,
-Zephyr's assert configs can be used to control the behavior via CONFIG_ASSERT_
-and CONFIG_ASSERT_LEVEL_.
-
-.. _CONFIG_ASSERT: https://docs.zephyrproject.org/latest/reference/kconfig/CONFIG_ASSERT.html#std-kconfig-CONFIG_ASSERT
-.. _CONFIG_ASSERT_LEVEL: https://docs.zephyrproject.org/latest/reference/kconfig/CONFIG_ASSERT_LEVEL.html#std-kconfig-CONFIG_ASSERT_LEVEL
\ No newline at end of file
diff --git a/pw_assert_zephyr/public/pw_assert_zephyr/assert_zephyr.h b/pw_assert_zephyr/public/pw_assert_zephyr/assert_zephyr.h
deleted file mode 100644
index 40c4e16..0000000
--- a/pw_assert_zephyr/public/pw_assert_zephyr/assert_zephyr.h
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// This override header merely points to the true backend, in this case the
-// basic one. The reason to redirect is to permit the use of multiple backends
-// (though only pw_assert/check.h can only point to 1 backend).
-#pragma once
-
-#include <sys/__assert.h>
-
-#include "pw_assert/assert.h"
-
-#define PW_HANDLE_CRASH(...)        \
-  {                                 \
-    __ASSERT_MSG_INFO(__VA_ARGS__); \
-    pw_assert_HandleFailure();      \
-  }
-
-#define PW_HANDLE_ASSERT_FAILURE(condition_string, ...)                    \
-  {                                                                        \
-    __ASSERT_MSG_INFO("Check failed: " condition_string ". " __VA_ARGS__); \
-    __ASSERT_POST_ACTION();                                                \
-  }
-
-#define PW_HANDLE_ASSERT_BINARY_COMPARE_FAILURE(arg_a_str,                 \
-                                                arg_a_val,                 \
-                                                comparison_op_str,         \
-                                                arg_b_str,                 \
-                                                arg_b_val,                 \
-                                                type_fmt,                  \
-                                                message,                   \
-                                                ...)                       \
-  PW_HANDLE_ASSERT_FAILURE(arg_a_str " (=" type_fmt ") " comparison_op_str \
-                                     " " arg_b_str " (=" type_fmt ")",     \
-                           message,                                        \
-                           arg_a_val,                                      \
-                           arg_b_val PW_COMMA_ARGS(__VA_ARGS__))
diff --git a/pw_assert_zephyr/public_overrides/pw_assert_backend/assert_backend.h b/pw_assert_zephyr/public_overrides/pw_assert_backend/assert_backend.h
deleted file mode 100644
index 63e5ab0..0000000
--- a/pw_assert_zephyr/public_overrides/pw_assert_backend/assert_backend.h
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// This override header merely points to the true backend, in this case the
-// basic one. The reason to redirect is to permit the use of multiple backends
-// (though only pw_assert/check.h can only point to 1 backend).
-#pragma once
-
-#include "pw_assert_zephyr/assert_zephyr.h"
diff --git a/pw_base64/BUILD b/pw_base64/BUILD
new file mode 100644
index 0000000..f30a5bc
--- /dev/null
+++ b/pw_base64/BUILD
@@ -0,0 +1,49 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_base64",
+    srcs = [
+        "base64.cc",
+    ],
+    hdrs = [
+        "public/pw_base64/base64.h",
+    ],
+    includes = ["public"],
+    deps = [
+        "//pw_span",
+    ],
+)
+
+pw_cc_test(
+    name = "base64_test",
+    srcs = [
+        "base64_test_c.c",
+        "base64_test.cc",
+    ],
+    deps = [
+        ":pw_base64",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_base64/BUILD.bazel b/pw_base64/BUILD.bazel
deleted file mode 100644
index 20c7b0d..0000000
--- a/pw_base64/BUILD.bazel
+++ /dev/null
@@ -1,49 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_base64",
-    srcs = [
-        "base64.cc",
-    ],
-    hdrs = [
-        "public/pw_base64/base64.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_span",
-    ],
-)
-
-pw_cc_test(
-    name = "base64_test",
-    srcs = [
-        "base64_test.cc",
-        "base64_test_c.c",
-    ],
-    deps = [
-        ":pw_base64",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_base64/OWNERS b/pw_base64/OWNERS
deleted file mode 100644
index d96cbc6..0000000
--- a/pw_base64/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-hepler@google.com
diff --git a/pw_base64/base64.cc b/pw_base64/base64.cc
index 77baf37..7b2396c 100644
--- a/pw_base64/base64.cc
+++ b/pw_base64/base64.cc
@@ -26,7 +26,7 @@
 constexpr char kPadding = '=';
 
 // Table that encodes a 6-bit pattern as a Base64 character
-constexpr char kEncodeTable[64] = {
+constexpr char encode_bits[64] = {
     'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',     'L',    'M',
     'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',     'Y',    'Z',
     'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',     'l',    'm',
@@ -34,18 +34,16 @@
     '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', kChar62, kChar63};
 
 constexpr char BitGroup0Char(uint8_t byte0) {
-  return kEncodeTable[(byte0 & 0b11111100) >> 2];
+  return encode_bits[(byte0 & 0b11111100) >> 2];
 }
 constexpr char BitGroup1Char(uint8_t byte0, uint8_t byte1 = 0) {
-  return kEncodeTable[((byte0 & 0b00000011) << 4) |
-                      ((byte1 & 0b11110000) >> 4)];
+  return encode_bits[((byte0 & 0b00000011) << 4) | ((byte1 & 0b11110000) >> 4)];
 }
 constexpr char BitGroup2Char(uint8_t byte1, uint8_t byte2 = 0) {
-  return kEncodeTable[((byte1 & 0b00001111) << 2) |
-                      ((byte2 & 0b11000000) >> 6)];
+  return encode_bits[((byte1 & 0b00001111) << 2) | ((byte2 & 0b11000000) >> 6)];
 }
 constexpr char BitGroup3Char(uint8_t byte2) {
-  return kEncodeTable[byte2 & 0b00111111];
+  return encode_bits[byte2 & 0b00111111];
 }
 
 // Decoding functions
@@ -56,7 +54,7 @@
 // Table that decodes a Base64 character to its 6-bit value. Supports the
 // standard (+/) and URL-safe (-_) alphabets. Starts from the lowest-value valid
 // character, which is +.
-constexpr uint8_t kDecodeTable[] = {
+constexpr uint8_t decode_char[] = {
     62, kX, 62, kX, 63, 52, 53, 54, 55, 56,  //  0 - 09
     57, 58, 59, 60, 61, kX, kX, kX, 0,  kX,  // 10 - 19
     kX, kX, 0,  1,  2,  3,  4,  5,  6,  7,   // 20 - 29
@@ -68,7 +66,7 @@
 };
 
 constexpr uint8_t CharToBits(char ch) {
-  return kDecodeTable[ch - kMinValidChar];
+  return decode_char[ch - kMinValidChar];
 }
 
 constexpr uint8_t Byte0(uint8_t bits0, uint8_t bits1) {
diff --git a/pw_base64/base64_test.cc b/pw_base64/base64_test.cc
index 681a092..17e5348 100644
--- a/pw_base64/base64_test.cc
+++ b/pw_base64/base64_test.cc
@@ -152,24 +152,104 @@
 };
 
 constexpr EncodedData kRandomTestData[] = {
+    {2, "\x63\xa9", "Y6k="},
+    {2, "\xa1\x49", "oUk="},
+    {2, "\x14\x58", "FFg="},
+    {2, "\x5d\xa2", "XaI="},
+    {2, "\x7c\x80", "fIA="},
+    {2, "\xc1\xbb", "wbs="},
+    {2, "\x08\x00", "CAA="},
+    {2, "\xd8\x88", "2Ig="},
     {2, "\x74\x6d", "dG0="},
     {2, "\x22\x86", "IoY="},
+    {3, "\x69\x89\x03", "aYkD"},
+    {3, "\x6c\xcb\xc5", "bMvF"},
+    {3, "\x72\x36\x8b", "cjaL"},
+    {3, "\xd3\xdc\xe0", "09zg"},
+    {3, "\x5d\x1f\x8a", "XR+K"},
+    {3, "\x0d\xc0\x5b", "DcBb"},
+    {3, "\xe3\x11\x1e", "4xEe"},
+    {3, "\xbc\x3c\xb9", "vDy5"},
     {3, "\xc0\xa2\x1c", "wKIc"},
     {3, "\xa9\x67\xfb", "qWf7"},
+    {4, "\x80\xf5\xc8\xd4", "gPXI1A=="},
+    {4, "\xa3\x54\x4a\xfa", "o1RK+g=="},
+    {4, "\x69\xdb\x14\x4c", "adsUTA=="},
+    {4, "\x95\x20\x23\x1a", "lSAjGg=="},
+    {4, "\xb9\x2c\x00\x11", "uSwAEQ=="},
+    {4, "\xef\xeb\x23\x44", "7+sjRA=="},
+    {4, "\xcf\xa9\xe6\x85", "z6nmhQ=="},
+    {4, "\xc5\xe0\x36\xde", "xeA23g=="},
     {4, "\x77\xe1\x63\x51", "d+FjUQ=="},
     {4, "\x7d\xa6\x8c\x5e", "faaMXg=="},
+    {5, "\x6e\xb8\x91\x3f\xac", "briRP6w="},
+    {5, "\xd1\x16\x7f\x1d\xef", "0RZ/He8="},
+    {5, "\x42\x95\xfb\x24\xee", "QpX7JO4="},
+    {5, "\x19\xfd\xe5\x96\xc1", "Gf3llsE="},
+    {5, "\x42\x5a\xb3\xfe\x13", "Qlqz/hM="},
+    {5, "\x2b\xf7\x1a\xcc\x13", "K/cazBM="},
+    {5, "\xba\x8f\x0d\xf7\xc1", "uo8N98E="},
+    {5, "\x28\xa6\x77\x2d\xfc", "KKZ3Lfw="},
     {5, "\x68\xaa\x19\x59\xd0", "aKoZWdA="},
     {5, "\x46\x73\xd3\x54\x7e", "RnPTVH4="},
+    {6, "\x1f\x88\x91\xbb\xd7\x10", "H4iRu9cQ"},
+    {6, "\x37\x23\x3b\x5a\x26\xe4", "NyM7Wibk"},
+    {6, "\xd2\xa0\xf4\x13\x91\xe6", "0qD0E5Hm"},
+    {6, "\x55\xe8\xe9\x06\x5d\xc3", "VejpBl3D"},
+    {6, "\xeb\xf5\xd8\x62\x3c\x5e", "6/XYYjxe"},
+    {6, "\xee\xad\x7e\xc4\x66\x83", "7q1+xGaD"},
+    {6, "\xbb\x07\x2c\x26\x3f\xb7", "uwcsJj+3"},
+    {6, "\xed\xf3\x34\x94\xab\x41", "7fM0lKtB"},
     {6, "\x3f\xe8\x18\x4c\xe8\xf4", "P+gYTOj0"},
     {6, "\x0a\xdd\x39\xbc\x1f\x65", "Ct05vB9l"},
+    {7, "\xac\xcf\xb2\xd5\xee\xa2\x8e", "rM+y1e6ijg=="},
+    {7, "\x78\x63\xeb\x3f\x07\xde\x04", "eGPrPwfeBA=="},
+    {7, "\x7a\xd7\x3b\x5c\x09\xc2\x93", "etc7XAnCkw=="},
+    {7, "\xd4\xe4\xda\xe3\xf3\x4d\xe9", "1OTa4/NN6Q=="},
+    {7, "\xa6\xc6\x7c\x47\xd5\xbe\xd3", "psZ8R9W+0w=="},
+    {7, "\x34\xad\x5d\x02\x47\xa1\x39", "NK1dAkehOQ=="},
+    {7, "\x33\x98\xd7\x02\x46\x4e\xad", "M5jXAkZOrQ=="},
+    {7, "\x08\x4d\x48\x48\xb1\x3d\x05", "CE1ISLE9BQ=="},
     {7, "\xc4\x5e\x4a\x6d\x4a\x04\xb6", "xF5KbUoEtg=="},
     {7, "\x12\xe9\xf4\xaa\x2e\x4c\x31", "Eun0qi5MMQ=="},
+    {8, "\xff\x15\x25\x7e\x7b\xc9\x7b\x60", "/xUlfnvJe2A="},
+    {8, "\xc7\xbb\x0b\x62\x5c\x62\x41\xc2", "x7sLYlxiQcI="},
+    {8, "\x48\x49\x6d\x7c\xca\xb7\xae\xed", "SEltfMq3ru0="},
+    {8, "\xfd\xec\x13\xd6\x93\x9f\xba\xe0", "/ewT1pOfuuA="},
+    {8, "\x7e\xff\xd2\xdd\x0e\xe2\x6c\x60", "fv/S3Q7ibGA="},
+    {8, "\xe5\xba\x41\x65\xa0\x46\x17\x27", "5bpBZaBGFyc="},
+    {8, "\xce\xec\xd5\x68\x3a\xb7\xb4\x16", "zuzVaDq3tBY="},
+    {8, "\xbe\x33\x9a\xc9\xfd\xcc\x29\xe8", "vjOayf3MKeg="},
     {8, "\x55\x8c\x60\xcc\xc4\x7d\x99\x1f", "VYxgzMR9mR8="},
     {8, "\xee\x21\x88\x2a\x0f\x7e\x76\xd7", "7iGIKg9+dtc="},
+    {9, "\xd5\xab\xd9\xa6\xae\xaa\x33\x9f\x66", "1avZpq6qM59m"},
+    {9, "\x6f\xe8\x06\xcf\xfd\x79\x3a\x4e\xdb", "b+gGz/15Ok7b"},
+    {9, "\x61\x00\x0a\x51\xad\x5b\xf1\xf9\x37", "YQAKUa1b8fk3"},
+    {9, "\x4f\x40\x0b\x79\x10\xa4\x12\x25\x3e", "T0ALeRCkEiU+"},
+    {9, "\xb1\x37\xb3\x41\x5b\xd7\xe8\xa4\xda", "sTezQVvX6KTa"},
+    {9, "\x82\xa5\x22\xd3\x48\xd8\xf7\x62\x7a", "gqUi00jY92J6"},
+    {9, "\xfd\x05\x33\x92\x2c\xd3\x85\x29\xa2", "/QUzkizThSmi"},
+    {9, "\x32\x93\x53\x06\x9c\xbb\x96\xbb\xf3", "MpNTBpy7lrvz"},
     {9, "\xba\x40\x1d\x06\x92\xce\xc2\x8a\x28", "ukAdBpLOwooo"},
     {9, "\xcc\x89\xf5\xeb\x49\x91\xa6\xa6\x88", "zIn160mRpqaI"},
+    {10, "\x6b\xfd\x95\xc5\x4a\xc7\xc2\x39\x45\xdc", "a/2VxUrHwjlF3A=="},
+    {10, "\x34\x50\xab\x78\xaf\x92\x47\x56\x8a\xb6", "NFCreK+SR1aKtg=="},
+    {10, "\x07\x14\x0a\xe8\x49\xca\x3a\x36\x80\xb0", "BxQK6EnKOjaAsA=="},
+    {10, "\xde\x79\x3d\xa7\xab\x22\xa9\xaa\xfc\x05", "3nk9p6siqar8BQ=="},
+    {10, "\x73\x62\x02\x77\x41\x91\xe6\x8b\x3f\x89", "c2ICd0GR5os/iQ=="},
+    {10, "\xf2\x09\xa9\x8b\x7c\x30\x26\x54\xf0\xd3", "8gmpi3wwJlTw0w=="},
+    {10, "\x32\xc8\xcc\xfc\x47\xa3\xac\x20\x37\x39", "MsjM/EejrCA3OQ=="},
+    {10, "\x32\x1b\x2b\x36\x07\x76\x90\xfa\xe0\x04", "MhsrNgd2kPrgBA=="},
     {10, "\x55\x6b\x11\xe4\xc2\x22\xb0\x40\x14\x53", "VWsR5MIisEAUUw=="},
     {10, "\xd3\x1e\xc4\xe5\x06\x60\x37\x51\x10\x48", "0x7E5QZgN1EQSA=="},
+    {11, "\x4c\xde\xee\xb8\x68\x0d\x9c\x66\x3e\xea\x46", "TN7uuGgNnGY+6kY="},
+    {11, "\x36\x79\x11\x5c\xce\xd0\xdf\x3c\xd2\xc9\x45", "NnkRXM7Q3zzSyUU="},
+    {11, "\xa0\x78\xc3\xc0\x79\xaf\xa1\xc3\xef\xd5\xf3", "oHjDwHmvocPv1fM="},
+    {11, "\xdd\x6b\x78\x18\x95\x80\x99\x7a\x02\x41\xe8", "3Wt4GJWAmXoCQeg="},
+    {11, "\x18\xfa\x19\xe0\xce\x3b\x0a\xa1\xec\x2b\x30", "GPoZ4M47CqHsKzA="},
+    {11, "\x74\xf2\x96\x90\x95\xbe\x14\x64\xbf\x10\xd9", "dPKWkJW+FGS/ENk="},
+    {11, "\x7f\xe8\x18\xab\xeb\x28\x86\xf1\x7c\x75\x47", "f+gYq+sohvF8dUc="},
+    {11, "\xa4\xc9\x62\x73\x0e\x89\xe1\x51\x8b\xf0\x96", "pMlicw6J4VGL8JY="},
     {11, "\x98\xae\x09\x8c\x61\x40\xbf\x77\xde\xd9\x0d", "mK4JjGFAv3fe2Q0="},
     {11, "\x86\x39\x06\xa1\xc6\xfc\xcf\x30\x21\xba\xdf", "hjkGocb8zzAhut8="},
 };
@@ -286,7 +366,6 @@
   EXPECT_STREQ("DO NOT TOUCH", buffer);
 
   EXPECT_EQ(0u, MaxDecodedSize(0));
-  // NOLINTNEXTLINE(bugprone-string-constructor)
   EXPECT_EQ(0u, Decode(std::string_view("nothing please", 0), buffer));
   EXPECT_STREQ("DO NOT TOUCH", buffer);
 }
diff --git a/pw_bloat/BUILD b/pw_bloat/BUILD
new file mode 100644
index 0000000..f0911f9
--- /dev/null
+++ b/pw_bloat/BUILD
@@ -0,0 +1,39 @@
+# Copyright 2019 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_binary",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+# Library which uses standard C/C++ functions such as memcpy to prevent them
+# from showing up within bloat diff reports.
+pw_cc_library(
+    name = "bloat_this_binary",
+    srcs = ["bloat_this_binary.cc"],
+    hdrs = ["public/pw_bloat/bloat_this_binary.h"],
+    includes = ["public"],
+)
+
+# Standard minimal base binary for bloat reports.
+pw_cc_binary(
+    name = "bloat_base",
+    srcs = ["base_main.cc"],
+    deps = [":bloat_this_binary"],
+)
diff --git a/pw_bloat/BUILD.bazel b/pw_bloat/BUILD.bazel
deleted file mode 100644
index 73c4628..0000000
--- a/pw_bloat/BUILD.bazel
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright 2019 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_binary",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-# Library which uses standard C/C++ functions such as memcpy to prevent them
-# from showing up within bloat diff reports.
-pw_cc_library(
-    name = "bloat_this_binary",
-    srcs = ["bloat_this_binary.cc"],
-    hdrs = ["public/pw_bloat/bloat_this_binary.h"],
-    includes = ["public"],
-    deps = [
-        "//pw_assert",
-        "//pw_log",
-    ],
-)
-
-# Standard minimal base binary for bloat reports.
-pw_cc_binary(
-    name = "bloat_base",
-    srcs = ["base_main.cc"],
-    deps = [":bloat_this_binary"],
-)
diff --git a/pw_bloat/OWNERS b/pw_bloat/OWNERS
deleted file mode 100644
index 3afb926..0000000
--- a/pw_bloat/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-frolv@google.com
diff --git a/pw_bloat/bloat.cmake b/pw_bloat/bloat.cmake
deleted file mode 100644
index 6a4e9c7..0000000
--- a/pw_bloat/bloat.cmake
+++ /dev/null
@@ -1,64 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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_guard(GLOBAL)
-
-include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-
-# This function creates a library under the specified ${NAME} which provides a
-# a generated bloaty configuration for a given ELF file using
-# pw_bloat.bloaty_config.
-#
-# Produces the ${OUTPUT} Bloaty McBloatface configuration file.
-#
-# Args:
-#
-#   NAME - name of the library to create
-#   ELF_FILE - The input ELF file to process using pw_bloat.bloaty_config
-#   OUTPUT - The output Bloaty McBloatface configuration file
-function(pw_bloaty_config NAME)
-  set(option_args)
-  set(one_value_args ELF_FILE OUTPUT)
-  set(multi_value_args)
-  _pw_parse_argv_strict(pw_bloaty_config
-      1 "${option_args}" "${one_value_args}" "${multi_value_args}"
-  )
-
-  if("${arg_ELF_FILE}" STREQUAL "")
-    message(FATAL_ERROR
-      "pw_bloaty_config requires an input ELF file in ELF_FILE. "
-      "No ELF_FILE was listed for ${NAME}")
-  endif()
-
-  if("${arg_OUTPUT}" STREQUAL "")
-    message(FATAL_ERROR
-      "pw_bloaty_config requires an output config file in OUTPUT. "
-      "No OUTPUT was listed for ${NAME}")
-  endif()
-
-  add_library(${NAME} INTERFACE)
-  add_dependencies(${NAME} INTERFACE ${NAME}_generated_config)
-
-  add_custom_command(
-    COMMENT "Generating ${NAME}'s ${arg_OUTPUT} for ${arg_ELF_FILE}."
-    COMMAND
-       ${Python3_EXECUTABLE}
-       "$ENV{PW_ROOT}/pw_bloat/py/pw_bloat/bloaty_config.py"
-       ${arg_ELF_FILE} -o ${arg_OUTPUT} -l warning
-    DEPENDS ${arg_ELF_FILE}
-    OUTPUT ${arg_OUTPUT} POST_BUILD
-  )
-  add_custom_target(${NAME}_generated_config
-    DEPENDS ${arg_OUTPUT}
-  )
-endfunction()
diff --git a/pw_bloat/bloat.gni b/pw_bloat/bloat.gni
index 661df32..432358c 100644
--- a/pw_bloat/bloat.gni
+++ b/pw_bloat/bloat.gni
@@ -34,9 +34,6 @@
   #
   # If this list is empty, pw_toolchain_size_report targets become no-ops.
   pw_bloat_TOOLCHAINS = []
-
-  # Controls whether to display size reports in the build output.
-  pw_bloat_SHOW_SIZE_REPORTS = false
 }
 
 # Creates a target which runs a size report diff on a set of executables.
@@ -116,9 +113,9 @@
 
       # Allow each binary to override the global bloaty config.
       if (defined(binary.bloaty_config)) {
-        _bloaty_configs += [ binary.bloaty_config ]
+        _bloaty_configs += [ rebase_path(binary.bloaty_config) ]
       } else {
-        _bloaty_configs += [ pw_bloat_BLOATY_CONFIG ]
+        _bloaty_configs += [ rebase_path(pw_bloat_BLOATY_CONFIG) ]
       }
 
       _binary_path += ";" + "<TARGET_FILE($_binary_base)>"
@@ -129,9 +126,9 @@
 
     _bloat_script_args = [
       "--bloaty-config",
-      string_join(";", rebase_path(_bloaty_configs, root_build_dir)),
+      string_join(";", _bloaty_configs),
       "--out-dir",
-      rebase_path(target_gen_dir, root_build_dir),
+      rebase_path(target_gen_dir),
       "--target",
       target_name,
       "--title",
@@ -165,7 +162,7 @@
         }
         script = "$dir_pw_bloat/py/pw_bloat/no_bloaty.py"
         python_deps = [ "$dir_pw_bloat/py" ]
-        args = [ rebase_path(_doc_rst_output, root_build_dir) ]
+        args = [ rebase_path(_doc_rst_output) ]
         outputs = [ _doc_rst_output ]
       }
 
@@ -183,14 +180,14 @@
         python_deps = [ "$dir_pw_bloat/py" ]
         inputs = _bloaty_configs
         outputs = [
-          "${_doc_rst_output}.txt",
+          "$target_gen_dir/${target_name}.txt",
           _doc_rst_output,
         ]
         deps = _all_target_dependencies
         args = _bloat_script_args + _binary_paths
 
-        # Print size reports to stdout when they are generated, if requested.
-        capture_output = !pw_bloat_SHOW_SIZE_REPORTS
+        # Print size reports to stdout when they are generated.
+        capture_output = false
       }
     }
   } else {
@@ -250,8 +247,7 @@
     _linker_script_target_name = "${_prefix}_linker_script"
     config(_linker_script_target_name) {
       if (defined(_toolchain.linker_script)) {
-        ldflags =
-            [ "-T" + rebase_path(_toolchain.linker_script, root_build_dir) ]
+        ldflags = [ "-T" + rebase_path(_toolchain.linker_script) ]
         inputs = [ _toolchain.linker_script ]
       } else {
         ldflags = []
@@ -328,7 +324,7 @@
       }
       script = "$dir_pw_bloat/py/pw_bloat/no_toolchains.py"
       python_deps = [ "$dir_pw_bloat/py" ]
-      args = [ rebase_path(_doc_rst_output, root_build_dir) ]
+      args = [ rebase_path(_doc_rst_output) ]
       outputs = [ _doc_rst_output ]
     }
   }
diff --git a/pw_bloat/bloat_this_binary.cc b/pw_bloat/bloat_this_binary.cc
index 2dbf47d..bf09f22 100644
--- a/pw_bloat/bloat_this_binary.cc
+++ b/pw_bloat/bloat_this_binary.cc
@@ -17,7 +17,7 @@
 #include <cstring>
 
 #include "pw_assert/assert.h"
-#include "pw_assert/check.h"
+#include "pw_assert/light.h"
 #include "pw_log/log.h"
 
 namespace pw::bloat {
@@ -25,7 +25,7 @@
 char* volatile non_optimizable_pointer;
 
 void BloatThisBinary() {
-  [[maybe_unused]] volatile unsigned counter = 0;
+  volatile unsigned counter = 0;
 
   // In case someone accidentally ends up flashing and running a bloat
   // executable on their device, loop forever instead of running this code.
diff --git a/pw_bloat/docs.rst b/pw_bloat/docs.rst
index a5f9c3d..8c00345 100644
--- a/pw_bloat/docs.rst
+++ b/pw_bloat/docs.rst
@@ -3,10 +3,9 @@
 --------
 pw_bloat
 --------
-The bloat module provides tools and helpers around using
-`Bloaty McBloatface <https://github.com/google/bloaty>`_ including generating
-generate size report cards for output binaries through Pigweed's GN build
-system.
+The bloat module provides tools to generate size report cards for output
+binaries using `Bloaty McBloatface <https://github.com/google/bloaty>`_ and
+Pigweed's GN build system.
 
 Bloat report cards allow tracking the memory usage of a system over time as code
 changes are made and provide a breakdown of which parts of the code have the
@@ -41,11 +40,11 @@
     sources = [ "empty_main.cc" ]
   }
 
-  executable("hello_world_printf") {
+  exectuable("hello_world_printf") {
     sources = [ "hello_printf.cc" ]
   }
 
-  executable("hello_world_iostream") {
+  exectuable("hello_world_iostream") {
     sources = [ "hello_iostream.cc" ]
   }
 
@@ -64,10 +63,9 @@
     ]
   }
 
-Size reports are typically included in ReST documentation, as described in
-`Documentation integration`_. Size reports may also be printed in the build
-output if desired. To enable this in the GN build, set the
-``pw_bloat_SHOW_SIZE_REPORTS`` build arg to ``true``.
+When the size report target runs, it will print its report card to stdout.
+Additionally, size report targets also generate ReST output, which is described
+below.
 
 Documentation integration
 =========================
@@ -98,202 +96,3 @@
 Simple bloat function example
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 .. include:: examples/simple_bloat_function
-
-Additional Bloaty data sources
-==============================
-`Bloaty McBloatface <https://github.com/google/bloaty>`_ by itself cannot help
-answer some questions which embedded developers frequently face such as
-understanding how much space is left. To address this, Pigweed provides Python
-tooling (``pw_bloat.bloaty_config``) to generate bloaty configuration files
-based on the final ELF files through small tweaks in the linker scripts to
-expose extra information.
-
-See the sections below on how to enable the additional data sections through
-modifications in your linker script(s).
-
-As an example to generate the helper configuration which enables additional data
-sources for ``example.elf`` if you've updated your linker script(s) accordingly,
-simply run
-``python -m pw_bloaty.bloaty_config example.elf > example.bloaty``. The
-``example.bloaty``  can then be used with bloaty using the ``-c`` flag, for
-example
-``bloaty -c example.bloaty example.elf --domain vm -d memoryregions,utilization``
-which may return something like:
-
-.. code-block::
-
-    84.2%  1023Ki    FLASH
-      94.2%   963Ki    Free space
-       5.8%  59.6Ki    Used space
-    15.8%   192Ki    RAM
-     100.0%   192Ki    Used space
-     0.0%     512    VECTOR_TABLE
-      96.9%     496    Free space
-       3.1%      16    Used space
-     0.0%       0    Not resident in memory
-       NAN%       0    Used space
-
-
-``utilization`` data source
-^^^^^^^^^^^^^^^^^^^^^^^^^^^
-The most common question many embedded developers face when using ``bloaty`` is
-how much space you are using and how much space is left. To correctly answer
-this, section sizes must be used in order to correctly account for section
-alignment requirements.
-
-The generated ``utilization`` data source will work with any ELF file, where
-``Used Space`` is reported for the sum of virtual memory size of all sections.
-
-In order for ``Free Space`` to be reported, your linker scripts must include
-properly aligned sections which span the unused remaining space for the relevant
-memory region with the ``unused_space`` string anywhere in their name. This
-typically means creating a trailing section which is pinned to span to the end
-of the memory region.
-
-For example imagine this partial example GNU LD linker script:
-
-.. code-block::
-
-  MEMORY
-  {
-    FLASH(rx) : \
-      ORIGIN = PW_BOOT_FLASH_BEGIN, \
-      LENGTH = PW_BOOT_FLASH_SIZE
-    RAM(rwx) : \
-      ORIGIN = PW_BOOT_RAM_BEGIN, \
-      LENGTH = PW_BOOT_RAM_SIZE
-  }
-
-  SECTIONS
-  {
-    /* Main executable code. */
-    .code : ALIGN(8)
-    {
-      /* Application code. */
-      *(.text)
-      *(.text*)
-      KEEP(*(.init))
-      KEEP(*(.fini))
-
-      . = ALIGN(8);
-      /* Constants.*/
-      *(.rodata)
-      *(.rodata*)
-    } >FLASH
-
-    /* Explicitly initialized global and static data. (.data)*/
-    .static_init_ram : ALIGN(8)
-    {
-      *(.data)
-      *(.data*)
-      . = ALIGN(8);
-    } >RAM AT> FLASH
-
-    /* Zero initialized global/static data. (.bss) */
-    .zero_init_ram : ALIGN(8)
-    {
-      *(.bss)
-      *(.bss*)
-      *(COMMON)
-      . = ALIGN(8);
-    } >RAM
-  }
-
-Could be modified as follows enable ``Free Space`` reporting:
-
-.. code-block::
-
-  MEMORY
-  {
-    FLASH(rx) : ORIGIN = PW_BOOT_FLASH_BEGIN, LENGTH = PW_BOOT_FLASH_SIZE
-    RAM(rwx) : ORIGIN = PW_BOOT_RAM_BEGIN, LENGTH = PW_BOOT_RAM_SIZE
-  }
-
-  SECTIONS
-  {
-    /* Main executable code. */
-    .code : ALIGN(8)
-    {
-      /* Application code. */
-      *(.text)
-      *(.text*)
-      KEEP(*(.init))
-      KEEP(*(.fini))
-
-      . = ALIGN(8);
-      /* Constants.*/
-      *(.rodata)
-      *(.rodata*)
-    } >FLASH
-
-    /* Explicitly initialized global and static data. (.data)*/
-    .static_init_ram : ALIGN(8)
-    {
-      *(.data)
-      *(.data*)
-      . = ALIGN(8);
-    } >RAM AT> FLASH
-
-    /* Zero initialized global/static data. (.bss). */
-    .zero_init_ram : ALIGN(8)
-    {
-      *(.bss)
-      *(.bss*)
-      *(COMMON)
-      . = ALIGN(8);
-    } >RAM
-
-    /*
-     * Do not declare any output sections after this comment. This area is
-     * reserved only for declaring unused sections of memory. These sections are
-     * used by pw_bloat.bloaty_config to create the utilization data source for
-     * bloaty.
-     */
-    .FLASH.unused_space (NOLOAD) : ALIGN(8)
-    {
-      . = ABSOLUTE(ORIGIN(FLASH) + LENGTH(FLASH));
-    } >FLASH
-
-    .RAM.unused_space (NOLOAD) : ALIGN(8)
-    {
-      . = ABSOLUTE(ORIGIN(RAM) + LENGTH(RAM));
-    } >RAM
-  }
-
-``memoryregions`` data source
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-Understanding how symbols, sections, and other data sources can be attributed
-back to the memory regions defined in your linker script is another common
-problem area. Unfortunately the ELF format does not include the original memory
-regions, meaning ``bloaty`` can not do this today by itself. In addition, it's
-relatively common that there are multiple memory regions which alias to the same
-memory but through different buses which could make attribution difficult.
-
-Instead of taking the less portable and brittle approach to parse ``*.map``
-files, ``pw_bloat.bloaty_config`` consumes symbols which are defined in the
-linker script with a special format to extract this information from the ELF
-file: ``pw_bloat_config_memory_region_NAME_{start,end}{_N,}``.
-
-These symbols are then used to determine how to map segments to these memory
-regions. Note that segments must be used in order to account for inter-section
-padding which are not attributed against any sections.
-
-As an example, if you have a single view in the single memory region named
-``FLASH``, then you should produce the following two symbols in your linker
-script:
-
-.. code-block::
-
-  pw_bloat_config_memory_region_FLASH_start = ORIGIN(FLASH);
-  pw_bloat_config_memory_region_FLASH_end = ORIGIN(FLASH) + LENGTH(FLASH);
-
-As another example, if you have two aliased memory regions (``DCTM`` and
-``ITCM``) into the same effective memory named you'd like to call ``RAM``, then
-you should produce the following four symbols in your linker script:
-
-.. code-block::
-
-  pw_bloat_config_memory_region_RAM_start_0 = ORIGIN(ITCM);
-  pw_bloat_config_memory_region_RAM_end_0 = ORIGIN(ITCM) + LENGTH(ITCM);
-  pw_bloat_config_memory_region_RAM_start_1 = ORIGIN(DTCM);
-  pw_bloat_config_memory_region_RAM_end_1 = ORIGIN(DTCM) + LENGTH(DTCM);
diff --git a/pw_bloat/examples/BUILD b/pw_bloat/examples/BUILD
new file mode 100644
index 0000000..5edf70b
--- /dev/null
+++ b/pw_bloat/examples/BUILD
@@ -0,0 +1,34 @@
+# Copyright 2019 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load("//pw_build:pigweed.bzl", "pw_cc_binary")
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_binary(
+    name = "simple_base",
+    srcs = ["simple_base.cc"],
+)
+
+pw_cc_binary(
+    name = "simple_loop",
+    srcs = ["simple_loop.cc"],
+)
+
+pw_cc_binary(
+    name = "simple_function",
+    srcs = ["simple_function.cc"],
+)
diff --git a/pw_bloat/examples/BUILD.bazel b/pw_bloat/examples/BUILD.bazel
deleted file mode 100644
index 0fea6ad..0000000
--- a/pw_bloat/examples/BUILD.bazel
+++ /dev/null
@@ -1,34 +0,0 @@
-# Copyright 2019 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load("//pw_build:pigweed.bzl", "pw_cc_binary")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_binary(
-    name = "simple_base",
-    srcs = ["simple_base.cc"],
-)
-
-pw_cc_binary(
-    name = "simple_loop",
-    srcs = ["simple_loop.cc"],
-)
-
-pw_cc_binary(
-    name = "simple_function",
-    srcs = ["simple_function.cc"],
-)
diff --git a/pw_bloat/py/BUILD.gn b/pw_bloat/py/BUILD.gn
index 028e4e1..c6d5ea3 100644
--- a/pw_bloat/py/BUILD.gn
+++ b/pw_bloat/py/BUILD.gn
@@ -17,21 +17,15 @@
 import("$dir_pw_build/python.gni")
 
 pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
+  setup = [ "setup.py" ]
   sources = [
     "pw_bloat/__init__.py",
     "pw_bloat/binary_diff.py",
     "pw_bloat/bloat.py",
     "pw_bloat/bloat_output.py",
-    "pw_bloat/bloaty_config.py",
     "pw_bloat/no_bloaty.py",
     "pw_bloat/no_toolchains.py",
   ]
-  tests = [ "bloaty_config_test.py" ]
   pylintrc = "$dir_pigweed/.pylintrc"
   python_deps = [ "$dir_pw_cli/py" ]
 }
diff --git a/pw_bloat/py/bloaty_config_test.py b/pw_bloat/py/bloaty_config_test.py
deleted file mode 100644
index 340ffe2..0000000
--- a/pw_bloat/py/bloaty_config_test.py
+++ /dev/null
@@ -1,114 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests for bloaty configuration tooling."""
-
-import unittest
-
-from pw_bloat import bloaty_config
-
-
-class BloatyConfigTest(unittest.TestCase):
-    """Tests that the bloaty config tool produces the expected config."""
-    def test_map_segments_to_memory_regions(self) -> None:
-        """Ensures the mapping works correctly based on a real example."""
-        segments = {
-            3: (int(0x800f268), int(0x8100200)),
-            5: (int(0x20004650), int(0x20020650)),
-            6: (int(0x20020650), int(0x20030000)),
-            1: (int(0x8000200), int(0x800f060)),
-            4: (int(0x20000208), int(0x20004650)),
-            2: (int(0x20000000), int(0x20000208)),
-            0: (int(0x8000000), int(0x8000200)),
-        }
-        memory_regions = {
-            'FLASH': {
-                0: (int(0x8000200), int(0x8100200))
-            },
-            'RAM': {
-                0: (int(0x20000000), int(0x20030000))
-            },
-            'VECTOR_TABLE': {
-                0: (int(0x8000000), int(0x8000200))
-            },
-        }
-        expected = {
-            3: 'FLASH',
-            5: 'RAM',
-            6: 'RAM',
-            1: 'FLASH',
-            4: 'RAM',
-            2: 'RAM',
-            0: 'VECTOR_TABLE',
-        }
-        actual = bloaty_config.map_segments_to_memory_regions(
-            segments=segments, memory_regions=memory_regions)
-        self.assertEqual(expected, actual)
-
-    def test_generate_memoryregions_data_source(self) -> None:
-        """Ensures the formatted generation works correctly."""
-        segments_to_memory_regions = {
-            0: 'RAM',
-            1: 'RAM',
-            13: 'FLASH',
-        }
-        config = bloaty_config.generate_memoryregions_data_source(
-            segments_to_memory_regions)
-        expected = '\n'.join((
-            r'custom_data_source: {',
-            r'  name: "memoryregions"',
-            r'  base_data_source: "segments"',
-            r'  rewrite: {',
-            r'    pattern:"^LOAD #0 \\[.*\\]$"',
-            r'    replacement:"RAM"',
-            r'  }',
-            r'  rewrite: {',
-            r'    pattern:"^LOAD #1 \\[.*\\]$"',
-            r'    replacement:"RAM"',
-            r'  }',
-            r'  rewrite: {',
-            r'    pattern:"^LOAD #13 \\[.*\\]$"',
-            r'    replacement:"FLASH"',
-            r'  }',
-            r'  rewrite: {',
-            r'    pattern:".*"',
-            r'    replacement:"Not resident in memory"',
-            r'  }',
-            r'}',
-            r'',
-        ))
-        self.assertEqual(expected, config)
-
-    def test_generate_utilization_data_source(self) -> None:
-        config = bloaty_config.generate_utilization_data_source()
-        expected = '\n'.join((
-            'custom_data_source: {',
-            '  name:"utilization"',
-            '  base_data_source:"sections"',
-            '  rewrite: {',
-            '    pattern:"unused_space"',
-            '    replacement:"Free space"',
-            '  }',
-            '  rewrite: {',
-            '    pattern:".*"',
-            '    replacement:"Used space"',
-            '  }',
-            '}',
-            '',
-        ))
-        self.assertEqual(expected, config)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_bloat/py/pw_bloat/bloat.py b/pw_bloat/py/pw_bloat/bloat.py
index d62f233..ee79d79 100755
--- a/pw_bloat/py/pw_bloat/bloat.py
+++ b/pw_bloat/py/pw_bloat/bloat.py
@@ -199,10 +199,6 @@
     write_file(f'{args.target}.txt', complete_output)
     print(complete_output)
 
-    # TODO(frolv): Remove when custom output for full mode is added.
-    if args.full:
-        write_file(f'{args.target}', complete_output)
-
     return 0
 
 
diff --git a/pw_bloat/py/pw_bloat/bloaty_config.py b/pw_bloat/py/pw_bloat/bloaty_config.py
deleted file mode 100644
index d5a38dc..0000000
--- a/pw_bloat/py/pw_bloat/bloaty_config.py
+++ /dev/null
@@ -1,339 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Generates a useful bloaty config file containing new data sources."""
-
-import argparse
-import logging
-import re
-import sys
-from typing import BinaryIO, Dict, List, Optional, TextIO
-
-import pw_cli.argument_types
-from elftools.elf import elffile  # type: ignore
-
-_LOG = logging.getLogger('bloaty_config')
-
-# 'pw_bloat_config_memory_region_NAME_{start,end}{_N,}' where _N defaults to 0.
-_MEMORY_REGION_SYMBOL_RE = re.compile(
-    r'pw_bloat_config_memory_region_' +
-    r'(?P<name>\w+)_(?P<limit>(start|end))(_(?P<index>\d+))?')
-
-
-def _parse_args() -> argparse.Namespace:
-    """Return a CLI argument parser for this module."""
-    parser = argparse.ArgumentParser(
-        description='Generates useful bloaty configurations entries',
-        epilog='Hint: try this:\n'
-        '   python -m pw_bloat.bloaty_config my_app.elf -o my_app.bloat')
-    parser.add_argument('elf_file', type=argparse.FileType('rb'))
-    parser.add_argument('--output',
-                        '-o',
-                        type=argparse.FileType('w'),
-                        help='The generated bloaty configuration',
-                        default=sys.stdout)
-    parser.add_argument(
-        '--utilization',
-        action=argparse.BooleanOptionalAction,
-        default=True,
-        help=(
-            'Generate the utilization custom_data_source based on sections ' +
-            'with "unused_space" in anywhere in their name'))
-    parser.add_argument(
-        '--memoryregions',
-        action=argparse.BooleanOptionalAction,
-        default=True,
-        help=('Generate the memoryregions custom_data_source based on ' +
-              'symbols defined in the linker script matching the following ' +
-              'pattern: ' +
-              '"pw::bloat::config::memory_region::NAME[0].{start,end}"'))
-    parser.add_argument('-l',
-                        '--loglevel',
-                        type=pw_cli.argument_types.log_level,
-                        default=logging.INFO,
-                        help='Set the log level'
-                        '(debug, info, warning, error, critical)')
-    return parser.parse_args()
-
-
-def _parse_memory_regions(parsed_elf_file: elffile.ELFFile) -> Optional[Dict]:
-    """
-    Search for the special pw::bloat::config symbols in the ELF binary.
-
-    This produces a dictionary which looks like:
-      {
-        MEMORY_REGION_NAME_0:{
-          0:(VM_START_ADDRESS, VM_END_ADDRESS)
-          ...
-          N:(VM_START_ADDRESS, VM_END_ADDRESS)
-        }
-        ...
-        MEMORY_REGION_NAME_M:{
-          0:(VM_START_ADDRESS, VM_END_ADDRESS)
-          ...
-          K:(VM_START_ADDRESS, VM_END_ADDRESS)
-        }
-      }
-    """
-    symtab_section = parsed_elf_file.get_section_by_name('.symtab')
-    assert symtab_section
-
-    # Produces an initial dictionary which looks like:
-    #  {
-    #    MEMORY_REGION_NAME_0:{
-    #      0:{ 'start':vm_start_address, 'end':vm_end_address }
-    #      ...
-    #      N:{ 'start':vm_start_address, 'end':vm_end_address }
-    #    }
-    #    ...
-    #    MEMORY_REGION_NAME_M:{
-    #      0:{ 'start':vm_start_address, 'end':vm_end_address }
-    #      ...
-    #      K:{ 'start':vm_start_address, 'end':vm_end_address }
-    #    }
-    #  }
-    memory_regions: Dict = {}
-    for symbol in symtab_section.iter_symbols():
-        match = _MEMORY_REGION_SYMBOL_RE.match(symbol.name)
-        if not match:
-            continue
-
-        name = match.group('name')
-        limit = match.group('limit')
-        if match.group('index'):
-            index = int(match.group('index'))
-        else:
-            index = 0
-        if name not in memory_regions:
-            memory_regions[name] = {}
-        memory_region = memory_regions[name]
-        if index not in memory_region:
-            memory_region[index] = {}
-        memory_region_segment = memory_region[index]
-        memory_region_segment[limit] = symbol.entry.st_value
-
-    # If the user did not provide a single pw::bloat::config symbol in the ELF
-    # binary then bail out and do nothing.
-    if not memory_regions:
-        _LOG.info('No valid pw::bloat::config::memory_region::* symbols found')
-        return None
-
-    # Ensure all memory regions' ranges have an end and start.
-    missing_range_limits = False
-    for region_name, ranges in memory_regions.items():
-        for index, limits in ranges.items():
-            if 'start' not in limits:
-                missing_range_limits = True
-                _LOG.error('%s[%d] is missing the start address', region_name,
-                           index)
-            if 'end' not in limits:
-                missing_range_limits = True
-                _LOG.error('%s[%d] is missing the end address', region_name,
-                           index)
-    if missing_range_limits:
-        _LOG.error('Invalid memory regions detected: missing ranges')
-        return None
-
-    # Translate the initial memory_regions dictionary to the tupled return
-    # format, i.e. (start, end) values in the nested dictionary.
-    tupled_memory_regions: Dict = {}
-    for region_name, ranges in memory_regions.items():
-        if region_name not in tupled_memory_regions:
-            tupled_memory_regions[region_name] = {}
-        for index, limits in ranges.items():
-            tupled_memory_regions[region_name][index] = (limits['start'],
-                                                         limits['end'])
-
-    # Ensure the memory regions do not overlap.
-    if _memory_regions_overlap(tupled_memory_regions):
-        _LOG.error('Invalid memory regions detected: overlaps detected')
-        return None
-
-    return tupled_memory_regions
-
-
-def _parse_segments(parsed_elf_file: elffile.ELFFile) -> Dict:
-    """
-    Report all of the segment information from the ELF binary.
-
-    Iterates over all of the segments in the ELF file's program header and
-    reports where they reside in virtual memory through a dictionary which
-    looks like:
-      {
-        0:(start_vmaddr,end_vmaddr),
-        ...
-        N:(start_vmaddr,end_vmaddr),
-      }
-    """
-    segments = {}
-    for i in range(parsed_elf_file.num_segments()):
-        segment = parsed_elf_file.get_segment(i)
-        start_vmaddr = segment['p_vaddr']
-        memory_size = segment['p_memsz']
-        if memory_size == 0:
-            continue  # Not a loaded segment which resides in virtual memory.
-        end_vmaddr = start_vmaddr + memory_size
-        segments[i] = (start_vmaddr, end_vmaddr)
-    return segments
-
-
-def _memory_regions_overlap(memory_regions: Dict) -> bool:
-    """Returns where any memory regions overlap each other."""
-    overlaps_detected = False
-    for current_name, current_ranges in memory_regions.items():
-        for current_index, (current_start,
-                            current_end) in current_ranges.items():
-            for other_name, other_ranges in memory_regions.items():
-                for other_index, (other_start,
-                                  other_end) in other_ranges.items():
-                    if (current_name == other_name
-                            and current_index == other_index):
-                        continue  # Skip yourself.
-                    # Check if the other region end is within this region.
-                    other_end_overlaps = (current_start < other_end <=
-                                          current_end)
-                    other_start_overlaps = (current_start <= other_start <
-                                            current_end)
-                    if other_end_overlaps or other_start_overlaps:
-                        overlaps_detected = True
-                        _LOG.error(f'error: {current_name}[{current_index}] ' +
-                                   f'[{hex(current_start)},' +
-                                   f'{hex(current_end)}] overlaps with ' +
-                                   f'{other_name}[{other_index}] '
-                                   f'[{hex(other_start)},' +
-                                   f'{hex(other_end)}] overlaps with ')
-    return overlaps_detected
-
-
-def _get_segments_to_memory_region_map(elf_file: BinaryIO) -> Optional[Dict]:
-    """
-    Processes an ELF file to look up what memory regions segments are in.
-
-    Returns the result from map_segments_to_memory_regions if valid memory
-    regions were parsed out of the ELF file.
-    """
-    parsed_elf_file = elffile.ELFFile(elf_file)
-
-    memory_regions = _parse_memory_regions(parsed_elf_file)
-    if not memory_regions:
-        return None
-
-    segments = _parse_segments(parsed_elf_file)
-
-    return map_segments_to_memory_regions(segments=segments,
-                                          memory_regions=memory_regions)
-
-
-def map_segments_to_memory_regions(segments: Dict,
-                                   memory_regions: Dict) -> Dict:
-    """
-    Maps segments to the virtual memory regions they reside in.
-
-    This takes in the results from _parse_memory_regions and _parse_segments and
-    produces a dictionary which looks like:
-    {
-      SEGMENT_INDEX_0:'MEMORY_REGION_NAME_0',
-      SEGMENT_INDEX_1:'MEMORY_REGION_NAME_0',
-      ...
-      SEGMENT_INDEX_N:'MEMORY_REGION_NAME_M',
-    }
-    """
-
-    # Now for each segment, determine what memory region it belongs to
-    # and generate a bloaty config output for it.
-    segment_to_memory_region = {}
-    for segment, (segment_start, segment_end) in segments.items():
-        # Note this is the final filter bloaty rewrite pattern format.
-        for memory_region_name, memory_region_info in memory_regions.items():
-            for _, (subregion_start,
-                    subregion_end) in memory_region_info.items():
-                if (segment_start >= subregion_start
-                        and segment_end <= subregion_end):
-                    # We found the subregion the segment resides in.
-                    segment_to_memory_region[segment] = memory_region_name
-        if segment not in segment_to_memory_region:
-            _LOG.error(
-                f'Error: Failed to find memory region for LOAD #{segment} ' +
-                f'[{hex(segment_start)},{hex(segment_end)}]')
-    return segment_to_memory_region
-
-
-def generate_memoryregions_data_source(segment_to_memory_region: Dict) -> str:
-    output: List[str] = []
-    output.append('custom_data_source: {')
-    output.append('  name: "memoryregions"')
-    output.append('  base_data_source: "segments"')
-    for segment_index, memory_region in segment_to_memory_region.items():
-        output.append('  rewrite: {')
-        segment_filter = r'^LOAD ' + f'#{segment_index}' + r' \\[.*\\]$'
-        output.append(f'    pattern:"{segment_filter}"')
-        output.append(f'    replacement:"{memory_region}"')
-        output.append('  }')
-    output.append('  rewrite: {')
-    output.append('    pattern:".*"')
-    output.append('    replacement:"Not resident in memory"')
-    output.append('  }')
-    output.append('}')
-    return '\n'.join(output) + '\n'
-
-
-def generate_utilization_data_source() -> str:
-    output: List[str] = []
-    output.append('custom_data_source: {')
-    output.append('  name:"utilization"')
-    output.append('  base_data_source:"sections"')
-    output.append('  rewrite: {')
-    output.append('    pattern:"unused_space"')
-    output.append('    replacement:"Free space"')
-    output.append('  }')
-    output.append('  rewrite: {')
-    output.append('    pattern:".*"')
-    output.append('    replacement:"Used space"')
-    output.append('  }')
-    output.append('}')
-    return '\n'.join(output) + '\n'
-
-
-def generate_bloaty_config(elf_file: BinaryIO, enable_memoryregions: bool,
-                           enable_utilization: bool, out_file: TextIO) -> None:
-    if enable_memoryregions:
-        # Enable the "memoryregions" data_source if the user provided the
-        # required pw_bloat specific symbols in their linker script.
-        segment_to_memory_region = _get_segments_to_memory_region_map(elf_file)
-        if not segment_to_memory_region:
-            _LOG.info('memoryregions data_source is not provided')
-        else:
-            _LOG.info('memoryregions data_source is provided')
-            out_file.write(
-                generate_memoryregions_data_source(segment_to_memory_region))
-
-    if enable_utilization:
-        _LOG.info('utilization data_source is provided')
-        out_file.write(generate_utilization_data_source())
-
-
-def main() -> int:
-    """Generates a useful bloaty config file containing new data sources."""
-    args = _parse_args()
-
-    logging.basicConfig(format='%(message)s', level=args.loglevel)
-
-    generate_bloaty_config(elf_file=args.elf_file,
-                           enable_memoryregions=args.memoryregions,
-                           enable_utilization=args.utilization,
-                           out_file=args.output)
-    return 0
-
-
-if __name__ == "__main__":
-    sys.exit(main())
diff --git a/pw_bloat/py/pyproject.toml b/pw_bloat/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/pw_bloat/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/pw_bloat/py/setup.cfg b/pw_bloat/py/setup.cfg
deleted file mode 100644
index b1f87fa..0000000
--- a/pw_bloat/py/setup.cfg
+++ /dev/null
@@ -1,29 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pw_bloat
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Tools for generating binary size report cards
-
-[options]
-packages = find:
-zip_safe = False
-install_requires =
-    pw_cli
-    pyelftools
-
-[options.package_data]
-pw_bloat = py.typed
diff --git a/pw_bloat/py/setup.py b/pw_bloat/py/setup.py
index 7fef9f7..73c2a7e 100644
--- a/pw_bloat/py/setup.py
+++ b/pw_bloat/py/setup.py
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2019 The Pigweed Authors
 #
 # 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
@@ -15,4 +15,14 @@
 
 import setuptools  # type: ignore
 
-setuptools.setup()  # Package definition in setup.cfg
+setuptools.setup(
+    name='pw_bloat',
+    version='0.0.1',
+    author='Pigweed Authors',
+    author_email='pigweed-developers@googlegroups.com',
+    description='Tools for generating binary size report cards',
+    packages=setuptools.find_packages(),
+    package_data={'pw_bloat': ['py.typed']},
+    zip_safe=False,
+    install_requires=['pw_cli'],
+)
diff --git a/pw_blob_store/BUILD b/pw_blob_store/BUILD
new file mode 100644
index 0000000..0ff8d86
--- /dev/null
+++ b/pw_blob_store/BUILD
@@ -0,0 +1,86 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_blob_store",
+    srcs = [ "blob_store.cc" ],
+    hdrs = [
+        "public/pw_blob_store/blob_store.h",
+    ],
+    includes = ["public"],
+    deps = [
+        "//pw_checksum",
+        "//pw_containers",
+        "//pw_log",
+        "//pw_span",
+        "//pw_status",
+    ],
+)
+
+pw_cc_test(
+    name = "blob_store_test",
+    srcs = [
+        "blob_store_test.cc",
+    ],
+    deps = [
+        ":pw_blob_store",
+        "//pw_kvs:crc16",
+        "//pw_kvs:fake_flash",
+        "//pw_kvs:fake_flash_test_key_value_store",
+        "//pw_log",
+        "//pw_random",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "blob_store_chunk_write_test",
+    srcs = [
+        "blob_store_chunk_write_test.cc",
+    ],
+    deps = [
+        ":pw_blob_store",
+        "//pw_kvs:crc16",
+        "//pw_kvs:fake_flash",
+        "//pw_kvs:fake_flash_test_key_value_store",
+        "//pw_log",
+        "//pw_random",
+        "//pw_unit_test",
+    ],
+)
+pw_cc_test(
+    name = "blob_store_deferred_write_test",
+    srcs = [
+        "blob_store_deferred_write_test.cc",
+    ],
+    deps = [
+        ":pw_blob_store",
+        "//pw_kvs:crc16",
+        "//pw_kvs:fake_flash",
+        "//pw_kvs:fake_flash_test_key_value_store",
+        "//pw_log",
+        "//pw_random",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_blob_store/BUILD.bazel b/pw_blob_store/BUILD.bazel
deleted file mode 100644
index 9facc2c..0000000
--- a/pw_blob_store/BUILD.bazel
+++ /dev/null
@@ -1,121 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_blob_store",
-    srcs = ["blob_store.cc"],
-    hdrs = [
-        "public/pw_blob_store/blob_store.h",
-        "public/pw_blob_store/internal/metadata_format.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_bytes",
-        "//pw_checksum",
-        "//pw_containers",
-        "//pw_kvs",
-        "//pw_log",
-        "//pw_preprocessor",
-        "//pw_span",
-        "//pw_status",
-        "//pw_stream",
-        "//pw_sync:borrow",
-    ],
-)
-
-pw_cc_library(
-    name = "flat_file_system_entry",
-    srcs = ["flat_file_system_entry.cc"],
-    hdrs = ["public/pw_blob_store/flat_file_system_entry.h"],
-    includes = ["public"],
-    deps = [
-        ":pw_blob_store",
-        "//pw_bytes",
-        "//pw_file:flat_file_system",
-        "//pw_status",
-        "//pw_sync:mutex",
-    ],
-)
-
-pw_cc_test(
-    name = "blob_store_test",
-    srcs = [
-        "blob_store_test.cc",
-    ],
-    deps = [
-        ":pw_blob_store",
-        "//pw_kvs:crc16",
-        "//pw_kvs:fake_flash",
-        "//pw_kvs:fake_flash_test_key_value_store",
-        "//pw_log",
-        "//pw_sync:borrow",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "blob_store_chunk_write_test",
-    srcs = [
-        "blob_store_chunk_write_test.cc",
-    ],
-    deps = [
-        ":pw_blob_store",
-        "//pw_kvs:crc16",
-        "//pw_kvs:fake_flash",
-        "//pw_kvs:fake_flash_test_key_value_store",
-        "//pw_log",
-        "//pw_random",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "blob_store_deferred_write_test",
-    srcs = [
-        "blob_store_deferred_write_test.cc",
-    ],
-    deps = [
-        ":pw_blob_store",
-        "//pw_kvs:crc16",
-        "//pw_kvs:fake_flash",
-        "//pw_kvs:fake_flash_test_key_value_store",
-        "//pw_log",
-        "//pw_random",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "flat_file_system_entry_test",
-    srcs = ["flat_file_system_entry_test.cc"],
-    deps = [
-        ":flat_file_system_entry",
-        ":pw_blob_store",
-        "//pw_kvs:crc16",
-        "//pw_kvs:fake_flash",
-        "//pw_kvs:fake_flash_test_key_value_store",
-        "//pw_random",
-        "//pw_sync:mutex",
-    ],
-)
diff --git a/pw_blob_store/BUILD.gn b/pw_blob_store/BUILD.gn
index 1280b58..236c8d1 100644
--- a/pw_blob_store/BUILD.gn
+++ b/pw_blob_store/BUILD.gn
@@ -17,26 +17,18 @@
 import("$dir_pw_bloat/bloat.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_sync/backend.gni")
 import("$dir_pw_unit_test/test.gni")
 
-config("public_include_path") {
+config("default_config") {
   include_dirs = [ "public" ]
-  visibility = [ ":*" ]
 }
 
 pw_source_set("pw_blob_store") {
-  public_configs = [ ":public_include_path" ]
-  public = [
-    "public/pw_blob_store/blob_store.h",
-    "public/pw_blob_store/internal/metadata_format.h",
-  ]
+  public_configs = [ ":default_config" ]
+  public = [ "public/pw_blob_store/blob_store.h" ]
   sources = [ "blob_store.cc" ]
   public_deps = [
-    "$dir_pw_sync:borrow",
-    dir_pw_bytes,
     dir_pw_kvs,
-    dir_pw_preprocessor,
     dir_pw_status,
     dir_pw_stream,
   ]
@@ -47,57 +39,26 @@
   ]
 }
 
-pw_source_set("flat_file_system_entry") {
-  public_configs = [ ":public_include_path" ]
-  public_deps = [
-    ":pw_blob_store",
-    "$dir_pw_file:flat_file_system",
-    "$dir_pw_sync:lock_annotations",
-    "$dir_pw_sync:virtual_basic_lockable",
-    dir_pw_status,
-  ]
-  public = [ "public/pw_blob_store/flat_file_system_entry.h" ]
-  sources = [ "flat_file_system_entry.cc" ]
-  deps = [ dir_pw_assert ]
-}
-
 pw_test_group("tests") {
   tests = [
-    ":blob_store_test_1_alignment",
-    ":blob_store_test_16_alignment",
+    ":blob_store_test",
     ":blob_store_deferred_write_test",
     ":blob_store_chunk_write_test",
-    ":flat_file_system_entry_test",
   ]
 }
 
-pw_test("blob_store_test_1_alignment") {
+pw_test("blob_store_test") {
   deps = [
     ":pw_blob_store",
     "$dir_pw_kvs:crc16",
     "$dir_pw_kvs:fake_flash",
     "$dir_pw_kvs:fake_flash_test_key_value_store",
-    "$dir_pw_sync:borrow",
     dir_pw_log,
     dir_pw_random,
   ]
   sources = [ "blob_store_test.cc" ]
 }
 
-pw_test("blob_store_test_16_alignment") {
-  deps = [
-    ":pw_blob_store",
-    "$dir_pw_kvs:crc16",
-    "$dir_pw_kvs:fake_flash",
-    "$dir_pw_kvs:fake_flash_test_key_value_store",
-    "$dir_pw_sync:borrow",
-    dir_pw_log,
-    dir_pw_random,
-  ]
-  sources = [ "blob_store_test.cc" ]
-  defines = [ "PW_FLASH_TEST_ALIGNMENT=16U" ]
-}
-
 pw_test("blob_store_chunk_write_test") {
   deps = [
     ":pw_blob_store",
@@ -122,20 +83,6 @@
   sources = [ "blob_store_deferred_write_test.cc" ]
 }
 
-pw_test("flat_file_system_entry_test") {
-  enable_if = pw_sync_MUTEX_BACKEND != ""
-  deps = [
-    ":flat_file_system_entry",
-    ":pw_blob_store",
-    "$dir_pw_kvs:crc16",
-    "$dir_pw_kvs:fake_flash",
-    "$dir_pw_kvs:fake_flash_test_key_value_store",
-    "$dir_pw_sync:mutex",
-    dir_pw_random,
-  ]
-  sources = [ "flat_file_system_entry_test.cc" ]
-}
-
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
   report_deps = [ ":blob_size" ]
diff --git a/pw_blob_store/CMakeLists.txt b/pw_blob_store/CMakeLists.txt
index b561baf..8041124 100644
--- a/pw_blob_store/CMakeLists.txt
+++ b/pw_blob_store/CMakeLists.txt
@@ -14,9 +14,8 @@
 
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
 
-pw_add_module_library(pw_blob_store
+pw_auto_add_simple_module(pw_blob_store
   PUBLIC_DEPS
-    pw_bytes
     pw_containers
     pw_kvs
     pw_span
@@ -29,51 +28,3 @@
     pw_random
     pw_string
 )
-
-pw_add_module_library(pw_blob_store.flat_file_system_entry
-  PUBLIC_DEPS
-    pw_blob_store
-    pw_bytes
-    pw_containers
-    pw_file.flat_file_system
-    pw_kvs
-    pw_span
-    pw_status
-    pw_stream
-    pw_sync.mutex
-  PRIVATE_DEPS
-    pw_assert
-    pw_checksum
-    pw_log
-    pw_random
-    pw_string
-)
-
-pw_add_test(pw_blob_store.blob_store_chunk_write_test
-  SOURCES
-    blob_store_chunk_write_test.cc
-  DEPS
-    pw_blob_store
-  GROUPS
-    pw_blob_store
-)
-
-pw_add_test(pw_blob_store.blob_store_deferred_write_test
-  SOURCES
-    blob_store_deferred_write_test.cc
-  DEPS
-    pw_blob_store
-  GROUPS
-    pw_blob_store
-)
-
-pw_add_test(pw_blob_store.flat_file_system_entry_test
-  SOURCES
-    flat_file_system_entry_test.cc
-  DEPS
-    pw_blob_store
-    pw_blob_store.flat_file_system_entry
-  GROUPS
-    pw_blob_store
-)
-
diff --git a/pw_blob_store/blob_store.cc b/pw_blob_store/blob_store.cc
index d584553..39d259c 100644
--- a/pw_blob_store/blob_store.cc
+++ b/pw_blob_store/blob_store.cc
@@ -16,24 +16,12 @@
 
 #include <algorithm>
 
-#include "pw_assert/check.h"
-#include "pw_blob_store/internal/metadata_format.h"
-#include "pw_bytes/byte_builder.h"
-#include "pw_bytes/span.h"
-#include "pw_kvs/checksum.h"
-#include "pw_kvs/flash_memory.h"
-#include "pw_kvs/key_value_store.h"
+#include "pw_assert/assert.h"
 #include "pw_log/log.h"
-#include "pw_status/status.h"
-#include "pw_status/status_with_size.h"
 #include "pw_status/try.h"
-#include "pw_stream/stream.h"
 
 namespace pw::blob_store {
 
-using internal::BlobMetadataHeader;
-using internal::ChecksumValue;
-
 Status BlobStore::Init() {
   if (initialized_) {
     return OkStatus();
@@ -41,64 +29,53 @@
 
   PW_LOG_INFO("Init BlobStore");
 
-  const size_t flash_write_size_alignment =
+  const size_t write_buffer_size_alignment =
       flash_write_size_bytes_ % partition_.alignment_bytes();
-  PW_CHECK_UINT_EQ(flash_write_size_alignment, 0);
+  PW_CHECK_UINT_EQ((write_buffer_size_alignment), 0);
+  PW_CHECK_UINT_GE(write_buffer_.size_bytes(), flash_write_size_bytes_);
   PW_CHECK_UINT_GE(flash_write_size_bytes_, partition_.alignment_bytes());
-  const size_t partition_size_alignment =
-      partition_.size_bytes() % flash_write_size_bytes_;
-  PW_CHECK_UINT_EQ(partition_size_alignment, 0);
-  if (!write_buffer_.empty()) {
-    PW_CHECK_UINT_GE(write_buffer_.size_bytes(), flash_write_size_bytes_);
-  }
 
   ResetChecksum();
   initialized_ = true;
 
   if (LoadMetadata().ok()) {
+    valid_data_ = true;
+    write_address_ = metadata_.data_size_bytes;
+    flash_address_ = metadata_.data_size_bytes;
+
     PW_LOG_DEBUG("BlobStore init - Have valid blob of %u bytes",
                  static_cast<unsigned>(write_address_));
     return OkStatus();
   }
 
-  // No saved blob, assume it has not been erased yet even if it has to avoid
-  // having to scan the potentially massive partition.
-  PW_LOG_DEBUG("BlobStore init - No valid blob, assuming not erased");
+  // No saved blob, check for flash being erased.
+  bool erased = false;
+  if (partition_.IsErased(&erased).ok() && erased) {
+    flash_erased_ = true;
+
+    // Blob data is considered valid as soon as the flash is erased. Even though
+    // there are 0 bytes written, they are valid.
+    valid_data_ = true;
+    PW_LOG_DEBUG("BlobStore init - is erased");
+  } else {
+    PW_LOG_DEBUG("BlobStore init - not erased");
+  }
   return OkStatus();
 }
 
 Status BlobStore::LoadMetadata() {
-  write_address_ = 0;
-  flash_address_ = 0;
-  file_name_length_ = 0;
-  valid_data_ = false;
-
-  BlobMetadataHeader metadata;
-  metadata.reset();
-
-  // For kVersion1 metadata versions, only the first member of
-  // BlobMetadataHeaderV2 will be populated. If a file name is present,
-  // kvs_.Get() will return RESOURCE_EXHAUSTED as the file name won't fit in the
-  // BlobMetadtataHeader object, which is intended behavior.
-  if (StatusWithSize sws = kvs_.acquire()->Get(
-          MetadataKey(), std::as_writable_bytes(std::span(&metadata, 1)));
-      !sws.ok() && !sws.IsResourceExhausted()) {
+  if (!kvs_.Get(MetadataKey(), &metadata_).ok()) {
+    // If no metadata was read, make sure the metadata is reset.
+    metadata_.reset();
     return Status::NotFound();
   }
 
-  if (!ValidateChecksum(metadata.v1_metadata.data_size_bytes,
-                        metadata.v1_metadata.checksum)
-           .ok()) {
+  if (!ValidateChecksum().ok()) {
     PW_LOG_ERROR("BlobStore init - Invalidating blob with invalid checksum");
-    Invalidate().IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    Invalidate();
     return Status::DataLoss();
   }
 
-  write_address_ = metadata.v1_metadata.data_size_bytes;
-  flash_address_ = metadata.v1_metadata.data_size_bytes;
-  file_name_length_ = metadata.file_name_length;
-  valid_data_ = true;
-
   return OkStatus();
 }
 
@@ -119,38 +96,11 @@
 
   writer_open_ = true;
 
-  // Clear any existing contents.
-  Invalidate().IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  Invalidate();
 
   return OkStatus();
 }
 
-StatusWithSize BlobStore::GetFileName(std::span<char> dest) const {
-  if (!initialized_) {
-    return StatusWithSize(Status::FailedPrecondition(), 0);
-  }
-
-  if (file_name_length_ == 0) {
-    return StatusWithSize(Status::NotFound(), 0);
-  }
-
-  const size_t bytes_to_read =
-      std::min(dest.size_bytes(), static_cast<size_t>(file_name_length_));
-
-  Status status = bytes_to_read == file_name_length_
-                      ? OkStatus()
-                      : Status::ResourceExhausted();
-
-  // Read file name from KVS.
-  constexpr size_t kFileNameOffset = sizeof(BlobMetadataHeader);
-  const StatusWithSize kvs_read_sws =
-      kvs_.acquire()->Get(MetadataKey(),
-                          std::as_writable_bytes(dest.first(bytes_to_read)),
-                          kFileNameOffset);
-  status.Update(kvs_read_sws.status());
-  return StatusWithSize(status, kvs_read_sws.size());
-}
-
 Status BlobStore::OpenRead() {
   if (!initialized_) {
     return Status::FailedPrecondition();
@@ -161,7 +111,7 @@
     return Status::Unavailable();
   }
 
-  if (!HasData()) {
+  if (!ValidToRead()) {
     PW_LOG_ERROR("Blob reader unable open without valid data");
     return Status::FailedPrecondition();
   }
@@ -172,6 +122,67 @@
   return OkStatus();
 }
 
+Status BlobStore::CloseWrite() {
+  auto do_close_write = [&]() -> Status {
+    // If not valid to write, there was data loss and the close will result in a
+    // not valid blob. Don't need to flush any write buffered bytes.
+    if (!ValidToWrite()) {
+      return Status::DataLoss();
+    }
+
+    if (write_address_ == 0) {
+      return OkStatus();
+    }
+
+    PW_LOG_DEBUG(
+        "Blob writer close of %u byte blob, with %u bytes still in write "
+        "buffer",
+        static_cast<unsigned>(write_address_),
+        static_cast<unsigned>(WriteBufferBytesUsed()));
+
+    // Do a Flush of any flash_write_size_bytes_ sized chunks so any remaining
+    // bytes in the write buffer are less than flash_write_size_bytes_.
+    PW_TRY(Flush());
+
+    // If any bytes remain in buffer it is because it is a chunk less than
+    // flash_write_size_bytes_. Pad the chunk to flash_write_size_bytes_ and
+    // write it to flash.
+    if (!WriteBufferEmpty()) {
+      PW_TRY(FlushFinalPartialChunk());
+    }
+    PW_DCHECK(WriteBufferEmpty());
+
+    // If things are still good, save the blob metadata.
+    metadata_ = {.checksum = 0, .data_size_bytes = flash_address_};
+    if (checksum_algo_ != nullptr) {
+      ConstByteSpan checksum = checksum_algo_->Finish();
+      std::memcpy(&metadata_.checksum,
+                  checksum.data(),
+                  std::min(checksum.size(), sizeof(metadata_.checksum)));
+    }
+
+    if (!ValidateChecksum().ok()) {
+      Invalidate();
+      return Status::DataLoss();
+    }
+
+    if (!kvs_.Put(MetadataKey(), metadata_).ok()) {
+      return Status::DataLoss();
+    }
+
+    return OkStatus();
+  };
+
+  const Status status = do_close_write();
+  writer_open_ = false;
+
+  if (!status.ok()) {
+    valid_data_ = false;
+    return Status::DataLoss();
+  }
+  return OkStatus();
+}
+
 Status BlobStore::CloseRead() {
   PW_CHECK_UINT_GT(readers_open_, 0);
   readers_open_--;
@@ -192,10 +203,6 @@
   if (WriteBytesRemaining() < data.size_bytes()) {
     return Status::ResourceExhausted();
   }
-  if ((write_buffer_.empty()) &&
-      ((data.size_bytes() % flash_write_size_bytes_) != 0)) {
-    return Status::InvalidArgument();
-  }
 
   if (!EraseIfNeeded().ok()) {
     return Status::DataLoss();
@@ -210,7 +217,6 @@
   // Step 1) If there is any data in the write buffer, finish filling write
   //         buffer and if full write it to flash.
   if (!WriteBufferEmpty()) {
-    PW_DCHECK(!write_buffer_.empty());
     size_t bytes_in_buffer = WriteBufferBytesUsed();
 
     // Non-deferred writes only use the first flash_write_size_bytes_ of the
@@ -237,9 +243,11 @@
     }
 
     // The write buffer is full, flush to flash.
-    if (!CommitToFlash(write_buffer_.first(flash_write_size_bytes_)).ok()) {
+    if (!CommitToFlash(write_buffer_).ok()) {
       return Status::DataLoss();
     }
+
+    PW_DCHECK(WriteBufferEmpty());
   }
 
   // At this point, if data.size_bytes() > 0, the write buffer should be empty.
@@ -247,31 +255,23 @@
 
   // Step 2) Write as many block-sized chunks as the data has remaining after
   //         step 1.
-  PW_DCHECK(WriteBufferEmpty());
+  while (data.size_bytes() >= flash_write_size_bytes_) {
+    PW_DCHECK(WriteBufferEmpty());
 
-  const size_t final_partial_write_size_bytes =
-      data.size_bytes() % flash_write_size_bytes_;
-
-  if (data.size_bytes() >= flash_write_size_bytes_) {
-    const size_t write_size_bytes =
-        data.size_bytes() - final_partial_write_size_bytes;
-    write_address_ += write_size_bytes;
-    if (!CommitToFlash(data.first(write_size_bytes)).ok()) {
+    write_address_ += flash_write_size_bytes_;
+    if (!CommitToFlash(data.first(flash_write_size_bytes_)).ok()) {
       return Status::DataLoss();
     }
-    data = data.subspan(write_size_bytes);
+
+    data = data.subspan(flash_write_size_bytes_);
   }
 
   // step 3) Put any remaining bytes to the buffer. Put the bytes starting at
   //         the begining of the buffer, since it must be empty if there are
   //         still bytes due to step 1 either cleaned out the buffer or didn't
   //         have any more data to write.
-  if (final_partial_write_size_bytes > 0) {
-    PW_DCHECK_INT_LT(data.size_bytes(), flash_write_size_bytes_);
-    PW_DCHECK(!write_buffer_.empty());
-
-    // Don't need to DCHECK that buffer is empty, nothing writes to it since the
-    // previous time it was DCHECK'ed
+  if (data.size_bytes() > 0) {
+    PW_DCHECK(WriteBufferEmpty());
     std::memcpy(write_buffer_.data(), data.data(), data.size_bytes());
     write_address_ += data.size_bytes();
   }
@@ -309,21 +309,18 @@
   // Don't need to check available space, AddToWriteBuffer() will not enqueue
   // more than can be written to flash.
 
-  // If there is no buffer there should never be any bytes enqueued.
-  PW_DCHECK(!write_buffer_.empty());
-
   if (!EraseIfNeeded().ok()) {
     return Status::DataLoss();
   }
 
   ByteSpan data = std::span(write_buffer_.data(), WriteBufferBytesUsed());
-  size_t write_size_bytes =
-      (data.size_bytes() / flash_write_size_bytes_) * flash_write_size_bytes_;
-  if (!CommitToFlash(data.first(write_size_bytes)).ok()) {
-    return Status::DataLoss();
+  while (data.size_bytes() >= flash_write_size_bytes_) {
+    if (!CommitToFlash(data.first(flash_write_size_bytes_)).ok()) {
+      return Status::DataLoss();
+    }
+
+    data = data.subspan(flash_write_size_bytes_);
   }
-  data = data.subspan(write_size_bytes);
-  PW_DCHECK_INT_LT(data.size_bytes(), flash_write_size_bytes_);
 
   // Only a multiple of flash_write_size_bytes_ are written in the flush. Any
   // remainder is held until later for either a flush with
@@ -345,11 +342,7 @@
 
   PW_DCHECK_UINT_GT(bytes_in_buffer, 0);
   PW_DCHECK_UINT_LE(bytes_in_buffer, flash_write_size_bytes_);
-  PW_DCHECK_UINT_LE(flash_write_size_bytes_,
-                    MaxDataSizeBytes() - flash_address_);
-
-  // If there is no buffer there should never be any bytes enqueued.
-  PW_DCHECK(!write_buffer_.empty());
+  PW_DCHECK_UINT_LE(flash_write_size_bytes_, WriteBytesRemaining());
 
   PW_LOG_DEBUG(
       "  Remainder %u bytes in write buffer to zero-pad to flash write "
@@ -370,7 +363,6 @@
   if (data_bytes == 0) {
     data_bytes = source.size_bytes();
   }
-
   flash_erased_ = false;
   StatusWithSize result = partition_.Write(flash_address_, source);
   flash_address_ += data_bytes;
@@ -407,7 +399,7 @@
 }
 
 StatusWithSize BlobStore::Read(size_t offset, ByteSpan dest) const {
-  if (!HasData()) {
+  if (!ValidToRead()) {
     return StatusWithSize::FailedPrecondition();
   }
   if (offset >= ReadableDataBytes()) {
@@ -421,7 +413,7 @@
 }
 
 Result<ConstByteSpan> BlobStore::GetMemoryMappedBlob() const {
-  if (!HasData()) {
+  if (!ValidToRead()) {
     return Status::FailedPrecondition();
   }
 
@@ -451,44 +443,43 @@
     return OkStatus();
   }
 
-  // If any writes have been performed, reset the state.
-  if (flash_address_ != 0) {
-    Invalidate().IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  Invalidate();
+
+  Status status = partition_.Erase();
+
+  if (status.ok()) {
+    flash_erased_ = true;
+
+    // Blob data is considered valid as soon as the flash is erased. Even though
+    // there are 0 bytes written, they are valid.
+    valid_data_ = true;
   }
-
-  PW_TRY(partition_.Erase());
-
-  flash_erased_ = true;
-
-  // Blob data is considered valid as soon as the flash is erased. Even though
-  // there are 0 bytes written, they are valid.
-  valid_data_ = true;
-  return OkStatus();
+  return status;
 }
 
 Status BlobStore::Invalidate() {
-  // Blob data is considered valid if the flash is erased. Even though
+  metadata_.reset();
+
+  // Blob data is considered if the flash is erased. Even though
   // there are 0 bytes written, they are valid.
   valid_data_ = flash_erased_;
   ResetChecksum();
   write_address_ = 0;
   flash_address_ = 0;
-  file_name_length_ = 0;
 
-  Status status = kvs_.acquire()->Delete(MetadataKey());
+  Status status = kvs_.Delete(MetadataKey());
 
   return (status.ok() || status.IsNotFound()) ? OkStatus() : Status::Internal();
 }
 
-Status BlobStore::ValidateChecksum(size_t blob_size_bytes,
-                                   ChecksumValue expected) {
-  if (blob_size_bytes == 0) {
+Status BlobStore::ValidateChecksum() {
+  if (metadata_.data_size_bytes == 0) {
     PW_LOG_INFO("Blob unable to validate checksum of an empty blob");
     return Status::Unavailable();
   }
 
   if (checksum_algo_ == nullptr) {
-    if (expected != 0) {
+    if (metadata_.checksum != 0) {
       PW_LOG_ERROR(
           "Blob invalid to have a checkum value with no checksum algo");
       return Status::DataLoss();
@@ -498,11 +489,12 @@
   }
 
   PW_LOG_DEBUG("Validate checksum of 0x%08x in flash for blob of %u bytes",
-               static_cast<unsigned>(expected),
-               static_cast<unsigned>(blob_size_bytes));
-  PW_TRY(CalculateChecksumFromFlash(blob_size_bytes));
+               static_cast<unsigned>(metadata_.checksum),
+               static_cast<unsigned>(metadata_.data_size_bytes));
+  PW_TRY(CalculateChecksumFromFlash(metadata_.data_size_bytes));
 
-  Status status = checksum_algo_->Verify(as_bytes(std::span(&expected, 1)));
+  Status status =
+      checksum_algo_->Verify(as_bytes(std::span(&metadata_.checksum, 1)));
   PW_LOG_DEBUG("  checksum verify of %s", status.str());
 
   return status;
@@ -534,205 +526,4 @@
   return OkStatus();
 }
 
-Status BlobStore::BlobWriter::SetFileName(std::string_view file_name) {
-  if (!open_) {
-    return Status::FailedPrecondition();
-  }
-  PW_DCHECK_NOTNULL(file_name.data());
-  PW_DCHECK(store_.writer_open_);
-
-  if (file_name.length() > MaxFileNameLength()) {
-    return Status::ResourceExhausted();
-  }
-
-  // Stage the file name to the encode buffer, just past the BlobMetadataHeader
-  // struct.
-  constexpr size_t kFileNameOffset = sizeof(BlobMetadataHeader);
-  const ByteSpan file_name_dest = metadata_buffer_.subspan(kFileNameOffset);
-  std::memcpy(file_name_dest.data(), file_name.data(), file_name.length());
-
-  store_.file_name_length_ = file_name.length();
-  return OkStatus();
-}
-
-Status BlobStore::BlobWriter::Open() {
-  PW_DCHECK(!open_);
-  PW_DCHECK_UINT_GE(metadata_buffer_.size_bytes(),
-                    sizeof(internal::BlobMetadataHeader));
-
-  const Status status = store_.OpenWrite();
-  if (status.ok()) {
-    open_ = true;
-  }
-  return status;
-}
-
-// Validates and commits BlobStore metadata to KVS.
-//
-// 1. Finalize checksum calculation.
-// 2. Check the calculated checksum against data actually committed to flash.
-// 3. Build the metadata header into the metadata buffer, placing it before the
-//    staged file name (if any).
-// 4. Commit the metadata to KVS.
-Status BlobStore::BlobWriter::WriteMetadata() {
-  // Finalize the in-progress checksum, if any.
-  ChecksumValue calculated_checksum = 0;
-  if (store_.checksum_algo_ != nullptr) {
-    ConstByteSpan checksum = store_.checksum_algo_->Finish();
-    std::memcpy(&calculated_checksum,
-                checksum.data(),
-                std::min(checksum.size(), sizeof(ChecksumValue)));
-  }
-
-  // Check the in-memory checksum against the data that was actually committed
-  // to flash.
-  if (!store_.ValidateChecksum(store_.flash_address_, calculated_checksum)
-           .ok()) {
-    PW_CHECK_OK(store_.Invalidate());
-    return Status::DataLoss();
-  }
-
-  // Encode the metadata header. This follows the latest struct behind
-  // BlobMetadataHeader. Currently, the order is as follows:
-  // - Encode checksum.
-  // - Encode stored data size.
-  // - Encode version magic.
-  // - Encode file name size.
-  // - File name, if present, is already staged at the end.
-  //
-  // Open() guarantees the metadata buffer is large enough to fit the metadata
-  // header.
-  ByteBuilder metadata_builder(metadata_buffer_);
-  metadata_builder.PutUint32(calculated_checksum);
-  metadata_builder.PutUint32(store_.flash_address_);
-  metadata_builder.PutUint32(internal::MetadataVersion::kLatest);
-  metadata_builder.PutUint8(store_.file_name_length_);
-  PW_DCHECK_INT_EQ(metadata_builder.size(), sizeof(BlobMetadataHeader));
-  PW_DCHECK_OK(metadata_builder.status());
-
-  // If a filename was provided, it is already written to the correct location
-  // in the buffer. When the file name was set, the metadata buffer was verified
-  // to fit the requested name in addition to the metadata header. If it doesn't
-  // fit now, something's very wrong.
-  const size_t bytes_to_write =
-      metadata_builder.size() + store_.file_name_length_;
-  PW_DCHECK(metadata_buffer_.size_bytes() >= bytes_to_write);
-
-  // Do final commit to KVS.
-  return store_.kvs_.acquire()->Put(store_.MetadataKey(),
-                                    metadata_buffer_.first(bytes_to_write));
-}
-
-Status BlobStore::BlobWriter::Close() {
-  if (!open_) {
-    return Status::FailedPrecondition();
-  }
-  open_ = false;
-
-  // This is a lambda so the BlobWriter will be unconditionally closed even if
-  // the final flash commits fail. This lambda may early return to Close() if
-  // errors are encountered, but Close() will not return without updating both
-  // the BlobWriter and BlobStore such that neither are open for writes
-  // anymore.
-  auto do_close_write = [&]() -> Status {
-    // If not valid to write, there was data loss and the close will result in a
-    // not valid blob. Don't need to flush any write buffered bytes.
-    if (!store_.ValidToWrite()) {
-      return Status::DataLoss();
-    }
-
-    if (store_.write_address_ == 0) {
-      return OkStatus();
-    }
-
-    PW_LOG_DEBUG(
-        "Blob writer close of %u byte blob, with %u bytes still in write "
-        "buffer",
-        static_cast<unsigned>(store_.write_address_),
-        static_cast<unsigned>(store_.WriteBufferBytesUsed()));
-
-    // Do a Flush of any flash_write_size_bytes_ sized chunks so any remaining
-    // bytes in the write buffer are less than flash_write_size_bytes_.
-    PW_TRY(store_.Flush());
-
-    // If any bytes remain in buffer it is because it is a chunk less than
-    // flash_write_size_bytes_. Pad the chunk to flash_write_size_bytes_ and
-    // write it to flash.
-    if (!store_.WriteBufferEmpty()) {
-      PW_TRY(store_.FlushFinalPartialChunk());
-    }
-    PW_DCHECK(store_.WriteBufferEmpty());
-
-    if (!WriteMetadata().ok()) {
-      return Status::DataLoss();
-    }
-
-    return OkStatus();
-  };
-
-  const Status status = do_close_write();
-  store_.writer_open_ = false;
-
-  if (!status.ok()) {
-    store_.valid_data_ = false;
-    return Status::DataLoss();
-  }
-  return OkStatus();
-}
-
-size_t BlobStore::BlobReader::ConservativeLimit(LimitType limit) const {
-  if (open_ && limit == LimitType::kRead) {
-    return store_.ReadableDataBytes() - offset_;
-  }
-  return 0;
-}
-
-Status BlobStore::BlobReader::Open(size_t offset) {
-  PW_DCHECK(!open_);
-  if (!store_.HasData()) {
-    return Status::FailedPrecondition();
-  }
-  if (offset >= store_.ReadableDataBytes()) {
-    return Status::InvalidArgument();
-  }
-
-  offset_ = offset;
-  Status status = store_.OpenRead();
-  if (status.ok()) {
-    open_ = true;
-  }
-  return status;
-}
-
-size_t BlobStore::BlobReader::DoTell() const {
-  return open_ ? offset_ : kUnknownPosition;
-}
-
-Status BlobStore::BlobReader::DoSeek(ptrdiff_t offset, Whence origin) {
-  if (!open_) {
-    return Status::FailedPrecondition();
-  }
-
-  // Note that Open ensures HasData() which in turn guarantees
-  // store_.ReadableDataBytes() > 0.
-
-  size_t pos = offset_;
-  PW_TRY(CalculateSeek(offset, origin, store_.ReadableDataBytes() - 1, pos));
-  offset_ = pos;
-
-  return OkStatus();
-}
-
-StatusWithSize BlobStore::BlobReader::DoRead(ByteSpan dest) {
-  if (!open_) {
-    return StatusWithSize::FailedPrecondition();
-  }
-
-  StatusWithSize status = store_.Read(offset_, dest);
-  if (status.ok()) {
-    offset_ += status.size();
-  }
-  return status;
-}
-
 }  // namespace pw::blob_store
diff --git a/pw_blob_store/blob_store_chunk_write_test.cc b/pw_blob_store/blob_store_chunk_write_test.cc
index b789c8d..3dabaa8 100644
--- a/pw_blob_store/blob_store_chunk_write_test.cc
+++ b/pw_blob_store/blob_store_chunk_write_test.cc
@@ -34,22 +34,18 @@
   BlobStoreChunkTest() : flash_(kFlashAlignment), partition_(&flash_) {}
 
   void InitFlashTo(std::span<const std::byte> contents) {
-    partition_.Erase()
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    partition_.Erase();
     std::memcpy(flash_.buffer().data(), contents.data(), contents.size());
   }
 
   void InitSourceBufferToRandom(uint64_t seed) {
-    partition_.Erase()
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    partition_.Erase();
     random::XorShiftStarRng64 rng(seed);
-    rng.Get(source_buffer_)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    rng.Get(source_buffer_);
   }
 
   void InitSourceBufferToFill(char fill) {
-    partition_.Erase()
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    partition_.Erase();
     std::memset(source_buffer_.data(), fill, source_buffer_.size());
   }
 
@@ -66,7 +62,7 @@
         name, partition_, &checksum, kvs::TestKvs(), kBufferSize);
     EXPECT_EQ(OkStatus(), blob.Init());
 
-    BlobStore::BlobWriter writer(blob, metadata_buffer_);
+    BlobStore::BlobWriter writer(blob);
     EXPECT_EQ(OkStatus(), writer.Open());
     EXPECT_EQ(OkStatus(), writer.Erase());
 
@@ -110,12 +106,9 @@
   static constexpr size_t kSectorSize = 2048;
   static constexpr size_t kSectorCount = 2;
   static constexpr size_t kBlobDataSize = (kSectorCount * kSectorSize);
-  static constexpr size_t kMetadataBufferSize =
-      BlobStore::BlobWriter::RequiredMetadataBufferSize(0);
 
   kvs::FakeFlashMemoryBuffer<kSectorSize, kSectorCount> flash_;
   kvs::FlashPartition partition_;
-  std::array<std::byte, kMetadataBufferSize> metadata_buffer_;
   std::array<std::byte, kBlobDataSize> source_buffer_;
 };
 
diff --git a/pw_blob_store/blob_store_deferred_write_test.cc b/pw_blob_store/blob_store_deferred_write_test.cc
index 5189be2..02491e6 100644
--- a/pw_blob_store/blob_store_deferred_write_test.cc
+++ b/pw_blob_store/blob_store_deferred_write_test.cc
@@ -33,33 +33,25 @@
  protected:
   DeferredWriteTest() : flash_(kFlashAlignment), partition_(&flash_) {}
 
-  void InitFlashToErased() { ASSERT_EQ(OkStatus(), partition_.Erase()); }
-
-  void InitFlashToRandom(uint64_t seed) {
-    random::XorShiftStarRng64 rng(seed);
-    StatusWithSize sws = rng.Get(flash_.buffer());
-    ASSERT_EQ(OkStatus(), sws.status());
-    ASSERT_EQ(sws.size(), flash_.buffer().size());
+  void InitFlashTo(std::span<const std::byte> contents) {
+    partition_.Erase();
+    std::memcpy(flash_.buffer().data(), contents.data(), contents.size());
   }
 
   void InitBufferToRandom(uint64_t seed) {
+    partition_.Erase();
     random::XorShiftStarRng64 rng(seed);
-    StatusWithSize sws = rng.Get(buffer_);
-    ASSERT_EQ(OkStatus(), sws.status());
-    ASSERT_EQ(sws.size(), buffer_.size());
+    rng.Get(buffer_);
   }
 
   void InitBufferToFill(char fill) {
-    ASSERT_EQ(OkStatus(), partition_.Erase());
+    partition_.Erase();
     std::memset(buffer_.data(), fill, buffer_.size());
   }
 
   // Fill the source buffer with random pattern based on given seed, written to
   // BlobStore in specified chunk size.
-  void ChunkWriteTest(size_t chunk_size,
-                      size_t flush_interval,
-                      bool explicit_discard = false,
-                      bool explicit_erase = false) {
+  void ChunkWriteTest(size_t chunk_size, size_t flush_interval) {
     constexpr size_t kWriteSize = 64;
     kvs::ChecksumCrc16 checksum;
 
@@ -72,17 +64,9 @@
         name, partition_, &checksum, kvs::TestKvs(), kWriteSize);
     EXPECT_EQ(OkStatus(), blob.Init());
 
-    BlobStore::DeferredWriterWithBuffer writer(blob);
+    BlobStore::DeferredWriter writer(blob);
     EXPECT_EQ(OkStatus(), writer.Open());
 
-    if (explicit_discard) {
-      EXPECT_EQ(OkStatus(), writer.Discard());
-    }
-
-    if (explicit_erase) {
-      EXPECT_EQ(OkStatus(), writer.Erase());
-    }
-
     ByteSpan source = buffer_;
     while (source.size_bytes() > 0) {
       const size_t write_size = std::min(source.size_bytes(), chunk_size);
@@ -137,88 +121,58 @@
 };
 
 TEST_F(DeferredWriteTest, ChunkWrite1) {
-  InitFlashToErased();
   InitBufferToRandom(0x8675309);
   ChunkWriteTest(1, 16);
 }
 
 TEST_F(DeferredWriteTest, ChunkWrite2) {
-  InitFlashToRandom(0x2283);
   InitBufferToRandom(0x8675);
   ChunkWriteTest(2, 16);
 }
 
 TEST_F(DeferredWriteTest, ChunkWrite3) {
-  InitFlashToErased();
   InitBufferToFill(0);
   ChunkWriteTest(3, 16);
 }
 
 TEST_F(DeferredWriteTest, ChunkWrite4) {
-  InitFlashToErased();
   InitBufferToFill(1);
   ChunkWriteTest(4, 64);
 }
 
 TEST_F(DeferredWriteTest, ChunkWrite5) {
-  InitFlashToErased();
   InitBufferToFill(0xff);
   ChunkWriteTest(5, 64);
 }
 
 TEST_F(DeferredWriteTest, ChunkWrite16) {
-  InitFlashToErased();
   InitBufferToRandom(0x86);
   ChunkWriteTest(16, 128);
 }
 
 TEST_F(DeferredWriteTest, ChunkWrite64) {
-  InitFlashToRandom(0x9223);
   InitBufferToRandom(0x9);
   ChunkWriteTest(64, 128);
 }
 
 TEST_F(DeferredWriteTest, ChunkWrite64FullBufferFill) {
-  InitFlashToErased();
   InitBufferToRandom(0x9);
   ChunkWriteTest(64, kBufferSize);
 }
 
 TEST_F(DeferredWriteTest, ChunkWrite256) {
-  InitFlashToErased();
   InitBufferToRandom(0x12345678);
   ChunkWriteTest(256, 256);
 }
 
-TEST_F(DeferredWriteTest, ChunkWriteDiscard16) {
-  InitFlashToErased();
-  InitBufferToRandom(0x86);
+// TODO: test that has dirty flash, invalidated blob, open writer, invalidate
+// (not erase) and start writing (does the auto/implicit erase).
 
-  // Test with a discard of an invalid blob and erased flash.
-  ChunkWriteTest(16, 128, true);
+// TODO: test that has dirty flash, invalidated blob, open writer, explicit
+// erase and start writing.
 
-  // Test with a discard of a valid blob.
-  ChunkWriteTest(16, 128, true);
-
-  // Test with a discard of an current blob with corrupted date.
-  InitFlashToRandom(0x9223);
-  ChunkWriteTest(16, 128, true);
-}
-
-TEST_F(DeferredWriteTest, ChunkWriteErase16) {
-  InitFlashToErased();
-  InitBufferToRandom(0x1286);
-
-  // Test with an erase of an invalid blob and erased flash.
-  ChunkWriteTest(16, 128, false, true);
-
-  // Test with an erase of a valid blob.
-  ChunkWriteTest(16, 128, false, true);
-
-  // Test with an erase of an current blob with corrupted date.
-  InitFlashToRandom(0x9223);
-  ChunkWriteTest(16, 128, false, true);
-}
+// TODO: test start with dirty flash/invalid blob, open writer, write, close.
+// Verifies erase logic when write buffer has contents.
 
 }  // namespace
 }  // namespace pw::blob_store
diff --git a/pw_blob_store/blob_store_test.cc b/pw_blob_store/blob_store_test.cc
index 5b35df2..e5ff2f1 100644
--- a/pw_blob_store/blob_store_test.cc
+++ b/pw_blob_store/blob_store_test.cc
@@ -27,22 +27,15 @@
 #include "pw_log/log.h"
 #include "pw_random/xor_shift.h"
 
-#ifndef PW_FLASH_TEST_ALIGNMENT
-#define PW_FLASH_TEST_ALIGNMENT 1
-#endif
-
 namespace pw::blob_store {
 namespace {
 
 class BlobStoreTest : public ::testing::Test {
  protected:
-  static constexpr char kBlobTitle[] = "TestBlobBlock";
-
   BlobStoreTest() : flash_(kFlashAlignment), partition_(&flash_) {}
 
   void InitFlashTo(std::span<const std::byte> contents) {
-    partition_.Erase()
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    partition_.Erase();
     std::memcpy(flash_.buffer().data(), contents.data(), contents.size());
   }
 
@@ -54,8 +47,7 @@
     std::memset(source_buffer_.data(),
                 static_cast<int>(flash_.erased_memory_content()),
                 source_buffer_.size());
-    rng.Get(std::span(source_buffer_).first(init_size_bytes))
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    rng.Get(std::span(source_buffer_).first(init_size_bytes));
   }
 
   void InitSourceBufferToFill(char fill,
@@ -77,11 +69,14 @@
     ConstByteSpan write_data =
         std::span(source_buffer_).first(write_size_bytes);
 
+    char name[16] = {};
+    snprintf(name, sizeof(name), "TestBlobBlock");
+
     BlobStoreBuffer<kBufferSize> blob(
-        kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
+        name, partition_, &checksum, kvs::TestKvs(), kBufferSize);
     EXPECT_EQ(OkStatus(), blob.Init());
 
-    BlobStore::BlobWriterWithBuffer writer(blob);
+    BlobStore::BlobWriter writer(blob);
     EXPECT_EQ(OkStatus(), writer.Open());
     ASSERT_EQ(OkStatus(), writer.Write(write_data));
     EXPECT_EQ(OkStatus(), writer.Close());
@@ -103,9 +98,10 @@
 
     VerifyFlash(flash_.buffer());
 
+    char name[16] = "TestBlobBlock";
     constexpr size_t kBufferSize = 16;
     BlobStoreBuffer<kBufferSize> blob(
-        kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
+        name, partition_, &checksum, kvs::TestKvs(), kBufferSize);
     EXPECT_EQ(OkStatus(), blob.Init());
 
     // Use reader to check for valid data.
@@ -151,7 +147,7 @@
     }
   }
 
-  static constexpr size_t kFlashAlignment = PW_FLASH_TEST_ALIGNMENT;
+  static constexpr size_t kFlashAlignment = 16;
   static constexpr size_t kSectorSize = 2048;
   static constexpr size_t kSectorCount = 2;
   static constexpr size_t kBlobDataSize = (kSectorCount * kSectorSize);
@@ -170,107 +166,30 @@
   EXPECT_EQ(OkStatus(), blob.Init());
 }
 
-TEST_F(BlobStoreTest, Writer_ConservativeLimits) {
-  constexpr size_t kBufferSize = 256;
-  BlobStoreBuffer<kBufferSize> blob(
-      "Blob_OK", partition_, nullptr, kvs::TestKvs(), kBufferSize);
-  ASSERT_EQ(OkStatus(), blob.Init());
-
-  BlobStore::BlobWriterWithBuffer writer(blob);
-  ASSERT_EQ(OkStatus(), writer.Open());
-  EXPECT_EQ(writer.ConservativeReadLimit(), 0u);
-  EXPECT_EQ(writer.ConservativeWriteLimit(), kSectorSize * kSectorCount);
-  ASSERT_EQ(OkStatus(), writer.Close());
-
-  BlobStore::DeferredWriterWithBuffer deferred_writer(blob);
-  ASSERT_EQ(OkStatus(), deferred_writer.Open());
-  EXPECT_EQ(deferred_writer.ConservativeReadLimit(), 0u);
-  EXPECT_EQ(deferred_writer.ConservativeWriteLimit(), kBufferSize);
-}
-
-// Write to the blob using a flash_write_size_bytes smaller than the
-// buffer size. Use Write operations smaller than flash_write_size_bytes
-// to ensure it checks the internal buffering path.
-TEST_F(BlobStoreTest, OversizedWriteBuffer) {
-  size_t write_size_bytes = 8;
-  ASSERT_LE(write_size_bytes, source_buffer_.size());
-  constexpr size_t kBufferSize = 256;
-  kvs::ChecksumCrc16 checksum;
-
-  InitSourceBufferToRandom(0x123d123);
-
-  ConstByteSpan write_data = std::span(source_buffer_);
-  ConstByteSpan original_source = std::span(source_buffer_);
-
-  EXPECT_EQ(OkStatus(), partition_.Erase());
-
-  BlobStoreBuffer<kBufferSize> blob(
-      kBlobTitle, partition_, &checksum, kvs::TestKvs(), 64);
-  EXPECT_EQ(OkStatus(), blob.Init());
-
-  BlobStore::BlobWriterWithBuffer writer(blob);
-  EXPECT_EQ(OkStatus(), writer.Open());
-  while (write_data.size_bytes() > 0) {
-    ASSERT_EQ(OkStatus(), writer.Write(write_data.first(write_size_bytes)));
-    write_data = write_data.subspan(write_size_bytes);
-  }
-  EXPECT_EQ(OkStatus(), writer.Close());
-
-  // Use reader to check for valid data.
-  BlobStore::BlobReader reader(blob);
-  ASSERT_EQ(OkStatus(), reader.Open());
-  Result<ConstByteSpan> result = reader.GetMemoryMappedBlob();
-  ASSERT_TRUE(result.ok());
-  EXPECT_EQ(original_source.size_bytes(), result.value().size_bytes());
-  VerifyFlash(result.value());
-  VerifyFlash(flash_.buffer());
-  EXPECT_EQ(OkStatus(), reader.Close());
-}
-
-TEST_F(BlobStoreTest, Reader_ConservativeLimits) {
-  InitSourceBufferToRandom(0x11309);
-  WriteTestBlock();
-
-  kvs::ChecksumCrc16 checksum;
-  constexpr size_t kBufferSize = 16;
-  BlobStoreBuffer<kBufferSize> blob(
-      "TestBlobBlock", partition_, &checksum, kvs::TestKvs(), kBufferSize);
-  EXPECT_EQ(OkStatus(), blob.Init());
-  EXPECT_TRUE(blob.HasData());
-  BlobStore::BlobReader reader(blob);
-  ASSERT_EQ(OkStatus(), reader.Open());
-
-  EXPECT_EQ(kBlobDataSize, reader.ConservativeReadLimit());
-  EXPECT_EQ(0u, reader.ConservativeWriteLimit());
-}
-
 TEST_F(BlobStoreTest, IsOpen) {
   constexpr size_t kBufferSize = 256;
   BlobStoreBuffer<kBufferSize> blob(
       "Blob_open", partition_, nullptr, kvs::TestKvs(), kBufferSize);
   EXPECT_EQ(OkStatus(), blob.Init());
 
-  BlobStore::DeferredWriterWithBuffer deferred_writer(blob);
+  BlobStore::DeferredWriter deferred_writer(blob);
   EXPECT_EQ(false, deferred_writer.IsOpen());
   EXPECT_EQ(OkStatus(), deferred_writer.Open());
   EXPECT_EQ(true, deferred_writer.IsOpen());
   EXPECT_EQ(OkStatus(), deferred_writer.Close());
   EXPECT_EQ(false, deferred_writer.IsOpen());
 
-  BlobStore::BlobWriterWithBuffer writer(blob);
+  BlobStore::BlobWriter writer(blob);
   EXPECT_EQ(false, writer.IsOpen());
   EXPECT_EQ(OkStatus(), writer.Open());
   EXPECT_EQ(true, writer.IsOpen());
 
-  EXPECT_FALSE(blob.HasData());
-
   // Need to write something, so the blob reader is able to open.
   std::array<std::byte, 64> tmp_buffer = {};
   EXPECT_EQ(OkStatus(), writer.Write(tmp_buffer));
   EXPECT_EQ(OkStatus(), writer.Close());
   EXPECT_EQ(false, writer.IsOpen());
 
-  EXPECT_TRUE(blob.HasData());
   BlobStore::BlobReader reader(blob);
   EXPECT_EQ(false, reader.IsOpen());
   ASSERT_EQ(OkStatus(), reader.Open());
@@ -279,291 +198,6 @@
   EXPECT_EQ(false, reader.IsOpen());
 }
 
-// Write to the blob using no write buffer size. Write operations must be
-// multiples of flash_write_size_bytes.
-TEST_F(BlobStoreTest, NoWriteBuffer_1Alignment) {
-  if (kFlashAlignment > 1) {
-    // Test not valid for flash alignments greater than 1.
-    return;
-  }
-
-  const size_t kWriteSizeBytes = 1;
-  kvs::ChecksumCrc16 checksum;
-
-  InitSourceBufferToRandom(0xaabd123);
-
-  ConstByteSpan write_data = std::span(source_buffer_);
-  ConstByteSpan original_source = std::span(source_buffer_);
-
-  EXPECT_EQ(OkStatus(), partition_.Erase());
-
-  BlobStore blob(kBlobTitle,
-                 partition_,
-                 &checksum,
-                 kvs::TestKvs(),
-                 std::span<std::byte>(),
-                 kWriteSizeBytes);
-  EXPECT_EQ(OkStatus(), blob.Init());
-
-  BlobStore::BlobWriterWithBuffer writer(blob);
-  EXPECT_EQ(OkStatus(), writer.Open());
-
-  size_t test_write_size[] = {1, 1, 2, 4, 32, 128};
-
-  for (size_t size : test_write_size) {
-    ASSERT_EQ(OkStatus(), writer.Write(write_data.first(size)));
-    write_data = write_data.subspan(size);
-  }
-
-  while (write_data.size_bytes() > 0) {
-    const size_t finish_write_size = 8;
-    ASSERT_EQ(OkStatus(), writer.Write(write_data.first(finish_write_size)));
-    write_data = write_data.subspan(finish_write_size);
-  }
-  EXPECT_EQ(write_data.size_bytes(), 0U);
-  EXPECT_EQ(OkStatus(), writer.Close());
-
-  // Use reader to check for valid data.
-  BlobStore::BlobReader reader(blob);
-  ASSERT_EQ(OkStatus(), reader.Open());
-  Result<ConstByteSpan> result = reader.GetMemoryMappedBlob();
-  ASSERT_TRUE(result.ok());
-  EXPECT_EQ(original_source.size_bytes(), result.value().size_bytes());
-  VerifyFlash(result.value());
-  VerifyFlash(flash_.buffer());
-  EXPECT_EQ(OkStatus(), reader.Close());
-}
-
-// Write to the blob using no write buffer size. Write operations must be
-// multiples of flash_write_size_bytes.
-TEST_F(BlobStoreTest, NoWriteBuffer_16Alignment) {
-  if (kFlashAlignment > 16) {
-    // Test not valid for flash alignments greater than 16.
-    return;
-  }
-
-  const size_t kWriteSizeBytes = 16;
-  kvs::ChecksumCrc16 checksum;
-
-  InitSourceBufferToRandom(0x6745d123);
-
-  ConstByteSpan write_data = std::span(source_buffer_);
-  ConstByteSpan original_source = std::span(source_buffer_);
-
-  EXPECT_EQ(OkStatus(), partition_.Erase());
-
-  BlobStore blob(kBlobTitle,
-                 partition_,
-                 &checksum,
-                 kvs::TestKvs(),
-                 std::span<std::byte>(),
-                 kWriteSizeBytes);
-  EXPECT_EQ(OkStatus(), blob.Init());
-
-  BlobStore::BlobWriterWithBuffer writer(blob);
-  EXPECT_EQ(OkStatus(), writer.Open());
-  ASSERT_EQ(Status::InvalidArgument(), writer.Write(write_data.first(1)));
-  ASSERT_EQ(Status::InvalidArgument(),
-            writer.Write(write_data.first(kWriteSizeBytes / 2)));
-
-  ASSERT_EQ(OkStatus(), writer.Write(write_data.first(4 * kWriteSizeBytes)));
-  write_data = write_data.subspan(4 * kWriteSizeBytes);
-
-  ASSERT_EQ(Status::InvalidArgument(), writer.Write(write_data.first(1)));
-  ASSERT_EQ(Status::InvalidArgument(),
-            writer.Write(write_data.first(kWriteSizeBytes / 2)));
-
-  while (write_data.size_bytes() > 0) {
-    ASSERT_EQ(OkStatus(), writer.Write(write_data.first(kWriteSizeBytes)));
-    write_data = write_data.subspan(kWriteSizeBytes);
-  }
-  EXPECT_EQ(OkStatus(), writer.Close());
-
-  // Use reader to check for valid data.
-  BlobStore::BlobReader reader(blob);
-  ASSERT_EQ(OkStatus(), reader.Open());
-  Result<ConstByteSpan> result = reader.GetMemoryMappedBlob();
-  ASSERT_TRUE(result.ok());
-  EXPECT_EQ(original_source.size_bytes(), result.value().size_bytes());
-  VerifyFlash(result.value());
-  VerifyFlash(flash_.buffer());
-  EXPECT_EQ(OkStatus(), reader.Close());
-}
-
-TEST_F(BlobStoreTest, FileName) {
-  InitSourceBufferToRandom(0x8675309);
-  WriteTestBlock();
-  constexpr std::string_view kFileName("my_file_1.bin");
-  std::array<std::byte, 64> tmp_buffer = {};
-  static_assert(kFileName.size() <= tmp_buffer.size());
-  kvs::ChecksumCrc16 checksum;
-  constexpr size_t kBufferSize = 256;
-  {
-    // Create/init a blob store in a nested scope so it can be re-initialized
-    // later when checking the read.
-    BlobStoreBuffer<kBufferSize> blob(
-        kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
-    EXPECT_EQ(OkStatus(), blob.Init());
-
-    BlobStore::BlobWriterWithBuffer<kFileName.size()> writer(blob);
-
-    EXPECT_EQ(OkStatus(), writer.Open());
-    EXPECT_EQ(OkStatus(), writer.SetFileName(kFileName));
-    EXPECT_EQ(OkStatus(), writer.Write(tmp_buffer));
-    EXPECT_EQ(OkStatus(), writer.Close());
-    EXPECT_EQ(OkStatus(),
-              kvs::TestKvs().acquire()->Get(kBlobTitle, tmp_buffer).status());
-  }
-
-  BlobStoreBuffer<kBufferSize> blob(
-      kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
-  EXPECT_EQ(OkStatus(), blob.Init());
-
-  // Ensure the file name can be read from a reader.
-  BlobStore::BlobReader reader(blob);
-  ASSERT_EQ(OkStatus(), reader.Open());
-
-  memset(tmp_buffer.data(), 0, tmp_buffer.size());
-  StatusWithSize sws = reader.GetFileName(
-      {reinterpret_cast<char*>(tmp_buffer.data()), tmp_buffer.size()});
-
-  EXPECT_EQ(OkStatus(), sws.status());
-  ASSERT_EQ(kFileName.size(), sws.size());
-  EXPECT_EQ(memcmp(kFileName.data(), tmp_buffer.data(), kFileName.size()), 0);
-}
-
-TEST_F(BlobStoreTest, FileNameUndersizedRead) {
-  InitSourceBufferToRandom(0x8675309);
-  WriteTestBlock();
-  constexpr std::string_view kFileName("my_file_1.bin");
-  std::array<std::byte, 4> tmp_buffer = {};
-  static_assert(kFileName.size() > tmp_buffer.size());
-
-  kvs::ChecksumCrc16 checksum;
-  constexpr size_t kBufferSize = 256;
-  BlobStoreBuffer<kBufferSize> blob(
-      kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
-  EXPECT_EQ(OkStatus(), blob.Init());
-
-  BlobStore::BlobWriterWithBuffer<kFileName.size()> writer(blob);
-
-  EXPECT_EQ(OkStatus(), writer.Open());
-  EXPECT_EQ(OkStatus(), writer.SetFileName(kFileName));
-  EXPECT_EQ(OkStatus(),
-            writer.Write(std::as_bytes(std::span("some interesting data"))));
-  EXPECT_EQ(OkStatus(), writer.Close());
-
-  // Ensure the file name can be read from a reader.
-  BlobStore::BlobReader reader(blob);
-  ASSERT_EQ(OkStatus(), reader.Open());
-
-  StatusWithSize sws = reader.GetFileName(
-      {reinterpret_cast<char*>(tmp_buffer.data()), tmp_buffer.size()});
-  EXPECT_EQ(Status::ResourceExhausted(), sws.status());
-  ASSERT_EQ(tmp_buffer.size(), sws.size());
-  EXPECT_EQ(memcmp(kFileName.data(), tmp_buffer.data(), sws.size()), 0);
-}
-
-TEST_F(BlobStoreTest, FileNameUndersizedSet) {
-  InitSourceBufferToRandom(0x8675309);
-  WriteTestBlock();
-  constexpr std::string_view kFileName("my_file_1.bin");
-
-  kvs::ChecksumCrc16 checksum;
-  constexpr size_t kBufferSize = 256;
-  BlobStoreBuffer<kBufferSize> blob(
-      kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
-  EXPECT_EQ(OkStatus(), blob.Init());
-
-  BlobStore::BlobWriterWithBuffer<2> writer(blob);
-
-  EXPECT_EQ(OkStatus(), writer.Open());
-  EXPECT_EQ(Status::ResourceExhausted(), writer.SetFileName(kFileName));
-  EXPECT_EQ(OkStatus(), writer.Close());
-}
-
-TEST_F(BlobStoreTest, FileNameInvalidation) {
-  InitSourceBufferToRandom(0x8675309);
-  WriteTestBlock();
-
-  constexpr std::string_view kFileName("sliced_cheese.png");
-  std::array<std::byte, 64> tmp_buffer = {};
-  static_assert(kFileName.size() <= tmp_buffer.size());
-
-  kvs::ChecksumCrc16 checksum;
-  constexpr size_t kBufferSize = 256;
-  BlobStoreBuffer<kBufferSize> blob(
-      kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
-  EXPECT_EQ(OkStatus(), blob.Init());
-
-  BlobStore::BlobWriterWithBuffer<kFileName.size()> writer(blob);
-
-  EXPECT_EQ(OkStatus(), writer.Open());
-  EXPECT_EQ(OkStatus(), writer.SetFileName(kFileName));
-  EXPECT_EQ(OkStatus(), writer.Write(tmp_buffer));
-  EXPECT_EQ(OkStatus(), writer.Discard());
-  EXPECT_EQ(OkStatus(), writer.Write(tmp_buffer));
-  EXPECT_EQ(OkStatus(), writer.Close());
-
-  // Check that the file name was discarded by Discard().
-  memset(tmp_buffer.data(), 0, tmp_buffer.size());
-  BlobStore::BlobReader reader(blob);
-  ASSERT_EQ(OkStatus(), reader.Open());
-  StatusWithSize sws = reader.GetFileName(
-      {reinterpret_cast<char*>(tmp_buffer.data()), tmp_buffer.size()});
-  EXPECT_EQ(Status::NotFound(), sws.status());
-  ASSERT_EQ(0u, sws.size());
-}
-
-TEST_F(BlobStoreTest, NoFileName) {
-  InitSourceBufferToRandom(0x8675309);
-  WriteTestBlock();
-
-  std::array<std::byte, 64> tmp_buffer = {};
-  kvs::ChecksumCrc16 checksum;
-  constexpr size_t kBufferSize = 256;
-  BlobStoreBuffer<kBufferSize> blob(
-      kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
-  EXPECT_EQ(OkStatus(), blob.Init());
-
-  // Ensure blobs with no file names work as expected.
-  BlobStore::BlobReader reader(blob);
-  ASSERT_EQ(OkStatus(), reader.Open());
-
-  StatusWithSize sws = reader.GetFileName(
-      {reinterpret_cast<char*>(tmp_buffer.data()), tmp_buffer.size()});
-  EXPECT_EQ(Status::NotFound(), sws.status());
-  ASSERT_EQ(0u, sws.size());
-}
-
-TEST_F(BlobStoreTest, V1MetadataBackwardsCompatible) {
-  constexpr size_t kWriteSize = 25;
-  WriteTestBlock(kWriteSize);
-
-  kvs::ChecksumCrc16 checksum;
-  constexpr size_t kBufferSize = 16;
-  BlobStoreBuffer<kBufferSize> blob(
-      kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
-  EXPECT_EQ(OkStatus(), blob.Init());
-
-  // Read the written data in the current format.
-  internal::BlobMetadataHeader current_metadata;
-  ASSERT_EQ(OkStatus(),
-            kvs::TestKvs().acquire()->Get(kBlobTitle, &current_metadata));
-
-  // Re-save only the V1 metadata contents.
-  ASSERT_EQ(
-      OkStatus(),
-      kvs::TestKvs().acquire()->Put(kBlobTitle, current_metadata.v1_metadata));
-
-  // Ensure the BlobStore's contents aren't invalid.
-  BlobStore::BlobReader reader(blob);
-  ASSERT_EQ(OkStatus(), reader.Open());
-  ASSERT_EQ(kWriteSize, reader.ConservativeReadLimit());
-  ASSERT_EQ(current_metadata.v1_metadata.data_size_bytes,
-            reader.ConservativeReadLimit());
-}
-
 TEST_F(BlobStoreTest, Discard) {
   InitSourceBufferToRandom(0x8675309);
   WriteTestBlock();
@@ -580,35 +214,24 @@
       blob_title, partition_, &checksum, kvs::TestKvs(), kBufferSize);
   EXPECT_EQ(OkStatus(), blob.Init());
 
-  EXPECT_TRUE(blob.HasData());
-
-  BlobStore::BlobWriterWithBuffer writer(blob);
+  BlobStore::BlobWriter writer(blob);
 
   EXPECT_EQ(OkStatus(), writer.Open());
   EXPECT_EQ(OkStatus(), writer.Write(tmp_buffer));
 
-  // Blob should NOT be valid to read, because the write data was only buffered,
-  // and has not been written to flash yet.
-  EXPECT_FALSE(blob.HasData());
-
   // The write does an implicit erase so there should be no key for this blob.
   EXPECT_EQ(Status::NotFound(),
-            kvs::TestKvs().acquire()->Get(blob_title, tmp_buffer).status());
+            kvs::TestKvs().Get(blob_title, tmp_buffer).status());
   EXPECT_EQ(OkStatus(), writer.Close());
 
-  EXPECT_TRUE(blob.HasData());
-
-  EXPECT_EQ(OkStatus(),
-            kvs::TestKvs().acquire()->Get(blob_title, tmp_buffer).status());
+  EXPECT_EQ(OkStatus(), kvs::TestKvs().Get(blob_title, tmp_buffer).status());
 
   EXPECT_EQ(OkStatus(), writer.Open());
   EXPECT_EQ(OkStatus(), writer.Discard());
   EXPECT_EQ(OkStatus(), writer.Close());
 
-  EXPECT_FALSE(blob.HasData());
-
   EXPECT_EQ(Status::NotFound(),
-            kvs::TestKvs().acquire()->Get(blob_title, tmp_buffer).status());
+            kvs::TestKvs().Get(blob_title, tmp_buffer).status());
 }
 
 TEST_F(BlobStoreTest, MultipleErase) {
@@ -617,7 +240,7 @@
       "Blob_OK", partition_, nullptr, kvs::TestKvs(), kBufferSize);
   EXPECT_EQ(OkStatus(), blob.Init());
 
-  BlobStore::BlobWriterWithBuffer writer(blob);
+  BlobStore::BlobWriter writer(blob);
   EXPECT_EQ(OkStatus(), writer.Open());
 
   EXPECT_EQ(OkStatus(), writer.Erase());
@@ -652,34 +275,6 @@
   VerifyFlash(read_buffer, kOffset);
 }
 
-TEST_F(BlobStoreTest, SeekOffsetRead) {
-  InitSourceBufferToRandom(0x11309);
-  WriteTestBlock();
-
-  constexpr size_t kOffset = 10;
-  ASSERT_LT(kOffset, kBlobDataSize);
-
-  kvs::ChecksumCrc16 checksum;
-
-  char name[16] = "TestBlobBlock";
-  constexpr size_t kBufferSize = 16;
-  BlobStoreBuffer<kBufferSize> blob(
-      name, partition_, &checksum, kvs::TestKvs(), kBufferSize);
-  EXPECT_EQ(OkStatus(), blob.Init());
-  BlobStore::BlobReader reader(blob);
-  ASSERT_EQ(OkStatus(), reader.Open());
-  ASSERT_EQ(OkStatus(), reader.Seek(kOffset));
-
-  std::array<std::byte, kBlobDataSize - kOffset> read_buffer;
-  ByteSpan read_span = read_buffer;
-  ASSERT_EQ(read_span.size_bytes(), reader.ConservativeReadLimit());
-
-  auto result = reader.Read(read_span);
-  ASSERT_EQ(result.status(), OkStatus());
-  EXPECT_EQ(OkStatus(), reader.Close());
-  VerifyFlash(read_buffer, kOffset);
-}
-
 TEST_F(BlobStoreTest, InvalidReadOffset) {
   InitSourceBufferToRandom(0x11309);
   WriteTestBlock();
@@ -697,70 +292,7 @@
   ASSERT_EQ(Status::InvalidArgument(), reader.Open(kOffset));
 }
 
-TEST_F(BlobStoreTest, ReadSeekClosedReader) {
-  InitSourceBufferToRandom(0x11309);
-  WriteTestBlock();
-
-  kvs::ChecksumCrc16 checksum;
-
-  char name[16] = "TestBlobBlock";
-  constexpr size_t kBufferSize = 16;
-  BlobStoreBuffer<kBufferSize> blob(
-      name, partition_, &checksum, kvs::TestKvs(), kBufferSize);
-  EXPECT_EQ(OkStatus(), blob.Init());
-  BlobStore::BlobReader reader(blob);
-  ASSERT_EQ(OkStatus(), reader.Open());
-  ASSERT_EQ(OkStatus(), reader.Close());
-
-  EXPECT_EQ(Status::FailedPrecondition(), reader.Seek(0));
-
-  std::byte read_buffer[32];
-  EXPECT_EQ(Status::FailedPrecondition(), reader.Read(read_buffer).status());
-}
-
-TEST_F(BlobStoreTest, InvalidSeekOffset) {
-  InitSourceBufferToRandom(0x11309);
-  WriteTestBlock();
-
-  constexpr size_t kOffset = kBlobDataSize;
-
-  kvs::ChecksumCrc16 checksum;
-
-  char name[16] = "TestBlobBlock";
-  constexpr size_t kBufferSize = 16;
-  BlobStoreBuffer<kBufferSize> blob(
-      name, partition_, &checksum, kvs::TestKvs(), kBufferSize);
-  EXPECT_EQ(OkStatus(), blob.Init());
-  BlobStore::BlobReader reader(blob);
-  ASSERT_EQ(OkStatus(), reader.Open());
-  ASSERT_EQ(Status::OutOfRange(), reader.Seek(kOffset));
-}
-
-// Write a block to blob and close with part of a write buffer with unflushed
-// data.
-TEST_F(BlobStoreTest, WriteBufferWithRemainderInBuffer) {
-  InitSourceBufferToRandom(0x11309);
-
-  kvs::ChecksumCrc16 checksum;
-  constexpr size_t kBufferSize = 256;
-  BlobStoreBuffer<kBufferSize> blob(
-      "TestBlobBlock", partition_, &checksum, kvs::TestKvs(), kBufferSize);
-  EXPECT_EQ(OkStatus(), blob.Init());
-
-  const size_t write_size_bytes = kBlobDataSize - 10;
-  ConstByteSpan write_data = std::span(source_buffer_).first(write_size_bytes);
-
-  BlobStore::BlobWriterWithBuffer writer(blob);
-  EXPECT_EQ(OkStatus(), writer.Open());
-  ASSERT_EQ(OkStatus(), writer.Write(write_data));
-  EXPECT_EQ(OkStatus(), writer.Close());
-
-  BlobStore::BlobReader reader(blob);
-  ASSERT_EQ(OkStatus(), reader.Open());
-  EXPECT_EQ(write_size_bytes, reader.ConservativeReadLimit());
-}
-
-// Test reading with a read buffer larger than the available data in the blob.
+// Test reading with a read buffer larger than the available data in the
 TEST_F(BlobStoreTest, ReadBufferIsLargerThanData) {
   InitSourceBufferToRandom(0x57326);
 
diff --git a/pw_blob_store/docs.rst b/pw_blob_store/docs.rst
index a418f58..906d942 100644
--- a/pw_blob_store/docs.rst
+++ b/pw_blob_store/docs.rst
@@ -1,118 +1,31 @@
 .. _module-pw_blob_store:
 
-=============
+-------------
 pw_blob_store
-=============
+-------------
 ``pw_blob_store`` is a storage container library for storing a single blob of
-data. ``BlobStore`` is a flash-backed persistent storage system with integrated
+data. Blob_store is a flash-backed persistent storage system with integrated
 data integrity checking that serves as a lightweight alternative to a file
 system.
 
------
-Usage
------
-Most operations on a ``BlobStore`` are done using ``BlobReader`` and
-``BlobWriter`` objects that have been constructed using a ``BlobStore``. Though
-a ``BlobStore`` may have multiple open ``BlobReader`` objects, no other
-readers/writers may be active if a ``BlobWriter`` is opened on a blob store.
+Write and read are only done using the BlobWriter and BlobReader classes.
 
-The data state of a blob can be checked using the ``HasData()`` method.
-The method returns true if the blob is currenty valid and has at least one data
-byte. This allows checking if a blob has stored data without needing to
-instantiate and open a reader or writer.
+Once a blob write is closed, reopening for write followed by a Discard(), Write(), or
+Erase() will discard the previous blob.
 
-Write buffer
-============
+Write blob:
+  0) Create BlobWriter instance
+  1) BlobWriter::Open().
+  2) Add data using BlobWriter::Write().
+  3) BlobWriter::Close().
 
-BlobStore uses a write buffer to allow writes smaller than and/or unaligned to
-the flash write aligment. BlobStore also supports using the write buffer for
-deferred writes that can be enqueued and written to flash at a later time or by
-a different thread/context.
-
-BlobStore can be used with a zero-size write buffer to reduce memory
-requirements. When using zero-size write buffer, the user is required to write
-maintain write sizes that are a multiple of the flash write size the blob is
-configured for.
-
-If a non-zero sized write buffer is used, the write buffer size must be a
-multiple of the flash write size.
-
-Writing to a BlobStore
-----------------------
-``BlobWriter`` objects are ``pw::stream::Writer`` compatible, but do not support
-reading any of the blob's contents. Opening a ``BlobWriter`` on a ``BlobStore``
-that contains data will discard any existing data if ``Discard()``, ``Write
-()``, or ``Erase()`` are called. There is currently no mechanism to allow
-appending to existing data.
-
-.. code-block:: cpp
-
-  BlobStore::BlobWriterWithBuffer writer(my_blob_store);
-  writer.Open();
-  writer.Write(my_data);
-
-  // ...
-
-  // A close is implied when a BlobWriter is destroyed. Manually closing a
-  // BlobWriter enables error handling on Close() failure.
-  writer.Close();
-
-Erasing a BlobStore
-===================
-There are two distinctly different mechanisms to "erase" the contents of a BlobStore:
-
-#. ``Discard()``: Discards any ongoing writes and ensures ``BlobReader`` objects
-   see the ``BlobStore`` as empty. This is the fastest way to logically erase a
-   ``BlobStore``.
-#. ``Erase()``: Performs an explicit flash erase of the ``BlobStore``'s
-   underlying partition. This is useful for manually controlling when a flash
-   erase is performed before a ``BlobWriter`` starts to write data (as flash
-   erase operations may be time-consuming).
-
-Naming a BlobStore's contents
-=============================
-Data in a ``BlobStore`` May be named similarly to a file. This enables
-identification of a BlobStore's contents in cases where different data may be
-stored to a shared blob store. This requires an additional RAM buffer that can
-be used to encode the BlobStore's KVS metadata entry. Calling
-``MaxFileNameLength()`` on a ``BlobWriter`` will provide the max file name
-length based on the ``BlobWriter``'s metadata encode buffer size.
-
-``SetFileName()`` performs a copy of the provided file name, meaning it's safe
-for the ``std::string_view`` to be invalidated after the function returns.
-
-.. code-block:: cpp
-
-  constexpr size_t kMaxFileNameLength = 48;
-  BlobStore::BlobWriterWithBuffer<kMaxFileNameLength> writer(my_blob_store);
-  writer.Open();
-  writer.SetFileName("stonks.jpg");
-  writer.Write(my_data);
-  // ...
-  writer.Close();
-
-Reading from a BlobStore
-------------------------
-A ``BlobStore`` may have multiple open ``BlobReader`` objects. No other
-readers/writers may be open/active if a ``BlobWriter`` is opened on a blob
-store.
-
+Read blob:
   0) Create BlobReader instance
   1) BlobReader::Open().
   2) Read data using BlobReader::Read() or
-     BlobReader::GetMemoryMappedBlob(). BlobReader is seekable. Use
-     BlobReader::Seek() to read from a desired offset.
+     BlobReader::GetMemoryMappedBlob().
   3) BlobReader::Close().
 
---------------------------
-FileSystem RPC integration
---------------------------
-``pw_blob_store`` provides an optional ``FileSystemEntry`` implementation for
-use with ``pw_file``'s ``FlatFileSystemService``. This simplifies the process of
-enumerating ``BlobStore`` objects as files via ``pw_file``'s ``FileSystem`` RPC
-service.
-
------------
 Size report
 -----------
 The following size report showcases the memory usage of the blob store.
diff --git a/pw_blob_store/flat_file_system_entry.cc b/pw_blob_store/flat_file_system_entry.cc
deleted file mode 100644
index 042141a..0000000
--- a/pw_blob_store/flat_file_system_entry.cc
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_blob_store/flat_file_system_entry.h"
-
-#include <cstddef>
-#include <mutex>
-#include <span>
-
-#include "pw_assert/check.h"
-#include "pw_blob_store/blob_store.h"
-#include "pw_file/flat_file_system.h"
-#include "pw_status/status.h"
-#include "pw_status/status_with_size.h"
-#include "pw_sync/virtual_basic_lockable.h"
-
-namespace pw::blob_store {
-
-Status FlatFileSystemBlobStoreEntry::Init() {
-  std::lock_guard lock(blob_store_lock_);
-  if (initialized_) {
-    return OkStatus();
-  }
-  const Status status = blob_store_.Init();
-  initialized_ = status.ok();
-  return status;
-}
-
-void FlatFileSystemBlobStoreEntry::EnsureInitialized() {
-  {  // Only hold lock for initial check so Init() doesn't recursively lock.
-    std::lock_guard lock(blob_store_lock_);
-    if (initialized_) {
-      return;
-    }
-  }
-
-  // Don't inline the Init() into the DCHECK() as disabling the DCHECK()
-  // statement would disable the Init() call as well.
-  const Status status = Init();
-  PW_DCHECK_OK(status);
-}
-
-StatusWithSize FlatFileSystemBlobStoreEntry::Name(std::span<char> dest) {
-  EnsureInitialized();
-  std::lock_guard lock(blob_store_lock_);
-  BlobStore::BlobReader reader(blob_store_);
-  if (const Status status = reader.Open(); !status.ok()) {
-    // When a BlobStore is empty, Open() reports FAILED_PRECONDITION. The
-    // FlatFileSystemService expects NOT_FOUND when a file is not present at the
-    // entry.
-    switch (status.code()) {
-      case Status::FailedPrecondition().code():
-        return StatusWithSize(Status::NotFound(), 0);
-      case Status::Unavailable().code():
-        return StatusWithSize(Status::Unavailable(), 0);
-      default:
-        return StatusWithSize(Status::Internal(), 0);
-    }
-  }
-  return reader.GetFileName(dest);
-}
-
-size_t FlatFileSystemBlobStoreEntry::SizeBytes() {
-  EnsureInitialized();
-  std::lock_guard lock(blob_store_lock_);
-  BlobStore::BlobReader reader(blob_store_);
-  if (!reader.Open().ok()) {
-    return 0;
-  }
-  return reader.ConservativeReadLimit();
-}
-
-// TODO(pwbug/488): This file can be deleted even though it is read-only.
-// This type of behavior should be possible to express via the FileSystem RPC
-// service.
-Status FlatFileSystemBlobStoreEntry::Delete() {
-  EnsureInitialized();
-  std::lock_guard lock(blob_store_lock_);
-
-  BlobStore::BlobWriterWithBuffer blob_writer(blob_store_);
-  PW_TRY(blob_writer.Open());
-  return blob_writer.Discard();
-}
-
-}  // namespace pw::blob_store
diff --git a/pw_blob_store/flat_file_system_entry_test.cc b/pw_blob_store/flat_file_system_entry_test.cc
deleted file mode 100644
index 8410dea..0000000
--- a/pw_blob_store/flat_file_system_entry_test.cc
+++ /dev/null
@@ -1,169 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_blob_store/flat_file_system_entry.h"
-
-#include <array>
-#include <cstddef>
-#include <cstring>
-#include <span>
-
-#include "gtest/gtest.h"
-#include "pw_blob_store/blob_store.h"
-#include "pw_kvs/crc16_checksum.h"
-#include "pw_kvs/fake_flash_memory.h"
-#include "pw_kvs/flash_memory.h"
-#include "pw_kvs/test_key_value_store.h"
-#include "pw_random/xor_shift.h"
-#include "pw_sync/mutex.h"
-
-namespace pw::blob_store {
-namespace {
-
-class FlatFileSystemBlobStoreEntryTest : public ::testing::Test {
- protected:
-  static constexpr char kBlobTitle[] = "TestBlobBlock";
-  static constexpr size_t kBufferSize = 64;
-
-  FlatFileSystemBlobStoreEntryTest()
-      : flash_(kFlashAlignment),
-        partition_(&flash_),
-        metadata_buffer_(),
-        source_buffer_(),
-        checksum_(),
-        blob_(kBlobTitle, partition_, &checksum_, kvs::TestKvs(), kBufferSize) {
-  }
-
-  void SetUp() override { ASSERT_EQ(OkStatus(), blob_.Init()); }
-
-  void InitSourceBufferToRandom(uint64_t seed,
-                                size_t init_size_bytes = kBlobDataSize) {
-    ASSERT_LE(init_size_bytes, source_buffer_.size());
-    random::XorShiftStarRng64 rng(seed);
-
-    std::memset(source_buffer_.data(),
-                static_cast<int>(flash_.erased_memory_content()),
-                source_buffer_.size());
-    rng.Get(std::span(source_buffer_).first(init_size_bytes))
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  }
-
-  // Fill the source buffer with random pattern based on given seed, written to
-  // BlobStore in specified chunk size.
-  void WriteTestBlock(std::string_view file_name, size_t write_size_bytes) {
-    ASSERT_LE(write_size_bytes, source_buffer_.size());
-
-    ConstByteSpan write_data =
-        std::span(source_buffer_).first(write_size_bytes);
-
-    BlobStore::BlobWriter writer(blob_, metadata_buffer_);
-    EXPECT_EQ(OkStatus(), writer.Open());
-    ASSERT_EQ(OkStatus(), writer.SetFileName(file_name));
-    ASSERT_EQ(OkStatus(), writer.Write(write_data));
-    EXPECT_EQ(OkStatus(), writer.Close());
-
-    // Use reader to check for valid data.
-    BlobStore::BlobReader reader(blob_);
-    ASSERT_EQ(OkStatus(), reader.Open());
-    Result<ConstByteSpan> result = reader.GetMemoryMappedBlob();
-    ASSERT_TRUE(result.ok());
-    EXPECT_EQ(write_size_bytes, result.value().size_bytes());
-    EXPECT_EQ(OkStatus(), reader.Close());
-  }
-
-  static constexpr size_t kFlashAlignment = 16;
-  static constexpr size_t kSectorSize = 2048;
-  static constexpr size_t kSectorCount = 2;
-  static constexpr size_t kBlobDataSize = (kSectorCount * kSectorSize);
-  static constexpr size_t kMaxFileNameLength = 32;
-  static constexpr size_t kMetadataBufferSize =
-      BlobStore::BlobWriter::RequiredMetadataBufferSize(kMaxFileNameLength);
-
-  kvs::FakeFlashMemoryBuffer<kSectorSize, kSectorCount> flash_;
-  kvs::FlashPartition partition_;
-  std::array<std::byte, kMetadataBufferSize> metadata_buffer_;
-  std::array<std::byte, kBlobDataSize> source_buffer_;
-  kvs::ChecksumCrc16 checksum_;
-  BlobStoreBuffer<kBufferSize> blob_;
-};
-
-TEST_F(FlatFileSystemBlobStoreEntryTest, BasicProperties) {
-  constexpr size_t kWrittenDataSizeBytes = 104;
-  constexpr uint32_t kExpectedFileId = 0x731ACAC0;
-  constexpr FlatFileSystemBlobStoreEntry::FilePermissions kExpectedPermissions =
-      FlatFileSystemBlobStoreEntry::FilePermissions::READ;
-
-  constexpr std::string_view kFileName("my_file_1.bin");
-  InitSourceBufferToRandom(0x5C4CA189);
-  WriteTestBlock(kFileName, kWrittenDataSizeBytes);
-  std::array<char, kMaxFileNameLength> tmp_buffer = {};
-  static_assert(kFileName.size() <= tmp_buffer.size());
-
-  sync::VirtualMutex blob_store_mutex;
-  FlatFileSystemBlobStoreEntry blob_store_file(
-      kExpectedFileId, kExpectedPermissions, blob_, blob_store_mutex);
-
-  StatusWithSize sws = blob_store_file.Name(tmp_buffer);
-  ASSERT_EQ(OkStatus(), sws.status());
-
-  const int comparison =
-      memcmp(tmp_buffer.data(), kFileName.data(), sws.size());
-  EXPECT_EQ(0, comparison);
-  EXPECT_EQ(kWrittenDataSizeBytes, blob_store_file.SizeBytes());
-  EXPECT_EQ(kExpectedPermissions, blob_store_file.Permissions());
-  EXPECT_EQ(kExpectedFileId, blob_store_file.FileId());
-}
-
-TEST_F(FlatFileSystemBlobStoreEntryTest, Delete) {
-  constexpr size_t kWrittenDataSizeBytes = 104;
-  constexpr uint32_t kExpectedFileId = 0x87ED0EF2;
-  constexpr FlatFileSystemBlobStoreEntry::FilePermissions kExpectedPermissions =
-      FlatFileSystemBlobStoreEntry::FilePermissions::READ;
-
-  constexpr std::string_view kFileName("my_file_1.bin");
-  InitSourceBufferToRandom(0x5C4CA189);
-  WriteTestBlock(kFileName, kWrittenDataSizeBytes);
-
-  sync::VirtualMutex blob_store_mutex;
-  FlatFileSystemBlobStoreEntry blob_store_file(
-      kExpectedFileId, kExpectedPermissions, blob_, blob_store_mutex);
-
-  ASSERT_EQ(OkStatus(), blob_store_file.Delete());
-
-  BlobStore::BlobReader reader(blob_);
-  // Failed precondition is the expected return value when a BlobStore is opened
-  // for reading and is empty.
-  ASSERT_EQ(Status::FailedPrecondition(), reader.Open());
-}
-
-TEST_F(FlatFileSystemBlobStoreEntryTest, NoData) {
-  constexpr uint32_t kExpectedFileId = 0x1;
-  constexpr FlatFileSystemBlobStoreEntry::FilePermissions kExpectedPermissions =
-      FlatFileSystemBlobStoreEntry::FilePermissions::READ;
-
-  // Ensure the BlobStore is erased.
-  ASSERT_EQ(OkStatus(), partition_.Erase());
-
-  sync::VirtualMutex blob_store_mutex;
-  FlatFileSystemBlobStoreEntry blob_store_file(
-      kExpectedFileId, kExpectedPermissions, blob_, blob_store_mutex);
-
-  std::array<char, kMaxFileNameLength> tmp_buffer = {};
-  StatusWithSize sws = blob_store_file.Name(tmp_buffer);
-  EXPECT_EQ(Status::NotFound(), sws.status());
-  EXPECT_EQ(0u, sws.size());
-}
-
-}  // namespace
-}  // namespace pw::blob_store
diff --git a/pw_blob_store/public/pw_blob_store/blob_store.h b/pw_blob_store/public/pw_blob_store/blob_store.h
index 899ae83..f8f7b80 100644
--- a/pw_blob_store/public/pw_blob_store/blob_store.h
+++ b/pw_blob_store/public/pw_blob_store/blob_store.h
@@ -13,26 +13,19 @@
 // the License.
 #pragma once
 
-#include <cstddef>
 #include <span>
 
-#include "pw_assert/assert.h"
-#include "pw_blob_store/internal/metadata_format.h"
-#include "pw_bytes/span.h"
+#include "pw_assert/light.h"
 #include "pw_kvs/checksum.h"
 #include "pw_kvs/flash_memory.h"
 #include "pw_kvs/key_value_store.h"
 #include "pw_status/status.h"
-#include "pw_status/status_with_size.h"
-#include "pw_status/try.h"
-#include "pw_stream/seek.h"
 #include "pw_stream/stream.h"
-#include "pw_sync/borrow.h"
 
 namespace pw::blob_store {
 
-// BlobStore is a storage container for a single blob of data. BlobStore is
-// a FlashPartition-backed persistent storage system with integrated data
+// BlobStore is a storage container for a single blob of data. BlobStore is a
+// FlashPartition-backed persistent storage system with integrated data
 // integrity checking that serves as a lightweight alternative to a file
 // system.
 //
@@ -59,38 +52,33 @@
   // already erased, the Write will do any needed erase.
   //
   // Only one writter (of either type) is allowed to be open at a time.
-  // Additionally, writers are unable to open if a reader is already open.
-  class BlobWriter : public stream::NonSeekableWriter {
+  // Additionally, writters are unable to open if a reader is already open.
+  class BlobWriter : public stream::Writer {
    public:
-    constexpr BlobWriter(BlobStore& store, ByteSpan metadata_buffer)
-        : store_(store), metadata_buffer_(metadata_buffer), open_(false) {}
+    constexpr BlobWriter(BlobStore& store) : store_(store), open_(false) {}
     BlobWriter(const BlobWriter&) = delete;
     BlobWriter& operator=(const BlobWriter&) = delete;
     virtual ~BlobWriter() {
       if (open_) {
-        Close().IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        Close();
       }
     }
 
-    static constexpr size_t RequiredMetadataBufferSize(
-        size_t max_file_name_size) {
-      return max_file_name_size + sizeof(internal::BlobMetadataHeader);
-    }
-
     // Open a blob for writing/erasing. Open will invalidate any existing blob
-    // that may be stored, and will not retain the previous file name. Can not
-    // open when already open. Only one writer is allowed to be open at a time.
-    // Returns:
-    //
-    // Preconditions:
-    // This writer must not already be open.
-    // This writer's metadata encode buffer must be at least the size of
-    // internal::BlobMetadataHeader.
+    // that may be stored. Can not open when already open. Only one writer is
+    // allowed to be open at a time. Returns:
     //
     // OK - success.
     // UNAVAILABLE - Unable to open, another writer or reader instance is
     //     already open.
-    Status Open();
+    Status Open() {
+      PW_DASSERT(!open_);
+      Status status = store_.OpenWrite();
+      if (status.ok()) {
+        open_ = true;
+      }
+      return status;
+    }
 
     // Finalize a blob write. Flush all remaining buffered data to storage and
     // store blob metadata. Close fails in the closed state, do NOT retry Close
@@ -99,7 +87,11 @@
     //
     // OK - success.
     // DATA_LOSS - Error writing data or fail to verify written data.
-    Status Close();
+    Status Close() {
+      PW_DASSERT(open_);
+      open_ = false;
+      return store_.CloseWrite();
+    }
 
     bool IsOpen() { return open_; }
 
@@ -111,7 +103,8 @@
     // UNAVAILABLE - Unable to erase while reader is open.
     // [error status] - flash erase failed.
     Status Erase() {
-      return open_ ? store_.Erase() : Status::FailedPrecondition();
+      PW_DASSERT(open_);
+      return store_.Erase();
     }
 
     // Discard the current blob. Any written bytes to this point are considered
@@ -120,84 +113,42 @@
     // OK - success.
     // FAILED_PRECONDITION - not open.
     Status Discard() {
-      return open_ ? store_.Invalidate() : Status::FailedPrecondition();
+      PW_DASSERT(open_);
+      return store_.Invalidate();
     }
 
-    // Sets file name to be associated with the data written by this
-    // ``BlobWriter``. This may be changed any time before Close() is called.
-    //
-    // Calling Discard() or Erase() will clear any set file name.
-    //
-    // The underlying buffer behind file_name may be invalidated after this
-    // function returns as the string is copied to the internally managed encode
-    // buffer.
-    //
-    // Preconditions:
-    // This writer must be open.
-    //
-    // OK - successfully set file name.
-    // RESOURCE_EXHAUSTED - File name too large to fit in metadata encode
-    //   buffer, file name not set.
-    Status SetFileName(std::string_view file_name);
-
-    size_t CurrentSizeBytes() const {
-      return open_ ? store_.write_address_ : 0;
+    // Probable (not guaranteed) minimum number of bytes at this time that can
+    // be written. This is not necessarily the full number of bytes remaining in
+    // the blob. Returns zero if, in the current state, Write would return
+    // status other than OK. See stream.h for additional details.
+    size_t ConservativeWriteLimit() const override {
+      PW_DASSERT(open_);
+      return store_.WriteBytesRemaining();
     }
 
-    // Max file name length, not including null terminator (null terminators
-    // are not stored).
-    size_t MaxFileNameLength() {
-      return metadata_buffer_.size_bytes() <
-                     sizeof(internal::BlobMetadataHeader)
-                 ? 0
-                 : metadata_buffer_.size_bytes() -
-                       sizeof(internal::BlobMetadataHeader);
+    size_t CurrentSizeBytes() {
+      PW_DASSERT(open_);
+      return store_.write_address_;
     }
 
    protected:
     Status DoWrite(ConstByteSpan data) override {
-      return open_ ? store_.Write(data) : Status::FailedPrecondition();
+      PW_DASSERT(open_);
+      return store_.Write(data);
     }
 
-    // Commits changes to KVS as a BlobStore metadata entry.
-    Status WriteMetadata();
-
     BlobStore& store_;
-    ByteSpan metadata_buffer_;
     bool open_;
-
-   private:
-    // Probable (not guaranteed) minimum number of bytes at this time that can
-    // be written. This is not necessarily the full number of bytes remaining in
-    // the blob. Returns zero if, in the current state, Write would return
-    // status other than OK. See stream.h for additional details.
-    size_t ConservativeLimit(LimitType limit) const override {
-      if (open_ && limit == LimitType::kWrite) {
-        return store_.WriteBytesRemaining();
-      }
-      return 0;
-    }
-  };
-
-  template <size_t kMaxFileNameSize = 0>
-  class BlobWriterWithBuffer final : public BlobWriter {
-   public:
-    constexpr BlobWriterWithBuffer(BlobStore& store)
-        : BlobWriter(store, buffer_), buffer_() {}
-
-   private:
-    std::array<std::byte, RequiredMetadataBufferSize(kMaxFileNameSize)> buffer_;
   };
 
   // Implement the stream::Writer and erase interface with deferred action for a
   // BlobStore. If not already erased, the Flush will do any needed erase.
   //
   // Only one writter (of either type) is allowed to be open at a time.
-  // Additionally, writers are unable to open if a reader is already open.
-  class DeferredWriter : public BlobWriter {
+  // Additionally, writters are unable to open if a reader is already open.
+  class DeferredWriter final : public BlobWriter {
    public:
-    constexpr DeferredWriter(BlobStore& store, ByteSpan metadata_buffer)
-        : BlobWriter(store, metadata_buffer) {}
+    constexpr DeferredWriter(BlobStore& store) : BlobWriter(store) {}
     DeferredWriter(const DeferredWriter&) = delete;
     DeferredWriter& operator=(const DeferredWriter&) = delete;
     virtual ~DeferredWriter() {}
@@ -206,58 +157,38 @@
     // are written in the flush. Any remainder is held until later for either
     // a flush with flash_write_size_bytes buffered or the writer is closed.
     Status Flush() {
-      return open_ ? store_.Flush() : Status::FailedPrecondition();
+      PW_DASSERT(open_);
+      return store_.Flush();
     }
 
     // Probable (not guaranteed) minimum number of bytes at this time that can
-    // be written. This is not necessarily the full number of bytes remaining in
+    // be written. This is not necessarily the full number of bytes remaining in
     // the blob. Returns zero if, in the current state, Write would return
     // status other than OK. See stream.h for additional details.
-    size_t ConservativeLimit(LimitType limit) const final {
-      if (open_ && limit == LimitType::kWrite) {
-        // Deferred writes need to fit in the write buffer.
-        return store_.WriteBufferBytesFree();
-      }
-      return 0;
+    size_t ConservativeWriteLimit() const override {
+      PW_DASSERT(open_);
+      // Deferred writes need to fit in the write buffer.
+      return store_.WriteBufferBytesFree();
     }
 
    private:
-    // Similar to normal Write, but instead immediately writing out to flash,
-    // it only buffers the data. A flush or Close is reqired to get bytes
-    // writen out to flash.
-    //
-    // AddToWriteBuffer will continue to accept new data after Flush has an
-    // erase error (buffer space permitting). Write errors during Flush will
-    // result in no new data being accepted.
-    Status DoWrite(ConstByteSpan data) final {
-      return open_ ? store_.AddToWriteBuffer(data)
-                   : Status::FailedPrecondition();
+    Status DoWrite(ConstByteSpan data) override {
+      PW_DASSERT(open_);
+      return store_.AddToWriteBuffer(data);
     }
   };
 
-  template <size_t kMaxFileNameSize = 0>
-  class DeferredWriterWithBuffer final : public DeferredWriter {
-   public:
-    constexpr DeferredWriterWithBuffer(BlobStore& store)
-        : DeferredWriter(store, buffer_), buffer_() {}
-
-   private:
-    std::array<std::byte, RequiredMetadataBufferSize(kMaxFileNameSize)> buffer_;
-  };
-
   // Implement stream::Reader interface for BlobStore. Multiple readers may be
   // open at the same time, but readers may not be open with a writer open.
-  class BlobReader final : public stream::SeekableReader {
+  class BlobReader final : public stream::Reader {
    public:
     constexpr BlobReader(BlobStore& store)
         : store_(store), open_(false), offset_(0) {}
-
     BlobReader(const BlobReader&) = delete;
     BlobReader& operator=(const BlobReader&) = delete;
-
     ~BlobReader() {
       if (open_) {
-        Close().IgnoreError();
+        Close();
       }
     }
 
@@ -265,68 +196,66 @@
     // when already open. Multiple readers can be open at the same time.
     // Returns:
     //
-    //   OK - success.
-    //   FAILED_PRECONDITION - No readable blob available.
-    //   INVALID_ARGUMENT - Invalid offset.
-    //   UNAVAILABLE - Unable to open, already open.
-    //
-    Status Open(size_t offset = 0);
+    // OK - success.
+    // FAILED_PRECONDITION - No readable blob available.
+    // INVALID_ARGUMENT - Invalid offset.
+    // UNAVAILABLE - Unable to open, already open.
+    Status Open(size_t offset = 0) {
+      PW_DASSERT(!open_);
+      if (!store_.ValidToRead()) {
+        return Status::FailedPrecondition();
+      }
+      if (offset >= store_.ReadableDataBytes()) {
+        return Status::InvalidArgument();
+      }
+
+      offset_ = offset;
+      Status status = store_.OpenRead();
+      if (status.ok()) {
+        open_ = true;
+      }
+      return status;
+    }
 
     // Finish reading a blob. Close fails in the closed state, do NOT retry
     // Close on error. Returns:
     //
-    //   OK - success
-    //   FAILED_PRECONDITION - already closed
-    //
+    // OK - success.
     Status Close() {
-      if (!open_) {
-        return Status::FailedPrecondition();
-      }
+      PW_DASSERT(open_);
       open_ = false;
       return store_.CloseRead();
     }
 
-    // Copies the file name of the stored data to `dest`, and returns the number
-    // of bytes written to the destination buffer. The string is not
-    // null-terminated.
-    //
-    // Returns:
-    //   OK - File name copied, size contains file name length.
-    //   RESOURCE_EXHAUSTED - `dest` too small to fit file name, size contains
-    //     first N bytes of the file name.
-    //   NOT_FOUND - No file name set for this blob.
-    //   FAILED_PRECONDITION - not open
-    //
-    StatusWithSize GetFileName(std::span<char> dest) {
-      return open_ ? store_.GetFileName(dest)
-                   : StatusWithSize::FailedPrecondition();
-    }
+    bool IsOpen() { return open_; }
 
-    bool IsOpen() const { return open_; }
-
-    // Get a span with the MCU pointer and size of the data. Returns:
-    //
-    //   OK with span - Valid span respresenting the blob data
-    //   FAILED_PRECONDITION - Reader not open.
-    //   UNIMPLEMENTED - Memory mapped access not supported for this blob.
-    //   FAILED_PRECONDITION - Writer is closed
-    //
-    Result<ConstByteSpan> GetMemoryMappedBlob() {
-      return open_ ? store_.GetMemoryMappedBlob()
-                   : Status::FailedPrecondition();
-    }
-
-   private:
     // Probable (not guaranteed) minimum number of bytes at this time that can
     // be read. Returns zero if, in the current state, Read would return status
     // other than OK. See stream.h for additional details.
-    size_t ConservativeLimit(LimitType limit) const override;
+    size_t ConservativeReadLimit() const override {
+      PW_DASSERT(open_);
+      return store_.ReadableDataBytes() - offset_;
+    }
 
-    size_t DoTell() const override;
+    // Get a span with the MCU pointer and size of the data. Returns:
+    //
+    // OK with span - Valid span respresenting the blob data
+    // FAILED_PRECONDITION - Reader not open.
+    // UNIMPLEMENTED - Memory mapped access not supported for this blob.
+    Result<ConstByteSpan> GetMemoryMappedBlob() {
+      PW_DASSERT(open_);
+      return store_.GetMemoryMappedBlob();
+    }
 
-    Status DoSeek(ptrdiff_t offset, Whence origin) override;
-
-    StatusWithSize DoRead(ByteSpan dest) override;
+   private:
+    StatusWithSize DoRead(ByteSpan dest) override {
+      PW_DASSERT(open_);
+      StatusWithSize status = store_.Read(offset_, dest);
+      if (status.ok()) {
+        offset_ += status.size();
+      }
+      return status;
+    }
 
     BlobStore& store_;
     bool open_;
@@ -349,7 +278,7 @@
   BlobStore(std::string_view name,
             kvs::FlashPartition& partition,
             kvs::ChecksumAlgorithm* checksum_algo,
-            sync::Borrowable<kvs::KeyValueStore>& kvs,
+            kvs::KeyValueStore& kvs,
             ByteSpan write_buffer,
             size_t flash_write_size_bytes)
       : name_(name),
@@ -363,9 +292,9 @@
         flash_erased_(false),
         writer_open_(false),
         readers_open_(0),
+        metadata_({}),
         write_address_(0),
-        flash_address_(0),
-        file_name_length_(0) {}
+        flash_address_(0) {}
 
   BlobStore(const BlobStore&) = delete;
   BlobStore& operator=(const BlobStore&) = delete;
@@ -379,18 +308,9 @@
   // Maximum number of data bytes this BlobStore is able to store.
   size_t MaxDataSizeBytes() const;
 
-  // Get the current data state of the blob without needing to instantiate
-  // and/or open a reader or writer. This check is independent of any writers or
-  // readers of this blob that might exist (open or closed).
-  //
-  // NOTE: This state can be changed by any writer that is open(ed) for this
-  //       blob. Readers can not be opened until any open writers are closed.
-  //
-  // true -  Blob is valid/OK and has at least 1 data byte.
-  // false -  Blob is either invalid or does not have any data bytes
-  bool HasData() const { return (valid_data_ && ReadableDataBytes() > 0); }
-
  private:
+  typedef uint32_t ChecksumValue;
+
   Status LoadMetadata();
 
   // Open to do a blob write. Returns:
@@ -412,6 +332,7 @@
   // OK - success, valid complete blob.
   // DATA_LOSS - Error during write (this close or previous write/flush). Blob
   //     is closed and marked as invalid.
+  Status CloseWrite();
   Status CloseRead();
 
   // Write/append data to the in-progress blob write. Data is written
@@ -430,11 +351,7 @@
 
   // Similar to Write, but instead immediately writing out to flash, it only
   // buffers the data. A flush or Close is reqired to get bytes writen out to
-  // flash.
-  //
-  // AddToWriteBuffer will continue to accept new data after Flush has an erase
-  // error (buffer space permitting). Write errors during Flush will result in
-  // no new data being accepted.
+  // flash
   //
   // OK - successful write/enqueue of data.
   // RESOURCE_EXHAUSTED - unable to write all of requested data at this time. No
@@ -478,7 +395,7 @@
   // true - Blob is valid and OK to write to.
   // false - Blob has previously had an error and not valid for writing new
   //     data.
-  bool ValidToWrite() { return (valid_data_ == true) || (flash_address_ == 0); }
+  bool ValidToWrite() { return (valid_data_ == true) || (write_address_ == 0); }
 
   bool WriteBufferEmpty() const { return flash_address_ == write_address_; }
 
@@ -488,6 +405,9 @@
 
   Status EraseIfNeeded();
 
+  // Blob is valid/OK and has data to read.
+  bool ValidToRead() const { return (valid_data_ && ReadableDataBytes() > 0); }
+
   // Read valid data. Attempts to read the lesser of output.size_bytes() or
   // available bytes worth of data. Returns:
   //
@@ -525,30 +445,34 @@
     }
   }
 
-  Status ValidateChecksum(size_t blob_size_bytes,
-                          internal::ChecksumValue expected);
+  Status ValidateChecksum();
 
   Status CalculateChecksumFromFlash(size_t bytes_to_check);
 
-  const std::string_view MetadataKey() const { return name_; }
+  const std::string_view MetadataKey() { return name_; }
 
-  // Copies the file name of the stored data to `dest`, and returns the number
-  // of bytes written to the destination buffer. The string is not
-  // null-terminated.
-  //
-  // Returns:
-  //   OK - File name copied, size contains file name length.
-  //   RESOURCE_EXHAUSTED - `dest` too small to fit file name, size contains
-  //     first N bytes of the file name.
-  //   NOT_FOUND - No file name set for this blob.
-  //   FAILED_PRECONDITION - BlobStore has not been initialized.
-  StatusWithSize GetFileName(std::span<char> dest) const;
+  // Changes to the metadata format should also get a different key signature to
+  // avoid new code improperly reading old format metadata.
+  struct BlobMetadata {
+    // The checksum of the blob data stored in flash.
+    ChecksumValue checksum;
+
+    // Number of blob data bytes stored in flash.
+    size_t data_size_bytes;
+
+    constexpr void reset() {
+      *this = {
+          .checksum = 0,
+          .data_size_bytes = 0,
+      };
+    }
+  };
 
   std::string_view name_;
   kvs::FlashPartition& partition_;
   // checksum_algo_ of nullptr indicates no checksum algorithm.
   kvs::ChecksumAlgorithm* const checksum_algo_;
-  sync::Borrowable<kvs::KeyValueStore>& kvs_;
+  kvs::KeyValueStore& kvs_;
   ByteSpan write_buffer_;
 
   // Size in bytes of flash write operations. This should be chosen to balance
@@ -577,23 +501,23 @@
   // Count of open BlobReader instances
   size_t readers_open_;
 
-  // Current index for end of overall blob data. Represents current byte size of
+  // Metadata for the blob.
+  BlobMetadata metadata_;
+
+  // Current index for end of overal blob data. Represents current byte size of
   // blob data since the FlashPartition starts at address 0.
   kvs::FlashPartition::Address write_address_;
 
   // Current index of end of data written to flash. Number of buffered data
   // bytes is write_address_ - flash_address_.
   kvs::FlashPartition::Address flash_address_;
-
-  // Length of the stored blob's filename.
-  size_t file_name_length_;
 };
 
 // Creates a BlobStore with the buffer of kBufferSizeBytes.
 //
 // kBufferSizeBytes - Size in bytes of write buffer to create.
 // name - Name of blob store, used for metadata KVS key
-// partition - Flash partition to use for this blob. Blob uses the entire
+// partition - Flash partiton to use for this blob. Blob uses the entire
 //     partition for blob data.
 // checksum_algo - Optional checksum for blob integrity checking. Use nullptr
 //     for no check.
@@ -611,7 +535,7 @@
   explicit BlobStoreBuffer(std::string_view name,
                            kvs::FlashPartition& partition,
                            kvs::ChecksumAlgorithm* checksum_algo,
-                           sync::Borrowable<kvs::KeyValueStore>& kvs,
+                           kvs::KeyValueStore& kvs,
                            size_t flash_write_size_bytes)
       : BlobStore(name,
                   partition,
diff --git a/pw_blob_store/public/pw_blob_store/flat_file_system_entry.h b/pw_blob_store/public/pw_blob_store/flat_file_system_entry.h
deleted file mode 100644
index 6cefcae..0000000
--- a/pw_blob_store/public/pw_blob_store/flat_file_system_entry.h
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstddef>
-#include <span>
-
-#include "pw_blob_store/blob_store.h"
-#include "pw_file/flat_file_system.h"
-#include "pw_status/status.h"
-#include "pw_status/status_with_size.h"
-#include "pw_sync/lock_annotations.h"
-#include "pw_sync/virtual_basic_lockable.h"
-
-namespace pw::blob_store {
-
-class FlatFileSystemBlobStoreEntry final
-    : public file::FlatFileSystemService::Entry {
- public:
-  using file::FlatFileSystemService::Entry::FilePermissions;
-  using file::FlatFileSystemService::Entry::Id;
-
-  // File IDs must be globally unique, and map to a pw_transfer TransferService
-  // read/write handler ID.
-  //
-  // TODO(pwbug/492): When BlobStore access is thread-safe, the mutex can be
-  // dropped.
-  FlatFileSystemBlobStoreEntry(Id file_id,
-                               FilePermissions permissions,
-                               BlobStore& blob_store,
-                               sync::VirtualBasicLockable& blob_store_lock)
-      : file_id_(file_id),
-        permissions_(permissions),
-        initialized_(false),
-        blob_store_(blob_store),
-        blob_store_lock_(blob_store_lock) {}
-
-  // Initializes the underlying BlobStore. Calling this before use is optional,
-  // as this class will also lazy-init
-  Status Init();
-
-  StatusWithSize Name(std::span<char> dest) final;
-
-  size_t SizeBytes() final;
-
-  FilePermissions Permissions() const final { return permissions_; }
-
-  Status Delete() final;
-
-  Id FileId() const override { return file_id_; }
-
- private:
-  // Initializes the BlobStore if uninitialized, and CHECK()s initialization
-  // to ensure it succeeded.
-  void EnsureInitialized();
-
-  const Id file_id_;
-  const FilePermissions permissions_;
-  bool initialized_ PW_GUARDED_BY(blob_store_lock_);
-  blob_store::BlobStore& blob_store_ PW_GUARDED_BY(blob_store_lock_);
-  sync::VirtualBasicLockable& blob_store_lock_;
-};
-
-}  // namespace pw::blob_store
diff --git a/pw_blob_store/public/pw_blob_store/internal/metadata_format.h b/pw_blob_store/public/pw_blob_store/internal/metadata_format.h
deleted file mode 100644
index 2d7a293..0000000
--- a/pw_blob_store/public/pw_blob_store/internal/metadata_format.h
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstddef>
-
-#include "pw_preprocessor/compiler.h"
-
-namespace pw::blob_store::internal {
-
-enum MetadataVersion : uint32_t {
-  // Original metadata format does not include a version.
-  kVersion1 = 0,
-  kVersion2 = 0x1197851D,
-  kLatest = kVersion2
-};
-
-// Technically the original BlobMetadataV1 was not packed.
-PW_PACKED(struct) BlobMetadataV1 {
-  typedef uint32_t ChecksumValue;
-
-  // The checksum of the blob data stored in flash.
-  ChecksumValue checksum;
-
-  // Number of blob data bytes stored in flash.
-  // Technically this was originally size_t, but backwards compatibility for
-  // platform-specific sized types has been dropped.
-  uint32_t data_size_bytes;
-};
-
-// Changes to the metadata format should also get a different key signature to
-// avoid new code improperly reading old format metadata.
-PW_PACKED(struct) BlobMetadataHeaderV2 {
-  BlobMetadataV1 v1_metadata;
-
-  // Metadata encoding version stored in flash.
-  MetadataVersion version;
-
-  // Length of the file name stored in the metadata entry.
-  uint8_t file_name_length;
-
-  // Following this struct is file_name_length chars of file name. Note that
-  // the string of characters is NOT null terminated.
-
-  constexpr void reset() {
-    *this = {
-        .v1_metadata =
-            {
-                .checksum = 0,
-                .data_size_bytes = 0,
-            },
-        .version = MetadataVersion::kLatest,
-        .file_name_length = 0,
-    };
-  }
-};
-
-using BlobMetadataHeader = BlobMetadataHeaderV2;
-using ChecksumValue = BlobMetadataV1::ChecksumValue;
-
-}  // namespace pw::blob_store::internal
diff --git a/pw_blob_store/size_report/BUILD b/pw_blob_store/size_report/BUILD
new file mode 100644
index 0000000..4811b4f
--- /dev/null
+++ b/pw_blob_store/size_report/BUILD
@@ -0,0 +1,63 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_binary",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_binary(
+    name = "base",
+    srcs = ["base.cc"],
+    deps = [
+        "//pw_assert",
+        "//pw_bloat:bloat_this_binary",
+        "//pw_kvs",
+        "//pw_kvs:flash_test_partition",
+        "//pw_kvs:fake_flash_12_byte_partition",
+        "//pw_log",
+    ],
+)
+
+pw_cc_binary(
+    name = "basic_blob",
+    srcs = ["basic_blob.cc"],
+    deps = [
+        "//pw_assert",
+        "//pw_bloat:bloat_this_binary",
+        "//pw_blob_store",
+        "//pw_kvs",
+        "//pw_kvs:flash_test_partition",
+        "//pw_kvs:fake_flash_12_byte_partition",
+        "//pw_log",
+    ],
+)
+
+pw_cc_binary(
+    name = "deferred_write_blob",
+    srcs = ["deferred_write_blob.cc"],
+    deps = [
+        "//pw_assert",
+        "//pw_bloat:bloat_this_binary",
+        "//pw_blob_store",
+        "//pw_kvs",
+        "//pw_kvs:flash_test_partition",
+        "//pw_kvs:fake_flash_12_byte_partition",
+        "//pw_log",
+    ],
+)
diff --git a/pw_blob_store/size_report/BUILD.bazel b/pw_blob_store/size_report/BUILD.bazel
deleted file mode 100644
index 509d763..0000000
--- a/pw_blob_store/size_report/BUILD.bazel
+++ /dev/null
@@ -1,69 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_binary",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_binary(
-    name = "base",
-    srcs = ["base.cc"],
-    deps = [
-        "//pw_assert",
-        "//pw_bloat:bloat_this_binary",
-        "//pw_kvs",
-        "//pw_kvs:fake_flash_12_byte_partition",
-        "//pw_kvs:flash_test_partition",
-        "//pw_log",
-        "//pw_sync:borrow",
-        "//pw_sync:virtual_basic_lockable",
-    ],
-)
-
-pw_cc_binary(
-    name = "basic_blob",
-    srcs = ["basic_blob.cc"],
-    deps = [
-        "//pw_assert",
-        "//pw_bloat:bloat_this_binary",
-        "//pw_blob_store",
-        "//pw_kvs",
-        "//pw_kvs:fake_flash_12_byte_partition",
-        "//pw_kvs:flash_test_partition",
-        "//pw_log",
-        "//pw_sync:borrow",
-        "//pw_sync:virtual_basic_lockable",
-    ],
-)
-
-pw_cc_binary(
-    name = "deferred_write_blob",
-    srcs = ["deferred_write_blob.cc"],
-    deps = [
-        "//pw_assert",
-        "//pw_bloat:bloat_this_binary",
-        "//pw_blob_store",
-        "//pw_kvs",
-        "//pw_kvs:fake_flash_12_byte_partition",
-        "//pw_kvs:flash_test_partition",
-        "//pw_log",
-        "//pw_sync:borrow",
-        "//pw_sync:virtual_basic_lockable",
-    ],
-)
diff --git a/pw_blob_store/size_report/BUILD.gn b/pw_blob_store/size_report/BUILD.gn
index 340994b..aca6b84 100644
--- a/pw_blob_store/size_report/BUILD.gn
+++ b/pw_blob_store/size_report/BUILD.gn
@@ -20,8 +20,6 @@
   "$dir_pw_bloat:bloat_this_binary",
   "$dir_pw_kvs:fake_flash_12_byte_partition",
   "$dir_pw_kvs:flash_test_partition",
-  "$dir_pw_sync:borrow",
-  "$dir_pw_sync:virtual_basic_lockable",
   dir_pw_assert,
   dir_pw_kvs,
   dir_pw_log,
diff --git a/pw_blob_store/size_report/base.cc b/pw_blob_store/size_report/base.cc
index 85ffa1a..8f0cc65 100644
--- a/pw_blob_store/size_report/base.cc
+++ b/pw_blob_store/size_report/base.cc
@@ -14,13 +14,11 @@
 
 #include <cstring>
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_bloat/bloat_this_binary.h"
 #include "pw_kvs/flash_test_partition.h"
 #include "pw_kvs/key_value_store.h"
 #include "pw_log/log.h"
-#include "pw_sync/borrow.h"
-#include "pw_sync/virtual_basic_lockable.h"
 
 char working_buffer[256];
 volatile bool is_set;
@@ -37,8 +35,6 @@
 
 pw::kvs::KeyValueStoreBuffer<kKvsMaxEntries, kMaxSectorCount> test_kvs(
     &pw::kvs::FlashTestPartition(), kvs_format);
-pw::sync::Borrowable<pw::kvs::KeyValueStore> borrowable_kvs(
-    test_kvs, pw::sync::NoOpLock::Instance());
 
 int volatile* unoptimizable;
 
@@ -54,21 +50,16 @@
       std::memset((void*)working_buffer, sizeof(working_buffer), 0x55);
   is_set = (result != nullptr);
 
-  {
-    pw::sync::BorrowedPointer<pw::kvs::KeyValueStore> kvs =
-        borrowable_kvs.acquire();
+  test_kvs.Init();
 
-    kvs->Init().IgnoreError();
+  unsigned kvs_value = 42;
+  test_kvs.Put("example_key", kvs_value);
 
-    unsigned kvs_value = 42;
-    kvs->Put("example_key", kvs_value).IgnoreError();
+  kvs_entry_count = test_kvs.size();
 
-    kvs_entry_count = kvs->size();
-
-    unsigned read_value = 0;
-    kvs->Get("example_key", &read_value).IgnoreError();
-    kvs->Delete("example_key").IgnoreError();
-  }
+  unsigned read_value = 0;
+  test_kvs.Get("example_key", &read_value);
+  test_kvs.Delete("example_key");
 
   auto val = pw::kvs::FlashTestPartition().PartitionAddressToMcuAddress(0);
   PW_LOG_INFO("Use the variable. %u", unsigned(*val));
diff --git a/pw_blob_store/size_report/basic_blob.cc b/pw_blob_store/size_report/basic_blob.cc
index a2ea610..667bc01 100644
--- a/pw_blob_store/size_report/basic_blob.cc
+++ b/pw_blob_store/size_report/basic_blob.cc
@@ -14,17 +14,12 @@
 
 #include <cstring>
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_bloat/bloat_this_binary.h"
 #include "pw_blob_store/blob_store.h"
 #include "pw_kvs/flash_test_partition.h"
 #include "pw_kvs/key_value_store.h"
 #include "pw_log/log.h"
-#include "pw_sync/borrow.h"
-#include "pw_sync/virtual_basic_lockable.h"
-
-using pw::blob_store::BlobStore;
-using pw::blob_store::BlobStoreBuffer;
 
 char working_buffer[256];
 volatile bool is_set;
@@ -41,8 +36,6 @@
 
 pw::kvs::KeyValueStoreBuffer<kKvsMaxEntries, kMaxSectorCount> test_kvs(
     &pw::kvs::FlashTestPartition(), kvs_format);
-pw::sync::Borrowable<pw::kvs::KeyValueStore> borrowable_kvs(
-    test_kvs, pw::sync::NoOpLock::Instance());
 
 int volatile* unoptimizable;
 
@@ -58,21 +51,16 @@
       std::memset((void*)working_buffer, sizeof(working_buffer), 0x55);
   is_set = (result != nullptr);
 
-  {
-    pw::sync::BorrowedPointer<pw::kvs::KeyValueStore> kvs =
-        borrowable_kvs.acquire();
+  test_kvs.Init();
 
-    kvs->Init().IgnoreError();
+  unsigned kvs_value = 42;
+  test_kvs.Put("example_key", kvs_value);
 
-    unsigned kvs_value = 42;
-    kvs->Put("example_key", kvs_value).IgnoreError();
+  kvs_entry_count = test_kvs.size();
 
-    kvs_entry_count = kvs->size();
-
-    unsigned read_value = 0;
-    kvs->Get("example_key", &read_value).IgnoreError();
-    kvs->Delete("example_key").IgnoreError();
-  }
+  unsigned read_value = 0;
+  test_kvs.Get("example_key", &read_value);
+  test_kvs.Delete("example_key");
 
   auto val = pw::kvs::FlashTestPartition().PartitionAddressToMcuAddress(0);
   PW_LOG_INFO("Use the variable. %u", unsigned(*val));
@@ -91,29 +79,23 @@
   // Start of basic blob **********************
   constexpr size_t kBufferSize = 1;
 
-  BlobStoreBuffer<kBufferSize> blob(name,
-                                    pw::kvs::FlashTestPartition(),
-                                    nullptr,
-                                    borrowable_kvs,
-                                    kBufferSize);
-  blob.Init().IgnoreError();
+  pw::blob_store::BlobStoreBuffer<kBufferSize> blob(
+      name, pw::kvs::FlashTestPartition(), nullptr, test_kvs, kBufferSize);
+  blob.Init();
 
   // Use writer.
-  constexpr size_t kMetadataBufferSize =
-      BlobStore::BlobWriter::RequiredMetadataBufferSize(0);
-  std::array<std::byte, kMetadataBufferSize> metadata_buffer;
-  BlobStore::BlobWriter writer(blob, metadata_buffer);
-  writer.Open().IgnoreError();
-  writer.Write(write_data).IgnoreError();
-  writer.Close().IgnoreError();
+  pw::blob_store::BlobStore::BlobWriter writer(blob);
+  writer.Open();
+  writer.Write(write_data);
+  writer.Close();
 
   // Use reader.
-  BlobStore::BlobReader reader(blob);
-  reader.Open().IgnoreError();
+  pw::blob_store::BlobStore::BlobReader reader(blob);
+  reader.Open();
   pw::Result<pw::ConstByteSpan> get_result = reader.GetMemoryMappedBlob();
   PW_LOG_INFO("%d", get_result.ok());
   auto reader_result = reader.Read(read_span);
-  reader.Close().IgnoreError();
+  reader.Close();
   PW_LOG_INFO("%d", reader_result.ok());
 
   // End of basic blob **********************
diff --git a/pw_blob_store/size_report/deferred_write_blob.cc b/pw_blob_store/size_report/deferred_write_blob.cc
index 948c5ae..db2f18a 100644
--- a/pw_blob_store/size_report/deferred_write_blob.cc
+++ b/pw_blob_store/size_report/deferred_write_blob.cc
@@ -14,16 +14,12 @@
 
 #include <cstring>
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_bloat/bloat_this_binary.h"
 #include "pw_blob_store/blob_store.h"
 #include "pw_kvs/flash_test_partition.h"
 #include "pw_kvs/key_value_store.h"
 #include "pw_log/log.h"
-#include "pw_sync/borrow.h"
-#include "pw_sync/virtual_basic_lockable.h"
-
-using pw::blob_store::BlobStore;
 
 char working_buffer[256];
 volatile bool is_set;
@@ -41,9 +37,6 @@
 pw::kvs::KeyValueStoreBuffer<kKvsMaxEntries, kMaxSectorCount> test_kvs(
     &pw::kvs::FlashTestPartition(), kvs_format);
 
-pw::sync::Borrowable<pw::kvs::KeyValueStore> borrowable_kvs(
-    test_kvs, pw::sync::NoOpLock::Instance());
-
 int volatile* unoptimizable;
 
 int main() {
@@ -58,21 +51,16 @@
       std::memset((void*)working_buffer, sizeof(working_buffer), 0x55);
   is_set = (result != nullptr);
 
-  {
-    pw::sync::BorrowedPointer<pw::kvs::KeyValueStore> kvs =
-        borrowable_kvs.acquire();
+  test_kvs.Init();
 
-    kvs->Init().IgnoreError();
+  unsigned kvs_value = 42;
+  test_kvs.Put("example_key", kvs_value);
 
-    unsigned kvs_value = 42;
-    kvs->Put("example_key", kvs_value).IgnoreError();
+  kvs_entry_count = test_kvs.size();
 
-    kvs_entry_count = kvs->size();
-
-    unsigned read_value = 0;
-    kvs->Get("example_key", &read_value).IgnoreError();
-    kvs->Delete("example_key").IgnoreError();
-  }
+  unsigned read_value = 0;
+  test_kvs.Get("example_key", &read_value);
+  test_kvs.Delete("example_key");
 
   auto val = pw::kvs::FlashTestPartition().PartitionAddressToMcuAddress(0);
   PW_LOG_INFO("Use the variable. %u", unsigned(*val));
@@ -92,30 +80,23 @@
   constexpr size_t kBufferSize = 1;
 
   pw::blob_store::BlobStoreBuffer<kBufferSize> blob(
-      name,
-      pw::kvs::FlashTestPartition(),
-      nullptr,
-      borrowable_kvs,
-      kBufferSize);
-  blob.Init().IgnoreError();
+      name, pw::kvs::FlashTestPartition(), nullptr, test_kvs, kBufferSize);
+  blob.Init();
 
   // Use writer.
-  constexpr size_t kMetadataBufferSize =
-      BlobStore::BlobWriter::RequiredMetadataBufferSize(0);
-  std::array<std::byte, kMetadataBufferSize> metadata_buffer;
-  pw::blob_store::BlobStore::DeferredWriter writer(blob, metadata_buffer);
-  writer.Open().IgnoreError();
-  writer.Write(write_data).IgnoreError();
-  writer.Flush().IgnoreError();
-  writer.Close().IgnoreError();
+  pw::blob_store::BlobStore::DeferredWriter writer(blob);
+  writer.Open();
+  writer.Write(write_data);
+  writer.Flush();
+  writer.Close();
 
   // Use reader.
   pw::blob_store::BlobStore::BlobReader reader(blob);
-  reader.Open().IgnoreError();
+  reader.Open();
   pw::Result<pw::ConstByteSpan> get_result = reader.GetMemoryMappedBlob();
   PW_LOG_INFO("%d", get_result.ok());
   auto reader_result = reader.Read(read_span);
-  reader.Close().IgnoreError();
+  reader.Close();
   PW_LOG_INFO("%d", reader_result.ok());
 
   // End of deferred blob **********************
diff --git a/pw_bluetooth_hci/BUILD.bazel b/pw_bluetooth_hci/BUILD.bazel
deleted file mode 100644
index 053bcab..0000000
--- a/pw_bluetooth_hci/BUILD.bazel
+++ /dev/null
@@ -1,102 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-load("//pw_fuzzer:fuzzer.bzl", "pw_cc_fuzz_test")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_bluetooth_hci",
-    deps = [
-        ":packet",
-        ":uart_transport",
-    ],
-)
-
-pw_cc_library(
-    name = "packet",
-    srcs = [
-        "packet.cc",
-    ],
-    hdrs = [
-        "public/pw_bluetooth_hci/packet.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_assert",
-        "//pw_bytes",
-        "//pw_result",
-        "//pw_status",
-    ],
-)
-
-pw_cc_library(
-    name = "uart_transport",
-    srcs = [
-        "uart_transport.cc",
-    ],
-    hdrs = [
-        "public/pw_bluetooth_hci/uart_transport.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":packet",
-        "//pw_bytes",
-        "//pw_function",
-        "//pw_status",
-    ],
-)
-
-pw_cc_test(
-    name = "packet_test",
-    srcs = ["packet_test.cc"],
-    deps = [
-        ":packet",
-        "//pw_bytes",
-        "//pw_status",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "uart_transport_test",
-    srcs = ["uart_transport_test.cc"],
-    deps = [
-        ":packet",
-        ":uart_transport",
-        "//pw_bytes",
-        "//pw_status",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_fuzz_test(
-    name = "uart_transport_fuzzer",
-    srcs = ["uart_transport_fuzzer.cc"],
-    deps = [
-        ":packet",
-        ":uart_transport",
-        "//pw_bytes",
-        "//pw_fuzzer",
-        "//pw_status",
-        "//pw_stream",
-    ],
-)
diff --git a/pw_bluetooth_hci/BUILD.gn b/pw_bluetooth_hci/BUILD.gn
deleted file mode 100644
index f701613..0000000
--- a/pw_bluetooth_hci/BUILD.gn
+++ /dev/null
@@ -1,98 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_fuzzer/fuzzer.gni")
-import("$dir_pw_unit_test/test.gni")
-
-config("public_include_path") {
-  include_dirs = [ "public" ]
-  visibility = [ ":*" ]
-}
-
-group("pw_bluetooth_hci") {
-  public_deps = [
-    ":packet",
-    ":uart_transport",
-  ]
-}
-
-pw_source_set("packet") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_bluetooth_hci/packet.h" ]
-  public_deps = [
-    dir_pw_assert,
-    dir_pw_bytes,
-    dir_pw_result,
-  ]
-  sources = [ "packet.cc" ]
-  deps = [ dir_pw_status ]
-}
-
-pw_source_set("uart_transport") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_bluetooth_hci/uart_transport.h" ]
-  sources = [ "uart_transport.cc" ]
-  public_deps = [
-    ":packet",
-    dir_pw_bytes,
-    dir_pw_function,
-    dir_pw_status,
-  ]
-}
-
-pw_test_group("tests") {
-  tests = [
-    ":packet_test",
-    ":uart_transport_test",
-    ":uart_transport_fuzzer",
-  ]
-}
-
-pw_test("packet_test") {
-  sources = [ "packet_test.cc" ]
-  deps = [
-    ":packet",
-    dir_pw_bytes,
-    dir_pw_status,
-  ]
-}
-
-pw_test("uart_transport_test") {
-  sources = [ "uart_transport_test.cc" ]
-  deps = [
-    ":packet",
-    ":uart_transport",
-    dir_pw_bytes,
-    dir_pw_status,
-  ]
-}
-
-pw_fuzzer("uart_transport_fuzzer") {
-  sources = [ "uart_transport_fuzzer.cc" ]
-  deps = [
-    ":packet",
-    ":uart_transport",
-    dir_pw_bytes,
-    dir_pw_status,
-    dir_pw_stream,
-  ]
-}
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
diff --git a/pw_bluetooth_hci/OWNERS b/pw_bluetooth_hci/OWNERS
deleted file mode 100644
index 21d24bc..0000000
--- a/pw_bluetooth_hci/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-ewout@google.com
diff --git a/pw_bluetooth_hci/docs.rst b/pw_bluetooth_hci/docs.rst
deleted file mode 100644
index 863be7c..0000000
--- a/pw_bluetooth_hci/docs.rst
+++ /dev/null
@@ -1,54 +0,0 @@
-.. _module-pw_bluetooth_hci:
-
-================
-pw_bluetooth_hci
-================
-The ``pw_bluetooth_hci`` module contains utilities for using the Host Controller
-Interface as defined by the Bluetooth Core Specification version 5.3.
-
------------
-HCI Packets
------------
-This module has support to parse a the subset of the HCI packets used in the HCI
-UART Transport Layer are provided as defined in the Bluetooth Core Specification
-version 5.3 "Host Controller Interface Transport Layer" volume 4, part A,
-meaning:
-
-* HCI Command Packet: ``pw::bluetooth_hci::CommandPacket``
-
-* HCI ACL Data Packet: ``pw::bluetooth_hci::AsyncDataPacket``
-
-* HCI SCO Data Packet: ``pw::bluetooth_hci::SyncDataPacket``
-
-* HCI Event Data Packet: ``pw::bluetooth_hci::EventPacket``
-
-------------------------
-HCI UART Transport Layer
-------------------------
-
-Decoding
-========
-A decoder function is provided to parse HCI packets out of a HCI UART Transport
-Layer buffer which may contain multiple packets.
-
-  .. cpp:function:: StatusWithSize DecodeHciUartData(ConstByteSpan data, const DecodedPacketCallback& packet_callback);
-
-    Parses the HCI Packets out of a HCI UART Transport Layer buffer.
-
-    Parses as many complete HCI packets out of the provided buffer based on the
-    HCI UART Transport Layer as defined by Bluetooth Core Specification version
-    5.3 "Host Controller Interface Transport Layer" volume 4, part A.
-
-    The HciPacketCallback is invoked for each full HCI packet.
-
-    Returns the number of bytes processed and a status based on:
-
-      * OK - No invalid packet indicator found.
-      * DATA_LOSS - An invalid packet indicator was detected between packets.
-        Synchronization has been lost. The caller is responsible for
-        regaining synchronization
-
-    .. note:: The caller is responsible for detecting the lack of progress due
-      to an undersized data buffer and/or an invalid length field in case a full
-      buffer is passed and no bytes are processed.
-
diff --git a/pw_bluetooth_hci/packet.cc b/pw_bluetooth_hci/packet.cc
deleted file mode 100644
index 7eb7236..0000000
--- a/pw_bluetooth_hci/packet.cc
+++ /dev/null
@@ -1,139 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_bluetooth_hci/packet.h"
-
-#include "pw_bytes/byte_builder.h"
-#include "pw_bytes/endian.h"
-#include "pw_status/try.h"
-
-namespace pw::bluetooth_hci {
-namespace {
-
-using pw::bytes::ReadInOrder;
-
-}  // namespace
-
-Result<ConstByteSpan> CommandPacket::Encode(ByteSpan buffer,
-                                            std::endian order) const {
-  ByteBuilder builder(buffer);
-  builder.PutUint16(opcode_, order);
-  builder.PutUint8(parameters_.size_bytes());
-  builder.append(parameters_);
-  PW_TRY(builder.status());
-  return ConstByteSpan(builder.data(), builder.size());
-};
-
-std::optional<CommandPacket> CommandPacket::Decode(ConstByteSpan data,
-                                                   std::endian order) {
-  if (data.size_bytes() < kHeaderSizeBytes) {
-    return std::nullopt;  // Not enough data to parse the packet header.
-  }
-
-  const uint8_t parameter_total_length =
-      static_cast<uint8_t>(data[kParameterTotalLengthByteOffset]);
-  if (data.size_bytes() < (kHeaderSizeBytes + parameter_total_length)) {
-    return std::nullopt;  // Not enough data to cover the parameter bytes.
-  }
-
-  const uint16_t opcode =
-      ReadInOrder<uint16_t>(order, &data[kOpcodeByteOffset]);
-  return CommandPacket(
-      opcode, ConstByteSpan(&data[kHeaderSizeBytes], parameter_total_length));
-}
-
-Result<ConstByteSpan> AsyncDataPacket::Encode(ByteSpan buffer,
-                                              std::endian order) const {
-  ByteBuilder builder(buffer);
-  builder.PutUint16(handle_and_fragmentation_bits_, order);
-  builder.PutUint16(data_.size_bytes(), order);
-  builder.append(data_);
-  PW_TRY(builder.status());
-  return ConstByteSpan(builder.data(), builder.size());
-};
-
-std::optional<AsyncDataPacket> AsyncDataPacket::Decode(ConstByteSpan data,
-                                                       std::endian order) {
-  if (data.size_bytes() < kHeaderSizeBytes) {
-    return std::nullopt;  // Not enough data to parse the packet header.
-  }
-
-  const uint16_t data_total_length =
-      ReadInOrder<uint16_t>(order, &data[kDataTotalLengthByteOffset]);
-  if (data.size_bytes() < (kHeaderSizeBytes + data_total_length)) {
-    return std::nullopt;  // Not enough data to cover the data bytes.
-  }
-
-  const uint16_t handle_and_flag_bits = ReadInOrder<uint16_t>(
-      order, &data[kHandleAndFragmentationBitsByteOffset]);
-  return AsyncDataPacket(
-      handle_and_flag_bits,
-      ConstByteSpan(&data[kHeaderSizeBytes], data_total_length));
-}
-
-Result<ConstByteSpan> SyncDataPacket::Encode(ByteSpan buffer,
-                                             std::endian order) const {
-  ByteBuilder builder(buffer);
-  builder.PutUint16(handle_and_status_bits_, order);
-  builder.PutUint8(data_.size_bytes());
-  builder.append(data_);
-  PW_TRY(builder.status());
-  return ConstByteSpan(builder.data(), builder.size());
-};
-
-std::optional<SyncDataPacket> SyncDataPacket::Decode(ConstByteSpan data,
-                                                     std::endian order) {
-  if (data.size_bytes() < kHeaderSizeBytes) {
-    return std::nullopt;  // Not enough data to parse the packet header.
-  }
-
-  const uint8_t data_total_length =
-      static_cast<uint8_t>(data[kDataTotalLengthByteOffset]);
-  if (data.size_bytes() < (kHeaderSizeBytes + data_total_length)) {
-    return std::nullopt;  // Not enough data to cover the data bytes.
-  }
-
-  const uint16_t handle_and_status_bits =
-      ReadInOrder<uint16_t>(order, &data[kHandleAndStatusBitsByteOffset]);
-  return SyncDataPacket(
-      handle_and_status_bits,
-      ConstByteSpan(&data[kHeaderSizeBytes], data_total_length));
-}
-
-Result<ConstByteSpan> EventPacket::Encode(ByteSpan buffer) const {
-  ByteBuilder builder(buffer);
-  builder.PutUint8(event_code_);
-  builder.PutUint8(parameters_.size_bytes());
-  builder.append(parameters_);
-  PW_TRY(builder.status());
-  return ConstByteSpan(builder.data(), builder.size());
-};
-
-std::optional<EventPacket> EventPacket::Decode(ConstByteSpan data) {
-  if (data.size_bytes() < kHeaderSizeBytes) {
-    return std::nullopt;  // Not enough data to parse the packet header.
-  }
-
-  const uint8_t parameter_total_length =
-      static_cast<uint8_t>(data[kParameterTotalLengthByteOffset]);
-  if (data.size_bytes() < (kHeaderSizeBytes + parameter_total_length)) {
-    return std::nullopt;  // Not enough data to cover the parameter bytes.
-  }
-
-  const uint8_t event_code = static_cast<uint8_t>(data[kEventCodeByteOffset]);
-  return EventPacket(
-      event_code,
-      ConstByteSpan(&data[kHeaderSizeBytes], parameter_total_length));
-}
-
-}  // namespace pw::bluetooth_hci
diff --git a/pw_bluetooth_hci/packet_test.cc b/pw_bluetooth_hci/packet_test.cc
deleted file mode 100644
index f5301d5..0000000
--- a/pw_bluetooth_hci/packet_test.cc
+++ /dev/null
@@ -1,531 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_bluetooth_hci/packet.h"
-
-#include "gtest/gtest.h"
-#include "pw_bytes/array.h"
-#include "pw_bytes/byte_builder.h"
-#include "pw_status/status.h"
-
-namespace pw::bluetooth_hci {
-namespace {
-
-class PacketTest : public ::testing::Test {
- protected:
-  constexpr static size_t kMaxHeaderSizeBytes = std::max({
-      CommandPacket::kHeaderSizeBytes,
-      AsyncDataPacket::kHeaderSizeBytes,
-      SyncDataPacket::kHeaderSizeBytes,
-      EventPacket::kHeaderSizeBytes,
-  });
-  // Arbitrarily add at most 2 bytes worth of payload (data or parameters).
-  constexpr static size_t kArbitraryMaxPayloadSizeBytes = 2;
-  constexpr static size_t kMaxPacketSizeBytes =
-      kMaxHeaderSizeBytes + kArbitraryMaxPayloadSizeBytes;
-  std::array<std::byte, kMaxPacketSizeBytes> packet_buffer_;
-};
-
-TEST_F(PacketTest, CommandPacketHeaderUndersizedEncode) {
-  const CommandPacket packet(0u, ConstByteSpan());
-  EXPECT_EQ(0u, packet.parameters().size_bytes());
-  const Result<ConstByteSpan> encode_result = packet.Encode(
-      {packet_buffer_.data(), CommandPacket::kHeaderSizeBytes - 1});
-  EXPECT_EQ(Status::ResourceExhausted(), encode_result.status());
-}
-
-TEST_F(PacketTest, CommandPacketHeaderUndersizedDecode) {
-  EXPECT_FALSE(CommandPacket::Decode(
-                   {packet_buffer_.data(), CommandPacket::kHeaderSizeBytes - 1})
-                   .has_value());
-}
-
-TEST_F(PacketTest, CommandPacketHeaderOnlyEncodeAndDecode) {
-  constexpr uint16_t kOpcodeCommandField = 0b00'0000'0000;
-  constexpr uint8_t kOpcodeGroupField = 0b11'1111;
-
-  constexpr uint16_t kOpcode = (kOpcodeGroupField << 10) | kOpcodeCommandField;
-
-  const CommandPacket packet(kOpcode, ConstByteSpan());
-  EXPECT_EQ(packet.type(), Packet::Type::kCommandPacket);
-  EXPECT_EQ(packet.size_bytes(), CommandPacket::kHeaderSizeBytes);
-  EXPECT_EQ(packet.opcode(), kOpcode);
-  EXPECT_EQ(packet.opcode_command_field(), kOpcodeCommandField);
-  EXPECT_EQ(packet.opcode_group_field(), kOpcodeGroupField);
-  EXPECT_EQ(packet.parameters().size_bytes(), 0u);
-
-  const Result<ConstByteSpan> encode_result = packet.Encode(packet_buffer_);
-  ASSERT_EQ(OkStatus(), encode_result.status());
-
-  constexpr std::array<const std::byte, CommandPacket::kHeaderSizeBytes>
-      kExpectedEncodedPacket = bytes::MakeArray<const std::byte>(
-          0b0000'0000, 0b1111'1100, 0b0000'0000);
-  const ConstByteSpan& encoded_packet = encode_result.value();
-  EXPECT_TRUE(std::equal(kExpectedEncodedPacket.begin(),
-                         kExpectedEncodedPacket.end(),
-                         encoded_packet.begin(),
-                         encoded_packet.end()));
-
-  // First, decode it from a perfectly sized span which we just encoded.
-  std::optional<CommandPacket> possible_packet =
-      CommandPacket::Decode(encoded_packet);
-  ASSERT_TRUE(possible_packet.has_value());
-  CommandPacket& decoded_packet = possible_packet.value();
-  EXPECT_EQ(decoded_packet.type(), Packet::Type::kCommandPacket);
-  EXPECT_EQ(decoded_packet.size_bytes(), CommandPacket::kHeaderSizeBytes);
-  EXPECT_EQ(decoded_packet.opcode(), kOpcode);
-  EXPECT_EQ(decoded_packet.opcode_command_field(), kOpcodeCommandField);
-  EXPECT_EQ(decoded_packet.opcode_group_field(), kOpcodeGroupField);
-  EXPECT_EQ(decoded_packet.parameters().size_bytes(), 0u);
-
-  // Second, decode it from an oversized buffer.
-  possible_packet = CommandPacket::Decode({packet_buffer_});
-  ASSERT_TRUE(possible_packet.has_value());
-  decoded_packet = possible_packet.value();
-  EXPECT_EQ(decoded_packet.type(), Packet::Type::kCommandPacket);
-  EXPECT_EQ(decoded_packet.size_bytes(), CommandPacket::kHeaderSizeBytes);
-  EXPECT_EQ(decoded_packet.opcode(), kOpcode);
-  EXPECT_EQ(decoded_packet.opcode_command_field(), kOpcodeCommandField);
-  EXPECT_EQ(decoded_packet.opcode_group_field(), kOpcodeGroupField);
-  EXPECT_EQ(decoded_packet.parameters().size_bytes(), 0u);
-}
-
-TEST_F(PacketTest, CommandPacketWithParametersEncodeAndDecode) {
-  constexpr uint16_t kOpcodeCommandField = 0b10'1010'1010;
-  constexpr uint8_t kOpcodeGroupField = 0b10'1010;
-
-  constexpr uint16_t kOpcode = (kOpcodeGroupField << 10) | kOpcodeCommandField;
-
-  constexpr std::array<const std::byte, kArbitraryMaxPayloadSizeBytes>
-      kParameters = bytes::MakeArray<const std::byte>(1, 2);
-  const CommandPacket packet(kOpcode, kParameters);
-  EXPECT_EQ(packet.type(), Packet::Type::kCommandPacket);
-  EXPECT_EQ(packet.size_bytes(),
-            CommandPacket::kHeaderSizeBytes + kArbitraryMaxPayloadSizeBytes);
-  EXPECT_EQ(packet.opcode(), kOpcode);
-  EXPECT_EQ(packet.opcode_command_field(), kOpcodeCommandField);
-  EXPECT_EQ(packet.opcode_group_field(), kOpcodeGroupField);
-  EXPECT_EQ(packet.parameters().size_bytes(), kArbitraryMaxPayloadSizeBytes);
-
-  const Result<ConstByteSpan> encode_result = packet.Encode(packet_buffer_);
-  ASSERT_EQ(OkStatus(), encode_result.status());
-
-  constexpr std::array<const std::byte,
-                       CommandPacket::kHeaderSizeBytes +
-                           kArbitraryMaxPayloadSizeBytes>
-      kExpectedEncodedPacket = bytes::MakeArray<const std::byte>(
-          0b1010'1010, 0b1010'1010, 0b0000'0010, 1, 2);
-  const ConstByteSpan& encoded_packet = encode_result.value();
-  EXPECT_TRUE(std::equal(kExpectedEncodedPacket.begin(),
-                         kExpectedEncodedPacket.end(),
-                         encoded_packet.begin(),
-                         encoded_packet.end()));
-
-  // First, decode it from a perfectly sized span which we just encoded.
-  std::optional<CommandPacket> possible_packet =
-      CommandPacket::Decode(encoded_packet);
-  ASSERT_TRUE(possible_packet.has_value());
-  CommandPacket& decoded_packet = possible_packet.value();
-  EXPECT_EQ(decoded_packet.type(), Packet::Type::kCommandPacket);
-  EXPECT_EQ(decoded_packet.size_bytes(),
-            CommandPacket::kHeaderSizeBytes + kArbitraryMaxPayloadSizeBytes);
-  EXPECT_EQ(decoded_packet.opcode(), kOpcode);
-  EXPECT_EQ(decoded_packet.opcode_command_field(), kOpcodeCommandField);
-  EXPECT_EQ(decoded_packet.opcode_group_field(), kOpcodeGroupField);
-  EXPECT_EQ(decoded_packet.parameters().size_bytes(),
-            kArbitraryMaxPayloadSizeBytes);
-
-  // Second, decode it from an oversized buffer.
-  possible_packet = CommandPacket::Decode({packet_buffer_});
-  ASSERT_TRUE(possible_packet.has_value());
-  decoded_packet = possible_packet.value();
-  EXPECT_EQ(decoded_packet.type(), Packet::Type::kCommandPacket);
-  EXPECT_EQ(decoded_packet.size_bytes(),
-            CommandPacket::kHeaderSizeBytes + kArbitraryMaxPayloadSizeBytes);
-  EXPECT_EQ(decoded_packet.opcode(), kOpcode);
-  EXPECT_EQ(decoded_packet.opcode_command_field(), kOpcodeCommandField);
-  EXPECT_EQ(decoded_packet.opcode_group_field(), kOpcodeGroupField);
-  EXPECT_EQ(decoded_packet.parameters().size_bytes(),
-            kArbitraryMaxPayloadSizeBytes);
-}
-
-TEST_F(PacketTest, AsyncDataPacketHeaderUndersizedEncode) {
-  const AsyncDataPacket packet(0u, ConstByteSpan());
-  EXPECT_EQ(0u, packet.data().size_bytes());
-  const Result<ConstByteSpan> encode_result = packet.Encode(
-      {packet_buffer_.data(), AsyncDataPacket::kHeaderSizeBytes - 1});
-  EXPECT_EQ(Status::ResourceExhausted(), encode_result.status());
-}
-
-TEST_F(PacketTest, AsyncDataPacketHeaderUndersizedDecode) {
-  EXPECT_FALSE(AsyncDataPacket::Decode({packet_buffer_.data(),
-                                        AsyncDataPacket::kHeaderSizeBytes - 1})
-                   .has_value());
-}
-
-TEST_F(PacketTest, AsyncDataPacketHeaderOnlyEncodeAndDecode) {
-  constexpr uint16_t kHandle = 0b00'0000'0000;
-  constexpr uint8_t kPbFlag = 0b01;
-  constexpr uint8_t kBcFlag = 0b10;
-
-  constexpr uint16_t kHandleAndFragmentationBits =
-      kHandle | (kPbFlag << 12) | (kBcFlag << 14);
-
-  const AsyncDataPacket packet(kHandleAndFragmentationBits, ConstByteSpan());
-  EXPECT_EQ(packet.type(), Packet::Type::kAsyncDataPacket);
-  EXPECT_EQ(packet.size_bytes(), AsyncDataPacket::kHeaderSizeBytes);
-  EXPECT_EQ(packet.handle_and_fragmentation_bits(),
-            kHandleAndFragmentationBits);
-  EXPECT_EQ(packet.handle(), kHandle);
-  EXPECT_EQ(packet.pb_flag(), kPbFlag);
-  EXPECT_EQ(packet.bc_flag(), kBcFlag);
-  EXPECT_EQ(packet.data().size_bytes(), 0u);
-
-  const Result<ConstByteSpan> encode_result = packet.Encode(packet_buffer_);
-  ASSERT_EQ(OkStatus(), encode_result.status());
-
-  constexpr std::array<const std::byte, AsyncDataPacket::kHeaderSizeBytes>
-      kExpectedEncodedPacket = bytes::MakeArray<const std::byte>(
-          0b0000'0000, 0b1001'0000, 0b0000'0000, 0b0000'0000);
-  const ConstByteSpan& encoded_packet = encode_result.value();
-  EXPECT_TRUE(std::equal(kExpectedEncodedPacket.begin(),
-                         kExpectedEncodedPacket.end(),
-                         encoded_packet.begin(),
-                         encoded_packet.end()));
-
-  // First, decode it from a perfectly sized span which we just encoded.
-  std::optional<AsyncDataPacket> possible_packet =
-      AsyncDataPacket::Decode(encoded_packet);
-  ASSERT_TRUE(possible_packet.has_value());
-  AsyncDataPacket& decoded_packet = possible_packet.value();
-  EXPECT_EQ(decoded_packet.type(), Packet::Type::kAsyncDataPacket);
-  EXPECT_EQ(decoded_packet.size_bytes(), AsyncDataPacket::kHeaderSizeBytes);
-  EXPECT_EQ(decoded_packet.handle_and_fragmentation_bits(),
-            kHandleAndFragmentationBits);
-  EXPECT_EQ(decoded_packet.handle(), kHandle);
-  EXPECT_EQ(decoded_packet.pb_flag(), kPbFlag);
-  EXPECT_EQ(decoded_packet.bc_flag(), kBcFlag);
-  EXPECT_EQ(decoded_packet.data().size_bytes(), 0u);
-
-  // Second, decode it from an oversized buffer.
-  possible_packet = AsyncDataPacket::Decode({packet_buffer_});
-  ASSERT_TRUE(possible_packet.has_value());
-  decoded_packet = possible_packet.value();
-  EXPECT_EQ(decoded_packet.type(), Packet::Type::kAsyncDataPacket);
-  EXPECT_EQ(decoded_packet.size_bytes(), AsyncDataPacket::kHeaderSizeBytes);
-  EXPECT_EQ(decoded_packet.handle_and_fragmentation_bits(),
-            kHandleAndFragmentationBits);
-  EXPECT_EQ(decoded_packet.handle(), kHandle);
-  EXPECT_EQ(decoded_packet.pb_flag(), kPbFlag);
-  EXPECT_EQ(decoded_packet.bc_flag(), kBcFlag);
-  EXPECT_EQ(decoded_packet.data().size_bytes(), 0u);
-}
-
-TEST_F(PacketTest, AsyncDataPacketWithDataEncodeAndDecode) {
-  constexpr uint16_t kHandle = 0b00'0000'0000;
-  constexpr uint8_t kPbFlag = 0b01;
-  constexpr uint8_t kBcFlag = 0b10;
-
-  constexpr uint16_t kHandleAndFragmentationBits =
-      kHandle | (kPbFlag << 12) | (kBcFlag << 14);
-
-  constexpr std::array<const std::byte, kArbitraryMaxPayloadSizeBytes> kData =
-      bytes::MakeArray<const std::byte>(1, 2);
-  const AsyncDataPacket packet(kHandleAndFragmentationBits, kData);
-  EXPECT_EQ(packet.type(), Packet::Type::kAsyncDataPacket);
-  EXPECT_EQ(packet.size_bytes(),
-            AsyncDataPacket::kHeaderSizeBytes + kArbitraryMaxPayloadSizeBytes);
-  EXPECT_EQ(packet.handle_and_fragmentation_bits(),
-            kHandleAndFragmentationBits);
-  EXPECT_EQ(packet.handle(), kHandle);
-  EXPECT_EQ(packet.pb_flag(), kPbFlag);
-  EXPECT_EQ(packet.bc_flag(), kBcFlag);
-  EXPECT_EQ(packet.data().size_bytes(), kArbitraryMaxPayloadSizeBytes);
-
-  const Result<ConstByteSpan> encode_result = packet.Encode(packet_buffer_);
-  ASSERT_EQ(OkStatus(), encode_result.status());
-
-  constexpr std::array<const std::byte,
-                       AsyncDataPacket::kHeaderSizeBytes +
-                           kArbitraryMaxPayloadSizeBytes>
-      kExpectedEncodedPacket = bytes::MakeArray<const std::byte>(
-          0b0000'0000, 0b1001'0000, 0b0000'0010, 0b0000'0000, 1, 2);
-  const ConstByteSpan& encoded_packet = encode_result.value();
-  EXPECT_TRUE(std::equal(kExpectedEncodedPacket.begin(),
-                         kExpectedEncodedPacket.end(),
-                         encoded_packet.begin(),
-                         encoded_packet.end()));
-
-  // First, decode it from a perfectly sized span which we just encoded.
-  std::optional<AsyncDataPacket> possible_packet =
-      AsyncDataPacket::Decode(encoded_packet);
-  ASSERT_TRUE(possible_packet.has_value());
-  AsyncDataPacket& decoded_packet = possible_packet.value();
-  EXPECT_EQ(decoded_packet.type(), Packet::Type::kAsyncDataPacket);
-  EXPECT_EQ(decoded_packet.size_bytes(),
-            AsyncDataPacket::kHeaderSizeBytes + kArbitraryMaxPayloadSizeBytes);
-  EXPECT_EQ(decoded_packet.handle_and_fragmentation_bits(),
-            kHandleAndFragmentationBits);
-  EXPECT_EQ(decoded_packet.handle(), kHandle);
-  EXPECT_EQ(decoded_packet.pb_flag(), kPbFlag);
-  EXPECT_EQ(decoded_packet.bc_flag(), kBcFlag);
-  EXPECT_EQ(decoded_packet.data().size_bytes(), kArbitraryMaxPayloadSizeBytes);
-
-  // Second, decode it from an oversized buffer.
-  possible_packet = AsyncDataPacket::Decode({packet_buffer_});
-  ASSERT_TRUE(possible_packet.has_value());
-  decoded_packet = possible_packet.value();
-  EXPECT_EQ(decoded_packet.type(), Packet::Type::kAsyncDataPacket);
-  EXPECT_EQ(decoded_packet.size_bytes(),
-            AsyncDataPacket::kHeaderSizeBytes + kArbitraryMaxPayloadSizeBytes);
-  EXPECT_EQ(decoded_packet.handle_and_fragmentation_bits(),
-            kHandleAndFragmentationBits);
-  EXPECT_EQ(decoded_packet.handle(), kHandle);
-  EXPECT_EQ(decoded_packet.pb_flag(), kPbFlag);
-  EXPECT_EQ(decoded_packet.bc_flag(), kBcFlag);
-  EXPECT_EQ(decoded_packet.data().size_bytes(), kArbitraryMaxPayloadSizeBytes);
-}
-
-TEST_F(PacketTest, SyncDataPacketHeaderUndersizedEncode) {
-  const SyncDataPacket packet(0u, ConstByteSpan());
-  EXPECT_EQ(0u, packet.data().size_bytes());
-  const Result<ConstByteSpan> encode_result = packet.Encode(
-      {packet_buffer_.data(), SyncDataPacket::kHeaderSizeBytes - 1});
-  EXPECT_EQ(Status::ResourceExhausted(), encode_result.status());
-}
-
-TEST_F(PacketTest, SyncDataPacketHeaderUndersizedDecode) {
-  EXPECT_FALSE(SyncDataPacket::Decode({packet_buffer_.data(),
-                                       SyncDataPacket::kHeaderSizeBytes - 1})
-                   .has_value());
-}
-
-TEST_F(PacketTest, SyncDataPacketHeaderOnlyEncodeAndDecode) {
-  constexpr uint16_t kHandle = 0b00'0000'0000;
-  constexpr uint8_t kPacketStatusFlag = 0b11;
-  constexpr uint8_t kReservedBits = 0;
-
-  constexpr uint16_t kHandleAndStatusBits =
-      kHandle | (kPacketStatusFlag << 12) | (kReservedBits << 14);
-
-  const SyncDataPacket packet(kHandleAndStatusBits, ConstByteSpan());
-  EXPECT_EQ(packet.type(), Packet::Type::kSyncDataPacket);
-  EXPECT_EQ(packet.size_bytes(), SyncDataPacket::kHeaderSizeBytes);
-  EXPECT_EQ(packet.handle_and_status_bits(), kHandleAndStatusBits);
-  EXPECT_EQ(packet.handle(), kHandle);
-  EXPECT_EQ(packet.packet_status_flag(), kPacketStatusFlag);
-  EXPECT_EQ(packet.data().size_bytes(), 0u);
-
-  const Result<ConstByteSpan> encode_result = packet.Encode(packet_buffer_);
-  ASSERT_EQ(OkStatus(), encode_result.status());
-
-  constexpr std::array<const std::byte, SyncDataPacket::kHeaderSizeBytes>
-      kExpectedEncodedPacket = bytes::MakeArray<const std::byte>(
-          0b0000'0000, 0b0011'0000, 0b0000'0000);
-  const ConstByteSpan& encoded_packet = encode_result.value();
-  EXPECT_TRUE(std::equal(kExpectedEncodedPacket.begin(),
-                         kExpectedEncodedPacket.end(),
-                         encoded_packet.begin(),
-                         encoded_packet.end()));
-
-  // First, decode it from a perfectly sized span which we just encoded.
-  std::optional<SyncDataPacket> possible_packet =
-      SyncDataPacket::Decode(encoded_packet);
-  ASSERT_TRUE(possible_packet.has_value());
-  SyncDataPacket& decoded_packet = possible_packet.value();
-  EXPECT_EQ(decoded_packet.type(), Packet::Type::kSyncDataPacket);
-  EXPECT_EQ(decoded_packet.size_bytes(), SyncDataPacket::kHeaderSizeBytes);
-  EXPECT_EQ(packet.handle_and_status_bits(), kHandleAndStatusBits);
-  EXPECT_EQ(decoded_packet.packet_status_flag(), kPacketStatusFlag);
-  EXPECT_EQ(decoded_packet.handle(), kHandle);
-  EXPECT_EQ(decoded_packet.data().size_bytes(), 0u);
-
-  // Second, decode it from an oversized buffer.
-  possible_packet = SyncDataPacket::Decode({packet_buffer_});
-  ASSERT_TRUE(possible_packet.has_value());
-  decoded_packet = possible_packet.value();
-  EXPECT_EQ(decoded_packet.type(), Packet::Type::kSyncDataPacket);
-  EXPECT_EQ(decoded_packet.size_bytes(), SyncDataPacket::kHeaderSizeBytes);
-  EXPECT_EQ(packet.handle_and_status_bits(), kHandleAndStatusBits);
-  EXPECT_EQ(decoded_packet.handle(), kHandle);
-  EXPECT_EQ(decoded_packet.packet_status_flag(), kPacketStatusFlag);
-  EXPECT_EQ(decoded_packet.data().size_bytes(), 0u);
-}
-
-TEST_F(PacketTest, SyncDataPacketWithDataEncodeAndDecode) {
-  constexpr uint16_t kHandle = 0b00'0000'0000;
-  constexpr uint8_t kPacketStatusFlag = 0b11;
-  constexpr uint8_t kReservedBits = 0;
-
-  constexpr uint16_t kHandleAndStatusBits =
-      kHandle | (kPacketStatusFlag << 12) | (kReservedBits << 14);
-
-  constexpr std::array<const std::byte, kArbitraryMaxPayloadSizeBytes> kData =
-      bytes::MakeArray<const std::byte>(1, 2);
-  const SyncDataPacket packet(kHandleAndStatusBits, kData);
-  EXPECT_EQ(packet.type(), Packet::Type::kSyncDataPacket);
-  EXPECT_EQ(packet.size_bytes(),
-            SyncDataPacket::kHeaderSizeBytes + kArbitraryMaxPayloadSizeBytes);
-  EXPECT_EQ(packet.handle_and_status_bits(), kHandleAndStatusBits);
-  EXPECT_EQ(packet.handle(), kHandle);
-  EXPECT_EQ(packet.packet_status_flag(), kPacketStatusFlag);
-  EXPECT_EQ(packet.data().size_bytes(), kArbitraryMaxPayloadSizeBytes);
-
-  const Result<ConstByteSpan> encode_result = packet.Encode(packet_buffer_);
-  ASSERT_EQ(OkStatus(), encode_result.status());
-
-  constexpr std::array<const std::byte,
-                       SyncDataPacket::kHeaderSizeBytes +
-                           kArbitraryMaxPayloadSizeBytes>
-      kExpectedEncodedPacket = bytes::MakeArray<const std::byte>(
-          0b0000'0000, 0b0011'0000, 0b0000'0010, 1, 2);
-  const ConstByteSpan& encoded_packet = encode_result.value();
-  EXPECT_TRUE(std::equal(kExpectedEncodedPacket.begin(),
-                         kExpectedEncodedPacket.end(),
-                         encoded_packet.begin(),
-                         encoded_packet.end()));
-
-  // First, decode it from a perfectly sized span which we just encoded.
-  std::optional<SyncDataPacket> possible_packet =
-      SyncDataPacket::Decode(encoded_packet);
-  ASSERT_TRUE(possible_packet.has_value());
-  SyncDataPacket& decoded_packet = possible_packet.value();
-  EXPECT_EQ(decoded_packet.type(), Packet::Type::kSyncDataPacket);
-  EXPECT_EQ(decoded_packet.size_bytes(),
-            SyncDataPacket::kHeaderSizeBytes + kArbitraryMaxPayloadSizeBytes);
-  EXPECT_EQ(packet.handle_and_status_bits(), kHandleAndStatusBits);
-  EXPECT_EQ(packet.handle(), kHandle);
-  EXPECT_EQ(packet.packet_status_flag(), kPacketStatusFlag);
-  EXPECT_EQ(decoded_packet.data().size_bytes(), kArbitraryMaxPayloadSizeBytes);
-
-  // Second, decode it from an oversized buffer.
-  possible_packet = SyncDataPacket::Decode({packet_buffer_});
-  ASSERT_TRUE(possible_packet.has_value());
-  decoded_packet = possible_packet.value();
-  EXPECT_EQ(decoded_packet.type(), Packet::Type::kSyncDataPacket);
-  EXPECT_EQ(decoded_packet.size_bytes(),
-            SyncDataPacket::kHeaderSizeBytes + kArbitraryMaxPayloadSizeBytes);
-  EXPECT_EQ(packet.handle_and_status_bits(), kHandleAndStatusBits);
-  EXPECT_EQ(packet.handle(), kHandle);
-  EXPECT_EQ(packet.packet_status_flag(), kPacketStatusFlag);
-  EXPECT_EQ(decoded_packet.data().size_bytes(), kArbitraryMaxPayloadSizeBytes);
-}
-
-TEST_F(PacketTest, EventPacketHeaderUndersizedEncode) {
-  const EventPacket packet(0u, ConstByteSpan());
-  EXPECT_EQ(0u, packet.parameters().size_bytes());
-  const Result<ConstByteSpan> encode_result =
-      packet.Encode({packet_buffer_.data(), EventPacket::kHeaderSizeBytes - 1});
-  EXPECT_EQ(Status::ResourceExhausted(), encode_result.status());
-}
-
-TEST_F(PacketTest, EventPacketHeaderUndersizedDecode) {
-  EXPECT_FALSE(EventPacket::Decode(
-                   {packet_buffer_.data(), EventPacket::kHeaderSizeBytes - 1})
-                   .has_value());
-}
-
-TEST_F(PacketTest, EventPacketHeaderOnlyEncodeAndDecode) {
-  constexpr uint8_t kEventCode = 0b1111'1111;
-
-  const EventPacket packet(kEventCode, ConstByteSpan());
-  EXPECT_EQ(packet.type(), Packet::Type::kEventPacket);
-  EXPECT_EQ(packet.size_bytes(), EventPacket::kHeaderSizeBytes);
-  EXPECT_EQ(packet.event_code(), kEventCode);
-  EXPECT_EQ(packet.parameters().size_bytes(), 0u);
-
-  const Result<ConstByteSpan> encode_result = packet.Encode(packet_buffer_);
-  ASSERT_EQ(OkStatus(), encode_result.status());
-
-  constexpr std::array<const std::byte, EventPacket::kHeaderSizeBytes>
-      kExpectedEncodedPacket =
-          bytes::MakeArray<const std::byte>(0b1111'11111, 0b0000'0000);
-  const ConstByteSpan& encoded_packet = encode_result.value();
-  EXPECT_TRUE(std::equal(kExpectedEncodedPacket.begin(),
-                         kExpectedEncodedPacket.end(),
-                         encoded_packet.begin(),
-                         encoded_packet.end()));
-
-  // First, decode it from a perfectly sized span which we just encoded.
-  std::optional<EventPacket> possible_packet =
-      EventPacket::Decode(encoded_packet);
-  ASSERT_TRUE(possible_packet.has_value());
-  EventPacket& decoded_packet = possible_packet.value();
-  EXPECT_EQ(decoded_packet.type(), Packet::Type::kEventPacket);
-  EXPECT_EQ(decoded_packet.size_bytes(), EventPacket::kHeaderSizeBytes);
-  EXPECT_EQ(decoded_packet.event_code(), kEventCode);
-  EXPECT_EQ(decoded_packet.parameters().size_bytes(), 0u);
-
-  // Second, decode it from an oversized buffer.
-  possible_packet = EventPacket::Decode({packet_buffer_});
-  ASSERT_TRUE(possible_packet.has_value());
-  decoded_packet = possible_packet.value();
-  EXPECT_EQ(decoded_packet.type(), Packet::Type::kEventPacket);
-  EXPECT_EQ(decoded_packet.size_bytes(), EventPacket::kHeaderSizeBytes);
-  EXPECT_EQ(decoded_packet.event_code(), kEventCode);
-  EXPECT_EQ(decoded_packet.parameters().size_bytes(), 0u);
-}
-
-TEST_F(PacketTest, EventPacketWithParametersEncodeAndDecode) {
-  constexpr uint8_t kEventCode = 0b1111'0000;
-
-  constexpr std::array<const std::byte, kArbitraryMaxPayloadSizeBytes>
-      kParameters = bytes::MakeArray<const std::byte>(1, 2);
-  const EventPacket packet(kEventCode, kParameters);
-  EXPECT_EQ(packet.type(), Packet::Type::kEventPacket);
-  EXPECT_EQ(packet.size_bytes(),
-            EventPacket::kHeaderSizeBytes + kArbitraryMaxPayloadSizeBytes);
-  EXPECT_EQ(packet.event_code(), kEventCode);
-  EXPECT_EQ(packet.parameters().size_bytes(), kArbitraryMaxPayloadSizeBytes);
-
-  const Result<ConstByteSpan> encode_result = packet.Encode(packet_buffer_);
-  ASSERT_EQ(OkStatus(), encode_result.status());
-
-  constexpr std::array<const std::byte,
-                       EventPacket::kHeaderSizeBytes +
-                           kArbitraryMaxPayloadSizeBytes>
-      kExpectedEncodedPacket =
-          bytes::MakeArray<const std::byte>(0b1111'0000, 0b0000'0010, 1, 2);
-  const ConstByteSpan& encoded_packet = encode_result.value();
-  EXPECT_TRUE(std::equal(kExpectedEncodedPacket.begin(),
-                         kExpectedEncodedPacket.end(),
-                         encoded_packet.begin(),
-                         encoded_packet.end()));
-
-  // First, decode it from a perfectly sized span which we just encoded.
-  std::optional<EventPacket> possible_packet =
-      EventPacket::Decode(encoded_packet);
-  ASSERT_TRUE(possible_packet.has_value());
-  EventPacket& decoded_packet = possible_packet.value();
-  EXPECT_EQ(decoded_packet.type(), Packet::Type::kEventPacket);
-  EXPECT_EQ(decoded_packet.size_bytes(),
-            EventPacket::kHeaderSizeBytes + kArbitraryMaxPayloadSizeBytes);
-  EXPECT_EQ(decoded_packet.event_code(), kEventCode);
-  EXPECT_EQ(decoded_packet.parameters().size_bytes(),
-            kArbitraryMaxPayloadSizeBytes);
-
-  // Second, decode it from an oversized buffer.
-  possible_packet = EventPacket::Decode({packet_buffer_});
-  ASSERT_TRUE(possible_packet.has_value());
-  decoded_packet = possible_packet.value();
-  EXPECT_EQ(decoded_packet.type(), Packet::Type::kEventPacket);
-  EXPECT_EQ(decoded_packet.size_bytes(),
-            EventPacket::kHeaderSizeBytes + kArbitraryMaxPayloadSizeBytes);
-  EXPECT_EQ(decoded_packet.event_code(), kEventCode);
-  EXPECT_EQ(decoded_packet.parameters().size_bytes(),
-            kArbitraryMaxPayloadSizeBytes);
-}
-
-}  // namespace
-}  // namespace pw::bluetooth_hci
diff --git a/pw_bluetooth_hci/public/pw_bluetooth_hci/packet.h b/pw_bluetooth_hci/public/pw_bluetooth_hci/packet.h
deleted file mode 100644
index 7ccec89..0000000
--- a/pw_bluetooth_hci/public/pw_bluetooth_hci/packet.h
+++ /dev/null
@@ -1,347 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <bit>
-#include <cstdint>
-#include <optional>
-
-#include "pw_assert/assert.h"
-#include "pw_bytes/span.h"
-#include "pw_result/result.h"
-
-namespace pw::bluetooth_hci {
-
-// HCI Packets as defined in the Bluetooth Core Specification Version 5.3
-// “Host Controller Interface Functional Specification” in Volume 2, Part E.
-//
-// Note that for now only the subset of the HCI packets used in the HCI UART
-// Transport Layer are provided as defined in the Bluetooth Core Specification
-// version 5.3 "Host Controller Interface Transport Layer" volume 4, part A.
-class CommandPacket;
-class AsyncDataPacket;
-class SyncDataPacket;
-class EventPacket;
-
-class Packet {
- public:
-  enum class Type {
-    kCommandPacket,
-    kAsyncDataPacket,
-    kSyncDataPacket,
-    kEventPacket,
-  };
-
-  constexpr Type type() const { return type_; }
-
-  constexpr size_t size_bytes() const { return packet_size_bytes_; }
-
-  const CommandPacket& command_packet() const {
-    PW_ASSERT(type_ == Type::kCommandPacket);
-    return *reinterpret_cast<const CommandPacket*>(this);
-  }
-
-  CommandPacket& command_packet() {
-    PW_ASSERT(type_ == Type::kCommandPacket);
-    return *reinterpret_cast<CommandPacket*>(this);
-  }
-
-  const AsyncDataPacket& async_data_packet() const {
-    PW_ASSERT(type_ == Type::kAsyncDataPacket);
-    return *reinterpret_cast<const AsyncDataPacket*>(this);
-  }
-
-  AsyncDataPacket& async_data_packet() {
-    PW_ASSERT(type_ == Type::kAsyncDataPacket);
-    return *reinterpret_cast<AsyncDataPacket*>(this);
-  }
-
-  const SyncDataPacket& sync_data_packet() const {
-    PW_ASSERT(type_ == Type::kSyncDataPacket);
-    return *reinterpret_cast<const SyncDataPacket*>(this);
-  }
-
-  SyncDataPacket& sync_data_packet() {
-    PW_ASSERT(type_ == Type::kSyncDataPacket);
-    return *reinterpret_cast<SyncDataPacket*>(this);
-  }
-
-  const EventPacket& event_packet() const {
-    PW_ASSERT(type_ == Type::kEventPacket);
-    return *reinterpret_cast<const EventPacket*>(this);
-  }
-
-  EventPacket& event_packet() {
-    PW_ASSERT(type_ == Type::kEventPacket);
-    return *reinterpret_cast<EventPacket*>(this);
-  }
-
- protected:
-  constexpr Packet(Type type, size_t packet_size_bytes)
-      : type_(type), packet_size_bytes_(packet_size_bytes) {}
-
- private:
-  Type type_;
-  size_t packet_size_bytes_;
-};
-
-class CommandPacket : public Packet {
- private:
-  static constexpr size_t kOpcodeByteOffset = 0;
-  static constexpr size_t kParameterTotalLengthByteOffset = 2;
-  static constexpr size_t kParametersByteOffset = 3;
-
-  static constexpr size_t kOpcodeOcfOffset = 0;
-  static constexpr uint16_t kOpcodeOcfMask = 0x3FF << kOpcodeOcfOffset;
-  static constexpr size_t kOpcodeOgfOffset = 10;
-  static constexpr uint16_t kOpcodeOgfMask = 0x3F << kOpcodeOgfOffset;
-
- public:
-  // HCI Command Packet Format, little-endian, based on bit offsets:
-  // 0           16                        24            24+8*N
-  // |  Opcode   |  Parameter Total Length | Parameter N |
-  // 0     10    16
-  // | OCF | OGF |
-  static constexpr size_t kHeaderSizeBytes = kParametersByteOffset;
-
-  constexpr CommandPacket(uint16_t opcode,
-                          const std::byte* parameters,
-                          uint8_t parameters_size_bytes)
-      : Packet(Type::kCommandPacket, kHeaderSizeBytes + parameters_size_bytes),
-        opcode_(opcode),
-        parameters_(parameters, parameters_size_bytes) {}
-
-  // Precondition: the parameters size must be <= 255 bytes.
-  constexpr CommandPacket(uint16_t opcode, ConstByteSpan parameters)
-      : CommandPacket(opcode,
-                      parameters.data(),
-                      static_cast<uint8_t>(parameters.size_bytes())) {
-    PW_ASSERT(parameters.size_bytes() <= std::numeric_limits<uint8_t>::max());
-  }
-
-  // Decodes the packet based on the specified endianness.
-  static std::optional<CommandPacket> Decode(
-      ConstByteSpan data, std::endian order = std::endian::little);
-
-  // Encodes the packet based on the specified endianness.
-  //
-  // Returns:
-  //   OK - returns the encoded packet.
-  //   RESOURCE_EXHAUSTED - The input buffer is too small for this packet.
-  Result<ConstByteSpan> Encode(ByteSpan buffer,
-                               std::endian order = std::endian::little) const;
-
-  constexpr uint16_t opcode() const { return opcode_; }
-
-  constexpr uint16_t opcode_command_field() const {
-    return static_cast<uint16_t>((opcode_ & kOpcodeOcfMask) >>
-                                 kOpcodeOcfOffset);
-  }
-
-  constexpr uint8_t opcode_group_field() const {
-    return static_cast<uint8_t>((opcode_ & kOpcodeOgfMask) >> kOpcodeOgfOffset);
-  }
-
-  constexpr const ConstByteSpan& parameters() const { return parameters_; }
-
- private:
-  uint16_t opcode_;
-  ConstByteSpan parameters_;
-};
-
-class AsyncDataPacket : public Packet {
- private:
-  static constexpr size_t kHandleAndFragmentationBitsByteOffset = 0;
-  static constexpr size_t kDataTotalLengthByteOffset = 2;
-  static constexpr size_t kDataByteOffset = 4;
-
-  static constexpr size_t kHandleOffset = 0;
-  static constexpr uint16_t kHandleMask = 0xFFF << kHandleOffset;
-  static constexpr size_t kPbFlagOffset = 12;
-  static constexpr uint16_t kPbFlagMask = 0x3 << kPbFlagOffset;
-  static constexpr size_t kBcFlagOffset = 14;
-  static constexpr uint16_t kBcFlagMask = 0x3 << kBcFlagOffset;
-
- public:
-  // HCI ACL Data Packet Format, little-endian, based on bit offsets:
-  // 0        12        14        16                  32       32+8*N
-  // | Handle | PB Flag | BC Flag | Data Total Length | Data N |
-  static constexpr size_t kHeaderSizeBytes = kDataByteOffset;
-
-  constexpr AsyncDataPacket(uint16_t handle_and_fragmentation_bits,
-                            const std::byte* data,
-                            uint16_t data_size_bytes)
-      : Packet(Type::kAsyncDataPacket, kHeaderSizeBytes + data_size_bytes),
-        handle_and_fragmentation_bits_(handle_and_fragmentation_bits),
-        data_(data, data_size_bytes) {}
-
-  // Precondition: the parameters size must be <= 65535 bytes.
-  constexpr AsyncDataPacket(uint16_t handle_and_fragmentation_bits,
-                            ConstByteSpan data)
-      : AsyncDataPacket(handle_and_fragmentation_bits,
-                        data.data(),
-                        static_cast<uint16_t>(data.size_bytes())) {
-    PW_ASSERT(data.size_bytes() <= std::numeric_limits<uint16_t>::max());
-  }
-
-  // Decodes the packet based on the specified endianness.
-  static std::optional<AsyncDataPacket> Decode(
-      ConstByteSpan data, std::endian order = std::endian::little);
-
-  // Encodes the packet based on the specified endianness.
-  //
-  // Returns:
-  //   OK - returns the encoded packet.
-  //   RESOURCE_EXHAUSTED - The input buffer is too small for this packet.
-  Result<ConstByteSpan> Encode(ByteSpan buffer,
-                               std::endian order = std::endian::little) const;
-
-  constexpr uint16_t handle_and_fragmentation_bits() const {
-    return handle_and_fragmentation_bits_;
-  }
-
-  constexpr uint16_t handle() const {
-    return (handle_and_fragmentation_bits_ & kHandleMask) >> kHandleOffset;
-  }
-
-  constexpr uint8_t pb_flag() const {
-    return static_cast<uint8_t>(
-        (handle_and_fragmentation_bits_ & kPbFlagMask) >> kPbFlagOffset);
-  }
-
-  constexpr uint8_t bc_flag() const {
-    return static_cast<uint8_t>(
-        (handle_and_fragmentation_bits_ & kBcFlagMask) >> kBcFlagOffset);
-  }
-
-  constexpr const ConstByteSpan& data() const { return data_; }
-
- private:
-  uint16_t handle_and_fragmentation_bits_;
-  ConstByteSpan data_;
-};
-
-class SyncDataPacket : public Packet {
- private:
-  static constexpr size_t kHandleAndStatusBitsByteOffset = 0;
-  static constexpr size_t kDataTotalLengthByteOffset = 2;
-  static constexpr size_t kDataByteOffset = 3;
-
-  static constexpr size_t kHandleOffset = 0;
-  static constexpr uint16_t kHandleMask = 0xFFF << kHandleOffset;
-  static constexpr size_t kPacketStatusFlagOffset = 12;
-  static constexpr uint16_t kPacketStatusFlagMask = 0x3
-                                                    << kPacketStatusFlagOffset;
-
- public:
-  // HCI SCO Data Packet Format, little-endian, based on bit offsets:
-  // 0        12                   14         16                  24      24+8*N
-  // | Handle | Packet Status Flag | Reserved | Data Total Length | Data N |
-  static constexpr size_t kHeaderSizeBytes = 3;
-
-  constexpr SyncDataPacket(uint16_t handle_and_status_bits,
-                           const std::byte* data,
-                           uint8_t data_size_bytes)
-      : Packet(Type::kSyncDataPacket, kHeaderSizeBytes + data_size_bytes),
-        handle_and_status_bits_(handle_and_status_bits),
-        data_(data, data_size_bytes) {}
-
-  // Precondition: the parameters size must be <= 255 bytes.
-  constexpr SyncDataPacket(uint16_t handle_and_status_bits, ConstByteSpan data)
-      : SyncDataPacket(handle_and_status_bits,
-                       data.data(),
-                       static_cast<uint8_t>(data.size_bytes())) {
-    PW_ASSERT(data.size_bytes() <= std::numeric_limits<uint8_t>::max());
-  }
-
-  // Decodes the packet based on the specified endianness.
-  static std::optional<SyncDataPacket> Decode(
-      ConstByteSpan data, std::endian order = std::endian::little);
-
-  // Encodes the packet based on the specified endianness.
-  //
-  // Returns:
-  //   OK - returns the encoded packet.
-  //   RESOURCE_EXHAUSTED - The input buffer is too small for this packet.
-  Result<ConstByteSpan> Encode(ByteSpan buffer,
-                               std::endian order = std::endian::little) const;
-
-  constexpr uint16_t handle_and_status_bits() const {
-    return handle_and_status_bits_;
-  }
-
-  constexpr uint16_t handle() const {
-    return (handle_and_status_bits_ & kHandleMask) >> kHandleOffset;
-  }
-
-  constexpr uint8_t packet_status_flag() const {
-    return static_cast<uint8_t>(
-        (handle_and_status_bits_ & kPacketStatusFlagMask) >>
-        kPacketStatusFlagOffset);
-  }
-
-  constexpr const ConstByteSpan& data() const { return data_; }
-
- private:
-  uint16_t handle_and_status_bits_;
-  ConstByteSpan data_;
-};
-
-class EventPacket : public Packet {
- private:
-  static constexpr size_t kEventCodeByteOffset = 0;
-  static constexpr size_t kParameterTotalLengthByteOffset = 1;
-  static constexpr size_t kParametersByteOffset = 2;
-
- public:
-  // HCI SCO Data Packet Format, little-endian, based on bit offsets:
-  // 0            8                        16            16+8*N
-  // | Event Code | Parameter Total Length | Parameter N |
-  static constexpr size_t kHeaderSizeBytes = kParametersByteOffset;
-
-  constexpr EventPacket(uint8_t event_code,
-                        const std::byte* parameters,
-                        uint8_t parameters_size_bytes)
-      : Packet(Type::kEventPacket, kHeaderSizeBytes + parameters_size_bytes),
-        event_code_(event_code),
-        parameters_(parameters, parameters_size_bytes) {}
-
-  // Precondition: the parameters size must be <= 255 bytes.
-  constexpr EventPacket(uint16_t event_code, ConstByteSpan parameters)
-      : EventPacket(event_code,
-                    parameters.data(),
-                    static_cast<uint8_t>(parameters.size_bytes())) {
-    PW_ASSERT(parameters.size_bytes() <= std::numeric_limits<uint8_t>::max());
-  }
-
-  // Decodes the packet based on the specified endianness.
-  static std::optional<EventPacket> Decode(ConstByteSpan data);
-
-  // Encodes the packet based on the specified endianness.
-  //
-  // Returns:
-  //   OK - returns the encoded packet.
-  //   RESOURCE_EXHAUSTED - The input buffer is too small for this packet.
-  Result<ConstByteSpan> Encode(ByteSpan buffer) const;
-
-  constexpr uint8_t event_code() const { return event_code_; }
-
-  constexpr const ConstByteSpan& parameters() const { return parameters_; }
-
- private:
-  uint8_t event_code_;
-  ConstByteSpan parameters_;
-};
-
-}  // namespace pw::bluetooth_hci
diff --git a/pw_bluetooth_hci/public/pw_bluetooth_hci/uart_transport.h b/pw_bluetooth_hci/public/pw_bluetooth_hci/uart_transport.h
deleted file mode 100644
index 6c6c78e..0000000
--- a/pw_bluetooth_hci/public/pw_bluetooth_hci/uart_transport.h
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <bit>
-
-#include "pw_bluetooth_hci/packet.h"
-#include "pw_bytes/span.h"
-#include "pw_function/function.h"
-#include "pw_status/status_with_size.h"
-
-namespace pw::bluetooth_hci {
-
-// The HCI UART Transport Layer supports uses the following packet indicator
-// bytes to decode the HCI packet type as defined by the Bluetooth Core
-// Specification version 5.3 "Host Controller Interface Transport Layer" volume
-// 4, part A:
-constexpr inline std::byte kUartCommandPacketIndicator = std::byte{0x01};
-constexpr inline std::byte kUartAsyncDataPacketIndicator = std::byte{0x02};
-constexpr inline std::byte kUartSyncDataPacketIndicator = std::byte{0x03};
-constexpr inline std::byte kUartEventPacketIndicator = std::byte{0x04};
-
-// The HCI UART Transport Layer may be invoked with the following packet types,
-// as defined by Bluetooth Core Specification version 5.3 "Host Controller
-// Interface Transport Layer" volume 4, part A:
-//   PacketType::kCommandPacket
-//   PacketType::kAsyncDataPacket
-//   PacketType::kSyncDataPacket
-//   PacketType::kEventPacket
-using DecodedPacketCallback = Function<void(const Packet& packet)>;
-
-// Parses the HCI Packets out of a HCI UART Transport Layer buffer.
-//
-// Parses as many complete HCI packets out of the provided buffer based on the
-// HCI UART Transport Layer as defined by Bluetooth Core Specification version
-// 5.3 "Host Controller Interface Transport Layer" volume 4, part A.
-//
-// The HciPacketCallback is invoked for each full HCI packet.
-//
-// Returns the number of bytes processed and a status based on:
-// OK - No invalid packet indicator found.
-// DATA_LOSS - An invalid packet indicator was detected between packets.
-//             Synchronization has been lost. The caller is responsible for
-//             regaining synchronization
-//
-// Note: The caller is responsible for detecting the lack of progress due to
-// an undersized data buffer and/or an invalid length field in case a full
-// buffer is passed and no bytes are processed.
-StatusWithSize DecodeHciUartData(ConstByteSpan data,
-                                 const DecodedPacketCallback& packet_callback);
-
-}  // namespace pw::bluetooth_hci
diff --git a/pw_bluetooth_hci/uart_transport.cc b/pw_bluetooth_hci/uart_transport.cc
deleted file mode 100644
index d62e300..0000000
--- a/pw_bluetooth_hci/uart_transport.cc
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_bluetooth_hci/uart_transport.h"
-
-namespace pw::bluetooth_hci {
-
-StatusWithSize DecodeHciUartData(ConstByteSpan data,
-                                 const DecodedPacketCallback& packet_callback) {
-  size_t bytes_consumed = 0;
-  while (data.size_bytes() > 0) {
-    const std::byte packet_indicator = data[0];
-    data = data.subspan(1);  // Pop off the packet indicator byte.
-    // Note that we do not yet claim the byte consumed until we know what it is,
-    // as it may be a partial HCI packet which cannot be consumed until later.
-
-    size_t packet_size_bytes = 0;
-    switch (packet_indicator) {
-      case kUartCommandPacketIndicator: {
-        const std::optional<CommandPacket> maybe_packet =
-            CommandPacket::Decode(data, std::endian::little);
-        if (!maybe_packet.has_value()) {
-          return StatusWithSize(
-              bytes_consumed);  // Not enough data to complete this packet.
-        }
-
-        const CommandPacket& packet = maybe_packet.value();
-        packet_callback(packet);
-        packet_size_bytes = packet.size_bytes();
-        break;
-      }
-
-      case kUartAsyncDataPacketIndicator: {
-        const std::optional<AsyncDataPacket> maybe_packet =
-            AsyncDataPacket::Decode(data, std::endian::little);
-        if (!maybe_packet.has_value()) {
-          return StatusWithSize(
-              bytes_consumed);  // Not enough data to complete this packet.
-        }
-
-        const AsyncDataPacket& packet = maybe_packet.value();
-        packet_callback(packet);
-        packet_size_bytes = packet.size_bytes();
-        break;
-      }
-
-      case kUartSyncDataPacketIndicator: {
-        const std::optional<SyncDataPacket> maybe_packet =
-            SyncDataPacket::Decode(data, std::endian::little);
-        if (!maybe_packet.has_value()) {
-          return StatusWithSize(
-              bytes_consumed);  // Not enough data to complete this packet.
-        }
-
-        const SyncDataPacket& packet = maybe_packet.value();
-        packet_callback(packet);
-        packet_size_bytes = packet.size_bytes();
-        break;
-      }
-
-      case kUartEventPacketIndicator: {
-        const std::optional<EventPacket> maybe_packet =
-            EventPacket::Decode(data);
-        if (!maybe_packet.has_value()) {
-          return StatusWithSize(
-              bytes_consumed);  // Not enough data to complete this packet.
-        }
-
-        const EventPacket& packet = maybe_packet.value();
-        packet_callback(packet);
-        packet_size_bytes = packet.size_bytes();
-        break;
-      }
-
-      default:
-        // Unrecognized PacketIndicator type, we've lost synchronization!
-        ++bytes_consumed;  // Consume the invalid packet indicator.
-        return StatusWithSize::DataLoss(bytes_consumed);
-    }
-
-    data = data.subspan(packet_size_bytes);  // Pop off the HCI packet.
-    // Consume the packet indicator and the packet.
-    bytes_consumed += 1 + packet_size_bytes;
-  }
-  return StatusWithSize(bytes_consumed);
-}
-
-}  // namespace pw::bluetooth_hci
diff --git a/pw_bluetooth_hci/uart_transport_fuzzer.cc b/pw_bluetooth_hci/uart_transport_fuzzer.cc
deleted file mode 100644
index 3097374..0000000
--- a/pw_bluetooth_hci/uart_transport_fuzzer.cc
+++ /dev/null
@@ -1,117 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstddef>
-#include <span>
-
-#include "pw_bluetooth_hci/packet.h"
-#include "pw_bluetooth_hci/uart_transport.h"
-#include "pw_bytes/span.h"
-#include "pw_status/status_with_size.h"
-#include "pw_stream/null_stream.h"
-
-namespace pw::bluetooth_hci {
-namespace {
-
-// A very simple structure unaware fuzzer.
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  DecodedPacketCallback packet_callback = [](const Packet& packet) {
-    // Instead of doing nothing with the random packet content, attempt to
-    // consume the entire packet API by streaming it into the null stream.
-    stream::Writer& stream = stream::NullStream::Instance();
-
-    switch (packet.type()) {
-      case Packet::Type::kCommandPacket: {
-        const CommandPacket& command_packet = packet.command_packet();
-
-        const uint16_t opcode = command_packet.opcode();
-        stream.Write(std::as_bytes(std::span<const uint16_t>(&opcode, 1)));
-
-        const uint16_t opcode_command_field =
-            command_packet.opcode_command_field();
-        stream.Write(
-            std::as_bytes(std::span<const uint16_t>(&opcode_command_field, 1)));
-
-        const uint8_t opcode_group_field = command_packet.opcode_group_field();
-        stream.Write(
-            std::as_bytes(std::span<const uint8_t>(&opcode_group_field, 1)));
-
-        stream.Write(command_packet.parameters());
-        return;
-      }
-
-      case Packet::Type::kAsyncDataPacket: {
-        const AsyncDataPacket& async_data_packet = packet.async_data_packet();
-
-        const uint16_t handle_and_fragmentation_bits =
-            async_data_packet.handle_and_fragmentation_bits();
-        stream.Write(std::as_bytes(
-            std::span<const uint16_t>(&handle_and_fragmentation_bits, 1)));
-
-        const uint16_t handle = async_data_packet.handle();
-        stream.Write(std::as_bytes(std::span<const uint16_t>(&handle, 1)));
-
-        const uint8_t pb_flag = async_data_packet.pb_flag();
-        stream.Write(std::as_bytes(std::span<const uint8_t>(&pb_flag, 1)));
-
-        const uint8_t bc_flag = async_data_packet.bc_flag();
-        stream.Write(std::as_bytes(std::span<const uint8_t>(&bc_flag, 1)));
-
-        stream.Write(async_data_packet.data());
-        return;
-      }
-
-      case Packet::Type::kSyncDataPacket: {
-        const SyncDataPacket& sync_data_packet = packet.sync_data_packet();
-
-        const uint16_t handle_and_status_bits =
-            sync_data_packet.handle_and_status_bits();
-        stream.Write(std::as_bytes(
-            std::span<const uint16_t>(&handle_and_status_bits, 1)));
-
-        const uint16_t handle = sync_data_packet.handle();
-        stream.Write(std::as_bytes(std::span<const uint16_t>(&handle, 1)));
-
-        const uint8_t packet_status_flag =
-            sync_data_packet.packet_status_flag();
-        stream.Write(
-            std::as_bytes(std::span<const uint8_t>(&packet_status_flag, 1)));
-
-        stream.Write(sync_data_packet.data());
-        return;
-      }
-
-      case Packet::Type::kEventPacket: {
-        const EventPacket& event_packet = packet.event_packet();
-
-        const uint8_t event_code = event_packet.event_code();
-        stream.Write(std::as_bytes(std::span<const uint8_t>(&event_code, 1)));
-
-        stream.Write(event_packet.parameters());
-        return;
-      }
-
-      default:
-        return;
-    }
-  };
-
-  const StatusWithSize result =
-      DecodeHciUartData(std::as_bytes(std::span(data, size)), packet_callback);
-  result.status().IgnoreError();
-  return 0;
-}
-
-}  // namespace
-}  // namespace pw::bluetooth_hci
diff --git a/pw_bluetooth_hci/uart_transport_test.cc b/pw_bluetooth_hci/uart_transport_test.cc
deleted file mode 100644
index 1a74eeb..0000000
--- a/pw_bluetooth_hci/uart_transport_test.cc
+++ /dev/null
@@ -1,409 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_bluetooth_hci/uart_transport.h"
-
-#include "gtest/gtest.h"
-#include "pw_bluetooth_hci/packet.h"
-#include "pw_bytes/byte_builder.h"
-#include "pw_status/status.h"
-
-namespace pw::bluetooth_hci {
-namespace {
-
-class UartTransportTest : public ::testing::Test {
- protected:
-  constexpr static std::byte kInvalidPacketIndicator = std::byte{0x0};
-  static_assert(kInvalidPacketIndicator != kUartCommandPacketIndicator);
-  static_assert(kInvalidPacketIndicator != kUartAsyncDataPacketIndicator);
-  static_assert(kInvalidPacketIndicator != kUartSyncDataPacketIndicator);
-  static_assert(kInvalidPacketIndicator != kUartEventPacketIndicator);
-
-  constexpr static size_t kUartBufferSizeBytes = 256;
-  ByteBuffer<kUartBufferSizeBytes> uart_buffer_;
-};
-
-TEST_F(UartTransportTest, EmptyBuffer) {
-  const StatusWithSize status_with_size =
-      DecodeHciUartData(ConstByteSpan(), [](const Packet&) { FAIL(); });
-  EXPECT_EQ(status_with_size.status(), OkStatus());
-  EXPECT_EQ(status_with_size.size(), 0u);
-}
-
-TEST_F(UartTransportTest, InvalidPacketIndicator) {
-  uart_buffer_.push_back(kInvalidPacketIndicator);
-  ASSERT_EQ(uart_buffer_.status(), OkStatus());
-
-  const StatusWithSize status_with_size =
-      DecodeHciUartData(uart_buffer_, [](const Packet&) { FAIL(); });
-  EXPECT_EQ(status_with_size.status(), Status::DataLoss());
-  EXPECT_EQ(status_with_size.size(), 1u);
-}
-
-TEST_F(UartTransportTest, CommandPacketIndicatorOnly) {
-  uart_buffer_.push_back(kUartCommandPacketIndicator);
-  ASSERT_EQ(uart_buffer_.status(), OkStatus());
-
-  const StatusWithSize status_with_size =
-      DecodeHciUartData(uart_buffer_, [](const Packet&) { FAIL(); });
-  EXPECT_EQ(status_with_size.status(), OkStatus());
-  EXPECT_EQ(status_with_size.size(), 0u);
-}
-
-TEST_F(UartTransportTest, CommandPacketPartialPacket) {
-  uart_buffer_.push_back(kUartCommandPacketIndicator);
-
-  std::array<std::byte, CommandPacket::kHeaderSizeBytes> packet_buffer;
-  CommandPacket packet(0u, ConstByteSpan());
-  const Result<ConstByteSpan> result = packet.Encode(packet_buffer);
-  ASSERT_EQ(result.status(), OkStatus());
-
-  uart_buffer_.append(result.value().first(result.value().size_bytes() - 1));
-  ASSERT_EQ(uart_buffer_.status(), OkStatus());
-
-  const StatusWithSize status_with_size =
-      DecodeHciUartData(uart_buffer_, [](const Packet&) { FAIL(); });
-  EXPECT_EQ(status_with_size.status(), OkStatus());
-  EXPECT_EQ(status_with_size.size(), 0u);
-}
-
-TEST_F(UartTransportTest, CommandPacket) {
-  uart_buffer_.push_back(kUartCommandPacketIndicator);
-
-  std::array<std::byte, CommandPacket::kHeaderSizeBytes> packet_buffer;
-  CommandPacket packet(0u, ConstByteSpan());
-  const Result<ConstByteSpan> result = packet.Encode(packet_buffer);
-  ASSERT_EQ(result.status(), OkStatus());
-
-  uart_buffer_.append(result.value());
-  ASSERT_EQ(uart_buffer_.status(), OkStatus());
-
-  size_t command_packet_count = 0;
-  const StatusWithSize status_with_size =
-      DecodeHciUartData(uart_buffer_, [&](const Packet& decoded_packet) {
-        ASSERT_EQ(decoded_packet.type(), Packet::Type::kCommandPacket);
-        ++command_packet_count;
-      });
-  EXPECT_EQ(status_with_size.status(), OkStatus());
-  EXPECT_EQ(status_with_size.size(), uart_buffer_.size());
-  EXPECT_EQ(command_packet_count, 1u);
-}
-
-TEST_F(UartTransportTest, AsyncDataPacketIndicatorOnly) {
-  uart_buffer_.push_back(kUartAsyncDataPacketIndicator);
-  ASSERT_EQ(uart_buffer_.status(), OkStatus());
-
-  const StatusWithSize status_with_size =
-      DecodeHciUartData(uart_buffer_, [](const Packet&) { FAIL(); });
-  EXPECT_EQ(status_with_size.status(), OkStatus());
-  EXPECT_EQ(status_with_size.size(), 0u);
-}
-
-TEST_F(UartTransportTest, AsyncDataPacketPartialPacket) {
-  uart_buffer_.push_back(kUartAsyncDataPacketIndicator);
-
-  std::array<std::byte, AsyncDataPacket::kHeaderSizeBytes> packet_buffer;
-  AsyncDataPacket packet(0u, ConstByteSpan());
-  const Result<ConstByteSpan> result = packet.Encode(packet_buffer);
-  ASSERT_EQ(result.status(), OkStatus());
-
-  uart_buffer_.append(result.value().first(result.value().size_bytes() - 1));
-  ASSERT_EQ(uart_buffer_.status(), OkStatus());
-
-  const StatusWithSize status_with_size =
-      DecodeHciUartData(uart_buffer_, [](const Packet&) { FAIL(); });
-  EXPECT_EQ(status_with_size.status(), OkStatus());
-  EXPECT_EQ(status_with_size.size(), 0u);
-}
-
-TEST_F(UartTransportTest, AsyncDataPacket) {
-  uart_buffer_.push_back(kUartAsyncDataPacketIndicator);
-
-  std::array<std::byte, AsyncDataPacket::kHeaderSizeBytes> packet_buffer;
-  AsyncDataPacket packet(0u, ConstByteSpan());
-  const Result<ConstByteSpan> result = packet.Encode(packet_buffer);
-  ASSERT_EQ(result.status(), OkStatus());
-
-  uart_buffer_.append(result.value());
-  ASSERT_EQ(uart_buffer_.status(), OkStatus());
-
-  size_t async_data_packet_count = 0;
-  const StatusWithSize status_with_size =
-      DecodeHciUartData(uart_buffer_, [&](const Packet& decoded_packet) {
-        ASSERT_EQ(decoded_packet.type(), Packet::Type::kAsyncDataPacket);
-        ++async_data_packet_count;
-      });
-  EXPECT_EQ(status_with_size.status(), OkStatus());
-  EXPECT_EQ(status_with_size.size(), uart_buffer_.size());
-  EXPECT_EQ(async_data_packet_count, 1u);
-}
-
-TEST_F(UartTransportTest, SyncDataPacketIndicatorOnly) {
-  uart_buffer_.push_back(kUartSyncDataPacketIndicator);
-  ASSERT_EQ(uart_buffer_.status(), OkStatus());
-
-  const StatusWithSize status_with_size =
-      DecodeHciUartData(uart_buffer_, [](const Packet&) { FAIL(); });
-  EXPECT_EQ(status_with_size.status(), OkStatus());
-  EXPECT_EQ(status_with_size.size(), 0u);
-}
-
-TEST_F(UartTransportTest, SyncDataPacketPartialPacket) {
-  uart_buffer_.push_back(kUartSyncDataPacketIndicator);
-
-  std::array<std::byte, SyncDataPacket::kHeaderSizeBytes> packet_buffer;
-  SyncDataPacket packet(0u, ConstByteSpan());
-  const Result<ConstByteSpan> result = packet.Encode(packet_buffer);
-  ASSERT_EQ(result.status(), OkStatus());
-
-  uart_buffer_.append(result.value().first(result.value().size_bytes() - 1));
-  ASSERT_EQ(uart_buffer_.status(), OkStatus());
-
-  const StatusWithSize status_with_size =
-      DecodeHciUartData(uart_buffer_, [](const Packet&) { FAIL(); });
-  EXPECT_EQ(status_with_size.status(), OkStatus());
-  EXPECT_EQ(status_with_size.size(), 0u);
-}
-
-TEST_F(UartTransportTest, SyncDataPacket) {
-  uart_buffer_.push_back(kUartSyncDataPacketIndicator);
-
-  std::array<std::byte, SyncDataPacket::kHeaderSizeBytes> packet_buffer;
-  SyncDataPacket packet(0u, ConstByteSpan());
-  const Result<ConstByteSpan> result = packet.Encode(packet_buffer);
-  ASSERT_EQ(result.status(), OkStatus());
-
-  uart_buffer_.append(result.value());
-  ASSERT_EQ(uart_buffer_.status(), OkStatus());
-
-  size_t sync_data_packet_count = 0;
-  const StatusWithSize status_with_size =
-      DecodeHciUartData(uart_buffer_, [&](const Packet& decoded_packet) {
-        ASSERT_EQ(decoded_packet.type(), Packet::Type::kSyncDataPacket);
-        ++sync_data_packet_count;
-      });
-  EXPECT_EQ(status_with_size.status(), OkStatus());
-  EXPECT_EQ(status_with_size.size(), uart_buffer_.size());
-  EXPECT_EQ(sync_data_packet_count, 1u);
-}
-
-TEST_F(UartTransportTest, EventPacketIndicatorOnly) {
-  uart_buffer_.push_back(kUartEventPacketIndicator);
-  ASSERT_EQ(uart_buffer_.status(), OkStatus());
-
-  const StatusWithSize status_with_size =
-      DecodeHciUartData(uart_buffer_, [](const Packet&) { FAIL(); });
-  EXPECT_EQ(status_with_size.status(), OkStatus());
-  EXPECT_EQ(status_with_size.size(), 0u);
-}
-
-TEST_F(UartTransportTest, EventPacketPartialPacket) {
-  uart_buffer_.push_back(kUartEventPacketIndicator);
-
-  std::array<std::byte, EventPacket::kHeaderSizeBytes> packet_buffer;
-  EventPacket packet(0u, ConstByteSpan());
-  const Result<ConstByteSpan> result = packet.Encode(packet_buffer);
-  ASSERT_EQ(result.status(), OkStatus());
-
-  uart_buffer_.append(result.value().first(result.value().size_bytes() - 1));
-  ASSERT_EQ(uart_buffer_.status(), OkStatus());
-
-  const StatusWithSize status_with_size =
-      DecodeHciUartData(uart_buffer_, [](const Packet&) { FAIL(); });
-  EXPECT_EQ(status_with_size.status(), OkStatus());
-  EXPECT_EQ(status_with_size.size(), 0u);
-}
-
-TEST_F(UartTransportTest, EventPacket) {
-  uart_buffer_.push_back(kUartEventPacketIndicator);
-
-  std::array<std::byte, EventPacket::kHeaderSizeBytes> packet_buffer;
-  EventPacket packet(0u, ConstByteSpan());
-  const Result<ConstByteSpan> result = packet.Encode(packet_buffer);
-  ASSERT_EQ(result.status(), OkStatus());
-
-  uart_buffer_.append(result.value());
-  ASSERT_EQ(uart_buffer_.status(), OkStatus());
-
-  size_t event_packet_count = 0;
-  const StatusWithSize status_with_size =
-      DecodeHciUartData(uart_buffer_, [&](const Packet& decoded_packet) {
-        ASSERT_EQ(decoded_packet.type(), Packet::Type::kEventPacket);
-        ++event_packet_count;
-      });
-  EXPECT_EQ(status_with_size.status(), OkStatus());
-  EXPECT_EQ(status_with_size.size(), uart_buffer_.size());
-  EXPECT_EQ(event_packet_count, 1u);
-}
-
-TEST_F(UartTransportTest, BadIndicatorThenPacketSequence) {
-  // Invalid packet indicator.
-  uart_buffer_.push_back(kInvalidPacketIndicator);
-
-  // Valid EventPacket w/ packet indicator.
-  uart_buffer_.push_back(kUartEventPacketIndicator);
-  std::array<std::byte, EventPacket::kHeaderSizeBytes> packet_buffer;
-  EventPacket packet(0u, ConstByteSpan());
-  const Result<ConstByteSpan> result = packet.Encode(packet_buffer);
-  ASSERT_EQ(result.status(), OkStatus());
-  uart_buffer_.append(result.value());
-  ASSERT_EQ(uart_buffer_.status(), OkStatus());
-
-  // First decode should fail after consuming the invalid packet indicator.
-  StatusWithSize status_with_size =
-      DecodeHciUartData(uart_buffer_, [&](const Packet&) { FAIL(); });
-  EXPECT_EQ(status_with_size.status(), Status::DataLoss());
-  EXPECT_EQ(status_with_size.size(), 1u);
-
-  const ConstByteSpan remaining_data =
-      ConstByteSpan(uart_buffer_).subspan(status_with_size.size());
-
-  // Second decode should work now that the invalid byte is gone.
-  size_t event_packet_count = 0;
-  status_with_size =
-      DecodeHciUartData(remaining_data, [&](const Packet& decoded_packet) {
-        ASSERT_EQ(decoded_packet.type(), Packet::Type::kEventPacket);
-        ++event_packet_count;
-      });
-  EXPECT_EQ(status_with_size.status(), OkStatus());
-  EXPECT_EQ(status_with_size.size(), remaining_data.size_bytes());
-  EXPECT_EQ(event_packet_count, 1u);
-}
-
-TEST_F(UartTransportTest, PacketThenBadIndicatorSequence) {
-  // Valid EventPacket w/ packet indicator.
-  uart_buffer_.push_back(kUartEventPacketIndicator);
-  std::array<std::byte, EventPacket::kHeaderSizeBytes> packet_buffer;
-  EventPacket packet(0u, ConstByteSpan());
-  const Result<ConstByteSpan> result = packet.Encode(packet_buffer);
-  ASSERT_EQ(result.status(), OkStatus());
-  uart_buffer_.append(result.value());
-  ASSERT_EQ(uart_buffer_.status(), OkStatus());
-
-  // Invalid packet indicator.
-  uart_buffer_.push_back(kInvalidPacketIndicator);
-
-  // First decode should fail, consuming all data.
-  size_t event_packet_count = 0;
-  const StatusWithSize status_with_size =
-      DecodeHciUartData(uart_buffer_, [&](const Packet& decoded_packet) {
-        ASSERT_EQ(decoded_packet.type(), Packet::Type::kEventPacket);
-        ++event_packet_count;
-      });
-  EXPECT_EQ(status_with_size.status(), Status::DataLoss());
-  EXPECT_EQ(status_with_size.size(), uart_buffer_.size());
-  EXPECT_EQ(event_packet_count, 1u);
-}
-
-TEST_F(UartTransportTest, AllPacketTypes) {
-  // Valid CommandPacket w/ packet indicator.
-  {
-    uart_buffer_.push_back(kUartCommandPacketIndicator);
-    std::array<std::byte, CommandPacket::kHeaderSizeBytes> packet_buffer;
-    CommandPacket packet(0u, ConstByteSpan());
-    const Result<ConstByteSpan> result = packet.Encode(packet_buffer);
-    ASSERT_EQ(result.status(), OkStatus());
-    uart_buffer_.append(result.value());
-    ASSERT_EQ(uart_buffer_.status(), OkStatus());
-  }
-
-  // Valid AsyncDataPacket w/ packet indicator.
-  {
-    uart_buffer_.push_back(kUartAsyncDataPacketIndicator);
-    std::array<std::byte, AsyncDataPacket::kHeaderSizeBytes> packet_buffer;
-    AsyncDataPacket packet(0u, ConstByteSpan());
-    const Result<ConstByteSpan> result = packet.Encode(packet_buffer);
-    ASSERT_EQ(result.status(), OkStatus());
-    uart_buffer_.append(result.value());
-    ASSERT_EQ(uart_buffer_.status(), OkStatus());
-  }
-
-  // Valid SyncDataPacket w/ packet indicator.
-  {
-    uart_buffer_.push_back(kUartSyncDataPacketIndicator);
-    std::array<std::byte, SyncDataPacket::kHeaderSizeBytes> packet_buffer;
-    SyncDataPacket packet(0u, ConstByteSpan());
-    const Result<ConstByteSpan> result = packet.Encode(packet_buffer);
-    ASSERT_EQ(result.status(), OkStatus());
-    uart_buffer_.append(result.value());
-    ASSERT_EQ(uart_buffer_.status(), OkStatus());
-  }
-
-  // Valid EventPacket w/ packet indicator.
-  {
-    uart_buffer_.push_back(kUartEventPacketIndicator);
-    std::array<std::byte, EventPacket::kHeaderSizeBytes> packet_buffer;
-    EventPacket packet(0u, ConstByteSpan());
-    const Result<ConstByteSpan> result = packet.Encode(packet_buffer);
-    ASSERT_EQ(result.status(), OkStatus());
-    uart_buffer_.append(result.value());
-    ASSERT_EQ(uart_buffer_.status(), OkStatus());
-  }
-
-  // First decode should succeed, consuming all data.
-  size_t packet_count = 0u;
-  const StatusWithSize status_with_size =
-      DecodeHciUartData(uart_buffer_, [&](const Packet& decoded_packet) {
-        ++packet_count;
-        switch (packet_count) {
-          case 1u:
-            EXPECT_EQ(decoded_packet.type(), Packet::Type::kCommandPacket);
-            break;
-
-          case 2u:
-            EXPECT_EQ(decoded_packet.type(), Packet::Type::kAsyncDataPacket);
-            break;
-
-          case 3u:
-            EXPECT_EQ(decoded_packet.type(), Packet::Type::kSyncDataPacket);
-            break;
-
-          case 4u:
-            EXPECT_EQ(decoded_packet.type(), Packet::Type::kEventPacket);
-            break;
-        }
-      });
-  EXPECT_EQ(status_with_size.status(), OkStatus());
-  EXPECT_EQ(status_with_size.size(), uart_buffer_.size());
-  EXPECT_EQ(packet_count, 4u);
-}
-
-TEST_F(UartTransportTest, LotsOfEventPackets) {
-  std::array<std::byte, EventPacket::kHeaderSizeBytes> packet_buffer;
-  EventPacket packet(0u, ConstByteSpan());
-  const Result<ConstByteSpan> result = packet.Encode(packet_buffer);
-  ASSERT_EQ(result.status(), OkStatus());
-
-  size_t expected_packet_count = 0;
-  while ((uart_buffer_.max_size() - uart_buffer_.size()) >
-         packet.size_bytes()) {
-    ++expected_packet_count;
-    uart_buffer_.push_back(kUartEventPacketIndicator);
-    uart_buffer_.append(result.value());
-    ASSERT_EQ(uart_buffer_.status(), OkStatus());
-  }
-
-  size_t event_packet_count = 0;
-  const StatusWithSize status_with_size =
-      DecodeHciUartData(uart_buffer_, [&](const Packet& decoded_packet) {
-        ASSERT_EQ(decoded_packet.type(), Packet::Type::kEventPacket);
-        ++event_packet_count;
-      });
-  EXPECT_EQ(status_with_size.status(), OkStatus());
-  EXPECT_EQ(status_with_size.size(), uart_buffer_.size());
-  EXPECT_EQ(event_packet_count, expected_packet_count);
-}
-
-}  // namespace
-}  // namespace pw::bluetooth_hci
diff --git a/pw_boot/BUILD.bazel b/pw_boot/BUILD.bazel
deleted file mode 100644
index 8871919..0000000
--- a/pw_boot/BUILD.bazel
+++ /dev/null
@@ -1,42 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_facade",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_facade(
-    name = "facade",
-    hdrs = [
-        "public/pw_boot/boot.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_preprocessor",
-    ],
-)
-
-pw_cc_library(
-    name = "pw_boot",
-    deps = [
-        ":facade",
-        "@pigweed_config//:pw_boot_backend",
-    ],
-)
diff --git a/pw_boot/BUILD.gn b/pw_boot/BUILD.gn
deleted file mode 100644
index 0082ad7..0000000
--- a/pw_boot/BUILD.gn
+++ /dev/null
@@ -1,34 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_boot/backend.gni")
-import("$dir_pw_build/facade.gni")
-import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_unit_test/test.gni")
-
-config("default_config") {
-  include_dirs = [ "public" ]
-}
-
-pw_facade("pw_boot") {
-  public_configs = [ ":default_config" ]
-  public = [ "public/pw_boot/boot.h" ]
-  backend = pw_boot_BACKEND
-}
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
diff --git a/pw_boot/OWNERS b/pw_boot/OWNERS
deleted file mode 100644
index 103db5c..0000000
--- a/pw_boot/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-amontanez@google.com
-ewout@google.com
-keybuk@google.com
diff --git a/pw_boot/backend.gni b/pw_boot/backend.gni
deleted file mode 100644
index 580e367..0000000
--- a/pw_boot/backend.gni
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-declare_args() {
-  # Backend for the pw_boot module.
-  pw_boot_BACKEND = ""
-}
diff --git a/pw_boot/docs.rst b/pw_boot/docs.rst
deleted file mode 100644
index 23ea54a..0000000
--- a/pw_boot/docs.rst
+++ /dev/null
@@ -1,104 +0,0 @@
-.. _module-pw_boot:
-
--------
-pw_boot
--------
-Pigweed's boot module should provide a linker script and some early
-initialization of static memory regions and C++ constructors. This is enough to
-get many targets booted and ready to run C++ code.
-
-This module is split into two components:
-
-1. The facade (this module) which declares the symbols exported by the backend
-2. The backend, provided elsewhere, that provides the implementation
-
-.. warning::
-  This module is currently NOT stable! Depending on this module may cause
-  breakages as this module is updated.
-
-Sequence
-========
-The high level pw_boot boot sequence looks like the following pseudo-code
-invocation of the user-implemented functions:
-
-.. code:: cpp
-
-  void pw_boot_Entry() {  // Boot entry point provided by backend.
-    pw_boot_PreStaticMemoryInit();  // User-implemented function.
-    // Static memory initialization.
-    pw_boot_PreStaticConstructorInit();  // User-implemented function.
-    // C++ static constructors are invoked.
-    pw_boot_PreMainInit();  // User-implemented function.
-    main();  // User-implemented function.
-    pw_boot_PostMain();  // User-implemented function.
-    PW_UNREACHABLE;
-  }
-
-Setup
-=====
-
-User-Implemented Functions
---------------------------
-This module expects all of these extern "C" functions to be defined outside this
-module:
-
- - ``int main()``: This is where applications reside.
- - ``void pw_boot_PreStaticMemoryInit()``: This function executes just before
-   static memory has been zeroed and static data is intialized. This function
-   should set up any early initialization that should be done before static
-   memory is initialized, such as:
-
-   - Enabling the FPU or other coprocessors.
-   - Opting into extra restrictions such as disabling unaligned access to ensure
-     the restrictions are active during static RAM initialization.
-   - Initial CPU clock, flash, and memory configurations including potentially
-     enabling extra memory regions with .bss and .data sections, such as SDRAM
-     or backup powered SRAM.
-   - Fault handler initialization if required before static memory
-     initialization.
-
-   .. warning::
-     Code running in this hook is violating the C spec as static values are not
-     yet initialized, meaning they have not been loaded (.data) nor
-     zero-initialized (.bss).
-
- - ``void pw_boot_PreStaticConstructorInit()``: This function executes just
-   before C++ static constructors are called. At this point, other static memory
-   has been zero or data initialized. This function should set up any early
-   initialization that should be done before C++ static constructors are run,
-   such as:
-
-   - Run time dependencies such as Malloc, and ergo sometimes the RTOS.
-   - Persistent memory that survives warm reboots.
-   - Enabling the MPU to catch nullptr dereferences during construction.
-   - Main stack watermarking.
-   - Further fault handling configuration necessary for your platform which
-     were not safe before pw_boot_PreStaticRamInit().
-   - Boot count and/or boot session UUID management.
-
- - ``void pw_boot_PreMainInit()``: This function executes just before main, and
-   can be used for any device initialization that isn't application specific.
-   Depending on your platform, this might be turning on a UART, setting up
-   default clocks, etc.
-
- - ``PW_NO_RETURN void pw_boot_PostMain()``: This function executes after main
-   has returned. This could be used for device specific teardown such as an
-   infinite loop, soft reset, or QEMU shutdown. In addition, if relevant for
-   your application, this would be the place to invoke the global static
-   destructors. This function must not return!
-
-
-If any of these functions are unimplemented, executables will encounter a link
-error.
-
-Backend-Implemented Functions
------------------------------
-Backends for this module must implement the following extern "C" function:
-
- - ``void pw_boot_Entry()``: This function executes as the entry point for
-   the application, and must perform call the user defined methods in the
-   appropriate sequence for the target (see Sequence above).
-
-Dependencies
-============
-  * ``pw_preprocessor`` module
diff --git a/pw_boot/public/pw_boot/boot.h b/pw_boot/public/pw_boot/boot.h
deleted file mode 100644
index 7d9729a..0000000
--- a/pw_boot/public/pw_boot/boot.h
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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
-
-// A pw_boot backend is similar to a traditional assembly startup file paired
-// with a linker script.
-
-#include <stdint.h>
-
-#include "pw_preprocessor/compiler.h"
-#include "pw_preprocessor/util.h"
-
-PW_EXTERN_C_START
-
-// Forward declaration of main. Pigweed applications are expected to implement
-// this function. An implementation of main() should NOT be provided by a
-// backend.
-int main(void);
-
-// Reset handler or boot entry point.
-//
-// Backends must provide this method, and this method must call the user
-// supplied functions below in the appropriate order, along with any other
-// early initialization required by the target.
-//
-// A minimal implementation would be:
-//
-//   void pw_boot_Entry() {  // Boot entry point provided by backend.
-//     pw_boot_PreStaticMemoryInit();  // User-implemented function.
-//     // Static memory initialization.
-//     pw_boot_PreStaticConstructorInit();  // User-implemented function.
-//     // C++ static constructors are invoked.
-//     pw_boot_PreMainInit();  // User-implemented function.
-//     main();  // User-implemented function.
-//     pw_boot_PostMain();  // User-implemented function.
-//     PW_UNREACHABLE;
-//   }
-PW_NO_RETURN void pw_boot_Entry(void);
-
-// pw_boot hook: Before static memory is initialized (user supplied)
-//
-// This is a hook function that users of pw_boot must supply. It is called
-// immediately upon entry to pw_boot_Entry() and before zero initialization of
-// RAM (.bss) and loading values into static memory (commonly labeled as the
-// .data section in an ELF file).
-// WARNING: Be EXTREMELY careful when in the context of this function as it
-// violates the C spec in several ways as .bss has not yet been zero-initialized
-// and static values have not yet been loaded into memory. This function should
-// NOT be implemented by a pw_boot backend.
-//
-// Interrupts are disabled until after this function returns.
-void pw_boot_PreStaticMemoryInit(void);
-
-// pw_boot hook: Before C++ static constructors are invoked (user supplied).
-//
-// This is a hook function that users of pw_boot must supply. It is called just
-// after zero initialization of RAM and loading values into static memory
-// (commonly labeled as the .data section in an ELF file). Per the naming, this
-// function is called just before C++ static constructors are invoked. It is
-// safe to run C code, but NOT safe to call out to any C++ code. This function
-// should NOT be implemented by a pw_boot backend.
-void pw_boot_PreStaticConstructorInit(void);
-
-// pw_boot hook: Before main is invoked (user supplied).
-//
-// This is a hook function that users of pw_boot must supply. It is called by
-// pw_boot_Entry() after memory initialization but before main. This allows
-// targets to have pre-main initialization of the device and seamlessly swap out
-// the main() implementation. This function should NOT be implemented by
-// a pw_boot backend.
-void pw_boot_PreMainInit(void);
-
-// pw_boot hook: After main returned (user supplied).
-//
-// This is a hook function that users of pw_boot must supply. It is called by
-// pw_boot_Entry() after main() has returned. This function must not return!
-// This function should NOT be implemented by a pw_boot backend.
-PW_NO_RETURN void pw_boot_PostMain(void);
-
-PW_EXTERN_C_END
diff --git a/pw_boot_armv7m/BUILD b/pw_boot_armv7m/BUILD
new file mode 100644
index 0000000..52f7901
--- /dev/null
+++ b/pw_boot_armv7m/BUILD
@@ -0,0 +1,38 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_boot_armv7m",
+    srcs = [
+        "core_init.c",
+        "public/pw_boot_armv7m/boot.h",
+    ],
+    includes = ["public"],
+    target_compatible_with = select({
+        "@platforms//cpu:armv7e-m": [],
+        "@platforms//cpu:armv7-m": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    }),
+    deps = [
+        "//pw_preprocessor",
+    ],
+)
diff --git a/pw_boot_armv7m/BUILD.gn b/pw_boot_armv7m/BUILD.gn
new file mode 100644
index 0000000..8b99407
--- /dev/null
+++ b/pw_boot_armv7m/BUILD.gn
@@ -0,0 +1,53 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/linker_script.gni")
+import("$dir_pw_build/target_types.gni")
+import("$dir_pw_docgen/docs.gni")
+
+declare_args() {
+  # TODO(frolv): Move this into pw_boot module when it is created.
+  pw_boot_BACKEND = ""
+
+  # This list should contain the necessary defines for setting pw_boot linker
+  # script memory regions.
+  pw_boot_armv7m_LINK_CONFIG_DEFINES = []
+}
+
+if (pw_boot_BACKEND == dir_pw_boot_armv7m) {
+  config("default_config") {
+    include_dirs = [ "public" ]
+  }
+
+  pw_linker_script("armv7m_linker_script") {
+    # pw_boot_armv7m_LINK_CONFIG_DEFINES is a list of defines provided by the
+    # target.
+    defines = pw_boot_armv7m_LINK_CONFIG_DEFINES
+    linker_script = "basic_armv7m.ld"
+  }
+
+  pw_source_set("pw_boot_armv7m") {
+    public_configs = [ ":default_config" ]
+    public = [ "public/pw_boot_armv7m/boot.h" ]
+    public_deps = [ "$dir_pw_preprocessor" ]
+    deps = [ ":armv7m_linker_script" ]
+    sources = [ "core_init.c" ]
+  }
+}
+
+pw_doc_group("docs") {
+  sources = [ "docs.rst" ]
+}
diff --git a/pw_boot_armv7m/basic_armv7m.ld b/pw_boot_armv7m/basic_armv7m.ld
new file mode 100644
index 0000000..e758aa2
--- /dev/null
+++ b/pw_boot_armv7m/basic_armv7m.ld
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2020 The Pigweed Authors
+ *
+ * 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
+ *
+ *     https://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.
+ */
+
+/* This relatively simplified linker script will work with many ARMv7-M cores
+ * that have on-board memory-mapped RAM and FLASH. For more complex projects and
+ * devices, it's possible this linker script will not be sufficient as-is.
+ *
+ * This linker script is likely not suitable for a project with a bootloader.
+ */
+
+/* Provide useful error messages when required configurations are not set. */
+#ifndef PW_BOOT_VECTOR_TABLE_BEGIN
+#error "PW_BOOT_VECTOR_TABLE_BEGIN is not defined, and is required to use pw_boot_armv7m"
+#endif  // PW_BOOT_VECTOR_TABLE_BEGIN
+
+#ifndef PW_BOOT_VECTOR_TABLE_SIZE
+#error "PW_BOOT_VECTOR_TABLE_SIZE is not defined, and is required to use pw_boot_armv7m"
+#endif  // PW_BOOT_VECTOR_TABLE_SIZE
+
+#ifndef PW_BOOT_FLASH_BEGIN
+#error "PW_BOOT_FLASH_BEGIN is not defined, and is required to use pw_boot_armv7m"
+#endif  // PW_BOOT_FLASH_BEGIN
+
+#ifndef PW_BOOT_FLASH_SIZE
+#error "PW_BOOT_FLASH_SIZE is not defined, and is required to use pw_boot_armv7m"
+#endif  // PW_BOOT_FLASH_SIZE
+
+#ifndef PW_BOOT_RAM_BEGIN
+#error "PW_BOOT_RAM_BEGIN is not defined, and is required to use pw_boot_armv7m"
+#endif  // PW_BOOT_RAM_BEGIN
+
+#ifndef PW_BOOT_RAM_SIZE
+#error "PW_BOOT_RAM_SIZE is not defined, and is required to use pw_boot_armv7m"
+#endif  // PW_BOOT_RAM_SIZE
+
+#ifndef PW_BOOT_HEAP_SIZE
+#error "PW_BOOT_HEAP_SIZE is not defined, and is required to use pw_boot_armv7m"
+#endif  // PW_BOOT_HEAP_SIZE
+
+#ifndef PW_BOOT_MIN_STACK_SIZE
+#error "PW_BOOT_MIN_STACK_SIZE is not defined, and is required to use pw_boot_armv7m"
+#endif  // PW_BOOT_MIN_STACK_SIZE
+
+
+/* Note: This technically doesn't set the firmware's entry point. Setting the
+ *       firmware entry point is done by setting vector_table[1]
+ *       (Reset_Handler). However, this DOES tell the compiler how to optimize
+ *       when --gc-sections is enabled.
+ */
+ENTRY(pw_boot_Entry)
+
+MEMORY
+{
+  /* TODO(pwbug/57): Make it possible for projects to freely customize
+   * memory regions.
+   */
+
+  /* Vector Table (typically in flash) */
+  VECTOR_TABLE(rx) : \
+    ORIGIN = PW_BOOT_VECTOR_TABLE_BEGIN, \
+    LENGTH = PW_BOOT_VECTOR_TABLE_SIZE
+  /* Internal Flash */
+  FLASH(rx) : \
+    ORIGIN = PW_BOOT_FLASH_BEGIN, \
+    LENGTH = PW_BOOT_FLASH_SIZE
+  /* Internal SRAM */
+  RAM(rwx) : \
+    ORIGIN = PW_BOOT_RAM_BEGIN, \
+    LENGTH = PW_BOOT_RAM_SIZE
+}
+
+SECTIONS
+{
+  /* This is the link-time vector table. If used, the VTOR (Vector Table Offset
+   * Register) MUST point to this memory location in order to be used. This can
+   * be done by ensuring this section exists at the default location of the VTOR
+   * so it's used on reset, or by explicitly setting the VTOR in a bootloader
+   * manually to point to &pw_boot_vector_table_addr before interrupts are enabled.
+   */
+  .vector_table : ALIGN(512)
+  {
+    pw_boot_vector_table_addr = .;
+    KEEP(*(.vector_table))
+  } >VECTOR_TABLE
+
+  /* Main executable code. */
+  .code : ALIGN(8)
+  {
+    . = ALIGN(8);
+    /* Application code. */
+    *(.text)
+    *(.text*)
+    KEEP(*(.init))
+    KEEP(*(.fini))
+
+    . = ALIGN(8);
+    /* Constants.*/
+    *(.rodata)
+    *(.rodata*)
+
+    /* .preinit_array, .init_array, .fini_array are used by libc.
+     * Each section is a list of function pointers that are called pre-main and
+     * post-exit for object initialization and tear-down.
+     * Since the region isn't explicitly referenced, specify KEEP to prevent
+     * link-time garbage collection. SORT is used for sections that have strict
+     * init/de-init ordering requirements. */
+    . = ALIGN(8);
+    PROVIDE_HIDDEN(__preinit_array_start = .);
+    KEEP(*(.preinit_array*))
+    PROVIDE_HIDDEN(__preinit_array_end = .);
+
+    PROVIDE_HIDDEN(__init_array_start = .);
+    KEEP(*(SORT(.init_array.*)))
+    KEEP(*(.init_array*))
+    PROVIDE_HIDDEN(__init_array_end = .);
+
+    PROVIDE_HIDDEN(__fini_array_start = .);
+    KEEP(*(SORT(.fini_array.*)))
+    KEEP(*(.fini_array*))
+    PROVIDE_HIDDEN(__fini_array_end = .);
+  } >FLASH
+
+  /* Used by unwind-arm/ */
+  .ARM : ALIGN(8) {
+    __exidx_start = .;
+    *(.ARM.exidx*)
+    __exidx_end = .;
+  } >FLASH
+
+  /* Explicitly initialized global and static data. (.data)*/
+  .static_init_ram : ALIGN(8)
+  {
+    *(.data)
+    *(.data*)
+    . = ALIGN(8);
+  } >RAM AT> FLASH
+
+  /* Zero initialized global/static data. (.bss)
+   * This section is zero initialized in pw_boot_Entry(). */
+  .zero_init_ram : ALIGN(8)
+  {
+    *(.bss)
+    *(.bss*)
+    *(COMMON)
+    . = ALIGN(8);
+  } >RAM
+
+  .heap : ALIGN(8)
+  {
+    pw_boot_heap_low_addr = .;
+    . = . + PW_BOOT_HEAP_SIZE;
+    . = ALIGN(8);
+    pw_boot_heap_high_addr = .;
+  } >RAM
+
+  /* Link-time check for stack overlaps. */
+  .stack (NOLOAD) : ALIGN(8)
+  {
+    /* Set the address that the main stack pointer should be initialized to. */
+    pw_boot_stack_low_addr = .;
+    HIDDEN(_stack_size = ORIGIN(RAM) + LENGTH(RAM) - .);
+    /* Align the stack to a lower address to ensure it isn't out of range. */
+    HIDDEN(_stack_high = (. + _stack_size) & ~0x7);
+    ASSERT(_stack_high - . >= PW_BOOT_MIN_STACK_SIZE,
+           "Error: Not enough RAM for desired minimum stack size.");
+    . = _stack_high;
+    pw_boot_stack_high_addr = .;
+  } >RAM
+
+  /* Discard unwind info. */
+  .ARM.extab 0x0 (INFO) :
+  {
+    KEEP(*(.ARM.extab*))
+  }
+}
+
+/* Symbols used by core_init.c: */
+/* Start of .static_init_ram in FLASH. */
+_pw_static_init_flash_start = LOADADDR(.static_init_ram);
+
+/* Region of .static_init_ram in RAM. */
+_pw_static_init_ram_start = ADDR(.static_init_ram);
+_pw_static_init_ram_end = _pw_static_init_ram_start + SIZEOF(.static_init_ram);
+
+/* Region of .zero_init_ram. */
+_pw_zero_init_ram_start = ADDR(.zero_init_ram);
+_pw_zero_init_ram_end = _pw_zero_init_ram_start + SIZEOF(.zero_init_ram);
+
+/* arm-none-eabi expects `end` symbol to point to start of heap for sbrk. */
+PROVIDE(end = _pw_zero_init_ram_end);
diff --git a/pw_boot_cortex_m/bloaty_config.bloaty b/pw_boot_armv7m/bloaty_config.bloaty
similarity index 100%
rename from pw_boot_cortex_m/bloaty_config.bloaty
rename to pw_boot_armv7m/bloaty_config.bloaty
diff --git a/pw_boot_armv7m/core_init.c b/pw_boot_armv7m/core_init.c
new file mode 100644
index 0000000..c1b7307
--- /dev/null
+++ b/pw_boot_armv7m/core_init.c
@@ -0,0 +1,127 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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.
+
+//                               !!!WARNING!!!
+//
+// Some of the code in this file is run without static initialization expected
+// by C/C++. Any accesses to statically initialized objects/variables before
+// memory is initialized will result in undefined values and violates the C
+// specification. Only code run after memory initialization is complete will be
+// compliant and truly safe to run. In general, make early initialization code
+// run AFTER memory initialization has completed unless it is ABSOLUTELY
+// NECESSARY to modify the way memory is initialized.
+//
+// This file is similar to a traditional assembly startup file. It turns out
+// that everything typically done in ARMv7-M assembly startup can be done
+// straight from C code. This makes startup code easier to maintain, modify,
+// and read.
+//
+// When execution begins due to SoC power-on (or the device is reset), three
+// key things must happen to properly enter C++ execution context:
+//   1. Static variables must be loaded from flash to RAM.
+//   2. Zero-initialized variables must be zero-initialized.
+//   3. Statically allocated objects must have their constructors run.
+// The SoC doesn't inherently have a notion of how to do this, so this is
+// handled in StaticInit();
+//
+// Following this, execution is handed over to pw_PreMainInit() to facilitate
+// platform, project, or application pre-main initialization. When
+// pw_PreMainInit() returns, main() is executed.
+//
+// The simple flow is as follows:
+//   1. Power on
+//   2. PC and SP set (from vector_table by SoC, or by bootloader)
+//   3. pw_boot_Entry()
+//     3.1. pw_boot_PreStaticMemoryInit();
+//     3.2. Static-init memory (.data, .bss)
+//     3.3. pw_boot_PreStaticConstructorInit();
+//     3.4. Static C++ constructors
+//     3.5. pw_boot_PreMainInit()
+//     3.6. main()
+//     3.7. pw_boot_PostMain()
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "pw_boot_armv7m/boot.h"
+#include "pw_preprocessor/compiler.h"
+
+// Extern symbols provided by linker script.
+// These symbols tell us where various memory sections start and end.
+extern uint8_t _pw_static_init_ram_start;
+extern uint8_t _pw_static_init_ram_end;
+extern uint8_t _pw_static_init_flash_start;
+extern uint8_t _pw_zero_init_ram_start;
+extern uint8_t _pw_zero_init_ram_end;
+
+// Functions called as part of firmware initialization.
+void __libc_init_array(void);
+
+// WARNING: Be EXTREMELY careful when running code before this function
+// completes. The context before this function violates the C spec
+// (Section 6.7.8, paragraph 10 for example, which requires uninitialized static
+// values to be zero-initialized).
+void StaticMemoryInit(void) {
+  // Static-init RAM (load static values into ram, .data section init).
+  memcpy(&_pw_static_init_ram_start,
+         &_pw_static_init_flash_start,
+         &_pw_static_init_ram_end - &_pw_static_init_ram_start);
+
+  // Zero-init RAM (.bss section init).
+  memset(&_pw_zero_init_ram_start,
+         0,
+         &_pw_zero_init_ram_end - &_pw_zero_init_ram_start);
+}
+
+// WARNING: This code is run immediately upon boot, and performs initialization
+// of RAM. Note that code running before this function finishes memory
+// initialization will violate the C spec (Section 6.7.8, paragraph 10 for
+// example, which requires uninitialized static values to be zero-initialized).
+// Be EXTREMELY careful when running code before this function finishes RAM
+// initialization.
+//
+// This function runs immediately at boot because it is at index 1 of the
+// interrupt vector table.
+void pw_boot_Entry() {
+  // Run any init that must be done before static init of RAM which preps the
+  // .data (static values not yet loaded into ram) and .bss sections (not yet
+  // zero-initialized).
+  pw_boot_PreStaticMemoryInit();
+
+  // Note that code running before this function finishes memory
+  // initialization will violate the C spec (Section 6.7.8, paragraph 10 for
+  // example, which requires uninitialized static values to be
+  // zero-initialized). Be EXTREMELY careful when running code before this
+  // function finishes static memory initialization.
+  StaticMemoryInit();
+
+  // Run any init that must be done before C++ static constructors.
+  pw_boot_PreStaticConstructorInit();
+
+  // Call static constructors.
+  __libc_init_array();
+
+  // This function is not provided by pw_boot_armv7m, a platform layer, project,
+  // or application is expected to implement it.
+  pw_boot_PreMainInit();
+
+  // Run main.
+  main();
+
+  // In case main() returns, invoke this hook.
+  pw_boot_PostMain();
+
+  PW_UNREACHABLE;
+}
diff --git a/pw_boot_armv7m/docs.rst b/pw_boot_armv7m/docs.rst
new file mode 100644
index 0000000..d00bec4
--- /dev/null
+++ b/pw_boot_armv7m/docs.rst
@@ -0,0 +1,205 @@
+.. _module-pw_boot_armv7m:
+
+--------------
+pw_boot_armv7m
+--------------
+
+The ARMv7-M boot module provides a linker script and some early initialization
+of static memory regions and C++ constructors. This is enough to get many
+ARMv7-M cores booted and ready to run C++ code.
+
+This module is currently designed to support a very minimal device memory layout
+configuration:
+
+ - One contiguous region for RAM.
+ - One contiguous region for flash.
+ - Static, in-flash vector table at the default location expected by the SoC.
+
+Note that this module is not yet particularly suited for projects that utilize
+a bootloader, as it's relatively opinionated regarding where code is stored.
+
+.. warning::
+  This module is currently NOT stable! Depending on this module may cause
+  breakages as this module is updated.
+
+Sequence
+========
+
+The high level pw_boot_armv7m boot sequence looks like the following psuedo-code
+invocation of the user-implemented functions:
+
+.. code:: cpp
+
+  void pw_boot_Entry() {  // Boot entry point.
+    pw_boot_PreStaticMemoryInit();  // User-implemented function.
+    // Static memory initialization.
+    pw_boot_PreStaticConstructorInit();  // User-implemented function.
+    // C++ static constructors are invoked.
+    pw_boot_PreMainInit();  // User-implemented function.
+    main();  // User-implemented function.
+    pw_boot_PostMain();  // User-implemented function.
+    PW_UNREACHABLE;
+  }
+
+Setup
+=====
+
+User-Implemented Functions
+--------------------------
+This module expects all of these extern "C" functions to be defined outside this
+module:
+
+ - ``int main()``: This is where applications reside.
+ - ``void pw_boot_PreStaticMemoryInit()``: This function executes just before
+   static memory has been zerod and static data is intialized. This function
+   should set up any early initialization that should be done before static
+   memory is initialized, such as:
+
+   - Enabling the FPU or other coprocessors.
+   - Opting into extra restrictions such as disabling unaligned access to ensure
+     the restrictions are active during static RAM initialization.
+   - Initial CPU clock, flash, and memory configurations including potentially
+     enabling extra memory regions with .bss and .data sections, such as SDRAM
+     or backup powered SRAM.
+   - Fault handler initialization if required before static memory
+     initialization.
+
+   .. warning::
+     Code running in this hook is violating the C spec as static values are not
+     yet initialized, meaning they have not been loaded (.data) nor
+     zero-initialized (.bss).
+
+ - ``void pw_boot_PreStaticConstructorInit()``: This function executes just
+   before C++ static constructors are called. At this point, other static memory
+   has been zero or data initialized. This function should set up any early
+   initialization that should be done before C++ static constructors are run,
+   such as:
+
+   - Run time dependencies such as Malloc, and ergo sometimes the RTOS.
+   - Persistent memory that survives warm reboots.
+   - Enabling the MPU to catch nullptr dereferences during construction.
+   - Main stack watermarking.
+   - Further fault handling configuration necessary for your platform which
+     were not safe before pw_boot_PreStaticRamInit().
+   - Boot count and/or boot session UUID management.
+
+ - ``void pw_boot_PreMainInit()``: This function executes just before main, and
+   can be used for any device initialization that isn't application specific.
+   Depending on your platform, this might be turning on a UART, setting up
+   default clocks, etc.
+
+ - ``PW_NO_RETURN void pw_boot_PostMain()``: This function executes after main
+   has returned. This could be used for device specific teardown such as an
+   infinite loop, soft reset, or QEMU shutdown. In addition, if relevant for
+   your application, this would be the place to invoke the global static
+   destructors. This function must not return!
+
+
+If any of these functions are unimplemented, executables will encounter a link
+error.
+
+Required Configs
+----------------
+This module has a number of required configuration options that mold the linker
+script to fit to a wide variety of ARMv7-M SoCs. The ``pw_boot_armv7m_config``
+GN variable has a ``defines`` member that can be used to modify these linker
+script options. See the documentation section on configuration for information
+regarding which configuration options are required.
+
+Vector Table
+------------
+Targets using ``pw_boot_armv7m`` will need to provide an ARMv7-M interrupt
+vector table (ARMv7-M Architecture Reference Manual DDI 0403E.b section B1.5.2
+and B1.5.3). This is done by storing an array into the ``.vector_table``
+section, and properly configuring ``PW_BOOT_VECTOR_TABLE_*`` preprocessor
+defines to cover the address region your SoC expects the vector table to be
+located at (often the beginning of the flash region). If using a bootloader,
+ensure VTOR (Vector Table Offset Register) is configured to point to the vector
+table. Otherwise, refer to the hardware vendor's documentation to determine
+where the vector table should be located such that it resides where VTOR is
+initialized to by default.
+
+Example vector table:
+
+.. code-block:: cpp
+
+  typedef void (*InterruptHandler)();
+
+  PW_KEEP_IN_SECTION(".vector_table")
+  const InterruptHandler vector_table[] = {
+      // The starting location of the stack pointer.
+      // This address is NOT an interrupt handler/function pointer, it is simply
+      // the address that the main stack pointer should be initialized to. The
+      // value is reinterpret casted because it needs to be in the vector table.
+      [0] = reinterpret_cast<InterruptHandler>(&pw_boot_stack_high_addr),
+
+      // Reset handler, dictates how to handle reset interrupt. This is the
+      // address that the Program Counter (PC) is initialized to at boot.
+      [1] = pw_boot_Entry,
+
+      // NMI handler.
+      [2] = DefaultFaultHandler,
+      // HardFault handler.
+      [3] = DefaultFaultHandler,
+      ...
+  };
+
+Usage
+=====
+
+Publicly exported symbols
+-------------------------
+The linker script provided by this module exports a number of symbols that
+may be used to retrieve the locations of specific memory regions at runtime.
+These symbols are declared as ``uint8_t`` variables. The variables themselves
+do not contain the addresses, they only reside at the memory location they
+reference. To retrieve the memory locations, simply take the reference of the
+symbol (e.g. ``&pw_boot_vector_table_addr``).
+
+``pw_boot_heap_[low/high]_addr``: Beginning and end of the memory range of the heap.
+These addresses may be identical, indicating a heap with a size of zero bytes.
+
+``pw_boot_stack_[low/high]_addr``: Beginning and end of the memory range of the main
+stack. This might not be the only stack in the system.
+
+``pw_boot_vector_table_addr``: Beginning of the ARMv7-M interrupt vector table.
+
+Configuration
+=============
+These configuration options can be controlled by appending list items to
+``pw_boot_armv7m_LINK_CONFIG_DEFINES`` as part of a Pigweed target
+configuration.
+
+``PW_BOOT_HEAP_SIZE`` (required):
+How much memory (in bytes) to reserve for the heap. This can be zero.
+
+``PW_BOOT_MIN_STACK_SIZE`` (required):
+The minimum size reserved for the main stack. If statically allocated memory
+begins to cut into the minimum, a link error will be emitted.
+
+``PW_BOOT_FLASH_BEGIN`` (required):
+The start address of the MCU's flash region. This region must NOT include the
+vector table. (i.e. if the VECTOR_TABLE is in flash, the flash region
+should begin *after* the vtable)
+
+``PW_BOOT_FLASH_SIZE`` (required):
+Size of the flash region in bytes.
+
+``PW_BOOT_RAM_BEGIN`` (required):
+The start address of the MCU's RAM region.
+
+``PW_BOOT_RAM_SIZE`` (required):
+Size of the RAM region in bytes.
+
+``PW_BOOT_VECTOR_TABLE_BEGIN`` (required):
+Address the target MCU expects the link-time vector table to be located at. This
+is typically the beginning of the flash region. While the vector table may be
+changed later in the boot process, a minimal vector table MUST be present for
+the MCU to operate as expected.
+
+``PW_BOOT_VECTOR_TABLE_SIZE`` (required):
+Number of bytes to reserve for the ARMv7-M vector table.
+
+Dependencies
+============
+  * ``pw_preprocessor`` module
diff --git a/pw_boot_armv7m/public/pw_boot_armv7m/boot.h b/pw_boot_armv7m/public/pw_boot_armv7m/boot.h
new file mode 100644
index 0000000..a7abd47
--- /dev/null
+++ b/pw_boot_armv7m/public/pw_boot_armv7m/boot.h
@@ -0,0 +1,128 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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
+
+// This module is similar to a traditional assembly startup file paired with a
+// linker script. It turns out that everything typically done in ARMv7-M
+// assembly startup can be done straight from C code. This makes startup code
+// easier to maintain, modify, and read.
+//
+// Core initialization is comprised of two primary parts:
+//
+// 1. Load boot information from ARMv7-M Vector Table: The ARMv7-M vector table
+//    (See ARMv7-M Architecture Reference Manual DDI 0403E.b section B1.5)
+//    dictates the starting Program Counter (PC) and Stack Pointer (SP) when the
+//    SoC powers on. The vector table also contains a number of other vectors to
+//    handle different exceptions. This module does not provide a vector table,
+//    but it does account for it in the linker script.
+//
+// 2. Initialize static memory: When execution begins due to SoC power-on (or
+//    the device is reset), static memory regions must be initialized to ensure
+//    they contains the expected values when code begins to run. The SoC doesn't
+//    inherently have a notion of how to do this, so before ANYTHING else the
+//    memory must be initialized. This is done at the beginning of
+//    pw_boot_Entry().
+//
+//
+// The simple flow is as follows:
+//   Power on -> PC and SP set (from vector_table by SoC) -> pw_boot_Entry()
+//
+// In pw_boot_Entry():
+//   Initialize memory -> pw_PreMainInit() -> main()
+
+#include <stdint.h>
+
+#include "pw_preprocessor/compiler.h"
+#include "pw_preprocessor/util.h"
+
+PW_EXTERN_C_START
+
+// The following extern symbols are provided by the linker script, and their
+// values are accessible via the reference of the symbol.
+//
+// Example:
+//   if (stack_pointer < &pw_boot_stack_low_addr) {
+//     PW_LOG_ERROR("Main stack overflowed!")
+//   }
+
+// pw_boot_stack_[low/high]_addr indicate the range of the main stack. Note that
+// this might not be the only stack in the system.
+//
+// The main stack pointer (sp_main) should be initialized to
+// pw_boot_stack_high_addr. This can be done by inserting the address into index
+// 0 of the ARMv7-M vector table. (See ARMv7-M Architecture Reference Manual DDI
+// 0403E.b section B1.5.3)
+extern uint8_t pw_boot_stack_low_addr;
+extern uint8_t pw_boot_stack_high_addr;
+
+// pw_boot_heap_[low/high]_addr indicate the address range reserved for the
+// heap.
+extern uint8_t pw_boot_heap_low_addr;
+extern uint8_t pw_boot_heap_high_addr;
+
+// The address that denotes the beginning of the .vector_table section. This
+// can be used to set VTOR (vector table offset register) by the bootloader.
+extern uint8_t pw_boot_vector_table_addr;
+
+// Forward declaration of main. Pigweed applications are expected to implement
+// this function. An implementation of main() is NOT provided by this module.
+int main(void);
+
+// Reset handler or boot entry point.
+//
+// For this module to work as expected, index 1 of the ARMv7-M vector table
+// (which usually points to Reset_Handler) must be set to point to this
+// function. This function is implemented by pw_boot_armv7m, and does early
+// memory initialization.
+PW_NO_RETURN void pw_boot_Entry(void);
+
+// pw_boot hook: Before static memory is initialized (user supplied)
+//
+// This is a hook function that users of pw_boot must supply. It is called
+// immediately upon entry to pw_boot_Entry() and before zero initialization of
+// RAM (.bss) and loading values into static memory (commonly labeled as the
+// .data section in an ELF file).
+// WARNING: Be EXTREMELY careful when in the context of this function as it
+// violates the C spec in several ways as .bss has not yet been zero-initialized
+// and static values have not yet been loaded into memory. This function is NOT
+// implemented by pw_boot_armv7m.
+void pw_boot_PreStaticMemoryInit(void);
+
+// pw_boot hook: Before C++ static constructors are invoked (user supplied).
+//
+// This is a hook function that users of pw_boot must supply. It is called just
+// after zero initialization of RAM and loading values into static memory
+// (commonly labeled as the .data section in an ELF file). Per the naming, this
+// function is called just before C++ static constructors are invoked. It is
+// safe to run C code, but NOT safe to call out to any C++ code. This function
+// is NOT implemented by pw_boot_armv7m.
+void pw_boot_PreStaticConstructorInit(void);
+
+// pw_boot hook: Before main is invoked (user supplied).
+//
+// This is a hook function that users of pw_boot must supply. It is called by
+// pw_boot_Entry() after memory initialization but before main. This allows
+// targets to have pre-main initialization of the device and seamlessly swap out
+// the main() implementation. This function is NOT implemented by
+// pw_boot_armv7m.
+void pw_boot_PreMainInit(void);
+
+// pw_boot hook: After main returned (user supplied).
+//
+// This is a hook function that users of pw_boot must supply. It is called by
+// pw_boot_Entry() after main() has returned. This function must not return!
+// This function is NOT implemented by pw_boot_armv7m.
+PW_NO_RETURN void pw_boot_PostMain(void);
+
+PW_EXTERN_C_END
diff --git a/pw_boot_cortex_m/BUILD.bazel b/pw_boot_cortex_m/BUILD.bazel
deleted file mode 100644
index 362b0f0..0000000
--- a/pw_boot_cortex_m/BUILD.bazel
+++ /dev/null
@@ -1,63 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_boot_cortex_m",
-    srcs = [
-        "core_init.c",
-        "public/pw_boot_cortex_m/boot.h",
-    ],
-    includes = ["public"],
-    target_compatible_with = select({
-        "@platforms//cpu:armv7-m": [],
-        "@platforms//cpu:armv7e-m": [],
-        "@platforms//cpu:armv7e-mf": [],
-        "@platforms//cpu:armv8-m": [],
-        "//conditions:default": ["@platforms//:incompatible"],
-    }),
-    deps = [
-        "//pw_boot:facade",
-        "//pw_preprocessor",
-        "//pw_preprocessor:cortex_m",
-    ],
-)
-
-# The following targets are deprecated, depend on ":pw_boot_cortex_m" instead.
-pw_cc_library(
-    name = "armv7m",
-    target_compatible_with = select({
-        "@platforms//cpu:armv7-m": [],
-        "@platforms//cpu:armv7e-m": [],
-        "@platforms//cpu:armv7e-mf": [],
-        "//conditions:default": ["@platforms//:incompatible"],
-    }),
-    deps = [":pw_boot_cortex_m"],
-)
-
-pw_cc_library(
-    name = "armv8m",
-    target_compatible_with = select({
-        "@platforms//cpu:armv8-m": [],
-        "//conditions:default": ["@platforms//:incompatible"],
-    }),
-    deps = [":pw_boot_cortex_m"],
-)
diff --git a/pw_boot_cortex_m/BUILD.gn b/pw_boot_cortex_m/BUILD.gn
deleted file mode 100644
index d28da4f..0000000
--- a/pw_boot_cortex_m/BUILD.gn
+++ /dev/null
@@ -1,75 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_boot/backend.gni")
-import("$dir_pw_build/linker_script.gni")
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_docgen/docs.gni")
-
-declare_args() {
-  # This list should contain the necessary defines for setting pw_boot linker
-  # script memory regions.
-  pw_boot_cortex_m_LINK_CONFIG_DEFINES = []
-
-  # The pw_linker_script that should be used for the target.
-  pw_boot_cortex_m_LINKER_SCRIPT = ":cortex_m_linker_script"
-}
-
-if (pw_boot_BACKEND != "$dir_pw_boot_cortex_m" &&
-    pw_boot_BACKEND != "$dir_pw_boot_cortex_m:armv7m" &&
-    pw_boot_BACKEND != "$dir_pw_boot_cortex_m:armv8m") {
-  group("pw_boot_cortex_m") {
-  }
-  group("armv7m") {
-  }
-  group("armv8m") {
-  }
-} else {
-  config("default_config") {
-    include_dirs = [ "public" ]
-  }
-
-  pw_linker_script("cortex_m_linker_script") {
-    # pw_boot_cortex_m_LINK_CONFIG_DEFINES is a list of defines provided by the
-    # target.
-    defines = pw_boot_cortex_m_LINK_CONFIG_DEFINES
-    linker_script = "basic_cortex_m.ld"
-  }
-
-  pw_source_set("pw_boot_cortex_m") {
-    public_configs = [ ":default_config" ]
-    public = [ "public/pw_boot_cortex_m/boot.h" ]
-    public_deps = [ "$dir_pw_preprocessor" ]
-    deps = [
-      "$dir_pw_boot:facade",
-      "$dir_pw_preprocessor:arch",
-      pw_boot_cortex_m_LINKER_SCRIPT,
-    ]
-    sources = [ "core_init.c" ]
-  }
-
-  # These targets are deprecated, use ":pw_boot_cortex_m" directly.
-  group("armv7m") {
-    public_deps = [ ":pw_boot_cortex_m" ]
-  }
-  group("armv8m") {
-    public_deps = [ ":pw_boot_cortex_m" ]
-  }
-}
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
diff --git a/pw_boot_cortex_m/OWNERS b/pw_boot_cortex_m/OWNERS
deleted file mode 100644
index 103db5c..0000000
--- a/pw_boot_cortex_m/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-amontanez@google.com
-ewout@google.com
-keybuk@google.com
diff --git a/pw_boot_cortex_m/basic_cortex_m.ld b/pw_boot_cortex_m/basic_cortex_m.ld
deleted file mode 100644
index a14a204..0000000
--- a/pw_boot_cortex_m/basic_cortex_m.ld
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * Copyright 2020 The Pigweed Authors
- *
- * 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
- *
- *     https://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.
- */
-
-/* This relatively simplified linker script will work with many ARMv7-M and
- * ARMv8-M cores that have on-board memory-mapped RAM and FLASH. For more
- * complex projects and devices, it's possible this linker script will not be
- * sufficient as-is.
- *
- * This linker script is likely not suitable for a project with a bootloader.
- */
-
-/* Provide useful error messages when required configurations are not set. */
-#ifndef PW_BOOT_VECTOR_TABLE_BEGIN
-#error "PW_BOOT_VECTOR_TABLE_BEGIN is not defined, and is required to use pw_boot_cortex_m"
-#endif  // PW_BOOT_VECTOR_TABLE_BEGIN
-
-#ifndef PW_BOOT_VECTOR_TABLE_SIZE
-#error "PW_BOOT_VECTOR_TABLE_SIZE is not defined, and is required to use pw_boot_cortex_m"
-#endif  // PW_BOOT_VECTOR_TABLE_SIZE
-
-#ifndef PW_BOOT_FLASH_BEGIN
-#error "PW_BOOT_FLASH_BEGIN is not defined, and is required to use pw_boot_cortex_m"
-#endif  // PW_BOOT_FLASH_BEGIN
-
-#ifndef PW_BOOT_FLASH_SIZE
-#error "PW_BOOT_FLASH_SIZE is not defined, and is required to use pw_boot_cortex_m"
-#endif  // PW_BOOT_FLASH_SIZE
-
-#ifndef PW_BOOT_RAM_BEGIN
-#error "PW_BOOT_RAM_BEGIN is not defined, and is required to use pw_boot_cortex_m"
-#endif  // PW_BOOT_RAM_BEGIN
-
-#ifndef PW_BOOT_RAM_SIZE
-#error "PW_BOOT_RAM_SIZE is not defined, and is required to use pw_boot_cortex_m"
-#endif  // PW_BOOT_RAM_SIZE
-
-#ifndef PW_BOOT_HEAP_SIZE
-#error "PW_BOOT_HEAP_SIZE is not defined, and is required to use pw_boot_cortex_m"
-#endif  // PW_BOOT_HEAP_SIZE
-
-#ifndef PW_BOOT_MIN_STACK_SIZE
-#error "PW_BOOT_MIN_STACK_SIZE is not defined, and is required to use pw_boot_cortex_m"
-#endif  // PW_BOOT_MIN_STACK_SIZE
-
-
-/* Note: This technically doesn't set the firmware's entry point. Setting the
- *       firmware entry point is done by setting vector_table[1]
- *       (Reset_Handler). However, this DOES tell the compiler how to optimize
- *       when --gc-sections is enabled.
- */
-ENTRY(pw_boot_Entry)
-
-MEMORY
-{
-  /* TODO(pwbug/57): Make it possible for projects to freely customize
-   * memory regions.
-   */
-
-  /* Vector Table (typically in flash) */
-  VECTOR_TABLE(rx) : \
-    ORIGIN = PW_BOOT_VECTOR_TABLE_BEGIN, \
-    LENGTH = PW_BOOT_VECTOR_TABLE_SIZE
-  /* Internal Flash */
-  FLASH(rx) : \
-    ORIGIN = PW_BOOT_FLASH_BEGIN, \
-    LENGTH = PW_BOOT_FLASH_SIZE
-  /* Internal SRAM */
-  RAM(rwx) : \
-    ORIGIN = PW_BOOT_RAM_BEGIN, \
-    LENGTH = PW_BOOT_RAM_SIZE
-}
-
-SECTIONS
-{
-  /* This is the link-time vector table. If used, the VTOR (Vector Table Offset
-   * Register) MUST point to this memory location in order to be used. This can
-   * be done by ensuring this section exists at the default location of the VTOR
-   * so it's used on reset, or by explicitly setting the VTOR in a bootloader
-   * manually to point to &pw_boot_vector_table_addr before interrupts are enabled.
-   */
-  .vector_table : ALIGN(512)
-  {
-    pw_boot_vector_table_addr = .;
-    KEEP(*(.vector_table))
-  } >VECTOR_TABLE
-
-  /* Main executable code. */
-  .code : ALIGN(8)
-  {
-    . = ALIGN(8);
-    /* Application code. */
-    *(.text)
-    *(.text*)
-    KEEP(*(.init))
-    KEEP(*(.fini))
-
-    . = ALIGN(8);
-    /* Constants.*/
-    *(.rodata)
-    *(.rodata*)
-
-    /* .preinit_array, .init_array, .fini_array are used by libc.
-     * Each section is a list of function pointers that are called pre-main and
-     * post-exit for object initialization and tear-down.
-     * Since the region isn't explicitly referenced, specify KEEP to prevent
-     * link-time garbage collection. SORT is used for sections that have strict
-     * init/de-init ordering requirements. */
-    . = ALIGN(8);
-    PROVIDE_HIDDEN(__preinit_array_start = .);
-    KEEP(*(.preinit_array*))
-    PROVIDE_HIDDEN(__preinit_array_end = .);
-
-    PROVIDE_HIDDEN(__init_array_start = .);
-    KEEP(*(SORT(.init_array.*)))
-    KEEP(*(.init_array*))
-    PROVIDE_HIDDEN(__init_array_end = .);
-
-    PROVIDE_HIDDEN(__fini_array_start = .);
-    KEEP(*(SORT(.fini_array.*)))
-    KEEP(*(.fini_array*))
-    PROVIDE_HIDDEN(__fini_array_end = .);
-  } >FLASH
-
-  /* Used by unwind-arm/ */
-  .ARM : ALIGN(8) {
-    __exidx_start = .;
-    *(.ARM.exidx*)
-    __exidx_end = .;
-  } >FLASH
-
-  /* Explicitly initialized global and static data. (.data)*/
-  .static_init_ram : ALIGN(8)
-  {
-    *(.data)
-    *(.data*)
-    . = ALIGN(8);
-  } >RAM AT> FLASH
-
-  /* Zero initialized global/static data. (.bss)
-   * This section is zero initialized in pw_boot_Entry(). */
-  .zero_init_ram : ALIGN(8)
-  {
-    *(.bss)
-    *(.bss*)
-    *(COMMON)
-    . = ALIGN(8);
-  } >RAM
-
-  .heap : ALIGN(8)
-  {
-    pw_boot_heap_low_addr = .;
-    . = . + PW_BOOT_HEAP_SIZE;
-    . = ALIGN(8);
-    pw_boot_heap_high_addr = .;
-  } >RAM
-
-  /* Link-time check for stack overlaps. */
-  .stack (NOLOAD) : ALIGN(8)
-  {
-    /* Set the address that the main stack pointer should be initialized to. */
-    pw_boot_stack_low_addr = .;
-    HIDDEN(_stack_size = ORIGIN(RAM) + LENGTH(RAM) - .);
-    /* Align the stack to a lower address to ensure it isn't out of range. */
-    HIDDEN(_stack_high = (. + _stack_size) & ~0x7);
-    ASSERT(_stack_high - . >= PW_BOOT_MIN_STACK_SIZE,
-           "Error: Not enough RAM for desired minimum stack size.");
-    . = _stack_high;
-    pw_boot_stack_high_addr = .;
-  } >RAM
-
-  /* Discard unwind info. */
-  .ARM.extab 0x0 (INFO) :
-  {
-    KEEP(*(.ARM.extab*))
-  }
-
-  /*
-   * Do not declare any output sections after this comment. This area is
-   * reserved only for declaring unused sections of memory. These sections are
-   * used by pw_bloat.bloaty_config to create the utilization data source for
-   * bloaty.
-   */
-  .VECTOR_TABLE.unused_space (NOLOAD) : ALIGN(8)
-  {
-    . = ABSOLUTE(ORIGIN(VECTOR_TABLE) + LENGTH(VECTOR_TABLE));
-  } >VECTOR_TABLE
-
-  .FLASH.unused_space (NOLOAD) : ALIGN(8)
-  {
-    . = ABSOLUTE(ORIGIN(FLASH) + LENGTH(FLASH));
-  } >FLASH
-
-  .RAM.unused_space (NOLOAD) : ALIGN(8)
-  {
-    . = ABSOLUTE(ORIGIN(RAM) + LENGTH(RAM));
-  } >RAM
-}
-
-/* Symbols used by core_init.c: */
-/* Start of .static_init_ram in FLASH. */
-_pw_static_init_flash_start = LOADADDR(.static_init_ram);
-
-/* Region of .static_init_ram in RAM. */
-_pw_static_init_ram_start = ADDR(.static_init_ram);
-_pw_static_init_ram_end = _pw_static_init_ram_start + SIZEOF(.static_init_ram);
-
-/* Region of .zero_init_ram. */
-_pw_zero_init_ram_start = ADDR(.zero_init_ram);
-_pw_zero_init_ram_end = _pw_zero_init_ram_start + SIZEOF(.zero_init_ram);
-
-/* arm-none-eabi expects `end` symbol to point to start of heap for sbrk. */
-PROVIDE(end = _pw_zero_init_ram_end);
-
-/* These symbols are used by pw_bloat.bloaty_config to create the memoryregions
- * data source for bloaty in this format (where the optional _N defaults to 0):
- * pw_bloat_config_memory_region_NAME_{start,end}{_N,} */
-pw_bloat_config_memory_region_VECTOR_TABLE_start = ORIGIN(VECTOR_TABLE);
-pw_bloat_config_memory_region_VECTOR_TABLE_end =
-    ORIGIN(VECTOR_TABLE) + LENGTH(VECTOR_TABLE);
-pw_bloat_config_memory_region_FLASH_start = ORIGIN(FLASH);
-pw_bloat_config_memory_region_FLASH_end = ORIGIN(FLASH) + LENGTH(FLASH);
-pw_bloat_config_memory_region_RAM_start = ORIGIN(RAM);
-pw_bloat_config_memory_region_RAM_end = ORIGIN(RAM) + LENGTH(RAM);
diff --git a/pw_boot_cortex_m/core_init.c b/pw_boot_cortex_m/core_init.c
deleted file mode 100644
index cbb8af3..0000000
--- a/pw_boot_cortex_m/core_init.c
+++ /dev/null
@@ -1,173 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-//                               !!!WARNING!!!
-//
-// Some of the code in this file is run without static initialization expected
-// by C/C++. Any accesses to statically initialized objects/variables before
-// memory is initialized will result in undefined values and violates the C
-// specification. Only code run after memory initialization is complete will be
-// compliant and truly safe to run. In general, make early initialization code
-// run AFTER memory initialization has completed unless it is ABSOLUTELY
-// NECESSARY to modify the way memory is initialized.
-//
-// This file is similar to a traditional assembly startup file. It turns out
-// that everything typically done in ARMv7-M assembly startup can be done
-// straight from C code. This makes startup code easier to maintain, modify,
-// and read.
-//
-// When execution begins due to SoC power-on (or the device is reset), three
-// key things must happen to properly enter C++ execution context:
-//   1. Static variables must be loaded from flash to RAM.
-//   2. Zero-initialized variables must be zero-initialized.
-//   3. Statically allocated objects must have their constructors run.
-// The SoC doesn't inherently have a notion of how to do this, so this is
-// handled in StaticInit();
-//
-// Following this, execution is handed over to pw_PreMainInit() to facilitate
-// platform, project, or application pre-main initialization. When
-// pw_PreMainInit() returns, main() is executed.
-//
-// The simple flow is as follows:
-//   1. Power on
-//   2. PC and SP set (from vector_table by SoC, or by bootloader)
-//   3. pw_boot_Entry()
-//     3.1. pw_boot_PreStaticMemoryInit();
-//     3.2. Static-init memory (.data, .bss)
-//     3.3. pw_boot_PreStaticConstructorInit();
-//     3.4. Static C++ constructors
-//     3.5. pw_boot_PreMainInit()
-//     3.6. main()
-//     3.7. pw_boot_PostMain()
-
-#include <stdbool.h>
-#include <stdint.h>
-#include <string.h>
-
-#include "pw_boot/boot.h"
-#include "pw_boot_cortex_m/boot.h"
-#include "pw_preprocessor/arch.h"
-#include "pw_preprocessor/compiler.h"
-
-#if !_PW_ARCH_ARM_CORTEX_M
-#error You can only build this for ARM Cortex-M architectures. If you are \
-       trying to do this and are still seeing this error, see \
-       pw_preprocessor/arch.h
-#endif  // !_PW_ARCH_ARM_CORTEX_M
-
-#if !_PW_ARCH_ARM_V6M && !_PW_ARCH_ARM_V7M && !_PW_ARCH_ARM_V7EM && \
-    !_PW_ARCH_ARM_V8M_MAINLINE && !_PW_ARCH_ARM_V8_1M_MAINLINE
-#error "Your selected Cortex-M arch is not yet supported by this module."
-#endif
-
-// Extern symbols provided by linker script.
-// These symbols tell us where various memory sections start and end.
-extern uint8_t _pw_static_init_ram_start;
-extern uint8_t _pw_static_init_ram_end;
-extern uint8_t _pw_static_init_flash_start;
-extern uint8_t _pw_zero_init_ram_start;
-extern uint8_t _pw_zero_init_ram_end;
-
-// Functions called as part of firmware initialization.
-void __libc_init_array(void);
-
-// WARNING: Be EXTREMELY careful when running code before this function
-// completes. The context before this function violates the C spec
-// (Section 6.7.8, paragraph 10 for example, which requires uninitialized static
-// values to be zero-initialized).
-void StaticMemoryInit(void) {
-  // Static-init RAM (load static values into ram, .data section init).
-  memcpy(&_pw_static_init_ram_start,
-         &_pw_static_init_flash_start,
-         &_pw_static_init_ram_end - &_pw_static_init_ram_start);
-
-  // Zero-init RAM (.bss section init).
-  memset(&_pw_zero_init_ram_start,
-         0,
-         &_pw_zero_init_ram_end - &_pw_zero_init_ram_start);
-}
-
-// WARNING: This code is run immediately upon boot, and performs initialization
-// of RAM. Note that code running before this function finishes memory
-// initialization will violate the C spec (Section 6.7.8, paragraph 10 for
-// example, which requires uninitialized static values to be zero-initialized).
-// Be EXTREMELY careful when running code before this function finishes RAM
-// initialization.
-//
-// This function runs immediately at boot because it is at index 1 of the
-// interrupt vector table.
-void pw_boot_Entry() {
-  // Disable interrupts.
-  //
-  // Until pw_boot_PreStaticMemoryInit() has completed, depending on the
-  // bootloader (or lack thereof), there is no guarantee that the vector
-  // table has been correctly set up, so it's not safe to run interrupts
-  // until after this function returns.
-  //
-  // Until StaticMemoryInit() has completed, interrupt handlers cannot use
-  // either statically initialized RAM or zero initialized RAM. Since most
-  // interrupt handlers need one or the other to change system state, it's
-  // not safe to run handlers until after this function returns.
-  asm volatile("cpsid i");
-
-#if _PW_ARCH_ARM_V8M_MAINLINE || _PW_ARCH_ARM_V8_1M_MAINLINE
-  // Configure MSP and MSPLIM.
-  asm volatile(
-      "msr msp, %0    \n"
-      "msr msplim, %1 \n"
-      // clang-format off
-      : /*output=*/
-      : /*input=*/ "r"(&pw_boot_stack_high_addr), "r"(&pw_boot_stack_low_addr)
-      : /*clobbers=*/
-      // clang-format on
-  );
-#endif  // _PW_ARCH_ARM_V8M_MAINLINE || _PW_ARCH_ARM_V8_1M_MAINLINE
-
-  // Run any init that must be done before static init of RAM which preps the
-  // .data (static values not yet loaded into ram) and .bss sections (not yet
-  // zero-initialized).
-  pw_boot_PreStaticMemoryInit();
-
-  // Note that code running before this function finishes memory
-  // initialization will violate the C spec (Section 6.7.8, paragraph 10 for
-  // example, which requires uninitialized static values to be
-  // zero-initialized). Be EXTREMELY careful when running code before this
-  // function finishes static memory initialization.
-  StaticMemoryInit();
-
-  // Reenable interrupts.
-  //
-  // Care is still required since C++ static constructors have not yet been
-  // initialized, however it's a lot less likely that an interrupt handler
-  // (which are small and focused) will have an issue there.
-  asm volatile("cpsie i");
-
-  // Run any init that must be done before C++ static constructors.
-  pw_boot_PreStaticConstructorInit();
-
-  // Call static constructors.
-  __libc_init_array();
-
-  // This function is not provided by pw_boot_cortex_m, a platform layer,
-  // project, or application is expected to implement it.
-  pw_boot_PreMainInit();
-
-  // Run main.
-  main();
-
-  // In case main() returns, invoke this hook.
-  pw_boot_PostMain();
-
-  PW_UNREACHABLE;
-}
diff --git a/pw_boot_cortex_m/docs.rst b/pw_boot_cortex_m/docs.rst
deleted file mode 100644
index 73ba80d..0000000
--- a/pw_boot_cortex_m/docs.rst
+++ /dev/null
@@ -1,223 +0,0 @@
-.. _module-pw_boot_cortex_m:
-
-----------------
-pw_boot_cortex_m
-----------------
-
-The ARM Cortex-M boot module provides a linker script and some early
-initialization of static memory regions and C++ constructors. This is enough to
-get many ARMv7-M and ARMv8-M cores booted and ready to run C++ code.
-
-This module is currently designed to support a very minimal device memory layout
-configuration:
-
- - One contiguous region for RAM.
- - One contiguous region for flash.
- - Static, in-flash vector table at the default location expected by the SoC.
-
-Note that this module is not yet particularly suited for projects that utilize
-a bootloader, as it's relatively opinionated regarding where code is stored.
-
-.. warning::
-  This module is currently NOT stable! Depending on this module may cause
-  breakages as this module is updated.
-
-Sequence
-========
-
-The high level pw_boot_cortex_m boot sequence looks like the following
-pseudo-code invocation of the user-implemented functions:
-
-.. code:: cpp
-
-  void pw_boot_Entry() {  // Boot entry point.
-    // Interrupts disabled.
-    pw_boot_PreStaticMemoryInit();  // User-implemented function.
-    // Static memory initialization.
-    // Interrupts enabled.
-    pw_boot_PreStaticConstructorInit();  // User-implemented function.
-    // C++ static constructors are invoked.
-    pw_boot_PreMainInit();  // User-implemented function.
-    main();  // User-implemented function.
-    pw_boot_PostMain();  // User-implemented function.
-    PW_UNREACHABLE;
-  }
-
-Setup
-=====
-
-Processor Selection
--------------------
-Set the ``pw_boot_BACKEND`` variable to the appropriate target for the processor
-in use.
-
- - ``pw_boot_cortex_m:armv7m`` for ARMv7-M cores.
-
- - ``pw_boot_cortex_m:armv8m`` for ARMv8-M cores. This sets the MSPLIM register
-   so that the main stack pointer (MSP) cannot descend outside the bounds of the
-   main stack defined in the linker script. The MSP of the entry point is also
-   adjusted to be within the bounds.
-
-User-Implemented Functions
---------------------------
-This module expects all of these extern "C" functions to be defined outside this
-module:
-
- - ``int main()``: This is where applications reside.
- - ``void pw_boot_PreStaticMemoryInit()``: This function executes just before
-   static memory has been zeroed and static data is intialized. This function
-   should set up any early initialization that should be done before static
-   memory is initialized, such as:
-
-   - Setup the interrupt vector table and VTOR if required.
-   - Enabling the FPU or other coprocessors.
-   - Opting into extra restrictions such as disabling unaligned access to ensure
-     the restrictions are active during static RAM initialization.
-   - Initial CPU clock, flash, and memory configurations including potentially
-     enabling extra memory regions with .bss and .data sections, such as SDRAM
-     or backup powered SRAM.
-   - Fault handler initialization if required before static memory
-     initialization.
-
-   .. warning::
-     Code running in this hook is violating the C spec as static values are not
-     yet initialized, meaning they have not been loaded (.data) nor
-     zero-initialized (.bss).
-
-     Interrupts are disabled until after this function returns.
-
- - ``void pw_boot_PreStaticConstructorInit()``: This function executes just
-   before C++ static constructors are called. At this point, other static memory
-   has been zero or data initialized. This function should set up any early
-   initialization that should be done before C++ static constructors are run,
-   such as:
-
-   - Run time dependencies such as Malloc, and ergo sometimes the RTOS.
-   - Persistent memory that survives warm reboots.
-   - Enabling the MPU to catch nullptr dereferences during construction.
-   - Main stack watermarking.
-   - Further fault handling configuration necessary for your platform which
-     were not safe before pw_boot_PreStaticRamInit().
-   - Boot count and/or boot session UUID management.
-
- - ``void pw_boot_PreMainInit()``: This function executes just before main, and
-   can be used for any device initialization that isn't application specific.
-   Depending on your platform, this might be turning on a UART, setting up
-   default clocks, etc.
-
- - ``PW_NO_RETURN void pw_boot_PostMain()``: This function executes after main
-   has returned. This could be used for device specific teardown such as an
-   infinite loop, soft reset, or QEMU shutdown. In addition, if relevant for
-   your application, this would be the place to invoke the global static
-   destructors. This function must not return!
-
-
-If any of these functions are unimplemented, executables will encounter a link
-error.
-
-Required Configs
-----------------
-This module has a number of required configuration options that mold the linker
-script to fit to a wide variety of ARM Cortex-M SoCs.
-
-Vector Table
-------------
-Targets using ``pw_boot_cortex_m`` will need to provide an ARMv7-M interrupt
-vector table (ARMv7-M Architecture Reference Manual DDI 0403E.b section B1.5.2
-and B1.5.3). This is done by storing an array into the ``.vector_table``
-section, and properly configuring ``PW_BOOT_VECTOR_TABLE_*`` preprocessor
-defines to cover the address region your SoC expects the vector table to be
-located at (often the beginning of the flash region). If using a bootloader,
-ensure VTOR (Vector Table Offset Register) is configured to point to the vector
-table. Otherwise, refer to the hardware vendor's documentation to determine
-where the vector table should be located such that it resides where VTOR is
-initialized to by default.
-
-Example vector table:
-
-.. code-block:: cpp
-
-  typedef void (*InterruptHandler)();
-
-  PW_KEEP_IN_SECTION(".vector_table")
-  const InterruptHandler vector_table[] = {
-      // The starting location of the stack pointer.
-      // This address is NOT an interrupt handler/function pointer, it is simply
-      // the address that the main stack pointer should be initialized to. The
-      // value is reinterpret casted because it needs to be in the vector table.
-      [0] = reinterpret_cast<InterruptHandler>(&pw_boot_stack_high_addr),
-
-      // Reset handler, dictates how to handle reset interrupt. This is the
-      // address that the Program Counter (PC) is initialized to at boot.
-      [1] = pw_boot_Entry,
-
-      // NMI handler.
-      [2] = DefaultFaultHandler,
-      // HardFault handler.
-      [3] = DefaultFaultHandler,
-      ...
-  };
-
-Usage
-=====
-
-Publicly exported symbols
--------------------------
-The linker script provided by this module exports a number of symbols that
-may be used to retrieve the locations of specific memory regions at runtime.
-These symbols are declared as ``uint8_t`` variables. The variables themselves
-do not contain the addresses, they only reside at the memory location they
-reference. To retrieve the memory locations, simply take the reference of the
-symbol (e.g. ``&pw_boot_vector_table_addr``).
-
-``pw_boot_heap_[low/high]_addr``: Beginning and end of the memory range of the heap.
-These addresses may be identical, indicating a heap with a size of zero bytes.
-
-``pw_boot_stack_[low/high]_addr``: Beginning and end of the memory range of the main
-stack. This might not be the only stack in the system.
-
-``pw_boot_vector_table_addr``: Beginning of the ARMv7-M interrupt vector table.
-
-Configuration
-=============
-These configuration options can be controlled by appending list items to
-``pw_boot_cortex_m_LINK_CONFIG_DEFINES`` as part of a Pigweed target
-configuration.
-
-``PW_BOOT_HEAP_SIZE`` (required):
-How much memory (in bytes) to reserve for the heap. This can be zero.
-
-``PW_BOOT_MIN_STACK_SIZE`` (required):
-The minimum size reserved for the main stack. If statically allocated memory
-begins to cut into the minimum, a link error will be emitted.
-
-``PW_BOOT_FLASH_BEGIN`` (required):
-The start address of the MCU's flash region. This region must NOT include the
-vector table. (i.e. if the VECTOR_TABLE is in flash, the flash region
-should begin *after* the vtable)
-
-``PW_BOOT_FLASH_SIZE`` (required):
-Size of the flash region in bytes.
-
-``PW_BOOT_RAM_BEGIN`` (required):
-The start address of the MCU's RAM region.
-
-``PW_BOOT_RAM_SIZE`` (required):
-Size of the RAM region in bytes.
-
-``PW_BOOT_VECTOR_TABLE_BEGIN`` (required):
-Address the target MCU expects the link-time vector table to be located at. This
-is typically the beginning of the flash region. While the vector table may be
-changed later in the boot process, a minimal vector table MUST be present for
-the MCU to operate as expected.
-
-``PW_BOOT_VECTOR_TABLE_SIZE`` (required):
-Number of bytes to reserve for the ARMv7-M vector table.
-
-Alternatively the linker script can be replaced by setting
-``pw_boot_cortex_m_LINKER_SCRIPT`` to a valid ``pw_linker_script`` target
-as part of a Pigweed target configuration.
-
-Dependencies
-============
-  * ``pw_preprocessor`` module
diff --git a/pw_boot_cortex_m/public/pw_boot_cortex_m/boot.h b/pw_boot_cortex_m/public/pw_boot_cortex_m/boot.h
deleted file mode 100644
index 3876eeb..0000000
--- a/pw_boot_cortex_m/public/pw_boot_cortex_m/boot.h
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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
-
-// This module is similar to a traditional assembly startup file paired with a
-// linker script. It turns out that everything typically done in ARMv7-M
-// assembly startup can be done straight from C code. This makes startup code
-// easier to maintain, modify, and read.
-//
-// Core initialization is comprised of two primary parts:
-//
-// 1. Load boot information from ARMv7-M Vector Table: The ARMv7-M vector table
-//    (See ARMv7-M Architecture Reference Manual DDI 0403E.b section B1.5)
-//    dictates the starting Program Counter (PC) and Stack Pointer (SP) when the
-//    SoC powers on. The vector table also contains a number of other vectors to
-//    handle different exceptions. This module does not provide a vector table,
-//    but it does account for it in the linker script.
-//
-// 2. Initialize static memory: When execution begins due to SoC power-on (or
-//    the device is reset), static memory regions must be initialized to ensure
-//    they contains the expected values when code begins to run. The SoC doesn't
-//    inherently have a notion of how to do this, so before ANYTHING else the
-//    memory must be initialized. This is done at the beginning of
-//    pw_boot_Entry().
-//
-//
-// The simple flow is as follows:
-//   Power on -> PC and SP set (from vector_table by SoC) -> pw_boot_Entry()
-//
-// In pw_boot_Entry():
-//   Initialize memory -> pw_PreMainInit() -> main()
-
-#include <stdint.h>
-
-#include "pw_preprocessor/compiler.h"
-#include "pw_preprocessor/util.h"
-
-PW_EXTERN_C_START
-
-// The following extern symbols are provided by the linker script, and their
-// values are accessible via the reference of the symbol.
-//
-// Example:
-//   if (stack_pointer < &pw_boot_stack_low_addr) {
-//     PW_LOG_ERROR("Main stack overflowed!")
-//   }
-
-// pw_boot_stack_[low/high]_addr indicate the range of the main stack. Note that
-// this might not be the only stack in the system.
-//
-// The main stack pointer (sp_main) should be initialized to
-// pw_boot_stack_high_addr. This can be done by inserting the address into index
-// 0 of the ARMv7-M vector table. (See ARMv7-M Architecture Reference Manual DDI
-// 0403E.b section B1.5.3)
-extern uint8_t pw_boot_stack_low_addr;
-extern uint8_t pw_boot_stack_high_addr;
-
-// pw_boot_heap_[low/high]_addr indicate the address range reserved for the
-// heap.
-extern uint8_t pw_boot_heap_low_addr;
-extern uint8_t pw_boot_heap_high_addr;
-
-// The address that denotes the beginning of the .vector_table section. This
-// can be used to set VTOR (vector table offset register) by the bootloader.
-extern uint8_t pw_boot_vector_table_addr;
-
-PW_EXTERN_C_END
diff --git a/pw_build/BUILD b/pw_build/BUILD
new file mode 100644
index 0000000..b98df6a
--- /dev/null
+++ b/pw_build/BUILD
@@ -0,0 +1,24 @@
+# Copyright 2019 The Pigweed Authors
+#
+# 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
+#
+#     https://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(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+config_setting(
+    name = "kythe",
+    values = {
+        "define": "kythe_corpus=pigweed.googlesource.com/pigweed/pigweed",
+    },
+)
diff --git a/pw_build/BUILD.bazel b/pw_build/BUILD.bazel
deleted file mode 100644
index 2761227..0000000
--- a/pw_build/BUILD.bazel
+++ /dev/null
@@ -1,24 +0,0 @@
-# Copyright 2019 The Pigweed Authors
-#
-# 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
-#
-#     https://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(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-config_setting(
-    name = "kythe",
-    values = {
-        "define": "kythe_corpus=pigweed.googlesource.com/pigweed/pigweed",
-    },
-)
diff --git a/pw_build/BUILD.gn b/pw_build/BUILD.gn
index 1a74c2b..706b8d6 100644
--- a/pw_build/BUILD.gn
+++ b/pw_build/BUILD.gn
@@ -15,9 +15,7 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_build/python.gni")
-import("$dir_pw_build/relative_source_file_names.gni")
 import("$dir_pw_docgen/docs.gni")
-import("target_types.gni")
 
 # IMPORTANT: The compilation flags in this file must be kept in sync with
 #            the CMake flags pw_build/CMakeLists.txt.
@@ -70,14 +68,6 @@
     "-fdata-sections",
   ]
   cflags_cc = [ "-fno-rtti" ]
-
-  if (current_os == "mac" || current_os == "ios") {
-    # Delete unreferenced sections. Helpful with -ffunction-sections.
-    ldflags = [ "-Wl,-dead_strip" ]
-  } else {
-    # Delete unreferenced sections. Helpful with -ffunction-sections.
-    ldflags = [ "-Wl,--gc-sections" ]
-  }
 }
 
 config("strict_warnings") {
@@ -114,6 +104,10 @@
   cflags_c = [ "-Wstrict-prototypes" ]
 }
 
+config("cpp11") {
+  cflags_cc = [ "-std=c++11" ]
+}
+
 config("cpp14") {
   cflags_cc = [ "-std=c++14" ]
 }
@@ -127,50 +121,6 @@
   ]
 }
 
-# Removes system-dependent prefixes from macros like __FILE__ and debug symbols.
-config("relative_paths") {
-  _transformations = [
-    # Remap absolute paths to the build directory to "out", in case absolute
-    # paths to files in the build directory are created.
-    #
-    # Clang and GCC apply these flags in opposite order. The build directory is
-    # often nested under //. To ensure that both compilers removed it before
-    # removing the absolute path to //, apply the option both first and last.
-    rebase_path(root_build_dir) + "=out",
-
-    # Remove absolute paths to the repo root.
-    rebase_path("//") + "=",
-
-    # Remove relative paths from the build directory to the source tree.
-    rebase_path("//", root_build_dir) + "=",
-
-    # Repeat option to remap absolute paths to the build directory.
-    rebase_path(root_build_dir) + "=out",
-  ]
-  cflags = []
-
-  foreach(transform, _transformations) {
-    cflags += [ "-ffile-prefix-map=" + transform ]
-  }
-
-  # Write the transformations to a well known path so that other utilities
-  # that need to present file names that match the compiler's __FILE__
-  # macro can apply the same transformation.
-  write_file(pw_build_RELATIVE_PATH_TRANSFORM_JSON, _transformations, "json")
-}
-
-# This group is linked into all pw_executable, pw_static_library, and
-# pw_shared_library targets. This makes it possible to ensure symbols are
-# defined without a dependency on them.
-#
-# pw_build_LINK_DEPS should only be used when necessary. For example,
-# pw_assert relies on pw_build_LINK_DEPS to avoid circular dependencies
-# in GN. In almost all other cases, build targets should explicitly depend on
-# the other targets they use.
-group("link_deps") {
-  deps = pw_build_LINK_DEPS
-}
-
 # This empty target is used as the default value for module configurations.
 # Projects may set pw_build_DEFAULT_MODULE_CONFIG to a different GN target that
 # overrides modules' configuration options via macro definitions or a header
@@ -178,18 +128,20 @@
 group("empty") {
 }
 
+pool("pip_pool") {
+  depth = 1
+}
+
+pool("copy_from_cipd_pool") {
+  depth = 1
+}
+
 # Requirements for the pw_python_package lint targets.
 pw_python_requirements("python_lint") {
   requirements = [
     "build",
-
-    # NOTE: mypy needs to stay in sync with mypy-protobuf
-    # Currently using mypy 0.910 and mypy-protobuf 2.9
-    "mypy==0.910",
-
-    # typeshed packages (required by mypy > 0.9)
-    "types-setuptools",
-    "pylint==2.9.3",
+    "mypy==0.800",
+    "pylint==2.6.0",
   ]
 }
 
@@ -199,3 +151,8 @@
     "python.rst",
   ]
 }
+
+# Pool to limit a single thread to download external Go packages at a time.
+pool("go_download_pool") {
+  depth = 1
+}
diff --git a/pw_build/CMakeLists.txt b/pw_build/CMakeLists.txt
index 07b44a2..602dd4a 100644
--- a/pw_build/CMakeLists.txt
+++ b/pw_build/CMakeLists.txt
@@ -29,9 +29,6 @@
     $<$<CXX_COMPILER_ID:Clang>:-fcolor-diagnostics>
     $<$<CXX_COMPILER_ID:GNU>:-fdiagnostics-color=always>
 )
-if(ZEPHYR_PIGWEED_MODULE_DIR)
-  target_link_libraries(pw_build INTERFACE zephyr_interface)
-endif()
 
 # Declare top-level targets for tests.
 add_custom_target(pw_tests.default)
@@ -51,34 +48,14 @@
     $<$<COMPILE_LANGUAGE:CXX>:-fno-rtti>
 )
 
-# Define the standard Pigweed compile options.
-#
-# The pw_build.warnings library is used by upstream Pigweed targets to add
-# compiler warnings to the build.
-#
-# Toolchains may override these warnings by setting pw_build_WARNINGS:
-#
-#   set(pw_build_WARNINGS my_warnings CACHE STRING "" FORCE)
-#
-set(pw_build_WARNINGS pw_build.strict_warnings
-    CACHE STRING "Warnings libraries to use for Pigweed upstream code")
-
-add_library(pw_build.warnings INTERFACE)
-target_link_libraries(pw_build.warnings INTERFACE ${pw_build_WARNINGS})
-
-# TODO(hepler): These Zephyr exceptions should be made by overriding
-#     pw_build_WARNINGS.
 add_library(pw_build.strict_warnings INTERFACE)
-if(NOT ZEPHYR_PIGWEED_MODULE_DIR)
-  # Only include these flags if we're not building with Zephyr.
-  set(strict_warnings_cond "-Wcast-qual" "-Wundef")
-endif()
 target_compile_options(pw_build.strict_warnings
   INTERFACE
     "-Wall"
     "-Wextra"
     "-Wimplicit-fallthrough"
-    ${strict_warnings_cond}
+    "-Wcast-qual"
+    "-Wundef"
     "-Wpointer-arith"
 
     # Make all warnings errors, except for the exemptions below.
@@ -90,14 +67,10 @@
 )
 
 add_library(pw_build.extra_strict_warnings INTERFACE)
-if(NOT ZEPHYR_PIGWEED_MODULE_DIR)
-  # Only include these flags if we're not building with Zephyr.
-  set(extra_strict_warnings_cond "-Wredundant-decls")
-endif()
 target_compile_options(pw_build.extra_strict_warnings
   INTERFACE
     "-Wshadow"
-    ${extra_strict_warnings_cond}
+    "-Wredundant-decls"
     $<$<COMPILE_LANGUAGE:C>:-Wstrict-prototypes>
 )
 
diff --git a/pw_build/OWNERS b/pw_build/OWNERS
deleted file mode 100644
index d96cbc6..0000000
--- a/pw_build/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-hepler@google.com
diff --git a/pw_build/bazel_internal/BUILD.bazel b/pw_build/bazel_internal/BUILD.bazel
deleted file mode 100644
index 8607c83..0000000
--- a/pw_build/bazel_internal/BUILD.bazel
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(":pigweed_internal.bzl", "pw_linker_script")
-
-pw_linker_script(
-    name = "linker_script_test",
-    defines = [
-        "PW_BOOT_FLASH_BEGIN=0x08000200",
-        "PW_BOOT_FLASH_SIZE=1024K",
-        "PW_BOOT_HEAP_SIZE=112K",
-        "PW_BOOT_MIN_STACK_SIZE=1K",
-        "PW_BOOT_RAM_BEGIN=0x20000000",
-        "PW_BOOT_RAM_SIZE=192K",
-        "PW_BOOT_VECTOR_TABLE_BEGIN=0x08000000",
-        "PW_BOOT_VECTOR_TABLE_SIZE=512",
-    ],
-    linker_script = "linker_script.ld",
-)
-
-# Use cc_binary to build the test to avoid duplicating the linker script in the
-# command line via implicit deps in pw_cc_binary.
-cc_binary(
-    name = "test_linker_script",
-    srcs = ["test.cc"],
-    additional_linker_inputs = [":linker_script_test"],
-    linkopts = ["-T $(location :linker_script_test)"],
-)
diff --git a/pw_build/bazel_internal/BUILD.gn b/pw_build/bazel_internal/BUILD.gn
deleted file mode 100644
index ef76ebd..0000000
--- a/pw_build/bazel_internal/BUILD.gn
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("$dir_pw_build/target_types.gni")
-
-# This is target is to keep the presubmit happy. But is not actually used.
-pw_source_set("tests") {
-  public_deps = [ dir_pw_preprocessor ]
-  sources = [ "test.cc" ]
-}
diff --git a/pw_build/bazel_internal/linker_script.ld b/pw_build/bazel_internal/linker_script.ld
deleted file mode 100644
index 7c3169e..0000000
--- a/pw_build/bazel_internal/linker_script.ld
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2022 The Pigweed Authors
- *
- * 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
- *
- *     https://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.
- */
-
-/* This linker script is for test purposes only and should not be used in a
- * production environment.
-*/
-#ifndef PW_BOOT_VECTOR_TABLE_BEGIN
-#error "PW_BOOT_VECTOR_TABLE_BEGIN is not defined, and is required to use pw_boot_cortex_m"
-#endif  // PW_BOOT_VECTOR_TABLE_BEGIN
-
-
- /* Note: This technically doesn't set the firmware's entry point. Setting the
- *       firmware entry point is done by setting vector_table[1]
- *       (Reset_Handler). However, this DOES tell the compiler how to optimize
- *       when --gc-sections is enabled.
- */
-ENTRY(pw_boot_Entry)
-
-MEMORY
-{
-  /* TODO(pwbug/57): Make it possible for projects to freely customize
-   * memory regions.
-   */
-
-  /* Vector Table (typically in flash) */
-  VECTOR_TABLE(rx) : \
-    ORIGIN = PW_BOOT_VECTOR_TABLE_BEGIN, \
-    LENGTH = PW_BOOT_VECTOR_TABLE_SIZE
-  /* Internal Flash */
-  FLASH(rx) : \
-    ORIGIN = PW_BOOT_FLASH_BEGIN, \
-    LENGTH = PW_BOOT_FLASH_SIZE
-  /* Internal SRAM */
-  RAM(rwx) : \
-    ORIGIN = PW_BOOT_RAM_BEGIN, \
-    LENGTH = PW_BOOT_RAM_SIZE
-}
-
diff --git a/pw_build/bazel_internal/pigweed_internal.bzl b/pw_build/bazel_internal/pigweed_internal.bzl
deleted file mode 100644
index 5a2bad1..0000000
--- a/pw_build/bazel_internal/pigweed_internal.bzl
+++ /dev/null
@@ -1,208 +0,0 @@
-#!/usr/bin/env python3
-
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-""" An internal set of tools for creating embedded CC targets. """
-
-load("@rules_cc//cc:action_names.bzl", "C_COMPILE_ACTION_NAME")
-load("@rules_cc//cc:toolchain_utils.bzl", "find_cpp_toolchain")
-
-DEBUGGING = [
-    "-g",
-]
-
-# Standard compiler flags to reduce output binary size.
-REDUCED_SIZE_COPTS = [
-    "-fno-common",
-    "-fno-exceptions",
-    "-ffunction-sections",
-    "-fdata-sections",
-]
-
-STRICT_WARNINGS_COPTS = [
-    "-Wall",
-    "-Wextra",
-    # Make all warnings errors, except for the exemptions below.
-    "-Werror",
-    "-Wno-error=cpp",  # preprocessor #warning statement
-    "-Wno-error=deprecated-declarations",  # [[deprecated]] attribute
-]
-
-CPP17_COPTS = [
-    "-std=c++17",
-    "-fno-rtti",
-    "-Wnon-virtual-dtor",
-    # Allow uses of the register keyword, which may appear in C headers.
-    "-Wno-register",
-]
-
-DISABLE_PENDING_WORKAROUND_COPTS = [
-    "-Wno-private-header",
-]
-
-PW_DEFAULT_COPTS = (
-    DEBUGGING +
-    REDUCED_SIZE_COPTS +
-    STRICT_WARNINGS_COPTS +
-    DISABLE_PENDING_WORKAROUND_COPTS
-)
-
-KYTHE_COPTS = [
-    "-Wno-unknown-warning-option",
-]
-
-PW_DEFAULT_LINKOPTS = []
-
-def add_defaults(kwargs):
-    """Adds default arguments suitable for both C and C++ code to kwargs.
-
-    Args:
-        kwargs: cc_* arguments to be modified.
-    """
-
-    copts = PW_DEFAULT_COPTS + kwargs.get("copts", [])
-    kwargs["copts"] = select({
-        "//pw_build:kythe": copts + KYTHE_COPTS,
-        "//conditions:default": copts,
-    })
-    kwargs["linkopts"] = kwargs.get("linkopts", []) + PW_DEFAULT_LINKOPTS
-
-    # Set linkstatic to avoid building .so files.
-    kwargs["linkstatic"] = True
-
-    kwargs.setdefault("features", [])
-
-    # Crosstool--adding this line to features disables header modules, which
-    # don't work with -fno-rtti. Note: this is not a command-line argument,
-    # it's "minus use_header_modules".
-    kwargs["features"].append("-use_header_modules")
-
-def default_cc_and_c_kwargs(kwargs):
-    """Splits kwargs into C and C++ arguments adding defaults.
-
-    Args:
-        kwargs: cc_* arguments to be modified.
-
-    Returns:
-        A tuple of (cc_cxx_kwargs cc_c_kwargs)
-    """
-    add_defaults(kwargs)
-    kwargs.setdefault("srcs", [])
-
-    cc = dict(kwargs.items())
-    cc["srcs"] = [src for src in kwargs["srcs"] if not src.endswith(".c")]
-    cc["copts"] = cc["copts"] + CPP17_COPTS
-
-    c_srcs = [src for src in kwargs["srcs"] if src.endswith(".c")]
-
-    if c_srcs:
-        c = dict(kwargs.items())
-        c["name"] += "_c"
-        c["srcs"] = c_srcs + [src for src in kwargs["srcs"] if src.endswith(".h")]
-
-        cc["deps"] = cc.get("deps", []) + [":" + c["name"]]
-        return cc, c
-
-    return cc, None
-
-def add_cc_and_c_targets(target, kwargs):  # buildifier: disable=unnamed-macro
-    """Splits target into C and C++ targets adding defaults.
-
-    Args:
-        target: cc_* target to be split.
-        kwargs: cc_* arguments to be modified.
-    """
-    cc_kwargs, c_kwargs = default_cc_and_c_kwargs(kwargs)
-
-    if c_kwargs:
-        native.cc_library(**c_kwargs)
-
-    target(**cc_kwargs)
-
-def has_pw_assert_dep(deps):
-    """Checks if the given deps contain a pw_assert dependency
-
-    Args:
-        deps: List of dependencies
-
-    Returns:
-        True if the list contains a pw_assert dependency.
-    """
-    pw_assert_targets = ["//pw_assert", "//pw_assert:pw_assert"]
-    pw_assert_targets.append(["@pigweed" + t for t in pw_assert_targets])
-    for dep in deps:
-        if dep in pw_assert_targets:
-            return True
-    return False
-
-def _preprocess_linker_script_impl(ctx):
-    cc_toolchain = find_cpp_toolchain(ctx)
-    output_script = ctx.actions.declare_file(ctx.label.name + ".ld")
-    feature_configuration = cc_common.configure_features(
-        ctx = ctx,
-        cc_toolchain = cc_toolchain,
-        requested_features = ctx.features,
-        unsupported_features = ctx.disabled_features,
-    )
-    cxx_compiler_path = cc_common.get_tool_for_action(
-        feature_configuration = feature_configuration,
-        action_name = C_COMPILE_ACTION_NAME,
-    )
-    c_compile_variables = cc_common.create_compile_variables(
-        feature_configuration = feature_configuration,
-        cc_toolchain = cc_toolchain,
-        user_compile_flags = ctx.fragments.cpp.copts + ctx.fragments.cpp.conlyopts,
-    )
-    env = cc_common.get_environment_variables(
-        feature_configuration = feature_configuration,
-        action_name = C_COMPILE_ACTION_NAME,
-        variables = c_compile_variables,
-    )
-    ctx.actions.run(
-        outputs = [output_script],
-        inputs = depset(
-            [ctx.file.linker_script],
-            transitive = [cc_toolchain.all_files],
-        ),
-        executable = cxx_compiler_path,
-        arguments = [
-            "-E",
-            "-P",
-            "-xc",
-            ctx.file.linker_script.short_path,
-            "-o",
-            output_script.path,
-        ] + [
-            "-D" + d
-            for d in ctx.attr.defines
-        ] + ctx.attr.copts,
-        env = env,
-    )
-    return [DefaultInfo(files = depset([output_script]))]
-
-pw_linker_script = rule(
-    _preprocess_linker_script_impl,
-    attrs = {
-        "copts": attr.string_list(doc = "C compile options."),
-        "defines": attr.string_list(doc = "C preprocessor defines."),
-        "linker_script": attr.label(
-            mandatory = True,
-            allow_single_file = True,
-            doc = "Linker script to preprocess.",
-        ),
-        "_cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")),
-    },
-    toolchains = ["@bazel_tools//tools/cpp:toolchain_type"],
-    fragments = ["cpp"],
-)
diff --git a/pw_build/bazel_internal/test.cc b/pw_build/bazel_internal/test.cc
deleted file mode 100644
index 6498ae9..0000000
--- a/pw_build/bazel_internal/test.cc
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// This file is intentionally very simple and is used only to test that the
-// linker script generator works as expected.
-int main() { return 0; }
\ No newline at end of file
diff --git a/pw_build/cc_blob_library.gni b/pw_build/cc_blob_library.gni
deleted file mode 100644
index 0a8a9d0..0000000
--- a/pw_build/cc_blob_library.gni
+++ /dev/null
@@ -1,101 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/python_action.gni")
-import("$dir_pw_build/target_types.gni")
-
-# Turns binary blobs into a C++ source_set library of hard-coded byte arrays.
-#
-#   blobs           A list of scopes, where each scope corresponds to a binary
-#                   blob to be transformed from file to byte array. This is a
-#                   required field. Blob fields include:
-#
-#                   symbol_name [required]: The C++ symbol for the byte array.
-#
-#                   file_path [required]: The file path for the binary blob.
-#
-#                   linker_section [optional]: If present, places the byte array
-#                     in the specified linker section.
-#
-#   out_header      The header file to generate. Users will include this file
-#                   exactly as it is written here to reference the byte arrays.
-#
-#   namespace       An optional (but highly recommended!) C++ namespace to place
-#                   the generated blobs within.
-template("pw_cc_blob_library") {
-  assert(defined(invoker.blobs), "pw_cc_blob_library requires 'blobs'")
-  assert(defined(invoker.out_header),
-         "pw_cc_blob_library requires an 'out_header'")
-
-  _blobs = []
-  _blob_files = []
-  foreach(blob, invoker.blobs) {
-    assert(defined(blob.symbol_name), "Each 'blob' requires a 'symbol_name'")
-    assert(defined(blob.file_path), "Each 'blob' requires a 'file_path'")
-    blob.file_path = rebase_path(blob.file_path)
-    _blobs += [ blob ]
-    _blob_files += [ blob.file_path ]
-  }
-
-  _blob_json_file = "$target_gen_dir/$target_name.json"
-  write_file(_blob_json_file, _blobs, "json")
-
-  pw_python_action("$target_name._gen") {
-    forward_variables_from(invoker,
-                           [
-                             "deps",
-                             "public_deps",
-                           ])
-    module = "pw_build.generate_cc_blob_library"
-
-    _header = "${target_gen_dir}/public/" + invoker.out_header
-    _source =
-        "${target_gen_dir}/" + get_path_info(invoker.out_header, "name") + ".cc"
-    args = [
-      "--blob-file",
-      rebase_path(_blob_json_file, root_build_dir),
-      "--out-header",
-      rebase_path(_header),
-      "--out-source",
-      rebase_path(_source),
-    ]
-
-    if (defined(invoker.namespace)) {
-      args += [
-        "--namespace",
-        invoker.namespace,
-      ]
-    }
-
-    inputs = [ _blob_json_file ] + _blob_files
-    outputs = [
-      _header,
-      _source,
-    ]
-  }
-
-  config("$target_name._include_path") {
-    include_dirs = [ "${target_gen_dir}/public" ]
-    visibility = [ ":*" ]
-  }
-
-  pw_source_set(target_name) {
-    sources = get_target_outputs(":$target_name._gen")
-    public_configs = [ ":$target_name._include_path" ]
-    deps = [ ":$target_name._gen" ]
-    public_deps = [ "$dir_pw_preprocessor" ]
-  }
-}
diff --git a/pw_build/cc_executable.gni b/pw_build/cc_executable.gni
deleted file mode 100644
index 5165324..0000000
--- a/pw_build/cc_executable.gni
+++ /dev/null
@@ -1,124 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/cc_library.gni")
-
-# Note: In general, prefer to import target_types.gni rather than this file.
-# cc_executable.gni and cc_library.gni are both provided by target_types.gni.
-#
-# cc_library.gni is split out from cc_executable.gni because pw_executable
-# templates may need to create pw_source_set targets internally, and can't
-# import target_types.gni because it creates a circular import path.
-
-declare_args() {
-  # The name of the GN target type used to build Pigweed executables.
-  #
-  # If this is a custom template, the .gni file containing the template must
-  # be imported at the top of the target configuration file to make it globally
-  # available.
-  pw_build_EXECUTABLE_TARGET_TYPE = "executable"
-
-  # The path to the .gni file that defines pw_build_EXECUTABLE_TARGET_TYPE.
-  #
-  # If pw_build_EXECUTABLE_TARGET_TYPE is not the default of `executable`, this
-  # .gni file is imported to provide the template definition.
-  pw_build_EXECUTABLE_TARGET_TYPE_FILE = ""
-}
-
-if (pw_build_EXECUTABLE_TARGET_TYPE != "executable" &&
-    pw_build_EXECUTABLE_TARGET_TYPE_FILE != "") {
-  import(pw_build_EXECUTABLE_TARGET_TYPE_FILE)
-}
-
-_supported_toolchain_defaults = [
-  "configs",
-  "public_deps",
-]
-
-# Wrapper for Pigweed executable build targets which uses a globally-defined,
-# configurable target type.
-template("pw_executable") {
-  _pw_source_files = []
-
-  # Boilerplate for tracking target sources.  For more information see
-  # https://pigweed.dev/pw_build/#target-types
-  if (defined(invoker.sources)) {
-    foreach(path, invoker.sources) {
-      _pw_source_files += [ path ]
-    }
-  }
-  if (defined(invoker.public)) {
-    foreach(path, invoker.public) {
-      _pw_source_files += [ path ]
-    }
-  }
-
-  _executable_output_dir = "${target_out_dir}/bin"
-  if (defined(invoker.output_dir)) {
-    _executable_output_dir = invoker.output_dir
-  }
-
-  target(pw_build_EXECUTABLE_TARGET_TYPE, target_name) {
-    import("$dir_pw_build/defaults.gni")
-
-    forward_variables_from(invoker, "*", _supported_toolchain_defaults)
-
-    # Ensure that we don't overwrite metadata forwared from the invoker above.
-    if (defined(metadata)) {
-      metadata.pw_source_files = _pw_source_files
-    } else {
-      metadata = {
-        pw_source_files = _pw_source_files
-      }
-    }
-
-    if (!defined(configs)) {
-      configs = []
-    }
-    if (defined(pw_build_defaults.configs)) {
-      configs += pw_build_defaults.configs
-    }
-    if (defined(remove_configs)) {
-      if (remove_configs != [] && remove_configs[0] == "*") {
-        configs = []
-      } else {
-        configs += remove_configs  # Add configs in case they aren't already
-        configs -= remove_configs  # present, then remove them.
-      }
-    }
-    if (defined(invoker.configs)) {
-      configs += invoker.configs
-    }
-
-    public_deps = [ "$dir_pw_build:link_deps" ]
-    if (defined(pw_build_defaults.public_deps)) {
-      public_deps += pw_build_defaults.public_deps
-    }
-    if (defined(remove_public_deps)) {
-      if (remove_public_deps != [] && remove_public_deps[0] == "*") {
-        public_deps = []
-      } else {
-        public_deps += remove_public_deps
-        public_deps -= remove_public_deps
-      }
-    }
-    if (defined(invoker.public_deps)) {
-      public_deps += invoker.public_deps
-    }
-
-    output_dir = _executable_output_dir
-  }
-}
diff --git a/pw_build/cc_library.gni b/pw_build/cc_library.gni
deleted file mode 100644
index 5a25f9f..0000000
--- a/pw_build/cc_library.gni
+++ /dev/null
@@ -1,250 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-# Note: In general, prefer to import target_types.gni rather than this file.
-# cc_executable.gni and cc_library.gni are both provided by target_types.gni.
-#
-# cc_library.gni is split out from cc_executable.gni because pw_executable
-# templates may need to create pw_source_set targets internally, and can't
-# import target_types.gni because it creates a circular import path.
-
-declare_args() {
-  # Additional build targets to add as dependencies for pw_executable,
-  # pw_static_library, and pw_shared_library targets. The
-  # $dir_pw_build:link_deps target pulls in these libraries.
-  #
-  # pw_build_LINK_DEPS can be used to break circular dependencies for low-level
-  # libraries such as pw_assert.
-  pw_build_LINK_DEPS = []
-}
-
-# TODO(frolv): The code in all of the templates below is duplicated, with the
-# exception of the target type. This file could be auto-generated with Python.
-
-_supported_toolchain_defaults = [
-  "configs",
-  "public_deps",
-]
-
-template("pw_source_set") {
-  _pw_source_files = []
-
-  # Boilerplate for tracking target sources.  For more information see
-  # https://pigweed.dev/pw_build/#target-types
-  if (defined(invoker.sources)) {
-    foreach(path, invoker.sources) {
-      _pw_source_files += [ path ]
-    }
-  }
-  if (defined(invoker.public)) {
-    foreach(path, invoker.public) {
-      _pw_source_files += [ path ]
-    }
-  }
-
-  source_set(target_name) {
-    import("$dir_pw_build/defaults.gni")
-    forward_variables_from(invoker, "*", _supported_toolchain_defaults)
-
-    # Ensure that we don't overwrite metadata forwared from the invoker above.
-    if (defined(metadata)) {
-      metadata.pw_source_files = _pw_source_files
-    } else {
-      metadata = {
-        pw_source_files = _pw_source_files
-      }
-    }
-
-    if (!defined(configs)) {
-      configs = []
-    }
-    if (defined(pw_build_defaults.configs)) {
-      configs += pw_build_defaults.configs
-    }
-    if (defined(remove_configs)) {
-      if (remove_configs != [] && remove_configs[0] == "*") {
-        configs = []
-      } else {
-        configs += remove_configs  # Add configs in case they aren't already
-        configs -= remove_configs  # present, then remove them.
-      }
-    }
-    if (defined(invoker.configs)) {
-      configs += invoker.configs
-    }
-
-    if (defined(pw_build_defaults.public_deps)) {
-      public_deps = pw_build_defaults.public_deps
-    } else {
-      public_deps = []
-    }
-    if (defined(remove_public_deps)) {
-      if (remove_public_deps != [] && remove_public_deps[0] == "*") {
-        public_deps = []
-      } else {
-        public_deps += remove_public_deps
-        public_deps -= remove_public_deps
-      }
-    }
-    if (defined(invoker.public_deps)) {
-      public_deps += invoker.public_deps
-    }
-  }
-}
-
-template("pw_static_library") {
-  _pw_source_files = []
-
-  # Boilerplate for tracking target sources.  For more information see
-  # https://pigweed.dev/pw_build/#target-types
-  if (defined(invoker.sources)) {
-    foreach(path, invoker.sources) {
-      _pw_source_files += [ path ]
-    }
-  }
-  if (defined(invoker.public)) {
-    foreach(path, invoker.public) {
-      _pw_source_files += [ path ]
-    }
-  }
-
-  _library_output_dir = "${target_out_dir}/lib"
-  if (defined(invoker.output_dir)) {
-    _library_output_dir = invoker.output_dir
-  }
-
-  static_library(target_name) {
-    import("$dir_pw_build/defaults.gni")
-    forward_variables_from(invoker, "*", _supported_toolchain_defaults)
-
-    # Ensure that we don't overwrite metadata forwared from the invoker above.
-    if (defined(metadata)) {
-      metadata.pw_source_files = _pw_source_files
-    } else {
-      metadata = {
-        pw_source_files = _pw_source_files
-      }
-    }
-
-    if (!defined(configs)) {
-      configs = []
-    }
-    if (defined(pw_build_defaults.configs)) {
-      configs += pw_build_defaults.configs
-    }
-    if (defined(remove_configs)) {
-      if (remove_configs != [] && remove_configs[0] == "*") {
-        configs = []
-      } else {
-        configs += remove_configs  # Add configs in case they aren't already
-        configs -= remove_configs  # present, then remove them.
-      }
-    }
-    if (defined(invoker.configs)) {
-      configs += invoker.configs
-    }
-
-    public_deps = [ "$dir_pw_build:link_deps" ]
-    if (defined(pw_build_defaults.public_deps)) {
-      public_deps += pw_build_defaults.public_deps
-    }
-    if (defined(remove_public_deps)) {
-      if (remove_public_deps != [] && remove_public_deps[0] == "*") {
-        public_deps = []
-      } else {
-        public_deps += remove_public_deps
-        public_deps -= remove_public_deps
-      }
-    }
-    if (defined(invoker.public_deps)) {
-      public_deps += invoker.public_deps
-    }
-
-    output_dir = _library_output_dir
-  }
-}
-
-template("pw_shared_library") {
-  _pw_source_files = []
-
-  # Boilerplate for tracking target sources.  For more information see
-  # https://pigweed.dev/pw_build/#target-types
-  if (defined(invoker.sources)) {
-    foreach(path, invoker.sources) {
-      _pw_source_files += [ path ]
-    }
-  }
-  if (defined(invoker.public)) {
-    foreach(path, invoker.public) {
-      _pw_source_files += [ path ]
-    }
-  }
-
-  _library_output_dir = "${target_out_dir}/lib"
-  if (defined(invoker.output_dir)) {
-    _library_output_dir = invoker.output_dir
-  }
-
-  shared_library(target_name) {
-    import("$dir_pw_build/defaults.gni")
-    forward_variables_from(invoker, "*", _supported_toolchain_defaults)
-
-    # Ensure that we don't overwrite metadata forwared from the invoker above.
-    if (defined(metadata)) {
-      metadata.pw_source_files = _pw_source_files
-    } else {
-      metadata = {
-        pw_source_files = _pw_source_files
-      }
-    }
-
-    if (!defined(configs)) {
-      configs = []
-    }
-    if (defined(pw_build_defaults.configs)) {
-      configs += pw_build_defaults.configs
-    }
-    if (defined(remove_configs)) {
-      if (remove_configs != [] && remove_configs[0] == "*") {
-        configs = []
-      } else {
-        configs += remove_configs  # Add configs in case they aren't already
-        configs -= remove_configs  # present, then remove them.
-      }
-    }
-    if (defined(invoker.configs)) {
-      configs += invoker.configs
-    }
-
-    public_deps = [ "$dir_pw_build:link_deps" ]
-    if (defined(pw_build_defaults.public_deps)) {
-      public_deps += pw_build_defaults.public_deps
-    }
-    if (defined(remove_public_deps)) {
-      if (remove_public_deps != [] && remove_public_deps[0] == "*") {
-        public_deps = []
-      } else {
-        public_deps += remove_public_deps
-        public_deps -= remove_public_deps
-      }
-    }
-    if (defined(invoker.public_deps)) {
-      public_deps += invoker.public_deps
-    }
-
-    output_dir = _library_output_dir
-  }
-}
diff --git a/pw_build/constraints/board/BUILD b/pw_build/constraints/board/BUILD
new file mode 100644
index 0000000..1bf3ea2
--- /dev/null
+++ b/pw_build/constraints/board/BUILD
@@ -0,0 +1,23 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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(default_visibility = ["//visibility:public"])
+
+constraint_setting(
+    name = "board",
+)
+
+constraint_value(
+    name = "stm32f429i-disc1",
+    constraint_setting = ":board",
+)
diff --git a/pw_build/constraints/board/BUILD.bazel b/pw_build/constraints/board/BUILD.bazel
deleted file mode 100644
index 4696f87..0000000
--- a/pw_build/constraints/board/BUILD.bazel
+++ /dev/null
@@ -1,30 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-constraint_setting(
-    name = "board",
-)
-
-constraint_value(
-    name = "stm32f429i-disc1",
-    constraint_setting = ":board",
-)
-
-constraint_value(
-    name = "mimxrt595_evk",
-    constraint_setting = ":board",
-)
diff --git a/pw_build/constraints/chipset/BUILD b/pw_build/constraints/chipset/BUILD
new file mode 100644
index 0000000..1c98052
--- /dev/null
+++ b/pw_build/constraints/chipset/BUILD
@@ -0,0 +1,28 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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(default_visibility = ["//visibility:public"])
+
+constraint_setting(
+    name = "chipset",
+)
+
+constraint_value(
+    name = "stm32f429",
+    constraint_setting = ":chipset",
+)
+
+constraint_value(
+    name = "lm3s6965evb",
+    constraint_setting = ":chipset",
+)
diff --git a/pw_build/constraints/chipset/BUILD.bazel b/pw_build/constraints/chipset/BUILD.bazel
deleted file mode 100644
index 3a2a2ea..0000000
--- a/pw_build/constraints/chipset/BUILD.bazel
+++ /dev/null
@@ -1,30 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-constraint_setting(
-    name = "chipset",
-)
-
-constraint_value(
-    name = "stm32f429",
-    constraint_setting = ":chipset",
-)
-
-constraint_value(
-    name = "lm3s6965evb",
-    constraint_setting = ":chipset",
-)
diff --git a/pw_build/constraints/rtos/BUILD.bazel b/pw_build/constraints/rtos/BUILD.bazel
deleted file mode 100644
index 10eb73b..0000000
--- a/pw_build/constraints/rtos/BUILD.bazel
+++ /dev/null
@@ -1,70 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-constraint_setting(
-    name = "rtos",
-    default_constraint_value = "none",
-)
-
-constraint_value(
-    name = "none",
-    constraint_setting = ":rtos",
-)
-
-config_setting(
-    name = "none_setting",
-    flag_values = {
-        "@pigweed_config//:target_os": ":none",
-    },
-)
-
-constraint_value(
-    name = "embos",
-    constraint_setting = ":rtos",
-)
-
-config_setting(
-    name = "embos_setting",
-    flag_values = {
-        "@pigweed_config//:target_os": ":embos",
-    },
-)
-
-constraint_value(
-    name = "freertos",
-    constraint_setting = ":rtos",
-)
-
-config_setting(
-    name = "freertos_setting",
-    flag_values = {
-        "@pigweed_config//:target_os": ":freertos",
-    },
-)
-
-constraint_value(
-    name = "threadx",
-    constraint_setting = ":rtos",
-)
-
-config_setting(
-    name = "threadx_setting",
-    flag_values = {
-        "@pigweed_config//:target_os": ":threadx",
-    },
-)
diff --git a/pw_build/copy_from_cipd.gni b/pw_build/copy_from_cipd.gni
index aff8afa..bb9502b 100644
--- a/pw_build/copy_from_cipd.gni
+++ b/pw_build/copy_from_cipd.gni
@@ -69,14 +69,12 @@
 
     # Parallel calls might both be invoking CIPD on the same directory which
     # might work but is redundant, so serialize calls.
-    pool = "$dir_pw_build/pool:copy_from_cipd($default_toolchain)"
+    pool = "$dir_pw_build:copy_from_cipd_pool"
 
     # TODO(pwbug/335): This should somehow track the actual .a for changes as
     # well.
     inputs = [ invoker.manifest ]
     outputs = [ _out_file ]
-
-    python_deps = [ "$dir_pw_build/py" ]
   }
 
   pw_source_set(target_name) {
diff --git a/pw_build/defaults.gni b/pw_build/defaults.gni
index dc258fa..6d341e7 100644
--- a/pw_build/defaults.gni
+++ b/pw_build/defaults.gni
@@ -39,10 +39,7 @@
     "$dir_pw_build:reduced_size",
     "$dir_pw_build:strict_warnings",
     "$dir_pw_build:cpp17",
-    "$dir_pw_build:relative_paths",
   ]
-
-  # TODO(pwbug/602): Remove this once all uses explicitly depend on polyfills.
   public_deps += [ "$dir_pw_polyfill:overrides" ]
 }
 
diff --git a/pw_build/docs.rst b/pw_build/docs.rst
index 90e499c..6135755 100644
--- a/pw_build/docs.rst
+++ b/pw_build/docs.rst
@@ -49,11 +49,10 @@
 
 Pigweed defines wrappers around the four basic GN binary types ``source_set``,
 ``executable``, ``static_library``, and ``shared_library``. These wrappers apply
-default arguments to each target, as defined in ``pw_build/default.gni``.
-Arguments may be added or removed globally using the ``default_configs``,
-``default_public_deps``, and ``remove_default_configs`` build args.
-Additionally, arguments may be removed on a per-target basis with the
-``remove_configs`` and ``remove_public_deps`` variables.
+default arguments to each target as specified in the ``default_configs`` and
+``default_public_deps`` build args. Additionally, they allow defaults to be
+removed on a per-target basis using ``remove_configs`` and
+``remove_public_deps`` variables, respectively.
 
 The ``pw_executable`` template provides additional functionality around building
 complete binaries. As Pigweed is a collection of libraries, it does not know how
@@ -63,28 +62,6 @@
 ``pw_executable_config.target_type``, specifying the name of the executable
 template for a project.
 
-In some uncommon cases, a project's ``pw_executable`` template definition may
-need to stamp out some ``pw_source_set``s. Since a pw_executable template can't
-import ``$dir_pw_build/target_types.gni`` due to circular imports, it should
-import ``$dir_pw_build/cc_library.gni`` instead.
-
-Additionally ``pw_executable``, ``pw_source_set``, ``pw_static_library``, and
-``pw_shared_library`` track source files via the ``pw_source_files`` field the
-target's
-`GN metadata <https://gn.googlesource.com/gn/+/main/docs/reference.md#metadata_collection>`_.
-This list can be writen to a file at build time using ``generated_file``.  The
-primary use case for this is to generate a token database containing all the
-source files.  This allows PW_ASSERT to emit filename tokens even though it
-can't add them to the elf file because of the resons described at
-:ref:`module-pw_assert-assert-api`.
-
-.. note::
-  ``pw_source_files``, if not rebased will default to outputing module relative
-  paths from a ``generated_file`` target.  This is likely not useful.  Adding
-  a ``rebase`` argument to ``generated_file`` such as
-  ``rebase = root_build_dir`` will result in usable paths.  For an example,
-  see `//pw_tokenizer/database.gni`'s `pw_tokenizer_filename_database` template.
-
 .. tip::
 
   Prefer to use ``pw_executable`` over plain ``executable`` targets to allow
@@ -95,21 +72,6 @@
 All of the ``pw_*`` target type overrides accept any arguments, as they simply
 forward them through to the underlying target.
 
-.. _module-pw_build-link-deps:
-
-Link-only deps
---------------
-It may be necessary to specify additional link-time dependencies that may not be
-explicitly depended on elsewhere in the build. One example of this is a
-``pw_assert`` backend, which may need to leave out dependencies to avoid
-circular dependencies. Its dependencies need to be linked for executables and
-libraries, even if they aren't pulled in elsewhere.
-
-The ``pw_build_LINK_DEPS`` build arg is a list of dependencies to add to all
-``pw_executable``, ``pw_static_library``, and ``pw_shared_library`` targets.
-This should only be used as a last resort when dependencies cannot be properly
-expressed in the build.
-
 Python packages
 ---------------
 GN templates for :ref:`Python build automation <docs-python-build>` are
@@ -120,96 +82,6 @@
 
   python
 
-
-.. _module-pw_build-cc_blob_library:
-
-pw_cc_blob_library
-------------------
-The ``pw_cc_blob_library`` template is useful for embedding binary data into a
-program. The template takes in a mapping of symbol names to file paths, and
-generates a set of C++ source and header files that embed the contents of the
-passed-in files as arrays.
-
-**Arguments**
-
-* ``blobs``: A list of GN scopes, where each scope corresponds to a binary blob
-  to be transformed from file to byte array. This is a required field. Blob
-  fields include:
-
-  * ``symbol_name``: The C++ symbol for the byte array.
-  * ``file_path``: The file path for the binary blob.
-  * ``linker_section``: If present, places the byte array in the specified
-    linker section.
-
-* ``out_header``: The header file to generate. Users will include this file
-  exactly as it is written here to reference the byte arrays.
-* ``namespace``: An optional (but highly recommended!) C++ namespace to place
-  the generated blobs within.
-
-Example
-^^^^^^^
-
-**BUILD.gn**
-
-.. code-block::
-
-  pw_cc_blob_library("foo_bar_blobs") {
-    blobs: [
-      {
-        symbol_name: "kFooBlob"
-        file_path: "${target_out_dir}/stuff/bin/foo.bin"
-      },
-      {
-        symbol_name: "kBarBlob"
-        file_path: "//stuff/bin/bar.bin"
-        linker_section: ".bar_section"
-      },
-    ]
-    out_header: "my/stuff/foo_bar_blobs.h"
-    namespace: "my::stuff"
-    deps = [ ":generate_foo_bin" ]
-  }
-
-.. note:: If the binary blobs are generated as part of the build, be sure to
-          list them as deps to the pw_cc_blob_library target.
-
-**Generated Header**
-
-.. code-block::
-
-  #pragma once
-
-  #include <array>
-  #include <cstddef>
-
-  namespace my::stuff {
-
-  extern const std::array<std::byte, 100> kFooBlob;
-
-  extern const std::array<std::byte, 50> kBarBlob;
-
-  }  // namespace my::stuff
-
-**Generated Source**
-
-.. code-block::
-
-  #include "my/stuff/foo_bar_blobs.h"
-
-  #include <array>
-  #include <cstddef>
-
-  #include "pw_preprocessor/compiler.h"
-
-  namespace my::stuff {
-
-  const std::array<std::byte, 100> kFooBlob = { ... };
-
-  PW_PLACE_IN_SECTION(".bar_section")
-  const std::array<std::byte, 50> kBarBlob = { ... };
-
-  }  // namespace my::stuff
-
 .. _module-pw_build-facade:
 
 pw_facade
@@ -238,14 +110,6 @@
     public = [ "public/pw_foo/foo.h" ]
   }
 
-Low-level facades like ``pw_assert`` cannot express all of their dependencies
-due to the potential for dependency cycles. Facades with this issue may require
-backends to place their implementations in a separate build target to be listed
-in ``pw_build_LINK_DEPS`` (see :ref:`module-pw_build-link-deps`). The
-``require_link_deps`` variable in ``pw_facade`` asserts that all specified build
-targets are present in ``pw_build_LINK_DEPS`` if the facade's backend variable
-is set.
-
 .. _module-pw_build-python-action:
 
 pw_python_action
@@ -259,7 +123,7 @@
 any outputs. Sometimes scripts run in a build do not directly produce output
 files, but GN requires that all actions have an output. ``pw_python_action``
 solves this by accepting a boolean ``stamp`` argument which tells it to create a
-placeholder output file for the action.
+dummy output file for the action.
 
 **Arguments**
 
@@ -271,15 +135,14 @@
 * ``capture_output``: Optional boolean. If true, script output is hidden unless
   the script fails with an error. Defaults to true.
 * ``stamp``: Optional variable indicating whether to automatically create a
-  placeholder output file for the script. This allows running scripts without
+  dummy output file for the script. This allows running scripts without
   specifying ``outputs``. If ``stamp`` is true, a generic output file is
   used. If ``stamp`` is a file path, that file is used as a stamp file. Like any
   output file, ``stamp`` must be in the build directory. Defaults to false.
+* ``directory``: Optional path. Change to this directory before executing the
+  command. Paths in arguments may need to be adjusted.
 * ``environment``: Optional list of strings. Environment variables to set,
   passed as NAME=VALUE strings.
-* ``working_directory``: Optional file path. When provided the current working
-  directory will be set to this location before the Python module or script is
-  run.
 
 **Expressions**
 
@@ -322,7 +185,7 @@
 
   ``TARGET_FILE`` only resolves GN target labels to their outputs. To resolve
   paths generally, use the standard GN approach of applying the
-  ``rebase_path(path, root_build_dir)`` function. This function
+  ``rebase_path(path)`` function. With default arguments, ``rebase_path``
   converts the provided GN path or list of paths to be relative to the build
   directory, from which all build commands and scripts are executed.
 
@@ -380,68 +243,12 @@
     script = "py/postprocess_binary.py"
     args = [
       "--database",
-      rebase_path("my/database.csv", root_build_dir),
+      rebase_path("my/database.csv"),
       "--binary=<TARGET_FILE(//firmware/images:main)>",
     ]
     stamp = true
   }
 
-.. _module-pw_build-pw_exec:
-
-pw_exec
--------
-``pw_exec`` allows for execution of arbitrary programs. It is a wrapper around
-``pw_python_action`` but allows for specifying the program to execute.
-
-.. note:: Prefer to use ``pw_python_action`` instead of calling out to shell
-  scripts, as the python will be more portable. ``pw_exec`` should generally
-  only be used for interacting with legacy/existing scripts.
-
-**Arguments**
-
-* ``program``: The program to run. Can be a full path or just a name (in which
-  case $PATH is searched).
-* ``args``: Optional list of arguments to the program.
-* ``deps``: Dependencies for this target.
-* ``public_deps``: Public dependencies for this target. In addition to outputs
-  from this target, outputs generated by public dependencies can be used as
-  inputs from targets that depend on this one. This is not the case for private
-  deps.
-* ``inputs``: Optional list of build inputs to the program.
-* ``outputs``: Optional list of artifacts produced by the program's execution.
-* ``env``: Optional list of key-value pairs defining environment variables for
-  the program.
-* ``env_file``: Optional path to a file containing a list of newline-separated
-  key-value pairs defining environment variables for the program.
-* ``args_file``: Optional path to a file containing additional positional
-  arguments to the program. Each line of the file is appended to the
-  invocation. Useful for specifying arguments from GN metadata.
-* ``skip_empty_args``: If args_file is provided, boolean indicating whether to
-  skip running the program if the file is empty. Used to avoid running
-  commands which error when called without arguments.
-* ``capture_output``: If true, output from the program is hidden unless the
-  program exits with an error. Defaults to true.
-* ``working_directory``: The working directory to execute the subprocess with.
-  If not specified it will not be set and the subprocess will have whatever
-  the parent current working directory is.
-
-**Example**
-
-.. code-block::
-
-  import("$dir_pw_build/exec.gni")
-
-  pw_exec("hello_world") {
-    program = "/bin/sh"
-    args = [
-      "-c",
-      "echo hello \$WORLD",
-    ]
-    env = [
-      "WORLD=world",
-    ]
-  }
-
 pw_input_group
 --------------
 ``pw_input_group`` defines a group of input files which are not directly
@@ -561,129 +368,16 @@
 
   foo.zip
   ├── bar/
-  │   ├── file3.txt
-  │   └── some_dir/
-  │       ├── file4.txt
-  │       └── some_other_dir/
-  │           └── file5.txt
+  │   ├── file3.txt
+  │   └── some_dir/
+  │       ├── file4.txt
+  │       └── some_other_dir/
+  │           └── file5.txt
   ├── file1.txt
   └── renamed.txt
 
-.. _module-pw_build-relative-source-file-names:
-
-pw_relative_source_file_names
------------------------------
-This template recursively walks the listed dependencies and collects the names
-of all the headers and source files required by the targets, and then transforms
-them such that they reflect the ``__FILE__`` when pw_build's ``relative_paths``
-config is applied. This is primarily intended for side-band generation of
-pw_tokenizer tokens so file name tokens can be utilized in places where
-pw_tokenizer is unable to embed token information as part of C/C++ compilation.
-
-This template produces a JSON file containing an array of strings (file paths
-with ``-ffile-prefix-map``-like transformations applied) that can be used to
-:ref:`generate a token database <module-pw_tokenizer-database-creation>`.
-
-**Arguments**
-
-* ``deps``: A required list of targets to recursively extract file names from.
-* ``outputs``: A required array with a single element: the path to write the
-  final JSON file to.
-
-**Example**
-
-Let's say we have the following project structure:
-
-.. code-block::
-
-  project root
-  ├── foo/
-  │   ├── foo.h
-  │   └── foo.cc
-  ├── bar/
-  │   ├── bar.h
-  │   └── bar.cc
-  ├── unused/
-  │   ├── unused.h
-  │   └── unused.cc
-  └── main.cc
-
-And a BUILD.gn at the root:
-
-.. code-block::
-
-  pw_source_set("bar") {
-    public_configs = [ ":bar_headers" ]
-    public = [ "bar/bar.h" ]
-    sources = [ "bar/bar.cc" ]
-  }
-
-  pw_source_set("foo") {
-    public_configs = [ ":foo_headers" ]
-    public = [ "foo/foo.h" ]
-    sources = [ "foo/foo.cc" ]
-    deps = [ ":bar" ]
-  }
-
-
-  pw_source_set("unused") {
-    public_configs = [ ":unused_headers" ]
-    public = [ "unused/unused.h" ]
-    sources = [ "unused/unused.cc" ]
-    deps = [ ":bar" ]
-  }
-
-  pw_executable("main") {
-    sources = [ "main.cc" ]
-    deps = [ ":foo" ]
-  }
-
-  pw_relative_source_file_names("main_source_files") {
-    deps = [ ":main" ]
-    outputs = [ "$target_gen_dir/main_source_files.json" ]
-  }
-
-The json file written to `out/gen/main_source_files.json` will contain:
-
-.. code-block::
-
-  [
-    "bar/bar.cc",
-    "bar/bar.h",
-    "foo/foo.cc",
-    "foo/foo.h",
-    "main.cc"
-  ]
-
-Since ``unused`` isn't a transitive dependency of ``main``, its source files
-are not included. Similarly, even though ``bar`` is not a direct dependency of
-``main``, its source files *are* included because ``foo`` brings in ``bar`` as
-a transitive dependency.
-
-Note how the file paths in this example are relative to the project root rather
-than being absolute paths (e.g. ``/home/user/ralph/coding/my_proj/main.cc``).
-This is a result of transformations applied to strip absolute pathing prefixes,
-matching the behavior of pw_build's ``$dir_pw_build:relative_paths`` config.
-
-Build time errors: pw_error and pw_build_assert
------------------------------------------------
-In Pigweed's complex, multi-toolchain GN build it is not possible to build every
-target in every configuration. GN's ``assert`` statement is not ideal for
-enforcing the correct configuration because it may prevent the GN build files or
-targets from being referred to at all, even if they aren't used.
-
-The ``pw_error`` GN template results in an error if it is executed during the
-build. These error targets can exist in the build graph, but cannot be depended
-on without an error.
-
-``pw_build_assert`` evaluates to a ``pw_error`` if a condition fails or nothing
-(an empty group) if the condition passes. Targets can add a dependency on a
-``pw_build_assert`` to enforce a condition at build time.
-
-The templates for build time errors are defined in ``pw_build/error.gni``.
-
-CMake
-=====
+CMake / Ninja
+=============
 Pigweed's `CMake`_ support is provided primarily for projects that have an
 existing CMake build and wish to integrate Pigweed without switching to a new
 build system.
@@ -759,18 +453,6 @@
 * Temporarily override a backend by setting it interactively with ``ccmake`` or
   ``cmake-gui``.
 
-If the backend is set to a build target that does not exist, there will be an
-error message like the following:
-
-.. code-block::
-
-  CMake Error at pw_build/pigweed.cmake:244 (add_custom_target):
-  Error evaluating generator expression:
-
-    $<TARGET_PROPERTY:my_backend_that_does_not_exist,TYPE>
-
-  Target "my_backend_that_does_not_exist" not found.
-
 Toolchain setup
 ---------------
 In CMake, the toolchain is configured by setting CMake variables, as described
@@ -780,13 +462,6 @@
 For Pigweed embedded builds, set ``CMAKE_SYSTEM_NAME`` to the empty string
 (``""``).
 
-Toolchains may set the ``pw_build_WARNINGS`` variable to a list of ``INTERFACE``
-libraries with compilation options for Pigweed's upstream libraries. This
-defaults to a strict set of warnings. Projects may need to use less strict
-compilation warnings to compile backends exposed to Pigweed code (such as
-``pw_log``) that cannot compile with Pigweed's flags. If desired, Projects can
-access these warnings by depending on ``pw_build.warnings``.
-
 Third party libraries
 ---------------------
 The CMake build includes third-party libraries similarly to the GN build. A
@@ -849,37 +524,10 @@
 are wrapped with ``pw_cc_binary``, ``pw_cc_library``, and ``pw_cc_test``.
 These wrappers add parameters to calls to the compiler and linker.
 
-In addition to wrapping the built-in rules, Pigweed also provides a custom
-rule for handling linker scripts with Bazel. e.g.
-
-.. code-block:: python
-
-  pw_linker_script(
-    name = "some_linker_script",
-    linker_script = ":some_configurable_linker_script.ld",
-    defines = [
-        "PW_BOOT_FLASH_BEGIN=0x08000200",
-        "PW_BOOT_FLASH_SIZE=1024K",
-        "PW_BOOT_HEAP_SIZE=112K",
-        "PW_BOOT_MIN_STACK_SIZE=1K",
-        "PW_BOOT_RAM_BEGIN=0x20000000",
-        "PW_BOOT_RAM_SIZE=192K",
-        "PW_BOOT_VECTOR_TABLE_BEGIN=0x08000000",
-        "PW_BOOT_VECTOR_TABLE_SIZE=512",
-    ],
-  )
-
-  pw_cc_binary(
-    name = "some_binary",
-    srcs = ["some_source.c"],
-    additional_linker_inputs = [":some_linker_script"],
-    linkopts = ["-T $(location :some_linker_script)"],
-  )
-
 Currently Pigweed is making use of a set of
-`open source <https://github.com/silvergasp/bazel-embedded>`_ toolchains. The
-host builds are only supported on Linux/Mac based systems. Additionally the
-host builds are not entirely hermetic, and will make use of system
+[open source](https://github.com/silvergasp/bazel-embedded) toolchains. The host
+builds are only supported on Linux/Mac based systems. Additionally the host
+builds are not entirely hermetic, and will make use of system
 libraries and headers. This is close to the default configuration for Bazel,
 though slightly more hermetic. The host toolchain is based around clang-11 which
 has a system dependency on 'libtinfo.so.5' which is often included as part of
diff --git a/pw_build/error.gni b/pw_build/error.gni
index 33804c9..a7c0e69 100644
--- a/pw_build/error.gni
+++ b/pw_build/error.gni
@@ -18,10 +18,8 @@
 # or 'message_lines' must be specified, but not both.
 #
 # Args:
-#
 #   message: The message to print. Use \n for newlines.
 #   message_lines: List of lines to use for the message.
-#   visibility: GN visibility to apply to the underlying target.
 #
 template("pw_error") {
   assert(
@@ -43,53 +41,12 @@
       "--message",
       _message,
       "--root",
-      rebase_path("//", root_build_dir),
+      rebase_path("//"),
       "--out",
-      ".",
+      rebase_path(root_build_dir),
     ]
 
     # This output file is never created.
     outputs = [ "$target_gen_dir/$target_name.build_error" ]
-
-    forward_variables_from(invoker, [ "visibility" ])
-  }
-}
-
-# An assert that is evaluated at build time. The assertion is only checked if
-# this target is depended on by another target. If the assertion passes, nothing
-# happens. If it fails, the target prints an error message with pw_error.
-#
-# To enforce a pw_build_assert, targets add a dependency on a pw_build_assert.
-# Multiple targets may depend on the same pw_build_assert if the same assertion
-# applies.
-#
-# Args:
-#
-#   condition: The assertion to verify.
-#   message: The message to print. Use \n for newlines.
-#   message_lines: List of lines to use for the message.
-#   visibility: GN visibility to apply to the underlying target.
-#
-template("pw_build_assert") {
-  assert(defined(invoker.condition),
-         "pw_build_assert requires a boolean condition")
-  assert(defined(invoker.message) != defined(invoker.message_lines),
-         "pw_build_assert requires either 'message' or 'message_lines'")
-
-  _pw_error_variables = [
-    "message",
-    "message_lines",
-    "visibility",
-  ]
-
-  if (invoker.condition) {
-    not_needed(invoker, _pw_error_variables)
-    group(target_name) {
-      forward_variables_from(invoker, [ "visibility" ])
-    }
-  } else {
-    pw_error(target_name) {
-      forward_variables_from(invoker, _pw_error_variables)
-    }
   }
 }
diff --git a/pw_build/exec.gni b/pw_build/exec.gni
index 241c87b..d2b4e69 100644
--- a/pw_build/exec.gni
+++ b/pw_build/exec.gni
@@ -27,11 +27,6 @@
 #
 #  deps: Dependencies for this target.
 #
-#  public_deps: Public dependencies for this target. In addition to outputs from
-#    this target, outputs generated by public dependencies can be used as inputs
-#    from targets that depend on this one. This is not the case for private
-#    deps.
-#
 #  inputs: Optional list of build inputs to the program.
 #
 #  outputs: Optional list of artifacts produced by the program's execution.
@@ -53,10 +48,6 @@
 #  capture_output: If true, output from the program is hidden unless the program
 #    exits with an error. Defaults to true.
 #
-#  working_directory: The working directory to execute the subprocess with. If
-#    not specified it will not be set and the subprocess will have whatever the
-#    parent current working directory is.
-#
 # Example:
 #
 #   pw_exec("hello_world") {
@@ -81,14 +72,14 @@
   if (defined(invoker.env_file)) {
     _script_args += [
       "--env-file",
-      rebase_path(invoker.env_file, root_build_dir),
+      rebase_path(invoker.env_file),
     ]
   }
 
   if (defined(invoker.args_file)) {
     _script_args += [
       "--args-file",
-      rebase_path(invoker.args_file, root_build_dir),
+      rebase_path(invoker.args_file),
     ]
 
     if (defined(invoker.skip_empty_args) && invoker.skip_empty_args) {
@@ -107,16 +98,6 @@
 
   if (!defined(invoker.capture_output) || invoker.capture_output) {
     _script_args += [ "--capture-output" ]
-    _capture_output = true
-  } else {
-    _capture_output = false
-  }
-
-  if (defined(invoker.working_directory)) {
-    _script_args += [
-      "--working-directory",
-      invoker.working_directory,
-    ]
   }
 
   _script_args += [
@@ -130,14 +111,12 @@
   pw_python_action(target_name) {
     script = "$dir_pw_build/py/pw_build/exec.py"
     args = _script_args
-    capture_output = _capture_output
 
     forward_variables_from(invoker,
                            [
                              "deps",
                              "inputs",
                              "pool",
-                             "public_deps",
                            ])
 
     if (!defined(inputs)) {
diff --git a/pw_build/facade.gni b/pw_build/facade.gni
index 0b188a1..4cb422e 100644
--- a/pw_build/facade.gni
+++ b/pw_build/facade.gni
@@ -72,9 +72,6 @@
 #    include headers from the facade itself. If the facade doesn't expose any
 #    headers, it's basically the same as just depending directly on the build
 #    arg that `backend` is set to.
-#  require_link_deps: A list of build targets that must be included in
-#    pw_build_LINK_DEPS if this facade is used. This mechanism is used to
-#    avoid circular dependencies in low-level facades like pw_assert.
 #
 template("pw_facade") {
   assert(defined(invoker.backend),
@@ -102,7 +99,7 @@
     "public",
   ]
   pw_source_set(_facade_name) {
-    forward_variables_from(invoker, _facade_vars, [ "require_link_deps" ])
+    forward_variables_from(invoker, _facade_vars)
   }
 
   if (invoker.backend == "") {
@@ -143,10 +140,7 @@
   # correctly expressed for gn check.
   pw_source_set(target_name) {
     # The main library contains everything else specified in the template.
-    _ignore_vars = _facade_vars + [
-                     "backend",
-                     "require_link_deps",
-                   ]
+    _ignore_vars = [ "backend" ] + _facade_vars
     forward_variables_from(invoker, "*", _ignore_vars)
 
     public_deps = [ ":$_facade_name" ]
@@ -159,28 +153,4 @@
       public_deps += [ ":$target_name.NO_BACKEND_SET" ]
     }
   }
-
-  if (defined(invoker.require_link_deps) && invoker.backend != "") {
-    # Check that the specified labels are listed in pw_build_LINK_DEPS. This
-    # ensures these dependencies are available during linking, even if nothing
-    # else in the build depends on them.
-    foreach(label, invoker.require_link_deps) {
-      _required_dep = get_label_info(label, "label_no_toolchain")
-      _dep_is_in_link_dependencies = false
-
-      foreach(link_dep, pw_build_LINK_DEPS) {
-        if (get_label_info(link_dep, "label_no_toolchain") == _required_dep) {
-          _dep_is_in_link_dependencies = true
-        }
-      }
-
-      _facade_name = get_label_info(":$target_name", "label_no_toolchain")
-      assert(_dep_is_in_link_dependencies,
-             "$_required_dep must be listed in the pw_build_LINK_DEPS build " +
-                 "arg when the $_facade_name facade is in use. Please update " +
-                 "your toolchain configuration.")
-    }
-  } else {
-    not_needed(invoker, [ "require_link_deps" ])
-  }
 }
diff --git a/pw_build/generated_pigweed_modules_lists.gni b/pw_build/generated_pigweed_modules_lists.gni
deleted file mode 100644
index f3d3627..0000000
--- a/pw_build/generated_pigweed_modules_lists.gni
+++ /dev/null
@@ -1,471 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-# Build args and lists for all modules in Pigweed.
-#
-# DO NOT EDIT! Generated by pw_build/py/pw_build/generate_modules_lists.py.
-#
-# To add modules here, list them in PIGWEED_MODULES and build the
-# update_modules target and commit the updated version of this file:
-#
-#   ninja -C out update_modules
-#
-# DO NOT IMPORT THIS FILE DIRECTLY!
-#
-# Import it through //build_overrides/pigweed.gni instead.
-
-# Declare a build arg for each module.
-declare_args() {
-  dir_docker = get_path_info("../docker", "abspath")
-  dir_pw_allocator = get_path_info("../pw_allocator", "abspath")
-  dir_pw_analog = get_path_info("../pw_analog", "abspath")
-  dir_pw_android_toolchain = get_path_info("../pw_android_toolchain", "abspath")
-  dir_pw_arduino_build = get_path_info("../pw_arduino_build", "abspath")
-  dir_pw_assert = get_path_info("../pw_assert", "abspath")
-  dir_pw_assert_basic = get_path_info("../pw_assert_basic", "abspath")
-  dir_pw_assert_log = get_path_info("../pw_assert_log", "abspath")
-  dir_pw_assert_tokenized = get_path_info("../pw_assert_tokenized", "abspath")
-  dir_pw_assert_zephyr = get_path_info("../pw_assert_zephyr", "abspath")
-  dir_pw_base64 = get_path_info("../pw_base64", "abspath")
-  dir_pw_bloat = get_path_info("../pw_bloat", "abspath")
-  dir_pw_blob_store = get_path_info("../pw_blob_store", "abspath")
-  dir_pw_bluetooth_hci = get_path_info("../pw_bluetooth_hci", "abspath")
-  dir_pw_boot = get_path_info("../pw_boot", "abspath")
-  dir_pw_boot_cortex_m = get_path_info("../pw_boot_cortex_m", "abspath")
-  dir_pw_build = get_path_info("../pw_build", "abspath")
-  dir_pw_build_info = get_path_info("../pw_build_info", "abspath")
-  dir_pw_build_mcuxpresso = get_path_info("../pw_build_mcuxpresso", "abspath")
-  dir_pw_bytes = get_path_info("../pw_bytes", "abspath")
-  dir_pw_checksum = get_path_info("../pw_checksum", "abspath")
-  dir_pw_chrono = get_path_info("../pw_chrono", "abspath")
-  dir_pw_chrono_embos = get_path_info("../pw_chrono_embos", "abspath")
-  dir_pw_chrono_freertos = get_path_info("../pw_chrono_freertos", "abspath")
-  dir_pw_chrono_stl = get_path_info("../pw_chrono_stl", "abspath")
-  dir_pw_chrono_threadx = get_path_info("../pw_chrono_threadx", "abspath")
-  dir_pw_chrono_zephyr = get_path_info("../pw_chrono_zephyr", "abspath")
-  dir_pw_cli = get_path_info("../pw_cli", "abspath")
-  dir_pw_console = get_path_info("../pw_console", "abspath")
-  dir_pw_containers = get_path_info("../pw_containers", "abspath")
-  dir_pw_cpu_exception = get_path_info("../pw_cpu_exception", "abspath")
-  dir_pw_cpu_exception_cortex_m =
-      get_path_info("../pw_cpu_exception_cortex_m", "abspath")
-  dir_pw_crypto = get_path_info("../pw_crypto", "abspath")
-  dir_pw_docgen = get_path_info("../pw_docgen", "abspath")
-  dir_pw_doctor = get_path_info("../pw_doctor", "abspath")
-  dir_pw_env_setup = get_path_info("../pw_env_setup", "abspath")
-  dir_pw_file = get_path_info("../pw_file", "abspath")
-  dir_pw_function = get_path_info("../pw_function", "abspath")
-  dir_pw_fuzzer = get_path_info("../pw_fuzzer", "abspath")
-  dir_pw_hdlc = get_path_info("../pw_hdlc", "abspath")
-  dir_pw_hex_dump = get_path_info("../pw_hex_dump", "abspath")
-  dir_pw_i2c = get_path_info("../pw_i2c", "abspath")
-  dir_pw_i2c_mcuxpresso = get_path_info("../pw_i2c_mcuxpresso", "abspath")
-  dir_pw_interrupt = get_path_info("../pw_interrupt", "abspath")
-  dir_pw_interrupt_cortex_m =
-      get_path_info("../pw_interrupt_cortex_m", "abspath")
-  dir_pw_interrupt_zephyr = get_path_info("../pw_interrupt_zephyr", "abspath")
-  dir_pw_kvs = get_path_info("../pw_kvs", "abspath")
-  dir_pw_libc = get_path_info("../pw_libc", "abspath")
-  dir_pw_log = get_path_info("../pw_log", "abspath")
-  dir_pw_log_android = get_path_info("../pw_log_android", "abspath")
-  dir_pw_log_basic = get_path_info("../pw_log_basic", "abspath")
-  dir_pw_log_null = get_path_info("../pw_log_null", "abspath")
-  dir_pw_log_rpc = get_path_info("../pw_log_rpc", "abspath")
-  dir_pw_log_string = get_path_info("../pw_log_string", "abspath")
-  dir_pw_log_tokenized = get_path_info("../pw_log_tokenized", "abspath")
-  dir_pw_log_zephyr = get_path_info("../pw_log_zephyr", "abspath")
-  dir_pw_malloc = get_path_info("../pw_malloc", "abspath")
-  dir_pw_malloc_freelist = get_path_info("../pw_malloc_freelist", "abspath")
-  dir_pw_metric = get_path_info("../pw_metric", "abspath")
-  dir_pw_minimal_cpp_stdlib =
-      get_path_info("../pw_minimal_cpp_stdlib", "abspath")
-  dir_pw_module = get_path_info("../pw_module", "abspath")
-  dir_pw_multisink = get_path_info("../pw_multisink", "abspath")
-  dir_pw_package = get_path_info("../pw_package", "abspath")
-  dir_pw_persistent_ram = get_path_info("../pw_persistent_ram", "abspath")
-  dir_pw_polyfill = get_path_info("../pw_polyfill", "abspath")
-  dir_pw_preprocessor = get_path_info("../pw_preprocessor", "abspath")
-  dir_pw_presubmit = get_path_info("../pw_presubmit", "abspath")
-  dir_pw_protobuf = get_path_info("../pw_protobuf", "abspath")
-  dir_pw_protobuf_compiler = get_path_info("../pw_protobuf_compiler", "abspath")
-  dir_pw_random = get_path_info("../pw_random", "abspath")
-  dir_pw_result = get_path_info("../pw_result", "abspath")
-  dir_pw_ring_buffer = get_path_info("../pw_ring_buffer", "abspath")
-  dir_pw_router = get_path_info("../pw_router", "abspath")
-  dir_pw_rpc = get_path_info("../pw_rpc", "abspath")
-  dir_pw_snapshot = get_path_info("../pw_snapshot", "abspath")
-  dir_pw_software_update = get_path_info("../pw_software_update", "abspath")
-  dir_pw_span = get_path_info("../pw_span", "abspath")
-  dir_pw_spi = get_path_info("../pw_spi", "abspath")
-  dir_pw_status = get_path_info("../pw_status", "abspath")
-  dir_pw_stm32cube_build = get_path_info("../pw_stm32cube_build", "abspath")
-  dir_pw_stream = get_path_info("../pw_stream", "abspath")
-  dir_pw_string = get_path_info("../pw_string", "abspath")
-  dir_pw_symbolizer = get_path_info("../pw_symbolizer", "abspath")
-  dir_pw_sync = get_path_info("../pw_sync", "abspath")
-  dir_pw_sync_baremetal = get_path_info("../pw_sync_baremetal", "abspath")
-  dir_pw_sync_embos = get_path_info("../pw_sync_embos", "abspath")
-  dir_pw_sync_freertos = get_path_info("../pw_sync_freertos", "abspath")
-  dir_pw_sync_stl = get_path_info("../pw_sync_stl", "abspath")
-  dir_pw_sync_threadx = get_path_info("../pw_sync_threadx", "abspath")
-  dir_pw_sync_zephyr = get_path_info("../pw_sync_zephyr", "abspath")
-  dir_pw_sys_io = get_path_info("../pw_sys_io", "abspath")
-  dir_pw_sys_io_arduino = get_path_info("../pw_sys_io_arduino", "abspath")
-  dir_pw_sys_io_baremetal_lm3s6965evb =
-      get_path_info("../pw_sys_io_baremetal_lm3s6965evb", "abspath")
-  dir_pw_sys_io_baremetal_stm32f429 =
-      get_path_info("../pw_sys_io_baremetal_stm32f429", "abspath")
-  dir_pw_sys_io_emcraft_sf2 =
-      get_path_info("../pw_sys_io_emcraft_sf2", "abspath")
-  dir_pw_sys_io_mcuxpresso = get_path_info("../pw_sys_io_mcuxpresso", "abspath")
-  dir_pw_sys_io_stdio = get_path_info("../pw_sys_io_stdio", "abspath")
-  dir_pw_sys_io_stm32cube = get_path_info("../pw_sys_io_stm32cube", "abspath")
-  dir_pw_sys_io_zephyr = get_path_info("../pw_sys_io_zephyr", "abspath")
-  dir_pw_system = get_path_info("../pw_system", "abspath")
-  dir_pw_target_runner = get_path_info("../pw_target_runner", "abspath")
-  dir_pw_thread = get_path_info("../pw_thread", "abspath")
-  dir_pw_thread_embos = get_path_info("../pw_thread_embos", "abspath")
-  dir_pw_thread_freertos = get_path_info("../pw_thread_freertos", "abspath")
-  dir_pw_thread_stl = get_path_info("../pw_thread_stl", "abspath")
-  dir_pw_thread_threadx = get_path_info("../pw_thread_threadx", "abspath")
-  dir_pw_tls_client = get_path_info("../pw_tls_client", "abspath")
-  dir_pw_tls_client_boringssl =
-      get_path_info("../pw_tls_client_boringssl", "abspath")
-  dir_pw_tls_client_mbedtls =
-      get_path_info("../pw_tls_client_mbedtls", "abspath")
-  dir_pw_tokenizer = get_path_info("../pw_tokenizer", "abspath")
-  dir_pw_tool = get_path_info("../pw_tool", "abspath")
-  dir_pw_toolchain = get_path_info("../pw_toolchain", "abspath")
-  dir_pw_trace = get_path_info("../pw_trace", "abspath")
-  dir_pw_trace_tokenized = get_path_info("../pw_trace_tokenized", "abspath")
-  dir_pw_transfer = get_path_info("../pw_transfer", "abspath")
-  dir_pw_unit_test = get_path_info("../pw_unit_test", "abspath")
-  dir_pw_varint = get_path_info("../pw_varint", "abspath")
-  dir_pw_watch = get_path_info("../pw_watch", "abspath")
-  dir_pw_web_ui = get_path_info("../pw_web_ui", "abspath")
-  dir_pw_work_queue = get_path_info("../pw_work_queue", "abspath")
-}
-
-# Declare these as GN args in case this is imported in args.gni.
-# Use a separate block so variables in the prior block can be used.
-declare_args() {
-  # A list with paths to all Pigweed module. DO NOT SET THIS BUILD ARGUMENT!
-  pw_modules = [
-    dir_docker,
-    dir_pw_allocator,
-    dir_pw_analog,
-    dir_pw_android_toolchain,
-    dir_pw_arduino_build,
-    dir_pw_assert,
-    dir_pw_assert_basic,
-    dir_pw_assert_log,
-    dir_pw_assert_tokenized,
-    dir_pw_assert_zephyr,
-    dir_pw_base64,
-    dir_pw_bloat,
-    dir_pw_blob_store,
-    dir_pw_bluetooth_hci,
-    dir_pw_boot,
-    dir_pw_boot_cortex_m,
-    dir_pw_build,
-    dir_pw_build_info,
-    dir_pw_build_mcuxpresso,
-    dir_pw_bytes,
-    dir_pw_checksum,
-    dir_pw_chrono,
-    dir_pw_chrono_embos,
-    dir_pw_chrono_freertos,
-    dir_pw_chrono_stl,
-    dir_pw_chrono_threadx,
-    dir_pw_chrono_zephyr,
-    dir_pw_cli,
-    dir_pw_console,
-    dir_pw_containers,
-    dir_pw_cpu_exception,
-    dir_pw_cpu_exception_cortex_m,
-    dir_pw_crypto,
-    dir_pw_docgen,
-    dir_pw_doctor,
-    dir_pw_env_setup,
-    dir_pw_file,
-    dir_pw_function,
-    dir_pw_fuzzer,
-    dir_pw_hdlc,
-    dir_pw_hex_dump,
-    dir_pw_i2c,
-    dir_pw_i2c_mcuxpresso,
-    dir_pw_interrupt,
-    dir_pw_interrupt_cortex_m,
-    dir_pw_interrupt_zephyr,
-    dir_pw_kvs,
-    dir_pw_libc,
-    dir_pw_log,
-    dir_pw_log_android,
-    dir_pw_log_basic,
-    dir_pw_log_null,
-    dir_pw_log_rpc,
-    dir_pw_log_string,
-    dir_pw_log_tokenized,
-    dir_pw_log_zephyr,
-    dir_pw_malloc,
-    dir_pw_malloc_freelist,
-    dir_pw_metric,
-    dir_pw_minimal_cpp_stdlib,
-    dir_pw_module,
-    dir_pw_multisink,
-    dir_pw_package,
-    dir_pw_persistent_ram,
-    dir_pw_polyfill,
-    dir_pw_preprocessor,
-    dir_pw_presubmit,
-    dir_pw_protobuf,
-    dir_pw_protobuf_compiler,
-    dir_pw_random,
-    dir_pw_result,
-    dir_pw_ring_buffer,
-    dir_pw_router,
-    dir_pw_rpc,
-    dir_pw_snapshot,
-    dir_pw_software_update,
-    dir_pw_span,
-    dir_pw_spi,
-    dir_pw_status,
-    dir_pw_stm32cube_build,
-    dir_pw_stream,
-    dir_pw_string,
-    dir_pw_symbolizer,
-    dir_pw_sync,
-    dir_pw_sync_baremetal,
-    dir_pw_sync_embos,
-    dir_pw_sync_freertos,
-    dir_pw_sync_stl,
-    dir_pw_sync_threadx,
-    dir_pw_sync_zephyr,
-    dir_pw_sys_io,
-    dir_pw_sys_io_arduino,
-    dir_pw_sys_io_baremetal_lm3s6965evb,
-    dir_pw_sys_io_baremetal_stm32f429,
-    dir_pw_sys_io_emcraft_sf2,
-    dir_pw_sys_io_mcuxpresso,
-    dir_pw_sys_io_stdio,
-    dir_pw_sys_io_stm32cube,
-    dir_pw_sys_io_zephyr,
-    dir_pw_system,
-    dir_pw_target_runner,
-    dir_pw_thread,
-    dir_pw_thread_embos,
-    dir_pw_thread_freertos,
-    dir_pw_thread_stl,
-    dir_pw_thread_threadx,
-    dir_pw_tls_client,
-    dir_pw_tls_client_boringssl,
-    dir_pw_tls_client_mbedtls,
-    dir_pw_tokenizer,
-    dir_pw_tool,
-    dir_pw_toolchain,
-    dir_pw_trace,
-    dir_pw_trace_tokenized,
-    dir_pw_transfer,
-    dir_pw_unit_test,
-    dir_pw_varint,
-    dir_pw_watch,
-    dir_pw_web_ui,
-    dir_pw_work_queue,
-  ]
-
-  # A list with all Pigweed module test groups. DO NOT SET THIS BUILD ARGUMENT!
-  pw_module_tests = [
-    "$dir_pw_allocator:tests",
-    "$dir_pw_analog:tests",
-    "$dir_pw_assert:tests",
-    "$dir_pw_base64:tests",
-    "$dir_pw_blob_store:tests",
-    "$dir_pw_bluetooth_hci:tests",
-    "$dir_pw_bytes:tests",
-    "$dir_pw_checksum:tests",
-    "$dir_pw_chrono:tests",
-    "$dir_pw_containers:tests",
-    "$dir_pw_cpu_exception_cortex_m:tests",
-    "$dir_pw_crypto:tests",
-    "$dir_pw_file:tests",
-    "$dir_pw_function:tests",
-    "$dir_pw_fuzzer:tests",
-    "$dir_pw_hdlc:tests",
-    "$dir_pw_hex_dump:tests",
-    "$dir_pw_i2c:tests",
-    "$dir_pw_kvs:tests",
-    "$dir_pw_libc:tests",
-    "$dir_pw_log:tests",
-    "$dir_pw_log_null:tests",
-    "$dir_pw_log_rpc:tests",
-    "$dir_pw_log_tokenized:tests",
-    "$dir_pw_malloc_freelist:tests",
-    "$dir_pw_metric:tests",
-    "$dir_pw_minimal_cpp_stdlib:tests",
-    "$dir_pw_multisink:tests",
-    "$dir_pw_persistent_ram:tests",
-    "$dir_pw_polyfill:tests",
-    "$dir_pw_preprocessor:tests",
-    "$dir_pw_protobuf:tests",
-    "$dir_pw_protobuf_compiler:tests",
-    "$dir_pw_random:tests",
-    "$dir_pw_result:tests",
-    "$dir_pw_ring_buffer:tests",
-    "$dir_pw_router:tests",
-    "$dir_pw_rpc:tests",
-    "$dir_pw_snapshot:tests",
-    "$dir_pw_software_update:tests",
-    "$dir_pw_span:tests",
-    "$dir_pw_spi:tests",
-    "$dir_pw_status:tests",
-    "$dir_pw_stream:tests",
-    "$dir_pw_string:tests",
-    "$dir_pw_sync:tests",
-    "$dir_pw_thread:tests",
-    "$dir_pw_thread_embos:tests",
-    "$dir_pw_thread_freertos:tests",
-    "$dir_pw_thread_stl:tests",
-    "$dir_pw_thread_threadx:tests",
-    "$dir_pw_tls_client:tests",
-    "$dir_pw_tls_client_boringssl:tests",
-    "$dir_pw_tls_client_mbedtls:tests",
-    "$dir_pw_tokenizer:tests",
-    "$dir_pw_trace:tests",
-    "$dir_pw_trace_tokenized:tests",
-    "$dir_pw_transfer:tests",
-    "$dir_pw_unit_test:tests",
-    "$dir_pw_varint:tests",
-    "$dir_pw_work_queue:tests",
-  ]
-
-  # A list with all Pigweed modules docs groups. DO NOT SET THIS BUILD ARGUMENT!
-  pw_module_docs = [
-    "$dir_docker:docs",
-    "$dir_pw_allocator:docs",
-    "$dir_pw_analog:docs",
-    "$dir_pw_android_toolchain:docs",
-    "$dir_pw_arduino_build:docs",
-    "$dir_pw_assert:docs",
-    "$dir_pw_assert_basic:docs",
-    "$dir_pw_assert_log:docs",
-    "$dir_pw_assert_tokenized:docs",
-    "$dir_pw_assert_zephyr:docs",
-    "$dir_pw_base64:docs",
-    "$dir_pw_bloat:docs",
-    "$dir_pw_blob_store:docs",
-    "$dir_pw_bluetooth_hci:docs",
-    "$dir_pw_boot:docs",
-    "$dir_pw_boot_cortex_m:docs",
-    "$dir_pw_build:docs",
-    "$dir_pw_build_info:docs",
-    "$dir_pw_build_mcuxpresso:docs",
-    "$dir_pw_bytes:docs",
-    "$dir_pw_checksum:docs",
-    "$dir_pw_chrono:docs",
-    "$dir_pw_chrono_embos:docs",
-    "$dir_pw_chrono_freertos:docs",
-    "$dir_pw_chrono_stl:docs",
-    "$dir_pw_chrono_threadx:docs",
-    "$dir_pw_chrono_zephyr:docs",
-    "$dir_pw_cli:docs",
-    "$dir_pw_console:docs",
-    "$dir_pw_containers:docs",
-    "$dir_pw_cpu_exception:docs",
-    "$dir_pw_cpu_exception_cortex_m:docs",
-    "$dir_pw_crypto:docs",
-    "$dir_pw_docgen:docs",
-    "$dir_pw_doctor:docs",
-    "$dir_pw_env_setup:docs",
-    "$dir_pw_file:docs",
-    "$dir_pw_function:docs",
-    "$dir_pw_fuzzer:docs",
-    "$dir_pw_hdlc:docs",
-    "$dir_pw_hex_dump:docs",
-    "$dir_pw_i2c:docs",
-    "$dir_pw_i2c_mcuxpresso:docs",
-    "$dir_pw_interrupt:docs",
-    "$dir_pw_interrupt_cortex_m:docs",
-    "$dir_pw_interrupt_zephyr:docs",
-    "$dir_pw_kvs:docs",
-    "$dir_pw_libc:docs",
-    "$dir_pw_log:docs",
-    "$dir_pw_log_basic:docs",
-    "$dir_pw_log_null:docs",
-    "$dir_pw_log_rpc:docs",
-    "$dir_pw_log_string:docs",
-    "$dir_pw_log_tokenized:docs",
-    "$dir_pw_malloc:docs",
-    "$dir_pw_malloc_freelist:docs",
-    "$dir_pw_metric:docs",
-    "$dir_pw_minimal_cpp_stdlib:docs",
-    "$dir_pw_module:docs",
-    "$dir_pw_multisink:docs",
-    "$dir_pw_package:docs",
-    "$dir_pw_persistent_ram:docs",
-    "$dir_pw_polyfill:docs",
-    "$dir_pw_preprocessor:docs",
-    "$dir_pw_presubmit:docs",
-    "$dir_pw_protobuf:docs",
-    "$dir_pw_protobuf_compiler:docs",
-    "$dir_pw_random:docs",
-    "$dir_pw_result:docs",
-    "$dir_pw_ring_buffer:docs",
-    "$dir_pw_router:docs",
-    "$dir_pw_rpc:docs",
-    "$dir_pw_snapshot:docs",
-    "$dir_pw_software_update:docs",
-    "$dir_pw_span:docs",
-    "$dir_pw_spi:docs",
-    "$dir_pw_status:docs",
-    "$dir_pw_stm32cube_build:docs",
-    "$dir_pw_stream:docs",
-    "$dir_pw_string:docs",
-    "$dir_pw_symbolizer:docs",
-    "$dir_pw_sync:docs",
-    "$dir_pw_sync_baremetal:docs",
-    "$dir_pw_sync_embos:docs",
-    "$dir_pw_sync_freertos:docs",
-    "$dir_pw_sync_stl:docs",
-    "$dir_pw_sync_threadx:docs",
-    "$dir_pw_sync_zephyr:docs",
-    "$dir_pw_sys_io:docs",
-    "$dir_pw_sys_io_arduino:docs",
-    "$dir_pw_sys_io_baremetal_stm32f429:docs",
-    "$dir_pw_sys_io_emcraft_sf2:docs",
-    "$dir_pw_sys_io_mcuxpresso:docs",
-    "$dir_pw_sys_io_stdio:docs",
-    "$dir_pw_sys_io_stm32cube:docs",
-    "$dir_pw_sys_io_zephyr:docs",
-    "$dir_pw_system:docs",
-    "$dir_pw_target_runner:docs",
-    "$dir_pw_thread:docs",
-    "$dir_pw_thread_embos:docs",
-    "$dir_pw_thread_freertos:docs",
-    "$dir_pw_thread_stl:docs",
-    "$dir_pw_thread_threadx:docs",
-    "$dir_pw_tls_client:docs",
-    "$dir_pw_tls_client_boringssl:docs",
-    "$dir_pw_tls_client_mbedtls:docs",
-    "$dir_pw_tokenizer:docs",
-    "$dir_pw_toolchain:docs",
-    "$dir_pw_trace:docs",
-    "$dir_pw_trace_tokenized:docs",
-    "$dir_pw_transfer:docs",
-    "$dir_pw_unit_test:docs",
-    "$dir_pw_varint:docs",
-    "$dir_pw_watch:docs",
-    "$dir_pw_web_ui:docs",
-    "$dir_pw_work_queue:docs",
-  ]
-}
diff --git a/pw_build/go.gni b/pw_build/go.gni
index d13af2e..ce84061 100644
--- a/pw_build/go.gni
+++ b/pw_build/go.gni
@@ -122,25 +122,14 @@
     program = "go"
     args = [ "get" ]
     deps = [ ":$_deps_metadata_target_name" ] + invoker.deps
-    env = [
-      "GO111MODULE=off",
-      "GOPATH=$_default_gopath",
-    ]
-
-    if (host_os == "mac") {
-      # TODO(frolv): Some versions of Go for MacOS have issues finding the C
-      # stdlib headers. Temporarily disable CGo on Mac. The root cause of this
-      # issue should be investigated and fixed.
-      env += [ "CGO_ENABLED=0" ]
-    }
-
+    env = [ "GOPATH=$_default_gopath" ]
     args_file = _deps_metadata_file
 
     # If the args file is empty, don't run the "go get" command.
     skip_empty_args = true
 
     # Limit download parallelization to 1.
-    pool = "$dir_pw_build/pool:go_download($default_toolchain)"
+    pool = "$dir_pw_build:go_download_pool"
   }
 
   # Run a "go build" command with the environment configured from metadata.
@@ -149,25 +138,14 @@
     args = [
       "build",
       "-o",
-      rebase_path(target_out_dir, root_build_dir),
+      rebase_path(target_out_dir),
       invoker.package,
     ]
     deps = [
       ":$_download_target_name",
       ":$_metadata_target_name",
     ]
-    env = [
-      "GO111MODULE=off",
-      "GOPATH+=$_default_gopath",
-    ]
-
-    if (host_os == "mac") {
-      # TODO(frolv): Some versions of Go for MacOS have issues finding the C
-      # stdlib headers. Temporarily disable CGo on Mac. The root cause of this
-      # issue should be investigated and fixed.
-      env += [ "CGO_ENABLED=0" ]
-    }
-
+    env = [ "GOPATH+=$_default_gopath" ]
     env_file = _metadata_file
 
     _binary_name = get_path_info(invoker.package, "name")
diff --git a/pw_build/hil.gni b/pw_build/hil.gni
deleted file mode 100644
index bb3b4ee..0000000
--- a/pw_build/hil.gni
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/python.gni")
-
-# A hardware-in-the-loop (HIL) test.
-#
-# The HIL tests are assumed to require exclusive access to a device-under-test,
-# and are always executed serially.
-#
-# Args:
-#   target_type: The type of underlying target that implements the HIL
-#     test. Currently "python" is the only supported value, producing a
-#     "py_python_script". Support for other languages will be added in the
-#     future.
-template("pw_hil_test") {
-  assert(defined(invoker.target_type),
-         "pw_hil_test must specify the 'target_type'")
-  if (invoker.target_type == "python") {
-    pw_python_script(target_name) {
-      action = {
-        pool = "$dir_pw_build/pool:pw_hil_test($default_toolchain)"
-        stamp = true
-
-        # We want the test stdout to be saved.
-        capture_output = false
-      }
-      forward_variables_from(invoker, "*", [ "target_type" ])
-    }
-  }
-}
diff --git a/pw_build/host_tool.gni b/pw_build/host_tool.gni
index 53344f3..cac3f23 100644
--- a/pw_build/host_tool.gni
+++ b/pw_build/host_tool.gni
@@ -14,36 +14,52 @@
 
 import("python_action.gni")
 
-# Defines a Pigweed host tool and installs it into the host_tools directory.
-#
-# Args:
-#   tool: The target that outputs the binary tool.
-#   name: Optional name for the installed program. Defaults to the name of
-#     the compiled binary.
-template("pw_host_tool") {
-  assert(defined(invoker.tool),
-         "pw_host_tool must specify an executable as the tool variable")
-
-  _script_args = [
-    "--src",
-    "<TARGET_FILE(${invoker.tool})>",
-    "--dst",
-    rebase_path("$root_out_dir/host_tools", root_build_dir),
-    "--out-root",
-    rebase_path(root_out_dir, root_build_dir),
-  ]
-
-  if (defined(invoker.name) && invoker.name != "") {
-    _script_args += [
-      "--name",
-      invoker.name,
-    ]
-  }
-
-  pw_python_action(target_name) {
-    script = "$dir_pw_build/py/pw_build/host_tool.py"
-    args = _script_args
-    deps = [ invoker.tool ]
-    stamp = true
-  }
+declare_args() {
+  # Whether to build host-side tooling.
+  pw_build_HOST_TOOLS = false
 }
+
+if (pw_build_HOST_TOOLS) {
+  # Defines a Pigweed host tool and installs it into the host_tools directory.
+  #
+  # Args:
+  #   tool: The target that outputs the binary tool.
+  #   name: Optional name for the installed program. Defaults to the name of
+  #     the compiled binary.
+  template("pw_host_tool") {
+    assert(defined(invoker.tool),
+           "pw_host_tool must specify an executable as the tool variable")
+
+    _script_args = [
+      "--src",
+      "<TARGET_FILE(${invoker.tool})>",
+      "--dst",
+      rebase_path("$root_out_dir/host_tools"),
+      "--out-root",
+      rebase_path(root_out_dir),
+    ]
+
+    if (defined(invoker.name) && invoker.name != "") {
+      _script_args += [
+        "--name",
+        invoker.name,
+      ]
+    }
+
+    pw_python_action(target_name) {
+      script = "$dir_pw_build/py/pw_build/host_tool.py"
+      args = _script_args
+      deps = [ invoker.tool ]
+      stamp = true
+    }
+  }
+} else {
+  # For builds without host tools, create an empty target.
+  template("pw_host_tool") {
+    not_needed("*")
+    not_needed(invoker, "*")
+
+    group(target_name) {
+    }
+  }
+}  # pw_build_HOST_TOOLS
diff --git a/pw_build/linker_script.gni b/pw_build/linker_script.gni
index 0cf681b..7ba64ac 100644
--- a/pw_build/linker_script.gni
+++ b/pw_build/linker_script.gni
@@ -73,7 +73,7 @@
       # Treat the following file as a C file.
       "-x",
       "c",
-      rebase_path(invoker.linker_script, root_build_dir),
+      rebase_path(invoker.linker_script),
     ]
 
     # Include any explicitly listed c flags.
@@ -91,7 +91,7 @@
     # Set output file.
     args += [
       "-o",
-      rebase_path(_final_linker_script, root_build_dir),
+      rebase_path(_final_linker_script),
     ]
     outputs = [ _final_linker_script ]
   }
@@ -103,7 +103,7 @@
     if (!defined(invoker.ldflags)) {
       ldflags = []
     }
-    ldflags += [ "-T" + rebase_path(_final_linker_script, root_build_dir) ]
+    ldflags += [ "-T" + rebase_path(_final_linker_script) ]
   }
 
   # The target that adds the linker script config to this library and everything
diff --git a/pw_build/mirror_tree.gni b/pw_build/mirror_tree.gni
index 6114bf4..5e78de8 100644
--- a/pw_build/mirror_tree.gni
+++ b/pw_build/mirror_tree.gni
@@ -14,6 +14,8 @@
 
 import("//build_overrides/pigweed.gni")
 
+import("$dir_pw_build/python_action.gni")
+
 # Mirrors a directory structure to the output directory.
 #
 # This is similar to a GN copy target, with some differences:
@@ -28,9 +30,12 @@
 #   directory: Output directory for the files.
 #   sources: List of files to mirror to the output directory.
 #   source_root: Root path for sources; defaults to ".".
+#   path_data_keys: GN metadata data_keys from which to extract file or
+#       directory paths to add to the list of sources.
 #
 template("pw_mirror_tree") {
-  assert(defined(invoker.sources), "'sources' must be provided")
+  assert(defined(invoker.sources) || defined(invoker.path_data_keys),
+         "At least one of 'sources' or 'path_data_keys' must be provided")
   assert(defined(invoker.directory) && invoker.directory != "",
          "The output path must be specified as 'directory'")
 
@@ -40,6 +45,13 @@
     _root = "."
   }
 
+  _args = [
+    "--source-root",
+    rebase_path(_root),
+    "--directory",
+    rebase_path(invoker.directory),
+  ]
+
   _deps = []
   if (defined(invoker.deps)) {
     _deps += invoker.deps
@@ -50,23 +62,52 @@
     _public_deps += invoker.public_deps
   }
 
-  _copy_deps = []
+  if (defined(invoker.path_data_keys)) {
+    generated_file("$target_name._path_list") {
+      data_keys = invoker.path_data_keys
+      rebase = root_build_dir
+      outputs = [ "$target_gen_dir/$target_name.txt" ]
+      deps = _deps + _public_deps
 
-  foreach(source, invoker.sources) {
-    _stripped_source = rebase_path(source, _root)
-    _subtarget_name =
-        string_replace("${target_name}_${_stripped_source}", "/", ".")
-
-    copy(_subtarget_name) {
-      sources = [ source ]
-      outputs = [ "${invoker.directory}/${_stripped_source}" ]
-      deps = _deps
-      public_deps = _public_deps
+      assert(deps != [],
+             "'path_data_keys' requires at least one dependency in 'deps'")
     }
-    _copy_deps += [ ":${_subtarget_name}" ]
+
+    _deps += [ ":$target_name._path_list" ]
+    _args += [ "--path-file" ] +
+             rebase_path(get_target_outputs(":$target_name._path_list"))
   }
 
-  group(target_name) {
-    public_deps = _copy_deps
+  pw_python_action(target_name) {
+    script = "$dir_pw_build/py/pw_build/mirror_tree.py"
+    args = _args
+
+    outputs = []
+
+    if (defined(invoker.sources)) {
+      args += rebase_path(invoker.sources)
+
+      foreach(path, rebase_path(invoker.sources, _root)) {
+        outputs += [ "${invoker.directory}/$path" ]
+      }
+    }
+
+    # If path_data_keys is used, the outputs may be unknown.
+    if (outputs == []) {
+      stamp = true
+    }
+
+    deps = _deps
+    public_deps = _public_deps
+
+    _ignore_args = [
+      "script",
+      "args",
+      "outputs",
+      "directory",
+      "deps",
+      "public_deps",
+    ]
+    forward_variables_from(invoker, "*", _ignore_args)
   }
 }
diff --git a/pw_build/pigweed.bzl b/pw_build/pigweed.bzl
index a1e24bc..3cc0de1 100644
--- a/pw_build/pigweed.bzl
+++ b/pw_build/pigweed.bzl
@@ -11,46 +11,109 @@
 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 # License for the specific language governing permissions and limitations under
 # the License.
+
 """Pigweed build environment for bazel."""
 
-load(
-    "//pw_build/bazel_internal:pigweed_internal.bzl",
-    _add_cc_and_c_targets = "add_cc_and_c_targets",
-    _has_pw_assert_dep = "has_pw_assert_dep",
+DEBUGGING = [
+    "-g",
+]
+
+# Standard compiler flags to reduce output binary size.
+REDUCED_SIZE_COPTS = [
+    "-fno-common",
+    "-fno-exceptions",
+    "-ffunction-sections",
+    "-fdata-sections",
+]
+
+STRICT_WARNINGS_COPTS = [
+    "-Wall",
+    "-Wextra",
+    # Make all warnings errors, except for the exemptions below.
+    "-Werror",
+    "-Wno-error=cpp",  # preprocessor #warning statement
+    "-Wno-error=deprecated-declarations",  # [[deprecated]] attribute
+]
+
+CPP17_COPTS = [
+    "-std=c++17",
+    "-fno-rtti",
+    "-Wnon-virtual-dtor",
+    # Allow uses of the register keyword, which may appear in C headers.
+    "-Wno-register",
+]
+
+DISABLE_PENDING_WORKAROUND_COPTS = [
+    "-Wno-private-header",
+]
+
+PW_DEFAULT_COPTS = (
+    DEBUGGING +
+    REDUCED_SIZE_COPTS +
+    STRICT_WARNINGS_COPTS +
+    DISABLE_PENDING_WORKAROUND_COPTS
 )
 
-def pw_cc_binary(**kwargs):
-    kwargs["deps"] = kwargs.get("deps", [])
+KYTHE_COPTS = [
+    "-Wno-unknown-warning-option",
+]
 
-    # TODO(pwbug/440): Remove this implicit dependency once we have a better
-    # way to handle the facades without introducing a circular dependency into
-    # the build.
-    if not _has_pw_assert_dep(kwargs["deps"]):
-        kwargs["deps"].append("@pigweed//pw_assert")
+PW_DEFAULT_LINKOPTS = []
+
+def _add_defaults(kwargs):
+    """Adds default arguments suitable for both C and C++ code to kwargs."""
+
+    copts = kwargs.get("copts", []) + PW_DEFAULT_COPTS
+    kwargs["copts"] = select({
+        "//pw_build:kythe": copts + KYTHE_COPTS,
+        "//conditions:default": copts,
+    })
+    kwargs["linkopts"] = kwargs.get("linkopts", []) + PW_DEFAULT_LINKOPTS
+
+    # Set linkstatic to avoid building .so files.
+    kwargs["linkstatic"] = True
+
+    kwargs.setdefault("features", [])
+
+    # Crosstool--adding this line to features disables header modules, which
+    # don't work with -fno-rtti. Note: this is not a command-line argument,
+    # it's "minus use_header_modules".
+    kwargs["features"].append("-use_header_modules")
+
+def _default_cc_and_c_kwargs(kwargs):
+    _add_defaults(kwargs)
+    kwargs.setdefault("srcs", [])
+
+    cc = dict(kwargs.items())
+    cc["srcs"] = [src for src in kwargs["srcs"] if not src.endswith(".c")]
+    cc["copts"] = cc["copts"] + CPP17_COPTS
+
+    c_srcs = [src for src in kwargs["srcs"] if src.endswith(".c")]
+
+    if c_srcs:
+        c = dict(kwargs.items())
+        c["name"] += "_c"
+        c["srcs"] = c_srcs + [src for src in kwargs["srcs"] if src.endswith(".h")]
+
+        cc["deps"] = cc.get("deps", []) + [":" + c["name"]]
+        return cc, c
+
+    return cc, None
+
+def _add_cc_and_c_targets(target, kwargs):
+    cc_kwargs, c_kwargs = _default_cc_and_c_kwargs(kwargs)
+
+    if c_kwargs:
+        native.cc_library(**c_kwargs)
+
+    target(**cc_kwargs)
+
+def pw_cc_binary(**kwargs):
     _add_cc_and_c_targets(native.cc_binary, kwargs)
 
 def pw_cc_library(**kwargs):
     _add_cc_and_c_targets(native.cc_library, kwargs)
 
 def pw_cc_test(**kwargs):
-    kwargs["deps"] = kwargs.get("deps", []) + \
-                     ["//pw_unit_test:main"]
-
-    # TODO(pwbug/440): Remove this implicit dependency once we have a better
-    # way to handle the facades without introducing a circular dependency into
-    # the build.
-    if not _has_pw_assert_dep(kwargs["deps"]):
-        kwargs["deps"].append("@pigweed//pw_assert")
+    kwargs["deps"] = kwargs.get("deps", []) + ["//pw_unit_test:main"]
     _add_cc_and_c_targets(native.cc_test, kwargs)
-
-def pw_cc_facade(**kwargs):
-    # Bazel facades should be source only cc_library's this is to simplify
-    # lazy header evaluation. Bazel headers are not 'precompiled' so the build
-    # system does not check to see if the build has the right dependant headers
-    # in the sandbox. If a source file is declared here and includes a header
-    # file the toolchain will compile as normal and complain about the missing
-    # backend headers.
-    if "srcs" in kwargs.keys():
-        fail("'srcs' attribute does not exist in pw_cc_facade, please use \
-        main implementing target.")
-    _add_cc_and_c_targets(native.cc_library, kwargs)
diff --git a/pw_build/pigweed.cmake b/pw_build/pigweed.cmake
index ceed0b6..2d0fa3f 100644
--- a/pw_build/pigweed.cmake
+++ b/pw_build/pigweed.cmake
@@ -13,15 +13,6 @@
 # the License.
 include_guard(GLOBAL)
 
-# The PW_ROOT environment variable should be set in bootstrap. If it is not set,
-# set it to the root of the Pigweed repository.
-if("$ENV{PW_ROOT}" STREQUAL "")
-  get_filename_component(pw_root "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
-  message("The PW_ROOT environment variable is not set; "
-          "using ${pw_root} within CMake")
-  set(ENV{PW_ROOT} "${pw_root}")
-endif()
-
 # Wrapper around cmake_parse_arguments that fails with an error if any arguments
 # remained unparsed.
 macro(_pw_parse_argv_strict function start_arg options one multi)
@@ -78,8 +69,7 @@
 
   # Create a library with all source files not ending in _test.
   set(sources "${all_sources}")
-  list(FILTER sources EXCLUDE REGEX "_test(\\.cc|(_c)?\\.c)$")  # *_test.cc
-  list(FILTER sources EXCLUDE REGEX "^test(\\.cc|(_c)?\\.c)$")  # test.cc
+  list(FILTER sources EXCLUDE REGEX "_test(\\.cc|(_c)?\\.c)$")
   list(FILTER sources EXCLUDE REGEX "_fuzzer\\.cc$")
 
   file(GLOB_RECURSE headers *.h)
@@ -103,6 +93,10 @@
       ${headers}
   )
 
+  if(arg_IMPLEMENTS_FACADE)
+    target_include_directories("${MODULE}" PUBLIC public_overrides)
+  endif()
+
   pw_auto_add_module_tests("${MODULE}"
     PRIVATE_DEPS
       ${arg_PUBLIC_DEPS}
@@ -164,25 +158,10 @@
 #   HEADERS - header files for this library
 #   PUBLIC_DEPS - public target_link_libraries arguments
 #   PRIVATE_DEPS - private target_link_libraries arguments
-#   PUBLIC_INCLUDES - public target_include_directories argument
-#   PRIVATE_INCLUDES - public target_include_directories argument
 #   IMPLEMENTS_FACADES - which facades this library implements
-#   PUBLIC_DEFINES - public target_compile_definitions arguments
-#   PRIVATE_DEFINES - private target_compile_definitions arguments
-#   PUBLIC_COMPILE_OPTIONS - public target_compile_options arguments
-#   PRIVATE_COMPILE_OPTIONS - private target_compile_options arguments
-#   PUBLIC_LINK_OPTIONS - public target_link_options arguments
-#   PRIVATE_LINK_OPTIONS - private target_link_options arguments
 #
 function(pw_add_module_library NAME)
-  _pw_library_args(
-      list_args
-          PUBLIC_INCLUDES PRIVATE_INCLUDES
-          IMPLEMENTS_FACADES
-          PUBLIC_DEFINES PRIVATE_DEFINES
-          PUBLIC_COMPILE_OPTIONS PRIVATE_COMPILE_OPTIONS
-          PUBLIC_LINK_OPTIONS PRIVATE_LINK_OPTIONS
-  )
+  _pw_library_args(list_args IMPLEMENTS_FACADES)
   _pw_parse_argv_strict(pw_add_module_library 1 "" "" "${list_args}")
 
   # Check that the library's name is prefixed by the module name.
@@ -195,110 +174,29 @@
     )
   endif()
 
-  # Instead of forking all of the code below or injecting an empty source file,
-  # conditionally select PUBLIC vs INTERFACE depending on whether there are
-  # sources to compile.
-  if(NOT "${arg_SOURCES}" STREQUAL "")
-    add_library("${NAME}" EXCLUDE_FROM_ALL)
-    set(public_or_interface PUBLIC)
-  else("${arg_SOURCES}" STREQUAL "")
-    add_library("${NAME}" EXCLUDE_FROM_ALL INTERFACE)
-    set(public_or_interface INTERFACE)
-  endif(NOT "${arg_SOURCES}" STREQUAL "")
-
-  target_sources("${NAME}" PRIVATE ${arg_SOURCES} ${arg_HEADERS})
-
-  # CMake 3.22 does not have a notion of target_headers yet, so in the mean
-  # time we ask for headers to be specified for consistency with GN & Bazel and
-  # to improve the IDE experience. However, we do want to ensure all the headers
-  # which are otherwise ignored by CMake are present.
-  #
-  # See https://gitlab.kitware.com/cmake/cmake/-/issues/22468 for adding support
-  # to CMake to associate headers with targets properly for CMake 3.23.
-  foreach(header IN ITEMS ${arg_HEADERS})
-    get_filename_component(header "${header}" ABSOLUTE)
-    if(NOT EXISTS ${header})
-      message(FATAL_ERROR "Header not found: \"${header}\"")
-    endif()
-  endforeach()
-
-  if(NOT "${arg_PUBLIC_INCLUDES}" STREQUAL "")
-    target_include_directories("${NAME}"
-      ${public_or_interface}
-        ${arg_PUBLIC_INCLUDES}
-    )
-  else("${arg_PUBLIC_INCLUDES}" STREQUAL "")
-    # TODO(pwbug/601): Deprecate this legacy implicit PUBLIC_INCLUDES.
-    target_include_directories("${NAME}" ${public_or_interface} public)
-  endif(NOT "${arg_PUBLIC_INCLUDES}" STREQUAL "")
-
-  if(NOT "${arg_PRIVATE_INCLUDES}" STREQUAL "")
-    target_include_directories("${NAME}" PRIVATE ${arg_PRIVATE_INCLUDES})
-  endif(NOT "${arg_PRIVATE_INCLUDES}" STREQUAL "")
-
+  add_library("${NAME}" EXCLUDE_FROM_ALL ${arg_HEADERS} ${arg_SOURCES})
+  target_include_directories("${NAME}" PUBLIC public)
   target_link_libraries("${NAME}"
-    ${public_or_interface}
+    PUBLIC
       pw_build
       ${arg_PUBLIC_DEPS}
+    PRIVATE
+      pw_build.strict_warnings
+      pw_build.extra_strict_warnings
+      ${arg_PRIVATE_DEPS}
   )
 
-  if(NOT "${arg_SOURCES}" STREQUAL "")
-    target_link_libraries("${NAME}"
-      PRIVATE
-        pw_build.warnings
-        ${arg_PRIVATE_DEPS}
-    )
-  endif(NOT "${arg_SOURCES}" STREQUAL "")
-
   if(NOT "${arg_IMPLEMENTS_FACADES}" STREQUAL "")
-    target_include_directories("${NAME}"
-      ${public_or_interface}
-        public_overrides
-    )
-    if("${arg_PUBLIC_INCLUDES}" STREQUAL "")
-      # TODO(pwbug/601): Deprecate this legacy implicit PUBLIC_INCLUDES.
-      target_include_directories("${NAME}"
-        ${public_or_interface}
-          public_overrides
-      )
-    endif("${arg_PUBLIC_INCLUDES}" STREQUAL "")
+    target_include_directories("${NAME}" PUBLIC public_overrides)
     set(facades ${arg_IMPLEMENTS_FACADES})
     list(TRANSFORM facades APPEND ".facade")
-    target_link_libraries("${NAME}" ${public_or_interface} ${facades})
-  endif(NOT "${arg_IMPLEMENTS_FACADES}" STREQUAL "")
+    target_link_libraries("${NAME}" PUBLIC ${facades})
+  endif()
 
-  if(NOT "${arg_PUBLIC_DEFINES}" STREQUAL "")
-    target_compile_definitions("${NAME}"
-      ${public_or_interface}
-        ${arg_PUBLIC_DEFINES}
-    )
-  endif(NOT "${arg_PUBLIC_DEFINES}" STREQUAL "")
-
-  if(NOT "${arg_PRIVATE_DEFINES}" STREQUAL "")
-    target_compile_definitions("${NAME}" PRIVATE ${arg_PRIVATE_DEFINES})
-  endif(NOT "${arg_PRIVATE_DEFINES}" STREQUAL "")
-
-  if(NOT "${arg_PUBLIC_COMPILE_OPTIONS}" STREQUAL "")
-    target_compile_options("${NAME}"
-      ${public_or_interface}
-        ${arg_PUBLIC_COMPILE_OPTIONS}
-    )
-  endif(NOT "${arg_PUBLIC_COMPILE_OPTIONS}" STREQUAL "")
-
-  if(NOT "${arg_PRIVATE_COMPILE_OPTIONS}" STREQUAL "")
-    target_compile_options("${NAME}" PRIVATE ${arg_PRIVATE_COMPILE_OPTIONS})
-  endif(NOT "${arg_PRIVATE_COMPILE_OPTIONS}" STREQUAL "")
-
-  if(NOT "${arg_PUBLIC_LINK_OPTIONS}" STREQUAL "")
-    target_link_options("${NAME}"
-      ${public_or_interface}
-        ${arg_PUBLIC_LINK_OPTIONS}
-    )
-  endif(NOT "${arg_PUBLIC_LINK_OPTIONS}" STREQUAL "")
-
-  if(NOT "${arg_PRIVATE_LINK_OPTIONS}" STREQUAL "")
-    target_link_options("${NAME}" PRIVATE ${arg_PRIVATE_LINK_OPTIONS})
-  endif(NOT "${arg_PRIVATE_LINK_OPTIONS}" STREQUAL "")
+  # Libraries require at least one source file.
+  if(NOT arg_SOURCES)
+    target_sources("${NAME}" PRIVATE $<TARGET_PROPERTY:pw_build.empty,SOURCES>)
+  endif()
 endfunction(pw_add_module_library)
 
 # Declares a module as a facade.
@@ -321,12 +219,7 @@
   # instead. If the facade is used in the build, it fails with this error.
   add_custom_target("${NAME}._no_backend_set_message"
     COMMAND
-      "${CMAKE_COMMAND}" -E echo
-        "ERROR: Attempted to build the ${NAME} facade with no backend."
-        "Configure the ${NAME} backend using pw_set_backend or remove all dependencies on it."
-        "See https://pigweed.dev/pw_build."
-    COMMAND
-      "${CMAKE_COMMAND}" -E false
+      python "$ENV{PW_ROOT}/pw_build/py/pw_build/null_backend.py" "${NAME}"
   )
   add_library("${NAME}.NO_BACKEND_SET" INTERFACE)
   add_dependencies("${NAME}.NO_BACKEND_SET" "${NAME}._no_backend_set_message")
@@ -340,14 +233,6 @@
   set("${NAME}_BACKEND" "${arg_DEFAULT_BACKEND}" CACHE STRING
       "Backend for ${NAME}")
 
-  # This target is never used; it simply tests that the specified backend
-  # actually exists in the build. The generator expression will fail to evaluate
-  # if the target is not defined.
-  add_custom_target(_pw_check_that_backend_for_${NAME}_is_defined
-    COMMAND
-      ${CMAKE_COMMAND} -E echo "$<TARGET_PROPERTY:${${NAME}_BACKEND},TYPE>"
-  )
-
   # Define the facade library, which is used by the backend to avoid circular
   # dependencies.
   add_library("${NAME}.facade" INTERFACE)
@@ -372,43 +257,6 @@
   set("${FACADE}_BACKEND" "${BACKEND}" CACHE STRING "Backend for ${NAME}" FORCE)
 endfunction(pw_set_backend)
 
-# Set up the default pw_build_DEFAULT_MODULE_CONFIG.
-set("pw_build_DEFAULT_MODULE_CONFIG" pw_build.empty CACHE STRING
-    "Default implementation for all Pigweed module configurations.")
-
-# Declares a module configuration variable for module libraries to depend on.
-# Configs should be set to libraries which can be used to provide defines
-# directly or though included header files.
-#
-# The configs can be selected either through the pw_set_module_config function
-# to set the pw_build_DEFAULT_MODULE_CONFIG used by default for all Pigweed
-# modules or by selecting a specific one for the given NAME'd configuration.
-#
-# Args:
-#
-#   NAME: name to use for the target which can be depended on for the config.
-function(pw_add_module_config NAME)
-  _pw_parse_argv_strict(pw_add_module_config 1 "" "" "")
-
-  # Declare the module configuration variable for this module.
-  set("${NAME}" "${pw_build_DEFAULT_MODULE_CONFIG}"
-      CACHE STRING "Module configuration for ${NAME}")
-endfunction(pw_add_module_config)
-
-# Sets which config library to use for the given module.
-#
-# This can be used to set a specific module configuration or the default
-# module configuration used for all Pigweed modules:
-#
-#   pw_set_module_config(pw_build_DEFAULT_MODULE_CONFIG my_config)
-#   pw_set_module_config(pw_foo_CONFIG my_foo_config)
-function(pw_set_module_config NAME LIBRARY)
-  _pw_parse_argv_strict(pw_set_module_config 2 "" "" "")
-
-  # Update the module configuration variable.
-  set("${NAME}" "${LIBRARY}" CACHE STRING "Config for ${NAME}" FORCE)
-endfunction(pw_set_module_config)
-
 # Declares a unit test. Creates two targets:
 #
 #  * <TEST_NAME> - the test executable
@@ -432,10 +280,6 @@
       pw_unit_test.main
       ${arg_DEPS}
   )
-  # Tests require at least one source file.
-  if(NOT arg_SOURCES)
-    target_sources("${NAME}" PRIVATE $<TARGET_PROPERTY:pw_build.empty,SOURCES>)
-  endif()
 
   # Define a target for running the test. The target creates a stamp file to
   # indicate successful test completion. This allows running tests in parallel
diff --git a/pw_build/pigweed_toolchain_upstream.bzl b/pw_build/pigweed_toolchain_upstream.bzl
index a77cce6..4b7767a 100644
--- a/pw_build/pigweed_toolchain_upstream.bzl
+++ b/pw_build/pigweed_toolchain_upstream.bzl
@@ -11,7 +11,6 @@
 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 # License for the specific language governing permissions and limitations under
 # the License.
-"""Implements the set of dependencies that bazel-embedded requires."""
 
 def _toolchain_upstream_repository_impl(rctx):
     """Creates a remote repository with a set of toolchain components.
diff --git a/pw_build/platforms/BUILD b/pw_build/platforms/BUILD
new file mode 100644
index 0000000..a19a462
--- /dev/null
+++ b/pw_build/platforms/BUILD
@@ -0,0 +1,70 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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(default_visibility = ["//visibility:public"])
+
+# --- CPU's ---
+alias(
+    name = "cortex_m0",
+    actual = "@bazel_embedded//platforms:cortex_m0",
+)
+
+alias(
+    name = "cortex_m1",
+    actual = "@bazel_embedded//platforms:cortex_m1",
+)
+
+alias(
+    name = "cortex_m3",
+    actual = "@bazel_embedded//platforms:cortex_m3",
+)
+
+alias(
+    name = "cortex_m4",
+    actual = "@bazel_embedded//platforms:cortex_m4",
+)
+
+alias(
+    name = "cortex_m4_fpu",
+    actual = "@bazel_embedded//platforms:cortex_m4",
+)
+
+alias(
+    name = "cortex_m7",
+    actual = "@bazel_embedded//platforms:cortex_m7",
+)
+
+alias(
+    name = "cortex_m7_fpu",
+    actual = "@bazel_embedded//platforms:cortex_m7_fpu",
+)
+
+# --- Chipsets ---
+platform(
+    name = "stm32f429",
+    constraint_values = ["//pw_build/constraints/chipset:stm32f429"],
+    parents = [":cortex_m4"],
+)
+
+platform(
+    name = "lm3s6965evb",
+    constraint_values = ["//pw_build/constraints/chipset:lm3s6965evb"],
+    parents = [":cortex_m3"],
+)
+
+# --- Boards ---
+platform(
+    name = "stm32f429i-disc1",
+    constraint_values = ["//pw_build/constraints/board:stm32f429i-disc1"],
+    parents = [":stm32f429"],
+)
diff --git a/pw_build/platforms/BUILD.bazel b/pw_build/platforms/BUILD.bazel
deleted file mode 100644
index 7bbc6a3..0000000
--- a/pw_build/platforms/BUILD.bazel
+++ /dev/null
@@ -1,107 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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(default_visibility = ["//visibility:public"])
-
-# --- OS's ---
-platform(
-    name = "freertos",
-    constraint_values = [
-        "//pw_build/constraints/rtos:freertos",
-        "@platforms//os:none",
-    ],
-)
-
-platform(
-    name = "embos",
-    constraint_values = [
-        "//pw_build/constraints/rtos:embos",
-        "@platforms//os:none",
-    ],
-)
-
-platform(
-    name = "threadx",
-    constraint_values = [
-        "//pw_build/constraints/rtos:threadx",
-        "@platforms//os:none",
-    ],
-)
-
-platform(
-    name = "none",
-    constraint_values = ["@platforms//os:none"],
-)
-
-# --- CPU's ---
-platform(
-    name = "cortex_m0",
-    constraint_values = ["@pigweed_config//:target_rtos"],
-    parents = ["@bazel_embedded//platforms:cortex_m0"],
-)
-
-platform(
-    name = "cortex_m1",
-    constraint_values = ["@pigweed_config//:target_rtos"],
-    parents = ["@bazel_embedded//platforms:cortex_m1"],
-)
-
-platform(
-    name = "cortex_m3",
-    constraint_values = ["@pigweed_config//:target_rtos"],
-    parents = ["@bazel_embedded//platforms:cortex_m3"],
-)
-
-platform(
-    name = "cortex_m4",
-    constraint_values = ["@pigweed_config//:target_rtos"],
-    parents = ["@bazel_embedded//platforms:cortex_m4"],
-)
-
-platform(
-    name = "cortex_m4_fpu",
-    constraint_values = ["@pigweed_config//:target_rtos"],
-    parents = ["@bazel_embedded//platforms:cortex_m4"],
-)
-
-platform(
-    name = "cortex_m7",
-    constraint_values = ["@pigweed_config//:target_rtos"],
-    parents = ["@bazel_embedded//platforms:cortex_m7"],
-)
-
-platform(
-    name = "cortex_m7_fpu",
-    constraint_values = ["@pigweed_config//:target_rtos"],
-    parents = ["@bazel_embedded//platforms:cortex_m7_fpu"],
-)
-
-# --- Chipsets ---
-platform(
-    name = "stm32f429",
-    constraint_values = ["//pw_build/constraints/chipset:stm32f429"],
-    parents = [":cortex_m4"],
-)
-
-platform(
-    name = "lm3s6965evb",
-    constraint_values = ["//pw_build/constraints/chipset:lm3s6965evb"],
-    parents = [":cortex_m3"],
-)
-
-# --- Boards ---
-platform(
-    name = "stm32f429i-disc1",
-    constraint_values = ["//pw_build/constraints/board:stm32f429i-disc1"],
-    parents = [":stm32f429"],
-)
diff --git a/pw_build/pool/BUILD.gn b/pw_build/pool/BUILD.gn
deleted file mode 100644
index fa34417..0000000
--- a/pw_build/pool/BUILD.gn
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-# Pools are used to limit concurrency; we don't want multiple instances.
-assert(current_toolchain == default_toolchain)
-
-# Pool to serialize python package installs.
-pool("pip") {
-  depth = 1
-}
-
-pool("copy_from_cipd") {
-  depth = 1
-}
-
-# Pool to limit a single thread to download external Go packages at a time.
-pool("go_download") {
-  depth = 1
-}
-
-# Pool to serialize HIL test execution.
-pool("pw_hil_test") {
-  depth = 1
-}
diff --git a/pw_build/py/BUILD.bazel b/pw_build/py/BUILD.bazel
deleted file mode 100644
index 5abf4e7..0000000
--- a/pw_build/py/BUILD.bazel
+++ /dev/null
@@ -1,30 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load("@rules_python//python:defs.bzl", "py_library")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-py_library(
-    name = "pw_build",
-    srcs = [
-        "pw_build/__init__.py",
-        # TODO(tonymd): Add more sources when needed.
-        # Used by pw_hdlc/py/decode_test.py:
-        "pw_build/generated_tests.py",
-    ],
-    imports = ["."],
-)
diff --git a/pw_build/py/BUILD.gn b/pw_build/py/BUILD.gn
index 5341a92..ea96dc4 100644
--- a/pw_build/py/BUILD.gn
+++ b/pw_build/py/BUILD.gn
@@ -17,36 +17,23 @@
 import("$dir_pw_build/python.gni")
 
 pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
+  setup = [ "setup.py" ]
   sources = [
     "pw_build/__init__.py",
-    "pw_build/collect_wheels.py",
     "pw_build/copy_from_cipd.py",
-    "pw_build/create_python_tree.py",
     "pw_build/error.py",
     "pw_build/exec.py",
-    "pw_build/file_prefix_map.py",
-    "pw_build/generate_cc_blob_library.py",
-    "pw_build/generate_modules_lists.py",
     "pw_build/generate_python_package.py",
     "pw_build/generate_python_package_gn.py",
     "pw_build/generated_tests.py",
     "pw_build/host_tool.py",
     "pw_build/mirror_tree.py",
     "pw_build/nop.py",
-    "pw_build/python_package.py",
     "pw_build/python_runner.py",
     "pw_build/python_wheels.py",
     "pw_build/zip.py",
   ]
   tests = [
-    "create_python_tree_test.py",
-    "file_prefix_map_test.py",
-    "generate_cc_blob_library_test.py",
     "python_runner_test.py",
     "zip_test.py",
   ]
diff --git a/pw_build/py/create_python_tree_test.py b/pw_build/py/create_python_tree_test.py
deleted file mode 100644
index 0f221f4..0000000
--- a/pw_build/py/create_python_tree_test.py
+++ /dev/null
@@ -1,317 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests for pw_build.create_python_tree"""
-
-import os
-from pathlib import Path
-import tempfile
-from typing import List
-import unittest
-
-from parameterized import parameterized  # type: ignore
-
-from pw_build.python_package import PythonPackage
-from pw_build.create_python_tree import build_python_tree, copy_extra_files
-from pw_build.generate_python_package import _PYPROJECT_FILE as PYPROJECT_TEXT
-
-
-def _setup_cfg(package_name: str) -> str:
-    return f'''
-[metadata]
-name = {package_name}
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Pigweed swiss-army knife
-
-[options]
-packages = find:
-zip_safe = False
-
-[options.package_data]
-{package_name} =
-    py.typed
-    '''
-
-
-def _create_fake_python_package(location: Path, files: List[str],
-                                package_name: str) -> None:
-    for file in files:
-        destination = location / file
-        destination.parent.mkdir(parents=True, exist_ok=True)
-        text = f'"""{package_name}"""'
-        if str(destination).endswith('setup.cfg'):
-            text = _setup_cfg(package_name)
-        elif str(destination).endswith('pyproject.toml'):
-            # Make sure pyproject.toml file has valid syntax.
-            text = PYPROJECT_TEXT
-        destination.write_text(text)
-
-
-class TestCreatePythonTree(unittest.TestCase):
-    """Integration tests for create_python_tree."""
-    def setUp(self):
-        self.maxDiff = None  # pylint: disable=invalid-name
-        # Save the starting working directory for returning to later.
-        self.start_dir = Path.cwd()
-        # Create a temp out directory
-        self.temp_dir = tempfile.TemporaryDirectory()
-
-    def tearDown(self):
-        # cd to the starting dir before cleaning up the temp out directory
-        os.chdir(self.start_dir)
-        # Delete the TemporaryDirectory
-        self.temp_dir.cleanup()
-
-    def _check_result_paths_equal(self, install_dir, expected_results) -> None:
-        # Normalize path strings to posix before comparing.
-        expected_paths = set(Path(p).as_posix() for p in expected_results)
-        actual_paths = set(
-            p.relative_to(install_dir).as_posix()
-            for p in install_dir.glob('**/*') if p.is_file())
-        self.assertEqual(expected_paths, actual_paths)
-
-    @parameterized.expand([
-        (
-            # Test name
-            'working case',
-            # Package name
-            'mars',
-            # File list
-            [
-                'planets/BUILD.mars_rocket',
-                'planets/mars/__init__.py',
-                'planets/mars/__main__.py',
-                'planets/mars/moons/__init__.py',
-                'planets/mars/moons/deimos.py',
-                'planets/mars/moons/phobos.py',
-                'planets/hohmann_transfer_test.py',
-                'planets/pyproject.toml',
-                'planets/setup.cfg',
-            ],
-            # Extra_files
-            [],
-            # Package definition
-            {
-                'generate_setup': {
-                    'metadata': {
-                        'name': 'mars',
-                        'version': '0.0.1'
-                    },
-                },
-                'inputs': [
-                ],
-                'setup_sources': [
-                    'planets/pyproject.toml',
-                    'planets/setup.cfg',
-                ],
-                'sources': [
-                    'planets/mars/__init__.py',
-                    'planets/mars/__main__.py',
-                    'planets/mars/moons/__init__.py',
-                    'planets/mars/moons/deimos.py',
-                    'planets/mars/moons/phobos.py',
-                ],
-                'tests': [
-                    'planets/hohmann_transfer_test.py',
-                ],
-            },
-            # Output file list
-            [
-                'mars/__init__.py',
-                'mars/__main__.py',
-                'mars/moons/__init__.py',
-                'mars/moons/deimos.py',
-                'mars/moons/phobos.py',
-                'mars/tests/hohmann_transfer_test.py',
-            ],
-        ),
-
-        (
-            # Test name
-            'with extra files',
-            # Package name
-            'saturn',
-            # File list
-            [
-                'planets/BUILD.saturn_rocket',
-                'planets/hohmann_transfer_test.py',
-                'planets/pyproject.toml',
-                'planets/saturn/__init__.py',
-                'planets/saturn/__main__.py',
-                'planets/saturn/misson.py',
-                'planets/saturn/moons/__init__.py',
-                'planets/saturn/moons/enceladus.py',
-                'planets/saturn/moons/iapetus.py',
-                'planets/saturn/moons/rhea.py',
-                'planets/saturn/moons/titan.py',
-                'planets/setup.cfg',
-                'planets/setup.py',
-            ],
-            # Extra files
-            [
-                'planets/BUILD.saturn_rocket > out/saturn/BUILD.rocket',
-            ],
-            # Package definition
-            {
-                'inputs': [
-                ],
-                'setup_sources': [
-                    'planets/pyproject.toml',
-                    'planets/setup.cfg',
-                    'planets/setup.py',
-                ],
-                'sources': [
-                    'planets/saturn/__init__.py',
-                    'planets/saturn/__main__.py',
-                    'planets/saturn/misson.py',
-                    'planets/saturn/moons/__init__.py',
-                    'planets/saturn/moons/enceladus.py',
-                    'planets/saturn/moons/iapetus.py',
-                    'planets/saturn/moons/rhea.py',
-                    'planets/saturn/moons/titan.py',
-                ],
-                'tests': [
-                    'planets/hohmann_transfer_test.py',
-                ]
-            },
-            # Output file list
-            [
-                'saturn/BUILD.rocket',
-                'saturn/__init__.py',
-                'saturn/__main__.py',
-                'saturn/misson.py',
-                'saturn/moons/__init__.py',
-                'saturn/moons/enceladus.py',
-                'saturn/moons/iapetus.py',
-                'saturn/moons/rhea.py',
-                'saturn/moons/titan.py',
-                'saturn/tests/hohmann_transfer_test.py',
-            ],
-        ),
-    ]) # yapf: disable
-    def test_build_python_tree(
-        self,
-        _test_name,
-        package_name,
-        file_list,
-        extra_files,
-        package_definition,
-        expected_file_list,
-    ) -> None:
-        """Check results of build_python_tree and copy_extra_files."""
-        temp_root = Path(self.temp_dir.name)
-        _create_fake_python_package(temp_root, file_list, package_name)
-
-        os.chdir(temp_root)
-        install_dir = temp_root / 'out'
-
-        package = PythonPackage.from_dict(**package_definition)
-        build_python_tree(python_packages=[package],
-                          tree_destination_dir=install_dir,
-                          include_tests=True)
-        copy_extra_files(extra_files)
-
-        # Check expected files are in place.
-        self._check_result_paths_equal(install_dir, expected_file_list)
-
-    @parameterized.expand([
-        (
-            # Test name
-            'everything in correct locations',
-            # Package name
-            'planets',
-            # File list
-            [
-                'BUILD.mars_rocket',
-            ],
-            # Extra_files
-            [
-                'BUILD.mars_rocket > out/mars/BUILD.rocket',
-            ],
-            # Output file list
-            [
-                'mars/BUILD.rocket',
-            ],
-            # Should raise exception
-            None,
-        ),
-        (
-            # Test name
-            'missing source files',
-            # Package name
-            'planets',
-            # File list
-            [
-                'BUILD.mars_rocket',
-            ],
-            # Extra_files
-            [
-                'BUILD.venus_rocket > out/venus/BUILD.rocket',
-            ],
-            # Output file list
-            [],
-            # Should raise exception
-            FileNotFoundError,
-        ),
-        (
-            # Test name
-            'existing destination files',
-            # Package name
-            'planets',
-            # File list
-            [
-                'BUILD.jupiter_rocket',
-                'out/jupiter/BUILD.rocket',
-            ],
-            # Extra_files
-            [
-                'BUILD.jupiter_rocket > out/jupiter/BUILD.rocket',
-            ],
-            # Output file list
-            [],
-            # Should raise exception
-            FileExistsError,
-        ),
-    ]) # yapf: disable
-    def test_copy_extra_files(
-        self,
-        _test_name,
-        package_name,
-        file_list,
-        extra_files,
-        expected_file_list,
-        should_raise_exception,
-    ) -> None:
-        """Check results of build_python_tree and copy_extra_files."""
-        temp_root = Path(self.temp_dir.name)
-        _create_fake_python_package(temp_root, file_list, package_name)
-
-        os.chdir(temp_root)
-        install_dir = temp_root / 'out'
-
-        # If exceptions should be raised
-        if should_raise_exception:
-            with self.assertRaises(should_raise_exception):
-                copy_extra_files(extra_files)
-            return
-
-        # Do the copy
-        copy_extra_files(extra_files)
-        # Check expected files are in place.
-        self._check_result_paths_equal(install_dir, expected_file_list)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_build/py/file_prefix_map_test.py b/pw_build/py/file_prefix_map_test.py
deleted file mode 100644
index 72630b6..0000000
--- a/pw_build/py/file_prefix_map_test.py
+++ /dev/null
@@ -1,71 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests for the file_prefix_map utility"""
-
-from io import StringIO
-import json
-import unittest
-
-from pw_build import file_prefix_map
-
-# pylint: disable=line-too-long
-JSON_SOURCE_FILES = json.dumps([
-    "../pigweed/pw_polyfill/standard_library_public/pw_polyfill/standard_library/assert.h",
-    "protocol_buffer/gen/pigweed/pw_protobuf/common_protos.proto_library/nanopb/pw_protobuf_protos/status.pb.h",
-    "../pigweed/pw_rpc/client_server.cc",
-    "../pigweed/pw_rpc/public/pw_rpc/client_server.h",
-    "/home/user/pigweed/out/../gen/generated_build_info.cc",
-    "/home/user/pigweed/pw_protobuf/encoder.cc",
-])
-
-JSON_PATH_TRANSFORMATIONS = json.dumps([
-    "/home/user/pigweed/out=out",
-    "/home/user/pigweed/=",
-    "../=",
-    "/home/user/pigweed/out=out",
-])
-
-EXPECTED_TRANSFORMED_PATHS = json.dumps([
-    "pigweed/pw_polyfill/standard_library_public/pw_polyfill/standard_library/assert.h",
-    "protocol_buffer/gen/pigweed/pw_protobuf/common_protos.proto_library/nanopb/pw_protobuf_protos/status.pb.h",
-    "pigweed/pw_rpc/client_server.cc",
-    "pigweed/pw_rpc/public/pw_rpc/client_server.h",
-    "out/../gen/generated_build_info.cc",
-    "pw_protobuf/encoder.cc",
-])
-
-
-class FilePrefixMapTest(unittest.TestCase):
-    def test_prefix_remap(self):
-        path_list = [
-            '/foo_root/root_subdir/source.cc',
-            '/foo_root/root_subdir/out/../gen.cc'
-        ]
-        prefix_maps = [('/foo_root/root_subdir/', ''), ('out/../', 'out/')]
-        expected_paths = ['source.cc', 'out/gen.cc']
-        self.assertEqual(
-            list(file_prefix_map.remap_paths(path_list, prefix_maps)),
-            expected_paths)
-
-    def test_json_prefix_map(self):
-        in_fd = StringIO(JSON_SOURCE_FILES)
-        prefix_map_fd = StringIO(JSON_PATH_TRANSFORMATIONS)
-        out_fd = StringIO()
-        file_prefix_map.remap_json_paths(in_fd, out_fd, prefix_map_fd)
-        self.assertEqual(json.loads(out_fd.getvalue()),
-                         json.loads(EXPECTED_TRANSFORMED_PATHS))
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_build/py/generate_cc_blob_library_test.py b/pw_build/py/generate_cc_blob_library_test.py
deleted file mode 100644
index 2185f85..0000000
--- a/pw_build/py/generate_cc_blob_library_test.py
+++ /dev/null
@@ -1,256 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Unit tests for generate_cc_blob_library.py"""
-
-from pathlib import Path
-import tempfile
-import textwrap
-import unittest
-
-from pw_build import generate_cc_blob_library
-
-
-class TestSplitIntoChunks(unittest.TestCase):
-    """Unit tests for the split_into_chunks() function."""
-    def test_perfect_split(self):
-        """Tests basic splitting where the iterable divides perfectly."""
-        data = (1, 7, 0, 1)
-        self.assertEqual(
-            ((1, 7), (0, 1)),
-            tuple(generate_cc_blob_library.split_into_chunks(data, 2)))
-
-    def test_split_with_remainder(self):
-        """Tests basic splitting where there is a remainder."""
-        data = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
-        self.assertEqual(
-            ((1, 2, 3), (4, 5, 6), (7, 8, 9), (10, )),
-            tuple(generate_cc_blob_library.split_into_chunks(data, 3)))
-
-
-class TestHeaderFromBlobs(unittest.TestCase):
-    """Unit tests for the header_from_blobs() function."""
-    def test_single_blob_header(self):
-        """Tests the generation of a header for a single blob."""
-        foo_blob = Path(tempfile.NamedTemporaryFile().name)
-        foo_blob.write_bytes(bytes((1, 2, 3, 4, 5, 6)))
-        blobs = [generate_cc_blob_library.Blob('fooBlob', foo_blob, None)]
-
-        header = generate_cc_blob_library.header_from_blobs(blobs)
-        expected_header = textwrap.dedent("""\
-            // This file is auto-generated; Do not hand-modify!
-            // See https://pigweed.dev/pw_build/#pw-cc-blob-library for details.
-
-            #pragma once
-
-            #include <array>
-            #include <cstddef>
-
-            extern const std::array<std::byte, 6> fooBlob;
-            """)
-
-        self.assertEqual(expected_header, header)
-
-    def test_multi_blob_header(self):
-        """Tests the generation of a header for multiple blobs."""
-        foo_blob = Path(tempfile.NamedTemporaryFile().name)
-        foo_blob.write_bytes(bytes((1, 2, 3, 4, 5, 6)))
-        bar_blob = Path(tempfile.NamedTemporaryFile().name)
-        bar_blob.write_bytes(bytes((10, 9, 8, 7, 6)))
-        blobs = [
-            generate_cc_blob_library.Blob('fooBlob', foo_blob, None),
-            generate_cc_blob_library.Blob('barBlob', bar_blob, None),
-        ]
-
-        header = generate_cc_blob_library.header_from_blobs(blobs)
-        expected_header = textwrap.dedent("""\
-            // This file is auto-generated; Do not hand-modify!
-            // See https://pigweed.dev/pw_build/#pw-cc-blob-library for details.
-
-            #pragma once
-
-            #include <array>
-            #include <cstddef>
-
-            extern const std::array<std::byte, 6> fooBlob;
-
-            extern const std::array<std::byte, 5> barBlob;
-            """)
-
-        self.assertEqual(expected_header, header)
-
-    def test_header_with_namespace(self):
-        """Tests the header generation of namespace definitions."""
-        foo_blob = Path(tempfile.NamedTemporaryFile().name)
-        foo_blob.write_bytes(bytes((1, 2, 3, 4, 5, 6)))
-        blobs = [generate_cc_blob_library.Blob('fooBlob', foo_blob, None)]
-
-        header = generate_cc_blob_library.header_from_blobs(blobs, 'pw::foo')
-        expected_header = textwrap.dedent("""\
-            // This file is auto-generated; Do not hand-modify!
-            // See https://pigweed.dev/pw_build/#pw-cc-blob-library for details.
-
-            #pragma once
-
-            #include <array>
-            #include <cstddef>
-
-            namespace pw::foo {
-
-            extern const std::array<std::byte, 6> fooBlob;
-
-            }  // namespace pw::foo
-            """)
-
-        self.assertEqual(expected_header, header)
-
-
-class TestArrayDefFromBlobData(unittest.TestCase):
-    """Unit tests for the array_def_from_blob_data() function."""
-    def test_single_line(self):
-        """Tests the generation of single-line array definitions."""
-        foo_data = bytes((1, 2))
-
-        foo_definition = generate_cc_blob_library.array_def_from_blob_data(
-            'fooBlob', foo_data)
-        expected_definition = ('const std::array<std::byte, 2> fooBlob'
-                               ' = { std::byte{0x01}, std::byte{0x02} };')
-
-        self.assertEqual(expected_definition, foo_definition)
-
-    def test_multi_line(self):
-        """Tests the generation of multi-line array definitions."""
-        foo_data = bytes((1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
-
-        foo_definition = generate_cc_blob_library.array_def_from_blob_data(
-            'fooBlob', foo_data)
-        expected_definition = ('const std::array<std::byte, 10> fooBlob = {\n'
-                               '  std::byte{0x01}, std::byte{0x02}, '
-                               'std::byte{0x03}, std::byte{0x04},\n'
-                               '  std::byte{0x05}, std::byte{0x06}, '
-                               'std::byte{0x07}, std::byte{0x08},\n'
-                               '  std::byte{0x09}, std::byte{0x0A}\n'
-                               '};')
-
-        self.assertEqual(expected_definition, foo_definition)
-
-
-class TestSourceFromBlobs(unittest.TestCase):
-    """Unit tests for the source_from_blobs() function."""
-    def test_source_with_mixed_blobs(self):
-        """Tests generation of a source file with single- and multi-liners."""
-        foo_blob = Path(tempfile.NamedTemporaryFile().name)
-        foo_blob.write_bytes(bytes((1, 2)))
-        bar_blob = Path(tempfile.NamedTemporaryFile().name)
-        bar_blob.write_bytes(bytes((1, 2, 3, 4, 5, 6, 7, 8, 9, 10)))
-        blobs = [
-            generate_cc_blob_library.Blob('fooBlob', foo_blob, None),
-            generate_cc_blob_library.Blob('barBlob', bar_blob, None),
-        ]
-
-        source = generate_cc_blob_library.source_from_blobs(
-            blobs, 'path/to/header.h')
-        expected_source = textwrap.dedent("""\
-            // This file is auto-generated; Do not hand-modify!
-            // See https://pigweed.dev/pw_build/#pw-cc-blob-library for details.
-
-            #include "path/to/header.h"
-
-            #include <array>
-            #include <cstddef>
-
-            #include "pw_preprocessor/compiler.h"
-
-            """)
-        expected_source += ('const std::array<std::byte, 2> fooBlob'
-                            ' = { std::byte{0x01}, std::byte{0x02} };\n\n')
-        expected_source += ('const std::array<std::byte, 10> barBlob = {\n'
-                            '  std::byte{0x01}, std::byte{0x02}, '
-                            'std::byte{0x03}, std::byte{0x04},\n'
-                            '  std::byte{0x05}, std::byte{0x06}, '
-                            'std::byte{0x07}, std::byte{0x08},\n'
-                            '  std::byte{0x09}, std::byte{0x0A}\n'
-                            '};\n')
-
-        self.assertEqual(expected_source, source)
-
-    def test_source_with_namespace(self):
-        """Tests the source generation of namespace definitions."""
-        foo_blob = Path(tempfile.NamedTemporaryFile().name)
-        foo_blob.write_bytes(bytes((1, 2)))
-        blobs = [generate_cc_blob_library.Blob('fooBlob', foo_blob, None)]
-
-        source = generate_cc_blob_library.source_from_blobs(
-            blobs, 'path/to/header.h', 'pw::foo')
-        expected_source = textwrap.dedent("""\
-            // This file is auto-generated; Do not hand-modify!
-            // See https://pigweed.dev/pw_build/#pw-cc-blob-library for details.
-
-            #include "path/to/header.h"
-
-            #include <array>
-            #include <cstddef>
-
-            #include "pw_preprocessor/compiler.h"
-
-            namespace pw::foo {
-
-            const std::array<std::byte, 2> fooBlob = { std::byte{0x01}, std::byte{0x02} };
-
-            }  // namespace pw::foo
-            """)
-
-        self.assertEqual(expected_source, source)
-
-    def test_source_with_linker_sections(self):
-        """Tests generation of a source file with defined linker sections"""
-        foo_blob = Path(tempfile.NamedTemporaryFile().name)
-        foo_blob.write_bytes(bytes((1, 2)))
-        bar_blob = Path(tempfile.NamedTemporaryFile().name)
-        bar_blob.write_bytes(bytes((1, 2, 3, 4, 5, 6, 7, 8, 9, 10)))
-        blobs = [
-            generate_cc_blob_library.Blob('fooBlob', foo_blob, ".foo_section"),
-            generate_cc_blob_library.Blob('barBlob', bar_blob, ".bar_section"),
-        ]
-
-        source = generate_cc_blob_library.source_from_blobs(
-            blobs, 'path/to/header.h')
-        expected_source = textwrap.dedent("""\
-            // This file is auto-generated; Do not hand-modify!
-            // See https://pigweed.dev/pw_build/#pw-cc-blob-library for details.
-
-            #include "path/to/header.h"
-
-            #include <array>
-            #include <cstddef>
-
-            #include "pw_preprocessor/compiler.h"
-
-            """)
-        expected_source += ('PW_PLACE_IN_SECTION(".foo_section")\n'
-                            'const std::array<std::byte, 2> fooBlob'
-                            ' = { std::byte{0x01}, std::byte{0x02} };\n\n')
-        expected_source += ('PW_PLACE_IN_SECTION(".bar_section")\n'
-                            'const std::array<std::byte, 10> barBlob = {\n'
-                            '  std::byte{0x01}, std::byte{0x02}, '
-                            'std::byte{0x03}, std::byte{0x04},\n'
-                            '  std::byte{0x05}, std::byte{0x06}, '
-                            'std::byte{0x07}, std::byte{0x08},\n'
-                            '  std::byte{0x09}, std::byte{0x0A}\n'
-                            '};\n')
-
-        self.assertEqual(expected_source, source)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_build/py/pw_build/collect_wheels.py b/pw_build/py/pw_build/collect_wheels.py
deleted file mode 100644
index ac53c28..0000000
--- a/pw_build/py/pw_build/collect_wheels.py
+++ /dev/null
@@ -1,65 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Collect Python wheels from a build into a central directory."""
-
-import argparse
-import logging
-from pathlib import Path
-import shutil
-import sys
-
-_LOG = logging.getLogger(__name__)
-
-
-def _parse_args():
-    parser = argparse.ArgumentParser(description=__doc__)
-    parser.add_argument(
-        '--prefix',
-        type=Path,
-        help='Root search path to use in conjunction with --wheels_file')
-    parser.add_argument(
-        '--suffix_file',
-        type=argparse.FileType('r'),
-        help=('File that lists subdirs relative to --prefix, one per line,'
-              'to search for .whl files to copy into --out_dir'))
-    parser.add_argument(
-        '--out_dir',
-        type=Path,
-        help='Path where all the built and collected .whl files should be put')
-
-    return parser.parse_args()
-
-
-def copy_wheels(prefix, suffix_file, out_dir):
-    if not out_dir.exists():
-        out_dir.mkdir()
-
-    for suffix in suffix_file.readlines():
-        path = prefix / suffix.strip()
-        _LOG.debug('Searching for wheels in %s', path)
-        if path == out_dir:
-            continue
-        for wheel in path.glob('**/*.whl'):
-            _LOG.debug('Copying %s to %s', wheel, out_dir)
-            shutil.copy(wheel, out_dir)
-
-
-def main():
-    copy_wheels(**vars(_parse_args()))
-
-
-if __name__ == '__main__':
-    logging.basicConfig()
-    main()
-    sys.exit(0)
diff --git a/pw_build/py/pw_build/copy_from_cipd.py b/pw_build/py/pw_build/copy_from_cipd.py
index 82e393c..63921ee 100755
--- a/pw_build/py/pw_build/copy_from_cipd.py
+++ b/pw_build/py/pw_build/copy_from_cipd.py
@@ -93,9 +93,6 @@
 
     with open(manifest, 'r') as ins:
         data = json.load(ins)
-    # TODO(pwbug/599) Always assume this is a dict.
-    if isinstance(data, dict):
-        data = data['packages']
 
     path = None
     expected_version = None
diff --git a/pw_build/py/pw_build/create_python_tree.py b/pw_build/py/pw_build/create_python_tree.py
deleted file mode 100644
index 90ddee5..0000000
--- a/pw_build/py/pw_build/create_python_tree.py
+++ /dev/null
@@ -1,282 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Build a Python Source tree."""
-
-import argparse
-import configparser
-from datetime import datetime
-import io
-from pathlib import Path
-import re
-import shutil
-import subprocess
-import tempfile
-from typing import Iterable
-
-from pw_build.python_package import PythonPackage, load_packages
-
-
-def _parse_args():
-    parser = argparse.ArgumentParser(description=__doc__)
-    parser.add_argument('--tree-destination-dir',
-                        type=Path,
-                        help='Path to output directory.')
-    parser.add_argument('--include-tests',
-                        action='store_true',
-                        help='Include tests in the tests dir.')
-
-    parser.add_argument('--setupcfg-common-file',
-                        type=Path,
-                        help='A file containing the common set of options for'
-                        'incluing in the merged setup.cfg provided version.')
-    parser.add_argument('--setupcfg-version-append-git-sha',
-                        action='store_true',
-                        help='Append the current git SHA to the setup.cfg '
-                        'version.')
-    parser.add_argument('--setupcfg-version-append-date',
-                        action='store_true',
-                        help='Append the current date to the setup.cfg '
-                        'version.')
-
-    parser.add_argument(
-        '--extra-files',
-        nargs='+',
-        help='Paths to extra files that should be included in the output dir.')
-
-    parser.add_argument(
-        '--input-list-files',
-        nargs='+',
-        type=Path,
-        help='Paths to text files containing lists of Python package metadata '
-        'json files.')
-
-    return parser.parse_args()
-
-
-class UnknownGitSha(Exception):
-    "Exception thrown when the current git SHA cannot be found."
-
-
-def get_current_git_sha() -> str:
-    git_command = 'git log -1 --pretty=format:%h'
-    process = subprocess.run(git_command.split(),
-                             stdout=subprocess.PIPE,
-                             stderr=subprocess.STDOUT)
-    gitsha = process.stdout.decode()
-    if process.returncode != 0 or not gitsha:
-        error_output = f'\n"{git_command}" failed with:' f'\n{gitsha}'
-        if process.stderr:
-            error_output += f'\n{process.stderr.decode()}'
-        raise UnknownGitSha('Could not determine the current git SHA.' +
-                            error_output)
-    return gitsha.strip()
-
-
-def get_current_date() -> str:
-    return datetime.now().strftime('%Y%m%d%H%M')
-
-
-class UnexpectedConfigSection(Exception):
-    "Exception thrown when the common configparser contains unexpected values."
-
-
-def load_common_config(common_config: Path,
-                       append_git_sha: bool = False,
-                       append_date: bool = False) -> configparser.ConfigParser:
-    """Load an existing ConfigParser file and update metadata.version."""
-    config = configparser.ConfigParser()
-    config.read(common_config)
-
-    # Check for existing values that should not be present
-    if config.has_option('options', 'packages'):
-        value = str(config['options']['packages'])
-        raise UnexpectedConfigSection(
-            f'[options] packages already defined as: {value}')
-
-    if config.has_section('options.package_data'):
-        raise UnexpectedConfigSection(
-            '[options.package_data] already defined as:\n' +
-            str(dict(config['options.package_data'].items())))
-
-    if config.has_section('options.entry_points'):
-        raise UnexpectedConfigSection(
-            '[options.entry_points] already defined as:\n' +
-            str(dict(config['options.entry_points'].items())))
-
-    # Metadata and option sections should already be defined.
-    assert config.has_section('metadata')
-    assert config.has_section('options')
-
-    # Append build metadata if applicable.
-    build_metadata = []
-    if append_date:
-        build_metadata.append(get_current_date())
-    if append_git_sha:
-        build_metadata.append(get_current_git_sha())
-    if build_metadata:
-        version_prefix = config['metadata']['version']
-        build_metadata_text = '.'.join(build_metadata)
-        config['metadata']['version'] = (
-            f'{version_prefix}+{build_metadata_text}')
-    return config
-
-
-def update_config_with_packages(
-    config: configparser.ConfigParser,
-    python_packages: Iterable[PythonPackage],
-) -> None:
-    """Merge setup.cfg files from a set of python packages."""
-    config['options']['packages'] = 'find:'
-    config['options.package_data'] = {}
-    config['options.entry_points'] = {}
-
-    # Save a list of packages being bundled.
-    included_packages = [pkg.package_name for pkg in python_packages]
-
-    for pkg in python_packages:
-        assert pkg.config
-
-        # Collect install_requires
-        if pkg.config.has_option('options', 'install_requires'):
-            existing_requires = config['options'].get('install_requires', '\n')
-            # Requires are delimited by newlines or semicolons.
-            # Split existing list on either one.
-            this_requires = re.split(r' *[\n;] *',
-                                     pkg.config['options']['install_requires'])
-            new_requires = existing_requires.splitlines() + this_requires
-            # Remove requires already included in this merged config.
-            new_requires = [
-                line for line in new_requires
-                if line and line not in included_packages
-            ]
-            # Remove duplictes and sort require list.
-            new_requires_text = '\n' + '\n'.join(sorted(set(new_requires)))
-            config['options']['install_requires'] = new_requires_text
-
-        # Collect package_data
-        if pkg.config.has_section('options.package_data'):
-            for key, value in pkg.config['options.package_data'].items():
-                config['options.package_data'][key] = value
-
-        # Collect entry_points
-        if pkg.config.has_section('options.entry_points'):
-            for key, value in pkg.config['options.entry_points'].items():
-                existing_entry_points = config['options.entry_points'].get(
-                    key, '')
-                new_entry_points = '\n'.join([existing_entry_points, value])
-                # Remove any empty lines
-                new_entry_points = new_entry_points.replace('\n\n', '\n')
-                config['options.entry_points'][key] = new_entry_points
-
-
-def write_config(
-    common_config: Path,
-    final_config: configparser.ConfigParser,
-    tree_destination_dir: Path,
-) -> None:
-    """Write a the final setup.cfg file with license comment block."""
-    # Get the license comment block from the common_config.
-    comment_block_text = ''
-    comment_block_match = re.search(r'((^#.*?[\r\n])*)([^#])',
-                                    common_config.read_text(), re.MULTILINE)
-    if comment_block_match:
-        comment_block_text = comment_block_match.group(1)
-
-    setup_cfg_file = tree_destination_dir.resolve() / 'setup.cfg'
-    setup_cfg_text = io.StringIO()
-    final_config.write(setup_cfg_text)
-    setup_cfg_file.write_text(comment_block_text + setup_cfg_text.getvalue())
-
-
-def build_python_tree(python_packages: Iterable[PythonPackage],
-                      tree_destination_dir: Path,
-                      include_tests: bool = False) -> None:
-    """Install PythonPackages to a destination directory."""
-
-    # Create the root destination directory.
-    destination_path = tree_destination_dir.resolve()
-    # Delete any existing files
-    shutil.rmtree(destination_path, ignore_errors=True)
-    destination_path.mkdir(exist_ok=True)
-
-    # Define a temporary location to run setup.py build in.
-    with tempfile.TemporaryDirectory() as build_base_name:
-        build_base = Path(build_base_name)
-
-        for pkg in python_packages:
-            lib_dir_path = pkg.setuptools_build_with_base(
-                build_base, include_tests=include_tests)
-
-            # Move installed files from the temp build-base into
-            # destination_path.
-            for new_file in lib_dir_path.glob('*'):
-                # Use str(Path) since shutil.move only accepts path-like objects
-                # in Python 3.9 and up:
-                #   https://docs.python.org/3/library/shutil.html#shutil.move
-                shutil.move(str(new_file), str(destination_path))
-
-            # Clean build base lib folder for next install
-            shutil.rmtree(lib_dir_path, ignore_errors=True)
-
-
-def copy_extra_files(extra_file_strings: Iterable[str]) -> None:
-    """Copy extra files to their destinations."""
-    if not extra_file_strings:
-        return
-
-    for extra_file_string in extra_file_strings:
-        # Convert 'source > destination' strings to Paths.
-        input_output = re.split(r' *> *', extra_file_string)
-        source_file = Path(input_output[0])
-        dest_file = Path(input_output[1])
-
-        if not source_file.exists():
-            raise FileNotFoundError(f'extra_file "{source_file}" not found.\n'
-                                    f'  Defined by: "{extra_file_string}"')
-
-        # Copy files and make parent directories.
-        dest_file.parent.mkdir(parents=True, exist_ok=True)
-        # Raise an error if the destination file already exists.
-        if dest_file.exists():
-            raise FileExistsError(
-                f'Copying "{source_file}" would overwrite "{dest_file}"')
-
-        shutil.copy(source_file, dest_file)
-
-
-def main():
-    args = _parse_args()
-
-    py_packages = load_packages(args.input_list_files)
-
-    build_python_tree(python_packages=py_packages,
-                      tree_destination_dir=args.tree_destination_dir,
-                      include_tests=args.include_tests)
-    copy_extra_files(args.extra_files)
-
-    if args.setupcfg_common_file:
-        config = load_common_config(
-            common_config=args.setupcfg_common_file,
-            append_git_sha=args.setupcfg_version_append_git_sha,
-            append_date=args.setupcfg_version_append_date)
-
-        update_config_with_packages(config=config, python_packages=py_packages)
-
-        write_config(common_config=args.setupcfg_common_file,
-                     final_config=config,
-                     tree_destination_dir=args.tree_destination_dir)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/pw_build/py/pw_build/exec.py b/pw_build/py/pw_build/exec.py
index aac800b..b956c13 100644
--- a/pw_build/py/pw_build/exec.py
+++ b/pw_build/py/pw_build/exec.py
@@ -20,7 +20,6 @@
 import shlex
 import subprocess
 import sys
-import pathlib
 from typing import Dict, Optional
 
 # Need to be able to run without pw_cli installed in the virtualenv.
@@ -71,9 +70,6 @@
         '--target',
         help='GN build target that runs the program',
     )
-    parser.add_argument('--working-directory',
-                        type=pathlib.Path,
-                        help='Directory to execute program in')
     parser.add_argument(
         'command',
         nargs=argparse.REMAINDER,
@@ -117,7 +113,6 @@
 
     # Command starts after the "--".
     command = args.command[1:]
-    extra_kw_args = {}
 
     if args.args_file is not None:
         empty = True
@@ -137,13 +132,11 @@
         apply_env_var(string, env)
 
     if args.capture_output:
-        extra_kw_args['stdout'] = subprocess.PIPE
-        extra_kw_args['stderr'] = subprocess.STDOUT
+        output_args = {'stdout': subprocess.PIPE, 'stderr': subprocess.STDOUT}
+    else:
+        output_args = {}
 
-    if args.working_directory:
-        extra_kw_args['cwd'] = args.working_directory
-
-    process = subprocess.run(command, env=env, **extra_kw_args)  # type: ignore
+    process = subprocess.run(command, env=env, **output_args)  # type: ignore
 
     if process.returncode != 0 and args.capture_output:
         _LOG.error('')
diff --git a/pw_build/py/pw_build/file_prefix_map.py b/pw_build/py/pw_build/file_prefix_map.py
deleted file mode 100644
index 69daff2..0000000
--- a/pw_build/py/pw_build/file_prefix_map.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Transforms a JSON list of paths using -ffile-prefix-map style rules."""
-
-import argparse
-import json
-from typing import Iterator, List, TextIO
-
-# Note: This should be List[Tuple[str, str]], but using string.split()
-# produces Tuple[Any,...], so this permits that typing for convenience.
-PrefixMaps = List[tuple]
-
-
-def _parse_args() -> argparse.Namespace:
-    """Parses and returns the command line arguments."""
-
-    parser = argparse.ArgumentParser(description=__doc__)
-    parser.add_argument('in_json',
-                        type=argparse.FileType('r'),
-                        help='The JSON file containing a list of file names '
-                        'that the prefix map operations should be applied to')
-    parser.add_argument(
-        '--prefix-map-json',
-        type=argparse.FileType('r'),
-        required=True,
-        help=
-        'JSON file containing an array of prefix map transformations to apply '
-        'to the strings before tokenizing. These string literal '
-        'transformations are of the form "from=to". All strings with the '
-        'prefix `from` will have the prefix replaced with `to`. '
-        'Transformations are applied in the order they are listed in the JSON '
-        'file.')
-
-    parser.add_argument('--output',
-                        type=argparse.FileType('w'),
-                        help='File path to write transformed paths to.')
-    return parser.parse_args()
-
-
-def remap_paths(paths: List[str], prefix_maps: PrefixMaps) -> Iterator[str]:
-    for path in paths:
-        for from_prefix, to_prefix in prefix_maps:
-            if path.startswith(from_prefix):
-                path = path.replace(from_prefix, to_prefix, 1)
-        yield path
-
-
-def remap_json_paths(in_json: TextIO, output: TextIO,
-                     prefix_map_json: TextIO) -> None:
-    paths = json.load(in_json)
-    prefix_maps: PrefixMaps = [
-        tuple(m.split('=', maxsplit=1)) for m in json.load(prefix_map_json)
-    ]
-
-    json.dump(list(remap_paths(paths, prefix_maps)), output)
-
-
-if __name__ == '__main__':
-    remap_json_paths(**vars(_parse_args()))
diff --git a/pw_build/py/pw_build/generate_cc_blob_library.py b/pw_build/py/pw_build/generate_cc_blob_library.py
deleted file mode 100644
index 551701e..0000000
--- a/pw_build/py/pw_build/generate_cc_blob_library.py
+++ /dev/null
@@ -1,190 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Outputs the contents of blobs as a hard-coded arrays in a C++ library."""
-
-import argparse
-import itertools
-import json
-from pathlib import Path
-from string import Template
-import textwrap
-from typing import Any, Generator, Iterable, NamedTuple, Optional, Tuple
-
-HEADER_PREFIX = textwrap.dedent("""\
-    // This file is auto-generated; Do not hand-modify!
-    // See https://pigweed.dev/pw_build/#pw-cc-blob-library for details.
-
-    #pragma once
-
-    #include <array>
-    #include <cstddef>
-    """)
-
-SOURCE_PREFIX_TEMPLATE = Template(
-    textwrap.dedent("""\
-        // This file is auto-generated; Do not hand-modify!
-        // See https://pigweed.dev/pw_build/#pw-cc-blob-library for details.
-
-        #include "$header_path"
-
-        #include <array>
-        #include <cstddef>
-
-        #include "pw_preprocessor/compiler.h"
-        """))
-
-NAMESPACE_OPEN_TEMPLATE = Template('namespace ${namespace} {\n')
-
-NAMESPACE_CLOSE_TEMPLATE = Template('}  // namespace ${namespace}\n')
-
-BLOB_DECLARATION_TEMPLATE = Template(
-    'extern const std::array<std::byte, ${size_bytes}> ${symbol_name};')
-
-LINKER_SECTION_TEMPLATE = Template('PW_PLACE_IN_SECTION("${linker_section}")')
-
-BLOB_DEFINITION_SINGLE_LINE = Template(
-    'const std::array<std::byte, ${size_bytes}> ${symbol_name}'
-    ' = { $bytes };')
-
-BLOB_DEFINITION_MULTI_LINE = Template(
-    'const std::array<std::byte, ${size_bytes}> ${symbol_name}'
-    ' = {\n${bytes_lines}\n};')
-
-BYTES_PER_LINE = 4
-
-
-class Blob(NamedTuple):
-    symbol_name: str
-    file_path: Path
-    linker_section: Optional[str]
-
-
-def parse_args():
-    parser = argparse.ArgumentParser(description=__doc__)
-    parser.add_argument('--blob-file',
-                        type=Path,
-                        required=True,
-                        help=('Path to json file containing the list of blobs '
-                              'to generate.'))
-    parser.add_argument('--out-source',
-                        type=Path,
-                        required=True,
-                        help='Path at which to write .cpp file')
-    parser.add_argument('--out-header',
-                        type=Path,
-                        required=True,
-                        help='Path at which to write .h file')
-    parser.add_argument('--namespace',
-                        type=str,
-                        required=False,
-                        help=('Optional namespace that blobs will be scoped'
-                              'within.'))
-
-    return parser.parse_args()
-
-
-def split_into_chunks(
-        data: Iterable[Any],
-        chunk_size: int) -> Generator[Tuple[Any, ...], None, None]:
-    """Splits an iterable into chunks of a given size."""
-    data_iterator = iter(data)
-    chunk = tuple(itertools.islice(data_iterator, chunk_size))
-    while chunk:
-        yield chunk
-        chunk = tuple(itertools.islice(data_iterator, chunk_size))
-
-
-def header_from_blobs(blobs: Iterable[Blob],
-                      namespace: Optional[str] = None) -> str:
-    """Generate the contents of a C++ header file from blobs."""
-    lines = [HEADER_PREFIX]
-    if namespace:
-        lines.append(NAMESPACE_OPEN_TEMPLATE.substitute(namespace=namespace))
-    for blob in blobs:
-        data = blob.file_path.read_bytes()
-        lines.append(
-            BLOB_DECLARATION_TEMPLATE.substitute(symbol_name=blob.symbol_name,
-                                                 size_bytes=len(data)))
-        lines.append('')
-    if namespace:
-        lines.append(NAMESPACE_CLOSE_TEMPLATE.substitute(namespace=namespace))
-
-    return '\n'.join(lines)
-
-
-def array_def_from_blob_data(symbol_name: str, blob_data: bytes) -> str:
-    """Generates an array definition for the given blob data."""
-    byte_strs = ['std::byte{{0x{:02X}}}'.format(b) for b in blob_data]
-
-    # Try to fit the blob definition on a single line first.
-    single_line_def = BLOB_DEFINITION_SINGLE_LINE.substitute(
-        symbol_name=symbol_name,
-        size_bytes=len(blob_data),
-        bytes=', '.join(byte_strs))
-    if len(single_line_def) <= 80:
-        return single_line_def
-
-    # Blob definition didn't fit on a single line; do multi-line.
-    lines = []
-    for byte_strs_for_line in split_into_chunks(byte_strs, BYTES_PER_LINE):
-        bytes_segment = ', '.join(byte_strs_for_line)
-        lines.append(f'  {bytes_segment},')
-    # Removing the trailing comma from the final line of bytes.
-    lines[-1] = lines[-1].rstrip(',')
-
-    return BLOB_DEFINITION_MULTI_LINE.substitute(symbol_name=symbol_name,
-                                                 size_bytes=len(blob_data),
-                                                 bytes_lines='\n'.join(lines))
-
-
-def source_from_blobs(blobs: Iterable[Blob],
-                      header_path: Path,
-                      namespace: Optional[str] = None) -> str:
-    """Generate the contents of a C++ source file from blobs."""
-    lines = [SOURCE_PREFIX_TEMPLATE.substitute(header_path=header_path)]
-    if namespace:
-        lines.append(NAMESPACE_OPEN_TEMPLATE.substitute(namespace=namespace))
-    for blob in blobs:
-        if blob.linker_section:
-            lines.append(
-                LINKER_SECTION_TEMPLATE.substitute(
-                    linker_section=blob.linker_section))
-        data = blob.file_path.read_bytes()
-        lines.append(array_def_from_blob_data(blob.symbol_name, data))
-        lines.append('')
-    if namespace:
-        lines.append(NAMESPACE_CLOSE_TEMPLATE.substitute(namespace=namespace))
-
-    return '\n'.join(lines)
-
-
-def load_blobs(blob_file: Path) -> Iterable[Blob]:
-    with blob_file.open() as blob_fp:
-        return [
-            Blob(b["symbol_name"], Path(b["file_path"]),
-                 b.get("linker_section")) for b in json.load(blob_fp)
-        ]
-
-
-def main(blob_file: Path,
-         out_source: Path,
-         out_header: Path,
-         namespace: Optional[str] = None) -> None:
-    blobs = load_blobs(blob_file)
-    out_header.write_text(header_from_blobs(blobs, namespace))
-    out_source.write_text(source_from_blobs(blobs, out_header, namespace))
-
-
-if __name__ == '__main__':
-    main(**vars(parse_args()))
diff --git a/pw_build/py/pw_build/generate_modules_lists.py b/pw_build/py/pw_build/generate_modules_lists.py
deleted file mode 100644
index 7026b22..0000000
--- a/pw_build/py/pw_build/generate_modules_lists.py
+++ /dev/null
@@ -1,254 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Manages the list of Pigweed modules.
-
-Used by modules.gni to generate:
-
-- a build arg for each module,
-- a list of module paths (pw_modules),
-- a list of module tests (pw_module_tests), and
-- a list of module docs (pw_module_docs).
-"""
-
-import argparse
-import difflib
-import io
-import os
-from pathlib import Path
-import sys
-import subprocess
-from typing import Iterator, List, Optional, Sequence, Tuple
-
-_COPYRIGHT_NOTICE = '''\
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.'''
-
-_WARNING = '\033[31m\033[1mWARNING:\033[0m '  # Red WARNING: prefix
-_ERROR = '\033[41m\033[37m\033[1mERROR:\033[0m '  # Red background ERROR: prefix
-
-_MISSING_MODULES_WARNING = _WARNING + '''\
-The PIGWEED_MODULES list is missing the following modules:
-{modules}
-
-If the listed modules are Pigweed modules, add them to PIGWEED_MODULES.
-
-If the listed modules are not actual Pigweed modules, remove any stray pw_*
-directories in the Pigweed repository (git clean -fd).
-'''
-
-_OUT_OF_DATE_WARNING = _ERROR + '''\
-The generated Pigweed modules list .gni file is out of date!
-
-Regenerate the modules lists and commit it to fix this:
-
-  ninja -C {out_dir} update_modules
-
-  git add {file}
-'''
-
-_FORMAT_FAILED_WARNING = _ERROR + '''\
-Failed to generate a valid .gni from PIGWEED_MODULES!
-
-This may be a Pigweed bug; please report this to the Pigweed team.
-'''
-
-_DO_NOT_SET = 'DO NOT SET THIS BUILD ARGUMENT!'
-
-
-def _module_list_warnings(root: Path, modules: Sequence[str]) -> Iterator[str]:
-    missing = _missing_modules(root, modules)
-    if missing:
-        yield _MISSING_MODULES_WARNING.format(modules=''.join(
-            f'\n  - {module}' for module in missing))
-
-    if any(modules[i] > modules[i + 1] for i in range(len(modules) - 1)):
-        yield _WARNING + 'The PIGWEED_MODULES list is not sorted!'
-        yield ''
-        yield 'Apply the following diff to fix the order:'
-        yield ''
-        yield from difflib.unified_diff(modules,
-                                        sorted(modules),
-                                        lineterm='',
-                                        n=1,
-                                        fromfile='PIGWEED_MODULES',
-                                        tofile='PIGWEED_MODULES')
-
-        yield ''
-
-
-# TODO(hepler): Add tests and docs targets to all modules.
-def _find_tests_and_docs(
-        root: Path, modules: Sequence[str]) -> Tuple[List[str], List[str]]:
-    """Lists "tests" and "docs" targets for modules that declare them."""
-    tests = []
-    docs = []
-
-    for module in modules:
-        build_gn_contents = root.joinpath(module, 'BUILD.gn').read_bytes()
-        if b'group("tests")' in build_gn_contents:
-            tests.append(f'"$dir_{module}:tests",')
-
-        if b'group("docs")' in build_gn_contents:
-            docs.append(f'"$dir_{module}:docs",')
-
-    return tests, docs
-
-
-def _generate_modules_gni(root: Path, prefix: Path,
-                          modules: Sequence[str]) -> Iterator[str]:
-    """Generates a .gni file with variables and lists for Pigweed modules."""
-    script = Path(__file__).resolve().relative_to(root.resolve()).as_posix()
-
-    yield _COPYRIGHT_NOTICE
-    yield ''
-    yield '# Build args and lists for all modules in Pigweed.'
-    yield '#'
-    yield f'# DO NOT EDIT! Generated by {script}.'
-    yield '#'
-    yield '# To add modules here, list them in PIGWEED_MODULES and build the'
-    yield '# update_modules target and commit the updated version of this file:'
-    yield '#'
-    yield '#   ninja -C out update_modules'
-    yield '#'
-    yield '# DO NOT IMPORT THIS FILE DIRECTLY!'
-    yield '#'
-    yield '# Import it through //build_overrides/pigweed.gni instead.'
-    yield ''
-    yield '# Declare a build arg for each module.'
-    yield 'declare_args() {'
-
-    for module in modules:
-        module_path = prefix.joinpath(module).as_posix()
-        yield f'dir_{module} = get_path_info("{module_path}", "abspath")'
-
-    yield '}'
-    yield ''
-    yield '# Declare these as GN args in case this is imported in args.gni.'
-    yield '# Use a separate block so variables in the prior block can be used.'
-    yield 'declare_args() {'
-    yield f'# A list with paths to all Pigweed module. {_DO_NOT_SET}'
-    yield 'pw_modules = ['
-
-    for module in modules:
-        yield f'dir_{module},'
-
-    yield ']'
-    yield ''
-
-    tests, docs = _find_tests_and_docs(root, modules)
-
-    yield f'# A list with all Pigweed module test groups. {_DO_NOT_SET}'
-    yield 'pw_module_tests = ['
-    yield from tests
-    yield ']'
-    yield ''
-    yield f'# A list with all Pigweed modules docs groups. {_DO_NOT_SET}'
-    yield 'pw_module_docs = ['
-    yield from docs
-    yield ']'
-    yield ''
-    yield '}'
-
-
-def _missing_modules(root: Path, modules: Sequence[str]) -> Sequence[str]:
-    return sorted(
-        frozenset(
-            str(p.relative_to(root))
-            for p in root.glob('pw_*') if p.is_dir()) - frozenset(modules))
-
-
-def _parse_args() -> dict:
-    parser = argparse.ArgumentParser(
-        description=__doc__,
-        formatter_class=argparse.RawDescriptionHelpFormatter)
-    parser.add_argument('root', type=Path, help='Root build dir')
-    parser.add_argument('modules_list', type=Path, help='Input modules list')
-    parser.add_argument('modules_gni_file', type=Path, help='Output .gni file')
-    parser.add_argument(
-        '--warn-only',
-        type=Path,
-        help='Only check PIGWEED_MODULES; takes a path to a stamp file to use')
-
-    return vars(parser.parse_args())
-
-
-def _main(root: Path, modules_list: Path, modules_gni_file: Path,
-          warn_only: Optional[Path]) -> int:
-    prefix = Path(os.path.relpath(root, modules_gni_file.parent))
-    modules = modules_list.read_text().splitlines()
-
-    # Detect any problems with the modules list.
-    warnings = list(_module_list_warnings(root, modules))
-    errors = []
-
-    modules.sort()  # Sort in case the modules list in case it wasn't sorted.
-
-    # Check if the contents of the .gni file are out of date.
-    if warn_only:
-        text = io.StringIO()
-        for line in _generate_modules_gni(root, prefix, modules):
-            print(line, file=text)
-
-        process = subprocess.run(['gn', 'format', '--stdin'],
-                                 input=text.getvalue().encode('utf-8'),
-                                 stdout=subprocess.PIPE)
-        if process.returncode != 0:
-            errors.append(_FORMAT_FAILED_WARNING)
-        elif modules_gni_file.read_bytes() != process.stdout:
-            errors.append(
-                _OUT_OF_DATE_WARNING.format(
-                    out_dir=os.path.relpath(os.curdir, root),
-                    file=os.path.relpath(modules_gni_file, root)))
-    elif not warnings:  # Update the modules .gni file.
-        with modules_gni_file.open('w', encoding='utf-8') as file:
-            for line in _generate_modules_gni(root, prefix, modules):
-                print(line, file=file)
-
-        process = subprocess.run(['gn', 'format', modules_gni_file],
-                                 stdout=subprocess.DEVNULL)
-        if process.returncode != 0:
-            errors.append(_FORMAT_FAILED_WARNING)
-
-    # If there are errors, display them and abort.
-    if warnings or errors:
-        for line in warnings + errors:
-            print(line, file=sys.stderr)
-
-        # Delete the stamp so this always reruns. Deleting is necessary since
-        # some of the checks do not depend on input files.
-        if warn_only and warn_only.exists():
-            warn_only.unlink()
-
-        # Warnings are non-fatal if warn_only is True.
-        return 1 if errors or not warn_only else 0
-
-    if warn_only:
-        warn_only.touch()
-
-    return 0
-
-
-if __name__ == '__main__':
-    sys.exit(_main(**_parse_args()))
diff --git a/pw_build/py/pw_build/generate_python_package.py b/pw_build/py/pw_build/generate_python_package.py
index d93cf8c..81e6c48 100644
--- a/pw_build/py/pw_build/generate_python_package.py
+++ b/pw_build/py/pw_build/generate_python_package.py
@@ -11,28 +11,15 @@
 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 # License for the specific language governing permissions and limitations under
 # the License.
-"""Generates a setup.cfg file for a Python package."""
+"""Script that invokes protoc to generate code for .proto files."""
 
 import argparse
 from collections import defaultdict
-import configparser
-from dataclasses import dataclass
-from itertools import chain
 import json
 from pathlib import Path
 import sys
 import textwrap
-from typing import (
-    Any,
-    Dict,
-    Iterable,
-    Iterator,
-    List,
-    Optional,
-    Sequence,
-    Set,
-    TextIO,
-)
+from typing import Dict, List, Set, TextIO
 
 try:
     from pw_build.mirror_tree import mirror_paths
@@ -46,14 +33,22 @@
 def _parse_args() -> argparse.Namespace:
     parser = argparse.ArgumentParser(description=__doc__)
 
+    parser.add_argument('--file-list',
+                        required=True,
+                        type=argparse.FileType('r'),
+                        help='A list of files to copy')
+    parser.add_argument('--file-list-root',
+                        required=True,
+                        type=argparse.FileType('r'),
+                        help='A file with the root of the file list')
     parser.add_argument('--label', help='Label for this Python package')
     parser.add_argument('--proto-library',
-                        dest='proto_libraries',
-                        type=argparse.FileType('r'),
-                        default=[],
-                        action='append',
-                        help='Paths')
-    parser.add_argument('--generated-root',
+                        default='',
+                        help='Name of proto library nested in this package')
+    parser.add_argument('--proto-library-file',
+                        type=Path,
+                        help="File with the proto library's name")
+    parser.add_argument('--root',
                         required=True,
                         type=Path,
                         help='The base directory for the Python package')
@@ -71,33 +66,35 @@
     return parser.parse_args()
 
 
-def _check_nested_protos(label: str, proto_info: Dict[str, Any]) -> None:
-    """Checks that the proto library refers to this package."""
-    python_package = proto_info['nested_in_python_package']
+def _check_nested_protos(label: str, proto_library_file: Path,
+                         proto_library: str) -> None:
+    """Checks that the proto library refers to this package; returns error."""
+    error = 'not set'
 
-    if python_package != label:
-        raise ValueError(
-            f"{label}'s 'proto_library' is set to {proto_info['label']}, but "
-            f"that target's 'python_package' is {python_package or 'not set'}. "
-            f"Set {proto_info['label']}'s 'python_package' to {label}.")
+    if proto_library_file.exists():
+        proto_label = proto_library_file.read_text().strip()
+        if proto_label == label:
+            return
+
+        if proto_label:
+            error = f'set to {proto_label}'
+
+    raise ValueError(
+        f"{label}'s 'proto_library' is set to {proto_library}, but that "
+        f"target's 'python_package' is {error}. Set {proto_library}'s "
+        f"'python_package' to {label}.")
 
 
-@dataclass(frozen=True)
-class _ProtoInfo:
-    root: Path
-    sources: Sequence[Path]
-    deps: Sequence[str]
-
-
-def _collect_all_files(
-        root: Path, files: List[Path],
-        paths_to_collect: Iterable[_ProtoInfo]) -> Dict[str, Set[str]]:
+def _collect_all_files(files: List[Path], root: Path, file_list: TextIO,
+                       file_list_root: TextIO) -> Dict[str, Set[str]]:
     """Collects files in output dir, adds to files; returns package_data."""
     root.mkdir(exist_ok=True)
 
-    for proto_info in paths_to_collect:
-        # Mirror the proto files to this package.
-        files += mirror_paths(proto_info.root, proto_info.sources, root)
+    other_files = [Path(p.rstrip()) for p in file_list]
+    other_files_root = Path(file_list_root.read().rstrip())
+
+    # Mirror the proto files to this package.
+    files += mirror_paths(other_files_root, other_files, root)
 
     # Find all subpackages, including empty ones.
     subpackages: Set[Path] = set()
@@ -116,77 +113,40 @@
 
     # Add all non-source files to package data.
     for file in (f for f in files if f.suffix != '.py'):
-        pkg = file.parent
-
+        pkg = root / file.parent
         package_name = pkg.relative_to(root).as_posix().replace('/', '.')
         pkg_data[package_name].add(file.name)
 
     return pkg_data
 
 
-_PYPROJECT_FILE = '''\
+_SETUP_PY_FILE = '''\
 # Generated file. Do not modify.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
+# pylint: skip-file
+
+import setuptools  # type: ignore
+
+setuptools.setup(
+{keywords}
+)
 '''
 
 
-def _get_setup_keywords(pkg_data: dict, keywords: dict) -> Dict:
-    """Gather all setuptools.setup() keyword args."""
-    options_keywords = dict(
+def _generate_setup_py(pkg_data: dict, setup_json: TextIO) -> str:
+    setup_keywords = dict(
         packages=list(pkg_data),
         package_data={pkg: list(files)
                       for pkg, files in pkg_data.items()},
     )
 
-    keywords['options'].update(options_keywords)
-    return keywords
+    specified_keywords = json.load(setup_json)
 
+    assert not any(kw in specified_keywords for kw in setup_keywords), (
+        'Generated packages may not specify "packages" or "package_data"')
+    setup_keywords.update(specified_keywords)
 
-def _write_to_config(config: configparser.ConfigParser,
-                     data: Dict,
-                     section: Optional[str] = None):
-    """Populate a ConfigParser instance with the contents of a dict."""
-    # Add a specified section if missing.
-    if section is not None and not config.has_section(section):
-        config.add_section(section)
-
-    for key, value in data.items():
-        # Value is a dict so create a new subsection
-        if isinstance(value, dict):
-            _write_to_config(
-                config,
-                value,
-                f'{section}.{key}' if section else key,
-            )
-        elif isinstance(value, list):
-            if value:
-                assert section is not None
-                # Convert the list to an allowed str format.
-                config[section][key] = '\n' + '\n'.join(value)
-        else:
-            assert section is not None
-            # Add the value as a string. See expected types here:
-            # https://setuptools.readthedocs.io/en/latest/userguide/declarative_config.html#specifying-values
-            config[section][key] = str(value)
-
-
-def _generate_setup_cfg(
-    pkg_data: dict,
-    keywords: dict,
-    config_file_path: Path,
-) -> None:
-    """Creates a setup.cfg file based on setuptools keywords."""
-    setup_keywords = _get_setup_keywords(pkg_data, keywords)
-
-    config = configparser.ConfigParser()
-
-    _write_to_config(config, setup_keywords)
-
-    # Write the config to a file.
-    with config_file_path.open('w') as config_file:
-        config.write(config_file)
+    return _SETUP_PY_FILE.format(keywords='\n'.join(
+        f'    {k}={v!r},' for k, v in setup_keywords.items()))
 
 
 def _import_module_in_package_init(all_files: List[Path]) -> None:
@@ -206,52 +166,35 @@
         f'from {source.stem}.{source.stem} import *\n')
 
 
-def _load_metadata(label: str,
-                   proto_libraries: Iterable[TextIO]) -> Iterator[_ProtoInfo]:
-    for proto_library_file in proto_libraries:
-        info = json.load(proto_library_file)
-        _check_nested_protos(label, info)
-
-        deps = []
-        for dep in info['dependencies']:
-            with open(dep) as file:
-                deps.append(json.load(file)['package'])
-
-        yield _ProtoInfo(Path(info['root']),
-                         tuple(Path(p) for p in info['protoc_outputs']), deps)
-
-
-def main(generated_root: Path, files: List[Path], module_as_package: bool,
-         setup_json: TextIO, label: str,
-         proto_libraries: Iterable[TextIO]) -> int:
+def main(files: List[Path],
+         root: Path,
+         file_list: TextIO,
+         file_list_root: TextIO,
+         module_as_package: bool,
+         setup_json: TextIO,
+         label: str,
+         proto_library: str = '',
+         proto_library_file: Path = None) -> int:
     """Generates a setup.py and other files for a Python package."""
-    proto_infos = list(_load_metadata(label, proto_libraries))
-    try:
-        pkg_data = _collect_all_files(generated_root, files, proto_infos)
-    except ValueError as error:
-        msg = '\n'.join(textwrap.wrap(str(error), 78))
-        print(
-            f'ERROR: Failed to generate Python package {label}:\n\n'
-            f'{textwrap.indent(msg, "  ")}\n',
-            file=sys.stderr)
-        return 1
+    if proto_library_file:
+        try:
+            _check_nested_protos(label, proto_library_file, proto_library)
+        except ValueError as error:
+            msg = '\n'.join(textwrap.wrap(str(error), 78))
+            print(
+                f'ERROR: Failed to generate Python package {label}:\n\n'
+                f'{textwrap.indent(msg, "  ")}\n',
+                file=sys.stderr)
+            return 1
 
-    with setup_json:
-        setup_keywords = json.load(setup_json)
-        setup_keywords.setdefault('options', {})
-
-    install_requires = setup_keywords['options'].setdefault(
-        'install_requires', [])
-    install_requires += chain.from_iterable(i.deps for i in proto_infos)
+    pkg_data = _collect_all_files(files, root, file_list, file_list_root)
 
     if module_as_package:
         _import_module_in_package_init(files)
 
-    # Create the pyproject.toml and setup.cfg files for this package.
-    generated_root.joinpath('pyproject.toml').write_text(_PYPROJECT_FILE)
-    _generate_setup_cfg(pkg_data,
-                        setup_keywords,
-                        config_file_path=generated_root.joinpath('setup.cfg'))
+    # Create the setup.py file for this package.
+    root.joinpath('setup.py').write_text(
+        _generate_setup_py(pkg_data, setup_json))
 
     return 0
 
diff --git a/pw_build/py/pw_build/generated_tests.py b/pw_build/py/pw_build/generated_tests.py
index af54806..b3c280a 100644
--- a/pw_build/py/pw_build/generated_tests.py
+++ b/pw_build/py/pw_build/generated_tests.py
@@ -22,7 +22,7 @@
 from typing import (Any, Callable, Dict, Generic, Iterable, Iterator, List,
                     Sequence, TextIO, TypeVar, Union)
 
-_COPYRIGHT = f"""\
+_CPP_HEADER = f"""\
 // Copyright {datetime.now().year} The Pigweed Authors
 //
 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
@@ -40,16 +40,10 @@
 // AUTOGENERATED - DO NOT EDIT
 //
 // Generated at {datetime.now().isoformat()}
-"""
 
-_HEADER_CPP = _COPYRIGHT + """\
 // clang-format off
 """
 
-_HEADER_JS = _COPYRIGHT + """\
-/* eslint-env browser, jasmine */
-"""
-
 
 class Error(Exception):
     """Something went wrong when generating tests."""
@@ -77,10 +71,6 @@
                                  for c in self.group.lower())
         return f'{name}_{self.count}' if self.total > 1 else name
 
-    def ts_name(self) -> str:
-        name = ''.join(c if c.isalnum() else ' ' for c in self.group.lower())
-        return f'{name} {self.count}' if self.total > 1 else name
-
 
 # Test cases are specified as a sequence of strings or test case instances. The
 # strings are used to separate the tests into named groups. For example:
@@ -105,8 +95,6 @@
 # code for the given test case.
 CcTestGenerator = Callable[[Context[T]], Iterable[str]]
 
-JsTestGenerator = Callable[[Context[T]], Iterable[str]]
-
 
 class TestGenerator(Generic[T]):
     """Generates tests for multiple languages from a series of test cases."""
@@ -157,7 +145,7 @@
 
     def _generate_cc_tests(self, define_cpp_test: CcTestGenerator, header: str,
                            footer: str) -> Iterator[str]:
-        yield _HEADER_CPP
+        yield _CPP_HEADER
         yield header
 
         for ctx in self._test_contexts():
@@ -173,22 +161,6 @@
             output.write(line)
             output.write('\n')
 
-    def _generate_ts_tests(self, define_ts_test: JsTestGenerator, header: str,
-                           footer: str) -> Iterator[str]:
-        yield _HEADER_JS
-        yield header
-
-        for ctx in self._test_contexts():
-            yield from define_ts_test(ctx)
-        yield footer
-
-    def ts_tests(self, output: TextIO, define_js_test: JsTestGenerator,
-                 header: str, footer: str):
-        """Writes JS unit tests for each test case to the given file."""
-        for line in self._generate_ts_tests(define_js_test, header, footer):
-            output.write(line)
-            output.write('\n')
-
 
 def _to_chars(data: bytes) -> Iterator[str]:
     for i, byte in enumerate(data):
@@ -212,7 +184,4 @@
     parser.add_argument('--generate-cc-test',
                         type=argparse.FileType('w'),
                         help='Generate the C++ test file')
-    parser.add_argument('--generate-ts-test',
-                        type=argparse.FileType('w'),
-                        help='Generate the JS test file')
     return parser.parse_known_args()[0]
diff --git a/pw_build/py/pw_build/mirror_tree.py b/pw_build/py/pw_build/mirror_tree.py
index 46d19a0..ef7e455 100644
--- a/pw_build/py/pw_build/mirror_tree.py
+++ b/pw_build/py/pw_build/mirror_tree.py
@@ -16,7 +16,6 @@
 import argparse
 import os
 from pathlib import Path
-import shutil
 from typing import Iterable, Iterator, List
 
 
@@ -55,16 +54,9 @@
 
         # Use a hard link to avoid unnecessary copies. Resolve the source before
         # linking in case it is a symlink.
-        source = source.resolve()
-        try:
-            os.link(source, dest)
-            yield dest
+        os.link(source.resolve(), dest)
 
-        # If the link failed try copying. If copying fails re-raise the
-        # original exception.
-        except OSError:
-            shutil.copy(source, dest)
-            yield dest
+        yield dest
 
 
 def _link_files_or_dirs(paths: Iterable[Path],
diff --git a/pw_build/py/pw_build/python_package.py b/pw_build/py/pw_build/python_package.py
deleted file mode 100644
index fa3be54..0000000
--- a/pw_build/py/pw_build/python_package.py
+++ /dev/null
@@ -1,167 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Dataclass for a Python package."""
-
-import configparser
-from contextlib import contextmanager
-import copy
-from dataclasses import dataclass
-import json
-import os
-from pathlib import Path
-import shutil
-from typing import Dict, List, Optional, Iterable
-
-import setuptools  # type: ignore
-
-
-@contextmanager
-def change_working_dir(directory: Path):
-    original_dir = Path.cwd()
-    try:
-        os.chdir(directory)
-        yield directory
-    finally:
-        os.chdir(original_dir)
-
-
-@dataclass
-class PythonPackage:
-    """Class to hold a single Python package's metadata."""
-
-    sources: List[Path]
-    setup_sources: List[Path]
-    tests: List[Path]
-    inputs: List[Path]
-    gn_target_name: Optional[str] = None
-    generate_setup: Optional[Dict] = None
-    config: Optional[configparser.ConfigParser] = None
-
-    @staticmethod
-    def from_dict(**kwargs) -> 'PythonPackage':
-        """Build a PythonPackage instance from a dictionary."""
-        transformed_kwargs = copy.copy(kwargs)
-
-        # Transform string filenames to Paths
-        for attribute in ['sources', 'tests', 'inputs', 'setup_sources']:
-            transformed_kwargs[attribute] = [
-                Path(s) for s in kwargs[attribute]
-            ]
-
-        return PythonPackage(**transformed_kwargs)
-
-    def __post_init__(self):
-        # Read the setup.cfg file if possible
-        if not self.config:
-            self.config = self._load_config()
-
-    @property
-    def setup_dir(self) -> Path:
-        assert len(self.setup_sources) > 0
-        # Assuming all setup_source files live in the same parent directory.
-        return self.setup_sources[0].parent
-
-    @property
-    def setup_py(self) -> Path:
-        setup_py = [
-            setup_file for setup_file in self.setup_sources
-            if str(setup_file).endswith('setup.py')
-        ]
-        # setup.py will not exist for GN generated Python packages
-        assert len(setup_py) == 1
-        return setup_py[0]
-
-    @property
-    def setup_cfg(self) -> Path:
-        setup_cfg = [
-            setup_file for setup_file in self.setup_sources
-            if str(setup_file).endswith('setup.cfg')
-        ]
-        assert len(setup_cfg) == 1
-        return setup_cfg[0]
-
-    @property
-    def package_name(self) -> str:
-        assert self.config
-        return self.config['metadata']['name']
-
-    @property
-    def package_dir(self) -> Path:
-        return self.setup_cfg.parent / self.package_name
-
-    def _load_config(self) -> Optional[configparser.ConfigParser]:
-        config = configparser.ConfigParser()
-        # Check for a setup.cfg and load that config.
-        if self.setup_cfg:
-            with self.setup_cfg.open() as config_file:
-                config.read_file(config_file)
-            return config
-        return None
-
-    def setuptools_build_with_base(self,
-                                   build_base: Path,
-                                   include_tests: bool = False) -> Path:
-        # Create the lib install dir in case it doesn't exist.
-        lib_dir_path = build_base / 'lib'
-        lib_dir_path.mkdir(parents=True, exist_ok=True)
-
-        starting_directory = Path.cwd()
-        # cd to the location of setup.py
-        with change_working_dir(self.setup_dir):
-            # Run build with temp build-base location
-            # Note: New files will be placed inside lib_dir_path
-            setuptools.setup(script_args=[
-                'build',
-                '--force',
-                '--build-base',
-                str(build_base),
-            ])
-
-            new_pkg_dir = lib_dir_path / self.package_name
-            # If tests should be included, copy them to the tests dir
-            if include_tests and self.tests:
-                test_dir_path = new_pkg_dir / 'tests'
-                test_dir_path.mkdir(parents=True, exist_ok=True)
-
-                for test_source_path in self.tests:
-                    shutil.copy(starting_directory / test_source_path,
-                                test_dir_path)
-
-        return lib_dir_path
-
-    def setuptools_develop(self) -> None:
-        with change_working_dir(self.setup_dir):
-            setuptools.setup(script_args=['develop'])
-
-    def setuptools_install(self) -> None:
-        with change_working_dir(self.setup_dir):
-            setuptools.setup(script_args=['install'])
-
-
-def load_packages(input_list_files: Iterable[Path]) -> List[PythonPackage]:
-    """Load Python package metadata and configs."""
-
-    packages = []
-    for input_path in input_list_files:
-
-        with input_path.open() as input_file:
-            # Each line contains the path to a json file.
-            for json_file in input_file.readlines():
-                # Load the json as a dict.
-                json_file_path = Path(json_file.strip()).resolve()
-                with json_file_path.open() as json_fp:
-                    json_dict = json.load(json_fp)
-
-                packages.append(PythonPackage.from_dict(**json_dict))
-    return packages
diff --git a/pw_build/py/pw_build/python_runner.py b/pw_build/py/pw_build/python_runner.py
index 8591c4f..b719650 100755
--- a/pw_build/py/pw_build/python_runner.py
+++ b/pw_build/py/pw_build/python_runner.py
@@ -18,7 +18,6 @@
 """
 
 import argparse
-import atexit
 from dataclasses import dataclass
 import enum
 import logging
@@ -28,16 +27,10 @@
 import shlex
 import subprocess
 import sys
-import time
 from typing import Callable, Dict, Iterable, Iterator, List, NamedTuple
 from typing import Optional, Tuple
 
-if sys.platform != 'win32':
-    import fcntl  # pylint: disable=import-error
-    # TODO(b/227670947): Support Windows.
-
 _LOG = logging.getLogger(__name__)
-_LOCK_ACQUISITION_TIMEOUT = 30 * 60  # 30 minutes in seconds
 
 
 def _parse_args() -> argparse.Namespace:
@@ -48,17 +41,20 @@
                         type=Path,
                         required=True,
                         help=('Path to the root of the GN tree; '
-                              'value of rebase_path("//", root_build_dir)'))
+                              'value of rebase_path("//")'))
     parser.add_argument('--current-path',
                         type=Path,
                         required=True,
-                        help='Value of rebase_path(".", root_build_dir)')
+                        help='Value of rebase_path(".")')
     parser.add_argument('--default-toolchain',
                         required=True,
                         help='Value of default_toolchain')
     parser.add_argument('--current-toolchain',
                         required=True,
                         help='Value of current_toolchain')
+    parser.add_argument('--directory',
+                        type=Path,
+                        help='Execute the command from this directory')
     parser.add_argument('--module', help='Run this module instead of a script')
     parser.add_argument('--env',
                         action='append',
@@ -74,21 +70,10 @@
         help='Capture subcommand output; display only on error',
     )
     parser.add_argument(
-        '--working-directory',
-        type=Path,
-        help='Change to this working directory before running the subcommand',
-    )
-    parser.add_argument(
         'original_cmd',
         nargs=argparse.REMAINDER,
         help='Python script with arguments to run',
     )
-    parser.add_argument(
-        '--lockfile',
-        type=Path,
-        required=True,
-        help=('Path to a pip lockfile. Any pip execution will aquire an '
-              'exclusive lock on it, any other module a shared lock.'))
     return parser.parse_args()
 
 
@@ -179,7 +164,7 @@
 _MAIN_ARTIFACTS = '', '.elf', '.a', '.so', '.dylib', '.exe', '.lib', '.dll'
 
 
-def _get_artifact(entries: List[str]) -> _Artifact:
+def _get_artifact(build_dir: Path, entries: List[str]) -> _Artifact:
     """Attempts to resolve which artifact to use if there are multiple.
 
     Selects artifacts based on extension. This will not work if a toolchain
@@ -188,19 +173,19 @@
     assert entries, "There should be at least one entry here!"
 
     if len(entries) == 1:
-        return _Artifact(Path(entries[0]), {})
+        return _Artifact(build_dir / entries[0], {})
 
     filtered = [p for p in entries if Path(p).suffix in _MAIN_ARTIFACTS]
 
     if len(filtered) == 1:
-        return _Artifact(Path(filtered[0]), {})
+        return _Artifact(build_dir / filtered[0], {})
 
     raise ExpressionError(
         f'Expected 1, but found {len(filtered)} artifacts, after filtering for '
         f'extensions {", ".join(repr(e) for e in _MAIN_ARTIFACTS)}: {entries}')
 
 
-def _parse_build_artifacts(fd) -> Iterator[_Artifact]:
+def _parse_build_artifacts(build_dir: Path, fd) -> Iterator[_Artifact]:
     """Partially parses the build statements in a Ninja file."""
     lines = iter(fd)
 
@@ -227,7 +212,7 @@
         else:
             match = _GN_NINJA_BUILD_STATEMENT.match(line)
             if match:
-                artifact = _get_artifact(match.group(1).split())
+                artifact = _get_artifact(build_dir, match.group(1).split())
 
             line = next_line()
 
@@ -235,7 +220,7 @@
         yield artifact
 
 
-def _search_target_ninja(ninja_file: Path,
+def _search_target_ninja(ninja_file: Path, paths: GnPaths,
                          target: Label) -> Tuple[Optional[Path], List[Path]]:
     """Parses the main output file and object files from <target>.ninja."""
 
@@ -245,16 +230,16 @@
     _LOG.debug('Parsing target Ninja file %s for %s', ninja_file, target)
 
     with ninja_file.open() as fd:
-        for path, variables in _parse_build_artifacts(fd):
+        for path, variables in _parse_build_artifacts(paths.build, fd):
             # Older GN used .stamp files when there is no build artifact.
             if path.suffix == '.stamp':
                 continue
 
             if variables:
                 assert not artifact, f'Multiple artifacts for {target}!'
-                artifact = Path(path)
+                artifact = path
             else:
-                objects.append(Path(path))
+                objects.append(path)
 
     return artifact, objects
 
@@ -272,9 +257,7 @@
 
     # Older versions of GN used a .stamp file to signal completion of a target.
     stamp_dir = target.out_dir.relative_to(paths.build).as_posix()
-    stamp_tool = 'stamp'
-    if target.toolchain_name() != '':
-        stamp_tool = f'{target.toolchain_name()}_stamp'
+    stamp_tool = f'{target.toolchain_name()}_stamp'
     stamp_statement = f'build {stamp_dir}/{target.name}.stamp: {stamp_tool} '
 
     # Newer GN uses a phony Ninja target to signal completion of a target.
@@ -288,7 +271,7 @@
                 if line.startswith(statement):
                     output_files = line[len(statement):].strip().split()
                     if len(output_files) == 1:
-                        return Path(output_files[0])
+                        return paths.build / output_files[0]
 
                     break
 
@@ -300,7 +283,7 @@
         target: Label) -> Tuple[bool, Optional[Path], List[Path]]:
     ninja_file = target.out_dir / f'{target.name}.ninja'
     if ninja_file.exists():
-        return (True, *_search_target_ninja(ninja_file, target))
+        return (True, *_search_target_ninja(ninja_file, paths, target))
 
     ninja_file = paths.build / target.toolchain_name() / 'toolchain.ninja'
     if ninja_file.exists():
@@ -383,7 +366,7 @@
         if target.artifact is None:
             raise ExpressionError(f'Target {target} has no output file!')
 
-        if paths.build.joinpath(target.artifact).exists():
+        if Path(target.artifact).exists():
             yield _ArgAction.APPEND, str(target.artifact)
             return
 
@@ -455,59 +438,10 @@
     return (''.join(arg) for arg in expanded_args if arg)
 
 
-class LockAcquisitionTimeoutError(Exception):
-    """Raised on a timeout."""
-
-
-def acquire_lock(lockfile: Path, exclusive: bool):
-    """Attempts to acquire the lock.
-
-    Args:
-      lockfile: pathlib.Path to the lock.
-      exclusive: whether this needs to be an exclusive lock.
-
-    Raises:
-      LockAcquisitionTimeoutError: If the lock is not acquired after a
-        reasonable time.
-    """
-    if sys.platform == 'win32':
-        # No-op on Windows, which doesn't have POSIX file locking.
-        # TODO(b/227670947): Get this working on Windows, too.
-        return
-
-    start_time = time.monotonic()
-    if exclusive:
-        lock_type = fcntl.LOCK_EX  # type: ignore[name-defined]
-    else:
-        lock_type = fcntl.LOCK_SH  # type: ignore[name-defined]
-    fd = os.open(lockfile, os.O_RDWR | os.O_CREAT)
-
-    # Make sure we close the file when the process exits. If we manage to
-    # acquire the lock below, closing the file will release it.
-    def cleanup():
-        os.close(fd)
-
-    atexit.register(cleanup)
-
-    backoff = 1
-    while time.monotonic() - start_time < _LOCK_ACQUISITION_TIMEOUT:
-        try:
-            fcntl.flock(  # type: ignore[name-defined]
-                fd, lock_type | fcntl.LOCK_NB)  # type: ignore[name-defined]
-            return  # Lock acquired!
-        except BlockingIOError:
-            pass  # Keep waiting.
-
-        time.sleep(backoff * 0.05)
-        backoff += 1
-
-    raise LockAcquisitionTimeoutError(
-        f"Failed to acquire lock {lockfile} in {_LOCK_ACQUISITION_TIMEOUT}")
-
-
-def main(  # pylint: disable=too-many-arguments
+def main(
     gn_root: Path,
     current_path: Path,
+    directory: Optional[Path],
     original_cmd: List[str],
     default_toolchain: str,
     current_toolchain: str,
@@ -515,8 +449,6 @@
     env: Optional[List[str]],
     capture_output: bool,
     touch: Optional[Path],
-    working_directory: Optional[Path],
-    lockfile: Path,
 ) -> int:
     """Script entry point."""
 
@@ -538,7 +470,7 @@
     if module is not None:
         command += ['-m', module]
 
-    run_args: dict = dict()
+    run_args: dict = dict(cwd=directory)
 
     if env is not None:
         environment = os.environ.copy()
@@ -558,15 +490,6 @@
         _LOG.error('%s: %s', sys.argv[0], err)
         return 1
 
-    if working_directory:
-        run_args['cwd'] = working_directory
-
-    try:
-        acquire_lock(lockfile, module == 'pip')
-    except LockAcquisitionTimeoutError as exception:
-        _LOG.error('%s', exception)
-        return 1
-
     _LOG.debug('RUN %s', ' '.join(shlex.quote(arg) for arg in command))
 
     completed_process = subprocess.run(command, **run_args)
@@ -579,11 +502,7 @@
     elif touch:
         # If a stamp file is provided and the command executed successfully,
         # touch the stamp file to indicate a successful run of the command.
-        touch = touch.resolve()
         _LOG.debug('TOUCH %s', touch)
-
-        # Create the parent directory in case GN / Ninja hasn't created it.
-        touch.parent.mkdir(parents=True, exist_ok=True)
         touch.touch()
 
     return completed_process.returncode
diff --git a/pw_build/py/pyproject.toml b/pw_build/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/pw_build/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/pw_build/py/python_runner_test.py b/pw_build/py/python_runner_test.py
index f9e188c..1ea69b0 100755
--- a/pw_build/py/python_runner_test.py
+++ b/pw_build/py/python_runner_test.py
@@ -194,8 +194,6 @@
         self._tempdir, self._outdir, self._paths = _create_ninja_files(
             NINJA_SOURCE_SET)
 
-        self._rel_outdir = self._outdir.relative_to(self._paths.build)
-
     def tearDown(self):
         self._tempdir.cleanup()
 
@@ -209,22 +207,22 @@
         self.assertTrue(target.generated)
         self.assertEqual(
             set(target.object_files), {
-                self._rel_outdir / 'fake_source_set.file_a.cc.o',
-                self._rel_outdir / 'fake_source_set.file_b.c.o',
+                self._outdir / 'fake_source_set.file_a.cc.o',
+                self._outdir / 'fake_source_set.file_b.c.o',
             })
 
     def test_executable_object_files(self):
         target = TargetInfo(self._paths, '//fake_module:fake_test')
         self.assertEqual(
             set(target.object_files), {
-                self._rel_outdir / 'fake_test.fake_test.cc.o',
-                self._rel_outdir / 'fake_test.fake_test_c.c.o',
+                self._outdir / 'fake_test.fake_test.cc.o',
+                self._outdir / 'fake_test.fake_test_c.c.o',
             })
 
     def test_executable_artifact(self):
         target = TargetInfo(self._paths, '//fake_module:fake_test')
         self.assertEqual(target.artifact,
-                         self._rel_outdir / 'test' / 'fake_test.elf')
+                         self._outdir / 'test' / 'fake_test.elf')
 
     def test_non_existent_target(self):
         target = TargetInfo(self._paths,
@@ -245,8 +243,6 @@
         self._tempdir, self._outdir, self._paths = _create_ninja_files(
             NINJA_SOURCE_SET_STAMP)
 
-        self._rel_outdir = self._outdir.relative_to(self._paths.build)
-
 
 class ExpandExpressionsTest(unittest.TestCase):
     """Tests expansion of expressions like <TARGET_FILE(//foo)>."""
@@ -264,7 +260,7 @@
             path.touch()
         else:
             assert not path.exists()
-        return str(path.relative_to(self._paths.build))
+        return str(path)
 
     def test_empty(self):
         self.assertEqual(list(expand_expressions(self._paths, '')), [''])
@@ -393,8 +389,6 @@
         self._tempdir, self._outdir, self._paths = _create_ninja_files(
             NINJA_SOURCE_SET_STAMP)
 
-        self._rel_outdir = self._outdir.relative_to(self._paths.build)
-
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/pw_build/py/setup.cfg b/pw_build/py/setup.cfg
deleted file mode 100644
index 72f7698..0000000
--- a/pw_build/py/setup.cfg
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pw_build
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Python scripts that support the GN build
-
-[options]
-packages = find:
-zip_safe = False
-install_requires =
-    wheel
-    coverage
-    pw_cli
-    pw_env_setup
-    pw_presubmit
-
-[options.entry_points]
-console_scripts =
-    copy_from_cipd = pw_build.copy_from_cipd:main
-
-[options.package_data]
-pw_build = py.typed
diff --git a/pw_build/py/setup.py b/pw_build/py/setup.py
index a8fc274..50388a5 100644
--- a/pw_build/py/setup.py
+++ b/pw_build/py/setup.py
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2020 The Pigweed Authors
 #
 # 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
@@ -15,4 +15,24 @@
 
 import setuptools  # type: ignore
 
-setuptools.setup()  # Package definition in setup.cfg
+setuptools.setup(
+    name='pw_build',
+    version='0.0.1',
+    author='Pigweed Authors',
+    author_email='pigweed-developers@googlegroups.com',
+    description='Python scripts that support the GN build',
+    packages=setuptools.find_packages(),
+    package_data={'pw_build': ['py.typed']},
+    zip_safe=False,
+    install_requires=[
+        'wheel',
+        'pw_cli',
+        'pw_env_setup',
+        'pw_presubmit',
+    ],
+    entry_points={
+        'console_scripts': [
+            'copy_from_cipd = pw_build.copy_from_cipd:main',
+        ],
+    },
+)
diff --git a/pw_build/python.gni b/pw_build/python.gni
index 0f8de8f..038d9a2 100644
--- a/pw_build/python.gni
+++ b/pw_build/python.gni
@@ -17,23 +17,6 @@
 import("$dir_pw_build/input_group.gni")
 import("$dir_pw_build/mirror_tree.gni")
 import("$dir_pw_build/python_action.gni")
-import("$dir_pw_protobuf_compiler/toolchain.gni")
-
-declare_args() {
-  # Python tasks, such as running tests and Pylint, are done in a single GN
-  # toolchain to avoid unnecessary duplication in the build.
-  pw_build_PYTHON_TOOLCHAIN = "$dir_pw_build/python_toolchain:python"
-
-  # Constraints file selection (arguments to pip install --constraint).
-  # See pip help install.
-  pw_build_PIP_CONSTRAINTS =
-      [ "$dir_pw_env_setup/py/pw_env_setup/virtualenv_setup/constraint.list" ]
-
-  # If true, GN will run each Python test using the coverage command. A separate
-  # coverage data file for each test will be saved. To generate reports from
-  # this information run: pw presubmit --step gn_python_test_coverage
-  pw_build_PYTHON_TEST_COVERAGE = false
-}
 
 # Python packages provide the following targets as $target_name.$subtarget.
 pw_python_package_subtargets = [
@@ -74,17 +57,17 @@
     ]
 
     if (defined(invoker.mypy_ini)) {
-      args +=
-          [ "--config-file=" + rebase_path(invoker.mypy_ini, root_build_dir) ]
+      args += [ "--config-file=" + rebase_path(invoker.mypy_ini) ]
       inputs = [ invoker.mypy_ini ]
     }
 
-    args += rebase_path(invoker.sources, root_build_dir)
+    args += rebase_path(invoker.sources)
 
     # Use this environment variable to force mypy to colorize output.
     # See https://github.com/python/mypy/issues/7771
     environment = [ "MYPY_FORCE_COLOR=1" ]
 
+    directory = invoker.directory
     stamp = true
 
     deps = invoker.deps
@@ -102,13 +85,13 @@
   pw_python_action_foreach(target_name) {
     module = "pylint"
     args = [
-      rebase_path(".", root_build_dir) + "/{{source_target_relative}}",
+      rebase_path(".") + "/{{source_target_relative}}",
       "--jobs=1",
       "--output-format=colorized",
     ]
 
     if (defined(invoker.pylintrc)) {
-      args += [ "--rcfile=" + rebase_path(invoker.pylintrc, root_build_dir) ]
+      args += [ "--rcfile=" + rebase_path(invoker.pylintrc) ]
       inputs = [ invoker.pylintrc ]
     }
 
@@ -118,6 +101,7 @@
     }
 
     sources = invoker.sources
+    directory = invoker.directory
 
     stamp = "$target_gen_dir/{{source_target_relative}}.pylint.passed"
 
@@ -141,9 +125,8 @@
 #   - $name.install - Installs the package in a venv.
 #   - $name.wheel - Builds a Python wheel for the package.
 #
-# All Python packages are instantiated with in pw_build_PYTHON_TOOLCHAIN,
-# regardless of the current toolchain. This prevents Python-specific work, like
-# running Pylint, from occurring multiple times in a build.
+# All Python packages are instantiated with the default toolchain, regardless of
+# the current toolchain.
 #
 # Args:
 #   setup: List of setup file paths (setup.py or pyproject.toml & setup.cfg),
@@ -160,17 +143,17 @@
 #       generate_setup is required in place of setup if proto_library is used.
 #   static_analysis: List of static analysis tools to run; "*" (default) runs
 #       all tools. The supported tools are "mypy" and "pylint".
-#   pylintrc: Path to a pylintrc configuration file to use. If not
-#       provided, Pylint's default rcfile search is used. As this may
-#       use the the local user's configuration file, it is highly
-#       recommended to pass this option to specify the rcfile explicitly.
+#   pylintrc: Optional path to a pylintrc configuration file to use. If not
+#       provided, Pylint's default rcfile search is used. Pylint is executed
+#       from the package's setup directory, so pylintrc files in that directory
+#       will take precedence over others.
 #   mypy_ini: Optional path to a mypy configuration file to use. If not
 #       provided, mypy's default configuration file search is used. mypy is
 #       executed from the package's setup directory, so mypy.ini files in that
 #       directory will take precedence over others.
 #
 template("pw_python_package") {
-  # The Python targets are always instantiated in pw_build_PYTHON_TOOLCHAIN. Use
+  # The Python targets are always instantiated in the default toolchain. Use
   # fully qualified labels so that the toolchain is not lost.
   _other_deps = []
   if (defined(invoker.other_deps)) {
@@ -205,11 +188,6 @@
                  "use 'generate_setup' instead of 'setup'")
 
       _import_protos = [ invoker.proto_library ]
-
-      # Depend on the dependencies of the proto library.
-      _proto = get_label_info(invoker.proto_library, "label_no_toolchain")
-      _toolchain = get_label_info(invoker.proto_library, "toolchain")
-      _python_deps += [ "$_proto.python._deps($_toolchain)" ]
     } else if (defined(invoker.generate_setup)) {
       _import_protos = []
     }
@@ -258,6 +236,19 @@
     _static_analysis = invoker.static_analysis
   }
 
+  # TODO(hepler): Remove support for the lint option.
+  if (defined(invoker.lint)) {
+    assert(!defined(invoker.static_analysis),
+           "'lint' is deprecated; use 'static_analysis' instead")
+
+    # Only allow 'lint = false', for backwards compatibility.
+    assert(invoker.lint == false, "'lint' is deprecated; use 'static_analysis'")
+    print("WARNING:",
+          "The 'lint' option for pw_python_package is deprecated.",
+          "Instead, use 'static_analysis = []' to disable linting.")
+    _static_analysis = []
+  }
+
   foreach(_tool, _static_analysis) {
     assert(_supported_static_analysis_tools + [ _tool ] - [ _tool ] !=
                _supported_static_analysis_tools,
@@ -293,10 +284,7 @@
   if (defined(invoker.setup)) {
     _setup_sources = invoker.setup
   } else if (_generate_package) {
-    _setup_sources = [
-      "$_setup_dir/pyproject.toml",
-      "$_setup_dir/setup.cfg",
-    ]
+    _setup_sources = [ "$_setup_dir/setup.py" ]
   }
 
   # Argument: python_test_deps (list)
@@ -314,46 +302,11 @@
     not_needed([ "_python_test_deps" ])
   }
 
-  _all_py_files =
-      _sources + _test_sources + filter_include(_setup_sources, [ "*.py" ])
+  _all_py_files = _sources + _test_sources + _setup_sources
 
-  # The pw_python_package subtargets are only instantiated in
-  # pw_build_PYTHON_TOOLCHAIN. Targets in other toolchains just refer to the
-  # targets in this toolchain.
-  if (current_toolchain == pw_build_PYTHON_TOOLCHAIN) {
-    # Create the package_metadata.json file. This is used by the
-    # pw_create_python_source_tree template.
-    _package_metadata_json_file =
-        "$target_gen_dir/$target_name/package_metadata.json"
-
-    # Get Python package metadata and write to disk as JSON.
-    _package_metadata = {
-      gn_target_name = get_label_info(invoker.target_name, "label_no_toolchain")
-
-      # Get package source files
-      sources = rebase_path(_sources, root_build_dir)
-
-      # Get setup.cfg, pyproject.toml, or setup.py file
-      setup_sources = rebase_path(_setup_sources, root_build_dir)
-
-      # Get test source files
-      tests = rebase_path(_test_sources, root_build_dir)
-
-      # Get package input files (package data)
-      inputs = []
-      if (defined(invoker.inputs)) {
-        inputs = rebase_path(invoker.inputs, root_build_dir)
-      }
-
-      # Get generate_setup
-      if (defined(invoker.generate_setup)) {
-        generate_setup = invoker.generate_setup
-      }
-    }
-
-    # Finally, write out the json
-    write_file(_package_metadata_json_file, _package_metadata, "json")
-
+  # The pw_python_package subtargets are only instantiated in the default
+  # toolchain. Other toolchains just refer to targets in the default toolchain.
+  if (current_toolchain == default_toolchain) {
     # Declare the main Python package group. This represents the Python files,
     # but does not take any actions. GN targets can depend on the package name
     # to run when any files in the package change.
@@ -369,70 +322,57 @@
         if (defined(invoker.tests)) {
           sources += invoker.tests
         }
-        if (defined(invoker.inputs)) {
-          sources += invoker.inputs
-        }
 
         source_root = _source_root
         public_deps = _python_deps + _other_deps
       }
 
-      # Get generated_setup scope and write it to disk as JSON.
+      # Depend on the proto's _gen targets (from the default toolchain).
+      _gen_protos = []
+      foreach(proto, _import_protos) {
+        _gen_protos +=
+            [ get_label_info(proto, "label_no_toolchain") + ".python._gen" ]
+      }
 
-      # Expected setup.cfg structure:
-      # https://setuptools.readthedocs.io/en/latest/userguide/declarative_config.html
+      generated_file("$target_name._protos") {
+        deps = _gen_protos
+        data_keys = [ "protoc_outputs" ]
+        outputs = [ "$_setup_dir/protos.txt" ]
+      }
+
+      _protos_file = get_target_outputs(":${invoker.target_name}._protos")
+
+      generated_file("$target_name._protos_root") {
+        deps = _gen_protos
+        data_keys = [ "root" ]
+        outputs = [ "$_setup_dir/proto_root.txt" ]
+      }
+
+      _root_file = get_target_outputs(":${invoker.target_name}._protos_root")
+
+      # Get generated_setup scope and write it to disk ask JSON.
       _gen_setup = invoker.generate_setup
-      assert(defined(_gen_setup.metadata),
-             "'metadata = {}' is required in generate_package")
-
-      # Get metadata which should contain at least name.
-      _gen_metadata = {
-      }
-      _gen_metadata = _gen_setup.metadata
-      assert(
-          defined(_gen_metadata.name),
-          "metadata = { name = 'package_name' } is required in generate_package")
-
-      # Get options which should not have packages or package_data.
-      if (defined(_gen_setup.options)) {
-        _gen_options = {
-        }
-        _gen_options = _gen_setup.options
-        assert(!defined(_gen_options.packages) &&
-                   !defined(_gen_options.package_data),
-               "'packages' and 'package_data' may not be provided " +
-                   "in 'generate_package' options.")
-      }
-
+      assert(defined(_gen_setup.name), "'name' is required in generate_package")
+      assert(!defined(_gen_setup.packages) && !defined(_gen_setup.package_data),
+             "'packages' and 'package_data' may not be provided " +
+                 "in 'generate_package'")
       write_file("$_setup_dir/setup.json", _gen_setup, "json")
 
       # Generate the setup.py, py.typed, and __init__.py files as needed.
       action(target_name) {
-        metadata = {
-          pw_python_package_metadata_json = [ _package_metadata_json_file ]
-        }
-
         script = "$dir_pw_build/py/pw_build/generate_python_package.py"
         args = [
                  "--label",
                  get_label_info(":$target_name", "label_no_toolchain"),
-                 "--generated-root",
-                 rebase_path(_setup_dir, root_build_dir),
+                 "--root",
+                 rebase_path(_setup_dir),
                  "--setup-json",
-                 rebase_path("$_setup_dir/setup.json", root_build_dir),
-               ] + rebase_path(_sources, root_build_dir)
-
-        # Pass in the .json information files for the imported proto libraries.
-        foreach(proto, _import_protos) {
-          _label = get_label_info(proto, "label_no_toolchain") +
-                   ".python($pw_protobuf_compiler_TOOLCHAIN)"
-          _file = get_label_info(_label, "target_gen_dir") + "/" +
-                  get_label_info(_label, "name") + ".json"
-          args += [
-            "--proto-library",
-            rebase_path(_file, root_build_dir),
-          ]
-        }
+                 rebase_path("$_setup_dir/setup.json"),
+                 "--file-list",
+                 rebase_path(_protos_file[0]),
+                 "--file-list-root",
+                 rebase_path(_root_file[0]),
+               ] + rebase_path(_sources)
 
         if (defined(invoker._pw_module_as_package) &&
             invoker._pw_module_as_package) {
@@ -441,16 +381,31 @@
 
         inputs = [ "$_setup_dir/setup.json" ]
 
-        public_deps = [ ":$target_name._mirror_sources_to_out_dir" ]
+        public_deps = [
+          ":$target_name._mirror_sources_to_out_dir",
+          ":$target_name._protos",
+          ":$target_name._protos_root",
+        ]
+
+        foreach(proto, _import_protos) {
+          _tgt = get_label_info(proto, "label_no_toolchain")
+          _path = get_label_info("$_tgt($default_toolchain)", "target_gen_dir")
+          _name = get_label_info(_tgt, "name")
+
+          args += [
+            "--proto-library=$_tgt",
+            "--proto-library-file",
+            rebase_path("$_path/$_name.proto_library/python_package.txt"),
+          ]
+
+          public_deps += [ "$_tgt.python._gen($default_toolchain)" ]
+        }
 
         outputs = _setup_sources
       }
     } else {
       # If the package is not generated, use an input group for the sources.
       pw_input_group(target_name) {
-        metadata = {
-          pw_python_package_metadata_json = [ _package_metadata_json_file ]
-        }
         inputs = _all_py_files
         if (defined(invoker.inputs)) {
           inputs += invoker.inputs
@@ -466,45 +421,26 @@
       pw_python_action("$target_name._run_pip_install") {
         module = "pip"
         public_deps = []
-        if (defined(invoker.public_deps)) {
-          public_deps += invoker.public_deps
-        }
 
-        args = [
-          "install",
-
-          # This speeds up pip installs. At this point in the gn build the
-          # virtualenv is already activated so build isolation isn't required.
-          # This requires that pip, setuptools, and wheel packages are
-          # installed.
-          "--no-build-isolation",
-        ]
-
-        inputs = pw_build_PIP_CONSTRAINTS
-        foreach(_constraints_file, pw_build_PIP_CONSTRAINTS) {
-          args += [
-            "--constraint",
-            rebase_path(_constraints_file, root_build_dir),
-          ]
-        }
+        args = [ "install" ]
 
         # For generated packages, reinstall when any files change. For regular
         # packages, only reinstall when setup.py changes.
         if (_generate_package) {
           public_deps += [ ":${invoker.target_name}" ]
         } else {
-          inputs += invoker.setup
+          inputs = invoker.setup
 
           # Install with --editable since the complete package is in source.
           args += [ "--editable" ]
         }
 
-        args += [ rebase_path(_setup_dir, root_build_dir) ]
+        args += [ rebase_path(_setup_dir) ]
 
         stamp = true
 
         # Parallel pip installations don't work, so serialize pip invocations.
-        pool = "$dir_pw_build/pool:pip($default_toolchain)"
+        pool = "$dir_pw_build:pip_pool"
 
         foreach(dep, _python_deps) {
           # We need to add a suffix to the target name, but the label is
@@ -524,13 +460,12 @@
 
         module = "build"
 
-        args =
-            [
-              rebase_path(_setup_dir, root_build_dir),
-              "--wheel",
-              "--no-isolation",
-              "--outdir",
-            ] + rebase_path(metadata.pw_python_package_wheels, root_build_dir)
+        args = [
+                 rebase_path(_setup_dir),
+                 "--wheel",
+                 "--no-isolation",
+                 "--outdir",
+               ] + rebase_path(metadata.pw_python_package_wheels)
 
         deps = [ ":${invoker.target_name}" ]
         foreach(dep, _python_deps) {
@@ -603,6 +538,12 @@
         deps = _test_install_deps
         python_deps = _python_deps
 
+        if (defined(_setup_dir)) {
+          directory = rebase_path(_setup_dir)
+        } else {
+          directory = rebase_path(".")
+        }
+
         _optional_variables = [
           "mypy_ini",
           "pylintrc",
@@ -634,13 +575,12 @@
     #
     # The $target_name.tests group is created separately below.
     group("$target_name") {
-      deps = [ ":$target_name($pw_build_PYTHON_TOOLCHAIN)" ]
+      deps = [ ":$target_name($default_toolchain)" ]
     }
 
     foreach(subtarget, pw_python_package_subtargets - [ "tests" ]) {
       group("$target_name.$subtarget") {
-        deps =
-            [ ":${invoker.target_name}.$subtarget($pw_build_PYTHON_TOOLCHAIN)" ]
+        deps = [ ":${invoker.target_name}.$subtarget($default_toolchain)" ]
       }
     }
 
@@ -662,35 +602,9 @@
 
     _test_target = "$target_name.tests." + string_replace(_name, "/", "_")
 
-    if (current_toolchain == pw_build_PYTHON_TOOLCHAIN) {
+    if (current_toolchain == default_toolchain) {
       pw_python_action(_test_target) {
-        if (pw_build_PYTHON_TEST_COVERAGE) {
-          module = "coverage"
-          working_directory =
-              rebase_path(get_path_info(test, "dir"), root_build_dir)
-          args = [
-            "run",
-            "--branch",
-
-            # Include all source files in the working_directory when calculating coverage.
-            "--source=.",
-
-            # Test file to run.
-            get_path_info(test, "file"),
-          ]
-
-          # Set the coverage file to a location in out/python/gen/
-          _coverage_data_file = "$target_gen_dir/$target_name.coverage"
-          outputs = [ _coverage_data_file ]
-
-          # The coverage tool only allows setting the output with an environment variable.
-          environment =
-              [ "COVERAGE_FILE=" +
-                rebase_path(_coverage_data_file, get_path_info(test, "dir")) ]
-        } else {
-          script = test
-        }
-
+        script = test
         stamp = true
 
         deps = _test_install_deps
@@ -703,7 +617,7 @@
       # Create a public version of each test target, so tests can be executed as
       # //path/to:package.tests.foo.py.
       group(_test_target) {
-        deps = [ ":$_test_target($pw_build_PYTHON_TOOLCHAIN)" ]
+        deps = [ ":$_test_target($default_toolchain)" ]
       }
     }
 
@@ -727,15 +641,10 @@
     _python_deps = invoker.python_deps
   } else {
     _python_deps = []
-    not_needed([ "invoker" ])  # Allow empty groups.
   }
 
   group(target_name) {
     deps = _python_deps
-
-    if (defined(invoker.other_deps)) {
-      deps += invoker.other_deps
-    }
   }
 
   foreach(subtarget, pw_python_package_subtargets) {
@@ -763,14 +672,8 @@
 # pw_python_script provides the same subtargets as pw_python_package, but
 # $target_name.install and $target_name.wheel only affect the python_deps of
 # this GN target, not the target itself.
-#
-# pw_python_script allows creating a pw_python_action associated with the
-# script. This is provided by passing an 'action' scope to pw_python_script.
-# This functions like a normal action, with a few additions: the action uses the
-# pw_python_script's python_deps and defaults to using the source file as its
-# 'script' argument, if there is only a single source file.
 template("pw_python_script") {
-  _package_variables = [
+  _supported_variables = [
     "sources",
     "tests",
     "python_deps",
@@ -783,26 +686,11 @@
 
   pw_python_package(target_name) {
     _pw_standalone = true
-    forward_variables_from(invoker, _package_variables)
+    forward_variables_from(invoker, _supported_variables)
   }
 
   _pw_create_aliases_if_name_matches_directory(target_name) {
   }
-
-  if (defined(invoker.action)) {
-    pw_python_action("$target_name.action") {
-      forward_variables_from(invoker.action, "*", [ "python_deps" ])
-      python_deps = [ ":${invoker.target_name}" ]
-
-      if (!defined(script) && !defined(module) && defined(invoker.sources)) {
-        _sources = invoker.sources
-        assert(_sources != [] && _sources == [ _sources[0] ],
-               "'script' must be specified unless there is only one source " +
-                   "in 'sources'")
-        script = _sources[0]
-      }
-    }
-  }
 }
 
 # Represents a list of Python requirements, as in a requirements.txt.
@@ -843,19 +731,11 @@
     foreach(_requirements_file, inputs) {
       args += [
         "--requirement",
-        rebase_path(_requirements_file, root_build_dir),
+        rebase_path(_requirements_file),
       ]
     }
 
-    inputs += pw_build_PIP_CONSTRAINTS
-    foreach(_constraints_file, pw_build_PIP_CONSTRAINTS) {
-      args += [
-        "--constraint",
-        rebase_path(_constraints_file, root_build_dir),
-      ]
-    }
-
-    pool = "$dir_pw_build/pool:pip($default_toolchain)"
+    pool = "$dir_pw_build:pip_pool"
     stamp = true
   }
 
diff --git a/pw_build/python.rst b/pw_build/python.rst
index 3eb9b5f..09b92a2 100644
--- a/pw_build/python.rst
+++ b/pw_build/python.rst
@@ -4,12 +4,10 @@
 Python GN templates
 -------------------
 The Python build is implemented with GN templates defined in
-``pw_build/python.gni``. See the .gni file for complete usage documentation.
+``pw_build/python.gni``. That file contains the complete usage documentation.
 
 .. seealso:: :ref:`docs-python-build`
 
-.. _module-pw_build-pw_python_package:
-
 pw_python_package
 =================
 The main Python template is ``pw_python_package``. Each ``pw_python_package``
@@ -33,32 +31,18 @@
   //path/to/my_python_package:my_python_package.tests
   //path/to/my_python_package:tests
 
-The actions in a ``pw_python_package`` (e.g. installing packages and running
-Pylint) are done within a single GN toolchain to avoid duplication in
-multi-toolchain builds. This toolchain can be set with the
-``pw_build_PYTHON_TOOLCHAIN`` GN arg, which defaults to
-``$dir_pw_build/python_toolchain:python``.
-
 Arguments
 ---------
 - ``setup`` - List of setup file paths (setup.py or pyproject.toml & setup.cfg),
   which must all be in the same directory.
 - ``generate_setup``: As an alternative to ``setup``, generate setup files with
-  the keywords in this scope. ``name`` is required. This follows the same
-  structure as a ``setup.cfg`` file's `declarative config
-  <https://setuptools.readthedocs.io/en/latest/userguide/declarative_config.html>`_
-  For example:
+  the keywords in this scope. ``name`` is required. For example:
 
   .. code-block::
 
     generate_setup = {
-      metadata = {
-        name = "a_nifty_package"
-        version = "1.2a"
-      }
-      options = {
-        install_requires = [ "a_pip_package" ]
-      }
+      name = "a_nifty_package"
+      version = "1.2a"
     }
 
 - ``sources`` - Python sources files in the package.
@@ -92,11 +76,7 @@
   import("$dir_pw_build/python.gni")
 
   pw_python_package("py") {
-    setup = [
-      "pyproject.toml",
-      "setup.cfg",
-      "setup.py",
-    ]
+    setup = [ "setup.py" ]
     sources = [
       "pw_my_module/__init__.py",
       "pw_my_module/alfa.py",
@@ -115,6 +95,30 @@
     pylintrc = "$dir_pigweed/.pylintrc"
   }
 
+
+.. _module-pw_build-python-wheels:
+
+Collecting Python wheels for distribution
+-----------------------------------------
+The ``.wheel`` subtarget generates a wheel (``.whl``) for the Python package.
+Wheels for a package and its transitive dependencies can be collected by
+traversing the ``pw_python_package_wheels`` `GN metadata
+<https://gn.googlesource.com/gn/+/master/docs/reference.md#var_metadata>`_ key,
+which lists the output directory for each wheel.
+
+The ``pw_mirror_tree`` template can be used to collect wheels in an output
+directory:
+
+.. code-block::
+
+  import("$dir_pw_build/mirror_tree.gni")
+
+  pw_mirror_tree("my_wheels") {
+    path_data_keys = [ "pw_python_package_wheels" ]
+    deps = [ ":python_packages.wheel" ]
+    directory = "$root_out_dir/the_wheels"
+  }
+
 pw_python_script
 ================
 A ``pw_python_script`` represents a set of standalone Python scripts and/or
@@ -122,25 +126,6 @@
 those ``setup``. These targets can be installed, but this only installs their
 dependencies.
 
-``pw_python_script`` allows creating a
-:ref:`pw_python_action <module-pw_build-python-action>` associated with the
-script. To create an action, pass an ``action`` scope to ``pw_python_script``.
-If there is only a single source file, it serves as the action's ``script`` by
-default.
-
-An action in ``pw_python_script`` can always be replaced with a standalone
-``pw_python_action``, but using the embedded action has some advantages:
-
-- The embedded action target bridges the gap between actions and Python targets.
-  A Python script can be expressed in a single, concise GN target, rather than
-  in two overlapping, dependent targets.
-- The action automatically depends on the ``pw_python_script``. This ensures
-  that the script's dependencies are installed and the action automatically
-  reruns when the script's sources change, without needing to specify a
-  dependency, a step which is easy to forget.
-- Using a ``pw_python_script`` with an embedded action is a simple way to check
-  an existing action's script with Pylint or Mypy or to add tests.
-
 pw_python_group
 ===============
 Represents a group of ``pw_python_package`` and ``pw_python_script`` targets.
@@ -151,191 +136,3 @@
 ======================
 Represents a set of local and PyPI requirements, with no associated source
 files. These targets serve the role of a ``requirements.txt`` file.
-
-When packages are installed by Pigweed, additional version constraints can be
-provided using the ``pw_build_PIP_CONSTRAINTS`` GN arg. This option should
-contain a list of paths to pass to the ``--constraint`` option of ``pip
-install``. This can be used to synchronize dependency upgrades across a project
-which facilitates reproducibility of builds.
-
-Note using multiple ``pw_python_requirements`` that install different versions
-of the same package will currently cause unpredictable results, while using
-constraints should have correct results (which may be an error indicating a
-conflict).
-
-.. _module-pw_build-python-dist:
-
----------------------
-Python Distributables
----------------------
-Pigweed also provides some templates to make it easier to bundle Python packages
-for deployment. These templates are found in ``pw_build/python_dist.gni``. See
-the .gni file for complete documentation on building distributables.
-
-pw_python_wheels
-================
-Collects Python wheels for one or more ``pw_python_package`` targets, plus any
-additional ``pw_python_package`` targets they depend on, directly or indirectly.
-Note that this does not include Python dependencies that come from outside the
-GN build, like packages from PyPI, for example. Those should still be declared
-in the package's ``setup.py`` file as usual.
-
-Arguments
----------
-- ``packages`` - List of ``pw_python_package`` targets whose wheels should be
-  included; their dependencies will be pulled in as wheels also.
-- ``directory`` - Output directory for the collected wheels. Defaults to
-  ``$target_out_dir/$target_name``.
-
-Wheel collection under the hood
--------------------------------
-The ``.wheel`` subtarget of every ``pw_python_package`` generates a wheel
-(``.whl``) for the Python package. The ``pw_python_wheels`` template figures
-out which wheels to collect by traversing the ``pw_python_package_wheels``
-`GN metadata
-<https://gn.googlesource.com/gn/+/HEAD/docs/reference.md#var_metadata>`_ key,
-which lists the output directory for each wheel.
-
-pw_python_zip_with_setup
-========================
-Generates a ``.zip`` archive suitable for deployment outside of the project's
-developer environment. The generated ``.zip`` contains Python wheels
-(``.whl`` files) for one or more ``pw_python_package`` targets, plus wheels for
-any additional ``pw_python_package`` targets in the GN build they depend on,
-directly or indirectly. Dependencies from outside the GN build, such as packages
-from PyPI, must be listed in packages' ``setup.py`` or ``setup.cfg`` files as
-usual.
-
-The ``.zip`` also includes simple setup scripts for Linux,
-MacOS, and Windows. The setup scripts automatically create a Python virtual
-environment and install the whole collection of wheels into it using ``pip``.
-
-Optionally, additional files and directories can be included in the archive.
-One common example of an additional file to include is a README file with setup
-and usage instructions for the distributable. A simple ready-to-use README file
-is available at ``pw_build/py_dist/README.md``.
-
-Arguments
----------
-- ``packages`` - A list of `pw_python_package` targets whose wheels should be
-  included; their dependencies will be pulled in as wheels also.
-- ``inputs`` - An optional list of extra files to include in the generated
-  ``.zip``, formatted the same way as the ``inputs`` argument to ``pw_zip``
-  targets.
-- ``dirs`` - An optional list of directories to include in the generated
-  ``.zip``, formatted the same was as the ``dirs`` argument to ``pw_zip``
-  targets.
-
-Example
--------
-
-.. code-block::
-
-  import("//build_overrides/pigweed.gni")
-
-  import("$dir_pw_build/python_dist.gni")
-
-  pw_python_zip_with_setup("my_tools") {
-    packages = [ ":some_python_package" ]
-    inputs = [ "$dir_pw_build/python_dist/README.md > /${target_name}/" ]
-  }
-
-pw_create_python_source_tree
-============================
-
-Generates a directory of Python packages from source files suitable for
-deployment outside of the project developer environment. The resulting directory
-contains only files mentioned in each package's ``setup.cfg`` file. This is
-useful for bundling multiple Python packages up into a single package for
-distribution to other locations like `<http://pypi.org>`_.
-
-Arguments
----------
-
-- ``packages`` - A list of :ref:`module-pw_build-pw_python_package` targets to be installed into
-  the build directory. Their dependencies will be pulled in as wheels also.
-
-- ``include_tests`` - If true, copy Python package tests to a ``tests`` subdir.
-
-- ``extra_files`` - A list of extra files that should be included in the output.
-  The format of each item in this list follows this convention:
-
-  .. code-block:: text
-
-     //some/nested/source_file > nested/destination_file
-
-  - Source and destination file should be separated by ``>``.
-
-  - The source file should be a GN target label (starting with ``//``).
-
-  - The destination file will be relative to the generated output
-    directory. Parent directories are automatically created for each file. If a
-    file would be overwritten an error is raised.
-
-- ``generate_setup_cfg`` - If included, create a merged ``setup.cfg`` for all
-  python Packages using a ``common_config_file`` as a base. That file should
-  contain the required fields in the ``metadata`` and ``options`` sections as
-  shown in
-  `Configuring setup() using setup.cfg files <https://setuptools.pypa.io/en/latest/userguide/declarative_config.html>`_.
-  ``append_git_sha_to_version`` and ``append_date_to_version`` will optionally
-  append the current git SHA or date to the package version string after a ``+``
-  sign.
-
-  .. code-block::
-
-     generate_setup_cfg = {
-       common_config_file = "pypi_common_setup.cfg"
-       append_git_sha_to_version = true
-       append_date_to_version = true
-     }
-
-Example
--------
-
-:octicon:`file;1em` ./pw_env_setup/BUILD.gn
-
-.. code-block::
-
-   import("//build_overrides/pigweed.gni")
-
-   import("$dir_pw_build/python_dist.gni")
-
-   pw_create_python_source_tree("build_python_source_tree") {
-     packages = [
-       ":some_python_package",
-       ":another_python_package",
-     ]
-     include_tests = true
-     extra_files = [
-       "//README.md > ./README.md",
-       "//some_python_package/py/BUILD.bazel > some_python_package/BUILD.bazel",
-       "//another_python_package/py/BUILD.bazel > another_python_package/BUILD.bazel",
-     ]
-     generate_setup_cfg = {
-       common_config_file = "pypi_common_setup.cfg"
-       append_git_sha_to_version = true
-       append_date_to_version = true
-     }
-   }
-
-:octicon:`file-directory;1em` ./out/obj/pw_env_setup/build_python_source_tree/
-
-.. code-block:: text
-
-   $ tree ./out/obj/pw_env_setup/build_python_source_tree/
-   ├── README.md
-   ├── setup.cfg
-   ├── some_python_package
-   │   ├── BUILD.bazel
-   │   ├── __init__.py
-   │   ├── py.typed
-   │   ├── some_source_file.py
-   │   └── tests
-   │       └── some_source_test.py
-   └── another_python_package
-       ├── BUILD.bazel
-       ├── __init__.py
-       ├── another_source_file.py
-       ├── py.typed
-       └── tests
-           └── another_source_test.py
diff --git a/pw_build/python_action.gni b/pw_build/python_action.gni
index 1110535..05b1e90 100644
--- a/pw_build/python_action.gni
+++ b/pw_build/python_action.gni
@@ -29,8 +29,12 @@
 #
 #   stamp           File to touch if the script is successful. Actions that
 #                   don't create output files can use this stamp file instead of
-#                   creating their own placeholder file. If true, a generic file
-#                   is used. If false or not set, no file is touched.
+#                   creating their own dummy file. If true, a generic file is
+#                   used. If false or not set, no file is touched.
+#
+#   directory       The directory from which to execute the Python script. Paths
+#                   in args may need to be adjusted to be relative to this
+#                   directory.
 #
 #   environment     Environment variables to set, passed as a list of NAME=VALUE
 #                   strings.
@@ -48,32 +52,28 @@
 #
 #   python_deps     Dependencies on pw_python_package or related Python targets.
 #
-#   working_directory  Switch to the provided working directory before running
-#                      the Python script or action.
-#
 template("pw_python_action") {
-  assert(defined(invoker.script) != defined(invoker.module),
-         "pw_python_action requires either 'script' or 'module'")
-
   _script_args = [
     # GN root directory relative to the build directory (in which the runner
     # script is invoked).
     "--gn-root",
-    rebase_path("//", root_build_dir),
+    rebase_path("//"),
 
     # Current directory, used to resolve relative paths.
     "--current-path",
-    rebase_path(".", root_build_dir),
-
-    # pip lockfile, prevents pip from running in parallel with other Python
-    # actions.
-    "--lockfile",
-    rebase_path("$root_out_dir/pip.lock", root_build_dir),
+    rebase_path("."),
 
     "--default-toolchain=$default_toolchain",
     "--current-toolchain=$current_toolchain",
   ]
 
+  if (defined(invoker.directory)) {
+    _script_args += [
+      "--directory",
+      rebase_path(invoker.directory),
+    ]
+  }
+
   if (defined(invoker.environment)) {
     foreach(variable, invoker.environment) {
       _script_args += [ "--env=$variable" ]
@@ -109,7 +109,7 @@
     _outputs += [ _stamp_file ]
     _script_args += [
       "--touch",
-      rebase_path(_stamp_file, root_build_dir),
+      rebase_path(_stamp_file),
     ]
   }
 
@@ -125,20 +125,13 @@
     ]
   }
 
-  if (defined(invoker.working_directory)) {
-    _script_args += [
-      "--working-directory",
-      invoker.working_directory,
-    ]
-  }
-
   # "--" indicates the end of arguments to the runner script.
   # Everything beyond this point is interpreted as the command and arguments
   # of the Python script to run.
   _script_args += [ "--" ]
 
   if (defined(invoker.script)) {
-    _script_args += [ rebase_path(invoker.script, root_build_dir) ]
+    _script_args += [ rebase_path(invoker.script) ]
   }
 
   if (defined(invoker.args)) {
@@ -162,10 +155,6 @@
       _deps += [ get_label_info(dep, "label_no_toolchain") + ".install(" +
                  get_label_info(dep, "toolchain") + ")" ]
     }
-
-    # Add the base target as a dep so the action reruns when any source files
-    # change, even if the package does not have to be reinstalled.
-    _deps += invoker.python_deps
   }
 
   target(_action_type, target_name) {
diff --git a/pw_build/python_dist.gni b/pw_build/python_dist.gni
deleted file mode 100644
index 36f797a..0000000
--- a/pw_build/python_dist.gni
+++ /dev/null
@@ -1,257 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/python.gni")
-import("$dir_pw_build/python_action.gni")
-import("$dir_pw_build/zip.gni")
-
-# Builds a directory containing a collection of Python wheels.
-#
-# Given one or more pw_python_package targets, this target will build their
-# .wheel sub-targets along with the .wheel sub-targets of all dependencies,
-# direct and indirect, as understood by GN. The resulting .whl files will be
-# collected into a single directory called 'python_wheels'.
-#
-# Args:
-#   packages: A list of pw_python_package targets whose wheels should be
-#       included; their dependencies will be pulled in as wheels also.
-#   directory: output directory for the wheels; defaults to
-#       $target_out_dir/$target_name
-#   deps: additional dependencies
-#
-template("pw_python_wheels") {
-  _wheel_paths_path = "${target_gen_dir}/${target_name}_wheel_paths.txt"
-
-  _deps = []
-  if (defined(invoker.deps)) {
-    _deps = invoker.deps
-  }
-
-  if (defined(invoker.directory)) {
-    _directory = invoker.directory
-  } else {
-    _directory = "$target_out_dir/$target_name"
-  }
-
-  _packages = []
-  foreach(_pkg, invoker.packages) {
-    _pkg_name = get_label_info(_pkg, "label_no_toolchain")
-    _pkg_toolchain = get_label_info(_pkg, "toolchain")
-    _packages += [ "${_pkg_name}.wheel(${_pkg_toolchain})" ]
-  }
-
-  # Build a list of relative paths containing all the wheels we depend on.
-  generated_file("${target_name}._wheel_paths") {
-    data_keys = [ "pw_python_package_wheels" ]
-    rebase = root_build_dir
-    deps = _packages
-    outputs = [ _wheel_paths_path ]
-  }
-
-  pw_python_action(target_name) {
-    forward_variables_from(invoker, [ "public_deps" ])
-    deps = _deps + [ ":$target_name._wheel_paths" ]
-    module = "pw_build.collect_wheels"
-
-    args = [
-      "--prefix",
-      rebase_path(root_build_dir, root_build_dir),
-      "--suffix",
-      rebase_path(_wheel_paths_path, root_build_dir),
-      "--out_dir",
-      rebase_path(_directory, root_build_dir),
-    ]
-
-    stamp = true
-  }
-}
-
-# Builds a .zip containing Python wheels and setup scripts.
-#
-# The resulting .zip archive will contain a directory with Python wheels for
-# all pw_python_package targets listed in 'packages', plus wheels for any
-# pw_python_package targets those packages depend on, directly or indirectly,
-# as understood by GN.
-#
-# In addition to Python wheels, the resulting .zip will also contain simple
-# setup scripts for Linux, MacOS, and Windows that take care of creating a
-# Python venv and installing all the included wheels into it, and a README.md
-# file with setup and usage instructions.
-#
-# Args:
-#   packages: A list of pw_python_package targets whose wheels should be
-#       included; their dependencies will be pulled in as wheels also.
-#   inputs: An optional list of extra files to include in the generated .zip,
-#       formatted the same was as the 'inputs' argument to pw_zip targets.
-#   dirs: An optional list of directories to include in the generated .zip,
-#       formatted the same way as the 'dirs' argument to pw_zip targets.
-template("pw_python_zip_with_setup") {
-  _outer_name = target_name
-  _zip_path = "${target_out_dir}/${target_name}.zip"
-
-  _inputs = []
-  if (defined(invoker.inputs)) {
-    _inputs = invoker.inputs
-  }
-  _dirs = []
-  if (defined(invoker.dirs)) {
-    _dirs = invoker.dirs
-  }
-  _public_deps = []
-  if (defined(invoker.public_deps)) {
-    _public_deps = invoker.public_deps
-  }
-
-  pw_python_wheels("$target_name.wheels") {
-    packages = invoker.packages
-    forward_variables_from(invoker, [ "deps" ])
-  }
-
-  pw_zip(target_name) {
-    forward_variables_from(invoker, [ "deps" ])
-    inputs = _inputs + [
-               "$dir_pw_build/python_dist/setup.bat > /${target_name}/",
-               "$dir_pw_build/python_dist/setup.sh > /${target_name}/",
-             ]
-
-    dirs = _dirs + [ "$target_out_dir/$target_name.wheels/ > /$target_name/python_wheels/" ]
-
-    output = _zip_path
-
-    # TODO(pwbug/634): Remove the plumbing-through of invoker's public_deps.
-    public_deps = _public_deps + [ ":${_outer_name}.wheels" ]
-  }
-}
-
-# Generates a directory of Python packages from source files suitable for
-# deployment outside of the project developer environment.
-#
-# The resulting directory contains only files mentioned in each package's
-# setup.cfg file. This is useful for bundling multiple Python packages up
-# into a single package for distribution to other locations like
-# http://pypi.org.
-#
-# Args:
-#   packages: A list of pw_python_package targets to be installed into the build
-#     directory. Their dependencies will be pulled in as wheels also.
-#
-#   include_tests: If true, copy Python package tests to a `tests` subdir.
-#
-#   extra_files: A list of extra files that should be included in the output. The
-#     format of each item in this list follows this convention:
-#       //some/nested/source_file > nested/destination_file
-template("pw_create_python_source_tree") {
-  _output_dir = "${target_out_dir}/${target_name}/"
-  _metadata_json_file_list =
-      "${target_gen_dir}/${target_name}_metadata_path_list.txt"
-
-  # If generating a setup.cfg file a common base file must be provided.
-  if (defined(invoker.generate_setup_cfg)) {
-    generate_setup_cfg = invoker.generate_setup_cfg
-    assert(defined(generate_setup_cfg.common_config_file),
-           "'common_config_file' is required in generate_setup_cfg")
-  }
-
-  _extra_file_inputs = []
-  _extra_file_args = []
-
-  # Convert extra_file strings to input, outputs and create_python_tree.py args.
-  if (defined(invoker.extra_files)) {
-    _delimiter = ">"
-    _extra_file_outputs = []
-    foreach(input, invoker.extra_files) {
-      # Remove spaces before and after the delimiter
-      input = string_replace(input, " $_delimiter", _delimiter)
-      input = string_replace(input, "$_delimiter ", _delimiter)
-
-      input_list = []
-      input_list = string_split(input, _delimiter)
-
-      # Save the input file
-      _extra_file_inputs += [ input_list[0] ]
-
-      # Save the output file
-      _this_output = _output_dir + "/" + input_list[1]
-      _extra_file_outputs += [ _this_output ]
-
-      # Compose an arg for passing to create_python_tree.py with properly
-      # rebased paths.
-      _extra_file_args +=
-          [ string_join(" $_delimiter ",
-                        [
-                          rebase_path(input_list[0], root_build_dir),
-                          rebase_path(_this_output, root_build_dir),
-                        ]) ]
-    }
-  }
-
-  _include_tests = defined(invoker.include_tests) && invoker.include_tests
-
-  # Build a list of relative paths containing all the python
-  # package_metadata.json files we depend on.
-  generated_file("${target_name}._metadata_path_list.txt") {
-    data_keys = [ "pw_python_package_metadata_json" ]
-    rebase = root_build_dir
-    deps = invoker.packages
-    outputs = [ _metadata_json_file_list ]
-  }
-
-  # Run the python action on the metadata_path_list.txt file
-  pw_python_action(target_name) {
-    deps =
-        invoker.packages + [ ":${invoker.target_name}._metadata_path_list.txt" ]
-    script = "$dir_pw_build/py/pw_build/create_python_tree.py"
-    inputs = _extra_file_inputs
-
-    args = [
-      "--tree-destination-dir",
-      rebase_path(_output_dir, root_build_dir),
-      "--input-list-files",
-      rebase_path(_metadata_json_file_list, root_build_dir),
-    ]
-
-    # Add required setup.cfg args if we are generating a merged config.
-    if (defined(generate_setup_cfg)) {
-      if (defined(generate_setup_cfg.common_config_file)) {
-        args += [
-          "--setupcfg-common-file",
-          rebase_path(generate_setup_cfg.common_config_file, root_build_dir),
-        ]
-      }
-      if (defined(generate_setup_cfg.append_git_sha_to_version)) {
-        args += [ "--setupcfg-version-append-git-sha" ]
-      }
-      if (defined(generate_setup_cfg.append_date_to_version)) {
-        args += [ "--setupcfg-version-append-date" ]
-      }
-    }
-
-    if (_extra_file_args == []) {
-      # No known output files - stamp instead.
-      stamp = true
-    } else {
-      args += [ "--extra-files" ]
-      args += _extra_file_args
-
-      # Include extra_files as outputs
-      outputs = _extra_file_outputs
-    }
-
-    if (_include_tests) {
-      args += [ "--include-tests" ]
-    }
-  }
-}
diff --git a/pw_build/python_dist/README.md b/pw_build/python_dist/README.md
deleted file mode 100644
index 655ca8e..0000000
--- a/pw_build/python_dist/README.md
+++ /dev/null
@@ -1,49 +0,0 @@
-# Python Distributables
-Setup and usage instructions for Pigweed Python distributables.
-
-## Prerequisites
-Python distributables require Python 3.7 or later.
-
-## Setup
-Run the included setup script found inside the unzipped directory. The setup
-script will create a virtual environment called `python-venv`.
-
-##### Linux / MacOS
-```bash
-setup.sh
-```
-
-Alternatively, the Linux/MacOS version of the setup script also allows
-installation over an existing venv:
-
-```bash
-setup.sh <PATH_TO_EXISTING_VENV>
-```
-
-##### Windows
-```
-setup.bat
-```
-
-##### Constraints File
-Both the Linux/MacOS and Windows versions of the setup script support the use
-of a constraints file. If a file named `constraints.txt` is present in the same
-directory as the setup script, it will automatically be used during setup to
-constrain the versions of any Python packages that get installed (see the
-official
-[pip documentation](https://pip.pypa.io/en/stable/user_guide/#constraints-files)
-for details).
-
-
-## Usage
-Once setup is complete, the Python tools can be invoked as runnable modules:
-
-Linux/MacOS:
-```bash
-python-venv/bin/python -m <MODULE_NAME> [OPTIONS]
-```
-
-Windows:
-```
-python-venv\Scripts\python -m <MODULE_NAME> [OPTIONS]
-```
diff --git a/pw_build/python_dist/setup.bat b/pw_build/python_dist/setup.bat
deleted file mode 100644
index 1ded5a7..0000000
--- a/pw_build/python_dist/setup.bat
+++ /dev/null
@@ -1,27 +0,0 @@
-:: Copyright 2021 The Pigweed Authors
-::
-:: 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
-::
-::     https://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.
-@echo off
-
-:: Generate python virtual environment using existing python.
-python3 -m venv %~dp0python-venv
-
-:: Install pip inside the virtual environment.
-%~dp0python-venv\Scripts\python.exe -m pip install --upgrade pip
-
-:: Install all wheel files, possibly with constraints.
-if exist %~dp0constraints.txt (
-  for %%f in (%~dp0python_wheels\*) do %~dp0python-venv\Scripts\python.exe -m pip install -c %~dp0constraints.txt --find-links=%~dp0python_wheels %%f
-) else (
-  for %%f in (%~dp0python_wheels\*) do %~dp0python-venv\Scripts\python.exe -m pip install --find-links=%~dp0python_wheels %%f
-)
diff --git a/pw_build/python_dist/setup.sh b/pw_build/python_dist/setup.sh
deleted file mode 100755
index 7974caf..0000000
--- a/pw_build/python_dist/setup.sh
+++ /dev/null
@@ -1,65 +0,0 @@
-#!/bin/bash
-
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-set -o xtrace -o errexit -o nounset
-
-SRC="${BASH_SOURCE[0]}"
-DIR="$(python3 -c "import os; print(os.path.dirname(os.path.abspath(os.path.realpath(\"$SRC\"))))")"
-VENV="${DIR}/python-venv"
-PY_TO_TEST="python3"
-CONSTRAINTS_PATH="${DIR}/constraints.txt"
-
-if [ ! -z "${1-}" ]; then
-  VENV="${1-}"
-  PY_TO_TEST="${VENV}/bin/python"
-fi
-
-CONSTRAINTS_ARG=""
-if [ -f ${CONSTRAINTS_PATH} ]; then
-    CONSTRAINTS_ARG="-c ${CONSTRAINTS_PATH}"
-fi
-
-PY_MAJOR_VERSION=$(${PY_TO_TEST} -c "import sys; print(sys.version_info[0])")
-PY_MINOR_VERSION=$(${PY_TO_TEST} -c "import sys; print(sys.version_info[1])")
-
-if [ ${PY_MAJOR_VERSION} -ne 3 ] || [ ${PY_MINOR_VERSION} -lt 7 ]
-then
-    echo "ERROR: This Python distributable requires Python 3.7 or newer."
-    exit 1
-fi
-
-if [ ! -d "${VENV}" ]
-then
-    ${PY_TO_TEST} -m venv ${VENV}
-fi
-
-${VENV}/bin/python -m pip install --upgrade pip
-
-# Uninstall wheels first, in case installing over an existing venv. This is a
-# faster and less destructive approach than --force-reinstall to ensure wheels
-# whose version numbers haven't incremented still get reinstalled.
-for wheel in $(ls ${DIR}/python_wheels/*.whl)
-do
-    ${VENV}/bin/python -m pip uninstall --yes $wheel
-done
-
-for wheel in $(ls ${DIR}/python_wheels/*.whl)
-do
-    ${VENV}/bin/python -m pip install \
-    --upgrade --find-links=${DIR}/python_wheels ${CONSTRAINTS_ARG} $wheel
-done
-
-exit 0
diff --git a/pw_build/python_toolchain/BUILD.gn b/pw_build/python_toolchain/BUILD.gn
deleted file mode 100644
index 56d2aba..0000000
--- a/pw_build/python_toolchain/BUILD.gn
+++ /dev/null
@@ -1,23 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_toolchain/non_c_toolchain.gni")
-
-# A toolchain that provides no C/C++ compiler. It can be used for non-C/C++
-# languages or actions that should only happen once across all builds. This
-# toolchain cannot compile C/C++, and trying to use it to do so causes errors.
-pw_non_c_toolchain("python") {
-}
diff --git a/pw_build/python_wheels.gni b/pw_build/python_wheels.gni
new file mode 100644
index 0000000..22210da
--- /dev/null
+++ b/pw_build/python_wheels.gni
@@ -0,0 +1,34 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/python_action.gni")
+
+# Builds a .whl from a Python package.
+template("pw_python_wheels") {
+  pw_python_action(target_name) {
+    forward_variables_from(invoker, [ "deps" ])
+
+    script = "$dir_pw_build/py/pw_build/python_wheels.py"
+
+    args = [
+      "--out_dir",
+      rebase_path("$target_out_dir/python_wheels"),
+    ]
+    args += rebase_path(invoker.inputs)
+
+    stamp = true
+  }
+}
diff --git a/pw_build/relative_source_file_names.gni b/pw_build/relative_source_file_names.gni
deleted file mode 100644
index 0aa1c17..0000000
--- a/pw_build/relative_source_file_names.gni
+++ /dev/null
@@ -1,67 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/python_action.gni")
-
-# This isn't in a declare_args() block as it likely isn't necessary to change
-# this.
-pw_build_RELATIVE_PATH_TRANSFORM_JSON =
-    "$root_build_dir/relative_path_transformations.json"
-
-# Creates a JSON file containing an array of source file names with
-# -ffile-prefix-map style transformations applied to match __FILE__ as seen in
-# C/C++ sources when using pw_build's `relative_paths` config.
-#
-# Args:
-#    deps (required): A list of targets to recursively extract file names from.
-#    outputs (required): An array with a single element: the path to write the
-#        transformed file paths to. The output format is a JSON array of
-#        strings.
-template("pw_relative_source_file_names") {
-  _raw_file_names_json = "$target_gen_dir/${target_name}.raw.json"
-
-  # The various pw_* templates add pw_source_files metadata which we
-  # aggregate here.
-  generated_file("${target_name}.raw_source_files") {
-    forward_variables_from(invoker, [ "deps" ])
-
-    # Rebase the paths so that they match those that are passed to the
-    # compiler.
-    rebase = root_build_dir
-    outputs = [ _raw_file_names_json ]
-    data_keys = [ "pw_source_files" ]
-    output_conversion = "json"
-  }
-
-  pw_python_action(target_name) {
-    deps = [ ":${target_name}.raw_source_files" ]
-    python_deps = [ "$dir_pw_build/py" ]
-    module = "pw_build.file_prefix_map"
-
-    # GN-ism: You can't do invoker.outputs[0], but _outs[0] works.
-    _outs = invoker.outputs
-    args = [
-      rebase_path(_raw_file_names_json, root_build_dir),
-      "--prefix-map-json",
-      rebase_path(pw_build_RELATIVE_PATH_TRANSFORM_JSON, root_build_dir),
-      "--out",
-      rebase_path(_outs[0], root_build_dir),
-    ]
-
-    inputs = [ _raw_file_names_json ]
-    forward_variables_from(invoker, [ "outputs" ])
-  }
-}
diff --git a/pw_build/selects.bzl b/pw_build/selects.bzl
deleted file mode 100644
index 236b24a..0000000
--- a/pw_build/selects.bzl
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Utility for tagging a target as compatible with a host OS."""
-
-_RTOS_NONE = "//pw_build/constraints/rtos:none"
-
-# Common select for tagging a target as only compatible with host OS's. This
-# select implements the logic '(Windows or Macos or Linux) and not RTOS'.
-# Example usage:
-#   load("//pw_build:selects.bzl","TARGET_COMPATIBLE_WITH_HOST_SELECT")
-#   pw_cc_library(
-#       name = "some_host_only_lib",
-#       hdrs = ["host.h"],
-#       target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-#   )
-TARGET_COMPATIBLE_WITH_HOST_SELECT = {
-    "@platforms//os:windows": [_RTOS_NONE],
-    "@platforms//os:macos": [_RTOS_NONE],
-    "@platforms//os:linux": [_RTOS_NONE],
-    "//conditions:default": ["@platforms//:incompatible"],
-}
diff --git a/pw_build/target_config.bzl b/pw_build/target_config.bzl
deleted file mode 100644
index 2a1f2a5..0000000
--- a/pw_build/target_config.bzl
+++ /dev/null
@@ -1,60 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Configure Pigweed's backend implementations."""
-
-def _pigweed_config_impl(repository_ctx):
-    if repository_ctx.attr.build_file_content and \
-       repository_ctx.attr.build_file:
-        fail("Attributes 'build_file_content' and 'build_file' cannot both be \
-        defined at the same time.")
-    if not repository_ctx.attr.build_file_content and \
-       not repository_ctx.attr.build_file:
-        fail("Either 'build_file_content' or 'build_file' must be defined.")
-
-    if repository_ctx.name != "pigweed_config":
-        fail("This repository should be name 'pigweed_config'")
-
-    if repository_ctx.attr.build_file_content:
-        repository_ctx.file("BUILD", repository_ctx.attr.build_file_content)
-
-    if repository_ctx.attr.build_file:
-        repository_ctx.template("BUILD", repository_ctx.attr.build_file, {})
-
-pigweed_config = repository_rule(
-    _pigweed_config_impl,
-    attrs = {
-        "build_file_content": attr.string(
-            doc = "The build file content to configure Pigweed.",
-            mandatory = False,
-            default = "",
-        ),
-        "build_file": attr.label(
-            doc = "The label for the Pigweed config build file to use.",
-            mandatory = False,
-            default = "@pigweed//targets/host:host_config.BUILD",
-        ),
-    },
-    doc = """
-Configure Pigweeds backend implementations.
-
-Example:
-    # WORKSPACE
-    pigweed_config(
-        # This must use the exact name specified here otherwise this
-        # remote repository will not function as expected.
-        name = "pigweed_config",
-        build_file = "//path/to/config.BUILD",
-    )
-""",
-)
diff --git a/pw_build/target_types.gni b/pw_build/target_types.gni
index afb0380..7590f2d 100644
--- a/pw_build/target_types.gni
+++ b/pw_build/target_types.gni
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2020 The Pigweed Authors
 #
 # 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
@@ -14,5 +14,222 @@
 
 import("//build_overrides/pigweed.gni")
 
-import("$dir_pw_build/cc_executable.gni")
-import("$dir_pw_build/cc_library.gni")
+declare_args() {
+  # The name of the GN target type used to build Pigweed executables.
+  #
+  # If this is a custom template, the .gni file containing the template must
+  # be imported at the top of the target configuration file to make it globally
+  # available.
+  pw_build_EXECUTABLE_TARGET_TYPE = "executable"
+
+  # The path to the .gni file that defines pw_build_EXECUTABLE_TARGET_TYPE.
+  #
+  # If pw_build_EXECUTABLE_TARGET_TYPE is not the default of `executable`, this
+  # .gni file is imported to provide the template definition.
+  pw_build_EXECUTABLE_TARGET_TYPE_FILE = ""
+}
+
+if (pw_build_EXECUTABLE_TARGET_TYPE != "executable" &&
+    pw_build_EXECUTABLE_TARGET_TYPE_FILE != "") {
+  import(pw_build_EXECUTABLE_TARGET_TYPE_FILE)
+}
+
+# TODO(frolv): The code in all of the templates below is duplicated, with the
+# exception of the target type. This file could be auto-generated with Python.
+
+_supported_toolchain_defaults = [
+  "configs",
+  "public_deps",
+]
+
+template("pw_source_set") {
+  source_set(target_name) {
+    import("$dir_pw_build/defaults.gni")
+    forward_variables_from(invoker, "*", _supported_toolchain_defaults)
+
+    if (!defined(configs)) {
+      configs = []
+    }
+    if (defined(pw_build_defaults.configs)) {
+      configs += pw_build_defaults.configs
+    }
+    if (defined(remove_configs)) {
+      if (remove_configs != [] && remove_configs[0] == "*") {
+        configs = []
+      } else {
+        configs += remove_configs  # Add configs in case they aren't already
+        configs -= remove_configs  # present, then remove them.
+      }
+    }
+    if (defined(invoker.configs)) {
+      configs += invoker.configs
+    }
+
+    if (defined(pw_build_defaults.public_deps)) {
+      public_deps = pw_build_defaults.public_deps
+    } else {
+      public_deps = []
+    }
+    if (defined(remove_public_deps)) {
+      if (remove_public_deps != [] && remove_public_deps[0] == "*") {
+        public_deps = []
+      } else {
+        public_deps += remove_public_deps
+        public_deps -= remove_public_deps
+      }
+    }
+    if (defined(invoker.public_deps)) {
+      public_deps += invoker.public_deps
+    }
+  }
+}
+
+template("pw_static_library") {
+  _library_output_dir = "${target_out_dir}/lib"
+  if (defined(invoker.output_dir)) {
+    _library_output_dir = invoker.output_dir
+  }
+
+  static_library(target_name) {
+    import("$dir_pw_build/defaults.gni")
+    forward_variables_from(invoker, "*", _supported_toolchain_defaults)
+
+    if (!defined(configs)) {
+      configs = []
+    }
+    if (defined(pw_build_defaults.configs)) {
+      configs += pw_build_defaults.configs
+    }
+    if (defined(remove_configs)) {
+      if (remove_configs != [] && remove_configs[0] == "*") {
+        configs = []
+      } else {
+        configs += remove_configs  # Add configs in case they aren't already
+        configs -= remove_configs  # present, then remove them.
+      }
+    }
+    if (defined(invoker.configs)) {
+      configs += invoker.configs
+    }
+
+    if (defined(pw_build_defaults.public_deps)) {
+      public_deps = pw_build_defaults.public_deps
+    } else {
+      public_deps = []
+    }
+    if (defined(remove_public_deps)) {
+      if (remove_public_deps != [] && remove_public_deps[0] == "*") {
+        public_deps = []
+      } else {
+        public_deps += remove_public_deps
+        public_deps -= remove_public_deps
+      }
+    }
+    if (defined(invoker.public_deps)) {
+      public_deps += invoker.public_deps
+    }
+
+    output_dir = _library_output_dir
+  }
+}
+
+template("pw_shared_library") {
+  _library_output_dir = "${target_out_dir}/lib"
+  if (defined(invoker.output_dir)) {
+    _library_output_dir = invoker.output_dir
+  }
+
+  shared_library(target_name) {
+    import("$dir_pw_build/defaults.gni")
+    forward_variables_from(invoker, "*", _supported_toolchain_defaults)
+
+    if (!defined(configs)) {
+      configs = []
+    }
+    if (defined(pw_build_defaults.configs)) {
+      configs += pw_build_defaults.configs
+    }
+    if (defined(remove_configs)) {
+      if (remove_configs != [] && remove_configs[0] == "*") {
+        configs = []
+      } else {
+        configs += remove_configs  # Add configs in case they aren't already
+        configs -= remove_configs  # present, then remove them.
+      }
+    }
+    if (defined(invoker.configs)) {
+      configs += invoker.configs
+    }
+
+    if (defined(pw_build_defaults.public_deps)) {
+      public_deps = pw_build_defaults.public_deps
+    } else {
+      public_deps = []
+    }
+    if (defined(remove_public_deps)) {
+      if (remove_public_deps != [] && remove_public_deps[0] == "*") {
+        public_deps = []
+      } else {
+        public_deps += remove_public_deps
+        public_deps -= remove_public_deps
+      }
+    }
+    if (defined(invoker.public_deps)) {
+      public_deps += invoker.public_deps
+    }
+
+    output_dir = _library_output_dir
+  }
+}
+
+# Wrapper for Pigweed executable build targets which uses a globally-defined,
+# configurable target type.
+template("pw_executable") {
+  _executable_output_dir = "${target_out_dir}/bin"
+  if (defined(invoker.output_dir)) {
+    _executable_output_dir = invoker.output_dir
+  }
+
+  target(pw_build_EXECUTABLE_TARGET_TYPE, target_name) {
+    import("$dir_pw_build/defaults.gni")
+
+    forward_variables_from(invoker, "*", _supported_toolchain_defaults)
+
+    if (!defined(configs)) {
+      configs = []
+    }
+    if (defined(pw_build_defaults.configs)) {
+      configs += pw_build_defaults.configs
+    }
+    if (defined(remove_configs)) {
+      if (remove_configs != [] && remove_configs[0] == "*") {
+        configs = []
+      } else {
+        configs += remove_configs  # Add configs in case they aren't already
+        configs -= remove_configs  # present, then remove them.
+      }
+    }
+    if (defined(invoker.configs)) {
+      configs += invoker.configs
+    }
+
+    if (defined(pw_build_defaults.public_deps)) {
+      public_deps = pw_build_defaults.public_deps
+    } else {
+      public_deps = []
+    }
+    if (defined(remove_public_deps)) {
+      if (remove_public_deps != [] && remove_public_deps[0] == "*") {
+        public_deps = []
+      } else {
+        public_deps += remove_public_deps
+        public_deps -= remove_public_deps
+      }
+    }
+    if (defined(invoker.public_deps)) {
+      public_deps += invoker.public_deps
+    }
+
+    output_dir = _executable_output_dir
+  }
+}
diff --git a/pw_build/update_bundle.gni b/pw_build/update_bundle.gni
deleted file mode 100644
index 1b0ff7b..0000000
--- a/pw_build/update_bundle.gni
+++ /dev/null
@@ -1,121 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/python_action.gni")
-
-# GN target that creates update bundles.
-#
-# Args:
-#   out: Filename at which to output the serialized update bundle.
-#   targets: List of targets mapping filenames to target names.
-#   persist: Optional boolean; if true, the raw tuf repo will be persisted to
-#       disk in the target out dir in addition to being serialized as a bundle.
-#   user_manifest: Optional path to an extra user-defined manifest file; if
-#       provided, this file will be included as a target payload, but handled
-#       specially. See the following file for details:
-#         pw_software_update/public/pw_software_update/bundled_update_backend.h
-#   targets_metadata_version: Optional manually-specified int version number for
-#       the targets metadata.
-#   targets_metadata_version_file: Optional manually-specified path/to/file
-#        containing int version number for the targets metadata. Cannot be
-#        specified together with targets_metadata_version
-#   signed_root_metadata: Optional path to a .pb file containing a serialized
-#       SignedRootMetadata (generated and signed via the tools in the
-#       pw_software_update Python module).
-#
-# Each target in targets should be a string formatted as follows:
-#   "/path/to/file > target_name"
-
-template("pw_update_bundle") {
-  assert(defined(invoker.out), "An output path must be provided via 'out'")
-  assert(defined(invoker.targets),
-         "A list of targets must be provided via 'targets'")
-  pw_python_action(target_name) {
-    _delimiter = ">"
-    forward_variables_from(invoker,
-                           [
-                             "deps",
-                             "public_deps",
-                           ])
-    _out_path = invoker.out
-    _persist_path = ""
-    if (defined(invoker.persist) && invoker.persist) {
-      _persist_path = "${target_out_dir}/${target_name}/tuf_repo"
-    }
-    module = "pw_software_update.update_bundle"
-    args = [
-      "--out",
-      rebase_path(_out_path),
-      "--targets",
-    ]
-    outputs = [ _out_path ]
-
-    foreach(tuf_target, invoker.targets) {
-      # Remove possible spaces around the delimiter before splitting
-      tuf_target = string_replace(tuf_target, " $_delimiter", _delimiter)
-      tuf_target = string_replace(tuf_target, "$_delimiter ", _delimiter)
-
-      tuf_target_list = []
-      tuf_target_list = string_split(tuf_target, _delimiter)
-      tuf_target_path = rebase_path(tuf_target_list[0], root_build_dir)
-      tuf_target_name = tuf_target_list[1]
-      assert(tuf_target_name != "user_manifest",
-             "The target name 'user_manifest' is reserved for special use.")
-      args += [ "${tuf_target_path} > ${tuf_target_name}" ]
-      if (_persist_path != "") {
-        outputs += [ "${_persist_path}/${tuf_target_name}" ]
-      }
-    }
-
-    if (defined(invoker.user_manifest)) {
-      args += [ rebase_path(invoker.user_manifest, root_build_dir) +
-                " > user_manifest" ]
-    }
-
-    if (_persist_path != "") {
-      args += [
-        "--persist",
-        rebase_path(_persist_path),
-      ]
-    }
-
-    if (defined(invoker.targets_metadata_version)) {
-      args += [
-        "--targets-metadata-version",
-        invoker.targets_metadata_version,
-      ]
-    }
-
-    if (defined(invoker.targets_metadata_version_file)) {
-      args += [
-        "--targets-metadata-version-file",
-        rebase_path(invoker.targets_metadata_version_file),
-      ]
-    }
-
-    assert(
-        !(defined(invoker.targets_metadata_version_file) &&
-              defined(invoker.targets_metadata_version)),
-        "Only one of targets_metadata_version and targets_metadata_version_file can be specified")
-
-    if (defined(invoker.signed_root_metadata)) {
-      args += [
-        "--signed-root-metadata",
-        rebase_path(invoker.signed_root_metadata),
-      ]
-    }
-  }
-}
diff --git a/pw_build/zip.gni b/pw_build/zip.gni
index 04d79e9..653b0cd 100644
--- a/pw_build/zip.gni
+++ b/pw_build/zip.gni
@@ -79,26 +79,22 @@
 #
 #     foo.zip
 #     ├── bar/
-#     │   ├── file3.txt
-#     │   └── some_dir/
-#     │       ├── file4.txt
-#     │       └── some_other_dir/
-#     │           └── file5.txt
+#     │   ├── file3.txt
+#     │   └── some_dir/
+#     │       ├── file4.txt
+#     │       └── some_other_dir/
+#     │           └── file5.txt
 #     ├── file1.txt
 #     └── renamed.txt
 #
 template("pw_zip") {
   _delimiter = ">"
   pw_python_action(target_name) {
-    forward_variables_from(invoker,
-                           [
-                             "deps",
-                             "public_deps",
-                           ])
+    forward_variables_from(invoker, [ "deps" ])
     script = "$dir_pw_build/py/pw_build/zip.py"
 
     args = [ "--out_filename" ]
-    args += [ rebase_path(invoker.output, root_build_dir) ]
+    args += [ rebase_path(invoker.output) ]
 
     inputs = []
     args += [ "--input_list" ]
@@ -111,8 +107,8 @@
 
         input_list = []
         input_list = string_split(input, _delimiter)
+        input_list[0] = rebase_path(input_list[0])
         inputs += [ input_list[0] ]
-        input_list[0] = rebase_path(input_list[0], root_build_dir)
 
         # Pass rebased and delimited path to script.
         args += [ string_join(_delimiter, input_list) ]
@@ -126,7 +122,7 @@
         dir = string_replace(dir, " $_delimiter", _delimiter)
         dir = string_replace(dir, "$_delimiter ", _delimiter)
 
-        args += [ rebase_path(dir, root_build_dir) ]
+        args += [ rebase_path(dir) ]
       }
     }
 
diff --git a/pw_build_info/BUILD.bazel b/pw_build_info/BUILD.bazel
deleted file mode 100644
index 0f242db..0000000
--- a/pw_build_info/BUILD.bazel
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "build_id",
-    srcs = [
-        "build_id.cc",
-    ],
-    hdrs = [
-        "public/pw_build_info/build_id.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_preprocessor",
-    ],
-)
-
-# This is only used for the python tests.
-filegroup(
-    name = "build_id_print_test",
-    srcs = [
-        "py/print_build_id.cc",
-    ],
-)
diff --git a/pw_build_info/BUILD.gn b/pw_build_info/BUILD.gn
deleted file mode 100644
index 659a01c..0000000
--- a/pw_build_info/BUILD.gn
+++ /dev/null
@@ -1,72 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_docgen/docs.gni")
-
-config("linker_script") {
-  inputs = [ "build_id_linker_snippet.ld" ]
-
-  # Automatically add the gnu build ID linker sections when building for Linux.
-  # macOS and Windows executables are not supported, and embedded targets must
-  # manually add the snippet to their linker script in a read-only section.
-  if (current_os == "linux") {
-    # When building for Linux, the linker provides a default linker script.
-    # The add_build_id_to_default_script.ld wrapper includes the
-    # build_id_linker_snippet.ld script in a way that appends to the the
-    # default linker script instead of overriding it.
-    ldflags = [
-      "-T",
-      rebase_path("add_build_id_to_default_script.ld", root_build_dir),
-    ]
-    lib_dirs = [ "." ]
-
-    inputs += [ "add_build_id_to_default_script.ld" ]
-  }
-  visibility = [ ":*" ]
-}
-
-config("gnu_build_id") {
-  ldflags = [ "-Wl,--build-id=sha1" ]
-}
-
-config("public_include_path") {
-  include_dirs = [ "public" ]
-  visibility = [ ":*" ]
-}
-
-# GNU build IDs aren't supported by Windows and macOS.
-if (current_os != "mac" && current_os != "win") {
-  pw_source_set("build_id") {
-    all_dependent_configs = [
-      ":gnu_build_id",
-      ":linker_script",
-    ]
-    public_configs = [ ":public_include_path" ]
-    cflags = [
-      "-Wno-array-bounds",
-      "-Wno-stringop-overflow",
-    ]
-    public = [ "public/pw_build_info/build_id.h" ]
-    sources = [ "build_id.cc" ]
-    deps = [ dir_pw_preprocessor ]
-  }
-}
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-  inputs = [ "build_id_linker_snippet.ld" ]
-}
diff --git a/pw_build_info/CMakeLists.txt b/pw_build_info/CMakeLists.txt
deleted file mode 100644
index 1fb6699..0000000
--- a/pw_build_info/CMakeLists.txt
+++ /dev/null
@@ -1,49 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-
-# GNU build IDs aren't supported by Windows and macOS.
-if((NOT "${CMAKE_SYSTEM_NAME}" STREQUAL "Windows") AND
-   (NOT "${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin"))
-  pw_add_module_library(pw_build_info.build_id
-    HEADERS
-      public/pw_build_info/build_id.h
-    PUBLIC_INCLUDES
-      public
-    PUBLIC_DEPS
-      pw_polyfill.cstddef
-      pw_polyfill.span
-    PUBLIC_LINK_OPTIONS
-      -Wl,--build-id=sha1
-    SOURCES
-      build_id.cc
-    PRIVATE_DEPS
-      pw_preprocessor
-    PRIVATE_COMPILE_OPTIONS
-      -Wno-array-bounds
-      -Wno-stringop-overflow
-  )
-endif()
-
-# Automatically add the gnu build ID linker sections when building for Linux.
-# macOS and Windows executables are not supported, and embedded targets must
-# manually add the snippet to their linker script in a read-only section.
-if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux")
-  target_link_options(pw_build_info.build_id
-    PUBLIC
-      "-T${CMAKE_CURRENT_SOURCE_DIR}/add_build_id_to_default_script.ld"
-      "-L${CMAKE_CURRENT_SOURCE_DIR}"
-  )
-endif()
diff --git a/pw_build_info/OWNERS b/pw_build_info/OWNERS
deleted file mode 100644
index 307b1de..0000000
--- a/pw_build_info/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-amontanez@google.com
diff --git a/pw_build_info/add_build_id_to_default_linker_script.ld b/pw_build_info/add_build_id_to_default_linker_script.ld
deleted file mode 100644
index 3e0dd52..0000000
--- a/pw_build_info/add_build_id_to_default_linker_script.ld
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2021 The Pigweed Authors
- *
- * 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
- *
- *     https://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.
- */
-
-SECTIONS
-{
-  .note.gnu.build-id :
-  {
-    INCLUDE build_id_linker_snippet.ld
-  }
-}
-
-/*
- * The INSERT directive instructs the linker to append the directives in this
- * script to the default linker script, rather than replace the default with
- * this script. The build ID is read only, so place it just after .rodata.
- */
-INSERT AFTER .rodata
diff --git a/pw_build_info/build_id.cc b/pw_build_info/build_id.cc
deleted file mode 100644
index d1e727f..0000000
--- a/pw_build_info/build_id.cc
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_build_info/build_id.h"
-
-#include <cstdint>
-#include <cstring>
-#include <span>
-
-#include "pw_preprocessor/compiler.h"
-
-extern "C" const uint8_t gnu_build_id_begin;
-
-namespace pw::build_info {
-namespace {
-
-PW_PACKED(struct) ElfNoteInfo {
-  uint32_t name_size;
-  uint32_t descriptor_size;
-  uint32_t type;
-};
-
-}  // namespace
-
-// Reading more than a uint8_t from gnu_build_id_begin triggers compiler
-// warnings that must be silenced.
-PW_MODIFY_DIAGNOSTICS_PUSH();
-PW_MODIFY_DIAGNOSTIC(ignored, "-Warray-bounds");
-PW_MODIFY_DIAGNOSTIC_GCC(ignored, "-Wstringop-overflow");
-
-std::span<const std::byte> BuildId() {
-  // Read the sizes at the beginning of the note section.
-  ElfNoteInfo build_id_note_sizes;
-  memcpy(
-      &build_id_note_sizes, &gnu_build_id_begin, sizeof(build_id_note_sizes));
-  // Skip the "name" entry of the note section, and return a span to the
-  // descriptor.
-  return std::as_bytes(std::span(&gnu_build_id_begin +
-                                     sizeof(build_id_note_sizes) +
-                                     build_id_note_sizes.name_size,
-                                 build_id_note_sizes.descriptor_size));
-}
-
-PW_MODIFY_DIAGNOSTICS_POP();
-
-}  // namespace pw::build_info
diff --git a/pw_build_info/build_id_linker_snippet.ld b/pw_build_info/build_id_linker_snippet.ld
deleted file mode 100644
index d4086d9..0000000
--- a/pw_build_info/build_id_linker_snippet.ld
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2021 The Pigweed Authors
- *
- * 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
- *
- *     https://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 this linker snippet in a section of your linker script that specifies
- * where .rodata or .text will live in flash.
- */
-. = ALIGN(4);
-gnu_build_id_begin = .;
-*(.note.gnu.build-id);
diff --git a/pw_build_info/docs.rst b/pw_build_info/docs.rst
deleted file mode 100644
index 18ac9d7..0000000
--- a/pw_build_info/docs.rst
+++ /dev/null
@@ -1,82 +0,0 @@
-.. _module-pw_build_info:
-
-=============
-pw_build_info
-=============
-
-.. warning::
-  This module is under construction and may not be ready for use.
-
-pw_build_info provides tooling, build integration, and libraries for generating,
-embedding, and parsing build-related information that is embedded into
-binaries. Simple numeric version numbering doesn't typically express things
-like where the binary originated, what devices it's compatible with, whether
-local changes were present when the binary was built, and more. pw_build_info
-simplifies the process of integrating rich version metadata to answer more
-complex questions about compiled binaries.
-
--------------
-GNU Build IDs
--------------
-This module provides C++ and python libraries for reading GNU build IDs
-generated by the link step of a C++ executable. These build IDs are essentially
-hashes of the final linked binary, meaning two identical binaries will have
-identical build IDs. This can be used to accurately identify matching
-binaries.
-
-Linux executables that depend on the ``build_id`` GN target will automatically
-generate GNU build IDs. Windows and macOS binaries cannot use this target as
-the implementation of GNU build IDs depends on the ELF file format.
-
-Embedded targets must first explicitly place the GNU build ID section into a
-non-info section of their linker script that is readable by the firmware. The
-following linker snippet may be copied into a read-only section (just like the
-.rodata or .text sections):
-
-.. literalinclude:: build_id_linker_snippet.ld
-
-This snippet may be placed directly into an existing section, as it is not
-required to live in its own dedicated section. When opting to create a
-dedicated section for the build ID to reside in, Pigweed recommends naming the
-section ``.note.gnu.build-id`` as it makes it slightly easier for tools to
-parse the build ID out of a binary. After the linker script has been properly
-set up, the ``build_id`` GN target may be used to read the build ID at
-runtime.
-
-Python API reference
-====================
-
-.. py:function:: read_build_id_from_section(elf_file: BinaryIO) -> \
-                     Optional[bytes]
-
-  Reads a GNU build ID from an ELF binary by searching for a
-  ``.note.gnu.build-id`` section.
-
-.. py:function:: read_build_id_from_symbol(elf_file: BinaryIO) -> \
-                     Optional[bytes]
-
-  Reads a GNU build ID from an ELF binary by searching for a
-  ``gnu_build_id_begin`` symbol. This can be a rather slow operation.
-
-.. py:function:: read_build_id(elf_file: BinaryIO) -> Optional[bytes]
-
-  Reads a GNU build ID from an ELF binary, first checking for a GNU build ID
-  section and then falling back to search for a ``gnu_build_id_begin`` symbol.
-
-.. py:function:: find_matching_elf(uuid: bytes, search_dir: Path) -> \
-                     Optional[Path]
-
-  Recursively searches a directory for an ELF file with a matching UUID.
-
-  Warning: This can take on the order of several seconds.
-
-Python utility
-==============
-GNU build IDs can be parsed out of ELF files using the ``build_id`` python tool.
-Simply point the tool to a binary with a GNU build ID and the build ID will be
-printed out if it is found.
-
-.. code-block:: sh
-
-  $ python -m pw_build_info.build_id my_device_image.elf
-  d43cce74f18522052f77a1fa3fb7a25fe33f40dd
diff --git a/pw_build_info/public/pw_build_info/build_id.h b/pw_build_info/public/pw_build_info/build_id.h
deleted file mode 100644
index b5535ff..0000000
--- a/pw_build_info/public/pw_build_info/build_id.h
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstddef>
-#include <span>
-
-namespace pw::build_info {
-
-// Build IDs may be generated with several different algorithms. The largest of
-// these (aside from user-provided build IDs) are a fixed size of 20 bytes.
-inline constexpr size_t kMaxBuildIdSizeBytes = 20;
-
-// Reads a GNU build ID from the address starting at the address of the
-// `gnu_build_id_begin` symbol. This must be manually explicitly provided as
-// part of a linker script. See build_id_linker_snippet.ld for an example.
-std::span<const std::byte> BuildId();
-
-}  // namespace pw::build_info
diff --git a/pw_build_info/py/BUILD.gn b/pw_build_info/py/BUILD.gn
deleted file mode 100644
index 95240b9..0000000
--- a/pw_build_info/py/BUILD.gn
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/python.gni")
-
-pw_python_package("py") {
-  generate_setup = {
-    metadata = {
-      name = "pw_build_info"
-      version = "0.0.1"
-    }
-    options = {
-      install_requires = [ "pyelftools" ]
-    }
-  }
-  inputs = [ "print_build_id.cc" ]
-  sources = [
-    "pw_build_info/__init__.py",
-    "pw_build_info/build_id.py",
-  ]
-
-  # This test will only ever work on Linux as it requires the ability to compile
-  # AND run an ELF file.
-  if (host_os == "linux") {
-    tests = [ "build_id_test.py" ]
-    python_test_deps = [ "$dir_pw_cli/py" ]
-  }
-
-  pylintrc = "$dir_pigweed/.pylintrc"
-}
diff --git a/pw_build_info/py/build_id_test.py b/pw_build_info/py/build_id_test.py
deleted file mode 100644
index 6d420a3..0000000
--- a/pw_build_info/py/build_id_test.py
+++ /dev/null
@@ -1,85 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests for pw_build_info's GNU build ID support."""
-
-import subprocess
-import tempfile
-import unittest
-from pathlib import Path
-from pw_cli import env
-from pw_build_info import build_id
-
-# Since build_id.cc depends on pw_preprocessor, we have to use the in-tree path.
-_MODULE_DIR = Path(env.pigweed_environment().PW_ROOT) / 'pw_build_info'
-_MODULE_PY_DIR = Path(__file__).parent.resolve()
-
-_SHA1_BUILD_ID_LENGTH = 20
-
-
-class TestGnuBuildId(unittest.TestCase):
-    """Unit tests for GNU build ID parsing."""
-    def test_build_id_correctness(self):
-        """Tests to ensure GNU build IDs are read/written correctly."""
-        with tempfile.TemporaryDirectory() as exe_dir:
-            exe_file = Path(exe_dir) / 'print_build_id.elf'
-
-            # Compiles a binary that prints the embedded GNU build id.
-            cmd = [
-                'clang++',
-                'build_id.cc',
-                _MODULE_PY_DIR / 'print_build_id.cc',
-                '-Ipublic',
-                '-I../pw_preprocessor/public',
-                '-std=c++20',
-                '-fuse-ld=lld',
-                '-Wl,-Tadd_build_id_to_default_linker_script.ld',
-                '-Wl,--build-id=sha1',
-                '-o',
-                exe_file,
-            ]
-
-            process = subprocess.run(cmd,
-                                     stdout=subprocess.PIPE,
-                                     stderr=subprocess.STDOUT,
-                                     cwd=_MODULE_DIR)
-            self.assertEqual(process.returncode, 0)
-
-            # Run the compiled binary so the printed build ID can be read.
-            process = subprocess.run([exe_file],
-                                     stdout=subprocess.PIPE,
-                                     stderr=subprocess.STDOUT,
-                                     cwd=_MODULE_DIR)
-            self.assertEqual(process.returncode, 0)
-
-            with open(exe_file, 'rb') as elf:
-                expected = build_id.read_build_id_from_section(elf)
-                self.assertEqual(len(expected), _SHA1_BUILD_ID_LENGTH)
-                self.assertEqual(process.stdout.decode().rstrip(),
-                                 expected.hex())
-
-                # Test method that parses using symbol information.
-                expected = build_id.read_build_id_from_symbol(elf)
-                self.assertEqual(len(expected), _SHA1_BUILD_ID_LENGTH)
-                self.assertEqual(process.stdout.decode().rstrip(),
-                                 expected.hex())
-
-                # Test the user-facing method.
-                expected = build_id.read_build_id(elf)
-                self.assertEqual(len(expected), _SHA1_BUILD_ID_LENGTH)
-                self.assertEqual(process.stdout.decode().rstrip(),
-                                 expected.hex())
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_build_info/py/print_build_id.cc b/pw_build_info/py/print_build_id.cc
deleted file mode 100644
index 5852e82..0000000
--- a/pw_build_info/py/print_build_id.cc
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cinttypes>
-#include <cstdio>
-
-#include "pw_build_info/build_id.h"
-
-int main() {
-  for (std::byte b : pw::build_info::BuildId()) {
-    printf("%02" PRIx8, static_cast<uint8_t>(b));
-  }
-  printf("\n");
-  return 0;
-}
diff --git a/pw_build_info/py/pw_build_info/__init__.py b/pw_build_info/py/pw_build_info/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/pw_build_info/py/pw_build_info/__init__.py
+++ /dev/null
diff --git a/pw_build_info/py/pw_build_info/build_id.py b/pw_build_info/py/pw_build_info/build_id.py
deleted file mode 100644
index 17d877d..0000000
--- a/pw_build_info/py/pw_build_info/build_id.py
+++ /dev/null
@@ -1,167 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Library that parses an ELF file for a GNU build-id."""
-
-import argparse
-import logging
-from pathlib import Path
-import sys
-from typing import BinaryIO, Optional
-import elftools  # type: ignore
-from elftools.elf import elffile, notes, sections  # type: ignore
-
-_LOG = logging.getLogger('build_id_parser')
-_PW_BUILD_ID_SYM_NAME = 'gnu_build_id_begin'
-
-
-class GnuBuildIdError(Exception):
-    """An exception raised when a GNU build ID is malformed."""
-
-
-def read_build_id_from_section(elf_file: BinaryIO) -> Optional[bytes]:
-    """Reads a build ID from a .note.gnu.build-id section."""
-    parsed_elf_file = elffile.ELFFile(elf_file)
-    build_id_section = parsed_elf_file.get_section_by_name(
-        '.note.gnu.build-id')
-
-    if build_id_section is None:
-        return None
-
-    section_notes = list(n for n in notes.iter_notes(
-        parsed_elf_file, build_id_section['sh_offset'],
-        build_id_section['sh_size']))
-
-    if len(section_notes) != 1:
-        raise GnuBuildIdError('GNU build ID section contains multiple notes')
-
-    build_id_note = section_notes[0]
-    if build_id_note['n_name'] != 'GNU':
-        raise GnuBuildIdError('GNU build ID note name invalid')
-
-    if build_id_note['n_type'] != 'NT_GNU_BUILD_ID':
-        raise GnuBuildIdError('GNU build ID note type invalid')
-
-    return bytes.fromhex(build_id_note['n_desc'])
-
-
-def _addr_is_in_segment(addr: int, segment) -> bool:
-    """Checks if the provided address resides within the provided segment."""
-    # Address references uninitialized memory. Can't read.
-    if addr >= segment['p_vaddr'] + segment['p_filesz']:
-        raise GnuBuildIdError('GNU build ID is runtime-initialized')
-
-    return addr in range(segment['p_vaddr'], segment['p_memsz'])
-
-
-def _read_build_id_from_offset(elf, offset: int) -> bytes:
-    """Attempts to read a GNU build ID from an offset in an elf file."""
-    note = elftools.common.utils.struct_parse(elf.structs.Elf_Nhdr,
-                                              elf.stream,
-                                              stream_pos=offset)
-    elf.stream.seek(offset + elf.structs.Elf_Nhdr.sizeof())
-    name = elf.stream.read(note['n_namesz'])
-
-    if name != b'GNU\0':
-        raise GnuBuildIdError('GNU build ID note name invalid')
-
-    return elf.stream.read(note['n_descsz'])
-
-
-def read_build_id_from_symbol(elf_file: BinaryIO) -> Optional[bytes]:
-    """Reads a GNU build ID using gnu_build_id_begin to locate the data."""
-    parsed_elf_file = elffile.ELFFile(elf_file)
-
-    matching_syms = None
-    for section in parsed_elf_file.iter_sections():
-        if not isinstance(section, sections.SymbolTableSection):
-            continue
-        matching_syms = section.get_symbol_by_name(_PW_BUILD_ID_SYM_NAME)
-        if matching_syms is not None:
-            break
-    if matching_syms is None:
-        return None
-
-    if len(matching_syms) != 1:
-        raise GnuBuildIdError('Multiple GNU build ID start symbols defined')
-
-    gnu_build_id_sym = matching_syms[0]
-    section_number = gnu_build_id_sym['st_shndx']
-
-    if section_number == 'SHN_UNDEF':
-        raise GnuBuildIdError('GNU build ID start symbol undefined')
-
-    matching_section = parsed_elf_file.get_section(section_number)
-
-    build_id_start_addr = gnu_build_id_sym['st_value']
-    for segment in parsed_elf_file.iter_segments():
-        if segment.section_in_segment(matching_section):
-            offset = build_id_start_addr - segment['p_vaddr'] + segment[
-                'p_offset']
-            return _read_build_id_from_offset(parsed_elf_file, offset)
-
-    return None
-
-
-def read_build_id(elf_file: BinaryIO) -> Optional[bytes]:
-    """Reads a GNU build ID from an ELF binary."""
-    # Prefer to read the build ID from a dedicated section.
-    maybe_build_id = read_build_id_from_section(elf_file)
-    if maybe_build_id is not None:
-        return maybe_build_id
-
-    # If there's no dedicated section, try and use symbol information to find
-    # the build info.
-    return read_build_id_from_symbol(elf_file)
-
-
-def find_matching_elf(uuid: bytes, search_dir: Path) -> Optional[Path]:
-    """Recursively searches a directory for an ELF file with a matching UUID."""
-    elf_file_paths = search_dir.glob('**/*.elf')
-    for elf_file in elf_file_paths:
-        try:
-            candidate_id = read_build_id(open(elf_file, 'rb'))
-        except GnuBuildIdError:
-            continue
-        if candidate_id is None:
-            continue
-        if candidate_id == uuid:
-            return elf_file
-
-    return None
-
-
-def _main(elf_file: BinaryIO) -> int:
-    logging.basicConfig(format='%(message)s', level=logging.INFO)
-    build_id = read_build_id(elf_file)
-    if build_id is None:
-        _LOG.error('Error: No GNU build ID found.')
-        return 1
-
-    _LOG.info(build_id.hex())
-    return 0
-
-
-def _parse_args():
-    """Parses command-line arguments."""
-
-    parser = argparse.ArgumentParser(description=__doc__)
-    parser.add_argument('elf_file',
-                        type=argparse.FileType('rb'),
-                        help='The .elf to parse build info from')
-
-    return parser.parse_args()
-
-
-if __name__ == '__main__':
-    sys.exit(_main(**vars(_parse_args())))
diff --git a/pw_build_info/py/pw_build_info/py.typed b/pw_build_info/py/pw_build_info/py.typed
deleted file mode 100644
index e69de29..0000000
--- a/pw_build_info/py/pw_build_info/py.typed
+++ /dev/null
diff --git a/pw_build_mcuxpresso/BUILD.gn b/pw_build_mcuxpresso/BUILD.gn
deleted file mode 100644
index 9a6699a..0000000
--- a/pw_build_mcuxpresso/BUILD.gn
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_docgen/docs.gni")
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
diff --git a/pw_build_mcuxpresso/OWNERS b/pw_build_mcuxpresso/OWNERS
deleted file mode 100644
index c224618..0000000
--- a/pw_build_mcuxpresso/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-keybuk@google.com
diff --git a/pw_build_mcuxpresso/docs.rst b/pw_build_mcuxpresso/docs.rst
deleted file mode 100644
index 1805871..0000000
--- a/pw_build_mcuxpresso/docs.rst
+++ /dev/null
@@ -1,132 +0,0 @@
-.. _module-pw_build_mcuxpresso:
-
--------------------
-pw_build_mcuxpresso
--------------------
-
-The ``pw_build_mcuxpresso`` module provides helper utilizies for building a
-target based on an NXP MCUXpresso SDK.
-
-The GN build files live in ``third_party/mcuxpresso`` but are documented here.
-The rationale for keeping the build files in ``third_party`` is that code
-depending on an MCUXpresso SDK can clearly see that their dependency is on
-third party, not pigweed code.
-
-Using an MCUXpresso SDK
-=======================
-An MCUXpresso SDK consists of a number of components, each of which has a set
-of sources, headers, pre-processor defines, and dependencies on other
-components. These are all described in an XML "manifest" file included in the
-SDK package.
-
-To use the SDK within a Pigweed project, the set of components you need must be
-combined into a ``pw_source_set`` that you can depend on. This source set will
-include all of the sources and headers, along with necessary pre-processor
-defines, for those components and their dependencies.
-
-The source set is defined using the ``pw_mcuxpresso_sdk`` template, providing
-the path to the ``manifest`` XML, along with the names of the components you
-wish to ``include``.
-
-.. code-block:: text
-
-   import("$dir_pw_third_party/mcuxpresso/mcuxpresso.gni")
-
-   pw_mcuxpresso_sdk("sample_project_sdk") {
-     manifest = "$dir_pw_third_party/mcuxpresso/evkmimxrt595/EVK-MIMXRT595_manifest_v3_8.xml"
-     include = [
-       "component.serial_manager_uart.MIMXRT595S",
-       "project_template.evkmimxrt595.MIMXRT595S",
-       "utility.debug_console.MIMXRT595S",
-     ]
-   }
-
-   pw_executable("hello_world") {
-     sources = [ "hello_world.cc" ]
-     deps = [ ":sample_project_sdk" ]
-   }
-
-Where the components you include have optional dependencies, they must be
-satisfied by the set of components you include otherwise the GN generation will
-fail with an error.
-
-Excluding components
---------------------
-Components can be excluded from the generated source set, for example to
-suppress errors about optional dependencies your project does not need, or to
-prevent an unwanted component dependency from being introduced into your
-project.
-
-This is accomplished by providing the list of components to ``exclude`` as an
-argument to the template.
-
-For example to replace the FreeRTOS kernel bundled with the MCUXpresso SDK with
-the Pigweed third-party target:
-
-.. code-block:: text
-
-   pw_mcuxpresso_sdk("freertos_project_sdk") {
-     // manifest and includes ommitted for clarity
-     exclude = [ "middleware.freertos-kernel.MIMXRT595S" ]
-     public_deps = [ "$dir_pw_third_party/freertos" ]
-   }
-
-Introducing dependencies
-------------------------
-As seen above, the generated source set can have dependencies added by passing
-the ``public_deps`` (or ``deps``) arguments to the template.
-
-You can also pass the ``allow_circular_includes_from``, ``configs``, and
-``public_configs`` arguments to augment the generated source set.
-
-For example it is very common to replace the ``project_template`` component with
-a source set of your own that provides modified copies of the files from the
-SDK.
-
-To resolve circular dependencies, in addition to the generated source set, two
-configs named with the ``__defines`` and ``__includes`` suffixes on the template
-name are generated, to provide the pre-processor defines and include paths that
-the source set uses.
-
-.. code-block:: text
-
-   pw_mcuxpresso_sdk("my_project_sdk") {
-     manifest = "$dir_pw_third_party/mcuxpresso/evkmimxrt595/EVK-MIMXRT595_manifest_v3_8.xml"
-     include = [
-       "component.serial_manager_uart.MIMXRT595S",
-       "utility.debug_console.MIMXRT595S",
-     ]
-     public_deps = [ ":my_project_config" ]
-     allow_circular_includes_from = [ ":my_project_config" ]
-   }
-
-   pw_source_set("my_project_config") {
-     sources = [ "board.c", "clock_config.c", "pin_mux.c" ]
-     public = [ "board.h", "clock_config.h", "pin_mux.h "]
-     public_configs = [
-       ":my_project_sdk__defines",
-       ":my_project_sdk__includes"
-     ]
-   }
-
-mcuxpresso_builder
-==================
-``mcuxpresso_builder`` is a utility that contains the backend scripts used by
-the GN build scripts in ``third_party/mcuxpresso``. You should only need to
-interact with ``mcuxpresso_builder`` directly if you are doing something custom.
-
-project
--------
-This command outputs a GN scope describing the result of expanding the set of
-included and excluded components.
-
-The ``--prefix`` option specifies the GN location of the SDK files.
-
-.. code-block:: bash
-
-  mcuxpresso_builder project /path/to/manifest.xml \
-      --include project_template.evkmimxrt595.MIMXRT595S \
-      --include utility.debug_console.MIMXRT595S \
-      --include component.serial_manager_uart.MIMXRT595S \
-      --exclude middleware.freertos-kernel.MIMXRT595S \
-      --prefix //path/to/sdk
diff --git a/pw_build_mcuxpresso/py/BUILD.gn b/pw_build_mcuxpresso/py/BUILD.gn
deleted file mode 100644
index 8d7eb65..0000000
--- a/pw_build_mcuxpresso/py/BUILD.gn
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/python.gni")
-
-pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
-  sources = [
-    "pw_build_mcuxpresso/__init__.py",
-    "pw_build_mcuxpresso/__main__.py",
-    "pw_build_mcuxpresso/components.py",
-  ]
-  tests = [ "tests/components_test.py" ]
-  pylintrc = "$dir_pigweed/.pylintrc"
-}
diff --git a/pw_build_mcuxpresso/py/pw_build_mcuxpresso/__init__.py b/pw_build_mcuxpresso/py/pw_build_mcuxpresso/__init__.py
deleted file mode 100644
index e97d22e..0000000
--- a/pw_build_mcuxpresso/py/pw_build_mcuxpresso/__init__.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""This package provides tooling specific to MCUXpresso."""
diff --git a/pw_build_mcuxpresso/py/pw_build_mcuxpresso/__main__.py b/pw_build_mcuxpresso/py/pw_build_mcuxpresso/__main__.py
deleted file mode 100644
index 1a39e50..0000000
--- a/pw_build_mcuxpresso/py/pw_build_mcuxpresso/__main__.py
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Command line interface for mcuxpresso_builder."""
-
-import argparse
-import pathlib
-import sys
-
-from pw_build_mcuxpresso import components
-
-
-def _parse_args() -> argparse.Namespace:
-    """Setup argparse and parse command line args."""
-    parser = argparse.ArgumentParser()
-
-    subparsers = parser.add_subparsers(dest='command',
-                                       metavar='<command>',
-                                       required=True)
-
-    project_parser = subparsers.add_parser(
-        'project', help='output components of an MCUXpresso project')
-    project_parser.add_argument('manifest_filename', type=pathlib.Path)
-    project_parser.add_argument('--include', type=str, action='append')
-    project_parser.add_argument('--exclude', type=str, action='append')
-    project_parser.add_argument('--prefix', dest='path_prefix', type=str)
-
-    return parser.parse_args()
-
-
-def main():
-    """Main command line function."""
-    args = _parse_args()
-
-    if args.command == 'project':
-        components.project(args.manifest_filename,
-                           include=args.include,
-                           exclude=args.exclude,
-                           path_prefix=args.path_prefix)
-
-    sys.exit(0)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/pw_build_mcuxpresso/py/pw_build_mcuxpresso/components.py b/pw_build_mcuxpresso/py/pw_build_mcuxpresso/components.py
deleted file mode 100644
index 061f5a0..0000000
--- a/pw_build_mcuxpresso/py/pw_build_mcuxpresso/components.py
+++ /dev/null
@@ -1,474 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Finds components for a given manifest."""
-
-from typing import Any, List, Optional, Tuple
-
-import pathlib
-import sys
-import xml.etree.ElementTree
-
-
-def _gn_str_out(name: str, val: Any):
-    """Outputs scoped string in GN format."""
-    print(f'{name} = "{val}"')
-
-
-def _gn_list_str_out(name: str, val: List[Any]):
-    """Outputs list of strings in GN format with correct escaping."""
-    list_str = ','.join('"' + str(x).replace('"', r'\"').replace('$', r'\$') +
-                        '"' for x in val)
-    print(f'{name} = [{list_str}]')
-
-
-def _gn_list_path_out(name: str,
-                      val: List[pathlib.Path],
-                      path_prefix: Optional[str] = None):
-    """Outputs list of paths in GN format with common prefix."""
-    if path_prefix is not None:
-        str_val = list(f'{path_prefix}/{str(d)}' for d in val)
-    else:
-        str_val = list(str(d) for d in val)
-    _gn_list_str_out(name, str_val)
-
-
-def get_component(
-    root: xml.etree.ElementTree.Element, component_id: str
-) -> Tuple[Optional[xml.etree.ElementTree.Element], Optional[pathlib.Path]]:
-    """Parse <component> manifest stanza.
-
-    Schema:
-        <component id="{component_id}" package_base_path="component">
-        </component>
-
-    Args:
-        root: root of element tree.
-        component_id: id of component to return.
-
-    Returns:
-        (element, base_path) for the component, or (None, None).
-    """
-    xpath = f'./components/component[@id="{component_id}"]'
-    component = root.find(xpath)
-    if component is None:
-        return (None, None)
-
-    try:
-        base_path = pathlib.Path(component.attrib['package_base_path'])
-        return (component, base_path)
-    except KeyError:
-        return (component, None)
-
-
-def parse_defines(root: xml.etree.ElementTree.Element,
-                  component_id: str) -> List[str]:
-    """Parse pre-processor definitions for a component.
-
-    Schema:
-        <defines>
-          <define name="EXAMPLE" value="1"/>
-          <define name="OTHER"/>
-        </defines>
-
-    Args:
-        root: root of element tree.
-        component_id: id of component to return.
-
-    Returns:
-        list of str NAME=VALUE or NAME for the component.
-    """
-    xpath = f'./components/component[@id="{component_id}"]/defines/define'
-    return list(_parse_define(define) for define in root.findall(xpath))
-
-
-def _parse_define(define: xml.etree.ElementTree.Element) -> str:
-    """Parse <define> manifest stanza.
-
-    Schema:
-        <define name="EXAMPLE" value="1"/>
-        <define name="OTHER"/>
-
-    Args:
-        define: XML Element for <define>.
-
-    Returns:
-        str with a value NAME=VALUE or NAME.
-    """
-    name = define.attrib['name']
-    value = define.attrib.get('value', None)
-    if value is None:
-        return name
-
-    return f'{name}={value}'
-
-
-def parse_include_paths(root: xml.etree.ElementTree.Element,
-                        component_id: str) -> List[pathlib.Path]:
-    """Parse include directories for a component.
-
-    Schema:
-        <component id="{component_id}" package_base_path="component">
-          <include_paths>
-            <include_path relative_path="./" type="c_include"/>
-          </include_paths>
-        </component>
-
-    Args:
-        root: root of element tree.
-        component_id: id of component to return.
-
-    Returns:
-        list of include directories for the component.
-    """
-    (component, base_path) = get_component(root, component_id)
-    if component is None:
-        return []
-
-    include_paths: List[pathlib.Path] = []
-    for include_type in ('c_include', 'asm_include'):
-        include_xpath = f'./include_paths/include_path[@type="{include_type}"]'
-
-        include_paths.extend(
-            _parse_include_path(include_path, base_path)
-            for include_path in component.findall(include_xpath))
-    return include_paths
-
-
-def _parse_include_path(include_path: xml.etree.ElementTree.Element,
-                        base_path: Optional[pathlib.Path]) -> pathlib.Path:
-    """Parse <include_path> manifest stanza.
-
-    Schema:
-        <include_path relative_path="./" type="c_include"/>
-
-    Args:
-        include_path: XML Element for <input_path>.
-        base_path: prefix for paths.
-
-    Returns:
-        Path, prefixed with `base_path`.
-    """
-    path = pathlib.Path(include_path.attrib['relative_path'])
-    if base_path is None:
-        return path
-    return base_path / path
-
-
-def parse_headers(root: xml.etree.ElementTree.Element,
-                  component_id: str) -> List[pathlib.Path]:
-    """Parse header files for a component.
-
-    Schema:
-        <component id="{component_id}" package_base_path="component">
-          <source relative_path="./" type="c_include">
-            <files mask="example.h"/>
-          </source>
-        </component>
-
-    Args:
-        root: root of element tree.
-        component_id: id of component to return.
-
-    Returns:
-        list of header files for the component.
-    """
-    return _parse_sources(root, component_id, 'c_include')
-
-
-def parse_sources(root: xml.etree.ElementTree.Element,
-                  component_id: str) -> List[pathlib.Path]:
-    """Parse source files for a component.
-
-    Schema:
-        <component id="{component_id}" package_base_path="component">
-          <source relative_path="./" type="src">
-            <files mask="example.cc"/>
-          </source>
-        </component>
-
-    Args:
-        root: root of element tree.
-        component_id: id of component to return.
-
-    Returns:
-        list of source files for the component.
-    """
-    source_files = []
-    for source_type in ('src', 'src_c', 'src_cpp', 'asm_include'):
-        source_files.extend(_parse_sources(root, component_id, source_type))
-    return source_files
-
-
-def parse_libs(root: xml.etree.ElementTree.Element,
-               component_id: str) -> List[pathlib.Path]:
-    """Parse pre-compiled libraries for a component.
-
-    Schema:
-        <component id="{component_id}" package_base_path="component">
-          <source relative_path="./" type="lib">
-            <files mask="example.a"/>
-          </source>
-        </component>
-
-    Args:
-        root: root of element tree.
-        component_id: id of component to return.
-
-    Returns:
-        list of pre-compiler libraries for the component.
-    """
-    return _parse_sources(root, component_id, 'lib')
-
-
-def _parse_sources(root: xml.etree.ElementTree.Element, component_id: str,
-                   source_type: str) -> List[pathlib.Path]:
-    """Parse <source> manifest stanza.
-
-    Schema:
-        <component id="{component_id}" package_base_path="component">
-          <source relative_path="./" type="{source_type}">
-            <files mask="example.h"/>
-          </source>
-        </component>
-
-    Args:
-        root: root of element tree.
-        component_id: id of component to return.
-        source_type: type of source to search for.
-
-    Returns:
-        list of source files for the component.
-    """
-    (component, base_path) = get_component(root, component_id)
-    if component is None:
-        return []
-
-    sources: List[pathlib.Path] = []
-    source_xpath = f'./source[@type="{source_type}"]'
-    for source in component.findall(source_xpath):
-        relative_path = pathlib.Path(source.attrib['relative_path'])
-        if base_path is not None:
-            relative_path = base_path / relative_path
-
-        sources.extend(relative_path / files.attrib['mask']
-                       for files in source.findall('./files'))
-    return sources
-
-
-def parse_dependencies(root: xml.etree.ElementTree.Element,
-                       component_id: str) -> List[str]:
-    """Parse the list of dependencies for a component.
-
-    Optional dependencies are ignored for parsing since they have to be
-    included explicitly.
-
-    Schema:
-        <dependencies>
-          <all>
-            <component_dependency value="component"/>
-            <component_dependency value="component"/>
-            <any_of>
-              <component_dependency value="component"/>
-              <component_dependency value="component"/>
-            </any_of>
-          </all>
-        </dependencies>
-
-    Args:
-        root: root of element tree.
-        component_id: id of component to return.
-
-    Returns:
-        list of component id dependencies of the component.
-    """
-    dependencies = []
-    xpath = f'./components/component[@id="{component_id}"]/dependencies/*'
-    for dependency in root.findall(xpath):
-        dependencies.extend(_parse_dependency(dependency))
-    return dependencies
-
-
-def _parse_dependency(dependency: xml.etree.ElementTree.Element) -> List[str]:
-    """Parse <all>, <any_of>, and <component_dependency> manifest stanzas.
-
-    Schema:
-        <all>
-          <component_dependency value="component"/>
-          <component_dependency value="component"/>
-          <any_of>
-            <component_dependency value="component"/>
-            <component_dependency value="component"/>
-          </any_of>
-        </all>
-
-    Args:
-        dependency: XML Element of dependency.
-
-    Returns:
-        list of component id dependencies.
-    """
-    if dependency.tag == 'component_dependency':
-        return [dependency.attrib['value']]
-    if dependency.tag == 'all':
-        dependencies = []
-        for subdependency in dependency:
-            dependencies.extend(_parse_dependency(subdependency))
-        return dependencies
-    if dependency.tag == 'any_of':
-        # Explicitly ignore.
-        return []
-
-    # Unknown dependency tag type.
-    return []
-
-
-def check_dependencies(root: xml.etree.ElementTree.Element,
-                       component_id: str,
-                       include: List[str],
-                       exclude: Optional[List[str]] = None) -> bool:
-    """Check the list of optional dependencies for a component.
-
-    Verifies that the optional dependencies for a component are satisfied by
-    components listed in `include` or `exclude`.
-
-    Args:
-        root: root of element tree.
-        component_id: id of component to check.
-        include: list of component ids included in the project.
-        exclude: list of component ids explicitly excluded from the project.
-
-    Returns:
-        True if dependencies are satisfied, False if not.
-    """
-    xpath = f'./components/component[@id="{component_id}"]/dependencies/*'
-    for dependency in root.findall(xpath):
-        if not _check_dependency(dependency, include, exclude=exclude):
-            return False
-    return True
-
-
-def _check_dependency(dependency: xml.etree.ElementTree.Element,
-                      include: List[str],
-                      exclude: Optional[List[str]] = None) -> bool:
-    """Check a dependency for a component.
-
-    Verifies that the given {dependency} is satisfied by components listed in
-    `include` or `exclude`.
-
-    Args:
-        dependency: XML Element of dependency.
-        include: list of component ids included in the project.
-        exclude: list of component ids explicitly excluded from the project.
-
-    Returns:
-        True if dependencies are satisfied, False if not.
-    """
-    if dependency.tag == 'component_dependency':
-        component_id = dependency.attrib['value']
-        return component_id in include or (exclude is not None
-                                           and component_id in exclude)
-    if dependency.tag == 'all':
-        for subdependency in dependency:
-            if not _check_dependency(subdependency, include, exclude=exclude):
-                return False
-        return True
-    if dependency.tag == 'any_of':
-        for subdependency in dependency:
-            if _check_dependency(subdependency, include, exclude=exclude):
-                return True
-
-        tree = xml.etree.ElementTree.tostring(dependency).decode('utf-8')
-        print(f'Unsatisfied dependency from: {tree}', file=sys.stderr)
-        return False
-
-    # Unknown dependency tag type.
-    return True
-
-
-def create_project(
-    root: xml.etree.ElementTree.Element,
-    include: List[str],
-    exclude: Optional[List[str]] = None
-) -> Tuple[List[str], List[str], List[pathlib.Path], List[pathlib.Path],
-           List[pathlib.Path], List[pathlib.Path]]:
-    """Create a project from a list of specified components.
-
-    Args:
-        root: root of element tree.
-        include: list of component ids included in the project.
-        exclude: list of component ids excluded from the project.
-
-    Returns:
-        (component_ids, defines, include_paths, headers, sources, libs) for the
-        project.
-    """
-    # Build the project list from the list of included components by expanding
-    # dependencies.
-    project_list = []
-    pending_list = include
-    while len(pending_list) > 0:
-        component_id = pending_list.pop(0)
-        if component_id in project_list:
-            continue
-        if exclude is not None and component_id in exclude:
-            continue
-
-        project_list.append(component_id)
-        pending_list.extend(parse_dependencies(root, component_id))
-
-    return (
-        project_list,
-        sum((parse_defines(root, component_id)
-             for component_id in project_list), []),
-        sum((parse_include_paths(root, component_id)
-             for component_id in project_list), []),
-        sum((parse_headers(root, component_id)
-             for component_id in project_list), []),
-        sum((parse_sources(root, component_id)
-             for component_id in project_list), []),
-        sum((parse_libs(root, component_id) for component_id in project_list),
-            []),
-    )
-
-
-def project(manifest_path: pathlib.Path,
-            include: Optional[List[str]] = None,
-            exclude: Optional[List[str]] = None,
-            path_prefix: Optional[str] = None):
-    """Output GN scope for a project with the specified components.
-
-    Args:
-        manifest_path: path to SDK manifest XML.
-        include: list of component ids included in the project.
-        exclude: list of component ids excluded from the project.
-        path_prefix: string prefix to prepend to all paths.
-    """
-    assert include is not None, "Project must include at least one component."
-
-    tree = xml.etree.ElementTree.parse(manifest_path)
-    root = tree.getroot()
-
-    (component_ids, defines, include_dirs, headers, sources, libs) = \
-        create_project(root, include, exclude=exclude)
-
-    for component_id in component_ids:
-        if not check_dependencies(
-                root, component_id, component_ids, exclude=exclude):
-            return
-
-    _gn_list_str_out('defines', defines)
-    _gn_list_path_out('include_dirs', include_dirs, path_prefix=path_prefix)
-    _gn_list_path_out('public', headers, path_prefix=path_prefix)
-    _gn_list_path_out('sources', sources, path_prefix=path_prefix)
-    _gn_list_path_out('libs', libs, path_prefix=path_prefix)
diff --git a/pw_build_mcuxpresso/py/pyproject.toml b/pw_build_mcuxpresso/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/pw_build_mcuxpresso/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/pw_build_mcuxpresso/py/setup.cfg b/pw_build_mcuxpresso/py/setup.cfg
deleted file mode 100644
index 6f093a4..0000000
--- a/pw_build_mcuxpresso/py/setup.cfg
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pw_build_mcuxpresso
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Python scripts for MCUXpresso targets
-
-[options]
-packages = find:
-zip_safe = False
-install_requires =
-
-[options.entry_points]
-console_scripts =
-    mcuxpresso_builder = pw_build_mcuxpresso.__main__:main
-
-[options.package_data]
-pw_build_mcuxpresso = py.typed
diff --git a/pw_build_mcuxpresso/py/setup.py b/pw_build_mcuxpresso/py/setup.py
deleted file mode 100644
index 694c286..0000000
--- a/pw_build_mcuxpresso/py/setup.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""pw_build_mcuxpresso"""
-
-import setuptools  # type: ignore
-
-setuptools.setup()  # Package definition in setup.cfg
diff --git a/pw_build_mcuxpresso/py/tests/components_test.py b/pw_build_mcuxpresso/py/tests/components_test.py
deleted file mode 100644
index e3559a8..0000000
--- a/pw_build_mcuxpresso/py/tests/components_test.py
+++ /dev/null
@@ -1,910 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Components Tests."""
-
-import pathlib
-import unittest
-import xml.etree.ElementTree
-
-from pw_build_mcuxpresso import components
-
-
-class GetComponentTest(unittest.TestCase):
-    """get_component tests."""
-    def test_without_basepath(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-
-        (component, base_path) = components.get_component(root, 'test')
-
-        self.assertIsInstance(component, xml.etree.ElementTree.Element)
-        self.assertEqual(component.tag, 'component')
-        self.assertEqual(base_path, None)
-
-    def test_with_basepath(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test" package_base_path="test">
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-
-        (component, base_path) = components.get_component(root, 'test')
-
-        self.assertIsInstance(component, xml.etree.ElementTree.Element)
-        self.assertEqual(component.tag, 'component')
-        self.assertEqual(base_path, pathlib.Path('test'))
-
-    def test_component_not_found(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="other">
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-
-        (component, base_path) = components.get_component(root, 'test')
-
-        self.assertEqual(component, None)
-        self.assertEqual(base_path, None)
-
-
-class ParseDefinesTest(unittest.TestCase):
-    """parse_defines tests."""
-    def test_parse_defines(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-              <defines>
-                <define name="TEST_WITH_VALUE" value="1"/>
-                <define name="TEST_WITHOUT_VALUE"/>
-              </defines>
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        defines = components.parse_defines(root, 'test')
-
-        self.assertEqual(defines, ['TEST_WITH_VALUE=1', 'TEST_WITHOUT_VALUE'])
-
-    def test_no_defines(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        defines = components.parse_defines(root, 'test')
-
-        self.assertEqual(defines, [])
-
-
-class ParseIncludePathsTest(unittest.TestCase):
-    """parse_include_paths tests."""
-    def test_parse_include_paths(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-              <include_paths>
-                <include_path relative_path="example" type="c_include"/>
-                <include_path relative_path="asm" type="asm_include"/>
-              </include_paths>
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        include_paths = components.parse_include_paths(root, 'test')
-
-        self.assertEqual(include_paths,
-                         [pathlib.Path('example'),
-                          pathlib.Path('asm')])
-
-    def test_with_base_path(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test" package_base_path="src">
-              <include_paths>
-                <include_path relative_path="example" type="c_include"/>
-                <include_path relative_path="asm" type="asm_include"/>
-              </include_paths>
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        include_paths = components.parse_include_paths(root, 'test')
-
-        self.assertEqual(
-            include_paths,
-            [pathlib.Path('src/example'),
-             pathlib.Path('src/asm')])
-
-    def test_unknown_type(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test" package_base_path="src">
-              <include_paths>
-                <include_path relative_path="rust" type="rust_include"/>
-              </include_paths>
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        include_paths = components.parse_include_paths(root, 'test')
-
-        self.assertEqual(include_paths, [])
-
-    def test_no_include_paths(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        include_paths = components.parse_include_paths(root, 'test')
-
-        self.assertEqual(include_paths, [])
-
-
-class ParseHeadersTest(unittest.TestCase):
-    """parse_headers tests."""
-    def test_parse_headers(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-              <source relative_path="include" type="c_include">
-                <files mask="test.h"/>
-                <files mask="test_types.h"/>
-              </source>
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        headers = components.parse_headers(root, 'test')
-
-        self.assertEqual(headers, [
-            pathlib.Path('include/test.h'),
-            pathlib.Path('include/test_types.h')
-        ])
-
-    def test_with_base_path(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test" package_base_path="src">
-              <source relative_path="include" type="c_include">
-                <files mask="test.h"/>
-                <files mask="test_types.h"/>
-              </source>
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        headers = components.parse_headers(root, 'test')
-
-        self.assertEqual(headers, [
-            pathlib.Path('src/include/test.h'),
-            pathlib.Path('src/include/test_types.h')
-        ])
-
-    def test_multiple_sets(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-              <source relative_path="include" type="c_include">
-                <files mask="test.h"/>
-              </source>
-              <source relative_path="internal" type="c_include">
-                <files mask="test_types.h"/>
-              </source>
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        headers = components.parse_headers(root, 'test')
-
-        self.assertEqual(headers, [
-            pathlib.Path('include/test.h'),
-            pathlib.Path('internal/test_types.h')
-        ])
-
-    def test_no_headers(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        headers = components.parse_headers(root, 'test')
-
-        self.assertEqual(headers, [])
-
-
-class ParseSourcesTest(unittest.TestCase):
-    """parse_sources tests."""
-    def test_parse_sources(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-              <source relative_path="src" type="src">
-                <files mask="main.cc"/>
-                <files mask="test.cc"/>
-              </source>
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        sources = components.parse_sources(root, 'test')
-
-        self.assertEqual(
-            sources,
-            [pathlib.Path('src/main.cc'),
-             pathlib.Path('src/test.cc')])
-
-    def test_with_base_path(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test" package_base_path="src">
-              <source relative_path="app" type="src">
-                <files mask="main.cc"/>
-                <files mask="test.cc"/>
-              </source>
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        sources = components.parse_sources(root, 'test')
-
-        self.assertEqual(
-            sources,
-            [pathlib.Path('src/app/main.cc'),
-             pathlib.Path('src/app/test.cc')])
-
-    def test_multiple_sets(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-              <source relative_path="shared" type="src">
-                <files mask="test.cc"/>
-              </source>
-              <source relative_path="lib" type="src_c">
-                <files mask="test.c"/>
-              </source>
-              <source relative_path="app" type="src_cpp">
-                <files mask="main.cc"/>
-              </source>
-              <source relative_path="startup" type="asm_include">
-                <files mask="boot.s"/>
-              </source>
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        sources = components.parse_sources(root, 'test')
-
-        self.assertEqual(sources, [
-            pathlib.Path('shared/test.cc'),
-            pathlib.Path('lib/test.c'),
-            pathlib.Path('app/main.cc'),
-            pathlib.Path('startup/boot.s')
-        ])
-
-    def test_unknown_type(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-            <source relative_path="src" type="rust">
-                <files mask="test.rs"/>
-              </source>
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        sources = components.parse_sources(root, 'test')
-
-        self.assertEqual(sources, [])
-
-    def test_no_sources(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        sources = components.parse_sources(root, 'test')
-
-        self.assertEqual(sources, [])
-
-
-class ParseLibsTest(unittest.TestCase):
-    """parse_libs tests."""
-    def test_parse_libs(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-              <source relative_path="gcc" type="lib">
-                <files mask="libtest.a"/>
-                <files mask="libtest_arm.a"/>
-              </source>
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        libs = components.parse_libs(root, 'test')
-
-        self.assertEqual(
-            libs,
-            [pathlib.Path('gcc/libtest.a'),
-             pathlib.Path('gcc/libtest_arm.a')])
-
-    def test_with_base_path(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test" package_base_path="src">
-              <source relative_path="gcc" type="lib">
-                <files mask="libtest.a"/>
-                <files mask="libtest_arm.a"/>
-              </source>
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        libs = components.parse_libs(root, 'test')
-
-        self.assertEqual(libs, [
-            pathlib.Path('src/gcc/libtest.a'),
-            pathlib.Path('src/gcc/libtest_arm.a')
-        ])
-
-    def test_multiple_sets(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-              <source relative_path="gcc" type="lib">
-                <files mask="libtest.a"/>
-              </source>
-              <source relative_path="arm" type="lib">
-                <files mask="libtest_arm.a"/>
-              </source>
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        libs = components.parse_libs(root, 'test')
-
-        self.assertEqual(
-            libs,
-            [pathlib.Path('gcc/libtest.a'),
-             pathlib.Path('arm/libtest_arm.a')])
-
-    def test_no_libs(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        libs = components.parse_libs(root, 'test')
-
-        self.assertEqual(libs, [])
-
-
-class ParseDependenciesTest(unittest.TestCase):
-    """parse_dependencies tests."""
-    def test_component_dependency(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-              <dependencies>
-                <component_dependency value="foo"/>
-              </dependencies>
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        dependencies = components.parse_dependencies(root, 'test')
-
-        self.assertEqual(dependencies, ['foo'])
-
-    def test_all(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-              <dependencies>
-                <all>
-                  <component_dependency value="foo"/>
-                  <component_dependency value="bar"/>
-                  <component_dependency value="baz"/>
-                </all>
-              </dependencies>
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        dependencies = components.parse_dependencies(root, 'test')
-
-        self.assertEqual(dependencies, ['foo', 'bar', 'baz'])
-
-    def test_any_of_ignored(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-              <dependencies>
-                <any_of>
-                  <component_dependency value="foo"/>
-                  <component_dependency value="bar"/>
-                  <component_dependency value="baz"/>
-                </any_of>
-              </dependencies>
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        dependencies = components.parse_dependencies(root, 'test')
-
-        self.assertEqual(dependencies, [])
-
-    def test_any_of_inside_all_ignored(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-              <dependencies>
-                <all>
-                  <component_dependency value="foo"/>
-                  <component_dependency value="bar"/>
-                  <component_dependency value="baz"/>
-                  <any_of>
-                    <all>
-                      <component_dependency value="frodo"/>
-                      <component_dependency value="bilbo"/>
-                    </all>
-                    <component_dependency value="gandalf"/>
-                  </any_of>
-                </all>
-              </dependencies>
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        dependencies = components.parse_dependencies(root, 'test')
-
-        self.assertEqual(dependencies, ['foo', 'bar', 'baz'])
-
-    def test_no_dependencies(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        dependencies = components.parse_dependencies(root, 'test')
-
-        self.assertEqual(dependencies, [])
-
-
-class CheckDependenciesTest(unittest.TestCase):
-    """check_dependencies tests."""
-    def test_any_of_satisfied(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-              <dependencies>
-                <any_of>
-                  <component_dependency value="foo"/>
-                  <component_dependency value="bar"/>
-                  <component_dependency value="baz"/>
-                </any_of>
-              </dependencies>
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        satisfied = components.check_dependencies(root,
-                                                  'test', ['test', 'foo'],
-                                                  exclude=None)
-
-        self.assertEqual(satisfied, True)
-
-    def test_any_of_not_satisfied(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-              <dependencies>
-                <any_of>
-                  <component_dependency value="foo"/>
-                  <component_dependency value="bar"/>
-                  <component_dependency value="baz"/>
-                </any_of>
-              </dependencies>
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        satisfied = components.check_dependencies(root,
-                                                  'test', ['test'],
-                                                  exclude=None)
-
-        self.assertEqual(satisfied, False)
-
-    def test_any_of_satisfied_by_exclude(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-              <dependencies>
-                <any_of>
-                  <component_dependency value="foo"/>
-                  <component_dependency value="bar"/>
-                  <component_dependency value="baz"/>
-                </any_of>
-              </dependencies>
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        satisfied = components.check_dependencies(root,
-                                                  'test', ['test'],
-                                                  exclude=['foo'])
-
-        self.assertEqual(satisfied, True)
-
-    def test_any_of_all_satisfied(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-              <dependencies>
-                <any_of>
-                  <all>
-                    <component_dependency value="foo"/>
-                    <component_dependency value="bar"/>
-                    <component_dependency value="baz"/>
-                  </all>
-                </any_of>
-              </dependencies>
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        satisfied = components.check_dependencies(
-            root, 'test', ['test', 'foo', 'bar', 'baz'], exclude=None)
-
-        self.assertEqual(satisfied, True)
-
-    def test_any_of_all_not_satisfied(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-              <dependencies>
-                <any_of>
-                  <all>
-                    <component_dependency value="foo"/>
-                    <component_dependency value="bar"/>
-                    <component_dependency value="baz"/>
-                  </all>
-                </any_of>
-              </dependencies>
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        satisfied = components.check_dependencies(root,
-                                                  'test',
-                                                  ['test', 'foo', 'bar'],
-                                                  exclude=None)
-
-        self.assertEqual(satisfied, False)
-
-    def test_any_of_all_satisfied_by_exclude(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-              <dependencies>
-                <any_of>
-                  <all>
-                    <component_dependency value="foo"/>
-                    <component_dependency value="bar"/>
-                    <component_dependency value="baz"/>
-                  </all>
-                </any_of>
-              </dependencies>
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        satisfied = components.check_dependencies(root,
-                                                  'test',
-                                                  ['test', 'foo', 'bar'],
-                                                  exclude=['baz'])
-
-        self.assertEqual(satisfied, True)
-
-    def test_any_of_all_or_one_satisfied(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-              <dependencies>
-                <any_of>
-                  <all>
-                    <component_dependency value="foo"/>
-                    <component_dependency value="bar"/>
-                    <component_dependency value="baz"/>
-                  </all>
-                  <component_dependency value="frodo"/>
-                </any_of>
-              </dependencies>
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        satisfied = components.check_dependencies(root,
-                                                  'test', ['test', 'frodo'],
-                                                  exclude=None)
-
-        self.assertEqual(satisfied, True)
-
-    def test_any_of_all_or_one_not_satisfied(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-              <dependencies>
-                <any_of>
-                  <all>
-                    <component_dependency value="foo"/>
-                    <component_dependency value="bar"/>
-                    <component_dependency value="baz"/>
-                  </all>
-                  <component_dependency value="frodo"/>
-                </any_of>
-              </dependencies>
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        satisfied = components.check_dependencies(root,
-                                                  'test', ['test'],
-                                                  exclude=None)
-
-        self.assertEqual(satisfied, False)
-
-    def test_any_of_all_or_one_satisfied_by_exclude(self):
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-              <dependencies>
-                <any_of>
-                  <all>
-                    <component_dependency value="foo"/>
-                    <component_dependency value="bar"/>
-                    <component_dependency value="baz"/>
-                  </all>
-                  <component_dependency value="frodo"/>
-                </any_of>
-              </dependencies>
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        satisfied = components.check_dependencies(root,
-                                                  'test', ['test'],
-                                                  exclude=['frodo'])
-
-        self.assertEqual(satisfied, True)
-
-
-class CreateProjectTest(unittest.TestCase):
-    """create_project tests."""
-    def test_create_project(self):
-        """test creating a project."""
-        test_manifest_xml = '''
-        <manifest>
-          <components>
-            <component id="test">
-              <dependencies>
-                <component_dependency value="foo"/>
-                <component_dependency value="bar"/>
-                <any_of>
-                  <component_dependency value="baz"/>
-                </any_of>
-              </dependencies>
-            </component>
-            <component id="foo" package_base_path="foo">
-              <defines>
-                <define name="FOO"/>
-              </defines>
-              <source relative_path="include" type="c_include">
-                <files mask="foo.h"/>
-              </source>
-              <source relative_path="src" type="src">
-                <files mask="foo.cc"/>
-              </source>
-              <include_paths>
-                <include_path relative_path="include" type="c_include"/>
-              </include_paths>
-            </component>
-            <component id="bar" package_base_path="bar">
-              <defines>
-                <define name="BAR"/>
-              </defines>
-              <source relative_path="include" type="c_include">
-                <files mask="bar.h"/>
-              </source>
-              <source relative_path="src" type="src">
-                <files mask="bar.cc"/>
-              </source>
-              <include_paths>
-                <include_path relative_path="include" type="c_include"/>
-              </include_paths>
-            </component>
-            <!-- baz should not be included in the output -->
-            <component id="baz" package_base_path="baz">
-              <defines>
-                <define name="BAZ"/>
-              </defines>
-              <source relative_path="include" type="c_include">
-                <files mask="baz.h"/>
-              </source>
-              <source relative_path="src" type="src">
-                <files mask="baz.cc"/>
-              </source>
-              <include_paths>
-                <include_path relative_path="include" type="c_include"/>
-              </include_paths>
-            </component>
-            <component id="frodo" package_base_path="frodo">
-              <dependencies>
-                <component_dependency value="bilbo"/>
-              </dependencies>
-              <defines>
-                <define name="FRODO"/>
-              </defines>
-              <source relative_path="include" type="c_include">
-                <files mask="frodo.h"/>
-              </source>
-              <source relative_path="src" type="src">
-                <files mask="frodo.cc"/>
-              </source>
-              <source relative_path="./" type="lib">
-                <files mask="libonering.a"/>
-              </source>
-              <include_paths>
-                <include_path relative_path="include" type="c_include"/>
-              </include_paths>
-            </component>
-            <!-- bilbo should be excluded from the project -->
-            <component id="bilbo" package_base_path="bilbo">
-              <defines>
-                <define name="BILBO"/>
-              </defines>
-              <source relative_path="include" type="c_include">
-                <files mask="bilbo.h"/>
-              </source>
-              <source relative_path="src" type="src">
-                <files mask="bilbo.cc"/>
-              </source>
-              <include_paths>
-                <include_path relative_path="include" type="c_include"/>
-              </include_paths>
-            </component>
-          </components>
-        </manifest>
-        '''
-        root = xml.etree.ElementTree.fromstring(test_manifest_xml)
-        (component_ids, defines, include_dirs, headers, sources, libs) = \
-            components.create_project(root, ['test', 'frodo'],
-                                      exclude=['bilbo'])
-
-        self.assertEqual(component_ids, ['test', 'frodo', 'foo', 'bar'])
-        self.assertEqual(defines, ['FRODO', 'FOO', 'BAR'])
-        self.assertEqual(include_dirs, [
-            pathlib.Path('frodo/include'),
-            pathlib.Path('foo/include'),
-            pathlib.Path('bar/include')
-        ])
-        self.assertEqual(headers, [
-            pathlib.Path('frodo/include/frodo.h'),
-            pathlib.Path('foo/include/foo.h'),
-            pathlib.Path('bar/include/bar.h')
-        ])
-        self.assertEqual(sources, [
-            pathlib.Path('frodo/src/frodo.cc'),
-            pathlib.Path('foo/src/foo.cc'),
-            pathlib.Path('bar/src/bar.cc')
-        ])
-        self.assertEqual(libs, [pathlib.Path('frodo/libonering.a')])
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_bytes/BUILD b/pw_bytes/BUILD
new file mode 100644
index 0000000..293ae83
--- /dev/null
+++ b/pw_bytes/BUILD
@@ -0,0 +1,69 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_bytes",
+    srcs = [
+        "byte_builder.cc",
+    ],
+    hdrs = [
+        "public/pw_bytes/array.h",
+        "public/pw_bytes/byte_builder.h",
+        "public/pw_bytes/endian.h",
+        "public/pw_bytes/span.h",
+    ],
+    includes = ["public"],
+    deps = [
+        "//pw_preprocessor",
+        "//pw_span",
+        "//pw_status",
+    ],
+)
+
+pw_cc_test(
+    name = "array_test",
+    srcs = ["array_test.cc"],
+    deps = [
+        ":pw_bytes",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "byte_builder_test",
+    srcs = ["byte_builder_test.cc"],
+    deps = [
+        ":pw_bytes",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "endian_test",
+    srcs = ["endian_test.cc"],
+    deps = [
+        ":pw_bytes",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_bytes/BUILD.bazel b/pw_bytes/BUILD.bazel
deleted file mode 100644
index 9609593..0000000
--- a/pw_bytes/BUILD.bazel
+++ /dev/null
@@ -1,83 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_bytes",
-    srcs = [
-        "byte_builder.cc",
-    ],
-    hdrs = [
-        "public/pw_bytes/array.h",
-        "public/pw_bytes/byte_builder.h",
-        "public/pw_bytes/endian.h",
-        "public/pw_bytes/span.h",
-        "public/pw_bytes/units.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_polyfill",
-        "//pw_polyfill:bit",
-        "//pw_polyfill:cstddef",
-        "//pw_polyfill:iterator",
-        "//pw_polyfill:type_traits",
-        "//pw_preprocessor",
-        "//pw_span",
-        "//pw_status",
-    ],
-)
-
-pw_cc_test(
-    name = "array_test",
-    srcs = ["array_test.cc"],
-    deps = [
-        ":pw_bytes",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "byte_builder_test",
-    srcs = ["byte_builder_test.cc"],
-    deps = [
-        ":pw_bytes",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "endian_test",
-    srcs = ["endian_test.cc"],
-    deps = [
-        ":pw_bytes",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "units_test",
-    srcs = ["units_test.cc"],
-    deps = [
-        ":pw_bytes",
-    ],
-)
diff --git a/pw_bytes/BUILD.gn b/pw_bytes/BUILD.gn
index 554fda1..1b98997 100644
--- a/pw_bytes/BUILD.gn
+++ b/pw_bytes/BUILD.gn
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2020 The Pigweed Authors
 #
 # 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
@@ -31,7 +31,6 @@
     "public/pw_bytes/byte_builder.h",
     "public/pw_bytes/endian.h",
     "public/pw_bytes/span.h",
-    "public/pw_bytes/units.h",
   ]
   sources = [ "byte_builder.cc" ]
   public_deps = [
@@ -45,7 +44,6 @@
     ":array_test",
     ":byte_builder_test",
     ":endian_test",
-    ":units_test",
   ]
   group_deps = [
     "$dir_pw_preprocessor:tests",
@@ -68,11 +66,6 @@
   sources = [ "endian_test.cc" ]
 }
 
-pw_test("units_test") {
-  deps = [ ":pw_bytes" ]
-  sources = [ "units_test.cc" ]
-}
-
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
   report_deps = [ ":byte_builder_size_report" ]
diff --git a/pw_bytes/CMakeLists.txt b/pw_bytes/CMakeLists.txt
index 1703386..33f48b7 100644
--- a/pw_bytes/CMakeLists.txt
+++ b/pw_bytes/CMakeLists.txt
@@ -14,64 +14,10 @@
 
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
 
-pw_add_module_library(pw_bytes
-  HEADERS
-    public/pw_bytes/array.h
-    public/pw_bytes/byte_builder.h
-    public/pw_bytes/endian.h
-    public/pw_bytes/span.h
-    public/pw_bytes/units.h
-  PUBLIC_INCLUDES
-    public
+pw_auto_add_simple_module(pw_bytes
   PUBLIC_DEPS
-    pw_polyfill
-    pw_polyfill.cstddef
-    pw_polyfill.span
+    pw_polyfill.overrides
     pw_preprocessor
+    pw_span
     pw_status
-  SOURCES
-    byte_builder.cc
-)
-if(Zephyr_FOUND AND CONFIG_PIGWEED_BYTES)
-  zephyr_link_libraries(pw_bytes)
-endif()
-
-pw_add_test(pw_bytes.array_test
-  SOURCES
-    array_test.cc
-  DEPS
-    pw_bytes
-  GROUPS
-    modules
-    pw_bytes
-)
-
-pw_add_test(pw_bytes.byte_builder_test
-  SOURCES
-    byte_builder_test.cc
-  DEPS
-    pw_bytes
-  GROUPS
-    modules
-    pw_bytes
-)
-
-pw_add_test(pw_bytes.endian_test
-  SOURCES
-    endian_test.cc
-  DEPS
-    pw_bytes
-  GROUPS
-    modules
-    pw_bytes
-)
-
-pw_add_test(pw_bytes.units_test
-  SOURCES
-    units_test.cc
-  DEPS
-    pw_bytes
-  GROUPS
-    modules
-    pw_bytes
 )
diff --git a/pw_bytes/Kconfig b/pw_bytes/Kconfig
deleted file mode 100644
index 482ef21..0000000
--- a/pw_bytes/Kconfig
+++ /dev/null
@@ -1,20 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-config PIGWEED_BYTES
-    bool "Enable the Pigweed bytes library (pw_bytes)"
-    select PIGWEED_POLYFILL_OVERRIDES
-    select PIGWEED_PREPROCESSOR
-    select PIGWEED_SPAN
-    select PIGWEED_STATUS
diff --git a/pw_bytes/OWNERS b/pw_bytes/OWNERS
deleted file mode 100644
index d96cbc6..0000000
--- a/pw_bytes/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-hepler@google.com
diff --git a/pw_bytes/byte_builder.cc b/pw_bytes/byte_builder.cc
index 96552a6..c3f2f7d 100644
--- a/pw_bytes/byte_builder.cc
+++ b/pw_bytes/byte_builder.cc
@@ -17,16 +17,14 @@
 namespace pw {
 
 ByteBuilder& ByteBuilder::append(size_t count, std::byte b) {
-  std::byte* const append_destination = buffer_.begin() + size_;
-  std::fill_n(append_destination, ResizeForAppend(count), b);
+  std::byte* const append_destination = &buffer_[size_];
+  std::memset(append_destination, static_cast<int>(b), ResizeForAppend(count));
   return *this;
 }
 
 ByteBuilder& ByteBuilder::append(const void* bytes, size_t count) {
-  std::byte* const append_destination = buffer_.begin() + size_;
-  std::copy_n(static_cast<const std::byte*>(bytes),
-              ResizeForAppend(count),
-              append_destination);
+  std::byte* const append_destination = &buffer_[size_];
+  std::memcpy(append_destination, bytes, ResizeForAppend(count));
   return *this;
 }
 
diff --git a/pw_bytes/docs.rst b/pw_bytes/docs.rst
index 6313abb..2e21aaa 100644
--- a/pw_bytes/docs.rst
+++ b/pw_bytes/docs.rst
@@ -42,43 +42,3 @@
 pw_bytes/endian.h
 -----------------
 Functions for converting the endianness of integral values.
-
-pw_bytes/units.h
-----------------
-Constants, functions and user-defined literals for specifying a number of bytes
-in powers of two, as defined by IEC 60027-2 A.2 and ISO/IEC 80000:13-2008.
-
-The supported suffixes include:
- * ``_B``   for bytes     (1024^0)
- * ``_KiB`` for kibibytes (1024^1)
- * ``_MiB`` for mibibytes (1024^2)
- * ``_GiB`` for gibibytes (1024^3)
- * ``_TiB`` for tebibytes (1024^4)
- * ``_PiB`` for pebibytes (1024^5)
- * ``_EiB`` for exbibytes (1024^6)
-
-In order to use these you must use a using namespace directive, for example:
-
-.. code-block:: cpp
-
-  #include "pw_bytes/units.h"
-
-  using namespace pw::bytes::unit_literals;
-
-  constexpr size_t kRandomBufferSizeBytes = 1_MiB + 42_KiB;
-
-In some cases, the use of user-defined literals is not permitted because of the
-required using namespace directive. One example of this is in header files,
-where it is undesirable to pollute the namespace. For this situation, there are
-also similar functions:
-
-.. code-block:: cpp
-
-  #include "pw_bytes/units.h"
-
-  constexpr size_t kBufferSizeBytes = pw::bytes::MiB(1) + pw::bytes::KiB(42);
-
-Zephyr
-======
-To enable ``pw_bytes`` for Zephyr add ``CONFIG_PIGWEED_BYTES=y`` to the
-project's configuration.
diff --git a/pw_bytes/public/pw_bytes/array.h b/pw_bytes/public/pw_bytes/array.h
index 5b81229..453f9ea 100644
--- a/pw_bytes/public/pw_bytes/array.h
+++ b/pw_bytes/public/pw_bytes/array.h
@@ -20,8 +20,6 @@
 #include <cstddef>
 #include <iterator>
 
-#include "pw_polyfill/language_feature_macros.h"
-
 namespace pw::bytes {
 namespace internal {
 
@@ -32,7 +30,7 @@
 // byte-sized elements or the underlying bytes of an integer (as little-endian).
 // std::memcpy cannot be used since it is not constexpr.
 template <typename B, typename T, typename... Args>
-PW_CONSTEVAL void CopyBytes(B* array, T value, Args... args) {
+consteval void CopyBytes(B* array, T value, Args... args) {
   static_assert(sizeof(B) == sizeof(std::byte));
 
   if constexpr (UseBytesDirectly<T>) {
@@ -58,7 +56,7 @@
 
 // Evaluates to the size in bytes of an integer or byte array.
 template <typename T>
-PW_CONSTEVAL size_t SizeOfBytes(const T& arg) {
+consteval size_t SizeOfBytes(const T& arg) {
   if constexpr (UseBytesDirectly<T>) {
     return sizeof(arg);
   } else {
@@ -68,12 +66,12 @@
 }
 
 template <typename B, typename T, size_t... kIndex>
-PW_CONSTEVAL auto String(const T& array, std::index_sequence<kIndex...>) {
+consteval auto String(const T& array, std::index_sequence<kIndex...>) {
   return std::array{static_cast<B>(array[kIndex])...};
 }
 
 template <typename T, typename U>
-PW_CONSTEVAL bool CanBeRepresentedAsByteType(const U& value) {
+consteval bool CanBeRepresentedAsByteType(const U& value) {
   return static_cast<U>(static_cast<T>(value)) == value;
 }
 
@@ -82,7 +80,7 @@
 // Concatenates arrays or integers as a byte array at compile time. Integer
 // values are copied little-endian. Spans are copied byte-for-byte.
 template <typename B = std::byte, typename... Args>
-PW_CONSTEVAL auto Concat(Args... args) {
+consteval auto Concat(Args... args) {
   std::array<B, (internal::SizeOfBytes(args) + ...)> bytes{};
   internal::CopyBytes(bytes.begin(), args...);
   return bytes;
@@ -92,27 +90,27 @@
 template <typename B = std::byte,
           size_t kSize,
           typename Indices = std::make_index_sequence<kSize - 1>>
-PW_CONSTEVAL auto String(const char (&str)[kSize]) {
+consteval auto String(const char (&str)[kSize]) {
   return internal::String<B>(str, Indices{});
 }
 
 // String overload for the empty string "".
 template <typename B = std::byte>
-PW_CONSTEVAL auto String(const char (&)[1]) {
+consteval auto String(const char (&)[1]) {
   return std::array<B, 0>{};
 }
 
 // Creates an array of bytes from values passed as template parameters. The
 // values are guaranteed to be representable in the destination byte type.
 template <typename B, auto... values>
-PW_CONSTEVAL auto Array() {
+consteval auto Array() {
   static_assert((internal::CanBeRepresentedAsByteType<B>(values) && ...));
   return std::array<B, sizeof...(values)>{static_cast<B>(values)...};
 }
 
 // Array() defaults to using std::byte.
 template <auto... values>
-PW_CONSTEVAL auto Array() {
+consteval auto Array() {
   return Array<std::byte, values...>();
 }
 
diff --git a/pw_bytes/public/pw_bytes/byte_builder.h b/pw_bytes/public/pw_bytes/byte_builder.h
index 978de5b..ae931b0 100644
--- a/pw_bytes/public/pw_bytes/byte_builder.h
+++ b/pw_bytes/public/pw_bytes/byte_builder.h
@@ -209,7 +209,7 @@
 
   ByteBuilder& operator=(const ByteBuilder&) = delete;
 
-  // Returns the contents of the bytes buffer.
+  // Returns the contents of the bytes buffer. Always null-terminated.
   const std::byte* data() const { return buffer_.data(); }
 
   // Returns the ByteBuilder's status, which reflects the most recent error
diff --git a/pw_bytes/public/pw_bytes/units.h b/pw_bytes/public/pw_bytes/units.h
deleted file mode 100644
index 97bb8d6..0000000
--- a/pw_bytes/public/pw_bytes/units.h
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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
-
-namespace pw::bytes {
-
-// Size constants for bytes in powers of two as defined by IEC 60027-2 A.2 and
-// ISO/IEC 80000:13-2008:
-
-// Kibibytes (KiB): 1024^1 or 2^10
-inline constexpr unsigned long long int kBytesInKibibyte = 1ull << 10;
-
-// Mebibytes (MiB): 1024^2 or 2^20
-inline constexpr unsigned long long int kBytesInMibibyte = 1ull << 20;
-
-// Gibibytes (GiB): 1024^3 or 2^30
-inline constexpr unsigned long long int kBytesInGibibyte = 1ull << 30;
-
-// Tebibytes (TiB): 1024^4 or 2^40
-inline constexpr unsigned long long int kBytesInTebibyte = 1ull << 40;
-
-// Pebibytes (PiB): 1024^5 or 2^50
-inline constexpr unsigned long long int kBytesInPebibyte = 1ull << 50;
-
-// Exbibytes (EiB): 1024^6 or 2^60
-inline constexpr unsigned long long int kBytesInExbibyte = 1ull << 60;
-
-// Functions for specifying a number of bytes in powers of two, as defined by
-// IEC 60027-2 A.2 and ISO/IEC 80000:13-2008.
-//
-// These are useful in headers when using user-defined literals are disallowed.
-//
-//   #include "pw_bytes/units.h"
-//
-//   constexpr size_t kBufferSizeBytes = pw::bytes::MiB(1) + pw::bytes::KiB(42);
-inline constexpr unsigned long long int B(unsigned long long int bytes) {
-  return bytes;
-}
-
-inline constexpr unsigned long long int KiB(unsigned long long int kibibytes) {
-  return kibibytes * kBytesInKibibyte;
-}
-
-inline constexpr unsigned long long int MiB(unsigned long long int mibibytes) {
-  return mibibytes * kBytesInMibibyte;
-}
-
-inline constexpr unsigned long long int GiB(unsigned long long int gibibytes) {
-  return gibibytes * kBytesInGibibyte;
-}
-
-inline constexpr unsigned long long int TiB(unsigned long long int tebibytes) {
-  return tebibytes * kBytesInTebibyte;
-}
-
-inline constexpr unsigned long long int PiB(unsigned long long int pebibytes) {
-  return pebibytes * kBytesInPebibyte;
-}
-
-inline constexpr unsigned long long int EiB(unsigned long long int exbibytes) {
-  return exbibytes * kBytesInExbibyte;
-}
-
-namespace unit_literals {
-
-// User-defined literals for specifying a number of bytes in powers of two, as
-// defined by IEC 60027-2 A.2 and ISO/IEC 80000:13-2008.
-//
-// The supported prefixes include:
-// _B   for bytes     (1024^0)
-// _KiB for kibibytes (1024^1)
-// _MiB for mibibytes (1024^2)
-// _GiB for gibibytes (1024^3)
-// _TiB for tebibytes (1024^4)
-// _PiB for pebibytes (1024^5)
-// _EiB for exbibytes (1024^6)
-//
-// In order to use these you must use a using namespace directive, for example:
-//
-//  #include "pw_bytes/units.h"
-//
-//  using namespace pw::bytes::unit_literals;
-//
-//  constepxr size_t kRandomBufferSizeBytes = 1_MiB + 42_KiB;
-constexpr unsigned long long int operator""_B(unsigned long long int bytes) {
-  return bytes;
-}
-
-constexpr unsigned long long int operator""_KiB(
-    unsigned long long int kibibytes) {
-  return kibibytes * kBytesInKibibyte;
-}
-
-constexpr unsigned long long int operator""_MiB(
-    unsigned long long int mibibytes) {
-  return mibibytes * kBytesInMibibyte;
-}
-
-constexpr unsigned long long int operator""_GiB(
-    unsigned long long int gibibytes) {
-  return gibibytes * kBytesInGibibyte;
-}
-
-constexpr unsigned long long int operator""_TiB(
-    unsigned long long int tebibytes) {
-  return tebibytes * kBytesInTebibyte;
-}
-
-constexpr unsigned long long int operator""_PiB(
-    unsigned long long int pebibytes) {
-  return pebibytes * kBytesInPebibyte;
-}
-
-constexpr unsigned long long int operator""_EiB(
-    unsigned long long int exbibytes) {
-  return exbibytes * kBytesInExbibyte;
-}
-
-}  // namespace unit_literals
-}  // namespace pw::bytes
diff --git a/pw_bytes/size_report/BUILD b/pw_bytes/size_report/BUILD
new file mode 100644
index 0000000..d58feea
--- /dev/null
+++ b/pw_bytes/size_report/BUILD
@@ -0,0 +1,31 @@
+# Copyright 2019 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_binary",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_binary(
+    name = "build_byte_buffer",
+    srcs = ["byte_builder_size_report.cc"],
+    deps = [
+        "//pw_bloat:bloat_this_binary",
+        "//pw_bytes",
+    ],
+)
\ No newline at end of file
diff --git a/pw_bytes/size_report/BUILD.bazel b/pw_bytes/size_report/BUILD.bazel
deleted file mode 100644
index 201b0a6..0000000
--- a/pw_bytes/size_report/BUILD.bazel
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright 2019 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_binary",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_binary(
-    name = "build_byte_buffer",
-    srcs = ["byte_builder_size_report.cc"],
-    deps = [
-        "//pw_bloat:bloat_this_binary",
-        "//pw_bytes",
-    ],
-)
diff --git a/pw_bytes/units_test.cc b/pw_bytes/units_test.cc
deleted file mode 100644
index a857eaf..0000000
--- a/pw_bytes/units_test.cc
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_bytes/units.h"
-
-#include <cstddef>
-
-namespace pw::bytes {
-namespace {
-
-using namespace pw::bytes::unit_literals;
-
-// Byte Function tests
-static_assert(B(1) == 1ull);
-static_assert(B(42) == 42ull);
-
-static_assert(KiB(1) == 1'024ull);
-static_assert(KiB(42) == 43'008ull);
-
-static_assert(MiB(1) == 1'048'576ull);
-static_assert(MiB(42) == 44'040'192ull);
-
-static_assert(GiB(1) == 1'073'741'824ull);
-static_assert(GiB(42) == 45'097'156'608ull);
-
-static_assert(TiB(1) == 1'099'511'627'776ull);
-static_assert(TiB(42) == 46'179'488'366'592ull);
-
-static_assert(PiB(1) == 1'125'899'906'842'624ull);
-static_assert(PiB(42) == 47'287'796'087'390'208ull);
-
-static_assert(EiB(1) == 1'152'921'504'606'846'976ull);
-static_assert(EiB(4) == 4'611'686'018'427'387'904ull);
-
-// User-defined literal tests
-static_assert(1_B == 1ull);
-static_assert(42_B == 42ull);
-
-static_assert(1_KiB == 1'024ull);
-static_assert(42_KiB == 43'008ull);
-
-static_assert(1_MiB == 1'048'576ull);
-static_assert(42_MiB == 44'040'192ull);
-
-static_assert(1_GiB == 1'073'741'824ull);
-static_assert(42_GiB == 45'097'156'608ull);
-
-static_assert(1_TiB == 1'099'511'627'776ull);
-static_assert(42_TiB == 46'179'488'366'592ull);
-
-static_assert(1_PiB == 1'125'899'906'842'624ull);
-static_assert(42_PiB == 47'287'796'087'390'208ull);
-
-static_assert(1_EiB == 1'152'921'504'606'846'976ull);
-static_assert(4_EiB == 4'611'686'018'427'387'904ull);
-
-}  // namespace
-}  // namespace pw::bytes
diff --git a/pw_checksum/BUILD b/pw_checksum/BUILD
new file mode 100644
index 0000000..5ac4a4c
--- /dev/null
+++ b/pw_checksum/BUILD
@@ -0,0 +1,63 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_checksum",
+    srcs = [
+        "crc16_ccitt.cc",
+        "crc32.cc",
+    ],
+    hdrs = [
+        "public/pw_checksum/crc16_ccitt.h",
+        "public/pw_checksum/crc32.h",
+    ],
+    includes = ["public"],
+    deps = ["//pw_span"],
+)
+
+pw_cc_test(
+    name = "crc16_ccitt_test",
+    srcs = [
+        "crc16_ccitt_test.cc",
+        "crc16_ccitt_test_c.c",
+    ],
+    deps = [
+        ":pw_checksum",
+        "//pw_bytes",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "crc32_test",
+    srcs = [
+        "crc32_test.cc",
+        "crc32_test_c.c",
+    ],
+    deps = [
+        ":pw_checksum",
+        "//pw_bytes",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_checksum/BUILD.bazel b/pw_checksum/BUILD.bazel
deleted file mode 100644
index 6cb4fe5..0000000
--- a/pw_checksum/BUILD.bazel
+++ /dev/null
@@ -1,66 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_checksum",
-    srcs = [
-        "crc16_ccitt.cc",
-        "crc32.cc",
-    ],
-    hdrs = [
-        "public/pw_checksum/crc16_ccitt.h",
-        "public/pw_checksum/crc32.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_bytes",
-        "//pw_span",
-    ],
-)
-
-pw_cc_test(
-    name = "crc16_ccitt_test",
-    srcs = [
-        "crc16_ccitt_test.cc",
-        "crc16_ccitt_test_c.c",
-    ],
-    deps = [
-        ":pw_checksum",
-        "//pw_bytes",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "crc32_test",
-    srcs = [
-        "crc32_test.cc",
-        "crc32_test_c.c",
-    ],
-    deps = [
-        ":pw_checksum",
-        "//pw_bytes",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_checksum/CMakeLists.txt b/pw_checksum/CMakeLists.txt
index 6a79e47..94fe371 100644
--- a/pw_checksum/CMakeLists.txt
+++ b/pw_checksum/CMakeLists.txt
@@ -14,44 +14,9 @@
 
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
 
-pw_add_module_library(pw_checksum
-  HEADERS
-    public/pw_checksum/crc16_ccitt.h
-    public/pw_checksum/crc32.h
-  PUBLIC_INCLUDES
-    public
+pw_auto_add_simple_module(pw_checksum
   PUBLIC_DEPS
-    pw_polyfill.cstddef
-    pw_polyfill.span
+    pw_span
+  PRIVATE_DEPS
     pw_bytes
-  SOURCES
-    crc16_ccitt.cc
-    crc32.cc
-)
-if(Zephyr_FOUND AND CONFIG_PIGWEED_CHECKSUM)
-  zephyr_link_libraries(pw_checksum)
-endif()
-
-pw_add_test(pw_checksum.crc16_ccitt_test
-  SOURCES
-    crc16_ccitt_test.cc
-    crc16_ccitt_test_c.c
-  DEPS
-    pw_checksum
-    pw_random
-  GROUPS
-    modules
-    pw_checksum
-)
-
-pw_add_test(pw_checksum.crc32_test
-  SOURCES
-    crc32_test.cc
-    crc32_test_c.c
-  DEPS
-    pw_checksum
-    pw_random
-  GROUPS
-    modules
-    pw_checksum
 )
diff --git a/pw_checksum/Kconfig b/pw_checksum/Kconfig
deleted file mode 100644
index 91a44d4..0000000
--- a/pw_checksum/Kconfig
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-config PIGWEED_CHECKSUM
-    bool "Enable Pigweed checksum library (pw_checksum)"
-    select PIGWEED_SPAN
-    select PIGWEED_BYTES
diff --git a/pw_checksum/OWNERS b/pw_checksum/OWNERS
deleted file mode 100644
index d96cbc6..0000000
--- a/pw_checksum/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-hepler@google.com
diff --git a/pw_checksum/docs.rst b/pw_checksum/docs.rst
index 456e080..3006ab0 100644
--- a/pw_checksum/docs.rst
+++ b/pw_checksum/docs.rst
@@ -62,8 +62,3 @@
 Dependencies
 ============
 * ``pw_span``
-
-Zephyr
-======
-To enable ``pw_checksum`` for Zephyr add ``CONFIG_PIGWEED_CHECKSUM=y`` to the
-project's configuration.
diff --git a/pw_chrono/BUILD b/pw_chrono/BUILD
new file mode 100644
index 0000000..ea76d5b
--- /dev/null
+++ b/pw_chrono/BUILD
@@ -0,0 +1,101 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+# TODO(pwbug/101): Need to add support for facades/backends to Bazel.
+PW_CHRONO_SYSTEM_CLOCK_BACKEND = "//pw_chrono_stl:system_clock"
+
+pw_cc_library(
+    name = "epoch",
+    hdrs = [
+        "public/pw_chrono/epoch.h",
+    ],
+    includes = ["public"],
+)
+
+pw_cc_library(
+    name = "system_clock_facade",
+    hdrs = [
+        "public/pw_chrono/internal/system_clock_macros.h",
+        "public/pw_chrono/system_clock.h",
+    ],
+    includes = ["public"],
+    srcs = [
+        "system_clock.cc"
+    ],
+    deps = [
+        ":epoch",
+        PW_CHRONO_SYSTEM_CLOCK_BACKEND + "_headers",
+        "//pw_preprocessor",
+    ],
+)
+
+pw_cc_library(
+    name = "system_clock",
+    deps = [
+        ":system_clock_facade",
+        PW_CHRONO_SYSTEM_CLOCK_BACKEND + "_headers",
+    ],
+)
+
+pw_cc_library(
+    name = "system_clock_backend",
+    deps = [
+       PW_CHRONO_SYSTEM_CLOCK_BACKEND,
+    ],
+)
+
+pw_cc_library(
+    name = "simulated_system_clock",
+    hdrs = [
+        "public/pw_chrono/simulated_system_clock.h",
+    ],
+    deps = [
+        ":system_clock",
+        "//pw_sync:interrupt_spin_lock",
+    ],
+)
+
+pw_cc_test(
+    name = "simulated_system_clock_test",
+    srcs = [
+        "simulated_system_clock_test.cc",
+    ],
+    deps = [
+        ":simulated_system_clock",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "system_clock_facade_test",
+    srcs = [
+        "system_clock_facade_test.cc",
+        "system_clock_facade_test_c.c",
+    ],
+    deps = [
+        ":system_clock",
+        "//pw_preprocessor",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_chrono/BUILD.bazel b/pw_chrono/BUILD.bazel
deleted file mode 100644
index 0704cca..0000000
--- a/pw_chrono/BUILD.bazel
+++ /dev/null
@@ -1,145 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_facade",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "epoch",
-    hdrs = [
-        "public/pw_chrono/epoch.h",
-    ],
-    includes = ["public"],
-)
-
-pw_cc_facade(
-    name = "system_clock_facade",
-    hdrs = [
-        "public/pw_chrono/internal/system_clock_macros.h",
-        "public/pw_chrono/system_clock.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":epoch",
-        "//pw_preprocessor",
-    ],
-)
-
-pw_cc_library(
-    name = "system_clock",
-    srcs = [
-        "system_clock.cc",
-    ],
-    deps = [
-        ":system_clock_facade",
-        "@pigweed_config//:pw_chrono_system_clock_backend",
-    ],
-)
-
-pw_cc_library(
-    name = "system_clock_backend_multiplexer",
-    visibility = ["@pigweed_config//:__pkg__"],
-    deps = select({
-        "//pw_build/constraints/rtos:freertos": ["//pw_chrono_freertos:system_clock"],
-        "//pw_build/constraints/rtos:embos": ["//pw_chrono_embos:system_clock"],
-        "//pw_build/constraints/rtos:threadx": ["//pw_chrono_threadx:system_clock"],
-        "//conditions:default": ["//pw_chrono_stl:system_clock"],
-    }),
-)
-
-pw_cc_library(
-    name = "system_timer_facade",
-    hdrs = [
-        "public/pw_chrono/system_timer.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":system_clock",
-        "//pw_function",
-    ],
-)
-
-pw_cc_library(
-    name = "system_timer",
-    deps = [
-        ":system_timer_facade",
-        "@pigweed_config//:pw_chrono_system_timer_backend",
-    ],
-)
-
-pw_cc_library(
-    name = "system_timer_backend_multiplexer",
-    visibility = ["@pigweed_config//:__pkg__"],
-    deps = select({
-        "//pw_build/constraints/rtos:freertos": ["//pw_chrono_freertos:system_timer"],
-        "//pw_build/constraints/rtos:embos": ["//pw_chrono_embos:system_timer"],
-        "//conditions:default": ["//pw_chrono_stl:system_timer"],
-    }),
-)
-
-pw_cc_library(
-    name = "simulated_system_clock",
-    hdrs = [
-        "public/pw_chrono/simulated_system_clock.h",
-    ],
-    deps = [
-        ":system_clock",
-        "//pw_sync:interrupt_spin_lock",
-    ],
-)
-
-pw_cc_test(
-    name = "simulated_system_clock_test",
-    srcs = [
-        "simulated_system_clock_test.cc",
-    ],
-    deps = [
-        ":simulated_system_clock",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "system_clock_facade_test",
-    srcs = [
-        "system_clock_facade_test.cc",
-        "system_clock_facade_test_c.c",
-    ],
-    deps = [
-        ":system_clock",
-        "//pw_preprocessor",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "system_timer_facade_test",
-    srcs = [
-        "system_timer_facade_test.cc",
-    ],
-    deps = [
-        ":system_clock",
-        ":system_timer",
-        "//pw_sync:thread_notification",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_chrono/BUILD.gn b/pw_chrono/BUILD.gn
index 433ab90..33176db 100644
--- a/pw_chrono/BUILD.gn
+++ b/pw_chrono/BUILD.gn
@@ -43,16 +43,6 @@
   sources = [ "system_clock.cc" ]
 }
 
-pw_facade("system_timer") {
-  backend = pw_chrono_SYSTEM_TIMER_BACKEND
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_chrono/system_timer.h" ]
-  public_deps = [
-    ":system_clock",
-    "$dir_pw_function",
-  ]
-}
-
 # Dependency injectable implementation of pw::chrono::SystemClock::Interface.
 pw_source_set("simulated_system_clock") {
   public_configs = [ ":public_include_path" ]
@@ -67,7 +57,6 @@
   tests = [
     ":simulated_system_clock_test",
     ":system_clock_facade_test",
-    ":system_timer_facade_test",
   ]
 }
 
@@ -90,16 +79,6 @@
   ]
 }
 
-pw_test("system_timer_facade_test") {
-  enable_if = pw_chrono_SYSTEM_TIMER_BACKEND != ""
-  sources = [ "system_timer_facade_test.cc" ]
-  deps = [
-    ":system_timer",
-    "$dir_pw_sync:thread_notification",
-    pw_chrono_SYSTEM_TIMER_BACKEND,
-  ]
-}
-
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
diff --git a/pw_chrono/CMakeLists.txt b/pw_chrono/CMakeLists.txt
index 8e31672..d6cc918 100644
--- a/pw_chrono/CMakeLists.txt
+++ b/pw_chrono/CMakeLists.txt
@@ -14,90 +14,9 @@
 
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
 
-pw_add_module_library(pw_chrono.epoch
-  HEADERS
-    public/pw_chrono/epoch.h
-  PUBLIC_INCLUDES
-    public
-)
-
 pw_add_facade(pw_chrono.system_clock
-  HEADERS
-    public/pw_chrono/internal/system_clock_macros.h
-    public/pw_chrono/system_clock.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_chrono.epoch
-    pw_preprocessor
   SOURCES
     system_clock.cc
-)
-
-pw_add_facade(pw_chrono.system_timer
-  HEADERS
-    public/pw_chrono/system_timer.h
-  PUBLIC_INCLUDES
-    public
   PUBLIC_DEPS
-    pw_chrono.system_clock
-    pw_function
+    pw_preprocessor
 )
-
-# Dependency injectable implementation of pw::chrono::SystemClock::Interface.
-pw_add_module_library(pw_chrono.simulated_system_clock
-  HEADERS
-    public/pw_chrono/simulated_system_clock.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_chrono.system_clock
-    pw_sync.interrupt_spin_lock
-)
-
-# TODO(ewout): Renable this once we've resolved the backend variable definition
-# ordering issue, likely by mirroring GN's definition of variables in external
-# files which can be imported where needed.
-# if((NOT "${pw_chrono.system_clock_BACKEND}"
-#     STREQUAL "pw_chrono.system_clock.NO_BACKEND_SET") AND
-#    (NOT "${pw_sync.interrupt_spin_lock_BACKEND}"
-#     STREQUAL "pw_sync.interrupt_spin_lock.NO_BACKEND_SET"))
-#   pw_add_test(pw_chrono.simulated_system_clock_test
-#     SOURCES
-#       simulated_system_clock_test.cc
-#     DEPS
-#       pw_chrono.simulated_system_clock
-#     GROUPS
-#       modules
-#       pw_chrono
-#   )
-# endif()
-
-if(NOT "${pw_chrono.system_clock_BACKEND}"
-   STREQUAL "pw_chrono.system_clock.NO_BACKEND_SET")
-  pw_add_test(pw_chrono.system_clock_facade_test
-    SOURCES
-      system_clock_facade_test.cc
-      system_clock_facade_test_c.c
-    DEPS
-      pw_chrono.system_clock
-      pw_preprocessor
-    GROUPS
-      modules
-      pw_chrono
-  )
-endif()
-
-if(NOT "${pw_chrono.system_timer_BACKEND}"
-   STREQUAL "pw_chrono.system_timer.NO_BACKEND_SET")
-  pw_add_test(pw_chrono.system_timer_facade_test
-    SOURCES
-      system_timer_facade_test.cc
-    DEPS
-      pw_chrono.system_timer
-      pw_sync.thread_notification
-    GROUPS
-      modules
-      pw_chrono
-  )
-endif()
diff --git a/pw_chrono/OWNERS b/pw_chrono/OWNERS
deleted file mode 100644
index e9a6c58..0000000
--- a/pw_chrono/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-ewout@google.com
-hepler@google.com
diff --git a/pw_chrono/backend.gni b/pw_chrono/backend.gni
index 526609f..f5f80e8 100644
--- a/pw_chrono/backend.gni
+++ b/pw_chrono/backend.gni
@@ -15,7 +15,4 @@
 declare_args() {
   # Backend for the pw_chrono module's system_clock.
   pw_chrono_SYSTEM_CLOCK_BACKEND = ""
-
-  # Backend for the pw_chrono module's system_timer.
-  pw_chrono_SYSTEM_TIMER_BACKEND = ""
 }
diff --git a/pw_chrono/docs.rst b/pw_chrono/docs.rst
index 4495b87..e62aba6 100644
--- a/pw_chrono/docs.rst
+++ b/pw_chrono/docs.rst
@@ -1,650 +1,21 @@
 .. _module-pw_chrono:
 
-=========
+---------
 pw_chrono
-=========
+---------
 Pigweed's chrono module provides facilities for applications to deal with time,
 leveraging many pieces of STL's the ``std::chrono`` library but with a focus
 on portability for constrained embedded devices and maintaining correctness.
 
-.. note::
-  This module is still under construction, the API is not yet stable.
-
--------------------------------
-``duration`` and ``time_point``
--------------------------------
-Pigweed's time primitives rely on C++'s
-`<chrono> <https://en.cppreference.com/w/cpp/header/chrono>`_ library to enable
-users to express intents with strongly typed real time units through
-`std::chrono::duration <https://en.cppreference.com/w/cpp/chrono/duration>`_
-and
-`std::chrono::time_point <https://en.cppreference.com/w/cpp/chrono/time_point>`_
-.
-
-What are they?
-==============
-At a high level, durations and time_points at run time are tick counts which
-are wrapped in templated metadata which is only used at compile time.
-
-The STL's
-`std::chrono::duration <https://en.cppreference.com/w/cpp/chrono/duration>`_
-class template represents a time interval. It consists of a count of ticks of
-type ``rep`` and a tick ``period``, where the tick period is a ``std::ratio``
-representing the time in seconds from one tick to the next.
-
-The only data stored in a duration is a tick count of type ``rep``. The
-``period`` is included as part of the duration's type, and is only used when
-converting between different durations.
-
-Similarly, the STL's
-`std::chrono::time_point <https://en.cppreference.com/w/cpp/chrono/time_point>`_
-class template represents a point in time (i.e. timestamp). It consists of a
-value of type ``duration`` which represents the time interval from the start of
-the ``clock``'s epoch.
-
-The ``duration`` and ``time_point`` class templates can be represented with the
-following simplified model, ignoring most of their member functions:
-
-.. code-block:: cpp
-
-  namespace std::chrono {
-
-  template<class Rep, class Period = std::ratio<1, 1>>
-  class duration {
-   public:
-    using rep = Rep;
-    using period = Period;
-
-    constexpr rep count() const { return tick_count_; }
-
-    static constexpr duration zero() noexcept {
-      return duration(0);
-    }
-
-    // Other member functions...
-
-   private:
-    rep tick_count_;
-  };
-
-  template<class Clock, class Duration = typename Clock::duration>
-  class time_point {
-   public:
-    using duration = Duration;
-    using rep = Duration::rep;
-    using period = Duration::period;
-    using clock = Clock;
-
-    constexpr duration time_since_epoch() const { return time_since_epoch_; }
-
-    // Other member functions...
-
-   private:
-    duration time_since_epoch_;
-  };
-
-  }  // namespace std::chrono
-
-What ``rep`` type should be used?
-=================================
-The duration's ``rep``, or tick count type, can be a floating point or a signed
-integer. For most applications, this is a signed integer just as how one may
-represent the number of ticks for an RTOS API or the number of nanoseconds in
-POSIX.
-
-The ``rep`` should be able to represent the durations of time necessary for the
-application. Pigweed recommends that the duration's ``rep`` used for a clock use
-``int64_t`` in order to trivially avoid integer underflow and overflow risks by
-covering a range of at least ±292 years. This matches the STL's requirements
-for the duration helper types which are relevant for a clock's tick period:
-
-* ``std::chrono::nanoseconds  duration</*signed integer type of at least 64 bits*/, std::nano>``
-* ``std::chrono::microseconds duration</*signed integer type of at least 55 bits*/, std::micro>``
-* ``std::chrono::milliseconds duration</*signed integer type of at least 45 bits*/, std::milli>``
-* ``std::chrono::seconds  duration</*signed integer type of at least 35 bits*/>``
-
-With this guidance one can avoid common pitfalls like ``uint32_t`` millisecond
-tick rollover bugs when using RTOSes every 49.7 days.
-
 .. warning::
-  We recommend avoiding the ``duration<>::min()`` and ``duration<>::max()``
-  helper member functions where possible as they exceed the ±292 years duration
-  limit assumption. There's an immediate risk of integer underflow or overflow
-  for any arithmetic operations. Consider using ``std::optional`` instead of
-  priming a variable with a value at the limit.
-
-Helper duration types and literals
-==================================
-The STL's ``<chrono>`` library includes a set of helper types based on actual
-time units, including the following (and more):
-
-* ``std::chrono::nanoseconds``
-* ``std::chrono::microseconds``
-* ``std::chrono::milliseconds``
-* ``std::chrono::seconds``
-* ``std::chrono::minutes``
-* ``std::chrono::hours``
-
-As an example you can use these as follows:
-
-.. code-block:: cpp
-
-  #include <chrono>
-
-  void Foo() {
-    Bar(std::chrono::milliseconds(42));
-  }
-
-In addition, the inline namespace ``std::literals::chrono_literals`` includes:
-
-* ``operator""ns`` for ``std::chrono::nanoseconds``
-* ``operator""us`` for ``std::chrono::microseconds``
-* ``operator""ms`` for ``std::chrono::milliseconds``
-* ``operator""s`` for ``std::chrono::seconds``
-* ``operator""min`` for ``std::chrono::minutes``
-* ``operator""h`` for ``std::chrono::hours``
-
-As an example you can use these as follows:
-
-.. code-block:: cpp
-
-  using std::literals::chrono_literals::ms;
-  // Or if you want them all: using namespace std::chrono_literals;
-
-  void Foo() {
-    Bar(42ms);
-  }
-
-For these helper duration types to be compatible with API's that take a
-`SystemClock::duration` either an :ref:`implicit<Implicit lossless conversions>`
-or :ref:`explicit lossy<Explicit lossy conversions>` conversion must be done.
-
-Converting between time units and clock durations
-=================================================
-So why go through all of this trouble instead of just using ticks or instead
-just using one time unit such as nanoseconds? For example, imagine that you have
-a 1kHz RTOS tick period and you would like to express a timeout duration:
-
-.. code-block:: cpp
-
-  // Instead of using ticks which are not portable between RTOS configurations,
-  // as the tick period may be different:
-  constexpr uint32_t kFooNotificationTimeoutTicks = 42;
-  bool TryGetNotificationFor(uint32_t ticks);
-
-  // And instead of using a time unit which is prone to accidental conversion
-  // errors as all variables must maintain the time units:
-  constexpr uint32_t kFooNotificationTimeoutMs = 42;
-  bool TryGetNotificationFor(uint32_t milliseconds);
-
-  // We can instead use a defined clock and its duration for the kernel and rely
-  // on implicit lossless conversions:
-  #include <chrono>
-  #include "pw_chrono/system_clock.h"
-  constexpr SystemClock::duration kFooNotificationTimeout =
-      std::chrono::milliseconds(42);
-  bool TryGetNotificationFor(SystemClock::duration timeout);
-
-  void MaybeProcessNotification() {
-    if (TryGetNotificationFor(kFooNotificationTimeout)) {
-      ProcessNotification();
-    }
-  }
-
-.. _Implicit lossless conversions:
-
-Implicit lossless conversions
------------------------------
-Wait, but how does this work? Is there a hidden cost? The ``duration`` type
-comes with built in implicit lossless conversion support which is evaluated at
-compile time where possible.
-
-If you rely on implicit conversions then the worst case cost is multiplication,
-there is no risk of a division operation.
-
-If the implicit conversion cannot be guaranteed at compile time to be lossless
-for all possible tick count values, then it will fail to compile.
-
-As an example you can always convert from ``std::chrono::seconds`` to
-``std::chrono::milliseconds`` in a lossless manner. However, you cannot
-guarantee for all tick count values that ``std::chrono::milliseconds`` can be
-losslessly converted to ``std::chrono::seconds``, even though it may work for
-some values like ``0``, ``1000``, etc.
-
-.. code-block:: cpp
-
-  #include <chrono>
-
-  constexpr std::chrono::milliseconds this_compiles =
-      std::chrono::seconds(42);
-
-  // This cannot compile, because for some duration values it is lossy even
-  // though this particular value can be in theory converted to whole seconds.
-  // constexpr std::chrono::seconds this_does_not_compile =
-  //    std::chrono::milliseconds(1000);
-
-.. _Explicit lossy conversions:
-
-Explicit lossy conversions
---------------------------
-Although we recommend sticking to implicit lossless conversions, what if for
-some reason a lossy conversion is required? For example what if we're using a
-128Hz RTOS tick clock?
-
-The 128Hz ``period`` can be perfectly represented with a ``std::ratio<1,128>``.
-However you will not be able to implicitly convert any real time unit durations
-to this duration type. Instead explicit lossy conversions must be used. Pigweed
-recommends explicitly using:
-
-* `std::chrono::floor <https://en.cppreference.com/w/cpp/chrono/duration/floor>`_
-  to round down.
-* `std::chrono::round <https://en.cppreference.com/w/cpp/chrono/duration/round>`_
-  to round to the nearest, rounding to even in halfway cases.
-* `std::chrono::ceil <https://en.cppreference.com/w/cpp/chrono/duration/ceil>`_
-  to round up.
-* `pw::chrono::SystemClock::for_at_least` to round up using the `SystemClock::period`,
-  as a more explicit form of std::chrono::ceil.
-
-.. Note::
-  Pigweed does not recommend using ``std::chrono::duration_cast<>`` which
-  truncates dowards zero like ``static_cast``. This is typically not the desired
-  rounding behavior when dealing with time units. Instead, where possible we
-  recommend the more explicit, self-documenting ``std::chrono::floor``,
-  ``std::chrono::round``, and ``std::chrono::ceil``.
-
-Now knowing this, the previous example could be portably and correctly handled
-as follows:
-
-.. code-block:: cpp
-
-  #include <chrono>
-
-  #include "pw_chrono/system_clock.h"
-
-  // We want to round up to ensure we block for at least the specified duration,
-  // instead of rounding down. Imagine for example the extreme case where you
-  // may round down to zero or one, you would definitely want to at least block.
-  constexpr SystemClock::duration kFooNotificationTimeout =
-      std::chrono::ceil(std::chrono::milliseconds(42));
-  bool TryGetNotificationFor(SystemClock::duration timeout);
-
-  void MaybeProcessNotification() {
-    if (TryGetNotificationFor(kFooNotificationTimeout)) {
-      ProcessNotification();
-    }
-  }
-
-This code is lossless if the clock period is 1kHz and it's correct using a
-division which rounds up when the clock period is 128Hz.
-
-.. Note::
-  When using ``pw::chrono::SystemClock::duration`` for timeouts we recommend
-  using its ``SystemClock::for_at_least()`` to round up timeouts in a more
-  explicit, self documenting manner which uses ``std::chrono::ceil`` internally.
-
-Use of ``count()`` and ``time_since_epoch()``
-=============================================
-It's easy to escape the typesafe chrono types through the use of
-``duration<>::count()`` and ``time_point<>::time_since_epoch()``, however this
-increases the risk of accidentally introduce conversion and arithmetic errors.
-
-For this reason, we recommend avoiding these two escape hatches until it's
-absolutely necessary due to I/O such as RPCs or writing to non-volatile storage.
-
-Discrete Timeouts
-=================
-We briefly want to mention a common pitfall when working with discrete
-representations of time durations for timeouts (ticks and real time units)
-on systems with a continously running clock which is backed by discrete time
-intervals (i.e. whole integer constant tick periods).
-
-Imagine an RTOS system where we have a constant tick interval. If we attempt to
-sleep for 1 tick, how long will the kernel actually let us sleep?
-
-In most kernels you will end up sleeping somewhere between 0 and 1 tick periods
-inclusively, i.e. ``[0, 1]``, if we ignore scheduling latency and preemption.
-**This means it can randomly be non-blocking vs blocking!**
-
-This is because internally kernels use a decrementing timeout counter or a
-deadline without taking the current current progression through the existing
-tick period into account.
-
-For this reason all of Pigweed's time bound APIs will internally add an extra
-tick to timeout intents when needed to guarantee that we will block for at least
-the specified timeout.
-
-This same risk exists if a continuously running hardware timer is used for a
-software timer service.
-
-.. Note::
-  When calculating deadlines based on a timeout when using
-  ``pw::chrono::SystemClock::timeout``, we recommend using its
-  ``SystemClock::TimePointAfterAtLeast()`` which adds an extra tick for you
-  internally.
-
-------
-Clocks
-------
-We do not recomend using the clocks provided by ``<chrono>`` including but not
-limited to the ``std::chrono::system_clock``, ``std::chrono::steady_clock``, and
-``std::chrono::high_resolution_clock``. These clocks typically do not work on
-embedded systems, as they are not backed by any actual clocks although they
-often do compile. In addition, their APIs miss guarantees and parameters which
-make them difficult and risky to use on embedded systems.
-
-In addition, the STL time bound APIs heavily rely on templating to permit
-different clocks and durations to be used. We believe this level of template
-metaprogramming and the indirection that comes with that can be confusing. On
-top of this, accidental use of the wrong clock and/or conversions between them
-is a frequent source of bugs. For example using a real time clock which is not
-monotonic for a timeout or deadline can wreak havoc when the clock is adjusted.
-
-For this reason Pigweed's timeout and deadline APIs will not permit arbitrary
-clock and duration selection. Outside of small templated helpers, all APIs will
-require a specific clock's duration and/or time-point. For almost all of Pigweed
-this means that the ``pw::chrono::SystemClock`` is used which is usually backed
-by the kernel's clock.
-
-PigweedClock Requirements
-=========================
-``pw_chrono`` extends the C++ named
-`Clock <https://en.cppreference.com/w/cpp/named_req/Clock>`_ and
-`TrivialClock <https://en.cppreference.com/w/cpp/named_req/TrivialClock>`_
-requirements with the ``PigweedClock Requirements`` to make clocks more friendly
-for embedded systems.
-
-This permits the clock compatibility to be verified through ``static_assert`` at
-compile time which the STL's requirements do not address. For example whether
-the clock continues to tick while interrupts are masked or whether the clock is
-monotonic even if the clock period may not be steady due to the use of low power
-sleep modes.
-
-For a type ``PWC`` to meet the ``PigweedClock Requirements``:
-
-* The type PWC must meet C++14's
-  `Clock <https://en.cppreference.com/w/cpp/named_req/Clock>`_ and
-  `TrivialClock <https://en.cppreference.com/w/cpp/named_req/TrivialClock>`_
-  requirements.
-* The ``PWC::rep`` must be ``int64_t`` to ensure that there cannot be any
-  overflow risk regardless of the ``PWC::period`` configuration.
-  This is done because we do not expect any clocks with periods coarser than
-  seconds which already require 35 bits.
-* ``const bool PWC::is_monotonic`` must return true if and only if the clock
-  can never move backwards.
-  This effectively allows one to describe an unsteady but monotonic clock by
-  combining the C++14's Clock requirement's ``const bool PWC::is_steady``.
-* ``const bool PWC::is_free_running`` must return true if and only if the clock
-  continues to move forward, without risk of overflow, regardless of whether
-  global interrupts are disabled or whether one is in a critical section or even
-  non maskable interrupt.
-* ``const bool PWC::is_always_enabled`` must return true if the clock is always
-  enabled and available. If false, the clock must:
-
-  + Ensure the ``const bool is_{steady,monotonic,free_running}`` attributes
-    are all valid while the clock is not enabled to ensure they properly meet
-    the previously stated requirements.
-  + Meet C++14's
-    `BasicLockable <https://en.cppreference.com/w/cpp/named_req/BasicLockable>`_
-    requirements (i.e. provide ``void lock()`` & ``void unlock()``) in order
-    to provide ``std::scoped_lock`` support to enable a user to enable the
-    clock.
-  + Provide ``const bool is_{steady,monotonic,free_running}_while_enabled``
-    attributes which meet the attributes only while the clock is enabled.
-* ``const bool PWC::is_stopped_in_halting_debug_mode`` must return true if and
-  only if the clock halts, without further modification, during halting debug
-  mode , for example during a breakpoint while a hardware debugger is used.
-* ``const Epoch PWC::epoch`` must return the epoch type of the clock, the
-  ``Epoch`` enumeration is defined in ``pw_chrono/epoch.h``.
-* The function ``time_point PWC::now() noexcept`` must always be thread and
-  interrupt safe, but not necessarily non-masking and bare-metal interrupt safe.
-* ``const bool PWC::is_non_masking_interrupt_safe`` must return true if and only
-  if the clock is safe to use from non-masking and bare-metal interrupts.
-
-The PigweedClock requirement will not require ``now()`` to be a static function,
-however the upstream façades will follow this approach.
+  This module is under construction, not ready for use, and the documentation
+  is incomplete.
 
 SystemClock facade
-==================
+------------------
 The ``pw::chrono::SystemClock`` is meant to serve as the clock used for time
 bound operations such as thread sleeping, waiting on mutexes/semaphores, etc.
 The ``SystemClock`` always uses a signed 64 bit as the underlying type for time
 points and durations. This means users do not have to worry about clock overflow
 risk as long as rational durations and time points as used, i.e. within a range
 of ±292 years.
-
-The ``SystemClock`` represents an unsteady, monotonic clock.
-
-The epoch of this clock is unspecified and may not be related to wall time
-(for example, it can be time since boot). The time between ticks of this
-clock may vary due to sleep modes and potential interrupt handling.
-``SystemClock`` meets the requirements of C++'s ``TrivialClock`` and Pigweed's
-``PigweedClock``.
-
-This clock is used for expressing timeout and deadline semantics with the
-scheduler in Pigweed including pw_sync, pw_thread, etc.
-
-C++
----
-
-.. cpp:class:: pw::chrono::SystemClock
-
-  .. cpp:type:: rep = int64_t;
-
-  .. cpp:type:: period = std::ratio<PW_CHRONO_SYSTEM_CLOCK_PERIOD_SECONDS_NUMBERATOR, PW_CHRONO_SYSTEM_CLOCK_PERIOD_SECONDS_DENOMINATOR>;
-
-    The period is specified by the backend.
-
-  .. cpp:type:: duration = std::chrono::duration<rep, period>;
-
-  .. cpp:type:: time_point = std::chrono::time_point<SystemClock>;
-
-  .. cpp:member:: static constexpr Epoch epoch = backend::kSystemClockEpoch;
-
-    The epoch must be provided by the backend.
-
-  .. cpp:member:: static constexpr bool is_monotonic = true;
-
-    The time points of this clock cannot decrease.
-
-  .. cpp:member:: static constexpr bool is_steady = false;
-
-    However, the time between ticks of this clock may slightly vary due to sleep
-    modes. The duration during sleep may be ignored or backfilled with another
-    clock.
-
-  .. cpp:member:: static constexpr bool is_free_running = backend::kSystemClockFreeRunning;
-
-    The now() function may not move forward while in a critical section or
-    interrupt. This must be provided by the backend.
-
-  .. cpp:member:: static constexpr bool is_stopped_in_halting_debug_mode = true;
-
-    The clock must stop while in halting debug mode.
-
-  .. cpp:member:: static constexpr bool is_always_enabled = true;
-
-    The now() function can be invoked at any time.
-
-  .. cpp:member:: static constexpr bool is_nmi_safe = backend::kSystemClockNmiSafe;
-
-    The now() function may work in non-masking interrupts, depending on the
-    backend. This must be provided by the backend.
-
-  .. cpp:function:: static time_point now() noexcept;
-
-    This is thread and IRQ safe.
-
-  .. cpp:function:: template <class Rep, class Period> static constexpr duration for_at_least(std::chrono::duration<Rep, Period> d);
-
-    This is purely a helper, identical to directly using std::chrono::ceil, to
-    convert a duration type which cannot be implicitly converted where the
-    result is rounded up.
-
-  .. cpp:function:: static time_point TimePointAfterAtLeast(duration after_at_least);
-
-    Computes the nearest time_point after the specified duration has elapsed.
-
-    This is useful for translating delay or timeout durations into deadlines.
-
-    The time_point is computed based on now() plus the specified duration
-    where a singular clock tick is added to handle partial ticks. This ensures
-    that a duration of at least 1 tick does not result in [0,1] ticks and
-    instead in [1,2] ticks.
-
-
-Example in C++
---------------
-
-.. code-block:: cpp
-
-  #include <chrono>
-
-  #include "pw_chrono/system_clock.h"
-
-  void Foo() {
-    const SystemClock::time_point before = SystemClock::now();
-    TakesALongTime();
-    const SystemClock::duration time_taken = SystemClock::now() - before;
-    bool took_way_too_long = false;
-    if (time_taken > std::chrono::seconds(42)) {
-      took_way_too_long = true;
-    }
-  }
-
----------------
-Software Timers
----------------
-
-SystemTimer facade
-==================
-The SystemTimer facade enables deferring execution of a callback until a later
-time. For example, enabling low power mode after a period of inactivity.
-
-The base SystemTimer only supports a one-shot style timer with a callback.
-A periodic timer can be implemented by rescheduling the timer in the callback
-through ``InvokeAt(kDesiredPeriod + expired_deadline)``.
-
-When implementing a periodic layer on top, the user should be mindful of
-handling missed periodic callbacks. They could opt to invoke the callback
-multiple times with the expected ``expired_deadline`` values or instead saturate
-and invoke the callback only once with the latest ``expired_deadline``.
-
-The entire API is thread safe, however it is NOT always IRQ safe.
-
-The ExpiryCallback is either invoked from a high priority thread or an
-interrupt. Ergo ExpiryCallbacks should be treated as if they are executed by an
-interrupt, meaning:
-
- * Processing inside of the callback should be kept to a minimum.
-
- * Callbacks should never attempt to block.
-
- * APIs which are not interrupt safe such as pw::sync::Mutex should not be used!
-
-C++
----
-.. cpp:class:: pw::chrono::SystemTimer
-
-  .. cpp:function:: SystemTimer(ExpiryCallback callback)
-
-    Constructs the SystemTimer based on the user provided
-    ``pw::Function<void(SystemClock::time_point expired_deadline)>``. Note that
-    The ExpiryCallback is either invoked from a high priority thread or an
-    interrupt.
-
-    .. note::
-      For a given timer instance, its ExpiryCallback will not preempt itself.
-      This makes it appear like there is a single executor of a timer instance's
-      ExpiryCallback.
-
-  .. cpp:function:: ~SystemTimer()
-
-    Cancels the timer and blocks if necssary if the callback is already being
-    processed.
-
-    **Postcondition:** The expiry callback is not in progress and will not be
-    called in the future.
-
-  .. cpp:function:: void InvokeAfter(chrono::SystemClock::duration delay)
-
-    Invokes the expiry callback as soon as possible after at least the
-    specified duration.
-
-    Scheduling a callback cancels the existing callback (if pending).
-    If the callback is already being executed while you reschedule it, it will
-    finish callback execution to completion. You are responsible for any
-    critical section locks which may be needed for timer coordination.
-
-    This is thread safe, it may not be IRQ safe.
-
-  .. cpp:function:: void InvokeAt(chrono::SystemClock::time_point timestamp)
-
-    Invokes the expiry callback as soon as possible starting at the specified
-    time_point.
-
-    Scheduling a callback cancels the existing callback (if pending).
-    If the callback is already being executed while you reschedule it, it will
-    finish callback execution to completion. You are responsible for any
-    critical section locks which may be needed for timer coordination.
-
-    This is thread safe, it may not be IRQ safe.
-
-  .. cpp:function:: void Cancel()
-
-    Cancels the software timer expiry callback if pending.
-
-    Canceling a timer which isn't scheduled does nothing.
-
-    If the callback is already being executed while you cancel it, it will
-    finish callback execution to completion. You are responsible for any
-    synchronization which is needed for thread safety.
-
-    This is thread safe, it may not be IRQ safe.
-
-  .. list-table::
-
-    * - *Safe to use in context*
-      - *Thread*
-      - *Interrupt*
-      - *NMI*
-    * - ``SystemTimer::SystemTimer``
-      - ✔
-      -
-      -
-    * - ``SystemTimer::~SystemTimer``
-      - ✔
-      -
-      -
-    * - ``void SystemTimer::InvokeAfter``
-      - ✔
-      -
-      -
-    * - ``void SystemTimer::InvokeAt``
-      - ✔
-      -
-      -
-    * - ``void SystemTimer::Cancel``
-      - ✔
-      -
-      -
-
-Example in C++
---------------
-
-.. code-block:: cpp
-
-  #include "pw_chrono/system_clock.h"
-  #include "pw_chrono/system_timer.h"
-  #include "pw_log/log.h"
-
-  using namespace std::chrono_literals;
-
-  void DoFoo(pw::chrono::SystemClock::time_point expired_deadline) {
-    PW_LOG_INFO("Timer callback invoked!");
-  }
-
-  pw::chrono::SystemTimer foo_timer(DoFoo);
-
-  void DoFooLater() {
-    foo_timer.InvokeAfter(42ms);  // DoFoo will be invoked after 42ms.
-  }
diff --git a/pw_chrono/public/pw_chrono/system_clock.h b/pw_chrono/public/pw_chrono/system_clock.h
index ad6e90e..3c5be0e 100644
--- a/pw_chrono/public/pw_chrono/system_clock.h
+++ b/pw_chrono/public/pw_chrono/system_clock.h
@@ -113,18 +113,6 @@
   static constexpr duration for_at_least(std::chrono::duration<Rep, Period> d) {
     return std::chrono::ceil<duration>(d);
   };
-
-  // Computes the nearest time_point after the specified duration has elapsed.
-  //
-  // This is useful for translating delay or timeout durations into deadlines.
-  //
-  // The time_point is computed based on now() plus the specified duration
-  // where a singular clock tick is added to handle partial ticks. This ensures
-  // that a duration of at least 1 tick does not result in [0,1] ticks and
-  // instead in [1,2] ticks.
-  static time_point TimePointAfterAtLeast(duration after_at_least) {
-    return now() + after_at_least + duration(1);
-  }
 };
 
 // An abstract interface representing a SystemClock.
diff --git a/pw_chrono/public/pw_chrono/system_timer.h b/pw_chrono/public/pw_chrono/system_timer.h
deleted file mode 100644
index f356ede..0000000
--- a/pw_chrono/public/pw_chrono/system_timer.h
+++ /dev/null
@@ -1,111 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_chrono/system_clock.h"
-#include "pw_chrono_backend/system_timer_native.h"
-#include "pw_function/function.h"
-
-namespace pw::chrono {
-
-// The SystemTimer allows an ExpiryCallback be executed at a set time in the
-// future.
-//
-// The base SystemTimer only supports a one-shot style timer with a callback.
-// A periodic timer can be implemented by rescheduling the timer in the callback
-// through InvokeAt(kDesiredPeriod + expired_deadline).
-//
-// When implementing a periodic layer on top, the user should be mindful of
-// handling missed periodic callbacks. They could opt to invoke the callback
-// multiple times with the expected expired_deadline values or instead
-// saturate and invoke the callback only once with the latest expired_deadline.
-//
-// The entire API is thread safe, however it is NOT always IRQ safe.
-class SystemTimer {
- public:
-  using native_handle_type = backend::NativeSystemTimerHandle;
-
-  // The ExpiryCallback is either invoked from a high priority thread or an
-  // interrupt.
-  //
-  // For a given timer instance, its ExpiryCallback will not preempt itself.
-  // This makes it appear like there is a single executor of a timer instance's
-  // ExpiryCallback.
-  //
-  // Ergo ExpiryCallbacks should be treated as if they are executed by an
-  // interrupt, meaning:
-  // - Processing inside of the callback should be kept to a minimum.
-  // - Callbacks should never attempt to block.
-  // - APIs which are not interrupt safe such as pw::sync::Mutex should not be
-  //   used!
-  using ExpiryCallback =
-      Function<void(SystemClock::time_point expired_deadline)>;
-
-  SystemTimer(ExpiryCallback&& callback);
-
-  // Cancels the timer and blocks if necssary if the callback is already being
-  // processed.
-  //
-  // Postcondition: The expiry callback is not in progress and will not be
-  // called in the future.
-  ~SystemTimer();
-
-  SystemTimer(const SystemTimer&) = delete;
-  SystemTimer(SystemTimer&&) = delete;
-  SystemTimer& operator=(const SystemTimer&) = delete;
-  SystemTimer& operator=(SystemTimer&&) = delete;
-
-  // Invokes the expiry callback as soon as possible after at least the
-  // specified duration.
-  //
-  // Scheduling a callback cancels the existing callback (if pending).
-  // If the callback is already being executed while you reschedule it, it will
-  // finish callback execution to completion. You are responsible for any
-  // critical section locks which may be needed for timer coordination.
-  //
-  // This is thread safe, it may not be IRQ safe.
-  void InvokeAfter(SystemClock::duration delay);
-
-  // Invokes the expiry callback as soon as possible starting at the specified
-  // time_point.
-  //
-  // Scheduling a callback cancels the existing callback (if pending).
-  // If the callback is already being executed while you reschedule it, it will
-  // finish callback execution to completion. You are responsible for any
-  // critical section locks which may be needed for timer coordination.
-  //
-  // This is thread safe, it may not be IRQ safe.
-  void InvokeAt(SystemClock::time_point timestamp);
-
-  // Cancels the software timer expiry callback if pending.
-  //
-  // Canceling a timer which isn't scheduled does nothing.
-  //
-  // If the callback is already being executed while you cancel it, it will
-  // finish callback execution to completion. You are responsible for any
-  // synchronization which is needed for thread safety.
-  //
-  // This is thread safe, it may not be IRQ safe.
-  void Cancel();
-
-  native_handle_type native_handle();
-
- private:
-  // This may be a wrapper around a native type with additional members.
-  backend::NativeSystemTimer native_type_;
-};
-
-}  // namespace pw::chrono
-
-#include "pw_chrono_backend/system_timer_inline.h"
diff --git a/pw_chrono/system_timer_facade_test.cc b/pw_chrono/system_timer_facade_test.cc
deleted file mode 100644
index d819e70..0000000
--- a/pw_chrono/system_timer_facade_test.cc
+++ /dev/null
@@ -1,259 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <chrono>
-
-#include "gtest/gtest.h"
-#include "pw_chrono/system_clock.h"
-#include "pw_chrono/system_timer.h"
-#include "pw_sync/thread_notification.h"
-
-using namespace std::chrono_literals;
-
-namespace pw::chrono {
-namespace {
-
-// We can't control the SystemClock's period configuration, so just in case
-// duration cannot be accurately expressed in integer ticks, round the
-// duration up.
-constexpr SystemClock::duration kRoundedArbitraryShortDuration =
-    SystemClock::for_at_least(42ms);
-constexpr SystemClock::duration kRoundedArbitraryLongDuration =
-    SystemClock::for_at_least(1s);
-
-void ShouldNotBeInvoked(SystemClock::time_point) { FAIL(); }
-
-TEST(SystemTimer, CancelInactive) {
-  SystemTimer timer(ShouldNotBeInvoked);
-  timer.Cancel();
-}
-
-TEST(SystemTimer, CancelExplicitly) {
-  SystemTimer timer(ShouldNotBeInvoked);
-  timer.InvokeAfter(kRoundedArbitraryLongDuration);
-  timer.Cancel();
-}
-
-TEST(SystemTimer, CancelThroughDestruction) {
-  SystemTimer timer(ShouldNotBeInvoked);
-  timer.InvokeAfter(kRoundedArbitraryLongDuration);
-}
-
-TEST(SystemTimer, CancelThroughRescheduling) {
-  SystemTimer timer(ShouldNotBeInvoked);
-  timer.InvokeAfter(kRoundedArbitraryLongDuration);
-  // Cancel the first with this rescheduling.
-  timer.InvokeAfter(kRoundedArbitraryLongDuration);
-  timer.Cancel();
-}
-
-// Helper class to let test cases easily instantiate a timer with a handler
-// and its own context.
-class TimerWithHandler {
- public:
-  TimerWithHandler()
-      : timer_([this](SystemClock::time_point expired_deadline) {
-          this->OnExpiryCallback(expired_deadline);
-        }) {}
-  virtual ~TimerWithHandler() = default;
-
-  // To be implemented by the test case.
-  virtual void OnExpiryCallback(SystemClock::time_point expired_deadline) = 0;
-
-  SystemTimer& timer() { return timer_; }
-
- private:
-  SystemTimer timer_;
-};
-
-TEST(SystemTimer, StaticInvokeAt) {
-  class TimerWithContext : public TimerWithHandler {
-   public:
-    void OnExpiryCallback(SystemClock::time_point expired_deadline) override {
-      EXPECT_GE(SystemClock::now(), expired_deadline);
-      EXPECT_EQ(expired_deadline, expected_deadline);
-      callback_ran_notification.release();
-    };
-
-    SystemClock::time_point expected_deadline;
-    sync::ThreadNotification callback_ran_notification;
-  };
-  static TimerWithContext uut;
-
-  uut.expected_deadline = SystemClock::now() + kRoundedArbitraryShortDuration;
-  uut.timer().InvokeAt(uut.expected_deadline);
-  uut.callback_ran_notification.acquire();
-
-  // Ensure you can re-use the timer.
-  uut.expected_deadline = SystemClock::now() + kRoundedArbitraryShortDuration;
-  uut.timer().InvokeAt(uut.expected_deadline);
-  uut.callback_ran_notification.acquire();
-}
-
-TEST(SystemTimer, InvokeAt) {
-  class TimerWithContext : public TimerWithHandler {
-   public:
-    void OnExpiryCallback(SystemClock::time_point expired_deadline) override {
-      EXPECT_GE(SystemClock::now(), expired_deadline);
-      EXPECT_EQ(expired_deadline, expected_deadline);
-      callback_ran_notification.release();
-    };
-
-    SystemClock::time_point expected_deadline;
-    sync::ThreadNotification callback_ran_notification;
-  };
-  TimerWithContext uut;
-
-  uut.expected_deadline = SystemClock::now() + kRoundedArbitraryShortDuration;
-  uut.timer().InvokeAt(uut.expected_deadline);
-  uut.callback_ran_notification.acquire();
-
-  // Ensure you can re-use the timer.
-  uut.expected_deadline = SystemClock::now() + kRoundedArbitraryShortDuration;
-  uut.timer().InvokeAt(uut.expected_deadline);
-  uut.callback_ran_notification.acquire();
-
-  // Ensure scheduling it in the past causes it to execute immediately.
-  uut.expected_deadline = SystemClock::now() - SystemClock::duration(1);
-  uut.timer().InvokeAt(uut.expected_deadline);
-  uut.callback_ran_notification.acquire();
-}
-
-TEST(SystemTimer, InvokeAfter) {
-  class TimerWithContext : public TimerWithHandler {
-   public:
-    void OnExpiryCallback(SystemClock::time_point expired_deadline) override {
-      EXPECT_GE(SystemClock::now(), expired_deadline);
-      EXPECT_GE(expired_deadline, expected_min_deadline);
-      callback_ran_notification.release();
-    };
-
-    SystemClock::time_point expected_min_deadline;
-    sync::ThreadNotification callback_ran_notification;
-  };
-  TimerWithContext uut;
-
-  uut.expected_min_deadline =
-      SystemClock::TimePointAfterAtLeast(kRoundedArbitraryShortDuration);
-  uut.timer().InvokeAfter(kRoundedArbitraryShortDuration);
-  uut.callback_ran_notification.acquire();
-
-  // Ensure you can re-use the timer.
-  uut.expected_min_deadline =
-      SystemClock::TimePointAfterAtLeast(kRoundedArbitraryShortDuration);
-  uut.timer().InvokeAfter(kRoundedArbitraryShortDuration);
-  uut.callback_ran_notification.acquire();
-
-  // Ensure scheduling it immediately works.
-  uut.expected_min_deadline = SystemClock::now();
-  uut.timer().InvokeAfter(SystemClock::duration(0));
-  uut.callback_ran_notification.acquire();
-}
-
-TEST(SystemTimer, CancelFromCallback) {
-  class TimerWithContext : public TimerWithHandler {
-   public:
-    void OnExpiryCallback(SystemClock::time_point) override {
-      timer().Cancel();
-      callback_ran_notification.release();
-    };
-
-    sync::ThreadNotification callback_ran_notification;
-  };
-  TimerWithContext uut;
-
-  uut.timer().InvokeAfter(kRoundedArbitraryShortDuration);
-  uut.callback_ran_notification.acquire();
-}
-
-TEST(SystemTimer, RescheduleAndCancelFromCallback) {
-  class TimerWithContext : public TimerWithHandler {
-   public:
-    void OnExpiryCallback(SystemClock::time_point) override {
-      timer().InvokeAfter(kRoundedArbitraryShortDuration);
-      timer().Cancel();
-      callback_ran_notification.release();
-    };
-
-    sync::ThreadNotification callback_ran_notification;
-  };
-  TimerWithContext uut;
-
-  uut.timer().InvokeAfter(kRoundedArbitraryShortDuration);
-  uut.callback_ran_notification.acquire();
-}
-
-TEST(SystemTimer, RescheduleFromCallback) {
-  class TimerWithContext : public TimerWithHandler {
-   public:
-    void OnExpiryCallback(SystemClock::time_point expired_deadline) override {
-      EXPECT_GE(SystemClock::now(), expired_deadline);
-
-      EXPECT_EQ(expired_deadline, expected_deadline);
-      invocation_count++;
-      ASSERT_LE(invocation_count, kRequiredInvocations);
-      if (invocation_count < kRequiredInvocations) {
-        expected_deadline = expired_deadline + kPeriod;
-        timer().InvokeAt(expected_deadline);
-      } else {
-        callbacks_done_notification.release();
-      }
-    };
-
-    const uint8_t kRequiredInvocations = 5;
-    const SystemClock::duration kPeriod = kRoundedArbitraryShortDuration;
-    uint8_t invocation_count = 0;
-    SystemClock::time_point expected_deadline;
-    sync::ThreadNotification callbacks_done_notification;
-  };
-  TimerWithContext uut;
-
-  uut.expected_deadline = SystemClock::now() + kRoundedArbitraryShortDuration;
-  uut.timer().InvokeAt(uut.expected_deadline);
-  uut.callbacks_done_notification.acquire();
-}
-
-TEST(SystemTimer, DoubleRescheduleFromCallback) {
-  class TimerWithContext : public TimerWithHandler {
-   public:
-    void OnExpiryCallback(SystemClock::time_point expired_deadline) override {
-      EXPECT_GE(SystemClock::now(), expired_deadline);
-
-      EXPECT_EQ(expired_deadline, expected_deadline);
-      invocation_count++;
-      ASSERT_LE(invocation_count, kExpectedInvocations);
-      if (invocation_count == 1) {
-        expected_deadline = expired_deadline + kPeriod;
-        timer().InvokeAt(expected_deadline);
-        timer().InvokeAt(expected_deadline);
-      } else {
-        callbacks_done_notification.release();
-      }
-    };
-
-    const uint8_t kExpectedInvocations = 2;
-    const SystemClock::duration kPeriod = kRoundedArbitraryShortDuration;
-    uint8_t invocation_count = 0;
-    SystemClock::time_point expected_deadline;
-    sync::ThreadNotification callbacks_done_notification;
-  };
-  TimerWithContext uut;
-
-  uut.expected_deadline = SystemClock::now() + kRoundedArbitraryShortDuration;
-  uut.timer().InvokeAt(uut.expected_deadline);
-  uut.callbacks_done_notification.acquire();
-}
-
-}  // namespace
-}  // namespace pw::chrono
diff --git a/pw_chrono_embos/BUILD b/pw_chrono_embos/BUILD
new file mode 100644
index 0000000..a65d083
--- /dev/null
+++ b/pw_chrono_embos/BUILD
@@ -0,0 +1,52 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "system_clock_headers",
+    hdrs = [
+        "public/pw_chrono_embos/config.h",
+        "public/pw_chrono_embos/system_clock_config.h",
+        "public/pw_chrono_embos/system_clock_constants.h",
+        "public_overrides/pw_chrono_backend/system_clock_config.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        "//pw_chrono:epoch",
+    ],
+)
+
+pw_cc_library(
+    name = "system_clock",
+    srcs = [
+        "system_clock.cc",
+    ],
+    deps = [
+        ":system_clock_headers",
+        "//pw_chrono:system_clock_facade",
+        # TODO(pwbug/317): This should depend on embOS but our third parties
+        # currently do not have Bazel support.
+    ],
+)
diff --git a/pw_chrono_embos/BUILD.bazel b/pw_chrono_embos/BUILD.bazel
deleted file mode 100644
index b86d63d..0000000
--- a/pw_chrono_embos/BUILD.bazel
+++ /dev/null
@@ -1,93 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "system_clock_headers",
-    hdrs = [
-        "public/pw_chrono_embos/config.h",
-        "public/pw_chrono_embos/system_clock_config.h",
-        "public/pw_chrono_embos/system_clock_constants.h",
-        "public_overrides/pw_chrono_backend/system_clock_config.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:embos",
-    ],
-    deps = [
-        "//pw_chrono:epoch",
-    ],
-)
-
-pw_cc_library(
-    name = "system_clock",
-    srcs = [
-        "system_clock.cc",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:embos",
-    ],
-    deps = [
-        ":system_clock_headers",
-        "//pw_chrono:system_clock_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "system_timer_headers",
-    hdrs = [
-        "public/pw_chrono_embos/system_timer_inline.h",
-        "public/pw_chrono_embos/system_timer_native.h",
-        "public_overrides/pw_chrono_backend/system_timer_inline.h",
-        "public_overrides/pw_chrono_backend/system_timer_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    deps = [
-        "//pw_chrono:system_clock",
-        "//pw_function",
-        "//pw_chrono:system_timer_facade",
-        # TODO(pwbug/317): This should depend on embOS but our third parties
-        # currently do not have Bazel support.
-    ],
-)
-
-pw_cc_library(
-    name = "system_timer",
-    srcs = [
-        "system_timer.cc",
-    ],
-    deps = [
-        ":system_timer_headers",
-        "//pw_chrono:system_timer_facade",
-        ":system_clock",
-        "//pw_assert",
-        "//pw_interrupt:context",
-        # TODO(pwbug/317): This should depend on embOS but our third parties
-        # currently do not have Bazel support.
-    ],
-)
diff --git a/pw_chrono_embos/BUILD.gn b/pw_chrono_embos/BUILD.gn
index b4ad97c..13a8a0c 100644
--- a/pw_chrono_embos/BUILD.gn
+++ b/pw_chrono_embos/BUILD.gn
@@ -68,33 +68,6 @@
   ]
 }
 
-# This target provides the backend for pw::chrono::SystemTimer.
-pw_source_set("system_timer") {
-  public_configs = [
-    ":public_include_path",
-    ":backend_config",
-  ]
-  public = [
-    "public/pw_chrono_embos/system_timer_inline.h",
-    "public/pw_chrono_embos/system_timer_native.h",
-    "public_overrides/pw_chrono_backend/system_timer_inline.h",
-    "public_overrides/pw_chrono_backend/system_timer_native.h",
-  ]
-  public_deps = [
-    "$dir_pw_chrono:system_clock",
-    "$dir_pw_chrono:system_timer.facade",
-    "$dir_pw_function",
-    "$dir_pw_third_party/embos",
-  ]
-  deps = [
-    "$dir_pw_assert",
-    "$dir_pw_chrono_embos:system_clock",
-    "$dir_pw_interrupt:context",
-  ]
-  allow_circular_includes_from = [ "$dir_pw_chrono:system_timer.facade" ]
-  sources = [ "system_timer.cc" ]
-}
-
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
diff --git a/pw_chrono_embos/OWNERS b/pw_chrono_embos/OWNERS
deleted file mode 100644
index e9a6c58..0000000
--- a/pw_chrono_embos/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-ewout@google.com
-hepler@google.com
diff --git a/pw_chrono_embos/docs.rst b/pw_chrono_embos/docs.rst
index 4e45946..c6be81b 100644
--- a/pw_chrono_embos/docs.rst
+++ b/pw_chrono_embos/docs.rst
@@ -7,7 +7,8 @@
 implemented using embOS v4 for 32bit targets.
 
 .. warning::
-  This module is still under construction, the API is not yet stable.
+  This module is under construction, not ready for use, and the documentation
+  is incomplete.
 
 SystemClock backend
 -------------------
@@ -22,13 +23,6 @@
 native embOS ``OS_GetTime32()`` overflow. Note that this duration may
 vary if ``OS_SUPPORT_TICKLESS`` is used.
 
-SystemTimer backend
--------------------
-The embOS based ``system_timer`` backend implements the
-``pw_chrono:system_timer`` facade by using embOS's software timer API.
-``pw::chrono::SystemTimer`` instances use ``OS_TIMER_EX`` &
-``OS_CreateTimerEx``, dynamic memory allocation is never used.
-
 Build targets
 -------------
 The GN build for ``pw_chrono_embos`` has one target: ``system_clock``.
diff --git a/pw_chrono_embos/public/pw_chrono_embos/system_timer_inline.h b/pw_chrono_embos/public/pw_chrono_embos/system_timer_inline.h
deleted file mode 100644
index 98a40b6..0000000
--- a/pw_chrono_embos/public/pw_chrono_embos/system_timer_inline.h
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "RTOS.h"
-#include "pw_chrono/system_clock.h"
-#include "pw_chrono/system_timer.h"
-
-namespace pw::chrono {
-
-inline void SystemTimer::InvokeAfter(SystemClock::duration delay) {
-  InvokeAt(SystemClock::TimePointAfterAtLeast(delay));
-}
-
-inline void SystemTimer::Cancel() { OS_StopTimerEx(&native_type_.tcb); }
-
-}  // namespace pw::chrono
diff --git a/pw_chrono_embos/public/pw_chrono_embos/system_timer_native.h b/pw_chrono_embos/public/pw_chrono_embos/system_timer_native.h
deleted file mode 100644
index 423bb24..0000000
--- a/pw_chrono_embos/public/pw_chrono_embos/system_timer_native.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "RTOS.h"
-#include "pw_chrono/system_clock.h"
-#include "pw_function/function.h"
-
-namespace pw::chrono::backend {
-
-struct NativeSystemTimer {
-  OS_TIMER_EX tcb;
-  SystemClock::time_point expiry_deadline;
-  Function<void(SystemClock::time_point expired_deadline)> user_callback;
-};
-using NativeSystemTimerHandle = NativeSystemTimer&;
-
-}  // namespace pw::chrono::backend
diff --git a/pw_chrono_embos/public_overrides/pw_chrono_backend/system_timer_inline.h b/pw_chrono_embos/public_overrides/pw_chrono_backend/system_timer_inline.h
deleted file mode 100644
index aeb649f..0000000
--- a/pw_chrono_embos/public_overrides/pw_chrono_backend/system_timer_inline.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// This override header includes the main tokenized logging header and defines
-// the PW_LOG macro as the tokenized logging macro.
-#pragma once
-
-#include "pw_chrono_embos/system_timer_inline.h"
diff --git a/pw_chrono_embos/public_overrides/pw_chrono_backend/system_timer_native.h b/pw_chrono_embos/public_overrides/pw_chrono_backend/system_timer_native.h
deleted file mode 100644
index 13fb7c6..0000000
--- a/pw_chrono_embos/public_overrides/pw_chrono_backend/system_timer_native.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// This override header includes the main tokenized logging header and defines
-// the PW_LOG macro as the tokenized logging macro.
-#pragma once
-
-#include "pw_chrono_embos/system_timer_native.h"
diff --git a/pw_chrono_embos/system_timer.cc b/pw_chrono_embos/system_timer.cc
deleted file mode 100644
index 52ed965..0000000
--- a/pw_chrono_embos/system_timer.cc
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_chrono/system_timer.h"
-
-#include <algorithm>
-#include <mutex>
-
-#include "RTOS.h"
-#include "pw_assert/check.h"
-#include "pw_chrono_embos/system_clock_constants.h"
-#include "pw_interrupt/context.h"
-
-namespace pw::chrono {
-namespace {
-
-// Instead of adding targeted locks to each instance, simply use the global
-// recursive critical section lock. Note it has to be recursive because a user
-// callback may use the Invoke* API which in turn needs to grab the lock.
-class RecursiveCriticalSectionLock {
- public:
-  void lock() {
-    OS_IncDI();            // Mask interrupts.
-    OS_SuspendAllTasks();  // Disable task switching.
-  }
-
-  void unlock() {
-    OS_ResumeAllSuspendedTasks();  // Restore task switching.
-    OS_DecRI();                    // Restore interrupts.
-  }
-};
-RecursiveCriticalSectionLock recursive_global_timer_lock;
-
-void HandleTimerCallback(void* void_native_system_timer) {
-  PW_DCHECK(interrupt::InInterruptContext(),
-            "HandleTimerCallback must be invoked from an interrupt");
-  std::lock_guard lock(recursive_global_timer_lock);
-
-  backend::NativeSystemTimer& native_type =
-      *static_cast<backend::NativeSystemTimer*>(void_native_system_timer);
-  const SystemClock::duration time_until_deadline =
-      native_type.expiry_deadline - SystemClock::now();
-  if (time_until_deadline <= SystemClock::duration::zero()) {
-    // We have met the deadline, execute the user's callback.
-    native_type.user_callback(native_type.expiry_deadline);
-    return;
-  }
-  const SystemClock::duration period =
-      std::min(pw::chrono::embos::kMaxTimeout, time_until_deadline);
-  OS_SetTimerPeriodEx(&native_type.tcb, static_cast<OS_TIME>(period.count()));
-  OS_StartTimerEx(&native_type.tcb);
-}
-
-// embOS requires a timer to have a non-zero period.
-constexpr SystemClock::duration kMinTimerPeriod = SystemClock::duration(1);
-constexpr OS_TIME kInvalidPeriod = 0;
-
-}  // namespace
-
-SystemTimer::SystemTimer(ExpiryCallback&& callback)
-    : native_type_{.tcb{},
-                   .expiry_deadline = SystemClock::time_point(),
-                   .user_callback = std::move(callback)} {
-  OS_CreateTimerEx(
-      &native_type_.tcb, HandleTimerCallback, kInvalidPeriod, &native_type_);
-}
-
-SystemTimer::~SystemTimer() {
-  // Not threadsafe by design.
-  Cancel();
-  OS_DeleteTimerEx(&native_type_.tcb);
-}
-
-void SystemTimer::InvokeAt(SystemClock::time_point timestamp) {
-  std::lock_guard lock(recursive_global_timer_lock);
-
-  // Ensure the timer has been cancelled first.
-  Cancel();
-
-  native_type_.expiry_deadline = timestamp;
-  const SystemClock::duration time_until_deadline =
-      timestamp - SystemClock::now();
-
-  // Schedule the timer as far out as possible. Note that the timeout might be
-  // clamped and it may be rescheduled internally.
-  const SystemClock::duration period = std::clamp(
-      kMinTimerPeriod, time_until_deadline, pw::chrono::embos::kMaxTimeout);
-
-  OS_SetTimerPeriodEx(&native_type_.tcb, static_cast<OS_TIME>(period.count()));
-  OS_RetriggerTimerEx(&native_type_.tcb);
-}
-
-}  // namespace pw::chrono
diff --git a/pw_chrono_freertos/BUILD b/pw_chrono_freertos/BUILD
new file mode 100644
index 0000000..6c65913
--- /dev/null
+++ b/pw_chrono_freertos/BUILD
@@ -0,0 +1,52 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "system_clock_headers",
+    hdrs = [
+        "public/pw_chrono_freertos/config.h",
+        "public/pw_chrono_freertos/system_clock_config.h",
+        "public/pw_chrono_freertos/system_clock_constants.h",
+        "public_overrides/pw_chrono_backend/system_clock_config.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        "//pw_chrono:epoch",
+    ],
+)
+
+pw_cc_library(
+    name = "system_clock",
+    srcs = [
+        "system_clock.cc",
+    ],
+    deps = [
+        ":system_clock_headers",
+        "//pw_chrono:system_clock_facade",
+        # TODO: This should depend on FreeRTOS but our third parties currently
+        # do not have Bazel support.
+    ],
+)
diff --git a/pw_chrono_freertos/BUILD.bazel b/pw_chrono_freertos/BUILD.bazel
deleted file mode 100644
index 1fafeda..0000000
--- a/pw_chrono_freertos/BUILD.bazel
+++ /dev/null
@@ -1,94 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "system_clock_headers",
-    hdrs = [
-        "public/pw_chrono_freertos/config.h",
-        "public/pw_chrono_freertos/system_clock_config.h",
-        "public/pw_chrono_freertos/system_clock_constants.h",
-        "public_overrides/pw_chrono_backend/system_clock_config.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:freertos",
-    ],
-    deps = [
-        "//pw_chrono:epoch",
-    ],
-)
-
-pw_cc_library(
-    name = "system_clock",
-    srcs = [
-        "system_clock.cc",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:freertos",
-    ],
-    deps = [
-        ":system_clock_headers",
-        "//pw_chrono:system_clock_facade",
-        # TODO: This should depend on FreeRTOS but our third parties currently
-        # do not have Bazel support.
-    ],
-)
-
-pw_cc_library(
-    name = "system_timer_headers",
-    hdrs = [
-        "public/pw_chrono_freertos/system_timer_inline.h",
-        "public/pw_chrono_freertos/system_timer_native.h",
-        "public_overrides/pw_chrono_backend/system_timer_inline.h",
-        "public_overrides/pw_chrono_backend/system_timer_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    deps = [
-        "//pw_chrono:system_clock",
-        "//pw_function",
-        "//pw_chrono:system_timer_facade",
-        # TODO(pwbug/317): This should depend on FreeRTOS but our third parties
-        # currently do not have Bazel support.
-    ],
-)
-
-pw_cc_library(
-    name = "system_timer",
-    srcs = [
-        "system_timer.cc",
-    ],
-    deps = [
-        ":system_timer_headers",
-        "//pw_chrono:system_timer_facade",
-        ":system_clock",
-        "//pw_assert",
-        # TODO(pwbug/317): This should depend on FreeRTOS but our third parties
-        # currently do not have Bazel support.
-    ],
-)
diff --git a/pw_chrono_freertos/BUILD.gn b/pw_chrono_freertos/BUILD.gn
index afe8afe..5b4c275 100644
--- a/pw_chrono_freertos/BUILD.gn
+++ b/pw_chrono_freertos/BUILD.gn
@@ -69,31 +69,6 @@
   ]
 }
 
-# This target provides the backend for pw::chrono::SystemTimer.
-pw_source_set("system_timer") {
-  public_configs = [
-    ":public_include_path",
-    ":backend_config",
-  ]
-  public = [
-    "public/pw_chrono_freertos/system_timer_inline.h",
-    "public/pw_chrono_freertos/system_timer_native.h",
-    "public_overrides/pw_chrono_backend/system_timer_inline.h",
-    "public_overrides/pw_chrono_backend/system_timer_native.h",
-  ]
-  public_deps = [
-    "$dir_pw_chrono:system_clock",
-    "$dir_pw_chrono:system_timer.facade",
-    "$dir_pw_third_party/freertos",
-  ]
-  deps = [
-    "$dir_pw_assert",
-    "$dir_pw_chrono_freertos:system_clock",
-  ]
-  allow_circular_includes_from = [ "$dir_pw_chrono:system_timer.facade" ]
-  sources = [ "system_timer.cc" ]
-}
-
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
diff --git a/pw_chrono_freertos/CMakeLists.txt b/pw_chrono_freertos/CMakeLists.txt
deleted file mode 100644
index 391f2e3..0000000
--- a/pw_chrono_freertos/CMakeLists.txt
+++ /dev/null
@@ -1,71 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-
-pw_add_module_config(pw_chrono_freertos_CONFIG)
-
-pw_add_module_library(pw_chrono_freertos.config
-  HEADERS
-    public/pw_chrono_freertos/config.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_third_party.freertos
-    ${pw_chrono_freertos_CONFIG}
-)
-
-# This target provides the backend for pw::chrono::SystemClock.
-pw_add_module_library(pw_chrono_freertos.system_clock
-  IMPLEMENTS_FACADES
-    pw_chrono.system_clock
-  HEADERS
-    public/pw_chrono_freertos/system_clock_config.h
-    public/pw_chrono_freertos/system_clock_constants.h
-    public_overrides/pw_chrono_backend/system_clock_config.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides
-  PUBLIC_DEPS
-    pw_chrono_freertos.config
-    pw_chrono.epoch
-    pw_third_party.freertos
-  SOURCES
-    system_clock.cc
-  PRIVATE_DEPS
-    pw_interrupt.context
-    pw_sync.interrupt_spin_lock
-)
-
-# This target provides the backend for pw::chrono::SystemTimer.
-pw_add_module_library(pw_chrono_freertos.system_timer
-  IMPLEMENTS_FACADES
-    pw_chrono.system_timer
-  HEADERS
-    public/pw_chrono_freertos/system_timer_inline.h
-    public/pw_chrono_freertos/system_timer_native.h
-    public_overrides/pw_chrono_backend/system_timer_inline.h
-    public_overrides/pw_chrono_backend/system_timer_native.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides
-  PUBLIC_DEPS
-    pw_chrono.system_clock
-    pw_third_party.freertos
-  SOURCES
-    system_timer.cc
-  PRIVATE_DEPS
-    pw_assert
-    pw_chrono_freertos.system_clock
-)
diff --git a/pw_chrono_freertos/OWNERS b/pw_chrono_freertos/OWNERS
deleted file mode 100644
index e9a6c58..0000000
--- a/pw_chrono_freertos/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-ewout@google.com
-hepler@google.com
diff --git a/pw_chrono_freertos/docs.rst b/pw_chrono_freertos/docs.rst
index dc18aaf..cb0d318 100644
--- a/pw_chrono_freertos/docs.rst
+++ b/pw_chrono_freertos/docs.rst
@@ -7,7 +7,8 @@
 implemented using FreeRTOS.
 
 .. warning::
-  This module is still under construction, the API is not yet stable.
+  This module is under construction, not ready for use, and the documentation
+  is incomplete.
 
 SystemClock backend
 -------------------
@@ -22,20 +23,6 @@
 vary if ``portSUPPRESS_TICKS_AND_SLEEP()``, ``vTaskStepTick()``, and/or
 ``xTaskCatchUpTicks()`` are used.
 
-SystemTimer backend
--------------------
-The FreeRTOS based ``system_timer`` backend implements the
-``pw_chrono:system_timer`` facade by using FreeRTOS's Software Timers API.
-``pw::chrono::SystemTimer`` instances use ``StaticTimer_t`` &
-``xTimerCreateStatic``, dynamic memory allocation is never used. This means
-that both ``#define configUSE_TIMERS 1`` and
-``#define configSUPPORT_STATIC_ALLOCATION 1`` must be set in the FreeRTOS
-configuration.
-
-This also means that FreeRTOS's ``Timer Service Daemon Task`` is used and must
-be configured appropriately by the user through ``configTIMER_TASK_PRIORITY``,
-``configTIMER_QUEUE_LENGTH``, and ``configTIMER_TASK_STACK_DEPTH``.
-
 Build targets
 -------------
 The GN build for ``pw_chrono_freertos`` has one target: ``system_clock``.
diff --git a/pw_chrono_freertos/public/pw_chrono_freertos/system_timer_inline.h b/pw_chrono_freertos/public/pw_chrono_freertos/system_timer_inline.h
deleted file mode 100644
index 8892a87..0000000
--- a/pw_chrono_freertos/public/pw_chrono_freertos/system_timer_inline.h
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "FreeRTOS.h"
-#include "pw_chrono/system_clock.h"
-#include "pw_chrono/system_timer.h"
-#include "timers.h"
-
-namespace pw::chrono {
-
-inline void SystemTimer::InvokeAfter(SystemClock::duration delay) {
-  InvokeAt(SystemClock::TimePointAfterAtLeast(delay));
-}
-
-}  // namespace pw::chrono
diff --git a/pw_chrono_freertos/public/pw_chrono_freertos/system_timer_native.h b/pw_chrono_freertos/public/pw_chrono_freertos/system_timer_native.h
deleted file mode 100644
index 999598c..0000000
--- a/pw_chrono_freertos/public/pw_chrono_freertos/system_timer_native.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "FreeRTOS.h"
-#include "pw_chrono/system_clock.h"
-#include "pw_function/function.h"
-#include "timers.h"
-
-namespace pw::chrono::backend {
-
-struct NativeSystemTimer {
-  StaticTimer_t tcb;
-  enum class State {
-    // Timer is not scheduled to execute the user's callback in the future. If
-    // the native callback gets invoked it will do nothing if it in this state.
-    // The native callback will also transition to this state before invoking
-    // the user's callback.
-    kCancelled = 0,
-
-    // Timer is scheduled to execute the user's callback. If the native callback
-    // gets invoked it will invoke the user's callback.
-    kScheduled = 1,
-  };
-  State state;
-  SystemClock::time_point expiry_deadline;
-  Function<void(SystemClock::time_point expired_deadline)> user_callback;
-};
-using NativeSystemTimerHandle = NativeSystemTimer&;
-
-}  // namespace pw::chrono::backend
diff --git a/pw_chrono_freertos/public_overrides/pw_chrono_backend/system_timer_inline.h b/pw_chrono_freertos/public_overrides/pw_chrono_backend/system_timer_inline.h
deleted file mode 100644
index 0f4eb85..0000000
--- a/pw_chrono_freertos/public_overrides/pw_chrono_backend/system_timer_inline.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// This override header includes the main tokenized logging header and defines
-// the PW_LOG macro as the tokenized logging macro.
-#pragma once
-
-#include "pw_chrono_freertos/system_timer_inline.h"
diff --git a/pw_chrono_freertos/public_overrides/pw_chrono_backend/system_timer_native.h b/pw_chrono_freertos/public_overrides/pw_chrono_backend/system_timer_native.h
deleted file mode 100644
index 811c0c0..0000000
--- a/pw_chrono_freertos/public_overrides/pw_chrono_backend/system_timer_native.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// This override header includes the main tokenized logging header and defines
-// the PW_LOG macro as the tokenized logging macro.
-#pragma once
-
-#include "pw_chrono_freertos/system_timer_native.h"
diff --git a/pw_chrono_freertos/system_timer.cc b/pw_chrono_freertos/system_timer.cc
deleted file mode 100644
index 3e9a171..0000000
--- a/pw_chrono_freertos/system_timer.cc
+++ /dev/null
@@ -1,207 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_chrono/system_timer.h"
-
-#include <algorithm>
-#include <mutex>
-
-#include "FreeRTOS.h"
-#include "pw_assert/check.h"
-#include "pw_chrono_freertos/system_clock_constants.h"
-#include "task.h"
-#include "timers.h"
-
-namespace pw::chrono {
-namespace {
-
-using State = backend::NativeSystemTimer::State;
-
-// Instead of adding targeted locks to each instance, simply use the global
-// scheduler critical section lock.
-class SchedulerLock {
- public:
-  static void lock() { vTaskSuspendAll(); }
-  static void unlock() { xTaskResumeAll(); }
-};
-SchedulerLock global_timer_lock;
-
-void HandleTimerCallback(TimerHandle_t timer_handle) {
-  // The FreeRTOS timer service is always handled by a thread, ergo to ensure
-  // this API is threadsafe we simply disable task switching.
-  std::unique_lock lock(global_timer_lock);
-
-  // Because the timer control block, AKA what the timer handle points at, is
-  // the first member of the NativeSystemTimer struct we play a trick to cheaply
-  // get the native handle reference.
-  backend::NativeSystemTimer& native_type =
-      *reinterpret_cast<backend::NativeSystemTimer*>(timer_handle);
-
-  PW_CHECK_UINT_EQ(xTimerIsTimerActive(timer_handle),
-                   pdFALSE,
-                   "The timer is still active while being executed");
-
-  if (native_type.state == State::kCancelled) {
-    // Do nothing, we were invoked while the stop command was in the queue.
-    return;
-  }
-
-  const SystemClock::duration time_until_deadline =
-      native_type.expiry_deadline - SystemClock::now();
-  if (time_until_deadline <= SystemClock::duration::zero()) {
-    // We have met the deadline, cancel the current state and execute the user's
-    // callback. Note we cannot update the state later as the user's callback
-    // may alter the desired state through the Invoke*() API.
-    native_type.state = State::kCancelled;
-
-    // Release the scheduler lock once we won't modify native_state any further.
-    lock.unlock();
-    native_type.user_callback(native_type.expiry_deadline);
-    return;
-  }
-
-  // We haven't met the deadline yet, reschedule as far out as possible.
-  // Note that this must be > SystemClock::duration::zero() based on the
-  // conditional above.
-  const SystemClock::duration period =
-      std::min(pw::chrono::freertos::kMaxTimeout, time_until_deadline);
-  PW_CHECK_UINT_EQ(
-      xTimerChangePeriod(reinterpret_cast<TimerHandle_t>(&native_type.tcb),
-                         static_cast<TickType_t>(period.count()),
-                         0),
-      pdPASS,
-      "Timer command queue overflowed");
-  PW_CHECK_UINT_EQ(
-      xTimerStart(reinterpret_cast<TimerHandle_t>(&native_type.tcb), 0),
-      pdPASS,
-      "Timer command queue overflowed");
-}
-
-// FreeRTOS requires a timer to have a non-zero period.
-constexpr SystemClock::duration kMinTimerPeriod = SystemClock::duration(1);
-constexpr TickType_t kInvalidPeriod = kMinTimerPeriod.count();
-constexpr UBaseType_t kOneShotMode = pdFALSE;  // Do not use auto reload.
-
-}  // namespace
-
-#if configUSE_TIMERS != 1
-#error \
-    "Backend requires your FreeRTOS configuration to have configUSE_TIMERS == 1"
-#endif
-
-#if configSUPPORT_STATIC_ALLOCATION != 1
-#error \
-    "Backend requires your FreeRTOS configuration to have configSUPPORT_STATIC_ALLOCATION == 1"
-#endif
-
-SystemTimer::SystemTimer(ExpiryCallback&& callback)
-    : native_type_{.tcb{},
-                   .state = State::kCancelled,
-                   .expiry_deadline = SystemClock::time_point(),
-                   .user_callback = std::move(callback)} {
-  // Note that timer "creation" is not enqueued through the command queue and
-  // is ergo safe to do before the scheduler is running.
-  const TimerHandle_t handle =
-      xTimerCreateStatic("",  // "pw::chrono::SystemTimer",
-                         kInvalidPeriod,
-                         kOneShotMode,
-                         this,
-                         HandleTimerCallback,
-                         &native_type_.tcb);
-
-  // This should never fail since the pointer provided was not null and it
-  // should return a pointer to the StaticTimer_t.
-  PW_DCHECK_PTR_EQ(handle, reinterpret_cast<TimerHandle_t>(&native_type_.tcb));
-}
-
-SystemTimer::~SystemTimer() {
-  Cancel();
-
-  // WARNING: This enqueues the request to delete the timer through a queue, it
-  // does not synchronously delete and disable the timer here! This means that
-  // if the timer is about to expire and the timer service thread is a lower
-  // priority that it may use the native_type_ after it is free'd.
-  PW_CHECK_UINT_EQ(
-      pdPASS,
-      xTimerDelete(reinterpret_cast<TimerHandle_t>(&native_type_.tcb), 0),
-      "Timer command queue overflowed");
-
-  // In case the timer is still active as warned above, busy yield loop until it
-  // has been removed. Note that this is safe before the scheduler has been
-  // started because the timer cannot have been added to the queue yet and ergo
-  // it shouldn't attempt to yield.
-  while (
-      xTimerIsTimerActive(reinterpret_cast<TimerHandle_t>(&native_type_.tcb))) {
-    taskYIELD();
-  }
-}
-
-void SystemTimer::InvokeAt(SystemClock::time_point timestamp) {
-  // The FreeRTOS timer service is always handled by a thread, ergo to ensure
-  // this API is threadsafe we simply disable task switching.
-  std::lock_guard lock(global_timer_lock);
-
-  // We don't want to call Cancel which would enqueue a stop command instead of
-  // synchronously updating the state. Instead we update the expiry deadline
-  // and update the state where the one shot only fires if the expiry deadline
-  // is exceeded and the callback is executed once.
-  native_type_.expiry_deadline = timestamp;
-
-  // Schedule the timer as far out as possible. Note that the timeout might be
-  // clamped and it may be rescheduled internally.
-  const SystemClock::duration time_until_deadline =
-      timestamp - SystemClock::now();
-  const SystemClock::duration period = std::clamp(
-      kMinTimerPeriod, time_until_deadline, pw::chrono::freertos::kMaxTimeout);
-
-  PW_CHECK_UINT_EQ(
-      xTimerChangePeriod(reinterpret_cast<TimerHandle_t>(&native_type_.tcb),
-                         static_cast<TickType_t>(period.count()),
-                         0),
-      pdPASS,
-      "Timer command queue overflowed");
-
-  // Don't enqueue the start multiple times, schedule it once and let the
-  // callback cancel.
-  if (native_type_.state == State::kCancelled) {
-    PW_CHECK_UINT_EQ(
-        xTimerStart(reinterpret_cast<TimerHandle_t>(&native_type_.tcb), 0),
-        pdPASS,
-        "Timer command queue overflowed");
-    native_type_.state = State::kScheduled;
-  }
-}
-
-void SystemTimer::Cancel() {
-  // The FreeRTOS timer service is always handled by a thread, ergo to ensure
-  // this API is threadsafe we simply disable task switching.
-  std::lock_guard lock(global_timer_lock);
-
-  // The stop command may not be executed until later in case we're in a
-  // critical section. For this reason update the internal state in case the
-  // callback gets invoked.
-  //
-  // Note that xTimerIsTimerActive cannot be used here as the timer service
-  // daemon may be a lower priority and ergo may still execute the callback
-  // after Cancel() was invoked. This is because a single expired timer may be
-  // processed before the entire command queue is emptied.
-  native_type_.state = State::kCancelled;
-
-  PW_CHECK_UINT_EQ(
-      xTimerStop(reinterpret_cast<TimerHandle_t>(&native_type_.tcb), 0),
-      pdPASS,
-      "Timer command queue overflowed");
-}
-
-}  // namespace pw::chrono
diff --git a/pw_chrono_stl/BUILD b/pw_chrono_stl/BUILD
new file mode 100644
index 0000000..e1abacf
--- /dev/null
+++ b/pw_chrono_stl/BUILD
@@ -0,0 +1,47 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "system_clock_headers",
+    hdrs = [
+        "public/pw_chrono_stl/system_clock_config.h",
+        "public/pw_chrono_stl/system_clock_inline.h",
+        "public_overrides/pw_chrono_backend/system_clock_config.h",
+        "public_overrides/pw_chrono_backend/system_clock_inline.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        "//pw_chrono:epoch",
+    ],
+)
+
+pw_cc_library(
+    name = "system_clock",
+    deps = [
+        ":system_clock_headers",
+        "//pw_chrono:system_clock_facade",
+    ],
+)
diff --git a/pw_chrono_stl/BUILD.bazel b/pw_chrono_stl/BUILD.bazel
deleted file mode 100644
index 1fa008f..0000000
--- a/pw_chrono_stl/BUILD.bazel
+++ /dev/null
@@ -1,83 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-load(
-    "//pw_build:selects.bzl",
-    "TARGET_COMPATIBLE_WITH_HOST_SELECT",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "system_clock_headers",
-    hdrs = [
-        "public/pw_chrono_stl/system_clock_config.h",
-        "public/pw_chrono_stl/system_clock_inline.h",
-        "public_overrides/pw_chrono_backend/system_clock_config.h",
-        "public_overrides/pw_chrono_backend/system_clock_inline.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    deps = [
-        "//pw_chrono:epoch",
-    ],
-)
-
-pw_cc_library(
-    name = "system_clock",
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-    deps = [
-        ":system_clock_headers",
-        "//pw_chrono:system_clock_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "system_timer_headers",
-    hdrs = [
-        "public/pw_chrono_stl/system_timer_inline.h",
-        "public/pw_chrono_stl/system_timer_native.h",
-        "public_overrides/pw_chrono_backend/system_timer_inline.h",
-        "public_overrides/pw_chrono_backend/system_timer_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    deps = [
-        "//pw_chrono:system_clock",
-        "//pw_chrono:system_timer_facade",
-        "//pw_function",
-    ],
-)
-
-pw_cc_library(
-    name = "system_timer",
-    srcs = [
-        "system_timer.cc",
-    ],
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-    deps = [
-        ":system_timer_headers",
-        "//pw_chrono:system_timer_facade",
-    ],
-)
diff --git a/pw_chrono_stl/BUILD.gn b/pw_chrono_stl/BUILD.gn
index 9ecbd85..948738d 100644
--- a/pw_chrono_stl/BUILD.gn
+++ b/pw_chrono_stl/BUILD.gn
@@ -45,27 +45,6 @@
   ]
 }
 
-# This target provides the backend for pw::chrono::SystemTimer.
-pw_source_set("system_timer") {
-  public_configs = [
-    ":public_include_path",
-    ":backend_config",
-  ]
-  public = [
-    "public/pw_chrono_stl/system_timer_inline.h",
-    "public/pw_chrono_stl/system_timer_native.h",
-    "public_overrides/pw_chrono_backend/system_timer_inline.h",
-    "public_overrides/pw_chrono_backend/system_timer_native.h",
-  ]
-  public_deps = [
-    "$dir_pw_chrono:system_clock",
-    "$dir_pw_chrono:system_timer.facade",
-    "$dir_pw_function",
-  ]
-  allow_circular_includes_from = [ "$dir_pw_chrono:system_timer.facade" ]
-  sources = [ "system_timer.cc" ]
-}
-
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
diff --git a/pw_chrono_stl/CMakeLists.txt b/pw_chrono_stl/CMakeLists.txt
index 1e83933..f0b6197 100644
--- a/pw_chrono_stl/CMakeLists.txt
+++ b/pw_chrono_stl/CMakeLists.txt
@@ -14,37 +14,7 @@
 
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
 
-# This target provides the backend for pw::chrono::SystemClock.
 pw_add_module_library(pw_chrono_stl.system_clock
   IMPLEMENTS_FACADES
     pw_chrono.system_clock
-  HEADERS
-    public/pw_chrono_stl/system_clock_config.h
-    public/pw_chrono_stl/system_clock_inline.h
-    public_overrides/pw_chrono_backend/system_clock_config.h
-    public_overrides/pw_chrono_backend/system_clock_inline.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides
-  PUBLIC_DEPS
-    pw_chrono.epoch
-)
-
-# This target provides the backend for pw::chrono::SystemTimer.
-pw_add_module_library(pw_chrono_stl.system_timer
-  IMPLEMENTS_FACADES
-    pw_chrono.system_timer
-  HEADERS
-    public/pw_chrono_stl/system_timer_inline.h
-    public/pw_chrono_stl/system_timer_native.h
-    public_overrides/pw_chrono_backend/system_timer_inline.h
-    public_overrides/pw_chrono_backend/system_timer_native.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides
-  PUBLIC_DEPS
-    pw_chrono.system_clock
-    pw_function
-  SOURCES
-    system_timer.cc
 )
diff --git a/pw_chrono_stl/OWNERS b/pw_chrono_stl/OWNERS
deleted file mode 100644
index e9a6c58..0000000
--- a/pw_chrono_stl/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-ewout@google.com
-hepler@google.com
diff --git a/pw_chrono_stl/docs.rst b/pw_chrono_stl/docs.rst
index b1aea39..3ba23ba 100644
--- a/pw_chrono_stl/docs.rst
+++ b/pw_chrono_stl/docs.rst
@@ -7,27 +7,15 @@
 using STL's ``std::chrono`` library.
 
 .. warning::
-  This module is still under construction, the API is not yet stable.
+  This module is under construction, not ready for use, and the documentation
+  is incomplete.
 
 SystemClock backend
 -------------------
-The STL based ``pw_chrono_stl:system_clock`` backend target implements the
-``pw_chrono:system_clock`` facade by using the ``std::chrono::steady_clock``.
-Note that the ``std::chrono::system_clock`` cannot be used as this is not always
-a monotonic clock source.
-
-See the documentation for ``pw_chrono`` for further details.
-
-SystemTimer backend
--------------------
-The STL based ``pw_chrono_stl:system_timer`` backend target implements the
-``pw_chrono:system_timer`` facade by spawning a detached thread for every single
-``InvokeAt()`` and ``InvokeAfter()`` call. This thread simply sleeps until the
-desired ``expiration_deadline`` and invokes the user's ``ExpiryCallback`` if it
-wasn't cancelled.
-
-.. Warning::
-  Although fully functional, the current implementation is NOT efficient!
+The STL based ``system_clock`` backend implements the ``pw_chrono:system_clock``
+facade by using the ``std::chrono::steady_clock``. Note that the
+``std::chrono::system_clock`` cannot be used as this is not always a monotonic
+clock source.
 
 See the documentation for ``pw_chrono`` for further details.
 
diff --git a/pw_chrono_stl/public/pw_chrono_stl/system_timer_inline.h b/pw_chrono_stl/public/pw_chrono_stl/system_timer_inline.h
deleted file mode 100644
index 2671770..0000000
--- a/pw_chrono_stl/public/pw_chrono_stl/system_timer_inline.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <memory>
-
-#include "pw_chrono/system_clock.h"
-#include "pw_chrono/system_timer.h"
-
-namespace pw::chrono {
-
-inline SystemTimer::SystemTimer(ExpiryCallback&& callback) : native_type_() {
-  native_type_.callback_context =
-      std::make_shared<backend::NativeSystemTimer::CallbackContext>(
-          std::move(callback));
-}
-
-inline SystemTimer::~SystemTimer() { Cancel(); }
-
-inline void SystemTimer::InvokeAfter(SystemClock::duration delay) {
-  InvokeAt(SystemClock::TimePointAfterAtLeast(delay));
-}
-
-inline SystemTimer::native_handle_type SystemTimer::native_handle() {
-  return native_type_;
-}
-
-}  // namespace pw::chrono
diff --git a/pw_chrono_stl/public/pw_chrono_stl/system_timer_native.h b/pw_chrono_stl/public/pw_chrono_stl/system_timer_native.h
deleted file mode 100644
index e9162cd..0000000
--- a/pw_chrono_stl/public/pw_chrono_stl/system_timer_native.h
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <memory>
-#include <mutex>
-
-#include "pw_chrono/system_clock.h"
-#include "pw_function/function.h"
-
-namespace pw::chrono::backend {
-
-struct NativeSystemTimer {
-  // Instead of using a more complex blocking timer cleanup, a shared_pointer is
-  // used so that the heap allocation is still valid for the detached threads
-  // even after the NativeSystemTimer has been destructed. Note this is shared
-  // with all detached threads.
-  struct CallbackContext {
-    CallbackContext(
-        Function<void(SystemClock::time_point expired_deadline)>&& cb)
-        : callback(std::move(cb)) {}
-
-    const Function<void(SystemClock::time_point expired_deadline)> callback;
-
-    // The mutex is used both to ensure the public API is threadsafe and to
-    // ensure that only one expiry callback is executed at time.
-    // A recurisve mutex is used as the timer callback must be able to invoke
-    // its own public API.
-    std::recursive_mutex mutex;
-  };
-  std::shared_ptr<CallbackContext> callback_context;
-
-  // This is only shared with the last active timer if there is one. Note that
-  // this is guarded by the callback_context's mutex.
-  std::shared_ptr<bool> active_timer_enabled;
-};
-
-using NativeSystemTimerHandle = NativeSystemTimer&;
-
-}  // namespace pw::chrono::backend
diff --git a/pw_chrono_stl/public_overrides/pw_chrono_backend/system_timer_inline.h b/pw_chrono_stl/public_overrides/pw_chrono_backend/system_timer_inline.h
deleted file mode 100644
index 6fd8078..0000000
--- a/pw_chrono_stl/public_overrides/pw_chrono_backend/system_timer_inline.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_chrono_stl/system_timer_inline.h"
diff --git a/pw_chrono_stl/public_overrides/pw_chrono_backend/system_timer_native.h b/pw_chrono_stl/public_overrides/pw_chrono_backend/system_timer_native.h
deleted file mode 100644
index c1a3543..0000000
--- a/pw_chrono_stl/public_overrides/pw_chrono_backend/system_timer_native.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_chrono_stl/system_timer_native.h"
diff --git a/pw_chrono_stl/system_timer.cc b/pw_chrono_stl/system_timer.cc
deleted file mode 100644
index 5557042..0000000
--- a/pw_chrono_stl/system_timer.cc
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_chrono/system_timer.h"
-
-#include <thread>
-
-namespace pw::chrono {
-namespace {
-
-using CallbackContext = backend::NativeSystemTimer::CallbackContext;
-
-void SystemTimerThreadFunction(
-    std::shared_ptr<bool> timer_enabled,
-    std::shared_ptr<CallbackContext> callback_context,
-    SystemClock::time_point expiry_deadline) {
-  // Sleep until the requested deadline.
-  std::this_thread::sleep_until(expiry_deadline);
-
-  {
-    std::lock_guard lock(callback_context->mutex);
-    // Only invoke the user's callback if this invocation has not been
-    // cancelled.
-    if (*timer_enabled) {
-      (callback_context->callback)(expiry_deadline);
-    }
-  }
-}
-
-}  // namespace
-
-void SystemTimer::InvokeAt(SystemClock::time_point timestamp) {
-  std::lock_guard lock(native_type_.callback_context->mutex);
-
-  // First, cancel any outstanding requests.
-  if (native_type_.active_timer_enabled) {
-    *native_type_.active_timer_enabled = false;
-  }
-
-  // Second, active another detached timer thread with a new shared atomic bool.
-  native_type_.active_timer_enabled = std::make_shared<bool>(true);
-  std::thread(SystemTimerThreadFunction,
-              native_type_.active_timer_enabled,
-              native_type_.callback_context,
-              timestamp)
-      .detach();
-}
-
-void SystemTimer::Cancel() {
-  std::lock_guard lock(native_type_.callback_context->mutex);
-
-  if (native_type_.active_timer_enabled) {
-    *native_type_.active_timer_enabled = false;
-  }
-}
-
-}  // namespace pw::chrono
diff --git a/pw_chrono_threadx/BUILD b/pw_chrono_threadx/BUILD
new file mode 100644
index 0000000..0920cfb
--- /dev/null
+++ b/pw_chrono_threadx/BUILD
@@ -0,0 +1,52 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "system_clock_headers",
+    hdrs = [
+        "public/pw_chrono_threadx/config.h",
+        "public/pw_chrono_threadx/system_clock_config.h",
+        "public/pw_chrono_threadx/system_clock_constants.h",
+        "public_overrides/pw_chrono_backend/system_clock_config.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        "//pw_chrono:epoch",
+    ],
+)
+
+pw_cc_library(
+    name = "system_clock",
+    srcs = [
+        "system_clock.cc",
+    ],
+    deps = [
+        ":system_clock_headers",
+        "//pw_chrono:system_clock_facade",
+        # TODO: This should depend on ThreadX but our third parties currently
+        # do not have Bazel support.
+    ],
+)
diff --git a/pw_chrono_threadx/BUILD.bazel b/pw_chrono_threadx/BUILD.bazel
deleted file mode 100644
index 8a5dafe..0000000
--- a/pw_chrono_threadx/BUILD.bazel
+++ /dev/null
@@ -1,58 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "system_clock_headers",
-    hdrs = [
-        "public/pw_chrono_threadx/config.h",
-        "public/pw_chrono_threadx/system_clock_config.h",
-        "public/pw_chrono_threadx/system_clock_constants.h",
-        "public_overrides/pw_chrono_backend/system_clock_config.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:threadx",
-    ],
-    deps = [
-        "//pw_chrono:epoch",
-    ],
-)
-
-pw_cc_library(
-    name = "system_clock",
-    srcs = [
-        "system_clock.cc",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:threadx",
-    ],
-    deps = [
-        ":system_clock_headers",
-        "//pw_chrono:system_clock_facade",
-        # TODO: This should depend on ThreadX but our third parties currently
-        # do not have Bazel support.
-    ],
-)
diff --git a/pw_chrono_threadx/OWNERS b/pw_chrono_threadx/OWNERS
deleted file mode 100644
index e9a6c58..0000000
--- a/pw_chrono_threadx/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-ewout@google.com
-hepler@google.com
diff --git a/pw_chrono_zephyr/BUILD.gn b/pw_chrono_zephyr/BUILD.gn
deleted file mode 100644
index 9a6699a..0000000
--- a/pw_chrono_zephyr/BUILD.gn
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_docgen/docs.gni")
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
diff --git a/pw_chrono_zephyr/CMakeLists.txt b/pw_chrono_zephyr/CMakeLists.txt
deleted file mode 100644
index ad979c9..0000000
--- a/pw_chrono_zephyr/CMakeLists.txt
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-
-if(Zephyr_FOUND AND CONFIG_PIGWEED_CHRONO_SYSTEM_CLOCK)
-  pw_add_module_library(pw_chrono_zephyr.system_clock
-    IMPLEMENTS_FACADES
-      pw_chrono.system_clock
-    PUBLIC_DEPS
-      pw_function
-  )
-  pw_set_backend(pw_chrono.system_clock pw_chrono_zephyr.system_clock)
-  zephyr_link_interface(pw_chrono_zephyr.system_clock)
-  zephyr_link_libraries(pw_chrono_zephyr.system_clock)
-endif()
\ No newline at end of file
diff --git a/pw_chrono_zephyr/Kconfig b/pw_chrono_zephyr/Kconfig
deleted file mode 100644
index c6eacac..0000000
--- a/pw_chrono_zephyr/Kconfig
+++ /dev/null
@@ -1,25 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-menuconfig PIGWEED_CHRONO
-    bool "Enable the Pigweed chrono facade (pw_chrono)"
-    select PIGWEED_PREPROCESSOR
-
-if PIGWEED_CHRONO
-
-config PIGWEED_CHRONO_SYSTEM_CLOCK
-    bool "Enabled the Pigweed chrono system clock library (pw_chrono.system_clock)"
-    select PIGWEED_FUNCTION
-
-endif # PIGWEED_CHRONO
diff --git a/pw_chrono_zephyr/OWNERS b/pw_chrono_zephyr/OWNERS
deleted file mode 100644
index 2230ec2..0000000
--- a/pw_chrono_zephyr/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-peress@google.com
diff --git a/pw_chrono_zephyr/docs.rst b/pw_chrono_zephyr/docs.rst
deleted file mode 100644
index 06760fd..0000000
--- a/pw_chrono_zephyr/docs.rst
+++ /dev/null
@@ -1,16 +0,0 @@
-.. _module-pw_chrono_zephyr:
-
-================
-pw_chrono_zephyr
-================
-
---------
-Overview
---------
-This chrono backend implements the ``pw_chrono`` facades. To enable, set
-``CONFIG_PIGWEED_CHRONO=y``.
-
-pw_chrono.system_clock
-----------------------
-To enable the ``system_clock`` facade, it is also required to add
-``CONFIG_PIGWEED_CHRONO_SYSTEM_CLOCK=y``.
\ No newline at end of file
diff --git a/pw_chrono_zephyr/public/pw_chrono_zephyr/system_clock_config.h b/pw_chrono_zephyr/public/pw_chrono_zephyr/system_clock_config.h
deleted file mode 100644
index 55bfda5..0000000
--- a/pw_chrono_zephyr/public/pw_chrono_zephyr/system_clock_config.h
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <kernel.h>
-
-// Use the Zephyr config's tick rate.
-#define PW_CHRONO_SYSTEM_CLOCK_PERIOD_SECONDS_NUMERATOR 1
-#define PW_CHRONO_SYSTEM_CLOCK_PERIOD_SECONDS_DENOMINATOR \
-  CONFIG_SYS_CLOCK_TICKS_PER_SEC
-
-#ifdef __cplusplus
-
-#include "pw_chrono/epoch.h"
-
-namespace pw::chrono::backend {
-
-// The Zephyr clock starts at zero during initialization, approximately the
-// time since boot.
-inline constexpr Epoch kSystemClockEpoch = pw::chrono::Epoch::kTimeSinceBoot;
-
-// The current backend implementation is not NMI safe.
-inline constexpr bool kSystemClockNmiSafe = false;
-
-// The Zephyr clock never halts.
-inline constexpr bool kSystemClockFreeRunning = false;
-
-}  // namespace pw::chrono::backend
-
-#endif  // __cplusplus
diff --git a/pw_chrono_zephyr/public/pw_chrono_zephyr/system_clock_constants.h b/pw_chrono_zephyr/public/pw_chrono_zephyr/system_clock_constants.h
deleted file mode 100644
index 4013b5c..0000000
--- a/pw_chrono_zephyr/public/pw_chrono_zephyr/system_clock_constants.h
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <kernel.h>
-
-#include "pw_chrono/system_clock.h"
-
-namespace pw::chrono::zephyr {
-
-// Max timeout to be used by users of the Zephyr pw::chrono::SystemClock
-// backend provided by this module.
-inline constexpr SystemClock::duration kMaxTimeout =
-    SystemClock::duration(K_FOREVER.ticks - 1);
-
-}  // namespace pw::chrono::zephyr
diff --git a/pw_chrono_zephyr/public/pw_chrono_zephyr/system_clock_inline.h b/pw_chrono_zephyr/public/pw_chrono_zephyr/system_clock_inline.h
deleted file mode 100644
index 9ea2278..0000000
--- a/pw_chrono_zephyr/public/pw_chrono_zephyr/system_clock_inline.h
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <kernel.h>
-
-#include "pw_chrono/system_clock.h"
-
-namespace pw::chrono::backend {
-
-inline int64_t GetSystemClockTickCount() { return k_uptime_ticks(); }
-
-}  // namespace pw::chrono::backend
diff --git a/pw_chrono_zephyr/public/pw_chrono_zephyr/system_timer_inline.h b/pw_chrono_zephyr/public/pw_chrono_zephyr/system_timer_inline.h
deleted file mode 100644
index f652467..0000000
--- a/pw_chrono_zephyr/public/pw_chrono_zephyr/system_timer_inline.h
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <kernel.h>
-
-#include "pw_chrono/system_timer.h"
-
-namespace pw::chrono {
-
-inline void SystemTimer::InvokeAfter(SystemClock::duration delay) {
-  InvokeAt(SystemClock::TimePointAfterAtLeast(delay));
-}
-
-}  // namespace pw::chrono
diff --git a/pw_chrono_zephyr/public/pw_chrono_zephyr/system_timer_native.h b/pw_chrono_zephyr/public/pw_chrono_zephyr/system_timer_native.h
deleted file mode 100644
index 9aa1a65..0000000
--- a/pw_chrono_zephyr/public/pw_chrono_zephyr/system_timer_native.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <kernel.h>
-#include <sys/mutex.h>
-
-#include "pw_chrono/system_clock.h"
-#include "pw_function/function.h"
-
-namespace pw::chrono::backend {
-
-struct NativeSystemTimer;
-
-struct ZephyrWorkWrapper {
-  k_work_delayable work;
-  NativeSystemTimer* owner;
-};
-
-struct NativeSystemTimer {
-  ZephyrWorkWrapper work_wrapper;
-  sys_mutex mutex;
-  SystemClock::time_point expiry_deadline;
-  Function<void(SystemClock::time_point expired_deadline)> user_callback;
-};
-using NativeSystemTimerHandle = NativeSystemTimer&;
-
-}  // namespace pw::chrono::backend
diff --git a/pw_chrono_zephyr/public_overrides/pw_chrono_backend/system_clock_config.h b/pw_chrono_zephyr/public_overrides/pw_chrono_backend/system_clock_config.h
deleted file mode 100644
index 7dfe019..0000000
--- a/pw_chrono_zephyr/public_overrides/pw_chrono_backend/system_clock_config.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// This override header includes the main tokenized logging header and defines
-// the PW_LOG macro as the tokenized logging macro.
-#pragma once
-
-#include "pw_chrono_zephyr/system_clock_config.h"
diff --git a/pw_chrono_zephyr/public_overrides/pw_chrono_backend/system_clock_inline.h b/pw_chrono_zephyr/public_overrides/pw_chrono_backend/system_clock_inline.h
deleted file mode 100644
index ea2e8e2..0000000
--- a/pw_chrono_zephyr/public_overrides/pw_chrono_backend/system_clock_inline.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_chrono_zephyr/system_clock_inline.h"
diff --git a/pw_chrono_zephyr/public_overrides/pw_chrono_backend/system_timer_inline.h b/pw_chrono_zephyr/public_overrides/pw_chrono_backend/system_timer_inline.h
deleted file mode 100644
index 18566e4..0000000
--- a/pw_chrono_zephyr/public_overrides/pw_chrono_backend/system_timer_inline.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// This override header includes the main tokenized logging header and defines
-// the PW_LOG macro as the tokenized logging macro.
-#pragma once
-
-#include "pw_chrono_zephyr/system_timer_inline.h"
diff --git a/pw_chrono_zephyr/public_overrides/pw_chrono_backend/system_timer_native.h b/pw_chrono_zephyr/public_overrides/pw_chrono_backend/system_timer_native.h
deleted file mode 100644
index 7bcb1ac..0000000
--- a/pw_chrono_zephyr/public_overrides/pw_chrono_backend/system_timer_native.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// This override header includes the main tokenized logging header and defines
-// the PW_LOG macro as the tokenized logging macro.
-#pragma once
-
-#include "pw_chrono_zephyr/system_timer_native.h"
diff --git a/pw_chrono_zephyr/system_timer.cc b/pw_chrono_zephyr/system_timer.cc
deleted file mode 100644
index abb7423..0000000
--- a/pw_chrono_zephyr/system_timer.cc
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_chrono/system_timer.h"
-
-#include <kernel.h>
-#include <sys/mutex.h>
-
-#include <algorithm>
-
-#include "pw_chrono_zephyr/system_clock_constants.h"
-#include "pw_chrono_zephyr/system_timer_native.h"
-
-namespace pw::chrono {
-namespace {
-
-constexpr SystemClock::duration kMinTimerPeriod = SystemClock::duration(1);
-// Work synchronization objects must be in cache-coherent memory, which excludes
-// stacks on some architectures.
-static k_work_sync work_sync;
-
-}  // namespace
-
-void HandleTimerWork(k_work* item) {
-  k_work_delayable* delayable_item = k_work_delayable_from_work(item);
-  backend::NativeSystemTimer* native_type =
-      CONTAINER_OF(delayable_item, backend::ZephyrWorkWrapper, work)->owner;
-
-  sys_mutex_lock(&native_type->mutex, K_FOREVER);
-#ifdef CONFIG_TIMEOUT_64BIT
-  native_type->user_callback(native_type->expiry_deadline);
-#else
-  const SystemClock::duration time_until_deadline =
-      native_type->expiry_deadline - SystemClock::now();
-  if (time_until_deadline <= SystemClock::duration::zero()) {
-    native_type->user_callback(native_type->expiry_deadline);
-  } else {
-    // We haven't met the deadline yet, reschedule as far out as possible.
-    const SystemClock::duration period =
-        std::min(pw::chrono::zephyr::kMaxTimeout, time_until_deadline);
-    k_work_schedule(&native_type->work_wrapper.work, K_TICKS(period.count()));
-  }
-#endif  // CONFIG_TIMEOUT_64BIT
-  sys_mutex_unlock(&native_type->mutex);
-}
-
-SystemTimer::SystemTimer(ExpiryCallback callback)
-    : native_type_{.work_wrapper =
-                       {
-                           .work = {},
-                           .owner = nullptr,
-                       },
-                   .mutex = {},
-                   .expiry_deadline = SystemClock::time_point(),
-                   .user_callback = std::move(callback)} {
-  k_work_init_delayable(&native_type_.work_wrapper.work, HandleTimerWork);
-  sys_mutex_init(&native_type_.mutex);
-  native_type_.work_wrapper.owner = &native_type_;
-}
-
-SystemTimer::~SystemTimer() {
-  k_work_cancel_sync(&native_type_.work_wrapper.work, &work_sync);
-}
-
-void SystemTimer::InvokeAt(SystemClock::time_point timestamp) {
-  sys_mutex_lock(&native_type_.mutex, K_FOREVER);
-  native_type_.expiry_deadline = timestamp;
-
-  const SystemClock::duration time_until_deadline =
-      timestamp - SystemClock::now();
-  const SystemClock::duration period =
-      IS_ENABLED(CONFIG_TIMEOUT_64BIT)
-          ? time_until_deadline
-          : std::clamp(kMinTimerPeriod,
-                       time_until_deadline,
-                       pw::chrono::zephyr::kMaxTimeout);
-
-  k_work_schedule(&native_type_.work_wrapper.work, K_TICKS(period.count()));
-  sys_mutex_unlock(&native_type_.mutex);
-}
-
-}  // namespace pw::chrono
diff --git a/pw_cli/OWNERS b/pw_cli/OWNERS
deleted file mode 100644
index d96cbc6..0000000
--- a/pw_cli/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-hepler@google.com
diff --git a/pw_cli/docs.rst b/pw_cli/docs.rst
index 9019764..42144f1 100644
--- a/pw_cli/docs.rst
+++ b/pw_cli/docs.rst
@@ -226,6 +226,16 @@
   into a file, and then point ``PW_BRANDING_BANNER`` at it.  Most of the fonts
   use normal ASCII characters; and fonts with extended ASCII characters use the
   Unicode versions of them (needed for modern terminals).
+* `Online ANSII Edit by Andy Herbert
+  <http://andyherbert.github.io/ansiedit/public/index.html>`_ - Browser based
+  editor that can export to mixed UTF-8 and ANSII color. It's also `open source
+  <https://github.com/andyherbert/ansiedit>`_. What's nice about this editor is
+  that you can create a multi-color banner, and save it with the ``File`` -->
+  ``Export as ANSi (UTF-8)`` option, and use it directly as a Pigweed banner.
+  One caveat is that the editor uses UTF-8 box drawing characters, which don't
+  work well with all terminals. However, the box drawing characters look so
+  slick on terminals that support them that we feel this is a worthwhile
+  tradeoff.
 
 There are other options, but these require additional work to put into Pigweed
 since they only export in the traditional ANS or ICE formats. The old ANS
diff --git a/pw_cli/py/BUILD.bazel b/pw_cli/py/BUILD.bazel
deleted file mode 100644
index eac6f4c..0000000
--- a/pw_cli/py/BUILD.bazel
+++ /dev/null
@@ -1,71 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load("@rules_python//python:defs.bzl", "py_library")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-py_library(
-    name = "pw_cli",
-    srcs = [
-        "pw_cli/__init__.py",
-        "pw_cli/__main__.py",
-        "pw_cli/argument_types.py",
-        "pw_cli/arguments.py",
-        "pw_cli/branding.py",
-        "pw_cli/color.py",
-        "pw_cli/env.py",
-        "pw_cli/envparse.py",
-        "pw_cli/log.py",
-        "pw_cli/plugins.py",
-        "pw_cli/process.py",
-        "pw_cli/pw_command_plugins.py",
-        "pw_cli/requires.py",
-    ],
-    imports = ["."],
-)
-
-py_binary(
-    name = "log",
-    srcs = [
-        "pw_cli/log.py",
-    ],
-    deps = [
-        ":pw_cli",
-    ],
-)
-
-py_test(
-    name = "plugins_test",
-    size = "small",
-    srcs = [
-        "plugins_test.py",
-    ],
-    deps = [
-        ":pw_cli",
-    ],
-)
-
-py_test(
-    name = "envparse_test",
-    size = "small",
-    srcs = [
-        "envparse_test.py",
-    ],
-    deps = [
-        ":pw_cli",
-    ],
-)
diff --git a/pw_cli/py/BUILD.gn b/pw_cli/py/BUILD.gn
index 0057e89..b7ed63c 100644
--- a/pw_cli/py/BUILD.gn
+++ b/pw_cli/py/BUILD.gn
@@ -17,15 +17,10 @@
 import("$dir_pw_build/python.gni")
 
 pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
+  setup = [ "setup.py" ]
   sources = [
     "pw_cli/__init__.py",
     "pw_cli/__main__.py",
-    "pw_cli/argument_types.py",
     "pw_cli/arguments.py",
     "pw_cli/branding.py",
     "pw_cli/color.py",
diff --git a/pw_cli/py/envparse_test.py b/pw_cli/py/envparse_test.py
index cbeb332..0a6f180 100644
--- a/pw_cli/py/envparse_test.py
+++ b/pw_cli/py/envparse_test.py
@@ -16,7 +16,7 @@
 import math
 import unittest
 
-from pw_cli import envparse
+import pw_cli.envparse as envparse
 
 # pylint: disable=no-member
 
@@ -38,7 +38,7 @@
             'ReVeRsE': 'pigweed',
         }
 
-        self.parser = envparse.EnvironmentParser(error_on_unrecognized=True)
+        self.parser = envparse.EnvironmentParser()
         self.parser.add_var('PATH')
         self.parser.add_var('FOO', type=int)
         self.parser.add_var('BAR', type=bool)
@@ -100,8 +100,7 @@
         }
 
     def test_parse_unrecognized_variable(self):
-        parser = envparse.EnvironmentParser(prefix='PW_',
-                                            error_on_unrecognized=True)
+        parser = envparse.EnvironmentParser(prefix='PW_')
         parser.add_var('PW_FOO')
         parser.add_var('PW_BAR')
 
@@ -109,16 +108,14 @@
             parser.parse_env(env=self.raw_env)
 
     def test_parse_unrecognized_but_allowed_suffix(self):
-        parser = envparse.EnvironmentParser(prefix='PW_',
-                                            error_on_unrecognized=True)
+        parser = envparse.EnvironmentParser(prefix='PW_')
         parser.add_allowed_suffix('_ALLOWED_SUFFIX')
 
         env = parser.parse_env(env={'PW_FOO_ALLOWED_SUFFIX': '001'})
         self.assertEqual(env.PW_FOO_ALLOWED_SUFFIX, '001')
 
     def test_parse_allowed_suffix_but_not_suffix(self):
-        parser = envparse.EnvironmentParser(prefix='PW_',
-                                            error_on_unrecognized=True)
+        parser = envparse.EnvironmentParser(prefix='PW_')
         parser.add_allowed_suffix('_ALLOWED_SUFFIX')
 
         with self.assertRaises(ValueError):
@@ -135,8 +132,7 @@
         self.assertEqual(env.PW_BAR, self.raw_env['PW_BAR'])
 
     def test_add_var_without_prefix(self):
-        parser = envparse.EnvironmentParser(prefix='PW_',
-                                            error_on_unrecognized=True)
+        parser = envparse.EnvironmentParser(prefix='PW_')
         with self.assertRaises(ValueError):
             parser.add_var('FOO')
 
diff --git a/pw_cli/py/pw_cli/argument_types.py b/pw_cli/py/pw_cli/argument_types.py
deleted file mode 100644
index a719e16..0000000
--- a/pw_cli/py/pw_cli/argument_types.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Defines argument types for use with argparse."""
-
-import argparse
-import logging
-from pathlib import Path
-
-
-def directory(arg: str) -> Path:
-    path = Path(arg)
-    if path.is_dir():
-        return path.resolve()
-
-    raise argparse.ArgumentTypeError(f'"{path}" is not a directory')
-
-
-def log_level(arg: str) -> int:
-    try:
-        return getattr(logging, arg.upper())
-    except AttributeError:
-        raise argparse.ArgumentTypeError(
-            f'"{arg.upper()}" is not a valid log level')
diff --git a/pw_cli/py/pw_cli/arguments.py b/pw_cli/py/pw_cli/arguments.py
index 86ff6e2..f9e2358 100644
--- a/pw_cli/py/pw_cli/arguments.py
+++ b/pw_cli/py/pw_cli/arguments.py
@@ -19,7 +19,7 @@
 import sys
 from typing import NoReturn
 
-from pw_cli import argument_types, plugins
+from pw_cli import plugins
 from pw_cli.branding import banner
 
 _HELP_HEADER = '''The Pigweed command line interface (CLI).
@@ -36,7 +36,7 @@
 
 def print_banner() -> None:
     """Prints the PIGWEED (or project specific) banner to stderr."""
-    print(banner() + '\n', file=sys.stderr)
+    print(banner(), file=sys.stderr)
 
 
 def format_help(registry: plugins.Registry) -> str:
@@ -60,6 +60,20 @@
         description=_HELP_HEADER,
         formatter_class=argparse.RawDescriptionHelpFormatter)
 
+    def directory(arg: str) -> Path:
+        path = Path(arg)
+        if path.is_dir():
+            return path.resolve()
+
+        raise argparse.ArgumentTypeError(f'{path} is not a directory')
+
+    def log_level(arg: str) -> int:
+        try:
+            return getattr(logging, arg.upper())
+        except AttributeError:
+            raise argparse.ArgumentTypeError(
+                f'{arg.upper()} is not a valid log level')
+
     # Do not use the built-in help argument so that displaying the help info can
     # be deferred until the pw plugins have been registered.
     argparser.add_argument('-h',
@@ -69,13 +83,13 @@
     argparser.add_argument(
         '-C',
         '--directory',
-        type=argument_types.directory,
+        type=directory,
         default=Path.cwd(),
         help='Change to this directory before doing anything')
     argparser.add_argument(
         '-l',
         '--loglevel',
-        type=argument_types.log_level,
+        type=log_level,
         default=logging.INFO,
         help='Set the log level (debug, info, warning, error, critical)')
     argparser.add_argument('--no-banner',
diff --git a/pw_cli/py/pw_cli/branding.py b/pw_cli/py/pw_cli/branding.py
index 81274ec..87a424cc 100644
--- a/pw_cli/py/pw_cli/branding.py
+++ b/pw_cli/py/pw_cli/branding.py
@@ -13,7 +13,6 @@
 # the License.
 """Facilities for accessing the current Pigweed branding"""
 
-import operator
 from typing import Optional
 from pathlib import Path
 
@@ -32,7 +31,7 @@
 '''
 
 
-def banner() -> str:
+def banner():
     global _memoized_banner  # pylint: disable=global-statement
     if _memoized_banner is not None:
         return _memoized_banner
@@ -47,8 +46,10 @@
     # Color the banner if requested.
     banner_color = parsed_env.PW_BRANDING_BANNER_COLOR
     if banner_color != '':
-        set_color = operator.attrgetter(banner_color)(pw_cli.color.colors())
-        _memoized_banner = '\n'.join(
-            set_color(line) for line in _memoized_banner.splitlines())
+        _memoized_banner = getattr(
+            pw_cli.color.colors(),
+            banner_color,
+            str,
+        )(_memoized_banner)
 
     return _memoized_banner
diff --git a/pw_cli/py/pw_cli/color.py b/pw_cli/py/pw_cli/color.py
index ef2563a..63c58e2 100644
--- a/pw_cli/py/pw_cli/color.py
+++ b/pw_cli/py/pw_cli/color.py
@@ -46,7 +46,6 @@
         self.blue = _make_color(34, 1)
         self.cyan = _make_color(36, 1)
         self.magenta = _make_color(35, 1)
-        self.bold_magenta = _make_color(30, 45)
         self.bold_white = _make_color(37, 1)
         self.black_on_white = _make_color(30, 47)  # black fg white bg
 
diff --git a/pw_cli/py/pw_cli/env.py b/pw_cli/py/pw_cli/env.py
index c1e4775..7b03e7f 100644
--- a/pw_cli/py/pw_cli/env.py
+++ b/pw_cli/py/pw_cli/env.py
@@ -51,16 +51,21 @@
                    type=envparse.strict_bool,
                    default=False)
 
+    # TODO(pwbug/274) Remove after some transition time. These are no longer
+    # used but may be set by users or downstream projects, or just in currently
+    # active shells.
+    parser.add_var('PW_CIPD_PACKAGE_FILES')
+    parser.add_var('PW_VIRTUALENV_REQUIREMENTS')
+    parser.add_var('PW_VIRTUALENV_REQUIREMENTS_APPEND_DEFAULT')
+    parser.add_var('PW_VIRTUALENV_SETUP_PY_ROOTS')
+    parser.add_var('PW_CARGO_PACKAGE_FILES')
+    parser.add_var('PW_CARGO_SETUP', type=envparse.strict_bool, default=False)
+    parser.add_var('PW_VIRTUALENV_REQUIREMENTS_APPEND_DEFAULT')
+
     parser.add_var('PW_BANNER_FUNC')
     parser.add_var('PW_BRANDING_BANNER')
     parser.add_var('PW_BRANDING_BANNER_COLOR', default='magenta')
 
-    parser.add_var('PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE',
-                   type=envparse.strict_bool)
-
-    parser.add_var('PW_CONSOLE_CONFIG_FILE')
-    parser.add_var('PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED')
-
     return parser
 
 
diff --git a/pw_cli/py/pw_cli/envparse.py b/pw_cli/py/pw_cli/envparse.py
index e27eb5b..9617d78 100644
--- a/pw_cli/py/pw_cli/envparse.py
+++ b/pw_cli/py/pw_cli/envparse.py
@@ -17,7 +17,6 @@
 from dataclasses import dataclass
 import os
 from typing import Callable, Dict, Generic, IO, List, Mapping, Optional, TypeVar
-from typing import Union
 
 
 class EnvNamespace(argparse.Namespace):  # pylint: disable=too-few-public-methods
@@ -57,9 +56,7 @@
           start with the specified string.
         error_on_unrecognized: If True and prefix is provided, will raise an
           exception if the environment contains a variable with the specified
-          prefix that is not registered on the EnvironmentParser. If None,
-          checks existence of PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED (but not
-          value).
+          prefix that is not registered on the EnvironmentParser.
 
     Example:
 
@@ -73,13 +70,9 @@
     """
     def __init__(self,
                  prefix: Optional[str] = None,
-                 error_on_unrecognized: Union[bool, None] = None) -> None:
+                 error_on_unrecognized: bool = True) -> None:
         self._prefix: Optional[str] = prefix
-        if error_on_unrecognized is None:
-            varname = 'PW_ENVIRONMENT_NO_ERROR_ON_UNRECOGNIZED'
-            error_on_unrecognized = varname not in os.environ
         self._error_on_unrecognized: bool = error_on_unrecognized
-
         self._variables: Dict[str, VariableDescriptor] = {}
         self._allowed_suffixes: List[str] = []
 
@@ -109,7 +102,7 @@
         self._variables[name] = VariableDescriptor(name, type, default)
 
     def add_allowed_suffix(self, suffix: str) -> None:
-        """Registers an environment variable name suffix to be allowed."""
+        """Registers an environmant variable name suffix to be allowed."""
 
         self._allowed_suffixes.append(suffix)
 
diff --git a/pw_cli/py/pw_cli/log.py b/pw_cli/py/pw_cli/log.py
index 3ef7554..df5ebdc 100644
--- a/pw_cli/py/pw_cli/log.py
+++ b/pw_cli/py/pw_cli/log.py
@@ -15,7 +15,7 @@
 
 import logging
 from pathlib import Path
-from typing import NamedTuple, Optional, Union, Iterator
+from typing import NamedTuple, Union, Iterator
 
 import pw_cli.color
 import pw_cli.env
@@ -24,9 +24,6 @@
 # Log level used for captured output of a subprocess run through pw.
 LOGLEVEL_STDOUT = 21
 
-# Log level indicating a irrecoverable failure.
-LOGLEVEL_FATAL = 70
-
 
 class _LogLevel(NamedTuple):
     level: int
@@ -38,24 +35,18 @@
 # Shorten all the log levels to 3 characters for column-aligned logs.
 # Color the logs using ANSI codes.
 _LOG_LEVELS = (
-    _LogLevel(LOGLEVEL_FATAL,   'bold_red',     'FTL', '☠️ '),
-    _LogLevel(logging.CRITICAL, 'bold_magenta', 'CRT', '‼️ '),
-    _LogLevel(logging.ERROR,    'red',          'ERR', '❌'),
-    _LogLevel(logging.WARNING,  'yellow',       'WRN', '⚠️ '),
-    _LogLevel(logging.INFO,     'magenta',      'INF', 'ℹ️ '),
-    _LogLevel(LOGLEVEL_STDOUT,  'cyan',         'OUT', '💬'),
-    _LogLevel(logging.DEBUG,    'blue',         'DBG', '👾'),
+    _LogLevel(logging.CRITICAL, 'bold_red', 'CRT', '☠️ '),
+    _LogLevel(logging.ERROR,    'red',      'ERR', '❌'),
+    _LogLevel(logging.WARNING,  'yellow',   'WRN', '⚠️ '),
+    _LogLevel(logging.INFO,     'magenta',  'INF', 'ℹ️ '),
+    _LogLevel(LOGLEVEL_STDOUT,  'cyan',     'OUT', '💬'),
+    _LogLevel(logging.DEBUG,    'blue',     'DBG', '👾'),
 )  # yapf: disable
 
 _LOG = logging.getLogger(__name__)
 _STDERR_HANDLER = logging.StreamHandler()
 
 
-def c_to_py_log_level(c_level: int) -> int:
-    """Converts pw_log C log-level macros to Python logging levels."""
-    return c_level * 10
-
-
 def main() -> None:
     """Shows how logs look at various levels."""
 
@@ -63,8 +54,7 @@
     _LOG.setLevel(logging.DEBUG)
 
     # Log one message for every log level.
-    _LOG.log(LOGLEVEL_FATAL, 'An irrecoverable error has occurred!')
-    _LOG.critical('Something important has happened!')
+    _LOG.critical('Something terrible has happened!')
     _LOG.error('There was an error on our last operation')
     _LOG.warning('Looks like something is amiss; consider investigating')
     _LOG.info('The operation went as expected')
@@ -73,47 +63,17 @@
 
 
 def _setup_handler(handler: logging.Handler, formatter: logging.Formatter,
-                   level: Union[str, int], logger: logging.Logger) -> None:
+                   level: int) -> None:
     handler.setLevel(level)
     handler.setFormatter(formatter)
-    logger.addHandler(handler)
+    logging.getLogger().addHandler(handler)
 
 
-def install(level: Union[str, int] = logging.INFO,
+def install(level: int = logging.INFO,
             use_color: bool = None,
             hide_timestamp: bool = False,
-            log_file: Union[str, Path] = None,
-            logger: Optional[logging.Logger] = None) -> None:
-    """Configures the system logger for the default pw command log format.
-
-    If you have Python loggers separate from the root logger you can use
-    `pw_cli.log.install` to get the Pigweed log formatting there too. For
-    example: ::
-
-        import logging
-
-        import pw_cli.log
-
-        pw_cli.log.install(
-            level=logging.INFO,
-            use_color=True,
-            hide_timestamp=False,
-            log_file=(Path.home() / 'logs.txt'),
-            logger=logging.getLogger(__package__),
-        )
-
-    Args:
-      level: The logging level to apply. Default: `logging.INFO`.
-      use_color: When `True` include ANSI escape sequences to colorize log
-          messages.
-      hide_timestamp: When `True` omit timestamps from the log formatting.
-      log_file: File to save logs into.
-      logger: Python Logger instance to install Pigweed formatting into.
-          Defaults to the Python root logger: `logging.getLogger()`.
-
-    """
-    if not logger:
-        logger = logging.getLogger()
+            log_file: Union[str, Path] = None) -> None:
+    """Configures the system logger for the default pw command log format."""
 
     colors = pw_cli.color.colors(use_color)
 
@@ -131,18 +91,15 @@
     formatter = logging.Formatter(timestamp_fmt + '%(levelname)s %(message)s',
                                   '%Y%m%d %H:%M:%S')
 
-    # Set the log level on the root logger to NOTSET, so that all logs
+    # Set the log level on the root logger to 1, so logs that all logs
     # propagated from child loggers are handled.
-    logging.getLogger().setLevel(logging.NOTSET)
+    logging.getLogger().setLevel(1)
 
     # Always set up the stderr handler, even if it isn't used.
-    _setup_handler(_STDERR_HANDLER, formatter, level, logger)
+    _setup_handler(_STDERR_HANDLER, formatter, level)
 
     if log_file:
-        # Set utf-8 encoding for the log file. Encoding errors may come up on
-        # Windows if the default system encoding is set to cp1250.
-        _setup_handler(logging.FileHandler(log_file, encoding='utf-8'),
-                       formatter, level, logger)
+        _setup_handler(logging.FileHandler(log_file), formatter, level)
         # Since we're using a file, filter logs out of the stderr handler.
         _STDERR_HANDLER.setLevel(logging.CRITICAL + 1)
 
diff --git a/pw_cli/py/pw_cli/plugins.py b/pw_cli/py/pw_cli/plugins.py
index ebaf3fb..d264a39 100644
--- a/pw_cli/py/pw_cli/plugins.py
+++ b/pw_cli/py/pw_cli/plugins.py
@@ -59,7 +59,7 @@
 
 
 def _get_module(member: object) -> types.ModuleType:
-    """Gets the module or a fake module if the module isn't found."""
+    """Gets the module or a dummy module if the module isn't found."""
     module = inspect.getmodule(member)
     return module if module else types.ModuleType('<unknown>')
 
diff --git a/pw_cli/py/pw_cli/process.py b/pw_cli/py/pw_cli/process.py
index 2366fc0..a3e686b 100644
--- a/pw_cli/py/pw_cli/process.py
+++ b/pw_cli/py/pw_cli/process.py
@@ -18,7 +18,7 @@
 import os
 import shlex
 import tempfile
-from typing import IO, Tuple, Union, Optional
+from typing import IO, Tuple, Union
 
 import pw_cli.color
 import pw_cli.log
@@ -78,8 +78,7 @@
 
 async def run_async(program: str,
                     *args: str,
-                    log_output: bool = False,
-                    timeout: Optional[float] = None) -> CompletedProcess:
+                    log_output: bool = False) -> CompletedProcess:
     """Runs a command, capturing and optionally logging its output.
 
     Returns a CompletedProcess with details from the process.
@@ -103,17 +102,10 @@
             stderr=asyncio.subprocess.STDOUT,
             env=env)
 
-    try:
-        await asyncio.wait_for(process.wait(), timeout)
-    except asyncio.TimeoutError:
-        _LOG.error('%s timed out after %d seconds', program, timeout)
-        process.kill()
-        await process.wait()
-
-    if process.returncode:
-        _LOG.error('%s exited with status %d', program, process.returncode)
-    else:
+    if await process.wait() == 0:
         _LOG.info('%s exited successfully', program)
+    else:
+        _LOG.error('%s exited with status %d', program, process.returncode)
 
     return CompletedProcess(process, output)
 
diff --git a/pw_cli/py/pw_cli/pw_command_plugins.py b/pw_cli/py/pw_cli/pw_command_plugins.py
index 845e007..a5c20df 100644
--- a/pw_cli/py/pw_cli/pw_command_plugins.py
+++ b/pw_cli/py/pw_cli/pw_command_plugins.py
@@ -30,8 +30,6 @@
 
     # Register these by name to avoid circular dependencies.
     registry.register_by_name('doctor', 'pw_doctor.doctor', 'main')
-    registry.register_by_name('python-packages',
-                              'pw_env_setup.python_packages', 'main')
     registry.register_by_name('format', 'pw_presubmit.format_code', 'main')
     registry.register_by_name('logdemo', 'pw_cli.log', 'main')
     registry.register_by_name('module-check', 'pw_module.check', 'main')
diff --git a/pw_cli/py/pw_cli/requires.py b/pw_cli/py/pw_cli/requires.py
index bf67dd3..5ff121f 100755
--- a/pw_cli/py/pw_cli/requires.py
+++ b/pw_cli/py/pw_cli/requires.py
@@ -39,17 +39,10 @@
 HELPER_PROJECT = 'requires-helper'
 HELPER_REPO = 'sso://{}/{}'.format(HELPER_GERRIT, HELPER_PROJECT)
 
-# Pass checks that look for "DO NOT ..." and block submission.
-_DNS = ' '.join((
-    'DO',
-    'NOT',
-    'SUBMIT',
-))
-
 # Subset of the output from pushing to Gerrit.
 DEFAULT_OUTPUT = f'''
 remote:
-remote:   https://{HELPER_GERRIT}-review.git.corp.google.com/c/{HELPER_PROJECT}/+/123456789 {_DNS} [NEW]
+remote:   https://{HELPER_GERRIT}-review.git.corp.google.com/c/{HELPER_PROJECT}/+/123456789 DO NOT SUBMIT [NEW]
 remote:
 '''.strip()
 
@@ -110,7 +103,7 @@
     _run_command(['git', 'add', path], cwd=requires_dir)
 
     commit_message = [
-        f'{_DNS} {change_id[0:10]}',
+        f'DO NOT SUBMIT {change_id[0:10]}',
         '',
         f'Change-Id: I{change_id}',
     ]
@@ -131,7 +124,7 @@
     output = DEFAULT_OUTPUT
     if push:
         res = _run_command(
-            ['git', 'push', HELPER_REPO, '+HEAD:refs/for/main'],
+            ['git', 'push', HELPER_REPO, '+HEAD:refs/for/master'],
             cwd=requires_dir,
         )
         output = res.stderr.decode()
diff --git a/pw_cli/py/pyproject.toml b/pw_cli/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/pw_cli/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/pw_cli/py/setup.cfg b/pw_cli/py/setup.cfg
deleted file mode 100644
index be83438..0000000
--- a/pw_cli/py/setup.cfg
+++ /dev/null
@@ -1,29 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pw_cli
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Pigweed swiss-army knife
-
-[options]
-packages = find:
-zip_safe = False
-
-[options.entry_points]
-console_scripts = pw = pw_cli.__main__:main
-
-[options.package_data]
-pw_cli = py.typed
diff --git a/pw_cli/py/setup.py b/pw_cli/py/setup.py
index 73ab745..19827b3 100644
--- a/pw_cli/py/setup.py
+++ b/pw_cli/py/setup.py
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2019 The Pigweed Authors
 #
 # 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
@@ -15,4 +15,14 @@
 
 import setuptools  # type: ignore
 
-setuptools.setup()  # Package definition in setup.cfg
+setuptools.setup(
+    name='pw_cli',
+    version='0.0.1',
+    author='Pigweed Authors',
+    author_email='pigweed-developers@googlegroups.com',
+    description='Pigweed swiss-army knife',
+    packages=setuptools.find_packages(),
+    package_data={'pw_cli': ['py.typed']},
+    zip_safe=False,
+    entry_points={'console_scripts': ['pw = pw_cli.__main__:main']},
+)
diff --git a/pw_console/BUILD.gn b/pw_console/BUILD.gn
deleted file mode 100644
index 712e74a..0000000
--- a/pw_console/BUILD.gn
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_docgen/docs.gni")
-
-pw_doc_group("docs") {
-  inputs = [
-    "images/calculator_plugin.png",
-    "images/clock_plugin1.png",
-    "images/clock_plugin2.png",
-    "images/command_runner_main_menu.svg",
-    "images/pw_system_boot.png",
-    "images/python_completion.png",
-    "images/serial_debug.svg",
-    "py/pw_console/plugins/calc_pane.py",
-    "py/pw_console/plugins/clock_pane.py",
-  ]
-  sources = [
-    "docs.rst",
-    "embedding.rst",
-    "internals.rst",
-    "plugins.rst",
-    "py/pw_console/docs/user_guide.rst",
-    "testing.rst",
-  ]
-}
diff --git a/pw_console/OWNERS b/pw_console/OWNERS
deleted file mode 100644
index ca011e8..0000000
--- a/pw_console/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-tonymd@google.com
diff --git a/pw_console/docs.rst b/pw_console/docs.rst
deleted file mode 100644
index 0c7ffc3..0000000
--- a/pw_console/docs.rst
+++ /dev/null
@@ -1,62 +0,0 @@
-.. _module-pw_console:
-
-----------
-pw_console
-----------
-
-:bdg-primary:`host`
-:bdg-secondary:`Python`
-:bdg-success:`stable`
-
-The Pigweed Console provides a Python repl (read eval print loop) using
-`ptpython`_ and a log message viewer in a single-window terminal based
-interface. It is designed to be a replacement for `IPython's embed()`_ function.
-
-.. figure:: images/pw_system_boot.png
-  :alt: Pigweed Console screenshot with serial debug log messages.
-
-Features
-========
-
-``pw_console`` aims to be a complete solution for interacting with hardware
-devices using :ref:`module-pw_rpc` over a :ref:`module-pw_hdlc` transport.
-
-- Interactive Python repl and log viewer in a single terminal window. This
-  provides interactive RPC sending while the log viewer provides immediate
-  feedback on device status.
-
-  .. figure:: images/python_completion.png
-    :alt: Pigweed Console screenshot showing RPC Python repl completions.
-
-- Easily embeddable within a project's own custom console. This should allow
-  users to define their own transport layer.
-
-- Log viewer with searching and filtering.
-
-Contributing
-============
-
-- All code submissions to ``pw_console`` require running the
-  :ref:`module-pw_console-testing`.
-
-- Commit messages should include a ``Testing:`` line with the steps that were
-  manually run.
-
-Guides
-======
-
-.. toctree::
-  :maxdepth: 1
-
-  py/pw_console/docs/user_guide
-  embedding
-  plugins
-  testing
-  internals
-
-
-.. _IPython's embed(): https://ipython.readthedocs.io/en/stable/interactive/reference.html#embedding
-.. _IPython: https://ipython.readthedocs.io/
-.. _prompt_toolkit: https://python-prompt-toolkit.readthedocs.io/
-.. _ptpython: https://github.com/prompt-toolkit/ptpython/
-
diff --git a/pw_console/embedding.rst b/pw_console/embedding.rst
deleted file mode 100644
index aad4d10..0000000
--- a/pw_console/embedding.rst
+++ /dev/null
@@ -1,148 +0,0 @@
-.. _module-pw_console-embedding:
-
-===============
-Embedding Guide
-===============
-
--------------
-Using embed()
--------------
-``pw console`` is invoked by calling ``PwConsoleEmbed().embed()`` in your
-own Python script. For a complete example of an embedded device console script see
-:bdg-link-primary-line:`pw_system/py/pw_system/console.py <https://cs.opensource.google/pigweed/pigweed/+/main:pw_system/py/pw_system/console.py>`.
-
-.. automodule:: pw_console.embed
-    :members: PwConsoleEmbed
-    :undoc-members:
-    :show-inheritance:
-
-.. _module-pw_console-embedding-logstore:
-
-.. autoclass:: pw_console.log_store.LogStore
-    :members: __init__
-    :undoc-members:
-    :show-inheritance:
-
-.. _module-pw_console-embedding-plugins:
-
-Adding Plugins
-==============
-User plugin instances are created before starting-up and passed to the Pigweed
-Console embed instance. Typically, a console is started by creating a
-``PwConsoleEmbed()`` instance, calling customization functions, then calling
-``.embed()`` as shown in `Using embed()`_. Adding plugins functions similarly by
-calling ``add_top_toolbar``, ``add_bottom_toolbar`` or
-``add_window_plugin``. For example:
-
-.. code-block:: python
-
-   # Create plugin instances
-   user_toolbar1 = DeviceStatusToolbar(device=client.client.channel(1))
-   user_toolbar2 = BandwithToolbar()
-   user_device_window = CustomWindowPlugin()
-
-   console = PwConsoleEmbed(
-       global_vars=local_variables,
-       loggers={
-           'Device Logs': [logging.getLogger('rpc_device')],
-           'Host Logs': [logging.getLogger()],
-       },
-       ...
-   )
-
-   # Add toolbar plugins
-   console.add_top_toolbar(user_toolbar1)
-   console.add_bottom_toolbar(user_toolbar2)
-
-   # Add Window plugins
-   console.add_window_plugin(user_device_window)
-
-   # Start the console
-   console.embed()
-
--------------------
-Adding Log Metadata
--------------------
-``pw_console`` can display log messages in a table with justified columns for
-metadata fields provided by :ref:`module-pw_log_tokenized`.
-
-It is also possible to manually add values that should be displayed in columns
-using the ``extra`` keyword argument when logging from Python. See the `Python's
-logging documentation`_ for how ``extra`` works. A dict of name, value pairs can
-be passed in as the ``extra_metadata_fields`` variable. For example, the
-following code will create a log message with two custom columns titled
-``module`` and ``timestamp``.
-
-.. code-block:: python
-
-  import logging
-
-  LOG = logging.getLogger('log_source_1')
-
-  LOG.info(
-      'Hello there!',
-      extra={
-          'extra_metadata_fields': {
-              'module': 'cool',
-              'timestamp': 1.2345,
-          }
-      }
-  )
-
-
----------------------
-Debugging Serial Data
----------------------
-``pw_console`` is often used to communicate with devices using `pySerial
-<https://pythonhosted.org/pyserial/>`_ and it may be necessary to monitor the
-raw data flowing over the wire to help with debugging. ``pw_console`` provides a
-simple wrapper for a pySerial instances that log data for each read and write
-call.
-
-.. code-block:: python
-
-   # Instead of 'import serial' use this import:
-   from pw_console.pyserial_wrapper import SerialWithLogging
-
-   serial_device = SerialWithLogging('/dev/ttyUSB0', 115200, timeout=1)
-
-With the above example each ``serial_device.read`` and ``write`` call will
-create a log message to the ``pw_console.serial_debug_logger`` Python
-logger. This logger can then be included as a log window pane in the
-``PwConsoleEmbed()`` call.
-
-.. code-block:: python
-
-   import logging
-   from pw_console import PwConsoleEmbed
-
-   console = PwConsoleEmbed(
-       global_vars=globals(),
-       local_vars=locals(),
-       loggers={
-           'Host Logs': [
-               # Root Python logger
-               logging.getLogger(''),
-               # Your current Python package logger.
-               logging.getLogger(__package__)
-           ],
-           'Device Logs': [
-               logging.getLogger('usb_gadget')
-           ],
-           'Serial Debug': [
-               # New log window to display serial read and writes
-               logging.getLogger('pw_console.serial_debug_logger')
-           ],
-       },
-       app_title='CoolConsole',
-   )
-   # Then run the console with:
-   console.embed()
-
-.. figure:: images/serial_debug.svg
-  :alt: Serial debug pw_console screenshot.
-
-  Screenshot of issuing an Echo RPC with serial debug logging.
-
-
-.. _Python's logging documentation: https://docs.python.org/3/library/logging.html#logging.Logger.debug
diff --git a/pw_console/images/calculator_plugin.png b/pw_console/images/calculator_plugin.png
deleted file mode 100644
index 84f6414..0000000
--- a/pw_console/images/calculator_plugin.png
+++ /dev/null
Binary files differ
diff --git a/pw_console/images/clock_plugin1.png b/pw_console/images/clock_plugin1.png
deleted file mode 100644
index 1924024..0000000
--- a/pw_console/images/clock_plugin1.png
+++ /dev/null
Binary files differ
diff --git a/pw_console/images/clock_plugin2.png b/pw_console/images/clock_plugin2.png
deleted file mode 100644
index 5d982f6..0000000
--- a/pw_console/images/clock_plugin2.png
+++ /dev/null
Binary files differ
diff --git a/pw_console/images/command_runner_main_menu.svg b/pw_console/images/command_runner_main_menu.svg
deleted file mode 100644
index 4ef98ef..0000000
--- a/pw_console/images/command_runner_main_menu.svg
+++ /dev/null
@@ -1,1984 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1428pt" height="682pt" viewBox="0 0 1428 682" version="1.1">
-<defs>
-<g>
-<symbol overflow="visible" id="glyph0-0">
-<path style="stroke:none;" d="M 3.203125 -1.046875 L 10.796875 -1.046875 L 10.796875 -19.8125 L 3.203125 -19.8125 Z M 1.265625 0.875 L 1.265625 -21.734375 L 12.734375 -21.734375 L 12.734375 0.875 Z M 7.59375 -9.09375 L 7.59375 -8.375 L 5.671875 -8.375 L 5.671875 -9.109375 C 5.671875 -9.796875 5.878906 -10.441406 6.296875 -11.046875 L 7.828125 -13.296875 C 8.097656 -13.679688 8.234375 -14.035156 8.234375 -14.359375 C 8.234375 -14.816406 8.097656 -15.207031 7.828125 -15.53125 C 7.566406 -15.851562 7.210938 -16.015625 6.765625 -16.015625 C 5.960938 -16.015625 5.34375 -15.484375 4.90625 -14.421875 L 3.34375 -15.34375 C 4.1875 -16.957031 5.332031 -17.765625 6.78125 -17.765625 C 7.75 -17.765625 8.550781 -17.4375 9.1875 -16.78125 C 9.820312 -16.132812 10.140625 -15.320312 10.140625 -14.34375 C 10.140625 -13.644531 9.898438 -12.941406 9.421875 -12.234375 L 7.8125 -9.890625 C 7.664062 -9.679688 7.59375 -9.414062 7.59375 -9.09375 Z M 6.78125 -6.75 C 7.144531 -6.75 7.457031 -6.617188 7.71875 -6.359375 C 7.988281 -6.109375 8.125 -5.796875 8.125 -5.421875 C 8.125 -5.046875 7.988281 -4.726562 7.71875 -4.46875 C 7.457031 -4.207031 7.144531 -4.078125 6.78125 -4.078125 C 6.394531 -4.078125 6.070312 -4.207031 5.8125 -4.46875 C 5.5625 -4.726562 5.4375 -5.046875 5.4375 -5.421875 C 5.4375 -5.796875 5.5625 -6.109375 5.8125 -6.359375 C 6.070312 -6.617188 6.394531 -6.75 6.78125 -6.75 Z M 6.78125 -6.75 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-1">
-<path style="stroke:none;" d=""/>
-</symbol>
-<symbol overflow="visible" id="glyph0-2">
-<path style="stroke:none;" d="M 11.546875 1.8125 L 4.296875 1.8125 L 4.296875 -22.671875 L 11.546875 -22.671875 L 11.546875 -20.609375 L 6.625 -20.609375 L 6.625 -0.265625 L 11.546875 -0.265625 Z M 11.546875 1.8125 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-3">
-<path style="stroke:none;" d="M 12.8125 -18.875 L 5.25 -18.875 L 5.25 -12.125 L 11.328125 -12.125 L 11.328125 -10.140625 L 5.25 -10.140625 L 5.25 0 L 2.921875 0 L 2.921875 -20.859375 L 12.8125 -20.859375 Z M 12.8125 -18.875 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-4">
-<path style="stroke:none;" d="M 8.484375 -20.734375 C 8.484375 -20.234375 8.3125 -19.804688 7.96875 -19.453125 C 7.632812 -19.109375 7.210938 -18.9375 6.703125 -18.9375 C 6.191406 -18.9375 5.765625 -19.109375 5.421875 -19.453125 C 5.078125 -19.804688 4.90625 -20.234375 4.90625 -20.734375 C 4.90625 -21.222656 5.078125 -21.640625 5.421875 -21.984375 C 5.765625 -22.335938 6.191406 -22.515625 6.703125 -22.515625 C 7.210938 -22.515625 7.632812 -22.335938 7.96875 -21.984375 C 8.3125 -21.640625 8.484375 -21.222656 8.484375 -20.734375 Z M 12 0 L 2.25 0 L 2.25 -1.984375 L 5.96875 -1.984375 L 5.96875 -13.5 L 3.296875 -13.5 L 3.296875 -15.484375 L 8.296875 -15.484375 L 8.296875 -1.984375 L 12 -1.984375 Z M 12 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-5">
-<path style="stroke:none;" d="M 11.875 0 L 2.125 0 L 2.125 -1.984375 L 5.84375 -1.984375 L 5.84375 -18.875 L 3.1875 -18.875 L 3.1875 -20.859375 L 8.171875 -20.859375 L 8.171875 -1.984375 L 11.875 -1.984375 Z M 11.875 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-6">
-<path style="stroke:none;" d="M 12.203125 -7.28125 L 4.109375 -7.28125 L 4.109375 -6.8125 C 4.109375 -4.8125 4.398438 -3.484375 4.984375 -2.828125 C 5.566406 -2.171875 6.359375 -1.84375 7.359375 -1.84375 C 8.628906 -1.84375 9.644531 -2.488281 10.40625 -3.78125 L 12.296875 -2.578125 C 11.109375 -0.679688 9.410156 0.265625 7.203125 0.265625 C 5.628906 0.265625 4.332031 -0.296875 3.3125 -1.421875 C 2.289062 -2.554688 1.78125 -4.351562 1.78125 -6.8125 L 1.78125 -8.6875 C 1.78125 -11.132812 2.289062 -12.925781 3.3125 -14.0625 C 4.332031 -15.195312 5.5625 -15.765625 7 -15.765625 C 8.53125 -15.765625 9.78125 -15.238281 10.75 -14.1875 C 11.71875 -13.144531 12.203125 -11.441406 12.203125 -9.078125 Z M 9.890625 -9.328125 C 9.890625 -10.898438 9.617188 -12.015625 9.078125 -12.671875 C 8.535156 -13.328125 7.84375 -13.65625 7 -13.65625 C 6.195312 -13.65625 5.515625 -13.328125 4.953125 -12.671875 C 4.390625 -12.015625 4.109375 -10.898438 4.109375 -9.328125 Z M 9.890625 -9.328125 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-7">
-<path style="stroke:none;" d="M 9.703125 1.8125 L 2.46875 1.8125 L 2.46875 -0.265625 L 7.375 -0.265625 L 7.375 -20.609375 L 2.46875 -20.609375 L 2.46875 -22.671875 L 9.703125 -22.671875 Z M 9.703125 1.8125 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-8">
-<path style="stroke:none;" d="M 12.203125 0 L 2.578125 0 L 2.578125 -20.859375 L 12.203125 -20.859375 L 12.203125 -18.875 L 4.90625 -18.875 L 4.90625 -12.125 L 10.296875 -12.125 L 10.296875 -10.140625 L 4.90625 -10.140625 L 4.90625 -1.984375 L 12.203125 -1.984375 Z M 12.203125 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-9">
-<path style="stroke:none;" d="M 1.59375 -8.8125 C 1.59375 -11.28125 2.03125 -13.054688 2.90625 -14.140625 C 3.789062 -15.222656 4.847656 -15.765625 6.078125 -15.765625 C 7.671875 -15.765625 8.875 -15.046875 9.6875 -13.609375 L 9.6875 -20.859375 L 12 -20.859375 L 12 0 L 9.6875 0 L 9.6875 -1.859375 C 8.875 -0.441406 7.671875 0.265625 6.078125 0.265625 C 4.847656 0.265625 3.789062 -0.269531 2.90625 -1.34375 C 2.03125 -2.425781 1.59375 -4.203125 1.59375 -6.671875 Z M 3.984375 -6.671875 C 3.984375 -4.878906 4.207031 -3.625 4.65625 -2.90625 C 5.113281 -2.195312 5.78125 -1.84375 6.65625 -1.84375 C 8.1875 -1.84375 9.195312 -2.867188 9.6875 -4.921875 L 9.6875 -10.4375 C 9.207031 -12.582031 8.195312 -13.65625 6.65625 -13.65625 C 5.78125 -13.65625 5.113281 -13.296875 4.65625 -12.578125 C 4.207031 -11.867188 3.984375 -10.613281 3.984375 -8.8125 Z M 3.984375 -6.671875 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-10">
-<path style="stroke:none;" d="M 12.953125 -2.15625 C 11.867188 -0.539062 10.414062 0.265625 8.59375 0.265625 C 7.394531 0.265625 6.382812 -0.140625 5.5625 -0.953125 C 4.75 -1.773438 4.34375 -2.984375 4.34375 -4.578125 L 4.34375 -13.5 L 1.75 -13.5 L 1.75 -15.484375 L 4.34375 -15.484375 L 4.34375 -20.859375 L 6.671875 -20.859375 L 6.671875 -15.484375 L 11.890625 -15.484375 L 11.890625 -13.5 L 6.671875 -13.5 L 6.671875 -4.4375 C 6.671875 -3.582031 6.851562 -2.9375 7.21875 -2.5 C 7.582031 -2.0625 8.085938 -1.84375 8.734375 -1.84375 C 9.804688 -1.84375 10.625 -2.378906 11.1875 -3.453125 Z M 12.953125 -2.15625 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-11">
-<path style="stroke:none;" d="M 5.6875 0 L 3.109375 -9.609375 C 1.941406 -13.910156 1.359375 -17.273438 1.359375 -19.703125 L 1.359375 -20.859375 L 3.6875 -20.859375 L 3.6875 -19.703125 C 3.6875 -17.691406 4.125 -14.804688 5 -11.046875 L 7 -2.609375 L 9 -11.046875 C 9.875 -14.804688 10.3125 -17.691406 10.3125 -19.703125 L 10.3125 -20.859375 L 12.640625 -20.859375 L 12.640625 -19.703125 C 12.640625 -17.273438 12.054688 -13.910156 10.890625 -9.609375 L 8.3125 0 Z M 5.6875 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-12">
-<path style="stroke:none;" d="M 9.578125 -1.28125 L 10.125 -6.296875 C 10.351562 -8.390625 10.46875 -10.53125 10.46875 -12.71875 L 10.46875 -15.484375 L 12.75 -15.484375 L 12.75 -13.421875 C 12.75 -11.554688 12.484375 -9.179688 11.953125 -6.296875 L 10.796875 0 L 8.609375 0 L 7 -6.96875 L 5.390625 0 L 3.203125 0 L 2.046875 -6.296875 C 1.523438 -9.179688 1.265625 -11.554688 1.265625 -13.421875 L 1.265625 -15.484375 L 3.53125 -15.484375 L 3.53125 -12.71875 C 3.53125 -10.53125 3.644531 -8.390625 3.875 -6.296875 L 4.421875 -1.28125 L 6.265625 -9.25 L 7.734375 -9.25 Z M 9.578125 -1.28125 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-13">
-<path style="stroke:none;" d="M 9.734375 -1.265625 L 10.09375 -6.1875 C 10.226562 -8.007812 10.296875 -10.070312 10.296875 -12.375 L 10.296875 -20.859375 L 12.625 -20.859375 L 12.625 -15.828125 C 12.625 -12.398438 12.375 -9.1875 11.875 -6.1875 L 10.90625 0 L 8.71875 0 L 7 -8.078125 L 5.28125 0 L 3.09375 0 L 2.125 -6.1875 C 1.625 -9.1875 1.375 -12.398438 1.375 -15.828125 L 1.375 -20.859375 L 3.703125 -20.859375 L 3.703125 -12.375 C 3.703125 -10.070312 3.769531 -8.007812 3.90625 -6.1875 L 4.265625 -1.265625 L 6.265625 -10.671875 L 7.734375 -10.671875 Z M 9.734375 -1.265625 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-14">
-<path style="stroke:none;" d="M 12.015625 0 L 9.6875 0 L 9.6875 -10.03125 C 9.6875 -11.375 9.476562 -12.304688 9.0625 -12.828125 C 8.644531 -13.359375 8.070312 -13.625 7.34375 -13.625 C 5.8125 -13.625 4.800781 -12.570312 4.3125 -10.46875 L 4.3125 0 L 1.984375 0 L 1.984375 -15.484375 L 4.3125 -15.484375 L 4.3125 -13.5 C 5.300781 -15.007812 6.503906 -15.765625 7.921875 -15.765625 C 9.066406 -15.765625 10.035156 -15.335938 10.828125 -14.484375 C 11.617188 -13.628906 12.015625 -12.191406 12.015625 -10.171875 Z M 12.015625 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-15">
-<path style="stroke:none;" d="M 12.21875 -6.703125 C 12.21875 -4.328125 11.726562 -2.570312 10.75 -1.4375 C 9.78125 -0.300781 8.53125 0.265625 7 0.265625 C 5.46875 0.265625 4.21875 -0.300781 3.25 -1.4375 C 2.28125 -2.570312 1.796875 -4.328125 1.796875 -6.703125 L 1.796875 -8.8125 C 1.796875 -11.175781 2.28125 -12.925781 3.25 -14.0625 C 4.21875 -15.195312 5.46875 -15.765625 7 -15.765625 C 8.53125 -15.765625 9.78125 -15.195312 10.75 -14.0625 C 11.726562 -12.925781 12.21875 -11.175781 12.21875 -8.8125 Z M 9.90625 -6.703125 L 9.90625 -8.8125 C 9.90625 -10.632812 9.628906 -11.894531 9.078125 -12.59375 C 8.535156 -13.300781 7.84375 -13.65625 7 -13.65625 C 6.15625 -13.65625 5.460938 -13.300781 4.921875 -12.59375 C 4.378906 -11.894531 4.109375 -10.632812 4.109375 -8.8125 L 4.109375 -6.703125 C 4.109375 -4.867188 4.378906 -3.597656 4.921875 -2.890625 C 5.460938 -2.191406 6.15625 -1.84375 7 -1.84375 C 7.84375 -1.84375 8.535156 -2.191406 9.078125 -2.890625 C 9.628906 -3.597656 9.90625 -4.867188 9.90625 -6.703125 Z M 9.90625 -6.703125 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-16">
-<path style="stroke:none;" d="M 7.0625 -1.796875 C 8.019531 -1.796875 8.75 -2.023438 9.25 -2.484375 C 9.75 -2.941406 10 -3.5 10 -4.15625 C 10 -5.0625 9.445312 -5.765625 8.34375 -6.265625 L 5.359375 -7.625 C 3.503906 -8.5 2.578125 -9.75 2.578125 -11.375 C 2.578125 -12.644531 3.03125 -13.691406 3.9375 -14.515625 C 4.851562 -15.347656 6.035156 -15.765625 7.484375 -15.765625 C 9.546875 -15.765625 11.128906 -14.8125 12.234375 -12.90625 L 10.34375 -11.765625 C 9.757812 -13.054688 8.804688 -13.703125 7.484375 -13.703125 C 6.691406 -13.703125 6.0625 -13.5 5.59375 -13.09375 C 5.132812 -12.695312 4.90625 -12.203125 4.90625 -11.609375 C 4.90625 -10.816406 5.421875 -10.175781 6.453125 -9.6875 L 9.265625 -8.375 C 11.296875 -7.425781 12.3125 -5.984375 12.3125 -4.046875 C 12.3125 -2.898438 11.835938 -1.894531 10.890625 -1.03125 C 9.941406 -0.164062 8.65625 0.265625 7.03125 0.265625 C 4.75 0.265625 2.96875 -0.789062 1.6875 -2.90625 L 3.59375 -4.0625 C 4.351562 -2.550781 5.507812 -1.796875 7.0625 -1.796875 Z M 7.0625 -1.796875 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-17">
-<path style="stroke:none;" d="M 12.015625 0 L 9.6875 0 L 9.6875 -10.4375 L 4.3125 -10.4375 L 4.3125 0 L 1.984375 0 L 1.984375 -20.859375 L 4.3125 -20.859375 L 4.3125 -12.421875 L 9.6875 -12.421875 L 9.6875 -20.859375 L 12.015625 -20.859375 Z M 12.015625 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-18">
-<path style="stroke:none;" d="M 12.421875 -6.65625 C 12.421875 -4.21875 11.953125 -2.453125 11.015625 -1.359375 C 10.085938 -0.273438 9.054688 0.265625 7.921875 0.265625 C 6.328125 0.265625 5.128906 -0.457031 4.328125 -1.90625 L 4.328125 4.828125 L 2 4.828125 L 2 -15.484375 L 4.328125 -15.484375 L 4.328125 -13.640625 C 5.128906 -15.054688 6.328125 -15.765625 7.921875 -15.765625 C 9.054688 -15.765625 10.085938 -15.222656 11.015625 -14.140625 C 11.953125 -13.066406 12.421875 -11.289062 12.421875 -8.8125 Z M 10.015625 -8.8125 C 10.015625 -10.625 9.765625 -11.882812 9.265625 -12.59375 C 8.765625 -13.300781 8.125 -13.65625 7.34375 -13.65625 C 5.8125 -13.65625 4.804688 -12.59375 4.328125 -10.46875 L 4.328125 -5.046875 C 4.796875 -2.910156 5.800781 -1.84375 7.34375 -1.84375 C 8.125 -1.84375 8.765625 -2.203125 9.265625 -2.921875 C 9.765625 -3.640625 10.015625 -4.882812 10.015625 -6.65625 Z M 10.015625 -8.8125 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-19">
-<path style="stroke:none;" d="M 12.953125 -3.09375 C 11.648438 -0.851562 9.878906 0.265625 7.640625 0.265625 C 6.128906 0.265625 4.851562 -0.238281 3.8125 -1.25 C 2.769531 -2.269531 2.25 -4.132812 2.25 -6.84375 L 2.25 -14.046875 C 2.25 -16.679688 2.769531 -18.523438 3.8125 -19.578125 C 4.851562 -20.640625 6.117188 -21.171875 7.609375 -21.171875 C 9.953125 -21.171875 11.6875 -20.039062 12.8125 -17.78125 L 10.90625 -16.625 C 10.21875 -18.238281 9.125 -19.046875 7.625 -19.046875 C 6.757812 -19.046875 6.035156 -18.726562 5.453125 -18.09375 C 4.878906 -17.457031 4.59375 -16.109375 4.59375 -14.046875 L 4.59375 -6.84375 C 4.59375 -4.78125 4.878906 -3.429688 5.453125 -2.796875 C 6.035156 -2.160156 6.757812 -1.84375 7.625 -1.84375 C 9.125 -1.84375 10.265625 -2.6875 11.046875 -4.375 Z M 12.953125 -3.09375 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-20">
-<path style="stroke:none;" d="M 12.453125 -18.875 L 8.15625 -18.875 L 8.15625 0 L 5.84375 0 L 5.84375 -18.875 L 1.546875 -18.875 L 1.546875 -20.859375 L 12.453125 -20.859375 Z M 12.453125 -18.875 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-21">
-<path style="stroke:none;" d="M 12.328125 0 L 10.015625 0 L 10.015625 -5.515625 C 10.015625 -9.023438 10.148438 -13.148438 10.421875 -17.890625 L 7.71875 -9.25 L 6.28125 -9.25 L 3.578125 -17.890625 C 3.859375 -13.148438 4 -9.023438 4 -5.515625 L 4 0 L 1.671875 0 L 1.671875 -20.859375 L 4 -20.859375 L 7 -12.203125 L 10.015625 -20.859375 L 12.328125 -20.859375 Z M 12.328125 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-22">
-<path style="stroke:none;" d="M 12.53125 -4.875 C 12.53125 -3.457031 12.054688 -2.242188 11.109375 -1.234375 C 10.171875 -0.234375 8.867188 0.265625 7.203125 0.265625 C 4.578125 0.265625 2.660156 -0.878906 1.453125 -3.171875 L 3.171875 -4.46875 C 4.078125 -2.71875 5.414062 -1.84375 7.1875 -1.84375 C 8.007812 -1.84375 8.71875 -2.09375 9.3125 -2.59375 C 9.90625 -3.101562 10.203125 -3.859375 10.203125 -4.859375 C 10.203125 -5.671875 9.691406 -6.554688 8.671875 -7.515625 L 4.1875 -11.703125 C 2.9375 -12.878906 2.3125 -14.359375 2.3125 -16.140625 C 2.3125 -17.554688 2.769531 -18.75 3.6875 -19.71875 C 4.613281 -20.6875 5.878906 -21.171875 7.484375 -21.171875 C 9.566406 -21.171875 11.253906 -20.21875 12.546875 -18.3125 L 10.765625 -16.90625 C 9.992188 -18.332031 8.90625 -19.046875 7.5 -19.046875 C 6.6875 -19.046875 6.007812 -18.8125 5.46875 -18.34375 C 4.925781 -17.882812 4.65625 -17.148438 4.65625 -16.140625 C 4.65625 -15.109375 5.0625 -14.210938 5.875 -13.453125 L 10.34375 -9.296875 C 11.800781 -7.910156 12.53125 -6.4375 12.53125 -4.875 Z M 12.53125 -4.875 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-23">
-<path style="stroke:none;" d="M 11.671875 0 L 9.359375 0 L 9.359375 -1.75 C 8.367188 -0.40625 7.125 0.265625 5.625 0.265625 C 4.351562 0.265625 3.328125 -0.164062 2.546875 -1.03125 C 1.765625 -1.90625 1.375 -3 1.375 -4.3125 C 1.375 -5.65625 1.863281 -6.78125 2.84375 -7.6875 C 3.820312 -8.601562 5.253906 -9.0625 7.140625 -9.0625 L 9.359375 -9.0625 L 9.359375 -10.734375 C 9.359375 -12.679688 8.429688 -13.65625 6.578125 -13.65625 C 5.210938 -13.65625 4.191406 -13.054688 3.515625 -11.859375 L 1.75 -13.171875 C 2.96875 -14.898438 4.582031 -15.765625 6.59375 -15.765625 C 8.070312 -15.765625 9.285156 -15.335938 10.234375 -14.484375 C 11.191406 -13.640625 11.671875 -12.390625 11.671875 -10.734375 Z M 9.359375 -3.640625 L 9.359375 -7.1875 L 7.140625 -7.1875 C 6.023438 -7.1875 5.1875 -6.925781 4.625 -6.40625 C 4.0625 -5.894531 3.78125 -5.195312 3.78125 -4.3125 C 3.78125 -2.664062 4.582031 -1.84375 6.1875 -1.84375 C 7.457031 -1.84375 8.515625 -2.441406 9.359375 -3.640625 Z M 9.359375 -3.640625 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-24">
-<path style="stroke:none;" d="M 13 -12.859375 L 10.984375 -11.96875 C 10.523438 -13.09375 9.753906 -13.65625 8.671875 -13.65625 C 6.390625 -13.65625 5.25 -11.535156 5.25 -7.296875 L 5.25 0 L 2.921875 0 L 2.921875 -15.484375 L 5.25 -15.484375 L 5.25 -13 C 6.164062 -14.84375 7.425781 -15.765625 9.03125 -15.765625 C 10.894531 -15.765625 12.21875 -14.796875 13 -12.859375 Z M 13 -12.859375 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-25">
-<path style="stroke:none;" d="M 12.84375 -2.578125 C 11.65625 -0.679688 9.957031 0.265625 7.75 0.265625 C 6.164062 0.265625 4.863281 -0.296875 3.84375 -1.421875 C 2.820312 -2.554688 2.3125 -4.347656 2.3125 -6.796875 L 2.3125 -8.703125 C 2.3125 -11.140625 2.820312 -12.925781 3.84375 -14.0625 C 4.863281 -15.195312 6.164062 -15.765625 7.75 -15.765625 C 9.957031 -15.765625 11.65625 -14.8125 12.84375 -12.90625 L 10.953125 -11.71875 C 10.179688 -13.007812 9.160156 -13.65625 7.890625 -13.65625 C 6.898438 -13.65625 6.113281 -13.328125 5.53125 -12.671875 C 4.945312 -12.015625 4.65625 -10.691406 4.65625 -8.703125 L 4.65625 -6.796875 C 4.65625 -4.804688 4.945312 -3.484375 5.53125 -2.828125 C 6.113281 -2.171875 6.898438 -1.84375 7.890625 -1.84375 C 9.160156 -1.84375 10.179688 -2.488281 10.953125 -3.78125 Z M 12.84375 -2.578125 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-26">
-<path style="stroke:none;" d="M 12.015625 0 L 9.6875 0 L 9.6875 -10.015625 C 9.6875 -11.398438 9.476562 -12.351562 9.0625 -12.875 C 8.644531 -13.394531 8.070312 -13.65625 7.34375 -13.65625 C 5.8125 -13.65625 4.800781 -12.59375 4.3125 -10.46875 L 4.3125 0 L 1.984375 0 L 1.984375 -20.859375 L 4.3125 -20.859375 L 4.3125 -13.625 C 5.300781 -15.050781 6.503906 -15.765625 7.921875 -15.765625 C 9.066406 -15.765625 10.035156 -15.335938 10.828125 -14.484375 C 11.617188 -13.628906 12.015625 -12.179688 12.015625 -10.140625 Z M 12.015625 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-27">
-<path style="stroke:none;" d="M 12.015625 0 L 9.6875 0 L 9.6875 -2.015625 C 8.695312 -0.492188 7.492188 0.265625 6.078125 0.265625 C 4.929688 0.265625 3.960938 -0.164062 3.171875 -1.03125 C 2.378906 -1.90625 1.984375 -3.460938 1.984375 -5.703125 L 1.984375 -15.484375 L 4.3125 -15.484375 L 4.3125 -5.703125 C 4.3125 -4.203125 4.519531 -3.179688 4.9375 -2.640625 C 5.351562 -2.109375 5.925781 -1.84375 6.65625 -1.84375 C 8.1875 -1.84375 9.195312 -2.851562 9.6875 -4.875 L 9.6875 -15.484375 L 12.015625 -15.484375 Z M 12.015625 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-28">
-<path style="stroke:none;" d="M 12.28125 -7.296875 L 1.71875 -7.296875 L 1.71875 -9.28125 L 12.28125 -9.28125 Z M 12.28125 -7.296875 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-29">
-<path style="stroke:none;" d="M 12.8125 0 L 10.484375 0 C 10.410156 -1.132812 9.851562 -2.460938 8.8125 -3.984375 L 6.5 -7.28125 L 4.765625 -5.1875 L 4.765625 0 L 2.453125 0 L 2.453125 -20.859375 L 4.765625 -20.859375 L 4.765625 -8.046875 L 8.1875 -12.25 C 9.332031 -13.65625 9.914062 -14.734375 9.9375 -15.484375 L 12.265625 -15.484375 L 12.234375 -14.96875 C 12.191406 -14.15625 11.421875 -12.835938 9.921875 -11.015625 L 8.0625 -8.734375 L 10.46875 -5.25 C 11.9375 -3.125 12.703125 -1.554688 12.765625 -0.546875 Z M 12.8125 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-30">
-<path style="stroke:none;" d="M 4.96875 0 L 2.640625 0 L 2.640625 -20.859375 L 7.1875 -20.859375 C 9.039062 -20.859375 10.441406 -20.351562 11.390625 -19.34375 C 12.335938 -18.34375 12.8125 -17.003906 12.8125 -15.328125 C 12.8125 -13.441406 12.289062 -11.992188 11.25 -10.984375 C 10.207031 -9.972656 8.851562 -9.46875 7.1875 -9.46875 L 4.96875 -9.46875 Z M 4.96875 -11.453125 L 7.171875 -11.453125 C 8.242188 -11.453125 9.050781 -11.722656 9.59375 -12.265625 C 10.132812 -12.804688 10.40625 -13.828125 10.40625 -15.328125 C 10.40625 -16.609375 10.15625 -17.519531 9.65625 -18.0625 C 9.164062 -18.601562 8.335938 -18.875 7.171875 -18.875 L 4.96875 -18.875 Z M 4.96875 -11.453125 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-31">
-<path style="stroke:none;" d="M 12.671875 -8.5625 C 12.671875 -5.832031 12.046875 -3.722656 10.796875 -2.234375 C 9.554688 -0.742188 7.875 0 5.75 0 L 2.296875 0 L 2.296875 -20.859375 L 5.75 -20.859375 C 7.875 -20.859375 9.554688 -20.15625 10.796875 -18.75 C 12.046875 -17.351562 12.671875 -15.300781 12.671875 -12.59375 Z M 10.28125 -8.5625 L 10.28125 -12.59375 C 10.28125 -14.71875 9.875 -16.296875 9.0625 -17.328125 C 8.25 -18.359375 7.144531 -18.875 5.75 -18.875 L 4.609375 -18.875 L 4.609375 -1.984375 L 5.75 -1.984375 C 7.144531 -1.984375 8.25 -2.535156 9.0625 -3.640625 C 9.875 -4.742188 10.28125 -6.382812 10.28125 -8.5625 Z M 10.28125 -8.5625 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-32">
-<path style="stroke:none;" d="M 12.421875 -6.671875 C 12.421875 -4.203125 11.976562 -2.425781 11.09375 -1.34375 C 10.207031 -0.269531 9.148438 0.265625 7.921875 0.265625 C 6.328125 0.265625 5.128906 -0.441406 4.328125 -1.859375 L 4.328125 0 L 2 0 L 2 -20.859375 L 4.328125 -20.859375 L 4.328125 -13.609375 C 5.128906 -15.046875 6.328125 -15.765625 7.921875 -15.765625 C 9.148438 -15.765625 10.207031 -15.222656 11.09375 -14.140625 C 11.976562 -13.054688 12.421875 -11.28125 12.421875 -8.8125 Z M 10.015625 -6.671875 L 10.015625 -8.8125 C 10.015625 -10.613281 9.785156 -11.867188 9.328125 -12.578125 C 8.878906 -13.296875 8.21875 -13.65625 7.34375 -13.65625 C 5.800781 -13.65625 4.796875 -12.625 4.328125 -10.5625 L 4.328125 -4.921875 C 4.804688 -2.867188 5.8125 -1.84375 7.34375 -1.84375 C 8.21875 -1.84375 8.878906 -2.195312 9.328125 -2.90625 C 9.785156 -3.625 10.015625 -4.878906 10.015625 -6.671875 Z M 10.015625 -6.671875 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-33">
-<path style="stroke:none;" d="M 13.046875 -13.625 L 12.234375 -13.625 L 10.984375 -13.75 C 11.671875 -12.90625 12.015625 -12.046875 12.015625 -11.171875 C 12.015625 -9.972656 11.582031 -8.976562 10.71875 -8.1875 C 9.851562 -7.394531 8.671875 -7 7.171875 -7 C 6.609375 -7 6.097656 -7 5.640625 -7 C 4.898438 -6.519531 4.53125 -6.054688 4.53125 -5.609375 C 4.53125 -5.140625 4.675781 -4.789062 4.96875 -4.5625 C 5.257812 -4.34375 5.738281 -4.234375 6.40625 -4.234375 L 7.703125 -4.234375 C 9.628906 -4.234375 11.023438 -3.820312 11.890625 -3 C 12.753906 -2.1875 13.1875 -1.15625 13.1875 0.09375 C 13.1875 1.5 12.632812 2.660156 11.53125 3.578125 C 10.4375 4.492188 8.988281 4.953125 7.1875 4.953125 C 5.3125 4.953125 3.882812 4.523438 2.90625 3.671875 C 1.9375 2.828125 1.453125 1.800781 1.453125 0.59375 C 1.453125 -0.988281 2.25 -2.1875 3.84375 -3 C 2.664062 -3.488281 2.078125 -4.238281 2.078125 -5.25 C 2.078125 -6.269531 2.703125 -7.132812 3.953125 -7.84375 C 2.671875 -8.707031 2.03125 -9.898438 2.03125 -11.421875 C 2.03125 -12.679688 2.484375 -13.707031 3.390625 -14.5 C 4.296875 -15.300781 5.554688 -15.703125 7.171875 -15.703125 C 7.648438 -15.703125 8.804688 -15.628906 10.640625 -15.484375 L 13.046875 -15.484375 Z M 9.78125 -11.4375 C 9.78125 -12.050781 9.550781 -12.59375 9.09375 -13.0625 C 8.632812 -13.53125 7.953125 -13.765625 7.046875 -13.765625 C 6.109375 -13.765625 5.414062 -13.539062 4.96875 -13.09375 C 4.519531 -12.65625 4.296875 -12.085938 4.296875 -11.390625 C 4.296875 -10.585938 4.53125 -9.972656 5 -9.546875 C 5.476562 -9.128906 6.144531 -8.921875 7 -8.921875 C 7.925781 -8.921875 8.617188 -9.140625 9.078125 -9.578125 C 9.546875 -10.015625 9.78125 -10.632812 9.78125 -11.4375 Z M 10.78125 0.09375 C 10.78125 -0.59375 10.554688 -1.132812 10.109375 -1.53125 C 9.660156 -1.9375 8.859375 -2.140625 7.703125 -2.140625 L 6.765625 -2.140625 C 5.710938 -2.140625 4.960938 -1.898438 4.515625 -1.421875 C 4.066406 -0.941406 3.84375 -0.328125 3.84375 0.421875 C 3.84375 1.203125 4.132812 1.800781 4.71875 2.21875 C 5.300781 2.644531 6.070312 2.859375 7.03125 2.859375 C 9.53125 2.859375 10.78125 1.9375 10.78125 0.09375 Z M 10.78125 0.09375 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-34">
-<path style="stroke:none;" d="M 4.140625 2.5 L 5.921875 -2.125 L 2.703125 -10.015625 C 1.941406 -11.910156 1.53125 -13.332031 1.46875 -14.28125 L 1.390625 -15.484375 L 3.71875 -15.484375 L 3.78125 -14.375 C 3.820312 -13.738281 4.179688 -12.503906 4.859375 -10.671875 L 7 -4.828125 L 9.140625 -10.59375 C 9.804688 -12.457031 10.164062 -13.71875 10.21875 -14.375 L 10.28125 -15.484375 L 12.609375 -15.484375 L 12.53125 -14.28125 C 12.476562 -13.4375 12.066406 -12.015625 11.296875 -10.015625 L 6.359375 2.796875 C 6.179688 3.234375 6.078125 3.742188 6.046875 4.328125 L 6.015625 4.828125 L 3.6875 4.828125 L 3.71875 4.328125 C 3.757812 3.742188 3.898438 3.132812 4.140625 2.5 Z M 4.140625 2.5 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-35">
-<path style="stroke:none;" d="M 7.265625 -9.515625 L 4.84375 -9.515625 L 4.84375 0 L 2.515625 0 L 2.515625 -20.859375 L 7.0625 -20.859375 C 8.914062 -20.859375 10.289062 -20.359375 11.1875 -19.359375 C 12.09375 -18.359375 12.546875 -17.015625 12.546875 -15.328125 C 12.546875 -13.898438 12.253906 -12.722656 11.671875 -11.796875 C 11.097656 -10.878906 10.382812 -10.257812 9.53125 -9.9375 L 9.78125 -9.5625 C 11.507812 -6.863281 12.460938 -3.988281 12.640625 -0.9375 L 12.703125 0 L 10.375 0 L 10.34375 -0.71875 C 10.164062 -3.632812 9.253906 -6.378906 7.609375 -8.953125 Z M 4.84375 -11.484375 L 7.0625 -11.484375 C 8.132812 -11.484375 8.914062 -11.800781 9.40625 -12.4375 C 9.894531 -13.082031 10.140625 -14.050781 10.140625 -15.34375 C 10.140625 -16.613281 9.894531 -17.519531 9.40625 -18.0625 C 8.914062 -18.601562 8.132812 -18.875 7.0625 -18.875 L 4.84375 -18.875 Z M 4.84375 -11.484375 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-36">
-<path style="stroke:none;" d="M 3.8125 -1.15625 L 3.8125 0 L 1.484375 0 L 1.484375 -1.15625 C 1.484375 -3.15625 2.082031 -6.519531 3.28125 -11.25 L 5.6875 -20.859375 L 8.3125 -20.859375 L 10.71875 -11.25 C 11.914062 -6.519531 12.515625 -3.15625 12.515625 -1.15625 L 12.515625 0 L 10.1875 0 L 10.1875 -1.15625 C 10.1875 -2.101562 10.054688 -3.34375 9.796875 -4.875 L 4.203125 -4.875 C 3.941406 -3.34375 3.8125 -2.101562 3.8125 -1.15625 Z M 7 -17.171875 L 5.265625 -9.953125 C 4.992188 -8.835938 4.757812 -7.804688 4.5625 -6.859375 L 9.421875 -6.859375 C 9.234375 -7.804688 9.003906 -8.835938 8.734375 -9.953125 Z M 7 -17.171875 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-37">
-<path style="stroke:none;" d="M 12.8125 0 L 3.328125 0 L 3.328125 -20.859375 L 5.640625 -20.859375 L 5.640625 -1.984375 L 12.8125 -1.984375 Z M 12.8125 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-38">
-<path style="stroke:none;" d="M 8.34375 0 L 5.65625 0 L 2.984375 -8.09375 C 2.054688 -10.863281 1.550781 -12.957031 1.46875 -14.375 L 1.390625 -15.484375 L 3.71875 -15.484375 L 3.78125 -14.375 C 3.863281 -13.03125 4.304688 -11.046875 5.109375 -8.421875 L 7 -2.1875 L 8.890625 -8.421875 C 9.691406 -11.046875 10.132812 -13.03125 10.21875 -14.375 L 10.28125 -15.484375 L 12.609375 -15.484375 L 12.53125 -14.375 C 12.445312 -12.957031 11.941406 -10.863281 11.015625 -8.09375 Z M 8.34375 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-39">
-<path style="stroke:none;" d="M 12.609375 -8.21875 L 10.125 -8.21875 L 6.984375 -17.765625 L 3.765625 -8.21875 L 1.375 -8.21875 L 5.765625 -20.859375 L 8.171875 -20.859375 Z M 12.609375 -8.21875 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-40">
-<path style="stroke:none;" d="M 14 -13.703125 L 14 -7.125 L 10.296875 -7.125 L 10.296875 4.953125 L 3.703125 4.953125 L 3.703125 -13.703125 Z M 14 -13.703125 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-41">
-<path style="stroke:none;" d="M 14.265625 -13.703125 L 14.265625 -7.125 L -0.265625 -7.125 L -0.265625 -13.703125 Z M 14.265625 -13.703125 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-42">
-<path style="stroke:none;" d="M 11.453125 0 L 2.53125 0 L 2.53125 -1.984375 L 5.828125 -1.984375 L 5.828125 -18.875 L 3.234375 -18.875 L 3.234375 -20.859375 L 10.75 -20.859375 L 10.75 -18.875 L 8.15625 -18.875 L 8.15625 -1.984375 L 11.453125 -1.984375 Z M 11.453125 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-43">
-<path style="stroke:none;" d="M 12.65625 0 L 10.765625 0 L 10.765625 -11.6875 C 10.765625 -13.007812 10.375 -13.671875 9.59375 -13.671875 C 8.914062 -13.671875 8.363281 -13.160156 7.9375 -12.140625 L 7.9375 0 L 6.0625 0 L 6.0625 -11.6875 C 6.039062 -13.007812 5.726562 -13.671875 5.125 -13.671875 C 4.351562 -13.671875 3.726562 -13.160156 3.25 -12.140625 L 3.25 0 L 1.34375 0 L 1.34375 -15.484375 L 3.25 -15.484375 L 3.25 -13.953125 C 3.945312 -15.160156 4.804688 -15.765625 5.828125 -15.765625 C 6.742188 -15.765625 7.375 -15.160156 7.71875 -13.953125 C 8.375 -15.160156 9.195312 -15.765625 10.1875 -15.765625 C 11.832031 -15.765625 12.65625 -14.441406 12.65625 -11.796875 Z M 12.65625 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-44">
-<path style="stroke:none;" d="M 10.296875 -13.703125 L 10.296875 4.953125 L 3.703125 4.953125 L 3.703125 -7.125 L 0 -7.125 L 0 -13.703125 Z M 10.296875 -13.703125 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-45">
-<path style="stroke:none;" d="M 10.296875 4.953125 L 3.703125 4.953125 L 3.703125 -25.765625 L 10.296875 -25.765625 Z M 10.296875 4.953125 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-46">
-<path style="stroke:none;" d="M 1.59375 -13.4375 L 1.59375 -15.9375 L 12.9375 -9.765625 L 12.9375 -6.828125 L 1.59375 -0.640625 L 1.59375 -3.140625 L 11.234375 -8.296875 Z M 1.59375 -13.4375 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-47">
-<path style="stroke:none;" d="M 12.171875 -6.515625 C 12.171875 -4.359375 11.648438 -2.6875 10.609375 -1.5 C 9.566406 -0.320312 8.300781 0.265625 6.8125 0.265625 C 4.550781 0.265625 2.847656 -0.804688 1.703125 -2.953125 L 3.609375 -4.21875 C 4.253906 -2.632812 5.316406 -1.84375 6.796875 -1.84375 C 7.597656 -1.84375 8.300781 -2.203125 8.90625 -2.921875 C 9.519531 -3.640625 9.828125 -4.835938 9.828125 -6.515625 C 9.828125 -8.203125 9.523438 -9.382812 8.921875 -10.0625 C 8.328125 -10.738281 7.351562 -11.078125 6 -11.078125 L 2.90625 -11.078125 L 2.90625 -20.859375 L 11.734375 -20.859375 L 11.734375 -18.875 L 5.234375 -18.875 L 5.234375 -13.1875 L 6.40625 -13.1875 C 8.132812 -13.1875 9.523438 -12.664062 10.578125 -11.625 C 11.640625 -10.59375 12.171875 -8.890625 12.171875 -6.515625 Z M 12.171875 -6.515625 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-48">
-<path style="stroke:none;" d="M 9.6875 -2.421875 C 9.6875 -1.710938 9.429688 -1.085938 8.921875 -0.546875 C 8.410156 -0.00390625 7.765625 0.265625 6.984375 0.265625 C 6.203125 0.265625 5.5625 -0.00390625 5.0625 -0.546875 C 4.570312 -1.085938 4.328125 -1.710938 4.328125 -2.421875 C 4.328125 -3.117188 4.570312 -3.738281 5.0625 -4.28125 C 5.5625 -4.832031 6.203125 -5.109375 6.984375 -5.109375 C 7.765625 -5.109375 8.410156 -4.832031 8.921875 -4.28125 C 9.429688 -3.726562 9.6875 -3.109375 9.6875 -2.421875 Z M 9.6875 -13.078125 C 9.6875 -12.359375 9.429688 -11.726562 8.921875 -11.1875 C 8.410156 -10.65625 7.765625 -10.390625 6.984375 -10.390625 C 6.203125 -10.390625 5.5625 -10.65625 5.0625 -11.1875 C 4.570312 -11.726562 4.328125 -12.359375 4.328125 -13.078125 C 4.328125 -13.785156 4.570312 -14.410156 5.0625 -14.953125 C 5.5625 -15.492188 6.203125 -15.765625 6.984375 -15.765625 C 7.765625 -15.765625 8.410156 -15.488281 8.921875 -14.9375 C 9.429688 -14.394531 9.6875 -13.773438 9.6875 -13.078125 Z M 9.6875 -13.078125 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-49">
-<path style="stroke:none;" d="M 3.5625 -7.390625 C 3.90625 -7.390625 4.191406 -7.1875 4.421875 -6.78125 C 4.984375 -5.882812 5.367188 -5.4375 5.578125 -5.4375 C 5.679688 -5.4375 5.75 -5.503906 5.78125 -5.640625 C 6.664062 -8.816406 7.515625 -11.3125 8.328125 -13.125 C 9.148438 -14.945312 9.847656 -16.269531 10.421875 -17.09375 C 11.078125 -18 11.707031 -18.453125 12.3125 -18.453125 C 12.46875 -18.453125 12.597656 -18.410156 12.703125 -18.328125 C 12.816406 -18.253906 12.875 -18.140625 12.875 -17.984375 C 12.875 -17.796875 12.753906 -17.507812 12.515625 -17.125 C 10.628906 -13.84375 8.894531 -9.082031 7.3125 -2.84375 C 7.1875 -2.425781 6.875 -2.117188 6.375 -1.921875 C 5.875 -1.722656 5.484375 -1.625 5.203125 -1.625 C 4.640625 -1.625 3.957031 -2.203125 3.15625 -3.359375 C 2.351562 -4.515625 1.953125 -5.25 1.953125 -5.5625 C 1.953125 -5.957031 2.148438 -6.359375 2.546875 -6.765625 C 2.953125 -7.179688 3.289062 -7.390625 3.5625 -7.390625 Z M 3.5625 -7.390625 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-50">
-<path style="stroke:none;" d="M 12.5 -20.859375 L 3.75 1.75 L 1.484375 1.75 L 10.25 -20.859375 Z M 12.5 -20.859375 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-51">
-<path style="stroke:none;" d="M 4.90625 -3.234375 C 4.175781 -2.210938 3.773438 -1.132812 3.703125 0 L 1.375 0 L 1.40625 -0.53125 C 1.46875 -1.550781 2.050781 -2.835938 3.15625 -4.390625 L 5.703125 -7.984375 L 3.515625 -11.09375 C 2.410156 -12.632812 1.828125 -13.921875 1.765625 -14.953125 L 1.75 -15.484375 L 4.078125 -15.484375 C 4.140625 -14.359375 4.535156 -13.28125 5.265625 -12.25 L 7 -9.8125 L 8.734375 -12.25 C 9.460938 -13.28125 9.859375 -14.359375 9.921875 -15.484375 L 12.25 -15.484375 L 12.234375 -14.953125 C 12.171875 -13.921875 11.585938 -12.632812 10.484375 -11.09375 L 8.28125 -7.984375 L 10.84375 -4.390625 C 11.945312 -2.835938 12.53125 -1.550781 12.59375 -0.53125 L 12.625 0 L 10.296875 0 C 10.222656 -1.132812 9.820312 -2.210938 9.09375 -3.234375 L 7 -6.171875 Z M 4.90625 -3.234375 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-52">
-<path style="stroke:none;" d="M 11.0625 -17.859375 C 10.695312 -18.640625 10.019531 -19.03125 9.03125 -19.03125 C 8.382812 -19.03125 7.878906 -18.820312 7.515625 -18.40625 C 7.160156 -17.988281 6.984375 -17.242188 6.984375 -16.171875 L 6.984375 -14.609375 L 12.203125 -14.609375 L 12.203125 -12.640625 L 6.984375 -12.640625 L 6.984375 0 L 4.65625 0 L 4.65625 -12.640625 L 2.046875 -12.640625 L 2.046875 -14.609375 L 4.65625 -14.609375 L 4.65625 -16.03125 C 4.65625 -17.789062 5.0625 -19.082031 5.875 -19.90625 C 6.695312 -20.738281 7.703125 -21.15625 8.890625 -21.15625 C 10.710938 -21.15625 12.019531 -20.523438 12.8125 -19.265625 Z M 11.0625 -17.859375 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-53">
-<path style="stroke:none;" d="M 3.703125 -7.140625 L 3.703125 -25.765625 L 10.296875 -25.765625 L 10.296875 -13.703125 L 14 -13.703125 L 14 -7.125 Z M 3.703125 -7.140625 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-54">
-<path style="stroke:none;" d="M 0 -7.125 L 0 -13.703125 L 3.703125 -13.703125 L 3.703125 -25.765625 L 10.296875 -25.765625 L 10.296875 -7.125 Z M 0 -7.125 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-55">
-<path style="stroke:none;" d="M -0.265625 -9.421875 L -0.265625 -11.390625 L 14.265625 -11.390625 L 14.265625 -9.421875 Z M -0.265625 -9.421875 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-56">
-<path style="stroke:none;" d="M 0 -7.125 L 0 -9.109375 L 14 -9.109375 L 14 -7.125 Z M 0 -11.71875 L 0 -13.703125 L 14 -13.703125 L 14 -11.71875 Z M 0 -11.71875 "/>
-</symbol>
-</g>
-</defs>
-<g id="surface89756">
-<rect x="0" y="0" width="1428" height="682" style="fill:rgb(16.078431%,17.647059%,24.313725%);fill-opacity:1;stroke:none;"/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 434 124 L 448 124 L 448 155 L 434 155 Z M 434 124 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 0 0 L 854 0 L 854 31 L 0 31 Z M 0 0 "/>
-<g style="fill:rgb(74.901961%,75.294118%,76.862745%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="0" y="26"/>
-  <use xlink:href="#glyph0-2" x="14" y="26"/>
-  <use xlink:href="#glyph0-3" x="28" y="26"/>
-  <use xlink:href="#glyph0-4" x="42" y="26"/>
-  <use xlink:href="#glyph0-5" x="56" y="26"/>
-  <use xlink:href="#glyph0-6" x="70" y="26"/>
-  <use xlink:href="#glyph0-7" x="84" y="26"/>
-  <use xlink:href="#glyph0-1" x="98" y="26"/>
-  <use xlink:href="#glyph0-2" x="112" y="26"/>
-  <use xlink:href="#glyph0-8" x="126" y="26"/>
-  <use xlink:href="#glyph0-9" x="140" y="26"/>
-  <use xlink:href="#glyph0-4" x="154" y="26"/>
-  <use xlink:href="#glyph0-10" x="168" y="26"/>
-  <use xlink:href="#glyph0-7" x="182" y="26"/>
-  <use xlink:href="#glyph0-1" x="196" y="26"/>
-  <use xlink:href="#glyph0-2" x="210" y="26"/>
-  <use xlink:href="#glyph0-11" x="224" y="26"/>
-  <use xlink:href="#glyph0-4" x="238" y="26"/>
-  <use xlink:href="#glyph0-6" x="252" y="26"/>
-  <use xlink:href="#glyph0-12" x="266" y="26"/>
-  <use xlink:href="#glyph0-7" x="280" y="26"/>
-  <use xlink:href="#glyph0-1" x="294" y="26"/>
-  <use xlink:href="#glyph0-2" x="308" y="26"/>
-  <use xlink:href="#glyph0-13" x="322" y="26"/>
-  <use xlink:href="#glyph0-4" x="336" y="26"/>
-  <use xlink:href="#glyph0-14" x="350" y="26"/>
-  <use xlink:href="#glyph0-9" x="364" y="26"/>
-  <use xlink:href="#glyph0-15" x="378" y="26"/>
-  <use xlink:href="#glyph0-12" x="392" y="26"/>
-  <use xlink:href="#glyph0-16" x="406" y="26"/>
-  <use xlink:href="#glyph0-7" x="420" y="26"/>
-  <use xlink:href="#glyph0-1" x="434" y="26"/>
-  <use xlink:href="#glyph0-2" x="448" y="26"/>
-  <use xlink:href="#glyph0-17" x="462" y="26"/>
-  <use xlink:href="#glyph0-6" x="476" y="26"/>
-  <use xlink:href="#glyph0-5" x="490" y="26"/>
-  <use xlink:href="#glyph0-18" x="504" y="26"/>
-  <use xlink:href="#glyph0-7" x="518" y="26"/>
-  <use xlink:href="#glyph0-1" x="532" y="26"/>
-  <use xlink:href="#glyph0-1" x="546" y="26"/>
-  <use xlink:href="#glyph0-1" x="560" y="26"/>
-  <use xlink:href="#glyph0-1" x="574" y="26"/>
-  <use xlink:href="#glyph0-1" x="588" y="26"/>
-  <use xlink:href="#glyph0-1" x="602" y="26"/>
-  <use xlink:href="#glyph0-1" x="616" y="26"/>
-  <use xlink:href="#glyph0-1" x="630" y="26"/>
-  <use xlink:href="#glyph0-1" x="644" y="26"/>
-  <use xlink:href="#glyph0-1" x="658" y="26"/>
-  <use xlink:href="#glyph0-1" x="672" y="26"/>
-  <use xlink:href="#glyph0-1" x="686" y="26"/>
-  <use xlink:href="#glyph0-1" x="700" y="26"/>
-  <use xlink:href="#glyph0-1" x="714" y="26"/>
-  <use xlink:href="#glyph0-1" x="728" y="26"/>
-  <use xlink:href="#glyph0-1" x="742" y="26"/>
-  <use xlink:href="#glyph0-1" x="756" y="26"/>
-  <use xlink:href="#glyph0-1" x="770" y="26"/>
-  <use xlink:href="#glyph0-1" x="784" y="26"/>
-  <use xlink:href="#glyph0-1" x="798" y="26"/>
-  <use xlink:href="#glyph0-1" x="812" y="26"/>
-  <use xlink:href="#glyph0-1" x="826" y="26"/>
-  <use xlink:href="#glyph0-1" x="840" y="26"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 854 0 L 1092 0 L 1092 31 L 854 31 Z M 854 0 "/>
-<g style="fill:rgb(100%,72.156863%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-19" x="854" y="26"/>
-  <use xlink:href="#glyph0-15" x="868" y="26"/>
-  <use xlink:href="#glyph0-14" x="882" y="26"/>
-  <use xlink:href="#glyph0-16" x="896" y="26"/>
-  <use xlink:href="#glyph0-15" x="910" y="26"/>
-  <use xlink:href="#glyph0-5" x="924" y="26"/>
-  <use xlink:href="#glyph0-6" x="938" y="26"/>
-  <use xlink:href="#glyph0-1" x="952" y="26"/>
-  <use xlink:href="#glyph0-20" x="966" y="26"/>
-  <use xlink:href="#glyph0-6" x="980" y="26"/>
-  <use xlink:href="#glyph0-16" x="994" y="26"/>
-  <use xlink:href="#glyph0-10" x="1008" y="26"/>
-  <use xlink:href="#glyph0-1" x="1022" y="26"/>
-  <use xlink:href="#glyph0-21" x="1036" y="26"/>
-  <use xlink:href="#glyph0-15" x="1050" y="26"/>
-  <use xlink:href="#glyph0-9" x="1064" y="26"/>
-  <use xlink:href="#glyph0-6" x="1078" y="26"/>
-</g>
-<g style="fill:rgb(100%,72.156863%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-19" x="855" y="26"/>
-  <use xlink:href="#glyph0-15" x="869" y="26"/>
-  <use xlink:href="#glyph0-14" x="883" y="26"/>
-  <use xlink:href="#glyph0-16" x="897" y="26"/>
-  <use xlink:href="#glyph0-15" x="911" y="26"/>
-  <use xlink:href="#glyph0-5" x="925" y="26"/>
-  <use xlink:href="#glyph0-6" x="939" y="26"/>
-  <use xlink:href="#glyph0-1" x="953" y="26"/>
-  <use xlink:href="#glyph0-20" x="967" y="26"/>
-  <use xlink:href="#glyph0-6" x="981" y="26"/>
-  <use xlink:href="#glyph0-16" x="995" y="26"/>
-  <use xlink:href="#glyph0-10" x="1009" y="26"/>
-  <use xlink:href="#glyph0-1" x="1023" y="26"/>
-  <use xlink:href="#glyph0-21" x="1037" y="26"/>
-  <use xlink:href="#glyph0-15" x="1051" y="26"/>
-  <use xlink:href="#glyph0-9" x="1065" y="26"/>
-  <use xlink:href="#glyph0-6" x="1079" y="26"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 1092 0 L 1120 0 L 1120 31 L 1092 31 Z M 1092 0 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 1120 0 L 1134 0 L 1134 31 L 1120 31 Z M 1120 0 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 1134 0 L 1302 0 L 1302 31 L 1134 31 Z M 1134 0 "/>
-<g style="fill:rgb(87.843137%,90.196078%,94.117647%);fill-opacity:1;">
-  <use xlink:href="#glyph0-22" x="1134" y="26"/>
-  <use xlink:href="#glyph0-6" x="1148" y="26"/>
-  <use xlink:href="#glyph0-23" x="1162" y="26"/>
-  <use xlink:href="#glyph0-24" x="1176" y="26"/>
-  <use xlink:href="#glyph0-25" x="1190" y="26"/>
-  <use xlink:href="#glyph0-26" x="1204" y="26"/>
-  <use xlink:href="#glyph0-1" x="1218" y="26"/>
-  <use xlink:href="#glyph0-21" x="1232" y="26"/>
-  <use xlink:href="#glyph0-6" x="1246" y="26"/>
-  <use xlink:href="#glyph0-14" x="1260" y="26"/>
-  <use xlink:href="#glyph0-27" x="1274" y="26"/>
-  <use xlink:href="#glyph0-1" x="1288" y="26"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 1302 0 L 1386 0 L 1386 31 L 1302 31 Z M 1302 0 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-19" x="1302" y="26"/>
-  <use xlink:href="#glyph0-10" x="1316" y="26"/>
-  <use xlink:href="#glyph0-24" x="1330" y="26"/>
-  <use xlink:href="#glyph0-5" x="1344" y="26"/>
-  <use xlink:href="#glyph0-28" x="1358" y="26"/>
-  <use xlink:href="#glyph0-18" x="1372" y="26"/>
-</g>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-19" x="1303" y="26"/>
-  <use xlink:href="#glyph0-10" x="1317" y="26"/>
-  <use xlink:href="#glyph0-24" x="1331" y="26"/>
-  <use xlink:href="#glyph0-5" x="1345" y="26"/>
-  <use xlink:href="#glyph0-28" x="1359" y="26"/>
-  <use xlink:href="#glyph0-18" x="1373" y="26"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 1386 0 L 1400 0 L 1400 31 L 1386 31 Z M 1386 0 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 1400 0 L 1414 0 L 1414 31 L 1400 31 Z M 1400 0 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117647%,15.686275%,21.568627%);fill-opacity:1;" d="M 1414 0 L 1428 0 L 1428 31 L 1414 31 Z M 1414 0 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(6.27451%,5.882353%,6.27451%);fill-opacity:1;" d="M 0 31 L 14 31 L 14 62 L 0 62 Z M 0 31 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 14 31 L 112 31 L 112 62 L 14 62 Z M 14 31 "/>
-<g style="fill:rgb(74.901961%,75.294118%,76.862745%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="14" y="57"/>
-  <use xlink:href="#glyph0-19" x="28" y="57"/>
-  <use xlink:href="#glyph0-5" x="42" y="57"/>
-  <use xlink:href="#glyph0-15" x="56" y="57"/>
-  <use xlink:href="#glyph0-25" x="70" y="57"/>
-  <use xlink:href="#glyph0-29" x="84" y="57"/>
-  <use xlink:href="#glyph0-1" x="98" y="57"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(6.27451%,5.882353%,6.27451%);fill-opacity:1;" d="M 112 31 L 126 31 L 126 62 L 112 62 Z M 112 31 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 126 31 L 364 31 L 364 62 L 126 62 Z M 126 31 "/>
-<g style="fill:rgb(74.901961%,75.294118%,76.862745%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="126" y="57"/>
-  <use xlink:href="#glyph0-30" x="140" y="57"/>
-  <use xlink:href="#glyph0-12" x="154" y="57"/>
-  <use xlink:href="#glyph0-19" x="168" y="57"/>
-  <use xlink:href="#glyph0-15" x="182" y="57"/>
-  <use xlink:href="#glyph0-14" x="196" y="57"/>
-  <use xlink:href="#glyph0-16" x="210" y="57"/>
-  <use xlink:href="#glyph0-15" x="224" y="57"/>
-  <use xlink:href="#glyph0-5" x="238" y="57"/>
-  <use xlink:href="#glyph0-6" x="252" y="57"/>
-  <use xlink:href="#glyph0-1" x="266" y="57"/>
-  <use xlink:href="#glyph0-31" x="280" y="57"/>
-  <use xlink:href="#glyph0-6" x="294" y="57"/>
-  <use xlink:href="#glyph0-32" x="308" y="57"/>
-  <use xlink:href="#glyph0-27" x="322" y="57"/>
-  <use xlink:href="#glyph0-33" x="336" y="57"/>
-  <use xlink:href="#glyph0-1" x="350" y="57"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(6.27451%,5.882353%,6.27451%);fill-opacity:1;" d="M 364 31 L 378 31 L 378 62 L 364 62 Z M 364 31 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 378 31 L 560 31 L 560 62 L 378 62 Z M 378 31 "/>
-<g style="fill:rgb(37.647059%,90.588235%,87.843137%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="378" y="57"/>
-  <use xlink:href="#glyph0-30" x="392" y="57"/>
-  <use xlink:href="#glyph0-34" x="406" y="57"/>
-  <use xlink:href="#glyph0-10" x="420" y="57"/>
-  <use xlink:href="#glyph0-26" x="434" y="57"/>
-  <use xlink:href="#glyph0-15" x="448" y="57"/>
-  <use xlink:href="#glyph0-14" x="462" y="57"/>
-  <use xlink:href="#glyph0-1" x="476" y="57"/>
-  <use xlink:href="#glyph0-35" x="490" y="57"/>
-  <use xlink:href="#glyph0-6" x="504" y="57"/>
-  <use xlink:href="#glyph0-18" x="518" y="57"/>
-  <use xlink:href="#glyph0-5" x="532" y="57"/>
-  <use xlink:href="#glyph0-1" x="546" y="57"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(6.27451%,5.882353%,6.27451%);fill-opacity:1;" d="M 560 31 L 574 31 L 574 62 L 560 62 Z M 560 31 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 574 31 L 714 31 L 714 62 L 574 62 Z M 574 31 "/>
-<g style="fill:rgb(74.901961%,75.294118%,76.862745%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="574" y="57"/>
-  <use xlink:href="#glyph0-36" x="588" y="57"/>
-  <use xlink:href="#glyph0-5" x="602" y="57"/>
-  <use xlink:href="#glyph0-5" x="616" y="57"/>
-  <use xlink:href="#glyph0-1" x="630" y="57"/>
-  <use xlink:href="#glyph0-37" x="644" y="57"/>
-  <use xlink:href="#glyph0-15" x="658" y="57"/>
-  <use xlink:href="#glyph0-33" x="672" y="57"/>
-  <use xlink:href="#glyph0-16" x="686" y="57"/>
-  <use xlink:href="#glyph0-1" x="700" y="57"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(6.27451%,5.882353%,6.27451%);fill-opacity:1;" d="M 714 31 L 728 31 L 728 62 L 714 62 Z M 714 31 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 728 31 L 980 31 L 980 62 L 728 62 Z M 728 31 "/>
-<g style="fill:rgb(74.901961%,75.294118%,76.862745%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="728" y="57"/>
-  <use xlink:href="#glyph0-3" x="742" y="57"/>
-  <use xlink:href="#glyph0-23" x="756" y="57"/>
-  <use xlink:href="#glyph0-29" x="770" y="57"/>
-  <use xlink:href="#glyph0-6" x="784" y="57"/>
-  <use xlink:href="#glyph0-1" x="798" y="57"/>
-  <use xlink:href="#glyph0-31" x="812" y="57"/>
-  <use xlink:href="#glyph0-6" x="826" y="57"/>
-  <use xlink:href="#glyph0-38" x="840" y="57"/>
-  <use xlink:href="#glyph0-4" x="854" y="57"/>
-  <use xlink:href="#glyph0-25" x="868" y="57"/>
-  <use xlink:href="#glyph0-6" x="882" y="57"/>
-  <use xlink:href="#glyph0-1" x="896" y="57"/>
-  <use xlink:href="#glyph0-37" x="910" y="57"/>
-  <use xlink:href="#glyph0-15" x="924" y="57"/>
-  <use xlink:href="#glyph0-33" x="938" y="57"/>
-  <use xlink:href="#glyph0-16" x="952" y="57"/>
-  <use xlink:href="#glyph0-1" x="966" y="57"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(6.27451%,5.882353%,6.27451%);fill-opacity:1;" d="M 980 31 L 994 31 L 994 62 L 980 62 Z M 980 31 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 994 31 L 1162 31 L 1162 62 L 994 62 Z M 994 31 "/>
-<g style="fill:rgb(74.901961%,75.294118%,76.862745%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="994" y="57"/>
-  <use xlink:href="#glyph0-19" x="1008" y="57"/>
-  <use xlink:href="#glyph0-23" x="1022" y="57"/>
-  <use xlink:href="#glyph0-5" x="1036" y="57"/>
-  <use xlink:href="#glyph0-25" x="1050" y="57"/>
-  <use xlink:href="#glyph0-27" x="1064" y="57"/>
-  <use xlink:href="#glyph0-5" x="1078" y="57"/>
-  <use xlink:href="#glyph0-23" x="1092" y="57"/>
-  <use xlink:href="#glyph0-10" x="1106" y="57"/>
-  <use xlink:href="#glyph0-15" x="1120" y="57"/>
-  <use xlink:href="#glyph0-24" x="1134" y="57"/>
-  <use xlink:href="#glyph0-1" x="1148" y="57"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(6.27451%,5.882353%,6.27451%);fill-opacity:1;" d="M 1162 31 L 1414 31 L 1414 62 L 1162 62 Z M 1162 31 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117647%,15.686275%,21.568627%);fill-opacity:1;" d="M 1414 31 L 1428 31 L 1428 62 L 1414 62 Z M 1414 31 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 0 62 L 1400 62 L 1400 93 L 0 93 Z M 0 62 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(6.27451%,5.882353%,6.27451%);fill-opacity:1;" d="M 1400 62 L 1414 62 L 1414 93 L 1400 93 Z M 1400 62 "/>
-<g style="fill:rgb(57.254902%,85.098039%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-39" x="1400" y="88"/>
-</g>
-<g style="fill:rgb(57.254902%,85.098039%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-39" x="1401" y="88"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117647%,15.686275%,21.568627%);fill-opacity:1;" d="M 1414 62 L 1428 62 L 1428 93 L 1414 93 Z M 1414 62 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 0 93 L 140 93 L 140 124 L 0 124 Z M 0 93 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 140 93 L 1260 93 L 1260 124 L 140 124 Z M 140 93 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-40" x="140" y="119"/>
-  <use xlink:href="#glyph0-41" x="154" y="119"/>
-  <use xlink:href="#glyph0-41" x="168" y="119"/>
-  <use xlink:href="#glyph0-1" x="182" y="119"/>
-  <use xlink:href="#glyph0-21" x="196" y="119"/>
-  <use xlink:href="#glyph0-6" x="210" y="119"/>
-  <use xlink:href="#glyph0-14" x="224" y="119"/>
-  <use xlink:href="#glyph0-27" x="238" y="119"/>
-  <use xlink:href="#glyph0-1" x="252" y="119"/>
-  <use xlink:href="#glyph0-42" x="266" y="119"/>
-  <use xlink:href="#glyph0-10" x="280" y="119"/>
-  <use xlink:href="#glyph0-6" x="294" y="119"/>
-  <use xlink:href="#glyph0-43" x="308" y="119"/>
-  <use xlink:href="#glyph0-16" x="322" y="119"/>
-  <use xlink:href="#glyph0-1" x="336" y="119"/>
-  <use xlink:href="#glyph0-41" x="350" y="119"/>
-  <use xlink:href="#glyph0-41" x="364" y="119"/>
-  <use xlink:href="#glyph0-41" x="378" y="119"/>
-  <use xlink:href="#glyph0-41" x="392" y="119"/>
-  <use xlink:href="#glyph0-41" x="406" y="119"/>
-  <use xlink:href="#glyph0-41" x="420" y="119"/>
-  <use xlink:href="#glyph0-41" x="434" y="119"/>
-  <use xlink:href="#glyph0-41" x="448" y="119"/>
-  <use xlink:href="#glyph0-41" x="462" y="119"/>
-  <use xlink:href="#glyph0-41" x="476" y="119"/>
-  <use xlink:href="#glyph0-41" x="490" y="119"/>
-  <use xlink:href="#glyph0-41" x="504" y="119"/>
-  <use xlink:href="#glyph0-41" x="518" y="119"/>
-  <use xlink:href="#glyph0-41" x="532" y="119"/>
-  <use xlink:href="#glyph0-41" x="546" y="119"/>
-  <use xlink:href="#glyph0-41" x="560" y="119"/>
-  <use xlink:href="#glyph0-41" x="574" y="119"/>
-  <use xlink:href="#glyph0-41" x="588" y="119"/>
-  <use xlink:href="#glyph0-41" x="602" y="119"/>
-  <use xlink:href="#glyph0-41" x="616" y="119"/>
-  <use xlink:href="#glyph0-41" x="630" y="119"/>
-  <use xlink:href="#glyph0-41" x="644" y="119"/>
-  <use xlink:href="#glyph0-41" x="658" y="119"/>
-  <use xlink:href="#glyph0-41" x="672" y="119"/>
-  <use xlink:href="#glyph0-41" x="686" y="119"/>
-  <use xlink:href="#glyph0-41" x="700" y="119"/>
-  <use xlink:href="#glyph0-41" x="714" y="119"/>
-  <use xlink:href="#glyph0-41" x="728" y="119"/>
-  <use xlink:href="#glyph0-41" x="742" y="119"/>
-  <use xlink:href="#glyph0-41" x="756" y="119"/>
-  <use xlink:href="#glyph0-41" x="770" y="119"/>
-  <use xlink:href="#glyph0-41" x="784" y="119"/>
-  <use xlink:href="#glyph0-41" x="798" y="119"/>
-  <use xlink:href="#glyph0-41" x="812" y="119"/>
-  <use xlink:href="#glyph0-41" x="826" y="119"/>
-  <use xlink:href="#glyph0-41" x="840" y="119"/>
-  <use xlink:href="#glyph0-41" x="854" y="119"/>
-  <use xlink:href="#glyph0-41" x="868" y="119"/>
-  <use xlink:href="#glyph0-41" x="882" y="119"/>
-  <use xlink:href="#glyph0-41" x="896" y="119"/>
-  <use xlink:href="#glyph0-41" x="910" y="119"/>
-  <use xlink:href="#glyph0-41" x="924" y="119"/>
-  <use xlink:href="#glyph0-41" x="938" y="119"/>
-  <use xlink:href="#glyph0-41" x="952" y="119"/>
-  <use xlink:href="#glyph0-41" x="966" y="119"/>
-  <use xlink:href="#glyph0-41" x="980" y="119"/>
-  <use xlink:href="#glyph0-41" x="994" y="119"/>
-  <use xlink:href="#glyph0-41" x="1008" y="119"/>
-  <use xlink:href="#glyph0-41" x="1022" y="119"/>
-  <use xlink:href="#glyph0-41" x="1036" y="119"/>
-  <use xlink:href="#glyph0-41" x="1050" y="119"/>
-  <use xlink:href="#glyph0-41" x="1064" y="119"/>
-  <use xlink:href="#glyph0-41" x="1078" y="119"/>
-  <use xlink:href="#glyph0-41" x="1092" y="119"/>
-  <use xlink:href="#glyph0-41" x="1106" y="119"/>
-  <use xlink:href="#glyph0-41" x="1120" y="119"/>
-  <use xlink:href="#glyph0-41" x="1134" y="119"/>
-  <use xlink:href="#glyph0-41" x="1148" y="119"/>
-  <use xlink:href="#glyph0-41" x="1162" y="119"/>
-  <use xlink:href="#glyph0-41" x="1176" y="119"/>
-  <use xlink:href="#glyph0-41" x="1190" y="119"/>
-  <use xlink:href="#glyph0-41" x="1204" y="119"/>
-  <use xlink:href="#glyph0-41" x="1218" y="119"/>
-  <use xlink:href="#glyph0-41" x="1232" y="119"/>
-  <use xlink:href="#glyph0-44" x="1246" y="119"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 1260 93 L 1400 93 L 1400 124 L 1260 124 Z M 1260 93 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;" d="M 1400 93 L 1414 93 L 1414 124 L 1400 124 Z M 1400 93 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117647%,15.686275%,21.568627%);fill-opacity:1;" d="M 1414 93 L 1428 93 L 1428 124 L 1414 124 Z M 1414 93 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 0 124 L 140 124 L 140 155 L 0 155 Z M 0 124 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 140 124 L 196 124 L 196 155 L 140 155 Z M 140 124 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-45" x="140" y="150"/>
-  <use xlink:href="#glyph0-1" x="154" y="150"/>
-  <use xlink:href="#glyph0-46" x="168" y="150"/>
-  <use xlink:href="#glyph0-1" x="182" y="150"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 196 124 L 854 124 L 854 155 L 196 155 Z M 196 124 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-9" x="196" y="150"/>
-  <use xlink:href="#glyph0-6" x="210" y="150"/>
-  <use xlink:href="#glyph0-38" x="224" y="150"/>
-  <use xlink:href="#glyph0-4" x="238" y="150"/>
-  <use xlink:href="#glyph0-25" x="252" y="150"/>
-  <use xlink:href="#glyph0-6" x="266" y="150"/>
-  <use xlink:href="#glyph0-1" x="280" y="150"/>
-  <use xlink:href="#glyph0-5" x="294" y="150"/>
-  <use xlink:href="#glyph0-15" x="308" y="150"/>
-  <use xlink:href="#glyph0-33" x="322" y="150"/>
-  <use xlink:href="#glyph0-1" x="336" y="150"/>
-  <use xlink:href="#glyph0-12" x="350" y="150"/>
-  <use xlink:href="#glyph0-4" x="364" y="150"/>
-  <use xlink:href="#glyph0-14" x="378" y="150"/>
-  <use xlink:href="#glyph0-9" x="392" y="150"/>
-  <use xlink:href="#glyph0-15" x="406" y="150"/>
-  <use xlink:href="#glyph0-12" x="420" y="150"/>
-  <use xlink:href="#glyph0-1" x="434" y="150"/>
-  <use xlink:href="#glyph0-1" x="448" y="150"/>
-  <use xlink:href="#glyph0-1" x="462" y="150"/>
-  <use xlink:href="#glyph0-1" x="476" y="150"/>
-  <use xlink:href="#glyph0-1" x="490" y="150"/>
-  <use xlink:href="#glyph0-1" x="504" y="150"/>
-  <use xlink:href="#glyph0-1" x="518" y="150"/>
-  <use xlink:href="#glyph0-1" x="532" y="150"/>
-  <use xlink:href="#glyph0-1" x="546" y="150"/>
-  <use xlink:href="#glyph0-1" x="560" y="150"/>
-  <use xlink:href="#glyph0-1" x="574" y="150"/>
-  <use xlink:href="#glyph0-1" x="588" y="150"/>
-  <use xlink:href="#glyph0-1" x="602" y="150"/>
-  <use xlink:href="#glyph0-1" x="616" y="150"/>
-  <use xlink:href="#glyph0-1" x="630" y="150"/>
-  <use xlink:href="#glyph0-1" x="644" y="150"/>
-  <use xlink:href="#glyph0-1" x="658" y="150"/>
-  <use xlink:href="#glyph0-1" x="672" y="150"/>
-  <use xlink:href="#glyph0-1" x="686" y="150"/>
-  <use xlink:href="#glyph0-1" x="700" y="150"/>
-  <use xlink:href="#glyph0-1" x="714" y="150"/>
-  <use xlink:href="#glyph0-1" x="728" y="150"/>
-  <use xlink:href="#glyph0-1" x="742" y="150"/>
-  <use xlink:href="#glyph0-1" x="756" y="150"/>
-  <use xlink:href="#glyph0-1" x="770" y="150"/>
-  <use xlink:href="#glyph0-1" x="784" y="150"/>
-  <use xlink:href="#glyph0-1" x="798" y="150"/>
-  <use xlink:href="#glyph0-1" x="812" y="150"/>
-  <use xlink:href="#glyph0-1" x="826" y="150"/>
-  <use xlink:href="#glyph0-1" x="840" y="150"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 854 124 L 868 124 L 868 155 L 854 155 Z M 854 124 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 868 124 L 966 124 L 966 155 L 868 155 Z M 868 124 "/>
-<g style="fill:rgb(87.843137%,90.196078%,94.117647%);fill-opacity:1;">
-  <use xlink:href="#glyph0-19" x="868" y="150"/>
-  <use xlink:href="#glyph0-23" x="882" y="150"/>
-  <use xlink:href="#glyph0-14" x="896" y="150"/>
-  <use xlink:href="#glyph0-25" x="910" y="150"/>
-  <use xlink:href="#glyph0-6" x="924" y="150"/>
-  <use xlink:href="#glyph0-5" x="938" y="150"/>
-  <use xlink:href="#glyph0-1" x="952" y="150"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 966 124 L 1050 124 L 1050 155 L 966 155 Z M 966 124 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-19" x="966" y="150"/>
-  <use xlink:href="#glyph0-10" x="980" y="150"/>
-  <use xlink:href="#glyph0-24" x="994" y="150"/>
-  <use xlink:href="#glyph0-5" x="1008" y="150"/>
-  <use xlink:href="#glyph0-28" x="1022" y="150"/>
-  <use xlink:href="#glyph0-25" x="1036" y="150"/>
-</g>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-19" x="967" y="150"/>
-  <use xlink:href="#glyph0-10" x="981" y="150"/>
-  <use xlink:href="#glyph0-24" x="995" y="150"/>
-  <use xlink:href="#glyph0-5" x="1009" y="150"/>
-  <use xlink:href="#glyph0-28" x="1023" y="150"/>
-  <use xlink:href="#glyph0-25" x="1037" y="150"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 1050 124 L 1064 124 L 1064 155 L 1050 155 Z M 1050 124 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 1064 124 L 1078 124 L 1078 155 L 1064 155 Z M 1064 124 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 1078 124 L 1092 124 L 1092 155 L 1078 155 Z M 1078 124 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 1092 124 L 1148 124 L 1148 155 L 1092 155 Z M 1092 124 "/>
-<g style="fill:rgb(87.843137%,90.196078%,94.117647%);fill-opacity:1;">
-  <use xlink:href="#glyph0-35" x="1092" y="150"/>
-  <use xlink:href="#glyph0-27" x="1106" y="150"/>
-  <use xlink:href="#glyph0-14" x="1120" y="150"/>
-  <use xlink:href="#glyph0-1" x="1134" y="150"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 1148 124 L 1218 124 L 1218 155 L 1148 155 Z M 1148 124 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-8" x="1148" y="150"/>
-  <use xlink:href="#glyph0-14" x="1162" y="150"/>
-  <use xlink:href="#glyph0-10" x="1176" y="150"/>
-  <use xlink:href="#glyph0-6" x="1190" y="150"/>
-  <use xlink:href="#glyph0-24" x="1204" y="150"/>
-</g>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-8" x="1149" y="150"/>
-  <use xlink:href="#glyph0-14" x="1163" y="150"/>
-  <use xlink:href="#glyph0-10" x="1177" y="150"/>
-  <use xlink:href="#glyph0-6" x="1191" y="150"/>
-  <use xlink:href="#glyph0-24" x="1205" y="150"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 1218 124 L 1232 124 L 1232 155 L 1218 155 Z M 1218 124 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 1232 124 L 1260 124 L 1260 155 L 1232 155 Z M 1232 124 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="1232" y="150"/>
-  <use xlink:href="#glyph0-45" x="1246" y="150"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 1260 124 L 1400 124 L 1400 155 L 1260 155 Z M 1260 124 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;" d="M 1400 124 L 1414 124 L 1414 155 L 1400 155 Z M 1400 124 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117647%,15.686275%,21.568627%);fill-opacity:1;" d="M 1414 124 L 1428 124 L 1428 155 L 1414 155 Z M 1414 124 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 0 155 L 140 155 L 140 186 L 0 186 Z M 0 155 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 140 155 L 168 155 L 168 186 L 140 186 Z M 140 155 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-45" x="140" y="181"/>
-  <use xlink:href="#glyph0-1" x="154" y="181"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(30.588235%,30.588235%,30.588235%);fill-opacity:1;" d="M 168 155 L 182 155 L 182 186 L 168 186 Z M 168 155 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-2" x="168" y="181"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(30.588235%,30.588235%,30.588235%);fill-opacity:1;" d="M 182 155 L 266 155 L 266 186 L 182 186 Z M 182 155 "/>
-<g style="fill:rgb(53.333333%,93.72549%,53.333333%);fill-opacity:1;">
-  <use xlink:href="#glyph0-13" x="182" y="181"/>
-  <use xlink:href="#glyph0-4" x="196" y="181"/>
-  <use xlink:href="#glyph0-14" x="210" y="181"/>
-  <use xlink:href="#glyph0-9" x="224" y="181"/>
-  <use xlink:href="#glyph0-15" x="238" y="181"/>
-  <use xlink:href="#glyph0-12" x="252" y="181"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(30.588235%,30.588235%,30.588235%);fill-opacity:1;" d="M 266 155 L 448 155 L 448 186 L 266 186 Z M 266 155 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-16" x="266" y="181"/>
-  <use xlink:href="#glyph0-7" x="280" y="181"/>
-  <use xlink:href="#glyph0-1" x="294" y="181"/>
-  <use xlink:href="#glyph0-46" x="308" y="181"/>
-  <use xlink:href="#glyph0-1" x="322" y="181"/>
-  <use xlink:href="#glyph0-47" x="336" y="181"/>
-  <use xlink:href="#glyph0-48" x="350" y="181"/>
-  <use xlink:href="#glyph0-1" x="364" y="181"/>
-  <use xlink:href="#glyph0-3" x="378" y="181"/>
-  <use xlink:href="#glyph0-23" x="392" y="181"/>
-  <use xlink:href="#glyph0-29" x="406" y="181"/>
-  <use xlink:href="#glyph0-6" x="420" y="181"/>
-  <use xlink:href="#glyph0-1" x="434" y="181"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(30.588235%,30.588235%,30.588235%);fill-opacity:1;" d="M 448 155 L 532 155 L 532 186 L 448 186 Z M 448 155 "/>
-<g style="fill:rgb(57.254902%,85.098039%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-31" x="448" y="181"/>
-  <use xlink:href="#glyph0-6" x="462" y="181"/>
-  <use xlink:href="#glyph0-38" x="476" y="181"/>
-  <use xlink:href="#glyph0-4" x="490" y="181"/>
-  <use xlink:href="#glyph0-25" x="504" y="181"/>
-  <use xlink:href="#glyph0-6" x="518" y="181"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(30.588235%,30.588235%,30.588235%);fill-opacity:1;" d="M 532 155 L 546 155 L 546 186 L 532 186 Z M 532 155 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(30.588235%,30.588235%,30.588235%);fill-opacity:1;" d="M 546 155 L 588 155 L 588 186 L 546 186 Z M 546 155 "/>
-<g style="fill:rgb(37.647059%,90.588235%,87.843137%);fill-opacity:1;">
-  <use xlink:href="#glyph0-37" x="546" y="181"/>
-  <use xlink:href="#glyph0-15" x="560" y="181"/>
-  <use xlink:href="#glyph0-33" x="574" y="181"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(30.588235%,30.588235%,30.588235%);fill-opacity:1;" d="M 588 155 L 840 155 L 840 186 L 588 186 Z M 588 155 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-16" x="588" y="181"/>
-  <use xlink:href="#glyph0-1" x="602" y="181"/>
-  <use xlink:href="#glyph0-46" x="616" y="181"/>
-  <use xlink:href="#glyph0-1" x="630" y="181"/>
-  <use xlink:href="#glyph0-2" x="644" y="181"/>
-  <use xlink:href="#glyph0-49" x="658" y="181"/>
-  <use xlink:href="#glyph0-7" x="672" y="181"/>
-  <use xlink:href="#glyph0-1" x="686" y="181"/>
-  <use xlink:href="#glyph0-22" x="700" y="181"/>
-  <use xlink:href="#glyph0-26" x="714" y="181"/>
-  <use xlink:href="#glyph0-15" x="728" y="181"/>
-  <use xlink:href="#glyph0-12" x="742" y="181"/>
-  <use xlink:href="#glyph0-50" x="756" y="181"/>
-  <use xlink:href="#glyph0-17" x="770" y="181"/>
-  <use xlink:href="#glyph0-4" x="784" y="181"/>
-  <use xlink:href="#glyph0-9" x="798" y="181"/>
-  <use xlink:href="#glyph0-6" x="812" y="181"/>
-  <use xlink:href="#glyph0-1" x="826" y="181"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(30.588235%,30.588235%,30.588235%);fill-opacity:1;" d="M 840 155 L 924 155 L 924 186 L 840 186 Z M 840 155 "/>
-<g style="fill:rgb(53.333333%,93.72549%,53.333333%);fill-opacity:1;">
-  <use xlink:href="#glyph0-13" x="840" y="181"/>
-  <use xlink:href="#glyph0-4" x="854" y="181"/>
-  <use xlink:href="#glyph0-14" x="868" y="181"/>
-  <use xlink:href="#glyph0-9" x="882" y="181"/>
-  <use xlink:href="#glyph0-15" x="896" y="181"/>
-  <use xlink:href="#glyph0-12" x="910" y="181"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(30.588235%,30.588235%,30.588235%);fill-opacity:1;" d="M 924 155 L 1232 155 L 1232 186 L 924 186 Z M 924 155 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 1232 155 L 1260 155 L 1260 186 L 1232 186 Z M 1232 155 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="1232" y="181"/>
-  <use xlink:href="#glyph0-45" x="1246" y="181"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 1260 155 L 1400 155 L 1400 186 L 1260 186 Z M 1260 155 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;" d="M 1400 155 L 1414 155 L 1414 186 L 1400 186 Z M 1400 155 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117647%,15.686275%,21.568627%);fill-opacity:1;" d="M 1414 155 L 1428 155 L 1428 186 L 1414 186 Z M 1414 155 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 0 186 L 140 186 L 140 217 L 0 217 Z M 0 186 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 140 186 L 168 186 L 168 217 L 140 217 Z M 140 186 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-45" x="140" y="212"/>
-  <use xlink:href="#glyph0-1" x="154" y="212"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 168 186 L 182 186 L 182 217 L 168 217 Z M 168 186 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-2" x="168" y="212"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 182 186 L 266 186 L 266 217 L 182 217 Z M 182 186 "/>
-<g style="fill:rgb(53.333333%,93.72549%,53.333333%);fill-opacity:1;">
-  <use xlink:href="#glyph0-13" x="182" y="212"/>
-  <use xlink:href="#glyph0-4" x="196" y="212"/>
-  <use xlink:href="#glyph0-14" x="210" y="212"/>
-  <use xlink:href="#glyph0-9" x="224" y="212"/>
-  <use xlink:href="#glyph0-15" x="238" y="212"/>
-  <use xlink:href="#glyph0-12" x="252" y="212"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 266 186 L 448 186 L 448 217 L 266 217 Z M 266 186 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-16" x="266" y="212"/>
-  <use xlink:href="#glyph0-7" x="280" y="212"/>
-  <use xlink:href="#glyph0-1" x="294" y="212"/>
-  <use xlink:href="#glyph0-46" x="308" y="212"/>
-  <use xlink:href="#glyph0-1" x="322" y="212"/>
-  <use xlink:href="#glyph0-47" x="336" y="212"/>
-  <use xlink:href="#glyph0-48" x="350" y="212"/>
-  <use xlink:href="#glyph0-1" x="364" y="212"/>
-  <use xlink:href="#glyph0-3" x="378" y="212"/>
-  <use xlink:href="#glyph0-23" x="392" y="212"/>
-  <use xlink:href="#glyph0-29" x="406" y="212"/>
-  <use xlink:href="#glyph0-6" x="420" y="212"/>
-  <use xlink:href="#glyph0-1" x="434" y="212"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 448 186 L 532 186 L 532 217 L 448 217 Z M 448 186 "/>
-<g style="fill:rgb(57.254902%,85.098039%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-31" x="448" y="212"/>
-  <use xlink:href="#glyph0-6" x="462" y="212"/>
-  <use xlink:href="#glyph0-38" x="476" y="212"/>
-  <use xlink:href="#glyph0-4" x="490" y="212"/>
-  <use xlink:href="#glyph0-25" x="504" y="212"/>
-  <use xlink:href="#glyph0-6" x="518" y="212"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 532 186 L 546 186 L 546 217 L 532 217 Z M 532 186 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 546 186 L 588 186 L 588 217 L 546 217 Z M 546 186 "/>
-<g style="fill:rgb(37.647059%,90.588235%,87.843137%);fill-opacity:1;">
-  <use xlink:href="#glyph0-37" x="546" y="212"/>
-  <use xlink:href="#glyph0-15" x="560" y="212"/>
-  <use xlink:href="#glyph0-33" x="574" y="212"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 588 186 L 1232 186 L 1232 217 L 588 217 Z M 588 186 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-16" x="588" y="212"/>
-  <use xlink:href="#glyph0-1" x="602" y="212"/>
-  <use xlink:href="#glyph0-46" x="616" y="212"/>
-  <use xlink:href="#glyph0-1" x="630" y="212"/>
-  <use xlink:href="#glyph0-22" x="644" y="212"/>
-  <use xlink:href="#glyph0-23" x="658" y="212"/>
-  <use xlink:href="#glyph0-38" x="672" y="212"/>
-  <use xlink:href="#glyph0-6" x="686" y="212"/>
-  <use xlink:href="#glyph0-50" x="700" y="212"/>
-  <use xlink:href="#glyph0-8" x="714" y="212"/>
-  <use xlink:href="#glyph0-51" x="728" y="212"/>
-  <use xlink:href="#glyph0-18" x="742" y="212"/>
-  <use xlink:href="#glyph0-15" x="756" y="212"/>
-  <use xlink:href="#glyph0-24" x="770" y="212"/>
-  <use xlink:href="#glyph0-10" x="784" y="212"/>
-  <use xlink:href="#glyph0-1" x="798" y="212"/>
-  <use xlink:href="#glyph0-23" x="812" y="212"/>
-  <use xlink:href="#glyph0-1" x="826" y="212"/>
-  <use xlink:href="#glyph0-25" x="840" y="212"/>
-  <use xlink:href="#glyph0-15" x="854" y="212"/>
-  <use xlink:href="#glyph0-18" x="868" y="212"/>
-  <use xlink:href="#glyph0-34" x="882" y="212"/>
-  <use xlink:href="#glyph0-1" x="896" y="212"/>
-  <use xlink:href="#glyph0-1" x="910" y="212"/>
-  <use xlink:href="#glyph0-1" x="924" y="212"/>
-  <use xlink:href="#glyph0-1" x="938" y="212"/>
-  <use xlink:href="#glyph0-1" x="952" y="212"/>
-  <use xlink:href="#glyph0-1" x="966" y="212"/>
-  <use xlink:href="#glyph0-1" x="980" y="212"/>
-  <use xlink:href="#glyph0-1" x="994" y="212"/>
-  <use xlink:href="#glyph0-1" x="1008" y="212"/>
-  <use xlink:href="#glyph0-1" x="1022" y="212"/>
-  <use xlink:href="#glyph0-1" x="1036" y="212"/>
-  <use xlink:href="#glyph0-1" x="1050" y="212"/>
-  <use xlink:href="#glyph0-1" x="1064" y="212"/>
-  <use xlink:href="#glyph0-1" x="1078" y="212"/>
-  <use xlink:href="#glyph0-1" x="1092" y="212"/>
-  <use xlink:href="#glyph0-1" x="1106" y="212"/>
-  <use xlink:href="#glyph0-1" x="1120" y="212"/>
-  <use xlink:href="#glyph0-1" x="1134" y="212"/>
-  <use xlink:href="#glyph0-1" x="1148" y="212"/>
-  <use xlink:href="#glyph0-1" x="1162" y="212"/>
-  <use xlink:href="#glyph0-1" x="1176" y="212"/>
-  <use xlink:href="#glyph0-1" x="1190" y="212"/>
-  <use xlink:href="#glyph0-1" x="1204" y="212"/>
-  <use xlink:href="#glyph0-1" x="1218" y="212"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 1232 186 L 1260 186 L 1260 217 L 1232 217 Z M 1232 186 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="1232" y="212"/>
-  <use xlink:href="#glyph0-45" x="1246" y="212"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 1260 186 L 1400 186 L 1400 217 L 1260 217 Z M 1260 186 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;" d="M 1400 186 L 1414 186 L 1414 217 L 1400 217 Z M 1400 186 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117647%,15.686275%,21.568627%);fill-opacity:1;" d="M 1414 186 L 1428 186 L 1428 217 L 1414 217 Z M 1414 186 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 0 217 L 140 217 L 140 248 L 0 248 Z M 0 217 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 140 217 L 168 217 L 168 248 L 140 248 Z M 140 217 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-45" x="140" y="243"/>
-  <use xlink:href="#glyph0-1" x="154" y="243"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 168 217 L 182 217 L 182 248 L 168 248 Z M 168 217 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-2" x="168" y="243"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 182 217 L 266 217 L 266 248 L 182 248 Z M 182 217 "/>
-<g style="fill:rgb(53.333333%,93.72549%,53.333333%);fill-opacity:1;">
-  <use xlink:href="#glyph0-13" x="182" y="243"/>
-  <use xlink:href="#glyph0-4" x="196" y="243"/>
-  <use xlink:href="#glyph0-14" x="210" y="243"/>
-  <use xlink:href="#glyph0-9" x="224" y="243"/>
-  <use xlink:href="#glyph0-15" x="238" y="243"/>
-  <use xlink:href="#glyph0-12" x="252" y="243"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 266 217 L 448 217 L 448 248 L 266 248 Z M 266 217 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-16" x="266" y="243"/>
-  <use xlink:href="#glyph0-7" x="280" y="243"/>
-  <use xlink:href="#glyph0-1" x="294" y="243"/>
-  <use xlink:href="#glyph0-46" x="308" y="243"/>
-  <use xlink:href="#glyph0-1" x="322" y="243"/>
-  <use xlink:href="#glyph0-47" x="336" y="243"/>
-  <use xlink:href="#glyph0-48" x="350" y="243"/>
-  <use xlink:href="#glyph0-1" x="364" y="243"/>
-  <use xlink:href="#glyph0-3" x="378" y="243"/>
-  <use xlink:href="#glyph0-23" x="392" y="243"/>
-  <use xlink:href="#glyph0-29" x="406" y="243"/>
-  <use xlink:href="#glyph0-6" x="420" y="243"/>
-  <use xlink:href="#glyph0-1" x="434" y="243"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 448 217 L 532 217 L 532 248 L 448 248 Z M 448 217 "/>
-<g style="fill:rgb(57.254902%,85.098039%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-31" x="448" y="243"/>
-  <use xlink:href="#glyph0-6" x="462" y="243"/>
-  <use xlink:href="#glyph0-38" x="476" y="243"/>
-  <use xlink:href="#glyph0-4" x="490" y="243"/>
-  <use xlink:href="#glyph0-25" x="504" y="243"/>
-  <use xlink:href="#glyph0-6" x="518" y="243"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 532 217 L 546 217 L 546 248 L 532 248 Z M 532 217 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 546 217 L 588 217 L 588 248 L 546 248 Z M 546 217 "/>
-<g style="fill:rgb(37.647059%,90.588235%,87.843137%);fill-opacity:1;">
-  <use xlink:href="#glyph0-37" x="546" y="243"/>
-  <use xlink:href="#glyph0-15" x="560" y="243"/>
-  <use xlink:href="#glyph0-33" x="574" y="243"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 588 217 L 1232 217 L 1232 248 L 588 248 Z M 588 217 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-16" x="588" y="243"/>
-  <use xlink:href="#glyph0-1" x="602" y="243"/>
-  <use xlink:href="#glyph0-46" x="616" y="243"/>
-  <use xlink:href="#glyph0-1" x="630" y="243"/>
-  <use xlink:href="#glyph0-2" x="644" y="243"/>
-  <use xlink:href="#glyph0-1" x="658" y="243"/>
-  <use xlink:href="#glyph0-7" x="672" y="243"/>
-  <use xlink:href="#glyph0-1" x="686" y="243"/>
-  <use xlink:href="#glyph0-37" x="700" y="243"/>
-  <use xlink:href="#glyph0-4" x="714" y="243"/>
-  <use xlink:href="#glyph0-14" x="728" y="243"/>
-  <use xlink:href="#glyph0-6" x="742" y="243"/>
-  <use xlink:href="#glyph0-1" x="756" y="243"/>
-  <use xlink:href="#glyph0-12" x="770" y="243"/>
-  <use xlink:href="#glyph0-24" x="784" y="243"/>
-  <use xlink:href="#glyph0-23" x="798" y="243"/>
-  <use xlink:href="#glyph0-18" x="812" y="243"/>
-  <use xlink:href="#glyph0-18" x="826" y="243"/>
-  <use xlink:href="#glyph0-4" x="840" y="243"/>
-  <use xlink:href="#glyph0-14" x="854" y="243"/>
-  <use xlink:href="#glyph0-33" x="868" y="243"/>
-  <use xlink:href="#glyph0-1" x="882" y="243"/>
-  <use xlink:href="#glyph0-1" x="896" y="243"/>
-  <use xlink:href="#glyph0-1" x="910" y="243"/>
-  <use xlink:href="#glyph0-1" x="924" y="243"/>
-  <use xlink:href="#glyph0-1" x="938" y="243"/>
-  <use xlink:href="#glyph0-1" x="952" y="243"/>
-  <use xlink:href="#glyph0-1" x="966" y="243"/>
-  <use xlink:href="#glyph0-1" x="980" y="243"/>
-  <use xlink:href="#glyph0-1" x="994" y="243"/>
-  <use xlink:href="#glyph0-1" x="1008" y="243"/>
-  <use xlink:href="#glyph0-1" x="1022" y="243"/>
-  <use xlink:href="#glyph0-1" x="1036" y="243"/>
-  <use xlink:href="#glyph0-1" x="1050" y="243"/>
-  <use xlink:href="#glyph0-1" x="1064" y="243"/>
-  <use xlink:href="#glyph0-1" x="1078" y="243"/>
-  <use xlink:href="#glyph0-1" x="1092" y="243"/>
-  <use xlink:href="#glyph0-1" x="1106" y="243"/>
-  <use xlink:href="#glyph0-1" x="1120" y="243"/>
-  <use xlink:href="#glyph0-1" x="1134" y="243"/>
-  <use xlink:href="#glyph0-1" x="1148" y="243"/>
-  <use xlink:href="#glyph0-1" x="1162" y="243"/>
-  <use xlink:href="#glyph0-1" x="1176" y="243"/>
-  <use xlink:href="#glyph0-1" x="1190" y="243"/>
-  <use xlink:href="#glyph0-1" x="1204" y="243"/>
-  <use xlink:href="#glyph0-1" x="1218" y="243"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 1232 217 L 1260 217 L 1260 248 L 1232 248 Z M 1232 217 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="1232" y="243"/>
-  <use xlink:href="#glyph0-45" x="1246" y="243"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 1260 217 L 1400 217 L 1400 248 L 1260 248 Z M 1260 217 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;" d="M 1400 217 L 1414 217 L 1414 248 L 1400 248 Z M 1400 217 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117647%,15.686275%,21.568627%);fill-opacity:1;" d="M 1414 217 L 1428 217 L 1428 248 L 1414 248 Z M 1414 217 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 0 248 L 140 248 L 140 279 L 0 279 Z M 0 248 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 140 248 L 168 248 L 168 279 L 140 279 Z M 140 248 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-45" x="140" y="274"/>
-  <use xlink:href="#glyph0-1" x="154" y="274"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 168 248 L 182 248 L 182 279 L 168 279 Z M 168 248 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-2" x="168" y="274"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 182 248 L 266 248 L 266 279 L 182 279 Z M 182 248 "/>
-<g style="fill:rgb(53.333333%,93.72549%,53.333333%);fill-opacity:1;">
-  <use xlink:href="#glyph0-13" x="182" y="274"/>
-  <use xlink:href="#glyph0-4" x="196" y="274"/>
-  <use xlink:href="#glyph0-14" x="210" y="274"/>
-  <use xlink:href="#glyph0-9" x="224" y="274"/>
-  <use xlink:href="#glyph0-15" x="238" y="274"/>
-  <use xlink:href="#glyph0-12" x="252" y="274"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 266 248 L 448 248 L 448 279 L 266 279 Z M 266 248 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-16" x="266" y="274"/>
-  <use xlink:href="#glyph0-7" x="280" y="274"/>
-  <use xlink:href="#glyph0-1" x="294" y="274"/>
-  <use xlink:href="#glyph0-46" x="308" y="274"/>
-  <use xlink:href="#glyph0-1" x="322" y="274"/>
-  <use xlink:href="#glyph0-47" x="336" y="274"/>
-  <use xlink:href="#glyph0-48" x="350" y="274"/>
-  <use xlink:href="#glyph0-1" x="364" y="274"/>
-  <use xlink:href="#glyph0-3" x="378" y="274"/>
-  <use xlink:href="#glyph0-23" x="392" y="274"/>
-  <use xlink:href="#glyph0-29" x="406" y="274"/>
-  <use xlink:href="#glyph0-6" x="420" y="274"/>
-  <use xlink:href="#glyph0-1" x="434" y="274"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 448 248 L 532 248 L 532 279 L 448 279 Z M 448 248 "/>
-<g style="fill:rgb(57.254902%,85.098039%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-31" x="448" y="274"/>
-  <use xlink:href="#glyph0-6" x="462" y="274"/>
-  <use xlink:href="#glyph0-38" x="476" y="274"/>
-  <use xlink:href="#glyph0-4" x="490" y="274"/>
-  <use xlink:href="#glyph0-25" x="504" y="274"/>
-  <use xlink:href="#glyph0-6" x="518" y="274"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 532 248 L 546 248 L 546 279 L 532 279 Z M 532 248 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 546 248 L 588 248 L 588 279 L 546 279 Z M 546 248 "/>
-<g style="fill:rgb(37.647059%,90.588235%,87.843137%);fill-opacity:1;">
-  <use xlink:href="#glyph0-37" x="546" y="274"/>
-  <use xlink:href="#glyph0-15" x="560" y="274"/>
-  <use xlink:href="#glyph0-33" x="574" y="274"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 588 248 L 1232 248 L 1232 279 L 588 279 Z M 588 248 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-16" x="588" y="274"/>
-  <use xlink:href="#glyph0-1" x="602" y="274"/>
-  <use xlink:href="#glyph0-46" x="616" y="274"/>
-  <use xlink:href="#glyph0-1" x="630" y="274"/>
-  <use xlink:href="#glyph0-2" x="644" y="274"/>
-  <use xlink:href="#glyph0-49" x="658" y="274"/>
-  <use xlink:href="#glyph0-7" x="672" y="274"/>
-  <use xlink:href="#glyph0-1" x="686" y="274"/>
-  <use xlink:href="#glyph0-20" x="700" y="274"/>
-  <use xlink:href="#glyph0-23" x="714" y="274"/>
-  <use xlink:href="#glyph0-32" x="728" y="274"/>
-  <use xlink:href="#glyph0-5" x="742" y="274"/>
-  <use xlink:href="#glyph0-6" x="756" y="274"/>
-  <use xlink:href="#glyph0-1" x="770" y="274"/>
-  <use xlink:href="#glyph0-38" x="784" y="274"/>
-  <use xlink:href="#glyph0-4" x="798" y="274"/>
-  <use xlink:href="#glyph0-6" x="812" y="274"/>
-  <use xlink:href="#glyph0-12" x="826" y="274"/>
-  <use xlink:href="#glyph0-1" x="840" y="274"/>
-  <use xlink:href="#glyph0-1" x="854" y="274"/>
-  <use xlink:href="#glyph0-1" x="868" y="274"/>
-  <use xlink:href="#glyph0-1" x="882" y="274"/>
-  <use xlink:href="#glyph0-1" x="896" y="274"/>
-  <use xlink:href="#glyph0-1" x="910" y="274"/>
-  <use xlink:href="#glyph0-1" x="924" y="274"/>
-  <use xlink:href="#glyph0-1" x="938" y="274"/>
-  <use xlink:href="#glyph0-1" x="952" y="274"/>
-  <use xlink:href="#glyph0-1" x="966" y="274"/>
-  <use xlink:href="#glyph0-1" x="980" y="274"/>
-  <use xlink:href="#glyph0-1" x="994" y="274"/>
-  <use xlink:href="#glyph0-1" x="1008" y="274"/>
-  <use xlink:href="#glyph0-1" x="1022" y="274"/>
-  <use xlink:href="#glyph0-1" x="1036" y="274"/>
-  <use xlink:href="#glyph0-1" x="1050" y="274"/>
-  <use xlink:href="#glyph0-1" x="1064" y="274"/>
-  <use xlink:href="#glyph0-1" x="1078" y="274"/>
-  <use xlink:href="#glyph0-1" x="1092" y="274"/>
-  <use xlink:href="#glyph0-1" x="1106" y="274"/>
-  <use xlink:href="#glyph0-1" x="1120" y="274"/>
-  <use xlink:href="#glyph0-1" x="1134" y="274"/>
-  <use xlink:href="#glyph0-1" x="1148" y="274"/>
-  <use xlink:href="#glyph0-1" x="1162" y="274"/>
-  <use xlink:href="#glyph0-1" x="1176" y="274"/>
-  <use xlink:href="#glyph0-1" x="1190" y="274"/>
-  <use xlink:href="#glyph0-1" x="1204" y="274"/>
-  <use xlink:href="#glyph0-1" x="1218" y="274"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 1232 248 L 1260 248 L 1260 279 L 1232 279 Z M 1232 248 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="1232" y="274"/>
-  <use xlink:href="#glyph0-45" x="1246" y="274"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 1260 248 L 1400 248 L 1400 279 L 1260 279 Z M 1260 248 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;" d="M 1400 248 L 1414 248 L 1414 279 L 1400 279 Z M 1400 248 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117647%,15.686275%,21.568627%);fill-opacity:1;" d="M 1414 248 L 1428 248 L 1428 279 L 1414 279 Z M 1414 248 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 0 279 L 140 279 L 140 310 L 0 310 Z M 0 279 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 140 279 L 168 279 L 168 310 L 140 310 Z M 140 279 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-45" x="140" y="305"/>
-  <use xlink:href="#glyph0-1" x="154" y="305"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 168 279 L 182 279 L 182 310 L 168 310 Z M 168 279 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-2" x="168" y="305"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 182 279 L 266 279 L 266 310 L 182 310 Z M 182 279 "/>
-<g style="fill:rgb(53.333333%,93.72549%,53.333333%);fill-opacity:1;">
-  <use xlink:href="#glyph0-13" x="182" y="305"/>
-  <use xlink:href="#glyph0-4" x="196" y="305"/>
-  <use xlink:href="#glyph0-14" x="210" y="305"/>
-  <use xlink:href="#glyph0-9" x="224" y="305"/>
-  <use xlink:href="#glyph0-15" x="238" y="305"/>
-  <use xlink:href="#glyph0-12" x="252" y="305"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 266 279 L 448 279 L 448 310 L 266 310 Z M 266 279 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-16" x="266" y="305"/>
-  <use xlink:href="#glyph0-7" x="280" y="305"/>
-  <use xlink:href="#glyph0-1" x="294" y="305"/>
-  <use xlink:href="#glyph0-46" x="308" y="305"/>
-  <use xlink:href="#glyph0-1" x="322" y="305"/>
-  <use xlink:href="#glyph0-47" x="336" y="305"/>
-  <use xlink:href="#glyph0-48" x="350" y="305"/>
-  <use xlink:href="#glyph0-1" x="364" y="305"/>
-  <use xlink:href="#glyph0-3" x="378" y="305"/>
-  <use xlink:href="#glyph0-23" x="392" y="305"/>
-  <use xlink:href="#glyph0-29" x="406" y="305"/>
-  <use xlink:href="#glyph0-6" x="420" y="305"/>
-  <use xlink:href="#glyph0-1" x="434" y="305"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 448 279 L 532 279 L 532 310 L 448 310 Z M 448 279 "/>
-<g style="fill:rgb(57.254902%,85.098039%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-31" x="448" y="305"/>
-  <use xlink:href="#glyph0-6" x="462" y="305"/>
-  <use xlink:href="#glyph0-38" x="476" y="305"/>
-  <use xlink:href="#glyph0-4" x="490" y="305"/>
-  <use xlink:href="#glyph0-25" x="504" y="305"/>
-  <use xlink:href="#glyph0-6" x="518" y="305"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 532 279 L 546 279 L 546 310 L 532 310 Z M 532 279 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 546 279 L 588 279 L 588 310 L 546 310 Z M 546 279 "/>
-<g style="fill:rgb(37.647059%,90.588235%,87.843137%);fill-opacity:1;">
-  <use xlink:href="#glyph0-37" x="546" y="305"/>
-  <use xlink:href="#glyph0-15" x="560" y="305"/>
-  <use xlink:href="#glyph0-33" x="574" y="305"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 588 279 L 1232 279 L 1232 310 L 588 310 Z M 588 279 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-16" x="588" y="305"/>
-  <use xlink:href="#glyph0-1" x="602" y="305"/>
-  <use xlink:href="#glyph0-46" x="616" y="305"/>
-  <use xlink:href="#glyph0-1" x="630" y="305"/>
-  <use xlink:href="#glyph0-2" x="644" y="305"/>
-  <use xlink:href="#glyph0-49" x="658" y="305"/>
-  <use xlink:href="#glyph0-7" x="672" y="305"/>
-  <use xlink:href="#glyph0-1" x="686" y="305"/>
-  <use xlink:href="#glyph0-3" x="700" y="305"/>
-  <use xlink:href="#glyph0-15" x="714" y="305"/>
-  <use xlink:href="#glyph0-5" x="728" y="305"/>
-  <use xlink:href="#glyph0-5" x="742" y="305"/>
-  <use xlink:href="#glyph0-15" x="756" y="305"/>
-  <use xlink:href="#glyph0-12" x="770" y="305"/>
-  <use xlink:href="#glyph0-1" x="784" y="305"/>
-  <use xlink:href="#glyph0-1" x="798" y="305"/>
-  <use xlink:href="#glyph0-1" x="812" y="305"/>
-  <use xlink:href="#glyph0-1" x="826" y="305"/>
-  <use xlink:href="#glyph0-1" x="840" y="305"/>
-  <use xlink:href="#glyph0-1" x="854" y="305"/>
-  <use xlink:href="#glyph0-1" x="868" y="305"/>
-  <use xlink:href="#glyph0-1" x="882" y="305"/>
-  <use xlink:href="#glyph0-1" x="896" y="305"/>
-  <use xlink:href="#glyph0-1" x="910" y="305"/>
-  <use xlink:href="#glyph0-1" x="924" y="305"/>
-  <use xlink:href="#glyph0-1" x="938" y="305"/>
-  <use xlink:href="#glyph0-1" x="952" y="305"/>
-  <use xlink:href="#glyph0-1" x="966" y="305"/>
-  <use xlink:href="#glyph0-1" x="980" y="305"/>
-  <use xlink:href="#glyph0-1" x="994" y="305"/>
-  <use xlink:href="#glyph0-1" x="1008" y="305"/>
-  <use xlink:href="#glyph0-1" x="1022" y="305"/>
-  <use xlink:href="#glyph0-1" x="1036" y="305"/>
-  <use xlink:href="#glyph0-1" x="1050" y="305"/>
-  <use xlink:href="#glyph0-1" x="1064" y="305"/>
-  <use xlink:href="#glyph0-1" x="1078" y="305"/>
-  <use xlink:href="#glyph0-1" x="1092" y="305"/>
-  <use xlink:href="#glyph0-1" x="1106" y="305"/>
-  <use xlink:href="#glyph0-1" x="1120" y="305"/>
-  <use xlink:href="#glyph0-1" x="1134" y="305"/>
-  <use xlink:href="#glyph0-1" x="1148" y="305"/>
-  <use xlink:href="#glyph0-1" x="1162" y="305"/>
-  <use xlink:href="#glyph0-1" x="1176" y="305"/>
-  <use xlink:href="#glyph0-1" x="1190" y="305"/>
-  <use xlink:href="#glyph0-1" x="1204" y="305"/>
-  <use xlink:href="#glyph0-1" x="1218" y="305"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 1232 279 L 1260 279 L 1260 310 L 1232 310 Z M 1232 279 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="1232" y="305"/>
-  <use xlink:href="#glyph0-45" x="1246" y="305"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 1260 279 L 1400 279 L 1400 310 L 1260 310 Z M 1260 279 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;" d="M 1400 279 L 1414 279 L 1414 310 L 1400 310 Z M 1400 279 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117647%,15.686275%,21.568627%);fill-opacity:1;" d="M 1414 279 L 1428 279 L 1428 310 L 1414 310 Z M 1414 279 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 0 310 L 140 310 L 140 341 L 0 341 Z M 0 310 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 140 310 L 168 310 L 168 341 L 140 341 Z M 140 310 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-45" x="140" y="336"/>
-  <use xlink:href="#glyph0-1" x="154" y="336"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 168 310 L 182 310 L 182 341 L 168 341 Z M 168 310 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-2" x="168" y="336"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 182 310 L 266 310 L 266 341 L 182 341 Z M 182 310 "/>
-<g style="fill:rgb(53.333333%,93.72549%,53.333333%);fill-opacity:1;">
-  <use xlink:href="#glyph0-13" x="182" y="336"/>
-  <use xlink:href="#glyph0-4" x="196" y="336"/>
-  <use xlink:href="#glyph0-14" x="210" y="336"/>
-  <use xlink:href="#glyph0-9" x="224" y="336"/>
-  <use xlink:href="#glyph0-15" x="238" y="336"/>
-  <use xlink:href="#glyph0-12" x="252" y="336"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 266 310 L 448 310 L 448 341 L 266 341 Z M 266 310 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-16" x="266" y="336"/>
-  <use xlink:href="#glyph0-7" x="280" y="336"/>
-  <use xlink:href="#glyph0-1" x="294" y="336"/>
-  <use xlink:href="#glyph0-46" x="308" y="336"/>
-  <use xlink:href="#glyph0-1" x="322" y="336"/>
-  <use xlink:href="#glyph0-47" x="336" y="336"/>
-  <use xlink:href="#glyph0-48" x="350" y="336"/>
-  <use xlink:href="#glyph0-1" x="364" y="336"/>
-  <use xlink:href="#glyph0-3" x="378" y="336"/>
-  <use xlink:href="#glyph0-23" x="392" y="336"/>
-  <use xlink:href="#glyph0-29" x="406" y="336"/>
-  <use xlink:href="#glyph0-6" x="420" y="336"/>
-  <use xlink:href="#glyph0-1" x="434" y="336"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 448 310 L 532 310 L 532 341 L 448 341 Z M 448 310 "/>
-<g style="fill:rgb(57.254902%,85.098039%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-31" x="448" y="336"/>
-  <use xlink:href="#glyph0-6" x="462" y="336"/>
-  <use xlink:href="#glyph0-38" x="476" y="336"/>
-  <use xlink:href="#glyph0-4" x="490" y="336"/>
-  <use xlink:href="#glyph0-25" x="504" y="336"/>
-  <use xlink:href="#glyph0-6" x="518" y="336"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 532 310 L 546 310 L 546 341 L 532 341 Z M 532 310 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 546 310 L 588 310 L 588 341 L 546 341 Z M 546 310 "/>
-<g style="fill:rgb(37.647059%,90.588235%,87.843137%);fill-opacity:1;">
-  <use xlink:href="#glyph0-37" x="546" y="336"/>
-  <use xlink:href="#glyph0-15" x="560" y="336"/>
-  <use xlink:href="#glyph0-33" x="574" y="336"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 588 310 L 1232 310 L 1232 341 L 588 341 Z M 588 310 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-16" x="588" y="336"/>
-  <use xlink:href="#glyph0-1" x="602" y="336"/>
-  <use xlink:href="#glyph0-46" x="616" y="336"/>
-  <use xlink:href="#glyph0-1" x="630" y="336"/>
-  <use xlink:href="#glyph0-19" x="644" y="336"/>
-  <use xlink:href="#glyph0-5" x="658" y="336"/>
-  <use xlink:href="#glyph0-6" x="672" y="336"/>
-  <use xlink:href="#glyph0-23" x="686" y="336"/>
-  <use xlink:href="#glyph0-24" x="700" y="336"/>
-  <use xlink:href="#glyph0-1" x="714" y="336"/>
-  <use xlink:href="#glyph0-26" x="728" y="336"/>
-  <use xlink:href="#glyph0-4" x="742" y="336"/>
-  <use xlink:href="#glyph0-16" x="756" y="336"/>
-  <use xlink:href="#glyph0-10" x="770" y="336"/>
-  <use xlink:href="#glyph0-15" x="784" y="336"/>
-  <use xlink:href="#glyph0-24" x="798" y="336"/>
-  <use xlink:href="#glyph0-34" x="812" y="336"/>
-  <use xlink:href="#glyph0-1" x="826" y="336"/>
-  <use xlink:href="#glyph0-1" x="840" y="336"/>
-  <use xlink:href="#glyph0-1" x="854" y="336"/>
-  <use xlink:href="#glyph0-1" x="868" y="336"/>
-  <use xlink:href="#glyph0-1" x="882" y="336"/>
-  <use xlink:href="#glyph0-1" x="896" y="336"/>
-  <use xlink:href="#glyph0-1" x="910" y="336"/>
-  <use xlink:href="#glyph0-1" x="924" y="336"/>
-  <use xlink:href="#glyph0-1" x="938" y="336"/>
-  <use xlink:href="#glyph0-1" x="952" y="336"/>
-  <use xlink:href="#glyph0-1" x="966" y="336"/>
-  <use xlink:href="#glyph0-1" x="980" y="336"/>
-  <use xlink:href="#glyph0-1" x="994" y="336"/>
-  <use xlink:href="#glyph0-1" x="1008" y="336"/>
-  <use xlink:href="#glyph0-1" x="1022" y="336"/>
-  <use xlink:href="#glyph0-1" x="1036" y="336"/>
-  <use xlink:href="#glyph0-1" x="1050" y="336"/>
-  <use xlink:href="#glyph0-1" x="1064" y="336"/>
-  <use xlink:href="#glyph0-1" x="1078" y="336"/>
-  <use xlink:href="#glyph0-1" x="1092" y="336"/>
-  <use xlink:href="#glyph0-1" x="1106" y="336"/>
-  <use xlink:href="#glyph0-1" x="1120" y="336"/>
-  <use xlink:href="#glyph0-1" x="1134" y="336"/>
-  <use xlink:href="#glyph0-1" x="1148" y="336"/>
-  <use xlink:href="#glyph0-1" x="1162" y="336"/>
-  <use xlink:href="#glyph0-1" x="1176" y="336"/>
-  <use xlink:href="#glyph0-1" x="1190" y="336"/>
-  <use xlink:href="#glyph0-1" x="1204" y="336"/>
-  <use xlink:href="#glyph0-1" x="1218" y="336"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 1232 310 L 1260 310 L 1260 341 L 1232 341 Z M 1232 310 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="1232" y="336"/>
-  <use xlink:href="#glyph0-45" x="1246" y="336"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 1260 310 L 1400 310 L 1400 341 L 1260 341 Z M 1260 310 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(6.27451%,5.882353%,6.27451%);fill-opacity:1;" d="M 1400 310 L 1414 310 L 1414 341 L 1400 341 Z M 1400 310 "/>
-<g style="fill:rgb(57.254902%,85.098039%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-38" x="1400" y="336"/>
-</g>
-<g style="fill:rgb(57.254902%,85.098039%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-38" x="1401" y="336"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117647%,15.686275%,21.568627%);fill-opacity:1;" d="M 1414 310 L 1428 310 L 1428 341 L 1414 341 Z M 1414 310 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 0 341 L 14 341 L 14 372 L 0 372 Z M 0 341 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 14 341 L 168 341 L 168 372 L 14 372 Z M 14 341 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="14" y="367"/>
-  <use xlink:href="#glyph0-30" x="28" y="367"/>
-  <use xlink:href="#glyph0-34" x="42" y="367"/>
-  <use xlink:href="#glyph0-10" x="56" y="367"/>
-  <use xlink:href="#glyph0-26" x="70" y="367"/>
-  <use xlink:href="#glyph0-15" x="84" y="367"/>
-  <use xlink:href="#glyph0-14" x="98" y="367"/>
-  <use xlink:href="#glyph0-1" x="112" y="367"/>
-  <use xlink:href="#glyph0-35" x="126" y="367"/>
-  <use xlink:href="#glyph0-45" x="140" y="367"/>
-  <use xlink:href="#glyph0-1" x="154" y="367"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 168 341 L 182 341 L 182 372 L 168 372 Z M 168 341 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-2" x="168" y="367"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 182 341 L 266 341 L 266 372 L 182 372 Z M 182 341 "/>
-<g style="fill:rgb(53.333333%,93.72549%,53.333333%);fill-opacity:1;">
-  <use xlink:href="#glyph0-13" x="182" y="367"/>
-  <use xlink:href="#glyph0-4" x="196" y="367"/>
-  <use xlink:href="#glyph0-14" x="210" y="367"/>
-  <use xlink:href="#glyph0-9" x="224" y="367"/>
-  <use xlink:href="#glyph0-15" x="238" y="367"/>
-  <use xlink:href="#glyph0-12" x="252" y="367"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 266 341 L 448 341 L 448 372 L 266 372 Z M 266 341 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-16" x="266" y="367"/>
-  <use xlink:href="#glyph0-7" x="280" y="367"/>
-  <use xlink:href="#glyph0-1" x="294" y="367"/>
-  <use xlink:href="#glyph0-46" x="308" y="367"/>
-  <use xlink:href="#glyph0-1" x="322" y="367"/>
-  <use xlink:href="#glyph0-47" x="336" y="367"/>
-  <use xlink:href="#glyph0-48" x="350" y="367"/>
-  <use xlink:href="#glyph0-1" x="364" y="367"/>
-  <use xlink:href="#glyph0-3" x="378" y="367"/>
-  <use xlink:href="#glyph0-23" x="392" y="367"/>
-  <use xlink:href="#glyph0-29" x="406" y="367"/>
-  <use xlink:href="#glyph0-6" x="420" y="367"/>
-  <use xlink:href="#glyph0-1" x="434" y="367"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 448 341 L 532 341 L 532 372 L 448 372 Z M 448 341 "/>
-<g style="fill:rgb(57.254902%,85.098039%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-31" x="448" y="367"/>
-  <use xlink:href="#glyph0-6" x="462" y="367"/>
-  <use xlink:href="#glyph0-38" x="476" y="367"/>
-  <use xlink:href="#glyph0-4" x="490" y="367"/>
-  <use xlink:href="#glyph0-25" x="504" y="367"/>
-  <use xlink:href="#glyph0-6" x="518" y="367"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 532 341 L 546 341 L 546 372 L 532 372 Z M 532 341 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 546 341 L 588 341 L 588 372 L 546 372 Z M 546 341 "/>
-<g style="fill:rgb(37.647059%,90.588235%,87.843137%);fill-opacity:1;">
-  <use xlink:href="#glyph0-37" x="546" y="367"/>
-  <use xlink:href="#glyph0-15" x="560" y="367"/>
-  <use xlink:href="#glyph0-33" x="574" y="367"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 588 341 L 1232 341 L 1232 372 L 588 372 Z M 588 341 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-16" x="588" y="367"/>
-  <use xlink:href="#glyph0-1" x="602" y="367"/>
-  <use xlink:href="#glyph0-46" x="616" y="367"/>
-  <use xlink:href="#glyph0-1" x="630" y="367"/>
-  <use xlink:href="#glyph0-31" x="644" y="367"/>
-  <use xlink:href="#glyph0-27" x="658" y="367"/>
-  <use xlink:href="#glyph0-18" x="672" y="367"/>
-  <use xlink:href="#glyph0-5" x="686" y="367"/>
-  <use xlink:href="#glyph0-4" x="700" y="367"/>
-  <use xlink:href="#glyph0-25" x="714" y="367"/>
-  <use xlink:href="#glyph0-23" x="728" y="367"/>
-  <use xlink:href="#glyph0-10" x="742" y="367"/>
-  <use xlink:href="#glyph0-6" x="756" y="367"/>
-  <use xlink:href="#glyph0-1" x="770" y="367"/>
-  <use xlink:href="#glyph0-18" x="784" y="367"/>
-  <use xlink:href="#glyph0-23" x="798" y="367"/>
-  <use xlink:href="#glyph0-14" x="812" y="367"/>
-  <use xlink:href="#glyph0-6" x="826" y="367"/>
-  <use xlink:href="#glyph0-1" x="840" y="367"/>
-  <use xlink:href="#glyph0-1" x="854" y="367"/>
-  <use xlink:href="#glyph0-1" x="868" y="367"/>
-  <use xlink:href="#glyph0-1" x="882" y="367"/>
-  <use xlink:href="#glyph0-1" x="896" y="367"/>
-  <use xlink:href="#glyph0-1" x="910" y="367"/>
-  <use xlink:href="#glyph0-1" x="924" y="367"/>
-  <use xlink:href="#glyph0-1" x="938" y="367"/>
-  <use xlink:href="#glyph0-1" x="952" y="367"/>
-  <use xlink:href="#glyph0-1" x="966" y="367"/>
-  <use xlink:href="#glyph0-1" x="980" y="367"/>
-  <use xlink:href="#glyph0-1" x="994" y="367"/>
-  <use xlink:href="#glyph0-1" x="1008" y="367"/>
-  <use xlink:href="#glyph0-1" x="1022" y="367"/>
-  <use xlink:href="#glyph0-1" x="1036" y="367"/>
-  <use xlink:href="#glyph0-1" x="1050" y="367"/>
-  <use xlink:href="#glyph0-1" x="1064" y="367"/>
-  <use xlink:href="#glyph0-1" x="1078" y="367"/>
-  <use xlink:href="#glyph0-1" x="1092" y="367"/>
-  <use xlink:href="#glyph0-1" x="1106" y="367"/>
-  <use xlink:href="#glyph0-1" x="1120" y="367"/>
-  <use xlink:href="#glyph0-1" x="1134" y="367"/>
-  <use xlink:href="#glyph0-1" x="1148" y="367"/>
-  <use xlink:href="#glyph0-1" x="1162" y="367"/>
-  <use xlink:href="#glyph0-1" x="1176" y="367"/>
-  <use xlink:href="#glyph0-1" x="1190" y="367"/>
-  <use xlink:href="#glyph0-1" x="1204" y="367"/>
-  <use xlink:href="#glyph0-1" x="1218" y="367"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 1232 341 L 1260 341 L 1260 372 L 1232 372 Z M 1232 341 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="1232" y="367"/>
-  <use xlink:href="#glyph0-45" x="1246" y="367"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 1260 341 L 1358 341 L 1358 372 L 1260 372 Z M 1260 341 "/>
-<g style="fill:rgb(87.843137%,90.196078%,94.117647%);fill-opacity:1;">
-  <use xlink:href="#glyph0-15" x="1260" y="367"/>
-  <use xlink:href="#glyph0-1" x="1274" y="367"/>
-  <use xlink:href="#glyph0-52" x="1288" y="367"/>
-  <use xlink:href="#glyph0-15" x="1302" y="367"/>
-  <use xlink:href="#glyph0-25" x="1316" y="367"/>
-  <use xlink:href="#glyph0-27" x="1330" y="367"/>
-  <use xlink:href="#glyph0-16" x="1344" y="367"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 1358 341 L 1372 341 L 1372 372 L 1358 372 Z M 1358 341 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 1372 341 L 1414 341 L 1414 372 L 1372 372 Z M 1372 341 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117647%,15.686275%,21.568627%);fill-opacity:1;" d="M 1414 341 L 1428 341 L 1428 372 L 1414 372 Z M 1414 341 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 0 372 L 56 372 L 56 403 L 0 403 Z M 0 372 "/>
-<g style="fill:rgb(87.843137%,90.196078%,94.117647%);fill-opacity:1;">
-  <use xlink:href="#glyph0-46" x="0" y="398"/>
-  <use xlink:href="#glyph0-46" x="14" y="398"/>
-  <use xlink:href="#glyph0-46" x="28" y="398"/>
-  <use xlink:href="#glyph0-1" x="42" y="398"/>
-</g>
-<g style="fill:rgb(87.843137%,90.196078%,94.117647%);fill-opacity:1;">
-  <use xlink:href="#glyph0-46" x="1" y="398"/>
-  <use xlink:href="#glyph0-46" x="15" y="398"/>
-  <use xlink:href="#glyph0-46" x="29" y="398"/>
-  <use xlink:href="#glyph0-1" x="43" y="398"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 56 372 L 140 372 L 140 403 L 56 403 Z M 56 372 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 140 372 L 168 372 L 168 403 L 140 403 Z M 140 372 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-45" x="140" y="398"/>
-  <use xlink:href="#glyph0-1" x="154" y="398"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 168 372 L 182 372 L 182 403 L 168 403 Z M 168 372 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-2" x="168" y="398"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 182 372 L 266 372 L 266 403 L 182 403 Z M 182 372 "/>
-<g style="fill:rgb(53.333333%,93.72549%,53.333333%);fill-opacity:1;">
-  <use xlink:href="#glyph0-13" x="182" y="398"/>
-  <use xlink:href="#glyph0-4" x="196" y="398"/>
-  <use xlink:href="#glyph0-14" x="210" y="398"/>
-  <use xlink:href="#glyph0-9" x="224" y="398"/>
-  <use xlink:href="#glyph0-15" x="238" y="398"/>
-  <use xlink:href="#glyph0-12" x="252" y="398"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 266 372 L 448 372 L 448 403 L 266 403 Z M 266 372 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-16" x="266" y="398"/>
-  <use xlink:href="#glyph0-7" x="280" y="398"/>
-  <use xlink:href="#glyph0-1" x="294" y="398"/>
-  <use xlink:href="#glyph0-46" x="308" y="398"/>
-  <use xlink:href="#glyph0-1" x="322" y="398"/>
-  <use xlink:href="#glyph0-47" x="336" y="398"/>
-  <use xlink:href="#glyph0-48" x="350" y="398"/>
-  <use xlink:href="#glyph0-1" x="364" y="398"/>
-  <use xlink:href="#glyph0-3" x="378" y="398"/>
-  <use xlink:href="#glyph0-23" x="392" y="398"/>
-  <use xlink:href="#glyph0-29" x="406" y="398"/>
-  <use xlink:href="#glyph0-6" x="420" y="398"/>
-  <use xlink:href="#glyph0-1" x="434" y="398"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 448 372 L 532 372 L 532 403 L 448 403 Z M 448 372 "/>
-<g style="fill:rgb(57.254902%,85.098039%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-31" x="448" y="398"/>
-  <use xlink:href="#glyph0-6" x="462" y="398"/>
-  <use xlink:href="#glyph0-38" x="476" y="398"/>
-  <use xlink:href="#glyph0-4" x="490" y="398"/>
-  <use xlink:href="#glyph0-25" x="504" y="398"/>
-  <use xlink:href="#glyph0-6" x="518" y="398"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 532 372 L 546 372 L 546 403 L 532 403 Z M 532 372 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 546 372 L 588 372 L 588 403 L 546 403 Z M 546 372 "/>
-<g style="fill:rgb(37.647059%,90.588235%,87.843137%);fill-opacity:1;">
-  <use xlink:href="#glyph0-37" x="546" y="398"/>
-  <use xlink:href="#glyph0-15" x="560" y="398"/>
-  <use xlink:href="#glyph0-33" x="574" y="398"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 588 372 L 1232 372 L 1232 403 L 588 403 Z M 588 372 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-16" x="588" y="398"/>
-  <use xlink:href="#glyph0-1" x="602" y="398"/>
-  <use xlink:href="#glyph0-46" x="616" y="398"/>
-  <use xlink:href="#glyph0-1" x="630" y="398"/>
-  <use xlink:href="#glyph0-17" x="644" y="398"/>
-  <use xlink:href="#glyph0-4" x="658" y="398"/>
-  <use xlink:href="#glyph0-9" x="672" y="398"/>
-  <use xlink:href="#glyph0-6" x="686" y="398"/>
-  <use xlink:href="#glyph0-1" x="700" y="398"/>
-  <use xlink:href="#glyph0-16" x="714" y="398"/>
-  <use xlink:href="#glyph0-6" x="728" y="398"/>
-  <use xlink:href="#glyph0-23" x="742" y="398"/>
-  <use xlink:href="#glyph0-24" x="756" y="398"/>
-  <use xlink:href="#glyph0-25" x="770" y="398"/>
-  <use xlink:href="#glyph0-26" x="784" y="398"/>
-  <use xlink:href="#glyph0-1" x="798" y="398"/>
-  <use xlink:href="#glyph0-26" x="812" y="398"/>
-  <use xlink:href="#glyph0-4" x="826" y="398"/>
-  <use xlink:href="#glyph0-33" x="840" y="398"/>
-  <use xlink:href="#glyph0-26" x="854" y="398"/>
-  <use xlink:href="#glyph0-5" x="868" y="398"/>
-  <use xlink:href="#glyph0-4" x="882" y="398"/>
-  <use xlink:href="#glyph0-33" x="896" y="398"/>
-  <use xlink:href="#glyph0-26" x="910" y="398"/>
-  <use xlink:href="#glyph0-10" x="924" y="398"/>
-  <use xlink:href="#glyph0-4" x="938" y="398"/>
-  <use xlink:href="#glyph0-14" x="952" y="398"/>
-  <use xlink:href="#glyph0-33" x="966" y="398"/>
-  <use xlink:href="#glyph0-1" x="980" y="398"/>
-  <use xlink:href="#glyph0-1" x="994" y="398"/>
-  <use xlink:href="#glyph0-1" x="1008" y="398"/>
-  <use xlink:href="#glyph0-1" x="1022" y="398"/>
-  <use xlink:href="#glyph0-1" x="1036" y="398"/>
-  <use xlink:href="#glyph0-1" x="1050" y="398"/>
-  <use xlink:href="#glyph0-1" x="1064" y="398"/>
-  <use xlink:href="#glyph0-1" x="1078" y="398"/>
-  <use xlink:href="#glyph0-1" x="1092" y="398"/>
-  <use xlink:href="#glyph0-1" x="1106" y="398"/>
-  <use xlink:href="#glyph0-1" x="1120" y="398"/>
-  <use xlink:href="#glyph0-1" x="1134" y="398"/>
-  <use xlink:href="#glyph0-1" x="1148" y="398"/>
-  <use xlink:href="#glyph0-1" x="1162" y="398"/>
-  <use xlink:href="#glyph0-1" x="1176" y="398"/>
-  <use xlink:href="#glyph0-1" x="1190" y="398"/>
-  <use xlink:href="#glyph0-1" x="1204" y="398"/>
-  <use xlink:href="#glyph0-1" x="1218" y="398"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 1232 372 L 1260 372 L 1260 403 L 1232 403 Z M 1232 372 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="1232" y="398"/>
-  <use xlink:href="#glyph0-45" x="1246" y="398"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 1260 372 L 1414 372 L 1414 403 L 1260 403 Z M 1260 372 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117647%,15.686275%,21.568627%);fill-opacity:1;" d="M 1414 372 L 1428 372 L 1428 403 L 1414 403 Z M 1414 372 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 0 403 L 140 403 L 140 434 L 0 434 Z M 0 403 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 140 403 L 168 403 L 168 434 L 140 434 Z M 140 403 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-45" x="140" y="429"/>
-  <use xlink:href="#glyph0-1" x="154" y="429"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 168 403 L 182 403 L 182 434 L 168 434 Z M 168 403 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-2" x="168" y="429"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 182 403 L 266 403 L 266 434 L 182 434 Z M 182 403 "/>
-<g style="fill:rgb(53.333333%,93.72549%,53.333333%);fill-opacity:1;">
-  <use xlink:href="#glyph0-13" x="182" y="429"/>
-  <use xlink:href="#glyph0-4" x="196" y="429"/>
-  <use xlink:href="#glyph0-14" x="210" y="429"/>
-  <use xlink:href="#glyph0-9" x="224" y="429"/>
-  <use xlink:href="#glyph0-15" x="238" y="429"/>
-  <use xlink:href="#glyph0-12" x="252" y="429"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 266 403 L 448 403 L 448 434 L 266 434 Z M 266 403 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-16" x="266" y="429"/>
-  <use xlink:href="#glyph0-7" x="280" y="429"/>
-  <use xlink:href="#glyph0-1" x="294" y="429"/>
-  <use xlink:href="#glyph0-46" x="308" y="429"/>
-  <use xlink:href="#glyph0-1" x="322" y="429"/>
-  <use xlink:href="#glyph0-47" x="336" y="429"/>
-  <use xlink:href="#glyph0-48" x="350" y="429"/>
-  <use xlink:href="#glyph0-1" x="364" y="429"/>
-  <use xlink:href="#glyph0-3" x="378" y="429"/>
-  <use xlink:href="#glyph0-23" x="392" y="429"/>
-  <use xlink:href="#glyph0-29" x="406" y="429"/>
-  <use xlink:href="#glyph0-6" x="420" y="429"/>
-  <use xlink:href="#glyph0-1" x="434" y="429"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 448 403 L 532 403 L 532 434 L 448 434 Z M 448 403 "/>
-<g style="fill:rgb(57.254902%,85.098039%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-31" x="448" y="429"/>
-  <use xlink:href="#glyph0-6" x="462" y="429"/>
-  <use xlink:href="#glyph0-38" x="476" y="429"/>
-  <use xlink:href="#glyph0-4" x="490" y="429"/>
-  <use xlink:href="#glyph0-25" x="504" y="429"/>
-  <use xlink:href="#glyph0-6" x="518" y="429"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 532 403 L 546 403 L 546 434 L 532 434 Z M 532 403 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 546 403 L 588 403 L 588 434 L 546 434 Z M 546 403 "/>
-<g style="fill:rgb(37.647059%,90.588235%,87.843137%);fill-opacity:1;">
-  <use xlink:href="#glyph0-37" x="546" y="429"/>
-  <use xlink:href="#glyph0-15" x="560" y="429"/>
-  <use xlink:href="#glyph0-33" x="574" y="429"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 588 403 L 1232 403 L 1232 434 L 588 434 Z M 588 403 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-16" x="588" y="429"/>
-  <use xlink:href="#glyph0-1" x="602" y="429"/>
-  <use xlink:href="#glyph0-46" x="616" y="429"/>
-  <use xlink:href="#glyph0-1" x="630" y="429"/>
-  <use xlink:href="#glyph0-19" x="644" y="429"/>
-  <use xlink:href="#glyph0-24" x="658" y="429"/>
-  <use xlink:href="#glyph0-6" x="672" y="429"/>
-  <use xlink:href="#glyph0-23" x="686" y="429"/>
-  <use xlink:href="#glyph0-10" x="700" y="429"/>
-  <use xlink:href="#glyph0-6" x="714" y="429"/>
-  <use xlink:href="#glyph0-1" x="728" y="429"/>
-  <use xlink:href="#glyph0-52" x="742" y="429"/>
-  <use xlink:href="#glyph0-4" x="756" y="429"/>
-  <use xlink:href="#glyph0-5" x="770" y="429"/>
-  <use xlink:href="#glyph0-10" x="784" y="429"/>
-  <use xlink:href="#glyph0-6" x="798" y="429"/>
-  <use xlink:href="#glyph0-24" x="812" y="429"/>
-  <use xlink:href="#glyph0-1" x="826" y="429"/>
-  <use xlink:href="#glyph0-52" x="840" y="429"/>
-  <use xlink:href="#glyph0-24" x="854" y="429"/>
-  <use xlink:href="#glyph0-15" x="868" y="429"/>
-  <use xlink:href="#glyph0-43" x="882" y="429"/>
-  <use xlink:href="#glyph0-1" x="896" y="429"/>
-  <use xlink:href="#glyph0-16" x="910" y="429"/>
-  <use xlink:href="#glyph0-6" x="924" y="429"/>
-  <use xlink:href="#glyph0-23" x="938" y="429"/>
-  <use xlink:href="#glyph0-24" x="952" y="429"/>
-  <use xlink:href="#glyph0-25" x="966" y="429"/>
-  <use xlink:href="#glyph0-26" x="980" y="429"/>
-  <use xlink:href="#glyph0-1" x="994" y="429"/>
-  <use xlink:href="#glyph0-24" x="1008" y="429"/>
-  <use xlink:href="#glyph0-6" x="1022" y="429"/>
-  <use xlink:href="#glyph0-16" x="1036" y="429"/>
-  <use xlink:href="#glyph0-27" x="1050" y="429"/>
-  <use xlink:href="#glyph0-5" x="1064" y="429"/>
-  <use xlink:href="#glyph0-10" x="1078" y="429"/>
-  <use xlink:href="#glyph0-16" x="1092" y="429"/>
-  <use xlink:href="#glyph0-1" x="1106" y="429"/>
-  <use xlink:href="#glyph0-1" x="1120" y="429"/>
-  <use xlink:href="#glyph0-1" x="1134" y="429"/>
-  <use xlink:href="#glyph0-1" x="1148" y="429"/>
-  <use xlink:href="#glyph0-1" x="1162" y="429"/>
-  <use xlink:href="#glyph0-1" x="1176" y="429"/>
-  <use xlink:href="#glyph0-1" x="1190" y="429"/>
-  <use xlink:href="#glyph0-1" x="1204" y="429"/>
-  <use xlink:href="#glyph0-1" x="1218" y="429"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 1232 403 L 1260 403 L 1260 434 L 1232 434 Z M 1232 403 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="1232" y="429"/>
-  <use xlink:href="#glyph0-45" x="1246" y="429"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 1260 403 L 1414 403 L 1414 434 L 1260 434 Z M 1260 403 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117647%,15.686275%,21.568627%);fill-opacity:1;" d="M 1414 403 L 1428 403 L 1428 434 L 1414 434 Z M 1414 403 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 0 434 L 140 434 L 140 465 L 0 465 Z M 0 434 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 140 434 L 168 434 L 168 465 L 140 465 Z M 140 434 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-45" x="140" y="460"/>
-  <use xlink:href="#glyph0-1" x="154" y="460"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 168 434 L 182 434 L 182 465 L 168 465 Z M 168 434 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-2" x="168" y="460"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 182 434 L 266 434 L 266 465 L 182 465 Z M 182 434 "/>
-<g style="fill:rgb(53.333333%,93.72549%,53.333333%);fill-opacity:1;">
-  <use xlink:href="#glyph0-13" x="182" y="460"/>
-  <use xlink:href="#glyph0-4" x="196" y="460"/>
-  <use xlink:href="#glyph0-14" x="210" y="460"/>
-  <use xlink:href="#glyph0-9" x="224" y="460"/>
-  <use xlink:href="#glyph0-15" x="238" y="460"/>
-  <use xlink:href="#glyph0-12" x="252" y="460"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 266 434 L 448 434 L 448 465 L 266 465 Z M 266 434 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-16" x="266" y="460"/>
-  <use xlink:href="#glyph0-7" x="280" y="460"/>
-  <use xlink:href="#glyph0-1" x="294" y="460"/>
-  <use xlink:href="#glyph0-46" x="308" y="460"/>
-  <use xlink:href="#glyph0-1" x="322" y="460"/>
-  <use xlink:href="#glyph0-47" x="336" y="460"/>
-  <use xlink:href="#glyph0-48" x="350" y="460"/>
-  <use xlink:href="#glyph0-1" x="364" y="460"/>
-  <use xlink:href="#glyph0-3" x="378" y="460"/>
-  <use xlink:href="#glyph0-23" x="392" y="460"/>
-  <use xlink:href="#glyph0-29" x="406" y="460"/>
-  <use xlink:href="#glyph0-6" x="420" y="460"/>
-  <use xlink:href="#glyph0-1" x="434" y="460"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 448 434 L 532 434 L 532 465 L 448 465 Z M 448 434 "/>
-<g style="fill:rgb(57.254902%,85.098039%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-31" x="448" y="460"/>
-  <use xlink:href="#glyph0-6" x="462" y="460"/>
-  <use xlink:href="#glyph0-38" x="476" y="460"/>
-  <use xlink:href="#glyph0-4" x="490" y="460"/>
-  <use xlink:href="#glyph0-25" x="504" y="460"/>
-  <use xlink:href="#glyph0-6" x="518" y="460"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 532 434 L 546 434 L 546 465 L 532 465 Z M 532 434 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 546 434 L 588 434 L 588 465 L 546 465 Z M 546 434 "/>
-<g style="fill:rgb(37.647059%,90.588235%,87.843137%);fill-opacity:1;">
-  <use xlink:href="#glyph0-37" x="546" y="460"/>
-  <use xlink:href="#glyph0-15" x="560" y="460"/>
-  <use xlink:href="#glyph0-33" x="574" y="460"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 588 434 L 1232 434 L 1232 465 L 588 465 Z M 588 434 "/>
-<g style="fill:rgb(100%,100%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-16" x="588" y="460"/>
-  <use xlink:href="#glyph0-1" x="602" y="460"/>
-  <use xlink:href="#glyph0-46" x="616" y="460"/>
-  <use xlink:href="#glyph0-1" x="630" y="460"/>
-  <use xlink:href="#glyph0-19" x="644" y="460"/>
-  <use xlink:href="#glyph0-5" x="658" y="460"/>
-  <use xlink:href="#glyph0-6" x="672" y="460"/>
-  <use xlink:href="#glyph0-23" x="686" y="460"/>
-  <use xlink:href="#glyph0-24" x="700" y="460"/>
-  <use xlink:href="#glyph0-50" x="714" y="460"/>
-  <use xlink:href="#glyph0-35" x="728" y="460"/>
-  <use xlink:href="#glyph0-6" x="742" y="460"/>
-  <use xlink:href="#glyph0-16" x="756" y="460"/>
-  <use xlink:href="#glyph0-6" x="770" y="460"/>
-  <use xlink:href="#glyph0-10" x="784" y="460"/>
-  <use xlink:href="#glyph0-1" x="798" y="460"/>
-  <use xlink:href="#glyph0-23" x="812" y="460"/>
-  <use xlink:href="#glyph0-25" x="826" y="460"/>
-  <use xlink:href="#glyph0-10" x="840" y="460"/>
-  <use xlink:href="#glyph0-4" x="854" y="460"/>
-  <use xlink:href="#glyph0-38" x="868" y="460"/>
-  <use xlink:href="#glyph0-6" x="882" y="460"/>
-  <use xlink:href="#glyph0-1" x="896" y="460"/>
-  <use xlink:href="#glyph0-52" x="910" y="460"/>
-  <use xlink:href="#glyph0-4" x="924" y="460"/>
-  <use xlink:href="#glyph0-5" x="938" y="460"/>
-  <use xlink:href="#glyph0-10" x="952" y="460"/>
-  <use xlink:href="#glyph0-6" x="966" y="460"/>
-  <use xlink:href="#glyph0-24" x="980" y="460"/>
-  <use xlink:href="#glyph0-16" x="994" y="460"/>
-  <use xlink:href="#glyph0-1" x="1008" y="460"/>
-  <use xlink:href="#glyph0-1" x="1022" y="460"/>
-  <use xlink:href="#glyph0-1" x="1036" y="460"/>
-  <use xlink:href="#glyph0-1" x="1050" y="460"/>
-  <use xlink:href="#glyph0-1" x="1064" y="460"/>
-  <use xlink:href="#glyph0-1" x="1078" y="460"/>
-  <use xlink:href="#glyph0-1" x="1092" y="460"/>
-  <use xlink:href="#glyph0-1" x="1106" y="460"/>
-  <use xlink:href="#glyph0-1" x="1120" y="460"/>
-  <use xlink:href="#glyph0-1" x="1134" y="460"/>
-  <use xlink:href="#glyph0-1" x="1148" y="460"/>
-  <use xlink:href="#glyph0-1" x="1162" y="460"/>
-  <use xlink:href="#glyph0-1" x="1176" y="460"/>
-  <use xlink:href="#glyph0-1" x="1190" y="460"/>
-  <use xlink:href="#glyph0-1" x="1204" y="460"/>
-  <use xlink:href="#glyph0-1" x="1218" y="460"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 1232 434 L 1260 434 L 1260 465 L 1232 465 Z M 1232 434 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="1232" y="460"/>
-  <use xlink:href="#glyph0-45" x="1246" y="460"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 1260 434 L 1414 434 L 1414 465 L 1260 465 Z M 1260 434 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117647%,15.686275%,21.568627%);fill-opacity:1;" d="M 1414 434 L 1428 434 L 1428 465 L 1414 465 Z M 1414 434 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 0 465 L 140 465 L 140 496 L 0 496 Z M 0 465 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 140 465 L 1260 465 L 1260 496 L 140 496 Z M 140 465 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-53" x="140" y="491"/>
-  <use xlink:href="#glyph0-41" x="154" y="491"/>
-  <use xlink:href="#glyph0-41" x="168" y="491"/>
-  <use xlink:href="#glyph0-41" x="182" y="491"/>
-  <use xlink:href="#glyph0-41" x="196" y="491"/>
-  <use xlink:href="#glyph0-41" x="210" y="491"/>
-  <use xlink:href="#glyph0-41" x="224" y="491"/>
-  <use xlink:href="#glyph0-41" x="238" y="491"/>
-  <use xlink:href="#glyph0-41" x="252" y="491"/>
-  <use xlink:href="#glyph0-41" x="266" y="491"/>
-  <use xlink:href="#glyph0-41" x="280" y="491"/>
-  <use xlink:href="#glyph0-41" x="294" y="491"/>
-  <use xlink:href="#glyph0-41" x="308" y="491"/>
-  <use xlink:href="#glyph0-41" x="322" y="491"/>
-  <use xlink:href="#glyph0-41" x="336" y="491"/>
-  <use xlink:href="#glyph0-41" x="350" y="491"/>
-  <use xlink:href="#glyph0-41" x="364" y="491"/>
-  <use xlink:href="#glyph0-41" x="378" y="491"/>
-  <use xlink:href="#glyph0-41" x="392" y="491"/>
-  <use xlink:href="#glyph0-41" x="406" y="491"/>
-  <use xlink:href="#glyph0-41" x="420" y="491"/>
-  <use xlink:href="#glyph0-41" x="434" y="491"/>
-  <use xlink:href="#glyph0-41" x="448" y="491"/>
-  <use xlink:href="#glyph0-41" x="462" y="491"/>
-  <use xlink:href="#glyph0-41" x="476" y="491"/>
-  <use xlink:href="#glyph0-41" x="490" y="491"/>
-  <use xlink:href="#glyph0-41" x="504" y="491"/>
-  <use xlink:href="#glyph0-41" x="518" y="491"/>
-  <use xlink:href="#glyph0-41" x="532" y="491"/>
-  <use xlink:href="#glyph0-41" x="546" y="491"/>
-  <use xlink:href="#glyph0-41" x="560" y="491"/>
-  <use xlink:href="#glyph0-41" x="574" y="491"/>
-  <use xlink:href="#glyph0-41" x="588" y="491"/>
-  <use xlink:href="#glyph0-41" x="602" y="491"/>
-  <use xlink:href="#glyph0-41" x="616" y="491"/>
-  <use xlink:href="#glyph0-41" x="630" y="491"/>
-  <use xlink:href="#glyph0-41" x="644" y="491"/>
-  <use xlink:href="#glyph0-41" x="658" y="491"/>
-  <use xlink:href="#glyph0-41" x="672" y="491"/>
-  <use xlink:href="#glyph0-41" x="686" y="491"/>
-  <use xlink:href="#glyph0-41" x="700" y="491"/>
-  <use xlink:href="#glyph0-41" x="714" y="491"/>
-  <use xlink:href="#glyph0-41" x="728" y="491"/>
-  <use xlink:href="#glyph0-41" x="742" y="491"/>
-  <use xlink:href="#glyph0-41" x="756" y="491"/>
-  <use xlink:href="#glyph0-41" x="770" y="491"/>
-  <use xlink:href="#glyph0-41" x="784" y="491"/>
-  <use xlink:href="#glyph0-41" x="798" y="491"/>
-  <use xlink:href="#glyph0-41" x="812" y="491"/>
-  <use xlink:href="#glyph0-41" x="826" y="491"/>
-  <use xlink:href="#glyph0-41" x="840" y="491"/>
-  <use xlink:href="#glyph0-41" x="854" y="491"/>
-  <use xlink:href="#glyph0-41" x="868" y="491"/>
-  <use xlink:href="#glyph0-41" x="882" y="491"/>
-  <use xlink:href="#glyph0-41" x="896" y="491"/>
-  <use xlink:href="#glyph0-41" x="910" y="491"/>
-  <use xlink:href="#glyph0-41" x="924" y="491"/>
-  <use xlink:href="#glyph0-41" x="938" y="491"/>
-  <use xlink:href="#glyph0-41" x="952" y="491"/>
-  <use xlink:href="#glyph0-41" x="966" y="491"/>
-  <use xlink:href="#glyph0-41" x="980" y="491"/>
-  <use xlink:href="#glyph0-41" x="994" y="491"/>
-  <use xlink:href="#glyph0-41" x="1008" y="491"/>
-  <use xlink:href="#glyph0-41" x="1022" y="491"/>
-  <use xlink:href="#glyph0-41" x="1036" y="491"/>
-  <use xlink:href="#glyph0-41" x="1050" y="491"/>
-  <use xlink:href="#glyph0-41" x="1064" y="491"/>
-  <use xlink:href="#glyph0-41" x="1078" y="491"/>
-  <use xlink:href="#glyph0-41" x="1092" y="491"/>
-  <use xlink:href="#glyph0-41" x="1106" y="491"/>
-  <use xlink:href="#glyph0-41" x="1120" y="491"/>
-  <use xlink:href="#glyph0-41" x="1134" y="491"/>
-  <use xlink:href="#glyph0-41" x="1148" y="491"/>
-  <use xlink:href="#glyph0-41" x="1162" y="491"/>
-  <use xlink:href="#glyph0-41" x="1176" y="491"/>
-  <use xlink:href="#glyph0-41" x="1190" y="491"/>
-  <use xlink:href="#glyph0-41" x="1204" y="491"/>
-  <use xlink:href="#glyph0-41" x="1218" y="491"/>
-  <use xlink:href="#glyph0-41" x="1232" y="491"/>
-  <use xlink:href="#glyph0-54" x="1246" y="491"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 1260 465 L 1414 465 L 1414 496 L 1260 496 Z M 1260 465 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117647%,15.686275%,21.568627%);fill-opacity:1;" d="M 1414 465 L 1428 465 L 1428 496 L 1414 496 Z M 1414 465 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 0 496 L 1414 496 L 1414 527 L 0 527 Z M 0 496 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117647%,15.686275%,21.568627%);fill-opacity:1;" d="M 1414 496 L 1428 496 L 1428 527 L 1414 527 Z M 1414 496 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 0 527 L 1414 527 L 1414 558 L 0 558 Z M 0 527 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117647%,15.686275%,21.568627%);fill-opacity:1;" d="M 1414 527 L 1428 527 L 1428 558 L 1414 558 Z M 1414 527 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 0 558 L 1414 558 L 1414 589 L 0 589 Z M 0 558 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117647%,15.686275%,21.568627%);fill-opacity:1;" d="M 1414 558 L 1428 558 L 1428 589 L 1414 589 Z M 1414 558 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 0 589 L 1414 589 L 1414 620 L 0 620 Z M 0 589 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117647%,15.686275%,21.568627%);fill-opacity:1;" d="M 1414 589 L 1428 589 L 1428 620 L 1414 620 Z M 1414 589 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 0 620 L 14 620 L 14 651 L 0 651 Z M 0 620 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 14 620 L 196 620 L 196 651 L 14 651 Z M 14 620 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="14" y="646"/>
-  <use xlink:href="#glyph0-30" x="28" y="646"/>
-  <use xlink:href="#glyph0-34" x="42" y="646"/>
-  <use xlink:href="#glyph0-10" x="56" y="646"/>
-  <use xlink:href="#glyph0-26" x="70" y="646"/>
-  <use xlink:href="#glyph0-15" x="84" y="646"/>
-  <use xlink:href="#glyph0-14" x="98" y="646"/>
-  <use xlink:href="#glyph0-1" x="112" y="646"/>
-  <use xlink:href="#glyph0-35" x="126" y="646"/>
-  <use xlink:href="#glyph0-6" x="140" y="646"/>
-  <use xlink:href="#glyph0-18" x="154" y="646"/>
-  <use xlink:href="#glyph0-5" x="168" y="646"/>
-  <use xlink:href="#glyph0-1" x="182" y="646"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 196 620 L 224 620 L 224 651 L 196 651 Z M 196 620 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 224 620 L 238 620 L 238 651 L 224 651 Z M 224 620 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 238 620 L 322 620 L 322 651 L 238 651 Z M 238 620 "/>
-<g style="fill:rgb(87.843137%,90.196078%,94.117647%);fill-opacity:1;">
-  <use xlink:href="#glyph0-30" x="238" y="646"/>
-  <use xlink:href="#glyph0-23" x="252" y="646"/>
-  <use xlink:href="#glyph0-16" x="266" y="646"/>
-  <use xlink:href="#glyph0-10" x="280" y="646"/>
-  <use xlink:href="#glyph0-6" x="294" y="646"/>
-  <use xlink:href="#glyph0-1" x="308" y="646"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 322 620 L 406 620 L 406 651 L 322 651 Z M 322 620 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-19" x="322" y="646"/>
-  <use xlink:href="#glyph0-10" x="336" y="646"/>
-  <use xlink:href="#glyph0-24" x="350" y="646"/>
-  <use xlink:href="#glyph0-5" x="364" y="646"/>
-  <use xlink:href="#glyph0-28" x="378" y="646"/>
-  <use xlink:href="#glyph0-38" x="392" y="646"/>
-</g>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-19" x="323" y="646"/>
-  <use xlink:href="#glyph0-10" x="337" y="646"/>
-  <use xlink:href="#glyph0-24" x="351" y="646"/>
-  <use xlink:href="#glyph0-5" x="365" y="646"/>
-  <use xlink:href="#glyph0-28" x="379" y="646"/>
-  <use xlink:href="#glyph0-38" x="393" y="646"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 406 620 L 420 620 L 420 651 L 406 651 Z M 406 620 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 420 620 L 448 620 L 448 651 L 420 651 Z M 420 620 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 448 620 L 462 620 L 462 651 L 448 651 Z M 448 620 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 462 620 L 644 620 L 644 651 L 462 651 Z M 462 620 "/>
-<g style="fill:rgb(87.843137%,90.196078%,94.117647%);fill-opacity:1;">
-  <use xlink:href="#glyph0-19" x="462" y="646"/>
-  <use xlink:href="#glyph0-15" x="476" y="646"/>
-  <use xlink:href="#glyph0-18" x="490" y="646"/>
-  <use xlink:href="#glyph0-34" x="504" y="646"/>
-  <use xlink:href="#glyph0-1" x="518" y="646"/>
-  <use xlink:href="#glyph0-50" x="532" y="646"/>
-  <use xlink:href="#glyph0-1" x="546" y="646"/>
-  <use xlink:href="#glyph0-19" x="560" y="646"/>
-  <use xlink:href="#glyph0-5" x="574" y="646"/>
-  <use xlink:href="#glyph0-6" x="588" y="646"/>
-  <use xlink:href="#glyph0-23" x="602" y="646"/>
-  <use xlink:href="#glyph0-24" x="616" y="646"/>
-  <use xlink:href="#glyph0-1" x="630" y="646"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 644 620 L 728 620 L 728 651 L 644 651 Z M 644 620 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-19" x="644" y="646"/>
-  <use xlink:href="#glyph0-10" x="658" y="646"/>
-  <use xlink:href="#glyph0-24" x="672" y="646"/>
-  <use xlink:href="#glyph0-5" x="686" y="646"/>
-  <use xlink:href="#glyph0-28" x="700" y="646"/>
-  <use xlink:href="#glyph0-25" x="714" y="646"/>
-</g>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-19" x="645" y="646"/>
-  <use xlink:href="#glyph0-10" x="659" y="646"/>
-  <use xlink:href="#glyph0-24" x="673" y="646"/>
-  <use xlink:href="#glyph0-5" x="687" y="646"/>
-  <use xlink:href="#glyph0-28" x="701" y="646"/>
-  <use xlink:href="#glyph0-25" x="715" y="646"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 728 620 L 742 620 L 742 651 L 728 651 Z M 728 620 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 742 620 L 770 620 L 770 651 L 742 651 Z M 742 620 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 770 620 L 784 620 L 784 651 L 770 651 Z M 770 620 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 784 620 L 840 620 L 840 651 L 784 651 Z M 784 620 "/>
-<g style="fill:rgb(87.843137%,90.196078%,94.117647%);fill-opacity:1;">
-  <use xlink:href="#glyph0-35" x="784" y="646"/>
-  <use xlink:href="#glyph0-27" x="798" y="646"/>
-  <use xlink:href="#glyph0-14" x="812" y="646"/>
-  <use xlink:href="#glyph0-1" x="826" y="646"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 840 620 L 910 620 L 910 651 L 840 651 Z M 840 620 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-8" x="840" y="646"/>
-  <use xlink:href="#glyph0-14" x="854" y="646"/>
-  <use xlink:href="#glyph0-10" x="868" y="646"/>
-  <use xlink:href="#glyph0-6" x="882" y="646"/>
-  <use xlink:href="#glyph0-24" x="896" y="646"/>
-</g>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-8" x="841" y="646"/>
-  <use xlink:href="#glyph0-14" x="855" y="646"/>
-  <use xlink:href="#glyph0-10" x="869" y="646"/>
-  <use xlink:href="#glyph0-6" x="883" y="646"/>
-  <use xlink:href="#glyph0-24" x="897" y="646"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 910 620 L 924 620 L 924 651 L 910 651 Z M 910 620 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 924 620 L 952 620 L 952 651 L 924 651 Z M 924 620 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 952 620 L 966 620 L 966 651 L 952 651 Z M 952 620 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 966 620 L 1092 620 L 1092 651 L 966 651 Z M 966 620 "/>
-<g style="fill:rgb(87.843137%,90.196078%,94.117647%);fill-opacity:1;">
-  <use xlink:href="#glyph0-22" x="966" y="646"/>
-  <use xlink:href="#glyph0-6" x="980" y="646"/>
-  <use xlink:href="#glyph0-10" x="994" y="646"/>
-  <use xlink:href="#glyph0-10" x="1008" y="646"/>
-  <use xlink:href="#glyph0-4" x="1022" y="646"/>
-  <use xlink:href="#glyph0-14" x="1036" y="646"/>
-  <use xlink:href="#glyph0-33" x="1050" y="646"/>
-  <use xlink:href="#glyph0-16" x="1064" y="646"/>
-  <use xlink:href="#glyph0-1" x="1078" y="646"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 1092 620 L 1106 620 L 1106 651 L 1092 651 Z M 1092 620 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 1106 620 L 1302 620 L 1302 651 L 1106 651 Z M 1106 620 "/>
-<g style="fill:rgb(87.843137%,90.196078%,94.117647%);fill-opacity:1;">
-  <use xlink:href="#glyph0-25" x="1106" y="646"/>
-  <use xlink:href="#glyph0-5" x="1120" y="646"/>
-  <use xlink:href="#glyph0-4" x="1134" y="646"/>
-  <use xlink:href="#glyph0-25" x="1148" y="646"/>
-  <use xlink:href="#glyph0-29" x="1162" y="646"/>
-  <use xlink:href="#glyph0-1" x="1176" y="646"/>
-  <use xlink:href="#glyph0-10" x="1190" y="646"/>
-  <use xlink:href="#glyph0-15" x="1204" y="646"/>
-  <use xlink:href="#glyph0-1" x="1218" y="646"/>
-  <use xlink:href="#glyph0-52" x="1232" y="646"/>
-  <use xlink:href="#glyph0-15" x="1246" y="646"/>
-  <use xlink:href="#glyph0-25" x="1260" y="646"/>
-  <use xlink:href="#glyph0-27" x="1274" y="646"/>
-  <use xlink:href="#glyph0-16" x="1288" y="646"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(19.607843%,19.607843%,19.607843%);fill-opacity:1;" d="M 1302 620 L 1316 620 L 1316 651 L 1302 651 Z M 1302 620 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 1316 620 L 1358 620 L 1358 651 L 1316 651 Z M 1316 620 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117647%,15.686275%,21.568627%);fill-opacity:1;" d="M 1414 620 L 1428 620 L 1428 651 L 1414 651 Z M 1414 620 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 1358 620 L 1414 620 L 1414 651 L 1358 651 Z M 1358 620 "/>
-<g style="fill:rgb(81.176471%,79.215686%,100%);fill-opacity:1;">
-  <use xlink:href="#glyph0-55" x="1358" y="646"/>
-  <use xlink:href="#glyph0-56" x="1372" y="646"/>
-  <use xlink:href="#glyph0-56" x="1386" y="646"/>
-  <use xlink:href="#glyph0-55" x="1400" y="646"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(52.941176%,58.823529%,69.019608%);fill-opacity:1;" d="M 434 124 L 448 124 L 448 155 L 434 155 Z M 434 124 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117647%,15.686275%,21.568627%);fill-opacity:1;" d="M 0 651 L 28 651 L 28 682 L 0 682 Z M 0 651 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.117647%,15.686275%,21.568627%);fill-opacity:1;" d="M 28 651 L 1429 651 L 1429 682 L 28 682 Z M 28 651 "/>
-</g>
-</svg>
diff --git a/pw_console/images/pw_system_boot.png b/pw_console/images/pw_system_boot.png
deleted file mode 100644
index d1c391b..0000000
--- a/pw_console/images/pw_system_boot.png
+++ /dev/null
Binary files differ
diff --git a/pw_console/images/python_completion.png b/pw_console/images/python_completion.png
deleted file mode 100644
index 87a424ca..0000000
--- a/pw_console/images/python_completion.png
+++ /dev/null
Binary files differ
diff --git a/pw_console/images/serial_debug.svg b/pw_console/images/serial_debug.svg
deleted file mode 100644
index 99d0904..0000000
--- a/pw_console/images/serial_debug.svg
+++ /dev/null
@@ -1,2360 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="2163pt" height="1314pt" viewBox="0 0 2163 1314" version="1.1">
-<defs>
-<g>
-<symbol overflow="visible" id="glyph0-0">
-<path style="stroke:none;" d="M 2.109375 0 L 2.109375 -23.515625 L 13.890625 -23.515625 L 13.890625 0 Z M 3.046875 -10.6875 L 7.71875 -16.546875 L 12.578125 -22.625 L 9.5625 -22.625 L 3.046875 -14.4375 Z M 3.046875 -15.6875 L 8.578125 -22.625 L 5.5625 -22.625 L 3.046875 -19.421875 Z M 3.046875 -20.671875 L 4.578125 -22.625 L 3.046875 -22.625 Z M 3.046875 -5.703125 L 12.953125 -18.078125 L 12.953125 -21.859375 L 3.046875 -9.4375 Z M 3.046875 -0.890625 L 3.234375 -0.890625 L 12.953125 -13.09375 L 12.953125 -16.828125 L 3.046875 -4.453125 Z M 4.21875 -0.890625 L 7.234375 -0.890625 L 12.953125 -8.09375 L 12.953125 -11.84375 Z M 8.21875 -0.890625 L 11.234375 -0.890625 L 12.953125 -3.109375 L 12.953125 -6.84375 Z M 12.21875 -0.890625 L 12.953125 -0.890625 L 12.953125 -1.859375 Z M 12.21875 -0.890625 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-1">
-<path style="stroke:none;" d=""/>
-</symbol>
-<symbol overflow="visible" id="glyph0-2">
-<path style="stroke:none;" d="M 3.296875 4.578125 L 3.296875 -26.34375 L 13.28125 -26.34375 L 13.28125 -24.578125 L 5.25 -24.578125 L 5.25 2.8125 L 13.28125 2.8125 L 13.28125 4.578125 Z M 3.296875 4.578125 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-3">
-<path style="stroke:none;" d="M 3.203125 0 L 3.203125 -23.515625 L 13.890625 -23.515625 L 13.890625 -21.765625 L 5.125 -21.765625 L 5.125 -13.125 L 12.125 -13.125 L 12.125 -11.359375 L 5.125 -11.359375 L 5.125 0 Z M 3.203125 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-4">
-<path style="stroke:none;" d="M 2.40625 0 L 2.40625 -1.765625 L 7.265625 -1.765625 L 7.265625 -15.203125 L 2.84375 -15.203125 L 2.84375 -16.953125 L 9.1875 -16.953125 L 9.1875 -1.765625 L 13.59375 -1.765625 L 13.59375 0 Z M 8 -20.421875 C 7.757812 -20.421875 7.535156 -20.453125 7.328125 -20.515625 C 7.117188 -20.578125 6.929688 -20.6875 6.765625 -20.84375 C 6.609375 -21.007812 6.5 -21.195312 6.4375 -21.40625 C 6.375 -21.625 6.34375 -21.847656 6.34375 -22.078125 C 6.34375 -22.316406 6.375 -22.539062 6.4375 -22.75 C 6.5 -22.96875 6.609375 -23.15625 6.765625 -23.3125 C 6.929688 -23.46875 7.117188 -23.578125 7.328125 -23.640625 C 7.535156 -23.710938 7.757812 -23.75 8 -23.75 C 8.238281 -23.75 8.460938 -23.710938 8.671875 -23.640625 C 8.878906 -23.578125 9.0625 -23.46875 9.21875 -23.3125 C 9.382812 -23.15625 9.5 -22.96875 9.5625 -22.75 C 9.625 -22.539062 9.65625 -22.316406 9.65625 -22.078125 C 9.65625 -21.847656 9.625 -21.625 9.5625 -21.40625 C 9.5 -21.195312 9.382812 -21.007812 9.21875 -20.84375 C 9.0625 -20.6875 8.878906 -20.578125 8.671875 -20.515625 C 8.460938 -20.453125 8.238281 -20.421875 8 -20.421875 Z M 8 -20.421875 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-5">
-<path style="stroke:none;" d="M 2.40625 0 L 2.40625 -1.765625 L 7.265625 -1.765625 L 7.265625 -21.765625 L 2.84375 -21.765625 L 2.84375 -23.515625 L 9.1875 -23.515625 L 9.1875 -1.765625 L 13.59375 -1.765625 L 13.59375 0 Z M 2.40625 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-6">
-<path style="stroke:none;" d="M 8.03125 0.25 C 7.457031 0.25 6.882812 0.191406 6.3125 0.078125 C 5.75 -0.0351562 5.21875 -0.226562 4.71875 -0.5 C 4.21875 -0.78125 3.773438 -1.140625 3.390625 -1.578125 C 3.003906 -2.015625 2.695312 -2.5 2.46875 -3.03125 C 2.25 -3.570312 2.09375 -4.125 2 -4.6875 C 1.90625 -5.25 1.859375 -5.820312 1.859375 -6.40625 L 1.859375 -10.5625 C 1.859375 -11.132812 1.90625 -11.703125 2 -12.265625 C 2.09375 -12.835938 2.25 -13.382812 2.46875 -13.90625 C 2.695312 -14.425781 3.003906 -14.90625 3.390625 -15.34375 C 3.773438 -15.78125 4.210938 -16.140625 4.703125 -16.421875 C 5.191406 -16.710938 5.71875 -16.914062 6.28125 -17.03125 C 6.851562 -17.15625 7.425781 -17.21875 8 -17.21875 C 8.570312 -17.21875 9.140625 -17.15625 9.703125 -17.03125 C 10.273438 -16.914062 10.804688 -16.710938 11.296875 -16.421875 C 11.785156 -16.140625 12.222656 -15.78125 12.609375 -15.34375 C 12.992188 -14.90625 13.296875 -14.425781 13.515625 -13.90625 C 13.742188 -13.382812 13.90625 -12.835938 14 -12.265625 C 14.09375 -11.703125 14.140625 -11.132812 14.140625 -10.5625 L 14.140625 -7.578125 L 3.8125 -7.578125 L 3.8125 -6.40625 C 3.8125 -6 3.835938 -5.59375 3.890625 -5.1875 C 3.941406 -4.78125 4.039062 -4.382812 4.1875 -4 C 4.34375 -3.613281 4.546875 -3.253906 4.796875 -2.921875 C 5.054688 -2.597656 5.351562 -2.328125 5.6875 -2.109375 C 6.03125 -1.898438 6.40625 -1.742188 6.8125 -1.640625 C 7.21875 -1.546875 7.625 -1.5 8.03125 -1.5 C 8.5 -1.5 8.972656 -1.546875 9.453125 -1.640625 C 9.929688 -1.742188 10.367188 -1.921875 10.765625 -2.171875 C 11.160156 -2.429688 11.476562 -2.773438 11.71875 -3.203125 C 11.96875 -3.628906 12.101562 -4.078125 12.125 -4.546875 L 14.078125 -4.546875 C 14.054688 -4.078125 13.960938 -3.609375 13.796875 -3.140625 C 13.640625 -2.671875 13.414062 -2.242188 13.125 -1.859375 C 12.84375 -1.472656 12.503906 -1.140625 12.109375 -0.859375 C 11.710938 -0.585938 11.289062 -0.363281 10.84375 -0.1875 C 10.394531 -0.0195312 9.9375 0.09375 9.46875 0.15625 C 9 0.21875 8.519531 0.25 8.03125 0.25 Z M 12.1875 -9.375 L 12.1875 -10.5625 C 12.1875 -10.96875 12.160156 -11.375 12.109375 -11.78125 C 12.054688 -12.1875 11.953125 -12.570312 11.796875 -12.9375 C 11.648438 -13.3125 11.453125 -13.664062 11.203125 -14 C 10.960938 -14.332031 10.671875 -14.609375 10.328125 -14.828125 C 9.992188 -15.054688 9.625 -15.21875 9.21875 -15.3125 C 8.8125 -15.40625 8.40625 -15.453125 8 -15.453125 C 7.59375 -15.453125 7.1875 -15.40625 6.78125 -15.3125 C 6.375 -15.21875 6 -15.054688 5.65625 -14.828125 C 5.320312 -14.609375 5.03125 -14.332031 4.78125 -14 C 4.539062 -13.664062 4.34375 -13.3125 4.1875 -12.9375 C 4.039062 -12.570312 3.941406 -12.1875 3.890625 -11.78125 C 3.835938 -11.375 3.8125 -10.96875 3.8125 -10.5625 L 3.8125 -9.375 Z M 12.1875 -9.375 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-7">
-<path style="stroke:none;" d="M 2.71875 4.578125 L 2.71875 2.8125 L 10.75 2.8125 L 10.75 -24.578125 L 2.71875 -24.578125 L 2.71875 -26.34375 L 12.703125 -26.34375 L 12.703125 4.578125 Z M 2.71875 4.578125 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-8">
-<path style="stroke:none;" d="M 2.375 0 L 2.375 -23.515625 L 13.890625 -23.515625 L 13.890625 -21.765625 L 4.3125 -21.765625 L 4.3125 -13.125 L 12.125 -13.125 L 12.125 -11.359375 L 4.3125 -11.359375 L 4.3125 -1.765625 L 13.890625 -1.765625 L 13.890625 0 Z M 2.375 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-9">
-<path style="stroke:none;" d="M 7.265625 0.25 C 6.710938 0.25 6.171875 0.179688 5.640625 0.046875 C 5.117188 -0.0859375 4.640625 -0.316406 4.203125 -0.640625 C 3.765625 -0.960938 3.390625 -1.347656 3.078125 -1.796875 C 2.773438 -2.242188 2.53125 -2.71875 2.34375 -3.21875 C 2.164062 -3.71875 2.039062 -4.238281 1.96875 -4.78125 C 1.894531 -5.320312 1.859375 -5.863281 1.859375 -6.40625 L 1.859375 -10.5625 C 1.859375 -11.09375 1.894531 -11.628906 1.96875 -12.171875 C 2.039062 -12.710938 2.164062 -13.234375 2.34375 -13.734375 C 2.53125 -14.242188 2.773438 -14.722656 3.078125 -15.171875 C 3.390625 -15.617188 3.765625 -16 4.203125 -16.3125 C 4.640625 -16.632812 5.117188 -16.863281 5.640625 -17 C 6.171875 -17.144531 6.710938 -17.21875 7.265625 -17.21875 C 7.773438 -17.21875 8.28125 -17.148438 8.78125 -17.015625 C 9.28125 -16.890625 9.738281 -16.671875 10.15625 -16.359375 C 10.570312 -16.054688 10.929688 -15.691406 11.234375 -15.265625 C 11.535156 -14.835938 11.769531 -14.378906 11.9375 -13.890625 L 11.9375 -23.515625 L 13.890625 -23.515625 L 13.890625 0 L 11.9375 0 L 11.9375 -3.078125 C 11.769531 -2.585938 11.535156 -2.128906 11.234375 -1.703125 C 10.929688 -1.273438 10.570312 -0.90625 10.15625 -0.59375 C 9.738281 -0.28125 9.28125 -0.0625 8.78125 0.0625 C 8.28125 0.1875 7.773438 0.25 7.265625 0.25 Z M 7.96875 -1.5 C 8.375 -1.5 8.773438 -1.546875 9.171875 -1.640625 C 9.566406 -1.742188 9.921875 -1.910156 10.234375 -2.140625 C 10.554688 -2.378906 10.828125 -2.660156 11.046875 -2.984375 C 11.273438 -3.316406 11.453125 -3.671875 11.578125 -4.046875 C 11.710938 -4.421875 11.804688 -4.804688 11.859375 -5.203125 C 11.910156 -5.597656 11.9375 -6 11.9375 -6.40625 L 11.9375 -10.5625 C 11.9375 -10.96875 11.910156 -11.367188 11.859375 -11.765625 C 11.804688 -12.160156 11.710938 -12.539062 11.578125 -12.90625 C 11.453125 -13.28125 11.273438 -13.632812 11.046875 -13.96875 C 10.828125 -14.300781 10.550781 -14.582031 10.21875 -14.8125 C 9.894531 -15.050781 9.539062 -15.21875 9.15625 -15.3125 C 8.769531 -15.40625 8.375 -15.453125 7.96875 -15.453125 C 7.5625 -15.453125 7.160156 -15.40625 6.765625 -15.3125 C 6.367188 -15.21875 6 -15.054688 5.65625 -14.828125 C 5.320312 -14.609375 5.03125 -14.332031 4.78125 -14 C 4.539062 -13.664062 4.34375 -13.3125 4.1875 -12.9375 C 4.039062 -12.570312 3.941406 -12.1875 3.890625 -11.78125 C 3.835938 -11.375 3.8125 -10.96875 3.8125 -10.5625 L 3.8125 -6.40625 C 3.8125 -6 3.835938 -5.59375 3.890625 -5.1875 C 3.941406 -4.78125 4.039062 -4.390625 4.1875 -4.015625 C 4.34375 -3.640625 4.539062 -3.285156 4.78125 -2.953125 C 5.03125 -2.628906 5.320312 -2.351562 5.65625 -2.125 C 6 -1.90625 6.367188 -1.742188 6.765625 -1.640625 C 7.160156 -1.546875 7.5625 -1.5 7.96875 -1.5 Z M 7.96875 -1.5 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-10">
-<path style="stroke:none;" d="M 10.40625 0.25 C 9.90625 0.25 9.40625 0.1875 8.90625 0.0625 C 8.40625 -0.0625 7.953125 -0.265625 7.546875 -0.546875 C 7.140625 -0.835938 6.796875 -1.195312 6.515625 -1.625 C 6.242188 -2.050781 6.019531 -2.5 5.84375 -2.96875 C 5.675781 -3.445312 5.566406 -3.9375 5.515625 -4.4375 C 5.460938 -4.9375 5.4375 -5.429688 5.4375 -5.921875 L 5.4375 -15.203125 L 1.65625 -15.203125 L 1.65625 -16.953125 L 5.4375 -16.953125 L 5.4375 -23.515625 L 7.359375 -23.515625 L 7.359375 -16.953125 L 12.859375 -16.953125 L 12.859375 -15.203125 L 7.359375 -15.203125 L 7.359375 -5.921875 C 7.359375 -5.597656 7.375 -5.265625 7.40625 -4.921875 C 7.4375 -4.585938 7.5 -4.257812 7.59375 -3.9375 C 7.695312 -3.613281 7.820312 -3.296875 7.96875 -2.984375 C 8.113281 -2.679688 8.304688 -2.414062 8.546875 -2.1875 C 8.796875 -1.96875 9.082031 -1.796875 9.40625 -1.671875 C 9.726562 -1.554688 10.0625 -1.5 10.40625 -1.5 C 10.71875 -1.5 11.019531 -1.539062 11.3125 -1.625 C 11.601562 -1.71875 11.867188 -1.851562 12.109375 -2.03125 C 12.359375 -2.207031 12.566406 -2.425781 12.734375 -2.6875 C 12.898438 -2.945312 13.035156 -3.21875 13.140625 -3.5 C 13.253906 -3.789062 13.332031 -4.082031 13.375 -4.375 C 13.414062 -4.675781 13.4375 -4.988281 13.4375 -5.3125 C 13.4375 -5.332031 13.4375 -5.363281 13.4375 -5.40625 C 13.4375 -5.445312 13.4375 -5.476562 13.4375 -5.5 L 15.390625 -5.5 C 15.390625 -5.457031 15.390625 -5.410156 15.390625 -5.359375 C 15.390625 -5.304688 15.390625 -5.257812 15.390625 -5.21875 C 15.390625 -4.75 15.347656 -4.285156 15.265625 -3.828125 C 15.179688 -3.367188 15.046875 -2.925781 14.859375 -2.5 C 14.679688 -2.070312 14.4375 -1.675781 14.125 -1.3125 C 13.8125 -0.945312 13.457031 -0.640625 13.0625 -0.390625 C 12.675781 -0.148438 12.253906 0.015625 11.796875 0.109375 C 11.335938 0.203125 10.875 0.25 10.40625 0.25 Z M 10.40625 0.25 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-11">
-<path style="stroke:none;" d="M 6.875 0 C 6.519531 -1.15625 6.164062 -2.304688 5.8125 -3.453125 C 5.457031 -4.609375 5.113281 -5.769531 4.78125 -6.9375 C 4.445312 -8.113281 4.128906 -9.28125 3.828125 -10.4375 C 3.535156 -11.601562 3.265625 -12.78125 3.015625 -13.96875 C 2.773438 -15.15625 2.5625 -16.347656 2.375 -17.546875 C 2.195312 -18.753906 2.109375 -19.96875 2.109375 -21.1875 L 2.109375 -23.515625 L 4.0625 -23.515625 L 4.0625 -21.1875 C 4.0625 -20.09375 4.132812 -19.015625 4.28125 -17.953125 C 4.4375 -16.890625 4.613281 -15.828125 4.8125 -14.765625 C 5.019531 -13.710938 5.242188 -12.660156 5.484375 -11.609375 C 5.734375 -10.566406 5.988281 -9.519531 6.25 -8.46875 C 6.519531 -7.425781 6.796875 -6.390625 7.078125 -5.359375 C 7.367188 -4.328125 7.675781 -3.296875 8 -2.265625 C 8.320312 -3.296875 8.625 -4.328125 8.90625 -5.359375 C 9.195312 -6.390625 9.472656 -7.425781 9.734375 -8.46875 C 10.003906 -9.519531 10.257812 -10.566406 10.5 -11.609375 C 10.75 -12.660156 10.972656 -13.710938 11.171875 -14.765625 C 11.378906 -15.828125 11.554688 -16.890625 11.703125 -17.953125 C 11.859375 -19.015625 11.9375 -20.09375 11.9375 -21.1875 L 11.9375 -23.515625 L 13.890625 -23.515625 L 13.890625 -21.1875 C 13.890625 -19.96875 13.796875 -18.753906 13.609375 -17.546875 C 13.429688 -16.347656 13.21875 -15.15625 12.96875 -13.96875 C 12.726562 -12.78125 12.457031 -11.601562 12.15625 -10.4375 C 11.863281 -9.28125 11.550781 -8.113281 11.21875 -6.9375 C 10.882812 -5.769531 10.539062 -4.609375 10.1875 -3.453125 C 9.832031 -2.304688 9.476562 -1.15625 9.125 0 Z M 6.875 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-12">
-<path style="stroke:none;" d="M 4.484375 0 C 4.265625 -0.6875 4.054688 -1.378906 3.859375 -2.078125 C 3.671875 -2.785156 3.484375 -3.484375 3.296875 -4.171875 C 3.109375 -4.867188 2.925781 -5.570312 2.75 -6.28125 C 2.582031 -7 2.425781 -7.707031 2.28125 -8.40625 C 2.144531 -9.113281 2.03125 -9.828125 1.9375 -10.546875 C 1.84375 -11.273438 1.796875 -12.003906 1.796875 -12.734375 L 1.796875 -16.953125 L 3.71875 -16.953125 L 3.71875 -12.734375 C 3.71875 -11.816406 3.773438 -10.910156 3.890625 -10.015625 C 4.003906 -9.117188 4.132812 -8.222656 4.28125 -7.328125 C 4.4375 -6.429688 4.597656 -5.539062 4.765625 -4.65625 C 4.941406 -3.769531 5.125 -2.878906 5.3125 -1.984375 L 7.203125 -10.171875 L 8.796875 -10.171875 L 10.6875 -1.984375 C 10.875 -2.878906 11.050781 -3.769531 11.21875 -4.65625 C 11.394531 -5.539062 11.554688 -6.429688 11.703125 -7.328125 C 11.859375 -8.222656 11.992188 -9.117188 12.109375 -10.015625 C 12.222656 -10.910156 12.28125 -11.816406 12.28125 -12.734375 L 12.28125 -16.953125 L 14.203125 -16.953125 L 14.203125 -12.734375 C 14.203125 -12.003906 14.15625 -11.273438 14.0625 -10.546875 C 13.96875 -9.828125 13.847656 -9.113281 13.703125 -8.40625 C 13.566406 -7.707031 13.410156 -7 13.234375 -6.28125 C 13.066406 -5.570312 12.890625 -4.867188 12.703125 -4.171875 C 12.515625 -3.484375 12.320312 -2.785156 12.125 -2.078125 C 11.9375 -1.378906 11.734375 -0.6875 11.515625 0 L 9.890625 0 L 8 -8.21875 L 6.109375 0 Z M 4.484375 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-13">
-<path style="stroke:none;" d="M 4.484375 0 C 4.265625 -0.957031 4.050781 -1.925781 3.84375 -2.90625 C 3.644531 -3.894531 3.457031 -4.875 3.28125 -5.84375 C 3.101562 -6.8125 2.925781 -7.785156 2.75 -8.765625 C 2.582031 -9.742188 2.425781 -10.722656 2.28125 -11.703125 C 2.144531 -12.691406 2.03125 -13.679688 1.9375 -14.671875 C 1.84375 -15.660156 1.796875 -16.644531 1.796875 -17.625 L 1.796875 -23.515625 L 3.71875 -23.515625 L 3.71875 -17.625 C 3.71875 -16.394531 3.773438 -15.15625 3.890625 -13.90625 C 4.003906 -12.65625 4.128906 -11.410156 4.265625 -10.171875 C 4.410156 -8.929688 4.570312 -7.691406 4.75 -6.453125 C 4.9375 -5.222656 5.125 -3.988281 5.3125 -2.75 L 7.203125 -14.109375 L 8.796875 -14.109375 L 10.6875 -2.75 C 10.875 -3.988281 11.054688 -5.222656 11.234375 -6.453125 C 11.421875 -7.691406 11.582031 -8.929688 11.71875 -10.171875 C 11.863281 -11.410156 11.992188 -12.65625 12.109375 -13.90625 C 12.222656 -15.15625 12.28125 -16.394531 12.28125 -17.625 L 12.28125 -23.515625 L 14.203125 -23.515625 L 14.203125 -17.625 C 14.203125 -16.644531 14.15625 -15.660156 14.0625 -14.671875 C 13.96875 -13.679688 13.847656 -12.691406 13.703125 -11.703125 C 13.566406 -10.722656 13.410156 -9.742188 13.234375 -8.765625 C 13.066406 -7.785156 12.894531 -6.8125 12.71875 -5.84375 C 12.539062 -4.875 12.347656 -3.894531 12.140625 -2.90625 C 11.941406 -1.925781 11.734375 -0.957031 11.515625 0 L 9.890625 0 L 8 -11.421875 L 6.109375 0 Z M 4.484375 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-14">
-<path style="stroke:none;" d="M 2.109375 0 L 2.109375 -16.953125 L 4.0625 -16.953125 L 4.0625 -13.890625 C 4.226562 -14.378906 4.460938 -14.835938 4.765625 -15.265625 C 5.066406 -15.691406 5.421875 -16.054688 5.828125 -16.359375 C 6.234375 -16.671875 6.691406 -16.890625 7.203125 -17.015625 C 7.710938 -17.148438 8.222656 -17.21875 8.734375 -17.21875 C 9.242188 -17.21875 9.757812 -17.140625 10.28125 -16.984375 C 10.8125 -16.835938 11.28125 -16.601562 11.6875 -16.28125 C 12.101562 -15.96875 12.457031 -15.582031 12.75 -15.125 C 13.039062 -14.664062 13.269531 -14.179688 13.4375 -13.671875 C 13.613281 -13.171875 13.734375 -12.660156 13.796875 -12.140625 C 13.859375 -11.617188 13.890625 -11.09375 13.890625 -10.5625 L 13.890625 0 L 11.9375 0 L 11.9375 -10.5625 C 11.9375 -10.96875 11.910156 -11.359375 11.859375 -11.734375 C 11.804688 -12.117188 11.710938 -12.5 11.578125 -12.875 C 11.453125 -13.25 11.273438 -13.609375 11.046875 -13.953125 C 10.828125 -14.296875 10.554688 -14.578125 10.234375 -14.796875 C 9.921875 -15.023438 9.570312 -15.191406 9.1875 -15.296875 C 8.800781 -15.398438 8.40625 -15.453125 8 -15.453125 C 7.59375 -15.453125 7.195312 -15.398438 6.8125 -15.296875 C 6.425781 -15.191406 6.070312 -15.023438 5.75 -14.796875 C 5.4375 -14.578125 5.164062 -14.296875 4.9375 -13.953125 C 4.71875 -13.609375 4.539062 -13.25 4.40625 -12.875 C 4.28125 -12.5 4.191406 -12.117188 4.140625 -11.734375 C 4.085938 -11.359375 4.0625 -10.96875 4.0625 -10.5625 L 4.0625 0 Z M 2.109375 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-15">
-<path style="stroke:none;" d="M 8 0.25 C 7.425781 0.25 6.851562 0.191406 6.28125 0.078125 C 5.71875 -0.0351562 5.191406 -0.234375 4.703125 -0.515625 C 4.210938 -0.804688 3.773438 -1.171875 3.390625 -1.609375 C 3.003906 -2.046875 2.695312 -2.523438 2.46875 -3.046875 C 2.25 -3.578125 2.09375 -4.125 2 -4.6875 C 1.90625 -5.25 1.859375 -5.820312 1.859375 -6.40625 L 1.859375 -10.5625 C 1.859375 -11.132812 1.90625 -11.703125 2 -12.265625 C 2.09375 -12.835938 2.25 -13.382812 2.46875 -13.90625 C 2.695312 -14.425781 3.003906 -14.910156 3.390625 -15.359375 C 3.773438 -15.804688 4.210938 -16.164062 4.703125 -16.4375 C 5.191406 -16.71875 5.71875 -16.929688 6.28125 -17.078125 C 6.851562 -17.234375 7.425781 -17.3125 8 -17.3125 C 8.570312 -17.3125 9.140625 -17.234375 9.703125 -17.078125 C 10.273438 -16.929688 10.804688 -16.71875 11.296875 -16.4375 C 11.785156 -16.164062 12.222656 -15.804688 12.609375 -15.359375 C 12.992188 -14.910156 13.296875 -14.425781 13.515625 -13.90625 C 13.742188 -13.382812 13.90625 -12.835938 14 -12.265625 C 14.09375 -11.703125 14.140625 -11.132812 14.140625 -10.5625 L 14.140625 -6.40625 C 14.140625 -5.820312 14.09375 -5.25 14 -4.6875 C 13.90625 -4.125 13.742188 -3.578125 13.515625 -3.046875 C 13.296875 -2.523438 12.992188 -2.046875 12.609375 -1.609375 C 12.222656 -1.171875 11.785156 -0.804688 11.296875 -0.515625 C 10.804688 -0.234375 10.273438 -0.0351562 9.703125 0.078125 C 9.140625 0.191406 8.570312 0.25 8 0.25 Z M 8 -1.5 C 8.40625 -1.5 8.8125 -1.546875 9.21875 -1.640625 C 9.625 -1.742188 9.992188 -1.90625 10.328125 -2.125 C 10.671875 -2.351562 10.960938 -2.628906 11.203125 -2.953125 C 11.453125 -3.285156 11.648438 -3.640625 11.796875 -4.015625 C 11.953125 -4.390625 12.054688 -4.78125 12.109375 -5.1875 C 12.160156 -5.59375 12.1875 -6 12.1875 -6.40625 L 12.1875 -10.5625 C 12.1875 -10.96875 12.160156 -11.375 12.109375 -11.78125 C 12.054688 -12.1875 11.953125 -12.578125 11.796875 -12.953125 C 11.648438 -13.335938 11.445312 -13.695312 11.1875 -14.03125 C 10.9375 -14.363281 10.632812 -14.632812 10.28125 -14.84375 C 9.9375 -15.0625 9.5625 -15.21875 9.15625 -15.3125 C 8.75 -15.40625 8.34375 -15.453125 7.9375 -15.453125 C 7.53125 -15.453125 7.128906 -15.40625 6.734375 -15.3125 C 6.335938 -15.21875 5.972656 -15.054688 5.640625 -14.828125 C 5.316406 -14.609375 5.03125 -14.332031 4.78125 -14 C 4.539062 -13.664062 4.34375 -13.3125 4.1875 -12.9375 C 4.039062 -12.570312 3.941406 -12.1875 3.890625 -11.78125 C 3.835938 -11.375 3.8125 -10.96875 3.8125 -10.5625 L 3.8125 -6.40625 C 3.8125 -6 3.835938 -5.59375 3.890625 -5.1875 C 3.941406 -4.78125 4.039062 -4.390625 4.1875 -4.015625 C 4.34375 -3.640625 4.539062 -3.285156 4.78125 -2.953125 C 5.03125 -2.628906 5.320312 -2.351562 5.65625 -2.125 C 6 -1.90625 6.375 -1.742188 6.78125 -1.640625 C 7.1875 -1.546875 7.59375 -1.5 8 -1.5 Z M 8 -1.5 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-16">
-<path style="stroke:none;" d="M 7.96875 0.25 C 7.5 0.25 7.035156 0.222656 6.578125 0.171875 C 6.117188 0.117188 5.664062 0.0195312 5.21875 -0.125 C 4.769531 -0.28125 4.347656 -0.484375 3.953125 -0.734375 C 3.554688 -0.992188 3.21875 -1.300781 2.9375 -1.65625 C 2.664062 -2.019531 2.445312 -2.425781 2.28125 -2.875 C 2.125 -3.320312 2.046875 -3.78125 2.046875 -4.25 C 2.046875 -4.25 2.046875 -4.253906 2.046875 -4.265625 C 2.046875 -4.273438 2.046875 -4.289062 2.046875 -4.3125 L 4 -4.3125 C 4 -4.3125 4 -4.304688 4 -4.296875 C 4 -4.285156 4 -4.28125 4 -4.28125 C 4 -3.832031 4.125 -3.410156 4.375 -3.015625 C 4.632812 -2.628906 4.957031 -2.320312 5.34375 -2.09375 C 5.726562 -1.875 6.148438 -1.71875 6.609375 -1.625 C 7.066406 -1.539062 7.519531 -1.5 7.96875 -1.5 C 8.414062 -1.5 8.867188 -1.539062 9.328125 -1.625 C 9.785156 -1.71875 10.210938 -1.878906 10.609375 -2.109375 C 11.003906 -2.347656 11.320312 -2.660156 11.5625 -3.046875 C 11.8125 -3.441406 11.9375 -3.878906 11.9375 -4.359375 C 11.9375 -4.804688 11.800781 -5.222656 11.53125 -5.609375 C 11.269531 -6.003906 10.941406 -6.316406 10.546875 -6.546875 C 10.148438 -6.785156 9.734375 -6.960938 9.296875 -7.078125 C 8.859375 -7.203125 8.421875 -7.332031 7.984375 -7.46875 C 7.546875 -7.613281 7.109375 -7.753906 6.671875 -7.890625 C 6.234375 -8.023438 5.804688 -8.191406 5.390625 -8.390625 C 4.972656 -8.597656 4.578125 -8.832031 4.203125 -9.09375 C 3.828125 -9.363281 3.5 -9.679688 3.21875 -10.046875 C 2.945312 -10.410156 2.738281 -10.816406 2.59375 -11.265625 C 2.445312 -11.710938 2.375 -12.160156 2.375 -12.609375 C 2.375 -13.054688 2.441406 -13.503906 2.578125 -13.953125 C 2.710938 -14.398438 2.910156 -14.8125 3.171875 -15.1875 C 3.441406 -15.5625 3.765625 -15.878906 4.140625 -16.140625 C 4.515625 -16.410156 4.914062 -16.625 5.34375 -16.78125 C 5.769531 -16.945312 6.210938 -17.0625 6.671875 -17.125 C 7.128906 -17.1875 7.582031 -17.21875 8.03125 -17.21875 C 8.476562 -17.21875 8.925781 -17.191406 9.375 -17.140625 C 9.820312 -17.085938 10.257812 -16.984375 10.6875 -16.828125 C 11.113281 -16.679688 11.515625 -16.472656 11.890625 -16.203125 C 12.265625 -15.941406 12.585938 -15.628906 12.859375 -15.265625 C 13.140625 -14.898438 13.347656 -14.503906 13.484375 -14.078125 C 13.628906 -13.648438 13.703125 -13.210938 13.703125 -12.765625 C 13.703125 -12.742188 13.703125 -12.726562 13.703125 -12.71875 C 13.703125 -12.707031 13.703125 -12.703125 13.703125 -12.703125 L 11.75 -12.703125 C 11.75 -12.703125 11.75 -12.710938 11.75 -12.734375 C 11.75 -13.160156 11.632812 -13.566406 11.40625 -13.953125 C 11.1875 -14.335938 10.894531 -14.640625 10.53125 -14.859375 C 10.164062 -15.085938 9.765625 -15.242188 9.328125 -15.328125 C 8.890625 -15.410156 8.457031 -15.453125 8.03125 -15.453125 C 7.601562 -15.453125 7.171875 -15.410156 6.734375 -15.328125 C 6.296875 -15.242188 5.894531 -15.078125 5.53125 -14.828125 C 5.164062 -14.585938 4.867188 -14.269531 4.640625 -13.875 C 4.421875 -13.476562 4.3125 -13.054688 4.3125 -12.609375 C 4.3125 -12.242188 4.398438 -11.890625 4.578125 -11.546875 C 4.765625 -11.210938 5.003906 -10.925781 5.296875 -10.6875 C 5.585938 -10.457031 5.910156 -10.269531 6.265625 -10.125 C 6.628906 -9.988281 6.992188 -9.867188 7.359375 -9.765625 L 7.390625 -9.765625 C 7.878906 -9.609375 8.367188 -9.457031 8.859375 -9.3125 C 9.347656 -9.164062 9.828125 -8.992188 10.296875 -8.796875 C 10.773438 -8.609375 11.226562 -8.378906 11.65625 -8.109375 C 12.09375 -7.847656 12.476562 -7.523438 12.8125 -7.140625 C 13.144531 -6.753906 13.40625 -6.320312 13.59375 -5.84375 C 13.789062 -5.363281 13.890625 -4.867188 13.890625 -4.359375 C 13.890625 -3.878906 13.8125 -3.414062 13.65625 -2.96875 C 13.507812 -2.519531 13.289062 -2.109375 13 -1.734375 C 12.71875 -1.367188 12.378906 -1.050781 11.984375 -0.78125 C 11.585938 -0.519531 11.171875 -0.304688 10.734375 -0.140625 C 10.296875 0.015625 9.84375 0.117188 9.375 0.171875 C 8.90625 0.222656 8.4375 0.25 7.96875 0.25 Z M 7.96875 0.25 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-17">
-<path style="stroke:none;" d="M 2.109375 0 L 2.109375 -23.515625 L 4.0625 -23.515625 L 4.0625 -13.09375 L 11.9375 -13.09375 L 11.9375 -23.515625 L 13.890625 -23.515625 L 13.890625 0 L 11.9375 0 L 11.9375 -11.296875 L 4.0625 -11.296875 L 4.0625 0 Z M 2.109375 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-18">
-<path style="stroke:none;" d="M 2.109375 6.5625 L 2.109375 -16.953125 L 4.0625 -16.953125 L 4.0625 -13.890625 C 4.226562 -14.378906 4.460938 -14.835938 4.765625 -15.265625 C 5.066406 -15.691406 5.425781 -16.054688 5.84375 -16.359375 C 6.257812 -16.671875 6.71875 -16.890625 7.21875 -17.015625 C 7.71875 -17.148438 8.222656 -17.21875 8.734375 -17.21875 C 9.285156 -17.21875 9.820312 -17.144531 10.34375 -17 C 10.875 -16.863281 11.359375 -16.632812 11.796875 -16.3125 C 12.234375 -16 12.601562 -15.617188 12.90625 -15.171875 C 13.21875 -14.722656 13.460938 -14.242188 13.640625 -13.734375 C 13.828125 -13.234375 13.957031 -12.710938 14.03125 -12.171875 C 14.101562 -11.628906 14.140625 -11.09375 14.140625 -10.5625 L 14.140625 -6.40625 C 14.140625 -5.863281 14.101562 -5.320312 14.03125 -4.78125 C 13.957031 -4.238281 13.828125 -3.71875 13.640625 -3.21875 C 13.460938 -2.71875 13.21875 -2.242188 12.90625 -1.796875 C 12.601562 -1.347656 12.234375 -0.960938 11.796875 -0.640625 C 11.359375 -0.316406 10.875 -0.0859375 10.34375 0.046875 C 9.820312 0.179688 9.285156 0.25 8.734375 0.25 C 8.222656 0.25 7.71875 0.1875 7.21875 0.0625 C 6.71875 -0.0625 6.257812 -0.28125 5.84375 -0.59375 C 5.425781 -0.90625 5.066406 -1.273438 4.765625 -1.703125 C 4.460938 -2.128906 4.226562 -2.585938 4.0625 -3.078125 L 4.0625 6.5625 Z M 8.03125 -1.5 C 8.4375 -1.5 8.835938 -1.546875 9.234375 -1.640625 C 9.628906 -1.742188 9.992188 -1.90625 10.328125 -2.125 C 10.671875 -2.351562 10.960938 -2.628906 11.203125 -2.953125 C 11.453125 -3.285156 11.648438 -3.640625 11.796875 -4.015625 C 11.953125 -4.390625 12.054688 -4.78125 12.109375 -5.1875 C 12.160156 -5.59375 12.1875 -6 12.1875 -6.40625 L 12.1875 -10.5625 C 12.1875 -10.96875 12.160156 -11.375 12.109375 -11.78125 C 12.054688 -12.1875 11.953125 -12.570312 11.796875 -12.9375 C 11.648438 -13.3125 11.453125 -13.664062 11.203125 -14 C 10.960938 -14.332031 10.671875 -14.609375 10.328125 -14.828125 C 9.992188 -15.054688 9.628906 -15.21875 9.234375 -15.3125 C 8.835938 -15.40625 8.4375 -15.453125 8.03125 -15.453125 C 7.625 -15.453125 7.226562 -15.40625 6.84375 -15.3125 C 6.457031 -15.21875 6.097656 -15.050781 5.765625 -14.8125 C 5.441406 -14.582031 5.164062 -14.300781 4.9375 -13.96875 C 4.71875 -13.632812 4.539062 -13.28125 4.40625 -12.90625 C 4.28125 -12.539062 4.191406 -12.160156 4.140625 -11.765625 C 4.085938 -11.367188 4.0625 -10.96875 4.0625 -10.5625 L 4.0625 -6.40625 C 4.0625 -6 4.085938 -5.597656 4.140625 -5.203125 C 4.191406 -4.804688 4.28125 -4.421875 4.40625 -4.046875 C 4.539062 -3.671875 4.71875 -3.316406 4.9375 -2.984375 C 5.164062 -2.660156 5.441406 -2.378906 5.765625 -2.140625 C 6.097656 -1.910156 6.457031 -1.742188 6.84375 -1.640625 C 7.226562 -1.546875 7.625 -1.5 8.03125 -1.5 Z M 8.03125 -1.5 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-19">
-<path style="stroke:none;" d="M 2.109375 0 L 2.109375 -0.03125 C 2.109375 -0.625 2.117188 -1.210938 2.140625 -1.796875 C 2.160156 -2.390625 2.222656 -2.972656 2.328125 -3.546875 C 2.441406 -4.128906 2.597656 -4.695312 2.796875 -5.25 C 3.003906 -5.800781 3.25 -6.332031 3.53125 -6.84375 C 3.820312 -7.351562 4.160156 -7.835938 4.546875 -8.296875 C 4.929688 -8.753906 5.335938 -9.179688 5.765625 -9.578125 C 6.191406 -9.972656 6.644531 -10.347656 7.125 -10.703125 C 7.601562 -11.054688 8.082031 -11.398438 8.5625 -11.734375 C 9.039062 -12.078125 9.503906 -12.445312 9.953125 -12.84375 C 10.398438 -13.238281 10.78125 -13.679688 11.09375 -14.171875 C 11.414062 -14.660156 11.65625 -15.191406 11.8125 -15.765625 C 11.976562 -16.347656 12.0625 -16.925781 12.0625 -17.5 C 12.0625 -18.082031 11.988281 -18.65625 11.84375 -19.21875 C 11.707031 -19.78125 11.453125 -20.28125 11.078125 -20.71875 C 10.710938 -21.15625 10.253906 -21.476562 9.703125 -21.6875 C 9.160156 -21.90625 8.601562 -22.015625 8.03125 -22.015625 C 7.5 -22.015625 6.976562 -21.929688 6.46875 -21.765625 C 5.957031 -21.609375 5.507812 -21.347656 5.125 -20.984375 C 4.738281 -20.628906 4.453125 -20.191406 4.265625 -19.671875 C 4.085938 -19.160156 4 -18.640625 4 -18.109375 C 4 -18.085938 4 -18.070312 4 -18.0625 C 4 -18.050781 4 -18.035156 4 -18.015625 L 2.046875 -18.015625 C 2.046875 -18.035156 2.046875 -18.054688 2.046875 -18.078125 C 2.046875 -18.097656 2.046875 -18.117188 2.046875 -18.140625 C 2.046875 -18.648438 2.109375 -19.160156 2.234375 -19.671875 C 2.367188 -20.191406 2.554688 -20.675781 2.796875 -21.125 C 3.046875 -21.570312 3.359375 -21.96875 3.734375 -22.3125 C 4.117188 -22.664062 4.546875 -22.953125 5.015625 -23.171875 C 5.492188 -23.398438 5.988281 -23.554688 6.5 -23.640625 C 7.007812 -23.734375 7.519531 -23.78125 8.03125 -23.78125 C 8.582031 -23.78125 9.125 -23.722656 9.65625 -23.609375 C 10.195312 -23.503906 10.707031 -23.320312 11.1875 -23.0625 C 11.664062 -22.8125 12.097656 -22.484375 12.484375 -22.078125 C 12.867188 -21.671875 13.171875 -21.21875 13.390625 -20.71875 C 13.617188 -20.21875 13.78125 -19.695312 13.875 -19.15625 C 13.96875 -18.613281 14.015625 -18.0625 14.015625 -17.5 C 14.015625 -16.863281 13.929688 -16.222656 13.765625 -15.578125 C 13.609375 -14.941406 13.367188 -14.34375 13.046875 -13.78125 C 12.734375 -13.226562 12.351562 -12.71875 11.90625 -12.25 C 11.457031 -11.78125 10.984375 -11.34375 10.484375 -10.9375 C 9.984375 -10.539062 9.460938 -10.148438 8.921875 -9.765625 C 8.390625 -9.378906 7.890625 -8.972656 7.421875 -8.546875 C 6.953125 -8.117188 6.507812 -7.648438 6.09375 -7.140625 C 5.675781 -6.628906 5.320312 -6.082031 5.03125 -5.5 C 4.75 -4.925781 4.53125 -4.316406 4.375 -3.671875 C 4.226562 -3.035156 4.132812 -2.398438 4.09375 -1.765625 L 13.890625 -1.765625 L 13.890625 0 Z M 2.109375 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-20">
-<path style="stroke:none;" d="M 8 0.25 C 7.382812 0.25 6.78125 0.128906 6.1875 -0.109375 C 5.601562 -0.359375 5.097656 -0.710938 4.671875 -1.171875 C 4.242188 -1.628906 3.882812 -2.132812 3.59375 -2.6875 C 3.3125 -3.238281 3.066406 -3.804688 2.859375 -4.390625 C 2.660156 -4.984375 2.492188 -5.582031 2.359375 -6.1875 C 2.234375 -6.800781 2.132812 -7.414062 2.0625 -8.03125 C 1.988281 -8.644531 1.9375 -9.257812 1.90625 -9.875 C 1.875 -10.5 1.859375 -11.125 1.859375 -11.75 L 1.859375 -11.78125 C 1.859375 -12.394531 1.875 -13.007812 1.90625 -13.625 C 1.9375 -14.25 1.988281 -14.867188 2.0625 -15.484375 C 2.132812 -16.109375 2.234375 -16.726562 2.359375 -17.34375 C 2.492188 -17.957031 2.660156 -18.554688 2.859375 -19.140625 C 3.066406 -19.734375 3.3125 -20.3125 3.59375 -20.875 C 3.882812 -21.445312 4.253906 -21.953125 4.703125 -22.390625 C 5.148438 -22.828125 5.664062 -23.164062 6.25 -23.40625 C 6.84375 -23.65625 7.457031 -23.78125 8.09375 -23.78125 C 8.71875 -23.78125 9.316406 -23.648438 9.890625 -23.390625 C 10.460938 -23.140625 10.957031 -22.78125 11.375 -22.3125 C 11.789062 -21.851562 12.140625 -21.347656 12.421875 -20.796875 C 12.710938 -20.242188 12.957031 -19.671875 13.15625 -19.078125 C 13.363281 -18.492188 13.523438 -17.898438 13.640625 -17.296875 C 13.765625 -16.691406 13.863281 -16.082031 13.9375 -15.46875 C 14.007812 -14.863281 14.0625 -14.242188 14.09375 -13.609375 C 14.125 -12.984375 14.140625 -12.363281 14.140625 -11.75 C 14.140625 -11.125 14.125 -10.5 14.09375 -9.875 C 14.0625 -9.257812 14.007812 -8.644531 13.9375 -8.03125 C 13.863281 -7.414062 13.757812 -6.800781 13.625 -6.1875 C 13.5 -5.582031 13.332031 -4.984375 13.125 -4.390625 C 12.925781 -3.804688 12.679688 -3.238281 12.390625 -2.6875 C 12.109375 -2.132812 11.753906 -1.628906 11.328125 -1.171875 C 10.898438 -0.710938 10.390625 -0.359375 9.796875 -0.109375 C 9.210938 0.128906 8.613281 0.25 8 0.25 Z M 8 -1.5 C 8.507812 -1.5 8.976562 -1.644531 9.40625 -1.9375 C 9.832031 -2.226562 10.179688 -2.578125 10.453125 -2.984375 C 10.734375 -3.398438 10.960938 -3.847656 11.140625 -4.328125 C 11.328125 -4.816406 11.476562 -5.300781 11.59375 -5.78125 C 11.71875 -6.257812 11.816406 -6.75 11.890625 -7.25 C 11.960938 -7.75 12.019531 -8.25 12.0625 -8.75 C 12.101562 -9.25 12.132812 -9.75 12.15625 -10.25 C 12.175781 -10.757812 12.1875 -11.269531 12.1875 -11.78125 C 12.1875 -12.269531 12.175781 -12.765625 12.15625 -13.265625 C 12.132812 -13.765625 12.101562 -14.265625 12.0625 -14.765625 C 12.019531 -15.265625 11.960938 -15.765625 11.890625 -16.265625 C 11.816406 -16.773438 11.71875 -17.269531 11.59375 -17.75 C 11.476562 -18.226562 11.328125 -18.703125 11.140625 -19.171875 C 10.960938 -19.640625 10.734375 -20.085938 10.453125 -20.515625 C 10.179688 -20.941406 9.832031 -21.289062 9.40625 -21.5625 C 8.976562 -21.84375 8.507812 -21.984375 8 -21.984375 C 7.488281 -21.984375 7.019531 -21.84375 6.59375 -21.5625 C 6.164062 -21.289062 5.8125 -20.941406 5.53125 -20.515625 C 5.257812 -20.085938 5.03125 -19.640625 4.84375 -19.171875 C 4.664062 -18.703125 4.515625 -18.226562 4.390625 -17.75 C 4.273438 -17.269531 4.179688 -16.773438 4.109375 -16.265625 C 4.035156 -15.765625 3.976562 -15.265625 3.9375 -14.765625 C 3.894531 -14.265625 3.863281 -13.765625 3.84375 -13.265625 C 3.820312 -12.765625 3.8125 -12.269531 3.8125 -11.78125 C 3.8125 -11.269531 3.820312 -10.757812 3.84375 -10.25 C 3.863281 -9.75 3.894531 -9.25 3.9375 -8.75 C 3.976562 -8.25 4.035156 -7.75 4.109375 -7.25 C 4.179688 -6.75 4.273438 -6.257812 4.390625 -5.78125 C 4.515625 -5.300781 4.664062 -4.816406 4.84375 -4.328125 C 5.03125 -3.847656 5.257812 -3.398438 5.53125 -2.984375 C 5.8125 -2.578125 6.164062 -2.226562 6.59375 -1.9375 C 7.019531 -1.644531 7.488281 -1.5 8 -1.5 Z M 8 -9.828125 C 7.738281 -9.828125 7.476562 -9.863281 7.21875 -9.9375 C 6.96875 -10.007812 6.75 -10.140625 6.5625 -10.328125 C 6.382812 -10.523438 6.257812 -10.742188 6.1875 -10.984375 C 6.113281 -11.234375 6.078125 -11.5 6.078125 -11.78125 C 6.078125 -12.03125 6.113281 -12.273438 6.1875 -12.515625 C 6.257812 -12.765625 6.382812 -12.984375 6.5625 -13.171875 C 6.75 -13.367188 6.96875 -13.503906 7.21875 -13.578125 C 7.476562 -13.660156 7.738281 -13.703125 8 -13.703125 C 8.257812 -13.703125 8.515625 -13.660156 8.765625 -13.578125 C 9.023438 -13.503906 9.242188 -13.367188 9.421875 -13.171875 C 9.609375 -12.984375 9.738281 -12.765625 9.8125 -12.515625 C 9.882812 -12.273438 9.921875 -12.03125 9.921875 -11.78125 C 9.921875 -11.5 9.882812 -11.234375 9.8125 -10.984375 C 9.738281 -10.742188 9.609375 -10.523438 9.421875 -10.328125 C 9.242188 -10.140625 9.023438 -10.007812 8.765625 -9.9375 C 8.515625 -9.863281 8.257812 -9.828125 8 -9.828125 Z M 8 -9.828125 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-21">
-<path style="stroke:none;" d="M 8 0 L 8 -21.3125 L 3.015625 -17.890625 L 1.921875 -19.328125 L 8 -23.515625 L 9.921875 -23.515625 L 9.921875 0 Z M 8 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-22">
-<path style="stroke:none;" d="M 8.03125 0.25 C 7.4375 0.25 6.84375 0.1875 6.25 0.0625 C 5.664062 -0.0625 5.125 -0.289062 4.625 -0.625 C 4.125 -0.957031 3.6875 -1.363281 3.3125 -1.84375 C 2.9375 -2.320312 2.640625 -2.835938 2.421875 -3.390625 C 2.210938 -3.941406 2.066406 -4.515625 1.984375 -5.109375 C 1.898438 -5.710938 1.859375 -6.3125 1.859375 -6.90625 L 1.859375 -17.125 C 1.859375 -17.695312 1.898438 -18.265625 1.984375 -18.828125 C 2.066406 -19.398438 2.21875 -19.945312 2.4375 -20.46875 C 2.664062 -20.988281 2.96875 -21.46875 3.34375 -21.90625 C 3.71875 -22.34375 4.148438 -22.703125 4.640625 -22.984375 C 5.128906 -23.273438 5.65625 -23.476562 6.21875 -23.59375 C 6.789062 -23.71875 7.351562 -23.78125 7.90625 -23.78125 C 8.4375 -23.78125 8.960938 -23.734375 9.484375 -23.640625 C 10.003906 -23.554688 10.5 -23.394531 10.96875 -23.15625 C 11.445312 -22.925781 11.875 -22.625 12.25 -22.25 C 12.632812 -21.882812 12.945312 -21.46875 13.1875 -21 C 13.4375 -20.539062 13.625 -20.050781 13.75 -19.53125 C 13.882812 -19.007812 13.953125 -18.484375 13.953125 -17.953125 C 13.953125 -17.953125 13.953125 -17.945312 13.953125 -17.9375 C 13.953125 -17.925781 13.953125 -17.921875 13.953125 -17.921875 L 12 -17.921875 C 12 -17.921875 12 -17.925781 12 -17.9375 C 12 -17.945312 12 -17.953125 12 -17.953125 C 12 -18.484375 11.90625 -19.015625 11.71875 -19.546875 C 11.539062 -20.085938 11.257812 -20.546875 10.875 -20.921875 C 10.488281 -21.304688 10.035156 -21.582031 9.515625 -21.75 C 8.992188 -21.925781 8.457031 -22.015625 7.90625 -22.015625 C 7.5 -22.015625 7.097656 -21.96875 6.703125 -21.875 C 6.304688 -21.78125 5.941406 -21.617188 5.609375 -21.390625 C 5.285156 -21.171875 5 -20.894531 4.75 -20.5625 C 4.507812 -20.226562 4.316406 -19.867188 4.171875 -19.484375 C 4.035156 -19.097656 3.941406 -18.707031 3.890625 -18.3125 C 3.835938 -17.925781 3.8125 -17.53125 3.8125 -17.125 L 3.8125 -11.9375 C 4.039062 -12.300781 4.3125 -12.617188 4.625 -12.890625 C 4.9375 -13.171875 5.273438 -13.398438 5.640625 -13.578125 C 6.015625 -13.765625 6.410156 -13.894531 6.828125 -13.96875 C 7.242188 -14.039062 7.664062 -14.078125 8.09375 -14.078125 C 8.644531 -14.078125 9.203125 -14.015625 9.765625 -13.890625 C 10.335938 -13.765625 10.867188 -13.554688 11.359375 -13.265625 C 11.847656 -12.972656 12.28125 -12.609375 12.65625 -12.171875 C 13.03125 -11.734375 13.328125 -11.253906 13.546875 -10.734375 C 13.773438 -10.210938 13.929688 -9.664062 14.015625 -9.09375 C 14.097656 -8.53125 14.140625 -7.972656 14.140625 -7.421875 L 14.140625 -6.40625 C 14.140625 -5.820312 14.097656 -5.25 14.015625 -4.6875 C 13.929688 -4.125 13.769531 -3.578125 13.53125 -3.046875 C 13.300781 -2.523438 13 -2.046875 12.625 -1.609375 C 12.25 -1.171875 11.816406 -0.804688 11.328125 -0.515625 C 10.835938 -0.234375 10.304688 -0.0351562 9.734375 0.078125 C 9.171875 0.191406 8.601562 0.25 8.03125 0.25 Z M 8.03125 -1.5 C 8.4375 -1.5 8.835938 -1.546875 9.234375 -1.640625 C 9.628906 -1.742188 9.992188 -1.90625 10.328125 -2.125 C 10.671875 -2.351562 10.960938 -2.628906 11.203125 -2.953125 C 11.453125 -3.285156 11.648438 -3.640625 11.796875 -4.015625 C 11.953125 -4.390625 12.054688 -4.78125 12.109375 -5.1875 C 12.160156 -5.59375 12.1875 -6 12.1875 -6.40625 L 12.1875 -7.421875 C 12.1875 -7.828125 12.160156 -8.234375 12.109375 -8.640625 C 12.054688 -9.046875 11.953125 -9.4375 11.796875 -9.8125 C 11.648438 -10.1875 11.453125 -10.535156 11.203125 -10.859375 C 10.960938 -11.191406 10.671875 -11.46875 10.328125 -11.6875 C 9.992188 -11.914062 9.625 -12.078125 9.21875 -12.171875 C 8.8125 -12.265625 8.40625 -12.3125 8 -12.3125 C 7.59375 -12.3125 7.191406 -12.265625 6.796875 -12.171875 C 6.398438 -12.078125 6.03125 -11.914062 5.6875 -11.6875 C 5.351562 -11.46875 5.054688 -11.191406 4.796875 -10.859375 C 4.546875 -10.535156 4.34375 -10.1875 4.1875 -9.8125 C 4.039062 -9.4375 3.941406 -9.046875 3.890625 -8.640625 C 3.835938 -8.234375 3.8125 -7.828125 3.8125 -7.421875 L 3.8125 -6.90625 C 3.8125 -6.476562 3.835938 -6.046875 3.890625 -5.609375 C 3.941406 -5.171875 4.035156 -4.75 4.171875 -4.34375 C 4.316406 -3.945312 4.503906 -3.5625 4.734375 -3.1875 C 4.972656 -2.8125 5.257812 -2.492188 5.59375 -2.234375 C 5.9375 -1.984375 6.320312 -1.796875 6.75 -1.671875 C 7.175781 -1.554688 7.601562 -1.5 8.03125 -1.5 Z M 8.03125 -1.5 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-23">
-<path style="stroke:none;" d="M 8.09375 0.25 C 7.539062 0.25 6.992188 0.207031 6.453125 0.125 C 5.921875 0.0390625 5.414062 -0.109375 4.9375 -0.328125 C 4.457031 -0.554688 4.015625 -0.859375 3.609375 -1.234375 C 3.210938 -1.609375 2.878906 -2.023438 2.609375 -2.484375 C 2.335938 -2.941406 2.132812 -3.4375 2 -3.96875 C 1.863281 -4.5 1.796875 -5.03125 1.796875 -5.5625 C 1.796875 -5.5625 1.796875 -5.566406 1.796875 -5.578125 C 1.796875 -5.585938 1.796875 -5.59375 1.796875 -5.59375 L 3.75 -5.59375 C 3.75 -5.59375 3.75 -5.582031 3.75 -5.5625 C 3.75 -5.007812 3.847656 -4.46875 4.046875 -3.9375 C 4.253906 -3.40625 4.5625 -2.945312 4.96875 -2.5625 C 5.382812 -2.175781 5.863281 -1.898438 6.40625 -1.734375 C 6.957031 -1.578125 7.519531 -1.5 8.09375 -1.5 C 8.6875 -1.5 9.269531 -1.597656 9.84375 -1.796875 C 10.425781 -2.003906 10.914062 -2.332031 11.3125 -2.78125 C 11.707031 -3.226562 11.992188 -3.742188 12.171875 -4.328125 C 12.359375 -4.921875 12.453125 -5.515625 12.453125 -6.109375 C 12.453125 -6.640625 12.375 -7.160156 12.21875 -7.671875 C 12.070312 -8.191406 11.84375 -8.671875 11.53125 -9.109375 C 11.226562 -9.546875 10.859375 -9.914062 10.421875 -10.21875 C 9.984375 -10.53125 9.507812 -10.769531 9 -10.9375 C 8.5 -11.113281 7.984375 -11.21875 7.453125 -11.25 C 6.921875 -11.28125 6.390625 -11.296875 5.859375 -11.296875 L 5.859375 -13.09375 C 6.347656 -13.09375 6.835938 -13.109375 7.328125 -13.140625 C 7.816406 -13.171875 8.296875 -13.257812 8.765625 -13.40625 C 9.234375 -13.550781 9.664062 -13.769531 10.0625 -14.0625 C 10.457031 -14.351562 10.796875 -14.695312 11.078125 -15.09375 C 11.367188 -15.488281 11.582031 -15.925781 11.71875 -16.40625 C 11.863281 -16.882812 11.9375 -17.367188 11.9375 -17.859375 C 11.9375 -18.390625 11.851562 -18.914062 11.6875 -19.4375 C 11.53125 -19.957031 11.28125 -20.421875 10.9375 -20.828125 C 10.601562 -21.234375 10.175781 -21.53125 9.65625 -21.71875 C 9.144531 -21.914062 8.625 -22.015625 8.09375 -22.015625 C 7.582031 -22.015625 7.078125 -21.925781 6.578125 -21.75 C 6.078125 -21.582031 5.648438 -21.3125 5.296875 -20.9375 C 4.941406 -20.570312 4.675781 -20.132812 4.5 -19.625 C 4.332031 -19.125 4.25 -18.617188 4.25 -18.109375 C 4.25 -18.085938 4.25 -18.070312 4.25 -18.0625 C 4.25 -18.050781 4.25 -18.035156 4.25 -18.015625 L 2.296875 -18.015625 C 2.296875 -18.035156 2.296875 -18.054688 2.296875 -18.078125 C 2.296875 -18.097656 2.296875 -18.117188 2.296875 -18.140625 C 2.296875 -18.648438 2.351562 -19.15625 2.46875 -19.65625 C 2.59375 -20.164062 2.769531 -20.644531 3 -21.09375 C 3.238281 -21.539062 3.546875 -21.9375 3.921875 -22.28125 C 4.296875 -22.632812 4.707031 -22.925781 5.15625 -23.15625 C 5.601562 -23.394531 6.078125 -23.554688 6.578125 -23.640625 C 7.078125 -23.734375 7.582031 -23.78125 8.09375 -23.78125 C 8.601562 -23.78125 9.113281 -23.726562 9.625 -23.625 C 10.144531 -23.53125 10.632812 -23.359375 11.09375 -23.109375 C 11.550781 -22.867188 11.957031 -22.554688 12.3125 -22.171875 C 12.675781 -21.785156 12.96875 -21.359375 13.1875 -20.890625 C 13.414062 -20.421875 13.585938 -19.929688 13.703125 -19.421875 C 13.828125 -18.910156 13.890625 -18.398438 13.890625 -17.890625 C 13.890625 -17.265625 13.800781 -16.65625 13.625 -16.0625 C 13.457031 -15.46875 13.195312 -14.914062 12.84375 -14.40625 C 12.488281 -13.90625 12.0625 -13.46875 11.5625 -13.09375 C 11.0625 -12.726562 10.523438 -12.4375 9.953125 -12.21875 C 10.585938 -12.007812 11.1875 -11.707031 11.75 -11.3125 C 12.320312 -10.914062 12.804688 -10.441406 13.203125 -9.890625 C 13.597656 -9.335938 13.894531 -8.734375 14.09375 -8.078125 C 14.300781 -7.429688 14.40625 -6.765625 14.40625 -6.078125 C 14.40625 -5.523438 14.335938 -4.96875 14.203125 -4.40625 C 14.078125 -3.851562 13.890625 -3.328125 13.640625 -2.828125 C 13.398438 -2.328125 13.082031 -1.867188 12.6875 -1.453125 C 12.289062 -1.035156 11.84375 -0.695312 11.34375 -0.4375 C 10.84375 -0.1875 10.3125 -0.0078125 9.75 0.09375 C 9.195312 0.195312 8.644531 0.25 8.09375 0.25 Z M 8.09375 0.25 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-24">
-<path style="stroke:none;" d="M 8 -12.953125 C 7.71875 -12.953125 7.4375 -12.992188 7.15625 -13.078125 C 6.882812 -13.171875 6.644531 -13.316406 6.4375 -13.515625 C 6.238281 -13.722656 6.097656 -13.960938 6.015625 -14.234375 C 5.929688 -14.515625 5.890625 -14.804688 5.890625 -15.109375 C 5.890625 -15.378906 5.929688 -15.65625 6.015625 -15.9375 C 6.097656 -16.21875 6.238281 -16.457031 6.4375 -16.65625 C 6.644531 -16.851562 6.882812 -16.992188 7.15625 -17.078125 C 7.4375 -17.171875 7.71875 -17.21875 8 -17.21875 C 8.28125 -17.21875 8.554688 -17.171875 8.828125 -17.078125 C 9.109375 -16.992188 9.347656 -16.851562 9.546875 -16.65625 C 9.753906 -16.457031 9.898438 -16.21875 9.984375 -15.9375 C 10.066406 -15.65625 10.109375 -15.378906 10.109375 -15.109375 C 10.109375 -14.804688 10.066406 -14.515625 9.984375 -14.234375 C 9.898438 -13.960938 9.753906 -13.722656 9.546875 -13.515625 C 9.347656 -13.316406 9.109375 -13.171875 8.828125 -13.078125 C 8.554688 -12.992188 8.28125 -12.953125 8 -12.953125 Z M 8 0.25 C 7.71875 0.25 7.4375 0.207031 7.15625 0.125 C 6.882812 0.0390625 6.644531 -0.0976562 6.4375 -0.296875 C 6.238281 -0.503906 6.097656 -0.742188 6.015625 -1.015625 C 5.929688 -1.296875 5.890625 -1.578125 5.890625 -1.859375 C 5.890625 -2.148438 5.929688 -2.4375 6.015625 -2.71875 C 6.097656 -3 6.238281 -3.238281 6.4375 -3.4375 C 6.644531 -3.644531 6.882812 -3.789062 7.15625 -3.875 C 7.4375 -3.957031 7.71875 -4 8 -4 C 8.28125 -4 8.554688 -3.957031 8.828125 -3.875 C 9.109375 -3.789062 9.347656 -3.644531 9.546875 -3.4375 C 9.753906 -3.238281 9.898438 -3 9.984375 -2.71875 C 10.066406 -2.4375 10.109375 -2.148438 10.109375 -1.859375 C 10.109375 -1.578125 10.066406 -1.296875 9.984375 -1.015625 C 9.898438 -0.742188 9.753906 -0.503906 9.546875 -0.296875 C 9.347656 -0.0976562 9.109375 0.0390625 8.828125 0.125 C 8.554688 0.207031 8.28125 0.25 8 0.25 Z M 8 0.25 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-25">
-<path style="stroke:none;" d="M 2.109375 0 L 2.109375 -23.515625 L 6.078125 -23.515625 C 6.609375 -23.515625 7.140625 -23.472656 7.671875 -23.390625 C 8.210938 -23.304688 8.722656 -23.144531 9.203125 -22.90625 C 9.679688 -22.675781 10.128906 -22.390625 10.546875 -22.046875 C 10.960938 -21.703125 11.332031 -21.316406 11.65625 -20.890625 C 11.988281 -20.460938 12.28125 -20.015625 12.53125 -19.546875 C 12.789062 -19.078125 13.007812 -18.59375 13.1875 -18.09375 C 13.375 -17.59375 13.523438 -17.082031 13.640625 -16.5625 C 13.765625 -16.039062 13.863281 -15.507812 13.9375 -14.96875 C 14.007812 -14.4375 14.0625 -13.90625 14.09375 -13.375 C 14.125 -12.84375 14.140625 -12.3125 14.140625 -11.78125 C 14.140625 -11.21875 14.125 -10.671875 14.09375 -10.140625 C 14.0625 -9.609375 14.007812 -9.078125 13.9375 -8.546875 C 13.863281 -8.015625 13.765625 -7.484375 13.640625 -6.953125 C 13.523438 -6.429688 13.375 -5.921875 13.1875 -5.421875 C 13.007812 -4.921875 12.789062 -4.4375 12.53125 -3.96875 C 12.28125 -3.5 11.988281 -3.050781 11.65625 -2.625 C 11.332031 -2.195312 10.960938 -1.8125 10.546875 -1.46875 C 10.128906 -1.125 9.679688 -0.832031 9.203125 -0.59375 C 8.722656 -0.363281 8.210938 -0.207031 7.671875 -0.125 C 7.140625 -0.0390625 6.609375 0 6.078125 0 Z M 4.0625 -1.765625 L 6.078125 -1.765625 C 6.503906 -1.765625 6.9375 -1.8125 7.375 -1.90625 C 7.8125 -2 8.21875 -2.15625 8.59375 -2.375 C 8.96875 -2.601562 9.304688 -2.867188 9.609375 -3.171875 C 9.921875 -3.484375 10.195312 -3.820312 10.4375 -4.1875 C 10.6875 -4.550781 10.898438 -4.9375 11.078125 -5.34375 C 11.265625 -5.75 11.421875 -6.160156 11.546875 -6.578125 C 11.679688 -6.992188 11.785156 -7.414062 11.859375 -7.84375 C 11.929688 -8.269531 11.992188 -8.703125 12.046875 -9.140625 C 12.097656 -9.578125 12.132812 -10.015625 12.15625 -10.453125 C 12.175781 -10.890625 12.1875 -11.332031 12.1875 -11.78125 C 12.1875 -12.207031 12.175781 -12.640625 12.15625 -13.078125 C 12.132812 -13.515625 12.097656 -13.953125 12.046875 -14.390625 C 11.992188 -14.828125 11.929688 -15.253906 11.859375 -15.671875 C 11.785156 -16.097656 11.679688 -16.519531 11.546875 -16.9375 C 11.421875 -17.351562 11.265625 -17.765625 11.078125 -18.171875 C 10.898438 -18.578125 10.6875 -18.960938 10.4375 -19.328125 C 10.195312 -19.691406 9.921875 -20.023438 9.609375 -20.328125 C 9.304688 -20.640625 8.96875 -20.90625 8.59375 -21.125 C 8.21875 -21.351562 7.8125 -21.515625 7.375 -21.609375 C 6.9375 -21.710938 6.503906 -21.765625 6.078125 -21.765625 L 4.0625 -21.765625 Z M 4.0625 -1.765625 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-26">
-<path style="stroke:none;" d="M 2.109375 0 L 2.109375 -23.515625 L 7.421875 -23.515625 C 7.972656 -23.515625 8.515625 -23.46875 9.046875 -23.375 C 9.585938 -23.28125 10.101562 -23.113281 10.59375 -22.875 C 11.082031 -22.644531 11.523438 -22.332031 11.921875 -21.9375 C 12.316406 -21.539062 12.640625 -21.101562 12.890625 -20.625 C 13.148438 -20.144531 13.328125 -19.628906 13.421875 -19.078125 C 13.515625 -18.535156 13.5625 -17.988281 13.5625 -17.4375 C 13.5625 -16.90625 13.507812 -16.375 13.40625 -15.84375 C 13.300781 -15.3125 13.113281 -14.804688 12.84375 -14.328125 C 12.582031 -13.859375 12.25 -13.445312 11.84375 -13.09375 C 11.4375 -12.75 10.976562 -12.46875 10.46875 -12.25 C 11.0625 -12.039062 11.617188 -11.753906 12.140625 -11.390625 C 12.660156 -11.023438 13.09375 -10.582031 13.4375 -10.0625 C 13.78125 -9.539062 14.023438 -8.96875 14.171875 -8.34375 C 14.328125 -7.726562 14.40625 -7.113281 14.40625 -6.5 C 14.40625 -5.894531 14.335938 -5.296875 14.203125 -4.703125 C 14.078125 -4.109375 13.867188 -3.546875 13.578125 -3.015625 C 13.296875 -2.492188 12.925781 -2.03125 12.46875 -1.625 C 12.007812 -1.226562 11.5 -0.90625 10.9375 -0.65625 C 10.382812 -0.40625 9.804688 -0.234375 9.203125 -0.140625 C 8.609375 -0.046875 8.015625 0 7.421875 0 Z M 4.0625 -13.09375 L 7.421875 -13.09375 C 7.804688 -13.09375 8.1875 -13.125 8.5625 -13.1875 C 8.9375 -13.25 9.289062 -13.367188 9.625 -13.546875 C 9.96875 -13.734375 10.269531 -13.960938 10.53125 -14.234375 C 10.800781 -14.515625 11.015625 -14.832031 11.171875 -15.1875 C 11.335938 -15.539062 11.453125 -15.898438 11.515625 -16.265625 C 11.578125 -16.640625 11.609375 -17.019531 11.609375 -17.40625 C 11.609375 -17.789062 11.578125 -18.175781 11.515625 -18.5625 C 11.453125 -18.945312 11.335938 -19.304688 11.171875 -19.640625 C 11.015625 -19.984375 10.800781 -20.300781 10.53125 -20.59375 C 10.269531 -20.882812 9.96875 -21.113281 9.625 -21.28125 C 9.289062 -21.445312 8.9375 -21.566406 8.5625 -21.640625 C 8.1875 -21.722656 7.804688 -21.765625 7.421875 -21.765625 L 4.0625 -21.765625 Z M 7.421875 -1.765625 C 7.867188 -1.765625 8.304688 -1.800781 8.734375 -1.875 C 9.160156 -1.945312 9.578125 -2.070312 9.984375 -2.25 C 10.390625 -2.4375 10.75 -2.675781 11.0625 -2.96875 C 11.382812 -3.269531 11.648438 -3.613281 11.859375 -4 C 12.078125 -4.382812 12.226562 -4.796875 12.3125 -5.234375 C 12.40625 -5.671875 12.453125 -6.101562 12.453125 -6.53125 C 12.453125 -6.976562 12.40625 -7.421875 12.3125 -7.859375 C 12.226562 -8.296875 12.078125 -8.703125 11.859375 -9.078125 C 11.648438 -9.460938 11.382812 -9.804688 11.0625 -10.109375 C 10.75 -10.410156 10.390625 -10.648438 9.984375 -10.828125 C 9.578125 -11.015625 9.160156 -11.140625 8.734375 -11.203125 C 8.304688 -11.265625 7.867188 -11.296875 7.421875 -11.296875 L 4.0625 -11.296875 L 4.0625 -1.765625 Z M 7.421875 -1.765625 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-27">
-<path style="stroke:none;" d="M 7.8125 0.25 C 7.25 0.25 6.695312 0.1875 6.15625 0.0625 C 5.613281 -0.0625 5.101562 -0.257812 4.625 -0.53125 C 4.144531 -0.8125 3.71875 -1.164062 3.34375 -1.59375 C 2.96875 -2.019531 2.664062 -2.488281 2.4375 -3 C 2.21875 -3.519531 2.066406 -4.050781 1.984375 -4.59375 C 1.898438 -5.132812 1.859375 -5.679688 1.859375 -6.234375 L 1.859375 -17.28125 C 1.859375 -17.832031 1.898438 -18.390625 1.984375 -18.953125 C 2.066406 -19.523438 2.222656 -20.066406 2.453125 -20.578125 C 2.691406 -21.085938 3 -21.554688 3.375 -21.984375 C 3.75 -22.410156 4.179688 -22.757812 4.671875 -23.03125 C 5.160156 -23.3125 5.679688 -23.503906 6.234375 -23.609375 C 6.796875 -23.722656 7.351562 -23.78125 7.90625 -23.78125 C 8.4375 -23.78125 8.957031 -23.734375 9.46875 -23.640625 C 9.976562 -23.554688 10.46875 -23.398438 10.9375 -23.171875 C 11.40625 -22.953125 11.835938 -22.65625 12.234375 -22.28125 C 12.628906 -21.914062 12.945312 -21.507812 13.1875 -21.0625 C 13.4375 -20.613281 13.625 -20.125 13.75 -19.59375 C 13.882812 -19.070312 13.953125 -18.554688 13.953125 -18.046875 C 13.953125 -18.023438 13.953125 -18.007812 13.953125 -18 C 13.953125 -17.988281 13.953125 -17.972656 13.953125 -17.953125 L 12 -17.953125 C 12 -17.972656 12 -17.988281 12 -18 C 12 -18.007812 12 -18.015625 12 -18.015625 C 12 -18.546875 11.90625 -19.070312 11.71875 -19.59375 C 11.539062 -20.125 11.257812 -20.578125 10.875 -20.953125 C 10.488281 -21.335938 10.03125 -21.609375 9.5 -21.765625 C 8.96875 -21.929688 8.4375 -22.015625 7.90625 -22.015625 C 7.519531 -22.015625 7.128906 -21.972656 6.734375 -21.890625 C 6.335938 -21.804688 5.972656 -21.65625 5.640625 -21.4375 C 5.316406 -21.226562 5.03125 -20.960938 4.78125 -20.640625 C 4.539062 -20.316406 4.34375 -19.96875 4.1875 -19.59375 C 4.039062 -19.226562 3.941406 -18.851562 3.890625 -18.46875 C 3.835938 -18.082031 3.8125 -17.6875 3.8125 -17.28125 L 3.8125 -6.234375 C 3.8125 -5.847656 3.835938 -5.460938 3.890625 -5.078125 C 3.941406 -4.703125 4.035156 -4.332031 4.171875 -3.96875 C 4.316406 -3.601562 4.503906 -3.257812 4.734375 -2.9375 C 4.972656 -2.625 5.253906 -2.359375 5.578125 -2.140625 C 5.910156 -1.929688 6.265625 -1.769531 6.640625 -1.65625 C 7.015625 -1.550781 7.40625 -1.5 7.8125 -1.5 C 8.195312 -1.5 8.582031 -1.550781 8.96875 -1.65625 C 9.363281 -1.769531 9.734375 -1.925781 10.078125 -2.125 C 10.421875 -2.332031 10.71875 -2.585938 10.96875 -2.890625 C 11.226562 -3.203125 11.429688 -3.539062 11.578125 -3.90625 C 11.734375 -4.269531 11.832031 -4.644531 11.875 -5.03125 C 11.914062 -5.425781 11.9375 -5.828125 11.9375 -6.234375 L 11.9375 -10.90625 L 8 -10.90625 L 8 -12.671875 L 13.890625 -12.671875 L 13.890625 -6.234375 C 13.890625 -5.679688 13.859375 -5.117188 13.796875 -4.546875 C 13.734375 -3.972656 13.582031 -3.425781 13.34375 -2.90625 C 13.101562 -2.394531 12.785156 -1.9375 12.390625 -1.53125 C 12.003906 -1.125 11.554688 -0.785156 11.046875 -0.515625 C 10.546875 -0.253906 10.019531 -0.0625 9.46875 0.0625 C 8.914062 0.1875 8.363281 0.25 7.8125 0.25 Z M 7.8125 0.25 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-28">
-<path style="stroke:none;" d="M 2.109375 0 L 2.109375 -23.515625 L 8.21875 -23.515625 C 8.78125 -23.515625 9.34375 -23.453125 9.90625 -23.328125 C 10.46875 -23.203125 10.988281 -22.988281 11.46875 -22.6875 C 11.945312 -22.382812 12.363281 -22.007812 12.71875 -21.5625 C 13.070312 -21.113281 13.351562 -20.628906 13.5625 -20.109375 C 13.78125 -19.585938 13.929688 -19.039062 14.015625 -18.46875 C 14.097656 -17.90625 14.140625 -17.335938 14.140625 -16.765625 C 14.140625 -16.066406 14.070312 -15.351562 13.9375 -14.625 C 13.800781 -13.894531 13.546875 -13.222656 13.171875 -12.609375 C 12.796875 -11.992188 12.304688 -11.472656 11.703125 -11.046875 C 11.109375 -10.617188 10.460938 -10.328125 9.765625 -10.171875 C 10.296875 -9.410156 10.8125 -8.632812 11.3125 -7.84375 C 11.8125 -7.050781 12.265625 -6.234375 12.671875 -5.390625 C 13.078125 -4.546875 13.421875 -3.671875 13.703125 -2.765625 C 13.992188 -1.859375 14.140625 -0.9375 14.140625 0 L 12.1875 0 C 12.1875 -0.613281 12.113281 -1.226562 11.96875 -1.84375 C 11.820312 -2.46875 11.628906 -3.066406 11.390625 -3.640625 C 11.160156 -4.222656 10.898438 -4.789062 10.609375 -5.34375 C 10.316406 -5.894531 10.003906 -6.4375 9.671875 -6.96875 C 9.347656 -7.507812 9.003906 -8.035156 8.640625 -8.546875 C 8.273438 -9.054688 7.890625 -9.554688 7.484375 -10.046875 L 4.0625 -10.046875 L 4.0625 0 Z M 4.0625 -11.8125 L 8.21875 -11.8125 C 8.601562 -11.8125 8.992188 -11.863281 9.390625 -11.96875 C 9.785156 -12.070312 10.144531 -12.238281 10.46875 -12.46875 C 10.800781 -12.707031 11.078125 -12.992188 11.296875 -13.328125 C 11.523438 -13.660156 11.703125 -14.015625 11.828125 -14.390625 C 11.960938 -14.773438 12.054688 -15.171875 12.109375 -15.578125 C 12.160156 -15.984375 12.1875 -16.378906 12.1875 -16.765625 C 12.1875 -17.171875 12.160156 -17.578125 12.109375 -17.984375 C 12.054688 -18.390625 11.960938 -18.78125 11.828125 -19.15625 C 11.703125 -19.53125 11.523438 -19.882812 11.296875 -20.21875 C 11.078125 -20.5625 10.800781 -20.847656 10.46875 -21.078125 C 10.144531 -21.316406 9.785156 -21.488281 9.390625 -21.59375 C 8.992188 -21.707031 8.601562 -21.765625 8.21875 -21.765625 L 4.0625 -21.765625 Z M 4.0625 -11.8125 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-29">
-<path style="stroke:none;" d="M 6.65625 0.25 C 5.988281 0.25 5.34375 0.15625 4.71875 -0.03125 C 4.09375 -0.21875 3.5625 -0.539062 3.125 -1 C 2.6875 -1.46875 2.363281 -2.019531 2.15625 -2.65625 C 1.957031 -3.289062 1.859375 -3.929688 1.859375 -4.578125 C 1.859375 -5.109375 1.925781 -5.632812 2.0625 -6.15625 C 2.195312 -6.675781 2.429688 -7.148438 2.765625 -7.578125 C 3.097656 -8.003906 3.5 -8.359375 3.96875 -8.640625 C 4.4375 -8.921875 4.921875 -9.144531 5.421875 -9.3125 C 5.921875 -9.476562 6.4375 -9.59375 6.96875 -9.65625 C 7.507812 -9.726562 8.046875 -9.765625 8.578125 -9.765625 L 11.9375 -9.765625 L 11.9375 -11.578125 C 11.9375 -11.921875 11.90625 -12.269531 11.84375 -12.625 C 11.78125 -12.976562 11.664062 -13.304688 11.5 -13.609375 C 11.34375 -13.921875 11.128906 -14.195312 10.859375 -14.4375 C 10.597656 -14.6875 10.3125 -14.882812 10 -15.03125 C 9.6875 -15.1875 9.351562 -15.296875 9 -15.359375 C 8.65625 -15.421875 8.300781 -15.453125 7.9375 -15.453125 C 7.46875 -15.453125 7.003906 -15.40625 6.546875 -15.3125 C 6.085938 -15.21875 5.660156 -15.035156 5.265625 -14.765625 C 4.867188 -14.503906 4.554688 -14.160156 4.328125 -13.734375 C 4.109375 -13.304688 4 -12.859375 4 -12.390625 L 2.046875 -12.390625 C 2.046875 -12.859375 2.117188 -13.320312 2.265625 -13.78125 C 2.421875 -14.238281 2.628906 -14.660156 2.890625 -15.046875 C 3.160156 -15.441406 3.492188 -15.785156 3.890625 -16.078125 C 4.285156 -16.367188 4.703125 -16.597656 5.140625 -16.765625 C 5.578125 -16.941406 6.035156 -17.0625 6.515625 -17.125 C 6.992188 -17.1875 7.46875 -17.21875 7.9375 -17.21875 C 8.46875 -17.21875 8.988281 -17.171875 9.5 -17.078125 C 10.007812 -16.992188 10.5 -16.84375 10.96875 -16.625 C 11.445312 -16.414062 11.875 -16.132812 12.25 -15.78125 C 12.632812 -15.4375 12.945312 -15.035156 13.1875 -14.578125 C 13.4375 -14.117188 13.613281 -13.632812 13.71875 -13.125 C 13.832031 -12.613281 13.890625 -12.097656 13.890625 -11.578125 L 13.890625 0 L 11.9375 0 L 11.9375 -2.90625 C 11.71875 -2.394531 11.425781 -1.929688 11.0625 -1.515625 C 10.707031 -1.097656 10.289062 -0.753906 9.8125 -0.484375 C 9.332031 -0.222656 8.816406 -0.0351562 8.265625 0.078125 C 7.722656 0.191406 7.1875 0.25 6.65625 0.25 Z M 7.359375 -1.5 C 7.929688 -1.5 8.507812 -1.578125 9.09375 -1.734375 C 9.6875 -1.898438 10.207031 -2.179688 10.65625 -2.578125 C 11.101562 -2.972656 11.425781 -3.453125 11.625 -4.015625 C 11.832031 -4.578125 11.9375 -5.160156 11.9375 -5.765625 L 11.9375 -7.96875 L 8.578125 -7.96875 C 8.210938 -7.96875 7.851562 -7.953125 7.5 -7.921875 C 7.144531 -7.890625 6.789062 -7.828125 6.4375 -7.734375 C 6.09375 -7.648438 5.753906 -7.53125 5.421875 -7.375 C 5.085938 -7.21875 4.789062 -7.007812 4.53125 -6.75 C 4.28125 -6.5 4.097656 -6.195312 3.984375 -5.84375 C 3.867188 -5.488281 3.8125 -5.128906 3.8125 -4.765625 C 3.8125 -4.296875 3.890625 -3.84375 4.046875 -3.40625 C 4.203125 -2.96875 4.453125 -2.597656 4.796875 -2.296875 C 5.140625 -2.003906 5.535156 -1.796875 5.984375 -1.671875 C 6.429688 -1.554688 6.890625 -1.5 7.359375 -1.5 Z M 7.359375 -1.5 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-30">
-<path style="stroke:none;" d="M 8.734375 0.25 C 8.222656 0.25 7.71875 0.1875 7.21875 0.0625 C 6.71875 -0.0625 6.257812 -0.28125 5.84375 -0.59375 C 5.425781 -0.90625 5.066406 -1.273438 4.765625 -1.703125 C 4.460938 -2.128906 4.226562 -2.585938 4.0625 -3.078125 L 4.0625 0 L 2.109375 0 L 2.109375 -23.515625 L 4.0625 -23.515625 L 4.0625 -13.890625 C 4.226562 -14.378906 4.460938 -14.835938 4.765625 -15.265625 C 5.066406 -15.691406 5.425781 -16.054688 5.84375 -16.359375 C 6.257812 -16.671875 6.71875 -16.890625 7.21875 -17.015625 C 7.71875 -17.148438 8.222656 -17.21875 8.734375 -17.21875 C 9.285156 -17.21875 9.820312 -17.144531 10.34375 -17 C 10.875 -16.863281 11.359375 -16.632812 11.796875 -16.3125 C 12.234375 -16 12.601562 -15.617188 12.90625 -15.171875 C 13.21875 -14.722656 13.460938 -14.242188 13.640625 -13.734375 C 13.828125 -13.234375 13.957031 -12.710938 14.03125 -12.171875 C 14.101562 -11.628906 14.140625 -11.09375 14.140625 -10.5625 L 14.140625 -6.40625 C 14.140625 -5.863281 14.101562 -5.320312 14.03125 -4.78125 C 13.957031 -4.238281 13.828125 -3.71875 13.640625 -3.21875 C 13.460938 -2.71875 13.21875 -2.242188 12.90625 -1.796875 C 12.601562 -1.347656 12.234375 -0.960938 11.796875 -0.640625 C 11.359375 -0.316406 10.875 -0.0859375 10.34375 0.046875 C 9.820312 0.179688 9.285156 0.25 8.734375 0.25 Z M 8.03125 -1.5 C 8.4375 -1.5 8.835938 -1.546875 9.234375 -1.640625 C 9.628906 -1.742188 9.992188 -1.90625 10.328125 -2.125 C 10.671875 -2.351562 10.960938 -2.628906 11.203125 -2.953125 C 11.453125 -3.285156 11.648438 -3.640625 11.796875 -4.015625 C 11.953125 -4.390625 12.054688 -4.78125 12.109375 -5.1875 C 12.160156 -5.59375 12.1875 -6 12.1875 -6.40625 L 12.1875 -10.5625 C 12.1875 -10.96875 12.160156 -11.375 12.109375 -11.78125 C 12.054688 -12.1875 11.953125 -12.570312 11.796875 -12.9375 C 11.648438 -13.3125 11.453125 -13.664062 11.203125 -14 C 10.960938 -14.332031 10.671875 -14.609375 10.328125 -14.828125 C 9.992188 -15.054688 9.628906 -15.21875 9.234375 -15.3125 C 8.835938 -15.40625 8.4375 -15.453125 8.03125 -15.453125 C 7.625 -15.453125 7.226562 -15.40625 6.84375 -15.3125 C 6.457031 -15.21875 6.097656 -15.050781 5.765625 -14.8125 C 5.441406 -14.582031 5.164062 -14.300781 4.9375 -13.96875 C 4.71875 -13.632812 4.539062 -13.28125 4.40625 -12.90625 C 4.28125 -12.539062 4.191406 -12.160156 4.140625 -11.765625 C 4.085938 -11.367188 4.0625 -10.96875 4.0625 -10.5625 L 4.0625 -6.40625 C 4.0625 -6 4.085938 -5.597656 4.140625 -5.203125 C 4.191406 -4.804688 4.28125 -4.421875 4.40625 -4.046875 C 4.539062 -3.671875 4.71875 -3.316406 4.9375 -2.984375 C 5.164062 -2.660156 5.441406 -2.378906 5.765625 -2.140625 C 6.097656 -1.910156 6.457031 -1.742188 6.84375 -1.640625 C 7.226562 -1.546875 7.625 -1.5 8.03125 -1.5 Z M 8.03125 -1.5 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-31">
-<path style="stroke:none;" d="M 7.078125 -14.4375 C 7.035156 -16.132812 7 -17.835938 6.96875 -19.546875 C 6.945312 -21.253906 6.9375 -22.972656 6.9375 -24.703125 L 9.0625 -24.703125 C 9.0625 -22.972656 9.046875 -21.253906 9.015625 -19.546875 C 8.992188 -17.835938 8.960938 -16.132812 8.921875 -14.4375 Z M 7.078125 -14.4375 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-32">
-<path style="stroke:none;" d="M 10.296875 -6.140625 C 10.128906 -6.140625 9.96875 -6.160156 9.8125 -6.203125 C 9.664062 -6.242188 9.523438 -6.300781 9.390625 -6.375 C 9.253906 -6.457031 9.125 -6.546875 9 -6.640625 C 8.882812 -6.734375 8.773438 -6.835938 8.671875 -6.953125 C 8.566406 -7.078125 8.460938 -7.195312 8.359375 -7.3125 C 8.265625 -7.425781 8.175781 -7.550781 8.09375 -7.6875 C 8.007812 -7.832031 7.925781 -7.96875 7.84375 -8.09375 C 7.757812 -8.21875 7.675781 -8.351562 7.59375 -8.5 C 7.519531 -8.65625 7.453125 -8.800781 7.390625 -8.9375 C 7.328125 -9.082031 7.257812 -9.222656 7.1875 -9.359375 C 7.113281 -9.492188 7.03125 -9.640625 6.9375 -9.796875 C 6.851562 -9.960938 6.773438 -10.117188 6.703125 -10.265625 C 6.628906 -10.421875 6.554688 -10.566406 6.484375 -10.703125 C 6.410156 -10.835938 6.332031 -10.957031 6.25 -11.0625 C 6.175781 -11.175781 6.082031 -11.300781 5.96875 -11.4375 C 5.851562 -11.570312 5.765625 -11.675781 5.703125 -11.75 C 5.628906 -11.726562 5.550781 -11.671875 5.46875 -11.578125 C 5.382812 -11.492188 5.304688 -11.398438 5.234375 -11.296875 C 5.160156 -11.191406 5.082031 -11.078125 5 -10.953125 C 4.925781 -10.835938 4.867188 -10.742188 4.828125 -10.671875 C 4.785156 -10.597656 4.742188 -10.515625 4.703125 -10.421875 C 4.660156 -10.335938 4.617188 -10.253906 4.578125 -10.171875 C 4.535156 -10.085938 4.492188 -10 4.453125 -9.90625 C 4.410156 -9.8125 4.359375 -9.710938 4.296875 -9.609375 C 4.242188 -9.515625 4.195312 -9.414062 4.15625 -9.3125 C 4.113281 -9.207031 4.066406 -9.085938 4.015625 -8.953125 C 3.960938 -8.828125 3.910156 -8.703125 3.859375 -8.578125 C 3.804688 -8.453125 3.75 -8.320312 3.6875 -8.1875 C 3.632812 -8.0625 3.582031 -7.921875 3.53125 -7.765625 C 3.476562 -7.617188 3.425781 -7.472656 3.375 -7.328125 C 3.320312 -7.179688 3.269531 -7.023438 3.21875 -6.859375 C 3.164062 -6.703125 3.109375 -6.539062 3.046875 -6.375 L 1.21875 -6.90625 C 1.300781 -7.207031 1.394531 -7.492188 1.5 -7.765625 C 1.601562 -8.046875 1.695312 -8.3125 1.78125 -8.5625 C 1.875 -8.820312 1.96875 -9.0625 2.0625 -9.28125 C 2.15625 -9.507812 2.25 -9.722656 2.34375 -9.921875 C 2.445312 -10.128906 2.539062 -10.328125 2.625 -10.515625 C 2.707031 -10.710938 2.789062 -10.894531 2.875 -11.0625 C 2.96875 -11.238281 3.054688 -11.394531 3.140625 -11.53125 C 3.222656 -11.675781 3.332031 -11.851562 3.46875 -12.0625 C 3.613281 -12.28125 3.753906 -12.46875 3.890625 -12.625 C 4.023438 -12.78125 4.1875 -12.925781 4.375 -13.0625 C 4.570312 -13.207031 4.78125 -13.320312 5 -13.40625 C 5.226562 -13.488281 5.460938 -13.53125 5.703125 -13.53125 C 5.867188 -13.53125 6.023438 -13.507812 6.171875 -13.46875 C 6.328125 -13.425781 6.472656 -13.375 6.609375 -13.3125 C 6.742188 -13.25 6.867188 -13.160156 6.984375 -13.046875 C 7.109375 -12.941406 7.222656 -12.835938 7.328125 -12.734375 C 7.429688 -12.628906 7.53125 -12.507812 7.625 -12.375 C 7.726562 -12.25 7.820312 -12.125 7.90625 -12 C 7.988281 -11.875 8.070312 -11.738281 8.15625 -11.59375 C 8.238281 -11.457031 8.316406 -11.316406 8.390625 -11.171875 C 8.472656 -11.035156 8.546875 -10.894531 8.609375 -10.75 C 8.671875 -10.601562 8.738281 -10.457031 8.8125 -10.3125 C 8.882812 -10.175781 8.960938 -10.023438 9.046875 -9.859375 C 9.140625 -9.703125 9.222656 -9.550781 9.296875 -9.40625 C 9.367188 -9.257812 9.441406 -9.113281 9.515625 -8.96875 C 9.585938 -8.832031 9.660156 -8.710938 9.734375 -8.609375 C 9.816406 -8.503906 9.910156 -8.382812 10.015625 -8.25 C 10.117188 -8.125 10.210938 -8.019531 10.296875 -7.9375 C 10.367188 -7.957031 10.445312 -8.007812 10.53125 -8.09375 C 10.613281 -8.175781 10.691406 -8.269531 10.765625 -8.375 C 10.835938 -8.488281 10.910156 -8.609375 10.984375 -8.734375 C 11.066406 -8.859375 11.128906 -8.953125 11.171875 -9.015625 C 11.210938 -9.085938 11.253906 -9.164062 11.296875 -9.25 C 11.335938 -9.332031 11.378906 -9.414062 11.421875 -9.5 C 11.460938 -9.582031 11.503906 -9.671875 11.546875 -9.765625 C 11.585938 -9.867188 11.632812 -9.96875 11.6875 -10.0625 C 11.75 -10.15625 11.800781 -10.257812 11.84375 -10.375 C 11.882812 -10.5 11.929688 -10.617188 11.984375 -10.734375 C 12.035156 -10.847656 12.085938 -10.96875 12.140625 -11.09375 C 12.191406 -11.226562 12.242188 -11.359375 12.296875 -11.484375 C 12.359375 -11.617188 12.414062 -11.757812 12.46875 -11.90625 C 12.519531 -12.050781 12.570312 -12.195312 12.625 -12.34375 C 12.675781 -12.5 12.726562 -12.660156 12.78125 -12.828125 C 12.832031 -13.003906 12.890625 -13.175781 12.953125 -13.34375 L 14.78125 -12.765625 C 14.695312 -12.460938 14.601562 -12.171875 14.5 -11.890625 C 14.394531 -11.617188 14.296875 -11.359375 14.203125 -11.109375 C 14.117188 -10.867188 14.03125 -10.628906 13.9375 -10.390625 C 13.84375 -10.160156 13.742188 -9.941406 13.640625 -9.734375 C 13.546875 -9.535156 13.457031 -9.335938 13.375 -9.140625 C 13.289062 -8.953125 13.203125 -8.78125 13.109375 -8.625 C 13.023438 -8.46875 12.941406 -8.3125 12.859375 -8.15625 C 12.773438 -8.007812 12.660156 -7.828125 12.515625 -7.609375 C 12.378906 -7.398438 12.242188 -7.210938 12.109375 -7.046875 C 11.972656 -6.890625 11.804688 -6.742188 11.609375 -6.609375 C 11.421875 -6.472656 11.210938 -6.359375 10.984375 -6.265625 C 10.765625 -6.179688 10.535156 -6.140625 10.296875 -6.140625 Z M 10.296875 -6.140625 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-33">
-<path style="stroke:none;" d="M 12 4.578125 L 2.015625 -26.34375 L 4 -26.34375 L 13.984375 4.578125 Z M 12 4.578125 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-34">
-<path style="stroke:none;" d="M 2.109375 0 L 2.109375 -0.859375 C 2.109375 -1.265625 2.164062 -1.671875 2.28125 -2.078125 C 2.40625 -2.484375 2.5625 -2.875 2.75 -3.25 C 2.945312 -3.625 3.15625 -3.976562 3.375 -4.3125 C 3.601562 -4.65625 3.851562 -4.988281 4.125 -5.3125 C 4.125 -5.3125 4.125 -5.316406 4.125 -5.328125 C 4.125 -5.335938 4.125 -5.34375 4.125 -5.34375 C 4.164062 -5.382812 4.195312 -5.421875 4.21875 -5.453125 C 4.238281 -5.484375 4.269531 -5.519531 4.3125 -5.5625 L 6.78125 -8.484375 L 4.3125 -11.390625 C 4.269531 -11.429688 4.238281 -11.46875 4.21875 -11.5 C 4.195312 -11.53125 4.164062 -11.566406 4.125 -11.609375 C 3.875 -11.953125 3.628906 -12.296875 3.390625 -12.640625 C 3.160156 -12.984375 2.945312 -13.335938 2.75 -13.703125 C 2.5625 -14.078125 2.40625 -14.46875 2.28125 -14.875 C 2.164062 -15.28125 2.109375 -15.695312 2.109375 -16.125 L 2.109375 -16.953125 L 4.0625 -16.953125 L 4.0625 -16.09375 C 4.0625 -15.769531 4.113281 -15.441406 4.21875 -15.109375 C 4.332031 -14.785156 4.46875 -14.472656 4.625 -14.171875 C 4.78125 -13.878906 4.957031 -13.597656 5.15625 -13.328125 C 5.363281 -13.054688 5.578125 -12.796875 5.796875 -12.546875 L 8 -9.921875 L 10.203125 -12.546875 C 10.421875 -12.796875 10.628906 -13.054688 10.828125 -13.328125 C 11.035156 -13.597656 11.21875 -13.878906 11.375 -14.171875 C 11.53125 -14.472656 11.660156 -14.785156 11.765625 -15.109375 C 11.878906 -15.441406 11.9375 -15.78125 11.9375 -16.125 L 11.9375 -16.953125 L 13.890625 -16.953125 L 13.890625 -16.125 C 13.890625 -15.695312 13.828125 -15.28125 13.703125 -14.875 C 13.585938 -14.46875 13.429688 -14.078125 13.234375 -13.703125 C 13.046875 -13.335938 12.835938 -12.984375 12.609375 -12.640625 C 12.390625 -12.296875 12.144531 -11.960938 11.875 -11.640625 C 11.875 -11.640625 11.875 -11.632812 11.875 -11.625 C 11.875 -11.613281 11.875 -11.609375 11.875 -11.609375 C 11.832031 -11.566406 11.800781 -11.53125 11.78125 -11.5 C 11.757812 -11.46875 11.726562 -11.429688 11.6875 -11.390625 L 9.21875 -8.484375 L 11.6875 -5.5625 C 11.726562 -5.519531 11.757812 -5.484375 11.78125 -5.453125 C 11.800781 -5.421875 11.832031 -5.382812 11.875 -5.34375 C 12.125 -5 12.363281 -4.65625 12.59375 -4.3125 C 12.832031 -3.976562 13.046875 -3.625 13.234375 -3.25 C 13.429688 -2.875 13.585938 -2.484375 13.703125 -2.078125 C 13.828125 -1.671875 13.890625 -1.265625 13.890625 -0.859375 L 13.890625 0 L 11.9375 0 L 11.9375 -0.859375 C 11.9375 -1.179688 11.878906 -1.507812 11.765625 -1.84375 C 11.660156 -2.175781 11.53125 -2.488281 11.375 -2.78125 C 11.21875 -3.082031 11.035156 -3.363281 10.828125 -3.625 C 10.628906 -3.894531 10.421875 -4.160156 10.203125 -4.421875 L 8 -7.046875 L 5.796875 -4.421875 C 5.578125 -4.160156 5.363281 -3.894531 5.15625 -3.625 C 4.957031 -3.363281 4.78125 -3.082031 4.625 -2.78125 C 4.46875 -2.488281 4.332031 -2.175781 4.21875 -1.84375 C 4.113281 -1.507812 4.0625 -1.179688 4.0625 -0.859375 L 4.0625 0 Z M 2.109375 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-35">
-<path style="stroke:none;" d="M 8.0625 0.25 C 7.289062 0.25 6.539062 0.132812 5.8125 -0.09375 C 5.09375 -0.332031 4.460938 -0.710938 3.921875 -1.234375 C 3.378906 -1.753906 2.957031 -2.363281 2.65625 -3.0625 C 2.351562 -3.769531 2.171875 -4.507812 2.109375 -5.28125 L 2.078125 -5.53125 L 4.03125 -5.53125 L 4.03125 -5.46875 C 4.070312 -4.9375 4.1875 -4.421875 4.375 -3.921875 C 4.570312 -3.421875 4.851562 -2.984375 5.21875 -2.609375 C 5.582031 -2.234375 6.015625 -1.953125 6.515625 -1.765625 C 7.015625 -1.585938 7.53125 -1.5 8.0625 -1.5 C 8.507812 -1.5 8.953125 -1.578125 9.390625 -1.734375 C 9.828125 -1.898438 10.210938 -2.132812 10.546875 -2.4375 C 10.878906 -2.75 11.148438 -3.113281 11.359375 -3.53125 C 11.578125 -3.945312 11.75 -4.375 11.875 -4.8125 C 12 -5.25 12.082031 -5.695312 12.125 -6.15625 C 12.164062 -6.613281 12.1875 -7.066406 12.1875 -7.515625 C 12.1875 -7.984375 12.164062 -8.441406 12.125 -8.890625 C 12.082031 -9.335938 12.003906 -9.78125 11.890625 -10.21875 C 11.773438 -10.65625 11.613281 -11.082031 11.40625 -11.5 C 11.207031 -11.914062 10.941406 -12.28125 10.609375 -12.59375 C 10.273438 -12.90625 9.890625 -13.140625 9.453125 -13.296875 C 9.015625 -13.453125 8.570312 -13.53125 8.125 -13.53125 C 7.632812 -13.53125 7.15625 -13.460938 6.6875 -13.328125 C 6.21875 -13.191406 5.800781 -12.957031 5.4375 -12.625 C 5.070312 -12.289062 4.800781 -11.894531 4.625 -11.4375 C 4.445312 -10.976562 4.332031 -10.503906 4.28125 -10.015625 L 2.375 -10.015625 L 2.375 -23.515625 L 13.28125 -23.515625 L 13.28125 -21.765625 L 4.28125 -21.765625 L 4.28125 -14.109375 C 4.820312 -14.535156 5.425781 -14.835938 6.09375 -15.015625 C 6.769531 -15.203125 7.445312 -15.296875 8.125 -15.296875 C 8.75 -15.296875 9.363281 -15.210938 9.96875 -15.046875 C 10.570312 -14.890625 11.113281 -14.617188 11.59375 -14.234375 C 12.070312 -13.847656 12.484375 -13.390625 12.828125 -12.859375 C 13.171875 -12.328125 13.4375 -11.765625 13.625 -11.171875 C 13.820312 -10.585938 13.957031 -9.988281 14.03125 -9.375 C 14.101562 -8.757812 14.140625 -8.140625 14.140625 -7.515625 C 14.140625 -6.898438 14.101562 -6.28125 14.03125 -5.65625 C 13.957031 -5.039062 13.816406 -4.4375 13.609375 -3.84375 C 13.410156 -3.257812 13.140625 -2.703125 12.796875 -2.171875 C 12.453125 -1.640625 12.035156 -1.1875 11.546875 -0.8125 C 11.054688 -0.4375 10.503906 -0.164062 9.890625 0 C 9.285156 0.164062 8.675781 0.25 8.0625 0.25 Z M 8.0625 0.25 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-36">
-<path style="stroke:none;" d="M 5.765625 0 L 5.765625 -14.109375 L 2.109375 -14.109375 L 2.109375 -15.875 L 5.765625 -15.875 L 5.765625 -18.53125 C 5.765625 -18.957031 5.789062 -19.382812 5.84375 -19.8125 C 5.894531 -20.238281 5.988281 -20.648438 6.125 -21.046875 C 6.269531 -21.453125 6.46875 -21.835938 6.71875 -22.203125 C 6.976562 -22.566406 7.285156 -22.867188 7.640625 -23.109375 C 8.003906 -23.359375 8.394531 -23.53125 8.8125 -23.625 C 9.226562 -23.726562 9.660156 -23.78125 10.109375 -23.78125 C 10.679688 -23.78125 11.25 -23.6875 11.8125 -23.5 C 12.382812 -23.320312 12.875 -23.019531 13.28125 -22.59375 C 13.6875 -22.164062 13.976562 -21.664062 14.15625 -21.09375 C 14.34375 -20.53125 14.4375 -19.960938 14.4375 -19.390625 C 14.4375 -19.347656 14.4375 -19.304688 14.4375 -19.265625 C 14.4375 -19.222656 14.4375 -19.179688 14.4375 -19.140625 L 12.484375 -19.140625 C 12.484375 -19.160156 12.484375 -19.1875 12.484375 -19.21875 C 12.484375 -19.25 12.484375 -19.273438 12.484375 -19.296875 C 12.484375 -19.640625 12.4375 -19.972656 12.34375 -20.296875 C 12.257812 -20.628906 12.109375 -20.929688 11.890625 -21.203125 C 11.679688 -21.484375 11.414062 -21.6875 11.09375 -21.8125 C 10.78125 -21.945312 10.453125 -22.015625 10.109375 -22.015625 C 9.835938 -22.015625 9.570312 -21.972656 9.3125 -21.890625 C 9.050781 -21.804688 8.828125 -21.671875 8.640625 -21.484375 C 8.453125 -21.304688 8.289062 -21.09375 8.15625 -20.84375 C 8.03125 -20.601562 7.9375 -20.351562 7.875 -20.09375 C 7.8125 -19.84375 7.765625 -19.582031 7.734375 -19.3125 C 7.703125 -19.039062 7.6875 -18.78125 7.6875 -18.53125 L 7.6875 -15.875 L 13.28125 -15.875 L 13.28125 -14.109375 L 7.6875 -14.109375 L 7.6875 0 Z M 5.765625 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-37">
-<path style="stroke:none;" d="M 9.734375 0 L 9.734375 -6.40625 L 1.859375 -6.40625 L 1.859375 -8.15625 L 2.90625 -10.375 L 9.25 -23.515625 L 11.6875 -23.515625 L 11.6875 -8.15625 L 13.890625 -8.15625 L 13.890625 -6.40625 L 11.6875 -6.40625 L 11.6875 0 Z M 4 -8.15625 L 9.734375 -8.15625 L 9.734375 -20.125 Z M 4 -8.15625 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-38">
-<path style="stroke:none;" d="M 2.109375 -16.484375 L 2.109375 -23.515625 L 5.03125 -23.515625 L 5.03125 -16.484375 Z M 2.109375 0 L 11.75 -23.515625 L 13.890625 -23.515625 L 4.25 0 Z M 10.96875 0 L 10.96875 -7.078125 L 13.890625 -7.078125 L 13.890625 0 Z M 10.96875 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-39">
-<path style="stroke:none;" d="M 8.09375 0.25 C 7.5625 0.25 7.035156 0.207031 6.515625 0.125 C 5.992188 0.0390625 5.492188 -0.113281 5.015625 -0.34375 C 4.546875 -0.582031 4.117188 -0.890625 3.734375 -1.265625 C 3.359375 -1.640625 3.046875 -2.054688 2.796875 -2.515625 C 2.554688 -2.972656 2.367188 -3.460938 2.234375 -3.984375 C 2.109375 -4.503906 2.046875 -5.03125 2.046875 -5.5625 C 2.046875 -5.5625 2.046875 -5.566406 2.046875 -5.578125 C 2.046875 -5.585938 2.046875 -5.59375 2.046875 -5.59375 L 4 -5.59375 C 4 -5.59375 4 -5.585938 4 -5.578125 C 4 -5.566406 4 -5.5625 4 -5.5625 C 4 -5.03125 4.085938 -4.5 4.265625 -3.96875 C 4.453125 -3.4375 4.738281 -2.976562 5.125 -2.59375 C 5.507812 -2.207031 5.960938 -1.925781 6.484375 -1.75 C 7.003906 -1.582031 7.539062 -1.5 8.09375 -1.5 C 8.5 -1.5 8.898438 -1.546875 9.296875 -1.640625 C 9.691406 -1.742188 10.050781 -1.90625 10.375 -2.125 C 10.707031 -2.351562 10.992188 -2.628906 11.234375 -2.953125 C 11.484375 -3.285156 11.675781 -3.644531 11.8125 -4.03125 C 11.957031 -4.414062 12.054688 -4.804688 12.109375 -5.203125 C 12.160156 -5.597656 12.1875 -6 12.1875 -6.40625 L 12.1875 -11.578125 C 11.957031 -11.222656 11.6875 -10.90625 11.375 -10.625 C 11.0625 -10.34375 10.71875 -10.109375 10.34375 -9.921875 C 9.976562 -9.742188 9.585938 -9.617188 9.171875 -9.546875 C 8.753906 -9.472656 8.332031 -9.4375 7.90625 -9.4375 C 7.351562 -9.4375 6.789062 -9.5 6.21875 -9.625 C 5.65625 -9.757812 5.128906 -9.96875 4.640625 -10.25 C 4.148438 -10.539062 3.71875 -10.90625 3.34375 -11.34375 C 2.96875 -11.78125 2.664062 -12.257812 2.4375 -12.78125 C 2.21875 -13.300781 2.066406 -13.84375 1.984375 -14.40625 C 1.898438 -14.976562 1.859375 -15.539062 1.859375 -16.09375 L 1.859375 -17.125 C 1.859375 -17.695312 1.898438 -18.265625 1.984375 -18.828125 C 2.066406 -19.398438 2.222656 -19.945312 2.453125 -20.46875 C 2.691406 -20.988281 3 -21.46875 3.375 -21.90625 C 3.75 -22.34375 4.179688 -22.703125 4.671875 -22.984375 C 5.160156 -23.273438 5.6875 -23.476562 6.25 -23.59375 C 6.820312 -23.71875 7.394531 -23.78125 7.96875 -23.78125 C 8.5625 -23.78125 9.148438 -23.710938 9.734375 -23.578125 C 10.328125 -23.453125 10.875 -23.222656 11.375 -22.890625 C 11.875 -22.566406 12.3125 -22.160156 12.6875 -21.671875 C 13.0625 -21.191406 13.351562 -20.675781 13.5625 -20.125 C 13.78125 -19.570312 13.929688 -18.992188 14.015625 -18.390625 C 14.097656 -17.796875 14.140625 -17.203125 14.140625 -16.609375 L 14.140625 -6.40625 C 14.140625 -5.820312 14.097656 -5.25 14.015625 -4.6875 C 13.929688 -4.125 13.773438 -3.578125 13.546875 -3.046875 C 13.328125 -2.523438 13.03125 -2.046875 12.65625 -1.609375 C 12.28125 -1.171875 11.847656 -0.804688 11.359375 -0.515625 C 10.867188 -0.234375 10.335938 -0.0351562 9.765625 0.078125 C 9.203125 0.191406 8.644531 0.25 8.09375 0.25 Z M 8 -11.203125 C 8.40625 -11.203125 8.804688 -11.25 9.203125 -11.34375 C 9.597656 -11.4375 9.960938 -11.59375 10.296875 -11.8125 C 10.640625 -12.039062 10.9375 -12.320312 11.1875 -12.65625 C 11.445312 -12.988281 11.648438 -13.335938 11.796875 -13.703125 C 11.953125 -14.078125 12.054688 -14.46875 12.109375 -14.875 C 12.160156 -15.28125 12.1875 -15.6875 12.1875 -16.09375 L 12.1875 -16.609375 C 12.1875 -17.035156 12.160156 -17.46875 12.109375 -17.90625 C 12.054688 -18.34375 11.957031 -18.765625 11.8125 -19.171875 C 11.675781 -19.578125 11.488281 -19.960938 11.25 -20.328125 C 11.019531 -20.703125 10.734375 -21.015625 10.390625 -21.265625 C 10.054688 -21.523438 9.675781 -21.710938 9.25 -21.828125 C 8.820312 -21.953125 8.394531 -22.015625 7.96875 -22.015625 C 7.5625 -22.015625 7.160156 -21.96875 6.765625 -21.875 C 6.367188 -21.78125 6 -21.617188 5.65625 -21.390625 C 5.320312 -21.171875 5.03125 -20.894531 4.78125 -20.5625 C 4.539062 -20.226562 4.34375 -19.875 4.1875 -19.5 C 4.039062 -19.125 3.941406 -18.734375 3.890625 -18.328125 C 3.835938 -17.929688 3.8125 -17.53125 3.8125 -17.125 L 3.8125 -16.09375 C 3.8125 -15.6875 3.835938 -15.28125 3.890625 -14.875 C 3.941406 -14.46875 4.039062 -14.078125 4.1875 -13.703125 C 4.34375 -13.335938 4.539062 -12.988281 4.78125 -12.65625 C 5.03125 -12.320312 5.320312 -12.039062 5.65625 -11.8125 C 6 -11.59375 6.375 -11.4375 6.78125 -11.34375 C 7.1875 -11.25 7.59375 -11.203125 8 -11.203125 Z M 8 -11.203125 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-40">
-<path style="stroke:none;" d="M 8 0.25 C 7.488281 0.25 6.976562 0.210938 6.46875 0.140625 C 5.957031 0.0664062 5.457031 -0.0664062 4.96875 -0.265625 C 4.488281 -0.472656 4.050781 -0.742188 3.65625 -1.078125 C 3.269531 -1.421875 2.941406 -1.8125 2.671875 -2.25 C 2.398438 -2.6875 2.195312 -3.15625 2.0625 -3.65625 C 1.925781 -4.164062 1.859375 -4.6875 1.859375 -5.21875 C 1.859375 -5.914062 1.984375 -6.601562 2.234375 -7.28125 C 2.492188 -7.96875 2.835938 -8.597656 3.265625 -9.171875 C 3.691406 -9.753906 4.175781 -10.273438 4.71875 -10.734375 C 5.257812 -11.191406 5.820312 -11.625 6.40625 -12.03125 C 5.863281 -12.4375 5.347656 -12.863281 4.859375 -13.3125 C 4.367188 -13.757812 3.9375 -14.253906 3.5625 -14.796875 C 3.195312 -15.335938 2.898438 -15.925781 2.671875 -16.5625 C 2.453125 -17.207031 2.34375 -17.863281 2.34375 -18.53125 C 2.34375 -19.019531 2.40625 -19.5 2.53125 -19.96875 C 2.65625 -20.4375 2.832031 -20.882812 3.0625 -21.3125 C 3.300781 -21.738281 3.601562 -22.113281 3.96875 -22.4375 C 4.332031 -22.769531 4.734375 -23.03125 5.171875 -23.21875 C 5.609375 -23.414062 6.066406 -23.554688 6.546875 -23.640625 C 7.023438 -23.734375 7.507812 -23.78125 8 -23.78125 C 8.488281 -23.78125 8.972656 -23.738281 9.453125 -23.65625 C 9.929688 -23.582031 10.390625 -23.441406 10.828125 -23.234375 C 11.265625 -23.035156 11.664062 -22.769531 12.03125 -22.4375 C 12.394531 -22.113281 12.691406 -21.738281 12.921875 -21.3125 C 13.160156 -20.882812 13.34375 -20.4375 13.46875 -19.96875 C 13.59375 -19.5 13.65625 -19.019531 13.65625 -18.53125 C 13.65625 -17.863281 13.539062 -17.210938 13.3125 -16.578125 C 13.09375 -15.953125 12.796875 -15.363281 12.421875 -14.8125 C 12.054688 -14.257812 11.628906 -13.757812 11.140625 -13.3125 C 10.648438 -12.863281 10.132812 -12.4375 9.59375 -12.03125 C 10.175781 -11.625 10.738281 -11.191406 11.28125 -10.734375 C 11.820312 -10.273438 12.304688 -9.753906 12.734375 -9.171875 C 13.160156 -8.597656 13.5 -7.96875 13.75 -7.28125 C 14.007812 -6.601562 14.140625 -5.914062 14.140625 -5.21875 C 14.140625 -4.6875 14.070312 -4.164062 13.9375 -3.65625 C 13.800781 -3.15625 13.597656 -2.6875 13.328125 -2.25 C 13.054688 -1.8125 12.722656 -1.421875 12.328125 -1.078125 C 11.941406 -0.742188 11.503906 -0.472656 11.015625 -0.265625 C 10.535156 -0.0664062 10.039062 0.0664062 9.53125 0.140625 C 9.019531 0.210938 8.507812 0.25 8 0.25 Z M 8 -13.15625 C 8.46875 -13.46875 8.921875 -13.804688 9.359375 -14.171875 C 9.796875 -14.535156 10.191406 -14.941406 10.546875 -15.390625 C 10.898438 -15.835938 11.179688 -16.328125 11.390625 -16.859375 C 11.609375 -17.390625 11.71875 -17.9375 11.71875 -18.5 C 11.71875 -18.988281 11.628906 -19.460938 11.453125 -19.921875 C 11.285156 -20.378906 11.023438 -20.769531 10.671875 -21.09375 C 10.316406 -21.425781 9.90625 -21.660156 9.4375 -21.796875 C 8.96875 -21.941406 8.488281 -22.015625 8 -22.015625 C 7.507812 -22.015625 7.03125 -21.941406 6.5625 -21.796875 C 6.09375 -21.660156 5.679688 -21.425781 5.328125 -21.09375 C 4.972656 -20.769531 4.707031 -20.378906 4.53125 -19.921875 C 4.363281 -19.460938 4.28125 -18.988281 4.28125 -18.5 C 4.28125 -17.9375 4.382812 -17.390625 4.59375 -16.859375 C 4.8125 -16.328125 5.097656 -15.835938 5.453125 -15.390625 C 5.804688 -14.941406 6.203125 -14.535156 6.640625 -14.171875 C 7.078125 -13.804688 7.53125 -13.46875 8 -13.15625 Z M 8 -1.5 C 8.53125 -1.5 9.054688 -1.566406 9.578125 -1.703125 C 10.109375 -1.847656 10.570312 -2.085938 10.96875 -2.421875 C 11.375 -2.765625 11.675781 -3.1875 11.875 -3.6875 C 12.082031 -4.195312 12.1875 -4.71875 12.1875 -5.25 C 12.1875 -5.84375 12.054688 -6.425781 11.796875 -7 C 11.546875 -7.582031 11.222656 -8.109375 10.828125 -8.578125 C 10.429688 -9.046875 9.988281 -9.472656 9.5 -9.859375 C 9.007812 -10.242188 8.507812 -10.601562 8 -10.9375 C 7.488281 -10.601562 6.988281 -10.242188 6.5 -9.859375 C 6.007812 -9.472656 5.566406 -9.046875 5.171875 -8.578125 C 4.773438 -8.109375 4.445312 -7.582031 4.1875 -7 C 3.9375 -6.425781 3.8125 -5.84375 3.8125 -5.25 C 3.8125 -4.71875 3.910156 -4.195312 4.109375 -3.6875 C 4.316406 -3.1875 4.617188 -2.765625 5.015625 -2.421875 C 5.421875 -2.085938 5.882812 -1.847656 6.40625 -1.703125 C 6.9375 -1.566406 7.46875 -1.5 8 -1.5 Z M 8 -1.5 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-41">
-<path style="stroke:none;" d="M 4.828125 -4.921875 L 3.265625 -5.953125 L 6.75 -10.5 L 1.3125 -12.125 L 1.921875 -13.796875 L 7.203125 -11.9375 L 7.046875 -17.59375 L 8.953125 -17.59375 L 8.796875 -11.9375 L 14.078125 -13.796875 L 14.6875 -12.125 L 9.25 -10.5 L 12.734375 -5.953125 L 11.171875 -4.921875 L 8 -9.53125 Z M 4.828125 -4.921875 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-42">
-<path style="stroke:none;" d="M 7.90625 0.25 C 7.351562 0.25 6.789062 0.191406 6.21875 0.078125 C 5.65625 -0.0351562 5.128906 -0.234375 4.640625 -0.515625 C 4.148438 -0.804688 3.71875 -1.171875 3.34375 -1.609375 C 2.96875 -2.046875 2.664062 -2.523438 2.4375 -3.046875 C 2.21875 -3.578125 2.066406 -4.125 1.984375 -4.6875 C 1.898438 -5.25 1.859375 -5.820312 1.859375 -6.40625 L 1.859375 -10.5625 C 1.859375 -11.132812 1.898438 -11.703125 1.984375 -12.265625 C 2.066406 -12.835938 2.21875 -13.382812 2.4375 -13.90625 C 2.664062 -14.425781 2.96875 -14.90625 3.34375 -15.34375 C 3.71875 -15.78125 4.148438 -16.140625 4.640625 -16.421875 C 5.128906 -16.710938 5.65625 -16.914062 6.21875 -17.03125 C 6.789062 -17.15625 7.351562 -17.21875 7.90625 -17.21875 C 8.4375 -17.21875 8.960938 -17.171875 9.484375 -17.078125 C 10.003906 -16.992188 10.5 -16.832031 10.96875 -16.59375 C 11.445312 -16.363281 11.875 -16.0625 12.25 -15.6875 C 12.632812 -15.320312 12.945312 -14.910156 13.1875 -14.453125 C 13.4375 -13.992188 13.625 -13.5 13.75 -12.96875 C 13.882812 -12.445312 13.953125 -11.921875 13.953125 -11.390625 C 13.953125 -11.390625 13.953125 -11.382812 13.953125 -11.375 C 13.953125 -11.363281 13.953125 -11.359375 13.953125 -11.359375 L 12 -11.359375 C 12 -11.359375 12 -11.363281 12 -11.375 C 12 -11.382812 12 -11.390625 12 -11.390625 C 12 -11.921875 11.90625 -12.453125 11.71875 -12.984375 C 11.539062 -13.523438 11.257812 -13.984375 10.875 -14.359375 C 10.488281 -14.742188 10.035156 -15.019531 9.515625 -15.1875 C 8.992188 -15.363281 8.457031 -15.453125 7.90625 -15.453125 C 7.5 -15.453125 7.097656 -15.40625 6.703125 -15.3125 C 6.304688 -15.21875 5.941406 -15.054688 5.609375 -14.828125 C 5.285156 -14.609375 5 -14.332031 4.75 -14 C 4.507812 -13.664062 4.316406 -13.304688 4.171875 -12.921875 C 4.035156 -12.546875 3.941406 -12.160156 3.890625 -11.765625 C 3.835938 -11.367188 3.8125 -10.96875 3.8125 -10.5625 L 3.8125 -6.40625 C 3.8125 -6 3.835938 -5.597656 3.890625 -5.203125 C 3.941406 -4.804688 4.035156 -4.414062 4.171875 -4.03125 C 4.316406 -3.644531 4.507812 -3.285156 4.75 -2.953125 C 5 -2.628906 5.285156 -2.351562 5.609375 -2.125 C 5.941406 -1.90625 6.304688 -1.742188 6.703125 -1.640625 C 7.097656 -1.546875 7.5 -1.5 7.90625 -1.5 C 8.457031 -1.5 8.992188 -1.582031 9.515625 -1.75 C 10.035156 -1.925781 10.488281 -2.207031 10.875 -2.59375 C 11.257812 -2.976562 11.539062 -3.4375 11.71875 -3.96875 C 11.90625 -4.5 12 -5.03125 12 -5.5625 C 12 -5.5625 12 -5.566406 12 -5.578125 C 12 -5.585938 12 -5.59375 12 -5.59375 L 13.953125 -5.59375 C 13.953125 -5.59375 13.953125 -5.585938 13.953125 -5.578125 C 13.953125 -5.566406 13.953125 -5.5625 13.953125 -5.5625 C 13.953125 -5.03125 13.882812 -4.503906 13.75 -3.984375 C 13.625 -3.460938 13.4375 -2.972656 13.1875 -2.515625 C 12.945312 -2.054688 12.632812 -1.640625 12.25 -1.265625 C 11.875 -0.890625 11.445312 -0.582031 10.96875 -0.34375 C 10.5 -0.113281 10.003906 0.0390625 9.484375 0.125 C 8.960938 0.207031 8.4375 0.25 7.90625 0.25 Z M 7.90625 0.25 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-43">
-<path style="stroke:none;" d="M 4.03125 0 L 4.03125 -16.953125 L 5.984375 -16.953125 L 5.984375 -12.828125 C 6.109375 -13.359375 6.28125 -13.882812 6.5 -14.40625 C 6.726562 -14.9375 7.023438 -15.414062 7.390625 -15.84375 C 7.753906 -16.269531 8.195312 -16.601562 8.71875 -16.84375 C 9.238281 -17.09375 9.78125 -17.21875 10.34375 -17.21875 C 10.738281 -17.21875 11.128906 -17.175781 11.515625 -17.09375 C 11.898438 -17.019531 12.257812 -16.878906 12.59375 -16.671875 C 12.925781 -16.460938 13.207031 -16.195312 13.4375 -15.875 C 13.675781 -15.550781 13.863281 -15.207031 14 -14.84375 C 14.132812 -14.476562 14.226562 -14.101562 14.28125 -13.71875 C 14.34375 -13.34375 14.375 -12.953125 14.375 -12.546875 L 12.421875 -12.546875 C 12.421875 -12.910156 12.382812 -13.265625 12.3125 -13.609375 C 12.25 -13.960938 12.113281 -14.285156 11.90625 -14.578125 C 11.707031 -14.867188 11.441406 -15.085938 11.109375 -15.234375 C 10.785156 -15.378906 10.453125 -15.453125 10.109375 -15.453125 C 9.617188 -15.453125 9.148438 -15.359375 8.703125 -15.171875 C 8.253906 -14.992188 7.863281 -14.726562 7.53125 -14.375 C 7.207031 -14.03125 6.945312 -13.632812 6.75 -13.1875 C 6.5625 -12.738281 6.40625 -12.285156 6.28125 -11.828125 C 6.164062 -11.367188 6.085938 -10.898438 6.046875 -10.421875 C 6.003906 -9.941406 5.984375 -9.460938 5.984375 -8.984375 L 5.984375 0 Z M 4.03125 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-44">
-<path style="stroke:none;" d="M 7.046875 -8.3125 L 7.046875 -23.515625 L 8.953125 -23.515625 L 8.953125 -8.3125 Z M 8 0.25 C 7.738281 0.25 7.476562 0.210938 7.21875 0.140625 C 6.96875 0.0664062 6.75 -0.0546875 6.5625 -0.234375 C 6.382812 -0.421875 6.257812 -0.640625 6.1875 -0.890625 C 6.113281 -1.148438 6.078125 -1.40625 6.078125 -1.65625 C 6.078125 -1.9375 6.113281 -2.195312 6.1875 -2.4375 C 6.257812 -2.6875 6.382812 -2.898438 6.5625 -3.078125 C 6.75 -3.265625 6.96875 -3.398438 7.21875 -3.484375 C 7.476562 -3.566406 7.738281 -3.609375 8 -3.609375 C 8.257812 -3.609375 8.515625 -3.566406 8.765625 -3.484375 C 9.023438 -3.398438 9.242188 -3.265625 9.421875 -3.078125 C 9.609375 -2.898438 9.738281 -2.6875 9.8125 -2.4375 C 9.882812 -2.195312 9.921875 -1.9375 9.921875 -1.65625 C 9.921875 -1.40625 9.882812 -1.148438 9.8125 -0.890625 C 9.738281 -0.640625 9.609375 -0.421875 9.421875 -0.234375 C 9.242188 -0.0546875 9.023438 0.0664062 8.765625 0.140625 C 8.515625 0.210938 8.257812 0.25 8 0.25 Z M 8 0.25 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-45">
-<path style="stroke:none;" d="M 9.125 5.28125 L 7.875 0.25 C 7.320312 0.25 6.765625 0.191406 6.203125 0.078125 C 5.648438 -0.0351562 5.132812 -0.226562 4.65625 -0.5 C 4.175781 -0.78125 3.75 -1.132812 3.375 -1.5625 C 3 -1.988281 2.691406 -2.457031 2.453125 -2.96875 C 2.222656 -3.488281 2.066406 -4.023438 1.984375 -4.578125 C 1.898438 -5.128906 1.859375 -5.679688 1.859375 -6.234375 L 1.859375 -17.28125 C 1.859375 -17.851562 1.90625 -18.414062 2 -18.96875 C 2.09375 -19.53125 2.253906 -20.066406 2.484375 -20.578125 C 2.722656 -21.085938 3.035156 -21.554688 3.421875 -21.984375 C 3.804688 -22.410156 4.242188 -22.757812 4.734375 -23.03125 C 5.222656 -23.3125 5.742188 -23.519531 6.296875 -23.65625 C 6.859375 -23.800781 7.425781 -23.875 8 -23.875 C 8.570312 -23.875 9.132812 -23.800781 9.6875 -23.65625 C 10.25 -23.519531 10.773438 -23.3125 11.265625 -23.03125 C 11.753906 -22.757812 12.191406 -22.410156 12.578125 -21.984375 C 12.960938 -21.554688 13.269531 -21.085938 13.5 -20.578125 C 13.738281 -20.066406 13.90625 -19.53125 14 -18.96875 C 14.09375 -18.414062 14.140625 -17.851562 14.140625 -17.28125 L 14.140625 -6.234375 C 14.140625 -5.765625 14.113281 -5.300781 14.0625 -4.84375 C 14.007812 -4.382812 13.898438 -3.9375 13.734375 -3.5 C 13.578125 -3.0625 13.375 -2.644531 13.125 -2.25 C 12.882812 -1.851562 12.59375 -1.492188 12.25 -1.171875 C 11.914062 -0.859375 11.539062 -0.597656 11.125 -0.390625 C 10.707031 -0.191406 10.265625 -0.0390625 9.796875 0.0625 L 11.078125 5.28125 Z M 8 -1.5 C 8.40625 -1.5 8.804688 -1.539062 9.203125 -1.625 C 9.597656 -1.71875 9.960938 -1.867188 10.296875 -2.078125 C 10.640625 -2.296875 10.929688 -2.554688 11.171875 -2.859375 C 11.421875 -3.171875 11.625 -3.515625 11.78125 -3.890625 C 11.945312 -4.265625 12.054688 -4.644531 12.109375 -5.03125 C 12.160156 -5.425781 12.1875 -5.828125 12.1875 -6.234375 L 12.1875 -17.28125 C 12.1875 -17.6875 12.160156 -18.085938 12.109375 -18.484375 C 12.054688 -18.878906 11.945312 -19.257812 11.78125 -19.625 C 11.625 -20 11.414062 -20.347656 11.15625 -20.671875 C 10.90625 -20.992188 10.601562 -21.253906 10.25 -21.453125 C 9.90625 -21.660156 9.535156 -21.804688 9.140625 -21.890625 C 8.742188 -21.972656 8.34375 -22.015625 7.9375 -22.015625 C 7.550781 -22.015625 7.160156 -21.96875 6.765625 -21.875 C 6.367188 -21.78125 6.003906 -21.625 5.671875 -21.40625 C 5.347656 -21.195312 5.054688 -20.9375 4.796875 -20.625 C 4.546875 -20.3125 4.34375 -19.96875 4.1875 -19.59375 C 4.039062 -19.226562 3.941406 -18.851562 3.890625 -18.46875 C 3.835938 -18.082031 3.8125 -17.6875 3.8125 -17.28125 L 3.8125 -6.234375 C 3.8125 -5.828125 3.835938 -5.425781 3.890625 -5.03125 C 3.941406 -4.644531 4.046875 -4.265625 4.203125 -3.890625 C 4.367188 -3.515625 4.570312 -3.171875 4.8125 -2.859375 C 5.0625 -2.554688 5.351562 -2.296875 5.6875 -2.078125 C 6.03125 -1.867188 6.398438 -1.71875 6.796875 -1.625 C 7.191406 -1.539062 7.59375 -1.5 8 -1.5 Z M 8 -1.5 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-46">
-<path style="stroke:none;" d="M 5.828125 0 C 5.828125 -1.894531 5.945312 -3.785156 6.1875 -5.671875 C 6.4375 -7.566406 6.804688 -9.429688 7.296875 -11.265625 C 7.785156 -13.097656 8.394531 -14.894531 9.125 -16.65625 C 9.863281 -18.414062 10.71875 -20.117188 11.6875 -21.765625 L 2.109375 -21.765625 L 2.109375 -23.515625 L 13.890625 -23.515625 L 13.890625 -21.765625 C 12.890625 -20.117188 12.003906 -18.421875 11.234375 -16.671875 C 10.460938 -14.921875 9.820312 -13.128906 9.3125 -11.296875 C 8.800781 -9.460938 8.410156 -7.59375 8.140625 -5.6875 C 7.878906 -3.789062 7.75 -1.894531 7.75 0 Z M 5.828125 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-47">
-<path style="stroke:none;" d="M 8 0.25 C 7.71875 0.25 7.4375 0.207031 7.15625 0.125 C 6.882812 0.0390625 6.644531 -0.0976562 6.4375 -0.296875 C 6.238281 -0.503906 6.097656 -0.742188 6.015625 -1.015625 C 5.929688 -1.296875 5.890625 -1.578125 5.890625 -1.859375 C 5.890625 -2.148438 5.929688 -2.4375 6.015625 -2.71875 C 6.097656 -3 6.238281 -3.238281 6.4375 -3.4375 C 6.644531 -3.644531 6.882812 -3.789062 7.15625 -3.875 C 7.4375 -3.957031 7.71875 -4 8 -4 C 8.28125 -4 8.554688 -3.957031 8.828125 -3.875 C 9.109375 -3.789062 9.347656 -3.644531 9.546875 -3.4375 C 9.753906 -3.238281 9.898438 -3 9.984375 -2.71875 C 10.066406 -2.4375 10.109375 -2.148438 10.109375 -1.859375 C 10.109375 -1.578125 10.066406 -1.296875 9.984375 -1.015625 C 9.898438 -0.742188 9.753906 -0.503906 9.546875 -0.296875 C 9.347656 -0.0976562 9.109375 0.0390625 8.828125 0.125 C 8.554688 0.207031 8.28125 0.25 8 0.25 Z M 8 0.25 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-48">
-<path style="stroke:none;" d="M 7.046875 3.265625 L 7.046875 -0.953125 C 6.335938 -1.023438 5.648438 -1.1875 4.984375 -1.4375 C 4.328125 -1.695312 3.753906 -2.070312 3.265625 -2.5625 C 2.773438 -3.050781 2.40625 -3.628906 2.15625 -4.296875 C 1.914062 -4.972656 1.796875 -5.664062 1.796875 -6.375 C 1.796875 -6.414062 1.796875 -6.453125 1.796875 -6.484375 C 1.796875 -6.515625 1.796875 -6.550781 1.796875 -6.59375 L 3.75 -6.59375 C 3.75 -6.570312 3.75 -6.546875 3.75 -6.515625 C 3.75 -6.484375 3.75 -6.457031 3.75 -6.4375 C 3.75 -5.894531 3.851562 -5.375 4.0625 -4.875 C 4.28125 -4.375 4.585938 -3.953125 4.984375 -3.609375 C 5.390625 -3.273438 5.851562 -3.035156 6.375 -2.890625 C 6.90625 -2.753906 7.4375 -2.6875 7.96875 -2.6875 C 8.5 -2.6875 9.023438 -2.75 9.546875 -2.875 C 10.078125 -3.007812 10.539062 -3.242188 10.9375 -3.578125 C 11.34375 -3.921875 11.648438 -4.335938 11.859375 -4.828125 C 12.078125 -5.316406 12.1875 -5.832031 12.1875 -6.375 C 12.1875 -6.90625 12.066406 -7.414062 11.828125 -7.90625 C 11.597656 -8.394531 11.289062 -8.816406 10.90625 -9.171875 C 10.53125 -9.523438 10.097656 -9.816406 9.609375 -10.046875 C 9.128906 -10.285156 8.644531 -10.492188 8.15625 -10.671875 C 7.664062 -10.847656 7.175781 -11.039062 6.6875 -11.25 C 6.195312 -11.46875 5.726562 -11.71875 5.28125 -12 C 4.832031 -12.28125 4.410156 -12.597656 4.015625 -12.953125 C 3.617188 -13.316406 3.273438 -13.71875 2.984375 -14.15625 C 2.703125 -14.59375 2.484375 -15.066406 2.328125 -15.578125 C 2.179688 -16.097656 2.109375 -16.625 2.109375 -17.15625 C 2.109375 -17.832031 2.21875 -18.492188 2.4375 -19.140625 C 2.664062 -19.796875 3.003906 -20.367188 3.453125 -20.859375 C 3.898438 -21.347656 4.4375 -21.738281 5.0625 -22.03125 C 5.695312 -22.320312 6.359375 -22.5 7.046875 -22.5625 L 7.046875 -26.8125 L 8.953125 -26.8125 L 8.953125 -22.5625 C 9.640625 -22.5 10.296875 -22.332031 10.921875 -22.0625 C 11.554688 -21.789062 12.101562 -21.40625 12.5625 -20.90625 C 13.019531 -20.40625 13.363281 -19.828125 13.59375 -19.171875 C 13.832031 -18.523438 13.953125 -17.863281 13.953125 -17.1875 C 13.953125 -17.164062 13.953125 -17.132812 13.953125 -17.09375 C 13.953125 -17.050781 13.953125 -17.003906 13.953125 -16.953125 L 12 -16.953125 C 12 -17.003906 12 -17.039062 12 -17.0625 C 12 -17.082031 12 -17.101562 12 -17.125 C 12 -17.632812 11.898438 -18.132812 11.703125 -18.625 C 11.515625 -19.113281 11.234375 -19.535156 10.859375 -19.890625 C 10.484375 -20.242188 10.046875 -20.488281 9.546875 -20.625 C 9.046875 -20.757812 8.539062 -20.828125 8.03125 -20.828125 C 7.519531 -20.828125 7.015625 -20.757812 6.515625 -20.625 C 6.015625 -20.488281 5.578125 -20.25 5.203125 -19.90625 C 4.828125 -19.5625 4.539062 -19.144531 4.34375 -18.65625 C 4.15625 -18.164062 4.0625 -17.664062 4.0625 -17.15625 C 4.0625 -16.625 4.171875 -16.109375 4.390625 -15.609375 C 4.617188 -15.117188 4.929688 -14.695312 5.328125 -14.34375 C 5.722656 -14 6.160156 -13.707031 6.640625 -13.46875 C 7.117188 -13.238281 7.601562 -13.03125 8.09375 -12.84375 C 8.582031 -12.664062 9.070312 -12.46875 9.5625 -12.25 C 10.050781 -12.039062 10.519531 -11.796875 10.96875 -11.515625 C 11.414062 -11.242188 11.835938 -10.929688 12.234375 -10.578125 C 12.628906 -10.222656 12.960938 -9.820312 13.234375 -9.375 C 13.515625 -8.925781 13.734375 -8.445312 13.890625 -7.9375 C 14.054688 -7.425781 14.140625 -6.90625 14.140625 -6.375 C 14.140625 -5.6875 14.015625 -5.007812 13.765625 -4.34375 C 13.523438 -3.6875 13.164062 -3.109375 12.6875 -2.609375 C 12.207031 -2.109375 11.640625 -1.722656 10.984375 -1.453125 C 10.335938 -1.191406 9.660156 -1.023438 8.953125 -0.953125 L 8.953125 3.265625 Z M 7.046875 3.265625 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-49">
-<path style="stroke:none;" d="M 2.015625 4.578125 L 12 -26.34375 L 13.984375 -26.34375 L 4 4.578125 Z M 2.015625 4.578125 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-50">
-<path style="stroke:none;" d="M 6.875 0 C 6.519531 -0.8125 6.164062 -1.628906 5.8125 -2.453125 C 5.457031 -3.273438 5.117188 -4.097656 4.796875 -4.921875 C 4.472656 -5.753906 4.160156 -6.59375 3.859375 -7.4375 C 3.566406 -8.28125 3.296875 -9.132812 3.046875 -10 C 2.804688 -10.863281 2.585938 -11.734375 2.390625 -12.609375 C 2.203125 -13.484375 2.109375 -14.367188 2.109375 -15.265625 L 2.109375 -16.953125 L 4.0625 -16.953125 L 4.0625 -15.265625 C 4.0625 -14.472656 4.132812 -13.691406 4.28125 -12.921875 C 4.4375 -12.160156 4.613281 -11.398438 4.8125 -10.640625 C 5.019531 -9.878906 5.242188 -9.125 5.484375 -8.375 C 5.734375 -7.632812 5.992188 -6.894531 6.265625 -6.15625 C 6.546875 -5.425781 6.828125 -4.691406 7.109375 -3.953125 C 7.398438 -3.210938 7.695312 -2.484375 8 -1.765625 C 8.300781 -2.484375 8.59375 -3.210938 8.875 -3.953125 C 9.164062 -4.691406 9.445312 -5.425781 9.71875 -6.15625 C 10 -6.894531 10.257812 -7.632812 10.5 -8.375 C 10.75 -9.125 10.972656 -9.878906 11.171875 -10.640625 C 11.378906 -11.398438 11.554688 -12.160156 11.703125 -12.921875 C 11.859375 -13.691406 11.9375 -14.472656 11.9375 -15.265625 L 11.9375 -16.953125 L 13.890625 -16.953125 L 13.890625 -15.265625 C 13.890625 -14.367188 13.789062 -13.484375 13.59375 -12.609375 C 13.40625 -11.734375 13.1875 -10.863281 12.9375 -10 C 12.695312 -9.132812 12.425781 -8.28125 12.125 -7.4375 C 11.832031 -6.59375 11.523438 -5.753906 11.203125 -4.921875 C 10.878906 -4.097656 10.539062 -3.273438 10.1875 -2.453125 C 9.832031 -1.628906 9.476562 -0.8125 9.125 0 Z M 6.875 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-51">
-<path style="stroke:none;" d="M 7.046875 0 L 7.046875 -9.21875 L 4.1875 -14.46875 C 3.570312 -15.632812 3.070312 -16.863281 2.6875 -18.15625 C 2.300781 -19.445312 2.109375 -20.765625 2.109375 -22.109375 L 2.109375 -23.515625 L 4.0625 -23.515625 L 4.0625 -22.109375 C 4.0625 -20.910156 4.234375 -19.738281 4.578125 -18.59375 C 4.921875 -17.457031 5.367188 -16.367188 5.921875 -15.328125 L 8 -11.421875 L 10.078125 -15.328125 C 10.628906 -16.367188 11.078125 -17.457031 11.421875 -18.59375 C 11.765625 -19.738281 11.9375 -20.910156 11.9375 -22.109375 L 11.9375 -23.515625 L 13.890625 -23.515625 L 13.890625 -22.109375 C 13.890625 -20.765625 13.695312 -19.445312 13.3125 -18.15625 C 12.925781 -16.863281 12.425781 -15.632812 11.8125 -14.46875 L 8.953125 -9.21875 L 8.953125 0 Z M 7.046875 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-52">
-<path style="stroke:none;" d="M 2.109375 0 L 2.109375 -23.515625 L 4.984375 -23.515625 L 11.9375 -11.078125 L 11.9375 -23.515625 L 13.890625 -23.515625 L 13.890625 0 L 11.9375 0 L 11.9375 -7.203125 L 4.0625 -21.25 L 4.0625 0 Z M 2.109375 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-53">
-<path style="stroke:none;" d="M 13.890625 -12.828125 L 2.109375 -12.828125 L 2.109375 -14.59375 L 13.890625 -14.59375 Z M 13.890625 -7.171875 L 2.109375 -7.171875 L 2.109375 -8.921875 L 13.890625 -8.921875 Z M 13.890625 -7.171875 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-54">
-<path style="stroke:none;" d="M 8 -12.953125 C 7.71875 -12.953125 7.4375 -12.992188 7.15625 -13.078125 C 6.882812 -13.171875 6.644531 -13.316406 6.4375 -13.515625 C 6.238281 -13.722656 6.097656 -13.960938 6.015625 -14.234375 C 5.929688 -14.515625 5.890625 -14.804688 5.890625 -15.109375 C 5.890625 -15.378906 5.929688 -15.65625 6.015625 -15.9375 C 6.097656 -16.21875 6.238281 -16.457031 6.4375 -16.65625 C 6.644531 -16.851562 6.882812 -16.992188 7.15625 -17.078125 C 7.4375 -17.171875 7.71875 -17.21875 8 -17.21875 C 8.28125 -17.21875 8.554688 -17.171875 8.828125 -17.078125 C 9.109375 -16.992188 9.347656 -16.851562 9.546875 -16.65625 C 9.753906 -16.457031 9.898438 -16.21875 9.984375 -15.9375 C 10.066406 -15.65625 10.109375 -15.378906 10.109375 -15.109375 C 10.109375 -14.804688 10.066406 -14.515625 9.984375 -14.234375 C 9.898438 -13.960938 9.753906 -13.722656 9.546875 -13.515625 C 9.347656 -13.316406 9.109375 -13.171875 8.828125 -13.078125 C 8.554688 -12.992188 8.28125 -12.953125 8 -12.953125 Z M 7.015625 6.5625 L 5.46875 5.46875 C 6.113281 4.695312 6.679688 3.875 7.171875 3 C 7.660156 2.132812 8.007812 1.21875 8.21875 0.25 C 8.175781 0.25 8.132812 0.25 8.09375 0.25 C 8.050781 0.25 8.007812 0.25 7.96875 0.25 C 7.6875 0.25 7.40625 0.207031 7.125 0.125 C 6.851562 0.0390625 6.617188 -0.0976562 6.421875 -0.296875 C 6.234375 -0.503906 6.097656 -0.742188 6.015625 -1.015625 C 5.929688 -1.296875 5.890625 -1.578125 5.890625 -1.859375 C 5.890625 -2.148438 5.929688 -2.4375 6.015625 -2.71875 C 6.097656 -3 6.238281 -3.238281 6.4375 -3.4375 C 6.644531 -3.644531 6.882812 -3.789062 7.15625 -3.875 C 7.4375 -3.957031 7.71875 -4 8 -4 C 8.300781 -4 8.59375 -3.953125 8.875 -3.859375 C 9.164062 -3.765625 9.40625 -3.609375 9.59375 -3.390625 C 9.789062 -3.179688 9.925781 -2.929688 10 -2.640625 C 10.070312 -2.347656 10.109375 -2.054688 10.109375 -1.765625 C 10.109375 -1.015625 10.023438 -0.269531 9.859375 0.46875 C 9.703125 1.21875 9.488281 1.941406 9.21875 2.640625 C 8.957031 3.335938 8.632812 4.015625 8.25 4.671875 C 7.875 5.328125 7.460938 5.957031 7.015625 6.5625 Z M 7.015625 6.5625 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-55">
-<path style="stroke:none;" d="M 7.96875 0.25 C 7.4375 0.25 6.910156 0.207031 6.390625 0.125 C 5.878906 0.0390625 5.382812 -0.101562 4.90625 -0.3125 C 4.425781 -0.53125 3.984375 -0.816406 3.578125 -1.171875 C 3.171875 -1.523438 2.835938 -1.925781 2.578125 -2.375 C 2.328125 -2.820312 2.132812 -3.300781 2 -3.8125 C 1.863281 -4.320312 1.796875 -4.832031 1.796875 -5.34375 C 1.796875 -5.382812 1.796875 -5.414062 1.796875 -5.4375 C 1.796875 -5.457031 1.796875 -5.476562 1.796875 -5.5 L 3.75 -5.5 C 3.75 -5.476562 3.75 -5.457031 3.75 -5.4375 C 3.75 -5.414062 3.75 -5.40625 3.75 -5.40625 C 3.75 -4.851562 3.847656 -4.320312 4.046875 -3.8125 C 4.253906 -3.300781 4.554688 -2.863281 4.953125 -2.5 C 5.359375 -2.132812 5.828125 -1.875 6.359375 -1.71875 C 6.898438 -1.570312 7.4375 -1.5 7.96875 -1.5 C 8.519531 -1.5 9.066406 -1.582031 9.609375 -1.75 C 10.160156 -1.925781 10.628906 -2.210938 11.015625 -2.609375 C 11.410156 -3.003906 11.703125 -3.46875 11.890625 -4 C 12.085938 -4.53125 12.1875 -5.082031 12.1875 -5.65625 C 12.1875 -6.21875 12.078125 -6.765625 11.859375 -7.296875 C 11.648438 -7.828125 11.351562 -8.296875 10.96875 -8.703125 C 10.59375 -9.109375 10.160156 -9.457031 9.671875 -9.75 C 9.191406 -10.050781 8.703125 -10.328125 8.203125 -10.578125 C 7.703125 -10.835938 7.207031 -11.097656 6.71875 -11.359375 C 6.226562 -11.617188 5.753906 -11.910156 5.296875 -12.234375 C 4.835938 -12.566406 4.410156 -12.9375 4.015625 -13.34375 C 3.617188 -13.75 3.273438 -14.191406 2.984375 -14.671875 C 2.703125 -15.148438 2.484375 -15.660156 2.328125 -16.203125 C 2.179688 -16.753906 2.109375 -17.316406 2.109375 -17.890625 C 2.109375 -18.398438 2.164062 -18.914062 2.28125 -19.4375 C 2.40625 -19.957031 2.582031 -20.445312 2.8125 -20.90625 C 3.050781 -21.363281 3.351562 -21.785156 3.71875 -22.171875 C 4.09375 -22.554688 4.515625 -22.867188 4.984375 -23.109375 C 5.460938 -23.359375 5.960938 -23.53125 6.484375 -23.625 C 7.003906 -23.726562 7.519531 -23.78125 8.03125 -23.78125 C 8.539062 -23.78125 9.050781 -23.734375 9.5625 -23.640625 C 10.082031 -23.554688 10.570312 -23.398438 11.03125 -23.171875 C 11.488281 -22.953125 11.898438 -22.660156 12.265625 -22.296875 C 12.640625 -21.941406 12.945312 -21.539062 13.1875 -21.09375 C 13.4375 -20.644531 13.625 -20.164062 13.75 -19.65625 C 13.882812 -19.15625 13.953125 -18.648438 13.953125 -18.140625 C 13.953125 -18.117188 13.953125 -18.097656 13.953125 -18.078125 C 13.953125 -18.054688 13.953125 -18.035156 13.953125 -18.015625 L 12 -18.015625 C 12 -18.035156 12 -18.050781 12 -18.0625 C 12 -18.070312 12 -18.085938 12 -18.109375 C 12 -18.617188 11.90625 -19.128906 11.71875 -19.640625 C 11.539062 -20.160156 11.269531 -20.601562 10.90625 -20.96875 C 10.550781 -21.34375 10.113281 -21.609375 9.59375 -21.765625 C 9.082031 -21.929688 8.5625 -22.015625 8.03125 -22.015625 C 7.5 -22.015625 6.96875 -21.921875 6.4375 -21.734375 C 5.914062 -21.554688 5.472656 -21.265625 5.109375 -20.859375 C 4.753906 -20.453125 4.488281 -19.988281 4.3125 -19.46875 C 4.144531 -18.945312 4.0625 -18.421875 4.0625 -17.890625 C 4.0625 -17.316406 4.164062 -16.765625 4.375 -16.234375 C 4.59375 -15.710938 4.890625 -15.242188 5.265625 -14.828125 C 5.640625 -14.410156 6.066406 -14.050781 6.546875 -13.75 C 7.023438 -13.457031 7.515625 -13.1875 8.015625 -12.9375 C 8.515625 -12.695312 9.015625 -12.441406 9.515625 -12.171875 C 10.015625 -11.910156 10.492188 -11.613281 10.953125 -11.28125 C 11.410156 -10.945312 11.835938 -10.578125 12.234375 -10.171875 C 12.628906 -9.765625 12.972656 -9.320312 13.265625 -8.84375 C 13.554688 -8.363281 13.773438 -7.851562 13.921875 -7.3125 C 14.066406 -6.769531 14.140625 -6.21875 14.140625 -5.65625 C 14.140625 -5.101562 14.078125 -4.566406 13.953125 -4.046875 C 13.828125 -3.523438 13.632812 -3.03125 13.375 -2.5625 C 13.113281 -2.09375 12.785156 -1.671875 12.390625 -1.296875 C 12.003906 -0.921875 11.570312 -0.613281 11.09375 -0.375 C 10.613281 -0.144531 10.101562 0.015625 9.5625 0.109375 C 9.03125 0.203125 8.5 0.25 7.96875 0.25 Z M 7.96875 0.25 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-56">
-<path style="stroke:none;" d="M 7.265625 0.25 C 6.753906 0.25 6.234375 0.175781 5.703125 0.03125 C 5.179688 -0.113281 4.710938 -0.347656 4.296875 -0.671875 C 3.890625 -0.992188 3.539062 -1.382812 3.25 -1.84375 C 2.957031 -2.300781 2.722656 -2.78125 2.546875 -3.28125 C 2.378906 -3.78125 2.265625 -4.289062 2.203125 -4.8125 C 2.140625 -5.332031 2.109375 -5.863281 2.109375 -6.40625 L 2.109375 -16.953125 L 4.0625 -16.953125 L 4.0625 -6.40625 C 4.0625 -6 4.085938 -5.601562 4.140625 -5.21875 C 4.191406 -4.832031 4.28125 -4.453125 4.40625 -4.078125 C 4.539062 -3.703125 4.71875 -3.34375 4.9375 -3 C 5.164062 -2.664062 5.4375 -2.382812 5.75 -2.15625 C 6.070312 -1.9375 6.425781 -1.769531 6.8125 -1.65625 C 7.195312 -1.550781 7.59375 -1.5 8 -1.5 C 8.40625 -1.5 8.800781 -1.550781 9.1875 -1.65625 C 9.570312 -1.769531 9.921875 -1.9375 10.234375 -2.15625 C 10.554688 -2.382812 10.828125 -2.664062 11.046875 -3 C 11.273438 -3.34375 11.453125 -3.703125 11.578125 -4.078125 C 11.710938 -4.453125 11.804688 -4.832031 11.859375 -5.21875 C 11.910156 -5.601562 11.9375 -6 11.9375 -6.40625 L 11.9375 -16.953125 L 13.890625 -16.953125 L 13.890625 0 L 11.9375 0 L 11.9375 -3.078125 C 11.769531 -2.585938 11.535156 -2.128906 11.234375 -1.703125 C 10.929688 -1.273438 10.578125 -0.90625 10.171875 -0.59375 C 9.765625 -0.28125 9.304688 -0.0625 8.796875 0.0625 C 8.285156 0.1875 7.773438 0.25 7.265625 0.25 Z M 7.265625 0.25 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-57">
-<path style="stroke:none;" d="M 8 6.8125 C 7.488281 6.8125 6.96875 6.78125 6.4375 6.71875 C 5.914062 6.65625 5.40625 6.535156 4.90625 6.359375 C 4.40625 6.191406 3.941406 5.953125 3.515625 5.640625 C 3.085938 5.335938 2.71875 4.976562 2.40625 4.5625 C 2.101562 4.144531 1.878906 3.675781 1.734375 3.15625 C 1.597656 2.632812 1.53125 2.113281 1.53125 1.59375 C 1.53125 1.21875 1.566406 0.835938 1.640625 0.453125 C 1.722656 0.0664062 1.851562 -0.296875 2.03125 -0.640625 C 2.207031 -0.984375 2.4375 -1.285156 2.71875 -1.546875 C 3 -1.816406 3.296875 -2.046875 3.609375 -2.234375 C 3.273438 -2.535156 3.007812 -2.894531 2.8125 -3.3125 C 2.625 -3.726562 2.53125 -4.148438 2.53125 -4.578125 C 2.53125 -5.128906 2.664062 -5.648438 2.9375 -6.140625 C 3.21875 -6.628906 3.582031 -7.023438 4.03125 -7.328125 C 3.664062 -7.585938 3.335938 -7.878906 3.046875 -8.203125 C 2.765625 -8.535156 2.53125 -8.898438 2.34375 -9.296875 C 2.164062 -9.691406 2.039062 -10.101562 1.96875 -10.53125 C 1.894531 -10.957031 1.859375 -11.382812 1.859375 -11.8125 C 1.859375 -12.320312 1.910156 -12.820312 2.015625 -13.3125 C 2.117188 -13.800781 2.289062 -14.265625 2.53125 -14.703125 C 2.78125 -15.140625 3.097656 -15.53125 3.484375 -15.875 C 3.867188 -16.21875 4.289062 -16.488281 4.75 -16.6875 C 5.207031 -16.882812 5.6875 -17.03125 6.1875 -17.125 C 6.6875 -17.226562 7.1875 -17.28125 7.6875 -17.28125 C 8 -17.28125 8.316406 -17.257812 8.640625 -17.21875 C 8.960938 -17.175781 9.269531 -17.125 9.5625 -17.0625 L 9.828125 -16.953125 L 14.65625 -16.953125 L 14.65625 -15.203125 L 12.453125 -15.265625 C 12.835938 -14.796875 13.109375 -14.253906 13.265625 -13.640625 C 13.421875 -13.035156 13.5 -12.425781 13.5 -11.8125 C 13.5 -11.320312 13.445312 -10.820312 13.34375 -10.3125 C 13.238281 -9.8125 13.0625 -9.34375 12.8125 -8.90625 C 12.570312 -8.46875 12.257812 -8.082031 11.875 -7.75 C 11.488281 -7.425781 11.066406 -7.160156 10.609375 -6.953125 C 10.148438 -6.753906 9.675781 -6.617188 9.1875 -6.546875 C 8.695312 -6.472656 8.195312 -6.4375 7.6875 -6.4375 C 7.34375 -6.4375 7.003906 -6.453125 6.671875 -6.484375 C 6.335938 -6.515625 6 -6.5625 5.65625 -6.625 C 5.46875 -6.5625 5.289062 -6.472656 5.125 -6.359375 C 4.96875 -6.253906 4.828125 -6.128906 4.703125 -5.984375 C 4.578125 -5.835938 4.472656 -5.664062 4.390625 -5.46875 C 4.316406 -5.28125 4.28125 -5.085938 4.28125 -4.890625 C 4.28125 -4.617188 4.351562 -4.351562 4.5 -4.09375 C 4.65625 -3.84375 4.851562 -3.640625 5.09375 -3.484375 C 5.34375 -3.335938 5.601562 -3.238281 5.875 -3.1875 C 6.15625 -3.132812 6.4375 -3.097656 6.71875 -3.078125 C 6.738281 -3.078125 6.765625 -3.078125 6.796875 -3.078125 C 6.828125 -3.078125 6.863281 -3.078125 6.90625 -3.078125 C 6.945312 -3.078125 7 -3.078125 7.0625 -3.078125 C 7.132812 -3.078125 7.191406 -3.078125 7.234375 -3.078125 L 8.765625 -3.078125 C 9.234375 -3.078125 9.703125 -3.046875 10.171875 -2.984375 C 10.640625 -2.929688 11.09375 -2.820312 11.53125 -2.65625 C 11.96875 -2.488281 12.382812 -2.269531 12.78125 -2 C 13.175781 -1.726562 13.503906 -1.398438 13.765625 -1.015625 C 14.035156 -0.640625 14.222656 -0.21875 14.328125 0.25 C 14.441406 0.71875 14.5 1.1875 14.5 1.65625 C 14.5 2.175781 14.425781 2.691406 14.28125 3.203125 C 14.144531 3.710938 13.921875 4.175781 13.609375 4.59375 C 13.304688 5.007812 12.9375 5.363281 12.5 5.65625 C 12.0625 5.957031 11.59375 6.191406 11.09375 6.359375 C 10.59375 6.535156 10.082031 6.65625 9.5625 6.71875 C 9.050781 6.78125 8.53125 6.8125 8 6.8125 Z M 7.6875 -8.1875 C 8.195312 -8.1875 8.695312 -8.25 9.1875 -8.375 C 9.675781 -8.507812 10.109375 -8.742188 10.484375 -9.078125 C 10.859375 -9.421875 11.128906 -9.835938 11.296875 -10.328125 C 11.460938 -10.828125 11.546875 -11.320312 11.546875 -11.8125 C 11.546875 -12.28125 11.476562 -12.742188 11.34375 -13.203125 C 11.207031 -13.660156 10.972656 -14.054688 10.640625 -14.390625 C 10.304688 -14.734375 9.910156 -14.984375 9.453125 -15.140625 C 8.992188 -15.304688 8.53125 -15.398438 8.0625 -15.421875 L 7.6875 -15.453125 C 7.6875 -15.453125 7.675781 -15.453125 7.65625 -15.453125 C 7.644531 -15.453125 7.628906 -15.453125 7.609375 -15.453125 C 7.117188 -15.453125 6.628906 -15.382812 6.140625 -15.25 C 5.648438 -15.113281 5.222656 -14.875 4.859375 -14.53125 C 4.492188 -14.1875 4.226562 -13.773438 4.0625 -13.296875 C 3.894531 -12.816406 3.8125 -12.320312 3.8125 -11.8125 C 3.8125 -11.320312 3.894531 -10.828125 4.0625 -10.328125 C 4.226562 -9.835938 4.5 -9.421875 4.875 -9.078125 C 5.25 -8.742188 5.679688 -8.507812 6.171875 -8.375 C 6.660156 -8.25 7.164062 -8.1875 7.6875 -8.1875 Z M 8 5.0625 C 8.363281 5.0625 8.71875 5.035156 9.0625 4.984375 C 9.414062 4.941406 9.765625 4.867188 10.109375 4.765625 C 10.453125 4.660156 10.773438 4.515625 11.078125 4.328125 C 11.390625 4.148438 11.65625 3.921875 11.875 3.640625 C 12.101562 3.367188 12.273438 3.0625 12.390625 2.71875 C 12.515625 2.375 12.578125 2.019531 12.578125 1.65625 C 12.578125 1.1875 12.472656 0.742188 12.265625 0.328125 C 12.066406 -0.078125 11.773438 -0.40625 11.390625 -0.65625 C 11.003906 -0.90625 10.582031 -1.078125 10.125 -1.171875 C 9.664062 -1.265625 9.210938 -1.3125 8.765625 -1.3125 L 7.234375 -1.3125 C 7.171875 -1.3125 7.101562 -1.3125 7.03125 -1.3125 C 6.96875 -1.3125 6.894531 -1.3125 6.8125 -1.3125 C 6.40625 -1.289062 6 -1.21875 5.59375 -1.09375 C 5.1875 -0.976562 4.816406 -0.796875 4.484375 -0.546875 C 4.160156 -0.304688 3.910156 0.00390625 3.734375 0.390625 C 3.566406 0.785156 3.484375 1.1875 3.484375 1.59375 C 3.484375 1.957031 3.535156 2.316406 3.640625 2.671875 C 3.753906 3.023438 3.914062 3.335938 4.125 3.609375 C 4.34375 3.890625 4.601562 4.125 4.90625 4.3125 C 5.21875 4.507812 5.546875 4.660156 5.890625 4.765625 C 6.234375 4.867188 6.578125 4.941406 6.921875 4.984375 C 7.273438 5.035156 7.632812 5.0625 8 5.0625 Z M 8 5.0625 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-58">
-<path style="stroke:none;" d="M 2.109375 0 L 2.109375 -23.515625 L 4.0625 -23.515625 L 4.0625 -13.890625 C 4.226562 -14.378906 4.460938 -14.835938 4.765625 -15.265625 C 5.066406 -15.691406 5.421875 -16.054688 5.828125 -16.359375 C 6.234375 -16.671875 6.691406 -16.890625 7.203125 -17.015625 C 7.710938 -17.148438 8.222656 -17.21875 8.734375 -17.21875 C 9.242188 -17.21875 9.757812 -17.140625 10.28125 -16.984375 C 10.8125 -16.835938 11.28125 -16.601562 11.6875 -16.28125 C 12.101562 -15.96875 12.457031 -15.582031 12.75 -15.125 C 13.039062 -14.664062 13.269531 -14.179688 13.4375 -13.671875 C 13.613281 -13.171875 13.734375 -12.660156 13.796875 -12.140625 C 13.859375 -11.617188 13.890625 -11.09375 13.890625 -10.5625 L 13.890625 0 L 11.9375 0 L 11.9375 -10.5625 C 11.9375 -10.96875 11.910156 -11.359375 11.859375 -11.734375 C 11.804688 -12.117188 11.710938 -12.5 11.578125 -12.875 C 11.453125 -13.25 11.273438 -13.609375 11.046875 -13.953125 C 10.828125 -14.296875 10.554688 -14.578125 10.234375 -14.796875 C 9.921875 -15.023438 9.570312 -15.191406 9.1875 -15.296875 C 8.800781 -15.398438 8.40625 -15.453125 8 -15.453125 C 7.59375 -15.453125 7.195312 -15.398438 6.8125 -15.296875 C 6.425781 -15.191406 6.070312 -15.023438 5.75 -14.796875 C 5.4375 -14.578125 5.164062 -14.296875 4.9375 -13.953125 C 4.71875 -13.609375 4.539062 -13.25 4.40625 -12.875 C 4.28125 -12.5 4.191406 -12.117188 4.140625 -11.734375 C 4.085938 -11.359375 4.0625 -10.96875 4.0625 -10.5625 L 4.0625 0 Z M 2.109375 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-59">
-<path style="stroke:none;" d="M 7.90625 0.25 C 7.351562 0.25 6.796875 0.195312 6.234375 0.09375 C 5.679688 -0.0078125 5.160156 -0.195312 4.671875 -0.46875 C 4.179688 -0.75 3.75 -1.101562 3.375 -1.53125 C 3 -1.957031 2.691406 -2.425781 2.453125 -2.9375 C 2.222656 -3.457031 2.066406 -4 1.984375 -4.5625 C 1.898438 -5.125 1.859375 -5.679688 1.859375 -6.234375 L 1.859375 -17.28125 C 1.859375 -17.832031 1.898438 -18.390625 1.984375 -18.953125 C 2.066406 -19.523438 2.222656 -20.066406 2.453125 -20.578125 C 2.691406 -21.085938 3 -21.554688 3.375 -21.984375 C 3.75 -22.410156 4.179688 -22.757812 4.671875 -23.03125 C 5.160156 -23.3125 5.679688 -23.503906 6.234375 -23.609375 C 6.796875 -23.722656 7.351562 -23.78125 7.90625 -23.78125 C 8.4375 -23.78125 8.957031 -23.734375 9.46875 -23.640625 C 9.976562 -23.554688 10.46875 -23.398438 10.9375 -23.171875 C 11.40625 -22.953125 11.835938 -22.65625 12.234375 -22.28125 C 12.628906 -21.914062 12.945312 -21.507812 13.1875 -21.0625 C 13.4375 -20.613281 13.625 -20.125 13.75 -19.59375 C 13.882812 -19.070312 13.953125 -18.554688 13.953125 -18.046875 C 13.953125 -18.023438 13.953125 -18.007812 13.953125 -18 C 13.953125 -17.988281 13.953125 -17.972656 13.953125 -17.953125 L 12 -17.953125 C 12 -17.972656 12 -17.988281 12 -18 C 12 -18.007812 12 -18.015625 12 -18.015625 C 12 -18.546875 11.90625 -19.070312 11.71875 -19.59375 C 11.539062 -20.125 11.257812 -20.578125 10.875 -20.953125 C 10.488281 -21.335938 10.03125 -21.609375 9.5 -21.765625 C 8.96875 -21.929688 8.4375 -22.015625 7.90625 -22.015625 C 7.519531 -22.015625 7.128906 -21.972656 6.734375 -21.890625 C 6.335938 -21.804688 5.972656 -21.65625 5.640625 -21.4375 C 5.316406 -21.226562 5.03125 -20.960938 4.78125 -20.640625 C 4.539062 -20.316406 4.34375 -19.96875 4.1875 -19.59375 C 4.039062 -19.226562 3.941406 -18.851562 3.890625 -18.46875 C 3.835938 -18.082031 3.8125 -17.6875 3.8125 -17.28125 L 3.8125 -6.234375 C 3.8125 -5.828125 3.835938 -5.429688 3.890625 -5.046875 C 3.941406 -4.671875 4.039062 -4.296875 4.1875 -3.921875 C 4.34375 -3.546875 4.539062 -3.195312 4.78125 -2.875 C 5.03125 -2.5625 5.316406 -2.296875 5.640625 -2.078125 C 5.972656 -1.867188 6.335938 -1.71875 6.734375 -1.625 C 7.128906 -1.539062 7.519531 -1.5 7.90625 -1.5 C 8.4375 -1.5 8.96875 -1.578125 9.5 -1.734375 C 10.03125 -1.898438 10.488281 -2.175781 10.875 -2.5625 C 11.257812 -2.945312 11.539062 -3.398438 11.71875 -3.921875 C 11.90625 -4.441406 12 -4.96875 12 -5.5 C 12 -5.5 12 -5.503906 12 -5.515625 C 12 -5.523438 12 -5.539062 12 -5.5625 L 13.953125 -5.5625 C 13.953125 -5.539062 13.953125 -5.523438 13.953125 -5.515625 C 13.953125 -5.503906 13.953125 -5.488281 13.953125 -5.46875 C 13.953125 -4.957031 13.882812 -4.441406 13.75 -3.921875 C 13.625 -3.398438 13.4375 -2.914062 13.1875 -2.46875 C 12.945312 -2.019531 12.628906 -1.609375 12.234375 -1.234375 C 11.835938 -0.859375 11.40625 -0.554688 10.9375 -0.328125 C 10.46875 -0.109375 9.976562 0.0390625 9.46875 0.125 C 8.957031 0.207031 8.4375 0.25 7.90625 0.25 Z M 7.90625 0.25 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-60">
-<path style="stroke:none;" d="M 5.125 6.5625 L 5.125 6.34375 C 5.125 5.800781 5.148438 5.265625 5.203125 4.734375 C 5.253906 4.203125 5.375 3.679688 5.5625 3.171875 L 6.96875 -0.421875 L 3.234375 -10.078125 C 3.085938 -10.441406 2.945312 -10.8125 2.8125 -11.1875 C 2.6875 -11.5625 2.578125 -11.929688 2.484375 -12.296875 C 2.390625 -12.671875 2.300781 -13.050781 2.21875 -13.4375 C 2.144531 -13.820312 2.109375 -14.21875 2.109375 -14.625 L 2.109375 -16.953125 L 4.0625 -16.953125 L 4.0625 -14.625 C 4.0625 -14.28125 4.09375 -13.941406 4.15625 -13.609375 C 4.21875 -13.285156 4.289062 -12.957031 4.375 -12.625 C 4.46875 -12.289062 4.570312 -11.960938 4.6875 -11.640625 C 4.800781 -11.328125 4.914062 -11.019531 5.03125 -10.71875 L 7.96875 -2.9375 L 10.96875 -10.71875 C 11.101562 -11.039062 11.222656 -11.351562 11.328125 -11.65625 C 11.429688 -11.96875 11.523438 -12.289062 11.609375 -12.625 C 11.703125 -12.957031 11.78125 -13.285156 11.84375 -13.609375 C 11.90625 -13.941406 11.9375 -14.28125 11.9375 -14.625 L 11.9375 -16.953125 L 13.890625 -16.953125 L 13.890625 -14.625 C 13.890625 -14.21875 13.847656 -13.820312 13.765625 -13.4375 C 13.691406 -13.050781 13.609375 -12.671875 13.515625 -12.296875 C 13.421875 -11.929688 13.304688 -11.5625 13.171875 -11.1875 C 13.046875 -10.8125 12.910156 -10.441406 12.765625 -10.078125 L 7.390625 3.8125 C 7.242188 4.21875 7.148438 4.632812 7.109375 5.0625 C 7.066406 5.488281 7.046875 5.914062 7.046875 6.34375 L 7.046875 6.5625 Z M 5.125 6.5625 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-61">
-<path style="stroke:none;" d="M 3.203125 0 L 3.203125 -23.515625 L 5.125 -23.515625 L 5.125 -1.765625 L 14.140625 -1.765625 L 14.140625 0 Z M 3.203125 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-62">
-<path style="stroke:none;" d="M 5.734375 -2.296875 C 5.085938 -3.671875 4.320312 -4.960938 3.4375 -6.171875 C 2.550781 -7.390625 1.554688 -8.507812 0.453125 -9.53125 L 1.65625 -10.71875 C 2.625 -9.78125 3.503906 -8.785156 4.296875 -7.734375 C 5.097656 -6.691406 5.8125 -5.597656 6.4375 -4.453125 C 7.375 -7.023438 8.5 -9.53125 9.8125 -11.96875 C 11.125 -14.414062 12.597656 -16.75 14.234375 -18.96875 L 15.640625 -18.015625 C 13.867188 -15.648438 12.296875 -13.144531 10.921875 -10.5 C 9.546875 -7.851562 8.398438 -5.117188 7.484375 -2.296875 Z M 5.734375 -2.296875 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-63">
-<path style="stroke:none;" d="M 7.046875 0 L 7.046875 -21.765625 L 1.734375 -21.765625 L 1.734375 -23.515625 L 14.265625 -23.515625 L 14.265625 -21.765625 L 8.953125 -21.765625 L 8.953125 0 Z M 7.046875 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-64">
-<path style="stroke:none;" d="M 2.109375 0 L 2.109375 -23.515625 L 4.0625 -23.515625 L 4.0625 -8.671875 C 4.320312 -8.859375 4.570312 -9.046875 4.8125 -9.234375 C 5.0625 -9.429688 5.304688 -9.628906 5.546875 -9.828125 C 5.796875 -10.035156 6.035156 -10.238281 6.265625 -10.4375 C 6.503906 -10.644531 6.742188 -10.851562 6.984375 -11.0625 C 7.234375 -11.28125 7.46875 -11.492188 7.6875 -11.703125 C 7.914062 -11.921875 8.140625 -12.140625 8.359375 -12.359375 C 8.585938 -12.585938 8.8125 -12.816406 9.03125 -13.046875 C 9.257812 -13.285156 9.476562 -13.519531 9.6875 -13.75 C 9.90625 -13.988281 10.113281 -14.226562 10.3125 -14.46875 C 10.519531 -14.71875 10.707031 -14.96875 10.875 -15.21875 C 11.050781 -15.476562 11.21875 -15.753906 11.375 -16.046875 C 11.53125 -16.335938 11.609375 -16.640625 11.609375 -16.953125 L 13.890625 -16.953125 C 13.890625 -16.640625 13.816406 -16.335938 13.671875 -16.046875 C 13.535156 -15.753906 13.378906 -15.472656 13.203125 -15.203125 C 13.035156 -14.941406 12.859375 -14.679688 12.671875 -14.421875 C 12.484375 -14.171875 12.285156 -13.921875 12.078125 -13.671875 C 11.878906 -13.429688 11.675781 -13.191406 11.46875 -12.953125 C 11.269531 -12.722656 11.054688 -12.492188 10.828125 -12.265625 C 10.609375 -12.046875 10.382812 -11.820312 10.15625 -11.59375 C 9.9375 -11.375 9.710938 -11.15625 9.484375 -10.9375 C 9.265625 -10.726562 9.035156 -10.515625 8.796875 -10.296875 C 8.566406 -10.085938 8.332031 -9.875 8.09375 -9.65625 C 8.414062 -9.300781 8.726562 -8.9375 9.03125 -8.5625 C 9.34375 -8.1875 9.648438 -7.8125 9.953125 -7.4375 C 10.253906 -7.0625 10.546875 -6.679688 10.828125 -6.296875 C 11.117188 -5.921875 11.398438 -5.535156 11.671875 -5.140625 C 11.953125 -4.742188 12.222656 -4.347656 12.484375 -3.953125 C 12.753906 -3.554688 13 -3.144531 13.21875 -2.71875 C 13.445312 -2.289062 13.65625 -1.851562 13.84375 -1.40625 C 14.039062 -0.957031 14.140625 -0.488281 14.140625 0 L 12.1875 0 C 12.1875 -0.425781 12.101562 -0.835938 11.9375 -1.234375 C 11.769531 -1.628906 11.582031 -2.007812 11.375 -2.375 C 11.175781 -2.75 10.957031 -3.117188 10.71875 -3.484375 C 10.488281 -3.847656 10.242188 -4.203125 9.984375 -4.546875 C 9.722656 -4.890625 9.460938 -5.222656 9.203125 -5.546875 C 8.953125 -5.878906 8.6875 -6.210938 8.40625 -6.546875 C 8.132812 -6.878906 7.859375 -7.203125 7.578125 -7.515625 C 7.304688 -7.835938 7.03125 -8.160156 6.75 -8.484375 C 6.300781 -8.117188 5.851562 -7.757812 5.40625 -7.40625 C 4.957031 -7.050781 4.507812 -6.707031 4.0625 -6.375 L 4.0625 0 Z M 2.109375 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-65">
-<path style="stroke:none;" d="M 2.109375 6.5625 L 2.109375 4.796875 L 13.890625 4.796875 L 13.890625 6.5625 Z M 2.109375 6.5625 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-66">
-<path style="stroke:none;" d="M 4.25 0 L 4.25 -1.765625 L 7.046875 -1.765625 L 7.046875 -21.765625 L 4.25 -21.765625 L 4.25 -23.515625 L 11.75 -23.515625 L 11.75 -21.765625 L 8.953125 -21.765625 L 8.953125 -1.765625 L 11.75 -1.765625 L 11.75 0 Z M 4.25 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-67">
-<path style="stroke:none;" d="M 1.796875 0 L 1.796875 -16.953125 L 3.71875 -16.953125 L 3.71875 -15.609375 C 3.820312 -15.847656 3.953125 -16.066406 4.109375 -16.265625 C 4.273438 -16.472656 4.457031 -16.648438 4.65625 -16.796875 C 4.851562 -16.953125 5.082031 -17.0625 5.34375 -17.125 C 5.601562 -17.1875 5.859375 -17.21875 6.109375 -17.21875 C 6.410156 -17.21875 6.707031 -17.160156 7 -17.046875 C 7.300781 -16.941406 7.566406 -16.785156 7.796875 -16.578125 C 8.035156 -16.378906 8.226562 -16.140625 8.375 -15.859375 C 8.53125 -15.585938 8.648438 -15.304688 8.734375 -15.015625 C 8.816406 -15.304688 8.929688 -15.585938 9.078125 -15.859375 C 9.234375 -16.140625 9.421875 -16.378906 9.640625 -16.578125 C 9.867188 -16.785156 10.128906 -16.941406 10.421875 -17.046875 C 10.722656 -17.160156 11.035156 -17.21875 11.359375 -17.21875 C 11.660156 -17.21875 11.960938 -17.160156 12.265625 -17.046875 C 12.578125 -16.941406 12.84375 -16.78125 13.0625 -16.5625 C 13.289062 -16.351562 13.476562 -16.109375 13.625 -15.828125 C 13.78125 -15.554688 13.898438 -15.265625 13.984375 -14.953125 C 14.066406 -14.648438 14.125 -14.34375 14.15625 -14.03125 C 14.1875 -13.71875 14.203125 -13.40625 14.203125 -13.09375 L 14.203125 0 L 12.28125 0 L 12.28125 -13.09375 C 12.28125 -13.363281 12.253906 -13.628906 12.203125 -13.890625 C 12.148438 -14.160156 12.0625 -14.414062 11.9375 -14.65625 C 11.8125 -14.894531 11.628906 -15.085938 11.390625 -15.234375 C 11.160156 -15.378906 10.90625 -15.453125 10.625 -15.453125 C 10.34375 -15.453125 10.082031 -15.378906 9.84375 -15.234375 C 9.613281 -15.085938 9.4375 -14.894531 9.3125 -14.65625 C 9.1875 -14.414062 9.09375 -14.160156 9.03125 -13.890625 C 8.976562 -13.628906 8.953125 -13.363281 8.953125 -13.09375 L 8.953125 0 L 7.046875 0 L 7.046875 -13.09375 C 7.046875 -13.363281 7.015625 -13.628906 6.953125 -13.890625 C 6.898438 -14.160156 6.8125 -14.414062 6.6875 -14.65625 C 6.5625 -14.894531 6.378906 -15.085938 6.140625 -15.234375 C 5.910156 -15.378906 5.65625 -15.453125 5.375 -15.453125 C 5.09375 -15.453125 4.832031 -15.378906 4.59375 -15.234375 C 4.363281 -15.085938 4.1875 -14.894531 4.0625 -14.65625 C 3.9375 -14.414062 3.847656 -14.160156 3.796875 -13.890625 C 3.742188 -13.628906 3.71875 -13.363281 3.71875 -13.09375 L 3.71875 0 Z M 1.796875 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-68">
-<path style="stroke:none;" d="M 12.703125 5.15625 C 11.785156 4.644531 10.925781 4.039062 10.125 3.34375 C 9.320312 2.644531 8.601562 1.875 7.96875 1.03125 C 7.332031 0.195312 6.785156 -0.695312 6.328125 -1.65625 C 5.878906 -2.625 5.515625 -3.613281 5.234375 -4.625 C 4.960938 -5.632812 4.769531 -6.664062 4.65625 -7.71875 C 4.539062 -8.78125 4.484375 -9.832031 4.484375 -10.875 C 4.484375 -11.925781 4.539062 -12.976562 4.65625 -14.03125 C 4.769531 -15.082031 4.960938 -16.113281 5.234375 -17.125 C 5.515625 -18.144531 5.878906 -19.132812 6.328125 -20.09375 C 6.785156 -21.050781 7.332031 -21.953125 7.96875 -22.796875 C 8.601562 -23.640625 9.320312 -24.40625 10.125 -25.09375 C 10.925781 -25.789062 11.785156 -26.394531 12.703125 -26.90625 L 13.65625 -25.375 C 12.425781 -24.664062 11.328125 -23.773438 10.359375 -22.703125 C 9.398438 -21.628906 8.625 -20.453125 8.03125 -19.171875 C 7.4375 -17.890625 7.019531 -16.535156 6.78125 -15.109375 C 6.550781 -13.691406 6.4375 -12.28125 6.4375 -10.875 C 6.4375 -9.46875 6.550781 -8.054688 6.78125 -6.640625 C 7.019531 -5.222656 7.4375 -3.875 8.03125 -2.59375 C 8.625 -1.3125 9.398438 -0.132812 10.359375 0.9375 C 11.328125 2.019531 12.425781 2.910156 13.65625 3.609375 Z M 12.703125 5.15625 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-69">
-<path style="stroke:none;" d="M 3.296875 5.15625 L 2.34375 3.609375 C 3.570312 2.910156 4.664062 2.019531 5.625 0.9375 C 6.59375 -0.132812 7.375 -1.3125 7.96875 -2.59375 C 8.5625 -3.875 8.972656 -5.222656 9.203125 -6.640625 C 9.441406 -8.054688 9.5625 -9.46875 9.5625 -10.875 C 9.5625 -12.28125 9.441406 -13.691406 9.203125 -15.109375 C 8.972656 -16.535156 8.5625 -17.890625 7.96875 -19.171875 C 7.375 -20.453125 6.59375 -21.628906 5.625 -22.703125 C 4.664062 -23.773438 3.570312 -24.664062 2.34375 -25.375 L 3.296875 -26.90625 C 4.210938 -26.394531 5.070312 -25.789062 5.875 -25.09375 C 6.675781 -24.40625 7.394531 -23.640625 8.03125 -22.796875 C 8.664062 -21.953125 9.207031 -21.050781 9.65625 -20.09375 C 10.113281 -19.132812 10.476562 -18.144531 10.75 -17.125 C 11.03125 -16.113281 11.226562 -15.082031 11.34375 -14.03125 C 11.457031 -12.976562 11.515625 -11.925781 11.515625 -10.875 C 11.515625 -9.832031 11.457031 -8.78125 11.34375 -7.71875 C 11.226562 -6.664062 11.03125 -5.632812 10.75 -4.625 C 10.476562 -3.613281 10.113281 -2.625 9.65625 -1.65625 C 9.207031 -0.695312 8.664062 0.195312 8.03125 1.03125 C 7.394531 1.875 6.675781 2.644531 5.875 3.34375 C 5.070312 4.039062 4.210938 4.644531 3.296875 5.15625 Z M 3.296875 5.15625 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-70">
-<path style="stroke:none;" d="M 8 0.25 C 7.445312 0.25 6.882812 0.195312 6.3125 0.09375 C 5.75 -0.0078125 5.222656 -0.195312 4.734375 -0.46875 C 4.242188 -0.75 3.804688 -1.101562 3.421875 -1.53125 C 3.035156 -1.957031 2.722656 -2.425781 2.484375 -2.9375 C 2.253906 -3.457031 2.09375 -4 2 -4.5625 C 1.90625 -5.125 1.859375 -5.679688 1.859375 -6.234375 L 1.859375 -17.28125 C 1.859375 -17.851562 1.90625 -18.414062 2 -18.96875 C 2.09375 -19.53125 2.253906 -20.066406 2.484375 -20.578125 C 2.722656 -21.085938 3.035156 -21.554688 3.421875 -21.984375 C 3.804688 -22.410156 4.242188 -22.757812 4.734375 -23.03125 C 5.222656 -23.3125 5.742188 -23.519531 6.296875 -23.65625 C 6.859375 -23.800781 7.425781 -23.875 8 -23.875 C 8.570312 -23.875 9.132812 -23.800781 9.6875 -23.65625 C 10.25 -23.519531 10.773438 -23.3125 11.265625 -23.03125 C 11.753906 -22.757812 12.191406 -22.410156 12.578125 -21.984375 C 12.960938 -21.554688 13.269531 -21.085938 13.5 -20.578125 C 13.738281 -20.066406 13.90625 -19.53125 14 -18.96875 C 14.09375 -18.414062 14.140625 -17.851562 14.140625 -17.28125 L 14.140625 -6.234375 C 14.140625 -5.679688 14.09375 -5.125 14 -4.5625 C 13.90625 -4 13.738281 -3.457031 13.5 -2.9375 C 13.269531 -2.425781 12.960938 -1.957031 12.578125 -1.53125 C 12.191406 -1.101562 11.753906 -0.75 11.265625 -0.46875 C 10.773438 -0.195312 10.242188 -0.0078125 9.671875 0.09375 C 9.109375 0.195312 8.550781 0.25 8 0.25 Z M 8 -1.5 C 8.40625 -1.5 8.804688 -1.539062 9.203125 -1.625 C 9.597656 -1.71875 9.960938 -1.867188 10.296875 -2.078125 C 10.640625 -2.296875 10.929688 -2.554688 11.171875 -2.859375 C 11.421875 -3.171875 11.625 -3.515625 11.78125 -3.890625 C 11.945312 -4.265625 12.054688 -4.644531 12.109375 -5.03125 C 12.160156 -5.425781 12.1875 -5.828125 12.1875 -6.234375 L 12.1875 -17.28125 C 12.1875 -17.6875 12.160156 -18.085938 12.109375 -18.484375 C 12.054688 -18.878906 11.945312 -19.257812 11.78125 -19.625 C 11.625 -20 11.414062 -20.347656 11.15625 -20.671875 C 10.90625 -20.992188 10.601562 -21.253906 10.25 -21.453125 C 9.90625 -21.660156 9.535156 -21.804688 9.140625 -21.890625 C 8.742188 -21.972656 8.34375 -22.015625 7.9375 -22.015625 C 7.550781 -22.015625 7.160156 -21.96875 6.765625 -21.875 C 6.367188 -21.78125 6.003906 -21.625 5.671875 -21.40625 C 5.347656 -21.195312 5.054688 -20.9375 4.796875 -20.625 C 4.546875 -20.3125 4.34375 -19.96875 4.1875 -19.59375 C 4.039062 -19.226562 3.941406 -18.851562 3.890625 -18.46875 C 3.835938 -18.082031 3.8125 -17.6875 3.8125 -17.28125 L 3.8125 -6.234375 C 3.8125 -5.828125 3.835938 -5.425781 3.890625 -5.03125 C 3.941406 -4.644531 4.046875 -4.265625 4.203125 -3.890625 C 4.367188 -3.515625 4.570312 -3.171875 4.8125 -2.859375 C 5.0625 -2.554688 5.351562 -2.296875 5.6875 -2.078125 C 6.03125 -1.867188 6.398438 -1.71875 6.796875 -1.625 C 7.191406 -1.539062 7.59375 -1.5 8 -1.5 Z M 8 -1.5 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-71">
-<path style="stroke:none;" d="M 2.109375 0 L 2.109375 -23.515625 L 4.0625 -23.515625 L 4.0625 -11.546875 C 4.382812 -11.890625 4.703125 -12.242188 5.015625 -12.609375 C 5.335938 -12.972656 5.65625 -13.335938 5.96875 -13.703125 C 6.28125 -14.078125 6.582031 -14.453125 6.875 -14.828125 C 7.175781 -15.203125 7.46875 -15.582031 7.75 -15.96875 C 8.039062 -16.351562 8.328125 -16.738281 8.609375 -17.125 C 8.890625 -17.507812 9.160156 -17.898438 9.421875 -18.296875 C 9.691406 -18.703125 9.945312 -19.113281 10.1875 -19.53125 C 10.4375 -19.945312 10.664062 -20.367188 10.875 -20.796875 C 11.09375 -21.222656 11.289062 -21.660156 11.46875 -22.109375 C 11.65625 -22.554688 11.75 -23.023438 11.75 -23.515625 L 13.890625 -23.515625 C 13.890625 -23.023438 13.796875 -22.546875 13.609375 -22.078125 C 13.429688 -21.609375 13.234375 -21.148438 13.015625 -20.703125 C 12.804688 -20.253906 12.578125 -19.816406 12.328125 -19.390625 C 12.085938 -18.960938 11.832031 -18.535156 11.5625 -18.109375 C 11.300781 -17.679688 11.023438 -17.265625 10.734375 -16.859375 C 10.441406 -16.453125 10.144531 -16.050781 9.84375 -15.65625 C 9.550781 -15.269531 9.253906 -14.878906 8.953125 -14.484375 C 8.660156 -14.085938 8.351562 -13.695312 8.03125 -13.3125 C 8.375 -12.800781 8.703125 -12.28125 9.015625 -11.75 C 9.335938 -11.226562 9.65625 -10.703125 9.96875 -10.171875 C 10.28125 -9.640625 10.582031 -9.101562 10.875 -8.5625 C 11.175781 -8.019531 11.460938 -7.472656 11.734375 -6.921875 C 12.015625 -6.378906 12.285156 -5.832031 12.546875 -5.28125 C 12.816406 -4.726562 13.054688 -4.160156 13.265625 -3.578125 C 13.484375 -3.003906 13.679688 -2.414062 13.859375 -1.8125 C 14.046875 -1.21875 14.140625 -0.613281 14.140625 0 L 12.1875 0 C 12.1875 -0.550781 12.113281 -1.085938 11.96875 -1.609375 C 11.820312 -2.140625 11.648438 -2.660156 11.453125 -3.171875 C 11.265625 -3.679688 11.050781 -4.1875 10.8125 -4.6875 C 10.582031 -5.1875 10.34375 -5.679688 10.09375 -6.171875 C 9.851562 -6.660156 9.597656 -7.140625 9.328125 -7.609375 C 9.054688 -8.078125 8.78125 -8.550781 8.5 -9.03125 C 8.226562 -9.519531 7.945312 -9.992188 7.65625 -10.453125 C 7.375 -10.910156 7.082031 -11.375 6.78125 -11.84375 C 6.351562 -11.332031 5.910156 -10.820312 5.453125 -10.3125 C 4.992188 -9.8125 4.53125 -9.316406 4.0625 -8.828125 L 4.0625 0 Z M 2.109375 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-72">
-<path style="stroke:none;" d="M 7.015625 6.5625 L 5.46875 5.46875 C 6.113281 4.695312 6.679688 3.875 7.171875 3 C 7.660156 2.132812 8.007812 1.21875 8.21875 0.25 C 8.175781 0.25 8.132812 0.25 8.09375 0.25 C 8.050781 0.25 8.007812 0.25 7.96875 0.25 C 7.6875 0.25 7.40625 0.207031 7.125 0.125 C 6.851562 0.0390625 6.617188 -0.0976562 6.421875 -0.296875 C 6.234375 -0.503906 6.097656 -0.742188 6.015625 -1.015625 C 5.929688 -1.296875 5.890625 -1.578125 5.890625 -1.859375 C 5.890625 -2.148438 5.929688 -2.4375 6.015625 -2.71875 C 6.097656 -3 6.238281 -3.238281 6.4375 -3.4375 C 6.644531 -3.644531 6.882812 -3.789062 7.15625 -3.875 C 7.4375 -3.957031 7.71875 -4 8 -4 C 8.300781 -4 8.59375 -3.953125 8.875 -3.859375 C 9.164062 -3.765625 9.40625 -3.609375 9.59375 -3.390625 C 9.789062 -3.179688 9.925781 -2.929688 10 -2.640625 C 10.070312 -2.347656 10.109375 -2.054688 10.109375 -1.765625 C 10.109375 -1.015625 10.023438 -0.269531 9.859375 0.46875 C 9.703125 1.21875 9.488281 1.941406 9.21875 2.640625 C 8.957031 3.335938 8.632812 4.015625 8.25 4.671875 C 7.875 5.328125 7.460938 5.957031 7.015625 6.5625 Z M 7.015625 6.5625 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-73">
-<path style="stroke:none;" d="M 1.796875 0 L 1.796875 -23.515625 L 4.546875 -23.515625 L 8 -10.4375 L 11.453125 -23.515625 L 14.203125 -23.515625 L 14.203125 0 L 12.28125 0 L 12.28125 -19.84375 L 8.890625 -7.078125 L 7.109375 -7.078125 L 3.71875 -19.84375 L 3.71875 0 Z M 1.796875 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-74">
-<path style="stroke:none;" d="M 2.65625 0 L 2.65625 -23.515625 L 8.21875 -23.515625 C 8.800781 -23.515625 9.375 -23.457031 9.9375 -23.34375 C 10.5 -23.226562 11.03125 -23.023438 11.53125 -22.734375 C 12.03125 -22.441406 12.472656 -22.070312 12.859375 -21.625 C 13.242188 -21.175781 13.550781 -20.6875 13.78125 -20.15625 C 14.019531 -19.625 14.179688 -19.066406 14.265625 -18.484375 C 14.359375 -17.910156 14.40625 -17.335938 14.40625 -16.765625 C 14.40625 -16.191406 14.359375 -15.613281 14.265625 -15.03125 C 14.179688 -14.457031 14.019531 -13.910156 13.78125 -13.390625 C 13.550781 -12.867188 13.242188 -12.382812 12.859375 -11.9375 C 12.472656 -11.488281 12.03125 -11.117188 11.53125 -10.828125 C 11.03125 -10.546875 10.5 -10.34375 9.9375 -10.21875 C 9.375 -10.101562 8.800781 -10.046875 8.21875 -10.046875 L 4.609375 -10.046875 L 4.609375 0 Z M 4.609375 -11.8125 L 8.21875 -11.8125 C 8.625 -11.8125 9.03125 -11.859375 9.4375 -11.953125 C 9.84375 -12.046875 10.21875 -12.203125 10.5625 -12.421875 C 10.90625 -12.648438 11.203125 -12.929688 11.453125 -13.265625 C 11.710938 -13.597656 11.914062 -13.953125 12.0625 -14.328125 C 12.207031 -14.710938 12.304688 -15.113281 12.359375 -15.53125 C 12.421875 -15.945312 12.453125 -16.359375 12.453125 -16.765625 C 12.453125 -17.191406 12.421875 -17.609375 12.359375 -18.015625 C 12.304688 -18.421875 12.207031 -18.816406 12.0625 -19.203125 C 11.914062 -19.585938 11.710938 -19.945312 11.453125 -20.28125 C 11.203125 -20.625 10.90625 -20.90625 10.5625 -21.125 C 10.21875 -21.351562 9.84375 -21.515625 9.4375 -21.609375 C 9.03125 -21.710938 8.625 -21.765625 8.21875 -21.765625 L 4.609375 -21.765625 Z M 4.609375 -11.8125 "/>
-</symbol>
-<symbol overflow="visible" id="glyph0-75">
-<path style="stroke:none;" d="M 2.109375 0 L 2.109375 -2.375 C 2.109375 -3.5625 2.195312 -4.757812 2.375 -5.96875 C 2.5625 -7.175781 2.773438 -8.367188 3.015625 -9.546875 C 3.265625 -10.734375 3.535156 -11.90625 3.828125 -13.0625 C 4.128906 -14.226562 4.445312 -15.398438 4.78125 -16.578125 C 5.113281 -17.753906 5.457031 -18.914062 5.8125 -20.0625 C 6.164062 -21.21875 6.519531 -22.367188 6.875 -23.515625 L 9.125 -23.515625 C 9.476562 -22.367188 9.832031 -21.21875 10.1875 -20.0625 C 10.539062 -18.914062 10.882812 -17.753906 11.21875 -16.578125 C 11.550781 -15.398438 11.863281 -14.226562 12.15625 -13.0625 C 12.457031 -11.90625 12.726562 -10.734375 12.96875 -9.546875 C 13.21875 -8.367188 13.429688 -7.175781 13.609375 -5.96875 C 13.796875 -4.757812 13.890625 -3.5625 13.890625 -2.375 L 13.890625 0 L 11.9375 0 L 11.9375 -2.375 C 11.9375 -3.09375 11.898438 -3.8125 11.828125 -4.53125 C 11.753906 -5.257812 11.660156 -5.988281 11.546875 -6.71875 L 4.453125 -6.71875 C 4.335938 -5.988281 4.242188 -5.257812 4.171875 -4.53125 C 4.097656 -3.8125 4.0625 -3.09375 4.0625 -2.375 L 4.0625 0 Z M 4.765625 -8.484375 L 11.234375 -8.484375 C 10.847656 -10.640625 10.367188 -12.773438 9.796875 -14.890625 C 9.234375 -17.015625 8.632812 -19.132812 8 -21.25 C 7.363281 -19.132812 6.757812 -17.015625 6.1875 -14.890625 C 5.625 -12.773438 5.148438 -10.640625 4.765625 -8.484375 Z M 4.765625 -8.484375 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-0">
-<path style="stroke:none;" d="M 1.890625 0 L 1.890625 -23.515625 L 14.109375 -23.515625 L 14.109375 0 Z M 2.9375 -10.59375 L 7.578125 -16.390625 L 12.453125 -22.46875 L 9.4375 -22.46875 L 2.9375 -14.34375 Z M 2.9375 -15.578125 L 6.078125 -19.484375 L 8.453125 -22.46875 L 5.4375 -22.46875 L 2.9375 -19.328125 Z M 2.9375 -20.578125 L 4.453125 -22.46875 L 2.9375 -22.46875 Z M 2.9375 -5.5625 L 10.40625 -14.90625 L 13.0625 -18.203125 L 13.0625 -21.953125 L 2.9375 -9.34375 Z M 2.9375 -1.0625 L 3.328125 -1.0625 L 13.0625 -13.1875 L 13.0625 -16.953125 L 2.9375 -4.3125 Z M 4.359375 -1.0625 L 7.328125 -1.0625 L 13.0625 -8.1875 L 13.0625 -11.9375 Z M 8.359375 -1.0625 L 11.328125 -1.0625 L 13.0625 -3.203125 L 13.0625 -6.9375 Z M 13.0625 -1.0625 L 13.0625 -1.953125 L 12.359375 -1.0625 Z M 13.0625 -1.0625 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-1">
-<path style="stroke:none;" d="M 2.375 0 L 2.375 -23.515625 L 8.15625 -23.515625 C 8.757812 -23.515625 9.363281 -23.453125 9.96875 -23.328125 C 10.570312 -23.203125 11.132812 -22.984375 11.65625 -22.671875 C 12.1875 -22.359375 12.644531 -21.960938 13.03125 -21.484375 C 13.425781 -21.003906 13.75 -20.488281 14 -19.9375 C 14.25 -19.382812 14.414062 -18.800781 14.5 -18.1875 C 14.582031 -17.582031 14.625 -16.972656 14.625 -16.359375 C 14.625 -15.734375 14.582031 -15.113281 14.5 -14.5 C 14.414062 -13.894531 14.25 -13.316406 14 -12.765625 C 13.75 -12.210938 13.425781 -11.695312 13.03125 -11.21875 C 12.644531 -10.738281 12.1875 -10.34375 11.65625 -10.03125 C 11.132812 -9.71875 10.570312 -9.5 9.96875 -9.375 C 9.363281 -9.25 8.757812 -9.1875 8.15625 -9.1875 L 5.921875 -9.1875 L 5.921875 0 Z M 5.921875 -12.25 L 8.15625 -12.25 C 8.476562 -12.25 8.785156 -12.296875 9.078125 -12.390625 C 9.378906 -12.492188 9.644531 -12.648438 9.875 -12.859375 C 10.113281 -13.078125 10.304688 -13.320312 10.453125 -13.59375 C 10.609375 -13.875 10.734375 -14.164062 10.828125 -14.46875 C 10.921875 -14.78125 10.984375 -15.09375 11.015625 -15.40625 C 11.054688 -15.71875 11.078125 -16.035156 11.078125 -16.359375 C 11.078125 -16.671875 11.054688 -16.984375 11.015625 -17.296875 C 10.984375 -17.609375 10.921875 -17.914062 10.828125 -18.21875 C 10.734375 -18.53125 10.609375 -18.820312 10.453125 -19.09375 C 10.304688 -19.375 10.113281 -19.617188 9.875 -19.828125 C 9.644531 -20.046875 9.378906 -20.203125 9.078125 -20.296875 C 8.785156 -20.398438 8.476562 -20.453125 8.15625 -20.453125 L 5.921875 -20.453125 Z M 5.921875 -12.25 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-2">
-<path style="stroke:none;" d="M 2.40625 0 L 2.40625 -3.078125 L 6.4375 -3.078125 L 6.4375 -13.890625 L 2.84375 -13.890625 L 2.84375 -16.953125 L 10.015625 -16.953125 L 10.015625 -3.078125 L 13.59375 -3.078125 L 13.59375 0 Z M 8 -19.515625 C 7.65625 -19.515625 7.3125 -19.566406 6.96875 -19.671875 C 6.632812 -19.785156 6.347656 -19.957031 6.109375 -20.1875 C 5.878906 -20.425781 5.707031 -20.710938 5.59375 -21.046875 C 5.488281 -21.390625 5.4375 -21.734375 5.4375 -22.078125 C 5.4375 -22.421875 5.488281 -22.757812 5.59375 -23.09375 C 5.707031 -23.4375 5.878906 -23.722656 6.109375 -23.953125 C 6.347656 -24.191406 6.632812 -24.363281 6.96875 -24.46875 C 7.3125 -24.582031 7.65625 -24.640625 8 -24.640625 C 8.34375 -24.640625 8.679688 -24.582031 9.015625 -24.46875 C 9.359375 -24.363281 9.644531 -24.191406 9.875 -23.953125 C 10.113281 -23.722656 10.285156 -23.4375 10.390625 -23.09375 C 10.503906 -22.757812 10.5625 -22.421875 10.5625 -22.078125 C 10.5625 -21.734375 10.503906 -21.390625 10.390625 -21.046875 C 10.285156 -20.710938 10.113281 -20.425781 9.875 -20.1875 C 9.644531 -19.957031 9.359375 -19.785156 9.015625 -19.671875 C 8.679688 -19.566406 8.34375 -19.515625 8 -19.515625 Z M 8 -19.515625 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-3">
-<path style="stroke:none;" d="M 8 6.8125 C 7.445312 6.8125 6.894531 6.773438 6.34375 6.703125 C 5.800781 6.628906 5.273438 6.492188 4.765625 6.296875 C 4.253906 6.109375 3.773438 5.847656 3.328125 5.515625 C 2.878906 5.191406 2.5 4.804688 2.1875 4.359375 C 1.882812 3.910156 1.664062 3.410156 1.53125 2.859375 C 1.40625 2.316406 1.34375 1.78125 1.34375 1.25 C 1.34375 0.582031 1.453125 -0.0625 1.671875 -0.6875 C 1.898438 -1.3125 2.25 -1.847656 2.71875 -2.296875 C 2.46875 -2.617188 2.273438 -2.972656 2.140625 -3.359375 C 2.015625 -3.742188 1.953125 -4.140625 1.953125 -4.546875 C 1.953125 -5.078125 2.054688 -5.597656 2.265625 -6.109375 C 2.484375 -6.617188 2.773438 -7.066406 3.140625 -7.453125 C 2.609375 -7.984375 2.222656 -8.613281 1.984375 -9.34375 C 1.742188 -10.070312 1.625 -10.804688 1.625 -11.546875 C 1.625 -12.085938 1.675781 -12.617188 1.78125 -13.140625 C 1.894531 -13.660156 2.078125 -14.148438 2.328125 -14.609375 C 2.585938 -15.066406 2.921875 -15.460938 3.328125 -15.796875 C 3.734375 -16.140625 4.175781 -16.414062 4.65625 -16.625 C 5.132812 -16.84375 5.632812 -17.003906 6.15625 -17.109375 C 6.675781 -17.222656 7.195312 -17.28125 7.71875 -17.28125 C 8.050781 -17.28125 8.382812 -17.257812 8.71875 -17.21875 C 9.050781 -17.175781 9.378906 -17.113281 9.703125 -17.03125 L 9.921875 -16.953125 L 14.84375 -16.953125 L 14.84375 -13.890625 L 13.40625 -13.953125 C 13.550781 -13.566406 13.648438 -13.171875 13.703125 -12.765625 C 13.765625 -12.359375 13.796875 -11.953125 13.796875 -11.546875 C 13.796875 -11.015625 13.742188 -10.492188 13.640625 -9.984375 C 13.546875 -9.472656 13.363281 -8.988281 13.09375 -8.53125 C 12.832031 -8.070312 12.503906 -7.664062 12.109375 -7.3125 C 11.710938 -6.957031 11.273438 -6.671875 10.796875 -6.453125 C 10.316406 -6.242188 9.816406 -6.097656 9.296875 -6.015625 C 8.773438 -5.929688 8.25 -5.890625 7.71875 -5.890625 C 7.351562 -5.890625 6.988281 -5.910156 6.625 -5.953125 C 6.257812 -5.992188 5.90625 -6.054688 5.5625 -6.140625 C 5.4375 -6.015625 5.347656 -5.863281 5.296875 -5.6875 C 5.242188 -5.519531 5.21875 -5.351562 5.21875 -5.1875 C 5.21875 -4.988281 5.257812 -4.789062 5.34375 -4.59375 C 5.425781 -4.40625 5.539062 -4.25 5.6875 -4.125 C 5.84375 -4 6.015625 -3.910156 6.203125 -3.859375 C 6.398438 -3.804688 6.601562 -3.769531 6.8125 -3.75 C 6.8125 -3.75 6.816406 -3.75 6.828125 -3.75 C 6.835938 -3.75 6.851562 -3.75 6.875 -3.75 C 6.914062 -3.75 6.96875 -3.75 7.03125 -3.75 C 7.101562 -3.75 7.160156 -3.75 7.203125 -3.75 L 8.796875 -3.75 C 9.285156 -3.75 9.773438 -3.710938 10.265625 -3.640625 C 10.765625 -3.578125 11.242188 -3.457031 11.703125 -3.28125 C 12.160156 -3.101562 12.582031 -2.867188 12.96875 -2.578125 C 13.363281 -2.285156 13.695312 -1.929688 13.96875 -1.515625 C 14.238281 -1.097656 14.425781 -0.644531 14.53125 -0.15625 C 14.632812 0.332031 14.6875 0.820312 14.6875 1.3125 C 14.6875 1.84375 14.613281 2.378906 14.46875 2.921875 C 14.332031 3.472656 14.109375 3.96875 13.796875 4.40625 C 13.492188 4.84375 13.117188 5.222656 12.671875 5.546875 C 12.222656 5.878906 11.742188 6.132812 11.234375 6.3125 C 10.722656 6.5 10.191406 6.628906 9.640625 6.703125 C 9.097656 6.773438 8.550781 6.8125 8 6.8125 Z M 7.71875 -8.953125 C 8.050781 -8.953125 8.390625 -9.003906 8.734375 -9.109375 C 9.078125 -9.222656 9.363281 -9.40625 9.59375 -9.65625 C 9.832031 -9.914062 10 -10.210938 10.09375 -10.546875 C 10.1875 -10.878906 10.234375 -11.210938 10.234375 -11.546875 C 10.234375 -11.867188 10.191406 -12.1875 10.109375 -12.5 C 10.023438 -12.820312 9.875 -13.109375 9.65625 -13.359375 C 9.445312 -13.609375 9.1875 -13.796875 8.875 -13.921875 C 8.570312 -14.046875 8.257812 -14.117188 7.9375 -14.140625 L 7.71875 -14.140625 C 7.71875 -14.140625 7.710938 -14.140625 7.703125 -14.140625 C 7.691406 -14.140625 7.6875 -14.140625 7.6875 -14.140625 C 7.34375 -14.140625 7.003906 -14.085938 6.671875 -13.984375 C 6.335938 -13.878906 6.050781 -13.695312 5.8125 -13.4375 C 5.582031 -13.1875 5.421875 -12.894531 5.328125 -12.5625 C 5.234375 -12.226562 5.1875 -11.890625 5.1875 -11.546875 C 5.1875 -11.210938 5.238281 -10.878906 5.34375 -10.546875 C 5.445312 -10.210938 5.613281 -9.914062 5.84375 -9.65625 C 6.082031 -9.40625 6.367188 -9.222656 6.703125 -9.109375 C 7.035156 -9.003906 7.375 -8.953125 7.71875 -8.953125 Z M 8 3.75 C 8.382812 3.75 8.765625 3.707031 9.140625 3.625 C 9.515625 3.550781 9.851562 3.40625 10.15625 3.1875 C 10.46875 2.976562 10.707031 2.703125 10.875 2.359375 C 11.050781 2.023438 11.140625 1.675781 11.140625 1.3125 C 11.140625 1.007812 11.078125 0.71875 10.953125 0.4375 C 10.835938 0.164062 10.660156 -0.0507812 10.421875 -0.21875 C 10.191406 -0.394531 9.929688 -0.515625 9.640625 -0.578125 C 9.359375 -0.640625 9.078125 -0.671875 8.796875 -0.671875 L 7.203125 -0.671875 C 7.160156 -0.671875 7.117188 -0.671875 7.078125 -0.671875 C 7.035156 -0.671875 6.988281 -0.671875 6.9375 -0.671875 C 6.6875 -0.648438 6.429688 -0.601562 6.171875 -0.53125 C 5.921875 -0.457031 5.691406 -0.332031 5.484375 -0.15625 C 5.285156 0.0078125 5.140625 0.21875 5.046875 0.46875 C 4.960938 0.726562 4.921875 0.988281 4.921875 1.25 C 4.921875 1.632812 5 2 5.15625 2.34375 C 5.320312 2.695312 5.5625 2.976562 5.875 3.1875 C 6.1875 3.40625 6.523438 3.550781 6.890625 3.625 C 7.265625 3.707031 7.632812 3.75 8 3.75 Z M 8 3.75 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-4">
-<path style="stroke:none;" d="M 4.125 0 C 3.894531 -0.6875 3.675781 -1.378906 3.46875 -2.078125 C 3.269531 -2.785156 3.066406 -3.484375 2.859375 -4.171875 C 2.660156 -4.867188 2.46875 -5.566406 2.28125 -6.265625 C 2.101562 -6.972656 1.9375 -7.679688 1.78125 -8.390625 C 1.632812 -9.109375 1.507812 -9.820312 1.40625 -10.53125 C 1.300781 -11.25 1.25 -11.984375 1.25 -12.734375 L 1.25 -16.953125 L 4.828125 -16.953125 L 4.828125 -12.734375 C 4.828125 -11.109375 4.878906 -9.492188 4.984375 -7.890625 C 5.097656 -6.296875 5.21875 -4.703125 5.34375 -3.109375 L 7.015625 -10.171875 L 8.984375 -10.171875 L 10.65625 -3.109375 C 10.78125 -4.703125 10.894531 -6.296875 11 -7.890625 C 11.113281 -9.492188 11.171875 -11.109375 11.171875 -12.734375 L 11.171875 -16.953125 L 14.75 -16.953125 L 14.75 -12.734375 C 14.75 -11.984375 14.695312 -11.25 14.59375 -10.53125 C 14.488281 -9.820312 14.359375 -9.109375 14.203125 -8.390625 C 14.054688 -7.679688 13.890625 -6.972656 13.703125 -6.265625 C 13.523438 -5.566406 13.332031 -4.867188 13.125 -4.171875 C 12.925781 -3.484375 12.722656 -2.785156 12.515625 -2.078125 C 12.316406 -1.378906 12.101562 -0.6875 11.875 0 L 9.890625 0 L 8 -8.03125 L 6.109375 0 Z M 4.125 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-5">
-<path style="stroke:none;" d="M 8.0625 0.25 C 7.488281 0.25 6.90625 0.195312 6.3125 0.09375 C 5.726562 -0.0078125 5.179688 -0.195312 4.671875 -0.46875 C 4.160156 -0.75 3.703125 -1.109375 3.296875 -1.546875 C 2.890625 -1.984375 2.554688 -2.460938 2.296875 -2.984375 C 2.046875 -3.515625 1.867188 -4.070312 1.765625 -4.65625 C 1.671875 -5.238281 1.625 -5.820312 1.625 -6.40625 L 1.625 -10.5625 C 1.625 -11.132812 1.671875 -11.710938 1.765625 -12.296875 C 1.867188 -12.890625 2.039062 -13.445312 2.28125 -13.96875 C 2.53125 -14.488281 2.851562 -14.96875 3.25 -15.40625 C 3.644531 -15.84375 4.097656 -16.195312 4.609375 -16.46875 C 5.117188 -16.75 5.664062 -16.941406 6.25 -17.046875 C 6.84375 -17.160156 7.425781 -17.21875 8 -17.21875 C 8.570312 -17.21875 9.148438 -17.160156 9.734375 -17.046875 C 10.328125 -16.941406 10.878906 -16.75 11.390625 -16.46875 C 11.898438 -16.195312 12.351562 -15.84375 12.75 -15.40625 C 13.144531 -14.96875 13.460938 -14.488281 13.703125 -13.96875 C 13.953125 -13.445312 14.125 -12.890625 14.21875 -12.296875 C 14.320312 -11.710938 14.375 -11.132812 14.375 -10.5625 L 14.375 -6.9375 L 5.1875 -6.9375 L 5.1875 -6.40625 C 5.1875 -5.976562 5.234375 -5.546875 5.328125 -5.109375 C 5.421875 -4.671875 5.585938 -4.273438 5.828125 -3.921875 C 6.078125 -3.566406 6.40625 -3.289062 6.8125 -3.09375 C 7.21875 -2.90625 7.632812 -2.8125 8.0625 -2.8125 C 8.382812 -2.8125 8.695312 -2.84375 9 -2.90625 C 9.3125 -2.976562 9.59375 -3.101562 9.84375 -3.28125 C 10.101562 -3.457031 10.3125 -3.691406 10.46875 -3.984375 C 10.632812 -4.273438 10.726562 -4.566406 10.75 -4.859375 L 14.296875 -4.859375 C 14.273438 -4.367188 14.1875 -3.878906 14.03125 -3.390625 C 13.875 -2.898438 13.648438 -2.453125 13.359375 -2.046875 C 13.066406 -1.640625 12.71875 -1.28125 12.3125 -0.96875 C 11.914062 -0.664062 11.476562 -0.421875 11 -0.234375 C 10.53125 -0.0546875 10.050781 0.0664062 9.5625 0.140625 C 9.070312 0.210938 8.570312 0.25 8.0625 0.25 Z M 5.1875 -10.015625 L 10.8125 -10.015625 L 10.8125 -10.5625 C 10.8125 -10.988281 10.769531 -11.414062 10.6875 -11.84375 C 10.601562 -12.269531 10.4375 -12.660156 10.1875 -13.015625 C 9.945312 -13.378906 9.628906 -13.65625 9.234375 -13.84375 C 8.835938 -14.039062 8.425781 -14.140625 8 -14.140625 C 7.570312 -14.140625 7.160156 -14.039062 6.765625 -13.84375 C 6.367188 -13.65625 6.046875 -13.378906 5.796875 -13.015625 C 5.554688 -12.660156 5.394531 -12.269531 5.3125 -11.84375 C 5.226562 -11.414062 5.1875 -10.988281 5.1875 -10.5625 Z M 5.1875 -10.015625 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-6">
-<path style="stroke:none;" d="M 6.5 0.25 C 5.988281 0.25 5.476562 0.171875 4.96875 0.015625 C 4.46875 -0.140625 4.019531 -0.382812 3.625 -0.71875 C 3.238281 -1.050781 2.910156 -1.445312 2.640625 -1.90625 C 2.367188 -2.363281 2.160156 -2.835938 2.015625 -3.328125 C 1.867188 -3.816406 1.765625 -4.320312 1.703125 -4.84375 C 1.648438 -5.363281 1.625 -5.882812 1.625 -6.40625 L 1.625 -10.5625 C 1.625 -11.070312 1.648438 -11.585938 1.703125 -12.109375 C 1.765625 -12.628906 1.867188 -13.132812 2.015625 -13.625 C 2.160156 -14.125 2.367188 -14.601562 2.640625 -15.0625 C 2.910156 -15.519531 3.238281 -15.910156 3.625 -16.234375 C 4.019531 -16.566406 4.46875 -16.8125 4.96875 -16.96875 C 5.476562 -17.132812 5.988281 -17.21875 6.5 -17.21875 C 6.945312 -17.21875 7.382812 -17.15625 7.8125 -17.03125 C 8.238281 -16.914062 8.628906 -16.722656 8.984375 -16.453125 C 9.347656 -16.191406 9.65625 -15.878906 9.90625 -15.515625 C 10.164062 -15.148438 10.382812 -14.757812 10.5625 -14.34375 L 10.5625 -23.515625 L 14.109375 -23.515625 L 14.109375 0 L 10.5625 0 L 10.5625 -2.625 C 10.382812 -2.195312 10.164062 -1.800781 9.90625 -1.4375 C 9.65625 -1.070312 9.347656 -0.753906 8.984375 -0.484375 C 8.628906 -0.222656 8.238281 -0.0351562 7.8125 0.078125 C 7.382812 0.191406 6.945312 0.25 6.5 0.25 Z M 7.96875 -2.8125 C 8.25 -2.8125 8.519531 -2.851562 8.78125 -2.9375 C 9.050781 -3.03125 9.289062 -3.164062 9.5 -3.34375 C 9.71875 -3.519531 9.894531 -3.734375 10.03125 -3.984375 C 10.164062 -4.234375 10.269531 -4.488281 10.34375 -4.75 C 10.425781 -5.019531 10.484375 -5.289062 10.515625 -5.5625 C 10.546875 -5.84375 10.5625 -6.125 10.5625 -6.40625 L 10.5625 -10.5625 C 10.5625 -10.832031 10.546875 -11.109375 10.515625 -11.390625 C 10.484375 -11.671875 10.425781 -11.941406 10.34375 -12.203125 C 10.269531 -12.472656 10.164062 -12.726562 10.03125 -12.96875 C 9.894531 -13.21875 9.71875 -13.429688 9.5 -13.609375 C 9.289062 -13.796875 9.050781 -13.929688 8.78125 -14.015625 C 8.519531 -14.097656 8.25 -14.140625 7.96875 -14.140625 C 7.539062 -14.140625 7.128906 -14.035156 6.734375 -13.828125 C 6.335938 -13.628906 6.019531 -13.351562 5.78125 -13 C 5.550781 -12.65625 5.394531 -12.269531 5.3125 -11.84375 C 5.226562 -11.414062 5.1875 -10.988281 5.1875 -10.5625 L 5.1875 -6.40625 C 5.1875 -5.976562 5.226562 -5.550781 5.3125 -5.125 C 5.394531 -4.695312 5.550781 -4.304688 5.78125 -3.953125 C 6.019531 -3.597656 6.335938 -3.316406 6.734375 -3.109375 C 7.128906 -2.910156 7.539062 -2.8125 7.96875 -2.8125 Z M 7.96875 -2.8125 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-7">
-<path style="stroke:none;" d=""/>
-</symbol>
-<symbol overflow="visible" id="glyph1-8">
-<path style="stroke:none;" d="M 7.90625 0.25 C 7.332031 0.25 6.753906 0.191406 6.171875 0.078125 C 5.597656 -0.0351562 5.054688 -0.234375 4.546875 -0.515625 C 4.035156 -0.804688 3.582031 -1.179688 3.1875 -1.640625 C 2.789062 -2.109375 2.472656 -2.609375 2.234375 -3.140625 C 2.003906 -3.671875 1.84375 -4.226562 1.75 -4.8125 C 1.664062 -5.40625 1.625 -5.988281 1.625 -6.5625 L 1.625 -16.953125 C 1.625 -17.535156 1.664062 -18.117188 1.75 -18.703125 C 1.84375 -19.285156 2.003906 -19.84375 2.234375 -20.375 C 2.472656 -20.914062 2.789062 -21.414062 3.1875 -21.875 C 3.582031 -22.332031 4.035156 -22.703125 4.546875 -22.984375 C 5.054688 -23.273438 5.597656 -23.476562 6.171875 -23.59375 C 6.753906 -23.71875 7.332031 -23.78125 7.90625 -23.78125 C 8.457031 -23.78125 9.003906 -23.726562 9.546875 -23.625 C 10.097656 -23.53125 10.617188 -23.359375 11.109375 -23.109375 C 11.597656 -22.867188 12.046875 -22.550781 12.453125 -22.15625 C 12.859375 -21.757812 13.179688 -21.316406 13.421875 -20.828125 C 13.671875 -20.335938 13.859375 -19.820312 13.984375 -19.28125 C 14.109375 -18.738281 14.171875 -18.1875 14.171875 -17.625 C 14.171875 -17.625 14.171875 -17.613281 14.171875 -17.59375 L 10.625 -17.59375 C 10.625 -17.613281 10.625 -17.625 10.625 -17.625 C 10.625 -18.007812 10.566406 -18.390625 10.453125 -18.765625 C 10.347656 -19.140625 10.175781 -19.476562 9.9375 -19.78125 C 9.707031 -20.09375 9.40625 -20.320312 9.03125 -20.46875 C 8.664062 -20.625 8.289062 -20.703125 7.90625 -20.703125 C 7.625 -20.703125 7.335938 -20.660156 7.046875 -20.578125 C 6.765625 -20.492188 6.515625 -20.351562 6.296875 -20.15625 C 6.085938 -19.96875 5.90625 -19.742188 5.75 -19.484375 C 5.601562 -19.234375 5.488281 -18.96875 5.40625 -18.6875 C 5.320312 -18.40625 5.265625 -18.117188 5.234375 -17.828125 C 5.203125 -17.546875 5.1875 -17.253906 5.1875 -16.953125 L 5.1875 -6.5625 C 5.1875 -6.257812 5.203125 -5.960938 5.234375 -5.671875 C 5.265625 -5.390625 5.320312 -5.109375 5.40625 -4.828125 C 5.488281 -4.554688 5.601562 -4.289062 5.75 -4.03125 C 5.90625 -3.769531 6.085938 -3.546875 6.296875 -3.359375 C 6.515625 -3.171875 6.765625 -3.03125 7.046875 -2.9375 C 7.335938 -2.851562 7.625 -2.8125 7.90625 -2.8125 C 8.289062 -2.8125 8.664062 -2.882812 9.03125 -3.03125 C 9.40625 -3.1875 9.707031 -3.414062 9.9375 -3.71875 C 10.175781 -4.03125 10.347656 -4.375 10.453125 -4.75 C 10.566406 -5.125 10.625 -5.503906 10.625 -5.890625 C 10.625 -5.890625 10.625 -5.898438 10.625 -5.921875 L 14.171875 -5.921875 C 14.171875 -5.898438 14.171875 -5.890625 14.171875 -5.890625 C 14.171875 -5.335938 14.109375 -4.785156 13.984375 -4.234375 C 13.859375 -3.691406 13.671875 -3.175781 13.421875 -2.6875 C 13.179688 -2.195312 12.859375 -1.753906 12.453125 -1.359375 C 12.046875 -0.960938 11.597656 -0.640625 11.109375 -0.390625 C 10.617188 -0.148438 10.097656 0.015625 9.546875 0.109375 C 9.003906 0.203125 8.457031 0.25 7.90625 0.25 Z M 7.90625 0.25 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-9">
-<path style="stroke:none;" d="M 8 0.25 C 7.425781 0.25 6.84375 0.195312 6.25 0.09375 C 5.664062 -0.0078125 5.117188 -0.195312 4.609375 -0.46875 C 4.097656 -0.75 3.644531 -1.109375 3.25 -1.546875 C 2.851562 -1.984375 2.53125 -2.460938 2.28125 -2.984375 C 2.039062 -3.515625 1.867188 -4.070312 1.765625 -4.65625 C 1.671875 -5.238281 1.625 -5.820312 1.625 -6.40625 L 1.625 -10.5625 C 1.625 -11.132812 1.671875 -11.710938 1.765625 -12.296875 C 1.867188 -12.890625 2.039062 -13.445312 2.28125 -13.96875 C 2.53125 -14.488281 2.859375 -14.96875 3.265625 -15.40625 C 3.671875 -15.84375 4.128906 -16.195312 4.640625 -16.46875 C 5.148438 -16.75 5.691406 -16.957031 6.265625 -17.09375 C 6.847656 -17.238281 7.425781 -17.3125 8 -17.3125 C 8.570312 -17.3125 9.144531 -17.238281 9.71875 -17.09375 C 10.300781 -16.957031 10.847656 -16.75 11.359375 -16.46875 C 11.867188 -16.195312 12.328125 -15.84375 12.734375 -15.40625 C 13.140625 -14.96875 13.460938 -14.488281 13.703125 -13.96875 C 13.953125 -13.445312 14.125 -12.890625 14.21875 -12.296875 C 14.320312 -11.710938 14.375 -11.132812 14.375 -10.5625 L 14.375 -6.40625 C 14.375 -5.820312 14.320312 -5.238281 14.21875 -4.65625 C 14.125 -4.070312 13.953125 -3.515625 13.703125 -2.984375 C 13.460938 -2.460938 13.144531 -1.984375 12.75 -1.546875 C 12.351562 -1.109375 11.898438 -0.75 11.390625 -0.46875 C 10.878906 -0.195312 10.328125 -0.0078125 9.734375 0.09375 C 9.148438 0.195312 8.570312 0.25 8 0.25 Z M 8 -2.8125 C 8.425781 -2.8125 8.835938 -2.90625 9.234375 -3.09375 C 9.628906 -3.289062 9.945312 -3.570312 10.1875 -3.9375 C 10.4375 -4.300781 10.601562 -4.695312 10.6875 -5.125 C 10.769531 -5.550781 10.8125 -5.976562 10.8125 -6.40625 L 10.8125 -10.5625 C 10.8125 -10.988281 10.769531 -11.421875 10.6875 -11.859375 C 10.601562 -12.296875 10.4375 -12.6875 10.1875 -13.03125 C 9.945312 -13.382812 9.625 -13.65625 9.21875 -13.84375 C 8.8125 -14.039062 8.394531 -14.140625 7.96875 -14.140625 C 7.539062 -14.140625 7.128906 -14.035156 6.734375 -13.828125 C 6.335938 -13.628906 6.019531 -13.351562 5.78125 -13 C 5.550781 -12.65625 5.394531 -12.269531 5.3125 -11.84375 C 5.226562 -11.414062 5.1875 -10.988281 5.1875 -10.5625 L 5.1875 -6.40625 C 5.1875 -5.976562 5.226562 -5.550781 5.3125 -5.125 C 5.394531 -4.695312 5.554688 -4.300781 5.796875 -3.9375 C 6.046875 -3.570312 6.367188 -3.289062 6.765625 -3.09375 C 7.160156 -2.90625 7.570312 -2.8125 8 -2.8125 Z M 8 -2.8125 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-10">
-<path style="stroke:none;" d="M 1.890625 0 L 1.890625 -16.953125 L 5.4375 -16.953125 L 5.4375 -14.34375 C 5.613281 -14.757812 5.828125 -15.148438 6.078125 -15.515625 C 6.335938 -15.878906 6.640625 -16.191406 6.984375 -16.453125 C 7.335938 -16.722656 7.726562 -16.914062 8.15625 -17.03125 C 8.582031 -17.15625 9.019531 -17.21875 9.46875 -17.21875 C 9.976562 -17.21875 10.476562 -17.128906 10.96875 -16.953125 C 11.46875 -16.785156 11.898438 -16.53125 12.265625 -16.1875 C 12.640625 -15.851562 12.941406 -15.453125 13.171875 -14.984375 C 13.410156 -14.523438 13.597656 -14.050781 13.734375 -13.5625 C 13.878906 -13.070312 13.976562 -12.578125 14.03125 -12.078125 C 14.082031 -11.578125 14.109375 -11.070312 14.109375 -10.5625 L 14.109375 0 L 10.5625 0 L 10.5625 -10.5625 C 10.5625 -10.832031 10.546875 -11.109375 10.515625 -11.390625 C 10.484375 -11.671875 10.425781 -11.941406 10.34375 -12.203125 C 10.269531 -12.472656 10.164062 -12.722656 10.03125 -12.953125 C 9.894531 -13.191406 9.722656 -13.40625 9.515625 -13.59375 C 9.316406 -13.789062 9.082031 -13.929688 8.8125 -14.015625 C 8.550781 -14.097656 8.28125 -14.140625 8 -14.140625 C 7.71875 -14.140625 7.441406 -14.097656 7.171875 -14.015625 C 6.910156 -13.929688 6.675781 -13.789062 6.46875 -13.59375 C 6.269531 -13.40625 6.101562 -13.191406 5.96875 -12.953125 C 5.832031 -12.722656 5.722656 -12.472656 5.640625 -12.203125 C 5.566406 -11.941406 5.515625 -11.671875 5.484375 -11.390625 C 5.453125 -11.109375 5.4375 -10.832031 5.4375 -10.5625 L 5.4375 0 Z M 1.890625 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-11">
-<path style="stroke:none;" d="M 7.9375 0.25 C 7.445312 0.25 6.957031 0.21875 6.46875 0.15625 C 5.976562 0.09375 5.492188 -0.0234375 5.015625 -0.203125 C 4.546875 -0.390625 4.109375 -0.628906 3.703125 -0.921875 C 3.304688 -1.222656 2.96875 -1.578125 2.6875 -1.984375 C 2.40625 -2.390625 2.191406 -2.835938 2.046875 -3.328125 C 1.898438 -3.816406 1.828125 -4.304688 1.828125 -4.796875 L 5.375 -4.796875 C 5.394531 -4.503906 5.472656 -4.21875 5.609375 -3.9375 C 5.753906 -3.65625 5.953125 -3.429688 6.203125 -3.265625 C 6.460938 -3.097656 6.738281 -2.976562 7.03125 -2.90625 C 7.332031 -2.84375 7.632812 -2.8125 7.9375 -2.8125 C 8.257812 -2.8125 8.566406 -2.84375 8.859375 -2.90625 C 9.160156 -2.976562 9.4375 -3.09375 9.6875 -3.25 C 9.945312 -3.40625 10.15625 -3.617188 10.3125 -3.890625 C 10.476562 -4.171875 10.5625 -4.460938 10.5625 -4.765625 C 10.5625 -5.109375 10.445312 -5.414062 10.21875 -5.6875 C 10 -5.96875 9.734375 -6.171875 9.421875 -6.296875 C 9.109375 -6.429688 8.785156 -6.539062 8.453125 -6.625 C 8.128906 -6.707031 7.800781 -6.796875 7.46875 -6.890625 C 7.132812 -6.992188 6.804688 -7.097656 6.484375 -7.203125 C 6.171875 -7.304688 5.859375 -7.421875 5.546875 -7.546875 C 5.242188 -7.679688 4.945312 -7.835938 4.65625 -8.015625 C 4.363281 -8.191406 4.082031 -8.382812 3.8125 -8.59375 C 3.550781 -8.8125 3.316406 -9.054688 3.109375 -9.328125 C 2.910156 -9.597656 2.738281 -9.882812 2.59375 -10.1875 C 2.445312 -10.5 2.332031 -10.816406 2.25 -11.140625 C 2.175781 -11.472656 2.140625 -11.8125 2.140625 -12.15625 C 2.140625 -12.644531 2.207031 -13.125 2.34375 -13.59375 C 2.488281 -14.0625 2.691406 -14.5 2.953125 -14.90625 C 3.222656 -15.3125 3.550781 -15.671875 3.9375 -15.984375 C 4.320312 -16.296875 4.738281 -16.546875 5.1875 -16.734375 C 5.632812 -16.929688 6.101562 -17.0625 6.59375 -17.125 C 7.082031 -17.1875 7.570312 -17.21875 8.0625 -17.21875 C 8.53125 -17.21875 9.003906 -17.1875 9.484375 -17.125 C 9.960938 -17.0625 10.425781 -16.9375 10.875 -16.75 C 11.320312 -16.570312 11.738281 -16.332031 12.125 -16.03125 C 12.507812 -15.726562 12.832031 -15.375 13.09375 -14.96875 C 13.363281 -14.570312 13.566406 -14.132812 13.703125 -13.65625 C 13.847656 -13.1875 13.921875 -12.707031 13.921875 -12.21875 L 10.375 -12.21875 C 10.351562 -12.5 10.28125 -12.769531 10.15625 -13.03125 C 10.039062 -13.300781 9.867188 -13.519531 9.640625 -13.6875 C 9.421875 -13.863281 9.171875 -13.984375 8.890625 -14.046875 C 8.617188 -14.109375 8.34375 -14.140625 8.0625 -14.140625 C 7.757812 -14.140625 7.46875 -14.109375 7.1875 -14.046875 C 6.914062 -13.984375 6.660156 -13.863281 6.421875 -13.6875 C 6.191406 -13.519531 6.015625 -13.300781 5.890625 -13.03125 C 5.765625 -12.769531 5.703125 -12.488281 5.703125 -12.1875 C 5.703125 -11.851562 5.8125 -11.546875 6.03125 -11.265625 C 6.257812 -10.984375 6.523438 -10.78125 6.828125 -10.65625 C 7.140625 -10.53125 7.457031 -10.421875 7.78125 -10.328125 C 8.113281 -10.242188 8.441406 -10.15625 8.765625 -10.0625 C 9.085938 -9.96875 9.40625 -9.867188 9.71875 -9.765625 C 10.039062 -9.671875 10.363281 -9.554688 10.6875 -9.421875 C 11.007812 -9.285156 11.3125 -9.128906 11.59375 -8.953125 C 11.882812 -8.785156 12.15625 -8.585938 12.40625 -8.359375 C 12.664062 -8.140625 12.898438 -7.894531 13.109375 -7.625 C 13.328125 -7.363281 13.507812 -7.082031 13.65625 -6.78125 C 13.8125 -6.488281 13.925781 -6.171875 14 -5.828125 C 14.070312 -5.484375 14.109375 -5.148438 14.109375 -4.828125 C 14.109375 -4.316406 14.03125 -3.820312 13.875 -3.34375 C 13.726562 -2.863281 13.507812 -2.414062 13.21875 -2 C 12.9375 -1.582031 12.59375 -1.222656 12.1875 -0.921875 C 11.78125 -0.628906 11.34375 -0.390625 10.875 -0.203125 C 10.40625 -0.0234375 9.925781 0.09375 9.4375 0.15625 C 8.945312 0.21875 8.445312 0.25 7.9375 0.25 Z M 7.9375 0.25 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-12">
-<path style="stroke:none;" d="M 2.40625 0 L 2.40625 -3.078125 L 6.4375 -3.078125 L 6.4375 -20.453125 L 2.84375 -20.453125 L 2.84375 -23.515625 L 10.015625 -23.515625 L 10.015625 -3.078125 L 13.59375 -3.078125 L 13.59375 0 Z M 2.40625 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-13">
-<path style="stroke:none;" d="M 2.8125 0 L 2.8125 -23.515625 L 14.109375 -23.515625 L 14.109375 -20.453125 L 6.40625 -20.453125 L 6.40625 -13.765625 L 12.28125 -13.765625 L 12.28125 -10.6875 L 6.40625 -10.6875 L 6.40625 0 Z M 2.8125 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-14">
-<path style="stroke:none;" d="M 7.171875 0 L 7.171875 -20.21875 L 3.484375 -17.28125 L 1.734375 -19.171875 L 7.171875 -23.515625 L 10.75 -23.515625 L 10.75 0 Z M 7.171875 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-15">
-<path style="stroke:none;" d="M 6.203125 0 L 6.203125 -20.453125 L 1.5 -20.453125 L 1.5 -23.515625 L 14.5 -23.515625 L 14.5 -20.453125 L 9.796875 -20.453125 L 9.796875 0 Z M 6.203125 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-16">
-<path style="stroke:none;" d="M 1.25 0 L 1.25 -16.953125 L 4.21875 -16.953125 L 4.21875 -15.421875 C 4.3125 -15.660156 4.429688 -15.890625 4.578125 -16.109375 C 4.722656 -16.335938 4.894531 -16.535156 5.09375 -16.703125 C 5.300781 -16.867188 5.535156 -16.992188 5.796875 -17.078125 C 6.066406 -17.171875 6.332031 -17.21875 6.59375 -17.21875 C 6.9375 -17.21875 7.257812 -17.148438 7.5625 -17.015625 C 7.875 -16.890625 8.140625 -16.703125 8.359375 -16.453125 C 8.585938 -16.210938 8.773438 -15.941406 8.921875 -15.640625 C 9.078125 -15.347656 9.1875 -15.039062 9.25 -14.71875 C 9.332031 -15.039062 9.441406 -15.347656 9.578125 -15.640625 C 9.722656 -15.941406 9.898438 -16.210938 10.109375 -16.453125 C 10.328125 -16.703125 10.585938 -16.890625 10.890625 -17.015625 C 11.203125 -17.148438 11.53125 -17.21875 11.875 -17.21875 C 12.21875 -17.21875 12.546875 -17.144531 12.859375 -17 C 13.179688 -16.863281 13.453125 -16.671875 13.671875 -16.421875 C 13.898438 -16.179688 14.082031 -15.898438 14.21875 -15.578125 C 14.363281 -15.265625 14.472656 -14.945312 14.546875 -14.625 C 14.617188 -14.300781 14.671875 -13.96875 14.703125 -13.625 C 14.734375 -13.289062 14.75 -12.953125 14.75 -12.609375 L 14.75 0 L 11.78125 0 L 11.78125 -12.609375 C 11.78125 -12.816406 11.769531 -13.03125 11.75 -13.25 C 11.726562 -13.476562 11.671875 -13.691406 11.578125 -13.890625 C 11.492188 -14.097656 11.367188 -14.273438 11.203125 -14.421875 C 11.046875 -14.578125 10.851562 -14.65625 10.625 -14.65625 C 10.40625 -14.65625 10.210938 -14.578125 10.046875 -14.421875 C 9.878906 -14.273438 9.753906 -14.097656 9.671875 -13.890625 C 9.597656 -13.691406 9.546875 -13.476562 9.515625 -13.25 C 9.484375 -13.03125 9.46875 -12.816406 9.46875 -12.609375 L 9.46875 0 L 6.53125 0 L 6.53125 -12.609375 C 6.53125 -12.816406 6.515625 -13.03125 6.484375 -13.25 C 6.453125 -13.476562 6.394531 -13.691406 6.3125 -13.890625 C 6.238281 -14.097656 6.117188 -14.273438 5.953125 -14.421875 C 5.785156 -14.578125 5.59375 -14.65625 5.375 -14.65625 C 5.144531 -14.65625 4.945312 -14.578125 4.78125 -14.421875 C 4.625 -14.273438 4.5 -14.097656 4.40625 -13.890625 C 4.320312 -13.691406 4.269531 -13.476562 4.25 -13.25 C 4.226562 -13.03125 4.21875 -12.816406 4.21875 -12.609375 L 4.21875 0 Z M 1.25 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-17">
-<path style="stroke:none;" d="M 2.8125 0 L 2.8125 -23.515625 L 6.40625 -23.515625 L 6.40625 -3.078125 L 14.375 -3.078125 L 14.375 0 Z M 2.8125 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-18">
-<path style="stroke:none;" d="M 5.953125 0 C 5.628906 -0.832031 5.316406 -1.660156 5.015625 -2.484375 C 4.722656 -3.304688 4.4375 -4.132812 4.15625 -4.96875 C 3.882812 -5.8125 3.617188 -6.65625 3.359375 -7.5 C 3.097656 -8.34375 2.867188 -9.191406 2.671875 -10.046875 C 2.472656 -10.898438 2.289062 -11.765625 2.125 -12.640625 C 1.96875 -13.515625 1.890625 -14.390625 1.890625 -15.265625 L 1.890625 -16.953125 L 5.4375 -16.953125 L 5.4375 -15.265625 C 5.4375 -14.222656 5.53125 -13.175781 5.71875 -12.125 C 5.914062 -11.082031 6.128906 -10.046875 6.359375 -9.015625 C 6.597656 -7.992188 6.851562 -6.972656 7.125 -5.953125 C 7.40625 -4.929688 7.695312 -3.914062 8 -2.90625 C 8.300781 -3.914062 8.585938 -4.929688 8.859375 -5.953125 C 9.140625 -6.972656 9.394531 -7.992188 9.625 -9.015625 C 9.863281 -10.046875 10.078125 -11.082031 10.265625 -12.125 C 10.460938 -13.175781 10.5625 -14.222656 10.5625 -15.265625 L 10.5625 -16.953125 L 14.109375 -16.953125 L 14.109375 -15.265625 C 14.109375 -14.390625 14.023438 -13.515625 13.859375 -12.640625 C 13.703125 -11.765625 13.523438 -10.898438 13.328125 -10.046875 C 13.128906 -9.191406 12.898438 -8.34375 12.640625 -7.5 C 12.378906 -6.65625 12.109375 -5.8125 11.828125 -4.96875 C 11.554688 -4.132812 11.269531 -3.304688 10.96875 -2.484375 C 10.675781 -1.660156 10.367188 -0.832031 10.046875 0 Z M 5.953125 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-19">
-<path style="stroke:none;" d="M 1.25 0 L 1.25 -23.515625 L 4.671875 -23.515625 L 8 -11.171875 L 11.328125 -23.515625 L 14.75 -23.515625 L 14.75 0 L 11.171875 0 L 11.171875 -4.703125 C 11.171875 -5.835938 11.195312 -6.96875 11.25 -8.09375 C 11.300781 -9.226562 11.375 -10.351562 11.46875 -11.46875 C 11.5625 -12.59375 11.664062 -13.71875 11.78125 -14.84375 C 11.90625 -15.976562 12.007812 -17.109375 12.09375 -18.234375 L 9.09375 -7.078125 L 6.90625 -7.078125 L 3.90625 -18.234375 C 3.988281 -17.109375 4.085938 -15.976562 4.203125 -14.84375 C 4.328125 -13.71875 4.4375 -12.59375 4.53125 -11.46875 C 4.625 -10.351562 4.695312 -9.226562 4.75 -8.09375 C 4.800781 -6.96875 4.828125 -5.835938 4.828125 -4.703125 L 4.828125 0 Z M 1.25 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-20">
-<path style="stroke:none;" d="M 1.890625 0 L 1.890625 -23.515625 L 7.515625 -23.515625 C 8.078125 -23.515625 8.640625 -23.460938 9.203125 -23.359375 C 9.765625 -23.253906 10.296875 -23.070312 10.796875 -22.8125 C 11.296875 -22.5625 11.742188 -22.226562 12.140625 -21.8125 C 12.535156 -21.394531 12.851562 -20.9375 13.09375 -20.4375 C 13.34375 -19.9375 13.515625 -19.398438 13.609375 -18.828125 C 13.710938 -18.265625 13.765625 -17.707031 13.765625 -17.15625 C 13.765625 -16.664062 13.722656 -16.175781 13.640625 -15.6875 C 13.566406 -15.207031 13.421875 -14.753906 13.203125 -14.328125 C 12.992188 -13.910156 12.722656 -13.523438 12.390625 -13.171875 C 12.066406 -12.816406 11.691406 -12.507812 11.265625 -12.25 C 11.796875 -12.019531 12.285156 -11.722656 12.734375 -11.359375 C 13.179688 -10.992188 13.546875 -10.566406 13.828125 -10.078125 C 14.117188 -9.585938 14.320312 -9.054688 14.4375 -8.484375 C 14.5625 -7.921875 14.625 -7.351562 14.625 -6.78125 C 14.625 -6.164062 14.5625 -5.546875 14.4375 -4.921875 C 14.320312 -4.304688 14.113281 -3.726562 13.8125 -3.1875 C 13.519531 -2.644531 13.140625 -2.160156 12.671875 -1.734375 C 12.203125 -1.304688 11.6875 -0.957031 11.125 -0.6875 C 10.570312 -0.414062 9.988281 -0.234375 9.375 -0.140625 C 8.757812 -0.046875 8.140625 0 7.515625 0 Z M 5.4375 -13.703125 L 7.515625 -13.703125 C 7.921875 -13.703125 8.316406 -13.789062 8.703125 -13.96875 C 9.085938 -14.144531 9.390625 -14.40625 9.609375 -14.75 C 9.835938 -15.09375 9.992188 -15.46875 10.078125 -15.875 C 10.160156 -16.28125 10.203125 -16.6875 10.203125 -17.09375 C 10.203125 -17.5 10.160156 -17.898438 10.078125 -18.296875 C 9.992188 -18.703125 9.835938 -19.070312 9.609375 -19.40625 C 9.390625 -19.738281 9.085938 -19.992188 8.703125 -20.171875 C 8.316406 -20.359375 7.921875 -20.453125 7.515625 -20.453125 L 5.4375 -20.453125 Z M 5.4375 -3.078125 L 7.515625 -3.078125 C 8.003906 -3.078125 8.484375 -3.15625 8.953125 -3.3125 C 9.421875 -3.46875 9.820312 -3.726562 10.15625 -4.09375 C 10.488281 -4.457031 10.722656 -4.882812 10.859375 -5.375 C 11.003906 -5.863281 11.078125 -6.351562 11.078125 -6.84375 C 11.078125 -7.332031 11.003906 -7.816406 10.859375 -8.296875 C 10.722656 -8.785156 10.492188 -9.210938 10.171875 -9.578125 C 9.859375 -9.953125 9.457031 -10.21875 8.96875 -10.375 C 8.488281 -10.539062 8.003906 -10.625 7.515625 -10.625 L 5.4375 -10.625 Z M 5.4375 -3.078125 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-21">
-<path style="stroke:none;" d="M 4.03125 6.5625 L 4.03125 6.34375 C 4.03125 5.71875 4.066406 5.097656 4.140625 4.484375 C 4.210938 3.878906 4.359375 3.289062 4.578125 2.71875 L 6.109375 -1.3125 L 2.9375 -9.703125 C 2.644531 -10.484375 2.394531 -11.285156 2.1875 -12.109375 C 1.988281 -12.929688 1.890625 -13.769531 1.890625 -14.625 L 1.890625 -16.953125 L 5.4375 -16.953125 L 5.4375 -14.625 C 5.4375 -13.9375 5.519531 -13.273438 5.6875 -12.640625 C 5.863281 -12.003906 6.054688 -11.375 6.265625 -10.75 L 7.90625 -6.015625 L 9.734375 -10.78125 C 9.960938 -11.40625 10.15625 -12.035156 10.3125 -12.671875 C 10.476562 -13.304688 10.5625 -13.957031 10.5625 -14.625 L 10.5625 -16.953125 L 14.109375 -16.953125 L 14.109375 -14.625 C 14.109375 -13.769531 14.003906 -12.929688 13.796875 -12.109375 C 13.597656 -11.285156 13.351562 -10.484375 13.0625 -9.703125 L 7.90625 3.8125 C 7.757812 4.21875 7.664062 4.632812 7.625 5.0625 C 7.59375 5.488281 7.578125 5.914062 7.578125 6.34375 L 7.578125 6.5625 Z M 4.03125 6.5625 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-22">
-<path style="stroke:none;" d="M 6.046875 0.25 C 5.617188 0.25 5.195312 0.207031 4.78125 0.125 C 4.363281 0.0390625 3.976562 -0.101562 3.625 -0.3125 C 3.28125 -0.53125 2.972656 -0.800781 2.703125 -1.125 C 2.429688 -1.457031 2.21875 -1.820312 2.0625 -2.21875 C 1.90625 -2.613281 1.789062 -3.015625 1.71875 -3.421875 C 1.65625 -3.828125 1.625 -4.242188 1.625 -4.671875 C 1.625 -5.242188 1.6875 -5.804688 1.8125 -6.359375 C 1.945312 -6.921875 2.1875 -7.429688 2.53125 -7.890625 C 2.875 -8.347656 3.28125 -8.734375 3.75 -9.046875 C 4.21875 -9.367188 4.722656 -9.625 5.265625 -9.8125 C 5.804688 -10.007812 6.359375 -10.140625 6.921875 -10.203125 C 7.492188 -10.265625 8.054688 -10.296875 8.609375 -10.296875 L 10.5625 -10.296875 L 10.5625 -11.546875 C 10.5625 -11.910156 10.503906 -12.257812 10.390625 -12.59375 C 10.285156 -12.925781 10.109375 -13.210938 9.859375 -13.453125 C 9.617188 -13.703125 9.328125 -13.878906 8.984375 -13.984375 C 8.648438 -14.085938 8.300781 -14.140625 7.9375 -14.140625 C 7.632812 -14.140625 7.328125 -14.101562 7.015625 -14.03125 C 6.710938 -13.957031 6.429688 -13.820312 6.171875 -13.625 C 5.921875 -13.4375 5.722656 -13.191406 5.578125 -12.890625 C 5.441406 -12.597656 5.375 -12.300781 5.375 -12 L 1.828125 -12 C 1.828125 -12.488281 1.894531 -12.976562 2.03125 -13.46875 C 2.164062 -13.957031 2.375 -14.414062 2.65625 -14.84375 C 2.9375 -15.269531 3.273438 -15.644531 3.671875 -15.96875 C 4.078125 -16.289062 4.515625 -16.539062 4.984375 -16.71875 C 5.460938 -16.894531 5.953125 -17.019531 6.453125 -17.09375 C 6.953125 -17.175781 7.445312 -17.21875 7.9375 -17.21875 C 8.46875 -17.21875 9 -17.175781 9.53125 -17.09375 C 10.070312 -17.019531 10.582031 -16.878906 11.0625 -16.671875 C 11.539062 -16.460938 11.976562 -16.179688 12.375 -15.828125 C 12.78125 -15.472656 13.117188 -15.066406 13.390625 -14.609375 C 13.660156 -14.148438 13.847656 -13.660156 13.953125 -13.140625 C 14.054688 -12.617188 14.109375 -12.085938 14.109375 -11.546875 L 14.109375 0 L 10.5625 0 L 10.5625 -2.5 C 10.457031 -2.28125 10.382812 -2.144531 10.34375 -2.09375 C 10.3125 -2.039062 10.265625 -1.96875 10.203125 -1.875 C 10.140625 -1.78125 10.070312 -1.679688 10 -1.578125 C 9.925781 -1.484375 9.847656 -1.394531 9.765625 -1.3125 C 9.691406 -1.226562 9.613281 -1.140625 9.53125 -1.046875 C 9.445312 -0.960938 9.363281 -0.882812 9.28125 -0.8125 C 9.195312 -0.738281 9.101562 -0.671875 9 -0.609375 C 8.90625 -0.546875 8.8125 -0.476562 8.71875 -0.40625 C 8.625 -0.34375 8.519531 -0.285156 8.40625 -0.234375 C 8.300781 -0.179688 8.195312 -0.132812 8.09375 -0.09375 C 7.988281 -0.0507812 7.875 -0.015625 7.75 0.015625 C 7.632812 0.046875 7.523438 0.0703125 7.421875 0.09375 C 7.316406 0.113281 7.203125 0.132812 7.078125 0.15625 C 6.960938 0.175781 6.847656 0.191406 6.734375 0.203125 C 6.617188 0.210938 6.5 0.222656 6.375 0.234375 C 6.257812 0.242188 6.148438 0.25 6.046875 0.25 Z M 7.484375 -2.8125 C 7.890625 -2.8125 8.289062 -2.867188 8.6875 -2.984375 C 9.082031 -3.109375 9.421875 -3.304688 9.703125 -3.578125 C 9.992188 -3.859375 10.207031 -4.191406 10.34375 -4.578125 C 10.488281 -4.960938 10.5625 -5.359375 10.5625 -5.765625 L 10.5625 -7.234375 L 8.609375 -7.234375 C 8.347656 -7.234375 8.085938 -7.222656 7.828125 -7.203125 C 7.578125 -7.179688 7.328125 -7.132812 7.078125 -7.0625 C 6.835938 -7 6.597656 -6.914062 6.359375 -6.8125 C 6.128906 -6.707031 5.921875 -6.566406 5.734375 -6.390625 C 5.554688 -6.222656 5.421875 -6.015625 5.328125 -5.765625 C 5.234375 -5.523438 5.1875 -5.28125 5.1875 -5.03125 C 5.1875 -4.726562 5.238281 -4.425781 5.34375 -4.125 C 5.445312 -3.832031 5.601562 -3.582031 5.8125 -3.375 C 6.03125 -3.175781 6.289062 -3.03125 6.59375 -2.9375 C 6.894531 -2.851562 7.191406 -2.8125 7.484375 -2.8125 Z M 7.484375 -2.8125 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-23">
-<path style="stroke:none;" d="M 1.59375 4.578125 L 10.75 -26.34375 L 14.40625 -26.34375 L 5.25 4.578125 Z M 1.59375 4.578125 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-24">
-<path style="stroke:none;" d="M 9.984375 0.25 C 9.453125 0.25 8.914062 0.1875 8.375 0.0625 C 7.84375 -0.0625 7.359375 -0.285156 6.921875 -0.609375 C 6.484375 -0.929688 6.109375 -1.316406 5.796875 -1.765625 C 5.492188 -2.210938 5.25 -2.691406 5.0625 -3.203125 C 4.882812 -3.710938 4.765625 -4.238281 4.703125 -4.78125 C 4.640625 -5.320312 4.609375 -5.863281 4.609375 -6.40625 L 4.609375 -13.890625 L 1.5 -13.890625 L 1.5 -16.953125 L 4.609375 -16.953125 L 4.609375 -23.515625 L 8.1875 -23.515625 L 8.1875 -16.953125 L 12.703125 -16.953125 L 12.703125 -13.890625 L 8.1875 -13.890625 L 8.1875 -6.40625 C 8.1875 -6.144531 8.191406 -5.890625 8.203125 -5.640625 C 8.210938 -5.398438 8.238281 -5.15625 8.28125 -4.90625 C 8.332031 -4.664062 8.394531 -4.425781 8.46875 -4.1875 C 8.539062 -3.957031 8.644531 -3.738281 8.78125 -3.53125 C 8.914062 -3.332031 9.085938 -3.160156 9.296875 -3.015625 C 9.515625 -2.878906 9.742188 -2.8125 9.984375 -2.8125 C 10.304688 -2.8125 10.597656 -2.910156 10.859375 -3.109375 C 11.128906 -3.316406 11.328125 -3.566406 11.453125 -3.859375 C 11.578125 -4.160156 11.660156 -4.472656 11.703125 -4.796875 C 11.753906 -5.117188 11.78125 -5.441406 11.78125 -5.765625 C 11.78125 -5.785156 11.78125 -5.8125 11.78125 -5.84375 C 11.78125 -5.875 11.78125 -5.898438 11.78125 -5.921875 L 15.359375 -5.921875 C 15.359375 -5.859375 15.359375 -5.796875 15.359375 -5.734375 C 15.359375 -5.679688 15.359375 -5.632812 15.359375 -5.59375 C 15.359375 -5.082031 15.3125 -4.582031 15.21875 -4.09375 C 15.125 -3.601562 14.976562 -3.128906 14.78125 -2.671875 C 14.59375 -2.210938 14.332031 -1.789062 14 -1.40625 C 13.664062 -1.019531 13.285156 -0.695312 12.859375 -0.4375 C 12.429688 -0.1875 11.972656 -0.0078125 11.484375 0.09375 C 10.992188 0.195312 10.492188 0.25 9.984375 0.25 Z M 9.984375 0.25 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-25">
-<path style="stroke:none;" d="M 3.6875 0 L 3.6875 -16.953125 L 7.265625 -16.953125 L 7.265625 -13.28125 C 7.390625 -13.769531 7.546875 -14.242188 7.734375 -14.703125 C 7.929688 -15.160156 8.1875 -15.582031 8.5 -15.96875 C 8.820312 -16.351562 9.210938 -16.65625 9.671875 -16.875 C 10.140625 -17.101562 10.617188 -17.21875 11.109375 -17.21875 C 11.515625 -17.21875 11.910156 -17.148438 12.296875 -17.015625 C 12.691406 -16.890625 13.035156 -16.6875 13.328125 -16.40625 C 13.617188 -16.132812 13.851562 -15.816406 14.03125 -15.453125 C 14.207031 -15.085938 14.347656 -14.707031 14.453125 -14.3125 C 14.566406 -13.925781 14.640625 -13.53125 14.671875 -13.125 C 14.703125 -12.71875 14.71875 -12.3125 14.71875 -11.90625 L 11.140625 -11.90625 C 11.140625 -12.15625 11.125 -12.410156 11.09375 -12.671875 C 11.0625 -12.929688 10.988281 -13.171875 10.875 -13.390625 C 10.769531 -13.617188 10.601562 -13.800781 10.375 -13.9375 C 10.15625 -14.070312 9.914062 -14.140625 9.65625 -14.140625 C 9.320312 -14.140625 9.007812 -14.03125 8.71875 -13.8125 C 8.425781 -13.601562 8.195312 -13.347656 8.03125 -13.046875 C 7.863281 -12.753906 7.726562 -12.4375 7.625 -12.09375 C 7.53125 -11.75 7.453125 -11.40625 7.390625 -11.0625 C 7.328125 -10.726562 7.289062 -10.382812 7.28125 -10.03125 C 7.269531 -9.675781 7.265625 -9.328125 7.265625 -8.984375 L 7.265625 0 Z M 3.6875 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-26">
-<path style="stroke:none;" d="M 1.890625 -9.34375 L 1.890625 -12.421875 L 14.109375 -12.421875 L 14.109375 -9.34375 Z M 1.890625 -9.34375 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-27">
-<path style="stroke:none;" d="M 7.90625 0.25 C 7.332031 0.25 6.753906 0.195312 6.171875 0.09375 C 5.597656 -0.0078125 5.0625 -0.203125 4.5625 -0.484375 C 4.0625 -0.773438 3.613281 -1.140625 3.21875 -1.578125 C 2.820312 -2.015625 2.5 -2.492188 2.25 -3.015625 C 2.007812 -3.546875 1.84375 -4.097656 1.75 -4.671875 C 1.664062 -5.242188 1.625 -5.820312 1.625 -6.40625 L 1.625 -10.5625 C 1.625 -11.132812 1.664062 -11.707031 1.75 -12.28125 C 1.84375 -12.863281 2.007812 -13.414062 2.25 -13.9375 C 2.5 -14.457031 2.820312 -14.9375 3.21875 -15.375 C 3.613281 -15.8125 4.0625 -16.171875 4.5625 -16.453125 C 5.0625 -16.742188 5.597656 -16.941406 6.171875 -17.046875 C 6.753906 -17.160156 7.332031 -17.21875 7.90625 -17.21875 C 8.457031 -17.21875 9.003906 -17.164062 9.546875 -17.0625 C 10.097656 -16.96875 10.617188 -16.800781 11.109375 -16.5625 C 11.597656 -16.332031 12.039062 -16.023438 12.4375 -15.640625 C 12.832031 -15.265625 13.15625 -14.832031 13.40625 -14.34375 C 13.664062 -13.863281 13.859375 -13.351562 13.984375 -12.8125 C 14.109375 -12.269531 14.171875 -11.734375 14.171875 -11.203125 C 14.171875 -11.179688 14.171875 -11.164062 14.171875 -11.15625 C 14.171875 -11.144531 14.171875 -11.140625 14.171875 -11.140625 L 10.625 -11.140625 C 10.625 -11.140625 10.625 -11.144531 10.625 -11.15625 C 10.625 -11.164062 10.625 -11.171875 10.625 -11.171875 C 10.625 -11.554688 10.5625 -11.925781 10.4375 -12.28125 C 10.320312 -12.644531 10.144531 -12.976562 9.90625 -13.28125 C 9.675781 -13.582031 9.378906 -13.800781 9.015625 -13.9375 C 8.660156 -14.070312 8.289062 -14.140625 7.90625 -14.140625 C 7.476562 -14.140625 7.070312 -14.035156 6.6875 -13.828125 C 6.300781 -13.628906 5.992188 -13.351562 5.765625 -13 C 5.546875 -12.65625 5.394531 -12.269531 5.3125 -11.84375 C 5.226562 -11.414062 5.1875 -10.988281 5.1875 -10.5625 L 5.1875 -6.40625 C 5.1875 -5.976562 5.226562 -5.550781 5.3125 -5.125 C 5.394531 -4.695312 5.546875 -4.304688 5.765625 -3.953125 C 5.992188 -3.597656 6.300781 -3.316406 6.6875 -3.109375 C 7.070312 -2.910156 7.476562 -2.8125 7.90625 -2.8125 C 8.289062 -2.8125 8.660156 -2.878906 9.015625 -3.015625 C 9.378906 -3.160156 9.675781 -3.378906 9.90625 -3.671875 C 10.144531 -3.972656 10.320312 -4.304688 10.4375 -4.671875 C 10.5625 -5.035156 10.625 -5.410156 10.625 -5.796875 C 10.625 -5.796875 10.625 -5.800781 10.625 -5.8125 C 10.625 -5.820312 10.625 -5.828125 10.625 -5.828125 L 14.171875 -5.828125 C 14.171875 -5.828125 14.171875 -5.820312 14.171875 -5.8125 C 14.171875 -5.800781 14.171875 -5.785156 14.171875 -5.765625 C 14.171875 -5.222656 14.109375 -4.679688 13.984375 -4.140625 C 13.859375 -3.597656 13.664062 -3.085938 13.40625 -2.609375 C 13.15625 -2.128906 12.832031 -1.695312 12.4375 -1.3125 C 12.039062 -0.925781 11.597656 -0.613281 11.109375 -0.375 C 10.617188 -0.144531 10.097656 0.015625 9.546875 0.109375 C 9.003906 0.203125 8.457031 0.25 7.90625 0.25 Z M 7.90625 0.25 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-28">
-<path style="stroke:none;" d="M 5.28125 0 L 5.28125 -13.125 L 1.890625 -13.125 L 1.890625 -16.1875 L 5.28125 -16.1875 L 5.28125 -18.265625 C 5.28125 -18.734375 5.304688 -19.195312 5.359375 -19.65625 C 5.410156 -20.125 5.519531 -20.566406 5.6875 -20.984375 C 5.863281 -21.410156 6.09375 -21.8125 6.375 -22.1875 C 6.664062 -22.5625 7.007812 -22.867188 7.40625 -23.109375 C 7.800781 -23.359375 8.222656 -23.53125 8.671875 -23.625 C 9.117188 -23.726562 9.578125 -23.78125 10.046875 -23.78125 C 10.691406 -23.78125 11.320312 -23.675781 11.9375 -23.46875 C 12.550781 -23.269531 13.078125 -22.945312 13.515625 -22.5 C 13.953125 -22.050781 14.269531 -21.515625 14.46875 -20.890625 C 14.675781 -20.273438 14.78125 -19.648438 14.78125 -19.015625 C 14.78125 -18.960938 14.78125 -18.910156 14.78125 -18.859375 C 14.78125 -18.804688 14.78125 -18.757812 14.78125 -18.71875 L 11.203125 -18.71875 C 11.203125 -18.738281 11.203125 -18.757812 11.203125 -18.78125 C 11.203125 -18.800781 11.203125 -18.820312 11.203125 -18.84375 C 11.203125 -19.039062 11.1875 -19.238281 11.15625 -19.4375 C 11.125 -19.644531 11.0625 -19.84375 10.96875 -20.03125 C 10.882812 -20.21875 10.765625 -20.375 10.609375 -20.5 C 10.453125 -20.632812 10.265625 -20.703125 10.046875 -20.703125 C 9.785156 -20.703125 9.570312 -20.597656 9.40625 -20.390625 C 9.238281 -20.191406 9.113281 -19.972656 9.03125 -19.734375 C 8.957031 -19.503906 8.90625 -19.265625 8.875 -19.015625 C 8.84375 -18.773438 8.828125 -18.523438 8.828125 -18.265625 L 8.828125 -16.1875 L 13.5 -16.1875 L 13.5 -13.125 L 8.828125 -13.125 L 8.828125 0 Z M 5.28125 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-29">
-<path style="stroke:none;" d="M 2.6875 -3.484375 L 1.09375 -6.234375 L 10.59375 -10.875 L 1.09375 -15.515625 L 2.6875 -18.265625 L 14.109375 -12.609375 L 14.109375 -9.15625 Z M 2.6875 -3.484375 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-30">
-<path style="stroke:none;" d="M 3.90625 -14.65625 L 0.859375 -16.546875 L 6.109375 -23.390625 L 9.890625 -23.390625 L 15.140625 -16.546875 L 12.09375 -14.65625 L 8 -20.640625 Z M 3.90625 -14.65625 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-31">
-<path style="stroke:none;" d="M 1.890625 0 L 1.890625 -2.375 C 1.890625 -3.5625 1.960938 -4.753906 2.109375 -5.953125 C 2.265625 -7.148438 2.445312 -8.332031 2.65625 -9.5 C 2.863281 -10.675781 3.097656 -11.847656 3.359375 -13.015625 C 3.617188 -14.191406 3.882812 -15.363281 4.15625 -16.53125 C 4.4375 -17.707031 4.722656 -18.878906 5.015625 -20.046875 C 5.316406 -21.210938 5.628906 -22.367188 5.953125 -23.515625 L 10.046875 -23.515625 C 10.367188 -22.367188 10.675781 -21.210938 10.96875 -20.046875 C 11.269531 -18.878906 11.554688 -17.707031 11.828125 -16.53125 C 12.109375 -15.363281 12.378906 -14.191406 12.640625 -13.015625 C 12.898438 -11.847656 13.132812 -10.675781 13.34375 -9.5 C 13.550781 -8.332031 13.726562 -7.148438 13.875 -5.953125 C 14.03125 -4.753906 14.109375 -3.5625 14.109375 -2.375 L 14.109375 0 L 10.5625 0 L 10.5625 -2.375 C 10.5625 -2.863281 10.546875 -3.363281 10.515625 -3.875 C 10.484375 -4.382812 10.445312 -4.894531 10.40625 -5.40625 L 5.59375 -5.40625 C 5.550781 -4.894531 5.515625 -4.382812 5.484375 -3.875 C 5.453125 -3.363281 5.4375 -2.863281 5.4375 -2.375 L 5.4375 0 Z M 10.046875 -8.484375 C 9.785156 -10.359375 9.476562 -12.222656 9.125 -14.078125 C 8.78125 -15.929688 8.40625 -17.785156 8 -19.640625 C 7.59375 -17.785156 7.210938 -15.929688 6.859375 -14.078125 C 6.515625 -12.222656 6.210938 -10.359375 5.953125 -8.484375 Z M 10.046875 -8.484375 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-32">
-<path style="stroke:none;" d="M 1.890625 0 L 1.890625 -23.515625 L 5.4375 -23.515625 L 5.4375 -14.34375 C 5.613281 -14.757812 5.828125 -15.148438 6.078125 -15.515625 C 6.335938 -15.878906 6.640625 -16.191406 6.984375 -16.453125 C 7.335938 -16.722656 7.726562 -16.914062 8.15625 -17.03125 C 8.582031 -17.15625 9.019531 -17.21875 9.46875 -17.21875 C 9.976562 -17.21875 10.476562 -17.128906 10.96875 -16.953125 C 11.46875 -16.785156 11.898438 -16.53125 12.265625 -16.1875 C 12.640625 -15.851562 12.941406 -15.453125 13.171875 -14.984375 C 13.410156 -14.523438 13.597656 -14.050781 13.734375 -13.5625 C 13.878906 -13.070312 13.976562 -12.578125 14.03125 -12.078125 C 14.082031 -11.578125 14.109375 -11.070312 14.109375 -10.5625 L 14.109375 0 L 10.5625 0 L 10.5625 -10.5625 C 10.5625 -10.832031 10.546875 -11.109375 10.515625 -11.390625 C 10.484375 -11.671875 10.425781 -11.941406 10.34375 -12.203125 C 10.269531 -12.472656 10.164062 -12.722656 10.03125 -12.953125 C 9.894531 -13.191406 9.722656 -13.40625 9.515625 -13.59375 C 9.316406 -13.789062 9.082031 -13.929688 8.8125 -14.015625 C 8.550781 -14.097656 8.28125 -14.140625 8 -14.140625 C 7.71875 -14.140625 7.441406 -14.097656 7.171875 -14.015625 C 6.910156 -13.929688 6.675781 -13.789062 6.46875 -13.59375 C 6.269531 -13.40625 6.101562 -13.191406 5.96875 -12.953125 C 5.832031 -12.722656 5.722656 -12.472656 5.640625 -12.203125 C 5.566406 -11.941406 5.515625 -11.671875 5.484375 -11.390625 C 5.453125 -11.109375 5.4375 -10.832031 5.4375 -10.5625 L 5.4375 0 Z M 1.890625 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-33">
-<path style="stroke:none;" d="M 3.9375 0 L 3.9375 -3.078125 L 6.203125 -3.078125 L 6.203125 -20.453125 L 3.9375 -20.453125 L 3.9375 -23.515625 L 12.0625 -23.515625 L 12.0625 -20.453125 L 9.796875 -20.453125 L 9.796875 -3.078125 L 12.0625 -3.078125 L 12.0625 0 Z M 3.9375 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-34">
-<path style="stroke:none;" d="M 1.890625 6.5625 L 1.890625 -16.953125 L 5.4375 -16.953125 L 5.4375 -14.34375 C 5.613281 -14.757812 5.828125 -15.148438 6.078125 -15.515625 C 6.335938 -15.878906 6.644531 -16.191406 7 -16.453125 C 7.363281 -16.722656 7.757812 -16.914062 8.1875 -17.03125 C 8.613281 -17.15625 9.050781 -17.21875 9.5 -17.21875 C 10.007812 -17.21875 10.515625 -17.132812 11.015625 -16.96875 C 11.523438 -16.8125 11.972656 -16.566406 12.359375 -16.234375 C 12.753906 -15.910156 13.085938 -15.519531 13.359375 -15.0625 C 13.628906 -14.601562 13.835938 -14.125 13.984375 -13.625 C 14.128906 -13.132812 14.226562 -12.628906 14.28125 -12.109375 C 14.34375 -11.585938 14.375 -11.070312 14.375 -10.5625 L 14.375 -6.40625 C 14.375 -5.882812 14.34375 -5.363281 14.28125 -4.84375 C 14.226562 -4.320312 14.128906 -3.816406 13.984375 -3.328125 C 13.835938 -2.835938 13.628906 -2.363281 13.359375 -1.90625 C 13.085938 -1.445312 12.753906 -1.050781 12.359375 -0.71875 C 11.972656 -0.382812 11.523438 -0.140625 11.015625 0.015625 C 10.515625 0.171875 10.007812 0.25 9.5 0.25 C 9.050781 0.25 8.613281 0.191406 8.1875 0.078125 C 7.757812 -0.0351562 7.363281 -0.222656 7 -0.484375 C 6.644531 -0.753906 6.335938 -1.070312 6.078125 -1.4375 C 5.828125 -1.800781 5.613281 -2.195312 5.4375 -2.625 L 5.4375 6.5625 Z M 8.03125 -2.8125 C 8.457031 -2.8125 8.867188 -2.910156 9.265625 -3.109375 C 9.660156 -3.316406 9.972656 -3.597656 10.203125 -3.953125 C 10.441406 -4.304688 10.601562 -4.695312 10.6875 -5.125 C 10.769531 -5.550781 10.8125 -5.976562 10.8125 -6.40625 L 10.8125 -10.5625 C 10.8125 -10.988281 10.769531 -11.414062 10.6875 -11.84375 C 10.601562 -12.269531 10.441406 -12.65625 10.203125 -13 C 9.972656 -13.351562 9.660156 -13.628906 9.265625 -13.828125 C 8.867188 -14.035156 8.457031 -14.140625 8.03125 -14.140625 C 7.75 -14.140625 7.472656 -14.097656 7.203125 -14.015625 C 6.941406 -13.929688 6.703125 -13.796875 6.484375 -13.609375 C 6.273438 -13.429688 6.101562 -13.21875 5.96875 -12.96875 C 5.832031 -12.726562 5.722656 -12.472656 5.640625 -12.203125 C 5.566406 -11.941406 5.515625 -11.671875 5.484375 -11.390625 C 5.453125 -11.109375 5.4375 -10.832031 5.4375 -10.5625 L 5.4375 -6.40625 C 5.4375 -6.125 5.453125 -5.84375 5.484375 -5.5625 C 5.515625 -5.289062 5.566406 -5.019531 5.640625 -4.75 C 5.722656 -4.488281 5.832031 -4.234375 5.96875 -3.984375 C 6.101562 -3.734375 6.273438 -3.519531 6.484375 -3.34375 C 6.703125 -3.164062 6.941406 -3.03125 7.203125 -2.9375 C 7.472656 -2.851562 7.75 -2.8125 8.03125 -2.8125 Z M 8.03125 -2.8125 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-35">
-<path style="stroke:none;" d="M 6.53125 0.25 C 6.019531 0.25 5.515625 0.164062 5.015625 0 C 4.523438 -0.164062 4.09375 -0.421875 3.71875 -0.765625 C 3.351562 -1.109375 3.050781 -1.507812 2.8125 -1.96875 C 2.582031 -2.425781 2.394531 -2.898438 2.25 -3.390625 C 2.113281 -3.878906 2.019531 -4.375 1.96875 -4.875 C 1.914062 -5.375 1.890625 -5.882812 1.890625 -6.40625 L 1.890625 -16.953125 L 5.4375 -16.953125 L 5.4375 -6.40625 C 5.4375 -6.125 5.453125 -5.84375 5.484375 -5.5625 C 5.515625 -5.289062 5.566406 -5.019531 5.640625 -4.75 C 5.722656 -4.488281 5.832031 -4.238281 5.96875 -4 C 6.101562 -3.757812 6.269531 -3.546875 6.46875 -3.359375 C 6.675781 -3.171875 6.910156 -3.03125 7.171875 -2.9375 C 7.441406 -2.851562 7.71875 -2.8125 8 -2.8125 C 8.28125 -2.8125 8.550781 -2.851562 8.8125 -2.9375 C 9.082031 -3.03125 9.316406 -3.171875 9.515625 -3.359375 C 9.722656 -3.546875 9.894531 -3.757812 10.03125 -4 C 10.164062 -4.238281 10.269531 -4.488281 10.34375 -4.75 C 10.425781 -5.019531 10.484375 -5.289062 10.515625 -5.5625 C 10.546875 -5.84375 10.5625 -6.125 10.5625 -6.40625 L 10.5625 -16.953125 L 14.109375 -16.953125 L 14.109375 0 L 10.5625 0 L 10.5625 -2.625 C 10.382812 -2.195312 10.164062 -1.800781 9.90625 -1.4375 C 9.65625 -1.070312 9.351562 -0.753906 9 -0.484375 C 8.65625 -0.222656 8.269531 -0.0351562 7.84375 0.078125 C 7.414062 0.191406 6.976562 0.25 6.53125 0.25 Z M 6.53125 0.25 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-36">
-<path style="stroke:none;" d="M 2.140625 0 L 2.140625 -23.515625 L 14.109375 -23.515625 L 14.109375 -20.453125 L 5.703125 -20.453125 L 5.703125 -13.765625 L 12.28125 -13.765625 L 12.28125 -10.6875 L 5.703125 -10.6875 L 5.703125 -3.078125 L 14.109375 -3.078125 L 14.109375 0 Z M 2.140625 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-37">
-<path style="stroke:none;" d="M 1.890625 0 L 1.890625 -0.03125 C 1.890625 -0.582031 1.898438 -1.140625 1.921875 -1.703125 C 1.941406 -2.273438 1.988281 -2.832031 2.0625 -3.375 C 2.132812 -3.914062 2.242188 -4.457031 2.390625 -5 C 2.546875 -5.550781 2.738281 -6.078125 2.96875 -6.578125 C 3.207031 -7.078125 3.476562 -7.5625 3.78125 -8.03125 C 4.09375 -8.5 4.429688 -8.941406 4.796875 -9.359375 C 5.160156 -9.773438 5.550781 -10.171875 5.96875 -10.546875 C 6.382812 -10.921875 6.816406 -11.273438 7.265625 -11.609375 C 7.710938 -11.953125 8.148438 -12.304688 8.578125 -12.671875 C 9.003906 -13.035156 9.378906 -13.441406 9.703125 -13.890625 C 10.035156 -14.335938 10.28125 -14.832031 10.4375 -15.375 C 10.601562 -15.914062 10.6875 -16.46875 10.6875 -17.03125 C 10.6875 -17.320312 10.671875 -17.601562 10.640625 -17.875 C 10.609375 -18.15625 10.550781 -18.4375 10.46875 -18.71875 C 10.382812 -19 10.269531 -19.257812 10.125 -19.5 C 9.988281 -19.75 9.8125 -19.96875 9.59375 -20.15625 C 9.382812 -20.351562 9.140625 -20.492188 8.859375 -20.578125 C 8.585938 -20.660156 8.3125 -20.703125 8.03125 -20.703125 C 7.644531 -20.703125 7.269531 -20.625 6.90625 -20.46875 C 6.550781 -20.320312 6.257812 -20.097656 6.03125 -19.796875 C 5.8125 -19.503906 5.644531 -19.171875 5.53125 -18.796875 C 5.425781 -18.421875 5.375 -18.039062 5.375 -17.65625 C 5.375 -17.65625 5.375 -17.644531 5.375 -17.625 L 1.828125 -17.625 C 1.828125 -17.644531 1.828125 -17.65625 1.828125 -17.65625 C 1.828125 -17.65625 1.828125 -17.671875 1.828125 -17.703125 C 1.828125 -18.234375 1.882812 -18.769531 2 -19.3125 C 2.113281 -19.851562 2.296875 -20.363281 2.546875 -20.84375 C 2.804688 -21.320312 3.132812 -21.757812 3.53125 -22.15625 C 3.925781 -22.550781 4.363281 -22.867188 4.84375 -23.109375 C 5.320312 -23.359375 5.832031 -23.53125 6.375 -23.625 C 6.925781 -23.726562 7.476562 -23.78125 8.03125 -23.78125 C 8.601562 -23.78125 9.175781 -23.71875 9.75 -23.59375 C 10.332031 -23.476562 10.875 -23.273438 11.375 -22.984375 C 11.875 -22.703125 12.316406 -22.335938 12.703125 -21.890625 C 13.085938 -21.441406 13.394531 -20.953125 13.625 -20.421875 C 13.863281 -19.898438 14.023438 -19.347656 14.109375 -18.765625 C 14.191406 -18.179688 14.234375 -17.601562 14.234375 -17.03125 C 14.234375 -16.320312 14.144531 -15.625 13.96875 -14.9375 C 13.789062 -14.257812 13.519531 -13.613281 13.15625 -13 C 12.789062 -12.394531 12.351562 -11.84375 11.84375 -11.34375 C 11.332031 -10.84375 10.804688 -10.367188 10.265625 -9.921875 C 9.734375 -9.472656 9.210938 -9.003906 8.703125 -8.515625 C 8.191406 -8.023438 7.726562 -7.492188 7.3125 -6.921875 C 6.894531 -6.359375 6.539062 -5.75 6.25 -5.09375 C 5.96875 -4.445312 5.769531 -3.773438 5.65625 -3.078125 L 14.109375 -3.078125 L 14.109375 0 Z M 1.890625 0 "/>
-</symbol>
-<symbol overflow="visible" id="glyph1-38">
-<path style="stroke:none;" d="M 8.0625 0.25 C 7.507812 0.25 6.945312 0.203125 6.375 0.109375 C 5.8125 0.015625 5.28125 -0.148438 4.78125 -0.390625 C 4.28125 -0.640625 3.820312 -0.960938 3.40625 -1.359375 C 2.988281 -1.753906 2.640625 -2.203125 2.359375 -2.703125 C 2.085938 -3.203125 1.882812 -3.726562 1.75 -4.28125 C 1.625 -4.84375 1.5625 -5.398438 1.5625 -5.953125 L 5.125 -5.953125 C 5.125 -5.546875 5.1875 -5.144531 5.3125 -4.75 C 5.4375 -4.351562 5.628906 -4 5.890625 -3.6875 C 6.160156 -3.382812 6.488281 -3.160156 6.875 -3.015625 C 7.257812 -2.878906 7.65625 -2.8125 8.0625 -2.8125 C 8.507812 -2.8125 8.9375 -2.898438 9.34375 -3.078125 C 9.75 -3.265625 10.078125 -3.535156 10.328125 -3.890625 C 10.585938 -4.242188 10.773438 -4.632812 10.890625 -5.0625 C 11.015625 -5.488281 11.078125 -5.914062 11.078125 -6.34375 C 11.078125 -6.757812 11.007812 -7.179688 10.875 -7.609375 C 10.75 -8.035156 10.5625 -8.425781 10.3125 -8.78125 C 10.070312 -9.132812 9.769531 -9.429688 9.40625 -9.671875 C 9.039062 -9.921875 8.65625 -10.109375 8.25 -10.234375 C 7.84375 -10.367188 7.421875 -10.453125 6.984375 -10.484375 C 6.554688 -10.515625 6.128906 -10.53125 5.703125 -10.53125 L 5.703125 -13.59375 C 6.078125 -13.59375 6.46875 -13.609375 6.875 -13.640625 C 7.28125 -13.679688 7.671875 -13.765625 8.046875 -13.890625 C 8.421875 -14.015625 8.765625 -14.191406 9.078125 -14.421875 C 9.398438 -14.660156 9.671875 -14.941406 9.890625 -15.265625 C 10.117188 -15.585938 10.285156 -15.941406 10.390625 -16.328125 C 10.503906 -16.722656 10.5625 -17.113281 10.5625 -17.5 C 10.5625 -17.882812 10.515625 -18.265625 10.421875 -18.640625 C 10.335938 -19.015625 10.195312 -19.359375 10 -19.671875 C 9.800781 -19.992188 9.523438 -20.242188 9.171875 -20.421875 C 8.816406 -20.609375 8.457031 -20.703125 8.09375 -20.703125 C 7.726562 -20.703125 7.375 -20.617188 7.03125 -20.453125 C 6.695312 -20.296875 6.425781 -20.066406 6.21875 -19.765625 C 6.019531 -19.472656 5.867188 -19.144531 5.765625 -18.78125 C 5.671875 -18.414062 5.625 -18.054688 5.625 -17.703125 C 5.625 -17.703125 5.625 -17.6875 5.625 -17.65625 L 2.078125 -17.65625 C 2.078125 -17.6875 2.078125 -17.707031 2.078125 -17.71875 C 2.078125 -17.726562 2.078125 -17.734375 2.078125 -17.734375 C 2.078125 -18.265625 2.132812 -18.796875 2.25 -19.328125 C 2.375 -19.859375 2.550781 -20.363281 2.78125 -20.84375 C 3.019531 -21.320312 3.328125 -21.753906 3.703125 -22.140625 C 4.085938 -22.523438 4.515625 -22.84375 4.984375 -23.09375 C 5.460938 -23.351562 5.96875 -23.53125 6.5 -23.625 C 7.03125 -23.726562 7.5625 -23.78125 8.09375 -23.78125 C 8.625 -23.78125 9.160156 -23.722656 9.703125 -23.609375 C 10.253906 -23.503906 10.765625 -23.316406 11.234375 -23.046875 C 11.703125 -22.785156 12.125 -22.453125 12.5 -22.046875 C 12.875 -21.640625 13.175781 -21.191406 13.40625 -20.703125 C 13.644531 -20.210938 13.820312 -19.703125 13.9375 -19.171875 C 14.050781 -18.640625 14.109375 -18.09375 14.109375 -17.53125 C 14.109375 -16.976562 14.039062 -16.425781 13.90625 -15.875 C 13.769531 -15.320312 13.550781 -14.804688 13.25 -14.328125 C 12.945312 -13.859375 12.582031 -13.429688 12.15625 -13.046875 C 11.726562 -12.671875 11.257812 -12.363281 10.75 -12.125 C 11.320312 -11.894531 11.851562 -11.582031 12.34375 -11.1875 C 12.84375 -10.789062 13.257812 -10.335938 13.59375 -9.828125 C 13.9375 -9.316406 14.191406 -8.753906 14.359375 -8.140625 C 14.535156 -7.535156 14.625 -6.921875 14.625 -6.296875 C 14.625 -5.722656 14.554688 -5.148438 14.421875 -4.578125 C 14.296875 -4.003906 14.097656 -3.453125 13.828125 -2.921875 C 13.566406 -2.398438 13.234375 -1.929688 12.828125 -1.515625 C 12.421875 -1.097656 11.960938 -0.75 11.453125 -0.46875 C 10.941406 -0.195312 10.394531 -0.0078125 9.8125 0.09375 C 9.238281 0.195312 8.65625 0.25 8.0625 0.25 Z M 8.0625 0.25 "/>
-</symbol>
-</g>
-</defs>
-<g id="surface3508">
-<rect x="0" y="0" width="2163" height="1314" style="fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;stroke:none;"/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 82 1067 L 98 1067 L 98 1108 L 82 1108 Z M 82 1067 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(24.705882%,24.705882%,24.705882%);fill-opacity:1;" d="M 18 1 L 1714 1 L 1714 42 L 18 42 Z M 18 1 "/>
-<g style="fill:rgb(74.901961%,74.901961%,74.901961%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="18" y="33"/>
-  <use xlink:href="#glyph0-2" x="34" y="33"/>
-  <use xlink:href="#glyph0-3" x="50" y="33"/>
-  <use xlink:href="#glyph0-4" x="66" y="33"/>
-  <use xlink:href="#glyph0-5" x="82" y="33"/>
-  <use xlink:href="#glyph0-6" x="98" y="33"/>
-  <use xlink:href="#glyph0-7" x="114" y="33"/>
-  <use xlink:href="#glyph0-1" x="130" y="33"/>
-  <use xlink:href="#glyph0-2" x="146" y="33"/>
-  <use xlink:href="#glyph0-8" x="162" y="33"/>
-  <use xlink:href="#glyph0-9" x="178" y="33"/>
-  <use xlink:href="#glyph0-4" x="194" y="33"/>
-  <use xlink:href="#glyph0-10" x="210" y="33"/>
-  <use xlink:href="#glyph0-7" x="226" y="33"/>
-  <use xlink:href="#glyph0-1" x="242" y="33"/>
-  <use xlink:href="#glyph0-2" x="258" y="33"/>
-  <use xlink:href="#glyph0-11" x="274" y="33"/>
-  <use xlink:href="#glyph0-4" x="290" y="33"/>
-  <use xlink:href="#glyph0-6" x="306" y="33"/>
-  <use xlink:href="#glyph0-12" x="322" y="33"/>
-  <use xlink:href="#glyph0-7" x="338" y="33"/>
-  <use xlink:href="#glyph0-1" x="354" y="33"/>
-  <use xlink:href="#glyph0-2" x="370" y="33"/>
-  <use xlink:href="#glyph0-13" x="386" y="33"/>
-  <use xlink:href="#glyph0-4" x="402" y="33"/>
-  <use xlink:href="#glyph0-14" x="418" y="33"/>
-  <use xlink:href="#glyph0-9" x="434" y="33"/>
-  <use xlink:href="#glyph0-15" x="450" y="33"/>
-  <use xlink:href="#glyph0-12" x="466" y="33"/>
-  <use xlink:href="#glyph0-16" x="482" y="33"/>
-  <use xlink:href="#glyph0-7" x="498" y="33"/>
-  <use xlink:href="#glyph0-1" x="514" y="33"/>
-  <use xlink:href="#glyph0-2" x="530" y="33"/>
-  <use xlink:href="#glyph0-17" x="546" y="33"/>
-  <use xlink:href="#glyph0-6" x="562" y="33"/>
-  <use xlink:href="#glyph0-5" x="578" y="33"/>
-  <use xlink:href="#glyph0-18" x="594" y="33"/>
-  <use xlink:href="#glyph0-7" x="610" y="33"/>
-  <use xlink:href="#glyph0-1" x="626" y="33"/>
-  <use xlink:href="#glyph0-1" x="642" y="33"/>
-  <use xlink:href="#glyph0-1" x="658" y="33"/>
-  <use xlink:href="#glyph0-1" x="674" y="33"/>
-  <use xlink:href="#glyph0-1" x="690" y="33"/>
-  <use xlink:href="#glyph0-1" x="706" y="33"/>
-  <use xlink:href="#glyph0-1" x="722" y="33"/>
-  <use xlink:href="#glyph0-1" x="738" y="33"/>
-  <use xlink:href="#glyph0-1" x="754" y="33"/>
-  <use xlink:href="#glyph0-1" x="770" y="33"/>
-  <use xlink:href="#glyph0-1" x="786" y="33"/>
-  <use xlink:href="#glyph0-1" x="802" y="33"/>
-  <use xlink:href="#glyph0-1" x="818" y="33"/>
-  <use xlink:href="#glyph0-1" x="834" y="33"/>
-  <use xlink:href="#glyph0-1" x="850" y="33"/>
-  <use xlink:href="#glyph0-1" x="866" y="33"/>
-  <use xlink:href="#glyph0-1" x="882" y="33"/>
-  <use xlink:href="#glyph0-1" x="898" y="33"/>
-  <use xlink:href="#glyph0-1" x="914" y="33"/>
-  <use xlink:href="#glyph0-1" x="930" y="33"/>
-  <use xlink:href="#glyph0-1" x="946" y="33"/>
-  <use xlink:href="#glyph0-1" x="962" y="33"/>
-  <use xlink:href="#glyph0-1" x="978" y="33"/>
-  <use xlink:href="#glyph0-1" x="994" y="33"/>
-  <use xlink:href="#glyph0-1" x="1010" y="33"/>
-  <use xlink:href="#glyph0-1" x="1026" y="33"/>
-  <use xlink:href="#glyph0-1" x="1042" y="33"/>
-  <use xlink:href="#glyph0-1" x="1058" y="33"/>
-  <use xlink:href="#glyph0-1" x="1074" y="33"/>
-  <use xlink:href="#glyph0-1" x="1090" y="33"/>
-  <use xlink:href="#glyph0-1" x="1106" y="33"/>
-  <use xlink:href="#glyph0-1" x="1122" y="33"/>
-  <use xlink:href="#glyph0-1" x="1138" y="33"/>
-  <use xlink:href="#glyph0-1" x="1154" y="33"/>
-  <use xlink:href="#glyph0-1" x="1170" y="33"/>
-  <use xlink:href="#glyph0-1" x="1186" y="33"/>
-  <use xlink:href="#glyph0-1" x="1202" y="33"/>
-  <use xlink:href="#glyph0-1" x="1218" y="33"/>
-  <use xlink:href="#glyph0-1" x="1234" y="33"/>
-  <use xlink:href="#glyph0-1" x="1250" y="33"/>
-  <use xlink:href="#glyph0-1" x="1266" y="33"/>
-  <use xlink:href="#glyph0-1" x="1282" y="33"/>
-  <use xlink:href="#glyph0-1" x="1298" y="33"/>
-  <use xlink:href="#glyph0-1" x="1314" y="33"/>
-  <use xlink:href="#glyph0-1" x="1330" y="33"/>
-  <use xlink:href="#glyph0-1" x="1346" y="33"/>
-  <use xlink:href="#glyph0-1" x="1362" y="33"/>
-  <use xlink:href="#glyph0-1" x="1378" y="33"/>
-  <use xlink:href="#glyph0-1" x="1394" y="33"/>
-  <use xlink:href="#glyph0-1" x="1410" y="33"/>
-  <use xlink:href="#glyph0-1" x="1426" y="33"/>
-  <use xlink:href="#glyph0-1" x="1442" y="33"/>
-  <use xlink:href="#glyph0-1" x="1458" y="33"/>
-  <use xlink:href="#glyph0-1" x="1474" y="33"/>
-  <use xlink:href="#glyph0-1" x="1490" y="33"/>
-  <use xlink:href="#glyph0-1" x="1506" y="33"/>
-  <use xlink:href="#glyph0-1" x="1522" y="33"/>
-  <use xlink:href="#glyph0-1" x="1538" y="33"/>
-  <use xlink:href="#glyph0-1" x="1554" y="33"/>
-  <use xlink:href="#glyph0-1" x="1570" y="33"/>
-  <use xlink:href="#glyph0-1" x="1586" y="33"/>
-  <use xlink:href="#glyph0-1" x="1602" y="33"/>
-  <use xlink:href="#glyph0-1" x="1618" y="33"/>
-  <use xlink:href="#glyph0-1" x="1634" y="33"/>
-  <use xlink:href="#glyph0-1" x="1650" y="33"/>
-  <use xlink:href="#glyph0-1" x="1666" y="33"/>
-  <use xlink:href="#glyph0-1" x="1682" y="33"/>
-  <use xlink:href="#glyph0-1" x="1698" y="33"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(24.705882%,24.705882%,24.705882%);fill-opacity:1;" d="M 1714 1 L 1954 1 L 1954 42 L 1714 42 Z M 1714 1 "/>
-<g style="fill:rgb(77.647059%,47.058824%,86.666667%);fill-opacity:1;">
-  <use xlink:href="#glyph1-1" x="1714" y="33"/>
-  <use xlink:href="#glyph1-2" x="1730" y="33"/>
-  <use xlink:href="#glyph1-3" x="1746" y="33"/>
-  <use xlink:href="#glyph1-4" x="1762" y="33"/>
-  <use xlink:href="#glyph1-5" x="1778" y="33"/>
-  <use xlink:href="#glyph1-5" x="1794" y="33"/>
-  <use xlink:href="#glyph1-6" x="1810" y="33"/>
-  <use xlink:href="#glyph1-7" x="1826" y="33"/>
-  <use xlink:href="#glyph1-8" x="1842" y="33"/>
-  <use xlink:href="#glyph1-9" x="1858" y="33"/>
-  <use xlink:href="#glyph1-10" x="1874" y="33"/>
-  <use xlink:href="#glyph1-11" x="1890" y="33"/>
-  <use xlink:href="#glyph1-9" x="1906" y="33"/>
-  <use xlink:href="#glyph1-12" x="1922" y="33"/>
-  <use xlink:href="#glyph1-5" x="1938" y="33"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(24.705882%,24.705882%,24.705882%);fill-opacity:1;" d="M 1954 1 L 1986 1 L 1986 42 L 1954 42 Z M 1954 1 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1986 1 L 2002 1 L 2002 42 L 1986 42 Z M 1986 1 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 2002 1 L 2082 1 L 2082 42 L 2002 42 Z M 2002 1 "/>
-<g style="fill:rgb(87.45098%,87.45098%,87.45098%);fill-opacity:1;">
-  <use xlink:href="#glyph0-17" x="2002" y="33"/>
-  <use xlink:href="#glyph0-6" x="2018" y="33"/>
-  <use xlink:href="#glyph0-5" x="2034" y="33"/>
-  <use xlink:href="#glyph0-18" x="2050" y="33"/>
-  <use xlink:href="#glyph0-1" x="2066" y="33"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 2082 1 L 2114 1 L 2114 42 L 2082 42 Z M 2082 1 "/>
-<g style="fill:rgb(66.27451%,63.137255%,88.235294%);fill-opacity:1;">
-  <use xlink:href="#glyph1-13" x="2082" y="33"/>
-  <use xlink:href="#glyph1-14" x="2098" y="33"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 2114 1 L 2130 1 L 2130 42 L 2114 42 Z M 2114 1 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(24.705882%,24.705882%,24.705882%);fill-opacity:1;" d="M 2130 1 L 2146 1 L 2146 42 L 2130 42 Z M 2130 1 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 1 L 2162 1 L 2162 42 L 2146 42 Z M 2146 1 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 1 L 18 1 L 18 42 L 1 42 Z M 1 1 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 18 42 L 290 42 L 290 83 L 18 83 Z M 18 42 "/>
-<g style="fill:rgb(74.901961%,74.901961%,74.901961%);fill-opacity:1;">
-  <use xlink:href="#glyph1-15" x="18" y="74"/>
-  <use xlink:href="#glyph1-2" x="34" y="74"/>
-  <use xlink:href="#glyph1-16" x="50" y="74"/>
-  <use xlink:href="#glyph1-5" x="66" y="74"/>
-  <use xlink:href="#glyph1-7" x="82" y="74"/>
-  <use xlink:href="#glyph1-7" x="98" y="74"/>
-  <use xlink:href="#glyph1-7" x="114" y="74"/>
-  <use xlink:href="#glyph1-7" x="130" y="74"/>
-  <use xlink:href="#glyph1-7" x="146" y="74"/>
-  <use xlink:href="#glyph1-7" x="162" y="74"/>
-  <use xlink:href="#glyph1-7" x="178" y="74"/>
-  <use xlink:href="#glyph1-7" x="194" y="74"/>
-  <use xlink:href="#glyph1-7" x="210" y="74"/>
-  <use xlink:href="#glyph1-7" x="226" y="74"/>
-  <use xlink:href="#glyph1-7" x="242" y="74"/>
-  <use xlink:href="#glyph1-7" x="258" y="74"/>
-  <use xlink:href="#glyph1-7" x="274" y="74"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 290 42 L 322 42 L 322 83 L 290 83 Z M 290 42 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 322 42 L 370 42 L 370 83 L 322 83 Z M 322 42 "/>
-<g style="fill:rgb(74.901961%,74.901961%,74.901961%);fill-opacity:1;">
-  <use xlink:href="#glyph1-17" x="322" y="74"/>
-  <use xlink:href="#glyph1-5" x="338" y="74"/>
-  <use xlink:href="#glyph1-18" x="354" y="74"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 370 42 L 402 42 L 402 83 L 370 83 Z M 370 42 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 402 42 L 482 42 L 482 83 L 402 83 Z M 402 42 "/>
-<g style="fill:rgb(74.901961%,74.901961%,74.901961%);fill-opacity:1;">
-  <use xlink:href="#glyph1-19" x="402" y="74"/>
-  <use xlink:href="#glyph1-9" x="418" y="74"/>
-  <use xlink:href="#glyph1-6" x="434" y="74"/>
-  <use xlink:href="#glyph1-5" x="450" y="74"/>
-  <use xlink:href="#glyph1-7" x="466" y="74"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 482 42 L 514 42 L 514 83 L 482 83 Z M 482 42 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 514 42 L 546 42 L 546 83 L 514 83 Z M 514 42 "/>
-<g style="fill:rgb(74.901961%,74.901961%,74.901961%);fill-opacity:1;">
-  <use xlink:href="#glyph1-20" x="514" y="74"/>
-  <use xlink:href="#glyph1-21" x="530" y="74"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 546 42 L 578 42 L 578 83 L 546 83 Z M 546 42 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 578 42 L 690 42 L 690 83 L 578 83 Z M 578 42 "/>
-<g style="fill:rgb(74.901961%,74.901961%,74.901961%);fill-opacity:1;">
-  <use xlink:href="#glyph1-19" x="578" y="74"/>
-  <use xlink:href="#glyph1-5" x="594" y="74"/>
-  <use xlink:href="#glyph1-11" x="610" y="74"/>
-  <use xlink:href="#glyph1-11" x="626" y="74"/>
-  <use xlink:href="#glyph1-22" x="642" y="74"/>
-  <use xlink:href="#glyph1-3" x="658" y="74"/>
-  <use xlink:href="#glyph1-5" x="674" y="74"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 690 42 L 2146 42 L 2146 83 L 690 83 Z M 690 42 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 42 L 2162 42 L 2162 83 L 2146 83 Z M 2146 42 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 42 L 18 42 L 18 83 L 1 83 Z M 1 42 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 18 83 L 2146 83 L 2146 124 L 18 124 Z M 18 83 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 83 L 2162 83 L 2162 124 L 2146 124 Z M 2146 83 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 83 L 18 83 L 18 124 L 1 124 Z M 1 83 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 18 124 L 2146 124 L 2146 165 L 18 165 Z M 18 124 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 124 L 2162 124 L 2162 165 L 2146 165 Z M 2146 124 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 124 L 18 124 L 18 165 L 1 165 Z M 1 124 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(73.333333%,76.078431%,81.176471%);fill-opacity:1;" d="M 18 165 L 290 165 L 290 206 L 18 206 Z M 18 165 "/>
-<g style="fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;">
-  <use xlink:href="#glyph0-19" x="18" y="197"/>
-  <use xlink:href="#glyph0-20" x="34" y="197"/>
-  <use xlink:href="#glyph0-19" x="50" y="197"/>
-  <use xlink:href="#glyph0-21" x="66" y="197"/>
-  <use xlink:href="#glyph0-21" x="82" y="197"/>
-  <use xlink:href="#glyph0-20" x="98" y="197"/>
-  <use xlink:href="#glyph0-20" x="114" y="197"/>
-  <use xlink:href="#glyph0-22" x="130" y="197"/>
-  <use xlink:href="#glyph0-1" x="146" y="197"/>
-  <use xlink:href="#glyph0-21" x="162" y="197"/>
-  <use xlink:href="#glyph0-23" x="178" y="197"/>
-  <use xlink:href="#glyph0-24" x="194" y="197"/>
-  <use xlink:href="#glyph0-23" x="210" y="197"/>
-  <use xlink:href="#glyph0-21" x="226" y="197"/>
-  <use xlink:href="#glyph0-24" x="242" y="197"/>
-  <use xlink:href="#glyph0-21" x="258" y="197"/>
-  <use xlink:href="#glyph0-20" x="274" y="197"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 290 165 L 322 165 L 322 206 L 290 206 Z M 290 165 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 322 165 L 370 165 L 370 206 L 322 206 Z M 322 165 "/>
-<g style="fill:rgb(40%,60%,80%);fill-opacity:1;">
-  <use xlink:href="#glyph0-25" x="322" y="197"/>
-  <use xlink:href="#glyph0-26" x="338" y="197"/>
-  <use xlink:href="#glyph0-27" x="354" y="197"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 370 165 L 402 165 L 402 206 L 370 206 Z M 370 165 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 402 165 L 482 165 L 482 206 L 402 206 Z M 402 165 "/>
-<g style="fill:rgb(40%,80%,80%);fill-opacity:1;">
-  <use xlink:href="#glyph0-28" x="402" y="197"/>
-  <use xlink:href="#glyph0-6" x="418" y="197"/>
-  <use xlink:href="#glyph0-29" x="434" y="197"/>
-  <use xlink:href="#glyph0-9" x="450" y="197"/>
-  <use xlink:href="#glyph0-1" x="466" y="197"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 482 165 L 514 165 L 514 206 L 482 206 Z M 482 165 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 514 165 L 546 165 L 546 206 L 514 206 Z M 514 165 "/>
-<g style="fill:rgb(59.607843%,74.509804%,39.607843%);fill-opacity:1;">
-  <use xlink:href="#glyph0-23" x="514" y="197"/>
-  <use xlink:href="#glyph0-22" x="530" y="197"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 546 165 L 2146 165 L 2146 206 L 546 206 Z M 546 165 "/>
-<g style="fill:rgb(87.45098%,87.45098%,87.45098%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="546" y="197"/>
-  <use xlink:href="#glyph0-1" x="562" y="197"/>
-  <use xlink:href="#glyph0-30" x="578" y="197"/>
-  <use xlink:href="#glyph0-31" x="594" y="197"/>
-  <use xlink:href="#glyph0-32" x="610" y="197"/>
-  <use xlink:href="#glyph0-33" x="626" y="197"/>
-  <use xlink:href="#glyph0-34" x="642" y="197"/>
-  <use xlink:href="#glyph0-29" x="658" y="197"/>
-  <use xlink:href="#glyph0-35" x="674" y="197"/>
-  <use xlink:href="#glyph0-33" x="690" y="197"/>
-  <use xlink:href="#glyph0-34" x="706" y="197"/>
-  <use xlink:href="#glyph0-20" x="722" y="197"/>
-  <use xlink:href="#glyph0-23" x="738" y="197"/>
-  <use xlink:href="#glyph0-33" x="754" y="197"/>
-  <use xlink:href="#glyph0-34" x="770" y="197"/>
-  <use xlink:href="#glyph0-21" x="786" y="197"/>
-  <use xlink:href="#glyph0-20" x="802" y="197"/>
-  <use xlink:href="#glyph0-33" x="818" y="197"/>
-  <use xlink:href="#glyph0-34" x="834" y="197"/>
-  <use xlink:href="#glyph0-20" x="850" y="197"/>
-  <use xlink:href="#glyph0-21" x="866" y="197"/>
-  <use xlink:href="#glyph0-33" x="882" y="197"/>
-  <use xlink:href="#glyph0-34" x="898" y="197"/>
-  <use xlink:href="#glyph0-21" x="914" y="197"/>
-  <use xlink:href="#glyph0-9" x="930" y="197"/>
-  <use xlink:href="#glyph0-28" x="946" y="197"/>
-  <use xlink:href="#glyph0-33" x="962" y="197"/>
-  <use xlink:href="#glyph0-34" x="978" y="197"/>
-  <use xlink:href="#glyph0-9" x="994" y="197"/>
-  <use xlink:href="#glyph0-20" x="1010" y="197"/>
-  <use xlink:href="#glyph0-33" x="1026" y="197"/>
-  <use xlink:href="#glyph0-34" x="1042" y="197"/>
-  <use xlink:href="#glyph0-36" x="1058" y="197"/>
-  <use xlink:href="#glyph0-30" x="1074" y="197"/>
-  <use xlink:href="#glyph0-33" x="1090" y="197"/>
-  <use xlink:href="#glyph0-34" x="1106" y="197"/>
-  <use xlink:href="#glyph0-21" x="1122" y="197"/>
-  <use xlink:href="#glyph0-37" x="1138" y="197"/>
-  <use xlink:href="#glyph0-38" x="1154" y="197"/>
-  <use xlink:href="#glyph0-33" x="1170" y="197"/>
-  <use xlink:href="#glyph0-34" x="1186" y="197"/>
-  <use xlink:href="#glyph0-6" x="1202" y="197"/>
-  <use xlink:href="#glyph0-39" x="1218" y="197"/>
-  <use xlink:href="#glyph0-33" x="1234" y="197"/>
-  <use xlink:href="#glyph0-34" x="1250" y="197"/>
-  <use xlink:href="#glyph0-20" x="1266" y="197"/>
-  <use xlink:href="#glyph0-6" x="1282" y="197"/>
-  <use xlink:href="#glyph0-27" x="1298" y="197"/>
-  <use xlink:href="#glyph0-33" x="1314" y="197"/>
-  <use xlink:href="#glyph0-34" x="1330" y="197"/>
-  <use xlink:href="#glyph0-40" x="1346" y="197"/>
-  <use xlink:href="#glyph0-30" x="1362" y="197"/>
-  <use xlink:href="#glyph0-41" x="1378" y="197"/>
-  <use xlink:href="#glyph0-33" x="1394" y="197"/>
-  <use xlink:href="#glyph0-34" x="1410" y="197"/>
-  <use xlink:href="#glyph0-20" x="1426" y="197"/>
-  <use xlink:href="#glyph0-6" x="1442" y="197"/>
-  <use xlink:href="#glyph0-33" x="1458" y="197"/>
-  <use xlink:href="#glyph0-14" x="1474" y="197"/>
-  <use xlink:href="#glyph0-33" x="1490" y="197"/>
-  <use xlink:href="#glyph0-34" x="1506" y="197"/>
-  <use xlink:href="#glyph0-20" x="1522" y="197"/>
-  <use xlink:href="#glyph0-42" x="1538" y="197"/>
-  <use xlink:href="#glyph0-17" x="1554" y="197"/>
-  <use xlink:href="#glyph0-6" x="1570" y="197"/>
-  <use xlink:href="#glyph0-5" x="1586" y="197"/>
-  <use xlink:href="#glyph0-5" x="1602" y="197"/>
-  <use xlink:href="#glyph0-15" x="1618" y="197"/>
-  <use xlink:href="#glyph0-1" x="1634" y="197"/>
-  <use xlink:href="#glyph0-13" x="1650" y="197"/>
-  <use xlink:href="#glyph0-15" x="1666" y="197"/>
-  <use xlink:href="#glyph0-43" x="1682" y="197"/>
-  <use xlink:href="#glyph0-5" x="1698" y="197"/>
-  <use xlink:href="#glyph0-9" x="1714" y="197"/>
-  <use xlink:href="#glyph0-44" x="1730" y="197"/>
-  <use xlink:href="#glyph0-7" x="1746" y="197"/>
-  <use xlink:href="#glyph0-33" x="1762" y="197"/>
-  <use xlink:href="#glyph0-34" x="1778" y="197"/>
-  <use xlink:href="#glyph0-6" x="1794" y="197"/>
-  <use xlink:href="#glyph0-22" x="1810" y="197"/>
-  <use xlink:href="#glyph0-33" x="1826" y="197"/>
-  <use xlink:href="#glyph0-34" x="1842" y="197"/>
-  <use xlink:href="#glyph0-36" x="1858" y="197"/>
-  <use xlink:href="#glyph0-20" x="1874" y="197"/>
-  <use xlink:href="#glyph0-45" x="1890" y="197"/>
-  <use xlink:href="#glyph0-32" x="1906" y="197"/>
-  <use xlink:href="#glyph0-31" x="1922" y="197"/>
-  <use xlink:href="#glyph0-1" x="1938" y="197"/>
-  <use xlink:href="#glyph0-1" x="1954" y="197"/>
-  <use xlink:href="#glyph0-1" x="1970" y="197"/>
-  <use xlink:href="#glyph0-1" x="1986" y="197"/>
-  <use xlink:href="#glyph0-1" x="2002" y="197"/>
-  <use xlink:href="#glyph0-1" x="2018" y="197"/>
-  <use xlink:href="#glyph0-1" x="2034" y="197"/>
-  <use xlink:href="#glyph0-1" x="2050" y="197"/>
-  <use xlink:href="#glyph0-1" x="2066" y="197"/>
-  <use xlink:href="#glyph0-1" x="2082" y="197"/>
-  <use xlink:href="#glyph0-1" x="2098" y="197"/>
-  <use xlink:href="#glyph0-1" x="2114" y="197"/>
-  <use xlink:href="#glyph0-1" x="2130" y="197"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 165 L 2162 165 L 2162 206 L 2146 206 Z M 2146 165 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 165 L 18 165 L 18 206 L 1 206 Z M 1 165 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(73.333333%,76.078431%,81.176471%);fill-opacity:1;" d="M 18 206 L 290 206 L 290 247 L 18 247 Z M 18 206 "/>
-<g style="fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;">
-  <use xlink:href="#glyph0-19" x="18" y="238"/>
-  <use xlink:href="#glyph0-20" x="34" y="238"/>
-  <use xlink:href="#glyph0-19" x="50" y="238"/>
-  <use xlink:href="#glyph0-21" x="66" y="238"/>
-  <use xlink:href="#glyph0-21" x="82" y="238"/>
-  <use xlink:href="#glyph0-20" x="98" y="238"/>
-  <use xlink:href="#glyph0-20" x="114" y="238"/>
-  <use xlink:href="#glyph0-22" x="130" y="238"/>
-  <use xlink:href="#glyph0-1" x="146" y="238"/>
-  <use xlink:href="#glyph0-21" x="162" y="238"/>
-  <use xlink:href="#glyph0-23" x="178" y="238"/>
-  <use xlink:href="#glyph0-24" x="194" y="238"/>
-  <use xlink:href="#glyph0-23" x="210" y="238"/>
-  <use xlink:href="#glyph0-21" x="226" y="238"/>
-  <use xlink:href="#glyph0-24" x="242" y="238"/>
-  <use xlink:href="#glyph0-21" x="258" y="238"/>
-  <use xlink:href="#glyph0-20" x="274" y="238"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 290 206 L 322 206 L 322 247 L 290 247 Z M 290 206 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 322 206 L 370 206 L 370 247 L 322 247 Z M 322 206 "/>
-<g style="fill:rgb(40%,60%,80%);fill-opacity:1;">
-  <use xlink:href="#glyph0-25" x="322" y="238"/>
-  <use xlink:href="#glyph0-26" x="338" y="238"/>
-  <use xlink:href="#glyph0-27" x="354" y="238"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 370 206 L 402 206 L 402 247 L 370 247 Z M 370 206 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 402 206 L 482 206 L 482 247 L 402 247 Z M 402 206 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 482 206 L 514 206 L 514 247 L 482 247 Z M 482 206 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 514 206 L 546 206 L 546 247 L 514 247 Z M 514 206 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 546 206 L 2146 206 L 2146 247 L 546 247 Z M 546 206 "/>
-<g style="fill:rgb(87.45098%,87.45098%,87.45098%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="546" y="238"/>
-  <use xlink:href="#glyph0-1" x="562" y="238"/>
-  <use xlink:href="#glyph0-46" x="578" y="238"/>
-  <use xlink:href="#glyph0-6" x="594" y="238"/>
-  <use xlink:href="#glyph0-1" x="610" y="238"/>
-  <use xlink:href="#glyph0-29" x="626" y="238"/>
-  <use xlink:href="#glyph0-35" x="642" y="238"/>
-  <use xlink:href="#glyph0-1" x="658" y="238"/>
-  <use xlink:href="#glyph0-20" x="674" y="238"/>
-  <use xlink:href="#glyph0-23" x="690" y="238"/>
-  <use xlink:href="#glyph0-1" x="706" y="238"/>
-  <use xlink:href="#glyph0-21" x="722" y="238"/>
-  <use xlink:href="#glyph0-20" x="738" y="238"/>
-  <use xlink:href="#glyph0-1" x="754" y="238"/>
-  <use xlink:href="#glyph0-20" x="770" y="238"/>
-  <use xlink:href="#glyph0-21" x="786" y="238"/>
-  <use xlink:href="#glyph0-1" x="802" y="238"/>
-  <use xlink:href="#glyph0-21" x="818" y="238"/>
-  <use xlink:href="#glyph0-9" x="834" y="238"/>
-  <use xlink:href="#glyph0-1" x="850" y="238"/>
-  <use xlink:href="#glyph0-35" x="866" y="238"/>
-  <use xlink:href="#glyph0-19" x="882" y="238"/>
-  <use xlink:href="#glyph0-1" x="898" y="238"/>
-  <use xlink:href="#glyph0-9" x="914" y="238"/>
-  <use xlink:href="#glyph0-20" x="930" y="238"/>
-  <use xlink:href="#glyph0-1" x="946" y="238"/>
-  <use xlink:href="#glyph0-36" x="962" y="238"/>
-  <use xlink:href="#glyph0-30" x="978" y="238"/>
-  <use xlink:href="#glyph0-1" x="994" y="238"/>
-  <use xlink:href="#glyph0-21" x="1010" y="238"/>
-  <use xlink:href="#glyph0-37" x="1026" y="238"/>
-  <use xlink:href="#glyph0-1" x="1042" y="238"/>
-  <use xlink:href="#glyph0-19" x="1058" y="238"/>
-  <use xlink:href="#glyph0-35" x="1074" y="238"/>
-  <use xlink:href="#glyph0-1" x="1090" y="238"/>
-  <use xlink:href="#glyph0-6" x="1106" y="238"/>
-  <use xlink:href="#glyph0-39" x="1122" y="238"/>
-  <use xlink:href="#glyph0-1" x="1138" y="238"/>
-  <use xlink:href="#glyph0-20" x="1154" y="238"/>
-  <use xlink:href="#glyph0-6" x="1170" y="238"/>
-  <use xlink:href="#glyph0-1" x="1186" y="238"/>
-  <use xlink:href="#glyph0-37" x="1202" y="238"/>
-  <use xlink:href="#glyph0-46" x="1218" y="238"/>
-  <use xlink:href="#glyph0-1" x="1234" y="238"/>
-  <use xlink:href="#glyph0-40" x="1250" y="238"/>
-  <use xlink:href="#glyph0-30" x="1266" y="238"/>
-  <use xlink:href="#glyph0-1" x="1282" y="238"/>
-  <use xlink:href="#glyph0-19" x="1298" y="238"/>
-  <use xlink:href="#glyph0-29" x="1314" y="238"/>
-  <use xlink:href="#glyph0-1" x="1330" y="238"/>
-  <use xlink:href="#glyph0-20" x="1346" y="238"/>
-  <use xlink:href="#glyph0-6" x="1362" y="238"/>
-  <use xlink:href="#glyph0-1" x="1378" y="238"/>
-  <use xlink:href="#glyph0-20" x="1394" y="238"/>
-  <use xlink:href="#glyph0-29" x="1410" y="238"/>
-  <use xlink:href="#glyph0-1" x="1426" y="238"/>
-  <use xlink:href="#glyph0-20" x="1442" y="238"/>
-  <use xlink:href="#glyph0-42" x="1458" y="238"/>
-  <use xlink:href="#glyph0-1" x="1474" y="238"/>
-  <use xlink:href="#glyph0-37" x="1490" y="238"/>
-  <use xlink:href="#glyph0-40" x="1506" y="238"/>
-  <use xlink:href="#glyph0-1" x="1522" y="238"/>
-  <use xlink:href="#glyph0-22" x="1538" y="238"/>
-  <use xlink:href="#glyph0-35" x="1554" y="238"/>
-  <use xlink:href="#glyph0-1" x="1570" y="238"/>
-  <use xlink:href="#glyph0-22" x="1586" y="238"/>
-  <use xlink:href="#glyph0-42" x="1602" y="238"/>
-  <use xlink:href="#glyph0-1" x="1618" y="238"/>
-  <use xlink:href="#glyph0-22" x="1634" y="238"/>
-  <use xlink:href="#glyph0-42" x="1650" y="238"/>
-  <use xlink:href="#glyph0-1" x="1666" y="238"/>
-  <use xlink:href="#glyph0-22" x="1682" y="238"/>
-  <use xlink:href="#glyph0-36" x="1698" y="238"/>
-  <use xlink:href="#glyph0-1" x="1714" y="238"/>
-  <use xlink:href="#glyph0-19" x="1730" y="238"/>
-  <use xlink:href="#glyph0-20" x="1746" y="238"/>
-  <use xlink:href="#glyph0-1" x="1762" y="238"/>
-  <use xlink:href="#glyph0-35" x="1778" y="238"/>
-  <use xlink:href="#glyph0-46" x="1794" y="238"/>
-  <use xlink:href="#glyph0-1" x="1810" y="238"/>
-  <use xlink:href="#glyph0-22" x="1826" y="238"/>
-  <use xlink:href="#glyph0-36" x="1842" y="238"/>
-  <use xlink:href="#glyph0-1" x="1858" y="238"/>
-  <use xlink:href="#glyph0-46" x="1874" y="238"/>
-  <use xlink:href="#glyph0-19" x="1890" y="238"/>
-  <use xlink:href="#glyph0-1" x="1906" y="238"/>
-  <use xlink:href="#glyph0-22" x="1922" y="238"/>
-  <use xlink:href="#glyph0-42" x="1938" y="238"/>
-  <use xlink:href="#glyph0-1" x="1954" y="238"/>
-  <use xlink:href="#glyph0-22" x="1970" y="238"/>
-  <use xlink:href="#glyph0-37" x="1986" y="238"/>
-  <use xlink:href="#glyph0-1" x="2002" y="238"/>
-  <use xlink:href="#glyph0-19" x="2018" y="238"/>
-  <use xlink:href="#glyph0-21" x="2034" y="238"/>
-  <use xlink:href="#glyph0-1" x="2050" y="238"/>
-  <use xlink:href="#glyph0-35" x="2066" y="238"/>
-  <use xlink:href="#glyph0-9" x="2082" y="238"/>
-  <use xlink:href="#glyph0-1" x="2098" y="238"/>
-  <use xlink:href="#glyph0-6" x="2114" y="238"/>
-  <use xlink:href="#glyph0-22" x="2130" y="238"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 206 L 2162 206 L 2162 247 L 2146 247 Z M 2146 206 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 206 L 18 206 L 18 247 L 1 247 Z M 1 206 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(73.333333%,76.078431%,81.176471%);fill-opacity:1;" d="M 18 247 L 290 247 L 290 288 L 18 288 Z M 18 247 "/>
-<g style="fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;">
-  <use xlink:href="#glyph0-19" x="18" y="279"/>
-  <use xlink:href="#glyph0-20" x="34" y="279"/>
-  <use xlink:href="#glyph0-19" x="50" y="279"/>
-  <use xlink:href="#glyph0-21" x="66" y="279"/>
-  <use xlink:href="#glyph0-21" x="82" y="279"/>
-  <use xlink:href="#glyph0-20" x="98" y="279"/>
-  <use xlink:href="#glyph0-20" x="114" y="279"/>
-  <use xlink:href="#glyph0-22" x="130" y="279"/>
-  <use xlink:href="#glyph0-1" x="146" y="279"/>
-  <use xlink:href="#glyph0-21" x="162" y="279"/>
-  <use xlink:href="#glyph0-23" x="178" y="279"/>
-  <use xlink:href="#glyph0-24" x="194" y="279"/>
-  <use xlink:href="#glyph0-23" x="210" y="279"/>
-  <use xlink:href="#glyph0-21" x="226" y="279"/>
-  <use xlink:href="#glyph0-24" x="242" y="279"/>
-  <use xlink:href="#glyph0-21" x="258" y="279"/>
-  <use xlink:href="#glyph0-20" x="274" y="279"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 290 247 L 322 247 L 322 288 L 290 288 Z M 290 247 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 322 247 L 370 247 L 370 288 L 322 288 Z M 322 247 "/>
-<g style="fill:rgb(40%,60%,80%);fill-opacity:1;">
-  <use xlink:href="#glyph0-25" x="322" y="279"/>
-  <use xlink:href="#glyph0-26" x="338" y="279"/>
-  <use xlink:href="#glyph0-27" x="354" y="279"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 370 247 L 402 247 L 402 288 L 370 288 Z M 370 247 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 402 247 L 482 247 L 482 288 L 402 288 Z M 402 247 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 482 247 L 514 247 L 514 288 L 482 288 Z M 482 247 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 514 247 L 546 247 L 546 288 L 514 288 Z M 514 247 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 546 247 L 2146 247 L 2146 288 L 546 288 Z M 546 247 "/>
-<g style="fill:rgb(87.45098%,87.45098%,87.45098%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="546" y="279"/>
-  <use xlink:href="#glyph0-1" x="562" y="279"/>
-  <use xlink:href="#glyph0-1" x="578" y="279"/>
-  <use xlink:href="#glyph0-32" x="594" y="279"/>
-  <use xlink:href="#glyph0-1" x="610" y="279"/>
-  <use xlink:href="#glyph0-47" x="626" y="279"/>
-  <use xlink:href="#glyph0-47" x="642" y="279"/>
-  <use xlink:href="#glyph0-1" x="658" y="279"/>
-  <use xlink:href="#glyph0-47" x="674" y="279"/>
-  <use xlink:href="#glyph0-47" x="690" y="279"/>
-  <use xlink:href="#glyph0-1" x="706" y="279"/>
-  <use xlink:href="#glyph0-47" x="722" y="279"/>
-  <use xlink:href="#glyph0-47" x="738" y="279"/>
-  <use xlink:href="#glyph0-1" x="754" y="279"/>
-  <use xlink:href="#glyph0-47" x="770" y="279"/>
-  <use xlink:href="#glyph0-47" x="786" y="279"/>
-  <use xlink:href="#glyph0-1" x="802" y="279"/>
-  <use xlink:href="#glyph0-47" x="818" y="279"/>
-  <use xlink:href="#glyph0-47" x="834" y="279"/>
-  <use xlink:href="#glyph0-1" x="850" y="279"/>
-  <use xlink:href="#glyph0-1" x="866" y="279"/>
-  <use xlink:href="#glyph0-28" x="882" y="279"/>
-  <use xlink:href="#glyph0-1" x="898" y="279"/>
-  <use xlink:href="#glyph0-47" x="914" y="279"/>
-  <use xlink:href="#glyph0-47" x="930" y="279"/>
-  <use xlink:href="#glyph0-1" x="946" y="279"/>
-  <use xlink:href="#glyph0-47" x="962" y="279"/>
-  <use xlink:href="#glyph0-47" x="978" y="279"/>
-  <use xlink:href="#glyph0-1" x="994" y="279"/>
-  <use xlink:href="#glyph0-47" x="1010" y="279"/>
-  <use xlink:href="#glyph0-47" x="1026" y="279"/>
-  <use xlink:href="#glyph0-1" x="1042" y="279"/>
-  <use xlink:href="#glyph0-1" x="1058" y="279"/>
-  <use xlink:href="#glyph0-38" x="1074" y="279"/>
-  <use xlink:href="#glyph0-1" x="1090" y="279"/>
-  <use xlink:href="#glyph0-47" x="1106" y="279"/>
-  <use xlink:href="#glyph0-47" x="1122" y="279"/>
-  <use xlink:href="#glyph0-1" x="1138" y="279"/>
-  <use xlink:href="#glyph0-47" x="1154" y="279"/>
-  <use xlink:href="#glyph0-47" x="1170" y="279"/>
-  <use xlink:href="#glyph0-1" x="1186" y="279"/>
-  <use xlink:href="#glyph0-1" x="1202" y="279"/>
-  <use xlink:href="#glyph0-27" x="1218" y="279"/>
-  <use xlink:href="#glyph0-1" x="1234" y="279"/>
-  <use xlink:href="#glyph0-47" x="1250" y="279"/>
-  <use xlink:href="#glyph0-47" x="1266" y="279"/>
-  <use xlink:href="#glyph0-1" x="1282" y="279"/>
-  <use xlink:href="#glyph0-1" x="1298" y="279"/>
-  <use xlink:href="#glyph0-41" x="1314" y="279"/>
-  <use xlink:href="#glyph0-1" x="1330" y="279"/>
-  <use xlink:href="#glyph0-47" x="1346" y="279"/>
-  <use xlink:href="#glyph0-47" x="1362" y="279"/>
-  <use xlink:href="#glyph0-1" x="1378" y="279"/>
-  <use xlink:href="#glyph0-33" x="1394" y="279"/>
-  <use xlink:href="#glyph0-14" x="1410" y="279"/>
-  <use xlink:href="#glyph0-1" x="1426" y="279"/>
-  <use xlink:href="#glyph0-47" x="1442" y="279"/>
-  <use xlink:href="#glyph0-47" x="1458" y="279"/>
-  <use xlink:href="#glyph0-1" x="1474" y="279"/>
-  <use xlink:href="#glyph0-1" x="1490" y="279"/>
-  <use xlink:href="#glyph0-17" x="1506" y="279"/>
-  <use xlink:href="#glyph0-1" x="1522" y="279"/>
-  <use xlink:href="#glyph0-1" x="1538" y="279"/>
-  <use xlink:href="#glyph0-6" x="1554" y="279"/>
-  <use xlink:href="#glyph0-1" x="1570" y="279"/>
-  <use xlink:href="#glyph0-1" x="1586" y="279"/>
-  <use xlink:href="#glyph0-5" x="1602" y="279"/>
-  <use xlink:href="#glyph0-1" x="1618" y="279"/>
-  <use xlink:href="#glyph0-1" x="1634" y="279"/>
-  <use xlink:href="#glyph0-5" x="1650" y="279"/>
-  <use xlink:href="#glyph0-1" x="1666" y="279"/>
-  <use xlink:href="#glyph0-1" x="1682" y="279"/>
-  <use xlink:href="#glyph0-15" x="1698" y="279"/>
-  <use xlink:href="#glyph0-1" x="1714" y="279"/>
-  <use xlink:href="#glyph0-1" x="1730" y="279"/>
-  <use xlink:href="#glyph0-1" x="1746" y="279"/>
-  <use xlink:href="#glyph0-1" x="1762" y="279"/>
-  <use xlink:href="#glyph0-1" x="1778" y="279"/>
-  <use xlink:href="#glyph0-13" x="1794" y="279"/>
-  <use xlink:href="#glyph0-1" x="1810" y="279"/>
-  <use xlink:href="#glyph0-1" x="1826" y="279"/>
-  <use xlink:href="#glyph0-15" x="1842" y="279"/>
-  <use xlink:href="#glyph0-1" x="1858" y="279"/>
-  <use xlink:href="#glyph0-1" x="1874" y="279"/>
-  <use xlink:href="#glyph0-43" x="1890" y="279"/>
-  <use xlink:href="#glyph0-1" x="1906" y="279"/>
-  <use xlink:href="#glyph0-1" x="1922" y="279"/>
-  <use xlink:href="#glyph0-5" x="1938" y="279"/>
-  <use xlink:href="#glyph0-1" x="1954" y="279"/>
-  <use xlink:href="#glyph0-1" x="1970" y="279"/>
-  <use xlink:href="#glyph0-9" x="1986" y="279"/>
-  <use xlink:href="#glyph0-1" x="2002" y="279"/>
-  <use xlink:href="#glyph0-1" x="2018" y="279"/>
-  <use xlink:href="#glyph0-44" x="2034" y="279"/>
-  <use xlink:href="#glyph0-1" x="2050" y="279"/>
-  <use xlink:href="#glyph0-1" x="2066" y="279"/>
-  <use xlink:href="#glyph0-7" x="2082" y="279"/>
-  <use xlink:href="#glyph0-1" x="2098" y="279"/>
-  <use xlink:href="#glyph0-47" x="2114" y="279"/>
-  <use xlink:href="#glyph0-47" x="2130" y="279"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 247 L 2162 247 L 2162 288 L 2146 288 Z M 2146 247 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 247 L 18 247 L 18 288 L 1 288 Z M 1 247 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(73.333333%,76.078431%,81.176471%);fill-opacity:1;" d="M 18 288 L 290 288 L 290 329 L 18 329 Z M 18 288 "/>
-<g style="fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;">
-  <use xlink:href="#glyph0-19" x="18" y="320"/>
-  <use xlink:href="#glyph0-20" x="34" y="320"/>
-  <use xlink:href="#glyph0-19" x="50" y="320"/>
-  <use xlink:href="#glyph0-21" x="66" y="320"/>
-  <use xlink:href="#glyph0-21" x="82" y="320"/>
-  <use xlink:href="#glyph0-20" x="98" y="320"/>
-  <use xlink:href="#glyph0-20" x="114" y="320"/>
-  <use xlink:href="#glyph0-22" x="130" y="320"/>
-  <use xlink:href="#glyph0-1" x="146" y="320"/>
-  <use xlink:href="#glyph0-21" x="162" y="320"/>
-  <use xlink:href="#glyph0-23" x="178" y="320"/>
-  <use xlink:href="#glyph0-24" x="194" y="320"/>
-  <use xlink:href="#glyph0-23" x="210" y="320"/>
-  <use xlink:href="#glyph0-21" x="226" y="320"/>
-  <use xlink:href="#glyph0-24" x="242" y="320"/>
-  <use xlink:href="#glyph0-21" x="258" y="320"/>
-  <use xlink:href="#glyph0-21" x="274" y="320"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 290 288 L 322 288 L 322 329 L 290 329 Z M 290 288 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 322 288 L 370 288 L 370 329 L 322 329 Z M 322 288 "/>
-<g style="fill:rgb(40%,60%,80%);fill-opacity:1;">
-  <use xlink:href="#glyph0-25" x="322" y="320"/>
-  <use xlink:href="#glyph0-26" x="338" y="320"/>
-  <use xlink:href="#glyph0-27" x="354" y="320"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 370 288 L 402 288 L 402 329 L 370 329 Z M 370 288 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 402 288 L 482 288 L 482 329 L 402 329 Z M 402 288 "/>
-<g style="fill:rgb(40%,80%,80%);fill-opacity:1;">
-  <use xlink:href="#glyph0-13" x="402" y="320"/>
-  <use xlink:href="#glyph0-43" x="418" y="320"/>
-  <use xlink:href="#glyph0-4" x="434" y="320"/>
-  <use xlink:href="#glyph0-10" x="450" y="320"/>
-  <use xlink:href="#glyph0-6" x="466" y="320"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 482 288 L 514 288 L 514 329 L 482 329 Z M 482 288 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 514 288 L 546 288 L 546 329 L 514 329 Z M 514 288 "/>
-<g style="fill:rgb(59.607843%,74.509804%,39.607843%);fill-opacity:1;">
-  <use xlink:href="#glyph0-35" x="514" y="320"/>
-  <use xlink:href="#glyph0-35" x="530" y="320"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 546 288 L 2146 288 L 2146 329 L 546 329 Z M 546 288 "/>
-<g style="fill:rgb(87.45098%,87.45098%,87.45098%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="546" y="320"/>
-  <use xlink:href="#glyph0-1" x="562" y="320"/>
-  <use xlink:href="#glyph0-30" x="578" y="320"/>
-  <use xlink:href="#glyph0-31" x="594" y="320"/>
-  <use xlink:href="#glyph0-32" x="610" y="320"/>
-  <use xlink:href="#glyph0-33" x="626" y="320"/>
-  <use xlink:href="#glyph0-34" x="642" y="320"/>
-  <use xlink:href="#glyph0-20" x="658" y="320"/>
-  <use xlink:href="#glyph0-23" x="674" y="320"/>
-  <use xlink:href="#glyph0-33" x="690" y="320"/>
-  <use xlink:href="#glyph0-34" x="706" y="320"/>
-  <use xlink:href="#glyph0-20" x="722" y="320"/>
-  <use xlink:href="#glyph0-23" x="738" y="320"/>
-  <use xlink:href="#glyph0-48" x="754" y="320"/>
-  <use xlink:href="#glyph0-49" x="770" y="320"/>
-  <use xlink:href="#glyph0-45" x="786" y="320"/>
-  <use xlink:href="#glyph0-50" x="802" y="320"/>
-  <use xlink:href="#glyph0-51" x="818" y="320"/>
-  <use xlink:href="#glyph0-52" x="834" y="320"/>
-  <use xlink:href="#glyph0-12" x="850" y="320"/>
-  <use xlink:href="#glyph0-53" x="866" y="320"/>
-  <use xlink:href="#glyph0-53" x="882" y="320"/>
-  <use xlink:href="#glyph0-33" x="898" y="320"/>
-  <use xlink:href="#glyph0-34" x="914" y="320"/>
-  <use xlink:href="#glyph0-29" x="930" y="320"/>
-  <use xlink:href="#glyph0-29" x="946" y="320"/>
-  <use xlink:href="#glyph0-54" x="962" y="320"/>
-  <use xlink:href="#glyph0-22" x="978" y="320"/>
-  <use xlink:href="#glyph0-33" x="994" y="320"/>
-  <use xlink:href="#glyph0-34" x="1010" y="320"/>
-  <use xlink:href="#glyph0-39" x="1026" y="320"/>
-  <use xlink:href="#glyph0-23" x="1042" y="320"/>
-  <use xlink:href="#glyph0-32" x="1058" y="320"/>
-  <use xlink:href="#glyph0-32" x="1074" y="320"/>
-  <use xlink:href="#glyph0-33" x="1090" y="320"/>
-  <use xlink:href="#glyph0-34" x="1106" y="320"/>
-  <use xlink:href="#glyph0-29" x="1122" y="320"/>
-  <use xlink:href="#glyph0-35" x="1138" y="320"/>
-  <use xlink:href="#glyph0-33" x="1154" y="320"/>
-  <use xlink:href="#glyph0-34" x="1170" y="320"/>
-  <use xlink:href="#glyph0-20" x="1186" y="320"/>
-  <use xlink:href="#glyph0-23" x="1202" y="320"/>
-  <use xlink:href="#glyph0-41" x="1218" y="320"/>
-  <use xlink:href="#glyph0-33" x="1234" y="320"/>
-  <use xlink:href="#glyph0-34" x="1250" y="320"/>
-  <use xlink:href="#glyph0-20" x="1266" y="320"/>
-  <use xlink:href="#glyph0-6" x="1282" y="320"/>
-  <use xlink:href="#glyph0-33" x="1298" y="320"/>
-  <use xlink:href="#glyph0-14" x="1314" y="320"/>
-  <use xlink:href="#glyph0-33" x="1330" y="320"/>
-  <use xlink:href="#glyph0-34" x="1346" y="320"/>
-  <use xlink:href="#glyph0-20" x="1362" y="320"/>
-  <use xlink:href="#glyph0-42" x="1378" y="320"/>
-  <use xlink:href="#glyph0-17" x="1394" y="320"/>
-  <use xlink:href="#glyph0-6" x="1410" y="320"/>
-  <use xlink:href="#glyph0-5" x="1426" y="320"/>
-  <use xlink:href="#glyph0-5" x="1442" y="320"/>
-  <use xlink:href="#glyph0-15" x="1458" y="320"/>
-  <use xlink:href="#glyph0-1" x="1474" y="320"/>
-  <use xlink:href="#glyph0-13" x="1490" y="320"/>
-  <use xlink:href="#glyph0-15" x="1506" y="320"/>
-  <use xlink:href="#glyph0-43" x="1522" y="320"/>
-  <use xlink:href="#glyph0-5" x="1538" y="320"/>
-  <use xlink:href="#glyph0-9" x="1554" y="320"/>
-  <use xlink:href="#glyph0-44" x="1570" y="320"/>
-  <use xlink:href="#glyph0-33" x="1586" y="320"/>
-  <use xlink:href="#glyph0-34" x="1602" y="320"/>
-  <use xlink:href="#glyph0-20" x="1618" y="320"/>
-  <use xlink:href="#glyph0-40" x="1634" y="320"/>
-  <use xlink:href="#glyph0-33" x="1650" y="320"/>
-  <use xlink:href="#glyph0-34" x="1666" y="320"/>
-  <use xlink:href="#glyph0-20" x="1682" y="320"/>
-  <use xlink:href="#glyph0-21" x="1698" y="320"/>
-  <use xlink:href="#glyph0-33" x="1714" y="320"/>
-  <use xlink:href="#glyph0-34" x="1730" y="320"/>
-  <use xlink:href="#glyph0-21" x="1746" y="320"/>
-  <use xlink:href="#glyph0-20" x="1762" y="320"/>
-  <use xlink:href="#glyph0-33" x="1778" y="320"/>
-  <use xlink:href="#glyph0-34" x="1794" y="320"/>
-  <use xlink:href="#glyph0-20" x="1810" y="320"/>
-  <use xlink:href="#glyph0-21" x="1826" y="320"/>
-  <use xlink:href="#glyph0-33" x="1842" y="320"/>
-  <use xlink:href="#glyph0-34" x="1858" y="320"/>
-  <use xlink:href="#glyph0-21" x="1874" y="320"/>
-  <use xlink:href="#glyph0-9" x="1890" y="320"/>
-  <use xlink:href="#glyph0-28" x="1906" y="320"/>
-  <use xlink:href="#glyph0-33" x="1922" y="320"/>
-  <use xlink:href="#glyph0-34" x="1938" y="320"/>
-  <use xlink:href="#glyph0-9" x="1954" y="320"/>
-  <use xlink:href="#glyph0-20" x="1970" y="320"/>
-  <use xlink:href="#glyph0-33" x="1986" y="320"/>
-  <use xlink:href="#glyph0-34" x="2002" y="320"/>
-  <use xlink:href="#glyph0-36" x="2018" y="320"/>
-  <use xlink:href="#glyph0-30" x="2034" y="320"/>
-  <use xlink:href="#glyph0-33" x="2050" y="320"/>
-  <use xlink:href="#glyph0-34" x="2066" y="320"/>
-  <use xlink:href="#glyph0-21" x="2082" y="320"/>
-  <use xlink:href="#glyph0-37" x="2098" y="320"/>
-  <use xlink:href="#glyph0-38" x="2114" y="320"/>
-  <use xlink:href="#glyph0-33" x="2130" y="320"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 288 L 2162 288 L 2162 329 L 2146 329 Z M 2146 288 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 288 L 18 288 L 18 329 L 1 329 Z M 1 288 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(73.333333%,76.078431%,81.176471%);fill-opacity:1;" d="M 18 329 L 290 329 L 290 370 L 18 370 Z M 18 329 "/>
-<g style="fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;">
-  <use xlink:href="#glyph0-19" x="18" y="361"/>
-  <use xlink:href="#glyph0-20" x="34" y="361"/>
-  <use xlink:href="#glyph0-19" x="50" y="361"/>
-  <use xlink:href="#glyph0-21" x="66" y="361"/>
-  <use xlink:href="#glyph0-21" x="82" y="361"/>
-  <use xlink:href="#glyph0-20" x="98" y="361"/>
-  <use xlink:href="#glyph0-20" x="114" y="361"/>
-  <use xlink:href="#glyph0-22" x="130" y="361"/>
-  <use xlink:href="#glyph0-1" x="146" y="361"/>
-  <use xlink:href="#glyph0-21" x="162" y="361"/>
-  <use xlink:href="#glyph0-23" x="178" y="361"/>
-  <use xlink:href="#glyph0-24" x="194" y="361"/>
-  <use xlink:href="#glyph0-23" x="210" y="361"/>
-  <use xlink:href="#glyph0-21" x="226" y="361"/>
-  <use xlink:href="#glyph0-24" x="242" y="361"/>
-  <use xlink:href="#glyph0-21" x="258" y="361"/>
-  <use xlink:href="#glyph0-21" x="274" y="361"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 290 329 L 322 329 L 322 370 L 290 370 Z M 290 329 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 322 329 L 370 329 L 370 370 L 322 370 Z M 322 329 "/>
-<g style="fill:rgb(40%,60%,80%);fill-opacity:1;">
-  <use xlink:href="#glyph0-25" x="322" y="361"/>
-  <use xlink:href="#glyph0-26" x="338" y="361"/>
-  <use xlink:href="#glyph0-27" x="354" y="361"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 370 329 L 402 329 L 402 370 L 370 370 Z M 370 329 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 402 329 L 482 329 L 482 370 L 402 370 Z M 402 329 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 482 329 L 514 329 L 514 370 L 482 370 Z M 482 329 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 514 329 L 546 329 L 546 370 L 514 370 Z M 514 329 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 546 329 L 2146 329 L 2146 370 L 546 370 Z M 546 329 "/>
-<g style="fill:rgb(87.45098%,87.45098%,87.45098%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="546" y="361"/>
-  <use xlink:href="#glyph0-1" x="562" y="361"/>
-  <use xlink:href="#glyph0-46" x="578" y="361"/>
-  <use xlink:href="#glyph0-6" x="594" y="361"/>
-  <use xlink:href="#glyph0-1" x="610" y="361"/>
-  <use xlink:href="#glyph0-20" x="626" y="361"/>
-  <use xlink:href="#glyph0-23" x="642" y="361"/>
-  <use xlink:href="#glyph0-1" x="658" y="361"/>
-  <use xlink:href="#glyph0-20" x="674" y="361"/>
-  <use xlink:href="#glyph0-23" x="690" y="361"/>
-  <use xlink:href="#glyph0-1" x="706" y="361"/>
-  <use xlink:href="#glyph0-19" x="722" y="361"/>
-  <use xlink:href="#glyph0-37" x="738" y="361"/>
-  <use xlink:href="#glyph0-1" x="754" y="361"/>
-  <use xlink:href="#glyph0-19" x="770" y="361"/>
-  <use xlink:href="#glyph0-36" x="786" y="361"/>
-  <use xlink:href="#glyph0-1" x="802" y="361"/>
-  <use xlink:href="#glyph0-35" x="818" y="361"/>
-  <use xlink:href="#glyph0-21" x="834" y="361"/>
-  <use xlink:href="#glyph0-1" x="850" y="361"/>
-  <use xlink:href="#glyph0-46" x="866" y="361"/>
-  <use xlink:href="#glyph0-22" x="882" y="361"/>
-  <use xlink:href="#glyph0-1" x="898" y="361"/>
-  <use xlink:href="#glyph0-35" x="914" y="361"/>
-  <use xlink:href="#glyph0-39" x="930" y="361"/>
-  <use xlink:href="#glyph0-1" x="946" y="361"/>
-  <use xlink:href="#glyph0-37" x="962" y="361"/>
-  <use xlink:href="#glyph0-6" x="978" y="361"/>
-  <use xlink:href="#glyph0-1" x="994" y="361"/>
-  <use xlink:href="#glyph0-46" x="1010" y="361"/>
-  <use xlink:href="#glyph0-46" x="1026" y="361"/>
-  <use xlink:href="#glyph0-1" x="1042" y="361"/>
-  <use xlink:href="#glyph0-23" x="1058" y="361"/>
-  <use xlink:href="#glyph0-9" x="1074" y="361"/>
-  <use xlink:href="#glyph0-1" x="1090" y="361"/>
-  <use xlink:href="#glyph0-23" x="1106" y="361"/>
-  <use xlink:href="#glyph0-9" x="1122" y="361"/>
-  <use xlink:href="#glyph0-1" x="1138" y="361"/>
-  <use xlink:href="#glyph0-29" x="1154" y="361"/>
-  <use xlink:href="#glyph0-29" x="1170" y="361"/>
-  <use xlink:href="#glyph0-1" x="1186" y="361"/>
-  <use xlink:href="#glyph0-23" x="1202" y="361"/>
-  <use xlink:href="#glyph0-30" x="1218" y="361"/>
-  <use xlink:href="#glyph0-1" x="1234" y="361"/>
-  <use xlink:href="#glyph0-23" x="1250" y="361"/>
-  <use xlink:href="#glyph0-22" x="1266" y="361"/>
-  <use xlink:href="#glyph0-1" x="1282" y="361"/>
-  <use xlink:href="#glyph0-39" x="1298" y="361"/>
-  <use xlink:href="#glyph0-23" x="1314" y="361"/>
-  <use xlink:href="#glyph0-1" x="1330" y="361"/>
-  <use xlink:href="#glyph0-46" x="1346" y="361"/>
-  <use xlink:href="#glyph0-6" x="1362" y="361"/>
-  <use xlink:href="#glyph0-1" x="1378" y="361"/>
-  <use xlink:href="#glyph0-46" x="1394" y="361"/>
-  <use xlink:href="#glyph0-6" x="1410" y="361"/>
-  <use xlink:href="#glyph0-1" x="1426" y="361"/>
-  <use xlink:href="#glyph0-29" x="1442" y="361"/>
-  <use xlink:href="#glyph0-35" x="1458" y="361"/>
-  <use xlink:href="#glyph0-1" x="1474" y="361"/>
-  <use xlink:href="#glyph0-20" x="1490" y="361"/>
-  <use xlink:href="#glyph0-23" x="1506" y="361"/>
-  <use xlink:href="#glyph0-1" x="1522" y="361"/>
-  <use xlink:href="#glyph0-19" x="1538" y="361"/>
-  <use xlink:href="#glyph0-29" x="1554" y="361"/>
-  <use xlink:href="#glyph0-1" x="1570" y="361"/>
-  <use xlink:href="#glyph0-20" x="1586" y="361"/>
-  <use xlink:href="#glyph0-6" x="1602" y="361"/>
-  <use xlink:href="#glyph0-1" x="1618" y="361"/>
-  <use xlink:href="#glyph0-20" x="1634" y="361"/>
-  <use xlink:href="#glyph0-29" x="1650" y="361"/>
-  <use xlink:href="#glyph0-1" x="1666" y="361"/>
-  <use xlink:href="#glyph0-20" x="1682" y="361"/>
-  <use xlink:href="#glyph0-42" x="1698" y="361"/>
-  <use xlink:href="#glyph0-1" x="1714" y="361"/>
-  <use xlink:href="#glyph0-37" x="1730" y="361"/>
-  <use xlink:href="#glyph0-40" x="1746" y="361"/>
-  <use xlink:href="#glyph0-1" x="1762" y="361"/>
-  <use xlink:href="#glyph0-22" x="1778" y="361"/>
-  <use xlink:href="#glyph0-35" x="1794" y="361"/>
-  <use xlink:href="#glyph0-1" x="1810" y="361"/>
-  <use xlink:href="#glyph0-22" x="1826" y="361"/>
-  <use xlink:href="#glyph0-42" x="1842" y="361"/>
-  <use xlink:href="#glyph0-1" x="1858" y="361"/>
-  <use xlink:href="#glyph0-22" x="1874" y="361"/>
-  <use xlink:href="#glyph0-42" x="1890" y="361"/>
-  <use xlink:href="#glyph0-1" x="1906" y="361"/>
-  <use xlink:href="#glyph0-22" x="1922" y="361"/>
-  <use xlink:href="#glyph0-36" x="1938" y="361"/>
-  <use xlink:href="#glyph0-1" x="1954" y="361"/>
-  <use xlink:href="#glyph0-19" x="1970" y="361"/>
-  <use xlink:href="#glyph0-20" x="1986" y="361"/>
-  <use xlink:href="#glyph0-1" x="2002" y="361"/>
-  <use xlink:href="#glyph0-35" x="2018" y="361"/>
-  <use xlink:href="#glyph0-46" x="2034" y="361"/>
-  <use xlink:href="#glyph0-1" x="2050" y="361"/>
-  <use xlink:href="#glyph0-22" x="2066" y="361"/>
-  <use xlink:href="#glyph0-36" x="2082" y="361"/>
-  <use xlink:href="#glyph0-1" x="2098" y="361"/>
-  <use xlink:href="#glyph0-46" x="2114" y="361"/>
-  <use xlink:href="#glyph0-19" x="2130" y="361"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 329 L 2162 329 L 2162 370 L 2146 370 Z M 2146 329 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 329 L 18 329 L 18 370 L 1 370 Z M 1 329 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(73.333333%,76.078431%,81.176471%);fill-opacity:1;" d="M 18 370 L 290 370 L 290 411 L 18 411 Z M 18 370 "/>
-<g style="fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;">
-  <use xlink:href="#glyph0-19" x="18" y="402"/>
-  <use xlink:href="#glyph0-20" x="34" y="402"/>
-  <use xlink:href="#glyph0-19" x="50" y="402"/>
-  <use xlink:href="#glyph0-21" x="66" y="402"/>
-  <use xlink:href="#glyph0-21" x="82" y="402"/>
-  <use xlink:href="#glyph0-20" x="98" y="402"/>
-  <use xlink:href="#glyph0-20" x="114" y="402"/>
-  <use xlink:href="#glyph0-22" x="130" y="402"/>
-  <use xlink:href="#glyph0-1" x="146" y="402"/>
-  <use xlink:href="#glyph0-21" x="162" y="402"/>
-  <use xlink:href="#glyph0-23" x="178" y="402"/>
-  <use xlink:href="#glyph0-24" x="194" y="402"/>
-  <use xlink:href="#glyph0-23" x="210" y="402"/>
-  <use xlink:href="#glyph0-21" x="226" y="402"/>
-  <use xlink:href="#glyph0-24" x="242" y="402"/>
-  <use xlink:href="#glyph0-21" x="258" y="402"/>
-  <use xlink:href="#glyph0-21" x="274" y="402"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 290 370 L 322 370 L 322 411 L 290 411 Z M 290 370 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 322 370 L 370 370 L 370 411 L 322 411 Z M 322 370 "/>
-<g style="fill:rgb(40%,60%,80%);fill-opacity:1;">
-  <use xlink:href="#glyph0-25" x="322" y="402"/>
-  <use xlink:href="#glyph0-26" x="338" y="402"/>
-  <use xlink:href="#glyph0-27" x="354" y="402"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 370 370 L 402 370 L 402 411 L 370 411 Z M 370 370 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 402 370 L 482 370 L 482 411 L 402 411 Z M 402 370 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 482 370 L 514 370 L 514 411 L 482 411 Z M 482 370 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 514 370 L 546 370 L 546 411 L 514 411 Z M 514 370 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 546 370 L 2146 370 L 2146 411 L 546 411 Z M 546 370 "/>
-<g style="fill:rgb(87.45098%,87.45098%,87.45098%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="546" y="402"/>
-  <use xlink:href="#glyph0-1" x="562" y="402"/>
-  <use xlink:href="#glyph0-1" x="578" y="402"/>
-  <use xlink:href="#glyph0-32" x="594" y="402"/>
-  <use xlink:href="#glyph0-1" x="610" y="402"/>
-  <use xlink:href="#glyph0-47" x="626" y="402"/>
-  <use xlink:href="#glyph0-47" x="642" y="402"/>
-  <use xlink:href="#glyph0-1" x="658" y="402"/>
-  <use xlink:href="#glyph0-47" x="674" y="402"/>
-  <use xlink:href="#glyph0-47" x="690" y="402"/>
-  <use xlink:href="#glyph0-1" x="706" y="402"/>
-  <use xlink:href="#glyph0-1" x="722" y="402"/>
-  <use xlink:href="#glyph0-48" x="738" y="402"/>
-  <use xlink:href="#glyph0-1" x="754" y="402"/>
-  <use xlink:href="#glyph0-1" x="770" y="402"/>
-  <use xlink:href="#glyph0-49" x="786" y="402"/>
-  <use xlink:href="#glyph0-1" x="802" y="402"/>
-  <use xlink:href="#glyph0-1" x="818" y="402"/>
-  <use xlink:href="#glyph0-45" x="834" y="402"/>
-  <use xlink:href="#glyph0-1" x="850" y="402"/>
-  <use xlink:href="#glyph0-1" x="866" y="402"/>
-  <use xlink:href="#glyph0-50" x="882" y="402"/>
-  <use xlink:href="#glyph0-1" x="898" y="402"/>
-  <use xlink:href="#glyph0-1" x="914" y="402"/>
-  <use xlink:href="#glyph0-51" x="930" y="402"/>
-  <use xlink:href="#glyph0-1" x="946" y="402"/>
-  <use xlink:href="#glyph0-1" x="962" y="402"/>
-  <use xlink:href="#glyph0-52" x="978" y="402"/>
-  <use xlink:href="#glyph0-1" x="994" y="402"/>
-  <use xlink:href="#glyph0-1" x="1010" y="402"/>
-  <use xlink:href="#glyph0-12" x="1026" y="402"/>
-  <use xlink:href="#glyph0-1" x="1042" y="402"/>
-  <use xlink:href="#glyph0-1" x="1058" y="402"/>
-  <use xlink:href="#glyph0-53" x="1074" y="402"/>
-  <use xlink:href="#glyph0-1" x="1090" y="402"/>
-  <use xlink:href="#glyph0-1" x="1106" y="402"/>
-  <use xlink:href="#glyph0-53" x="1122" y="402"/>
-  <use xlink:href="#glyph0-1" x="1138" y="402"/>
-  <use xlink:href="#glyph0-47" x="1154" y="402"/>
-  <use xlink:href="#glyph0-47" x="1170" y="402"/>
-  <use xlink:href="#glyph0-1" x="1186" y="402"/>
-  <use xlink:href="#glyph0-1" x="1202" y="402"/>
-  <use xlink:href="#glyph0-54" x="1218" y="402"/>
-  <use xlink:href="#glyph0-1" x="1234" y="402"/>
-  <use xlink:href="#glyph0-1" x="1250" y="402"/>
-  <use xlink:href="#glyph0-22" x="1266" y="402"/>
-  <use xlink:href="#glyph0-1" x="1282" y="402"/>
-  <use xlink:href="#glyph0-47" x="1298" y="402"/>
-  <use xlink:href="#glyph0-47" x="1314" y="402"/>
-  <use xlink:href="#glyph0-1" x="1330" y="402"/>
-  <use xlink:href="#glyph0-1" x="1346" y="402"/>
-  <use xlink:href="#glyph0-32" x="1362" y="402"/>
-  <use xlink:href="#glyph0-1" x="1378" y="402"/>
-  <use xlink:href="#glyph0-1" x="1394" y="402"/>
-  <use xlink:href="#glyph0-32" x="1410" y="402"/>
-  <use xlink:href="#glyph0-1" x="1426" y="402"/>
-  <use xlink:href="#glyph0-47" x="1442" y="402"/>
-  <use xlink:href="#glyph0-47" x="1458" y="402"/>
-  <use xlink:href="#glyph0-1" x="1474" y="402"/>
-  <use xlink:href="#glyph0-47" x="1490" y="402"/>
-  <use xlink:href="#glyph0-47" x="1506" y="402"/>
-  <use xlink:href="#glyph0-1" x="1522" y="402"/>
-  <use xlink:href="#glyph0-1" x="1538" y="402"/>
-  <use xlink:href="#glyph0-41" x="1554" y="402"/>
-  <use xlink:href="#glyph0-1" x="1570" y="402"/>
-  <use xlink:href="#glyph0-47" x="1586" y="402"/>
-  <use xlink:href="#glyph0-47" x="1602" y="402"/>
-  <use xlink:href="#glyph0-1" x="1618" y="402"/>
-  <use xlink:href="#glyph0-33" x="1634" y="402"/>
-  <use xlink:href="#glyph0-14" x="1650" y="402"/>
-  <use xlink:href="#glyph0-1" x="1666" y="402"/>
-  <use xlink:href="#glyph0-47" x="1682" y="402"/>
-  <use xlink:href="#glyph0-47" x="1698" y="402"/>
-  <use xlink:href="#glyph0-1" x="1714" y="402"/>
-  <use xlink:href="#glyph0-1" x="1730" y="402"/>
-  <use xlink:href="#glyph0-17" x="1746" y="402"/>
-  <use xlink:href="#glyph0-1" x="1762" y="402"/>
-  <use xlink:href="#glyph0-1" x="1778" y="402"/>
-  <use xlink:href="#glyph0-6" x="1794" y="402"/>
-  <use xlink:href="#glyph0-1" x="1810" y="402"/>
-  <use xlink:href="#glyph0-1" x="1826" y="402"/>
-  <use xlink:href="#glyph0-5" x="1842" y="402"/>
-  <use xlink:href="#glyph0-1" x="1858" y="402"/>
-  <use xlink:href="#glyph0-1" x="1874" y="402"/>
-  <use xlink:href="#glyph0-5" x="1890" y="402"/>
-  <use xlink:href="#glyph0-1" x="1906" y="402"/>
-  <use xlink:href="#glyph0-1" x="1922" y="402"/>
-  <use xlink:href="#glyph0-15" x="1938" y="402"/>
-  <use xlink:href="#glyph0-1" x="1954" y="402"/>
-  <use xlink:href="#glyph0-1" x="1970" y="402"/>
-  <use xlink:href="#glyph0-1" x="1986" y="402"/>
-  <use xlink:href="#glyph0-1" x="2002" y="402"/>
-  <use xlink:href="#glyph0-1" x="2018" y="402"/>
-  <use xlink:href="#glyph0-13" x="2034" y="402"/>
-  <use xlink:href="#glyph0-1" x="2050" y="402"/>
-  <use xlink:href="#glyph0-1" x="2066" y="402"/>
-  <use xlink:href="#glyph0-15" x="2082" y="402"/>
-  <use xlink:href="#glyph0-1" x="2098" y="402"/>
-  <use xlink:href="#glyph0-1" x="2114" y="402"/>
-  <use xlink:href="#glyph0-43" x="2130" y="402"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 370 L 2162 370 L 2162 411 L 2146 411 Z M 2146 370 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 370 L 18 370 L 18 411 L 1 411 Z M 1 370 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(24.705882%,24.705882%,24.705882%);fill-opacity:1;" d="M 18 411 L 34 411 L 34 452 L 18 452 Z M 18 411 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(24.705882%,24.705882%,24.705882%);fill-opacity:1;" d="M 34 411 L 258 411 L 258 452 L 34 452 Z M 34 411 "/>
-<g style="fill:rgb(66.27451%,63.137255%,88.235294%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="34" y="443"/>
-  <use xlink:href="#glyph0-55" x="50" y="443"/>
-  <use xlink:href="#glyph0-6" x="66" y="443"/>
-  <use xlink:href="#glyph0-43" x="82" y="443"/>
-  <use xlink:href="#glyph0-4" x="98" y="443"/>
-  <use xlink:href="#glyph0-29" x="114" y="443"/>
-  <use xlink:href="#glyph0-5" x="130" y="443"/>
-  <use xlink:href="#glyph0-1" x="146" y="443"/>
-  <use xlink:href="#glyph0-25" x="162" y="443"/>
-  <use xlink:href="#glyph0-6" x="178" y="443"/>
-  <use xlink:href="#glyph0-30" x="194" y="443"/>
-  <use xlink:href="#glyph0-56" x="210" y="443"/>
-  <use xlink:href="#glyph0-57" x="226" y="443"/>
-  <use xlink:href="#glyph0-1" x="242" y="443"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(24.705882%,24.705882%,24.705882%);fill-opacity:1;" d="M 258 411 L 290 411 L 290 452 L 258 452 Z M 258 411 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 290 411 L 306 411 L 306 452 L 290 452 Z M 290 411 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 306 411 L 418 411 L 418 452 L 306 452 Z M 306 411 "/>
-<g style="fill:rgb(87.45098%,87.45098%,87.45098%);fill-opacity:1;">
-  <use xlink:href="#glyph0-55" x="306" y="443"/>
-  <use xlink:href="#glyph0-6" x="322" y="443"/>
-  <use xlink:href="#glyph0-29" x="338" y="443"/>
-  <use xlink:href="#glyph0-43" x="354" y="443"/>
-  <use xlink:href="#glyph0-42" x="370" y="443"/>
-  <use xlink:href="#glyph0-58" x="386" y="443"/>
-  <use xlink:href="#glyph0-1" x="402" y="443"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 418 411 L 434 411 L 434 452 L 418 452 Z M 418 411 "/>
-<g style="fill:rgb(66.27451%,63.137255%,88.235294%);fill-opacity:1;">
-  <use xlink:href="#glyph1-23" x="418" y="443"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 434 411 L 450 411 L 450 452 L 434 452 Z M 434 411 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(24.705882%,24.705882%,24.705882%);fill-opacity:1;" d="M 450 411 L 482 411 L 482 452 L 450 452 Z M 450 411 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 482 411 L 498 411 L 498 452 L 482 452 Z M 482 411 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 498 411 L 674 411 L 674 452 L 498 452 Z M 498 411 "/>
-<g style="fill:rgb(87.45098%,87.45098%,87.45098%);fill-opacity:1;">
-  <use xlink:href="#glyph0-59" x="498" y="443"/>
-  <use xlink:href="#glyph0-15" x="514" y="443"/>
-  <use xlink:href="#glyph0-18" x="530" y="443"/>
-  <use xlink:href="#glyph0-60" x="546" y="443"/>
-  <use xlink:href="#glyph0-1" x="562" y="443"/>
-  <use xlink:href="#glyph0-61" x="578" y="443"/>
-  <use xlink:href="#glyph0-4" x="594" y="443"/>
-  <use xlink:href="#glyph0-14" x="610" y="443"/>
-  <use xlink:href="#glyph0-6" x="626" y="443"/>
-  <use xlink:href="#glyph0-16" x="642" y="443"/>
-  <use xlink:href="#glyph0-1" x="658" y="443"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 674 411 L 770 411 L 770 452 L 674 452 Z M 674 411 "/>
-<g style="fill:rgb(66.27451%,63.137255%,88.235294%);fill-opacity:1;">
-  <use xlink:href="#glyph1-8" x="674" y="443"/>
-  <use xlink:href="#glyph1-24" x="690" y="443"/>
-  <use xlink:href="#glyph1-25" x="706" y="443"/>
-  <use xlink:href="#glyph1-12" x="722" y="443"/>
-  <use xlink:href="#glyph1-26" x="738" y="443"/>
-  <use xlink:href="#glyph1-27" x="754" y="443"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 770 411 L 786 411 L 786 452 L 770 452 Z M 770 411 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(24.705882%,24.705882%,24.705882%);fill-opacity:1;" d="M 786 411 L 818 411 L 818 452 L 786 452 Z M 786 411 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 818 411 L 834 411 L 834 452 L 818 452 Z M 818 411 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 834 411 L 898 411 L 898 452 L 834 452 Z M 834 411 "/>
-<g style="fill:rgb(74.901961%,74.901961%,74.901961%);fill-opacity:1;">
-  <use xlink:href="#glyph0-2" x="834" y="443"/>
-  <use xlink:href="#glyph0-62" x="850" y="443"/>
-  <use xlink:href="#glyph0-7" x="866" y="443"/>
-  <use xlink:href="#glyph0-1" x="882" y="443"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 898 411 L 1010 411 L 1010 452 L 898 452 Z M 898 411 "/>
-<g style="fill:rgb(87.45098%,87.45098%,87.45098%);fill-opacity:1;">
-  <use xlink:href="#glyph0-3" x="898" y="443"/>
-  <use xlink:href="#glyph0-15" x="914" y="443"/>
-  <use xlink:href="#glyph0-5" x="930" y="443"/>
-  <use xlink:href="#glyph0-5" x="946" y="443"/>
-  <use xlink:href="#glyph0-15" x="962" y="443"/>
-  <use xlink:href="#glyph0-12" x="978" y="443"/>
-  <use xlink:href="#glyph0-1" x="994" y="443"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1010 411 L 1026 411 L 1026 452 L 1010 452 Z M 1010 411 "/>
-<g style="fill:rgb(66.27451%,63.137255%,88.235294%);fill-opacity:1;">
-  <use xlink:href="#glyph1-28" x="1010" y="443"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1026 411 L 1042 411 L 1042 452 L 1026 452 Z M 1026 411 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(24.705882%,24.705882%,24.705882%);fill-opacity:1;" d="M 1042 411 L 1074 411 L 1074 452 L 1042 452 Z M 1042 411 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1074 411 L 1090 411 L 1090 452 L 1074 452 Z M 1074 411 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1090 411 L 1154 411 L 1154 452 L 1090 452 Z M 1090 411 "/>
-<g style="fill:rgb(74.901961%,74.901961%,74.901961%);fill-opacity:1;">
-  <use xlink:href="#glyph0-2" x="1090" y="443"/>
-  <use xlink:href="#glyph0-62" x="1106" y="443"/>
-  <use xlink:href="#glyph0-7" x="1122" y="443"/>
-  <use xlink:href="#glyph0-1" x="1138" y="443"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1154 411 L 1250 411 L 1250 452 L 1154 452 Z M 1154 411 "/>
-<g style="fill:rgb(87.45098%,87.45098%,87.45098%);fill-opacity:1;">
-  <use xlink:href="#glyph0-63" x="1154" y="443"/>
-  <use xlink:href="#glyph0-29" x="1170" y="443"/>
-  <use xlink:href="#glyph0-30" x="1186" y="443"/>
-  <use xlink:href="#glyph0-5" x="1202" y="443"/>
-  <use xlink:href="#glyph0-6" x="1218" y="443"/>
-  <use xlink:href="#glyph0-1" x="1234" y="443"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1250 411 L 1266 411 L 1266 452 L 1250 452 Z M 1250 411 "/>
-<g style="fill:rgb(66.27451%,63.137255%,88.235294%);fill-opacity:1;">
-  <use xlink:href="#glyph1-24" x="1250" y="443"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1266 411 L 1282 411 L 1282 452 L 1266 452 Z M 1266 411 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(24.705882%,24.705882%,24.705882%);fill-opacity:1;" d="M 1282 411 L 1314 411 L 1314 452 L 1282 452 Z M 1282 411 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1314 411 L 1330 411 L 1330 452 L 1314 452 Z M 1314 411 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1330 411 L 1362 411 L 1362 452 L 1330 452 Z M 1330 411 "/>
-<g style="fill:rgb(74.901961%,74.901961%,74.901961%);fill-opacity:1;">
-  <use xlink:href="#glyph0-2" x="1330" y="443"/>
-  <use xlink:href="#glyph0-1" x="1346" y="443"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1362 411 L 1378 411 L 1378 452 L 1362 452 Z M 1362 411 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1378 411 L 1602 411 L 1602 452 L 1378 452 Z M 1378 411 "/>
-<g style="fill:rgb(87.45098%,87.45098%,87.45098%);fill-opacity:1;">
-  <use xlink:href="#glyph0-42" x="1378" y="443"/>
-  <use xlink:href="#glyph0-5" x="1394" y="443"/>
-  <use xlink:href="#glyph0-4" x="1410" y="443"/>
-  <use xlink:href="#glyph0-42" x="1426" y="443"/>
-  <use xlink:href="#glyph0-64" x="1442" y="443"/>
-  <use xlink:href="#glyph0-1" x="1458" y="443"/>
-  <use xlink:href="#glyph0-10" x="1474" y="443"/>
-  <use xlink:href="#glyph0-15" x="1490" y="443"/>
-  <use xlink:href="#glyph0-1" x="1506" y="443"/>
-  <use xlink:href="#glyph0-36" x="1522" y="443"/>
-  <use xlink:href="#glyph0-15" x="1538" y="443"/>
-  <use xlink:href="#glyph0-42" x="1554" y="443"/>
-  <use xlink:href="#glyph0-56" x="1570" y="443"/>
-  <use xlink:href="#glyph0-16" x="1586" y="443"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1602 411 L 1618 411 L 1618 452 L 1602 452 Z M 1602 411 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(24.705882%,24.705882%,24.705882%);fill-opacity:1;" d="M 1618 411 L 2146 411 L 2146 452 L 1618 452 Z M 1618 411 "/>
-<g style="fill:rgb(74.901961%,74.901961%,74.901961%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="1618" y="443"/>
-  <use xlink:href="#glyph0-1" x="1634" y="443"/>
-  <use xlink:href="#glyph0-18" x="1650" y="443"/>
-  <use xlink:href="#glyph0-12" x="1666" y="443"/>
-  <use xlink:href="#glyph0-65" x="1682" y="443"/>
-  <use xlink:href="#glyph0-42" x="1698" y="443"/>
-  <use xlink:href="#glyph0-15" x="1714" y="443"/>
-  <use xlink:href="#glyph0-14" x="1730" y="443"/>
-  <use xlink:href="#glyph0-16" x="1746" y="443"/>
-  <use xlink:href="#glyph0-15" x="1762" y="443"/>
-  <use xlink:href="#glyph0-5" x="1778" y="443"/>
-  <use xlink:href="#glyph0-6" x="1794" y="443"/>
-  <use xlink:href="#glyph0-47" x="1810" y="443"/>
-  <use xlink:href="#glyph0-16" x="1826" y="443"/>
-  <use xlink:href="#glyph0-6" x="1842" y="443"/>
-  <use xlink:href="#glyph0-43" x="1858" y="443"/>
-  <use xlink:href="#glyph0-4" x="1874" y="443"/>
-  <use xlink:href="#glyph0-29" x="1890" y="443"/>
-  <use xlink:href="#glyph0-5" x="1906" y="443"/>
-  <use xlink:href="#glyph0-65" x="1922" y="443"/>
-  <use xlink:href="#glyph0-9" x="1938" y="443"/>
-  <use xlink:href="#glyph0-6" x="1954" y="443"/>
-  <use xlink:href="#glyph0-30" x="1970" y="443"/>
-  <use xlink:href="#glyph0-56" x="1986" y="443"/>
-  <use xlink:href="#glyph0-57" x="2002" y="443"/>
-  <use xlink:href="#glyph0-65" x="2018" y="443"/>
-  <use xlink:href="#glyph0-5" x="2034" y="443"/>
-  <use xlink:href="#glyph0-15" x="2050" y="443"/>
-  <use xlink:href="#glyph0-57" x="2066" y="443"/>
-  <use xlink:href="#glyph0-57" x="2082" y="443"/>
-  <use xlink:href="#glyph0-6" x="2098" y="443"/>
-  <use xlink:href="#glyph0-43" x="2114" y="443"/>
-  <use xlink:href="#glyph0-1" x="2130" y="443"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 411 L 2162 411 L 2162 452 L 2146 452 Z M 2146 411 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 411 L 18 411 L 18 452 L 1 452 Z M 1 411 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 18 452 L 290 452 L 290 493 L 18 493 Z M 18 452 "/>
-<g style="fill:rgb(74.901961%,74.901961%,74.901961%);fill-opacity:1;">
-  <use xlink:href="#glyph1-15" x="18" y="484"/>
-  <use xlink:href="#glyph1-2" x="34" y="484"/>
-  <use xlink:href="#glyph1-16" x="50" y="484"/>
-  <use xlink:href="#glyph1-5" x="66" y="484"/>
-  <use xlink:href="#glyph1-7" x="82" y="484"/>
-  <use xlink:href="#glyph1-7" x="98" y="484"/>
-  <use xlink:href="#glyph1-7" x="114" y="484"/>
-  <use xlink:href="#glyph1-7" x="130" y="484"/>
-  <use xlink:href="#glyph1-7" x="146" y="484"/>
-  <use xlink:href="#glyph1-7" x="162" y="484"/>
-  <use xlink:href="#glyph1-7" x="178" y="484"/>
-  <use xlink:href="#glyph1-7" x="194" y="484"/>
-  <use xlink:href="#glyph1-7" x="210" y="484"/>
-  <use xlink:href="#glyph1-7" x="226" y="484"/>
-  <use xlink:href="#glyph1-7" x="242" y="484"/>
-  <use xlink:href="#glyph1-7" x="258" y="484"/>
-  <use xlink:href="#glyph1-7" x="274" y="484"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 290 452 L 322 452 L 322 493 L 290 493 Z M 290 452 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 322 452 L 370 452 L 370 493 L 322 493 Z M 322 452 "/>
-<g style="fill:rgb(74.901961%,74.901961%,74.901961%);fill-opacity:1;">
-  <use xlink:href="#glyph1-17" x="322" y="484"/>
-  <use xlink:href="#glyph1-5" x="338" y="484"/>
-  <use xlink:href="#glyph1-18" x="354" y="484"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 370 452 L 402 452 L 402 493 L 370 493 Z M 370 452 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 402 452 L 514 452 L 514 493 L 402 493 Z M 402 452 "/>
-<g style="fill:rgb(74.901961%,74.901961%,74.901961%);fill-opacity:1;">
-  <use xlink:href="#glyph1-19" x="402" y="484"/>
-  <use xlink:href="#glyph1-5" x="418" y="484"/>
-  <use xlink:href="#glyph1-11" x="434" y="484"/>
-  <use xlink:href="#glyph1-11" x="450" y="484"/>
-  <use xlink:href="#glyph1-22" x="466" y="484"/>
-  <use xlink:href="#glyph1-3" x="482" y="484"/>
-  <use xlink:href="#glyph1-5" x="498" y="484"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 514 452 L 2146 452 L 2146 493 L 514 493 Z M 514 452 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 452 L 2162 452 L 2162 493 L 2146 493 Z M 2146 452 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 452 L 18 452 L 18 493 L 1 493 Z M 1 452 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 18 493 L 2146 493 L 2146 534 L 18 534 Z M 18 493 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 493 L 2162 493 L 2162 534 L 2146 534 Z M 2146 493 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 493 L 18 493 L 18 534 L 1 534 Z M 1 493 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 18 534 L 2146 534 L 2146 575 L 18 575 Z M 18 534 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 534 L 2162 534 L 2162 575 L 2146 575 Z M 2146 534 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 534 L 18 534 L 18 575 L 1 575 Z M 1 534 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 18 575 L 2146 575 L 2146 616 L 18 616 Z M 18 575 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 575 L 2162 575 L 2162 616 L 2146 616 Z M 2146 575 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 575 L 18 575 L 18 616 L 1 616 Z M 1 575 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 18 616 L 2146 616 L 2146 657 L 18 657 Z M 18 616 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 616 L 2162 616 L 2162 657 L 2146 657 Z M 2146 616 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 616 L 18 616 L 18 657 L 1 657 Z M 1 616 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 18 657 L 2146 657 L 2146 698 L 18 698 Z M 18 657 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 657 L 2162 657 L 2162 698 L 2146 698 Z M 2146 657 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 657 L 18 657 L 18 698 L 1 698 Z M 1 657 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 18 698 L 2146 698 L 2146 739 L 18 739 Z M 18 698 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 698 L 2162 698 L 2162 739 L 2146 739 Z M 2146 698 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 698 L 18 698 L 18 739 L 1 739 Z M 1 698 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(14.901961%,14.901961%,14.901961%);fill-opacity:1;" d="M 18 739 L 2146 739 L 2146 780 L 18 780 Z M 18 739 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 739 L 2162 739 L 2162 780 L 2146 780 Z M 2146 739 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 739 L 18 739 L 18 780 L 1 780 Z M 1 739 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(73.333333%,76.078431%,81.176471%);fill-opacity:1;" d="M 18 780 L 290 780 L 290 821 L 18 821 Z M 18 780 "/>
-<g style="fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;">
-  <use xlink:href="#glyph0-19" x="18" y="812"/>
-  <use xlink:href="#glyph0-20" x="34" y="812"/>
-  <use xlink:href="#glyph0-19" x="50" y="812"/>
-  <use xlink:href="#glyph0-21" x="66" y="812"/>
-  <use xlink:href="#glyph0-21" x="82" y="812"/>
-  <use xlink:href="#glyph0-20" x="98" y="812"/>
-  <use xlink:href="#glyph0-20" x="114" y="812"/>
-  <use xlink:href="#glyph0-22" x="130" y="812"/>
-  <use xlink:href="#glyph0-1" x="146" y="812"/>
-  <use xlink:href="#glyph0-21" x="162" y="812"/>
-  <use xlink:href="#glyph0-23" x="178" y="812"/>
-  <use xlink:href="#glyph0-24" x="194" y="812"/>
-  <use xlink:href="#glyph0-23" x="210" y="812"/>
-  <use xlink:href="#glyph0-21" x="226" y="812"/>
-  <use xlink:href="#glyph0-24" x="242" y="812"/>
-  <use xlink:href="#glyph0-21" x="258" y="812"/>
-  <use xlink:href="#glyph0-21" x="274" y="812"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 290 780 L 322 780 L 322 821 L 290 821 Z M 290 780 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 322 780 L 370 780 L 370 821 L 322 821 Z M 322 780 "/>
-<g style="fill:rgb(66.27451%,63.137255%,88.235294%);fill-opacity:1;">
-  <use xlink:href="#glyph0-66" x="322" y="812"/>
-  <use xlink:href="#glyph0-52" x="338" y="812"/>
-  <use xlink:href="#glyph0-3" x="354" y="812"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(11.764706%,11.764706%,11.764706%);fill-opacity:1;" d="M 370 780 L 2146 780 L 2146 821 L 370 821 Z M 370 780 "/>
-<g style="fill:rgb(87.45098%,87.45098%,87.45098%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="370" y="812"/>
-  <use xlink:href="#glyph0-1" x="386" y="812"/>
-  <use xlink:href="#glyph0-27" x="402" y="812"/>
-  <use xlink:href="#glyph0-15" x="418" y="812"/>
-  <use xlink:href="#glyph0-10" x="434" y="812"/>
-  <use xlink:href="#glyph0-1" x="450" y="812"/>
-  <use xlink:href="#glyph0-42" x="466" y="812"/>
-  <use xlink:href="#glyph0-15" x="482" y="812"/>
-  <use xlink:href="#glyph0-67" x="498" y="812"/>
-  <use xlink:href="#glyph0-18" x="514" y="812"/>
-  <use xlink:href="#glyph0-5" x="530" y="812"/>
-  <use xlink:href="#glyph0-6" x="546" y="812"/>
-  <use xlink:href="#glyph0-10" x="562" y="812"/>
-  <use xlink:href="#glyph0-6" x="578" y="812"/>
-  <use xlink:href="#glyph0-1" x="594" y="812"/>
-  <use xlink:href="#glyph0-17" x="610" y="812"/>
-  <use xlink:href="#glyph0-25" x="626" y="812"/>
-  <use xlink:href="#glyph0-61" x="642" y="812"/>
-  <use xlink:href="#glyph0-59" x="658" y="812"/>
-  <use xlink:href="#glyph0-1" x="674" y="812"/>
-  <use xlink:href="#glyph0-18" x="690" y="812"/>
-  <use xlink:href="#glyph0-29" x="706" y="812"/>
-  <use xlink:href="#glyph0-42" x="722" y="812"/>
-  <use xlink:href="#glyph0-64" x="738" y="812"/>
-  <use xlink:href="#glyph0-6" x="754" y="812"/>
-  <use xlink:href="#glyph0-10" x="770" y="812"/>
-  <use xlink:href="#glyph0-1" x="786" y="812"/>
-  <use xlink:href="#glyph0-1" x="802" y="812"/>
-  <use xlink:href="#glyph0-1" x="818" y="812"/>
-  <use xlink:href="#glyph0-1" x="834" y="812"/>
-  <use xlink:href="#glyph0-1" x="850" y="812"/>
-  <use xlink:href="#glyph0-1" x="866" y="812"/>
-  <use xlink:href="#glyph0-1" x="882" y="812"/>
-  <use xlink:href="#glyph0-1" x="898" y="812"/>
-  <use xlink:href="#glyph0-1" x="914" y="812"/>
-  <use xlink:href="#glyph0-1" x="930" y="812"/>
-  <use xlink:href="#glyph0-1" x="946" y="812"/>
-  <use xlink:href="#glyph0-1" x="962" y="812"/>
-  <use xlink:href="#glyph0-1" x="978" y="812"/>
-  <use xlink:href="#glyph0-1" x="994" y="812"/>
-  <use xlink:href="#glyph0-1" x="1010" y="812"/>
-  <use xlink:href="#glyph0-1" x="1026" y="812"/>
-  <use xlink:href="#glyph0-1" x="1042" y="812"/>
-  <use xlink:href="#glyph0-1" x="1058" y="812"/>
-  <use xlink:href="#glyph0-1" x="1074" y="812"/>
-  <use xlink:href="#glyph0-1" x="1090" y="812"/>
-  <use xlink:href="#glyph0-1" x="1106" y="812"/>
-  <use xlink:href="#glyph0-1" x="1122" y="812"/>
-  <use xlink:href="#glyph0-1" x="1138" y="812"/>
-  <use xlink:href="#glyph0-1" x="1154" y="812"/>
-  <use xlink:href="#glyph0-1" x="1170" y="812"/>
-  <use xlink:href="#glyph0-1" x="1186" y="812"/>
-  <use xlink:href="#glyph0-1" x="1202" y="812"/>
-  <use xlink:href="#glyph0-1" x="1218" y="812"/>
-  <use xlink:href="#glyph0-1" x="1234" y="812"/>
-  <use xlink:href="#glyph0-1" x="1250" y="812"/>
-  <use xlink:href="#glyph0-1" x="1266" y="812"/>
-  <use xlink:href="#glyph0-1" x="1282" y="812"/>
-  <use xlink:href="#glyph0-1" x="1298" y="812"/>
-  <use xlink:href="#glyph0-1" x="1314" y="812"/>
-  <use xlink:href="#glyph0-1" x="1330" y="812"/>
-  <use xlink:href="#glyph0-1" x="1346" y="812"/>
-  <use xlink:href="#glyph0-1" x="1362" y="812"/>
-  <use xlink:href="#glyph0-1" x="1378" y="812"/>
-  <use xlink:href="#glyph0-1" x="1394" y="812"/>
-  <use xlink:href="#glyph0-1" x="1410" y="812"/>
-  <use xlink:href="#glyph0-1" x="1426" y="812"/>
-  <use xlink:href="#glyph0-1" x="1442" y="812"/>
-  <use xlink:href="#glyph0-1" x="1458" y="812"/>
-  <use xlink:href="#glyph0-1" x="1474" y="812"/>
-  <use xlink:href="#glyph0-1" x="1490" y="812"/>
-  <use xlink:href="#glyph0-1" x="1506" y="812"/>
-  <use xlink:href="#glyph0-1" x="1522" y="812"/>
-  <use xlink:href="#glyph0-1" x="1538" y="812"/>
-  <use xlink:href="#glyph0-1" x="1554" y="812"/>
-  <use xlink:href="#glyph0-1" x="1570" y="812"/>
-  <use xlink:href="#glyph0-1" x="1586" y="812"/>
-  <use xlink:href="#glyph0-1" x="1602" y="812"/>
-  <use xlink:href="#glyph0-1" x="1618" y="812"/>
-  <use xlink:href="#glyph0-1" x="1634" y="812"/>
-  <use xlink:href="#glyph0-1" x="1650" y="812"/>
-  <use xlink:href="#glyph0-1" x="1666" y="812"/>
-  <use xlink:href="#glyph0-1" x="1682" y="812"/>
-  <use xlink:href="#glyph0-1" x="1698" y="812"/>
-  <use xlink:href="#glyph0-1" x="1714" y="812"/>
-  <use xlink:href="#glyph0-1" x="1730" y="812"/>
-  <use xlink:href="#glyph0-1" x="1746" y="812"/>
-  <use xlink:href="#glyph0-1" x="1762" y="812"/>
-  <use xlink:href="#glyph0-1" x="1778" y="812"/>
-  <use xlink:href="#glyph0-1" x="1794" y="812"/>
-  <use xlink:href="#glyph0-1" x="1810" y="812"/>
-  <use xlink:href="#glyph0-1" x="1826" y="812"/>
-  <use xlink:href="#glyph0-1" x="1842" y="812"/>
-  <use xlink:href="#glyph0-1" x="1858" y="812"/>
-  <use xlink:href="#glyph0-1" x="1874" y="812"/>
-  <use xlink:href="#glyph0-1" x="1890" y="812"/>
-  <use xlink:href="#glyph0-1" x="1906" y="812"/>
-  <use xlink:href="#glyph0-1" x="1922" y="812"/>
-  <use xlink:href="#glyph0-1" x="1938" y="812"/>
-  <use xlink:href="#glyph0-1" x="1954" y="812"/>
-  <use xlink:href="#glyph0-1" x="1970" y="812"/>
-  <use xlink:href="#glyph0-1" x="1986" y="812"/>
-  <use xlink:href="#glyph0-1" x="2002" y="812"/>
-  <use xlink:href="#glyph0-1" x="2018" y="812"/>
-  <use xlink:href="#glyph0-1" x="2034" y="812"/>
-  <use xlink:href="#glyph0-1" x="2050" y="812"/>
-  <use xlink:href="#glyph0-1" x="2066" y="812"/>
-  <use xlink:href="#glyph0-1" x="2082" y="812"/>
-  <use xlink:href="#glyph0-1" x="2098" y="812"/>
-  <use xlink:href="#glyph0-1" x="2114" y="812"/>
-  <use xlink:href="#glyph0-1" x="2130" y="812"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 780 L 2162 780 L 2162 821 L 2146 821 Z M 2146 780 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 780 L 18 780 L 18 821 L 1 821 Z M 1 780 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(24.705882%,24.705882%,24.705882%);fill-opacity:1;" d="M 18 821 L 34 821 L 34 862 L 18 862 Z M 18 821 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(24.705882%,24.705882%,24.705882%);fill-opacity:1;" d="M 34 821 L 242 821 L 242 862 L 34 862 Z M 34 821 "/>
-<g style="fill:rgb(66.27451%,63.137255%,88.235294%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="34" y="853"/>
-  <use xlink:href="#glyph0-25" x="50" y="853"/>
-  <use xlink:href="#glyph0-6" x="66" y="853"/>
-  <use xlink:href="#glyph0-50" x="82" y="853"/>
-  <use xlink:href="#glyph0-4" x="98" y="853"/>
-  <use xlink:href="#glyph0-42" x="114" y="853"/>
-  <use xlink:href="#glyph0-6" x="130" y="853"/>
-  <use xlink:href="#glyph0-1" x="146" y="853"/>
-  <use xlink:href="#glyph0-61" x="162" y="853"/>
-  <use xlink:href="#glyph0-15" x="178" y="853"/>
-  <use xlink:href="#glyph0-57" x="194" y="853"/>
-  <use xlink:href="#glyph0-16" x="210" y="853"/>
-  <use xlink:href="#glyph0-1" x="226" y="853"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(24.705882%,24.705882%,24.705882%);fill-opacity:1;" d="M 242 821 L 274 821 L 274 862 L 242 862 Z M 242 821 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 274 821 L 290 821 L 290 862 L 274 862 Z M 274 821 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 290 821 L 402 821 L 402 862 L 290 862 Z M 290 821 "/>
-<g style="fill:rgb(87.45098%,87.45098%,87.45098%);fill-opacity:1;">
-  <use xlink:href="#glyph0-55" x="290" y="853"/>
-  <use xlink:href="#glyph0-6" x="306" y="853"/>
-  <use xlink:href="#glyph0-29" x="322" y="853"/>
-  <use xlink:href="#glyph0-43" x="338" y="853"/>
-  <use xlink:href="#glyph0-42" x="354" y="853"/>
-  <use xlink:href="#glyph0-58" x="370" y="853"/>
-  <use xlink:href="#glyph0-1" x="386" y="853"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 402 821 L 418 821 L 418 862 L 402 862 Z M 402 821 "/>
-<g style="fill:rgb(66.27451%,63.137255%,88.235294%);fill-opacity:1;">
-  <use xlink:href="#glyph1-23" x="402" y="853"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 418 821 L 434 821 L 434 862 L 418 862 Z M 418 821 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(24.705882%,24.705882%,24.705882%);fill-opacity:1;" d="M 434 821 L 466 821 L 466 862 L 434 862 Z M 434 821 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 466 821 L 482 821 L 482 862 L 466 862 Z M 466 821 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 482 821 L 658 821 L 658 862 L 482 862 Z M 482 821 "/>
-<g style="fill:rgb(87.45098%,87.45098%,87.45098%);fill-opacity:1;">
-  <use xlink:href="#glyph0-59" x="482" y="853"/>
-  <use xlink:href="#glyph0-15" x="498" y="853"/>
-  <use xlink:href="#glyph0-18" x="514" y="853"/>
-  <use xlink:href="#glyph0-60" x="530" y="853"/>
-  <use xlink:href="#glyph0-1" x="546" y="853"/>
-  <use xlink:href="#glyph0-61" x="562" y="853"/>
-  <use xlink:href="#glyph0-4" x="578" y="853"/>
-  <use xlink:href="#glyph0-14" x="594" y="853"/>
-  <use xlink:href="#glyph0-6" x="610" y="853"/>
-  <use xlink:href="#glyph0-16" x="626" y="853"/>
-  <use xlink:href="#glyph0-1" x="642" y="853"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 658 821 L 754 821 L 754 862 L 658 862 Z M 658 821 "/>
-<g style="fill:rgb(66.27451%,63.137255%,88.235294%);fill-opacity:1;">
-  <use xlink:href="#glyph1-8" x="658" y="853"/>
-  <use xlink:href="#glyph1-24" x="674" y="853"/>
-  <use xlink:href="#glyph1-25" x="690" y="853"/>
-  <use xlink:href="#glyph1-12" x="706" y="853"/>
-  <use xlink:href="#glyph1-26" x="722" y="853"/>
-  <use xlink:href="#glyph1-27" x="738" y="853"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 754 821 L 770 821 L 770 862 L 754 862 Z M 754 821 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(24.705882%,24.705882%,24.705882%);fill-opacity:1;" d="M 770 821 L 802 821 L 802 862 L 770 862 Z M 770 821 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 802 821 L 818 821 L 818 862 L 802 862 Z M 802 821 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 818 821 L 882 821 L 882 862 L 818 862 Z M 818 821 "/>
-<g style="fill:rgb(74.901961%,74.901961%,74.901961%);fill-opacity:1;">
-  <use xlink:href="#glyph0-2" x="818" y="853"/>
-  <use xlink:href="#glyph0-62" x="834" y="853"/>
-  <use xlink:href="#glyph0-7" x="850" y="853"/>
-  <use xlink:href="#glyph0-1" x="866" y="853"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 882 821 L 994 821 L 994 862 L 882 862 Z M 882 821 "/>
-<g style="fill:rgb(87.45098%,87.45098%,87.45098%);fill-opacity:1;">
-  <use xlink:href="#glyph0-3" x="882" y="853"/>
-  <use xlink:href="#glyph0-15" x="898" y="853"/>
-  <use xlink:href="#glyph0-5" x="914" y="853"/>
-  <use xlink:href="#glyph0-5" x="930" y="853"/>
-  <use xlink:href="#glyph0-15" x="946" y="853"/>
-  <use xlink:href="#glyph0-12" x="962" y="853"/>
-  <use xlink:href="#glyph0-1" x="978" y="853"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 994 821 L 1010 821 L 1010 862 L 994 862 Z M 994 821 "/>
-<g style="fill:rgb(66.27451%,63.137255%,88.235294%);fill-opacity:1;">
-  <use xlink:href="#glyph1-28" x="994" y="853"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1010 821 L 1026 821 L 1026 862 L 1010 862 Z M 1010 821 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(24.705882%,24.705882%,24.705882%);fill-opacity:1;" d="M 1026 821 L 1058 821 L 1058 862 L 1026 862 Z M 1026 821 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1058 821 L 1074 821 L 1074 862 L 1058 862 Z M 1058 821 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1074 821 L 1138 821 L 1138 862 L 1074 862 Z M 1074 821 "/>
-<g style="fill:rgb(74.901961%,74.901961%,74.901961%);fill-opacity:1;">
-  <use xlink:href="#glyph0-2" x="1074" y="853"/>
-  <use xlink:href="#glyph0-62" x="1090" y="853"/>
-  <use xlink:href="#glyph0-7" x="1106" y="853"/>
-  <use xlink:href="#glyph0-1" x="1122" y="853"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1138 821 L 1234 821 L 1234 862 L 1138 862 Z M 1138 821 "/>
-<g style="fill:rgb(87.45098%,87.45098%,87.45098%);fill-opacity:1;">
-  <use xlink:href="#glyph0-63" x="1138" y="853"/>
-  <use xlink:href="#glyph0-29" x="1154" y="853"/>
-  <use xlink:href="#glyph0-30" x="1170" y="853"/>
-  <use xlink:href="#glyph0-5" x="1186" y="853"/>
-  <use xlink:href="#glyph0-6" x="1202" y="853"/>
-  <use xlink:href="#glyph0-1" x="1218" y="853"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1234 821 L 1250 821 L 1250 862 L 1234 862 Z M 1234 821 "/>
-<g style="fill:rgb(66.27451%,63.137255%,88.235294%);fill-opacity:1;">
-  <use xlink:href="#glyph1-24" x="1234" y="853"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1250 821 L 1266 821 L 1266 862 L 1250 862 Z M 1250 821 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(24.705882%,24.705882%,24.705882%);fill-opacity:1;" d="M 1266 821 L 1298 821 L 1298 862 L 1266 862 Z M 1266 821 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1298 821 L 1314 821 L 1314 862 L 1298 862 Z M 1298 821 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1314 821 L 1378 821 L 1378 862 L 1314 862 Z M 1314 821 "/>
-<g style="fill:rgb(74.901961%,74.901961%,74.901961%);fill-opacity:1;">
-  <use xlink:href="#glyph0-2" x="1314" y="853"/>
-  <use xlink:href="#glyph0-1" x="1330" y="853"/>
-  <use xlink:href="#glyph0-7" x="1346" y="853"/>
-  <use xlink:href="#glyph0-1" x="1362" y="853"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1378 821 L 1458 821 L 1458 862 L 1378 862 Z M 1378 821 "/>
-<g style="fill:rgb(87.45098%,87.45098%,87.45098%);fill-opacity:1;">
-  <use xlink:href="#glyph0-13" x="1378" y="853"/>
-  <use xlink:href="#glyph0-43" x="1394" y="853"/>
-  <use xlink:href="#glyph0-29" x="1410" y="853"/>
-  <use xlink:href="#glyph0-18" x="1426" y="853"/>
-  <use xlink:href="#glyph0-1" x="1442" y="853"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1458 821 L 1474 821 L 1474 862 L 1458 862 Z M 1458 821 "/>
-<g style="fill:rgb(66.27451%,63.137255%,88.235294%);fill-opacity:1;">
-  <use xlink:href="#glyph1-4" x="1458" y="853"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1474 821 L 1490 821 L 1490 862 L 1474 862 Z M 1474 821 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(24.705882%,24.705882%,24.705882%);fill-opacity:1;" d="M 1490 821 L 1522 821 L 1522 862 L 1490 862 Z M 1490 821 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1522 821 L 1538 821 L 1538 862 L 1522 862 Z M 1522 821 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1538 821 L 1634 821 L 1634 862 L 1538 862 Z M 1538 821 "/>
-<g style="fill:rgb(87.45098%,87.45098%,87.45098%);fill-opacity:1;">
-  <use xlink:href="#glyph0-59" x="1538" y="853"/>
-  <use xlink:href="#glyph0-5" x="1554" y="853"/>
-  <use xlink:href="#glyph0-6" x="1570" y="853"/>
-  <use xlink:href="#glyph0-29" x="1586" y="853"/>
-  <use xlink:href="#glyph0-43" x="1602" y="853"/>
-  <use xlink:href="#glyph0-1" x="1618" y="853"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1634 821 L 1650 821 L 1650 862 L 1634 862 Z M 1634 821 "/>
-<g style="fill:rgb(66.27451%,63.137255%,88.235294%);fill-opacity:1;">
-  <use xlink:href="#glyph1-8" x="1634" y="853"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1650 821 L 1666 821 L 1666 862 L 1650 862 Z M 1650 821 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(24.705882%,24.705882%,24.705882%);fill-opacity:1;" d="M 1666 821 L 1682 821 L 1682 862 L 1666 862 Z M 1666 821 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1682 821 L 1698 821 L 1698 862 L 1682 862 Z M 1682 821 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1698 821 L 1922 821 L 1922 862 L 1698 862 Z M 1698 821 "/>
-<g style="fill:rgb(87.45098%,87.45098%,87.45098%);fill-opacity:1;">
-  <use xlink:href="#glyph0-42" x="1698" y="853"/>
-  <use xlink:href="#glyph0-5" x="1714" y="853"/>
-  <use xlink:href="#glyph0-4" x="1730" y="853"/>
-  <use xlink:href="#glyph0-42" x="1746" y="853"/>
-  <use xlink:href="#glyph0-64" x="1762" y="853"/>
-  <use xlink:href="#glyph0-1" x="1778" y="853"/>
-  <use xlink:href="#glyph0-10" x="1794" y="853"/>
-  <use xlink:href="#glyph0-15" x="1810" y="853"/>
-  <use xlink:href="#glyph0-1" x="1826" y="853"/>
-  <use xlink:href="#glyph0-36" x="1842" y="853"/>
-  <use xlink:href="#glyph0-15" x="1858" y="853"/>
-  <use xlink:href="#glyph0-42" x="1874" y="853"/>
-  <use xlink:href="#glyph0-56" x="1890" y="853"/>
-  <use xlink:href="#glyph0-16" x="1906" y="853"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1922 821 L 1938 821 L 1938 862 L 1922 862 Z M 1922 821 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(24.705882%,24.705882%,24.705882%);fill-opacity:1;" d="M 1938 821 L 2146 821 L 2146 862 L 1938 862 Z M 1938 821 "/>
-<g style="fill:rgb(74.901961%,74.901961%,74.901961%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="1938" y="853"/>
-  <use xlink:href="#glyph0-1" x="1954" y="853"/>
-  <use xlink:href="#glyph0-43" x="1970" y="853"/>
-  <use xlink:href="#glyph0-18" x="1986" y="853"/>
-  <use xlink:href="#glyph0-42" x="2002" y="853"/>
-  <use xlink:href="#glyph0-65" x="2018" y="853"/>
-  <use xlink:href="#glyph0-9" x="2034" y="853"/>
-  <use xlink:href="#glyph0-6" x="2050" y="853"/>
-  <use xlink:href="#glyph0-50" x="2066" y="853"/>
-  <use xlink:href="#glyph0-4" x="2082" y="853"/>
-  <use xlink:href="#glyph0-42" x="2098" y="853"/>
-  <use xlink:href="#glyph0-6" x="2114" y="853"/>
-  <use xlink:href="#glyph0-1" x="2130" y="853"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 821 L 2162 821 L 2162 862 L 2146 862 Z M 2146 821 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 821 L 18 821 L 18 862 L 1 862 Z M 1 821 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 18 862 L 82 862 L 82 903 L 18 903 Z M 18 862 "/>
-<g style="fill:rgb(74.901961%,74.901961%,74.901961%);fill-opacity:1;">
-  <use xlink:href="#glyph1-29" x="18" y="894"/>
-  <use xlink:href="#glyph1-29" x="34" y="894"/>
-  <use xlink:href="#glyph1-29" x="50" y="894"/>
-  <use xlink:href="#glyph1-7" x="66" y="894"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 82 862 L 178 862 L 178 903 L 82 903 Z M 82 862 "/>
-<g style="fill:rgb(97.254902%,97.254902%,94.901961%);fill-opacity:1;">
-  <use xlink:href="#glyph0-9" x="82" y="894"/>
-  <use xlink:href="#glyph0-6" x="98" y="894"/>
-  <use xlink:href="#glyph0-50" x="114" y="894"/>
-  <use xlink:href="#glyph0-4" x="130" y="894"/>
-  <use xlink:href="#glyph0-42" x="146" y="894"/>
-  <use xlink:href="#glyph0-6" x="162" y="894"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 178 862 L 194 862 L 194 903 L 178 903 Z M 178 862 "/>
-<g style="fill:rgb(100%,47.45098%,77.647059%);fill-opacity:1;">
-  <use xlink:href="#glyph0-47" x="178" y="894"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 194 862 L 258 862 L 258 903 L 194 903 Z M 194 862 "/>
-<g style="fill:rgb(97.254902%,97.254902%,94.901961%);fill-opacity:1;">
-  <use xlink:href="#glyph0-43" x="194" y="894"/>
-  <use xlink:href="#glyph0-18" x="210" y="894"/>
-  <use xlink:href="#glyph0-42" x="226" y="894"/>
-  <use xlink:href="#glyph0-16" x="242" y="894"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 258 862 L 274 862 L 274 903 L 258 903 Z M 258 862 "/>
-<g style="fill:rgb(100%,47.45098%,77.647059%);fill-opacity:1;">
-  <use xlink:href="#glyph0-47" x="258" y="894"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 274 862 L 306 862 L 306 903 L 274 903 Z M 274 862 "/>
-<g style="fill:rgb(97.254902%,97.254902%,94.901961%);fill-opacity:1;">
-  <use xlink:href="#glyph0-18" x="274" y="894"/>
-  <use xlink:href="#glyph0-12" x="290" y="894"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 306 862 L 322 862 L 322 903 L 306 903 Z M 306 862 "/>
-<g style="fill:rgb(100%,47.45098%,77.647059%);fill-opacity:1;">
-  <use xlink:href="#glyph0-47" x="306" y="894"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 322 862 L 370 862 L 370 903 L 322 903 Z M 322 862 "/>
-<g style="fill:rgb(97.254902%,97.254902%,94.901961%);fill-opacity:1;">
-  <use xlink:href="#glyph0-43" x="322" y="894"/>
-  <use xlink:href="#glyph0-18" x="338" y="894"/>
-  <use xlink:href="#glyph0-42" x="354" y="894"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 370 862 L 386 862 L 386 903 L 370 903 Z M 370 862 "/>
-<g style="fill:rgb(100%,47.45098%,77.647059%);fill-opacity:1;">
-  <use xlink:href="#glyph0-47" x="370" y="894"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 386 862 L 562 862 L 562 903 L 386 903 Z M 386 862 "/>
-<g style="fill:rgb(97.254902%,97.254902%,94.901961%);fill-opacity:1;">
-  <use xlink:href="#glyph0-8" x="386" y="894"/>
-  <use xlink:href="#glyph0-42" x="402" y="894"/>
-  <use xlink:href="#glyph0-58" x="418" y="894"/>
-  <use xlink:href="#glyph0-15" x="434" y="894"/>
-  <use xlink:href="#glyph0-55" x="450" y="894"/>
-  <use xlink:href="#glyph0-6" x="466" y="894"/>
-  <use xlink:href="#glyph0-43" x="482" y="894"/>
-  <use xlink:href="#glyph0-50" x="498" y="894"/>
-  <use xlink:href="#glyph0-4" x="514" y="894"/>
-  <use xlink:href="#glyph0-42" x="530" y="894"/>
-  <use xlink:href="#glyph0-6" x="546" y="894"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 562 862 L 578 862 L 578 903 L 562 903 Z M 562 862 "/>
-<g style="fill:rgb(100%,47.45098%,77.647059%);fill-opacity:1;">
-  <use xlink:href="#glyph0-47" x="562" y="894"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 578 862 L 706 862 L 706 903 L 578 903 Z M 578 862 "/>
-<g style="fill:rgb(97.254902%,97.254902%,94.901961%);fill-opacity:1;">
-  <use xlink:href="#glyph0-8" x="578" y="894"/>
-  <use xlink:href="#glyph0-42" x="594" y="894"/>
-  <use xlink:href="#glyph0-58" x="610" y="894"/>
-  <use xlink:href="#glyph0-15" x="626" y="894"/>
-  <use xlink:href="#glyph0-68" x="642" y="894"/>
-  <use xlink:href="#glyph0-67" x="658" y="894"/>
-  <use xlink:href="#glyph0-16" x="674" y="894"/>
-  <use xlink:href="#glyph0-57" x="690" y="894"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 706 862 L 722 862 L 722 903 L 706 903 Z M 706 862 "/>
-<g style="fill:rgb(100%,47.45098%,77.647059%);fill-opacity:1;">
-  <use xlink:href="#glyph0-53" x="706" y="894"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 722 862 L 946 862 L 946 903 L 722 903 Z M 722 862 "/>
-<g style="fill:rgb(94.509804%,98.039216%,54.901961%);fill-opacity:1;">
-  <use xlink:href="#glyph0-31" x="722" y="894"/>
-  <use xlink:href="#glyph0-17" x="738" y="894"/>
-  <use xlink:href="#glyph0-6" x="754" y="894"/>
-  <use xlink:href="#glyph0-5" x="770" y="894"/>
-  <use xlink:href="#glyph0-5" x="786" y="894"/>
-  <use xlink:href="#glyph0-15" x="802" y="894"/>
-  <use xlink:href="#glyph0-1" x="818" y="894"/>
-  <use xlink:href="#glyph0-13" x="834" y="894"/>
-  <use xlink:href="#glyph0-15" x="850" y="894"/>
-  <use xlink:href="#glyph0-43" x="866" y="894"/>
-  <use xlink:href="#glyph0-5" x="882" y="894"/>
-  <use xlink:href="#glyph0-9" x="898" y="894"/>
-  <use xlink:href="#glyph0-44" x="914" y="894"/>
-  <use xlink:href="#glyph0-31" x="930" y="894"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 946 862 L 962 862 L 962 903 L 946 903 Z M 946 862 "/>
-<g style="fill:rgb(97.254902%,97.254902%,94.901961%);fill-opacity:1;">
-  <use xlink:href="#glyph0-69" x="946" y="894"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 962 862 L 2130 862 L 2130 903 L 962 903 Z M 962 862 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 2130 862 L 2146 862 L 2146 903 L 2130 903 Z M 2130 862 "/>
-<g style="fill:rgb(40%,60%,80%);fill-opacity:1;">
-  <use xlink:href="#glyph1-30" x="2130" y="894"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 862 L 2162 862 L 2162 903 L 2146 903 Z M 2146 862 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 862 L 18 862 L 18 903 L 1 903 Z M 1 862 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 18 903 L 834 903 L 834 944 L 18 944 Z M 18 903 "/>
-<g style="fill:rgb(97.254902%,97.254902%,94.901961%);fill-opacity:1;">
-  <use xlink:href="#glyph0-68" x="18" y="935"/>
-  <use xlink:href="#glyph0-55" x="34" y="935"/>
-  <use xlink:href="#glyph0-10" x="50" y="935"/>
-  <use xlink:href="#glyph0-29" x="66" y="935"/>
-  <use xlink:href="#glyph0-10" x="82" y="935"/>
-  <use xlink:href="#glyph0-56" x="98" y="935"/>
-  <use xlink:href="#glyph0-16" x="114" y="935"/>
-  <use xlink:href="#glyph0-47" x="130" y="935"/>
-  <use xlink:href="#glyph0-70" x="146" y="935"/>
-  <use xlink:href="#glyph0-71" x="162" y="935"/>
-  <use xlink:href="#glyph0-72" x="178" y="935"/>
-  <use xlink:href="#glyph0-1" x="194" y="935"/>
-  <use xlink:href="#glyph0-18" x="210" y="935"/>
-  <use xlink:href="#glyph0-12" x="226" y="935"/>
-  <use xlink:href="#glyph0-47" x="242" y="935"/>
-  <use xlink:href="#glyph0-43" x="258" y="935"/>
-  <use xlink:href="#glyph0-18" x="274" y="935"/>
-  <use xlink:href="#glyph0-42" x="290" y="935"/>
-  <use xlink:href="#glyph0-47" x="306" y="935"/>
-  <use xlink:href="#glyph0-8" x="322" y="935"/>
-  <use xlink:href="#glyph0-42" x="338" y="935"/>
-  <use xlink:href="#glyph0-58" x="354" y="935"/>
-  <use xlink:href="#glyph0-15" x="370" y="935"/>
-  <use xlink:href="#glyph0-73" x="386" y="935"/>
-  <use xlink:href="#glyph0-6" x="402" y="935"/>
-  <use xlink:href="#glyph0-16" x="418" y="935"/>
-  <use xlink:href="#glyph0-16" x="434" y="935"/>
-  <use xlink:href="#glyph0-29" x="450" y="935"/>
-  <use xlink:href="#glyph0-57" x="466" y="935"/>
-  <use xlink:href="#glyph0-6" x="482" y="935"/>
-  <use xlink:href="#glyph0-68" x="498" y="935"/>
-  <use xlink:href="#glyph0-67" x="514" y="935"/>
-  <use xlink:href="#glyph0-16" x="530" y="935"/>
-  <use xlink:href="#glyph0-57" x="546" y="935"/>
-  <use xlink:href="#glyph0-53" x="562" y="935"/>
-  <use xlink:href="#glyph0-31" x="578" y="935"/>
-  <use xlink:href="#glyph0-17" x="594" y="935"/>
-  <use xlink:href="#glyph0-6" x="610" y="935"/>
-  <use xlink:href="#glyph0-5" x="626" y="935"/>
-  <use xlink:href="#glyph0-5" x="642" y="935"/>
-  <use xlink:href="#glyph0-15" x="658" y="935"/>
-  <use xlink:href="#glyph0-1" x="674" y="935"/>
-  <use xlink:href="#glyph0-13" x="690" y="935"/>
-  <use xlink:href="#glyph0-15" x="706" y="935"/>
-  <use xlink:href="#glyph0-43" x="722" y="935"/>
-  <use xlink:href="#glyph0-5" x="738" y="935"/>
-  <use xlink:href="#glyph0-9" x="754" y="935"/>
-  <use xlink:href="#glyph0-44" x="770" y="935"/>
-  <use xlink:href="#glyph0-31" x="786" y="935"/>
-  <use xlink:href="#glyph0-69" x="802" y="935"/>
-  <use xlink:href="#glyph0-69" x="818" y="935"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 834 903 L 2130 903 L 2130 944 L 834 944 Z M 834 903 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(66.27451%,63.137255%,88.235294%);fill-opacity:1;" d="M 2130 903 L 2146 903 L 2146 944 L 2130 944 Z M 2130 903 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 903 L 2162 903 L 2162 944 L 2146 944 Z M 2146 903 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 903 L 18 903 L 18 944 L 1 944 Z M 1 903 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 18 944 L 2130 944 L 2130 985 L 18 985 Z M 18 944 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(66.27451%,63.137255%,88.235294%);fill-opacity:1;" d="M 2130 944 L 2146 944 L 2146 985 L 2130 985 Z M 2130 944 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 944 L 2162 944 L 2162 985 L 2146 985 Z M 2146 944 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 944 L 18 944 L 18 985 L 1 985 Z M 1 944 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 18 985 L 2130 985 L 2130 1026 L 18 1026 Z M 18 985 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 2130 985 L 2146 985 L 2146 1026 L 2130 1026 Z M 2130 985 "/>
-<g style="fill:rgb(40%,60%,80%);fill-opacity:1;">
-  <use xlink:href="#glyph1-18" x="2130" y="1017"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 985 L 2162 985 L 2162 1026 L 2146 1026 Z M 2146 985 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 985 L 18 985 L 18 1026 L 1 1026 Z M 1 985 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(24.705882%,24.705882%,24.705882%);fill-opacity:1;" d="M 18 1026 L 34 1026 L 34 1067 L 18 1067 Z M 18 1026 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 34 1026 L 290 1026 L 290 1067 L 34 1067 Z M 34 1026 "/>
-<g style="fill:rgb(66.27451%,63.137255%,88.235294%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="34" y="1058"/>
-  <use xlink:href="#glyph0-74" x="50" y="1058"/>
-  <use xlink:href="#glyph0-60" x="66" y="1058"/>
-  <use xlink:href="#glyph0-10" x="82" y="1058"/>
-  <use xlink:href="#glyph0-58" x="98" y="1058"/>
-  <use xlink:href="#glyph0-15" x="114" y="1058"/>
-  <use xlink:href="#glyph0-14" x="130" y="1058"/>
-  <use xlink:href="#glyph0-1" x="146" y="1058"/>
-  <use xlink:href="#glyph0-28" x="162" y="1058"/>
-  <use xlink:href="#glyph0-6" x="178" y="1058"/>
-  <use xlink:href="#glyph0-16" x="194" y="1058"/>
-  <use xlink:href="#glyph0-56" x="210" y="1058"/>
-  <use xlink:href="#glyph0-5" x="226" y="1058"/>
-  <use xlink:href="#glyph0-10" x="242" y="1058"/>
-  <use xlink:href="#glyph0-16" x="258" y="1058"/>
-  <use xlink:href="#glyph0-1" x="274" y="1058"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 290 1026 L 322 1026 L 322 1067 L 290 1067 Z M 290 1026 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(38.431373%,38.431373%,38.431373%);fill-opacity:1;" d="M 322 1026 L 338 1026 L 338 1067 L 322 1067 Z M 322 1026 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(38.431373%,38.431373%,38.431373%);fill-opacity:1;" d="M 338 1026 L 594 1026 L 594 1067 L 338 1067 Z M 338 1026 "/>
-<g style="fill:rgb(87.45098%,87.45098%,87.45098%);fill-opacity:1;">
-  <use xlink:href="#glyph0-59" x="338" y="1058"/>
-  <use xlink:href="#glyph0-15" x="354" y="1058"/>
-  <use xlink:href="#glyph0-18" x="370" y="1058"/>
-  <use xlink:href="#glyph0-60" x="386" y="1058"/>
-  <use xlink:href="#glyph0-1" x="402" y="1058"/>
-  <use xlink:href="#glyph0-75" x="418" y="1058"/>
-  <use xlink:href="#glyph0-5" x="434" y="1058"/>
-  <use xlink:href="#glyph0-5" x="450" y="1058"/>
-  <use xlink:href="#glyph0-1" x="466" y="1058"/>
-  <use xlink:href="#glyph0-70" x="482" y="1058"/>
-  <use xlink:href="#glyph0-56" x="498" y="1058"/>
-  <use xlink:href="#glyph0-10" x="514" y="1058"/>
-  <use xlink:href="#glyph0-18" x="530" y="1058"/>
-  <use xlink:href="#glyph0-56" x="546" y="1058"/>
-  <use xlink:href="#glyph0-10" x="562" y="1058"/>
-  <use xlink:href="#glyph0-1" x="578" y="1058"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(38.431373%,38.431373%,38.431373%);fill-opacity:1;" d="M 594 1026 L 754 1026 L 754 1067 L 594 1067 Z M 594 1026 "/>
-<g style="fill:rgb(66.27451%,63.137255%,88.235294%);fill-opacity:1;">
-  <use xlink:href="#glyph1-8" x="594" y="1058"/>
-  <use xlink:href="#glyph1-24" x="610" y="1058"/>
-  <use xlink:href="#glyph1-25" x="626" y="1058"/>
-  <use xlink:href="#glyph1-12" x="642" y="1058"/>
-  <use xlink:href="#glyph1-26" x="658" y="1058"/>
-  <use xlink:href="#glyph1-31" x="674" y="1058"/>
-  <use xlink:href="#glyph1-12" x="690" y="1058"/>
-  <use xlink:href="#glyph1-24" x="706" y="1058"/>
-  <use xlink:href="#glyph1-26" x="722" y="1058"/>
-  <use xlink:href="#glyph1-27" x="738" y="1058"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(38.431373%,38.431373%,38.431373%);fill-opacity:1;" d="M 754 1026 L 770 1026 L 770 1067 L 754 1067 Z M 754 1026 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 770 1026 L 802 1026 L 802 1067 L 770 1067 Z M 770 1026 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(38.431373%,38.431373%,38.431373%);fill-opacity:1;" d="M 802 1026 L 1058 1026 L 1058 1067 L 802 1067 Z M 802 1026 "/>
-<g style="fill:rgb(87.45098%,87.45098%,87.45098%);fill-opacity:1;">
-  <use xlink:href="#glyph0-1" x="802" y="1058"/>
-  <use xlink:href="#glyph0-42" x="818" y="1058"/>
-  <use xlink:href="#glyph0-5" x="834" y="1058"/>
-  <use xlink:href="#glyph0-4" x="850" y="1058"/>
-  <use xlink:href="#glyph0-42" x="866" y="1058"/>
-  <use xlink:href="#glyph0-64" x="882" y="1058"/>
-  <use xlink:href="#glyph0-1" x="898" y="1058"/>
-  <use xlink:href="#glyph0-10" x="914" y="1058"/>
-  <use xlink:href="#glyph0-15" x="930" y="1058"/>
-  <use xlink:href="#glyph0-1" x="946" y="1058"/>
-  <use xlink:href="#glyph0-36" x="962" y="1058"/>
-  <use xlink:href="#glyph0-15" x="978" y="1058"/>
-  <use xlink:href="#glyph0-42" x="994" y="1058"/>
-  <use xlink:href="#glyph0-56" x="1010" y="1058"/>
-  <use xlink:href="#glyph0-16" x="1026" y="1058"/>
-  <use xlink:href="#glyph0-1" x="1042" y="1058"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1058 1026 L 2146 1026 L 2146 1067 L 1058 1067 Z M 1058 1026 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 1026 L 2162 1026 L 2162 1067 L 2146 1067 Z M 2146 1026 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 1026 L 18 1026 L 18 1067 L 1 1067 Z M 1 1026 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 18 1067 L 82 1067 L 82 1108 L 18 1108 Z M 18 1067 "/>
-<g style="fill:rgb(73.333333%,76.078431%,81.176471%);fill-opacity:1;">
-  <use xlink:href="#glyph1-29" x="18" y="1099"/>
-  <use xlink:href="#glyph1-29" x="34" y="1099"/>
-  <use xlink:href="#glyph1-29" x="50" y="1099"/>
-  <use xlink:href="#glyph1-7" x="66" y="1099"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 82 1067 L 2146 1067 L 2146 1108 L 82 1108 Z M 82 1067 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 1067 L 2162 1067 L 2162 1108 L 2146 1108 Z M 2146 1067 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 1067 L 18 1067 L 18 1108 L 1 1108 Z M 1 1067 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 18 1108 L 2146 1108 L 2146 1149 L 18 1149 Z M 18 1108 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 1108 L 2162 1108 L 2162 1149 L 2146 1149 Z M 2146 1108 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 1108 L 18 1108 L 18 1149 L 1 1149 Z M 1 1108 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 18 1149 L 2146 1149 L 2146 1190 L 18 1190 Z M 18 1149 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 1149 L 2162 1149 L 2162 1190 L 2146 1190 Z M 2146 1149 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 1149 L 18 1149 L 18 1190 L 1 1190 Z M 1 1149 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(18.039216%,18.039216%,18.039216%);fill-opacity:1;" d="M 18 1190 L 2146 1190 L 2146 1231 L 18 1231 Z M 18 1190 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 1190 L 2162 1190 L 2162 1231 L 2146 1231 Z M 2146 1190 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 1190 L 18 1190 L 18 1231 L 1 1231 Z M 1 1190 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(77.647059%,47.058824%,86.666667%);fill-opacity:1;" d="M 18 1231 L 34 1231 L 34 1272 L 18 1272 Z M 18 1231 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 34 1231 L 258 1231 L 258 1272 L 34 1272 Z M 34 1231 "/>
-<g style="fill:rgb(77.647059%,47.058824%,86.666667%);fill-opacity:1;">
-  <use xlink:href="#glyph1-7" x="34" y="1263"/>
-  <use xlink:href="#glyph1-1" x="50" y="1263"/>
-  <use xlink:href="#glyph1-21" x="66" y="1263"/>
-  <use xlink:href="#glyph1-24" x="82" y="1263"/>
-  <use xlink:href="#glyph1-32" x="98" y="1263"/>
-  <use xlink:href="#glyph1-9" x="114" y="1263"/>
-  <use xlink:href="#glyph1-10" x="130" y="1263"/>
-  <use xlink:href="#glyph1-7" x="146" y="1263"/>
-  <use xlink:href="#glyph1-33" x="162" y="1263"/>
-  <use xlink:href="#glyph1-10" x="178" y="1263"/>
-  <use xlink:href="#glyph1-34" x="194" y="1263"/>
-  <use xlink:href="#glyph1-35" x="210" y="1263"/>
-  <use xlink:href="#glyph1-24" x="226" y="1263"/>
-  <use xlink:href="#glyph1-7" x="242" y="1263"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 258 1231 L 274 1231 L 274 1272 L 258 1272 Z M 258 1231 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(38.431373%,38.431373%,38.431373%);fill-opacity:1;" d="M 274 1231 L 290 1231 L 290 1272 L 274 1272 Z M 274 1231 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(38.431373%,38.431373%,38.431373%);fill-opacity:1;" d="M 290 1231 L 386 1231 L 386 1272 L 290 1272 Z M 290 1231 "/>
-<g style="fill:rgb(87.45098%,87.45098%,87.45098%);fill-opacity:1;">
-  <use xlink:href="#glyph0-74" x="290" y="1263"/>
-  <use xlink:href="#glyph0-29" x="306" y="1263"/>
-  <use xlink:href="#glyph0-16" x="322" y="1263"/>
-  <use xlink:href="#glyph0-10" x="338" y="1263"/>
-  <use xlink:href="#glyph0-6" x="354" y="1263"/>
-  <use xlink:href="#glyph0-1" x="370" y="1263"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(38.431373%,38.431373%,38.431373%);fill-opacity:1;" d="M 386 1231 L 482 1231 L 482 1272 L 386 1272 Z M 386 1231 "/>
-<g style="fill:rgb(66.27451%,63.137255%,88.235294%);fill-opacity:1;">
-  <use xlink:href="#glyph1-8" x="386" y="1263"/>
-  <use xlink:href="#glyph1-24" x="402" y="1263"/>
-  <use xlink:href="#glyph1-25" x="418" y="1263"/>
-  <use xlink:href="#glyph1-12" x="434" y="1263"/>
-  <use xlink:href="#glyph1-26" x="450" y="1263"/>
-  <use xlink:href="#glyph1-18" x="466" y="1263"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(38.431373%,38.431373%,38.431373%);fill-opacity:1;" d="M 482 1231 L 498 1231 L 498 1272 L 482 1272 Z M 482 1231 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 498 1231 L 530 1231 L 530 1272 L 498 1272 Z M 498 1231 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(38.431373%,38.431373%,38.431373%);fill-opacity:1;" d="M 530 1231 L 546 1231 L 546 1272 L 530 1272 Z M 530 1231 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(38.431373%,38.431373%,38.431373%);fill-opacity:1;" d="M 546 1231 L 642 1231 L 642 1272 L 546 1272 Z M 546 1231 "/>
-<g style="fill:rgb(87.45098%,87.45098%,87.45098%);fill-opacity:1;">
-  <use xlink:href="#glyph0-59" x="546" y="1263"/>
-  <use xlink:href="#glyph0-5" x="562" y="1263"/>
-  <use xlink:href="#glyph0-6" x="578" y="1263"/>
-  <use xlink:href="#glyph0-29" x="594" y="1263"/>
-  <use xlink:href="#glyph0-43" x="610" y="1263"/>
-  <use xlink:href="#glyph0-1" x="626" y="1263"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(38.431373%,38.431373%,38.431373%);fill-opacity:1;" d="M 642 1231 L 738 1231 L 738 1272 L 642 1272 Z M 642 1231 "/>
-<g style="fill:rgb(66.27451%,63.137255%,88.235294%);fill-opacity:1;">
-  <use xlink:href="#glyph1-8" x="642" y="1263"/>
-  <use xlink:href="#glyph1-24" x="658" y="1263"/>
-  <use xlink:href="#glyph1-25" x="674" y="1263"/>
-  <use xlink:href="#glyph1-12" x="690" y="1263"/>
-  <use xlink:href="#glyph1-26" x="706" y="1263"/>
-  <use xlink:href="#glyph1-27" x="722" y="1263"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(38.431373%,38.431373%,38.431373%);fill-opacity:1;" d="M 738 1231 L 754 1231 L 754 1272 L 738 1272 Z M 738 1231 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 754 1231 L 786 1231 L 786 1272 L 754 1272 Z M 754 1231 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(38.431373%,38.431373%,38.431373%);fill-opacity:1;" d="M 786 1231 L 802 1231 L 802 1272 L 786 1272 Z M 786 1231 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(38.431373%,38.431373%,38.431373%);fill-opacity:1;" d="M 802 1231 L 866 1231 L 866 1272 L 802 1272 Z M 802 1231 "/>
-<g style="fill:rgb(87.45098%,87.45098%,87.45098%);fill-opacity:1;">
-  <use xlink:href="#glyph0-28" x="802" y="1263"/>
-  <use xlink:href="#glyph0-56" x="818" y="1263"/>
-  <use xlink:href="#glyph0-14" x="834" y="1263"/>
-  <use xlink:href="#glyph0-1" x="850" y="1263"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(38.431373%,38.431373%,38.431373%);fill-opacity:1;" d="M 866 1231 L 946 1231 L 946 1272 L 866 1272 Z M 866 1231 "/>
-<g style="fill:rgb(66.27451%,63.137255%,88.235294%);fill-opacity:1;">
-  <use xlink:href="#glyph1-36" x="866" y="1263"/>
-  <use xlink:href="#glyph1-10" x="882" y="1263"/>
-  <use xlink:href="#glyph1-24" x="898" y="1263"/>
-  <use xlink:href="#glyph1-5" x="914" y="1263"/>
-  <use xlink:href="#glyph1-25" x="930" y="1263"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(38.431373%,38.431373%,38.431373%);fill-opacity:1;" d="M 946 1231 L 962 1231 L 962 1272 L 946 1272 Z M 946 1231 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 962 1231 L 1730 1231 L 1730 1272 L 962 1272 Z M 962 1231 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1730 1231 L 1746 1231 L 1746 1272 L 1730 1272 Z M 1730 1231 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1746 1231 L 1890 1231 L 1890 1272 L 1746 1272 Z M 1746 1231 "/>
-<g style="fill:rgb(87.45098%,87.45098%,87.45098%);fill-opacity:1;">
-  <use xlink:href="#glyph0-55" x="1746" y="1263"/>
-  <use xlink:href="#glyph0-6" x="1762" y="1263"/>
-  <use xlink:href="#glyph0-10" x="1778" y="1263"/>
-  <use xlink:href="#glyph0-10" x="1794" y="1263"/>
-  <use xlink:href="#glyph0-4" x="1810" y="1263"/>
-  <use xlink:href="#glyph0-14" x="1826" y="1263"/>
-  <use xlink:href="#glyph0-57" x="1842" y="1263"/>
-  <use xlink:href="#glyph0-16" x="1858" y="1263"/>
-  <use xlink:href="#glyph0-1" x="1874" y="1263"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1890 1231 L 1922 1231 L 1922 1272 L 1890 1272 Z M 1890 1231 "/>
-<g style="fill:rgb(66.27451%,63.137255%,88.235294%);fill-opacity:1;">
-  <use xlink:href="#glyph1-13" x="1890" y="1263"/>
-  <use xlink:href="#glyph1-37" x="1906" y="1263"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1922 1231 L 1938 1231 L 1938 1272 L 1922 1272 Z M 1922 1231 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1938 1231 L 1954 1231 L 1954 1272 L 1938 1272 Z M 1938 1231 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1954 1231 L 1970 1231 L 1970 1272 L 1954 1272 Z M 1954 1231 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 1970 1231 L 2098 1231 L 2098 1272 L 1970 1272 Z M 1970 1231 "/>
-<g style="fill:rgb(87.45098%,87.45098%,87.45098%);fill-opacity:1;">
-  <use xlink:href="#glyph0-17" x="1970" y="1263"/>
-  <use xlink:href="#glyph0-4" x="1986" y="1263"/>
-  <use xlink:href="#glyph0-16" x="2002" y="1263"/>
-  <use xlink:href="#glyph0-10" x="2018" y="1263"/>
-  <use xlink:href="#glyph0-15" x="2034" y="1263"/>
-  <use xlink:href="#glyph0-43" x="2050" y="1263"/>
-  <use xlink:href="#glyph0-60" x="2066" y="1263"/>
-  <use xlink:href="#glyph0-1" x="2082" y="1263"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 2098 1231 L 2130 1231 L 2130 1272 L 2098 1272 Z M 2098 1231 "/>
-<g style="fill:rgb(66.27451%,63.137255%,88.235294%);fill-opacity:1;">
-  <use xlink:href="#glyph1-13" x="2098" y="1263"/>
-  <use xlink:href="#glyph1-38" x="2114" y="1263"/>
-</g>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(32.156863%,32.156863%,32.156863%);fill-opacity:1;" d="M 2130 1231 L 2146 1231 L 2146 1272 L 2130 1272 Z M 2130 1231 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,14.117647%,16.862745%);fill-opacity:1;" d="M 2146 1231 L 2162 1231 L 2162 1272 L 2146 1272 Z M 2146 1231 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 1231 L 18 1231 L 18 1272 L 1 1272 Z M 1 1231 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(38.039216%,68.627451%,93.72549%);fill-opacity:1;" d="M 82 1067 L 84 1067 L 84 1108 L 82 1108 Z M 82 1067 "/>
-<path style=" stroke:none;fill-rule:nonzero;fill:rgb(15.686275%,17.254902%,20.392157%);fill-opacity:1;" d="M 1 1272 L 18 1272 L 18 1313 L 1 1313 Z M 1 1272 "/>
-</g>
-</svg>
diff --git a/pw_console/internals.rst b/pw_console/internals.rst
deleted file mode 100644
index c204a1e..0000000
--- a/pw_console/internals.rst
+++ /dev/null
@@ -1,78 +0,0 @@
-.. _module-pw_console-internals:
-
-Internal Design
-===============
-
-Threads and Event Loops
------------------------
-
-In `ptpython`_ and `IPython`_ all user repl code is run in the foreground. This
-allows interrupts like ``Ctrl-C`` and functions like ``print()`` and
-``time.sleep()`` to work as expected. Pigweed's Console doesn't use this
-approach as it would hide or freeze the `prompt_toolkit`_ user interface while
-running repl code.
-
-To get around this issue all user repl code is run in a dedicated thread with
-stdout and stderr patched to capture output. This lets the user interface stay
-responsive and new log messages to continue to be displayed.
-
-Here's a diagram showing how ``pw_console`` threads and `asyncio`_ tasks are
-organized.
-
-.. mermaid::
-
-   flowchart LR
-       classDef eventLoop fill:#e3f2fd,stroke:#90caf9,stroke-width:1px;
-       classDef thread fill:#fffde7,stroke:#ffeb3b,stroke-width:1px;
-       classDef plugin fill:#fce4ec,stroke:#f06292,stroke-width:1px;
-       classDef builtinFeature fill:#e0f2f1,stroke:#4db6ac,stroke-width:1px;
-
-       %% Subgraphs are drawn in reverse order.
-
-       subgraph pluginThread [Plugin Thread 1]
-           subgraph pluginLoop [Plugin Event Loop 1]
-               toolbarFunc-->|"Refresh<br/>UI Tokens"| toolbarFunc
-               toolbarFunc[Toolbar Update Function]
-           end
-           class pluginLoop eventLoop;
-       end
-       class pluginThread thread;
-
-       subgraph pluginThread2 [Plugin Thread 2]
-           subgraph pluginLoop2 [Plugin Event Loop 2]
-               paneFunc-->|"Refresh<br/>UI Tokens"| paneFunc
-               paneFunc[Pane Update Function]
-           end
-           class pluginLoop2 eventLoop;
-       end
-       class pluginThread2 thread;
-
-       subgraph replThread [Repl Thread]
-           subgraph replLoop [Repl Event Loop]
-               Task1 -->|Finished| Task2 -->|Cancel with Ctrl-C| Task3
-           end
-           class replLoop eventLoop;
-       end
-       class replThread thread;
-
-       subgraph main [Main Thread]
-           subgraph mainLoop [User Interface Event Loop]
-               log[[Log Pane]]
-               repl[[Python Repl]]
-               pluginToolbar([User Toolbar Plugin])
-               pluginPane([User Pane Plugin])
-               class log,repl builtinFeature;
-               class pluginToolbar,pluginPane plugin;
-           end
-           class mainLoop eventLoop;
-       end
-       class main thread;
-
-       repl-.->|Run Code| replThread
-       pluginToolbar-.->|Register Plugin| pluginThread
-       pluginPane-.->|Register Plugin| pluginThread2
-
-.. _IPython: https://ipython.readthedocs.io/
-.. _prompt_toolkit: https://python-prompt-toolkit.readthedocs.io/
-.. _asyncio: https://docs.python.org/3/library/asyncio.html
-.. _ptpython: https://github.com/prompt-toolkit/ptpython/
diff --git a/pw_console/plugins.rst b/pw_console/plugins.rst
deleted file mode 100644
index b1ad999..0000000
--- a/pw_console/plugins.rst
+++ /dev/null
@@ -1,146 +0,0 @@
-.. _module-pw_console-plugins:
-
-============
-Plugin Guide
-============
-Pigweed Console supports extending the user interface with custom widgets. For
-example: Toolbars that display device information and provide buttons for
-interacting with the device.
-
----------------
-Writing Plugins
----------------
-Creating new plugins has a few high level steps:
-
-1. Create a new Python class inheriting from either `WindowPane`_ or
-   `WindowPaneToolbar`_.
-
-   - Optionally inherit from The ``PluginMixin`` class as well for running
-     background tasks.
-
-2. Enable the plugin before pw_console startup by calling ``add_window_plugin``,
-   ``add_top_toolbar`` or ``add_bottom_toolbar``. See the
-   :ref:`module-pw_console-embedding-plugins` section of the
-   :ref:`module-pw_console-embedding` for an example.
-
-3. Run the console and enjoy!
-
-   - Debugging Plugin behavior can be done by logging to a dedicated Python
-     logger and viewing in-app. See `Debugging Plugin Behavior`_ below.
-
-Background Tasks
-================
-Plugins may need to have long running background tasks which could block or slow
-down the Pigweed Console user interface. For those situations use the
-``PluginMixin`` class. Plugins can inherit from this and setup the callback that
-should be executed in the background.
-
-.. autoclass:: pw_console.plugin_mixin.PluginMixin
-    :members:
-    :show-inheritance:
-
-Debugging Plugin Behavior
-=========================
-If your plugin uses background threads for updating it can be difficult to see
-errors. Often, nothing will appear to be happening and exceptions may not be
-visible. When using ``PluginMixin`` you can specify a name for a Python logger
-to use with the ``plugin_logger_name`` keyword argument.
-
-.. code-block:: python
-
-   class AwesomeToolbar(WindowPaneToolbar, PluginMixin):
-
-       def __init__(self, *args, **kwargs):
-           super().__init__(*args, **kwargs)
-           self.update_count = 0
-
-           self.plugin_init(
-               plugin_callback=self._background_task,
-               plugin_callback_frequency=1.0,
-               plugin_logger_name='my_awesome_plugin',
-           )
-
-       def _background_task(self) -> bool:
-           self.update_count += 1
-           self.plugin_logger.debug('background_task_update_count: %s',
-                                    self.update_count)
-           return True
-
-This will let you open up a new log window while the console is running to see
-what the plugin is doing. Open up the logger name provided above by clicking in
-the main menu: :guilabel:`File > Open Logger > my_awesome_plugin`.
-
---------------
-Sample Plugins
---------------
-Pigweed Console will provide a few sample plugins to serve as templates for
-creating your own plugins. These are a work in progress at the moment and not
-available at this time.
-
-Calculator
-==========
-This plugin is similar to the full-screen `calculator.py example`_ provided in
-prompt_toolkit. It's a full window that can be moved around the user interface
-like other Pigweed Console window panes. An input prompt is displayed on the
-bottom of the window where the user can type in some math equation. When the
-enter key is pressed the input is processed and the result shown in the top half
-of the window.
-
-Both input and output fields are prompt_toolkit `TextArea`_ objects which can
-have their own options like syntax highlighting.
-
-.. figure:: images/calculator_plugin.png
-  :alt: Screenshot of the CalcPane plugin showing some math calculations.
-
-  Screenshot of the ``CalcPane`` plugin showing some math calculations.
-
-The code is heavily commented and describes what each line is doing. See
-the :ref:`calc_pane_code` for the full source.
-
-Clock
-=====
-The ClockPane is another WindowPane based plugin that displays a clock and some
-formatted text examples. It inherits from both WindowPane and PluginMixin.
-
-.. figure:: images/clock_plugin1.png
-  :alt: ClockPane plugin screenshot showing the clock text.
-
-  ``ClockPane`` plugin screenshot showing the clock text.
-
-This plugin makes use of PluginMixin to run a task a background thread that
-triggers UI re-draws. There are also two toolbar buttons to toggle view mode
-(between the clock and some sample text) and line wrapping. pressing the
-:kbd:`v` key or mouse clicking on the :guilabel:`View Mode` button will toggle
-the view to show some formatted text samples:
-
-.. figure:: images/clock_plugin2.png
-  :alt: ClockPane plugin screenshot showing formatted text examples.
-
-  ``ClockPane`` plugin screenshot showing formatted text examples.
-
-Like the CalcPane example the code is heavily commented to guide plugin authors
-through developmenp. See the :ref:`clock_pane_code` below for the full source.
-
---------
-Appendix
---------
-.. _calc_pane_code:
-
-Code Listing: ``calc_pane.py``
-==============================
-.. literalinclude:: ./py/pw_console/plugins/calc_pane.py
-   :language: python
-   :linenos:
-
-.. _clock_pane_code:
-
-Code Listing: ``clock_pane.py``
-===============================
-.. literalinclude:: ./py/pw_console/plugins/clock_pane.py
-   :language: python
-   :linenos:
-
-.. _WindowPane: https://cs.opensource.google/pigweed/pigweed/+/main:pw_console/py/pw_console/widgets/window_pane.py
-.. _WindowPaneToolbar: https://cs.opensource.google/pigweed/pigweed/+/main:pw_console/py/pw_console/widgets/window_pane_toolbar.py
-.. _calculator.py example: https://github.com/prompt-toolkit/python-prompt-toolkit/blob/3.0.23/examples/full-screen/calculator.py
-.. _TextArea: https://python-prompt-toolkit.readthedocs.io/en/latest/pages/reference.html#prompt_toolkit.widgets.TextArea
diff --git a/pw_console/py/BUILD.gn b/pw_console/py/BUILD.gn
deleted file mode 100644
index b7164c9..0000000
--- a/pw_console/py/BUILD.gn
+++ /dev/null
@@ -1,100 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/python.gni")
-
-pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
-  sources = [
-    "pw_console/__init__.py",
-    "pw_console/__main__.py",
-    "pw_console/command_runner.py",
-    "pw_console/console_app.py",
-    "pw_console/console_prefs.py",
-    "pw_console/embed.py",
-    "pw_console/filter_toolbar.py",
-    "pw_console/get_pw_console_app.py",
-    "pw_console/help_window.py",
-    "pw_console/key_bindings.py",
-    "pw_console/log_filter.py",
-    "pw_console/log_line.py",
-    "pw_console/log_pane.py",
-    "pw_console/log_pane_saveas_dialog.py",
-    "pw_console/log_pane_selection_dialog.py",
-    "pw_console/log_pane_toolbars.py",
-    "pw_console/log_screen.py",
-    "pw_console/log_store.py",
-    "pw_console/log_view.py",
-    "pw_console/mouse.py",
-    "pw_console/pigweed_code_style.py",
-    "pw_console/plugin_mixin.py",
-    "pw_console/plugins/__init__.py",
-    "pw_console/plugins/bandwidth_toolbar.py",
-    "pw_console/plugins/calc_pane.py",
-    "pw_console/plugins/clock_pane.py",
-    "pw_console/progress_bar/__init__.py",
-    "pw_console/progress_bar/progress_bar_impl.py",
-    "pw_console/progress_bar/progress_bar_state.py",
-    "pw_console/progress_bar/progress_bar_task_counter.py",
-    "pw_console/pw_ptpython_repl.py",
-    "pw_console/pyserial_wrapper.py",
-    "pw_console/python_logging.py",
-    "pw_console/quit_dialog.py",
-    "pw_console/repl_pane.py",
-    "pw_console/search_toolbar.py",
-    "pw_console/style.py",
-    "pw_console/text_formatting.py",
-    "pw_console/widgets/__init__.py",
-    "pw_console/widgets/border.py",
-    "pw_console/widgets/checkbox.py",
-    "pw_console/widgets/event_count_history.py",
-    "pw_console/widgets/mouse_handlers.py",
-    "pw_console/widgets/table.py",
-    "pw_console/widgets/window_pane.py",
-    "pw_console/widgets/window_pane_toolbar.py",
-    "pw_console/window_list.py",
-    "pw_console/window_manager.py",
-    "pw_console/yaml_config_loader_mixin.py",
-  ]
-  tests = [
-    "command_runner_test.py",
-    "console_app_test.py",
-    "console_prefs_test.py",
-    "help_window_test.py",
-    "log_filter_test.py",
-    "log_store_test.py",
-    "log_view_test.py",
-    "repl_pane_test.py",
-    "table_test.py",
-    "text_formatting_test.py",
-    "window_manager_test.py",
-  ]
-  python_deps = [
-    "$dir_pw_cli/py",
-    "$dir_pw_log_tokenized/py",
-    "$dir_pw_tokenizer/py",
-  ]
-  inputs = [
-    "pw_console/templates/keybind_list.jinja",
-    "pw_console/templates/repl_output.jinja",
-  ]
-
-  pylintrc = "$dir_pigweed/.pylintrc"
-}
diff --git a/pw_console/py/command_runner_test.py b/pw_console/py/command_runner_test.py
deleted file mode 100644
index 45362fe..0000000
--- a/pw_console/py/command_runner_test.py
+++ /dev/null
@@ -1,258 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests for pw_console.command_runner dialog."""
-
-import logging
-import re
-import unittest
-from typing import Callable, List, Tuple
-
-from unittest.mock import MagicMock
-
-from prompt_toolkit.application import create_app_session
-from prompt_toolkit.output import ColorDepth
-# inclusive-language: ignore
-from prompt_toolkit.output import DummyOutput as FakeOutput
-
-from pw_console.console_app import ConsoleApp
-from pw_console.console_prefs import ConsolePrefs
-from pw_console.text_formatting import (
-    flatten_formatted_text_tuples,
-    join_adjacent_style_tuples,
-)
-from window_manager_test import target_list_and_pane, window_pane_titles
-
-
-def _create_console_app(log_pane_count=2):
-    console_app = ConsoleApp(color_depth=ColorDepth.DEPTH_8_BIT,
-                             prefs=ConsolePrefs(project_file=False,
-                                                project_user_file=False,
-                                                user_file=False))
-    console_app.prefs.reset_config()
-
-    # Setup log panes
-    loggers = {}
-    for i in range(log_pane_count):
-        loggers['LogPane-{}'.format(i)] = [
-            logging.getLogger('test_log{}'.format(i))
-        ]
-    for window_title, logger_instances in loggers.items():
-        console_app.add_log_handler(window_title, logger_instances)
-
-    return console_app
-
-
-class TestCommandRunner(unittest.TestCase):
-    """Tests for CommandRunner."""
-    def setUp(self):
-        self.maxDiff = None  # pylint: disable=invalid-name
-
-    def test_flatten_menu_items(self) -> None:
-        with create_app_session(output=FakeOutput()):
-            console_app = _create_console_app(log_pane_count=2)
-            flattened_menu_items = [
-                text for text, handler in
-                console_app.command_runner.load_menu_items()
-            ]
-
-            # Check some common menu items exist.
-            self.assertIn('[File] > Open Logger', flattened_menu_items)
-            self.assertIn('[File] > Themes > UI Themes > High Contrast',
-                          flattened_menu_items)
-            self.assertIn('[Help] > User Guide', flattened_menu_items)
-            self.assertIn('[Help] > Keyboard Shortcuts', flattened_menu_items)
-            # Check for log windows
-            self.assertRegex(
-                '\n'.join(flattened_menu_items),
-                re.compile(r'^\[Windows\] > .* LogPane-[0-9]+ > .*$',
-                           re.MULTILINE),
-            )
-
-    def test_filter_and_highlight_matches(self) -> None:
-        """Check filtering matches and highlighting works correctly."""
-        with create_app_session(output=FakeOutput()):
-            console_app = _create_console_app(log_pane_count=2)
-            command_runner = console_app.command_runner
-
-            command_runner.filter_completions = MagicMock(
-                wraps=command_runner.filter_completions)
-            command_runner.width = 20
-
-            # Define custom completion items
-            def empty_handler() -> None:
-                return None
-
-            def get_completions() -> List[Tuple[str, Callable]]:
-                return [
-                    ('[File] > Open Logger', empty_handler),
-                    ('[Windows] > 1: Host Logs > Show/Hide', empty_handler),
-                    ('[Windows] > 2: Device Logs > Show/Hide', empty_handler),
-                    ('[Help] > User Guide', empty_handler),
-                ]
-
-            command_runner.filter_completions.assert_not_called()
-            command_runner.set_completions(window_title='Test Completions',
-                                           load_completions=get_completions)
-            command_runner.filter_completions.assert_called_once()
-            command_runner.filter_completions.reset_mock()
-
-            # Input field should be empty
-            self.assertEqual(command_runner.input_field.buffer.text, '')
-            # Flatten resulting formatted text
-            result_items = join_adjacent_style_tuples(
-                flatten_formatted_text_tuples(
-                    command_runner.completion_fragments))
-
-            # index 0: the selected line
-            # index 1: the rest of the completions with line breaks
-            self.assertEqual(len(result_items), 2)
-            first_item_style = result_items[0][0]
-            first_item_text = result_items[0][1]
-            second_item_text = result_items[1][1]
-            # Check expected number of lines are present
-            self.assertEqual(len(first_item_text.splitlines()), 1)
-            self.assertEqual(len(second_item_text.splitlines()), 3)
-            # First line is highlighted as a selected item
-            self.assertEqual(first_item_style,
-                             'class:command-runner-selected-item')
-            self.assertIn('[File] > Open Logger', first_item_text)
-
-            # Type: file open
-            command_runner.input_field.buffer.text = 'file open'
-            self.assertEqual(command_runner.input_field.buffer.text,
-                             'file open')
-            # Run the filter
-            command_runner.filter_completions()
-            # Flatten resulting formatted text
-            result_items = join_adjacent_style_tuples(
-                flatten_formatted_text_tuples(
-                    command_runner.completion_fragments))
-            # Check file and open are highlighted
-            self.assertEqual(
-                result_items[:4],
-                [
-                    ('class:command-runner-selected-item', '['),
-                    ('class:command-runner-selected-item '
-                     'class:command-runner-fuzzy-highlight-0 ', 'File'),
-                    ('class:command-runner-selected-item', '] > '),
-                    ('class:command-runner-selected-item '
-                     'class:command-runner-fuzzy-highlight-1 ', 'Open'),
-                ],
-            )
-
-            # Type: open file
-            command_runner.input_field.buffer.text = 'open file'
-            # Run the filter
-            command_runner.filter_completions()
-            result_items = join_adjacent_style_tuples(
-                flatten_formatted_text_tuples(
-                    command_runner.completion_fragments))
-            # Check file and open are highlighted, the fuzzy-highlight class
-            # should be swapped.
-            self.assertEqual(
-                result_items[:4],
-                [
-                    ('class:command-runner-selected-item', '['),
-                    ('class:command-runner-selected-item '
-                     'class:command-runner-fuzzy-highlight-1 ', 'File'),
-                    ('class:command-runner-selected-item', '] > '),
-                    ('class:command-runner-selected-item '
-                     'class:command-runner-fuzzy-highlight-0 ', 'Open'),
-                ],
-            )
-
-            # Clear input
-            command_runner._reset_selected_item()  # pylint: disable=protected-access
-            command_runner.filter_completions()
-            result_items = join_adjacent_style_tuples(
-                flatten_formatted_text_tuples(
-                    command_runner.completion_fragments))
-            self.assertEqual(len(first_item_text.splitlines()), 1)
-            self.assertEqual(len(second_item_text.splitlines()), 3)
-
-            # Press down (select the next item)
-            command_runner._next_item()  # pylint: disable=protected-access
-            # Filter and check results
-            command_runner.filter_completions()
-            result_items = join_adjacent_style_tuples(
-                flatten_formatted_text_tuples(
-                    command_runner.completion_fragments))
-            self.assertEqual(len(result_items), 3)
-            # First line - not selected
-            self.assertEqual(result_items[0], ('', '[File] > Open Logger\n'))
-            # Second line - is selected
-            self.assertEqual(result_items[1],
-                             ('class:command-runner-selected-item',
-                              '[Windows] > 1: Host Logs > Show/Hide\n'))
-            # Third and fourth lines separated by \n - not selected
-            self.assertEqual(result_items[2],
-                             ('', '[Windows] > 2: Device Logs > Show/Hide\n'
-                              '[Help] > User Guide'))
-
-    def test_run_action(self) -> None:
-        """Check running an action works correctly."""
-        with create_app_session(output=FakeOutput()):
-            console_app = _create_console_app(log_pane_count=2)
-            command_runner = console_app.command_runner
-            self.assertEqual(
-                window_pane_titles(console_app.window_manager),
-                [
-                    # Split 1
-                    [
-                        'LogPane-1 - test_log1',
-                        'LogPane-0 - test_log0',
-                        'Python Repl - ',
-                    ],
-                ],
-            )
-            command_runner.open_dialog()
-            # Set LogPane-1 as the focused window pane
-            target_list_and_pane(console_app.window_manager, 0, 0)
-
-            command_runner.input_field.buffer.text = 'move right'
-
-            # pylint: disable=protected-access
-            command_runner._make_regexes = MagicMock(
-                wraps=command_runner._make_regexes)
-            # pylint: enable=protected-access
-            command_runner.filter_completions()
-            # Filter should only be re-run if input text changed
-            command_runner.filter_completions()
-            command_runner._make_regexes.assert_called_once()  # pylint: disable=protected-access
-
-            self.assertIn('[View] > Move Window Right',
-                          command_runner.selected_item_text)
-            # Run the Move Window Right action
-            command_runner._run_selected_item()  # pylint: disable=protected-access
-            # Dialog should be closed
-            self.assertFalse(command_runner.show_dialog)
-            # LogPane-1 should be moved to the right in it's own split
-            self.assertEqual(
-                window_pane_titles(console_app.window_manager),
-                [
-                    # Split 1
-                    [
-                        'LogPane-0 - test_log0',
-                        'Python Repl - ',
-                    ],
-                    # Split 2
-                    [
-                        'LogPane-1 - test_log1',
-                    ],
-                ],
-            )
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_console/py/console_app_test.py b/pw_console/py/console_app_test.py
deleted file mode 100644
index 4dec65f..0000000
--- a/pw_console/py/console_app_test.py
+++ /dev/null
@@ -1,72 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests for pw_console.console_app"""
-
-import logging
-import unittest
-
-from prompt_toolkit.application import create_app_session
-from prompt_toolkit.output import ColorDepth
-# inclusive-language: ignore
-from prompt_toolkit.output import DummyOutput as FakeOutput
-
-from pw_console.console_app import ConsoleApp
-from pw_console.console_prefs import ConsolePrefs
-
-
-class TestConsoleApp(unittest.TestCase):
-    """Tests for ConsoleApp."""
-    def test_instantiate(self) -> None:
-        """Test init."""
-        with create_app_session(output=FakeOutput()):
-            console_app = ConsoleApp(color_depth=ColorDepth.DEPTH_8_BIT,
-                                     prefs=ConsolePrefs(
-                                         project_file=False,
-                                         project_user_file=False,
-                                         user_file=False))
-            self.assertIsNotNone(console_app)
-
-    def test_multiple_loggers_in_one_pane(self) -> None:
-        """Test window resizing."""
-        # pylint: disable=protected-access
-        with create_app_session(output=FakeOutput()):
-            console_app = ConsoleApp(color_depth=ColorDepth.DEPTH_8_BIT,
-                                     prefs=ConsolePrefs(
-                                         project_file=False,
-                                         project_user_file=False,
-                                         user_file=False))
-
-            loggers = {
-                'Logs': [
-                    logging.getLogger('test_log1'),
-                    logging.getLogger('test_log2'),
-                    logging.getLogger('test_log3'),
-                ]
-            }
-            for window_title, logger_instances in loggers.items():
-                console_app.add_log_handler(window_title, logger_instances)
-
-            # Two panes, one for the loggers and one for the repl.
-            window_list = console_app.window_manager.first_window_list()
-            self.assertEqual(len(window_list.active_panes), 2)
-
-            self.assertEqual(window_list.active_panes[0].pane_title(), 'Logs')
-            self.assertEqual(window_list.active_panes[0]._pane_subtitle,
-                             'test_log1, test_log2, test_log3')
-            self.assertEqual(window_list.active_panes[0].pane_subtitle(),
-                             'test_log1 + 3 more')
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_console/py/console_prefs_test.py b/pw_console/py/console_prefs_test.py
deleted file mode 100644
index 958c00f..0000000
--- a/pw_console/py/console_prefs_test.py
+++ /dev/null
@@ -1,174 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests for pw_console.console_app"""
-
-from datetime import datetime
-from pathlib import Path
-import tempfile
-import unittest
-
-import yaml
-
-# pylint: disable=protected-access
-from pw_console.console_prefs import (
-    ConsolePrefs,
-    _DEFAULT_CONFIG,
-)
-
-
-def _create_tempfile(content: str) -> Path:
-    # Grab the current system timestamp as a string.
-    isotime = datetime.now().isoformat(sep='_', timespec='seconds')
-    isotime = isotime.replace(':', '')
-
-    with tempfile.NamedTemporaryFile(prefix=f'{__package__}_{isotime}_',
-                                     delete=False) as output_file:
-        file_path = Path(output_file.name)
-        output_file.write(content.encode('UTF-8'))
-    return file_path
-
-
-class TestConsolePrefs(unittest.TestCase):
-    """Tests for ConsolePrefs."""
-    def setUp(self):
-        self.maxDiff = None  # pylint: disable=invalid-name
-
-    def test_load_no_existing_files(self) -> None:
-        prefs = ConsolePrefs(project_file=False,
-                             project_user_file=False,
-                             user_file=False)
-        self.assertEqual(_DEFAULT_CONFIG, prefs._config)
-        self.assertTrue(str(prefs.repl_history).endswith('pw_console_history'))
-        self.assertTrue(
-            str(prefs.search_history).endswith('pw_console_search'))
-
-    def test_load_empty_file(self) -> None:
-        # Create an empty file
-        project_config_file = _create_tempfile('')
-        try:
-            prefs = ConsolePrefs(project_file=project_config_file,
-                                 project_user_file=False,
-                                 user_file=False)
-            result_settings = {
-                k: v
-                for k, v in prefs._config.items()
-                if k in _DEFAULT_CONFIG.keys()
-            }
-            other_settings = {
-                k: v
-                for k, v in prefs._config.items()
-                if k not in _DEFAULT_CONFIG.keys()
-            }
-            # Check that only the default config was loaded.
-            self.assertEqual(_DEFAULT_CONFIG, result_settings)
-            self.assertEqual(0, len(other_settings))
-        finally:
-            project_config_file.unlink()
-
-    def test_load_project_file(self) -> None:
-        project_config = {
-            'pw_console': {
-                'ui_theme': 'light',
-                'code_theme': 'cool-code',
-                'swap_light_and_dark': True,
-            },
-        }
-        project_config_file = _create_tempfile(yaml.dump(project_config))
-        try:
-            prefs = ConsolePrefs(project_file=project_config_file,
-                                 project_user_file=False,
-                                 user_file=False)
-            result_settings = {
-                k: v
-                for k, v in prefs._config.items()
-                if k in project_config['pw_console'].keys()
-            }
-            other_settings = {
-                k: v
-                for k, v in prefs._config.items()
-                if k not in project_config['pw_console'].keys()
-            }
-            self.assertEqual(project_config['pw_console'], result_settings)
-            self.assertNotEqual(0, len(other_settings))
-        finally:
-            project_config_file.unlink()
-
-    def test_load_project_and_user_file(self) -> None:
-        """Test user settings override project settings."""
-        project_config = {
-            'pw_console': {
-                'ui_theme': 'light',
-                'code_theme': 'cool-code',
-                'swap_light_and_dark': True,
-                'repl_history': '~/project_history',
-                'search_history': '~/project_search',
-            },
-        }
-        project_config_file = _create_tempfile(yaml.dump(project_config))
-
-        project_user_config = {
-            'pw_console': {
-                'ui_theme': 'nord',
-                'repl_history': '~/project_user_history',
-                'search_history': '~/project_user_search',
-            },
-        }
-        project_user_config_file = _create_tempfile(
-            yaml.dump(project_user_config))
-
-        user_config = {
-            'pw_console': {
-                'ui_theme': 'dark',
-                'search_history': '~/user_search',
-            },
-        }
-        user_config_file = _create_tempfile(yaml.dump(user_config))
-        try:
-            prefs = ConsolePrefs(project_file=project_config_file,
-                                 project_user_file=project_user_config_file,
-                                 user_file=user_config_file)
-            # Set by the project
-            self.assertEqual(project_config['pw_console']['code_theme'],
-                             prefs.code_theme)
-            self.assertEqual(
-                project_config['pw_console']['swap_light_and_dark'],
-                prefs.swap_light_and_dark)
-
-            # Project user setting, result should not be project only setting.
-            project_history = project_config['pw_console']['repl_history']
-            assert isinstance(project_history, str)
-            self.assertNotEqual(
-                Path(project_history).expanduser(), prefs.repl_history)
-
-            history = project_user_config['pw_console']['repl_history']
-            assert isinstance(history, str)
-            self.assertEqual(Path(history).expanduser(), prefs.repl_history)
-
-            # User config overrides project and project_user
-            self.assertEqual(user_config['pw_console']['ui_theme'],
-                             prefs.ui_theme)
-            self.assertEqual(
-                Path(user_config['pw_console']['search_history']).expanduser(),
-                prefs.search_history)
-            # ui_theme should not be the project_user file setting
-            project_user_theme = project_user_config['pw_console']['ui_theme']
-            self.assertNotEqual(project_user_theme, prefs.ui_theme)
-        finally:
-            project_config_file.unlink()
-            project_user_config_file.unlink()
-            user_config_file.unlink()
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_console/py/help_window_test.py b/pw_console/py/help_window_test.py
deleted file mode 100644
index 2f25cac..0000000
--- a/pw_console/py/help_window_test.py
+++ /dev/null
@@ -1,159 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests for pw_console.console_app"""
-
-import inspect
-import logging
-import unittest
-from unittest.mock import MagicMock
-
-from jinja2 import Environment, PackageLoader, make_logging_undefined
-from prompt_toolkit.key_binding import KeyBindings
-
-from pw_console.help_window import HelpWindow
-
-_jinja_env = Environment(
-    loader=PackageLoader('pw_console'),
-    undefined=make_logging_undefined(logger=logging.getLogger('pw_console')),
-    trim_blocks=True,
-    lstrip_blocks=True,
-)
-
-
-def _create_app_mock():
-    template = _jinja_env.get_template('keybind_list.jinja')
-    mock_app = MagicMock()
-    mock_app.get_template = MagicMock(return_value=template)
-    return mock_app
-
-
-class TestHelpWindow(unittest.TestCase):
-    """Tests for HelpWindow text and keybind lists."""
-    def setUp(self):
-        self.maxDiff = None  # pylint: disable=invalid-name
-
-    def test_instantiate(self) -> None:
-        app = _create_app_mock()
-        help_window = HelpWindow(app)
-        self.assertIsNotNone(help_window)
-
-    # pylint: disable=unused-variable,unused-argument
-    def test_add_keybind_help_text(self) -> None:
-        key_bindings = KeyBindings()
-
-        @key_bindings.add('f1')
-        def show_help(event):
-            """Toggle help window."""
-
-        @key_bindings.add('c-w')
-        @key_bindings.add('c-q')
-        def exit_(event):
-            """Quit the application."""
-
-        app = _create_app_mock()
-
-        help_window = HelpWindow(app)
-        help_window.add_keybind_help_text('Global', key_bindings)
-
-        self.assertEqual(
-            help_window.help_text_sections,
-            {
-                'Global': {
-                    'Quit the application.': ['Ctrl-Q', 'Ctrl-W'],
-                    'Toggle help window.': ['F1'],
-                }
-            },
-        )
-
-    def test_generate_help_text(self) -> None:
-        """Test keybind list template generation."""
-        global_bindings = KeyBindings()
-
-        @global_bindings.add('f1')
-        def show_help(event):
-            """Toggle help window."""
-
-        @global_bindings.add('c-w')
-        @global_bindings.add('c-q')
-        def exit_(event):
-            """Quit the application."""
-
-        focus_bindings = KeyBindings()
-
-        @focus_bindings.add('s-tab')
-        @focus_bindings.add('c-right')
-        @focus_bindings.add('c-down')
-        def app_focus_next(event):
-            """Move focus to the next widget."""
-
-        @focus_bindings.add('c-left')
-        @focus_bindings.add('c-up')
-        def app_focus_previous(event):
-            """Move focus to the previous widget."""
-
-        app = _create_app_mock()
-
-        help_window = HelpWindow(
-            app,
-            preamble='Pigweed CLI v0.1',
-            additional_help_text=inspect.cleandoc("""
-                Welcome to the Pigweed Console!
-                Please enjoy this extra help text.
-            """),
-        )
-        help_window.add_keybind_help_text('Global', global_bindings)
-        help_window.add_keybind_help_text('Focus', focus_bindings)
-        help_window.generate_help_text()
-
-        self.assertIn(
-            inspect.cleandoc("""
-            Welcome to the Pigweed Console!
-            Please enjoy this extra help text.
-            """),
-            help_window.help_text,
-        )
-        self.assertIn(
-            inspect.cleandoc("""
-            ==== Global Keys ====
-            """),
-            help_window.help_text,
-        )
-        self.assertIn(
-            inspect.cleandoc("""
-            Toggle help window. -----------------  F1
-            Quit the application. ---------------  Ctrl-Q
-                                                   Ctrl-W
-            """),
-            help_window.help_text,
-        )
-        self.assertIn(
-            inspect.cleandoc("""
-            ==== Focus Keys ====
-            """),
-            help_window.help_text,
-        )
-        self.assertIn(
-            inspect.cleandoc("""
-            Move focus to the next widget. ------  Ctrl-Down
-                                                   Ctrl-Right
-                                                   Shift-Tab
-            Move focus to the previous widget. --  Ctrl-Left
-                                                   Ctrl-Up
-            """),
-            help_window.help_text,
-        )
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_console/py/log_filter_test.py b/pw_console/py/log_filter_test.py
deleted file mode 100644
index d885560..0000000
--- a/pw_console/py/log_filter_test.py
+++ /dev/null
@@ -1,215 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests for pw_console.log_view"""
-
-import logging
-import re
-import unittest
-from parameterized import parameterized  # type: ignore
-
-from prompt_toolkit.document import Document
-from prompt_toolkit.validation import ValidationError
-
-from pw_console.log_line import LogLine
-from pw_console.log_filter import (
-    LogFilter,
-    RegexValidator,
-    SearchMatcher,
-    preprocess_search_regex,
-)
-
-
-class TestLogFilter(unittest.TestCase):
-    """Tests for LogFilter."""
-    def setUp(self):
-        self.maxDiff = None  # pylint: disable=invalid-name
-
-    # pylint: disable=anomalous-backslash-in-string
-    @parameterized.expand([
-        (
-            'raw string',
-            SearchMatcher.STRING,
-            'f(x)',
-            'f\(x\)',
-            re.IGNORECASE,
-        ),
-        (
-            'simple regex',
-            SearchMatcher.REGEX,
-            'f(x)',
-            'f(x)',
-            re.IGNORECASE,
-        ),
-        (
-            'regex with case sensitivity',
-            SearchMatcher.REGEX,
-            'f(X)',
-            'f(X)',
-            re.RegexFlag(0),
-        ),
-        (
-            'regex with error',
-            SearchMatcher.REGEX,
-            'f of (x', # Un-terminated open paren
-            'f of (x',
-            re.IGNORECASE,
-            True,  # fails_validation
-        ),
-        (
-            'simple fuzzy',
-            SearchMatcher.FUZZY,
-            'f x y',
-            '(f)(.*?)(x)(.*?)(y)',
-            re.IGNORECASE,
-        ),
-        (
-            'fuzzy with case sensitivity',
-            SearchMatcher.FUZZY,
-            'f X y',
-            '(f)(.*?)(X)(.*?)(y)',
-            re.RegexFlag(0),
-        ),
-    ]) # yapf: disable
-    def test_preprocess_search_regex(
-        self,
-        _name,
-        input_matcher,
-        input_text,
-        expected_regex,
-        expected_re_flag,
-        should_fail_validation=False,
-    ) -> None:
-        """Test preprocess_search_regex returns the expected regex settings."""
-        result_text, re_flag = preprocess_search_regex(input_text,
-                                                       input_matcher)
-        self.assertEqual(expected_regex, result_text)
-        self.assertEqual(expected_re_flag, re_flag)
-
-        if should_fail_validation:
-            document = Document(text=input_text)
-            with self.assertRaisesRegex(ValidationError,
-                                        r'Regex Error.*at position [0-9]+'):
-                RegexValidator().validate(document)
-
-    def _create_logs(self, log_messages):
-        test_log = logging.getLogger('log_filter.test')
-        with self.assertLogs(test_log, level='DEBUG') as log_context:
-            for log, extra_arg in log_messages:
-                test_log.debug('%s', log, extra=extra_arg)
-
-        return log_context
-
-    @parameterized.expand([
-        (
-            'simple fuzzy',
-            SearchMatcher.FUZZY,
-            'log item',
-            [
-                ('Log some item', {'planet': 'Jupiter'}),
-                ('Log another item', {'planet': 'Earth'}),
-                ('Some exception', {'planet': 'Earth'}),
-            ],
-            [
-                'Log some item',
-                'Log another item',
-            ],
-            None,  # field
-            False,  # invert
-        ),
-        (
-            'simple fuzzy inverted',
-            SearchMatcher.FUZZY,
-            'log item',
-            [
-                ('Log some item', dict()),
-                ('Log another item', dict()),
-                ('Some exception', dict()),
-            ],
-            [
-                'Some exception',
-            ],
-            None,  # field
-            True,  # invert
-        ),
-        (
-            'regex with field',
-            SearchMatcher.REGEX,
-            'earth',
-            [
-                ('Log some item',
-                 dict(extra_metadata_fields={'planet': 'Jupiter'})),
-                ('Log another item',
-                 dict(extra_metadata_fields={'planet': 'Earth'})),
-                ('Some exception',
-                 dict(extra_metadata_fields={'planet': 'Earth'})),
-            ],
-            [
-                'Log another item',
-                'Some exception',
-            ],
-            'planet',  # field
-            False,  # invert
-        ),
-        (
-            'regex with field inverted',
-            SearchMatcher.REGEX,
-            'earth',
-            [
-                ('Log some item',
-                 dict(extra_metadata_fields={'planet': 'Jupiter'})),
-                ('Log another item',
-                 dict(extra_metadata_fields={'planet': 'Earth'})),
-                ('Some exception',
-                 dict(extra_metadata_fields={'planet': 'Earth'})),
-            ],
-            [
-                'Log some item',
-            ],
-            'planet',  # field
-            True,  # invert
-        ),
-    ]) # yapf: disable
-    def test_log_filter_matches(
-        self,
-        _name,
-        input_matcher,
-        input_text,
-        input_lines,
-        expected_matched_lines,
-        field=None,
-        invert=False,
-    ) -> None:
-        """Test log filter matches expected lines."""
-        result_text, re_flag = preprocess_search_regex(input_text,
-                                                       input_matcher)
-        log_filter = LogFilter(
-            regex=re.compile(result_text, re_flag),
-            input_text=input_text,
-            invert=invert,
-            field=field,
-        )
-
-        matched_lines = []
-        logs = self._create_logs(input_lines)
-
-        for record in logs.records:
-            if log_filter.matches(
-                    LogLine(record, record.message, record.message)):
-                matched_lines.append(record.message)
-
-        self.assertEqual(expected_matched_lines, matched_lines)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_console/py/log_store_test.py b/pw_console/py/log_store_test.py
deleted file mode 100644
index 37b6287..0000000
--- a/pw_console/py/log_store_test.py
+++ /dev/null
@@ -1,135 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests for pw_console.log_store"""
-
-import logging
-import unittest
-from unittest.mock import MagicMock
-
-from pw_console.log_store import LogStore
-from pw_console.console_prefs import ConsolePrefs
-
-
-def _create_log_store():
-    log_store = LogStore(prefs=ConsolePrefs(
-        project_file=False, project_user_file=False, user_file=False))
-
-    assert not log_store.table.prefs.show_python_file
-    viewer = MagicMock()
-    viewer.new_logs_arrived = MagicMock()
-    log_store.register_viewer(viewer)
-    return log_store, viewer
-
-
-class TestLogStore(unittest.TestCase):
-    """Tests for LogStore."""
-    def setUp(self):
-        self.maxDiff = None  # pylint: disable=invalid-name
-
-    def test_get_total_count(self) -> None:
-        log_store, viewer = _create_log_store()
-        test_log = logging.getLogger('log_store.test')
-        # Must use the assertLogs context manager and the addHandler call.
-        with self.assertLogs(test_log, level='DEBUG') as log_context:
-            test_log.addHandler(log_store)
-            for i in range(5):
-                test_log.debug('Test log %s', i)
-
-        # Expected log message content
-        self.assertEqual(
-            ['DEBUG:log_store.test:Test log {}'.format(i) for i in range(5)],
-            log_context.output,
-        )
-        # LogStore state checks
-        viewer.new_logs_arrived.assert_called()
-        self.assertEqual(5, log_store.get_total_count())
-        self.assertEqual(4, log_store.get_last_log_index())
-
-        log_store.clear_logs()
-        self.assertEqual(0, log_store.get_total_count())
-
-    def test_channel_counts_and_prefix_width(self) -> None:
-        """Test logger names and prefix width calculations."""
-        log_store, _viewer = _create_log_store()
-
-        # Log some messagse on 3 separate logger instances
-        for i, logger_name in enumerate([
-                'log_store.test',
-                'log_store.dev',
-                'log_store.production',
-        ]):
-            test_log = logging.getLogger(logger_name)
-            with self.assertLogs(test_log, level='DEBUG') as _log_context:
-                test_log.addHandler(log_store)
-                test_log.debug('Test log message')
-                for j in range(i):
-                    test_log.debug('%s', j)
-
-        self.assertEqual(
-            {
-                'log_store.test': 1,
-                'log_store.dev': 2,
-                'log_store.production': 3,
-            },
-            log_store.channel_counts,
-        )
-        self.assertEqual(
-            'log_store.test: 1, log_store.dev: 2, log_store.production: 3',
-            log_store.get_channel_counts(),
-        )
-
-        self.assertRegex(
-            log_store.logs[0].ansi_stripped_log,
-            r'[0-9]{8} [0-9]{2}:[0-9]{2}:[0-9]{2} DEBUG Test log message',
-        )
-        self.assertEqual(
-            {
-                'log_store.test': 0,
-                'log_store.dev': 0,
-                'log_store.production': 0,
-            },
-            log_store.channel_formatted_prefix_widths,
-        )
-
-    def test_render_table_header_with_metadata(self) -> None:
-        log_store, _viewer = _create_log_store()
-        test_log = logging.getLogger('log_store.test')
-
-        # Log table with extra columns
-        with self.assertLogs(test_log, level='DEBUG') as _log_context:
-            test_log.addHandler(log_store)
-            test_log.debug('Test log %s',
-                           extra=dict(extra_metadata_fields={
-                               'planet': 'Jupiter',
-                               'galaxy': 'Milky Way'
-                           }))
-
-        self.assertEqual(
-            [
-                ('bold', 'Time             '),
-                ('', '  '),
-                ('bold', 'Level'),
-                ('', '  '),
-                ('bold', 'Planet '),
-                ('', '  '),
-                ('bold', 'Galaxy   '),
-                ('', '  '),
-                ('bold', 'Message'),
-            ],
-            log_store.render_table_header(),
-        )
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_console/py/log_view_test.py b/pw_console/py/log_view_test.py
deleted file mode 100644
index a21a564..0000000
--- a/pw_console/py/log_view_test.py
+++ /dev/null
@@ -1,593 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests for pw_console.log_view"""
-
-import logging
-import time
-import sys
-import unittest
-from datetime import datetime
-from unittest.mock import MagicMock, patch
-from parameterized import parameterized  # type: ignore
-from prompt_toolkit.data_structures import Point
-
-from pw_console.console_prefs import ConsolePrefs
-from pw_console.log_view import LogView
-from pw_console.log_screen import ScreenLine
-from pw_console.text_formatting import (
-    flatten_formatted_text_tuples,
-    join_adjacent_style_tuples,
-)
-
-_PYTHON_3_8 = sys.version_info >= (
-    3,
-    8,
-)
-
-
-def _create_log_view():
-    log_pane = MagicMock()
-    log_pane.pane_resized = MagicMock(return_value=True)
-    log_pane.current_log_pane_width = 80
-    log_pane.current_log_pane_height = 10
-
-    application = MagicMock()
-    application.prefs = ConsolePrefs(project_file=False,
-                                     project_user_file=False,
-                                     user_file=False)
-    application.prefs.reset_config()
-    log_view = LogView(log_pane, application)
-    return log_view, log_pane
-
-
-class TestLogView(unittest.TestCase):
-    """Tests for LogView."""
-
-    # pylint: disable=invalid-name
-    def setUp(self):
-        self.maxDiff = None
-
-    # pylint: enable=invalid-name
-
-    def _create_log_view_with_logs(self, log_count=100):
-        log_view, log_pane = _create_log_view()
-
-        if log_count > 0:
-            test_log = logging.getLogger('log_view.test')
-            with self.assertLogs(test_log, level='DEBUG') as _log_context:
-                test_log.addHandler(log_view.log_store)
-                for i in range(log_count):
-                    test_log.debug('Test log %s', i)
-
-        return log_view, log_pane
-
-    def test_follow_toggle(self) -> None:
-        log_view, _pane = _create_log_view()
-        self.assertTrue(log_view.follow)
-        log_view.toggle_follow()
-        self.assertFalse(log_view.follow)
-
-    def test_follow_scrolls_to_bottom(self) -> None:
-        log_view, _pane = _create_log_view()
-        log_view.toggle_follow()
-        _fragments = log_view.render_content()
-        self.assertFalse(log_view.follow)
-        self.assertEqual(log_view.get_current_line(), 0)
-
-        test_log = logging.getLogger('log_view.test')
-
-        # Log 5 messagse, current_line should stay at 0
-        with self.assertLogs(test_log, level='DEBUG') as _log_context:
-            test_log.addHandler(log_view.log_store)
-            for i in range(5):
-                test_log.debug('Test log %s', i)
-        _fragments = log_view.render_content()
-
-        self.assertEqual(log_view.get_total_count(), 5)
-        self.assertEqual(log_view.get_current_line(), 0)
-        # Turn follow on
-        log_view.toggle_follow()
-        self.assertTrue(log_view.follow)
-
-        # Log another messagse, current_line should move to the last.
-        with self.assertLogs(test_log, level='DEBUG') as _log_context:
-            test_log.addHandler(log_view.log_store)
-            test_log.debug('Test log')
-        _fragments = log_view.render_content()
-
-        self.assertEqual(log_view.get_total_count(), 6)
-        self.assertEqual(log_view.get_current_line(), 5)
-
-    def test_scrolling(self) -> None:
-        """Test all scrolling methods."""
-        log_view, log_pane = self._create_log_view_with_logs(log_count=100)
-
-        # Page scrolling needs to know the current window height.
-        log_pane.pane_resized = MagicMock(return_value=True)
-        log_pane.current_log_pane_width = 80
-        log_pane.current_log_pane_height = 10
-
-        log_view.render_content()
-
-        # Follow is on by default, current line should be at the end.
-        self.assertEqual(log_view.get_current_line(), 99)
-
-        # Move to the beginning.
-        log_view.scroll_to_top()
-        log_view.render_content()
-        self.assertEqual(log_view.get_current_line(), 0)
-
-        # Should not be able to scroll before the beginning.
-        log_view.scroll_up()
-        log_view.render_content()
-        self.assertEqual(log_view.get_current_line(), 0)
-        log_view.scroll_up_one_page()
-        log_view.render_content()
-        self.assertEqual(log_view.get_current_line(), 0)
-
-        # Single and multi line movement.
-        log_view.scroll_down()
-        log_view.render_content()
-        self.assertEqual(log_view.get_current_line(), 1)
-        log_view.scroll(5)
-        log_view.render_content()
-        self.assertEqual(log_view.get_current_line(), 6)
-        log_view.scroll_up()
-        log_view.render_content()
-        self.assertEqual(log_view.get_current_line(), 5)
-
-        # Page down and up.
-        log_view.scroll_down_one_page()
-        self.assertEqual(log_view.get_current_line(), 15)
-
-        log_view.scroll_up_one_page()
-        self.assertEqual(log_view.get_current_line(), 5)
-
-        # Move to the end.
-        log_view.scroll_to_bottom()
-        log_view.render_content()
-        self.assertEqual(log_view.get_current_line(), 99)
-
-        # Should not be able to scroll beyond the end.
-        log_view.scroll_down()
-        log_view.render_content()
-        self.assertEqual(log_view.get_current_line(), 99)
-        log_view.scroll_down_one_page()
-        log_view.render_content()
-        self.assertEqual(log_view.get_current_line(), 99)
-
-        # Move up a bit to turn off follow
-        self.assertEqual(log_view.log_screen.cursor_position, 9)
-        log_view.scroll(-1)
-        self.assertEqual(log_view.log_screen.cursor_position, 8)
-        log_view.render_content()
-        self.assertEqual(log_view.get_current_line(), 98)
-
-        # Simulate a mouse click to scroll.
-        # Click 1 lines from the top of the window.
-        log_view.scroll_to_position(Point(0, 1))
-        log_view.render_content()
-        self.assertEqual(log_view.get_current_line(), 90)
-
-        # Disable follow mode if mouse click on line.
-        log_view.toggle_follow()
-        log_view.render_content()
-        self.assertTrue(log_view.follow)
-        self.assertEqual(log_view.get_current_line(), 99)
-        log_view.scroll_to_position(Point(0, 5))
-        log_view.render_content()
-        self.assertEqual(log_view.get_current_line(), 95)
-        self.assertFalse(log_view.follow)
-
-    def test_render_content_and_cursor_position(self) -> None:
-        """Test render_content results and get_cursor_position
-
-        get_cursor_position() should return the correct position depending on
-        what line is selected."""
-
-        # Mock time to always return the same value.
-        mock_time = MagicMock(  # type: ignore
-            return_value=time.mktime(
-                datetime(2021, 7, 13, 0, 0, 0).timetuple()))
-        with patch('time.time', new=mock_time):
-            log_view, log_pane = self._create_log_view_with_logs(log_count=4)
-
-        # Mock needed LogPane functions that pull info from prompt_toolkit.
-        log_pane.get_horizontal_scroll_amount = MagicMock(return_value=0)
-        log_pane.current_log_pane_width = 80
-        log_pane.current_log_pane_height = 10
-
-        log_view.render_content()
-        log_view.scroll_to_top()
-        log_view.render_content()
-        # Scroll to top keeps the cursor on the bottom of the window.
-        self.assertEqual(log_view.get_cursor_position(), Point(x=0, y=9))
-
-        log_view.scroll_to_bottom()
-        log_view.render_content()
-        self.assertEqual(log_view.get_cursor_position(), Point(x=0, y=9))
-
-        expected_formatted_text = [
-            ('', ''),
-            ('class:log-time', '20210713 00:00:00'),
-            ('', '  '),
-            ('class:log-level-10', 'DEBUG'),
-            ('', '  Test log 0'),
-
-            ('class:log-time', '20210713 00:00:00'),
-            ('', '  '),
-            ('class:log-level-10', 'DEBUG'),
-            ('', '  Test log 1'),
-
-            ('class:log-time', '20210713 00:00:00'),
-            ('', '  '),
-            ('class:log-level-10', 'DEBUG'),
-            ('', '  Test log 2'),
-
-            ('class:selected-log-line class:log-time', '20210713 00:00:00'),
-            ('class:selected-log-line ', '  '),
-            ('class:selected-log-line class:log-level-10', 'DEBUG'),
-            ('class:selected-log-line ',
-             '  Test log 3                                             ')
-        ]  # yapf: disable
-
-        result_text = join_adjacent_style_tuples(
-            flatten_formatted_text_tuples(log_view._line_fragment_cache))  # pylint: disable=protected-access
-
-        self.assertEqual(result_text, expected_formatted_text)
-
-    def test_clear_scrollback(self) -> None:
-        """Test various functions with clearing log scrollback history."""
-        # pylint: disable=protected-access
-        # Create log_view with 4 logs
-        starting_log_count = 4
-        log_view, _pane = self._create_log_view_with_logs(
-            log_count=starting_log_count)
-        log_view.render_content()
-
-        # Check setup is correct
-        self.assertTrue(log_view.follow)
-        self.assertEqual(log_view.get_current_line(), 3)
-        self.assertEqual(log_view.get_total_count(), 4)
-        self.assertEqual(
-            list(log.record.message
-                 for log in log_view._get_visible_log_lines()),
-            ['Test log 0', 'Test log 1', 'Test log 2', 'Test log 3'])
-
-        # Clear scrollback
-        log_view.clear_scrollback()
-        log_view.render_content()
-        # Follow is still on
-        self.assertTrue(log_view.follow)
-        self.assertEqual(log_view.hidden_line_count(), 4)
-        # Current line index should stay the same
-        self.assertEqual(log_view.get_current_line(), 3)
-        # Total count should stay the same
-        self.assertEqual(log_view.get_total_count(), 4)
-        # No lines returned
-        self.assertEqual(
-            list(log.record.message
-                 for log in log_view._get_visible_log_lines()), [])
-
-        # Add Log 4 more lines
-        test_log = logging.getLogger('log_view.test')
-        with self.assertLogs(test_log, level='DEBUG') as _log_context:
-            test_log.addHandler(log_view.log_store)
-            for i in range(4):
-                test_log.debug('Test log %s', i + starting_log_count)
-        log_view.render_content()
-
-        # Current line
-        self.assertEqual(log_view.hidden_line_count(), 4)
-        self.assertEqual(log_view.get_last_log_index(), 7)
-        self.assertEqual(log_view.get_current_line(), 7)
-        self.assertEqual(log_view.get_total_count(), 8)
-        # Only the last 4 logs should appear
-        self.assertEqual(
-            list(log.record.message
-                 for log in log_view._get_visible_log_lines()),
-            ['Test log 4', 'Test log 5', 'Test log 6', 'Test log 7'])
-
-        log_view.scroll_to_bottom()
-        log_view.render_content()
-        self.assertEqual(log_view.get_current_line(), 7)
-        # Turn follow back on
-        log_view.toggle_follow()
-
-        log_view.undo_clear_scrollback()
-        # Current line and total are the same
-        self.assertEqual(log_view.get_current_line(), 7)
-        self.assertEqual(log_view.get_total_count(), 8)
-        # All logs should appear
-        self.assertEqual(
-            list(log.record.message
-                 for log in log_view._get_visible_log_lines()), [
-                     'Test log 0', 'Test log 1', 'Test log 2', 'Test log 3',
-                     'Test log 4', 'Test log 5', 'Test log 6', 'Test log 7'
-                 ])
-
-        log_view.scroll_to_bottom()
-        log_view.render_content()
-        self.assertEqual(log_view.get_current_line(), 7)
-
-    def test_get_line_at_cursor_position(self) -> None:
-        """Tests fuctions that rely on getting a log_index for the current
-        cursor position.
-
-        Including:
-        - LogScreen.fetch_subline_up
-        - LogScreen.fetch_subline_down
-        - LogView._update_log_index
-        """
-        # pylint: disable=protected-access
-        # Create log_view with 4 logs
-        starting_log_count = 4
-        log_view, _pane = self._create_log_view_with_logs(
-            log_count=starting_log_count)
-        log_view.render_content()
-
-        # Check setup is correct
-        self.assertTrue(log_view.follow)
-        self.assertEqual(log_view.get_current_line(), 3)
-        self.assertEqual(log_view.get_total_count(), 4)
-        self.assertEqual(
-            list(log.record.message
-                 for log in log_view._get_visible_log_lines()),
-            ['Test log 0', 'Test log 1', 'Test log 2', 'Test log 3'])
-
-        self.assertEqual(log_view.log_screen.cursor_position, 9)
-        # Force the cursor_position to be larger than the log_screen
-        # line_buffer.
-        log_view.log_screen.cursor_position = 10
-        # Attempt to get the current line, no exception should be raised
-        result = log_view.log_screen.get_line_at_cursor_position()
-        # Log index should be None
-        self.assertEqual(result.log_index, None)
-
-        # Force the cursor_position to be < 0. This won't produce an error but
-        # would wrap around to the beginning.
-        log_view.log_screen.cursor_position = -1
-        # Attempt to get the current line, no exception should be raised
-        result = log_view.log_screen.get_line_at_cursor_position()
-        # Result should be a blank line
-        self.assertEqual(result, ScreenLine([('', '')]))
-        # Log index should be None
-        self.assertEqual(result.log_index, None)
-
-    def test_visual_select(self) -> None:
-        """Test log line selection."""
-        log_view, log_pane = self._create_log_view_with_logs(log_count=100)
-        self.assertEqual(100, log_view.get_total_count())
-
-        # Page scrolling needs to know the current window height.
-        log_pane.pane_resized = MagicMock(return_value=True)
-        log_pane.current_log_pane_width = 80
-        log_pane.current_log_pane_height = 10
-
-        log_view.log_screen.reset_logs = MagicMock(
-            wraps=log_view.log_screen.reset_logs)
-        log_view.log_screen.get_lines = MagicMock(
-            wraps=log_view.log_screen.get_lines)
-
-        log_view.render_content()
-        log_view.log_screen.reset_logs.assert_called_once()
-        log_view.log_screen.get_lines.assert_called_once_with(
-            marked_logs_start=None, marked_logs_end=None)
-        log_view.log_screen.get_lines.reset_mock()
-        log_view.log_screen.reset_logs.reset_mock()
-
-        self.assertIsNone(log_view.marked_logs_start)
-        self.assertIsNone(log_view.marked_logs_end)
-        log_view.visual_select_line(Point(0, 9))
-        self.assertEqual(
-            (99, 99), (log_view.marked_logs_start, log_view.marked_logs_end))
-
-        log_view.visual_select_line(Point(0, 8))
-        log_view.visual_select_line(Point(0, 7))
-        self.assertEqual(
-            (97, 99), (log_view.marked_logs_start, log_view.marked_logs_end))
-
-        log_view.clear_visual_selection()
-        self.assertIsNone(log_view.marked_logs_start)
-        self.assertIsNone(log_view.marked_logs_end)
-
-        log_view.visual_select_line(Point(0, 1))
-        log_view.visual_select_line(Point(0, 2))
-        log_view.visual_select_line(Point(0, 3))
-        log_view.visual_select_line(Point(0, 4))
-        self.assertEqual(
-            (91, 94), (log_view.marked_logs_start, log_view.marked_logs_end))
-
-        # Make sure the log screen was not re-generated.
-        log_view.log_screen.reset_logs.assert_not_called()
-        log_view.log_screen.reset_logs.reset_mock()
-
-        # Render the screen
-        log_view.render_content()
-        log_view.log_screen.reset_logs.assert_called_once()
-        # Check the visual selection was specified
-        log_view.log_screen.get_lines.assert_called_once_with(
-            marked_logs_start=91, marked_logs_end=94)
-        log_view.log_screen.get_lines.reset_mock()
-        log_view.log_screen.reset_logs.reset_mock()
-
-
-if _PYTHON_3_8:
-    from unittest import IsolatedAsyncioTestCase  # type: ignore # pylint: disable=no-name-in-module
-
-    class TestLogViewFiltering(IsolatedAsyncioTestCase):  # pylint: disable=undefined-variable
-        """Test LogView log filtering capabilities."""
-
-        # pylint: disable=invalid-name
-        def setUp(self):
-            self.maxDiff = None
-
-        # pylint: enable=invalid-name
-
-        def _create_log_view_from_list(self, log_messages):
-            log_view, log_pane = _create_log_view()
-
-            test_log = logging.getLogger('log_view.test')
-            with self.assertLogs(test_log, level='DEBUG') as _log_context:
-                test_log.addHandler(log_view.log_store)
-                for log, extra_arg in log_messages:
-                    test_log.debug('%s', log, extra=extra_arg)
-
-            return log_view, log_pane
-
-        @parameterized.expand([
-            (
-                # Test name
-                'regex filter',
-                # Search input_text
-                'log.*item',
-                # input_logs
-                [
-                    ('Log some item', dict()),
-                    ('Log another item', dict()),
-                    ('Some exception', dict()),
-                ],
-                # expected_matched_lines
-                [
-                    'Log some item',
-                    'Log another item',
-                ],
-                # expected_match_line_numbers
-                {0: 0, 1: 1},
-                # expected_export_text
-                (
-                    '  DEBUG  Log some item\n'
-                    '  DEBUG  Log another item\n'
-                ),
-                None,  # field
-                False,  # invert
-            ),
-            (
-                # Test name
-                'regex filter with field',
-                # Search input_text
-                'earth',
-                # input_logs
-                [
-                    ('Log some item',
-                    dict(extra_metadata_fields={'planet': 'Jupiter'})),
-                    ('Log another item',
-                    dict(extra_metadata_fields={'planet': 'Earth'})),
-                    ('Some exception',
-                    dict(extra_metadata_fields={'planet': 'Earth'})),
-                ],
-                # expected_matched_lines
-                [
-                    'Log another item',
-                    'Some exception',
-                ],
-                # expected_match_line_numbers
-                {1: 0, 2: 1},
-                # expected_export_text
-                (
-                    '  DEBUG  Earth    Log another item\n'
-                    '  DEBUG  Earth    Some exception\n'
-                ),
-                'planet',  # field
-                False,  # invert
-            ),
-            (
-                # Test name
-                'regex filter with field inverted',
-                # Search input_text
-                'earth',
-                # input_logs
-                [
-                    ('Log some item',
-                    dict(extra_metadata_fields={'planet': 'Jupiter'})),
-                    ('Log another item',
-                    dict(extra_metadata_fields={'planet': 'Earth'})),
-                    ('Some exception',
-                    dict(extra_metadata_fields={'planet': 'Earth'})),
-                ],
-                # expected_matched_lines
-                [
-                    'Log some item',
-                ],
-                # expected_match_line_numbers
-                {0: 0},
-                # expected_export_text
-                (
-                    '  DEBUG  Jupiter  Log some item\n'
-                ),
-                'planet',  # field
-                True,  # invert
-            ),
-        ]) # yapf: disable
-        async def test_log_filtering(
-            self,
-            _test_name,
-            input_text,
-            input_logs,
-            expected_matched_lines,
-            expected_match_line_numbers,
-            expected_export_text,
-            field=None,
-            invert=False,
-        ) -> None:
-            """Test run log view filtering."""
-            log_view, _log_pane = self._create_log_view_from_list(input_logs)
-            log_view.render_content()
-
-            self.assertEqual(log_view.get_total_count(), len(input_logs))
-            # Apply the search and wait for the match count background task
-            log_view.new_search(input_text, invert=invert, field=field)
-            await log_view.search_match_count_task
-            self.assertEqual(log_view.search_matched_lines,
-                             expected_match_line_numbers)
-
-            # Apply the filter and wait for the filter background task
-            log_view.apply_filter()
-            await log_view.filter_existing_logs_task
-
-            # Do the number of logs match the expected count?
-            self.assertEqual(log_view.get_total_count(),
-                             len(expected_matched_lines))
-            self.assertEqual(
-                [log.record.message for log in log_view.filtered_logs],
-                expected_matched_lines)
-
-            # Check exported text respects filtering
-            log_text = log_view._logs_to_text(  # pylint: disable=protected-access
-                use_table_formatting=True)
-            # Remove leading time from resulting logs
-            log_text_no_datetime = ''
-            for line in log_text.splitlines():
-                log_text_no_datetime += (line[17:] + '\n')
-            self.assertEqual(log_text_no_datetime, expected_export_text)
-
-            # Select the bottom log line
-            log_view.render_content()
-            log_view.visual_select_line(Point(0, 9))  # Window height is 10
-            # Export to text
-            log_text = log_view._logs_to_text(  # pylint: disable=protected-access
-                selected_lines_only=True,
-                use_table_formatting=False)
-            self.assertEqual(
-                # Remove date, time, and level
-                log_text[24:].strip(),
-                expected_matched_lines[0].strip())
-
-            # Clear filters and check the numbe of lines is back to normal.
-            log_view.clear_filters()
-            self.assertEqual(log_view.get_total_count(), len(input_logs))
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_console/py/pw_console/__init__.py b/pw_console/py/pw_console/__init__.py
deleted file mode 100644
index 86b1519..0000000
--- a/pw_console/py/pw_console/__init__.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Pigweed interactive console."""
-
-from pw_console.embed import PwConsoleEmbed
-from pw_console.log_store import LogStore
diff --git a/pw_console/py/pw_console/__main__.py b/pw_console/py/pw_console/__main__.py
deleted file mode 100644
index 936bd32..0000000
--- a/pw_console/py/pw_console/__main__.py
+++ /dev/null
@@ -1,148 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Pigweed Console entry point."""
-
-import argparse
-import inspect
-import logging
-from pathlib import Path
-import sys
-
-import pw_cli.log
-import pw_cli.argument_types
-
-import pw_console
-import pw_console.python_logging
-from pw_console.log_store import LogStore
-from pw_console.plugins.calc_pane import CalcPane
-from pw_console.plugins.clock_pane import ClockPane
-
-_LOG = logging.getLogger(__package__)
-_ROOT_LOG = logging.getLogger('')
-
-
-# TODO(tonymd): Remove this when no downstream projects are using it.
-def create_temp_log_file():
-    return pw_console.python_logging.create_temp_log_file()
-
-
-def _build_argument_parser() -> argparse.ArgumentParser:
-    """Setup argparse."""
-    parser = argparse.ArgumentParser(description=__doc__)
-
-    parser.add_argument('-l',
-                        '--loglevel',
-                        type=pw_cli.argument_types.log_level,
-                        default=logging.DEBUG,
-                        help='Set the log level'
-                        '(debug, info, warning, error, critical)')
-
-    parser.add_argument('--logfile', help='Pigweed Console log file.')
-
-    parser.add_argument('--test-mode',
-                        action='store_true',
-                        help='Enable fake log messages for testing purposes.')
-    parser.add_argument('--config-file',
-                        type=Path,
-                        help='Path to a pw_console yaml config file.')
-    parser.add_argument('--console-debug-log-file',
-                        help='Log file to send console debug messages to.')
-
-    return parser
-
-
-def main() -> int:
-    """Pigweed Console."""
-
-    parser = _build_argument_parser()
-    args = parser.parse_args()
-
-    if not args.logfile:
-        # Create a temp logfile to prevent logs from appearing over stdout. This
-        # would corrupt the prompt toolkit UI.
-        args.logfile = pw_console.python_logging.create_temp_log_file()
-
-    pw_cli.log.install(level=args.loglevel,
-                       use_color=True,
-                       hide_timestamp=False,
-                       log_file=args.logfile)
-
-    if args.console_debug_log_file:
-        pw_cli.log.install(level=logging.DEBUG,
-                           use_color=True,
-                           hide_timestamp=False,
-                           log_file=args.console_debug_log_file,
-                           logger=logging.getLogger('pw_console'))
-
-    global_vars = None
-    default_loggers = {}
-    if args.test_mode:
-        root_log_store = LogStore()
-        _ROOT_LOG.addHandler(root_log_store)
-        _ROOT_LOG.debug('pw_console test-mode starting...')
-
-        fake_logger = logging.getLogger(
-            pw_console.console_app.FAKE_DEVICE_LOGGER_NAME)
-        default_loggers = {
-            # Don't include pw_console package logs (_LOG) in the log pane UI.
-            # Add the fake logger for test_mode.
-            'Fake Device Logs': [fake_logger],
-            'PwConsole Debug': [logging.getLogger('pw_console')],
-            'All Logs': root_log_store,
-        }
-        # Give access to adding log messages from the repl via: `LOG.warning()`
-        global_vars = dict(LOG=fake_logger)
-
-    help_text = None
-    app_title = None
-    if args.test_mode:
-        app_title = 'Console Test Mode'
-        help_text = inspect.cleandoc("""
-            Welcome to the Pigweed Console Test Mode!
-
-            Example commands:
-
-              rpcs.pw.rpc.EchoService.Echo(msg='hello!')
-
-              LOG.warning('Message appears console log window.')
-        """)
-
-    console = pw_console.PwConsoleEmbed(
-        global_vars=global_vars,
-        loggers=default_loggers,
-        test_mode=args.test_mode,
-        help_text=help_text,
-        app_title=app_title,
-        config_file_path=args.config_file,
-    )
-
-    # Add example plugins used to validate behavior in the Pigweed Console
-    # manual test procedure: https://pigweed.dev/pw_console/testing.html
-    if args.test_mode:
-        _ROOT_LOG.debug('pw_console.PwConsoleEmbed init complete')
-        _ROOT_LOG.debug('Adding plugins...')
-        console.add_window_plugin(ClockPane())
-        console.add_window_plugin(CalcPane())
-        _ROOT_LOG.debug('Starting prompt_toolkit full-screen application...')
-
-    console.embed()
-
-    if args.logfile:
-        print(f'Logs saved to: {args.logfile}')
-
-    return 0
-
-
-if __name__ == '__main__':
-    sys.exit(main())
diff --git a/pw_console/py/pw_console/command_runner.py b/pw_console/py/pw_console/command_runner.py
deleted file mode 100644
index 88ff984..0000000
--- a/pw_console/py/pw_console/command_runner.py
+++ /dev/null
@@ -1,515 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""CommandRunner dialog classes."""
-
-from __future__ import annotations
-import functools
-import logging
-import re
-from typing import (
-    Callable,
-    Iterable,
-    Iterator,
-    List,
-    Optional,
-    TYPE_CHECKING,
-    Tuple,
-)
-
-from prompt_toolkit.buffer import Buffer
-from prompt_toolkit.filters import Condition
-from prompt_toolkit.formatted_text import StyleAndTextTuples
-from prompt_toolkit.formatted_text.utils import fragment_list_to_text
-from prompt_toolkit.layout.utils import explode_text_fragments
-from prompt_toolkit.history import InMemoryHistory
-from prompt_toolkit.key_binding import (
-    KeyBindings,
-    KeyBindingsBase,
-    KeyPressEvent,
-)
-from prompt_toolkit.layout import (
-    AnyContainer,
-    ConditionalContainer,
-    DynamicContainer,
-    FormattedTextControl,
-    HSplit,
-    VSplit,
-    Window,
-    WindowAlign,
-)
-from prompt_toolkit.widgets import MenuItem
-from prompt_toolkit.widgets import TextArea
-
-import pw_console.widgets.border
-import pw_console.widgets.checkbox
-import pw_console.widgets.mouse_handlers
-
-if TYPE_CHECKING:
-    from pw_console.console_app import ConsoleApp
-
-_LOG = logging.getLogger(__package__)
-
-
-def flatten_menu_items(items: List[MenuItem],
-                       prefix: str = '') -> Iterator[Tuple[str, Callable]]:
-    """Flatten nested prompt_toolkit MenuItems into text and callable tuples."""
-    for item in items:
-        new_text = []
-        if prefix:
-            new_text.append(prefix)
-        new_text.append(item.text)
-        new_prefix = ' > '.join(new_text)
-
-        if item.children:
-            yield from flatten_menu_items(item.children, new_prefix)
-        elif item.handler:
-            # Skip this item if it's a separator or disabled.
-            if item.text == '-' or item.disabled:
-                continue
-            yield (new_prefix, item.handler)
-
-
-def highlight_matches(
-        regexes: Iterable[re.Pattern],
-        line_fragments: StyleAndTextTuples) -> StyleAndTextTuples:
-    """Highlight regex matches in prompt_toolkit FormattedTextTuples."""
-    line_text = fragment_list_to_text(line_fragments)
-    exploded_fragments = explode_text_fragments(line_fragments)
-
-    def apply_highlighting(fragments: StyleAndTextTuples,
-                           index: int,
-                           matching_regex_index: int = 0) -> None:
-        # Expand all fragments and apply the highlighting style.
-        old_style, _text, *_ = fragments[index]
-        # There are 6 fuzzy-highlight styles defined in style.py. Get an index
-        # from 0-5 to use one style after the other in turn.
-        style_index = matching_regex_index % 6
-        fragments[index] = (
-            old_style +
-            f' class:command-runner-fuzzy-highlight-{style_index} ',
-            fragments[index][1],
-        )
-
-    # Highlight each non-overlapping search match.
-    for regex_i, regex in enumerate(regexes):
-        for match in regex.finditer(line_text):
-            for fragment_i in range(match.start(), match.end()):
-                apply_highlighting(exploded_fragments, fragment_i, regex_i)
-
-    return exploded_fragments
-
-
-class CommandRunner:
-    """CommandRunner dialog box."""
-
-    # pylint: disable=too-many-instance-attributes
-
-    def __init__(
-            self,
-            application: ConsoleApp,
-            window_title: str = None,
-            load_completions: Optional[Callable[[],
-                                                List[Tuple[str,
-                                                           Callable]]]] = None,
-            width: int = 80,
-            height: int = 10):
-        # Parent pw_console application
-        self.application = application
-        # Visibility toggle
-        self.show_dialog = False
-        # Tracks the last focused container, to enable restoring focus after
-        # closing the dialog.
-        self.last_focused_pane = None
-
-        # List of all possible completion items
-        self.completions: List[Tuple[str, Callable]] = []
-        # Formatted text fragments of matched items
-        self.completion_fragments: List[StyleAndTextTuples] = []
-
-        # Current selected item tracking variables
-        self.selected_item: int = 0
-        self.selected_item_text: str = ''
-        self.selected_item_handler: Optional[Callable] = None
-        # Previous input text
-        self.last_input_field_text: str = 'EMPTY'
-        # Previous selected item
-        self.last_selected_item: int = 0
-
-        # Dialog width, height and title
-        self.width = width
-        self.height = height
-        self.window_title: str
-
-        # Callable to fetch completion items
-        self.load_completions: Callable[[], List[Tuple[str, Callable]]]
-
-        # Command runner text input field
-        self.input_field = TextArea(
-            prompt=[
-                ('class:command-runner-setting', '> ',
-                 functools.partial(pw_console.widgets.mouse_handlers.on_click,
-                                   self.focus_self))
-            ],
-            focusable=True,
-            focus_on_click=True,
-            scrollbar=False,
-            multiline=False,
-            height=1,
-            dont_extend_height=True,
-            dont_extend_width=False,
-            accept_handler=self._command_accept_handler,
-            history=InMemoryHistory(),
-        )
-        # Set additional keybindings for the input field
-        self.input_field.control.key_bindings = self._create_key_bindings()
-
-        # Container for the Cancel and Run buttons
-        input_field_buttons_container = ConditionalContainer(
-            Window(
-                content=FormattedTextControl(
-                    self._get_input_field_button_fragments,
-                    focusable=False,
-                    show_cursor=False,
-                ),
-                height=1,
-                align=WindowAlign.RIGHT,
-                dont_extend_width=True,
-            ),
-            filter=Condition(lambda: self.content_width() > 40),
-        )
-
-        # Container for completion matches
-        command_items_window = Window(
-            content=FormattedTextControl(
-                self.render_completion_items,
-                show_cursor=False,
-                focusable=False,
-            ),
-            align=WindowAlign.LEFT,
-            dont_extend_width=False,
-            height=self.height,
-        )
-
-        # Main content HSplit
-        self.command_runner_content = HSplit(
-            [
-                # Input field and buttons on the same line
-                VSplit([
-                    self.input_field,
-                    input_field_buttons_container,
-                ]),
-                # Completion items below
-                command_items_window,
-            ],
-            style='class:command-runner class:theme-fg-default',
-        )
-
-        # Set completions if passed in.
-        self.set_completions(window_title, load_completions)
-
-        # bordered_content wraps the above command_runner_content in a border.
-        self.bordered_content: AnyContainer
-        # Root prompt_toolkit container
-        self.container = ConditionalContainer(
-            DynamicContainer(lambda: self.bordered_content),
-            filter=Condition(lambda: self.show_dialog),
-        )
-
-    def _create_bordered_content(self) -> None:
-        """Wrap self.command_runner_content in a border."""
-        # This should be called whenever the window_title changes.
-        self.bordered_content = pw_console.widgets.border.create_border(
-            self.command_runner_content,
-            title=self.window_title,
-            border_style='class:command-runner-border',
-            left_margin_columns=1,
-            right_margin_columns=1,
-        )
-
-    def __pt_container__(self) -> AnyContainer:
-        """Return the prompt_toolkit root container for this dialog."""
-        return self.container
-
-    def _create_key_bindings(self) -> KeyBindingsBase:
-        """Create additional key bindings for the command input field."""
-        key_bindings = KeyBindings()
-        register = self.application.prefs.register_keybinding
-
-        @register('command-runner.cancel', key_bindings)
-        def _cancel(_event: KeyPressEvent) -> None:
-            """Clear input or close command."""
-            if self._get_input_field_text() != '':
-                self._reset_selected_item()
-                return
-
-            self.close_dialog()
-
-        @register('command-runner.select-previous-item', key_bindings)
-        def _select_previous_item(_event: KeyPressEvent) -> None:
-            """Select previous completion item."""
-            self._previous_item()
-
-        @register('command-runner.select-next-item', key_bindings)
-        def _select_next_item(_event: KeyPressEvent) -> None:
-            """Select next completion item."""
-            self._next_item()
-
-        return key_bindings
-
-    def content_width(self) -> int:
-        """Return the smaller value of self.width and the available width."""
-        window_manager_width = (
-            self.application.window_manager.current_window_manager_width)
-        if not window_manager_width:
-            window_manager_width = self.width
-        return min(self.width, window_manager_width)
-
-    def focus_self(self) -> None:
-        self.application.layout.focus(self)
-
-    def close_dialog(self) -> None:
-        """Close command runner dialog box."""
-        self.show_dialog = False
-        self._reset_selected_item()
-
-        # Restore original focus if possible.
-        if self.last_focused_pane:
-            self.application.focus_on_container(self.last_focused_pane)
-        else:
-            # Fallback to focusing on the main menu.
-            self.application.focus_main_menu()
-
-    def open_dialog(self) -> None:
-        self.show_dialog = True
-        self.last_focused_pane = self.application.focused_window()
-        self.focus_self()
-        self.application.redraw_ui()
-
-    def set_completions(
-        self,
-        window_title: str = None,
-        load_completions: Optional[Callable[[], List[Tuple[str,
-                                                           Callable]]]] = None,
-    ) -> None:
-        """Set window title and callable to fetch possible completions.
-
-        Call this function whenever new completion items need to be loaded.
-        """
-        self.window_title = window_title if window_title else 'Menu Items'
-        self.load_completions = (load_completions
-                                 if load_completions else self.load_menu_items)
-        self._reset_selected_item()
-
-        self.completions = []
-        self.completion_fragments = []
-
-        # Load and filter completions
-        self.filter_completions()
-
-        # (Re)create the bordered content with the window_title set.
-        self._create_bordered_content()
-
-    def reload_completions(self) -> None:
-        self.completions = self.load_completions()
-
-    def load_menu_items(self) -> List[Tuple[str, Callable]]:
-        # pylint: disable=no-self-use
-        return list(flatten_menu_items(self.application.menu_items))
-
-    def _get_input_field_text(self) -> str:
-        return self.input_field.buffer.text
-
-    def _make_regexes(self, input_text) -> List[re.Pattern]:
-        # pylint: disable=no-self-use
-        regexes: List[re.Pattern] = []
-        if not input_text:
-            return regexes
-
-        text_tokens = input_text.split(' ')
-        if len(text_tokens) > 0:
-            regexes = [
-                re.compile(re.escape(text), re.IGNORECASE)
-                for text in text_tokens
-            ]
-
-        return regexes
-
-    def _matches_orderless(self, regexes: List[re.Pattern], text) -> bool:
-        """Check if all supplied regexs match the input text."""
-        # pylint: disable=no-self-use
-        return all(regex.search(text) for regex in regexes)
-
-    def filter_completions(self) -> None:
-        """Filter completion items if new user input detected."""
-        if not self.input_text_changed() and not self.selected_item_changed():
-            return
-
-        self.reload_completions()
-
-        input_text = self._get_input_field_text()
-        self.completion_fragments = []
-
-        regexes = self._make_regexes(input_text)
-        check_match = self._matches_orderless
-
-        i = 0
-        for text, handler in self.completions:
-            if not (input_text == '' or check_match(regexes, text)):
-                continue
-            style = ''
-            if i == self.selected_item:
-                style = 'class:command-runner-selected-item'
-                self.selected_item_text = text
-                self.selected_item_handler = handler
-                text = text.ljust(self.content_width())
-            fragments: StyleAndTextTuples = highlight_matches(
-                regexes, [(style, text + '\n')])
-            self.completion_fragments.append(fragments)
-            i += 1
-
-    def input_text_changed(self) -> bool:
-        """Return True if text in the input field has changed."""
-        input_text = self._get_input_field_text()
-        if input_text != self.last_input_field_text:
-            self.last_input_field_text = input_text
-            self.selected_item = 0
-            return True
-        return False
-
-    def selected_item_changed(self) -> bool:
-        """Check if the user pressed up or down to select a different item."""
-        return self.last_selected_item != self.selected_item
-
-    def _next_item(self) -> None:
-        self.last_selected_item = self.selected_item
-        self.selected_item = min(
-            # Don't move past the height of the window or the length of possible
-            # items.
-            min(self.height, len(self.completion_fragments)) - 1,
-            self.selected_item + 1)
-        self.application.redraw_ui()
-
-    def _previous_item(self) -> None:
-        self.last_selected_item = self.selected_item
-        self.selected_item = max(0, self.selected_item - 1)
-        self.application.redraw_ui()
-
-    def _get_input_field_button_fragments(self) -> StyleAndTextTuples:
-        # Mouse handlers
-        focus = functools.partial(pw_console.widgets.mouse_handlers.on_click,
-                                  self.focus_self)
-        cancel = functools.partial(pw_console.widgets.mouse_handlers.on_click,
-                                   self.close_dialog)
-        select_item = functools.partial(
-            pw_console.widgets.mouse_handlers.on_click,
-            self._run_selected_item)
-
-        separator_text = ('', ' ', focus)
-
-        # Default button style
-        button_style = 'class:toolbar-button-inactive'
-
-        fragments: StyleAndTextTuples = []
-
-        # Cancel button
-        fragments.extend(
-            pw_console.widgets.checkbox.to_keybind_indicator(
-                key='Ctrl-c',
-                description='Cancel',
-                mouse_handler=cancel,
-                base_style=button_style,
-            ))
-        fragments.append(separator_text)
-
-        # Run button
-        fragments.extend(
-            pw_console.widgets.checkbox.to_keybind_indicator(
-                'Enter', 'Run', select_item, base_style=button_style))
-        return fragments
-
-    def render_completion_items(self) -> StyleAndTextTuples:
-        """Render completion items."""
-        fragments: StyleAndTextTuples = []
-
-        # Update completions if any state change since the last render (new text
-        # entered or arrow keys pressed).
-        self.filter_completions()
-
-        for completion_item in self.completion_fragments:
-            fragments.extend(completion_item)
-
-        return fragments
-
-    def _reset_selected_item(self) -> None:
-        self.selected_item = 0
-        self.last_selected_item = 0
-        self.selected_item_text = ''
-        self.selected_item_handler = None
-        self.last_input_field_text = 'EMPTY'
-        self.input_field.buffer.reset()
-
-    def _run_selected_item(self) -> None:
-        """Run the selected action."""
-        if not self.selected_item_handler:
-            return
-        # Save the selected item handler. This is reset by self.close_dialog()
-        handler = self.selected_item_handler
-
-        # Depending on what action is run, the command runner dialog may need to
-        # be closed, left open, or closed before running the selected action.
-        close_dialog = True
-        close_dialog_first = False
-
-        # Actions that launch new command runners, close_dialog should not run.
-        for command_text in [
-                '[File] > Open Logger',
-        ]:
-            if command_text in self.selected_item_text:
-                close_dialog = False
-                break
-
-        # Actions that change what is in focus should be run after closing the
-        # command runner dialog.
-        for command_text in [
-                '[View] > Focus Next Window/Tab',
-                '[View] > Focus Prev Window/Tab',
-                # All help menu entries open popup windows.
-                '[Help] > ',
-                # This focuses on a save dialog bor.
-                'Save/Export a copy',
-        ]:
-            if command_text in self.selected_item_text:
-                close_dialog_first = True
-                break
-
-        # Close first if needed
-        if close_dialog and close_dialog_first:
-            self.close_dialog()
-
-        # Run the selected item handler
-        handler()
-
-        # If not already closed earlier.
-        if close_dialog and not close_dialog_first:
-            self.close_dialog()
-
-    def _command_accept_handler(self, _buff: Buffer) -> bool:
-        """Function run when pressing Enter in the command runner input box."""
-        # If at least one match is available
-        if len(self.completion_fragments) > 0:
-            self._run_selected_item()
-            # Erase input text
-            return False
-        # Keep input text
-        return True
diff --git a/pw_console/py/pw_console/console_app.py b/pw_console/py/pw_console/console_app.py
deleted file mode 100644
index 560d946..0000000
--- a/pw_console/py/pw_console/console_app.py
+++ /dev/null
@@ -1,909 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""ConsoleApp control class."""
-
-import asyncio
-import builtins
-import functools
-import logging
-import os
-from pathlib import Path
-import sys
-from threading import Thread
-from typing import Any, Callable, Iterable, List, Optional, Tuple, Union
-
-from jinja2 import Environment, FileSystemLoader, make_logging_undefined
-from prompt_toolkit.clipboard.pyperclip import PyperclipClipboard
-from prompt_toolkit.layout.menus import CompletionsMenu
-from prompt_toolkit.output import ColorDepth
-from prompt_toolkit.application import Application
-from prompt_toolkit.filters import Condition
-from prompt_toolkit.styles import (
-    DynamicStyle,
-    merge_styles,
-)
-from prompt_toolkit.layout import (
-    ConditionalContainer,
-    Float,
-    Layout,
-)
-from prompt_toolkit.widgets import FormattedTextToolbar
-from prompt_toolkit.widgets import (
-    MenuContainer,
-    MenuItem,
-)
-from prompt_toolkit.key_binding import KeyBindings, merge_key_bindings
-from prompt_toolkit.history import (
-    FileHistory,
-    History,
-    ThreadedHistory,
-)
-from ptpython.layout import CompletionVisualisation  # type: ignore
-from ptpython.key_bindings import (  # type: ignore
-    load_python_bindings, load_sidebar_bindings,
-)
-
-from pw_console.console_prefs import ConsolePrefs
-from pw_console.help_window import HelpWindow
-from pw_console.command_runner import CommandRunner
-import pw_console.key_bindings
-from pw_console.log_pane import LogPane
-from pw_console.log_store import LogStore
-from pw_console.pw_ptpython_repl import PwPtPythonRepl
-from pw_console.python_logging import all_loggers
-from pw_console.quit_dialog import QuitDialog
-from pw_console.repl_pane import ReplPane
-import pw_console.style
-import pw_console.widgets.checkbox
-import pw_console.widgets.mouse_handlers
-from pw_console.window_manager import WindowManager
-
-_LOG = logging.getLogger(__package__)
-
-# Fake logger for --test-mode
-FAKE_DEVICE_LOGGER_NAME = 'pw_console_fake_device'
-_FAKE_DEVICE_LOG = logging.getLogger(FAKE_DEVICE_LOGGER_NAME)
-# Don't send fake_device logs to the root Python logger.
-_FAKE_DEVICE_LOG.propagate = False
-
-MAX_FPS = 15
-MIN_REDRAW_INTERVAL = (60.0 / MAX_FPS) / 60.0
-
-
-class FloatingMessageBar(ConditionalContainer):
-    """Floating message bar for showing status messages."""
-    def __init__(self, application):
-        super().__init__(
-            FormattedTextToolbar(
-                (lambda: application.message if application.message else []),
-                style='class:toolbar_inactive',
-            ),
-            filter=Condition(
-                lambda: application.message and application.message != ''))
-
-
-def _add_log_handler_to_pane(logger: Union[str, logging.Logger],
-                             pane: 'LogPane',
-                             level_name: Optional[str] = None) -> None:
-    """A log pane handler for a given logger instance."""
-    if not pane:
-        return
-    pane.add_log_handler(logger, level_name=level_name)
-
-
-def get_default_colordepth(
-        color_depth: Optional[ColorDepth] = None) -> ColorDepth:
-    # Set prompt_toolkit color_depth to the highest possible.
-    if color_depth is None:
-        # Default to 24bit color
-        color_depth = ColorDepth.DEPTH_24_BIT
-
-        # If using Apple Terminal switch to 256 (8bit) color.
-        term_program = os.environ.get('TERM_PROGRAM', '')
-        if sys.platform == 'darwin' and 'Apple_Terminal' in term_program:
-            color_depth = ColorDepth.DEPTH_8_BIT
-
-    # Check for any PROMPT_TOOLKIT_COLOR_DEPTH environment variables
-    color_depth_override = os.environ.get('PROMPT_TOOLKIT_COLOR_DEPTH', '')
-    if color_depth_override:
-        color_depth = ColorDepth(color_depth_override)
-    return color_depth
-
-
-class ConsoleApp:
-    """The main ConsoleApp class that glues everything together."""
-
-    # pylint: disable=too-many-instance-attributes,too-many-public-methods
-    def __init__(
-        self,
-        global_vars=None,
-        local_vars=None,
-        repl_startup_message=None,
-        help_text=None,
-        app_title=None,
-        color_depth=None,
-        extra_completers=None,
-        prefs=None,
-    ):
-        self.prefs = prefs if prefs else ConsolePrefs()
-        self.color_depth = get_default_colordepth(color_depth)
-
-        # Create a default global and local symbol table. Values are the same
-        # structure as what is returned by globals():
-        #   https://docs.python.org/3/library/functions.html#globals
-        if global_vars is None:
-            global_vars = {
-                '__name__': '__main__',
-                '__package__': None,
-                '__doc__': None,
-                '__builtins__': builtins,
-            }
-
-        local_vars = local_vars or global_vars
-
-        # Setup the Jinja environment
-        self.jinja_env = Environment(
-            # Load templates automatically from pw_console/templates
-            loader=FileSystemLoader(Path(__file__).parent / 'templates'),
-            # Raise errors if variables are undefined in templates
-            undefined=make_logging_undefined(
-                logger=logging.getLogger(__package__), ),
-            # Trim whitespace in templates
-            trim_blocks=True,
-            lstrip_blocks=True,
-        )
-
-        self.repl_history_filename = self.prefs.repl_history
-        self.search_history_filename = self.prefs.search_history
-
-        # History instance for search toolbars.
-        self.search_history: History = ThreadedHistory(
-            FileHistory(self.search_history_filename))
-
-        # Event loop for executing user repl code.
-        self.user_code_loop = asyncio.new_event_loop()
-
-        self.app_title = app_title if app_title else 'Pigweed Console'
-
-        # Top level UI state toggles.
-        self.load_theme(self.prefs.ui_theme)
-
-        # Pigweed upstream RST user guide
-        self.user_guide_window = HelpWindow(self, title='User Guide')
-        self.user_guide_window.load_user_guide()
-
-        # Top title message
-        self.message = [('class:logo', self.app_title), ('', '  ')]
-
-        self.message.extend(
-            pw_console.widgets.checkbox.to_keybind_indicator(
-                'Ctrl-p',
-                'Search Menu',
-                functools.partial(pw_console.widgets.mouse_handlers.on_click,
-                                  self.open_command_runner_main_menu),
-                base_style='class:toolbar-button-inactive',
-            ))
-        # One space separator
-        self.message.append(('', ' '))
-
-        # Auto-generated keybindings list for all active panes
-        self.keybind_help_window = HelpWindow(self, title='Keyboard Shortcuts')
-
-        # Downstream project specific help text
-        self.app_help_text = help_text if help_text else None
-        self.app_help_window = HelpWindow(self,
-                                          additional_help_text=help_text,
-                                          title=(self.app_title + ' Help'))
-        self.app_help_window.generate_help_text()
-
-        self.prefs_file_window = HelpWindow(self, title='.pw_console.yaml')
-        self.prefs_file_window.load_yaml_text(
-            self.prefs.current_config_as_yaml())
-
-        # Used for tracking which pane was in focus before showing help window.
-        self.last_focused_pane = None
-
-        # Create a ptpython repl instance.
-        self.pw_ptpython_repl = PwPtPythonRepl(
-            get_globals=lambda: global_vars,
-            get_locals=lambda: local_vars,
-            color_depth=self.color_depth,
-            history_filename=self.repl_history_filename,
-            extra_completers=extra_completers,
-        )
-        self.input_history = self.pw_ptpython_repl.history
-
-        self.repl_pane = ReplPane(
-            application=self,
-            python_repl=self.pw_ptpython_repl,
-            startup_message=repl_startup_message,
-        )
-        self.pw_ptpython_repl.use_code_colorscheme(self.prefs.code_theme)
-
-        if self.prefs.swap_light_and_dark:
-            self.toggle_light_theme()
-
-        # Window panes are added via the window_manager
-        self.window_manager = WindowManager(self)
-        self.window_manager.add_pane_no_checks(self.repl_pane)
-
-        # Top of screen menu items
-        self.menu_items = self._create_menu_items()
-
-        self.quit_dialog = QuitDialog(self)
-
-        # Key bindings registry.
-        self.key_bindings = pw_console.key_bindings.create_key_bindings(self)
-
-        # Create help window text based global key_bindings and active panes.
-        self._update_help_window()
-
-        self.command_runner = CommandRunner(
-            self,
-            width=self.prefs.command_runner_width,
-            height=self.prefs.command_runner_height,
-        )
-
-        self.floats = [
-            # Top message bar
-            Float(
-                content=FloatingMessageBar(self),
-                top=0,
-                right=0,
-                height=1,
-            ),
-            # Centered floating help windows
-            Float(
-                content=self.prefs_file_window,
-                top=2,
-                bottom=2,
-                # Callable to get width
-                width=self.prefs_file_window.content_width,
-            ),
-            Float(
-                content=self.app_help_window,
-                top=2,
-                bottom=2,
-                # Callable to get width
-                width=self.app_help_window.content_width,
-            ),
-            Float(
-                content=self.user_guide_window,
-                top=2,
-                bottom=2,
-                # Callable to get width
-                width=self.user_guide_window.content_width,
-            ),
-            Float(
-                content=self.keybind_help_window,
-                top=2,
-                bottom=2,
-                # Callable to get width
-                width=self.keybind_help_window.content_width,
-            ),
-            # Completion menu that can overlap other panes since it lives in
-            # the top level Float container.
-            Float(
-                xcursor=True,
-                ycursor=True,
-                content=ConditionalContainer(
-                    content=CompletionsMenu(
-                        scroll_offset=(lambda: self.pw_ptpython_repl.
-                                       completion_menu_scroll_offset),
-                        max_height=16,
-                    ),
-                    # Only show our completion if ptpython's is disabled.
-                    filter=Condition(
-                        lambda: self.pw_ptpython_repl.completion_visualisation
-                        == CompletionVisualisation.NONE),
-                ),
-            ),
-            Float(
-                content=self.command_runner,
-                # Callable to get width
-                width=self.command_runner.content_width,
-                **self.prefs.command_runner_position,
-            ),
-            Float(
-                content=self.quit_dialog,
-                top=2,
-                left=2,
-            ),
-        ]
-
-        # prompt_toolkit root container.
-        self.root_container = MenuContainer(
-            body=self.window_manager.create_root_container(),
-            menu_items=self.menu_items,
-            floats=self.floats,
-        )
-
-        # NOTE: ptpython stores it's completion menus in this HSplit:
-        #
-        # self.pw_ptpython_repl.__pt_container__()
-        #   .children[0].children[0].children[0].floats[0].content.children
-        #
-        # Index 1 is a CompletionsMenu and is shown when:
-        #   self.pw_ptpython_repl
-        #     .completion_visualisation == CompletionVisualisation.POP_UP
-        #
-        # Index 2 is a MultiColumnCompletionsMenu and is shown when:
-        #   self.pw_ptpython_repl
-        #     .completion_visualisation == CompletionVisualisation.MULTI_COLUMN
-        #
-
-        # Setup the prompt_toolkit layout with the repl pane as the initially
-        # focused element.
-        self.layout: Layout = Layout(
-            self.root_container,
-            focused_element=self.pw_ptpython_repl,
-        )
-
-        # Create the prompt_toolkit Application instance.
-        self.application: Application = Application(
-            layout=self.layout,
-            key_bindings=merge_key_bindings([
-                # Pull key bindings from ptpython
-                load_python_bindings(self.pw_ptpython_repl),
-                load_sidebar_bindings(self.pw_ptpython_repl),
-                self.window_manager.key_bindings,
-                self.key_bindings,
-            ]),
-            style=DynamicStyle(lambda: merge_styles([
-                self._current_theme,
-                # Include ptpython styles
-                self.pw_ptpython_repl._current_style,  # pylint: disable=protected-access
-            ])),
-            style_transformation=self.pw_ptpython_repl.style_transformation,
-            enable_page_navigation_bindings=True,
-            full_screen=True,
-            mouse_support=True,
-            color_depth=self.color_depth,
-            clipboard=PyperclipClipboard(),
-            min_redraw_interval=MIN_REDRAW_INTERVAL,
-        )
-
-    def get_template(self, file_name: str):
-        return self.jinja_env.get_template(file_name)
-
-    def run_pane_menu_option(self, function_to_run):
-        # Run the function for a particular menu item.
-        return_value = function_to_run()
-        # It's return value dictates if the main menu should close or not.
-        # - True: The main menu stays open. This is the default prompt_toolkit
-        #   menu behavior.
-        # - False: The main menu closes.
-
-        # Update menu content. This will refresh checkboxes and add/remove
-        # items.
-        self.update_menu_items()
-        # Check if the main menu should stay open.
-        if not return_value:
-            # Keep the main menu open
-            self.focus_main_menu()
-
-    def open_new_log_pane_for_logger(
-            self,
-            logger_name: str,
-            level_name='NOTSET',
-            window_title: Optional[str] = None) -> None:
-        pane_title = window_title if window_title else logger_name
-        self.run_pane_menu_option(
-            functools.partial(self.add_log_handler,
-                              pane_title, [logger_name],
-                              log_level_name=level_name))
-
-    def set_ui_theme(self, theme_name: str) -> Callable:
-        call_function = functools.partial(
-            self.run_pane_menu_option,
-            functools.partial(self.load_theme, theme_name))
-        return call_function
-
-    def set_code_theme(self, theme_name: str) -> Callable:
-        call_function = functools.partial(
-            self.run_pane_menu_option,
-            functools.partial(self.pw_ptpython_repl.use_code_colorscheme,
-                              theme_name))
-        return call_function
-
-    def update_menu_items(self):
-        self.menu_items = self._create_menu_items()
-        self.root_container.menu_items = self.menu_items
-
-    def open_command_runner_main_menu(self) -> None:
-        self.command_runner.set_completions()
-        if not self.command_runner_is_open():
-            self.command_runner.open_dialog()
-
-    def open_command_runner_loggers(self) -> None:
-        self.command_runner.set_completions(
-            window_title='Open Logger',
-            load_completions=self._create_logger_completions)
-        if not self.command_runner_is_open():
-            self.command_runner.open_dialog()
-
-    def _create_logger_completions(self) -> List[Tuple[str, Callable]]:
-        completions: List[Tuple[str, Callable]] = [
-            (
-                'root',
-                functools.partial(self.open_new_log_pane_for_logger,
-                                  '',
-                                  window_title='root'),
-            ),
-        ]
-
-        all_logger_names = sorted([logger.name for logger in all_loggers()])
-
-        for logger_name in all_logger_names:
-            completions.append((
-                logger_name,
-                functools.partial(self.open_new_log_pane_for_logger,
-                                  logger_name),
-            ))
-        return completions
-
-    def _create_menu_items(self):
-        themes_submenu = [
-            MenuItem('Toggle Light/Dark', handler=self.toggle_light_theme),
-            MenuItem('-'),
-            MenuItem(
-                'UI Themes',
-                children=[
-                    MenuItem('Default: Dark', self.set_ui_theme('dark')),
-                    MenuItem('High Contrast',
-                             self.set_ui_theme('high-contrast-dark')),
-                    MenuItem('Nord', self.set_ui_theme('nord')),
-                    MenuItem('Nord Light', self.set_ui_theme('nord-light')),
-                    MenuItem('Moonlight', self.set_ui_theme('moonlight')),
-                ],
-            ),
-            MenuItem(
-                'Code Themes',
-                children=[
-                    MenuItem('Code: pigweed-code',
-                             self.set_code_theme('pigweed-code')),
-                    MenuItem('Code: pigweed-code-light',
-                             self.set_code_theme('pigweed-code-light')),
-                    MenuItem('Code: material',
-                             self.set_code_theme('material')),
-                    MenuItem('Code: gruvbox-light',
-                             self.set_code_theme('gruvbox-light')),
-                    MenuItem('Code: gruvbox-dark',
-                             self.set_code_theme('gruvbox-dark')),
-                    MenuItem('Code: tomorrow-night',
-                             self.set_code_theme('tomorrow-night')),
-                    MenuItem('Code: tomorrow-night-bright',
-                             self.set_code_theme('tomorrow-night-bright')),
-                    MenuItem('Code: tomorrow-night-blue',
-                             self.set_code_theme('tomorrow-night-blue')),
-                    MenuItem('Code: tomorrow-night-eighties',
-                             self.set_code_theme('tomorrow-night-eighties')),
-                    MenuItem('Code: dracula', self.set_code_theme('dracula')),
-                    MenuItem('Code: zenburn', self.set_code_theme('zenburn')),
-                ],
-            ),
-        ]
-
-        file_menu = [
-            # File menu
-            MenuItem(
-                '[File]',
-                children=[
-                    MenuItem('Open Logger',
-                             handler=self.open_command_runner_loggers),
-                    MenuItem(
-                        'Log Table View',
-                        children=[
-                            MenuItem(
-                                '{check} Hide Date'.format(
-                                    check=pw_console.widgets.checkbox.
-                                    to_checkbox_text(
-                                        self.prefs.hide_date_from_log_time,
-                                        end='')),
-                                handler=functools.partial(
-                                    self.run_pane_menu_option,
-                                    functools.partial(
-                                        self.toggle_pref_option,
-                                        'hide_date_from_log_time')),
-                            ),
-                            MenuItem(
-                                '{check} Show Source File'.format(
-                                    check=pw_console.widgets.checkbox.
-                                    to_checkbox_text(
-                                        self.prefs.show_source_file, end='')),
-                                handler=functools.partial(
-                                    self.run_pane_menu_option,
-                                    functools.partial(self.toggle_pref_option,
-                                                      'show_source_file')),
-                            ),
-                            MenuItem(
-                                '{check} Show Python File'.format(
-                                    check=pw_console.widgets.checkbox.
-                                    to_checkbox_text(
-                                        self.prefs.show_python_file, end='')),
-                                handler=functools.partial(
-                                    self.run_pane_menu_option,
-                                    functools.partial(self.toggle_pref_option,
-                                                      'show_python_file')),
-                            ),
-                            MenuItem(
-                                '{check} Show Python Logger'.format(
-                                    check=pw_console.widgets.checkbox.
-                                    to_checkbox_text(
-                                        self.prefs.show_python_logger,
-                                        end='')),
-                                handler=functools.partial(
-                                    self.run_pane_menu_option,
-                                    functools.partial(self.toggle_pref_option,
-                                                      'show_python_logger')),
-                            ),
-                        ]),
-                    MenuItem('-'),
-                    MenuItem(
-                        'Themes',
-                        children=themes_submenu,
-                    ),
-                    MenuItem('-'),
-                    MenuItem('Exit', handler=self.exit_console),
-                ],
-            ),
-        ]
-
-        edit_menu = [
-            MenuItem(
-                '[Edit]',
-                children=[
-                    MenuItem('Paste to Python Input',
-                             handler=self.repl_pane.
-                             paste_system_clipboard_to_input_buffer),
-                    MenuItem('-'),
-                    MenuItem('Copy all Python Output',
-                             handler=self.repl_pane.copy_all_output_text),
-                    MenuItem('Copy all Python Input',
-                             handler=self.repl_pane.copy_all_input_text),
-                ],
-            ),
-        ]
-
-        view_menu = [
-            MenuItem(
-                '[View]',
-                children=[
-                    #         [Menu Item             ][Keybind  ]
-                    MenuItem('Focus Next Window/Tab   Ctrl-Alt-n',
-                             handler=self.window_manager.focus_next_pane),
-                    #         [Menu Item             ][Keybind  ]
-                    MenuItem('Focus Prev Window/Tab   Ctrl-Alt-p',
-                             handler=self.window_manager.focus_previous_pane),
-                    MenuItem('-'),
-
-                    #         [Menu Item             ][Keybind  ]
-                    MenuItem('Move Window Up         Ctrl-Alt-Up',
-                             handler=functools.partial(
-                                 self.run_pane_menu_option,
-                                 self.window_manager.move_pane_up)),
-                    #         [Menu Item             ][Keybind  ]
-                    MenuItem('Move Window Down     Ctrl-Alt-Down',
-                             handler=functools.partial(
-                                 self.run_pane_menu_option,
-                                 self.window_manager.move_pane_down)),
-                    #         [Menu Item             ][Keybind  ]
-                    MenuItem('Move Window Left     Ctrl-Alt-Left',
-                             handler=functools.partial(
-                                 self.run_pane_menu_option,
-                                 self.window_manager.move_pane_left)),
-                    #         [Menu Item             ][Keybind  ]
-                    MenuItem('Move Window Right   Ctrl-Alt-Right',
-                             handler=functools.partial(
-                                 self.run_pane_menu_option,
-                                 self.window_manager.move_pane_right)),
-                    MenuItem('-'),
-
-                    #         [Menu Item             ][Keybind  ]
-                    MenuItem('Shrink Height            Alt-Minus',
-                             handler=functools.partial(
-                                 self.run_pane_menu_option,
-                                 self.window_manager.shrink_pane)),
-                    #         [Menu Item             ][Keybind  ]
-                    MenuItem('Enlarge Height               Alt-=',
-                             handler=functools.partial(
-                                 self.run_pane_menu_option,
-                                 self.window_manager.enlarge_pane)),
-                    MenuItem('-'),
-
-                    #         [Menu Item             ][Keybind  ]
-                    MenuItem('Shrink Column                Alt-,',
-                             handler=functools.partial(
-                                 self.run_pane_menu_option,
-                                 self.window_manager.shrink_split)),
-                    #         [Menu Item             ][Keybind  ]
-                    MenuItem('Enlarge Column               Alt-.',
-                             handler=functools.partial(
-                                 self.run_pane_menu_option,
-                                 self.window_manager.enlarge_split)),
-                    MenuItem('-'),
-
-                    #         [Menu Item            ][Keybind  ]
-                    MenuItem('Balance Window Sizes       Ctrl-u',
-                             handler=functools.partial(
-                                 self.run_pane_menu_option,
-                                 self.window_manager.balance_window_sizes)),
-                ],
-            ),
-        ]
-
-        window_menu = self.window_manager.create_window_menu()
-
-        help_menu_items = [
-            MenuItem(self.user_guide_window.menu_title(),
-                     handler=self.user_guide_window.toggle_display),
-            MenuItem(self.keybind_help_window.menu_title(),
-                     handler=self.keybind_help_window.toggle_display),
-            MenuItem('-'),
-            MenuItem('View Key Binding Config',
-                     handler=self.prefs_file_window.toggle_display),
-        ]
-
-        if self.app_help_text:
-            help_menu_items.extend([
-                MenuItem('-'),
-                MenuItem(self.app_help_window.menu_title(),
-                         handler=self.app_help_window.toggle_display)
-            ])
-
-        help_menu = [
-            # Info / Help
-            MenuItem(
-                '[Help]',
-                children=help_menu_items,
-            ),
-        ]
-
-        return file_menu + edit_menu + view_menu + window_menu + help_menu
-
-    def focus_main_menu(self):
-        """Set application focus to the main menu."""
-        self.application.layout.focus(self.root_container.window)
-
-    def focus_on_container(self, pane):
-        """Set application focus to a specific container."""
-        # Try to focus on the given pane
-        try:
-            self.application.layout.focus(pane)
-        except ValueError:
-            # If the container can't be focused, focus on the first visible
-            # window pane.
-            self.window_manager.focus_first_visible_pane()
-
-    def toggle_light_theme(self):
-        """Toggle light and dark theme colors."""
-        # Use ptpython's style_transformation to swap dark and light colors.
-        self.pw_ptpython_repl.swap_light_and_dark = (
-            not self.pw_ptpython_repl.swap_light_and_dark)
-        if self.application:
-            self.focus_main_menu()
-
-    def toggle_pref_option(self, setting_name):
-        self.prefs.toggle_bool_option(setting_name)
-
-    def load_theme(self, theme_name=None):
-        """Regenerate styles for the current theme_name."""
-        self._current_theme = pw_console.style.generate_styles(theme_name)
-        if theme_name:
-            self.prefs.set_ui_theme(theme_name)
-
-    def _create_log_pane(self,
-                         title: str = '',
-                         log_store: Optional[LogStore] = None) -> 'LogPane':
-        # Create one log pane.
-        log_pane = LogPane(application=self,
-                           pane_title=title,
-                           log_store=log_store)
-        self.window_manager.add_pane(log_pane)
-        return log_pane
-
-    def load_clean_config(self, config_file: Path) -> None:
-        self.prefs.reset_config()
-        self.prefs.load_config_file(config_file)
-
-    def apply_window_config(self) -> None:
-        self.window_manager.apply_config(self.prefs)
-
-    def refresh_layout(self) -> None:
-        self.window_manager.update_root_container_body()
-        self.update_menu_items()
-        self._update_help_window()
-
-    def add_log_handler(
-            self,
-            window_title: str,
-            logger_instances: Union[Iterable[logging.Logger], LogStore],
-            separate_log_panes: bool = False,
-            log_level_name: Optional[str] = None) -> Optional[LogPane]:
-        """Add the Log pane as a handler for this logger instance."""
-
-        existing_log_pane = None
-        # Find an existing LogPane with the same window_title.
-        for pane in self.window_manager.active_panes():
-            if isinstance(pane, LogPane) and pane.pane_title() == window_title:
-                existing_log_pane = pane
-                break
-
-        log_store: Optional[LogStore] = None
-        if isinstance(logger_instances, LogStore):
-            log_store = logger_instances
-
-        if not existing_log_pane or separate_log_panes:
-            existing_log_pane = self._create_log_pane(title=window_title,
-                                                      log_store=log_store)
-
-        if isinstance(logger_instances, list):
-            for logger in logger_instances:
-                _add_log_handler_to_pane(logger, existing_log_pane,
-                                         log_level_name)
-
-        self.refresh_layout()
-        return existing_log_pane
-
-    def _user_code_thread_entry(self):
-        """Entry point for the user code thread."""
-        asyncio.set_event_loop(self.user_code_loop)
-        self.user_code_loop.run_forever()
-
-    def start_user_code_thread(self):
-        """Create a thread for running user code so the UI isn't blocked."""
-        thread = Thread(target=self._user_code_thread_entry,
-                        args=(),
-                        daemon=True)
-        thread.start()
-
-    def _update_help_window(self):
-        """Generate the help window text based on active pane keybindings."""
-        # Add global mouse bindings to the help text.
-        mouse_functions = {
-            'Focus pane, menu or log line.': ['Click'],
-            'Scroll current window.': ['Scroll wheel'],
-        }
-
-        self.keybind_help_window.add_custom_keybinds_help_text(
-            'Global Mouse', mouse_functions)
-
-        # Add global key bindings to the help text.
-        self.keybind_help_window.add_keybind_help_text('Global',
-                                                       self.key_bindings)
-
-        self.keybind_help_window.add_keybind_help_text(
-            'Window Management', self.window_manager.key_bindings)
-
-        # Add activated plugin key bindings to the help text.
-        for pane in self.window_manager.active_panes():
-            for key_bindings in pane.get_all_key_bindings():
-                help_section_title = pane.__class__.__name__
-                if isinstance(key_bindings, KeyBindings):
-                    self.keybind_help_window.add_keybind_help_text(
-                        help_section_title, key_bindings)
-                elif isinstance(key_bindings, dict):
-                    self.keybind_help_window.add_custom_keybinds_help_text(
-                        help_section_title, key_bindings)
-
-        self.keybind_help_window.generate_help_text()
-
-    def toggle_log_line_wrapping(self):
-        """Menu item handler to toggle line wrapping of all log panes."""
-        for pane in self.window_manager.active_panes():
-            if isinstance(pane, LogPane):
-                pane.toggle_wrap_lines()
-
-    def focused_window(self):
-        """Return the currently focused window."""
-        return self.application.layout.current_window
-
-    def command_runner_is_open(self) -> bool:
-        return self.command_runner.show_dialog
-
-    def command_runner_last_focused_pane(self) -> Any:
-        return self.command_runner.last_focused_pane
-
-    def modal_window_is_open(self):
-        """Return true if any modal window or dialog is open."""
-        if self.app_help_text:
-            return (self.app_help_window.show_window
-                    or self.keybind_help_window.show_window
-                    or self.prefs_file_window.show_window
-                    or self.user_guide_window.show_window
-                    or self.quit_dialog.show_dialog
-                    or self.command_runner.show_dialog)
-        return (self.keybind_help_window.show_window
-                or self.prefs_file_window.show_window
-                or self.user_guide_window.show_window
-                or self.quit_dialog.show_dialog
-                or self.command_runner.show_dialog)
-
-    def exit_console(self):
-        """Quit the console prompt_toolkit application UI."""
-        self.application.exit()
-
-    def redraw_ui(self):
-        """Redraw the prompt_toolkit UI."""
-        if hasattr(self, 'application'):
-            # Thread safe way of sending a repaint trigger to the input event
-            # loop.
-            self.application.invalidate()
-
-    async def run(self, test_mode=False):
-        """Start the prompt_toolkit UI."""
-        if test_mode:
-            background_log_task = asyncio.create_task(self.log_forever())
-
-        # Repl pane has focus by default, if it's hidden switch focus to another
-        # visible pane.
-        if not self.repl_pane.show_pane:
-            self.window_manager.focus_first_visible_pane()
-
-        try:
-            unused_result = await self.application.run_async(
-                set_exception_handler=True)
-        finally:
-            if test_mode:
-                background_log_task.cancel()
-
-    async def log_forever(self):
-        """Test mode async log generator coroutine that runs forever."""
-        message_count = 0
-        # Sample log line format:
-        # Log message [=         ] # 100
-
-        # Fake module column names.
-        module_names = ['APP', 'RADIO', 'BAT', 'USB', 'CPU']
-        while True:
-            if message_count > 32 or message_count < 2:
-                await asyncio.sleep(1)
-            bar_size = 10
-            position = message_count % bar_size
-            bar_content = " " * (bar_size - position - 1) + "="
-            if position > 0:
-                bar_content = "=".rjust(position) + " " * (bar_size - position)
-            new_log_line = 'Log message [{}] # {}'.format(
-                bar_content, message_count)
-            if message_count % 10 == 0:
-                new_log_line += (
-                    ' Lorem ipsum \033[34m\033[1mdolor sit amet\033[0m'
-                    ', consectetur '
-                    'adipiscing elit.') * 8
-            if message_count % 11 == 0:
-                new_log_line += ' '
-                new_log_line += (
-                    '[PYTHON] START\n'
-                    'In []: import time;\n'
-                    '        def t(s):\n'
-                    '            time.sleep(s)\n'
-                    '            return "t({}) seconds done".format(s)\n\n')
-
-            module_name = module_names[message_count % len(module_names)]
-            _FAKE_DEVICE_LOG.info(new_log_line,
-                                  extra=dict(extra_metadata_fields=dict(
-                                      module=module_name, file='fake_app.cc')))
-            message_count += 1
-
-
-# TODO(tonymd): Remove this alias when not used by downstream projects.
-def embed(
-    *args,
-    **kwargs,
-) -> None:
-    """PwConsoleEmbed().embed() alias."""
-    # Import here to avoid circular dependency
-    from pw_console.embed import PwConsoleEmbed  # pylint: disable=import-outside-toplevel
-    console = PwConsoleEmbed(*args, **kwargs)
-    console.embed()
diff --git a/pw_console/py/pw_console/console_prefs.py b/pw_console/py/pw_console/console_prefs.py
deleted file mode 100644
index 1fda908..0000000
--- a/pw_console/py/pw_console/console_prefs.py
+++ /dev/null
@@ -1,294 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""pw_console preferences"""
-
-import os
-from pathlib import Path
-from typing import Dict, Callable, List, Union
-
-from prompt_toolkit.key_binding import KeyBindings
-import yaml
-
-from pw_console.style import get_theme_colors
-from pw_console.key_bindings import DEFAULT_KEY_BINDINGS
-from pw_console.yaml_config_loader_mixin import YamlConfigLoaderMixin
-
-_DEFAULT_REPL_HISTORY: Path = Path.home() / '.pw_console_history'
-_DEFAULT_SEARCH_HISTORY: Path = Path.home() / '.pw_console_search'
-
-_DEFAULT_CONFIG = {
-    # History files
-    'repl_history': _DEFAULT_REPL_HISTORY,
-    'search_history': _DEFAULT_SEARCH_HISTORY,
-    # Appearance
-    'ui_theme': 'dark',
-    'code_theme': 'pigweed-code',
-    'swap_light_and_dark': False,
-    'spaces_between_columns': 2,
-    'column_order_omit_unspecified_columns': False,
-    'column_order': [],
-    'column_colors': {},
-    'show_python_file': False,
-    'show_python_logger': False,
-    'show_source_file': False,
-    'hide_date_from_log_time': False,
-    # Window arrangement
-    'windows': {},
-    'window_column_split_method': 'vertical',
-    'command_runner': {
-        'width': 80,
-        'height': 10,
-        'position': {
-            'top': 3
-        },
-    },
-    'key_bindings': DEFAULT_KEY_BINDINGS,
-}
-
-_DEFAULT_PROJECT_FILE = Path('$PW_PROJECT_ROOT/.pw_console.yaml')
-_DEFAULT_PROJECT_USER_FILE = Path('$PW_PROJECT_ROOT/.pw_console.user.yaml')
-_DEFAULT_USER_FILE = Path('$HOME/.pw_console.yaml')
-
-
-class UnknownWindowTitle(Exception):
-    """Exception for window titles not present in the window manager layout."""
-
-
-class EmptyWindowList(Exception):
-    """Exception for window lists with no content."""
-
-
-def error_unknown_window(window_title: str,
-                         existing_pane_titles: List[str]) -> None:
-    """Raise an error when the window config has an unknown title.
-
-    If a window title does not already exist on startup it must have a loggers:
-    or duplicate_of: option set."""
-
-    pane_title_text = '  ' + '\n  '.join(existing_pane_titles)
-    existing_pane_title_example = 'Window Title'
-    if existing_pane_titles:
-        existing_pane_title_example = existing_pane_titles[0]
-    raise UnknownWindowTitle(
-        f'\n\n"{window_title}" does not exist.\n'
-        'Existing windows include:\n'
-        f'{pane_title_text}\n'
-        'If this window should be a duplicate of one of the above,\n'
-        f'add "duplicate_of: {existing_pane_title_example}" to your config.\n'
-        'If this is a brand new window, include a "loggers:" section.\n'
-        'See also: '
-        'https://pigweed.dev/pw_console/docs/user_guide.html#example-config')
-
-
-def error_empty_window_list(window_list_title: str, ) -> None:
-    """Raise an error if a window list is empty."""
-
-    raise EmptyWindowList(
-        f'\n\nError: The window layout heading "{window_list_title}" contains '
-        'no windows.\n'
-        'See also: '
-        'https://pigweed.dev/pw_console/docs/user_guide.html#example-config')
-
-
-class ConsolePrefs(YamlConfigLoaderMixin):
-    """Pigweed Console preferences storage class."""
-
-    # pylint: disable=too-many-public-methods
-
-    def __init__(
-        self,
-        project_file: Union[Path, bool] = _DEFAULT_PROJECT_FILE,
-        project_user_file: Union[Path, bool] = _DEFAULT_PROJECT_USER_FILE,
-        user_file: Union[Path, bool] = _DEFAULT_USER_FILE,
-    ) -> None:
-        self.config_init(
-            config_section_title='pw_console',
-            project_file=project_file,
-            project_user_file=project_user_file,
-            user_file=user_file,
-            default_config=_DEFAULT_CONFIG,
-            environment_var='PW_CONSOLE_CONFIG_FILE',
-        )
-
-        self.registered_commands = DEFAULT_KEY_BINDINGS
-        self.registered_commands.update(self.user_key_bindings)
-
-    @property
-    def ui_theme(self) -> str:
-        return self._config.get('ui_theme', '')
-
-    def set_ui_theme(self, theme_name: str):
-        self._config['ui_theme'] = theme_name
-
-    @property
-    def theme_colors(self):
-        return get_theme_colors(self.ui_theme)
-
-    @property
-    def code_theme(self) -> str:
-        return self._config.get('code_theme', '')
-
-    @property
-    def swap_light_and_dark(self) -> bool:
-        return self._config.get('swap_light_and_dark', False)
-
-    @property
-    def repl_history(self) -> Path:
-        history = Path(self._config['repl_history'])
-        history = Path(os.path.expandvars(str(history.expanduser())))
-        return history
-
-    @property
-    def search_history(self) -> Path:
-        history = Path(self._config['search_history'])
-        history = Path(os.path.expandvars(str(history.expanduser())))
-        return history
-
-    @property
-    def spaces_between_columns(self) -> int:
-        spaces = self._config.get('spaces_between_columns', 2)
-        assert isinstance(spaces, int) and spaces > 0
-        return spaces
-
-    @property
-    def omit_unspecified_columns(self) -> bool:
-        return self._config.get('column_order_omit_unspecified_columns', False)
-
-    @property
-    def hide_date_from_log_time(self) -> bool:
-        return self._config.get('hide_date_from_log_time', False)
-
-    @property
-    def show_python_file(self) -> bool:
-        return self._config.get('show_python_file', False)
-
-    @property
-    def show_source_file(self) -> bool:
-        return self._config.get('show_source_file', False)
-
-    @property
-    def show_python_logger(self) -> bool:
-        return self._config.get('show_python_logger', False)
-
-    def toggle_bool_option(self, name: str):
-        existing_setting = self._config[name]
-        assert isinstance(existing_setting, bool)
-        self._config[name] = not existing_setting
-
-    @property
-    def column_order(self) -> list:
-        return self._config.get('column_order', [])
-
-    def column_style(self,
-                     column_name: str,
-                     column_value: str,
-                     default='') -> str:
-        column_colors = self._config.get('column_colors', {})
-        column_style = default
-
-        if column_name in column_colors:
-            # If key exists but doesn't have any values.
-            if not column_colors[column_name]:
-                return default
-            # Check for user supplied default.
-            column_style = column_colors[column_name].get('default', default)
-            # Check for value specific color, otherwise use the default.
-            column_style = column_colors[column_name].get(
-                column_value, column_style)
-        return column_style
-
-    @property
-    def window_column_split_method(self) -> str:
-        return self._config.get('window_column_split_method', 'vertical')
-
-    @property
-    def windows(self) -> dict:
-        return self._config.get('windows', {})
-
-    @property
-    def window_column_modes(self) -> list:
-        return list(column_type for column_type in self.windows.keys())
-
-    @property
-    def command_runner_position(self) -> Dict[str, int]:
-        position = self._config.get('command_runner',
-                                    {}).get('position', {'top': 3})
-        return {
-            key: value
-            for key, value in position.items()
-            if key in ['top', 'bottom', 'left', 'right']
-        }
-
-    @property
-    def command_runner_width(self) -> int:
-        return self._config.get('command_runner', {}).get('width', 80)
-
-    @property
-    def command_runner_height(self) -> int:
-        return self._config.get('command_runner', {}).get('height', 10)
-
-    @property
-    def user_key_bindings(self) -> Dict[str, List[str]]:
-        return self._config.get('key_bindings', {})
-
-    def current_config_as_yaml(self) -> str:
-        yaml_options = dict(sort_keys=True,
-                            default_style='',
-                            default_flow_style=False)
-
-        title = {'config_title': 'pw_console'}
-        text = '\n'
-        text += yaml.safe_dump(title, **yaml_options)  # type: ignore
-
-        keys = {'key_bindings': self.registered_commands}
-        text += '\n'
-        text += yaml.safe_dump(keys, **yaml_options)  # type: ignore
-
-        return text
-
-    @property
-    def unique_window_titles(self) -> set:
-        titles = []
-        for window_list_title, column in self.windows.items():
-            if not column:
-                error_empty_window_list(window_list_title)
-
-            for window_key_title, window_dict in column.items():
-                window_options = window_dict if window_dict else {}
-                # Use 'duplicate_of: Title' if it exists, otherwise use the key.
-                titles.append(
-                    window_options.get('duplicate_of', window_key_title))
-        return set(titles)
-
-    def get_function_keys(self, name: str) -> List:
-        """Return the keys for the named function."""
-        try:
-            return self.registered_commands[name]
-        except KeyError as error:
-            raise KeyError('Unbound key function: {}'.format(name)) from error
-
-    def register_named_key_function(self, name: str,
-                                    default_bindings: List[str]) -> None:
-        self.registered_commands[name] = default_bindings
-
-    def register_keybinding(self, name: str, key_bindings: KeyBindings,
-                            **kwargs) -> Callable:
-        """Apply registered keys for the given named function."""
-        def decorator(handler: Callable) -> Callable:
-            "`handler` is a callable or Binding."
-            for keys in self.get_function_keys(name):
-                key_bindings.add(*keys.split(' '), **kwargs)(handler)
-            return handler
-
-        return decorator
diff --git a/pw_console/py/pw_console/docs/user_guide.rst b/pw_console/py/pw_console/docs/user_guide.rst
deleted file mode 100644
index 3f1edcb..0000000
--- a/pw_console/py/pw_console/docs/user_guide.rst
+++ /dev/null
@@ -1,948 +0,0 @@
-.. _module-pw_console-user_guide:
-
-User Guide
-==========
-
-.. seealso::
-
-   This guide can be viewed online at:
-   https://pigweed.dev/pw_console/py/pw_console/docs/user_guide.html
-
-
-The Pigweed Console provides a Python repl (read eval print loop) and log viewer
-in a single-window terminal based interface.
-
-
-Starting the Console
---------------------
-
-::
-
-  pw rpc -s localhost:33000 --proto-globs pw_rpc/echo.proto
-
-
-Exiting
-~~~~~~~
-
-There are a few ways to exit the Pigweed Console user interface:
-
-1.  Click the :guilabel:`[File]` menu and then :guilabel:`Exit`.
-2.  Type ``quit`` or ``exit`` in the Python Input window and press :kbd:`Enter`.
-3.  Press :kbd:`Ctrl-d` once to show the quit confirmation dialog. From there
-    press :kbd:`Ctrl-d` a second time or :kbd:`y` will exit.
-4.  Press :kbd:`Ctrl-x` quickly followed by :kbd:`Ctrl-c` will exit without
-    confirmation.
-5.  Press :kbd:`Ctrl-p` to search for commands, type ``exit``, then press
-    :kbd:`Enter`.
-
-
-Interface Layout
-----------------
-
-On startup the console will display multiple windows one on top of the other.
-
-::
-
-  +---------------------------------------------------------+
-  | [File] [Edit] [View] [Window] [Help]    Pigweed Console |
-  +=========================================================+
-  |                                                         |
-  |                                                         |
-  |                                                         |
-  | Log Window                                              |
-  +=========================================================+
-  |                                                         |
-  |                                                         |
-  | Python Results                                          |
-  +- - - - - - - - - - - - - - - - - - - - - - - - - - - - -+
-  |                                                         |
-  | Python Input                                            |
-  +---------------------------------------------------------+
-
-
-Navigation
-----------
-
-All menus, windows, and toolbar buttons can be clicked on. Scrolling with the
-mouse wheel should work too. This requires that your terminal is able to send
-mouse events.
-
-
-Navigation with the Keyboard
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The main menu can be searched by pressing :kbd:`Ctrl-p`. This opens a fuzzy
-search box containing all main menu item actions.
-
-Words separated by spaces are used to narrow down the match results. The order
-each word is entered does not matter.
-
-.. figure:: /pw_console/images/command_runner_main_menu.svg
-  :alt: Main menu item search dialog.
-
-============================================  =====================
-Function                                      Keys
-============================================  =====================
-Open main menu search                         :kbd:`Ctrl-p`
-Cancel search                                 :kbd:`Ctrl-c`
-Run selected item                             :kbd:`Enter`
-
-Select next item                              :kbd:`Tab`
-                                              :kbd:`Down`
-Select previous item                          :kbd:`Shift-Tab`
-                                              :kbd:`Up`
-============================================  =====================
-
-Switching Focus
-~~~~~~~~~~~~~~~
-
-Clicking on any window will focus on it. Alternatively, the key bindings below
-will switch focus.
-
-============================================  =====================
-Function                                      Keys
-============================================  =====================
-Switch focus to the next window or tab        :kbd:`Ctrl-Alt-n`
-Switch focus to the previous window or tab    :kbd:`Ctrl-Alt-p`
-
-Switch focus to the next UI element           :kbd:`Shift-Tab`
-                                              :kbd:`Ctrl-Right`
-Switch focus to the previous UI element       :kbd:`Ctrl-Left`
-
-Move selection in the main menu               :kbd:`Up`
-                                              :kbd:`Down`
-                                              :kbd:`Left`
-                                              :kbd:`Right`
-============================================  =====================
-
-
-Toolbars
-~~~~~~~~
-
-Log toolbar functions are clickable. You can also press the keyboard
-shortcut highlighted in blue:
-
-::
-
-        / : Search  f : [x] Follow  t : [x] Table  w : [ ] Wrap  C : Clear
-
-
-Log Window
-~~~~~~~~~~
-
-Log Window Navigation
-^^^^^^^^^^^^^^^^^^^^^
-
-============================================  =====================
-Function                                      Keys
-============================================  =====================
-Move cursor up 1 line                         :kbd:`Up`
-                                              :kbd:`k`
-
-Move cursor down 1 line                       :kbd:`Down`
-                                              :kbd:`j`
-
-Move cursor up 5 lines                        :guilabel:`Mouse Wheel Up`
-Move cursor down 5 lines                      :guilabel:`Mouse Wheel Down`
-
-Move cursor up one page                       :kbd:`PageUp`
-Move cursor down one page                     :kbd:`PageDown`
-
-Jump to the beginning                         :kbd:`g`
-Jump to the end                               :kbd:`G`
-============================================  =====================
-
-Log Line Selection
-^^^^^^^^^^^^^^^^^^
-
-============================================  =====================
-Function                                      Keys
-============================================  =====================
-Select the next log line                      :kbd:`Shift-Down`
-Select the previous log line                  :kbd:`Shift-Up`
-
-Select a range of log lines                   :guilabel:`Left Mouse Drag`
-
-Select all lines                              :kbd:`Ctrl-a`
-Clear Selection                               :kbd:`Ctrl-c`
-============================================  =====================
-
-When making log line selections a popup will appear in the upper right of the log
-window showing the number of lines selected along with copy and export options.
-
-::
-
-  +--------------------------------------------------------+
-  | 32 Selected  Format:  [✓] Table  [ ] Markdown          |
-  | [ Cancel ]  [ Select All ]   [ Save as File ] [ Copy ] |
-  +--------------------------------------------------------+
-
-
-Log Window Functions
-^^^^^^^^^^^^^^^^^^^^
-
-============================================  =====================
-Function                                      Keys
-============================================  =====================
-Open the search bar                           :kbd:`/`
-                                              :kbd:`Ctrl-f`
-Save a copy of logs to a file                 :kbd:`Ctrl-o`
-Toggle line following.                        :kbd:`f`
-Toggle table view.                            :kbd:`t`
-Toggle line wrapping.                         :kbd:`w`
-Clear log pane history.                       :kbd:`C`
-============================================  =====================
-
-Log Window Management
-^^^^^^^^^^^^^^^^^^^^^^^
-
-============================================  =====================
-Function                                      Keys
-============================================  =====================
-Duplicate this log pane.                      :kbd:`Insert`
-Remove log pane.                              :kbd:`Delete`
-============================================  =====================
-
-Log Searching
-^^^^^^^^^^^^^
-
-============================================  =====================
-Function                                      Keys
-============================================  =====================
-Open the search bar                           :kbd:`/`
-                                              :kbd:`Ctrl-f`
-Navigate search term history                  :kbd:`Up`
-                                              :kbd:`Down`
-Start the search and highlight matches        :kbd:`Enter`
-Close the search bar without searching        :kbd:`Ctrl-c`
-============================================  =====================
-
-Here is a view of the search bar:
-
-::
-
-  +--------------------------------------------------------------------------+
-  | Search   Column:All Ctrl-t   [ ] Invert Ctrl-v   Matcher:REGEX Ctrl-n    |
-  | /                                            Search Enter  Cancel Ctrl-c |
-  +--------------------------------------------------------------------------+
-
-Across the top are various functions with keyboard shortcuts listed. Each of
-these are clickable with the mouse.
-
-**Search Parameters**
-
-- ``Column:All`` Change the part of the log message to match on. For example:
-  ``All``, ``Message`` or any extra metadata column.
-
-- ``Invert`` match. Find lines that don't match the entered text.
-
-- ``Matcher``: How the search input should be interpreted.
-
-  - ``REGEX``: Treat input text as a regex.
-
-  - ``STRING``: Treat input as a plain string. Any regex characters will be
-    escaped when search is performed.
-
-  - ``FUZZY``: input text is split on spaces using the ``.*`` regex. For
-    example if you search for ``idle run`` the resulting search regex used
-    under the hood is ``(idle)(.*?)(run)``. This would match both of these
-    lines:
-
-    .. code-block:: text
-
-       Idle task is running
-       Idle thread is running
-
-**Active Search Shortcuts**
-
-When a search is started the bar will close, log follow mode is disabled and all
-matches will be highlighted.  At this point a few extra keyboard shortcuts are
-available.
-
-============================================  =====================
-Function                                      Keys
-============================================  =====================
-Move to the next search result                :kbd:`n`
-                                              :kbd:`Ctrl-g`
-                                              :kbd:`Ctrl-s`
-Move to the previous search result            :kbd:`N`
-                                              :kbd:`Ctrl-r`
-Clear active search                           :kbd:`Ctrl-c`
-Creates a filter using the active search      :kbd:`Ctrl-Alt-f`
-Reset all active filters.                     :kbd:`Ctrl-Alt-r`
-============================================  =====================
-
-
-Log Filtering
-^^^^^^^^^^^^^
-
-Log filtering allows you to limit what log lines appear in any given log
-window. Filters can be added from the currently active search or directly in the
-search bar.
-
-- With the search bar **open**:
-
-  Type something to search for then press :kbd:`Ctrl-Alt-f` or click on
-  :guilabel:`Add Filter`.
-
-- With the search bar **closed**:
-
-  Press :kbd:`Ctrl-Alt-f` to use the current search term as a filter.
-
-When a filter is active the ``Filters`` toolbar will appear at the bottom of the
-log window. For example, here are some logs with one active filter for
-``lorem ipsum``.
-
-::
-
-  +------------------------------------------------------------------------------+
-  | Time               Lvl  Module  Message                                      |
-  +------------------------------------------------------------------------------+
-  | 20210722 15:38:14  INF  APP     Log message # 270 Lorem ipsum dolor sit amet |
-  | 20210722 15:38:24  INF  APP     Log message # 280 Lorem ipsum dolor sit amet |
-  | 20210722 15:38:34  INF  APP     Log message # 290 Lorem ipsum dolor sit amet |
-  | 20210722 15:38:44  INF  APP     Log message # 300 Lorem ipsum dolor sit amet |
-  | 20210722 15:38:54  INF  APP     Log message # 310 Lorem ipsum dolor sit amet |
-  | 20210722 15:39:04  INF  APP     Log message # 320 Lorem ipsum dolor sit amet |
-  +------------------------------------------------------------------------------+
-  |  Filters   <lorem ipsum (X)>  Ctrl-Alt-r : Clear Filters                     |
-  +------------------------------------------------------------------------------+
-  |   Logs   / : Search  f : [x] Follow  t : [x] Table  w : [ ] Wrap  C : Clear  |
-  +------------------------------------------------------------------------------+
-
-**Stacking Filters**
-
-Adding a second filter on the above logs for ``# 2`` would update the filter
-toolbar to show:
-
-::
-
-  +------------------------------------------------------------------------------+
-  | Time               Lvl  Module  Message                                      |
-  +------------------------------------------------------------------------------+
-  |                                                                              |
-  |                                                                              |
-  |                                                                              |
-  | 20210722 15:38:14  INF  APP     Log message # 270 Lorem ipsum dolor sit amet |
-  | 20210722 15:38:24  INF  APP     Log message # 280 Lorem ipsum dolor sit amet |
-  | 20210722 15:38:34  INF  APP     Log message # 290 Lorem ipsum dolor sit amet |
-  +------------------------------------------------------------------------------+
-  |  Filters   <lorem ipsum (X)>  <# 2 (X)>  Ctrl-Alt-r : Clear Filters          |
-  +------------------------------------------------------------------------------+
-  |   Logs   / : Search  f : [x] Follow  t : [x] Table  w : [ ] Wrap  C : Clear  |
-  +------------------------------------------------------------------------------+
-
-Any filter listed in the Filters toolbar and can be individually removed by
-clicking on the red ``(X)`` text.
-
-
-Python Window
-~~~~~~~~~~~~~
-
-
-Running Code in the Python Repl
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
--  Type code and hit :kbd:`Enter` to run.
--  If multiple lines are used, move the cursor to the end and press
-   :kbd:`Enter` twice.
--  :kbd:`Up` / :kbd:`Down` Navigate command history
--  :kbd:`Ctrl-r` Start reverse history searching
--  :kbd:`Ctrl-c` Erase the input buffer
--  :kbd:`Ctrl-v` Paste text from the clipboard to the input buffer
--  :kbd:`Ctrl-Alt-c` Copy the Python Output to the system clipboard
-
-   -  If the input buffer is empty:
-      :kbd:`Ctrl-c` cancels any currently running Python commands.
-
--  :kbd:`F2` Open the python repl settings (from
-   `ptpython <https://github.com/prompt-toolkit/ptpython>`__). This
-   works best in vertical split mode.
-
-   -  To exit: hit :kbd:`F2` again.
-   -  Navigate options with the arrow keys, Enter will close the menu.
-
--  :kbd:`F3` Open the python repl history (from
-   `ptpython <https://github.com/prompt-toolkit/ptpython>`__).
-
-   -  To exit: hit :kbd:`F3` again.
-   -  Left side shows previously entered commands
-   -  Use arrow keys to navigate.
-   -  :kbd:`Space` to select as many lines you want to use
-
-      -  Selected lines will be appended to the right side.
-
-   -  :kbd:`Enter` to accept the right side text, this will be inserted
-      into the repl.
-
-
-Copy & Pasting
-~~~~~~~~~~~~~~
-
-Copying Text
-^^^^^^^^^^^^
-
-Text can be copied from the Log and Python windows when they are in focus with
-these keybindings.
-
-============================================  =====================
-Function                                      Keys
-============================================  =====================
-Copy Logs from the focused log window         :kbd:`Ctrl-c`
-Copy Python Output if window is focused       :kbd:`Ctrl-Alt-c`
-============================================  =====================
-
-Text will be put in the host computer's system clipboard using the
-`pyperclip package <https://pypi.org/project/pyperclip/>`__.
-
-The above functions can also be accessed by clicking on the toolbar help text or
-accessed under the :guilabel:`[Edit]` menu.
-
-If you need to copy text from any other part of the UI you will have to use your
-terminal's built in text selection:
-
-**Linux**
-
-- Holding :kbd:`Shift` and dragging the mouse in most terminals.
-
-**Mac**
-
-- **Apple Terminal**:
-
-  Hold :kbd:`Fn` and drag the mouse
-
-- **iTerm2**:
-
-  Hold :kbd:`Cmd+Option` and drag the mouse
-
-**Windows**
-
-- **Git CMD** (included in `Git for Windows <https://git-scm.com/downloads>`__)
-
-  1. Click on the Git window icon in the upper left of the title bar
-  2. Click ``Edit`` then ``Mark``
-  3. Drag the mouse to select text and press Enter to copy.
-
-- **Windows Terminal**
-
-  1. Hold :kbd:`Shift` and drag the mouse to select text
-  2. Press :kbd:`Ctrl-Shift-C` to copy.
-
-Pasting Text
-^^^^^^^^^^^^
-
-Text can be pasted into the Python Input window from the system clipboard with
-:kbd:`Ctrl-v`.
-
-If you are using the console on a separate machine (over an ssh connection for
-example) then pasting will use that machine's clipboard. This may not be the
-computer where you copied the text. In that case you will need to use your
-terminal emulator's paste function. How to do this depends on what terminal you
-are using and on which OS. Here's how on various platforms:
-
-**Linux**
-
-- **XTerm**
-
-  :kbd:`Shift-Insert` pastes text
-
-- **Gnome Terminal**
-
-  :kbd:`Ctrl-Shift-V` pastes text
-
-**Windows**
-
-- **Git CMD** (included in `Git for Windows <https://git-scm.com/downloads>`__)
-
-  1. Click on the Git icon in the upper left of the windows title bar and open
-     ``Properties``.
-  2. Checkmark the option ``Use Ctrl+Shift+C/V as Copy Paste`` and hit ok.
-  3. Then use :kbd:`Ctrl-Shift-V` to paste.
-
-- **Windows Terminal**
-
-  1. :kbd:`Ctrl-Shift-V` pastes text.
-  2. :kbd:`Shift-RightClick` also pastes text.
-
-
-Window Management
-~~~~~~~~~~~~~~~~~
-
-Any window can be hidden by clicking the :guilabel:`[x] Show Window` checkbox
-under the :guilabel:`[Window]` menu.
-
-The active window can be moved and resized with the following keys. There are
-also menu options under :guilabel:`[View]` for the same actions. Additionally,
-windows can be resized with the mouse by click dragging on the :guilabel:`====`
-text on the far right side of any toolbar.
-
-============================================  =====================
-Function                                      Keys
-============================================  =====================
-Enlarge window height                         :kbd:`Alt-=`
-Shrink window height                          :kbd:`Alt--`
-                                              (:kbd:`Alt` and :kbd:`Minus`)
-Enlarge vertical split width                  :kbd:`Alt-,`
-Shrink vertical split width                   :kbd:`Alt-.`
-Reset window sizes                            :kbd:`Ctrl-u`
-
-Move window up                                :kbd:`Ctrl-Alt-Up`
-Move window down                              :kbd:`Ctrl-Alt-Down`
-Move window left                              :kbd:`Ctrl-Alt-Left`
-Move window right                             :kbd:`Ctrl-Alt-Right`
-============================================  =====================
-
-Moving windows left and right will create a new vertical splits. Each vertical
-stack can contain multiple windows and show windows as a stack or tabbed
-view.
-
-For example here we have 3 window panes in a single stack. If you focus on Log
-Window 1 and move it to the right a new stack is formed in a vertical
-split. This can be done repeatedly to form additional window stacks.
-
-::
-
-  +----------------------------------+     +----------------------------------+
-  | [File] [View] [Window]   Console |     | [File] [View] [Window]   Console |
-  +==================================+     +================+=================+
-  | Log Window 1                     |     | Log Window 2   | Log Window 1    |
-  |                                  |     |                |                 |
-  +==================================+     |                |                 |
-  | Log Window 2                     |     |                |                 |
-  |                                  |     |                |                 |
-  +==================================+     +================+                 |
-  |                                  |     |                |                 |
-  |                                  |     |                |                 |
-  | Python Results                   |     | Python Results |                 |
-  |                                  |     |                |                 |
-  | Python Input                     |     | Python Input   |                 |
-  +----------------------------------+     +----------------+-----------------+
-
-Color Depth
------------
-
-Some terminals support full 24-bit color and pw console will use that by default
-in most cases. One notable exeception is Apple Terminal on MacOS which supports
-256 colors only. `iTerm2 <https://iterm2.com/>`__ is a good MacOS alternative
-that supports 24-bit colors.
-
-To force a particular color depth: set one of these environment variables before
-launching the console. For ``bash`` and ``zsh`` shells you can use the
-``export`` command.
-
-::
-
-   # 1 bit | Black and white
-   export PROMPT_TOOLKIT_COLOR_DEPTH=DEPTH_1_BIT
-   # 4 bit | ANSI colors
-   export PROMPT_TOOLKIT_COLOR_DEPTH=DEPTH_4_BIT
-   # 8 bit | 256 colors
-   export PROMPT_TOOLKIT_COLOR_DEPTH=DEPTH_8_BIT
-   # 24 bit | True colors
-   export PROMPT_TOOLKIT_COLOR_DEPTH=DEPTH_24_BIT
-
-For Windows command prompt (``cmd.exe``) use the ``set`` command:
-
-::
-
-   set PROMPT_TOOLKIT_COLOR_DEPTH=DEPTH_1_BIT
-   set PROMPT_TOOLKIT_COLOR_DEPTH=DEPTH_4_BIT
-   set PROMPT_TOOLKIT_COLOR_DEPTH=DEPTH_8_BIT
-   set PROMPT_TOOLKIT_COLOR_DEPTH=DEPTH_24_BIT
-
-Configuration
--------------
-
-Pigweed Console supports loading project and user specific settings stored in
-YAML files. Each file follows the same format and are loaded one after the
-other. Any setting specified multiple locations will be overridden by files
-loaded later in the startup sequence.
-
-1. ``$PW_PROJECT_ROOT/.pw_console.yaml``
-
-   Project level config file. This is intended to be a file living somewhere
-   under a project folder and is checked into version control. It serves as a
-   base config for all users to inherit from.
-
-2. ``$PW_PROJECT_ROOT/.pw_console.user.yaml``
-
-   User's personal config file for a specific project. This can be a file that
-   lives in a project folder but is git-ignored and not checked into version
-   control. This lets users change settings applicable to this project only.
-
-3. ``$HOME/.pw_console.yaml``
-
-   A global user based config file. This file is located in the user's home
-   directory and settings here apply to all projects. This is a good location to
-   set appearance options such as:
-
-   .. code-block:: yaml
-
-      ui_theme: nord
-      code_theme: pigweed-code
-      swap_light_and_dark: False
-      spaces_between_columns: 2
-      hide_date_from_log_time: False
-
-It's also possible to specify a config file via a shell environment variable. If
-this method is used only this config file is applied. Project and user config
-file options will not be set.
-
-::
-
-   export PW_CONSOLE_CONFIG_FILE=/home/.config/pw_console/config.yaml
-
-Example Config
-~~~~~~~~~~~~~~
-
-.. code-block:: yaml
-
-   ---
-   config_title: pw_console
-
-   # Repl and Search History files
-   # Setting these to a file located $PW_PROJECT_ROOT is a
-   # good way to make Python repl history project specific.
-
-   # Default: $HOME/.pw_console_history
-   repl_history: $PW_PROJECT_ROOT/.pw_console_history
-
-   # Default: $HOME/.pw_console_search
-   search_history: $PW_PROJECT_ROOT/.pw_console_search
-
-   # Theme Settings
-
-   # Default: dark
-   ui_theme: high-contrast-dark
-
-   # Default: pigweed-code
-   code_theme: material
-
-   # Default: False
-   swap_light_and_dark: False
-
-   # Log Table View Settings
-
-   # Number of spaces to insert between columns
-   # Default: 2
-   spaces_between_columns: 2
-
-   # Hide the year month and day from the time column.
-   hide_date_from_log_time: False
-
-   # Show the Python file and line number responsible for creating log messages.
-   show_python_file: False
-   # Show the Python logger responsible for creating log messages.
-   show_python_logger: False
-   # Show the 'file' metadata column.
-   show_source_file: False
-
-   # Custom Column Ordering
-   # By default columns are ordered as:
-   #   time, level, metadata1, metadata2, ..., message
-   # The log message is always the last value and not required in this list.
-   column_order:
-     # Column name
-     - time
-     - level
-     - metadata1
-     - metadata2
-
-   # If True, any metadata field not listed above in 'column_order'
-   # will be hidden in table view.
-   column_order_omit_unspecified_columns: False
-
-   # Unique Colors for Column Values
-   #   Color format: 'bg:#BG-HEX #FG-HEX STYLE'
-   # All parts are optional.
-   # Empty strings will leave styling unchanged.
-   column_colors:
-     # Column name
-     time:
-     level:
-     metadata1:
-       # Field values
-       # Default will be applied if no match found
-       default: '#98be65'
-       BATTERY: 'bg:#6699cc #000000 bold'
-       CORE1: 'bg:#da8548 #000000 bold'
-       CORE2: 'bg:#66cccc #000000 bold'
-     metadata2:
-       default: '#ffcc66'
-       APP: 'bg:#ff6c6b #000000 bold'
-       WIFI: '#555555'
-
-   # Each window column is normally aligned side by side in vertical splits. You
-   # can change this to one group of windows on top of the other with horizontal
-   # splits using this method
-
-   # Default: vertical
-   window_column_split_method: vertical
-
-   # Window Layout
-   windows:
-     # First window column (vertical split)
-     # Each split should have a unique name and include either
-     # 'stacked' or 'tabbed' to select a window pane display method.
-     Split 1 stacked:
-       # Items here are window titles, each should be unique.
-       # Window 1
-       Device Logs:
-         height: 33  # Weighted value for window height
-         hidden: False  # Hide this window if True
-       # Window 2
-       Python Repl:
-         height: 67
-       # Window 3
-       Host Logs:
-         hidden: True
-
-     # Second window column
-     Split 2 tabbed:
-       # This is a duplicate of the existing 'Device Logs' window.
-       # The title is 'NEW DEVICE'
-       NEW DEVICE:
-         duplicate_of: Device Logs
-         # Log filters are defined here
-         filters:
-           # Metadata column names here or 'all'
-           source_name:
-             # Matching method name here
-             # regex, regex-inverted, string, string-inverted
-             regex: 'USB'
-           module:
-             # An inverted match will remove matching log lines
-             regex-inverted: 'keyboard'
-       NEW HOST:
-         duplicate_of: Host Logs
-         filters:
-           all:
-             string: 'FLASH'
-
-     # Third window column
-     Split 3 tabbed:
-       # This is a brand new log Window
-       Keyboard Logs - IBM:
-         loggers:
-           # Python logger names to include in this log window
-           my_cool_keyboard_device:
-             # Level the logger should be set to.
-             level: DEBUG
-           # The empty string logger name is the root Python logger.
-           # In most cases this should capture all log messages.
-           '':
-             level: DEBUG
-         filters:
-           all:
-             regex: 'IBM Model M'
-       Keyboard Logs - Apple:
-         loggers:
-           my_cool_keyboard_device:
-             level: DEBUG
-         filters:
-           all:
-             regex: 'Apple.*USB'
-
-   # Command Runner dialog size and position
-   command_runner:
-     width: 80
-     height: 10
-     position:
-       top: 3  # 3 lines below the top edge of the screen
-       # Alternatively one of these options can be used instead:
-       # bottom: 2  # 2 lines above the bottom edge of the screen
-       # left: 2    # 2 lines away from the left edge of the screen
-       # right: 2   # 2 lines away from the right edge of the screen
-
-   # Key bindings can be changed as well with the following format:
-   #   named-command: [ list_of_keys ]
-   # Where list_of_keys is a string of keys one for each alternate key
-   # To see all named commands open '[Help] > View Key Binding Config'
-   # See below for the names of special keys
-   key_bindings:
-     log-pane.move-cursor-up:
-     - j
-     - up
-     log-pane.move-cursor-down:
-     - k
-     - down
-     log-pane.search-next-match:
-     - n
-     log-pane.search-previous-match:
-     - N
-
-     # Chorded keys are supported.
-     # For example, 'z t' means pressing z quickly followed by t.
-     log-pane.shift-line-to-top:
-     - z t
-     log-pane.shift-line-to-center:
-     - z z
-
-Changing Keyboard Shortcuts
----------------------------
-
-Pigweed Console uses `prompt_toolkit
-<https://python-prompt-toolkit.readthedocs.io/en/latest/>`_ to manage its
-keybindings.
-
-Bindings can be changed in the YAML config file under the ``key_bindings:``
-section by adding a named function followed by a of keys to bind. For example
-this config sets the keys for log pane cursor movement.
-
-- Moving down is set to :kbd:`j` or the :kbd:`Down` arrow.
-- Moving up is set to :kbd:`k` or the :kbd:`Up` arrow.
-
-.. code-block:: yaml
-
-   key_bindings:
-     log-pane.move-cursor-down:
-     - j
-     - down
-     log-pane.move-cursor-up:
-     - k
-     - up
-
-List of Special Key Names
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-This table is from prompt_toolkit's :bdg-link-primary-line:`List of special keys
-<https://python-prompt-toolkit.readthedocs.io/en/latest/pages/advanced_topics/key_bindings.html#list-of-special-keys>`.
-
-.. list-table::
-   :widths: 30 70
-   :header-rows: 1
-
-   * - Keyboard Function
-     - Key Values
-
-   * - Literal characters
-     - ``a b c d e f g h i j k l m n o p q r s t u v w x y z``
-       ``A B C D E F G H I J K L M N O P Q R S T U V W X Y Z``
-       ``1 2 3 4 5 6 7 8 9 0``
-       ``! @ # $ % ^ & * ( )``
-       ``- _ + = ~``
-
-   * - Escape and Shift-Escape
-     - ``escape`` ``s-escape``
-
-   * - Arrows
-     - ``left`` ``right`` ``up`` ``down``
-
-   * - Navigation
-     - ``home`` ``end`` ``delete`` ``pageup`` ``pagedown`` ``insert``
-
-   * - Control-letter
-     - ``c-a c-b c-c c-d c-e c-f c-g c-h c-i c-j c-k c-l c-m``
-       ``c-n c-o c-p c-q c-r c-s c-t c-u c-v c-w c-x c-y c-z``
-
-   * - Control-number
-     - ``c-1`` ``c-2`` ``c-3`` ``c-4`` ``c-5`` ``c-6`` ``c-7`` ``c-8`` ``c-9`` ``c-0``
-
-   * - Control-arrow
-     - ``c-left`` ``c-right`` ``c-up`` ``c-down``
-
-   * - Other control keys
-     - ``c-@`` ``c-\`` ``c-]`` ``c-^`` ``c-_`` ``c-delete``
-
-   * - Shift-arrow
-     - ``s-left`` ``s-right`` ``s-up`` ``s-down``
-
-   * - Control-Shift-arrow
-     - ``c-s-left`` ``c-s-right`` ``c-s-up`` ``c-s-down``
-
-   * - Other Shift` keys
-     - ``s-delete`` ``s-tab``
-
-   * - F Keys
-     - ``f1  f2  f3  f4  f5  f6  f7  f8  f9  f10 f11 f12``
-       ``f13 f14 f15 f16 f17 f18 f19 f20 f21 f22 f23 f24``
-
-There are some key aliases as well. Most of these exist due to how keys are
-processed in VT100 terminals. For example when pressing :kbd:`Tab` terminal
-emulators receive :kbd:`Ctrl-i`.
-
-.. list-table::
-   :widths: 40 60
-   :header-rows: 1
-
-   * - Key
-     - Key Value Alias
-
-   * - Space
-     - ``space``
-
-   * - ``c-h``
-     - ``backspace``
-
-   * - ``c-@``
-     - ``c-space``
-
-   * - ``c-m``
-     - ``enter``
-
-   * - ``c-i``
-     - ``tab``
-
-Binding Alt / Option / Meta
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-In terminals the :kbd:`Alt` key is converted into a leading :kbd:`Escape` key
-press. For example pressing :kbd:`Alt-t` actually sends the :kbd:`Escape` key
-followed by the :kbd:`t` key. Similarly :kbd:`Ctrl-Alt-t` sends :kbd:`Escape`
-followed by :kbd:`Ctrl-t`.
-
-To bind :kbd:`Alt` (or :kbd:`Option` on MacOS) add ``escape`` before the key
-that should be modified.
-
-.. code-block:: yaml
-
-   key_bindings:
-     window-manager.move-pane-down:
-     - escape c-up  # Alt-Ctrl-up
-     window-manager.move-pane-left:
-     - escape c-left  # Alt-Ctrl-left
-     window-manager.move-pane-right:
-     - escape c-right  # Alt-Ctrl-right
-     window-manager.move-pane-up:
-     - escape c-down  # Alt-Ctrl-down
-
-Key Sequence Bindings
-~~~~~~~~~~~~~~~~~~~~~
-
-Bindings can consist of multiple key presses in sequence. This is also known as
-chorded keys. Multiple keys separated by spaces define a chorded key
-binding. For example to bind :kbd:`z` quickly followed by :kbd:`t` use ``z t``.
-
-.. code-block:: yaml
-
-   key_bindings:
-     log-pane.shift-line-to-top:
-     - z t
-     log-pane.shift-line-to-center:
-     - z z
-
-
-Known Issues
-------------
-
-Log Window
-~~~~~~~~~~
-
-- Tab character rendering will not work in the log pane view. They will
-  appear as ``^I`` since prompt_toolkit can't render them. See this issue for
-  details:
-  https://github.com/prompt-toolkit/python-prompt-toolkit/issues/556
-
-
-Upcoming Features
------------------
-
-For upcoming features see the Pigweed Console Bug Hotlist at:
-https://bugs.chromium.org/u/542633886/hotlists/Console
-
-
-Feature Requests
-~~~~~~~~~~~~~~~~
-
-Create a feature request bugs using this template:
-https://bugs.chromium.org/p/pigweed/issues/entry?owner=tonymd@google.com&labels=Type-Enhancement,Priority-Medium&summary=pw_console
diff --git a/pw_console/py/pw_console/embed.py b/pw_console/py/pw_console/embed.py
deleted file mode 100644
index 5ef83f0..0000000
--- a/pw_console/py/pw_console/embed.py
+++ /dev/null
@@ -1,307 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""pw_console embed class."""
-
-import asyncio
-import logging
-from pathlib import Path
-from typing import Any, Dict, List, Iterable, Optional, Union
-
-from prompt_toolkit.completion import WordCompleter
-
-from pw_console.console_app import ConsoleApp
-from pw_console.get_pw_console_app import PW_CONSOLE_APP_CONTEXTVAR
-from pw_console.plugin_mixin import PluginMixin
-import pw_console.python_logging
-from pw_console.widgets import WindowPane, WindowPaneToolbar
-
-
-def _set_console_app_instance(plugin: Any, console_app: ConsoleApp) -> None:
-    if hasattr(plugin, 'pw_console_init'):
-        plugin.pw_console_init(console_app)
-    else:
-        plugin.application = console_app
-
-
-class PwConsoleEmbed:
-    """Embed class for customizing the console before startup."""
-
-    # pylint: disable=too-many-instance-attributes
-    def __init__(self,
-                 global_vars=None,
-                 local_vars=None,
-                 loggers: Optional[Union[Dict[str, Iterable[logging.Logger]],
-                                         Iterable]] = None,
-                 test_mode=False,
-                 repl_startup_message: Optional[str] = None,
-                 help_text: Optional[str] = None,
-                 app_title: Optional[str] = None,
-                 config_file_path: Optional[Union[str, Path]] = None) -> None:
-        """Call this to embed pw console at the call point within your program.
-
-        Example usage:
-
-        .. code-block:: python
-
-            import logging
-
-            from pw_console import PwConsoleEmbed
-
-            # Create the pw_console embed instance
-            console = PwConsoleEmbed(
-                global_vars=globals(),
-                local_vars=locals(),
-                loggers={
-                    'Host Logs': [
-                        logging.getLogger(__package__),
-                        logging.getLogger(__file__),
-                    ],
-                    'Device Logs': [
-                        logging.getLogger('usb_gadget'),
-                    ],
-                },
-                app_title='My Awesome Console',
-                config_file_path='/home/user/project/.pw_console.yaml',
-            )
-            # Optional: Add custom completions
-            console.add_sentence_completer(
-                {
-                    'some_function', 'Function',
-                    'some_variable', 'Variable',
-                }
-            )
-
-            # Setup Python loggers to output to a file instead of STDOUT.
-            console.setup_python_logging()
-
-            # Then run the console with:
-            console.embed()
-
-        Args:
-            global_vars: Dictionary representing the desired global symbol
-                table. Similar to what is returned by `globals()`.
-            local_vars: Dictionary representing the desired local symbol
-                table. Similar to what is returned by `locals()`.
-            loggers: Dict with keys of log window titles and values of either:
-
-                    1. List of `logging.getLogger()
-                       <https://docs.python.org/3/library/logging.html#logging.getLogger>`_
-                       instances.
-                    2. A single pw_console.log_store.LogStore instance.
-
-            app_title: Custom title text displayed in the user interface.
-            repl_startup_message: Custom text shown by default in the repl
-                output pane.
-            help_text: Custom text shown at the top of the help window before
-                keyboard shortcuts.
-            config_file_path: Path to a pw_console yaml config file.
-        """
-
-        self.global_vars = global_vars
-        self.local_vars = local_vars
-        self.loggers = loggers
-        self.test_mode = test_mode
-        self.repl_startup_message = repl_startup_message
-        self.help_text = help_text
-        self.app_title = app_title
-        self.config_file_path = Path(
-            config_file_path) if config_file_path else None
-
-        self.console_app: Optional[ConsoleApp] = None
-        self.extra_completers: List = []
-
-        self.setup_python_logging_called = False
-        self.hidden_by_default_windows: List[str] = []
-        self.window_plugins: List[WindowPane] = []
-        self.top_toolbar_plugins: List[WindowPaneToolbar] = []
-        self.bottom_toolbar_plugins: List[WindowPaneToolbar] = []
-
-    def add_window_plugin(self, window_pane: WindowPane) -> None:
-        """Include a custom window pane plugin.
-
-        Args:
-            window_pane: Any instance of the WindowPane class.
-        """
-        self.window_plugins.append(window_pane)
-
-    def add_top_toolbar(self, toolbar: WindowPaneToolbar) -> None:
-        """Include a toolbar plugin to display on the top of the screen.
-
-        Top toolbars appear above all window panes and just below the main menu
-        bar. They span the full width of the screen.
-
-        Args:
-            toolbar: Instance of the WindowPaneToolbar class.
-        """
-        self.top_toolbar_plugins.append(toolbar)
-
-    def add_bottom_toolbar(self, toolbar: WindowPaneToolbar) -> None:
-        """Include a toolbar plugin to display at the bottom of the screen.
-
-        Bottom toolbars appear below all window panes and span the full width of
-        the screen.
-
-        Args:
-            toolbar: Instance of the WindowPaneToolbar class.
-        """
-        self.bottom_toolbar_plugins.append(toolbar)
-
-    def add_sentence_completer(self,
-                               word_meta_dict: Dict[str, str],
-                               ignore_case=True) -> None:
-        """Include a custom completer that matches on the entire repl input.
-
-        Args:
-            word_meta_dict: Dictionary representing the sentence completions
-                and descriptions. Keys are completion text, values are
-                descriptions.
-        """
-
-        # Don't modify completion if empty.
-        if len(word_meta_dict) == 0:
-            return
-
-        sentences: List[str] = list(word_meta_dict.keys())
-        word_completer = WordCompleter(
-            sentences,
-            meta_dict=word_meta_dict,
-            ignore_case=ignore_case,
-            # Whole input field should match
-            sentence=True,
-        )
-
-        self.extra_completers.append(word_completer)
-
-    def _setup_log_panes(self) -> None:
-        """Add loggers to ConsoleApp log pane(s)."""
-        if not self.loggers:
-            return
-
-        assert isinstance(self.console_app, ConsoleApp)
-
-        if isinstance(self.loggers, list):
-            self.console_app.add_log_handler('Logs', self.loggers)
-
-        elif isinstance(self.loggers, dict):
-            for window_title, logger_instances in self.loggers.items():
-                window_pane = self.console_app.add_log_handler(
-                    window_title, logger_instances)
-
-                if (window_pane and window_pane.pane_title()
-                        in self.hidden_by_default_windows):
-                    window_pane.show_pane = False
-
-    def setup_python_logging(
-        self,
-        last_resort_filename: Optional[str] = None,
-        loggers_with_no_propagation: Optional[Iterable[logging.Logger]] = None
-    ) -> None:
-        """Setup friendly logging for full-screen prompt_toolkit applications.
-
-        This function sets up Python log handlers to be friendly for full-screen
-        prompt_toolkit applications. That is, logging to terminal STDOUT and
-        STDERR is disabled so the terminal user interface can be drawn.
-
-        Specifically, all Python STDOUT and STDERR log handlers are
-        disabled. It also sets `log propagation to True
-        <https://docs.python.org/3/library/logging.html#logging.Logger.propagate>`_.
-        to ensure that all log messages are sent to the root logger.
-
-        Args:
-            last_resort_filename: If specified use this file as a fallback for
-                unhandled Python logging messages. Normally Python will output
-                any log messages with no handlers to STDERR as a fallback. If
-                None, a temp file will be created instead. See Python
-                documentation on `logging.lastResort
-                <https://docs.python.org/3/library/logging.html#logging.lastResort>`_
-                for more info.
-            loggers_with_no_propagation: List of logger instances to skip
-               setting ``propagate = True``. This is useful if you would like
-               log messages from a particular source to not appear in the root
-               logger.
-        """
-        self.setup_python_logging_called = True
-        pw_console.python_logging.setup_python_logging(
-            last_resort_filename, loggers_with_no_propagation)
-
-    def hide_windows(self, *window_titles) -> None:
-        """Hide window panes specified by title on console startup."""
-        for window_title in window_titles:
-            self.hidden_by_default_windows.append(window_title)
-
-    def embed(self) -> None:
-        """Start the console."""
-
-        # Create the ConsoleApp instance.
-        self.console_app = ConsoleApp(
-            global_vars=self.global_vars,
-            local_vars=self.local_vars,
-            repl_startup_message=self.repl_startup_message,
-            help_text=self.help_text,
-            app_title=self.app_title,
-            extra_completers=self.extra_completers,
-        )
-        PW_CONSOLE_APP_CONTEXTVAR.set(self.console_app)  # type: ignore
-        # Setup Python logging and log panes.
-        if not self.setup_python_logging_called:
-            self.setup_python_logging()
-        self._setup_log_panes()
-
-        # Add window pane plugins to the layout.
-        for window_pane in self.window_plugins:
-            _set_console_app_instance(window_pane, self.console_app)
-            # Hide window plugins if the title is hidden by default.
-            if window_pane.pane_title() in self.hidden_by_default_windows:
-                window_pane.show_pane = False
-            self.console_app.window_manager.add_pane(window_pane)
-
-        # Add toolbar plugins to the layout.
-        for toolbar in self.top_toolbar_plugins:
-            _set_console_app_instance(toolbar, self.console_app)
-            self.console_app.window_manager.add_top_toolbar(toolbar)
-        for toolbar in self.bottom_toolbar_plugins:
-            _set_console_app_instance(toolbar, self.console_app)
-            self.console_app.window_manager.add_bottom_toolbar(toolbar)
-
-        # Rebuild prompt_toolkit containers, menu items, and help content with
-        # any new plugins added above.
-        self.console_app.refresh_layout()
-
-        # Load external config if passed in.
-        if self.config_file_path:
-            self.console_app.load_clean_config(self.config_file_path)
-
-        self.console_app.apply_window_config()
-
-        # Hide the repl pane if it's in the hidden windows list.
-        if 'Python Repl' in self.hidden_by_default_windows:
-            self.console_app.repl_pane.show_pane = False
-
-        # Start a thread for running user code.
-        self.console_app.start_user_code_thread()
-
-        # Startup any background threads and tasks required by plugins.
-        for window_pane in self.window_plugins:
-            if isinstance(window_pane, PluginMixin):
-                window_pane.plugin_start()
-        for toolbar in self.bottom_toolbar_plugins:
-            if isinstance(toolbar, PluginMixin):
-                toolbar.plugin_start()
-        for toolbar in self.top_toolbar_plugins:
-            if isinstance(toolbar, PluginMixin):
-                toolbar.plugin_start()
-
-        # Start the prompt_toolkit UI app.
-        asyncio.run(self.console_app.run(test_mode=self.test_mode),
-                    debug=self.test_mode)
diff --git a/pw_console/py/pw_console/filter_toolbar.py b/pw_console/py/pw_console/filter_toolbar.py
deleted file mode 100644
index df37669..0000000
--- a/pw_console/py/pw_console/filter_toolbar.py
+++ /dev/null
@@ -1,121 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""LogPane FilterToolbar class."""
-
-from __future__ import annotations
-import functools
-from typing import TYPE_CHECKING
-
-from prompt_toolkit.filters import Condition
-from prompt_toolkit.layout import (
-    ConditionalContainer,
-    FormattedTextControl,
-    VSplit,
-    Window,
-    WindowAlign,
-    HorizontalAlign,
-)
-from prompt_toolkit.mouse_events import MouseEvent, MouseEventType
-
-import pw_console.widgets.checkbox
-import pw_console.widgets.mouse_handlers
-import pw_console.style
-
-if TYPE_CHECKING:
-    from pw_console.log_pane import LogPane
-
-
-class FilterToolbar(ConditionalContainer):
-    """Container showing each filter applied in order."""
-
-    TOOLBAR_HEIGHT = 1
-
-    def mouse_handler_delete_filter(self, filter_text,
-                                    mouse_event: MouseEvent):
-        """Delete the given log filter."""
-        if mouse_event.event_type == MouseEventType.MOUSE_UP:
-            self.log_pane.log_view.delete_filter(filter_text)
-            return None
-        return NotImplemented
-
-    def get_left_fragments(self):
-        """Return formatted text tokens for display."""
-        separator = ('', '  ')
-        space = ('', ' ')
-        fragments = [('class:filter-bar-title', ' Filters '), separator]
-
-        button_style = pw_console.style.get_button_style(self.log_pane)
-
-        for filter_text, log_filter in self.log_pane.log_view.filters.items():
-            fragments.append(('class:filter-bar-delimiter', '<'))
-
-            if log_filter.invert:
-                fragments.append(('class:filter-bar-setting', 'NOT '))
-
-            if log_filter.field:
-                fragments.append(
-                    ('class:filter-bar-setting', log_filter.field))
-                fragments.append(space)
-
-            fragments.append(('', filter_text))
-            fragments.append(space)
-
-            fragments.append(
-                (button_style + ' class:filter-bar-delete', ' (X) ',
-                 functools.partial(self.mouse_handler_delete_filter,
-                                   filter_text)))  # type: ignore
-            fragments.append(('class:filter-bar-delimiter', '>'))
-
-            fragments.append(separator)
-        return fragments
-
-    def get_center_fragments(self):
-        """Return formatted text tokens for display."""
-        clear_filters = functools.partial(
-            pw_console.widgets.mouse_handlers.on_click,
-            self.log_pane.log_view.clear_filters)
-
-        button_style = pw_console.style.get_button_style(self.log_pane)
-
-        return pw_console.widgets.checkbox.to_keybind_indicator(
-            'Ctrl-Alt-r',
-            'Clear Filters',
-            clear_filters,
-            base_style=button_style)
-
-    def __init__(self, log_pane: 'LogPane'):
-        self.log_pane = log_pane
-        left_bar_control = FormattedTextControl(self.get_left_fragments)
-        left_bar_window = Window(content=left_bar_control,
-                                 align=WindowAlign.LEFT,
-                                 dont_extend_width=True)
-        center_bar_control = FormattedTextControl(self.get_center_fragments)
-        center_bar_window = Window(content=center_bar_control,
-                                   align=WindowAlign.LEFT,
-                                   dont_extend_width=False)
-        super().__init__(
-            VSplit(
-                [
-                    left_bar_window,
-                    center_bar_window,
-                ],
-                style=functools.partial(pw_console.style.get_toolbar_style,
-                                        self.log_pane,
-                                        dim=True),
-                height=1,
-                align=HorizontalAlign.LEFT,
-            ),
-            # Only show if filtering is enabled.
-            filter=Condition(lambda: self.log_pane.log_view.filtering_on),
-        )
diff --git a/pw_console/py/pw_console/get_pw_console_app.py b/pw_console/py/pw_console/get_pw_console_app.py
deleted file mode 100644
index b1fefcc..0000000
--- a/pw_console/py/pw_console/get_pw_console_app.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Fetch active Pigweed Console application instance."""
-
-from contextvars import ContextVar
-
-PW_CONSOLE_APP_CONTEXTVAR = ContextVar('pw_console_current_app', default=None)
-
-
-def get_pw_console_app():
-    return PW_CONSOLE_APP_CONTEXTVAR.get()
diff --git a/pw_console/py/pw_console/help_window.py b/pw_console/py/pw_console/help_window.py
deleted file mode 100644
index d8ca7ad..0000000
--- a/pw_console/py/pw_console/help_window.py
+++ /dev/null
@@ -1,340 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Help window container class."""
-
-import functools
-import inspect
-import logging
-from pathlib import Path
-from typing import Dict, TYPE_CHECKING
-
-from prompt_toolkit.document import Document
-from prompt_toolkit.filters import Condition
-from prompt_toolkit.key_binding import KeyBindings, KeyPressEvent
-from prompt_toolkit.layout import (
-    ConditionalContainer,
-    DynamicContainer,
-    FormattedTextControl,
-    HSplit,
-    VSplit,
-    Window,
-    WindowAlign,
-)
-from prompt_toolkit.layout.dimension import Dimension
-from prompt_toolkit.lexers import PygmentsLexer
-from prompt_toolkit.widgets import Box, TextArea
-
-from pygments.lexers.markup import RstLexer  # type: ignore
-from pygments.lexers.data import YamlLexer  # type: ignore
-import pw_console.widgets.mouse_handlers
-
-if TYPE_CHECKING:
-    from pw_console.console_app import ConsoleApp
-
-_LOG = logging.getLogger(__package__)
-
-
-def _longest_line_length(text):
-    """Return the longest line in the given text."""
-    max_line_length = 0
-    for line in text.splitlines():
-        if len(line) > max_line_length:
-            max_line_length = len(line)
-    return max_line_length
-
-
-class HelpWindow(ConditionalContainer):
-    """Help window container for displaying keybindings."""
-
-    # pylint: disable=too-many-instance-attributes
-
-    def _create_help_text_area(self, **kwargs):
-        help_text_area = TextArea(
-            focusable=True,
-            focus_on_click=True,
-            scrollbar=True,
-            style='class:help_window_content',
-            wrap_lines=False,
-            **kwargs,
-        )
-
-        # Additional keybindings for the text area.
-        key_bindings = KeyBindings()
-        register = self.application.prefs.register_keybinding
-
-        @register('help-window.close', key_bindings)
-        def _close_window(_event: KeyPressEvent) -> None:
-            """Close the current dialog window."""
-            self.toggle_display()
-
-        @register('help-window.copy-all', key_bindings)
-        def _copy_all(_event: KeyPressEvent) -> None:
-            """Close the current dialog window."""
-            self.copy_all_text()
-
-        help_text_area.control.key_bindings = key_bindings
-        return help_text_area
-
-    def __init__(self,
-                 application: 'ConsoleApp',
-                 preamble: str = '',
-                 additional_help_text: str = '',
-                 title: str = '') -> None:
-        # Dict containing key = section title and value = list of key bindings.
-        self.application: 'ConsoleApp' = application
-        self.show_window: bool = False
-        self.help_text_sections: Dict[str, Dict] = {}
-        self._pane_title: str = title
-
-        # Tracks the last focused container, to enable restoring focus after
-        # closing the dialog.
-        self.last_focused_pane = None
-
-        # Generated keybinding text
-        self.preamble: str = preamble
-        self.additional_help_text: str = additional_help_text
-        self.help_text: str = ''
-
-        self.max_additional_help_text_width: int = (_longest_line_length(
-            self.additional_help_text) if additional_help_text else 0)
-        self.max_description_width: int = 0
-        self.max_key_list_width: int = 0
-        self.max_line_length: int = 0
-
-        self.help_text_area: TextArea = self._create_help_text_area()
-
-        close_mouse_handler = functools.partial(
-            pw_console.widgets.mouse_handlers.on_click, self.toggle_display)
-        copy_mouse_handler = functools.partial(
-            pw_console.widgets.mouse_handlers.on_click, self.copy_all_text)
-
-        toolbar_padding = 1
-        toolbar_title = ' ' * toolbar_padding
-        toolbar_title += self.pane_title()
-
-        buttons = []
-        buttons.extend(
-            pw_console.widgets.checkbox.to_keybind_indicator(
-                'Ctrl-c',
-                'Copy All',
-                copy_mouse_handler,
-                base_style='class:toolbar-button-active'))
-        buttons.append(('', '  '))
-        buttons.extend(
-            pw_console.widgets.checkbox.to_keybind_indicator(
-                'q',
-                'Close',
-                close_mouse_handler,
-                base_style='class:toolbar-button-active'))
-        top_toolbar = VSplit(
-            [
-                Window(
-                    content=FormattedTextControl(
-                        # [('', toolbar_title)]
-                        functools.partial(pw_console.style.get_pane_indicator,
-                                          self, toolbar_title)),
-                    align=WindowAlign.LEFT,
-                    dont_extend_width=True,
-                ),
-                Window(
-                    content=FormattedTextControl([]),
-                    align=WindowAlign.LEFT,
-                    dont_extend_width=False,
-                ),
-                Window(
-                    content=FormattedTextControl(buttons),
-                    align=WindowAlign.RIGHT,
-                    dont_extend_width=True,
-                ),
-            ],
-            height=1,
-            style='class:toolbar_active',
-        )
-
-        self.container = HSplit([
-            top_toolbar,
-            Box(
-                body=DynamicContainer(lambda: self.help_text_area),
-                padding=Dimension(preferred=1, max=1),
-                padding_bottom=0,
-                padding_top=0,
-                char=' ',
-                style='class:frame.border',  # Same style used for Frame.
-            ),
-        ])
-
-        super().__init__(
-            self.container,
-            filter=Condition(lambda: self.show_window),
-        )
-
-    def pane_title(self):
-        return self._pane_title
-
-    def menu_title(self):
-        """Return the title to display in the Window menu."""
-        return self.pane_title()
-
-    def __pt_container__(self):
-        """Return the prompt_toolkit container for displaying this HelpWindow.
-
-        This allows self to be used wherever prompt_toolkit expects a container
-        object."""
-        return self.container
-
-    def copy_all_text(self):
-        """Copy all text in the Python input to the system clipboard."""
-        self.application.application.clipboard.set_text(
-            self.help_text_area.buffer.text)
-
-    def toggle_display(self):
-        """Toggle visibility of this help window."""
-        # Toggle state variable.
-        self.show_window = not self.show_window
-
-        if self.show_window:
-            # Save previous focus
-            self.last_focused_pane = self.application.focused_window()
-            # Set the help window in focus.
-            self.application.layout.focus(self.help_text_area)
-        else:
-            # Restore original focus if possible.
-            if self.last_focused_pane:
-                self.application.layout.focus(self.last_focused_pane)
-            else:
-                # Fallback to focusing on the first window pane.
-                self.application.focus_main_menu()
-
-    def content_width(self) -> int:
-        """Return total width of help window."""
-        # Widths of UI elements
-        frame_width = 1
-        padding_width = 1
-        left_side_frame_and_padding_width = frame_width + padding_width
-        right_side_frame_and_padding_width = frame_width + padding_width
-        scrollbar_padding = 1
-        scrollbar_width = 1
-
-        desired_width = self.max_line_length + (
-            left_side_frame_and_padding_width +
-            right_side_frame_and_padding_width + scrollbar_padding +
-            scrollbar_width)
-        desired_width = max(60, desired_width)
-
-        window_manager_width = (
-            self.application.window_manager.current_window_manager_width)
-        if not window_manager_width:
-            window_manager_width = 80
-        return min(desired_width, window_manager_width)
-
-    def load_user_guide(self):
-        rstdoc = Path(__file__).parent / 'docs/user_guide.rst'
-        max_line_length = 0
-        rst_text = ''
-        with rstdoc.open() as rstfile:
-            for line in rstfile.readlines():
-                if 'https://' not in line and len(line) > max_line_length:
-                    max_line_length = len(line)
-                rst_text += line
-        self.max_line_length = max_line_length
-
-        self.help_text_area = self._create_help_text_area(
-            lexer=PygmentsLexer(RstLexer),
-            text=rst_text,
-        )
-
-    def load_yaml_text(self, content: str):
-        max_line_length = 0
-        for line in content.splitlines():
-            if 'https://' not in line and len(line) > max_line_length:
-                max_line_length = len(line)
-        self.max_line_length = max_line_length
-
-        self.help_text_area = self._create_help_text_area(
-            lexer=PygmentsLexer(YamlLexer),
-            text=content,
-        )
-
-    def generate_help_text(self):
-        """Generate help text based on added key bindings."""
-
-        template = self.application.get_template('keybind_list.jinja')
-
-        self.help_text = template.render(
-            sections=self.help_text_sections,
-            max_additional_help_text_width=self.max_additional_help_text_width,
-            max_description_width=self.max_description_width,
-            max_key_list_width=self.max_key_list_width,
-            preamble=self.preamble,
-            additional_help_text=self.additional_help_text,
-        )
-
-        # Find the longest line in the rendered template.
-        self.max_line_length = _longest_line_length(self.help_text)
-
-        # Replace the TextArea content.
-        self.help_text_area.buffer.document = Document(text=self.help_text,
-                                                       cursor_position=0)
-
-        return self.help_text
-
-    def add_custom_keybinds_help_text(self, section_name, key_bindings: Dict):
-        """Add hand written key_bindings."""
-        self.help_text_sections[section_name] = key_bindings
-
-    def add_keybind_help_text(self, section_name, key_bindings: KeyBindings):
-        """Append formatted key binding text to this help window."""
-
-        # Create a new keybind section, erasing any old section with thesame
-        # title.
-        self.help_text_sections[section_name] = {}
-
-        # Loop through passed in prompt_toolkit key_bindings.
-        for binding in key_bindings.bindings:
-            # Skip this keybind if the method name ends in _hidden.
-            if binding.handler.__name__.endswith('_hidden'):
-                continue
-
-            # Get the key binding description from the function doctstring.
-            docstring = binding.handler.__doc__
-            if not docstring:
-                docstring = ''
-            description = inspect.cleandoc(docstring)
-            description = description.replace('\n', ' ')
-
-            # Save the length of the description.
-            if len(description) > self.max_description_width:
-                self.max_description_width = len(description)
-
-            # Get the existing list of keys for this function or make a new one.
-            key_list = self.help_text_sections[section_name].get(
-                description, list())
-
-            # Save the name of the key e.g. F1, q, ControlQ, ControlUp
-            key_name = ' '.join(
-                [getattr(key, 'name', str(key)) for key in binding.keys])
-            key_name = key_name.replace('Control', 'Ctrl-')
-            key_name = key_name.replace('Shift', 'Shift-')
-            key_name = key_name.replace('Escape ', 'Alt-')
-            key_name = key_name.replace('Alt-Ctrl-', 'Ctrl-Alt-')
-            key_name = key_name.replace('BackTab', 'Shift-Tab')
-            key_list.append(key_name)
-
-            key_list_width = len(', '.join(key_list))
-            # Save the length of the key list.
-            if key_list_width > self.max_key_list_width:
-                self.max_key_list_width = key_list_width
-
-            # Update this functions key_list
-            self.help_text_sections[section_name][description] = key_list
diff --git a/pw_console/py/pw_console/key_bindings.py b/pw_console/py/pw_console/key_bindings.py
deleted file mode 100644
index c44d866..0000000
--- a/pw_console/py/pw_console/key_bindings.py
+++ /dev/null
@@ -1,176 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-# pylint: skip-file
-"""Console key bindings."""
-import logging
-from typing import Dict, List
-
-from prompt_toolkit.filters import (
-    Condition,
-    has_focus,
-)
-from prompt_toolkit.key_binding import KeyBindings
-from prompt_toolkit.key_binding.bindings.focus import focus_next, focus_previous
-from prompt_toolkit.key_binding.key_bindings import Binding
-
-import pw_console.pw_ptpython_repl
-
-__all__ = ('create_key_bindings', )
-
-_LOG = logging.getLogger(__package__)
-
-DEFAULT_KEY_BINDINGS: Dict[str, List[str]] = {
-    'global.open-user-guide': ['f1'],
-    'global.open-menu-search': ['c-p'],
-    'global.focus-previous-widget': ['c-left'],
-    'global.focus-next-widget': ['c-right', 's-tab'],
-    'global.exit-no-confirmation': ['c-x c-c'],
-    'global.exit-with-confirmation': ['c-d'],
-    'log-pane.shift-line-to-top': ['z t'],
-    'log-pane.shift-line-to-center': ['z z'],
-    'log-pane.toggle-follow': ['f'],
-    'log-pane.toggle-wrap-lines': ['w'],
-    'log-pane.toggle-table-view': ['t'],
-    'log-pane.duplicate-log-pane': ['insert'],
-    'log-pane.remove-duplicated-log-pane': ['delete'],
-    'log-pane.clear-history': ['C'],
-    'log-pane.toggle-follow': ['f'],
-    'log-pane.move-cursor-up': ['up', 'k'],
-    'log-pane.move-cursor-down': ['down', 'j'],
-    'log-pane.visual-select-up': ['s-up'],
-    'log-pane.visual-select-down': ['s-down'],
-    'log-pane.visual-select-all': ['N', 'c-r'],
-    'log-pane.deselect-cancel-search': ['c-c'],
-    'log-pane.scroll-page-up': ['pageup'],
-    'log-pane.scroll-page-down': ['pagedown'],
-    'log-pane.scroll-to-top': ['g'],
-    'log-pane.scroll-to-bottom': ['G'],
-    'log-pane.save-copy': ['c-o'],
-    'log-pane.search': ['/', 'c-f'],
-    'log-pane.search-next-match': ['n', 'c-s', 'c-g'],
-    'log-pane.search-previous-match': ['N', 'c-r'],
-    'log-pane.search-apply-filter': ['escape c-f'],
-    'log-pane.clear-filters': ['escape c-r'],
-    'search-toolbar.toggle-column': ['c-t'],
-    'search-toolbar.toggle-invert': ['c-v'],
-    'search-toolbar.toggle-matcher': ['c-n'],
-    'search-toolbar.cancel': ['escape', 'c-c', 'c-d'],
-    'search-toolbar.create-filter': ['escape c-f'],
-    'window-manager.move-pane-left': ['escape c-left'],  # Alt-Ctrl-
-    'window-manager.move-pane-right': ['escape c-right'],  # Alt-Ctrl-
-    # NOTE: c-up and c-down seem swapped in prompt-toolkit
-    'window-manager.move-pane-down': ['escape c-up'],  # Alt-Ctrl-
-    'window-manager.move-pane-up': ['escape c-down'],  # Alt-Ctrl-
-    'window-manager.enlarge-pane': ['escape ='],  # Alt-= (mnemonic: Alt Plus)
-    'window-manager.shrink-pane':
-    ['escape -'],  # Alt-minus (mnemonic: Alt Minus)
-    'window-manager.shrink-split': ['escape ,'],  # Alt-, (mnemonic: Alt <)
-    'window-manager.enlarge-split': ['escape .'],  # Alt-. (mnemonic: Alt >)
-    'window-manager.focus-prev-pane': ['escape c-p'],  # Ctrl-Alt-p
-    'window-manager.focus-next-pane': ['escape c-n'],  # Ctrl-Alt-n
-    'window-manager.balance-window-panes': ['c-u'],
-    'python-repl.copy-output-selection': ['c-c'],
-    'python-repl.copy-all-output': ['escape c-c'],
-    'python-repl.copy-clear-or-cancel': ['c-c'],
-    'python-repl.paste-to-input': ['c-v'],
-    'save-as-dialog.cancel': ['escape', 'c-c', 'c-d'],
-    'quit-dialog.no': ['escape', 'n', 'c-c'],
-    'quit-dialog.yes': ['y', 'c-d'],
-    'command-runner.cancel': ['escape', 'c-c'],
-    'command-runner.select-previous-item': ['up', 's-tab'],
-    'command-runner.select-next-item': ['down', 'tab'],
-    'help-window.close': ['q', 'f1', 'escape'],
-    'help-window.copy-all': ['c-c'],
-}
-
-
-def create_key_bindings(console_app) -> KeyBindings:
-    """Create custom key bindings.
-
-    This starts with the key bindings, defined by `prompt-toolkit`, but adds the
-    ones which are specific for the console_app. A console_app instance
-    reference is passed in so key bind functions can access it.
-    """
-
-    key_bindings = KeyBindings()
-    register = console_app.prefs.register_keybinding
-
-    @register('global.open-user-guide',
-              key_bindings,
-              filter=Condition(lambda: not console_app.modal_window_is_open()))
-    def show_help(event):
-        """Toggle user guide window."""
-        console_app.user_guide_window.toggle_display()
-
-    # F2 is ptpython settings
-    # F3 is ptpython history
-
-    @register('global.open-menu-search',
-              key_bindings,
-              filter=Condition(lambda: not console_app.modal_window_is_open()))
-    def show_command_runner(event):
-        """Open command runner window."""
-        console_app.open_command_runner_main_menu()
-
-    @register('global.focus-previous-widget', key_bindings)
-    def app_focus_previous(event):
-        """Move focus to the previous widget."""
-        focus_previous(event)
-
-    @register('global.focus-next-widget', key_bindings)
-    def app_focus_next(event):
-        """Move focus to the next widget."""
-        focus_next(event)
-
-    # Bindings for when the ReplPane input field is in focus.
-    # These are hidden from help window global keyboard shortcuts since the
-    # method names end with `_hidden`.
-    @register('python-repl.copy-clear-or-cancel',
-              key_bindings,
-              filter=has_focus(console_app.pw_ptpython_repl))
-    def handle_ctrl_c_hidden(event):
-        """Reset the python repl on Ctrl-c"""
-        console_app.repl_pane.ctrl_c()
-
-    @register('global.exit-no-confirmation', key_bindings)
-    def quit_no_confirm(event):
-        """Quit without confirmation."""
-        event.app.exit()
-
-    @register(
-        'global.exit-with-confirmation',
-        key_bindings,
-        filter=console_app.pw_ptpython_repl.input_empty_if_in_focus_condition(
-        ) | has_focus(console_app.quit_dialog))
-    def quit(event):
-        """Quit with confirmation dialog."""
-        # If the python repl is in focus and has text input then Ctrl-d will
-        # delete forward characters instead.
-        console_app.quit_dialog.open_dialog()
-
-    @register('python-repl.paste-to-input',
-              key_bindings,
-              filter=has_focus(console_app.pw_ptpython_repl))
-    def paste_into_repl(event):
-        """Reset the python repl on Ctrl-c"""
-        console_app.repl_pane.paste_system_clipboard_to_input_buffer()
-
-    @register('python-repl.copy-all-output',
-              key_bindings,
-              filter=console_app.repl_pane.input_or_output_has_focus())
-    def copy_repl_output_text(event):
-        """Copy all Python output to the system clipboard."""
-        console_app.repl_pane.copy_all_output_text()
-
-    return key_bindings
diff --git a/pw_console/py/pw_console/log_filter.py b/pw_console/py/pw_console/log_filter.py
deleted file mode 100644
index 9873a46..0000000
--- a/pw_console/py/pw_console/log_filter.py
+++ /dev/null
@@ -1,142 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""LogFilters define how to search log lines in LogViews."""
-
-from __future__ import annotations
-import logging
-import re
-from dataclasses import dataclass
-from enum import Enum
-from typing import Optional
-
-from prompt_toolkit.formatted_text import StyleAndTextTuples
-from prompt_toolkit.formatted_text.utils import fragment_list_to_text
-from prompt_toolkit.layout.utils import explode_text_fragments
-from prompt_toolkit.validation import ValidationError, Validator
-
-from pw_console.log_line import LogLine
-
-_LOG = logging.getLogger(__package__)
-
-_UPPERCASE_REGEX = re.compile(r'[A-Z]')
-
-
-class SearchMatcher(Enum):
-    """Possible search match methods."""
-    FUZZY = 'FUZZY'
-    REGEX = 'REGEX'
-    STRING = 'STRING'
-
-
-DEFAULT_SEARCH_MATCHER = SearchMatcher.REGEX
-
-
-def preprocess_search_regex(text,
-                            matcher: SearchMatcher = DEFAULT_SEARCH_MATCHER):
-    # Ignorecase unless the text has capital letters in it.
-    regex_flags = re.IGNORECASE
-    if _UPPERCASE_REGEX.search(text):
-        regex_flags = re.RegexFlag(0)
-
-    if matcher == SearchMatcher.FUZZY:
-        # Fuzzy match replace spaces with .*
-        text_tokens = text.split(' ')
-        if len(text_tokens) > 1:
-            text = '(.*?)'.join(
-                ['({})'.format(re.escape(text)) for text in text_tokens])
-    elif matcher == SearchMatcher.STRING:
-        # Escape any regex specific characters to match the string literal.
-        text = re.escape(text)
-    elif matcher == SearchMatcher.REGEX:
-        # Don't modify search text input.
-        pass
-
-    return text, regex_flags
-
-
-class RegexValidator(Validator):
-    """Validation of regex input."""
-    def validate(self, document):
-        """Check search input for regex syntax errors."""
-        regex_text, regex_flags = preprocess_search_regex(document.text)
-        try:
-            re.compile(regex_text, regex_flags)
-        except re.error as error:
-            raise ValidationError(error.pos,
-                                  "Regex Error: %s" % error) from error
-
-
-@dataclass
-class LogFilter:
-    """Log Filter Dataclass."""
-    regex: re.Pattern
-    input_text: Optional[str] = None
-    invert: bool = False
-    field: Optional[str] = None
-
-    def pattern(self):
-        return self.regex.pattern
-
-    def matches(self, log: LogLine):
-        field = log.ansi_stripped_log
-        if self.field:
-            if hasattr(log, 'metadata') and hasattr(log.metadata, 'fields'):
-                field = log.metadata.fields.get(self.field,
-                                                log.ansi_stripped_log)
-            if hasattr(log.record, 'extra_metadata_fields'):  # type: ignore
-                field = log.record.extra_metadata_fields.get(  # type: ignore
-                    self.field, log.ansi_stripped_log)
-            if self.field == 'lvl':
-                field = log.record.levelname
-            elif self.field == 'time':
-                field = log.record.asctime
-
-        match = self.regex.search(field)
-
-        if self.invert:
-            return not match
-        return match
-
-    def highlight_search_matches(self,
-                                 line_fragments,
-                                 selected=False) -> StyleAndTextTuples:
-        """Highlight search matches in the current line_fragment."""
-        line_text = fragment_list_to_text(line_fragments)
-        exploded_fragments = explode_text_fragments(line_fragments)
-
-        def apply_highlighting(fragments, i):
-            # Expand all fragments and apply the highlighting style.
-            old_style, _text, *_ = fragments[i]
-            if selected:
-                fragments[i] = (
-                    old_style + ' class:search.current ',
-                    fragments[i][1],
-                )
-            else:
-                fragments[i] = (
-                    old_style + ' class:search ',
-                    fragments[i][1],
-                )
-
-        if self.invert:
-            # Highlight the whole line
-            for i, _fragment in enumerate(exploded_fragments):
-                apply_highlighting(exploded_fragments, i)
-        else:
-            # Highlight each non-overlapping search match.
-            for match in self.regex.finditer(line_text):
-                for fragment_i in range(match.start(), match.end()):
-                    apply_highlighting(exploded_fragments, fragment_i)
-
-        return exploded_fragments
diff --git a/pw_console/py/pw_console/log_line.py b/pw_console/py/pw_console/log_line.py
deleted file mode 100644
index 97d75f4..0000000
--- a/pw_console/py/pw_console/log_line.py
+++ /dev/null
@@ -1,96 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""LogLine storage class."""
-
-import logging
-from dataclasses import dataclass
-from datetime import datetime
-from typing import Dict, Optional
-
-from prompt_toolkit.formatted_text import ANSI, StyleAndTextTuples
-
-from pw_log_tokenized import FormatStringWithMetadata
-
-
-@dataclass
-class LogLine:
-    """Class to hold a single log event."""
-    record: logging.LogRecord
-    formatted_log: str
-    ansi_stripped_log: str
-
-    def __post_init__(self):
-        self.metadata = None
-        self.fragment_cache = None
-
-    def time(self):
-        """Return a datetime object for the log record."""
-        return datetime.fromtimestamp(self.record.created)
-
-    def update_metadata(self, extra_fields: Optional[Dict] = None):
-        """Parse log metadata fields from various sources."""
-
-        # 1. Parse any metadata from the message itself.
-        self.metadata = FormatStringWithMetadata(str(self.record.message))
-        self.formatted_log = self.formatted_log.replace(
-            self.metadata.raw_string, self.metadata.message)
-        # Remove any trailing line breaks.
-        self.formatted_log = self.formatted_log.rstrip()
-
-        # 2. Check for a metadata Dict[str, str] stored in the log record in the
-        # `extra_metadata_fields` attribute. This should be set using the
-        # extra={} kwarg. For example:
-        # LOGGER.log(
-        #     level,
-        #     '%s',
-        #     message,
-        #     extra=dict(
-        #         extra_metadata_fields={
-        #             'Field1': 'Value1',
-        #             'Field2': 'Value2',
-        #         }))
-        # See:
-        # https://docs.python.org/3/library/logging.html#logging.debug
-        if hasattr(self.record, 'extra_metadata_fields') and (
-                self.record.extra_metadata_fields):  # type: ignore
-            fields = self.record.extra_metadata_fields  # type: ignore
-            for key, value in fields.items():
-                self.metadata.fields[key] = value
-
-        # 3. Check for additional passed in metadata.
-        if extra_fields:
-            for key, value in extra_fields.items():
-                self.metadata.fields[key] = value
-
-        lineno = self.record.lineno
-        file_name = str(self.record.filename)
-        self.metadata.fields['py_file'] = f'{file_name}:{lineno}'
-        self.metadata.fields['py_logger'] = str(self.record.name)
-
-        return self.metadata
-
-    def get_fragments(self) -> StyleAndTextTuples:
-        """Return this log line as a list of FormattedText tuples."""
-        # Parse metadata if any.
-        if self.metadata is None:
-            self.update_metadata()
-
-        # Create prompt_toolkit FormattedText tuples based on the log ANSI
-        # escape sequences.
-        if self.fragment_cache is None:
-            self.fragment_cache = ANSI(self.formatted_log +
-                                       '\n'  # Add a trailing linebreak
-                                       ).__pt_formatted_text__()
-
-        return self.fragment_cache
diff --git a/pw_console/py/pw_console/log_pane.py b/pw_console/py/pw_console/log_pane.py
deleted file mode 100644
index eb4fbfd..0000000
--- a/pw_console/py/pw_console/log_pane.py
+++ /dev/null
@@ -1,678 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""LogPane class."""
-
-import functools
-import logging
-import re
-from typing import Any, List, Optional, Union, TYPE_CHECKING
-
-from prompt_toolkit.application.current import get_app
-from prompt_toolkit.filters import (
-    Condition,
-    has_focus,
-)
-from prompt_toolkit.formatted_text import StyleAndTextTuples
-from prompt_toolkit.key_binding import (
-    KeyBindings,
-    KeyPressEvent,
-    KeyBindingsBase,
-)
-from prompt_toolkit.layout import (
-    ConditionalContainer,
-    Float,
-    FloatContainer,
-    UIContent,
-    UIControl,
-    VerticalAlign,
-    Window,
-)
-from prompt_toolkit.mouse_events import MouseEvent, MouseEventType, MouseButton
-
-import pw_console.widgets.checkbox
-import pw_console.style
-from pw_console.log_view import LogView
-from pw_console.log_pane_toolbars import (
-    LineInfoBar,
-    TableToolbar,
-)
-from pw_console.log_pane_saveas_dialog import LogPaneSaveAsDialog
-from pw_console.log_pane_selection_dialog import LogPaneSelectionDialog
-from pw_console.log_store import LogStore
-from pw_console.search_toolbar import SearchToolbar
-from pw_console.filter_toolbar import FilterToolbar
-from pw_console.widgets import (
-    ToolbarButton,
-    WindowPane,
-    WindowPaneHSplit,
-    WindowPaneToolbar,
-)
-
-if TYPE_CHECKING:
-    from pw_console.console_app import ConsoleApp
-
-_LOG_OUTPUT_SCROLL_AMOUNT = 5
-_LOG = logging.getLogger(__package__)
-
-
-class LogContentControl(UIControl):
-    """LogPane prompt_toolkit UIControl for displaying LogContainer lines."""
-    def __init__(self, log_pane: 'LogPane') -> None:
-        # pylint: disable=too-many-locals
-        self.log_pane = log_pane
-        self.log_view = log_pane.log_view
-
-        # Mouse drag visual selection flags.
-        self.visual_select_mode_drag_start = False
-        self.visual_select_mode_drag_stop = False
-
-        self.uicontent: Optional[UIContent] = None
-        self.lines: List[StyleAndTextTuples] = []
-
-        # Key bindings.
-        key_bindings = KeyBindings()
-        register = log_pane.application.prefs.register_keybinding
-
-        @register('log-pane.shift-line-to-top', key_bindings)
-        def _shift_log_to_top(_event: KeyPressEvent) -> None:
-            """Shift the selected log line to the top."""
-            self.log_view.move_selected_line_to_top()
-
-        @register('log-pane.shift-line-to-center', key_bindings)
-        def _shift_log_to_center(_event: KeyPressEvent) -> None:
-            """Shift the selected log line to the center."""
-            self.log_view.center_log_line()
-
-        @register('log-pane.toggle-wrap-lines', key_bindings)
-        def _toggle_wrap_lines(_event: KeyPressEvent) -> None:
-            """Toggle log line wrapping."""
-            self.log_pane.toggle_wrap_lines()
-
-        @register('log-pane.toggle-table-view', key_bindings)
-        def _toggle_table_view(_event: KeyPressEvent) -> None:
-            """Toggle table view."""
-            self.log_pane.toggle_table_view()
-
-        @register('log-pane.duplicate-log-pane', key_bindings)
-        def _duplicate(_event: KeyPressEvent) -> None:
-            """Duplicate this log pane."""
-            self.log_pane.duplicate()
-
-        @register('log-pane.remove-duplicated-log-pane', key_bindings)
-        def _delete(_event: KeyPressEvent) -> None:
-            """Remove log pane."""
-            if self.log_pane.is_a_duplicate:
-                self.log_pane.application.window_manager.remove_pane(
-                    self.log_pane)
-
-        @register('log-pane.clear-history', key_bindings)
-        def _clear_history(_event: KeyPressEvent) -> None:
-            """Clear log pane history."""
-            self.log_pane.clear_history()
-
-        @register('log-pane.scroll-to-top', key_bindings)
-        def _scroll_to_top(_event: KeyPressEvent) -> None:
-            """Scroll to top."""
-            self.log_view.scroll_to_top()
-
-        @register('log-pane.scroll-to-bottom', key_bindings)
-        def _scroll_to_bottom(_event: KeyPressEvent) -> None:
-            """Scroll to bottom."""
-            self.log_view.scroll_to_bottom()
-
-        @register('log-pane.toggle-follow', key_bindings)
-        def _toggle_follow(_event: KeyPressEvent) -> None:
-            """Toggle log line following."""
-            self.log_pane.toggle_follow()
-
-        @register('log-pane.move-cursor-up', key_bindings)
-        def _up(_event: KeyPressEvent) -> None:
-            """Move cursor up."""
-            self.log_view.scroll_up()
-
-        @register('log-pane.move-cursor-down', key_bindings)
-        def _down(_event: KeyPressEvent) -> None:
-            """Move cursor down."""
-            self.log_view.scroll_down()
-
-        @register('log-pane.visual-select-up', key_bindings)
-        def _visual_select_up(_event: KeyPressEvent) -> None:
-            """Select previous log line."""
-            self.log_view.visual_select_up()
-
-        @register('log-pane.visual-select-down', key_bindings)
-        def _visual_select_down(_event: KeyPressEvent) -> None:
-            """Select next log line."""
-            self.log_view.visual_select_down()
-
-        @register('log-pane.scroll-page-up', key_bindings)
-        def _pageup(_event: KeyPressEvent) -> None:
-            """Scroll the logs up by one page."""
-            self.log_view.scroll_up_one_page()
-
-        @register('log-pane.scroll-page-down', key_bindings)
-        def _pagedown(_event: KeyPressEvent) -> None:
-            """Scroll the logs down by one page."""
-            self.log_view.scroll_down_one_page()
-
-        @register('log-pane.save-copy', key_bindings)
-        def _start_saveas(_event: KeyPressEvent) -> None:
-            """Save logs to a file."""
-            self.log_pane.start_saveas()
-
-        @register('log-pane.search', key_bindings)
-        def _start_search(_event: KeyPressEvent) -> None:
-            """Start searching."""
-            self.log_pane.start_search()
-
-        @register('log-pane.search-next-match', key_bindings)
-        def _next_search(_event: KeyPressEvent) -> None:
-            """Next search match."""
-            self.log_view.search_forwards()
-
-        @register('log-pane.search-previous-match', key_bindings)
-        def _previous_search(_event: KeyPressEvent) -> None:
-            """Previous search match."""
-            self.log_view.search_backwards()
-
-        @register('log-pane.visual-select-all', key_bindings)
-        def _select_all_logs(_event: KeyPressEvent) -> None:
-            """Clear search."""
-            self.log_pane.log_view.visual_select_all()
-
-        @register('log-pane.deselect-cancel-search', key_bindings)
-        def _clear_search_and_selection(_event: KeyPressEvent) -> None:
-            """Clear selection or search."""
-            if self.log_pane.log_view.visual_select_mode:
-                self.log_pane.log_view.clear_visual_selection()
-            elif self.log_pane.search_bar_active:
-                self.log_pane.search_toolbar.cancel_search()
-
-        @register('log-pane.search-apply-filter', key_bindings)
-        def _apply_filter(_event: KeyPressEvent) -> None:
-            """Apply current search as a filter."""
-            self.log_pane.search_toolbar.close_search_bar()
-            self.log_view.apply_filter()
-
-        @register('log-pane.clear-filters', key_bindings)
-        def _clear_filter(_event: KeyPressEvent) -> None:
-            """Reset / erase active filters."""
-            self.log_view.clear_filters()
-
-        self.key_bindings: KeyBindingsBase = key_bindings
-
-    def is_focusable(self) -> bool:
-        return True
-
-    def get_key_bindings(self) -> Optional[KeyBindingsBase]:
-        return self.key_bindings
-
-    def preferred_width(self, max_available_width: int) -> int:
-        """Return the width of the longest line."""
-        line_lengths = [len(l) for l in self.lines]
-        return max(line_lengths)
-
-    def preferred_height(
-        self,
-        width: int,
-        max_available_height: int,
-        wrap_lines: bool,
-        get_line_prefix,
-    ) -> Optional[int]:
-        """Return the preferred height for the log lines."""
-        content = self.create_content(width, None)
-        return content.line_count
-
-    def create_content(self, width: int, height: Optional[int]) -> UIContent:
-        # Update lines to render
-        self.lines = self.log_view.render_content()
-
-        # Create a UIContent instance if none exists
-        if self.uicontent is None:
-            self.uicontent = UIContent(get_line=lambda i: self.lines[i],
-                                       line_count=len(self.lines),
-                                       show_cursor=False)
-
-        # Update line_count
-        self.uicontent.line_count = len(self.lines)
-
-        return self.uicontent
-
-    def mouse_handler(self, mouse_event: MouseEvent):
-        """Mouse handler for this control."""
-        mouse_position = mouse_event.position
-
-        # Left mouse button release should:
-        # 1. check if a mouse drag just completed.
-        # 2. If not in focus, switch focus to this log pane
-        #    If in focus, move the cursor to that position.
-        if (mouse_event.event_type == MouseEventType.MOUSE_UP
-                and mouse_event.button == MouseButton.LEFT):
-
-            # If a drag was in progress and this is the first mouse release
-            # press, set the stop flag.
-            if (self.visual_select_mode_drag_start
-                    and not self.visual_select_mode_drag_stop):
-                self.visual_select_mode_drag_stop = True
-
-            if not has_focus(self)():
-                # Focus the save as dialog if open.
-                if self.log_pane.saveas_dialog_active:
-                    get_app().layout.focus(self.log_pane.saveas_dialog)
-                # Focus the search bar if open.
-                elif self.log_pane.search_bar_active:
-                    get_app().layout.focus(self.log_pane.search_toolbar)
-                # Otherwise, focus on the log pane content.
-                else:
-                    get_app().layout.focus(self)
-                # Mouse event handled, return None.
-                return None
-
-            # Log pane in focus already, move the cursor to the position of the
-            # mouse click.
-            self.log_pane.log_view.scroll_to_position(mouse_position)
-            # Mouse event handled, return None.
-            return None
-
-        # Mouse drag with left button should start selecting lines.
-        # The log pane does not need to be in focus to start this.
-        if (mouse_event.event_type == MouseEventType.MOUSE_MOVE
-                and mouse_event.button == MouseButton.LEFT):
-            # If a previous mouse drag was completed, clear the selection.
-            if (self.visual_select_mode_drag_start
-                    and self.visual_select_mode_drag_stop):
-                self.log_pane.log_view.clear_visual_selection()
-            # Drag select in progress, set flags accordingly.
-            self.visual_select_mode_drag_start = True
-            self.visual_select_mode_drag_stop = False
-
-            self.log_pane.log_view.visual_select_line(mouse_position)
-            # Mouse event handled, return None.
-            return None
-
-        # Mouse wheel events should move the cursor +/- some amount of lines
-        # even if this pane is not in focus.
-        if mouse_event.event_type == MouseEventType.SCROLL_DOWN:
-            self.log_pane.log_view.scroll_down(lines=_LOG_OUTPUT_SCROLL_AMOUNT)
-            # Mouse event handled, return None.
-            return None
-
-        if mouse_event.event_type == MouseEventType.SCROLL_UP:
-            self.log_pane.log_view.scroll_up(lines=_LOG_OUTPUT_SCROLL_AMOUNT)
-            # Mouse event handled, return None.
-            return None
-
-        # Mouse event not handled, return NotImplemented.
-        return NotImplemented
-
-
-class LogPane(WindowPane):
-    """LogPane class."""
-
-    # pylint: disable=too-many-instance-attributes,too-many-public-methods
-
-    def __init__(
-        self,
-        application: Any,
-        pane_title: str = 'Logs',
-        log_store: Optional[LogStore] = None,
-    ):
-        super().__init__(application, pane_title)
-
-        # TODO(tonymd): Read these settings from a project (or user) config.
-        self.wrap_lines = False
-        self._table_view = True
-        self.is_a_duplicate = False
-
-        # Create the log container which stores and handles incoming logs.
-        self.log_view: LogView = LogView(self,
-                                         self.application,
-                                         log_store=log_store)
-
-        # Log pane size variables. These are updated just befor rendering the
-        # pane by the LogLineHSplit class.
-        self.current_log_pane_width = 0
-        self.current_log_pane_height = 0
-        self.last_log_pane_width = None
-        self.last_log_pane_height = None
-
-        # Search tracking
-        self.search_bar_active = False
-        self.search_toolbar = SearchToolbar(self)
-        self.filter_toolbar = FilterToolbar(self)
-
-        self.saveas_dialog = LogPaneSaveAsDialog(self)
-        self.saveas_dialog_active = False
-        self.visual_selection_dialog = LogPaneSelectionDialog(self)
-
-        # Table header bar, only shown if table view is active.
-        self.table_header_toolbar = TableToolbar(self)
-
-        # Create the bottom toolbar for the whole log pane.
-        self.bottom_toolbar = WindowPaneToolbar(self)
-        self.bottom_toolbar.add_button(
-            ToolbarButton('/', 'Search', self.start_search))
-        self.bottom_toolbar.add_button(
-            ToolbarButton('Ctrl-o', 'Save', self.start_saveas))
-        self.bottom_toolbar.add_button(
-            ToolbarButton('f',
-                          'Follow',
-                          self.toggle_follow,
-                          is_checkbox=True,
-                          checked=lambda: self.log_view.follow))
-        self.bottom_toolbar.add_button(
-            ToolbarButton('t',
-                          'Table',
-                          self.toggle_table_view,
-                          is_checkbox=True,
-                          checked=lambda: self.table_view))
-        self.bottom_toolbar.add_button(
-            ToolbarButton('w',
-                          'Wrap',
-                          self.toggle_wrap_lines,
-                          is_checkbox=True,
-                          checked=lambda: self.wrap_lines))
-        self.bottom_toolbar.add_button(
-            ToolbarButton('C', 'Clear', self.clear_history))
-
-        self.log_content_control = LogContentControl(self)
-
-        self.log_display_window = Window(
-            content=self.log_content_control,
-            # Scrolling is handled by LogScreen
-            allow_scroll_beyond_bottom=False,
-            # Line wrapping is handled by LogScreen
-            wrap_lines=False,
-            # Selected line highlighting is handled by LogScreen
-            cursorline=False,
-            # Don't make the window taller to fill the parent split container.
-            # Window should match the height of the log line content. This will
-            # also allow the parent HSplit to justify the content to the bottom
-            dont_extend_height=True,
-            # Window width should be extended to make backround highlighting
-            # extend to the end of the container. Otherwise backround colors
-            # will only appear until the end of the log line.
-            dont_extend_width=False,
-            # Needed for log lines ANSI sequences that don't specify foreground
-            # or background colors.
-            style=functools.partial(pw_console.style.get_pane_style, self),
-        )
-
-        # Root level container
-        self.container = ConditionalContainer(
-            FloatContainer(
-                # Horizonal split containing the log lines and the toolbar.
-                WindowPaneHSplit(
-                    self,  # LogPane reference
-                    [
-                        self.table_header_toolbar,
-                        self.log_display_window,
-                        self.filter_toolbar,
-                        self.search_toolbar,
-                        self.bottom_toolbar,
-                    ],
-                    # Align content with the bottom of the container.
-                    align=VerticalAlign.BOTTOM,
-                    height=lambda: self.height,
-                    width=lambda: self.width,
-                    style=functools.partial(pw_console.style.get_pane_style,
-                                            self),
-                ),
-                floats=[
-                    Float(top=0, right=0, height=1, content=LineInfoBar(self)),
-                    Float(top=0,
-                          right=0,
-                          height=LogPaneSelectionDialog.DIALOG_HEIGHT,
-                          content=self.visual_selection_dialog),
-                    Float(top=3,
-                          left=2,
-                          right=2,
-                          height=LogPaneSaveAsDialog.DIALOG_HEIGHT + 2,
-                          content=self.saveas_dialog),
-                ]),
-            filter=Condition(lambda: self.show_pane))
-
-    @property
-    def table_view(self):
-        return self._table_view
-
-    @table_view.setter
-    def table_view(self, table_view):
-        self._table_view = table_view
-
-    def menu_title(self):
-        """Return the title to display in the Window menu."""
-        title = self.pane_title()
-
-        # List active filters
-        if self.log_view.filtering_on:
-            title += ' (FILTERS: '
-            title += ' '.join([
-                log_filter.pattern()
-                for log_filter in self.log_view.filters.values()
-            ])
-            title += ')'
-        return title
-
-    def append_pane_subtitle(self, text):
-        if not self._pane_subtitle:
-            self._pane_subtitle = text
-        else:
-            self._pane_subtitle = self._pane_subtitle + ', ' + text
-
-    def pane_subtitle(self) -> str:
-        if not self._pane_subtitle:
-            return ', '.join(self.log_view.log_store.channel_counts.keys())
-        logger_names = self._pane_subtitle.split(', ')
-        additional_text = ''
-        if len(logger_names) > 1:
-            additional_text = ' + {} more'.format(len(logger_names))
-
-        return logger_names[0] + additional_text
-
-    def start_search(self):
-        """Show the search bar to begin a search."""
-        # Show the search bar
-        self.search_bar_active = True
-        # Focus on the search bar
-        self.application.focus_on_container(self.search_toolbar)
-
-    def start_saveas(self, **export_kwargs) -> bool:
-        """Show the saveas bar to begin saving logs to a file."""
-        # Show the search bar
-        self.saveas_dialog_active = True
-        # Set export options if any
-        self.saveas_dialog.set_export_options(**export_kwargs)
-        # Focus on the search bar
-        self.application.focus_on_container(self.saveas_dialog)
-        return True
-
-    def pane_resized(self) -> bool:
-        """Return True if the current window size has changed."""
-        return (self.last_log_pane_width != self.current_log_pane_width
-                or self.last_log_pane_height != self.current_log_pane_height)
-
-    def update_pane_size(self, width, height):
-        """Save width and height of the log pane for the current UI render
-        pass."""
-        if width:
-            self.last_log_pane_width = self.current_log_pane_width
-            self.current_log_pane_width = width
-        if height:
-            # Subtract the height of the bottom toolbar
-            height -= WindowPaneToolbar.TOOLBAR_HEIGHT
-            if self._table_view:
-                height -= TableToolbar.TOOLBAR_HEIGHT
-            if self.search_bar_active:
-                height -= SearchToolbar.TOOLBAR_HEIGHT
-            if self.log_view.filtering_on:
-                height -= FilterToolbar.TOOLBAR_HEIGHT
-            self.last_log_pane_height = self.current_log_pane_height
-            self.current_log_pane_height = height
-
-    def toggle_table_view(self):
-        """Enable or disable table view."""
-        self._table_view = not self._table_view
-        self.log_view.view_mode_changed()
-        self.redraw_ui()
-
-    def toggle_wrap_lines(self):
-        """Enable or disable line wraping/truncation."""
-        self.wrap_lines = not self.wrap_lines
-        self.log_view.view_mode_changed()
-        self.redraw_ui()
-
-    def toggle_follow(self):
-        """Enable or disable following log lines."""
-        self.log_view.toggle_follow()
-        self.redraw_ui()
-
-    def clear_history(self):
-        """Erase stored log lines."""
-        self.log_view.clear_scrollback()
-        self.redraw_ui()
-
-    def get_all_key_bindings(self) -> List:
-        """Return all keybinds for this pane."""
-        # Return log content control keybindings
-        return [self.log_content_control.get_key_bindings()]
-
-    def get_all_menu_options(self) -> List:
-        """Return all menu options for the log pane."""
-
-        options = [
-            # Menu separator
-            ('-', None),
-            (
-                'Save/Export a copy',
-                self.start_saveas,
-            ),
-            ('-', None),
-            (
-                '{check} Line wrapping'.format(
-                    check=pw_console.widgets.checkbox.to_checkbox_text(
-                        self.wrap_lines, end='')),
-                self.toggle_wrap_lines,
-            ),
-            (
-                '{check} Table view'.format(
-                    check=pw_console.widgets.checkbox.to_checkbox_text(
-                        self._table_view, end='')),
-                self.toggle_table_view,
-            ),
-            (
-                '{check} Follow'.format(
-                    check=pw_console.widgets.checkbox.to_checkbox_text(
-                        self.log_view.follow, end='')),
-                self.toggle_follow,
-            ),
-            # Menu separator
-            ('-', None),
-            (
-                'Clear history',
-                self.clear_history,
-            ),
-            (
-                'Duplicate pane',
-                self.duplicate,
-            ),
-        ]
-        if self.is_a_duplicate:
-            options += [(
-                'Remove/Delete pane',
-                functools.partial(self.application.window_manager.remove_pane,
-                                  self),
-            )]
-
-        # Search / Filter section
-        options += [
-            # Menu separator
-            ('-', None),
-            (
-                'Hide search highlighting',
-                self.log_view.disable_search_highlighting,
-            ),
-            (
-                'Create filter from search results',
-                self.log_view.apply_filter,
-            ),
-            (
-                'Clear/Reset active filters',
-                self.log_view.clear_filters,
-            ),
-        ]
-
-        return options
-
-    def apply_filters_from_config(self, window_options) -> None:
-        if 'filters' not in window_options:
-            return
-
-        for field, criteria in window_options['filters'].items():
-            for matcher_name, search_string in criteria.items():
-                inverted = matcher_name.endswith('-inverted')
-                matcher_name = re.sub(r'-inverted$', '', matcher_name)
-                if field == 'all':
-                    field = None
-                if self.log_view.new_search(
-                        search_string,
-                        invert=inverted,
-                        field=field,
-                        search_matcher=matcher_name,
-                        interactive=False,
-                ):
-                    self.log_view.install_new_filter()
-
-    def create_duplicate(self) -> 'LogPane':
-        """Create a duplicate of this LogView."""
-        new_pane = LogPane(self.application, pane_title=self.pane_title())
-        # Set the log_store
-        log_store = self.log_view.log_store
-        new_pane.log_view.log_store = log_store
-        # Register the duplicate pane as a viewer
-        log_store.register_viewer(new_pane.log_view)
-
-        # Set any existing search state.
-        new_pane.log_view.search_text = self.log_view.search_text
-        new_pane.log_view.search_filter = self.log_view.search_filter
-        new_pane.log_view.search_matcher = self.log_view.search_matcher
-        new_pane.log_view.search_highlight = self.log_view.search_highlight
-
-        # Mark new pane as a duplicate so it can be deleted.
-        new_pane.is_a_duplicate = True
-        return new_pane
-
-    def duplicate(self) -> None:
-        new_pane = self.create_duplicate()
-        # Add the new pane.
-        self.application.window_manager.add_pane(new_pane)
-
-    def add_log_handler(self,
-                        logger: Union[str, logging.Logger],
-                        level_name: Optional[str] = None) -> None:
-        """Add a log handlers to this LogPane."""
-
-        if isinstance(logger, logging.Logger):
-            logger_instance = logger
-        elif isinstance(logger, str):
-            logger_instance = logging.getLogger(logger)
-
-        if level_name:
-            if not hasattr(logging, level_name):
-                raise Exception(f'Unknown log level: {level_name}')
-            logger_instance.level = getattr(logging, level_name, logging.INFO)
-        logger_instance.addHandler(self.log_view.log_store  # type: ignore
-                                   )
-        self.append_pane_subtitle(  # type: ignore
-            logger_instance.name)
diff --git a/pw_console/py/pw_console/log_pane_saveas_dialog.py b/pw_console/py/pw_console/log_pane_saveas_dialog.py
deleted file mode 100644
index f142a8a..0000000
--- a/pw_console/py/pw_console/log_pane_saveas_dialog.py
+++ /dev/null
@@ -1,292 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""LogPane Save As Dialog."""
-
-from __future__ import annotations
-import functools
-from pathlib import Path
-from typing import Optional, TYPE_CHECKING
-
-from prompt_toolkit.buffer import Buffer
-from prompt_toolkit.key_binding import KeyBindings, KeyPressEvent
-from prompt_toolkit.completion import PathCompleter
-from prompt_toolkit.filters import Condition
-from prompt_toolkit.history import InMemoryHistory
-from prompt_toolkit.layout import (
-    ConditionalContainer,
-    FormattedTextControl,
-    HSplit,
-    Window,
-    WindowAlign,
-)
-from prompt_toolkit.widgets import TextArea
-from prompt_toolkit.validation import (
-    ValidationError,
-    Validator,
-)
-
-import pw_console.widgets.checkbox
-import pw_console.widgets.border
-import pw_console.widgets.mouse_handlers
-import pw_console.style
-
-if TYPE_CHECKING:
-    from pw_console.log_pane import LogPane
-
-
-class PathValidator(Validator):
-    """Validation of file path input."""
-    def validate(self, document):
-        """Check input path leads to a valid parent directory."""
-        target_path = Path(document.text).expanduser()
-
-        if not target_path.parent.exists():
-            raise ValidationError(
-                # Set cursor position to the end
-                len(document.text),
-                "Directory doesn't exist: %s" % document.text)
-
-        if target_path.is_dir():
-            raise ValidationError(
-                # Set cursor position to the end
-                len(document.text),
-                "File input is an existing directory: %s" % document.text)
-
-
-class LogPaneSaveAsDialog(ConditionalContainer):
-    """Dialog box for saving logs to a file."""
-    # Height of the dialog box contens in lines of text.
-    DIALOG_HEIGHT = 3
-
-    def __init__(self, log_pane: 'LogPane'):
-        self.log_pane = log_pane
-
-        self.path_validator = PathValidator()
-
-        self._export_with_table_formatting: bool = True
-        self._export_with_selected_lines_only: bool = False
-
-        self.starting_file_path: str = str(Path.cwd())
-
-        self.input_field = TextArea(
-            prompt=[
-                ('class:saveas-dialog-setting', 'File: ',
-                 functools.partial(pw_console.widgets.mouse_handlers.on_click,
-                                   self.focus_self))
-            ],
-            # Pre-fill the current working directory.
-            text=self.starting_file_path,
-            focusable=True,
-            focus_on_click=True,
-            scrollbar=False,
-            multiline=False,
-            height=1,
-            dont_extend_height=True,
-            dont_extend_width=False,
-            accept_handler=self._saveas_accept_handler,
-            validator=self.path_validator,
-            history=InMemoryHistory(),
-            completer=PathCompleter(expanduser=True),
-        )
-
-        self.input_field.buffer.cursor_position = len(self.starting_file_path)
-
-        settings_bar_control = FormattedTextControl(
-            self.get_settings_fragments)
-        settings_bar_window = Window(content=settings_bar_control,
-                                     height=1,
-                                     align=WindowAlign.LEFT,
-                                     dont_extend_width=False)
-
-        action_bar_control = FormattedTextControl(self.get_action_fragments)
-        action_bar_window = Window(content=action_bar_control,
-                                   height=1,
-                                   align=WindowAlign.RIGHT,
-                                   dont_extend_width=False)
-
-        # Add additional keybindings for the input_field text area.
-        key_bindings = KeyBindings()
-        register = self.log_pane.application.prefs.register_keybinding
-
-        @register('save-as-dialog.cancel', key_bindings)
-        def _close_saveas_dialog(_event: KeyPressEvent) -> None:
-            """Close save as dialog."""
-            self.close_dialog()
-
-        self.input_field.control.key_bindings = key_bindings
-
-        super().__init__(
-            pw_console.widgets.border.create_border(
-                HSplit(
-                    [
-                        settings_bar_window,
-                        self.input_field,
-                        action_bar_window,
-                    ],
-                    height=LogPaneSaveAsDialog.DIALOG_HEIGHT,
-                    style='class:saveas-dialog',
-                ),
-                LogPaneSaveAsDialog.DIALOG_HEIGHT,
-                border_style='class:saveas-dialog-border',
-                left_margin_columns=1,
-            ),
-            filter=Condition(lambda: self.log_pane.saveas_dialog_active),
-        )
-
-    def focus_self(self):
-        self.log_pane.application.application.layout.focus(self)
-
-    def close_dialog(self):
-        """Close this dialog."""
-        self.log_pane.saveas_dialog_active = False
-        self.log_pane.application.focus_on_container(self.log_pane)
-        self.log_pane.redraw_ui()
-
-    def _toggle_table_formatting(self):
-        self._export_with_table_formatting = (
-            not self._export_with_table_formatting)
-
-    def _toggle_selected_lines(self):
-        self._export_with_selected_lines_only = (
-            not self._export_with_selected_lines_only)
-
-    def set_export_options(self,
-                           table_format: Optional[bool] = None,
-                           selected_lines_only: Optional[bool] = None) -> None:
-        # Allows external callers such as the line selection dialog to set
-        # export format options.
-        if table_format is not None:
-            self._export_with_table_formatting = table_format
-
-        if selected_lines_only is not None:
-            self._export_with_selected_lines_only = selected_lines_only
-
-    def save_action(self):
-        """Trigger save file execution on mouse click.
-
-        This ultimately runs LogPaneSaveAsDialog._saveas_accept_handler()."""
-        self.input_field.buffer.validate_and_handle()
-
-    def _saveas_accept_handler(self, buff: Buffer) -> bool:
-        """Function run when hitting Enter in the input_field."""
-        input_text = buff.text
-        if len(input_text) == 0:
-            self.close_dialog()
-            # Don't save anything if empty input.
-            return False
-
-        if self.log_pane.log_view.export_logs(
-                file_name=input_text,
-                use_table_formatting=self._export_with_table_formatting,
-                selected_lines_only=self._export_with_selected_lines_only):
-            self.close_dialog()
-            # Reset selected_lines_only
-            self.set_export_options(selected_lines_only=False)
-            # Erase existing input text.
-            return False
-
-        # Keep existing text if error
-        return True
-
-    def get_settings_fragments(self):
-        """Return FormattedText with current save settings."""
-        # Mouse handlers
-        focus = functools.partial(pw_console.widgets.mouse_handlers.on_click,
-                                  self.focus_self)
-        toggle_table_formatting = functools.partial(
-            pw_console.widgets.mouse_handlers.on_click,
-            self._toggle_table_formatting)
-        toggle_selected_lines = functools.partial(
-            pw_console.widgets.mouse_handlers.on_click,
-            self._toggle_selected_lines)
-
-        # Separator should have the focus mouse handler so clicking on any
-        # whitespace focuses the input field.
-        separator_text = ('', '  ', focus)
-
-        # Default button style
-        button_style = 'class:toolbar-button-inactive'
-
-        fragments = [('class:saveas-dialog-title', 'Save as File', focus)]
-        fragments.append(separator_text)
-
-        # Table checkbox
-        fragments.extend(
-            pw_console.widgets.checkbox.to_checkbox_with_keybind_indicator(
-                checked=self._export_with_table_formatting,
-                key='',  # No key shortcut help text
-                description='Table Formatting',
-                mouse_handler=toggle_table_formatting,
-                base_style=button_style))
-
-        # Two space separator
-        fragments.append(separator_text)
-
-        # Selected lines checkbox
-        fragments.extend(
-            pw_console.widgets.checkbox.to_checkbox_with_keybind_indicator(
-                checked=self._export_with_selected_lines_only,
-                key='',  # No key shortcut help text
-                description='Selected Lines Only',
-                mouse_handler=toggle_selected_lines,
-                base_style=button_style))
-
-        # Two space separator
-        fragments.append(separator_text)
-
-        return fragments
-
-    def get_action_fragments(self):
-        """Return FormattedText with the save action buttons."""
-        # Mouse handlers
-        focus = functools.partial(pw_console.widgets.mouse_handlers.on_click,
-                                  self.focus_self)
-        cancel = functools.partial(pw_console.widgets.mouse_handlers.on_click,
-                                   self.close_dialog)
-        save = functools.partial(pw_console.widgets.mouse_handlers.on_click,
-                                 self.save_action)
-
-        # Separator should have the focus mouse handler so clicking on any
-        # whitespace focuses the input field.
-        separator_text = ('', '  ', focus)
-
-        # Default button style
-        button_style = 'class:toolbar-button-inactive'
-
-        fragments = [separator_text]
-        # Cancel button
-        fragments.extend(
-            pw_console.widgets.checkbox.to_keybind_indicator(
-                key='Ctrl-c',
-                description='Cancel',
-                mouse_handler=cancel,
-                base_style=button_style,
-            ))
-
-        # Two space separator
-        fragments.append(separator_text)
-
-        # Save button
-        fragments.extend(
-            pw_console.widgets.checkbox.to_keybind_indicator(
-                key='Enter',
-                description='Save',
-                mouse_handler=save,
-                base_style=button_style,
-            ))
-
-        # One space separator
-        fragments.append(('', ' ', focus))
-
-        return fragments
diff --git a/pw_console/py/pw_console/log_pane_selection_dialog.py b/pw_console/py/pw_console/log_pane_selection_dialog.py
deleted file mode 100644
index 9c76f51..0000000
--- a/pw_console/py/pw_console/log_pane_selection_dialog.py
+++ /dev/null
@@ -1,191 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Dialog box for log selection functions."""
-
-from __future__ import annotations
-import functools
-from typing import TYPE_CHECKING
-
-from prompt_toolkit.filters import Condition
-from prompt_toolkit.layout import (
-    ConditionalContainer,
-    FormattedTextControl,
-    Window,
-    WindowAlign,
-)
-
-import pw_console.style
-import pw_console.widgets.checkbox
-import pw_console.widgets.border
-import pw_console.widgets.mouse_handlers
-
-if TYPE_CHECKING:
-    from pw_console.log_pane import LogPane
-
-
-class LogPaneSelectionDialog(ConditionalContainer):
-    """Dialog box for showing log selection functions.
-
-    Displays number of lines selected, buttons for copying to the clipboar or
-    saving to a file, and buttons to select all or cancel (clear) the
-    selection."""
-    # Height of the dialog box contens in lines of text.
-    DIALOG_HEIGHT = 3
-
-    def __init__(self, log_pane: 'LogPane'):
-        self.log_pane = log_pane
-        self.log_view = log_pane.log_view
-
-        self._markdown_flag: bool = False
-        self._table_flag: bool = True
-
-        selection_bar_control = FormattedTextControl(self.get_fragments)
-        selection_bar_window = Window(content=selection_bar_control,
-                                      height=1,
-                                      align=WindowAlign.LEFT,
-                                      dont_extend_width=False,
-                                      style='class:selection-dialog')
-
-        super().__init__(
-            pw_console.widgets.border.create_border(
-                selection_bar_window,
-                (LogPaneSelectionDialog.DIALOG_HEIGHT - 1),
-                border_style='class:selection-dialog-border',
-                base_style='class:selection-dialog-default-fg',
-                top=False,
-                right=False,
-            ),
-            filter=Condition(lambda: self.log_view.visual_select_mode))
-
-    def focus_log_pane(self):
-        self.log_pane.application.focus_on_container(self.log_pane)
-
-    def _toggle_markdown_flag(self) -> None:
-        self._markdown_flag = not self._markdown_flag
-
-    def _toggle_table_flag(self) -> None:
-        self._table_flag = not self._table_flag
-
-    def _select_all(self) -> None:
-        self.log_view.visual_select_all()
-
-    def _select_none(self) -> None:
-        self.log_view.clear_visual_selection()
-
-    def _copy_selection(self) -> None:
-        if self.log_view.export_logs(
-                to_clipboard=True,
-                use_table_formatting=self._table_flag,
-                selected_lines_only=True,
-                add_markdown_fence=self._markdown_flag,
-        ):
-            self._select_none()
-
-    def _saveas_file(self) -> None:
-        self.log_pane.start_saveas(table_format=self._table_flag,
-                                   selected_lines_only=True)
-
-    def get_fragments(self):
-        """Return formatted text tuples for both rows of the selection
-        dialog."""
-
-        focus = functools.partial(pw_console.widgets.mouse_handlers.on_click,
-                                  self.focus_log_pane)
-
-        one_space = ('', ' ', focus)
-        two_spaces = ('', '  ', focus)
-        select_all = functools.partial(
-            pw_console.widgets.mouse_handlers.on_click, self._select_all)
-        select_none = functools.partial(
-            pw_console.widgets.mouse_handlers.on_click, self._select_none)
-
-        copy_selection = functools.partial(
-            pw_console.widgets.mouse_handlers.on_click, self._copy_selection)
-        saveas_file = functools.partial(
-            pw_console.widgets.mouse_handlers.on_click, self._saveas_file)
-        toggle_markdown = functools.partial(
-            pw_console.widgets.mouse_handlers.on_click,
-            self._toggle_markdown_flag)
-        toggle_table = functools.partial(
-            pw_console.widgets.mouse_handlers.on_click,
-            self._toggle_table_flag)
-
-        button_style = 'class:toolbar-button-inactive'
-
-        # First row of text
-        fragments = [('class:selection-dialog-title', ' {} Selected '.format(
-            self.log_view.visual_selected_log_count()), focus), one_space,
-                     ('class:selection-dialog-default-fg', 'Format: ', focus)]
-
-        # Table and Markdown options
-        fragments.extend(
-            pw_console.widgets.checkbox.to_checkbox_with_keybind_indicator(
-                self._table_flag,
-                key='',
-                description='Table',
-                mouse_handler=toggle_table,
-                base_style='class:selection-dialog-default-bg'))
-
-        fragments.extend(
-            pw_console.widgets.checkbox.to_checkbox_with_keybind_indicator(
-                self._markdown_flag,
-                key='',
-                description='Markdown',
-                mouse_handler=toggle_markdown,
-                base_style='class:selection-dialog-default-bg'))
-
-        # Line break
-        fragments.append(('', '\n'))
-
-        # Second row of text
-        fragments.append(one_space)
-
-        fragments.extend(
-            pw_console.widgets.checkbox.to_keybind_indicator(
-                key='Ctrl-c',
-                description='Cancel',
-                mouse_handler=select_none,
-                base_style=button_style,
-            ))
-        fragments.append(two_spaces)
-
-        fragments.extend(
-            pw_console.widgets.checkbox.to_keybind_indicator(
-                key='Ctrl-a',
-                description='Select All',
-                mouse_handler=select_all,
-                base_style=button_style,
-            ))
-        fragments.append(two_spaces)
-
-        fragments.append(one_space)
-        fragments.extend(
-            pw_console.widgets.checkbox.to_keybind_indicator(
-                key='',
-                description='Save as File',
-                mouse_handler=saveas_file,
-                base_style=button_style,
-            ))
-        fragments.append(two_spaces)
-
-        fragments.extend(
-            pw_console.widgets.checkbox.to_keybind_indicator(
-                key='',
-                description='Copy',
-                mouse_handler=copy_selection,
-                base_style=button_style,
-            ))
-        fragments.append(one_space)
-
-        return fragments
diff --git a/pw_console/py/pw_console/log_pane_toolbars.py b/pw_console/py/pw_console/log_pane_toolbars.py
deleted file mode 100644
index 038b953..0000000
--- a/pw_console/py/pw_console/log_pane_toolbars.py
+++ /dev/null
@@ -1,89 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""LogPane Info Toolbar classes."""
-
-from __future__ import annotations
-import functools
-from typing import TYPE_CHECKING
-
-from prompt_toolkit.filters import Condition
-from prompt_toolkit.layout import (
-    ConditionalContainer,
-    FormattedTextControl,
-    VSplit,
-    Window,
-    WindowAlign,
-    HorizontalAlign,
-)
-
-import pw_console.widgets.checkbox
-import pw_console.widgets.mouse_handlers
-import pw_console.style
-
-if TYPE_CHECKING:
-    from pw_console.log_pane import LogPane
-
-
-class LineInfoBar(ConditionalContainer):
-    """One line bar for showing current and total log lines."""
-    def get_tokens(self):
-        """Return formatted text tokens for display."""
-        tokens = ' {} / {} '.format(
-            self.log_pane.log_view.get_current_line() + 1,
-            self.log_pane.log_view.get_total_count(),
-        )
-        return [('', tokens)]
-
-    def __init__(self, log_pane: 'LogPane'):
-        self.log_pane = log_pane
-        info_bar_control = FormattedTextControl(self.get_tokens)
-        info_bar_window = Window(content=info_bar_control,
-                                 align=WindowAlign.RIGHT,
-                                 dont_extend_width=True)
-
-        super().__init__(
-            VSplit([info_bar_window],
-                   height=1,
-                   style=functools.partial(pw_console.style.get_toolbar_style,
-                                           self.log_pane,
-                                           dim=True),
-                   align=HorizontalAlign.RIGHT),
-            # Only show current/total line info if not auto-following
-            # logs. Similar to tmux behavior.
-            filter=Condition(lambda: not self.log_pane.log_view.follow))
-
-
-class TableToolbar(ConditionalContainer):
-    """One line toolbar for showing table headers."""
-    TOOLBAR_HEIGHT = 1
-
-    def __init__(self, log_pane: 'LogPane'):
-        # FormattedText of the table column headers.
-        table_header_bar_control = FormattedTextControl(
-            log_pane.log_view.render_table_header)
-        # Left justify the header content.
-        table_header_bar_window = Window(
-            content=table_header_bar_control,
-            align=WindowAlign.LEFT,
-            dont_extend_width=False,
-        )
-        super().__init__(
-            VSplit([table_header_bar_window],
-                   height=1,
-                   style=functools.partial(pw_console.style.get_toolbar_style,
-                                           log_pane,
-                                           dim=True),
-                   align=HorizontalAlign.LEFT),
-            filter=Condition(lambda: log_pane.table_view and log_pane.log_view.
-                             get_total_count() > 0))
diff --git a/pw_console/py/pw_console/log_screen.py b/pw_console/py/pw_console/log_screen.py
deleted file mode 100644
index e3a0f05..0000000
--- a/pw_console/py/pw_console/log_screen.py
+++ /dev/null
@@ -1,654 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""LogScreen tracks lines to display on screen with a set of ScreenLines."""
-
-from __future__ import annotations
-import collections
-import dataclasses
-import logging
-from typing import Callable, List, Optional, Tuple, TYPE_CHECKING
-
-from prompt_toolkit.formatted_text import (
-    to_formatted_text,
-    StyleAndTextTuples,
-)
-
-from pw_console.log_filter import LogFilter
-from pw_console.text_formatting import (
-    fill_character_width,
-    insert_linebreaks,
-    split_lines,
-)
-
-if TYPE_CHECKING:
-    from pw_console.log_line import LogLine
-    from pw_console.log_pane import LogPane
-
-_LOG = logging.getLogger(__package__)
-
-
-@dataclasses.dataclass
-class ScreenLine:
-    """A single line of text for displaying on screen.
-
-    Instances of ScreenLine are stored in a LogScreen's line_buffer deque. When
-    a new log message is added it may be converted into multiple ScreenLine
-    instances if the text is wrapped across multiple lines.
-
-    For example: say our screen is 80 characters wide and a log message 240
-    characters long needs to be displayed. With line wrapping on we will need
-    240/80 = 3 lines to show the full message. Say also that this single log
-    message is at index #5 in the LogStore classes deque, this is the log_index
-    value. This single log message will then be split into 3 separate
-    ScreenLine instances:
-
-    ::
-        ScreenLine(fragments=[('', 'Log message text line one')],
-                   log_index=5, subline=0, height=3)
-        ScreenLine(fragments=[('', 'Log message text line two')],
-                   log_index=5, subline=1, height=3)
-        ScreenLine(fragments=[('', 'Log message text line three')],
-                   log_index=5, subline=2, height=3)
-
-    Each `fragments` attribute will store the formatted text indended to be
-    direcly drawn to the screen. Since these three lines are all displaying the
-    same log message their `log_index` reference will be the same. The `subline`
-    attribute is the zero-indexed number for this log's wrapped line count and
-    `height` is the total ScreenLines needed to show this log message.
-
-    Continuing with this example say the next two log messages to display both
-    fit on screen with no wrapping. They will both be represented with one
-    ScreenLine each:
-
-    ::
-        ScreenLine(fragments=[('', 'Another log message')],
-                   log_index=6, subline=0, height=1)
-        ScreenLine(fragments=[('', 'Yet another log message')],
-                   log_index=7, subline=0, height=1)
-
-    The `log_index` is different for each since these are both separate
-    logs. The subline is 0 since each line is the first one for this log. Both
-    have a height of 1 since no line wrapping was performed.
-    """
-    # The StyleAndTextTuples for this line ending with a '\n'. These are the raw
-    # prompt_toolkit formatted text tuples to display on screen. The colors and
-    # spacing can change depending on the formatters used in the
-    # LogScreen._get_fragments_per_line() function.
-    fragments: StyleAndTextTuples
-
-    # Log index reference for this screen line. This is the index to where the
-    # log message resides in the parent LogStore.logs deque. It is set to None
-    # if this is an empty ScreenLine. If a log message requires line wrapping
-    # then each resulting ScreenLine instance will have the same log_index
-    # value.
-    #
-    # This log_index may also be the integer index into a LogView.filtered_logs
-    # deque depending on if log messages are being filtered by the user. The
-    # LogScreen class below doesn't need to do anything different in either
-    # case. It's the responsibility of LogScreen.get_log_source() to return the
-    # correct source.
-    #
-    # Note this is NOT an index into LogScreen.line_buffer.
-    log_index: Optional[int] = None
-
-    # Keep track the total height and subline number for this log message.
-    # For example this line could be subline (0, 1, or 2) of a log message with
-    # a total height 3.
-
-    # Subline index.
-    subline: int = 0
-    # Total height in lines of text (also ScreenLine count) that the log message
-    # referred to by log_index requires. When a log message is split across
-    # multiple lines height will be set to the same value for each ScreenLine
-    # instance.
-    height: int = 1
-
-    # Empty lines will have no log_index
-    def empty(self) -> bool:
-        return self.log_index is None
-
-
-@dataclasses.dataclass
-class LogScreen:
-    """LogScreen maintains the state of visible logs on screen.
-
-    It is responsible for moving the cursor_position, prepending and appending
-    log lines as the user moves the cursor."""
-    # Callable functions to retrieve logs and display formatting.
-    get_log_source: Callable[[], Tuple[int, collections.deque[LogLine]]]
-    get_line_wrapping: Callable[[], bool]
-    get_log_formatter: Callable[[], Optional[Callable[[LogLine],
-                                                      StyleAndTextTuples]]]
-    get_search_filter: Callable[[], Optional[LogFilter]]
-    get_search_highlight: Callable[[], bool]
-
-    # Window row of the current cursor position
-    cursor_position: int = 0
-    # Screen width and height in number of characters.
-    width: int = 0
-    height: int = 0
-    # Buffer of literal text lines to be displayed on screen. Each visual line
-    # is represented by a ScreenLine instance and will have a max width equal
-    # to the screen's width. If any given whole log message requires line
-    # wrapping to be displayed it will be represented by multiple ScreenLine
-    # instances in this deque.
-    line_buffer: collections.deque[ScreenLine] = dataclasses.field(
-        default_factory=collections.deque)
-
-    def __post_init__(self) -> None:
-        # Empty screen flag. Will be true if the screen contains only newlines.
-        self._empty: bool = True
-        # Save the last log index when appending. Useful for tracking how many
-        # new lines need appending in follow mode.
-        self.last_appended_log_index: int = 0
-
-    def _fill_top_with_empty_lines(self) -> None:
-        """Add empty lines to fill the remaining empty screen space."""
-        for _ in range(self.height - len(self.line_buffer)):
-            self.line_buffer.appendleft(ScreenLine([('', '')]))
-
-    def clear_screen(self) -> None:
-        """Erase all lines and fill with empty lines."""
-        self.line_buffer.clear()
-        self._fill_top_with_empty_lines()
-        self._empty = True
-
-    def empty(self) -> bool:
-        """Return True if the screen has no lines with content."""
-        return self._empty
-
-    def reset_logs(
-        self,
-        log_index: int = 0,
-    ) -> None:
-        """Erase the screen and append logs starting from log_index."""
-        self.clear_screen()
-
-        start_log_index, log_source = self.get_log_source()
-        if len(log_source) == 0:
-            return
-
-        # Append at most at most the window height number worth of logs. If the
-        # number of available logs is less, use that amount.
-        max_log_messages_to_fetch = min(self.height, len(log_source))
-
-        # Including the target log_index, fetch the desired logs.
-        # For example if we are rendering log_index 10 and the window height is
-        # 6 the range below will be:
-        # >>> list(i for i in range((10 - 6) + 1, 10 + 1))
-        # [5, 6, 7, 8, 9, 10]
-        for i in range((log_index - max_log_messages_to_fetch) + 1,
-                       log_index + 1):
-            # If i is < 0 it's an invalid log, skip to the next line. The next
-            # index could be 0 or higher since we are traversing in increasing
-            # order.
-            if i < start_log_index:
-                continue
-            self.append_log(i)
-        # Make sure the bottom line is highlighted.
-        self.move_cursor_to_bottom()
-
-    def resize(self, width, height) -> None:
-        """Update screen width and height.
-
-        Following a resize the caller should run reset_logs()."""
-        self.width = width
-        self.height = height
-
-    def get_lines(
-        self,
-        marked_logs_start: Optional[int] = None,
-        marked_logs_end: Optional[int] = None,
-    ) -> List[StyleAndTextTuples]:
-        """Return lines for final display.
-
-        Styling is added for the line under the cursor."""
-        if not marked_logs_start:
-            marked_logs_start = -1
-        if not marked_logs_end:
-            marked_logs_end = -1
-
-        all_lines: List[StyleAndTextTuples] = []
-        # Loop through a copy of the line_buffer in case it is mutated before
-        # this function is complete.
-        for i, line in enumerate(list(self.line_buffer)):
-
-            # Is this line the cursor_position? Apply line highlighting
-            if (i == self.cursor_position
-                    and (self.cursor_position < len(self.line_buffer))
-                    and not self.line_buffer[self.cursor_position].empty()):
-                # Fill in empty charaters to the width of the screen. This
-                # ensures the backgound is highlighted to the edge of the
-                # screen.
-                new_fragments = fill_character_width(
-                    line.fragments,
-                    len(line.fragments) - 1,  # -1 for the ending line break
-                    self.width,
-                )
-
-                # Apply a style to highlight this line.
-                all_lines.append(
-                    to_formatted_text(new_fragments,
-                                      style='class:selected-log-line'))
-            elif line.log_index is not None and (
-                    marked_logs_start <= line.log_index <= marked_logs_end):
-                new_fragments = fill_character_width(
-                    line.fragments,
-                    len(line.fragments) - 1,  # -1 for the ending line break
-                    self.width,
-                )
-
-                # Apply a style to highlight this line.
-                all_lines.append(
-                    to_formatted_text(new_fragments,
-                                      style='class:marked-log-line'))
-
-            else:
-                all_lines.append(line.fragments)
-
-        return all_lines
-
-    def _prepend_line(self, line: ScreenLine) -> None:
-        """Add a line to the top of the screen."""
-        self.line_buffer.appendleft(line)
-        self._empty = False
-
-    def _append_line(self, line: ScreenLine) -> None:
-        """Add a line to the bottom of the screen."""
-        self.line_buffer.append(line)
-        self._empty = False
-
-    def _trim_top_lines(self) -> None:
-        """Remove lines from the top if larger than the screen height."""
-        overflow_amount = len(self.line_buffer) - self.height
-        for _ in range(overflow_amount):
-            self.line_buffer.popleft()
-
-    def _trim_bottom_lines(self) -> None:
-        """Remove lines from the bottom if larger than the screen height."""
-        overflow_amount = len(self.line_buffer) - self.height
-        for _ in range(overflow_amount):
-            self.line_buffer.pop()
-
-    def move_cursor_up(self, line_count: int) -> int:
-        """Move the cursor up as far as it can go without fetching new lines.
-
-        Args:
-            line_count: A negative number of lines to move the cursor by.
-
-        Returns:
-            int: The remaining line count that was not moved. This is the number
-            of new lines that need to be fetched and prepended to the screen
-            line buffer."""
-        remaining_lines = line_count
-
-        # Loop from a negative line_count value to zero.
-        # For example if line_count is -5 the loop will traverse:
-        # >>> list(i for i in range(-5, 0, 1))
-        # [-5, -4, -3, -2, -1]
-        for _ in range(line_count, 0, 1):
-            new_index = self.cursor_position - 1
-            if new_index < 0:
-                break
-            if (new_index < len(self.line_buffer)
-                    and self.line_buffer[new_index].empty()):
-                # The next line is empty and has no content.
-                break
-            self.cursor_position -= 1
-            remaining_lines += 1
-        return remaining_lines
-
-    def move_cursor_down(self, line_count: int) -> int:
-        """Move the cursor down as far as it can go without fetching new lines.
-
-        Args:
-            line_count: A positive number of lines to move the cursor down by.
-
-        Returns:
-            int: The remaining line count that was not moved. This is the number
-            of new lines that need to be fetched and appended to the screen line
-            buffer."""
-        remaining_lines = line_count
-        for _ in range(line_count):
-            new_index = self.cursor_position + 1
-            if new_index >= self.height:
-                break
-            if (new_index < len(self.line_buffer)
-                    and self.line_buffer[new_index].empty()):
-                # The next line is empty and has no content.
-                break
-            self.cursor_position += 1
-            remaining_lines -= 1
-        return remaining_lines
-
-    def move_cursor_to_bottom(self) -> None:
-        """Move the cursor to the bottom of the screen.
-
-        Only use this for movement not initiated by users. For example if new
-        logs were just added to the bottom of the screen in follow
-        mode. The LogScreen class does not allow scrolling beyond the bottom of
-        the content so the cursor will fall on a log message as long as there
-        are some log messages. If there are no log messages the line is not
-        highlighted by get_lines()."""
-        self.cursor_position = self.height - 1
-
-    def move_cursor_to_position(self, window_row: int) -> None:
-        """Move the cursor to a line if there is a log message there."""
-        if window_row >= len(self.line_buffer):
-            return
-        if 0 <= window_row < self.height:
-            current_line = self.line_buffer[window_row]
-            if current_line.log_index is not None:
-                self.cursor_position = window_row
-
-    def _move_selection_to_log(self, log_index: int, subline: int) -> None:
-        """Move the cursor to the location of log_index."""
-        for i, line in enumerate(self.line_buffer):
-            if line.log_index == log_index and line.subline == subline:
-                self.cursor_position = i
-                return
-
-    def shift_selected_log_to_top(self) -> None:
-        """Shift the selected line to the top.
-
-        This moves the lines on screen and keeps the originally selected line
-        highlighted. Example use case: when jumping to a search match the
-        matched line will be shown at the top of the screen."""
-        if not 0 <= self.cursor_position < len(self.line_buffer):
-            return
-
-        current_line = self.line_buffer[self.cursor_position]
-        amount = max(self.cursor_position, current_line.height)
-        amount -= current_line.subline
-        remaining_lines = self.scroll_subline(amount)
-        if remaining_lines != 0 and current_line.log_index is not None:
-            # Restore original selected line.
-            self._move_selection_to_log(current_line.log_index,
-                                        current_line.subline)
-            return
-        # Lines scrolled as expected, set cursor_position to top.
-        self.cursor_position = 0
-
-    def shift_selected_log_to_center(self) -> None:
-        """Shift the selected line to the center.
-
-        This moves the lines on screen and keeps the originally selected line
-        highlighted. Example use case: when jumping to a search match the
-        matched line will be shown at the center of the screen."""
-        if not 0 <= self.cursor_position < len(self.line_buffer):
-            return
-
-        half_height = int(self.height / 2)
-        current_line = self.line_buffer[self.cursor_position]
-
-        amount = max(self.cursor_position - half_height, current_line.height)
-        amount -= current_line.subline
-
-        remaining_lines = self.scroll_subline(amount)
-        if remaining_lines != 0 and current_line.log_index is not None:
-            # Restore original selected line.
-            self._move_selection_to_log(current_line.log_index,
-                                        current_line.subline)
-            return
-
-        # Lines scrolled as expected, set cursor_position to center.
-        self.cursor_position -= amount
-        self.cursor_position -= (current_line.height - 1)
-
-    def scroll_subline(self, line_count: int = 1) -> int:
-        """Move the cursor down or up by positive or negative lines.
-
-        Args:
-            line_count: A positive or negative number of lines the cursor should
-                move. Positive for down, negative for up.
-
-        Returns:
-            int: The remaining line count that was not moved. This is the number
-            of new lines that could not be fetched in the case that the top or
-            bottom of available log message lines was reached."""
-        # Move self.cursor_position as far as it can go on screen without
-        # fetching new log message lines.
-        if line_count > 0:
-            remaining_lines = self.move_cursor_down(line_count)
-        else:
-            remaining_lines = self.move_cursor_up(line_count)
-
-        if remaining_lines == 0:
-            # No more lines needed, return
-            return remaining_lines
-
-        # Top or bottom of the screen was reached, fetch and add new log lines.
-        if remaining_lines < 0:
-            return self.fetch_subline_up(remaining_lines)
-        return self.fetch_subline_down(remaining_lines)
-
-    def fetch_subline_up(self, line_count: int = -1) -> int:
-        """Fetch new lines from the top in order of decreasing log_indexes.
-
-        Args:
-            line_count: A negative number of lines that should be fetched and
-                added to the top of the screen.
-
-        Returns:
-            int: The number of lines that were not fetched. Returns 0 if the
-                desired number of lines were fetched successfully."""
-        start_log_index, _log_source = self.get_log_source()
-        remaining_lines = line_count
-        for _ in range(line_count, 0, 1):
-            current_line = self.get_line_at_cursor_position()
-            if current_line.log_index is None:
-                return remaining_lines + 1
-
-            target_log_index: int
-            target_subline: int
-
-            # If the current subline is at the start of this log, fetch the
-            # previous log message's last subline.
-            if current_line.subline == 0:
-                target_log_index = current_line.log_index - 1
-                # Set -1 to signal fetching the previous log's last subline
-                target_subline = -1
-            else:
-                # Get previous sub line of current log
-                target_log_index = current_line.log_index
-                target_subline = current_line.subline - 1
-
-            if target_log_index < start_log_index:
-                # Invalid log_index, don't scroll further
-                return remaining_lines + 1
-
-            self.prepend_log(target_log_index, subline=target_subline)
-            remaining_lines += 1
-
-        return remaining_lines
-
-    def get_line_at_cursor_position(self) -> ScreenLine:
-        """Returns the ScreenLine under the cursor."""
-        if (self.cursor_position >= len(self.line_buffer)
-                or self.cursor_position < 0):
-            return ScreenLine([('', '')])
-        return self.line_buffer[self.cursor_position]
-
-    def fetch_subline_down(self, line_count: int = 1) -> int:
-        """Fetch new lines from the bottom in order of increasing log_indexes.
-
-        Args:
-            line_count: A positive number of lines that should be fetched and
-                added to the bottom of the screen.
-
-        Returns:
-            int: The number of lines that were not fetched. Returns 0 if the
-                desired number of lines were fetched successfully."""
-        _start_log_index, log_source = self.get_log_source()
-        remaining_lines = line_count
-        for _ in range(line_count):
-            # Skip this line if not at the bottom
-            if self.cursor_position < self.height - 1:
-                self.cursor_position += 1
-                continue
-
-            current_line = self.get_line_at_cursor_position()
-            if current_line.log_index is None:
-                return remaining_lines - 1
-
-            target_log_index: int
-            target_subline: int
-
-            # If the current subline is at the height of this log, fetch the
-            # next log message.
-            if current_line.subline == current_line.height - 1:
-                # Get next log's first subline
-                target_log_index = current_line.log_index + 1
-                target_subline = 0
-            else:
-                # Get next sub line of current log
-                target_log_index = current_line.log_index
-                target_subline = current_line.subline + 1
-
-            if target_log_index >= len(log_source):
-                # Invalid log_index, don't scroll further
-                return remaining_lines - 1
-
-            self.append_log(target_log_index, subline=target_subline)
-            remaining_lines -= 1
-
-        return remaining_lines
-
-    def first_rendered_log_index(self) -> Optional[int]:
-        """Scan the screen for the first valid log_index and return it."""
-        log_index = None
-        for i in range(self.height):
-            if i >= len(self.line_buffer):
-                break
-            if self.line_buffer[i].log_index is not None:
-                log_index = self.line_buffer[i].log_index
-                break
-        return log_index
-
-    def last_rendered_log_index(self) -> Optional[int]:
-        """Return the last log_index shown on screen."""
-        log_index = None
-        if len(self.line_buffer) == 0:
-            return None
-        if self.line_buffer[-1].log_index is not None:
-            log_index = self.line_buffer[-1].log_index
-        return log_index
-
-    def _get_fragments_per_line(self,
-                                log_index: int) -> List[StyleAndTextTuples]:
-        """Return a list of lines wrapped to the screen width for a log.
-
-        Before fetching the log message this function updates the log_source and
-        formatting options."""
-        _start_log_index, log_source = self.get_log_source()
-        if log_index >= len(log_source):
-            return []
-        log = log_source[log_index]
-        table_formatter = self.get_log_formatter()
-        truncate_lines = not self.get_line_wrapping()
-        search_filter = self.get_search_filter()
-        search_highlight = self.get_search_highlight()
-
-        # Select the log display formatter; table or standard.
-        fragments: StyleAndTextTuples = []
-        if table_formatter:
-            fragments = table_formatter(log)
-        else:
-            fragments = log.get_fragments()
-
-        # Apply search term highlighting.
-        if search_filter and search_highlight and search_filter.matches(log):
-            fragments = search_filter.highlight_search_matches(fragments)
-
-        # Word wrap the log message or truncate to screen width
-        line_fragments, _log_line_height = insert_linebreaks(
-            fragments,
-            max_line_width=self.width,
-            truncate_long_lines=truncate_lines)
-        # Convert the existing flattened fragments to a list of lines.
-        fragments_per_line = split_lines(line_fragments)
-
-        return fragments_per_line
-
-    def prepend_log(
-        self,
-        log_index: int,
-        subline: Optional[int] = None,
-    ) -> None:
-        """Add a log message or a single line to the top of the screen.
-
-        Args:
-            log_index: The index of the log message to fetch.
-            subline: The desired subline of the log message. When displayed on
-                screen the log message may take up more than one line. If
-                subline is 0 or higher that line will be added. If subline is -1
-                the last subline will be prepended regardless of the total log
-                message height.
-        """
-        fragments_per_line = self._get_fragments_per_line(log_index)
-
-        # Target the last subline if the subline arg is set to -1.
-        fetch_last_subline = (subline == -1)
-
-        for line_index, line in enumerate(fragments_per_line):
-            # If we are looking for a specific subline and this isn't it, skip.
-            if subline is not None:
-                # If subline is set to -1 we need to append the last subline of
-                # this log message. Skip this line if it isn't the last one.
-                if fetch_last_subline and (line_index !=
-                                           len(fragments_per_line) - 1):
-                    continue
-                # If subline is not -1 (0 or higher) and this isn't the desired
-                # line, skip to the next one.
-                if not fetch_last_subline and line_index != subline:
-                    continue
-
-            self._prepend_line(
-                ScreenLine(
-                    fragments=line,
-                    log_index=log_index,
-                    subline=line_index,
-                    height=len(fragments_per_line),
-                ))
-
-        # Remove lines from the bottom if over the screen height.
-        if len(self.line_buffer) > self.height:
-            self._trim_bottom_lines()
-
-    def append_log(
-        self,
-        log_index: int,
-        subline: Optional[int] = None,
-    ) -> None:
-        """Add a log message or a single line to the bottom of the screen."""
-        # Save this log_index
-        self.last_appended_log_index = log_index
-        fragments_per_line = self._get_fragments_per_line(log_index)
-
-        for line_index, line in enumerate(fragments_per_line):
-            # If we are looking for a specific subline and this isn't it, skip.
-            if subline is not None and line_index != subline:
-                continue
-
-            self._append_line(
-                ScreenLine(
-                    fragments=line,
-                    log_index=log_index,
-                    subline=line_index,
-                    height=len(fragments_per_line),
-                ))
-
-        # Remove lines from the top if over the screen height.
-        if len(self.line_buffer) > self.height:
-            self._trim_top_lines()
diff --git a/pw_console/py/pw_console/log_store.py b/pw_console/py/pw_console/log_store.py
deleted file mode 100644
index ed6ebb4..0000000
--- a/pw_console/py/pw_console/log_store.py
+++ /dev/null
@@ -1,250 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""LogStore saves logs and acts as a Python logging handler."""
-
-from __future__ import annotations
-import collections
-import logging
-import sys
-from datetime import datetime
-from typing import Dict, List, Optional, TYPE_CHECKING
-
-import pw_cli.color
-
-from pw_console.console_prefs import ConsolePrefs
-from pw_console.log_line import LogLine
-import pw_console.text_formatting
-from pw_console.widgets.table import TableView
-
-if TYPE_CHECKING:
-    from pw_console.log_view import LogView
-
-
-class LogStore(logging.Handler):
-    """Pigweed Console logging handler.
-
-    This is a `Python logging.Handler
-    <https://docs.python.org/3/library/logging.html#handler-objects>`_ class
-    used to store logs for display in the pw_console user interface.
-
-    You may optionally add this as a handler to an existing logger
-    instances. This will be required if logs need to be captured for display in
-    the pw_console UI before the user interface is running.
-
-    Example usage:
-
-    .. code-block:: python
-
-        import logging
-
-        from pw_console import PwConsoleEmbed, LogStore
-
-        _DEVICE_LOG = logging.getLogger('usb_gadget')
-
-        # Create a log store and add as a handler.
-        device_log_store = LogStore()
-        _DEVICE_LOG.addHander(device_log_store)
-
-        # Start communication with your device here, before embedding
-        # pw_console.
-
-        # Create the pw_console embed instance
-        console = PwConsoleEmbed(
-            global_vars=globals(),
-            local_vars=locals(),
-            loggers={
-                'Host Logs': [
-                    logging.getLogger(__package__),
-                    logging.getLogger(__file__),
-                ],
-                # Set the LogStore as the value of this logger window.
-                'Device Logs': device_log_store,
-            },
-            app_title='My Awesome Console',
-        )
-
-        console.setup_python_logging()
-        console.embed()
-    """
-    def __init__(self, prefs: Optional[ConsolePrefs] = None):
-        """Initializes the LogStore instance."""
-
-        # ConsolePrefs may not be passed on init. For example, if the user is
-        # creating a LogStore to capture log messages before console startup.
-        if not prefs:
-            prefs = ConsolePrefs(project_file=False,
-                                 project_user_file=False,
-                                 user_file=False)
-        self.prefs = prefs
-        # Log storage deque for fast addition and deletion from the beginning
-        # and end of the iterable.
-        self.logs: collections.deque = collections.deque()
-
-        # Estimate of the logs in memory.
-        self.byte_size: int = 0
-
-        # Only allow this many log lines in memory.
-        self.max_history_size: int = 1000000
-
-        # Counts of logs per python logger name
-        self.channel_counts: Dict[str, int] = {}
-        # Widths of each logger prefix string. For example: the character length
-        # of the timestamp string.
-        self.channel_formatted_prefix_widths: Dict[str, int] = {}
-        # Longest of the above prefix widths.
-        self.longest_channel_prefix_width = 0
-
-        self.table: TableView = TableView(prefs=self.prefs)
-
-        # Erase existing logs.
-        self.clear_logs()
-
-        # List of viewers that should be notified on new log line arrival.
-        self.registered_viewers: List['LogView'] = []
-
-        super().__init__()
-
-        # Set formatting after logging.Handler init.
-        self.set_formatting()
-
-    def set_prefs(self, prefs: ConsolePrefs) -> None:
-        """Set the ConsolePrefs for this LogStore."""
-        self.prefs = prefs
-        self.table.set_prefs(prefs)
-
-    def register_viewer(self, viewer: 'LogView') -> None:
-        """Register this LogStore with a LogView."""
-        self.registered_viewers.append(viewer)
-
-    def set_formatting(self) -> None:
-        """Setup log formatting."""
-        # Copy of pw_cli log formatter
-        colors = pw_cli.color.colors(True)
-        timestamp_prefix = colors.black_on_white('%(asctime)s') + ' '
-        timestamp_format = '%Y%m%d %H:%M:%S'
-        format_string = timestamp_prefix + '%(levelname)s %(message)s'
-        formatter = logging.Formatter(format_string, timestamp_format)
-
-        self.setLevel(logging.DEBUG)
-        self.setFormatter(formatter)
-
-        # Update log time character width.
-        example_time_string = datetime.now().strftime(timestamp_format)
-        self.table.column_widths['time'] = len(example_time_string)
-
-    def clear_logs(self):
-        """Erase all stored pane lines."""
-        self.logs = collections.deque()
-        self.byte_size = 0
-        self.channel_counts = {}
-        self.channel_formatted_prefix_widths = {}
-        self.line_index = 0
-
-    def get_channel_counts(self):
-        """Return the seen channel log counts for this conatiner."""
-        return ', '.join([
-            f'{name}: {count}' for name, count in self.channel_counts.items()
-        ])
-
-    def get_total_count(self):
-        """Total size of the logs store."""
-        return len(self.logs)
-
-    def get_last_log_index(self):
-        """Last valid index of the logs."""
-        # Subtract 1 since self.logs is zero indexed.
-        total = self.get_total_count()
-        return 0 if total < 0 else total - 1
-
-    def _update_log_prefix_width(self, record: logging.LogRecord):
-        """Save the formatted prefix width if this is a new logger channel
-        name."""
-        if self.formatter and (
-                record.name
-                not in self.channel_formatted_prefix_widths.keys()):
-            # Find the width of the formatted timestamp and level
-            format_string = self.formatter._fmt  # pylint: disable=protected-access
-
-            # There may not be a _fmt defined.
-            if not format_string:
-                return
-
-            format_without_message = format_string.replace('%(message)s', '')
-            # If any other style parameters are left, get the width of them.
-            if (format_without_message and 'asctime' in format_without_message
-                    and 'levelname' in format_without_message):
-                formatted_time_and_level = format_without_message % dict(
-                    asctime=record.asctime, levelname=record.levelname)
-
-                # Delete ANSI escape sequences.
-                ansi_stripped_time_and_level = (
-                    pw_console.text_formatting.strip_ansi(
-                        formatted_time_and_level))
-
-                self.channel_formatted_prefix_widths[record.name] = len(
-                    ansi_stripped_time_and_level)
-            else:
-                self.channel_formatted_prefix_widths[record.name] = 0
-
-            # Set the max width of all known formats so far.
-            self.longest_channel_prefix_width = max(
-                self.channel_formatted_prefix_widths.values())
-
-    def _append_log(self, record: logging.LogRecord):
-        """Add a new log event."""
-        # Format incoming log line.
-        formatted_log = self.format(record)
-        ansi_stripped_log = pw_console.text_formatting.strip_ansi(
-            formatted_log)
-        # Save this log.
-        self.logs.append(
-            LogLine(record=record,
-                    formatted_log=formatted_log,
-                    ansi_stripped_log=ansi_stripped_log))
-        # Increment this logger count
-        self.channel_counts[record.name] = self.channel_counts.get(
-            record.name, 0) + 1
-
-        # TODO(pwbug/614): Revisit calculating prefix widths automatically when
-        # line wrapping indentation is supported.
-        # Set the prefix width to 0
-        self.channel_formatted_prefix_widths[record.name] = 0
-
-        # Parse metadata fields
-        self.logs[-1].update_metadata()
-
-        # Check for bigger column widths.
-        self.table.update_metadata_column_widths(self.logs[-1])
-
-        # Update estimated byte_size.
-        self.byte_size += sys.getsizeof(self.logs[-1])
-        # If the total log lines is > max_history_size, delete the oldest line.
-        if self.get_total_count() > self.max_history_size:
-            self.byte_size -= sys.getsizeof(self.logs.popleft())
-
-    def emit(self, record) -> None:
-        """Process a new log record.
-
-        This defines the logging.Handler emit() fuction which is called by
-        logging.Handler.handle() We don't implement handle() as it is done in
-        the parent class with thread safety and filters applied.
-        """
-        self._append_log(record)
-        # Notify viewers of new logs
-        for viewer in self.registered_viewers:
-            viewer.new_logs_arrived()
-
-    def render_table_header(self):
-        """Get pre-formatted table header."""
-        return self.table.formatted_header()
diff --git a/pw_console/py/pw_console/log_view.py b/pw_console/py/pw_console/log_view.py
deleted file mode 100644
index 2b54f9e..0000000
--- a/pw_console/py/pw_console/log_view.py
+++ /dev/null
@@ -1,868 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""LogView maintains a log pane's scrolling and searching state."""
-
-from __future__ import annotations
-import asyncio
-import collections
-import copy
-from enum import Enum
-import itertools
-import logging
-import operator
-from pathlib import Path
-import re
-import time
-from typing import Callable, Dict, List, Optional, Tuple, TYPE_CHECKING
-
-from prompt_toolkit.data_structures import Point
-from prompt_toolkit.formatted_text import StyleAndTextTuples
-
-from pw_console.log_filter import (
-    DEFAULT_SEARCH_MATCHER,
-    LogFilter,
-    RegexValidator,
-    SearchMatcher,
-    preprocess_search_regex,
-)
-from pw_console.log_screen import ScreenLine, LogScreen
-from pw_console.log_store import LogStore
-from pw_console.text_formatting import remove_formatting
-
-if TYPE_CHECKING:
-    from pw_console.console_app import ConsoleApp
-    from pw_console.log_line import LogLine
-    from pw_console.log_pane import LogPane
-
-_LOG = logging.getLogger(__package__)
-
-
-class FollowEvent(Enum):
-    """Follow mode scroll event types."""
-    SEARCH_MATCH = 'scroll_to_bottom'
-    STICKY_FOLLOW = 'scroll_to_bottom_with_sticky_follow'
-
-
-class LogView:
-    """Viewing window into a LogStore."""
-
-    # pylint: disable=too-many-instance-attributes,too-many-public-methods
-
-    def __init__(
-        self,
-        log_pane: 'LogPane',
-        application: 'ConsoleApp',
-        log_store: Optional[LogStore] = None,
-    ):
-        # Parent LogPane reference. Updated by calling `set_log_pane()`.
-        self.log_pane = log_pane
-        self.log_store = log_store if log_store else LogStore(
-            prefs=application.prefs)
-        self.log_store.set_prefs(application.prefs)
-        self.log_store.register_viewer(self)
-
-        self.marked_logs_start: Optional[int] = None
-        self.marked_logs_end: Optional[int] = None
-
-        # Search variables
-        self.search_text: Optional[str] = None
-        self.search_filter: Optional[LogFilter] = None
-        self.search_highlight: bool = False
-        self.search_matcher = DEFAULT_SEARCH_MATCHER
-        self.search_validator = RegexValidator()
-
-        # Container for each log_index matched by active searches.
-        self.search_matched_lines: Dict[int, int] = {}
-        # Background task to find historical matched lines.
-        self.search_match_count_task: Optional[asyncio.Task] = None
-
-        # Flag for automatically jumping to each new search match as they
-        # appear.
-        self.follow_search_match: bool = False
-        self.last_search_matched_log: Optional[int] = None
-
-        # Follow event flag. This is set to by the new_logs_arrived() function
-        # as a signal that the log screen should be scrolled to the bottom.
-        # This is read by render_content() whenever the screen is drawn.
-        self.follow_event: Optional[FollowEvent] = None
-
-        self.log_screen = LogScreen(
-            get_log_source=self._get_log_lines,
-            get_line_wrapping=self.wrap_lines_enabled,
-            get_log_formatter=self._get_table_formatter,
-            get_search_filter=lambda: self.search_filter,
-            get_search_highlight=lambda: self.search_highlight,
-        )
-
-        # Filter
-        self.filtering_on: bool = False
-        self.filters: 'collections.OrderedDict[str, LogFilter]' = (
-            collections.OrderedDict())
-        self.filtered_logs: collections.deque = collections.deque()
-        self.filter_existing_logs_task: Optional[asyncio.Task] = None
-
-        # Current log line index state variables:
-        self._last_log_index = -1
-        self._log_index = 0
-        self._filtered_log_index = 0
-        self._last_start_index = 0
-        self._last_end_index = 0
-        self._current_start_index = 0
-        self._current_end_index = 0
-        self._scrollback_start_index = 0
-
-        # LogPane prompt_toolkit container render size.
-        self._window_height = 20
-        self._window_width = 80
-        self._reset_log_screen_on_next_render: bool = True
-        self._user_scroll_event: bool = False
-
-        # Max frequency in seconds of prompt_toolkit UI redraws triggered by new
-        # log lines.
-        self._ui_update_frequency = 0.05
-        self._last_ui_update_time = time.time()
-        self._last_log_store_index = 0
-        self._new_logs_since_last_render = True
-
-        # Should new log lines be tailed?
-        self.follow: bool = True
-
-        self.visual_select_mode: bool = False
-
-        # Cache of formatted text tuples used in the last UI render.
-        self._line_fragment_cache: List[StyleAndTextTuples] = []
-
-    def view_mode_changed(self) -> None:
-        self._reset_log_screen_on_next_render = True
-
-    @property
-    def log_index(self):
-        if self.filtering_on:
-            return self._filtered_log_index
-        return self._log_index
-
-    @log_index.setter
-    def log_index(self, new_log_index):
-        # Save the old log_index
-        self._last_log_index = self.log_index
-        if self.filtering_on:
-            self._filtered_log_index = new_log_index
-        else:
-            self._log_index = new_log_index
-
-    def _reset_log_index_changed(self) -> None:
-        self._last_log_index = self.log_index
-
-    def log_index_changed_since_last_render(self) -> bool:
-        return self._last_log_index != self.log_index
-
-    def _set_match_position(self, position: int):
-        self.follow = False
-        self.log_index = position
-        self.save_search_matched_line(position)
-        self.log_screen.reset_logs(log_index=self.log_index)
-        self.log_screen.shift_selected_log_to_center()
-        self._user_scroll_event = True
-        self.log_pane.application.redraw_ui()
-
-    def select_next_search_matcher(self):
-        matchers = list(SearchMatcher)
-        index = matchers.index(self.search_matcher)
-        new_index = (index + 1) % len(matchers)
-        self.search_matcher = matchers[new_index]
-
-    def search_forwards(self):
-        if not self.search_filter:
-            return
-        self.search_highlight = True
-
-        log_beginning_index = self.hidden_line_count()
-
-        starting_index = self.log_index + 1
-        if starting_index > self.get_last_log_index():
-            starting_index = log_beginning_index
-
-        _, logs = self._get_log_lines()
-
-        # From current position +1 and down
-        for i in range(starting_index, self.get_last_log_index() + 1):
-            if self.search_filter.matches(logs[i]):
-                self._set_match_position(i)
-                return
-
-        # From the beginning to the original start
-        for i in range(log_beginning_index, starting_index):
-            if self.search_filter.matches(logs[i]):
-                self._set_match_position(i)
-                return
-
-    def search_backwards(self):
-        if not self.search_filter:
-            return
-        self.search_highlight = True
-
-        log_beginning_index = self.hidden_line_count()
-
-        starting_index = self.log_index - 1
-        if starting_index < 0:
-            starting_index = self.get_last_log_index()
-
-        _, logs = self._get_log_lines()
-
-        # From current position - 1 and up
-        for i in range(starting_index, log_beginning_index - 1, -1):
-            if self.search_filter.matches(logs[i]):
-                self._set_match_position(i)
-                return
-
-        # From the end to the original start
-        for i in range(self.get_last_log_index(), starting_index, -1):
-            if self.search_filter.matches(logs[i]):
-                self._set_match_position(i)
-                return
-
-    def set_search_regex(self,
-                         text,
-                         invert,
-                         field,
-                         matcher: Optional[SearchMatcher] = None) -> bool:
-        search_matcher = matcher if matcher else self.search_matcher
-        _LOG.debug(search_matcher)
-
-        regex_text, regex_flags = preprocess_search_regex(
-            text, matcher=search_matcher)
-
-        try:
-            compiled_regex = re.compile(regex_text, regex_flags)
-            self.search_filter = LogFilter(
-                regex=compiled_regex,
-                input_text=text,
-                invert=invert,
-                field=field,
-            )
-            _LOG.debug(self.search_filter)
-        except re.error as error:
-            _LOG.debug(error)
-            return False
-
-        self.search_highlight = True
-        self.search_text = regex_text
-        return True
-
-    def new_search(
-        self,
-        text,
-        invert=False,
-        field: Optional[str] = None,
-        search_matcher: Optional[str] = None,
-        interactive: bool = True,
-    ) -> bool:
-        """Start a new search for the given text."""
-        valid_matchers = list(s.name for s in SearchMatcher)
-        selected_matcher: Optional[SearchMatcher] = None
-        if (search_matcher is not None
-                and search_matcher.upper() in valid_matchers):
-            selected_matcher = SearchMatcher(search_matcher.upper())
-
-        if not self.set_search_regex(text, invert, field, selected_matcher):
-            return False
-
-        # Clear matched lines
-        self.search_matched_lines = {}
-
-        if interactive:
-            # Start count historical search matches task.
-            self.search_match_count_task = asyncio.create_task(
-                self.count_search_matches())
-
-        # Default search direction when hitting enter in the search bar.
-        if interactive:
-            self.search_forwards()
-        return True
-
-    def save_search_matched_line(self, log_index: int) -> None:
-        """Save the log_index at position as a matched line."""
-        self.search_matched_lines[log_index] = 0
-        # Keep matched lines sorted by position
-        self.search_matched_lines = {
-            # Save this log_index and its match number.
-            log_index: match_number
-            for match_number, log_index in enumerate(
-                sorted(self.search_matched_lines.keys()))
-        }
-
-    def disable_search_highlighting(self):
-        self.log_pane.log_view.search_highlight = False
-
-    def _restart_filtering(self):
-        # Turn on follow
-        if not self.follow:
-            self.toggle_follow()
-
-        # Reset filtered logs.
-        self.filtered_logs.clear()
-        # Reset scrollback start
-        self._scrollback_start_index = 0
-
-        # Start filtering existing log lines.
-        self.filter_existing_logs_task = asyncio.create_task(
-            self.filter_past_logs())
-
-        # Reset existing search
-        self.clear_search()
-
-        # Trigger a main menu update to set log window menu titles.
-        self.log_pane.application.update_menu_items()
-        # Redraw the UI
-        self.log_pane.application.redraw_ui()
-
-    def install_new_filter(self):
-        """Set a filter using the current search_regex."""
-        if not self.search_filter:
-            return
-
-        self.filtering_on = True
-        self.filters[self.search_text] = copy.deepcopy(self.search_filter)
-
-        self.clear_search()
-
-    def apply_filter(self):
-        """Set new filter and schedule historical log filter asyncio task."""
-        self.install_new_filter()
-        self._restart_filtering()
-
-    def clear_search_highlighting(self):
-        self.search_highlight = False
-        self._reset_log_screen_on_next_render = True
-
-    def clear_search(self):
-        self.search_matched_lines = {}
-        self.search_text = None
-        self.search_filter = None
-        self.search_highlight = False
-        self._reset_log_screen_on_next_render = True
-
-    def _get_log_lines(self) -> Tuple[int, collections.deque[LogLine]]:
-        logs = self.log_store.logs
-        if self.filtering_on:
-            logs = self.filtered_logs
-        return self._scrollback_start_index, logs
-
-    def _get_visible_log_lines(self):
-        _, logs = self._get_log_lines()
-        if self._scrollback_start_index > 0:
-            return collections.deque(
-                itertools.islice(logs, self.hidden_line_count(), len(logs)))
-        return logs
-
-    def _get_table_formatter(self) -> Optional[Callable]:
-        table_formatter = None
-        if self.log_pane.table_view:
-            table_formatter = self.log_store.table.formatted_row
-        return table_formatter
-
-    def delete_filter(self, filter_text):
-        if filter_text not in self.filters:
-            return
-
-        # Delete this filter
-        del self.filters[filter_text]
-
-        # If no filters left, stop filtering.
-        if len(self.filters) == 0:
-            self.clear_filters()
-        else:
-            # Erase existing filtered lines.
-            self._restart_filtering()
-
-    def clear_filters(self):
-        if not self.filtering_on:
-            return
-        self.clear_search()
-        self.filtering_on = False
-        self.filters: 'collections.OrderedDict[str, re.Pattern]' = (
-            collections.OrderedDict())
-        self.filtered_logs.clear()
-        # Reset scrollback start
-        self._scrollback_start_index = 0
-        if not self.follow:
-            self.toggle_follow()
-
-    async def count_search_matches(self):
-        """Count search matches and save their locations."""
-        # Wait for any filter_existing_logs_task to finish.
-        if self.filtering_on and self.filter_existing_logs_task:
-            await self.filter_existing_logs_task
-
-        starting_index = self.get_last_log_index()
-        ending_index, logs = self._get_log_lines()
-
-        # From the end of the log store to the beginning.
-        for i in range(starting_index, ending_index - 1, -1):
-            # Is this log a match?
-            if self.search_filter.matches(logs[i]):
-                self.save_search_matched_line(i)
-            # Pause every 100 lines or so
-            if i % 100 == 0:
-                await asyncio.sleep(.1)
-
-    async def filter_past_logs(self):
-        """Filter past log lines."""
-        starting_index = self.log_store.get_last_log_index()
-        ending_index = -1
-
-        # From the end of the log store to the beginning.
-        for i in range(starting_index, ending_index, -1):
-            # Is this log a match?
-            if self.filter_scan(self.log_store.logs[i]):
-                # Add to the beginning of the deque.
-                self.filtered_logs.appendleft(self.log_store.logs[i])
-            # TODO(tonymd): Tune these values.
-            # Pause every 100 lines or so
-            if i % 100 == 0:
-                await asyncio.sleep(.1)
-
-    def set_log_pane(self, log_pane: 'LogPane'):
-        """Set the parent LogPane instance."""
-        self.log_pane = log_pane
-
-    def _update_log_index(self) -> ScreenLine:
-        line_at_cursor = self.log_screen.get_line_at_cursor_position()
-        if line_at_cursor.log_index is not None:
-            self.log_index = line_at_cursor.log_index
-        return line_at_cursor
-
-    def get_current_line(self) -> int:
-        """Return the currently selected log event index."""
-        return self.log_index
-
-    def get_total_count(self):
-        """Total size of the logs store."""
-        return (len(self.filtered_logs)
-                if self.filtering_on else self.log_store.get_total_count())
-
-    def get_last_log_index(self):
-        total = self.get_total_count()
-        return 0 if total < 0 else total - 1
-
-    def clear_scrollback(self):
-        """Hide log lines before the max length of the stored logs."""
-        # Enable follow and scroll to the bottom, then clear.
-        if not self.follow:
-            self.toggle_follow()
-        self._scrollback_start_index = self.log_index
-        self._reset_log_screen_on_next_render = True
-
-    def hidden_line_count(self):
-        """Return the number of hidden lines."""
-        if self._scrollback_start_index > 0:
-            return self._scrollback_start_index + 1
-        return 0
-
-    def undo_clear_scrollback(self):
-        """Reset the current scrollback start index."""
-        self._scrollback_start_index = 0
-
-    def wrap_lines_enabled(self):
-        """Get the parent log pane wrap lines setting."""
-        if not self.log_pane:
-            return False
-        return self.log_pane.wrap_lines
-
-    def toggle_follow(self):
-        """Toggle auto line following."""
-        self.follow = not self.follow
-        if self.follow:
-            # Disable search match follow mode.
-            self.follow_search_match = False
-            self.scroll_to_bottom()
-
-    def filter_scan(self, log: 'LogLine'):
-        filter_match_count = 0
-        for _filter_text, log_filter in self.filters.items():
-            if log_filter.matches(log):
-                filter_match_count += 1
-            else:
-                break
-
-        if filter_match_count == len(self.filters):
-            return True
-        return False
-
-    def new_logs_arrived(self):
-        """Check newly arrived log messages.
-
-        Depending on where log statements occur ``new_logs_arrived`` may be in a
-        separate thread since it is triggerd by the Python log handler
-        ``emit()`` function. In this case the log handler is the LogStore
-        instance ``self.log_store``. This function should not redraw the screen
-        or scroll.
-        """
-        latest_total = self.log_store.get_total_count()
-
-        if self.filtering_on:
-            # Scan newly arived log lines
-            for i in range(self._last_log_store_index, latest_total):
-                if self.filter_scan(self.log_store.logs[i]):
-                    self.filtered_logs.append(self.log_store.logs[i])
-
-        if self.search_filter:
-            last_matched_log: Optional[int] = None
-            # Scan newly arived log lines
-            for i in range(self._last_log_store_index, latest_total):
-                if self.search_filter.matches(self.log_store.logs[i]):
-                    self.save_search_matched_line(i)
-                    last_matched_log = i
-            if last_matched_log and self.follow_search_match:
-                # Set the follow event flag for the next render_content call.
-                self.follow_event = FollowEvent.SEARCH_MATCH
-                self.last_search_matched_log = last_matched_log
-
-        self._last_log_store_index = latest_total
-        self._new_logs_since_last_render = True
-
-        if self.follow:
-            # Set the follow event flag for the next render_content call.
-            self.follow_event = FollowEvent.STICKY_FOLLOW
-
-        # Trigger a UI update
-        self._update_prompt_toolkit_ui()
-
-    def _update_prompt_toolkit_ui(self):
-        """Update Prompt Toolkit UI if a certain amount of time has passed."""
-        emit_time = time.time()
-        # Has enough time passed since last UI redraw?
-        if emit_time > self._last_ui_update_time + self._ui_update_frequency:
-            # Update last log time
-            self._last_ui_update_time = emit_time
-
-            # Trigger Prompt Toolkit UI redraw.
-            self.log_pane.application.redraw_ui()
-
-    def get_cursor_position(self) -> Point:
-        """Return the position of the cursor."""
-        return Point(0, self.log_screen.cursor_position)
-
-    def scroll_to_top(self):
-        """Move selected index to the beginning."""
-        # Stop following so cursor doesn't jump back down to the bottom.
-        self.follow = False
-        # First possible log index that should be displayed
-        log_beginning_index = self.hidden_line_count()
-        self.log_index = log_beginning_index
-        self.log_screen.reset_logs(log_index=self.log_index)
-        self.log_screen.shift_selected_log_to_top()
-        self._user_scroll_event = True
-
-    def move_selected_line_to_top(self):
-        self.follow = False
-
-        # Update selected line
-        self._update_log_index()
-
-        self.log_screen.reset_logs(log_index=self.log_index)
-        self.log_screen.shift_selected_log_to_top()
-        self._user_scroll_event = True
-
-    def center_log_line(self):
-        self.follow = False
-
-        # Update selected line
-        self._update_log_index()
-
-        self.log_screen.reset_logs(log_index=self.log_index)
-        self.log_screen.shift_selected_log_to_center()
-        self._user_scroll_event = True
-
-    def scroll_to_bottom(self, with_sticky_follow: bool = True):
-        """Move selected index to the end."""
-        # Don't change following state like scroll_to_top.
-        self.log_index = max(0, self.get_last_log_index())
-        self.log_screen.reset_logs(log_index=self.log_index)
-
-        # Sticky follow mode
-        if with_sticky_follow:
-            self.follow = True
-        self._user_scroll_event = True
-
-    def scroll(self, lines) -> None:
-        """Scroll up or down by plus or minus lines.
-
-        This method is only called by user keybindings.
-        """
-        # If the user starts scrolling, stop auto following.
-        self.follow = False
-
-        self.log_screen.scroll_subline(lines)
-        self._user_scroll_event = True
-
-        # Update the current log
-        current_line = self._update_log_index()
-
-        # Don't check for sticky follow mode if selecting lines.
-        if self.visual_select_mode:
-            return
-        # Is the last log line selected?
-        if self.log_index == self.get_last_log_index():
-            # Is the last line of the current log selected?
-            if current_line.subline + 1 == current_line.height:
-                # Sticky follow mode
-                self.follow = True
-
-    def visual_selected_log_count(self) -> int:
-        if self.marked_logs_start is None or self.marked_logs_end is None:
-            return 0
-        return (self.marked_logs_end - self.marked_logs_start) + 1
-
-    def clear_visual_selection(self) -> None:
-        self.marked_logs_start = None
-        self.marked_logs_end = None
-        self.visual_select_mode = False
-        self._user_scroll_event = True
-        self.log_pane.application.redraw_ui()
-
-    def visual_select_all(self) -> None:
-        self.marked_logs_start = self._scrollback_start_index
-        self.marked_logs_end = self.get_total_count() - 1
-
-        self.visual_select_mode = True
-        self._user_scroll_event = True
-        self.log_pane.application.redraw_ui()
-
-    def visual_select_up(self) -> None:
-        # Select the current line
-        self.visual_select_line(self.get_cursor_position(), autoscroll=False)
-        # Move the cursor by 1
-        self.scroll_up(1)
-        # Select the new line
-        self.visual_select_line(self.get_cursor_position(), autoscroll=False)
-
-    def visual_select_down(self) -> None:
-        # Select the current line
-        self.visual_select_line(self.get_cursor_position(), autoscroll=False)
-        # Move the cursor by 1
-        self.scroll_down(1)
-        # Select the new line
-        self.visual_select_line(self.get_cursor_position(), autoscroll=False)
-
-    def visual_select_line(self,
-                           mouse_position: Point,
-                           autoscroll: bool = True) -> None:
-        """Mark the log under mouse_position as visually selected."""
-        # Check mouse_position is valid
-        if not 0 <= mouse_position.y < len(self.log_screen.line_buffer):
-            return
-        # Update mode flags
-        self.visual_select_mode = True
-        self.follow = False
-        # Get the ScreenLine for the cursor position
-        screen_line = self.log_screen.line_buffer[mouse_position.y]
-        if screen_line.log_index is None:
-            return
-
-        if self.marked_logs_start is None:
-            self.marked_logs_start = screen_line.log_index
-        if self.marked_logs_end is None:
-            self.marked_logs_end = screen_line.log_index
-
-        if screen_line.log_index < self.marked_logs_start:
-            self.marked_logs_start = screen_line.log_index
-        elif screen_line.log_index > self.marked_logs_end:
-            self.marked_logs_end = screen_line.log_index
-
-        # Update cursor position
-        self.log_screen.move_cursor_to_position(mouse_position.y)
-
-        # Autoscroll when mouse dragging on the top or bottom of the window.
-        if autoscroll:
-            if mouse_position.y == 0:
-                self.scroll_up(1)
-            elif mouse_position.y == self._window_height - 1:
-                self.scroll_down(1)
-
-        # Trigger a rerender.
-        self._user_scroll_event = True
-        self.log_pane.application.redraw_ui()
-
-    def scroll_to_position(self, mouse_position: Point):
-        """Set the selected log line to the mouse_position."""
-        # Disable follow mode when the user clicks or mouse drags on a log line.
-        self.follow = False
-
-        self.log_screen.move_cursor_to_position(mouse_position.y)
-        self._update_log_index()
-
-        self._user_scroll_event = True
-
-    def scroll_up_one_page(self):
-        """Move the selected log index up by one window height."""
-        lines = 1
-        if self._window_height > 0:
-            lines = self._window_height
-        self.scroll(-1 * lines)
-
-    def scroll_down_one_page(self):
-        """Move the selected log index down by one window height."""
-        lines = 1
-        if self._window_height > 0:
-            lines = self._window_height
-        self.scroll(lines)
-
-    def scroll_down(self, lines=1):
-        """Move the selected log index down by one or more lines."""
-        self.scroll(lines)
-
-    def scroll_up(self, lines=1):
-        """Move the selected log index up by one or more lines."""
-        self.scroll(-1 * lines)
-
-    def log_start_end_indexes_changed(self) -> bool:
-        return (self._last_start_index != self._current_start_index
-                or self._last_end_index != self._current_end_index)
-
-    def render_table_header(self):
-        """Get pre-formatted table header."""
-        return self.log_store.render_table_header()
-
-    def render_content(self) -> list:
-        """Return logs to display on screen as a list of FormattedText tuples.
-
-        This function determines when the log screen requires re-rendeing based
-        on user scroll events, follow mode being on, or log pane being
-        empty. The FormattedText tuples passed to prompt_toolkit are cached if
-        no updates are required.
-        """
-        screen_update_needed = False
-
-        # Check window size
-        if self.log_pane.pane_resized():
-            self._window_width = self.log_pane.current_log_pane_width
-            self._window_height = self.log_pane.current_log_pane_height
-            self.log_screen.resize(self._window_width, self._window_height)
-            self._reset_log_screen_on_next_render = True
-
-        if self.follow_event is not None:
-            if (self.follow_event == FollowEvent.SEARCH_MATCH
-                    and self.last_search_matched_log):
-                self.log_index = self.last_search_matched_log
-                self.last_search_matched_log = None
-                self._reset_log_screen_on_next_render = True
-
-            elif self.follow_event == FollowEvent.STICKY_FOLLOW:
-                # Jump to the last log message
-                self.log_index = max(0, self.get_last_log_index())
-
-            self.follow_event = None
-            screen_update_needed = True
-
-        if self._reset_log_screen_on_next_render or self.log_screen.empty():
-            # Clear the reset flag.
-            self._reset_log_screen_on_next_render = False
-            self.log_screen.reset_logs(log_index=self.log_index)
-            screen_update_needed = True
-
-        elif self.follow and self._new_logs_since_last_render:
-            # Follow mode is on so add new logs to the screen
-            self._new_logs_since_last_render = False
-
-            current_log_index = self.log_index
-            last_rendered_log_index = self.log_screen.last_appended_log_index
-            # If so many logs have arrived than can fit on the screen, redraw
-            # the whole screen from the new position.
-            if (current_log_index -
-                    last_rendered_log_index) > self.log_screen.height:
-                self.log_screen.reset_logs(log_index=self.log_index)
-            # A small amount of logs have arrived, append them one at a time
-            # without redrawing the whole screen.
-            else:
-                for i in range(last_rendered_log_index + 1,
-                               current_log_index + 1):
-                    self.log_screen.append_log(i)
-
-            screen_update_needed = True
-
-        if self.follow:
-            # Select the last line for follow mode.
-            self.log_screen.move_cursor_to_bottom()
-            screen_update_needed = True
-
-        if self._user_scroll_event:
-            self._user_scroll_event = False
-            screen_update_needed = True
-
-        if screen_update_needed:
-            self._line_fragment_cache = self.log_screen.get_lines(
-                marked_logs_start=self.marked_logs_start,
-                marked_logs_end=self.marked_logs_end,
-            )
-        return self._line_fragment_cache
-
-    def _logs_to_text(
-        self,
-        use_table_formatting: bool = True,
-        selected_lines_only: bool = False,
-    ) -> str:
-        """Convert all or selected log messages to plaintext."""
-        def get_table_string(log: LogLine) -> str:
-            return remove_formatting(self.log_store.table.formatted_row(log))
-
-        formatter: Callable[[LogLine],
-                            str] = operator.attrgetter('ansi_stripped_log')
-        if use_table_formatting:
-            formatter = get_table_string
-
-        _start_log_index, log_source = self._get_log_lines()
-
-        log_index_range = range(self._scrollback_start_index,
-                                self.get_total_count())
-        if (selected_lines_only and self.marked_logs_start is not None
-                and self.marked_logs_end is not None):
-            log_index_range = range(self.marked_logs_start,
-                                    self.marked_logs_end + 1)
-
-        text_output = ''
-        for i in log_index_range:
-            log_text = formatter(log_source[i])
-            text_output += log_text
-            if not log_text.endswith('\n'):
-                text_output += '\n'
-
-        return text_output
-
-    def export_logs(
-        self,
-        use_table_formatting: bool = True,
-        selected_lines_only: bool = False,
-        file_name: Optional[str] = None,
-        to_clipboard: bool = False,
-        add_markdown_fence: bool = False,
-    ) -> bool:
-        """Export log lines to file or clipboard."""
-        text_output = self._logs_to_text(use_table_formatting,
-                                         selected_lines_only)
-
-        if file_name:
-            target_path = Path(file_name).expanduser()
-            with target_path.open('w') as output_file:
-                output_file.write(text_output)
-            _LOG.debug('Saved to file: %s', file_name)
-
-        elif to_clipboard:
-            if add_markdown_fence:
-                text_output = '```\n' + text_output + '```\n'
-            self.log_pane.application.application.clipboard.set_text(
-                text_output)
-            _LOG.debug('Copied logs to clipboard.')
-
-        return True
diff --git a/pw_console/py/pw_console/mouse.py b/pw_console/py/pw_console/mouse.py
deleted file mode 100644
index c40897a..0000000
--- a/pw_console/py/pw_console/mouse.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Mouse handler fuctions."""
-
-from prompt_toolkit.application.current import get_app
-from prompt_toolkit.filters import has_focus
-from prompt_toolkit.mouse_events import MouseEvent, MouseEventType
-
-
-def focus_handler(container, mouse_event: MouseEvent):
-    """Focus container on click."""
-    if not has_focus(container)():
-        if mouse_event.event_type == MouseEventType.MOUSE_UP:
-            get_app().layout.focus(container)
-            return None
-    return NotImplemented
diff --git a/pw_console/py/pw_console/pigweed_code_style.py b/pw_console/py/pw_console/pigweed_code_style.py
deleted file mode 100644
index 325cac4..0000000
--- a/pw_console/py/pw_console/pigweed_code_style.py
+++ /dev/null
@@ -1,81 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Brighter PigweedCode pygments style."""
-
-import copy
-import re
-
-from prompt_toolkit.styles.style_transformation import get_opposite_color
-
-from pygments.style import Style  # type: ignore
-from pygments.token import Comment, Keyword, Name, Generic  # type: ignore
-from pygments_style_dracula.dracula import DraculaStyle  # type: ignore
-
-_style_list = copy.copy(DraculaStyle.styles)
-
-# Darker Prompt
-_style_list[Generic.Prompt] = '#bfbfbf'
-# Lighter comments
-_style_list[Comment] = '#778899'
-_style_list[Comment.Hashbang] = '#778899'
-_style_list[Comment.Multiline] = '#778899'
-_style_list[Comment.Preproc] = '#ff79c6'
-_style_list[Comment.Single] = '#778899'
-_style_list[Comment.Special] = '#778899'
-# Lighter output
-_style_list[Generic.Output] = '#f8f8f2'
-_style_list[Generic.Emph] = '#f8f8f2'
-# Remove 'italic' from these
-_style_list[Keyword.Declaration] = '#8be9fd'
-_style_list[Name.Builtin] = '#8be9fd'
-_style_list[Name.Label] = '#8be9fd'
-_style_list[Name.Variable] = '#8be9fd'
-_style_list[Name.Variable.Class] = '#8be9fd'
-_style_list[Name.Variable.Global] = '#8be9fd'
-_style_list[Name.Variable.Instance] = '#8be9fd'
-
-_COLOR_REGEX = re.compile(r'#(?P<hex>[0-9a-fA-F]{6}) *(?P<rest>.*?)$')
-
-
-def swap_light_dark(color: str) -> str:
-    if not color:
-        return color
-    match = _COLOR_REGEX.search(color)
-    if not match:
-        return color
-    match_groups = match.groupdict()
-    opposite_color = get_opposite_color(match_groups['hex'])
-    if not opposite_color:
-        return color
-    rest = match_groups.get('rest', '')
-    return '#' + ' '.join([opposite_color, rest])
-
-
-class PigweedCodeStyle(Style):
-
-    background_color = '#2e2e2e'
-    default_style = ''
-
-    styles = _style_list
-
-
-class PigweedCodeLightStyle(Style):
-
-    background_color = '#f8f8f8'
-    default_style = ''
-
-    styles = {
-        key: swap_light_dark(value)
-        for key, value in _style_list.items()
-    }
diff --git a/pw_console/py/pw_console/plugin_mixin.py b/pw_console/py/pw_console/plugin_mixin.py
deleted file mode 100644
index 1461080..0000000
--- a/pw_console/py/pw_console/plugin_mixin.py
+++ /dev/null
@@ -1,145 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Window pane base class."""
-
-import asyncio
-import logging
-from threading import Thread
-import time
-from typing import Callable, Optional
-
-from pw_console.get_pw_console_app import get_pw_console_app
-
-
-class PluginMixin:
-    """Handles background task management in a Pigweed Console plugin.
-
-    Pigweed Console plugins can inherit from this class if they require running
-    tasks in the background. This is important as any plugin code not in its
-    own dedicated thread can potentially block the user interface
-
-    Example usage: ::
-
-        import logging
-        from pw_console.plugin_mixin import PluginMixin
-        from pw_console.widgets import WindowPaneToolbar
-
-        class AwesomeToolbar(WindowPaneToolbar, PluginMixin):
-            TOOLBAR_HEIGHT = 1
-
-            def __init__(self, *args, **kwargs):
-                # Call parent class WindowPaneToolbar.__init__
-                super().__init__(*args, **kwargs)
-
-                # Set PluginMixin to execute
-                # self._awesome_background_task every 10 seconds.
-                self.plugin_init(
-                    plugin_callback=self._awesome_background_task,
-                    plugin_callback_frequency=10.0,
-                    plugin_logger_name='awesome_toolbar_plugin')
-
-            # This function will be run in a separate thread every 10 seconds.
-            def _awesome_background_task(self) -> bool:
-                time.sleep(1)  # Do real work here.
-
-                if self.new_data_processed:
-                    # If new data was processed, and the user interface
-                    # should be updated return True.
-
-                    # Log using self.plugin_logger for debugging.
-                    self.plugin_logger.debug('New data processed')
-
-                    # Return True to signal a UI redraw.
-                    return True
-
-                # Returning False means no updates needed.
-                return False
-
-    Attributes:
-        plugin_callback: Callable that is run in a background thread.
-        plugin_callback_frequency: Number of seconds to wait between
-            executing plugin_callback.
-        plugin_logger: logging instance for this plugin. Useful for debugging
-            code running in a separate thread.
-        plugin_callback_future: `Future`_ object for the plugin background task.
-        plugin_event_loop: asyncio event loop running in the background thread.
-        plugin_enable_background_task: If True, keep periodically running
-            plugin_callback at the desired frequency. If False the background
-            task will stop.
-
-    .. _Future: https://docs.python.org/3/library/asyncio-future.html
-    """
-    def plugin_init(
-        self,
-        plugin_callback: Optional[Callable[..., bool]] = None,
-        plugin_callback_frequency: float = 30.0,
-        plugin_logger_name: Optional[str] = 'pw_console_plugins',
-    ) -> None:
-        """Call this on __init__() to set plugin background task variables.
-
-        Args:
-            plugin_callback: Callable to run in a separate thread from the
-                Pigweed Console UI. This function should return True if the UI
-                should be redrawn after execution.
-            plugin_callback_frequency: Number of seconds to wait between
-                executing plugin_callback.
-            plugin_logger_name: Unique name for this plugin's Python
-                logger. Useful for debugging code running in a separate thread.
-        """
-        self.plugin_callback = plugin_callback
-        self.plugin_callback_frequency = plugin_callback_frequency
-        self.plugin_logger = logging.getLogger(plugin_logger_name)
-
-        self.plugin_callback_future = None
-
-        # Event loop for executing plugin code.
-        self.plugin_event_loop = asyncio.new_event_loop()
-        self.plugin_enable_background_task = True
-
-    def plugin_start(self):
-        """Function used to start this plugin's background thead and task."""
-
-        # Create an entry point for the plugin thread.
-        def _plugin_thread_entry():
-            # Disable log propagation
-            self.plugin_logger.propagate = False
-            asyncio.set_event_loop(self.plugin_event_loop)
-            self.plugin_event_loop.run_forever()
-
-        # Create a thread for running user code so the UI isn't blocked.
-        thread = Thread(target=_plugin_thread_entry, args=(), daemon=True)
-        thread.start()
-
-        self.plugin_logger.debug('Starting plugin: %s', self)
-        if self.plugin_callback is None:
-            return
-
-        self.plugin_enable_background_task = True
-        self.plugin_callback_future = asyncio.run_coroutine_threadsafe(
-            # This function will be executed in a separate thread.
-            self._plugin_periodically_run_callback(),
-            # Using this asyncio event loop.
-            self.plugin_event_loop)  # type: ignore
-
-    def plugin_stop(self):
-        self.plugin_enable_background_task = False
-
-    async def _plugin_periodically_run_callback(self) -> None:
-        while self.plugin_enable_background_task:
-            start_time = time.time()
-            # Run the callback and redraw the UI if return value is True
-            if self.plugin_callback and self.plugin_callback():
-                get_pw_console_app().redraw_ui()
-            run_time = time.time() - start_time
-            await asyncio.sleep(self.plugin_callback_frequency - run_time)
diff --git a/pw_console/py/pw_console/plugins/__init__.py b/pw_console/py/pw_console/plugins/__init__.py
deleted file mode 100644
index 693076d..0000000
--- a/pw_console/py/pw_console/plugins/__init__.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Pigweed Console example plugins."""
diff --git a/pw_console/py/pw_console/plugins/bandwidth_toolbar.py b/pw_console/py/pw_console/plugins/bandwidth_toolbar.py
deleted file mode 100644
index e661a41..0000000
--- a/pw_console/py/pw_console/plugins/bandwidth_toolbar.py
+++ /dev/null
@@ -1,86 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Bandwidth Monitor Toolbar"""
-
-from prompt_toolkit.layout import WindowAlign
-
-from pw_console.plugin_mixin import PluginMixin
-from pw_console.widgets import ToolbarButton, WindowPaneToolbar
-from pw_console.pyserial_wrapper import BANDWIDTH_HISTORY_CONTEXTVAR
-
-
-class BandwidthToolbar(WindowPaneToolbar, PluginMixin):
-    """Toolbar for displaying bandwidth history."""
-    TOOLBAR_HEIGHT = 1
-
-    def _update_toolbar_text(self):
-        """Format toolbar text.
-
-        This queries pyserial_wrapper's EventCountHistory context var to
-        retrieve the byte count history for read, write and totals."""
-        tokens = []
-        self.plugin_logger.debug('BandwidthToolbar _update_toolbar_text')
-
-        for count_name, events in self.history.items():
-            tokens.extend([
-                ('', '  '),
-                ('class:theme-bg-active class:theme-fg-active',
-                 ' {}: '.format(count_name.title())),
-                ('class:theme-bg-active class:theme-fg-cyan',
-                 '{:.3f} '.format(events.last_count())),
-                ('class:theme-bg-active class:theme-fg-orange',
-                 '{} '.format(events.display_unit_title)),
-            ])
-            if count_name == 'total':
-                tokens.append(
-                    ('class:theme-fg-cyan', '{}'.format(events.sparkline())))
-
-        self.formatted_text = tokens
-
-    def get_left_text_tokens(self):
-        """Formatted text to display on the far left side."""
-        return self.formatted_text
-
-    def get_right_text_tokens(self):
-        """Formatted text to display on the far right side."""
-        return [('class:theme-fg-blue', 'Serial Bandwidth Usage ')]
-
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args,
-                         center_section_align=WindowAlign.RIGHT,
-                         **kwargs)
-
-        self.history = BANDWIDTH_HISTORY_CONTEXTVAR.get()
-        self.show_toolbar = True
-        self.formatted_text = []
-
-        # Buttons for display in the center
-        self.add_button(
-            ToolbarButton(description='Refresh',
-                          mouse_handler=self._update_toolbar_text))
-
-        # Set plugin options
-        self.background_task_update_count: int = 0
-        self.plugin_init(
-            plugin_callback=self._background_task,
-            plugin_callback_frequency=1.0,
-            plugin_logger_name='pw_console_bandwidth_toolbar',
-        )
-
-    def _background_task(self) -> bool:
-        self.background_task_update_count += 1
-        self._update_toolbar_text()
-        self.plugin_logger.debug('BandwidthToolbar Scheduled Update: #%s',
-                                 self.background_task_update_count)
-        return True
diff --git a/pw_console/py/pw_console/plugins/calc_pane.py b/pw_console/py/pw_console/plugins/calc_pane.py
deleted file mode 100644
index f85a600..0000000
--- a/pw_console/py/pw_console/plugins/calc_pane.py
+++ /dev/null
@@ -1,199 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Example text input-output Plugin."""
-
-from typing import TYPE_CHECKING
-
-from prompt_toolkit.document import Document
-from prompt_toolkit.key_binding import KeyBindings, KeyPressEvent
-from prompt_toolkit.layout import Window
-from prompt_toolkit.widgets import SearchToolbar, TextArea
-
-from pw_console.widgets import ToolbarButton, WindowPane, WindowPaneToolbar
-
-if TYPE_CHECKING:
-    from pw_console.console_app import ConsoleApp
-
-
-class CalcPane(WindowPane):
-    """Example plugin that accepts text input and displays output.
-
-    This plugin is similar to the full-screen calculator example provided in
-    prompt_toolkit:
-    https://github.com/prompt-toolkit/python-prompt-toolkit/blob/3.0.23/examples/full-screen/calculator.py
-
-    It's a full window that can be moved around the user interface like other
-    Pigweed Console window panes. An input prompt is displayed on the bottom of
-    the window where the user can type in some math equation. When the enter key
-    is pressed the input is processed and the result shown in the top half of
-    the window.
-
-    Both input and output fields are prompt_toolkit TextArea objects which can
-    have their own options like syntax highlighting.
-    """
-    def __init__(self):
-        # Call WindowPane.__init__ and set the title to 'Calculator'
-        super().__init__(pane_title='Calculator')
-
-        # Create a TextArea for the output-field
-        # TextArea is a prompt_toolkit widget that can display editable text in
-        # a buffer. See the prompt_toolkit docs for all possible options:
-        # https://python-prompt-toolkit.readthedocs.io/en/latest/pages/reference.html#prompt_toolkit.widgets.TextArea
-        self.output_field = TextArea(
-            # Optional Styles to apply to this TextArea
-            style='class:output-field',
-            # Initial text to put into the buffer.
-            text='Calculator Output',
-            # Allow this buffer to be in focus. This lets you drag select text
-            # contained inside, and edit the contents unless readonly.
-            focusable=True,
-            # Focus on mouse click.
-            focus_on_click=True,
-        )
-
-        # This is the search toolbar and only appears if the user presses ctrl-r
-        # to do reverse history search (similar to bash or zsh). Its used by the
-        # input_field below.
-        self.search_field = SearchToolbar()
-
-        # Create a TextArea for the user input.
-        self.input_field = TextArea(
-            # The height is set to 1 line
-            height=1,
-            # Prompt string that appears before the cursor.
-            prompt='>>> ',
-            # Optional Styles to apply to this TextArea
-            style='class:input-field',
-            # We only allow one line input for this example but multiline is
-            # supported by prompt_toolkit.
-            multiline=False,
-            wrap_lines=False,
-            # Allow reverse history search
-            search_field=self.search_field,
-            # Allow this input to be focused.
-            focusable=True,
-            # Focus on mouse click.
-            focus_on_click=True,
-        )
-
-        # The TextArea accept_handler function is called by prompt_toolkit (the
-        # UI) when the user presses enter. Here we override it to our own accept
-        # handler defined in this CalcPane class.
-        self.input_field.accept_handler = self.accept_input
-
-        # Create a toolbar for display at the bottom of this window. It will
-        # show the window title and toolbar buttons.
-        self.bottom_toolbar = WindowPaneToolbar(self)
-        self.bottom_toolbar.add_button(
-            ToolbarButton(
-                key='Enter',  # Key binding for this function
-                description='Run Calculation',  # Button name
-                # Function to run when clicked.
-                mouse_handler=self.run_calculation,
-            ))
-        self.bottom_toolbar.add_button(
-            ToolbarButton(
-                key='Ctrl-c',  # Key binding for this function
-                description='Copy Output',  # Button name
-                # Function to run when clicked.
-                mouse_handler=self.copy_all_output,
-            ))
-
-        # self.container is the root container that contains objects to be
-        # rendered in the UI, one on top of the other.
-        self.container = self._create_pane_container(
-            # Show the output_field on top
-            self.output_field,
-            # Draw a separator line with height=1
-            Window(height=1, char='─', style='class:line'),
-            # Show the input field just below that.
-            self.input_field,
-            # If ctrl-r reverse history is active, show the search box below the
-            # input_field.
-            self.search_field,
-            # Lastly, show the toolbar.
-            self.bottom_toolbar,
-        )
-
-    def pw_console_init(self, app: 'ConsoleApp') -> None:
-        """Set the Pigweed Console application instance.
-
-        This function is called after the Pigweed Console starts up and allows
-        access to the user preferences. Prefs is required for creating new
-        user-remappable keybinds."""
-        self.application = app
-        self.set_custom_keybinds()
-
-    def set_custom_keybinds(self) -> None:
-        # Fetch ConsoleApp preferences to load user keybindings
-        prefs = self.application.prefs
-        # Register a named keybind function that is user re-mappable
-        prefs.register_named_key_function(
-            'calc-pane.copy-selected-text',
-            # default bindings
-            ['c-c'])
-
-        # For setting additional keybindings to the output_field.
-        key_bindings = KeyBindings()
-
-        # Map the 'calc-pane.copy-selected-text' function keybind to the
-        # _copy_all_output function below. This will set
-        @prefs.register_keybinding('calc-pane.copy-selected-text',
-                                   key_bindings)
-        def _copy_all_output(_event: KeyPressEvent) -> None:
-            """Copy selected text from the output buffer."""
-            self.copy_selected_output()
-
-        # Set the output_field controls key_bindings to the new bindings.
-        self.output_field.control.key_bindings = key_bindings
-
-    def run_calculation(self):
-        """Trigger the input_field's accept_handler.
-
-        This has the same effect as pressing enter in the input_field.
-        """
-        self.input_field.buffer.validate_and_handle()
-
-    def accept_input(self, _buffer):
-        """Function run when the user presses enter in the input_field.
-
-        Takes a buffer argument that contains the user's input text.
-        """
-        # Evaluate the user's calculator expression as Python and format the
-        # output result.
-        try:
-            output = "\n\nIn:  {}\nOut: {}".format(
-                self.input_field.text,
-                # NOTE: Don't use 'eval' in real code (this is just an example)
-                eval(self.input_field.text))  # pylint: disable=eval-used
-        except BaseException as exception:  # pylint: disable=broad-except
-            output = "\n\n{}".format(exception)
-
-        # Append the new output result to the existing output_field contents.
-        new_text = self.output_field.text + output
-
-        # Update the output_field with the new contents and move the
-        # cursor_position to the end.
-        self.output_field.buffer.document = Document(
-            text=new_text, cursor_position=len(new_text))
-
-    def copy_selected_output(self):
-        """Copy highlighted text in the output_field to the system clipboard."""
-        clipboard_data = self.output_field.buffer.copy_selection()
-        self.application.application.clipboard.set_data(clipboard_data)
-
-    def copy_all_output(self):
-        """Copy all text in the output_field to the system clipboard."""
-        self.application.application.clipboard.set_text(
-            self.output_field.buffer.text)
diff --git a/pw_console/py/pw_console/plugins/clock_pane.py b/pw_console/py/pw_console/plugins/clock_pane.py
deleted file mode 100644
index 090ac81..0000000
--- a/pw_console/py/pw_console/plugins/clock_pane.py
+++ /dev/null
@@ -1,433 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Example Plugin that displays some dynamic content (a clock) and examples of
-text formatting."""
-
-from datetime import datetime
-
-from prompt_toolkit.filters import Condition, has_focus
-from prompt_toolkit.formatted_text import (
-    FormattedText,
-    HTML,
-    merge_formatted_text,
-)
-from prompt_toolkit.key_binding import KeyBindings, KeyPressEvent
-from prompt_toolkit.layout import FormattedTextControl, Window, WindowAlign
-from prompt_toolkit.mouse_events import MouseEvent, MouseEventType
-
-from pw_console.plugin_mixin import PluginMixin
-from pw_console.widgets import ToolbarButton, WindowPane, WindowPaneToolbar
-from pw_console.get_pw_console_app import get_pw_console_app
-
-# Helper class used by the ClockPane plugin for displaying dynamic text,
-# handling key bindings and mouse input. See the ClockPane class below for the
-# beginning of the plugin implementation.
-
-
-class ClockControl(FormattedTextControl):
-    """Example prompt_toolkit UIControl for displaying formatted text.
-
-    This is the prompt_toolkit class that is responsible for drawing the clock,
-    handling keybindings if in focus, and mouse input.
-    """
-    def __init__(self, clock_pane: 'ClockPane', *args, **kwargs) -> None:
-        self.clock_pane = clock_pane
-
-        # Set some custom key bindings to toggle the view mode and wrap lines.
-        key_bindings = KeyBindings()
-
-        # If you press the v key this _toggle_view_mode function will be run.
-        @key_bindings.add('v')
-        def _toggle_view_mode(_event: KeyPressEvent) -> None:
-            """Toggle view mode."""
-            self.clock_pane.toggle_view_mode()
-
-        # If you press the w key this _toggle_wrap_lines function will be run.
-        @key_bindings.add('w')
-        def _toggle_wrap_lines(_event: KeyPressEvent) -> None:
-            """Toggle line wrapping."""
-            self.clock_pane.toggle_wrap_lines()
-
-        # Include the key_bindings keyword arg when passing to the parent class
-        # __init__ function.
-        kwargs['key_bindings'] = key_bindings
-        # Call the parent FormattedTextControl.__init__
-        super().__init__(*args, **kwargs)
-
-    def mouse_handler(self, mouse_event: MouseEvent):
-        """Mouse handler for this control."""
-        # If the user clicks anywhere this function is run.
-
-        # Mouse positions relative to this control. x is the column starting
-        # from the left size as zero. y is the row starting with the top as
-        # zero.
-        _click_x = mouse_event.position.x
-        _click_y = mouse_event.position.y
-
-        # Mouse click behavior usually depends on if this window pane is in
-        # focus. If not in focus, then focus on it when left clicking. If
-        # already in focus then perform the action specific to this window.
-
-        # If not in focus, change focus to this clock pane and do nothing else.
-        if not has_focus(self.clock_pane)():
-            if mouse_event.event_type == MouseEventType.MOUSE_UP:
-                get_pw_console_app().focus_on_container(self.clock_pane)
-                # Mouse event handled, return None.
-                return None
-
-        # If code reaches this point, this window is already in focus.
-        # On left click
-        if mouse_event.event_type == MouseEventType.MOUSE_UP:
-            # Toggle the view mode.
-            self.clock_pane.toggle_view_mode()
-            # Mouse event handled, return None.
-            return None
-
-        # Mouse event not handled, return NotImplemented.
-        return NotImplemented
-
-
-class ClockPane(WindowPane, PluginMixin):
-    """Example Pigweed Console plugin window that displays a clock.
-
-    The ClockPane is a WindowPane based plugin that displays a clock and some
-    formatted text examples. It inherits from both WindowPane and
-    PluginMixin. It can be added on console startup by calling: ::
-
-        my_console.add_window_plugin(ClockPane())
-
-    For an example see:
-    https://pigweed.dev/pw_console/embedding.html#adding-plugins
-    """
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, pane_title='Clock', **kwargs)
-        # Some toggle settings to change view and wrap lines.
-        self.view_mode_clock: bool = True
-        self.wrap_lines: bool = False
-        # Counter variable to track how many times the background task runs.
-        self.background_task_update_count: int = 0
-
-        # ClockControl is responsible for rendering the dynamic content provided
-        # by self._get_formatted_text() and handle keyboard and mouse input.
-        # Using a control is always necessary for displaying any content that
-        # will change.
-        self.clock_control = ClockControl(
-            self,  # This ClockPane class
-            self._get_formatted_text,  # Callable to get text for display
-            # These are FormattedTextControl options.
-            # See the prompt_toolkit docs for all possible options
-            # https://python-prompt-toolkit.readthedocs.io/en/latest/pages/reference.html#prompt_toolkit.layout.FormattedTextControl
-            show_cursor=False,
-            focusable=True,
-        )
-
-        # Every FormattedTextControl object (ClockControl) needs to live inside
-        # a prompt_toolkit Window() instance. Here is where you specify
-        # alignment, style, and dimensions. See the prompt_toolkit docs for all
-        # opitons:
-        # https://python-prompt-toolkit.readthedocs.io/en/latest/pages/reference.html#prompt_toolkit.layout.Window
-        self.clock_control_window = Window(
-            # Set the content to the clock_control defined above.
-            content=self.clock_control,
-            # Make content left aligned
-            align=WindowAlign.LEFT,
-            # These two set to false make this window fill all available space.
-            dont_extend_width=False,
-            dont_extend_height=False,
-            # Content inside this window will have its lines wrapped if
-            # self.wrap_lines is True.
-            wrap_lines=Condition(lambda: self.wrap_lines),
-        )
-
-        # Create a toolbar for display at the bottom of this clock window. It
-        # will show the window title and buttons.
-        self.bottom_toolbar = WindowPaneToolbar(self)
-
-        # Add a button to toggle the view mode.
-        self.bottom_toolbar.add_button(
-            ToolbarButton(
-                key='v',  # Key binding for this function
-                description='View Mode',  # Button name
-                # Function to run when clicked.
-                mouse_handler=self.toggle_view_mode,
-            ))
-
-        # Add a checkbox button to display if wrap_lines is enabled.
-        self.bottom_toolbar.add_button(
-            ToolbarButton(
-                key='w',  # Key binding for this function
-                description='Wrap',  # Button name
-                # Function to run when clicked.
-                mouse_handler=self.toggle_wrap_lines,
-                # Display a checkbox in this button.
-                is_checkbox=True,
-                # lambda that returns the state of the checkbox
-                checked=lambda: self.wrap_lines,
-            ))
-
-        # self.container is the root container that contains objects to be
-        # rendered in the UI, one on top of the other.
-        self.container = self._create_pane_container(
-            # Display the clock window on top...
-            self.clock_control_window,
-            # and the bottom_toolbar below.
-            self.bottom_toolbar,
-        )
-
-        # This plugin needs to run a task in the background periodically and
-        # uses self.plugin_init() to set which function to run, and how often.
-        # This is provided by PluginMixin. See the docs for more info:
-        # https://pigweed.dev/pw_console/plugins.html#background-tasks
-        self.plugin_init(
-            plugin_callback=self._background_task,
-            # Run self._background_task once per second.
-            plugin_callback_frequency=1.0,
-            plugin_logger_name='pw_console_example_clock_plugin',
-        )
-
-    def _background_task(self) -> bool:
-        """Function run in the background for the ClockPane plugin."""
-        self.background_task_update_count += 1
-        # Make a log message for debugging purposes. For more info see:
-        # https://pigweed.dev/pw_console/plugins.html#debugging-plugin-behavior
-        self.plugin_logger.debug('background_task_update_count: %s',
-                                 self.background_task_update_count)
-
-        # Returning True in the background task will force the user interface to
-        # re-draw.
-        # Returning False means no updates required.
-        return True
-
-    def toggle_view_mode(self):
-        """Toggle the view mode between the clock and formatted text example."""
-        self.view_mode_clock = not self.view_mode_clock
-        self.redraw_ui()
-
-    def toggle_wrap_lines(self):
-        """Enable or disable line wraping/truncation."""
-        self.wrap_lines = not self.wrap_lines
-        self.redraw_ui()
-
-    def _get_formatted_text(self):
-        """This function returns the content that will be displayed in the user
-        interface depending on which view mode is active."""
-        if self.view_mode_clock:
-            return self._get_clock_text()
-        return self._get_example_text()
-
-    def _get_clock_text(self):
-        """Create the time with some color formatting."""
-        # pylint: disable=no-self-use
-
-        # Get the date and time
-        date, time = datetime.now().isoformat(sep='_',
-                                              timespec='seconds').split('_')
-
-        # Formatted text is represented as (style, text) tuples.
-        # For more examples see:
-        # https://python-prompt-toolkit.readthedocs.io/en/latest/pages/printing_text.html
-
-        # These styles are selected using class names and start with the
-        # 'class:' prefix. For all classes defined by Pigweed Console see:
-        # https://cs.opensource.google/pigweed/pigweed/+/main:pw_console/py/pw_console/style.py;l=189
-
-        # Date in cyan matching the current Pigweed Console theme.
-        date_with_color = ('class:theme-fg-cyan', date)
-        # Time in magenta
-        time_with_color = ('class:theme-fg-magenta', time)
-
-        # No color styles for line breaks and spaces.
-        line_break = ('', '\n')
-        space = ('', ' ')
-
-        # Concatenate the (style, text) tuples.
-        return FormattedText([
-            line_break,
-            space,
-            space,
-            date_with_color,
-            space,
-            time_with_color,
-        ])
-
-    def _get_example_text(self):
-        """Examples of how to create formatted text."""
-        # pylint: disable=no-self-use
-        # Make a list to hold all the formatted text to display.
-        fragments = []
-
-        # Some spacing vars
-        wide_space = ('', '       ')
-        space = ('', ' ')
-        newline = ('', '\n')
-
-        # HTML() is a shorthand way to style text. See:
-        # https://python-prompt-toolkit.readthedocs.io/en/latest/pages/printing_text.html#html
-        # This formats 'Foreground Colors' as underlined:
-        fragments.append(HTML('<u>Foreground Colors</u>\n'))
-
-        # Standard ANSI colors examples
-        fragments.append(
-            FormattedText([
-                # These tuples follow this format:
-                #   (style_string, text_to_display)
-                ('ansiblack', 'ansiblack'),
-                wide_space,
-                ('ansired', 'ansired'),
-                wide_space,
-                ('ansigreen', 'ansigreen'),
-                wide_space,
-                ('ansiyellow', 'ansiyellow'),
-                wide_space,
-                ('ansiblue', 'ansiblue'),
-                wide_space,
-                ('ansimagenta', 'ansimagenta'),
-                wide_space,
-                ('ansicyan', 'ansicyan'),
-                wide_space,
-                ('ansigray', 'ansigray'),
-                wide_space,
-                newline,
-                ('ansibrightblack', 'ansibrightblack'),
-                space,
-                ('ansibrightred', 'ansibrightred'),
-                space,
-                ('ansibrightgreen', 'ansibrightgreen'),
-                space,
-                ('ansibrightyellow', 'ansibrightyellow'),
-                space,
-                ('ansibrightblue', 'ansibrightblue'),
-                space,
-                ('ansibrightmagenta', 'ansibrightmagenta'),
-                space,
-                ('ansibrightcyan', 'ansibrightcyan'),
-                space,
-                ('ansiwhite', 'ansiwhite'),
-                space,
-            ]))
-
-        fragments.append(HTML('\n<u>Background Colors</u>\n'))
-        fragments.append(
-            FormattedText([
-                # Here's an example of a style that specifies both background
-                # and foreground colors. The background color is prefixed with
-                # 'bg:'. The foreground color follows that with no prefix.
-                ('bg:ansiblack ansiwhite', 'ansiblack'),
-                wide_space,
-                ('bg:ansired', 'ansired'),
-                wide_space,
-                ('bg:ansigreen', 'ansigreen'),
-                wide_space,
-                ('bg:ansiyellow', 'ansiyellow'),
-                wide_space,
-                ('bg:ansiblue ansiwhite', 'ansiblue'),
-                wide_space,
-                ('bg:ansimagenta', 'ansimagenta'),
-                wide_space,
-                ('bg:ansicyan', 'ansicyan'),
-                wide_space,
-                ('bg:ansigray', 'ansigray'),
-                wide_space,
-                ('', '\n'),
-                ('bg:ansibrightblack', 'ansibrightblack'),
-                space,
-                ('bg:ansibrightred', 'ansibrightred'),
-                space,
-                ('bg:ansibrightgreen', 'ansibrightgreen'),
-                space,
-                ('bg:ansibrightyellow', 'ansibrightyellow'),
-                space,
-                ('bg:ansibrightblue', 'ansibrightblue'),
-                space,
-                ('bg:ansibrightmagenta', 'ansibrightmagenta'),
-                space,
-                ('bg:ansibrightcyan', 'ansibrightcyan'),
-                space,
-                ('bg:ansiwhite', 'ansiwhite'),
-                space,
-            ]))
-
-        # These themes use Pigweed Console style classes. See full list in:
-        # https://cs.opensource.google/pigweed/pigweed/+/main:pw_console/py/pw_console/style.py;l=189
-        fragments.append(HTML('\n\n<u>Current Theme Foreground Colors</u>\n'))
-        fragments.append([
-            ('class:theme-fg-red', 'class:theme-fg-red'),
-            newline,
-            ('class:theme-fg-orange', 'class:theme-fg-orange'),
-            newline,
-            ('class:theme-fg-yellow', 'class:theme-fg-yellow'),
-            newline,
-            ('class:theme-fg-green', 'class:theme-fg-green'),
-            newline,
-            ('class:theme-fg-cyan', 'class:theme-fg-cyan'),
-            newline,
-            ('class:theme-fg-blue', 'class:theme-fg-blue'),
-            newline,
-            ('class:theme-fg-purple', 'class:theme-fg-purple'),
-            newline,
-            ('class:theme-fg-magenta', 'class:theme-fg-magenta'),
-            newline,
-        ])
-
-        fragments.append(HTML('\n<u>Current Theme Background Colors</u>\n'))
-        fragments.append([
-            ('class:theme-bg-red', 'class:theme-bg-red'),
-            newline,
-            ('class:theme-bg-orange', 'class:theme-bg-orange'),
-            newline,
-            ('class:theme-bg-yellow', 'class:theme-bg-yellow'),
-            newline,
-            ('class:theme-bg-green', 'class:theme-bg-green'),
-            newline,
-            ('class:theme-bg-cyan', 'class:theme-bg-cyan'),
-            newline,
-            ('class:theme-bg-blue', 'class:theme-bg-blue'),
-            newline,
-            ('class:theme-bg-purple', 'class:theme-bg-purple'),
-            newline,
-            ('class:theme-bg-magenta', 'class:theme-bg-magenta'),
-            newline,
-        ])
-
-        fragments.append(HTML('\n<u>Theme UI Colors</u>\n'))
-        fragments.append([
-            ('class:theme-fg-default', 'class:theme-fg-default'),
-            space,
-            ('class:theme-bg-default', 'class:theme-bg-default'),
-            space,
-            ('class:theme-bg-active', 'class:theme-bg-active'),
-            space,
-            ('class:theme-fg-active', 'class:theme-fg-active'),
-            space,
-            ('class:theme-bg-inactive', 'class:theme-bg-inactive'),
-            space,
-            ('class:theme-fg-inactive', 'class:theme-fg-inactive'),
-            newline,
-            ('class:theme-fg-dim', 'class:theme-fg-dim'),
-            space,
-            ('class:theme-bg-dim', 'class:theme-bg-dim'),
-            space,
-            ('class:theme-bg-dialog', 'class:theme-bg-dialog'),
-            space,
-            ('class:theme-bg-line-highlight', 'class:theme-bg-line-highlight'),
-            space,
-            ('class:theme-bg-button-active', 'class:theme-bg-button-active'),
-            space,
-            ('class:theme-bg-button-inactive',
-             'class:theme-bg-button-inactive'),
-            space,
-        ])
-
-        # Return all formatted text lists merged together.
-        return merge_formatted_text(fragments)
diff --git a/pw_console/py/pw_console/progress_bar/__init__.py b/pw_console/py/pw_console/progress_bar/__init__.py
deleted file mode 100644
index 5e0fa18..0000000
--- a/pw_console/py/pw_console/progress_bar/__init__.py
+++ /dev/null
@@ -1,66 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Pigweed Console progress bar functions."""
-from pw_console.progress_bar.progress_bar_state import TASKS_CONTEXTVAR
-from pw_console.progress_bar.progress_bar_task_counter import (
-    ProgressBarTaskCounter)
-
-__all__ = [
-    'start_progress',
-    'update_progress',
-]
-
-
-def start_progress(task_name: str, total: int, hide_eta=False):
-    progress_state = TASKS_CONTEXTVAR.get()
-
-    progress_state.startup_progress_bar_impl()
-
-    assert progress_state.instance is not None
-
-    progress_state.tasks[task_name] = ProgressBarTaskCounter(
-        name=task_name,
-        total=total,
-        prompt_toolkit_counter=progress_state.instance(range(total),
-                                                       label=task_name))
-    ptc = progress_state.tasks[task_name].prompt_toolkit_counter
-    ptc.hide_eta = hide_eta  # type: ignore
-
-
-def update_progress(task_name: str,
-                    count=1,
-                    completed=False,
-                    canceled=False,
-                    new_total=None):
-    progress_state = TASKS_CONTEXTVAR.get()
-    # The caller may not actually get canceled and will continue trying to
-    # update after an interrupt.
-    if task_name not in progress_state.tasks:
-        return
-
-    # Take one action
-    if completed:
-        progress_state.tasks[task_name].mark_completed()
-    elif canceled:
-        progress_state.tasks[task_name].mark_canceled()
-    elif new_total:
-        progress_state.tasks[task_name].set_new_total(new_total)
-    else:
-        progress_state.tasks[task_name].update(count)
-
-    # Check if all tasks are complete
-    if (progress_state.instance is not None
-            and progress_state.all_tasks_complete):
-        if hasattr(progress_state.instance, '__exit__'):
-            progress_state.instance.__exit__()
diff --git a/pw_console/py/pw_console/progress_bar/progress_bar_impl.py b/pw_console/py/pw_console/progress_bar/progress_bar_impl.py
deleted file mode 100644
index fc818ed..0000000
--- a/pw_console/py/pw_console/progress_bar/progress_bar_impl.py
+++ /dev/null
@@ -1,167 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Pigweed Console ProgressBar implementation.
-
-Designed to be embedded in an existing prompt_toolkit full screen
-application."""
-
-import functools
-from typing import (
-    Iterable,
-    List,
-    Optional,
-    Sequence,
-)
-
-from prompt_toolkit.filters import Condition
-from prompt_toolkit.formatted_text import AnyFormattedText
-from prompt_toolkit.layout import (
-    ConditionalContainer,
-    FormattedTextControl,
-    HSplit,
-    VSplit,
-    Window,
-)
-from prompt_toolkit.layout.dimension import AnyDimension, D
-from prompt_toolkit.styles import BaseStyle
-
-from prompt_toolkit.shortcuts.progress_bar import (
-    ProgressBar,
-    ProgressBarCounter,
-)
-from prompt_toolkit.shortcuts.progress_bar.base import _ProgressControl
-from prompt_toolkit.shortcuts.progress_bar.formatters import (
-    Formatter,
-    IterationsPerSecond,
-    Text,
-    TimeLeft,
-    create_default_formatters,
-)
-
-
-class TextIfNotHidden(Text):
-    def format(
-        self,
-        progress_bar: ProgressBar,
-        progress: 'ProgressBarCounter[object]',
-        width: int,
-    ) -> AnyFormattedText:
-        formatted_text = super().format(progress_bar, progress, width)
-        if hasattr(progress, 'hide_eta') and progress.hide_eta:  # type: ignore
-
-            formatted_text = [('', ' ' * width)]
-        return formatted_text
-
-
-class IterationsPerSecondIfNotHidden(IterationsPerSecond):
-    def format(
-        self,
-        progress_bar: ProgressBar,
-        progress: 'ProgressBarCounter[object]',
-        width: int,
-    ) -> AnyFormattedText:
-        formatted_text = super().format(progress_bar, progress, width)
-        if hasattr(progress, 'hide_eta') and progress.hide_eta:  # type: ignore
-            formatted_text = [('class:iterations-per-second', ' ' * width)]
-        return formatted_text
-
-
-class TimeLeftIfNotHidden(TimeLeft):
-    def format(
-        self,
-        progress_bar: ProgressBar,
-        progress: 'ProgressBarCounter[object]',
-        width: int,
-    ) -> AnyFormattedText:
-        formatted_text = super().format(progress_bar, progress, width)
-        if hasattr(progress, 'hide_eta') and progress.hide_eta:  # type: ignore
-            formatted_text = [('class:time-left', ' ' * width)]
-        return formatted_text
-
-
-class ProgressBarImpl:
-    """ProgressBar for rendering in an existing prompt_toolkit application."""
-    def __init__(
-        self,
-        title: AnyFormattedText = None,
-        formatters: Optional[Sequence[Formatter]] = None,
-        style: Optional[BaseStyle] = None,
-    ) -> None:
-
-        self.title = title
-        self.formatters = formatters or create_default_formatters()
-        self.counters: List[ProgressBarCounter[object]] = []
-        self.style = style
-
-        # Create UI Application.
-        title_toolbar = ConditionalContainer(
-            Window(
-                FormattedTextControl(lambda: self.title),
-                height=1,
-                style='class:progressbar,title',
-            ),
-            filter=Condition(lambda: self.title is not None),
-        )
-
-        def width_for_formatter(formatter: Formatter) -> AnyDimension:
-            # Needs to be passed as callable (partial) to the 'width'
-            # parameter, because we want to call it on every resize.
-            return formatter.get_width(progress_bar=self)  # type: ignore
-
-        progress_controls = [
-            Window(
-                content=_ProgressControl(self, f),  # type: ignore
-                width=functools.partial(width_for_formatter, f),
-            ) for f in self.formatters
-        ]
-
-        self.container = HSplit([
-            title_toolbar,
-            VSplit(
-                progress_controls,
-                height=lambda: D(min=len(self.counters),
-                                 max=len(self.counters)),
-            ),
-        ])
-
-    def __pt_container__(self):
-        return self.container
-
-    def __exit__(self, *a: object) -> None:
-        pass
-
-    def __call__(
-        self,
-        data: Optional[Iterable] = None,
-        label: AnyFormattedText = '',
-        remove_when_done: bool = False,
-        total: Optional[int] = None,
-    ) -> 'ProgressBarCounter':
-        """
-        Start a new counter.
-
-        :param label: Title text or description for this progress. (This can be
-            formatted text as well).
-        :param remove_when_done: When `True`, hide this progress bar.
-        :param total: Specify the maximum value if it can't be calculated by
-            calling ``len``.
-        """
-        counter = ProgressBarCounter(
-            self,  # type: ignore
-            data,
-            label=label,
-            remove_when_done=remove_when_done,
-            total=total)
-        self.counters.append(counter)
-        return counter
diff --git a/pw_console/py/pw_console/progress_bar/progress_bar_state.py b/pw_console/py/pw_console/progress_bar/progress_bar_state.py
deleted file mode 100644
index de8486a..0000000
--- a/pw_console/py/pw_console/progress_bar/progress_bar_state.py
+++ /dev/null
@@ -1,132 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Pigweed Console progress bar task state."""
-
-from contextvars import ContextVar
-import copy
-from dataclasses import dataclass, field
-import signal
-from typing import Dict, Optional, Union
-
-from prompt_toolkit.application import get_app_or_none
-from prompt_toolkit.shortcuts import ProgressBar
-from prompt_toolkit.shortcuts.progress_bar import formatters
-
-from pw_console.progress_bar.progress_bar_impl import (
-    IterationsPerSecondIfNotHidden,
-    ProgressBarImpl,
-    TextIfNotHidden,
-    TimeLeftIfNotHidden,
-)
-from pw_console.progress_bar.progress_bar_task_counter import (
-    ProgressBarTaskCounter)
-from pw_console.style import generate_styles
-
-CUSTOM_FORMATTERS = [
-    formatters.Label(suffix=': '),
-    formatters.Rainbow(
-        formatters.Bar(start='|Pigw',
-                       end='|',
-                       sym_a='e',
-                       sym_b='d!',
-                       sym_c=' ')),
-    formatters.Text(' '),
-    formatters.Progress(),
-    formatters.Text(' ['),
-    formatters.Percentage(),
-    formatters.Text('] in '),
-    formatters.TimeElapsed(),
-    TextIfNotHidden(' ('),
-    IterationsPerSecondIfNotHidden(),
-    TextIfNotHidden('/s, eta: '),
-    TimeLeftIfNotHidden(),
-    TextIfNotHidden(')'),
-]
-
-
-def prompt_toolkit_app_running() -> bool:
-    existing_app = get_app_or_none()
-    if existing_app:
-        return True
-    return False
-
-
-@dataclass
-class ProgressBarState:
-    """Pigweed Console wide state for all repl progress bars.
-
-    An instance of this class is intended to be a global variable."""
-    tasks: Dict[str, ProgressBarTaskCounter] = field(default_factory=dict)
-    instance: Optional[Union[ProgressBar, ProgressBarImpl]] = None
-
-    def _install_sigint_handler(self) -> None:
-        """Add ctrl-c handling if not running inside pw_console"""
-        def handle_sigint(_signum, _frame):
-            # Shut down the ProgressBar prompt_toolkit application
-            prog_bar = self.instance
-            if prog_bar is not None and hasattr(prog_bar, '__exit__'):
-                prog_bar.__exit__()
-            raise KeyboardInterrupt
-
-        signal.signal(signal.SIGINT, handle_sigint)
-
-    def startup_progress_bar_impl(self):
-        prog_bar = self.instance
-        if not prog_bar:
-            if prompt_toolkit_app_running():
-                prog_bar = ProgressBarImpl(style=get_app_or_none().style,
-                                           formatters=CUSTOM_FORMATTERS)
-            else:
-                self._install_sigint_handler()
-                prog_bar = ProgressBar(style=generate_styles(),
-                                       formatters=CUSTOM_FORMATTERS)
-                # Start the ProgressBar prompt_toolkit application in a separate
-                # thread.
-                prog_bar.__enter__()
-            self.instance = prog_bar
-        return self.instance
-
-    def cleanup_finished_tasks(self) -> None:
-        for task_name in copy.copy(list(self.tasks.keys())):
-            task = self.tasks[task_name]
-            if task.completed or task.canceled:
-                ptc = task.prompt_toolkit_counter
-                self.tasks.pop(task_name, None)
-                if (self.instance and self.instance.counters
-                        and ptc in self.instance.counters):
-                    self.instance.counters.remove(ptc)
-
-    @property
-    def all_tasks_complete(self) -> bool:
-        tasks_complete = [
-            task.completed or task.canceled
-            for _task_name, task in self.tasks.items()
-        ]
-        self.cleanup_finished_tasks()
-        return all(tasks_complete)
-
-    def cancel_all_tasks(self):
-        self.tasks = {}
-        if self.instance is not None:
-            self.instance.counters = []
-
-    def get_container(self):
-        prog_bar = self.instance
-        if prog_bar is not None and hasattr(prog_bar, '__pt_container__'):
-            return prog_bar.__pt_container__()
-        return None
-
-
-TASKS_CONTEXTVAR = (ContextVar('pw_console_progress_bar_tasks',
-                               default=ProgressBarState()))
diff --git a/pw_console/py/pw_console/progress_bar/progress_bar_task_counter.py b/pw_console/py/pw_console/progress_bar/progress_bar_task_counter.py
deleted file mode 100644
index 43eee19..0000000
--- a/pw_console/py/pw_console/progress_bar/progress_bar_task_counter.py
+++ /dev/null
@@ -1,74 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Container class for a single progress bar task."""
-
-from dataclasses import dataclass
-from typing import Optional
-
-from prompt_toolkit.application import get_app_or_none
-from prompt_toolkit.shortcuts.progress_bar import ProgressBarCounter
-
-
-def _redraw_ui() -> None:
-    """Signal the prompt_toolkit app to re-draw"""
-    pt_app = get_app_or_none()
-    if pt_app:
-        pt_app.invalidate()
-
-
-@dataclass
-class ProgressBarTaskCounter:
-    """Class to hold a single progress bar state."""
-    name: str
-    total: int
-    count: int = 0
-    completed: bool = False
-    canceled: bool = False
-    prompt_toolkit_counter: Optional[ProgressBarCounter] = None
-
-    def mark_canceled(self):
-        self.canceled = True
-        self.prompt_toolkit_counter.stopped = True  # type: ignore
-
-    def mark_completed(self):
-        self.completed = True
-        self.prompt_toolkit_counter.done = True  # type: ignore
-
-    def check_completion(self) -> None:
-        # Check for completion
-        if self.count >= self.total:
-            self.mark_completed()
-
-    def stop_updating_prompt_toolkit_counter(self) -> None:
-        """If count is over total, stop updating the prompt_toolkit ETA."""
-        if self.count >= self.total:
-            self.prompt_toolkit_counter.done = True  # type: ignore
-
-    def update(self, count: int = 1) -> None:
-        """Increment this counter."""
-        self.count += count
-
-        if self.prompt_toolkit_counter:
-            self.prompt_toolkit_counter.items_completed += count
-            self.stop_updating_prompt_toolkit_counter()
-            _redraw_ui()
-
-    def set_new_total(self, new_total: int) -> None:
-        """Set a new total count."""
-        self.count = new_total
-
-        if self.prompt_toolkit_counter:
-            self.prompt_toolkit_counter.items_completed = new_total
-            self.stop_updating_prompt_toolkit_counter()
-            _redraw_ui()
diff --git a/pw_console/py/pw_console/pw_ptpython_repl.py b/pw_console/py/pw_console/pw_ptpython_repl.py
deleted file mode 100644
index 5d68578..0000000
--- a/pw_console/py/pw_console/pw_ptpython_repl.py
+++ /dev/null
@@ -1,336 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""PwPtPythonPane class."""
-
-import asyncio
-import functools
-import io
-import logging
-import sys
-from typing import Iterable, Optional, TYPE_CHECKING
-
-from prompt_toolkit.buffer import Buffer
-from prompt_toolkit.layout.controls import BufferControl
-from prompt_toolkit.completion import merge_completers
-from prompt_toolkit.filters import (
-    Condition,
-    has_focus,
-    to_filter,
-)
-from ptpython.completer import (  # type: ignore
-    CompletePrivateAttributes, PythonCompleter,
-)
-import ptpython.repl  # type: ignore
-from ptpython.layout import (  # type: ignore
-    CompletionVisualisation, Dimension,
-)
-
-import pw_console.text_formatting
-
-if TYPE_CHECKING:
-    from pw_console.repl_pane import ReplPane
-
-_LOG = logging.getLogger(__package__)
-
-
-class MissingPtpythonBufferControl(Exception):
-    """Exception for a missing ptpython BufferControl object."""
-
-
-class PwPtPythonRepl(ptpython.repl.PythonRepl):  # pylint: disable=too-many-instance-attributes
-    """A ptpython repl class with changes to code execution and output related
-    methods."""
-    def __init__(
-        self,
-        *args,
-        # pw_console specific kwargs
-        extra_completers: Optional[Iterable] = None,
-        **ptpython_kwargs,
-    ):
-
-        completer = None
-        if extra_completers:
-            # Create the default python completer used by
-            # ptpython.repl.PythonRepl
-            python_completer = PythonCompleter(
-                # No self.get_globals yet so this must be a lambda
-                # pylint: disable=unnecessary-lambda
-                lambda: self.get_globals(),
-                lambda: self.get_locals(),
-                lambda: self.enable_dictionary_completion,  # type: ignore
-            )
-
-            all_completers = [python_completer]
-            all_completers.extend(extra_completers)
-            # Merge default Python completer with the new custom one.
-            completer = merge_completers(all_completers)
-
-        super().__init__(
-            *args,
-            create_app=False,
-            # Absolute minimum height of 1
-            _input_buffer_height=Dimension(min=1),
-            _completer=completer,
-            **ptpython_kwargs,
-        )
-
-        self.enable_mouse_support: bool = True
-        self.enable_history_search: bool = True
-        self.enable_dictionary_completion: bool = True
-        self._set_pt_python_input_buffer_control_focusable()
-
-        # Change some ptpython.repl defaults.
-        self.show_status_bar = False
-        self.show_exit_confirmation = False
-        self.complete_private_attributes = (
-            CompletePrivateAttributes.IF_NO_PUBLIC)
-
-        # Function signature that shows args, kwargs, and types under the cursor
-        # of the input window.
-        self.show_signature: bool = True
-        # Docstring of the current completed function that appears at the bottom
-        # of the input window.
-        self.show_docstring: bool = False
-
-        # Turn off the completion menu in ptpython. The CompletionsMenu in
-        # ConsoleApp.root_container will handle this.
-        self.completion_visualisation: CompletionVisualisation = (
-            CompletionVisualisation.NONE)
-
-        # Additional state variables.
-        self.repl_pane: 'Optional[ReplPane]' = None
-        self._last_result = None
-        self._last_exception = None
-
-    def _set_pt_python_input_buffer_control_focusable(self) -> None:
-        """Enable focus_on_click for ptpython's input buffer."""
-        error_message = (
-            'Unable to find ptpythons BufferControl input object.\n'
-            '  For the last known position see:\n'
-            '  https://github.com/prompt-toolkit/ptpython/'
-            'blob/6072174eace5b645b0cfd5b21b4c237e2539f577/'
-            'ptpython/layout.py#L598\n'
-            '\n'
-            'The installed version of ptpython may not be compatible with'
-            ' pw console; please try re-running environment setup.')
-
-        try:
-            # Fetch the Window's BufferControl object.
-            # From ptpython/layout.py:
-            #   self.root_container = HSplit([
-            #     VSplit([
-            #       HSplit([
-            #         FloatContainer(
-            #           content=HSplit(
-            #             [create_python_input_window()] + extra_body
-            #           ), ...
-            ptpython_buffer_control = (
-                self.ptpython_layout.root_container.children[0].children[0].
-                children[0].content.children[0].content)
-            # This should be a BufferControl instance
-            if not isinstance(ptpython_buffer_control, BufferControl):
-                raise MissingPtpythonBufferControl(error_message)
-            # Enable focus options
-            ptpython_buffer_control.focusable = to_filter(True)
-            ptpython_buffer_control.focus_on_click = to_filter(True)
-        except IndexError as _error:
-            raise MissingPtpythonBufferControl(error_message)
-
-    def __pt_container__(self):
-        """Return the prompt_toolkit root container for class.
-
-        This allows self to be used wherever prompt_toolkit expects a container
-        object."""
-        return self.ptpython_layout.root_container
-
-    def set_repl_pane(self, repl_pane):
-        """Update the parent pw_console.ReplPane reference."""
-        self.repl_pane = repl_pane
-
-    def _save_result(self, formatted_text):
-        """Save the last repl execution result."""
-        unformatted_result = pw_console.text_formatting.remove_formatting(
-            formatted_text)
-        self._last_result = unformatted_result
-
-    def _save_exception(self, formatted_text):
-        """Save the last repl exception."""
-        unformatted_result = pw_console.text_formatting.remove_formatting(
-            formatted_text)
-        self._last_exception = unformatted_result
-
-    def clear_last_result(self):
-        """Erase the last repl execution result."""
-        self._last_result = None
-        self._last_exception = None
-
-    def show_result(self, result):
-        """Format and save output results.
-
-        This function is called from the _run_user_code() function which is
-        always run from the user code thread, within
-        .run_and_show_expression_async().
-        """
-        formatted_result = self._format_result_output(result)
-        self._save_result(formatted_result)
-
-    def _handle_exception(self, e: BaseException) -> None:
-        """Format and save output results.
-
-        This function is called from the _run_user_code() function which is
-        always run from the user code thread, within
-        .run_and_show_expression_async().
-        """
-        formatted_result = self._format_exception_output(e)
-        self._save_exception(formatted_result.__pt_formatted_text__())
-
-    def user_code_complete_callback(self, input_text, future):
-        """Callback to run after user repl code is finished."""
-        # If there was an exception it will be saved in self._last_result
-        result_text = self._last_result
-        result_object = None
-        exception_text = self._last_exception
-
-        # _last_results consumed, erase for the next run.
-        self.clear_last_result()
-
-        stdout_contents = None
-        stderr_contents = None
-        if future.result():
-            future_result = future.result()
-            stdout_contents = future_result['stdout']
-            stderr_contents = future_result['stderr']
-            result_object = future_result['result']
-
-            if result_object is not None:
-                # Use ptpython formatted results:
-                formatted_result = self._format_result_output(result_object)
-                result_text = pw_console.text_formatting.remove_formatting(
-                    formatted_result)
-
-        # Job is finished, append the last result.
-        self.repl_pane.append_result_to_executed_code(
-            input_text,
-            future,
-            result_text,
-            stdout_contents,
-            stderr_contents,
-            exception_text=exception_text,
-            result_object=result_object,
-        )
-
-        # Rebuild output buffer.
-        self.repl_pane.update_output_buffer(
-            'pw_ptpython_repl.user_code_complete_callback')
-
-        # Trigger a prompt_toolkit application redraw.
-        self.repl_pane.application.application.invalidate()
-
-    async def _run_user_code(self, text, stdout_proxy, stdin_proxy):
-        """Run user code and capture stdout+err.
-
-        This fuction should be run in a separate thread from the main
-        prompt_toolkit application."""
-        # NOTE: This function runs in a separate thread using the asyncio event
-        # loop defined by self.repl_pane.application.user_code_loop. Patching
-        # stdout here will not effect the stdout used by prompt_toolkit and the
-        # main user interface.
-
-        # Patch stdout and stderr to capture repl print() statements.
-        original_stdout = sys.stdout
-        original_stderr = sys.stderr
-
-        sys.stdout = stdout_proxy
-        sys.stderr = stdin_proxy
-
-        # Run user repl code
-        try:
-            result = await self.run_and_show_expression_async(text)
-        finally:
-            # Always restore original stdout and stderr
-            sys.stdout = original_stdout
-            sys.stderr = original_stderr
-
-        # Save the captured output
-        stdout_contents = stdout_proxy.getvalue()
-        stderr_contents = stdin_proxy.getvalue()
-
-        return {
-            'stdout': stdout_contents,
-            'stderr': stderr_contents,
-            'result': result
-        }
-
-    def _accept_handler(self, buff: Buffer) -> bool:
-        """Function executed when pressing enter in the ptpython.repl.PythonRepl
-        input buffer."""
-        # Do nothing if no text is entered.
-        if len(buff.text) == 0:
-            return False
-        if self.repl_pane is None:
-            return False
-
-        repl_input_text = buff.text
-        # Exit if quit or exit
-        if repl_input_text.strip() in ['quit', 'quit()', 'exit', 'exit()']:
-            self.repl_pane.application.application.exit()  # type: ignore
-
-        # Create stdout and stderr proxies
-        temp_stdout = io.StringIO()
-        temp_stderr = io.StringIO()
-
-        # The help() command with no args uses it's own interactive prompt which
-        # will not work if prompt_toolkit is running.
-        if repl_input_text.strip() in ['help()']:
-            # Run nothing
-            repl_input_text = ''
-            # Override stdout
-            temp_stdout.write(
-                'Error: Interactive help() is not compatible with this repl.')
-
-        # Execute the repl code in the the separate user_code thread loop.
-        future = asyncio.run_coroutine_threadsafe(
-            # This function will be executed in a separate thread.
-            self._run_user_code(repl_input_text, temp_stdout, temp_stderr),
-            # Using this asyncio event loop.
-            self.repl_pane.application.user_code_loop)  # type: ignore
-
-        # Save the input text and future object.
-        self.repl_pane.append_executed_code(repl_input_text, future,
-                                            temp_stdout,
-                                            temp_stderr)  # type: ignore
-
-        # Run user_code_complete_callback() when done.
-        done_callback = functools.partial(self.user_code_complete_callback,
-                                          repl_input_text)
-        future.add_done_callback(done_callback)
-
-        # Rebuild the parent ReplPane output buffer.
-        self.repl_pane.update_output_buffer('pw_ptpython_repl._accept_handler')
-
-        # TODO(tonymd): Return True if exception is found?
-        # Don't keep input for now. Return True to keep input text.
-        return False
-
-    def line_break_count(self) -> int:
-        return self.default_buffer.text.count('\n')
-
-    def input_empty_if_in_focus_condition(self) -> Condition:
-        @Condition
-        def test() -> bool:
-            if has_focus(self)() and len(self.default_buffer.text) == 0:
-                return True
-            return not has_focus(self)()
-
-        return test
diff --git a/pw_console/py/pw_console/py.typed b/pw_console/py/pw_console/py.typed
deleted file mode 100644
index e69de29..0000000
--- a/pw_console/py/pw_console/py.typed
+++ /dev/null
diff --git a/pw_console/py/pw_console/pyserial_wrapper.py b/pw_console/py/pw_console/pyserial_wrapper.py
deleted file mode 100644
index ac00e05..0000000
--- a/pw_console/py/pw_console/pyserial_wrapper.py
+++ /dev/null
@@ -1,115 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Wrapers for pyserial classes to log read and write data."""
-
-from contextvars import ContextVar
-import logging
-import textwrap
-
-import serial  # type: ignore
-
-from pw_console.widgets.event_count_history import EventCountHistory
-
-_LOG = logging.getLogger('pw_console.serial_debug_logger')
-
-
-def _log_hex_strings(data, prefix=''):
-    """Create alinged hex number and character view log messages."""
-    # Make a list of 2 character hex number strings.
-    hex_numbers = textwrap.wrap(data.hex(), 2)
-
-    hex_chars = [
-        ('<' + str(b.to_bytes(1, byteorder='big')) + '>')
-        .replace("<b'\\x", '', 1)  # Remove b'\x from the beginning
-        .replace("<b'", '', 1)  # Remove b' from the beginning
-        .replace("'>", '', 1)  # Remove ' from the end
-        .rjust(2)
-        for b in data
-    ] # yapf: disable
-
-    # Replace non-printable bytes with dots.
-    for i, num in enumerate(hex_numbers):
-        if num == hex_chars[i]:
-            hex_chars[i] = '..'
-
-    hex_numbers_msg = ' '.join(hex_numbers)
-    hex_chars_msg = ' '.join(hex_chars)
-
-    _LOG.debug('%s%s',
-               prefix,
-               hex_numbers_msg,
-               extra=dict(extra_metadata_fields={
-                   'msg': hex_numbers_msg,
-               }))
-    _LOG.debug('%s%s',
-               prefix,
-               hex_chars_msg,
-               extra=dict(extra_metadata_fields={
-                   'msg': hex_chars_msg,
-               }))
-
-
-BANDWIDTH_HISTORY_CONTEXTVAR = (ContextVar('pw_console_bandwidth_history',
-                                           default={
-                                               'total':
-                                               EventCountHistory(interval=3),
-                                               'read':
-                                               EventCountHistory(interval=3),
-                                               'write':
-                                               EventCountHistory(interval=3),
-                                           }))
-
-
-class SerialWithLogging(serial.Serial):  # pylint: disable=too-many-ancestors
-    """pyserial with read and write wrappers for logging."""
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        self.pw_bps_history = BANDWIDTH_HISTORY_CONTEXTVAR.get()
-
-    def read(self, *args, **kwargs):
-        data = super().read(*args, **kwargs)
-        self.pw_bps_history['read'].log(len(data))
-        self.pw_bps_history['total'].log(len(data))
-
-        if len(data) > 0:
-            prefix = 'Read %2d B: ' % len(data)
-            _LOG.debug('%s%s',
-                       prefix,
-                       data,
-                       extra=dict(extra_metadata_fields={
-                           'mode': 'Read',
-                           'bytes': len(data),
-                           'msg': str(data),
-                       }))
-            _log_hex_strings(data, prefix=prefix)
-
-        return data
-
-    def write(self, data, *args, **kwargs):
-        self.pw_bps_history['write'].log(len(data))
-        self.pw_bps_history['total'].log(len(data))
-
-        if len(data) > 0:
-            prefix = 'Write %2d B: ' % len(data)
-            _LOG.debug('%s%s',
-                       prefix,
-                       data,
-                       extra=dict(extra_metadata_fields={
-                           'mode': 'Write',
-                           'bytes': len(data),
-                           'msg': str(data)
-                       }))
-            _log_hex_strings(data, prefix=prefix)
-
-        super().write(data, *args, **kwargs)
diff --git a/pw_console/py/pw_console/python_logging.py b/pw_console/py/pw_console/python_logging.py
deleted file mode 100644
index 7f5d9be..0000000
--- a/pw_console/py/pw_console/python_logging.py
+++ /dev/null
@@ -1,110 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Python logging helper fuctions."""
-
-import copy
-import logging
-import tempfile
-from datetime import datetime
-from typing import Iterable, Iterator, Optional
-
-
-def all_loggers() -> Iterator[logging.Logger]:
-    """Iterates over all loggers known to Python logging."""
-    manager = logging.getLogger().manager  # type: ignore[attr-defined]
-
-    for logger_name in manager.loggerDict:  # pylint: disable=no-member
-        yield logging.getLogger(logger_name)
-
-
-def create_temp_log_file(prefix: Optional[str] = None,
-                         add_time: bool = True) -> str:
-    """Create a unique tempfile for saving logs.
-
-    Example format: /tmp/pw_console_2021-05-04_151807_8hem6iyq
-    """
-    if not prefix:
-        prefix = str(__package__)
-
-    # Grab the current system timestamp as a string.
-    isotime = datetime.now().isoformat(sep='_', timespec='seconds')
-    # Timestamp string should not have colons in it.
-    isotime = isotime.replace(':', '')
-
-    if add_time:
-        prefix += f'_{isotime}'
-
-    log_file_name = None
-    with tempfile.NamedTemporaryFile(prefix=f'{prefix}_',
-                                     delete=False) as log_file:
-        log_file_name = log_file.name
-
-    return log_file_name
-
-
-def set_logging_last_resort_file_handler(
-        file_name: Optional[str] = None) -> None:
-    log_file = file_name if file_name else create_temp_log_file()
-    logging.lastResort = logging.FileHandler(log_file)
-
-
-def disable_stdout_handlers(logger: logging.Logger) -> None:
-    """Remove all stdout and stdout & stderr logger handlers."""
-    for handler in copy.copy(logger.handlers):
-        # Must use type() check here since this returns True:
-        #   isinstance(logging.FileHandler, logging.StreamHandler)
-        if type(handler) == logging.StreamHandler:  # pylint: disable=unidiomatic-typecheck
-            logger.removeHandler(handler)
-
-
-def setup_python_logging(
-    last_resort_filename: Optional[str] = None,
-    loggers_with_no_propagation: Optional[Iterable[logging.Logger]] = None
-) -> None:
-    """Disable log handlers for full screen prompt_toolkit applications."""
-    if not loggers_with_no_propagation:
-        loggers_with_no_propagation = []
-    disable_stdout_handlers(logging.getLogger())
-
-    if logging.lastResort is not None:
-        set_logging_last_resort_file_handler(last_resort_filename)
-
-    for logger in list(all_loggers()):
-        # Prevent stdout handlers from corrupting the prompt_toolkit UI.
-        disable_stdout_handlers(logger)
-        if logger in loggers_with_no_propagation:
-            continue
-        # Make sure all known loggers propagate to the root logger.
-        logger.propagate = True
-
-    # Prevent these loggers from propagating to the root logger.
-    hidden_host_loggers = [
-        'pw_console',
-        'pw_console.plugins',
-
-        # prompt_toolkit triggered debug log messages
-        'prompt_toolkit',
-        'prompt_toolkit.buffer',
-        'parso.python.diff',
-        'parso.cache',
-        'pw_console.serial_debug_logger',
-    ]
-    for logger_name in hidden_host_loggers:
-        logging.getLogger(logger_name).propagate = False
-
-    # Set asyncio log level to WARNING
-    logging.getLogger('asyncio').setLevel(logging.WARNING)
-
-    # Always set DEBUG level for serial debug.
-    logging.getLogger('pw_console.serial_debug_logger').setLevel(logging.DEBUG)
diff --git a/pw_console/py/pw_console/quit_dialog.py b/pw_console/py/pw_console/quit_dialog.py
deleted file mode 100644
index b466580..0000000
--- a/pw_console/py/pw_console/quit_dialog.py
+++ /dev/null
@@ -1,177 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""LogPane Info Toolbar classes."""
-
-from __future__ import annotations
-import functools
-import logging
-import sys
-from typing import Optional, Callable, TYPE_CHECKING
-
-from prompt_toolkit.data_structures import Point
-from prompt_toolkit.key_binding import KeyBindings, KeyPressEvent
-from prompt_toolkit.filters import Condition
-from prompt_toolkit.layout import (
-    ConditionalContainer,
-    FormattedTextControl,
-    HSplit,
-    Window,
-    WindowAlign,
-)
-
-import pw_console.widgets.border
-import pw_console.widgets.checkbox
-import pw_console.widgets.mouse_handlers
-
-if TYPE_CHECKING:
-    from pw_console.console_app import ConsoleApp
-
-_LOG = logging.getLogger(__package__)
-
-
-class QuitDialog(ConditionalContainer):
-    """Confirmation quit dialog box."""
-
-    DIALOG_HEIGHT = 2
-
-    def __init__(self,
-                 application: ConsoleApp,
-                 on_quit: Optional[Callable] = None):
-        self.application = application
-        self.show_dialog = False
-        # Tracks the last focused container, to enable restoring focus after
-        # closing the dialog.
-        self.last_focused_pane = None
-
-        self.on_quit_function = (on_quit if on_quit else
-                                 self._default_on_quit_function)
-
-        # Quit keybindings are active when this dialog is in focus
-        key_bindings = KeyBindings()
-        register = self.application.prefs.register_keybinding
-
-        @register('quit-dialog.yes', key_bindings)
-        def _quit(_event: KeyPressEvent) -> None:
-            """Close save as bar."""
-            self.quit_action()
-
-        @register('quit-dialog.no', key_bindings)
-        def _cancel(_event: KeyPressEvent) -> None:
-            """Close save as bar."""
-            self.close_dialog()
-
-        self.exit_message = 'Quit? y/n '
-
-        action_bar_control = FormattedTextControl(
-            self.get_action_fragments,
-            show_cursor=True,
-            focusable=True,
-            key_bindings=key_bindings,
-            # Cursor will appear after the exit_message
-            get_cursor_position=lambda: Point(len(self.exit_message), 0),
-        )
-
-        action_bar_window = Window(content=action_bar_control,
-                                   height=QuitDialog.DIALOG_HEIGHT,
-                                   align=WindowAlign.LEFT,
-                                   dont_extend_width=False)
-
-        super().__init__(
-            pw_console.widgets.border.create_border(
-                HSplit(
-                    [action_bar_window],
-                    height=QuitDialog.DIALOG_HEIGHT,
-                    style='class:quit-dialog',
-                ),
-                QuitDialog.DIALOG_HEIGHT,
-                border_style='class:quit-dialog-border',
-                left_margin_columns=1,
-            ),
-            filter=Condition(lambda: self.show_dialog),
-        )
-
-    def focus_self(self):
-        self.application.layout.focus(self)
-
-    def close_dialog(self):
-        """Close this dialog box."""
-        self.show_dialog = False
-        # Restore original focus if possible.
-        if self.last_focused_pane:
-            self.application.layout.focus(self.last_focused_pane)
-        else:
-            # Fallback to focusing on the main menu.
-            self.application.focus_main_menu()
-
-    def open_dialog(self):
-        self.show_dialog = True
-        self.last_focused_pane = self.application.focused_window()
-        self.focus_self()
-        self.application.redraw_ui()
-
-    def _default_on_quit_function(self):
-        if hasattr(self.application, 'application'):
-            self.application.application.exit()
-        else:
-            sys.exit()
-
-    def quit_action(self):
-        self.on_quit_function()
-
-    def get_action_fragments(self):
-        """Return FormattedText with action buttons."""
-
-        # Mouse handlers
-        focus = functools.partial(pw_console.widgets.mouse_handlers.on_click,
-                                  self.focus_self)
-        cancel = functools.partial(pw_console.widgets.mouse_handlers.on_click,
-                                   self.close_dialog)
-        quit_action = functools.partial(
-            pw_console.widgets.mouse_handlers.on_click, self.quit_action)
-
-        # Separator should have the focus mouse handler so clicking on any
-        # whitespace focuses the input field.
-        separator_text = ('', '  ', focus)
-
-        # Default button style
-        button_style = 'class:toolbar-button-inactive'
-
-        fragments = [('', self.exit_message), separator_text]
-        fragments.append(('', '\n'))
-
-        # Cancel button
-        fragments.extend(
-            pw_console.widgets.checkbox.to_keybind_indicator(
-                key='n / Ctrl-c',
-                description='Cancel',
-                mouse_handler=cancel,
-                base_style=button_style,
-            ))
-
-        # Two space separator
-        fragments.append(separator_text)
-
-        # Save button
-        fragments.extend(
-            pw_console.widgets.checkbox.to_keybind_indicator(
-                key='y / Ctrl-d',
-                description='Quit',
-                mouse_handler=quit_action,
-                base_style=button_style,
-            ))
-
-        # One space separator
-        fragments.append(('', ' ', focus))
-
-        return fragments
diff --git a/pw_console/py/pw_console/repl_pane.py b/pw_console/py/pw_console/repl_pane.py
deleted file mode 100644
index 10d52a5..0000000
--- a/pw_console/py/pw_console/repl_pane.py
+++ /dev/null
@@ -1,471 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""ReplPane class."""
-
-import asyncio
-import concurrent
-import functools
-import logging
-import pprint
-from dataclasses import dataclass
-from typing import (
-    Any,
-    Callable,
-    Dict,
-    List,
-    Optional,
-    TYPE_CHECKING,
-)
-
-from prompt_toolkit.filters import (
-    Condition,
-    has_focus,
-)
-from prompt_toolkit.document import Document
-from prompt_toolkit.key_binding import KeyBindings, KeyPressEvent
-from prompt_toolkit.layout.dimension import AnyDimension
-from prompt_toolkit.widgets import TextArea
-from prompt_toolkit.layout import (
-    ConditionalContainer,
-    DynamicContainer,
-    Dimension,
-    FloatContainer,
-    HSplit,
-    Window,
-)
-from prompt_toolkit.lexers import PygmentsLexer  # type: ignore
-from pygments.lexers.python import PythonConsoleLexer  # type: ignore
-# Alternative Formatting
-# from IPython.lib.lexers import IPythonConsoleLexer  # type: ignore
-
-from pw_console.progress_bar.progress_bar_state import TASKS_CONTEXTVAR
-from pw_console.pw_ptpython_repl import PwPtPythonRepl
-from pw_console.widgets import (
-    ToolbarButton,
-    WindowPane,
-    WindowPaneHSplit,
-    WindowPaneToolbar,
-)
-import pw_console.mouse
-import pw_console.style
-
-if TYPE_CHECKING:
-    from pw_console.console_app import ConsoleApp
-
-_LOG = logging.getLogger(__package__)
-
-_Namespace = Dict[str, Any]
-_GetNamespace = Callable[[], _Namespace]
-
-_REPL_OUTPUT_SCROLL_AMOUNT = 5
-
-
-@dataclass
-class UserCodeExecution:
-    """Class to hold a single user repl execution event."""
-    input: str
-    future: concurrent.futures.Future
-    output: str
-    stdout: str
-    stderr: str
-    stdout_check_task: Optional[concurrent.futures.Future] = None
-    result_object: Optional[Any] = None
-    exception_text: Optional[str] = None
-
-    @property
-    def is_running(self):
-        return not self.future.done()
-
-    def update_stdout(self, text: Optional[str]):
-        if text:
-            self.stdout = text
-
-    def update_stderr(self, text: Optional[str]):
-        if text:
-            self.stderr = text
-
-
-class ReplPane(WindowPane):
-    """Pane for reading Python input."""
-
-    # pylint: disable=too-many-instance-attributes,too-many-public-methods
-    def __init__(
-        self,
-        application: 'ConsoleApp',
-        python_repl: PwPtPythonRepl,
-        pane_title: str = 'Python Repl',
-        startup_message: Optional[str] = None,
-    ) -> None:
-        super().__init__(application, pane_title)
-
-        self.executed_code: List = []
-        self.application = application
-
-        self.pw_ptpython_repl = python_repl
-        self.pw_ptpython_repl.set_repl_pane(self)
-
-        self.wrap_output_lines = True
-
-        self.startup_message = startup_message if startup_message else ''
-
-        self.output_field = TextArea(
-            text=self.startup_message,
-            focusable=True,
-            focus_on_click=True,
-            scrollbar=True,
-            wrap_lines=Condition(lambda: self.wrap_output_lines),
-            lexer=PygmentsLexer(PythonConsoleLexer),
-        )
-
-        # Additional keybindings for the text area.
-        key_bindings = KeyBindings()
-        register = self.application.prefs.register_keybinding
-
-        @register('python-repl.copy-output-selection', key_bindings)
-        def _copy_selection(_event: KeyPressEvent) -> None:
-            """Copy selected text."""
-            self.copy_output_selection()
-
-        self.output_field.control.key_bindings = key_bindings
-
-        # Override output buffer mouse wheel scroll
-        self.output_field.window._scroll_up = (  # type: ignore
-            self.scroll_output_up)
-        self.output_field.window._scroll_down = (  # type: ignore
-            self.scroll_output_down)
-
-        self.bottom_toolbar = self._create_input_toolbar()
-        self.results_toolbar = self._create_output_toolbar()
-
-        self.progress_state = TASKS_CONTEXTVAR.get()
-
-        # ReplPane root container
-        self.container = ConditionalContainer(
-            FloatContainer(
-                # Horizontal split of all Repl pane sections.
-                WindowPaneHSplit(
-                    self,
-                    [
-                        HSplit(
-                            [
-                                # 1. Repl Output
-                                self.output_field,
-                                # 2. Progress bars if any
-                                ConditionalContainer(
-                                    DynamicContainer(
-                                        self.get_progress_bar_task_container),
-                                    filter=Condition(
-                                        lambda: not self.progress_state.
-                                        all_tasks_complete)),
-                                # 3. Static separator toolbar.
-                                self.results_toolbar,
-                            ],
-                            # Output area only dimensions
-                            height=self.get_output_height,
-                        ),
-                        HSplit(
-                            [
-                                # 3. Repl Input
-                                self.pw_ptpython_repl,
-                                # 4. Bottom toolbar
-                                self.bottom_toolbar,
-                            ],
-                            # Input area only dimensions
-                            height=self.get_input_height,
-                        ),
-                    ],
-                    # Repl pane dimensions
-                    height=lambda: self.height,
-                    width=lambda: self.width,
-                    style=functools.partial(pw_console.style.get_pane_style,
-                                            self),
-                ),
-                floats=[]),
-            filter=Condition(lambda: self.show_pane))
-
-    def toggle_wrap_output_lines(self):
-        """Enable or disable output line wraping/truncation."""
-        self.wrap_output_lines = not self.wrap_output_lines
-
-    def scroll_output_down(self) -> None:
-        """Scroll the output buffer down on mouse wheel down events."""
-        for _i in range(_REPL_OUTPUT_SCROLL_AMOUNT):
-            # There is no move cursor more than one line at a time function.
-            self.output_field.control.move_cursor_down()
-        self.output_field.window.vertical_scroll += _REPL_OUTPUT_SCROLL_AMOUNT
-
-    def scroll_output_up(self) -> None:
-        """Scroll the output buffer up on mouse wheel up events."""
-        for _i in range(_REPL_OUTPUT_SCROLL_AMOUNT):
-            # There is no move cursor more than one line at a time function.
-            self.output_field.control.move_cursor_up()
-        self.output_field.window.vertical_scroll -= _REPL_OUTPUT_SCROLL_AMOUNT
-
-    def focus_output(self):
-        self.application.focus_on_container(self.output_field)
-
-    def focus_input(self):
-        self.application.focus_on_container(self.pw_ptpython_repl)
-
-    def get_progress_bar_task_container(self):
-        bar_container = self.progress_state.get_container()
-        if bar_container:
-            return bar_container
-        return Window()
-
-    def get_output_height(self) -> AnyDimension:
-        # pylint: disable=no-self-use
-        return Dimension(min=1)
-
-    def get_input_height(self) -> AnyDimension:
-        desired_max_height = 10
-        # Check number of line breaks in the input buffer.
-        input_line_count = self.pw_ptpython_repl.line_break_count()
-        if input_line_count > desired_max_height:
-            desired_max_height = input_line_count
-        # Check if it's taller than the available space
-        if desired_max_height > self.current_pane_height:
-            # Leave space for minimum of
-            #   1 line of content in the output
-            #   + 1 for output toolbar
-            #   + 1 for input toolbar
-            desired_max_height = self.current_pane_height - 3
-
-        if desired_max_height > 1:
-            return Dimension(min=1, max=desired_max_height)
-        # Fall back to at least a height of 1
-        return Dimension(min=1)
-
-    def _create_input_toolbar(self):
-        bottom_toolbar = WindowPaneToolbar(
-            self,
-            focus_action_callable=self.focus_input,
-            focus_check_container=self.pw_ptpython_repl,
-        )
-        bottom_toolbar.add_button(
-            ToolbarButton('Ctrl-v', 'Paste',
-                          self.paste_system_clipboard_to_input_buffer))
-        bottom_toolbar.add_button(
-            ToolbarButton('Ctrl-c', 'Copy / Clear',
-                          self.copy_or_clear_input_buffer))
-        bottom_toolbar.add_button(ToolbarButton('Enter', 'Run', self.run_code))
-        bottom_toolbar.add_button(ToolbarButton('F2', 'Settings'))
-        bottom_toolbar.add_button(ToolbarButton('F3', 'History'))
-        return bottom_toolbar
-
-    def _create_output_toolbar(self):
-        results_toolbar = WindowPaneToolbar(
-            self,
-            title='Python Results',
-            focus_action_callable=self.focus_output,
-            focus_check_container=self.output_field,
-            include_resize_handle=False,
-        )
-        results_toolbar.add_button(
-            ToolbarButton(description='Wrap lines',
-                          mouse_handler=self.toggle_wrap_output_lines,
-                          is_checkbox=True,
-                          checked=lambda: self.wrap_output_lines))
-        results_toolbar.add_button(
-            ToolbarButton('Ctrl-Alt-c', 'Copy All Output',
-                          self.copy_all_output_text))
-        results_toolbar.add_button(
-            ToolbarButton('Ctrl-c', 'Copy Selected Text',
-                          self.copy_output_selection))
-
-        results_toolbar.add_button(
-            ToolbarButton('Shift+Arrows / Mouse Drag', 'Select Text'))
-
-        return results_toolbar
-
-    def copy_output_selection(self):
-        """Copy highlighted output text to the system clipboard."""
-        clipboard_data = self.output_field.buffer.copy_selection()
-        self.application.application.clipboard.set_data(clipboard_data)
-
-    def copy_input_selection(self):
-        """Copy highlighted input text to the system clipboard."""
-        clipboard_data = self.pw_ptpython_repl.default_buffer.copy_selection()
-        self.application.application.clipboard.set_data(clipboard_data)
-
-    def copy_all_output_text(self):
-        """Copy all text in the Python output to the system clipboard."""
-        self.application.application.clipboard.set_text(
-            self.output_field.buffer.text)
-
-    def copy_all_input_text(self):
-        """Copy all text in the Python input to the system clipboard."""
-        self.application.application.clipboard.set_text(
-            self.pw_ptpython_repl.default_buffer.text)
-
-    # pylint: disable=no-self-use
-    def get_all_key_bindings(self) -> List:
-        """Return all keybinds for this plugin."""
-        # ptpython native bindings:
-        # return [load_python_bindings(self.pw_ptpython_repl)]
-
-        # Hand-crafted bindings for display in the HelpWindow:
-        return [{
-            'Execute code': ['Enter', 'Option-Enter', 'Alt-Enter'],
-            'Reverse search history': ['Ctrl-r'],
-            'Erase input buffer.': ['Ctrl-c'],
-            'Show settings.': ['F2'],
-            'Show history.': ['F3'],
-        }]
-
-    def get_all_menu_options(self):
-        return []
-
-    def run_code(self):
-        """Trigger a repl code execution on mouse click."""
-        self.pw_ptpython_repl.default_buffer.validate_and_handle()
-
-    def ctrl_c(self):
-        """Ctrl-C keybinding behavior."""
-        # If there is text in the input buffer
-        if self.pw_ptpython_repl.default_buffer.text:
-            self.copy_or_clear_input_buffer()
-        else:
-            self.interrupt_last_code_execution()
-
-    def paste_system_clipboard_to_input_buffer(self, erase_buffer=False):
-        if erase_buffer:
-            self.clear_input_buffer()
-
-        clip_data = self.application.application.clipboard.get_data()
-        self.pw_ptpython_repl.default_buffer.paste_clipboard_data(clip_data)
-
-    def clear_input_buffer(self):
-        # Erase input buffer.
-        self.pw_ptpython_repl.default_buffer.reset()
-        # Clear any displayed function signatures.
-        self.pw_ptpython_repl.on_reset()
-
-    def copy_or_clear_input_buffer(self):
-        # Copy selected text if a selection is active.
-        if self.pw_ptpython_repl.default_buffer.selection_state:
-            self.copy_input_selection()
-            return
-        # Otherwise, clear the input buffer
-        self.clear_input_buffer()
-
-    def interrupt_last_code_execution(self):
-        code = self._get_currently_running_code()
-        if code:
-            code.future.cancel()
-            code.output = 'Canceled'
-            self.progress_state.cancel_all_tasks()
-        self.pw_ptpython_repl.clear_last_result()
-        self.update_output_buffer('repl_pane.interrupt_last_code_execution')
-
-    def _get_currently_running_code(self):
-        for code in self.executed_code:
-            if not code.future.done():
-                return code
-        return None
-
-    def _get_executed_code(self, future):
-        for code in self.executed_code:
-            if code.future == future:
-                return code
-        return None
-
-    def _log_executed_code(self, code, prefix=''):
-        """Log repl command input text along with a prefix string."""
-        text = self.get_output_buffer_text([code], show_index=False)
-        text = text.strip()
-        for line in text.splitlines():
-            _LOG.debug('[PYTHON %s]  %s', prefix, line.strip())
-
-    async def periodically_check_stdout(self, user_code: UserCodeExecution,
-                                        stdout_proxy, stderr_proxy):
-        while not user_code.future.done():
-            await asyncio.sleep(0.3)
-            stdout_text_so_far = stdout_proxy.getvalue()
-            stderr_text_so_far = stderr_proxy.getvalue()
-            if stdout_text_so_far:
-                user_code.update_stdout(stdout_text_so_far)
-            if stderr_text_so_far:
-                user_code.update_stderr(stderr_text_so_far)
-
-            # if stdout_text_so_far or stderr_text_so_far:
-            self.update_output_buffer('repl_pane.periodic_check')
-
-    def append_executed_code(self, text, future, temp_stdout, temp_stderr):
-        user_code = UserCodeExecution(input=text,
-                                      future=future,
-                                      output=None,
-                                      stdout=None,
-                                      stderr=None)
-
-        background_stdout_check = asyncio.create_task(
-            self.periodically_check_stdout(user_code, temp_stdout,
-                                           temp_stderr))
-        user_code.stdout_check_task = background_stdout_check
-        self.executed_code.append(user_code)
-        self._log_executed_code(user_code, prefix='START')
-
-    def append_result_to_executed_code(
-        self,
-        _input_text,
-        future,
-        result_text,
-        stdout_text='',
-        stderr_text='',
-        exception_text='',
-        result_object=None,
-    ):
-
-        code = self._get_executed_code(future)
-        if code:
-            code.output = result_text
-            code.stdout = stdout_text
-            code.stderr = stderr_text
-            code.exception_text = exception_text
-            code.result_object = result_object
-        self._log_executed_code(code, prefix='FINISH')
-        self.update_output_buffer('repl_pane.append_result_to_executed_code')
-
-    def get_output_buffer_text(self, code_items=None, show_index=True):
-        content_width = (self.current_pane_width
-                         if self.current_pane_width else 80)
-        pprint_respecting_width = pprint.PrettyPrinter(
-            indent=2, width=content_width).pformat
-
-        executed_code = code_items or self.executed_code
-
-        template = self.application.get_template('repl_output.jinja')
-        return template.render(code_items=executed_code,
-                               result_format=pprint_respecting_width,
-                               show_index=show_index)
-
-    def update_output_buffer(self, *unused_args):
-        text = self.get_output_buffer_text()
-        # Add an extra line break so the last cursor position is in column 0
-        # instead of the end of the last line.
-        text += '\n'
-        self.output_field.buffer.set_document(
-            Document(text=text, cursor_position=len(text)))
-
-        self.application.redraw_ui()
-
-    def input_or_output_has_focus(self) -> Condition:
-        @Condition
-        def test() -> bool:
-            if has_focus(self.output_field)() or has_focus(
-                    self.pw_ptpython_repl)():
-                return True
-            return False
-
-        return test
diff --git a/pw_console/py/pw_console/search_toolbar.py b/pw_console/py/pw_console/search_toolbar.py
deleted file mode 100644
index 72ad917..0000000
--- a/pw_console/py/pw_console/search_toolbar.py
+++ /dev/null
@@ -1,453 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""SearchToolbar class used by LogPanes."""
-
-from __future__ import annotations
-import functools
-from typing import TYPE_CHECKING
-
-from prompt_toolkit.buffer import Buffer
-from prompt_toolkit.filters import Condition, has_focus
-from prompt_toolkit.formatted_text import StyleAndTextTuples
-from prompt_toolkit.key_binding import (
-    KeyBindings,
-    KeyBindingsBase,
-    KeyPressEvent,
-)
-from prompt_toolkit.layout import (
-    ConditionalContainer,
-    FormattedTextControl,
-    HSplit,
-    VSplit,
-    Window,
-    WindowAlign,
-)
-from prompt_toolkit.widgets import TextArea
-from prompt_toolkit.validation import DynamicValidator
-
-from pw_console.log_view import RegexValidator, SearchMatcher
-# import pw_console.widgets.checkbox
-import pw_console.widgets.mouse_handlers
-
-if TYPE_CHECKING:
-    from pw_console.log_pane import LogPane
-
-
-class SearchToolbar(ConditionalContainer):
-    """Toolbar for entering search text and viewing match counts."""
-
-    TOOLBAR_HEIGHT = 2
-
-    def __init__(self, log_pane: 'LogPane'):
-        self.log_pane = log_pane
-        self.log_view = log_pane.log_view
-        self.search_validator = RegexValidator()
-        self._search_successful = False
-        self._search_invert = False
-        self._search_field = None
-
-        self.input_field = TextArea(
-            prompt=[
-                ('class:search-bar-setting', '/',
-                 functools.partial(pw_console.widgets.mouse_handlers.on_click,
-                                   self.focus_self))
-            ],
-            focusable=True,
-            focus_on_click=True,
-            scrollbar=False,
-            multiline=False,
-            height=1,
-            dont_extend_height=True,
-            dont_extend_width=False,
-            accept_handler=self._search_accept_handler,
-            validator=DynamicValidator(self.get_search_matcher),
-            history=self.log_pane.application.search_history,
-        )
-
-        self.input_field.control.key_bindings = self._create_key_bindings()
-
-        match_count_window = Window(
-            content=FormattedTextControl(self.get_match_count_fragments),
-            height=1,
-            align=WindowAlign.LEFT,
-            dont_extend_width=True,
-            style='class:search-match-count-dialog',
-        )
-
-        match_buttons_window = Window(
-            content=FormattedTextControl(self.get_button_fragments),
-            height=1,
-            align=WindowAlign.LEFT,
-            dont_extend_width=False,
-            style='class:search-match-count-dialog',
-        )
-
-        input_field_buttons_window = Window(
-            content=FormattedTextControl(self.get_search_help_fragments),
-            height=1,
-            align=WindowAlign.RIGHT,
-            dont_extend_width=True,
-        )
-
-        settings_bar_window = Window(
-            content=FormattedTextControl(self.get_search_settings_fragments),
-            height=1,
-            align=WindowAlign.LEFT,
-            dont_extend_width=False,
-        )
-
-        super().__init__(
-            HSplit(
-                [
-                    # Top row
-                    VSplit([
-                        # Search Settings toggles, only show if the search input
-                        # field is in focus.
-                        ConditionalContainer(settings_bar_window,
-                                             filter=has_focus(
-                                                 self.input_field)),
-
-                        # Match count numbers and buttons, only show if the
-                        # search input is NOT in focus.
-                        ConditionalContainer(
-                            match_count_window,
-                            filter=~has_focus(self.input_field)),  # pylint: disable=invalid-unary-operand-type
-                        ConditionalContainer(
-                            match_buttons_window,
-                            filter=~has_focus(self.input_field)),  # pylint: disable=invalid-unary-operand-type
-                    ]),
-                    # Bottom row
-                    VSplit([
-                        self.input_field,
-                        ConditionalContainer(input_field_buttons_window,
-                                             filter=has_focus(self))
-                    ])
-                ],
-                height=SearchToolbar.TOOLBAR_HEIGHT,
-                style='class:search-bar',
-            ),
-            filter=Condition(lambda: log_pane.search_bar_active),
-        )
-
-    def _create_key_bindings(self) -> KeyBindingsBase:
-        """Create additional key bindings for the search input."""
-        # Clear filter keybind is handled by the parent log_pane.
-
-        key_bindings = KeyBindings()
-        register = self.log_pane.application.prefs.register_keybinding
-
-        @register('search-toolbar.cancel', key_bindings)
-        def _close_search_bar(_event: KeyPressEvent) -> None:
-            """Close search bar."""
-            self.cancel_search()
-
-        @register('search-toolbar.toggle-matcher', key_bindings)
-        def _select_next_search_matcher(_event: KeyPressEvent) -> None:
-            """Select the next search matcher."""
-            self.log_pane.log_view.select_next_search_matcher()
-
-        @register('search-toolbar.create-filter', key_bindings)
-        def _create_filter(_event: KeyPressEvent) -> None:
-            """Create a filter."""
-            self.create_filter()
-
-        @register('search-toolbar.toggle-invert', key_bindings)
-        def _toggle_search_invert(_event: KeyPressEvent) -> None:
-            """Toggle inverted search matching."""
-            self._invert_search()
-
-        @register('search-toolbar.toggle-column', key_bindings)
-        def _select_next_field(_event: KeyPressEvent) -> None:
-            """Select next search field/column."""
-            self._next_field()
-
-        return key_bindings
-
-    def focus_self(self) -> None:
-        self.log_pane.application.application.layout.focus(self)
-
-    def focus_log_pane(self) -> None:
-        self.log_pane.application.focus_on_container(self.log_pane)
-
-    def _create_filter(self) -> None:
-        self.input_field.buffer.reset()
-        self.close_search_bar()
-        self.log_view.apply_filter()
-
-    def _next_match(self) -> None:
-        self.log_view.search_forwards()
-
-    def _previous_match(self) -> None:
-        self.log_view.search_backwards()
-
-    def cancel_search(self) -> None:
-        self.input_field.buffer.reset()
-        self.close_search_bar()
-        self.log_view.clear_search()
-
-    def close_search_bar(self) -> None:
-        """Close search bar."""
-        # Reset invert setting for the next search
-        self._search_invert = False
-        self.log_view.follow_search_match = False
-        # Hide the search bar
-        self.log_pane.search_bar_active = False
-        # Focus on the log_pane.
-        self.log_pane.application.focus_on_container(self.log_pane)
-        self.log_pane.redraw_ui()
-
-    def _start_search(self) -> None:
-        self.input_field.buffer.validate_and_handle()
-
-    def _invert_search(self) -> None:
-        self._search_invert = not self._search_invert
-
-    def _toggle_search_follow(self) -> None:
-        self.log_view.follow_search_match = (
-            not self.log_view.follow_search_match)
-        # If automatically jumping to the next search match, disable normal
-        # follow mode.
-        if self.log_view.follow_search_match:
-            self.log_view.follow = False
-
-    def _next_field(self) -> None:
-        fields = self.log_pane.log_view.log_store.table.all_column_names()
-        fields.append(None)
-        current_index = fields.index(self._search_field)
-        next_index = (current_index + 1) % len(fields)
-        self._search_field = fields[next_index]
-
-    def create_filter(self) -> None:
-        self._start_search()
-        if self._search_successful:
-            self.log_pane.log_view.apply_filter()
-
-    def _search_accept_handler(self, buff: Buffer) -> bool:
-        """Function run when hitting Enter in the search bar."""
-        self._search_successful = False
-        if len(buff.text) == 0:
-            self.close_search_bar()
-            # Don't apply an empty search.
-            return False
-
-        if self.log_pane.log_view.new_search(buff.text,
-                                             invert=self._search_invert,
-                                             field=self._search_field):
-            self._search_successful = True
-
-            # Don't close the search bar, instead focus on the log content.
-            self.log_pane.application.focus_on_container(
-                self.log_pane.log_display_window)
-            # Keep existing search text.
-            return True
-
-        # Keep existing text if regex error
-        return True
-
-    def get_search_help_fragments(self):
-        """Return FormattedText with search general help keybinds."""
-        focus = functools.partial(pw_console.widgets.mouse_handlers.on_click,
-                                  self.focus_self)
-        start_search = functools.partial(
-            pw_console.widgets.mouse_handlers.on_click, self._start_search)
-        close_search = functools.partial(
-            pw_console.widgets.mouse_handlers.on_click, self.cancel_search)
-
-        # Search toolbar is darker than pane toolbars, use the darker button
-        # style here.
-        button_style = 'class:toolbar-button-inactive'
-
-        separator_text = [('', '  ', focus)]
-
-        # Empty text matching the width of the search bar title.
-        fragments = [
-            ('', '        ', focus),
-        ]
-        fragments.extend(separator_text)
-
-        fragments.extend(
-            pw_console.widgets.checkbox.to_keybind_indicator(
-                'Enter', 'Search', start_search, base_style=button_style))
-        fragments.extend(separator_text)
-
-        fragments.extend(
-            pw_console.widgets.checkbox.to_keybind_indicator(
-                'Ctrl-c', 'Cancel', close_search, base_style=button_style))
-
-        return fragments
-
-    def get_search_settings_fragments(self):
-        """Return FormattedText with current search settings and keybinds."""
-        focus = functools.partial(pw_console.widgets.mouse_handlers.on_click,
-                                  self.focus_self)
-        next_field = functools.partial(
-            pw_console.widgets.mouse_handlers.on_click, self._next_field)
-        toggle_invert = functools.partial(
-            pw_console.widgets.mouse_handlers.on_click, self._invert_search)
-        next_matcher = functools.partial(
-            pw_console.widgets.mouse_handlers.on_click,
-            self.log_pane.log_view.select_next_search_matcher)
-
-        separator_text = [('', '  ', focus)]
-
-        # Search toolbar is darker than pane toolbars, use the darker button
-        # style here.
-        button_style = 'class:toolbar-button-inactive'
-
-        fragments = [
-            # Title
-            ('class:search-bar-title', ' Search ', focus),
-        ]
-        fragments.extend(separator_text)
-
-        selected_column_text = [
-            (button_style + ' class:search-bar-setting',
-             (self._search_field.title() if self._search_field else 'All'),
-             next_field),
-        ]
-        fragments.extend(
-            pw_console.widgets.checkbox.to_keybind_indicator(
-                'Ctrl-t',
-                'Column:',
-                next_field,
-                middle_fragments=selected_column_text,
-                base_style=button_style,
-            ))
-        fragments.extend(separator_text)
-
-        fragments.extend(
-            pw_console.widgets.checkbox.to_checkbox_with_keybind_indicator(
-                self._search_invert,
-                'Ctrl-v',
-                'Invert',
-                toggle_invert,
-                base_style=button_style))
-        fragments.extend(separator_text)
-
-        # Matching Method
-        current_matcher_text = [
-            (button_style + ' class:search-bar-setting',
-             str(self.log_pane.log_view.search_matcher.name), next_matcher)
-        ]
-        fragments.extend(
-            pw_console.widgets.checkbox.to_keybind_indicator(
-                'Ctrl-n',
-                'Matcher:',
-                next_matcher,
-                middle_fragments=current_matcher_text,
-                base_style=button_style,
-            ))
-        fragments.extend(separator_text)
-
-        return fragments
-
-    def get_search_matcher(self):
-        if self.log_pane.log_view.search_matcher == SearchMatcher.REGEX:
-            return self.log_pane.log_view.search_validator
-        return False
-
-    def get_match_count_fragments(self):
-        """Return formatted text for the match count indicator."""
-        focus = functools.partial(pw_console.widgets.mouse_handlers.on_click,
-                                  self.focus_log_pane)
-        two_spaces = ('', '  ', focus)
-
-        # Check if this line is a search match
-        match_number = self.log_view.search_matched_lines.get(
-            self.log_view.log_index, -1)
-
-        # If valid, increment the zero indexed value by one for better human
-        # readability.
-        if match_number >= 0:
-            match_number += 1
-        # If no match, mark as zero
-        else:
-            match_number = 0
-
-        return [
-            ('class:search-match-count-dialog-title', ' Match ', focus),
-            ('', '{} / {}'.format(match_number,
-                                  len(self.log_view.search_matched_lines)),
-             focus),
-            two_spaces,
-        ]
-
-    def get_button_fragments(self) -> StyleAndTextTuples:
-        """Return formatted text for the action buttons."""
-        focus = functools.partial(pw_console.widgets.mouse_handlers.on_click,
-                                  self.focus_log_pane)
-
-        one_space = ('', ' ', focus)
-        two_spaces = ('', '  ', focus)
-        cancel = functools.partial(pw_console.widgets.mouse_handlers.on_click,
-                                   self.cancel_search)
-        create_filter = functools.partial(
-            pw_console.widgets.mouse_handlers.on_click, self._create_filter)
-        next_match = functools.partial(
-            pw_console.widgets.mouse_handlers.on_click, self._next_match)
-        previous_match = functools.partial(
-            pw_console.widgets.mouse_handlers.on_click, self._previous_match)
-        toggle_search_follow = functools.partial(
-            pw_console.widgets.mouse_handlers.on_click,
-            self._toggle_search_follow)
-
-        button_style = 'class:toolbar-button-inactive'
-
-        fragments = []
-        fragments.extend(
-            pw_console.widgets.checkbox.to_keybind_indicator(
-                key='n',
-                description='Next',
-                mouse_handler=next_match,
-                base_style=button_style,
-            ))
-        fragments.append(two_spaces)
-
-        fragments.extend(
-            pw_console.widgets.checkbox.to_keybind_indicator(
-                key='N',
-                description='Previous',
-                mouse_handler=previous_match,
-                base_style=button_style,
-            ))
-        fragments.append(two_spaces)
-
-        fragments.extend(
-            pw_console.widgets.checkbox.to_keybind_indicator(
-                key='Ctrl-c',
-                description='Cancel',
-                mouse_handler=cancel,
-                base_style=button_style,
-            ))
-        fragments.append(two_spaces)
-
-        fragments.extend(
-            pw_console.widgets.checkbox.to_keybind_indicator(
-                key='Ctrl-Alt-f',
-                description='Add Filter',
-                mouse_handler=create_filter,
-                base_style=button_style,
-            ))
-        fragments.append(two_spaces)
-
-        fragments.extend(
-            pw_console.widgets.checkbox.to_checkbox_with_keybind_indicator(
-                checked=self.log_view.follow_search_match,
-                key='',
-                description='Jump to new matches',
-                mouse_handler=toggle_search_follow,
-                base_style=button_style))
-        fragments.append(one_space)
-
-        return fragments
diff --git a/pw_console/py/pw_console/style.py b/pw_console/py/pw_console/style.py
deleted file mode 100644
index da252b4..0000000
--- a/pw_console/py/pw_console/style.py
+++ /dev/null
@@ -1,504 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""UI Color Styles for ConsoleApp."""
-
-import logging
-from dataclasses import dataclass
-
-from prompt_toolkit.formatted_text import StyleAndTextTuples
-from prompt_toolkit.formatted_text.base import OneStyleAndTextTuple
-from prompt_toolkit.styles import Style
-from prompt_toolkit.filters import has_focus
-
-_LOG = logging.getLogger(__package__)
-
-
-@dataclass
-class HighContrastDarkColors:
-    # pylint: disable=too-many-instance-attributes
-    default_bg = '#100f10'
-    default_fg = '#ffffff'
-
-    dim_bg = '#000000'
-    dim_fg = '#e0e6f0'
-
-    button_active_bg = '#4e4e4e'
-    button_inactive_bg = '#323232'
-
-    active_bg = '#323232'
-    active_fg = '#f4f4f4'
-
-    inactive_bg = '#1e1e1e'
-    inactive_fg = '#bfc0c4'
-
-    line_highlight_bg = '#2f2f2f'
-    selected_line_bg = '#4e4e4e'
-    dialog_bg = '#3c3c3c'
-
-    red_accent = '#ffc0bf'
-    orange_accent = '#f5ca80'
-    yellow_accent = '#eedc82'
-    green_accent = '#88ef88'
-    cyan_accent = '#60e7e0'
-    blue_accent = '#92d9ff'
-    purple_accent = '#cfcaff'
-    magenta_accent = '#ffb8ff'
-
-
-@dataclass
-class DarkColors:
-    # pylint: disable=too-many-instance-attributes
-    default_bg = '#2e2e2e'
-    default_fg = '#eeeeee'
-
-    dim_bg = '#262626'
-    dim_fg = '#dfdfdf'
-
-    button_active_bg = '#626262'
-    button_inactive_bg = '#525252'
-
-    active_bg = '#525252'
-    active_fg = '#dfdfdf'
-
-    inactive_bg = '#3f3f3f'
-    inactive_fg = '#bfbfbf'
-
-    line_highlight_bg = '#525252'
-    selected_line_bg = '#626262'
-    dialog_bg = '#3c3c3c'
-
-    red_accent = '#ff6c6b'
-    orange_accent = '#da8548'
-    yellow_accent = '#ffcc66'
-    green_accent = '#98be65'
-    cyan_accent = '#66cccc'
-    blue_accent = '#6699cc'
-    purple_accent = '#a9a1e1'
-    magenta_accent = '#c678dd'
-
-
-@dataclass
-class NordColors:
-    # pylint: disable=too-many-instance-attributes
-    default_bg = '#2e3440'
-    default_fg = '#eceff4'
-
-    dim_bg = '#272c36'
-    dim_fg = '#e5e9f0'
-
-    button_active_bg = '#4c566a'
-    button_inactive_bg = '#434c5e'
-
-    active_bg = '#434c5e'
-    active_fg = '#eceff4'
-
-    inactive_bg = '#373e4c'
-    inactive_fg = '#d8dee9'
-
-    line_highlight_bg = '#191c25'
-    selected_line_bg = '#4c566a'
-    dialog_bg = '#2c333f'
-
-    red_accent = '#bf616a'
-    orange_accent = '#d08770'
-    yellow_accent = '#ebcb8b'
-    green_accent = '#a3be8c'
-    cyan_accent = '#88c0d0'
-    blue_accent = '#81a1c1'
-    purple_accent = '#a9a1e1'
-    magenta_accent = '#b48ead'
-
-
-@dataclass
-class NordLightColors:
-    # pylint: disable=too-many-instance-attributes
-    default_bg = '#e5e9f0'
-    default_fg = '#3b4252'
-    dim_bg = '#d8dee9'
-    dim_fg = '#2e3440'
-    button_active_bg = '#aebacf'
-    button_inactive_bg = '#b8c5db'
-    active_bg = '#b8c5db'
-    active_fg = '#3b4252'
-    inactive_bg = '#c2d0e7'
-    inactive_fg = '#60728c'
-    line_highlight_bg = '#f0f4fc'
-    selected_line_bg = '#f0f4fc'
-    dialog_bg = '#d8dee9'
-
-    red_accent = '#99324b'
-    orange_accent = '#ac4426'
-    yellow_accent = '#9a7500'
-    green_accent = '#4f894c'
-    cyan_accent = '#398eac'
-    blue_accent = '#3b6ea8'
-    purple_accent = '#842879'
-    magenta_accent = '#97365b'
-
-
-@dataclass
-class MoonlightColors:
-    # pylint: disable=too-many-instance-attributes
-    default_bg = '#212337'
-    default_fg = '#c8d3f5'
-    dim_bg = '#191a2a'
-    dim_fg = '#b4c2f0'
-    button_active_bg = '#444a73'
-    button_inactive_bg = '#2f334d'
-    active_bg = '#2f334d'
-    active_fg = '#c8d3f5'
-    inactive_bg = '#222436'
-    inactive_fg = '#a9b8e8'
-    line_highlight_bg = '#383e5c'
-    selected_line_bg = '#444a73'
-    dialog_bg = '#1e2030'
-
-    red_accent = '#d95468'
-    orange_accent = '#d98e48'
-    yellow_accent = '#ebbf83'
-    green_accent = '#8bd49c'
-    cyan_accent = '#70e1e8'
-    blue_accent = '#5ec4ff'
-    purple_accent = '#b62d65'
-    magenta_accent = '#e27e8d'
-
-
-@dataclass
-class AnsiTerm:
-    # pylint: disable=too-many-instance-attributes
-    default_bg = 'default'
-    default_fg = 'default'
-
-    dim_bg = 'default'
-    dim_fg = 'default'
-
-    button_active_bg = 'default underline'
-    button_inactive_bg = 'default'
-
-    active_bg = 'default'
-    active_fg = 'default'
-
-    inactive_bg = 'default'
-    inactive_fg = 'default'
-
-    line_highlight_bg = 'ansidarkgray white'
-    selected_line_bg = 'default reverse'
-    dialog_bg = 'default'
-
-    red_accent = 'ansired'
-    orange_accent = 'orange'
-    yellow_accent = 'ansiyellow'
-    green_accent = 'ansigreen'
-    cyan_accent = 'ansicyan'
-    blue_accent = 'ansiblue'
-    purple_accent = 'ansipurple'
-    magenta_accent = 'ansimagenta'
-
-
-_THEME_NAME_MAPPING = {
-    'moonlight': MoonlightColors(),
-    'nord': NordColors(),
-    'nord-light': NordLightColors(),
-    'dark': DarkColors(),
-    'high-contrast-dark': HighContrastDarkColors(),
-    'ansi': AnsiTerm(),
-}  # yapf: disable
-
-
-def get_theme_colors(theme_name=''):
-    theme = _THEME_NAME_MAPPING.get(theme_name, DarkColors())
-    return theme
-
-
-def generate_styles(theme_name='dark'):
-    """Return prompt_toolkit styles for the given theme name."""
-    # Use DarkColors() if name not found.
-    theme = _THEME_NAME_MAPPING.get(theme_name, DarkColors())
-
-    pw_console_styles = {
-        # Default text and background.
-        'default': 'bg:{} {}'.format(theme.default_bg, theme.default_fg),
-        # Dim inactive panes.
-        'pane_inactive': 'bg:{} {}'.format(theme.dim_bg, theme.dim_fg),
-        # Use default for active panes.
-        'pane_active': 'bg:{} {}'.format(theme.default_bg, theme.default_fg),
-
-        # Brighten active pane toolbars.
-        'toolbar_active': 'bg:{} {}'.format(theme.active_bg, theme.active_fg),
-        'toolbar_inactive': 'bg:{} {}'.format(theme.inactive_bg,
-                                              theme.inactive_fg),
-
-        # Dimmer toolbar.
-        'toolbar_dim_active': 'bg:{} {}'.format(theme.active_bg,
-                                                theme.active_fg),
-        'toolbar_dim_inactive': 'bg:{} {}'.format(theme.default_bg,
-                                                  theme.inactive_fg),
-        # Used for pane titles
-        'toolbar_accent': theme.cyan_accent,
-
-        'toolbar-button-decoration': '{}'.format(theme.cyan_accent),
-        'toolbar-setting-active': 'bg:{} {}'.format(
-            theme.green_accent,
-            theme.active_bg,
-        ),
-        'toolbar-button-active': 'bg:{}'.format(theme.button_active_bg),
-        'toolbar-button-inactive': 'bg:{}'.format(theme.button_inactive_bg),
-
-        # prompt_toolkit scrollbar styles:
-        'scrollbar.background': 'bg:{} {}'.format(theme.default_bg,
-                                                  theme.default_fg),
-        # Scrollbar handle, bg is the bar color.
-        'scrollbar.button': 'bg:{} {}'.format(theme.purple_accent,
-                                              theme.default_bg),
-        'scrollbar.arrow': 'bg:{} {}'.format(theme.default_bg,
-                                             theme.blue_accent),
-        # Unstyled scrollbar classes:
-        # 'scrollbar.start'
-        # 'scrollbar.end'
-
-        # Top menu bar styles
-        'menu-bar': 'bg:{} {}'.format(theme.inactive_bg, theme.inactive_fg),
-        'menu-bar.selected-item': 'bg:{} {}'.format(theme.blue_accent,
-                                                    theme.inactive_bg),
-        # Menu background
-        'menu': 'bg:{} {}'.format(theme.dialog_bg, theme.dim_fg),
-        # Menu item separator
-        'menu-border': theme.magenta_accent,
-
-        # Top bar logo + keyboard shortcuts
-        'logo':    '{} bold'.format(theme.magenta_accent),
-        'keybind': '{} bold'.format(theme.purple_accent),
-        'keyhelp': theme.dim_fg,
-
-        # Help window styles
-        'help_window_content': 'bg:{} {}'.format(theme.dialog_bg, theme.dim_fg),
-        'frame.border': 'bg:{} {}'.format(theme.dialog_bg, theme.purple_accent),
-
-        'pane_indicator_active': 'bg:{}'.format(theme.magenta_accent),
-        'pane_indicator_inactive': 'bg:{}'.format(theme.inactive_bg),
-
-        'pane_title_active': '{} bold'.format(theme.magenta_accent),
-        'pane_title_inactive': '{}'.format(theme.purple_accent),
-
-        'window-tab-active': 'bg:{} {}'.format(theme.active_bg,
-                                               theme.cyan_accent),
-        'window-tab-inactive': 'bg:{} {}'.format(theme.inactive_bg,
-                                                 theme.inactive_fg),
-
-        'pane_separator': 'bg:{} {}'.format(theme.default_bg,
-                                            theme.purple_accent),
-
-        # Search matches
-        'search': 'bg:{} {}'.format(theme.cyan_accent, theme.default_bg),
-        'search.current': 'bg:{} {}'.format(theme.cyan_accent,
-                                            theme.default_bg),
-
-        # Highlighted line styles
-        'selected-log-line': 'bg:{}'.format(theme.line_highlight_bg),
-        'marked-log-line': 'bg:{}'.format(theme.selected_line_bg),
-        'cursor-line': 'bg:{} nounderline'.format(theme.line_highlight_bg),
-
-        # Messages like 'Window too small'
-        'warning-text': 'bg:{} {}'.format(theme.default_bg,
-                                          theme.yellow_accent),
-
-        'log-time': 'bg:{} {}'.format(theme.default_fg,
-                                      theme.default_bg),
-
-        # Apply foreground only for level and column values. This way the text
-        # can inherit the background color of the parent window pane or line
-        # selection.
-        'log-level-{}'.format(logging.CRITICAL): '{} bold'.format(
-            theme.red_accent),
-        'log-level-{}'.format(logging.ERROR): '{}'.format(theme.red_accent),
-        'log-level-{}'.format(logging.WARNING): '{}'.format(
-            theme.yellow_accent),
-        'log-level-{}'.format(logging.INFO): '{}'.format(theme.purple_accent),
-        'log-level-{}'.format(logging.DEBUG): '{}'.format(theme.blue_accent),
-
-        'log-table-column-0': '{}'.format(theme.cyan_accent),
-        'log-table-column-1': '{}'.format(theme.green_accent),
-        'log-table-column-2': '{}'.format(theme.yellow_accent),
-        'log-table-column-3': '{}'.format(theme.magenta_accent),
-        'log-table-column-4': '{}'.format(theme.purple_accent),
-        'log-table-column-5': '{}'.format(theme.blue_accent),
-        'log-table-column-6': '{}'.format(theme.orange_accent),
-        'log-table-column-7': '{}'.format(theme.red_accent),
-
-        'search-bar': 'bg:{}'.format(theme.inactive_bg),
-        'search-bar-title': 'bg:{} {}'.format(theme.cyan_accent,
-                                              theme.default_bg),
-        'search-bar-setting': '{}'.format(theme.cyan_accent),
-        'search-bar-border': 'bg:{} {}'.format(theme.inactive_bg,
-                                               theme.cyan_accent),
-        'search-match-count-dialog': 'bg:{}'.format(theme.inactive_bg),
-        'search-match-count-dialog-title': '{}'.format(theme.cyan_accent),
-        'search-match-count-dialog-default-fg': '{}'.format(theme.default_fg),
-        'search-match-count-dialog-border': 'bg:{} {}'.format(
-            theme.inactive_bg,
-            theme.cyan_accent),
-
-        'filter-bar': 'bg:{}'.format(theme.inactive_bg),
-        'filter-bar-title': 'bg:{} {}'.format(theme.red_accent,
-                                              theme.default_bg),
-        'filter-bar-setting': '{}'.format(theme.cyan_accent),
-        'filter-bar-delete': '{}'.format(theme.red_accent),
-        'filter-bar-delimiter': '{}'.format(theme.purple_accent),
-
-        'saveas-dialog': 'bg:{}'.format(theme.inactive_bg),
-        'saveas-dialog-title': 'bg:{} {}'.format(theme.inactive_bg,
-                                                 theme.default_fg),
-        'saveas-dialog-setting': '{}'.format(theme.cyan_accent),
-        'saveas-dialog-border': 'bg:{} {}'.format(theme.inactive_bg,
-                                                  theme.cyan_accent),
-
-        'selection-dialog': 'bg:{}'.format(theme.inactive_bg),
-        'selection-dialog-title': '{}'.format(theme.yellow_accent),
-        'selection-dialog-default-fg': '{}'.format(theme.default_fg),
-        'selection-dialog-action-bg': 'bg:{}'.format(theme.yellow_accent),
-        'selection-dialog-action-fg': '{}'.format(theme.button_inactive_bg),
-        'selection-dialog-border': 'bg:{} {}'.format(theme.inactive_bg,
-                                                     theme.yellow_accent),
-
-        'quit-dialog': 'bg:{}'.format(theme.inactive_bg),
-        'quit-dialog-border': 'bg:{} {}'.format(theme.inactive_bg,
-                                                theme.red_accent),
-
-        'command-runner': 'bg:{}'.format(theme.inactive_bg),
-        'command-runner-title': 'bg:{} {}'.format(theme.inactive_bg,
-                                                  theme.default_fg),
-        'command-runner-setting': '{}'.format(theme.purple_accent),
-        'command-runner-border': 'bg:{} {}'.format(theme.inactive_bg,
-                                                   theme.purple_accent),
-        'command-runner-selected-item': 'bg:{}'.format(theme.selected_line_bg),
-        'command-runner-fuzzy-highlight-0': '{}'.format(theme.blue_accent),
-        'command-runner-fuzzy-highlight-1': '{}'.format(theme.cyan_accent),
-        'command-runner-fuzzy-highlight-2': '{}'.format(theme.green_accent),
-        'command-runner-fuzzy-highlight-3': '{}'.format(theme.yellow_accent),
-        'command-runner-fuzzy-highlight-4': '{}'.format(theme.orange_accent),
-        'command-runner-fuzzy-highlight-5': '{}'.format(theme.red_accent),
-
-        # Progress Bar Styles
-        # Entire set of ProgressBars - no title is used in pw_console
-        'title': '',
-        # Actual bar title
-        'label': 'bold',
-        'percentage': '{}'.format(theme.green_accent),
-        'bar': '{}'.format(theme.magenta_accent),
-        # Filled part of the bar
-        'bar-a': '{} bold'.format(theme.cyan_accent),
-        # End of current progress
-        'bar-b': '{} bold'.format(theme.purple_accent),
-        # Empty part of the bar
-        'bar-c': '',
-        # current/total counts
-        'current': '{}'.format(theme.cyan_accent),
-        'total': '{}'.format(theme.cyan_accent),
-        'time-elapsed': '{}'.format(theme.purple_accent),
-        'time-left': '{}'.format(theme.magenta_accent),
-
-        # Named theme color classes for use in user plugins.
-        'theme-fg-red': '{}'.format(theme.red_accent),
-        'theme-fg-orange': '{}'.format(theme.orange_accent),
-        'theme-fg-yellow': '{}'.format(theme.yellow_accent),
-        'theme-fg-green': '{}'.format(theme.green_accent),
-        'theme-fg-cyan': '{}'.format(theme.cyan_accent),
-        'theme-fg-blue': '{}'.format(theme.blue_accent),
-        'theme-fg-purple': '{}'.format(theme.purple_accent),
-        'theme-fg-magenta': '{}'.format(theme.magenta_accent),
-        'theme-bg-red': 'bg:{}'.format(theme.red_accent),
-        'theme-bg-orange': 'bg:{}'.format(theme.orange_accent),
-        'theme-bg-yellow': 'bg:{}'.format(theme.yellow_accent),
-        'theme-bg-green': 'bg:{}'.format(theme.green_accent),
-        'theme-bg-cyan': 'bg:{}'.format(theme.cyan_accent),
-        'theme-bg-blue': 'bg:{}'.format(theme.blue_accent),
-        'theme-bg-purple': 'bg:{}'.format(theme.purple_accent),
-        'theme-bg-magenta': 'bg:{}'.format(theme.magenta_accent),
-
-        'theme-bg-active': 'bg:{}'.format(theme.active_bg),
-        'theme-fg-active': '{}'.format(theme.active_fg),
-
-        'theme-bg-inactive': 'bg:{}'.format(theme.inactive_bg),
-        'theme-fg-inactive': '{}'.format(theme.inactive_fg),
-
-        'theme-fg-default': '{}'.format(theme.default_fg),
-        'theme-bg-default': 'bg:{}'.format(theme.default_bg),
-
-        'theme-fg-dim': '{}'.format(theme.dim_fg),
-        'theme-bg-dim': 'bg:{}'.format(theme.dim_bg),
-
-        'theme-bg-dialog': 'bg:{}'.format(theme.dialog_bg),
-        'theme-bg-line-highlight': 'bg:{}'.format(theme.line_highlight_bg),
-
-        'theme-bg-button-active': 'bg:{}'.format(theme.button_active_bg),
-        'theme-bg-button-inactive': 'bg:{}'.format(theme.button_inactive_bg),
-    }  # yapf: disable
-
-    return Style.from_dict(pw_console_styles)
-
-
-def get_toolbar_style(pt_container, dim=False) -> str:
-    """Return the style class for a toolbar if pt_container is in focus."""
-    if has_focus(pt_container.__pt_container__())():
-        return 'class:toolbar_dim_active' if dim else 'class:toolbar_active'
-    return 'class:toolbar_dim_inactive' if dim else 'class:toolbar_inactive'
-
-
-def get_button_style(pt_container) -> str:
-    """Return the style class for a toolbar if pt_container is in focus."""
-    if has_focus(pt_container.__pt_container__())():
-        return 'class:toolbar-button-active'
-    return 'class:toolbar-button-inactive'
-
-
-def get_pane_style(pt_container) -> str:
-    """Return the style class for a pane title if pt_container is in focus."""
-    if has_focus(pt_container.__pt_container__())():
-        return 'class:pane_active'
-    return 'class:pane_inactive'
-
-
-def get_pane_indicator(pt_container,
-                       title,
-                       mouse_handler=None,
-                       hide_indicator=False) -> StyleAndTextTuples:
-    """Return formatted text for a pane indicator and title."""
-
-    inactive_indicator: OneStyleAndTextTuple
-    active_indicator: OneStyleAndTextTuple
-    inactive_title: OneStyleAndTextTuple
-    active_title: OneStyleAndTextTuple
-
-    if mouse_handler:
-        inactive_indicator = ('class:pane_indicator_inactive', ' ',
-                              mouse_handler)
-        active_indicator = ('class:pane_indicator_active', ' ', mouse_handler)
-        inactive_title = ('class:pane_title_inactive', title, mouse_handler)
-        active_title = ('class:pane_title_active', title, mouse_handler)
-    else:
-        inactive_indicator = ('class:pane_indicator_inactive', ' ')
-        active_indicator = ('class:pane_indicator_active', ' ')
-        inactive_title = ('class:pane_title_inactive', title)
-        active_title = ('class:pane_title_active', title)
-
-    fragments: StyleAndTextTuples = []
-    if has_focus(pt_container.__pt_container__())():
-        if not hide_indicator:
-            fragments.append(active_indicator)
-        fragments.append(active_title)
-    else:
-        if not hide_indicator:
-            fragments.append(inactive_indicator)
-        fragments.append(inactive_title)
-    return fragments
diff --git a/pw_console/py/pw_console/templates/keybind_list.jinja b/pw_console/py/pw_console/templates/keybind_list.jinja
deleted file mode 100644
index 140d13b..0000000
--- a/pw_console/py/pw_console/templates/keybind_list.jinja
+++ /dev/null
@@ -1,39 +0,0 @@
-{#
-Copyright 2021 The Pigweed Authors
-
-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
-
-    https://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.
-#}
-{% set total_width = [max_description_width + max_key_list_width + 5, max_additional_help_text_width]|max %}
-{% set key_whitespace_indentation = ' ' * (max_description_width + 5) %}
-{% if preamble %}
-{{ preamble }}
-
-{% endif %}
-{% if additional_help_text %}
-{{ ' Help '.format(section).center(total_width, '=') }}
-
-{{ additional_help_text.rstrip() }}
-
-
-{# Two empty lines between sections #}
-{% endif %}
-{% for section, key_dict in sections.items() %}
-{{ ' {} Keys '.format(section).center(total_width, '=') }}
-
-{% for description, key_list in key_dict.items() %}
-{{ (description+' ').ljust(max_description_width + 3, '-') }}  {{ key_list|sort|join('\n' + key_whitespace_indentation) }}
-{% endfor %}
-
-
-{# Two empty lines between sections #}
-{% endfor %}
diff --git a/pw_console/py/pw_console/templates/repl_output.jinja b/pw_console/py/pw_console/templates/repl_output.jinja
deleted file mode 100644
index 4c482c1..0000000
--- a/pw_console/py/pw_console/templates/repl_output.jinja
+++ /dev/null
@@ -1,38 +0,0 @@
-{#
-Copyright 2021 The Pigweed Authors
-
-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
-
-    https://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.
-#}
-{% for code in code_items %}
-{% set index = loop.index if show_index else '' %}
-{% set prompt_indent_text = '... ' %}
-{% set input_text = code.input if code.input else '' %}
-
->>> {{ input_text|indent(width=prompt_indent_text) }}
-{% if code.stdout -%}
-  {{ code.stdout }}
-{%- endif %}
-{% if code.stderr -%}
-  {{ code.stderr }}
-{%- endif %}
-{% if code.is_running %}
-Running...
-{% endif %}
-{% if code.exception_text %}
-{{ code.exception_text }}
-{% elif code.result_object %}
-{{ result_format(code.result_object) }}
-{% elif code.output %}
-{{ code.output }}
-{% endif %}
-{% endfor -%}
diff --git a/pw_console/py/pw_console/text_formatting.py b/pw_console/py/pw_console/text_formatting.py
deleted file mode 100644
index 6fb53f9..0000000
--- a/pw_console/py/pw_console/text_formatting.py
+++ /dev/null
@@ -1,257 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Text formatting functions."""
-
-import copy
-import re
-from typing import Iterable, List, Tuple
-
-from prompt_toolkit.formatted_text import StyleAndTextTuples
-from prompt_toolkit.formatted_text.base import OneStyleAndTextTuple
-from prompt_toolkit.utils import get_cwidth
-
-_ANSI_SEQUENCE_REGEX = re.compile(r'\x1b[^m]*m')
-
-
-def strip_ansi(text: str):
-    """Strip out ANSI escape sequences."""
-    return _ANSI_SEQUENCE_REGEX.sub('', text)
-
-
-def split_lines(
-        input_fragments: StyleAndTextTuples) -> List[StyleAndTextTuples]:
-    """Break a flattened list of StyleAndTextTuples into a list of lines.
-
-    Ending line breaks are not preserved."""
-    lines: List[StyleAndTextTuples] = []
-    this_line: StyleAndTextTuples = []
-    for item in input_fragments:
-        if item[1].endswith('\n'):
-            # If there are no elements in this line except for a linebreak add
-            # an empty StyleAndTextTuple so this line isn't an empty list.
-            if len(this_line) == 0 and item[1] == '\n':
-                this_line.append((item[0], item[1][:-1]))
-            lines.append(this_line)
-            this_line = []
-        else:
-            this_line.append(item)
-    return lines
-
-
-def insert_linebreaks(
-        input_fragments: StyleAndTextTuples,
-        max_line_width: int,
-        truncate_long_lines: bool = True) -> Tuple[StyleAndTextTuples, int]:
-    """Add line breaks at max_line_width if truncate_long_lines is True.
-
-    Returns input_fragments with each character as it's own formatted text
-    tuple."""
-    fragments: StyleAndTextTuples = []
-    total_width = 0
-    line_width = 0
-    line_height = 0
-    new_break_inserted = False
-
-    for item in input_fragments:
-        # Check for non-printable fragment; doesn't affect the width.
-        if '[ZeroWidthEscape]' in item[0]:
-            fragments.append(item)
-            continue
-
-        new_item_style = item[0]
-
-        # For each character in the fragment
-        for character in item[1]:
-            # Get the width respecting double width characters
-            width = get_cwidth(character)
-            # Increment counters
-            total_width += width
-            line_width += width
-            # Save this character as it's own fragment
-            if line_width <= max_line_width:
-                if not new_break_inserted or character != '\n':
-                    fragments.append((new_item_style, character))
-                    # Was a line break just inserted?
-                    if character == '\n':
-                        # Increase height
-                        line_height += 1
-                new_break_inserted = False
-
-            # Reset width to zero even if we are beyond the max line width.
-            if character == '\n':
-                line_width = 0
-
-            # Are we at the limit for this line?
-            elif line_width == max_line_width:
-                # Insert a new linebreak fragment
-                fragments.append((new_item_style, '\n'))
-                # Increase height
-                line_height += 1
-                # Set a flag for skipping the next character if it is also a
-                # line break.
-                new_break_inserted = True
-
-                if not truncate_long_lines:
-                    # Reset line width to zero
-                    line_width = 0
-
-    # Check if the string ends in a final line break
-    last_fragment_style = fragments[-1][0]
-    last_fragment_text = fragments[-1][1]
-    if not last_fragment_text.endswith('\n'):
-        # Add a line break if none exists
-        fragments.append((last_fragment_style, '\n'))
-        line_height += 1
-
-    return fragments, line_height
-
-
-def join_adjacent_style_tuples(
-        fragments: StyleAndTextTuples) -> StyleAndTextTuples:
-    """Join adjacent FormattedTextTuples if they have the same style."""
-    new_fragments: StyleAndTextTuples = []
-
-    for i, fragment in enumerate(fragments):
-        # Add the first fragment
-        if i == 0:
-            new_fragments.append(fragment)
-            continue
-
-        # Get this style
-        style = fragment[0]
-        # If the previous style matches
-        if style == new_fragments[-1][0]:
-            # Get the previous text
-            new_text = new_fragments[-1][1]
-            # Append this text
-            new_text += fragment[1]
-            # Replace the last fragment
-            new_fragments[-1] = (style, new_text)
-        else:
-            # Styles don't match, just append.
-            new_fragments.append(fragment)
-
-    return new_fragments
-
-
-def fill_character_width(input_fragments: StyleAndTextTuples,
-                         fragment_width: int,
-                         window_width: int,
-                         line_wrapping: bool = False,
-                         remaining_width: int = 0,
-                         horizontal_scroll_amount: int = 0,
-                         add_cursor: bool = False) -> StyleAndTextTuples:
-    """Fill line to the width of the window using spaces."""
-    # Calculate the number of spaces to add at the end.
-    empty_characters = window_width - fragment_width
-    # If wrapping is on, use remaining_width
-    if line_wrapping and (fragment_width > window_width):
-        empty_characters = remaining_width
-
-    # Add additional spaces for horizontal scrolling.
-    empty_characters += horizontal_scroll_amount
-
-    if empty_characters <= 0:
-        # No additional spaces required
-        return input_fragments
-
-    line_fragments = copy.copy(input_fragments)
-
-    single_space = ('', ' ')
-    line_ends_in_a_break = False
-    # Replace the trailing \n with a space
-    if line_fragments[-1][1] == '\n':
-        line_fragments[-1] = single_space
-        empty_characters -= 1
-        line_ends_in_a_break = True
-
-    # Append remaining spaces
-    for _i in range(empty_characters):
-        line_fragments.append(single_space)
-
-    if line_ends_in_a_break:
-        # Restore the \n
-        line_fragments.append(('', '\n'))
-
-    if add_cursor:
-        # Add a cursor to this line by adding SetCursorPosition fragment.
-        line_fragments_remainder = line_fragments
-        line_fragments = [('[SetCursorPosition]', '')]
-        # Use extend to keep types happy.
-        line_fragments.extend(line_fragments_remainder)
-
-    return line_fragments
-
-
-def flatten_formatted_text_tuples(
-        lines: Iterable[StyleAndTextTuples]) -> StyleAndTextTuples:
-    """Flatten a list of lines of FormattedTextTuples
-
-    This function will also remove trailing newlines to avoid displaying extra
-    empty lines in prompt_toolkit containers.
-    """
-    fragments: StyleAndTextTuples = []
-
-    # Return empty list if lines is empty.
-    if not lines:
-        return fragments
-
-    for line_fragments in lines:
-        # Append all FormattedText tuples for this line.
-        for fragment in line_fragments:
-            fragments.append(fragment)
-
-    # Strip off any trailing line breaks
-    last_fragment: OneStyleAndTextTuple = fragments[-1]
-    style = last_fragment[0]
-    text = last_fragment[1].rstrip('\n')
-    fragments[-1] = (style, text)
-    return fragments
-
-
-def remove_formatting(formatted_text: StyleAndTextTuples) -> str:
-    """Throw away style info from prompt_toolkit formatted text tuples."""
-    return ''.join([formatted_tuple[1] for formatted_tuple in formatted_text])
-
-
-def get_line_height(text_width, screen_width, prefix_width):
-    """Calculates line height for a string with line wrapping enabled."""
-    if text_width == 0:
-        return 0
-
-    # If text will fit on the screen without wrapping.
-    if text_width <= screen_width:
-        return 1, screen_width - text_width
-
-    # Assume zero width prefix if it's >= width of the screen.
-    if prefix_width >= screen_width:
-        prefix_width = 0
-
-    # Start with height of 1 row.
-    total_height = 1
-
-    # One screen_width of characters (with no prefix) is displayed first.
-    remaining_width = text_width - screen_width
-
-    # While we have caracters remaining to be displayed
-    while remaining_width > 0:
-        # Add the new indentation prefix
-        remaining_width += prefix_width
-        # Display this line
-        remaining_width -= screen_width
-        # Add a line break
-        total_height += 1
-
-    # Remaining characters is what's left below zero.
-    return (total_height, abs(remaining_width))
diff --git a/pw_console/py/pw_console/widgets/__init__.py b/pw_console/py/pw_console/widgets/__init__.py
deleted file mode 100644
index 946e3af..0000000
--- a/pw_console/py/pw_console/widgets/__init__.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Pigweed Console Reusable UI widgets."""
-
-# pylint: disable=unused-import
-from pw_console.widgets.checkbox import (
-    ToolbarButton,
-    to_checkbox,
-    to_setting,
-    to_checkbox_with_keybind_indicator,
-    to_keybind_indicator,
-    to_checkbox_text,
-)
-from pw_console.widgets.mouse_handlers import on_click
-from pw_console.widgets.window_pane import WindowPane, WindowPaneHSplit
-from pw_console.widgets.window_pane_toolbar import WindowPaneToolbar
diff --git a/pw_console/py/pw_console/widgets/border.py b/pw_console/py/pw_console/widgets/border.py
deleted file mode 100644
index 0cf1170..0000000
--- a/pw_console/py/pw_console/widgets/border.py
+++ /dev/null
@@ -1,121 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Wrapper fuctions to add borders around prompt_toolkit containers."""
-
-from typing import Callable, List, Optional, Union
-
-from prompt_toolkit.layout import (
-    AnyContainer,
-    FormattedTextControl,
-    HSplit,
-    VSplit,
-    Window,
-)
-
-
-def create_border(
-    # pylint: disable=too-many-arguments
-    content: AnyContainer,
-    content_height: Optional[int] = None,
-    title: str = '',
-    border_style: Union[Callable[[], str], str] = '',
-    base_style: Union[Callable[[], str], str] = '',
-    top: bool = True,
-    bottom: bool = True,
-    left: bool = True,
-    right: bool = True,
-    horizontal_char: str = '━',
-    vertical_char: str = '┃',
-    top_left_char: str = '┏',
-    top_right_char: str = '┓',
-    bottom_left_char: str = '┗',
-    bottom_right_char: str = '┛',
-    left_margin_columns: int = 0,
-    right_margin_columns: int = 0,
-) -> HSplit:
-    """Wrap any prompt_toolkit container in a border."""
-
-    top_border_items: List[AnyContainer] = []
-    if left:
-        top_border_items.append(
-            Window(width=1, height=1, char=top_left_char, style=border_style))
-
-    title_text = None
-    if title:
-        title_text = FormattedTextControl([
-            ('', f'{horizontal_char}{horizontal_char} {title} ')
-        ])
-
-    top_border_items.append(
-        Window(
-            title_text,
-            char=horizontal_char,
-            # Expand width to max available space
-            dont_extend_width=False,
-            style=border_style))
-    if right:
-        top_border_items.append(
-            Window(width=1, height=1, char=top_right_char, style=border_style))
-
-    content_items: List[AnyContainer] = []
-    if left:
-        content_items.append(
-            Window(width=1,
-                   height=content_height,
-                   char=vertical_char,
-                   style=border_style))
-
-    if left_margin_columns > 0:
-        content_items.append(
-            Window(width=left_margin_columns,
-                   height=content_height,
-                   char=' ',
-                   style=border_style))
-    content_items.append(content)
-    if right_margin_columns > 0:
-        content_items.append(
-            Window(width=right_margin_columns,
-                   height=content_height,
-                   char=' ',
-                   style=border_style))
-
-    if right:
-        content_items.append(
-            Window(width=1, height=2, char=vertical_char, style=border_style))
-
-    bottom_border_items: List[AnyContainer] = []
-    if left:
-        bottom_border_items.append(
-            Window(width=1, height=1, char=bottom_left_char))
-    bottom_border_items.append(
-        Window(
-            char=horizontal_char,
-            # Expand width to max available space
-            dont_extend_width=False))
-    if right:
-        bottom_border_items.append(
-            Window(width=1, height=1, char=bottom_right_char))
-
-    rows: List[AnyContainer] = []
-    if top:
-        rows.append(VSplit(top_border_items, height=1, padding=0))
-    rows.append(VSplit(content_items, height=content_height))
-    if bottom:
-        rows.append(
-            VSplit(bottom_border_items,
-                   height=1,
-                   padding=0,
-                   style=border_style))
-
-    return HSplit(rows, style=base_style)
diff --git a/pw_console/py/pw_console/widgets/checkbox.py b/pw_console/py/pw_console/widgets/checkbox.py
deleted file mode 100644
index 91805e5..0000000
--- a/pw_console/py/pw_console/widgets/checkbox.py
+++ /dev/null
@@ -1,148 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Functions to create checkboxes for menus and toolbars."""
-
-import sys
-from typing import Callable, Iterable, Optional, NamedTuple
-
-from prompt_toolkit.formatted_text.base import OneStyleAndTextTuple
-from prompt_toolkit.formatted_text import StyleAndTextTuples
-
-_KEY_SEPARATOR = ' '
-_CHECKED_BOX = '[✓]'
-
-if sys.platform in ['win32']:
-    _CHECKED_BOX = '[x]'
-
-
-class ToolbarButton(NamedTuple):
-    key: Optional[str] = None
-    description: Optional[str] = 'Button'
-    mouse_handler: Optional[Callable] = None
-    is_checkbox: bool = False
-    checked: Optional[Callable] = None
-
-
-def to_checkbox(
-    checked: bool,
-    mouse_handler: Optional[Callable] = None,
-    end: str = ' ',
-    unchecked_style: str = 'class:checkbox',
-    checked_style: str = 'class:checkbox-checked',
-) -> OneStyleAndTextTuple:
-    text = _CHECKED_BOX if checked else '[ ]'
-    text += end
-    style = checked_style if checked else unchecked_style
-    if mouse_handler:
-        return (style, text, mouse_handler)
-    return (style, text)
-
-
-def to_checkbox_text(checked: bool, end=' '):
-    return to_checkbox(checked, end=end)[1]
-
-
-def to_setting(
-    checked: bool,
-    text: str,
-    active_style='class:toolbar-setting-active',
-    inactive_style='',
-    mouse_handler=None,
-):
-    """Apply a style to text if checked is True."""
-    style = active_style if checked else inactive_style
-    if mouse_handler:
-        return (style, text, mouse_handler)
-    return (style, text)
-
-
-def to_checkbox_with_keybind_indicator(
-    checked: bool,
-    key: str,
-    description: str,
-    mouse_handler=None,
-    base_style: str = '',
-    **checkbox_kwargs,
-):
-    """Create a clickable keybind indicator with checkbox for toolbars."""
-    if mouse_handler:
-        return to_keybind_indicator(key,
-                                    description,
-                                    mouse_handler,
-                                    leading_fragments=[
-                                        to_checkbox(checked, mouse_handler,
-                                                    **checkbox_kwargs)
-                                    ],
-                                    base_style=base_style)
-    return to_keybind_indicator(
-        key,
-        description,
-        leading_fragments=[to_checkbox(checked, **checkbox_kwargs)],
-        base_style=base_style)
-
-
-def to_keybind_indicator(
-    key: str,
-    description: str,
-    mouse_handler: Optional[Callable] = None,
-    leading_fragments: Optional[Iterable] = None,
-    middle_fragments: Optional[Iterable] = None,
-    base_style: str = '',
-    key_style: str = 'class:keybind',
-    description_style: str = 'class:keyhelp',
-):
-    """Create a clickable keybind indicator for toolbars."""
-    if base_style:
-        base_style += ' '
-
-    fragments: StyleAndTextTuples = []
-    fragments.append((base_style + 'class:toolbar-button-decoration', ' '))
-
-    def append_fragment_with_base_style(frag_list, fragment) -> None:
-        if mouse_handler:
-            frag_list.append(
-                (base_style + fragment[0], fragment[1], mouse_handler))
-        else:
-            frag_list.append((base_style + fragment[0], fragment[1]))
-
-    # Add any starting fragments first
-    if leading_fragments:
-        for fragment in leading_fragments:
-            append_fragment_with_base_style(fragments, fragment)
-
-    # Function name
-    if mouse_handler:
-        fragments.append(
-            (base_style + description_style, description, mouse_handler))
-    else:
-        fragments.append((base_style + description_style, description))
-
-    if middle_fragments:
-        for fragment in middle_fragments:
-            append_fragment_with_base_style(fragments, fragment)
-
-    # Separator and keybind
-    if key:
-        if mouse_handler:
-            fragments.append((base_style + description_style, _KEY_SEPARATOR,
-                              mouse_handler))
-            fragments.append((base_style + key_style, key, mouse_handler))
-        else:
-            fragments.append((base_style + description_style, _KEY_SEPARATOR))
-            fragments.append((base_style + key_style, key))
-
-    fragments.append((base_style + 'class:toolbar-button-decoration', ' '))
-    return fragments
diff --git a/pw_console/py/pw_console/widgets/event_count_history.py b/pw_console/py/pw_console/widgets/event_count_history.py
deleted file mode 100644
index 1779ad0..0000000
--- a/pw_console/py/pw_console/widgets/event_count_history.py
+++ /dev/null
@@ -1,120 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Wrapers for pyserial classes to log read and write data."""
-
-import collections
-import logging
-from dataclasses import dataclass
-import time
-from typing import Optional
-
-_LOG = logging.getLogger('pw_console')
-
-
-@dataclass
-class EventCountHistory:
-    """Track counts of events over time.
-
-    Example usage: ::
-
-        events = EventCountHistory(
-            base_count_units='Bytes',
-            display_unit_title='KiB/s',
-            display_unit_factor=0.001,
-            interval=1.0,
-            show_sparkline=True)
-
-        # Log 1 event now.
-        events.log(1)
-        time.sleep(1)
-
-        # Log 100 events at this time.
-        events.log(100)
-        time.sleep(1)
-
-        events.log(200)
-        time.sleep(1)
-        events.log(400)
-        print(events)
-        ▂▄█ 0.400 [KiB/s]
-
-    """
-
-    base_count_units: str = 'Bytes'
-    display_unit_title: str = 'KiB/s'
-    display_unit_factor: float = 0.001
-    interval: float = 1.0  # Number of seconds per sum of events.
-    history_limit: int = 20
-    scale_characters = ' ▁▂▃▄▅▆▇█'
-    history: collections.deque = collections.deque()
-    show_sparkline: bool = False
-    _this_count: int = 0
-    _last_count: int = 0
-    _last_update_time: float = time.time()
-
-    def log(self, count: int) -> None:
-        self._this_count += count
-
-        this_time = time.time()
-        if this_time - self._last_update_time >= self.interval:
-            self._last_update_time = this_time
-            self._last_count = self._this_count
-            self._this_count = 0
-            self.history.append(self._last_count)
-
-            if len(self.history) > self.history_limit:
-                self.history.popleft()
-
-    def last_count(self) -> float:
-        return self._last_count * self.display_unit_factor
-
-    def last_count_raw(self) -> int:
-        return self._last_count
-
-    def last_count_with_units(self) -> str:
-        return '{:.3f} [{}]'.format(
-            self._last_count * self.display_unit_factor,
-            self.display_unit_title)
-
-    def __repr__(self) -> str:
-        sparkline = ''
-        if self.show_sparkline:
-            sparkline = self.sparkline()
-        return ' '.join([sparkline, self.last_count_with_units()])
-
-    def __pt_formatted_text__(self):
-        return [('', self.__repr__())]
-
-    def sparkline(self,
-                  min_value: int = 0,
-                  max_value: Optional[int] = None) -> str:
-        msg = ''.rjust(self.history_limit)
-        if len(self.history) == 0:
-            return msg
-
-        minimum = min_value
-        maximum = max_value if max_value else max(self.history)
-        max_minus_min = maximum - min_value
-        if max_minus_min == 0:
-            return msg
-
-        msg = ''
-        for i in self.history:
-            # (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
-            index = int((((1.0 * i) - minimum) / max_minus_min) *
-                        len(self.scale_characters))
-            if index >= len(self.scale_characters):
-                index = len(self.scale_characters) - 1
-            msg += self.scale_characters[index]
-        return msg.rjust(self.history_limit)
diff --git a/pw_console/py/pw_console/widgets/mouse_handlers.py b/pw_console/py/pw_console/widgets/mouse_handlers.py
deleted file mode 100644
index af3c586..0000000
--- a/pw_console/py/pw_console/widgets/mouse_handlers.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Functions for handling mouse events."""
-
-from typing import Callable, TYPE_CHECKING
-
-from prompt_toolkit.layout.mouse_handlers import MouseHandlers
-from prompt_toolkit.mouse_events import MouseEvent, MouseEventType
-
-if TYPE_CHECKING:
-    # pylint: disable=ungrouped-imports
-    from prompt_toolkit.key_binding.key_bindings import NotImplementedOrNone
-
-
-def on_click(on_click_function: Callable, mouse_event: MouseEvent):
-    """Run a function on mouse click.
-
-    Here is an example of how to add add click functionality to a piece of
-    formatted text. ::
-
-        import functools
-        import pw_console.widgets.mouse_handlers
-
-        def get_tokens(self):
-            mouse_handler = functools.partial(
-                pw_console.widgets.mouse_handlers.on_click,
-                self.your_widget.do_something)
-            return [
-                (
-                    'class:text-button',
-                    ' Click Here to Do Something ',
-                    mouse_handler,
-                ),
-            ]
-
-    """
-    if mouse_event.event_type == MouseEventType.MOUSE_UP:
-        on_click_function()
-        return None
-    return NotImplemented
-
-
-class EmptyMouseHandler(MouseHandlers):
-    """MouseHandler that does not propagate events."""
-    def set_mouse_handler_for_range(
-        self,
-        x_min: int,
-        x_max: int,
-        y_min: int,
-        y_max: int,
-        handler: Callable[[MouseEvent], 'NotImplementedOrNone'],
-    ) -> None:
-        return
diff --git a/pw_console/py/pw_console/widgets/table.py b/pw_console/py/pw_console/widgets/table.py
deleted file mode 100644
index ea31543..0000000
--- a/pw_console/py/pw_console/widgets/table.py
+++ /dev/null
@@ -1,260 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Table view renderer for LogLines."""
-
-import collections
-import copy
-
-from prompt_toolkit.formatted_text import StyleAndTextTuples
-
-from pw_console.console_prefs import ConsolePrefs
-from pw_console.log_line import LogLine
-import pw_console.text_formatting
-
-
-class TableView:
-    """Store column information and render logs into formatted tables."""
-
-    # TODO(tonymd): Add a method to provide column formatters externally.
-    # Should allow for string format, column color, and column ordering.
-    FLOAT_FORMAT = '%.3f'
-    INT_FORMAT = '%s'
-    LAST_TABLE_COLUMN_NAMES = ['msg', 'message']
-
-    def __init__(self, prefs: ConsolePrefs):
-        self.set_prefs(prefs)
-        self.column_widths: collections.OrderedDict = collections.OrderedDict()
-        self._header_fragment_cache = None
-
-        # Assume common defaults here before recalculating in set_formatting().
-        self._default_time_width: int = 17
-        self.column_widths['time'] = self._default_time_width
-        self.column_widths['level'] = 3
-        self._year_month_day_width: int = 9
-
-        # Width of all columns except the final message
-        self.column_width_prefix_total = 0
-
-    def set_prefs(self, prefs: ConsolePrefs) -> None:
-        self.prefs = prefs
-        # Max column widths of each log field
-        self.column_padding = ' ' * self.prefs.spaces_between_columns
-
-    def all_column_names(self):
-        columns_names = [
-            name for name, _width in self._ordered_column_widths()
-        ]
-        return columns_names + ['message']
-
-    def _width_of_justified_fields(self):
-        """Calculate the width of all columns except LAST_TABLE_COLUMN_NAMES."""
-        padding_width = len(self.column_padding)
-        used_width = sum([
-            width + padding_width for key, width in self.column_widths.items()
-            if key not in TableView.LAST_TABLE_COLUMN_NAMES
-        ])
-        return used_width
-
-    def _ordered_column_widths(self):
-        """Return each column and width in the preferred order."""
-        if self.prefs.column_order:
-            # Get ordered_columns
-            columns = copy.copy(self.column_widths)
-            ordered_columns = {}
-
-            for column_name in self.prefs.column_order:
-                # If valid column name
-                if column_name in columns:
-                    ordered_columns[column_name] = columns.pop(column_name)
-
-            # Add remaining columns unless user preference to hide them.
-            if not self.prefs.omit_unspecified_columns:
-                for column_name in columns:
-                    ordered_columns[column_name] = columns[column_name]
-        else:
-            ordered_columns = copy.copy(self.column_widths)
-
-        if not self.prefs.show_python_file and 'py_file' in ordered_columns:
-            del ordered_columns['py_file']
-        if not self.prefs.show_python_logger and 'py_logger' in ordered_columns:
-            del ordered_columns['py_logger']
-        if not self.prefs.show_source_file and 'file' in ordered_columns:
-            del ordered_columns['file']
-
-        return ordered_columns.items()
-
-    def update_metadata_column_widths(self, log: LogLine):
-        """Calculate the max widths for each metadata field."""
-        for field_name, value in log.metadata.fields.items():
-            value_string = str(value)
-
-            # Get width of formatted numbers
-            if isinstance(value, float):
-                value_string = TableView.FLOAT_FORMAT % value
-            elif isinstance(value, int):
-                value_string = TableView.INT_FORMAT % value
-
-            current_width = self.column_widths.get(field_name, 0)
-            if len(value_string) > current_width:
-                self.column_widths[field_name] = len(value_string)
-
-        # Update log level character width.
-        ansi_stripped_level = pw_console.text_formatting.strip_ansi(
-            log.record.levelname)
-        if len(ansi_stripped_level) > self.column_widths['level']:
-            self.column_widths['level'] = len(ansi_stripped_level)
-
-        self.column_width_prefix_total = self._width_of_justified_fields()
-        self._update_table_header()
-
-    def _update_table_header(self):
-        default_style = 'bold'
-        fragments: collections.deque = collections.deque()
-
-        # Update time column width to current prefs setting
-        self.column_widths['time'] = self._default_time_width
-        if self.prefs.hide_date_from_log_time:
-            self.column_widths['time'] = (self._default_time_width -
-                                          self._year_month_day_width)
-
-        for name, width in self._ordered_column_widths():
-            # These fields will be shown at the end
-            if name in ['msg', 'message']:
-                continue
-            fragments.append(
-                (default_style, name.title()[:width].ljust(width)))
-            fragments.append(('', self.column_padding))
-
-        fragments.append((default_style, 'Message'))
-
-        self._header_fragment_cache = list(fragments)
-
-    def formatted_header(self):
-        """Get pre-formatted table header."""
-        return self._header_fragment_cache
-
-    def formatted_row(self, log: LogLine) -> StyleAndTextTuples:
-        """Render a single table row."""
-        # pylint: disable=too-many-locals
-        padding_formatted_text = ('', self.column_padding)
-        # Don't apply any background styling that would override the parent
-        # window or selected-log-line style.
-        default_style = ''
-
-        table_fragments: StyleAndTextTuples = []
-
-        # NOTE: To preseve ANSI formatting on log level use:
-        # table_fragments.extend(
-        #     ANSI(log.record.levelname.ljust(
-        #         self.column_widths['level'])).__pt_formatted_text__())
-
-        # Collect remaining columns to display after host time and level.
-        columns = {}
-        for name, width in self._ordered_column_widths():
-            # Skip these modifying these fields
-            if name in ['msg', 'message']:
-                continue
-
-            # hasattr checks are performed here since a log record may not have
-            # asctime or levelname if they are not included in the formatter
-            # fmt string.
-            if name == 'time' and hasattr(log.record, 'asctime'):
-                time_text = log.record.asctime
-                if self.prefs.hide_date_from_log_time:
-                    time_text = time_text[self._year_month_day_width:]
-                time_style = self.prefs.column_style('time',
-                                                     time_text,
-                                                     default='class:log-time')
-                columns['time'] = (time_style,
-                                   time_text.ljust(self.column_widths['time']))
-                continue
-
-            if name == 'level' and hasattr(log.record, 'levelname'):
-                # Remove any existing ANSI formatting and apply our colors.
-                level_text = pw_console.text_formatting.strip_ansi(
-                    log.record.levelname)
-                level_style = self.prefs.column_style(
-                    'level',
-                    level_text,
-                    default='class:log-level-{}'.format(log.record.levelno))
-                columns['level'] = (level_style,
-                                    level_text.ljust(
-                                        self.column_widths['level']))
-                continue
-
-            value = log.metadata.fields.get(name, ' ')
-            left_justify = True
-
-            # Right justify and format numbers
-            if isinstance(value, float):
-                value = TableView.FLOAT_FORMAT % value
-                left_justify = False
-            elif isinstance(value, int):
-                value = TableView.INT_FORMAT % value
-                left_justify = False
-
-            if left_justify:
-                columns[name] = value.ljust(width)
-            else:
-                columns[name] = value.rjust(width)
-
-        # Grab the message to appear after the justified columns with ANSI
-        # escape sequences removed.
-        message_text = pw_console.text_formatting.strip_ansi(
-            log.record.message)
-        message = log.metadata.fields.get(
-            'msg',
-            message_text.rstrip(),  # Remove any trailing line breaks
-        )
-        # Alternatively ANSI formatting can be preserved with:
-        #   message = ANSI(log.record.message).__pt_formatted_text__()
-
-        # Convert to FormattedText if we have a raw string from fields.
-        if isinstance(message, str):
-            message_style = default_style
-            if log.record.levelno >= 30:  # Warning, Error and Critical
-                # Style the whole message to match it's level
-                message_style = 'class:log-level-{}'.format(log.record.levelno)
-            message = (message_style, message)
-        # Add to columns
-        columns['message'] = message
-
-        index_modifier = 0
-        # Go through columns and convert to FormattedText where needed.
-        for i, column in enumerate(columns.items()):
-            column_name, column_value = column
-            if i in [0, 1] and column_name in ['time', 'level']:
-                index_modifier -= 1
-            # For raw strings that don't have their own ANSI colors, apply the
-            # theme color style for this column.
-            if isinstance(column_value, str):
-                fallback_style = 'class:log-table-column-{}'.format(
-                    i + index_modifier) if 0 <= i <= 7 else default_style
-
-                style = self.prefs.column_style(column_name,
-                                                column_value.rstrip(),
-                                                default=fallback_style)
-
-                table_fragments.append((style, column_value))
-                table_fragments.append(padding_formatted_text)
-            # Add this tuple to table_fragments.
-            elif isinstance(column, tuple):
-                table_fragments.append(column_value)
-                # Add padding if not the last column.
-                if i < len(columns) - 1:
-                    table_fragments.append(padding_formatted_text)
-
-        # Add the final new line for this row.
-        table_fragments.append(('', '\n'))
-        return table_fragments
diff --git a/pw_console/py/pw_console/widgets/window_pane.py b/pw_console/py/pw_console/widgets/window_pane.py
deleted file mode 100644
index ab2484a..0000000
--- a/pw_console/py/pw_console/widgets/window_pane.py
+++ /dev/null
@@ -1,211 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Window pane base class."""
-
-from abc import ABC
-from typing import Any, Optional, TYPE_CHECKING, Union
-import functools
-
-from prompt_toolkit.layout.dimension import AnyDimension
-
-from prompt_toolkit.filters import Condition
-from prompt_toolkit.layout import (
-    AnyContainer,
-    ConditionalContainer,
-    Dimension,
-    HSplit,
-    walk,
-)
-
-from pw_console.get_pw_console_app import get_pw_console_app
-
-import pw_console.widgets.checkbox
-import pw_console.widgets.mouse_handlers
-import pw_console.style
-
-if TYPE_CHECKING:
-    from pw_console.console_app import ConsoleApp
-
-
-class WindowPaneHSplit(HSplit):
-    """PromptToolkit HSplit that saves the current width and height.
-
-    This overrides the write_to_screen function to save the width and height of
-    the container to be rendered.
-    """
-    def __init__(self, parent_window_pane, *args, **kwargs):
-        # Save a reference to the parent window pane.
-        self.parent_window_pane = parent_window_pane
-        super().__init__(*args, **kwargs)
-
-    def write_to_screen(
-        self,
-        screen,
-        mouse_handlers,
-        write_position,
-        parent_style: str,
-        erase_bg: bool,
-        z_index: Optional[int],
-    ) -> None:
-        # Save the width and height for the current render pass. This will be
-        # used by the log pane to render the correct amount of log lines.
-        self.parent_window_pane.update_pane_size(write_position.width,
-                                                 write_position.height)
-        # Continue writing content to the screen.
-        super().write_to_screen(screen, mouse_handlers, write_position,
-                                parent_style, erase_bg, z_index)
-
-
-class WindowPane(ABC):
-    """The Pigweed Console Window Pane parent class."""
-
-    # pylint: disable=too-many-instance-attributes
-    def __init__(
-        self,
-        application: Union['ConsoleApp', Any] = None,
-        pane_title: str = 'Window',
-        height: Optional[AnyDimension] = None,
-        width: Optional[AnyDimension] = None,
-    ):
-        if application:
-            self.application = application
-        else:
-            self.application = get_pw_console_app()
-
-        self._pane_title = pane_title
-        self._pane_subtitle: str = ''
-
-        # Default width and height to 10 lines each. They will be resized by the
-        # WindowManager later.
-        self.height = height if height else Dimension(preferred=10)
-        self.width = width if width else Dimension(preferred=10)
-
-        # Boolean to show or hide this window pane
-        self.show_pane = True
-        # Booleans for toggling top and bottom toolbars
-        self.show_top_toolbar = True
-        self.show_bottom_toolbar = True
-
-        # Height and width values for the current rendering pass.
-        self.current_pane_width = 0
-        self.current_pane_height = 0
-        self.last_pane_width = 0
-        self.last_pane_height = 0
-
-    def __repr__(self) -> str:
-        """Create a repr with this pane's title and subtitle."""
-        repr_str = f'{type(self).__qualname__}(pane_title="{self.pane_title()}"'
-        if self.pane_subtitle():
-            repr_str += f', pane_subtitle="{self.pane_subtitle()}"'
-        repr_str += ')'
-        return repr_str
-
-    def pane_title(self) -> str:
-        return self._pane_title
-
-    def set_pane_title(self, title: str) -> None:
-        self._pane_title = title
-
-    def menu_title(self) -> str:
-        """Return a title to display in the Window menu."""
-        return self.pane_title()
-
-    def pane_subtitle(self) -> str:  # pylint: disable=no-self-use
-        """Further title info for display in the Window menu."""
-        return ''
-
-    def redraw_ui(self) -> None:
-        """Redraw the prompt_toolkit UI."""
-        if not hasattr(self, 'application'):
-            return
-        # Thread safe way of sending a repaint trigger to the input event loop.
-        self.application.redraw_ui()
-
-    def focus_self(self) -> None:
-        """Switch prompt_toolkit focus to this window pane."""
-        if not hasattr(self, 'application'):
-            return
-        self.application.focus_on_container(self)
-
-    def __pt_container__(self):
-        """Return the prompt_toolkit root container for this log pane.
-
-        This allows self to be used wherever prompt_toolkit expects a container
-        object."""
-        return self.container  # pylint: disable=no-member
-
-    def get_all_key_bindings(self) -> list:
-        """Return keybinds for display in the help window.
-
-        For example:
-
-        Using a prompt_toolkit control:
-
-          return [self.some_content_control_instance.get_key_bindings()]
-
-        Hand-crafted bindings for display in the HelpWindow:
-
-          return [{
-              'Execute code': ['Enter', 'Option-Enter', 'Meta-Enter'],
-              'Reverse search history': ['Ctrl-R'],
-              'Erase input buffer.': ['Ctrl-C'],
-              'Show settings.': ['F2'],
-              'Show history.': ['F3'],
-          }]
-        """
-        # pylint: disable=no-self-use
-        return []
-
-    def get_all_menu_options(self) -> list:
-        """Return menu options for the window pane.
-
-        Should return a list of tuples containing with the display text and
-        callable to invoke on click.
-        """
-        # pylint: disable=no-self-use
-        return []
-
-    def pane_resized(self) -> bool:
-        """Return True if the current window size has changed."""
-        return (self.last_pane_width != self.current_pane_width
-                or self.last_pane_height != self.current_pane_height)
-
-    def update_pane_size(self, width, height) -> None:
-        """Save pane width and height for the current UI render pass."""
-        if width:
-            self.last_pane_width = self.current_pane_width
-            self.current_pane_width = width
-        if height:
-            self.last_pane_height = self.current_pane_height
-            self.current_pane_height = height
-
-    def _create_pane_container(self, *content) -> ConditionalContainer:
-        return ConditionalContainer(
-            WindowPaneHSplit(
-                self,
-                content,
-                # Window pane dimensions
-                height=lambda: self.height,
-                width=lambda: self.width,
-                style=functools.partial(pw_console.style.get_pane_style, self),
-            ),
-            filter=Condition(lambda: self.show_pane))
-
-    def has_child_container(self, child_container: AnyContainer) -> bool:
-        if not child_container:
-            return False
-        for container in walk(self.__pt_container__()):
-            if container == child_container:
-                return True
-        return False
diff --git a/pw_console/py/pw_console/widgets/window_pane_toolbar.py b/pw_console/py/pw_console/widgets/window_pane_toolbar.py
deleted file mode 100644
index 3c16beb..0000000
--- a/pw_console/py/pw_console/widgets/window_pane_toolbar.py
+++ /dev/null
@@ -1,251 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Window pane toolbar base class."""
-
-import logging
-from typing import Any, Callable, List, Optional
-import functools
-
-from prompt_toolkit.filters import Condition, has_focus
-from prompt_toolkit.layout import (
-    ConditionalContainer,
-    FormattedTextControl,
-    VSplit,
-    Window,
-    WindowAlign,
-)
-from prompt_toolkit.mouse_events import MouseEvent, MouseEventType
-
-from pw_console.get_pw_console_app import get_pw_console_app
-import pw_console.style
-from pw_console.widgets import (
-    ToolbarButton,
-    to_checkbox_with_keybind_indicator,
-    to_keybind_indicator,
-)
-import pw_console.widgets.mouse_handlers
-
-_LOG = logging.getLogger(__package__)
-
-
-class WindowPaneResizeHandle(FormattedTextControl):
-    """Button to initiate window pane resize drag events."""
-    def __init__(self, parent_window_pane: Any, *args, **kwargs) -> None:
-        self.parent_window_pane = parent_window_pane
-        super().__init__(*args, **kwargs)
-
-    def mouse_handler(self, mouse_event: MouseEvent):
-        """Mouse handler for this control."""
-        # Start resize mouse drag event
-        if mouse_event.event_type == MouseEventType.MOUSE_DOWN:
-            get_pw_console_app().window_manager.start_resize_pane(
-                self.parent_window_pane)
-            # Mouse event handled, return None.
-            return None
-
-        # Mouse event not handled, return NotImplemented.
-        return NotImplemented
-
-
-class WindowPaneToolbar:
-    """One line toolbar for display at the bottom of of a window pane."""
-    # pylint: disable=too-many-instance-attributes
-    TOOLBAR_HEIGHT = 1
-
-    def get_left_text_tokens(self):
-        """Return toolbar indicator and title."""
-
-        title = self.title
-        if not title and self.parent_window_pane:
-            # No title was set, fetch the parent window pane title if available.
-            parent_pane_title = self.parent_window_pane.pane_title()
-            title = parent_pane_title if parent_pane_title else title
-        return pw_console.style.get_pane_indicator(self.focus_check_container,
-                                                   f' {title} ',
-                                                   self.focus_mouse_handler)
-
-    def get_center_text_tokens(self):
-        """Return formatted text tokens for display in the center part of the
-        toolbar."""
-
-        button_style = pw_console.style.get_button_style(
-            self.focus_check_container)
-
-        # FormattedTextTuple contents: (Style, Text, Mouse handler)
-        separator_text = [('', '  ')
-                          ]  # 2 spaces of separaton between keybinds.
-        if self.focus_mouse_handler:
-            separator_text = [('', '  ', self.focus_mouse_handler)]
-
-        fragments = []
-        fragments.extend(separator_text)
-
-        for button in self.buttons:
-            on_click_handler = None
-            if button.mouse_handler:
-                on_click_handler = functools.partial(
-                    pw_console.widgets.mouse_handlers.on_click,
-                    button.mouse_handler)
-
-            if button.is_checkbox:
-                fragments.extend(
-                    to_checkbox_with_keybind_indicator(
-                        button.checked(),
-                        button.key,
-                        button.description,
-                        on_click_handler,
-                        base_style=button_style))
-            else:
-                fragments.extend(
-                    to_keybind_indicator(button.key,
-                                         button.description,
-                                         on_click_handler,
-                                         base_style=button_style))
-
-            fragments.extend(separator_text)
-
-        # Remaining whitespace should focus on click.
-        fragments.extend(separator_text)
-
-        return fragments
-
-    def get_right_text_tokens(self):
-        """Return formatted text tokens for display."""
-        fragments = []
-        if not has_focus(self.focus_check_container.__pt_container__())():
-            fragments.append((
-                'class:toolbar-button-inactive class:toolbar-button-decoration',
-                ' ', self.focus_mouse_handler))
-            fragments.append(('class:toolbar-button-inactive class:keyhelp',
-                              'click to focus', self.focus_mouse_handler))
-            fragments.append((
-                'class:toolbar-button-inactive class:toolbar-button-decoration',
-                ' ', self.focus_mouse_handler))
-        fragments.append(
-            ('', '  {} '.format(self.subtitle()), self.focus_mouse_handler))
-        return fragments
-
-    def get_resize_handle(self):
-        return pw_console.style.get_pane_indicator(self.focus_check_container,
-                                                   '─══─',
-                                                   hide_indicator=True)
-
-    def add_button(self, button: ToolbarButton):
-        self.buttons.append(button)
-
-    def __init__(
-        self,
-        parent_window_pane: Optional[Any] = None,
-        title: Optional[str] = None,
-        subtitle: Optional[Callable[[], str]] = None,
-        focus_check_container: Optional[Any] = None,
-        focus_action_callable: Optional[Callable] = None,
-        center_section_align: WindowAlign = WindowAlign.LEFT,
-        include_resize_handle: bool = True,
-    ):
-
-        self.parent_window_pane = parent_window_pane
-        self.title = title
-        self.subtitle = subtitle
-
-        # Assume check this container for focus
-        self.focus_check_container = self
-        self.focus_action_callable = None
-
-        # Set parent_window_pane related options
-        if self.parent_window_pane:
-            if not subtitle:
-                self.subtitle = self.parent_window_pane.pane_subtitle
-            self.focus_check_container = self.parent_window_pane
-            self.focus_action_callable = self.parent_window_pane.focus_self
-
-        # Set title overrides
-        if self.subtitle is None:
-
-            def empty_subtitle() -> str:
-                return ''
-
-            self.subtitle = empty_subtitle
-
-        if focus_check_container:
-            self.focus_check_container = focus_check_container
-        if focus_action_callable:
-            self.focus_action_callable = focus_action_callable
-
-        self.focus_mouse_handler = None
-        if self.focus_action_callable:
-            self.focus_mouse_handler = functools.partial(
-                pw_console.widgets.mouse_handlers.on_click,
-                self.focus_action_callable)
-
-        self.buttons: List[ToolbarButton] = []
-        self.show_toolbar = True
-
-        self.left_section_window = Window(
-            content=FormattedTextControl(self.get_left_text_tokens),
-            align=WindowAlign.LEFT,
-            dont_extend_width=True,
-        )
-
-        self.center_section_window = Window(
-            content=FormattedTextControl(self.get_center_text_tokens),
-            align=center_section_align,
-            dont_extend_width=False,
-        )
-
-        self.right_section_window = Window(
-            content=FormattedTextControl(self.get_right_text_tokens),
-            # Right side text should appear at the far right of the toolbar
-            align=WindowAlign.RIGHT,
-            dont_extend_width=True,
-        )
-
-        get_toolbar_style = functools.partial(
-            pw_console.style.get_toolbar_style, self.focus_check_container)
-
-        sections = [
-            self.left_section_window,
-            self.center_section_window,
-            self.right_section_window,
-        ]
-        if self.parent_window_pane and include_resize_handle:
-            resize_handle = Window(
-                content=WindowPaneResizeHandle(
-                    self.parent_window_pane,
-                    self.get_resize_handle,
-                ),
-                # Right side text should appear at the far right of the toolbar
-                align=WindowAlign.RIGHT,
-                dont_extend_width=True,
-            )
-            sections.append(resize_handle)
-
-        self.toolbar_vsplit = VSplit(
-            sections,
-            height=WindowPaneToolbar.TOOLBAR_HEIGHT,
-            style=get_toolbar_style,
-        )
-
-        self.container = self._create_toolbar_container(self.toolbar_vsplit)
-
-    def _create_toolbar_container(self, content):
-        return ConditionalContainer(
-            content, filter=Condition(lambda: self.show_toolbar))
-
-    def __pt_container__(self):
-        """Return the prompt_toolkit root container for this log pane.
-
-        This allows self to be used wherever prompt_toolkit expects a container
-        object."""
-        return self.container  # pylint: disable=no-member
diff --git a/pw_console/py/pw_console/window_list.py b/pw_console/py/pw_console/window_list.py
deleted file mode 100644
index ec21b58..0000000
--- a/pw_console/py/pw_console/window_list.py
+++ /dev/null
@@ -1,594 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""WindowList"""
-
-import collections
-from enum import Enum
-import functools
-import logging
-from typing import Any, List, Optional, TYPE_CHECKING
-
-from prompt_toolkit.filters import has_focus
-from prompt_toolkit.layout import (
-    Dimension,
-    FormattedTextControl,
-    HSplit,
-    HorizontalAlign,
-    VSplit,
-    Window,
-    WindowAlign,
-)
-from prompt_toolkit.mouse_events import MouseEvent, MouseEventType, MouseButton
-
-import pw_console.style
-import pw_console.widgets.mouse_handlers
-
-if TYPE_CHECKING:
-    # pylint: disable=ungrouped-imports
-    from pw_console.window_manager import WindowManager
-
-_LOG = logging.getLogger(__package__)
-
-
-class DisplayMode(Enum):
-    """WindowList display modes."""
-    STACK = 'Stacked'
-    TABBED = 'Tabbed'
-
-
-DEFAULT_DISPLAY_MODE = DisplayMode.STACK
-
-# Weighted amount for adjusting window dimensions when enlarging and shrinking.
-_WINDOW_HEIGHT_ADJUST = 1
-
-
-class WindowListHSplit(HSplit):
-    """PromptToolkit HSplit class with some additions for size and mouse resize.
-
-    This HSplit has a write_to_screen function that saves the width and height
-    of the container for the current render pass. It also handles overriding
-    mouse handlers for triggering window resize adjustments.
-    """
-    def __init__(self, parent_window_list, *args, **kwargs):
-        # Save a reference to the parent window pane.
-        self.parent_window_list = parent_window_list
-        super().__init__(*args, **kwargs)
-
-    def write_to_screen(
-        self,
-        screen,
-        mouse_handlers,
-        write_position,
-        parent_style: str,
-        erase_bg: bool,
-        z_index: Optional[int],
-    ) -> None:
-        new_mouse_handlers = mouse_handlers
-        # Is resize mode active?
-        if self.parent_window_list.resize_mode:
-            # Ignore future mouse_handler updates.
-            new_mouse_handlers = (
-                pw_console.widgets.mouse_handlers.EmptyMouseHandler())
-            # Set existing mouse_handlers to the parent_window_list's
-            # mouse_handler. This will handle triggering resize events.
-            mouse_handlers.set_mouse_handler_for_range(
-                write_position.xpos,
-                write_position.xpos + write_position.width,
-                write_position.ypos,
-                write_position.ypos + write_position.height,
-                self.parent_window_list.mouse_handler)
-
-        # Save the width, height, and draw position for the current render pass.
-        self.parent_window_list.update_window_list_size(
-            write_position.width, write_position.height, write_position.xpos,
-            write_position.ypos)
-        # Continue writing content to the screen.
-        super().write_to_screen(screen, new_mouse_handlers, write_position,
-                                parent_style, erase_bg, z_index)
-
-
-class WindowList:
-    """WindowList holds a stack of windows for the WindowManager."""
-
-    # pylint: disable=too-many-instance-attributes,too-many-public-methods
-    def __init__(
-        self,
-        window_manager: 'WindowManager',
-    ):
-        self.window_manager = window_manager
-        self.application = window_manager.application
-
-        self.current_window_list_width: int = 0
-        self.current_window_list_height: int = 0
-        self.last_window_list_width: int = 0
-        self.last_window_list_height: int = 0
-
-        self.current_window_list_xposition: int = 0
-        self.last_window_list_xposition: int = 0
-        self.current_window_list_yposition: int = 0
-        self.last_window_list_yposition: int = 0
-
-        self.display_mode = DEFAULT_DISPLAY_MODE
-        self.active_panes: collections.deque = collections.deque()
-        self.focused_pane_index: Optional[int] = None
-
-        self.height = Dimension(preferred=10)
-        self.width = Dimension(preferred=10)
-
-        self.resize_mode = False
-        self.resize_target_pane_index = None
-        self.resize_target_pane = None
-        self.resize_current_row = 0
-
-        # Reference to the current prompt_toolkit window split for the current
-        # set of active_panes.
-        self.container = None
-
-    def _calculate_actual_heights(self) -> List[int]:
-        heights = [
-            p.height.preferred if p.show_pane else 0 for p in self.active_panes
-        ]
-        available_height = self.current_window_list_height
-        remaining_rows = available_height - sum(heights)
-        window_index = 0
-
-        # Distribute remaining unaccounted rows to each window in turn.
-        while remaining_rows > 0:
-            # 0 heights are hiden windows, only add +1 to visible windows.
-            if heights[window_index] > 0:
-                heights[window_index] += 1
-                remaining_rows -= 1
-            window_index = (window_index + 1) % len(heights)
-
-        return heights
-
-    def _update_resize_current_row(self):
-        heights = self._calculate_actual_heights()
-        start_row = 0
-
-        # Find the starting row
-        for i in range(self.resize_target_pane_index + 1):
-            # If we are past the current pane, exit the loop.
-            if i > self.resize_target_pane_index:
-                break
-            # 0 heights are hidden windows, only count visible windows.
-            if heights[i] > 0:
-                start_row += heights[i]
-        self.resize_current_row = start_row
-
-    def start_resize(self, target_pane, pane_index):
-        # Can only resize if view mode is stacked.
-        if self.display_mode != DisplayMode.STACK:
-            return
-
-        # Check the target_pane isn't the last one in the list
-        visible_panes = [pane for pane in self.active_panes if pane.show_pane]
-        if target_pane == visible_panes[-1]:
-            return
-
-        self.resize_mode = True
-        self.resize_target_pane_index = pane_index
-        self._update_resize_current_row()
-
-    def stop_resize(self):
-        self.resize_mode = False
-        self.resize_target_pane_index = None
-        self.resize_current_row = 0
-
-    def get_tab_mode_active_pane(self):
-        if self.focused_pane_index is None:
-            self.focused_pane_index = 0
-
-        pane = None
-        try:
-            pane = self.active_panes[self.focused_pane_index]
-        except IndexError:
-            # Ignore ValueError which can be raised by the self.active_panes
-            # deque if existing_pane can't be found.
-            self.focused_pane_index = 0
-            pane = self.active_panes[self.focused_pane_index]
-        return pane
-
-    def get_current_active_pane(self):
-        """Return the current active window pane."""
-        focused_pane = None
-
-        command_runner_focused_pane = None
-        if self.application.command_runner_is_open():
-            command_runner_focused_pane = (
-                self.application.command_runner_last_focused_pane())
-
-        for index, pane in enumerate(self.active_panes):
-            in_focus = False
-            if has_focus(pane)():
-                in_focus = True
-            elif command_runner_focused_pane and pane.has_child_container(
-                    command_runner_focused_pane):
-                in_focus = True
-
-            if in_focus:
-                focused_pane = pane
-                self.focused_pane_index = index
-                break
-        return focused_pane
-
-    def get_pane_titles(self, omit_subtitles=False, use_menu_title=True):
-        fragments = []
-        separator = ('', ' ')
-        fragments.append(separator)
-        for pane_index, pane in enumerate(self.active_panes):
-            title = pane.menu_title() if use_menu_title else pane.pane_title()
-            subtitle = pane.pane_subtitle()
-            text = f' {title} {subtitle} '
-            if omit_subtitles:
-                text = f' {title} '
-
-            fragments.append((
-                # Style
-                ('class:window-tab-active' if pane_index
-                 == self.focused_pane_index else 'class:window-tab-inactive'),
-                # Text
-                text,
-                # Mouse handler
-                functools.partial(
-                    pw_console.widgets.mouse_handlers.on_click,
-                    functools.partial(self.switch_to_tab, pane_index),
-                ),
-            ))
-            fragments.append(separator)
-        return fragments
-
-    def switch_to_tab(self, index: int):
-        self.focused_pane_index = index
-
-        # refresh_ui() will focus on the new tab container.
-        self.refresh_ui()
-
-    def set_display_mode(self, mode: DisplayMode):
-        self.display_mode = mode
-
-        if self.display_mode == DisplayMode.TABBED:
-            self.focused_pane_index = 0
-            # Un-hide all panes, they must be visible to switch between tabs.
-            for pane in self.active_panes:
-                pane.show_pane = True
-
-        self.application.focus_main_menu()
-        self.refresh_ui()
-
-    def refresh_ui(self):
-        self.window_manager.update_root_container_body()
-        # Update menu after the window manager rebuilds the root container.
-        self.application.update_menu_items()
-
-        if self.display_mode == DisplayMode.TABBED:
-            self.application.focus_on_container(
-                self.active_panes[self.focused_pane_index])
-
-        self.application.redraw_ui()
-
-    def _set_window_heights(self, new_heights: List[int]):
-        for pane in self.active_panes:
-            if not pane.show_pane:
-                continue
-            pane.height = Dimension(preferred=new_heights[0])
-            new_heights = new_heights[1:]
-
-    def rebalance_window_heights(self):
-        available_height = self.current_window_list_height
-
-        old_values = [
-            p.height.preferred for p in self.active_panes if p.show_pane
-        ]
-        # Make sure the old total is not zero.
-        old_total = max(sum(old_values), 1)
-        percentages = [value / old_total for value in old_values]
-        new_heights = [
-            int(available_height * percentage) for percentage in percentages
-        ]
-
-        self._set_window_heights(new_heights)
-
-    def update_window_list_size(self, width, height, xposition,
-                                yposition) -> None:
-        """Save width and height of the repl pane for the current UI render
-        pass."""
-        if width:
-            self.last_window_list_width = self.current_window_list_width
-            self.current_window_list_width = width
-        if height:
-            self.last_window_list_height = self.current_window_list_height
-            self.current_window_list_height = height
-        if xposition:
-            self.last_window_list_xposition = (
-                self.current_window_list_xposition)
-            self.current_window_list_xposition = xposition
-        if yposition:
-            self.last_window_list_yposition = (
-                self.current_window_list_yposition)
-            self.current_window_list_yposition = yposition
-
-        if (self.current_window_list_width != self.last_window_list_width
-                or self.current_window_list_height !=
-                self.last_window_list_height):
-            self.rebalance_window_heights()
-
-    def mouse_handler(self, mouse_event: MouseEvent):
-        mouse_position = mouse_event.position
-
-        if (mouse_event.event_type == MouseEventType.MOUSE_MOVE
-                and mouse_event.button == MouseButton.LEFT):
-            self.mouse_resize(mouse_position.x, mouse_position.y)
-        elif mouse_event.event_type == MouseEventType.MOUSE_UP:
-            self.stop_resize()
-            # Mouse event handled, return None.
-            return None
-        else:
-            self.stop_resize()
-
-        # Mouse event not handled, return NotImplemented.
-        return NotImplemented
-
-    def update_container(self):
-        """Re-create the window list split depending on the display mode."""
-
-        if self.display_mode == DisplayMode.STACK:
-            content_split = WindowListHSplit(
-                self,
-                list(pane for pane in self.active_panes if pane.show_pane),
-                height=lambda: self.height,
-                width=lambda: self.width,
-            )
-
-        elif self.display_mode == DisplayMode.TABBED:
-            content_split = WindowListHSplit(
-                self,
-                [
-                    self._create_window_tab_toolbar(),
-                    self.get_tab_mode_active_pane(),
-                ],
-                height=lambda: self.height,
-                width=lambda: self.width,
-            )
-
-        self.container = content_split
-
-    def _create_window_tab_toolbar(self):
-        tab_bar_control = FormattedTextControl(
-            functools.partial(self.get_pane_titles,
-                              omit_subtitles=True,
-                              use_menu_title=False))
-        tab_bar_window = Window(content=tab_bar_control,
-                                align=WindowAlign.LEFT,
-                                dont_extend_width=True)
-
-        spacer = Window(content=FormattedTextControl([('', '')]),
-                        align=WindowAlign.LEFT,
-                        dont_extend_width=False)
-
-        tab_toolbar = VSplit(
-            [
-                tab_bar_window,
-                spacer,
-            ],
-            style='class:toolbar_dim_inactive',
-            height=1,
-            align=HorizontalAlign.LEFT,
-        )
-        return tab_toolbar
-
-    def empty(self) -> bool:
-        return len(self.active_panes) == 0
-
-    def pane_index(self, pane):
-        pane_index = None
-        try:
-            pane_index = self.active_panes.index(pane)
-        except ValueError:
-            # Ignore ValueError which can be raised by the self.active_panes
-            # deque if existing_pane can't be found.
-            pass
-        return pane_index
-
-    def add_pane_no_checks(self, pane: Any, add_at_beginning=False):
-        if add_at_beginning:
-            self.active_panes.appendleft(pane)
-        else:
-            self.active_panes.append(pane)
-
-    def add_pane(self, new_pane, existing_pane=None, add_at_beginning=False):
-        existing_pane_index = self.pane_index(existing_pane)
-        if existing_pane_index is not None:
-            self.active_panes.insert(new_pane, existing_pane_index + 1)
-        else:
-            if add_at_beginning:
-                self.active_panes.appendleft(new_pane)
-            else:
-                self.active_panes.append(new_pane)
-
-        self.refresh_ui()
-
-    def remove_pane_no_checks(self, pane: Any):
-        try:
-            self.active_panes.remove(pane)
-        except ValueError:
-            # ValueError will be raised if the the pane is not found
-            pass
-        return pane
-
-    def remove_pane(self, existing_pane):
-        existing_pane_index = self.pane_index(existing_pane)
-        if existing_pane_index is None:
-            return
-
-        self.active_panes.remove(existing_pane)
-        self.refresh_ui()
-
-        # Set focus to the previous window pane
-        if len(self.active_panes) > 0:
-            existing_pane_index -= 1
-            try:
-                self.application.focus_on_container(
-                    self.active_panes[existing_pane_index])
-            except ValueError:
-                # ValueError will be raised if the the pane at
-                # existing_pane_index can't be accessed.
-                # Focus on the main menu if the existing pane is hidden.
-                self.application.focus_main_menu()
-
-        self.application.redraw_ui()
-
-    def enlarge_pane(self):
-        """Enlarge the currently focused window pane."""
-        pane = self.get_current_active_pane()
-        if pane:
-            self.adjust_pane_size(pane, _WINDOW_HEIGHT_ADJUST)
-
-    def shrink_pane(self):
-        """Shrink the currently focused window pane."""
-        pane = self.get_current_active_pane()
-        if pane:
-            self.adjust_pane_size(pane, -_WINDOW_HEIGHT_ADJUST)
-
-    def mouse_resize(self, _xpos, ypos) -> None:
-        if self.resize_target_pane_index is None:
-            return
-
-        target_pane = self.active_panes[self.resize_target_pane_index]
-
-        diff = ypos - self.resize_current_row
-        if not self.window_manager.vertical_window_list_spliting():
-            # The mouse ypos value includes rows from other window lists. If
-            # horizontal splitting is active we need to check the diff relative
-            # to the starting y position row. Subtract the start y position and
-            # an additional 1 for the top menu bar.
-            diff -= self.current_window_list_yposition - 1
-
-        if diff == 0:
-            return
-        self.adjust_pane_size(target_pane, diff)
-        self._update_resize_current_row()
-        self.application.redraw_ui()
-
-    def adjust_pane_size(self,
-                         pane,
-                         diff: int = _WINDOW_HEIGHT_ADJUST) -> None:
-        """Increase or decrease a given pane's height."""
-        # Placeholder next_pane value to allow setting width and height without
-        # any consequences if there is no next visible pane.
-        next_pane = HSplit([],
-                           height=Dimension(preferred=10),
-                           width=Dimension(preferred=10))  # type: ignore
-        # Try to get the next visible pane to subtract a weight value from.
-        next_visible_pane = self._get_next_visible_pane_after(pane)
-        if next_visible_pane:
-            next_pane = next_visible_pane
-
-        # If the last pane is selected, and there are at least 2 panes, make
-        # next_pane the previous pane.
-        try:
-            if len(self.active_panes) >= 2 and (self.active_panes.index(pane)
-                                                == len(self.active_panes) - 1):
-                next_pane = self.active_panes[-2]
-        except ValueError:
-            # Ignore ValueError raised if self.active_panes[-2] doesn't exist.
-            pass
-
-        old_height = pane.height.preferred
-        if diff < 0 and old_height <= 1:
-            return
-        next_old_height = next_pane.height.preferred  # type: ignore
-
-        # Add to the current pane
-        new_height = old_height + diff
-        if new_height <= 0:
-            new_height = old_height
-
-        # Subtract from the next pane
-        next_new_height = next_old_height - diff
-        if next_new_height <= 0:
-            next_new_height = next_old_height
-
-        # If new height is too small or no change, make no adjustments.
-        if new_height < 3 or next_new_height < 3 or old_height == new_height:
-            return
-
-        # Set new heigts of the target pane and next pane.
-        pane.height.preferred = new_height
-        next_pane.height.preferred = next_new_height  # type: ignore
-
-    def reset_pane_sizes(self):
-        """Reset all active pane heights evenly."""
-
-        available_height = self.current_window_list_height
-        old_values = [
-            p.height.preferred for p in self.active_panes if p.show_pane
-        ]
-        new_heights = [int(available_height / len(old_values))
-                       ] * len(old_values)
-
-        self._set_window_heights(new_heights)
-
-    def move_pane_up(self):
-        pane = self.get_current_active_pane()
-        pane_index = self.pane_index(pane)
-        if pane_index is None or pane_index <= 0:
-            # Already at the beginning
-            return
-
-        # Swap with the previous pane
-        previous_pane = self.active_panes[pane_index - 1]
-        self.active_panes[pane_index - 1] = pane
-        self.active_panes[pane_index] = previous_pane
-
-        self.refresh_ui()
-
-    def move_pane_down(self):
-        pane = self.get_current_active_pane()
-        pane_index = self.pane_index(pane)
-        pane_count = len(self.active_panes)
-        if pane_index is None or pane_index + 1 >= pane_count:
-            # Already at the end
-            return
-
-        # Swap with the next pane
-        next_pane = self.active_panes[pane_index + 1]
-        self.active_panes[pane_index + 1] = pane
-        self.active_panes[pane_index] = next_pane
-
-        self.refresh_ui()
-
-    def _get_next_visible_pane_after(self, target_pane):
-        """Return the next visible pane that appears after the target pane."""
-        try:
-            target_pane_index = self.active_panes.index(target_pane)
-        except ValueError:
-            # If pane can't be found, focus on the main menu.
-            return None
-
-        # Loop through active panes (not including the target_pane).
-        for i in range(1, len(self.active_panes)):
-            next_pane_index = (target_pane_index + i) % len(self.active_panes)
-            next_pane = self.active_panes[next_pane_index]
-            if next_pane.show_pane:
-                return next_pane
-        return None
-
-    def focus_next_visible_pane(self, pane):
-        """Focus on the next visible window pane if possible."""
-        next_visible_pane = self._get_next_visible_pane_after(pane)
-        if next_visible_pane:
-            self.application.layout.focus(next_visible_pane)
-            return
-        self.application.focus_main_menu()
diff --git a/pw_console/py/pw_console/window_manager.py b/pw_console/py/pw_console/window_manager.py
deleted file mode 100644
index 044751b..0000000
--- a/pw_console/py/pw_console/window_manager.py
+++ /dev/null
@@ -1,1030 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""WindowManager"""
-
-import collections
-import copy
-import functools
-from itertools import chain
-import logging
-import operator
-from typing import Any, Dict, Iterable, List, Optional
-
-from prompt_toolkit.key_binding import KeyBindings
-from prompt_toolkit.layout import (
-    Dimension,
-    HSplit,
-    VSplit,
-    FormattedTextControl,
-    Window,
-    WindowAlign,
-)
-from prompt_toolkit.mouse_events import MouseEvent, MouseEventType, MouseButton
-from prompt_toolkit.widgets import MenuItem
-
-from pw_console.console_prefs import ConsolePrefs, error_unknown_window
-from pw_console.log_pane import LogPane
-import pw_console.widgets.checkbox
-from pw_console.widgets import WindowPaneToolbar
-import pw_console.widgets.mouse_handlers
-from pw_console.window_list import WindowList, DisplayMode
-
-_LOG = logging.getLogger(__package__)
-
-# Amount for adjusting window dimensions when enlarging and shrinking.
-_WINDOW_SPLIT_ADJUST = 1
-
-
-class WindowListResizeHandle(FormattedTextControl):
-    """Button to initiate window list resize drag events."""
-    def __init__(self, window_manager, window_list: Any, *args,
-                 **kwargs) -> None:
-        self.window_manager = window_manager
-        self.window_list = window_list
-        super().__init__(*args, **kwargs)
-
-    def mouse_handler(self, mouse_event: MouseEvent):
-        """Mouse handler for this control."""
-        # Start resize mouse drag event
-        if mouse_event.event_type == MouseEventType.MOUSE_DOWN:
-            self.window_manager.start_resize(self.window_list)
-            # Mouse event handled, return None.
-            return None
-
-        # Mouse event not handled, return NotImplemented.
-        return NotImplemented
-
-
-class WindowManagerVSplit(VSplit):
-    """PromptToolkit VSplit class with some additions for size and mouse resize.
-
-    This VSplit has a write_to_screen function that saves the width and height
-    of the container for the current render pass. It also handles overriding
-    mouse handlers for triggering window resize adjustments.
-    """
-    def __init__(self, parent_window_manager, *args, **kwargs):
-        # Save a reference to the parent window pane.
-        self.parent_window_manager = parent_window_manager
-        super().__init__(*args, **kwargs)
-
-    def write_to_screen(
-        self,
-        screen,
-        mouse_handlers,
-        write_position,
-        parent_style: str,
-        erase_bg: bool,
-        z_index: Optional[int],
-    ) -> None:
-        new_mouse_handlers = mouse_handlers
-        # Is resize mode active?
-        if self.parent_window_manager.resize_mode:
-            # Ignore future mouse_handler updates.
-            new_mouse_handlers = (
-                pw_console.widgets.mouse_handlers.EmptyMouseHandler())
-            # Set existing mouse_handlers to the parent_window_managers's
-            # mouse_handler. This will handle triggering resize events.
-            mouse_handlers.set_mouse_handler_for_range(
-                write_position.xpos,
-                write_position.xpos + write_position.width,
-                write_position.ypos,
-                write_position.ypos + write_position.height,
-                self.parent_window_manager.mouse_handler)
-
-        # Save the width and height for the current render pass.
-        self.parent_window_manager.update_window_manager_size(
-            write_position.width, write_position.height)
-        # Continue writing content to the screen.
-        super().write_to_screen(screen, new_mouse_handlers, write_position,
-                                parent_style, erase_bg, z_index)
-
-
-class WindowManagerHSplit(HSplit):
-    """PromptToolkit HSplit class with some additions for size and mouse resize.
-
-    This HSplit has a write_to_screen function that saves the width and height
-    of the container for the current render pass. It also handles overriding
-    mouse handlers for triggering window resize adjustments.
-    """
-    def __init__(self, parent_window_manager, *args, **kwargs):
-        # Save a reference to the parent window pane.
-        self.parent_window_manager = parent_window_manager
-        super().__init__(*args, **kwargs)
-
-    def write_to_screen(
-        self,
-        screen,
-        mouse_handlers,
-        write_position,
-        parent_style: str,
-        erase_bg: bool,
-        z_index: Optional[int],
-    ) -> None:
-        new_mouse_handlers = mouse_handlers
-        # Is resize mode active?
-        if self.parent_window_manager.resize_mode:
-            # Ignore future mouse_handler updates.
-            new_mouse_handlers = (
-                pw_console.widgets.mouse_handlers.EmptyMouseHandler())
-            # Set existing mouse_handlers to the parent_window_managers's
-            # mouse_handler. This will handle triggering resize events.
-            mouse_handlers.set_mouse_handler_for_range(
-                write_position.xpos,
-                write_position.xpos + write_position.width,
-                write_position.ypos,
-                write_position.ypos + write_position.height,
-                self.parent_window_manager.mouse_handler)
-
-        # Save the width and height for the current render pass.
-        self.parent_window_manager.update_window_manager_size(
-            write_position.width, write_position.height)
-        # Continue writing content to the screen.
-        super().write_to_screen(screen, new_mouse_handlers, write_position,
-                                parent_style, erase_bg, z_index)
-
-
-class WindowManager:
-    """WindowManager class
-
-    This class handles adding/removing/resizing windows and rendering the
-    prompt_toolkit split layout."""
-
-    # pylint: disable=too-many-public-methods,too-many-instance-attributes
-
-    def __init__(
-        self,
-        application: Any,
-    ):
-        self.application = application
-        self.window_lists: collections.deque = collections.deque()
-        self.window_lists.append(WindowList(self))
-        self.key_bindings = self._create_key_bindings()
-        self.top_toolbars: List[WindowPaneToolbar] = []
-        self.bottom_toolbars: List[WindowPaneToolbar] = []
-
-        self.resize_mode: bool = False
-        self.resize_target_window_list_index: Optional[int] = None
-        self.resize_target_window_list: Optional[int] = None
-        self.resize_current_row: int = 0
-        self.resize_current_column: int = 0
-
-        self.current_window_manager_width: int = 0
-        self.current_window_manager_height: int = 0
-        self.last_window_manager_width: int = 0
-        self.last_window_manager_height: int = 0
-
-    def update_window_manager_size(self, width, height):
-        """Save width and height for the current UI render pass."""
-        if width:
-            self.last_window_manager_width = self.current_window_manager_width
-            self.current_window_manager_width = width
-        if height:
-            self.last_window_manager_height = self.current_window_manager_height
-            self.current_window_manager_height = height
-
-        if (self.current_window_manager_width != self.last_window_manager_width
-                or self.current_window_manager_height !=
-                self.last_window_manager_height):
-            self.rebalance_window_list_sizes()
-
-    def _set_window_list_sizes(self, new_heights: List[int],
-                               new_widths: List[int]) -> None:
-        for window_list in self.window_lists:
-            window_list.height = Dimension(preferred=new_heights[0])
-            new_heights = new_heights[1:]
-            window_list.width = Dimension(preferred=new_widths[0])
-            new_widths = new_widths[1:]
-
-    def vertical_window_list_spliting(self) -> bool:
-        return self.application.prefs.window_column_split_method == 'vertical'
-
-    def rebalance_window_list_sizes(self) -> None:
-        """Adjust relative split sizes to fill available space."""
-        available_height = self.current_window_manager_height
-        available_width = self.current_window_manager_width
-
-        old_heights = [w.height.preferred for w in self.window_lists]
-        old_widths = [w.width.preferred for w in self.window_lists]
-
-        # Make sure the old totals are not zero.
-        old_height_total = max(sum(old_heights), 1)
-        old_width_total = max(sum(old_widths), 1)
-
-        height_percentages = [
-            value / old_height_total for value in old_heights
-        ]
-        width_percentages = [value / old_width_total for value in old_widths]
-
-        new_heights = [
-            int(available_height * percentage)
-            for percentage in height_percentages
-        ]
-        new_widths = [
-            int(available_width * percentage)
-            for percentage in width_percentages
-        ]
-
-        if self.vertical_window_list_spliting():
-            new_heights = [
-                self.current_window_manager_height for h in new_heights
-            ]
-        else:
-            new_widths = [
-                self.current_window_manager_width for h in new_widths
-            ]
-
-        self._set_window_list_sizes(new_heights, new_widths)
-
-    def _create_key_bindings(self) -> KeyBindings:
-        key_bindings = KeyBindings()
-        register = self.application.prefs.register_keybinding
-
-        @register('window-manager.move-pane-left', key_bindings)
-        def move_pane_left(_event):
-            """Move window pane left."""
-            self.move_pane_left()
-
-        @register('window-manager.move-pane-right', key_bindings)
-        def move_pane_right(_event):
-            """Move window pane right."""
-            self.move_pane_right()
-
-        @register('window-manager.move-pane-down', key_bindings)
-        def move_pane_down(_event):
-            """Move window pane down."""
-            self.move_pane_down()
-
-        @register('window-manager.move-pane-up', key_bindings)
-        def move_pane_up(_event):
-            """Move window pane up."""
-            self.move_pane_up()
-
-        @register('window-manager.enlarge-pane', key_bindings)
-        def enlarge_pane(_event):
-            """Enlarge the active window pane."""
-            self.enlarge_pane()
-
-        @register('window-manager.shrink-pane', key_bindings)
-        def shrink_pane(_event):
-            """Shrink the active window pane."""
-            self.shrink_pane()
-
-        @register('window-manager.shrink-split', key_bindings)
-        def shrink_split(_event):
-            """Shrink the current window split."""
-            self.shrink_split()
-
-        @register('window-manager.enlarge-split', key_bindings)
-        def enlarge_split(_event):
-            """Enlarge the current window split."""
-            self.enlarge_split()
-
-        @register('window-manager.focus-prev-pane', key_bindings)
-        def focus_prev_pane(_event):
-            """Switch focus to the previous window pane or tab."""
-            self.focus_previous_pane()
-
-        @register('window-manager.focus-next-pane', key_bindings)
-        def focus_next_pane(_event):
-            """Switch focus to the next window pane or tab."""
-            self.focus_next_pane()
-
-        @register('window-manager.balance-window-panes', key_bindings)
-        def balance_window_panes(_event):
-            """Balance all window sizes."""
-            self.balance_window_sizes()
-
-        return key_bindings
-
-    def delete_empty_window_lists(self):
-        empty_lists = [
-            window_list for window_list in self.window_lists
-            if window_list.empty()
-        ]
-        for empty_list in empty_lists:
-            self.window_lists.remove(empty_list)
-
-    def add_top_toolbar(self, toolbar: WindowPaneToolbar) -> None:
-        self.top_toolbars.append(toolbar)
-
-    def add_bottom_toolbar(self, toolbar: WindowPaneToolbar) -> None:
-        self.bottom_toolbars.append(toolbar)
-
-    def create_root_container(self):
-        """Create vertical or horizontal splits for all active panes."""
-        self.delete_empty_window_lists()
-
-        for window_list in self.window_lists:
-            window_list.update_container()
-
-        vertical_split = self.vertical_window_list_spliting()
-
-        window_containers = []
-        for i, window_list in enumerate(self.window_lists):
-            window_containers.append(window_list.container)
-            if (i + 1) >= len(self.window_lists):
-                continue
-
-            if vertical_split:
-                separator_padding = Window(
-                    content=WindowListResizeHandle(self, window_list, "│"),
-                    char='│',
-                    width=1,
-                    dont_extend_height=False,
-                )
-                resize_separator = HSplit(
-                    [
-                        separator_padding,
-                        Window(
-                            content=WindowListResizeHandle(
-                                self, window_list, "║\n║\n║"),
-                            char='│',
-                            width=1,
-                            dont_extend_height=True,
-                        ),
-                        separator_padding,
-                    ],
-                    style='class:pane_separator',
-                )
-            else:
-                resize_separator = Window(
-                    content=WindowListResizeHandle(self, window_list, "════"),
-                    char='─',
-                    height=1,
-                    align=WindowAlign.CENTER,
-                    dont_extend_width=False,
-                    style='class:pane_separator',
-                )
-            window_containers.append(resize_separator)
-
-        if vertical_split:
-            split = WindowManagerVSplit(self, window_containers)
-        else:
-            split = WindowManagerHSplit(self, window_containers)
-
-        split_items = []
-        split_items.extend(self.top_toolbars)
-        split_items.append(split)
-        split_items.extend(self.bottom_toolbars)
-        return HSplit(split_items)
-
-    def update_root_container_body(self):
-        # Replace the root MenuContainer body with the new split.
-        self.application.root_container.container.content.children[
-            1] = self.create_root_container()
-
-    def _get_active_window_list_and_pane(self):
-        active_pane = None
-        active_window_list = None
-        for window_list in self.window_lists:
-            active_pane = window_list.get_current_active_pane()
-            if active_pane:
-                active_window_list = window_list
-                break
-        return active_window_list, active_pane
-
-    def window_list_index(self, window_list: WindowList) -> Optional[int]:
-        index = None
-        try:
-            index = self.window_lists.index(window_list)
-        except ValueError:
-            # Ignore ValueError which can be raised by the self.window_lists
-            # deque if the window_list can't be found.
-            pass
-        return index
-
-    def run_action_on_active_pane(self, function_name):
-        _active_window_list, active_pane = (
-            self._get_active_window_list_and_pane())
-        if not hasattr(active_pane, function_name):
-            return
-        method_to_call = getattr(active_pane, function_name)
-        method_to_call()
-        return
-
-    def focus_previous_pane(self) -> None:
-        """Focus on the previous visible window pane or tab."""
-        self.focus_next_pane(reverse_order=True)
-
-    def focus_next_pane(self, reverse_order=False) -> None:
-        """Focus on the next visible window pane or tab."""
-        active_window_list, active_pane = (
-            self._get_active_window_list_and_pane())
-        if active_window_list is None:
-            return
-
-        # Total count of window lists and panes
-        window_list_count = len(self.window_lists)
-        pane_count = len(active_window_list.active_panes)
-
-        # Get currently focused indices
-        active_window_list_index = self.window_list_index(active_window_list)
-        if active_window_list_index is None:
-            return
-        active_pane_index = active_window_list.pane_index(active_pane)
-
-        increment = -1 if reverse_order else 1
-        # Assume we can switch to the next pane in the current window_list
-        next_pane_index = active_pane_index + increment
-
-        # Case 1: next_pane_index does not exist in this window list.
-        # Action: Switch to the first pane of the next window list.
-        if next_pane_index >= pane_count or next_pane_index < 0:
-            # Get the next window_list
-            next_window_list_index = ((active_window_list_index + increment) %
-                                      window_list_count)
-            next_window_list = self.window_lists[next_window_list_index]
-
-            # If tabbed window mode is enabled, switch to the first tab.
-            if next_window_list.display_mode == DisplayMode.TABBED:
-                if reverse_order:
-                    next_window_list.switch_to_tab(
-                        len(next_window_list.active_panes) - 1)
-                else:
-                    next_window_list.switch_to_tab(0)
-                return
-
-            # Otherwise switch to the first visible window pane.
-            pane_list = next_window_list.active_panes
-            if reverse_order:
-                pane_list = reversed(pane_list)
-            for pane in pane_list:
-                if pane.show_pane:
-                    self.application.focus_on_container(pane)
-                    return
-
-        # Case 2: next_pane_index does exist and display mode is tabs.
-        # Action: Switch to the next tab of the current window list.
-        if active_window_list.display_mode == DisplayMode.TABBED:
-            active_window_list.switch_to_tab(next_pane_index)
-            return
-
-        # Case 3: next_pane_index does exist and display mode is stacked.
-        # Action: Switch to the next visible window pane.
-        index_range = range(1, pane_count)
-        if reverse_order:
-            index_range = range(pane_count - 1, 0, -1)
-        for i in index_range:
-            next_pane_index = (active_pane_index + i) % pane_count
-            next_pane = active_window_list.active_panes[next_pane_index]
-            if next_pane.show_pane:
-                self.application.focus_on_container(next_pane)
-                return
-        return
-
-    def move_pane_left(self):
-        active_window_list, active_pane = (
-            self._get_active_window_list_and_pane())
-        if not active_window_list:
-            return
-
-        window_list_index = self.window_list_index(active_window_list)
-        # Move left should pick the previous window_list
-        target_window_list_index = window_list_index - 1
-
-        # Check if a new WindowList should be created on the left
-        if target_window_list_index == -1:
-            # Add the new WindowList
-            target_window_list = WindowList(self)
-            self.window_lists.appendleft(target_window_list)
-            self.reset_split_sizes()
-            # New index is 0
-            target_window_list_index = 0
-
-        # Get the destination window_list
-        target_window_list = self.window_lists[target_window_list_index]
-
-        # Move the pane
-        active_window_list.remove_pane_no_checks(active_pane)
-        target_window_list.add_pane(active_pane, add_at_beginning=True)
-        target_window_list.reset_pane_sizes()
-        self.delete_empty_window_lists()
-
-    def move_pane_right(self):
-        active_window_list, active_pane = (
-            self._get_active_window_list_and_pane())
-        if not active_window_list:
-            return
-
-        window_list_index = self.window_list_index(active_window_list)
-        # Move right should pick the next window_list
-        target_window_list_index = window_list_index + 1
-
-        # Check if a new WindowList should be created
-        if target_window_list_index == len(self.window_lists):
-            # Add a new WindowList
-            target_window_list = WindowList(self)
-            self.window_lists.append(target_window_list)
-            self.reset_split_sizes()
-
-        # Get the destination window_list
-        target_window_list = self.window_lists[target_window_list_index]
-
-        # Move the pane
-        active_window_list.remove_pane_no_checks(active_pane)
-        target_window_list.add_pane(active_pane, add_at_beginning=True)
-        target_window_list.reset_pane_sizes()
-        self.delete_empty_window_lists()
-
-    def move_pane_up(self):
-        active_window_list, _active_pane = (
-            self._get_active_window_list_and_pane())
-        if not active_window_list:
-            return
-
-        active_window_list.move_pane_up()
-
-    def move_pane_down(self):
-        active_window_list, _active_pane = (
-            self._get_active_window_list_and_pane())
-        if not active_window_list:
-            return
-
-        active_window_list.move_pane_down()
-
-    def shrink_pane(self):
-        active_window_list, _active_pane = (
-            self._get_active_window_list_and_pane())
-        if not active_window_list:
-            return
-
-        active_window_list.shrink_pane()
-
-    def enlarge_pane(self):
-        active_window_list, _active_pane = (
-            self._get_active_window_list_and_pane())
-        if not active_window_list:
-            return
-
-        active_window_list.enlarge_pane()
-
-    def shrink_split(self):
-        if len(self.window_lists) < 2:
-            return
-
-        active_window_list, _active_pane = (
-            self._get_active_window_list_and_pane())
-        if not active_window_list:
-            return
-
-        self.adjust_split_size(active_window_list, -_WINDOW_SPLIT_ADJUST)
-
-    def enlarge_split(self):
-        active_window_list, _active_pane = (
-            self._get_active_window_list_and_pane())
-        if not active_window_list:
-            return
-
-        self.adjust_split_size(active_window_list, _WINDOW_SPLIT_ADJUST)
-
-    def balance_window_sizes(self):
-        """Reset all splits and pane sizes."""
-        self.reset_pane_sizes()
-        self.reset_split_sizes()
-
-    def reset_split_sizes(self):
-        """Reset all active pane width and height to defaults"""
-        available_height = self.current_window_manager_height
-        available_width = self.current_window_manager_width
-        old_heights = [w.height.preferred for w in self.window_lists]
-        old_widths = [w.width.preferred for w in self.window_lists]
-        new_heights = [int(available_height / len(old_heights))
-                       ] * len(old_heights)
-        new_widths = [int(available_width / len(old_widths))] * len(old_widths)
-
-        self._set_window_list_sizes(new_heights, new_widths)
-
-    def _get_next_window_list_for_resizing(
-            self, window_list: WindowList) -> Optional[WindowList]:
-        window_list_index = self.window_list_index(window_list)
-        if window_list_index is None:
-            return None
-
-        next_window_list_index = ((window_list_index + 1) %
-                                  len(self.window_lists))
-
-        # Use the previous window if we are on the last split
-        if window_list_index == len(self.window_lists) - 1:
-            next_window_list_index = window_list_index - 1
-
-        next_window_list = self.window_lists[next_window_list_index]
-        return next_window_list
-
-    def adjust_split_size(self,
-                          window_list: WindowList,
-                          diff: int = _WINDOW_SPLIT_ADJUST) -> None:
-        """Increase or decrease a given window_list's vertical split width."""
-        # No need to resize if only one split.
-        if len(self.window_lists) < 2:
-            return
-
-        # Get the next split to subtract from.
-        next_window_list = self._get_next_window_list_for_resizing(window_list)
-        if not next_window_list:
-            return
-
-        if self.vertical_window_list_spliting():
-            # Get current width
-            old_value = window_list.width.preferred
-            next_old_value = next_window_list.width.preferred  # type: ignore
-        else:
-            # Get current height
-            old_value = window_list.height.preferred
-            next_old_value = next_window_list.height.preferred  # type: ignore
-
-        # Add to the current split
-        new_value = old_value + diff
-        if new_value <= 0:
-            new_value = old_value
-
-        # Subtract from the next split
-        next_new_value = next_old_value - diff
-        if next_new_value <= 0:
-            next_new_value = next_old_value
-
-        # If new height is too small or no change, make no adjustments.
-        if new_value < 3 or next_new_value < 3 or old_value == new_value:
-            return
-
-        if self.vertical_window_list_spliting():
-            # Set new width
-            window_list.width.preferred = new_value
-            next_window_list.width.preferred = next_new_value  # type: ignore
-        else:
-            # Set new height
-            window_list.height.preferred = new_value
-            next_window_list.height.preferred = next_new_value  # type: ignore
-            window_list.rebalance_window_heights()
-            next_window_list.rebalance_window_heights()
-
-    def toggle_pane(self, pane):
-        """Toggle a pane on or off."""
-        window_list, _pane_index = (
-            self._find_window_list_and_pane_index(pane))
-
-        # Don't hide the window if tabbed mode is enabled. Switching to a
-        # separate tab is preffered.
-        if window_list.display_mode == DisplayMode.TABBED:
-            return
-        pane.show_pane = not pane.show_pane
-        self.update_root_container_body()
-        self.application.update_menu_items()
-
-        # Set focus to the top level menu. This has the effect of keeping the
-        # menu open if it's already open.
-        self.application.focus_main_menu()
-
-    def focus_first_visible_pane(self):
-        """Focus on the first visible container."""
-        for pane in self.active_panes():
-            if pane.show_pane:
-                self.application.application.layout.focus(pane)
-                break
-
-    def check_for_all_hidden_panes_and_unhide(self) -> None:
-        """Scan for window_lists containing only hidden panes."""
-        for window_list in self.window_lists:
-            all_hidden = all(not pane.show_pane
-                             for pane in window_list.active_panes)
-            if all_hidden:
-                # Unhide the first pane
-                self.toggle_pane(window_list.active_panes[0])
-
-    def add_pane_no_checks(self, pane: Any):
-        self.window_lists[0].add_pane_no_checks(pane)
-
-    def add_pane(self, pane: Any):
-        self.window_lists[0].add_pane(pane, add_at_beginning=True)
-
-    def first_window_list(self):
-        return self.window_lists[0]
-
-    def active_panes(self):
-        """Return all active panes from all window lists."""
-        return chain.from_iterable(
-            map(operator.attrgetter('active_panes'), self.window_lists))
-
-    def start_resize_pane(self, pane):
-        window_list, pane_index = self._find_window_list_and_pane_index(pane)
-        window_list.start_resize(pane, pane_index)
-
-    def mouse_resize(self, xpos, ypos):
-        if self.resize_target_window_list_index is None:
-            return
-        target_window_list = self.window_lists[
-            self.resize_target_window_list_index]
-
-        diff = ypos - self.resize_current_row
-        if self.vertical_window_list_spliting():
-            diff = xpos - self.resize_current_column
-        if diff == 0:
-            return
-
-        self.adjust_split_size(target_window_list, diff)
-        self._resize_update_current_row_column()
-        self.application.redraw_ui()
-
-    def mouse_handler(self, mouse_event: MouseEvent):
-        """MouseHandler used when resize_mode == True."""
-        mouse_position = mouse_event.position
-
-        if (mouse_event.event_type == MouseEventType.MOUSE_MOVE
-                and mouse_event.button == MouseButton.LEFT):
-            self.mouse_resize(mouse_position.x, mouse_position.y)
-        elif mouse_event.event_type == MouseEventType.MOUSE_UP:
-            self.stop_resize()
-            # Mouse event handled, return None.
-            return None
-        else:
-            self.stop_resize()
-
-        # Mouse event not handled, return NotImplemented.
-        return NotImplemented
-
-    def _calculate_actual_widths(self) -> List[int]:
-        widths = [w.width.preferred for w in self.window_lists]
-
-        available_width = self.current_window_manager_width
-        # Subtract 1 for each separator
-        available_width -= len(self.window_lists) - 1
-        remaining_rows = available_width - sum(widths)
-        window_list_index = 0
-        # Distribute remaining unaccounted columns to each window in turn.
-        while remaining_rows > 0:
-            widths[window_list_index] += 1
-            remaining_rows -= 1
-            window_list_index = (window_list_index + 1) % len(widths)
-
-        return widths
-
-    def _calculate_actual_heights(self) -> List[int]:
-        heights = [w.height.preferred for w in self.window_lists]
-
-        available_height = self.current_window_manager_height
-        # Subtract 1 for each vertical separator
-        available_height -= len(self.window_lists) - 1
-        remaining_rows = available_height - sum(heights)
-        window_list_index = 0
-        # Distribute remaining unaccounted columns to each window in turn.
-        while remaining_rows > 0:
-            heights[window_list_index] += 1
-            remaining_rows -= 1
-            window_list_index = (window_list_index + 1) % len(heights)
-
-        return heights
-
-    def _resize_update_current_row_column(self) -> None:
-        if self.resize_target_window_list_index is None:
-            return
-
-        widths = self._calculate_actual_widths()
-        heights = self._calculate_actual_heights()
-
-        start_column = 0
-        start_row = 0
-
-        # Find the starting column
-        for i in range(self.resize_target_window_list_index + 1):
-            # If we are past the target window_list, exit the loop.
-            if i > self.resize_target_window_list_index:
-                break
-            start_column += widths[i]
-            start_row += heights[i]
-            if i < self.resize_target_window_list_index - 1:
-                start_column += 1
-                start_row += 1
-
-        self.resize_current_column = start_column
-        self.resize_current_row = start_row
-
-    def start_resize(self, window_list):
-        # Check the target window_list isn't the last one.
-        if window_list == self.window_lists[-1]:
-            return
-
-        list_index = self.window_list_index(window_list)
-        if list_index is None:
-            return
-
-        self.resize_mode = True
-        self.resize_target_window_list = window_list
-        self.resize_target_window_list_index = list_index
-        self._resize_update_current_row_column()
-
-    def stop_resize(self):
-        self.resize_mode = False
-        self.resize_target_window_list = None
-        self.resize_target_window_list_index = None
-        self.resize_current_row = 0
-        self.resize_current_column = 0
-
-    def _find_window_list_and_pane_index(self, pane: Any):
-        pane_index = None
-        parent_window_list = None
-        for window_list in self.window_lists:
-            pane_index = window_list.pane_index(pane)
-            if pane_index is not None:
-                parent_window_list = window_list
-                break
-        return parent_window_list, pane_index
-
-    def remove_pane(self, existing_pane: Any):
-        window_list, _pane_index = (
-            self._find_window_list_and_pane_index(existing_pane))
-        if window_list:
-            window_list.remove_pane(existing_pane)
-            # Reset focus if this list is empty
-            if len(window_list.active_panes) == 0:
-                self.application.focus_main_menu()
-
-    def reset_pane_sizes(self):
-        for window_list in self.window_lists:
-            window_list.reset_pane_sizes()
-
-    def _remove_panes_from_layout(
-            self, pane_titles: Iterable[str]) -> Dict[str, Any]:
-        # Gather pane objects and remove them from the window layout.
-        collected_panes = {}
-
-        for window_list in self.window_lists:
-            # Make a copy of active_panes to prevent mutating the while
-            # iterating.
-            for pane in copy.copy(window_list.active_panes):
-                if pane.pane_title() in pane_titles:
-                    collected_panes[pane.pane_title()] = (
-                        window_list.remove_pane_no_checks(pane))
-        return collected_panes
-
-    def _set_pane_options(self, pane, options: dict) -> None:  # pylint: disable=no-self-use
-        if options.get('hidden', False):
-            # Hide this pane
-            pane.show_pane = False
-        if options.get('height', False):
-            # Apply new height
-            new_height = options['height']
-            assert isinstance(new_height, int)
-            pane.height.preferred = new_height
-
-    def _set_window_list_display_modes(self, prefs: ConsolePrefs) -> None:
-        # Set column display modes
-        for column_index, column_type in enumerate(prefs.window_column_modes):
-            mode = DisplayMode.STACK
-            if 'tabbed' in column_type:
-                mode = DisplayMode.TABBED
-            self.window_lists[column_index].set_display_mode(mode)
-
-    def _create_new_log_pane_with_loggers(self, window_title, window_options,
-                                          existing_pane_titles) -> LogPane:
-        if 'loggers' not in window_options:
-            error_unknown_window(window_title, existing_pane_titles)
-
-        new_pane = LogPane(application=self.application,
-                           pane_title=window_title)
-        # Add logger handlers
-        for logger_name, logger_options in window_options.get('loggers',
-                                                              {}).items():
-
-            log_level_name = logger_options.get('level', None)
-            new_pane.add_log_handler(logger_name, level_name=log_level_name)
-        return new_pane
-
-    # TODO(tonymd): Split this large function up.
-    def apply_config(self, prefs: ConsolePrefs) -> None:
-        """Apply window configuration from loaded ConsolePrefs."""
-        if not prefs.windows:
-            return
-
-        unique_titles = prefs.unique_window_titles
-        collected_panes = self._remove_panes_from_layout(unique_titles)
-        existing_pane_titles = [
-            p.pane_title() for p in collected_panes.values()
-            if isinstance(p, LogPane)
-        ]
-
-        # Keep track of original non-duplicated pane titles
-        already_added_panes = []
-
-        for column_index, column in enumerate(prefs.windows.items()):  # pylint: disable=too-many-nested-blocks
-            _column_type, windows = column
-            # Add a new window_list if needed
-            if column_index >= len(self.window_lists):
-                self.window_lists.append(WindowList(self))
-
-            # Set column display mode to stacked by default.
-            self.window_lists[column_index].display_mode = DisplayMode.STACK
-
-            # Add windows to the this column (window_list)
-            for window_title, window_dict in windows.items():
-                window_options = window_dict if window_dict else {}
-                new_pane = None
-                desired_window_title = window_title
-                # Check for duplicate_of: Title value
-                window_title = window_options.get('duplicate_of', window_title)
-
-                # Check if this pane is brand new, ready to be added, or should
-                # be duplicated.
-                if (window_title not in already_added_panes
-                        and window_title not in collected_panes):
-                    # New pane entirely
-                    new_pane = self._create_new_log_pane_with_loggers(
-                        window_title, window_options, existing_pane_titles)
-
-                elif window_title not in already_added_panes:
-                    # First time adding this pane
-                    already_added_panes.append(window_title)
-                    new_pane = collected_panes[window_title]
-
-                elif window_title in collected_panes:
-                    # Pane added once, duplicate it
-                    new_pane = collected_panes[window_title].create_duplicate()
-                    # Rename this duplicate pane
-                    assert isinstance(new_pane, LogPane)
-                    new_pane.set_pane_title(desired_window_title)
-
-                if new_pane:
-                    # Set window size and visibility
-                    self._set_pane_options(new_pane, window_options)
-                    # Add the new pane
-                    self.window_lists[column_index].add_pane_no_checks(
-                        new_pane)
-                    # Apply log filters
-                    if isinstance(new_pane, LogPane):
-                        new_pane.apply_filters_from_config(window_options)
-
-        # Update column display modes.
-        self._set_window_list_display_modes(prefs)
-        # Check for columns where all panes are hidden and unhide at least one.
-        self.check_for_all_hidden_panes_and_unhide()
-
-        # Update prompt_toolkit containers.
-        self.update_root_container_body()
-        self.application.update_menu_items()
-
-        # Focus on the first visible pane.
-        self.focus_first_visible_pane()
-
-    def create_window_menu(self):
-        """Build the [Window] menu for the current set of window lists."""
-        root_menu_items = []
-        for window_list_index, window_list in enumerate(self.window_lists):
-            menu_items = []
-            menu_items.append(
-                MenuItem(
-                    'Column {index} View Modes'.format(
-                        index=window_list_index + 1),
-                    children=[
-                        MenuItem(
-                            '{check} {display_mode} Windows'.format(
-                                display_mode=display_mode.value,
-                                check=pw_console.widgets.checkbox.
-                                to_checkbox_text(
-                                    window_list.display_mode == display_mode,
-                                    end='',
-                                )),
-                            handler=functools.partial(
-                                window_list.set_display_mode, display_mode),
-                        ) for display_mode in DisplayMode
-                    ],
-                ))
-            menu_items.extend(
-                MenuItem(
-                    '{index}: {title}'.format(
-                        index=pane_index + 1,
-                        title=pane.menu_title(),
-                    ),
-                    children=[
-                        MenuItem(
-                            '{check} Show/Hide Window'.format(
-                                check=pw_console.widgets.checkbox.
-                                to_checkbox_text(pane.show_pane, end='')),
-                            handler=functools.partial(self.toggle_pane, pane),
-                        ),
-                    ] + [
-                        MenuItem(text,
-                                 handler=functools.partial(
-                                     self.application.run_pane_menu_option,
-                                     handler))
-                        for text, handler in pane.get_all_menu_options()
-                    ],
-                ) for pane_index, pane in enumerate(window_list.active_panes))
-            if window_list_index + 1 < len(self.window_lists):
-                menu_items.append(MenuItem('-'))
-            root_menu_items.extend(menu_items)
-
-        menu = MenuItem(
-            '[Windows]',
-            children=root_menu_items,
-        )
-
-        return [menu]
diff --git a/pw_console/py/pw_console/yaml_config_loader_mixin.py b/pw_console/py/pw_console/yaml_config_loader_mixin.py
deleted file mode 100644
index ff009a0..0000000
--- a/pw_console/py/pw_console/yaml_config_loader_mixin.py
+++ /dev/null
@@ -1,154 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Yaml config file loader mixin."""
-
-import os
-import logging
-from pathlib import Path
-from typing import Any, Dict, Optional, Union
-
-import yaml
-
-_LOG = logging.getLogger(__package__)
-
-
-class MissingConfigTitle(Exception):
-    """Exception for when an existing YAML file is missing config_title."""
-
-
-class YamlConfigLoaderMixin:
-    """Yaml Config file loader mixin.
-
-    Use this mixin to load yaml file settings and save them into
-    ``self._config``. For example:
-
-    ::
-
-       class ConsolePrefs(YamlConfigLoaderMixin):
-           def __init__(
-               self.config_init(
-                   config_section_title='pw_console',
-                   project_file=Path('project_file.yaml'),
-                   project_user_file=Path('project_user_file.yaml'),
-                   user_file=Path('~/user_file.yaml'),
-                   default_config={},
-                   environment_var='PW_CONSOLE_CONFIG_FILE',
-               )
-
-    """
-    def config_init(
-        self,
-        config_section_title: str,
-        project_file: Union[Path, bool] = None,
-        project_user_file: Union[Path, bool] = None,
-        user_file: Union[Path, bool] = None,
-        default_config: Optional[Dict[Any, Any]] = None,
-        environment_var: Optional[str] = None,
-    ) -> None:
-        """Call this to load YAML config files in order of precedence.
-
-        The following files are loaded in this order:
-        1. project_file
-        2. project_user_file
-        3. user_file
-
-        Lastly, if a valid file path is specified at
-        ``os.environ[environment_var]`` then load that file overriding all
-        config options.
-
-        Args:
-            config_section_title: String name of this config section. For
-                example: ``pw_console`` or ``pw_watch``. In the YAML file this
-                is represented by a ``config_title`` key.
-
-                ::
-
-                   ---
-                   config_title: pw_console
-
-            project_file: Project level config file. This is intended to be a
-                file living somewhere under a project folder and is checked into
-                the repo. It serves as a base config all developers can inherit
-                from.
-            project_user_file: User's personal config file for a specific
-                project. This can be a file that lives in a project folder that
-                is git-ignored and not checked into the repo.
-            user_file: A global user based config file. This is typically a file
-                in the users home directory and settings here apply to all
-                projects.
-            default_config: A Python dict representing the base default
-                config. This dict will be applied as a starting point before
-                loading any yaml files.
-            environment_var: The name of an environment variable to check for a
-                config file. If a config file exists there it will be loaded on
-                top of the default_config ignoring project and user files.
-        """
-
-        self._config_section_title: str = config_section_title
-        self.default_config = default_config if default_config else {}
-        self.reset_config()
-
-        if project_file and isinstance(project_file, Path):
-            self.project_file = Path(
-                os.path.expandvars(str(project_file.expanduser())))
-            self.load_config_file(self.project_file)
-
-        if project_user_file and isinstance(project_user_file, Path):
-            self.project_user_file = Path(
-                os.path.expandvars(str(project_user_file.expanduser())))
-            self.load_config_file(self.project_user_file)
-
-        if user_file and isinstance(user_file, Path):
-            self.user_file = Path(
-                os.path.expandvars(str(user_file.expanduser())))
-            self.load_config_file(self.user_file)
-
-        # Check for a config file specified by an environment variable.
-        if environment_var is None:
-            return
-        environment_config = os.environ.get(environment_var, None)
-        if environment_config:
-            env_file_path = Path(environment_config)
-            if not env_file_path.is_file():
-                raise FileNotFoundError(
-                    f'Cannot load config file: {env_file_path}')
-            self.reset_config()
-            self.load_config_file(env_file_path)
-
-    def _update_config(self, cfg: Dict[Any, Any]) -> None:
-        if cfg is None:
-            cfg = {}
-        self._config.update(cfg)
-
-    def reset_config(self) -> None:
-        self._config: Dict[Any, Any] = {}
-        self._update_config(self.default_config)
-
-    def load_config_file(self, file_path: Path) -> None:
-        if not file_path.is_file():
-            return
-
-        cfgs = yaml.safe_load_all(file_path.read_text())
-
-        for cfg in cfgs:
-            if self._config_section_title in cfg:
-                self._update_config(cfg[self._config_section_title])
-
-            elif cfg.get('config_title', False) == self._config_section_title:
-                self._update_config(cfg)
-            else:
-                raise MissingConfigTitle(
-                    '\n\nThe YAML config file "{}" is missing the expected '
-                    '"config_title: {}" setting.'.format(
-                        str(file_path), self._config_section_title))
diff --git a/pw_console/py/pyproject.toml b/pw_console/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/pw_console/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/pw_console/py/repl_pane_test.py b/pw_console/py/repl_pane_test.py
deleted file mode 100644
index 86f41ce..0000000
--- a/pw_console/py/repl_pane_test.py
+++ /dev/null
@@ -1,248 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests for pw_console.console_app"""
-
-import asyncio
-import builtins
-import inspect
-import io
-import sys
-import threading
-import unittest
-from unittest.mock import MagicMock, call
-
-from prompt_toolkit.application import create_app_session
-from prompt_toolkit.output import (
-    ColorDepth,
-    # inclusive-language: ignore
-    DummyOutput as FakeOutput,
-)
-
-from pw_console.console_app import ConsoleApp
-from pw_console.console_prefs import ConsolePrefs
-from pw_console.repl_pane import ReplPane
-from pw_console.pw_ptpython_repl import PwPtPythonRepl
-
-_PYTHON_3_8 = sys.version_info >= (
-    3,
-    8,
-)
-
-if _PYTHON_3_8:
-    from unittest import IsolatedAsyncioTestCase  # type: ignore # pylint: disable=no-name-in-module
-
-    class TestReplPane(IsolatedAsyncioTestCase):
-        """Tests for ReplPane."""
-        def setUp(self):  # pylint: disable=invalid-name
-            self.maxDiff = None  # pylint: disable=invalid-name
-
-        def test_repl_code_return_values(self) -> None:
-            """Test stdout, return values, and exceptions can be returned from
-            running user repl code."""
-            app = MagicMock()
-
-            global_vars = {
-                '__name__': '__main__',
-                '__package__': None,
-                '__doc__': None,
-                '__builtins__': builtins,
-            }
-
-            pw_ptpython_repl = PwPtPythonRepl(
-                get_globals=lambda: global_vars,
-                get_locals=lambda: global_vars,
-                color_depth=ColorDepth.DEPTH_8_BIT)
-            repl_pane = ReplPane(
-                application=app,
-                python_repl=pw_ptpython_repl,
-            )
-            # Check pw_ptpython_repl has a reference to the parent repl_pane.
-            self.assertEqual(repl_pane, pw_ptpython_repl.repl_pane)
-
-            # Define a function, should return nothing.
-            code = inspect.cleandoc("""
-                def run():
-                    print('The answer is ', end='')
-                    return 1+1+4+16+20
-            """)
-            temp_stdout = io.StringIO()
-            temp_stderr = io.StringIO()
-            # pylint: disable=protected-access
-            result = asyncio.run(
-                pw_ptpython_repl._run_user_code(code, temp_stdout,
-                                                temp_stderr))
-            self.assertEqual(result, {
-                'stdout': '',
-                'stderr': '',
-                'result': None
-            })
-
-            temp_stdout = io.StringIO()
-            temp_stderr = io.StringIO()
-            # Check stdout and return value
-            result = asyncio.run(
-                pw_ptpython_repl._run_user_code('run()', temp_stdout,
-                                                temp_stderr))
-            self.assertEqual(result, {
-                'stdout': 'The answer is ',
-                'stderr': '',
-                'result': 42
-            })
-
-            temp_stdout = io.StringIO()
-            temp_stderr = io.StringIO()
-            # Check for repl exception
-            result = asyncio.run(
-                pw_ptpython_repl._run_user_code('return "blah"', temp_stdout,
-                                                temp_stderr))
-            self.assertIn("SyntaxError: 'return' outside function",
-                          pw_ptpython_repl._last_exception)  # type: ignore
-
-        async def test_user_thread(self) -> None:
-            """Test user code thread."""
-
-            with create_app_session(output=FakeOutput()):
-                # Setup Mocks
-                app = ConsoleApp(color_depth=ColorDepth.DEPTH_8_BIT,
-                                 prefs=ConsolePrefs(project_file=False,
-                                                    project_user_file=False,
-                                                    user_file=False))
-
-                app.start_user_code_thread()
-
-                pw_ptpython_repl = app.pw_ptpython_repl
-                repl_pane = app.repl_pane
-
-                # Mock update_output_buffer to track number of update calls
-                repl_pane.update_output_buffer = MagicMock(
-                    wraps=repl_pane.update_output_buffer)
-
-                # Mock complete callback
-                pw_ptpython_repl.user_code_complete_callback = MagicMock(
-                    wraps=pw_ptpython_repl.user_code_complete_callback)
-
-                # Repl done flag for tests
-                user_code_done = threading.Event()
-
-                # Run some code
-                code = inspect.cleandoc("""
-                    import time
-                    def run():
-                        for i in range(2):
-                            time.sleep(0.5)
-                            print(i)
-                        print('The answer is ', end='')
-                        return 1+1+4+16+20
-                """)
-                input_buffer = MagicMock(text=code)
-                pw_ptpython_repl._accept_handler(input_buffer)  # pylint: disable=protected-access
-
-                # Get last executed code object.
-                user_code1 = repl_pane.executed_code[-1]
-                # Wait for repl code to finish.
-                user_code1.future.add_done_callback(
-                    lambda future: user_code_done.set())
-                # Wait for stdout monitoring to complete.
-                if user_code1.stdout_check_task:
-                    await user_code1.stdout_check_task
-                # Wait for test done callback.
-                user_code_done.wait(timeout=3)
-
-                # Check user_code1 results
-                # NOTE: Avoid using assert_has_calls. Thread timing can make the
-                # test flaky.
-                expected_calls = [
-                    # Initial exec start
-                    call('pw_ptpython_repl._accept_handler'),
-                    # Code finishes
-                    call('repl_pane.append_result_to_executed_code'),
-                    # Complete callback
-                    call('pw_ptpython_repl.user_code_complete_callback'),
-                ]
-                for expected_call in expected_calls:
-                    self.assertIn(expected_call,
-                                  repl_pane.update_output_buffer.mock_calls)
-
-                pw_ptpython_repl.user_code_complete_callback.assert_called_once(
-                )
-
-                self.assertIsNotNone(user_code1)
-                self.assertTrue(user_code1.future.done())
-                self.assertEqual(user_code1.input, code)
-                self.assertEqual(user_code1.output, None)
-                # stdout / stderr may be '' or None
-                self.assertFalse(user_code1.stdout)
-                self.assertFalse(user_code1.stderr)
-
-                # Reset mocks
-                user_code_done.clear()
-                pw_ptpython_repl.user_code_complete_callback.reset_mock()
-                repl_pane.update_output_buffer.reset_mock()
-
-                # Run some code
-                input_buffer = MagicMock(text='run()')
-                pw_ptpython_repl._accept_handler(input_buffer)  # pylint: disable=protected-access
-
-                # Get last executed code object.
-                user_code2 = repl_pane.executed_code[-1]
-                # Wait for repl code to finish.
-                user_code2.future.add_done_callback(
-                    lambda future: user_code_done.set())
-                # Wait for stdout monitoring to complete.
-                if user_code2.stdout_check_task:
-                    await user_code2.stdout_check_task
-                # Wait for test done callback.
-                user_code_done.wait(timeout=3)
-
-                # Check user_code2 results
-                # NOTE: Avoid using assert_has_calls. Thread timing can make the
-                # test flaky.
-                expected_calls = [
-                    # Initial exec start
-                    call('pw_ptpython_repl._accept_handler'),
-                    # Periodic checks, should be a total of 4:
-                    #   Code should take 1.0 second to run.
-                    #   Periodic checks every 0.3 seconds
-                    #   1.0 / 0.3 = 3.33 (4) checks
-                    call('repl_pane.periodic_check'),
-                    call('repl_pane.periodic_check'),
-                    call('repl_pane.periodic_check'),
-                    # Code finishes
-                    call('repl_pane.append_result_to_executed_code'),
-                    # Complete callback
-                    call('pw_ptpython_repl.user_code_complete_callback'),
-                    # Final periodic check
-                    call('repl_pane.periodic_check'),
-                ]
-                for expected_call in expected_calls:
-                    self.assertIn(expected_call,
-                                  repl_pane.update_output_buffer.mock_calls)
-
-                pw_ptpython_repl.user_code_complete_callback.assert_called_once(
-                )
-                self.assertIsNotNone(user_code2)
-                self.assertTrue(user_code2.future.done())
-                self.assertEqual(user_code2.input, 'run()')
-                self.assertEqual(user_code2.output, '42')
-                self.assertEqual(user_code2.stdout, '0\n1\nThe answer is ')
-                self.assertFalse(user_code2.stderr)
-
-                # Reset mocks
-                user_code_done.clear()
-                pw_ptpython_repl.user_code_complete_callback.reset_mock()
-                repl_pane.update_output_buffer.reset_mock()
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_console/py/setup.cfg b/pw_console/py/setup.cfg
deleted file mode 100644
index 8d3ce30..0000000
--- a/pw_console/py/setup.cfg
+++ /dev/null
@@ -1,50 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pw_console
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Pigweed interactive console
-
-[options]
-packages = find:
-zip_safe = False
-install_requires =
-    ipython
-    jinja2
-    prompt_toolkit>=3.0.26
-    ptpython>=3.0.20
-    pw_cli
-    pw_tokenizer
-    pygments
-    pygments-style-dracula
-    pygments-style-tomorrow
-    pyperclip
-    pyyaml
-    types-pygments
-    types-PyYAML
-
-[options.entry_points]
-console_scripts = pw-console = pw_console.__main__:main
-pygments.styles =
-    pigweed-code = pw_console.pigweed_code_style:PigweedCodeStyle
-    pigweed-code-light = pw_console.pigweed_code_style:PigweedCodeLightStyle
-
-[options.package_data]
-pw_console =
-    docs/user_guide.rst
-    py.typed
-    templates/keybind_list.jinja
-    templates/repl_output.jinja
diff --git a/pw_console/py/setup.py b/pw_console/py/setup.py
deleted file mode 100644
index 7308b8c..0000000
--- a/pw_console/py/setup.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""pw_console"""
-
-import setuptools  # type: ignore
-
-setuptools.setup()  # Package definition in setup.cfg
diff --git a/pw_console/py/table_test.py b/pw_console/py/table_test.py
deleted file mode 100644
index d8896c9..0000000
--- a/pw_console/py/table_test.py
+++ /dev/null
@@ -1,246 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests for pw_console.text_formatting"""
-
-from datetime import datetime
-import logging
-import unittest
-
-from parameterized import parameterized  # type: ignore
-
-from pw_console.console_prefs import ConsolePrefs
-from pw_console.log_line import LogLine
-from pw_console.widgets.table import TableView
-
-_TIMESTAMP_FORMAT = '%Y%m%d %H:%M:%S'
-_TIMESTAMP_SAMPLE = datetime(2021, 6, 30, 16, 10, 37, 818901)
-_TIMESTAMP_SAMPLE_STRING = _TIMESTAMP_SAMPLE.strftime(_TIMESTAMP_FORMAT)
-
-_TABLE_PADDING = '  '
-_TABLE_PADDING_FRAGMENT = ('', _TABLE_PADDING)
-
-formatter = logging.Formatter(
-    '\x1b[30m\x1b[47m'
-    '%(asctime)s'
-    '\x1b[0m'
-    ' '
-    '\x1b[33m\x1b[1m'
-    '%(levelname)s'
-    '\x1b[0m'
-    ' '
-    '%(message)s', _TIMESTAMP_FORMAT)
-
-
-def make_log(**kwargs):
-    """Create a LogLine instance."""
-    # Construct a LogRecord
-    attributes = dict(name='testlogger',
-                      levelno=logging.INFO,
-                      levelname='INF',
-                      msg='[%s] %.3f %s',
-                      args=('MOD1', 3.14159, 'Real message here'),
-                      created=_TIMESTAMP_SAMPLE.timestamp(),
-                      filename='test.py',
-                      lineno=42,
-                      pathname='/home/user/test.py')
-    # Override any above attrs that are passed in.
-    attributes.update(kwargs)
-    # Create the record
-    record = logging.makeLogRecord(dict(attributes))
-    # Format
-    formatted_message = formatter.format(record)
-    log_line = LogLine(record=record,
-                       formatted_log=formatted_message,
-                       ansi_stripped_log='')
-    log_line.update_metadata()
-    return log_line
-
-
-class TestTableView(unittest.TestCase):
-    """Tests for rendering log lines into tables."""
-    def setUp(self):
-        # Show large diffs
-        self.maxDiff = None  # pylint: disable=invalid-name
-        self.prefs = ConsolePrefs(project_file=False,
-                                  project_user_file=False,
-                                  user_file=False)
-        self.prefs.reset_config()
-
-    @parameterized.expand([
-        (
-            'Correct column widths with all fields set',
-            [
-                make_log(
-                    args=('M1', 1.2345, 'Something happened'),
-                    extra_metadata_fields=dict(module='M1', anumber=12)),
-
-                make_log(
-                    args=('MD2', 567.5, 'Another cool event'),
-                    extra_metadata_fields=dict(module='MD2', anumber=123)),
-            ],
-            dict(module=len('MD2'), anumber=len('123')),
-        ),
-        (
-            'Missing metadata fields on some rows',
-            [
-                make_log(
-                    args=('M1', 54321.2, 'Something happened'),
-                    extra_metadata_fields=dict(module='M1', anumber=54321.2)),
-
-                make_log(
-                    args=('MOD2', 567.5, 'Another cool event'),
-                    extra_metadata_fields=dict(module='MOD2')),
-            ],
-            dict(module=len('MOD2'), anumber=len('54321.200')),
-        ),
-    ]) # yapf: disable
-    def test_column_widths(self, _name, logs, expected_widths) -> None:
-        """Test colum widths calculation."""
-        table = TableView(self.prefs)
-        for log in logs:
-            table.update_metadata_column_widths(log)
-            metadata_fields = {
-                k: v
-                for k, v in log.metadata.fields.items()
-                if k not in ['py_file', 'py_logger']
-            }
-            # update_metadata_column_widths shoulp populate self.metadata.fields
-            self.assertEqual(metadata_fields, log.record.extra_metadata_fields)
-        # Check expected column widths
-        results = {
-            k: v
-            for k, v in dict(table.column_widths).items()
-            if k not in ['time', 'level', 'py_file', 'py_logger']
-        }
-        self.assertCountEqual(expected_widths, results)
-
-    @parameterized.expand([
-        (
-            'Build header adding fields incrementally',
-            [
-                make_log(
-                    args=('MODULE2', 567.5, 'Another cool event'),
-                    extra_metadata_fields=dict(
-                        # timestamp missing
-                        module='MODULE2')),
-
-                make_log(
-                    args=('MODULE1', 54321.2, 'Something happened'),
-                    extra_metadata_fields=dict(
-                        # timestamp added in
-                        module='MODULE1', timestamp=54321.2)),
-            ],
-            [
-                [('bold', 'Time             '),
-                 _TABLE_PADDING_FRAGMENT,
-                 ('bold', 'Lev'),
-                 _TABLE_PADDING_FRAGMENT,
-                 ('bold', 'Module '),
-                 _TABLE_PADDING_FRAGMENT,
-                 ('bold', 'Message')],
-
-                [('bold', 'Time             '),
-                 _TABLE_PADDING_FRAGMENT,
-                 ('bold', 'Lev'),
-                 _TABLE_PADDING_FRAGMENT,
-                 ('bold', 'Module '),
-                 _TABLE_PADDING_FRAGMENT,
-                 # timestamp added in
-                 ('bold', 'Timestamp'),
-                 _TABLE_PADDING_FRAGMENT,
-                 ('bold', 'Message')],
-            ],
-        ),
-    ]) # yapf: disable
-    def test_formatted_header(self, _name, logs, expected_headers) -> None:
-        """Test colum widths calculation."""
-        table = TableView(self.prefs)
-
-        for log, header in zip(logs, expected_headers):
-            table.update_metadata_column_widths(log)
-            self.assertEqual(table.formatted_header(), header)
-
-    @parameterized.expand([
-        (
-            'Build rows adding fields incrementally',
-            [
-                make_log(
-                    args=('MODULE2', 567.5, 'Another cool event'),
-                    extra_metadata_fields=dict(
-                        # timestamp missing
-                        module='MODULE2',
-                        msg='Another cool event')),
-
-                make_log(
-                    args=('MODULE2', 567.5, 'Another cool event'),
-                    extra_metadata_fields=dict(
-                        # timestamp and msg missing
-                        module='MODULE2')),
-
-                make_log(
-                    args=('MODULE1', 54321.2, 'Something happened'),
-                    extra_metadata_fields=dict(
-                        # timestamp added in
-                        module='MODULE1', timestamp=54321.2,
-                        msg='Something happened')),
-            ],
-            [
-                [
-                    ('class:log-time', _TIMESTAMP_SAMPLE_STRING),
-                    _TABLE_PADDING_FRAGMENT,
-                    ('class:log-level-20', 'INF'),
-                    _TABLE_PADDING_FRAGMENT,
-                    ('class:log-table-column-0', 'MODULE2'),
-                    _TABLE_PADDING_FRAGMENT,
-                    ('', 'Another cool event'),
-                    ('', '\n')
-                ],
-
-                [
-                    ('class:log-time', _TIMESTAMP_SAMPLE_STRING),
-                    _TABLE_PADDING_FRAGMENT,
-                    ('class:log-level-20', 'INF'),
-                    _TABLE_PADDING_FRAGMENT,
-                    ('class:log-table-column-0', 'MODULE2'),
-                    _TABLE_PADDING_FRAGMENT,
-                    ('', '[MODULE2] 567.500 Another cool event'),
-                    ('', '\n')
-                ],
-
-                [
-                    ('class:log-time', _TIMESTAMP_SAMPLE_STRING),
-                    _TABLE_PADDING_FRAGMENT,
-                    ('class:log-level-20', 'INF'),
-                    _TABLE_PADDING_FRAGMENT,
-                    ('class:log-table-column-0', 'MODULE1'),
-                    _TABLE_PADDING_FRAGMENT,
-                    ('class:log-table-column-1', '54321.200'),
-                    _TABLE_PADDING_FRAGMENT,
-                    ('', 'Something happened'),
-                    ('', '\n')
-                ],
-            ],
-        ),
-    ]) # yapf: disable
-    def test_formatted_rows(self, _name, logs, expected_log_format) -> None:
-        """Test colum widths calculation."""
-        table = TableView(self.prefs)
-        # Check each row meets expected formats incrementally.
-        for log, formatted_log in zip(logs, expected_log_format):
-            table.update_metadata_column_widths(log)
-            self.assertEqual(formatted_log, table.formatted_row(log))
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_console/py/text_formatting_test.py b/pw_console/py/text_formatting_test.py
deleted file mode 100644
index 340cc58..0000000
--- a/pw_console/py/text_formatting_test.py
+++ /dev/null
@@ -1,265 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests for pw_console.text_formatting"""
-
-import unittest
-from parameterized import parameterized  # type: ignore
-
-from prompt_toolkit.formatted_text import ANSI
-
-from pw_console.text_formatting import (
-    get_line_height,
-    insert_linebreaks,
-    split_lines,
-)
-
-
-class TestTextFormatting(unittest.TestCase):
-    """Tests for manipulating prompt_toolkit formatted text tuples."""
-    def setUp(self):
-        self.maxDiff = None  # pylint: disable=invalid-name
-
-    @parameterized.expand([
-        (
-            'with short prefix height 2',
-            len('LINE that should be wrapped'),  # text_width
-            len('|                |'),  # screen_width
-            len('--->'),  # prefix_width
-            (   'LINE that should b\n'
-                '--->e wrapped     \n').count('\n'),  # expected_height
-            len(             '_____'),  # expected_trailing_characters
-        ),
-        (
-            'with short prefix height 3',
-            len('LINE that should be wrapped three times.'),  # text_width
-            len('|                |'),  # screen_width
-            len('--->'),  # prefix_width
-            (   'LINE that should b\n'
-                '--->e wrapped thre\n'
-                '--->e times.      \n').count('\n'),  # expected_height
-            len(            '______'),  # expected_trailing_characters
-        ),
-        (
-            'with short prefix height 4',
-            len('LINE that should be wrapped even more times, say four.'),
-            len('|                |'),  # screen_width
-            len('--->'),  # prefix_width
-            (   'LINE that should b\n'
-                '--->e wrapped even\n'
-                '---> more times, s\n'
-                '--->ay four.      \n').count('\n'),  # expected_height
-            len(            '______'),  # expected_trailing_characters
-        ),
-        (
-            'no wrapping needed',
-            len('LINE wrapped'),  # text_width
-            len('|                |'),  # screen_width
-            len('--->'),  # prefix_width
-            (   'LINE wrapped      \n').count('\n'),  # expected_height
-            len(            '______'),  # expected_trailing_characters
-        ),
-        (
-            'prefix is > screen width',
-            len('LINE that should be wrapped'),  # text_width
-            len('|                |'),  # screen_width
-            len('------------------>'),  # prefix_width
-            (   'LINE that should b\n'
-                'e wrapped         \n').count('\n'),  # expected_height
-            len(         '_________'),  # expected_trailing_characters
-        ),
-        (
-            'prefix is == screen width',
-            len('LINE that should be wrapped'),  # text_width
-            len('|                |'),  # screen_width
-            len('----------------->'),  # prefix_width
-            (   'LINE that should b\n'
-                'e wrapped         \n').count('\n'),  # expected_height
-            len(         '_________'),  # expected_trailing_characters
-        ),
-    ]) # yapf: disable
-
-    def test_get_line_height(self, _name, text_width, screen_width,
-                             prefix_width, expected_height,
-                             expected_trailing_characters) -> None:
-        """Test line height calculations."""
-        height, remaining_width = get_line_height(text_width, screen_width,
-                                                  prefix_width)
-        self.assertEqual(height, expected_height)
-        self.assertEqual(remaining_width, expected_trailing_characters)
-
-    # pylint: disable=line-too-long
-    @parameterized.expand([
-        (
-            'One line with ANSI escapes and no included breaks',
-            12,  # screen_width
-            False,  # truncate_long_lines
-            'Lorem ipsum \x1b[34m\x1b[1mdolor sit amet\x1b[0m, consectetur adipiscing elit.',  # message
-            ANSI(
-                # Line 1
-                'Lorem ipsum \n'
-                # Line 2
-                '\x1b[34m\x1b[1m'  # zero width
-                'dolor sit am\n'
-                # Line 3
-                'et'
-                '\x1b[0m'  # zero width
-                ', consecte\n'
-                # Line 4
-                'tur adipisci\n'
-                # Line 5
-                'ng elit.\n').__pt_formatted_text__(),
-            5,  # expected_height
-        ),
-        (
-            'One line with ANSI escapes and included breaks',
-            12,  # screen_width
-            False,  # truncate_long_lines
-            'Lorem\n ipsum \x1b[34m\x1b[1mdolor sit amet\x1b[0m, consectetur adipiscing elit.',  # message
-            ANSI(
-                # Line 1
-                'Lorem\n'
-                # Line 2
-                ' ipsum \x1b[34m\x1b[1mdolor\n'
-                # Line 3
-                ' sit amet\x1b[0m, c\n'
-                # Line 4
-                'onsectetur a\n'
-                # Line 5
-                'dipiscing el\n'
-                # Line 6
-                'it.\n'
-                ).__pt_formatted_text__(),
-            6,  # expected_height
-        ),
-        (
-            'One line with ANSI escapes and included breaks; truncate lines enabled',
-            12,  # screen_width
-            True,  # truncate_long_lines
-            'Lorem\n ipsum dolor sit amet, consectetur adipiscing \nelit.\n',  # message
-            ANSI(
-                # Line 1
-                'Lorem\n'
-                # Line 2
-                ' ipsum dolor\n'
-                # Line 3
-                'elit.\n'
-                ).__pt_formatted_text__(),
-            3,  # expected_height
-        ),
-        (
-            'wrapping enabled with a line break just after screen_width',
-            10,  # screen_width
-            False,  # truncate_long_lines
-            '01234567890\nTest Log\n',  # message
-            ANSI(
-                '0123456789\n'
-                '0\n'
-                'Test Log\n'
-                ).__pt_formatted_text__(),
-            3,  # expected_height
-        ),
-        (
-            'log message with a line break at screen_width',
-            10,  # screen_width
-            True,  # truncate_long_lines
-            '0123456789\nTest Log\n',  # message
-            ANSI(
-                '0123456789\n'
-                'Test Log\n'
-                ).__pt_formatted_text__(),
-            2,  # expected_height
-        ),
-    ]) # yapf: disable
-    # pylint: enable=line-too-long
-    def test_insert_linebreaks(
-        self,
-        _name,
-        screen_width,
-        truncate_long_lines,
-        raw_text,
-        expected_fragments,
-        expected_height,
-    ) -> None:
-        """Test inserting linebreaks to wrap lines."""
-
-        formatted_text = ANSI(raw_text).__pt_formatted_text__()
-
-        fragments, line_height = insert_linebreaks(
-            formatted_text,
-            max_line_width=screen_width,
-            truncate_long_lines=truncate_long_lines)
-
-        self.assertEqual(fragments, expected_fragments)
-        self.assertEqual(line_height, expected_height)
-
-    @parameterized.expand([
-        (
-            'flattened split',
-            ANSI(
-                'Lorem\n'
-                ' ipsum dolor\n'
-                'elit.\n'
-            ).__pt_formatted_text__(),
-            [
-                ANSI('Lorem').__pt_formatted_text__(),
-                ANSI(' ipsum dolor').__pt_formatted_text__(),
-                ANSI('elit.').__pt_formatted_text__(),
-            ],  # expected_lines
-        ),
-        (
-            'split fragments from insert_linebreaks',
-            insert_linebreaks(
-                ANSI(
-                    'Lorem\n ipsum dolor sit amet, consectetur adipiscing elit.'
-                ).__pt_formatted_text__(),
-                max_line_width=15,
-                # [0] for the fragments, [1] is line_height
-                truncate_long_lines=False)[0],
-            [
-                ANSI('Lorem').__pt_formatted_text__(),
-                ANSI(' ipsum dolor si').__pt_formatted_text__(),
-                ANSI('t amet, consect').__pt_formatted_text__(),
-                ANSI('etur adipiscing').__pt_formatted_text__(),
-                ANSI(' elit.').__pt_formatted_text__(),
-            ],
-        ),
-        (
-            'empty lines',
-            # Each line should have at least one StyleAndTextTuple but without
-            # an ending line break.
-            [
-                ('', '\n'),
-                ('', '\n'),
-            ],
-            [
-                [('', '')],
-                [('', '')],
-            ],
-        )
-    ]) # yapf: disable
-    def test_split_lines(
-        self,
-        _name,
-        input_fragments,
-        expected_lines,
-    ) -> None:
-        """Test splitting flattened StyleAndTextTuples into a list of lines."""
-
-        result_lines = split_lines(input_fragments)
-
-        self.assertEqual(result_lines, expected_lines)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_console/py/window_manager_test.py b/pw_console/py/window_manager_test.py
deleted file mode 100644
index 676d07c..0000000
--- a/pw_console/py/window_manager_test.py
+++ /dev/null
@@ -1,804 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests for pw_console.console_app"""
-
-import logging
-import unittest
-from unittest.mock import MagicMock
-
-from prompt_toolkit.application import create_app_session
-from prompt_toolkit.output import ColorDepth
-# inclusive-language: ignore
-from prompt_toolkit.output import DummyOutput as FakeOutput
-
-from pw_console.console_app import ConsoleApp
-from pw_console.console_prefs import ConsolePrefs
-from pw_console.window_manager import _WINDOW_SPLIT_ADJUST
-from pw_console.window_list import _WINDOW_HEIGHT_ADJUST, DisplayMode
-
-
-def _create_console_app(logger_count=2):
-    console_app = ConsoleApp(color_depth=ColorDepth.DEPTH_8_BIT,
-                             prefs=ConsolePrefs(project_file=False,
-                                                project_user_file=False,
-                                                user_file=False))
-    console_app.focus_on_container = MagicMock()
-
-    loggers = {}
-    for i in range(logger_count):
-        loggers['Log{}'.format(i)] = [
-            logging.getLogger('test_log{}'.format(i))
-        ]
-    for window_title, logger_instances in loggers.items():
-        console_app.add_log_handler(window_title, logger_instances)
-    return console_app
-
-
-_WINDOW_MANAGER_WIDTH = 80
-_WINDOW_MANAGER_HEIGHT = 30
-_DEFAULT_WINDOW_WIDTH = 10
-_DEFAULT_WINDOW_HEIGHT = 10
-
-
-def _window_list_widths(window_manager):
-    window_manager.update_window_manager_size(_WINDOW_MANAGER_WIDTH,
-                                              _WINDOW_MANAGER_HEIGHT)
-
-    return [
-        window_list.width.preferred
-        for window_list in window_manager.window_lists
-    ]
-
-
-def _window_list_heights(window_manager):
-    window_manager.update_window_manager_size(_WINDOW_MANAGER_WIDTH,
-                                              _WINDOW_MANAGER_HEIGHT)
-
-    return [
-        window_list.height.preferred
-        for window_list in window_manager.window_lists
-    ]
-
-
-def _window_pane_widths(window_manager, window_list_index=0):
-    window_manager.update_window_manager_size(_WINDOW_MANAGER_WIDTH,
-                                              _WINDOW_MANAGER_HEIGHT)
-
-    return [
-        pane.width.preferred
-        for pane in window_manager.window_lists[window_list_index].active_panes
-    ]
-
-
-def _window_pane_heights(window_manager, window_list_index=0):
-    window_manager.update_window_manager_size(_WINDOW_MANAGER_WIDTH,
-                                              _WINDOW_MANAGER_HEIGHT)
-
-    return [
-        pane.height.preferred
-        for pane in window_manager.window_lists[window_list_index].active_panes
-    ]
-
-
-def _window_pane_counts(window_manager):
-    return [
-        len(window_list.active_panes)
-        for window_list in window_manager.window_lists
-    ]
-
-
-def window_pane_titles(window_manager):
-    return [[
-        pane.pane_title() + ' - ' + pane.pane_subtitle()
-        for pane in window_list.active_panes
-    ] for window_list in window_manager.window_lists]
-
-
-def target_list_and_pane(window_manager, list_index, pane_index):
-    # pylint: disable=protected-access
-    # Bypass prompt_toolkit has_focus()
-    window_manager._get_active_window_list_and_pane = (
-        MagicMock(  # type: ignore
-            return_value=(
-                window_manager.window_lists[list_index],
-                window_manager.window_lists[list_index].
-                active_panes[pane_index],
-            )))
-
-
-class TestWindowManager(unittest.TestCase):
-    # pylint: disable=protected-access
-    """Tests for window management functions."""
-    def setUp(self):
-        self.maxDiff = None  # pylint: disable=invalid-name
-
-    def test_find_window_list_and_pane(self) -> None:
-        """Test getting the window list for a given pane."""
-        with create_app_session(output=FakeOutput()):
-            console_app = _create_console_app(logger_count=3)
-
-            window_manager = console_app.window_manager
-            self.assertEqual([4], _window_pane_counts(window_manager))
-
-            # Move 2 windows to the right into their own splits
-            target_list_and_pane(window_manager, 0, 0)
-            window_manager.move_pane_right()
-            target_list_and_pane(window_manager, 0, 0)
-            window_manager.move_pane_right()
-            target_list_and_pane(window_manager, 1, 0)
-            window_manager.move_pane_right()
-            # 3 splits, first split has 2 windows
-            self.assertEqual([2, 1, 1], _window_pane_counts(window_manager))
-
-            # Move the first window in the first split left
-            target_list_and_pane(window_manager, 0, 0)
-            window_manager.move_pane_left()
-            # 4 splits, each with their own window
-            self.assertEqual([1, 1, 1, 1], _window_pane_counts(window_manager))
-
-            # Move the first window to the right
-            target_list_and_pane(window_manager, 0, 0)
-            window_manager.move_pane_right()
-            # 3 splits, first split has 2 windows
-            self.assertEqual([2, 1, 1], _window_pane_counts(window_manager))
-
-            target_pane = window_manager.window_lists[2].active_panes[0]
-
-            result_window_list, result_pane_index = (
-                window_manager._find_window_list_and_pane_index(target_pane))
-            self.assertEqual(
-                (result_window_list, result_pane_index),
-                (window_manager.window_lists[2], 0),
-            )
-            window_manager.remove_pane(target_pane)
-            self.assertEqual([2, 1], _window_pane_counts(window_manager))
-
-    def test_window_list_moving_and_resizing(self) -> None:
-        """Test window split movement resizing."""
-        with create_app_session(output=FakeOutput()):
-            console_app = _create_console_app(logger_count=3)
-
-            window_manager = console_app.window_manager
-
-            target_list_and_pane(window_manager, 0, 0)
-            # Should have one window list split of size 50.
-            self.assertEqual(
-                _window_list_widths(window_manager),
-                [_WINDOW_MANAGER_WIDTH],
-            )
-
-            # Move one pane to the right, creating a new window_list split.
-            window_manager.move_pane_right()
-
-            self.assertEqual(_window_list_widths(window_manager), [
-                int(_WINDOW_MANAGER_WIDTH / 2),
-                int(_WINDOW_MANAGER_WIDTH / 2),
-            ])
-
-            # Move another pane to the right twice, creating a third
-            # window_list split.
-            target_list_and_pane(window_manager, 0, 0)
-            window_manager.move_pane_right()
-
-            # Above window pane is at a new location
-            target_list_and_pane(window_manager, 1, 0)
-            window_manager.move_pane_right()
-
-            # Should have 3 splits now
-            self.assertEqual(
-                _window_list_widths(window_manager),
-                [
-                    int(_WINDOW_MANAGER_WIDTH / 3),
-                    int(_WINDOW_MANAGER_WIDTH / 3),
-                    int(_WINDOW_MANAGER_WIDTH / 3),
-                ],
-            )
-
-            # Total of 4 active panes
-            self.assertEqual(len(list(window_manager.active_panes())), 4)
-
-            # Target the middle split
-            target_list_and_pane(window_manager, 1, 0)
-            # Shrink the middle split twice
-            window_manager.shrink_split()
-            window_manager.shrink_split()
-            self.assertEqual(
-                _window_list_widths(window_manager),
-                [
-                    int(_WINDOW_MANAGER_WIDTH / 3),
-                    int(_WINDOW_MANAGER_WIDTH / 3) -
-                    (2 * _WINDOW_SPLIT_ADJUST),
-                    int(_WINDOW_MANAGER_WIDTH / 3) +
-                    (2 * _WINDOW_SPLIT_ADJUST),
-                ],
-            )
-
-            # Target the first split
-            target_list_and_pane(window_manager, 0, 0)
-            window_manager.reset_split_sizes()
-            # Shrink the first split twice
-            window_manager.shrink_split()
-            self.assertEqual(
-                _window_list_widths(window_manager),
-                [
-                    int(_WINDOW_MANAGER_WIDTH / 3) -
-                    (1 * _WINDOW_SPLIT_ADJUST),
-                    int(_WINDOW_MANAGER_WIDTH / 3) +
-                    (1 * _WINDOW_SPLIT_ADJUST),
-                    int(_WINDOW_MANAGER_WIDTH / 3),
-                ],
-            )
-
-            # Target the third (last) split
-            target_list_and_pane(window_manager, 2, 0)
-            window_manager.reset_split_sizes()
-            # Shrink the third split once
-            window_manager.shrink_split()
-            self.assertEqual(
-                _window_list_widths(window_manager),
-                [
-                    int(_WINDOW_MANAGER_WIDTH / 3),
-                    int(_WINDOW_MANAGER_WIDTH / 3) +
-                    (1 * _WINDOW_SPLIT_ADJUST),
-                    int(_WINDOW_MANAGER_WIDTH / 3) -
-                    (1 * _WINDOW_SPLIT_ADJUST),
-                ],
-            )
-
-            window_manager.reset_split_sizes()
-            # Enlarge the third split a few times.
-            window_manager.enlarge_split()
-            window_manager.enlarge_split()
-            window_manager.enlarge_split()
-            self.assertEqual(
-                _window_list_widths(window_manager),
-                [
-                    int(_WINDOW_MANAGER_WIDTH / 3),
-                    int(_WINDOW_MANAGER_WIDTH / 3) -
-                    (3 * _WINDOW_SPLIT_ADJUST),
-                    int(_WINDOW_MANAGER_WIDTH / 3) +
-                    (3 * _WINDOW_SPLIT_ADJUST),
-                ],
-            )
-
-            # Target the middle split
-            target_list_and_pane(window_manager, 1, 0)
-            # Move the middle window pane left
-            window_manager.move_pane_left()
-            # This is called on the next render pass
-            window_manager.rebalance_window_list_sizes()
-            # Middle split should be removed
-            self.assertEqual(
-                _window_list_widths(window_manager),
-                [
-                    int(_WINDOW_MANAGER_WIDTH / 2) -
-                    (3 * _WINDOW_SPLIT_ADJUST),
-                    # This split is removed
-                    int(_WINDOW_MANAGER_WIDTH / 2) +
-                    (2 * _WINDOW_SPLIT_ADJUST),
-                ],
-            )
-
-            # Revert sizes to default
-            window_manager.reset_split_sizes()
-            self.assertEqual(
-                _window_list_widths(window_manager),
-                [
-                    int(_WINDOW_MANAGER_WIDTH / 2),
-                    int(_WINDOW_MANAGER_WIDTH / 2),
-                ],
-            )
-
-    def test_get_pane_titles(self) -> None:
-        """Test window resizing."""
-        with create_app_session(output=FakeOutput()):
-            console_app = _create_console_app(logger_count=3)
-
-            window_manager = console_app.window_manager
-            list_pane_titles = [
-                # Remove mouse click handler partials in tup[2]
-                [(tup[0], tup[1]) for tup in window_list.get_pane_titles()]
-                for window_list in window_manager.window_lists
-            ]
-            self.assertEqual(
-                list_pane_titles[0],
-                [('', ' '), ('class:window-tab-inactive', ' Log2 test_log2 '),
-                 ('', ' '), ('class:window-tab-inactive', ' Log1 test_log1 '),
-                 ('', ' '), ('class:window-tab-inactive', ' Log0 test_log0 '),
-                 ('', ' '), ('class:window-tab-inactive', ' Python Repl  '),
-                 ('', ' ')],
-            )
-
-    def test_window_pane_movement_resizing(self) -> None:
-        """Test window resizing."""
-        with create_app_session(output=FakeOutput()):
-            console_app = _create_console_app(logger_count=3)
-
-            window_manager = console_app.window_manager
-
-            # 4 panes, 3 for the loggers and 1 for the repl.
-            self.assertEqual(
-                len(window_manager.first_window_list().active_panes), 4)
-
-            def target_window_pane(index: int):
-                # Bypass prompt_toolkit has_focus()
-                window_manager._get_active_window_list_and_pane = (
-                    MagicMock(  # type: ignore
-                        return_value=(
-                            window_manager.window_lists[0],
-                            window_manager.window_lists[0].active_panes[index],
-                        )))
-                window_list = console_app.window_manager.first_window_list()
-                window_list.get_current_active_pane = (
-                    MagicMock(  # type: ignore
-                        return_value=window_list.active_panes[index]))
-
-            # Target the first window pane
-            target_window_pane(0)
-
-            # Shrink the first pane
-            window_manager.shrink_pane()
-            self.assertEqual(
-                _window_pane_heights(window_manager),
-                [
-                    _DEFAULT_WINDOW_HEIGHT - (1 * _WINDOW_HEIGHT_ADJUST),
-                    _DEFAULT_WINDOW_HEIGHT + (1 * _WINDOW_HEIGHT_ADJUST),
-                    _DEFAULT_WINDOW_HEIGHT,
-                    _DEFAULT_WINDOW_HEIGHT,
-                ],
-            )
-
-            # Reset pane sizes
-            window_manager.window_lists[0].current_window_list_height = (
-                4 * _DEFAULT_WINDOW_HEIGHT)
-            window_manager.reset_pane_sizes()
-            self.assertEqual(
-                _window_pane_heights(window_manager),
-                [
-                    _DEFAULT_WINDOW_HEIGHT,
-                    _DEFAULT_WINDOW_HEIGHT,
-                    _DEFAULT_WINDOW_HEIGHT,
-                    _DEFAULT_WINDOW_HEIGHT,
-                ],
-            )
-
-            # Shrink last pane
-            target_window_pane(3)
-
-            window_manager.shrink_pane()
-            self.assertEqual(
-                _window_pane_heights(window_manager),
-                [
-                    _DEFAULT_WINDOW_HEIGHT,
-                    _DEFAULT_WINDOW_HEIGHT,
-                    _DEFAULT_WINDOW_HEIGHT + (1 * _WINDOW_HEIGHT_ADJUST),
-                    _DEFAULT_WINDOW_HEIGHT - (1 * _WINDOW_HEIGHT_ADJUST),
-                ],
-            )
-
-            # Enlarge second pane
-            target_window_pane(1)
-            window_manager.reset_pane_sizes()
-
-            window_manager.enlarge_pane()
-            window_manager.enlarge_pane()
-            self.assertEqual(
-                _window_pane_heights(window_manager),
-                [
-                    _DEFAULT_WINDOW_HEIGHT,
-                    _DEFAULT_WINDOW_HEIGHT + (2 * _WINDOW_HEIGHT_ADJUST),
-                    _DEFAULT_WINDOW_HEIGHT - (2 * _WINDOW_HEIGHT_ADJUST),
-                    _DEFAULT_WINDOW_HEIGHT,
-                ],
-            )
-
-            # Check window pane ordering
-            self.assertEqual(
-                window_pane_titles(window_manager),
-                [
-                    [
-                        'Log2 - test_log2',
-                        'Log1 - test_log1',
-                        'Log0 - test_log0',
-                        'Python Repl - ',
-                    ],
-                ],
-            )
-
-            target_window_pane(0)
-            window_manager.move_pane_down()
-            self.assertEqual(
-                window_pane_titles(window_manager),
-                [
-                    [
-                        'Log1 - test_log1',
-                        'Log2 - test_log2',
-                        'Log0 - test_log0',
-                        'Python Repl - ',
-                    ],
-                ],
-            )
-            target_window_pane(2)
-            window_manager.move_pane_up()
-            target_window_pane(1)
-            window_manager.move_pane_up()
-            self.assertEqual(
-                window_pane_titles(window_manager),
-                [
-                    [
-                        'Log0 - test_log0',
-                        'Log1 - test_log1',
-                        'Log2 - test_log2',
-                        'Python Repl - ',
-                    ],
-                ],
-            )
-            target_window_pane(0)
-            window_manager.move_pane_up()
-            self.assertEqual(
-                window_pane_titles(window_manager),
-                [
-                    [
-                        'Log0 - test_log0',
-                        'Log1 - test_log1',
-                        'Log2 - test_log2',
-                        'Python Repl - ',
-                    ],
-                ],
-            )
-
-    def test_focus_next_and_previous_pane(self) -> None:
-        """Test switching focus to next and previous window panes."""
-        with create_app_session(output=FakeOutput()):
-            console_app = _create_console_app(logger_count=4)
-
-            window_manager = console_app.window_manager
-            self.assertEqual(
-                window_pane_titles(window_manager),
-                [
-                    [
-                        'Log3 - test_log3',
-                        'Log2 - test_log2',
-                        'Log1 - test_log1',
-                        'Log0 - test_log0',
-                        'Python Repl - ',
-                    ],
-                ],
-            )
-
-            # Scenario: Move between panes with a single stacked window list.
-
-            # Set the first pane in focus.
-            target_list_and_pane(window_manager, 0, 0)
-            # Switch focus to the next pane
-            window_manager.focus_next_pane()
-            # Pane index 1 should now be focused.
-            console_app.focus_on_container.assert_called_once_with(
-                window_manager.window_lists[0].active_panes[1])
-            console_app.focus_on_container.reset_mock()
-
-            # Set the first pane in focus.
-            target_list_and_pane(window_manager, 0, 0)
-            # Switch focus to the previous pane
-            window_manager.focus_previous_pane()
-            # Previous pane should wrap around to the last pane in the first
-            # window_list.
-            console_app.focus_on_container.assert_called_once_with(
-                window_manager.window_lists[0].active_panes[-1])
-            console_app.focus_on_container.reset_mock()
-
-            # Set the last pane in focus.
-            target_list_and_pane(window_manager, 0, 4)
-            # Switch focus to the next pane
-            window_manager.focus_next_pane()
-            # Next pane should wrap around to the first pane in the first
-            # window_list.
-            console_app.focus_on_container.assert_called_once_with(
-                window_manager.window_lists[0].active_panes[0])
-            console_app.focus_on_container.reset_mock()
-
-            # Scenario: Move between panes with a single tabbed window list.
-
-            # Switch to Tabbed view mode
-            window_manager.window_lists[0].set_display_mode(DisplayMode.TABBED)
-            # The set_display_mode call above will call focus_on_container once.
-            console_app.focus_on_container.reset_mock()
-
-            # Setup the switch_to_tab mock
-            window_manager.window_lists[0].switch_to_tab = MagicMock(
-                wraps=window_manager.window_lists[0].switch_to_tab)
-
-            # Set the first pane/tab in focus.
-            target_list_and_pane(window_manager, 0, 0)
-            # Switch focus to the next pane/tab
-            window_manager.focus_next_pane()
-            # Check switch_to_tab is called
-            window_manager.window_lists[
-                0].switch_to_tab.assert_called_once_with(1)
-            # And that focus_on_container is called only once
-            console_app.focus_on_container.assert_called_once_with(
-                window_manager.window_lists[0].active_panes[1])
-            console_app.focus_on_container.reset_mock()
-            window_manager.window_lists[0].switch_to_tab.reset_mock()
-
-            # Set the last pane/tab in focus.
-            target_list_and_pane(window_manager, 0, 4)
-            # Switch focus to the next pane/tab
-            window_manager.focus_next_pane()
-            # Check switch_to_tab is called
-            window_manager.window_lists[
-                0].switch_to_tab.assert_called_once_with(0)
-            # And that focus_on_container is called only once
-            console_app.focus_on_container.assert_called_once_with(
-                window_manager.window_lists[0].active_panes[0])
-            console_app.focus_on_container.reset_mock()
-            window_manager.window_lists[0].switch_to_tab.reset_mock()
-
-            # Set the first pane/tab in focus.
-            target_list_and_pane(window_manager, 0, 0)
-            # Switch focus to the prev pane/tab
-            window_manager.focus_previous_pane()
-            # Check switch_to_tab is called
-            window_manager.window_lists[
-                0].switch_to_tab.assert_called_once_with(4)
-            # And that focus_on_container is called only once
-            console_app.focus_on_container.assert_called_once_with(
-                window_manager.window_lists[0].active_panes[4])
-            console_app.focus_on_container.reset_mock()
-            window_manager.window_lists[0].switch_to_tab.reset_mock()
-
-            # Scenario: Move between multiple window lists with mixed stacked
-            # and tabbed view modes.
-
-            # Setup: Move two panes to the right into their own stacked
-            # window_list.
-            target_list_and_pane(window_manager, 0, 4)
-            window_manager.move_pane_right()
-            target_list_and_pane(window_manager, 0, 3)
-            window_manager.move_pane_right()
-            self.assertEqual(
-                window_pane_titles(window_manager),
-                [
-                    [
-                        'Log3 - test_log3',
-                        'Log2 - test_log2',
-                        'Log1 - test_log1',
-                    ],
-                    [
-                        'Log0 - test_log0',
-                        'Python Repl - ',
-                    ],
-                ],
-            )
-
-            # Setup the switch_to_tab mock on the second window_list
-            window_manager.window_lists[1].switch_to_tab = MagicMock(
-                wraps=window_manager.window_lists[1].switch_to_tab)
-
-            # Set Log1 in focus
-            target_list_and_pane(window_manager, 0, 2)
-            window_manager.focus_next_pane()
-            # Log0 should now have focus
-            console_app.focus_on_container.assert_called_once_with(
-                window_manager.window_lists[1].active_panes[0])
-            console_app.focus_on_container.reset_mock()
-
-            # Set Log0 in focus
-            target_list_and_pane(window_manager, 1, 0)
-            window_manager.focus_previous_pane()
-            # Log1 should now have focus
-            console_app.focus_on_container.assert_called_once_with(
-                window_manager.window_lists[0].active_panes[2])
-            # The first window list is in tabbed mode so switch_to_tab should be
-            # called once.
-            window_manager.window_lists[
-                0].switch_to_tab.assert_called_once_with(2)
-            # Reset
-            window_manager.window_lists[0].switch_to_tab.reset_mock()
-            console_app.focus_on_container.reset_mock()
-
-            # Set Python Repl in focus
-            target_list_and_pane(window_manager, 1, 1)
-            window_manager.focus_next_pane()
-            # Log3 should now have focus
-            console_app.focus_on_container.assert_called_once_with(
-                window_manager.window_lists[0].active_panes[0])
-            window_manager.window_lists[
-                0].switch_to_tab.assert_called_once_with(0)
-            # Reset
-            window_manager.window_lists[0].switch_to_tab.reset_mock()
-            console_app.focus_on_container.reset_mock()
-
-            # Set Log3 in focus
-            target_list_and_pane(window_manager, 0, 0)
-            window_manager.focus_next_pane()
-            # Log2 should now have focus
-            console_app.focus_on_container.assert_called_once_with(
-                window_manager.window_lists[0].active_panes[1])
-            window_manager.window_lists[
-                0].switch_to_tab.assert_called_once_with(1)
-            # Reset
-            window_manager.window_lists[0].switch_to_tab.reset_mock()
-            console_app.focus_on_container.reset_mock()
-
-            # Set Python Repl in focus
-            target_list_and_pane(window_manager, 1, 1)
-            window_manager.focus_previous_pane()
-            # Log0 should now have focus
-            console_app.focus_on_container.assert_called_once_with(
-                window_manager.window_lists[1].active_panes[0])
-            # The second window list is in stacked mode so switch_to_tab should
-            # not be called.
-            window_manager.window_lists[1].switch_to_tab.assert_not_called()
-            # Reset
-            window_manager.window_lists[1].switch_to_tab.reset_mock()
-            console_app.focus_on_container.reset_mock()
-
-    def test_resize_vertical_splits(self) -> None:
-        """Test resizing window splits."""
-        with create_app_session(output=FakeOutput()):
-            console_app = _create_console_app(logger_count=4)
-            window_manager = console_app.window_manager
-
-            # Required before moving windows
-            window_manager.update_window_manager_size(_WINDOW_MANAGER_WIDTH,
-                                                      _WINDOW_MANAGER_HEIGHT)
-            window_manager.create_root_container()
-
-            # Vertical split by default
-            self.assertTrue(window_manager.vertical_window_list_spliting())
-
-            # Move windows to create 3 splits
-            target_list_and_pane(window_manager, 0, 0)
-            window_manager.move_pane_right()
-            target_list_and_pane(window_manager, 0, 0)
-            window_manager.move_pane_right()
-            target_list_and_pane(window_manager, 1, 1)
-            window_manager.move_pane_right()
-
-            # Check windows are where expected
-            self.assertEqual(
-                window_pane_titles(window_manager),
-                [
-                    [
-                        'Log1 - test_log1',
-                        'Log0 - test_log0',
-                        'Python Repl - ',
-                    ],
-                    [
-                        'Log2 - test_log2',
-                    ],
-                    [
-                        'Log3 - test_log3',
-                    ],
-                ],
-            )
-
-            # Check initial split widths
-            widths = [
-                int(_WINDOW_MANAGER_WIDTH / 3),
-                int(_WINDOW_MANAGER_WIDTH / 3),
-                int(_WINDOW_MANAGER_WIDTH / 3),
-            ]
-            self.assertEqual(_window_list_widths(window_manager), widths)
-
-            # Decrease size of first split
-            window_manager.adjust_split_size(window_manager.window_lists[0],
-                                             -4)
-            widths = [
-                widths[0] - (4 * _WINDOW_SPLIT_ADJUST),
-                widths[1] + (4 * _WINDOW_SPLIT_ADJUST),
-                widths[2],
-            ]
-            self.assertEqual(_window_list_widths(window_manager), widths)
-
-            # Increase size of last split
-            widths = [
-                widths[0],
-                widths[1] - (4 * _WINDOW_SPLIT_ADJUST),
-                widths[2] + (4 * _WINDOW_SPLIT_ADJUST),
-            ]
-            window_manager.adjust_split_size(window_manager.window_lists[2], 4)
-            self.assertEqual(_window_list_widths(window_manager), widths)
-
-            # Check heights are all the same
-            window_manager.rebalance_window_list_sizes()
-            heights = [
-                int(_WINDOW_MANAGER_HEIGHT),
-                int(_WINDOW_MANAGER_HEIGHT),
-                int(_WINDOW_MANAGER_HEIGHT),
-            ]
-            self.assertEqual(_window_list_heights(window_manager), heights)
-
-    def test_resize_horizontal_splits(self) -> None:
-        """Test resizing window splits."""
-        with create_app_session(output=FakeOutput()):
-            console_app = _create_console_app(logger_count=4)
-            window_manager = console_app.window_manager
-
-            # We want horizontal window splits
-            window_manager.vertical_window_list_spliting = (MagicMock(
-                return_value=False))
-            self.assertFalse(window_manager.vertical_window_list_spliting())
-
-            # Required before moving windows
-            window_manager.update_window_manager_size(_WINDOW_MANAGER_WIDTH,
-                                                      _WINDOW_MANAGER_HEIGHT)
-            window_manager.create_root_container()
-
-            # Move windows to create 3 splits
-            target_list_and_pane(window_manager, 0, 0)
-            window_manager.move_pane_right()
-            target_list_and_pane(window_manager, 0, 0)
-            window_manager.move_pane_right()
-            target_list_and_pane(window_manager, 1, 1)
-            window_manager.move_pane_right()
-
-            # Check windows are where expected
-            self.assertEqual(
-                window_pane_titles(window_manager),
-                [
-                    [
-                        'Log1 - test_log1',
-                        'Log0 - test_log0',
-                        'Python Repl - ',
-                    ],
-                    [
-                        'Log2 - test_log2',
-                    ],
-                    [
-                        'Log3 - test_log3',
-                    ],
-                ],
-            )
-
-            # Check initial split widths
-            heights = [
-                int(_WINDOW_MANAGER_HEIGHT / 3),
-                int(_WINDOW_MANAGER_HEIGHT / 3),
-                int(_WINDOW_MANAGER_HEIGHT / 3),
-            ]
-            self.assertEqual(_window_list_heights(window_manager), heights)
-
-            # Decrease size of first split
-            window_manager.adjust_split_size(window_manager.window_lists[0],
-                                             -4)
-            heights = [
-                heights[0] - (4 * _WINDOW_SPLIT_ADJUST),
-                heights[1] + (4 * _WINDOW_SPLIT_ADJUST),
-                heights[2],
-            ]
-            self.assertEqual(_window_list_heights(window_manager), heights)
-
-            # Increase size of last split
-            heights = [
-                heights[0],
-                heights[1] - (4 * _WINDOW_SPLIT_ADJUST),
-                heights[2] + (4 * _WINDOW_SPLIT_ADJUST),
-            ]
-            window_manager.adjust_split_size(window_manager.window_lists[2], 4)
-            self.assertEqual(_window_list_heights(window_manager), heights)
-
-            # Check widths are all the same
-            window_manager.rebalance_window_list_sizes()
-            widths = [
-                int(_WINDOW_MANAGER_WIDTH),
-                int(_WINDOW_MANAGER_WIDTH),
-                int(_WINDOW_MANAGER_WIDTH),
-            ]
-            self.assertEqual(_window_list_widths(window_manager), widths)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_console/testing.rst b/pw_console/testing.rst
deleted file mode 100644
index 0f9b719..0000000
--- a/pw_console/testing.rst
+++ /dev/null
@@ -1,825 +0,0 @@
-.. _module-pw_console-testing:
-
-=====================
-Manual Test Procedure
-=====================
-
-``pw_console`` is a Terminal based user interface which is difficult to
-completely test in an automated fashion. Unit tests that don't depend on the
-user interface are preferred but not always possible. For those situations
-manual tests should be added here to validate expected behavior.
-
-Run in Test Mode
-================
-
-Begin each section below by running the console in test mode:
-
-.. code-block:: shell
-
-  touch /tmp/empty.yaml
-  env PW_CONSOLE_CONFIG_FILE='/tmp/empty.yaml' pw console --test-mode
-
-Test Sections
-=============
-
-Log Pane: Basic Actions
-^^^^^^^^^^^^^^^^^^^^^^^
-
-.. list-table::
-   :widths: 5 45 45 5
-   :header-rows: 1
-
-   * - #
-     - Test Action
-     - Expected Result
-     - ✅
-
-   * - 1
-     - Click the :guilabel:`Fake Device Logs` window title
-     - Log pane is focused
-     - |checkbox|
-
-   * - 2
-     - In the main menu enable :guilabel:`[File] > Log Table View > Hide Date`
-     - The time column shows only the time. E.g. ``09:34:53``.
-     - |checkbox|
-
-   * - 3
-     - In the main menu turn off :guilabel:`[File] > Log Table View > Hide Date`
-     - The time column shows the date and time. E.g. ``20220208 09:34:53``.
-     - |checkbox|
-
-   * - 4
-     - Click :guilabel:`Search` on the log toolbar
-     - | The search bar appears
-       | The cursor should appear after the ``/``
-     - |checkbox|
-
-   * - 5
-     - Press :kbd:`Ctrl-c`
-     - The search bar disappears
-     - |checkbox|
-
-   * - 6
-     - Click :guilabel:`Follow` on the log toolbar
-     - Logs stop following
-     - |checkbox|
-
-   * - 7
-     - Click :guilabel:`Table` on the log toolbar
-     - Table mode is disabled
-     - |checkbox|
-
-   * - 8
-     - Click :guilabel:`Wrap` on the log toolbar
-     - Line wrapping is enabled
-     - |checkbox|
-
-   * - 9
-     - Click :guilabel:`Clear` on the log toolbar
-     - | All log lines are erased
-       | Follow mode is on
-       | New lines start appearing
-     - |checkbox|
-
-   * - 10
-     - | Mouse drag across a few log messages
-     - | Entire logs are highlighted and a dialog
-       | box appears in the upper right
-     - |checkbox|
-
-   * - 11
-     - | Without scrolling mouse drag across a set
-       | of different log messages.
-     - | The old selection disappears leaving only the new selection.
-     - |checkbox|
-
-   * - 12
-     - | Click the :guilabel:`Cancel` button
-       | in the selection dialog box.
-     - | The selection and the dialog box disappears.
-     - |checkbox|
-
-   * - 13
-     - | Mouse drag across a few log messages and
-       | click the :guilabel:`Save as File` button.
-     - | The save as file dialog appears with the
-       | :guilabel:`[x] Selected Lines Only` opion checked.
-     - |checkbox|
-
-   * - 14
-     - | Press :kbd:`Cancel`
-     - | The save dialog closes
-     - |checkbox|
-
-   * - 15
-     - | Click the :guilabel:`Save` button on the log toolbar.
-       | A dialog appears prompting for a file.
-     - | The current working directory should be pre-filled.
-     - |checkbox|
-
-   * - 16
-     - | Check :guilabel:`[x] Table Formatting`
-       | Uncheck :guilabel:`[ ] Selected Lines Only`
-       | Add ``/log.txt`` to the end and press :kbd:`Enter`
-       | Click the menu :guilabel:`[File] > Exit`
-     - | In the terminal run ``cat log.txt`` to verify logs
-       | were saved correctly.
-     - |checkbox|
-
-Log Pane: Search and Filtering
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. list-table::
-   :widths: 5 45 45 5
-   :header-rows: 1
-
-   * - #
-     - Test Action
-     - Expected Result
-     - ✅
-
-   * - 1
-     - Click the :guilabel:`Fake Device Logs` window title
-     - Log pane is focused
-     - |checkbox|
-
-   * - 2
-     - Press :kbd:`/`
-     - | The search bar appears
-       | The cursor should appear after the ``/``
-     - |checkbox|
-
-   * - 3
-     - | Type ``lorem``
-       | Press :kbd:`Enter`
-     - | Logs stop following
-       | ``Lorem`` words are highlighted in cyan
-       | The cursor on the first log message
-       | The search toolbar is un-focused and displays:
-       | ``Match 1 / 10`` where the second number (the total match count)
-       | increases once every 10 seconds when new logs arrive.
-     - |checkbox|
-
-   * - 4
-     - Press :kbd:`Ctrl-f`
-     - | The search bar is focused
-       | The cursor should appear after ``/Lorem``
-     - |checkbox|
-
-   * - 5
-     - Press :kbd:`Ctrl-c`
-     - | The search bar disappears
-       | ``Lorem`` words are no longer highlighted
-     - |checkbox|
-
-   * - 6
-     - Press :kbd:`/`
-     - | The search bar appears and is empty
-       | The cursor should appear after ``/``
-     - |checkbox|
-
-   * - 7
-     - Click :guilabel:`Matcher:` once
-     - ``Matcher:STRING`` is shown
-     - |checkbox|
-
-   * - 8
-     - | Type ``[=``
-       | Press :kbd:`Enter`
-     - | All instances of ``[=`` should be highlighted
-       | The cursor should be on log message 2
-     - |checkbox|
-
-   * - 7
-     - Press :kbd:`/`
-     - | The search bar is focused
-       | The cursor should appear after the ``/[=``
-     - |checkbox|
-
-   * - 8
-     - Press :kbd:`Ctrl-c`
-     - | The search bar disappears
-       | ``[=`` matches are no longer highlighted
-     - |checkbox|
-
-   * - 9
-     - Press :kbd:`/`
-     - | The search bar appears and is empty
-       | The cursor should appear after ``/``
-     - |checkbox|
-
-   * - 10
-     - Press :kbd:`Up`
-     - The text ``[=`` should appear in the search input field
-     - |checkbox|
-
-   * - 11
-     - Click :guilabel:`Search Enter`
-     - | All instances of ``[=`` should be highlighted
-       | The cursor should be on log message 12
-     - |checkbox|
-
-   * - 12
-     - Click :guilabel:`Add Filter`
-     - | A ``Filters`` toolbar will appear
-       | showing the new filter: ``<\[= (X)>``.
-       | Only log messages matching ``[=`` appear in the logs.
-       | Follow mode is enabled
-     - |checkbox|
-
-   * - 13
-     - | Press :kbd:`/`
-     - | The search bar appears and is empty
-       | The cursor should appear after ``/``
-     - |checkbox|
-
-   * - 14
-     - | Type ``# 1`` and press :kbd:`Enter`
-       | Click :guilabel:`Add Filter`
-     - | The ``Filters`` toolbar shows a new filter: ``<\#\ 1 (X)>``.
-       | Only log messages matching both filters will appear in the logs.
-     - |checkbox|
-
-   * - 15
-     - | Click the first :guilabel:`(X)`
-       | in the filter toolbar.
-     - | The ``Filters`` toolbar shows only one filter: ``<\#\ 1 (X)>``.
-       | More log messages will appear in the log window
-       | Lines all end in: ``# 1.*``
-     - |checkbox|
-
-   * - 16
-     - Click :guilabel:`Clear Filters`
-     - | The ``Filters`` toolbar will disappear.
-       | All log messages will be shown in the log window.
-     - |checkbox|
-
-   * - 17
-     - | Press :kbd:`/`
-       | Type ``BAT``
-       | Click :guilabel:`Column` until ``Column:Module`` is shown
-       | Press :kbd:`Enter`
-     - | Logs stop following
-       | ``BAT`` is highlighted in cyan
-       | The cursor on the 3rd log message
-       | The search toolbar is un-focused and displays:
-       | ``Match 1 / 10`` where the second number (the total match count)
-     - |checkbox|
-
-   * - 18
-     - Press :kbd:`n`
-     - | ``BAT`` is highlighted in cyan
-       | The cursor on the 7th log message and is in the center of the
-       | log window (not the bottom).
-     - |checkbox|
-
-   * - 19
-     - Click :guilabel:`Jump to new matches`
-     - | :guilabel:`Jump to new matches` is checked and every 5 seconds
-       | the cursor jumps to the latest matching log message.
-     - |checkbox|
-
-   * - 20
-     - Click :guilabel:`Follow`
-     - | :guilabel:`Jump to new matches` is unchecked
-       | The cursor jumps to every new log message once a second.
-     - |checkbox|
-
-   * - 21
-     - | Click :guilabel:`Add Filter`
-     - | The Filters toolbar appears with one filter: ``<module BAT (X)>``
-       | Only logs with Module matching ``BAT`` appear.
-     - |checkbox|
-
-   * - 22
-     - Click :guilabel:`Clear Filters`
-     - | The ``Filters`` toolbar will disappear.
-       | All log messages will be shown in the log window.
-     - |checkbox|
-
-   * - 23
-     - | Press :kbd:`/`
-       | Type ``BAT``
-       | Click :guilabel:`Invert`
-     - ``[x] Invert`` setting is shown
-     - |checkbox|
-
-   * - 24
-     - | Press :kbd:`Enter` then click :guilabel:`Add Filter`
-     - | The Filters toolbar appears
-       | One filter is shown: ``<NOT module BAT (X)>``
-       | Only logs with Modules other than ``BAT`` appear.
-     - |checkbox|
-
-Help Windows
-^^^^^^^^^^^^
-
-.. list-table::
-   :widths: 5 45 45 5
-   :header-rows: 1
-
-   * - #
-     - Test Action
-     - Expected Result
-     - ✅
-
-   * - 1
-     - Click the :guilabel:`[Help] > User Guide`
-     - | Window appears showing the user guide with
-       | RST formatting and syntax highlighting
-     - |checkbox|
-
-   * - 2
-     - Press :guilabel:`q`
-     - Window is hidden
-     - |checkbox|
-
-   * - 3
-     - Click the :guilabel:`[Help] > Keyboard Shortcuts`
-     - Window appears showing the keybind list
-     - |checkbox|
-
-   * - 4
-     - Press :kbd:`F1`
-     - Window is hidden
-     - |checkbox|
-
-   * - 5
-     - Click the :guilabel:`[Help] > Console Test Mode Help`
-     - | Window appears showing help with content
-       | ``Welcome to the Pigweed Console Test Mode!``
-     - |checkbox|
-
-   * - 6
-     - Click the :guilabel:`Close q` button.
-     - Window is hidden
-     - |checkbox|
-
-Window Management
-^^^^^^^^^^^^^^^^^
-
-.. list-table::
-   :widths: 5 45 45 5
-   :header-rows: 1
-
-   * - #
-     - Test Action
-     - Expected Result
-     - ✅
-
-   * - 1
-     - | Click the :guilabel:`Fake Device Logs` window title
-     - Log pane is focused
-     - |checkbox|
-
-   * - 2
-     - | Click the menu :guilabel:`Windows > #: Fake Device Logs...`
-       | Click :guilabel:`Duplicate pane`
-     - | 3 panes are visible:
-       | Log pane on top
-       | Repl pane in the middle
-       | Log pane on the bottom
-     - |checkbox|
-
-   * - 3
-     - | Click the :guilabel:`Python Input` window title
-     - Python Input pane is focused
-     - |checkbox|
-
-   * - 4
-     - Click the :guilabel:`View > Move Window Down`
-     - | 3 panes are visible:
-       | Log pane on top
-       | Log pane in the middle
-       | Repl pane on the bottom
-     - |checkbox|
-
-   * - 5
-     - Click the :guilabel:`View > Move Window Down` again
-     - | Nothing changes
-       | Windows remain in the same order
-     - |checkbox|
-
-   * - 6
-     - Click the :guilabel:`View > Move Window Up`
-     - | 3 panes are visible:
-       | Log pane on top
-       | Repl pane in the middle
-       | Log pane on the bottom
-     - |checkbox|
-
-   * - 7
-     - | Click the menu :guilabel:`Windows > #: Fake Device Logs...`
-       | Click :guilabel:`Remove pane`
-     - | 2 panes are visible:
-       | Repl pane on the top
-       | Log pane on bottom
-     - |checkbox|
-
-   * - 8
-     - | Click the :guilabel:`Python Input`
-       | window title
-     - Repl pane is focused
-     - |checkbox|
-
-   * - 9
-     - | Hold the keys :guilabel:`Alt- -`
-       | `Alt` and `Minus`
-     - Repl pane shrinks
-     - |checkbox|
-
-   * - 10
-     - Hold the keys :guilabel:`Alt-=`
-     - Repl pane enlarges
-     - |checkbox|
-
-   * - 11
-     - | Click the menu :guilabel:`Windows > 1: Logs fake_device.1`
-       | Click :guilabel:`Duplicate pane`
-     - | 3 panes are visible:
-       | 2 Log panes on the left
-       | Repl pane on the right
-     - |checkbox|
-
-   * - 12
-     - | Click the left top :guilabel:`Logs` window title
-     - Log pane is focused
-     - |checkbox|
-
-   * - 13
-     - Click the :guilabel:`View > Move Window Right`
-     - | 3 panes are visible:
-       | 1 Log panes on the left
-       | 1 Log and Repl pane on the right
-     - |checkbox|
-
-   * - 14
-     - | Click the menu :guilabel:`Windows > Column 2 View Modes`
-       | Then click :guilabel:`[ ] Tabbed Windows`
-     - | 2 panes are visible:
-       | 1 Log panes on the left
-       | 1 Log panes on the right
-       | A tab bar on the top of the right side
-       | `Logs fake_device.1` is highlighted
-     - |checkbox|
-
-   * - 15
-     - | On the right side tab bar
-       | Click :guilabel:`Python Repl`
-     - | 2 panes are visible:
-       | 1 Log pane on the left
-       | 1 Repl pane on the right
-       | `Python Repl` is highlighted
-       | on the tab bar
-     - |checkbox|
-
-Mouse Window Resizing
-^^^^^^^^^^^^^^^^^^^^^
-
-.. list-table::
-   :widths: 5 45 45 5
-   :header-rows: 1
-
-   * - #
-     - Test Action
-     - Expected Result
-     - ✅
-
-   * - 1
-     - | Click the :guilabel:`Fake Device Logs` window
-     - Log pane is focused
-     - |checkbox|
-
-   * - 2
-     - | Left click and hold the :guilabel:`-==-` of that window
-       | Drag the mouse up and down
-     - This log pane is resized
-     - |checkbox|
-
-   * - 3
-     - | Left click and hold the :guilabel:`-==-`
-       | of the :guilabel:`PwConsole Debug` window
-       | Drag the mouse up and down
-     - | The :guilabel:`PwConsole Debug` should NOT be focused
-       | The window should be resized as expected
-     - |checkbox|
-
-   * - 4
-     - Click the :guilabel:`View > Move Window Right`
-     - :guilabel:`Fake Device Logs` should appear in a right side split
-     - |checkbox|
-
-   * - 5
-     - | Left click and hold anywhere on the vertical separator
-       | Drag the mouse left and right
-     - | The window splits should be resized as expected
-     - |checkbox|
-
-   * - 6
-     - Click the :guilabel:`View > Balance Window Sizes`
-     - Window split sizes should reset to equal widths
-     - |checkbox|
-
-   * - 7
-     - | Focus on the :guilabel:`Python Repl` window
-       | Click the :guilabel:`View > Move Window Left`
-     - | :guilabel:`Python Repl` should appear in a left side split
-       | There should be 3 vertical splits in total
-     - |checkbox|
-
-   * - 8
-     - | Left click and hold anywhere on the vertical separator
-       | between the first two splits (Python Repl and the middle split)
-       | Drag the mouse left and right
-     - | The first two window splits should be resized.
-       | The 3rd split size should not change.
-     - |checkbox|
-
-Copy Paste
-^^^^^^^^^^
-
-.. list-table::
-   :widths: 5 45 45 5
-   :header-rows: 1
-
-   * - #
-     - Test Action
-     - Expected Result
-     - ✅
-
-   * - 1
-     - | Click the :guilabel:`Fake Device Logs` window title
-     - Log pane is focused
-     - |checkbox|
-
-   * - 2
-     - | Mouse drag across a few log messages
-     - | A dialog appears in the upper right showing
-       | the number of lines selected and
-       | buttons for :guilabel:`Cancel`, :guilabel:`Select All`,
-       | :guilabel:`Save as File`, and :guilabel:`Copy`.
-     - |checkbox|
-
-   * - 3
-     - | Click the :guilabel:`Copy` button
-     - | Try pasting into a separate text editor
-       | Log lines like this should be pasted:
-       | ``20:07:25  INF  APP    Log message [    =     ] # 25``
-       | ``20:07:25  INF  RADIO  Log message [     =    ] # 26``
-     - |checkbox|
-
-   * - 4
-     - | Copy this text in your browser or
-       | text editor to the system clipboard:
-       | ``print('copy paste test!')``
-     - | Click the :guilabel:`Python Input` window title
-       | Press :kbd:`Ctrl-v`
-       | ``print('copy paste test!')`` appears
-       | after the prompt.
-     - |checkbox|
-
-   * - 5
-     - Press :kbd:`Enter`
-     - | This appears in Python Results:
-       | ``In [1]: print('copy paste test!')``
-       | ``copy paste test!``
-     - |checkbox|
-
-   * - 6
-     - | Click :guilabel:`Ctrl-Alt-c -> Copy Output`
-       | on the Python Results toolbar
-       | Try pasting into a separate text editor
-     - | The contents of the Python Results
-       | are in the system clipboard.
-     - |checkbox|
-
-   * - 7
-     - Click the :guilabel:`Python Results` window title
-     - | Python Results is focused with cursor
-       | appearing below the last line
-     - |checkbox|
-
-   * - 8
-     - | Click and drag over ``copy paste text``
-       | highlighting won't appear until
-       | after the mouse button is released
-     - | ``copy paste text`` is highlighted
-     - |checkbox|
-
-   * - 9
-     - | Press :kbd:`Ctrl-c`
-       | Try pasting into a separate text editor
-     - | ``copy paste text`` should appear (and is
-       | in the system clipboard)
-     - |checkbox|
-
-   * - 10
-     - Click the :guilabel:`Python Input` window title
-     - Python Input is focused
-     - |checkbox|
-
-   * - 11
-     - | Type ``print('hello there')`` into the Python input.
-       | Mouse drag select that text
-       | Press :kbd:`Ctrl-c`
-     - | The selection should disappear.
-       | Try pasting into a separate text editor, the paste should
-       | match the text you drag selected.
-     - |checkbox|
-
-Incremental Stdout
-^^^^^^^^^^^^^^^^^^
-
-.. list-table::
-   :widths: 5 45 45 5
-   :header-rows: 1
-
-   * - #
-     - Test Action
-     - Expected Result
-     - ✅
-
-   * - 1
-     - | Click the :guilabel:`Python Input` window title
-     - Python Input pane is focused
-     - |checkbox|
-
-   * - 2
-     - | Enter the following text and hit enter twice
-       | ``import time``
-       | ``for i in range(10):``
-       | ``print(i); time.sleep(1)``
-     - | ``Running...`` should appear in the python with
-       | increasing integers incrementally appearing above
-       | (not all at once after a delay).
-     - |checkbox|
-
-Python Input & Output
-^^^^^^^^^^^^^^^^^^^^^
-
-.. list-table::
-   :widths: 5 45 45 5
-   :header-rows: 1
-
-   * - #
-     - Test Action
-     - Expected Result
-     - ✅
-
-   * - 1
-     - Click the ``Logs`` window title
-     - Log pane is focused
-     - |checkbox|
-
-   * - 2
-     - Click empty whitespace in the ``Python Results`` window
-     - Python Results pane is focused
-     - |checkbox|
-
-   * - 3
-     - Click empty whitespace in the ``Python Input`` window
-     - Python Input pane is focused
-     - |checkbox|
-
-   * - 4
-     - | Enter the following text and press :kbd:`Enter` to run
-       | ``[i for i in __builtins__ if not i.startswith('_')]``
-     - | The results should appear pretty printed
-       | with each list element on it's own line:
-       |
-       |   >>> [i for i in __builtins__ if not i.startswith('_')]
-       |   [ 'abs',
-       |     'all',
-       |     'any',
-       |     'ascii'
-       |
-     - |checkbox|
-
-   * - 5
-     - | Enter the following text and press :kbd:`Enter` to run
-       | ``globals()``
-     - | The results should appear pretty printed
-     - |checkbox|
-
-   * - 6
-     - | With the cursor over the Python Output,
-       | use the mouse wheel to scroll up and down.
-     - | The output window should be able to scroll all
-       | the way to the beginning and end of the buffer.
-     - |checkbox|
-
-Early Startup
-^^^^^^^^^^^^^
-
-.. list-table::
-   :widths: 5 45 45 5
-   :header-rows: 1
-
-   * - #
-     - Test Action
-     - Expected Result
-     - ✅
-
-   * - 1
-     - | Start the pw console test mode by
-       | running ``pw console --test-mode``
-     - | Console starts up showing an ``All Logs`` window.
-     - |checkbox|
-
-   * - 2
-     - | Click the :guilabel:`All Logs` window title
-       | Press :kbd:`g` to jump to the top of the log history
-     - | These log messages should be at the top:
-       | ``DBG Adding plugins...``
-       | ``DBG Starting prompt_toolkit full-screen application...``
-       | ``DBG pw_console test-mode starting...``
-       | ``DBG pw_console.PwConsoleEmbed init complete``
-     - |checkbox|
-
-Quit Confirmation Dialog
-^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. list-table::
-   :widths: 5 45 45 5
-   :header-rows: 1
-
-   * - #
-     - Test Action
-     - Expected Result
-     - ✅
-
-   * - 1
-     - | Press :kbd:`Ctrl-d`
-     - | The quit dialog appears
-     - |checkbox|
-
-   * - 2
-     - | Press :kbd:`n`
-     - | The quit dialog disappears
-     - |checkbox|
-
-   * - 3
-     - | Press :kbd:`Ctrl-d`
-     - | The quit dialog appears
-     - |checkbox|
-
-   * - 4
-     - | Press :kbd:`y`
-     - | The console exits
-     - |checkbox|
-
-   * - 5
-     - | Restart the console and
-       | Press :kbd:`Ctrl-d` twice in quick succession.
-     - | The console exits
-     - |checkbox|
-
-   * - 6
-     - | Restart the console and Press :kbd:`F1`
-     - | The help window appears
-     - |checkbox|
-
-   * - 7
-     - | Press :kbd:`Ctrl-d`
-     - | The quit dialog appears on top of the help window
-     - |checkbox|
-
-   * - 8
-     - | Press :kbd:`n`
-     - | The quit dialog disappears and the help window is
-       | back in focus.
-     - |checkbox|
-
-   * - 9
-     - | Press :kbd:`q`
-     - | The help window disappears and the Python Input is in focus.
-     - |checkbox|
-
-   * - 10
-     - | Type some text into the Python Input.
-       | Press :kbd:`Home` or move the cursor to the
-       | beginning of the text you just entered.
-       | Press :kbd:`Ctrl-d`
-     - | Each :kbd:`Ctrl-d` press deletes one character
-     - |checkbox|
-
-   * - 11
-     - | Press :kbd:`Ctrl-c` to clear the Python Input text
-       | Press :kbd:`Ctrl-d`
-     - | The quit dialog appears.
-     - |checkbox|
-
-Add note to the commit message
-==============================
-
-Add a ``Testing:`` line to your commit message and mention the steps
-executed. For example:
-
-.. code-block:: text
-
-   Testing: Log Pane Steps 1-6
-
-.. |checkbox| raw:: html
-
-    <input type="checkbox">
diff --git a/pw_containers/BUILD b/pw_containers/BUILD
new file mode 100644
index 0000000..99926f4
--- /dev/null
+++ b/pw_containers/BUILD
@@ -0,0 +1,94 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_containers",
+    deps = [
+        ":flat_map",
+        ":vector",
+        ":intrusive_list",
+    ],
+)
+
+pw_cc_library(
+    name = "intrusive_list",
+    deps = [ "//pw_assert" ],
+    srcs = [
+        "intrusive_list.cc",
+        "public/pw_containers/internal/intrusive_list_impl.h",
+    ],
+    hdrs = [
+        "public/pw_containers/intrusive_list.h",
+    ],
+    includes = ["public"],
+)
+
+pw_cc_library(
+    name = "vector",
+    hdrs = [
+        "public/pw_containers/vector.h",
+    ],
+    includes = ["public"],
+)
+
+pw_cc_library(
+    name = "flat_map",
+    hdrs = [
+        "public/pw_containers/flat_map.h",
+    ],
+    includes = ["public"],
+)
+
+pw_cc_test(
+    name = "flat_map_test",
+    srcs = [
+        "flat_map_test.cc",
+    ],
+    deps = [
+        ":pw_containers",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "vector_test",
+    srcs = [
+        "vector_test.cc",
+    ],
+    deps = [
+        ":pw_containers",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "intrusive_list_test",
+    srcs = [
+        "intrusive_list_test.cc",
+    ],
+    deps = [
+        ":intrusive_list",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_containers/BUILD.bazel b/pw_containers/BUILD.bazel
deleted file mode 100644
index 6928f09..0000000
--- a/pw_containers/BUILD.bazel
+++ /dev/null
@@ -1,136 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_containers",
-    deps = [
-        ":flat_map",
-        ":intrusive_list",
-        ":vector",
-    ],
-)
-
-pw_cc_library(
-    name = "intrusive_list",
-    srcs = [
-        "intrusive_list.cc",
-        "public/pw_containers/internal/intrusive_list_impl.h",
-    ],
-    hdrs = [
-        "public/pw_containers/intrusive_list.h",
-    ],
-    includes = ["public"],
-    deps = ["//pw_assert"],
-)
-
-pw_cc_library(
-    name = "vector",
-    hdrs = [
-        "public/pw_containers/vector.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_assert",
-        "//pw_polyfill",
-    ],
-)
-
-pw_cc_library(
-    name = "filtered_view",
-    hdrs = ["public/pw_containers/filtered_view.h"],
-    includes = ["public"],
-    deps = ["//pw_assert"],
-)
-
-pw_cc_library(
-    name = "flat_map",
-    hdrs = ["public/pw_containers/flat_map.h"],
-    includes = ["public"],
-)
-
-pw_cc_library(
-    name = "to_array",
-    hdrs = ["public/pw_containers/to_array.h"],
-    includes = ["public"],
-)
-
-pw_cc_library(
-    name = "wrapped_iterator",
-    hdrs = ["public/pw_containers/wrapped_iterator.h"],
-    includes = ["public"],
-)
-
-pw_cc_test(
-    name = "filtered_view_test",
-    srcs = ["filtered_view_test.cc"],
-    deps = [
-        ":filtered_view",
-        ":intrusive_list",
-    ],
-)
-
-pw_cc_test(
-    name = "flat_map_test",
-    srcs = [
-        "flat_map_test.cc",
-    ],
-    deps = [
-        ":pw_containers",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "vector_test",
-    srcs = [
-        "vector_test.cc",
-    ],
-    deps = [
-        ":pw_containers",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "to_array_test",
-    srcs = ["to_array_test.cc"],
-    deps = [":to_array"],
-)
-
-pw_cc_test(
-    name = "wrapped_iterator_test",
-    srcs = ["wrapped_iterator_test.cc"],
-    deps = [":wrapped_iterator"],
-)
-
-pw_cc_test(
-    name = "intrusive_list_test",
-    srcs = [
-        "intrusive_list_test.cc",
-    ],
-    deps = [
-        ":intrusive_list",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_containers/BUILD.gn b/pw_containers/BUILD.gn
index 42835a6..d59ba21 100644
--- a/pw_containers/BUILD.gn
+++ b/pw_containers/BUILD.gn
@@ -18,7 +18,7 @@
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_unit_test/test.gni")
 
-config("public_include_path") {
+config("default_config") {
   include_dirs = [ "public" ]
 }
 
@@ -30,34 +30,19 @@
   ]
 }
 
-pw_source_set("filtered_view") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_containers/filtered_view.h" ]
-}
-
 pw_source_set("flat_map") {
-  public_configs = [ ":public_include_path" ]
+  public_configs = [ ":default_config" ]
   public = [ "public/pw_containers/flat_map.h" ]
 }
 
-pw_source_set("to_array") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_containers/to_array.h" ]
-}
-
 pw_source_set("vector") {
-  public_configs = [ ":public_include_path" ]
+  public_configs = [ ":default_config" ]
   public_deps = [ dir_pw_assert ]
   public = [ "public/pw_containers/vector.h" ]
 }
 
-pw_source_set("wrapped_iterator") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_containers/wrapped_iterator.h" ]
-}
-
 pw_source_set("intrusive_list") {
-  public_configs = [ ":public_include_path" ]
+  public_configs = [ ":default_config" ]
   public = [
     "public/pw_containers/internal/intrusive_list_impl.h",
     "public/pw_containers/intrusive_list.h",
@@ -68,20 +53,9 @@
 
 pw_test_group("tests") {
   tests = [
-    ":filtered_view_test",
     ":flat_map_test",
     ":intrusive_list_test",
-    ":to_array_test",
     ":vector_test",
-    ":wrapped_iterator_test",
-  ]
-}
-
-pw_test("filtered_view_test") {
-  sources = [ "filtered_view_test.cc" ]
-  deps = [
-    ":filtered_view",
-    ":intrusive_list",
   ]
 }
 
@@ -90,21 +64,11 @@
   deps = [ ":flat_map" ]
 }
 
-pw_test("to_array_test") {
-  sources = [ "to_array_test.cc" ]
-  deps = [ ":to_array" ]
-}
-
 pw_test("vector_test") {
   sources = [ "vector_test.cc" ]
   deps = [ ":vector" ]
 }
 
-pw_test("wrapped_iterator_test") {
-  sources = [ "wrapped_iterator_test.cc" ]
-  deps = [ ":wrapped_iterator" ]
-}
-
 pw_test("intrusive_list_test") {
   sources = [ "intrusive_list_test.cc" ]
   deps = [
diff --git a/pw_containers/CMakeLists.txt b/pw_containers/CMakeLists.txt
index e960e3d..3936249 100644
--- a/pw_containers/CMakeLists.txt
+++ b/pw_containers/CMakeLists.txt
@@ -14,124 +14,8 @@
 
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
 
-pw_add_module_library(pw_containers
-  PUBLIC_DEPS
-    pw_containers.flat_map
-    pw_containers.intrusive_list
-    pw_containers.vector
-)
-if(Zephyr_FOUND AND CONFIG_PIGWEED_CONTAINERS)
-  zephyr_link_libraries(pw_containers)
-endif()
-
-pw_add_module_library(pw_containers.filtered_view
-  HEADERS
-    public/pw_containers/filtered_view.h
-  PUBLIC_INCLUDES
-    public
-)
-
-pw_add_module_library(pw_containers.flat_map
-  HEADERS
-    public/pw_containers/flat_map.h
-  PUBLIC_INCLUDES
-    public
-)
-
-pw_add_module_library(pw_containers.to_array
-  HEADERS
-    public/pw_containers/to_array.h
-  PUBLIC_INCLUDES
-    public
-)
-
-pw_add_module_library(pw_containers.vector
-  HEADERS
-    public/pw_containers/vector.h
-  PUBLIC_INCLUDES
-    public
+pw_auto_add_simple_module(pw_containers
   PUBLIC_DEPS
     pw_assert
-)
-
-pw_add_module_library(pw_containers.wrapped_iterator
-  HEADERS
-    public/pw_containers/wrapped_iterator.h
-  PUBLIC_INCLUDES
-    public
-)
-
-pw_add_module_library(pw_containers.intrusive_list
-  HEADERS
-    public/pw_containers/internal/intrusive_list_impl.h
-    public/pw_containers/intrusive_list.h
-  PUBLIC_INCLUDES
-    public
-  SOURCES
-    intrusive_list.cc
-  PRIVATE_DEPS
-    pw_assert
-)
-
-pw_add_test(pw_containers.filtered_view_test
-  SOURCES
-    filtered_view_test.cc
-  DEPS
-    pw_containers.filtered_view
-    pw_containers.intrusive_list
-    pw_polyfill.span
-  GROUPS
-    modules
-    pw_containers
-)
-
-pw_add_test(pw_containers.flat_map_test
-  SOURCES
-    flat_map_test.cc
-  DEPS
-    pw_containers.flat_map
-  GROUPS
-    modules
-    pw_containers
-)
-
-pw_add_test(pw_containers.to_array_test
-  SOURCES
-    to_array_test.cc
-  DEPS
-    pw_containers.to_array
-  GROUPS
-    modules
-    pw_containers
-)
-
-pw_add_test(pw_containers.vector_test
-  SOURCES
-    vector_test.cc
-  DEPS
-    pw_containers.vector
-  GROUPS
-    modules
-    pw_containers
-)
-
-pw_add_test(pw_containers.wrapped_iterator_test
-  SOURCES
-    wrapped_iterator_test.cc
-  DEPS
-    pw_containers.wrapped_iterator
-  GROUPS
-    modules
-    pw_containers
-)
-
-pw_add_test(pw_containers.intrusive_list_test
-  SOURCES
-    intrusive_list_test.cc
-  DEPS
-    pw_containers.intrusive_list
-    pw_preprocessor
-  GROUPS
-    modules
-    pw_containers
+    pw_status
 )
diff --git a/pw_containers/Kconfig b/pw_containers/Kconfig
deleted file mode 100644
index 4b83c36..0000000
--- a/pw_containers/Kconfig
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-config PIGWEED_CONTAINERS
-    bool "Enable the Pigweed containers library (pw_containers)"
-    select PIGWEED_ASSERT
-    select PIGWEED_STATUS
diff --git a/pw_containers/OWNERS b/pw_containers/OWNERS
deleted file mode 100644
index d96cbc6..0000000
--- a/pw_containers/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-hepler@google.com
diff --git a/pw_containers/docs.rst b/pw_containers/docs.rst
index 07093db..f875ce4 100644
--- a/pw_containers/docs.rst
+++ b/pw_containers/docs.rst
@@ -18,36 +18,49 @@
 their maximum size at compile time. It also keeps code size small since
 function implementations are shared for all maximum sizes.
 
+
 pw::IntrusiveList
 =================
-IntrusiveList provides an embedded-friendly singly-linked intrusive list
-implementation. An intrusive list is a type of linked list that embeds the
-"next" pointer into the list object itself. This allows the construction of a
-linked list without the need to dynamically allocate list entries.
+IntrusiveList provides an embedded-friendly singly-linked list implementation.
+An intrusive list is a type of linked list that embeds the "next" pointer into
+the list object itself. This allows the construction of a linked list without
+the need to dynamically allocate list entries to point to the actual in-memory
+objects. In C, an intrusive list can be made by manually including the "next"
+pointer as a member of the object's struct. `pw::IntrusiveList` uses C++
+features to simplify the process of creating an intrusive list and intrusive
+list objects by providing a class that list elements can inherit from. This
+protects the "next" pointer from being accessed by the actual item that is
+stored in the linked list; only the `pw::IntrusiveList` class can modify the
+list.
 
-In C, an intrusive list can be made by manually including the "next" pointer as
-a member of the object's struct. ``pw::IntrusiveList`` uses C++ features to
-simplify the process of creating an intrusive list. ``pw::IntrusiveList``
-provides a class that list elements can inherit from. This protects the "next"
-pointer from being accessed by the item class, so only the ``pw::IntrusiveList``
-class can modify the list.
+
+pw::containers::FlatMap
+=======================
+FlatMap provides a simple, fixed-size associative array with lookup by key or
+value. ``pw::containers::FlatMap`` contains the same methods and features for
+looking up data as std::map. However, there are no methods that modify the
+underlying data.  The underlying array in ``pw::containers::FlatMap`` does not
+need to be sorted. During construction, ``pw::containers::FlatMap`` will
+perform a constexpr insertion sort.
+
 
 Usage
 -----
-While the API of ``pw::IntrusiveList`` is similar to a ``std::forward_list``,
-there are extra steps to creating objects that can be stored in this data
-structure. Objects that will be added to a ``IntrusiveList<T>`` must inherit
-from ``IntrusiveList<T>::Item``. They can inherit directly from it or inherit
-from it through another base class. When an item is instantiated and added to a
-linked list, the pointer to the object is added to the "next" pointer of
-whichever object is the current tail.
+While the API of `pw::IntrusiveList` is relatively similar to a
+``std::forward_list``, there are extra steps to creating objects that can be
+stored in this data structure. Objects that will be added to a
+``IntrusiveList<T>`` must inherit from ``IntrusiveList<T>::Item``. When an item
+is instantiated and added to a linked list, the pointer to the object is added
+to the "next" pointer of whichever object is the current tail.
+
 
 That means two key things:
 
- - An instantiated ``IntrusiveList<T>::Item`` must remain in scope for the
-   lifetime of the ``IntrusiveList`` it has been added to.
- - A linked list item CANNOT be included in two lists. Attempting to do so
-   results in an assert failure.
+ - An instantiated IntrusiveList::Item must remain in scope for the lifetime of
+   the IntrusiveList it has been added to.
+ - A linked list item CANNOT be included in two lists, as it is part of a
+   preexisting list and adding it to another implicitly breaks correctness
+   of the first list.
 
 .. code-block:: cpp
 
@@ -72,130 +85,21 @@
   squares.push_back(large);
 
   {
-    // When different_scope goes out of scope, it removes itself from the list.
+    // ERROR: When this goes out of scope, it will break the linked list.
     Square different_scope = Square(5);
     squares.push_back(&different_scope);
   }
 
-  for (const auto& square : squares) {
-    PW_LOG_INFO("Found a square with an area of %lu", square.Area());
+  for (auto& square : squares) {
+    PW_LOG_INFO("Found a square with an area of %ul", square.Area());
   }
 
-  // Like std::forward_list, an iterator is invalidated when the item it refers
-  // to is removed. It is *NOT* safe to remove items from a list while iterating
-  // over it in a range-based for loop.
-  for (const auto& square_bad_example : squares) {
-    if (square_bad_example.verticies() != 4) {
-      // BAD EXAMPLE of how to remove matching items from a singly linked list.
-      squares.remove(square_bad_example);  // NEVER DO THIS! THIS IS A BUG!
-    }
-  }
-
-  // To remove items while iterating, use an iterator to the previous item.
-  auto previous = squares.before_begin();
-  auto current = squares.begin();
-
-  while (current != squares.end()) {
-    if (current->verticies() != 4) {
-      current = squares.erase_after(previous);
-    } else {
-      previous = current;
-      ++current;
-    }
-  }
-
-pw::containers::FlatMap
-=======================
-FlatMap provides a simple, fixed-size associative array with lookup by key or
-value. ``pw::containers::FlatMap`` contains the same methods and features for
-looking up data as std::map. However, there are no methods that modify the
-underlying data.  The underlying array in ``pw::containers::FlatMap`` does not
-need to be sorted. During construction, ``pw::containers::FlatMap`` will
-perform a constexpr insertion sort.
-
-pw::containers::FilteredView
-============================
-``pw::containers::FilteredView`` provides a view of a container that only
-contains elements that match the specified filter. This class is similar to
-C++20's `std::ranges::filter_view
-<https://en.cppreference.com/w/cpp/ranges/filter_view>`_.
-
-To create a ``FilteredView``, pass a container and a filter object, which may be
-a lambda or class that implements ``operator()`` for the container's value type.
-
-.. code-block:: cpp
-
-  std::array<int, 99> kNumbers = {3, 1, 4, 1, ...};
-
-  for (int even : FilteredView(kNumbers, [](int n) { return n % 2 == 0; })) {
-    PW_LOG_INFO("This number is even: %d", even);
-  }
-
-pw::containers::WrappedIterator
-===============================
-``pw::containers::WrappedIterator`` is a class that makes it easy to wrap an
-existing iterator type. It reduces boilerplate by providing ``operator++``,
-``operator--``, ``operator==``, ``operator!=``, and the standard iterator
-aliases (``difference_type``, ``value_type``, etc.). It does not provide the
-dereference operator; that must be supplied by a derived class.
-
-To use it, create a class that derives from ``WrappedIterator`` and define
-``operator*()`` and ``operator->()`` as appropriate. The new iterator might
-apply a transformation to or access a member of the values provided by the
-original iterator. The following example defines an iterator that multiplies the
-values in an array by 2.
-
-.. code-block:: cpp
-
-  // Divides values in a std::array by two.
-  class DoubleIterator
-      : public pw::containers::WrappedIterator<DoubleIterator, const int*, int> {
-   public:
-    constexpr DoubleIterator(const int* it) : WrappedIterator(it) {}
-
-    int operator*() const { return value() * 2; }
-
-    // Don't define operator-> since this iterator returns by value.
-  };
-
-  constexpr std::array<int, 6> kArray{0, 1, 2, 3, 4, 5};
-
-  void SomeFunction {
-    for (DoubleIterator it(kArray.begin()); it != DoubleIterator(kArray.end()); ++it) {
-      // The iterator yields 0, 2, 4, 6, 8, 10 instead of the original values.
-    }
-  };
-
-``WrappedIterator`` may be used in concert with ``FilteredView`` to create a
-view that iterates over a matching values in a container and applies a
-transformation to the values. For example, it could be used with
-``FilteredView`` to filter a list of packets and yield only one field from the
-packet.
-
-The combination of ``FilteredView`` and ``WrappedIterator`` provides some basic
-functional programming features similar to (though much more cumbersome than)
-`generator expressions <https://www.python.org/dev/peps/pep-0289/>`_ (or `filter
-<https://docs.python.org/3/library/functions.html#filter>`_/`map
-<https://docs.python.org/3/library/functions.html#map>`_) in Python or streams
-in Java 8. ``WrappedIterator`` and ``FilteredView`` require no memory
-allocation, which is helpful when memory is too constrained to process the items
-into a new container.
-
-pw::containers::to_array
-========================
-``pw::containers::to_array`` is a C++14-compatible implementation of C++20's
-`std::to_array <https://en.cppreference.com/w/cpp/container/array/to_array>`_.
-It converts a C array to a ``std::array``.
 
 Compatibility
 =============
+* C
 * C++17
 
 Dependencies
 ============
 * ``pw_span``
-
-Zephyr
-======
-To enable ``pw_containers`` for Zephyr add ``CONFIG_PIGWEED_CONTAINERS=y`` to
-the project's configuration.
diff --git a/pw_containers/filtered_view_test.cc b/pw_containers/filtered_view_test.cc
deleted file mode 100644
index 1583981..0000000
--- a/pw_containers/filtered_view_test.cc
+++ /dev/null
@@ -1,176 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_containers/filtered_view.h"
-
-#include <array>
-#include <span>
-
-#include "gtest/gtest.h"
-#include "pw_containers/intrusive_list.h"
-
-namespace pw::containers {
-namespace {
-
-struct Item : IntrusiveList<Item>::Item {
-  constexpr Item(int x) : value(x) {}
-
-  int value;
-};
-
-constexpr std::array<int, 6> kArray{0, 1, 2, 3, 4, 5};
-
-TEST(FilteredView, Array_MatchSubset) {
-  FilteredView view(kArray, [](int x) { return x == 3 || x == 5; });
-
-  auto it = view.begin();
-  ASSERT_EQ(*it, 3);
-  ++it;
-  ASSERT_EQ(*it, 5);
-  ++it;
-  EXPECT_EQ(it, view.end());
-}
-
-TEST(FilteredView, Array_MatchAll) {
-  FilteredView view(kArray, [](int) { return true; });
-
-  std::array<bool, 6> found = {};
-  for (int value : view) {
-    found[value] = true;
-  }
-  EXPECT_TRUE(
-      std::all_of(found.begin(), found.end(), [](bool b) { return b; }));
-}
-
-TEST(FilteredView, Array_MatchNone) {
-  for (int unused : FilteredView(kArray, [](int) { return false; })) {
-    static_cast<void>(unused);
-    FAIL();
-  }
-}
-
-TEST(FilteredView, EmptyContainer) {
-  constexpr std::array<int, 0> nothing{};
-  for (int unused : FilteredView(nothing, [](int) { return true; })) {
-    static_cast<void>(unused);
-    FAIL();
-  }
-
-  IntrusiveList<Item> intrusive_list;
-  for (const Item& unused :
-       FilteredView(nothing, [](const Item&) { return true; })) {
-    static_cast<void>(unused);
-    FAIL();
-  }
-}
-
-TEST(FilteredView, IntrusiveList_MatchSubset) {
-  Item item_1{1};
-  Item item_2{2};
-  Item item_3{3};
-  IntrusiveList<Item> intrusive_list({&item_1, &item_2, &item_3});
-
-  FilteredView view(intrusive_list,
-                    [](const Item& i) { return i.value % 2 != 0; });
-
-  auto it = view.begin();
-  ASSERT_EQ(it->value, 1);
-  ++it;
-  ASSERT_EQ((*it).value, 3);
-  ++it;
-  EXPECT_EQ(it, view.end());
-}
-
-TEST(FilteredView, IntrusiveList_MatchAll) {
-  Item item_1{0};
-  Item item_2{1};
-  Item item_3{2};
-  IntrusiveList<Item> intrusive_list({&item_1, &item_2, &item_3});
-
-  std::array<bool, 3> found = {};
-
-  for (const Item& item :
-       FilteredView(intrusive_list, [](const Item&) { return true; })) {
-    found[item.value] = true;
-  }
-  EXPECT_TRUE(
-      std::all_of(found.begin(), found.end(), [](bool b) { return b; }));
-}
-
-TEST(FilteredView, IntrusiveList_MatchNone) {
-  Item item_1{0};
-  Item item_2{1};
-  Item item_3{2};
-  IntrusiveList<Item> intrusive_list({&item_1, &item_2, &item_3});
-
-  for (const Item& unused :
-       FilteredView(kArray, [](const Item&) { return false; })) {
-    static_cast<void>(unused);
-    FAIL();
-  }
-}
-
-TEST(FilteredView, Front_OneElement) {
-  EXPECT_EQ(FilteredView(kArray, [](int x) { return x == 0; }).front(), 0);
-}
-
-TEST(FilteredView, Back_OneElement) {
-  EXPECT_EQ(FilteredView(kArray, [](int x) { return x == 0; }).back(), 0);
-}
-
-TEST(FilteredView, Front_MultipleElements) {
-  EXPECT_EQ(
-      FilteredView(kArray, [](int x) { return x == 3 || x == 5; }).front(), 3);
-}
-
-TEST(FilteredView, Back_MultipleElements) {
-  EXPECT_EQ(FilteredView(kArray, [](int x) { return x == 3 || x == 5; }).back(),
-            5);
-}
-
-TEST(FilteredView, Size_Empty) {
-  EXPECT_EQ(FilteredView(kArray, [](int x) { return x < 0; }).size(), 0u);
-
-  EXPECT_TRUE(FilteredView(kArray, [](int x) { return x < 0; }).empty());
-
-  constexpr std::array<int, 0> empty{};
-  FilteredView empty_view(empty, [](const Item&) { return true; });
-  EXPECT_EQ(empty_view.size(), 0u);
-  EXPECT_TRUE(empty_view.empty());
-}
-
-TEST(FilteredView, Size_OneElement) {
-  EXPECT_EQ(FilteredView(kArray, [](int x) { return x == 0; }).size(), 1u);
-  EXPECT_EQ(FilteredView(kArray, [](int x) { return x == 3; }).size(), 1u);
-  EXPECT_EQ(FilteredView(kArray, [](int x) { return x == 5; }).size(), 1u);
-
-  EXPECT_FALSE(FilteredView(kArray, [](int x) { return x == 5; }).empty());
-}
-
-TEST(FilteredView, Size_MultipleElements) {
-  EXPECT_EQ(FilteredView(kArray, [](int x) { return x <= 1; }).size(), 2u);
-  EXPECT_EQ(FilteredView(kArray, [](int x) { return x > 1; }).size(), 4u);
-  EXPECT_EQ(FilteredView(kArray, [](int x) { return x < 5; }).size(), 5u);
-
-  EXPECT_FALSE(FilteredView(kArray, [](int x) { return x < 5; }).empty());
-}
-
-TEST(FilteredView, Size_AllElements) {
-  EXPECT_EQ(FilteredView(kArray, [](int) { return true; }).size(), 6u);
-
-  EXPECT_FALSE(FilteredView(kArray, [](int x) { return x < 5; }).empty());
-}
-
-}  // namespace
-}  // namespace pw::containers
diff --git a/pw_containers/intrusive_list.cc b/pw_containers/intrusive_list.cc
index c005b50..ba35f61 100644
--- a/pw_containers/intrusive_list.cc
+++ b/pw_containers/intrusive_list.cc
@@ -14,10 +14,12 @@
 
 #include "pw_containers/intrusive_list.h"
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 
 namespace pw::intrusive_list_impl {
 
+List::Item::~Item() { unlist(); }
+
 void List::Item::unlist(Item* prev) {
   if (prev == nullptr) {
     prev = previous();
diff --git a/pw_containers/intrusive_list_test.cc b/pw_containers/intrusive_list_test.cc
index 2681cc0..9f56c1d 100644
--- a/pw_containers/intrusive_list_test.cc
+++ b/pw_containers/intrusive_list_test.cc
@@ -220,39 +220,6 @@
   EXPECT_EQ(i, PW_ARRAY_SIZE(item_array) + 1);
 }
 
-TEST(IntrusiveList, InsertAfterBeforeBegin) {
-  // Create a test item to insert at the beginning of the list.
-  constexpr int kMagicValue = 42;
-  TestItem inserted_item(kMagicValue);
-
-  // Create initial values to fill in the start/end.
-  TestItem item_array[20];
-
-  IntrusiveList<TestItem> list;
-  // Fill the list with TestItem objects that have a value of zero.
-  for (size_t i = 0; i < PW_ARRAY_SIZE(item_array); ++i) {
-    item_array[i].SetNumber(0);
-    list.push_back(item_array[i]);
-  }
-
-  auto it = list.insert_after(list.before_begin(), inserted_item);
-
-  // Ensure the returned iterator from insert_after is the newly inserted
-  // element.
-  EXPECT_EQ(it->GetNumber(), kMagicValue);
-
-  // Ensure the value is at the beginning of the list.
-  size_t i = 0;
-  for (TestItem& item : list) {
-    if (item.GetNumber() == kMagicValue) {
-      EXPECT_EQ(i, static_cast<size_t>(0));
-    } else {
-      EXPECT_EQ(item.GetNumber(), 0);
-    }
-    i++;
-  }
-}
-
 TEST(IntrusiveList, PushFront) {
   constexpr int kMagicValue = 42;
   TestItem pushed_item(kMagicValue);
@@ -409,26 +376,10 @@
   }
 }
 
-TEST(IntrusiveList, CompareConstAndNonConstIterator) {
-  IntrusiveList<TestItem> list;
-  EXPECT_EQ(list.end(), list.cend());
-}
-
-#if defined(PW_COMPILE_FAIL_TEST_incompatible_iterator_types)
-
-struct OtherItem : public IntrusiveList<OtherItem>::Item {};
-
-TEST(IntrusiveList, CompareConstAndNonConstIterator_CompilationFails) {
-  IntrusiveList<TestItem> list;
-  IntrusiveList<OtherItem> list2;
-  static_cast<void>(list.end() == list2.end());
-}
-
-#endif
-
 // TODO(pwbug/47): These tests should fail to compile, enable when no-compile
 // tests are set up in Pigweed.
-#if defined(PW_COMPILE_FAIL_TEST_cannot_modify_through_const_iterator)
+#define NO_COMPILE_TESTS 0
+#if NO_COMPILE_TESTS
 TEST(IntrusiveList, ConstIteratorModify) {
   TestItem item1(1);
   TestItem item2(99);
@@ -445,7 +396,7 @@
     it++;
   }
 }
-#endif  // Compile failure test
+#endif  // NO_COMPILE_TESTS
 
 // TODO(pwbug/88): These tests should trigger a CHECK failure. This requires
 // using a testing version of pw_assert.
@@ -652,51 +603,5 @@
   EXPECT_EQ(list.size(), static_cast<size_t>(0));
 }
 
-// Test that a list of items derived from a different Item class can be created.
-class DerivedTestItem : public TestItem {};
-
-TEST(InstrusiveList, AddItemsOfDerivedClassToList) {
-  IntrusiveList<TestItem> list;
-
-  DerivedTestItem item1;
-  list.push_front(item1);
-
-  TestItem item2;
-  list.push_front(item2);
-
-  EXPECT_EQ(2u, list.size());
-}
-
-TEST(InstrusiveList, ListOfDerivedClassItems) {
-  IntrusiveList<DerivedTestItem> derived_from_compatible_item_type;
-
-  DerivedTestItem item1;
-  derived_from_compatible_item_type.push_front(item1);
-
-  EXPECT_EQ(1u, derived_from_compatible_item_type.size());
-
-// TODO(pwbug/47): Make these proper automated compilation failure tests.
-#if defined(PW_COMPILE_FAIL_TEST_cannot_add_base_class_to_derived_class_list)
-  TestItem item2;
-  derived_from_compatible_item_type.push_front(item2);
-#endif
-}
-
-#if defined(PW_COMPILE_FAIL_TEST_incompatibile_item_type)
-
-struct Foo {};
-
-class BadItem : public IntrusiveList<Foo>::Item {};
-
-[[maybe_unused]] IntrusiveList<BadItem> derived_from_incompatible_item_type;
-
-#elif defined(PW_COMPILE_FAIL_TEST_does_not_inherit_from_item)
-
-struct NotAnItem {};
-
-[[maybe_unused]] IntrusiveList<NotAnItem> list;
-
-#endif
-
 }  // namespace
 }  // namespace pw
diff --git a/pw_containers/public/pw_containers/filtered_view.h b/pw_containers/public/pw_containers/filtered_view.h
deleted file mode 100644
index 7daf821..0000000
--- a/pw_containers/public/pw_containers/filtered_view.h
+++ /dev/null
@@ -1,165 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstddef>
-#include <iterator>
-
-#include "pw_assert/assert.h"
-
-namespace pw::containers {
-
-// FilteredView supports iterating over only elements that match a filter in a
-// container. FilteredView works with any container with an incrementable
-// iterator. The back() function currently requires a bidirectional iterator.
-//
-// FilteredView is similar to C++20's std::filter_view.
-template <typename Container, typename Filter>
-class FilteredView {
- public:
-  // Iterator that only moves to elements that match the provided filter.
-  class iterator {
-   public:
-    using difference_type = std::ptrdiff_t;
-    using value_type = typename Container::value_type;
-    using pointer = typename Container::pointer;
-    using reference = typename Container::reference;
-    using iterator_category = std::bidirectional_iterator_tag;
-
-    constexpr iterator() : view_(nullptr), it_(0) {}
-
-    iterator& operator++();
-
-    iterator operator++(int) {
-      iterator original = *this;
-      operator++();
-      return original;
-    }
-
-    iterator& operator--();
-
-    iterator operator--(int) {
-      iterator original = *this;
-      operator--();
-      return original;
-    }
-
-    const auto& operator*() const { return value(); }
-
-    const auto* operator->() const { return &value(); }
-
-    constexpr bool operator==(const iterator& other) const {
-      return view_ == other.view_ && it_ == other.it_;
-    }
-
-    constexpr bool operator!=(const iterator& other) const {
-      return !(*this == other);
-    }
-
-   private:
-    friend class FilteredView;
-
-    enum EndIterator { kEnd };
-
-    explicit iterator(const FilteredView& view)
-        : view_(&view), it_(view.container_.begin()) {
-      FindMatch();
-    }
-
-    iterator(const FilteredView& view, EndIterator)
-        : view_(&view), it_(view.container_.end()) {}
-
-    // Accesses the value referred to by this iterator.
-    const auto& value() const { return *it_; }
-
-    // Iterates until a match is found, up to end().
-    void FindMatch();
-
-    bool MatchesItem(const value_type& value) const {
-      return view_->filter_(value);
-    }
-
-    const FilteredView* view_;
-    typename Container::const_iterator it_;
-  };
-
-  using const_iterator = iterator;
-
-  template <typename... FilterArgs>
-  constexpr FilteredView(const Container& container, Filter&& filter)
-      : container_(container), filter_(std::move(filter)) {}
-
-  constexpr FilteredView(const FilteredView&) = delete;
-  constexpr FilteredView& operator=(const FilteredView&) = delete;
-
-  const auto& operator[](size_t index) const {
-    auto it = begin();
-    std::advance(it, index);
-    return *it;
-  }
-
-  // Accesses the first matching element. Invalid if empty().
-  const auto& front() const { return *begin(); }
-
-  // Accesses the last matching element. Invalid if empty().
-  const auto& back() const { return *std::prev(end()); }
-
-  // The number of elements in the container that match the filter.
-  size_t size() const { return std::distance(begin(), end()); }
-
-  bool empty() const { return begin() == end(); }
-
-  iterator begin() const { return iterator(*this); }
-  iterator end() const { return iterator(*this, iterator::kEnd); }
-
- private:
-  const Container& container_;
-  Filter filter_;
-};
-
-template <typename Container, typename Filter>
-void FilteredView<Container, Filter>::iterator::FindMatch() {
-  for (; it_ != view_->container_.end(); ++it_) {
-    if (MatchesItem(*it_)) {
-      break;
-    }
-  }
-}
-
-template <typename Container, typename Filter>
-typename FilteredView<Container, Filter>::iterator&
-FilteredView<Container, Filter>::iterator::operator++() {
-  PW_ASSERT(it_ != view_->container_.end());
-
-  ++it_;
-  FindMatch();
-  return *this;
-}
-
-template <typename Container, typename Filter>
-typename FilteredView<Container, Filter>::iterator&
-FilteredView<Container, Filter>::iterator::operator--() {
-  decltype(it_) new_it = view_->container_.end();
-  while (new_it != view_->container_.begin()) {
-    --new_it;
-    if (MatchesItem(*new_it)) {
-      it_ = new_it;
-      return *this;
-    }
-  }
-
-  PW_ASSERT(false);
-}
-
-}  // namespace pw::containers
diff --git a/pw_containers/public/pw_containers/internal/intrusive_list_impl.h b/pw_containers/public/pw_containers/internal/intrusive_list_impl.h
index c77a2ca..13bd448 100644
--- a/pw_containers/public/pw_containers/internal/intrusive_list_impl.h
+++ b/pw_containers/public/pw_containers/internal/intrusive_list_impl.h
@@ -13,9 +13,7 @@
 // the License.
 #pragma once
 
-#include <cstddef>
 #include <iterator>
-#include <type_traits>
 
 namespace pw {
 
@@ -27,7 +25,7 @@
 template <typename T, typename I>
 class Iterator {
  public:
-  using difference_type = std::ptrdiff_t;
+  using difference_type = void;
   using value_type = std::remove_cv_t<T>;
   using pointer = T*;
   using reference = T&;
@@ -52,20 +50,14 @@
   constexpr const T* operator->() const { return static_cast<T*>(item_); }
   constexpr T* operator->() { return static_cast<T*>(item_); }
 
-  template <typename U, typename J>
-  constexpr bool operator==(const Iterator<U, J>& rhs) const {
+  constexpr bool operator==(const Iterator& rhs) const {
     return item_ == rhs.item_;
   }
-
-  template <typename U, typename J>
-  constexpr bool operator!=(const Iterator<U, J>& rhs) const {
+  constexpr bool operator!=(const Iterator& rhs) const {
     return item_ != rhs.item_;
   }
 
  private:
-  template <typename, typename>
-  friend class Iterator;
-
   template <typename>
   friend class ::pw::IntrusiveList;
 
@@ -81,7 +73,15 @@
    protected:
     constexpr Item() : Item(this) {}
 
-    ~Item() { unlist(); }
+    bool unlisted() const { return this == next_; }
+
+    // Unlink this from the list it is apart of, if any. Specifying prev saves
+    // calling previous(), which requires looping around the cycle.
+    void unlist(Item* prev = nullptr);
+
+    Item* previous();  // Note: O(n) since it loops around the cycle.
+
+    ~Item();
 
    private:
     friend class List;
@@ -91,14 +91,6 @@
 
     constexpr Item(Item* next) : next_(next) {}
 
-    bool unlisted() const { return this == next_; }
-
-    // Unlink this from the list it is apart of, if any. Specifying prev saves
-    // calling previous(), which requires looping around the cycle.
-    void unlist(Item* prev = nullptr);
-
-    Item* previous();  // Note: O(n) since it loops around the cycle.
-
     // The next pointer. Unlisted items must be self-cycles (next_ == this).
     Item* next_;
   };
@@ -169,21 +161,5 @@
   }
 }
 
-// Gets the element type from an Item. This is used to check that an
-// IntrusiveList element class inherits from Item, either directly or through
-// another class.
-template <typename T, bool kIsItem = std::is_base_of<List::Item, T>()>
-struct GetListElementTypeFromItem {
-  using Type = void;
-};
-
-template <typename T>
-struct GetListElementTypeFromItem<T, true> {
-  using Type = typename T::PwIntrusiveListElementType;
-};
-
-template <typename T>
-using ElementTypeFromItem = typename GetListElementTypeFromItem<T>::Type;
-
 }  // namespace intrusive_list_impl
 }  // namespace pw
diff --git a/pw_containers/public/pw_containers/intrusive_list.h b/pw_containers/public/pw_containers/intrusive_list.h
index 6bd4860..b73effe 100644
--- a/pw_containers/public/pw_containers/intrusive_list.h
+++ b/pw_containers/public/pw_containers/intrusive_list.h
@@ -1,4 +1,4 @@
-// Copyright 2021 The Pigweed Authors
+// Copyright 2020 The Pigweed Authors
 //
 // 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
@@ -53,20 +53,8 @@
 class IntrusiveList {
  public:
   class Item : public intrusive_list_impl::List::Item {
-   public:
-    Item(const Item&) = delete;
-    Item& operator=(const Item&) = delete;
-
    protected:
     constexpr Item() = default;
-
-   private:
-    // GetListElementTypeFromItem is used to find the element type from an item.
-    // It is used to ensure list items inherit from the correct Item type.
-    template <typename, bool>
-    friend struct intrusive_list_impl::GetListElementTypeFromItem;
-
-    using PwIntrusiveListElementType = T;
   };
 
   using element_type = T;
@@ -108,7 +96,7 @@
   void push_back(T& item) { list_.insert_after(list_.before_end(), item); }
 
   iterator insert_after(iterator pos, T& item) {
-    list_.insert_after(pos.item_, item);
+    list_.insert_after(&(*pos), item);
     return iterator(&item);
   }
 
@@ -117,7 +105,7 @@
 
   // Removes the item following pos from the list. The item is not destructed.
   iterator erase_after(iterator pos) {
-    list_.erase_after(pos.item_);
+    list_.erase_after(&(*pos));
     return ++pos;
   }
 
@@ -166,9 +154,8 @@
   // defined when the IntrusiveList<T> class is instantiated.
   static constexpr void CheckItemType() {
     static_assert(
-        std::is_base_of<intrusive_list_impl::ElementTypeFromItem<T>, T>(),
-        "IntrusiveList items must be derived from IntrusiveList<T>::Item, "
-        "where T is the item or one of its bases.");
+        std::is_base_of<Item, T>(),
+        "IntrusiveList items must be derived from IntrusiveList<T>::Item");
   }
 
   intrusive_list_impl::List list_;
diff --git a/pw_containers/public/pw_containers/to_array.h b/pw_containers/public/pw_containers/to_array.h
deleted file mode 100644
index 63a107f..0000000
--- a/pw_containers/public/pw_containers/to_array.h
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <array>
-#include <cstddef>
-#include <type_traits>
-#include <utility>
-
-namespace pw {
-namespace containers {
-namespace impl {
-
-template <typename T, size_t kSize, size_t... kIndices>
-constexpr std::array<std::remove_cv_t<T>, kSize> CopyArray(
-    const T (&values)[kSize], std::index_sequence<kIndices...>) {
-  return {{values[kIndices]...}};
-}
-
-template <typename T, size_t kSize, size_t... kIndices>
-constexpr std::array<std::remove_cv_t<T>, kSize> MoveArray(
-    T (&&values)[kSize], std::index_sequence<kIndices...>) {
-  return {{std::move(values[kIndices])...}};
-}
-
-}  // namespace impl
-
-// pw::containers::to_array is C++14-compatible implementation of C++20's
-// std::to_array.
-template <typename T, size_t kSize>
-constexpr std::array<std::remove_cv_t<T>, kSize> to_array(T (&values)[kSize]) {
-  return impl::CopyArray(values, std::make_index_sequence<kSize>{});
-}
-
-template <typename T, size_t kSize>
-constexpr std::array<std::remove_cv_t<T>, kSize> to_array(T (&&values)[kSize]) {
-  return impl::MoveArray(std::move(values), std::make_index_sequence<kSize>{});
-}
-
-}  // namespace containers
-}  // namespace pw
diff --git a/pw_containers/public/pw_containers/vector.h b/pw_containers/public/pw_containers/vector.h
index 3438074..1d05fd1 100644
--- a/pw_containers/public/pw_containers/vector.h
+++ b/pw_containers/public/pw_containers/vector.h
@@ -65,7 +65,7 @@
 // the maximum size in a variable. This allows Vectors to be used without having
 // to know their maximum size at compile time. It also keeps code size small
 // since function implementations are shared for all maximum sizes.
-template <typename T, size_t kMaxSize = vector_impl::kGeneric>
+template <typename T, size_t max_size = vector_impl::kGeneric>
 class Vector : public Vector<T, vector_impl::kGeneric> {
  public:
   using typename Vector<T, vector_impl::kGeneric>::value_type;
@@ -81,37 +81,37 @@
   using typename Vector<T, vector_impl::kGeneric>::const_reverse_iterator;
 
   // Construct
-  Vector() noexcept : Vector<T, vector_impl::kGeneric>(kMaxSize) {}
+  Vector() noexcept : Vector<T, vector_impl::kGeneric>(max_size) {}
 
   Vector(size_type count, const T& value)
-      : Vector<T, vector_impl::kGeneric>(kMaxSize, count, value) {}
+      : Vector<T, vector_impl::kGeneric>(max_size, count, value) {}
 
   explicit Vector(size_type count)
-      : Vector<T, vector_impl::kGeneric>(kMaxSize, count, T()) {}
+      : Vector<T, vector_impl::kGeneric>(max_size, count, T()) {}
 
   template <
       typename Iterator,
       typename...,
       typename = std::enable_if_t<vector_impl::IsIterator<Iterator>::value>>
   Vector(Iterator first, Iterator last)
-      : Vector<T, vector_impl::kGeneric>(kMaxSize, first, last) {}
+      : Vector<T, vector_impl::kGeneric>(max_size, first, last) {}
 
   Vector(const Vector& other)
-      : Vector<T, vector_impl::kGeneric>(kMaxSize, other) {}
+      : Vector<T, vector_impl::kGeneric>(max_size, other) {}
 
   template <size_t kOtherMaxSize>
   Vector(const Vector<T, kOtherMaxSize>& other)
-      : Vector<T, vector_impl::kGeneric>(kMaxSize, other) {}
+      : Vector<T, vector_impl::kGeneric>(max_size, other) {}
 
   Vector(Vector&& other) noexcept
-      : Vector<T, vector_impl::kGeneric>(kMaxSize, std::move(other)) {}
+      : Vector<T, vector_impl::kGeneric>(max_size, std::move(other)) {}
 
   template <size_t kOtherMaxSize>
   Vector(Vector<T, kOtherMaxSize>&& other) noexcept
-      : Vector<T, vector_impl::kGeneric>(kMaxSize, std::move(other)) {}
+      : Vector<T, vector_impl::kGeneric>(max_size, std::move(other)) {}
 
   Vector(std::initializer_list<T> list)
-      : Vector<T, vector_impl::kGeneric>(kMaxSize, list) {}
+      : Vector<T, vector_impl::kGeneric>(max_size, list) {}
 
   Vector& operator=(const Vector& other) {
     Vector<T>::assign(other.begin(), other.end());
@@ -145,7 +145,7 @@
  private:
   friend class Vector<T, vector_impl::kGeneric>;
 
-  static_assert(kMaxSize <= std::numeric_limits<size_type>::max());
+  static_assert(max_size <= std::numeric_limits<size_type>::max());
 
   // Provides access to the underlying array as an array of T.
 #ifdef __cpp_lib_launder
@@ -166,7 +166,7 @@
   // The alignas specifier is required ensure that a zero-length array is
   // aligned the same as an array with elements.
   alignas(T) std::array<std::aligned_storage_t<sizeof(T), alignof(T)>,
-                        kMaxSize> array_;
+                        max_size> array_;
 };
 
 // Defines the generic-sized Vector<T> specialization, which serves as the base
@@ -196,7 +196,7 @@
   using const_reverse_iterator = std::reverse_iterator<const_iterator>;
 
   // A vector without an explicit maximum size (Vector<T>) cannot be constructed
-  // directly. Instead, construct a Vector<T, kMaxSize>. Vectors of any max size
+  // directly. Instead, construct a Vector<T, max_size>. Vectors of any max size
   // can be used through a Vector<T> pointer or reference.
 
   // Assign
diff --git a/pw_containers/public/pw_containers/wrapped_iterator.h b/pw_containers/public/pw_containers/wrapped_iterator.h
deleted file mode 100644
index 98807a7..0000000
--- a/pw_containers/public/pw_containers/wrapped_iterator.h
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstddef>
-#include <iterator>
-
-namespace pw::containers {
-
-// Wraps an iterator with another iterator. This is helpful for creating an
-// iterator that yields items derived from the original iterator's type. For
-// example, the derived iterator might return a member of or a value calculated
-// from the original iterator's value.
-//
-// Classes inherit from this and provide operator* and operator-> as
-// appropriate.
-template <typename Impl, typename Iterator, typename ValueType>
-class WrappedIterator {
- public:
-  using difference_type = std::ptrdiff_t;
-  using value_type = ValueType;
-  using pointer = ValueType*;
-  using reference = ValueType&;
-  using iterator_category = std::bidirectional_iterator_tag;
-
-  constexpr WrappedIterator(const WrappedIterator&) = default;
-  constexpr WrappedIterator& operator=(const WrappedIterator&) = default;
-
-  Impl& operator++() {
-    ++iterator_;
-    return static_cast<Impl&>(*this);
-  }
-
-  Impl operator++(int) {
-    Impl original = static_cast<const Impl&>(*this);
-    ++iterator_;
-    return original;
-  }
-
-  Impl& operator--() {
-    --iterator_;
-    return static_cast<Impl&>(*this);
-  }
-
-  Impl operator--(int) {
-    Impl original = static_cast<const Impl&>(*this);
-    --iterator_;
-    return original;
-  }
-
-  constexpr bool operator==(const WrappedIterator& other) const {
-    return iterator_ == other.iterator_;
-  }
-
-  constexpr bool operator!=(const WrappedIterator& other) const {
-    return !(*this == other);
-  }
-
- protected:
-  constexpr WrappedIterator() = default;
-
-  constexpr WrappedIterator(const Iterator& it) : iterator_(it) {}
-
-  const auto& value() const { return *iterator_; }
-  const auto* ptr() const { return iterator_.operator->(); }
-
- private:
-  Iterator iterator_;
-};
-
-}  // namespace pw::containers
diff --git a/pw_containers/to_array_test.cc b/pw_containers/to_array_test.cc
deleted file mode 100644
index 783b078..0000000
--- a/pw_containers/to_array_test.cc
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_containers/to_array.h"
-
-#include "gtest/gtest.h"
-
-namespace pw::containers {
-namespace {
-
-TEST(Array, ToArray_StringLiteral) {
-  std::array<char, sizeof("literally!")> array = to_array("literally!");
-  EXPECT_EQ(std::strcmp(array.data(), "literally!"), 0);
-}
-
-TEST(Array, ToArray_Inline) {
-  constexpr std::array<int, 3> kArray = to_array({1, 2, 3});
-  static_assert(kArray.size() == 3);
-  EXPECT_EQ(kArray[0], 1);
-}
-
-TEST(Array, ToArray_Array) {
-  char c_array[] = "array!";
-  std::array<char, sizeof("array!")> array = to_array(c_array);
-  EXPECT_EQ(std::strcmp(array.data(), "array!"), 0);
-}
-
-struct MoveOnly {
-  MoveOnly(char ch) : value(ch) {}
-
-  MoveOnly(const MoveOnly&) = delete;
-  MoveOnly& operator=(const MoveOnly&) = delete;
-
-  MoveOnly(MoveOnly&&) = default;
-  MoveOnly& operator=(MoveOnly&&) = default;
-
-  char value;
-};
-
-TEST(Array, ToArray_MoveOnly) {
-  MoveOnly c_array[]{MoveOnly('a'), MoveOnly('b')};
-  std::array<MoveOnly, 2> array = to_array(std::move(c_array));
-  EXPECT_EQ(array[0].value, 'a');
-  EXPECT_EQ(array[1].value, 'b');
-}
-
-}  // namespace
-}  // namespace pw::containers
diff --git a/pw_containers/vector_test.cc b/pw_containers/vector_test.cc
index 30d5e17..9471114 100644
--- a/pw_containers/vector_test.cc
+++ b/pw_containers/vector_test.cc
@@ -145,7 +145,6 @@
     EXPECT_EQ(vector[i].value, 421);
   }
 
-  // NOLINTNEXTLINE(bugprone-use-after-move)
   for (size_t i = 0; i < origin_vector.size(); ++i) {
     EXPECT_EQ(origin_vector[i].value, MoveOnly::kDeleted);
   }
@@ -254,7 +253,6 @@
     EXPECT_EQ(vector[i].value, 421);
   }
 
-  // NOLINTNEXTLINE(bugprone-use-after-move)
   for (size_t i = 0; i < origin_vector.size(); ++i) {
     EXPECT_EQ(origin_vector[i].value, MoveOnly::kDeleted);
   }
@@ -335,7 +333,6 @@
 
     EXPECT_EQ(vector.size(), 1u);
     EXPECT_EQ(vector.front().value, 99);
-    // NOLINTNEXTLINE(bugprone-use-after-move)
     EXPECT_EQ(value.value, 0);
   }
 
diff --git a/pw_containers/wrapped_iterator_test.cc b/pw_containers/wrapped_iterator_test.cc
deleted file mode 100644
index a5ea858..0000000
--- a/pw_containers/wrapped_iterator_test.cc
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_containers/wrapped_iterator.h"
-
-#include <array>
-
-#include "gtest/gtest.h"
-
-namespace pw::containers {
-namespace {
-
-class HalfIterator : public WrappedIterator<HalfIterator, const int*, int> {
- public:
-  constexpr HalfIterator(const int* it) : WrappedIterator(it) {}
-
-  int operator*() const { return value() / 2; }
-};
-
-constexpr std::array<int, 6> kArray{0, 2, 4, 6, 8, 10};
-
-TEST(WrappedIterator, IterateForwards) {
-  int expected = 0;
-  for (HalfIterator it(kArray.begin()); it != HalfIterator(kArray.end());
-       ++it) {
-    EXPECT_EQ(*it, expected);
-    expected += 1;
-  }
-}
-
-TEST(WrappedIterator, IterateBackwards) {
-  HalfIterator it(kArray.end());
-
-  int expected = 5;
-  do {
-    --it;
-    EXPECT_EQ(*it, expected);
-    expected -= 1;
-  } while (it != HalfIterator(kArray.begin()));
-}
-
-TEST(WrappedIterator, PostIncrement) {
-  HalfIterator it(kArray.begin());
-  EXPECT_EQ(it++, HalfIterator(kArray.begin()));
-  EXPECT_EQ(it, HalfIterator(kArray.begin() + 1));
-  EXPECT_EQ(*it, 1);
-}
-
-TEST(WrappedIterator, PreIncrement) {
-  HalfIterator it(kArray.begin());
-  EXPECT_EQ(++it, HalfIterator(kArray.begin() + 1));
-  EXPECT_EQ(*it, 1);
-}
-
-TEST(WrappedIterator, PostDecrement) {
-  HalfIterator it(kArray.end());
-  EXPECT_EQ(it--, HalfIterator(kArray.end()));
-  EXPECT_EQ(it, HalfIterator(kArray.end() - 1));
-  EXPECT_EQ(*it, 5);
-}
-
-TEST(WrappedIterator, PreDecrement) {
-  HalfIterator it(kArray.end());
-  EXPECT_EQ(--it, HalfIterator(kArray.end() - 1));
-  EXPECT_EQ(*it, 5);
-}
-
-}  // namespace
-}  // namespace pw::containers
diff --git a/pw_cpu_exception/BUILD b/pw_cpu_exception/BUILD
new file mode 100644
index 0000000..d58a55c
--- /dev/null
+++ b/pw_cpu_exception/BUILD
@@ -0,0 +1,28 @@
+# Copyright 2019 The Pigweed Authors
+#
+# 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
+#
+#     https://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(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+filegroup(
+    name = "pw_cpu_exception",
+    srcs = [
+        "basic_handler.cc",
+        "public/pw_cpu_exception/entry.h",
+        "public/pw_cpu_exception/handler.h",
+        "public/pw_cpu_exception/support.h",
+        "start_exception_handler.cc",
+    ],
+)
diff --git a/pw_cpu_exception/BUILD.bazel b/pw_cpu_exception/BUILD.bazel
deleted file mode 100644
index 1a0b024..0000000
--- a/pw_cpu_exception/BUILD.bazel
+++ /dev/null
@@ -1,29 +0,0 @@
-# Copyright 2019 The Pigweed Authors
-#
-# 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
-#
-#     https://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(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-filegroup(
-    name = "pw_cpu_exception",
-    srcs = [
-        "basic_handler.cc",
-        "public/pw_cpu_exception/entry.h",
-        "public/pw_cpu_exception/handler.h",
-        "public/pw_cpu_exception/state.h",
-        "public/pw_cpu_exception/support.h",
-        "start_exception_handler.cc",
-    ],
-)
diff --git a/pw_cpu_exception/BUILD.gn b/pw_cpu_exception/BUILD.gn
index f81194d..879bb9c 100644
--- a/pw_cpu_exception/BUILD.gn
+++ b/pw_cpu_exception/BUILD.gn
@@ -18,7 +18,7 @@
 import("$dir_pw_docgen/docs.gni")
 import("backend.gni")
 
-config("public_include_path") {
+config("default_config") {
   include_dirs = [ "public" ]
 }
 
@@ -34,8 +34,8 @@
 #
 # - entry: This is the library that handles early exception entry and prepares
 #   any CPU state that must be available to the exception handler via the
-#   pw_cpu_exception_State object. The backend for this facade is
-#   architecture-specific.
+#   pw_CpuState object. The backend for this facade will be architecture-
+#   specific.
 #   Set this facade's backend via `pw_cpu_exception_ENTRY_BACKEND`
 #
 # - handler: This facade is backed by an application-specific handler that
@@ -52,43 +52,16 @@
 
 pw_facade("entry") {
   backend = pw_cpu_exception_ENTRY_BACKEND
-  public_configs = [ ":public_include_path" ]
+  public_configs = [ ":default_config" ]
   public_deps = [ "$dir_pw_preprocessor" ]
-  public = [
-    "public/pw_cpu_exception/entry.h",
-    "public/pw_cpu_exception/state.h",
-  ]
-}
-
-# The entry facade is hard tied to the definition of the pw_cpu_exception_State,
-# so spliting them into separate facades would require extra configurations
-# along with extra compatibility checks to ensure they are never mismatched.
-#
-# Instead, this ":entry_impl" target collects the entry implementation from the
-# backend that depends on the handler which in turn depends on ":entry" to avoid
-# circular deps.
-#
-# This group ("$dir_pw_cpu_exception:entry_impl") must listed in
-# pw_build_LINK_DEPS if pw_cpu_exception_ENTRY_BACKEND is set.
-#
-# Entry backends must provide their own "*.impl" target that collects their
-# entry implementation.
-group("entry_impl") {
-  public_deps = []
-
-  if (pw_cpu_exception_ENTRY_BACKEND != "") {
-    public_deps += [ get_label_info(pw_cpu_exception_ENTRY_BACKEND,
-                                    "label_no_toolchain") + ".impl" ]
-  }
+  deps = [ ":handler.facade" ]
+  public = [ "public/pw_cpu_exception/entry.h" ]
 }
 
 pw_facade("handler") {
   backend = pw_cpu_exception_HANDLER_BACKEND
-  public_configs = [ ":public_include_path" ]
-  public_deps = [
-    ":entry",
-    "$dir_pw_preprocessor",
-  ]
+  public_configs = [ ":default_config" ]
+  public_deps = [ "$dir_pw_preprocessor" ]
   sources = [ "start_exception_handler.cc" ]
   public = [ "public/pw_cpu_exception/handler.h" ]
 }
@@ -99,8 +72,7 @@
 # pw_cpu_exception_State members.
 pw_facade("support") {
   backend = pw_cpu_exception_SUPPORT_BACKEND
-  public_configs = [ ":public_include_path" ]
-  public_deps = [ ":entry" ]
+  public_configs = [ ":default_config" ]
   public = [ "public/pw_cpu_exception/support.h" ]
 }
 
diff --git a/pw_cpu_exception/CMakeLists.txt b/pw_cpu_exception/CMakeLists.txt
index 6cc9ced..1e134c6 100644
--- a/pw_cpu_exception/CMakeLists.txt
+++ b/pw_cpu_exception/CMakeLists.txt
@@ -14,41 +14,9 @@
 
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
 
-pw_add_facade(pw_cpu_exception.entry
-  HEADERS
-    public/pw_cpu_exception/entry.h
-    public/pw_cpu_exception/state.h
-  PUBLIC_INCLUDES
-    public
+pw_add_facade(pw_cpu_exception
   PUBLIC_DEPS
     pw_preprocessor
-)
-
-pw_add_facade(pw_cpu_exception.handler
-  HEADERS
-    public/pw_cpu_exception/handler.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_cpu_exception.entry
-    pw_preprocessor
-  SOURCES
-    start_exception_handler.cc
-)
-
-pw_add_facade(pw_cpu_exception.support
-  HEADERS
-    public/pw_cpu_exception/support.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_cpu_exception.entry
-)
-
-pw_add_module_library(pw_cpu_exception.basic_handler
-  SOURCES
-    basic_handler.cc
-  PRIVATE_DEPS
-    pw_cpu_exception.entry
-    pw_log
+    pw_span
+    pw_string
 )
diff --git a/pw_cpu_exception/OWNERS b/pw_cpu_exception/OWNERS
deleted file mode 100644
index b09a54f..0000000
--- a/pw_cpu_exception/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-amontanez@google.com
-ewout@google.com
diff --git a/pw_cpu_exception/docs.rst b/pw_cpu_exception/docs.rst
index 0cd2140..d375623 100644
--- a/pw_cpu_exception/docs.rst
+++ b/pw_cpu_exception/docs.rst
@@ -1,8 +1,8 @@
 .. _module-pw_cpu_exception:
 
-================
+----------------
 pw_cpu_exception
-================
+----------------
 Pigweed's exception module provides a consistent interface for entering an
 application's CPU exception handler. While the actual exception handling
 behavior is left to an application to implement, this module deals with any
@@ -10,31 +10,13 @@
 handler. More specifically, the exception module collects CPU state that may
 otherwise be clobbered by an application's exception handler.
 
------
 Setup
------
-This module has three facades, each of whose backends are set with a
-different GN variable.
-
-``pw_cpu_exception_ENTRY_BACKEND``
-==================================
-This is the library that handles early exception entry and prepares any CPU
-state that must be available to the exception handler via the
-pw_cpu_exception_State object. The backend for this facade is
-architecture-specific.
-
+=====
 An application using this module **must** connect ``pw_cpu_exception_Entry()`` to
 the platform's CPU exception handler interrupt so ``pw_cpu_exception_Entry()`` is
 called immediately upon a CPU exception. For specifics on how this may be done,
 see the backend documentation for your architecture.
 
-``pw_cpu_exception_HANDLER_BACKEND``
-====================================
-This facade is backed by an application-specific handler that determines what to
-do when an exception is encountered. This may be capturing a crash report before
-resetting the device, or in some cases handling the exception to allow execution
-to continue.
-
 Applications must also provide an implementation for
 ``pw_cpu_exception_DefaultHandler()``. The behavior of this functions is entirely
 up to the application/project, but some examples are provided below:
@@ -46,31 +28,8 @@
     report.
   * A combination of the above, using logic that fits the needs of your project.
 
-``pw_cpu_exception_SUPPORT_BACKEND``
-====================================
-This facade provides architecture-independent functions that may be helpful for
-dumping CPU state in various forms. This allows an application to create an
-application-specific handler that is portable across multiple architectures.
-
-Avoiding circular dependencies with ``pw_cpu_exception_ENTRY_BACKEND``
-======================================================================
-The entry facade is hard tied to the definition of the
-``pw_cpu_exception_State``, so spliting them into separate facades would require
-extra configurations along with extra compatibility checks to ensure they are
-never mismatched.
-
-In GN, this circular dependency is avoided by collecting the backend's full
-implementation including the entry method through the
-``pw_cpu_exception:entry_impl`` group. When ``pw_cpu_exception_ENTRY_BACKEND``
-is set, ``$dir_pw_cpu_exception:entry_impl`` must listed in the
-``pw_build_LINK_DEPS`` variable. See :ref:`module-pw_build-link-deps`.
-
-Entry backends must provide their own ``*.impl`` target that collects their
-entry implementation.
-
-------------
 Module Usage
-------------
+============
 Basic usage of this module entails applications supplying a definition for
 ``pw_cpu_exception_DefaultHandler()``. ``pw_cpu_exception_DefaultHandler()`` should
 contain any logic to determine if a exception can be recovered from, as well as
@@ -95,38 +54,30 @@
 attempt to correct a fault. In this situation, the application's exception
 handler will be tied to the backend implementation of the CPU exception module.
 
---------------------
 Backend Expectations
---------------------
+====================
 CPU exception backends do not provide an exception handler, but instead provide
 mechanisms to capture CPU state for use by an application's exception handler,
 and allow recovery from CPU exceptions when possible.
 
-  * The entry backend should provide a definition for the
-    ``pw_cpu_exception_State`` object through
-    ``pw_cpu_exception_backend/state.h``.
-  * In GN, the entry backend should also provide a ``.impl`` suffixed form of
-    the entry backend target which collects the actual entry implementation to
-    avoid circular dependencies due to the state definition in the entry backend
-    target.
-  * The entry backend should implement the ``pw_cpu_exception_Entry()`` function
-    that will call ``pw_cpu_exception_HandleException()`` after performing any
-    necessary actions prior to handing control to the application's exception
-    handler (e.g. capturing necessary CPU state).
-  * If an application's exception handler backend modifies the captured CPU
-    state, the state should be treated as though it were the original state of
-    the CPU when the exception occurred. The backend may need to manually
-    restore some of the modified state to ensure this on exception handler
-    return.
+  * A backend should provide a definition for the ``pw_cpu_exception_State``
+    struct that provides suitable means to access and modify any captured CPU
+    state.
+  * If an application's exception handler modifies the captured CPU state, the
+    state should be treated as though it were the original state of the CPU when
+    the exception occurred. The backend may need to manually restore some of the
+    modified state to ensure this on exception handler return.
+  * A backend should implement the ``pw_cpu_exception_Entry()`` function that will
+    call ``pw_cpu_exception_HandleException()`` after performing any necessary
+    actions prior to handing control to the application's exception handler
+    (e.g. capturing necessary CPU state).
 
--------------
 Compatibility
--------------
+=============
 Most of the pw_cpu_exception module is C-compatible. The exception to this is
 the "support" facade and library, which requires C++.
 
-------------
 Dependencies
-------------
+============
   * ``pw_span``
   * ``pw_preprocessor``
diff --git a/pw_cpu_exception/public/pw_cpu_exception/handler.h b/pw_cpu_exception/public/pw_cpu_exception/handler.h
index 047a111..7c255fa 100644
--- a/pw_cpu_exception/public/pw_cpu_exception/handler.h
+++ b/pw_cpu_exception/public/pw_cpu_exception/handler.h
@@ -13,12 +13,15 @@
 // the License.
 #pragma once
 
-#include "pw_cpu_exception/state.h"
 #include "pw_preprocessor/compiler.h"
 #include "pw_preprocessor/util.h"
 
 PW_EXTERN_C_START
 
+// Forward declaration of pw_cpu_exception_State. Definition provided by cpu
+// exception entry backend.
+struct pw_cpu_exception_State;
+
 // By default, the exception entry function will terminate by handing execution
 // over to pw_cpu_exception_DefaultHandler(). This can be used to override the
 // current handler. This allows runtime insertion of an exception handler which
diff --git a/pw_cpu_exception/public/pw_cpu_exception/state.h b/pw_cpu_exception/public/pw_cpu_exception/state.h
deleted file mode 100644
index 600fa21..0000000
--- a/pw_cpu_exception/public/pw_cpu_exception/state.h
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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
-
-// The cpu exception entry backend must provide a definition of the
-// pw_cpu_exception_State struct through this header.
-#include "pw_cpu_exception_backend/state.h"
diff --git a/pw_cpu_exception/public/pw_cpu_exception/support.h b/pw_cpu_exception/public/pw_cpu_exception/support.h
index c8df964..d525039 100644
--- a/pw_cpu_exception/public/pw_cpu_exception/support.h
+++ b/pw_cpu_exception/public/pw_cpu_exception/support.h
@@ -22,7 +22,9 @@
 #include <cstdint>
 #include <span>
 
-#include "pw_cpu_exception/state.h"
+// Forward declaration of pw_cpu_exception_State. Definition provided by
+// backend.
+struct pw_cpu_exception_State;
 
 namespace pw::cpu_exception {
 
diff --git a/pw_cpu_exception_cortex_m/BUILD b/pw_cpu_exception_cortex_m/BUILD
new file mode 100644
index 0000000..a39d11c
--- /dev/null
+++ b/pw_cpu_exception_cortex_m/BUILD
@@ -0,0 +1,85 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_binary",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "support_armv7m",
+    includes = ["public"],
+    deps = [
+        "//pw_preprocessor",
+        "//pw_string",
+        "//pw_log",
+    ],
+    hdrs = [ "public/pw_cpu_exception_cortex_m/cpu_state.h" ],
+    srcs = [
+        "cpu_state.cc",
+        "pw_cpu_exception_cortex_m_private/cortex_m_constants.h",
+    ],
+)
+
+pw_cc_library(
+    name = "proto_dump_armv7m",
+    deps = [
+        ":support_armv7m",
+        ":cpu_state_protos",
+        "//pw_protobuf",
+        "//pw_status",
+        "//pw_stream",
+    ],
+    hdrs = ["public/pw_cpu_exception_cortex_m/proto_dump.h"],
+    srcs = ["proto_dump.cc"],
+)
+
+proto_library(
+    name = "cpu_state_protos",
+    srcs = ["pw_cpu_exception_cortex_m_protos/cpu_state.proto"],
+)
+
+# TODO(pwbug/296): The *_armv7m libraries work on ARMv8-M, but needs some minor
+# patches for complete correctness. Add *_armv8m targets that use the same files
+# but provide preprocessor defines to enable/disable architecture specific code.
+pw_cc_library(
+    name = "cpu_exception_armv7m",
+    deps = [
+        ":proto_dump_armv7m",
+        ":support_armv7m",
+        # TODO(pwbug/101): Need to add support for facades/backends to Bazel.
+        "//pw_cpu_exception",
+        "//pw_preprocessor",
+    ],
+    srcs = [
+        "entry.cc",
+        "pw_cpu_exception_cortex_m_private/cortex_m_constants.h",
+    ],
+)
+
+pw_cc_test(
+    name = "cpu_exception_entry_test",
+    srcs = [
+        "exception_entry_test.cc",
+    ],
+    deps = [
+        ":cpu_exception_armv7m",
+    ],
+)
diff --git a/pw_cpu_exception_cortex_m/BUILD.bazel b/pw_cpu_exception_cortex_m/BUILD.bazel
deleted file mode 100644
index 0123310..0000000
--- a/pw_cpu_exception_cortex_m/BUILD.bazel
+++ /dev/null
@@ -1,158 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "config",
-    hdrs = ["pw_cpu_exception_cortex_m_private/config.h"],
-)
-
-pw_cc_library(
-    name = "cpu_state",
-    hdrs = ["public/pw_cpu_exception_cortex_m/cpu_state.h"],
-    includes = ["public"],
-    deps = [
-        "//pw_preprocessor",
-        "//pw_preprocessor:arch",
-    ],
-)
-
-pw_cc_library(
-    name = "util",
-    srcs = ["util.cc"],
-    hdrs = ["public/pw_cpu_exception_cortex_m/util.h"],
-    includes = ["public"],
-    deps = [
-        ":config",
-        ":cortex_m_constants",
-        ":cpu_state",
-        "//pw_log",
-        "//pw_preprocessor:arch",
-    ],
-)
-
-pw_cc_library(
-    name = "support",
-    srcs = ["support.cc"],
-    deps = [
-        ":config",
-        ":cortex_m_constants",
-        ":cpu_state",
-        ":util",
-        "//pw_log",
-        "//pw_preprocessor",
-        "//pw_preprocessor:arch",
-        "//pw_string",
-    ],
-)
-
-pw_cc_library(
-    name = "proto_dump",
-    srcs = ["proto_dump.cc"],
-    hdrs = ["public/pw_cpu_exception_cortex_m/proto_dump.h"],
-    includes = ["public"],
-    deps = [
-        ":config",
-        ":cpu_state",
-        ":cpu_state_protos",
-        ":support",
-        "//pw_protobuf",
-        "//pw_status",
-        "//pw_stream",
-    ],
-)
-
-proto_library(
-    name = "cpu_state_protos",
-    srcs = ["pw_cpu_exception_cortex_m_protos/cpu_state.proto"],
-)
-
-pw_cc_library(
-    name = "cpu_exception",
-    srcs = ["entry.cc"],
-    hdrs = [
-        "public/pw_cpu_exception_cortex_m/cpu_state.h",
-        "public_overrides/pw_cpu_exception_backend/state.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":config",
-        ":cpu_state",
-        ":cortex_m_constants",
-        ":proto_dump",
-        ":support",
-        ":util",
-        # TODO(pwbug/101): Need to add support for facades/backends to Bazel.
-        "//pw_cpu_exception",
-        "//pw_preprocessor",
-        "//pw_preprocessor:arch",
-    ],
-)
-
-pw_cc_library(
-    name = "snapshot",
-    srcs = ["snapshot.cc"],
-    hdrs = ["public/pw_cpu_exception_cortex_m/snapshot.h"],
-    deps = [
-        ":config",
-        ":cortex_m_constants",
-        ":cpu_state",
-        ":cpu_state_protos",
-        ":proto_dump",
-        ":util",
-        "//pw_log",
-        "//pw_protobuf",
-        "//pw_status",
-        "//pw_thread:protos",
-        "//pw_thread:snapshot",
-    ],
-)
-
-pw_cc_library(
-    name = "cortex_m_constants",
-    hdrs = ["pw_cpu_exception_cortex_m_private/cortex_m_constants.h"],
-    visibility = ["//visibility:private"],
-    deps = ["//pw_preprocessor:arch"],
-)
-
-pw_cc_test(
-    name = "cpu_exception_entry_test",
-    srcs = [
-        "exception_entry_test.cc",
-    ],
-    deps = [
-        ":cpu_exception",
-        ":cpu_state",
-    ],
-)
-
-pw_cc_test(
-    name = "util_test",
-    srcs = [
-        "util_test.cc",
-    ],
-    deps = [
-        ":cpu_state",
-        ":util",
-    ],
-)
diff --git a/pw_cpu_exception_cortex_m/BUILD.gn b/pw_cpu_exception_cortex_m/BUILD.gn
index dd91dea..a0a9096 100644
--- a/pw_cpu_exception_cortex_m/BUILD.gn
+++ b/pw_cpu_exception_cortex_m/BUILD.gn
@@ -14,226 +14,77 @@
 
 import("//build_overrides/pigweed.gni")
 
-import("$dir_pw_build/module_config.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_cpu_exception/backend.gni")
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_protobuf_compiler/proto.gni")
 import("$dir_pw_unit_test/test.gni")
 
-declare_args() {
-  # The build target that overrides the default configuration options for this
-  # module. This should point to a source set that provides defines through a
-  # public config (which may -include a file or add defines directly).
-  pw_cpu_exception_cortex_m_CONFIG = pw_build_DEFAULT_MODULE_CONFIG
-}
-
-pw_source_set("config") {
-  public_deps = [
-    "$dir_pw_preprocessor:arch",
-    pw_cpu_exception_cortex_m_CONFIG,
-  ]
-  public = [ "pw_cpu_exception_cortex_m_private/config.h" ]
-  visibility = [ ":*" ]
-}
-
-config("public_include_path") {
+config("default_config") {
   include_dirs = [ "public" ]
   visibility = [ ":*" ]
 }
 
-config("backend_config") {
-  include_dirs = [ "public_overrides" ]
-  visibility = [ ":*" ]
-}
-
-pw_source_set("support") {
-  deps = [
-    ":config",
-    ":cortex_m_constants",
-    ":util",
+pw_source_set("support_armv7m") {
+  public_configs = [ ":default_config" ]
+  public_deps = [
     "$dir_pw_cpu_exception:support.facade",
-    "$dir_pw_preprocessor:arch",
-    dir_pw_log,
+    dir_pw_preprocessor,
     dir_pw_string,
   ]
-  sources = [ "support.cc" ]
-}
-
-# The following targets are deprecated, use ":support" instead.
-group("support_armv7m") {
-  public_deps = [ ":support" ]
-}
-group("support_armv8m") {
-  public_deps = [ ":support" ]
-}
-
-pw_source_set("util") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_cpu_exception_cortex_m/util.h" ]
-  public_deps = [ ":cpu_state" ]
-  deps = [
-    ":config",
-    ":cortex_m_constants",
-    "$dir_pw_preprocessor:arch",
-    dir_pw_log,
+  deps = [ dir_pw_log ]
+  public = [ "public/pw_cpu_exception_cortex_m/cpu_state.h" ]
+  sources = [
+    "cpu_state.cc",
+    "pw_cpu_exception_cortex_m_private/cortex_m_constants.h",
   ]
-  sources = [ "util.cc" ]
 }
 
-pw_source_set("proto_dump") {
-  public_configs = [ ":public_include_path" ]
+pw_source_set("proto_dump_armv7m") {
   public_deps = [
-    ":cpu_exception_armv7m",
+    ":support_armv7m",
     dir_pw_protobuf,
     dir_pw_status,
     dir_pw_stream,
   ]
   public = [ "public/pw_cpu_exception_cortex_m/proto_dump.h" ]
-  deps = [
-    ":config",
-    ":cpu_state_protos.pwpb",
-  ]
+  deps = [ ":cpu_state_protos.pwpb" ]
   sources = [ "proto_dump.cc" ]
 }
 
-# The following targets are deprecated, use ":proto_dump" instead.
-group("proto_dump_armv7m") {
-  public_deps = [ ":proto_dump" ]
-}
-group("proto_dump_armv8m") {
-  public_deps = [ ":proto_dump" ]
-}
-
 pw_proto_library("cpu_state_protos") {
   sources = [ "pw_cpu_exception_cortex_m_protos/cpu_state.proto" ]
 }
 
-pw_source_set("cpu_state") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_cpu_exception_cortex_m/cpu_state.h" ]
+# TODO(pwbug/296): The *_armv7m libraries work on ARMv8-M, but needs some minor
+# patches for complete correctness. Add *_armv8m targets that use the same files
+# but provide preprocessor defines to enable/disable architecture specific code.
+pw_source_set("cpu_exception_armv7m") {
+  public_configs = [ ":default_config" ]
   public_deps = [
-    "$dir_pw_preprocessor",
-    "$dir_pw_preprocessor:arch",
-  ]
-}
-
-pw_source_set("cpu_exception") {
-  public_configs = [
-    ":backend_config",
-    ":public_include_path",
-  ]
-  public = [ "public_overrides/pw_cpu_exception_backend/state.h" ]
-  public_deps = [
-    ":cpu_state",
-    "$dir_pw_preprocessor",
-    "$dir_pw_preprocessor:arch",
-  ]
-}
-
-# The following targets are deprecated, use ":cpu_exception" instead.
-group("cpu_exception_armv7m") {
-  public_deps = [ ":cpu_exception" ]
-}
-group("cpu_exception_armv8m") {
-  public_deps = [ ":cpu_exception" ]
-}
-
-pw_source_set("cpu_exception.impl") {
-  sources = [ "entry.cc" ]
-  deps = [
-    ":config",
-    ":cortex_m_constants",
-    ":cpu_state",
-    ":util",
+    ":proto_dump_armv7m",
+    ":support_armv7m",
     "$dir_pw_cpu_exception:entry.facade",
     "$dir_pw_cpu_exception:handler",
-    "$dir_pw_preprocessor:arch",
+    "$dir_pw_preprocessor",
   ]
-}
-
-# The following targets are deprecated, use ":cpu_exception.impl" instead.
-group("cpu_exception_armv7m.impl") {
-  public_deps = [ ":cpu_exception.impl" ]
-}
-group("cpu_exception_armv8m.impl") {
-  public_deps = [ ":cpu_exception.impl" ]
-}
-
-pw_source_set("snapshot") {
-  public_configs = [ ":public_include_path" ]
-  public_deps = [
-    ":cpu_state",
-    ":cpu_state_protos.pwpb",
-    "$dir_pw_thread:protos.pwpb",
-    "$dir_pw_thread:snapshot",
-    dir_pw_protobuf,
-    dir_pw_status,
+  sources = [
+    "entry.cc",
+    "pw_cpu_exception_cortex_m_private/cortex_m_constants.h",
   ]
-  public = [ "public/pw_cpu_exception_cortex_m/snapshot.h" ]
-  sources = [ "snapshot.cc" ]
-  deps = [
-    ":config",
-    ":cortex_m_constants",
-    ":proto_dump",
-    ":util",
-    dir_pw_log,
-  ]
-}
-
-# The following targets are deprecated, use ":snapshot" instead.
-group("snapshot_armv7m") {
-  public_deps = [ ":snapshot" ]
-}
-group("snapshot_armv8m") {
-  public_deps = [ ":snapshot" ]
-}
-
-pw_source_set("cortex_m_constants") {
-  public = [ "pw_cpu_exception_cortex_m_private/cortex_m_constants.h" ]
-  public_deps = [ "$dir_pw_preprocessor:arch" ]
-  visibility = [ ":*" ]
-}
-
-# The following targets are deprecated, use ":cortex_m_constants" instead.
-group("cortex_m_constants_armv7m") {
-  public_deps = [ ":cortex_m_constants" ]
-  visibility = [ ":*" ]
-}
-group("cortex_m_constants_armv8m") {
-  public_deps = [ ":cortex_m_constants" ]
-  visibility = [ ":*" ]
 }
 
 pw_test_group("tests") {
+  enable_if = pw_cpu_exception_ENTRY_BACKEND ==
+              "$dir_pw_cpu_exception_cortex_m:cpu_exception_armv7m"
   tests = [ ":cpu_exception_entry_test" ]
 }
 
-# TODO(pwbug/583): Add ARMv8-M mainline coverage.
 pw_test("cpu_exception_entry_test") {
-  enable_if = pw_cpu_exception_ENTRY_BACKEND ==
-              "$dir_pw_cpu_exception_cortex_m:cpu_exception"
-  deps = [
-    ":cortex_m_constants",
-    ":cpu_exception",
-    ":cpu_state",
-    "$dir_pw_cpu_exception:entry",
-    "$dir_pw_cpu_exception:handler",
-    "$dir_pw_cpu_exception:support",
-  ]
+  deps = [ ":cpu_exception_armv7m" ]
   sources = [ "exception_entry_test.cc" ]
 }
 
-pw_test("util_test") {
-  enable_if = pw_cpu_exception_ENTRY_BACKEND ==
-              "$dir_pw_cpu_exception_cortex_m:cpu_exception"
-  deps = [
-    ":cpu_state",
-    ":util",
-  ]
-  sources = [ "util_test.cc" ]
-}
-
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
diff --git a/pw_cpu_exception_cortex_m/CMakeLists.txt b/pw_cpu_exception_cortex_m/CMakeLists.txt
index 82d12b4..c434a21 100644
--- a/pw_cpu_exception_cortex_m/CMakeLists.txt
+++ b/pw_cpu_exception_cortex_m/CMakeLists.txt
@@ -13,155 +13,8 @@
 # the License.
 
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-include($ENV{PW_ROOT}/pw_protobuf_compiler/proto.cmake)
 
-pw_add_module_config(pw_cpu_exception_cortex_m_CONFIG)
-
-pw_add_module_library(pw_cpu_exception_cortex_m.config
-  HEADERS
-    pw_cpu_exception_cortex_m_private/config.h
-  PUBLIC_DEPS
-    ${pw_cpu_exception_cortex_m_CONFIG}
+pw_auto_add_simple_module(pw_cpu_exception_cortex_m
+  IMPLEMENTS_FACADE
+    pw_cpu_exception
 )
-
-pw_add_module_library(pw_cpu_exception_cortex_m.cpu_state
-  HEADERS
-    public/pw_cpu_exception_cortex_m/cpu_state.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_preprocessor
-    pw_preprocessor.arch
-)
-
-pw_add_module_library(pw_cpu_exception_cortex_m.cpu_exception
-  IMPLEMENTS_FACADES
-    pw_cpu_exception.entry
-  HEADERS
-    public_overrides/pw_cpu_exception_backend/state.h
-  PUBLIC_INCLUDES
-    public_overrides
-  PUBLIC_DEPS
-    pw_preprocessor
-    pw_preprocessor.arch
-    pw_cpu_exception_cortex_m.cpu_state
-  PRIVATE_DEPS
-    pw_cpu_exception.handler
-    pw_cpu_exception_cortex_m.config
-    pw_cpu_exception_cortex_m.constants
-    pw_cpu_exception_cortex_m.util
-  SOURCES
-    entry.cc
-)
-
-pw_add_module_library(pw_cpu_exception_cortex_m.util
-  HEADERS
-    public/pw_cpu_exception_cortex_m/util.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_cpu_exception_cortex_m.cpu_state
-  PRIVATE_DEPS
-    pw_cpu_exception_cortex_m.config
-    pw_cpu_exception_cortex_m.constants
-    pw_log
-    pw_preprocessor.arch
-  SOURCES
-    util.cc
-)
-
-pw_add_module_library(pw_cpu_exception_cortex_m.support
-  IMPLEMENTS_FACADES
-    pw_cpu_exception.support
-  PRIVATE_DEPS
-    pw_cpu_exception_cortex_m.config
-    pw_cpu_exception_cortex_m.constants
-    pw_cpu_exception_cortex_m.util
-    pw_log
-    pw_polyfill.span
-    pw_preprocessor.arch
-    pw_string
-  SOURCES
-    support.cc
-)
-
-pw_proto_library(pw_cpu_exception_cortex_m.cpu_state_protos
-  SOURCES
-    pw_cpu_exception_cortex_m_protos/cpu_state.proto
-)
-
-pw_add_module_library(pw_cpu_exception_cortex_m.proto_dump
-  HEADERS
-    public/pw_cpu_exception_cortex_m/proto_dump.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_cpu_exception_cortex_m.cpu_state
-    pw_protobuf
-    pw_status
-    pw_stream
-  PRIVATE_DEPS
-    pw_cpu_exception_cortex_m.config
-    pw_cpu_exception_cortex_m.cpu_state_protos.pwpb
-  SOURCES
-    proto_dump.cc
-)
-
-pw_add_module_library(pw_cpu_exception_cortex_m.snapshot
-  PUBLIC_DEPS
-    pw_cpu_exception_cortex_m.cpu_state
-    pw_cpu_exception_cortex_m.cpu_state_protos.pwpb
-    pw_protobuf
-    pw_status
-    pw_thread.protos.pwpb
-    pw_thread.snapshot
-  PRIVATE_DEPS
-    pw_cpu_exception_cortex_m.config
-    pw_cpu_exception_cortex_m.constants
-    pw_cpu_exception_cortex_m.proto_dump
-    pw_cpu_exception_cortex_m.util
-    pw_log
-    pw_polyfill.span
-  SOURCES
-    snapshot.cc
-  HEADERS
-    public/pw_cpu_exception_cortex_m/snapshot.h
-)
-
-pw_add_module_library(pw_cpu_exception_cortex_m.constants
-  PUBLIC_DEPS
-    pw_preprocessor.arch
-  HEADERS
-    pw_cpu_exception_cortex_m_private/cortex_m_constants.h
-)
-
-# TODO(pwbug/583): Add ARMv8-M mainline coverage.
-if("${pw_cpu_exception.entry_BACKEND}" STREQUAL
-   "pw_cpu_exception_cortex_m.cpu_exception")
-  pw_add_test(pw_cpu_exception_cortex_m.cpu_exception_entry_test
-    SOURCES
-      exception_entry_test.cc
-    DEPS
-      pw_cpu_exception.entry
-      pw_cpu_exception.handler
-      pw_cpu_exception.support
-      pw_cpu_exception_cortex_m.cortex_m_constants
-      pw_cpu_exception_cortex_m.cpu_exception
-      pw_cpu_exception_cortex_m.cpu_state
-      pw_polyfill.span
-    GROUPS
-      modules
-      pw_cpu_exception_cortex_m
-  )
-
-  pw_add_test(pw_cpu_exception_cortex_m.util_test
-    SOURCES
-      util_test.cc
-    DEPS
-      pw_cpu_exception_cortex_m.cpu_state
-      pw_cpu_exception_cortex_m.util
-    GROUPS
-      modules
-      pw_cpu_exception_cortex_m
-  )
-endif()
diff --git a/pw_cpu_exception_cortex_m/OWNERS b/pw_cpu_exception_cortex_m/OWNERS
deleted file mode 100644
index 307b1de..0000000
--- a/pw_cpu_exception_cortex_m/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-amontanez@google.com
diff --git a/pw_cpu_exception_cortex_m/cpu_state.cc b/pw_cpu_exception_cortex_m/cpu_state.cc
new file mode 100644
index 0000000..7ee6e03
--- /dev/null
+++ b/pw_cpu_exception_cortex_m/cpu_state.cc
@@ -0,0 +1,250 @@
+// Copyright 2019 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_cpu_exception_cortex_m/cpu_state.h"
+
+#include <cinttypes>
+#include <cstdint>
+#include <span>
+
+#include "pw_cpu_exception/support.h"
+#include "pw_cpu_exception_cortex_m_private/cortex_m_constants.h"
+#include "pw_log/log.h"
+#include "pw_string/string_builder.h"
+
+// TODO(amontanez): Set up config when this module is moved to *_cortex_m.
+#ifndef PW_CPU_EXCEPTION_EXTENDED_CFSR_DUMP
+#define PW_CPU_EXCEPTION_EXTENDED_CFSR_DUMP 0
+#endif  // PW_CPU_EXCEPTION_EXTENDED_CFSR_DUMP
+
+namespace pw::cpu_exception {
+namespace {
+
+[[maybe_unused]] void AnalyzeCfsr(const uint32_t cfsr) {
+  if (cfsr == 0) {
+    return;
+  }
+
+  PW_LOG_INFO("Active CFSR fields:");
+
+  // Memory managment fault fields.
+  if (cfsr & kCfsrIaccviolMask) {
+    PW_LOG_ERROR("  IACCVIOL: MPU violation on instruction fetch");
+  }
+  if (cfsr & kCfsrDaccviolMask) {
+    PW_LOG_ERROR("  DACCVIOL: MPU violation on memory read/write");
+  }
+  if (cfsr & kCfsrMunstkerrMask) {
+    PW_LOG_ERROR("  MUNSTKERR: 'MPU violation on exception return");
+  }
+  if (cfsr & kCfsrMstkerrMask) {
+    PW_LOG_ERROR("  MSTKERR: MPU violation on exception entry");
+  }
+  if (cfsr & kCfsrMlsperrMask) {
+    PW_LOG_ERROR("  MLSPERR: MPU violation on lazy FPU state preservation");
+  }
+  if (cfsr & kCfsrMmarvalidMask) {
+    PW_LOG_ERROR("  MMARVALID: MMFAR register is valid");
+  }
+
+  // Bus fault fields.
+  if (cfsr & kCfsrIbuserrMask) {
+    PW_LOG_ERROR("  IBUSERR: Bus fault on instruction fetch");
+  }
+  if (cfsr & kCfsrPreciserrMask) {
+    PW_LOG_ERROR("  PRECISERR: Precise bus fault");
+  }
+  if (cfsr & kCfsrImpreciserrMask) {
+    PW_LOG_ERROR("  IMPRECISERR: Imprecise bus fault");
+  }
+  if (cfsr & kCfsrUnstkerrMask) {
+    PW_LOG_ERROR("  UNSTKERR: Derived bus fault on exception context save");
+  }
+  if (cfsr & kCfsrStkerrMask) {
+    PW_LOG_ERROR("  STKERR: Derived bus fault on exception context restore");
+  }
+  if (cfsr & kCfsrLsperrMask) {
+    PW_LOG_ERROR("  LSPERR: Derived bus fault on lazy FPU state preservation");
+  }
+  if (cfsr & kCfsrBfarvalidMask) {
+    PW_LOG_ERROR("  BFARVALID: BFAR register is valid");
+  }
+
+  // Usage fault fields.
+  if (cfsr & kCfsrUndefinstrMask) {
+    PW_LOG_ERROR("  UNDEFINSTR: Encountered invalid instruction");
+  }
+  if (cfsr & kCfsrInvstateMask) {
+    PW_LOG_ERROR(
+        "  INVSTATE: Attempted to execute an instruction with an invalid "
+        "Execution Program Status Register (EPSR) value");
+  }
+  if (cfsr & kCfsrInvpcMask) {
+    PW_LOG_ERROR("  INVPC: Program Counter (PC) is not legal");
+  }
+  if (cfsr & kCfsrNocpMask) {
+    PW_LOG_ERROR("  NOCP: Coprocessor disabled or not present");
+  }
+  if (cfsr & kCfsrUnalignedMask) {
+    PW_LOG_ERROR("  UNALIGNED: Unaligned memory access");
+  }
+  if (cfsr & kCfsrDivbyzeroMask) {
+    PW_LOG_ERROR("  DIVBYZERO: Division by zero");
+  }
+  // This flag is only present on ARMv8-M cores.
+  if (cfsr & kCfsrStkofMask) {
+    PW_LOG_ERROR("  STKOF: Stack overflowed");
+  }
+}
+
+void AnalyzeException(const pw_cpu_exception_State& cpu_state) {
+  // This provides a high-level assessment of the cause of the exception.
+  // These conditionals are ordered by priority to ensure the most critical
+  // issues are highlighted first. These are not mutually exclusive; a bus fault
+  // could occur during the handling of a MPU violation, causing a nested fault.
+  if (cpu_state.extended.hfsr & kHfsrForcedMask) {
+    PW_LOG_CRITICAL("Encountered a nested CPU fault (See active CFSR fields)");
+  }
+  // TODO(pwbug/296): #if this out on non-ARMv7-M builds.
+  if (cpu_state.extended.cfsr & kCfsrStkofMask) {
+    if (cpu_state.extended.exc_return & kExcReturnStackMask) {
+      PW_LOG_CRITICAL("Encountered stack overflow in thread mode");
+    } else {
+      PW_LOG_CRITICAL("Encountered main (interrupt handler) stack overflow");
+    }
+  }
+  if (cpu_state.extended.cfsr & kCfsrMemFaultMask) {
+    if (cpu_state.extended.cfsr & kCfsrMmarvalidMask) {
+      PW_LOG_CRITICAL(
+          "Encountered Memory Protection Unit (MPU) violation at 0x%08" PRIx32,
+          cpu_state.extended.mmfar);
+    } else {
+      PW_LOG_CRITICAL("Encountered Memory Protection Unit (MPU) violation");
+    }
+  }
+  if (cpu_state.extended.cfsr & kCfsrBusFaultMask) {
+    if (cpu_state.extended.cfsr & kCfsrBfarvalidMask) {
+      PW_LOG_CRITICAL("Encountered bus fault at 0x%08" PRIx32,
+                      cpu_state.extended.bfar);
+    } else {
+      PW_LOG_CRITICAL("Encountered bus fault");
+    }
+  }
+  if (cpu_state.extended.cfsr & kCfsrUsageFaultMask) {
+    PW_LOG_CRITICAL("Encountered usage fault (See active CFSR fields)");
+  }
+  if ((cpu_state.extended.icsr & kIcsrVectactiveMask) == kNmiIsrNum) {
+    PW_LOG_INFO("Encountered non-maskable interrupt (NMI)");
+  }
+#if PW_CPU_EXCEPTION_EXTENDED_CFSR_DUMP
+  AnalyzeCfsr(cpu_state.extended.cfsr);
+#endif  // PW_CPU_EXCEPTION_EXTENDED_CFSR_DUMP
+}
+}  // namespace
+
+std::span<const uint8_t> RawFaultingCpuState(
+    const pw_cpu_exception_State& cpu_state) {
+  return std::span(reinterpret_cast<const uint8_t*>(&cpu_state),
+                   sizeof(cpu_state));
+}
+
+// Using this function adds approximately 100 bytes to binary size.
+void ToString(const pw_cpu_exception_State& cpu_state,
+              const std::span<char>& dest) {
+  StringBuilder builder(dest);
+  const CortexMExceptionRegisters& base = cpu_state.base;
+  const CortexMExtraRegisters& extended = cpu_state.extended;
+
+#define _PW_FORMAT_REGISTER(state_section, name) \
+  builder.Format("%s=0x%08" PRIx32 "\n", #name, state_section.name)
+
+  // Other registers.
+  _PW_FORMAT_REGISTER(base, pc);
+  _PW_FORMAT_REGISTER(base, lr);
+  _PW_FORMAT_REGISTER(base, psr);
+  _PW_FORMAT_REGISTER(extended, msp);
+  _PW_FORMAT_REGISTER(extended, psp);
+  _PW_FORMAT_REGISTER(extended, exc_return);
+  _PW_FORMAT_REGISTER(extended, cfsr);
+  _PW_FORMAT_REGISTER(extended, mmfar);
+  _PW_FORMAT_REGISTER(extended, bfar);
+  _PW_FORMAT_REGISTER(extended, icsr);
+  _PW_FORMAT_REGISTER(extended, hfsr);
+  _PW_FORMAT_REGISTER(extended, shcsr);
+  _PW_FORMAT_REGISTER(extended, control);
+
+  // General purpose registers.
+  _PW_FORMAT_REGISTER(base, r0);
+  _PW_FORMAT_REGISTER(base, r1);
+  _PW_FORMAT_REGISTER(base, r2);
+  _PW_FORMAT_REGISTER(base, r3);
+  _PW_FORMAT_REGISTER(extended, r4);
+  _PW_FORMAT_REGISTER(extended, r5);
+  _PW_FORMAT_REGISTER(extended, r6);
+  _PW_FORMAT_REGISTER(extended, r7);
+  _PW_FORMAT_REGISTER(extended, r8);
+  _PW_FORMAT_REGISTER(extended, r9);
+  _PW_FORMAT_REGISTER(extended, r10);
+  _PW_FORMAT_REGISTER(extended, r11);
+  _PW_FORMAT_REGISTER(base, r12);
+
+#undef _PW_FORMAT_REGISTER
+}
+
+// Using this function adds approximately 100 bytes to binary size.
+void LogCpuState(const pw_cpu_exception_State& cpu_state) {
+  const CortexMExceptionRegisters& base = cpu_state.base;
+  const CortexMExtraRegisters& extended = cpu_state.extended;
+
+  AnalyzeException(cpu_state);
+
+  PW_LOG_INFO("All captured CPU registers:");
+
+#define _PW_LOG_REGISTER(state_section, name) \
+  PW_LOG_INFO("  %-10s 0x%08" PRIx32, #name, state_section.name)
+
+  // Other registers.
+  _PW_LOG_REGISTER(base, pc);
+  _PW_LOG_REGISTER(base, lr);
+  _PW_LOG_REGISTER(base, psr);
+  _PW_LOG_REGISTER(extended, msp);
+  _PW_LOG_REGISTER(extended, psp);
+  _PW_LOG_REGISTER(extended, exc_return);
+  _PW_LOG_REGISTER(extended, cfsr);
+  _PW_LOG_REGISTER(extended, mmfar);
+  _PW_LOG_REGISTER(extended, bfar);
+  _PW_LOG_REGISTER(extended, icsr);
+  _PW_LOG_REGISTER(extended, hfsr);
+  _PW_LOG_REGISTER(extended, shcsr);
+  _PW_LOG_REGISTER(extended, control);
+
+  // General purpose registers.
+  _PW_LOG_REGISTER(base, r0);
+  _PW_LOG_REGISTER(base, r1);
+  _PW_LOG_REGISTER(base, r2);
+  _PW_LOG_REGISTER(base, r3);
+  _PW_LOG_REGISTER(extended, r4);
+  _PW_LOG_REGISTER(extended, r5);
+  _PW_LOG_REGISTER(extended, r6);
+  _PW_LOG_REGISTER(extended, r7);
+  _PW_LOG_REGISTER(extended, r8);
+  _PW_LOG_REGISTER(extended, r9);
+  _PW_LOG_REGISTER(extended, r10);
+  _PW_LOG_REGISTER(extended, r11);
+  _PW_LOG_REGISTER(base, r12);
+
+#undef _PW_LOG_REGISTER
+}
+
+}  // namespace pw::cpu_exception
diff --git a/pw_cpu_exception_cortex_m/docs.rst b/pw_cpu_exception_cortex_m/docs.rst
index b8726b2..2cb3064 100644
--- a/pw_cpu_exception_cortex_m/docs.rst
+++ b/pw_cpu_exception_cortex_m/docs.rst
@@ -1,18 +1,15 @@
 .. _module-pw_cpu_exception_cortex_m:
 
-=========================
+-------------------------
 pw_cpu_exception_cortex_m
-=========================
-This backend provides an implementations for the CPU exception module frontend
-for the following Cortex-M architectures:
-
-* ARMv7-M - Cortex M3
-* ARMv7-EM - Cortex M4, M7
-* ARMv8-M Mainline - Cortex M33, M33P
+-------------------------
+This backend provides an ARMv7-M implementation for the CPU exception module
+frontend. See the CPU exception frontend module description for more
+information.
 
 Setup
 =====
-There are a few ways to set up the Cortex M exception handler so the
+There are a few ways to set up the ARMv7-M exception handler so the
 application's exception handler is properly called during an exception.
 
 **1. Use existing CMSIS functions**
@@ -29,9 +26,9 @@
 **2. Modify a startup file**
   Assembly startup files for some microcontrollers initialize the interrupt
   vector table. The functions to call for fault handlers can be changed here.
-  For ARMv7-M and ARMv8-M, the fault handlers are indexes 3 to 6 of the
-  interrupt vector table. It's also may be helpful to redirect the NMI handler
-  to the entry function (if it's otherwise unused in your project).
+  For ARMv7-M, the fault handlers are indexes 3 to 6 of the interrupt vector
+  table. It's also may be helpful to redirect the NMI handler to the entry
+  function (if it's otherwise unused in your project).
 
   Default:
 
@@ -64,7 +61,7 @@
 
 **3. Modify interrupt vector table at runtime**
   Some applications may choose to modify their interrupt vector tables at
-  runtime. The exception handler works with this use case (see the
+  runtime. The ARMv7-M exception handler works with this use case (see the
   exception_entry_test integration test), but keep in mind that your
   application's exception handler will not be entered if an exception occurs
   before the vector table entries are updated to point to
@@ -86,18 +83,13 @@
 Expected Behavior
 -----------------
 In most cases, the CPU state captured by the exception handler will contain the
-basic register frame in addition to an extended set of registers
-(see ``cpu_state.h``).
-
-The exception to this is when the program stack pointer is in an MPU-protected
-or otherwise invalid memory region when the CPU attempts to push the exception
-register frame to it. In this situation, the PC, LR, and PSR registers will NOT
-be captured and will be marked with ``0xFFFFFFFF`` to indicate they are invalid.
-This backend will still be able to capture all the other registers though.
-
-``0xFFFFFFFF`` is an illegal LR value, which is why it was selected for this
-purpose. PC and PSR values of 0xFFFFFFFF are dubious too, so this constant is
-clear enough at suggesting that the registers weren't properly captured.
+ARMv7-M basic register frame in addition to an extended set of registers (see
+``cpu_state.h``). The exception to this is when the program stack pointer is in
+an MPU-protected or otherwise invalid memory region when the CPU attempts to
+push the exception register frame to it. In this situation, the PC, LR, and PSR
+registers will NOT be captured and will be marked with 0xFFFFFFFF to indicate
+they are invalid. This backend will still be able to capture all the other
+registers though.
 
 In the situation where the main stack pointer is in a memory protected or
 otherwise invalid region and fails to push CPU context, behavior is undefined.
@@ -105,10 +97,9 @@
 Nested Exceptions
 -----------------
 To enable nested fault handling:
-
-1. Enable separate detection of usage/bus/memory faults via the SHCSR.
-2. Decrease the priority of the memory, bus, and usage fault handlers. This
-   gives headroom for escalation.
+  1. Enable separate detection of usage/bus/memory faults via the SHCSR.
+  2. Decrease the priority of the memory, bus, and usage fault handlers. This
+     gives headroom for escalation.
 
 While this allows some faults to nest, it doesn't guarantee all will properly
 nest.
@@ -116,14 +107,12 @@
 Configuration Options
 =====================
 
-- ``PW_CPU_EXCEPTION_CORTEX_M_EXTENDED_CFSR_DUMP``: Enable extended logging in
-  ``pw::cpu_exception::LogCpuState()`` that dumps the active CFSR fields with
-  help strings. This is disabled by default since it increases the binary size
-  by >1.5KB when using plain-text logs, or ~460 Bytes when using tokenized
-  logging. It's useful to enable this for device bringup until your application
-  has an end-to-end crash reporting solution.
-- ``PW_CPU_EXCEPTION_CORTEX_M_LOG_LEVEL``: The log level to use for this module.
-  Logs below this level are omitted.
+ - ``PW_CPU_EXCEPTION_EXTENDED_CFSR_DUMP``: Enable extended logging in
+   ``pw::cpu_exception::LogCpuState()`` that dumps the active CFSR fields with
+   help strings. This is disabled by default since it increases the binary size
+   by >1.5KB when using plain-text logs, or ~460 Bytes when using tokenized
+   logging. It's useful to enable this for device bringup until your application
+   has an end-to-end crash reporting solution.
 
 Exception Analysis
 ==================
@@ -145,118 +134,11 @@
     20210412 15:11:14 INF Exception caused by a usage fault, bus fault.
 
     Active Crash Fault Status Register (CFSR) fields:
-    IBUSERR     Instruction bus error.
-        The processor attempted to issue an invalid instruction. It
-        detects the instruction bus error on prefecting, but this
-        flag is only set to 1 if it attempts to issue the faulting
-        instruction. When this bit is set, the processor has not
-        written a fault address to the BFAR.
+    IBUSERR     Bus fault on instruction fetch.
     UNDEFINSTR  Encountered invalid instruction.
-        The processor has attempted to execute an undefined
-        instruction. When this bit is set to 1, the PC value stacked
-        for the exception return points to the undefined instruction.
-        An undefined instruction is an instruction that the processor
-        cannot decode.
 
     All registers:
     cfsr       0x00010100
 
 .. note::
   The CFSR is not supported on ARMv6-M CPUs (Cortex M0, M0+, M1).
-
---------------------
-Snapshot integration
---------------------
-This ``pw_cpu_exception`` backend provides helper functions that capture CPU
-exception state to snapshot protos.
-
-SnapshotCpuState()
-==================
-``SnapshotCpuState()`` captures the ``pw_cpu_exception_State`` to a
-``pw.cpu_exception.cortex_m.ArmV7mCpuState`` protobuf encoder.
-
-
-SnapshotMainStackThread()
-=========================
-``SnapshotMainStackThread()`` captures the main stack's execution thread state
-if active either from a given ``pw_cpu_exception_State`` or from the current
-running context. It captures the thread name depending on the processor mode,
-either ``Main Stack (Handler Mode)`` or ``Main Stack (Thread Mode)``. The stack
-limits must be provided along with a stack processing callback. All of this
-information is captured by a ``pw::thread::Thread`` protobuf encoder.
-
-.. note::
-  We recommend providing the ``pw_cpu_exception_State``, for example through
-  ``pw_cpu_exception_DefaultHandler()`` instead of using the current running
-  context to capture the main stack to minimize how much of the snapshot
-  handling is captured in the stack.
-
-Python processor
-================
-This module's included Python exception analyzer tooling provides snapshot
-integration via a ``process_snapshot()`` function that produces a multi-line
-dump from a serialized snapshot proto, for example:
-
-.. code-block::
-
-  Exception caused by a usage fault.
-
-  Active Crash Fault Status Register (CFSR) fields:
-  UNDEFINSTR  Undefined Instruction UsageFault.
-      The processor has attempted to execute an undefined
-      instruction. When this bit is set to 1, the PC value stacked
-      for the exception return points to the undefined instruction.
-      An undefined instruction is an instruction that the processor
-      cannot decode.
-
-  All registers:
-  pc         0x0800e1c4 example::Service::Crash(_example_service_CrashRequest const&, _pw_protobuf_Empty&) (src/example_service/service.cc:131)
-  lr         0x0800e141 example::Service::Crash(_example_service_CrashRequest const&, _pw_protobuf_Empty&) (src/example_service/service.cc:128)
-  psr        0x81000000
-  msp        0x20040fd8
-  psp        0x20001488
-  exc_return 0xffffffed
-  cfsr       0x00010000
-  mmfar      0xe000ed34
-  bfar       0xe000ed38
-  icsr       0x00000803
-  hfsr       0x40000000
-  shcsr      0x00000000
-  control    0x00000000
-  r0         0xe03f7847
-  r1         0x714083dc
-  r2         0x0b36dc49
-  r3         0x7fbfbe1a
-  r4         0xc36e8efb
-  r5         0x69a14b13
-  r6         0x0ec35eaa
-  r7         0xa5df5543
-  r8         0xc892b931
-  r9         0xa2372c94
-  r10        0xbd15c968
-  r11        0x759b95ab
-  r12        0x00000000
-
-Module Configuration Options
-============================
-The following configurations can be adjusted via compile-time configuration of
-this module, see the
-:ref:`module documentation <module-structure-compile-time-configuration>` for
-more details.
-
-.. c:macro:: PW_CPU_EXCEPTION_CORTEX_M_LOG_LEVEL
-
-  The log level to use for this module. Logs below this level are omitted.
-
-  This defaults to ``PW_LOG_LEVEL_DEBUG``.
-
-.. c:macro:: PW_CPU_EXCEPTION_CORTEX_M_EXTENDED_CFSR_DUMP
-
-  Enables extended logging in pw::cpu_exception::LogCpuState() and
-  pw::cpu_exception::cortex_m::LogExceptionAnalysis() that dumps the active
-  CFSR fields with help strings. This is disabled by default since it
-  increases the binary size by >1.5KB when using plain-text logs, or ~460
-  Bytes when using tokenized logging. It's useful to enable this for device
-  bringup until your application has an end-to-end crash reporting solution.
-
-  This is disabled by default.
diff --git a/pw_cpu_exception_cortex_m/entry.cc b/pw_cpu_exception_cortex_m/entry.cc
index 0edff82..97925f5 100644
--- a/pw_cpu_exception_cortex_m/entry.cc
+++ b/pw_cpu_exception_cortex_m/entry.cc
@@ -19,18 +19,32 @@
 
 #include "pw_cpu_exception/handler.h"
 #include "pw_cpu_exception_cortex_m/cpu_state.h"
-#include "pw_cpu_exception_cortex_m/util.h"
 #include "pw_cpu_exception_cortex_m_private/cortex_m_constants.h"
-#include "pw_preprocessor/arch.h"
 #include "pw_preprocessor/compiler.h"
 
 // TODO(pwbug/311): Deprecated naming.
 PW_EXTERN_C PW_NO_PROLOGUE __attribute__((alias("pw_cpu_exception_Entry"))) void
 pw_CpuExceptionEntry(void);
 
-namespace pw::cpu_exception::cortex_m {
+namespace pw::cpu_exception {
 namespace {
 
+// If the CPU fails to capture some registers, the captured struct members will
+// be populated with this value. The only registers that this value should be
+// loaded into are pc, lr, and psr when the CPU fails to push an exception
+// context frame.
+//
+// 0xFFFFFFFF is an illegal lr value, which is why it was selected for this
+// purpose. pc and psr values of 0xFFFFFFFF are dubious too, so this constant
+// is clear enough at expressing that the registers weren't properly captured.
+constexpr uint32_t kInvalidRegisterValue = 0xFFFFFFFF;
+
+// Checks exc_return in the captured CPU state to determine which stack pointer
+// was in use prior to entering the exception handler.
+bool PspWasActive(const pw_cpu_exception_State& cpu_state) {
+  return cpu_state.extended.exc_return & kExcReturnStackMask;
+}
+
 // Checks exc_return to determine if FPU state was pushed to the stack in
 // addition to the base CPU context frame.
 bool FpuStateWasPushed(const pw_cpu_exception_State& cpu_state) {
@@ -44,9 +58,6 @@
 void CloneBaseRegistersFromPsp(pw_cpu_exception_State* cpu_state) {
   // If CPU succeeded in pushing context to PSP, copy it to the MSP.
   if (!(cpu_state->extended.cfsr & kCfsrStkerrMask) &&
-#if _PW_ARCH_ARM_V8M_MAINLINE
-      !(cpu_state->extended.cfsr & kCfsrStkofMask) &&
-#endif  // _PW_ARCH_ARM_V8M_MAINLINE
       !(cpu_state->extended.cfsr & kCfsrMstkerrMask)) {
     // TODO(amontanez): {r0-r3,r12} are captured in pw_cpu_exception_Entry(),
     //                  so this only really needs to copy pc, lr, and psr. Could
@@ -54,14 +65,14 @@
     //                  complexity.
     std::memcpy(&cpu_state->base,
                 reinterpret_cast<void*>(cpu_state->extended.psp),
-                sizeof(ExceptionRegisters));
+                sizeof(CortexMExceptionRegisters));
   } else {
     // If CPU context wasn't pushed to stack on exception entry, we can't
     // recover psr, lr, and pc from exception-time. Make these values clearly
     // invalid.
-    cpu_state->base.lr = kUndefinedPcLrOrPsrRegValue;
-    cpu_state->base.pc = kUndefinedPcLrOrPsrRegValue;
-    cpu_state->base.psr = kUndefinedPcLrOrPsrRegValue;
+    cpu_state->base.lr = kInvalidRegisterValue;
+    cpu_state->base.pc = kInvalidRegisterValue;
+    cpu_state->base.psr = kInvalidRegisterValue;
   }
 }
 
@@ -76,21 +87,18 @@
   // continue. Otherwise, don't attempt as we'll likely end up in an escalated
   // hard fault.
   if (!(cpu_state->extended.cfsr & kCfsrStkerrMask) &&
-#if _PW_ARCH_ARM_V8M_MAINLINE
-      !(cpu_state->extended.cfsr & kCfsrStkofMask) &&
-#endif  // _PW_ARCH_ARM_V8M_MAINLINE
       !(cpu_state->extended.cfsr & kCfsrMstkerrMask)) {
     std::memcpy(reinterpret_cast<void*>(cpu_state->extended.psp),
                 &cpu_state->base,
-                sizeof(ExceptionRegisters));
+                sizeof(CortexMExceptionRegisters));
   }
 }
 
 // Determines the size of the CPU-pushed context frame.
 uint32_t CpuContextSize(const pw_cpu_exception_State& cpu_state) {
-  uint32_t cpu_context_size = sizeof(ExceptionRegisters);
+  uint32_t cpu_context_size = sizeof(CortexMExceptionRegisters);
   if (FpuStateWasPushed(cpu_state)) {
-    cpu_context_size += sizeof(ExceptionRegistersFpu);
+    cpu_context_size += sizeof(CortexMExceptionRegistersFpu);
   }
   if (cpu_state.base.psr & kPsrExtraStackAlignBit) {
     // Account for the extra 4-bytes the processor
@@ -108,11 +116,7 @@
   // If CPU context was not pushed to program stack (because program stack
   // wasn't in use, or an error occurred when pushing context), the PSP doesn't
   // need to be shifted.
-  if (!ProcessStackActive(cpu_state) ||
-      (cpu_state.extended.cfsr & kCfsrStkerrMask) ||
-#if _PW_ARCH_ARM_V8M_MAINLINE
-      (cpu_state.extended.cfsr & kCfsrStkofMask) ||
-#endif  // _PW_ARCH_ARM_V8M_MAINLINE
+  if (!PspWasActive(cpu_state) || (cpu_state.extended.cfsr & kCfsrStkerrMask) ||
       (cpu_state.extended.cfsr & kCfsrMstkerrMask)) {
     return 0;
   }
@@ -124,15 +128,15 @@
 // at exception-time. On exception return, it is restored to the appropriate
 // location. This calculates the delta that is used for these patch operations.
 uint32_t CalculateMspDelta(const pw_cpu_exception_State& cpu_state) {
-  if (ProcessStackActive(cpu_state)) {
+  if (PspWasActive(cpu_state)) {
     // TODO(amontanez): Since FPU state isn't captured at this time, we ignore
     //                  it when patching MSP. To add FPU capture support,
     //                  delete this if block as CpuContextSize() will include
     //                  FPU context size in the calculation.
-    return sizeof(ExceptionRegisters) + sizeof(ExtraRegisters);
+    return sizeof(CortexMExceptionRegisters) + sizeof(CortexMExtraRegisters);
   }
 
-  return CpuContextSize(cpu_state) + sizeof(ExtraRegisters);
+  return CpuContextSize(cpu_state) + sizeof(CortexMExtraRegisters);
 }
 
 }  // namespace
@@ -155,7 +159,7 @@
   // the values can be copied into in the pw_cpu_exception_State struct that is
   // passed to HandleCpuException(). The cpu_state passed to the handler is
   // ALWAYS stored on the main stack (MSP).
-  if (ProcessStackActive(*cpu_state)) {
+  if (PspWasActive(*cpu_state)) {
     CloneBaseRegistersFromPsp(cpu_state);
     // If PSP wasn't active, this delta is 0.
     cpu_state->extended.psp += CalculatePspDelta(*cpu_state);
@@ -178,7 +182,7 @@
   // If PSP was active and the CPU pushed a context frame, we must copy the
   // potentially modified state from cpu_state back to the PSP so the CPU can
   // resume execution with the modified values.
-  if (ProcessStackActive(*cpu_state)) {
+  if (PspWasActive(*cpu_state)) {
     // In this case, there's no need to touch the MSP as it's at the location
     // before we entering the exception (effectively popping the state initially
     // pushed to the main stack).
@@ -207,9 +211,9 @@
       // for more details)
       // The following block of assembly is equivalent to:
       //   if (lr & (1 << 2)) {
-      //     msp -= sizeof(ExceptionRegisters);
-      //     ExceptionRegisters* state =
-      //         (ExceptionRegisters*) msp;
+      //     msp -= sizeof(CortexMExceptionRegisters);
+      //     CortexMExceptionRegisters* state =
+      //         (CortexMExceptionRegisters*) msp;
       //     state->r0 = r0;
       //     state->r1 = r1;
       //     state->r2 = r2;
@@ -238,21 +242,9 @@
       " mrs r3, psp                                           \n"
       " mrs r4, control                                       \n"
 
-#if _PW_ARCH_ARM_V7M || _PW_ARCH_ARM_V7EM
       // Store special registers to stack.
       " stmdb r0!, {r1-r4}                                    \n"
 
-#elif _PW_ARCH_ARM_V8M_MAINLINE
-      // Load ARMv8-M specific special registers.
-      " mrs r5, msplim                                        \n"
-      " mrs r6, psplim                                        \n"
-
-      // Store special registers to stack.
-      " stmdb r0!, {r1-r6}                                    \n"
-#else
-#error "Support required for your Cortex-M Arch"
-#endif  // defined(PW_CPU_EXCEPTION_CORTEX_M_ARMV7M)
-
       // Store a pointer to the beginning of special registers in r4 so they can
       // be restored later.
       " mov r4, r0                                            \n"
@@ -285,11 +277,11 @@
       // Exit exception.
       " bx lr                                                 \n"
       : /*output=*/
-      : /*input=*/[base_state_size]"i"(sizeof(ExceptionRegisters)),
-                  [extra_state_size]"i"(sizeof(ExtraRegisters))
+      : /*input=*/[base_state_size]"i"(sizeof(CortexMExceptionRegisters)),
+                  [extra_state_size]"i"(sizeof(CortexMExtraRegisters))
       // clang-format on
   );
 }
 
 }  // extern "C"
-}  // namespace pw::cpu_exception::cortex_m
+}  // namespace pw::cpu_exception
diff --git a/pw_cpu_exception_cortex_m/exception_entry_test.cc b/pw_cpu_exception_cortex_m/exception_entry_test.cc
index 8b7c2f6..0cd21da 100644
--- a/pw_cpu_exception_cortex_m/exception_entry_test.cc
+++ b/pw_cpu_exception_cortex_m/exception_entry_test.cc
@@ -21,17 +21,28 @@
 #include "pw_cpu_exception/handler.h"
 #include "pw_cpu_exception/support.h"
 #include "pw_cpu_exception_cortex_m/cpu_state.h"
-#include "pw_cpu_exception_cortex_m_private/cortex_m_constants.h"
 
-namespace pw::cpu_exception::cortex_m {
+namespace pw::cpu_exception {
 namespace {
 
-using pw::cpu_exception::RawFaultingCpuState;
-
 // CMSIS/Cortex-M/ARMv7 related constants.
 // These values are from the ARMv7-M Architecture Reference Manual DDI 0403E.b.
 // https://static.docs.arm.com/ddi0403/e/DDI0403E_B_armv7m_arm.pdf
 
+// Exception ISR number. (ARMv7-M Section B1.5.2)
+constexpr uint32_t kHardFaultIsrNum = 0x3u;
+constexpr uint32_t kMemFaultIsrNum = 0x4u;
+constexpr uint32_t kBusFaultIsrNum = 0x5u;
+constexpr uint32_t kUsageFaultIsrNum = 0x6u;
+
+// Masks for individual bits of HFSR. (ARMv7-M Section B3.2.16)
+constexpr uint32_t kForcedHardfaultMask = 0x1u << 30;
+
+// Masks for individual bits of CFSR. (ARMv7-M Section B3.2.15)
+constexpr uint32_t kUsageFaultStart = 0x1u << 16;
+constexpr uint32_t kUnalignedFaultMask = kUsageFaultStart << 8;
+constexpr uint32_t kDivByZeroFaultMask = kUsageFaultStart << 9;
+
 // CCR flags. (ARMv7-M Section B3.2.8)
 constexpr uint32_t kUnalignedTrapEnableMask = 0x1u << 3;
 constexpr uint32_t kDivByZeroTrapEnableMask = 0x1u << 4;
@@ -41,6 +52,9 @@
 constexpr uint32_t kBusFaultEnableMask = 0x1 << 17;
 constexpr uint32_t kUsageFaultEnableMask = 0x1 << 18;
 
+// Bit masks for an exception return value. (ARMv7-M Section B1.5.8)
+constexpr uint32_t kExcReturnBasicFrameMask = (0x1u << 4);
+
 // CPCAR mask that enables FPU. (ARMv7-M Section B3.2.20)
 constexpr uint32_t kFpuEnableMask = (0xFu << 20);
 
@@ -49,6 +63,12 @@
     *reinterpret_cast<volatile uint32_t*>(0xE000ED08u);
 volatile uint32_t& cortex_m_ccr =
     *reinterpret_cast<volatile uint32_t*>(0xE000ED14u);
+volatile uint32_t& cortex_m_shcsr =
+    *reinterpret_cast<volatile uint32_t*>(0xE000ED24u);
+volatile uint32_t& cortex_m_cfsr =
+    *reinterpret_cast<volatile uint32_t*>(0xE000ED28u);
+volatile uint32_t& cortex_m_hfsr =
+    *reinterpret_cast<volatile uint32_t*>(0xE000ED2Cu);
 volatile uint32_t& cortex_m_cpacr =
     *reinterpret_cast<volatile uint32_t*>(0xE000ED88u);
 
@@ -393,7 +413,7 @@
   BeginExtendedFaultTest();
   ASSERT_EQ(exceptions_handled, 1u);
   ASSERT_TRUE(span_matches);
-  const ExtraRegisters& extended_registers = captured_state.extended;
+  const CortexMExtraRegisters& extended_registers = captured_state.extended;
   // captured_state values must be cast since they're in a packed struct.
   EXPECT_EQ(static_cast<uint32_t>(extended_registers.r4), kMagicPattern);
   EXPECT_EQ(static_cast<uint32_t>(extended_registers.r5), 0u);
@@ -401,7 +421,7 @@
 
   // Check expected values for this crash.
   EXPECT_EQ(static_cast<uint32_t>(extended_registers.cfsr),
-            static_cast<uint32_t>(kCfsrDivbyzeroMask));
+            static_cast<uint32_t>(kDivByZeroFaultMask));
   EXPECT_EQ((extended_registers.icsr & 0x1FFu), kUsageFaultIsrNum);
 }
 
@@ -410,7 +430,7 @@
   BeginExtendedFaultUnalignedStackTest();
   ASSERT_EQ(exceptions_handled, 1u);
   ASSERT_TRUE(span_matches);
-  const ExtraRegisters& extended_registers = captured_state.extended;
+  const CortexMExtraRegisters& extended_registers = captured_state.extended;
   // captured_state values must be cast since they're in a packed struct.
   EXPECT_EQ(static_cast<uint32_t>(extended_registers.r4), kMagicPattern);
   EXPECT_EQ(static_cast<uint32_t>(extended_registers.r5), 0u);
@@ -418,7 +438,7 @@
 
   // Check expected values for this crash.
   EXPECT_EQ(static_cast<uint32_t>(extended_registers.cfsr),
-            static_cast<uint32_t>(kCfsrDivbyzeroMask));
+            static_cast<uint32_t>(kDivByZeroFaultMask));
   EXPECT_EQ((extended_registers.icsr & 0x1FFu), kUsageFaultIsrNum);
 }
 
@@ -486,7 +506,7 @@
   Setup(/*use_fpu=*/true);
   BeginExtendedFaultFloatTest();
   ASSERT_EQ(exceptions_handled, 1u);
-  const ExtraRegisters& extended_registers = captured_state.extended;
+  const CortexMExtraRegisters& extended_registers = captured_state.extended;
   // captured_state values must be cast since they're in a packed struct.
   EXPECT_EQ(static_cast<uint32_t>(extended_registers.r4), kMagicPattern);
   EXPECT_EQ(static_cast<uint32_t>(extended_registers.r5), 0u);
@@ -494,7 +514,7 @@
 
   // Check expected values for this crash.
   EXPECT_EQ(static_cast<uint32_t>(extended_registers.cfsr),
-            static_cast<uint32_t>(kCfsrDivbyzeroMask));
+            static_cast<uint32_t>(kDivByZeroFaultMask));
   EXPECT_EQ((extended_registers.icsr & 0x1FFu), kUsageFaultIsrNum);
 
   // Check fpu state was pushed during exception
@@ -509,7 +529,7 @@
   BeginExtendedFaultUnalignedStackFloatTest();
   ASSERT_EQ(exceptions_handled, 1u);
   ASSERT_TRUE(span_matches);
-  const ExtraRegisters& extended_registers = captured_state.extended;
+  const CortexMExtraRegisters& extended_registers = captured_state.extended;
   // captured_state values must be cast since they're in a packed struct.
   EXPECT_EQ(static_cast<uint32_t>(extended_registers.r4), kMagicPattern);
   EXPECT_EQ(static_cast<uint32_t>(extended_registers.r5), 0u);
@@ -517,7 +537,7 @@
 
   // Check expected values for this crash.
   EXPECT_EQ(static_cast<uint32_t>(extended_registers.cfsr),
-            static_cast<uint32_t>(kCfsrDivbyzeroMask));
+            static_cast<uint32_t>(kDivByZeroFaultMask));
   EXPECT_EQ((extended_registers.icsr & 0x1FFu), kUsageFaultIsrNum);
 
   // Check fpu state was pushed during exception.
@@ -555,21 +575,21 @@
   // Clear HFSR forced (nested) hard fault mask if set. This will only be
   // set by the nested fault test.
   EXPECT_EQ(state->extended.hfsr, cortex_m_hfsr);
-  if (cortex_m_hfsr & kHfsrForcedMask) {
-    cortex_m_hfsr = kHfsrForcedMask;
+  if (cortex_m_hfsr & kForcedHardfaultMask) {
+    cortex_m_hfsr = kForcedHardfaultMask;
   }
 
-  if (cortex_m_cfsr & kCfsrUnalignedMask) {
+  if (cortex_m_cfsr & kUnalignedFaultMask) {
     // Copy captured state to check later.
     std::memcpy(&captured_states[exceptions_handled],
                 state,
                 sizeof(pw_cpu_exception_State));
 
     // Disable unaligned read/write trapping to "handle" exception.
-    cortex_m_cfsr = kCfsrUnalignedMask;
+    cortex_m_cfsr = kUnalignedFaultMask;
     exceptions_handled++;
     return;
-  } else if (cortex_m_cfsr & kCfsrDivbyzeroMask) {
+  } else if (cortex_m_cfsr & kDivByZeroFaultMask) {
     // Copy captured state to check later.
     std::memcpy(&captured_states[exceptions_handled],
                 state,
@@ -585,7 +605,7 @@
     }
 
     // Disable divide-by-zero trapping to "handle" exception.
-    cortex_m_cfsr = kCfsrDivbyzeroMask;
+    cortex_m_cfsr = kDivByZeroFaultMask;
     exceptions_handled++;
     return;
   }
@@ -598,4 +618,4 @@
 }
 
 }  // namespace
-}  // namespace pw::cpu_exception::cortex_m
+}  // namespace pw::cpu_exception
diff --git a/pw_cpu_exception_cortex_m/proto_dump.cc b/pw_cpu_exception_cortex_m/proto_dump.cc
index 0b38836..0faec5a 100644
--- a/pw_cpu_exception_cortex_m/proto_dump.cc
+++ b/pw_cpu_exception_cortex_m/proto_dump.cc
@@ -16,65 +16,49 @@
 #include "pw_preprocessor/compiler.h"
 #include "pw_protobuf/encoder.h"
 
-namespace pw::cpu_exception::cortex_m {
+namespace pw::cpu_exception {
 
-Status DumpCpuStateProto(protobuf::StreamEncoder& dest,
+Status DumpCpuStateProto(protobuf::Encoder& dest,
                          const pw_cpu_exception_State& cpu_state) {
-  // Note that the encoder's status is checked at the end and ignored at the
-  // Write*() calls to reduce the number of branches.
-  cortex_m::ArmV7mCpuState::StreamEncoder& state_encoder =
-      *static_cast<cortex_m::ArmV7mCpuState::StreamEncoder*>(&dest);
-
-  const ExceptionRegisters& base = cpu_state.base;
-  const ExtraRegisters& extended = cpu_state.extended;
+  cortex_m::ArmV7mCpuState::Encoder state_encoder(&dest);
 
   // Special and mem-mapped registers.
-  if (base.pc != kUndefinedPcLrOrPsrRegValue) {
-    state_encoder.WritePc(base.pc).IgnoreError();
-  }
-  if (base.lr != kUndefinedPcLrOrPsrRegValue) {
-    state_encoder.WriteLr(base.lr).IgnoreError();
-  }
-  if (base.psr != kUndefinedPcLrOrPsrRegValue) {
-    state_encoder.WritePsr(base.psr).IgnoreError();
-  }
-  state_encoder.WriteMsp(extended.msp).IgnoreError();
-  state_encoder.WritePsp(extended.psp).IgnoreError();
-  state_encoder.WriteExcReturn(extended.exc_return).IgnoreError();
-  state_encoder.WriteCfsr(extended.cfsr).IgnoreError();
-#if _PW_ARCH_ARM_V8M_MAINLINE
-  state_encoder.WriteMsplim(extended.msplim).IgnoreError();
-  state_encoder.WritePsplim(extended.psplim).IgnoreError();
-#endif  // _PW_ARCH_ARM_V8M_MAINLINE
-  state_encoder.WriteMmfar(extended.mmfar).IgnoreError();
-  state_encoder.WriteBfar(extended.bfar).IgnoreError();
-  state_encoder.WriteIcsr(extended.icsr).IgnoreError();
-  state_encoder.WriteHfsr(extended.hfsr).IgnoreError();
-  state_encoder.WriteShcsr(extended.shcsr).IgnoreError();
-  state_encoder.WriteControl(extended.control).IgnoreError();
+  state_encoder.WritePc(cpu_state.base.pc);
+  state_encoder.WriteLr(cpu_state.base.lr);
+  state_encoder.WritePsr(cpu_state.base.psr);
+  state_encoder.WriteMsp(cpu_state.extended.msp);
+  state_encoder.WritePsp(cpu_state.extended.psp);
+  state_encoder.WriteExcReturn(cpu_state.extended.exc_return);
+  state_encoder.WriteCfsr(cpu_state.extended.cfsr);
+  state_encoder.WriteMmfar(cpu_state.extended.mmfar);
+  state_encoder.WriteBfar(cpu_state.extended.bfar);
+  state_encoder.WriteIcsr(cpu_state.extended.icsr);
+  state_encoder.WriteHfsr(cpu_state.extended.hfsr);
+  state_encoder.WriteShcsr(cpu_state.extended.shcsr);
+  state_encoder.WriteControl(cpu_state.extended.control);
 
   // General purpose registers.
-  state_encoder.WriteR0(base.r0).IgnoreError();
-  state_encoder.WriteR1(base.r1).IgnoreError();
-  state_encoder.WriteR2(base.r2).IgnoreError();
-  state_encoder.WriteR3(base.r3).IgnoreError();
-  state_encoder.WriteR4(extended.r4).IgnoreError();
-  state_encoder.WriteR5(extended.r5).IgnoreError();
-  state_encoder.WriteR6(extended.r6).IgnoreError();
-  state_encoder.WriteR7(extended.r7).IgnoreError();
-  state_encoder.WriteR8(extended.r8).IgnoreError();
-  state_encoder.WriteR9(extended.r9).IgnoreError();
-  state_encoder.WriteR10(extended.r10).IgnoreError();
-  state_encoder.WriteR11(extended.r11).IgnoreError();
-  state_encoder.WriteR12(base.r12).IgnoreError();
+  state_encoder.WriteR0(cpu_state.base.r0);
+  state_encoder.WriteR1(cpu_state.base.r1);
+  state_encoder.WriteR2(cpu_state.base.r2);
+  state_encoder.WriteR3(cpu_state.base.r3);
+  state_encoder.WriteR4(cpu_state.extended.r4);
+  state_encoder.WriteR5(cpu_state.extended.r5);
+  state_encoder.WriteR6(cpu_state.extended.r6);
+  state_encoder.WriteR7(cpu_state.extended.r7);
+  state_encoder.WriteR8(cpu_state.extended.r8);
+  state_encoder.WriteR9(cpu_state.extended.r9);
+  state_encoder.WriteR10(cpu_state.extended.r10);
+  state_encoder.WriteR11(cpu_state.extended.r11);
 
   // If the encode buffer was exhausted in an earlier write, it will be
   // reflected here.
-  if (const Status status = state_encoder.status(); !status.ok()) {
+  Status status = state_encoder.WriteR12(cpu_state.base.r12);
+  if (!status.ok()) {
     return status.IsResourceExhausted() ? Status::ResourceExhausted()
                                         : Status::Unknown();
   }
   return OkStatus();
 }
 
-}  // namespace pw::cpu_exception::cortex_m
+}  // namespace pw::cpu_exception
diff --git a/pw_cpu_exception_cortex_m/public/pw_cpu_exception_cortex_m/cpu_state.h b/pw_cpu_exception_cortex_m/public/pw_cpu_exception_cortex_m/cpu_state.h
index 4aea5e4..440207d 100644
--- a/pw_cpu_exception_cortex_m/public/pw_cpu_exception_cortex_m/cpu_state.h
+++ b/pw_cpu_exception_cortex_m/public/pw_cpu_exception_cortex_m/cpu_state.h
@@ -13,42 +13,26 @@
 // the License.
 #pragma once
 
-#ifdef __cplusplus
-
 #include <cstdint>
 
-#include "pw_preprocessor/arch.h"
 #include "pw_preprocessor/compiler.h"
 
-namespace pw::cpu_exception::cortex_m {
-
-// The PC, LR, and PSR registers are not captured when the program stack
-// pointer is in an MPU-protected or otherwise invalid memory region. In
-// these situations, the registers are set to 0xFFFF'FFFF to indicate they
-// are invalid.
-//
-// 0xFFFFFFFF is an illegal LR value, which is why it was selected for
-// this purpose. PC and PSR values of 0xFFFFFFFF are dubious too, so this
-// constant is clear enough at suggesting that the registers weren't
-// properly captured.
-constexpr uintptr_t kUndefinedPcLrOrPsrRegValue = 0xFFFF'FFFF;
+namespace pw::cpu_exception {
 
 // This is dictated by ARMv7-M architecture. Do not change.
-struct ExceptionRegisters {
+PW_PACKED(struct) CortexMExceptionRegisters {
   uint32_t r0;
   uint32_t r1;
   uint32_t r2;
   uint32_t r3;
   uint32_t r12;
-  uint32_t lr;   // Link register, note this may be invalid.
-  uint32_t pc;   // Program counter, note this may be invalid.
-  uint32_t psr;  // Program status register, note this may be invalid.
+  uint32_t lr;   // Link register.
+  uint32_t pc;   // Program counter.
+  uint32_t psr;  // Program status register.
 };
-static_assert(sizeof(ExceptionRegisters) == (sizeof(uint32_t) * 8),
-              "There's unexpected padding.");
 
 // This is dictated by ARMv7-M architecture. Do not change.
-struct ExceptionRegistersFpu {
+PW_PACKED(struct) CortexMExceptionRegistersFpu {
   uint32_t s0;
   uint32_t s1;
   uint32_t s2;
@@ -68,8 +52,6 @@
   uint32_t fpscr;
   uint32_t reserved;
 };
-static_assert(sizeof(ExceptionRegistersFpu) == (sizeof(uint32_t) * 18),
-              "There's unexpected padding.");
 
 // Bit in the PSR that indicates CPU added an extra word on the stack to
 // align it during context save for an exception.
@@ -80,7 +62,7 @@
 // values are populated in assembly).
 //
 // NOTE: Memory mapped registers are NOT restored upon fault return!
-struct ExtraRegisters {
+PW_PACKED(struct) CortexMExtraRegisters {
   // Memory mapped registers.
   uint32_t cfsr;
   uint32_t mmfar;
@@ -93,10 +75,6 @@
   uint32_t msp;
   uint32_t psp;
   uint32_t control;
-#if _PW_ARCH_ARM_V8M_MAINLINE
-  uint32_t msplim;
-  uint32_t psplim;
-#endif  // _PW_ARCH_ARM_V8M_MAINLINE
   // General purpose registers.
   uint32_t r4;
   uint32_t r5;
@@ -107,26 +85,13 @@
   uint32_t r10;
   uint32_t r11;
 };
-static_assert(sizeof(ExtraRegisters) ==
-#if _PW_ARCH_ARM_V8M_MAINLINE
-                  (sizeof(uint32_t) * 20),
-#else   // !_PW_ARCH_ARM_V8M_MAINLINE
-                  (sizeof(uint32_t) * 18),
-#endif  // _PW_ARCH_ARM_V8M_MAINLINE
-              "There's unexpected padding.");
 
-}  // namespace pw::cpu_exception::cortex_m
+}  // namespace pw::cpu_exception
 
-struct pw_cpu_exception_State {
-  pw::cpu_exception::cortex_m::ExtraRegisters extended;
-  pw::cpu_exception::cortex_m::ExceptionRegisters base;
+PW_PACKED(struct) pw_cpu_exception_State {
+  pw::cpu_exception::CortexMExtraRegisters extended;
+  pw::cpu_exception::CortexMExceptionRegisters base;
   // TODO(amontanez): FPU registers may or may not be here as well. Make the
   // availability of the FPU registers a compile-time configuration when FPU
   // register support is added.
 };
-
-#else  // !__cplusplus
-
-typedef struct pw_cpu_exception_State pw_cpu_exception_State;
-
-#endif  // __cplusplus
diff --git a/pw_cpu_exception_cortex_m/public/pw_cpu_exception_cortex_m/proto_dump.h b/pw_cpu_exception_cortex_m/public/pw_cpu_exception_cortex_m/proto_dump.h
index bc714e5..8a8d5ba 100644
--- a/pw_cpu_exception_cortex_m/public/pw_cpu_exception_cortex_m/proto_dump.h
+++ b/pw_cpu_exception_cortex_m/public/pw_cpu_exception_cortex_m/proto_dump.h
@@ -17,7 +17,7 @@
 #include "pw_protobuf/encoder.h"
 #include "pw_status/status.h"
 
-namespace pw::cpu_exception::cortex_m {
+namespace pw::cpu_exception {
 
 // Dumps the cpu state struct as a proto (defined in
 // pw_cpu_exception_cortex_m_protos/cpu_state.proto). The final proto is up to
@@ -27,7 +27,7 @@
 //   OK - Entire proto was written to the encoder.
 //   RESOURCE_EXHAUSTED - Insufficient space to encode proto.
 //   UNKNOWN - Some other proto encoding error occurred.
-Status DumpCpuStateProto(protobuf::StreamEncoder& dest,
+Status DumpCpuStateProto(protobuf::Encoder& dest,
                          const pw_cpu_exception_State& cpu_state);
 
-}  // namespace pw::cpu_exception::cortex_m
+}  // namespace pw::cpu_exception
diff --git a/pw_cpu_exception_cortex_m/public/pw_cpu_exception_cortex_m/snapshot.h b/pw_cpu_exception_cortex_m/public/pw_cpu_exception_cortex_m/snapshot.h
deleted file mode 100644
index 50ba3a3..0000000
--- a/pw_cpu_exception_cortex_m/public/pw_cpu_exception_cortex_m/snapshot.h
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_cpu_exception_cortex_m/cpu_state.h"
-#include "pw_cpu_exception_cortex_m_protos/cpu_state.pwpb.h"
-#include "pw_protobuf/encoder.h"
-#include "pw_status/status.h"
-#include "pw_thread/snapshot.h"
-#include "pw_thread_protos/thread.pwpb.h"
-
-namespace pw::cpu_exception::cortex_m {
-
-// Takes the provided pw_cpu_exception_State, and writes the cpu register state
-// to the provided SnapshotCpuStateOverlay encoder.
-//
-// Captures the pw.cpu_exception.cortex_m.ArmV7mCpuState proto field.
-Status SnapshotCpuState(
-    const pw_cpu_exception_State& cpu_state,
-    cpu_exception::cortex_m::SnapshotCpuStateOverlay::StreamEncoder& encoder);
-
-// Captures the main stack thread if active as part of a snapshot based on a
-// previously captured cpu_state.
-Status SnapshotMainStackThread(
-    const pw_cpu_exception_State& cpu_state,
-    uintptr_t stack_low_addr,
-    uintptr_t stack_high_addr,
-    thread::SnapshotThreadInfo::StreamEncoder& encoder,
-    thread::ProcessThreadStackCallback& thread_stack_callback);
-
-// Captures the main stack thread if active as part of a snapshot based on the
-// current context.
-//
-// Note: This is NOT the recommended way to capture the main stack as it will
-// include the snapshot handling as part of the main stack capture.
-Status SnapshotMainStackThread(
-    uintptr_t stack_low_addr,
-    uintptr_t stack_high_addr,
-    thread::SnapshotThreadInfo::StreamEncoder& encoder,
-    thread::ProcessThreadStackCallback& thread_stack_callback);
-
-// Captures the main stack thread if active as part of the cpu register state if
-// optionally provided, else the current context is used.
-//
-// Note: This is NOT the recommended way to capture the main stack as it will
-// include the snapshot handling as part of the main stack capture.
-inline Status SnapshotMainStackThread(
-    const pw_cpu_exception_State* optional_cpu_state,
-    uintptr_t stack_low_addr,
-    uintptr_t stack_high_addr,
-    thread::SnapshotThreadInfo::StreamEncoder& encoder,
-    thread::ProcessThreadStackCallback& thread_stack_callback) {
-  if (optional_cpu_state != nullptr) {
-    return SnapshotMainStackThread(*optional_cpu_state,
-                                   stack_low_addr,
-                                   stack_high_addr,
-                                   encoder,
-                                   thread_stack_callback);
-  }
-  return SnapshotMainStackThread(
-      stack_low_addr, stack_high_addr, encoder, thread_stack_callback);
-}
-
-}  // namespace pw::cpu_exception::cortex_m
diff --git a/pw_cpu_exception_cortex_m/public/pw_cpu_exception_cortex_m/util.h b/pw_cpu_exception_cortex_m/public/pw_cpu_exception_cortex_m/util.h
deleted file mode 100644
index 3c37c4a..0000000
--- a/pw_cpu_exception_cortex_m/public/pw_cpu_exception_cortex_m/util.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_cpu_exception_cortex_m/cpu_state.h"
-
-namespace pw::cpu_exception::cortex_m {
-
-void LogExceptionAnalysis(const pw_cpu_exception_State& cpu_state);
-
-enum class ProcessorMode {
-  kHandlerMode,  // Handling interrupts/exceptions (msp).
-  kThreadMode,   // May be on either psp or msp.
-};
-ProcessorMode ActiveProcessorMode(const pw_cpu_exception_State& cpu_state);
-
-// Returns whether the msp was active in thread or handler modes.
-bool MainStackActive(const pw_cpu_exception_State& cpu_state);
-
-// Returns whether the psp was active in thread mode.
-inline bool ProcessStackActive(const pw_cpu_exception_State& cpu_state) {
-  return !MainStackActive(cpu_state);
-}
-
-}  // namespace pw::cpu_exception::cortex_m
diff --git a/pw_cpu_exception_cortex_m/public_overrides/pw_cpu_exception_backend/state.h b/pw_cpu_exception_cortex_m/public_overrides/pw_cpu_exception_backend/state.h
deleted file mode 100644
index 6bf27d3..0000000
--- a/pw_cpu_exception_cortex_m/public_overrides/pw_cpu_exception_backend/state.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_cpu_exception_cortex_m/cpu_state.h"
diff --git a/pw_cpu_exception_cortex_m/pw_cpu_exception_cortex_m_private/config.h b/pw_cpu_exception_cortex_m/pw_cpu_exception_cortex_m_private/config.h
deleted file mode 100644
index eb0b7dd..0000000
--- a/pw_cpu_exception_cortex_m/pw_cpu_exception_cortex_m_private/config.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_preprocessor/arch.h"
-
-#if !_PW_ARCH_ARM_CORTEX_M
-#error You can only build this for ARM Cortex-M architectures. If you are \
-       trying to do this and are still seeing this error, see \
-       pw_preprocessor/arch.h
-#endif  // !_PW_ARCH_ARM_CORTEX_M
-
-#if !_PW_ARCH_ARM_V7M && !_PW_ARCH_ARM_V7EM && !_PW_ARCH_ARM_V8M_MAINLINE && \
-    !_PW_ARCH_ARM_V8_1M_MAINLINE
-#error "Your selected ARM Cortex-M arch is not yet supported by this module."
-#endif
-
-// Which log level to use for this module.
-#ifndef PW_CPU_EXCEPTION_CORTEX_M_LOG_LEVEL
-#define PW_CPU_EXCEPTION_CORTEX_M_LOG_LEVEL PW_LOG_LEVEL_DEBUG
-#endif  // PW_CPU_EXCEPTION_CORTEX_M_LOG_LEVEL
-
-// Enables extended logging in pw::cpu_exception::LogCpuState() and
-// pw::cpu_exception::cortex_m::LogExceptionAnalysis() that dumps the active
-// CFSR fields with help strings. This is disabled by default since it
-// increases the binary size by >1.5KB when using plain-text logs, or ~460
-// Bytes when using tokenized logging. It's useful to enable this for device
-// bringup until your application has an end-to-end crash reporting solution.
-#ifndef PW_CPU_EXCEPTION_CORTEX_M_EXTENDED_CFSR_DUMP
-#define PW_CPU_EXCEPTION_CORTEX_M_EXTENDED_CFSR_DUMP 0
-#endif  // PW_CPU_EXCEPTION_CORTEX_M_EXTENDED_CFSR_DUMP
diff --git a/pw_cpu_exception_cortex_m/pw_cpu_exception_cortex_m_private/cortex_m_constants.h b/pw_cpu_exception_cortex_m/pw_cpu_exception_cortex_m_private/cortex_m_constants.h
index a47d672..f384187 100644
--- a/pw_cpu_exception_cortex_m/pw_cpu_exception_cortex_m_private/cortex_m_constants.h
+++ b/pw_cpu_exception_cortex_m/pw_cpu_exception_cortex_m_private/cortex_m_constants.h
@@ -16,9 +16,7 @@
 
 #include <cstdint>
 
-#include "pw_preprocessor/arch.h"
-
-namespace pw::cpu_exception::cortex_m {
+namespace pw::cpu_exception {
 
 // CMSIS/Cortex-M/ARMv7 related constants.
 // These values are from the ARMv7-M Architecture Reference Manual DDI 0403E.b.
@@ -66,24 +64,14 @@
 constexpr uint32_t kCfsrInvstateMask = (kCfsrUsageFaultStart << 1);
 constexpr uint32_t kCfsrInvpcMask = (kCfsrUsageFaultStart << 2);
 constexpr uint32_t kCfsrNocpMask = (kCfsrUsageFaultStart << 3);
-#if _PW_ARCH_ARM_V8M_MAINLINE
 constexpr uint32_t kCfsrStkofMask = (kCfsrUsageFaultStart << 4);
-#endif  // _PW_ARCH_ARM_V8M_MAINLINE
 constexpr uint32_t kCfsrUnalignedMask = (kCfsrUsageFaultStart << 8);
 constexpr uint32_t kCfsrDivbyzeroMask = (kCfsrUsageFaultStart << 9);
 
 // Bit masks for an exception return value. (ARMv7-M Section B1.5.8)
-constexpr uint32_t kExcReturnStackMask = 0x1u << 2;  // 0=MSP, 1=PSP
-constexpr uint32_t kExcReturnModeMask = 0x1u << 3;   // 0=Handler, 1=Thread
+constexpr uint32_t kExcReturnStackMask = 0x1u << 2;
 constexpr uint32_t kExcReturnBasicFrameMask = 0x1u << 4;
 
-// Mask for the IPSR, bits 8:0, of the xPSR register.
-constexpr uint32_t kXpsrIpsrMask = 0b1'1111'1111;
-
-// Bit masks for the control register. (ARMv7-M Section B1.4.4)
-// The SPSEL bit is only valid while in Thread Mode:
-constexpr uint32_t kControlThreadModeStackMask = 0x1u << 1;  // 0=MSP, 1=PSP
-
 // Memory mapped registers. (ARMv7-M Section B3.2.2, Table B3-4)
 // TODO(pwbug/316): Only some of these are supported on ARMv6-M.
 inline volatile uint32_t& cortex_m_cfsr =
@@ -99,4 +87,4 @@
 inline volatile uint32_t& cortex_m_shcsr =
     *reinterpret_cast<volatile uint32_t*>(0xE000ED24u);
 
-}  // namespace pw::cpu_exception::cortex_m
+}  // namespace pw::cpu_exception
diff --git a/pw_cpu_exception_cortex_m/pw_cpu_exception_cortex_m_protos/cpu_state.proto b/pw_cpu_exception_cortex_m/pw_cpu_exception_cortex_m_protos/cpu_state.proto
index ecf685d..ad1a054 100644
--- a/pw_cpu_exception_cortex_m/pw_cpu_exception_cortex_m_protos/cpu_state.proto
+++ b/pw_cpu_exception_cortex_m/pw_cpu_exception_cortex_m_protos/cpu_state.proto
@@ -23,8 +23,6 @@
   optional uint32 psp = 5;
   optional uint32 exc_return = 6;
   optional uint32 cfsr = 7;
-  optional uint32 msplim = 27;
-  optional uint32 psplim = 28;
   optional uint32 mmfar = 8;
   optional uint32 bfar = 9;
   optional uint32 icsr = 10;
@@ -47,13 +45,5 @@
   optional uint32 r11 = 23;
   optional uint32 r12 = 24;
 
-  // Next tag: 29
-}
-
-// This message overlays the pw.snapshot.Snapshot proto. It's valid to encode
-// this message to the same sink that a Snapshot proto is being written to.
-message SnapshotCpuStateOverlay {
-  // TODO(amontanez): Rename this to be Cortex-M arch generic, e.g.:
-  //   optional CpuState cpu_state = 20;
-  optional ArmV7mCpuState armv7m_cpu_state = 20;
+  // Next tag: 27
 }
diff --git a/pw_cpu_exception_cortex_m/py/BUILD.gn b/pw_cpu_exception_cortex_m/py/BUILD.gn
index 9de835e..efa0b85 100644
--- a/pw_cpu_exception_cortex_m/py/BUILD.gn
+++ b/pw_cpu_exception_cortex_m/py/BUILD.gn
@@ -17,11 +17,7 @@
 import("$dir_pw_build/python.gni")
 
 pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
+  setup = [ "setup.py" ]
   sources = [
     "pw_cpu_exception_cortex_m/__init__.py",
     "pw_cpu_exception_cortex_m/cfsr_decoder.py",
@@ -32,7 +28,6 @@
   python_deps = [
     "$dir_pw_cli/py",
     "$dir_pw_protobuf_compiler/py",
-    "$dir_pw_symbolizer/py",
     "..:cpu_state_protos.python",
   ]
   pylintrc = "$dir_pigweed/.pylintrc"
diff --git a/pw_cpu_exception_cortex_m/py/exception_analyzer_test.py b/pw_cpu_exception_cortex_m/py/exception_analyzer_test.py
index e12ee6b..8566c5b 100644
--- a/pw_cpu_exception_cortex_m/py/exception_analyzer_test.py
+++ b/pw_cpu_exception_cortex_m/py/exception_analyzer_test.py
@@ -15,9 +15,19 @@
 """Tests dumped Cortex-M CPU state."""
 
 import unittest
+import os
+
+from pw_protobuf_compiler import python_protos
+from pw_cli import env
 from pw_cpu_exception_cortex_m import exception_analyzer, cortex_m_constants
-from pw_cpu_exception_cortex_m_protos import cpu_state_pb2
-import pw_symbolizer
+
+CPU_STATE_PROTO_PATH = os.path.join(
+    env.pigweed_environment().PW_ROOT,  #pylint: disable=no-member
+    'pw_cpu_exception_cortex_m',
+    'pw_cpu_exception_cortex_m_protos',
+    'cpu_state.proto')
+
+cpu_state_pb2 = python_protos.compile_and_import_file(CPU_STATE_PROTO_PATH)
 
 # pylint: disable=protected-access
 
@@ -164,25 +174,6 @@
         ))
         self.assertEqual(cpu_state_info.dump_registers(), expected_dump)
 
-    def test_symbolization(self):
-        """Ensure certain registers are symbolized."""
-        cpu_state_proto = cpu_state_pb2.ArmV7mCpuState()
-        known_symbols = (
-            pw_symbolizer.Symbol(0x0800A200, 'foo()', 'src/foo.c', 41),
-            pw_symbolizer.Symbol(0x08000004, 'boot_entry()',
-                                 'src/vector_table.c', 5),
-        )
-        symbolizer = pw_symbolizer.FakeSymbolizer(known_symbols)
-        cpu_state_proto.pc = 0x0800A200
-        cpu_state_proto.lr = 0x08000004
-        cpu_state_info = exception_analyzer.CortexMExceptionAnalyzer(
-            cpu_state_proto, symbolizer)
-        expected_dump = '\n'.join((
-            'pc         0x0800a200 foo() (src/foo.c:41)',
-            'lr         0x08000004 boot_entry() (src/vector_table.c:5)',
-        ))
-        self.assertEqual(cpu_state_info.dump_registers(), expected_dump)
-
     def test_dump_no_cfsr(self):
         """Validate basic CPU state dump."""
         cpu_state_proto = cpu_state_pb2.ArmV7mCpuState()
@@ -219,11 +210,7 @@
             'Exception caused by a bus fault at 0xdeadbeef.',
             '',
             'Active Crash Fault Status Register (CFSR) fields:',
-            'PRECISERR   Precise data bus error.',
-            '    A data bus error has occurred, and the PC value stacked for',
-            '    the exception return points to the instruction that caused',
-            '    the fault. When the processor sets this bit to 1, it writes',
-            '    the faulting address to the BFAR',
+            'PRECISERR   Precise bus fault.',
             'BFARVALID   BFAR is valid.',
             '',
             'All registers:',
diff --git a/pw_cpu_exception_cortex_m/py/pw_cpu_exception_cortex_m/__init__.py b/pw_cpu_exception_cortex_m/py/pw_cpu_exception_cortex_m/__init__.py
index 99a4f92..e69de29 100644
--- a/pw_cpu_exception_cortex_m/py/pw_cpu_exception_cortex_m/__init__.py
+++ b/pw_cpu_exception_cortex_m/py/pw_cpu_exception_cortex_m/__init__.py
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Python tooling for Cortex-M CPU state analysis."""
-from pw_cpu_exception_cortex_m.exception_analyzer import (
-    CortexMExceptionAnalyzer, process_snapshot)
diff --git a/pw_cpu_exception_cortex_m/py/pw_cpu_exception_cortex_m/cortex_m_constants.py b/pw_cpu_exception_cortex_m/py/pw_cpu_exception_cortex_m/cortex_m_constants.py
index b44aa50..b623c38 100644
--- a/pw_cpu_exception_cortex_m/py/pw_cpu_exception_cortex_m/cortex_m_constants.py
+++ b/pw_cpu_exception_cortex_m/py/pw_cpu_exception_cortex_m/cortex_m_constants.py
@@ -72,120 +72,46 @@
 
 # TODO(amontanez): We could probably make a whole module on bit field handling
 # in python.
-BitField = collections.namedtuple(
-    'BitField', ['name', 'bit_mask', 'description', 'long_description'])
-
-# Information about faults from:
-# * ARM Cortex-M4 Devices Generic User Guide 4.3.10
-# * ARM Cortex-M33 Devices Generic User Guide 4.2.11
+BitField = collections.namedtuple('BitField',
+                                  ['name', 'bit_mask', 'description'])
 
 PW_CORTEX_M_CFSR_BIT_FIELDS = [
     BitField('IACCVIOL', PW_CORTEX_M_CFSR_IACCVIOL_MASK,
-             'Instruction access violation fault.',
-             ('The processor attempted an instruction fetch from a location',
-              'that does not permit execution. The PC value stacked for the',
-              'exception return points to the faulting instruction.')),
+             'MPU violation on instruction fetch.'),
     BitField('DACCVIOL', PW_CORTEX_M_CFSR_DACCVIOL_MASK,
-             'Data access violation fault.',
-             ('The processor attempted a load or store at a location that',
-              'does not permit the operation. The PC value stacked for the',
-              'exception return points to the faulting instruction. The',
-              'processor has loaded the MMFAR with the address of the',
-              'attempted access.')),
+             'MPU violation on memory read/write.'),
     BitField('MUNSTKERR', PW_CORTEX_M_CFSR_MUNSTKERR_MASK,
-             'MemManage fault on unstacking for a return from exception.',
-             ('Unstack for an exception return has caused one or more access',
-              'violations. This fault is chained to the handler. This means',
-              'that when this bit is 1, the original return stack is still',
-              'present. The processor has not adjusted the SP from the',
-              'failing return, and has not performed a new save. The',
-              'processor has not written a fault address to the MMAR.')),
+             'MPU violation on exception return.'),
     BitField('MSTKERR', PW_CORTEX_M_CFSR_MSTKERR_MASK,
-             'MemManage fault on stacking for exception entry.',
-             ('When this bit is 1, the SP is still adjusted but the values',
-              'in the context area on the stack might be incorrect. The',
-              'processor has not written a fault address to the MMAR.')),
+             'MPU violation on exception entry.'),
     BitField('MLSPERR', PW_CORTEX_M_CFSR_MLSPERR_MASK,
-             'MemManage Fault during FPU lazy state preservation.', ''),
+             'FPU lazy state preservation failed.'),
     BitField('MMARVALID', PW_CORTEX_M_CFSR_MMARVALID_MASK,
-             'MMFAR register is valid.', ''),
+             'MMFAR register is valid.'),
     BitField('IBUSERR', PW_CORTEX_M_CFSR_IBUSERR_MASK,
-             'Instruction bus error.',
-             ('The processor attempted to issue an invalid instruction. It',
-              'detects the instruction bus error on prefetching, but this',
-              'flag is only set to 1 if it attempts to issue the faulting',
-              'instruction. When this bit is set, the processor has not',
-              'written a fault address to the BFAR.')),
+             'Bus fault on instruction fetch.'),
     BitField('PRECISERR', PW_CORTEX_M_CFSR_PRECISERR_MASK,
-             'Precise data bus error.',
-             ('A data bus error has occurred, and the PC value stacked for',
-              'the exception return points to the instruction that caused',
-              'the fault. When the processor sets this bit to 1, it writes',
-              'the faulting address to the BFAR')),
+             'Precise bus fault.'),
     BitField('IMPRECISERR', PW_CORTEX_M_CFSR_IMPRECISERR_MASK,
-             'Imprecise data bus error.',
-             ('A data bus error has occurred, but the return address in the',
-              'stack frame is not related to the instruction that caused the',
-              'error. This is an asynchronous fault. Therefore, if it is',
-              'detected when the priority of the current processes is higher',
-              'than the BusFault priority, the BusFault becomes pending and',
-              'becomes active only when the processor returns from all higher',
-              'priority processes. If a precise fault occurs before the',
-              'processor enters the handler for the imprecise BusFault, the',
-              'handler detects both IMPRECISERR set to 1 and one of the',
-              'precise fault status bits set to 1')),
+             'Imprecise bus fault.'),
     BitField('UNSTKERR', PW_CORTEX_M_CFSR_UNSTKERR_MASK,
-             'BusFault on Unstacking for a return from exception.',
-             ('Unstack for an exception return has caused one or more',
-              'BusFaults. This fault is chained to the handler. This means',
-              'when the processor sets this bit to 1, the original return',
-              'stack is still present. The processor does not adjust the SP',
-              'from the failing return, does not perform a new save, and does',
-              'not write a fault address to the BFAR')),
+             'Hardware failure on context restore.'),
     BitField('STKERR', PW_CORTEX_M_CFSR_STKERR_MASK,
-             'BusFault on Stacking for Exception Entry.',
-             ('Stacking for an exception entry has caused one or more',
-              'BusFaults. When the processor sets this bit to 1, the SP is',
-              'still adjusted but the values in the context area on the stack',
-              'might be incorrect. The processor does not write a fault',
-              'address to the BFAR')),
+             'Hardware failure on context save.'),
     BitField('LSPERR', PW_CORTEX_M_CFSR_LSPERR_MASK,
-             'BusFault during FPU lazy state preservation.', ''),
-    BitField('BFARVALID', PW_CORTEX_M_CFSR_BFARVALID_MASK, 'BFAR is valid.',
-             ''),
+             'FPU lazy state preservation failed.'),
+    BitField('BFARVALID', PW_CORTEX_M_CFSR_BFARVALID_MASK, 'BFAR is valid.'),
     BitField('UNDEFINSTR', PW_CORTEX_M_CFSR_UNDEFINSTR_MASK,
-             'Undefined Instruction UsageFault.',
-             ('The processor has attempted to execute an undefined',
-              'instruction. When this bit is set to 1, the PC value stacked',
-              'for the exception return points to the undefined instruction.',
-              'An undefined instruction is an instruction that the processor',
-              'cannot decode.')),
+             'Encountered invalid instruction.'),
     BitField('INVSTATE', PW_CORTEX_M_CFSR_INVSTATE_MASK,
-             'Invalid State UsageFault.',
-             ('The processor has attempted to execute an instruction that',
-              'makes illegal use of the EPSR. The PC value stacked for the',
-              'exception return points to the instruction that attempt',
-              'illegal use of the EPSR')),
+             ('Attempted to execute an instruction with an invalid Execution '
+              'Program Status Register (EPSR) value.')),
     BitField('INVPC', PW_CORTEX_M_CFSR_INVPC_MASK,
-             'Invalid PC Load UsageFault.',
-             ('The processor has attempted an illegal load of EXC_RETURN',
-              'to the PC, as a result of an invalid context, or an invalid',
-              'EXC_RETURN value. The PC value stacked for the exception',
-              'return points to the instruction that tried to perform the',
-              'illegal load of the PC')),
+             'Program Counter (PC) is not legal.'),
     BitField('NOCP', PW_CORTEX_M_CFSR_NOCP_MASK,
-             'Coprocessor disabled or not present.', ''),
-    BitField('STKOF', PW_CORTEX_M_CFSR_STKOF_MASK, 'Stack overflowed.', ''),
+             'Coprocessor disabled or not present.'),
+    BitField('STKOF', PW_CORTEX_M_CFSR_STKOF_MASK, 'Stack overflowed.'),
     BitField('UNALIGNED', PW_CORTEX_M_CFSR_UNALIGNED_MASK,
-             'Unaligned access UsageFault.',
-             ('The processor has made an unaligned memory access. This fault',
-              'can be enabled or disabled using the UNALIGN_TRP bit in the',
-              'CCR. Unaligned LDM, STM, LDRD, and STRD instructions always',
-              'fault irrespective of the CCR setting.')),
-    BitField('DIVBYZERO', PW_CORTEX_M_CFSR_DIVBYZERO_MASK, 'Divide by zero.',
-             ('The processor has executed an SDIV or UDIV instruction with',
-              'a divisor of 0. The PC value stacked for the exception',
-              'return points to the instruction that performed the divide',
-              'by zero. This fault can be enabled or disabled using the',
-              'DIV_0_TRP bit in the CCR.')),
+             'Unaligned load or store. (This exception can be disabled)'),
+    BitField('DIVBYZERO', PW_CORTEX_M_CFSR_DIVBYZERO_MASK, 'Divide by zero.'),
 ]
diff --git a/pw_cpu_exception_cortex_m/py/pw_cpu_exception_cortex_m/exception_analyzer.py b/pw_cpu_exception_cortex_m/py/pw_cpu_exception_cortex_m/exception_analyzer.py
index dd96af2..c246a80 100644
--- a/pw_cpu_exception_cortex_m/py/pw_cpu_exception_cortex_m/exception_analyzer.py
+++ b/pw_cpu_exception_cortex_m/py/pw_cpu_exception_cortex_m/exception_analyzer.py
@@ -13,27 +13,16 @@
 # the License.
 """Tools to analyze Cortex-M CPU state context captured during an exception."""
 
-from typing import Optional, Tuple
+from typing import Tuple
 
 from pw_cpu_exception_cortex_m import cortex_m_constants
-from pw_cpu_exception_cortex_m_protos import cpu_state_pb2
-import pw_symbolizer
-
-# These registers are symbolized when dumped.
-_SYMBOLIZED_REGISTERS = ('pc', 'lr', 'bfar', 'mmfar', 'msp', 'psp', 'r0', 'r1',
-                         'r2', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', 'r10',
-                         'r11', 'r12')
 
 
 class CortexMExceptionAnalyzer:
     """This class provides helper functions to dump a ArmV7mCpuState proto."""
-    def __init__(self,
-                 cpu_state,
-                 symbolizer: Optional[pw_symbolizer.Symbolizer] = None):
+    def __init__(self, cpu_state):
         self._cpu_state = cpu_state
-        self._symbolizer = symbolizer
-        self._active_cfsr_fields: Optional[Tuple[cortex_m_constants.BitField,
-                                                 ...]] = None
+        self._active_cfsr_fields = None
 
     def active_cfsr_fields(self) -> Tuple[cortex_m_constants.BitField, ...]:
         """Returns a list of BitFields for each active CFSR flag."""
@@ -125,16 +114,11 @@
     def dump_registers(self) -> str:
         """Dumps all captured CPU registers as a multi-line string."""
         registers = []
+        # TODO(amontanez): Do fancier decode of some registers like PC and LR.
         for field in self._cpu_state.DESCRIPTOR.fields:
             if self._cpu_state.HasField(field.name):
                 register_value = getattr(self._cpu_state, field.name)
-                register_str = f'{field.name:<10} 0x{register_value:08x}'
-                if (self._symbolizer is not None
-                        and field.name in _SYMBOLIZED_REGISTERS):
-                    symbol = self._symbolizer.symbolize(register_value)
-                    if symbol.name:
-                        register_str += f' {symbol}'
-                registers.append(register_str)
+                registers.append(f'{field.name:<10} 0x{register_value:08x}')
         return '\n'.join(registers)
 
     def dump_active_active_cfsr_fields(self) -> str:
@@ -142,10 +126,6 @@
         fields = []
         for field in self.active_cfsr_fields():
             fields.append(f'{field.name:<11} {field.description}')
-            if isinstance(field.long_description, tuple):
-                long_desc = '    {}'.format('\n    '.join(
-                    field.long_description))
-                fields.append(long_desc)
         return '\n'.join(fields)
 
     def __str__(self):
@@ -166,20 +146,3 @@
             self.dump_registers(),
         ))
         return '\n'.join(dump)
-
-
-def process_snapshot(
-        serialized_snapshot: bytes,
-        symbolizer: Optional[pw_symbolizer.Symbolizer] = None) -> str:
-    """Returns the stringified result of a SnapshotCpuStateOverlay message run
-    though a CortexMExceptionAnalyzer.
-    """
-    snapshot = cpu_state_pb2.SnapshotCpuStateOverlay()
-    snapshot.ParseFromString(serialized_snapshot)
-
-    if snapshot.HasField('armv7m_cpu_state'):
-        state_analyzer = CortexMExceptionAnalyzer(snapshot.armv7m_cpu_state,
-                                                  symbolizer)
-        return f'{state_analyzer}\n'
-
-    return ''
diff --git a/pw_cpu_exception_cortex_m/py/pw_cpu_exception_cortex_m/py.typed b/pw_cpu_exception_cortex_m/py/pw_cpu_exception_cortex_m/py.typed
deleted file mode 100644
index e69de29..0000000
--- a/pw_cpu_exception_cortex_m/py/pw_cpu_exception_cortex_m/py.typed
+++ /dev/null
diff --git a/pw_cpu_exception_cortex_m/py/pyproject.toml b/pw_cpu_exception_cortex_m/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/pw_cpu_exception_cortex_m/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/pw_cpu_exception_cortex_m/py/setup.cfg b/pw_cpu_exception_cortex_m/py/setup.cfg
deleted file mode 100644
index 178a77a..0000000
--- a/pw_cpu_exception_cortex_m/py/setup.cfg
+++ /dev/null
@@ -1,30 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pw_cpu_exception_cortex_m
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Tools for analyzing dumped ARM Cortex-M CPU exceptions
-
-[options]
-packages = find:
-zip_safe = False
-install_requires =
-    protobuf
-    pw_cli
-    pw_protobuf_compiler
-
-[options.package_data]
-pw_cpu_exception_cortex_m = py.typed
diff --git a/pw_cpu_exception_cortex_m/py/setup.py b/pw_cpu_exception_cortex_m/py/setup.py
index cbbf5d3..9cc3e8e 100644
--- a/pw_cpu_exception_cortex_m/py/setup.py
+++ b/pw_cpu_exception_cortex_m/py/setup.py
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2020 The Pigweed Authors
 #
 # 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
@@ -15,4 +15,18 @@
 
 import setuptools  # type: ignore
 
-setuptools.setup()  # Package definition in setup.cfg
+setuptools.setup(
+    name='pw_cpu_exception_cortex_m',
+    version='0.0.1',
+    author='Pigweed Authors',
+    author_email='pigweed-developers@googlegroups.com',
+    description='Tools for analyzing dumped ARM Cortex-M CPU exceptions',
+    packages=setuptools.find_packages(),
+    package_data={'pw_cpu_exception_cortex_m': ['py.typed']},
+    zip_safe=False,
+    install_requires=[
+        'protobuf',
+        'pw_cli',
+        'pw_protobuf_compiler',
+    ],
+)
diff --git a/pw_cpu_exception_cortex_m/snapshot.cc b/pw_cpu_exception_cortex_m/snapshot.cc
deleted file mode 100644
index f4925ed..0000000
--- a/pw_cpu_exception_cortex_m/snapshot.cc
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 PW_LOG_LEVEL PW_CPU_EXCEPTION_CORTEX_M_LOG_LEVEL
-
-#include "pw_cpu_exception_cortex_m/snapshot.h"
-
-#include "pw_cpu_exception_cortex_m/proto_dump.h"
-#include "pw_cpu_exception_cortex_m/util.h"
-#include "pw_cpu_exception_cortex_m_private/config.h"
-#include "pw_cpu_exception_cortex_m_private/cortex_m_constants.h"
-#include "pw_cpu_exception_cortex_m_protos/cpu_state.pwpb.h"
-#include "pw_log/log.h"
-#include "pw_protobuf/encoder.h"
-#include "pw_status/status.h"
-#include "pw_thread/snapshot.h"
-#include "pw_thread_protos/thread.pwpb.h"
-
-namespace pw::cpu_exception::cortex_m {
-namespace {
-
-constexpr char kMainStackHandlerModeName[] = "Main Stack (Handler Mode)";
-constexpr char kMainStackThreadModeName[] = "Main Stack (Thread Mode)";
-
-Status CaptureMainStack(
-    ProcessorMode mode,
-    uintptr_t stack_low_addr,
-    uintptr_t stack_high_addr,
-    uintptr_t stack_pointer,
-    thread::SnapshotThreadInfo::StreamEncoder& snapshot_encoder,
-    thread::ProcessThreadStackCallback& thread_stack_callback) {
-  thread::Thread::StreamEncoder encoder = snapshot_encoder.GetThreadsEncoder();
-
-  const char* thread_name;
-  thread::ThreadState::Enum thread_state;
-  if (mode == ProcessorMode::kHandlerMode) {
-    thread_name = kMainStackHandlerModeName;
-    PW_LOG_DEBUG("Capturing thread info for Main Stack (Handler Mode)");
-    thread_state = thread::ThreadState::Enum::INTERRUPT_HANDLER;
-    PW_LOG_DEBUG("Thread state: INTERRUPT_HANDLER");
-  } else {  // mode == ProcessorMode::kThreadMode
-    thread_name = kMainStackHandlerModeName;
-    PW_LOG_DEBUG("Capturing thread info for Main Stack (Thread Mode)");
-    thread_state = thread::ThreadState::Enum::RUNNING;
-    PW_LOG_DEBUG("Thread state: RUNNING");
-  }
-  encoder.WriteState(thread_state);
-  encoder.WriteName(std::as_bytes(std::span(std::string_view(thread_name))));
-
-  const thread::StackContext thread_ctx = {
-      .thread_name = thread_name,
-      .stack_low_addr = stack_low_addr,
-      .stack_high_addr = stack_high_addr,
-      .stack_pointer = stack_pointer,
-      .stack_pointer_est_peak = std::nullopt,
-  };
-  return thread::SnapshotStack(thread_ctx, encoder, thread_stack_callback);
-}
-
-}  // namespace
-
-Status SnapshotCpuState(
-    const pw_cpu_exception_State& cpu_state,
-    SnapshotCpuStateOverlay::StreamEncoder& snapshot_encoder) {
-  {
-    ArmV7mCpuState::StreamEncoder cpu_state_encoder =
-        snapshot_encoder.GetArmv7mCpuStateEncoder();
-    DumpCpuStateProto(cpu_state_encoder, cpu_state);
-  }
-  return snapshot_encoder.status();
-}
-
-Status SnapshotMainStackThread(
-    uintptr_t stack_low_addr,
-    uintptr_t stack_high_addr,
-    thread::SnapshotThreadInfo::StreamEncoder& encoder,
-    thread::ProcessThreadStackCallback& thread_stack_callback) {
-  uintptr_t stack_pointer;
-  asm volatile("mrs %0, msp\n" : "=r"(stack_pointer));
-
-  // First check if we're in Handler mode, AKA handling exceptions/interrupts.
-  //
-  // Handler mode vs thread mode can be determined via IPSR, bits 8:0 of xPSR.
-  // In thread mode the value is 0, in handler mode the value is non-zero.
-  uint32_t xpsr;
-  asm volatile("mrs %0, xpsr\n" : "=r"(xpsr));
-  if ((xpsr & kXpsrIpsrMask) != 0) {
-    return CaptureMainStack(ProcessorMode::kHandlerMode,
-                            stack_low_addr,
-                            stack_high_addr,
-                            stack_pointer,
-                            encoder,
-                            thread_stack_callback);
-  }
-
-  // It looks like we're in Thread mode which means we need to check whether
-  // or not we are executing off the main stack currently.
-  //
-  // See ARMv7-M Architecture Reference Manual Section B1.4.4 for the control
-  // register values, in particular the SPSEL bit while in Thread mode which
-  // is 0 while running off the main stack and 1 while running off the proces
-  // stack.
-  uint32_t control;
-  asm volatile("mrs %0, control\n" : "=r"(control));
-  if ((control & kControlThreadModeStackMask) != 0) {
-    return OkStatus();  // Main stack is not currently active.
-  }
-
-  // We're running off the main stack in Thread mode.
-  return CaptureMainStack(ProcessorMode::kThreadMode,
-                          stack_low_addr,
-                          stack_high_addr,
-                          stack_pointer,
-                          encoder,
-                          thread_stack_callback);
-}
-
-Status SnapshotMainStackThread(
-    const pw_cpu_exception_State& cpu_state,
-    uintptr_t stack_low_addr,
-    uintptr_t stack_high_addr,
-    thread::SnapshotThreadInfo::StreamEncoder& encoder,
-    thread::ProcessThreadStackCallback& thread_stack_callback) {
-  if (!MainStackActive(cpu_state)) {
-    return OkStatus();  // Main stack wasn't active, nothing to capture.
-  }
-
-  return CaptureMainStack(ActiveProcessorMode(cpu_state),
-                          stack_low_addr,
-                          stack_high_addr,
-                          cpu_state.extended.msp,
-                          encoder,
-                          thread_stack_callback);
-}
-
-}  // namespace pw::cpu_exception::cortex_m
diff --git a/pw_cpu_exception_cortex_m/support.cc b/pw_cpu_exception_cortex_m/support.cc
deleted file mode 100644
index 719ac5b..0000000
--- a/pw_cpu_exception_cortex_m/support.cc
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright 2019 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_cpu_exception/support.h"
-
-#include <cinttypes>
-#include <cstdint>
-#include <span>
-
-#include "pw_cpu_exception_cortex_m/cpu_state.h"
-#include "pw_cpu_exception_cortex_m/util.h"
-#include "pw_cpu_exception_cortex_m_private/config.h"
-#include "pw_cpu_exception_cortex_m_private/cortex_m_constants.h"
-#include "pw_log/log.h"
-#include "pw_preprocessor/arch.h"
-#include "pw_string/string_builder.h"
-
-namespace pw::cpu_exception {
-
-std::span<const uint8_t> RawFaultingCpuState(
-    const pw_cpu_exception_State& cpu_state) {
-  return std::span(reinterpret_cast<const uint8_t*>(&cpu_state),
-                   sizeof(cpu_state));
-}
-
-// Using this function adds approximately 100 bytes to binary size.
-void ToString(const pw_cpu_exception_State& cpu_state,
-              const std::span<char>& dest) {
-  StringBuilder builder(dest);
-  const cortex_m::ExceptionRegisters& base = cpu_state.base;
-  const cortex_m::ExtraRegisters& extended = cpu_state.extended;
-
-#define _PW_FORMAT_REGISTER(state_section, name) \
-  builder.Format("%s=0x%08" PRIx32 "\n", #name, state_section.name)
-
-  // Other registers.
-  if (base.pc != cortex_m::kUndefinedPcLrOrPsrRegValue) {
-    _PW_FORMAT_REGISTER(base, pc);
-  }
-  if (base.lr != cortex_m::kUndefinedPcLrOrPsrRegValue) {
-    _PW_FORMAT_REGISTER(base, lr);
-  }
-  if (base.psr != cortex_m::kUndefinedPcLrOrPsrRegValue) {
-    _PW_FORMAT_REGISTER(base, psr);
-  }
-  _PW_FORMAT_REGISTER(extended, msp);
-  _PW_FORMAT_REGISTER(extended, psp);
-  _PW_FORMAT_REGISTER(extended, exc_return);
-#if _PW_ARCH_ARM_V8M_MAINLINE
-  _PW_FORMAT_REGISTER(extended, msplim);
-  _PW_FORMAT_REGISTER(extended, psplim);
-#endif  // _PW_ARCH_ARM_V8M_MAINLINE
-  _PW_FORMAT_REGISTER(extended, cfsr);
-  _PW_FORMAT_REGISTER(extended, mmfar);
-  _PW_FORMAT_REGISTER(extended, bfar);
-  _PW_FORMAT_REGISTER(extended, icsr);
-  _PW_FORMAT_REGISTER(extended, hfsr);
-  _PW_FORMAT_REGISTER(extended, shcsr);
-  _PW_FORMAT_REGISTER(extended, control);
-
-  // General purpose registers.
-  _PW_FORMAT_REGISTER(base, r0);
-  _PW_FORMAT_REGISTER(base, r1);
-  _PW_FORMAT_REGISTER(base, r2);
-  _PW_FORMAT_REGISTER(base, r3);
-  _PW_FORMAT_REGISTER(extended, r4);
-  _PW_FORMAT_REGISTER(extended, r5);
-  _PW_FORMAT_REGISTER(extended, r6);
-  _PW_FORMAT_REGISTER(extended, r7);
-  _PW_FORMAT_REGISTER(extended, r8);
-  _PW_FORMAT_REGISTER(extended, r9);
-  _PW_FORMAT_REGISTER(extended, r10);
-  _PW_FORMAT_REGISTER(extended, r11);
-  _PW_FORMAT_REGISTER(base, r12);
-
-#undef _PW_FORMAT_REGISTER
-}
-
-// Using this function adds approximately 100 bytes to binary size.
-void LogCpuState(const pw_cpu_exception_State& cpu_state) {
-  const cortex_m::ExceptionRegisters& base = cpu_state.base;
-  const cortex_m::ExtraRegisters& extended = cpu_state.extended;
-
-  cortex_m::LogExceptionAnalysis(cpu_state);
-
-  PW_LOG_INFO("All captured CPU registers:");
-
-#define _PW_LOG_REGISTER(state_section, name) \
-  PW_LOG_INFO("  %-10s 0x%08" PRIx32, #name, state_section.name)
-
-  // Other registers.
-  if (base.pc != cortex_m::kUndefinedPcLrOrPsrRegValue) {
-    _PW_LOG_REGISTER(base, pc);
-  }
-  if (base.lr != cortex_m::kUndefinedPcLrOrPsrRegValue) {
-    _PW_LOG_REGISTER(base, lr);
-  }
-  if (base.psr != cortex_m::kUndefinedPcLrOrPsrRegValue) {
-    _PW_LOG_REGISTER(base, psr);
-  }
-  _PW_LOG_REGISTER(extended, msp);
-  _PW_LOG_REGISTER(extended, psp);
-  _PW_LOG_REGISTER(extended, exc_return);
-#if _PW_ARCH_ARM_V8M_MAINLINE
-  _PW_LOG_REGISTER(extended, msplim);
-  _PW_LOG_REGISTER(extended, psplim);
-#endif  // _PW_ARCH_ARM_V8M_MAINLINE
-  _PW_LOG_REGISTER(extended, cfsr);
-  _PW_LOG_REGISTER(extended, mmfar);
-  _PW_LOG_REGISTER(extended, bfar);
-  _PW_LOG_REGISTER(extended, icsr);
-  _PW_LOG_REGISTER(extended, hfsr);
-  _PW_LOG_REGISTER(extended, shcsr);
-  _PW_LOG_REGISTER(extended, control);
-
-  // General purpose registers.
-  _PW_LOG_REGISTER(base, r0);
-  _PW_LOG_REGISTER(base, r1);
-  _PW_LOG_REGISTER(base, r2);
-  _PW_LOG_REGISTER(base, r3);
-  _PW_LOG_REGISTER(extended, r4);
-  _PW_LOG_REGISTER(extended, r5);
-  _PW_LOG_REGISTER(extended, r6);
-  _PW_LOG_REGISTER(extended, r7);
-  _PW_LOG_REGISTER(extended, r8);
-  _PW_LOG_REGISTER(extended, r9);
-  _PW_LOG_REGISTER(extended, r10);
-  _PW_LOG_REGISTER(extended, r11);
-  _PW_LOG_REGISTER(base, r12);
-
-#undef _PW_LOG_REGISTER
-}
-
-}  // namespace pw::cpu_exception
diff --git a/pw_cpu_exception_cortex_m/util.cc b/pw_cpu_exception_cortex_m/util.cc
deleted file mode 100644
index fc29252..0000000
--- a/pw_cpu_exception_cortex_m/util.cc
+++ /dev/null
@@ -1,178 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_cpu_exception_cortex_m/util.h"
-
-#include <cinttypes>
-
-#include "pw_cpu_exception_cortex_m/cpu_state.h"
-#include "pw_cpu_exception_cortex_m_private/config.h"
-#include "pw_cpu_exception_cortex_m_private/cortex_m_constants.h"
-#include "pw_log/log.h"
-#include "pw_preprocessor/arch.h"
-
-namespace pw::cpu_exception::cortex_m {
-namespace {
-
-[[maybe_unused]] void LogCfsrAnalysis(const uint32_t cfsr) {
-  if (cfsr == 0) {
-    return;
-  }
-
-  PW_LOG_INFO("Active CFSR fields:");
-
-  // Memory managment fault fields.
-  if (cfsr & kCfsrIaccviolMask) {
-    PW_LOG_ERROR("  IACCVIOL: MPU violation on instruction fetch");
-  }
-  if (cfsr & kCfsrDaccviolMask) {
-    PW_LOG_ERROR("  DACCVIOL: MPU violation on memory read/write");
-  }
-  if (cfsr & kCfsrMunstkerrMask) {
-    PW_LOG_ERROR("  MUNSTKERR: 'MPU violation on exception return");
-  }
-  if (cfsr & kCfsrMstkerrMask) {
-    PW_LOG_ERROR("  MSTKERR: MPU violation on exception entry");
-  }
-  if (cfsr & kCfsrMlsperrMask) {
-    PW_LOG_ERROR("  MLSPERR: MPU violation on lazy FPU state preservation");
-  }
-  if (cfsr & kCfsrMmarvalidMask) {
-    PW_LOG_ERROR("  MMARVALID: MMFAR register is valid");
-  }
-
-  // Bus fault fields.
-  if (cfsr & kCfsrIbuserrMask) {
-    PW_LOG_ERROR("  IBUSERR: Bus fault on instruction fetch");
-  }
-  if (cfsr & kCfsrPreciserrMask) {
-    PW_LOG_ERROR("  PRECISERR: Precise bus fault");
-  }
-  if (cfsr & kCfsrImpreciserrMask) {
-    PW_LOG_ERROR("  IMPRECISERR: Imprecise bus fault");
-  }
-  if (cfsr & kCfsrUnstkerrMask) {
-    PW_LOG_ERROR("  UNSTKERR: Derived bus fault on exception context save");
-  }
-  if (cfsr & kCfsrStkerrMask) {
-    PW_LOG_ERROR("  STKERR: Derived bus fault on exception context restore");
-  }
-  if (cfsr & kCfsrLsperrMask) {
-    PW_LOG_ERROR("  LSPERR: Derived bus fault on lazy FPU state preservation");
-  }
-  if (cfsr & kCfsrBfarvalidMask) {
-    PW_LOG_ERROR("  BFARVALID: BFAR register is valid");
-  }
-
-  // Usage fault fields.
-  if (cfsr & kCfsrUndefinstrMask) {
-    PW_LOG_ERROR("  UNDEFINSTR: Encountered invalid instruction");
-  }
-  if (cfsr & kCfsrInvstateMask) {
-    PW_LOG_ERROR(
-        "  INVSTATE: Attempted to execute an instruction with an invalid "
-        "Execution Program Status Register (EPSR) value");
-  }
-  if (cfsr & kCfsrInvpcMask) {
-    PW_LOG_ERROR("  INVPC: Program Counter (PC) is not legal");
-  }
-  if (cfsr & kCfsrNocpMask) {
-    PW_LOG_ERROR("  NOCP: Coprocessor disabled or not present");
-  }
-  if (cfsr & kCfsrUnalignedMask) {
-    PW_LOG_ERROR("  UNALIGNED: Unaligned memory access");
-  }
-  if (cfsr & kCfsrDivbyzeroMask) {
-    PW_LOG_ERROR("  DIVBYZERO: Division by zero");
-  }
-#if _PW_ARCH_ARM_V8M_MAINLINE
-  if (cfsr & kCfsrStkofMask) {
-    PW_LOG_ERROR("  STKOF: Stack overflowed");
-  }
-#endif  // _PW_ARCH_ARM_V8M_MAINLINE
-}
-
-}  // namespace
-
-void LogExceptionAnalysis(const pw_cpu_exception_State& cpu_state) {
-  // This provides a high-level assessment of the cause of the exception.
-  // These conditionals are ordered by priority to ensure the most critical
-  // issues are highlighted first. These are not mutually exclusive; a bus fault
-  // could occur during the handling of a MPU violation, causing a nested fault.
-  if (cpu_state.extended.hfsr & kHfsrForcedMask) {
-    PW_LOG_CRITICAL("Encountered a nested CPU fault (See active CFSR fields)");
-  }
-#if _PW_ARCH_ARM_V8M_MAINLINE
-  if (cpu_state.extended.cfsr & kCfsrStkofMask) {
-    if (ProcessStackActive(cpu_state)) {
-      PW_LOG_CRITICAL("Encountered process stack overflow (psp)");
-    } else {
-      PW_LOG_CRITICAL("Encountered main stack overflow (msp)");
-    }
-  }
-#endif  // _PW_ARCH_ARM_V8M_MAINLINE
-  if (cpu_state.extended.cfsr & kCfsrMemFaultMask) {
-    if (cpu_state.extended.cfsr & kCfsrMmarvalidMask) {
-      PW_LOG_CRITICAL(
-          "Encountered Memory Protection Unit (MPU) violation at 0x%08" PRIx32,
-          cpu_state.extended.mmfar);
-    } else {
-      PW_LOG_CRITICAL("Encountered Memory Protection Unit (MPU) violation");
-    }
-  }
-  if (cpu_state.extended.cfsr & kCfsrBusFaultMask) {
-    if (cpu_state.extended.cfsr & kCfsrBfarvalidMask) {
-      PW_LOG_CRITICAL("Encountered bus fault at 0x%08" PRIx32,
-                      cpu_state.extended.bfar);
-    } else {
-      PW_LOG_CRITICAL("Encountered bus fault");
-    }
-  }
-  if (cpu_state.extended.cfsr & kCfsrUsageFaultMask) {
-    PW_LOG_CRITICAL("Encountered usage fault (See active CFSR fields)");
-  }
-  if ((cpu_state.extended.icsr & kIcsrVectactiveMask) == kNmiIsrNum) {
-    PW_LOG_INFO("Encountered non-maskable interrupt (NMI)");
-  }
-#if PW_CPU_EXCEPTION_CORTEX_M_EXTENDED_CFSR_DUMP
-  LogCfsrAnalysis(cpu_state.extended.cfsr);
-#endif  // PW_CPU_EXCEPTION_CORTEX_M_EXTENDED_CFSR_DUMP
-}
-
-ProcessorMode ActiveProcessorMode(const pw_cpu_exception_State& cpu_state) {
-  // See ARMv7-M Architecture Reference Manual Section B1.5.8 for the exception
-  // return values, in particular bits 0:3.
-  // Bits 0:3 of EXC_RETURN:
-  // 0b0001 - 0x1 Handler mode Main
-  // 0b1001 - 0x9 Thread mode Main
-  // 0b1101 - 0xD Thread mode Process
-  //   ^
-  if (cpu_state.extended.exc_return & kExcReturnModeMask) {
-    return ProcessorMode::kThreadMode;
-  }
-  return ProcessorMode::kHandlerMode;
-}
-
-bool MainStackActive(const pw_cpu_exception_State& cpu_state) {
-  // See ARMv7-M Architecture Reference Manual Section B1.5.8 for the exception
-  // return values, in particular bits 0:3.
-  // Bits 0:3 of EXC_RETURN:
-  // 0b0001 - 0x1 Handler mode Main
-  // 0b1001 - 0x9 Thread mode Main
-  // 0b1101 - 0xD Thread mode Process
-  //    ^
-  return (cpu_state.extended.exc_return & kExcReturnStackMask) == 0;
-}
-
-}  // namespace pw::cpu_exception::cortex_m
diff --git a/pw_cpu_exception_cortex_m/util_test.cc b/pw_cpu_exception_cortex_m/util_test.cc
deleted file mode 100644
index eec46a7..0000000
--- a/pw_cpu_exception_cortex_m/util_test.cc
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_pw_cpu_exception_cortex_m/util.h"
-
-#include "gtest/gtest.h"
-#include "pw_pw_cpu_exception_cortex_m/cpu_state.h"
-
-namespace pw::pw_cpu_exception::cortex_m {
-namespace {
-
-TEST(ActiveProcessorMode, HandlerModeMain) {
-  pw_cpu_exception_State cpu_state = {};
-  // See ARMv7-M Architecture Reference Manual Section B1.5.8 for the exception
-  // return values, in particular bits 0:3.
-  // Bits 0:3 of EXC_RETURN:
-  // 0b0001 - 0x1 Handler mode Main
-  cpu_state.extended.exc_return = 0b0001;
-  EXPECT_EQ(ActiveProcessorMode(cpu_state), ProcessorMode::HandlerMode);
-}
-
-TEST(ActiveProcessorMode, ThreadModeMain) {
-  pw_cpu_exception_State cpu_state = {};
-  // See ARMv7-M Architecture Reference Manual Section B1.5.8 for the exception
-  // return values, in particular bits 0:3.
-  // Bits 0:3 of EXC_RETURN:
-  // 0b1001 - 0x9 Thread mode Main
-  cpu_state.extended.exc_return = 0b1001;
-  EXPECT_EQ(ActiveProcessorMode(cpu_state), ProcessorMode::ThreadMode);
-}
-
-TEST(ActiveProcessorMode, ThreadModeProcess) {
-  pw_cpu_exception_State cpu_state = {};
-  // See ARMv7-M Architecture Reference Manual Section B1.5.8 for the exception
-  // return values, in particular bits 0:3.
-  // Bits 0:3 of EXC_RETURN:
-  // 0b1101 - 0xD Thread mode Process
-  cpu_state.extended.exc_return = 0b1001;
-  EXPECT_EQ(ActiveProcessorMode(cpu_state), ProcessorMode::ThreadMode);
-}
-
-TEST(MainStackActive, HandlerModeMain) {
-  pw_cpu_exception_State cpu_state = {};
-  // See ARMv7-M Architecture Reference Manual Section B1.5.8 for the exception
-  // return values, in particular bits 0:3.
-  // Bits 0:3 of EXC_RETURN:
-  // 0b0001 - 0x1 Handler mode Main
-  cpu_state.extended.exc_return = 0b0001;
-  EXPECT_TRUE(MainStackActive(cpu_state));
-}
-
-TEST(MainStackActive, ThreadModeMain) {
-  pw_cpu_exception_State cpu_state = {};
-  // See ARMv7-M Architecture Reference Manual Section B1.5.8 for the exception
-  // return values, in particular bits 0:3.
-  // Bits 0:3 of EXC_RETURN:
-  // 0b1001 - 0x9 Thread mode Main
-  cpu_state.extended.exc_return = 0b1001;
-  EXPECT_TRUE(MainStackActive(cpu_state));
-}
-
-TEST(MainStackActive, ThreadModeProcess) {
-  pw_cpu_exception_State cpu_state = {};
-  // See ARMv7-M Architecture Reference Manual Section B1.5.8 for the exception
-  // return values, in particular bits 0:3.
-  // Bits 0:3 of EXC_RETURN:
-  // 0b1101 - 0xD Thread mode Process
-  cpu_state.extended.exc_return = 0b1001;
-  EXPECT_FALSE(MainStackActive(cpu_state));
-}
-
-TEST(ProcessStackActive, HandlerModeMain) {
-  pw_cpu_exception_State cpu_state = {};
-  // See ARMv7-M Architecture Reference Manual Section B1.5.8 for the exception
-  // return values, in particular bits 0:3.
-  // Bits 0:3 of EXC_RETURN:
-  // 0b0001 - 0x1 Handler mode Main
-  cpu_state.extended.exc_return = 0b0001;
-  EXPECT_FALSE(ProcessStackActive(cpu_state));
-}
-
-TEST(ProcessStackActive, ThreadModeMain) {
-  pw_cpu_exception_State cpu_state = {};
-  // See ARMv7-M Architecture Reference Manual Section B1.5.8 for the exception
-  // return values, in particular bits 0:3.
-  // Bits 0:3 of EXC_RETURN:
-  // 0b1001 - 0x9 Thread mode Main
-  cpu_state.extended.exc_return = 0b1001;
-  EXPECT_FALSE(ProcessStackActive(cpu_state));
-}
-
-TEST(ProcessStackActive, ThreadModeProcess) {
-  pw_cpu_exception_State cpu_state = {};
-  // See ARMv7-M Architecture Reference Manual Section B1.5.8 for the exception
-  // return values, in particular bits 0:3.
-  // Bits 0:3 of EXC_RETURN:
-  // 0b1101 - 0xD Thread mode Process
-  cpu_state.extended.exc_return = 0b1001;
-  EXPECT_TRUE(ProcessStackActive(cpu_state));
-}
-
-}  // namespace
-}  // namespace pw::pw_cpu_exception::cortex_m
diff --git a/pw_crypto/BUILD.bazel b/pw_crypto/BUILD.bazel
deleted file mode 100644
index d8fc6f3..0000000
--- a/pw_crypto/BUILD.bazel
+++ /dev/null
@@ -1,131 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_facade",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_facade(
-    name = "sha256_facade",
-    hdrs = [
-        "public/pw_crypto/sha256.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_assert",
-        "//pw_bytes",
-        "//pw_status",
-    ],
-)
-
-pw_cc_library(
-    name = "sha256_mbedtls",
-    srcs = ["sha256_mbedtls.cc"],
-    hdrs = [
-        "public/pw_crypto/sha256_mbedtls.h",
-        "public_overrides/mbedtls/pw_crypto/sha256_backend.h",
-    ],
-    includes = ["public_overrides"],
-    deps = [":sha256_facade"],
-)
-
-pw_cc_library(
-    name = "sha256_boringssl",
-    srcs = ["sha256_boringssl.cc"],
-    hdrs = [
-        "public/pw_crypto/sha256_boringssl.h",
-        "public_overrides/boringssl/pw_crypto/sha256_backend.h",
-    ],
-    includes = ["public_overrides"],
-    deps = [":sha256_facade"],
-)
-
-pw_cc_test(
-    name = "sha256_test",
-    srcs = ["sha256_test.cc"],
-    deps = [
-        ":sha256_facade",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_library(
-    name = "sha256_mock",
-    srcs = ["sha256_mock.cc"],
-    hdrs = [
-        "public/pw_crypto/sha256_mock.h",
-        "public_overrides/mock/pw_crypto/sha256_backend.h",
-    ],
-    includes = ["public_overrides"],
-    deps = [":sha256_facade"],
-)
-
-pw_cc_test(
-    name = "sha256_mock_test",
-    srcs = ["sha256_mock_test.cc"],
-    deps = [
-        ":sha256_facade",
-        ":sha256_mock",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_facade(
-    name = "ecdsa_facade",
-    hdrs = [
-        "public/pw_crypto/ecdsa.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_bytes",
-        "//pw_status",
-    ],
-)
-
-pw_cc_library(
-    name = "ecdsa_mbedtls",
-    srcs = ["ecdsa_mbedtls.cc"],
-    deps = [":ecdsa_facade"],
-)
-
-pw_cc_library(
-    name = "ecdsa_boringssl",
-    srcs = ["ecdsa_boringssl.cc"],
-    deps = [":ecdsa_facade"],
-)
-
-pw_cc_library(
-    name = "ecdsa_uecc",
-    srcs = [
-        "ecdsa_uecc.cc",
-        "micro-ecc/uEDD.c",
-    ],
-    deps = [":ecdsa_facade"],
-)
-
-pw_cc_test(
-    name = "ecdsa_test",
-    srcs = ["ecdsa_test.cc"],
-    deps = [
-        ":ecdsa_facade",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_crypto/BUILD.gn b/pw_crypto/BUILD.gn
deleted file mode 100644
index 38c10c0..0000000
--- a/pw_crypto/BUILD.gn
+++ /dev/null
@@ -1,200 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_bloat/bloat.gni")
-import("$dir_pw_build/facade.gni")
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_crypto/backend.gni")
-import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_unit_test/test.gni")
-
-config("default_config") {
-  include_dirs = [ "public" ]
-  visibility = [ ":*" ]
-}
-
-pw_facade("sha256") {
-  backend = pw_crypto_SHA256_BACKEND
-  public_configs = [ ":default_config" ]
-  public = [ "public/pw_crypto/sha256.h" ]
-  public_deps = [
-    "$dir_pw_bytes",
-    "$dir_pw_log",
-    "$dir_pw_status",
-    "$dir_pw_stream",
-  ]
-  deps = [ "$dir_pw_assert" ]
-}
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-  report_deps = [ ":size_report" ]
-}
-
-pw_size_report("size_report") {
-  title = "pw::crypto Size Report"
-  base = "$dir_pw_bloat:bloat_base"
-
-  binaries = []
-
-  if (pw_crypto_SHA256_BACKEND != "") {
-    binaries += [
-      {
-        target = "size_report:sha256_simple"
-        label = "SHA256 ($pw_crypto_SHA256_BACKEND)"
-      },
-    ]
-  }
-
-  if (pw_crypto_ECDSA_BACKEND != "") {
-    binaries += [
-      {
-        target = "size_report:ecdsa_p256_verify"
-        label = "ECDSA P256 Verify ($pw_crypto_ECDSA_BACKEND)"
-      },
-    ]
-  }
-
-  if (binaries == []) {
-    binaries += [
-      {
-        target = "$dir_pw_bloat:bloat_base"
-        label = "No backend is selected."
-      },
-    ]
-  }
-}
-
-pw_test_group("tests") {
-  tests = [
-    ":sha256_test",
-    ":sha256_mock_test",
-    ":ecdsa_test",
-  ]
-}
-
-# Sha256 tests against the selected real backend.
-pw_test("sha256_test") {
-  enable_if = pw_crypto_SHA256_BACKEND != ""
-  deps = [ ":sha256" ]
-  sources = [ "sha256_test.cc" ]
-}
-
-config("mock_config") {
-  visibility = [ ":*" ]
-  include_dirs = [ "public_overrides/mock" ]
-}
-
-pw_source_set("sha256_mock") {
-  public_configs = [ ":mock_config" ]
-  public = [
-    "public/pw_crypto/sha256_mock.h",
-    "public_overrides/mock/pw_crypto/sha256_backend.h",
-  ]
-  sources = [ "sha256_mock.cc" ]
-  public_deps = [ ":sha256.facade" ]
-}
-
-# Sha256 frontend tests against a mocked backend.
-pw_test("sha256_mock_test") {
-  # Depend on ":sha256.facade" instead of ":sha256" to bypass normal backend
-  # selection via `pw_crypto_SHA256_BACKEND`.
-  deps = [
-    ":sha256.facade",
-    ":sha256_mock",
-  ]
-  sources = [ "sha256_mock_test.cc" ]
-}
-
-config("mbedtls_config") {
-  visibility = [ ":*" ]
-  include_dirs = [ "public_overrides/mbedtls" ]
-}
-
-pw_source_set("sha256_mbedtls") {
-  public_configs = [ ":mbedtls_config" ]
-  public = [
-    "public/pw_crypto/sha256_mbedtls.h",
-    "public_overrides/mbedtls/pw_crypto/sha256_backend.h",
-  ]
-  sources = [ "sha256_mbedtls.cc" ]
-  public_deps = [
-    ":sha256.facade",
-    "$dir_pw_third_party/mbedtls",
-  ]
-}
-
-config("boringssl_config") {
-  visibility = [ ":*" ]
-  include_dirs = [ "public_overrides/boringssl" ]
-}
-
-pw_source_set("sha256_boringssl") {
-  public_configs = [ ":boringssl_config" ]
-  public = [
-    "public/pw_crypto/sha256_boringssl.h",
-    "public_overrides/boringssl/pw_crypto/sha256_backend.h",
-  ]
-  sources = [ "sha256_boringssl.cc" ]
-  public_deps = [
-    ":sha256.facade",
-    "$dir_pw_third_party/boringssl",
-  ]
-}
-
-pw_facade("ecdsa") {
-  backend = pw_crypto_ECDSA_BACKEND
-  public_configs = [ ":default_config" ]
-  public = [ "public/pw_crypto/ecdsa.h" ]
-  public_deps = [
-    "$dir_pw_bytes",
-    "$dir_pw_status",
-  ]
-}
-
-pw_source_set("ecdsa_mbedtls") {
-  sources = [ "ecdsa_mbedtls.cc" ]
-  deps = [
-    "$dir_pw_function",
-    "$dir_pw_log",
-    "$dir_pw_third_party/mbedtls",
-  ]
-  public_deps = [ ":ecdsa.facade" ]
-}
-
-pw_source_set("ecdsa_boringssl") {
-  sources = [ "ecdsa_boringssl.cc" ]
-  deps = [
-    "$dir_pw_log",
-    "$dir_pw_third_party/boringssl",
-  ]
-  public_deps = [ ":ecdsa.facade" ]
-}
-
-pw_source_set("ecdsa_uecc") {
-  sources = [ "ecdsa_uecc.cc" ]
-  deps = [
-    "$dir_pw_log",
-    "$dir_pw_third_party/micro_ecc",
-  ]
-  public_deps = [ ":ecdsa.facade" ]
-}
-
-pw_test("ecdsa_test") {
-  enable_if = pw_crypto_ECDSA_BACKEND != ""
-  deps = [ ":ecdsa" ]
-  sources = [ "ecdsa_test.cc" ]
-}
diff --git a/pw_crypto/OWNERS b/pw_crypto/OWNERS
deleted file mode 100644
index b01d16c..0000000
--- a/pw_crypto/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-alizhang@google.com
diff --git a/pw_crypto/README.md b/pw_crypto/README.md
deleted file mode 100644
index bdbcff3..0000000
--- a/pw_crypto/README.md
+++ /dev/null
@@ -1,8 +0,0 @@
-# pw\_crypto: Safe embedded friendly crypto services
-
-This module is being built with the following services coming soon:
-
-1. Hashing a message with SHA256.
-1. Verifying a digital signature with ECDSA over the NIST P256 curve.
-
-The bulk of the documentation is at https://pigweed.dev/pw_crypto/.
\ No newline at end of file
diff --git a/pw_crypto/backend.gni b/pw_crypto/backend.gni
deleted file mode 100644
index ae934bc..0000000
--- a/pw_crypto/backend.gni
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-declare_args() {
-  pw_crypto_SHA256_BACKEND = ""
-  pw_crypto_ECDSA_BACKEND = ""
-}
diff --git a/pw_crypto/docs.rst b/pw_crypto/docs.rst
deleted file mode 100644
index 6ec17ad..0000000
--- a/pw_crypto/docs.rst
+++ /dev/null
@@ -1,175 +0,0 @@
-.. _module-pw_crypto:
-
-pw_crypto
-=========
-A set of safe (read: easy to use, hard to misuse) crypto APIs.
-
-The following crypto services are provided by this module.
-
-1. Hashing a message with `SHA256`_.
-2. Verifying a digital signature signed with `ECDSA`_ over the NIST P256 curve.
-3. Many more to come ...
-
-SHA256
-------
-
-1. Obtaining a oneshot digest.
-
-.. code-block:: cpp
-
-  #include "pw_crypto/sha256.h"
-
-  std::byte digest[32];
-  if (!pw::crypto::sha256::Hash(message, digest).ok()) {
-    // Handle errors.
-  }
-
-  // The content can also come from a pw::stream::Reader.
-  if (!pw::crypto::sha256::Hash(reader, digest).ok()) {
-    // Handle errors.
-  }
-
-2. Hashing a long, potentially non-contiguous message.
-
-.. code-block:: cpp
-
-  #include "pw_crypto/sha256.h"
-
-  std::byte digest[32];
-
-  if (!pw::crypto::sha256::Sha256()
-      .Update(chunk1).Update(chunk2).Update(chunk...)
-      .Final().ok()) {
-    // Handle errors.
-  }
-
-ECDSA
------
-
-1. Verifying a digital signature signed with ECDSA over the NIST P256 curve.
-
-.. code-block:: cpp
-
-  #include "pw_crypto/sha256.h"
-
-  std::byte digest[32];
-  if (!pw::crypto::sha256::Hash(message, digest).ok()) {
-    // handle errors.
-  }
-
-  if (!pw::crypto::ecdsa::VerifyP256Signature(public_key, digest,
-                                              signature).ok()) {
-    // handle errors.
-  }
-
-2. Verifying a digital signature signed with ECDSA over the NIST P256 curve,
-   with a long and/or non-contiguous message.
-
-.. code-block:: cpp
-
-  #include "pw_crypto/sha256.h"
-
-  std::byte digest[32];
-
-  if (!pw::crypto::sha256::Sha256()
-      .Update(chunk1).Update(chunk2).Update(chunkN)
-      .Final(digest).ok()) {
-      // Handle errors.
-  }
-
-  if (!pw::crypto::ecdsa::VerifyP256Signature(public_key, digest,
-                                              signature).ok()) {
-      // Handle errors.
-  }
-
-Configuration
--------------
-
-The crypto services offered by pw_crypto can be backed by different backend
-crypto libraries.
-
-Mbed TLS
-^^^^^^^^
-
-To select the Mbed TLS backend, the MbedTLS library needs to be installed and
-configured.
-
-.. code-block:: sh
-
-  # Install and configure MbedTLS
-  pw package install mbedtls
-  gn gen out \
-      --args='dir_pw_third_party_mbedtls="//.environment/packages/mbedtls" \
-      pw_crypto_SHA256_BACKEND="//pw_crypto:sha256_mbedtls" \
-      pw_crypto_ECDSA_BACKEND="//pw_crypto:ecdsa_mbedtls"'
-
-  ninja -C out
-
-For optimal code size and/or performance, the Mbed TLS library can be configured
-per product. Mbed TLS configuration is achieved by turning on and off MBEDTLS_*
-options in a config.h file. See //third_party/mbedtls for how this is done.
-
-``pw::crypto::sha256`` does not need any special configuration as it uses the
-mbedtls_sha256_* APIs directly. However you can optionally turn on
-``MBEDTLS_SHA256_SMALLER`` to further reduce the code size to from 3KiB to
-~1.8KiB at a ~30% slowdown cost (Cortex-M4).
-
-.. code-block:: c
-
-   #define MBEDTLS_SHA256_SMALLER
-
-``pw::crypto::ecdsa`` requires the following minimum configurations which yields
-a code size of ~12KiB.
-
-.. code-block:: c
-
-   #define MBEDTLS_BIGNUM_C
-   #define MBEDTLS_ECP_C
-   #define MBEDTLS_ECDSA_C
-   // The ASN1 options are needed only because mbedtls considers and verifies
-   // them (in check_config.h) as dependencies of MBEDTLS_ECDSA_C.
-   #define MBEDTLS_ASN1_WRITE_C
-   #define MBEDTLS_ASN1_PARSE_C
-   #define MBEDTLS_ECP_NO_INTERNAL_RNG
-   #define MBEDTLS_ECP_DP_SECP256R1_ENABLED
-
-BoringSSL
-^^^^^^^^^
-
-To select the BoringSSL backend, the BoringSSL library needs to be installed and
-configured.
-
-.. code-block:: sh
-
-  # Install and configure BoringSSL
-  pw package install boringssl
-  gn gen out \
-      --args='dir_pw_third_party_boringssl="//.environment/packages/boringssl" \
-      pw_crypto_SHA256_BACKEND="//pw_crypto:sha256_boringssl" \
-      pw_crypto_ECDSA_BACKEND="//pw_crypto:ecdsa_boringssl"'
-
-  ninja -C out
-
-BoringSSL does not provide a public configuration interface to reduce the code
-size.
-
-Micro ECC
-^^^^^^^^^
-
-To select Micro ECC, the library needs to be installed and configured.
-
-.. code-block:: sh
-
-  # Install and configure Micro ECC
-  pw package install micro-ecc
-  gn gen out --args='dir_pw_third_party_micro_ecc="//.environment/packages/micro-ecc" pw_crypto_ECDSA_BACKEND="//pw_crypto:ecdsa_uecc"'
-
-Note Micro-ECC does not implement any hashing functions, so you will need to use other backends for SHA256 functionality if needed.
-
-Size Reports
-------------
-
-Below are size reports for each crypto service. These vary across
-configurations.
-
-.. include:: size_report
diff --git a/pw_crypto/ecdsa_boringssl.cc b/pw_crypto/ecdsa_boringssl.cc
deleted file mode 100644
index 4d361e3..0000000
--- a/pw_crypto/ecdsa_boringssl.cc
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 PW_LOG_MODULE_NAME "ECDSA-BSSL"
-#define PW_LOG_LEVEL PW_LOG_LEVEL_WARN
-
-#include "openssl/bn.h"
-#include "openssl/ec.h"
-#include "openssl/ecdsa.h"
-#include "openssl/nid.h"
-#include "pw_crypto/ecdsa.h"
-#include "pw_log/log.h"
-
-namespace pw::crypto::ecdsa {
-
-constexpr size_t kP256CurveOrderBytes = 32;
-
-Status VerifyP256Signature(ConstByteSpan public_key,
-                           ConstByteSpan digest,
-                           ConstByteSpan signature) {
-  const uint8_t* public_key_bytes =
-      reinterpret_cast<const uint8_t*>(public_key.data());
-  const uint8_t* digest_bytes = reinterpret_cast<const uint8_t*>(digest.data());
-  const uint8_t* signature_bytes =
-      reinterpret_cast<const uint8_t*>(signature.data());
-
-  // Allocate objects needed for ECDSA verification. BoringSSL relies on
-  // dynamic allocation.
-  bssl::UniquePtr<EC_GROUP> group(
-      EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
-  if (!group) {
-    return Status::ResourceExhausted();
-  }
-
-  bssl::UniquePtr<EC_POINT> pub_key(EC_POINT_new(group.get()));
-  bssl::UniquePtr<EC_KEY> key(EC_KEY_new());
-  bssl::UniquePtr<ECDSA_SIG> sig(ECDSA_SIG_new());
-  if (!(pub_key && key && sig)) {
-    return Status::ResourceExhausted();
-  }
-
-  // Load the public key.
-  if (!EC_POINT_oct2point(group.get(),
-                          pub_key.get(),
-                          public_key_bytes,
-                          public_key.size(),
-                          nullptr)) {
-    PW_LOG_DEBUG("Bad public key format");
-    return Status::InvalidArgument();
-  }
-
-  if (!EC_KEY_set_group(key.get(), group.get())) {
-    return Status::InvalidArgument();
-  }
-
-  if (!EC_KEY_set_public_key(key.get(), pub_key.get())) {
-    return Status::InvalidArgument();
-  }
-
-  // Load the signature.
-  if (signature.size() != kP256CurveOrderBytes * 2) {
-    PW_LOG_DEBUG("Bad signature format");
-    return Status::InvalidArgument();
-  }
-
-  if (!(BN_bin2bn(signature_bytes, kP256CurveOrderBytes, sig->r) &&
-        BN_bin2bn(signature_bytes + kP256CurveOrderBytes,
-                  kP256CurveOrderBytes,
-                  sig->s))) {
-    return Status::Internal();
-  }
-
-  // Digest must be 32 bytes or longer (and will be truncated).
-  if (digest.size() < kP256CurveOrderBytes) {
-    PW_LOG_DEBUG("Digest is too short");
-    return Status::InvalidArgument();
-  }
-
-  // Verify the signature.
-  if (!ECDSA_do_verify(digest_bytes, digest.size(), sig.get(), key.get())) {
-    PW_LOG_DEBUG("Signature verification failed");
-    return Status::Unauthenticated();
-  }
-
-  return OkStatus();
-}
-
-}  // namespace pw::crypto::ecdsa
diff --git a/pw_crypto/ecdsa_mbedtls.cc b/pw_crypto/ecdsa_mbedtls.cc
deleted file mode 100644
index dd68eac..0000000
--- a/pw_crypto/ecdsa_mbedtls.cc
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 PW_LOG_MODULE_NAME "ECDSA-MTLS"
-#define PW_LOG_LEVEL PW_LOG_LEVEL_WARN
-
-#include "mbedtls/ecdsa.h"
-#include "pw_crypto/ecdsa.h"
-#include "pw_function/function.h"
-#include "pw_log/log.h"
-
-namespace pw::crypto::ecdsa {
-
-namespace {
-
-// Defer calls a given function upon exiting a scope.
-class Defer {
- public:
-  Defer(Function<void()>&& callback) : callback_(std::move(callback)) {}
-  ~Defer() { callback_(); }
-
- private:
-  Function<void()> callback_;
-};
-
-}  // namespace
-
-constexpr size_t kP256CurveOrderBytes = 32;
-
-Status VerifyP256Signature(ConstByteSpan public_key,
-                           ConstByteSpan digest,
-                           ConstByteSpan signature) {
-  // Use a local structure to avoid going over the default inline storage
-  // for the `cleanup` callable used below.
-  struct {
-    // The elliptic curve group.
-    mbedtls_ecp_group grp;
-    // The public key point.
-    mbedtls_ecp_point Q;
-    // The signature (r, s).
-    mbedtls_mpi r, s;
-  } ctx;
-
-  const uint8_t* public_key_data =
-      reinterpret_cast<const uint8_t*>(public_key.data());
-  const uint8_t* digest_data = reinterpret_cast<const uint8_t*>(digest.data());
-  const uint8_t* signature_data =
-      reinterpret_cast<const uint8_t*>(signature.data());
-
-  // These init functions never fail.
-  mbedtls_ecp_group_init(&ctx.grp);
-  mbedtls_ecp_point_init(&ctx.Q);
-  mbedtls_mpi_init(&ctx.r);
-  mbedtls_mpi_init(&ctx.s);
-
-  // Auto clean up on exit.
-  Defer cleanup([&ctx](void) {
-    mbedtls_ecp_group_free(&ctx.grp);
-    mbedtls_ecp_point_free(&ctx.Q);
-    mbedtls_mpi_free(&ctx.r);
-    mbedtls_mpi_free(&ctx.s);
-  });
-
-  // Load the curve parameters.
-  if (mbedtls_ecp_group_load(&ctx.grp, MBEDTLS_ECP_DP_SECP256R1)) {
-    return Status::Internal();
-  }
-
-  // Load the public key.
-  if (mbedtls_ecp_point_read_binary(
-          &ctx.grp, &ctx.Q, public_key_data, public_key.size())) {
-    PW_LOG_DEBUG("Bad public key format");
-    return Status::InvalidArgument();
-  }
-
-  // Load the signature.
-  if (signature.size() != kP256CurveOrderBytes * 2) {
-    PW_LOG_DEBUG("Bad signature format");
-    return Status::InvalidArgument();
-  }
-
-  if (mbedtls_mpi_read_binary(&ctx.r, signature_data, kP256CurveOrderBytes) ||
-      mbedtls_mpi_read_binary(&ctx.s,
-                              signature_data + kP256CurveOrderBytes,
-                              kP256CurveOrderBytes)) {
-    return Status::Internal();
-  }
-
-  // Digest must be 32 bytes or longer (and be truncated).
-  if (digest.size() < kP256CurveOrderBytes) {
-    PW_LOG_DEBUG("Digest is too short");
-    return Status::InvalidArgument();
-  }
-
-  // Verify the signature.
-  if (mbedtls_ecdsa_verify(
-          &ctx.grp, digest_data, digest.size(), &ctx.Q, &ctx.r, &ctx.s)) {
-    PW_LOG_DEBUG("Signature verification failed");
-    return Status::Unauthenticated();
-  }
-
-  return OkStatus();
-}
-
-}  // namespace pw::crypto::ecdsa
diff --git a/pw_crypto/ecdsa_test.cc b/pw_crypto/ecdsa_test.cc
deleted file mode 100644
index 63d1fd0..0000000
--- a/pw_crypto/ecdsa_test.cc
+++ /dev/null
@@ -1,163 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_crypto/ecdsa.h"
-
-#include <cstring>
-
-#include "gtest/gtest.h"
-
-namespace pw::crypto::ecdsa {
-namespace {
-
-#define AS_BYTES(s) std::as_bytes(std::span(s, sizeof(s) - 1))
-
-#define ASSERT_OK(expr) ASSERT_EQ(OkStatus(), expr)
-#define ASSERT_FAIL(expr) ASSERT_NE(OkStatus(), expr)
-
-// TEST_DIGEST/PUBKEY/SIGNATURE are generated using the pkey/ecdsa.c
-// example in Mbed TLS.
-
-// The SHA256 digest of "Hello, Pigweed!", 32 bytes.
-#define TEST_DIGEST                                                  \
-  "\x8D\xCE\x14\xEE\x2C\xD9\xFD\x9B\xBD\x8C\x8D\x57\x68\x50\x2C\x2F" \
-  "\xFB\xB3\x52\x36\xCE\x93\x47\x1B\x80\xFC\xA4\x7D\xB5\xF8\x41\x9D"
-
-// The public key in uncompressed form, 65 bytes.
-#define TEST_PUBKEY                                                  \
-  "\x04"                                                             \
-  "\xD1\x82\x2E\x6A\xD2\x4B\x2A\x80\x2E\x8F\xBC\x03\x00\x95\x11\xF9" \
-  "\x81\x24\xA7\x3C\x45\xC8\xBA\xDD\x5F\x77\x1C\xC3\x71\x8B\xB2\xE9" \
-  "\x3A\x0A\x84\xFF\xEA\x13\xC2\x27\xD2\xCF\x42\x7D\xA5\x95\xD6\x88" \
-  "\xCD\x23\x00\x3F\xF9\xD9\x75\x46\xFF\x58\xE9\xBE\xC3\x74\x13\xB8"
-
-// The ECDSA P256 signature of `DIGEST`.
-#define TEST_SIGNATURE                                               \
-  "\x16\x54\x43\xD4\x00\x07\xC4\xD7\x26\x2E\x3C\xB1\x65\x54\x00\x6A" \
-  "\x6A\x5B\x4A\xBB\x16\x6F\x44\xD0\x91\x3F\xD3\xC2\x50\xAC\x1A\x87" \
-  "\x86\x41\xEE\x56\xDA\x31\xF2\xFF\x38\x3C\xBB\x32\x3E\x2D\xDB\x98" \
-  "\xEA\x05\x9E\x8F\x91\x8E\x0E\x99\xE5\x4F\x32\x13\x92\x7F\x17\x68"
-
-// The public key in uncompressed form, missing the header byte.
-#define MALFORMED_PUBKEY_MISSING_HEADER                              \
-  "\xD1\x82\x2E\x6A\xD2\x4B\x2A\x80\x2E\x8F\xBC\x03\x00\x95\x11\xF9" \
-  "\x81\x24\xA7\x3C\x45\xC8\xBA\xDD\x5F\x77\x1C\xC3\x71\x8B\xB2\xE9" \
-  "\x3A\x0A\x84\xFF\xEA\x13\xC2\x27\xD2\xCF\x42\x7D\xA5\x95\xD6\x88" \
-  "\xCD\x23\x00\x3F\xF9\xD9\x75\x46\xFF\x58\xE9\xBE\xC3\x74\x13\xB8"
-
-// The public key in compressed form, wrong the header byte (03 instead of 02).
-#define MALFORMED_PUBKEY_WRONG_HEADER                                \
-  "\x03"                                                             \
-  "\xD1\x82\x2E\x6A\xD2\x4B\x2A\x80\x2E\x8F\xBC\x03\x00\x95\x11\xF9" \
-  "\x81\x24\xA7\x3C\x45\xC8\xBA\xDD\x5F\x77\x1C\xC3\x71\x8B\xB2\xE9"
-
-// Tampered signature (first bit flipped).
-#define TAMPERED_SIGNATURE                                           \
-  "\x17\x54\x43\xD4\x00\x07\xC4\xD7\x26\x2E\x3C\xB1\x65\x54\x00\x6A" \
-  "\x6A\x5B\x4A\xBB\x16\x6F\x44\xD0\x91\x3F\xD3\xC2\x50\xAC\x1A\x87" \
-  "\x86\x41\xEE\x56\xDA\x31\xF2\xFF\x38\x3C\xBB\x32\x3E\x2D\xDB\x98" \
-  "\xEA\x05\x9E\x8F\x91\x8E\x0E\x99\xE5\x4F\x32\x13\x92\x7F\x17\x68"
-
-// Short signature (last byte removed).
-#define SHORT_SIGNATURE                                              \
-  "\x16\x54\x43\xD4\x00\x07\xC4\xD7\x26\x2E\x3C\xB1\x65\x54\x00\x6A" \
-  "\x6A\x5B\x4A\xBB\x16\x6F\x44\xD0\x91\x3F\xD3\xC2\x50\xAC\x1A\x87" \
-  "\x86\x41\xEE\x56\xDA\x31\xF2\xFF\x38\x3C\xBB\x32\x3E\x2D\xDB\x98" \
-  "\xEA\x05\x9E\x8F\x91\x8E\x0E\x99\xE5\x4F\x32\x13\x92\x7F\x17"
-
-// Short digest (last byte removed)
-#define SHORT_DIGEST                                                 \
-  "\x8D\xCE\x14\xEE\x2C\xD9\xFD\x9B\xBD\x8C\x8D\x57\x68\x50\x2C\x2F" \
-  "\xFB\xB3\x52\x36\xCE\x93\x47\x1B\x80\xFC\xA4\x7D\xB5\xF8\x41"
-
-// Tampered digest (first bit flipped)
-#define TAMPERED_DIGEST                                              \
-  "\x8C\xCE\x14\xEE\x2C\xD9\xFD\x9B\xBD\x8C\x8D\x57\x68\x50\x2C\x2F" \
-  "\xFB\xB3\x52\x36\xCE\x93\x47\x1B\x80\xFC\xA4\x7D\xB5\xF8\x41\x9D"
-
-// Tampered public key (last bit flipped).
-#define TAMPERED_PUBKEY                                              \
-  "\x04"                                                             \
-  "\xD1\x82\x2E\x6A\xD2\x4B\x2A\x80\x2E\x8F\xBC\x03\x00\x95\x11\xF9" \
-  "\x81\x24\xA7\x3C\x45\xC8\xBA\xDD\x5F\x77\x1C\xC3\x71\x8B\xB2\xE9" \
-  "\x3A\x0A\x84\xFF\xEA\x13\xC2\x27\xD2\xCF\x42\x7D\xA5\x95\xD6\x88" \
-  "\xCD\x23\x00\x3F\xF9\xD9\x75\x46\xFF\x58\xE9\xBE\xC3\x74\x13\xB9"
-
-TEST(EcdsaP256, ValidSignature) {
-  ASSERT_OK(VerifyP256Signature(
-      AS_BYTES(TEST_PUBKEY), AS_BYTES(TEST_DIGEST), AS_BYTES(TEST_SIGNATURE)));
-}
-
-TEST(EcdsaP256, LongerDigestGetsTruncated) {
-  ASSERT_OK(VerifyP256Signature(AS_BYTES(TEST_PUBKEY),
-                                AS_BYTES(TEST_DIGEST "extra stuff"),
-                                AS_BYTES(TEST_SIGNATURE)));
-}
-
-TEST(EcdsaP256, MalformedPublicKeyMissingHeader) {
-  ASSERT_EQ(Status::InvalidArgument(),
-            VerifyP256Signature(AS_BYTES(MALFORMED_PUBKEY_MISSING_HEADER),
-                                AS_BYTES(TEST_DIGEST),
-                                AS_BYTES(TEST_SIGNATURE)));
-}
-
-TEST(EcdsaP256, MalformedPublicKeyWrongHeader) {
-  ASSERT_FAIL(VerifyP256Signature(AS_BYTES(MALFORMED_PUBKEY_WRONG_HEADER),
-                                  AS_BYTES(TEST_DIGEST),
-                                  AS_BYTES(TEST_SIGNATURE)));
-}
-
-TEST(EcdsaP256, TamperedSignature) {
-  ASSERT_EQ(Status::Unauthenticated(),
-            VerifyP256Signature(AS_BYTES(TEST_PUBKEY),
-                                AS_BYTES(TEST_DIGEST),
-                                AS_BYTES(TAMPERED_SIGNATURE)));
-}
-
-TEST(EcdsaP256, SignatureTooLong) {
-  ASSERT_EQ(Status::InvalidArgument(),
-            VerifyP256Signature(AS_BYTES(TEST_PUBKEY),
-                                AS_BYTES(TEST_DIGEST),
-                                AS_BYTES(TEST_SIGNATURE "extra stuff")));
-}
-
-TEST(EcdsaP256, SignatureTooShort) {
-  ASSERT_EQ(Status::InvalidArgument(),
-            VerifyP256Signature(AS_BYTES(TEST_PUBKEY),
-                                AS_BYTES(TEST_DIGEST),
-                                AS_BYTES(SHORT_SIGNATURE)));
-}
-
-TEST(EcdsaP256, DigestTooShort) {
-  ASSERT_EQ(Status::InvalidArgument(),
-            VerifyP256Signature(AS_BYTES(TEST_PUBKEY),
-                                AS_BYTES(SHORT_DIGEST),
-                                AS_BYTES(TEST_SIGNATURE)));
-}
-
-TEST(EcdsaP256, TamperedDigest) {
-  ASSERT_EQ(Status::Unauthenticated(),
-            VerifyP256Signature(AS_BYTES(TEST_PUBKEY),
-                                AS_BYTES(TAMPERED_DIGEST),
-                                AS_BYTES(TEST_SIGNATURE)));
-}
-
-TEST(EcdsaP256, TamperedPubkey) {
-  ASSERT_FAIL(VerifyP256Signature(AS_BYTES(TAMPERED_PUBKEY),
-                                  AS_BYTES(TEST_DIGEST),
-                                  AS_BYTES(TEST_SIGNATURE)));
-}
-
-}  // namespace
-}  // namespace pw::crypto::ecdsa
diff --git a/pw_crypto/ecdsa_uecc.cc b/pw_crypto/ecdsa_uecc.cc
deleted file mode 100644
index 937d79d..0000000
--- a/pw_crypto/ecdsa_uecc.cc
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 PW_LOG_MODULE_NAME "ECDSA-UECC"
-#define PW_LOG_LEVEL PW_LOG_LEVEL_WARN
-
-#include "pw_crypto/ecdsa.h"
-#include "pw_log/log.h"
-#include "uECC.h"
-
-namespace pw::crypto::ecdsa {
-
-constexpr size_t kP256CurveOrderBytes = 32;
-
-Status VerifyP256Signature(ConstByteSpan public_key,
-                           ConstByteSpan digest,
-                           ConstByteSpan signature) {
-  const uint8_t* public_key_bytes =
-      reinterpret_cast<const uint8_t*>(public_key.data());
-  const uint8_t* digest_bytes = reinterpret_cast<const uint8_t*>(digest.data());
-  const uint8_t* signature_bytes =
-      reinterpret_cast<const uint8_t*>(signature.data());
-
-  uECC_Curve curve = uECC_secp256r1();
-
-  // Supports SEC 1 uncompressed form (04||X||Y) only.
-  if (public_key.size() != (2 * kP256CurveOrderBytes + 1) ||
-      public_key_bytes[0] != 0x04) {
-    PW_LOG_DEBUG("Bad public key format");
-    return Status::InvalidArgument();
-  }
-
-  // Make sure the public key is on the curve.
-  if (!uECC_valid_public_key(public_key_bytes + 1, curve)) {
-    return Status::InvalidArgument();
-  }
-
-  // Signature expected in raw format (r||s)
-  if (signature.size() != kP256CurveOrderBytes * 2) {
-    PW_LOG_DEBUG("Bad signature format");
-    return Status::InvalidArgument();
-  }
-
-  // Digests must be at least 32 bytes. Digests longer than 32
-  // bytes are truncated to 32 bytes.
-  if (digest.size() < kP256CurveOrderBytes) {
-    PW_LOG_DEBUG("Digest is too short");
-    return Status::InvalidArgument();
-  }
-
-  // Verify the signature.
-  if (!uECC_verify(public_key_bytes + 1,
-                   digest_bytes,
-                   digest.size(),
-                   signature_bytes,
-                   curve)) {
-    PW_LOG_DEBUG("Signature verification failed");
-    return Status::Unauthenticated();
-  }
-
-  return OkStatus();
-}
-
-}  // namespace pw::crypto::ecdsa
diff --git a/pw_crypto/public/pw_crypto/ecdsa.h b/pw_crypto/public/pw_crypto/ecdsa.h
deleted file mode 100644
index 3aa3766..0000000
--- a/pw_crypto/public/pw_crypto/ecdsa.h
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_bytes/span.h"
-#include "pw_status/status.h"
-
-namespace pw::crypto::ecdsa {
-
-// VerifyP256Signature verifies the `signature` of `digest` using `public_key`.
-//
-// `public_key` is a byte string in SEC 1 uncompressed form (0x04||X||Y), which
-// is exactly 65 bytes. Compressed forms (02/03||X) *may* not be supported
-// by some backends, e.g. Mbed TLS.
-//
-// `digest` is a raw byte string, truncated to 32 bytes.
-//
-// `signature` is a raw byte string (r||s) of exactly 64 bytes.
-//
-// Returns Status::OkStatus() for a successful verification, or an error Status
-// otherwise.
-Status VerifyP256Signature(ConstByteSpan public_key,
-                           ConstByteSpan digest,
-                           ConstByteSpan signature);
-
-}  // namespace pw::crypto::ecdsa
diff --git a/pw_crypto/public/pw_crypto/sha256.h b/pw_crypto/public/pw_crypto/sha256.h
deleted file mode 100644
index 1389e28..0000000
--- a/pw_crypto/public/pw_crypto/sha256.h
+++ /dev/null
@@ -1,153 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_bytes/span.h"
-#include "pw_crypto/sha256_backend.h"
-#include "pw_log/log.h"
-#include "pw_status/status.h"
-#include "pw_status/try.h"
-#include "pw_stream/stream.h"
-
-namespace pw::crypto::sha256 {
-
-// Size in bytes of a SHA256 digest.
-constexpr uint32_t kDigestSizeBytes = 32;
-
-// State machine of a hashing session.
-enum class Sha256State {
-  // Initialized and accepting input (via Update()).
-  kReady = 1,
-
-  // Finalized by Final(). Any additional requests, Update() or Final(), will
-  // trigger a transition to kError.
-  kFinalized = 2,
-
-  // In an unrecoverable error state.
-  kError = 3,
-};
-
-namespace backend {
-
-// Primitive operations to be implemented by backends.
-Status DoInit(NativeSha256Context& ctx);
-Status DoUpdate(NativeSha256Context& ctx, ConstByteSpan data);
-Status DoFinal(NativeSha256Context& ctx, ByteSpan out_digest);
-
-}  // namespace backend
-
-// Sha256 computes the SHA256 digest of potentially long, non-contiguous input
-// messages.
-//
-// Usage:
-//
-// if (!Sha256().Update(message).Update(more_message).Final(out_digest).ok()) {
-//   // Error handling.
-// }
-class Sha256 {
- public:
-  Sha256() {
-    if (!backend::DoInit(native_ctx_).ok()) {
-      PW_LOG_DEBUG("backend::DoInit() failed");
-      state_ = Sha256State::kError;
-      return;
-    }
-
-    state_ = Sha256State::kReady;
-  }
-
-  // Update feeds `data` to the running hasher. The feeding can involve zero
-  // or more `Update()` calls and the order matters.
-  Sha256& Update(ConstByteSpan data) {
-    if (state_ != Sha256State::kReady) {
-      PW_LOG_DEBUG("The backend is not ready/initialized");
-      return *this;
-    }
-
-    if (!backend::DoUpdate(native_ctx_, data).ok()) {
-      PW_LOG_DEBUG("backend::DoUpdate() failed");
-      state_ = Sha256State::kError;
-      return *this;
-    }
-
-    return *this;
-  }
-
-  // Final wraps up the hashing session and outputs the final digest in the
-  // first `kDigestSizeBytes` of `out_digest`. `out_digest` must be at least
-  // `kDigestSizeBytes` long.
-  //
-  // Final locks down the Sha256 instance from any additional use.
-  //
-  // Any error, including those occurr inside `Init()` or `Update()` will be
-  // reflected in the return value of Final();
-  Status Final(ByteSpan out_digest) {
-    if (out_digest.size() < kDigestSizeBytes) {
-      PW_LOG_DEBUG("Digest output buffer is too small");
-      state_ = Sha256State::kError;
-      return Status::InvalidArgument();
-    }
-
-    if (state_ != Sha256State::kReady) {
-      PW_LOG_DEBUG("The backend is not ready/initialized");
-      return Status::FailedPrecondition();
-    }
-
-    auto status = backend::DoFinal(native_ctx_, out_digest);
-    if (!status.ok()) {
-      PW_LOG_DEBUG("backend::DoFinal() failed");
-      state_ = Sha256State::kError;
-      return status;
-    }
-
-    state_ = Sha256State::kFinalized;
-    return OkStatus();
-  }
-
- private:
-  // Common hasher state. Tracked by the front-end.
-  Sha256State state_;
-  // Backend-specific context.
-  backend::NativeSha256Context native_ctx_;
-};
-
-// Hash calculates the SHA256 digest of `message` and stores the result
-// in `out_digest`. `out_digest` must be at least `kDigestSizeBytes` long.
-inline Status Hash(ConstByteSpan message, ByteSpan out_digest) {
-  return Sha256().Update(message).Final(out_digest);
-}
-
-inline Status Hash(stream::Reader& reader, ByteSpan out_digest) {
-  if (out_digest.size() < kDigestSizeBytes) {
-    return Status::InvalidArgument();
-  }
-
-  Sha256 sha256;
-  while (true) {
-    Result<ByteSpan> res = reader.Read(out_digest);
-    if (res.status().IsOutOfRange()) {
-      break;
-    }
-
-    PW_TRY(res.status());
-    sha256.Update(res.value());
-  }
-
-  return sha256.Final(out_digest);
-}
-
-}  // namespace pw::crypto::sha256
diff --git a/pw_crypto/public/pw_crypto/sha256_boringssl.h b/pw_crypto/public/pw_crypto/sha256_boringssl.h
deleted file mode 100644
index 50b2ffd..0000000
--- a/pw_crypto/public/pw_crypto/sha256_boringssl.h
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "openssl/sha.h"
-
-namespace pw::crypto::sha256::backend {
-
-typedef SHA256_CTX NativeSha256Context;
-
-}  // namespace pw::crypto::sha256::backend
diff --git a/pw_crypto/public/pw_crypto/sha256_mbedtls.h b/pw_crypto/public/pw_crypto/sha256_mbedtls.h
deleted file mode 100644
index 63c017c..0000000
--- a/pw_crypto/public/pw_crypto/sha256_mbedtls.h
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "mbedtls/sha256.h"
-
-namespace pw::crypto::sha256::backend {
-
-typedef mbedtls_sha256_context NativeSha256Context;
-
-}  // namespace pw::crypto::sha256::backend
diff --git a/pw_crypto/public/pw_crypto/sha256_mock.h b/pw_crypto/public/pw_crypto/sha256_mock.h
deleted file mode 100644
index c9ac8e0..0000000
--- a/pw_crypto/public/pw_crypto/sha256_mock.h
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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
-
-namespace pw::crypto::sha256::backend {
-
-// Not used.
-typedef struct empty {
-} NativeSha256Context;
-
-enum class ErrorKind {
-  kNone = 0,
-  kInit = 1,
-  kUpdate = 2,
-  kFinal = 3,
-};
-
-void InjectError(const ErrorKind err);
-
-inline void ClearError(void) { InjectError(ErrorKind::kNone); }
-
-}  // namespace pw::crypto::sha256::backend
diff --git a/pw_crypto/public_overrides/boringssl/pw_crypto/sha256_backend.h b/pw_crypto/public_overrides/boringssl/pw_crypto/sha256_backend.h
deleted file mode 100644
index 5ba880e..0000000
--- a/pw_crypto/public_overrides/boringssl/pw_crypto/sha256_backend.h
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_crypto/sha256_boringssl.h"
diff --git a/pw_crypto/public_overrides/mbedtls/pw_crypto/sha256_backend.h b/pw_crypto/public_overrides/mbedtls/pw_crypto/sha256_backend.h
deleted file mode 100644
index adff812..0000000
--- a/pw_crypto/public_overrides/mbedtls/pw_crypto/sha256_backend.h
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_crypto/sha256_mbedtls.h"
\ No newline at end of file
diff --git a/pw_crypto/public_overrides/mock/pw_crypto/sha256_backend.h b/pw_crypto/public_overrides/mock/pw_crypto/sha256_backend.h
deleted file mode 100644
index 8afee16..0000000
--- a/pw_crypto/public_overrides/mock/pw_crypto/sha256_backend.h
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_crypto/sha256_mock.h"
diff --git a/pw_crypto/sha256_boringssl.cc b/pw_crypto/sha256_boringssl.cc
deleted file mode 100644
index fb9c783..0000000
--- a/pw_crypto/sha256_boringssl.cc
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 PW_LOG_MODULE_NAME "SHA256-BSSL"
-#define PW_LOG_LEVEL PW_LOG_LEVEL_WARN
-
-#include "pw_crypto/sha256.h"
-#include "pw_status/status.h"
-
-namespace pw::crypto::sha256::backend {
-
-Status DoInit(NativeSha256Context& ctx) {
-  if (!SHA256_Init(&ctx)) {
-    return Status::Internal();
-  }
-  return OkStatus();
-}
-
-Status DoUpdate(NativeSha256Context& ctx, ConstByteSpan data) {
-  if (!SHA256_Update(&ctx,
-                     reinterpret_cast<const unsigned char*>(data.data()),
-                     data.size())) {
-    return Status::Internal();
-  }
-
-  return OkStatus();
-}
-
-Status DoFinal(NativeSha256Context& ctx, ByteSpan out_digest) {
-  if (!SHA256_Final(reinterpret_cast<unsigned char*>(out_digest.data()),
-                    &ctx)) {
-    return Status::Internal();
-  }
-
-  return OkStatus();
-}
-
-}  // namespace pw::crypto::sha256::backend
diff --git a/pw_crypto/sha256_mbedtls.cc b/pw_crypto/sha256_mbedtls.cc
deleted file mode 100644
index 8cb6595..0000000
--- a/pw_crypto/sha256_mbedtls.cc
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 PW_LOG_MODULE_NAME "SHA256-MTLS"
-#define PW_LOG_LEVEL PW_LOG_LEVEL_WARN
-
-#include "pw_crypto/sha256.h"
-#include "pw_status/status.h"
-
-namespace pw::crypto::sha256::backend {
-
-Status DoInit(NativeSha256Context& ctx) {
-  // mbedtsl_sha256_init() never fails (returns void).
-  mbedtls_sha256_init(&ctx);
-
-  if (mbedtls_sha256_starts_ret(&ctx, /* is224 = */ 0)) {
-    return Status::Internal();
-  }
-
-  return OkStatus();
-}
-
-Status DoUpdate(NativeSha256Context& ctx, ConstByteSpan data) {
-  if (mbedtls_sha256_update_ret(
-          &ctx,
-          reinterpret_cast<const unsigned char*>(data.data()),
-          data.size())) {
-    return Status::Internal();
-  }
-
-  return OkStatus();
-}
-
-Status DoFinal(NativeSha256Context& ctx, ByteSpan out_digest) {
-  if (mbedtls_sha256_finish_ret(
-          &ctx, reinterpret_cast<unsigned char*>(out_digest.data()))) {
-    return Status::Internal();
-  }
-
-  return OkStatus();
-}
-
-}  // namespace pw::crypto::sha256::backend
diff --git a/pw_crypto/sha256_mock.cc b/pw_crypto/sha256_mock.cc
deleted file mode 100644
index ed289eb..0000000
--- a/pw_crypto/sha256_mock.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_crypto/sha256.h"
-#include "pw_status/status.h"
-
-namespace pw::crypto::sha256::backend {
-
-namespace {
-ErrorKind g_injected_error = ErrorKind::kNone;
-}  // namespace
-
-void InjectError(const ErrorKind err) { g_injected_error = err; }
-
-Status DoInit(NativeSha256Context&) {
-  return (g_injected_error == ErrorKind::kInit) ? Status::Internal()
-                                                : OkStatus();
-}
-
-Status DoUpdate(NativeSha256Context&, ConstByteSpan) {
-  return (g_injected_error == ErrorKind::kUpdate) ? Status::Internal()
-                                                  : OkStatus();
-}
-
-Status DoFinal(NativeSha256Context&, ByteSpan) {
-  return (g_injected_error == ErrorKind::kFinal) ? Status::Internal()
-                                                 : OkStatus();
-}
-
-}  // namespace pw::crypto::sha256::backend
diff --git a/pw_crypto/sha256_mock_test.cc b/pw_crypto/sha256_mock_test.cc
deleted file mode 100644
index 77c065b..0000000
--- a/pw_crypto/sha256_mock_test.cc
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstring>
-
-#include "gtest/gtest.h"
-#include "pw_crypto/sha256.h"
-#include "pw_crypto/sha256_backend.h"
-
-namespace pw::crypto::sha256 {
-namespace {
-
-#define AS_BYTES(str) std::as_bytes(std::span(str, sizeof(str) - 1))
-
-#define ASSERT_OK(expr) ASSERT_EQ(OkStatus(), expr)
-#define ASSERT_FAIL(expr) ASSERT_NE(OkStatus(), expr)
-
-TEST(Sha256, HandlesBackendInitFailures) {
-  std::byte digest[kDigestSizeBytes];
-
-  backend::ClearError();
-  ASSERT_OK(Sha256().Update(AS_BYTES("blahblah")).Final(digest));
-
-  backend::InjectError(backend::ErrorKind::kInit);
-  ASSERT_FAIL(Sha256().Update(AS_BYTES("blahblah")).Final(digest));
-}
-
-TEST(Sha256, HandlesBackendUpdateFailures) {
-  std::byte digest[kDigestSizeBytes];
-
-  backend::ClearError();
-  ASSERT_OK(Sha256().Update(AS_BYTES("blahblah")).Final(digest));
-
-  backend::InjectError(backend::ErrorKind::kUpdate);
-  ASSERT_FAIL(Sha256().Update(AS_BYTES("blahblah")).Final(digest));
-}
-
-TEST(Sha256, HandlesBackendFinalFailures) {
-  std::byte digest[kDigestSizeBytes];
-
-  backend::ClearError();
-  ASSERT_OK(Sha256().Update(AS_BYTES("blahblah")).Final(digest));
-
-  backend::InjectError(backend::ErrorKind::kFinal);
-  ASSERT_FAIL(Sha256().Update(AS_BYTES("blahblah")).Final(digest));
-}
-
-}  // namespace
-}  // namespace pw::crypto::sha256
diff --git a/pw_crypto/sha256_test.cc b/pw_crypto/sha256_test.cc
deleted file mode 100644
index 32e8566..0000000
--- a/pw_crypto/sha256_test.cc
+++ /dev/null
@@ -1,144 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_crypto/sha256.h"
-
-#include <cstring>
-
-#include "gtest/gtest.h"
-#include "pw_stream/memory_stream.h"
-
-namespace pw::crypto::sha256 {
-namespace {
-
-#define ASSERT_OK(expr) ASSERT_EQ(OkStatus(), expr)
-#define ASSERT_FAIL(expr) ASSERT_NE(OkStatus(), expr)
-
-#define AS_BYTES(s) std::as_bytes(std::span(s, sizeof(s) - 1))
-
-// Generated in Python 3 with:
-// `hashlib.sha256('Hello, Pigweed!'.encode('ascii')).hexdigest()`.
-#define SHA256_HASH_OF_HELLO_PIGWEED                                 \
-  "\x8d\xce\x14\xee\x2c\xd9\xfd\x9b\xbd\x8c\x8d\x57\x68\x50\x2c\x2f" \
-  "\xfb\xb3\x52\x36\xce\x93\x47\x1b\x80\xfc\xa4\x7d\xb5\xf8\x41\x9d"
-
-// Generated in Python with `hashlib.sha256().hexdigest()`.
-#define SHA256_HASH_OF_EMPTY_STRING                                  \
-  "\xe3\xb0\xc4\x42\x98\xfc\x1c\x14\x9a\xfb\xf4\xc8\x99\x6f\xb9\x24" \
-  "\x27\xae\x41\xe4\x64\x9b\x93\x4c\xa4\x95\x99\x1b\x78\x52\xb8\x55"
-
-TEST(Hash, ComputesCorrectDigest) {
-  std::byte digest[kDigestSizeBytes];
-
-  ASSERT_OK(Hash(AS_BYTES("Hello, Pigweed!"), digest));
-  ASSERT_EQ(0,
-            std::memcmp(digest, SHA256_HASH_OF_HELLO_PIGWEED, sizeof(digest)));
-}
-
-TEST(Hash, ComputesCorrectDigestFromReader) {
-  std::byte digest[kDigestSizeBytes];
-  ConstByteSpan message = AS_BYTES("Hello, Pigweed!");
-
-  stream::MemoryReader reader(message);
-  ASSERT_OK(Hash(reader, digest));
-  ASSERT_EQ(0,
-            std::memcmp(digest, SHA256_HASH_OF_HELLO_PIGWEED, sizeof(digest)));
-}
-
-TEST(Hash, ComputesCorrectDigestOnEmptyMessage) {
-  std::byte digest[kDigestSizeBytes];
-
-  ASSERT_OK(Hash({}, digest));
-  ASSERT_EQ(0,
-            std::memcmp(digest, SHA256_HASH_OF_EMPTY_STRING, sizeof(digest)));
-}
-
-TEST(Hash, ComputesCorrectDigestOnEmptyMessageFromReader) {
-  std::byte digest[kDigestSizeBytes];
-
-  ConstByteSpan empty;
-  stream::MemoryReader reader(empty);
-  ASSERT_OK(Hash(reader, digest));
-  ASSERT_EQ(0,
-            std::memcmp(digest, SHA256_HASH_OF_EMPTY_STRING, sizeof(digest)));
-}
-
-TEST(Hash, DigestBufferTooSmall) {
-  std::array<std::byte, 31> digest = {};
-  ASSERT_FAIL(Hash({}, digest));
-}
-
-TEST(Hash, DigestBufferTooSmallForReaderBasedAPI) {
-  std::array<std::byte, 31> digest = {};
-  ConstByteSpan empty;
-  stream::MemoryReader reader(empty);
-  ASSERT_FAIL(Hash(reader, digest));
-}
-
-TEST(Hash, AcceptsLargerDigestBuffer) {
-  std::array<std::byte, 33> digest = {};
-  ASSERT_OK(Hash({}, digest));
-}
-
-TEST(Hash, AcceptsLargerDigestBufferForReaderBasedAPI) {
-  std::array<std::byte, 33> digest = {};
-
-  ConstByteSpan empty;
-  stream::MemoryReader reader(empty);
-  ASSERT_OK(Hash(reader, digest));
-}
-
-TEST(Sha256, AllowsSkippedUpdate) {
-  std::byte digest[kDigestSizeBytes];
-
-  ASSERT_OK(Sha256().Final(digest));
-  ASSERT_EQ(0,
-            std::memcmp(digest, SHA256_HASH_OF_EMPTY_STRING, sizeof(digest)));
-}
-
-TEST(Sha256, AllowsEmptyUpdate) {
-  std::byte digest[kDigestSizeBytes];
-  ASSERT_OK(Sha256().Update({}).Final(digest));
-  ASSERT_EQ(0,
-            std::memcmp(digest, SHA256_HASH_OF_EMPTY_STRING, sizeof(digest)));
-}
-
-TEST(Sha256, AllowsMultipleUpdates) {
-  std::byte digest[kDigestSizeBytes];
-  ASSERT_OK(Sha256()
-                .Update(AS_BYTES("Hello, "))
-                .Update(AS_BYTES("Pigweed!"))
-                .Final(digest));
-  ASSERT_EQ(0,
-            std::memcmp(digest, SHA256_HASH_OF_HELLO_PIGWEED, sizeof(digest)));
-}
-
-TEST(Sha256, NoFinalAfterFinal) {
-  std::byte digest[kDigestSizeBytes];
-  auto h = Sha256();
-
-  ASSERT_OK(h.Final(digest));
-  ASSERT_FAIL(h.Final(digest));
-}
-
-TEST(Sha256, NoUpdateAfterFinal) {
-  std::byte digest[kDigestSizeBytes];
-  auto h = Sha256();
-
-  ASSERT_OK(h.Final(digest));
-  ASSERT_FAIL(h.Update(AS_BYTES("blah")).Final(digest));
-}
-
-}  // namespace
-}  // namespace pw::crypto::sha256
diff --git a/pw_crypto/size_report/BUILD.bazel b/pw_crypto/size_report/BUILD.bazel
deleted file mode 100644
index 1166d1e..0000000
--- a/pw_crypto/size_report/BUILD.bazel
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_binary",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_binary(
-    name = "sha256_simple",
-    srcs = ["sha256_simple.cc"],
-)
-
-pw_cc_binary(
-    name = "ecdsa_p256_verify",
-    srcs = ["ecdsa_p256_verify.cc"],
-)
diff --git a/pw_crypto/size_report/BUILD.gn b/pw_crypto/size_report/BUILD.gn
deleted file mode 100644
index d19bacb..0000000
--- a/pw_crypto/size_report/BUILD.gn
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-pw_executable("sha256_simple") {
-  sources = [ "sha256_simple.cc" ]
-  deps = [
-    "$dir_pw_bloat:bloat_this_binary",
-    "..:sha256",
-  ]
-}
-
-pw_executable("ecdsa_p256_verify") {
-  sources = [ "ecdsa_p256_verify.cc" ]
-  deps = [
-    "$dir_pw_bloat:bloat_this_binary",
-    "..:ecdsa",
-  ]
-}
diff --git a/pw_crypto/size_report/ecdsa_p256_verify.cc b/pw_crypto/size_report/ecdsa_p256_verify.cc
deleted file mode 100644
index f50e01b..0000000
--- a/pw_crypto/size_report/ecdsa_p256_verify.cc
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstring>
-
-#include "pw_bloat/bloat_this_binary.h"
-#include "pw_crypto/ecdsa.h"
-
-namespace {
-#define STR_TO_BYTES(s) std::as_bytes(std::span(s, sizeof(s) - 1))
-
-// The SHA256 digest of "Hello, Pigweed!", 32 bytes.
-#define DIGEST                                                       \
-  "\x8D\xCE\x14\xEE\x2C\xD9\xFD\x9B\xBD\x8C\x8D\x57\x68\x50\x2C\x2F" \
-  "\xFB\xB3\x52\x36\xCE\x93\x47\x1B\x80\xFC\xA4\x7D\xB5\xF8\x41\x9D"
-
-// The public key in uncompressed form, 65 bytes.
-#define PUBKEY                                                       \
-  "\x04"                                                             \
-  "\xD1\x82\x2E\x6A\xD2\x4B\x2A\x80\x2E\x8F\xBC\x03\x00\x95\x11\xF9" \
-  "\x81\x24\xA7\x3C\x45\xC8\xBA\xDD\x5F\x77\x1C\xC3\x71\x8B\xB2\xE9" \
-  "\x3A\x0A\x84\xFF\xEA\x13\xC2\x27\xD2\xCF\x42\x7D\xA5\x95\xD6\x88" \
-  "\xCD\x23\x00\x3F\xF9\xD9\x75\x46\xFF\x58\xE9\xBE\xC3\x74\x13\xB8"
-
-// The ECDSA P256 signature of `DIGEST`.
-#define SIGNATURE                                                    \
-  "\x16\x54\x43\xD4\x00\x07\xC4\xD7\x26\x2E\x3C\xB1\x65\x54\x00\x6A" \
-  "\x6A\x5B\x4A\xBB\x16\x6F\x44\xD0\x91\x3F\xD3\xC2\x50\xAC\x1A\x87" \
-  "\x86\x41\xEE\x56\xDA\x31\xF2\xFF\x38\x3C\xBB\x32\x3E\x2D\xDB\x98" \
-  "\xEA\x05\x9E\x8F\x91\x8E\x0E\x99\xE5\x4F\x32\x13\x92\x7F\x17\x68"
-}  // namespace
-
-int main() {
-  pw::bloat::BloatThisBinary();
-
-  pw::crypto::ecdsa::VerifyP256Signature(
-      STR_TO_BYTES(PUBKEY), STR_TO_BYTES(DIGEST), STR_TO_BYTES(SIGNATURE));
-  return 0;
-}
diff --git a/pw_crypto/size_report/sha256_simple.cc b/pw_crypto/size_report/sha256_simple.cc
deleted file mode 100644
index c8d6cec..0000000
--- a/pw_crypto/size_report/sha256_simple.cc
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstring>
-
-#include "pw_bloat/bloat_this_binary.h"
-#include "pw_crypto/sha256.h"
-
-namespace {
-#define MESSAGE "Hello, Pigweed!"
-#define STR_TO_BYTES(s) std::as_bytes(std::span(s, std::strlen(s)))
-}  // namespace
-
-int main() {
-  pw::bloat::BloatThisBinary();
-
-  std::byte digest[32];
-
-  return pw::crypto::sha256::Hash(STR_TO_BYTES(MESSAGE), digest).ok() ? 0 : 1;
-}
diff --git a/pw_docgen/BUILD b/pw_docgen/BUILD
new file mode 100644
index 0000000..85a5ca0
--- /dev/null
+++ b/pw_docgen/BUILD
@@ -0,0 +1,17 @@
+# Copyright 2019 The Pigweed Authors
+#
+# 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
+#
+#     https://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(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
diff --git a/pw_docgen/BUILD.bazel b/pw_docgen/BUILD.bazel
deleted file mode 100644
index 7bb1810..0000000
--- a/pw_docgen/BUILD.bazel
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright 2019 The Pigweed Authors
-#
-# 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
-#
-#     https://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(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
diff --git a/pw_docgen/OWNERS b/pw_docgen/OWNERS
deleted file mode 100644
index 3afb926..0000000
--- a/pw_docgen/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-frolv@google.com
diff --git a/pw_docgen/docs.gni b/pw_docgen/docs.gni
index b0ae93a..50bf8d6 100644
--- a/pw_docgen/docs.gni
+++ b/pw_docgen/docs.gni
@@ -20,9 +20,6 @@
 declare_args() {
   # Whether or not the current target should build docs.
   pw_docgen_BUILD_DOCS = false
-
-  # Set to enable Google Analytics tracking of generated docs.
-  pw_docs_google_analytics_id = ""
 }
 
 # Defines a group of documentation files and assets.
@@ -100,27 +97,18 @@
     "--gn-gen-root",
     rebase_path(root_gen_dir, root_build_dir) + "/",
     "--sphinx-build-dir",
-    rebase_path("$target_gen_dir/pw_docgen_tree", root_build_dir),
+    rebase_path("$target_gen_dir/pw_docgen_tree"),
     "--conf",
-    rebase_path(invoker.conf, root_build_dir),
+    rebase_path(invoker.conf),
     "--out-dir",
-    rebase_path(invoker.output_directory, root_build_dir),
+    rebase_path(invoker.output_directory),
+    "--metadata",
   ]
 
-  # Enable Google Analytics if a measurement ID is provided
-  if (pw_docs_google_analytics_id != "") {
-    _script_args += [
-      "--google-analytics-id",
-      pw_docs_google_analytics_id,
-    ]
-  }
-
   # Metadata JSON file path.
-  _script_args += [ "--metadata" ]
-  _script_args +=
-      rebase_path(get_target_outputs(":$_metadata_file_target"), root_build_dir)
+  _script_args += rebase_path(get_target_outputs(":$_metadata_file_target"))
 
-  _script_args += rebase_path(invoker.sources, root_build_dir)
+  _script_args += rebase_path(invoker.sources)
 
   if (pw_docgen_BUILD_DOCS) {
     pw_python_action(target_name) {
diff --git a/pw_docgen/docs.rst b/pw_docgen/docs.rst
index c722d0a..041893e 100644
--- a/pw_docgen/docs.rst
+++ b/pw_docgen/docs.rst
@@ -20,7 +20,7 @@
 Any time the code is changed, documentation will be regenerated with the updated
 reports.
 
-Documentation Overview
+Documentation overview
 ======================
 Each Pigweed module provides documentation describing its functionality, use
 cases, and programming API.
@@ -29,34 +29,28 @@
 the module's size cost and performance benchmarks. These allow prospective users
 to evaluate the impact of including the module in their projects.
 
-Build Integration
+Build integration
 =================
 
 Pigweed documentation files are written in `reStructuredText`_ format and
 rendered to HTML using `Sphinx`_ through Pigweed's GN build system.
 
 .. _reStructuredText: http://docutils.sourceforge.net/rst.html
-.. inclusive-language: ignore
 .. _Sphinx: http://www.sphinx-doc.org/en/master
 
-There are additonal Sphinx plugins used for rendering diagrams within
-reStructuredText files including:
-
-* `mermaid <https://mermaid-js.github.io/>`_ via the `sphinxcontrib-mermaid
-  <https://pypi.org/project/sphinxcontrib-mermaid/>`_ package.
-
 Documentation source and asset files are placed alongside code within a module
-and registered as a ``pw_doc_group`` target within a ``BUILD.gn`` file. These
+and registered as a ``pw_docgen_group`` target within a ``BUILD.gn`` file. These
 groups become available for import within a special documentation generation
 target, which accumulates all of them and renders the resulting HTML. This
 system can either be used directly within Pigweed, or integrated into a
 downstream project.
 
-GN Templates
+GN templates
 ------------
 
 pw_doc_group
 ____________
+
 The main template for defining documentation files is ``pw_doc_group``. It is
 used to logically group a collection of documentation source files and assets.
 Each Pigweed module is expected to provide at least one ``pw_doc_group`` target
@@ -85,6 +79,7 @@
 
 pw_doc_gen
 __________
+
 The ``pw_doc_gen`` template creates a target which renders complete HTML
 documentation for a project. It depends on registered ``pw_doc_group`` targets
 and creates an action which collects and renders them.
@@ -114,8 +109,9 @@
     ]
   }
 
-Generating Documentation
+Generating documentation
 ------------------------
+
 All source files listed under a ``pw_doc_gen`` target and its ``pw_doc_group``
 dependencies get copied out into a directory structure mirroring the original
 layout of the modules in which the sources appear. This is demonstrated below
@@ -162,20 +158,3 @@
 identical to the project's directory structure. The only special case is the
 top-level ``index.rst`` file's imports; they must start from the project's build
 root.
-
-Sphinx Extensions
-=================
-This module houses Pigweed-specific extensions for the Sphinx documentation
-generator. Extensions are included and configured in ``docs/conf.py``.
-
-google_analytics
-----------------
-When this extension is included and a ``google_analytics_id`` is set in the
-Sphinx configuration, a Google Analytics tracking tag will be added to each
-page of the documentation when it is rendered to HTML.
-
-By default, the Sphinx configuration's ``google_analytics_id`` is set
-automatically based on the value of the GN argument
-``pw_docs_google_analytics_id``, allowing you to control whether tracking is
-enabled or not in your build configuration. Typically, you would only enable
-this for documentation builds intended for deployment on the web.
diff --git a/pw_docgen/py/BUILD.gn b/pw_docgen/py/BUILD.gn
index dc23ed4..1397437 100644
--- a/pw_docgen/py/BUILD.gn
+++ b/pw_docgen/py/BUILD.gn
@@ -17,16 +17,10 @@
 import("$dir_pw_build/python.gni")
 
 pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
+  setup = [ "setup.py" ]
   sources = [
     "pw_docgen/__init__.py",
     "pw_docgen/docgen.py",
-    "pw_docgen/sphinx/__init__.py",
-    "pw_docgen/sphinx/google_analytics.py",
   ]
   pylintrc = "$dir_pigweed/.pylintrc"
 }
diff --git a/pw_docgen/py/pw_docgen/docgen.py b/pw_docgen/py/pw_docgen/docgen.py
index 7b65e77..e130931 100644
--- a/pw_docgen/py/pw_docgen/docgen.py
+++ b/pw_docgen/py/pw_docgen/docgen.py
@@ -25,7 +25,7 @@
 import sys
 
 from pathlib import Path
-from typing import Dict, List, Optional, Tuple
+from typing import Dict, List, Tuple
 
 SCRIPT_HEADER: str = '''
 ██████╗ ██╗ ██████╗ ██╗    ██╗███████╗███████╗██████╗     ██████╗  ██████╗  ██████╗███████╗
@@ -63,29 +63,27 @@
                         required=True,
                         type=argparse.FileType('r'),
                         help='Metadata JSON file')
-    parser.add_argument('--google-analytics-id',
-                        const=None,
-                        help='Enables Google Analytics with the provided ID')
     return parser.parse_args()
 
 
-def build_docs(src_dir: str,
-               dst_dir: str,
-               google_analytics_id: Optional[str] = None) -> int:
+def build_docs(src_dir: str, dst_dir: str) -> int:
     """Runs Sphinx to render HTML documentation from a doc tree."""
 
     # TODO(frolv): Specify the Sphinx script from a prebuilts path instead of
     # requiring it in the tree.
-    command = ['sphinx-build', '-W', '-b', 'html', '-d', f'{dst_dir}/help']
-
-    if google_analytics_id is not None:
-        command.append(f'-Dgoogle_analytics_id={google_analytics_id}')
-
-    command.extend([src_dir, f'{dst_dir}/html'])
-
+    command = [
+        'sphinx-build', '-W', '-b', 'html', '-d', f'{dst_dir}/help', src_dir,
+        f'{dst_dir}/html'
+    ]
     return subprocess.call(command)
 
 
+def mkdir(dirname: str, exist_ok: bool = False) -> None:
+    """Wrapper around os.makedirs that prints the operation."""
+    print(f'MKDIR {dirname}')
+    os.makedirs(dirname, exist_ok=exist_ok)
+
+
 def copy_doc_tree(args: argparse.Namespace) -> None:
     """Copies doc source and input files into a build tree."""
     def build_path(path):
@@ -100,7 +98,7 @@
     source_files = json.load(args.metadata)
     copy_paths = [build_path(f) for f in source_files]
 
-    os.makedirs(args.sphinx_build_dir)
+    mkdir(args.sphinx_build_dir)
     for source_path in args.sources:
         os.link(source_path,
                 f'{args.sphinx_build_dir}/{Path(source_path).name}')
@@ -114,7 +112,7 @@
         dirs[dirname].append((source_file, copy_path))
 
     for directory, file_pairs in dirs.items():
-        os.makedirs(directory, exist_ok=True)
+        mkdir(directory, exist_ok=True)
         for src, dst in file_pairs:
             os.link(src, dst)
 
@@ -136,8 +134,7 @@
     # Flush all script output before running Sphinx.
     print('-' * 80, flush=True)
 
-    return build_docs(args.sphinx_build_dir, args.out_dir,
-                      args.google_analytics_id)
+    return build_docs(args.sphinx_build_dir, args.out_dir)
 
 
 if __name__ == '__main__':
diff --git a/pw_docgen/py/pw_docgen/sphinx/__init__.py b/pw_docgen/py/pw_docgen/sphinx/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/pw_docgen/py/pw_docgen/sphinx/__init__.py
+++ /dev/null
diff --git a/pw_docgen/py/pw_docgen/sphinx/google_analytics.py b/pw_docgen/py/pw_docgen/sphinx/google_analytics.py
deleted file mode 100644
index 3816fa2..0000000
--- a/pw_docgen/py/pw_docgen/sphinx/google_analytics.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""A Sphinx extension to add a Google Analytics tag to generated docs"""
-
-
-def add_google_analytics_tag(app, pagename, templatename, context, doctree):  # pylint: disable=unused-argument
-    if app.config.google_analytics_id is None:
-        return
-
-    if 'metatags' not in context:
-        context['metatags'] = ''
-
-    # pylint: disable=line-too-long
-    context['metatags'] += (
-        f"""<script async src="https://www.googletagmanager.com/gtag/js?id={app.config.google_analytics_id}"></script>
-<script>
-  window.dataLayer = window.dataLayer || [];
-  function gtag(){{dataLayer.push(arguments);}}
-  gtag('js', new Date());
-
-  gtag('config', '{app.config.google_analytics_id}');
-</script>""")
-
-
-def setup(app):
-    app.add_config_value('google_analytics_id', None, 'html')
-    app.connect('html-page-context', add_google_analytics_tag)
-    return {'parallel_read_safe': True}
diff --git a/pw_docgen/py/pyproject.toml b/pw_docgen/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/pw_docgen/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/pw_docgen/py/setup.cfg b/pw_docgen/py/setup.cfg
deleted file mode 100644
index 62d65c3..0000000
--- a/pw_docgen/py/setup.cfg
+++ /dev/null
@@ -1,30 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pw_docgen
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Generate Sphinx documentation
-
-[options]
-packages = find:
-zip_safe = False
-install_requires =
-    sphinx >3
-    sphinx-rtd-theme
-    sphinxcontrib-mermaid >=0.7.1
-
-[options.package_data]
-pw_docgen = py.typed
diff --git a/pw_docgen/py/setup.py b/pw_docgen/py/setup.py
index 0230048..e14a315 100644
--- a/pw_docgen/py/setup.py
+++ b/pw_docgen/py/setup.py
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2020 The Pigweed Authors
 #
 # 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
@@ -15,4 +15,24 @@
 
 import setuptools  # type: ignore
 
-setuptools.setup()  # Package definition in setup.cfg
+setuptools.setup(
+    name='pw_docgen',
+    version='0.0.1',
+    author='Pigweed Authors',
+    author_email='pigweed-developers@googlegroups.com',
+    description='Generate Sphinx documentation',
+    packages=setuptools.find_packages(),
+    package_data={'pw_docgen': ['py.typed']},
+    zip_safe=False,
+    install_requires=[
+        'sphinx>=2, <3',  # Restrict version for compatibility with m2r plugin
+        'sphinx-rtd-theme',
+        # Markdown to REST for documentation.
+        'm2r',
+        # Diagram generation modules.
+        'sphinxcontrib-actdiag',
+        'sphinxcontrib-blockdiag',
+        'sphinxcontrib-nwdiag',
+        'sphinxcontrib-seqdiag',
+    ],
+)
diff --git a/pw_doctor/BUILD b/pw_doctor/BUILD
new file mode 100644
index 0000000..85a5ca0
--- /dev/null
+++ b/pw_doctor/BUILD
@@ -0,0 +1,17 @@
+# Copyright 2019 The Pigweed Authors
+#
+# 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
+#
+#     https://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(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
diff --git a/pw_doctor/BUILD.bazel b/pw_doctor/BUILD.bazel
deleted file mode 100644
index 7bb1810..0000000
--- a/pw_doctor/BUILD.bazel
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright 2019 The Pigweed Authors
-#
-# 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
-#
-#     https://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(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
diff --git a/pw_doctor/OWNERS b/pw_doctor/OWNERS
deleted file mode 100644
index c0fb30b..0000000
--- a/pw_doctor/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-mohrr@google.com
diff --git a/pw_doctor/docs.rst b/pw_doctor/docs.rst
index 6fb524e..1bd6578 100644
--- a/pw_doctor/docs.rst
+++ b/pw_doctor/docs.rst
@@ -7,19 +7,10 @@
 it checks that things exactly match what is expected and it checks that things
 look compatible without.
 
+Currently pw_doctor expects the running python to be Python 3.8 or 3.9.
+
 Projects that adjust the behavior of pw_env_setup may need to customize
 these checks, but unfortunately this is not supported yet.
 
-Checks carried out by pw_doctor include:
-
-* The bootstrapped OS matches the current OS.
-* ``PW_ROOT`` is defined and points to the root of the Pigweed repo.
-* The presubmit git hook is installed.
-* The current Python version is 3.8 or 3.9.
-* The Pigweed virtual env is active.
-* CIPD is set up correctly and in use.
-* The CIPD packages required by Pigweed are up to date.
-* The platform support symlinks.
-
 .. note::
   The documentation for this module is currently incomplete.
diff --git a/pw_doctor/py/BUILD.gn b/pw_doctor/py/BUILD.gn
index 1750e5d..bec5e59 100644
--- a/pw_doctor/py/BUILD.gn
+++ b/pw_doctor/py/BUILD.gn
@@ -17,11 +17,7 @@
 import("$dir_pw_build/python.gni")
 
 pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
+  setup = [ "setup.py" ]
   sources = [
     "pw_doctor/__init__.py",
     "pw_doctor/doctor.py",
diff --git a/pw_doctor/py/pw_doctor/doctor.py b/pw_doctor/py/pw_doctor/doctor.py
index 1d2d373..7594616 100755
--- a/pw_doctor/py/pw_doctor/doctor.py
+++ b/pw_doctor/py/pw_doctor/doctor.py
@@ -27,7 +27,6 @@
 from typing import Callable, Iterable, List, Set
 
 import pw_cli.pw_command_plugins
-import pw_env_setup.cipd_setup.update as cipd_update
 
 
 def call_stdout(*args, **kwargs):
@@ -268,6 +267,7 @@
     # TODO(mohrr) get these tools in CIPD for Windows.
     if os.name == 'posix':
         commands_expected_from_cipd += [
+            'bazel',
             'bloaty',
             'clang++',
         ]
@@ -289,52 +289,32 @@
     if os.environ.get('PW_DOCTOR_SKIP_CIPD_CHECKS'):
         return
 
-    if 'PW_CIPD_INSTALL_DIR' not in os.environ:
-        ctx.error('PW_CIPD_INSTALL_DIR not set')
-    cipd_dir = pathlib.Path(os.environ['PW_CIPD_INSTALL_DIR'])
+    try:
+        root = pathlib.Path(os.environ['PW_ROOT']).resolve()
+    except KeyError:
+        return  # This case is handled elsewhere.
 
-    with open(cipd_dir / '_all_package_files.json', 'r') as ins:
-        json_paths = [pathlib.Path(x) for x in json.load(ins)]
+    if 'PW_PIGWEED_CIPD_INSTALL_DIR' not in os.environ:
+        ctx.error('PW_PIGWEED_CIPD_INSTALL_DIR not set')
+    cipd_dir = pathlib.Path(os.environ['PW_PIGWEED_CIPD_INSTALL_DIR'])
 
-    platform = cipd_update.platform()
+    versions_path = cipd_dir / '.versions'
+    # Deliberately not checking luci.json--it's not required to be up-to-date.
+    json_path = root.joinpath('pw_env_setup', 'py', 'pw_env_setup',
+                              'cipd_setup', 'pigweed.json')
 
-    def check_cipd(package, install_path):
-        if platform not in package['platforms']:
-            ctx.debug("skipping %s because it doesn't apply to %s",
-                      package['path'], platform)
-            return
-
-        tags_without_refs = [x for x in package['tags'] if ':' in x]
-        if not tags_without_refs:
-            ctx.debug('skipping %s because it tracks a ref, not a tag (%s)',
-                      package['path'], ', '.join(package['tags']))
-            return
-
+    def check_cipd(package):
         ctx.debug('checking version of %s', package['path'])
-
         name = [
             part for part in package['path'].split('/') if '{' not in part
         ][-1]
-
-        # If the exact path is specified in the JSON file use it, and require it
-        # exist.
-        if 'version_file' in package:
-            path = install_path / package['version_file']
-            if not path.is_file():
-                ctx.error(f'no version file for {name} at {path}')
-                return
-
-        # Otherwise, follow a heuristic to find the file but don't require the
-        # file to exist.
-        else:
-            path = install_path / '.versions' / f'{name}.cipd_version'
-            if not path.is_file():
-                ctx.debug(f'no version file for {name} at {path}')
-                return
+        path = versions_path.joinpath(f'{name}.cipd_version')
+        if not path.is_file():
+            ctx.debug('no version file')
+            return
 
         with path.open() as ins:
             installed = json.load(ins)
-        ctx.debug(f'found version file for {name} at {path}')
 
         describe = (
             'cipd',
@@ -351,45 +331,11 @@
         for tag in package['tags']:
             if tag not in output:
                 ctx.error(
-                    'CIPD package %s in %s is out of date, please rerun '
-                    'bootstrap', installed['package_name'], install_path)
+                    'CIPD package %s is out of date, please rerun bootstrap',
+                    installed['package_name'])
 
-            else:
-                ctx.debug('CIPD package %s in %s is current',
-                          installed['package_name'], install_path)
-
-    for json_path in json_paths:
-        ctx.debug(f'Checking packages in {json_path}')
-        install_path = pathlib.Path(
-            cipd_update.package_installation_path(cipd_dir, json_path))
-        for package in json.loads(json_path.read_text()).get('packages', ()):
-            ctx.submit(check_cipd, package, install_path)
-
-
-@register_into(CHECKS)
-def symlinks(ctx: DoctorContext):
-    """Check that the platform supports symlinks."""
-
-    try:
-        root = pathlib.Path(os.environ['PW_ROOT']).resolve()
-    except KeyError:
-        return  # This case is handled elsewhere.
-
-    with tempfile.TemporaryDirectory() as tmpdir:
-        dest = pathlib.Path(tmpdir).resolve() / 'symlink'
-        try:
-            os.symlink(root, dest)
-            failure = False
-        except OSError:
-            # TODO(pwbug/500) Find out what errno is set when symlinks aren't
-            # supported by the OS.
-            failure = True
-
-        if not os.path.islink(dest) or failure:
-            ctx.warning(
-                'Symlinks are not supported or current user does not have '
-                'permission to use them. This may cause build issues. If on '
-                'Windows, turn on Development Mode to enable symlink support.')
+    for package in json.loads(json_path.read_text()):
+        ctx.submit(check_cipd, package)
 
 
 def run_doctor(strict=False, checks=None):
diff --git a/pw_doctor/py/pyproject.toml b/pw_doctor/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/pw_doctor/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/pw_doctor/py/setup.cfg b/pw_doctor/py/setup.cfg
deleted file mode 100644
index 12e5efa..0000000
--- a/pw_doctor/py/setup.cfg
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pw_doctor
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Environment check script for Pigweed
-
-[options]
-packages = find:
-zip_safe = False
-
-[options.package_data]
-pw_doctor = py.typed
diff --git a/pw_doctor/py/setup.py b/pw_doctor/py/setup.py
index d8ec216..bc80827 100644
--- a/pw_doctor/py/setup.py
+++ b/pw_doctor/py/setup.py
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2019 The Pigweed Authors
 #
 # 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
@@ -11,8 +11,17 @@
 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 # License for the specific language governing permissions and limitations under
 # the License.
-"""pw_doctor"""
+"""The pw_doctor package."""
 
 import setuptools  # type: ignore
 
-setuptools.setup()  # Package definition in setup.cfg
+setuptools.setup(
+    name='pw_doctor',
+    version='0.0.1',
+    author='Pigweed Authors',
+    author_email='pigweed-developers@googlegroups.com',
+    description='Environment check script for Pigweed',
+    packages=setuptools.find_packages(),
+    package_data={'pw_doctor': ['py.typed']},
+    zip_safe=False,
+)
diff --git a/pw_env_setup/BUILD.bazel b/pw_env_setup/BUILD.bazel
deleted file mode 100644
index 1e2525c..0000000
--- a/pw_env_setup/BUILD.bazel
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-exports_files([
-    "py/pw_env_setup/cipd_setup/.cipd_version",
-    "py/pw_env_setup/cipd_setup/.cipd_version.digests",
-] + glob(["py/**/*.json"]))
diff --git a/pw_env_setup/BUILD.gn b/pw_env_setup/BUILD.gn
index 6cf0dfc..2a4528f 100644
--- a/pw_env_setup/BUILD.gn
+++ b/pw_env_setup/BUILD.gn
@@ -15,7 +15,6 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_build/python.gni")
-import("$dir_pw_build/python_dist.gni")
 import("$dir_pw_docgen/docs.gni")
 
 pw_doc_group("docs") {
@@ -23,99 +22,43 @@
   sources = [ "docs.rst" ]
 }
 
-_pigweed_python_deps = [
-  # Python packages
-  "$dir_pw_allocator/py",
-  "$dir_pw_arduino_build/py",
-  "$dir_pw_bloat/py",
-  "$dir_pw_build/py",
-  "$dir_pw_build_info/py",
-  "$dir_pw_build_mcuxpresso/py",
-  "$dir_pw_cli/py",
-  "$dir_pw_console/py",
-  "$dir_pw_cpu_exception_cortex_m/py",
-  "$dir_pw_docgen/py",
-  "$dir_pw_doctor/py",
-  "$dir_pw_env_setup/py",
-  "$dir_pw_hdlc/py",
-  "$dir_pw_log:protos.python",
-  "$dir_pw_log_tokenized/py",
-  "$dir_pw_module/py",
-  "$dir_pw_package/py",
-  "$dir_pw_presubmit/py",
-  "$dir_pw_protobuf/py",
-  "$dir_pw_protobuf_compiler/py",
-  "$dir_pw_rpc/py",
-  "$dir_pw_snapshot/py:pw_snapshot",
-  "$dir_pw_snapshot/py:pw_snapshot_metadata",
-  "$dir_pw_software_update/py",
-  "$dir_pw_status/py",
-  "$dir_pw_stm32cube_build/py",
-  "$dir_pw_symbolizer/py",
-  "$dir_pw_system/py",
-  "$dir_pw_thread/py",
-  "$dir_pw_tls_client/py",
-  "$dir_pw_tokenizer/py",
-  "$dir_pw_toolchain/py",
-  "$dir_pw_trace/py",
-  "$dir_pw_trace_tokenized/py",
-  "$dir_pw_transfer/py",
-  "$dir_pw_unit_test/py",
-  "$dir_pw_watch/py",
-]
-
 pw_python_group("python") {
-  python_deps = _pigweed_python_deps
-  python_deps += [
+  python_deps = [
+    # Python packages
+    "$dir_pw_allocator/py",
+    "$dir_pw_arduino_build/py",
+    "$dir_pw_bloat/py",
+    "$dir_pw_build/py",
+    "$dir_pw_cli/py",
+    "$dir_pw_cpu_exception_cortex_m/py",
+    "$dir_pw_docgen/py",
+    "$dir_pw_doctor/py",
+    "$dir_pw_env_setup/py",
+    "$dir_pw_hdlc/py",
+    "$dir_pw_log_tokenized/py",
+    "$dir_pw_module/py",
+    "$dir_pw_package/py",
+    "$dir_pw_presubmit/py",
+    "$dir_pw_protobuf/py",
+    "$dir_pw_protobuf_compiler/py",
+    "$dir_pw_rpc/py",
+    "$dir_pw_status/py",
+    "$dir_pw_tokenizer/py",
+    "$dir_pw_toolchain/py",
+    "$dir_pw_trace/py",
+    "$dir_pw_trace_tokenized/py",
+    "$dir_pw_unit_test/py",
+    "$dir_pw_watch/py",
+
     # Standalone scripts
     "$dir_pw_hdlc/rpc_example:example_script",
-    "$dir_pw_rpc/py:python_client_cpp_server_test",
-    "$dir_pw_third_party/nanopb:generate_nanopb_proto",
-    "$dir_pw_transfer/py:python_cpp_transfer_test",
-
-    # Python requirements for CIPD packages that don't have dedicated modules.
-    ":renode_requirements",
   ]
 }
 
 # Python packages for supporting specific targets.
 pw_python_group("target_support_packages") {
   python_deps = [
-    "$dir_pigweed/targets/lm3s6965evb_qemu/py",
-    "$dir_pigweed/targets/stm32f429i_disc1/py",
-  ]
-}
-
-pw_python_requirements("renode_requirements") {
-  requirements = [
-    "psutil",
-    "pyyaml",
-    "robotframework==3.1",
-  ]
-}
-
-pw_create_python_source_tree("build_pigweed_python_source_tree") {
-  packages = _pigweed_python_deps
-  include_tests = true
-  extra_files = [
-    "$dir_pigweed/pw_build/py/BUILD.bazel > pw_build/BUILD.bazel",
-    "$dir_pigweed/pw_cli/py/BUILD.bazel > pw_cli/BUILD.bazel",
-    "$dir_pigweed/pw_hdlc/py/BUILD.bazel > pw_hdlc/BUILD.bazel",
-    "$dir_pigweed/pw_protobuf/py/BUILD.bazel > pw_protobuf/BUILD.bazel",
-    "$dir_pigweed/pw_protobuf_compiler/py/BUILD.bazel > pw_protobuf_compiler/BUILD.bazel",
-    "$dir_pigweed/pw_rpc/py/BUILD.bazel > pw_rpc/BUILD.bazel",
-    "$dir_pigweed/pw_status/py/BUILD.bazel > pw_status/BUILD.bazel",
-  ]
-}
-
-pw_create_python_source_tree("pypi_pigweed_python_source_tree") {
-  packages = _pigweed_python_deps
-  generate_setup_cfg = {
-    common_config_file = "pypi_common_setup.cfg"
-  }
-  extra_files = [
-    "$dir_pigweed/LICENSE > LICENSE",
-    "$dir_pigweed/README.md > README.md",
-    "pypi_pyproject.toml > pyproject.toml",
+    "$dir_pigweed/targets/lm3s6965evb-qemu/py",
+    "$dir_pigweed/targets/stm32f429i-disc1/py",
   ]
 }
diff --git a/pw_env_setup/OWNERS b/pw_env_setup/OWNERS
deleted file mode 100644
index 792c5ad..0000000
--- a/pw_env_setup/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-jethier@google.com
-mohrr@google.com
diff --git a/pw_env_setup/bazel/cipd_setup/BUILD.bazel b/pw_env_setup/bazel/cipd_setup/BUILD.bazel
deleted file mode 100644
index e3edb0b..0000000
--- a/pw_env_setup/bazel/cipd_setup/BUILD.bazel
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-# This file is intentionally left empty, but it is required to mark this
-# directory as a package for 'cipd_rules.bzl'.
diff --git a/pw_env_setup/bazel/cipd_setup/cipd_rules.bzl b/pw_env_setup/bazel/cipd_setup/cipd_rules.bzl
deleted file mode 100644
index f7faf81..0000000
--- a/pw_env_setup/bazel/cipd_setup/cipd_rules.bzl
+++ /dev/null
@@ -1,103 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Bazel rules for downloading CIPD packages."""
-
-load(
-    "//pw_env_setup/bazel/cipd_setup/internal:cipd_internal.bzl",
-    _cipd_client_impl = "cipd_client_impl",
-    _cipd_deps_impl = "cipd_deps_impl",
-    _cipd_repository_impl = "cipd_repository_impl",
-)
-
-_cipd_client_repository = repository_rule(
-    _cipd_client_impl,
-    attrs = {
-        "_cipd_version_file": attr.label(default = "@pigweed//pw_env_setup:py/pw_env_setup/cipd_setup/.cipd_version"),
-        "_cipd_digest_file": attr.label(default = "@pigweed//pw_env_setup:py/pw_env_setup/cipd_setup/.cipd_version.digests"),
-    },
-    doc = """
-Fetches the cipd client.
-
-This rule should not be used directly and instead should be called via
-the cipd_client_repository macro.
-""",
-)
-
-def cipd_client_repository():
-    """Fetches the cipd client.
-
-    Fetches the cipd client to the prescribed remote repository target
-    prefix 'cipd_client'. This rule should be called before a
-    cipd_repository rule is instantiated.
-    """
-    _cipd_client_repository(
-        name = "cipd_client",
-    )
-
-cipd_repository = repository_rule(
-    _cipd_repository_impl,
-    attrs = {
-        "_cipd_client": attr.label(default = "@cipd_client//:cipd"),
-        "path": attr.string(),
-        "tag": attr.string(),
-    },
-    doc = """
-Downloads a singular CIPD dependency to the root of a remote repository.
-
-Example:
-
-    load(
-        "//pw_env_setup/bazel/cipd_setup:cipd_rules.bzl",
-        "cipd_client_repository",
-        "cipd_repository",
-    )
-
-    # Must be called before cipd_repository
-    cipd_client_repository()
-
-    cipd_repository(
-        name = "bloaty",
-        path = "pigweed/third_party/bloaty-embedded/${os=linux,mac}-${arch=amd64}",
-        tag = "git_revision:2d87d204057b419f5290f8d38b61b9c2c5b4fb52-2",
-    )
-""",
-)
-
-_pigweed_deps = repository_rule(
-    _cipd_deps_impl,
-    attrs = {
-        "_pigweed_packages_json": attr.label(
-            default = "@pigweed//pw_env_setup:py/pw_env_setup/cipd_setup/pigweed.json",
-        ),
-        "_python_packages_json": attr.label(
-            default = "@pigweed//pw_env_setup:py/pw_env_setup/cipd_setup/python.json",
-        ),
-    },
-)
-
-def pigweed_deps():
-    """Configures Pigweeds Bazel dependencies
-
-    Example:
-        load("@pigweed//pw_env_setup:pigweed_deps.bzl", "pigweed_deps")
-
-        pigweed_deps()
-
-        load("@cipd_deps//:cipd_init.bzl", "cipd_init")
-
-        cipd_init()
-"""
-    _pigweed_deps(
-        name = "cipd_deps",
-    )
diff --git a/pw_env_setup/bazel/cipd_setup/ensure.tpl b/pw_env_setup/bazel/cipd_setup/ensure.tpl
deleted file mode 100644
index 07e3fd1..0000000
--- a/pw_env_setup/bazel/cipd_setup/ensure.tpl
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-$VerifiedPlatform linux-amd64
-$VerifiedPlatform mac-amd64
-$ParanoidMode CheckPresence
-@Subdir
-%{path} %{tag}
\ No newline at end of file
diff --git a/pw_env_setup/bazel/cipd_setup/internal/BUILD.bazel b/pw_env_setup/bazel/cipd_setup/internal/BUILD.bazel
deleted file mode 100644
index d3c52e1..0000000
--- a/pw_env_setup/bazel/cipd_setup/internal/BUILD.bazel
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-# This file is intentionally left empty to mark this as a package for
-# cipd_internal.bzl
diff --git a/pw_env_setup/bazel/cipd_setup/internal/cipd_internal.bzl b/pw_env_setup/bazel/cipd_setup/internal/cipd_internal.bzl
deleted file mode 100644
index bbd2c95..0000000
--- a/pw_env_setup/bazel/cipd_setup/internal/cipd_internal.bzl
+++ /dev/null
@@ -1,189 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Internal Bazel helpers for downloading CIPD packages."""
-
-load(
-    ":cipd_repository_list_templates.bzl",
-    "CIPD_INIT_BZL_TEMPLATE",
-    "CIPD_REPOSITORY_TEMPLATE",
-)
-
-_CIPD_HOST = "https://chrome-infra-packages.appspot.com"
-
-def platform_normalized(rctx):
-    """Normalizes the platform to match CIPDs naming system.
-
-    Args:
-        rctx: Repository context.
-
-    Returns:
-        str: Normalized string.
-    """
-
-    # Chained if else used because Bazel's rctx.os.name is not stable
-    # between different versions of windows i.e. windows 10 vs windows
-    # server.
-    if "windows" in rctx.os.name:
-        return "windows"
-    elif "linux" == rctx.os.name:
-        return "linux"
-    elif "mac os x" == rctx.os.name:
-        return "mac"
-    else:
-        fail("Could not normalize os:", rctx.os.name)
-
-# TODO(pwbug/388): Enable unused variable check.
-# buildifier: disable=unused-variable
-def arch_normalized(rctx):
-    """Normalizes the architecture string to match CIPDs naming system.
-
-    Args:
-        rctx: Repository context.
-
-    Returns:
-        str: Normalized architecture.
-    """
-
-    # TODO(pwbug/388): Find a way to get host architecture information from a
-    # repository context.
-    return "amd64"
-
-def get_client_cipd_version(rctx):
-    """Gets the CIPD client version from the config file.
-
-    Args:
-        rctx: Repository context.
-
-    Returns:
-        str: The CIPD client version tag to use.
-    """
-    return rctx.read(rctx.attr._cipd_version_file).strip()
-
-def _platform(rctx):
-    return "{}-{}".format(platform_normalized(rctx), arch_normalized(rctx))
-
-def get_client_cipd_digest(rctx):
-    """Gets the CIPD client digest from the digest file.
-
-    Args:
-        rctx: Repository context.
-
-    Returns:
-        str: The CIPD client digest.
-    """
-    platform = _platform(rctx)
-    digest_file = rctx.read(rctx.attr._cipd_digest_file)
-    digest_lines = [
-        digest
-        for digest in digest_file.splitlines()
-        # Remove comments from version file
-        if not digest.startswith("#") and digest
-    ]
-
-    for line in digest_lines:
-        (digest_platform, digest_type, digest) = \
-            [element for element in line.split(" ") if element]
-        if digest_platform == platform:
-            if digest_type != "sha256":
-                fail("Bazel only supports sha256 type digests.")
-            return digest
-    fail("Could not find CIPD digest that matches this platform.")
-
-def cipd_client_impl(rctx):
-    platform = _platform(rctx)
-    path = "/client?platform={}&version={}".format(
-        platform,
-        get_client_cipd_version(rctx),
-    )
-    rctx.download(
-        output = "cipd",
-        url = _CIPD_HOST + path,
-        sha256 = get_client_cipd_digest(rctx),
-        executable = True,
-    )
-    rctx.file("BUILD", "exports_files([\"cipd\"])")
-
-def cipd_repository_base(rctx):
-    cipd_path = rctx.path(rctx.attr._cipd_client).basename
-    ensure_path = rctx.name + ".ensure"
-    rctx.template(
-        ensure_path,
-        Label("@pigweed//pw_env_setup/bazel/cipd_setup:ensure.tpl"),
-        {
-            "%{path}": rctx.attr.path,
-            "%{tag}": rctx.attr.tag,
-        },
-    )
-    rctx.execute([cipd_path, "ensure", "-root", ".", "-ensure-file", ensure_path])
-
-def cipd_repository_impl(rctx):
-    cipd_repository_base(rctx)
-    rctx.file("BUILD", """
-exports_files(glob([\"**/*\"]))
-
-filegroup(
-    name = "all",
-    srcs = glob(["**/*"]),
-    visibility = ["//visibility:public"],
-)
-""")
-
-def _cipd_path_to_repository_name(path, platform):
-    """ Converts a cipd path to a repository name
-
-    Args:
-        path: The cipd path.
-        platform: The cipd platform name.
-
-    Example:
-        print(_cipd_path_to_repository_name(
-            "infra/3pp/tools/cpython3/windows-amd64",
-            "linux-amd64"
-        ))
-        >> cipd_infra_3pp_tools_cpython3_windows_amd64
-    """
-    return "cipd_" + \
-           path.replace("/", "_") \
-               .replace("${platform}", platform) \
-               .replace("-", "_")
-
-def _cipd_dep_to_cipd_repositories_str(dep, indent):
-    """ Converts a CIPD dependency to a CIPD repositories string
-
-    Args:
-        dep: The CIPD dependency.
-        indent: The indentation to use.
-    """
-    return "\n".join([CIPD_REPOSITORY_TEMPLATE.format(
-        name = _cipd_path_to_repository_name(dep["path"], platform),
-        path = dep["path"].replace("${platform}", platform),
-        tag = dep["tags"][0],
-        indent = indent,
-    ) for platform in dep["platforms"]])
-
-def cipd_deps_impl(repository_ctx):
-    """ Generates a CIPD dependencies file """
-    pigweed_deps = json.decode(
-        repository_ctx.read(repository_ctx.attr._pigweed_packages_json),
-    )["packages"] + json.decode(
-        repository_ctx.read(repository_ctx.attr._python_packages_json),
-    )["packages"]
-    repository_ctx.file("BUILD", "exports_files(glob([\"**/*\"]))\n")
-
-    repository_ctx.file("cipd_init.bzl", CIPD_INIT_BZL_TEMPLATE.format(
-        cipd_deps = "\n".join([
-            _cipd_dep_to_cipd_repositories_str(dep, indent = "    ")
-            for dep in pigweed_deps
-        ]),
-    ))
diff --git a/pw_env_setup/bazel/cipd_setup/internal/cipd_repository_list_templates.bzl b/pw_env_setup/bazel/cipd_setup/internal/cipd_repository_list_templates.bzl
deleted file mode 100644
index 55f5fe1..0000000
--- a/pw_env_setup/bazel/cipd_setup/internal/cipd_repository_list_templates.bzl
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""CIPD rule templates."""
-
-CIPD_REPOSITORY_TEMPLATE = """
-{indent}cipd_repository(
-{indent}    name = "{name}",
-{indent}    path = "{path}",
-{indent}    tag = "{tag}",
-{indent})"""
-
-CIPD_INIT_BZL_TEMPLATE = """
-load("@pigweed//pw_env_setup/bazel/cipd_setup:cipd_rules.bzl",
-    "cipd_repository",
-    "cipd_client_repository",
-)
-
-def cipd_init():
-    cipd_client_repository()
-{cipd_deps}
-"""
diff --git a/pw_env_setup/bazel_only.json b/pw_env_setup/bazel_only.json
deleted file mode 100644
index 3e5fa96..0000000
--- a/pw_env_setup/bazel_only.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-  "root_variable": "PW_ROOT",
-  "cipd_package_files": [
-    "pw_env_setup/py/pw_env_setup/cipd_setup/bazel.json",
-    "pw_env_setup/py/pw_env_setup/cipd_setup/python.json",
-  ],
-  "virtualenv": {
-    "gn_root": ".",
-    "gn_targets": [
-      ":python.install",
-      ":target_support_packages.install"
-    ]
-  }
-}
diff --git a/pw_env_setup/compatibility.json b/pw_env_setup/compatibility.json
index 402ea7e..07db79a 100644
--- a/pw_env_setup/compatibility.json
+++ b/pw_env_setup/compatibility.json
@@ -1,13 +1,14 @@
 {
-  "root_variable": "PW_ROOT",
   "cipd_package_files": [
-    "pw_env_setup/py/pw_env_setup/cipd_setup/default.json",
+    "pw_env_setup/py/pw_env_setup/cipd_setup/pigweed.json",
+    "pw_env_setup/py/pw_env_setup/cipd_setup/luci.json",
     "pw_env_setup/py/pw_env_setup/cipd_setup/compatibility.json"
   ],
   "virtualenv": {
     "gn_root": ".",
     "gn_targets": [
-      ":python.install"
+      ":python.install",
+      ":target_support_packages.install"
     ]
   }
 }
diff --git a/pw_env_setup/config.json b/pw_env_setup/config.json
index 5d3e5b0..435be72 100644
--- a/pw_env_setup/config.json
+++ b/pw_env_setup/config.json
@@ -1,14 +1,13 @@
 {
-  "root_variable": "PW_ROOT",
   "cipd_package_files": [
-    "pw_env_setup/py/pw_env_setup/cipd_setup/default.json"
+    "pw_env_setup/py/pw_env_setup/cipd_setup/pigweed.json",
+    "pw_env_setup/py/pw_env_setup/cipd_setup/luci.json"
   ],
   "virtualenv": {
     "gn_root": ".",
     "gn_targets": [
-      ":python.install"
+      ":python.install",
+      ":target_support_packages.install"
     ]
-  },
-  "pw_packages": [],
-  "gni_file": "build_overrides/pigweed_environment.gni"
+  }
 }
diff --git a/pw_env_setup/destination.md b/pw_env_setup/destination.md
deleted file mode 100644
index 37a1b9a..0000000
--- a/pw_env_setup/destination.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# Pigweed Environment Directory
-
-This is the directory into which Pigweed installed its environment. In general,
-it's acceptable to delete this entire directory, but don't delete any files
-within this directory without deleting the entire directory or there may be
-strange results.
-
-After deleting this directory, run `. bootstrap.sh` or `bootstrap.bat` instead
-of `. activate.sh` or `activate.bat`.
diff --git a/pw_env_setup/docs.rst b/pw_env_setup/docs.rst
index ecaecb3..7832a9f 100644
--- a/pw_env_setup/docs.rst
+++ b/pw_env_setup/docs.rst
@@ -28,7 +28,7 @@
 changes to your system. This tooling is designed to be reused by any
 project.
 
-.. _CIPD: https://github.com/luci/luci-go/tree/HEAD/cipd
+.. _CIPD: https://github.com/luci/luci-go/tree/master/cipd
 
 Users interact with  ``pw_env_setup`` with two commands: ``. bootstrap.sh`` and
 ``. activate.sh``. The bootstrap command always pulls down the current versions
@@ -49,8 +49,6 @@
 
 .. _send us a note: pigweed@googlegroups.com
 
-On POSIX systems, the environment can be deactivated by running ``deactivate``.
-
 ==================================
 Using pw_env_setup in your project
 ==================================
@@ -93,49 +91,6 @@
   pw_bootstrap --args...  # See below for details about args.
   pw_finalize bootstrap "$SETUP_SH"
 
-
-Bazel Usage
------------
-It is possible to pull in a CIPD dependency into Bazel using WORKSPACE rules
-rather than using `bootstrap.sh`. e.g.
-
-.. code:: python
-
-  # WORKSPACE
-
-  load("//pw_env_setup/bazel/cipd_setup:cipd_rules.bzl", "pigweed_deps")
-
-  # Setup CIPD client and packages.
-  # Required by: pigweed.
-  # Used by modules: all.
-  pigweed_deps()
-
-  load("@cipd_deps//:cipd_init.bzl", "cipd_init")
-
-  cipd_init()
-
-
-This will make the entire set of Pigweeds remote repositories available
-to your project. Though these repositories will only be donwloaded if
-you use them. To get a full list of the remote repositories that this
-configures, run:
-
-.. code:: sh
-
-  bazel query //external:all | grep cipd_
-
-All files and executables in each CIPD remote repository is exported
-and visible either directely (`@cipd_<dep>//:<file>`) or from 'all' filegroup
-(`@cipd_<dep>//:all`).
-
-From here it is possible to get access to the Bloaty binaries using the
-following command. For example;
-
-.. code:: sh
-
-  bazel run @cipd_pigweed_third_party_bloaty_embedded_linux_amd64//:bloaty \
-   -- --help
-
 User-Friendliness
 -----------------
 
@@ -209,48 +164,11 @@
 environment variables. Explanations of parts of ``config.json`` are described
 here.
 
-.. _sample project: https://pigweed.googlesource.com/pigweed/sample_project/+/HEAD
-
-``root_variable``
-  Variable used to point to the root of the source tree. Optional, can always
-  use ``PW_PROJECT_ROOT`` instead. (That variable will be set regardless of
-  whether this is provided.)
+.. _sample project: https://pigweed.googlesource.com/pigweed/sample_project/+/master
 
 ``cipd_package_files``
-  CIPD package file. JSON file consisting of a list of additional CIPD package
-  files to import and a list of dictionaries with "path", "platforms", "subdir",
-  "tags", and "version_file" keys. Both top-level lists are optional. An
-  example is below. Only "path", "platforms", and "tags" are required. If
-  "version_file" is specified then ``pw doctor`` will fail if that version file
-  is not present. If "subdir" is specified then this packages will be installed
-  in a subdirectory of the directory created for packages in this file.
-
-.. code-block:: json
-
-  {
-    "included_files": [
-      "foo.json"
-    ],
-    "packages": [
-      {
-        "path": "infra/3pp/tools/go/${platform}",
-        "platforms": [
-            "linux-amd64",
-            "linux-arm64",
-            "mac-amd64",
-            "windows-amd64"
-        ],
-        "subdir": "pa/th",
-        "tags": [
-          "version:2@1.16.3"
-        ],
-        "version_file": ".versions/go.cipd_version"
-      }
-    ]
-  }
-
-``virtualenv.gn_args``
-  Any necessary GN args to be used when installing Python packages.
+  CIPD package file. JSON file consisting of a list of dictionaries with "path"
+  and "tags" keys, where "tags" is a list of strings.
 
 ``virtualenv.gn_targets``
   Target for installing Python packages. Downstream projects will need to
@@ -262,39 +180,11 @@
   only installing Pigweed Python packages, use the location of the Pigweed
   submodule.
 
-``virtualenv.system_packages``
-  A boolean value that can be used the give the Python virtual environment
-  access to the system site packages. Defaults to ``false``.
-
-``optional_submodules``
-  By default environment setup will check that all submodules are present in
-  the checkout. Any submodules in this list are excluded from that check.
-
-``required_submodules``
-  If this is specified instead of ``optional_submodules`` bootstrap will only
-  complain if one of the required submodules is not present. Combining this
-  with ``optional_submodules`` is not supported.
-
-``pw_packages``
-  A list of packages to install using :ref:`pw_package <module-pw_package>`
-  after the rest of bootstrap completes.
-
-``gni_file``
-  Location to write a ``.gni`` file containing paths to many things within the
-  environment directory. Defaults to
-  ``build_overrides/pigweed_environment.gni``.
-
-``json_file``
-  Location to write a ``.json`` file containing step-by-step modifications to
-  the environment, for reading by tools that don't inherit an environment from
-  a sourced ``bootstrap.sh``.
-
 An example of a config file is below.
 
 .. code-block:: json
 
   {
-    "root_variable": "EXAMPLE_ROOT",
     "cipd_package_files": [
       "pigweed/pw_env_setup/py/pw_env_setup/cipd_setup/pigweed.json",
       "pigweed/pw_env_setup/py/pw_env_setup/cipd_setup/luci.json"
@@ -304,16 +194,8 @@
       "gn_root": ".",
       "gn_targets": [
         ":python.install",
-      ],
-      "system_packages": false
-    },
-    "pw_packages": [],
-    "optional_submodules": [
-      "optional/submodule/one",
-      "optional/submodule/two"
-    ],
-    "gni_file": "tools/environment.gni",
-    "json_file": "tools/environment.json"
+      ]
+    }
   }
 
 In case the CIPD packages need to be referenced from other scripts, variables
@@ -327,75 +209,18 @@
  - ``PW_MYPROJECTNAME_CIPD_INSTALL_DIR``
  - ``PW_PIGWEED_CIPD_INSTALL_DIR``
 
-These directories are also referenced in the gni_file specified by the
-environment config file as ``dir_cipd_${BASENAME}``. This allows the GN build to
-reliably reference these directories without using GN ``getenv()`` calls or
-hardcoding paths.
-
-In addition, ``PW_${BASENAME}_CIPD_INSTALL_DIR`` and
-``PW_${BASENAME}_CIPD_INSTALL_DIR/bin`` are both added to ``PATH`` for each
-package directory.
-
-If multiple packages install executables with the same name, the file mentioned
-last topologically takes priority. For example, with the file contents below,
-``d.json``'s entries will appear in ``PATH`` before ``c.json``'s, which will
-appear before ``b.json``'s, which will appear before ``a.json``'s.
-
-``config.json``
-  ``{"cipd_package_files": ["a.json", "b.json", "d.json"], ...}``
-
-``a.json``
-  ``{"package_files": [...]}``
-
-``b.json``
-  ``{"included_files": ["c.json"], "package_files": [...]}``
-
-``c.json``
-  ``{"package_files": [...]}``
-
-``d.json``
-  ``{"package_files": [...]}``
-
-Pinning Python Packages
-***********************
-Python modules usually express dependencies as ranges, which makes it easier to
-install many Python packages that might otherwise have conflicting dependencies.
-However, this means version of packages can often change underneath us and
-builds will not be hermetic.
-
-To ensure versions don't change without approval, Pigweed by default pins the
-versions of packages it depends on using a `pip constraints file`_. To pin the
-versions of additional packages your project depends on, run
-``pw python-packages list <path/to/constraints/file>`` and then add
-``pw_build_PIP_CONSTRAINTS = ["//path/to/constraints/file"]`` to your project's
-``.gn`` file (see `Pigweed's .gn file`_ for an example).
-
-.. _pip constraints file: https://pip.pypa.io/en/stable/user_guide/#constraints-files
-.. _default constraints: https://cs.opensource.google/pigweed/pigweed/+/main:pw_env_setup/py/pw_env_setup/virtualenv_setup/constraint.list
-.. _Pigweed's .gn file: https://cs.opensource.google/pigweed/pigweed/+/main:.gn
-
-To update packages, set ``pw_build_PIP_CONSTRAINTS = []``, delete the
-environment, and bootstrap again. Then run the ``list`` command from above
-again, and run ``pw presubmit``.
-
 Environment Variables
 *********************
-Input Variables
----------------
 The following environment variables affect env setup behavior. Most users will
 never need to set these.
 
 ``CIPD_CACHE_DIR``
-  Location of CIPD cache dir. Read by CIPD, but if unset will be defaulted to
-  ``$HOME/.cipd-cache-dir``.
+  Location of CIPD cache dir. Defaults to ``$HOME/.cipd-cache-dir``.
 
 ``PW_ACTIVATE_SKIP_CHECKS``
   If set, skip running ``pw doctor`` at end of bootstrap/activate. Intended to
   be used by automated tools but not interactively.
 
-``PW_BANNER_FUNC``
-  Command to print a banner at the beginning of bootstrap.
-
 ``PW_BOOTSTRAP_PYTHON``
   Python executable to be used, for example "python2" or "python3". Defaults to
   "python".
@@ -408,111 +233,9 @@
   Disable the spinner during env setup. Intended to be used when the output is
   being redirected to a log.
 
-``PW_ENVSETUP_DISABLE_SPINNER``
-  Disable the console spinner that runs when waiting for env setup steps to
-  complete.
-
-``PW_ENVSETUP_NO_BANNER``
-  Skip printing the banner.
-
 ``PW_ENVSETUP_QUIET``
   Disables all non-error output.
 
-``PW_PROJECT_ROOT``
-  The absolute path of the project using Pigweed's env setup. For Pigweed this
-  is the same as ``PW_ROOT``. This should be set by the project's bootstrap
-  script.
-
-``PW_ROOT``
-  The absolute path to the Pigweed repository within ``PW_PROJECT_ROOT``. This
-  should be set by the project's bootstrap script.
-
-Output Variables
-----------------
-The following environment variables are set by env setup.
-
-``PATH``
-  System executable search path. Many of the environment variables below are
-  also added to this variable.
-
-``_PW_ACTUAL_ENVIRONMENT_ROOT``
-  Location the environment was installed into. Separate from
-  ``PW_ENVIRONMENT_ROOT`` because setting that implicitly and switching to
-  another project directory causes unexpected behavior.
-
-``PW_CIPD_INSTALL_DIR``
-  Top-level CIPD install directory. This is where the ``cipd`` executable is.
-
-``PW_*_CIPD_INSTALL_DIR``
-  Each CIPD package file is installed into its own directory. This allows other
-  tools to determine what those directories are. The ``*`` is replaced with an
-  all-caps version of the basename of the package file, without the extension.
-  (E.g., "path/foo.json" becomes ``PW_FOO_CIPD_INSTALL_DIR``.)
-
-``VIRTUAL_ENV``
-  Path to Pigweed's virtualenv.
-
-Non-Shell Environments
-**********************
-If using this outside of bash—for example directly from an IDE or CI
-system—users can process the ``actions.json`` file that's generated in the
-location specified by the environment config. It lists variables to set, clear,
-and modify. An example ``actions.json`` is shown below. The "append" and
-"prepend" actions are listed in the order they should be applied, so the
-``<pigweed-root>/out/host/host_tools`` entry should be at the beginning of
-``PATH`` and not in the middle somewhere.
-
-.. code-block:: json
-
-  {
-      "modify": {
-          "PATH": {
-              "append": [],
-              "prepend": [
-                  "<pigweed-root>/.environment/cipd",
-                  "<pigweed-root>/.environment/cipd/pigweed",
-                  "<pigweed-root>/.environment/cipd/pigweed/bin",
-                  "<pigweed-root>/.environment/cipd/luci",
-                  "<pigweed-root>/.environment/cipd/luci/bin",
-                  "<pigweed-root>/.environment/pigweed-venv/bin",
-                  "<pigweed-root>/out/host/host_tools"
-              ],
-              "remove": []
-          }
-      },
-      "set": {
-          "PW_PROJECT_ROOT": "<pigweed-root>",
-          "PW_ROOT": "<pigweed-root>",
-          "_PW_ACTUAL_ENVIRONMENT_ROOT": "<pigweed-root>/.environment",
-          "PW_CIPD_INSTALL_DIR": "<pigweed-root>/.environment/cipd",
-          "CIPD_CACHE_DIR": "<home>/.cipd-cache-dir",
-          "PW_PIGWEED_CIPD_INSTALL_DIR": "<pigweed-root>/.environment/cipd/pigweed",
-          "PW_LUCI_CIPD_INSTALL_DIR": "<pigweed-root>/.environment/cipd/luci",
-          "VIRTUAL_ENV": "<pigweed-root>/.environment/pigweed-venv",
-          "PYTHONHOME": null,
-          "__PYVENV_LAUNCHER__": null
-      }
-  }
-
-Many of these variables are directly exposed to the GN build as well, through
-the GNI file specified in the environment config file.
-
-.. code-block::
-
-  declare_args() {
-    dir_cipd_pigweed = "<pigweed-root>/.environment/cipd/packages/pigweed"
-    dir_cipd_luci = "<pigweed-root>/.environment/cipd/packages/luci"
-    dir_virtual_env = "<pigweed-root>/.environment/pigweed-venv"
-  }
-
-It's straightforward to use these variables.
-
-.. code-block:: cpp
-
-    import("//build_overrides/pigweed_environment.gni")
-
-    deps = [ "$dir_cipd_pigweed/..." ]
-
 Implementation
 **************
 
diff --git a/pw_env_setup/get_pw_env_setup.sh b/pw_env_setup/get_pw_env_setup.sh
index ff74220..0317b2e 100755
--- a/pw_env_setup/get_pw_env_setup.sh
+++ b/pw_env_setup/get_pw_env_setup.sh
@@ -46,11 +46,6 @@
   ARCH="amd64"
 fi
 
-# Support `mac-arm64` through Rosetta until `mac-arm64` binaries are ready
-if [[ "$OS" = "mac" ] && [ "$ARCH" = "arm64" ]]; then
-  ARCH="amd64"
-fi
-
 for HASH in $(git --git-dir="$PW_ROOT/.git" --no-pager log --max-count=10 --format=format:%H); do
   URL="https://storage.googleapis.com/pigweed-envsetup/$OS-$ARCH"
   URL="$URL/$HASH/pw_env_setup"
diff --git a/pw_env_setup/post-checkout-hook-helper.sh b/pw_env_setup/post-checkout-hook-helper.sh
deleted file mode 100755
index 91d1064..0000000
--- a/pw_env_setup/post-checkout-hook-helper.sh
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/bin/bash
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-# If we're not in a bootstrapped shell exit immediately. We won't know where
-# the config file is.
-if [ -z "$PW_PROJECT_ROOT" ]; then
-  exit 0
-fi
-
-set -o errexit
-set -o nounset
-set -o pipefail
-
-echo -n "Updating CIPD packages..."
-
-"$PW_ROOT/pw_env_setup/py/pw_env_setup/env_setup.py" \
-  --project-root "$PW_PROJECT_ROOT" \
-  --pw-root "$PW_ROOT" \
-  --config-file "$_PW_ENVIRONMENT_CONFIG_FILE" \
-  --shell-file "$_PW_ACTUAL_ENVIRONMENT_ROOT/unused.sh" \
-  --install-dir "$_PW_ACTUAL_ENVIRONMENT_ROOT" \
-  --quiet \
-  --trust-cipd-hash \
-  --cipd-only
-
-echo "done."
diff --git a/pw_env_setup/post-checkout-hook.sh b/pw_env_setup/post-checkout-hook.sh
deleted file mode 100755
index 065e328..0000000
--- a/pw_env_setup/post-checkout-hook.sh
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/bash
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-# The logic for the post-checkout hook is kept in another file so it can be
-# updated without requiring users to install updated hooks.
-
-# If we're not in a bootstrapped shell exit immediately. We won't know where
-# the helper or env_setup scripts are.
-if [ -z "$PW_ROOT" ]; then
-  exit 0
-fi
-
-"$PW_ROOT/pw_env_setup/post-checkout-hook-helper.sh"
diff --git a/pw_env_setup/py/BUILD.gn b/pw_env_setup/py/BUILD.gn
index cce9a21..030b671 100644
--- a/pw_env_setup/py/BUILD.gn
+++ b/pw_env_setup/py/BUILD.gn
@@ -17,24 +17,19 @@
 import("$dir_pw_build/python.gni")
 
 pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
+  setup = [ "setup.py" ]
   sources = [
     "pw_env_setup/__init__.py",
     "pw_env_setup/apply_visitor.py",
     "pw_env_setup/batch_visitor.py",
+    "pw_env_setup/cargo_setup/__init__.py",
     "pw_env_setup/cipd_setup/__init__.py",
     "pw_env_setup/cipd_setup/update.py",
     "pw_env_setup/cipd_setup/wrapper.py",
     "pw_env_setup/colors.py",
     "pw_env_setup/env_setup.py",
     "pw_env_setup/environment.py",
-    "pw_env_setup/gni_visitor.py",
     "pw_env_setup/json_visitor.py",
-    "pw_env_setup/python_packages.py",
     "pw_env_setup/shell_visitor.py",
     "pw_env_setup/spinner.py",
     "pw_env_setup/virtualenv_setup/__init__.py",
@@ -44,7 +39,6 @@
   ]
   tests = [
     "environment_test.py",
-    "python_packages_test.py",
     "json_visitor_test.py",
   ]
   pylintrc = "$dir_pigweed/.pylintrc"
diff --git a/pw_env_setup/py/pw_env_setup/cargo_setup/__init__.py b/pw_env_setup/py/pw_env_setup/cargo_setup/__init__.py
new file mode 100644
index 0000000..641e5ea
--- /dev/null
+++ b/pw_env_setup/py/pw_env_setup/cargo_setup/__init__.py
@@ -0,0 +1,62 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+"""Installs rust tools using cargo."""
+
+import os
+import subprocess
+import sys
+import tempfile
+
+
+def install(install_dir, package_files, env):
+    """Installs rust tools using cargo."""
+    # Adding to PATH at the beginning to suppress a warning about this not
+    # being in PATH.
+    env.prepend('PATH', os.path.join(install_dir, 'bin'))
+
+    if 'CARGO_TARGET_DIR' not in os.environ:
+        env.set('CARGO_TARGET_DIR', os.path.expanduser('~/.cargo-cache'))
+
+    with env():
+        for package_file in package_files:
+            with open(package_file, 'r') as ins:
+                for line in ins:
+                    line = line.strip()
+                    if not line or line.startswith('#'):
+                        continue
+
+                    package, version = line.split()
+                    cmd = [
+                        'cargo',
+                        'install',
+                        # If downgrading (which could happen when switching
+                        # branches) '--force' is required.
+                        '--force',
+                        '--root', install_dir,
+                        '--version', version,
+                        package,
+                    ]  # yapf: disable
+
+                    # TODO(pwbug/135) Use function from common utility module.
+                    with tempfile.TemporaryFile(mode='w+') as temp:
+                        try:
+                            subprocess.check_call(cmd,
+                                                  stdout=temp,
+                                                  stderr=subprocess.STDOUT)
+                        except subprocess.CalledProcessError:
+                            temp.seek(0)
+                            sys.stderr.write(temp.read())
+                            raise
+
+    return True
diff --git a/pw_env_setup/py/pw_env_setup/cargo_setup/packages.txt b/pw_env_setup/py/pw_env_setup/cargo_setup/packages.txt
new file mode 100644
index 0000000..5c2b50b
--- /dev/null
+++ b/pw_env_setup/py/pw_env_setup/cargo_setup/packages.txt
@@ -0,0 +1,18 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+# Rust packages to install via cargo.
+# Format: "package version"
+
+pyoxidizer 0.6.0
diff --git a/pw_env_setup/py/pw_env_setup/cipd_setup/.cipd_version b/pw_env_setup/py/pw_env_setup/cipd_setup/.cipd_version
index 815525c..d60179a 100644
--- a/pw_env_setup/py/pw_env_setup/cipd_setup/.cipd_version
+++ b/pw_env_setup/py/pw_env_setup/cipd_setup/.cipd_version
@@ -1 +1 @@
-git_revision:1897f73d5e9b48a00ac6935127751b18a8e2a6d7
+git_revision:0595f95a2a689f4f93f1b363fddeeb5614cfd435
diff --git a/pw_env_setup/py/pw_env_setup/cipd_setup/.cipd_version.digests b/pw_env_setup/py/pw_env_setup/cipd_setup/.cipd_version.digests
index a1226a1..831294e 100644
--- a/pw_env_setup/py/pw_env_setup/cipd_setup/.cipd_version.digests
+++ b/pw_env_setup/py/pw_env_setup/cipd_setup/.cipd_version.digests
@@ -1,23 +1,23 @@
 # This file was generated by
 #
 #  cipd selfupdate-roll -version-file .cipd_version \
-#      -version git_revision:1897f73d5e9b48a00ac6935127751b18a8e2a6d7
+#      -version git_revision:0595f95a2a689f4f93f1b363fddeeb5614cfd435
 #
 # Do not modify manually. All changes will be overwritten.
 # Use 'cipd selfupdate-roll ...' to modify.
 
-aix-ppc64       sha256  f3c819c44da1ebe55cea4e792c3b311218d5beab419a2e2fe1c65520a97bf88f
-linux-386       sha256  c0a16c1521e6691184eebcb971a3a4c0b5144a3922b4aeaaa4cdb2eabf5c5ab3
-linux-amd64     sha256  bc3ae4d3dfff7827930cfb92fa7aa067ae2428acb0d46c5c5269fe8268faa65b
-linux-arm64     sha256  312a9c2f4de0fb74fb510909b182f6dc34b59590022ccd8dbb23f0049029aea4
-linux-armv6l    sha256  552221c6f5f2e14522b8200ccb83d94052bb51c1d640c85d11671535fa028b88
-linux-mips64    sha256  2f4c3b33b925ecfabe2eb8569e952971f85dee165ab77cde367ef182a3fa2b9f
-linux-mips64le  sha256  b40e5b0d2bcebfbb7dca4e1221b57b39b38cc57ce943300120ffdcf6ea83f2d3
-linux-mipsle    sha256  bd0ed1738e4597d8f45b969e80fbd1919792be7bf4cb862f5ece442d4050aea9
-linux-ppc64     sha256  242b99f085473cdbcfa66fc800248a43863b1c1b932436f464de5cd0b79d3a17
-linux-ppc64le   sha256  0001aa0ee79450e1efaedf58b032f8a491c0006a1b66b440b91bef0f2afe883b
-linux-s390x     sha256  f15344e8b15c3e1448894a9d4511974fa8225e8bc9ebe61e5cb09cc68749c31e
-mac-amd64       sha256  d951395043a43f4f972af9a858550e9ffe82acabd39da301e025070357c8b2a1
-mac-arm64       sha256  9e298f81d18413910daa68392b0e60e9b40b9c0ab1fb817596b21669af36fd65
-windows-386     sha256  2ade2352f199d07f565701a09da972328de46dc66a26c216bb83c501f0d3e947
-windows-amd64   sha256  49cb17221ef4e6b6344c0b521614ec5845da60c3901b39297af6927bca435295
+aix-ppc64       sha256  15696797ee66c845ce225916013a4636696bd41ed47a0ac535a9124d22c41022
+linux-386       sha256  5470be1985b0e81ec0d23dc00576152545733cde81fa473eb49c33bc4036599c
+linux-amd64     sha256  1acf447faa0b487871cabdd7f6fc6be81386f5984d265ce39f8566513e8cef7f
+linux-arm64     sha256  c84fe3197e270e8e47845714db8bba8ccef5bfa764c1f8d8e93c1998f3159fd3
+linux-armv6l    sha256  0563054c1634d216978d5c551cb1c219288d22e2db5719270e423d1fd65665c3
+linux-mips64    sha256  263de3e97efb3e0c7478e353c2f90212df8f190d03d448bafb47b52dca1abe5e
+linux-mips64le  sha256  49abd7f3d1f92327e689a8a81f4f3cda9c1fe178cab0d05b624befc30711c3ec
+linux-mipsle    sha256  f782241a056dca749731b2412ab04ce252d2d882abba148a46b2b6fd8639e602
+linux-ppc64     sha256  627fa35e35cd9d1192aa142a763225ab937dbafe9b7380b69acc2202b88682e2
+linux-ppc64le   sha256  0ab2152e60571655c65c89381ff151773e70c4418a3dbbc1823d44ff6493567f
+linux-s390x     sha256  e35193afdbc5b44e056acf95debc12c2efaf4714815173fae9254b6ca6bf768f
+mac-amd64       sha256  7d4dce3743c139f9343011ba151f11a702477c02e0add14844f45e6600e56310
+mac-arm64       sha256  6faad926d9bd27f60dd12f1b5fa8d4977360cd8120fbd12e43ce0e7dd2176ee2
+windows-386     sha256  eba4e0ba48d329cb87685b484582cc86780f88be41ccc276409e6e224e70642d
+windows-amd64   sha256  c78ca9c4180ca98a57a697c1cf2ac86ef9ea7df197a606bb8fa67f0702c21837
diff --git a/pw_env_setup/py/pw_env_setup/cipd_setup/arm.json b/pw_env_setup/py/pw_env_setup/cipd_setup/arm.json
deleted file mode 100644
index ff12cd2..0000000
--- a/pw_env_setup/py/pw_env_setup/cipd_setup/arm.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
-  "packages": [
-    {
-      "path": "pigweed/third_party/gcc-arm-none-eabi/${platform}",
-      "platforms": [
-        "linux-amd64",
-        "mac-amd64",
-        "windows-amd64"
-      ],
-      "tags": [
-        "version:10-2020-q4-major"
-      ],
-      "version_file": ".versions/gcc-arm-none-eabi.cipd_version"
-    }
-  ]
-}
diff --git a/pw_env_setup/py/pw_env_setup/cipd_setup/bazel.json b/pw_env_setup/py/pw_env_setup/cipd_setup/bazel.json
deleted file mode 100644
index c053f27..0000000
--- a/pw_env_setup/py/pw_env_setup/cipd_setup/bazel.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
-  "packages": [
-    {
-      "path": "pigweed/third_party/bazel/${platform}",
-      "platforms": [
-        "linux-amd64",
-        "mac-amd64",
-        "windows-amd64"
-      ],
-      "tags": [
-        "version:5.0.0.1"
-      ],
-      "version_file": ".versions/bazel.cipd_version"
-    }
-  ]
-}
diff --git a/pw_env_setup/py/pw_env_setup/cipd_setup/compatibility.json b/pw_env_setup/py/pw_env_setup/cipd_setup/compatibility.json
index 7afe4d3..9132342 100644
--- a/pw_env_setup/py/pw_env_setup/cipd_setup/compatibility.json
+++ b/pw_env_setup/py/pw_env_setup/cipd_setup/compatibility.json
@@ -1,16 +1,8 @@
-{
-  "packages": [
-    {
-      "path": "infra/3pp/tools/cpython3/${os}-${arch=amd64}",
-      "tags": [
-	"version:3.7.7.chromium.10"
-      ]
-    },
-    {
-      "path": "pigweed/third_party/gcc-arm-none-eabi/${os}-${arch=amd64}",
-      "tags": [
-        "version:9-2020-q2-update"
-      ]
-    }
-  ]
-}
+[
+  {
+    "path": "infra/3pp/tools/cpython3/${os}-${arch=amd64}",
+    "tags": [
+      "version:3.7.7.chromium.10"
+    ]
+  }
+]
diff --git a/pw_env_setup/py/pw_env_setup/cipd_setup/default.json b/pw_env_setup/py/pw_env_setup/cipd_setup/default.json
deleted file mode 100644
index b20f270..0000000
--- a/pw_env_setup/py/pw_env_setup/cipd_setup/default.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-  "included_files": [
-    "pigweed.json",
-    "arm.json",
-    "python.json",
-    "bazel.json",
-    "luci.json"
-  ]
-}
diff --git a/pw_env_setup/py/pw_env_setup/cipd_setup/kythe.json b/pw_env_setup/py/pw_env_setup/cipd_setup/kythe.json
index 0540709..d1e02c0 100644
--- a/pw_env_setup/py/pw_env_setup/cipd_setup/kythe.json
+++ b/pw_env_setup/py/pw_env_setup/cipd_setup/kythe.json
@@ -1,22 +1,14 @@
-{
-  'packages': [
-    {
-      "path": "fuchsia/third_party/kythe",
-      "platforms": [
-        "linux-amd64"
-      ],
-      "tags": [
-        "version:0.0.46"
-      ]
-    },
-    {
-      "path": "fuchsia/third_party/kythe-libs/${platform}",
-      "platforms": [
-        "linux-amd64"
-      ],
-      "tags": [
-        "version:2020-08-05"
-      ]
-    }
-  ]
-}
+[
+  {
+    "path": "fuchsia/third_party/kythe",
+    "tags": [
+      "version:0.0.46"
+    ]
+  },
+  {
+    "path": "fuchsia/third_party/kythe-libs/${os=linux}-${arch=amd64}",
+    "tags": [
+      "version:2020-08-05"
+    ]
+  }
+]
diff --git a/pw_env_setup/py/pw_env_setup/cipd_setup/luci.json b/pw_env_setup/py/pw_env_setup/cipd_setup/luci.json
index e751955..1cb2bf7 100644
--- a/pw_env_setup/py/pw_env_setup/cipd_setup/luci.json
+++ b/pw_env_setup/py/pw_env_setup/cipd_setup/luci.json
@@ -1,101 +1,44 @@
-{
-  "packages": [
+[
     {
-      "path": "infra/tools/bb/${platform}",
-      "platforms": [
-        "linux-amd64",
-        "linux-arm64",
-        "mac-amd64",
-        "mac-arm64",
-        "windows-amd64"
-      ],
-      "tags": [
-        "latest"
-      ],
-      "version_file": ".versions/bb.cipd_version"
+        "path": "infra/tools/bb/${platform}",
+        "tags": [
+            "latest"
+        ]
     },
     {
-      "path": "infra/tools/luci-auth/${platform}",
-      "platforms": [
-        "linux-amd64",
-        "linux-arm64",
-        "mac-amd64",
-        "mac-arm64",
-        "windows-amd64"
-      ],
-      "tags": [
-        "latest"
-      ],
-      "version_file": ".versions/luci-auth.cipd_version"
+        "path": "infra/tools/luci-auth/${platform}",
+        "tags": [
+            "latest"
+        ]
     },
     {
-      "path": "infra/tools/luci/gerrit/${platform}",
-      "platforms": [
-        "linux-amd64",
-        "linux-arm64",
-        "mac-amd64",
-        "mac-arm64",
-        "windows-amd64"
-      ],
-      "tags": [
-        "latest"
-      ],
-      "version_file": ".versions/gerrit.cipd_version"
+        "path": "infra/tools/luci/gerrit/${platform}",
+        "tags": [
+            "latest"
+        ]
     },
     {
-      "path": "infra/tools/luci/gitiles/${platform}",
-      "platforms": [
-        "linux-amd64",
-        "linux-arm64",
-        "mac-amd64",
-        "mac-arm64",
-        "windows-amd64"
-      ],
-      "tags": [
-        "latest"
-      ],
-      "version_file": ".versions/gitiles.cipd_version"
+        "path": "infra/tools/luci/gitiles/${platform}",
+        "tags": [
+            "latest"
+        ]
     },
     {
-      "path": "infra/tools/luci/cas/${platform}",
-      "platforms": [
-        "linux-amd64",
-        "linux-arm64",
-        "mac-amd64",
-        "mac-arm64",
-        "windows-amd64"
-      ],
-      "tags": [
-        "latest"
-      ],
-      "version_file": ".versions/cas.cipd_version"
+        "path": "infra/tools/luci/isolated/${platform}",
+        "tags": [
+            "latest"
+        ]
     },
     {
-      "path": "infra/tools/luci/led/${platform}",
-      "platforms": [
-        "linux-amd64",
-        "linux-arm64",
-        "mac-amd64",
-        "mac-arm64",
-        "windows-amd64"
-      ],
-      "tags": [
-        "latest"
-      ],
-      "version_file": ".versions/led.cipd_version"
+        "path": "infra/tools/luci/led/${platform}",
+        "tags": [
+            "latest"
+        ]
     },
     {
-      "path": "infra/tools/luci/logdog/logdog/${platform}",
-      "platforms": [
-        "linux-amd64",
-        "mac-amd64",
-        "mac-arm64",
-        "windows-amd64"
-      ],
-      "tags": [
-        "latest"
-      ],
-      "version_file": ".versions/logdog.cipd_version"
+        "path": "infra/tools/luci/logdog/logdog/${os}-${arch=amd64}",
+        "tags": [
+            "latest"
+        ]
     }
-  ]
-}
+]
diff --git a/pw_env_setup/py/pw_env_setup/cipd_setup/pigweed.json b/pw_env_setup/py/pw_env_setup/cipd_setup/pigweed.json
index 6d89385..264e86d 100644
--- a/pw_env_setup/py/pw_env_setup/cipd_setup/pigweed.json
+++ b/pw_env_setup/py/pw_env_setup/cipd_setup/pigweed.json
@@ -1,213 +1,129 @@
-{
-  "packages": [
-    {
-      "path": "gn/gn/${platform}",
-      "platforms": [
-        "linux-amd64",
-        "linux-arm64",
-        "mac-amd64",
-        "mac-arm64",
-        "windows-amd64"
-      ],
-      "tags": [
-        "git_revision:f27bae882b2178ccc3c24f314c88db9a34118992"
-      ],
-      "version_file": ".versions/gn.cipd_version"
-    },
-    {
-      "path": "infra/3pp/tools/ninja/${platform}",
-      "platforms": [
-        "linux-amd64",
-        "linux-arm64",
-        "mac-amd64",
-        "mac-arm64",
-        "windows-amd64"
-      ],
-      "tags": [
-        "version:2@1.10.2"
-      ]
-    },
-    {
-      "path": "fuchsia/third_party/cmake/${platform}",
-      "platforms": [
-        "mac-amd64",
-        "linux-amd64",
-        "linux-arm64",
-        "windows-amd64"
-      ],
-      "tags": [
-        "version:3.23.20220402-g6733ad4"
-      ],
-      "version_file": ".versions/cmake.cipd_version"
-    },
-    {
-      "path": "pigweed/third_party/bloaty-embedded/${platform}",
-      "platforms": [
-        "linux-amd64",
-        "mac-amd64"
-      ],
-      "tags": [
-        "git_revision:2d87d204057b419f5290f8d38b61b9c2c5b4fb52-2"
-      ],
-      "version_file": ".versions/bloaty-embedded.cipd_version"
-    },
-    {
-      "path": "infra/3pp/tools/protoc/${platform}",
-      "platforms": [
-        "linux-amd64",
-        "linux-arm64",
-        "mac-amd64",
-        "windows-amd64"
-      ],
-      "tags": [
-        "version:2@3.17.3"
-      ]
-    },
-    {
-      "_comment": "Until there's an arm64 Mac version, get the amd64 Mac version on arm64 Macs.",
-      "path": "infra/3pp/tools/protoc/mac-amd64",
-      "platforms": [
-        "mac-arm64"
-      ],
-      "tags": [
-        "version:2@3.17.3"
-      ]
-    },
-    {
-      "path": "fuchsia/third_party/clang/${platform}",
-      "platforms": [
-        "linux-amd64",
-        "linux-arm64",
-        "mac-amd64",
-        "windows-amd64"
-      ],
-      "tags": [
-        "git_revision:1aa59ff2f789776ebfa2d4b315fd3ea589652b4a"
-      ],
-      "version_file": ".versions/clang.cipd_version"
-    },
-    {
-      "path": "infra/3pp/tools/go/${platform}",
-      "platforms": [
-        "linux-amd64",
-        "linux-arm64",
-        "mac-amd64",
-        "mac-arm64",
-        "windows-amd64"
-      ],
-      "tags": [
-        "version:2@1.18"
-      ]
-    },
-    {
-      "path": "pigweed/third_party/protoc-gen-go/${platform}",
-      "platforms": [
-        "linux-amd64",
-        "mac-amd64",
-        "windows-amd64"
-      ],
-      "tags": [
-        "version:1.3.2"
-      ]
-    },
-    {
-      "path": "infra/3pp/tools/openocd/${platform}",
-      "platforms": [
-        "linux-amd64",
-        "linux-arm64",
-        "mac-amd64",
-        "mac-arm64",
-        "windows-amd64"
-      ],
-      "tags": [
-        "version:2@0.11.0-3"
-      ]
-    },
-    {
-      "path": "pigweed/third_party/mingw64-x86_64-win32-seh/${platform}",
-      "platforms": [
-        "windows-amd64"
-      ],
-      "tags": [
-        "version:10.2.0-11"
-      ]
-    },
-    {
-      "path": "pigweed/host_tools/${platform}",
-      "platforms": [
-        "linux-amd64",
-        "mac-amd64",
-        "windows-amd64"
-      ],
-      "tags": [
-        "git_revision:3f30a2e1ac848bd3dde1322ee2ace4d4f935c29d"
-      ],
-      "version_file": ".versions/host_tools.cipd_version"
-    },
-    {
-      "path": "infra/rbe/client/${platform}",
-      "platforms": [
-        "linux-amd64",
-        "windows-amd64"
-      ],
-      "tags": [
-        "re_client_version:0.41.4.3f0d8bb"
-      ]
-    },
-    {
-      "path": "fuchsia/third_party/qemu/${platform}",
-      "platforms": [
-        "linux-amd64",
-        "linux-arm64",
-        "mac-amd64"
-      ],
-      "tags": [
-        "git_revision:44f28df24767cf9dca1ddc9b23157737c4cbb645"
-      ],
-      "version_file": ".versions/qemu.cipd_version"
-    },
-    {
-      "path": "fuchsia/third_party/kythe",
-      "platforms": [
-        "linux-amd64"
-      ],
-      "subdir": "kythe",
-      "tags": [
-        "version:1.0.3"
-      ]
-    },
-    {
-      "path": "fuchsia/third_party/kythe-libs/${platform}",
-      "platforms": [
-        "linux-amd64"
-      ],
-      "subdir": "kythe",
-      "tags": [
-        "version:2020-08-05"
-      ]
-    },
-    {
-      "path": "infra/3pp/tools/renode/${platform}",
-      "platforms": [
-        "linux-amd64",
-        "linux-arm64"
-      ],
-      "tags": [
-        "version:2@renode-1.11.0+20210306gite7897c1"
-      ]
-    },
-    {
-      "path": "infra/3pp/tools/buildifier/${platform}",
-      "platforms": [
-        "linux-amd64",
-        "linux-arm64",
-        "mac-amd64",
-        "mac-arm64",
-        "windows-amd64"
-      ],
-      "tags": [
-        "version:2@5.0.1"
-      ]
-    }
-  ]
-}
+[
+  {
+    "path": "gn/gn/${os}-${arch=amd64,arm64}",
+    "tags": [
+      "git_revision:b2e3d8622c1ce1bd853c7a11f62a739946669cdd"
+    ]
+  },
+  {
+    "path": "infra/ninja/${os}-${arch=amd64}",
+    "tags": [
+      "version:1.9.0"
+    ]
+  },
+  {
+    "path": "infra/cmake/${os}-${arch=amd64}",
+    "tags": [
+      "version:3.16.1"
+    ]
+  },
+  {
+    "_comment": "TODO(pwbug/93): Package Bazel for Windows.",
+    "path": "fuchsia/third_party/bazel/${os=linux,mac}-${arch=amd64}",
+    "tags": [
+      "version:4.0.0"
+    ]
+  },
+  {
+    "path": "pigweed/third_party/bloaty-embedded/${os=linux,mac}-${arch=amd64}",
+    "tags": [
+      "git_revision:2d87d204057b419f5290f8d38b61b9c2c5b4fb52-2"
+    ]
+  },
+  {
+    "path": "pigweed/third_party/gcc-arm-none-eabi/${os}-${arch=amd64}",
+    "tags": [
+      "version:9-2020-q2-update"
+    ]
+  },
+  {
+    "path": "infra/3pp/tools/protoc/${os}-${arch}",
+    "tags": [
+      "version:3.14.0"
+    ]
+  },
+  {
+    "_comment": "TODO(pwbug/70) Put clang in cipd for Windows.",
+    "path": "fuchsia/third_party/clang/${os=linux,mac}-${arch}",
+    "tags": [
+      "git_revision:8af160b0b8ca8102b9490a287244af75727872f5"
+    ]
+  },
+  {
+    "_comment": "When bumping the minor version (e.g., to 3.9.x) also update env_setup/virtualenv/init.py to check for the new version.",
+    "path": "infra/3pp/tools/cpython3/${os}-${arch}",
+    "tags": [
+      "version:3.8.2.chromium.10"
+    ]
+  },
+  {
+    "path": "infra/go/${os}-${arch=amd64}",
+    "tags": [
+      "version:1.13.5"
+    ]
+  },
+  {
+    "path": "pigweed/third_party/protoc-gen-go/${os}-${arch=amd64}",
+    "tags": [
+      "version:1.3.2"
+    ]
+  },
+  {
+    "_comment": "TODO(pwbug/66) Put openocd in cipd for Windows.",
+    "path": "pigweed/third_party/openocd/${os=linux,mac}-${arch=amd64}",
+    "tags": [
+      "git_revision:e41c0f4906e46d1076ce62a0da5518aa1ca280b8"
+    ]
+  },
+  {
+    "_comment": "TODO(pwbug/69) Put rust in cipd for Windows.",
+    "path": "fuchsia/rust/${os=linux,mac}-${arch=amd64}",
+    "tags": [
+      "git_revision:027149919e36ce5645ca5d02d55b97ef52eb55ba"
+    ]
+  },
+  {
+    "path": "pigweed/third_party/mingw64-x86_64-win32-seh/${os=windows}-${arch=amd64}",
+    "tags": [
+      "version:8.1.0"
+    ]
+  },
+  {
+    "path": "pigweed/host_tools/${os}-${arch=amd64}",
+    "tags": [
+      "git_revision:2f56efcee3192685898200f6049538ebd809ae36"
+    ]
+  },
+  {
+    "path": "infra/rbe/client/${os=linux,windows}-${arch=amd64}",
+    "tags": [
+      "git_revision:bbfff8b0a8701cebd503d961c99e9587605b19e2"
+    ]
+  },
+  {
+    "path": "fuchsia/third_party/qemu/${os=linux,mac}-${arch}",
+    "tags": [
+      "git_revision:841f14e74f5af7886cf49cfcd4fed264911ae58e"
+    ]
+  },
+  {
+    "path": "fuchsia/third_party/kythe",
+    "subdir": "kythe",
+    "tags": [
+      "version:1.0.1"
+    ]
+  },
+  {
+    "path": "fuchsia/third_party/kythe-libs/${os=linux}-${arch=amd64}",
+    "subdir": "kythe",
+    "tags": [
+      "version:2020-08-05"
+    ]
+  },
+  {
+    "path": "infra/3pp/tools/renode/${os=linux}-${arch=amd64}",
+    "tags": [
+      "version:2@renode-1.11.0+20210306gite7897c1"
+    ]
+  }
+]
diff --git a/pw_env_setup/py/pw_env_setup/cipd_setup/python.json b/pw_env_setup/py/pw_env_setup/cipd_setup/python.json
deleted file mode 100644
index 210e597..0000000
--- a/pw_env_setup/py/pw_env_setup/cipd_setup/python.json
+++ /dev/null
@@ -1,29 +0,0 @@
-{
-  "packages": [
-    {
-      "_comment": "When bumping the minor version (e.g., to 3.9.x) also update env_setup/virtualenv/init.py to check for the new version.",
-      "path": "infra/3pp/tools/cpython3/${platform}",
-      "platforms": [
-        "linux-amd64",
-        "linux-arm64",
-        "mac-amd64",
-        "windows-amd64"
-      ],
-      "tags": [
-        "version:2@3.9.5.chromium.19"
-      ],
-      "version_file": ".versions/cpython3.cipd_version"
-    },
-    {
-      "_comment": "TODO(pwbug/455) Use 3.9 for Macs too.",
-      "path": "infra/3pp/tools/cpython3/${platform}",
-      "platforms": [
-        "mac-arm64"
-      ],
-      "tags": [
-        "version:2@3.8.10.chromium.21"
-      ],
-      "version_file": ".versions/cpython3.cipd_version"
-    }
-  ]
-}
diff --git a/pw_env_setup/py/pw_env_setup/cipd_setup/update.py b/pw_env_setup/py/pw_env_setup/cipd_setup/update.py
index eb2b835..c4301ec 100755
--- a/pw_env_setup/py/pw_env_setup/cipd_setup/update.py
+++ b/pw_env_setup/py/pw_env_setup/cipd_setup/update.py
@@ -21,13 +21,44 @@
 
 from __future__ import print_function
 
-import hashlib
+import argparse
 import json
 import os
-import platform as platform_module
 import re
+import shutil
 import subprocess
 import sys
+import tempfile
+
+
+def parse(argv=None):
+    """Parse arguments."""
+
+    script_root = os.path.join(os.environ['PW_ROOT'], 'pw_env_setup', 'py',
+                               'pw_env_setup', 'cipd_setup')
+    git_root = subprocess.check_output(
+        ('git', 'rev-parse', '--show-toplevel'),
+        cwd=script_root,
+    ).decode('utf-8').strip()
+
+    parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
+    parser.add_argument(
+        '--install-dir',
+        dest='root_install_dir',
+        default=os.path.join(git_root, '.cipd'),
+    )
+    parser.add_argument('--package-file',
+                        dest='package_files',
+                        metavar='PACKAGE_FILE',
+                        action='append')
+    parser.add_argument('--cipd',
+                        default=os.path.join(script_root, 'wrapper.py'))
+    parser.add_argument('--cache-dir',
+                        default=os.environ.get(
+                            'CIPD_CACHE_DIR',
+                            os.path.expanduser('~/.cipd-cache-dir')))
+
+    return parser.parse_args(argv)
 
 
 def check_auth(cipd, package_files, spin):
@@ -38,7 +69,7 @@
         with open(package_file, 'r') as ins:
             # This is an expensive RPC, so only check the first few entries
             # in each file.
-            for i, entry in enumerate(json.load(ins).get('packages', ())):
+            for i, entry in enumerate(json.load(ins)):
                 if i >= 3:
                     break
                 parts = entry['path'].split('/')
@@ -88,8 +119,7 @@
         with spin.pause():
             stderr = lambda *args: print(*args, file=sys.stderr)
             stderr()
-            stderr('Not logged in to CIPD and no anonymous access to the '
-                   'following CIPD paths:')
+            stderr('No access to the following CIPD paths:')
             for path in inaccessible_paths:
                 stderr('  {}'.format(path))
             stderr()
@@ -118,108 +148,23 @@
     return True
 
 
-def platform():
-    osname = {
-        'darwin': 'mac',
-        'linux': 'linux',
-        'windows': 'windows',
-    }[platform_module.system().lower()]
+def write_ensure_file(package_file, ensure_file):
+    with open(package_file, 'r') as ins:
+        data = json.load(ins)
 
-    if platform_module.machine().startswith(('aarch64', 'armv8')):
-        arch = 'arm64'
-    elif platform_module.machine() == 'x86_64':
-        arch = 'amd64'
-    elif platform_module.machine() == 'i686':
-        arch = 'i386'
-    else:
-        arch = platform_module.machine()
-
-    platform_arch = '{}-{}'.format(osname, arch).lower()
-
-    # Support `mac-arm64` through Rosetta until `mac-arm64` binaries are ready
-    if platform_arch == 'mac-arm64':
-        return 'mac-amd64'
-
-    return platform_arch
-
-
-def all_package_files(env_vars, package_files):
-    """Recursively retrieve all package files."""
-
-    result = []
-    to_process = []
-    for pkg_file in package_files:
-        args = []
-        if env_vars:
-            args.append(env_vars.get('PW_PROJECT_ROOT'))
-        args.append(pkg_file)
-
-        # The signature here is os.path.join(a, *p). Pylint doesn't like when
-        # we call os.path.join(*args), but is happy if we instead call
-        # os.path.join(args[0], *args[1:]). Disabling the option on this line
-        # seems to be a less confusing choice.
-        path = os.path.join(*args)  # pylint: disable=no-value-for-parameter
-
-        to_process.append(path)
-
-    while to_process:
-        package_file = to_process.pop(0)
-        result.append(package_file)
-
-        with open(package_file, 'r') as ins:
-            entries = json.load(ins).get('included_files', ())
-
-        for entry in entries:
-            entry = os.path.join(os.path.dirname(package_file), entry)
-
-            if entry not in result and entry not in to_process:
-                to_process.append(entry)
-
-    return result
-
-
-def write_ensure_file(package_files, ensure_file):
-    packages = []
-
-    for package_file in package_files:
-        name = package_file_name(package_file)
-        with open(package_file, 'r') as ins:
-            file_packages = json.load(ins).get('packages', ())
-            for package in file_packages:
-                if 'subdir' in package:
-                    package['subdir'] = os.path.join(name, package['subdir'])
-                else:
-                    package['subdir'] = name
-            packages.extend(file_packages)
+    # TODO(pwbug/103) Remove 30 days after bug fixed.
+    if os.path.isdir(ensure_file):
+        shutil.rmtree(ensure_file)
 
     with open(ensure_file, 'w') as outs:
         outs.write('$VerifiedPlatform linux-amd64\n'
                    '$VerifiedPlatform mac-amd64\n'
                    '$ParanoidMode CheckPresence\n')
 
-        for pkg in packages:
-            # If this is a new-style package manifest platform handling must
-            # be done here instead of by the cipd executable.
-            if 'platforms' in pkg and platform() not in pkg['platforms']:
-                continue
-
-            outs.write('@Subdir {}\n'.format(pkg.get('subdir', '')))
-            outs.write('{} {}\n'.format(pkg['path'], ' '.join(pkg['tags'])))
-
-
-def package_file_name(package_file):
-    return os.path.basename(os.path.splitext(package_file)[0])
-
-
-def package_installation_path(root_install_dir, package_file):
-    """Returns the package installation path.
-
-    Args:
-      root_install_dir: The CIPD installation directory.
-      package_file: The path to the .json package definition file.
-    """
-    return os.path.join(root_install_dir, 'packages',
-                        package_file_name(package_file))
+        for entry in data:
+            outs.write('@Subdir {}\n'.format(entry.get('subdir', '')))
+            outs.write('{} {}\n'.format(entry['path'],
+                                        ' '.join(entry['tags'])))
 
 
 def update(
@@ -229,114 +174,84 @@
     cache_dir,
     env_vars=None,
     spin=None,
-    trust_hash=False,
 ):
     """Grab the tools listed in ensure_files."""
 
-    package_files = all_package_files(env_vars, package_files)
+    if not check_auth(cipd, package_files, spin):
+        return False
 
     # TODO(mohrr) use os.makedirs(..., exist_ok=True).
     if not os.path.isdir(root_install_dir):
         os.makedirs(root_install_dir)
 
-    # This file is read by 'pw doctor' which needs to know which package files
-    # were used in the environment.
-    package_files_file = os.path.join(root_install_dir,
-                                      '_all_package_files.json')
-    with open(package_files_file, 'w') as outs:
-        json.dump(package_files, outs, indent=2)
-
     if env_vars:
         env_vars.prepend('PATH', root_install_dir)
         env_vars.set('PW_CIPD_INSTALL_DIR', root_install_dir)
         env_vars.set('CIPD_CACHE_DIR', cache_dir)
 
     pw_root = None
-
     if env_vars:
         pw_root = env_vars.get('PW_ROOT', None)
     if not pw_root:
         pw_root = os.environ['PW_ROOT']
 
-    ensure_file = os.path.join(root_install_dir, 'packages.ensure')
-    write_ensure_file(package_files, ensure_file)
+    # Run cipd for each json file.
+    for package_file in package_files:
+        if os.path.splitext(package_file)[1] == '.ensure':
+            ensure_file = package_file
+        else:
+            ensure_file = os.path.join(
+                root_install_dir,
+                os.path.basename(
+                    os.path.splitext(package_file)[0] + '.ensure'))
+            write_ensure_file(package_file, ensure_file)
 
-    install_dir = os.path.join(root_install_dir, 'packages')
+        install_dir = os.path.join(
+            root_install_dir,
+            os.path.basename(os.path.splitext(package_file)[0]))
 
-    cmd = [
-        cipd,
-        'ensure',
-        '-ensure-file', ensure_file,
-        '-root', install_dir,
-        '-log-level', 'debug',
-        '-json-output', os.path.join(root_install_dir, 'packages.json'),
-        '-cache-dir', cache_dir,
-        '-max-threads', '0',  # 0 means use CPU count.
-    ]  # yapf: disable
+        cmd = [
+            cipd,
+            'ensure',
+            '-ensure-file', ensure_file,
+            '-root', install_dir,
+            '-log-level', 'warning',
+            '-cache-dir', cache_dir,
+            '-max-threads', '0',  # 0 means use CPU count.
+        ]  # yapf: disable
 
-    hasher = hashlib.sha256()
-    encoded = '\0'.join(cmd)
-    if hasattr(encoded, 'encode'):
-        encoded = encoded.encode()
-    hasher.update(encoded)
-    with open(ensure_file, 'rb') as ins:
-        hasher.update(ins.read())
-    digest = hasher.hexdigest()
+        # TODO(pwbug/135) Use function from common utility module.
+        with tempfile.TemporaryFile(mode='w+') as temp:
+            print(*cmd, file=temp)
+            try:
+                subprocess.check_call(cmd,
+                                      stdout=temp,
+                                      stderr=subprocess.STDOUT)
+            except subprocess.CalledProcessError:
+                temp.seek(0)
+                sys.stderr.write(temp.read())
+                raise
 
-    with open(os.path.join(root_install_dir, 'hash.log'), 'w') as hashlog:
-        print('calculated digest:', digest, file=hashlog)
+        # Set environment variables so tools can later find things under, for
+        # example, 'share'.
+        name = os.path.basename(install_dir)
 
-        hash_file = os.path.join(root_install_dir, 'packages.sha256')
-        print('hash file path:', hash_file, file=hashlog)
-        print('exists:', os.path.isfile(hash_file), file=hashlog)
-        print('trust_hash:', trust_hash, file=hashlog)
-        if trust_hash and os.path.isfile(hash_file):
-            with open(hash_file, 'r') as ins:
-                digest_file = ins.read().strip()
-                print('contents:', digest_file, file=hashlog)
-                print('equal:', digest == digest_file, file=hashlog)
-                if digest == digest_file:
-                    return True
-
-    if not check_auth(cipd, package_files, spin):
-        return False
-
-    # TODO(pwbug/135) Use function from common utility module.
-    log = os.path.join(root_install_dir, 'packages.log')
-    try:
-        with open(log, 'w') as outs:
-            print(*cmd, file=outs)
-            subprocess.check_call(cmd, stdout=outs, stderr=subprocess.STDOUT)
-    except subprocess.CalledProcessError:
-        with open(log, 'r') as ins:
-            sys.stderr.write(ins.read())
-            raise
-
-    with open(hash_file, 'w') as outs:
-        print(digest, file=outs)
-
-    # Set environment variables so tools can later find things under, for
-    # example, 'share'.
-    if env_vars:
-        for package_file in package_files:
-            name = package_file_name(package_file)
-            file_install_dir = os.path.join(install_dir, name)
+        if env_vars:
             # Some executables get installed at top-level and some get
-            # installed under 'bin'. A small number of old packages prefix the
-            # entire tree with the platform (e.g., chromium/third_party/tcl).
-            for bin_dir in (
-                    file_install_dir,
-                    os.path.join(file_install_dir, 'bin'),
-                    os.path.join(file_install_dir, platform(), 'bin'),
-            ):
-                if os.path.isdir(bin_dir):
-                    env_vars.prepend('PATH', bin_dir)
+            # installed under 'bin'.
+            env_vars.prepend('PATH', install_dir)
+            env_vars.prepend('PATH', os.path.join(install_dir, 'bin'))
             env_vars.set('PW_{}_CIPD_INSTALL_DIR'.format(name.upper()),
-                         file_install_dir)
+                         install_dir)
 
             # Windows has its own special toolchain.
             if os.name == 'nt':
-                env_vars.prepend(
-                    'PATH', os.path.join(file_install_dir, 'mingw64', 'bin'))
+                env_vars.prepend('PATH',
+                                 os.path.join(install_dir, 'mingw64', 'bin'))
 
     return True
+
+
+if __name__ == '__main__':
+    update(**vars(parse()))
+    sys.exit(0)
diff --git a/pw_env_setup/py/pw_env_setup/cipd_setup/wrapper.py b/pw_env_setup/py/pw_env_setup/cipd_setup/wrapper.py
index 6ca7db3..61843fe 100755
--- a/pw_env_setup/py/pw_env_setup/cipd_setup/wrapper.py
+++ b/pw_env_setup/py/pw_env_setup/cipd_setup/wrapper.py
@@ -122,10 +122,7 @@
 
     machine = platform.machine()
     if machine.startswith(('arm', 'aarch')):
-        machine = machine.replace('aarch', 'arm')
-        if machine == 'arm64':
-            return machine
-        return 'armv6l'
+        return machine.replace('aarch', 'arm')
     if machine.endswith('64'):
         return 'amd64'
     if machine.endswith('86'):
@@ -133,16 +130,6 @@
     raise Exception('unrecognized arch: {}'.format(machine))
 
 
-def platform_arch_normalized():
-    platform_arch = '{}-{}'.format(platform_normalized(), arch_normalized())
-
-    # Support `mac-arm64` through Rosetta until `mac-arm64` binaries are ready
-    if platform_arch == 'mac-arm64':
-        platform_arch = 'mac-amd64'
-
-    return platform_arch
-
-
 def user_agent():
     """Generate a user-agent based on the project name and current hash."""
 
@@ -170,7 +157,7 @@
 def expected_hash():
     """Pulls expected hash from digests file."""
 
-    expected_plat = platform_arch_normalized()
+    expected_plat = '{}-{}'.format(platform_normalized(), arch_normalized())
 
     with open(DIGESTS_FILE, 'r') as ins:
         for line in ins:
@@ -233,7 +220,7 @@
         print('=' * 70)
         raise
 
-    full_platform = platform_arch_normalized()
+    full_platform = '{}-{}'.format(platform_normalized(), arch_normalized())
     if full_platform not in SUPPORTED_PLATFORMS:
         raise UnsupportedPlatform(full_platform)
 
@@ -254,21 +241,12 @@
                 '\n'
                 '    sudo pip install certifi\n'
                 '\n'
-                'And if on the system Python on a Mac try\n'
-                '\n'
-                '    /Applications/Python 3.6/Install Certificates.command\n'
-                '\n'
                 'If using Homebrew Python try\n'
                 '\n'
                 '    brew install openssl\n'
                 '    brew uninstall python\n'
                 '    brew install python\n'
                 '\n'
-                "If those don't work, address all the potential issues shown \n"
-                'by the following command.\n'
-                '\n'
-                '    brew doctor\n'
-                '\n'
                 "Otherwise, check that your machine's Python can use SSL, "
                 'testing with the httplib module on Python 2 or http.client on '
                 'Python 3.',
@@ -303,8 +281,8 @@
         os.makedirs(client_dir)
 
     if not silent:
-        print('Bootstrapping cipd client for {}'.format(
-            platform_arch_normalized()))
+        print('Bootstrapping cipd client for {}-{}'.format(
+            platform_normalized(), arch_normalized()))
 
     tmp_path = client + '.tmp'
     with open(tmp_path, 'wb') as tmp:
diff --git a/pw_env_setup/py/pw_env_setup/env_setup.py b/pw_env_setup/py/pw_env_setup/env_setup.py
index d742e4f..794086c 100755
--- a/pw_env_setup/py/pw_env_setup/env_setup.py
+++ b/pw_env_setup/py/pw_env_setup/env_setup.py
@@ -33,7 +33,6 @@
 import shutil
 import subprocess
 import sys
-import time
 
 # TODO(pwbug/67): Remove import hacks once the oxidized prebuilt binaries are
 # proven stable for first-time bootstrapping. For now, continue to support
@@ -70,6 +69,7 @@
 from pw_env_setup.cipd_setup import update as cipd_update
 from pw_env_setup.cipd_setup import wrapper as cipd_wrapper
 from pw_env_setup.colors import Color, enable_colors
+from pw_env_setup import cargo_setup
 from pw_env_setup import environment
 from pw_env_setup import spinner
 from pw_env_setup import virtualenv_setup
@@ -130,27 +130,36 @@
     def ok(self):
         return self._status in {_Result.Status.DONE, _Result.Status.SKIPPED}
 
-    def status_str(self, duration=None):
-        if not duration:
-            return self._status
-
-        duration_parts = []
-        if duration > 60:
-            minutes = int(duration // 60)
-            duration %= 60
-            duration_parts.append('{}m'.format(minutes))
-        duration_parts.append('{:.1f}s'.format(duration))
-        return '{} ({})'.format(self._status, ''.join(duration_parts))
+    def status_str(self):
+        return self._status
 
     def messages(self):
         return self._messages
 
 
-class ConfigError(Exception):
-    pass
+def _process_globs(globs):
+    unique_globs = []
+    for pat in globs:
+        if pat and pat not in unique_globs:
+            unique_globs.append(pat)
+
+    files = []
+    warnings = []
+    for pat in unique_globs:
+        if pat:
+            matches = glob.glob(pat)
+            if not matches:
+                warnings.append(
+                    'warning: pattern "{}" matched 0 files'.format(pat))
+            files.extend(matches)
+
+    if globs and not files:
+        warnings.append('warning: matched 0 total files')
+
+    return files, warnings
 
 
-def result_func(glob_warnings=()):
+def result_func(glob_warnings):
     def result(status, *args):
         return _Result(status, *([str(x) for x in glob_warnings] + list(args)))
 
@@ -161,10 +170,6 @@
     pass
 
 
-class MissingSubmodulesError(Exception):
-    pass
-
-
 # TODO(mohrr) remove disable=useless-object-inheritance once in Python 3.
 # pylint: disable=useless-object-inheritance
 # pylint: disable=too-many-instance-attributes
@@ -172,9 +177,10 @@
 class EnvSetup(object):
     """Run environment setup for Pigweed."""
     def __init__(self, pw_root, cipd_cache_dir, shell_file, quiet, install_dir,
-                 virtualenv_root, strict, virtualenv_gn_out_dir, json_file,
-                 project_root, config_file, use_existing_cipd,
-                 use_pinned_pip_packages, cipd_only, trust_cipd_hash):
+                 use_pigweed_defaults, cipd_package_file, virtualenv_root,
+                 virtualenv_requirements, virtualenv_gn_target,
+                 virtualenv_gn_out_dir, cargo_package_file, enable_cargo,
+                 json_file, project_root, config_file):
         self._env = environment.Environment()
         self._project_root = project_root
         self._pw_root = pw_root
@@ -187,9 +193,6 @@
         self._install_dir = install_dir
         self._virtualenv_root = (virtualenv_root
                                  or os.path.join(install_dir, 'pigweed-venv'))
-        self._strict = strict
-        self._cipd_only = cipd_only
-        self._trust_cipd_hash = trust_cipd_hash
 
         if os.path.isfile(shell_file):
             os.unlink(shell_file)
@@ -200,87 +203,55 @@
         self._cipd_package_file = []
         self._virtualenv_requirements = []
         self._virtualenv_gn_targets = []
-        self._virtualenv_gn_args = []
-        self._use_pinned_pip_packages = use_pinned_pip_packages
-        self._optional_submodules = []
-        self._required_submodules = []
-        self._virtualenv_system_packages = False
-        self._pw_packages = []
-        self._root_variable = None
+        self._cargo_package_file = []
+        self._enable_cargo = enable_cargo
 
-        self._json_file = json_file
-        self._gni_file = None
-
-        self._config_file_name = getattr(config_file, 'name', 'config file')
-        self._env.set('_PW_ENVIRONMENT_CONFIG_FILE', self._config_file_name)
         if config_file:
             self._parse_config_file(config_file)
 
-        self._check_submodules()
+        self._json_file = json_file
 
-        self._use_existing_cipd = use_existing_cipd
+        setup_root = os.path.join(pw_root, 'pw_env_setup', 'py',
+                                  'pw_env_setup')
+
+        # TODO(pwbug/67, pwbug/68) Investigate pulling these files into an
+        # oxidized env setup executable instead of referring to them in the
+        # source tree. Note that this could be error-prone because users expect
+        # changes to the files in the source tree to affect bootstrap.
+        if use_pigweed_defaults:
+            # If updating this section make sure to update
+            # $PW_ROOT/pw_env_setup/docs.rst as well.
+            self._cipd_package_file.append(
+                os.path.join(setup_root, 'cipd_setup', 'pigweed.json'))
+            self._cipd_package_file.append(
+                os.path.join(setup_root, 'cipd_setup', 'luci.json'))
+            # Only set if no other GN target is provided.
+            if not virtualenv_gn_target:
+                self._virtualenv_gn_targets.append(
+                    virtualenv_setup.GnTarget(
+                        '{}#pw_env_setup:python.install'.format(pw_root)))
+            self._cargo_package_file.append(
+                os.path.join(setup_root, 'cargo_setup', 'packages.txt'))
+
+        self._cipd_package_file.extend(cipd_package_file)
+        self._virtualenv_requirements.extend(virtualenv_requirements)
+        self._virtualenv_gn_targets.extend(virtualenv_gn_target)
         self._virtualenv_gn_out_dir = virtualenv_gn_out_dir
+        self._cargo_package_file.extend(cargo_package_file)
 
-        if self._root_variable:
-            self._env.set(self._root_variable, project_root, deactivate=False)
-        self._env.set('PW_PROJECT_ROOT', project_root, deactivate=False)
-        self._env.set('PW_ROOT', pw_root, deactivate=False)
+        self._env.set('PW_PROJECT_ROOT', project_root)
+        self._env.set('PW_ROOT', pw_root)
         self._env.set('_PW_ACTUAL_ENVIRONMENT_ROOT', install_dir)
         self._env.add_replacement('_PW_ACTUAL_ENVIRONMENT_ROOT', install_dir)
         self._env.add_replacement('PW_ROOT', pw_root)
 
-    def _process_globs(self, globs):
-        unique_globs = []
-        for pat in globs:
-            if pat and pat not in unique_globs:
-                unique_globs.append(pat)
-
-        files = []
-        warnings = []
-        for pat in unique_globs:
-            if pat:
-                matches = glob.glob(pat)
-                if not matches:
-                    warning = 'pattern "{}" matched 0 files'.format(pat)
-                    warnings.append('warning: {}'.format(warning))
-                    if self._strict:
-                        raise ConfigError(warning)
-
-                files.extend(matches)
-
-        if globs and not files:
-            warnings.append('warning: matched 0 total files')
-            if self._strict:
-                raise ConfigError('matched 0 total files')
-
-        return files, warnings
-
     def _parse_config_file(self, config_file):
         config = json.load(config_file)
 
-        self._root_variable = config.pop('root_variable', None)
-
-        if 'json_file' in config:
-            self._json_file = config.pop('json_file')
-
-        self._gni_file = config.pop('gni_file', None)
-
-        self._optional_submodules.extend(config.pop('optional_submodules', ()))
-        self._required_submodules.extend(config.pop('required_submodules', ()))
-
-        if self._optional_submodules and self._required_submodules:
-            raise ValueError(
-                '{} contains both "optional_submodules" and '
-                '"required_submodules", but these options are mutually '
-                'exclusive'.format(self._config_file_name))
-
         self._cipd_package_file.extend(
             os.path.join(self._project_root, x)
             for x in config.pop('cipd_package_files', ()))
 
-        for pkg in config.pop('pw_packages', ()):
-            self._pw_packages.append(pkg)
-
         virtualenv = config.pop('virtualenv', {})
 
         if virtualenv.get('gn_root'):
@@ -292,80 +263,14 @@
             self._virtualenv_gn_targets.append(
                 virtualenv_setup.GnTarget('{}#{}'.format(root, target)))
 
-        self._virtualenv_gn_args = virtualenv.pop('gn_args', ())
-
-        self._virtualenv_system_packages = virtualenv.pop(
-            'system_packages', False)
-
         if virtualenv:
             raise ConfigFileError(
                 'unrecognized option in {}: "virtualenv.{}"'.format(
-                    self._config_file_name, next(iter(virtualenv))))
+                    config_file.name, next(iter(virtualenv))))
 
         if config:
             raise ConfigFileError('unrecognized option in {}: "{}"'.format(
-                self._config_file_name, next(iter(config))))
-
-    def _check_submodules(self):
-        unitialized = set()
-
-        # Don't check submodule presence if using the Android Repo Tool.
-        if os.path.isdir(os.path.join(self._project_root, '.repo')):
-            return
-
-        cmd = ['git', 'submodule', 'status', '--recursive']
-
-        for line in subprocess.check_output(
-                cmd, cwd=self._project_root).splitlines():
-            if isinstance(line, bytes):
-                line = line.decode()
-            # Anything but an initial '-' means the submodule is initialized.
-            if not line.startswith('-'):
-                continue
-            unitialized.add(line.split()[1])
-
-        missing = unitialized - set(self._optional_submodules)
-        if self._required_submodules:
-            missing = set(self._required_submodules) & unitialized
-
-        if missing:
-            print(
-                'Not all submodules are initialized. Please run the '
-                'following commands.',
-                file=sys.stderr)
-            print('', file=sys.stderr)
-
-            for miss in missing:
-                print('    git submodule update --init {}'.format(miss),
-                      file=sys.stderr)
-            print('', file=sys.stderr)
-
-            if self._required_submodules:
-                print(
-                    'If these submodules are not required, remove them from '
-                    'the "required_submodules"',
-                    file=sys.stderr)
-
-            else:
-                print(
-                    'If these submodules are not required, add them to the '
-                    '"optional_submodules"',
-                    file=sys.stderr)
-
-            print('list in the environment config JSON file:', file=sys.stderr)
-            print('    {}'.format(self._config_file_name), file=sys.stderr)
-            print('', file=sys.stderr)
-
-            raise MissingSubmodulesError(', '.join(sorted(missing)))
-
-    def _write_gni_file(self):
-        gni_file = os.path.join(self._project_root, 'build_overrides',
-                                'pigweed_environment.gni')
-        if self._gni_file:
-            gni_file = os.path.join(self._project_root, self._gni_file)
-
-        with open(gni_file, 'w') as outs:
-            self._env.gni(outs, self._project_root)
+                config_file.name, next(iter(config))))
 
     def _log(self, *args, **kwargs):
         # Not using logging module because it's awkward to flush a log handler.
@@ -387,16 +292,16 @@
         steps = [
             ('CIPD package manager', self.cipd),
             ('Python environment', self.virtualenv),
-            ('pw packages', self.pw_package),
             ('Host tools', self.host_tools),
         ]
 
+        # TODO(pwbug/63): Add a Windows version of cargo to CIPD.
+        if not self._is_windows and self._enable_cargo:
+            steps.append(("Rust cargo", self.cargo))
+
         if self._is_windows:
             steps.append(("Windows scripts", self.win_scripts))
 
-        if self._cipd_only:
-            steps = [('CIPD package manager', self.cipd)]
-
         self._log(
             Color.bold('Downloading and installing packages into local '
                        'source directory:\n'))
@@ -432,13 +337,11 @@
                 newline=False,
             )
 
-            start = time.time()
-            spin = spinner.Spinner(self._quiet)
+            spin = spinner.Spinner()
             with spin():
                 result = step(spin)
-            stop = time.time()
 
-            self._log(result.status_str(stop - start))
+            self._log(result.status_str())
 
             self._env.echo(result.status_str())
             for message in result.messages():
@@ -448,21 +351,6 @@
             if not result.ok():
                 return -1
 
-            # Log the environment state at the end of each step for debugging.
-            log_dir = os.path.join(self._install_dir, 'logs')
-            if not os.path.isdir(log_dir):
-                os.makedirs(log_dir)
-            actions_json = os.path.join(
-                log_dir, 'post-{}.json'.format(name.replace(' ', '_')))
-            with open(actions_json, 'w') as outs:
-                self._env.json(outs)
-
-            # This file needs to be written after the CIPD step and before the
-            # Python virtualenv step. It also needs to be rewritten after the
-            # Python virtualenv step, so it's easiest to just write it after
-            # every step.
-            self._write_gni_file()
-
         self._log('')
         self._env.echo('')
 
@@ -478,10 +366,6 @@
             Color.bold('Environment looks good, you are ready to go!'))
         self._env.echo()
 
-        # Don't write new files if all we did was update CIPD packages.
-        if self._cipd_only:
-            return 0
-
         with open(self._shell_file, 'w') as outs:
             self._env.write(outs)
 
@@ -504,35 +388,24 @@
             outs.write(
                 json.dumps(config, indent=4, separators=(',', ': ')) + '\n')
 
-        json_file = (self._json_file
-                     or os.path.join(self._install_dir, 'actions.json'))
-        with open(json_file, 'w') as outs:
-            self._env.json(outs)
+        if self._json_file is not None:
+            with open(self._json_file, 'w') as outs:
+                self._env.json(outs)
 
         return 0
 
     def cipd(self, spin):
-        """Set up cipd and install cipd packages."""
-
         install_dir = os.path.join(self._install_dir, 'cipd')
 
-        # There's no way to get to the UnsupportedPlatform exception if this
-        # flag is set, but this flag should only be set in LUCI builds which
-        # will always have CIPD.
-        if self._use_existing_cipd:
-            cipd_client = 'cipd'
+        try:
+            cipd_client = cipd_wrapper.init(install_dir, silent=True)
+        except cipd_wrapper.UnsupportedPlatform as exc:
+            return result_func(('    {!r}'.format(exc), ))(
+                _Result.Status.SKIPPED,
+                '    abandoning CIPD setup',
+            )
 
-        else:
-            try:
-                cipd_client = cipd_wrapper.init(install_dir, silent=True)
-            except cipd_wrapper.UnsupportedPlatform as exc:
-                return result_func(('    {!r}'.format(exc), ))(
-                    _Result.Status.SKIPPED,
-                    '    abandoning CIPD setup',
-                )
-
-        package_files, glob_warnings = self._process_globs(
-            self._cipd_package_file)
+        package_files, glob_warnings = _process_globs(self._cipd_package_file)
         result = result_func(glob_warnings)
 
         if not package_files:
@@ -543,8 +416,7 @@
                                   package_files=package_files,
                                   cache_dir=self._cipd_cache_dir,
                                   env_vars=self._env,
-                                  spin=spin,
-                                  trust_hash=self._trust_cipd_hash):
+                                  spin=spin):
             return result(_Result.Status.FAILED)
 
         return result(_Result.Status.DONE)
@@ -552,7 +424,7 @@
     def virtualenv(self, unused_spin):
         """Setup virtualenv."""
 
-        requirements, req_glob_warnings = self._process_globs(
+        requirements, req_glob_warnings = _process_globs(
             self._virtualenv_requirements)
         result = result_func(req_glob_warnings)
 
@@ -579,49 +451,15 @@
                 project_root=self._project_root,
                 venv_path=self._virtualenv_root,
                 requirements=requirements,
-                gn_args=self._virtualenv_gn_args,
                 gn_targets=self._virtualenv_gn_targets,
                 gn_out_dir=self._virtualenv_gn_out_dir,
                 python=new_python3,
                 env=self._env,
-                system_packages=self._virtualenv_system_packages,
-                use_pinned_pip_packages=self._use_pinned_pip_packages,
         ):
             return result(_Result.Status.FAILED)
 
         return result(_Result.Status.DONE)
 
-    def pw_package(self, unused_spin):
-        """Install "default" pw packages."""
-
-        result = result_func()
-
-        if not self._pw_packages:
-            return result(_Result.Status.SKIPPED)
-
-        logdir = os.path.join(self._install_dir, 'packages')
-        if not os.path.isdir(logdir):
-            os.makedirs(logdir)
-
-        for pkg in self._pw_packages:
-            print('installing {}'.format(pkg))
-            cmd = ['pw', 'package', 'install', pkg]
-
-            log = os.path.join(logdir, '{}.log'.format(pkg))
-            try:
-                with open(log, 'w') as outs, self._env():
-                    print(*cmd, file=outs)
-                    subprocess.check_call(cmd,
-                                          cwd=self._project_root,
-                                          stdout=outs,
-                                          stderr=subprocess.STDOUT)
-            except subprocess.CalledProcessError:
-                with open(log, 'r') as ins:
-                    sys.stderr.write(ins.read())
-                    raise
-
-        return result(_Result.Status.DONE)
-
     def host_tools(self, unused_spin):
         # The host tools are grabbed from CIPD, at least initially. If the
         # user has a current host build, that build will be used instead.
@@ -637,6 +475,22 @@
                                                'windows_scripts'))
         return _Result(_Result.Status.DONE)
 
+    def cargo(self, unused_spin):
+        install_dir = os.path.join(self._install_dir, 'cargo')
+
+        package_files, glob_warnings = _process_globs(self._cargo_package_file)
+        result = result_func(glob_warnings)
+
+        if not package_files:
+            return result(_Result.Status.SKIPPED)
+
+        if not cargo_setup.install(install_dir=install_dir,
+                                   package_files=package_files,
+                                   env=self._env):
+            return result(_Result.Status.FAILED)
+
+        return result(_Result.Status.DONE)
+
 
 def parse(argv=None):
     """Parse command-line arguments."""
@@ -673,14 +527,6 @@
     )
 
     parser.add_argument(
-        '--trust-cipd-hash',
-        action='store_true',
-        help='Only run the cipd executable if the ensure file or command-line '
-        'has changed. Defaults to false since files could have been deleted '
-        'from the installation directory and cipd would add them back.',
-    )
-
-    parser.add_argument(
         '--shell-file',
         help='Where to write the file for shells to source.',
         required=True,
@@ -703,7 +549,37 @@
         '--config-file',
         help='JSON file describing CIPD and virtualenv requirements.',
         type=argparse.FileType('r'),
-        required=True,
+    )
+
+    parser.add_argument(
+        '--use-pigweed-defaults',
+        help='Use Pigweed default values in addition to the given environment '
+        'variables.',
+        action='store_true',
+    )
+
+    parser.add_argument(
+        '--cipd-package-file',
+        help='CIPD package file. JSON file consisting of a list of dicts with '
+        '"path" and "tags" keys, where "tags" a list of str.',
+        default=[],
+        action='append',
+    )
+
+    parser.add_argument(
+        '--virtualenv-requirements',
+        help='Pip requirements file. Compiled with pip-compile.',
+        default=[],
+        action='append',
+    )
+
+    parser.add_argument(
+        '--virtualenv-gn-target',
+        help=('GN targets that build and install Python packages. Format: '
+              'path/to/gn_root#target'),
+        default=[],
+        action='append',
+        type=virtualenv_setup.GnTarget,
     )
 
     parser.add_argument(
@@ -719,35 +595,47 @@
         default=None,
     )
 
-    parser.add_argument('--json-file', help=argparse.SUPPRESS, default=None)
+    parser.add_argument(
+        '--cargo-package-file',
+        help='Rust cargo packages to install. Lines with package name and '
+        'version separated by a space.',
+        default=[],
+        action='append',
+    )
 
     parser.add_argument(
-        '--use-existing-cipd',
-        help='Use cipd executable from the environment instead of fetching it.',
+        '--enable-cargo',
+        help='Enable cargo installation.',
         action='store_true',
     )
 
     parser.add_argument(
-        '--strict',
-        help='Fail if there are any warnings.',
-        action='store_true',
-    )
-
-    parser.add_argument(
-        '--unpin-pip-packages',
-        dest='use_pinned_pip_packages',
-        help='Do not use pins of pip packages.',
-        action='store_false',
-    )
-
-    parser.add_argument(
-        '--cipd-only',
-        help='Skip non-CIPD steps.',
-        action='store_true',
+        '--json-file',
+        help='Dump environment variable operations to a JSON file.',
+        default=None,
     )
 
     args = parser.parse_args(argv)
 
+    others = (
+        'use_pigweed_defaults',
+        'cipd_package_file',
+        'virtualenv_requirements',
+        'virtualenv_gn_target',
+        'cargo_package_file',
+    )
+
+    one_required = others + ('config_file', )
+
+    if not any(getattr(args, x) for x in one_required):
+        parser.error('At least one of ({}) is required'.format(', '.join(
+            '"--{}"'.format(x.replace('_', '-')) for x in one_required)))
+
+    if args.config_file and any(getattr(args, x) for x in others):
+        parser.error('Cannot combine --config-file with any of {}'.format(
+            ', '.join('"--{}"'.format(x.replace('_', '-'))
+                      for x in one_required)))
+
     return args
 
 
diff --git a/pw_env_setup/py/pw_env_setup/environment.py b/pw_env_setup/py/pw_env_setup/environment.py
index 39a601a..1a97c0a 100644
--- a/pw_env_setup/py/pw_env_setup/environment.py
+++ b/pw_env_setup/py/pw_env_setup/environment.py
@@ -28,7 +28,6 @@
 
 from . import apply_visitor
 from . import batch_visitor
-from . import gni_visitor
 from . import json_visitor
 from . import shell_visitor
 
@@ -125,18 +124,9 @@
         else:
             env.pop(self.name, None)
 
-    def __repr__(self):
-        return '{}({}, {})'.format(self.__class__.__name__, self.name,
-                                   self.value)
-
 
 class Set(_VariableAction):
     """Set a variable."""
-    def __init__(self, *args, **kwargs):
-        deactivate = kwargs.pop('deactivate', True)
-        super(Set, self).__init__(*args, **kwargs)
-        self.deactivate = deactivate
-
     def accept(self, visitor):
         visitor.visit_set(self)
 
@@ -212,9 +202,6 @@
     def accept(self, visitor):
         visitor.visit_echo(self)
 
-    def __repr__(self):
-        return 'Echo({}, newline={})'.format(self.value, self.newline)
-
 
 class Comment(_Action):
     """Add a comment to the init script."""
@@ -225,9 +212,6 @@
     def accept(self, visitor):
         visitor.visit_comment(self)
 
-    def __repr__(self):
-        return 'Comment({})'.format(self.value)
-
 
 class Command(_Action):
     """Run a command."""
@@ -241,9 +225,6 @@
     def accept(self, visitor):
         visitor.visit_command(self)
 
-    def __repr__(self):
-        return 'Command({})'.format(self.command)
-
 
 class Doctor(Command):
     def __init__(self, *args, **kwargs):
@@ -256,18 +237,12 @@
     def accept(self, visitor):
         visitor.visit_doctor(self)
 
-    def __repr__(self):
-        return 'Doctor()'
-
 
 class BlankLine(_Action):
     """Write a blank line to the init script."""
     def accept(self, visitor):
         visitor.visit_blank_line(self)
 
-    def __repr__(self):
-        return 'BlankLine()'
-
 
 class Function(_Action):
     def __init__(self, name, body, *args, **kwargs):
@@ -278,17 +253,11 @@
     def accept(self, visitor):
         visitor.visit_function(self)
 
-    def __repr__(self):
-        return 'Function({}, {})'.format(self.name, self.body)
-
 
 class Hash(_Action):
     def accept(self, visitor):
         visitor.visit_hash(self)
 
-    def __repr__(self):
-        return 'Hash()'
-
 
 class Join(object):  # pylint: disable=useless-object-inheritance
     def __init__(self, pathsep=os.pathsep):
@@ -332,11 +301,11 @@
     # A newline is printed after each high-level operation. Top-level
     # operations should not invoke each other (this is why _remove() exists).
 
-    def set(self, name, value, deactivate=True):
+    def set(self, name, value):
         """Set a variable."""
         assert not self._finalized
         name = self.normalize_key(name)
-        self._actions.append(Set(name, value, deactivate=deactivate))
+        self._actions.append(Set(name, value))
         self._blankline()
 
     def clear(self, name):
@@ -430,9 +399,6 @@
         for action in self._actions:
             action.accept(visitor)
 
-    def gni(self, outs, project_root):
-        gni_visitor.GNIVisitor(project_root).serialize(self, outs)
-
     def json(self, outs):
         json_visitor.JSONVisitor().serialize(self, outs)
 
diff --git a/pw_env_setup/py/pw_env_setup/gni_visitor.py b/pw_env_setup/py/pw_env_setup/gni_visitor.py
deleted file mode 100644
index cd60d80..0000000
--- a/pw_env_setup/py/pw_env_setup/gni_visitor.py
+++ /dev/null
@@ -1,100 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Serializes an Environment into a JSON file."""
-
-from __future__ import print_function
-
-import re
-
-# Disable super() warnings since this file must be Python 2 compatible.
-# pylint: disable=super-with-arguments
-
-
-class GNIVisitor(object):  # pylint: disable=useless-object-inheritance
-    """Serializes portions of an Environment into a gni file.
-
-    Example gni file:
-
-    declare_args() {
-      dir_cipd_default = "<ENVIRONMENT_DIR>/cipd/packages/default"
-      dir_cipd_pigweed = "<ENVIRONMENT_DIR>/cipd/packages/pigweed"
-      dir_cipd_arm = "<ENVIRONMENT_DIR>/cipd/packages/arm"
-      dir_cipd_python = "<ENVIRONMENT_DIR>/cipd/packages/python"
-      dir_cipd_bazel = "<ENVIRONMENT_DIR>/cipd/packages/bazel"
-      dir_cipd_luci = "<ENVIRONMENT_DIR>/cipd/packages/luci"
-      dir_virtual_env = "<ENVIRONMENT_DIR>/pigweed-venv"
-    }
-    """
-    def __init__(self, project_root, *args, **kwargs):
-        super(GNIVisitor, self).__init__(*args, **kwargs)
-        self._project_root = project_root
-        self._lines = []
-
-    def serialize(self, env, outs):
-        self._lines.append("""
-# This file is automatically generated by Pigweed's environment setup. Do not
-# edit it manually or check it in.
-""".strip())
-
-        self._lines.append('declare_args() {')
-
-        env.accept(self)
-
-        self._lines.append('}')
-
-        for line in self._lines:
-            print(line, file=outs)
-        self._lines = []
-
-    def visit_set(self, set):  # pylint: disable=redefined-builtin
-        match = re.search(r'PW_(.*)_CIPD_INSTALL_DIR', set.name)
-        if match:
-            name = 'dir_cipd_{}'.format(match.group(1).lower())
-            self._lines.append('  {} = "{}"'.format(name, set.value))
-
-        if set.name == 'VIRTUAL_ENV':
-            self._lines.append('  dir_virtual_env = "{}"'.format(set.value))
-
-    def visit_clear(self, clear):
-        pass
-
-    def visit_remove(self, remove):
-        pass
-
-    def visit_prepend(self, prepend):
-        pass
-
-    def visit_append(self, append):
-        pass
-
-    def visit_echo(self, echo):
-        pass
-
-    def visit_comment(self, comment):
-        pass
-
-    def visit_command(self, command):
-        pass
-
-    def visit_doctor(self, doctor):
-        pass
-
-    def visit_blank_line(self, blank_line):
-        pass
-
-    def visit_function(self, function):
-        pass
-
-    def visit_hash(self, hash):  # pylint: disable=redefined-builtin
-        pass
diff --git a/pw_env_setup/py/pw_env_setup/python_packages.py b/pw_env_setup/py/pw_env_setup/python_packages.py
deleted file mode 100644
index f2fb138..0000000
--- a/pw_env_setup/py/pw_env_setup/python_packages.py
+++ /dev/null
@@ -1,148 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Save list of installed packages and versions."""
-
-import argparse
-import subprocess
-import sys
-from typing import Dict, List, TextIO, Union
-
-
-def _installed_packages():
-    """Run pip python_packages and write to out."""
-    cmd = [
-        'python',
-        '-m',
-        'pip',
-        'freeze',
-        '--exclude-editable',
-        '--local',
-    ]
-    proc = subprocess.run(cmd, capture_output=True)
-    for line in proc.stdout.decode().splitlines():
-        if ' @ ' not in line:
-            yield line
-
-
-def ls(out: TextIO) -> int:  # pylint: disable=invalid-name
-    """Run pip python_packages and write to out."""
-    for package in _installed_packages():
-        print(package, file=out)
-
-    return 0
-
-
-class UpdateRequiredError(Exception):
-    pass
-
-
-def _stderr(*args, **kwargs):
-    return print(*args, file=sys.stderr, **kwargs)
-
-
-def diff(expected: TextIO) -> int:
-    """Report on differences between installed and expected versions."""
-    actual_lines = set(_installed_packages())
-    expected_lines = set(expected.read().splitlines())
-
-    if actual_lines == expected_lines:
-        _stderr('package versions are identical')
-        return 0
-
-    removed_entries: Dict[str, str] = dict(
-        x.split('==', 1)  # type: ignore[misc]
-        for x in expected_lines - actual_lines)
-    added_entries: Dict[str, str] = dict(
-        x.split('==', 1)  # type: ignore[misc]
-        for x in actual_lines - expected_lines)
-
-    new_packages = set(added_entries) - set(removed_entries)
-    removed_packages = set(removed_entries) - set(added_entries)
-    updated_packages = set(added_entries).intersection(set(removed_entries))
-
-    if removed_packages:
-        _stderr('Removed packages')
-        for package in removed_packages:
-            _stderr(f'  {package}=={removed_entries[package]}')
-
-    if updated_packages:
-        _stderr('Updated packages')
-        for package in updated_packages:
-            _stderr(f'  {package}=={added_entries[package]} (from '
-                    f'{removed_entries[package]})')
-
-    if new_packages:
-        _stderr('New packages')
-        for package in new_packages:
-            _stderr(f'  {package}=={added_entries[package]}')
-
-    if updated_packages or new_packages:
-        _stderr("Package versions don't match!")
-        _stderr(f"""
-Please do the following:
-
-* purge your environment directory
-  * Linux/Mac: 'rm -rf "$_PW_ACTUAL_ENVIRONMENT_ROOT"'
-  * Windows: 'rmdir /S %_PW_ACTUAL_ENVIRONMENT_ROOT%'
-* bootstrap
-  * Linux/Mac: '. ./bootstrap.sh'
-  * Windows: 'bootstrap.bat'
-* update the constraint file
-  * 'pw python-packages list {expected.name}'
-""")
-        return -1
-
-    return 0
-
-
-def parse(argv: Union[List[str], None] = None) -> argparse.Namespace:
-    """Parse command-line arguments."""
-    parser = argparse.ArgumentParser()
-    subparsers = parser.add_subparsers(dest='cmd')
-
-    list_parser = subparsers.add_parser(
-        'list', aliases=('ls', ), help='List installed package versions.')
-    list_parser.add_argument('out',
-                             type=argparse.FileType('w'),
-                             default=sys.stdout,
-                             nargs='?')
-
-    diff_parser = subparsers.add_parser(
-        'diff',
-        help='Show differences between expected and actual package versions.',
-    )
-    diff_parser.add_argument('expected', type=argparse.FileType('r'))
-
-    return parser.parse_args(argv)
-
-
-def main() -> int:
-    try:
-        args = vars(parse())
-        cmd = args.pop('cmd')
-        if cmd == 'diff':
-            return diff(**args)
-        if cmd == 'list':
-            return ls(**args)
-        return -1
-    except subprocess.CalledProcessError as err:
-        print(file=sys.stderr)
-        print(err.output, file=sys.stderr)
-        raise
-
-
-if __name__ == '__main__':
-    sys.exit(main())
diff --git a/pw_env_setup/py/pw_env_setup/shell_visitor.py b/pw_env_setup/py/pw_env_setup/shell_visitor.py
index ccbde51..18a42fb 100644
--- a/pw_env_setup/py/pw_env_setup/shell_visitor.py
+++ b/pw_env_setup/py/pw_env_setup/shell_visitor.py
@@ -161,8 +161,7 @@
             self._outs = None
 
     def visit_set(self, set):  # pylint: disable=redefined-builtin
-        if set.deactivate:
-            self._outs.write('unset {name}\n'.format(name=set.name))
+        self._outs.write('unset {name}\n'.format(name=set.name))
 
     def visit_clear(self, clear):
         pass  # Not relevant.
diff --git a/pw_env_setup/py/pw_env_setup/spinner.py b/pw_env_setup/py/pw_env_setup/spinner.py
index 0a5c099..63d577e 100644
--- a/pw_env_setup/py/pw_env_setup/spinner.py
+++ b/pw_env_setup/py/pw_env_setup/spinner.py
@@ -19,24 +19,14 @@
 import threading
 import time
 
+PW_ENVSETUP_DISABLE_SPINNER = os.environ.get('PW_ENVSETUP_DISABLE_SPINNER')
+
 
 class Spinner(object):  # pylint: disable=useless-object-inheritance
     """Spinner!"""
-    def __init__(self, quiet=False):
+    def __init__(self):
         self._done = None
         self._thread = None
-        self._quiet = quiet
-
-    def _disabled(self):
-        if os.environ.get('PW_ENVSETUP_DISABLE_SPINNER'):
-            return True
-        if os.environ.get('PW_ENVSETUP_QUIET'):
-            return True
-        if self._quiet:
-            return True
-        if not sys.stdout.isatty():
-            return True
-        return False
 
     def __del__(self):
         self._done = True
@@ -52,7 +42,7 @@
             i = (i + 1) % len(chars)
 
     def start(self):
-        if self._disabled():
+        if PW_ENVSETUP_DISABLE_SPINNER:
             return
 
         self._done = False
@@ -60,7 +50,7 @@
         self._thread.start()
 
     def stop(self):
-        if self._disabled():
+        if PW_ENVSETUP_DISABLE_SPINNER:
             return
 
         assert self._thread
diff --git a/pw_env_setup/py/pw_env_setup/virtualenv_setup/constraint.list b/pw_env_setup/py/pw_env_setup/virtualenv_setup/constraint.list
deleted file mode 100644
index ef1d395..0000000
--- a/pw_env_setup/py/pw_env_setup/virtualenv_setup/constraint.list
+++ /dev/null
@@ -1,89 +0,0 @@
-alabaster==0.7.12
-appdirs==1.4.4
-astroid==2.6.6
-Babel==2.9.1
-backcall==0.2.0
-beautifulsoup4==4.10.0
-build==0.7.0
-certifi==2021.10.8
-cffi==1.15.0
-charset-normalizer==2.0.10
-coloredlogs==15.0.1
-coverage==6.3
-cryptography==36.0.1
-decorator==5.1.1
-docutils==0.17.1
-furo==2022.1.2
-future==0.18.2
-grpcio==1.43.0
-grpcio-tools==1.43.0
-httpwatcher==0.5.2
-humanfriendly==10.0
-idna==3.3
-imagesize==1.3.0
-ipython==7.31.0
-isort==5.10.1
-jedi==0.18.1
-Jinja2==3.0.3
-lazy-object-proxy==1.7.1
-MarkupSafe==2.0.1
-matplotlib-inline==0.1.3
-mccabe==0.6.1
-mypy==0.910
-mypy-extensions==0.4.3
-mypy-protobuf==2.9
-packaging==21.3
-parameterized==0.8.1
-parso==0.8.3
-pep517==0.12.0
-pexpect==4.8.0
-pickleshare==0.7.5
-prompt-toolkit==3.0.26
-protobuf==3.19.1
-psutil==5.9.0
-ptpython==3.0.20
-ptyprocess==0.7.0
-pycparser==2.21
-pyelftools==0.27
-Pygments==2.11.2
-pygments-style-dracula==1.2.5.1
-pygments-style-tomorrow==1.0.0.1
-pylint==2.9.3
-pyparsing==3.0.6
-pyperclip==1.8.2
-pyserial==3.5
-pytz==2021.3
-PyYAML==6.0
-requests==2.27.1
-robotframework==3.1
-scan-build==2.0.19
-six==1.16.0
-snowballstemmer==2.2.0
-soupsieve==2.3.1
-Sphinx==4.3.2
-sphinx-design==0.0.13
-sphinx-rtd-theme==1.0.0
-sphinxcontrib-applehelp==1.0.2
-sphinxcontrib-devhelp==1.0.2
-sphinxcontrib-htmlhelp==2.0.0
-sphinxcontrib-jsmath==1.0.1
-sphinxcontrib-mermaid==0.7.1
-sphinxcontrib-qthelp==1.0.3
-sphinxcontrib-serializinghtml==1.1.5
-toml==0.10.2
-tomli==2.0.0
-tornado==4.5.3
-traitlets==5.1.1
-types-docutils==0.17.4
-types-futures==3.3.2
-types-protobuf==3.18.4
-types-Pygments==2.9.13
-types-PyYAML==6.0.3
-types-setuptools==57.4.7
-types-six==1.16.9
-typing_extensions==4.0.1
-urllib3==1.26.8
-watchdog==2.1.6
-wcwidth==0.2.5
-wrapt==1.12.1
-yapf==0.31.0
diff --git a/pw_env_setup/py/pw_env_setup/virtualenv_setup/install.py b/pw_env_setup/py/pw_env_setup/virtualenv_setup/install.py
index 067b244..d7134d4 100644
--- a/pw_env_setup/py/pw_env_setup/virtualenv_setup/install.py
+++ b/pw_env_setup/py/pw_env_setup/virtualenv_setup/install.py
@@ -15,27 +15,34 @@
 
 from __future__ import print_function
 
-import contextlib
-import datetime
 import glob
+import hashlib
 import os
-import platform
 import re
-import shutil
 import subprocess
 import sys
 import tempfile
 
-# Grabbing datetime string once so it will always be the same for all GnTarget
-# objects.
-_DATETIME_STRING = datetime.datetime.now().strftime('%Y%m%d-%H%M%S')
-
 
 class GnTarget(object):  # pylint: disable=useless-object-inheritance
     def __init__(self, val):
         self.directory, self.target = val.split('#', 1)
-        self.name = '-'.join(
-            (re.sub(r'\W+', '_', self.target).strip('_'), _DATETIME_STRING))
+        # hash() doesn't necessarily give the same value in new runs of Python,
+        # so compute a unique id for this object that's consistent from run to
+        # run.
+        try:
+            val = val.encode()
+        except AttributeError:
+            pass
+        self._unique_id = hashlib.md5(val).hexdigest()
+
+    @property
+    def name(self):
+        """A reasonably stable and unique name for each pair."""
+        result = '{}-{}'.format(
+            os.path.basename(os.path.normpath(self.directory)),
+            self._unique_id)
+        return re.sub(r'[:/#_]+', '_', result)
 
 
 def git_stdout(*args, **kwargs):
@@ -108,40 +115,15 @@
     return matches
 
 
-def _check_venv(python, version, venv_path, pyvenv_cfg):
-    # TODO(pwbug/400) Re-enable this check on Windows.
-    if platform.system().lower() == 'windows':
-        return
-
-    # Check if the python location and version used for the existing virtualenv
-    # is the same as the python we're using. If it doesn't match, we need to
-    # delete the existing virtualenv and start again.
-    if os.path.exists(pyvenv_cfg):
-        pyvenv_values = {}
-        with open(pyvenv_cfg, 'r') as ins:
-            for line in ins:
-                key, value = line.strip().split(' = ', 1)
-                pyvenv_values[key] = value
-        pydir = os.path.dirname(python)
-        home = pyvenv_values.get('home')
-        if pydir != home and not pydir.startswith(venv_path):
-            shutil.rmtree(venv_path)
-        elif pyvenv_values.get('version') not in version:
-            shutil.rmtree(venv_path)
-
-
-def install(  # pylint: disable=too-many-arguments
-    project_root,
-    venv_path,
-    full_envsetup=True,
-    requirements=(),
-    gn_args=(),
-    gn_targets=(),
-    gn_out_dir=None,
-    python=sys.executable,
-    env=None,
-    system_packages=False,
-    use_pinned_pip_packages=True,
+def install(
+        project_root,
+        venv_path,
+        full_envsetup=True,
+        requirements=(),
+        gn_targets=(),
+        gn_out_dir=None,
+        python=sys.executable,
+        env=None,
 ):
     """Creates a venv and installs all packages in this Git repo."""
 
@@ -158,14 +140,6 @@
     # The bin/ directory is called Scripts/ on Windows. Don't ask.
     venv_bin = os.path.join(venv_path, 'Scripts' if os.name == 'nt' else 'bin')
 
-    if env:
-        env.set('VIRTUAL_ENV', venv_path)
-        env.prepend('PATH', venv_bin)
-        env.clear('PYTHONHOME')
-        env.clear('__PYVENV_LAUNCHER__')
-    else:
-        env = contextlib.nullcontext()
-
     # Delete activation scripts. Typically they're created read-only and venv
     # will complain when trying to write over them fails.
     if os.path.isdir(venv_bin):
@@ -174,9 +148,6 @@
                 os.unlink(os.path.join(venv_bin, entry))
 
     pyvenv_cfg = os.path.join(venv_path, 'pyvenv.cfg')
-
-    _check_venv(python, version, venv_path, pyvenv_cfg)
-
     if full_envsetup or not os.path.exists(pyvenv_cfg):
         # On Mac sometimes the CIPD Python has __PYVENV_LAUNCHER__ set to
         # point to the system Python, which causes CIPD Python to create
@@ -186,12 +157,7 @@
         if '__PYVENV_LAUNCHER__' in envcopy:
             del envcopy['__PYVENV_LAUNCHER__']
 
-        # TODO(spang): Pass --upgrade-deps and remove pip & setuptools
-        # upgrade below. This can only be done once the minimum python
-        # version is at least 3.9.
-        cmd = [python, '-m', 'venv', '--upgrade']
-        cmd += ['--system-site-packages'] if system_packages else []
-        cmd += [venv_path]
+        cmd = (python, '-m', 'venv', '--upgrade', venv_path)
         _check_call(cmd, env=envcopy)
 
     venv_python = os.path.join(venv_bin, 'python')
@@ -213,19 +179,10 @@
         os.unlink(egg_link)
 
     def pip_install(*args):
-        with env():
-            cmd = [venv_python, '-m', 'pip', 'install'] + list(args)
-            return _check_call(cmd)
+        cmd = [venv_python, '-m', 'pip', 'install'] + list(args)
+        return _check_call(cmd)
 
-    pip_install(
-        '--log',
-        os.path.join(venv_path, 'pip-upgrade.log'),
-        '--upgrade',
-        'pip',
-        'setuptools',
-        # Include wheel so pip installs can be done without build
-        # isolation.
-        'wheel')
+    pip_install('--upgrade', 'pip')
 
     if requirements:
         requirement_args = tuple('--requirement={}'.format(req)
@@ -235,7 +192,7 @@
 
     def install_packages(gn_target):
         if gn_out_dir is None:
-            build_dir = os.path.join(venv_path, 'gn-install-dir')
+            build_dir = os.path.join(venv_path, gn_target.name)
         else:
             build_dir = gn_out_dir
 
@@ -256,15 +213,12 @@
         gn_log_path = os.path.join(venv_path, gn_log)
         try:
             with open(gn_log_path, 'w') as outs:
-                gn_cmd = ['gn', 'gen', build_dir]
-
-                args = list(gn_args)
-                if not use_pinned_pip_packages:
-                    args.append('pw_build_PIP_CONSTRAINTS=[]')
-
-                args.append('dir_pigweed="{}"'.format(pw_root))
-                gn_cmd.append('--args={}'.format(' '.join(args)))
-
+                gn_cmd = (
+                    'gn',
+                    'gen',
+                    build_dir,
+                    '--args=dir_pigweed="{}"'.format(pw_root),
+                )
                 print(gn_cmd, file=outs)
                 subprocess.check_call(gn_cmd,
                                       cwd=os.path.join(project_root,
@@ -280,7 +234,7 @@
         ninja_log_path = os.path.join(venv_path, ninja_log)
         try:
             with open(ninja_log_path, 'w') as outs:
-                ninja_cmd = ['ninja', '-C', build_dir, '-v']
+                ninja_cmd = ['ninja', '-C', build_dir]
                 ninja_cmd.append(gn_target.target)
                 print(ninja_cmd, file=outs)
                 subprocess.check_call(ninja_cmd, stdout=outs, stderr=outs)
@@ -296,7 +250,14 @@
             )
 
     if gn_targets:
-        with env():
+        if env:
+            env.set('VIRTUAL_ENV', venv_path)
+            env.prepend('PATH', venv_bin)
+            env.clear('PYTHONHOME')
+            with env():
+                for gn_target in gn_targets:
+                    install_packages(gn_target)
+        else:
             for gn_target in gn_targets:
                 install_packages(gn_target)
 
diff --git a/pw_env_setup/py/pyproject.toml b/pw_env_setup/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/pw_env_setup/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/pw_env_setup/py/python_packages_test.py b/pw_env_setup/py/python_packages_test.py
deleted file mode 100755
index 27720c6..0000000
--- a/pw_env_setup/py/python_packages_test.py
+++ /dev/null
@@ -1,81 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests the python_packages module."""
-
-import collections
-import io
-import unittest
-from unittest import mock
-
-from pw_env_setup import python_packages
-
-
-def _subprocess_run_stdout(stdout=b'foo==1.0\nbar==2.0\npw-foo @ file:...\n'):
-    def subprocess_run(*unused_args, **unused_kwargs):
-        CompletedProcess = collections.namedtuple('CompletedProcess', 'stdout')
-        return CompletedProcess(stdout=stdout)
-
-    return subprocess_run
-
-
-class TestPythonPackages(unittest.TestCase):
-    """Tests the python_packages module."""
-    @mock.patch('pw_env_setup.python_packages.subprocess.run',
-                side_effect=_subprocess_run_stdout())
-    def test_list(self, unused_mock):
-        buf = io.StringIO()
-        python_packages.ls(buf)
-        self.assertIn('foo==1.0', buf.getvalue())
-        self.assertIn('bar==2.0', buf.getvalue())
-        self.assertNotIn('pw-foo', buf.getvalue())
-
-    @mock.patch('pw_env_setup.python_packages.subprocess.run',
-                side_effect=_subprocess_run_stdout())
-    @mock.patch('pw_env_setup.python_packages._stderr')
-    def test_diff_removed(self, stderr_mock, unused_mock):
-        expected = io.StringIO('foo==1.0\nbar==2.0\nbaz==3.0\n')
-        expected.name = 'test.name'
-        self.assertFalse(python_packages.diff(expected))
-
-        stderr_mock.assert_any_call('Removed packages')
-        stderr_mock.assert_any_call('  baz==3.0')
-
-    @mock.patch('pw_env_setup.python_packages.subprocess.run',
-                side_effect=_subprocess_run_stdout())
-    @mock.patch('pw_env_setup.python_packages._stderr')
-    def test_diff_updated(self, stderr_mock, unused_mock):
-        expected = io.StringIO('foo==1.0\nbar==1.9\n')
-        expected.name = 'test.name'
-        self.assertTrue(python_packages.diff(expected))
-
-        stderr_mock.assert_any_call('Updated packages')
-        stderr_mock.assert_any_call('  bar==2.0 (from 1.9)')
-        stderr_mock.assert_any_call("Package versions don't match!")
-
-    @mock.patch('pw_env_setup.python_packages.subprocess.run',
-                side_effect=_subprocess_run_stdout())
-    @mock.patch('pw_env_setup.python_packages._stderr')
-    def test_diff_new(self, stderr_mock, unused_mock):
-        expected = io.StringIO('foo==1.0\n')
-        expected.name = 'test.name'
-        self.assertTrue(python_packages.diff(expected))
-
-        stderr_mock.assert_any_call('New packages')
-        stderr_mock.assert_any_call('  bar==2.0')
-        stderr_mock.assert_any_call("Package versions don't match!")
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_env_setup/py/setup.cfg b/pw_env_setup/py/setup.cfg
deleted file mode 100644
index bbcb87f..0000000
--- a/pw_env_setup/py/setup.cfg
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pw_env_setup
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Environment setup for Pigweed
-
-[options]
-packages = find:
-zip_safe = False
-install_requires =
-    six
-    types-six
-
-[options.entry_points]
-console_scripts =
-    _pw_env_setup = pw_env_setup.env_setup:main
-
-[options.package_data]
-pw_env_setup =
-    py.typed
-    cargo_setup/packages.txt
-    cipd_setup/luci.json
-    cipd_setup/pigweed.json
-    virtualenv_setup/requirements.in
-    virtualenv_setup/requirements.txt
diff --git a/pw_env_setup/py/setup.py b/pw_env_setup/py/setup.py
index ff2824d..b50b414 100644
--- a/pw_env_setup/py/setup.py
+++ b/pw_env_setup/py/setup.py
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2020 The Pigweed Authors
 #
 # 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
@@ -11,8 +11,29 @@
 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 # License for the specific language governing permissions and limitations under
 # the License.
-"""pw_env_setup"""
+"""pw_env_setup package definition."""
 
 import setuptools  # type: ignore
 
-setuptools.setup()  # Package definition in setup.cfg
+setuptools.setup(
+    name='pw_env_setup',
+    version='0.0.1',
+    author='Pigweed Authors',
+    author_email='pigweed-developers@googlegroups.com',
+    description='Environment setup for Pigweed',
+    packages=setuptools.find_packages(),
+    entry_points={
+        'console_scripts': ['_pw_env_setup = pw_env_setup.env_setup:main'],
+    },
+    package_data={
+        'pw_env_setup': [
+            'py.typed',
+            'cargo_setup/packages.txt',
+            'cipd_setup/luci.json',
+            'cipd_setup/pigweed.json',
+            'virtualenv_setup/requirements.in',
+            'virtualenv_setup/requirements.txt',
+        ],
+    },
+    zip_safe=False,
+)
diff --git a/pw_env_setup/pypi_common_setup.cfg b/pw_env_setup/pypi_common_setup.cfg
deleted file mode 100644
index f4ad8fb..0000000
--- a/pw_env_setup/pypi_common_setup.cfg
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pigweed
-version = 0.0.7
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Pigweed Python modules
-long_description = file: README.md
-long_description_content_type = text/markdown
-url = https://pigweed.dev
-project_urls =
-    Bug Tracker = https://bugs.chromium.org/p/pigweed/issues/list
-    Code Search = https://cs.opensource.google/pigweed/pigweed
-    Gerrit = https://pigweed-review.googlesource.com
-classifiers =
-    License :: OSI Approved :: Apache Software License
-    Operating System :: OS Independent
-    Programming Language :: Python :: 3
-    Topic :: Software Development :: Embedded Systems
-
-[options]
-zip_safe = False
-python_requires = >=3.7
diff --git a/pw_env_setup/pypi_pyproject.toml b/pw_env_setup/pypi_pyproject.toml
deleted file mode 100644
index 23f8983..0000000
--- a/pw_env_setup/pypi_pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
\ No newline at end of file
diff --git a/pw_env_setup/util.sh b/pw_env_setup/util.sh
index d0aa2e7..1379611 100644
--- a/pw_env_setup/util.sh
+++ b/pw_env_setup/util.sh
@@ -155,25 +155,21 @@
 }
 
 pw_deactivate() {
-  # Assume PW_ROOT and PW_PROJECT_ROOT have already been set and we need to
+  # Assume PW_ROOT and PW_PROJECT_ROOT has already been set and we need to
   # preserve their values.
   _NEW_PW_ROOT="$PW_ROOT"
   _NEW_PW_PROJECT_ROOT="$PW_PROJECT_ROOT"
 
-  # Find deactivate script, run it, and then delete it. This way if the
-  # deactivate script is doing something wrong subsequent bootstraps still
-  # have a chance to pass.
+  # Find deactivate script and run it.
   _PW_DEACTIVATE_SH="$_PW_ACTUAL_ENVIRONMENT_ROOT/deactivate.sh"
   if [ -f "$_PW_DEACTIVATE_SH" ]; then
     . "$_PW_DEACTIVATE_SH"
-    rm -f "$_PW_DEACTIVATE_SH" &> /dev/null
   fi
 
   # If there's a _pw_deactivate function run it. Redirect output to /dev/null
-  # in case _pw_deactivate doesn't exist. Remove _pw_deactivate when complete.
+  # in case _pw_deactivate doesn't exist.
   if [ -n "$(command -v _pw_deactivate)" ]; then
-    _pw_deactivate > /dev/null 2> /dev/null
-    unset -f _pw_deactivate
+    _pw_deactivate &> /dev/null
   fi
 
   # Restore.
@@ -183,19 +179,10 @@
   export PW_PROJECT_ROOT
 }
 
-deactivate() {
-  pw_deactivate
-  unset -f pw_deactivate
-  unset -f deactivate
-  unset PW_ROOT
-  unset PW_PROJECT_ROOT
-  unset PW_BRANDING_BANNER
-  unset PW_BRANDING_BANNER_COLOR
-}
-
 # The next three functions use the following variables.
 # * PW_BANNER_FUNC: function to print banner
 # * PW_BOOTSTRAP_PYTHON: specific Python interpreter to use for bootstrap
+# * PW_USE_GCS_ENVSETUP: attempt to grab env setup executable from GCS if "true"
 # * PW_ROOT: path to Pigweed root
 # * PW_ENVSETUP_QUIET: limit output if "true"
 #
@@ -206,29 +193,15 @@
 pw_bootstrap() {
   _pw_hello "  BOOTSTRAP! Bootstrap may take a few minutes; please be patient.\n"
 
-  local _pw_alias_check=0
-  alias python > /dev/null 2> /dev/null || _pw_alias_check=$?
-  if [ "$_pw_alias_check" -eq 0 ]; then
-    pw_bold_red "Error: 'python' is an alias"
-    pw_red "The shell has a 'python' alias set. This causes many obscure"
-    pw_red "Python-related issues both in and out of Pigweed. Please remove"
-    pw_red "the Python alias from your shell init file or at least run the"
-    pw_red "following command before bootstrapping Pigweed."
-    pw_red
-    pw_red "  unalias python"
-    pw_red
-    return
-  fi
-
   # Allow forcing a specific version of Python for testing pursposes.
   if [ -n "$PW_BOOTSTRAP_PYTHON" ]; then
     _PW_PYTHON="$PW_BOOTSTRAP_PYTHON"
-  elif command -v python3 > /dev/null 2> /dev/null; then
-    _PW_PYTHON=python3
-  elif command -v python2 > /dev/null 2> /dev/null; then
-    _PW_PYTHON=python2
-  elif command -v python > /dev/null 2> /dev/null; then
+  elif which python &> /dev/null; then
     _PW_PYTHON=python
+  elif which python3 &> /dev/null; then
+    _PW_PYTHON=python3
+  elif which python2 &> /dev/null; then
+    _PW_PYTHON=python2
   else
     pw_bold_red "Error: No system Python present\n"
     pw_red "  Pigweed's bootstrap process requires a local system Python."
@@ -237,6 +210,10 @@
     return
   fi
 
+  if [ -n "$PW_USE_GCS_ENVSETUP" ]; then
+    _PW_ENV_SETUP="$("$PW_ROOT/pw_env_setup/get_pw_env_setup.sh")"
+  fi
+
   if [ -n "$_PW_ENV_SETUP" ]; then
     "$_PW_ENV_SETUP" "$@"
     _PW_ENV_SETUP_STATUS="$?"
@@ -244,9 +221,6 @@
     "$_PW_PYTHON" "$PW_ROOT/pw_env_setup/py/pw_env_setup/env_setup.py" "$@"
     _PW_ENV_SETUP_STATUS="$?"
   fi
-
-  # Create the environment README file. Use quotes to prevent alias expansion.
-  "cp" "$PW_ROOT/pw_env_setup/destination.md" "$_PW_ACTUAL_ENVIRONMENT_ROOT/README.md"
 }
 
 pw_activate() {
@@ -267,15 +241,10 @@
 
     if [ "$?" -eq 0 ]; then
       if [ "$_PW_NAME" = "bootstrap" ] && [ -z "$PW_ENVSETUP_QUIET" ]; then
-        echo "To reactivate this environment in the future, run this in your "
+        echo "To activate this environment in the future, run this in your "
         echo "terminal:"
         echo
-        pw_green "  source ./activate.sh"
-        echo
-        echo "To deactivate this environment, run this:"
-        echo
-        pw_green "  deactivate"
-        echo
+        pw_green "  source ./activate.sh\n"
       fi
     else
       pw_red "Error during $_PW_NAME--see messages above."
@@ -288,7 +257,6 @@
 pw_cleanup() {
   unset _PW_BANNER
   unset _PW_BANNER_FUNC
-  unset PW_BANNER_FUNC
   unset _PW_ENV_SETUP
   unset _PW_NAME
   unset _PW_PYTHON
@@ -297,24 +265,23 @@
   unset _NEW_PW_ROOT
   unset _PW_ENV_SETUP_STATUS
 
-  unset -f pw_none
-  unset -f pw_red
-  unset -f pw_bold_red
-  unset -f pw_yellow
-  unset -f pw_bold_yellow
-  unset -f pw_green
-  unset -f pw_bold_green
-  unset -f pw_blue
-  unset -f pw_cyan
-  unset -f pw_magenta
-  unset -f pw_bold_white
-  unset -f pw_eval_sourced
-  unset -f pw_check_root
-  unset -f pw_get_env_root
-  unset -f _pw_banner
-  unset -f pw_bootstrap
-  unset -f pw_activate
-  unset -f pw_finalize
-  unset -f pw_cleanup
-  unset -f _pw_hello
+  unset pw_none
+  unset pw_red
+  unset pw_bold_red
+  unset pw_yellow
+  unset pw_bold_yellow
+  unset pw_green
+  unset pw_bold_green
+  unset pw_blue
+  unset pw_cyan
+  unset pw_magenta
+  unset pw_bold_white
+  unset pw_eval_sourced
+  unset pw_check_root
+  unset pw_get_env_root
+  unset _pw_banner
+  unset pw_bootstrap
+  unset pw_activate
+  unset pw_finalize
+  unset _pw_cleanup
 }
diff --git a/pw_file/BUILD.bazel b/pw_file/BUILD.bazel
deleted file mode 100644
index 7eda65c..0000000
--- a/pw_file/BUILD.bazel
+++ /dev/null
@@ -1,60 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-proto_library(
-    name = "proto",
-    srcs = ["file.proto"],
-)
-
-pw_cc_library(
-    name = "flat_file_system",
-    srcs = [
-        "flat_file_system.cc",
-    ],
-    hdrs = [
-        "public/pw_file/flat_file_system.h",
-    ],
-    deps = [
-        ":proto",
-        "//pw_bytes",
-        "//pw_result",
-        "//pw_rpc/raw:method",
-        "//pw_status",
-    ],
-)
-
-pw_cc_test(
-    name = "flat_file_system_test",
-    srcs = [
-        "flat_file_system_test.cc",
-    ],
-    deps = [
-        ":flat_file_system",
-        ":proto",
-        "//pw_bytes",
-        "//pw_protobuf",
-        "//pw_rpc/raw:test_method_context",
-        "//pw_status",
-    ],
-)
diff --git a/pw_file/BUILD.gn b/pw_file/BUILD.gn
deleted file mode 100644
index e36b182..0000000
--- a/pw_file/BUILD.gn
+++ /dev/null
@@ -1,65 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_protobuf_compiler/proto.gni")
-import("$dir_pw_unit_test/test.gni")
-
-config("public_includes") {
-  include_dirs = [ "public" ]
-}
-
-pw_proto_library("proto") {
-  sources = [ "file.proto" ]
-  prefix = "pw_file"
-  deps = [ "$dir_pw_protobuf:common_protos" ]
-}
-
-pw_source_set("flat_file_system") {
-  public_deps = [
-    ":proto.pwpb",
-    ":proto.raw_rpc",
-    dir_pw_assert,
-    dir_pw_bytes,
-    dir_pw_log,
-    dir_pw_result,
-    dir_pw_status,
-  ]
-  public_configs = [ ":public_includes" ]
-  public = [ "public/pw_file/flat_file_system.h" ]
-  sources = [ "flat_file_system.cc" ]
-}
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-  inputs = [ "file.proto" ]
-}
-
-pw_test_group("tests") {
-  tests = [ ":flat_file_system_test" ]
-}
-
-pw_test("flat_file_system_test") {
-  deps = [
-    ":flat_file_system",
-    ":proto.pwpb",
-    "$dir_pw_rpc/raw:test_method_context",
-    dir_pw_bytes,
-    dir_pw_protobuf,
-    dir_pw_status,
-  ]
-  sources = [ "flat_file_system_test.cc" ]
-}
diff --git a/pw_file/CMakeLists.txt b/pw_file/CMakeLists.txt
deleted file mode 100644
index 646931a..0000000
--- a/pw_file/CMakeLists.txt
+++ /dev/null
@@ -1,38 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-include($ENV{PW_ROOT}/pw_protobuf_compiler/proto.cmake)
-
-pw_add_module_library(pw_file.flat_file_system
-  PUBLIC_DEPS
-    pw_file.proto.pwpb
-    pw_file.proto.raw_rpc
-    pw_assert
-    pw_bytes
-    pw_log
-    pw_result
-    pw_status
-  TEST_DEPS
-    pw_rpc.test_utils
-)
-
-pw_proto_library(pw_file.proto
-  SOURCES
-    file.proto
-  DEPS
-     pw_protobuf.common_protos
-  PREFIX
-    pw_file
-)
diff --git a/pw_file/OWNERS b/pw_file/OWNERS
deleted file mode 100644
index 307b1de..0000000
--- a/pw_file/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-amontanez@google.com
diff --git a/pw_file/docs.rst b/pw_file/docs.rst
deleted file mode 100644
index c961466..0000000
--- a/pw_file/docs.rst
+++ /dev/null
@@ -1,59 +0,0 @@
-.. _module-pw_file:
-
-=======
-pw_file
-=======
-
-.. attention::
-
-  ``pw_file`` is under construction, and may see significant breaking API
-  changes.
-
-The ``pw_file`` module defines a service for file system-like interactions
-between a client and server. FileSystem services may be backed by true file
-systems, or by virtual file systems that provide a file-system like interface
-with no true underlying file system.
-
-pw_file does not define a protocol for file transfers.
-:ref:`module-pw_transfer` provides a generalized mechanism for
-performing file transfers, and is recommended to be used in tandem with pw_file.
-
------------
-RPC service
------------
-The FileSystem RPC service is oriented to allow direct interaction, and has no
-sequenced protocol. Unlike FTP, all interactions are stateless. This service
-also does not yet define any authentication mechanism, meaning that all clients
-that can access a FileSystem service are granted equal permissions.
-
-.. literalinclude:: file.proto
-  :language: protobuf
-  :lines: 14-
-
-------------------------------
-Flat FileSystem implementation
-------------------------------
-This module provides the ``FlatFileSystemService``, an optional implementation
-of the FileSystem RPC service with a virtual interface that allows different
-data storage implementations to expose logical files. As the name implies, the
-file system is treated as a flat file system; it does not support any
-directory-like interactions.
-
-The ``FlatFileSystemService`` implementation requires a static, fixed list of
-``Entry`` pointers. Each ``Entry`` represents a potential
-file, and acts as an interface boundary that is backed by some kind of storage
-mechanism (e.g. ``BlobStore``, ``PersistentBuffer``).
-
-All ``Entry`` objects that should be enumerated by a
-``FlatFileSystemService`` MUST be named, and names must be globally unique to
-prevent ambiguity. Unnamed file entries will NOT be enumerated by a
-``FlatFileSystemService``, and are considered empty/deleted files. It is valid
-to have empty files that are enumerated with a name.
-
-``FlatFileSystemService`` requires two buffers at construction: one buffer for
-reading file names and another for encoding protobuf responses. The recommended
-encoding buffer size for a particular maximum file name length can be calculated
-with ``EncodingBufferSizeBytes``. For convenience, the
-``FlatFileSystemServiceWithBuffer<kMaxFileNameLength>`` class is provided. That
-class creates a ``FlatFileSystemService`` with a buffer automatically sized
-based on the maximum file name length.
diff --git a/pw_file/file.proto b/pw_file/file.proto
deleted file mode 100644
index ce41900..0000000
--- a/pw_file/file.proto
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-syntax = "proto3";
-
-package pw.file;
-
-import "pw_protobuf_protos/common.proto";
-
-option java_outer_classname = "File";
-
-// The FileSystem RPC service is used to enumerate and manage files present on a
-// server.
-service FileSystem {
-  // Returns a series of file paths with associated metadata for all immediate
-  // children of the provided path.
-  rpc List(ListRequest) returns (stream ListResponse) {}
-
-  // Deletes the file at the requested path.
-  rpc Delete(DeleteRequest) returns (pw.protobuf.Empty) {}
-}
-
-// A ListRequest has the following properties:
-//
-//  - A request with an empty `path` string is valid and will list the contents
-//    at the "root" directory.
-//  - Only exact path matches will be resolved (i.e. no prefix matching).
-//  - Paths should be treated as case-sensitive.
-//  - The provided path must be absolute. If no matching path is found, a
-//    NOT_FOUND error is raised.
-message ListRequest {
-  string path = 1;
-}
-
-// A DeleteRequest has the following properties:
-//
-//  - Only exact path matches will be resolved (i.e. no prefix matching).
-//  - Paths should be treated as case-sensitive.
-//  - Deletion of directories is implementation-defined, and may be
-//    disallowed and return an UNIMPLEMENTED error.
-//  - The provided path must be absolute. If no matching path is found, a
-//    NOT_FOUND error is raised.
-message DeleteRequest {
-  string path = 1;
-}
-
-message Path {
-  // This enum is a bitmask of permissions:
-  // Bit 0: readable.
-  // Bit 1: writable.
-  enum Permissions {
-    NONE = 0;
-    READ = 1;
-    WRITE = 2;
-    READ_AND_WRITE = 3;
-  }
-
-  // A path to a file/directory. This path is relative to the requested path
-  // to reduce transmission of redundant information.
-  string path = 1;
-
-  // Permitted operations on this path.
-  optional Permissions permissions = 2;
-
-  // The size of the file at this path.
-  optional uint32 size_bytes = 3;
-
-  // A globally-unique transfer ID for this file path (e.g. for use with
-  // pw_transfer's RPC service). It is implementation defined whether a file's
-  // file ID is stable or ephemeral.
-  optional uint32 file_id = 4;
-}
-
-message ListResponse {
-  // Each returned Path's path name is always relative to the requested path to
-  // reduce transmission of redundant information.
-  repeated Path paths = 1;
-}
diff --git a/pw_file/flat_file_system.cc b/pw_file/flat_file_system.cc
deleted file mode 100644
index 48aaf69..0000000
--- a/pw_file/flat_file_system.cc
+++ /dev/null
@@ -1,183 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 PW_LOG_MODULE_NAME "FS"
-
-#include "pw_file/flat_file_system.h"
-
-#include <cstddef>
-#include <cstdint>
-#include <span>
-#include <string_view>
-
-#include "pw_assert/check.h"
-#include "pw_bytes/span.h"
-#include "pw_file/file.pwpb.h"
-#include "pw_log/log.h"
-#include "pw_protobuf/decoder.h"
-#include "pw_protobuf/encoder.h"
-#include "pw_protobuf/serialized_size.h"
-#include "pw_rpc/raw/server_reader_writer.h"
-#include "pw_status/status.h"
-#include "pw_status/status_with_size.h"
-
-namespace pw::file {
-
-using Entry = FlatFileSystemService::Entry;
-
-Status FlatFileSystemService::EnumerateFile(
-    Entry& entry, pw::file::ListResponse::StreamEncoder& output_encoder) {
-  StatusWithSize sws = entry.Name(file_name_buffer_);
-  if (!sws.ok()) {
-    return sws.status();
-  }
-  {
-    pw::file::Path::StreamEncoder encoder = output_encoder.GetPathsEncoder();
-
-    encoder
-        .WritePath(reinterpret_cast<const char*>(file_name_buffer_.data()),
-                   sws.size())
-        .IgnoreError();
-    encoder.WriteSizeBytes(entry.SizeBytes()).IgnoreError();
-    encoder.WritePermissions(entry.Permissions()).IgnoreError();
-    encoder.WriteFileId(entry.FileId()).IgnoreError();
-  }
-  return output_encoder.status();
-}
-
-void FlatFileSystemService::EnumerateAllFiles(RawServerWriter& writer) {
-  for (Entry* entry : entries_) {
-    PW_DCHECK_NOTNULL(entry);
-    // For now, don't try to pack entries.
-    pw::file::ListResponse::MemoryEncoder encoder(encoding_buffer_);
-    if (Status status = EnumerateFile(*entry, encoder); !status.ok()) {
-      if (status != Status::NotFound()) {
-        PW_LOG_ERROR("Failed to enumerate file (id: %u) with status %d",
-                     static_cast<unsigned>(entry->FileId()),
-                     static_cast<int>(status.code()));
-      }
-      continue;
-    }
-
-    Status write_status = writer.Write(encoder);
-    if (!write_status.ok()) {
-      writer.Finish(write_status)
-          .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-      return;
-    }
-  }
-  writer.Finish(OkStatus())
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-}
-
-void FlatFileSystemService::List(ConstByteSpan request,
-                                 RawServerWriter& writer) {
-  protobuf::Decoder decoder(request);
-  // If a file name was provided, try and find and enumerate the file.
-  while (decoder.Next().ok()) {
-    if (decoder.FieldNumber() !=
-        static_cast<uint32_t>(pw::file::ListRequest::Fields::PATH)) {
-      continue;
-    }
-
-    std::string_view file_name_view;
-    if (!decoder.ReadString(&file_name_view).ok() ||
-        file_name_view.length() == 0) {
-      writer.Finish(Status::DataLoss())
-          .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-      return;
-    }
-
-    // Find and enumerate the file requested.
-    Result<Entry*> result = FindFile(file_name_view);
-    if (!result.ok()) {
-      writer.Finish(result.status())
-          .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-      return;
-    }
-
-    pw::file::ListResponse::MemoryEncoder encoder(encoding_buffer_);
-    Status proto_encode_status = EnumerateFile(*result.value(), encoder);
-    if (!proto_encode_status.ok()) {
-      writer.Finish(proto_encode_status)
-          .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-      return;
-    }
-
-    writer.Finish(writer.Write(encoder))
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    return;
-  }
-
-  // If no path was provided in the ListRequest, just enumerate everything.
-  EnumerateAllFiles(writer);
-}
-
-void FlatFileSystemService::Delete(ConstByteSpan request,
-                                   rpc::RawUnaryResponder& responder) {
-  protobuf::Decoder decoder(request);
-  while (decoder.Next().ok()) {
-    if (decoder.FieldNumber() !=
-        static_cast<uint32_t>(pw::file::DeleteRequest::Fields::PATH)) {
-      continue;
-    }
-
-    std::string_view file_name_view;
-    if (!decoder.ReadString(&file_name_view).ok()) {
-      responder.Finish({}, Status::DataLoss()).IgnoreError();
-      return;
-    }
-    responder.Finish({}, FindAndDeleteFile(file_name_view)).IgnoreError();
-    return;
-  }
-  responder.Finish({}, Status::InvalidArgument()).IgnoreError();
-}
-
-Result<Entry*> FlatFileSystemService::FindFile(std::string_view file_name) {
-  Status search_status;
-  for (Entry* entry : entries_) {
-    PW_DCHECK_NOTNULL(entry);
-    StatusWithSize sws = entry->Name(file_name_buffer_);
-
-    // If there not an exact file name length match, don't try and check against
-    // a prefix.
-    if (!sws.ok() || file_name.length() != sws.size()) {
-      if (sws.status() != Status::NotFound()) {
-        PW_LOG_ERROR("Failed to read file name (id: %u) with status %d",
-                     static_cast<unsigned>(entry->FileId()),
-                     static_cast<int>(sws.status().code()));
-      }
-      continue;
-    }
-
-    if (memcmp(file_name.data(), file_name_buffer_.data(), file_name.size()) ==
-        0) {
-      return entry;
-    }
-  }
-
-  search_status.Update(Status::NotFound());
-  return search_status;
-}
-
-Status FlatFileSystemService::FindAndDeleteFile(std::string_view file_name) {
-  Result<Entry*> result = FindFile(file_name);
-  if (!result.ok()) {
-    return result.status();
-  }
-
-  return result.value()->Delete();
-}
-
-}  // namespace pw::file
diff --git a/pw_file/flat_file_system_test.cc b/pw_file/flat_file_system_test.cc
deleted file mode 100644
index 204391c..0000000
--- a/pw_file/flat_file_system_test.cc
+++ /dev/null
@@ -1,232 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_file/flat_file_system.h"
-
-#include <array>
-#include <cstddef>
-#include <cstdint>
-#include <span>
-#include <string_view>
-
-#include "gtest/gtest.h"
-#include "pw_bytes/span.h"
-#include "pw_file/file.pwpb.h"
-#include "pw_protobuf/decoder.h"
-#include "pw_rpc/raw/test_method_context.h"
-#include "pw_status/status.h"
-#include "pw_status/status_with_size.h"
-
-namespace pw::file {
-namespace {
-
-class FakeFile : public FlatFileSystemService::Entry {
- public:
-  constexpr FakeFile(std::string_view file_name, size_t size, uint32_t file_id)
-      : name_(file_name), size_(size), file_id_(file_id) {}
-
-  StatusWithSize Name(std::span<char> dest) override {
-    if (name_.empty()) {
-      return StatusWithSize(Status::NotFound(), 0);
-    }
-
-    size_t bytes_to_copy = std::min(dest.size_bytes(), name_.size());
-    memcpy(dest.data(), name_.data(), bytes_to_copy);
-    if (bytes_to_copy != name_.size()) {
-      return StatusWithSize(Status::ResourceExhausted(), bytes_to_copy);
-    }
-
-    return StatusWithSize(OkStatus(), bytes_to_copy);
-  }
-
-  size_t SizeBytes() override { return size_; }
-
-  FlatFileSystemService::Entry::FilePermissions Permissions() const override {
-    return FlatFileSystemService::Entry::FilePermissions::NONE;
-  }
-
-  Status Delete() override { return Status::Unimplemented(); }
-
-  FlatFileSystemService::Entry::Id FileId() const override { return file_id_; }
-
- private:
-  std::string_view name_;
-  size_t size_;
-  uint32_t file_id_;
-};
-
-bool EntryHasName(FlatFileSystemService::Entry* entry) {
-  std::array<char, 4> expected_name;
-  StatusWithSize file_name_sws = entry->Name(expected_name);
-  return file_name_sws.size() != 0;
-}
-
-// Compares a serialized Path message to a flat file system entry.
-void ComparePathToEntry(ConstByteSpan serialized_path,
-                        FlatFileSystemService::Entry* entry) {
-  std::array<char, 64> expected_name;
-  StatusWithSize file_name_sws = entry->Name(expected_name);
-
-  // A partial name read shouldn't happen.
-  ASSERT_EQ(OkStatus(), file_name_sws.status());
-
-  protobuf::Decoder decoder(serialized_path);
-  while (decoder.Next().ok()) {
-    switch (decoder.FieldNumber()) {
-      case static_cast<uint32_t>(pw::file::Path::Fields::PATH): {
-        std::string_view serialized_name;
-        EXPECT_EQ(OkStatus(), decoder.ReadString(&serialized_name));
-        size_t name_bytes_to_read =
-            std::min(serialized_name.size(), file_name_sws.size());
-        EXPECT_EQ(0,
-                  memcmp(expected_name.data(),
-                         serialized_name.data(),
-                         name_bytes_to_read));
-        break;
-      }
-
-      case static_cast<uint32_t>(pw::file::Path::Fields::PERMISSIONS): {
-        uint32_t seralized_permissions;
-        EXPECT_EQ(OkStatus(), decoder.ReadUint32(&seralized_permissions));
-        EXPECT_EQ(static_cast<uint32_t>(entry->Permissions()),
-                  seralized_permissions);
-        break;
-      }
-
-      case static_cast<uint32_t>(pw::file::Path::Fields::SIZE_BYTES): {
-        uint32_t serialized_file_size;
-        EXPECT_EQ(OkStatus(), decoder.ReadUint32(&serialized_file_size));
-        EXPECT_EQ(static_cast<uint32_t>(entry->SizeBytes()),
-                  serialized_file_size);
-        break;
-      }
-
-      case static_cast<uint32_t>(pw::file::Path::Fields::FILE_ID): {
-        uint32_t serialized_file_id;
-        EXPECT_EQ(OkStatus(), decoder.ReadUint32(&serialized_file_id));
-        EXPECT_EQ(static_cast<uint32_t>(entry->FileId()), serialized_file_id);
-        break;
-      }
-
-      default:
-        // unexpected result.
-        // TODO something here.
-        break;
-    }
-  }
-}
-
-size_t ValidateExpectedPaths(
-    std::span<FlatFileSystemService::Entry*> flat_file_system,
-    const rpc::PayloadsView& results) {
-  size_t serialized_path_entry_count = 0;
-  size_t file_system_index = 0;
-  for (ConstByteSpan response : results) {
-    protobuf::Decoder decoder(response);
-    while (decoder.Next().ok()) {
-      constexpr uint32_t kListResponsePathsFieldNumber =
-          static_cast<uint32_t>(pw::file::ListResponse::Fields::PATHS);
-      EXPECT_EQ(decoder.FieldNumber(), kListResponsePathsFieldNumber);
-      if (decoder.FieldNumber() != kListResponsePathsFieldNumber) {
-        return 0;
-      }
-
-      serialized_path_entry_count++;
-
-      // Skip any file system entries without names.
-      while (!EntryHasName(flat_file_system[file_system_index])) {
-        file_system_index++;
-        EXPECT_GT(flat_file_system.size(), file_system_index);
-      }
-
-      // There's a 1:1 mapping in the same order for all files that have a name.
-      ConstByteSpan serialized_path;
-      EXPECT_EQ(OkStatus(), decoder.ReadBytes(&serialized_path));
-      ComparePathToEntry(serialized_path,
-                         flat_file_system[file_system_index++]);
-    }
-  }
-  return serialized_path_entry_count;
-}
-
-TEST(FlatFileSystem, EncodingBufferSizeBytes) {
-  EXPECT_EQ(FlatFileSystemService::EncodingBufferSizeBytes(10),
-            2u /* path nested message key and size */ + 12 /* path */ +
-                2 /* permissions */ + 6 /* size_bytes */ + 6 /* file_id */);
-  EXPECT_EQ(FlatFileSystemService::EncodingBufferSizeBytes(10, 2),
-            2 * (1u + 1 + 12 + 2 + 6 + 6));
-  EXPECT_EQ(FlatFileSystemService::EncodingBufferSizeBytes(100, 3),
-            3 * (1u + 1 + 102 + 2 + 6 + 6));
-}
-
-TEST(FlatFileSystem, List_NoFiles) {
-  PW_RAW_TEST_METHOD_CONTEXT(FlatFileSystemServiceWithBuffer<1>, List)
-  ctx{std::span<FlatFileSystemService::Entry*>()};
-  ctx.call(ConstByteSpan());
-
-  EXPECT_TRUE(ctx.done());
-  EXPECT_EQ(OkStatus(), ctx.status());
-  EXPECT_EQ(0u, ctx.responses().size());
-}
-
-TEST(FlatFileSystem, List_OneFile) {
-  FakeFile file{"compressed.zip.gz", 2, 1231};
-  std::array<FlatFileSystemService::Entry*, 1> static_file_system{&file};
-
-  PW_RAW_TEST_METHOD_CONTEXT(FlatFileSystemServiceWithBuffer<20>, List)
-  ctx(static_file_system);
-  ctx.call(ConstByteSpan());
-
-  EXPECT_EQ(1u, ValidateExpectedPaths(static_file_system, ctx.responses()));
-}
-
-TEST(FlatFileSystem, List_ThreeFiles) {
-  std::array<FakeFile, 3> files{
-      {{"SNAP_001", 372, 9}, {"tokens.csv", 808, 15038202}, {"a.txt", 0, 2}}};
-  std::array<FlatFileSystemService::Entry*, 3> static_file_system{
-      &files[0], &files[1], &files[2]};
-
-  PW_RAW_TEST_METHOD_CONTEXT(FlatFileSystemServiceWithBuffer<10>, List)
-  ctx(static_file_system);
-  ctx.call(ConstByteSpan());
-
-  EXPECT_EQ(3u, ValidateExpectedPaths(static_file_system, ctx.responses()));
-}
-
-TEST(FlatFileSystem, List_UnnamedFile) {
-  FakeFile file{"", 0, 0};
-  std::array<FlatFileSystemService::Entry*, 1> static_file_system{&file};
-
-  PW_RAW_TEST_METHOD_CONTEXT(FlatFileSystemServiceWithBuffer<10>, List)
-  ctx(static_file_system);
-  ctx.call(ConstByteSpan());
-
-  EXPECT_EQ(0u, ValidateExpectedPaths(static_file_system, ctx.responses()));
-}
-
-TEST(FlatFileSystem, List_FileMissingName) {
-  std::array<FakeFile, 3> files{
-      {{"SNAP_001", 372, 9}, {"", 808, 15038202}, {"a.txt", 0, 2}}};
-  std::array<FlatFileSystemService::Entry*, 3> static_file_system{
-      &files[0], &files[1], &files[2]};
-
-  PW_RAW_TEST_METHOD_CONTEXT(FlatFileSystemServiceWithBuffer<10>, List)
-  ctx(static_file_system);
-  ctx.call(ConstByteSpan());
-
-  EXPECT_EQ(2u, ValidateExpectedPaths(static_file_system, ctx.responses()));
-}
-
-}  // namespace
-}  // namespace pw::file
diff --git a/pw_file/public/pw_file/flat_file_system.h b/pw_file/public/pw_file/flat_file_system.h
deleted file mode 100644
index 1035dbd..0000000
--- a/pw_file/public/pw_file/flat_file_system.h
+++ /dev/null
@@ -1,152 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstddef>
-#include <cstdint>
-#include <span>
-#include <string_view>
-
-#include "pw_bytes/span.h"
-#include "pw_file/file.pwpb.h"
-#include "pw_file/file.raw_rpc.pb.h"
-#include "pw_protobuf/serialized_size.h"
-#include "pw_result/result.h"
-#include "pw_rpc/raw/server_reader_writer.h"
-#include "pw_status/status.h"
-#include "pw_status/status_with_size.h"
-
-namespace pw::file {
-
-// This implements the pw.file.FileSystem RPC service. This implementation
-// has a strict limitation that everything is treated as if the file system
-// was "flat" (i.e. no directories). This means there's no concept of logical
-// directories, despite any "path like" naming that may be employed by a user.
-class FlatFileSystemService
-    : public pw_rpc::raw::FileSystem::Service<FlatFileSystemService> {
- public:
-  class Entry {
-   public:
-    using FilePermissions = pw::file::Path::Permissions;
-    using Id = uint32_t;
-
-    Entry() = default;
-    virtual ~Entry() = default;
-
-    // All readable files MUST be named, and names must be globally unique to
-    // prevent ambiguity. Unnamed file entries will NOT be enumerated by a
-    // FlatFileSystemService. The returned status must indicate the length
-    // of the string written to `dest`, and should NOT include any null
-    // terminator that may have been written.
-    //
-    // Note: The bounded string written to `dest` is not expected to be
-    // null-terminated, and should be treated like a std::string_view.
-    //
-    // Returns:
-    //   OK - Successfully read file name to `dest`.
-    //   NOT_FOUND - No file to enumerate for this entry.
-    //   RESOURCE_EXHAUSTED - `dest` buffer too small to fit the full file name.
-    virtual StatusWithSize Name(std::span<char> dest) = 0;
-
-    virtual size_t SizeBytes() = 0;
-    virtual FilePermissions Permissions() const = 0;
-
-    // Deleting a file, if allowed, should cause the backing data store to be
-    // cleared. Read-only files should also no longer enumerate (i.e. Name()
-    // should return NOT_FOUND). Write-only and read/write files may still
-    // enumerate but with SizeBytes() reporting zero.
-    virtual Status Delete() = 0;
-
-    // File IDs must be globally unique, and map to a pw_transfer
-    // TransferService read/write handler.
-    virtual Id FileId() const = 0;
-  };
-
-  // Returns the size of encoding buffer guaranteed to support encoding
-  // minimum_entries paths with file names up max_file_name_length.
-  static constexpr size_t EncodingBufferSizeBytes(size_t max_file_name_length,
-                                                  size_t minimum_entries = 1) {
-    return minimum_entries *
-           protobuf::SizeOfDelimitedField(
-               ListResponse::Fields::PATHS,
-               EncodedPathProtoSizeBytes(max_file_name_length));
-  }
-
-  // Constructs a flat file system from a static list of file entries.
-  //
-  // Args:
-  //   entry_list - A list of pointers to all Entry objects that may
-  //     contain files. These pointers may not be null. The span's underlying
-  //     buffer must outlive this object.
-  //   encoding_buffer - Used internally by this class to encode its responses.
-  //   file_name_buffer - Used internally by this class to find and enumerate
-  //     files. Should be large enough to hold the longest expected file name.
-  //     The span's underlying buffer must outlive this object.
-  //   max_file_name_length - Number of bytes to reserve for the file name.
-  constexpr FlatFileSystemService(std::span<Entry*> entry_list,
-                                  std::span<std::byte> encoding_buffer,
-                                  std::span<char> file_name_buffer)
-      : encoding_buffer_(encoding_buffer),
-        file_name_buffer_(file_name_buffer),
-        entries_(entry_list) {}
-
-  // Method definitions for pw.file.FileSystem.
-  void List(ConstByteSpan request, RawServerWriter& writer);
-
-  // Returns:
-  //   OK - File successfully deleted.
-  //   NOT_FOUND - Could not find
-  void Delete(ConstByteSpan request, rpc::RawUnaryResponder& responder);
-
- private:
-  // Returns the maximum size of a single encoded Path proto.
-  static constexpr size_t EncodedPathProtoSizeBytes(
-      size_t max_file_name_length) {
-    return protobuf::SizeOfFieldString(Path::Fields::PATH,
-                                       max_file_name_length) +
-           protobuf::SizeOfFieldEnum(Path::Fields::PERMISSIONS,
-                                     Path::Permissions::READ_AND_WRITE) +
-           protobuf::SizeOfFieldUint32(Path::Fields::SIZE_BYTES) +
-           protobuf::SizeOfFieldUint32(Path::Fields::FILE_ID);
-  }
-
-  Result<Entry*> FindFile(std::string_view file_name);
-  Status FindAndDeleteFile(std::string_view file_name);
-
-  Status EnumerateFile(Entry& entry,
-                       pw::file::ListResponse::StreamEncoder& output_encoder);
-  void EnumerateAllFiles(RawServerWriter& writer);
-
-  const std::span<std::byte> encoding_buffer_;
-  const std::span<char> file_name_buffer_;
-  const std::span<Entry*> entries_;
-};
-
-// Provides the encoding and file name buffers to a FlatFileSystemService.
-template <unsigned kMaxFileNameLength,
-          unsigned kMinGuaranteedEntriesPerResponse = 1>
-class FlatFileSystemServiceWithBuffer : public FlatFileSystemService {
- public:
-  constexpr FlatFileSystemServiceWithBuffer(std::span<Entry*> entry_list)
-      : FlatFileSystemService(entry_list, encoding_buffer_, file_name_buffer_) {
-  }
-
- private:
-  static_assert(kMaxFileNameLength > 0u);
-
-  std::byte encoding_buffer_[EncodingBufferSizeBytes(
-      kMaxFileNameLength, kMinGuaranteedEntriesPerResponse)];
-  char file_name_buffer_[kMaxFileNameLength];
-};
-}  // namespace pw::file
diff --git a/pw_function/BUILD.bazel b/pw_function/BUILD.bazel
deleted file mode 100644
index 273ae58..0000000
--- a/pw_function/BUILD.bazel
+++ /dev/null
@@ -1,47 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "config",
-    hdrs = ["public/pw_function/config.h"],
-    includes = ["public"],
-)
-
-pw_cc_library(
-    name = "pw_function",
-    srcs = ["public/pw_function/internal/function.h"],
-    hdrs = ["public/pw_function/function.h"],
-    includes = ["public"],
-    deps = [
-        ":config",
-        "//pw_assert",
-        "//pw_preprocessor",
-    ],
-)
-
-pw_cc_test(
-    name = "function_test",
-    srcs = ["function_test.cc"],
-    deps = [":pw_function"],
-)
diff --git a/pw_function/BUILD.gn b/pw_function/BUILD.gn
deleted file mode 100644
index 17befd1..0000000
--- a/pw_function/BUILD.gn
+++ /dev/null
@@ -1,120 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_bloat/bloat.gni")
-import("$dir_pw_build/module_config.gni")
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_unit_test/test.gni")
-
-declare_args() {
-  # The build target that overrides the default configuration options for this
-  # module. This should point to a source set that provides defines through a
-  # public config (which may -include a file or add defines directly).
-  pw_function_CONFIG = pw_build_DEFAULT_MODULE_CONFIG
-}
-
-config("public_include_path") {
-  include_dirs = [ "public" ]
-  visibility = [ ":*" ]
-}
-
-pw_source_set("config") {
-  public = [ "public/pw_function/config.h" ]
-  public_configs = [ ":public_include_path" ]
-  public_deps = [ pw_function_CONFIG ]
-  visibility = [ ":*" ]
-}
-
-pw_source_set("pw_function") {
-  public_configs = [ ":public_include_path" ]
-  public_deps = [
-    ":config",
-    dir_pw_assert,
-    dir_pw_preprocessor,
-  ]
-  public = [ "public/pw_function/function.h" ]
-  sources = [ "public/pw_function/internal/function.h" ]
-}
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-  report_deps = [
-    ":callable_size",
-    ":function_size",
-  ]
-}
-
-pw_test_group("tests") {
-  tests = [ ":function_test" ]
-}
-
-pw_test("function_test") {
-  deps = [
-    ":pw_function",
-    dir_pw_polyfill,
-  ]
-  sources = [ "function_test.cc" ]
-}
-
-pw_size_report("function_size") {
-  title = "Pigweed function size report"
-
-  binaries = [
-    {
-      target = "size_report:basic_function"
-      base = "size_report:pointer_base"
-      label = "Simple pw::Function vs. function pointer"
-    },
-  ]
-}
-
-pw_size_report("callable_size") {
-  title = "Size comparison of callable objects"
-
-  binaries = [
-    {
-      target = "size_report:callable_size_function_pointer"
-      base = "size_report:callable_size_base"
-      label = "Function pointer"
-    },
-    {
-      target = "size_report:callable_size_static_lambda"
-      base = "size_report:callable_size_base"
-      label = "Static lambda (operator+)"
-    },
-    {
-      target = "size_report:callable_size_simple_lambda"
-      base = "size_report:callable_size_base"
-      label = "Non-capturing lambda"
-    },
-    {
-      target = "size_report:callable_size_capturing_lambda"
-      base = "size_report:callable_size_base"
-      label = "Simple capturing lambda"
-    },
-    {
-      target = "size_report:callable_size_multi_capturing_lambda"
-      base = "size_report:callable_size_base"
-      label = "Multi-argument capturing lambda"
-    },
-    {
-      target = "size_report:callable_size_custom_class"
-      base = "size_report:callable_size_base"
-      label = "Custom class"
-    },
-  ]
-}
diff --git a/pw_function/CMakeLists.txt b/pw_function/CMakeLists.txt
deleted file mode 100644
index 0dc579e..0000000
--- a/pw_function/CMakeLists.txt
+++ /dev/null
@@ -1,52 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-
-pw_add_module_config(pw_function_CONFIG)
-
-pw_add_module_library(pw_function.config
-  HEADERS
-    public/pw_function/config.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    ${pw_function_CONFIG}
-)
-
-pw_add_module_library(pw_function
-  HEADERS
-    public/pw_function/function.h
-    public/pw_function/internal/function.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_assert
-    pw_function.config
-    pw_preprocessor
-)
-if(Zephyr_FOUND AND CONFIG_PIGWEED_FUNCTION)
-  zephyr_link_libraries(pw_function)
-endif()
-
-pw_add_test(pw_function.function_test
-  SOURCES
-    function_test.cc
-  DEPS
-    pw_function
-    pw_polyfill
-  GROUPS
-    modules
-    pw_function
-)
diff --git a/pw_function/Kconfig b/pw_function/Kconfig
deleted file mode 100644
index e9ff1ef..0000000
--- a/pw_function/Kconfig
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-config PIGWEED_FUNCTION
-    bool "Enable the Pigweed function library (pw_function)"
-    select PIGWEED_ASSERT
-    select PIGWEED_PREPROCESSOR
diff --git a/pw_function/OWNERS b/pw_function/OWNERS
deleted file mode 100644
index 639b747..0000000
--- a/pw_function/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-amontanez@google.com
-ewout@google.com
-hepler@google.com
diff --git a/pw_function/docs.rst b/pw_function/docs.rst
deleted file mode 100644
index 14ce906..0000000
--- a/pw_function/docs.rst
+++ /dev/null
@@ -1,253 +0,0 @@
-.. _module-pw_function:
-
------------
-pw_function
------------
-The function module provides a standard, general-purpose API for wrapping
-callable objects.
-
-.. note::
-  This module is under construction and its API is not complete.
-
-Overview
-========
-
-Basic usage
------------
-``pw_function`` defines the ``pw::Function`` class. A ``Function`` is a
-move-only callable wrapper constructable from any callable object. Functions
-are templated on the signature of the callable they store.
-
-Functions implement the call operator --- invoking the object will forward to
-the stored callable.
-
-.. code-block:: c++
-
-  int Add(int a, int b) { return a + b; }
-
-  // Construct a Function object from a function pointer.
-  pw::Function<int(int, int)> add_function(Add);
-
-  // Invoke the function object.
-  int result = add_function(3, 5);
-  EXPECT_EQ(result, 8);
-
-  // Construct a function from a lambda.
-  pw::Function<int(int)> negate([](int value) { return -value; });
-  EXPECT_EQ(negate(27), -27);
-
-Functions are nullable. Invoking a null function triggers a runtime assert.
-
-.. code-block:: c++
-
-  // A function intialized without a callable is implicitly null.
-  pw::Function<void()> null_function;
-
-  // Null functions may also be explicitly created or set.
-  pw::Function<void()> explicit_null_function(nullptr);
-
-  pw::Function<void()> function([]() {});  // Valid (non-null) function.
-  function = nullptr;  // Set to null, clearing the stored callable.
-
-  // Functions are comparable to nullptr.
-  if (function != nullptr) {
-    function();
-  }
-
-``pw::Function``'s default constructor is ``constexpr``, so default-constructed
-functions may be used in classes with ``constexpr`` constructors and in
-``constinit`` expressions.
-
-.. code-block:: c++
-
-  class MyClass {
-   public:
-    // Default construction of a pw::Function is constexpr.
-    constexpr MyClass() { ... }
-
-    pw::Function<void(int)> my_function;
-  };
-
-  // pw::Function and classes that use it may be constant initialized.
-  constinit MyClass instance;
-
-Storage
--------
-By default, a ``Function`` stores its callable inline within the object. The
-inline storage size defaults to the size of two pointers, but is configurable
-through the build system. The size of a ``Function`` object is equivalent to its
-inline storage size.
-
-Attempting to construct a function from a callable larger than its inline size
-is a compile-time error.
-
-.. admonition:: Inline storage size
-
-  The default inline size of two pointers is sufficient to store most common
-  callable objects, including function pointers, simple non-capturing and
-  capturing lambdas, and lightweight custom classes.
-
-.. code-block:: c++
-
-  // The lambda is moved into the function's internal storage.
-  pw::Function<int(int, int)> subtract([](int a, int b) { return a - b; });
-
-  // Functions can be also be constructed from custom classes that implement
-  // operator(). This particular object is large (8 ints of space).
-  class MyCallable {
-   public:
-    int operator()(int value);
-
-   private:
-    int data_[8];
-  };
-
-  // Compiler error: sizeof(MyCallable) exceeds function's inline storage size.
-  pw::Function<int(int)> function((MyCallable()));
-
-..
-  For larger callables, a ``Function`` can be constructed with an external buffer
-  in which the callable should be stored. The user must ensure that the lifetime
-  of the buffer exceeds that of the function object.
-
-  .. code-block:: c++
-
-    // Initialize a function with an external 16-byte buffer in which to store its
-    // callable. The callable will be stored in the buffer regardless of whether
-    // it fits inline.
-    pw::FunctionStorage<16> storage;
-    pw::Function<int()> get_random_number([]() { return 4; }, storage);
-
-  .. admonition:: External storage
-
-    Functions which use external storage still take up the configured inline
-    storage size, which should be accounted for when storing function objects.
-
-In the future, ``pw::Function`` may support dynamic allocation of callable
-storage using the system allocator. This operation will always be explicit.
-
-API usage
-=========
-
-``pw::Function`` function parameters
-------------------------------------
-When implementing an API which takes a callback, a ``Function`` can be used in
-place of a function pointer or equivalent callable.
-
-.. code-block:: c++
-
-  // Before:
-  void DoTheThing(int arg, void (*callback)(int result));
-
-  // After. Note that it is possible to have parameter names within the function
-  // signature template for clarity.
-  void DoTheThing(int arg, const pw::Function<void(int result)>& callback);
-
-``pw::Function`` is movable, but not copyable, so APIs must accept
-``pw::Function`` objects either by const reference (``const
-pw::Function<void()>&``) or rvalue reference (``const pw::Function<void()>&&``).
-If the ``pw::Function`` simply needs to be called, it should be passed by const
-reference. If the ``pw::Function`` needs to be stored, it should be passed as an
-rvalue reference and moved into a ``pw::Function`` variable as appropriate.
-
-.. code-block:: c++
-
-  // This function calls a pw::Function but doesn't store it, so it takes a
-  // const reference.
-  void CallTheCallback(const pw::Function<void(int)>& callback) {
-    callback(123);
-  }
-
-  // This function move-assigns a pw::Function to another variable, so it takes
-  // an rvalue reference.
-  void StoreTheCallback(pw::Function<void(int)>&& callback) {
-    stored_callback_ = std::move(callback);
-  }
-
-.. admonition:: Rules of thumb for passing a ``pw::Function`` to a function
-
-   * **Pass by value**: Never.
-
-     This results in unnecessary ``pw::Function`` instances and move operations.
-   * **Pass by const reference** (``const pw::Function&``): When the
-     ``pw::Function`` is only invoked.
-
-     When a ``pw::Function`` is called or inspected, but not moved, take a const
-     reference to avoid copies and support temporaries.
-   * **Pass by rvalue reference** (``pw::Function&&``): When the
-     ``pw::Function`` is moved.
-
-     When the function takes ownership of the ``pw::Function`` object, always
-     use an rvalue reference (``pw::Function<void()>&&``) instead of a mutable
-     lvalue reference (``pw::Function<void()>&``). An rvalue reference forces
-     the caller to ``std::move`` when passing a preexisting ``pw::Function``
-     variable, which makes the transfer of ownership explicit. It is possible to
-     move-assign from an lvalue reference, but this fails to make it obvious to
-     the caller that the object is no longer valid.
-   * **Pass by non-const reference** (``pw::Function&``): Rarely, when modifying
-     a variable.
-
-     Non-const references are only necessary when modifying an existing
-     ``pw::Function`` variable. Use an rvalue reference instead if the
-     ``pw::Function`` is moved into another variable.
-
-Calling functions that use ``pw::Function``
--------------------------------------------
-A ``pw::Function`` can be implicitly constructed from any callback object. When
-calling an API that takes a ``pw::Function``, simply pass the callable object.
-There is no need to create an intermediate ``pw::Function`` object.
-
-.. code-block:: c++
-
-  // Implicitly creates a pw::Function from a capturing lambda and calls it.
-  CallTheCallback([this](int result) { result_ = result; });
-
-  // Implicitly creates a pw::Function from a capturing lambda and stores it.
-  StoreTheCallback([this](int result) { result_ = result; });
-
-When working with an existing ``pw::Function`` variable, the variable can be
-passed directly to functions that take a const reference. If the function takes
-ownership of the ``pw::Function``, move the ``pw::Function`` variable at the
-call site.
-
-.. code-block:: c++
-
-  // Accepts the pw::Function by const reference.
-  CallTheCallback(my_function_);
-
-  // Takes ownership of the pw::Function.
-  void StoreTheCallback(std::move(my_function));
-
-Size reports
-============
-
-Function class
---------------
-The following size report compares an API using a ``pw::Function`` to a
-traditional function pointer.
-
-.. include:: function_size
-
-Callable sizes
---------------
-The table below demonstrates typical sizes of various callable types, which can
-be used as a reference when sizing external buffers for ``Function`` objects.
-
-.. include:: callable_size
-
-Design
-======
-``pw::Function`` is based largely on
-`fbl::Function <https://cs.opensource.google/fuchsia/fuchsia/+/main:zircon/system/ulib/fbl/include/fbl/function.h>`_
-from Fuchsia with some changes to make it more suitable for embedded
-development.
-
-Functions are movable, but not copyable. This allows them to store and manage
-callables without having to perform bookkeeping such as reference counting, and
-avoids any reliance on dynamic memory management. The result is a simpler
-implementation which is easy to conceptualize and use in an embedded context.
-
-Zephyr
-======
-To enable ``pw_function` for Zephyr add ``CONFIG_PIGWEED_FUNCTION=y`` to the
-project's configuration.
diff --git a/pw_function/function_test.cc b/pw_function/function_test.cc
deleted file mode 100644
index 8ba01d9..0000000
--- a/pw_function/function_test.cc
+++ /dev/null
@@ -1,303 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_function/function.h"
-
-#include "gtest/gtest.h"
-#include "pw_polyfill/language_feature_macros.h"
-
-namespace pw {
-namespace {
-
-// TODO(pwbug/47): Convert this to a compilation failure test.
-#if defined(PW_COMPILE_FAIL_TEST_CannotInstantiateWithNonFunction)
-
-[[maybe_unused]] Function<int> function_pointer;
-
-#elif defined(PW_COMPILE_FAIL_TEST_CannotInstantiateWithFunctionPointer1)
-
-[[maybe_unused]] Function<void (*)()> function_pointer;
-
-#elif defined(PW_COMPILE_FAIL_TEST_CannotInstantiateWithFunctionPointer2)
-
-[[maybe_unused]] void SomeFunction(int);
-
-[[maybe_unused]] Function<decltype(&SomeFunction)> function_pointer;
-
-#elif defined(PW_COMPILE_FAIL_TEST_CannotInstantiateWithFunctionReference)
-
-[[maybe_unused]] Function<void (&)()> function_pointer;
-
-#endif  // compile fail tests
-
-// Ensure that Function can be constant initialized.
-[[maybe_unused]] PW_CONSTINIT Function<void()> can_be_constant_initialized;
-
-int Multiply(int a, int b) { return a * b; }
-
-TEST(Function, OperatorCall) {
-  Function<int(int, int)> multiply(Multiply);
-  EXPECT_EQ(multiply(3, 7), 21);
-}
-
-void CallbackAdd(int a, int b, pw::Function<void(int sum)> callback) {
-  callback(a + b);
-}
-
-int add_result = -1;
-
-void free_add_callback(int sum) { add_result = sum; }
-
-TEST(Function, ConstructInPlace_FreeFunction) {
-  add_result = -1;
-  CallbackAdd(25, 17, free_add_callback);
-  EXPECT_EQ(add_result, 42);
-}
-
-TEST(Function, ConstructInPlace_NonCapturingLambda) {
-  add_result = -1;
-  CallbackAdd(25, 18, [](int sum) { add_result = sum; });
-  EXPECT_EQ(add_result, 43);
-}
-
-TEST(Function, ConstructInPlace_CapturingLambda) {
-  int result = -1;
-  CallbackAdd(25, 19, [&](int sum) { result = sum; });
-  EXPECT_EQ(result, 44);
-}
-
-class CallableObject {
- public:
-  CallableObject(int* result) : result_(result) {}
-
-  CallableObject(CallableObject&& other) = default;
-  CallableObject& operator=(CallableObject&& other) = default;
-
-  void operator()(int sum) { *result_ = sum; }
-
- private:
-  int* result_;
-};
-
-TEST(Function, ConstructInPlace_CallableObject) {
-  int result = -1;
-  CallbackAdd(25, 20, CallableObject(&result));
-  EXPECT_EQ(result, 45);
-}
-
-class MemberFunctionTest : public ::testing::Test {
- protected:
-  MemberFunctionTest() : result_(-1) {}
-
-  void set_result(int result) { result_ = result; }
-
-  int result_;
-};
-
-TEST_F(MemberFunctionTest, ConstructInPlace_Lambda) {
-  CallbackAdd(25, 21, [this](int sum) { set_result(sum); });
-  EXPECT_EQ(result_, 46);
-}
-
-TEST(Function, Null_OperatorBool) {
-  Closure implicit_null;
-  Closure explicit_null(nullptr);
-  Closure assigned_null = nullptr;
-  Closure not_null([]() {});
-
-  EXPECT_FALSE(bool(implicit_null));
-  EXPECT_FALSE(bool(explicit_null));
-  EXPECT_FALSE(bool(assigned_null));
-  EXPECT_TRUE(bool(not_null));
-
-  EXPECT_TRUE(!implicit_null);
-  EXPECT_TRUE(!explicit_null);
-  EXPECT_TRUE(!assigned_null);
-  EXPECT_FALSE(!not_null);
-}
-
-TEST(Function, Null_OperatorEquals) {
-  Closure implicit_null;
-  Closure explicit_null(nullptr);
-  Closure assigned_null = nullptr;
-  Closure not_null([]() {});
-
-  EXPECT_TRUE(implicit_null == nullptr);
-  EXPECT_TRUE(explicit_null == nullptr);
-  EXPECT_TRUE(assigned_null == nullptr);
-  EXPECT_TRUE(not_null != nullptr);
-
-  EXPECT_FALSE(implicit_null != nullptr);
-  EXPECT_FALSE(explicit_null != nullptr);
-  EXPECT_FALSE(assigned_null != nullptr);
-  EXPECT_FALSE(not_null == nullptr);
-}
-
-TEST(Function, Null_Set) {
-  Closure function = []() {};
-  EXPECT_NE(function, nullptr);
-  function = nullptr;
-  EXPECT_EQ(function, nullptr);
-}
-
-void DoNothing() {}
-
-TEST(Function, Null_FunctionPointer) {
-  void (*ptr)() = DoNothing;
-  Closure not_null(ptr);
-  EXPECT_NE(not_null, nullptr);
-  ptr = nullptr;
-  Closure is_null(ptr);
-  EXPECT_EQ(is_null, nullptr);
-}
-
-TEST(Function, Move_Null) {
-  Closure moved;
-  EXPECT_EQ(moved, nullptr);
-  Closure function(std::move(moved));
-  EXPECT_EQ(function, nullptr);
-
-// Ignore use-after-move.
-#ifndef __clang_analyzer__
-  EXPECT_EQ(moved, nullptr);
-#endif  // __clang_analyzer__
-}
-
-TEST(Function, MoveAssign_Null) {
-  Closure moved;
-  EXPECT_EQ(moved, nullptr);
-  Closure function = std::move(moved);
-  EXPECT_EQ(function, nullptr);
-
-// Ignore use-after-move.
-#ifndef __clang_analyzer__
-  EXPECT_EQ(moved, nullptr);
-#endif  // __clang_analyzer__
-}
-
-TEST(Function, Move_Inline) {
-  Function<int(int, int)> moved(Multiply);
-  EXPECT_NE(moved, nullptr);
-  Function<int(int, int)> multiply(std::move(moved));
-  EXPECT_EQ(multiply(3, 3), 9);
-
-// Ignore use-after-move.
-#ifndef __clang_analyzer__
-  EXPECT_EQ(moved, nullptr);
-#endif  // __clang_analyzer__
-}
-
-TEST(Function, MoveAssign_Inline) {
-  Function<int(int, int)> moved(Multiply);
-  EXPECT_NE(moved, nullptr);
-  Function<int(int, int)> multiply = std::move(moved);
-  EXPECT_EQ(multiply(3, 3), 9);
-
-// Ignore use-after-move.
-#ifndef __clang_analyzer__
-  EXPECT_EQ(moved, nullptr);
-#endif  // __clang_analyzer__
-}
-
-TEST(Function, MoveAssign_Callable) {
-  Function<int(int, int)> operation = Multiply;
-  EXPECT_EQ(operation(3, 3), 9);
-  operation = [](int a, int b) -> int { return a + b; };
-  EXPECT_EQ(operation(3, 3), 6);
-}
-
-class MoveTracker {
- public:
-  MoveTracker() : move_count_(0) {}
-
-  MoveTracker(MoveTracker&& other) : move_count_(other.move_count_ + 1) {}
-  MoveTracker& operator=(MoveTracker&& other) = default;
-
-  int operator()() const { return move_count_; }
-
- private:
-  int move_count_;
-};
-
-TEST(Function, Move_CustomObject) {
-  Function<int()> moved((MoveTracker()));
-  EXPECT_EQ(moved(), 2);  // internally moves twice on construction
-  Function<int()> tracker(std::move(moved));
-  EXPECT_EQ(tracker(), 3);
-
-// Ignore use-after-move.
-#ifndef __clang_analyzer__
-  EXPECT_EQ(moved, nullptr);
-#endif  // __clang_analyzer__
-}
-
-TEST(Function, MoveAssign_CustomObject) {
-  Function<int()> moved((MoveTracker()));
-  EXPECT_EQ(moved(), 2);  // internally moves twice on construction
-  Function<int()> tracker = std::move(moved);
-  EXPECT_EQ(tracker(), 3);
-
-// Ignore use-after-move.
-#ifndef __clang_analyzer__
-  EXPECT_EQ(moved, nullptr);
-#endif  // __clang_analyzer__
-}
-
-TEST(Function, MoveOnlyType) {
-  class MoveOnlyType {
-   public:
-    MoveOnlyType() = default;
-
-    MoveOnlyType(const MoveOnlyType& other) = delete;
-    MoveOnlyType& operator=(const MoveOnlyType& other) = delete;
-
-    MoveOnlyType(MoveOnlyType&&) = default;
-    MoveOnlyType& operator=(MoveOnlyType&&) = default;
-
-    bool ItsWorking() const { return true; }
-  };
-
-  pw::Function<bool(MoveOnlyType)> function = [](MoveOnlyType value) {
-    return value.ItsWorking();
-  };
-
-  MoveOnlyType move_only;
-  EXPECT_TRUE(function(std::move(move_only)));
-}
-
-}  // namespace
-}  // namespace pw
-
-namespace obscure_different_namespace_which_should_never_collide {
-namespace {
-
-TEST(Function, Null_OperatorEquals_DifferentNamespace) {
-  pw::Closure implicit_null;
-  pw::Closure explicit_null(nullptr);
-  pw::Closure assigned_null = nullptr;
-  pw::Closure not_null([]() {});
-
-  EXPECT_TRUE(implicit_null == nullptr);
-  EXPECT_TRUE(explicit_null == nullptr);
-  EXPECT_TRUE(assigned_null == nullptr);
-  EXPECT_TRUE(not_null != nullptr);
-
-  EXPECT_FALSE(implicit_null != nullptr);
-  EXPECT_FALSE(explicit_null != nullptr);
-  EXPECT_FALSE(assigned_null != nullptr);
-  EXPECT_FALSE(not_null == nullptr);
-}
-
-}  // namespace
-}  // namespace obscure_different_namespace_which_should_never_collide
diff --git a/pw_function/public/pw_function/config.h b/pw_function/public/pw_function/config.h
deleted file mode 100644
index b98db4b..0000000
--- a/pw_function/public/pw_function/config.h
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// Configuration macros for the function module.
-#pragma once
-
-#include <cstddef>
-
-// The maximum size of a callable that can be inlined within a function. This is
-// also the size of the Function object itself. Callables larger than this are
-// stored externally to the function.
-//
-// This defaults to 2 pointers, which is capable of storing common callables
-// such as function pointers and simple lambdas.
-#ifndef PW_FUNCTION_INLINE_CALLABLE_SIZE
-#define PW_FUNCTION_INLINE_CALLABLE_SIZE (2 * sizeof(void*))
-#endif  // PW_FUNCTION_INLINE_CALLABLE_SIZE
-
-static_assert(PW_FUNCTION_INLINE_CALLABLE_SIZE > 0 &&
-              PW_FUNCTION_INLINE_CALLABLE_SIZE % alignof(void*) == 0);
-
-// Whether functions should allocate memory dynamically (using operator new) if
-// a callable is larger than the inline size.
-//
-// NOTE: This is not currently used.
-#ifndef PW_FUNCTION_ENABLE_DYNAMIC_ALLOCATION
-#define PW_FUNCTION_ENABLE_DYNAMIC_ALLOCATION 0
-#endif  // PW_FUNCTION_ENABLE_DYNAMIC_ALLOCATION
-
-namespace pw::function_internal::config {
-
-inline constexpr size_t kInlineCallableSize = PW_FUNCTION_INLINE_CALLABLE_SIZE;
-inline constexpr bool kEnableDynamicAllocation =
-    PW_FUNCTION_ENABLE_DYNAMIC_ALLOCATION;
-
-}  // namespace pw::function_internal::config
diff --git a/pw_function/public/pw_function/function.h b/pw_function/public/pw_function/function.h
deleted file mode 100644
index 5fd5fc2..0000000
--- a/pw_function/public/pw_function/function.h
+++ /dev/null
@@ -1,158 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_function/internal/function.h"
-
-namespace pw {
-
-// pw::Function is a wrapper for an aribtrary callable object. It can be used by
-// callback-based APIs to allow callers to provide any type of callable.
-//
-// Example:
-//
-//   template <typename T>
-//   bool All(const pw::Vector<T>& items,
-//            pw::Function<bool(const T& item)> predicate) {
-//     for (const T& item : items) {
-//       if (!predicate(item)) {
-//         return false;
-//       }
-//     }
-//     return true;
-//   }
-//
-//   bool ElementsArePostive(const pw::Vector<int>& items) {
-//     return All(items, [](const int& i) { return i > 0; });
-//   }
-//
-//   bool IsEven(const int& i) { return i % 2 == 0; }
-//
-//   bool ElementsAreEven(const pw::Vector<int>& items) {
-//     return All(items, IsEven);
-//   }
-//
-template <typename Callable>
-class Function {
-  static_assert(std::is_function_v<Callable>,
-                "pw::Function may only be instantianted for a function type, "
-                "such as pw::Function<void(int)>.");
-};
-
-using Closure = Function<void()>;
-
-template <typename Return, typename... Args>
-class Function<Return(Args...)> {
- public:
-  constexpr Function() = default;
-  constexpr Function(std::nullptr_t) : Function() {}
-
-  template <typename Callable>
-  Function(Callable callable) {
-    if (function_internal::IsNull(callable)) {
-      holder_.InitializeNullTarget();
-    } else {
-      holder_.InitializeInlineTarget(std::move(callable));
-    }
-  }
-
-  Function(Function&& other) {
-    holder_.MoveInitializeTargetFrom(other.holder_);
-    other.holder_.InitializeNullTarget();
-  }
-
-  Function& operator=(Function&& other) {
-    holder_.DestructTarget();
-    holder_.MoveInitializeTargetFrom(other.holder_);
-    other.holder_.InitializeNullTarget();
-    return *this;
-  }
-
-  Function& operator=(std::nullptr_t) {
-    holder_.DestructTarget();
-    holder_.InitializeNullTarget();
-    return *this;
-  }
-
-  template <typename Callable>
-  Function& operator=(Callable callable) {
-    holder_.DestructTarget();
-    if (function_internal::IsNull(callable)) {
-      holder_.InitializeNullTarget();
-    } else {
-      holder_.InitializeInlineTarget(std::move(callable));
-    }
-    return *this;
-  }
-
-  ~Function() { holder_.DestructTarget(); }
-
-  template <typename... PassedArgs>
-  Return operator()(PassedArgs&&... args) const {
-    return holder_.target()(std::forward<PassedArgs>(args)...);
-  };
-
-  explicit operator bool() const { return !holder_.target().IsNull(); }
-
- private:
-  // TODO(frolv): This is temporarily private while the API is worked out.
-  template <typename Callable, size_t kSizeBytes>
-  Function(Callable&& callable,
-           function_internal::FunctionStorage<kSizeBytes>& storage)
-      : Function(callable, &storage) {
-    static_assert(sizeof(Callable) <= kSizeBytes,
-                  "pw::Function callable does not fit into provided storage");
-  }
-
-  // Constructs a function that stores its callable at the provided location.
-  // Public constructors wrapping this must ensure that the memory region is
-  // capable of storing the callable in terms of both size and alignment.
-  template <typename Callable>
-  Function(Callable&& callable, void* storage) {
-    if (function_internal::IsNull(callable)) {
-      holder_.InitializeNullTarget();
-    } else {
-      holder_.InitializeMemoryTarget(std::forward(callable), storage);
-    }
-  }
-
-  function_internal::FunctionTargetHolder<
-      function_internal::config::kInlineCallableSize,
-      Return,
-      Args...>
-      holder_;
-};
-
-// nullptr comparisions for functions.
-template <typename T>
-bool operator==(const Function<T>& f, std::nullptr_t) {
-  return !static_cast<bool>(f);
-}
-
-template <typename T>
-bool operator!=(const Function<T>& f, std::nullptr_t) {
-  return static_cast<bool>(f);
-}
-
-template <typename T>
-bool operator==(std::nullptr_t, const Function<T>& f) {
-  return !static_cast<bool>(f);
-}
-
-template <typename T>
-bool operator!=(std::nullptr_t, const Function<T>& f) {
-  return static_cast<bool>(f);
-}
-
-}  // namespace pw
diff --git a/pw_function/public/pw_function/internal/function.h b/pw_function/public/pw_function/internal/function.h
deleted file mode 100644
index 8cffd02..0000000
--- a/pw_function/public/pw_function/internal/function.h
+++ /dev/null
@@ -1,259 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstddef>
-#include <new>
-#include <utility>
-
-#include "pw_assert/assert.h"
-#include "pw_function/config.h"
-#include "pw_preprocessor/compiler.h"
-
-namespace pw::function_internal {
-
-template <typename T, typename Comparison = bool>
-struct NullEq {
-  static constexpr bool Test(const T&) { return false; }
-};
-
-// Partial specialization for values of T comparable to nullptr.
-template <typename T>
-struct NullEq<T, decltype(std::declval<T>() == nullptr)> {
-  // This is intended to be used for comparing function pointers to nullptr, but
-  // the specialization also matches Ts that implicitly convert to a function
-  // pointer, such as function types. The compiler may then complain that the
-  // comparison is false, as the address is known at compile time and cannot be
-  // nullptr. Silence this warning. (The compiler will optimize out the
-  // comparison.)
-  PW_MODIFY_DIAGNOSTICS_PUSH();
-  PW_MODIFY_DIAGNOSTIC(ignored, "-Waddress");
-  static constexpr bool Test(const T& v) { return v == nullptr; }
-  PW_MODIFY_DIAGNOSTICS_POP();
-};
-
-// Tests whether a value is considered to be null.
-template <typename T>
-static constexpr bool IsNull(const T& v) {
-  return NullEq<T>::Test(v);
-}
-
-// FunctionTarget is an interface for storing a callable object and providing a
-// way to invoke it. The GenericFunctionTarget expresses the interface common to
-// all pw::Function instantiations. The derived FunctionTarget class adds the
-// call operator, which is templated on the function arguments and return type.
-class GenericFunctionTarget {
- public:
-  constexpr GenericFunctionTarget() = default;
-
-  GenericFunctionTarget(const GenericFunctionTarget&) = delete;
-  GenericFunctionTarget(GenericFunctionTarget&&) = delete;
-  GenericFunctionTarget& operator=(const GenericFunctionTarget&) = delete;
-  GenericFunctionTarget& operator=(GenericFunctionTarget&&) = delete;
-
-  virtual void Destroy() {}
-
-  // Only returns true for NullFunctionTarget.
-  virtual bool IsNull() const { return false; }
-
-  // Move initialize the function target to a provided location.
-  virtual void MoveInitializeTo(void* ptr) = 0;
-
- protected:
-  ~GenericFunctionTarget() = default;  // The destructor is never called.
-};
-
-// FunctionTarget is an interface for storing a callable object and providing a
-// way to invoke it.
-template <typename Return, typename... Args>
-class FunctionTarget : public GenericFunctionTarget {
- public:
-  constexpr FunctionTarget() = default;
-
-  // Invoke the callable stored by the function target.
-  virtual Return operator()(Args... args) const = 0;
-
- protected:
-  ~FunctionTarget() = default;  // The destructor is never called.
-};
-
-// A function target that does not store any callable. Attempting to invoke it
-// results in a crash.
-class NullFunctionTarget final : public FunctionTarget<void> {
- public:
-  constexpr NullFunctionTarget() = default;
-
-  NullFunctionTarget(const NullFunctionTarget&) = delete;
-  NullFunctionTarget(NullFunctionTarget&&) = delete;
-  NullFunctionTarget& operator=(const NullFunctionTarget&) = delete;
-  NullFunctionTarget& operator=(NullFunctionTarget&&) = delete;
-
-  bool IsNull() const final { return true; }
-
-  void operator()() const final { PW_ASSERT(false); }
-
-  void MoveInitializeTo(void* ptr) final { new (ptr) NullFunctionTarget(); }
-};
-
-// Function target that stores a callable as a member within the class.
-template <typename Callable, typename Return, typename... Args>
-class InlineFunctionTarget final : public FunctionTarget<Return, Args...> {
- public:
-  explicit InlineFunctionTarget(Callable&& callable)
-      : callable_(std::move(callable)) {}
-
-  void Destroy() final { callable_.~Callable(); }
-
-  InlineFunctionTarget(const InlineFunctionTarget&) = delete;
-  InlineFunctionTarget& operator=(const InlineFunctionTarget&) = delete;
-
-  InlineFunctionTarget(InlineFunctionTarget&& other)
-      : callable_(std::move(other.callable_)) {}
-  InlineFunctionTarget& operator=(InlineFunctionTarget&&) = default;
-
-  Return operator()(Args... args) const final {
-    return callable_(std::forward<Args>(args)...);
-  }
-
-  void MoveInitializeTo(void* ptr) final {
-    new (ptr) InlineFunctionTarget(std::move(*this));
-  }
-
- private:
-  // This must be mutable to support custom objects that implement operator() in
-  // a non-const way.
-  mutable Callable callable_;
-};
-
-// Function target which stores a callable at a provided location in memory.
-// The creating context must ensure that the region is properly sized and
-// aligned for the callable.
-template <typename Callable, typename Return, typename... Args>
-class MemoryFunctionTarget final : public FunctionTarget<Return, Args...> {
- public:
-  MemoryFunctionTarget(void* address, Callable&& callable) : address_(address) {
-    new (address_) Callable(std::move(callable));
-  }
-
-  void Destroy() final {
-    // Multiple MemoryFunctionTargets may have referred to the same callable
-    // (due to moves), but only one can have a valid pointer to it. The owner is
-    // responsible for destructing the callable.
-    if (address_ != nullptr) {
-      callable().~Callable();
-    }
-  }
-
-  MemoryFunctionTarget(const MemoryFunctionTarget&) = delete;
-  MemoryFunctionTarget& operator=(const MemoryFunctionTarget&) = delete;
-
-  // Transfer the pointer to the initialized callable to this object without
-  // reinitializing the callable, clearing the address from the other.
-  MemoryFunctionTarget(MemoryFunctionTarget&& other)
-      : address_(other.address_) {
-    other.address_ = nullptr;
-  }
-  MemoryFunctionTarget& operator=(MemoryFunctionTarget&&) = default;
-
-  Return operator()(Args... args) const final { return callable()(args...); }
-
-  void MoveInitializeTo(void* ptr) final {
-    new (ptr) MemoryFunctionTarget(std::move(*this));
-  }
-
- private:
-  Callable& callable() {
-    return *std::launder(reinterpret_cast<Callable*>(address_));
-  }
-  const Callable& callable() const {
-    return *std::launder(reinterpret_cast<const Callable*>(address_));
-  }
-
-  void* address_;
-};
-
-template <size_t kSizeBytes>
-using FunctionStorage =
-    std::aligned_storage_t<kSizeBytes, alignof(std::max_align_t)>;
-
-// A FunctionTargetHolder stores an instance of a FunctionTarget implementation.
-//
-// The concrete implementation is initialized in an internal buffer by calling
-// one of the initialization functions. After initialization, all
-// implementations are accessed through the virtual FunctionTarget base.
-template <size_t kSizeBytes, typename Return, typename... Args>
-class FunctionTargetHolder {
- public:
-  constexpr FunctionTargetHolder() : null_function_ {}
-  {}
-
-  FunctionTargetHolder(const FunctionTargetHolder&) = delete;
-  FunctionTargetHolder(FunctionTargetHolder&&) = delete;
-  FunctionTargetHolder& operator=(const FunctionTargetHolder&) = delete;
-  FunctionTargetHolder& operator=(FunctionTargetHolder&&) = delete;
-
-  constexpr void InitializeNullTarget() {
-    static_assert(sizeof(NullFunctionTarget) <= kSizeBytes,
-                  "NullFunctionTarget must fit within FunctionTargetHolder");
-    new (&null_function_) NullFunctionTarget;
-  }
-
-  // Initializes an InlineFunctionTarget with the callable, failing if it is too
-  // large.
-  template <typename Callable>
-  void InitializeInlineTarget(Callable callable) {
-    using InlineFunctionTarget =
-        InlineFunctionTarget<Callable, Return, Args...>;
-    static_assert(sizeof(InlineFunctionTarget) <= kSizeBytes,
-                  "Inline callable must fit within FunctionTargetHolder");
-    new (&bits_) InlineFunctionTarget(std::move(callable));
-  }
-
-  // Initializes a MemoryTarget that stores the callable at the provided
-  // location.
-  template <typename Callable>
-  void InitializeMemoryTarget(Callable callable, void* storage) {
-    using MemoryFunctionTarget =
-        MemoryFunctionTarget<Callable, Return, Args...>;
-    static_assert(sizeof(MemoryFunctionTarget) <= kSizeBytes,
-                  "MemoryFunctionTarget must fit within FunctionTargetHolder");
-    new (&bits_) MemoryFunctionTarget(storage, std::move(callable));
-  }
-
-  void DestructTarget() { target().Destroy(); }
-
-  // Initializes the function target within this callable from another target
-  // holder's function target.
-  void MoveInitializeTargetFrom(FunctionTargetHolder& other) {
-    other.target().MoveInitializeTo(&bits_);
-  }
-
-  // The stored implementation is accessed by punning to the virtual base class.
-  using Target = FunctionTarget<Return, Args...>;
-  Target& target() { return *std::launder(reinterpret_cast<Target*>(&bits_)); }
-  const Target& target() const {
-    return *std::launder(reinterpret_cast<const Target*>(&bits_));
-  }
-
- private:
-  // Storage for an implementation of the FunctionTarget interface. Make this a
-  // union with NullFunctionTarget so that the constexpr constructor can
-  // initialize null_function_ directly.
-  union {
-    FunctionStorage<kSizeBytes> bits_;
-    NullFunctionTarget null_function_;
-  };
-};
-
-}  // namespace pw::function_internal
diff --git a/pw_function/size_report/BUILD.bazel b/pw_function/size_report/BUILD.bazel
deleted file mode 100644
index 7de277c..0000000
--- a/pw_function/size_report/BUILD.bazel
+++ /dev/null
@@ -1,50 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_binary",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_binary(
-    name = "pointer_base",
-    srcs = ["pointer_base.cc"],
-    deps = [
-        "//pw_bloat:bloat_this_binary",
-        "//pw_function",
-    ],
-)
-
-pw_cc_binary(
-    name = "basic_function",
-    srcs = ["basic_function.cc"],
-    deps = [
-        "//pw_bloat:bloat_this_binary",
-        "//pw_function",
-    ],
-)
-
-pw_cc_binary(
-    name = "callable_size",
-    srcs = ["callable_size.cc"],
-    defines = ["_BASE=1"],
-    deps = [
-        "//pw_bloat:bloat_this_binary",
-        "//pw_function",
-    ],
-)
diff --git a/pw_function/size_report/BUILD.gn b/pw_function/size_report/BUILD.gn
deleted file mode 100644
index 232fbb9..0000000
--- a/pw_function/size_report/BUILD.gn
+++ /dev/null
@@ -1,74 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-_deps = [
-  "$dir_pw_bloat:bloat_this_binary",
-  "..:pw_function",
-]
-
-pw_executable("pointer_base") {
-  sources = [ "pointer_base.cc" ]
-  deps = _deps
-}
-
-pw_executable("basic_function") {
-  sources = [ "basic_function.cc" ]
-  deps = _deps
-}
-
-pw_executable("callable_size_base") {
-  sources = [ "callable_size.cc" ]
-  defines = [ "_BASE=1" ]
-  deps = _deps
-}
-
-pw_executable("callable_size_function_pointer") {
-  sources = [ "callable_size.cc" ]
-  defines = [ "_FUNCTION_POINTER=1" ]
-  deps = _deps
-}
-
-pw_executable("callable_size_static_lambda") {
-  sources = [ "callable_size.cc" ]
-  defines = [ "_STATIC_LAMBDA=1" ]
-  deps = _deps
-}
-
-pw_executable("callable_size_simple_lambda") {
-  sources = [ "callable_size.cc" ]
-  defines = [ "_SIMPLE_LAMBDA=1" ]
-  deps = _deps
-}
-
-pw_executable("callable_size_capturing_lambda") {
-  sources = [ "callable_size.cc" ]
-  defines = [ "_CAPTURING_LAMBDA=1" ]
-  deps = _deps
-}
-
-pw_executable("callable_size_multi_capturing_lambda") {
-  sources = [ "callable_size.cc" ]
-  defines = [ "_MULTI_CAPTURING_LAMBDA=1" ]
-  deps = _deps
-}
-
-pw_executable("callable_size_custom_class") {
-  sources = [ "callable_size.cc" ]
-  defines = [ "_CUSTOM_CLASS=1" ]
-  deps = _deps
-}
diff --git a/pw_function/size_report/basic_function.cc b/pw_function/size_report/basic_function.cc
deleted file mode 100644
index c715eaf..0000000
--- a/pw_function/size_report/basic_function.cc
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_function/function.h"
-
-namespace {
-
-int volatile* unoptimizable;
-
-void DoTheThing(int value, const pw::Function<void(int)>& callback) {
-  callback(value ^ *unoptimizable);
-}
-
-}  // namespace
-
-int main() {
-  int result;
-  DoTheThing(3, [&](int r) { result = r; });
-  return result;
-}
diff --git a/pw_function/size_report/callable_size.cc b/pw_function/size_report/callable_size.cc
deleted file mode 100644
index 3b00b24..0000000
--- a/pw_function/size_report/callable_size.cc
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <array>
-#include <cstddef>
-
-namespace {
-
-int volatile* unoptimizable;
-
-template <typename Callable>
-class CallableSize {
- public:
-  constexpr CallableSize(Callable callable) : callable_(std::move(callable)) {}
-
-  int PreventOptimization() { return *unoptimizable; }
-
- private:
-  alignas(std::max_align_t) Callable callable_;
-};
-
-[[maybe_unused]] void Function() {}
-
-class CustomCallableClass {
- public:
-  void operator()() {}
-
- private:
-  std::array<std::byte, 16> data_;
-};
-
-}  // namespace
-
-int main() {
-  int a = 0;
-  int b = 1;
-  int c = 2;
-  int d = 3;
-  static_cast<void>(a);
-  static_cast<void>(b);
-  static_cast<void>(c);
-  static_cast<void>(d);
-
-#if defined(_BASE)
-  CallableSize<std::array<std::byte, 0>> callable_size({});
-#elif defined(_FUNCTION_POINTER)
-  static CallableSize callable_size(Function);
-#elif defined(_STATIC_LAMBDA)
-  static CallableSize callable_size(+[]() {});
-#elif defined(_SIMPLE_LAMBDA)
-  static CallableSize callable_size([]() {});
-#elif defined(_CAPTURING_LAMBDA)
-  static CallableSize callable_size([a]() {});
-#elif defined(_MULTI_CAPTURING_LAMBDA)
-  static CallableSize callable_size([a, b, c, d]() {});
-#elif defined(_CUSTOM_CLASS)
-  static CallableSize callable_size((CustomCallableClass()));
-#endif
-
-  int foo = callable_size.PreventOptimization();
-  return sizeof(callable_size) + foo;
-}
diff --git a/pw_function/size_report/pointer_base.cc b/pw_function/size_report/pointer_base.cc
deleted file mode 100644
index 581d6ce..0000000
--- a/pw_function/size_report/pointer_base.cc
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-namespace {
-
-int volatile* unoptimizable;
-
-void DoTheThing(int value, void (*callback)(int)) {
-  callback(value ^ *unoptimizable);
-}
-
-int thing_result;
-
-void Callback(int result) { thing_result = result; }
-
-}  // namespace
-
-int main() {
-  DoTheThing(3, Callback);
-  return thing_result;
-}
diff --git a/pw_fuzzer/BUILD.bazel b/pw_fuzzer/BUILD.bazel
deleted file mode 100644
index 4fdfcab..0000000
--- a/pw_fuzzer/BUILD.bazel
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_fuzzer",
-    hdrs = [
-        "public/pw_fuzzer/asan_interface.h",
-        "public/pw_fuzzer/fuzzed_data_provider.h",
-    ],
-    includes = ["public"],
-)
diff --git a/pw_fuzzer/BUILD.gn b/pw_fuzzer/BUILD.gn
index 91cbd08..e97d084 100644
--- a/pw_fuzzer/BUILD.gn
+++ b/pw_fuzzer/BUILD.gn
@@ -19,9 +19,8 @@
 import("$dir_pw_fuzzer/fuzzer.gni")
 import("$dir_pw_fuzzer/oss_fuzz.gni")
 
-config("public_include_path") {
+config("default_config") {
   include_dirs = [ "public" ]
-  visibility = [ ":*" ]
 }
 
 # This is added automatically by the `pw_fuzzer` template.
@@ -46,7 +45,7 @@
 }
 
 pw_source_set("pw_fuzzer") {
-  public_configs = [ ":public_include_path" ]
+  public_configs = [ ":default_config" ]
   public = [
     "public/pw_fuzzer/asan_interface.h",
     "public/pw_fuzzer/fuzzed_data_provider.h",
@@ -55,7 +54,7 @@
 }
 
 pw_source_set("run_as_unit_test") {
-  configs = [ ":public_include_path" ]
+  configs = [ ":default_config" ]
   sources = [ "pw_fuzzer_disabled.cc" ]
   deps = [
     dir_pw_log,
@@ -80,10 +79,7 @@
 # Sample fuzzer
 pw_fuzzer("toy_fuzzer") {
   sources = [ "examples/toy_fuzzer.cc" ]
-  deps = [
-    "$dir_pw_result",
-    "$dir_pw_string",
-  ]
+  deps = [ "$dir_pw_string" ]
 }
 
 pw_test_group("tests") {
diff --git a/pw_fuzzer/OWNERS b/pw_fuzzer/OWNERS
deleted file mode 100644
index 8819b2c..0000000
--- a/pw_fuzzer/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-keir@google.com
diff --git a/pw_fuzzer/docs.rst b/pw_fuzzer/docs.rst
index e52b9f1..fb406e1 100644
--- a/pw_fuzzer/docs.rst
+++ b/pw_fuzzer/docs.rst
@@ -37,7 +37,7 @@
 To write a fuzzer, a developer needs to write a fuzz target function follwing
 the `fuzz target function`__ guidelines given by libFuzzer:
 
-.. code:: cpp
+.. code::
 
   extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
     DoSomethingInterestingWithMyAPI(data, size);
@@ -48,7 +48,7 @@
 
 When writing you fuzz target function, you may want to consider:
 
-- It is acceptable to return early if the input doesn't meet some constraints,
+- It is acceptable to return early if the input doesn't mean some constraints,
   e.g. it is too short.
 - If your fuzzer accepts data with a well-defined format, you can bootstrap
   coverage by crafting examples and adding them to a `corpus`_.
@@ -63,8 +63,8 @@
 
 .. _build:
 
-Building fuzzers with GN
-========================
+Building fuzzers
+================
 
 To build a fuzzer, do the following:
 
@@ -99,49 +99,6 @@
 
 .. _run:
 
-Building and running fuzzers with Bazel
-=======================================
-To build a fuzzer, do the following:
-
-1. Add the Bazel target using ``pw_cc_fuzz_test`` macro.
-
-.. code:: py
-
-  load("@pigweed//pw_fuzzer:fuzzer.bzl", "pw_cc_fuzz_test")
-
-  pw_cc_fuzz_test(
-    name = "my_fuzz_test",
-    srcs = ["my_fuzzer.cc"],
-    deps = [
-      "@pigweed//pw_fuzzer",
-      ":my_lib",
-    ],
-  )
-
-2. Build and run the fuzzer.
-
-.. code:: sh
-
-  bazel test //my_module:my_fuzz_test
-
-3. Swap fuzzer backend to use ASAN fuzzing engine.
-
-.. code::
-
-  # .bazelrc
-  # Define the --config=asan-libfuzzer configuration.
-  build:asan-libfuzzer \
-    --@rules_fuzzing//fuzzing:cc_engine=@rules_fuzzing//fuzzing/engines:libfuzzer
-  build:asan-libfuzzer \
-    --@rules_fuzzing//fuzzing:cc_engine_instrumentation=libfuzzer
-  build:asan-libfuzzer --@rules_fuzzing//fuzzing:cc_engine_sanitizer=asan
-
-4. Re-run fuzz tests.
-
-.. code::
-
-  bazel test //my_module:my_fuzz_test --config asan-libfuzzer
-
 Running fuzzers locally
 =======================
 
@@ -264,7 +221,7 @@
 .. _compiler_rt: https://compiler-rt.llvm.org/
 .. _corpus: https://llvm.org/docs/LibFuzzer.html#corpus
 .. _FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION: https://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode
-.. _FuzzedDataProvider: https://github.com/llvm/llvm-project/blob/HEAD/compiler-rt/include/fuzzer/FuzzedDataProvider.h
+.. _FuzzedDataProvider: https://github.com/llvm/llvm-project/blob/master/compiler-rt/include/fuzzer/FuzzedDataProvider.h
 .. _libFuzzer: https://llvm.org/docs/LibFuzzer.html
 .. _libFuzzer options: https://llvm.org/docs/LibFuzzer.html#options
 .. _LLVMFuzzerTestOneInput: https://llvm.org/docs/LibFuzzer.html#fuzz-target
@@ -273,7 +230,7 @@
 .. _reproducing: https://google.github.io/oss-fuzz/advanced-topics/reproducing/
 .. _running a fuzzer: https://llvm.org/docs/LibFuzzer.html#running
 .. _sanitizer runtime flags: https://github.com/google/sanitizers/wiki/SanitizerCommonFlags
-.. _split a fuzzing input: https://github.com/google/fuzzing/blob/HEAD/docs/split-inputs.md
+.. _split a fuzzing input: https://github.com/google/fuzzing/blob/master/docs/split-inputs.md
 .. _startup initialization: https://llvm.org/docs/LibFuzzer.html#startup-initialization
-.. _structure aware fuzzing: https://github.com/google/fuzzing/blob/HEAD/docs/structure-aware-fuzzing.md
+.. _structure aware fuzzing: https://github.com/google/fuzzing/blob/master/docs/structure-aware_fuzzing.md
 .. _valid options: https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html
diff --git a/pw_fuzzer/examples/toy_fuzzer.cc b/pw_fuzzer/examples/toy_fuzzer.cc
index 58ae461..bcbaef1 100644
--- a/pw_fuzzer/examples/toy_fuzzer.cc
+++ b/pw_fuzzer/examples/toy_fuzzer.cc
@@ -22,9 +22,7 @@
 #include <cstddef>
 #include <cstdint>
 #include <cstring>
-#include <span>
 
-#include "pw_result/result.h"
 #include "pw_string/util.h"
 
 namespace {
@@ -64,26 +62,24 @@
 // The fuzz target function
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
   // We want to split our input into two strings.
-  const std::span<const char> input(reinterpret_cast<const char*>(data), size);
+  const char* word1 = reinterpret_cast<const char*>(data);
 
   // If that's not feasible, toss this input. The fuzzer will quickly learn that
   // inputs without null-terminators are uninteresting.
-  const pw::Result<size_t> possible_word1_size =
-      pw::string::NullTerminatedLength(input);
-  if (!possible_word1_size.ok()) {
+  size_t offset = pw::string::Length(word1, size) + 1;
+  if (offset >= size) {
     return 0;
   }
-  const std::span<const char> word1 =
-      input.first(possible_word1_size.value() + 1);
 
   // Actually, inputs without TWO null terminators are uninteresting.
-  std::span<const char> remaining_input = input.subspan(word1.size());
-  if (!pw::string::NullTerminatedLength(remaining_input).ok()) {
+  const char* word2 = reinterpret_cast<const char*>(&data[offset]);
+  size -= offset;
+  if (pw::string::Length(word2, size) == size) {
     return 0;
   }
 
   // Call the code we're targeting!
-  toy_example(word1.data(), remaining_input.data());
+  toy_example(word1, word2);
 
   // By convention, the fuzzer always returns zero.
   return 0;
diff --git a/pw_fuzzer/fuzzer.bzl b/pw_fuzzer/fuzzer.bzl
deleted file mode 100644
index 2c82fee..0000000
--- a/pw_fuzzer/fuzzer.bzl
+++ /dev/null
@@ -1,29 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Utilities for fuzzing."""
-
-load("@rules_fuzzing//fuzzing:cc_defs.bzl", "cc_fuzz_test")
-load(
-    "//pw_build/bazel_internal:pigweed_internal.bzl",
-    _add_cc_and_c_targets = "add_cc_and_c_targets",
-    _has_pw_assert_dep = "has_pw_assert_dep",
-)
-
-def pw_cc_fuzz_test(**kwargs):
-    # TODO(pwbug/440): Remove this implicit dependency once we have a better
-    # way to handle the facades without introducing a circular dependency into
-    # the build.
-    if not _has_pw_assert_dep(kwargs["deps"]):
-        kwargs["deps"].append("@pigweed//pw_assert")
-    _add_cc_and_c_targets(cc_fuzz_test, kwargs)
diff --git a/pw_fuzzer/fuzzer.gni b/pw_fuzzer/fuzzer.gni
index 9bcf50e..776fa5d 100644
--- a/pw_fuzzer/fuzzer.gni
+++ b/pw_fuzzer/fuzzer.gni
@@ -33,8 +33,7 @@
     "mac",
   ]
 
-  fuzzing_toolchains =
-      [ get_path_info("$dir_pigweed/targets/host:host_clang_fuzz", "abspath") ]
+  fuzzing_toolchains = [ "//targets/host:host_clang_fuzz" ]
 
   # This is how GN says 'elem in list':
   can_fuzz = fuzzing_platforms + [ host_os ] - [ host_os ] != fuzzing_platforms
@@ -47,15 +46,10 @@
     pw_executable(target_name) {
       forward_variables_from(invoker, "*", [ "visibility" ])
       forward_variables_from(invoker, [ "visibility" ])
-
-      if (!defined(deps)) {
-        deps = []
-      }
-      deps += [ dir_pw_fuzzer ]
-
       if (!defined(configs)) {
         configs = []
       }
+      configs += [ "$dir_pw_fuzzer:default_config" ]
       if (pw_toolchain_OSS_FUZZ_ENABLED) {
         configs += [ "$dir_pw_fuzzer:oss_fuzz" ]
       } else {
@@ -80,12 +74,12 @@
       }
     }
 
-    # No-op target to satisfy `pw_test_group`. It is empty as we don't want to
+    # Dummy target to satisfy `pw_test_group`. It is empty as we don't want to
     # automatically run fuzzers.
     group(target_name + ".run") {
     }
 
-    # No-op target to satisfy `pw_test`. It is empty as we don't need a separate
+    # Dummy target to satisfy `pw_test`. It is empty as we don't need a separate
     # lib target.
     group(target_name + ".lib") {
     }
diff --git a/pw_hdlc/BUILD b/pw_hdlc/BUILD
new file mode 100644
index 0000000..9463dbc
--- /dev/null
+++ b/pw_hdlc/BUILD
@@ -0,0 +1,123 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_hdlc",
+    srcs = [
+        "decoder.cc",
+        "encoder.cc",
+        "public/pw_hdlc/internal/encoder.h",
+        "public/pw_hdlc/internal/protocol.h",
+        "rpc_packets.cc",
+    ],
+    hdrs = [
+        "public/pw_hdlc/decoder.h",
+        "public/pw_hdlc/encoder.h",
+    ],
+    includes = ["public"],
+    deps = [
+        "//pw_bytes",
+        "//pw_checksum",
+        "//pw_log",
+        "//pw_result",
+        "//pw_span",
+        "//pw_status",
+        "//pw_stream",
+        "//pw_varint",
+    ],
+)
+
+pw_cc_library(
+    name = "rpc_channel_output",
+    hdrs = ["public/pw_hdlc/rpc_channel.h"],
+    includes = ["public"],
+    deps = [
+        ":pw_hdlc",
+        "//pw_rpc:server",
+    ],
+)
+
+pw_cc_library(
+    name = "pw_rpc",
+    srcs = ["rpc_packets.cc"],
+    hdrs = ["public/pw_hdlc/rpc_packets.h"],
+    includes = ["public"],
+    deps = [
+        ":pw_hdlc",
+        "//pw_rpc:server",
+    ],
+)
+
+pw_cc_library(
+    name = "packet_parser",
+    srcs = ["wire_packet_parser.cc"],
+    hdrs = ["public/pw_hdlc/wire_packet_parser.h"],
+    includes = ["public"],
+    deps = [
+        ":pw_hdlc",
+        "//pw_assert",
+        "//pw_bytes",
+        "//pw_checksum",
+        "//pw_router:packet_parser",
+    ],
+)
+
+cc_test(
+    name = "encoder_test",
+    srcs = ["encoder_test.cc"],
+    deps = [
+        ":pw_hdlc",
+        "//pw_stream",
+        "//pw_unit_test",
+    ],
+)
+
+cc_test(
+    name = "decoder_test",
+    srcs = ["decoder_test.cc"],
+    deps = [
+        ":pw_hdlc",
+        "//pw_result",
+        "//pw_stream",
+        "//pw_unit_test",
+    ],
+)
+
+cc_test(
+    name = "wire_packet_parser_test",
+    srcs = ["wire_packet_parser_test.cc"],
+    deps = [
+        ":packet_parser",
+        "//pw_bytes",
+    ],
+)
+
+cc_test(
+    name = "rpc_channel_test",
+    srcs = ["rpc_channel_test.cc"],
+    deps = [
+        ":pw_hdlc",
+        "//pw_stream",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_hdlc/BUILD.bazel b/pw_hdlc/BUILD.bazel
deleted file mode 100644
index 99d2031..0000000
--- a/pw_hdlc/BUILD.bazel
+++ /dev/null
@@ -1,122 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_hdlc",
-    srcs = [
-        "decoder.cc",
-        "encoder.cc",
-        "public/pw_hdlc/internal/encoder.h",
-        "public/pw_hdlc/internal/protocol.h",
-    ],
-    hdrs = [
-        "public/pw_hdlc/decoder.h",
-        "public/pw_hdlc/encoder.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_bytes",
-        "//pw_checksum",
-        "//pw_log",
-        "//pw_result",
-        "//pw_span",
-        "//pw_status",
-        "//pw_stream",
-        "//pw_varint",
-    ],
-)
-
-pw_cc_library(
-    name = "rpc_channel_output",
-    hdrs = ["public/pw_hdlc/rpc_channel.h"],
-    includes = ["public"],
-    deps = [
-        ":pw_hdlc",
-        "//pw_rpc",
-    ],
-)
-
-pw_cc_library(
-    name = "pw_rpc",
-    srcs = ["rpc_packets.cc"],
-    hdrs = ["public/pw_hdlc/rpc_packets.h"],
-    includes = ["public"],
-    deps = [
-        ":pw_hdlc",
-        "//pw_rpc",
-    ],
-)
-
-pw_cc_library(
-    name = "packet_parser",
-    srcs = ["wire_packet_parser.cc"],
-    hdrs = ["public/pw_hdlc/wire_packet_parser.h"],
-    includes = ["public"],
-    deps = [
-        ":pw_hdlc",
-        "//pw_assert",
-        "//pw_bytes",
-        "//pw_checksum",
-        "//pw_router:packet_parser",
-    ],
-)
-
-cc_test(
-    name = "encoder_test",
-    srcs = ["encoder_test.cc"],
-    deps = [
-        ":pw_hdlc",
-        "//pw_stream",
-        "//pw_unit_test",
-    ],
-)
-
-cc_test(
-    name = "decoder_test",
-    srcs = ["decoder_test.cc"],
-    deps = [
-        ":pw_hdlc",
-        "//pw_result",
-        "//pw_stream",
-        "//pw_unit_test",
-    ],
-)
-
-cc_test(
-    name = "wire_packet_parser_test",
-    srcs = ["wire_packet_parser_test.cc"],
-    deps = [
-        ":packet_parser",
-        "//pw_bytes",
-    ],
-)
-
-cc_test(
-    name = "rpc_channel_test",
-    srcs = ["rpc_channel_test.cc"],
-    deps = [
-        ":pw_hdlc",
-        "//pw_stream",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_hdlc/BUILD.gn b/pw_hdlc/BUILD.gn
index 7ee98c1..41e5e4b 100644
--- a/pw_hdlc/BUILD.gn
+++ b/pw_hdlc/BUILD.gn
@@ -125,7 +125,7 @@
 pw_python_action("generate_decoder_test") {
   outputs = [ "$target_gen_dir/generated_decoder_test.cc" ]
   script = "py/decode_test.py"
-  args = [ "--generate-cc-test" ] + rebase_path(outputs, root_build_dir)
+  args = [ "--generate-cc-test" ] + rebase_path(outputs)
   python_deps = [
     "$dir_pw_build/py",
     "py",
diff --git a/pw_hdlc/CMakeLists.txt b/pw_hdlc/CMakeLists.txt
index 97626c2..5f48d55 100644
--- a/pw_hdlc/CMakeLists.txt
+++ b/pw_hdlc/CMakeLists.txt
@@ -30,7 +30,3 @@
 )
 
 add_subdirectory(rpc_example)
-
-if(Zephyr_FOUND AND CONFIG_PIGWEED_HDLC)
-  zephyr_link_libraries(pw_hdlc)
-endif()
diff --git a/pw_hdlc/Kconfig b/pw_hdlc/Kconfig
deleted file mode 100644
index de042ed..0000000
--- a/pw_hdlc/Kconfig
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-config PIGWEED_HDLC
-    bool "Enable Pigweed HDLC library (pw_hdlc)"
-    select PIGWEED_ASSERT
-    select PIGWEED_BYTES
-    select PIGWEED_CHECKSUM
-    select PIGWEED_RESULT
-    select PIGWEED_ROUTER_PACKET_PARSER
-    select PIGWEED_RPC_COMMON
-    select PIGWEED_STATUS
-    select PIGWEED_STREAM
-    select PIGWEED_SYS_IO
-    select PIGWEED_LOG
diff --git a/pw_hdlc/OWNERS b/pw_hdlc/OWNERS
deleted file mode 100644
index 34fbf1b..0000000
--- a/pw_hdlc/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-frolv@google.com
-hepler@google.com
diff --git a/pw_hdlc/decoder.cc b/pw_hdlc/decoder.cc
index b66cdf9..c2e10aa 100644
--- a/pw_hdlc/decoder.cc
+++ b/pw_hdlc/decoder.cc
@@ -14,7 +14,7 @@
 
 #include "pw_hdlc/decoder.h"
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_bytes/endian.h"
 #include "pw_hdlc/internal/protocol.h"
 #include "pw_log/log.h"
diff --git a/pw_hdlc/docs.rst b/pw_hdlc/docs.rst
index 3004891..6791855 100644
--- a/pw_hdlc/docs.rst
+++ b/pw_hdlc/docs.rst
@@ -134,32 +134,13 @@
   from pw_hdlc import encode
 
   ser = serial.Serial()
-  address = 123
-  ser.write(encode.ui_frame(address, b'your data here!'))
-
-Typescript
-^^^^^^^^^^
-
-Encoder
--------
-The Encoder class provides a way to build complete, escaped HDLC UI frames.
-
-.. js:method:: Encoder.uiFrame(address, data)
-
-    :param number address: frame address.
-    :param Uint8Array data: frame data.
-    :returns: Uint8Array containing a complete HDLC frame.
+  ser.write(encode.ui_frame(b'your data here!'))
 
 Decoder
 -------
 The decoder class unescapes received bytes and adds them to a buffer. Complete,
 valid HDLC frames are yielded as they are received.
 
-.. js:method:: Decoder.process(bytes)
-
-    :param Uint8Array bytes: bytes received from the medium.
-    :yields: Frame complete frames.
-
 C++
 ^^^
 .. cpp:class:: pw::hdlc::Decoder
@@ -223,22 +204,6 @@
       for frame in decoder.process_valid_frames(ser.read()):
           # Handle the decoded frame
 
-Typescript
-^^^^^^^^^^
-
-Decodes one or more HDLC frames from a stream of data.
-
-.. js:method:: process(data)
-
-    :param Uint8Array data: bytes to be decoded.
-    :yields: HDLC frames, including corrupt frames.
-      The Frame.ok() method whether the frame is valid.
-
-.. js:method:: processValidFrames(data)
-
-    :param Uint8Array data: bytes to be decoded.
-    :yields: Valid HDLC frames, logging any errors.
-
 Additional features
 ===================
 
@@ -253,9 +218,6 @@
 .. autoclass:: pw_hdlc.rpc.HdlcRpcClient
   :members:
 
-.. autoclass:: pw_hdlc.rpc.HdlcRpcLocalServerAndClient
-  :members:
-
 Roadmap
 =======
 - **Expanded protocol support** - ``pw_hdlc`` currently only supports
@@ -268,8 +230,3 @@
 Compatibility
 =============
 C++17
-
-Zephyr
-======
-To enable ``pw_hdlc`` for Zephyr add ``CONFIG_PIGWEED_HDLC=y`` to the project's
-configuration.
diff --git a/pw_hdlc/encoder_test.cc b/pw_hdlc/encoder_test.cc
index 1904fbc..e5359a7 100644
--- a/pw_hdlc/encoder_test.cc
+++ b/pw_hdlc/encoder_test.cc
@@ -181,7 +181,7 @@
   EXPECT_EQ(0u, writer_.bytes_written());
 }
 
-class ErrorWriter : public stream::NonSeekableWriter {
+class ErrorWriter : public stream::Writer {
  private:
   Status DoWrite(ConstByteSpan) override { return Status::Unimplemented(); }
 };
diff --git a/pw_hdlc/public/pw_hdlc/decoder.h b/pw_hdlc/public/pw_hdlc/decoder.h
index 94caf17..f6f5707 100644
--- a/pw_hdlc/public/pw_hdlc/decoder.h
+++ b/pw_hdlc/public/pw_hdlc/decoder.h
@@ -19,7 +19,7 @@
 #include <cstring>
 #include <functional>  // std::invoke
 
-#include "pw_assert/assert.h"
+#include "pw_assert/light.h"
 #include "pw_bytes/span.h"
 #include "pw_checksum/crc32.h"
 #include "pw_result/result.h"
@@ -93,7 +93,7 @@
   //     DATA_LOSS - A frame completed, but it was invalid. The frame was
   //         incomplete or the frame check sequence verification failed.
   //
-  Result<Frame> Process(std::byte new_byte);
+  Result<Frame> Process(std::byte b);
 
   // Processes a span of data and calls the provided callback with each frame or
   // error.
diff --git a/pw_hdlc/public/pw_hdlc/rpc_channel.h b/pw_hdlc/public/pw_hdlc/rpc_channel.h
index 7f0cbf2..58dac72 100644
--- a/pw_hdlc/public/pw_hdlc/rpc_channel.h
+++ b/pw_hdlc/public/pw_hdlc/rpc_channel.h
@@ -16,7 +16,7 @@
 #include <array>
 #include <span>
 
-#include "pw_assert/assert.h"
+#include "pw_assert/light.h"
 #include "pw_hdlc/encoder.h"
 #include "pw_rpc/channel.h"
 #include "pw_stream/stream.h"
@@ -27,23 +27,62 @@
 // the HDLC protocol.
 //
 // WARNING: This ChannelOutput is not thread-safe. If thread-safety is required,
-// create a similar class that adds a mtuex to Send.
+// wrap this in a pw::rpc::SynchronizedChannelOutput.
 class RpcChannelOutput : public rpc::ChannelOutput {
  public:
   // The RpcChannelOutput class does not own the buffer it uses to store the
   // protobuf bytes. This buffer is specified at the time of creation along with
   // a writer object to which will be used to write and send the bytes.
   constexpr RpcChannelOutput(stream::Writer& writer,
+                             std::span<std::byte> buffer,
                              uint64_t address,
                              const char* channel_name)
-      : ChannelOutput(channel_name), writer_(writer), address_(address) {}
+      : ChannelOutput(channel_name),
+        writer_(writer),
+        buffer_(buffer),
+        address_(address) {}
 
-  Status Send(std::span<const std::byte> buffer) override {
+  std::span<std::byte> AcquireBuffer() override { return buffer_; }
+
+  Status SendAndReleaseBuffer(std::span<const std::byte> buffer) override {
+    PW_DASSERT(buffer.data() == buffer_.data());
+    if (buffer.empty()) {
+      return OkStatus();
+    }
     return hdlc::WriteUIFrame(address_, buffer, writer_);
   }
 
  private:
   stream::Writer& writer_;
+  const std::span<std::byte> buffer_;
+  const uint64_t address_;
+};
+
+// RpcChannelOutput with its own buffer.
+//
+// WARNING: This ChannelOutput is not thread-safe. If thread-safety is required,
+// wrap this in a pw::rpc::SynchronizedChannelOutput.
+template <size_t kBufferSize>
+class RpcChannelOutputBuffer : public rpc::ChannelOutput {
+ public:
+  constexpr RpcChannelOutputBuffer(stream::Writer& writer,
+                                   uint64_t address,
+                                   const char* channel_name)
+      : ChannelOutput(channel_name), writer_(writer), address_(address) {}
+
+  std::span<std::byte> AcquireBuffer() override { return buffer_; }
+
+  Status SendAndReleaseBuffer(std::span<const std::byte> buffer) override {
+    PW_DASSERT(buffer.data() == buffer_.data());
+    if (buffer.empty()) {
+      return OkStatus();
+    }
+    return hdlc::WriteUIFrame(address_, buffer, writer_);
+  }
+
+ private:
+  stream::Writer& writer_;
+  std::array<std::byte, kBufferSize> buffer_;
   const uint64_t address_;
 };
 
diff --git a/pw_hdlc/py/BUILD.bazel b/pw_hdlc/py/BUILD.bazel
deleted file mode 100644
index a88424d..0000000
--- a/pw_hdlc/py/BUILD.bazel
+++ /dev/null
@@ -1,59 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load("@rules_python//python:defs.bzl", "py_library", "py_test")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-py_library(
-    name = "pw_hdlc",
-    srcs = [
-        "pw_hdlc/__init__.py",
-        "pw_hdlc/decode.py",
-        "pw_hdlc/encode.py",
-        "pw_hdlc/protocol.py",
-        "pw_hdlc/rpc.py",
-    ],
-    imports = ["."],
-    deps = [
-        "//pw_protobuf_compiler/py:pw_protobuf_compiler",
-        "//pw_rpc/py:pw_rpc",
-        "//pw_status/py:pw_status",
-    ],
-)
-
-py_test(
-    name = "encode_test",
-    size = "small",
-    srcs = [
-        "encode_test.py",
-    ],
-    deps = [
-        ":pw_hdlc",
-    ],
-)
-
-py_test(
-    name = "decode_test",
-    size = "small",
-    srcs = [
-        "decode_test.py",
-    ],
-    deps = [
-        ":pw_hdlc",
-        "//pw_build/py:pw_build",
-    ],
-)
diff --git a/pw_hdlc/py/BUILD.gn b/pw_hdlc/py/BUILD.gn
index 8e9c157..f01f29d 100644
--- a/pw_hdlc/py/BUILD.gn
+++ b/pw_hdlc/py/BUILD.gn
@@ -17,11 +17,7 @@
 import("$dir_pw_build/python.gni")
 
 pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
+  setup = [ "setup.py" ]
   sources = [
     "pw_hdlc/__init__.py",
     "pw_hdlc/decode.py",
@@ -35,7 +31,6 @@
     "encode_test.py",
   ]
   python_deps = [
-    "$dir_pw_console/py",
     "$dir_pw_protobuf_compiler/py",
     "$dir_pw_rpc/py",
   ]
diff --git a/pw_hdlc/py/decode_test.py b/pw_hdlc/py/decode_test.py
index f230ad2..5aa8f90 100755
--- a/pw_hdlc/py/decode_test.py
+++ b/pw_hdlc/py/decode_test.py
@@ -218,57 +218,6 @@
 }  // namespace
 }  // namespace pw::hdlc"""
 
-_TS_HEADER = """\
-import 'jasmine';
-
-import {Buffer} from 'buffer';
-
-import {Decoder, FrameStatus} from './decoder'
-import * as protocol from './protocol'
-import * as util from './util'
-
-class Expected {
-  address: number
-  control: Uint8Array
-  data: Uint8Array
-  status: FrameStatus
-
-  constructor(
-      address: number,
-      control: Uint8Array,
-      data: Uint8Array,
-      status: FrameStatus) {
-    this.address = address;
-    this.control = control;
-    this.data = data;
-    this.status = status;
-  }
-}
-
-class ExpectedRaw {
-  raw: Uint8Array
-  status: FrameStatus
-
-  constructor(raw: Uint8Array, status: FrameStatus) {
-    this.status = status;
-    this.raw = raw;
-  }
-}
-
-describe('Decoder', () => {
-  let decoder: Decoder;
-  let textEncoder: TextEncoder;
-
-  beforeEach(() => {
-    decoder = new Decoder();
-    textEncoder = new TextEncoder();
-  });
-
-"""
-_TS_FOOTER = """\
-});
-"""
-
 
 def _cpp_test(ctx: Context) -> Iterator[str]:
     """Generates a C++ test for the provided test data."""
@@ -335,6 +284,7 @@
         self.assertEqual(expected_frames,
                          list(FrameDecoder().process(data)),
                          msg=f'{ctx.group}: {data!r}')
+
         # Decode byte-by-byte
         decoder = FrameDecoder()
         decoded_frames: List[Frame] = []
@@ -348,65 +298,6 @@
     return test
 
 
-def _ts_byte_array(data: bytes) -> str:
-    return '[' + ', '.join(rf'0x{byte:02x}' for byte in data) + ']'
-
-
-def _ts_test(ctx: Context) -> Iterator[str]:
-    """Generates a TS test for the provided test data."""
-    data, _ = ctx.test_case
-    frames = list(FrameDecoder().process(data))
-    data_bytes = _ts_byte_array(data)
-
-    yield f'  it(\'{ctx.ts_name()}\', () => {{'
-    yield f'    const data = new Uint8Array({data_bytes});'
-
-    yield '    const expectedFrames = ['
-    for frame in frames:
-        control_bytes = _ts_byte_array(frame.control)
-        frame_bytes = _ts_byte_array(frame.data)
-
-        if frame is Expected:
-            yield (f'      new Expected({frame.address}, '
-                   f'new Uint8Array({control_bytes}), '
-                   f'new Uint8Array({frame_bytes}), {frame.status}),')
-        else:
-            raw = _ts_byte_array(frame.raw_encoded)
-            yield (
-                f'      new ExpectedRaw(new Uint8Array({raw}), {frame.status}),'
-            )
-
-    yield '    ].values();\n'
-
-    yield """\
-    const result = decoder.process(data);
-
-    while (true) {
-      const expectedFrame = expectedFrames.next();
-      const actualFrame = result.next();
-      if (expectedFrame.done && actualFrame.done) {
-        break;
-      }
-      expect(expectedFrame.done).toBeFalse();
-      expect(actualFrame.done).toBeFalse();
-
-      const expected = expectedFrame.value;
-      const actual = actualFrame.value;
-      if (expected instanceof Expected) {
-        expect(actual.address).toEqual(expected.address);
-        expect(actual.control).toEqual(expected.control);
-        expect(actual.data).toEqual(expected.data);
-        expect(actual.status).toEqual(expected.status);
-      } else {
-        // Expected Raw
-        expect(actual.rawEncoded).toEqual(expected.raw);
-        expect(actual.status).toEqual(expected.status);
-      }
-    }
-  });
-"""
-
-
 # Class that tests all cases in TEST_CASES.
 DecoderTest = _TESTS.python_tests('DecoderTest', _define_py_test)
 
@@ -415,8 +306,5 @@
     if args.generate_cc_test:
         _TESTS.cc_tests(args.generate_cc_test, _cpp_test, _CPP_HEADER,
                         _CPP_FOOTER)
-    elif args.generate_ts_test:
-        _TESTS.ts_tests(args.generate_ts_test, _ts_test, _TS_HEADER,
-                        _TS_FOOTER)
     else:
         unittest.main()
diff --git a/pw_hdlc/py/pw_hdlc/rpc.py b/pw_hdlc/py/pw_hdlc/rpc.py
index b412753..d36463f 100644
--- a/pw_hdlc/py/pw_hdlc/rpc.py
+++ b/pw_hdlc/py/pw_hdlc/rpc.py
@@ -13,19 +13,13 @@
 # the License.
 """Utilities for using HDLC with pw_rpc."""
 
-import collections
 from concurrent.futures import ThreadPoolExecutor
-import io
 import logging
-from queue import SimpleQueue
-import random
 import sys
 import threading
 import time
-import socket
-import subprocess
-from typing import (Any, BinaryIO, Callable, Deque, Dict, Iterable, List,
-                    NoReturn, Optional, Sequence, Tuple, Union)
+from typing import (Any, BinaryIO, Callable, Dict, Iterable, List, NoReturn,
+                    Optional, Union)
 
 from pw_protobuf_compiler import python_protos
 import pw_rpc
@@ -38,7 +32,6 @@
 
 STDOUT_ADDRESS = 1
 DEFAULT_ADDRESS = ord('R')
-_VERBOSE = logging.DEBUG - 1
 
 
 def channel_output(writer: Callable[[bytes], Any],
@@ -58,7 +51,7 @@
 
     def write_hdlc(data: bytes):
         frame = encode.ui_frame(address, data)
-        _LOG.log(_VERBOSE, 'Write %2d B: %s', len(frame), frame)
+        _LOG.debug('Write %2d B: %s', len(frame), frame)
         writer(frame)
 
     return write_hdlc
@@ -111,7 +104,7 @@
                 continue
 
             if data:
-                _LOG.log(_VERBOSE, 'Read %2d B: %s', len(data), data)
+                _LOG.debug('Read %2d B: %s', len(data), data)
 
                 for frame in decoder.process_valid_frames(data):
                     executor.submit(handle_frame, frame)
@@ -126,21 +119,15 @@
     return [pw_rpc.Channel(1, channel_output(write))]
 
 
-PathsModulesOrProtoLibrary = Union[Iterable[python_protos.PathOrModule],
-                                   python_protos.Library]
-
-
 class HdlcRpcClient:
     """An RPC client configured to run over HDLC."""
     def __init__(self,
                  read: Callable[[], bytes],
-                 paths_or_modules: PathsModulesOrProtoLibrary,
+                 paths_or_modules: Union[Iterable[python_protos.PathOrModule],
+                                         python_protos.Library],
                  channels: Iterable[pw_rpc.Channel],
                  output: Callable[[bytes], Any] = write_to_file,
-                 client_impl: pw_rpc.client.ClientImpl = None,
-                 *,
-                 _incoming_packet_filter_for_testing: pw_rpc.
-                 ChannelManipulator = None):
+                 client_impl: pw_rpc.client.ClientImpl = None):
         """Creates an RPC client configured to communicate using HDLC.
 
         Args:
@@ -159,14 +146,8 @@
 
         self.client = pw_rpc.Client.from_modules(client_impl, channels,
                                                  self.protos.modules())
-
-        rpc_output: Callable[[bytes], Any] = self._handle_rpc_packet
-        if _incoming_packet_filter_for_testing is not None:
-            _incoming_packet_filter_for_testing.send_packet = rpc_output
-            rpc_output = _incoming_packet_filter_for_testing
-
         frame_handlers: FrameHandlers = {
-            DEFAULT_ADDRESS: lambda frame: rpc_output(frame.data),
+            DEFAULT_ADDRESS: self._handle_rpc_packet,
             STDOUT_ADDRESS: lambda frame: output(frame.data),
         }
 
@@ -188,166 +169,6 @@
 
         return self.client.channel(channel_id).rpcs
 
-    def _handle_rpc_packet(self, packet: bytes) -> None:
-        if not self.client.process_packet(packet):
-            _LOG.error('Packet not handled by RPC client: %s', packet)
-
-
-def _try_connect(port: int, attempts: int = 10) -> socket.socket:
-    """Tries to connect to the specified port up to the given number of times.
-
-    This is helpful when connecting to a process that was started by this
-    script. The process may need time to start listening for connections, and
-    that length of time can vary. This retries with a short delay rather than
-    having to wait for the worst case delay every time.
-    """
-    while True:
-        attempts -= 1
-        time.sleep(0.001)
-
-        try:
-            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-            sock.connect(('localhost', port))
-            return sock
-        except ConnectionRefusedError:
-            sock.close()
-            if attempts <= 0:
-                raise
-
-
-class SocketSubprocess:
-    """Executes a subprocess and connects to it with a socket."""
-    def __init__(self, command: Sequence, port: int) -> None:
-        self._server_process = subprocess.Popen(command, stdin=subprocess.PIPE)
-        self.stdin = self._server_process.stdin
-
-        try:
-            self.socket: socket.socket = _try_connect(port)  # 🧦
-        except:
-            self._server_process.terminate()
-            self._server_process.communicate()
-            raise
-
-    def close(self) -> None:
-        try:
-            self.socket.close()
-        finally:
-            self._server_process.terminate()
-            self._server_process.communicate()
-
-    def __enter__(self) -> 'SocketSubprocess':
-        return self
-
-    def __exit__(self, exc_type, exc_value, traceback) -> None:
-        self.close()
-
-
-class PacketFilter(pw_rpc.ChannelManipulator):
-    """Determines if a packet should be kept or dropped for testing purposes."""
-    _Action = Callable[[int], Tuple[bool, bool]]
-    _KEEP = lambda _: (True, False)
-    _DROP = lambda _: (False, False)
-
-    def __init__(self, name: str) -> None:
-        super().__init__()
-        self.name = name
-        self.packet_count = 0
-        self._actions: Deque[PacketFilter._Action] = collections.deque()
-
-    def process_and_send(self, packet: bytes):
-        if self.keep_packet(packet):
-            self.send_packet(packet)
-
-    def reset(self) -> None:
-        self.packet_count = 0
-        self._actions.clear()
-
-    def keep(self, count: int) -> None:
-        """Keeps the next count packets."""
-        self._actions.extend(PacketFilter._KEEP for _ in range(count))
-
-    def drop(self, count: int) -> None:
-        """Drops the next count packets."""
-        self._actions.extend(PacketFilter._DROP for _ in range(count))
-
-    def drop_every(self, every: int) -> None:
-        """Drops every Nth packet forever."""
-        self._actions.append(lambda count: (count % every != 0, True))
-
-    def randomly_drop(self, one_in: int, gen: random.Random) -> None:
-        """Drops packets randomly forever."""
-        self._actions.append(lambda _: (gen.randrange(one_in) != 0, True))
-
-    def keep_packet(self, packet: bytes) -> bool:
-        """Returns whether the provided packet should be kept or dropped."""
-        self.packet_count += 1
-
-        if not self._actions:
-            return True
-
-        keep, repeat = self._actions[0](self.packet_count)
-
-        if not repeat:
-            self._actions.popleft()
-
-        if not keep:
-            _LOG.debug('Dropping %s packet %d for testing: %s', self.name,
-                       self.packet_count, packet)
-        return keep
-
-
-class HdlcRpcLocalServerAndClient:
-    """Runs an RPC server in a subprocess and connects to it over a socket.
-
-    This can be used to run a local RPC server in an integration test.
-    """
-    def __init__(
-        self,
-        server_command: Sequence,
-        port: int,
-        protos: PathsModulesOrProtoLibrary,
-        *,
-        incoming_processor: Optional[pw_rpc.ChannelManipulator] = None,
-        outgoing_processor: Optional[pw_rpc.ChannelManipulator] = None
-    ) -> None:
-        """Creates a new HdlcRpcLocalServerAndClient."""
-
-        self.server = SocketSubprocess(server_command, port)
-
-        self._bytes_queue: 'SimpleQueue[bytes]' = SimpleQueue()
-        self._read_thread = threading.Thread(target=self._read_from_socket)
-        self._read_thread.start()
-
-        self.output = io.BytesIO()
-
-        self.channel_output: Any = self.server.socket.sendall
-
-        self._incoming_processor = incoming_processor
-        if outgoing_processor is not None:
-            outgoing_processor.send_packet = self.channel_output
-            self.channel_output = outgoing_processor
-
-        self.client = HdlcRpcClient(
-            self._bytes_queue.get,
-            protos,
-            default_channels(self.channel_output),
-            self.output.write,
-            _incoming_packet_filter_for_testing=incoming_processor).client
-
-    def _read_from_socket(self):
-        while True:
-            data = self.server.socket.recv(4096)
-            self._bytes_queue.put(data)
-            if not data:
-                return
-
-    def close(self):
-        self.server.close()
-        self.output.close()
-        self._read_thread.join()
-
-    def __enter__(self) -> 'HdlcRpcLocalServerAndClient':
-        return self
-
-    def __exit__(self, exc_type, exc_value, traceback) -> None:
-        self.close()
+    def _handle_rpc_packet(self, frame: Frame) -> None:
+        if not self.client.process_packet(frame.data):
+            _LOG.error('Packet not handled by RPC client: %s', frame.data)
diff --git a/pw_hdlc/py/pw_hdlc/rpc_console.py b/pw_hdlc/py/pw_hdlc/rpc_console.py
index 38bc6e0..f5484c8 100644
--- a/pw_hdlc/py/pw_hdlc/rpc_console.py
+++ b/pw_hdlc/py/pw_hdlc/rpc_console.py
@@ -33,42 +33,18 @@
 
 import argparse
 import glob
-from inspect import cleandoc
 import logging
 from pathlib import Path
 import sys
-from types import ModuleType
-from typing import (
-    Any,
-    BinaryIO,
-    Collection,
-    Iterable,
-    Iterator,
-    List,
-    Optional,
-    Union,
-)
+from typing import Any, Collection, Iterable, Iterator
 import socket
 
+import IPython  # type: ignore
 import serial  # type: ignore
 
-import pw_cli.log
-import pw_console.python_logging
-from pw_console import PwConsoleEmbed
-from pw_console.pyserial_wrapper import SerialWithLogging
-from pw_console.plugins.bandwidth_toolbar import BandwidthToolbar
-
-from pw_log.proto import log_pb2
-from pw_rpc.console_tools.console import ClientInfo, flattened_rpc_completions
-from pw_rpc import callback_client
-from pw_tokenizer.database import LoadTokenDatabases
-from pw_tokenizer.detokenize import Detokenizer, detokenize_base64
-from pw_tokenizer import tokens
-
-from pw_hdlc.rpc import HdlcRpcClient, default_channels
+from pw_hdlc.rpc import HdlcRpcClient, default_channels, write_to_file
 
 _LOG = logging.getLogger(__name__)
-_DEVICE_LOG = logging.getLogger('rpc_device')
 
 PW_RPC_MAX_PACKET_SIZE = 256
 SOCKET_SERVER = 'localhost'
@@ -87,32 +63,18 @@
                         default=115200,
                         help='the baud rate to use')
     parser.add_argument(
-        '--serial-debug',
-        action='store_true',
-        help=('Enable debug log tracing of all data passed through'
-              'pyserial read and write.'))
-    parser.add_argument(
         '-o',
         '--output',
         type=argparse.FileType('wb'),
         default=sys.stdout.buffer,
         help=('The file to which to write device output (HDLC channel 1); '
               'provide - or omit for stdout.'))
-    parser.add_argument('--logfile', help='Console debug log file.')
     group.add_argument('-s',
                        '--socket-addr',
                        type=str,
                        help='use socket to connect to server, type default for\
             localhost:33000, or manually input the server address:port')
-    parser.add_argument("--token-databases",
-                        metavar='elf_or_token_database',
-                        nargs="+",
-                        action=LoadTokenDatabases,
-                        help="Path to tokenizer database csv file(s).")
-    parser.add_argument('--config-file',
-                        type=Path,
-                        help='Path to a pw_console yaml config file.')
-    parser.add_argument('--proto-globs',
+    parser.add_argument('proto_globs',
                         nargs='+',
                         help='glob pattern for .proto files')
     return parser.parse_args()
@@ -124,66 +86,18 @@
             yield Path(file)
 
 
-def _start_ipython_terminal(client: HdlcRpcClient,
-                            serial_debug: bool = False,
-                            config_file_path: Optional[Path] = None) -> None:
+def _start_ipython_terminal(client: HdlcRpcClient) -> None:
     """Starts an interactive IPython terminal with preset variables."""
     local_variables = dict(
         client=client,
-        device=client.client.channel(1),
+        channel_client=client.client.channel(1),
         rpcs=client.client.channel(1).rpcs,
         protos=client.protos.packages,
-        # Include the active pane logger for creating logs in the repl.
-        DEVICE_LOG=_DEVICE_LOG,
-        LOG=logging.getLogger(),
     )
 
-    welcome_message = cleandoc("""
-        Welcome to the Pigweed Console!
-
-        Help: Press F1 or click the [Help] menu
-        To move focus: Press Shift-Tab or click on a window
-
-        Example Python commands:
-
-          device.rpcs.pw.rpc.EchoService.Echo(msg='hello!')
-          LOG.warning('Message appears in Host Logs window.')
-          DEVICE_LOG.warning('Message appears in Device Logs window.')
-    """)
-
-    client_info = ClientInfo('device',
-                             client.client.channel(1).rpcs, client.client)
-    completions = flattened_rpc_completions([client_info])
-
-    log_windows = {
-        'Device Logs': [_DEVICE_LOG],
-        'Host Logs': [logging.getLogger()],
-    }
-    if serial_debug:
-        log_windows['Serial Debug'] = [
-            logging.getLogger('pw_console.serial_debug_logger')
-        ]
-
-    interactive_console = PwConsoleEmbed(
-        global_vars=local_variables,
-        local_vars=None,
-        loggers=log_windows,
-        repl_startup_message=welcome_message,
-        help_text=__doc__,
-        config_file_path=config_file_path,
-    )
-    interactive_console.hide_windows('Host Logs')
-    interactive_console.add_sentence_completer(completions)
-    if serial_debug:
-        interactive_console.add_bottom_toolbar(BandwidthToolbar())
-
-    # Setup Python logger propagation
-    interactive_console.setup_python_logging()
-
-    # Don't send device logs to the root logger.
-    _DEVICE_LOG.propagate = False
-
-    interactive_console.embed()
+    print(__doc__)  # Print the banner
+    IPython.terminal.embed.InteractiveShellEmbed().mainloop(
+        local_ns=local_variables, module=argparse.Namespace())
 
 
 class SocketClientImpl:
@@ -207,40 +121,17 @@
         return self.socket.recv(num_bytes)
 
 
-def console(device: str,
-            baudrate: int,
-            proto_globs: Collection[str],
-            token_databases: Collection[tokens.Database],
-            socket_addr: str,
-            logfile: str,
-            output: Any,
-            serial_debug: bool = False,
-            config_file: Optional[Path] = None) -> int:
+def console(device: str, baudrate: int, proto_globs: Collection[str],
+            socket_addr: str, output: Any) -> int:
     """Starts an interactive RPC console for HDLC."""
     # argparse.FileType doesn't correctly handle '-' for binary files.
     if output is sys.stdout:
         output = sys.stdout.buffer
 
-    if not logfile:
-        # Create a temp logfile to prevent logs from appearing over stdout. This
-        # would corrupt the prompt toolkit UI.
-        logfile = pw_console.python_logging.create_temp_log_file()
-    pw_cli.log.install(logging.INFO, True, False, logfile)
-
-    detokenizer = None
-    if token_databases:
-        detokenizer = Detokenizer(tokens.Database.merged(*token_databases),
-                                  show_errors=False)
-
     if not proto_globs:
         proto_globs = ['**/*.proto']
 
-    protos: List[Union[ModuleType, Path]] = list(_expand_globs(proto_globs))
-
-    # Append compiled log.proto library to avoid include errors when manually
-    # provided, and shadowing errors due to ordering when the default global
-    # search path is used.
-    protos.append(log_pb2)
+    protos = list(_expand_globs(proto_globs))
 
     if not protos:
         _LOG.critical('No .proto files were found with %s',
@@ -251,16 +142,8 @@
     _LOG.debug('Found %d .proto files found with %s', len(protos),
                ', '.join(proto_globs))
 
-    serial_impl = serial.Serial
-    if serial_debug:
-        serial_impl = SerialWithLogging
-
     if socket_addr is None:
-        serial_device = serial_impl(
-            device,
-            baudrate,
-            timeout=0,  # Non-blocking mode
-        )
+        serial_device = serial.Serial(device, baudrate, timeout=1)
         read = lambda: serial_device.read(8192)
         write = serial_device.write
     else:
@@ -272,32 +155,12 @@
             _LOG.exception('Failed to initialize socket at %s', socket_addr)
             return 1
 
-    callback_client_impl = callback_client.Impl(
-        default_unary_timeout_s=5.0,
-        default_stream_timeout_s=None,
-    )
     _start_ipython_terminal(
-        HdlcRpcClient(read,
-                      protos,
-                      default_channels(write),
-                      lambda data: detokenize_and_write_to_output(
-                          data, output, detokenizer),
-                      client_impl=callback_client_impl), serial_debug,
-        config_file)
+        HdlcRpcClient(read, protos, default_channels(write),
+                      lambda data: write_to_file(data, output)))
     return 0
 
 
-def detokenize_and_write_to_output(data: bytes,
-                                   unused_output: BinaryIO = sys.stdout.buffer,
-                                   detokenizer=None):
-    log_line = data
-    if detokenizer:
-        log_line = detokenize_base64(detokenizer, data)
-
-    for line in log_line.decode(errors="surrogateescape").splitlines():
-        _DEVICE_LOG.info(line)
-
-
 def main() -> int:
     return console(**vars(_parse_args()))
 
diff --git a/pw_hdlc/py/pyproject.toml b/pw_hdlc/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/pw_hdlc/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/pw_hdlc/py/setup.cfg b/pw_hdlc/py/setup.cfg
deleted file mode 100644
index b3b18a3..0000000
--- a/pw_hdlc/py/setup.cfg
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pw_hdlc
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Tools for Encoding/Decoding data using the HDLC protocol
-
-[options]
-packages = find:
-zip_safe = False
-install_requires =
-    ipython
-    pw_console
-    pw_protobuf_compiler
-    pw_rpc
-tests_require = pw_build
-
-[options.package_data]
-pw_hdlc = py.typed
diff --git a/pw_hdlc/py/setup.py b/pw_hdlc/py/setup.py
index bf8cfe6..88db976 100644
--- a/pw_hdlc/py/setup.py
+++ b/pw_hdlc/py/setup.py
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2020 The Pigweed Authors
 #
 # 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
@@ -15,4 +15,19 @@
 
 import setuptools  # type: ignore
 
-setuptools.setup()  # Package definition in setup.cfg
+setuptools.setup(
+    name='pw_hdlc',
+    version='0.0.1',
+    author='Pigweed Authors',
+    author_email='pigweed-developers@googlegroups.com',
+    description='Tools for Encoding/Decoding data using the HDLC protocol',
+    packages=setuptools.find_packages(),
+    package_data={'pw_hdlc': ['py.typed']},
+    zip_safe=False,
+    install_requires=[
+        'ipython',
+        'pw_protobuf_compiler',
+        'pw_rpc',
+    ],
+    tests_require=['pw_build'],
+)
diff --git a/pw_hdlc/rpc_channel_test.cc b/pw_hdlc/rpc_channel_test.cc
index e0b972f..538f699 100644
--- a/pw_hdlc/rpc_channel_test.cc
+++ b/pw_hdlc/rpc_channel_test.cc
@@ -50,19 +50,21 @@
 constexpr size_t kSinkBufferSize = 15;
 
 TEST(RpcChannelOutput, 1BytePayload) {
+  std::array<byte, kSinkBufferSize> channel_output_buffer;
   stream::MemoryWriterBuffer<kSinkBufferSize> memory_writer;
 
-  RpcChannelOutput output(memory_writer, kAddress, "RpcChannelOutput");
+  RpcChannelOutput output(
+      memory_writer, channel_output_buffer, kAddress, "RpcChannelOutput");
 
   constexpr byte test_data = byte{'A'};
-  std::array<std::byte, 128> buffer;
+  auto buffer = output.AcquireBuffer();
   std::memcpy(buffer.data(), &test_data, sizeof(test_data));
 
   constexpr auto expected = bytes::Concat(
       kFlag, kEncodedAddress, kControl, 'A', uint32_t{0x653c9e82}, kFlag);
 
   EXPECT_EQ(OkStatus(),
-            output.Send(std::span(buffer).first(sizeof(test_data))));
+            output.SendAndReleaseBuffer(buffer.first(sizeof(test_data))));
 
   ASSERT_EQ(memory_writer.bytes_written(), expected.size());
   EXPECT_EQ(
@@ -72,12 +74,14 @@
 }
 
 TEST(RpcChannelOutput, EscapingPayloadTest) {
+  std::array<byte, kSinkBufferSize> channel_output_buffer;
   stream::MemoryWriterBuffer<kSinkBufferSize> memory_writer;
 
-  RpcChannelOutput output(memory_writer, kAddress, "RpcChannelOutput");
+  RpcChannelOutput output(
+      memory_writer, channel_output_buffer, kAddress, "RpcChannelOutput");
 
   constexpr auto test_data = bytes::Array<0x7D>();
-  std::array<std::byte, 128> buffer;
+  auto buffer = output.AcquireBuffer();
   std::memcpy(buffer.data(), test_data.data(), test_data.size());
 
   constexpr auto expected = bytes::Concat(kFlag,
@@ -87,7 +91,8 @@
                                           byte{0x7d} ^ byte{0x20},
                                           uint32_t{0x4a53e205},
                                           kFlag);
-  EXPECT_EQ(OkStatus(), output.Send(std::span(buffer).first(test_data.size())));
+  EXPECT_EQ(OkStatus(),
+            output.SendAndReleaseBuffer(buffer.first(test_data.size())));
 
   ASSERT_EQ(memory_writer.bytes_written(), 10u);
   EXPECT_EQ(
@@ -99,17 +104,18 @@
 TEST(RpcChannelOutputBuffer, 1BytePayload) {
   stream::MemoryWriterBuffer<kSinkBufferSize> memory_writer;
 
-  RpcChannelOutput output(memory_writer, kAddress, "RpcChannelOutput");
+  RpcChannelOutputBuffer<kSinkBufferSize> output(
+      memory_writer, kAddress, "RpcChannelOutput");
 
   constexpr byte test_data = byte{'A'};
-  std::array<std::byte, 128> buffer;
+  auto buffer = output.AcquireBuffer();
   std::memcpy(buffer.data(), &test_data, sizeof(test_data));
 
   constexpr auto expected = bytes::Concat(
       kFlag, kEncodedAddress, kControl, 'A', uint32_t{0x653c9e82}, kFlag);
 
   EXPECT_EQ(OkStatus(),
-            output.Send(std::span(buffer).first(sizeof(test_data))));
+            output.SendAndReleaseBuffer(buffer.first(sizeof(test_data))));
 
   ASSERT_EQ(memory_writer.bytes_written(), expected.size());
   EXPECT_EQ(
@@ -121,10 +127,11 @@
 TEST(RpcChannelOutputBuffer, MultibyteAddress) {
   stream::MemoryWriterBuffer<kSinkBufferSize> memory_writer;
 
-  RpcChannelOutput output(memory_writer, 0x3fff, "RpcChannelOutput");
+  RpcChannelOutputBuffer<kSinkBufferSize> output(
+      memory_writer, 0x3fff, "RpcChannelOutput");
 
   constexpr byte test_data = byte{'A'};
-  std::array<std::byte, 128> buffer;
+  auto buffer = output.AcquireBuffer();
   std::memcpy(buffer.data(), &test_data, sizeof(test_data));
 
   constexpr auto expected = bytes::Concat(kFlag,
@@ -135,7 +142,7 @@
                                           kFlag);
 
   EXPECT_EQ(OkStatus(),
-            output.Send(std::span(buffer).first(sizeof(test_data))));
+            output.SendAndReleaseBuffer(buffer.first(sizeof(test_data))));
 
   ASSERT_EQ(memory_writer.bytes_written(), expected.size());
   EXPECT_EQ(
diff --git a/pw_hdlc/rpc_example/BUILD b/pw_hdlc/rpc_example/BUILD
new file mode 100644
index 0000000..b2a48a2
--- /dev/null
+++ b/pw_hdlc/rpc_example/BUILD
@@ -0,0 +1,38 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+pw_cc_library(
+    name = "rpc_example",
+    srcs = [
+        "hdlc_rpc_server.cc",
+        "main.cc",
+    ],
+    hdrs = [
+        "public/pw_hdlc/decoder.h",
+        "public/pw_hdlc/hdlc_channel.h",
+        "public/pw_hdlc/rpc_server_packets.h",
+    ],
+    deps = [
+        "//pw_hdlc",
+        "//pw_hdlc:pw_rpc",
+        "//pw_rpc:server",
+        "//pw_log",
+    ],
+)
+
diff --git a/pw_hdlc/rpc_example/BUILD.bazel b/pw_hdlc/rpc_example/BUILD.bazel
deleted file mode 100644
index fd942e3..0000000
--- a/pw_hdlc/rpc_example/BUILD.bazel
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-pw_cc_library(
-    name = "rpc_example",
-    srcs = [
-        "hdlc_rpc_server.cc",
-        "main.cc",
-    ],
-    hdrs = [
-        "public/pw_hdlc/decoder.h",
-        "public/pw_hdlc/hdlc_channel.h",
-        "public/pw_hdlc/rpc_server_packets.h",
-    ],
-    deps = [
-        "//pw_hdlc",
-        "//pw_hdlc:pw_rpc",
-        "//pw_log",
-        "//pw_rpc",
-    ],
-)
diff --git a/pw_hdlc/rpc_example/docs.rst b/pw_hdlc/rpc_example/docs.rst
index 0db8bfa..d4b4ca3 100644
--- a/pw_hdlc/rpc_example/docs.rst
+++ b/pw_hdlc/rpc_example/docs.rst
@@ -41,7 +41,7 @@
 
 .. code-block:: sh
 
- openocd -f targets/stm32f429i_disc1/py/stm32f429i_disc1_utils/openocd_stm32f4xx.cfg \
+ openocd -f targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/openocd_stm32f4xx.cfg \
      -c "program out/stm32f429i_disc1_debug/obj/pw_hdlc/rpc_example/bin/rpc_example.elf"
 
 4. Invoke RPCs from in an interactive console
@@ -129,6 +129,6 @@
 
 .. code-block:: sh
 
-  out/pw_strict_host_clang_debug/obj/pw_hdlc/rpc_example/bin/rpc_example
+  out/host_clang_debug/obj/pw_hdlc/rpc_example/bin/rpc_example
 
 Then you can invoke RPCs from the interactive console on the client side.
diff --git a/pw_hdlc/rpc_example/hdlc_rpc_server.cc b/pw_hdlc/rpc_example/hdlc_rpc_server.cc
index b878e64..a6184c7 100644
--- a/pw_hdlc/rpc_example/hdlc_rpc_server.cc
+++ b/pw_hdlc/rpc_example/hdlc_rpc_server.cc
@@ -16,7 +16,6 @@
 #include <span>
 #include <string_view>
 
-#include "pw_assert/check.h"
 #include "pw_hdlc/encoder.h"
 #include "pw_hdlc/rpc_packets.h"
 #include "pw_log/log.h"
@@ -27,6 +26,8 @@
 namespace hdlc_example {
 namespace {
 
+using std::byte;
+
 pw::rpc::EchoService echo_service;
 
 void RegisterServices() {
@@ -42,7 +43,7 @@
   RegisterServices();
 
   PW_LOG_INFO("Starting pw_rpc server");
-  PW_CHECK_OK(pw::rpc::system_server::Start());
+  pw::rpc::system_server::Start();
 }
 
 }  // namespace hdlc_example
diff --git a/pw_hdlc/rpc_packets.cc b/pw_hdlc/rpc_packets.cc
index e3cf4ac..9d83d86 100644
--- a/pw_hdlc/rpc_packets.cc
+++ b/pw_hdlc/rpc_packets.cc
@@ -32,8 +32,7 @@
     if (auto result = decoder.Process(data); result.ok()) {
       Frame& frame = result.value();
       if (frame.address() == rpc_address) {
-        server.ProcessPacket(frame.data(), output)
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        server.ProcessPacket(frame.data(), output);
       }
     }
   }
diff --git a/pw_hdlc/ts/BUILD.bazel b/pw_hdlc/ts/BUILD.bazel
deleted file mode 100644
index f4999ff..0000000
--- a/pw_hdlc/ts/BUILD.bazel
+++ /dev/null
@@ -1,68 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
-load("@npm//@bazel/typescript:index.bzl", "ts_library", "ts_project")
-load("@npm//@bazel/jasmine:index.bzl", "jasmine_node_test")
-
-package(default_visibility = ["//visibility:public"])
-
-ts_project(
-    name = "lib",
-    srcs = [
-        "decoder.ts",
-        "encoder.ts",
-        "index.ts",
-        "protocol.ts",
-        "util.ts",
-    ],
-    declaration = True,
-    source_map = True,
-    deps = ["@npm//:node_modules"],  # can't use fine-grained deps
-)
-
-js_library(
-    name = "pw_hdlc",
-    package_name = "@pigweed/pw_hdlc",
-    srcs = ["package.json"],
-    deps = [":lib"],
-)
-
-ts_library(
-    name = "hdlc_test_lib",
-    srcs = [
-        "decoder_test.ts",
-        "encoder_test.ts",
-    ],
-    deps = [
-        ":lib",
-        "@npm//@types/jasmine",
-        "@npm//@types/node",
-        "@npm//buffer",
-    ],
-)
-
-jasmine_node_test(
-    name = "hdlc_test",
-    srcs = [
-        ":hdlc_test_lib",
-    ],
-)
-
-# needed for embedding into downstream projects
-filegroup(name = "pw_hdlc__contents")
-
-filegroup(name = "pw_hdlc__files")
-
-filegroup(name = "pw_hdlc__nested_node_modules")
diff --git a/pw_hdlc/ts/decoder.ts b/pw_hdlc/ts/decoder.ts
deleted file mode 100644
index 881d2cb..0000000
--- a/pw_hdlc/ts/decoder.ts
+++ /dev/null
@@ -1,200 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-/** Decoder class for decoding bytes using HDLC protocol */
-
-import * as protocol from './protocol';
-import * as util from './util';
-
-const _MIN_FRAME_SIZE = 6; // 1 B address + 1 B control + 4 B CRC-32
-
-/** Indicates if an error occurred */
-export enum FrameStatus {
-  OK = 'OK',
-  FCS_MISMATCH = 'frame check sequence failure',
-  FRAMING_ERROR = 'invalid flag or escape characters',
-  BAD_ADDRESS = 'address field too long',
-}
-
-/**
- * A single HDLC frame
- */
-export class Frame {
-  rawEncoded: Uint8Array;
-  rawDecoded: Uint8Array;
-  status: FrameStatus;
-
-  address = -1;
-  control: Uint8Array = new Uint8Array();
-  data: Uint8Array = new Uint8Array();
-
-  constructor(
-    rawEncoded: Uint8Array,
-    rawDecoded: Uint8Array,
-    status: FrameStatus = FrameStatus.OK
-  ) {
-    this.rawEncoded = rawEncoded;
-    this.rawDecoded = rawDecoded;
-    this.status = status;
-
-    if (status === FrameStatus.OK) {
-      const [address, addressLength] = protocol.decodeAddress(rawDecoded);
-      if (addressLength === 0) {
-        this.status = FrameStatus.BAD_ADDRESS;
-        return;
-      }
-      this.address = address;
-      this.control = rawDecoded.slice(addressLength, addressLength + 1);
-      this.data = rawDecoded.slice(addressLength + 1, -4);
-    }
-  }
-
-  /**
-   * True if this represents a valid frame.
-   *
-   * If false, then parsing failed. The status is set to indicate what type of
-   * error occurred, and the data field contains all bytes parsed from the frame
-   * (including bytes parsed as address or control bytes).
-   */
-  ok(): boolean {
-    return this.status === FrameStatus.OK;
-  }
-}
-
-enum DecoderState {
-  INTERFRAME,
-  FRAME,
-  FRAME_ESCAPE,
-}
-
-/** Decodes one or more HDLC frames from a stream of data. */
-export class Decoder {
-  private decodedData = new Uint8Array();
-  private rawData = new Uint8Array();
-  private state = DecoderState.INTERFRAME;
-
-  /**
-   *  Decodes and yields HDLC frames, including corrupt frames
-   *
-   *  The ok() method on Frame indicates whether it is valid or represents a
-   *  frame parsing error.
-   *
-   *  @yield Frames, which may be valid (frame.ok)) okr corrupt (!frame.ok())
-   */
-  *process(data: Uint8Array): IterableIterator<Frame> {
-    for (const byte of data) {
-      const frame = this.processByte(byte);
-      if (frame != null) {
-        yield frame;
-      }
-    }
-  }
-
-  /**
-   *  Decodes and yields valid HDLC frames, logging any errors.
-   *
-   *  @yield Valid HDLC frames
-   */
-  *processValidFrames(data: Uint8Array): IterableIterator<Frame> {
-    const frames = this.process(data);
-    for (const frame of frames) {
-      if (frame.ok()) {
-        yield frame;
-      } else {
-        console.warn(
-          'Failed to decode frame: %s; discarded %d bytes',
-          frame.status,
-          frame.rawEncoded.length
-        );
-        console.debug('Discarded data: %s', frame.rawEncoded);
-      }
-    }
-  }
-
-  private checkFrame(data: Uint8Array): FrameStatus {
-    if (data.length < _MIN_FRAME_SIZE) {
-      return FrameStatus.FRAMING_ERROR;
-    }
-    const frameCrc = new DataView(data.slice(-4).buffer).getInt8(0);
-    const crc = new DataView(
-      protocol.frameCheckSequence(data.slice(0, -4)).buffer
-    ).getInt8(0);
-    if (crc !== frameCrc) {
-      return FrameStatus.FCS_MISMATCH;
-    }
-    return FrameStatus.OK;
-  }
-
-  private finishFrame(status: FrameStatus): Frame {
-    const frame = new Frame(
-      new Uint8Array(this.rawData),
-      new Uint8Array(this.decodedData),
-      status
-    );
-    this.rawData = new Uint8Array();
-    this.decodedData = new Uint8Array();
-    return frame;
-  }
-
-  private processByte(byte: number): Frame | undefined {
-    let frame;
-
-    // Record every byte except the flag character.
-    if (byte != protocol.FLAG) {
-      this.rawData = util.concatenate(this.rawData, Uint8Array.of(byte));
-    }
-
-    switch (this.state) {
-      case DecoderState.INTERFRAME:
-        if (byte === protocol.FLAG) {
-          if (this.rawData.length > 0) {
-            frame = this.finishFrame(FrameStatus.FRAMING_ERROR);
-          }
-          this.state = DecoderState.FRAME;
-        }
-        break;
-
-      case DecoderState.FRAME:
-        if (byte == protocol.FLAG) {
-          if (this.rawData.length > 0) {
-            frame = this.finishFrame(this.checkFrame(this.decodedData));
-          }
-        } else if (byte == protocol.ESCAPE) {
-          this.state = DecoderState.FRAME_ESCAPE;
-        } else {
-          this.decodedData = util.concatenate(
-            this.decodedData,
-            Uint8Array.of(byte)
-          );
-        }
-        break;
-
-      case DecoderState.FRAME_ESCAPE:
-        if (byte === protocol.FLAG) {
-          frame = this.finishFrame(FrameStatus.FRAMING_ERROR);
-          this.state = DecoderState.FRAME;
-        } else if (protocol.VALID_ESCAPED_BYTES.includes(byte)) {
-          this.state = DecoderState.FRAME;
-          this.decodedData = util.concatenate(
-            this.decodedData,
-            Uint8Array.of(protocol.escape(byte))
-          );
-        } else {
-          this.state = DecoderState.INTERFRAME;
-        }
-        break;
-    }
-    return frame;
-  }
-}
diff --git a/pw_hdlc/ts/decoder_test.ts b/pw_hdlc/ts/decoder_test.ts
deleted file mode 100644
index af12ca3..0000000
--- a/pw_hdlc/ts/decoder_test.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-/* eslint-env browser, jasmine */
-import 'jasmine';
-
-import {Decoder} from './decoder';
-import * as protocol from './protocol';
-import * as util from './util';
-
-const FLAG = Uint8Array.from([protocol.FLAG]);
-
-function withFCS(data: Uint8Array): Uint8Array {
-  return util.concatenate(data, protocol.frameCheckSequence(data));
-}
-
-function withFlags(data: Uint8Array): Uint8Array {
-  return util.concatenate(FLAG, data, FLAG);
-}
-
-describe('Decoder', () => {
-  let decoder: Decoder;
-  let textEncoder: TextEncoder;
-
-  beforeEach(() => {
-    decoder = new Decoder();
-    textEncoder = new TextEncoder();
-  });
-
-  it('parses a correct UI frame peoperly', () => {
-    const expectedData = textEncoder.encode('123456789');
-    const expectedAddress = 128;
-
-    const frameData = withFlags(
-      withFCS(textEncoder.encode('\x00\x03\x03123456789'))
-    );
-
-    const frames = Array.from(decoder.process(frameData));
-
-    expect(frames.length).toEqual(1);
-    expect(frames[0].address).toEqual(expectedAddress);
-    expect(frames[0].data).toEqual(expectedData);
-    expect(frames[0].ok()).toBe(true);
-  });
-});
diff --git a/pw_hdlc/ts/encoder.ts b/pw_hdlc/ts/encoder.ts
deleted file mode 100644
index 1423356..0000000
--- a/pw_hdlc/ts/encoder.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 * as protocol from './protocol';
-import * as util from './util';
-
-const FLAG_BYTE = Uint8Array.from([protocol.FLAG]);
-
-export class Encoder {
-  constructor() {}
-
-  /** Encodes an HDLC UI-frame with a CRC-32 frame check sequence. */
-  uiFrame(address: number, data: Uint8Array): Uint8Array {
-    const addr = protocol.encodeAddress(address);
-    const frameControl = protocol.UI_FRAME_CONTROL;
-    const start = util.concatenate(addr, frameControl, data);
-    let frame = util.concatenate(start, protocol.frameCheckSequence(start));
-
-    frame = util.replace(frame, 0x7d, [0x7d, 0x5d]);
-    frame = util.replace(frame, 0x7e, [0x7d, 0x5e]);
-    return util.concatenate(FLAG_BYTE, frame, FLAG_BYTE);
-  }
-}
diff --git a/pw_hdlc/ts/encoder_test.ts b/pw_hdlc/ts/encoder_test.ts
deleted file mode 100644
index cb8a9b4..0000000
--- a/pw_hdlc/ts/encoder_test.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-/* eslint-env browser, jasmine */
-import 'jasmine';
-
-import {Buffer} from 'buffer';
-
-import {Encoder} from './encoder';
-import * as protocol from './protocol';
-import * as util from './util';
-
-const FLAG = Uint8Array.from([protocol.FLAG]);
-
-function withFCS(data: Uint8Array): Uint8Array {
-  return util.concatenate(data, protocol.frameCheckSequence(data));
-}
-
-function withFlags(data: Uint8Array): Uint8Array {
-  return util.concatenate(FLAG, data, FLAG);
-}
-
-describe('Encoder', () => {
-  let encoder: Encoder;
-  let textEncoder: TextEncoder;
-
-  beforeEach(() => {
-    encoder = new Encoder();
-    textEncoder = new TextEncoder();
-  });
-
-  it('creates frame for empty data', () => {
-    const data = textEncoder.encode('');
-    expect(encoder.uiFrame(0, data)).toEqual(
-      withFlags(withFCS(new Uint8Array([0x01, 0x03])))
-    );
-    expect(encoder.uiFrame(0x1a, data)).toEqual(
-      withFlags(withFCS(new Uint8Array([0x35, 0x03])))
-    );
-    expect(encoder.uiFrame(0x1a, data)).toEqual(
-      withFlags(withFCS(textEncoder.encode('\x35\x03')))
-    );
-  });
-
-  it('creates frame for one byte', () => {
-    const data = textEncoder.encode('A');
-    expect(encoder.uiFrame(0, data)).toEqual(
-      withFlags(withFCS(textEncoder.encode('\x01\x03A')))
-    );
-  });
-
-  it('creates frame for multibyte data', () => {
-    const data = textEncoder.encode('123456789');
-    expect(encoder.uiFrame(0, data)).toEqual(
-      withFlags(withFCS(textEncoder.encode('\x01\x03123456789')))
-    );
-  });
-
-  it('creates frame for multibyte data with address', () => {
-    const data = textEncoder.encode('123456789');
-    expect(encoder.uiFrame(128, data)).toEqual(
-      withFlags(withFCS(textEncoder.encode('\x00\x03\x03123456789')))
-    );
-  });
-
-  it('creates frame for data with escape sequence', () => {
-    const data = textEncoder.encode('\x7d');
-    const expectedContent = util.concatenate(
-      textEncoder.encode('\x7d\x5d\x03\x7d\x5d'),
-      protocol.frameCheckSequence(textEncoder.encode('\x7d\x03\x7d'))
-    );
-    expect(encoder.uiFrame(0x3e, data)).toEqual(withFlags(expectedContent));
-
-    const data2 = textEncoder.encode('A\x7e\x7dBC');
-    const expectedContent2 = util.concatenate(
-      textEncoder.encode('\x7d\x5d\x03A\x7d\x5e\x7d\x5dBC'),
-      protocol.frameCheckSequence(textEncoder.encode('\x7d\x03A\x7e\x7dBC'))
-    );
-    expect(encoder.uiFrame(0x3e, data2)).toEqual(withFlags(expectedContent2));
-  });
-});
diff --git a/pw_hdlc/ts/index.ts b/pw_hdlc/ts/index.ts
deleted file mode 100644
index 2a629a4..0000000
--- a/pw_hdlc/ts/index.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-export {Decoder, Frame, FrameStatus} from './decoder';
-export {Encoder} from './encoder';
diff --git a/pw_hdlc/ts/package.json b/pw_hdlc/ts/package.json
deleted file mode 100644
index bd8660d..0000000
--- a/pw_hdlc/ts/package.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-  "name": "@pigweed/pw_hdlc",
-  "version": "1.0.0",
-  "main": "index.js",
-  "license": "Apache-2.0",
-  "dependencies": {
-    "@bazel/jasmine": "^4.1.0",
-    "@types/crc": "^3.4.0",
-    "@types/jasmine": "^3.9.0",
-    "buffer": "^6.0.3",
-    "crc": "^3.8.0",
-    "jasmine": "^3.9.0",
-    "jasmine-core": "^3.9.0"
-  }
-}
diff --git a/pw_hdlc/ts/protocol.ts b/pw_hdlc/ts/protocol.ts
deleted file mode 100644
index ef357a0..0000000
--- a/pw_hdlc/ts/protocol.ts
+++ /dev/null
@@ -1,103 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-/** Low-level HDLC protocol features. */
-import {Buffer} from 'buffer';
-import {crc32} from 'crc';
-
-/** Special flag character for delimiting HDLC frames. */
-export const FLAG = 0x7e;
-
-/** Special character for escaping other special characters in a frame. */
-export const ESCAPE = 0x7d;
-
-/** Characters allowed after a 0x7d escape character. */
-export const VALID_ESCAPED_BYTES = [0x5d, 0x5e];
-
-/** Frame control for unnumbered information */
-export const UI_FRAME_CONTROL = frameControl(0x00);
-
-/** Maximum allowed HDLC address (uint64_t in C++). */
-const MAX_ADDRESS = 2 ** 64 - 1;
-
-/**
- * Bitwise OR operation on numbers up to MAX_ADDRESS size.
- * Native bitwise operators only support signed Int32.
- */
-function bitwiseOr(x: number, y: number) {
-  const highMask = 0x80000000;
-  const lowMask = 0x7fffffff;
-  const highX = ~~(x / highMask);
-  const highY = ~~(y / highMask);
-  const lowX = x & lowMask;
-  const lowY = y & lowMask;
-  const highOr = highX | highY;
-  const lowOr = lowX | lowY;
-  return highOr * highMask + lowOr;
-}
-
-/** Calculates the CRC32 of |data| */
-export function frameCheckSequence(data: Uint8Array): Uint8Array {
-  const crc = crc32(Buffer.from(data.buffer, data.byteOffset, data.byteLength));
-  const arr = new ArrayBuffer(4);
-  const view = new DataView(arr);
-  view.setUint32(0, crc, true); // litteEndian = true
-  return new Uint8Array(arr);
-}
-
-/** Escapes or unescapes a byte, which should have been preceeded by 0x7d */
-export function escape(byte: number): number {
-  return byte ^ 0x20;
-}
-
-/** Encodes an HDLC address as a one-terminated LSB varint. */
-export function encodeAddress(address: number): Uint8Array {
-  const byteList = [];
-  while (true) {
-    byteList.push((address & 0x7f) << 1);
-    address >>= 7;
-    if (address === 0) {
-      break;
-    }
-  }
-
-  const result = Uint8Array.from(byteList);
-  result[result.length - 1] |= 0x1;
-  return result;
-}
-
-/** Decodes an HDLC address from a frame, returning it and its size. */
-export function decodeAddress(frame: Uint8Array): [number, number] {
-  let result = 0;
-  let length = 0;
-
-  while (length < frame.length) {
-    const byte = frame[length];
-    const shift = (byte >> 1) * 2 ** (length * 7);
-    result = bitwiseOr(result, shift);
-    length += 1;
-
-    if (shift > MAX_ADDRESS || result > MAX_ADDRESS) {
-      return [-1, 0];
-    }
-    if ((byte & 0x1) === 0x1) {
-      break;
-    }
-  }
-  return [result, length];
-}
-
-function frameControl(frameType: number): Uint8Array {
-  return Uint8Array.from([0x03 | frameType]);
-}
diff --git a/pw_hdlc/ts/tsconfig.json b/pw_hdlc/ts/tsconfig.json
deleted file mode 100644
index 4ddd637..0000000
--- a/pw_hdlc/ts/tsconfig.json
+++ /dev/null
@@ -1,34 +0,0 @@
-{
-  "compilerOptions": {
-    "allowUnreachableCode": false,
-    "allowUnusedLabels": false,
-    "declaration": true,
-    "forceConsistentCasingInFileNames": true,
-    "lib": [
-      "es2018",
-      "dom",
-      "dom.iterable",
-      "esnext"
-    ],
-    "module": "commonjs",
-    "noEmitOnError": true,
-    "noFallthroughCasesInSwitch": true,
-    "noImplicitReturns": true,
-    "pretty": true,
-    "sourceMap": true,
-    "strict": true,
-    "target": "es2018",
-    "jsx": "react",
-    "plugins": [
-      {
-        "name": "@bazel/tsetse",
-        "disabledRules": [
-          "must-use-promises"
-        ]
-      }
-    ]
-  },
-  "exclude": [
-    "node_modules"
-  ]
-}
diff --git a/pw_hdlc/ts/util.ts b/pw_hdlc/ts/util.ts
deleted file mode 100644
index 34cb6a0..0000000
--- a/pw_hdlc/ts/util.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-/** Convenience methods for working with Uint8Array buffers */
-
-/**
- * Returns a new array where all instances of target have been replaced by the
- * provided substitue.
- */
-export function replace(
-  data: Uint8Array,
-  target: number,
-  substitute: number[]
-): Uint8Array {
-  const result: number[] = [];
-  data.forEach(value => {
-    if (value === target) {
-      result.push(...substitute);
-    } else {
-      result.push(value);
-    }
-  });
-  return Uint8Array.from(result);
-}
-
-/** Flattens the provided list of Uint8Arrays into a single array. */
-export function concatenate(...byteList: Uint8Array[]): Uint8Array {
-  const length = byteList.reduce(
-    (accumulator, bytes) => accumulator + bytes.length,
-    0
-  );
-  const result = new Uint8Array(length);
-  let offset = 0;
-  byteList.forEach(value => {
-    result.set(value, offset);
-    offset += value.length;
-  });
-  return result;
-}
diff --git a/pw_hdlc/ts/yarn.lock b/pw_hdlc/ts/yarn.lock
deleted file mode 100644
index 6407502..0000000
--- a/pw_hdlc/ts/yarn.lock
+++ /dev/null
@@ -1,512 +0,0 @@
-# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
-# yarn lockfile v1
-
-
-"@bazel/jasmine@^4.1.0":
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/@bazel/jasmine/-/jasmine-4.1.0.tgz#ec9fc5179af265de47aba8bb40a094e9b062aab2"
-  integrity sha512-AUKzBZ12qKkcI5apXzL/2VKfsF4tHkdLPNsF/p6gEnIW4/aYb6M9wZOFsUh1MLYds+kqx1zN90EGfiZKa6wbOw==
-  dependencies:
-    c8 "~7.5.0"
-    jasmine-reporters "~2.4.0"
-
-"@bcoe/v8-coverage@^0.2.3":
-  version "0.2.3"
-  resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
-  integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
-
-"@istanbuljs/schema@^0.1.2":
-  version "0.1.3"
-  resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98"
-  integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==
-
-"@types/crc@^3.4.0":
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/@types/crc/-/crc-3.4.0.tgz#2366beb4399cd734b33e42c7ac809576e617d48a"
-  integrity sha1-I2a+tDmc1zSzPkLHrICVduYX1Io=
-  dependencies:
-    "@types/node" "*"
-
-"@types/is-windows@^1.0.0":
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/@types/is-windows/-/is-windows-1.0.0.tgz#1011fa129d87091e2f6faf9042d6704cdf2e7be0"
-  integrity sha512-tJ1rq04tGKuIJoWIH0Gyuwv4RQ3+tIu7wQrC0MV47raQ44kIzXSSFKfrxFUOWVRvesoF7mrTqigXmqoZJsXwTg==
-
-"@types/istanbul-lib-coverage@^2.0.1":
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
-  integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==
-
-"@types/jasmine@^3.9.0":
-  version "3.9.1"
-  resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.9.1.tgz#94c65ee8bf9d24d9e1d84abaed57b6e0da8b49de"
-  integrity sha512-PVpjh8S8lqKFKurWSKdFATlfBHGPzgy0PoDdzQ+rr78jTQ0aacyh9YndzZcAUPxhk4kRujItFFGQdUJ7flHumw==
-
-"@types/node@*":
-  version "16.9.1"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-16.9.1.tgz#0611b37db4246c937feef529ddcc018cf8e35708"
-  integrity sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==
-
-ansi-regex@^5.0.0:
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
-  integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
-
-ansi-styles@^4.0.0:
-  version "4.3.0"
-  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
-  integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
-  dependencies:
-    color-convert "^2.0.1"
-
-balanced-match@^1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
-  integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
-
-base64-js@^1.3.1:
-  version "1.5.1"
-  resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
-  integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
-
-brace-expansion@^1.1.7:
-  version "1.1.11"
-  resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
-  integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
-  dependencies:
-    balanced-match "^1.0.0"
-    concat-map "0.0.1"
-
-buffer@^5.1.0:
-  version "5.7.1"
-  resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
-  integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
-  dependencies:
-    base64-js "^1.3.1"
-    ieee754 "^1.1.13"
-
-buffer@^6.0.3:
-  version "6.0.3"
-  resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
-  integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
-  dependencies:
-    base64-js "^1.3.1"
-    ieee754 "^1.2.1"
-
-c8@~7.5.0:
-  version "7.5.0"
-  resolved "https://registry.yarnpkg.com/c8/-/c8-7.5.0.tgz#a69439ab82848f344a74bb25dc5dd4e867764481"
-  integrity sha512-GSkLsbvDr+FIwjNSJ8OwzWAyuznEYGTAd1pzb/Kr0FMLuV4vqYJTyjboDTwmlUNAG6jAU3PFWzqIdKrOt1D8tw==
-  dependencies:
-    "@bcoe/v8-coverage" "^0.2.3"
-    "@istanbuljs/schema" "^0.1.2"
-    find-up "^5.0.0"
-    foreground-child "^2.0.0"
-    furi "^2.0.0"
-    istanbul-lib-coverage "^3.0.0"
-    istanbul-lib-report "^3.0.0"
-    istanbul-reports "^3.0.2"
-    rimraf "^3.0.0"
-    test-exclude "^6.0.0"
-    v8-to-istanbul "^7.1.0"
-    yargs "^16.0.0"
-    yargs-parser "^20.0.0"
-
-cliui@^7.0.2:
-  version "7.0.4"
-  resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
-  integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
-  dependencies:
-    string-width "^4.2.0"
-    strip-ansi "^6.0.0"
-    wrap-ansi "^7.0.0"
-
-color-convert@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
-  integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
-  dependencies:
-    color-name "~1.1.4"
-
-color-name@~1.1.4:
-  version "1.1.4"
-  resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
-  integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
-
-concat-map@0.0.1:
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
-  integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
-
-convert-source-map@^1.6.0:
-  version "1.8.0"
-  resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369"
-  integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==
-  dependencies:
-    safe-buffer "~5.1.1"
-
-crc@^3.8.0:
-  version "3.8.0"
-  resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6"
-  integrity sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==
-  dependencies:
-    buffer "^5.1.0"
-
-cross-spawn@^7.0.0:
-  version "7.0.3"
-  resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
-  integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
-  dependencies:
-    path-key "^3.1.0"
-    shebang-command "^2.0.0"
-    which "^2.0.1"
-
-emoji-regex@^8.0.0:
-  version "8.0.0"
-  resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
-  integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
-
-escalade@^3.1.1:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
-  integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
-
-find-up@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
-  integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
-  dependencies:
-    locate-path "^6.0.0"
-    path-exists "^4.0.0"
-
-foreground-child@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-2.0.0.tgz#71b32800c9f15aa8f2f83f4a6bd9bff35d861a53"
-  integrity sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==
-  dependencies:
-    cross-spawn "^7.0.0"
-    signal-exit "^3.0.2"
-
-fs.realpath@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
-  integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
-
-furi@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/furi/-/furi-2.0.0.tgz#13d85826a1af21acc691da6254b3888fc39f0b4a"
-  integrity sha512-uKuNsaU0WVaK/vmvj23wW1bicOFfyqSsAIH71bRZx8kA4Xj+YCHin7CJKJJjkIsmxYaPFLk9ljmjEyB7xF7WvQ==
-  dependencies:
-    "@types/is-windows" "^1.0.0"
-    is-windows "^1.0.2"
-
-get-caller-file@^2.0.5:
-  version "2.0.5"
-  resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
-  integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
-
-glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
-  version "7.1.7"
-  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
-  integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==
-  dependencies:
-    fs.realpath "^1.0.0"
-    inflight "^1.0.4"
-    inherits "2"
-    minimatch "^3.0.4"
-    once "^1.3.0"
-    path-is-absolute "^1.0.0"
-
-has-flag@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
-  integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
-
-html-escaper@^2.0.0:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
-  integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
-
-ieee754@^1.1.13, ieee754@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
-  integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
-
-inflight@^1.0.4:
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
-  integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
-  dependencies:
-    once "^1.3.0"
-    wrappy "1"
-
-inherits@2:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
-  integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
-
-is-fullwidth-code-point@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
-  integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
-
-is-windows@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
-  integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
-
-isexe@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
-  integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
-
-istanbul-lib-coverage@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec"
-  integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==
-
-istanbul-lib-report@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6"
-  integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==
-  dependencies:
-    istanbul-lib-coverage "^3.0.0"
-    make-dir "^3.0.0"
-    supports-color "^7.1.0"
-
-istanbul-reports@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.2.tgz#d593210e5000683750cb09fc0644e4b6e27fd53b"
-  integrity sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==
-  dependencies:
-    html-escaper "^2.0.0"
-    istanbul-lib-report "^3.0.0"
-
-jasmine-core@^3.9.0, jasmine-core@~3.9.0:
-  version "3.9.0"
-  resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.9.0.tgz#09a3c8169fe98ec69440476d04a0e4cb4d59e452"
-  integrity sha512-Tv3kVbPCGVrjsnHBZ38NsPU3sDOtNa0XmbG2baiyJqdb5/SPpDO6GVwJYtUryl6KB4q1Ssckwg612ES9Z0dreQ==
-
-jasmine-reporters@~2.4.0:
-  version "2.4.0"
-  resolved "https://registry.yarnpkg.com/jasmine-reporters/-/jasmine-reporters-2.4.0.tgz#708c17ae70ba6671e3a930bb1b202aab80a31409"
-  integrity sha512-jxONSrBLN1vz/8zCx5YNWQSS8iyDAlXQ5yk1LuqITe4C6iXCDx5u6Q0jfNtkKhL4qLZPe69fL+AWvXFt9/x38w==
-  dependencies:
-    mkdirp "^0.5.1"
-    xmldom "^0.5.0"
-
-jasmine@^3.9.0:
-  version "3.9.0"
-  resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-3.9.0.tgz#286c4f9f88b69defc24acf3989af5533d5c6a0e6"
-  integrity sha512-JgtzteG7xnqZZ51fg7N2/wiQmXon09szkALcRMTgCMX4u/m17gVJFjObnvw5FXkZOWuweHPaPRVB6DI2uN0wVA==
-  dependencies:
-    glob "^7.1.6"
-    jasmine-core "~3.9.0"
-
-locate-path@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
-  integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
-  dependencies:
-    p-locate "^5.0.0"
-
-make-dir@^3.0.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
-  integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
-  dependencies:
-    semver "^6.0.0"
-
-minimatch@^3.0.4:
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
-  integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
-  dependencies:
-    brace-expansion "^1.1.7"
-
-minimist@^1.2.5:
-  version "1.2.5"
-  resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
-  integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
-
-mkdirp@^0.5.1:
-  version "0.5.5"
-  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
-  integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
-  dependencies:
-    minimist "^1.2.5"
-
-once@^1.3.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
-  integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
-  dependencies:
-    wrappy "1"
-
-p-limit@^3.0.2:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
-  integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
-  dependencies:
-    yocto-queue "^0.1.0"
-
-p-locate@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
-  integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
-  dependencies:
-    p-limit "^3.0.2"
-
-path-exists@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
-  integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
-
-path-is-absolute@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
-  integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
-
-path-key@^3.1.0:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
-  integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
-
-require-directory@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
-  integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
-
-rimraf@^3.0.0:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
-  integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
-  dependencies:
-    glob "^7.1.3"
-
-safe-buffer@~5.1.1:
-  version "5.1.2"
-  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
-  integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
-
-semver@^6.0.0:
-  version "6.3.0"
-  resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
-  integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
-
-shebang-command@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
-  integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
-  dependencies:
-    shebang-regex "^3.0.0"
-
-shebang-regex@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
-  integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
-
-signal-exit@^3.0.2:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
-  integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==
-
-source-map@^0.7.3:
-  version "0.7.3"
-  resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
-  integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
-
-string-width@^4.1.0, string-width@^4.2.0:
-  version "4.2.2"
-  resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5"
-  integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==
-  dependencies:
-    emoji-regex "^8.0.0"
-    is-fullwidth-code-point "^3.0.0"
-    strip-ansi "^6.0.0"
-
-strip-ansi@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532"
-  integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==
-  dependencies:
-    ansi-regex "^5.0.0"
-
-supports-color@^7.1.0:
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
-  integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
-  dependencies:
-    has-flag "^4.0.0"
-
-test-exclude@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e"
-  integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==
-  dependencies:
-    "@istanbuljs/schema" "^0.1.2"
-    glob "^7.1.4"
-    minimatch "^3.0.4"
-
-v8-to-istanbul@^7.1.0:
-  version "7.1.2"
-  resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz#30898d1a7fa0c84d225a2c1434fb958f290883c1"
-  integrity sha512-TxNb7YEUwkLXCQYeudi6lgQ/SZrzNO4kMdlqVxaZPUIUjCv6iSSypUQX70kNBSERpQ8fk48+d61FXk+tgqcWow==
-  dependencies:
-    "@types/istanbul-lib-coverage" "^2.0.1"
-    convert-source-map "^1.6.0"
-    source-map "^0.7.3"
-
-which@^2.0.1:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
-  integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
-  dependencies:
-    isexe "^2.0.0"
-
-wrap-ansi@^7.0.0:
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
-  integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
-  dependencies:
-    ansi-styles "^4.0.0"
-    string-width "^4.1.0"
-    strip-ansi "^6.0.0"
-
-wrappy@1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
-  integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
-
-xmldom@^0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.5.0.tgz#193cb96b84aa3486127ea6272c4596354cb4962e"
-  integrity sha512-Foaj5FXVzgn7xFzsKeNIde9g6aFBxTPi37iwsno8QvApmtg7KYrr+OPyRHcJF7dud2a5nGRBXK3n0dL62Gf7PA==
-
-y18n@^5.0.5:
-  version "5.0.8"
-  resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
-  integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
-
-yargs-parser@^20.0.0, yargs-parser@^20.2.2:
-  version "20.2.9"
-  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
-  integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
-
-yargs@^16.0.0:
-  version "16.2.0"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
-  integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
-  dependencies:
-    cliui "^7.0.2"
-    escalade "^3.1.1"
-    get-caller-file "^2.0.5"
-    require-directory "^2.1.1"
-    string-width "^4.2.0"
-    y18n "^5.0.5"
-    yargs-parser "^20.2.2"
-
-yocto-queue@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
-  integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
diff --git a/pw_hdlc/wire_packet_parser_test.cc b/pw_hdlc/wire_packet_parser_test.cc
index 54d8bb1..809afd2 100644
--- a/pw_hdlc/wire_packet_parser_test.cc
+++ b/pw_hdlc/wire_packet_parser_test.cc
@@ -135,7 +135,6 @@
   EXPECT_FALSE(parser.Parse(bytes::Concat(kFlag,
                                           kEncodedAddress,
                                           kControl,
-                                          // inclusive-language: ignore
                                           bytes::String("he~lo"),
                                           0xdbae98fe,
                                           kFlag)));
diff --git a/pw_hex_dump/BUILD b/pw_hex_dump/BUILD
new file mode 100644
index 0000000..c0e21c9
--- /dev/null
+++ b/pw_hex_dump/BUILD
@@ -0,0 +1,51 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_hex_dump",
+    deps = [
+        "//pw_bytes",
+        "//pw_span",
+        "//pw_status",
+    ],
+    srcs = [
+        "hex_dump.cc",
+    ],
+    hdrs = [
+        "public/pw_hex_dump/hex_dump.h",
+    ],
+    includes = ["public"],
+)
+
+
+pw_cc_test(
+    name = "hex_dump_test",
+    srcs = [
+        "hex_dump_test.cc",
+    ],
+    deps = [
+        ":pw_hex_dump",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_hex_dump/BUILD.bazel b/pw_hex_dump/BUILD.bazel
deleted file mode 100644
index ed7709f..0000000
--- a/pw_hex_dump/BUILD.bazel
+++ /dev/null
@@ -1,52 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_hex_dump",
-    srcs = [
-        "hex_dump.cc",
-    ],
-    hdrs = [
-        "public/pw_hex_dump/hex_dump.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_bytes",
-        "//pw_span",
-        "//pw_status",
-        "//pw_string",
-    ],
-)
-
-pw_cc_test(
-    name = "hex_dump_test",
-    srcs = [
-        "hex_dump_test.cc",
-    ],
-    deps = [
-        ":pw_hex_dump",
-        "//pw_log",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_hex_dump/OWNERS b/pw_hex_dump/OWNERS
deleted file mode 100644
index 307b1de..0000000
--- a/pw_hex_dump/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-amontanez@google.com
diff --git a/pw_hex_dump/hex_dump.cc b/pw_hex_dump/hex_dump.cc
index 1758b52..67f0f3c 100644
--- a/pw_hex_dump/hex_dump.cc
+++ b/pw_hex_dump/hex_dump.cc
@@ -44,26 +44,6 @@
   return std::to_integer<char>(b);
 }
 
-void AddGroupingByte(size_t byte_index,
-                     FormattedHexDumper::Flags& flags,
-                     StringBuilder& builder) {
-  // Never add grouping when it is disabled.
-  if (flags.group_every == 0) {
-    return;
-  }
-  // If this byte isn't at the end of a group, don't add a space.
-  if ((byte_index + 1) % flags.group_every != 0) {
-    return;
-  }
-  // If this byte is the last byte in a line, don't add a grouping byte
-  // (prevents trailing spaces).
-  if (byte_index + 1 == flags.bytes_per_line) {
-    return;
-  }
-
-  builder << ' ';
-}
-
 }  // namespace
 
 Status DumpAddr(std::span<char> dest, uintptr_t addr) {
@@ -110,11 +90,27 @@
     // bytes_per_line.
     if (flags.group_every != 0 &&
         i % static_cast<uint8_t>(flags.group_every) == 0) {
-      builder << std::byte(i);
+      uint8_t c = static_cast<uint8_t>(i);
+      if (c >> 4 == 0) {
+        builder << ' ';
+      } else {
+        builder << std::byte(c >> 4);
+      }
+      builder << std::byte(c & 0xF);
     } else {
       builder.append(2, ' ');
     }
-    AddGroupingByte(i, flags, builder);
+    if (flags.group_every != 0 && (i + 1) % flags.group_every == 0) {
+      builder << ' ';
+    }
+  }
+
+  // Removes extraneous space from end when bytes_per_line is divisible by
+  // group_every. kSectionSeparator includes a space, so it's unnecessary.
+  // Ommitting the space from the section separator actually makes for more
+  // workarounds and code duplication, so this is better.
+  if (flags.group_every != 0 && flags.bytes_per_line % flags.group_every == 0) {
+    builder.pop_back();
   }
 
   if (flags.show_ascii) {
@@ -172,8 +168,14 @@
   for (size_t i = 0; i < bytes_in_line; ++i) {
     // Early loop termination for when bytes_remaining <
     // bytes_per_line.
-    builder << source_data_[i];
-    AddGroupingByte(i, flags, builder);
+    uint8_t c = std::to_integer<uint8_t>(source_data_[i]);
+    // TODO(amontanez): Maybe StringBuilder can be augmented to support full-
+    // width bytes? (`04` instead of `4`, for example)
+    builder << std::byte(c >> 4);
+    builder << std::byte(c & 0xF);
+    if (flags.group_every != 0 && (i + 1) % flags.group_every == 0) {
+      builder << ' ';
+    }
   }
   // Add padding spaces to ensure lines are aligned.
   if (flags.show_ascii) {
@@ -181,10 +183,20 @@
          i < static_cast<size_t>(flags.bytes_per_line);
          ++i) {
       builder.append(2, ' ');
-      AddGroupingByte(i, flags, builder);
+      if (flags.group_every != 0 && (i + 1) % flags.group_every == 0) {
+        builder << ' ';
+      }
     }
   }
 
+  // Removes extraneous space from end when bytes_per_line is divisible by
+  // group_every. kSectionSeparator includes a space, so it's unnecessary.
+  // Ommitting the space from the section separator actually makes for more
+  // workarounds and code duplication, so this is better.
+  if (flags.group_every != 0 && flags.bytes_per_line % flags.group_every == 0) {
+    builder.pop_back();
+  }
+
   // Interpret bytes as characters.
   if (flags.show_ascii) {
     builder << kSectionSeparator;
diff --git a/pw_hex_dump/hex_dump_test.cc b/pw_hex_dump/hex_dump_test.cc
index c2b4126..f374431 100644
--- a/pw_hex_dump/hex_dump_test.cc
+++ b/pw_hex_dump/hex_dump_test.cc
@@ -137,7 +137,7 @@
 
 TEST_F(HexDump, FormattedHexDump_DefaultHeader) {
   constexpr const char* expected =
-      "00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f";
+      " 0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f";
 
   default_flags_.show_header = true;
   dumper_ = FormattedHexDumper(dest_, default_flags_);
@@ -223,27 +223,6 @@
   EXPECT_STREQ(expected2, dest_.data());
 }
 
-TEST_F(HexDump, FormattedHexDump_LastLineCheck) {
-  constexpr const char* expected1 = "a4cc32629b46381a 231a2a7abce240a0";
-  constexpr const char* expected2 = "ff33e52b9e9f6b3c be9b893c7e4a7a48";
-  constexpr const char* expected3 = "18";
-
-  default_flags_.bytes_per_line = 16;
-  default_flags_.group_every = 8;
-  dumper_ = FormattedHexDumper(dest_, default_flags_);
-
-  EXPECT_TRUE(dumper_.BeginDump(source_data).ok());
-  // Dump first line.
-  EXPECT_TRUE(dumper_.DumpLine().ok());
-  EXPECT_STREQ(expected1, dest_.data());
-  // Dump second line.
-  EXPECT_TRUE(dumper_.DumpLine().ok());
-  EXPECT_STREQ(expected2, dest_.data());
-  // Dump third line.
-  EXPECT_TRUE(dumper_.DumpLine().ok());
-  EXPECT_STREQ(expected3, dest_.data());
-}
-
 TEST_F(HexDump, FormattedHexDump_Ascii) {
   constexpr const char* expected1 = "6d 79 20 74 65 73 74 20  my test ";
   constexpr const char* expected2 = "73 74 72 69 6e 67 0a     string.";
@@ -262,7 +241,7 @@
 }
 
 TEST_F(HexDump, FormattedHexDump_AsciiHeader) {
-  constexpr const char* expected0 = "00       04        Text";
+  constexpr const char* expected0 = " 0        4        Text";
   constexpr const char* expected1 = "6d792074 65737420  my test ";
   constexpr const char* expected2 = "73747269 6e670a    string.";
 
@@ -285,7 +264,7 @@
 }
 
 TEST_F(HexDump, FormattedHexDump_AsciiHeaderGroupEvery) {
-  constexpr const char* expected0 = "00 01 02 03 04 05 06 07  Text";
+  constexpr const char* expected0 = " 0  1  2  3  4  5  6  7  Text";
   constexpr const char* expected1 = "6d 79 20 74 65 73 74 20  my test ";
   constexpr const char* expected2 = "73 74 72 69 6e 67 0a     string.";
 
@@ -333,10 +312,8 @@
   constexpr size_t kTestBytesPerLine = 16;
   std::array<char, kHexAddrStringSize + 1> expected1;
   std::array<char, kHexAddrStringSize + 1> expected2;
-  DumpAddr(expected1, source_data.data())
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  DumpAddr(expected2, source_data.data() + kTestBytesPerLine)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  DumpAddr(expected1, source_data.data());
+  DumpAddr(expected2, source_data.data() + kTestBytesPerLine);
 
   default_flags_.bytes_per_line = kTestBytesPerLine;
   default_flags_.prefix_mode = FormattedHexDumper::AddressMode::kAbsolute;
diff --git a/pw_hex_dump/public/pw_hex_dump/hex_dump.h b/pw_hex_dump/public/pw_hex_dump/hex_dump.h
index aaf6ef1..58cdea6 100644
--- a/pw_hex_dump/public/pw_hex_dump/hex_dump.h
+++ b/pw_hex_dump/public/pw_hex_dump/hex_dump.h
@@ -97,14 +97,10 @@
                  .prefix_mode = AddressMode::kOffset};
 
   FormattedHexDumper() = default;
-  FormattedHexDumper(std::span<char> dest) {
-    SetLineBuffer(dest)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  }
+  FormattedHexDumper(std::span<char> dest) { SetLineBuffer(dest); }
   FormattedHexDumper(std::span<char> dest, Flags config_flags)
       : flags(config_flags) {
-    SetLineBuffer(dest)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    SetLineBuffer(dest);
   }
 
   // TODO(pwbug/218): Add iterator support.
diff --git a/pw_i2c/BUILD b/pw_i2c/BUILD
new file mode 100644
index 0000000..2343bdc
--- /dev/null
+++ b/pw_i2c/BUILD
@@ -0,0 +1,61 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "address",
+    hdrs = [
+        "public/pw_i2c/address.h",
+    ],
+    includes = ["public"],
+    srcs = [
+      "address.cc",
+    ],
+    deps = [
+        "//pw_assert",
+    ],
+)
+
+pw_cc_library(
+    name = "initiator",
+    hdrs = [
+        "public/pw_i2c/initiator.h",
+    ],
+    includes = ["public"],
+    deps = [
+        "//pw_bytes",
+        "//pw_chrono:system_clock",
+        "//pw_status",
+    ],
+)
+
+pw_cc_test(
+    name = "address_test",
+    srcs = [
+        "address_test.cc",
+    ],
+    deps = [
+        ":address",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_i2c/BUILD.bazel b/pw_i2c/BUILD.bazel
deleted file mode 100644
index a70cf9f..0000000
--- a/pw_i2c/BUILD.bazel
+++ /dev/null
@@ -1,155 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "address",
-    srcs = [
-        "address.cc",
-    ],
-    hdrs = [
-        "public/pw_i2c/address.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_assert",
-    ],
-)
-
-pw_cc_library(
-    name = "initiator",
-    hdrs = [
-        "public/pw_i2c/initiator.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_bytes",
-        "//pw_chrono:system_clock",
-        "//pw_status",
-    ],
-)
-
-pw_cc_library(
-    name = "device",
-    hdrs = [
-        "public/pw_i2c/device.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":address",
-        ":initiator",
-        "//pw_bytes",
-        "//pw_chrono:system_clock",
-        "//pw_status",
-    ],
-)
-
-pw_cc_library(
-    name = "register_device",
-    srcs = ["register_device.cc"],
-    hdrs = [
-        "public/pw_i2c/register_device.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":address",
-        ":device",
-        ":initiator",
-        "//pw_bytes",
-        "//pw_chrono:system_clock",
-        "//pw_result",
-        "//pw_status",
-    ],
-)
-
-pw_cc_test(
-    name = "address_test",
-    srcs = [
-        "address_test.cc",
-    ],
-    deps = [
-        ":address",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_library(
-    name = "initiator_mock",
-    testonly = True,
-    srcs = ["initiator_mock.cc"],
-    hdrs = ["public/pw_i2c/initiator_mock.h"],
-    includes = ["public"],
-    deps = [
-        ":address",
-        ":initiator",
-        "//pw_assert",
-        "//pw_containers:to_array",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_library(
-    name = "initiator_gmock",
-    hdrs = [
-        "public/pw_i2c/initiator_gmock.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":initiator",
-        "@com_google_googletest//:gtest",
-    ],
-)
-
-pw_cc_test(
-    name = "initiator_mock_test",
-    srcs = [
-        "initiator_mock_test.cc",
-    ],
-    deps = [
-        ":initiator_mock",
-        "//pw_bytes",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "device_test",
-    srcs = [
-        "device_test.cc",
-    ],
-    deps = [
-        ":device",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "register_device_test",
-    srcs = [
-        "register_device_test.cc",
-    ],
-    deps = [
-        ":register_device",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_i2c/BUILD.gn b/pw_i2c/BUILD.gn
index e049cde..b939587 100644
--- a/pw_i2c/BUILD.gn
+++ b/pw_i2c/BUILD.gn
@@ -15,7 +15,6 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_build/target_types.gni")
-import("$dir_pw_chrono/backend.gni")
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_unit_test/test.gni")
 
@@ -41,66 +40,8 @@
   ]
 }
 
-pw_source_set("device") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_i2c/device.h" ]
-  public_deps = [
-    ":address",
-    ":initiator",
-    "$dir_pw_bytes",
-    "$dir_pw_chrono:system_clock",
-    "$dir_pw_status",
-  ]
-}
-
-pw_source_set("register_device") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_i2c/register_device.h" ]
-  public_deps = [
-    ":address",
-    ":device",
-    ":initiator",
-    "$dir_pw_bytes",
-    "$dir_pw_chrono:system_clock",
-    "$dir_pw_result",
-    "$dir_pw_status",
-  ]
-  sources = [ "register_device.cc" ]
-  deps = [ "$dir_pw_assert" ]
-}
-
-pw_source_set("mock") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_i2c/initiator_mock.h" ]
-  sources = [ "initiator_mock.cc" ]
-  public_deps = [
-    ":initiator",
-    "$dir_pw_bytes",
-    "$dir_pw_containers:to_array",
-  ]
-  deps = [
-    "$dir_pw_assert",
-    "$dir_pw_unit_test",
-  ]
-}
-
-pw_source_set("gmock") {
-  public_configs = [ ":public_include_path" ]
-  public_deps = [
-    ":initiator",
-    "$dir_pw_third_party/googletest",
-  ]
-  public = [ "public/pw_i2c/initiator_gmock.h" ]
-}
-
-# TODO: add mock_test here once chrono backend is supported for stm32f429i-disc1
 pw_test_group("tests") {
-  tests = [
-    ":address_test",
-    ":device_test",
-    ":initiator_mock_test",
-    ":register_device_test",
-  ]
+  tests = [ ":address_test" ]
 }
 
 pw_test("address_test") {
@@ -108,27 +49,6 @@
   deps = [ ":address" ]
 }
 
-pw_test("device_test") {
-  enable_if = pw_chrono_SYSTEM_CLOCK_BACKEND != ""
-  sources = [ "device_test.cc" ]
-  deps = [ ":device" ]
-}
-
-pw_test("register_device_test") {
-  enable_if = pw_chrono_SYSTEM_CLOCK_BACKEND != ""
-  sources = [ "register_device_test.cc" ]
-  deps = [
-    ":register_device",
-    "$dir_pw_assert",
-  ]
-}
-
-pw_test("initiator_mock_test") {
-  enable_if = pw_chrono_SYSTEM_CLOCK_BACKEND != ""
-  sources = [ "initiator_mock_test.cc" ]
-  deps = [ ":mock" ]
-}
-
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
diff --git a/pw_i2c/OWNERS b/pw_i2c/OWNERS
deleted file mode 100644
index 21d24bc..0000000
--- a/pw_i2c/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-ewout@google.com
diff --git a/pw_i2c/address.cc b/pw_i2c/address.cc
index 74584b4..5cca79a 100644
--- a/pw_i2c/address.cc
+++ b/pw_i2c/address.cc
@@ -14,7 +14,7 @@
 
 #include "pw_i2c/address.h"
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 
 namespace pw::i2c {
 
diff --git a/pw_i2c/device_test.cc b/pw_i2c/device_test.cc
deleted file mode 100644
index dea5b1e..0000000
--- a/pw_i2c/device_test.cc
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_i2c/device.h"
-
-#include "gtest/gtest.h"
-#include "pw_bytes/byte_builder.h"
-
-namespace pw {
-namespace i2c {
-namespace {
-
-// Fake test initiator that's used for testing.
-class TestInitiator : public Initiator {
- public:
-  explicit TestInitiator() {}
-
- private:
-  Status DoWriteReadFor(Address,
-                        ConstByteSpan,
-                        ByteSpan,
-                        chrono::SystemClock::duration) override {
-    // Empty implementation.
-    return OkStatus();
-  }
-
-  ByteBuffer<10> write_buffer_;
-  ByteBuffer<10> read_buffer_;
-};
-
-// This test just checks to make sure the Device object compiles.
-// TODO(b/185609270): Full test coverage.
-TEST(DeviceCompilationTest, CompileOk) {
-  constexpr Address kTestDeviceAddress = Address::SevenBit<0x3F>();
-
-  TestInitiator initiator;
-  Device device = Device(initiator, kTestDeviceAddress);
-}
-
-}  // namespace
-}  // namespace i2c
-}  // namespace pw
diff --git a/pw_i2c/docs.rst b/pw_i2c/docs.rst
index 168a784..a82fb23 100644
--- a/pw_i2c/docs.rst
+++ b/pw_i2c/docs.rst
@@ -15,70 +15,6 @@
 
 pw::i2c::Initiator
 ------------------
-.. inclusive-language: disable
-
 The common interface for initiating transactions with devices on an I2C bus.
 Other documentation sources may call this style of interface an I2C "master",
 "central" or "controller".
-
-.. inclusive-language: enable
-
-pw::i2c::Device
----------------
-The common interface for interfacing with generic I2C devices. This object
-contains ``pw::i2c::Address`` and wraps the ``pw::i2c::Initiator`` API.
-Common use case includes streaming arbitrary data (Read/Write). Only works
-with devices with a single device address.
-
-pw::i2c::RegisterDevice
------------------------
-The common interface for interfacing with register devices. Contains methods
-to help read and write registers from and to the device. Users should have a
-understanding of the capabilities of their device such as register address
-sizes, register data sizes, byte addressability, bulk transactions, etc in
-order to effectively use this interface.
-
-pw::i2c::MockInitiator
-----------------------
-A generic mocked backend for for pw::i2c::Initiator. This is specifically
-intended for use when developing drivers for i2c devices. This is structured
-around a set of 'transactions' where each transaction contains a write, read and
-a timeout. A transaction list can then be passed to the MockInitiator, where
-each consecutive call to read/write will iterate to the next transaction in the
-list. An example of this is shown below:
-
-.. code-block:: cpp
-
-  using pw::i2c::Address;
-  using pw::i2c::MakeExpectedTransactionlist;
-  using pw::i2c::MockInitiator;
-  using pw::i2c::WriteTransaction;
-  using std::literals::chrono_literals::ms;
-
-  constexpr Address kAddress1 = Address::SevenBit<0x01>();
-  constexpr auto kExpectWrite1 = pw::bytes::Array<1, 2, 3, 4, 5>();
-  constexpr auto kExpectWrite2 = pw::bytes::Array<3, 4, 5>();
-  auto expected_transactions = MakeExpectedTransactionArray(
-      {WriteTransaction(pw::OkStatus(), kAddress1, kExpectWrite1, 1ms),
-       WriteTransaction(pw::OkStatus(), kAddress2, kExpectWrite2, 1ms)});
-  MockInitiator i2c_mock(expected_transactions);
-
-  // Begin driver code
-  ConstByteSpan write1 = kExpectWrite1;
-  // write1 is ok as i2c_mock expects {1, 2, 3, 4, 5} == {1, 2, 3, 4, 5}
-  Status status = i2c_mock.WriteFor(kAddress1, write1, 2ms);
-
-  // Takes the first two bytes from the expected array to build a mismatching
-  // span to write.
-  ConstByteSpan write2 = std::span(kExpectWrite2).first(2);
-  // write2 fails as i2c_mock expects {3, 4, 5} != {3, 4}
-  status = i2c_mock.WriteFor(kAddress2, write2, 2ms);
-  // End driver code
-
-  // Optionally check if the mocked transaction list has been exhausted.
-  // Alternatively this is also called from MockInitiator::~MockInitiator().
-  EXPECT_EQ(mocked_i2c.Finalize(), OkStatus());
-
-pw::i2c::GmockInitiator
------------------------
-gMock of Initiator used for testing and mocking out the Initiator.
diff --git a/pw_i2c/initiator_mock.cc b/pw_i2c/initiator_mock.cc
deleted file mode 100644
index 1056d29..0000000
--- a/pw_i2c/initiator_mock.cc
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_i2c/initiator_mock.h"
-
-#include "gtest/gtest.h"
-#include "pw_assert/check.h"
-
-namespace pw::i2c {
-
-Status MockInitiator::DoWriteReadFor(Address device_address,
-                                     ConstByteSpan tx_buffer,
-                                     ByteSpan rx_buffer,
-                                     chrono::SystemClock::duration timeout) {
-  PW_CHECK_INT_LT(expected_transaction_index_, expected_transactions_.size());
-
-  EXPECT_EQ(
-      expected_transactions_[expected_transaction_index_].address().GetTenBit(),
-      device_address.GetTenBit());
-
-  auto expected_timeout =
-      expected_transactions_[expected_transaction_index_].timeout();
-  if (expected_timeout.has_value()) {
-    EXPECT_EQ(expected_timeout.value(), timeout);
-  }
-
-  ConstByteSpan expected_tx_buffer =
-      expected_transactions_[expected_transaction_index_].write_buffer();
-  EXPECT_TRUE(std::equal(expected_tx_buffer.begin(),
-                         expected_tx_buffer.end(),
-                         tx_buffer.begin(),
-                         tx_buffer.end()));
-
-  ConstByteSpan expected_rx_buffer =
-      expected_transactions_[expected_transaction_index_].read_buffer();
-  PW_CHECK_INT_EQ(expected_rx_buffer.size(), rx_buffer.size());
-
-  std::copy(
-      expected_rx_buffer.begin(), expected_rx_buffer.end(), rx_buffer.begin());
-
-  // Do not directly return this value as expected_transaction_index_ should be
-  // incremented.
-  const Status expected_return_value =
-      expected_transactions_[expected_transaction_index_].return_value();
-
-  expected_transaction_index_ += 1;
-
-  return expected_return_value;
-}
-
-MockInitiator::~MockInitiator() { EXPECT_EQ(Finalize(), OkStatus()); }
-
-}  // namespace pw::i2c
diff --git a/pw_i2c/initiator_mock_test.cc b/pw_i2c/initiator_mock_test.cc
deleted file mode 100644
index beed22f..0000000
--- a/pw_i2c/initiator_mock_test.cc
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_i2c/initiator_mock.h"
-
-#include <array>
-#include <chrono>
-#include <span>
-
-#include "gtest/gtest.h"
-#include "pw_bytes/array.h"
-#include "pw_bytes/span.h"
-#include "pw_i2c/address.h"
-
-using namespace std::literals::chrono_literals;
-
-namespace pw::i2c {
-namespace {
-
-TEST(Transaction, Read) {
-  static constexpr Address kAddress1 = Address::SevenBit<0x01>();
-  constexpr auto kExpectRead1 = bytes::Array<1, 2, 3, 4, 5>();
-
-  static constexpr Address kAddress2 = Address::SevenBit<0x02>();
-  constexpr auto kExpectRead2 = bytes::Array<3, 4, 5>();
-
-  auto expected_transactions = MakeExpectedTransactionArray(
-      {ReadTransaction(OkStatus(), kAddress1, kExpectRead1, 2ms),
-       ReadTransaction(OkStatus(), kAddress2, kExpectRead2, 2ms)});
-
-  MockInitiator mocked_i2c(expected_transactions);
-
-  std::array<std::byte, kExpectRead1.size()> read1;
-  EXPECT_EQ(mocked_i2c.ReadFor(kAddress1, read1, 2ms), OkStatus());
-  EXPECT_TRUE(std::equal(
-      read1.begin(), read1.end(), kExpectRead1.begin(), kExpectRead1.end()));
-
-  std::array<std::byte, kExpectRead2.size()> read2;
-  EXPECT_EQ(mocked_i2c.ReadFor(kAddress2, read2, 2ms), OkStatus());
-  EXPECT_TRUE(std::equal(
-      read2.begin(), read2.end(), kExpectRead2.begin(), kExpectRead2.end()));
-
-  EXPECT_EQ(mocked_i2c.Finalize(), OkStatus());
-}
-
-TEST(Transaction, Write) {
-  static constexpr Address kAddress1 = Address::SevenBit<0x01>();
-  constexpr auto kExpectWrite1 = bytes::Array<1, 2, 3, 4, 5>();
-
-  static constexpr Address kAddress2 = Address::SevenBit<0x02>();
-  constexpr auto kExpectWrite2 = bytes::Array<3, 4, 5>();
-
-  auto expected_transactions = MakeExpectedTransactionArray(
-      {WriteTransaction(OkStatus(), kAddress1, kExpectWrite1, 2ms),
-       WriteTransaction(OkStatus(), kAddress2, kExpectWrite2, 2ms)});
-
-  MockInitiator mocked_i2c(expected_transactions);
-
-  EXPECT_EQ(mocked_i2c.WriteFor(kAddress1, kExpectWrite1, 2ms), OkStatus());
-
-  EXPECT_EQ(mocked_i2c.WriteFor(kAddress2, kExpectWrite2, 2ms), OkStatus());
-
-  EXPECT_EQ(mocked_i2c.Finalize(), OkStatus());
-}
-
-TEST(Transaction, WriteRead) {
-  static constexpr Address kAddress1 = Address::SevenBit<0x01>();
-  constexpr auto kExpectWrite1 = bytes::Array<1, 2, 3, 4, 5>();
-  constexpr auto kExpectRead1 = bytes::Array<1, 2>();
-
-  static constexpr Address kAddress2 = Address::SevenBit<0x02>();
-  constexpr auto kExpectWrite2 = bytes::Array<3, 4, 5>();
-  constexpr const auto kExpectRead2 = bytes::Array<3, 4>();
-
-  auto expected_transactions = MakeExpectedTransactionArray({
-      Transaction(OkStatus(), kAddress1, kExpectWrite1, kExpectRead1, 2ms),
-      Transaction(OkStatus(), kAddress2, kExpectWrite2, kExpectRead2, 2ms),
-  });
-
-  MockInitiator mocked_i2c(expected_transactions);
-
-  std::array<std::byte, kExpectRead1.size()> read1;
-  EXPECT_EQ(mocked_i2c.WriteReadFor(kAddress1, kExpectWrite1, read1, 2ms),
-            OkStatus());
-  EXPECT_TRUE(std::equal(read1.begin(), read1.end(), kExpectRead1.begin()));
-
-  std::array<std::byte, kExpectRead1.size()> read2;
-  EXPECT_EQ(mocked_i2c.WriteReadFor(kAddress2, kExpectWrite2, read2, 2ms),
-            OkStatus());
-  EXPECT_TRUE(std::equal(
-      read2.begin(), read2.end(), kExpectRead2.begin(), kExpectRead2.end()));
-
-  EXPECT_EQ(mocked_i2c.Finalize(), OkStatus());
-}
-
-}  // namespace
-}  // namespace pw::i2c
\ No newline at end of file
diff --git a/pw_i2c/public/pw_i2c/device.h b/pw_i2c/public/pw_i2c/device.h
deleted file mode 100644
index 41129e3..0000000
--- a/pw_i2c/public/pw_i2c/device.h
+++ /dev/null
@@ -1,174 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_bytes/span.h"
-#include "pw_chrono/system_clock.h"
-#include "pw_i2c/address.h"
-#include "pw_i2c/initiator.h"
-#include "pw_status/status.h"
-
-namespace pw {
-namespace i2c {
-
-// Device is used to write/read arbitrary chunks of data over a bus to a device.
-// This object essentially just wrap the Initiator API with a fixed I2C device
-// address.
-class Device {
- public:
-  constexpr Device(Initiator& initiator, Address device_address)
-      : initiator_(initiator), device_address_(device_address) {}
-
-  Device(const Device&) = delete;
-  ~Device() = default;
-
-  // Write bytes and then read bytes as either one atomic or two independent I2C
-  // transaction.
-  // The signal on the bus should appear as follows:
-  // 1) Write Only:
-  //   START + I2C Address + WRITE(0) + TX_BUFFER_BYTES + STOP
-  // 2) Read Only:
-  //   START + I2C Address + READ(1) + RX_BUFFER_BYTES + STOP
-  // 3A) Write + Read (atomic):
-  //   START + I2C Address + WRITE(0) + TX_BUFFER_BYTES +
-  //   START + I2C Address + READ(1) + RX_BUFFER_BYTES + STOP
-  // 3B) Write + Read (separate):
-  //   START + I2C Address + WRITE(0) + TX_BUFFER_BYTES + STOP
-  //   START + I2C Address + READ(1) + RX_BUFFER_BYTES + STOP
-  //
-  // The timeout defines the minimum duration one may block waiting for both
-  // exclusive bus access and the completion of the I2C transaction.
-  //
-  // Preconditions:
-  // The Address must be supported by the Initiator, i.e. do not use a 10
-  //     address if the Initiator only supports 7 bit. This will assert.
-  //
-  // Returns:
-  // Ok - Success.
-  // InvalidArgument - device_address is larger than the 10 bit address space.
-  // DeadlineExceeded - Was unable to acquire exclusive Initiator access
-  //   and complete the I2C transaction in time.
-  // Unavailable - NACK condition occurred, meaning the addressed device did
-  //   not respond or was unable to process the request.
-  // FailedPrecondition - The interface is not currently initialized and/or
-  //    enabled.
-  Status WriteReadFor(ConstByteSpan tx_buffer,
-                      ByteSpan rx_buffer,
-                      chrono::SystemClock::duration timeout) {
-    return initiator_.WriteReadFor(
-        device_address_, tx_buffer, rx_buffer, timeout);
-  }
-  Status WriteReadFor(const void* tx_buffer,
-                      size_t tx_size_bytes,
-                      void* rx_buffer,
-                      size_t rx_size_bytes,
-                      chrono::SystemClock::duration timeout) {
-    return initiator_.WriteReadFor(device_address_,
-                                   tx_buffer,
-                                   tx_size_bytes,
-                                   rx_buffer,
-                                   rx_size_bytes,
-                                   timeout);
-  }
-
-  // Write bytes. The signal on the bus should appear as follows:
-  //   START + I2C Address + WRITE(0) + TX_BUFFER_BYTES + STOP
-  //
-  // The timeout defines the minimum duration one may block waiting for both
-  // exclusive bus access and the completion of the I2C transaction.
-  //
-  // Preconditions:
-  // The Address must be supported by the Initiator, i.e. do not use a 10
-  //     address if the Initiator only supports 7 bit. This will assert.
-  //
-  // Returns:
-  // Ok - Success.
-  // InvalidArgument - device_address is larger than the 10 bit address space.
-  // DeadlineExceeded - Was unable to acquire exclusive Initiator access
-  //   and complete the I2C transaction in time.
-  // Unavailable - NACK condition occurred, meaning the addressed device did
-  //   not respond or was unable to process the request.
-  // FailedPrecondition - The interface is not currently initialized and/or
-  //    enabled.
-  Status WriteFor(ConstByteSpan tx_buffer,
-                  chrono::SystemClock::duration timeout) {
-    return initiator_.WriteFor(device_address_, tx_buffer, timeout);
-  }
-  Status WriteFor(const void* tx_buffer,
-                  size_t tx_size_bytes,
-                  chrono::SystemClock::duration timeout) {
-    return initiator_.WriteFor(
-        device_address_, tx_buffer, tx_size_bytes, timeout);
-  }
-
-  // Read bytes. The signal on the bus should appear as follows:
-  //   START + I2C Address + READ(1) + RX_BUFFER_BYTES + STOP
-  //
-  // The timeout defines the minimum duration one may block waiting for both
-  // exclusive bus access and the completion of the I2C transaction.
-  //
-  // Preconditions:
-  // The Address must be supported by the Initiator, i.e. do not use a 10
-  //     address if the Initiator only supports 7 bit. This will assert.
-  //
-  // Returns:
-  // Ok - Success.
-  // InvalidArgument - device_address is larger than the 10 bit address space.
-  // DeadlineExceeded - Was unable to acquire exclusive Initiator access
-  //   and complete the I2C transaction in time.
-  // Unavailable - NACK condition occurred, meaning the addressed device did
-  //   not respond or was unable to process the request.
-  // FailedPrecondition - The interface is not currently initialized and/or
-  //    enabled.
-  Status ReadFor(ByteSpan rx_buffer, chrono::SystemClock::duration timeout) {
-    return initiator_.ReadFor(device_address_, rx_buffer, timeout);
-  }
-  Status ReadFor(void* rx_buffer,
-                 size_t rx_size_bytes,
-                 chrono::SystemClock::duration timeout) {
-    return initiator_.ReadFor(
-        device_address_, rx_buffer, rx_size_bytes, timeout);
-  }
-
-  // Probes the device for an I2C ACK after only writing the address.
-  // This is done by attempting to read a single byte from the specified device.
-  //
-  // The timeout defines the minimum duration one may block waiting for both
-  // exclusive bus access and the completion of the I2C transaction.
-  //
-  // Preconditions:
-  // The Address must be supported by the Initiator, i.e. do not use a 10
-  //     address if the Initiator only supports 7 bit. This will assert.
-  //
-  // Returns:
-  // Ok - Success.
-  // InvalidArgument - device_address is larger than the 10 bit address space.
-  // DeadlineExceeded - Was unable to acquire exclusive Initiator access
-  //   and complete the I2C transaction in time.
-  // Unavailable - NACK condition occurred, meaning the addressed device did
-  //   not respond or was unable to process the request.
-  // FailedPrecondition - The interface is not currently initialized and/or
-  //    enabled.
-  Status ProbeFor(chrono::SystemClock::duration timeout) {
-    return initiator_.ProbeDeviceFor(device_address_, timeout);
-  }
-
- private:
-  Initiator& initiator_;
-  const Address device_address_;
-};
-
-}  // namespace i2c
-}  // namespace pw
diff --git a/pw_i2c/public/pw_i2c/initiator.h b/pw_i2c/public/pw_i2c/initiator.h
index 15d56fc..bbcf9a5 100644
--- a/pw_i2c/public/pw_i2c/initiator.h
+++ b/pw_i2c/public/pw_i2c/initiator.h
@@ -24,7 +24,7 @@
 
 // Base driver interface for I2C initiating I2C transactions in a thread safe
 // manner. Other documentation sources may call this style of interface an I2C
-// "master", "central" or "controller".  // inclusive-language: ignore
+// "master", "central" or "controller".
 //
 // The Initiator is not required to support 10bit addressing. If only 7bit
 // addressing is supported, the Initiator will assert when given an address
@@ -72,20 +72,20 @@
   Status WriteReadFor(Address device_address,
                       ConstByteSpan tx_buffer,
                       ByteSpan rx_buffer,
-                      chrono::SystemClock::duration timeout) {
-    return DoWriteReadFor(device_address, tx_buffer, rx_buffer, timeout);
+                      chrono::SystemClock::duration for_at_least) {
+    return DoWriteReadFor(device_address, tx_buffer, rx_buffer, for_at_least);
   }
   Status WriteReadFor(Address device_address,
                       const void* tx_buffer,
                       size_t tx_size_bytes,
                       void* rx_buffer,
                       size_t rx_size_bytes,
-                      chrono::SystemClock::duration timeout) {
+                      chrono::SystemClock::duration for_at_least) {
     return WriteReadFor(
         device_address,
         std::span(static_cast<const std::byte*>(tx_buffer), tx_size_bytes),
         std::span(static_cast<std::byte*>(rx_buffer), rx_size_bytes),
-        timeout);
+        for_at_least);
   }
 
   // Write bytes. The signal on the bus should appear as follows:
@@ -109,17 +109,17 @@
   //    enabled.
   Status WriteFor(Address device_address,
                   ConstByteSpan tx_buffer,
-                  chrono::SystemClock::duration timeout) {
-    return WriteReadFor(device_address, tx_buffer, ByteSpan(), timeout);
+                  chrono::SystemClock::duration for_at_least) {
+    return WriteReadFor(device_address, tx_buffer, ByteSpan(), for_at_least);
   }
   Status WriteFor(Address device_address,
                   const void* tx_buffer,
                   size_t tx_size_bytes,
-                  chrono::SystemClock::duration timeout) {
+                  chrono::SystemClock::duration for_at_least) {
     return WriteFor(
         device_address,
         std::span(static_cast<const std::byte*>(tx_buffer), tx_size_bytes),
-        timeout);
+        for_at_least);
   }
 
   // Read bytes. The signal on the bus should appear as follows:
@@ -143,16 +143,17 @@
   //    enabled.
   Status ReadFor(Address device_address,
                  ByteSpan rx_buffer,
-                 chrono::SystemClock::duration timeout) {
-    return WriteReadFor(device_address, ConstByteSpan(), rx_buffer, timeout);
+                 chrono::SystemClock::duration for_at_least) {
+    return WriteReadFor(
+        device_address, ConstByteSpan(), rx_buffer, for_at_least);
   }
   Status ReadFor(Address device_address,
                  void* rx_buffer,
                  size_t rx_size_bytes,
-                 chrono::SystemClock::duration timeout) {
+                 chrono::SystemClock::duration for_at_least) {
     return ReadFor(device_address,
                    std::span(static_cast<std::byte*>(rx_buffer), rx_size_bytes),
-                   timeout);
+                   for_at_least);
   }
 
   // Probes the device for an I2C ACK after only writing the address.
@@ -175,17 +176,17 @@
   // FailedPrecondition - The interface is not currently initialized and/or
   //    enabled.
   Status ProbeDeviceFor(Address device_address,
-                        chrono::SystemClock::duration timeout) {
-    std::byte ignored_buffer[1] = {};  // Read a byte to probe.
+                        chrono::SystemClock::duration for_at_least) {
+    std::byte ignored_buffer[1] = {};  // Read a dummy byte to probe.
     return WriteReadFor(
-        device_address, ConstByteSpan(), ignored_buffer, timeout);
+        device_address, ConstByteSpan(), ignored_buffer, for_at_least);
   }
 
  private:
   virtual Status DoWriteReadFor(Address device_address,
                                 ConstByteSpan tx_buffer,
                                 ByteSpan rx_buffer,
-                                chrono::SystemClock::duration timeout) = 0;
+                                chrono::SystemClock::duration for_at_least) = 0;
 };
 
 }  // namespace pw::i2c
diff --git a/pw_i2c/public/pw_i2c/initiator_gmock.h b/pw_i2c/public/pw_i2c/initiator_gmock.h
deleted file mode 100644
index 4f7fdb9..0000000
--- a/pw_i2c/public/pw_i2c/initiator_gmock.h
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "gmock/gmock.h"
-#include "pw_i2c/initiator.h"
-
-namespace pw::i2c {
-
-class GmockInitiator : public Initiator {
- public:
-  MOCK_METHOD(Status,
-              DoWriteReadFor,
-              (Address device_address,
-               ConstByteSpan tx_buffer,
-               ByteSpan rx_buffer,
-               chrono::SystemClock::duration timeout),
-              (override));
-};
-
-}  // namespace pw::i2c
diff --git a/pw_i2c/public/pw_i2c/initiator_mock.h b/pw_i2c/public/pw_i2c/initiator_mock.h
deleted file mode 100644
index 67fc2fa..0000000
--- a/pw_i2c/public/pw_i2c/initiator_mock.h
+++ /dev/null
@@ -1,151 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <array>
-#include <cstddef>
-#include <optional>
-
-#include "pw_bytes/span.h"
-#include "pw_containers/to_array.h"
-#include "pw_i2c/initiator.h"
-
-namespace pw::i2c {
-
-// Represents a complete parameter set for the Initiator::DoWriteReadFor().
-class Transaction {
- public:
-  // Same set of parameters as  Initiator::DoWriteReadFor(), with the exception
-  // of optional parameter timeout.
-  constexpr Transaction(
-      Status expected_return_value,
-      Address device_address,
-      ConstByteSpan write_buffer,
-      ConstByteSpan read_buffer,
-      std::optional<chrono::SystemClock::duration> timeout = std::nullopt)
-      : return_value_(expected_return_value),
-        read_buffer_(read_buffer),
-        write_buffer_(write_buffer),
-        address_(device_address),
-        timeout_(timeout) {}
-
-  // Gets the buffer that is virtually read.
-  ConstByteSpan read_buffer() const { return read_buffer_; }
-
-  // Gets the buffer that should be written by the driver.
-  ConstByteSpan write_buffer() const { return write_buffer_; }
-
-  // Gets the min duration for a blocking i2c transaction.
-  std::optional<chrono::SystemClock::duration> timeout() const {
-    return timeout_;
-  }
-
-  // Gets the i2c address that the i2c transaction is targetting.
-  Address address() const { return address_; }
-
-  // Gets the expected return value.
-  Status return_value() const { return return_value_; }
-
- private:
-  const Status return_value_;
-  const ConstByteSpan read_buffer_;
-  const ConstByteSpan write_buffer_;
-  const Address address_;
-  const std::optional<chrono::SystemClock::duration> timeout_;
-};
-
-// Read transaction is a helper that constructs a read only transaction.
-constexpr Transaction ReadTransaction(
-    Status expected_return_value,
-    Address device_address,
-    ConstByteSpan read_buffer,
-    std::optional<chrono::SystemClock::duration> timeout = std::nullopt) {
-  return Transaction(expected_return_value,
-                     device_address,
-                     ConstByteSpan(),
-                     read_buffer,
-                     timeout);
-}
-
-// WriteTransaction is a helper that constructs a write only transaction.
-constexpr Transaction WriteTransaction(
-    Status expected_return_value,
-    Address device_address,
-    ConstByteSpan write_buffer,
-    std::optional<chrono::SystemClock::duration> timeout = std::nullopt) {
-  return Transaction(expected_return_value,
-                     device_address,
-                     write_buffer,
-                     ConstByteSpan(),
-                     timeout);
-}
-
-// MockInitiator takes a series of read and/or write transactions and
-// compares them against user/driver input.
-//
-// This mock uses Gtest to ensure that the transactions instantiated meet
-// expectations. This MockedInitiator should be instantiated inside a Gtest test
-// frame.
-class MockInitiator : public Initiator {
- public:
-  explicit constexpr MockInitiator(std::span<Transaction> transaction_list)
-      : expected_transactions_(transaction_list),
-        expected_transaction_index_(0) {}
-
-  // Should be called at the end of the test to ensure that all expected
-  // transactions have been met.
-  // Returns:
-  // Ok - Success.
-  // OutOfRange - The mocked set of transactions has not been exhausted.
-  Status Finalize() const {
-    if (expected_transaction_index_ != expected_transactions_.size()) {
-      return Status::OutOfRange();
-    }
-    return Status();
-  }
-
-  // Runs Finalize() regardless of whether it was already optionally finalized.
-  ~MockInitiator();
-
- private:
-  // Implements a mocked backend for the i2c initiator.
-  //
-  // Expects (via Gtest):
-  // tx_buffer == expected_transaction_tx_buffer
-  // tx_buffer.size() == expected_transaction_tx_buffer.size()
-  // rx_buffer.size() == expected_transaction_rx_buffer.size()
-  //
-  // Asserts:
-  // When the number of calls to this method exceed the number of expected
-  //    transactions.
-  //
-  // Returns:
-  // Specified transaction return type
-  Status DoWriteReadFor(Address device_address,
-                        ConstByteSpan tx_buffer,
-                        ByteSpan rx_buffer,
-                        chrono::SystemClock::duration timeout) override;
-
-  std::span<Transaction> expected_transactions_;
-  size_t expected_transaction_index_;
-};
-
-// Makes a new i2c transactions list.
-template <size_t kSize>
-constexpr std::array<Transaction, kSize> MakeExpectedTransactionArray(
-    const Transaction (&transactions)[kSize]) {
-  return containers::to_array(transactions);
-}
-
-}  // namespace pw::i2c
diff --git a/pw_i2c/public/pw_i2c/register_device.h b/pw_i2c/public/pw_i2c/register_device.h
deleted file mode 100644
index 3d3cfce..0000000
--- a/pw_i2c/public/pw_i2c/register_device.h
+++ /dev/null
@@ -1,422 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_bytes/endian.h"
-#include "pw_bytes/span.h"
-#include "pw_chrono/system_clock.h"
-#include "pw_i2c/address.h"
-#include "pw_i2c/device.h"
-#include "pw_i2c/initiator.h"
-#include "pw_result/result.h"
-#include "pw_status/status.h"
-#include "pw_status/try.h"
-
-namespace pw {
-namespace i2c {
-
-enum class RegisterAddressSize {
-  k1Byte = 1,
-  k2Bytes = 2,
-  k4Bytes = 4,
-};
-
-// RegisterDevice is used to write/read registers, chunks of data
-// or just an array of bytes over a bus to a device.
-//
-// DISCLAIMER:
-// It is important to note that bulk write/read may not be supported for every
-// device and that it's up to the user to know the capabilities of their device.
-// Users should also be aware of the register and address size and use the
-// appropriate methods for their device.
-//
-//  - WriteRegisters*
-//       Write to a set of registers starting at a specific address/offset.
-//       Endianness will be applied to data that's read or written.
-//
-//  - WriteRegister*
-//       Write data to a register where the max register size is 4 bytes.
-//       Endianness will be applied to data that's read or written.
-//
-//  - ReadRegisters*
-//       Read a set of registers starting at a specific address/offset.
-//       Endianness will be applied to data that's read or written.
-//
-//  - ReadRegister*
-//       Read data to a register where the max register size is 4 bytes.
-//       Endianness will be applied to data that's read or written.
-class RegisterDevice : public Device {
- public:
-  // Args:
-  //   initiator: I2C initiator for the bus the device is on.
-  //   address: I2C device address.
-  //   register_address_order: Endianness of the register address.
-  //   data_order: Endianness of the data.
-  //   register_address_size: Size of the register address.
-  constexpr RegisterDevice(Initiator& initiator,
-                           Address address,
-                           std::endian register_address_order,
-                           std::endian data_order,
-                           RegisterAddressSize register_address_size)
-      : Device(initiator, address),
-        register_address_order_(register_address_order),
-        data_order_(data_order),
-        register_address_size_(register_address_size) {}
-
-  // Args:
-  //   initiator: I2C initiator for the bus the device is on.
-  //   address: I2C device address.
-  //   order: Endianness of the register address and data.
-  //   register_address_size: Size of the register address.
-  constexpr RegisterDevice(Initiator& initiator,
-                           Address address,
-                           std::endian order,
-                           RegisterAddressSize register_address_size)
-      : Device(initiator, address),
-        register_address_order_(order),
-        data_order_(order),
-        register_address_size_(register_address_size) {}
-
-  // Writes data to multiple contiguous registers starting at specific register.
-  // WriteRegisters has byte addressable capabilities and it is up to the user
-  // to determine the appropriate size based on the features of the device. The
-  // amount of data to write is the size of the span. Endianness is taken into
-  // account if register_data_size is 2 bytes or 4 bytes. Both address and
-  // data will use the same endianness provided by the constructor.
-  //
-  // It is important to note that bulk write may not be supported for every
-  // device and that it's up to the user to know the capabilities of their
-  // device. Args:
-  //   register_address: Register address to send.
-  //   register_data: Data to write.
-  //   buffer: Since we need a buffer to construct the write data that consists
-  //           of the register address and the register data, the buffer should
-  //           be big enough such that the two can be concatenated.
-  //   timeout: timeout that's used for both lock and transaction.
-  // Returns:
-  //   Ok: Successful.
-  //   DeadlineExceeded: Unable to acquire exclusive Initiator access and
-  //                     complete the I2C transaction in time.
-  //   FailedPrecondition: Interface is not initialized and/or enabled.
-  //   Internal: Building data for the write buffer has an issue.
-  //   InvalidArgument: Device_address is larger than the 10 bit address space.
-  //   OutOfRange: if buffer size is too small for data and register_address.
-  //   Unavailable: if NACK and device did not respond in time.
-  Status WriteRegisters(uint32_t register_address,
-                        ConstByteSpan register_data,
-                        ByteSpan buffer,
-                        chrono::SystemClock::duration timeout);
-
-  Status WriteRegisters8(uint32_t register_address,
-                         std::span<const uint8_t> register_data,
-                         ByteSpan buffer,
-                         chrono::SystemClock::duration timeout);
-
-  Status WriteRegisters16(uint32_t register_address,
-                          std::span<const uint16_t> register_data,
-                          ByteSpan buffer,
-                          chrono::SystemClock::duration timeout);
-
-  Status WriteRegisters32(uint32_t register_address,
-                          std::span<const uint32_t> register_data,
-                          ByteSpan buffer,
-                          chrono::SystemClock::duration timeout);
-
-  // Reads data chunk starting at specific offset or register.
-  // ReadRegisters has byte addressable capabilities and it is up to the user
-  // to determine the appropriate size based on the features of the device. The
-  // amount of data to read is the size of the span. Endianness is taken into
-  // account for the *16 and *32 bit methods.  Both address and data will use
-  // the same endianness provided by the constructor.
-  //
-  // It is important to note that bulk read may not be supported for every
-  // device and that it's up to the user to know the capabilities of their
-  // device. Args:
-  //   register_address: Register address to send.
-  //   return_data: Area to read data to.
-  //   timeout: Timeout that's used for both lock and transaction.
-  // Returns:
-  //   Ok: Successful.
-  //   DeadlineExceeded: Unable to acquire exclusive Initiator access and
-  //                     complete the I2C transaction in time.
-  //   FailedPrecondition: Interface is not initialized and/or enabled.
-  //   Internal: Building data for the write buffer has an issue.
-  //   InvalidArgument: Device_address is larger than the 10 bit address space.
-  //   Unavailable: if NACK and device did not respond in time.
-  Status ReadRegisters(uint32_t register_address,
-                       ByteSpan return_data,
-                       chrono::SystemClock::duration timeout);
-
-  Status ReadRegisters8(uint32_t register_address,
-                        std::span<uint8_t> return_data,
-                        chrono::SystemClock::duration timeout);
-
-  Status ReadRegisters16(uint32_t register_address,
-                         std::span<uint16_t> return_data,
-                         chrono::SystemClock::duration timeout);
-
-  Status ReadRegisters32(uint32_t register_address,
-                         std::span<uint32_t> return_data,
-                         chrono::SystemClock::duration timeout);
-
-  // Writes the register address first before data.
-  // User should be careful which WriteRegister* API is used and should use
-  // the one that matches their register data size if not byte addressable.
-  //
-  // Both address and data will use the same endianness provided by the
-  // constructor.
-  // Args:
-  //   register_address: Register address to send.
-  //   register_data: Data to write.
-  //   timeout: Timeout that's used for both lock and transaction.
-  // Returns:
-  //   Ok: Successful.
-  //   DeadlineExceeded: Unable to acquire exclusive Initiator access and
-  //                     complete the I2C transaction in time.
-  //   FailedPrecondition: Interface is not initialized and/or enabled.
-  //   Internal: Building data for the write buffer has an issue.
-  //   InvalidArgument: Device_address is larger than the 10 bit address space.
-  //   Unavailable: if NACK and device did not respond in time.
-  Status WriteRegister(uint32_t register_address,
-                       std::byte register_data,
-                       chrono::SystemClock::duration timeout);
-
-  Status WriteRegister8(uint32_t register_address,
-                        uint8_t register_data,
-                        chrono::SystemClock::duration timeout);
-
-  Status WriteRegister16(uint32_t register_address,
-                         uint16_t register_data,
-                         chrono::SystemClock::duration timeout);
-
-  Status WriteRegister32(uint32_t register_address,
-                         uint32_t register_data,
-                         chrono::SystemClock::duration timeout);
-
-  // Reads data from the device after sending the register address first.
-  // User should be careful which ReadRegister* API is used and should use
-  // the one that matches their register data size if not byte addressable.
-  //
-  // Both address and data will use the same endianness provided by the
-  // constructor.
-  // Args:
-  //   register_address: Register address to send.
-  //   timeout: Timeout that's used for both lock and transaction.
-  // Returns:
-  //   Ok: Successful.
-  //   DeadlineExceeded: Unable to acquire exclusive Initiator access and
-  //                     complete the I2C transaction in time.
-  //   FailedPrecondition: Interface is not initialized and/or enabled.
-  //   Internal: Building data for the write buffer has an issue.
-  //   InvalidArgument: Device_address is larger than the 10 bit address space.
-  //   Unavailable: if NACK and device did not respond in time.
-  Result<std::byte> ReadRegister(uint32_t register_address,
-                                 chrono::SystemClock::duration timeout);
-
-  Result<uint8_t> ReadRegister8(uint32_t register_address,
-                                chrono::SystemClock::duration timeout);
-
-  Result<uint16_t> ReadRegister16(uint32_t register_address,
-                                  chrono::SystemClock::duration timeout);
-
-  Result<uint32_t> ReadRegister32(uint32_t register_address,
-                                  chrono::SystemClock::duration timeout);
-
- private:
-  // Helper write registers.
-  Status WriteRegisters(uint32_t register_address,
-                        ConstByteSpan register_data,
-                        const size_t register_data_size,
-                        ByteSpan buffer,
-                        chrono::SystemClock::duration timeout);
-
-  const std::endian register_address_order_;
-  const std::endian data_order_;
-  const RegisterAddressSize register_address_size_;
-};
-
-inline Status RegisterDevice::WriteRegisters(
-    uint32_t register_address,
-    ConstByteSpan register_data,
-    ByteSpan buffer,
-    chrono::SystemClock::duration timeout) {
-  return WriteRegisters(register_address,
-                        register_data,
-                        sizeof(decltype(register_data)::value_type),
-                        buffer,
-                        timeout);
-}
-
-inline Status RegisterDevice::WriteRegisters8(
-    uint32_t register_address,
-    std::span<const uint8_t> register_data,
-    ByteSpan buffer,
-    chrono::SystemClock::duration timeout) {
-  return WriteRegisters(register_address,
-                        std::as_bytes(register_data),
-                        sizeof(decltype(register_data)::value_type),
-                        buffer,
-                        timeout);
-}
-
-inline Status RegisterDevice::WriteRegisters16(
-    uint32_t register_address,
-    std::span<const uint16_t> register_data,
-    ByteSpan buffer,
-    chrono::SystemClock::duration timeout) {
-  return WriteRegisters(register_address,
-                        std::as_bytes(register_data),
-                        sizeof(decltype(register_data)::value_type),
-                        buffer,
-                        timeout);
-}
-
-inline Status RegisterDevice::WriteRegisters32(
-    uint32_t register_address,
-    std::span<const uint32_t> register_data,
-    ByteSpan buffer,
-    chrono::SystemClock::duration timeout) {
-  return WriteRegisters(register_address,
-                        std::as_bytes(register_data),
-                        sizeof(decltype(register_data)::value_type),
-                        buffer,
-                        timeout);
-}
-
-inline Status RegisterDevice::WriteRegister(
-    uint32_t register_address,
-    std::byte register_data,
-    chrono::SystemClock::duration timeout) {
-  std::array<std::byte, sizeof(register_data) + sizeof(register_address)>
-      byte_buffer;
-  return WriteRegisters(register_address,
-                        std::span(&register_data, 1),
-                        sizeof(register_data),
-                        byte_buffer,
-                        timeout);
-}
-
-inline Status RegisterDevice::WriteRegister8(
-    uint32_t register_address,
-    uint8_t register_data,
-    chrono::SystemClock::duration timeout) {
-  std::array<std::byte, sizeof(register_data) + sizeof(register_address)>
-      byte_buffer;
-  return WriteRegisters(register_address,
-                        std::as_bytes(std::span(&register_data, 1)),
-                        sizeof(register_data),
-                        byte_buffer,
-                        timeout);
-}
-
-inline Status RegisterDevice::WriteRegister16(
-    uint32_t register_address,
-    uint16_t register_data,
-    chrono::SystemClock::duration timeout) {
-  std::array<std::byte, sizeof(register_data) + sizeof(register_address)>
-      byte_buffer;
-  return WriteRegisters(register_address,
-                        std::as_bytes(std::span(&register_data, 1)),
-                        sizeof(register_data),
-                        byte_buffer,
-                        timeout);
-}
-
-inline Status RegisterDevice::WriteRegister32(
-    uint32_t register_address,
-    uint32_t register_data,
-    chrono::SystemClock::duration timeout) {
-  std::array<std::byte, sizeof(register_data) + sizeof(register_address)>
-      byte_buffer;
-  return WriteRegisters(register_address,
-                        std::as_bytes(std::span(&register_data, 1)),
-                        sizeof(register_data),
-                        byte_buffer,
-                        timeout);
-}
-
-inline Status RegisterDevice::ReadRegisters8(
-    uint32_t register_address,
-    std::span<uint8_t> return_data,
-    chrono::SystemClock::duration timeout) {
-  // For a single byte, there's no endian data, and so we can return the
-  // data as is.
-  return ReadRegisters(
-      register_address, std::as_writable_bytes(return_data), timeout);
-}
-
-inline Status RegisterDevice::ReadRegisters16(
-    uint32_t register_address,
-    std::span<uint16_t> return_data,
-    chrono::SystemClock::duration timeout) {
-  PW_TRY(ReadRegisters(
-      register_address, std::as_writable_bytes(return_data), timeout));
-
-  // Post process endian information.
-  for (uint16_t& register_value : return_data) {
-    register_value = bytes::ReadInOrder<uint16_t>(data_order_, &register_value);
-  }
-
-  return pw::OkStatus();
-}
-
-inline Status RegisterDevice::ReadRegisters32(
-    uint32_t register_address,
-    std::span<uint32_t> return_data,
-    chrono::SystemClock::duration timeout) {
-  PW_TRY(ReadRegisters(
-      register_address, std::as_writable_bytes(return_data), timeout));
-
-  // TODO(b/185952662): Extend endian in pw_byte to support this conversion
-  //                    as optimization.
-  // Post process endian information.
-  for (uint32_t& register_value : return_data) {
-    register_value = bytes::ReadInOrder<uint32_t>(data_order_, &register_value);
-  }
-
-  return pw::OkStatus();
-}
-
-inline Result<std::byte> RegisterDevice::ReadRegister(
-    uint32_t register_address, chrono::SystemClock::duration timeout) {
-  std::byte data = {};
-  PW_TRY(ReadRegisters(register_address, std::span(&data, 1), timeout));
-  return data;
-}
-
-inline Result<uint8_t> RegisterDevice::ReadRegister8(
-    uint32_t register_address, chrono::SystemClock::duration timeout) {
-  uint8_t data = 0;
-  PW_TRY(ReadRegisters8(register_address, std::span(&data, 1), timeout));
-  return data;
-}
-
-inline Result<uint16_t> RegisterDevice::ReadRegister16(
-    uint32_t register_address, chrono::SystemClock::duration timeout) {
-  std::array<uint16_t, 1> data = {};
-  PW_TRY(ReadRegisters16(register_address, data, timeout));
-  return data[0];
-}
-
-inline Result<uint32_t> RegisterDevice::ReadRegister32(
-    uint32_t register_address, chrono::SystemClock::duration timeout) {
-  std::array<uint32_t, 1> data = {};
-  PW_TRY(ReadRegisters32(register_address, data, timeout));
-  return data[0];
-}
-
-}  // namespace i2c
-}  // namespace pw
-
-// TODO (zengk): Register modification.
diff --git a/pw_i2c/register_device.cc b/pw_i2c/register_device.cc
deleted file mode 100644
index 188782d..0000000
--- a/pw_i2c/register_device.cc
+++ /dev/null
@@ -1,152 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_i2c/register_device.h"
-
-#include "pw_assert/check.h"
-#include "pw_bytes/byte_builder.h"
-
-namespace pw {
-namespace i2c {
-namespace {
-
-// Puts the register address data into the buffer based on the size of the
-// register address.
-void PutRegisterAddressInByteBuilder(
-    ByteBuilder& byte_builder,
-    const uint32_t register_address,
-    const std::endian order,
-    RegisterAddressSize register_address_size) {
-  // TODO(b/185952662): Simplify the call site by extending the byte builder
-  //                   and endian API.
-  switch (register_address_size) {
-    case RegisterAddressSize::k1Byte:
-      byte_builder.PutUint8(static_cast<uint8_t>(register_address));
-      break;
-
-    case RegisterAddressSize::k2Bytes:
-      byte_builder.PutUint16(static_cast<uint16_t>(register_address), order);
-      break;
-
-    case RegisterAddressSize::k4Bytes:
-      byte_builder.PutUint32(register_address, order);
-      break;
-
-    default:
-      PW_CRASH("Invalid address size being put in byte buffer");
-  }
-}
-
-void PutRegisterData16InByteBuilder(ByteBuilder& byte_builder,
-                                    ConstByteSpan register_data,
-                                    const std::endian order) {
-  uint32_t data_pointer_index = 0;
-
-  while (data_pointer_index < register_data.size()) {
-    const uint16_t data = *reinterpret_cast<const uint16_t*>(
-        register_data.data() + data_pointer_index);
-    byte_builder.PutUint16(data, order);
-    data_pointer_index += sizeof(data);
-  }
-}
-
-Status PutRegisterData32InByteBuilder(ByteBuilder& byte_builder,
-                                      ConstByteSpan register_data,
-                                      const std::endian order) {
-  uint32_t data_pointer_index = 0;
-
-  while (data_pointer_index < register_data.size()) {
-    const uint32_t data = *reinterpret_cast<const uint32_t*>(
-        register_data.data() + data_pointer_index);
-    byte_builder.PutUint32(data, order);
-    data_pointer_index += sizeof(data);
-  }
-
-  if (data_pointer_index == register_data.size()) {
-    return pw::OkStatus();
-  } else {
-    // The write data that was given doesn't align with the expected register
-    // data size.
-    return Status::InvalidArgument();
-  }
-}
-
-}  // namespace
-
-Status RegisterDevice::WriteRegisters(const uint32_t register_address,
-                                      ConstByteSpan register_data,
-                                      const size_t register_data_size,
-                                      ByteSpan buffer,
-                                      chrono::SystemClock::duration timeout) {
-  // Make sure the buffer is big enough to handle the address and data.
-  if (buffer.size() <
-      register_data.size() + static_cast<uint32_t>(register_address_size_)) {
-    return pw::Status::OutOfRange();
-  }
-
-  ByteBuilder builder = ByteBuilder(buffer);
-  PutRegisterAddressInByteBuilder(builder,
-                                  register_address,
-                                  register_address_order_,
-                                  register_address_size_);
-
-  switch (register_data_size) {
-    case 1:
-      builder.append(register_data.data(), register_data.size());
-      break;
-
-    case 2:
-      PutRegisterData16InByteBuilder(builder, register_data, data_order_);
-      break;
-
-    case 4:
-      PutRegisterData32InByteBuilder(builder, register_data, data_order_)
-          .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-      break;
-
-    default:
-      PW_CRASH("Invalid data size being put in byte buffer");
-  }
-
-  if (!builder.ok()) {
-    return pw::Status::Internal();
-  }
-
-  ConstByteSpan write_buffer(builder.data(), builder.size());
-  return WriteFor(write_buffer, timeout);
-}
-
-Status RegisterDevice::ReadRegisters(uint32_t register_address,
-                                     ByteSpan return_data,
-                                     chrono::SystemClock::duration timeout) {
-  ByteBuffer<sizeof(register_address)> byte_buffer;
-
-  PutRegisterAddressInByteBuilder(byte_buffer,
-                                  register_address,
-                                  register_address_order_,
-                                  register_address_size_);
-
-  if (!byte_buffer.ok()) {
-    return pw::Status::Internal();
-  }
-
-  return WriteReadFor(byte_buffer.data(),
-                      byte_buffer.size(),
-                      return_data.data(),
-                      return_data.size(),
-                      timeout);
-}
-
-}  // namespace i2c
-}  // namespace pw
diff --git a/pw_i2c/register_device_test.cc b/pw_i2c/register_device_test.cc
deleted file mode 100644
index 93f1524..0000000
--- a/pw_i2c/register_device_test.cc
+++ /dev/null
@@ -1,821 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_i2c/register_device.h"
-
-#include "gtest/gtest.h"
-#include "pw_assert/check.h"
-#include "pw_bytes/byte_builder.h"
-
-namespace pw {
-namespace i2c {
-namespace {
-
-using ::pw::Status;
-using namespace std::literals::chrono_literals;
-
-constexpr uint8_t kErrorValue = 0x11;
-constexpr Address kTestDeviceAddress = Address::SevenBit<0x3F>();
-
-constexpr chrono::SystemClock::duration kTimeout =
-    std::chrono::duration_cast<chrono::SystemClock::duration>(100ms);
-
-// Default test object. Mimics closely to I2c devices.
-class TestInitiator : public Initiator {
- public:
-  explicit TestInitiator() {}
-
-  ByteBuilder& GetWriteBuffer() { return write_buffer_; }
-
-  void SetReadData(ByteSpan read_data) {
-    read_buffer_.append(read_data.data(), read_data.size());
-  }
-
- private:
-  Status DoWriteReadFor(Address,
-                        ConstByteSpan tx_data,
-                        ByteSpan rx_data,
-                        chrono::SystemClock::duration) override {
-    // Write
-    if (!tx_data.empty()) {
-      write_buffer_.append(tx_data.data(), tx_data.size());
-    }
-
-    // Read
-    if (!rx_data.empty()) {
-      PW_CHECK_UINT_EQ(
-          read_buffer_.size(), rx_data.size(), "Buffer to read is too big");
-      for (uint32_t i = 0; i < rx_data.size(); i++) {
-        rx_data[i] = read_buffer_.data()[i];
-      }
-    }
-
-    return OkStatus();
-  }
-
-  ByteBuffer<10> write_buffer_;
-  ByteBuffer<10> read_buffer_;
-};
-
-TEST(RegisterDevice, Construction) {
-  TestInitiator initiator;
-  RegisterDevice device(initiator,
-                        kTestDeviceAddress,
-                        std::endian::little,
-                        RegisterAddressSize::k1Byte);
-}
-
-TEST(RegisterDevice, WriteRegisters8With2RegistersAnd1ByteAddress) {
-  TestInitiator initiator;
-  RegisterDevice device(initiator,
-                        kTestDeviceAddress,
-                        std::endian::little,
-                        RegisterAddressSize::k1Byte);
-
-  std::array<std::byte, 2> register_data = {std::byte{0xCD}, std::byte{0xEF}};
-  std::array<std::byte, 3> builder;
-  constexpr uint32_t kRegisterAddress = 0xAB;
-  EXPECT_EQ(
-      device.WriteRegisters(kRegisterAddress, register_data, builder, kTimeout),
-      pw::OkStatus());
-
-  ByteBuilder& test_device_builder = initiator.GetWriteBuffer();
-  EXPECT_EQ(sizeof(builder), test_device_builder.size());
-
-  // Check address.
-  EXPECT_EQ(kRegisterAddress,
-            static_cast<uint32_t>(test_device_builder.data()[0]));
-
-  // Check data.
-  constexpr uint32_t kAddressSize =
-      static_cast<uint32_t>(RegisterAddressSize::k1Byte);
-  for (uint32_t i = 0; i < test_device_builder.size() - kAddressSize; i++) {
-    EXPECT_EQ(register_data[i], test_device_builder.data()[i + kAddressSize]);
-  }
-}
-
-TEST(RegisterDevice, WriteRegisters8With2RegistersAnd2ByteAddress) {
-  TestInitiator initiator;
-  RegisterDevice device(initiator,
-                        kTestDeviceAddress,
-                        std::endian::little,
-                        RegisterAddressSize::k2Bytes);
-
-  constexpr uint32_t kRegisterAddress = 0x89AB;
-  std::byte register_data[2] = {std::byte{0xCD}, std::byte{0xEF}};
-  std::array<std::byte, 4> builder;
-  EXPECT_EQ(
-      device.WriteRegisters(kRegisterAddress, register_data, builder, kTimeout),
-      pw::OkStatus());
-
-  ByteBuilder& test_device_builder = initiator.GetWriteBuffer();
-  EXPECT_EQ(sizeof(builder), test_device_builder.size());
-
-  // Check address.
-  const uint16_t kActualAddress = *(reinterpret_cast<uint16_t*>(
-      const_cast<std::byte*>(test_device_builder.data())));
-  EXPECT_EQ(kRegisterAddress, kActualAddress);
-
-  // Check data.
-  constexpr uint32_t kAddressSize =
-      static_cast<uint32_t>(RegisterAddressSize::k2Bytes);
-  for (uint32_t i = 0; i < test_device_builder.size() - kAddressSize; i++) {
-    EXPECT_EQ(register_data[i], test_device_builder.data()[i + kAddressSize]);
-  }
-}
-
-TEST(RegisterDevice, WriteRegisters16With2RegistersAnd2ByteAddress) {
-  TestInitiator initiator;
-  RegisterDevice device(initiator,
-                        kTestDeviceAddress,
-                        std::endian::little,
-                        RegisterAddressSize::k2Bytes);
-
-  constexpr uint32_t kRegisterAddress = 0x89AB;
-  std::array<uint16_t, 2> register_data = {0xCDEF, 0x1234};
-  std::array<std::byte, 6> builder;
-  EXPECT_EQ(device.WriteRegisters16(
-                kRegisterAddress, register_data, builder, kTimeout),
-            pw::OkStatus());
-
-  ByteBuilder& test_device_builder = initiator.GetWriteBuffer();
-  EXPECT_EQ(sizeof(builder), test_device_builder.size());
-
-  // Check address.
-  const uint16_t kActualAddress = *(reinterpret_cast<uint16_t*>(
-      const_cast<std::byte*>(test_device_builder.data())));
-  EXPECT_EQ(kRegisterAddress, kActualAddress);
-
-  // Check data.
-  constexpr uint32_t kAddressSize =
-      static_cast<uint32_t>(RegisterAddressSize::k2Bytes);
-
-  const uint16_t* read_pointer = reinterpret_cast<const uint16_t*>(
-      test_device_builder.data() + kAddressSize);
-  for (uint32_t i = 0; i < (test_device_builder.size() - kAddressSize) /
-                               sizeof(register_data[0]);
-       i++) {
-    EXPECT_EQ(register_data[i], read_pointer[i]);
-  }
-}
-
-TEST(RegisterDevice, WriteRegisters16With2RegistersAnd2ByteAddressBigEndian) {
-  TestInitiator initiator;
-  RegisterDevice device(initiator,
-                        kTestDeviceAddress,
-                        std::endian::big,
-                        RegisterAddressSize::k2Bytes);
-
-  constexpr uint32_t kRegisterAddress = 0x89AB;
-  std::array<uint16_t, 2> register_data = {0xCDEF, 0x1234};
-  std::array<std::byte, 6> builder;
-  EXPECT_EQ(device.WriteRegisters16(
-                kRegisterAddress, register_data, builder, kTimeout),
-            pw::OkStatus());
-
-  ByteBuilder& test_device_builder = initiator.GetWriteBuffer();
-  EXPECT_EQ(sizeof(builder), test_device_builder.size());
-
-  // Check address.
-  const uint16_t kActualAddress = *(reinterpret_cast<uint16_t*>(
-      const_cast<std::byte*>(test_device_builder.data())));
-  EXPECT_EQ(bytes::ReadInOrder<uint16_t>(std::endian::big, &kRegisterAddress),
-            kActualAddress);
-
-  // Check data.
-  constexpr uint32_t kAddressSize =
-      static_cast<uint32_t>(RegisterAddressSize::k2Bytes);
-
-  const uint16_t* read_pointer = reinterpret_cast<const uint16_t*>(
-      test_device_builder.data() + kAddressSize);
-  for (uint32_t i = 0; i < (test_device_builder.size() - kAddressSize) /
-                               sizeof(register_data[0]);
-       i++) {
-    EXPECT_EQ(bytes::ReadInOrder<uint16_t>(std::endian::big, &register_data[i]),
-              read_pointer[i]);
-  }
-}
-
-TEST(RegisterDevice, WriteRegisters8BufferTooSmall) {
-  TestInitiator initiator;
-  RegisterDevice device(initiator,
-                        kTestDeviceAddress,
-                        std::endian::little,
-                        RegisterAddressSize::k2Bytes);
-
-  constexpr uint32_t kRegisterAddress = 0x89AB;
-  std::array<std::byte, 2> register_data = {std::byte{0xCD}, std::byte{0xEF}};
-  std::array<std::byte, 2> builder;
-  EXPECT_EQ(
-      device.WriteRegisters(kRegisterAddress, register_data, builder, kTimeout),
-      pw::Status::OutOfRange());
-}
-
-TEST(RegisterDevice, WriteRegister16With1ByteAddress) {
-  TestInitiator initiator;
-  RegisterDevice device(initiator,
-                        kTestDeviceAddress,
-                        std::endian::little,
-                        RegisterAddressSize::k1Byte);
-
-  constexpr uint32_t kRegisterAddress = 0xAB;
-  constexpr uint16_t kRegisterData = 0xBCDE;
-  EXPECT_EQ(device.WriteRegister16(kRegisterAddress, kRegisterData, kTimeout),
-            pw::OkStatus());
-
-  constexpr uint32_t kAddressSize =
-      static_cast<uint32_t>(RegisterAddressSize::k1Byte);
-  ByteBuilder& test_device_builder = initiator.GetWriteBuffer();
-  EXPECT_EQ(test_device_builder.size(), kAddressSize + sizeof(kRegisterData));
-
-  // Check address.
-  EXPECT_EQ(kRegisterAddress,
-            static_cast<uint32_t>(test_device_builder.data()[0]));
-
-  // Check data.
-  for (uint32_t i = 0; i < test_device_builder.size() - kAddressSize; i++) {
-    EXPECT_EQ(
-        (kRegisterData >> (8 * i)) & 0xFF,
-        static_cast<uint16_t>(test_device_builder.data()[i + kAddressSize]));
-  }
-}
-
-TEST(RegisterDevice, WriteRegister32With1ByteAddress) {
-  TestInitiator initiator;
-  RegisterDevice device(initiator,
-                        kTestDeviceAddress,
-                        std::endian::little,
-                        RegisterAddressSize::k1Byte);
-
-  constexpr uint32_t kRegisterAddress = 0xAB;
-  constexpr uint32_t kRegisterData = 0xBCCDDEEF;
-  EXPECT_EQ(device.WriteRegister32(kRegisterAddress, kRegisterData, kTimeout),
-            pw::OkStatus());
-
-  constexpr uint32_t kAddressSize =
-      static_cast<uint32_t>(RegisterAddressSize::k1Byte);
-  ByteBuilder& test_device_builder = initiator.GetWriteBuffer();
-  EXPECT_EQ(test_device_builder.size(), kAddressSize + sizeof(kRegisterData));
-
-  // Check address.
-  EXPECT_EQ(kRegisterAddress,
-            static_cast<uint32_t>(test_device_builder.data()[0]));
-
-  // Check data.
-  for (uint32_t i = 0; i < test_device_builder.size() - kAddressSize; i++) {
-    EXPECT_EQ(
-        (kRegisterData >> (8 * i)) & 0xFF,
-        static_cast<uint32_t>(test_device_builder.data()[i + kAddressSize]));
-  }
-}
-
-TEST(RegisterDevice, WriteRegister16with2ByteAddress) {
-  TestInitiator initiator;
-  RegisterDevice device(initiator,
-                        kTestDeviceAddress,
-                        std::endian::little,
-                        RegisterAddressSize::k2Bytes);
-
-  constexpr uint32_t kRegisterAddress = 0xAB23;
-  constexpr uint16_t kRegisterData = 0xBCDD;
-  EXPECT_EQ(device.WriteRegister16(kRegisterAddress, kRegisterData, kTimeout),
-            pw::OkStatus());
-
-  constexpr uint32_t kAddressSize =
-      static_cast<uint32_t>(RegisterAddressSize::k2Bytes);
-  ByteBuilder& test_device_builder = initiator.GetWriteBuffer();
-  EXPECT_EQ(test_device_builder.size(), kAddressSize + sizeof(kRegisterData));
-
-  // Check address.
-  const uint16_t kActualAddress = *(reinterpret_cast<uint16_t*>(
-      const_cast<std::byte*>(test_device_builder.data())));
-  EXPECT_EQ(kRegisterAddress, kActualAddress);
-
-  // Check data.
-  for (uint32_t i = 0; i < test_device_builder.size() - kAddressSize; i++) {
-    EXPECT_EQ(
-        (kRegisterData >> (8 * i)) & 0xFF,
-        static_cast<uint16_t>(test_device_builder.data()[i + kAddressSize]));
-  }
-}
-
-TEST(RegisterDevice, WriteRegister16With1ByteAddressAndBigEndian) {
-  TestInitiator initiator;
-  RegisterDevice device(initiator,
-                        kTestDeviceAddress,
-                        std::endian::big,
-                        RegisterAddressSize::k1Byte);
-
-  constexpr uint32_t kRegisterAddress = 0xAB;
-  constexpr uint16_t kRegisterData = 0xBCDE;
-  EXPECT_EQ(device.WriteRegister16(kRegisterAddress, kRegisterData, kTimeout),
-            pw::OkStatus());
-
-  constexpr uint32_t kAddressSize =
-      static_cast<uint32_t>(RegisterAddressSize::k1Byte);
-  ByteBuilder& test_device_builder = initiator.GetWriteBuffer();
-  EXPECT_EQ(test_device_builder.size(), kAddressSize + sizeof(kRegisterData));
-
-  // Check address.
-  EXPECT_EQ(kRegisterAddress,
-            static_cast<uint32_t>(test_device_builder.data()[0]));
-
-  // Check data.
-  for (uint32_t i = 0; i < test_device_builder.size() - kAddressSize; i++) {
-    const uint32_t shift = test_device_builder.size() - kAddressSize - (i + 1);
-    EXPECT_EQ(
-        (kRegisterData >> (8 * shift)) & 0xFF,
-        static_cast<uint16_t>(test_device_builder.data()[i + kAddressSize]));
-  }
-}
-
-TEST(RegisterDevice, WriteRegister32With1ByteAddressAndBigEndian) {
-  TestInitiator initiator;
-  RegisterDevice device(initiator,
-                        kTestDeviceAddress,
-                        std::endian::big,
-                        RegisterAddressSize::k1Byte);
-
-  constexpr uint32_t kRegisterAddress = 0xAB;
-  constexpr uint32_t kRegisterData = 0xBCCDDEEF;
-  EXPECT_EQ(device.WriteRegister32(kRegisterAddress, kRegisterData, kTimeout),
-            pw::OkStatus());
-
-  constexpr uint32_t kAddressSize =
-      static_cast<uint32_t>(RegisterAddressSize::k1Byte);
-  ByteBuilder& test_device_builder = initiator.GetWriteBuffer();
-  EXPECT_EQ(test_device_builder.size(), kAddressSize + sizeof(kRegisterData));
-
-  // Check address.
-  EXPECT_EQ(kRegisterAddress,
-            static_cast<uint32_t>(test_device_builder.data()[0]));
-
-  // Check data.
-  for (uint32_t i = 0; i < test_device_builder.size() - kAddressSize; i++) {
-    const uint32_t shift = test_device_builder.size() - kAddressSize - (i + 1);
-    EXPECT_EQ(
-        (kRegisterData >> (8 * shift)) & 0xFF,
-        static_cast<uint32_t>(test_device_builder.data()[i + kAddressSize]));
-  }
-}
-
-TEST(RegisterDevice, WriteRegister16With2ByteAddressAndBigEndian) {
-  TestInitiator initiator;
-  RegisterDevice device(initiator,
-                        kTestDeviceAddress,
-                        std::endian::big,
-                        RegisterAddressSize::k2Bytes);
-
-  constexpr uint32_t kRegisterAddress = 0xAB11;
-  constexpr uint16_t kRegisterData = 0xBCDF;
-  EXPECT_EQ(device.WriteRegister16(kRegisterAddress, kRegisterData, kTimeout),
-            pw::OkStatus());
-
-  constexpr uint32_t kAddressSize =
-      static_cast<uint32_t>(RegisterAddressSize::k2Bytes);
-  ByteBuilder& test_device_builder = initiator.GetWriteBuffer();
-  EXPECT_EQ(test_device_builder.size(), kAddressSize + sizeof(kRegisterData));
-
-  // Check address.
-  const uint16_t kActualAddress = *(reinterpret_cast<uint16_t*>(
-      const_cast<std::byte*>(test_device_builder.data())));
-  EXPECT_EQ(bytes::ReadInOrder<uint16_t>(std::endian::big, &kRegisterAddress),
-            kActualAddress);
-
-  // Check data.
-  for (uint32_t i = 0; i < test_device_builder.size() - kAddressSize; i++) {
-    const uint32_t shift = test_device_builder.size() - kAddressSize - (i + 1);
-    EXPECT_EQ(
-        (kRegisterData >> (8 * shift)) & 0xFF,
-        static_cast<uint16_t>(test_device_builder.data()[i + kAddressSize]));
-  }
-}
-
-TEST(RegisterDevice, ReadRegisters8ByteWith2RegistersAnd1ByteAddress) {
-  TestInitiator initiator;
-  RegisterDevice device(initiator,
-                        kTestDeviceAddress,
-                        std::endian::little,
-                        RegisterAddressSize::k1Byte);
-
-  std::array<std::byte, 2> register_data = {std::byte{0xCD}, std::byte{0xEF}};
-  initiator.SetReadData(register_data);
-
-  std::array<std::byte, 2> buffer;
-  constexpr uint32_t kRegisterAddress = 0xAB;
-  EXPECT_EQ(device.ReadRegisters(kRegisterAddress, buffer, kTimeout),
-            pw::OkStatus());
-
-  // Check address.
-  ByteBuilder& address_buffer = initiator.GetWriteBuffer();
-  EXPECT_EQ(static_cast<uint32_t>(RegisterAddressSize::k1Byte),
-            address_buffer.size());
-
-  const uint8_t kActualAddress = *(reinterpret_cast<uint8_t*>(
-      const_cast<std::byte*>(address_buffer.data())));
-  EXPECT_EQ(kRegisterAddress, kActualAddress);
-
-  // Check data.
-  for (uint32_t i = 0; i < sizeof(buffer); i++) {
-    EXPECT_EQ(buffer[i], register_data[i]);
-  }
-}
-
-TEST(RegisterDevice, ReadRegisters8IntWith2RegistersAnd1ByteAddress) {
-  TestInitiator initiator;
-  RegisterDevice device(initiator,
-                        kTestDeviceAddress,
-                        std::endian::little,
-                        RegisterAddressSize::k1Byte);
-
-  std::array<uint8_t, 2> register_data = {0xCD, 0xEF};
-  initiator.SetReadData(std::as_writable_bytes(
-      std::span(register_data.data(), register_data.size())));
-
-  std::array<uint8_t, 2> buffer;
-  constexpr uint32_t kRegisterAddress = 0xAB;
-  EXPECT_EQ(device.ReadRegisters8(kRegisterAddress, buffer, kTimeout),
-            pw::OkStatus());
-
-  // Check address.
-  ByteBuilder& address_buffer = initiator.GetWriteBuffer();
-  EXPECT_EQ(static_cast<uint32_t>(RegisterAddressSize::k1Byte),
-            address_buffer.size());
-
-  const uint8_t kActualAddress = *(reinterpret_cast<uint8_t*>(
-      const_cast<std::byte*>(address_buffer.data())));
-  EXPECT_EQ(kRegisterAddress, kActualAddress);
-
-  // Check data.
-  for (uint32_t i = 0; i < sizeof(buffer); i++) {
-    EXPECT_EQ(buffer[i], register_data[i]);
-  }
-}
-
-TEST(RegisterDevice, ReadRegisters8ByteWith2RegistersAnd2ByteAddress) {
-  TestInitiator initiator;
-  RegisterDevice device(initiator,
-                        kTestDeviceAddress,
-                        std::endian::little,
-                        RegisterAddressSize::k2Bytes);
-
-  std::array<std::byte, 2> register_data = {std::byte{0xCD}, std::byte{0xEF}};
-  initiator.SetReadData(register_data);
-
-  std::array<std::byte, 2> buffer;
-  constexpr uint32_t kRegisterAddress = 0xABBA;
-  EXPECT_EQ(device.ReadRegisters(kRegisterAddress, buffer, kTimeout),
-            pw::OkStatus());
-
-  // Check address.
-  ByteBuilder& address_buffer = initiator.GetWriteBuffer();
-  EXPECT_EQ(static_cast<uint32_t>(RegisterAddressSize::k2Bytes),
-            address_buffer.size());
-
-  const uint16_t kActualAddress = *(reinterpret_cast<uint16_t*>(
-      const_cast<std::byte*>(address_buffer.data())));
-  EXPECT_EQ(kRegisterAddress, kActualAddress);
-
-  // Check data.
-  for (uint32_t i = 0; i < sizeof(buffer); i++) {
-    EXPECT_EQ(buffer[i], register_data[i]);
-  }
-}
-
-TEST(RegisterDevice, ReadRegisters16With2RegistersAnd2ByteAddress) {
-  TestInitiator initiator;
-  RegisterDevice device(initiator,
-                        kTestDeviceAddress,
-                        std::endian::little,
-                        RegisterAddressSize::k2Bytes);
-
-  std::array<uint16_t, 2> register_data = {0xCDEF, 0x1234};
-  initiator.SetReadData(std::as_writable_bytes(
-      std::span(register_data.data(), register_data.size())));
-
-  std::array<uint16_t, 2> buffer;
-  constexpr uint32_t kRegisterAddress = 0xAB;
-  EXPECT_EQ(device.ReadRegisters16(kRegisterAddress, buffer, kTimeout),
-            pw::OkStatus());
-
-  // Check address.
-  ByteBuilder& address_buffer = initiator.GetWriteBuffer();
-  EXPECT_EQ(static_cast<uint32_t>(RegisterAddressSize::k2Bytes),
-            address_buffer.size());
-
-  const uint16_t kActualAddress = *(reinterpret_cast<uint16_t*>(
-      const_cast<std::byte*>(address_buffer.data())));
-  EXPECT_EQ(kRegisterAddress, kActualAddress);
-
-  // Check data.
-  for (uint32_t i = 0; i < buffer.size(); i++) {
-    EXPECT_EQ(buffer[i], register_data[i]);
-  }
-}
-
-TEST(RegisterDevice, ReadRegisters16With2RegistersAnd2ByteAddressBigEndian) {
-  TestInitiator initiator;
-  RegisterDevice device(initiator,
-                        kTestDeviceAddress,
-                        std::endian::big,
-                        RegisterAddressSize::k2Bytes);
-
-  std::array<uint16_t, 2> register_data = {0xCDEF, 0x1234};
-  initiator.SetReadData(std::as_writable_bytes(
-      std::span(register_data.data(), register_data.size())));
-
-  std::array<uint16_t, 2> buffer;
-  constexpr uint32_t kRegisterAddress = 0xAB;
-  EXPECT_EQ(device.ReadRegisters16(kRegisterAddress, buffer, kTimeout),
-            pw::OkStatus());
-
-  // Check address.
-  ByteBuilder& address_buffer = initiator.GetWriteBuffer();
-  EXPECT_EQ(static_cast<uint32_t>(RegisterAddressSize::k2Bytes),
-            address_buffer.size());
-
-  const uint16_t kActualAddress = *(reinterpret_cast<uint16_t*>(
-      const_cast<std::byte*>(address_buffer.data())));
-  EXPECT_EQ(bytes::ReadInOrder<uint16_t>(std::endian::big, &kRegisterAddress),
-            kActualAddress);
-
-  // Check data.
-  for (uint32_t i = 0; i < buffer.size(); i++) {
-    EXPECT_EQ(bytes::ReadInOrder<uint16_t>(std::endian::big, &register_data[i]),
-              buffer[i]);
-  }
-}
-
-TEST(RegisterDevice, ReadRegister16With1ByteAddress) {
-  TestInitiator initiator;
-  RegisterDevice device(initiator,
-                        kTestDeviceAddress,
-                        std::endian::little,
-                        RegisterAddressSize::k1Byte);
-
-  std::array<std::byte, 2> register_data = {std::byte{0xCD}, std::byte{0xEF}};
-  initiator.SetReadData(register_data);
-
-  constexpr uint32_t kRegisterAddress = 0xAB;
-  Result<uint16_t> result = device.ReadRegister16(kRegisterAddress, kTimeout);
-  EXPECT_TRUE(result.ok());
-  uint16_t read_data = result.value_or(kErrorValue);
-
-  // Check address.
-  ByteBuilder& address_buffer = initiator.GetWriteBuffer();
-  EXPECT_EQ(static_cast<uint32_t>(RegisterAddressSize::k1Byte),
-            address_buffer.size());
-
-  const uint8_t kActualAddress = *(reinterpret_cast<uint8_t*>(
-      const_cast<std::byte*>(address_buffer.data())));
-  EXPECT_EQ(kRegisterAddress, kActualAddress);
-
-  // Check data.
-  uint8_t* read_pointer = reinterpret_cast<uint8_t*>(&read_data);
-  for (uint32_t i = 0; i < sizeof(read_data); i++) {
-    EXPECT_EQ(read_pointer[i], static_cast<uint8_t>(register_data[i]));
-  }
-}
-
-TEST(RegisterDevice, ReadRegister32With1ByteAddress) {
-  TestInitiator initiator;
-  RegisterDevice device(initiator,
-                        kTestDeviceAddress,
-                        std::endian::little,
-                        RegisterAddressSize::k1Byte);
-
-  std::array<std::byte, 4> register_data = {
-      std::byte{0x98}, std::byte{0x76}, std::byte{0x54}, std::byte{0x32}};
-  initiator.SetReadData(register_data);
-
-  constexpr uint32_t kRegisterAddress = 0xAB;
-  Result<uint32_t> result = device.ReadRegister32(kRegisterAddress, kTimeout);
-  EXPECT_TRUE(result.ok());
-  uint32_t read_data = result.value_or(kErrorValue);
-
-  // Check address.
-  ByteBuilder& address_buffer = initiator.GetWriteBuffer();
-  EXPECT_EQ(static_cast<uint32_t>(RegisterAddressSize::k1Byte),
-            address_buffer.size());
-
-  const uint8_t kActualAddress = *(reinterpret_cast<uint8_t*>(
-      const_cast<std::byte*>(address_buffer.data())));
-  EXPECT_EQ(kRegisterAddress, kActualAddress);
-
-  // Check data.
-  uint8_t* read_pointer = reinterpret_cast<uint8_t*>(&read_data);
-  for (uint32_t i = 0; i < sizeof(read_data); i++) {
-    EXPECT_EQ(read_pointer[i], static_cast<uint8_t>(register_data[i]));
-  }
-}
-
-TEST(RegisterDevice, ReadRegister16With2ByteAddress) {
-  TestInitiator initiator;
-  RegisterDevice device(initiator,
-                        kTestDeviceAddress,
-                        std::endian::little,
-                        RegisterAddressSize::k2Bytes);
-
-  std::array<std::byte, 2> register_data = {std::byte{0x98}, std::byte{0x76}};
-  initiator.SetReadData(register_data);
-
-  constexpr uint32_t kRegisterAddress = 0xA4AB;
-  Result<uint16_t> result = device.ReadRegister16(kRegisterAddress, kTimeout);
-  EXPECT_TRUE(result.ok());
-  uint16_t read_data = result.value_or(kErrorValue);
-
-  // Check address.
-  ByteBuilder& address_buffer = initiator.GetWriteBuffer();
-  EXPECT_EQ(static_cast<uint32_t>(RegisterAddressSize::k2Bytes),
-            address_buffer.size());
-
-  const uint16_t kActualAddress = *(reinterpret_cast<uint16_t*>(
-      const_cast<std::byte*>(address_buffer.data())));
-  EXPECT_EQ(kRegisterAddress, kActualAddress);
-
-  // Check data.
-  uint8_t* read_pointer = reinterpret_cast<uint8_t*>(&read_data);
-  for (uint32_t i = 0; i < sizeof(read_data); i++) {
-    EXPECT_EQ(read_pointer[i], static_cast<uint8_t>(register_data[i]));
-  }
-}
-
-TEST(RegisterDevice, ReadRegister16With1ByteAddressAndBigEndian) {
-  TestInitiator initiator;
-  RegisterDevice device(initiator,
-                        kTestDeviceAddress,
-                        std::endian::big,
-                        RegisterAddressSize::k1Byte);
-
-  std::array<std::byte, 2> register_data = {std::byte{0x98}, std::byte{0x76}};
-  initiator.SetReadData(register_data);
-
-  constexpr uint32_t kRegisterAddress = 0xAB;
-  Result<uint16_t> result = device.ReadRegister16(kRegisterAddress, kTimeout);
-  EXPECT_TRUE(result.ok());
-  uint16_t read_data = result.value_or(kErrorValue);
-
-  // Check address.
-  ByteBuilder& address_buffer = initiator.GetWriteBuffer();
-  EXPECT_EQ(static_cast<uint32_t>(RegisterAddressSize::k1Byte),
-            address_buffer.size());
-
-  const uint8_t kActualAddress = *(reinterpret_cast<uint8_t*>(
-      const_cast<std::byte*>(address_buffer.data())));
-  EXPECT_EQ(kRegisterAddress, kActualAddress);
-
-  // Check data.
-  uint8_t* read_pointer = reinterpret_cast<uint8_t*>(&read_data);
-  for (uint32_t i = 0; i < sizeof(read_data); i++) {
-    const uint32_t kReadPointerIndex = sizeof(read_data) - 1 - i;
-    EXPECT_EQ(read_pointer[kReadPointerIndex],
-              static_cast<uint8_t>(register_data[i]));
-  }
-}
-
-TEST(RegisterDevice, ReadRegister32With1ByteAddressAndBigEndian) {
-  TestInitiator initiator;
-  RegisterDevice device(initiator,
-                        kTestDeviceAddress,
-                        std::endian::big,
-                        RegisterAddressSize::k1Byte);
-
-  std::array<std::byte, 4> register_data = {
-      std::byte{0x98}, std::byte{0x76}, std::byte{0x54}, std::byte{0x32}};
-  initiator.SetReadData(register_data);
-
-  constexpr uint32_t kRegisterAddress = 0xAB;
-  Result<uint32_t> result = device.ReadRegister32(kRegisterAddress, kTimeout);
-  EXPECT_TRUE(result.ok());
-  uint32_t read_data = result.value_or(kErrorValue);
-
-  // Check address.
-  ByteBuilder& address_buffer = initiator.GetWriteBuffer();
-  EXPECT_EQ(static_cast<uint32_t>(RegisterAddressSize::k1Byte),
-            address_buffer.size());
-
-  const uint8_t kActualAddress = *(reinterpret_cast<uint8_t*>(
-      const_cast<std::byte*>(address_buffer.data())));
-  EXPECT_EQ(kRegisterAddress, kActualAddress);
-
-  // Check data.
-  uint8_t* read_pointer = reinterpret_cast<uint8_t*>(&read_data);
-  for (uint32_t i = 0; i < sizeof(read_data); i++) {
-    const uint32_t kReadPointerIndex = sizeof(read_data) - 1 - i;
-    EXPECT_EQ(read_pointer[kReadPointerIndex],
-              static_cast<uint8_t>(register_data[i]));
-  }
-}
-
-TEST(RegisterDevice, ReadRegister16With2ByteAddressAndBigEndian) {
-  TestInitiator initiator;
-  RegisterDevice device(initiator,
-                        kTestDeviceAddress,
-                        std::endian::big,
-                        RegisterAddressSize::k2Bytes);
-
-  std::array<std::byte, 2> register_data = {std::byte{0x98}, std::byte{0x76}};
-  initiator.SetReadData(register_data);
-
-  constexpr uint32_t kRegisterAddress = 0xABEF;
-  Result<uint16_t> result = device.ReadRegister16(kRegisterAddress, kTimeout);
-  EXPECT_TRUE(result.ok());
-  uint16_t read_data = result.value_or(kErrorValue);
-
-  // Check address.
-  ByteBuilder& address_buffer = initiator.GetWriteBuffer();
-  EXPECT_EQ(static_cast<uint32_t>(RegisterAddressSize::k2Bytes),
-            address_buffer.size());
-
-  const uint16_t kActualAddress = *(reinterpret_cast<uint16_t*>(
-      const_cast<std::byte*>(address_buffer.data())));
-  EXPECT_EQ(bytes::ReadInOrder<uint16_t>(std::endian::big, &kRegisterAddress),
-            kActualAddress);
-
-  // Check data.
-  uint8_t* read_pointer = reinterpret_cast<uint8_t*>(&read_data);
-  for (uint32_t i = 0; i < sizeof(read_data); i++) {
-    const uint32_t kReadPointerIndex = sizeof(read_data) - 1 - i;
-    EXPECT_EQ(read_pointer[kReadPointerIndex],
-              static_cast<uint8_t>(register_data[i]));
-  }
-}
-
-TEST(RegisterDevice, ReadRegister16With2ByteBigEndianAddress) {
-  TestInitiator initiator;
-  RegisterDevice device(initiator,
-                        kTestDeviceAddress,
-                        std::endian::big,
-                        std::endian::little,
-                        RegisterAddressSize::k2Bytes);
-
-  std::array<std::byte, 2> register_data = {std::byte{0x98}, std::byte{0x76}};
-  initiator.SetReadData(register_data);
-
-  constexpr uint32_t kRegisterAddress = 0xABEF;
-  Result<uint16_t> result = device.ReadRegister16(kRegisterAddress, kTimeout);
-  EXPECT_TRUE(result.ok());
-  uint16_t read_data = result.value_or(kErrorValue);
-
-  // Check address.
-  ByteBuilder& address_buffer = initiator.GetWriteBuffer();
-  EXPECT_EQ(static_cast<uint32_t>(RegisterAddressSize::k2Bytes),
-            address_buffer.size());
-
-  const uint16_t kActualAddress = *(reinterpret_cast<uint16_t*>(
-      const_cast<std::byte*>(address_buffer.data())));
-  EXPECT_EQ(bytes::ReadInOrder<uint16_t>(std::endian::big, &kRegisterAddress),
-            kActualAddress);
-
-  // Check data.
-  uint8_t* read_pointer = reinterpret_cast<uint8_t*>(&read_data);
-  for (uint32_t i = 0; i < sizeof(read_data); i++) {
-    EXPECT_EQ(read_pointer[i], static_cast<uint8_t>(register_data[i]));
-  }
-}
-
-TEST(RegisterDevice, WriteRegister16with2ByteBigEndianAddress) {
-  TestInitiator initiator;
-  RegisterDevice device(initiator,
-                        kTestDeviceAddress,
-                        std::endian::big,
-                        std::endian::little,
-                        RegisterAddressSize::k2Bytes);
-
-  constexpr uint32_t kRegisterAddress = 0xAB11;
-  constexpr uint16_t kRegisterData = 0xBCDF;
-  EXPECT_EQ(device.WriteRegister16(kRegisterAddress, kRegisterData, kTimeout),
-            pw::OkStatus());
-
-  constexpr uint32_t kAddressSize =
-      static_cast<uint32_t>(RegisterAddressSize::k2Bytes);
-  ByteBuilder& test_device_builder = initiator.GetWriteBuffer();
-  EXPECT_EQ(test_device_builder.size(), kAddressSize + sizeof(kRegisterData));
-
-  // Check address.
-  const uint16_t kActualAddress = *(reinterpret_cast<uint16_t*>(
-      const_cast<std::byte*>(test_device_builder.data())));
-  EXPECT_EQ(bytes::ReadInOrder<uint16_t>(std::endian::big, &kRegisterAddress),
-            kActualAddress);
-
-  // Check data.
-  for (uint32_t i = 0; i < test_device_builder.size() - kAddressSize; i++) {
-    EXPECT_EQ(
-        (kRegisterData >> (8 * i)) & 0xFF,
-        static_cast<uint16_t>(test_device_builder.data()[i + kAddressSize]));
-  }
-}
-
-}  // namespace
-}  // namespace i2c
-}  // namespace pw
diff --git a/pw_i2c_mcuxpresso/BUILD.bazel b/pw_i2c_mcuxpresso/BUILD.bazel
deleted file mode 100644
index baaa7f1..0000000
--- a/pw_i2c_mcuxpresso/BUILD.bazel
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_i2c_mcuxpresso",
-    srcs = ["initiator.cc"],
-    hdrs = ["public/pw_i2c_mcuxpresso/initiator.h"],
-    deps = [
-        "//pw_chrono:system_clock",
-        "//pw_i2c:initiator",
-        "//pw_status",
-        "//pw_sync:interrupt_spin_lock",
-        "//pw_sync:lock_annotations",
-        "//pw_sync:mutex",
-        "//pw_sync:timed_thread_notification",
-    ],
-)
diff --git a/pw_i2c_mcuxpresso/BUILD.gn b/pw_i2c_mcuxpresso/BUILD.gn
deleted file mode 100644
index b3b62b4..0000000
--- a/pw_i2c_mcuxpresso/BUILD.gn
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_third_party/mcuxpresso/mcuxpresso.gni")
-
-config("default_config") {
-  include_dirs = [ "public" ]
-}
-
-if (pw_third_party_mcuxpresso_SDK != "") {
-  pw_source_set("pw_i2c_mcuxpresso") {
-    public_configs = [ ":default_config" ]
-    public = [ "public/pw_i2c_mcuxpresso/initiator.h" ]
-    public_deps = [
-      "$dir_pw_chrono:system_clock",
-      "$dir_pw_i2c:initiator",
-      "$dir_pw_status",
-      "$dir_pw_sync:interrupt_spin_lock",
-      "$dir_pw_sync:lock_annotations",
-      "$dir_pw_sync:mutex",
-      "$dir_pw_sync:timed_thread_notification",
-      "$pw_third_party_mcuxpresso_SDK",
-    ]
-    sources = [ "initiator.cc" ]
-  }
-}
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
diff --git a/pw_i2c_mcuxpresso/OWNERS b/pw_i2c_mcuxpresso/OWNERS
deleted file mode 100644
index 504a930..0000000
--- a/pw_i2c_mcuxpresso/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-ewout@google.com
-swatiwagh@google.com
\ No newline at end of file
diff --git a/pw_i2c_mcuxpresso/docs.rst b/pw_i2c_mcuxpresso/docs.rst
deleted file mode 100644
index 449f6e2..0000000
--- a/pw_i2c_mcuxpresso/docs.rst
+++ /dev/null
@@ -1,26 +0,0 @@
-.. _module-pw_i2c_mcuxpresso:
-
------------------
-pw_i2c_mcuxpresso
------------------
-
-``pw_i2c_mcuxpresso`` implements the ``pw_i2c`` interface using the
-NXP MCUXpresso SDK.
-
-The implementation is based on the i2c driver in SDK. I2C transfers use
-non-blocking driver API.
-
-Setup
-=====
-
-This module requires following setup:
-
- 1. Use ``pw_build_mcuxpresso`` to create a ``pw_source_set`` for an
-    MCUXpresso SDK.
- 2. Include the i2c driver component in this SDK definition.
- 3. Specify the ``pw_third_party_mcuxpresso_SDK`` GN global variable to specify
-    the name of this source set.
- 4. Use ``pw::i2c::McuxpressoInitiator`` implementation of
-    ``pw::i2c::Initiator`` while creating ``pw::i2c::Device`` or
-    ``pw::i2c::RegisterDevice`` interface to access the I2C devices connected to
-    target.
\ No newline at end of file
diff --git a/pw_i2c_mcuxpresso/initiator.cc b/pw_i2c_mcuxpresso/initiator.cc
deleted file mode 100644
index 55b5717..0000000
--- a/pw_i2c_mcuxpresso/initiator.cc
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_i2c_mcuxpresso/initiator.h"
-
-#include <mutex>
-
-#include "fsl_i2c.h"
-#include "pw_chrono/system_clock.h"
-#include "pw_status/status.h"
-#include "pw_status/try.h"
-
-namespace pw::i2c {
-namespace {
-
-Status HalStatusToPwStatus(status_t status) {
-  switch (status) {
-    case kStatus_Success:
-      return OkStatus();
-    case kStatus_I2C_Nak:
-    case kStatus_I2C_Addr_Nak:
-      return Status::Unavailable();
-    case kStatus_I2C_InvalidParameter:
-      return Status::InvalidArgument();
-    case kStatus_I2C_Timeout:
-      return Status::DeadlineExceeded();
-    default:
-      return Status::Unknown();
-  }
-}
-}  // namespace
-
-// inclusive-language: disable
-McuxpressoInitiator::McuxpressoInitiator(I2C_Type* base,
-                                         uint32_t baud_rate_bps,
-                                         uint32_t src_clock_hz)
-    : base_(base) {
-  i2c_master_config_t master_config;
-  I2C_MasterGetDefaultConfig(&master_config);
-  master_config.baudRate_Bps = baud_rate_bps;
-  I2C_MasterInit(base_, &master_config, src_clock_hz);
-
-  // Create the handle for the non-blocking transfer and register callback.
-  I2C_MasterTransferCreateHandle(
-      base_, &handle_, McuxpressoInitiator::TransferCompleteCallback, this);
-}
-
-McuxpressoInitiator::~McuxpressoInitiator() { I2C_MasterDeinit(base_); }
-
-void McuxpressoInitiator::TransferCompleteCallback(I2C_Type* base,
-                                                   i2c_master_handle_t* handle,
-                                                   status_t status,
-                                                   void* initiator_ptr) {
-  McuxpressoInitiator& initiator =
-      *static_cast<McuxpressoInitiator*>(initiator_ptr);
-  initiator.callback_isl_.lock();
-  initiator.transfer_status_ = status;
-  initiator.callback_isl_.unlock();
-  initiator.callback_complete_notification_.release();
-}
-
-Status McuxpressoInitiator::InitiateNonBlockingTransfer(
-    chrono::SystemClock::duration rw_timeout, i2c_master_transfer_t* transfer) {
-  const status_t status =
-      I2C_MasterTransferNonBlocking(base_, &handle_, transfer);
-  if (status != kStatus_Success) {
-    return HalStatusToPwStatus(status);
-  }
-
-  if (!callback_complete_notification_.try_acquire_for(rw_timeout)) {
-    I2C_MasterTransferAbort(base_, &handle_);
-    return Status::DeadlineExceeded();
-  }
-
-  callback_isl_.lock();
-  const status_t transfer_status = transfer_status_;
-  callback_isl_.unlock();
-
-  return HalStatusToPwStatus(transfer_status);
-}
-
-// Performs non-blocking I2C write, read and read-after-write depending on the
-// tx and rx buffer states.
-Status McuxpressoInitiator::DoWriteReadFor(
-    Address device_address,
-    ConstByteSpan tx_buffer,
-    ByteSpan rx_buffer,
-    chrono::SystemClock::duration timeout) {
-  if (timeout <= chrono::SystemClock::duration::zero()) {
-    return Status::DeadlineExceeded();
-  }
-
-  const uint8_t address = device_address.GetSevenBit();
-  std::lock_guard lock(mutex_);
-
-  if (!tx_buffer.empty() && rx_buffer.empty()) {
-    i2c_master_transfer_t transfer{kI2C_TransferDefaultFlag,
-                                   address,
-                                   kI2C_Write,
-                                   0,
-                                   0,
-                                   const_cast<std::byte*>(tx_buffer.data()),
-                                   tx_buffer.size()};
-    return InitiateNonBlockingTransfer(timeout, &transfer);
-  } else if (tx_buffer.empty() && !rx_buffer.empty()) {
-    i2c_master_transfer_t transfer{kI2C_TransferDefaultFlag,
-                                   address,
-                                   kI2C_Read,
-                                   0,
-                                   0,
-                                   rx_buffer.data(),
-                                   rx_buffer.size()};
-    return InitiateNonBlockingTransfer(timeout, &transfer);
-  } else if (!tx_buffer.empty() && !rx_buffer.empty()) {
-    i2c_master_transfer_t w_transfer{kI2C_TransferNoStopFlag,
-                                     address,
-                                     kI2C_Write,
-                                     0,
-                                     0,
-                                     const_cast<std::byte*>(tx_buffer.data()),
-                                     tx_buffer.size()};
-    const chrono::SystemClock::time_point deadline =
-        chrono::SystemClock::TimePointAfterAtLeast(timeout);
-    PW_TRY(InitiateNonBlockingTransfer(timeout, &w_transfer));
-    i2c_master_transfer_t r_transfer{kI2C_TransferRepeatedStartFlag,
-                                     address,
-                                     kI2C_Read,
-                                     0,
-                                     0,
-                                     rx_buffer.data(),
-                                     rx_buffer.size()};
-    const chrono::SystemClock::duration time_remaining =
-        deadline - chrono::SystemClock::now();
-    if (time_remaining <= chrono::SystemClock::duration::zero()) {
-      // Abort transfer in an unlikely scenario of timeout even with
-      // successful write.
-      I2C_MasterTransferAbort(base_, &handle_);
-      return Status::DeadlineExceeded();
-    }
-    return InitiateNonBlockingTransfer(time_remaining, &r_transfer);
-  } else {
-    return Status::InvalidArgument();
-  }
-}
-// inclusive-language: enable
-}  // namespace pw::i2c
diff --git a/pw_i2c_mcuxpresso/public/pw_i2c_mcuxpresso/initiator.h b/pw_i2c_mcuxpresso/public/pw_i2c_mcuxpresso/initiator.h
deleted file mode 100644
index ab32beb..0000000
--- a/pw_i2c_mcuxpresso/public/pw_i2c_mcuxpresso/initiator.h
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "fsl_i2c.h"
-#include "pw_i2c/initiator.h"
-#include "pw_sync/interrupt_spin_lock.h"
-#include "pw_sync/lock_annotations.h"
-#include "pw_sync/mutex.h"
-#include "pw_sync/timed_thread_notification.h"
-
-namespace pw::i2c {
-
-// Initiator interface implementation based on I2C driver in NXP MCUXpresso SDK.
-// Currently supports only devices with 7 bit adresses.
-class McuxpressoInitiator final : public Initiator {
- public:
-  McuxpressoInitiator(I2C_Type* base,
-                      uint32_t baud_rate_bps,
-                      uint32_t src_clock_hz);
-
-  ~McuxpressoInitiator();
-
- private:
-  Status DoWriteReadFor(Address device_address,
-                        ConstByteSpan tx_buffer,
-                        ByteSpan rx_buffer,
-                        chrono::SystemClock::duration timeout) override
-      PW_LOCKS_EXCLUDED(mutex_);
-
-  // inclusive-language: disable
-  Status InitiateNonBlockingTransfer(chrono::SystemClock::duration rw_timeout,
-                                     i2c_master_transfer_t* transfer)
-      PW_LOCKS_EXCLUDED(callback_isl_);
-
-  // Non-blocking I2C transfer callback.
-  static void TransferCompleteCallback(I2C_Type* base,
-                                       i2c_master_handle_t* handle,
-                                       status_t status,
-                                       void* initiator_ptr)
-      PW_GUARDED_BY(callback_isl_);
-  // inclusive-language: enable
-
-  sync::Mutex mutex_;
-  I2C_Type* base_ PW_GUARDED_BY(mutex_);
-
-  // Transfer completion status for non-blocking I2C transfer.
-  sync::TimedThreadNotification callback_complete_notification_;
-  sync::InterruptSpinLock callback_isl_;
-  status_t transfer_status_ PW_GUARDED_BY(callback_isl_);
-
-  // inclusive-language: disable
-  i2c_master_handle_t handle_;
-  // inclusive-language: enable
-};
-
-}  // namespace pw::i2c
\ No newline at end of file
diff --git a/pw_interrupt/BUILD b/pw_interrupt/BUILD
new file mode 100644
index 0000000..b1f738b
--- /dev/null
+++ b/pw_interrupt/BUILD
@@ -0,0 +1,54 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+# TODO(pwbug/101): Need to add support for facades/backends to Bazel.
+PW_INTERRUPT_CONTEXT_BACKEND = "//pw_interrupt_context_cortex_m:context_armv7m"
+
+pw_cc_library(
+    name = "context_facade",
+    hdrs = [
+        "public/pw_interrupt/context.h",
+    ],
+    includes = ["public"],
+    srcs = [
+        "context.cc"
+    ],
+    deps = [
+        PW_INTERRUPT_CONTEXT_BACKEND + "_headers",
+    ],
+)
+
+pw_cc_library(
+    name = "context",
+    deps = [
+        ":context_facade",
+        PW_INTERRUPT_CONTEXT_BACKEND + "_headers",
+    ],
+)
+
+pw_cc_library(
+    name = "context_backend",
+    deps = [
+       PW_INTERRUPT_CONTEXT_BACKEND,
+    ],
+)
diff --git a/pw_interrupt/BUILD.bazel b/pw_interrupt/BUILD.bazel
deleted file mode 100644
index 5379e06..0000000
--- a/pw_interrupt/BUILD.bazel
+++ /dev/null
@@ -1,63 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_facade",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_facade(
-    name = "context_facade",
-    hdrs = [
-        "public/pw_interrupt/context.h",
-    ],
-    includes = ["public"],
-)
-
-pw_cc_library(
-    name = "context",
-    deps = [
-        ":context_facade",
-        "@pigweed_config//:pw_interrupt_backend",
-    ],
-)
-
-pw_cc_library(
-    name = "backend_multiplexer",
-    # Normally this would be done in the backend packages but because there is
-    # no host implementation we have to define this here.
-    target_compatible_with = select({
-        "@platforms//cpu:armv7-m": [],
-        "@platforms//cpu:armv7e-m": [],
-        "@platforms//cpu:armv8-m": [],
-        "//conditions:default": ["@platforms//:incompatible"],
-    }),
-    visibility = ["@pigweed_config//:__pkg__"],
-    deps = select({
-        "@platforms//cpu:armv7-m": ["//pw_interrupt_cortex_m:context_armv7m"],
-        "@platforms//cpu:armv7e-m": ["//pw_interrupt_cortex_m:context_armv7m"],
-        "@platforms//cpu:armv8-m": ["//pw_interrupt_cortex_m:context_armv8m"],
-        # This is required for this to be a valid select when building for the
-        # host i.e. 'bazel build //pw_interrupt/...'. The
-        # target_compatible_with attribute is used to skip this target when
-        # built with a wildcard. If explicitly depended on for a host build
-        # the build will fail.
-        "//conditions:default": [],
-    }),
-)
diff --git a/pw_interrupt/BUILD.gn b/pw_interrupt/BUILD.gn
index 37604bb..fb3581e 100644
--- a/pw_interrupt/BUILD.gn
+++ b/pw_interrupt/BUILD.gn
@@ -17,7 +17,11 @@
 import("$dir_pw_build/facade.gni")
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_unit_test/test.gni")
-import("backend.gni")
+
+declare_args() {
+  # Backend for the pw_interrupt module.
+  pw_interrupt_CONTEXT_BACKEND = ""
+}
 
 config("public_include_path") {
   include_dirs = [ "public" ]
diff --git a/pw_interrupt/CMakeLists.txt b/pw_interrupt/CMakeLists.txt
deleted file mode 100644
index f162729..0000000
--- a/pw_interrupt/CMakeLists.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-
-pw_add_facade(pw_interrupt.context
-  HEADERS
-    public/pw_interrupt/context.h
-  PUBLIC_INCLUDES
-    public
-)
diff --git a/pw_interrupt/OWNERS b/pw_interrupt/OWNERS
deleted file mode 100644
index 21d24bc..0000000
--- a/pw_interrupt/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-ewout@google.com
diff --git a/pw_interrupt/backend.gni b/pw_interrupt/backend.gni
deleted file mode 100644
index 535648a..0000000
--- a/pw_interrupt/backend.gni
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-declare_args() {
-  # Backend for the pw_interrupt module.
-  pw_interrupt_CONTEXT_BACKEND = ""
-}
diff --git a/pw_interrupt/docs.rst b/pw_interrupt/docs.rst
index 1bc7d21..2f1b40b 100644
--- a/pw_interrupt/docs.rst
+++ b/pw_interrupt/docs.rst
@@ -7,7 +7,6 @@
 whether one is currently executing in an interrupt context (IRQ or NMI) or not.
 
 .. c:function:: bool InInterruptContext()
-
   Returns true if currently executing within an interrupt service routine
   handling an IRQ or NMI.:w!
 
diff --git a/pw_interrupt_cortex_m/BUILD b/pw_interrupt_cortex_m/BUILD
new file mode 100644
index 0000000..02bd1f0
--- /dev/null
+++ b/pw_interrupt_cortex_m/BUILD
@@ -0,0 +1,66 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "context_armv7m_headers",
+    hdrs = [
+        "public/pw_interrupt_cortex_m/context_inline.h",
+        "public_overrides/pw_interrupt_backend/context_inline.h",
+    ],
+    copts = [ "-DPW_INTERRUPT_CORTEX_M_ARMV7M=1" ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+)
+
+pw_cc_library(
+    name = "context_armv7m",
+    copts = [ "-DPW_INTERRUPT_CORTEX_M_ARMV7M=1" ],
+    deps = [
+        ":context_armv7m_headers",
+        "//pw_interrupt:context_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "context_armv8m_headers",
+    hdrs = [
+        "public/pw_interrupt_cortex_m/context.h",
+        "public_overrides/pw_interrupt_backend/context_backend.h",
+    ],
+    copts = [ "-DPW_INTERRUPT_CORTEX_M_ARMV8M=1" ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+)
+
+pw_cc_library(
+    name = "context_armv8m",
+    copts = [ "-DPW_INTERRUPT_CORTEX_M_ARMV8M=1" ],
+    deps = [
+        ":context_armv8m_headers",
+        "//pw_interrupt:context_facade",
+    ],
+)
diff --git a/pw_interrupt_cortex_m/BUILD.bazel b/pw_interrupt_cortex_m/BUILD.bazel
deleted file mode 100644
index 153af39..0000000
--- a/pw_interrupt_cortex_m/BUILD.bazel
+++ /dev/null
@@ -1,74 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "context_headers",
-    hdrs = [
-        "public/pw_interrupt_cortex_m/context_inline.h",
-        "public_overrides/pw_interrupt_backend/context_inline.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    deps = [
-        "//pw_preprocessor:cortex_m",
-    ],
-)
-
-pw_cc_library(
-    name = "context",
-    deps = [
-        ":context_headers",
-        "//pw_interrupt:context_facade",
-    ],
-)
-
-# The following targets are deprecated, depend on ":context" instead.
-pw_cc_library(
-    name = "context_armv7m_headers",
-    deps = [
-        ":context_headers",
-    ],
-)
-
-pw_cc_library(
-    name = "context_armv7m",
-    deps = [
-        ":context",
-    ],
-)
-
-pw_cc_library(
-    name = "context_armv8m_headers",
-    deps = [
-        ":context_headers",
-    ],
-)
-
-pw_cc_library(
-    name = "context_armv8m",
-    deps = [
-        ":context",
-    ],
-)
diff --git a/pw_interrupt_cortex_m/BUILD.gn b/pw_interrupt_cortex_m/BUILD.gn
index fed8d29..c4fc14d 100644
--- a/pw_interrupt_cortex_m/BUILD.gn
+++ b/pw_interrupt_cortex_m/BUILD.gn
@@ -27,11 +27,20 @@
   visibility = [ ":*" ]
 }
 
-pw_source_set("context") {
-  public_deps = [
-    "$dir_pw_interrupt:context.facade",
-    "$dir_pw_preprocessor:arch",
-  ]
+config("armv6m") {
+  defines = [ "PW_INTERRUPT_CORTEX_M_ARMV6M=1" ]
+}
+
+config("armv7m") {
+  defines = [ "PW_INTERRUPT_CORTEX_M_ARMV7M=1" ]
+}
+
+config("armv8m") {
+  defines = [ "PW_INTERRUPT_CORTEX_M_ARMV8M=1" ]
+}
+
+_context_common = {
+  public_deps = [ "$dir_pw_interrupt:context.facade" ]
   public_configs = [
     ":public_include_path",
     ":backend_config",
@@ -42,15 +51,22 @@
   ]
 }
 
-# These targets are deprecated, use ":context" directly.
-group("context_armv6m") {
-  public_deps = [ ":context" ]
+# This targets provides the ARMv6-M backend for pw_interrupt's context facade.
+pw_source_set("context_armv6m") {
+  forward_variables_from(_context_common, "*")
+  public_configs += [ ":armv6m" ]
 }
-group("context_armv7m") {
-  public_deps = [ ":context" ]
+
+# This targets provides the ARMv7-M backend for pw_interrupt's context facade.
+pw_source_set("context_armv7m") {
+  forward_variables_from(_context_common, "*")
+  public_configs += [ ":armv7m" ]
 }
-group("context_armv8m") {
-  public_deps = [ ":context" ]
+
+# This targets provides the ARMv8-M backend for pw_interrupt's context facade.
+pw_source_set("context_armv8m") {
+  forward_variables_from(_context_common, "*")
+  public_configs += [ ":armv8m" ]
 }
 
 pw_doc_group("docs") {
diff --git a/pw_interrupt_cortex_m/CMakeLists.txt b/pw_interrupt_cortex_m/CMakeLists.txt
deleted file mode 100644
index 2845783..0000000
--- a/pw_interrupt_cortex_m/CMakeLists.txt
+++ /dev/null
@@ -1,28 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-
-pw_add_module_library(pw_interrupt_cortex_m.context
-  IMPLEMENTS_FACADES
-    pw_interrupt.context
-  HEADERS
-    public/pw_interrupt_cortex_m/context_inline.h
-    public_overrides/pw_interrupt_backend/context_inline.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides
-  PUBLIC_DEPS
-    pw_preprocessor.arch
-)
diff --git a/pw_interrupt_cortex_m/OWNERS b/pw_interrupt_cortex_m/OWNERS
deleted file mode 100644
index 21d24bc..0000000
--- a/pw_interrupt_cortex_m/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-ewout@google.com
diff --git a/pw_interrupt_cortex_m/public/pw_interrupt_cortex_m/context_inline.h b/pw_interrupt_cortex_m/public/pw_interrupt_cortex_m/context_inline.h
index 8d25f14..ad20f40 100644
--- a/pw_interrupt_cortex_m/public/pw_interrupt_cortex_m/context_inline.h
+++ b/pw_interrupt_cortex_m/public/pw_interrupt_cortex_m/context_inline.h
@@ -15,19 +15,11 @@
 
 #include <cstdint>
 
-#include "pw_preprocessor/arch.h"
-
 namespace pw::interrupt {
 
-#if !_PW_ARCH_ARM_CORTEX_M
-#error You can only build this for ARM Cortex-M architectures. If you are \
-       trying to do this and are still seeing this error, see \
-       pw_preprocessor/arch.h
-#endif  // !_PW_ARCH_ARM_CORTEX_M
-
-#if _PW_ARCH_ARM_V6M || _PW_ARCH_ARM_V7M || _PW_ARCH_ARM_V7EM || \
-    _PW_ARCH_ARM_V8M_BASELINE || _PW_ARCH_ARM_V8M_MAINLINE ||    \
-    _PW_ARCH_ARM_V8_1M_MAINLINE
+#if defined(PW_INTERRUPT_CORTEX_M_ARMV6M) || \
+    defined(PW_INTERRUPT_CORTEX_M_ARMV7M) || \
+    defined(PW_INTERRUPT_CORTEX_M_ARMV8M)
 inline bool InInterruptContext() {
   // ARMv7M Reference manual section B1.4.2 describes how the Interrupt
   // Program Status Register (IPSR) is zero if there is no exception (interrupt)
@@ -37,7 +29,7 @@
   return ipsr != 0;
 }
 #else
-#error "Your selected ARM Cortex-M arch is not yet supported by this module."
+#error "Please select an architecture specific backend."
 #endif
 
 }  // namespace pw::interrupt
diff --git a/pw_interrupt_zephyr/BUILD.gn b/pw_interrupt_zephyr/BUILD.gn
deleted file mode 100644
index 9a6699a..0000000
--- a/pw_interrupt_zephyr/BUILD.gn
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_docgen/docs.gni")
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
diff --git a/pw_interrupt_zephyr/CMakeLists.txt b/pw_interrupt_zephyr/CMakeLists.txt
deleted file mode 100644
index 9563dba..0000000
--- a/pw_interrupt_zephyr/CMakeLists.txt
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-
-if(NOT CONFIG_PIGWEED_INTERRUPT)
-  return()
-endif()
-
-pw_add_module_library(pw_interrupt_zephyr
-  IMPLEMENTS_FACADES
-    pw_interrupt
-)
-pw_set_backend(pw_interrupt pw_interrupt_zephyr)
-zephyr_link_interface(pw_interrupt_zephyr)
-zephyr_link_libraries(pw_interrupt_zephyr)
diff --git a/pw_interrupt_zephyr/Kconfig b/pw_interrupt_zephyr/Kconfig
deleted file mode 100644
index 44626a9..0000000
--- a/pw_interrupt_zephyr/Kconfig
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-config PIGWEED_INTERRUPT
-    bool "Enable the Pigweed interrupt library (pw_interrupt)"
diff --git a/pw_interrupt_zephyr/OWNERS b/pw_interrupt_zephyr/OWNERS
deleted file mode 100644
index 2230ec2..0000000
--- a/pw_interrupt_zephyr/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-peress@google.com
diff --git a/pw_interrupt_zephyr/docs.rst b/pw_interrupt_zephyr/docs.rst
deleted file mode 100644
index bfa3977..0000000
--- a/pw_interrupt_zephyr/docs.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-.. _module-pw_interrupt_zephyr:
-
-===================
-pw_interrupt_zephyr
-===================
-
---------
-Overview
---------
-This interrupt backend implements the ``pw_interrupt`` facade. To enable, set
-``CONFIG_PIGWEED_INTERRUPT=y``.
diff --git a/pw_interrupt_zephyr/public/pw_interrupt_zephyr/context_inline.h b/pw_interrupt_zephyr/public/pw_interrupt_zephyr/context_inline.h
deleted file mode 100644
index cf8fb78..0000000
--- a/pw_interrupt_zephyr/public/pw_interrupt_zephyr/context_inline.h
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <kernel.h>
-
-namespace pw::interrupt {
-
-inline bool InInterruptContext() { return k_is_in_isr(); }
-
-}  // namespace pw::interrupt
diff --git a/pw_interrupt_zephyr/public_overrides/pw_interrupt_backend/context_inline.h b/pw_interrupt_zephyr/public_overrides/pw_interrupt_backend/context_inline.h
deleted file mode 100644
index b6d96e4..0000000
--- a/pw_interrupt_zephyr/public_overrides/pw_interrupt_backend/context_inline.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// This override header includes the main tokenized logging header and defines
-// the PW_LOG macro as the tokenized logging macro.
-#pragma once
-
-#include "pw_interrupt_zephyr/context_inline.h"
diff --git a/pw_kvs/BUILD b/pw_kvs/BUILD
new file mode 100644
index 0000000..82c4002
--- /dev/null
+++ b/pw_kvs/BUILD
@@ -0,0 +1,437 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_kvs",
+    srcs = [
+        "alignment.cc",
+        "checksum.cc",
+        "entry.cc",
+        "entry_cache.cc",
+        "flash_memory.cc",
+        "format.cc",
+        "key_value_store.cc",
+        "public/pw_kvs/internal/entry.h",
+        "public/pw_kvs/internal/entry_cache.h",
+        "public/pw_kvs/internal/hash.h",
+        "public/pw_kvs/internal/key_descriptor.h",
+        "public/pw_kvs/internal/sectors.h",
+        "public/pw_kvs/internal/span_traits.h",
+        "pw_kvs_private/config.h",
+        "sectors.cc",
+    ],
+    hdrs = [
+        "public/pw_kvs/alignment.h",
+        "public/pw_kvs/checksum.h",
+        "public/pw_kvs/crc16_checksum.h",
+        "public/pw_kvs/flash_memory.h",
+        "public/pw_kvs/format.h",
+        "public/pw_kvs/io.h",
+        "public/pw_kvs/key.h",
+        "public/pw_kvs/key_value_store.h",
+    ],
+    includes = ["public"],
+    deps = [
+        "//pw_assert",
+        "//pw_checksum",
+        "//pw_containers",
+        "//pw_log",
+        "//pw_log:facade",
+        "//pw_span",
+        "//pw_status",
+    ],
+)
+
+pw_cc_library(
+    name = "crc16",
+    hdrs = [
+        "public/pw_kvs/crc16_checksum.h",
+    ],
+    deps = [
+        ":pw_kvs",
+        "//pw_checksum",
+        "//pw_span",
+    ],
+)
+
+pw_cc_library(
+    name = "fake_flash",
+    srcs = [
+        "fake_flash_memory.cc",
+    ],
+    hdrs = [
+        "public/pw_kvs/fake_flash_memory.h",
+    ],
+    deps = [
+        ":pw_kvs",
+        "//pw_containers",
+        "//pw_log",
+        "//pw_log:facade",
+        "//pw_span",
+        "//pw_status",
+    ],
+)
+
+pw_cc_library(
+    name = "fake_flash_small_partition",
+    srcs = [
+        "fake_flash_test_partition.cc",
+    ],
+    hdrs = [
+        "public/pw_kvs/flash_test_partition.h",
+    ],
+    deps = [
+        ":fake_flash",
+        ":pw_kvs",
+    ],
+)
+
+pw_cc_library(
+    name = "fake_flash_64_aligned_partition",
+    srcs = [
+        "fake_flash_test_partition.cc",
+    ],
+    hdrs = [
+        "public/pw_kvs/flash_test_partition.h",
+    ],
+    defines = ["PW_FLASH_TEST_ALIGNMENT=64"],
+    deps = [
+        ":fake_flash",
+        ":pw_kvs",
+    ],
+)
+
+pw_cc_library(
+    name = "fake_flash_256_aligned_partition",
+    srcs = [
+        "fake_flash_test_partition.cc",
+    ],
+    hdrs = [
+        "public/pw_kvs/flash_test_partition.h",
+    ],
+    defines = ["PW_FLASH_TEST_ALIGNMENT=256"],
+    deps = [
+        ":fake_flash",
+        ":pw_kvs",
+    ],
+)
+
+pw_cc_library(
+    name = "fake_flash_test_key_value_store",
+    srcs = [
+        "fake_flash_test_key_value_store.cc",
+    ],
+    hdrs = [
+        "public/pw_kvs/test_key_value_store.h",
+    ],
+    deps = [
+        ":crc16",
+        ":fake_flash",
+        ":pw_kvs",
+    ],
+)
+
+pw_cc_library(
+    name = "test_utils",
+    hdrs = [
+        "pw_kvs_private/byte_utils.h",
+    ],
+    includes = ["public"],
+    visibility = ["//visibility:private"],
+)
+
+pw_cc_library(
+    name = "test_partition",
+    srcs = [
+        "flash_partition_with_stats.cc",
+    ],
+    hdrs = [
+        "public/pw_kvs/flash_partition_with_stats.h",
+    ],
+    includes = ["public"],
+    visibility = ["//visibility:private"],
+    deps = [
+        "//pw_containers",
+        "//pw_kvs",
+        "//pw_log",
+        "//pw_log:facade",
+        "//pw_status",
+    ],
+)
+
+pw_cc_test(
+    name = "alignment_test",
+    srcs = [
+        "alignment_test.cc",
+    ],
+    deps = [
+        ":pw_kvs",
+        "//pw_status",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "checksum_test",
+    srcs = ["checksum_test.cc"],
+    deps = [
+        ":crc16",
+        ":pw_kvs",
+        "//pw_checksum",
+        "//pw_log",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "converts_to_span_test",
+    srcs = ["converts_to_span_test.cc"],
+    deps = [":pw_kvs"],
+)
+
+pw_cc_test(
+    name = "entry_test",
+    srcs = [
+        "entry_test.cc",
+    ],
+    deps = [
+        ":pw_kvs",
+        ":test_utils",
+        "//pw_log:backend",
+        "//pw_span",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "entry_cache_test",
+    srcs = ["entry_cache_test.cc"],
+    deps = [
+        ":pw_kvs",
+        ":test_utils",
+        "//pw_log:backend",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "flash_partition_small_test",
+    srcs = ["flash_partition_test.cc"],
+    defines = ["PW_FLASH_TEST_ITERATIONS=100"],
+    deps = [
+        ":fake_flash_small_partition",
+        ":pw_kvs",
+        "//pw_log:backend",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "flash_partition_64_alignment_test",
+    srcs = ["flash_partition_test.cc"],
+    defines = ["PW_FLASH_TEST_ITERATIONS=100"],
+    deps = [
+        ":fake_flash_64_aligned_partition",
+        ":pw_kvs",
+        "//pw_log:backend",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "flash_partition_256_alignment_test",
+    srcs = ["flash_partition_test.cc"],
+    defines = ["PW_FLASH_TEST_ITERATIONS=100"],
+    deps = [
+        ":fake_flash_256_aligned_partition",
+        ":pw_kvs",
+        "//pw_log:backend",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "key_test",
+    srcs = [
+        "key_test.cc",
+    ],
+    deps = [
+        ":pw_kvs",
+        "//pw_status",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "key_value_store_test",
+    srcs = ["key_value_store_test.cc"],
+    deps = [
+        ":crc16",
+        ":pw_kvs",
+        ":test_utils",
+        "//pw_checksum",
+        "//pw_log:backend",
+        "//pw_log:facade",
+        "//pw_span",
+        "//pw_status",
+        "//pw_string",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "key_value_store_small_flash_test",
+    srcs = ["key_value_store_initialized_test.cc"],
+    deps = [
+        ":crc16",
+        ":fake_flash_small_partition",
+        ":pw_kvs",
+        ":test_utils",
+        "//pw_checksum",
+        "//pw_log:backend",
+        "//pw_log:facade",
+        "//pw_span",
+        "//pw_status",
+        "//pw_string",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "key_value_store_64_alignment_flash_test",
+    srcs = ["key_value_store_initialized_test.cc"],
+    deps = [
+        ":crc16",
+        ":fake_flash_64_aligned_partition",
+        ":pw_kvs",
+        ":test_utils",
+        "//pw_checksum",
+        "//pw_log:backend",
+        "//pw_log:facade",
+        "//pw_span",
+        "//pw_status",
+        "//pw_string",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "key_value_store_256_alignment_flash_test",
+    srcs = ["key_value_store_initialized_test.cc"],
+    deps = [
+        ":crc16",
+        ":fake_flash_256_aligned_partition",
+        ":pw_kvs",
+        ":test_utils",
+        "//pw_checksum",
+        "//pw_log:backend",
+        "//pw_log:facade",
+        "//pw_span",
+        "//pw_status",
+        "//pw_string",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "fake_flash_test_key_value_store_test",
+    srcs = ["test_key_value_store_test.cc"],
+    deps = [
+        ":crc16",
+        ":fake_flash_test_key_value_store",
+        ":pw_kvs",
+        "//pw_log:backend",
+        "//pw_status",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "key_value_store_binary_format_test",
+    srcs = [
+        "key_value_store_binary_format_test.cc",
+    ],
+    deps = [
+        ":crc16",
+        ":pw_kvs",
+        ":test_utils",
+        "//pw_log:backend",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "key_value_store_fuzz_test",
+    srcs = ["key_value_store_fuzz_test.cc"],
+    deps = [
+        ":crc16",
+        ":pw_kvs",
+        ":test_partition",
+        ":test_utils",
+        "//pw_checksum",
+        "//pw_log:backend",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "key_value_store_map_test",
+    srcs = ["key_value_store_map_test.cc"],
+    deps = [
+        ":crc16",
+        ":pw_kvs",
+        ":test_partition",
+        ":test_utils",
+        "//pw_checksum",
+        "//pw_log:backend",
+        "//pw_log:facade",
+        "//pw_span",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "sectors_test",
+    srcs = ["sectors_test.cc"],
+    deps = [
+        ":pw_kvs",
+        ":test_utils",
+        "//pw_log:backend",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "key_value_store_wear_test",
+    srcs = [
+        "key_value_store_wear_test.cc",
+    ],
+    deps = [
+        ":pw_kvs",
+        ":test_partition",
+        ":test_utils",
+        "//pw_log:backend",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_kvs/BUILD.bazel b/pw_kvs/BUILD.bazel
deleted file mode 100644
index 41359d7..0000000
--- a/pw_kvs/BUILD.bazel
+++ /dev/null
@@ -1,531 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_kvs",
-    srcs = [
-        "alignment.cc",
-        "checksum.cc",
-        "entry.cc",
-        "entry_cache.cc",
-        "flash_memory.cc",
-        "format.cc",
-        "key_value_store.cc",
-        "public/pw_kvs/internal/entry.h",
-        "public/pw_kvs/internal/entry_cache.h",
-        "public/pw_kvs/internal/hash.h",
-        "public/pw_kvs/internal/key_descriptor.h",
-        "public/pw_kvs/internal/sectors.h",
-        "public/pw_kvs/internal/span_traits.h",
-        "pw_kvs_private/config.h",
-        "sectors.cc",
-    ],
-    hdrs = [
-        "public/pw_kvs/alignment.h",
-        "public/pw_kvs/checksum.h",
-        "public/pw_kvs/crc16_checksum.h",
-        "public/pw_kvs/flash_memory.h",
-        "public/pw_kvs/format.h",
-        "public/pw_kvs/io.h",
-        "public/pw_kvs/key.h",
-        "public/pw_kvs/key_value_store.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_assert",
-        "//pw_bytes",
-        "//pw_checksum",
-        "//pw_containers",
-        "//pw_log",
-        "//pw_log:facade",
-        "//pw_polyfill",
-        "//pw_span",
-        "//pw_status",
-        "//pw_stream",
-    ],
-)
-
-pw_cc_library(
-    name = "crc16",
-    hdrs = [
-        "public/pw_kvs/crc16_checksum.h",
-    ],
-    deps = [
-        ":pw_kvs",
-        "//pw_checksum",
-        "//pw_span",
-    ],
-)
-
-pw_cc_library(
-    name = "fake_flash",
-    srcs = [
-        "fake_flash_memory.cc",
-    ],
-    hdrs = [
-        "public/pw_kvs/fake_flash_memory.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":pw_kvs",
-        "//pw_containers",
-        "//pw_log",
-        "//pw_log:facade",
-        "//pw_span",
-        "//pw_status",
-    ],
-)
-
-pw_cc_library(
-    name = "fake_flash_1_aligned_partition",
-    srcs = [
-        "fake_flash_test_partition.cc",
-    ],
-    hdrs = [
-        "public/pw_kvs/flash_test_partition.h",
-    ],
-    defines = [
-        "PW_FLASH_TEST_SECTORS=6U",
-        "PW_FLASH_TEST_SECTOR_SIZE=4096U",
-        "PW_FLASH_TEST_ALIGNMENT=1U",
-    ],
-    deps = [
-        ":fake_flash",
-        ":pw_kvs",
-    ],
-)
-
-pw_cc_library(
-    name = "fake_flash_16_aligned_partition",
-    srcs = [
-        "fake_flash_test_partition.cc",
-    ],
-    hdrs = [
-        "public/pw_kvs/flash_test_partition.h",
-    ],
-    defines = [
-        "PW_FLASH_TEST_SECTORS=6U",
-        "PW_FLASH_TEST_SECTOR_SIZE=4096U",
-        "PW_FLASH_TEST_ALIGNMENT=16",
-    ],
-    deps = [
-        ":fake_flash",
-        ":pw_kvs",
-    ],
-)
-
-pw_cc_library(
-    name = "fake_flash_64_aligned_partition",
-    srcs = [
-        "fake_flash_test_partition.cc",
-    ],
-    hdrs = [
-        "public/pw_kvs/flash_test_partition.h",
-    ],
-    defines = [
-        "PW_FLASH_TEST_SECTORS=6U",
-        "PW_FLASH_TEST_SECTOR_SIZE=4096U",
-        "PW_FLASH_TEST_ALIGNMENT=64U",
-    ],
-    deps = [
-        ":fake_flash",
-        ":pw_kvs",
-    ],
-)
-
-pw_cc_library(
-    name = "fake_flash_256_aligned_partition",
-    srcs = [
-        "fake_flash_test_partition.cc",
-    ],
-    hdrs = [
-        "public/pw_kvs/flash_test_partition.h",
-    ],
-    defines = [
-        "PW_FLASH_TEST_SECTORS=6U",
-        "PW_FLASH_TEST_SECTOR_SIZE=4096U",
-        "PW_FLASH_TEST_ALIGNMENT=256U",
-    ],
-    deps = [
-        ":fake_flash",
-        ":pw_kvs",
-    ],
-)
-
-pw_cc_library(
-    name = "fake_flash_test_key_value_store",
-    srcs = [
-        "fake_flash_test_key_value_store.cc",
-    ],
-    hdrs = [
-        "public/pw_kvs/test_key_value_store.h",
-    ],
-    deps = [
-        ":crc16",
-        ":fake_flash",
-        ":pw_kvs",
-        "//pw_sync:borrow",
-    ],
-)
-
-pw_cc_library(
-    name = "test_partition",
-    srcs = [
-        "flash_partition_with_stats.cc",
-    ],
-    hdrs = [
-        "public/pw_kvs/flash_partition_with_stats.h",
-    ],
-    includes = ["public"],
-    visibility = ["//visibility:private"],
-    deps = [
-        "//pw_containers",
-        "//pw_kvs",
-        "//pw_log",
-        "//pw_log:facade",
-        "//pw_status",
-    ],
-)
-
-pw_cc_test(
-    name = "alignment_test",
-    srcs = [
-        "alignment_test.cc",
-    ],
-    deps = [
-        ":pw_kvs",
-        "//pw_status",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "checksum_test",
-    srcs = ["checksum_test.cc"],
-    deps = [
-        ":crc16",
-        ":pw_kvs",
-        "//pw_checksum",
-        "//pw_log",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "converts_to_span_test",
-    srcs = ["converts_to_span_test.cc"],
-    deps = [":pw_kvs"],
-)
-
-pw_cc_test(
-    name = "entry_test",
-    srcs = [
-        "entry_test.cc",
-    ],
-    deps = [
-        ":fake_flash",
-        ":pw_kvs",
-        "//pw_log",
-        "//pw_span",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "entry_cache_test",
-    srcs = ["entry_cache_test.cc"],
-    deps = [
-        ":fake_flash",
-        ":pw_kvs",
-        "//pw_log",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "flash_partition_stream_test",
-    srcs = ["flash_partition_stream_test.cc"],
-    deps = [
-        ":fake_flash",
-        ":pw_kvs",
-        "//pw_log",
-        "//pw_random",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "flash_partition_1_alignment_test",
-    srcs = ["flash_partition_test.cc"],
-    defines = [
-        "PW_FLASH_TEST_ITERATIONS=100",
-        "PW_FLASH_TEST_WRITE_SIZE=1",
-    ],
-    deps = [
-        ":fake_flash_1_aligned_partition",
-        ":pw_kvs",
-        "//pw_log",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "flash_partition_16_alignment_test",
-    srcs = ["flash_partition_test.cc"],
-    defines = [
-        "PW_FLASH_TEST_ITERATIONS=100",
-        "PW_FLASH_TEST_WRITE_SIZE=1",
-    ],
-    deps = [
-        ":fake_flash_16_aligned_partition",
-        ":pw_kvs",
-        "//pw_log",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "flash_partition_64_alignment_test",
-    srcs = ["flash_partition_test.cc"],
-    defines = [
-        "PW_FLASH_TEST_ITERATIONS=100",
-        "PW_FLASH_TEST_WRITE_SIZE=1",
-    ],
-    deps = [
-        ":fake_flash_64_aligned_partition",
-        ":pw_kvs",
-        "//pw_log",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "flash_partition_256_alignment_test",
-    srcs = ["flash_partition_test.cc"],
-    defines = [
-        "PW_FLASH_TEST_ITERATIONS=100",
-        "PW_FLASH_TEST_WRITE_SIZE=1",
-    ],
-    deps = [
-        ":fake_flash_256_aligned_partition",
-        ":pw_kvs",
-        "//pw_log",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "flash_partition_256_write_size_test",
-    srcs = ["flash_partition_test.cc"],
-    defines = [
-        "PW_FLASH_TEST_ITERATIONS=100",
-        "PW_FLASH_TEST_WRITE_SIZE=256",
-    ],
-    deps = [
-        ":fake_flash_1_aligned_partition",
-        ":pw_kvs",
-        "//pw_log",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "key_test",
-    srcs = [
-        "key_test.cc",
-    ],
-    deps = [
-        ":pw_kvs",
-        "//pw_status",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "key_value_store_test",
-    srcs = ["key_value_store_test.cc"],
-    deps = [
-        ":crc16",
-        ":fake_flash",
-        ":pw_kvs",
-        "//pw_checksum",
-        "//pw_log",
-        "//pw_log:facade",
-        "//pw_span",
-        "//pw_status",
-        "//pw_string",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "key_value_store_1_alignment_flash_test",
-    srcs = ["key_value_store_initialized_test.cc"],
-    deps = [
-        ":crc16",
-        ":fake_flash_1_aligned_partition",
-        ":pw_kvs",
-        "//pw_checksum",
-        "//pw_log",
-        "//pw_log:facade",
-        "//pw_span",
-        "//pw_status",
-        "//pw_string",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "key_value_store_16_alignment_flash_test",
-    srcs = ["key_value_store_initialized_test.cc"],
-    deps = [
-        ":crc16",
-        ":fake_flash_16_aligned_partition",
-        ":pw_kvs",
-        "//pw_checksum",
-        "//pw_log",
-        "//pw_log:facade",
-        "//pw_span",
-        "//pw_status",
-        "//pw_string",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "key_value_store_64_alignment_flash_test",
-    srcs = ["key_value_store_initialized_test.cc"],
-    deps = [
-        ":crc16",
-        ":fake_flash_64_aligned_partition",
-        ":pw_kvs",
-        "//pw_checksum",
-        "//pw_log",
-        "//pw_log:facade",
-        "//pw_span",
-        "//pw_status",
-        "//pw_string",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "key_value_store_256_alignment_flash_test",
-    srcs = ["key_value_store_initialized_test.cc"],
-    deps = [
-        ":crc16",
-        ":fake_flash_256_aligned_partition",
-        ":pw_kvs",
-        "//pw_checksum",
-        "//pw_log",
-        "//pw_log:facade",
-        "//pw_span",
-        "//pw_status",
-        "//pw_string",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "fake_flash_test_key_value_store_test",
-    srcs = ["test_key_value_store_test.cc"],
-    deps = [
-        ":crc16",
-        ":fake_flash_test_key_value_store",
-        ":pw_kvs",
-        "//pw_log",
-        "//pw_status",
-        "//pw_sync:borrow",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "key_value_store_binary_format_test",
-    srcs = [
-        "key_value_store_binary_format_test.cc",
-    ],
-    deps = [
-        ":crc16",
-        ":fake_flash",
-        ":pw_kvs",
-        "//pw_log",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "key_value_store_put_test",
-    srcs = ["key_value_store_put_test.cc"],
-    deps = [
-        ":crc16",
-        ":fake_flash",
-        ":pw_kvs",
-        ":test_partition",
-        "//pw_checksum",
-        "//pw_log",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "key_value_store_map_test",
-    srcs = ["key_value_store_map_test.cc"],
-    deps = [
-        ":crc16",
-        ":fake_flash",
-        ":pw_kvs",
-        ":test_partition",
-        "//pw_checksum",
-        "//pw_log",
-        "//pw_log:facade",
-        "//pw_span",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "sectors_test",
-    srcs = ["sectors_test.cc"],
-    deps = [
-        ":fake_flash",
-        ":pw_kvs",
-        "//pw_log",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "key_value_store_wear_test",
-    srcs = [
-        "key_value_store_wear_test.cc",
-    ],
-    deps = [
-        ":fake_flash",
-        ":pw_kvs",
-        ":test_partition",
-        "//pw_log",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_kvs/BUILD.gn b/pw_kvs/BUILD.gn
index 10b0545..a8d84ab 100644
--- a/pw_kvs/BUILD.gn
+++ b/pw_kvs/BUILD.gn
@@ -18,7 +18,6 @@
 import("$dir_pw_build/module_config.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_toolchain/generate_toolchain.gni")
 import("$dir_pw_unit_test/test.gni")
 
 declare_args() {
@@ -66,7 +65,6 @@
     dir_pw_bytes,
     dir_pw_containers,
     dir_pw_status,
-    dir_pw_stream,
     dir_pw_string,
   ]
   deps = [
@@ -98,10 +96,7 @@
 
 pw_source_set("test_key_value_store") {
   public = [ "public/pw_kvs/test_key_value_store.h" ]
-  public_deps = [
-    ":pw_kvs",
-    "$dir_pw_sync:borrow",
-  ]
+  public_deps = [ ":pw_kvs" ]
 }
 
 pw_source_set("fake_flash") {
@@ -119,6 +114,17 @@
   ]
 }
 
+pw_source_set("fake_flash_small_partition") {
+  public_configs = [ ":public_include_path" ]
+  public = [ "public/pw_kvs/flash_test_partition.h" ]
+  sources = [ "fake_flash_test_partition.cc" ]
+  public_deps = [ ":flash_test_partition" ]
+  deps = [
+    ":fake_flash",
+    dir_pw_kvs,
+  ]
+}
+
 pw_source_set("fake_flash_12_byte_partition") {
   public_configs = [ ":public_include_path" ]
   public = [ "public/pw_kvs/flash_test_partition.h" ]
@@ -135,38 +141,6 @@
   ]
 }
 
-pw_source_set("fake_flash_1_aligned_partition") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_kvs/flash_test_partition.h" ]
-  sources = [ "fake_flash_test_partition.cc" ]
-  public_deps = [ ":flash_test_partition" ]
-  deps = [
-    ":fake_flash",
-    dir_pw_kvs,
-  ]
-  defines = [
-    "PW_FLASH_TEST_SECTORS=6U",
-    "PW_FLASH_TEST_SECTOR_SIZE=4096U",
-    "PW_FLASH_TEST_ALIGNMENT=1U",
-  ]
-}
-
-pw_source_set("fake_flash_16_aligned_partition") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_kvs/flash_test_partition.h" ]
-  sources = [ "fake_flash_test_partition.cc" ]
-  public_deps = [ ":flash_test_partition" ]
-  deps = [
-    ":fake_flash",
-    dir_pw_kvs,
-  ]
-  defines = [
-    "PW_FLASH_TEST_SECTORS=6U",
-    "PW_FLASH_TEST_SECTOR_SIZE=4096U",
-    "PW_FLASH_TEST_ALIGNMENT=16U",
-  ]
-}
-
 pw_source_set("fake_flash_64_aligned_partition") {
   public_configs = [ ":public_include_path" ]
   public = [ "public/pw_kvs/flash_test_partition.h" ]
@@ -176,11 +150,7 @@
     ":fake_flash",
     dir_pw_kvs,
   ]
-  defines = [
-    "PW_FLASH_TEST_SECTORS=6U",
-    "PW_FLASH_TEST_SECTOR_SIZE=4096U",
-    "PW_FLASH_TEST_ALIGNMENT=64U",
-  ]
+  defines = [ "PW_FLASH_TEST_ALIGNMENT=64" ]
 }
 
 pw_source_set("fake_flash_256_aligned_partition") {
@@ -192,11 +162,7 @@
     ":fake_flash",
     dir_pw_kvs,
   ]
-  defines = [
-    "PW_FLASH_TEST_SECTORS=6U",
-    "PW_FLASH_TEST_SECTOR_SIZE=4096U",
-    "PW_FLASH_TEST_ALIGNMENT=256U",
-  ]
+  defines = [ "PW_FLASH_TEST_ALIGNMENT=256" ]
 }
 
 pw_source_set("fake_flash_test_key_value_store") {
@@ -210,30 +176,6 @@
   ]
 }
 
-pw_source_set("flash_partition_stream_test") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_kvs/flash_memory.h" ]
-  sources = [ "flash_partition_stream_test.cc" ]
-  public_deps = [
-    "$dir_pw_sync:borrow",
-    dir_pw_bytes,
-    dir_pw_kvs,
-    dir_pw_polyfill,
-    dir_pw_preprocessor,
-    dir_pw_status,
-    dir_pw_stream,
-  ]
-  deps = [
-    ":config",
-    ":fake_flash",
-    ":flash_test_partition",
-    dir_pw_kvs,
-    dir_pw_log,
-    dir_pw_random,
-    dir_pw_unit_test,
-  ]
-}
-
 pw_source_set("flash_partition_test_100_iterations") {
   deps = [
     ":config",
@@ -243,10 +185,7 @@
     dir_pw_unit_test,
   ]
   sources = [ "flash_partition_test.cc" ]
-  defines = [
-    "PW_FLASH_TEST_ITERATIONS=100",
-    "PW_FLASH_TEST_WRITE_SIZE=1",
-  ]
+  defines = [ "PW_FLASH_TEST_ITERATIONS=100" ]
 }
 
 pw_source_set("flash_partition_test_2_iterations") {
@@ -258,40 +197,7 @@
     dir_pw_unit_test,
   ]
   sources = [ "flash_partition_test.cc" ]
-  defines = [
-    "PW_FLASH_TEST_ITERATIONS=2",
-    "PW_FLASH_TEST_WRITE_SIZE=1",
-  ]
-}
-
-pw_source_set("flash_partition_test_100_iterations_256_write") {
-  deps = [
-    ":config",
-    ":flash_test_partition",
-    dir_pw_kvs,
-    dir_pw_log,
-    dir_pw_unit_test,
-  ]
-  sources = [ "flash_partition_test.cc" ]
-  defines = [
-    "PW_FLASH_TEST_ITERATIONS=100",
-    "PW_FLASH_TEST_WRITE_SIZE=256",
-  ]
-}
-
-pw_source_set("flash_partition_test_2_iterations_256_write") {
-  deps = [
-    ":config",
-    ":flash_test_partition",
-    dir_pw_kvs,
-    dir_pw_log,
-    dir_pw_unit_test,
-  ]
-  sources = [ "flash_partition_test.cc" ]
-  defines = [
-    "PW_FLASH_TEST_ITERATIONS=2",
-    "PW_FLASH_TEST_WRITE_SIZE=256",
-  ]
+  defines = [ "PW_FLASH_TEST_ITERATIONS=2" ]
 }
 
 pw_source_set("key_value_store_initialized_test") {
@@ -307,24 +213,10 @@
   sources = [ "key_value_store_initialized_test.cc" ]
 }
 
-pw_source_set("key_value_store_fuzz_test") {
-  deps = [
-    ":crc16",
-    ":flash_test_partition",
-    ":pw_kvs",
-    dir_pw_bytes,
-    dir_pw_checksum,
-    dir_pw_log,
-    dir_pw_unit_test,
-  ]
-  sources = [ "key_value_store_fuzz_test.cc" ]
-}
-
 pw_source_set("test_key_value_store_test") {
   deps = [
     ":pw_kvs",
     ":test_key_value_store",
-    "$dir_pw_sync:borrow",
     dir_pw_unit_test,
   ]
   sources = [ "test_key_value_store_test.cc" ]
@@ -348,41 +240,23 @@
     ":alignment_test",
     ":checksum_test",
     ":converts_to_span_test",
+    ":entry_test",
+    ":entry_cache_test",
+    ":flash_partition_small_test",
+    ":flash_partition_64_alignment_test",
+    ":flash_partition_256_alignment_test",
+    ":key_value_store_test",
+    ":key_value_store_small_flash_test",
+    ":key_value_store_64_alignment_flash_test",
+    ":key_value_store_256_alignment_flash_test",
+    ":key_value_store_binary_format_test",
+    ":key_value_store_fuzz_test",
+    ":key_value_store_map_test",
+    ":fake_flash_test_key_value_store_test",
+    ":sectors_test",
     ":key_test",
+    ":key_value_store_wear_test",
   ]
-
-  if (defined(pw_toolchain_SCOPE.is_host_toolchain) &&
-      pw_toolchain_SCOPE.is_host_toolchain) {
-    # TODO(pwbug/196): KVS tests are not compatible with device builds as they
-    # use features such as std::map and are computationally expensive. Solving
-    # this requires a more complex capabilities-based build and configuration
-    # system which allowing enabling specific tests for targets that support
-    # them and modifying test parameters for different targets.
-
-    tests += [
-      ":entry_test",
-      ":entry_cache_test",
-      ":flash_partition_1_stream_test",
-      ":flash_partition_1_alignment_test",
-      ":flash_partition_16_alignment_test",
-      ":flash_partition_64_alignment_test",
-      ":flash_partition_256_alignment_test",
-      ":flash_partition_256_write_size_test",
-      ":key_value_store_test",
-      ":key_value_store_1_alignment_flash_test",
-      ":key_value_store_16_alignment_flash_test",
-      ":key_value_store_64_alignment_flash_test",
-      ":key_value_store_256_alignment_flash_test",
-      ":key_value_store_fuzz_1_alignment_flash_test",
-      ":key_value_store_fuzz_64_alignment_flash_test",
-      ":key_value_store_binary_format_test",
-      ":key_value_store_put_test",
-      ":key_value_store_map_test",
-      ":key_value_store_wear_test",
-      ":fake_flash_test_key_value_store_test",
-      ":sectors_test",
-    ]
-  }
 }
 
 pw_test("alignment_test") {
@@ -423,28 +297,10 @@
   sources = [ "entry_cache_test.cc" ]
 }
 
-pw_test("flash_partition_1_stream_test") {
+pw_test("flash_partition_small_test") {
   deps = [
     ":fake_flash",
-    ":fake_flash_1_aligned_partition",
-    ":flash_partition_stream_test",
-    dir_pw_log,
-  ]
-}
-
-pw_test("flash_partition_1_alignment_test") {
-  deps = [
-    ":fake_flash",
-    ":fake_flash_1_aligned_partition",
-    ":flash_partition_test_100_iterations",
-    dir_pw_log,
-  ]
-}
-
-pw_test("flash_partition_16_alignment_test") {
-  deps = [
-    ":fake_flash",
-    ":fake_flash_16_aligned_partition",
+    ":fake_flash_small_partition",
     ":flash_partition_test_100_iterations",
     dir_pw_log,
   ]
@@ -468,15 +324,6 @@
   ]
 }
 
-pw_test("flash_partition_256_write_size_test") {
-  deps = [
-    ":fake_flash",
-    ":fake_flash_1_aligned_partition",
-    ":flash_partition_test_100_iterations_256_write",
-    dir_pw_log,
-  ]
-}
-
 pw_test("key_value_store_test") {
   deps = [
     ":crc16",
@@ -489,16 +336,9 @@
   sources = [ "key_value_store_test.cc" ]
 }
 
-pw_test("key_value_store_1_alignment_flash_test") {
+pw_test("key_value_store_small_flash_test") {
   deps = [
-    ":fake_flash_1_aligned_partition",
-    ":key_value_store_initialized_test",
-  ]
-}
-
-pw_test("key_value_store_16_alignment_flash_test") {
-  deps = [
-    ":fake_flash_16_aligned_partition",
+    ":fake_flash_small_partition",
     ":key_value_store_initialized_test",
   ]
 }
@@ -517,20 +357,6 @@
   ]
 }
 
-pw_test("key_value_store_fuzz_1_alignment_flash_test") {
-  deps = [
-    ":fake_flash_1_aligned_partition",
-    ":key_value_store_fuzz_test",
-  ]
-}
-
-pw_test("key_value_store_fuzz_64_alignment_flash_test") {
-  deps = [
-    ":fake_flash_64_aligned_partition",
-    ":key_value_store_fuzz_test",
-  ]
-}
-
 pw_test("key_value_store_binary_format_test") {
   deps = [
     ":crc16",
@@ -542,21 +368,20 @@
   sources = [ "key_value_store_binary_format_test.cc" ]
 }
 
-pw_test("key_value_store_put_test") {
+pw_test("key_value_store_fuzz_test") {
   deps = [
     ":crc16",
     ":fake_flash",
     ":pw_kvs",
     ":test_partition",
   ]
-  sources = [ "key_value_store_put_test.cc" ]
+  sources = [ "key_value_store_fuzz_test.cc" ]
 }
 
 pw_test("fake_flash_test_key_value_store_test") {
   deps = [
     ":fake_flash_test_key_value_store",
     ":test_key_value_store_test",
-    "$dir_pw_sync:borrow",
   ]
 }
 
diff --git a/pw_kvs/CMakeLists.txt b/pw_kvs/CMakeLists.txt
index 8e20e5b..3d48927 100644
--- a/pw_kvs/CMakeLists.txt
+++ b/pw_kvs/CMakeLists.txt
@@ -14,29 +14,14 @@
 
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
 
-pw_add_module_config(pw_kvs_CONFIG)
-
 pw_auto_add_simple_module(pw_kvs
   PUBLIC_DEPS
     pw_containers
     pw_status
-    pw_sync.borrow
   PRIVATE_DEPS
     pw_assert
     pw_bytes
     pw_checksum
-    ${pw_kvs_CONFIG}
     pw_log
-    pw_random
-    pw_stream
     pw_string
 )
-
-target_compile_definitions(
-  pw_kvs
-  PUBLIC PW_FLASH_TEST_SECTORS=6
-  PUBLIC PW_FLASH_TEST_SECTOR_SIZE=4096
-  PUBLIC PW_FLASH_TEST_ALIGNMENT=1
-  PUBLIC PW_FLASH_TEST_ITERATIONS=2
-  PUBLIC PW_FLASH_TEST_WRITE_SIZE=256
-)
diff --git a/pw_kvs/OWNERS b/pw_kvs/OWNERS
deleted file mode 100644
index d96cbc6..0000000
--- a/pw_kvs/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-hepler@google.com
diff --git a/pw_kvs/alignment_test.cc b/pw_kvs/alignment_test.cc
index 71d1a8e..fca1a7f 100644
--- a/pw_kvs/alignment_test.cc
+++ b/pw_kvs/alignment_test.cc
@@ -175,8 +175,7 @@
 
   {
     AlignedWriterBuffer<64> writer(3, output);
-    writer.Write(std::as_bytes(std::span("What is this?")))
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    writer.Write(std::as_bytes(std::span("What is this?")));
     EXPECT_EQ(called_with_bytes, 0u);  // Buffer not full; no output yet.
   }
 
@@ -212,13 +211,12 @@
 
   {
     AlignedWriterBuffer<4> writer(3, output);
-    writer.Write(std::as_bytes(std::span("Everything is fine.")))
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    writer.Write(std::as_bytes(std::span("Everything is fine.")));
     output.state = OutputWithErrorInjection::kBreakOnNext;
     EXPECT_EQ(Status::Unknown(),
               writer.Write(std::as_bytes(std::span("No more writes, okay?")))
                   .status());
-    writer.Flush().IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    writer.Flush();
   }
 }
 
diff --git a/pw_kvs/checksum.cc b/pw_kvs/checksum.cc
index f3e28f5..4215406 100644
--- a/pw_kvs/checksum.cc
+++ b/pw_kvs/checksum.cc
@@ -24,11 +24,6 @@
   if (checksum.size() < size_bytes()) {
     return Status::InvalidArgument();
   }
-  if (state_.empty()) {
-    // Special case: state_.data is a nullptr, so calling std::memcmp would be
-    // undefined behavior.
-    return OkStatus();
-  }
   if (std::memcmp(state_.data(), checksum.data(), size_bytes()) != 0) {
     return Status::DataLoss();
   }
diff --git a/pw_kvs/converts_to_span_test.cc b/pw_kvs/converts_to_span_test.cc
index 33d59c6..68fc5b2 100644
--- a/pw_kvs/converts_to_span_test.cc
+++ b/pw_kvs/converts_to_span_test.cc
@@ -46,8 +46,8 @@
 static_assert(!ConvertsToSpan<const int[]>());
 static_assert(!ConvertsToSpan<bool (&)[]>());
 static_assert(!ConvertsToSpan<const int (&)[]>());
-static_assert(!ConvertsToSpan<bool (&&)[]>());
-static_assert(!ConvertsToSpan<const int (&&)[]>());
+static_assert(!ConvertsToSpan<bool(&&)[]>());
+static_assert(!ConvertsToSpan<const int(&&)[]>());
 
 // C arrays convert to span.
 static_assert(ConvertsToSpan<std::array<int, 5>>());
@@ -61,10 +61,10 @@
 static_assert(ConvertsToSpan<char (&)[35]>());
 static_assert(ConvertsToSpan<const int (&)[35]>());
 
-static_assert(ConvertsToSpan<bool (&&)[1]>());
-static_assert(ConvertsToSpan<bool (&&)[1]>());
-static_assert(ConvertsToSpan<char (&&)[35]>());
-static_assert(ConvertsToSpan<const int (&&)[35]>());
+static_assert(ConvertsToSpan<bool(&&)[1]>());
+static_assert(ConvertsToSpan<bool(&&)[1]>());
+static_assert(ConvertsToSpan<char(&&)[35]>());
+static_assert(ConvertsToSpan<const int(&&)[35]>());
 
 // Container types convert to span.
 struct FakeContainer {
@@ -96,7 +96,7 @@
 static_assert(ConvertsToSpan<std::span<bool>&&>());
 
 // These tests for the make_span function were copied from Chromium:
-// https://chromium.googlesource.com/chromium/src/+/main/base/containers/span_unittest.cc
+// https://chromium.googlesource.com/chromium/src/+/master/base/containers/span_unittest.cc
 
 TEST(SpanTest, MakeSpanFromDataAndSize) {
   int* nullint = nullptr;
diff --git a/pw_kvs/docs.rst b/pw_kvs/docs.rst
index fd21996..7504566 100644
--- a/pw_kvs/docs.rst
+++ b/pw_kvs/docs.rst
@@ -49,7 +49,7 @@
 entries for that key are not modified or removed from flash storage, until
 garbage collection reclaims the “stale” entries.
 
-`Garbage collection`_ is done by copying any currently valid KV entries in the
+`Garbage collection`_ is done by coping any currently valid KV entries in the
 sector to be garbage collected to a different sector and then erasing the
 sector.
 
@@ -71,21 +71,17 @@
 
 Writes to flash must have a start address that is a multiple of the flash
 write alignment. Write size must also be a multiple of flash write alignment.
-Write alignment varies by flash device and partition type. Reads from flash do
-not have any address or size alignment requirement - reads always have a
-minimum alignment of 1.
+Write alignment varies by flash device and partition type. FlashPartitions may
+have a different alignment than the FlashMemory they are part of, so long as
+the partition's alignment is a multiple of the alignment for the FlashMemory.
+Reads from flash do not have any address or size alignment requirement - reads
+always have a minimum alignment of 1.
 
-FlashPartitions may have a different alignment than the FlashMemory they are
-part of, so long as the partition's alignment is a multiple of the alignment
-for the FlashMemory.
-
-Sectors are the minimum erase size for both FlashMemory and FlashPartition.
-FlashPartitions may have a different logical sector size than the FlashMemory
-they are part of. Partition logical sectors may be smaller due to partition
-overhead (encryption, wear tracking, etc) or larger due to combining raw
-sectors into larger logical sectors.
-
-FlashPartition supports access via NonSeekableWriter and SeekableReader.
+Flash sectors are the minimum erase size for both FlashMemory and
+FlashPartition. FlashPartitions may have a different logical sector size than
+the FlashMemory they are part of. Partition logical sectors may be smaller due
+to partition overhead (encryption, wear tracking, etc) or larger due to
+combining raw sectors into larger logical sectors.
 
 Size report
 -----------
diff --git a/pw_kvs/entry_cache.cc b/pw_kvs/entry_cache.cc
index ece008b..e71d42e 100644
--- a/pw_kvs/entry_cache.cc
+++ b/pw_kvs/entry_cache.cc
@@ -118,9 +118,9 @@
 }
 
 EntryMetadata EntryCache::AddNew(const KeyDescriptor& descriptor,
-                                 Address address) const {
+                                 Address entry_address) const {
   // TODO(hepler): DCHECK(!full());
-  Address* first_address = ResetAddresses(descriptors_.size(), address);
+  Address* first_address = ResetAddresses(descriptors_.size(), entry_address);
   descriptors_.push_back(descriptor);
   return EntryMetadata(descriptors_.back(), std::span(first_address, 1));
 }
diff --git a/pw_kvs/entry_test.cc b/pw_kvs/entry_test.cc
index 76def54..f9496e3 100644
--- a/pw_kvs/entry_test.cc
+++ b/pw_kvs/entry_test.cc
@@ -410,10 +410,10 @@
 constexpr EntryFormat kFormatWithSum{kMagicWithSum, &sum_checksum};
 constexpr internal::EntryFormats kFormatsWithSum(kFormatWithSum);
 
-template <size_t kAlignment>
+template <size_t alignment>
 constexpr auto MakeNewFormatWithSumEntry() {
-  constexpr uint8_t alignment_units = (kAlignment + 15) / 16 - 1;
-  constexpr size_t size = AlignUp(kEntryWithoutPadding1.size(), kAlignment);
+  constexpr uint8_t alignment_units = (alignment + 15) / 16 - 1;
+  constexpr size_t size = AlignUp(kEntryWithoutPadding1.size(), alignment);
 
   constexpr uint32_t checksum =
       ByteSum(bytes::Concat(kFormatWithSum.magic)) + 0 /* checksum */ +
@@ -428,7 +428,7 @@
                     uint8_t(kKey1.size()),     // key length
                     uint16_t(kValue1.size()),  // value size
                     kTransactionId1 + 1);      // transaction ID
-  constexpr size_t padding = Padding(kEntryWithoutPadding1.size(), kAlignment);
+  constexpr size_t padding = Padding(kEntryWithoutPadding1.size(), alignment);
   return bytes::Concat(
       kNewHeader1, kKey1, kValue1, bytes::Initialized<padding>(0));
 }
diff --git a/pw_kvs/fake_flash_memory.cc b/pw_kvs/fake_flash_memory.cc
index 0f9b601..4c30913 100644
--- a/pw_kvs/fake_flash_memory.cc
+++ b/pw_kvs/fake_flash_memory.cc
@@ -12,7 +12,7 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-#define PW_LOG_MODULE_NAME "PW_FLASH"
+#define PW_LOG_MODULE_NAME "KVS"
 #define PW_LOG_LEVEL PW_KVS_LOG_LEVEL
 
 #include "pw_kvs/fake_flash_memory.h"
@@ -102,6 +102,13 @@
     return StatusWithSize::InvalidArgument();
   }
 
+  if (data.size() > sector_size_bytes() - (address % sector_size_bytes())) {
+    PW_LOG_ERROR("Write crosses sector boundary; address %x, size %u B",
+                 unsigned(address),
+                 unsigned(data.size()));
+    return StatusWithSize::InvalidArgument();
+  }
+
   if (address + data.size() > sector_count() * sector_size_bytes()) {
     PW_LOG_ERROR(
         "Write beyond end of memory; address %x, size %u B, max address %x",
diff --git a/pw_kvs/fake_flash_test_key_value_store.cc b/pw_kvs/fake_flash_test_key_value_store.cc
index e6f1778..49a22cd 100644
--- a/pw_kvs/fake_flash_test_key_value_store.cc
+++ b/pw_kvs/fake_flash_test_key_value_store.cc
@@ -17,8 +17,6 @@
 #include "pw_kvs/flash_memory.h"
 #include "pw_kvs/key_value_store.h"
 #include "pw_kvs/test_key_value_store.h"
-#include "pw_sync/borrow.h"
-#include "pw_sync/virtual_basic_lockable.h"
 
 namespace pw::kvs {
 
@@ -65,16 +63,14 @@
 
 KeyValueStoreBuffer<kKvsTestMaxEntries, kFlashTestSectors, kKvsTestRedundancy>
     test_kvs(&test_partition, kvs_format);
-sync::Borrowable<KeyValueStore> borrowable_kvs(test_kvs,
-                                               sync::NoOpLock::Instance());
 
 }  // namespace
 
-sync::Borrowable<KeyValueStore>& TestKvs() {
+KeyValueStore& TestKvs() {
   if (!test_kvs.initialized()) {
-    test_kvs.Init().IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    test_kvs.Init();
   }
 
-  return borrowable_kvs;
+  return test_kvs;
 }
 }  // namespace pw::kvs
diff --git a/pw_kvs/fake_flash_test_partition.cc b/pw_kvs/fake_flash_test_partition.cc
index cfe8982..c3298ab 100644
--- a/pw_kvs/fake_flash_test_partition.cc
+++ b/pw_kvs/fake_flash_test_partition.cc
@@ -20,16 +20,16 @@
 
 namespace {
 
-#if !defined(PW_FLASH_TEST_SECTORS) || (PW_FLASH_TEST_SECTORS <= 0)
-#error PW_FLASH_TEST_SECTORS must be defined and > 0
+#ifndef PW_FLASH_TEST_SECTORS
+#define PW_FLASH_TEST_SECTORS 6U
 #endif  // PW_FLASH_TEST_SECTORS
 
-#if !defined(PW_FLASH_TEST_SECTOR_SIZE) || (PW_FLASH_TEST_SECTOR_SIZE <= 0)
-#error PW_FLASH_TEST_SECTOR_SIZE must be defined and > 0
+#ifndef PW_FLASH_TEST_SECTOR_SIZE
+#define PW_FLASH_TEST_SECTOR_SIZE (4 * 1024U)
 #endif  // PW_FLASH_TEST_SECTOR_SIZE
 
-#if !defined(PW_FLASH_TEST_ALIGNMENT) || (PW_FLASH_TEST_ALIGNMENT <= 0)
-#error PW_FLASH_TEST_ALIGNMENT must be defined and > 0
+#ifndef PW_FLASH_TEST_ALIGNMENT
+#define PW_FLASH_TEST_ALIGNMENT 16U
 #endif  // PW_FLASH_TEST_ALIGNMENT
 
 constexpr size_t kFlashTestSectors = PW_FLASH_TEST_SECTORS;
diff --git a/pw_kvs/flash_memory.cc b/pw_kvs/flash_memory.cc
index 217ad16..8424496 100644
--- a/pw_kvs/flash_memory.cc
+++ b/pw_kvs/flash_memory.cc
@@ -12,7 +12,7 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-#define PW_LOG_MODULE_NAME "PW_FLASH"
+#define PW_LOG_MODULE_NAME "KVS"
 #define PW_LOG_LEVEL PW_KVS_LOG_LEVEL
 
 #include "pw_kvs/flash_memory.h"
@@ -21,7 +21,7 @@
 #include <cinttypes>
 #include <cstring>
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_kvs_private/config.h"
 #include "pw_log/log.h"
 #include "pw_status/status_with_size.h"
@@ -31,44 +31,6 @@
 
 using std::byte;
 
-#if PW_CXX_STANDARD_IS_SUPPORTED(17)
-
-Status FlashPartition::Writer::DoWrite(ConstByteSpan data) {
-  if (partition_.size_bytes() <= position_) {
-    return Status::OutOfRange();
-  }
-  if (data.size_bytes() > (partition_.size_bytes() - position_)) {
-    return Status::ResourceExhausted();
-  }
-  if (data.size_bytes() == 0) {
-    return OkStatus();
-  }
-
-  const StatusWithSize sws = partition_.Write(position_, data);
-  if (sws.ok()) {
-    position_ += data.size_bytes();
-  }
-  return sws.status();
-}
-
-StatusWithSize FlashPartition::Reader::DoRead(ByteSpan data) {
-  if (position_ >= partition_.size_bytes()) {
-    return StatusWithSize::OutOfRange();
-  }
-
-  size_t bytes_to_read =
-      std::min(data.size_bytes(), partition_.size_bytes() - position_);
-
-  const StatusWithSize sws =
-      partition_.Read(position_, data.first(bytes_to_read));
-  if (sws.ok()) {
-    position_ += bytes_to_read;
-  }
-  return sws;
-}
-
-#endif  // PW_CXX_STANDARD_IS_SUPPORTED(17)
-
 StatusWithSize FlashPartition::Output::DoWrite(std::span<const byte> data) {
   PW_TRY_WITH_SIZE(flash_.Write(address_, data));
   address_ += data.size();
@@ -188,11 +150,9 @@
 Status FlashPartition::CheckBounds(Address address, size_t length) const {
   if (address + length > size_bytes()) {
     PW_LOG_ERROR(
-        "FlashPartition - Attempted access (address: %u length: %u), exceeds "
-        "partition size %u bytes",
+        "Attempted out-of-bound flash memory access (address: %u length: %u)",
         unsigned(address),
-        unsigned(length),
-        unsigned(size_bytes()));
+        unsigned(length));
     return Status::OutOfRange();
   }
   return OkStatus();
diff --git a/pw_kvs/flash_partition_stream_test.cc b/pw_kvs/flash_partition_stream_test.cc
deleted file mode 100644
index 47fd974..0000000
--- a/pw_kvs/flash_partition_stream_test.cc
+++ /dev/null
@@ -1,396 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <algorithm>
-#include <array>
-#include <cstddef>
-#include <cstring>
-#include <span>
-
-#include "gtest/gtest.h"
-#include "public/pw_kvs/flash_memory.h"
-#include "pw_kvs/fake_flash_memory.h"
-#include "pw_kvs/flash_memory.h"
-#include "pw_kvs_private/config.h"
-#include "pw_log/log.h"
-#include "pw_random/xor_shift.h"
-
-#if PW_CXX_STANDARD_IS_SUPPORTED(17)
-
-#ifndef PW_FLASH_TEST_ALIGNMENT
-#define PW_FLASH_TEST_ALIGNMENT 1
-#endif
-
-namespace pw::kvs {
-namespace {
-
-class FlashStreamTest : public ::testing::Test {
- protected:
-  FlashStreamTest() : flash_(kFlashAlignment), partition_(&flash_) {}
-
-  void InitBufferToFill(ByteSpan buffer_span, char fill) {
-    std::memset(buffer_span.data(), fill, buffer_span.size_bytes());
-  }
-
-  void InitBufferToRandom(ByteSpan buffer_span, uint64_t seed) {
-    random::XorShiftStarRng64 rng(seed);
-
-    std::memset(buffer_span.data(),
-                static_cast<int>(flash_.erased_memory_content()),
-                buffer_span.size());
-    ASSERT_EQ(OkStatus(), rng.Get(buffer_span).status());
-  }
-
-  void VerifyFlash(ConstByteSpan verify_bytes, size_t offset = 0) {
-    // Should be defined as same size.
-    EXPECT_EQ(source_buffer_.size(), flash_.buffer().size_bytes());
-
-    // Can't allow it to march off the end of source_buffer_.
-    ASSERT_LE((verify_bytes.size_bytes() + offset), source_buffer_.size());
-
-    for (size_t i = 0; i < verify_bytes.size_bytes(); i++) {
-      ASSERT_EQ(source_buffer_[i + offset], verify_bytes[i]);
-    }
-  }
-
-  void VerifyFlashContent(ConstByteSpan verify_bytes, size_t offset = 0) {
-    // Can't allow it to march off the end of source_buffer_.
-    ASSERT_LE((verify_bytes.size_bytes() + offset),
-              flash_.buffer().size_bytes());
-
-    for (size_t i = 0; i < verify_bytes.size_bytes(); i++) {
-      ASSERT_EQ(flash_.buffer()[i + offset], verify_bytes[i]);
-    }
-  }
-
-  void DoWriteInChunks(size_t chunk_write_size_bytes, uint64_t seed) {
-    InitBufferToRandom(std::span(source_buffer_), seed);
-    ConstByteSpan write_data = std::span(source_buffer_);
-
-    ASSERT_EQ(OkStatus(), partition_.Erase());
-
-    FlashPartition::Writer writer(partition_);
-
-    while (write_data.size_bytes() > 0) {
-      size_t offset_before_write = writer.Tell();
-      size_t write_chunk_size =
-          std::min(chunk_write_size_bytes, write_data.size_bytes());
-
-      ConstByteSpan write_chunk = write_data.first(write_chunk_size);
-      ASSERT_EQ(OkStatus(), writer.Write(write_chunk));
-      VerifyFlashContent(write_chunk, offset_before_write);
-
-      write_data = write_data.subspan(write_chunk_size);
-
-      ASSERT_EQ(writer.ConservativeWriteLimit(), write_data.size_bytes());
-    }
-
-    VerifyFlashContent(std::span(source_buffer_));
-  }
-
-  void DoReadInChunks(size_t chunk_read_size_bytes,
-                      uint64_t seed,
-                      size_t start_offset,
-                      size_t bytes_to_read) {
-    InitBufferToRandom(flash_.buffer(), seed);
-
-    ASSERT_LE((start_offset + bytes_to_read), flash_.buffer().size_bytes());
-
-    FlashPartition::Reader reader(partition_);
-    ASSERT_EQ(reader.ConservativeReadLimit(), flash_.buffer().size_bytes());
-
-    ASSERT_EQ(reader.Seek(start_offset), OkStatus());
-    ASSERT_EQ(reader.ConservativeReadLimit(),
-              flash_.buffer().size_bytes() - start_offset);
-
-    while (bytes_to_read > 0) {
-      ASSERT_EQ(start_offset, reader.Tell());
-
-      size_t chunk_size = std::min(chunk_read_size_bytes, bytes_to_read);
-
-      ByteSpan read_chunk = std::span(source_buffer_).first(chunk_size);
-      InitBufferToFill(read_chunk, 0);
-      ASSERT_EQ(read_chunk.size_bytes(), chunk_size);
-
-      auto result = reader.Read(read_chunk);
-      ASSERT_EQ(result.status(), OkStatus());
-      ASSERT_EQ(result.value().size_bytes(), chunk_size);
-      VerifyFlashContent(read_chunk, start_offset);
-
-      start_offset += chunk_size;
-      bytes_to_read -= chunk_size;
-
-      ASSERT_EQ(reader.ConservativeReadLimit(),
-                flash_.buffer().size_bytes() - start_offset);
-    }
-  }
-
-  static constexpr size_t kFlashAlignment = PW_FLASH_TEST_ALIGNMENT;
-  static constexpr size_t kSectorSize = 2048;
-  static constexpr size_t kSectorCount = 2;
-  static constexpr size_t kFPDataSize = (kSectorCount * kSectorSize);
-
-  FakeFlashMemoryBuffer<kSectorSize, kSectorCount> flash_;
-  FlashPartition partition_;
-  std::array<std::byte, kFPDataSize> source_buffer_;
-  size_t size_bytes_;
-};
-
-TEST_F(FlashStreamTest, Write_1_Byte_Chunks) {
-  // Write in 1 byte chunks.
-  DoWriteInChunks(1, 0xab1234);
-}
-
-TEST_F(FlashStreamTest, Write_5_Byte_Chunks) {
-  // Write in 5 byte chunks.
-  DoWriteInChunks(5, 0xdc2274);
-}
-
-TEST_F(FlashStreamTest, Write_16_Byte_Chunks) {
-  // Write in 16 byte chunks.
-  DoWriteInChunks(16, 0xef8224);
-}
-
-TEST_F(FlashStreamTest, Write_255_Byte_Chunks) {
-  // Write in 255 byte chunks.
-  DoWriteInChunks(255, 0xffe1348);
-}
-
-TEST_F(FlashStreamTest, Write_256_Byte_Chunks) {
-  // Write in 256 byte chunks.
-  DoWriteInChunks(256, 0xe11234);
-}
-
-TEST_F(FlashStreamTest, Read_1_Byte_Chunks) {
-  // Read in 1 byte chunks.
-  DoReadInChunks(1, 0x7643ff, 0, flash_.buffer().size_bytes());
-}
-
-TEST_F(FlashStreamTest, Read_16_Byte_Chunks) {
-  // Read in 16 byte chunks.
-  DoReadInChunks(16, 0x61e234, 0, flash_.buffer().size_bytes());
-}
-
-TEST_F(FlashStreamTest, Read_255_Byte_Chunks) {
-  // Read in 256 byte chunks.
-  DoReadInChunks(255, 0xe13514, 0, flash_.buffer().size_bytes());
-}
-
-TEST_F(FlashStreamTest, Read_256_Byte_Chunks) {
-  // Read in 256 byte chunks.
-  DoReadInChunks(256, 0xe11234, 0, flash_.buffer().size_bytes());
-}
-
-TEST_F(FlashStreamTest, Read_256_Byte_Chunks_With_Offset) {
-  // Read in 256 byte chunks.
-  DoReadInChunks(256, 0xfffe34, 1024, (flash_.buffer().size_bytes() - 1024));
-}
-
-TEST_F(FlashStreamTest, Read_Multiple_Seeks) {
-  static const size_t kSeekReadSizeBytes = 512;
-  static const size_t kSeekReadIterations = 4;
-  ASSERT_GE(flash_.buffer().size_bytes(),
-            (kSeekReadIterations * (2 * kSeekReadSizeBytes)));
-
-  InitBufferToRandom(flash_.buffer(), 0xffde176);
-  FlashPartition::Reader reader(partition_);
-
-  for (size_t i = 0; i < kSeekReadIterations; i++) {
-    size_t start_offset = kSeekReadSizeBytes + (i * 2 * kSeekReadSizeBytes);
-    ASSERT_EQ(reader.Seek(start_offset), OkStatus());
-    ASSERT_EQ(start_offset, reader.Tell());
-
-    ByteSpan read_chunk = std::span(source_buffer_).first(kSeekReadSizeBytes);
-    InitBufferToFill(read_chunk, 0);
-
-    auto result = reader.Read(read_chunk);
-    ASSERT_EQ(result.status(), OkStatus());
-    ASSERT_EQ(result.value().size_bytes(), kSeekReadSizeBytes);
-    VerifyFlashContent(read_chunk, start_offset);
-    ASSERT_EQ(start_offset + kSeekReadSizeBytes, reader.Tell());
-  }
-}
-
-TEST_F(FlashStreamTest, Read_Seek_Forward_and_Back) {
-  static const size_t kSeekReadSizeBytes = 256;
-  static const size_t kTotalIterations = 3;
-  static const size_t kSeekReadIterations =
-      flash_.buffer().size_bytes() / (2 * kSeekReadSizeBytes);
-
-  InitBufferToRandom(flash_.buffer(), 0xffde176);
-  FlashPartition::Reader reader(partition_);
-
-  for (size_t outer_count = 0; outer_count < kTotalIterations; outer_count++) {
-    // Seek and read going forward.
-    for (size_t i = 0; i < kSeekReadIterations; i++) {
-      size_t start_offset = kSeekReadSizeBytes + (i * 2 * kSeekReadSizeBytes);
-      ASSERT_EQ(reader.Seek(start_offset), OkStatus());
-      ASSERT_EQ(start_offset, reader.Tell());
-
-      ByteSpan read_chunk = std::span(source_buffer_).first(kSeekReadSizeBytes);
-      InitBufferToFill(read_chunk, 0);
-
-      auto result = reader.Read(read_chunk);
-      ASSERT_EQ(result.status(), OkStatus());
-      ASSERT_EQ(result.value().size_bytes(), kSeekReadSizeBytes);
-      VerifyFlashContent(read_chunk, start_offset);
-      ASSERT_EQ(start_offset + kSeekReadSizeBytes, reader.Tell());
-    }
-
-    // Seek and read going backward.
-    for (size_t j = (kSeekReadIterations * 2); j > 0; j--) {
-      size_t start_offset = (j - 1) * kSeekReadSizeBytes;
-      ASSERT_EQ(reader.Seek(start_offset), OkStatus());
-      ASSERT_EQ(start_offset, reader.Tell());
-      ASSERT_GE(reader.ConservativeReadLimit(), kSeekReadSizeBytes);
-
-      ByteSpan read_chunk = std::span(source_buffer_).first(kSeekReadSizeBytes);
-      InitBufferToFill(read_chunk, 0);
-
-      auto result = reader.Read(read_chunk);
-      ASSERT_EQ(result.status(), OkStatus());
-      ASSERT_EQ(result.value().size_bytes(), kSeekReadSizeBytes);
-      VerifyFlashContent(read_chunk, start_offset);
-      ASSERT_EQ(start_offset + kSeekReadSizeBytes, reader.Tell());
-    }
-  }
-}
-
-TEST_F(FlashStreamTest, Read_Past_End) {
-  InitBufferToRandom(flash_.buffer(), 0xcccde176);
-  FlashPartition::Reader reader(partition_);
-
-  static const size_t kBytesForFinalRead = 50;
-
-  ByteSpan read_chunk = std::span(source_buffer_)
-                            .first(source_buffer_.size() - kBytesForFinalRead);
-
-  auto result = reader.Read(read_chunk);
-  ASSERT_EQ(result.status(), OkStatus());
-  ASSERT_EQ(result.value().size_bytes(), read_chunk.size_bytes());
-  ASSERT_EQ(reader.Tell(), read_chunk.size_bytes());
-  ASSERT_EQ(reader.ConservativeReadLimit(), kBytesForFinalRead);
-  ASSERT_EQ(result.value().data(), read_chunk.data());
-  VerifyFlashContent(read_chunk);
-
-  result = reader.Read(read_chunk);
-  ASSERT_EQ(result.status(), OkStatus());
-  ASSERT_EQ(result.value().size_bytes(), kBytesForFinalRead);
-  ASSERT_EQ(reader.Tell(), flash_.buffer().size_bytes());
-  ASSERT_EQ(reader.ConservativeReadLimit(), 0U);
-  ASSERT_EQ(result.value().data(), read_chunk.data());
-  VerifyFlashContent(result.value(), read_chunk.size_bytes());
-}
-
-TEST_F(FlashStreamTest, Read_Past_End_After_Seek) {
-  InitBufferToRandom(flash_.buffer(), 0xddcde176);
-  FlashPartition::Reader reader(partition_);
-
-  static const size_t kBytesForFinalRead = 50;
-  size_t start_offset = flash_.buffer().size_bytes() - kBytesForFinalRead;
-  ASSERT_EQ(reader.Seek(start_offset), OkStatus());
-
-  ASSERT_EQ(start_offset, reader.Tell());
-  ASSERT_EQ(reader.ConservativeReadLimit(), kBytesForFinalRead);
-
-  ByteSpan read_chunk = std::span(source_buffer_);
-  auto result = reader.Read(read_chunk);
-  ASSERT_EQ(result.status(), OkStatus());
-  ASSERT_EQ(result.value().size_bytes(), kBytesForFinalRead);
-  ASSERT_EQ(reader.Tell(), flash_.buffer().size_bytes());
-  ASSERT_EQ(reader.ConservativeReadLimit(), 0U);
-  ASSERT_EQ(result.value().data(), read_chunk.data());
-  VerifyFlashContent(result.value(), start_offset);
-}
-
-TEST_F(FlashStreamTest, Read_Out_Of_Range) {
-  InitBufferToRandom(flash_.buffer(), 0x531de176);
-  FlashPartition::Reader reader(partition_);
-
-  ByteSpan read_chunk = std::span(source_buffer_);
-
-  auto result = reader.Read(read_chunk);
-  ASSERT_EQ(result.status(), OkStatus());
-  ASSERT_EQ(result.value().size_bytes(), read_chunk.size_bytes());
-  ASSERT_EQ(reader.Tell(), read_chunk.size_bytes());
-  ASSERT_EQ(reader.ConservativeReadLimit(), 0U);
-  ASSERT_EQ(result.value().data(), read_chunk.data());
-  VerifyFlashContent(read_chunk);
-
-  result = reader.Read(read_chunk);
-  ASSERT_EQ(result.status(), Status::OutOfRange());
-  ASSERT_EQ(reader.Tell(), flash_.buffer().size_bytes());
-  ASSERT_EQ(reader.ConservativeReadLimit(), 0U);
-}
-
-TEST_F(FlashStreamTest, Read_Out_Of_Range_After_Seek) {
-  InitBufferToRandom(flash_.buffer(), 0x8c94566);
-  FlashPartition::Reader reader(partition_);
-
-  ByteSpan read_chunk = std::span(source_buffer_);
-
-  ASSERT_EQ(reader.Seek(flash_.buffer().size_bytes()), OkStatus());
-  ASSERT_EQ(reader.Tell(), flash_.buffer().size_bytes());
-  ASSERT_EQ(reader.ConservativeReadLimit(), 0U);
-
-  auto result = reader.Read(read_chunk);
-  ASSERT_EQ(result.status(), Status::OutOfRange());
-  ASSERT_EQ(reader.Tell(), flash_.buffer().size_bytes());
-  ASSERT_EQ(reader.ConservativeReadLimit(), 0U);
-}
-
-TEST_F(FlashStreamTest, Reader_Seek_Ops) {
-  size_t kPartitionSizeBytes = flash_.buffer().size_bytes();
-  FlashPartition::Reader reader(partition_);
-
-  // Seek from 0 to past end.
-  ASSERT_EQ(reader.Seek(kPartitionSizeBytes + 5), Status::OutOfRange());
-  ASSERT_EQ(reader.Tell(), 0U);
-
-  // Seek to end then seek again going past end.
-  ASSERT_EQ(reader.Seek(0), OkStatus());
-  ASSERT_EQ(reader.Tell(), 0U);
-  ASSERT_EQ(reader.ConservativeReadLimit(), kPartitionSizeBytes);
-
-  ASSERT_EQ(reader.Seek(kPartitionSizeBytes,
-                        FlashPartition::Reader::Whence::kCurrent),
-            OkStatus());
-  ASSERT_EQ(reader.Tell(), kPartitionSizeBytes);
-  ASSERT_EQ(reader.ConservativeReadLimit(), 0U);
-
-  ASSERT_EQ(reader.Seek(5, FlashPartition::Reader::Whence::kCurrent),
-            Status::OutOfRange());
-  ASSERT_EQ(reader.Tell(), kPartitionSizeBytes);
-  ASSERT_EQ(reader.ConservativeReadLimit(), 0U);
-
-  // Seek to beginning then seek backwards going past start.
-  ASSERT_EQ(reader.Seek(0), OkStatus());
-  ASSERT_EQ(reader.Seek(-5, FlashPartition::Reader::Whence::kCurrent),
-            Status::OutOfRange());
-  ASSERT_EQ(reader.Tell(), 0U);
-  ASSERT_EQ(reader.ConservativeReadLimit(), kPartitionSizeBytes);
-}
-
-TEST_F(FlashStreamTest, Invald_Ops) {
-  FlashPartition::Reader reader(partition_);
-  ASSERT_EQ(reader.ConservativeWriteLimit(), 0U);
-
-  FlashPartition::Writer writer(partition_);
-  ASSERT_EQ(writer.ConservativeReadLimit(), 0U);
-}
-
-}  // namespace
-}  // namespace pw::kvs
-
-#endif  // PW_CXX_STANDARD_IS_SUPPORTED(17)
diff --git a/pw_kvs/flash_partition_test.cc b/pw_kvs/flash_partition_test.cc
index fbe4503..193f3d2 100644
--- a/pw_kvs/flash_partition_test.cc
+++ b/pw_kvs/flash_partition_test.cc
@@ -12,7 +12,6 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-#include <algorithm>
 #include <span>
 
 #include "gtest/gtest.h"
@@ -24,16 +23,11 @@
 namespace pw::kvs::PartitionTest {
 namespace {
 
-#if !defined(PW_FLASH_TEST_ITERATIONS) || (PW_FLASH_TEST_ITERATIONS <= 0)
-#error PW_FLASH_TEST_ITERATIONS must be defined and > 0
+#ifndef PW_FLASH_TEST_ITERATIONS
+#define PW_FLASH_TEST_ITERATIONS 2
 #endif  // PW_FLASH_TEST_ITERATIONS
 
-#if !defined(PW_FLASH_TEST_WRITE_SIZE) || (PW_FLASH_TEST_WRITE_SIZE <= 0)
-#error PW_FLASH_TEST_WRITE_SIZE must be defined and > 0
-#endif  // PW_FLASH_TEST_WRITE_SIZE
-
 constexpr size_t kTestIterations = PW_FLASH_TEST_ITERATIONS;
-constexpr size_t kTestWriteSize = PW_FLASH_TEST_WRITE_SIZE;
 
 size_t error_count = 0;
 
@@ -41,16 +35,15 @@
   uint8_t test_data[kMaxFlashAlignment];
   memset(test_data, fill_byte, sizeof(test_data));
 
-  const size_t write_size =
-      std::max(kTestWriteSize, partition.alignment_bytes());
+  const size_t alignment = partition.alignment_bytes();
 
   ASSERT_EQ(OkStatus(), partition.Erase(0, partition.sector_count()));
 
-  const size_t chunks_per_sector = partition.sector_size_bytes() / write_size;
+  const size_t chunks_per_sector = partition.sector_size_bytes() / alignment;
 
   // Fill partition sector by sector. Fill the sector with an integer number
-  // of write_size-size chunks. If the sector is not evenly divisible by
-  // write_size-size, the remainder is not written.
+  // of alignment-size chunks. If the sector is not evenly divisible by
+  // alignment-size, the remainder is not written.
   for (size_t sector_index = 0; sector_index < partition.sector_count();
        sector_index++) {
     FlashPartition::Address address =
@@ -59,10 +52,10 @@
     for (size_t chunk_index = 0; chunk_index < chunks_per_sector;
          chunk_index++) {
       StatusWithSize status =
-          partition.Write(address, as_bytes(std::span(test_data, write_size)));
+          partition.Write(address, as_bytes(std::span(test_data, alignment)));
       ASSERT_EQ(OkStatus(), status.status());
-      ASSERT_EQ(write_size, status.size());
-      address += write_size;
+      ASSERT_EQ(alignment, status.size());
+      address += alignment;
     }
   }
 
@@ -76,20 +69,20 @@
     for (size_t chunk_index = 0; chunk_index < chunks_per_sector;
          chunk_index++) {
       memset(test_data, 0, sizeof(test_data));
-      StatusWithSize status = partition.Read(address, write_size, test_data);
+      StatusWithSize status = partition.Read(address, alignment, test_data);
 
       EXPECT_EQ(OkStatus(), status.status());
-      EXPECT_EQ(write_size, status.size());
-      if (!status.ok() || (write_size != status.size())) {
+      EXPECT_EQ(alignment, status.size());
+      if (!status.ok() || (alignment != status.size())) {
         error_count++;
         PW_LOG_DEBUG("   Read Error [%s], %u of %u",
                      status.status().str(),
                      unsigned(status.size()),
-                     unsigned(write_size));
+                     unsigned(alignment));
         continue;
       }
 
-      for (size_t i = 0; i < write_size; i++) {
+      for (size_t i = 0; i < alignment; i++) {
         if (test_data[i] != fill_byte) {
           error_count++;
           PW_LOG_DEBUG(
@@ -102,7 +95,7 @@
         }
       }
 
-      address += write_size;
+      address += alignment;
     }
   }
 
@@ -192,14 +185,9 @@
   const size_t alignment = test_partition.alignment_bytes();
   const size_t sector_size_bytes = test_partition.sector_size_bytes();
 
-  EXPECT_LE(kTestWriteSize, kMaxFlashAlignment);
-  EXPECT_GT(kTestWriteSize, 0u);
-  EXPECT_EQ(kMaxFlashAlignment % kTestWriteSize, 0U);
-
   EXPECT_LE(alignment, kMaxFlashAlignment);
   EXPECT_GT(alignment, 0u);
   EXPECT_EQ(kMaxFlashAlignment % alignment, 0U);
-
   EXPECT_LE(kMaxFlashAlignment, sector_size_bytes);
   EXPECT_LE(sector_size_bytes % kMaxFlashAlignment, 0U);
 }
@@ -249,8 +237,7 @@
 
 TEST(FlashPartitionTest, IsErased) {
   FlashPartition& test_partition = FlashTestPartition();
-  const size_t write_size =
-      std::max(kTestWriteSize, test_partition.alignment_bytes());
+  const size_t alignment = test_partition.alignment_bytes();
 
   // Make sure the partition is big enough to do this test.
   ASSERT_GE(test_partition.size_bytes(), 3 * kMaxFlashAlignment);
@@ -267,7 +254,7 @@
   auto data_span = std::span(test_data);
 
   // Write the chunk with fill byte.
-  StatusWithSize status = test_partition.Write(write_size, as_bytes(data_span));
+  StatusWithSize status = test_partition.Write(alignment, as_bytes(data_span));
   ASSERT_EQ(OkStatus(), status.status());
   ASSERT_EQ(data_span.size_bytes(), status.size());
 
@@ -277,18 +264,18 @@
   // Check the chunk that was written.
   EXPECT_EQ(OkStatus(),
             test_partition.IsRegionErased(
-                write_size, data_span.size_bytes(), &is_erased));
+                alignment, data_span.size_bytes(), &is_erased));
   EXPECT_EQ(false, is_erased);
 
   // Check a region that starts erased but later has been written.
   EXPECT_EQ(OkStatus(),
-            test_partition.IsRegionErased(0, 2 * write_size, &is_erased));
+            test_partition.IsRegionErased(0, 2 * alignment, &is_erased));
   EXPECT_EQ(false, is_erased);
 
   // Check erased for a region smaller than kMaxFlashAlignment. This has been a
   // bug in the past.
   EXPECT_EQ(OkStatus(),
-            test_partition.IsRegionErased(0, write_size, &is_erased));
+            test_partition.IsRegionErased(0, alignment, &is_erased));
   EXPECT_EQ(true, is_erased);
 }
 
diff --git a/pw_kvs/flash_partition_with_stats.cc b/pw_kvs/flash_partition_with_stats.cc
index 0b88382..26b0eee 100644
--- a/pw_kvs/flash_partition_with_stats.cc
+++ b/pw_kvs/flash_partition_with_stats.cc
@@ -12,7 +12,7 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-#define PW_LOG_MODULE_NAME "PW_FLASH"
+#define PW_LOG_MODULE_NAME "KVS"
 #define PW_LOG_LEVEL PW_KVS_LOG_LEVEL
 
 #include "pw_kvs/flash_partition_with_stats.h"
@@ -27,8 +27,8 @@
 
 Status FlashPartitionWithStats::SaveStorageStats(const KeyValueStore& kvs,
                                                  const char* label) {
-  // If empty, saving stats is disabled so do not save any stats.
-  if (sector_counters_.empty()) {
+  // If size is zero saving stats is disabled so do not save any stats.
+  if (sector_counters_.size() == 0) {
     return OkStatus();
   }
 
diff --git a/pw_kvs/key_value_store.cc b/pw_kvs/key_value_store.cc
index 2b0bbe8..335efd5 100644
--- a/pw_kvs/key_value_store.cc
+++ b/pw_kvs/key_value_store.cc
@@ -22,7 +22,7 @@
 #include <cstring>
 #include <type_traits>
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_kvs_private/config.h"
 #include "pw_log/shorter.h"
 #include "pw_status/try.h"
@@ -462,8 +462,7 @@
 
   Entry entry;
   if (kvs_.ReadEntry(*iterator_, entry).ok()) {
-    entry.ReadKey(key_buffer_)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    entry.ReadKey(key_buffer_);
   }
 }
 
@@ -513,9 +512,9 @@
   return read_result;
 }
 
-Status KeyValueStore::FindEntry(Key key, EntryMetadata* metadata_out) const {
+Status KeyValueStore::FindEntry(Key key, EntryMetadata* found_entry) const {
   StatusWithSize find_result =
-      entry_cache_.Find(partition_, sectors_, formats_, key, metadata_out);
+      entry_cache_.Find(partition_, sectors_, formats_, key, found_entry);
 
   if (find_result.size() > 0u) {
     error_detected_ = true;
@@ -523,13 +522,13 @@
   return find_result.status();
 }
 
-Status KeyValueStore::FindExisting(Key key, EntryMetadata* metadata_out) const {
-  Status status = FindEntry(key, metadata_out);
+Status KeyValueStore::FindExisting(Key key, EntryMetadata* metadata) const {
+  Status status = FindEntry(key, metadata);
 
   // If the key's hash collides with an existing key or if the key is deleted,
   // treat it as if it is not in the KVS.
   if (status.IsAlreadyExists() ||
-      (status.ok() && metadata_out->state() == EntryState::kDeleted)) {
+      (status.ok() && metadata->state() == EntryState::kDeleted)) {
     return Status::NotFound();
   }
   return status;
@@ -738,11 +737,10 @@
 //
 //                 OK: Sector found with needed space.
 // RESOURCE_EXHAUSTED: No sector available with the needed space.
-Status KeyValueStore::GetSectorForWrite(
-    SectorDescriptor** sector,
-    size_t entry_size,
-    std::span<const Address> reserved_addresses) {
-  Status result = sectors_.FindSpace(sector, entry_size, reserved_addresses);
+Status KeyValueStore::GetSectorForWrite(SectorDescriptor** sector,
+                                        size_t entry_size,
+                                        std::span<const Address> reserved) {
+  Status result = sectors_.FindSpace(sector, entry_size, reserved);
 
   size_t gc_sector_count = 0;
   bool do_auto_gc = options_.gc_on_write != GargbageCollectOnWrite::kDisabled;
@@ -755,7 +753,7 @@
       do_auto_gc = false;
     }
     // Garbage collect and then try again to find the best sector.
-    Status gc_status = GarbageCollect(reserved_addresses);
+    Status gc_status = GarbageCollect(reserved);
     if (!gc_status.ok()) {
       if (gc_status.IsNotFound()) {
         // Not enough space, and no reclaimable bytes, this KVS is full!
@@ -764,7 +762,7 @@
       return gc_status;
     }
 
-    result = sectors_.FindSpace(sector, entry_size, reserved_addresses);
+    result = sectors_.FindSpace(sector, entry_size, reserved);
 
     gc_sector_count++;
     // Allow total sectors + 2 number of GC cycles so that once reclaimable
@@ -1209,8 +1207,7 @@
   INF("Starting KVS repair");
 
   DBG("Reinitialize KVS metadata");
-  InitializeMetadata()
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  InitializeMetadata();
 
   return FixErrors();
 }
diff --git a/pw_kvs/key_value_store_binary_format_test.cc b/pw_kvs/key_value_store_binary_format_test.cc
index dbaf44b..1fb046a 100644
--- a/pw_kvs/key_value_store_binary_format_test.cc
+++ b/pw_kvs/key_value_store_binary_format_test.cc
@@ -177,8 +177,7 @@
         kvs_(&partition_, default_format, kNoGcOptions) {}
 
   void InitFlashTo(std::span<const byte> contents) {
-    partition_.Erase()
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    partition_.Erase();
     std::memcpy(flash_.buffer().data(), contents.data(), contents.size());
   }
 
@@ -386,8 +385,7 @@
              kRecoveryNoGcOptions) {}
 
   void InitFlashTo(std::span<const byte> contents) {
-    partition_.Erase()
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    partition_.Erase();
     std::memcpy(flash_.buffer().data(), contents.data(), contents.size());
   }
 
@@ -644,8 +642,7 @@
                  {.magic = kNoChecksumMagic, .checksum = nullptr},
              }},
              kRecoveryNoGcOptions) {
-    partition_.Erase()
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    partition_.Erase();
     std::memcpy(flash_.buffer().data(),
                 kInitialContents.data(),
                 kInitialContents.size());
@@ -883,8 +880,7 @@
                  {.magic = kNoChecksumMagic, .checksum = nullptr},
              }},
              kRecoveryNoGcOptions) {
-    partition_.Erase()
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    partition_.Erase();
     std::memcpy(flash_.buffer().data(),
                 kInitialContents.data(),
                 kInitialContents.size());
@@ -937,8 +933,7 @@
         kvs_(&partition_,
              {.magic = kMagic, .checksum = &default_checksum},
              kRecoveryLazyGcOptions) {
-    partition_.Erase()
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    partition_.Erase();
     std::memcpy(flash_.buffer().data(),
                 kInitialContents.data(),
                 kInitialContents.size());
@@ -1030,8 +1025,7 @@
         kvs_(&partition_,
              {.magic = kMagic, .checksum = &default_checksum},
              kRecoveryLazyGcOptions) {
-    partition_.Erase()
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    partition_.Erase();
     std::memcpy(flash_.buffer().data(),
                 kInitialContents.data(),
                 kInitialContents.size());
diff --git a/pw_kvs/key_value_store_fuzz_test.cc b/pw_kvs/key_value_store_fuzz_test.cc
index d35a128..6a257d6 100644
--- a/pw_kvs/key_value_store_fuzz_test.cc
+++ b/pw_kvs/key_value_store_fuzz_test.cc
@@ -1,4 +1,4 @@
-// Copyright 2021 The Pigweed Authors
+// Copyright 2020 The Pigweed Authors
 //
 // 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
@@ -12,97 +12,66 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-#include <array>
-#include <cstring>
-#include <span>
-
 #include "gtest/gtest.h"
-#include "pw_checksum/crc16_ccitt.h"
 #include "pw_kvs/crc16_checksum.h"
-#include "pw_kvs/flash_memory.h"
-#include "pw_kvs/flash_test_partition.h"
+#include "pw_kvs/fake_flash_memory.h"
+#include "pw_kvs/flash_partition_with_stats.h"
 #include "pw_kvs/key_value_store.h"
-#include "pw_log/log.h"
-#include "pw_status/status.h"
 
 namespace pw::kvs {
 namespace {
 
 using std::byte;
 
-constexpr size_t kMaxEntries = 256;
-constexpr size_t kMaxUsableSectors = 1024;
+#ifndef PW_KVS_FUZZ_ITERATIONS
+#define PW_KVS_FUZZ_ITERATIONS 2
+#endif  // PW_KVS_FUZZ_ITERATIONS
+constexpr int kFuzzIterations = PW_KVS_FUZZ_ITERATIONS;
 
-constexpr std::array<const char*, 3> keys{"TestKey1", "Key2", "TestKey3"};
+constexpr size_t kMaxEntries = 256;
+constexpr size_t kMaxUsableSectors = 256;
+
+// 4 x 4k sectors, 16 byte alignment
+FakeFlashMemoryBuffer<4 * 1024, 6> test_flash(16);
+
+FlashPartitionWithStatsBuffer<kMaxUsableSectors> test_partition(
+    &test_flash, 0, test_flash.sector_count());
 
 ChecksumCrc16 checksum;
-// For KVS magic value always use a random 32 bit integer rather than a
-// human readable 4 bytes. See pw_kvs/format.h for more information.
-constexpr EntryFormat default_format{.magic = 0x749c361e,
-                                     .checksum = &checksum};
-}  // namespace
 
-TEST(KvsFuzz, FuzzTest) {
-  FlashPartition& test_partition = FlashTestPartition();
-  test_partition.Erase()
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-
-  KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs_(&test_partition,
-                                                           default_format);
-
-  ASSERT_EQ(OkStatus(), kvs_.Init());
-
-  if (test_partition.sector_size_bytes() < 4 * 1024 ||
-      test_partition.sector_count() < 4) {
-    PW_LOG_INFO("Sectors too small, skipping test.");
-    return;  // TODO: Test could be generalized
-  }
-  const char* key1 = "Buf1";
-  const char* key2 = "Buf2";
-  const size_t kLargestBufSize = 3 * 1024;
-  static byte buf1[kLargestBufSize];
-  static byte buf2[kLargestBufSize];
-  std::memset(buf1, 1, sizeof(buf1));
-  std::memset(buf2, 2, sizeof(buf2));
-
-  // Start with things in KVS
-  ASSERT_EQ(OkStatus(), kvs_.Put(key1, buf1));
-  ASSERT_EQ(OkStatus(), kvs_.Put(key2, buf2));
-  for (size_t j = 0; j < keys.size(); j++) {
-    ASSERT_EQ(OkStatus(), kvs_.Put(keys[j], j));
-  }
-
-  for (size_t i = 0; i < 100; i++) {
-    // Vary two sizes
-    size_t size1 = (kLargestBufSize) / (i + 1);
-    size_t size2 = (kLargestBufSize) / (100 - i);
-    for (size_t j = 0; j < 50; j++) {
-      // Rewrite a single key many times, can fill up a sector
-      ASSERT_EQ(OkStatus(), kvs_.Put("some_data", j));
-    }
-    // Delete and re-add everything
-    ASSERT_EQ(OkStatus(), kvs_.Delete(key1));
-    ASSERT_EQ(OkStatus(), kvs_.Put(key1, std::span(buf1, size1)));
-    ASSERT_EQ(OkStatus(), kvs_.Delete(key2));
-    ASSERT_EQ(OkStatus(), kvs_.Put(key2, std::span(buf2, size2)));
-    for (size_t j = 0; j < keys.size(); j++) {
-      ASSERT_EQ(OkStatus(), kvs_.Delete(keys[j]));
-      ASSERT_EQ(OkStatus(), kvs_.Put(keys[j], j));
-    }
-
-    // Re-enable and verify
+class EmptyInitializedKvs : public ::testing::Test {
+ protected:
+  // For KVS magic value always use a random 32 bit integer rather than a
+  // human readable 4 bytes. See pw_kvs/format.h for more information.
+  EmptyInitializedKvs()
+      : kvs_(&test_partition, {.magic = 0x873a9b50, .checksum = &checksum}) {
+    test_partition.Erase(0, test_partition.sector_count());
     ASSERT_EQ(OkStatus(), kvs_.Init());
-    static byte buf[4 * 1024];
-    ASSERT_EQ(OkStatus(), kvs_.Get(key1, std::span(buf, size1)).status());
-    ASSERT_EQ(std::memcmp(buf, buf1, size1), 0);
-    ASSERT_EQ(OkStatus(), kvs_.Get(key2, std::span(buf, size2)).status());
-    ASSERT_EQ(std::memcmp(buf2, buf2, size2), 0);
-    for (size_t j = 0; j < keys.size(); j++) {
-      size_t ret = 1000;
-      ASSERT_EQ(OkStatus(), kvs_.Get(keys[j], &ret));
-      ASSERT_EQ(ret, j);
+  }
+
+  KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs_;
+};
+
+TEST_F(EmptyInitializedKvs, Put_VaryingKeysAndValues) {
+  char value[] =
+      "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"  // 52
+      "34567890123";  // 64 (with final \0);
+  static_assert(sizeof(value) == 64);
+
+  test_partition.ResetCounters();
+
+  for (int i = 0; i < kFuzzIterations; ++i) {
+    for (unsigned key_size = 1; key_size < sizeof(value); ++key_size) {
+      for (unsigned value_size = 0; value_size < sizeof(value); ++value_size) {
+        ASSERT_EQ(OkStatus(),
+                  kvs_.Put(std::string_view(value, key_size),
+                           std::as_bytes(std::span(value, value_size))));
+      }
     }
   }
+
+  test_partition.SaveStorageStats(kvs_, "fuzz Put_VaryingKeysAndValues");
 }
 
+}  // namespace
 }  // namespace pw::kvs
diff --git a/pw_kvs/key_value_store_initialized_test.cc b/pw_kvs/key_value_store_initialized_test.cc
index 4314564..9994fb6 100644
--- a/pw_kvs/key_value_store_initialized_test.cc
+++ b/pw_kvs/key_value_store_initialized_test.cc
@@ -36,7 +36,7 @@
 using std::byte;
 
 constexpr size_t kMaxEntries = 256;
-constexpr size_t kMaxUsableSectors = 1024;
+constexpr size_t kMaxUsableSectors = 256;
 
 FlashPartition& test_partition = FlashTestPartition();
 
@@ -82,8 +82,7 @@
 class EmptyInitializedKvs : public ::testing::Test {
  protected:
   EmptyInitializedKvs() : kvs_(&test_partition, default_format) {
-    test_partition.Erase()
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    test_partition.Erase();
     ASSERT_EQ(OkStatus(), kvs_.Init());
   }
 
@@ -409,32 +408,58 @@
   }
 }
 
-TEST_F(EmptyInitializedKvs, Iterator) {
-  ASSERT_EQ(OkStatus(), kvs_.Put("kEy", std::as_bytes(std::span("123"))));
-
-  for (KeyValueStore::iterator it = kvs_.begin(); it != kvs_.end(); ++it) {
-    EXPECT_STREQ(it->key(), "kEy");
-
-    char temp[sizeof("123")] = {};
-    EXPECT_EQ(OkStatus(), it->Get(&temp));
-    EXPECT_STREQ("123", temp);
+TEST_F(EmptyInitializedKvs, FuzzTest) {
+  if (test_partition.sector_size_bytes() < 4 * 1024 ||
+      test_partition.sector_count() < 4) {
+    PW_LOG_INFO("Sectors too small, skipping test.");
+    return;  // TODO: Test could be generalized
   }
-}
+  const char* key1 = "Buf1";
+  const char* key2 = "Buf2";
+  const size_t kLargestBufSize = 3 * 1024;
+  static byte buf1[kLargestBufSize];
+  static byte buf2[kLargestBufSize];
+  std::memset(buf1, 1, sizeof(buf1));
+  std::memset(buf2, 2, sizeof(buf2));
 
-TEST_F(EmptyInitializedKvs, Iterator_PostIncrement) {
-  ASSERT_EQ(OkStatus(), kvs_.Put("kEy", std::as_bytes(std::span("123"))));
+  // Start with things in KVS
+  ASSERT_EQ(OkStatus(), kvs_.Put(key1, buf1));
+  ASSERT_EQ(OkStatus(), kvs_.Put(key2, buf2));
+  for (size_t j = 0; j < keys.size(); j++) {
+    ASSERT_EQ(OkStatus(), kvs_.Put(keys[j], j));
+  }
 
-  KeyValueStore::iterator it = kvs_.begin();
-  EXPECT_EQ(it++, kvs_.begin());
-  EXPECT_EQ(it, kvs_.end());
-}
+  for (size_t i = 0; i < 100; i++) {
+    // Vary two sizes
+    size_t size1 = (kLargestBufSize) / (i + 1);
+    size_t size2 = (kLargestBufSize) / (100 - i);
+    for (size_t j = 0; j < 50; j++) {
+      // Rewrite a single key many times, can fill up a sector
+      ASSERT_EQ(OkStatus(), kvs_.Put("some_data", j));
+    }
+    // Delete and re-add everything
+    ASSERT_EQ(OkStatus(), kvs_.Delete(key1));
+    ASSERT_EQ(OkStatus(), kvs_.Put(key1, std::span(buf1, size1)));
+    ASSERT_EQ(OkStatus(), kvs_.Delete(key2));
+    ASSERT_EQ(OkStatus(), kvs_.Put(key2, std::span(buf2, size2)));
+    for (size_t j = 0; j < keys.size(); j++) {
+      ASSERT_EQ(OkStatus(), kvs_.Delete(keys[j]));
+      ASSERT_EQ(OkStatus(), kvs_.Put(keys[j], j));
+    }
 
-TEST_F(EmptyInitializedKvs, Iterator_PreIncrement) {
-  ASSERT_EQ(OkStatus(), kvs_.Put("kEy", std::as_bytes(std::span("123"))));
-
-  KeyValueStore::iterator it = kvs_.begin();
-  EXPECT_EQ(++it, kvs_.end());
-  EXPECT_EQ(it, kvs_.end());
+    // Re-enable and verify
+    ASSERT_EQ(OkStatus(), kvs_.Init());
+    static byte buf[4 * 1024];
+    ASSERT_EQ(OkStatus(), kvs_.Get(key1, std::span(buf, size1)).status());
+    ASSERT_EQ(std::memcmp(buf, buf1, size1), 0);
+    ASSERT_EQ(OkStatus(), kvs_.Get(key2, std::span(buf, size2)).status());
+    ASSERT_EQ(std::memcmp(buf2, buf2, size2), 0);
+    for (size_t j = 0; j < keys.size(); j++) {
+      size_t ret = 1000;
+      ASSERT_EQ(OkStatus(), kvs_.Get(keys[j], &ret));
+      ASSERT_EQ(ret, j);
+    }
+  }
 }
 
 TEST_F(EmptyInitializedKvs, Basic) {
@@ -469,8 +494,7 @@
   EXPECT_EQ(test2, value2);
 
   // Delete other key
-  kvs_.Delete(keys[1])
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  kvs_.Delete(keys[1]);
 
   // Verify it was erased
   EXPECT_EQ(kvs_.size(), 0u);
diff --git a/pw_kvs/key_value_store_map_test.cc b/pw_kvs/key_value_store_map_test.cc
index 45ced14..947a3fa 100644
--- a/pw_kvs/key_value_store_map_test.cc
+++ b/pw_kvs/key_value_store_map_test.cc
@@ -38,6 +38,8 @@
 namespace pw::kvs {
 namespace {
 
+using std::byte;
+
 constexpr size_t kMaxEntries = 256;
 constexpr size_t kMaxUsableSectors = 256;
 
@@ -164,8 +166,7 @@
       label << ((options == kReinitWithPartialGC) ? "PartialGC" : "");
       label << ((kvs_.redundancy() > 1) ? "Redundant" : "");
 
-      partition_.SaveStorageStats(kvs_, label.data())
-          .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+      partition_.SaveStorageStats(kvs_, label.data());
     }
   }
 
diff --git a/pw_kvs/key_value_store_put_test.cc b/pw_kvs/key_value_store_put_test.cc
deleted file mode 100644
index 5e69f3f..0000000
--- a/pw_kvs/key_value_store_put_test.cc
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "gtest/gtest.h"
-#include "pw_kvs/crc16_checksum.h"
-#include "pw_kvs/fake_flash_memory.h"
-#include "pw_kvs/flash_partition_with_stats.h"
-#include "pw_kvs/key_value_store.h"
-
-namespace pw::kvs {
-namespace {
-
-#ifndef PW_KVS_PUT_ITERATIONS
-#define PW_KVS_PUT_ITERATIONS 2
-#endif  // PW_KVS_PUT_ITERATIONS
-constexpr int kPutIterations = PW_KVS_PUT_ITERATIONS;
-
-constexpr size_t kMaxEntries = 256;
-constexpr size_t kMaxUsableSectors = 256;
-
-// 4 x 4k sectors, 16 byte alignment
-FakeFlashMemoryBuffer<4 * 1024, 6> test_flash(16);
-
-FlashPartitionWithStatsBuffer<kMaxUsableSectors> test_partition(
-    &test_flash, 0, test_flash.sector_count());
-
-ChecksumCrc16 checksum;
-
-class EmptyInitializedKvs : public ::testing::Test {
- protected:
-  // For KVS magic value always use a random 32 bit integer rather than a
-  // human readable 4 bytes. See pw_kvs/format.h for more information.
-  EmptyInitializedKvs()
-      : kvs_(&test_partition, {.magic = 0x873a9b50, .checksum = &checksum}) {
-    test_partition.Erase(0, test_partition.sector_count())
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    ASSERT_EQ(OkStatus(), kvs_.Init());
-  }
-
-  KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs_;
-};
-
-TEST_F(EmptyInitializedKvs, Put_VaryingKeysAndValues) {
-  char value[] =
-      "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"  // 52
-      "34567890123";  // 64 (with final \0);
-  static_assert(sizeof(value) == 64);
-
-  test_partition.ResetCounters();
-
-  for (int i = 0; i < kPutIterations; ++i) {
-    for (unsigned key_size = 1; key_size < sizeof(value); ++key_size) {
-      for (unsigned value_size = 0; value_size < sizeof(value); ++value_size) {
-        ASSERT_EQ(OkStatus(),
-                  kvs_.Put(std::string_view(value, key_size),
-                           std::as_bytes(std::span(value, value_size))));
-      }
-    }
-  }
-
-  test_partition.SaveStorageStats(kvs_, "Put_VaryingKeysAndValues")
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-}
-
-}  // namespace
-}  // namespace pw::kvs
diff --git a/pw_kvs/key_value_store_test.cc b/pw_kvs/key_value_store_test.cc
index 24f5daf..2926cce 100644
--- a/pw_kvs/key_value_store_test.cc
+++ b/pw_kvs/key_value_store_test.cc
@@ -42,6 +42,7 @@
 namespace {
 
 using internal::EntryHeader;
+using std::byte;
 
 constexpr size_t kMaxEntries = 256;
 constexpr size_t kMaxUsableSectors = 256;
@@ -203,8 +204,7 @@
              sizeof(fname_buf),
              "WriteOneKeyMultipleTimes_%d.bin",
              reload);
-    flash.Dump(fname_buf)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    flash.Dump(fname_buf);
   }
 }
 
@@ -233,8 +233,7 @@
     EXPECT_OK(kvs.Put(key.view(), value));
     EXPECT_EQ(kvs.size(), i + 1);
   }
-  flash.Dump("WritingMultipleKeysIncreasesSize.bin")
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  flash.Dump("WritingMultipleKeysIncreasesSize.bin");
 }
 
 TEST(InMemoryKvs, WriteAndReadOneKey) {
diff --git a/pw_kvs/key_value_store_wear_test.cc b/pw_kvs/key_value_store_wear_test.cc
index 94bb042..4110c16 100644
--- a/pw_kvs/key_value_store_wear_test.cc
+++ b/pw_kvs/key_value_store_wear_test.cc
@@ -73,8 +73,7 @@
   EXPECT_GE(partition_.min_erase_count(), 7u);
   EXPECT_LE(partition_.max_erase_count(), partition_.min_erase_count() + 1u);
 
-  partition_.SaveStorageStats(kvs_, "WearTest RepeatedLargeEntry")
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  partition_.SaveStorageStats(kvs_, "WearTest RepeatedLargeEntry");
 }
 
 // Test a KVS with a number of entries, several sectors that are nearly full
diff --git a/pw_kvs/public/pw_kvs/alignment.h b/pw_kvs/public/pw_kvs/alignment.h
index 4965895..e86ec73 100644
--- a/pw_kvs/public/pw_kvs/alignment.h
+++ b/pw_kvs/public/pw_kvs/alignment.h
@@ -59,9 +59,7 @@
     // TODO(hepler): Add DCHECK to ensure that buffer.size() >= alignment_bytes.
   }
 
-  ~AlignedWriter() {
-    Flush().IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  }
+  ~AlignedWriter() { Flush(); }
 
   // Writes bytes to the AlignedWriter. The output may be called if the internal
   // buffer is filled.
diff --git a/pw_kvs/public/pw_kvs/checksum.h b/pw_kvs/public/pw_kvs/checksum.h
index 9b6a3be..7624e39 100644
--- a/pw_kvs/public/pw_kvs/checksum.h
+++ b/pw_kvs/public/pw_kvs/checksum.h
@@ -90,10 +90,7 @@
 template <size_t kAlignmentBytes, size_t kBufferSize = kAlignmentBytes>
 class AlignedChecksum : public ChecksumAlgorithm {
  public:
-  void Update(std::span<const std::byte> data) final {
-    writer_.Write(data)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  }
+  void Update(std::span<const std::byte> data) final { writer_.Write(data); }
 
  protected:
   constexpr AlignedChecksum(std::span<const std::byte> state)
@@ -107,7 +104,7 @@
   static_assert(kBufferSize >= kAlignmentBytes);
 
   void Finalize() final {
-    writer_.Flush().IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    writer_.Flush();
     FinalizeAligned();
   }
 
diff --git a/pw_kvs/public/pw_kvs/fake_flash_memory.h b/pw_kvs/public/pw_kvs/fake_flash_memory.h
index 49e36d7..e892d92 100644
--- a/pw_kvs/public/pw_kvs/fake_flash_memory.h
+++ b/pw_kvs/public/pw_kvs/fake_flash_memory.h
@@ -154,6 +154,12 @@
   // Creates a flash memory with no data written.
   explicit FakeFlashMemoryBuffer(
       size_t alignment_bytes = kDefaultAlignmentBytes)
+      : FakeFlashMemoryBuffer(std::array<std::byte, 0>{}, alignment_bytes) {}
+
+  // Creates a flash memory initialized to the provided contents.
+  explicit FakeFlashMemoryBuffer(
+      std::span<const std::byte> contents,
+      size_t alignment_bytes = kDefaultAlignmentBytes)
       : FakeFlashMemory(buffer_,
                         kSectorSize,
                         kSectorCount,
@@ -161,13 +167,6 @@
                         read_errors_,
                         write_errors_) {
     std::memset(buffer_.data(), int(kErasedValue), buffer_.size());
-  }
-
-  // Creates a flash memory initialized to the provided contents.
-  explicit FakeFlashMemoryBuffer(
-      std::span<const std::byte> contents,
-      size_t alignment_bytes = kDefaultAlignmentBytes)
-      : FakeFlashMemoryBuffer(alignment_bytes) {
     std::memcpy(buffer_.data(),
                 contents.data(),
                 std::min(contents.size(), buffer_.size()));
diff --git a/pw_kvs/public/pw_kvs/flash_memory.h b/pw_kvs/public/pw_kvs/flash_memory.h
index 073db84..b767386 100644
--- a/pw_kvs/public/pw_kvs/flash_memory.h
+++ b/pw_kvs/public/pw_kvs/flash_memory.h
@@ -18,17 +18,11 @@
 #include <initializer_list>
 #include <span>
 
-#include "pw_assert/assert.h"
+#include "pw_assert/light.h"
 #include "pw_kvs/alignment.h"
-#include "pw_polyfill/standard.h"
 #include "pw_status/status.h"
 #include "pw_status/status_with_size.h"
 
-#if PW_CXX_STANDARD_IS_SUPPORTED(17)  // Requires C++17 for pw::Result
-#include "pw_stream/seek.h"
-#include "pw_stream/stream.h"
-#endif  // PW_CXX_STANDARD_IS_SUPPORTED(17)
-
 namespace pw {
 namespace kvs {
 
@@ -146,52 +140,6 @@
   // The flash address is in the range of: 0 to PartitionSize.
   using Address = uint32_t;
 
-#if PW_CXX_STANDARD_IS_SUPPORTED(17)  // Requires C++17 for pw::Result
-  class Writer final : public stream::NonSeekableWriter {
-   public:
-    constexpr Writer(kvs::FlashPartition& partition)
-        : partition_(partition), position_(0) {}
-
-   private:
-    Status DoWrite(ConstByteSpan data) override;
-
-    size_t DoTell() const override { return position_; }
-
-    size_t ConservativeLimit(LimitType type) const override {
-      return type == LimitType::kWrite ? partition_.size_bytes() - position_
-                                       : 0;
-    }
-
-    FlashPartition& partition_;
-    size_t position_;
-  };
-
-  class Reader final : public stream::SeekableReader {
-   public:
-    constexpr Reader(kvs::FlashPartition& partition)
-        : partition_(partition), position_(0) {}
-
-    Reader(const Reader&) = delete;
-    Reader& operator=(const Reader&) = delete;
-
-   private:
-    StatusWithSize DoRead(ByteSpan data) override;
-
-    size_t DoTell() const override { return position_; }
-
-    Status DoSeek(ptrdiff_t offset, Whence origin) override {
-      return CalculateSeek(offset, origin, partition_.size_bytes(), position_);
-    }
-
-    size_t ConservativeLimit(LimitType type) const override {
-      return type == LimitType::kRead ? partition_.size_bytes() - position_ : 0;
-    }
-
-    FlashPartition& partition_;
-    size_t position_;
-  };
-#endif  // PW_CXX_STANDARD_IS_SUPPORTED(17)
-
   // Implement Output for the Write method.
   class Output final : public pw::Output {
    public:
diff --git a/pw_kvs/public/pw_kvs/flash_partition_with_stats.h b/pw_kvs/public/pw_kvs/flash_partition_with_stats.h
index 4baca4a..569ab9f 100644
--- a/pw_kvs/public/pw_kvs/flash_partition_with_stats.h
+++ b/pw_kvs/public/pw_kvs/flash_partition_with_stats.h
@@ -63,8 +63,7 @@
   }
 
   size_t total_erase_count() const {
-    return std::accumulate(
-        sector_counters_.begin(), sector_counters_.end(), 0ul);
+    return std::accumulate(sector_counters_.begin(), sector_counters_.end(), 0);
   }
 
   void ResetCounters() { sector_counters_.assign(sector_count(), 0); }
diff --git a/pw_kvs/public/pw_kvs/internal/entry_cache.h b/pw_kvs/public/pw_kvs/internal/entry_cache.h
index ecf7094..18e3484 100644
--- a/pw_kvs/public/pw_kvs/internal/entry_cache.h
+++ b/pw_kvs/public/pw_kvs/internal/entry_cache.h
@@ -174,7 +174,7 @@
 
   // Adds a new descriptor to the descriptor list. The entry MUST be unique and
   // the EntryCache must NOT be full!
-  EntryMetadata AddNew(const KeyDescriptor& descriptor, Address address) const;
+  EntryMetadata AddNew(const KeyDescriptor& entry, Address address) const;
 
   // Adds a new descriptor, overwrites an existing one, or adds an additional
   // redundant address to one. The sector size is included for checking that
diff --git a/pw_kvs/public/pw_kvs/internal/sectors.h b/pw_kvs/public/pw_kvs/internal/sectors.h
index 760952f..50fda8d 100644
--- a/pw_kvs/public/pw_kvs/internal/sectors.h
+++ b/pw_kvs/public/pw_kvs/internal/sectors.h
@@ -182,7 +182,7 @@
   // Finds a sector that is ready to be garbage collected. Returns nullptr if no
   // sectors can / need to be garbage collected.
   SectorDescriptor* FindSectorToGarbageCollect(
-      std::span<const Address> reserved_addresses) const;
+      std::span<const Address> addresses_to_avoid) const;
 
   // The number of sectors in use.
   size_t size() const { return descriptors_.size(); }
diff --git a/pw_kvs/public/pw_kvs/internal/span_traits.h b/pw_kvs/public/pw_kvs/internal/span_traits.h
index afb0528..f85f0d6 100644
--- a/pw_kvs/public/pw_kvs/internal/span_traits.h
+++ b/pw_kvs/public/pw_kvs/internal/span_traits.h
@@ -22,7 +22,7 @@
 
 // This borrows the `make_span` function from Chromium and uses to see if a type
 // can be represented as a span. See:
-// https://chromium.googlesource.com/chromium/src/+/main/base/containers/span.h
+// https://chromium.googlesource.com/chromium/src/+/master/base/containers/span.h
 
 // Simplified implementation of C++20's std::iter_reference_t.
 // As opposed to std::iter_reference_t, this implementation does not restrict
diff --git a/pw_kvs/public/pw_kvs/key.h b/pw_kvs/public/pw_kvs/key.h
index 789036e..62b8532 100644
--- a/pw_kvs/public/pw_kvs/key.h
+++ b/pw_kvs/public/pw_kvs/key.h
@@ -21,7 +21,7 @@
 #include <string_view>
 #endif  // __cplusplus >= 201703L
 
-#include "pw_string/internal/length.h"
+#include "pw_string/util.h"
 
 namespace pw {
 namespace kvs {
@@ -35,8 +35,7 @@
   constexpr Key(const Key&) = default;
   constexpr Key(const char* str)
       : str_{str},
-        length_{string::internal::ClampedLength(
-            str, std::numeric_limits<size_t>::max())} {}
+        length_{string::Length(str, std::numeric_limits<size_t>::max())} {}
   constexpr Key(const char* str, size_t len) : str_{str}, length_{len} {}
   Key(const std::string& str) : str_{str.data()}, length_{str.length()} {}
 
@@ -46,9 +45,6 @@
   operator std::string_view() { return std::string_view{str_, length_}; }
 #endif  // __cplusplus >= 201703L
 
-  // Assignment
-  constexpr Key& operator=(const Key&) = default;
-
   // Traits
   constexpr size_t size() const { return length_; }
   constexpr size_t length() const { return length_; }
diff --git a/pw_kvs/public/pw_kvs/key_value_store.h b/pw_kvs/public/pw_kvs/key_value_store.h
index 086dcb8..f5d4695 100644
--- a/pw_kvs/public/pw_kvs/key_value_store.h
+++ b/pw_kvs/public/pw_kvs/key_value_store.h
@@ -66,8 +66,7 @@
   // garbage collection is attempted if space for an entry cannot be found. This
   // is a relatively lengthy operation. If kDisabled, Put calls that would
   // require garbage collection fail with RESOURCE_EXHAUSTED.
-  GargbageCollectOnWrite gc_on_write =
-      GargbageCollectOnWrite::kAsManySectorsNeeded;
+  GargbageCollectOnWrite gc_on_write = GargbageCollectOnWrite::kOneSector;
 
   // When the KVS handles errors that are discovered, such as corrupt entries,
   // not enough redundant copys of an entry, etc.
@@ -246,8 +245,7 @@
 
     constexpr Item(const KeyValueStore& kvs,
                    const internal::EntryCache::const_iterator& item_iterator)
-        : kvs_(kvs), iterator_(item_iterator), key_buffer_ {}
-    {}
+        : kvs_(kvs), iterator_(item_iterator), key_buffer_{} {}
 
     void ReadKey();
 
@@ -262,11 +260,7 @@
    public:
     iterator& operator++();
 
-    iterator operator++(int) {
-      const iterator original(item_.kvs_, item_.iterator_);
-      operator++();
-      return original;
-    }
+    iterator& operator++(int) { return operator++(); }
 
     // Reads the entry's key from flash.
     const Item& operator*() {
@@ -390,8 +384,8 @@
   Status ReadEntry(const EntryMetadata& metadata, Entry& entry) const;
 
   // Finds the metadata for an entry matching a particular key. Searches for a
-  // KeyDescriptor that matches this key and sets *metadata_out to point to it
-  // if one is found.
+  // KeyDescriptor that matches this key and sets *metadata to point to it if
+  // one is found.
   //
   //             OK: there is a matching descriptor and *metadata is set
   //      NOT_FOUND: there is no descriptor that matches this key, but this key
@@ -401,15 +395,15 @@
   //                 key's hash collides with the hash for an existing
   //                 descriptor
   //
-  Status FindEntry(Key key, EntryMetadata* metadata_out) const;
+  Status FindEntry(Key key, EntryMetadata* metadata) const;
 
-  // Searches for a KeyDescriptor that matches this key and sets *metadata_out
-  // to point to it if one is found.
+  // Searches for a KeyDescriptor that matches this key and sets *metadata to
+  // point to it if one is found.
   //
-  //          OK: there is a matching descriptor and *metadata_out is set
+  //          OK: there is a matching descriptor and *metadata is set
   //   NOT_FOUND: there is no descriptor that matches this key
   //
-  Status FindExisting(Key key, EntryMetadata* metadata_out) const;
+  Status FindExisting(Key key, EntryMetadata* metadata) const;
 
   StatusWithSize Get(Key key,
                      const EntryMetadata& metadata,
@@ -419,7 +413,7 @@
   Status FixedSizeGet(Key key, void* value, size_t size_bytes) const;
 
   Status FixedSizeGet(Key key,
-                      const EntryMetadata& metadata,
+                      const EntryMetadata& descriptor,
                       void* value,
                       size_t size_bytes) const;
 
@@ -453,7 +447,7 @@
 
   Status GetSectorForWrite(SectorDescriptor** sector,
                            size_t entry_size,
-                           std::span<const Address> reserved_addresses);
+                           std::span<const Address> addresses_to_skip);
 
   Status MarkSectorCorruptIfNotOk(Status status, SectorDescriptor* sector);
 
@@ -467,7 +461,7 @@
 
   Status RelocateEntry(const EntryMetadata& metadata,
                        KeyValueStore::Address& address,
-                       std::span<const Address> reserved_addresses);
+                       std::span<const Address> addresses_to_skip);
 
   // Perform all maintenance possible, including all neeeded repairing of
   // corruption and garbage collection of reclaimable space in the KVS. When
@@ -484,17 +478,17 @@
   };
   Status FullMaintenanceHelper(MaintenanceType maintenance_type);
 
-  // Find and garbage collect a singe sector that does not include a reserved
-  // address.
-  Status GarbageCollect(std::span<const Address> reserved_addresses);
+  // Find and garbage collect a singe sector that does not include an address to
+  // skip.
+  Status GarbageCollect(std::span<const Address> addresses_to_skip);
 
   Status RelocateKeyAddressesInSector(
       SectorDescriptor& sector_to_gc,
-      const EntryMetadata& metadata,
-      std::span<const Address> reserved_addresses);
+      const EntryMetadata& descriptor,
+      std::span<const Address> addresses_to_skip);
 
   Status GarbageCollectSector(SectorDescriptor& sector_to_gc,
-                              std::span<const Address> reserved_addresses);
+                              std::span<const Address> addresses_to_skip);
 
   // Ensure that all entries are on the primary (first) format. Entries that are
   // not on the primary format are rewritten.
diff --git a/pw_kvs/public/pw_kvs/test_key_value_store.h b/pw_kvs/public/pw_kvs/test_key_value_store.h
index 3e02cf5..e57e74f 100644
--- a/pw_kvs/public/pw_kvs/test_key_value_store.h
+++ b/pw_kvs/public/pw_kvs/test_key_value_store.h
@@ -14,12 +14,11 @@
 #pragma once
 
 #include "pw_kvs/key_value_store.h"
-#include "pw_sync/borrow.h"
 
 namespace pw {
 namespace kvs {
 
-sync::Borrowable<KeyValueStore>& TestKvs();
+KeyValueStore& TestKvs();
 
 }  // namespace kvs
 }  // namespace pw
diff --git a/pw_kvs/size_report/BUILD b/pw_kvs/size_report/BUILD
new file mode 100644
index 0000000..bd752f3
--- /dev/null
+++ b/pw_kvs/size_report/BUILD
@@ -0,0 +1,58 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_binary",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_binary(
+    name = "base",
+    srcs = ["base.cc"],
+    deps = [
+        "//pw_assert",
+        "//pw_bloat:bloat_this_binary",
+        "//pw_log",
+    ],
+)
+
+pw_cc_binary(
+    name = "base_with_only_flash",
+    srcs = ["base_with_only_flash.cc"],
+    deps = [
+        "//pw_assert",
+        "//pw_bloat:bloat_this_binary",
+        "//pw_kvs",
+        "//pw_kvs:flash_test_partition",
+        "//pw_kvs:fake_flash_12_byte_partition",
+        "//pw_log",
+    ],
+)
+
+pw_cc_binary(
+    name = "with_kvs",
+    srcs = ["with_kvs.cc"],
+    deps = [
+        "//pw_assert",
+        "//pw_bloat:bloat_this_binary",
+        "//pw_kvs",
+        "//pw_kvs:flash_test_partition",
+        "//pw_kvs:fake_flash_12_byte_partition",
+        "//pw_log",
+    ],
+)
diff --git a/pw_kvs/size_report/BUILD.bazel b/pw_kvs/size_report/BUILD.bazel
deleted file mode 100644
index 45f3435..0000000
--- a/pw_kvs/size_report/BUILD.bazel
+++ /dev/null
@@ -1,58 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_binary",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_binary(
-    name = "base",
-    srcs = ["base.cc"],
-    deps = [
-        "//pw_assert",
-        "//pw_bloat:bloat_this_binary",
-        "//pw_log",
-    ],
-)
-
-pw_cc_binary(
-    name = "base_with_only_flash",
-    srcs = ["base_with_only_flash.cc"],
-    deps = [
-        "//pw_assert",
-        "//pw_bloat:bloat_this_binary",
-        "//pw_kvs",
-        "//pw_kvs:fake_flash_12_byte_partition",
-        "//pw_kvs:flash_test_partition",
-        "//pw_log",
-    ],
-)
-
-pw_cc_binary(
-    name = "with_kvs",
-    srcs = ["with_kvs.cc"],
-    deps = [
-        "//pw_assert",
-        "//pw_bloat:bloat_this_binary",
-        "//pw_kvs",
-        "//pw_kvs:fake_flash_12_byte_partition",
-        "//pw_kvs:flash_test_partition",
-        "//pw_log",
-    ],
-)
diff --git a/pw_kvs/size_report/base.cc b/pw_kvs/size_report/base.cc
index 103ca10..be6d89c 100644
--- a/pw_kvs/size_report/base.cc
+++ b/pw_kvs/size_report/base.cc
@@ -14,7 +14,7 @@
 
 #include <cstring>
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_bloat/bloat_this_binary.h"
 #include "pw_log/log.h"
 
diff --git a/pw_kvs/size_report/base_with_only_flash.cc b/pw_kvs/size_report/base_with_only_flash.cc
index ab1fcbd..bb7c182 100644
--- a/pw_kvs/size_report/base_with_only_flash.cc
+++ b/pw_kvs/size_report/base_with_only_flash.cc
@@ -14,7 +14,7 @@
 
 #include <cstring>
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_bloat/bloat_this_binary.h"
 #include "pw_kvs/flash_test_partition.h"
 #include "pw_log/log.h"
@@ -39,21 +39,17 @@
       std::memset((void*)working_buffer, 0x55, sizeof(working_buffer));
   is_set = (result != nullptr);
 
-  test_partition.Erase()
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  test_partition.Erase();
 
   std::memset((void*)working_buffer, 0x55, sizeof(working_buffer));
 
-  test_partition.Write(0, std::as_bytes(std::span(working_buffer)))
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  test_partition.Write(0, std::as_bytes(std::span(working_buffer)));
 
   bool tmp_bool;
-  test_partition.IsErased(&tmp_bool)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  test_partition.IsErased(&tmp_bool);
   is_erased = tmp_bool;
 
-  test_partition.Read(0, as_writable_bytes(std::span(working_buffer)))
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  test_partition.Read(0, as_writable_bytes(std::span(working_buffer)));
 
   return 0;
 }
diff --git a/pw_kvs/size_report/with_kvs.cc b/pw_kvs/size_report/with_kvs.cc
index fccbab5..fc99e55 100644
--- a/pw_kvs/size_report/with_kvs.cc
+++ b/pw_kvs/size_report/with_kvs.cc
@@ -14,7 +14,7 @@
 
 #include <cstring>
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_bloat/bloat_this_binary.h"
 #include "pw_kvs/flash_test_partition.h"
 #include "pw_kvs/key_value_store.h"
@@ -49,17 +49,15 @@
       std::memset((void*)working_buffer, 0x55, sizeof(working_buffer));
   is_set = (result != nullptr);
 
-  kvs.Init().IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  kvs.Init();
 
   unsigned kvs_value = 42;
-  kvs.Put("example_key", kvs_value)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  kvs.Put("example_key", kvs_value);
 
   kvs_entry_count = kvs.size();
 
   unsigned read_value = 0;
-  kvs.Get("example_key", &read_value)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  kvs.Get("example_key", &read_value);
 
   return 0;
 }
diff --git a/pw_kvs/test_key_value_store_test.cc b/pw_kvs/test_key_value_store_test.cc
index cb9a3dd..97ececd 100644
--- a/pw_kvs/test_key_value_store_test.cc
+++ b/pw_kvs/test_key_value_store_test.cc
@@ -17,18 +17,17 @@
 #include "gtest/gtest.h"
 #include "pw_kvs/key_value_store.h"
 #include "pw_status/status.h"
-#include "pw_sync/borrow.h"
 
 namespace pw::kvs {
 namespace {
 
 // Simple test to verify that the TestKvs() does basic function.
 TEST(TestKvs, PutGetValue) {
-  sync::BorrowedPointer<KeyValueStore> kvs = TestKvs().acquire();
-  ASSERT_EQ(OkStatus(), kvs->Put("key", uint32_t(0xfeedbeef)));
+  KeyValueStore& kvs = TestKvs();
+  ASSERT_EQ(OkStatus(), kvs.Put("key", uint32_t(0xfeedbeef)));
 
   uint32_t value = 0;
-  EXPECT_EQ(OkStatus(), kvs->Get("key", &value));
+  EXPECT_EQ(OkStatus(), kvs.Get("key", &value));
   EXPECT_EQ(uint32_t(0xfeedbeef), value);
 }
 
diff --git a/pw_libc/BUILD.bazel b/pw_libc/BUILD.bazel
deleted file mode 100644
index 0139ae7..0000000
--- a/pw_libc/BUILD.bazel
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_test(
-    name = "memset_test",
-    srcs = [
-        "memset_test.cc",
-    ],
-    deps = [
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_libc/BUILD.gn b/pw_libc/BUILD.gn
deleted file mode 100644
index 9a55cef..0000000
--- a/pw_libc/BUILD.gn
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright 2019 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_unit_test/test.gni")
-
-config("default_config") {
-  include_dirs = [ "public" ]
-}
-
-pw_test_group("tests") {
-  tests = [ ":memset_test" ]
-}
-
-pw_test("memset_test") {
-  sources = [ "memset_test.cc" ]
-}
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
diff --git a/pw_libc/CMakeLists.txt b/pw_libc/CMakeLists.txt
deleted file mode 100644
index 0b1b28d..0000000
--- a/pw_libc/CMakeLists.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-
diff --git a/pw_libc/OWNERS b/pw_libc/OWNERS
deleted file mode 100644
index 8819b2c..0000000
--- a/pw_libc/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-keir@google.com
diff --git a/pw_libc/docs.rst b/pw_libc/docs.rst
deleted file mode 100644
index ffee57a..0000000
--- a/pw_libc/docs.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-.. _module-pw_libc:
-
--------
-pw_libc
--------
-The ``pw_libc`` module provides a restricted subset of libc suitable for some
-microcontroller projects. At this time, only a test suite is provided for
-certain libc functions.
diff --git a/pw_libc/memset_test.cc b/pw_libc/memset_test.cc
deleted file mode 100644
index 5ab2a3f..0000000
--- a/pw_libc/memset_test.cc
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-//
-// This tests the system installed C standard library version of memset.
-//
-// Note: We have caught real production bugs with these tests. Do not assume
-// your vendor's C library is correct! For standard C functions like memset and
-// memcpy, there are compiler intrisics which assume that the C standard is
-// followed. If the implemention of memset or memcpy does not exactly follow
-// the standard, subtle and hard to track down bugs can be the result.
-
-#include <array>
-#include <cstring>
-#include <numeric>
-
-#include "gtest/gtest.h"
-
-namespace pw {
-namespace {
-
-// From the ISO C standard:
-// http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf
-//
-// Section 7.21.6.1: memset(void *s, int c, size_t n)
-//
-//   void* memset(void* buffer,
-//                int character,
-//                size_t num_bytes);
-//
-//   Copy c into the first n bytes of s.
-//   Returns buffer, a copy of the destination pointer.
-//
-
-TEST(Memset, EmptyCase) {
-  std::array<char, 5> arr{'h', 'e', 'l', 'l', 'o'};
-  void* ret = memset(arr.data(), 0, 0);
-
-  // Destination buffer returned.
-  EXPECT_EQ(ret, arr.data());
-
-  // Destination buffer untouched.
-  constexpr std::array<char, 5> kExpected{'h', 'e', 'l', 'l', 'o'};
-  EXPECT_TRUE(
-      std::equal(arr.begin(), arr.end(), kExpected.begin(), kExpected.end()));
-}
-
-TEST(Memset, OneCharacter) {
-  std::array<char, 5> arr{'h', 'e', 'l', 'l', 'o'};
-  void* ret = memset(arr.data(), 0, 1);
-
-  // Ensure the destination buffer is returned.
-  EXPECT_EQ(ret, arr.data());
-
-  // Ensure the destination buffer is untouched.
-  constexpr std::array<char, 5> kExpected{0, 'e', 'l', 'l', 'o'};
-  EXPECT_TRUE(
-      std::equal(arr.begin(), arr.end(), kExpected.begin(), kExpected.end()));
-}
-
-// Now do a detailed case with more values. Span both word sizes and alignments
-// to ensure we hit some edge cases.
-TEST(Memset, MultipleSizesMultipleAlignments) {
-  constexpr int kMaxBytes = 64;
-  std::array<char, kMaxBytes> arr;
-
-  constexpr int kMaxAlignment = 16;
-
-  // Avoid 0 sentinel to prevent interaction with uninitialized memory.
-  constexpr char kSentinel = 3;
-  constexpr char kIotaStart = kSentinel + 7;
-
-  // Try different alignments.
-  for (int alignment = 0; alignment < kMaxAlignment; ++alignment) {
-    // Try different memset sizes.
-    for (int write_size = 0; write_size < (kMaxBytes - kMaxAlignment);
-         ++write_size) {
-      // Fill entire array with incrementing integers; starting above sentinel.
-      std::iota(arr.begin(), arr.end(), kIotaStart);
-
-      // Memset the first write_size bytes, with our sentinel
-      void* write_head = &arr[alignment];
-      const void* ret = memset(write_head, kSentinel, write_size);
-
-      // Check destination buffer returned.
-      EXPECT_EQ(ret, write_head);
-
-      for (int j = 0; j < kMaxBytes; ++j) {
-        if (j < alignment) {
-          // First part of destination buffer untouched; should match iota.
-          EXPECT_EQ(arr[j], kIotaStart + j);
-        } else if (j < alignment + write_size) {
-          // Second part is set to the sentinel value.
-          EXPECT_EQ(arr[j], kSentinel);
-        } else {
-          // Third part is back to the iota content.
-          EXPECT_EQ(arr[j], kIotaStart + j);
-        }
-      }
-    }
-  }
-}
-
-}  // namespace
-}  // namespace pw
diff --git a/pw_log/Android.bp b/pw_log/Android.bp
deleted file mode 100644
index d9cadd4..0000000
--- a/pw_log/Android.bp
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-cc_library {
-    name: "libpw_log",
-    vendor_available: true,
-    cpp_std: "c++2a",
-    export_include_dirs: [
-        "public",
-    ],
-}
-
-android_library {
-    name: "pw_log_android_java",
-    srcs: ["java/android_main/dev/pigweed/pw_log/*.java"],
-    visibility: ["//visibility:public"],
-    sdk_version: "current",
-}
diff --git a/pw_log/AndroidManifest.xml b/pw_log/AndroidManifest.xml
deleted file mode 100644
index 6b359c2..0000000
--- a/pw_log/AndroidManifest.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-   Copyright 2022 The Pigweed Authors
-
-   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
-
-   https://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.
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="dev.pigweed.pw_log" >
-
-    <uses-sdk
-        android:minSdkVersion="14"
-        android:targetSdkVersion="32" />
-</manifest>
diff --git a/pw_log/BUILD b/pw_log/BUILD
new file mode 100644
index 0000000..723f75d
--- /dev/null
+++ b/pw_log/BUILD
@@ -0,0 +1,72 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+# TODO(pwbug/101): Need to add support for facades/backends to Bazel.
+PW_LOG_BACKEND = "//pw_log_basic"
+
+pw_cc_library(
+    name = "facade",
+    hdrs = [
+        "public/pw_log/levels.h",
+        "public/pw_log/log.h",
+        "public/pw_log/options.h",
+        "public/pw_log/short.h",
+        "public/pw_log/shorter.h",
+    ],
+    includes = ["public"],
+    deps = [
+        PW_LOG_BACKEND + ":headers",
+        "//pw_preprocessor",
+    ],
+)
+
+pw_cc_library(
+    name = "pw_log",
+    deps = [
+        ":facade",
+        PW_LOG_BACKEND + ":headers",
+    ],
+)
+
+pw_cc_library(
+    name = "backend",
+    deps = [
+        PW_LOG_BACKEND,
+    ],
+)
+
+pw_cc_test(
+    name = "test",
+    srcs = [
+        "basic_log_test.cc",
+        "basic_log_test_plain_c.c",
+    ],
+    deps = [
+        ":backend",
+        ":facade",
+        ":pw_log",
+        "//pw_preprocessor",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_log/BUILD.bazel b/pw_log/BUILD.bazel
deleted file mode 100644
index 3afe0af..0000000
--- a/pw_log/BUILD.bazel
+++ /dev/null
@@ -1,148 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_facade",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-load("//pw_protobuf_compiler:proto.bzl", "pw_proto_library")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_facade(
-    name = "facade",
-    hdrs = [
-        "public/pw_log/config.h",
-        "public/pw_log/levels.h",
-        "public/pw_log/log.h",
-        "public/pw_log/options.h",
-        "public/pw_log/short.h",
-        "public/pw_log/shorter.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_preprocessor",
-    ],
-)
-
-pw_cc_library(
-    name = "pw_log",
-    deps = [
-        ":facade",
-        "@pigweed_config//:pw_log_backend",
-    ],
-)
-
-pw_cc_library(
-    name = "glog_adapter",
-    hdrs = [
-        "public/pw_log/glog_adapter.h",
-        "public/pw_log/glog_adapter_config.h",
-        "public/pw_log/internal/glog_adapter.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_assert",
-        "//pw_log",
-        "//pw_preprocessor",
-        "//pw_string",
-    ],
-)
-
-pw_cc_library(
-    name = "proto_utils",
-    srcs = [
-        "proto_utils.cc",
-    ],
-    hdrs = [
-        "public/pw_log/proto_utils.h",
-    ],
-    deps = [
-        ":facade",
-        ":log_proto_cc.pwpb",
-        "//pw_bytes",
-        "//pw_log_tokenized:headers",
-        "//pw_protobuf",
-        "//pw_result",
-    ],
-)
-
-proto_library(
-    name = "log_proto",
-    srcs = [
-        "log.proto",
-    ],
-    import_prefix = "pw_log/proto",
-    strip_import_prefix = "//pw_log",
-    deps = [
-        "//pw_protobuf:common_protos",
-        "//pw_tokenizer:tokenizer_proto",
-    ],
-)
-
-pw_proto_library(
-    name = "log_proto_cc",
-    deps = [":log_proto"],
-)
-
-pw_cc_library(
-    name = "backend_multiplexer",
-    visibility = ["@pigweed_config//:__pkg__"],
-    deps = ["//pw_log_basic"],
-)
-
-pw_cc_test(
-    name = "test",
-    srcs = [
-        "basic_log_test.cc",
-        "basic_log_test_plain_c.c",
-    ],
-    deps = [
-        ":facade",
-        ":pw_log",
-        "//pw_preprocessor",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "glog_adapter_test",
-    srcs = [
-        "glog_adapter_test.cc",
-    ],
-    deps = [
-        ":glog_adapter",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "proto_utils_test",
-    srcs = [
-        "proto_utils_test.cc",
-    ],
-    deps = [
-        ":facade",
-        ":log_proto_cc.pwpb",
-        ":proto_utils",
-        "//pw_preprocessor",
-        "//pw_protobuf",
-        "//pw_protobuf:bytes_utils",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_log/BUILD.gn b/pw_log/BUILD.gn
index 1fbeee1..913e840 100644
--- a/pw_log/BUILD.gn
+++ b/pw_log/BUILD.gn
@@ -15,38 +15,18 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_build/facade.gni")
-import("$dir_pw_build/module_config.gni")
-import("$dir_pw_chrono/backend.gni")
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_log/backend.gni")
 import("$dir_pw_protobuf_compiler/proto.gni")
 import("$dir_pw_unit_test/test.gni")
 
-declare_args() {
-  # The build target that overrides the default configuration options for this
-  # module. This should point to a source set that provides defines through a
-  # public config (which may -include a file or add defines directly).
-  pw_log_CONFIG = pw_build_DEFAULT_MODULE_CONFIG
-
-  # The build target that overrides the default configuration options for the
-  # glog adapter portion of this module.
-  pw_log_GLOG_ADAPTER_CONFIG = pw_build_DEFAULT_MODULE_CONFIG
-}
-
-config("public_include_path") {
+config("default_config") {
   include_dirs = [ "public" ]
-  visibility = [ ":*" ]
-}
-
-pw_source_set("config") {
-  public = [ "public/pw_log/config.h" ]
-  public_configs = [ ":public_include_path" ]
-  public_deps = [ pw_log_CONFIG ]
 }
 
 pw_facade("pw_log") {
   backend = pw_log_BACKEND
-  public_configs = [ ":public_include_path" ]
+  public_configs = [ ":default_config" ]
   public = [
     "public/pw_log/levels.h",
     "public/pw_log/log.h",
@@ -54,67 +34,10 @@
     "public/pw_log/short.h",
     "public/pw_log/shorter.h",
   ]
-  public_deps = [ ":config" ]
-
-  require_link_deps = [ ":impl" ]
-}
-
-pw_source_set("glog_adapter") {
-  public_configs = [ ":public_include_path" ]
-  public = [
-    "public/pw_log/glog_adapter.h",
-    "public/pw_log/glog_adapter_config.h",
-  ]
-  public_deps = [
-    ":config",
-    "$dir_pw_assert",
-    "$dir_pw_log",
-    "$dir_pw_preprocessor",
-    "$dir_pw_string",
-    pw_log_GLOG_ADAPTER_CONFIG,
-  ]
-  sources = [ "public/pw_log/internal/glog_adapter.h" ]
-}
-
-pw_source_set("proto_utils") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_log/proto_utils.h" ]
-  public_deps = [
-    ":pw_log.facade",
-    "$dir_pw_bytes",
-    "$dir_pw_log:protos.pwpb",
-    "$dir_pw_log_tokenized:metadata",
-    "$dir_pw_result",
-  ]
-  deps = [ "$dir_pw_protobuf" ]
-  sources = [ "proto_utils.cc" ]
-}
-
-# pw_log is low-level and ubiquitous. Because of this, it can often cause
-# circular dependencies. This target collects dependencies from the backend that
-# cannot be used because they would cause circular deps.
-#
-# This group ("$dir_pw_log:impl") must listed in pw_build_LINK_DEPS if
-# pw_log_BACKEND is set.
-#
-# pw_log backends must provide their own "impl" target that collects their
-# actual dependencies. The backend "impl" group may be empty if everything can
-# go directly in the backend target without causing circular dependencies.
-group("impl") {
-  public_deps = []
-
-  if (pw_log_BACKEND != "") {
-    public_deps +=
-        [ get_label_info(pw_log_BACKEND, "label_no_toolchain") + ".impl" ]
-  }
 }
 
 pw_test_group("tests") {
-  tests = [
-    ":basic_log_test",
-    ":glog_adapter_test",
-    ":proto_utils_test",
-  ]
+  tests = [ ":basic_log_test" ]
 }
 
 pw_test("basic_log_test") {
@@ -131,44 +54,10 @@
   ]
 }
 
-pw_test("glog_adapter_test") {
-  enable_if = pw_log_BACKEND != ""
-  deps = [
-    ":glog_adapter",
-    pw_log_BACKEND,
-  ]
-  sources = [ "glog_adapter_test.cc" ]
-}
-
-pw_test("proto_utils_test") {
-  enable_if = pw_log_BACKEND != ""
-  deps = [
-    ":proto_utils",
-    ":pw_log.facade",
-    "$dir_pw_log:protos.pwpb",
-    "$dir_pw_preprocessor",
-    "$dir_pw_protobuf",
-    "$dir_pw_protobuf:bytes_utils",
-  ]
-  sources = [ "proto_utils_test.cc" ]
-}
-
 pw_proto_library("protos") {
-  sources = [ "log.proto" ]
-  prefix = "pw_log/proto"
-  deps = [
-    "$dir_pw_protobuf:common_protos",
-    "$dir_pw_tokenizer:proto",
-  ]
+  sources = [ "pw_log_proto/log.proto" ]
 }
 
 pw_doc_group("docs") {
-  sources = [
-    "docs.rst",
-    "protobuf.rst",
-  ]
-  inputs = [
-    "example_layer_diagram.svg",
-    "log.proto",
-  ]
+  sources = [ "docs.rst" ]
 }
diff --git a/pw_log/CMakeLists.txt b/pw_log/CMakeLists.txt
index 9ee4eea..4b77940 100644
--- a/pw_log/CMakeLists.txt
+++ b/pw_log/CMakeLists.txt
@@ -13,112 +13,8 @@
 # the License.
 
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-include($ENV{PW_ROOT}/pw_protobuf_compiler/proto.cmake)
-
-pw_add_module_config(pw_log_CONFIG)
-
-pw_add_module_config(pw_log_GLOG_ADAPTER_CONFIG)
-
-pw_add_module_library(pw_log.config
-  HEADERS
-    public/pw_log/config.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    ${pw_log_CONFIG}
-)
 
 pw_add_facade(pw_log
-  HEADERS
-    public/pw_log/levels.h
-    public/pw_log/log.h
-    public/pw_log/options.h
-    public/pw_log/short.h
-    public/pw_log/shorter.h
-  PUBLIC_INCLUDES
-    public
   PUBLIC_DEPS
-    pw_log.config
-)
-
-pw_add_module_library(pw_log.glog_adapter
-  HEADERS
-    public/pw_log/glog_adapter.h
-    public/pw_log/glog_adapter_config.h
-    public/pw_log/internal/glog_adapter.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_assert
-    pw_log
-    pw_log.config
     pw_preprocessor
-    pw_string
-    ${pw_log_GLOG_ADAPTER_CONFIG}
 )
-
-pw_add_module_library(pw_log.proto_utils
-  HEADERS
-    public/pw_log/proto_utils.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_log.facade
-    pw_bytes
-    pw_log.protos.pwpb
-    pw_log_tokenized.metadata
-    pw_result
-  PRIVATE_DEPS
-    pw_protobuf
-  SOURCES
-    proto_utils.cc
-)
-
-pw_proto_library(pw_log.protos
-  SOURCES
-    log.proto
-  PREFIX
-    pw_log/proto
-  DEPS
-    pw_protobuf.common_protos
-    pw_tokenizer.proto
-)
-
-if(NOT "${pw_log_BACKEND}" STREQUAL "pw_log.NO_BACKEND_SET")
-  pw_add_test(pw_log.basic_log_test
-    SOURCES
-      basic_log_test.cc
-      basic_log_test_plain_c.c
-    DEPS
-      pw_log
-      pw_preprocessor
-    GROUPS
-      modules
-      pw_log
-  )
-
-  pw_add_test(pw_log.glog_adapter_test
-    SOURCES
-      glog_adapter_test.cc
-    DEPS
-      pw_log.glog_adapter
-    GROUPS
-      modules
-      pw_log
-  )
-
-  pw_add_test(pw_log.proto_utils_test
-    SOURCES
-      proto_utils_test.cc
-    DEPS
-      pw_log
-      pw_log.proto_utils
-      pw_log.protos.pwpb
-      pw_preprocessor
-      pw_protobuf
-      pw_protobuf.bytes_utils
-    GROUPS
-      modules
-      pw_log
-  )
-endif()
diff --git a/pw_log/OWNERS b/pw_log/OWNERS
deleted file mode 100644
index 05eeac9..0000000
--- a/pw_log/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-frolv@google.com
-hepler@google.com
-keir@google.com
diff --git a/pw_log/basic_log_test_plain_c.c b/pw_log/basic_log_test_plain_c.c
index 1b7a41d..ee82374 100644
--- a/pw_log/basic_log_test_plain_c.c
+++ b/pw_log/basic_log_test_plain_c.c
@@ -85,11 +85,11 @@
 
   // Log levels other than the standard ones work; what each backend does is
   // implementation defined.
-  PW_LOG(0, PW_LOG_FLAGS, "Custom log level: 0");
-  PW_LOG(1, PW_LOG_FLAGS, "Custom log level: 1");
-  PW_LOG(2, PW_LOG_FLAGS, "Custom log level: 2");
-  PW_LOG(3, PW_LOG_FLAGS, "Custom log level: 3");
-  PW_LOG(100, PW_LOG_FLAGS, "Custom log level: 100");
+  PW_LOG(0, PW_LOG_DEFAULT_FLAGS, "Custom log level: 0");
+  PW_LOG(1, PW_LOG_DEFAULT_FLAGS, "Custom log level: 1");
+  PW_LOG(2, PW_LOG_DEFAULT_FLAGS, "Custom log level: 2");
+  PW_LOG(3, PW_LOG_DEFAULT_FLAGS, "Custom log level: 3");
+  PW_LOG(100, PW_LOG_DEFAULT_FLAGS, "Custom log level: 100");
 
   // Logging from a function.
   LoggingFromFunctionPlainC();
diff --git a/pw_log/docs.rst b/pw_log/docs.rst
index b3eac43..c36490b 100644
--- a/pw_log/docs.rst
+++ b/pw_log/docs.rst
@@ -1,8 +1,8 @@
 .. _module-pw_log:
 
-======
+------
 pw_log
-======
+------
 Pigweed's logging module provides facilities for applications to log
 information about the execution of their application. The module is split into
 two components:
@@ -10,16 +10,6 @@
 1. The facade (this module) which is only a macro interface layer
 2. The backend, provided elsewhere, that implements the low level logging
 
-``pw_log`` also defines a logging protobuf, helper utilities, and an RPC
-service for efficiently storing and transmitting log messages. See
-:ref:`module-pw_log-protobuf` for details.
-
-.. toctree::
-  :hidden:
-
-  protobuf
-
---------------
 Usage examples
 --------------
 Here is a typical usage example, showing setting the module name, and using the
@@ -62,17 +52,43 @@
   }
 
 Layer diagram example: ``stm32f429i-disc1``
-===========================================
+-------------------------------------------
 Below is an example diagram showing how the modules connect together for the
 ``stm32f429i-disc1`` target, where the ``pw_log`` backend is ``pw_log_basic``.
 ``pw_log_basic`` uses the ``pw_sys_io`` module to log in plaintext, which in
 turn outputs to the STM32F429 bare metal backend for ``pw_sys_io``, which is
 ``pw_sys_io_baremetal_stm32f429i``.
 
-.. image:: example_layer_diagram.svg
+.. blockdiag::
+
+  blockdiag {
+    default_fontsize = 14;
+    orientation = portrait;
+
+    group {
+      color = "#AAAAAA";
+      label = "Microcontroller"
+
+      app       [label = "App code"];
+      facade    [label = "pw_log"];
+      backend   [label = "pw_log_basic"];
+      sys_io    [label = "pw_sys_io"];
+      sys_io_bm [label = "pw_sys_io_\nstm32f429"];
+      uart      [label = "UART pins"];
+    }
+    ftdi     [label = "FTDI cable"];
+    computer [label = "Minicom"];
+
+    app -> facade -> backend -> sys_io -> sys_io_bm -> uart -> ftdi -> computer;
+
+    //app -> facade [folded];
+    //backend -> sys_io [folded];
+    //uart -> ftdi [folded];
+  }
 
 Logging macros
-==============
+--------------
+
 These are the primary macros for logging information about the functioning of a
 system, intended to be used directly.
 
@@ -85,8 +101,6 @@
   *flags* - Arbitrary flags the backend can leverage. The semantics of these
   flags are not defined in the facade, but are instead meant as a general
   mechanism for communication bits of information to the logging backend.
-  ``pw_log`` reserves 2 flag bits by default, but log backends may provide for
-  more or fewer flag bits.
 
   Here are some ideas for what a backend might use flags for:
 
@@ -104,7 +118,7 @@
 
   .. code-block:: cpp
 
-    PW_LOG(PW_LOG_FLAGS, PW_LOG_LEVEL_INFO, "Temp is %d degrees", temp);
+    PW_LOG(PW_LOG_DEFAULT_FLAGS, PW_LOG_LEVEL_INFO, "Temp is %d degrees", temp);
     PW_LOG(UNRELIABLE_DELIVERY, PW_LOG_LEVEL_ERROR, "It didn't work!");
 
   .. note::
@@ -122,41 +136,13 @@
 .. cpp:function:: PW_LOG_ERROR(fmt, ...)
 .. cpp:function:: PW_LOG_CRITICAL(fmt, ...)
 
-  Shorthand for `PW_LOG(PW_LOG_FLAGS, <level>, fmt, ...)`.
+  Shorthand for `PW_LOG(PW_LOG_DEFAULT_FLAGS, <level>, fmt, ...)`.
 
---------------------
-Module configuration
---------------------
-This module has configuration options that globally affect the behavior of
-pw_log via compile-time configuration of this module, see the
-:ref:`module documentation <module-structure-compile-time-configuration>` for
-more details.
-
-.. c:macro:: PW_LOG_LEVEL_DEFAULT
-
-  Controls the default value of ``PW_LOG_LEVEL``. Setting
-  ``PW_LOG_LEVEL_DEFAULT`` will change the behavior of all source files that
-  have not explicitly set ``PW_LOG_LEVEL``. Defaults to ``PW_LOG_LEVEL_DEBUG``.
-
-.. c:macro:: PW_LOG_FLAGS_DEFAULT
-
-  Controls the default value of ``PW_LOG_FLAGS``. Setting
-  ``PW_LOG_FLAGS_DEFAULT`` will change the behavior of all source files that
-  have not explicitly set ``PW_LOG_FLAGS``. Defaults to ``0``.
-
-.. c:macro:: PW_LOG_ENABLE_IF_DEFAULT
-
-  Controls the default value of ``PW_LOG_ENABLE_IF``. Setting
-  ``PW_LOG_ENABLE_IF_DEFAULT`` will change the behavior of all source files that
-  have not explicitly set ``PW_LOG_ENABLE_IF``. Defaults to
-  ``((level) >= PW_LOG_LEVEL)``.
-
-
-Per-source file configuration
-=============================
-This module defines macros that can be overridden to independently control the
-behavior of ``pw_log`` statements for each C or C++ source file. To override
-these macros, add ``#define`` statements for them before including headers.
+Option macros
+-------------
+This module defines macros that can be overridden to control the behavior of
+``pw_log`` statements. To override these macros, add ``#define`` statements
+for them before including headers.
 
 The option macro definitions must be visibile to ``pw_log/log.h`` the first time
 it is included. To handle potential transitive includes, place these
@@ -187,20 +173,20 @@
   ``PW_LOG_MODULE_NAME_DEFINED`` macro is set to ``1`` or ``0`` to indicate
   whether ``PW_LOG_MODULE_NAME`` was overridden.
 
-.. c:macro:: PW_LOG_FLAGS
+.. c:macro:: PW_LOG_DEFAULT_FLAGS
 
   Log flags to use for the ``PW_LOG_<level>`` macros. Different flags may be
   applied when using the ``PW_LOG`` macro directly.
 
   Log backends use flags to change how they handle individual log messages.
   Potential uses include assigning logs priority or marking them as containing
-  personal information. Defaults to ``PW_LOG_FLAGS_DEFAULT``.
+  personal information. Defaults to ``0``.
 
 .. c:macro:: PW_LOG_LEVEL
 
    Filters logs by level. Source files that define ``PW_LOG_LEVEL`` will display
    only logs at or above the chosen level. Log statements below this level will
-   be compiled out of optimized builds. Defaults to ``PW_LOG_LEVEL_DEFAULT``.
+   be compiled out of optimized builds. Defaults to ``PW_LOG_LEVEL_DEBUG``.
 
    Example:
 
@@ -216,12 +202,11 @@
        PW_LOG_WARN("This is above INFO level, and will display");
      }
 
-.. c:macro:: PW_LOG_ENABLE_IF(level, flags)
+.. c:function:: PW_LOG_ENABLE_IF(level, flags)
 
    Filters logs by an arbitrary expression based on ``level`` and ``flags``.
    Source files that define ``PW_LOG_ENABLE_IF(level, flags)`` will display if
-   the given expression evaluates true. Defaults to
-   ``PW_LOG_ENABLE_IF_DEFAULT``.
+   the given expression evaluates true.
 
    Example:
 
@@ -259,9 +244,9 @@
   At this time, only compile time filtering is supported. In the future, we
   plan to add support for runtime filtering.
 
-------------------
 Logging attributes
 ------------------
+
 The logging facade in Pigweed is designed to facilitate the capture of at least
 the following attributes:
 
@@ -284,64 +269,11 @@
 between call site code size, call site run time, wire format size, logging
 complexity, and more.
 
-.. _module-pw_log-circular-deps:
-
-----------------------------------------------
-Avoiding circular dependencies with ``PW_LOG``
-----------------------------------------------
-Because logs are so widely used, including in low-level libraries, it is
-common for the ``pw_log`` backend to cause circular dependencies. Because of
-this, log backends may avoid declaring explicit dependencies, instead relying
-on include paths to access header files.
-
-In GN, the ``pw_log`` backend's full implementation with true dependencies is
-made available through the ``$dir_pw_log:impl`` group. When ``pw_log_BACKEND``
-is set, ``$dir_pw_log:impl`` must be listed in the ``pw_build_LINK_DEPS``
-variable. See :ref:`module-pw_build-link-deps`.
-
-In the ``pw_log``, the backend's full implementation is placed in the
-``$pw_log_BACKEND.impl`` target. ``$dir_pw_log:impl`` depends on this
-backend target. The ``$pw_log_BACKEND.impl`` target may be an empty group if
-the backend target can use its dependencies directly without causing circular
-dependencies.
-
-In order to break dependency cycles, the ``pw_log_BACKEND`` target may need
-to directly provide dependencies through include paths only, rather than GN
-``public_deps``. In this case, GN header checking can be disabled with
-``check_includes = false``.
-
-----------------------
-Google Logging Adapter
-----------------------
-Pigweed provides a minimal C++ stream-style Google Log set of adapter
-macros around PW_LOG under ``pw_log/glog_adapter.h`` for compatibility with
-non-embedded code. While it is effective for porting server code to
-microcontrollers quickly, we do not advise embedded projects use that approach
-unless absolutely necessary.
-
-Configuration
-==============
-
-.. c:macro:: PW_LOG_CFG_GLOG_BUFFER_SIZE_BYTES
-
-  The size of the stack-allocated buffer used by the Google Logging (glog)
-  macros. This only affects the glog macros provided through pw_log/glog.h.
-
-  Pigweed strongly recommends sticking to printf-style logging instead
-  of C++ stream-style Google Log logging unless absolutely necessary. The glog
-  macros are only provided for compatibility with non-embedded code. See
-  :ref:`module-pw_log-design-discussion` for more details.
-
-  Undersizing this buffer will result in truncated log messages.
-
------------------
 Design discussion
 -----------------
 
-.. _module-pw_log-design-discussion:
-
 Why not use C++ style stream logging operators like Google Log?
-===============================================================
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 There are multiple reasons to avoid the C++ stream logging style in embedded,
 but the biggest reason is that C++ stream logging defeats log tokenization. By
 having the string literals broken up between ``<<`` operators, tokenization
@@ -387,14 +319,18 @@
 - is C compatibile
 - has smaller call sites
 
-See also :ref:`module-pw_log_tokenized` for details on leveraging Pigweed's
-tokenizer module for logging.
+The Pigweed authors additionally maintain a C++ stream-style embedded logging
+library for compatibility with non-embedded code. While it is effective for
+porting server code to microcontrollers quickly, we do not advise embedded
+projects use that approach unless absolutely necessary.
 
-See also :ref:`module-pw_tokenizer` for details on Pigweed's tokenizer,
-which is useful for more than just logging.
+- See also :ref:`module-pw_log_tokenized` for details on leveraging Pigweed's
+  tokenizer module for logging.
+- See also :ref:`module-pw_tokenizer` for details on Pigweed's tokenizer,
+  which is useful for more than just logging.
 
 Why does the facade use header redirection instead of C functions?
-==================================================================
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 Without header redirection, it is not possible to do sophisticated macro
 transforms in the backend. For example, to apply tokenization to log strings,
 the backend must define the handling macros. Additionally, compile-time
@@ -404,7 +340,7 @@
 restriction we want to avoid.
 
 Why is the module name done as a preprocessor define rather than an argument?
-=============================================================================
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 APIs are a balance between power and ease of use. In the practical cases we
 have seen over the years, most translation units only need to log to one
 module, like ``"BLE"``, ``"PWR"``, ``"BAT"`` and so on. Thus, adding the
@@ -412,14 +348,3 @@
 something that are typically added on a per-log-statement basis, and is why the
 flags are added on a per-call basis (though hidden through the high-level
 macros).
-
---------------
-pw_log in Java
---------------
-``pw_log`` provides a thin Java logging class that uses Google's `Flogger
-<https://google.github.io/flogger/>`_ API. The purpose of this wrapper is to
-support logging on platforms that do not support Flogger. The main
-implementation in ``pw_log/java/main`` simply wraps a
-``com.google.common.flogger.FluentLogger``. An implementation that logs to
-Android's ``android.util.Log`` instead is provided in
-``pw_log/java/android_main``.
diff --git a/pw_log/example_layer_diagram.svg b/pw_log/example_layer_diagram.svg
deleted file mode 100644
index 2840284..0000000
--- a/pw_log/example_layer_diagram.svg
+++ /dev/null
@@ -1,78 +0,0 @@
-<!--
-This SVG was originally created with blockdiag.
-
-  blockdiag {
-    default_fontsize = 14;
-    orientation = portrait;
-
-    group {
-      color = "#AAAAAA";
-      label = "Microcontroller"
-
-      app       [label = "App code"];
-      facade    [label = "pw_log"];
-      backend   [label = "pw_log_basic"];
-      sys_io    [label = "pw_sys_io"];
-      sys_io_bm [label = "pw_sys_io_\nstm32f429"];
-      uart      [label = "UART pins"];
-    }
-    ftdi     [label = "FTDI cable"];
-    computer [label = "Minicom"];
-
-    app -> facade -> backend -> sys_io -> sys_io_bm -> uart -> ftdi -> computer;
-
-    //app -> facade [folded];
-    //backend -> sys_io [folded];
-    //uart -> ftdi [folded];
-  }
--->
-<svg height="680" viewBox="0 0 256 680" width="256" xmlns="http://www.w3.org/2000/svg" xmlns:inkspace="http://www.inkscape.org/namespaces/inkscape" xmlns:xlink="http://www.w3.org/1999/xlink" style="background-color:white">
-<defs id="defs_block">
-<filter height="1.504" id="filter_blur" inkspace:collect="always" width="1.1575" x="-0.07875" y="-0.252">
-<feGaussianBlur id="feGaussianBlur3780" inkspace:collect="always" stdDeviation="4.2"></feGaussianBlur>
-</filter>
-</defs>
-<title>Example pw_log Layer Diagram</title>
-<desc></desc>
-<rect fill="rgb(170,170,170)" height="460" style="filter:url(#filter_blur)" width="144" x="56" y="30"></rect>
-<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="67" y="46"></rect>
-<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="67" y="126"></rect>
-<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="67" y="206"></rect>
-<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="67" y="286"></rect>
-<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="67" y="366"></rect>
-<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="67" y="446"></rect>
-<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="67" y="526"></rect>
-<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="67" y="606"></rect>
-<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="64" y="40"></rect>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="14" font-style="normal" font-weight="normal" text-anchor="middle" textLength="61" x="128.5" y="67">App code</text>
-<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="64" y="120"></rect>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="14" font-style="normal" font-weight="normal" text-anchor="middle" textLength="45" x="128.5" y="147">pw_log</text>
-<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="64" y="200"></rect>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="14" font-style="normal" font-weight="normal" text-anchor="middle" textLength="91" x="128.5" y="227">pw_log_basic</text>
-<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="64" y="280"></rect>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="14" font-style="normal" font-weight="normal" text-anchor="middle" textLength="68" x="128.0" y="307">pw_sys_io</text>
-<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="64" y="360"></rect>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="14" font-style="normal" font-weight="normal" text-anchor="middle" textLength="76" x="128.0" y="379">pw_sys_io_</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="14" font-style="normal" font-weight="normal" text-anchor="middle" textLength="68" x="128.0" y="395">stm32f429</text>
-<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="64" y="440"></rect>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="14" font-style="normal" font-weight="normal" text-anchor="middle" textLength="68" x="128.0" y="467">UART pins</text>
-<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="64" y="520"></rect>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="14" font-style="normal" font-weight="normal" text-anchor="middle" textLength="76" x="128.0" y="547">FTDI cable</text>
-<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="64" y="600"></rect>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="14" font-style="normal" font-weight="normal" text-anchor="middle" textLength="53" x="128.5" y="627">Minicom</text>
-<path d="M 128 560 L 128 592" fill="none" stroke="rgb(0,0,0)"></path>
-<polygon fill="rgb(0,0,0)" points="128,599 124,592 132,592 128,599" stroke="rgb(0,0,0)"></polygon>
-<path d="M 128 80 L 128 112" fill="none" stroke="rgb(0,0,0)"></path>
-<polygon fill="rgb(0,0,0)" points="128,119 124,112 132,112 128,119" stroke="rgb(0,0,0)"></polygon>
-<path d="M 128 160 L 128 192" fill="none" stroke="rgb(0,0,0)"></path>
-<polygon fill="rgb(0,0,0)" points="128,199 124,192 132,192 128,199" stroke="rgb(0,0,0)"></polygon>
-<path d="M 128 240 L 128 272" fill="none" stroke="rgb(0,0,0)"></path>
-<polygon fill="rgb(0,0,0)" points="128,279 124,272 132,272 128,279" stroke="rgb(0,0,0)"></polygon>
-<path d="M 128 320 L 128 352" fill="none" stroke="rgb(0,0,0)"></path>
-<polygon fill="rgb(0,0,0)" points="128,359 124,352 132,352 128,359" stroke="rgb(0,0,0)"></polygon>
-<path d="M 128 400 L 128 432" fill="none" stroke="rgb(0,0,0)"></path>
-<polygon fill="rgb(0,0,0)" points="128,439 124,432 132,432 128,439" stroke="rgb(0,0,0)"></polygon>
-<path d="M 128 480 L 128 512" fill="none" stroke="rgb(0,0,0)"></path>
-<polygon fill="rgb(0,0,0)" points="128,519 124,512 132,512 128,519" stroke="rgb(0,0,0)"></polygon>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="14" font-style="normal" font-weight="normal" text-anchor="middle" textLength="114" x="128.0" y="37">Microcontroller</text>
-</svg>
diff --git a/pw_log/glog_adapter_test.cc b/pw_log/glog_adapter_test.cc
deleted file mode 100644
index f826503..0000000
--- a/pw_log/glog_adapter_test.cc
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-// TODO(pwbug/88): Add verification of the actually logged statements.
-
-// clang-format off
-#define PW_LOG_MODULE_NAME "TST"
-#define PW_LOG_LEVEL PW_LOG_LEVEL_DEBUG
-
-#include "pw_log/glog_adapter.h"
-
-#include "gtest/gtest.h"
-// clang-format on
-
-namespace pw::log {
-namespace {
-
-volatile bool conditional;
-
-TEST(Glog, Debug) { LOG(DEBUG) << "LOG(DEBUG) works"; }
-
-TEST(Glog, ConditionalDebug) {
-  conditional = true;
-  LOG_IF(DEBUG, conditional) << "LOG_IF(DEBUG, true) works";
-  conditional = false;
-  LOG_IF(DEBUG, conditional) << "You should not see this log";
-}
-
-TEST(Glog, Info) { LOG(INFO) << "LOG(INFO) works"; }
-
-TEST(Glog, ConditionalInfo) {
-  conditional = true;
-  LOG_IF(INFO, conditional) << "LOG_IF(INFO, true) works";
-  conditional = false;
-  LOG_IF(INFO, conditional) << "You should not see this log";
-}
-
-TEST(Glog, Warning) { LOG(WARNING) << "LOG(WARNING) works"; }
-
-TEST(Glog, ConditionalWarning) {
-  conditional = true;
-  LOG_IF(WARNING, conditional) << "LOG_IF(WARNING, true) works";
-  conditional = false;
-  LOG_IF(WARNING, conditional) << "You should not see this log";
-}
-
-TEST(Glog, Error) { LOG(ERROR) << "LOG(ERROR) works"; }
-
-TEST(Glog, ConditionalError) {
-  conditional = true;
-  LOG_IF(ERROR, conditional) << "LOG_IF(ERROR, true) works";
-  conditional = false;
-  LOG_IF(ERROR, conditional) << "You should not see this log";
-}
-
-TEST(Glog, Fatal) {
-  conditional = false;
-  if (conditional) {
-    LOG(FATAL) << "LOG(FATAL) compiles but you should not see this log";
-  }
-}
-
-TEST(Glog, ConditionalFatal) {
-  conditional = false;
-  LOG_IF(FATAL, conditional) << "LOG_IF(FATAL, false) compiles but you should "
-                             << "not see this log";
-}
-
-TEST(Glog, Dfatal) {
-  conditional = false;
-  if (conditional) {
-#if defined(NDEBUG)
-    LOG(DFATAL) << "LOG(DFATAL) works through PW_LOG_ERROR as NDEBUG is set";
-#else   // !defined(NDEBUG)
-    LOG(DFATAL) << "LOG(DFATAL) compiles but you should not see this log";
-#endif  // defined(NDEBUG)
-  }
-}
-
-TEST(Glog, ConditionalDfatal) {
-#if defined(NDEBUG)
-  conditional = true;
-  LOG_IF(DFATAL, conditional) << "LOG_IF(DFATAL, true) works through "
-                              << "PW_LOG_ERROR as NDEBUG is set";
-#endif  // defined(NDEBUG)
-  conditional = false;
-  LOG_IF(DFATAL, conditional) << "LOG_IF(DFATAL, false) compiles but you "
-                              << "should not see this log";
-}
-
-}  // namespace
-}  // namespace pw::log
diff --git a/pw_log/java/android_main/dev/pigweed/pw_log/Logger.java b/pw_log/java/android_main/dev/pigweed/pw_log/Logger.java
deleted file mode 100644
index 3390011..0000000
--- a/pw_log/java/android_main/dev/pigweed/pw_log/Logger.java
+++ /dev/null
@@ -1,103 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 dev.pigweed.pw_log;
-
-import android.util.Log;
-import java.util.logging.Level;
-
-/**
- * Partial implementation of the com.google.common.flogger.FluentLogger API that
- * logs to android.util.Log.
- */
-public final class Logger {
-  private final String tag;
-
-  public final class AndroidLogApi {
-    private final int level;
-
-    private Throwable cause = null;
-
-    private AndroidLogApi(Level level) {
-      if (level == Level.FINEST || level == Level.FINER) {
-        this.level = Log.VERBOSE;
-      } else if (level == Level.FINE || level == Level.CONFIG) {
-        this.level = Log.DEBUG;
-      } else if (level == Level.WARNING) {
-        this.level = Log.WARN;
-      } else if (level == Level.SEVERE) {
-        this.level = Log.ERROR;
-      } else {
-        this.level = Log.INFO;
-      }
-    }
-
-    public AndroidLogApi withCause(Throwable cause) {
-      this.cause = cause;
-      return this;
-    }
-
-    public void log(String message) {
-      if (cause != null) {
-        message = String.format("%s: %s", cause, message);
-      }
-
-      Log.println(level, tag, message);
-    }
-
-    public void log(String message, Object... args) {
-      log(String.format(message, args));
-    }
-  }
-
-  public static Logger forClass(Class<?> enclosingClass) {
-    return new Logger(enclosingClass.getSimpleName());
-  }
-
-  private Logger(String tag) {
-    this.tag = tag;
-  }
-
-  public AndroidLogApi at(Level level) {
-    return new AndroidLogApi(level);
-  }
-
-  public AndroidLogApi atSevere() {
-    return at(Level.SEVERE);
-  }
-
-  public AndroidLogApi atWarning() {
-    return at(Level.WARNING);
-  }
-
-  public AndroidLogApi atInfo() {
-    return at(Level.INFO);
-  }
-
-  public AndroidLogApi atConfig() {
-    return at(Level.CONFIG);
-  }
-
-  public AndroidLogApi atFine() {
-    return at(Level.FINE);
-  }
-
-  public AndroidLogApi atFiner() {
-    return at(Level.FINER);
-  }
-
-  public AndroidLogApi atFinest() {
-    return at(Level.FINEST);
-  }
-}
diff --git a/pw_log/java/main/dev/pigweed/pw_log/BUILD.bazel b/pw_log/java/main/dev/pigweed/pw_log/BUILD.bazel
deleted file mode 100644
index 8ebacb0..0000000
--- a/pw_log/java/main/dev/pigweed/pw_log/BUILD.bazel
+++ /dev/null
@@ -1,25 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-# Logging API that maps to Google's Flogger (https://google.github.io/flogger/),
-# or an alternate API if Flogger is not supported.
-
-java_library(
-    name = "pw_log",
-    srcs = ["Logger.java"],
-    visibility = ["//visibility:public"],
-    deps = [
-        "@maven//:com_google_flogger_flogger",
-    ],
-)
diff --git a/pw_log/java/main/dev/pigweed/pw_log/Logger.java b/pw_log/java/main/dev/pigweed/pw_log/Logger.java
deleted file mode 100644
index f1ba744..0000000
--- a/pw_log/java/main/dev/pigweed/pw_log/Logger.java
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 dev.pigweed.pw_log;
-
-import com.google.common.flogger.FluentLogger;
-import java.util.logging.Level;
-
-/**
- * Partial implementation of the com.google.common.flogger.FluentLogger API that
- * wraps a FluentLogger instance.
- *
- * This class is used instead of directly logging to FluentLogger to support
- * swapping the implementation on systems that don't support FluentLogger (i.e.
- * Android).
- */
-@SuppressWarnings("FloggerSplitLogStatement")
-public final class Logger {
-  private final FluentLogger wrappedLogger;
-
-  public static Logger forClass(Class<?> enclosingClass) {
-    return new Logger(FluentLogger.forEnclosingClass());
-  }
-
-  private Logger(FluentLogger fluentLogger) {
-    this.wrappedLogger = fluentLogger;
-  }
-
-  public FluentLogger.Api at(Level level) {
-    return wrappedLogger.at(level);
-  }
-
-  public FluentLogger.Api atSevere() {
-    return at(Level.SEVERE);
-  }
-
-  public FluentLogger.Api atWarning() {
-    return at(Level.WARNING);
-  }
-
-  public FluentLogger.Api atInfo() {
-    return at(Level.INFO);
-  }
-
-  public FluentLogger.Api atConfig() {
-    return at(Level.CONFIG);
-  }
-
-  public FluentLogger.Api atFine() {
-    return at(Level.FINE);
-  }
-
-  public FluentLogger.Api atFiner() {
-    return at(Level.FINER);
-  }
-
-  public FluentLogger.Api atFinest() {
-    return at(Level.FINEST);
-  }
-}
diff --git a/pw_log/log.proto b/pw_log/log.proto
deleted file mode 100644
index 611724a..0000000
--- a/pw_log/log.proto
+++ /dev/null
@@ -1,231 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-syntax = "proto3";
-
-package pw.log;
-
-import "pw_protobuf_protos/common.proto";
-import "pw_tokenizer/proto/options.proto";
-
-option java_outer_classname = "Log";
-
-// A log message and metadata. Logs come in a few different forms:
-//
-//  1. A tokenized log message (recommended for production)
-//  2. A non-tokenized log message (good for development)
-//  3. A "log missed" tombstone, indicating that some logs were dropped
-//
-// Size analysis for tokenized log messages, including each field's proto tag:
-//
-//  - message     - 6-12 bytes; depending on number and value of arguments
-//  - line_level  - 3 bytes; 4 bytes if line > 2048 (uncommon)
-//  - timestamp   - 3 bytes; assuming delta encoding
-//  - thread      - 2-6 bytes; depending on whether value is a token or string
-//
-// Adding the fields gives the total proto message size:
-//
-//    6-12 bytes - log
-//    9-15 bytes - log + level + line
-//   12-18 bytes - log + level + line + timestamp
-//
-// An analysis of a project's log token database revealed the following
-// distribution of the number of arguments to log messages:
-//
-//   # args   # messages
-//     0         2,700
-//     1         2,400
-//     2         1,200
-//     3+        1,000
-//
-// Note: The below proto makes some compromises compared to what one might
-// expect for a "clean" proto design, in order to shave bytes off of the
-// messages. It is critical that the log messages are as small as possible to
-// enable storing more logs in limited memory. This is why, for example, there
-// is no separate "DroppedLog" type, or a "TokenizedLog" and "StringLog", which
-// would add at least 2 extra bytes per message
-message LogEntry {
-  // The log message, which may be tokenized.
-  //
-  // If tokenized logging is used, implementations may encode metadata in the
-  // log message rather than as separate proto fields. This reduces the size of
-  // the protobuf with no overhead.
-  //
-  // The standard format for encoding metadata in the log message is defined by
-  // the pw_log_tokenized module. The message and metadata are encoded as
-  // key-value pairs using ■ and ♦ as delimiters. For example:
-  //
-  //  ■msg♦This is the log message: %d■module♦wifi■file♦../path/to/file.cc
-  //
-  // See http://pigweed.dev/pw_log_tokenized for full details. When
-  // pw_log_tokenized is used, this metadata is automatically included as
-  // described.
-  //
-  // The level and flags are not included since they may be runtime values and
-  // thus cannot always be tokenized. The line number is not included because
-  // line numbers change frequently and a new token is created for each line.
-  //
-  // Size analysis when tokenized:
-  //
-  //   tag+wire = 1 byte
-  //   size     = 1 byte; payload will almost always be < 127 bytes
-  //   payload  = N bytes; typically 4-10 in practice
-  //
-  // Total: 2 + N ~= 6-12 bytes
-  optional bytes message = 1 [(tokenizer.format) = TOKENIZATION_OPTIONAL];
-
-  // Packed log level and line number. Structure:
-  //
-  //   Level: Bottom 3 bits; level = line_level & 0x7
-  //   Line: Remaining bits; line = (line_level >> 3)
-  //
-  // Note: This packing saves two bytes per log message in most cases compared
-  // to having line and level separately; and is zero-cost if the log backend
-  // omits the line number.
-  optional uint32 line_level = 2;
-
-  // Some log messages have flags to indicate attributes such as whether they
-  // are from an assert or if they contain PII. The particular flags are
-  // product- and implementation-dependent.
-  optional uint32 flags = 3;
-
-  // Timestamps are either specified with an absolute timestamp or relative to
-  // the previous log entry.
-  oneof time {
-    // The absolute timestamp in implementation-defined ticks. Applications
-    // determine how to interpret this on the receiving end. In the simplest
-    // case, these ticks might be milliseconds or microseconds since boot.
-    // Applications could also access clock information out-of-band with a
-    // ClockParameters protobuf.
-    int64 timestamp = 4;
-
-    // Time since the last entry in implementation-defined ticks, as for the
-    // timestamp field. This enables delta encoding when batching entries
-    // together.
-    //
-    // Size analysis for this field including tag and varint, assuming 1 kHz
-    // ticks:
-    //
-    //           < 127 ms gap == 127 ms      ==  7 bits == 2 bytes
-    //        < 16,000 ms gap ==  16 seconds == 14 bits == 3 bytes
-    //     < 2,000,000 ms gap ==  35 minutes == 21 bits == 4 bytes
-    //   < 300,000,000 ms gap ==  74 hours   == 28 bits == 5 bytes
-    //
-    // Log bursts will thus consume just 2 bytes (tag + up to 127ms delta) for
-    // the timestamp, which is a good improvement over an absolute timestamp.
-    int64 time_since_last_entry = 5;
-  }
-
-  // When the log buffers are full but more logs come in, the logs are counted
-  // and a special log message is omitted with only counts for the number of
-  // messages dropped.
-  optional uint32 dropped = 6;
-
-  // The PW_LOG_MODULE_NAME for this log message.
-  optional bytes module = 7 [(tokenizer.format) = TOKENIZATION_OPTIONAL];
-
-  // The file path where this log was created, if not encoded in the message.
-  optional bytes file = 8 [(tokenizer.format) = TOKENIZATION_OPTIONAL];
-
-  // The task or thread name that created the log message. If the log was not
-  // created on a thread, it should use a name appropriate to that context.
-  optional bytes thread = 9 [(tokenizer.format) = TOKENIZATION_OPTIONAL];
-
-  // The following fields are planned but will not be added until they are
-  // needed. Protobuf field numbers over 15 use an extra byte, so these fields
-  // are left out for now to avoid reserving field numbers unnecessarily.
-
-  // Represents the device from which the log originated. The meaning of this
-  // field is implementation defined
-  // optional uint32 source_id = ?;
-
-  // Some messages are associated with trace events, which may carry additional
-  // contextual data. This is a tuple of a data format string which could be
-  // used by the decoder to identify the data (e.g. printf-style tokens) and the
-  // data itself in bytes.
-  // optional bytes data_format = ?
-  //     [(tokenizer.format) = TOKENIZATION_OPTIONAL];
-  // optional bytes data = ?;
-}
-
-message LogRequest {}
-
-message LogEntries {
-  repeated LogEntry entries = 1;
-  optional uint32 first_entry_sequence_id = 2;
-}
-
-// RPC service for accessing logs.
-service Logs {
-  rpc Listen(LogRequest) returns (stream LogEntries);
-}
-
-message FilterRule {
-  // Log level values match pw_log/levels.h. Enum names avoid collissions with
-  // possible macros.
-  enum Level {
-    ANY_LEVEL = 0;
-    DEBUG_LEVEL = 1;
-    INFO_LEVEL = 2;
-    WARN_LEVEL = 3;
-    ERROR_LEVEL = 4;
-    CRITICAL_LEVEL = 5;
-    FATAL_LEVEL = 7;
-  };
-  // Condition 1: log.level >= level_greater_than_or_equal.
-  Level level_greater_than_or_equal = 1;
-
-  // Condition 2: (module_equals.size() == 0) || (log.module == module_equals);
-  bytes module_equals = 2 [(tokenizer.format) = TOKENIZATION_OPTIONAL];
-
-  // Condition 3: (any_flags_set == 0) || (log.flags & any_flags_set) != 0))
-  uint32 any_flags_set = 3;
-
-  // Action to take if all conditions are met and rule is not inactive.
-  enum Action {
-    INACTIVE = 0;  // Ignore the rule entirely.
-    KEEP = 1;      // Keep the log entry if all conditions are met.
-    DROP = 2;      // Drop the log entry if all conditions are met
-  };
-  Action action = 4;
-}
-
-// A filter is a series of rules. First matching rule wins.
-message Filter {
-  repeated FilterRule rule = 1;
-}
-
-message SetFilterRequest {
-  // A filter can be identified by a human readable string, token, or number.
-  bytes filter_id = 1 [(tokenizer.format) = TOKENIZATION_OPTIONAL];
-
-  Filter filter = 2;
-}
-
-message GetFilterRequest {
-  bytes filter_id = 1 [(tokenizer.format) = TOKENIZATION_OPTIONAL];
-}
-
-message FilterIdListRequest {}
-
-message FilterIdListResponse {
-  repeated bytes filter_id = 1 [(tokenizer.format) = TOKENIZATION_OPTIONAL];
-}
-
-// RPC service for retrieving and modifying log filters.
-service Filters {
-  rpc SetFilter(SetFilterRequest) returns (pw.protobuf.Empty);
-  rpc GetFilter(GetFilterRequest) returns (Filter);
-  rpc ListFilterIds(FilterIdListRequest) returns (FilterIdListResponse);
-}
diff --git a/pw_log/proto_utils.cc b/pw_log/proto_utils.cc
deleted file mode 100644
index 23739aa..0000000
--- a/pw_log/proto_utils.cc
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_log/proto_utils.h"
-
-#include <span>
-#include <string_view>
-
-#include "pw_bytes/endian.h"
-#include "pw_log/levels.h"
-#include "pw_log_tokenized/metadata.h"
-#include "pw_protobuf/wire_format.h"
-
-namespace pw::log {
-
-Result<ConstByteSpan> EncodeLog(int level,
-                                unsigned int flags,
-                                std::string_view module_name,
-                                std::string_view thread_name,
-                                std::string_view file_name,
-                                int line_number,
-                                int64_t ticks_since_epoch,
-                                std::string_view message,
-                                ByteSpan encode_buffer) {
-  // Encode message to the LogEntry protobuf.
-  LogEntry::MemoryEncoder encoder(encode_buffer);
-
-  if (message.empty()) {
-    return Status::InvalidArgument();
-  }
-
-  // Defer status checks until the end.
-  Status status = encoder.WriteMessage(std::as_bytes(std::span(message)));
-  status = encoder.WriteLineLevel(PackLineLevel(line_number, level));
-  if (flags != 0) {
-    status = encoder.WriteFlags(flags);
-  }
-  status = encoder.WriteTimestamp(ticks_since_epoch);
-
-  // Module name and file name may or may not be present.
-  if (!module_name.empty()) {
-    status = encoder.WriteModule(std::as_bytes(std::span(module_name)));
-  }
-  if (!file_name.empty()) {
-    status = encoder.WriteFile(std::as_bytes(std::span(file_name)));
-  }
-  if (!thread_name.empty()) {
-    status = encoder.WriteThread(std::as_bytes(std::span(thread_name)));
-  }
-  PW_TRY(encoder.status());
-  return ConstByteSpan(encoder);
-}
-
-LogEntry::MemoryEncoder CreateEncoderAndEncodeTokenizedLog(
-    pw::log_tokenized::Metadata metadata,
-    ConstByteSpan tokenized_data,
-    int64_t ticks_since_epoch,
-    ByteSpan encode_buffer) {
-  // Encode message to the LogEntry protobuf.
-  LogEntry::MemoryEncoder encoder(encode_buffer);
-
-  // Defer status checks until the end.
-  Status status = encoder.WriteMessage(tokenized_data);
-  status = encoder.WriteLineLevel(
-      PackLineLevel(metadata.line_number(), metadata.level()));
-  if (metadata.flags() != 0) {
-    status = encoder.WriteFlags(metadata.flags());
-  }
-  status = encoder.WriteTimestamp(ticks_since_epoch);
-  if (metadata.module() != 0) {
-    const uint32_t little_endian_module =
-        bytes::ConvertOrderTo(std::endian::little, metadata.module());
-    status =
-        encoder.WriteModule(std::as_bytes(std::span(&little_endian_module, 1)));
-  }
-  return encoder;
-}
-
-}  // namespace pw::log
diff --git a/pw_log/proto_utils_test.cc b/pw_log/proto_utils_test.cc
deleted file mode 100644
index 7b01800..0000000
--- a/pw_log/proto_utils_test.cc
+++ /dev/null
@@ -1,516 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_log/proto_utils.h"
-
-#include "gtest/gtest.h"
-#include "pw_bytes/span.h"
-#include "pw_log/levels.h"
-#include "pw_log/proto/log.pwpb.h"
-#include "pw_protobuf/bytes_utils.h"
-#include "pw_protobuf/decoder.h"
-
-namespace pw::log {
-
-void VerifyTokenizedLogEntry(pw::protobuf::Decoder& entry_decoder,
-                             pw::log_tokenized::Metadata expected_metadata,
-                             ConstByteSpan expected_tokenized_data,
-                             const int64_t expected_timestamp,
-                             ConstByteSpan expected_thread_name) {
-  ConstByteSpan tokenized_data;
-  EXPECT_TRUE(entry_decoder.Next().ok());  // message [tokenized]
-  EXPECT_EQ(entry_decoder.FieldNumber(),
-            static_cast<uint32_t>(log::LogEntry::Fields::MESSAGE));
-  EXPECT_TRUE(entry_decoder.ReadBytes(&tokenized_data).ok());
-  EXPECT_TRUE(std::memcmp(tokenized_data.begin(),
-                          expected_tokenized_data.begin(),
-                          expected_tokenized_data.size()) == 0);
-
-  uint32_t line_level;
-  EXPECT_TRUE(entry_decoder.Next().ok());  // line_level
-  EXPECT_EQ(entry_decoder.FieldNumber(),
-            static_cast<uint32_t>(log::LogEntry::Fields::LINE_LEVEL));
-  EXPECT_TRUE(entry_decoder.ReadUint32(&line_level).ok());
-
-  uint32_t line_number;
-  uint8_t level;
-  std::tie(line_number, level) = UnpackLineLevel(line_level);
-  EXPECT_EQ(expected_metadata.level(), level);
-  EXPECT_EQ(expected_metadata.line_number(), line_number);
-
-  if (expected_metadata.flags() != 0) {
-    uint32_t flags;
-    EXPECT_TRUE(entry_decoder.Next().ok());  // flags
-    EXPECT_EQ(entry_decoder.FieldNumber(),
-              static_cast<uint32_t>(log::LogEntry::Fields::FLAGS));
-    EXPECT_TRUE(entry_decoder.ReadUint32(&flags).ok());
-    EXPECT_EQ(expected_metadata.flags(), flags);
-  }
-
-  int64_t timestamp;
-  EXPECT_TRUE(entry_decoder.Next().ok());  // timestamp
-  EXPECT_TRUE(
-      entry_decoder.FieldNumber() ==
-          static_cast<uint32_t>(log::LogEntry::Fields::TIMESTAMP) ||
-      entry_decoder.FieldNumber() ==
-          static_cast<uint32_t>(log::LogEntry::Fields::TIME_SINCE_LAST_ENTRY));
-  EXPECT_TRUE(entry_decoder.ReadInt64(&timestamp).ok());
-  EXPECT_EQ(expected_timestamp, timestamp);
-
-  if (expected_metadata.module() != 0) {
-    EXPECT_TRUE(entry_decoder.Next().ok());  // module name
-    EXPECT_EQ(entry_decoder.FieldNumber(),
-              static_cast<uint32_t>(log::LogEntry::Fields::MODULE));
-    const Result<uint32_t> module =
-        protobuf::DecodeBytesToUint32(entry_decoder);
-    ASSERT_TRUE(module.ok());
-    EXPECT_EQ(expected_metadata.module(), module.value());
-  }
-
-  if (!expected_thread_name.empty()) {
-    ConstByteSpan tokenized_thread_name;
-    EXPECT_TRUE(entry_decoder.Next().ok());  // thread [tokenized]
-    EXPECT_EQ(entry_decoder.FieldNumber(),
-              static_cast<uint32_t>(log::LogEntry::Fields::THREAD));
-    EXPECT_TRUE(entry_decoder.ReadBytes(&tokenized_thread_name).ok());
-    EXPECT_TRUE(std::memcmp(tokenized_thread_name.begin(),
-                            expected_thread_name.begin(),
-                            expected_thread_name.size()) == 0);
-  }
-}
-
-void VerifyLogEntry(pw::protobuf::Decoder& entry_decoder,
-                    int expected_level,
-                    unsigned int expected_flags,
-                    std::string_view expected_module,
-                    std::string_view expected_thread_name,
-                    std::string_view expected_file_name,
-                    int expected_line_number,
-                    int64_t expected_ticks_since_epoch,
-                    std::string_view expected_message) {
-  std::string_view message;
-  EXPECT_TRUE(entry_decoder.Next().ok());  // message
-  EXPECT_EQ(entry_decoder.FieldNumber(),
-            static_cast<uint32_t>(log::LogEntry::Fields::MESSAGE));
-  EXPECT_TRUE(entry_decoder.ReadString(&message).ok());
-  EXPECT_TRUE(std::equal(message.begin(),
-                         message.end(),
-                         expected_message.begin(),
-                         expected_message.end()));
-
-  uint32_t line_level;
-  EXPECT_TRUE(entry_decoder.Next().ok());  // line_level
-  EXPECT_EQ(entry_decoder.FieldNumber(),
-            static_cast<uint32_t>(log::LogEntry::Fields::LINE_LEVEL));
-  EXPECT_TRUE(entry_decoder.ReadUint32(&line_level).ok());
-  uint32_t line_number;
-  uint8_t level;
-  std::tie(line_number, level) = UnpackLineLevel(line_level);
-  EXPECT_EQ(static_cast<unsigned int>(expected_line_number), line_number);
-  EXPECT_EQ(expected_level, level);
-
-  if (expected_flags != 0) {
-    uint32_t flags;
-    EXPECT_TRUE(entry_decoder.Next().ok());  // flags
-    EXPECT_EQ(entry_decoder.FieldNumber(),
-              static_cast<uint32_t>(log::LogEntry::Fields::FLAGS));
-    EXPECT_TRUE(entry_decoder.ReadUint32(&flags).ok());
-    EXPECT_EQ(expected_flags, flags);
-  }
-
-  int64_t timestamp;
-  EXPECT_TRUE(entry_decoder.Next().ok());  // timestamp
-  EXPECT_TRUE(
-      entry_decoder.FieldNumber() ==
-          static_cast<uint32_t>(log::LogEntry::Fields::TIMESTAMP) ||
-      entry_decoder.FieldNumber() ==
-          static_cast<uint32_t>(log::LogEntry::Fields::TIME_SINCE_LAST_ENTRY));
-  EXPECT_TRUE(entry_decoder.ReadInt64(&timestamp).ok());
-  EXPECT_EQ(expected_ticks_since_epoch, timestamp);
-
-  if (!expected_module.empty()) {
-    std::string_view module_name;
-    EXPECT_TRUE(entry_decoder.Next().ok());  // module
-    EXPECT_EQ(entry_decoder.FieldNumber(),
-              static_cast<uint32_t>(log::LogEntry::Fields::MODULE));
-    EXPECT_TRUE(entry_decoder.ReadString(&module_name).ok());
-    EXPECT_TRUE(std::equal(module_name.begin(),
-                           module_name.end(),
-                           expected_module.begin(),
-                           expected_module.end()));
-  }
-
-  if (!expected_file_name.empty()) {
-    std::string_view file_name;
-    EXPECT_TRUE(entry_decoder.Next().ok());  // file
-    EXPECT_EQ(entry_decoder.FieldNumber(),
-              static_cast<uint32_t>(log::LogEntry::Fields::FILE));
-    EXPECT_TRUE(entry_decoder.ReadString(&file_name).ok());
-    EXPECT_TRUE(std::equal(file_name.begin(),
-                           file_name.end(),
-                           expected_file_name.begin(),
-                           expected_file_name.end()));
-  }
-
-  if (!expected_thread_name.empty()) {
-    std::string_view thread_name;
-    EXPECT_TRUE(entry_decoder.Next().ok());  // file
-    EXPECT_EQ(entry_decoder.FieldNumber(),
-              static_cast<uint32_t>(log::LogEntry::Fields::THREAD));
-    EXPECT_TRUE(entry_decoder.ReadString(&thread_name).ok());
-    EXPECT_TRUE(std::equal(thread_name.begin(),
-                           thread_name.end(),
-                           expected_thread_name.begin(),
-                           expected_thread_name.end()));
-  }
-}
-
-TEST(UtilsTest, LineLevelPacking) {
-  constexpr uint8_t kExpectedLevel = PW_LOG_LEVEL_ERROR;
-  constexpr uint32_t kExpectedLine = 1234567;
-  constexpr uint32_t kExpectedLineLevel =
-      (kExpectedLine << PW_LOG_LEVEL_BITS) |
-      (kExpectedLevel & PW_LOG_LEVEL_BITMASK);
-
-  EXPECT_EQ(kExpectedLineLevel, PackLineLevel(kExpectedLine, kExpectedLevel));
-}
-
-TEST(UtilsTest, LineLevelUnpacking) {
-  constexpr uint8_t kExpectedLevel = PW_LOG_LEVEL_ERROR;
-  constexpr uint32_t kExpectedLine = 1234567;
-  constexpr uint32_t kExpectedLineLevel =
-      (kExpectedLine << PW_LOG_LEVEL_BITS) |
-      (kExpectedLevel & PW_LOG_LEVEL_BITMASK);
-
-  uint32_t line_number;
-  uint8_t level;
-  std::tie(line_number, level) = UnpackLineLevel(kExpectedLineLevel);
-
-  EXPECT_EQ(kExpectedLine, line_number);
-  EXPECT_EQ(kExpectedLevel, level);
-}
-
-TEST(UtilsTest, LineLevelPackAndUnpack) {
-  constexpr uint8_t kExpectedLevel = PW_LOG_LEVEL_ERROR;
-  constexpr uint32_t kExpectedLine = 1234567;
-
-  uint32_t line_number;
-  uint8_t level;
-  std::tie(line_number, level) =
-      UnpackLineLevel(PackLineLevel(kExpectedLine, kExpectedLevel));
-
-  EXPECT_EQ(kExpectedLine, line_number);
-  EXPECT_EQ(kExpectedLevel, level);
-}
-
-TEST(UtilsTest, EncodeTokenizedLog) {
-  constexpr std::byte kTokenizedData[1] = {std::byte(0x01)};
-  constexpr int64_t kExpectedTimestamp = 1;
-  constexpr std::byte kExpectedThreadName[1] = {std::byte(0x02)};
-  std::byte encode_buffer[32];
-
-  pw::log_tokenized::Metadata metadata =
-      pw::log_tokenized::Metadata::Set<1, 2, 3, 4>();
-
-  Result<ConstByteSpan> result = EncodeTokenizedLog(metadata,
-                                                    kTokenizedData,
-                                                    kExpectedTimestamp,
-                                                    kExpectedThreadName,
-                                                    encode_buffer);
-  EXPECT_TRUE(result.ok());
-
-  pw::protobuf::Decoder log_decoder(result.value());
-  VerifyTokenizedLogEntry(log_decoder,
-                          metadata,
-                          kTokenizedData,
-                          kExpectedTimestamp,
-                          kExpectedThreadName);
-
-  result = EncodeTokenizedLog(metadata,
-                              reinterpret_cast<const uint8_t*>(kTokenizedData),
-                              sizeof(kTokenizedData),
-                              kExpectedTimestamp,
-                              kExpectedThreadName,
-                              encode_buffer);
-  EXPECT_TRUE(result.ok());
-
-  log_decoder.Reset(result.value());
-  VerifyTokenizedLogEntry(log_decoder,
-                          metadata,
-                          kTokenizedData,
-                          kExpectedTimestamp,
-                          kExpectedThreadName);
-}
-
-TEST(UtilsTest, EncodeTokenizedLog_EmptyFlags) {
-  constexpr std::byte kTokenizedData[1] = {std::byte(0x01)};
-  constexpr int64_t kExpectedTimestamp = 1;
-  constexpr std::byte kExpectedThreadName[1] = {std::byte(0x02)};
-  std::byte encode_buffer[32];
-
-  // Create an empty flags set.
-  pw::log_tokenized::Metadata metadata =
-      pw::log_tokenized::Metadata::Set<1, 2, 0, 4>();
-
-  Result<ConstByteSpan> result = EncodeTokenizedLog(metadata,
-                                                    kTokenizedData,
-                                                    kExpectedTimestamp,
-                                                    kExpectedThreadName,
-                                                    encode_buffer);
-  EXPECT_TRUE(result.ok());
-
-  pw::protobuf::Decoder log_decoder(result.value());
-  VerifyTokenizedLogEntry(log_decoder,
-                          metadata,
-                          kTokenizedData,
-                          kExpectedTimestamp,
-                          kExpectedThreadName);
-}
-
-TEST(UtilsTest, EncodeTokenizedLog_InsufficientSpace) {
-  constexpr std::byte kTokenizedData[1] = {std::byte(0x01)};
-  constexpr int64_t kExpectedTimestamp = 1;
-  constexpr std::byte kExpectedThreadName[1] = {std::byte(0x02)};
-  std::byte encode_buffer[1];
-
-  pw::log_tokenized::Metadata metadata =
-      pw::log_tokenized::Metadata::Set<1, 2, 3, 4>();
-
-  Result<ConstByteSpan> result = EncodeTokenizedLog(metadata,
-                                                    kTokenizedData,
-                                                    kExpectedTimestamp,
-                                                    kExpectedThreadName,
-                                                    encode_buffer);
-  EXPECT_TRUE(result.status().IsResourceExhausted());
-}
-
-TEST(UtilsTest, EncodeLog) {
-  constexpr int kExpectedLevel = PW_LOG_LEVEL_INFO;
-  constexpr unsigned int kExpectedFlags = 2;
-  constexpr std::string_view kExpectedModule("TST");
-  constexpr std::string_view kExpectedThread("thread");
-  constexpr std::string_view kExpectedFile("proto_test.cc");
-  constexpr int kExpectedLine = 14;
-  constexpr int64_t kExpectedTimestamp = 1;
-  constexpr std::string_view kExpectedMessage("msg");
-  std::byte encode_buffer[64];
-
-  Result<ConstByteSpan> result = EncodeLog(kExpectedLevel,
-                                           kExpectedFlags,
-                                           kExpectedModule,
-                                           kExpectedThread,
-                                           kExpectedFile,
-                                           kExpectedLine,
-                                           kExpectedTimestamp,
-                                           kExpectedMessage,
-                                           encode_buffer);
-  EXPECT_TRUE(result.ok());
-
-  pw::protobuf::Decoder log_decoder(result.value());
-  VerifyLogEntry(log_decoder,
-                 kExpectedLevel,
-                 kExpectedFlags,
-                 kExpectedModule,
-                 kExpectedThread,
-                 kExpectedFile,
-                 kExpectedLine,
-                 kExpectedTimestamp,
-                 kExpectedMessage);
-}
-
-TEST(UtilsTest, EncodeLog_EmptyFlags) {
-  constexpr int kExpectedLevel = PW_LOG_LEVEL_INFO;
-  constexpr unsigned int kExpectedFlags = 0;
-  constexpr std::string_view kExpectedModule("TST");
-  constexpr std::string_view kExpectedThread("thread");
-  constexpr std::string_view kExpectedFile("proto_test.cc");
-  constexpr int kExpectedLine = 14;
-  constexpr int64_t kExpectedTimestamp = 1;
-  constexpr std::string_view kExpectedMessage("msg");
-  std::byte encode_buffer[64];
-
-  Result<ConstByteSpan> result = EncodeLog(kExpectedLevel,
-                                           kExpectedFlags,
-                                           kExpectedModule,
-                                           kExpectedThread,
-                                           kExpectedFile,
-                                           kExpectedLine,
-                                           kExpectedTimestamp,
-                                           kExpectedMessage,
-                                           encode_buffer);
-  EXPECT_TRUE(result.ok());
-
-  pw::protobuf::Decoder log_decoder(result.value());
-  VerifyLogEntry(log_decoder,
-                 kExpectedLevel,
-                 kExpectedFlags,
-                 kExpectedModule,
-                 kExpectedThread,
-                 kExpectedFile,
-                 kExpectedLine,
-                 kExpectedTimestamp,
-                 kExpectedMessage);
-}
-
-TEST(UtilsTest, EncodeLog_EmptyFile) {
-  constexpr int kExpectedLevel = PW_LOG_LEVEL_INFO;
-  constexpr unsigned int kExpectedFlags = 0;
-  constexpr std::string_view kExpectedModule("TST");
-  constexpr std::string_view kExpectedThread("thread");
-  constexpr std::string_view kExpectedFile;
-  constexpr int kExpectedLine = 14;
-  constexpr int64_t kExpectedTimestamp = 1;
-  constexpr std::string_view kExpectedMessage("msg");
-  std::byte encode_buffer[64];
-
-  Result<ConstByteSpan> result = EncodeLog(kExpectedLevel,
-                                           kExpectedFlags,
-                                           kExpectedModule,
-                                           kExpectedThread,
-                                           kExpectedFile,
-                                           kExpectedLine,
-                                           kExpectedTimestamp,
-                                           kExpectedMessage,
-                                           encode_buffer);
-  EXPECT_TRUE(result.ok());
-
-  pw::protobuf::Decoder log_decoder(result.value());
-  VerifyLogEntry(log_decoder,
-                 kExpectedLevel,
-                 kExpectedFlags,
-                 kExpectedModule,
-                 kExpectedThread,
-                 kExpectedFile,
-                 kExpectedLine,
-                 kExpectedTimestamp,
-                 kExpectedMessage);
-}
-
-TEST(UtilsTest, EncodeLog_EmptyModule) {
-  constexpr int kExpectedLevel = PW_LOG_LEVEL_INFO;
-  constexpr unsigned int kExpectedFlags = 3;
-  constexpr std::string_view kExpectedModule;
-  constexpr std::string_view kExpectedThread("thread");
-  constexpr std::string_view kExpectedFile("test.cc");
-  constexpr int kExpectedLine = 14;
-  constexpr int64_t kExpectedTimestamp = 1;
-  constexpr std::string_view kExpectedMessage("msg");
-  std::byte encode_buffer[64];
-
-  Result<ConstByteSpan> result = EncodeLog(kExpectedLevel,
-                                           kExpectedFlags,
-                                           kExpectedModule,
-                                           kExpectedThread,
-                                           kExpectedFile,
-                                           kExpectedLine,
-                                           kExpectedTimestamp,
-                                           kExpectedMessage,
-                                           encode_buffer);
-  EXPECT_TRUE(result.ok());
-
-  pw::protobuf::Decoder log_decoder(result.value());
-  VerifyLogEntry(log_decoder,
-                 kExpectedLevel,
-                 kExpectedFlags,
-                 kExpectedModule,
-                 kExpectedThread,
-                 kExpectedFile,
-                 kExpectedLine,
-                 kExpectedTimestamp,
-                 kExpectedMessage);
-}
-
-TEST(UtilsTest, EncodeLog_EmptyThread) {
-  constexpr int kExpectedLevel = PW_LOG_LEVEL_INFO;
-  constexpr unsigned int kExpectedFlags = 2;
-  constexpr std::string_view kExpectedModule("TST");
-  constexpr std::string_view kExpectedThread;
-  constexpr std::string_view kExpectedFile("proto_test.cc");
-  constexpr int kExpectedLine = 14;
-  constexpr int64_t kExpectedTimestamp = 1;
-  constexpr std::string_view kExpectedMessage("msg");
-  std::byte encode_buffer[64];
-
-  Result<ConstByteSpan> result = EncodeLog(kExpectedLevel,
-                                           kExpectedFlags,
-                                           kExpectedModule,
-                                           kExpectedThread,
-                                           kExpectedFile,
-                                           kExpectedLine,
-                                           kExpectedTimestamp,
-                                           kExpectedMessage,
-                                           encode_buffer);
-  EXPECT_TRUE(result.ok());
-
-  pw::protobuf::Decoder log_decoder(result.value());
-  VerifyLogEntry(log_decoder,
-                 kExpectedLevel,
-                 kExpectedFlags,
-                 kExpectedModule,
-                 kExpectedThread,
-                 kExpectedFile,
-                 kExpectedLine,
-                 kExpectedTimestamp,
-                 kExpectedMessage);
-}
-
-TEST(UtilsTest, EncodeLog_EmptyMessage) {
-  constexpr int kExpectedLevel = PW_LOG_LEVEL_INFO;
-  constexpr unsigned int kExpectedFlags = 0;
-  constexpr std::string_view kExpectedModule;
-  constexpr std::string_view kExpectedThread;
-  constexpr std::string_view kExpectedFile;
-  constexpr int kExpectedLine = 14;
-  constexpr int64_t kExpectedTimestamp = 1;
-  constexpr std::string_view kExpectedMessage;
-  std::byte encode_buffer[64];
-
-  Result<ConstByteSpan> result = EncodeLog(kExpectedLevel,
-                                           kExpectedFlags,
-                                           kExpectedModule,
-                                           kExpectedThread,
-                                           kExpectedFile,
-                                           kExpectedLine,
-                                           kExpectedTimestamp,
-                                           kExpectedMessage,
-                                           encode_buffer);
-
-  EXPECT_TRUE(result.status().IsInvalidArgument());
-}
-
-TEST(UtilsTest, EncodeLog_InsufficientSpace) {
-  constexpr int kExpectedLevel = PW_LOG_LEVEL_INFO;
-  constexpr unsigned int kExpectedFlags = 0;
-  constexpr std::string_view kExpectedModule;
-  constexpr std::string_view kExpectedThread;
-  constexpr std::string_view kExpectedFile;
-  constexpr int kExpectedLine = 14;
-  constexpr int64_t kExpectedTimestamp = 1;
-  constexpr std::string_view kExpectedMessage("msg");
-  std::byte encode_buffer[1];
-
-  Result<ConstByteSpan> result = EncodeLog(kExpectedLevel,
-                                           kExpectedFlags,
-                                           kExpectedModule,
-                                           kExpectedThread,
-                                           kExpectedFile,
-                                           kExpectedLine,
-                                           kExpectedTimestamp,
-                                           kExpectedMessage,
-                                           encode_buffer);
-
-  EXPECT_TRUE(result.status().IsResourceExhausted());
-}
-
-}  // namespace pw::log
diff --git a/pw_log/protobuf.rst b/pw_log/protobuf.rst
deleted file mode 100644
index 32f9ba2..0000000
--- a/pw_log/protobuf.rst
+++ /dev/null
@@ -1,110 +0,0 @@
-.. _module-pw_log-protobuf:
-
--------------------
-The pw_log protobuf
--------------------
-``pw_log`` defines a protocol buffer for storing and transmitting log messages.
-The protobuf is optimized to be maximally efficient.
-
-Fields
-======
-The ``pw_log`` protobuf is defined in ``log.proto``.
-
-.. literalinclude:: log.proto
-  :language: protobuf
-  :lines: 14-
-
-Timestamps
-----------
-Timestamps are specified in implementation-defined ticks. Ticks could be
-milliseconds, microsends, or any arbitrary duration derived from the device’s
-clock.
-
-For many applications, the timestamp field can be interpreted based on the prior
-knowledge of the system. For example, ``timestamp`` might be known to be
-milliseconds since boot for one core or microseconds since boot for another.
-
-If desired, a project could collect information about clock parameters
-separately from ``pw_log``, and use that to interpret log timestamps. For
-example, they may call an RPC that returns a ``pw_chrono`` ``ClockParamters``
-protobuf. The values from that could be used to interpret timestamp from that
-device.
-
-The ``pw_log`` proto contains two timestamp fields, only one of which may be set
-at a time:
-
-- ``timestamp`` – Absolute time when message was logged.
-- ``time_since_last_entry`` – When the message was logged, relative
-  to the previously encoded log message. This is used when multiple log entries
-  are sent in a single ``LogEntries`` proto. The previous log entry must use the
-  same time source. If logs with multiple time sources are intermingled in a
-  single ``LogEntries`` proto, they must use an absolute timestamp each time the
-  time source changes.
-
-Optionally tokenized text fields
---------------------------------
-Several fields in the ``pw_log`` proto store text. Examples include ``message``,
-``module``, and ``thread``. These fields may contain either plain or tokenized
-text, either of which is represented as a single bytes field. These fields are
-marked with a protocol buffer option so the ``pw_tokenizer.proto`` module can
-detect and detokenize tokenized fields as appropriate.
-
-See :ref:`module-pw_tokenizer-proto` for details.
-
-Packing and unpacking line_level
---------------------------------
-As a way to minimize on-the-wire log message size, the log level and the line
-number of a given log statement are packed into a single proto field. There are
-helpers in ``pw_log/proto_utils.h`` for properly packing and unpacking this
-field.
-
-.. code-block:: cpp
-
-   #include "pw_bytes/span.h"
-   #include "pw_log/levels.h"
-   #include "pw_log/proto_utils.h"
-   #include "pw_protobuf/decoder.h"
-
-  bool FilterLog(pw::ConstByteSpan serialized_log) {
-    pw::protobuf::Decoder log_decoder(serialized_log);
-    while (log_decoder.Next().ok()) {
-      if (log_decoder.FieldNumber() == 2) {
-        uint32_t line_and_level;
-        entry_decoder.ReadUint32(&line_and_level);
-        PW_DCHECK(entry_decoder.ok());
-
-        uint8_t level = std::get<1>(pw::log::UnpackLineLevel(line_and_level));
-        if (level < PW_LOG_LEVEL_INFO) {
-          return false;
-        }
-      }
-    }
-
-    return true;
-  }
-
-Log encoding helpers
---------------------
-Encoding logs to the ``log.proto`` format can be performed using the helpers
-provided in the ``pw_log/proto_utils.h`` header. Separate helpers are provided
-for encoding tokenized logs and string-based logs.
-
-.. code-block:: cpp
-
-   #include "pw_log/proto_utils.h"
-
-   extern "C" void pw_tokenizer_HandleEncodedMessageWithPayload(
-       pw_tokenizer_Payload payload, const uint8_t data[], size_t size) {
-     pw::log_tokenized::Metadata metadata(payload);
-     std::byte log_buffer[kLogBufferSize];
-
-     Result<ConstByteSpan> result = EncodeTokenizedLog(
-         metadata,
-         std::as_bytes(std::span(data, size)),
-         log_buffer);
-     if (result.ok()) {
-       // This makes use of the encoded log proto and is custom per-product.
-       // It should be implemented by the caller and is not in Pigweed.
-       EmitProtoLogEntry(result.value());
-     }
-   }
diff --git a/pw_log/public/pw_log/config.h b/pw_log/public/pw_log/config.h
deleted file mode 100644
index 340fbe4..0000000
--- a/pw_log/public/pw_log/config.h
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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
-
-// These configuration options differ from the options in pw_log/options.h in
-// that these should be set at a global level in the build system rather than
-// at a module or compile unit level.
-
-// PW_LOG_LEVEL_DEFAULT controls the default value of PW_LOG_LEVEL.
-//
-// Defaults to PW_LOG_LEVEL_DEBUG.
-#ifndef PW_LOG_LEVEL_DEFAULT
-#define PW_LOG_LEVEL_DEFAULT PW_LOG_LEVEL_DEBUG
-#endif  // PW_LOG_LEVEL_DEFAULT
-
-// PW_LOG_FLAGS_DEFAULT controls the default value of PW_LOG_FLAGS.
-//
-// For log statements like LOG_INFO that don't have an explicit argument, this
-// is used for the flags value.
-#ifndef PW_LOG_FLAGS_DEFAULT
-#define PW_LOG_FLAGS_DEFAULT 0
-#endif  // PW_LOG_FLAGS_DEFAULT
-
-// PW_LOG_ENABLE_IF_DEFAULT controls the default value of PW_LOG_ENABLE_IF.
-//
-// This expression determines whether or not the statement is enabled and
-// should be passed to the backend.
-#ifndef PW_LOG_ENABLE_IF_DEFAULT
-#define PW_LOG_ENABLE_IF_DEFAULT(level, flags) ((level) >= PW_LOG_LEVEL)
-#endif  // PW_LOG_ENABLE_IF_DEFAULT
diff --git a/pw_log/public/pw_log/glog_adapter.h b/pw_log/public/pw_log/glog_adapter.h
deleted file mode 100644
index 0511658..0000000
--- a/pw_log/public/pw_log/glog_adapter.h
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_log/internal/glog_adapter.h"
-#include "pw_preprocessor/concat.h"
-
-// WARNING: Pigweed strongly recommends sticking to printf-style logging instead
-// of C++ stream-style Google Log logging unless absolutely necessary. These
-// macros are only provided for compatibility with non-embedded code. See
-// https://pigweed.dev/pw_log/ for more details.
-
-// A subset of the streaming Google logging (glog) macros are supported:
-// - LOG(glog_level)
-// - LOG_IF(glog_level, condition)
-//
-// The supported glog levels are DEBUG, INFO, WARNING, ERROR, FATAL & DFATAL
-//
-// This means the following are NOT supported:
-// - glog level DFATAL
-// - {D,P,SYS}LOG*
-// - {,D}VLOG*
-// - {,D}CHECK*
-// - LOG_EVERY_*, LOG_EVERY_*, LOG_IF_EVERY_*, LOG_FIRST_N
-#define LOG(glog_level)                               \
-  _PW_LOG_GLOG(_PW_LOG_GLOG_DECLARATION_##glog_level, \
-               PW_CONCAT(GlogStreamingLog, __COUNTER__))
-
-#define LOG_IF(glog_level, expr)                         \
-  _PW_LOG_GLOG_IF(_PW_LOG_GLOG_DECLARATION_##glog_level, \
-                  expr,                                  \
-                  PW_CONCAT(GlogStreamingLog, __COUNTER__))
diff --git a/pw_log/public/pw_log/glog_adapter_config.h b/pw_log/public/pw_log/glog_adapter_config.h
deleted file mode 100644
index 3907fd3..0000000
--- a/pw_log/public/pw_log/glog_adapter_config.h
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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
-
-// The size of the stack-allocated buffer used by the Google Logging (glog)
-// macros. This only affects the glog macros provided through pw_log/glog.h.
-//
-// Pigweed strongly recommends sticking to printf-style logging instead
-// of C++ stream-style Google Log logging unless absolutely necessary. The glog
-// macros are only provided for compatibility with non-embedded code. See
-// https://pigweed.dev/pw_log/ for more details.
-//
-// Undersizing this buffer will result in truncated log messages.
-#ifndef PW_LOG_CFG_GLOG_BUFFER_SIZE_BYTES
-#define PW_LOG_CFG_GLOG_BUFFER_SIZE_BYTES 128
-#endif  // PW_LOG_CFG_GLOG_BUFFER_SIZE_BYTES
diff --git a/pw_log/public/pw_log/internal/glog_adapter.h b/pw_log/public/pw_log/internal/glog_adapter.h
deleted file mode 100644
index 3e63b90..0000000
--- a/pw_log/public/pw_log/internal/glog_adapter.h
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_assert/check.h"
-#include "pw_log/glog_adapter_config.h"
-#include "pw_log/levels.h"
-#include "pw_log/log.h"
-#include "pw_string/string_builder.h"
-
-namespace pw::log::internal {
-
-class GlogStreamingLog {
- public:
-  GlogStreamingLog() = default;
-
-  template <typename T>
-  GlogStreamingLog& operator<<(const T& value) {
-    string_builder_ << value;
-    return *this;
-  }
-
- protected:
-  pw::StringBuffer<PW_LOG_CFG_GLOG_BUFFER_SIZE_BYTES> string_builder_;
-};
-
-}  // namespace pw::log::internal
-
-// Declares a unique GlogStreamingLog class definition with a destructor which
-// matches the desired pw_log_level.
-#define _PW_LOG_GLOG_DECLARATION_PW_LOG(pw_log_level, unique)         \
-  class unique : public ::pw::log::internal::GlogStreamingLog {       \
-   public:                                                            \
-    ~unique() {                                                       \
-      PW_HANDLE_LOG(                                                  \
-          pw_log_level, PW_LOG_FLAGS, "%s", string_builder_.c_str()); \
-    }                                                                 \
-  }
-
-// Declares a unique GlogStreamingLog class definition with a destructor which
-// invokes PW_CRASH.
-#define _PW_LOG_GLOG_DECLARATION_PW_CRASH(unique)               \
-  class unique : public ::pw::log::internal::GlogStreamingLog { \
-   public:                                                      \
-    ~unique() { PW_CRASH("%s", string_builder_.c_str()); }      \
-  }
-
-// Dispatching macros to translate the glog level to PW_LOG and PW_CRASH.
-#define _PW_LOG_GLOG_DECLARATION_DEBUG(unique) \
-  _PW_LOG_GLOG_DECLARATION_PW_LOG(PW_LOG_LEVEL_DEBUG, unique)
-
-#define _PW_LOG_GLOG_DECLARATION_INFO(unique) \
-  _PW_LOG_GLOG_DECLARATION_PW_LOG(PW_LOG_LEVEL_INFO, unique)
-
-#define _PW_LOG_GLOG_DECLARATION_WARNING(unique) \
-  _PW_LOG_GLOG_DECLARATION_PW_LOG(PW_LOG_LEVEL_WARN, unique)
-
-#define _PW_LOG_GLOG_DECLARATION_ERROR(unique) \
-  _PW_LOG_GLOG_DECLARATION_PW_LOG(PW_LOG_LEVEL_ERROR, unique)
-
-#define _PW_LOG_GLOG_DECLARATION_FATAL(unique) \
-  _PW_LOG_GLOG_DECLARATION_PW_CRASH(unique)
-
-#if defined(NDEBUG)
-#define _PW_LOG_GLOG_DECLARATION_DFATAL(unique) \
-  _PW_LOG_GLOG_DECLARATION_PW_LOG(PW_LOG_LEVEL_ERROR, unique)
-#else  // !defined(NDEBUG)
-#define _PW_LOG_GLOG_DECLARATION_DFATAL(unique) \
-  _PW_LOG_GLOG_DECLARATION_PW_CRASH(unique)
-#endif  // defined(NDEBUG)
-
-#define _PW_LOG_GLOG(glog_declaration, unique) \
-  glog_declaration(unique);                    \
-  unique()
-
-#define _PW_LOG_GLOG_IF(glog_declaration, expr, unique) \
-  glog_declaration(unique);                             \
-  if (!(expr)) {                                        \
-  } else                                                \
-    unique()
diff --git a/pw_log/public/pw_log/levels.h b/pw_log/public/pw_log/levels.h
index 8702837..6886b6b 100644
--- a/pw_log/public/pw_log/levels.h
+++ b/pw_log/public/pw_log/levels.h
@@ -24,9 +24,6 @@
 #define PW_LOG_LEVEL_ERROR    4
 #define PW_LOG_LEVEL_CRITICAL 5
 
-#define PW_LOG_LEVEL_FATAL    7
-
-// Number of bits required to represent the log level
-#define PW_LOG_LEVEL_BITS     3
 #define PW_LOG_LEVEL_BITMASK  7  // 0b111
+#define PW_LOG_LEVEL_BITWIDTH 3
 // clang-format on
diff --git a/pw_log/public/pw_log/log.h b/pw_log/public/pw_log/log.h
index b9bda56..56764e3 100644
--- a/pw_log/public/pw_log/log.h
+++ b/pw_log/public/pw_log/log.h
@@ -61,15 +61,23 @@
 //
 #include "pw_log_backend/log_backend.h"
 
-// The PW_LOG macro accepts the format string and its arguments in a variadic
-// macro. The format string is not listed as a separate argument to avoid adding
-// a comma after the format string when it has no arguments.
+// For compatibility with code that uses the deprecated PW_LOG_USE_SHORT_NAMES
+// and PW_LOG_USE_ULTRA_SHORT_NAMES, include the appropriate headers.
+// TODO(hepler): Remove this workaround when all users have migrated.
+#if defined(PW_LOG_USE_SHORT_NAMES) && PW_LOG_USE_SHORT_NAMES == 1
+#include "pw_log/short.h"
+#endif  // PW_LOG_USE_SHORT_NAMES
+
+#if defined(PW_LOG_USE_ULTRA_SHORT_NAMES) && PW_LOG_USE_ULTRA_SHORT_NAMES == 1
+#include "pw_log/shorter.h"
+#endif  // PW_LOG_USE_ULTRA_SHORT_NAMES
+
 #ifndef PW_LOG
-#define PW_LOG(level, flags, /* format string and arguments */...) \
-  do {                                                             \
-    if (PW_LOG_ENABLE_IF(level, flags)) {                          \
-      PW_HANDLE_LOG(level, flags, __VA_ARGS__);                    \
-    }                                                              \
+#define PW_LOG(level, flags, message, ...)               \
+  do {                                                   \
+    if (PW_LOG_ENABLE_IF(level, flags)) {                \
+      PW_HANDLE_LOG(level, flags, message, __VA_ARGS__); \
+    }                                                    \
   } while (0)
 #endif  // PW_LOG
 
@@ -77,31 +85,44 @@
 // specialized versions, define the standard PW_LOG_<level>() macros in terms
 // of the general PW_LOG().
 #ifndef PW_LOG_DEBUG
-#define PW_LOG_DEBUG(...) PW_LOG(PW_LOG_LEVEL_DEBUG, PW_LOG_FLAGS, __VA_ARGS__)
+#define PW_LOG_DEBUG(message, ...) \
+  PW_LOG(PW_LOG_LEVEL_DEBUG, PW_LOG_DEFAULT_FLAGS, message, __VA_ARGS__)
 #endif  // PW_LOG_DEBUG
 
 #ifndef PW_LOG_INFO
-#define PW_LOG_INFO(...) PW_LOG(PW_LOG_LEVEL_INFO, PW_LOG_FLAGS, __VA_ARGS__)
+#define PW_LOG_INFO(message, ...) \
+  PW_LOG(PW_LOG_LEVEL_INFO, PW_LOG_DEFAULT_FLAGS, message, __VA_ARGS__)
 #endif  // PW_LOG_INFO
 
 #ifndef PW_LOG_WARN
-#define PW_LOG_WARN(...) PW_LOG(PW_LOG_LEVEL_WARN, PW_LOG_FLAGS, __VA_ARGS__)
+#define PW_LOG_WARN(message, ...) \
+  PW_LOG(PW_LOG_LEVEL_WARN, PW_LOG_DEFAULT_FLAGS, message, __VA_ARGS__)
 #endif  // PW_LOG_WARN
 
 #ifndef PW_LOG_ERROR
-#define PW_LOG_ERROR(...) PW_LOG(PW_LOG_LEVEL_ERROR, PW_LOG_FLAGS, __VA_ARGS__)
+#define PW_LOG_ERROR(message, ...) \
+  PW_LOG(PW_LOG_LEVEL_ERROR, PW_LOG_DEFAULT_FLAGS, message, __VA_ARGS__)
 #endif  // PW_LOG_ERROR
 
 #ifndef PW_LOG_CRITICAL
-#define PW_LOG_CRITICAL(...) \
-  PW_LOG(PW_LOG_LEVEL_CRITICAL, PW_LOG_FLAGS, __VA_ARGS__)
+#define PW_LOG_CRITICAL(message, ...) \
+  PW_LOG(PW_LOG_LEVEL_CRITICAL, PW_LOG_DEFAULT_FLAGS, message, __VA_ARGS__)
 #endif  // PW_LOG_CRITICAL
 
+// Default: Number of bits available for the log level
+//
+// All log statements have a level, and this define is the number of bits
+// available for the level. Some backends restrict this for better efficiency.
+// By default, pick a restricted but large enough value to work for most cases.
+#ifndef PW_LOG_LEVEL_BITS
+#define PW_LOG_LEVEL_BITS 6
+#endif  // PW_LOG_LEVEL_BITS
+
 // Default: Number of bits available for the log flags
 //
 // All log statements have a flags field, and this define is the number of bits
 // available for the flags. Some backends restrict this for better efficiency.
 // By default, pick a restricted but large enough value to work for most cases.
 #ifndef PW_LOG_FLAG_BITS
-#define PW_LOG_FLAG_BITS 2
+#define PW_LOG_FLAG_BITS 10
 #endif  // PW_LOG_FLAG_BITS
diff --git a/pw_log/public/pw_log/options.h b/pw_log/public/pw_log/options.h
index 2d20bd6..3bd9c85 100644
--- a/pw_log/public/pw_log/options.h
+++ b/pw_log/public/pw_log/options.h
@@ -30,12 +30,6 @@
 // circular dependencies when implementing the pw_log facade.
 #pragma once
 
-#include "pw_log/config.h"
-
-// These configuration options differ from the options in pw_log/config.h in
-// that these should be set at a module/compile unit level rather than a global
-// level level.
-
 // Default: Module name
 //
 // An empty string is used for the module name if it is not set. The
@@ -50,33 +44,26 @@
 #define PW_LOG_MODULE_NAME_DEFINED 0
 #endif  // PW_LOG_MODULE_NAME
 
-// Default: Log level filtering
-//
-// All log statements have a level, and this define sets the log level to the
-// globally set default if PW_LOG_LEVEL was not already set by the module.
-// This is compile-time filtering if the level is a constant.
-#ifndef PW_LOG_LEVEL
-#define PW_LOG_LEVEL PW_LOG_LEVEL_DEFAULT
-#endif  // PW_LOG_LEVEL
-
 // Default: Flags
 //
 // For log statements like LOG_INFO that don't have an explicit argument, this
 // is used for the flags value.
-#ifndef PW_LOG_FLAGS
-#define PW_LOG_FLAGS PW_LOG_FLAGS_DEFAULT
-#endif  // PW_LOG_FLAGS
-
-// DEPRECATED: Use PW_LOG_FLAGS.
-// TODO(pwbug/561): Remove this macro after migration.
 #ifndef PW_LOG_DEFAULT_FLAGS
-#define PW_LOG_DEFAULT_FLAGS PW_LOG_FLAGS
+#define PW_LOG_DEFAULT_FLAGS 0
 #endif  // PW_LOG_DEFAULT_FLAGS
 
+// Default: Log level filtering
+//
+// All log statements have a level, and this define is the default filtering.
+// This is compile-time filtering if the level is a constant.
+#ifndef PW_LOG_LEVEL
+#define PW_LOG_LEVEL PW_LOG_LEVEL_DEBUG
+#endif  // PW_LOG_LEVEL
+
 // Default: Log enabled expression
 //
 // This expression determines whether or not the statement is enabled and
 // should be passed to the backend.
 #ifndef PW_LOG_ENABLE_IF
-#define PW_LOG_ENABLE_IF(level, flags) PW_LOG_ENABLE_IF_DEFAULT(level, flags)
+#define PW_LOG_ENABLE_IF(level, flags) ((level) >= PW_LOG_LEVEL)
 #endif  // PW_LOG_ENABLE_IF
diff --git a/pw_log/public/pw_log/proto_utils.h b/pw_log/public/pw_log/proto_utils.h
deleted file mode 100644
index a9bada5..0000000
--- a/pw_log/public/pw_log/proto_utils.h
+++ /dev/null
@@ -1,152 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_bytes/span.h"
-#include "pw_log/levels.h"
-#include "pw_log/proto/log.pwpb.h"
-#include "pw_log_tokenized/metadata.h"
-#include "pw_result/result.h"
-#include "pw_status/try.h"
-
-namespace pw::log {
-
-// Packs line number and log level into a single uint32_t as dictated by the
-// line_level field in the Log proto message.
-//
-// Note:
-//   line_number is restricted to 29 bits. Values beyond 536870911 will be lost.
-//   level is restricted to 3 bits. Values beyond 7 will be lost.
-constexpr inline uint32_t PackLineLevel(uint32_t line_number, uint8_t level) {
-  return (level & PW_LOG_LEVEL_BITMASK) |
-         ((line_number << PW_LOG_LEVEL_BITS) & ~PW_LOG_LEVEL_BITMASK);
-}
-
-// Unpacks the line_level field as dictated by the Log proto message into line
-// number (uint32_t) and level (uint8_t).
-constexpr inline std::tuple<uint32_t, uint8_t> UnpackLineLevel(
-    uint32_t line_and_level) {
-  return std::make_tuple(
-      (line_and_level & ~PW_LOG_LEVEL_BITMASK) >> PW_LOG_LEVEL_BITS,
-      line_and_level & PW_LOG_LEVEL_BITMASK);
-}
-
-// Convenience functions to encode multiple log attributes as a log proto
-// message.
-//
-// Returns:
-// OK - A byte span containing the encoded log proto.
-// INVALID_ARGUMENT - `message` argument is zero-length.
-// RESOURCE_EXHAUSTED - The provided buffer was not large enough to encode the
-//   proto.
-Result<ConstByteSpan> EncodeLog(int level,
-                                unsigned int flags,
-                                std::string_view module_name,
-                                std::string_view thread_name,
-                                std::string_view file_name,
-                                int line_number,
-                                int64_t ticks_since_epoch,
-                                std::string_view message,
-                                ByteSpan encode_buffer);
-
-// Encodes tokenized message and metadata, with a timestamp as a log proto.
-// Extra fields can be encoded into the returned encoder. The caller must check
-// the encoder status.
-LogEntry::MemoryEncoder CreateEncoderAndEncodeTokenizedLog(
-    log_tokenized::Metadata metadata,
-    ConstByteSpan tokenized_data,
-    int64_t ticks_since_epoch,
-    ByteSpan encode_buffer);
-
-// Convenience functions to convert from tokenized metadata to the log proto
-// format.
-//
-// Returns:
-// OK - A byte span containing the encoded log proto.
-// RESOURCE_EXHAUSTED - The provided buffer was not large enough to store the
-//   proto.
-inline Result<ConstByteSpan> EncodeTokenizedLog(
-    log_tokenized::Metadata metadata,
-    ConstByteSpan tokenized_data,
-    int64_t ticks_since_epoch,
-    ByteSpan encode_buffer) {
-  LogEntry::MemoryEncoder encoder = CreateEncoderAndEncodeTokenizedLog(
-      metadata, tokenized_data, ticks_since_epoch, encode_buffer);
-  PW_TRY(encoder.status());
-  return ConstByteSpan(encoder);
-}
-
-inline Result<ConstByteSpan> EncodeTokenizedLog(
-    log_tokenized::Metadata metadata,
-    const uint8_t* tokenized_data,
-    size_t tokenized_data_size,
-    int64_t ticks_since_epoch,
-    ByteSpan encode_buffer) {
-  return EncodeTokenizedLog(
-      metadata,
-      std::as_bytes(std::span(tokenized_data, tokenized_data_size)),
-      ticks_since_epoch,
-      encode_buffer);
-}
-
-// Encodes tokenized message (passed as pointer and size), tokenized metadata,
-// timestamp, and thread name as a log proto.
-//
-// Returns:
-// OK - A byte span containing the encoded log proto.
-// RESOURCE_EXHAUSTED - The provided buffer was not large enough to store the
-//   proto.
-inline Result<ConstByteSpan> EncodeTokenizedLog(
-    log_tokenized::Metadata metadata,
-    const uint8_t* tokenized_data,
-    size_t tokenized_data_size,
-    int64_t ticks_since_epoch,
-    ConstByteSpan thread_name,
-    ByteSpan encode_buffer) {
-  LogEntry::MemoryEncoder encoder = CreateEncoderAndEncodeTokenizedLog(
-      metadata,
-      std::as_bytes(std::span(tokenized_data, tokenized_data_size)),
-      ticks_since_epoch,
-      encode_buffer);
-  if (!thread_name.empty()) {
-    encoder.WriteThread(thread_name).IgnoreError();
-  }
-  PW_TRY(encoder.status());
-  return ConstByteSpan(encoder);
-}
-
-// Encodes tokenized message (passed as a byte span), tokenized metadata,
-// timestamp, and thread name as a log proto.
-//
-// Returns:
-// OK - A byte span containing the encoded log proto.
-// RESOURCE_EXHAUSTED - The provided buffer was not large enough to store the
-//   proto.
-inline Result<ConstByteSpan> EncodeTokenizedLog(
-    log_tokenized::Metadata metadata,
-    ConstByteSpan tokenized_data,
-    int64_t ticks_since_epoch,
-    ConstByteSpan thread_name,
-    ByteSpan encode_buffer) {
-  LogEntry::MemoryEncoder encoder = CreateEncoderAndEncodeTokenizedLog(
-      metadata, tokenized_data, ticks_since_epoch, encode_buffer);
-  if (!thread_name.empty()) {
-    encoder.WriteThread(thread_name).IgnoreError();
-  }
-  PW_TRY(encoder.status());
-  return ConstByteSpan(encoder);
-}
-}  // namespace pw::log
diff --git a/pw_log/pw_log_proto/log.proto b/pw_log/pw_log_proto/log.proto
new file mode 100644
index 0000000..2e38f42
--- /dev/null
+++ b/pw_log/pw_log_proto/log.proto
@@ -0,0 +1,165 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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.
+
+syntax = "proto2";
+
+package pw.log;
+
+option java_package = "pw.rpc.proto";
+option java_outer_classname = "Log";
+
+// A log with a tokenized message, a string message, or dropped indicator.  A
+// message can be one of three types:
+//
+//  1. A tokenized log message (recommended for production)
+//  2. A non-tokenized log message (good for development)
+//  3. A "log missed" tombstone, indicating that some logs were dropped
+//
+// Size analysis:
+//
+// For tokenized log messages in the common case; including the proto tag for
+// the field (so adding the fields gives the total proto message size):
+//
+//  - message_tokenized  - 6-12 bytes, depending on # and value of arguments
+//  - line_level         - 3 bytes; 4 bytes if line > 2048 (uncommon)
+//  - timestamp          - 3 bytes; assuming delta encoding
+//  - thread_tokenized   - 3 bytes
+//
+// Total:
+//
+//    6-12 bytes - log
+//    9-15 bytes - log + level + line
+//   12-18 bytes - log + level + line + timestamp
+//   15-21 bytes - log + level + line + timestamp + task
+//
+// An analysis of a project's log token database revealed the following
+// distribution of the number of arguments to log messages:
+//
+//   # args   # messages
+//     0         2,700
+//     1         2,400
+//     2         1,200
+//     3+        1,000
+//
+// Note: The below proto makes some compromises compared to what one might
+// expect for a "clean" proto design, in order to shave bytes off of the
+// messages. It is critical that the log messages are as small as possible to
+// enable storing more logs in limited memory. This is why, for example, there
+// is no separate "DroppedLog" type, or a "TokenizedLog" and "StringLog", which
+// would add at least 2 extra bytes per message
+// Note: Time-related fields will likely support specifying the time as a ratio
+// (period) and an absolute time separate from the current delta fields.
+message LogEntry {
+  // The tokenized log message. Internally, the format has a 32-bit token
+  // followed by the arguments for that message. The unformatted log string
+  // corresponding to the token in the token database must follow this format:
+  //
+  //   file|module|message
+  //
+  // For example:
+  //
+  //   ../boot/bluetooth.cc|BOOT|Bluetooth is on the fritz; error code: %d
+  //
+  // Note: The level and flags are not included since level and flags are
+  // runtime values and so cannot be tokenized.
+  //
+  // Size analysis:
+  //
+  //   tag+wire = 1 byte
+  //   size     = 1 byte; payload will almost always be < 127 bytes
+  //   payload  = N bytes; typically 4-10 in practice
+  //
+  // Total: 2 + N ~= 6-12 bytes
+  optional bytes message_tokenized = 1;
+
+  // Packed log level and line number. Structure:
+  //
+  //   Level: Bottom 3 bits; level = line_level & 0x7
+  //   Line: Remaining bits; line = (line_level >> 3)
+  //
+  // Note: This packing saves two bytes per log message in most cases compared
+  // to having line and level separately; and is zero-cost if the log backend
+  // omits the line number.
+  optional uint32 line_level = 2;
+
+  // Some log messages have flags to indicate for example assert or PII. The
+  // particular flags are product- and implementation-dependent. When no flags
+  // are present, the field is omitted entirely.
+  optional uint32 flags = 3;
+
+  // The task or thread that created the log message.
+  //
+  // In practice, the task token and tag should be just 3 bytes, since a 14 bit
+  // token for the task name should be enough.
+  optional uint32 thread_tokenized = 4;
+
+  // Timestamp. Note: The units here are TBD and will likely require a separate
+  // mechanism to indicate units. This field is likely to change as we figure
+  // out the right strategy for timestamps in Pigweed. This is a variable-sized
+  // integer to enable scaling this up to a uint64 later on without impacting
+  // the wire format.
+  optional int64 timestamp = 5;
+
+  // Time since the last entry. Generally, one of timestamp or this field will
+  // be specified. This enables delta encoding when batching entries together.
+  //
+  // Size analysis for this field including tag and varint:
+  //
+  //           < 127 ms gap == 127 ms      ==  7 bits == 2 bytes
+  //        < 16,000 ms gap ==  16 seconds == 14 bits == 3 bytes
+  //     < 2,000,000 ms gap ==  35 minutes == 21 bits == 4 bytes
+  //   < 300,000,000 ms gap ==  74 hours   == 28 bits == 5 bytes
+  //
+  // Log bursts will thus consume just 2 bytes (tag + up to 127ms delta) for
+  // the timestamp, which is a good improvement over timestamp in many cases.
+  // Note: The units of this field are TBD and will likely require a separate
+  // mechanism to indicate units. The calculations above assume milliseconds
+  // and may change if the units differ.
+  optional int64 elapsed_time_since_last_entry = 6;
+
+  // Fully formatted textual log message.
+  optional string message_string = 16;
+
+  // For non-tokenized logging, the file name.
+  optional string file = 17;
+
+  // String representation of the task that created the log message.
+  optional string thread_string = 18;
+
+  // When the log buffers are full but more logs come in, the logs are counted
+  // and a special log message is omitted with only counts for the number of
+  // messages dropped. The timestamp indicates the time that the "missed logs"
+  // message was inserted into the queue.
+  //
+  // Missed logs messages will only have one of the timestamp fields and these
+  // counters specified.
+  optional uint32 dropped = 19;
+  optional uint32 dropped_warning_or_above = 20;
+
+  // Some messages are associated with trace events, which may carry additional
+  // contextual data. This is a tuple of a data format string which could be
+  // used by the decoder to identify the data (e.g. printf-style tokens) and the
+  // data itself in bytes.
+  optional string data_format_string = 21;
+  optional bytes data = 22;
+}
+
+message LogRequest {}
+message LogEntries {
+  repeated LogEntry entries = 1;
+}
+
+service Logs {
+  rpc Get(LogRequest) returns (stream LogEntries) {}
+}
diff --git a/pw_log_android/Android.bp b/pw_log_android/Android.bp
deleted file mode 100644
index 179249e..0000000
--- a/pw_log_android/Android.bp
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-cc_library {
-    name: "libpw_log_android",
-    vendor_available: true,
-    cpp_std: "c++2a",
-    export_include_dirs: [
-        "public",
-        "public_overrides",
-    ],
-    export_static_lib_headers: [
-        "libpw_log",
-    ],
-    static_libs: [
-        "libpw_log",
-    ],
-}
diff --git a/pw_log_android/BUILD.gn b/pw_log_android/BUILD.gn
deleted file mode 100644
index 8316fd8..0000000
--- a/pw_log_android/BUILD.gn
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-# Android only uses Soong Blueprints, so this file is empty.
diff --git a/pw_log_android/public/pw_log_android/log_android.h b/pw_log_android/public/pw_log_android/log_android.h
deleted file mode 100644
index 410f149..0000000
--- a/pw_log_android/public/pw_log_android/log_android.h
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <log/log.h>
-
-// This backend supports PW_LOG_MODULE_NAME as a fallback for Android logging's
-// LOG_TAG if and only if LOG_TAG is not already set.
-#if !defined(LOG_TAG) && defined(PW_LOG_MODULE_NAME)
-#define LOG_TAG PW_LOG_MODULE_NAME
-#endif
-
-// #define PW_LOG_LEVEL_DEBUG    1
-#define _PW_LOG_ANDROID_LEVEL_1(...) ALOGD(__VA_ARGS__)
-
-// #define PW_LOG_LEVEL_INFO    2
-#define _PW_LOG_ANDROID_LEVEL_2(...) ALOGI(__VA_ARGS__)
-
-// #define PW_LOG_LEVEL_WARN    3
-#define _PW_LOG_ANDROID_LEVEL_3(...) ALOGW(__VA_ARGS__)
-
-// #define PW_LOG_LEVEL_ERROR    4
-#define _PW_LOG_ANDROID_LEVEL_4(...) ALOGE(__VA_ARGS__)
-
-// #define PW_LOG_LEVEL_CRITICAL    5
-#define _PW_LOG_ANDROID_LEVEL_5(...) ALOGE(__VA_ARGS__)
-
-// #define PW_LOG_LEVEL_FATAL    7
-#define _PW_LOG_ANDROID_LEVEL_7(...) LOG_ALWAYS_FATAL(__VA_ARGS__)
-
-#define PW_HANDLE_LOG(level, flags, ...) \
-  _PW_LOG_ANDROID_LEVEL_##level(__VA_ARGS__)
diff --git a/pw_log_android/public_overrides/pw_log_backend/log_backend.h b/pw_log_android/public_overrides/pw_log_backend/log_backend.h
deleted file mode 100644
index dab27b4..0000000
--- a/pw_log_android/public_overrides/pw_log_backend/log_backend.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_log_android/log_android.h"
diff --git a/pw_log_basic/BUILD b/pw_log_basic/BUILD
new file mode 100644
index 0000000..2c4598d
--- /dev/null
+++ b/pw_log_basic/BUILD
@@ -0,0 +1,51 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "headers",
+    hdrs = [
+        "public/pw_log_basic/log_basic.h",
+        "public_overrides/pw_log_backend/log_backend.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        "//pw_preprocessor",
+    ],
+)
+
+pw_cc_library(
+    name = "pw_log_basic",
+    srcs = [
+        "log_basic.cc",
+        "pw_log_basic_private/config.h",
+    ],
+    deps = [
+        ":headers",
+        "//pw_log:facade",
+        "//pw_string",
+        "//pw_sys_io",
+    ],
+)
diff --git a/pw_log_basic/BUILD.bazel b/pw_log_basic/BUILD.bazel
deleted file mode 100644
index 1b21fc8..0000000
--- a/pw_log_basic/BUILD.bazel
+++ /dev/null
@@ -1,51 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "headers",
-    hdrs = [
-        "public/pw_log_basic/log_basic.h",
-        "public_overrides/pw_log_backend/log_backend.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    deps = [
-        "//pw_preprocessor",
-    ],
-)
-
-pw_cc_library(
-    name = "pw_log_basic",
-    srcs = [
-        "log_basic.cc",
-        "pw_log_basic_private/config.h",
-    ],
-    deps = [
-        ":headers",
-        "//pw_log:facade",
-        "//pw_string",
-        "//pw_sys_io",
-    ],
-)
diff --git a/pw_log_basic/BUILD.gn b/pw_log_basic/BUILD.gn
index a478ea2..5269f09 100644
--- a/pw_log_basic/BUILD.gn
+++ b/pw_log_basic/BUILD.gn
@@ -25,7 +25,7 @@
   pw_log_basic_CONFIG = pw_build_DEFAULT_MODULE_CONFIG
 }
 
-config("public_include_path") {
+config("default_config") {
   include_dirs = [ "public" ]
 }
 
@@ -33,31 +33,25 @@
   include_dirs = [ "public_overrides" ]
 }
 
-# pw_log_basic only provides the backend's interface. The implementation is
-# pulled in through pw_build_LINK_DEPS.
 pw_source_set("pw_log_basic") {
   public_configs = [
     ":backend_config",
-    ":public_include_path",
+    ":default_config",
   ]
-  public = [
-    "public/pw_log_basic/log_basic.h",
-    "public_overrides/pw_log_backend/log_backend.h",
-  ]
-  public_deps = [ dir_pw_preprocessor ]
+  public = [ "public_overrides/pw_log_backend/log_backend.h" ]
+  public_deps = [ ":core" ]
 }
 
-# The log backend deps that might cause circular dependencies, since
-# pw_log is so ubiquitous. These deps are kept separate so they can be
-# depended on from elsewhere.
-pw_source_set("pw_log_basic.impl") {
+pw_source_set("core") {
+  public_configs = [ ":default_config" ]
+  public_deps = [ dir_pw_preprocessor ]
   deps = [
-    ":pw_log_basic",
     "$dir_pw_log:facade",
     dir_pw_string,
     dir_pw_sys_io,
     pw_log_basic_CONFIG,
   ]
+  public = [ "public/pw_log_basic/log_basic.h" ]
 
   # Use emoji log levels if they've been enabled.
   _use_emoji = getenv("PW_EMOJI")
diff --git a/pw_log_basic/CMakeLists.txt b/pw_log_basic/CMakeLists.txt
index 64724a9..3cf0f74 100644
--- a/pw_log_basic/CMakeLists.txt
+++ b/pw_log_basic/CMakeLists.txt
@@ -14,13 +14,10 @@
 
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
 
-pw_add_module_config(pw_log_basic_CONFIG)
-
 pw_auto_add_simple_module(pw_log_basic
   IMPLEMENTS_FACADE
     pw_log
   PRIVATE_DEPS
     pw_string
     pw_sys_io
-    ${pw_log_basic_CONFIG}
 )
diff --git a/pw_log_basic/OWNERS b/pw_log_basic/OWNERS
deleted file mode 100644
index 05eeac9..0000000
--- a/pw_log_basic/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-frolv@google.com
-hepler@google.com
-keir@google.com
diff --git a/pw_log_basic/docs.rst b/pw_log_basic/docs.rst
index 7bdf8c5..985ec50 100644
--- a/pw_log_basic/docs.rst
+++ b/pw_log_basic/docs.rst
@@ -13,7 +13,7 @@
 
 .. cpp:namespace:: pw::log_basic
 
-.. cpp:function:: void SetOutput(void (*log_output)(std::string_view))
+.. cpp:function:: void SetOutput(void (\*log_output)(std::string_view))
 
   Set the log output function, which defaults ``pw_sys_io::WriteLine``. This
   function is called with each formatted log message.
diff --git a/pw_log_basic/log_basic.cc b/pw_log_basic/log_basic.cc
index 71a6197..88fd527 100644
--- a/pw_log_basic/log_basic.cc
+++ b/pw_log_basic/log_basic.cc
@@ -84,8 +84,7 @@
 #endif  // PW_LOG_SHOW_FILENAME
 
 void (*write_log)(std::string_view) = [](std::string_view log) {
-  sys_io::WriteLine(log)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  sys_io::WriteLine(log);
 };
 
 }  // namespace
diff --git a/pw_log_multisink/BUILD b/pw_log_multisink/BUILD
new file mode 100644
index 0000000..979c752
--- /dev/null
+++ b/pw_log_multisink/BUILD
@@ -0,0 +1,52 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_log_queue",
+    srcs = [ "log_queue.cc" ],
+    includes = [ "public" ],
+    deps = [
+        "//pw_bytes",
+        "//pw_containers",
+        "//pw_log",
+        "//pw_result",
+        "//pw_ring_buffer",
+        "//pw_status",
+    ],
+    hdrs = [
+        "public/pw_log_multisink/log_queue.h",
+        "public/pw_log_multisink/sink.h",
+    ]
+)
+
+pw_cc_test(
+    name = "log_queue_test",
+    srcs = [
+        "log_queue_test.cc",
+    ],
+    deps = [
+        "//pw_preprocessor",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_log_multisink/BUILD.gn b/pw_log_multisink/BUILD.gn
new file mode 100644
index 0000000..36093c9
--- /dev/null
+++ b/pw_log_multisink/BUILD.gn
@@ -0,0 +1,59 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/target_types.gni")
+import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
+
+config("default_config") {
+  include_dirs = [ "public" ]
+  visibility = [ ":*" ]
+}
+
+pw_source_set("log_queue") {
+  public_configs = [ ":default_config" ]
+  public = [
+    "public/pw_log_multisink/log_queue.h",
+    "public/pw_log_multisink/sink.h",
+  ]
+  public_deps = [
+    "$dir_pw_bytes",
+    "$dir_pw_containers",
+    "$dir_pw_log",
+    "$dir_pw_result",
+    "$dir_pw_ring_buffer",
+    "$dir_pw_status",
+  ]
+  sources = [ "log_queue.cc" ]
+  deps = [ "$dir_pw_log:protos.pwpb" ]
+}
+
+pw_doc_group("docs") {
+  sources = [ "docs.rst" ]
+}
+
+pw_test("log_queue_test") {
+  sources = [ "log_queue_test.cc" ]
+  deps = [
+    ":log_queue",
+    "$dir_pw_log:protos.pwpb",
+    "$dir_pw_protobuf",
+  ]
+}
+
+pw_test_group("tests") {
+  tests = [ ":log_queue_test" ]
+}
diff --git a/pw_log_multisink/docs.rst b/pw_log_multisink/docs.rst
new file mode 100644
index 0000000..1c16a0c
--- /dev/null
+++ b/pw_log_multisink/docs.rst
@@ -0,0 +1,8 @@
+.. _module-pw_log_multisink:
+
+----------------
+pw_log_multisink
+----------------
+This is a RPC-based logging backend for Pigweed. It is not ready for use, and
+is under construction.
+
diff --git a/pw_log_multisink/log_queue.cc b/pw_log_multisink/log_queue.cc
new file mode 100644
index 0000000..48f9661
--- /dev/null
+++ b/pw_log_multisink/log_queue.cc
@@ -0,0 +1,123 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_log_multisink/log_queue.h"
+
+#include "pw_assert/assert.h"
+#include "pw_log/levels.h"
+#include "pw_log_proto/log.pwpb.h"
+#include "pw_protobuf/wire_format.h"
+#include "pw_status/try.h"
+
+namespace pw::log_rpc {
+namespace {
+
+using pw::protobuf::WireType;
+constexpr uint32_t kLogKey = pw::protobuf::MakeKey(
+    static_cast<uint32_t>(pw::log::LogEntries::Fields::ENTRIES),
+    WireType::kDelimited);
+
+}  // namespace
+
+Status LogQueue::PushTokenizedMessage(ConstByteSpan message,
+                                      uint32_t flags,
+                                      uint32_t level,
+                                      uint32_t line,
+                                      uint32_t thread,
+                                      int64_t timestamp) {
+  pw::protobuf::NestedEncoder nested_encoder(encode_buffer_);
+  pw::log::LogEntry::Encoder encoder(&nested_encoder);
+  Status status;
+
+  encoder.WriteMessageTokenized(message);
+  encoder.WriteLineLevel(
+      (level & PW_LOG_LEVEL_BITMASK) |
+      ((line << PW_LOG_LEVEL_BITWIDTH) & ~PW_LOG_LEVEL_BITMASK));
+  encoder.WriteFlags(flags);
+  encoder.WriteThreadTokenized(thread);
+
+  // TODO(prashanthsw): Add support for delta encoding of the timestamp.
+  encoder.WriteTimestamp(timestamp);
+
+  if (dropped_entries_ > 0) {
+    encoder.WriteDropped(dropped_entries_);
+  }
+
+  ConstByteSpan log_entry;
+  status = nested_encoder.Encode(&log_entry);
+  if (!status.ok() || log_entry.size_bytes() > max_log_entry_size_) {
+    // If an encoding failure occurs or the constructed log entry is larger
+    // than the configured max size, map the error to INTERNAL. If the
+    // underlying allocation of this encode buffer or the nested encoding
+    // sequencing are at fault, they are not the caller's responsibility. If
+    // the log entry is larger than the max allowed size, the log is dropped
+    // intentionally, and it is expected that the caller accepts this
+    // possibility.
+    status = PW_STATUS_INTERNAL;
+  } else {
+    // Try to push back the encoded log entry.
+    status = ring_buffer_.TryPushBack(log_entry, kLogKey);
+  }
+
+  if (!status.ok()) {
+    // The ring buffer may hit the RESOURCE_EXHAUSTED state, causing us
+    // to drop packets. However, this check captures all failures from
+    // Encode and TryPushBack, as any failure here causes packet drop.
+    dropped_entries_++;
+    latest_dropped_timestamp_ = timestamp;
+    return status;
+  }
+
+  dropped_entries_ = 0;
+  return OkStatus();
+}
+
+Result<LogEntries> LogQueue::Pop(LogEntriesBuffer entry_buffer) {
+  size_t ring_buffer_entry_size = 0;
+  PW_TRY(pop_status_for_test_);
+  // The caller must provide a buffer that is at minimum max_log_entry_size, to
+  // ensure that the front entry of the ring buffer can be popped.
+  PW_DCHECK_UINT_GE(entry_buffer.size_bytes(), max_log_entry_size_);
+  PW_TRY(ring_buffer_.PeekFrontWithPreamble(entry_buffer,
+                                            &ring_buffer_entry_size));
+  PW_DCHECK_OK(ring_buffer_.PopFront());
+
+  return LogEntries{
+      .entries = ConstByteSpan(entry_buffer.first(ring_buffer_entry_size)),
+      .entry_count = 1};
+}
+
+LogEntries LogQueue::PopMultiple(LogEntriesBuffer entries_buffer) {
+  size_t offset = 0;
+  size_t entry_count = 0;
+
+  // The caller must provide a buffer that is at minimum max_log_entry_size, to
+  // ensure that the front entry of the ring buffer can be popped.
+  PW_DCHECK_UINT_GE(entries_buffer.size_bytes(), max_log_entry_size_);
+
+  while (ring_buffer_.EntryCount() > 0 &&
+         (entries_buffer.size_bytes() - offset) > max_log_entry_size_) {
+    const Result<LogEntries> result = Pop(entries_buffer.subspan(offset));
+    if (!result.ok()) {
+      break;
+    }
+    offset += result.value().entries.size_bytes();
+    entry_count += result.value().entry_count;
+  }
+
+  return LogEntries{.entries = ConstByteSpan(entries_buffer.first(offset)),
+                    .entry_count = entry_count};
+}
+
+}  // namespace pw::log_rpc
diff --git a/pw_log_multisink/log_queue_test.cc b/pw_log_multisink/log_queue_test.cc
new file mode 100644
index 0000000..fc75706
--- /dev/null
+++ b/pw_log_multisink/log_queue_test.cc
@@ -0,0 +1,235 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_log_multisink/log_queue.h"
+
+#include "gtest/gtest.h"
+#include "pw_log/levels.h"
+#include "pw_log_proto/log.pwpb.h"
+#include "pw_protobuf/decoder.h"
+
+namespace pw::log_rpc {
+namespace {
+
+constexpr size_t kEncodeBufferSize = 512;
+
+constexpr const char kTokenizedMessage[] = "msg_token";
+constexpr uint32_t kFlags = 0xF;
+constexpr uint32_t kLevel = 0b010;
+constexpr uint32_t kLine = 0b101011000;
+constexpr uint32_t kTokenizedThread = 0xF;
+constexpr int64_t kTimestamp = 0;
+
+constexpr size_t kLogBufferSize = kEncodeBufferSize * 3;
+
+void VerifyLogEntry(pw::protobuf::Decoder& log_decoder,
+                    const char* expected_tokenized_message,
+                    const uint32_t expected_flags,
+                    const uint32_t expected_level,
+                    const uint32_t expected_line,
+                    const uint32_t expected_tokenized_thread,
+                    const int64_t expected_timestamp) {
+  ConstByteSpan log_entry_message;
+  EXPECT_TRUE(log_decoder.Next().ok());  // preamble
+  EXPECT_EQ(1U, log_decoder.FieldNumber());
+  EXPECT_TRUE(log_decoder.ReadBytes(&log_entry_message).ok());
+
+  pw::protobuf::Decoder entry_decoder(log_entry_message);
+  ConstByteSpan tokenized_message;
+  EXPECT_TRUE(entry_decoder.Next().ok());  // tokenized_message
+  EXPECT_EQ(1U, entry_decoder.FieldNumber());
+  EXPECT_TRUE(entry_decoder.ReadBytes(&tokenized_message).ok());
+  EXPECT_TRUE(std::memcmp(tokenized_message.begin(),
+                          (const void*)expected_tokenized_message,
+                          tokenized_message.size()) == 0);
+
+  uint32_t line_level;
+  EXPECT_TRUE(entry_decoder.Next().ok());  // line_level
+  EXPECT_EQ(2U, entry_decoder.FieldNumber());
+  EXPECT_TRUE(entry_decoder.ReadUint32(&line_level).ok());
+  EXPECT_EQ(expected_level, line_level & PW_LOG_LEVEL_BITMASK);
+  EXPECT_EQ(expected_line,
+            (line_level & ~PW_LOG_LEVEL_BITMASK) >> PW_LOG_LEVEL_BITWIDTH);
+
+  uint32_t flags;
+  EXPECT_TRUE(entry_decoder.Next().ok());  // flags
+  EXPECT_EQ(3U, entry_decoder.FieldNumber());
+  EXPECT_TRUE(entry_decoder.ReadUint32(&flags).ok());
+  EXPECT_EQ(expected_flags, flags);
+
+  uint32_t tokenized_thread;
+  EXPECT_TRUE(entry_decoder.Next().ok());  // tokenized_thread
+  EXPECT_EQ(4U, entry_decoder.FieldNumber());
+  EXPECT_TRUE(entry_decoder.ReadUint32(&tokenized_thread).ok());
+  EXPECT_EQ(expected_tokenized_thread, tokenized_thread);
+
+  int64_t timestamp;
+  EXPECT_TRUE(entry_decoder.Next().ok());  // timestamp
+  EXPECT_EQ(5U, entry_decoder.FieldNumber());
+  EXPECT_TRUE(entry_decoder.ReadInt64(&timestamp).ok());
+  EXPECT_EQ(expected_timestamp, timestamp);
+}
+
+}  // namespace
+
+TEST(LogQueue, SinglePushPopTokenizedMessage) {
+  std::byte log_buffer[kLogBufferSize];
+  LogQueueWithEncodeBuffer<kEncodeBufferSize> log_queue(log_buffer);
+
+  EXPECT_EQ(OkStatus(),
+            log_queue.PushTokenizedMessage(
+                std::as_bytes(std::span(kTokenizedMessage)),
+                kFlags,
+                kLevel,
+                kLine,
+                kTokenizedThread,
+                kTimestamp));
+
+  std::byte log_entry[kEncodeBufferSize];
+  Result<LogEntries> pop_result = log_queue.Pop(std::span(log_entry));
+  EXPECT_TRUE(pop_result.ok());
+
+  pw::protobuf::Decoder log_decoder(pop_result.value().entries);
+  EXPECT_EQ(pop_result.value().entry_count, 1U);
+  VerifyLogEntry(log_decoder,
+                 kTokenizedMessage,
+                 kFlags,
+                 kLevel,
+                 kLine,
+                 kTokenizedThread,
+                 kTimestamp);
+}
+
+TEST(LogQueue, MultiplePushPopTokenizedMessage) {
+  constexpr size_t kEntryCount = 3;
+
+  std::byte log_buffer[1024];
+  LogQueueWithEncodeBuffer<kEncodeBufferSize> log_queue(log_buffer);
+
+  for (size_t i = 0; i < kEntryCount; i++) {
+    EXPECT_EQ(OkStatus(),
+              log_queue.PushTokenizedMessage(
+                  std::as_bytes(std::span(kTokenizedMessage)),
+                  kFlags,
+                  kLevel,
+                  kLine + (i << 3),
+                  kTokenizedThread,
+                  kTimestamp + i));
+  }
+
+  std::byte log_entry[kEncodeBufferSize];
+  for (size_t i = 0; i < kEntryCount; i++) {
+    Result<LogEntries> pop_result = log_queue.Pop(std::span(log_entry));
+    EXPECT_TRUE(pop_result.ok());
+
+    pw::protobuf::Decoder log_decoder(pop_result.value().entries);
+    EXPECT_EQ(pop_result.value().entry_count, 1U);
+    VerifyLogEntry(log_decoder,
+                   kTokenizedMessage,
+                   kFlags,
+                   kLevel,
+                   kLine + (i << 3),
+                   kTokenizedThread,
+                   kTimestamp + i);
+  }
+}
+
+TEST(LogQueue, PopMultiple) {
+  constexpr size_t kEntryCount = 3;
+
+  std::byte log_buffer[kLogBufferSize];
+  LogQueueWithEncodeBuffer<kEncodeBufferSize> log_queue(log_buffer);
+
+  for (size_t i = 0; i < kEntryCount; i++) {
+    EXPECT_EQ(OkStatus(),
+              log_queue.PushTokenizedMessage(
+                  std::as_bytes(std::span(kTokenizedMessage)),
+                  kFlags,
+                  kLevel,
+                  kLine + (i << 3),
+                  kTokenizedThread,
+                  kTimestamp + i));
+  }
+
+  std::byte log_entries[kLogBufferSize];
+  Result<LogEntries> pop_result = log_queue.PopMultiple(log_entries);
+  EXPECT_TRUE(pop_result.ok());
+
+  pw::protobuf::Decoder log_decoder(pop_result.value().entries);
+  EXPECT_EQ(pop_result.value().entry_count, kEntryCount);
+  for (size_t i = 0; i < kEntryCount; i++) {
+    VerifyLogEntry(log_decoder,
+                   kTokenizedMessage,
+                   kFlags,
+                   kLevel,
+                   kLine + (i << 3),
+                   kTokenizedThread,
+                   kTimestamp + i);
+  }
+}
+
+TEST(LogQueue, TooSmallEncodeBuffer) {
+  constexpr size_t kSmallBuffer = 1;
+
+  std::byte log_buffer[kLogBufferSize];
+  LogQueueWithEncodeBuffer<kSmallBuffer> log_queue(log_buffer);
+  EXPECT_EQ(Status::Internal(),
+            log_queue.PushTokenizedMessage(
+                std::as_bytes(std::span(kTokenizedMessage)),
+                kFlags,
+                kLevel,
+                kLine,
+                kTokenizedThread,
+                kTimestamp));
+}
+
+TEST(LogQueue, TooSmallLogBuffer) {
+  constexpr size_t kSmallerThanPreamble = 1;
+  constexpr size_t kEntryCount = 100;
+
+  // Expect OUT_OF_RANGE when the buffer is smaller than a preamble.
+  std::byte log_buffer[kLogBufferSize];
+  LogQueueWithEncodeBuffer<kEncodeBufferSize> log_queue_small(
+      std::span(log_buffer, kSmallerThanPreamble));
+  EXPECT_EQ(Status::OutOfRange(),
+            log_queue_small.PushTokenizedMessage(
+                std::as_bytes(std::span(kTokenizedMessage)),
+                kFlags,
+                kLevel,
+                kLine,
+                kTokenizedThread,
+                kTimestamp));
+
+  // Expect RESOURCE_EXHAUSTED when there's not enough space for the chunk.
+  LogQueueWithEncodeBuffer<kEncodeBufferSize> log_queue_medium(log_buffer);
+  for (size_t i = 0; i < kEntryCount; i++) {
+    log_queue_medium.PushTokenizedMessage(
+        std::as_bytes(std::span(kTokenizedMessage)),
+        kFlags,
+        kLevel,
+        kLine,
+        kTokenizedThread,
+        kTimestamp);
+  }
+  EXPECT_EQ(Status::ResourceExhausted(),
+            log_queue_medium.PushTokenizedMessage(
+                std::as_bytes(std::span(kTokenizedMessage)),
+                kFlags,
+                kLevel,
+                kLine,
+                kTokenizedThread,
+                kTimestamp));
+}
+
+}  // namespace pw::log_rpc
diff --git a/pw_log_multisink/public/pw_log_multisink/log_queue.h b/pw_log_multisink/public/pw_log_multisink/log_queue.h
new file mode 100644
index 0000000..f358abb
--- /dev/null
+++ b/pw_log_multisink/public/pw_log_multisink/log_queue.h
@@ -0,0 +1,134 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_bytes/span.h"
+#include "pw_result/result.h"
+#include "pw_ring_buffer/prefixed_entry_ring_buffer.h"
+#include "pw_status/status.h"
+#include "pw_status/status_with_size.h"
+
+// LogQueue is a ring-buffer queue of log messages. LogQueue is backed by
+// a caller-provided byte array and stores its messages in the format
+// dictated by the pw_log log.proto format.
+//
+// Logs can be returned as a repeated proto message and the output of this
+// class can be directly fed into an RPC stream.
+//
+// Push logs:
+// 0) Create LogQueue instance.
+// 1) LogQueue::PushTokenizedMessage().
+//
+// Pop logs:
+// 0) Use exsiting LogQueue instance.
+// 1) For single entires, LogQueue::Pop().
+// 2) For multiple entries, LogQueue::PopMultiple().
+namespace pw::log_rpc {
+namespace {
+constexpr size_t kLogEntryMaxSize = 100;
+}  // namespace
+
+using LogEntriesBuffer = ByteSpan;
+
+struct LogEntries {
+  // A buffer containing an encoded protobuf of type pw.log.LogEntries.
+  ConstByteSpan entries;
+  size_t entry_count;
+};
+
+class LogQueue {
+ public:
+  // Constructs a LogQueue. Callers can optionally supply a maximum log entry
+  // size, which limits the size of messages that can be pushed into this log
+  // queue. When such an entry arrives, the queue increments its drop counter.
+  // Calls to Pop and PopMultiple should be provided a buffer of at least the
+  // configured max size.
+  LogQueue(ByteSpan log_buffer,
+           ByteSpan encode_buffer,
+           size_t max_log_entry_size = kLogEntryMaxSize)
+      : pop_status_for_test_(OkStatus()),
+        max_log_entry_size_(max_log_entry_size),
+        encode_buffer_(encode_buffer),
+        ring_buffer_(true) {
+    ring_buffer_.SetBuffer(log_buffer);
+  }
+
+  LogQueue(const LogQueue&) = delete;
+  LogQueue& operator=(const LogQueue&) = delete;
+  LogQueue(LogQueue&&) = delete;
+  LogQueue& operator=(LogQueue&&) = delete;
+
+  // Construct a LogEntry proto message and push it into the ring buffer.
+  // Returns:
+  //
+  //  OK - success.
+  //  FAILED_PRECONDITION - Failed when encoding the proto message.
+  //  RESOURCE_EXHAUSTED - Not enough space in the buffer to write the entry.
+  Status PushTokenizedMessage(ConstByteSpan message,
+                              uint32_t flags,
+                              uint32_t level,
+                              uint32_t line,
+                              uint32_t thread,
+                              int64_t timestamp);
+
+  // Pop the oldest LogEntry from the queue into the provided buffer.
+  // On success, the size is the length of the entry, on failure, the size is 0.
+  // Returns:
+  //
+  // For now, don't support batching. This will always use a single absolute
+  // timestamp, and not use delta encoding.
+  //
+  //  OK - success.
+  //  OUT_OF_RANGE - No entries in queue to read.
+  //  RESOURCE_EXHAUSTED - Destination data std::span was smaller number of
+  //  bytes than the data size of the data chunk being read.  Available
+  //  destination bytes were filled, remaining bytes of the data chunk were
+  //  ignored.
+  Result<LogEntries> Pop(LogEntriesBuffer entry_buffer);
+
+  // Pop entries from the queue into the provided buffer. The provided buffer is
+  // filled until there is insufficient space for the next log entry.
+  // Returns:
+  //
+  // LogEntries - contains an encoded protobuf byte span of pw.log.LogEntries.
+  LogEntries PopMultiple(LogEntriesBuffer entries_buffer);
+
+ protected:
+  friend class LogQueueTester;
+  // For testing, status to return on calls to Pop.
+  Status pop_status_for_test_;
+
+ private:
+  const size_t max_log_entry_size_;
+  size_t dropped_entries_;
+  int64_t latest_dropped_timestamp_;
+
+  ByteSpan encode_buffer_;
+  pw::ring_buffer::PrefixedEntryRingBuffer ring_buffer_{true};
+};
+
+// LogQueueWithEncodeBuffer is a LogQueue where the internal encode buffer is
+// created and managed by this class.
+template <size_t kEncodeBufferSize>
+class LogQueueWithEncodeBuffer : public LogQueue {
+ public:
+  LogQueueWithEncodeBuffer(ByteSpan log_buffer)
+      : LogQueue(log_buffer, encode_buffer_) {}
+
+ private:
+  std::byte encode_buffer_[kEncodeBufferSize];
+};
+
+}  // namespace pw::log_rpc
diff --git a/pw_log_multisink/public/pw_log_multisink/sink.h b/pw_log_multisink/public/pw_log_multisink/sink.h
new file mode 100644
index 0000000..53929ac
--- /dev/null
+++ b/pw_log_multisink/public/pw_log_multisink/sink.h
@@ -0,0 +1,31 @@
+// Copyright 2021 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_containers/intrusive_list.h"
+
+namespace pw::log_sink {
+
+class Sink : public IntrusiveList<Sink>::Item {
+ public:
+  // Provides an entry to the sink (e.g. an encoded protobuf).
+  virtual void HandleEntry(ConstByteSpan entry) = 0;
+  // Informs the sink of an entry that was dropped. This is used to indicate
+  // to the sink that it will produce gaps in outgoing entries.
+  virtual void HandleDropped(size_t count = 1) = 0;
+
+  virtual ~Sink() = default;
+};
+
+}  // namespace pw::log_sink
diff --git a/pw_log_null/BUILD b/pw_log_null/BUILD
new file mode 100644
index 0000000..f87a328
--- /dev/null
+++ b/pw_log_null/BUILD
@@ -0,0 +1,59 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "headers",
+    hdrs = [
+        "public/pw_log_null/log_null.h",
+        "public_overrides/pw_log_backend/log_backend.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        "//pw_preprocessor",
+    ],
+)
+
+pw_cc_library(
+    name = "pw_log_null",
+    srcs = [
+        "log_null.cc",
+    ],
+    deps = [
+        "//pw_log:facade",
+        "//pw_log_null:headers",
+        "//pw_string",
+        "//pw_sys_io",
+    ],
+)
+
+pw_cc_library(
+    name = "test",
+    srcs = [
+        "test.cc",
+        "test_c.c",
+    ],
+)
diff --git a/pw_log_null/BUILD.bazel b/pw_log_null/BUILD.bazel
deleted file mode 100644
index 0e5fe5a..0000000
--- a/pw_log_null/BUILD.bazel
+++ /dev/null
@@ -1,58 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "headers",
-    hdrs = [
-        "public/pw_log_null/log_null.h",
-        "public_overrides/pw_log_backend/log_backend.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    deps = [
-        "//pw_preprocessor",
-    ],
-)
-
-pw_cc_library(
-    name = "pw_log_null",
-    srcs = [
-        "log_null.cc",
-    ],
-    deps = [
-        "//pw_log:facade",
-        "//pw_log_null:headers",
-        "//pw_string",
-        "//pw_sys_io",
-    ],
-)
-
-pw_cc_library(
-    name = "test",
-    srcs = [
-        "test.cc",
-        "test_c.c",
-    ],
-)
diff --git a/pw_log_null/BUILD.gn b/pw_log_null/BUILD.gn
index ff5ddcb..f4b546a 100644
--- a/pw_log_null/BUILD.gn
+++ b/pw_log_null/BUILD.gn
@@ -31,11 +31,6 @@
   public = [ "public_overrides/pw_log_backend/log_backend.h" ]
   sources = [ "public/pw_log_null/log_null.h" ]
   friend = [ ":*" ]
-  public_deps = [ dir_pw_preprocessor ]
-}
-
-pw_source_set("pw_log_null.impl") {
-  sources = []
 }
 
 pw_doc_group("docs") {
diff --git a/pw_log_null/OWNERS b/pw_log_null/OWNERS
deleted file mode 100644
index 05eeac9..0000000
--- a/pw_log_null/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-frolv@google.com
-hepler@google.com
-keir@google.com
diff --git a/pw_log_rpc/BUILD b/pw_log_rpc/BUILD
new file mode 100644
index 0000000..a779851
--- /dev/null
+++ b/pw_log_rpc/BUILD
@@ -0,0 +1,47 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_logs",
+    srcs = [ "logs_rpc.cc" ],
+    includes = [ "public" ],
+    deps = [
+        "//pw_bytes",
+        "//pw_result",
+        "//pw_ring_buffer",
+        "//pw_status",
+    ],
+    hdrs = [ "public/pw_log_rpc/logs_rpc.h" ]
+)
+
+pw_cc_test(
+    name = "logs_rpc_test",
+    srcs = [
+        "logs_rpc_test.cc",
+    ],
+    deps = [
+        "//pw_preprocessor",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_log_rpc/BUILD.bazel b/pw_log_rpc/BUILD.bazel
deleted file mode 100644
index dab9083..0000000
--- a/pw_log_rpc/BUILD.bazel
+++ /dev/null
@@ -1,206 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "log_service",
-    srcs = [
-        "log_service.cc",
-        "public/pw_log_rpc/internal/log_config.h",
-    ],
-    hdrs = ["public/pw_log_rpc/log_service.h"],
-    includes = ["public"],
-    deps = [
-        ":log_filter",
-        ":rpc_log_drain",
-        "//pw_log",
-        "//pw_log:log_proto_cc.pwpb",
-        "//pw_log:log_proto_cc.raw_rpc",
-    ],
-)
-
-pw_cc_library(
-    name = "log_filter_service",
-    srcs = ["log_filter_service.cc"],
-    hdrs = ["public/pw_log_rpc/log_filter_service.h"],
-    includes = ["public"],
-    deps = [
-        ":log_filter",
-        "//pw_log",
-        "//pw_log:log_proto_cc.pwpb",
-        "//pw_log:log_proto_cc.raw_rpc",
-        "//pw_protobuf",
-        "//pw_protobuf:bytes_utils",
-    ],
-)
-
-pw_cc_library(
-    name = "log_filter",
-    srcs = [
-        "log_filter.cc",
-        "public/pw_log_rpc/internal/config.h",
-    ],
-    hdrs = [
-        "public/pw_log_rpc/log_filter.h",
-        "public/pw_log_rpc/log_filter_map.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_assert",
-        "//pw_bytes",
-        "//pw_containers:vector",
-        "//pw_log",
-        "//pw_log:log_proto_cc.pwpb",
-        "//pw_protobuf",
-        "//pw_status",
-    ],
-)
-
-pw_cc_library(
-    name = "rpc_log_drain",
-    srcs = [
-        "public/pw_log_rpc/internal/config.h",
-        "rpc_log_drain.cc",
-    ],
-    hdrs = [
-        "public/pw_log_rpc/rpc_log_drain.h",
-        "public/pw_log_rpc/rpc_log_drain_map.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":log_filter",
-        "//pw_assert",
-        "//pw_function",
-        "//pw_log:log_proto_cc.pwpb",
-        "//pw_log:log_proto_cc.raw_rpc",
-        "//pw_multisink",
-        "//pw_protobuf",
-        "//pw_result",
-        "//pw_status",
-        "//pw_sync:lock_annotations",
-        "//pw_sync:mutex",
-    ],
-)
-
-pw_cc_library(
-    name = "rpc_log_drain_thread",
-    hdrs = ["public/pw_log_rpc/rpc_log_drain_thread.h"],
-    includes = ["public"],
-    deps = [
-        ":log_service",
-        ":rpc_log_drain",
-        "//pw_chrono:system_clock",
-        "//pw_multisink",
-        "//pw_result",
-        "//pw_rpc/raw:server_api",
-        "//pw_status",
-        "//pw_sync:timed_thread_notification",
-        "//pw_thread:thread",
-    ],
-)
-
-pw_cc_library(
-    name = "test_utils",
-    srcs = ["test_utils.cc"],
-    hdrs = ["pw_log_rpc_private/test_utils.h"],
-    deps = [
-        "//pw_bytes",
-        "//pw_containers:vector",
-        "//pw_log",
-        "//pw_log:log_pwpb",
-        "//pw_log_tokenized:metadata",
-        "//pw_protobuf",
-        "//pw_protobuf:bytes_utils",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "log_service_test",
-    srcs = ["log_service_test.cc"],
-    deps = [
-        ":log_filter",
-        ":log_service",
-        ":test_utils",
-        "//pw_containers:vector",
-        "//pw_log",
-        "//pw_log:log_proto_cc.pwpb",
-        "//pw_log:proto_utils",
-        "//pw_log_tokenized:headers",
-        "//pw_protobuf",
-        "//pw_protobuf:bytes_utils",
-        "//pw_result",
-        "//pw_rpc/raw:test_method_context",
-        "//pw_status",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "log_filter_service_test",
-    srcs = ["log_filter_service_test.cc"],
-    deps = [
-        ":log_filter",
-        ":log_filter_service",
-        "//pw_log:log_proto_cc.pwpb",
-        "//pw_protobuf",
-        "//pw_protobuf:bytes_utils",
-        "//pw_result",
-        "//pw_rpc/raw:test_method_context",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "log_filter_test",
-    srcs = ["log_filter_test.cc"],
-    deps = [
-        ":log_filter",
-        "//pw_log:log_proto_cc.pwpb",
-        "//pw_log:proto_utils",
-        "//pw_log_tokenized:headers",
-        "//pw_result",
-        "//pw_status",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "rpc_log_drain_test",
-    srcs = ["rpc_log_drain_test.cc"],
-    deps = [
-        ":log_service",
-        ":rpc_log_drain",
-        ":test_utils",
-        "//pw_bytes",
-        "//pw_log:log_pwpb",
-        "//pw_multisink",
-        "//pw_protobuf",
-        "//pw_rpc",
-        "//pw_rpc/raw:server_api",
-        "//pw_rpc/raw:test_method_context",
-        "//pw_status",
-        "//pw_sync:mutex",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_log_rpc/BUILD.gn b/pw_log_rpc/BUILD.gn
index bfe2f6a..ea40507 100644
--- a/pw_log_rpc/BUILD.gn
+++ b/pw_log_rpc/BUILD.gn
@@ -15,219 +15,37 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_build/target_types.gni")
-import("$dir_pw_chrono/backend.gni")
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_unit_test/test.gni")
 
-config("public_include_path") {
+config("default_config") {
   include_dirs = [ "public" ]
   visibility = [ ":*" ]
 }
 
-pw_source_set("config") {
-  sources = [ "public/pw_log_rpc/internal/config.h" ]
-  public_configs = [ ":public_include_path" ]
-  visibility = [ "./*" ]
-  friend = [ "./*" ]
-}
-
-pw_source_set("log_config") {
-  sources = [ "public/pw_log_rpc/internal/log_config.h" ]
-  public_configs = [ ":public_include_path" ]
-  public_deps = [ ":config" ]
-  visibility = [ "./*" ]
-  friend = [ "./*" ]
-}
-
-pw_source_set("log_service") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_log_rpc/log_service.h" ]
-  sources = [ "log_service.cc" ]
-  deps = [
-    ":log_config",
-    "$dir_pw_log",
-    "$dir_pw_log:protos.pwpb",
-  ]
+pw_source_set("logs") {
+  public_configs = [ ":default_config" ]
+  public = [ "public/pw_log_rpc/logs_rpc.h" ]
+  sources = [ "logs_rpc.cc" ]
   public_deps = [
-    ":rpc_log_drain",
-    "$dir_pw_log:protos.raw_rpc",
-  ]
-}
-
-pw_source_set("log_filter_service") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_log_rpc/log_filter_service.h" ]
-  sources = [ "log_filter_service.cc" ]
-  deps = [
-    "$dir_pw_log:protos.pwpb",
-    "$dir_pw_protobuf",
-  ]
-  public_deps = [
-    ":log_filter",
-    "$dir_pw_log:protos.raw_rpc",
-    "$dir_pw_protobuf:bytes_utils",
-  ]
-}
-
-pw_source_set("log_filter") {
-  public_configs = [ ":public_include_path" ]
-  public = [
-    "public/pw_log_rpc/log_filter.h",
-    "public/pw_log_rpc/log_filter_map.h",
-  ]
-  sources = [ "log_filter.cc" ]
-  deps = [
-    "$dir_pw_log",
-    "$dir_pw_protobuf",
-  ]
-  public_deps = [
-    ":config",
-    "$dir_pw_assert",
-    "$dir_pw_bytes",
-    "$dir_pw_containers:vector",
-    "$dir_pw_log:protos.pwpb",
-    "$dir_pw_protobuf",
-    "$dir_pw_status",
-  ]
-}
-
-pw_source_set("rpc_log_drain") {
-  public_configs = [ ":public_include_path" ]
-  public = [
-    "public/pw_log_rpc/rpc_log_drain.h",
-    "public/pw_log_rpc/rpc_log_drain_map.h",
-  ]
-  sources = [ "rpc_log_drain.cc" ]
-  public_deps = [
-    ":config",
-    ":log_filter",
-    "$dir_pw_assert",
-    "$dir_pw_chrono:system_clock",
-    "$dir_pw_function",
     "$dir_pw_log:protos.pwpb",
     "$dir_pw_log:protos.raw_rpc",
-    "$dir_pw_multisink",
-    "$dir_pw_protobuf",
-    "$dir_pw_result",
-    "$dir_pw_status",
-    "$dir_pw_sync:lock_annotations",
-    "$dir_pw_sync:mutex",
+    "$dir_pw_log_multisink:log_queue",
   ]
 }
 
-pw_source_set("rpc_log_drain_thread") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_log_rpc/rpc_log_drain_thread.h" ]
-  public_deps = [
-    ":log_service",
-    ":rpc_log_drain",
-    "$dir_pw_chrono:system_clock",
-    "$dir_pw_multisink",
-    "$dir_pw_result",
-    "$dir_pw_rpc/raw:server_api",
-    "$dir_pw_status",
-    "$dir_pw_sync:timed_thread_notification",
-    "$dir_pw_thread:thread",
-  ]
-}
-
-pw_source_set("test_utils") {
-  public_deps = [
-    "$dir_pw_bytes",
-    "$dir_pw_containers:vector",
-    "$dir_pw_log_tokenized:metadata",
-    "$dir_pw_protobuf",
-    "$dir_pw_unit_test",
-  ]
+pw_test("logs_rpc_test") {
   deps = [
-    "$dir_pw_log",
-    "$dir_pw_log:protos.pwpb",
-    "$dir_pw_protobuf:bytes_utils",
-  ]
-  public = [ "pw_log_rpc_private/test_utils.h" ]
-  sources = [ "test_utils.cc" ]
-  visibility = [ ":*" ]
-}
-
-pw_test("log_service_test") {
-  enable_if = pw_chrono_SYSTEM_CLOCK_BACKEND != ""
-  sources = [ "log_service_test.cc" ]
-  deps = [
-    ":log_filter",
-    ":log_service",
-    ":test_utils",
-    "$dir_pw_containers:vector",
-    "$dir_pw_log",
-    "$dir_pw_log:proto_utils",
-    "$dir_pw_log:protos.pwpb",
-    "$dir_pw_log_tokenized:metadata",
-    "$dir_pw_protobuf",
-    "$dir_pw_protobuf:bytes_utils",
-    "$dir_pw_result",
+    ":logs",
     "$dir_pw_rpc/raw:test_method_context",
-    "$dir_pw_status",
   ]
+  sources = [ "logs_rpc_test.cc" ]
 }
 
-pw_test("log_filter_service_test") {
-  sources = [ "log_filter_service_test.cc" ]
-  deps = [
-    ":log_filter",
-    ":log_filter_service",
-    "$dir_pw_log:protos.pwpb",
-    "$dir_pw_protobuf",
-    "$dir_pw_protobuf:bytes_utils",
-    "$dir_pw_result",
-    "$dir_pw_rpc/raw:test_method_context",
-    "$dir_pw_status",
-  ]
-}
-
-pw_test("log_filter_test") {
-  sources = [ "log_filter_test.cc" ]
-  deps = [
-    ":log_filter",
-    "$dir_pw_log:proto_utils",
-    "$dir_pw_log:protos.pwpb",
-    "$dir_pw_log_tokenized:metadata",
-    "$dir_pw_result",
-    "$dir_pw_status",
-  ]
-}
-
-pw_test("rpc_log_drain_test") {
-  enable_if = pw_chrono_SYSTEM_CLOCK_BACKEND != ""
-  sources = [ "rpc_log_drain_test.cc" ]
-  deps = [
-    ":log_filter",
-    ":log_service",
-    ":rpc_log_drain",
-    ":test_utils",
-    "$dir_pw_bytes",
-    "$dir_pw_log:proto_utils",
-    "$dir_pw_log:protos.pwpb",
-    "$dir_pw_log_tokenized:metadata",
-    "$dir_pw_multisink",
-    "$dir_pw_protobuf",
-    "$dir_pw_rpc:common",
-    "$dir_pw_rpc/raw:fake_channel_output",
-    "$dir_pw_rpc/raw:server_api",
-    "$dir_pw_rpc/raw:test_method_context",
-    "$dir_pw_status",
-    "$dir_pw_sync:mutex",
-  ]
-}
-
-# TODO(cachinchilla): update docs.
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
 
 pw_test_group("tests") {
-  tests = [
-    ":log_filter_test",
-    ":log_filter_service_test",
-    ":log_service_test",
-    ":rpc_log_drain_test",
-  ]
+  tests = [ ":logs_rpc_test" ]
 }
diff --git a/pw_log_rpc/OWNERS b/pw_log_rpc/OWNERS
deleted file mode 100644
index df7095d..0000000
--- a/pw_log_rpc/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-cachinchilla@google.com
-frolv@google.com
-hepler@google.com
-keir@google.com
-prashanthsw@google.com
diff --git a/pw_log_rpc/docs.rst b/pw_log_rpc/docs.rst
index cf98d85..ff8b466 100644
--- a/pw_log_rpc/docs.rst
+++ b/pw_log_rpc/docs.rst
@@ -1,450 +1,7 @@
 .. _module-pw_log_rpc:
 
-==========
+----------
 pw_log_rpc
-==========
-An RPC-based logging solution for Pigweed with log filtering and log drops
-reporting -- coming soon!
-
-.. warning::
-  This module is under construction and might change in the future.
-
------------
-RPC Logging
------------
-
-How to Use
-==========
-1. Set up RPC
--------------
-Set up RPC for your target device. Basic deployments run RPC over a UART, with
-HDLC on top for framing. See :ref:`module-pw_rpc` for details on how to enable
-``pw_rpc``.
-
-2. Set up tokenized logging (optional)
---------------------------------------
-Set up the :ref:`module-pw_log_tokenized` log backend.
-
-3. Connect the tokenized logging handler to the MultiSink
----------------------------------------------------------
-Create a :ref:`MultiSink <module-pw_multisink>` instance to buffer log entries.
-Then, make the log backend handler,
-``pw_tokenizer_HandleEncodedMessageWithPayload``, encode log entries in the
-``log::LogEntry`` format, and add them to the ``MultiSink``.
-
-4. Create log drains and filters
---------------------------------
-Create an ``RpcLogDrainMap`` with one ``RpcLogDrain`` for each RPC channel used
-to stream logs. Optionally, create a ``FilterMap`` with ``Filter`` objects with
-different IDs. Provide these map to the ``LogService`` and register the latter
-with the application's RPC service. The ``RpcLogDrainMap`` provides a convenient
-way to access and maintain each ``RpcLogDrain``. Attach each ``RpcLogDrain`` to
-the ``MultiSink``. Optionally, set the ``RpcLogDrain`` callback to decide if a
-log should be kept or dropped. This callback can be ``Filter::ShouldDropLog``.
-
-5. Flush the log drains in the background
------------------------------------------
-Depending on the product's requirements, create a thread to flush all
-``RpcLogDrain``\s or one thread per drain. The thread(s) must continuously call
-``RpcLogDrain::Flush()`` to pull entries from the ``MultiSink`` and send them to
-the log listeners. Alternatively, use ``RpcLogDrain::Trickle`` to control the
-rate of log entries streamed. Optionally, set up a callback to notify the
-thread(s) when a drain is open.
-
-Logging over RPC diagrams
-=========================
-
-Sample RPC logs request
------------------------
-The log listener, e.g. a computer, requests logs via RPC. The log service
-receives the request and sets up the corresponding ``RpcLogDrain`` to start the
-log stream.
-
-.. mermaid::
-
-  graph TD
-    computer[Computer]-->pw_rpc;
-    pw_rpc-->log_service[LogService];
-    log_service-->rpc_log_drain_pc[RpcLogDrain<br>streams to<br>computer];;
-
-Sample logging over RPC
-------------------------
-Logs are streamed via RPC to a computer, and to another log listener. There can
-also be internal log readers, i.e. ``MultiSink::Drain``\s, attached to the
-``MultiSink``, such as a writer to persistent memory, for example.
-
-.. mermaid::
-
-  graph TD
-    source1[Source 1]-->log_api[pw_log API];
-    source2[Source 2]-->log_api;
-    log_api-->log_backend[Log backend];
-    log_backend-->multisink[MultiSink];
-    multisink-->drain[MultiSink::Drain];
-    multisink-->rpc_log_drain_pc[RpcLogDrain<br>streams to<br>computer];
-    multisink-->rpc_log_drain_other[RpcLogDrain<br>streams to<br>other log listener];
-    drain-->other_consumer[Other log consumer<br>e.g. persistent memory];
-    rpc_log_drain_pc-->pw_rpc;
-    rpc_log_drain_other-->pw_rpc;
-    pw_rpc-->computer[Computer];
-    pw_rpc-->other_listener[Other log<br>listener];
-
-Components Overview
-===================
-LogEntry and LogEntries
------------------------
-RPC logging uses ``LogEntry`` to encapsulate each entry's data, such as level,
-timestamp, and message. ``LogEntries`` can hold multiple instances of
-``LogEntry`` to send more data using fewer transmissions. The ``LogEntries`` has
-an optional field for the first message's sequence ID that corresponds to the
-count of each ``LogEntry`` that passes the log filter and is sent. A client can
-use this sequence ID and the number of messages in a ``LogEntries`` to figure
-out if logs were dropped during transmission.
-
-RPC log service
----------------
-The ``LogService`` class is an RPC service that provides a way to request a log
-stream sent via RPC and configure log filters. Thus, it helps avoid using a
-different protocol for logs and RPCs over the same interface(s).
-It requires a ``RpcLogDrainMap`` to assign stream writers and delegate the
-log stream flushing to the user's preferred method, as well as a ``FilterMap``
-to retrieve and modify filters.
-
-RpcLogDrain
------------
-An ``RpcLogDrain`` reads from the ``MultiSink`` instance that buffers logs, then
-packs, and sends the retrieved log entries to the log listener. One
-``RpcLogDrain`` is needed for each log listener. An ``RpcLogDrain`` needs a
-thread to continuously call ``Flush()`` to maintain the log stream. A thread can
-maintain multiple log streams, but it must not be the same thread used by the
-RPC server, to avoid blocking it.
-
-Each ``RpcLogDrain`` is identified by a known RPC channel ID and requires a
-``rpc::RawServerWriter`` to write the packed multiple log entries. This writer
-is assigned by the ``LogService::Listen`` RPC.
-
-``RpcLogDrain``\s can also be provided an open RPC writer, to constantly stream
-logs without the need to request them. This is useful in cases where the
-connection to the client is dropped silently because the log stream can continue
-when reconnected without the client requesting logs again if the error handling
-is set to ``kIgnoreWriterErrors`` otherwise the writer will be closed.
-
-An ``RpcLogDrain`` must be attached to a ``MultiSink`` containing multiple
-``log::LogEntry``\s. When ``Flush`` is called, the drain acquires the
-``rpc::RawServerWriter`` 's write buffer, grabs one ``log::LogEntry`` from the
-multisink, encodes it into a ``log::LogEntries`` stream, and repeats the process
-until the write buffer is full. Then the drain calls
-``rpc::RawServerWriter::Write`` to flush the write buffer and repeats the
-process until all the entries in the ``MultiSink`` are read or an error is
-found.
-
-The user must provide a buffer large enough for the largest entry in the
-``MultiSink`` while also accounting for the interface's Maximum Transmission
-Unit (MTU). If the ``RpcLogDrain`` finds a drop message count as it reads the
-``MultiSink`` it will insert a message in the stream with the drop message
-count in the log proto dropped optional field. The receiving end can display the
-count with the logs if desired.
-
-RpcLogDrainMap
---------------
-Provides a convenient way to access all or a single ``RpcLogDrain`` by its RPC
-channel ID.
-
-RpcLogDrainThread
------------------
-The module includes a sample thread that flushes each drain sequentially.
-``RpcLogDrainThread`` takes an encoding buffer span at construction.
-``RpcLogDrainThreadWithBuffer`` takes a template parameter for the buffer size,
-which must be large enough to fit at least one log entry.
-
-Future work might replace this with enqueueing the flush work on a work queue.
-The user can also choose to have different threads flushing individual
-``RpcLogDrain``\s with different priorities.
-
-When creating a ``RpcLogDrainThread``, the thread can be configured to
-rate limit logs by introducing a limit to how many logs can be flushed from
-each sink before a configurable sleep period begins to give the sinks time to
-handle the flushed logs. For example, if the rate limiting is configured to 2
-log bundles per flush with minimum delay of 100ms between flushes, the logging
-thread will send at most 20 log bundles per second over each sink. Log bundle
-size is dictated by the size of the encode buffer provided to the
-RpcLogDrainThread.
-
-Rate limiting is helpful in cases where transient bursts of high volumes of logs
-cause transport buffers to saturate. By rate limiting the RPC log drain, the
-transport buffers are given time to send data. As long as the average logging
-rate is significantly less than the rate limit imposed by the
-``RpcLogDrainThread``, the logging pipeline should be more resilient high
-volume log bursts.
-
-Rate limiting log drains is particularly helpful for systems that collect logs
-to a multisink in bulk when communications aren't available (e.g. collecting
-early boot logs until the logging thread starts). If a very full log buffer is
-suddenly flushed to the sinks without rate limiting, it's possible to overwhelm
-the output buffers if they don't have sufficient headroom.
-
-.. note::
-  Introducing a logging drain rate limit will increase logging latency, but
-  usually not by much. It's important to tune the rate limit configuration to
-  ensure it doesn't unnecessarily introduce a logging bottleneck or
-  significantly increase latency.
-
-Calling ``OpenUnrequestedLogStream()`` is a convenient way to set up a log
-stream that is started without the need to receive an RCP request for logs.
-
-The ``RpcLogDrainThread`` sets up a callback for each drain, to be notified when
-a drain is opened and flushing must resume.
-
----------
-Log Drops
----------
-Unfortunately, logs can be dropped and not reach the destination. This module
-expects to cover all cases and be able to notify the user of log drops when
-possible. Logs can be dropped when
-
-- They don't pass a filter. This is the expected behavior, so filtered logs will
-  not be tracked as dropped logs.
-- The drains are too slow to keep up. In this case, the ring buffer is full of
-  undrained entries; when new logs come in, old entries are dropped. The log
-  stream will contain a ``LogEntry`` message with the number of dropped logs.
-  E.g.
-
-      Dropped 15 logs due to slow reader
-
-- There is an error creating or adding a new log entry, and the ring buffer is
-  notified that the log had to be dropped. The log stream will contain a
-  ``LogEntry`` message with the number of dropped logs.
-  E.g.
-
-      Dropped 15 logs due to slow reader
-
-- A log entry is too large for the stack buffer. The log stream will contain
-  an error message with the drop count. Provide a log buffer that fits the
-  largest entry added to the MultiSink to avoid this error.
-  E.g.
-
-      Dropped 1 log due to stack buffer too small
-
-- A log entry is too large for the outbound buffer. The log stream will contain
-  an error message with the drop count. Provide a log buffer that fits the
-  largest entry added to the MultiSink to avoid this error.
-  E.g.
-
-      Dropped 1 log due to outbound buffer too small
-
-- There are detected errors transmitting log entries. The log stream will
-  contain a ``LogEntry`` with an error message and the number of dropped logs
-  the next time the stream is flushed only if the drain's error handling is set
-  to close the stream on error.
-  E.g.
-
-      Dropped 10 logs due to writer error
-
-- There are undetected errors transmitting or receiving log entries, such as an
-  interface interruption. Clients can calculate the number of logs lost in
-  transit using the sequence ID and number of entries in each stream packet.
-  E.g.
-
-      Dropped 50 logs due to transmission error
-
-The drop count is combined when possible, and reported only when an entry, that
-passes any filters, is going to be sent.
-
--------------
-Log Filtering
--------------
-A ``Filter`` anywhere in the path of a ``LogEntry`` proto, for example, in the
-``PW_LOG*`` macro implementation, or in an ``RpcLogDrain`` if using RPC logging.
-The log filtering service provides read and modify access to the ``Filter``\s
-registered in the ``FilterMap``.
-
-How to Use
-==========
-1. Set up RPC
--------------
-Set up RPC for your target device. See :ref:`module-pw_rpc` for details.
-
-2. Create ``Filter``\s
-----------------------
-Provide each ``Filter`` with its own container for the ``FilterRules`` as big as
-the number of rules desired. These rules can be pre-poluated.
-
-3. Create a ``FilterMap`` and ``FilterService``
------------------------------------------------
-Set up the ``FilterMap`` with the filters than can be modified with the
-``FilterService``. Register the service with the RPC server.
-
-4. Use RPCs to retrieve and modify filter rules
------------------------------------------------
-
-Components Overview
-===================
-Filter::Rule
-------------
-Contains a set of values that are compared against a log when set. All
-conditions must be met for the rule to be met.
-
-- ``action``: drops or keeps the log if the other conditions match.
-  The rule is ignored when inactive.
-
-- ``any_flags_set``: the condition is met if this value is 0 or the log has any
-  of these flags set.
-
-- ``level_greater_than_or_equal``: the condition is met when the log level is
-  greater than or equal to this value.
-
-- ``module_equals``: the condition is met if this byte array is empty, or the
-  log module equals the contents of this byte array.
-
-Filter
-------
-Encapsulates a collection of zero or more ``Filter::Rule``\s and has
-an ID used to modify or retrieve its contents.
-
-FilterMap
----------
-Provides a convenient way to retrieve register filters by ID.
-
-----------------------------
-Logging with filters example
-----------------------------
-The following code shows a sample setup to defer the log handling to the
-``RpcLogDrainThread`` to avoid having the log streaming block at the log
-callsite.
-
-main.cc
-=======
-.. code-block:: cpp
-
-  #include "foo/log.h"
-  #include "pw_log/log.h"
-  #include "pw_thread/detached_thread.h"
-  #include "pw_thread_stl/options.h"
-
-  namespace {
-
-  void RegisterServices() {
-    pw::rpc::system_server::Server().RegisterService(foo::log::log_service);
-    pw::rpc::system_server::Server().RegisterService(foo::log::filter_service);
-  }
-  }  // namespace
-
-  int main() {
-    PW_LOG_INFO("Deferred logging over RPC example");
-    pw::rpc::system_server::Init();
-    RegisterServices();
-    pw::thread::DetachedThread(pw::thread::stl::Options(), foo::log::log_thread);
-    pw::rpc::system_server::Start();
-    return 0;
-  }
-
-foo/log.cc
-==========
-Example of a log backend implementation, where logs enter the ``MultiSink`` and
-log drains and filters are set up.
-
-.. code-block:: cpp
-
-  #include "foo/log.h"
-
-  #include <array>
-  #include <cstdint>
-
-  #include "pw_chrono/system_clock.h"
-  #include "pw_log/proto_utils.h"
-  #include "pw_log_rpc/log_filter.h"
-  #include "pw_log_rpc/log_filter_map.h"
-  #include "pw_log_rpc/log_filter_service.h"
-  #include "pw_log_rpc/log_service.h"
-  #include "pw_log_rpc/rpc_log_drain.h"
-  #include "pw_log_rpc/rpc_log_drain_map.h"
-  #include "pw_log_rpc/rpc_log_drain_thread.h"
-  #include "pw_rpc_system_server/rpc_server.h"
-  #include "pw_sync/interrupt_spin_lock.h"
-  #include "pw_sync/lock_annotations.h"
-  #include "pw_sync/mutex.h"
-  #include "pw_tokenizer/tokenize_to_global_handler_with_payload.h"
-
-  namespace foo::log {
-  namespace {
-  constexpr size_t kLogBufferSize = 5000;
-  // Tokenized logs are typically 12-24 bytes.
-  constexpr size_t kMaxMessageSize = 32;
-  // kMaxLogEntrySize should be less than the MTU of the RPC channel output used
-  // by the provided server writer.
-  constexpr size_t kMaxLogEntrySize =
-      pw::log_rpc::RpcLogDrain::kMinEntrySizeWithoutPayload + kMaxMessageSize;
-  std::array<std::byte, kLogBufferSize> multisink_buffer;
-
-  // To save RAM, share the mutex, since drains will be managed sequentially.
-  pw::sync::Mutex shared_mutex;
-  std::array<std::byte, kMaxEntrySize> client1_buffer
-      PW_GUARDED_BY(shared_mutex);
-  std::array<std::byte, kMaxEntrySize> client2_buffer
-      PW_GUARDED_BY(shared_mutex);
-  std::array<pw::log_rpc::RpcLogDrain, 2> drains = {
-      pw::log_rpc::RpcLogDrain(
-          1,
-          client1_buffer,
-          shared_mutex,
-          RpcLogDrain::LogDrainErrorHandling::kIgnoreWriterErrors),
-      pw::log_rpc::RpcLogDrain(
-          2,
-          client2_buffer,
-          shared_mutex,
-          RpcLogDrain::LogDrainErrorHandling::kIgnoreWriterErrors),
-  };
-
-  pw::sync::InterruptSpinLock log_encode_lock;
-  std::array<std::byte, kMaxLogEntrySize> log_encode_buffer
-      PW_GUARDED_BY(log_encode_lock);
-
-  std::array<Filter::Rule, 2> logs_to_host_filter_rules;
-  std::array<Filter::Rule, 2> logs_to_server_filter_rules{{
-      {
-          .action = Filter::Rule::Action::kKeep,
-          .level_greater_than_or_equal = pw::log::FilterRule::Level::INFO_LEVEL,
-      },
-      {
-          .action = Filter::Rule::Action::kDrop,
-      },
-  }};
-  std::array<Filter, 2> filters{
-      Filter(std::as_bytes(std::span("HOST", 4)), logs_to_host_filter_rules),
-      Filter(std::as_bytes(std::span("WEB", 3)), logs_to_server_filter_rules),
-  };
-  pw::log_rpc::FilterMap filter_map(filters);
-
-  extern "C" void pw_tokenizer_HandleEncodedMessageWithPayload(
-      pw_tokenizer_Payload metadata, const uint8_t message[], size_t size_bytes) {
-    int64_t timestamp =
-        pw::chrono::SystemClock::now().time_since_epoch().count();
-    std::lock_guard lock(log_encode_lock);
-    pw::Result<pw::ConstByteSpan> encoded_log_result =
-      pw::log::EncodeTokenizedLog(
-          metadata, message, size_bytes, timestamp, log_encode_buffer);
-
-    if (!encoded_log_result.ok()) {
-      GetMultiSink().HandleDropped();
-      return;
-    }
-    GetMultiSink().HandleEntry(encoded_log_result.value());
-  }
-  }  // namespace
-
-  pw::log_rpc::RpcLogDrainMap drain_map(drains);
-  pw::log_rpc::RpcLogDrainThread log_thread(GetMultiSink(), drain_map);
-  pw::log_rpc::LogService log_service(drain_map);
-  pw::log_rpc::FilterService filter_service(filter_map);
-
-  pw::multisink::MultiSink& GetMultiSink() {
-    static pw::multisink::MultiSink multisink(multisink_buffer);
-    return multisink;
-  }
-  }  // namespace foo::log
-
-Logging in other source files
------------------------------
-To defer logging, other source files must simply include ``pw_log/log.h`` and
-use the :ref:`module-pw_log` APIs, as long as the source set that includes
-``foo/log.cc`` is setup as the log backend.
+----------
+This is a RPC-based logging backend for Pigweed. It is not ready for use, and
+is under construction.
diff --git a/pw_log_rpc/log_filter.cc b/pw_log_rpc/log_filter.cc
deleted file mode 100644
index 5385be4..0000000
--- a/pw_log_rpc/log_filter.cc
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_log_rpc/log_filter.h"
-
-#include "pw_log/levels.h"
-#include "pw_protobuf/decoder.h"
-#include "pw_status/try.h"
-
-namespace pw::log_rpc {
-namespace {
-
-// Returns true if the provided log parameters match the given filter rule.
-bool IsRuleMet(const Filter::Rule& rule,
-               uint32_t level,
-               ConstByteSpan module,
-               uint32_t flags) {
-  if (level < static_cast<uint32_t>(rule.level_greater_than_or_equal)) {
-    return false;
-  }
-  if ((rule.any_flags_set != 0) && ((flags & rule.any_flags_set) == 0)) {
-    return false;
-  }
-  if (!rule.module_equals.empty() && !std::equal(module.begin(),
-                                                 module.end(),
-                                                 rule.module_equals.begin(),
-                                                 rule.module_equals.end())) {
-    return false;
-  }
-  return true;
-}
-
-}  // namespace
-
-Status Filter::UpdateRulesFromProto(ConstByteSpan buffer) {
-  if (rules_.empty()) {
-    return Status::FailedPrecondition();
-  }
-
-  // Reset rules.
-  for (auto& rule : rules_) {
-    rule = {};
-  }
-
-  protobuf::Decoder decoder(buffer);
-  Status status;
-  for (size_t i = 0; (i < rules_.size()) && (status = decoder.Next()).ok();
-       ++i) {
-    ConstByteSpan rule_buffer;
-    PW_TRY(decoder.ReadBytes(&rule_buffer));
-    protobuf::Decoder rule_decoder(rule_buffer);
-    while ((status = rule_decoder.Next()).ok()) {
-      switch (
-          static_cast<log::FilterRule::Fields>(rule_decoder.FieldNumber())) {
-        case log::FilterRule::Fields::LEVEL_GREATER_THAN_OR_EQUAL:
-          PW_TRY(rule_decoder.ReadUint32(reinterpret_cast<uint32_t*>(
-              &rules_[i].level_greater_than_or_equal)));
-          break;
-        case log::FilterRule::Fields::MODULE_EQUALS: {
-          ConstByteSpan module;
-          PW_TRY(rule_decoder.ReadBytes(&module));
-          if (module.size() > rules_[i].module_equals.max_size()) {
-            return Status::InvalidArgument();
-          }
-          rules_[i].module_equals.assign(module.begin(), module.end());
-        } break;
-        case log::FilterRule::Fields::ANY_FLAGS_SET:
-          PW_TRY(rule_decoder.ReadUint32(&rules_[i].any_flags_set));
-          break;
-        case log::FilterRule::Fields::ACTION:
-          PW_TRY(rule_decoder.ReadUint32(
-              reinterpret_cast<uint32_t*>(&rules_[i].action)));
-          break;
-      }
-    }
-  }
-  return status.IsOutOfRange() ? OkStatus() : status;
-}
-
-bool Filter::ShouldDropLog(ConstByteSpan entry) const {
-  if (rules_.empty()) {
-    return false;
-  }
-
-  uint32_t log_level = 0;
-  ConstByteSpan log_module;
-  uint32_t log_flags = 0;
-  protobuf::Decoder decoder(entry);
-  while (decoder.Next().ok()) {
-    switch (static_cast<log::LogEntry::Fields>(decoder.FieldNumber())) {
-      case log::LogEntry::Fields::LINE_LEVEL:
-        if (decoder.ReadUint32(&log_level).ok()) {
-          log_level &= PW_LOG_LEVEL_BITMASK;
-        }
-        break;
-      case log::LogEntry::Fields::MODULE:
-        decoder.ReadBytes(&log_module).IgnoreError();
-        break;
-      case log::LogEntry::Fields::FLAGS:
-        decoder.ReadUint32(&log_flags).IgnoreError();
-        break;
-      default:
-        break;
-    }
-  }
-
-  // Follow the action of the first rule whose condition is met.
-  for (const auto& rule : rules_) {
-    if (rule.action == Filter::Rule::Action::kInactive) {
-      continue;
-    }
-    if (IsRuleMet(rule, log_level, log_module, log_flags)) {
-      return rule.action == Filter::Rule::Action::kDrop;
-    }
-  }
-
-  return false;
-}
-
-}  // namespace pw::log_rpc
diff --git a/pw_log_rpc/log_filter_service.cc b/pw_log_rpc/log_filter_service.cc
deleted file mode 100644
index 65e3974..0000000
--- a/pw_log_rpc/log_filter_service.cc
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_log_rpc/log_filter_service.h"
-
-#include "pw_log/log.h"
-#include "pw_log/proto/log.pwpb.h"
-#include "pw_log_rpc/log_filter.h"
-#include "pw_protobuf/decoder.h"
-
-namespace pw::log_rpc {
-
-Status FilterService::SetFilterImpl(ConstByteSpan request) {
-  protobuf::Decoder decoder(request);
-  PW_TRY(decoder.Next());
-  if (static_cast<log::SetFilterRequest::Fields>(decoder.FieldNumber()) !=
-      log::SetFilterRequest::Fields::FILTER_ID) {
-    return Status::InvalidArgument();
-  }
-  ConstByteSpan filter_id;
-  PW_TRY(decoder.ReadBytes(&filter_id));
-  Result<Filter*> filter = filter_map_.GetFilterFromId(filter_id);
-  if (!filter.ok()) {
-    return Status::NotFound();
-  }
-
-  PW_TRY(decoder.Next());
-  ConstByteSpan filter_buffer;
-  if (static_cast<log::SetFilterRequest::Fields>(decoder.FieldNumber()) !=
-      log::SetFilterRequest::Fields::FILTER) {
-    return Status::InvalidArgument();
-  }
-  PW_TRY(decoder.ReadBytes(&filter_buffer));
-
-  return filter.value()->UpdateRulesFromProto(filter_buffer);
-}
-
-StatusWithSize FilterService::GetFilterImpl(ConstByteSpan request,
-                                            ByteSpan response) {
-  protobuf::Decoder decoder(request);
-  PW_TRY_WITH_SIZE(decoder.Next());
-  if (static_cast<log::GetFilterRequest::Fields>(decoder.FieldNumber()) !=
-      log::GetFilterRequest::Fields::FILTER_ID) {
-    return StatusWithSize::InvalidArgument();
-  }
-  ConstByteSpan filter_id;
-  PW_TRY_WITH_SIZE(decoder.ReadBytes(&filter_id));
-  Result<Filter*> filter = filter_map_.GetFilterFromId(filter_id);
-  if (!filter.ok()) {
-    return StatusWithSize::NotFound();
-  }
-
-  log::Filter::MemoryEncoder encoder(response);
-  for (auto& rule : (*filter)->rules()) {
-    log::FilterRule::StreamEncoder rule_encoder = encoder.GetRuleEncoder();
-    rule_encoder.WriteLevelGreaterThanOrEqual(rule.level_greater_than_or_equal)
-        .IgnoreError();
-    rule_encoder.WriteModuleEquals(rule.module_equals).IgnoreError();
-    rule_encoder.WriteAnyFlagsSet(rule.any_flags_set).IgnoreError();
-    rule_encoder.WriteAction(static_cast<log::FilterRule::Action>(rule.action))
-        .IgnoreError();
-    PW_TRY_WITH_SIZE(rule_encoder.status());
-  }
-  PW_TRY_WITH_SIZE(encoder.status());
-
-  return StatusWithSize(encoder.size());
-}
-
-StatusWithSize FilterService::ListFilterIdsImpl(ByteSpan response) {
-  log::FilterIdListResponse::MemoryEncoder encoder(response);
-  for (auto& filter : filter_map_.filters()) {
-    PW_TRY_WITH_SIZE(encoder.WriteFilterId(filter.id()));
-  }
-  return StatusWithSize(encoder.size());
-}
-
-}  // namespace pw::log_rpc
diff --git a/pw_log_rpc/log_filter_service_test.cc b/pw_log_rpc/log_filter_service_test.cc
deleted file mode 100644
index d0d9afc..0000000
--- a/pw_log_rpc/log_filter_service_test.cc
+++ /dev/null
@@ -1,388 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_log_rpc/log_filter_service.h"
-
-#include <array>
-#include <cstdint>
-#include <limits>
-
-#include "gtest/gtest.h"
-#include "pw_bytes/endian.h"
-#include "pw_log/proto/log.pwpb.h"
-#include "pw_log_rpc/log_filter.h"
-#include "pw_log_rpc/log_filter_map.h"
-#include "pw_protobuf/bytes_utils.h"
-#include "pw_protobuf/decoder.h"
-#include "pw_result/result.h"
-#include "pw_rpc/channel.h"
-#include "pw_rpc/raw/test_method_context.h"
-
-namespace pw::log_rpc {
-namespace {
-
-class FilterServiceTest : public ::testing::Test {
- public:
-  FilterServiceTest() : filter_map_(filters_) {}
-
- protected:
-  FilterMap filter_map_;
-  static constexpr size_t kMaxFilterRules = 3;
-  std::array<Filter::Rule, kMaxFilterRules> rules1_;
-  std::array<Filter::Rule, kMaxFilterRules> rules2_;
-  std::array<Filter::Rule, kMaxFilterRules> rules3_;
-  static constexpr std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id1_{
-      std::byte(65), std::byte(66), std::byte(67), std::byte(0)};
-  static constexpr std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id2_{
-      std::byte(68), std::byte(69), std::byte(70), std::byte(0)};
-  static constexpr std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id3_{
-      std::byte(71), std::byte(72), std::byte(73), std::byte(0)};
-  static constexpr size_t kMaxFilters = 3;
-  std::array<Filter, kMaxFilters> filters_ = {
-      Filter(filter_id1_, rules1_),
-      Filter(filter_id2_, rules2_),
-      Filter(filter_id3_, rules3_),
-  };
-};
-
-TEST_F(FilterServiceTest, GetFilterIds) {
-  PW_RAW_TEST_METHOD_CONTEXT(FilterService, ListFilterIds, 1)
-  context(filter_map_);
-  context.call({});
-  ASSERT_EQ(OkStatus(), context.status());
-  ASSERT_TRUE(context.done());
-  ASSERT_EQ(context.responses().size(), 1u);
-  protobuf::Decoder decoder(context.responses()[0]);
-
-  for (const auto& filter : filter_map_.filters()) {
-    ASSERT_EQ(decoder.Next(), OkStatus());
-    ASSERT_EQ(decoder.FieldNumber(), 1u);  // filter_id
-    ConstByteSpan filter_id;
-    ASSERT_EQ(decoder.ReadBytes(&filter_id), OkStatus());
-    ASSERT_EQ(filter_id.size(), filter.id().size());
-    EXPECT_EQ(
-        std::memcmp(filter_id.data(), filter.id().data(), filter_id.size()), 0);
-  }
-  EXPECT_FALSE(decoder.Next().ok());
-
-  // No IDs reported when the filter map is empty.
-  FilterMap empty_filter_map({});
-  PW_RAW_TEST_METHOD_CONTEXT(FilterService, ListFilterIds, 1)
-  no_filter_context(empty_filter_map);
-  no_filter_context.call({});
-  ASSERT_EQ(OkStatus(), no_filter_context.status());
-  ASSERT_TRUE(no_filter_context.done());
-  ASSERT_EQ(no_filter_context.responses().size(), 1u);
-  protobuf::Decoder no_filter_decoder(no_filter_context.responses()[0]);
-  uint32_t filter_count = 0;
-  while (no_filter_decoder.Next().ok()) {
-    EXPECT_EQ(no_filter_decoder.FieldNumber(), 1u);  // filter_id
-    ++filter_count;
-  }
-  EXPECT_EQ(filter_count, 0u);
-}
-
-Status EncodeFilterRule(const Filter::Rule& rule,
-                        log::FilterRule::StreamEncoder& encoder) {
-  PW_TRY(
-      encoder.WriteLevelGreaterThanOrEqual(rule.level_greater_than_or_equal));
-  PW_TRY(encoder.WriteModuleEquals(rule.module_equals));
-  PW_TRY(encoder.WriteAnyFlagsSet(rule.any_flags_set));
-  return encoder.WriteAction(static_cast<log::FilterRule::Action>(rule.action));
-}
-
-Status EncodeFilter(const Filter& filter, log::Filter::StreamEncoder& encoder) {
-  for (auto& rule : filter.rules()) {
-    log::FilterRule::StreamEncoder rule_encoder = encoder.GetRuleEncoder();
-    PW_TRY(EncodeFilterRule(rule, rule_encoder));
-  }
-  return OkStatus();
-}
-
-Result<ConstByteSpan> EncodeFilterRequest(const Filter& filter,
-                                          ByteSpan buffer) {
-  stream::MemoryWriter writer(buffer);
-  std::byte encode_buffer[256];
-  protobuf::StreamEncoder encoder(writer, encode_buffer);
-  PW_TRY(encoder.WriteBytes(
-      static_cast<uint32_t>(log::SetFilterRequest::Fields::FILTER_ID),
-      filter.id()));
-  {
-    log::Filter::StreamEncoder filter_encoder = encoder.GetNestedEncoder(
-        static_cast<uint32_t>(log::SetFilterRequest::Fields::FILTER));
-    PW_TRY(EncodeFilter(filter, filter_encoder));
-  }  // Let the StreamEncoder destructor finalize the data.
-  return ConstByteSpan(writer.data(), writer.bytes_written());
-}
-
-void VerifyRule(const Filter::Rule& rule, const Filter::Rule& expected_rule) {
-  EXPECT_EQ(rule.level_greater_than_or_equal,
-            expected_rule.level_greater_than_or_equal);
-  EXPECT_EQ(rule.module_equals, expected_rule.module_equals);
-  EXPECT_EQ(rule.any_flags_set, expected_rule.any_flags_set);
-  EXPECT_EQ(rule.action, expected_rule.action);
-}
-
-TEST_F(FilterServiceTest, SetFilterRules) {
-  const std::array<Filter::Rule, 4> new_rules{{
-      {
-          .action = Filter::Rule::Action::kKeep,
-          .level_greater_than_or_equal = log::FilterRule::Level::DEBUG_LEVEL,
-          .any_flags_set = 0x0f,
-          .module_equals{std::byte(123)},
-      },
-      {
-          .action = Filter::Rule::Action::kInactive,
-          .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
-          .any_flags_set = 0xef,
-          .module_equals{},
-      },
-      {
-          .action = Filter::Rule::Action::kKeep,
-          .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
-          .any_flags_set = 0x1234,
-          .module_equals{std::byte(99)},
-      },
-      {
-          .action = Filter::Rule::Action::kDrop,
-          .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
-          .any_flags_set = 0,
-          .module_equals{std::byte(4)},
-      },
-  }};
-  const Filter new_filter(filters_[0].id(),
-                          const_cast<std::array<Filter::Rule, 4>&>(new_rules));
-
-  std::byte request_buffer[512];
-  const auto request = EncodeFilterRequest(new_filter, request_buffer);
-  ASSERT_EQ(request.status(), OkStatus());
-
-  PW_RAW_TEST_METHOD_CONTEXT(FilterService, SetFilter, 1)
-  context(filter_map_);
-  context.call(request.value());
-  ASSERT_EQ(OkStatus(), context.status());
-
-  size_t i = 0;
-  for (const auto& rule : filters_[0].rules()) {
-    VerifyRule(rule, new_rules[i++]);
-  }
-}
-
-TEST_F(FilterServiceTest, SetFilterRulesWhenUsedByDrain) {
-  const std::array<Filter::Rule, 4> new_filter_rules{{
-      {
-          .action = Filter::Rule::Action::kKeep,
-          .level_greater_than_or_equal = log::FilterRule::Level::CRITICAL_LEVEL,
-          .any_flags_set = 0xfd,
-          .module_equals{std::byte(543)},
-      },
-      {
-          .action = Filter::Rule::Action::kInactive,
-          .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
-          .any_flags_set = 0xca,
-          .module_equals{},
-      },
-      {
-          .action = Filter::Rule::Action::kKeep,
-          .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
-          .any_flags_set = 0xabcd,
-          .module_equals{std::byte(9000)},
-      },
-      {
-          .action = Filter::Rule::Action::kDrop,
-          .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
-          .any_flags_set = 0,
-          .module_equals{std::byte(123)},
-      },
-  }};
-  Filter& filter = filters_[0];
-  const Filter new_filter(
-      filter.id(), const_cast<std::array<Filter::Rule, 4>&>(new_filter_rules));
-
-  std::byte request_buffer[256];
-  const auto request = EncodeFilterRequest(new_filter, request_buffer);
-  ASSERT_EQ(request.status(), OkStatus());
-
-  PW_RAW_TEST_METHOD_CONTEXT(FilterService, SetFilter, 1)
-  context(filter_map_);
-  context.call(request.value());
-  ASSERT_EQ(OkStatus(), context.status());
-
-  size_t i = 0;
-  for (const auto& rule : filter.rules()) {
-    VerifyRule(rule, new_filter_rules[i++]);
-  }
-
-  // An empty request should not modify the filter.
-  PW_RAW_TEST_METHOD_CONTEXT(FilterService, SetFilter, 1)
-  context_no_filter(filter_map_);
-  context_no_filter.call({});
-  EXPECT_EQ(Status::OutOfRange(), context_no_filter.status());
-
-  i = 0;
-  for (const auto& rule : filter.rules()) {
-    VerifyRule(rule, new_filter_rules[i++]);
-  }
-
-  // A new request for logs with a new filter updates filter.
-  const std::array<Filter::Rule, 4> second_filter_rules{{
-      {
-          .action = Filter::Rule::Action::kKeep,
-          .level_greater_than_or_equal = log::FilterRule::Level::DEBUG_LEVEL,
-          .any_flags_set = 0xab,
-          .module_equals{},
-      },
-      {
-          .action = Filter::Rule::Action::kDrop,
-          .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
-          .any_flags_set = 0x11,
-          .module_equals{std::byte(34)},
-      },
-      {
-          .action = Filter::Rule::Action::kKeep,
-          .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
-          .any_flags_set = 0xef,
-          .module_equals{std::byte(23)},
-      },
-      {
-          .action = Filter::Rule::Action::kDrop,
-          .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
-          .any_flags_set = 0x0f,
-          .module_equals{},
-      },
-  }};
-  const Filter second_filter(
-      filter.id(),
-      const_cast<std::array<Filter::Rule, 4>&>(second_filter_rules));
-
-  std::memset(request_buffer, 0, sizeof(request_buffer));
-  const auto second_filter_request =
-      EncodeFilterRequest(second_filter, request_buffer);
-  ASSERT_EQ(second_filter_request.status(), OkStatus());
-  PW_RAW_TEST_METHOD_CONTEXT(FilterService, SetFilter, 1)
-  context_new_filter(filter_map_);
-  context_new_filter.call(second_filter_request.value());
-  ASSERT_EQ(OkStatus(), context.status());
-
-  i = 0;
-  for (const auto& rule : filter.rules()) {
-    VerifyRule(rule, second_filter_rules[i++]);
-  }
-}
-
-void VerifyFilterRule(protobuf::Decoder& decoder,
-                      const Filter::Rule& expected_rule) {
-  ASSERT_TRUE(decoder.Next().ok());
-  ASSERT_EQ(decoder.FieldNumber(), 1u);  // level_greater_than_or_equal
-  log::FilterRule::Level level_greater_than_or_equal;
-  ASSERT_EQ(decoder.ReadUint32(
-                reinterpret_cast<uint32_t*>(&level_greater_than_or_equal)),
-            OkStatus());
-  EXPECT_EQ(level_greater_than_or_equal,
-            expected_rule.level_greater_than_or_equal);
-
-  ASSERT_TRUE(decoder.Next().ok());
-  ASSERT_EQ(decoder.FieldNumber(), 2u);  // module_equals
-  ConstByteSpan module_equals;
-  ASSERT_EQ(decoder.ReadBytes(&module_equals), OkStatus());
-  ASSERT_EQ(module_equals.size(), expected_rule.module_equals.size());
-  EXPECT_EQ(std::memcmp(module_equals.data(),
-                        expected_rule.module_equals.data(),
-                        module_equals.size()),
-            0);
-
-  ASSERT_TRUE(decoder.Next().ok());
-  ASSERT_EQ(decoder.FieldNumber(), 3u);  // any_flags_set
-  uint32_t any_flags_set;
-  ASSERT_EQ(decoder.ReadUint32(&any_flags_set), OkStatus());
-  EXPECT_EQ(any_flags_set, expected_rule.any_flags_set);
-
-  ASSERT_TRUE(decoder.Next().ok());
-  ASSERT_EQ(decoder.FieldNumber(), 4u);  // action
-  Filter::Rule::Action action;
-  ASSERT_EQ(decoder.ReadUint32(reinterpret_cast<uint32_t*>(&action)),
-            OkStatus());
-  EXPECT_EQ(action, expected_rule.action);
-}
-
-void VerifyFilterRules(protobuf::Decoder& decoder,
-                       std::span<const Filter::Rule> expected_rules) {
-  size_t rules_found = 0;
-  while (decoder.Next().ok()) {
-    ConstByteSpan rule;
-    EXPECT_TRUE(decoder.ReadBytes(&rule).ok());
-    protobuf::Decoder rule_decoder(rule);
-    if (rules_found >= expected_rules.size()) {
-      break;
-    }
-    VerifyFilterRule(rule_decoder, expected_rules[rules_found]);
-    ++rules_found;
-  }
-  EXPECT_EQ(rules_found, expected_rules.size());
-}
-
-TEST_F(FilterServiceTest, GetFilterRules) {
-  PW_RAW_TEST_METHOD_CONTEXT(FilterService, GetFilter, 1)
-  context(filter_map_);
-
-  std::byte request_buffer[64];
-  log::GetFilterRequest::MemoryEncoder encoder(request_buffer);
-  ASSERT_EQ(OkStatus(), encoder.WriteFilterId(filter_id1_));
-  const auto request = ConstByteSpan(encoder);
-  context.call(request);
-  ASSERT_EQ(OkStatus(), context.status());
-  ASSERT_TRUE(context.done());
-  ASSERT_EQ(context.responses().size(), 1u);
-
-  // Verify against empty rules.
-  protobuf::Decoder decoder(context.responses()[0]);
-  VerifyFilterRules(decoder, rules1_);
-
-  // Partially populate rules.
-  rules1_[0].action = Filter::Rule::Action::kKeep;
-  rules1_[0].level_greater_than_or_equal = log::FilterRule::Level::DEBUG_LEVEL;
-  rules1_[0].any_flags_set = 0xab;
-  const std::array<std::byte, 2> module1{std::byte(123), std::byte(0xab)};
-  rules1_[0].module_equals.assign(module1.begin(), module1.end());
-  rules1_[1].action = Filter::Rule::Action::kDrop;
-  rules1_[1].level_greater_than_or_equal = log::FilterRule::Level::ERROR_LEVEL;
-  rules1_[1].any_flags_set = 0;
-
-  PW_RAW_TEST_METHOD_CONTEXT(FilterService, GetFilter, 1)
-  context2(filter_map_);
-  context2.call(request);
-  ASSERT_EQ(OkStatus(), context2.status());
-  ASSERT_EQ(context2.responses().size(), 1u);
-  protobuf::Decoder decoder2(context2.responses()[0]);
-  VerifyFilterRules(decoder2, rules1_);
-
-  // Modify the rest of the filter rules.
-  rules1_[2].action = Filter::Rule::Action::kKeep;
-  rules1_[2].level_greater_than_or_equal = log::FilterRule::Level::FATAL_LEVEL;
-  rules1_[2].any_flags_set = 0xcd;
-  const std::array<std::byte, 2> module2{std::byte(1), std::byte(2)};
-  rules1_[2].module_equals.assign(module2.begin(), module2.end());
-  rules1_[3].action = Filter::Rule::Action::kInactive;
-
-  PW_RAW_TEST_METHOD_CONTEXT(FilterService, GetFilter, 1)
-  context3(filter_map_);
-  context3.call(request);
-  ASSERT_EQ(OkStatus(), context3.status());
-  ASSERT_EQ(context3.responses().size(), 1u);
-  protobuf::Decoder decoder3(context3.responses()[0]);
-  VerifyFilterRules(decoder3, rules1_);
-}
-
-}  // namespace
-}  // namespace pw::log_rpc
diff --git a/pw_log_rpc/log_filter_test.cc b/pw_log_rpc/log_filter_test.cc
deleted file mode 100644
index be1fcdd..0000000
--- a/pw_log_rpc/log_filter_test.cc
+++ /dev/null
@@ -1,515 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_log_rpc/log_filter.h"
-
-#include <array>
-#include <cstddef>
-#include <cstring>
-
-#include "gtest/gtest.h"
-#include "pw_bytes/endian.h"
-#include "pw_log/levels.h"
-#include "pw_log/log.h"
-#include "pw_log/proto/log.pwpb.h"
-#include "pw_log/proto_utils.h"
-#include "pw_log_rpc/log_filter_map.h"
-#include "pw_log_tokenized/metadata.h"
-#include "pw_result/result.h"
-#include "pw_status/status.h"
-#include "pw_status/try.h"
-
-namespace pw::log_rpc {
-namespace {
-
-constexpr uint32_t kSampleModule = 0x1234;
-constexpr uint32_t kSampleFlags = 0x3;
-constexpr char kSampleMessage[] = "message";
-constexpr auto kSampleModuleLittleEndian =
-    bytes::CopyInOrder<uint32_t>(std::endian::little, kSampleModule);
-
-// Creates and encodes a log entry in the provided buffer.
-template <uintptr_t log_level, uintptr_t module, uintptr_t flags>
-Result<ConstByteSpan> EncodeLogEntry(std::string_view message,
-                                     ByteSpan buffer) {
-  auto metadata = log_tokenized::Metadata::Set<log_level, module, flags, 0>();
-  return log::EncodeTokenizedLog(metadata,
-                                 std::as_bytes(std::span(message)),
-                                 /*ticks_since_epoch=*/0,
-                                 /*thread_name=*/{},
-                                 buffer);
-}
-
-Status EncodeFilterRule(const Filter::Rule& rule,
-                        log::FilterRule::StreamEncoder& encoder) {
-  PW_TRY(
-      encoder.WriteLevelGreaterThanOrEqual(rule.level_greater_than_or_equal));
-  PW_TRY(encoder.WriteModuleEquals(rule.module_equals));
-  PW_TRY(encoder.WriteAnyFlagsSet(rule.any_flags_set));
-  return encoder.WriteAction(static_cast<log::FilterRule::Action>(rule.action));
-}
-
-Result<ConstByteSpan> EncodeFilter(const Filter& filter, ByteSpan buffer) {
-  log::Filter::MemoryEncoder encoder(buffer);
-  for (auto& rule : filter.rules()) {
-    log::FilterRule::StreamEncoder rule_encoder = encoder.GetRuleEncoder();
-    PW_TRY(EncodeFilterRule(rule, rule_encoder));
-  }
-  return ConstByteSpan(encoder);
-}
-
-void VerifyRule(const Filter::Rule& rule, const Filter::Rule& expected_rule) {
-  EXPECT_EQ(rule.level_greater_than_or_equal,
-            expected_rule.level_greater_than_or_equal);
-  EXPECT_EQ(rule.module_equals, expected_rule.module_equals);
-  EXPECT_EQ(rule.any_flags_set, expected_rule.any_flags_set);
-  EXPECT_EQ(rule.action, expected_rule.action);
-}
-
-TEST(FilterMap, RetrieveFiltersById) {
-  const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id1{
-      std::byte(0xfe), std::byte(0xed), std::byte(0xba), std::byte(0xb1)};
-  const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id2{
-      std::byte(0xca), std::byte(0xfe), std::byte(0xc0), std::byte(0xc0)};
-  const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id3{
-      std::byte(0xfa), std::byte(0xfe), std::byte(0xf1), std::byte(0xf0)};
-  std::array<Filter, 3> filters = {
-      Filter(filter_id1, {}),
-      Filter(filter_id2, {}),
-      Filter(filter_id3, {}),
-  };
-
-  FilterMap filter_map(filters);
-
-  // Check that each filters() element points to the same object provided.
-  std::span<const Filter> filter_list = filter_map.filters();
-  ASSERT_EQ(filter_list.size(), filters.size());
-  size_t i = 0;
-  for (auto& filter : filter_list) {
-    EXPECT_EQ(&filter, &filters[i++]);
-  }
-
-  auto filter_result = filter_map.GetFilterFromId(filter_id3);
-  ASSERT_TRUE(filter_result.ok());
-  EXPECT_EQ(filter_result.value(), &filters[2]);
-
-  filter_result = filter_map.GetFilterFromId(filter_id2);
-  ASSERT_TRUE(filter_result.ok());
-  EXPECT_EQ(filter_result.value(), &filters[1]);
-
-  filter_result = filter_map.GetFilterFromId(filter_id1);
-  ASSERT_TRUE(filter_result.ok());
-  EXPECT_EQ(filter_result.value(), &filters[0]);
-
-  const std::array<std::byte, cfg::kMaxFilterIdBytes> invalid_id{
-      std::byte(0xd0), std::byte(0x1c), std::byte(0xe7), std::byte(0xea)};
-  filter_result = filter_map.GetFilterFromId(invalid_id);
-  ASSERT_EQ(filter_result.status(), Status::NotFound());
-}
-
-TEST(Filter, UpdateFilterRules) {
-  const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id{
-      std::byte(0xba), std::byte(0x1d), std::byte(0xba), std::byte(0xb1)};
-  std::array<Filter::Rule, 4> rules;
-  const std::array<Filter::Rule, 4> new_rules{{
-      {
-          .action = Filter::Rule::Action::kKeep,
-          .level_greater_than_or_equal = log::FilterRule::Level::DEBUG_LEVEL,
-          .any_flags_set = 0x0f,
-          .module_equals{std::byte(123)},
-      },
-      {
-          .action = Filter::Rule::Action::kInactive,
-          .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
-          .any_flags_set = 0xef,
-          .module_equals{},
-      },
-      {
-          .action = Filter::Rule::Action::kKeep,
-          .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
-          .any_flags_set = 0x1234,
-          .module_equals{std::byte(99)},
-      },
-      {
-          .action = Filter::Rule::Action::kDrop,
-          .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
-          .any_flags_set = 0,
-          .module_equals{std::byte(4)},
-      },
-  }};
-
-  Filter filter(filter_id, rules);
-  const Filter new_filter(filter_id,
-                          const_cast<std::array<Filter::Rule, 4>&>(new_rules));
-  std::byte buffer[256];
-  auto encode_result = EncodeFilter(new_filter, buffer);
-  ASSERT_EQ(encode_result.status(), OkStatus());
-  EXPECT_EQ(filter.UpdateRulesFromProto(encode_result.value()), OkStatus());
-
-  size_t i = 0;
-  for (const auto& rule : filter.rules()) {
-    VerifyRule(rule, new_rules[i++]);
-  }
-
-  // A new filter with no rules should clear filter.
-  const Filter empty_filter(filter_id, {});
-  std::memset(buffer, 0, sizeof(buffer));
-  encode_result = EncodeFilter(empty_filter, buffer);
-  ASSERT_EQ(encode_result.status(), OkStatus());
-  EXPECT_EQ(filter.UpdateRulesFromProto(encode_result.value()), OkStatus());
-  const Filter::Rule empty_rule{
-      .action = Filter::Rule::Action::kInactive,
-      .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
-      .any_flags_set = 0,
-      .module_equals{},
-  };
-  for (const auto& rule : filter.rules()) {
-    VerifyRule(rule, empty_rule);
-  }
-  EXPECT_TRUE(empty_filter.rules().empty());
-
-  // Passing a new filter with less rules.
-  const std::array<Filter::Rule, 2> few_rules{{
-      {
-          .action = Filter::Rule::Action::kInactive,
-          .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
-          .any_flags_set = 0xef,
-          .module_equals{},
-      },
-      {
-          .action = Filter::Rule::Action::kKeep,
-          .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
-          .any_flags_set = 0x1234,
-          .module_equals{std::byte(99)},
-      },
-  }};
-  const Filter filter_few_rules(
-      filter_id, const_cast<std::array<Filter::Rule, 2>&>(few_rules));
-  std::memset(buffer, 0, sizeof(buffer));
-  encode_result = EncodeFilter(filter_few_rules, buffer);
-  ASSERT_EQ(encode_result.status(), OkStatus());
-  EXPECT_EQ(filter.UpdateRulesFromProto(encode_result.value()), OkStatus());
-  i = 0;
-  for (const auto& rule : filter.rules()) {
-    if (i >= few_rules.size()) {
-      VerifyRule(rule, empty_rule);
-    } else {
-      VerifyRule(rule, few_rules[i++]);
-    }
-  }
-
-  // Passing a new filter with extra rules.
-  const std::array<Filter::Rule, 6> extra_rules{{
-      {
-          .action = Filter::Rule::Action::kKeep,
-          .level_greater_than_or_equal = log::FilterRule::Level::DEBUG_LEVEL,
-          .any_flags_set = 0x0f,
-          .module_equals{std::byte(123)},
-      },
-      {
-          .action = Filter::Rule::Action::kInactive,
-          .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
-          .any_flags_set = 0xef,
-          .module_equals{},
-      },
-      {
-          .action = Filter::Rule::Action::kInactive,
-          .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
-          .any_flags_set = 0xef,
-          .module_equals{},
-      },
-      {
-          .action = Filter::Rule::Action::kKeep,
-          .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
-          .any_flags_set = 0x1234,
-          .module_equals{std::byte(99)},
-      },
-      {
-          .action = Filter::Rule::Action::kDrop,
-          .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
-          .any_flags_set = 0,
-          .module_equals{std::byte(4)},
-      },
-      {
-          .action = Filter::Rule::Action::kKeep,
-          .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
-          .any_flags_set = 0x1234,
-          .module_equals{std::byte(99)},
-      },
-  }};
-  const Filter filter_extra_rules(
-      filter_id, const_cast<std::array<Filter::Rule, 6>&>(extra_rules));
-  std::memset(buffer, 0, sizeof(buffer));
-  encode_result = EncodeFilter(filter_extra_rules, buffer);
-  ASSERT_EQ(encode_result.status(), OkStatus());
-  EXPECT_EQ(filter.UpdateRulesFromProto(encode_result.value()), OkStatus());
-  i = 0;
-  for (const auto& rule : filter.rules()) {
-    VerifyRule(rule, extra_rules[i++]);
-  }
-
-  // A filter with no rules buffer cannot get rules updated.
-  Filter filter_no_rules(filter_id, {});
-  EXPECT_EQ(filter_no_rules.UpdateRulesFromProto(encode_result.value()),
-            Status::FailedPrecondition());
-}
-
-TEST(FilterTest, FilterLogsRuleDefaultDrop) {
-  const std::array<Filter::Rule, 2> rules{{
-      {
-          .action = Filter::Rule::Action::kKeep,
-          .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
-          .any_flags_set = kSampleFlags,
-          .module_equals{kSampleModuleLittleEndian.begin(),
-                         kSampleModuleLittleEndian.end()},
-      },
-      // This rule catches all logs.
-      {
-          .action = Filter::Rule::Action::kDrop,
-          .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
-          .any_flags_set = 0,
-          .module_equals = {},
-      },
-  }};
-  const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id{
-      std::byte(0xfe), std::byte(0xed), std::byte(0xba), std::byte(0xb1)};
-  const Filter filter(filter_id,
-                      const_cast<std::array<Filter::Rule, 2>&>(rules));
-
-  std::array<std::byte, 50> buffer;
-  const Result<ConstByteSpan> log_entry_info =
-      EncodeLogEntry<PW_LOG_LEVEL_INFO, kSampleModule, kSampleFlags>(
-          kSampleMessage, buffer);
-  ASSERT_EQ(log_entry_info.status(), OkStatus());
-  EXPECT_FALSE(filter.ShouldDropLog(log_entry_info.value()));
-
-  buffer.fill(std::byte(0));
-  const Result<ConstByteSpan> log_entry_debug =
-      EncodeLogEntry<PW_LOG_LEVEL_DEBUG, kSampleModule, kSampleFlags>(
-          kSampleMessage, buffer);
-  ASSERT_EQ(log_entry_debug.status(), OkStatus());
-  EXPECT_TRUE(filter.ShouldDropLog(log_entry_debug.value()));
-
-  buffer.fill(std::byte(0));
-  const Result<ConstByteSpan> log_entry_warn =
-      EncodeLogEntry<PW_LOG_LEVEL_WARN, kSampleModule, kSampleFlags>(
-          kSampleMessage, buffer);
-  ASSERT_EQ(log_entry_warn.status(), OkStatus());
-  EXPECT_FALSE(filter.ShouldDropLog(log_entry_warn.value()));
-
-  buffer.fill(std::byte(0));
-  const Result<ConstByteSpan> log_entry_error =
-      EncodeLogEntry<PW_LOG_LEVEL_ERROR, kSampleModule, kSampleFlags>(
-          kSampleMessage, buffer);
-  ASSERT_EQ(log_entry_error.status(), OkStatus());
-  EXPECT_FALSE(filter.ShouldDropLog(log_entry_error.value()));
-
-  buffer.fill(std::byte(0));
-  const Result<ConstByteSpan> log_entry_info_different =
-      EncodeLogEntry<PW_LOG_LEVEL_INFO, 0, 0>(kSampleMessage, buffer);
-  ASSERT_EQ(log_entry_info_different.status(), OkStatus());
-  EXPECT_TRUE(filter.ShouldDropLog(log_entry_info_different.value()));
-  // Because the last rule catches all logs, the filter default action is not
-  // applied.
-  const Filter filter_default_drop(
-      filter_id, const_cast<std::array<Filter::Rule, 2>&>(rules));
-  EXPECT_TRUE(
-      filter_default_drop.ShouldDropLog(log_entry_info_different.value()));
-
-  buffer.fill(std::byte(0));
-  const Result<ConstByteSpan> log_entry_same_flags =
-      EncodeLogEntry<0, 0, kSampleFlags>(kSampleMessage, buffer);
-  ASSERT_EQ(log_entry_same_flags.status(), OkStatus());
-  EXPECT_TRUE(filter.ShouldDropLog(log_entry_same_flags.value()));
-
-  buffer.fill(std::byte(0));
-  const Result<ConstByteSpan> log_entry_same_module =
-      EncodeLogEntry<0, kSampleModule, 0>(kSampleMessage, buffer);
-  ASSERT_EQ(log_entry_same_module.status(), OkStatus());
-  EXPECT_TRUE(filter.ShouldDropLog(log_entry_same_module.value()));
-}
-
-TEST(FilterTest, FilterLogsKeepLogsWhenNoRuleMatches) {
-  // There is no rule that catches all logs.
-  const std::array<Filter::Rule, 1> rules{{
-      {
-          .action = Filter::Rule::Action::kKeep,
-          .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
-          .any_flags_set = kSampleFlags,
-          .module_equals = {kSampleModuleLittleEndian.begin(),
-                            kSampleModuleLittleEndian.end()},
-      },
-  }};
-
-  // Filters should not share rules if they are mutable, to avoid race
-  // conditions.
-  const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id{
-      std::byte(0xfe), std::byte(0xed), std::byte(0xba), std::byte(0xb1)};
-  const Filter filter(filter_id,
-                      const_cast<std::array<Filter::Rule, 1>&>(rules));
-
-  std::array<std::byte, 50> buffer;
-  const Result<ConstByteSpan> log_entry_info =
-      EncodeLogEntry<PW_LOG_LEVEL_INFO, kSampleModule, kSampleFlags>(
-          kSampleMessage, buffer);
-  ASSERT_EQ(log_entry_info.status(), OkStatus());
-  EXPECT_FALSE(filter.ShouldDropLog(log_entry_info.value()));
-
-  buffer.fill(std::byte(0));
-  const Result<ConstByteSpan> log_entry_debug =
-      EncodeLogEntry<PW_LOG_LEVEL_DEBUG, kSampleModule, kSampleFlags>(
-          kSampleMessage, buffer);
-  ASSERT_EQ(log_entry_debug.status(), OkStatus());
-  EXPECT_FALSE(filter.ShouldDropLog(log_entry_debug.value()));
-
-  buffer.fill(std::byte(0));
-  const Result<ConstByteSpan> log_entry_warn =
-      EncodeLogEntry<PW_LOG_LEVEL_WARN, kSampleModule, kSampleFlags>(
-          kSampleMessage, buffer);
-  ASSERT_EQ(log_entry_warn.status(), OkStatus());
-  EXPECT_FALSE(filter.ShouldDropLog(log_entry_warn.value()));
-
-  buffer.fill(std::byte(0));
-  const Result<ConstByteSpan> log_entry_error =
-      EncodeLogEntry<PW_LOG_LEVEL_ERROR, kSampleModule, kSampleFlags>(
-          kSampleMessage, buffer);
-  ASSERT_EQ(log_entry_error.status(), OkStatus());
-  EXPECT_FALSE(filter.ShouldDropLog(log_entry_error.value()));
-
-  buffer.fill(std::byte(0));
-  const Result<ConstByteSpan> log_entry_info_different =
-      EncodeLogEntry<PW_LOG_LEVEL_INFO, 0, 0>(kSampleMessage, buffer);
-  ASSERT_EQ(log_entry_info_different.status(), OkStatus());
-  EXPECT_FALSE(filter.ShouldDropLog(log_entry_info_different.value()));
-
-  buffer.fill(std::byte(0));
-  const Result<ConstByteSpan> log_entry_same_flags =
-      EncodeLogEntry<0, 0, kSampleFlags>(kSampleMessage, buffer);
-  ASSERT_EQ(log_entry_same_flags.status(), OkStatus());
-  EXPECT_FALSE(filter.ShouldDropLog(log_entry_same_flags.value()));
-
-  buffer.fill(std::byte(0));
-  const Result<ConstByteSpan> log_entry_same_module =
-      EncodeLogEntry<0, kSampleModule, 0>(kSampleMessage, buffer);
-  ASSERT_EQ(log_entry_same_module.status(), OkStatus());
-  EXPECT_FALSE(filter.ShouldDropLog(log_entry_same_module.value()));
-}
-
-TEST(FilterTest, FilterLogsKeepLogsWhenRulesEmpty) {
-  // Filters should not share rules if they are mutable, to avoid race
-  // conditions.
-  const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id{
-      std::byte(0xfe), std::byte(0xed), std::byte(0xba), std::byte(0xb1)};
-  const Filter filter(filter_id, {});
-
-  std::array<std::byte, 50> buffer;
-  const Result<ConstByteSpan> log_entry_info =
-      EncodeLogEntry<PW_LOG_LEVEL_INFO, kSampleModule, kSampleFlags>(
-          kSampleMessage, buffer);
-  ASSERT_EQ(log_entry_info.status(), OkStatus());
-  EXPECT_FALSE(filter.ShouldDropLog(log_entry_info.value()));
-
-  buffer.fill(std::byte(0));
-  const Result<ConstByteSpan> log_entry_debug =
-      EncodeLogEntry<PW_LOG_LEVEL_DEBUG, kSampleModule, kSampleFlags>(
-          kSampleMessage, buffer);
-  ASSERT_EQ(log_entry_debug.status(), OkStatus());
-  EXPECT_FALSE(filter.ShouldDropLog(log_entry_debug.value()));
-
-  buffer.fill(std::byte(0));
-  const Result<ConstByteSpan> log_entry_warn =
-      EncodeLogEntry<PW_LOG_LEVEL_WARN, kSampleModule, kSampleFlags>(
-          kSampleMessage, buffer);
-  ASSERT_EQ(log_entry_warn.status(), OkStatus());
-  EXPECT_FALSE(filter.ShouldDropLog(log_entry_warn.value()));
-
-  buffer.fill(std::byte(0));
-  const Result<ConstByteSpan> log_entry_error =
-      EncodeLogEntry<PW_LOG_LEVEL_ERROR, kSampleModule, kSampleFlags>(
-          kSampleMessage, buffer);
-  ASSERT_EQ(log_entry_error.status(), OkStatus());
-  EXPECT_FALSE(filter.ShouldDropLog(log_entry_error.value()));
-
-  buffer.fill(std::byte(0));
-  const Result<ConstByteSpan> log_entry_info_different =
-      EncodeLogEntry<PW_LOG_LEVEL_INFO, 0, 0>(kSampleMessage, buffer);
-  ASSERT_EQ(log_entry_info_different.status(), OkStatus());
-  EXPECT_FALSE(filter.ShouldDropLog(log_entry_info_different.value()));
-
-  buffer.fill(std::byte(0));
-  const Result<ConstByteSpan> log_entry_same_flags =
-      EncodeLogEntry<0, 0, kSampleFlags>(kSampleMessage, buffer);
-  ASSERT_EQ(log_entry_same_flags.status(), OkStatus());
-  EXPECT_FALSE(filter.ShouldDropLog(log_entry_same_flags.value()));
-
-  buffer.fill(std::byte(0));
-  const Result<ConstByteSpan> log_entry_same_module =
-      EncodeLogEntry<0, kSampleModule, 0>(kSampleMessage, buffer);
-  ASSERT_EQ(log_entry_same_module.status(), OkStatus());
-  EXPECT_FALSE(filter.ShouldDropLog(log_entry_same_module.value()));
-}
-
-TEST(FilterTest, FilterLogsFirstRuleWins) {
-  const std::array<Filter::Rule, 2> rules{{
-      {
-          .action = Filter::Rule::Action::kKeep,
-          .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
-          .any_flags_set = kSampleFlags,
-          .module_equals = {kSampleModuleLittleEndian.begin(),
-                            kSampleModuleLittleEndian.end()},
-      },
-      {
-          .action = Filter::Rule::Action::kDrop,
-          .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
-          .any_flags_set = kSampleFlags,
-          .module_equals = {kSampleModuleLittleEndian.begin(),
-                            kSampleModuleLittleEndian.end()},
-      },
-  }};
-  const std::array<Filter::Rule, 2> rules_reversed{{
-      {
-          .action = Filter::Rule::Action::kDrop,
-          .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
-          .any_flags_set = kSampleFlags,
-          .module_equals = {kSampleModuleLittleEndian.begin(),
-                            kSampleModuleLittleEndian.end()},
-      },
-      {
-          .action = Filter::Rule::Action::kKeep,
-          .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
-          .any_flags_set = kSampleFlags,
-          .module_equals = {kSampleModuleLittleEndian.begin(),
-                            kSampleModuleLittleEndian.end()},
-      },
-  }};
-  const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id1{
-      std::byte(0xfe), std::byte(0xed), std::byte(0xba), std::byte(0xb1)};
-  const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id2{
-      std::byte(0), std::byte(0), std::byte(0), std::byte(2)};
-  const Filter filter(filter_id1,
-                      const_cast<std::array<Filter::Rule, 2>&>(rules));
-  const Filter filter_reverse_rules(
-      filter_id2, const_cast<std::array<Filter::Rule, 2>&>(rules_reversed));
-
-  std::array<std::byte, 50> buffer;
-  const Result<ConstByteSpan> log_entry_info =
-      EncodeLogEntry<PW_LOG_LEVEL_INFO, kSampleModule, kSampleFlags>(
-          kSampleMessage, buffer);
-  ASSERT_EQ(log_entry_info.status(), OkStatus());
-  EXPECT_FALSE(filter.ShouldDropLog(log_entry_info.value()));
-  EXPECT_TRUE(filter_reverse_rules.ShouldDropLog(log_entry_info.value()));
-}
-
-}  // namespace
-}  // namespace pw::log_rpc
diff --git a/pw_log_rpc/log_service.cc b/pw_log_rpc/log_service.cc
deleted file mode 100644
index c700af4..0000000
--- a/pw_log_rpc/log_service.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// clang-format off
-#include "pw_log_rpc/internal/log_config.h" // PW_LOG_* macros must be first.
-
-#include "pw_log_rpc/log_service.h"
-// clang-format on
-
-#include "pw_log/log.h"
-#include "pw_log/proto/log.pwpb.h"
-
-namespace pw::log_rpc {
-
-void LogService::Listen(ConstByteSpan, rpc::RawServerWriter& writer) {
-  uint32_t channel_id = writer.channel_id();
-  Result<RpcLogDrain*> drain = drains_.GetDrainFromChannelId(channel_id);
-  if (!drain.ok()) {
-    return;
-  }
-
-  if (const Status status = drain.value()->Open(writer); !status.ok()) {
-    PW_LOG_DEBUG("Could not start new log stream. %d",
-                 static_cast<int>(status.code()));
-  }
-}
-
-}  // namespace pw::log_rpc
diff --git a/pw_log_rpc/log_service_test.cc b/pw_log_rpc/log_service_test.cc
deleted file mode 100644
index 1072ec5..0000000
--- a/pw_log_rpc/log_service_test.cc
+++ /dev/null
@@ -1,787 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_log_rpc/log_service.h"
-
-#include <array>
-#include <cstdint>
-#include <limits>
-
-#include "gtest/gtest.h"
-#include "pw_assert/check.h"
-#include "pw_bytes/endian.h"
-#include "pw_containers/vector.h"
-#include "pw_log/log.h"
-#include "pw_log/proto/log.pwpb.h"
-#include "pw_log/proto_utils.h"
-#include "pw_log_rpc/log_filter.h"
-#include "pw_log_rpc_private/test_utils.h"
-#include "pw_log_tokenized/metadata.h"
-#include "pw_protobuf/bytes_utils.h"
-#include "pw_protobuf/decoder.h"
-#include "pw_result/result.h"
-#include "pw_rpc/channel.h"
-#include "pw_rpc/raw/fake_channel_output.h"
-#include "pw_rpc/raw/test_method_context.h"
-#include "pw_sync/mutex.h"
-
-namespace pw::log_rpc {
-namespace {
-
-using log::pw_rpc::raw::Logs;
-
-#define LOG_SERVICE_METHOD_CONTEXT \
-  PW_RAW_TEST_METHOD_CONTEXT(LogService, Listen, 10)
-
-constexpr size_t kMaxMessageSize = 50;
-constexpr size_t kMaxLogEntrySize =
-    RpcLogDrain::kMinEntryBufferSize + kMaxMessageSize;
-static_assert(RpcLogDrain::kMinEntryBufferSize < kMaxLogEntrySize);
-constexpr size_t kMultiSinkBufferSize = kMaxLogEntrySize * 10;
-constexpr size_t kMaxDrains = 3;
-constexpr char kMessage[] = "message";
-// A message small enough to fit encoded in
-// LogServiceTest::entry_encode_buffer_ but large enough to not fit in
-// LogServiceTest::small_buffer_.
-constexpr char kLongMessage[] =
-    "This is a long log message that will be dropped.";
-static_assert(sizeof(kLongMessage) < kMaxMessageSize);
-static_assert(sizeof(kLongMessage) + RpcLogDrain::kMinEntrySizeWithoutPayload >
-              RpcLogDrain::kMinEntryBufferSize);
-std::array<std::byte, 1> rpc_request_buffer;
-constexpr auto kSampleMetadata =
-    log_tokenized::Metadata::Set<PW_LOG_LEVEL_INFO, 123, 0x03, __LINE__>();
-constexpr auto kDropMessageMetadata =
-    log_tokenized::Metadata::Set<0, 0, 0, 0>();
-constexpr int64_t kSampleTimestamp = 1000;
-
-// `LogServiceTest` sets up a logging environment for testing with a
-// `MultiSink` for log entries, and multiple `RpcLogDrain`s for consuming such
-// log entries. It includes methods to add log entries to the `MultiSink`, and
-// buffers for encoding and retrieving log entries. Tests can choose how many
-// entries to add to the multisink, and which drain to use.
-class LogServiceTest : public ::testing::Test {
- public:
-  LogServiceTest() : multisink_(multisink_buffer_), drain_map_(drains_) {
-    for (auto& drain : drain_map_.drains()) {
-      multisink_.AttachDrain(drain);
-    }
-  }
-
-  void AddLogEntries(size_t log_count,
-                     std::string_view message,
-                     log_tokenized::Metadata metadata,
-                     int64_t timestamp) {
-    for (size_t i = 0; i < log_count; ++i) {
-      ASSERT_TRUE(AddLogEntry(message, metadata, timestamp).ok());
-    }
-  }
-
-  StatusWithSize AddLogEntry(std::string_view message,
-                             log_tokenized::Metadata metadata,
-                             int64_t timestamp) {
-    Result<ConstByteSpan> encoded_log_result =
-        log::EncodeTokenizedLog(metadata,
-                                std::as_bytes(std::span(message)),
-                                timestamp,
-                                /*thread_name=*/{},
-                                entry_encode_buffer_);
-    PW_TRY_WITH_SIZE(encoded_log_result.status());
-    multisink_.HandleEntry(encoded_log_result.value());
-    return StatusWithSize(encoded_log_result.value().size());
-  }
-
- protected:
-  std::array<std::byte, kMultiSinkBufferSize> multisink_buffer_;
-  multisink::MultiSink multisink_;
-  RpcLogDrainMap drain_map_;
-  std::array<std::byte, kMaxLogEntrySize> entry_encode_buffer_;
-  static constexpr size_t kMaxFilterRules = 3;
-  std::array<Filter::Rule, kMaxFilterRules> rules1_;
-  std::array<Filter::Rule, kMaxFilterRules> rules2_;
-  std::array<Filter::Rule, kMaxFilterRules> rules3_;
-  static constexpr std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id1_{
-      std::byte(65), std::byte(66), std::byte(67), std::byte(0)};
-  static constexpr std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id2_{
-      std::byte(68), std::byte(69), std::byte(70), std::byte(0)};
-  static constexpr std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id3_{
-      std::byte(71), std::byte(72), std::byte(73), std::byte(0)};
-  std::array<Filter, kMaxDrains> filters_ = {
-      Filter(filter_id1_, rules1_),
-      Filter(filter_id2_, rules2_),
-      Filter(filter_id3_, rules3_),
-  };
-
-  // Drain Buffers
-  std::array<std::byte, kMaxLogEntrySize> drain_buffer1_;
-  std::array<std::byte, kMaxLogEntrySize> drain_buffer2_;
-  std::array<std::byte, RpcLogDrain::kMinEntryBufferSize> small_buffer_;
-  static constexpr uint32_t kIgnoreWriterErrorsDrainId = 1;
-  static constexpr uint32_t kCloseWriterOnErrorDrainId = 2;
-  static constexpr uint32_t kSmallBufferDrainId = 3;
-  sync::Mutex shared_mutex_;
-  std::array<RpcLogDrain, kMaxDrains> drains_{
-      RpcLogDrain(kIgnoreWriterErrorsDrainId,
-                  drain_buffer1_,
-                  shared_mutex_,
-                  RpcLogDrain::LogDrainErrorHandling::kIgnoreWriterErrors,
-                  &filters_[0]),
-      RpcLogDrain(kCloseWriterOnErrorDrainId,
-                  drain_buffer2_,
-                  shared_mutex_,
-                  RpcLogDrain::LogDrainErrorHandling::kCloseStreamOnWriterError,
-                  &filters_[1]),
-      RpcLogDrain(kSmallBufferDrainId,
-                  small_buffer_,
-                  shared_mutex_,
-                  RpcLogDrain::LogDrainErrorHandling::kIgnoreWriterErrors,
-                  &filters_[2]),
-  };
-
-  std::array<std::byte, 128> encoding_buffer_ = {};
-};
-
-TEST_F(LogServiceTest, AssignWriter) {
-  // Drains don't have writers.
-  for (auto& drain : drain_map_.drains()) {
-    EXPECT_EQ(drain.Flush(encoding_buffer_), Status::Unavailable());
-  }
-
-  // Create context directed to drain with ID 1.
-  RpcLogDrain& active_drain = drains_[0];
-  const uint32_t drain_channel_id = active_drain.channel_id();
-  LOG_SERVICE_METHOD_CONTEXT context(drain_map_);
-  context.set_channel_id(drain_channel_id);
-
-  // Call RPC, which sets the drain's writer.
-  context.call(rpc_request_buffer);
-  EXPECT_EQ(active_drain.Flush(encoding_buffer_), OkStatus());
-
-  // Other drains are still missing writers.
-  for (auto& drain : drain_map_.drains()) {
-    if (drain.channel_id() != drain_channel_id) {
-      EXPECT_EQ(drain.Flush(encoding_buffer_), Status::Unavailable());
-    }
-  }
-
-  // Calling an ongoing log stream must not change the active drain's
-  // writer, and the second writer must not get any responses.
-  LOG_SERVICE_METHOD_CONTEXT second_call_context(drain_map_);
-  second_call_context.set_channel_id(drain_channel_id);
-  second_call_context.call(rpc_request_buffer);
-  EXPECT_EQ(active_drain.Flush(encoding_buffer_), OkStatus());
-  ASSERT_TRUE(second_call_context.done());
-  EXPECT_EQ(second_call_context.responses().size(), 0u);
-
-  // Setting a new writer on a closed stream is allowed.
-  ASSERT_EQ(active_drain.Close(), OkStatus());
-  LOG_SERVICE_METHOD_CONTEXT third_call_context(drain_map_);
-  third_call_context.set_channel_id(drain_channel_id);
-  third_call_context.call(rpc_request_buffer);
-  EXPECT_EQ(active_drain.Flush(encoding_buffer_), OkStatus());
-  ASSERT_FALSE(third_call_context.done());
-  EXPECT_EQ(third_call_context.responses().size(), 0u);
-  EXPECT_EQ(active_drain.Close(), OkStatus());
-}
-
-TEST_F(LogServiceTest, StartAndEndStream) {
-  RpcLogDrain& active_drain = drains_[2];
-  const uint32_t drain_channel_id = active_drain.channel_id();
-  LOG_SERVICE_METHOD_CONTEXT context(drain_map_);
-  context.set_channel_id(drain_channel_id);
-
-  // Add log entries.
-  const size_t total_entries = 10;
-  AddLogEntries(total_entries, kMessage, kSampleMetadata, kSampleTimestamp);
-
-  // Request logs.
-  context.call(rpc_request_buffer);
-  EXPECT_EQ(active_drain.Flush(encoding_buffer_), OkStatus());
-
-  // Not done until the stream is finished.
-  ASSERT_FALSE(context.done());
-  EXPECT_EQ(OkStatus(), active_drain.Close());
-  ASSERT_TRUE(context.done());
-
-  EXPECT_EQ(context.status(), OkStatus());
-  // There is at least 1 response with multiple log entries packed.
-  EXPECT_GE(context.responses().size(), 1u);
-
-  // Verify data in responses.
-  Vector<TestLogEntry, total_entries> expected_messages;
-  for (size_t i = 0; i < total_entries; ++i) {
-    expected_messages.push_back({.metadata = kSampleMetadata,
-                                 .timestamp = kSampleTimestamp,
-                                 .tokenized_data = std::as_bytes(
-                                     std::span(std::string_view(kMessage)))});
-  }
-  size_t entries_found = 0;
-  uint32_t drop_count_found = 0;
-  for (auto& response : context.responses()) {
-    protobuf::Decoder entry_decoder(response);
-    VerifyLogEntries(entry_decoder,
-                     expected_messages,
-                     entries_found,
-                     entries_found,
-                     drop_count_found);
-  }
-  EXPECT_EQ(entries_found, total_entries);
-  EXPECT_EQ(drop_count_found, 0u);
-}
-
-TEST_F(LogServiceTest, HandleDropped) {
-  RpcLogDrain& active_drain = drains_[0];
-  const uint32_t drain_channel_id = active_drain.channel_id();
-  LOG_SERVICE_METHOD_CONTEXT context(drain_map_);
-  context.set_channel_id(drain_channel_id);
-
-  // Add log entries.
-  const size_t total_entries = 5;
-  const size_t entries_before_drop = 1;
-  const uint32_t total_drop_count = 2;
-
-  // Force a drop entry in between entries.
-  AddLogEntries(
-      entries_before_drop, kMessage, kSampleMetadata, kSampleTimestamp);
-  multisink_.HandleDropped(total_drop_count);
-  AddLogEntries(total_entries - entries_before_drop,
-                kMessage,
-                kSampleMetadata,
-                kSampleTimestamp);
-
-  // Request logs.
-  context.call(rpc_request_buffer);
-  EXPECT_EQ(active_drain.Flush(encoding_buffer_), OkStatus());
-  EXPECT_EQ(OkStatus(), active_drain.Close());
-  ASSERT_EQ(context.status(), OkStatus());
-  // There is at least 1 response with multiple log entries packed.
-  ASSERT_GE(context.responses().size(), 1u);
-
-  Vector<TestLogEntry, total_entries + 1> expected_messages;
-  size_t i = 0;
-  for (; i < entries_before_drop; ++i) {
-    expected_messages.push_back({.metadata = kSampleMetadata,
-                                 .timestamp = kSampleTimestamp,
-                                 .tokenized_data = std::as_bytes(
-                                     std::span(std::string_view(kMessage)))});
-  }
-  expected_messages.push_back(
-      {.metadata = kDropMessageMetadata,
-       .dropped = total_drop_count,
-       .tokenized_data = std::as_bytes(
-           std::span(std::string_view(RpcLogDrain::kIngressErrorMessage)))});
-  for (; i < total_entries; ++i) {
-    expected_messages.push_back({.metadata = kSampleMetadata,
-                                 .timestamp = kSampleTimestamp,
-                                 .tokenized_data = std::as_bytes(
-                                     std::span(std::string_view(kMessage)))});
-  }
-
-  // Verify data in responses.
-  size_t entries_found = 0;
-  uint32_t drop_count_found = 0;
-  for (auto& response : context.responses()) {
-    protobuf::Decoder entry_decoder(response);
-    VerifyLogEntries(entry_decoder,
-                     expected_messages,
-                     entries_found,
-                     entries_found,
-                     drop_count_found);
-  }
-  EXPECT_EQ(entries_found, total_entries);
-  EXPECT_EQ(drop_count_found, total_drop_count);
-}
-
-TEST_F(LogServiceTest, HandleDroppedBetweenFilteredOutLogs) {
-  RpcLogDrain& active_drain = drains_[0];
-  const uint32_t drain_channel_id = active_drain.channel_id();
-  LOG_SERVICE_METHOD_CONTEXT context(drain_map_);
-  context.set_channel_id(drain_channel_id);
-  // Set filter to drop INFO+ and keep DEBUG logs
-  rules1_[0].action = Filter::Rule::Action::kDrop;
-  rules1_[0].level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL;
-
-  // Add log entries.
-  const size_t total_entries = 5;
-  const uint32_t total_drop_count = total_entries - 1;
-
-  // Force a drop entry in between entries that will be filtered out.
-  for (size_t i = 1; i < total_entries; ++i) {
-    ASSERT_EQ(
-        OkStatus(),
-        AddLogEntry(kMessage, kSampleMetadata, kSampleTimestamp).status());
-    multisink_.HandleDropped(1);
-  }
-  // Add message that won't be filtered out.
-  constexpr auto metadata =
-      log_tokenized::Metadata::Set<PW_LOG_LEVEL_DEBUG, 0, 0, __LINE__>();
-  ASSERT_EQ(OkStatus(),
-            AddLogEntry(kMessage, metadata, kSampleTimestamp).status());
-
-  // Request logs.
-  context.call(rpc_request_buffer);
-  EXPECT_EQ(active_drain.Flush(encoding_buffer_), OkStatus());
-  EXPECT_EQ(OkStatus(), active_drain.Close());
-  ASSERT_EQ(context.status(), OkStatus());
-  // There is at least 1 response with multiple log entries packed.
-  ASSERT_GE(context.responses().size(), 1u);
-
-  Vector<TestLogEntry, 2> expected_messages;
-  expected_messages.push_back(
-      {.metadata = kDropMessageMetadata,
-       .dropped = total_drop_count,
-       .tokenized_data = std::as_bytes(
-           std::span(std::string_view(RpcLogDrain::kIngressErrorMessage)))});
-  expected_messages.push_back(
-      {.metadata = metadata,
-       .timestamp = kSampleTimestamp,
-       .tokenized_data = std::as_bytes(std::span(std::string_view(kMessage)))});
-
-  // Verify data in responses.
-  size_t entries_found = 0;
-  uint32_t drop_count_found = 0;
-  for (auto& response : context.responses()) {
-    protobuf::Decoder entry_decoder(response);
-    VerifyLogEntries(entry_decoder,
-                     expected_messages,
-                     entries_found,
-                     entries_found,
-                     drop_count_found);
-  }
-  EXPECT_EQ(entries_found, 1u);
-  EXPECT_EQ(drop_count_found, total_drop_count);
-}
-
-TEST_F(LogServiceTest, HandleSmallLogEntryBuffer) {
-  LOG_SERVICE_METHOD_CONTEXT context(drain_map_);
-  context.set_channel_id(kSmallBufferDrainId);
-  auto small_buffer_drain =
-      drain_map_.GetDrainFromChannelId(kSmallBufferDrainId);
-  ASSERT_TRUE(small_buffer_drain.ok());
-
-  // Add long entries that don't fit the drain's log entry buffer, except for
-  // one, since drop count messages are only sent when a log entry can be sent.
-  const size_t total_entries = 5;
-  const uint32_t total_drop_count = total_entries - 1;
-  AddLogEntries(
-      total_drop_count, kLongMessage, kSampleMetadata, kSampleTimestamp);
-  EXPECT_EQ(OkStatus(),
-            AddLogEntry(kMessage, kSampleMetadata, kSampleTimestamp).status());
-
-  // Request logs.
-  context.call(rpc_request_buffer);
-  EXPECT_EQ(small_buffer_drain.value()->Flush(encoding_buffer_), OkStatus());
-  EXPECT_EQ(small_buffer_drain.value()->Close(), OkStatus());
-  ASSERT_EQ(context.status(), OkStatus());
-  ASSERT_EQ(context.responses().size(), 1u);
-
-  Vector<TestLogEntry, total_entries + 1> expected_messages;
-  expected_messages.push_back(
-      {.metadata = kDropMessageMetadata,
-       .dropped = total_drop_count,
-       .tokenized_data = std::as_bytes(std::span(
-           std::string_view(RpcLogDrain::kSmallStackBufferErrorMessage)))});
-  expected_messages.push_back(
-      {.metadata = kSampleMetadata,
-       .timestamp = kSampleTimestamp,
-       .tokenized_data = std::as_bytes(std::span(std::string_view(kMessage)))});
-
-  // Expect one drop message with the total drop count, and the only message
-  // that fits the buffer.
-  size_t entries_found = 0;
-  uint32_t drop_count_found = 0;
-  for (auto& response : context.responses()) {
-    protobuf::Decoder entry_decoder(response);
-    VerifyLogEntries(entry_decoder,
-                     expected_messages,
-                     entries_found,
-                     entries_found,
-                     drop_count_found);
-  }
-  EXPECT_EQ(entries_found, 1u);
-  EXPECT_EQ(drop_count_found, total_drop_count);
-}
-
-TEST_F(LogServiceTest, FlushDrainWithoutMultisink) {
-  auto& detached_drain = drains_[0];
-  multisink_.DetachDrain(detached_drain);
-  LOG_SERVICE_METHOD_CONTEXT context(drain_map_);
-  context.set_channel_id(detached_drain.channel_id());
-
-  // Add log entries.
-  const size_t total_entries = 5;
-  AddLogEntries(total_entries, kMessage, kSampleMetadata, kSampleTimestamp);
-  // Request logs.
-  context.call(rpc_request_buffer);
-  EXPECT_EQ(detached_drain.Close(), OkStatus());
-  ASSERT_EQ(context.status(), OkStatus());
-  EXPECT_EQ(context.responses().size(), 0u);
-}
-
-TEST_F(LogServiceTest, LargeLogEntry) {
-  const TestLogEntry expected_entry{
-      .metadata =
-          log_tokenized::Metadata::Set<PW_LOG_LEVEL_WARN,
-                                       (1 << PW_LOG_TOKENIZED_MODULE_BITS) - 1,
-                                       (1 << PW_LOG_TOKENIZED_FLAG_BITS) - 1,
-                                       (1 << PW_LOG_TOKENIZED_LINE_BITS) - 1>(),
-      .timestamp = std::numeric_limits<int64_t>::max(),
-      .tokenized_data = std::as_bytes(std::span(kMessage)),
-  };
-
-  // Add entry to multisink.
-  log::LogEntry::MemoryEncoder encoder(entry_encode_buffer_);
-  ASSERT_EQ(encoder.WriteMessage(expected_entry.tokenized_data), OkStatus());
-  ASSERT_EQ(encoder.WriteLineLevel(
-                (expected_entry.metadata.level() & PW_LOG_LEVEL_BITMASK) |
-                ((expected_entry.metadata.line_number() << PW_LOG_LEVEL_BITS) &
-                 ~PW_LOG_LEVEL_BITMASK)),
-            OkStatus());
-  ASSERT_EQ(encoder.WriteFlags(expected_entry.metadata.flags()), OkStatus());
-  ASSERT_EQ(encoder.WriteTimestamp(expected_entry.timestamp), OkStatus());
-  const uint32_t little_endian_module = bytes::ConvertOrderTo(
-      std::endian::little, expected_entry.metadata.module());
-  ASSERT_EQ(
-      encoder.WriteModule(std::as_bytes(std::span(&little_endian_module, 1))),
-      OkStatus());
-  ASSERT_EQ(encoder.status(), OkStatus());
-  multisink_.HandleEntry(encoder);
-
-  // Start log stream.
-  RpcLogDrain& active_drain = drains_[0];
-  const uint32_t drain_channel_id = active_drain.channel_id();
-  LOG_SERVICE_METHOD_CONTEXT context(drain_map_);
-  context.set_channel_id(drain_channel_id);
-  context.call(rpc_request_buffer);
-  ASSERT_EQ(active_drain.Flush(encoding_buffer_), OkStatus());
-  EXPECT_EQ(OkStatus(), active_drain.Close());
-  ASSERT_EQ(context.status(), OkStatus());
-  ASSERT_EQ(context.responses().size(), 1u);
-
-  // Verify message.
-  protobuf::Decoder entries_decoder(context.responses()[0]);
-  ASSERT_TRUE(entries_decoder.Next().ok());
-  ConstByteSpan entry;
-  EXPECT_TRUE(entries_decoder.ReadBytes(&entry).ok());
-  protobuf::Decoder entry_decoder(entry);
-  uint32_t drop_count = 0;
-  VerifyLogEntry(entry_decoder, expected_entry, drop_count);
-  EXPECT_EQ(drop_count, 0u);
-}
-
-TEST_F(LogServiceTest, InterruptedLogStreamSendsDropCount) {
-  const uint32_t drain_channel_id = kCloseWriterOnErrorDrainId;
-  auto drain = drain_map_.GetDrainFromChannelId(drain_channel_id);
-  ASSERT_TRUE(drain.ok());
-
-  LogService log_service(drain_map_);
-  const size_t max_packets = 10;
-  rpc::RawFakeChannelOutput<10, 512> output;
-  rpc::Channel channel(rpc::Channel::Create<drain_channel_id>(&output));
-  rpc::Server server(std::span(&channel, 1));
-
-  // Add as many entries needed to have multiple packets send.
-  StatusWithSize status =
-      AddLogEntry(kMessage, kSampleMetadata, kSampleTimestamp);
-  ASSERT_TRUE(status.ok());
-
-  const uint32_t max_messages_per_response =
-      encoding_buffer_.size() / status.size();
-  // Send less packets than the max to avoid crashes.
-  const uint32_t packets_sent = max_packets / 2;
-  const size_t total_entries = packets_sent * max_messages_per_response;
-  const size_t max_entries = 50;
-  // Check we can test all these entries.
-  ASSERT_GE(max_entries, total_entries);
-  AddLogEntries(total_entries - 1, kMessage, kSampleMetadata, kSampleTimestamp);
-
-  // Interrupt log stream with an error.
-  const uint32_t successful_packets_sent = packets_sent / 2;
-  output.set_send_status(Status::Unavailable(), successful_packets_sent);
-
-  // Request logs.
-  rpc::RawServerWriter writer = rpc::RawServerWriter::Open<Logs::Listen>(
-      server, drain_channel_id, log_service);
-  EXPECT_EQ(drain.value()->Open(writer), OkStatus());
-  // This drain closes on errors.
-  EXPECT_EQ(drain.value()->Flush(encoding_buffer_), Status::Aborted());
-  EXPECT_TRUE(output.done());
-
-  // Make sure not all packets were sent.
-  ASSERT_EQ(output.payloads<Logs::Listen>().size(), successful_packets_sent);
-
-  // Verify data in responses.
-  Vector<TestLogEntry, max_entries> expected_messages;
-  for (size_t i = 0; i < total_entries; ++i) {
-    expected_messages.push_back({.metadata = kSampleMetadata,
-                                 .timestamp = kSampleTimestamp,
-                                 .tokenized_data = std::as_bytes(
-                                     std::span(std::string_view(kMessage)))});
-  }
-  size_t entries_found = 0;
-  uint32_t drop_count_found = 0;
-  for (auto& response : output.payloads<Logs::Listen>()) {
-    protobuf::Decoder entry_decoder(response);
-    VerifyLogEntries(entry_decoder,
-                     expected_messages,
-                     entries_found,
-                     entries_found,
-                     drop_count_found);
-  }
-
-  // Verify that not all the entries were sent.
-  EXPECT_LT(entries_found, total_entries);
-  // The drain closes on errors, thus the drop count is reported on the next
-  // call to Flush.
-  EXPECT_EQ(drop_count_found, 0u);
-
-  // Reset channel output and resume log stream with a new writer.
-  output.clear();
-  writer = rpc::RawServerWriter::Open<Logs::Listen>(
-      server, drain_channel_id, log_service);
-  EXPECT_EQ(drain.value()->Open(writer), OkStatus());
-  EXPECT_EQ(drain.value()->Flush(encoding_buffer_), OkStatus());
-
-  // One full packet was dropped. Since all messages are the same length,
-  // there are entries_found / successful_packets_sent per packet.
-  const uint32_t total_drop_count = entries_found / successful_packets_sent;
-  Vector<TestLogEntry, max_entries> expected_messages_after_reset;
-  expected_messages_after_reset.push_back(
-      {.metadata = kDropMessageMetadata,
-       .dropped = total_drop_count,
-       .tokenized_data = std::as_bytes(
-           std::span(std::string_view(RpcLogDrain::kWriterErrorMessage)))});
-
-  const uint32_t remaining_entries = total_entries - total_drop_count;
-  for (size_t i = 0; i < remaining_entries; ++i) {
-    expected_messages_after_reset.push_back(
-        {.metadata = kSampleMetadata,
-         .timestamp = kSampleTimestamp,
-         .tokenized_data =
-             std::as_bytes(std::span(std::string_view(kMessage)))});
-  }
-
-  size_t entries_found_after_reset = 0;
-  for (auto& response : output.payloads<Logs::Listen>()) {
-    protobuf::Decoder entry_decoder(response);
-    uint32_t expected_sequence_id =
-        entries_found + entries_found_after_reset + total_drop_count;
-    VerifyLogEntries(entry_decoder,
-                     expected_messages_after_reset,
-                     expected_sequence_id,
-                     entries_found_after_reset,
-                     drop_count_found);
-  }
-  EXPECT_EQ(entries_found + entries_found_after_reset, remaining_entries);
-  EXPECT_EQ(drop_count_found, total_drop_count);
-}
-
-TEST_F(LogServiceTest, InterruptedLogStreamIgnoresErrors) {
-  const uint32_t drain_channel_id = kIgnoreWriterErrorsDrainId;
-  auto drain = drain_map_.GetDrainFromChannelId(drain_channel_id);
-  ASSERT_TRUE(drain.ok());
-
-  LogService log_service(drain_map_);
-  const size_t max_packets = 20;
-  rpc::RawFakeChannelOutput<max_packets, 512> output;
-  rpc::Channel channel(rpc::Channel::Create<drain_channel_id>(&output));
-  rpc::Server server(std::span(&channel, 1));
-
-  // Add as many entries needed to have multiple packets send.
-  StatusWithSize status =
-      AddLogEntry(kMessage, kSampleMetadata, kSampleTimestamp);
-  ASSERT_TRUE(status.ok());
-
-  const uint32_t max_messages_per_response =
-      encoding_buffer_.size() / status.size();
-  // Send less packets than the max to avoid crashes.
-  const uint32_t packets_sent = 4;
-  const size_t total_entries = packets_sent * max_messages_per_response;
-  const size_t max_entries = 50;
-  // Check we can test all these entries.
-  ASSERT_GT(max_entries, total_entries);
-  AddLogEntries(total_entries - 1, kMessage, kSampleMetadata, kSampleTimestamp);
-
-  // Interrupt log stream with an error.
-  const uint32_t error_on_packet_count = packets_sent / 2;
-  output.set_send_status(Status::Unavailable(), error_on_packet_count);
-
-  // Request logs.
-  rpc::RawServerWriter writer = rpc::RawServerWriter::Open<Logs::Listen>(
-      server, drain_channel_id, log_service);
-  EXPECT_EQ(drain.value()->Open(writer), OkStatus());
-  // This drain ignores errors.
-  EXPECT_EQ(drain.value()->Flush(encoding_buffer_), OkStatus());
-  EXPECT_FALSE(output.done());
-
-  // Make sure some packets were sent.
-  ASSERT_GT(output.payloads<Logs::Listen>().size(), 0u);
-
-  // Verify that not all the entries were sent.
-  size_t entries_found = 0;
-  for (auto& response : output.payloads<Logs::Listen>()) {
-    protobuf::Decoder entry_decoder(response);
-    entries_found += CountLogEntries(entry_decoder);
-  }
-  ASSERT_LT(entries_found, total_entries);
-
-  // Verify that all messages were sent.
-  const uint32_t total_drop_count = total_entries - entries_found;
-  Vector<TestLogEntry, max_entries> expected_messages;
-  for (size_t i = 0; i < entries_found; ++i) {
-    expected_messages.push_back({.metadata = kSampleMetadata,
-                                 .timestamp = kSampleTimestamp,
-                                 .tokenized_data = std::as_bytes(
-                                     std::span(std::string_view(kMessage)))});
-  }
-
-  entries_found = 0;
-  uint32_t drop_count_found = 0;
-  uint32_t i = 0;
-  for (; i < error_on_packet_count; ++i) {
-    protobuf::Decoder entry_decoder(output.payloads<Logs::Listen>()[i]);
-    VerifyLogEntries(entry_decoder,
-                     expected_messages,
-                     entries_found,
-                     entries_found,
-                     drop_count_found);
-  }
-  for (; i < output.payloads<Logs::Listen>().size(); ++i) {
-    protobuf::Decoder entry_decoder(output.payloads<Logs::Listen>()[i]);
-    VerifyLogEntries(entry_decoder,
-                     expected_messages,
-                     entries_found + total_drop_count,
-                     entries_found,
-                     drop_count_found);
-  }
-  // This drain ignores errors and thus doesn't report drops on its own.
-  EXPECT_EQ(drop_count_found, 0u);
-
-  // More calls to flush with errors will not affect this stubborn drain.
-  const size_t previous_stream_packet_count =
-      output.payloads<Logs::Listen>().size();
-  output.set_send_status(Status::Unavailable());
-  EXPECT_EQ(drain.value()->Flush(encoding_buffer_), OkStatus());
-  EXPECT_FALSE(output.done());
-  ASSERT_EQ(output.payloads<Logs::Listen>().size(),
-            previous_stream_packet_count);
-
-  output.clear();
-  EXPECT_EQ(drain.value()->Close(), OkStatus());
-  EXPECT_TRUE(output.done());
-}
-
-TEST_F(LogServiceTest, FilterLogs) {
-  // Add a variety of logs.
-  const uint32_t module = 0xcafe;
-  const uint32_t flags = 0x02;
-  const uint32_t line_number = 100;
-  const auto debug_metadata = log_tokenized::Metadata::
-      Set<PW_LOG_LEVEL_DEBUG, module, flags, line_number>();
-  ASSERT_TRUE(AddLogEntry(kMessage, debug_metadata, kSampleTimestamp).ok());
-  const auto info_metadata = log_tokenized::Metadata::
-      Set<PW_LOG_LEVEL_INFO, module, flags, line_number>();
-  ASSERT_TRUE(AddLogEntry(kMessage, info_metadata, kSampleTimestamp).ok());
-  const auto warn_metadata = log_tokenized::Metadata::
-      Set<PW_LOG_LEVEL_WARN, module, flags, line_number>();
-  ASSERT_TRUE(AddLogEntry(kMessage, warn_metadata, kSampleTimestamp).ok());
-  const auto error_metadata = log_tokenized::Metadata::
-      Set<PW_LOG_LEVEL_ERROR, module, flags, line_number>();
-  ASSERT_TRUE(AddLogEntry(kMessage, error_metadata, kSampleTimestamp).ok());
-  const auto different_flags_metadata = log_tokenized::Metadata::
-      Set<PW_LOG_LEVEL_ERROR, module, 0x01, line_number>();
-  ASSERT_TRUE(
-      AddLogEntry(kMessage, different_flags_metadata, kSampleTimestamp).ok());
-  const auto different_module_metadata = log_tokenized::Metadata::
-      Set<PW_LOG_LEVEL_ERROR, 0xabcd, flags, line_number>();
-  ASSERT_TRUE(
-      AddLogEntry(kMessage, different_module_metadata, kSampleTimestamp).ok());
-
-  Vector<TestLogEntry, 3> expected_messages{
-      {.metadata = info_metadata,
-       .timestamp = kSampleTimestamp,
-       .tokenized_data = std::as_bytes(std::span(std::string_view(kMessage)))},
-      {.metadata = warn_metadata,
-       .timestamp = kSampleTimestamp,
-       .tokenized_data = std::as_bytes(std::span(std::string_view(kMessage)))},
-      {.metadata = error_metadata,
-       .timestamp = kSampleTimestamp,
-       .tokenized_data = std::as_bytes(std::span(std::string_view(kMessage)))},
-  };
-
-  // Set up filter rules for drain at drains_[1].
-  RpcLogDrain& drain = drains_[1];
-  for (auto& rule : rules2_) {
-    rule = {};
-  }
-  const auto module_little_endian =
-      bytes::CopyInOrder<uint32_t>(std::endian::little, module);
-  rules2_[0] = {
-      .action = Filter::Rule::Action::kKeep,
-      .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
-      .any_flags_set = flags,
-      .module_equals{module_little_endian.begin(), module_little_endian.end()}};
-  rules2_[1] = {
-      .action = Filter::Rule::Action::kDrop,
-      .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
-      .any_flags_set = 0,
-      .module_equals{},
-  };
-
-  // Request logs.
-  LOG_SERVICE_METHOD_CONTEXT context(drain_map_);
-  context.set_channel_id(drain.channel_id());
-  context.call({});
-  ASSERT_EQ(drain.Flush(encoding_buffer_), OkStatus());
-
-  size_t entries_found = 0;
-  uint32_t drop_count_found = 0;
-  for (auto& response : context.responses()) {
-    protobuf::Decoder entry_decoder(response);
-    VerifyLogEntries(entry_decoder,
-                     expected_messages,
-                     entries_found,
-                     entries_found,
-                     drop_count_found);
-  }
-  EXPECT_EQ(entries_found, 3u);
-  EXPECT_EQ(drop_count_found, 0u);
-}
-
-TEST_F(LogServiceTest, ReopenClosedLogStreamWithAcquiredBuffer) {
-  const uint32_t drain_channel_id = kCloseWriterOnErrorDrainId;
-  auto drain = drain_map_.GetDrainFromChannelId(drain_channel_id);
-  ASSERT_TRUE(drain.ok());
-
-  LogService log_service(drain_map_);
-  rpc::RawFakeChannelOutput<10, 512> output;
-  rpc::Channel channel(rpc::Channel::Create<drain_channel_id>(&output));
-  rpc::Server server(std::span(&channel, 1));
-
-  // Request logs.
-  rpc::RawServerWriter writer = rpc::RawServerWriter::Open<Logs::Listen>(
-      server, drain_channel_id, log_service);
-  EXPECT_EQ(drain.value()->Open(writer), OkStatus());
-  // This drain closes on errors.
-  EXPECT_EQ(drain.value()->Flush(encoding_buffer_), OkStatus());
-
-  // Request log stream with a new writer.
-  writer = rpc::RawServerWriter::Open<Logs::Listen>(
-      server, drain_channel_id, log_service);
-  EXPECT_EQ(drain.value()->Open(writer), OkStatus());
-  EXPECT_EQ(drain.value()->Flush(encoding_buffer_), OkStatus());
-}
-
-}  // namespace
-}  // namespace pw::log_rpc
diff --git a/pw_log_rpc/logs_rpc.cc b/pw_log_rpc/logs_rpc.cc
new file mode 100644
index 0000000..c790b22
--- /dev/null
+++ b/pw_log_rpc/logs_rpc.cc
@@ -0,0 +1,75 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_log_rpc/logs_rpc.h"
+
+#include "pw_log/log.h"
+#include "pw_log_proto/log.pwpb.h"
+#include "pw_status/try.h"
+
+namespace pw::log_rpc {
+namespace {
+
+Result<ConstByteSpan> GenerateDroppedEntryMessage(ByteSpan encode_buffer,
+                                                  size_t dropped_entries) {
+  pw::protobuf::NestedEncoder nested_encoder(encode_buffer);
+  pw::log::LogEntry::Encoder encoder(&nested_encoder);
+  encoder.WriteDropped(dropped_entries);
+  return nested_encoder.Encode();
+}
+
+}  // namespace
+
+void Logs::Get(ServerContext&, ConstByteSpan, rpc::RawServerWriter& writer) {
+  response_writer_ = std::move(writer);
+}
+
+Status Logs::Flush() {
+  // If the response writer was not initialized or has since been closed,
+  // ignore the flush operation.
+  if (!response_writer_.open()) {
+    return OkStatus();
+  }
+
+  // If previous calls to flush resulted in dropped entries, generate a
+  // dropped entry message and write it before further log messages.
+  if (dropped_entries_ > 0) {
+    ByteSpan payload = response_writer_.PayloadBuffer();
+    Result dropped_log = GenerateDroppedEntryMessage(payload, dropped_entries_);
+    PW_TRY(dropped_log.status());
+    PW_TRY(response_writer_.Write(dropped_log.value()));
+    dropped_entries_ = 0;
+  }
+
+  // Write logs to the response writer. An important limitation of this
+  // implementation is that if this RPC call fails, the logs are lost -
+  // a subsequent call to the RPC will produce a drop count message.
+  ByteSpan payload = response_writer_.PayloadBuffer();
+  Result possible_logs = log_queue_.PopMultiple(payload);
+  PW_TRY(possible_logs.status());
+  if (possible_logs.value().entry_count == 0) {
+    return OkStatus();
+  }
+
+  Status status = response_writer_.Write(possible_logs.value().entries);
+  if (!status.ok()) {
+    // On a failure to send logs, track the dropped entries.
+    dropped_entries_ = possible_logs.value().entry_count;
+    return status;
+  }
+
+  return OkStatus();
+}
+
+}  // namespace pw::log_rpc
diff --git a/pw_log_rpc/logs_rpc_test.cc b/pw_log_rpc/logs_rpc_test.cc
new file mode 100644
index 0000000..88a3f50
--- /dev/null
+++ b/pw_log_rpc/logs_rpc_test.cc
@@ -0,0 +1,132 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_log_rpc/logs_rpc.h"
+
+#include "gtest/gtest.h"
+#include "pw_log/log.h"
+#include "pw_rpc/raw_test_method_context.h"
+
+namespace pw::log_rpc {
+namespace {
+
+#define LOGS_METHOD_CONTEXT PW_RAW_TEST_METHOD_CONTEXT(Logs, Get)
+
+constexpr size_t kEncodeBufferSize = 128;
+constexpr size_t kLogBufferSize = 4096;
+
+class LogQueueTester : public LogQueueWithEncodeBuffer<kLogBufferSize> {
+ public:
+  LogQueueTester(ByteSpan log_queue)
+      : LogQueueWithEncodeBuffer<kLogBufferSize>(log_queue) {}
+
+  void SetPopStatus(Status error_status) {
+    pop_status_for_test_ = error_status;
+  }
+};
+
+class LogsService : public ::testing::Test {
+ public:
+  LogsService() : log_queue_(log_queue_buffer_) {}
+
+ protected:
+  void AddLogs(const size_t log_count = 1) {
+    constexpr char kTokenizedMessage[] = "message";
+    for (size_t i = 0; i < log_count; i++) {
+      EXPECT_EQ(
+          OkStatus(),
+          log_queue_.PushTokenizedMessage(
+              std::as_bytes(std::span(kTokenizedMessage)), 0, 0, 0, 0, 0));
+    }
+  }
+
+  static Logs& GetLogs(LOGS_METHOD_CONTEXT& context) {
+    return (Logs&)(context.service());
+  }
+
+  std::array<std::byte, kEncodeBufferSize> log_queue_buffer_;
+  LogQueueWithEncodeBuffer<kLogBufferSize> log_queue_;
+};
+
+TEST_F(LogsService, Get) {
+  constexpr size_t kLogEntryCount = 3;
+  std::array<std::byte, 1> rpc_buffer;
+  LOGS_METHOD_CONTEXT context(log_queue_);
+
+  context.call(rpc_buffer);
+
+  // Flush all logs from the buffer, then close the RPC.
+  AddLogs(kLogEntryCount);
+  GetLogs(context).Flush();
+  GetLogs(context).Finish();
+
+  EXPECT_TRUE(context.done());
+  EXPECT_EQ(OkStatus(), context.status());
+
+  // Although |kLogEntryCount| messages were in the queue, they are batched
+  // before being written to the client, so there is only one response.
+  EXPECT_EQ(1U, context.total_responses());
+}
+
+TEST_F(LogsService, GetMultiple) {
+  constexpr size_t kLogEntryCount = 1;
+  constexpr size_t kFlushCount = 3;
+  std::array<std::byte, 1> rpc_buffer;
+  LOGS_METHOD_CONTEXT context(log_queue_);
+
+  context.call(rpc_buffer);
+
+  for (size_t i = 0; i < kFlushCount; i++) {
+    AddLogs(kLogEntryCount);
+    GetLogs(context).Flush();
+  }
+  GetLogs(context).Finish();
+
+  EXPECT_TRUE(context.done());
+  EXPECT_EQ(OkStatus(), context.status());
+  EXPECT_EQ(kFlushCount, context.total_responses());
+}
+
+TEST_F(LogsService, NoEntriesOnEmptyQueue) {
+  std::array<std::byte, 1> rpc_buffer;
+  LOGS_METHOD_CONTEXT context(log_queue_);
+
+  // Invoking flush with no logs in the queue should behave like a no-op.
+  context.call(rpc_buffer);
+  GetLogs(context).Flush();
+  GetLogs(context).Finish();
+
+  EXPECT_TRUE(context.done());
+  EXPECT_EQ(OkStatus(), context.status());
+  EXPECT_EQ(0U, context.total_responses());
+}
+
+TEST_F(LogsService, QueueError) {
+  std::array<std::byte, 1> rpc_buffer;
+  LogQueueTester log_queue_tester(log_queue_buffer_);
+  LOGS_METHOD_CONTEXT context(log_queue_tester);
+
+  // Generate failure on log queue.
+  log_queue_tester.SetPopStatus(Status::Internal());
+  context.call(rpc_buffer);
+  GetLogs(context).Flush();
+  GetLogs(context).Finish();
+
+  EXPECT_TRUE(context.done());
+  EXPECT_EQ(OkStatus(), context.status());
+  EXPECT_EQ(0U, context.total_responses());
+}
+
+}  // namespace
+}  // namespace pw::log_rpc
diff --git a/pw_log_rpc/public/pw_log_rpc/internal/config.h b/pw_log_rpc/public/pw_log_rpc/internal/config.h
deleted file mode 100644
index 9fbcfbf..0000000
--- a/pw_log_rpc/public/pw_log_rpc/internal/config.h
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// Configuration macros for the pw_rpc module.
-#pragma once
-
-#include <cstddef>
-
-// Log filter modules are optionally tokenized, and thus their backing on-device
-// container can have different sizes. A token may be represented by a 32-bit
-// integer (though it is usually 2 bytes). Default the max module name size to
-// 4 bytes.
-#ifndef PW_LOG_RPC_CONFIG_MAX_FILTER_RULE_MODULE_NAME_SIZE
-#define PW_LOG_RPC_CONFIG_MAX_FILTER_RULE_MODULE_NAME_SIZE 4
-#endif  // PW_LOG_RPC_CONFIG_MAX_FILTER_RULE_MODULE_NAME_SIZE
-
-// Log filter IDs are optionally tokenized, and thus their backing on-device
-// container can have different sizes. A token may be represented by a 32-bit
-// integer (though it is usually 2 bytes). Default the max module name size to
-// 4 bytes.
-#ifndef PW_LOG_RPC_CONFIG_MAX_FILTER_ID_SIZE
-#define PW_LOG_RPC_CONFIG_MAX_FILTER_ID_SIZE 4
-#endif  // PW_LOG_RPC_CONFIG_MAX_FILTER_ID_SIZE
-
-// The log level to use for this module. Logs below this level are omitted.
-#ifndef PW_LOG_RPC_CONFIG_LOG_LEVEL
-#define PW_LOG_RPC_CONFIG_LOG_LEVEL PW_LOG_LEVEL_INFO
-#endif  // PW_LOG_RPC_CONFIG_LOG_LEVEL
-
-// The log module name to use for this module.
-#ifndef PW_LOG_RPC_CONFIG_LOG_MODULE_NAME
-#define PW_LOG_RPC_CONFIG_LOG_MODULE_NAME "PW_LOG_RPC"
-#endif  // PW_LOG_RPC_CONFIG_LOG_MODULE_NAME
-
-// Messages to descrive the log drop reasons.
-// See https://pigweed.dev/pw_log_rpc/#log-drops
-//
-// Message for when an entry could not be added to the MultiSink.
-#ifndef PW_LOG_RPC_INGRESS_ERROR_MSG
-#define PW_LOG_RPC_INGRESS_ERROR_MSG "Ingress error"
-#endif  // PW_LOG_RPC_INGRESS_ERROR_MSG
-
-// Message for when a drain drains too slow and has to be advanced, dropping
-// logs.
-#ifndef PW_LOG_RPC_SLOW_DRAIN_MSG
-#define PW_LOG_RPC_SLOW_DRAIN_MSG "Slow drain"
-#endif  // PW_LOG_RPC_SLOW_DRAIN_MSG
-
-// Message for when a is too too large to fit in the outbound buffer, so it is
-// dropped.
-#ifndef PW_LOG_RPC_SMALL_OUTBOUND_BUFFER_MSG
-#define PW_LOG_RPC_SMALL_OUTBOUND_BUFFER_MSG "Outbound log buffer too small"
-#endif  // PW_LOG_RPC_SMALL_OUTBOUND_BUFFER_MSG
-
-// Message for when the log entry in the MultiSink is too large to be peeked or
-// popped out, so it is dropped.
-#ifndef PW_LOG_RPC_SMALL_STACK_BUFFER_MSG
-#define PW_LOG_RPC_SMALL_STACK_BUFFER_MSG "Stack log buffer too small"
-#endif  // PW_LOG_RPC_SMALL_STACK_BUFFER_MSG
-
-// Message for when a bulk of logs cannot be sent due to a writer error.
-#ifndef PW_LOG_RPC_WRITER_ERROR_MSG
-#define PW_LOG_RPC_WRITER_ERROR_MSG "Writer error"
-#endif  // PW_LOG_RPC_WRITER_ERROR_MSG
-
-namespace pw::log_rpc::cfg {
-inline constexpr size_t kMaxModuleNameBytes =
-    PW_LOG_RPC_CONFIG_MAX_FILTER_RULE_MODULE_NAME_SIZE;
-
-inline constexpr size_t kMaxFilterIdBytes =
-    PW_LOG_RPC_CONFIG_MAX_FILTER_ID_SIZE;
-}  // namespace pw::log_rpc::cfg
diff --git a/pw_log_rpc/public/pw_log_rpc/internal/log_config.h b/pw_log_rpc/public/pw_log_rpc/internal/log_config.h
deleted file mode 100644
index fff1e3f..0000000
--- a/pw_log_rpc/public/pw_log_rpc/internal/log_config.h
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// Configuration macros for the pw_rpc module.
-#pragma once
-
-#include "pw_log_rpc/internal/config.h"
-
-#define PW_LOG_LEVEL PW_LOG_RPC_CONFIG_LOG_LEVEL
-#define PW_LOG_MODULE_NAME PW_LOG_RPC_CONFIG_LOG_MODULE_NAME
diff --git a/pw_log_rpc/public/pw_log_rpc/log_filter.h b/pw_log_rpc/public/pw_log_rpc/log_filter.h
deleted file mode 100644
index 56e01f7..0000000
--- a/pw_log_rpc/public/pw_log_rpc/log_filter.h
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstddef>
-#include <cstring>
-#include <span>
-
-#include "pw_assert/assert.h"
-#include "pw_bytes/span.h"
-#include "pw_containers/vector.h"
-#include "pw_log/proto/log.pwpb.h"
-#include "pw_log_rpc/internal/config.h"
-#include "pw_status/status.h"
-
-namespace pw::log_rpc {
-
-// A Filter is a collection of rules used to check if a log entry can be kept
-// or dropped wherever the filter is placed in the log path.
-class Filter {
- public:
-  struct Rule {
-    // Action to perform if the rule is met.
-    enum class Action {
-      kInactive = 0,  // Ignore this rule.
-      kKeep = 1,
-      kDrop = 2,
-    };
-    Action action = Action::kInactive;
-
-    // Checks if the log level is greater or equal to this value when it
-    // does not equal NOT_SET.
-    log::FilterRule::Level level_greater_than_or_equal =
-        log::FilterRule::Level::ANY_LEVEL;
-
-    // Checks if the log entry has any flag is set when it doesn't equal 0.
-    uint32_t any_flags_set = 0;
-
-    // Checks if the log entry module equals this value when not empty.
-    Vector<std::byte, cfg::kMaxModuleNameBytes> module_equals{};
-  };
-
-  Filter(std::span<const std::byte> id, std::span<Rule> rules) : rules_(rules) {
-    PW_ASSERT(!id.empty());
-    id_.assign(id.begin(), id.end());
-  }
-
-  // Not copyable.
-  Filter(const Filter&) = delete;
-  Filter& operator=(const Filter&) = delete;
-
-  ConstByteSpan id() const { return ConstByteSpan(id_.data(), id_.size()); }
-  std::span<const Rule> rules() const { return rules_; }
-
-  // Verifies a log entry against the filter's rules in the order they were
-  // provided, stopping at the first rule that matches.
-  // Returns true when the log should be dropped, false otherwise. Defaults to
-  // false if there are no rules, or no rules were matched.
-  bool ShouldDropLog(ConstByteSpan entry) const;
-
-  // Decodes and updates the filter's rules given a buffer with a proto-encoded
-  // log::Filter message. If there are more rules than this filter can hold, the
-  // extra rules are discarded.
-  //
-  // Return values:
-  // OK - rules were updated successfully.
-  // FAILED_PRECONDITION - the provided buffer is empty.
-  // Forwarded errors from protobuff::decoder.
-  Status UpdateRulesFromProto(ConstByteSpan buffer);
-
- private:
-  Vector<std::byte, cfg::kMaxFilterIdBytes> id_;
-  std::span<Rule> rules_;
-};
-
-}  // namespace pw::log_rpc
diff --git a/pw_log_rpc/public/pw_log_rpc/log_filter_map.h b/pw_log_rpc/public/pw_log_rpc/log_filter_map.h
deleted file mode 100644
index 5a1be6e..0000000
--- a/pw_log_rpc/public/pw_log_rpc/log_filter_map.h
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <span>
-
-#include "pw_bytes/span.h"
-#include "pw_log_rpc/log_filter.h"
-#include "pw_result/result.h"
-
-namespace pw::log_rpc {
-
-// Holds an inmutable Filter map, ordered by filter ID to facilitate set up.
-class FilterMap {
- public:
-  explicit constexpr FilterMap(std::span<Filter> filters) : filters_(filters) {}
-
-  // Not copyable nor movable.
-  FilterMap(FilterMap const&) = delete;
-  FilterMap& operator=(FilterMap const&) = delete;
-  FilterMap(FilterMap&&) = delete;
-  FilterMap& operator=(FilterMap&&) = delete;
-
-  Result<Filter*> GetFilterFromId(ConstByteSpan id) const {
-    for (auto& filter : filters_) {
-      if (id.size() == filter.id().size() &&
-          std::memcmp(id.data(), filter.id().data(), id.size()) == 0) {
-        return &filter;
-      }
-    }
-    return Status::NotFound();
-  }
-
-  const std::span<Filter>& filters() const { return filters_; }
-
- protected:
-  const std::span<Filter> filters_;
-};
-
-}  // namespace pw::log_rpc
diff --git a/pw_log_rpc/public/pw_log_rpc/log_filter_service.h b/pw_log_rpc/public/pw_log_rpc/log_filter_service.h
deleted file mode 100644
index bb074af..0000000
--- a/pw_log_rpc/public/pw_log_rpc/log_filter_service.h
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_log/proto/log.raw_rpc.pb.h"
-#include "pw_log_rpc/log_filter_map.h"
-#include "pw_status/status_with_size.h"
-
-namespace pw::log_rpc {
-
-// Provides a way to retrieve and modify log filters.
-class FilterService final
-    : public log::pw_rpc::raw::Filters::Service<FilterService> {
- public:
-  FilterService(FilterMap& filter_map) : filter_map_(filter_map) {}
-
-  //  Modifies a log filter and its rules. The filter must be registered in the
-  //  provided filter map.
-  void SetFilter(ConstByteSpan request, rpc::RawUnaryResponder& responder) {
-    responder.Finish({}, SetFilterImpl(request)).IgnoreError();
-  }
-
-  // Retrieves a log filter and its rules. The filter must be registered in the
-  // provided filter map.
-  void GetFilter(ConstByteSpan request, rpc::RawUnaryResponder& responder) {
-    std::byte buffer[kFilterResponseBufferSize] = {};
-    StatusWithSize result = GetFilterImpl(request, buffer);
-    responder.Finish(std::span(buffer).first(result.size()), result.status())
-        .IgnoreError();
-  }
-
-  void ListFilterIds(ConstByteSpan, rpc::RawUnaryResponder& responder) {
-    std::byte buffer[kFilterIdsResponseBufferSize] = {};
-    StatusWithSize result = ListFilterIdsImpl(buffer);
-    responder.Finish(std::span(buffer).first(result.size()), result.status())
-        .IgnoreError();
-  }
-
- private:
-  static constexpr size_t kMinSupportedFilters = 4;
-
-  static constexpr size_t kFilterResponseBufferSize =
-      protobuf::FieldNumberSizeBytes(log::Filter::Fields::RULE) +
-      protobuf::kMaxSizeOfLength +
-      kMinSupportedFilters *
-          (protobuf::SizeOfFieldEnum(
-               log::FilterRule::Fields::LEVEL_GREATER_THAN_OR_EQUAL, 7) +
-           protobuf::SizeOfFieldBytes(log::FilterRule::Fields::MODULE_EQUALS,
-                                      6) +
-           protobuf::SizeOfFieldUint32(log::FilterRule::Fields::ANY_FLAGS_SET,
-                                       1) +
-           protobuf::SizeOfFieldEnum(log::FilterRule::Fields::ACTION, 2));
-
-  static constexpr size_t kFilterIdsResponseBufferSize =
-      kMinSupportedFilters *
-      protobuf::SizeOfFieldBytes(log::FilterIdListResponse::Fields::FILTER_ID,
-                                 4);
-
-  Status SetFilterImpl(ConstByteSpan request);
-  StatusWithSize GetFilterImpl(ConstByteSpan request, ByteSpan response);
-  StatusWithSize ListFilterIdsImpl(ByteSpan response);
-
-  FilterMap& filter_map_;
-};
-
-}  // namespace pw::log_rpc
diff --git a/pw_log_rpc/public/pw_log_rpc/log_service.h b/pw_log_rpc/public/pw_log_rpc/log_service.h
deleted file mode 100644
index d4e15dd..0000000
--- a/pw_log_rpc/public/pw_log_rpc/log_service.h
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_log/proto/log.raw_rpc.pb.h"
-#include "pw_log_rpc/rpc_log_drain_map.h"
-#include "pw_status/status.h"
-
-namespace pw::log_rpc {
-
-// The RPC LogService provides a way to start a log stream on a known RPC
-// channel with a writer provided on a call. Log streams maintenance is flexible
-// and delegated outside the service.
-class LogService final : public log::pw_rpc::raw::Logs::Service<LogService> {
- public:
-  LogService(RpcLogDrainMap& drains) : drains_(drains) {}
-
-  // Starts listening to logs on the given RPC channel and writer. The call is
-  // ignored if the channel was not pre-registered in the drain map. If there is
-  // an existent stream of logs for the given channel and previous writer, the
-  // writer in this call is closed without finishing the RPC call and the log
-  // stream using the previous writer continues.
-  void Listen(ConstByteSpan, rpc::RawServerWriter& writer);
-
- private:
-  RpcLogDrainMap& drains_;
-};
-
-}  // namespace pw::log_rpc
diff --git a/pw_log_rpc/public/pw_log_rpc/logs_rpc.h b/pw_log_rpc/public/pw_log_rpc/logs_rpc.h
new file mode 100644
index 0000000..0e3d0fd
--- /dev/null
+++ b/pw_log_rpc/public/pw_log_rpc/logs_rpc.h
@@ -0,0 +1,52 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_log/log.h"
+#include "pw_log_multisink/log_queue.h"
+#include "pw_log_proto/log.raw_rpc.pb.h"
+
+namespace pw::log_rpc {
+
+// The Logs RPC service will send logs when requested by Get(). For now, Get()
+// requests result in a stream of responses, containing all log entries from
+// the attached log queue.
+//
+// The Get() method will return logs in the current queue immediately, but
+// someone else is responsible for pumping the log queue using Flush().
+class Logs final : public pw::log::generated::Logs<Logs> {
+ public:
+  Logs(LogQueue& log_queue) : log_queue_(log_queue), dropped_entries_(0) {}
+
+  // RPC API for the Logs that produces a log stream. This method will
+  // return immediately, another class must call Flush() to push logs from
+  // the queue to this stream.
+  void Get(ServerContext&, ConstByteSpan, rpc::RawServerWriter& writer);
+
+  // Interface for the owner of the service instance to flush all existing
+  // logs to the writer, if one is attached.
+  Status Flush();
+
+  // Interface for the owner of the service instance to close the RPC, if
+  // one is attached.
+  void Finish() { response_writer_.Finish(); }
+
+ private:
+  LogQueue& log_queue_;
+  rpc::RawServerWriter response_writer_;
+  size_t dropped_entries_;
+};
+
+}  // namespace pw::log_rpc
diff --git a/pw_log_rpc/public/pw_log_rpc/rpc_log_drain.h b/pw_log_rpc/public/pw_log_rpc/rpc_log_drain.h
deleted file mode 100644
index 76dd7e3..0000000
--- a/pw_log_rpc/public/pw_log_rpc/rpc_log_drain.h
+++ /dev/null
@@ -1,235 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <algorithm>
-#include <array>
-#include <cstdint>
-#include <limits>
-#include <optional>
-#include <string_view>
-
-#include "pw_assert/assert.h"
-#include "pw_bytes/span.h"
-#include "pw_chrono/system_clock.h"
-#include "pw_function/function.h"
-#include "pw_log/proto/log.pwpb.h"
-#include "pw_log_rpc/internal/config.h"
-#include "pw_log_rpc/log_filter.h"
-#include "pw_multisink/multisink.h"
-#include "pw_protobuf/serialized_size.h"
-#include "pw_result/result.h"
-#include "pw_rpc/raw/server_reader_writer.h"
-#include "pw_status/status.h"
-#include "pw_sync/lock_annotations.h"
-#include "pw_sync/mutex.h"
-
-namespace pw::log_rpc {
-
-// RpcLogDrain matches a MultiSink::Drain with with an RPC channel's writer. A
-// RPC channel ID identifies this drain. The user must attach this drain
-// to a MultiSink that returns a log::LogEntry, and provide a buffer large
-// enough to hold the largest log::LogEntry transmittable. The user must call
-// Flush(), which, on every call, packs as many log::LogEntry items as possible
-// into a log::LogEntries message, writes the message to the provided writer,
-// then repeats the process until there are no more entries in the MultiSink or
-// the writer failed to write the outgoing package and error_handling is set to
-// `kCloseStreamOnWriterError`. When error_handling is `kIgnoreWriterErrors` the
-// drain will continue to retrieve log entries out of the MultiSink and attempt
-// to send them out ignoring the writer errors without sending a drop count.
-// Note: the error handling and drop count reporting might change in the future.
-// Log filtering is done using the rules of the Filter provided if any.
-class RpcLogDrain : public multisink::MultiSink::Drain {
- public:
-  // Dictates how to handle server writer errors.
-  enum class LogDrainErrorHandling {
-    kIgnoreWriterErrors,
-    kCloseStreamOnWriterError,
-  };
-
-  // The minimum buffer size, without the message payload or module sizes,
-  // needed to retrieve a log::LogEntry from the attached MultiSink. The user
-  // must account for the max message size to avoid log entry drops. The dropped
-  // field is not accounted since a dropped message has all other fields unset.
-  static constexpr size_t kMinEntrySizeWithoutPayload =
-      protobuf::SizeOfFieldBytes(log::LogEntry::Fields::MESSAGE, 0) +
-      protobuf::SizeOfFieldUint32(log::LogEntry::Fields::LINE_LEVEL) +
-      protobuf::SizeOfFieldUint32(log::LogEntry::Fields::FLAGS) +
-      protobuf::SizeOfFieldInt64(log::LogEntry::Fields::TIMESTAMP) +
-      protobuf::SizeOfFieldBytes(log::LogEntry::Fields::MODULE, 0) +
-      protobuf::SizeOfFieldBytes(log::LogEntry::Fields::FILE, 0) +
-      protobuf::SizeOfFieldBytes(log::LogEntry::Fields::THREAD, 0);
-
-  // Error messages sent when logs are dropped.
-  static constexpr std::string_view kIngressErrorMessage{
-      PW_LOG_RPC_INGRESS_ERROR_MSG};
-  static constexpr std::string_view kSlowDrainErrorMessage{
-      PW_LOG_RPC_SLOW_DRAIN_MSG};
-  static constexpr std::string_view kSmallOutboundBufferErrorMessage{
-      PW_LOG_RPC_SMALL_OUTBOUND_BUFFER_MSG};
-  static constexpr std::string_view kSmallStackBufferErrorMessage{
-      PW_LOG_RPC_SMALL_STACK_BUFFER_MSG};
-  static constexpr std::string_view kWriterErrorMessage{
-      PW_LOG_RPC_WRITER_ERROR_MSG};
-  // The smallest entry buffer must fit the largest error message, or a typical
-  // token size (4B), whichever is largest.
-  static constexpr size_t kLargestErrorMessageOrTokenSize =
-      std::max({size_t(4),
-                kIngressErrorMessage.size(),
-                kSlowDrainErrorMessage.size(),
-                kSmallOutboundBufferErrorMessage.size(),
-                kSmallStackBufferErrorMessage.size(),
-                kWriterErrorMessage.size()});
-  static constexpr size_t kMinEntryBufferSize =
-      kMinEntrySizeWithoutPayload + sizeof(kLargestErrorMessageOrTokenSize);
-
-  // When encoding LogEntry in LogEntries, there are kLogEntriesEncodeFrameSize
-  // bytes added to the encoded LogEntry. This constant and kMinEntryBufferSize
-  // can be used to calculate the minimum RPC ChannelOutput buffer size.
-  static constexpr size_t kLogEntriesEncodeFrameSize =
-      protobuf::FieldNumberSizeBytes(log::LogEntries::Fields::ENTRIES) +
-      protobuf::kMaxSizeOfLength +
-      protobuf::SizeOfFieldUint32(
-          log::LogEntries::Fields::FIRST_ENTRY_SEQUENCE_ID);
-
-  // Creates a closed log stream with a writer that can be set at a later time.
-  // The provided buffer must be large enough to hold the largest transmittable
-  // log::LogEntry or a drop count message at the very least. The user can
-  // choose to provide a unique mutex for the drain, or share it to save RAM as
-  // long as they are aware of contengency issues.
-  RpcLogDrain(
-      const uint32_t channel_id,
-      ByteSpan log_entry_buffer,
-      sync::Mutex& mutex,
-      LogDrainErrorHandling error_handling,
-      Filter* filter = nullptr,
-      size_t max_bundles_per_trickle = std::numeric_limits<size_t>::max(),
-      pw::chrono::SystemClock::duration trickle_delay =
-          chrono::SystemClock::duration::zero())
-      : channel_id_(channel_id),
-        error_handling_(error_handling),
-        server_writer_(),
-        log_entry_buffer_(log_entry_buffer),
-        drop_count_ingress_error_(0),
-        drop_count_slow_drain_(0),
-        drop_count_small_outbound_buffer_(0),
-        drop_count_small_stack_buffer_(0),
-        drop_count_writer_error_(0),
-        mutex_(mutex),
-        filter_(filter),
-        sequence_id_(0),
-        max_bundles_per_trickle_(max_bundles_per_trickle),
-        trickle_delay_(trickle_delay),
-        no_writes_until_(chrono::SystemClock::now()),
-        on_open_callback_(nullptr) {
-    PW_ASSERT(log_entry_buffer.size_bytes() >= kMinEntryBufferSize);
-  }
-
-  // Not copyable.
-  RpcLogDrain(const RpcLogDrain&) = delete;
-  RpcLogDrain& operator=(const RpcLogDrain&) = delete;
-
-  // Configures the drain with a new open server writer if the current one is
-  // not open.
-  //
-  // Return values:
-  // OK - Successfully set the new open writer.
-  // FAILED_PRECONDITION - The given writer is not open.
-  // ALREADY_EXISTS - an open writer is already set.
-  Status Open(rpc::RawServerWriter& writer) PW_LOCKS_EXCLUDED(mutex_);
-
-  // Accesses log entries and sends them via the writer. Expected to be called
-  // frequently to avoid log drops. If the writer fails to send a packet with
-  // multiple log entries, the entries are dropped and a drop message with the
-  // count is sent. When error_handling is kCloseStreamOnWriterError, the stream
-  // will automatically be closed and Flush will return the writer error.
-  //
-  // Precondition: the drain must be attached to a MultiSink.
-  //
-  // Return values:
-  // OK - all entries were consumed.
-  // ABORTED - there was an error writing the packet, and error_handling equals
-  // `kCloseStreamOnWriterError`.
-  Status Flush(ByteSpan encoding_buffer) PW_LOCKS_EXCLUDED(mutex_);
-
-  // Writes entries as dictated by this drain's rate limiting configuration.
-  //
-  // Returns:
-  //   A minimum wait duration before Trickle() will be ready to write more logs
-  // If no duration is returned, this drain is caught up.
-  std::optional<pw::chrono::SystemClock::duration> Trickle(
-      ByteSpan encoding_buffer) PW_LOCKS_EXCLUDED(mutex_);
-
-  // Ends RPC log stream without flushing.
-  //
-  // Return values:
-  // OK - successfully closed the server writer.
-  // FAILED_PRECONDITION - The given writer is not open.
-  // Errors from the underlying writer send packet.
-  Status Close() PW_LOCKS_EXCLUDED(mutex_);
-
-  uint32_t channel_id() const { return channel_id_; }
-
-  size_t max_bundles_per_trickle() const { return max_bundles_per_trickle_; }
-  void set_max_bundles_per_trickle(size_t max_num_entries) {
-    max_bundles_per_trickle_ = max_num_entries;
-  }
-
-  chrono::SystemClock::duration trickle_delay() const { return trickle_delay_; }
-  void set_trickle_delay(chrono::SystemClock::duration trickle_delay) {
-    trickle_delay_ = trickle_delay;
-  }
-
-  // Stores a function that is called when Open() is successful. Pass nulltpr to
-  // clear it. This is useful in cases where the owner of the drain needs to be
-  // notified that the drain was opened.
-  void set_on_open_callback(pw::Function<void()>&& callback) {
-    on_open_callback_ = std::move(callback);
-  }
-
- private:
-  enum class LogDrainState {
-    kCaughtUp,
-    kMoreEntriesRemaining,
-  };
-
-  LogDrainState SendLogs(size_t max_num_bundles,
-                         ByteSpan encoding_buffer,
-                         Status& encoding_status) PW_LOCKS_EXCLUDED(mutex_);
-
-  // Fills the outgoing buffer with as many entries as possible.
-  LogDrainState EncodeOutgoingPacket(log::LogEntries::MemoryEncoder& encoder,
-                                     uint32_t& packed_entry_count_out)
-      PW_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
-
-  const uint32_t channel_id_;
-  const LogDrainErrorHandling error_handling_;
-  rpc::RawServerWriter server_writer_ PW_GUARDED_BY(mutex_);
-  const ByteSpan log_entry_buffer_ PW_GUARDED_BY(mutex_);
-  uint32_t drop_count_ingress_error_ PW_GUARDED_BY(mutex_);
-  uint32_t drop_count_slow_drain_ PW_GUARDED_BY(mutex_);
-  uint32_t drop_count_small_outbound_buffer_ PW_GUARDED_BY(mutex_);
-  uint32_t drop_count_small_stack_buffer_ PW_GUARDED_BY(mutex_);
-  uint32_t drop_count_writer_error_ PW_GUARDED_BY(mutex_);
-  sync::Mutex& mutex_;
-  Filter* filter_;
-  uint32_t sequence_id_;
-  size_t max_bundles_per_trickle_;
-  pw::chrono::SystemClock::duration trickle_delay_;
-  pw::chrono::SystemClock::time_point no_writes_until_;
-  pw::Function<void()> on_open_callback_;
-};
-
-}  // namespace pw::log_rpc
diff --git a/pw_log_rpc/public/pw_log_rpc/rpc_log_drain_map.h b/pw_log_rpc/public/pw_log_rpc/rpc_log_drain_map.h
deleted file mode 100644
index d21c37b..0000000
--- a/pw_log_rpc/public/pw_log_rpc/rpc_log_drain_map.h
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <span>
-
-#include "pw_log_rpc/rpc_log_drain.h"
-#include "pw_result/result.h"
-#include "pw_status/status.h"
-
-namespace pw::log_rpc {
-
-// Holds an inmutable map of RPC channel ID to RpcLogDrain to fascilitate the
-// maintenance of all RPC log streams.
-class RpcLogDrainMap {
- public:
-  explicit constexpr RpcLogDrainMap(std::span<RpcLogDrain> drains)
-      : drains_(drains) {}
-
-  // Not copyable nor movable.
-  RpcLogDrainMap(RpcLogDrainMap const&) = delete;
-  RpcLogDrainMap& operator=(RpcLogDrainMap const&) = delete;
-  RpcLogDrainMap(RpcLogDrainMap&&) = delete;
-  RpcLogDrainMap& operator=(RpcLogDrainMap&&) = delete;
-
-  Result<RpcLogDrain*> GetDrainFromChannelId(uint32_t channel_id) const {
-    for (auto& drain : drains_) {
-      if (drain.channel_id() == channel_id) {
-        return &drain;
-      }
-    }
-    return Status::NotFound();
-  }
-
-  const std::span<RpcLogDrain>& drains() const { return drains_; }
-
- protected:
-  const std::span<RpcLogDrain> drains_;
-};
-
-}  // namespace pw::log_rpc
diff --git a/pw_log_rpc/public/pw_log_rpc/rpc_log_drain_thread.h b/pw_log_rpc/public/pw_log_rpc/rpc_log_drain_thread.h
deleted file mode 100644
index 7b86007..0000000
--- a/pw_log_rpc/public/pw_log_rpc/rpc_log_drain_thread.h
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstddef>
-#include <optional>
-#include <span>
-
-#include "pw_chrono/system_clock.h"
-#include "pw_log_rpc/log_service.h"
-#include "pw_log_rpc/rpc_log_drain_map.h"
-#include "pw_multisink/multisink.h"
-#include "pw_result/result.h"
-#include "pw_rpc/raw/server_reader_writer.h"
-#include "pw_status/status.h"
-#include "pw_status/try.h"
-#include "pw_sync/timed_thread_notification.h"
-#include "pw_thread/thread_core.h"
-
-namespace pw::log_rpc {
-
-// RpcLogDrainThread is a single thread and single MultiSink::Listener that
-// manages multiple log streams. It is a suitable option when a minimal
-// thread count is desired but comes with the cost of individual log streams
-// blocking each other's flushing.
-class RpcLogDrainThread : public thread::ThreadCore,
-                          public multisink::MultiSink::Listener {
- public:
-  RpcLogDrainThread(multisink::MultiSink& multisink,
-                    RpcLogDrainMap& drain_map,
-                    std::span<std::byte> encoding_buffer)
-      : drain_map_(drain_map),
-        multisink_(multisink),
-        encoding_buffer_(encoding_buffer) {}
-
-  void OnNewEntryAvailable() override {
-    ready_to_flush_notification_.release();
-  }
-
-  // Sequentially flushes each log stream.
-  void Run() override {
-    for (auto& drain : drain_map_.drains()) {
-      multisink_.AttachDrain(drain);
-      drain.set_on_open_callback(
-          [this]() { this->ready_to_flush_notification_.release(); });
-    }
-    multisink_.AttachListener(*this);
-
-    bool drains_pending = true;
-    std::optional<chrono::SystemClock::duration> min_delay =
-        chrono::SystemClock::duration::zero();
-    while (true) {
-      if (drains_pending && min_delay.has_value()) {
-        ready_to_flush_notification_.try_acquire_for(min_delay.value());
-      } else {
-        ready_to_flush_notification_.acquire();
-      }
-      drains_pending = false;
-      min_delay = std::nullopt;
-      for (auto& drain : drain_map_.drains()) {
-        std::optional<chrono::SystemClock::duration> drain_ready_in =
-            drain.Trickle(encoding_buffer_);
-        if (drain_ready_in.has_value()) {
-          min_delay = std::min(drain_ready_in.value(),
-                               min_delay.value_or(drain_ready_in.value()));
-          drains_pending = true;
-        }
-      }
-    }
-  }
-
-  // Opens a server writer to set up an unrequested log stream.
-  Status OpenUnrequestedLogStream(uint32_t channel_id,
-                                  rpc::Server& rpc_server,
-                                  LogService& log_service) {
-    rpc::RawServerWriter writer =
-        rpc::RawServerWriter::Open<log::pw_rpc::raw::Logs::Listen>(
-            rpc_server, channel_id, log_service);
-    const Result<RpcLogDrain*> drain =
-        drain_map_.GetDrainFromChannelId(channel_id);
-    PW_TRY(drain.status());
-    return drain.value()->Open(writer);
-  }
-
- private:
-  sync::TimedThreadNotification ready_to_flush_notification_;
-  RpcLogDrainMap& drain_map_;
-  multisink::MultiSink& multisink_;
-  std::span<std::byte> encoding_buffer_;
-};
-
-template <size_t kEncodingBufferSizeBytes>
-class RpcLogDrainThreadWithBuffer final : public RpcLogDrainThread {
- public:
-  RpcLogDrainThreadWithBuffer(multisink::MultiSink& multisink,
-                              RpcLogDrainMap& drain_map)
-      : RpcLogDrainThread(multisink, drain_map, encoding_buffer_array_) {}
-
- private:
-  static_assert(kEncodingBufferSizeBytes >=
-                    RpcLogDrain::kLogEntriesEncodeFrameSize +
-                        RpcLogDrain::kMinEntryBufferSize,
-                "RpcLogDrainThread's encoding buffer must be large enough for "
-                "at least one entry");
-
-  std::byte encoding_buffer_array_[kEncodingBufferSizeBytes];
-};
-
-}  // namespace pw::log_rpc
diff --git a/pw_log_rpc/pw_log_rpc_private/test_utils.h b/pw_log_rpc/pw_log_rpc_private/test_utils.h
deleted file mode 100644
index f89f229..0000000
--- a/pw_log_rpc/pw_log_rpc_private/test_utils.h
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_bytes/span.h"
-#include "pw_containers/vector.h"
-#include "pw_log_tokenized/metadata.h"
-#include "pw_protobuf/decoder.h"
-
-namespace pw::log_rpc {
-
-struct TestLogEntry {
-  log_tokenized::Metadata metadata;
-  int64_t timestamp = 0;
-  uint32_t dropped = 0;
-  ConstByteSpan tokenized_data = {};
-  ConstByteSpan file = {};
-  ConstByteSpan thread = {};
-};
-
-// Unpacks a `LogEntry` proto buffer to compare it with the expected data and
-// updates the total drop count found.
-void VerifyLogEntry(protobuf::Decoder& entry_decoder,
-                    const TestLogEntry& expected_entry,
-                    uint32_t& drop_count_out);
-
-// Verifies a stream of log entries and updates the total entry and drop counts.
-void VerifyLogEntries(protobuf::Decoder& entries_decoder,
-                      const Vector<TestLogEntry>& expected_entries,
-                      uint32_t expected_first_entry_sequence_id,
-                      size_t& entries_count_out,
-                      uint32_t& drop_count_out);
-
-size_t CountLogEntries(protobuf::Decoder& entries_decoder);
-
-}  // namespace pw::log_rpc
diff --git a/pw_log_rpc/rpc_log_drain.cc b/pw_log_rpc/rpc_log_drain.cc
deleted file mode 100644
index fe12ba0..0000000
--- a/pw_log_rpc/rpc_log_drain.cc
+++ /dev/null
@@ -1,260 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_log_rpc/rpc_log_drain.h"
-
-#include <limits>
-#include <mutex>
-#include <optional>
-#include <span>
-#include <string_view>
-
-#include "pw_assert/check.h"
-#include "pw_chrono/system_clock.h"
-#include "pw_log/proto/log.pwpb.h"
-#include "pw_result/result.h"
-#include "pw_rpc/raw/server_reader_writer.h"
-#include "pw_status/status.h"
-#include "pw_status/try.h"
-
-namespace pw::log_rpc {
-namespace {
-
-// Creates an encoded drop message on the provided buffer and adds it to the
-// bulk log entries. Resets the drop count when successfull.
-void TryEncodeDropMessage(ByteSpan encoded_drop_message_buffer,
-                          std::string_view reason,
-                          uint32_t& drop_count,
-                          log::LogEntries::MemoryEncoder& entries_encoder) {
-  // Encode drop count and reason, if any, in log proto.
-  log::LogEntry::MemoryEncoder encoder(encoded_drop_message_buffer);
-  if (!reason.empty()) {
-    encoder.WriteMessage(std::as_bytes(std::span(reason))).IgnoreError();
-  }
-  encoder.WriteDropped(drop_count).IgnoreError();
-  if (!encoder.status().ok()) {
-    return;
-  }
-  // Add encoded drop messsage if fits in buffer.
-  ConstByteSpan drop_message(encoder);
-  if (drop_message.size() + RpcLogDrain::kLogEntriesEncodeFrameSize <
-      entries_encoder.ConservativeWriteLimit()) {
-    PW_CHECK_OK(entries_encoder.WriteBytes(
-        static_cast<uint32_t>(log::LogEntries::Fields::ENTRIES), drop_message));
-    drop_count = 0;
-  }
-}
-
-}  // namespace
-
-Status RpcLogDrain::Open(rpc::RawServerWriter& writer) {
-  if (!writer.active()) {
-    return Status::FailedPrecondition();
-  }
-  std::lock_guard lock(mutex_);
-  if (server_writer_.active()) {
-    return Status::AlreadyExists();
-  }
-  server_writer_ = std::move(writer);
-  if (on_open_callback_ != nullptr) {
-    on_open_callback_();
-  }
-  return OkStatus();
-}
-
-Status RpcLogDrain::Flush(ByteSpan encoding_buffer) {
-  Status status;
-  SendLogs(std::numeric_limits<size_t>::max(), encoding_buffer, status);
-  return status;
-}
-
-std::optional<chrono::SystemClock::duration> RpcLogDrain::Trickle(
-    ByteSpan encoding_buffer) {
-  chrono::SystemClock::time_point now = chrono::SystemClock::now();
-  // Called before drain is ready to send more logs. Ignore this request and
-  // remind the caller how much longer they'll need to wait.
-  if (no_writes_until_ > now) {
-    return no_writes_until_ - now;
-  }
-
-  Status encoding_status;
-  if (SendLogs(max_bundles_per_trickle_, encoding_buffer, encoding_status) ==
-      LogDrainState::kCaughtUp) {
-    return std::nullopt;
-  }
-
-  no_writes_until_ = chrono::SystemClock::TimePointAfterAtLeast(trickle_delay_);
-  return trickle_delay_;
-}
-
-RpcLogDrain::LogDrainState RpcLogDrain::SendLogs(size_t max_num_bundles,
-                                                 ByteSpan encoding_buffer,
-                                                 Status& encoding_status_out) {
-  PW_CHECK_NOTNULL(multisink_);
-
-  LogDrainState log_sink_state = LogDrainState::kMoreEntriesRemaining;
-  std::lock_guard lock(mutex_);
-  size_t sent_bundle_count = 0;
-  while (sent_bundle_count < max_num_bundles &&
-         log_sink_state != LogDrainState::kCaughtUp) {
-    if (!server_writer_.active()) {
-      encoding_status_out = Status::Unavailable();
-      // No reason to keep polling this drain until the writer is opened.
-      return LogDrainState::kCaughtUp;
-    }
-    log::LogEntries::MemoryEncoder encoder(encoding_buffer);
-    uint32_t packed_entry_count = 0;
-    log_sink_state = EncodeOutgoingPacket(encoder, packed_entry_count);
-
-    // Avoid sending empty packets.
-    if (encoder.size() == 0) {
-      continue;
-    }
-
-    encoder.WriteFirstEntrySequenceId(sequence_id_)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    sequence_id_ += packed_entry_count;
-    const Status status = server_writer_.Write(encoder);
-    sent_bundle_count++;
-
-    if (!status.ok() &&
-        error_handling_ == LogDrainErrorHandling::kCloseStreamOnWriterError) {
-      // Only update this drop count when writer errors are not ignored.
-      drop_count_writer_error_ += packed_entry_count;
-      server_writer_.Finish().IgnoreError();
-      encoding_status_out = Status::Aborted();
-      return log_sink_state;
-    }
-  }
-  return log_sink_state;
-}
-
-RpcLogDrain::LogDrainState RpcLogDrain::EncodeOutgoingPacket(
-    log::LogEntries::MemoryEncoder& encoder, uint32_t& packed_entry_count_out) {
-  const size_t total_buffer_size = encoder.ConservativeWriteLimit();
-  do {
-    // Peek entry and get drop count from multisink.
-    uint32_t drop_count = 0;
-    uint32_t ingress_drop_count = 0;
-    Result<multisink::MultiSink::Drain::PeekedEntry> possible_entry =
-        PeekEntry(log_entry_buffer_, drop_count, ingress_drop_count);
-    drop_count_ingress_error_ += ingress_drop_count;
-
-    // Check if the entry fits in the entry buffer.
-    if (possible_entry.status().IsResourceExhausted()) {
-      ++drop_count_small_stack_buffer_;
-      continue;
-    }
-
-    // Check if there are any entries left.
-    if (possible_entry.status().IsOutOfRange()) {
-      // Stash multisink's reported drop count that will be reported later with
-      // any other drop counts.
-      drop_count_slow_drain_ += drop_count;
-      return LogDrainState::kCaughtUp;  // There are no more entries.
-    }
-
-    // At this point all expected errors have been handled.
-    PW_CHECK_OK(possible_entry.status());
-
-    // Check if the entry passes any set filter rules.
-    if (filter_ != nullptr &&
-        filter_->ShouldDropLog(possible_entry.value().entry())) {
-      // Add the drop count from the multisink peek, stored in `drop_count`, to
-      // the total drop count. Then drop the entry without counting it towards
-      // the total drop count. Drops will be reported later all together.
-      drop_count_slow_drain_ += drop_count;
-      PW_CHECK_OK(PopEntry(possible_entry.value()));
-      continue;
-    }
-
-    // Check if the entry fits in the encoder buffer by itself.
-    const size_t encoded_entry_size =
-        possible_entry.value().entry().size() + kLogEntriesEncodeFrameSize;
-    if (encoded_entry_size + kLogEntriesEncodeFrameSize > total_buffer_size) {
-      // Entry is larger than the entire available buffer.
-      ++drop_count_small_outbound_buffer_;
-      PW_CHECK_OK(PopEntry(possible_entry.value()));
-      continue;
-    }
-
-    // At this point, we have a valid entry that may fit in the encode buffer.
-    // Report any drop counts combined reusing the log_entry_buffer_ to encode a
-    // drop message.
-    drop_count_slow_drain_ += drop_count;
-    // Account for dropped entries too large for stack buffer, which PeekEntry()
-    // also reports.
-    drop_count_slow_drain_ -= drop_count_small_stack_buffer_;
-    bool log_entry_buffer_has_valid_entry = possible_entry.ok();
-    if (drop_count_slow_drain_ > 0) {
-      TryEncodeDropMessage(log_entry_buffer_,
-                           std::string_view(kSlowDrainErrorMessage),
-                           drop_count_slow_drain_,
-                           encoder);
-      log_entry_buffer_has_valid_entry = false;
-    }
-    if (drop_count_ingress_error_ > 0) {
-      TryEncodeDropMessage(log_entry_buffer_,
-                           std::string_view(kIngressErrorMessage),
-                           drop_count_ingress_error_,
-                           encoder);
-      log_entry_buffer_has_valid_entry = false;
-    }
-    if (drop_count_small_stack_buffer_ > 0) {
-      TryEncodeDropMessage(log_entry_buffer_,
-                           std::string_view(kSmallStackBufferErrorMessage),
-                           drop_count_small_stack_buffer_,
-                           encoder);
-      log_entry_buffer_has_valid_entry = false;
-    }
-    if (drop_count_small_outbound_buffer_ > 0) {
-      TryEncodeDropMessage(log_entry_buffer_,
-                           std::string_view(kSmallOutboundBufferErrorMessage),
-                           drop_count_small_outbound_buffer_,
-                           encoder);
-      log_entry_buffer_has_valid_entry = false;
-    }
-    if (drop_count_writer_error_ > 0) {
-      TryEncodeDropMessage(log_entry_buffer_,
-                           std::string_view(kWriterErrorMessage),
-                           drop_count_writer_error_,
-                           encoder);
-      log_entry_buffer_has_valid_entry = false;
-    }
-    if (possible_entry.ok() && !log_entry_buffer_has_valid_entry) {
-      PW_CHECK_OK(PeekEntry(log_entry_buffer_, drop_count, ingress_drop_count)
-                      .status());
-    }
-
-    // Check if the entry fits in the partially filled encoder buffer.
-    if (encoded_entry_size > encoder.ConservativeWriteLimit()) {
-      // Notify the caller there are more entries to send.
-      return LogDrainState::kMoreEntriesRemaining;
-    }
-
-    // Encode the entry and remove it from multisink.
-    PW_CHECK_OK(encoder.WriteBytes(
-        static_cast<uint32_t>(log::LogEntries::Fields::ENTRIES),
-        possible_entry.value().entry()));
-    PW_CHECK_OK(PopEntry(possible_entry.value()));
-    ++packed_entry_count_out;
-  } while (true);
-}
-
-Status RpcLogDrain::Close() {
-  std::lock_guard lock(mutex_);
-  return server_writer_.Finish();
-}
-
-}  // namespace pw::log_rpc
diff --git a/pw_log_rpc/rpc_log_drain_test.cc b/pw_log_rpc/rpc_log_drain_test.cc
deleted file mode 100644
index 157e115..0000000
--- a/pw_log_rpc/rpc_log_drain_test.cc
+++ /dev/null
@@ -1,467 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_log_rpc/rpc_log_drain.h"
-
-#include <array>
-#include <cstdint>
-#include <span>
-#include <string_view>
-
-#include "gtest/gtest.h"
-#include "pw_bytes/array.h"
-#include "pw_bytes/span.h"
-#include "pw_log/proto/log.pwpb.h"
-#include "pw_log/proto_utils.h"
-#include "pw_log_rpc/log_filter.h"
-#include "pw_log_rpc/log_service.h"
-#include "pw_log_rpc/rpc_log_drain_map.h"
-#include "pw_log_rpc_private/test_utils.h"
-#include "pw_log_tokenized/metadata.h"
-#include "pw_multisink/multisink.h"
-#include "pw_protobuf/decoder.h"
-#include "pw_protobuf/serialized_size.h"
-#include "pw_rpc/channel.h"
-#include "pw_rpc/raw/fake_channel_output.h"
-#include "pw_rpc/raw/server_reader_writer.h"
-#include "pw_status/status.h"
-#include "pw_sync/mutex.h"
-
-namespace pw::log_rpc {
-namespace {
-static constexpr size_t kBufferSize =
-    RpcLogDrain::kMinEntrySizeWithoutPayload + 32;
-
-TEST(RpcLogDrain, TryFlushDrainWithClosedWriter) {
-  // Drain without a writer.
-  const uint32_t drain_id = 1;
-  std::array<std::byte, kBufferSize> buffer;
-  sync::Mutex mutex;
-  RpcLogDrain drain(
-      drain_id,
-      buffer,
-      mutex,
-      RpcLogDrain::LogDrainErrorHandling::kCloseStreamOnWriterError,
-      nullptr);
-  EXPECT_EQ(drain.channel_id(), drain_id);
-
-  std::byte encoding_buffer[128] = {};
-
-  // Attach drain to a MultiSink.
-  std::array<std::byte, kBufferSize * 2> multisink_buffer;
-  multisink::MultiSink multisink(multisink_buffer);
-  multisink.AttachDrain(drain);
-  EXPECT_EQ(drain.Flush(encoding_buffer), Status::Unavailable());
-
-  rpc::RawServerWriter writer;
-  ASSERT_FALSE(writer.active());
-  EXPECT_EQ(drain.Open(writer), Status::FailedPrecondition());
-  EXPECT_EQ(drain.Flush(encoding_buffer), Status::Unavailable());
-}
-
-TEST(RpcLogDrainMap, GetDrainsByIdFromDrainMap) {
-  static constexpr size_t kMaxDrains = 3;
-  sync::Mutex mutex;
-  std::array<std::array<std::byte, kBufferSize>, kMaxDrains> buffers;
-  std::array<RpcLogDrain, kMaxDrains> drains{
-      RpcLogDrain(0,
-                  buffers[0],
-                  mutex,
-                  RpcLogDrain::LogDrainErrorHandling::kCloseStreamOnWriterError,
-                  nullptr),
-      RpcLogDrain(1,
-                  buffers[1],
-                  mutex,
-                  RpcLogDrain::LogDrainErrorHandling::kCloseStreamOnWriterError,
-                  nullptr),
-      RpcLogDrain(2,
-                  buffers[2],
-                  mutex,
-                  RpcLogDrain::LogDrainErrorHandling::kIgnoreWriterErrors,
-                  nullptr),
-  };
-
-  RpcLogDrainMap drain_map(drains);
-  for (uint32_t channel_id = 0; channel_id < kMaxDrains; ++channel_id) {
-    auto drain_result = drain_map.GetDrainFromChannelId(channel_id);
-    ASSERT_TRUE(drain_result.ok());
-    EXPECT_EQ(drain_result.value(), &drains[channel_id]);
-  }
-  const std::span<RpcLogDrain> mapped_drains = drain_map.drains();
-  ASSERT_EQ(mapped_drains.size(), kMaxDrains);
-  for (uint32_t channel_id = 0; channel_id < kMaxDrains; ++channel_id) {
-    EXPECT_EQ(&mapped_drains[channel_id], &drains[channel_id]);
-  }
-}
-
-TEST(RpcLogDrain, FlushingDrainWithOpenWriter) {
-  const uint32_t drain_id = 1;
-  std::array<std::byte, kBufferSize> buffer;
-  sync::Mutex mutex;
-  std::array<RpcLogDrain, 1> drains{
-      RpcLogDrain(drain_id,
-                  buffer,
-                  mutex,
-                  RpcLogDrain::LogDrainErrorHandling::kCloseStreamOnWriterError,
-                  nullptr),
-  };
-  RpcLogDrainMap drain_map(drains);
-  LogService log_service(drain_map);
-
-  std::byte encoding_buffer[128] = {};
-
-  rpc::RawFakeChannelOutput<3> output;
-  rpc::Channel channel(rpc::Channel::Create<drain_id>(&output));
-  rpc::Server server(std::span(&channel, 1));
-
-  // Attach drain to a MultiSink.
-  RpcLogDrain& drain = drains[0];
-  std::array<std::byte, kBufferSize * 2> multisink_buffer;
-  multisink::MultiSink multisink(multisink_buffer);
-  multisink.AttachDrain(drain);
-  EXPECT_EQ(drain.Flush(encoding_buffer), Status::Unavailable());
-
-  rpc::RawServerWriter writer =
-      rpc::RawServerWriter::Open<log::pw_rpc::raw::Logs::Listen>(
-          server, drain_id, log_service);
-  ASSERT_TRUE(writer.active());
-  EXPECT_EQ(drain.Open(writer), OkStatus());
-  EXPECT_EQ(drain.Flush(encoding_buffer), OkStatus());
-  // Can call multliple times until closed on error.
-  EXPECT_EQ(drain.Flush(encoding_buffer), OkStatus());
-  EXPECT_EQ(drain.Close(), OkStatus());
-  rpc::RawServerWriter& writer_ref = writer;
-  ASSERT_FALSE(writer_ref.active());
-  EXPECT_EQ(drain.Flush(encoding_buffer), Status::Unavailable());
-}
-
-TEST(RpcLogDrain, TryReopenOpenedDrain) {
-  const uint32_t drain_id = 1;
-  std::array<std::byte, kBufferSize> buffer;
-  sync::Mutex mutex;
-  std::array<RpcLogDrain, 1> drains{
-      RpcLogDrain(drain_id,
-                  buffer,
-                  mutex,
-                  RpcLogDrain::LogDrainErrorHandling::kCloseStreamOnWriterError,
-                  nullptr),
-  };
-  RpcLogDrainMap drain_map(drains);
-  LogService log_service(drain_map);
-
-  rpc::RawFakeChannelOutput<1> output;
-  rpc::Channel channel(rpc::Channel::Create<drain_id>(&output));
-  rpc::Server server(std::span(&channel, 1));
-
-  // Open Drain and try to open with a new writer.
-  rpc::RawServerWriter writer =
-      rpc::RawServerWriter::Open<log::pw_rpc::raw::Logs::Listen>(
-          server, drain_id, log_service);
-  ASSERT_TRUE(writer.active());
-  RpcLogDrain& drain = drains[0];
-  EXPECT_EQ(drain.Open(writer), OkStatus());
-  rpc::RawServerWriter second_writer =
-      rpc::RawServerWriter::Open<log::pw_rpc::raw::Logs::Listen>(
-          server, drain_id, log_service);
-  ASSERT_FALSE(writer.active());
-  ASSERT_TRUE(second_writer.active());
-  EXPECT_EQ(drain.Open(second_writer), OkStatus());
-}
-
-class TrickleTest : public ::testing::Test {
- protected:
-  TrickleTest()
-      : log_message_encode_buffer_(),
-        drain_encode_buffer_(),
-        channel_encode_buffer_(),
-        mutex_(),
-        drains_{
-            RpcLogDrain(
-                kDrainChannelId,
-                drain_encode_buffer_,
-                mutex_,
-                RpcLogDrain::LogDrainErrorHandling::kCloseStreamOnWriterError,
-                nullptr),
-        },
-        multisink_buffer_(),
-        multisink_(multisink_buffer_),
-        drain_map_(drains_),
-        log_service_(drain_map_),
-        output_(),
-        channel_(rpc::Channel::Create<kDrainChannelId>(&output_)),
-        server_(std::span(&channel_, 1)) {}
-
-  TestLogEntry BasicLog(std::string_view message) {
-    return {.metadata = kSampleMetadata,
-            .timestamp = kSampleTimestamp,
-            .dropped = 0,
-            .tokenized_data = std::as_bytes(std::span(message)),
-            .thread = std::as_bytes(std::span(kSampleThreadName))};
-  }
-
-  void AttachDrain() { multisink_.AttachDrain(drains_[0]); }
-  void OpenWriter() {
-    writer_ = rpc::RawServerWriter::Open<log::pw_rpc::raw::Logs::Listen>(
-        server_, kDrainChannelId, log_service_);
-  }
-
-  void AddLogEntry(const TestLogEntry& entry) {
-    Result<ConstByteSpan> encoded_log_result =
-        log::EncodeTokenizedLog(entry.metadata,
-                                entry.tokenized_data,
-                                entry.timestamp,
-                                entry.thread,
-                                log_message_encode_buffer_);
-    ASSERT_EQ(encoded_log_result.status(), OkStatus());
-    EXPECT_LE(encoded_log_result.value().size(), kMaxMessageSize);
-    multisink_.HandleEntry(encoded_log_result.value());
-  }
-
-  void AddLogEntries(const Vector<TestLogEntry>& entries) {
-    for (const TestLogEntry& entry : entries) {
-      AddLogEntry(entry);
-    }
-  }
-
-  static constexpr uint32_t kDrainChannelId = 1;
-  static constexpr size_t kMaxMessageSize = 60;
-
-  // Use the size of the encoded BasicLog entry to calculate buffer sizes and
-  // better control the number of entries in each sent bulk.
-  static constexpr log_tokenized::Metadata kSampleMetadata =
-      log_tokenized::Metadata::Set<PW_LOG_LEVEL_INFO, 123, 0x03, 300>();
-  static constexpr uint64_t kSampleTimestamp = 9000;
-  static constexpr std::string_view kSampleThreadName = "thread";
-  static constexpr size_t kBasicLogSizeWithoutPayload =
-      protobuf::SizeOfFieldBytes(log::LogEntry::Fields::MESSAGE, 0) +
-      protobuf::SizeOfFieldUint32(
-          log::LogEntry::Fields::LINE_LEVEL,
-          log::PackLineLevel(kSampleMetadata.line_number(),
-                             kSampleMetadata.level())) +
-      protobuf::SizeOfFieldUint32(log::LogEntry::Fields::FLAGS,
-                                  kSampleMetadata.flags()) +
-      protobuf::SizeOfFieldInt64(log::LogEntry::Fields::TIMESTAMP,
-                                 kSampleTimestamp) +
-      protobuf::SizeOfFieldBytes(log::LogEntry::Fields::MODULE,
-                                 sizeof(kSampleMetadata.module())) +
-      protobuf::SizeOfFieldBytes(log::LogEntry::Fields::THREAD,
-                                 kSampleThreadName.size());
-  static constexpr size_t kDrainEncodeBufferSize =
-      kBasicLogSizeWithoutPayload + kMaxMessageSize;
-  static constexpr size_t kChannelEncodeBufferSize = kDrainEncodeBufferSize * 2;
-  std::array<std::byte, kMaxMessageSize> log_message_encode_buffer_;
-  std::array<std::byte, kDrainEncodeBufferSize> drain_encode_buffer_;
-  // Make actual encode buffer slightly smaller to account for RPC overhead.
-  std::array<std::byte, kChannelEncodeBufferSize - 8> channel_encode_buffer_;
-  sync::Mutex mutex_;
-  std::array<RpcLogDrain, 1> drains_;
-
-  std::array<std::byte, kDrainEncodeBufferSize * 12> multisink_buffer_;
-  multisink::MultiSink multisink_;
-
-  RpcLogDrainMap drain_map_;
-  LogService log_service_;
-
-  // TODO(amontanez): Why do we need 4 packets? Three should work, but seemingly
-  // on destruction a 14-byte payload is sent out, forcing us to use max
-  // expected packet count plus one.
-  rpc::RawFakeChannelOutput<4, kDrainEncodeBufferSize * 6> output_;
-  rpc::Channel channel_;
-  rpc::Server server_;
-  rpc::RawServerWriter writer_;
-};
-
-TEST_F(TrickleTest, EntriesAreFlushedToSinglePayload) {
-  AttachDrain();
-  OpenWriter();
-
-  Vector<TestLogEntry, 3> kExpectedEntries{
-      BasicLog(":D"), BasicLog("A useful log"), BasicLog("blink")};
-  AddLogEntries(kExpectedEntries);
-
-  ASSERT_TRUE(writer_.active());
-  EXPECT_EQ(drains_[0].Open(writer_), OkStatus());
-
-  std::optional<chrono::SystemClock::duration> min_delay =
-      drains_[0].Trickle(channel_encode_buffer_);
-  EXPECT_EQ(min_delay.has_value(), false);
-
-  rpc::PayloadsView payloads =
-      output_.payloads<log::pw_rpc::raw::Logs::Listen>(kDrainChannelId);
-  EXPECT_EQ(payloads.size(), 1u);
-
-  uint32_t drop_count = 0;
-  size_t entries_count = 0;
-  protobuf::Decoder payload_decoder(payloads[0]);
-  payload_decoder.Reset(payloads[0]);
-  VerifyLogEntries(
-      payload_decoder, kExpectedEntries, 0, entries_count, drop_count);
-  EXPECT_EQ(drop_count, 0u);
-  EXPECT_EQ(entries_count, 3u);
-}
-
-TEST_F(TrickleTest, ManyLogsOverflowToNextPayload) {
-  AttachDrain();
-  OpenWriter();
-
-  Vector<TestLogEntry, 3> kFirstFlushedBundle{
-      BasicLog("Use longer logs in this test"),
-      BasicLog("My feet are cold"),
-      BasicLog("I'm hungry, what's for dinner?")};
-  Vector<TestLogEntry, 3> kSecondFlushedBundle{
-      BasicLog("Add a few longer logs"),
-      BasicLog("Eventually the logs will"),
-      BasicLog("Overflow into another payload")};
-
-  AddLogEntries(kFirstFlushedBundle);
-  AddLogEntries(kSecondFlushedBundle);
-
-  ASSERT_TRUE(writer_.active());
-  EXPECT_EQ(drains_[0].Open(writer_), OkStatus());
-
-  // A single flush should produce two payloads.
-  std::optional<chrono::SystemClock::duration> min_delay =
-      drains_[0].Trickle(channel_encode_buffer_);
-  EXPECT_EQ(min_delay.has_value(), false);
-
-  rpc::PayloadsView payloads =
-      output_.payloads<log::pw_rpc::raw::Logs::Listen>(kDrainChannelId);
-  ASSERT_EQ(payloads.size(), 2u);
-
-  uint32_t drop_count = 0;
-  size_t entries_count = 0;
-  protobuf::Decoder payload_decoder(payloads[0]);
-  payload_decoder.Reset(payloads[0]);
-  VerifyLogEntries(
-      payload_decoder, kFirstFlushedBundle, 0, entries_count, drop_count);
-  EXPECT_EQ(drop_count, 0u);
-  EXPECT_EQ(entries_count, 3u);
-
-  entries_count = 0;
-  payload_decoder.Reset(payloads[1]);
-  VerifyLogEntries(
-      payload_decoder, kSecondFlushedBundle, 3, entries_count, drop_count);
-  EXPECT_EQ(drop_count, 0u);
-  EXPECT_EQ(entries_count, 3u);
-}
-
-TEST_F(TrickleTest, LimitedFlushOverflowsToNextPayload) {
-  AttachDrain();
-  OpenWriter();
-
-  Vector<TestLogEntry, 3> kFirstFlushedBundle{
-      BasicLog("Use longer logs in this test"),
-      BasicLog("My feet are cold"),
-      BasicLog("I'm hungry, what's for dinner?")};
-  Vector<TestLogEntry, 3> kSecondFlushedBundle{
-      BasicLog("Add a few longer logs"),
-      BasicLog("Eventually the logs will"),
-      BasicLog("Overflow into another payload")};
-
-  AddLogEntries(kFirstFlushedBundle);
-
-  // These logs will get pushed into the next payload due to overflowing max
-  // payload size.
-  AddLogEntries(kSecondFlushedBundle);
-
-  ASSERT_TRUE(writer_.active());
-  EXPECT_EQ(drains_[0].Open(writer_), OkStatus());
-  drains_[0].set_max_bundles_per_trickle(1);
-
-  // A single flush should produce two payloads.
-  std::optional<chrono::SystemClock::duration> min_delay =
-      drains_[0].Trickle(channel_encode_buffer_);
-  EXPECT_EQ(min_delay.has_value(), true);
-  EXPECT_EQ(min_delay.value(), chrono::SystemClock::duration::zero());
-
-  rpc::PayloadsView first_flush_payloads =
-      output_.payloads<log::pw_rpc::raw::Logs::Listen>(kDrainChannelId);
-  ASSERT_EQ(first_flush_payloads.size(), 1u);
-  uint32_t drop_count = 0;
-  size_t entries_count = 0;
-  protobuf::Decoder payload_decoder(first_flush_payloads[0]);
-  payload_decoder.Reset(first_flush_payloads[0]);
-  VerifyLogEntries(
-      payload_decoder, kFirstFlushedBundle, 0, entries_count, drop_count);
-  EXPECT_EQ(entries_count, 3u);
-
-  // An additional flush should produce another payload.
-  min_delay = drains_[0].Trickle(channel_encode_buffer_);
-  EXPECT_EQ(min_delay.has_value(), false);
-  drop_count = 0;
-  entries_count = 0;
-
-  rpc::PayloadsView second_flush_payloads =
-      output_.payloads<log::pw_rpc::raw::Logs::Listen>(kDrainChannelId);
-  ASSERT_EQ(second_flush_payloads.size(), 2u);
-  payload_decoder.Reset(second_flush_payloads[1]);
-  VerifyLogEntries(
-      payload_decoder, kSecondFlushedBundle, 3, entries_count, drop_count);
-  EXPECT_EQ(drop_count, 0u);
-  EXPECT_EQ(entries_count, 3u);
-}
-
-TEST(RpcLogDrain, OnOpenCallbackCalled) {
-  // Create drain and log components.
-  const uint32_t drain_id = 1;
-  std::array<std::byte, kBufferSize> buffer;
-  sync::Mutex mutex;
-  RpcLogDrain drain(
-      drain_id,
-      buffer,
-      mutex,
-      RpcLogDrain::LogDrainErrorHandling::kCloseStreamOnWriterError,
-      nullptr);
-  RpcLogDrainMap drain_map(std::span(&drain, 1));
-  LogService log_service(drain_map);
-  std::array<std::byte, kBufferSize * 2> multisink_buffer;
-  multisink::MultiSink multisink(multisink_buffer);
-  multisink.AttachDrain(drain);
-
-  // Create server writer.
-  rpc::RawFakeChannelOutput<3> output;
-  rpc::Channel channel(rpc::Channel::Create<drain_id>(&output));
-  rpc::Server server(std::span(&channel, 1));
-  rpc::RawServerWriter writer =
-      rpc::RawServerWriter::Open<log::pw_rpc::raw::Logs::Listen>(
-          server, drain_id, log_service);
-
-  int callback_call_times = 0;
-  Function<void()> callback = [&callback_call_times]() {
-    ++callback_call_times;
-  };
-
-  // Callback not called when not set.
-  ASSERT_TRUE(writer.active());
-  ASSERT_EQ(drain.Open(writer), OkStatus());
-  EXPECT_EQ(callback_call_times, 0);
-
-  drain.set_on_open_callback(std::move(callback));
-
-  // Callback called when writer is open.
-  writer = rpc::RawServerWriter::Open<log::pw_rpc::raw::Logs::Listen>(
-      server, drain_id, log_service);
-  ASSERT_TRUE(writer.active());
-  ASSERT_EQ(drain.Open(writer), OkStatus());
-  EXPECT_EQ(callback_call_times, 1);
-
-  // Callback not called when writer is closed.
-  rpc::RawServerWriter closed_writer;
-  ASSERT_FALSE(closed_writer.active());
-  ASSERT_EQ(drain.Open(closed_writer), Status::FailedPrecondition());
-  EXPECT_EQ(callback_call_times, 1);
-}
-
-}  // namespace
-}  // namespace pw::log_rpc
diff --git a/pw_log_rpc/test_utils.cc b/pw_log_rpc/test_utils.cc
deleted file mode 100644
index 3a06ec2..0000000
--- a/pw_log_rpc/test_utils.cc
+++ /dev/null
@@ -1,167 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_log_rpc_private/test_utils.h"
-
-#include <cstdint>
-
-#include "gtest/gtest.h"
-#include "pw_bytes/span.h"
-#include "pw_containers/vector.h"
-#include "pw_log/log.h"
-#include "pw_log/proto/log.pwpb.h"
-#include "pw_log_tokenized/metadata.h"
-#include "pw_protobuf/bytes_utils.h"
-#include "pw_protobuf/decoder.h"
-
-namespace pw::log_rpc {
-namespace {
-void VerifyOptionallyTokenizedField(protobuf::Decoder& entry_decoder,
-                                    log::LogEntry::Fields field_number,
-                                    ConstByteSpan expected_data) {
-  if (expected_data.empty()) {
-    return;
-  }
-  ConstByteSpan tokenized_data;
-  ASSERT_EQ(entry_decoder.Next(), OkStatus());
-  ASSERT_EQ(entry_decoder.FieldNumber(), static_cast<uint32_t>(field_number));
-  ASSERT_EQ(entry_decoder.ReadBytes(&tokenized_data), OkStatus());
-  std::string_view data_as_string(
-      reinterpret_cast<const char*>(tokenized_data.begin()),
-      tokenized_data.size());
-  std::string_view expected_data_as_string(
-      reinterpret_cast<const char*>(expected_data.begin()),
-      expected_data.size());
-  EXPECT_EQ(data_as_string, expected_data_as_string);
-}
-}  // namespace
-
-// Unpacks a `LogEntry` proto buffer to compare it with the expected data and
-// updates the total drop count found.
-void VerifyLogEntry(protobuf::Decoder& entry_decoder,
-                    const TestLogEntry& expected_entry,
-                    uint32_t& drop_count_out) {
-  VerifyOptionallyTokenizedField(entry_decoder,
-                                 log::LogEntry::Fields::MESSAGE,
-                                 expected_entry.tokenized_data);
-  if (expected_entry.metadata.level()) {
-    ASSERT_EQ(entry_decoder.Next(), OkStatus());
-    ASSERT_EQ(entry_decoder.FieldNumber(),
-              static_cast<uint32_t>(log::LogEntry::Fields::LINE_LEVEL));
-    uint32_t line_level;
-    ASSERT_TRUE(entry_decoder.ReadUint32(&line_level).ok());
-    EXPECT_EQ(expected_entry.metadata.level(),
-              line_level & PW_LOG_LEVEL_BITMASK);
-    EXPECT_EQ(expected_entry.metadata.line_number(),
-              (line_level & ~PW_LOG_LEVEL_BITMASK) >> PW_LOG_LEVEL_BITS);
-  }
-  if (expected_entry.metadata.flags()) {
-    ASSERT_EQ(entry_decoder.Next(), OkStatus());
-    ASSERT_EQ(entry_decoder.FieldNumber(),
-              static_cast<uint32_t>(log::LogEntry::Fields::FLAGS));
-    uint32_t flags;
-    ASSERT_TRUE(entry_decoder.ReadUint32(&flags).ok());
-    EXPECT_EQ(expected_entry.metadata.flags(), flags);
-  }
-  if (expected_entry.timestamp) {
-    ASSERT_EQ(entry_decoder.Next(), OkStatus());
-    ASSERT_TRUE(entry_decoder.FieldNumber() ==
-                    static_cast<uint32_t>(log::LogEntry::Fields::TIMESTAMP) ||
-                entry_decoder.FieldNumber() ==
-                    static_cast<uint32_t>(
-                        log::LogEntry::Fields::TIME_SINCE_LAST_ENTRY));
-    int64_t timestamp;
-    ASSERT_TRUE(entry_decoder.ReadInt64(&timestamp).ok());
-    EXPECT_EQ(expected_entry.timestamp, timestamp);
-  }
-  if (expected_entry.dropped) {
-    ASSERT_EQ(entry_decoder.Next(), OkStatus());
-    ASSERT_EQ(entry_decoder.FieldNumber(),
-              static_cast<uint32_t>(log::LogEntry::Fields::DROPPED));
-    uint32_t dropped = 0;
-    ASSERT_TRUE(entry_decoder.ReadUint32(&dropped).ok());
-    EXPECT_EQ(expected_entry.dropped, dropped);
-    drop_count_out += dropped;
-  }
-  if (expected_entry.metadata.module()) {
-    ASSERT_EQ(entry_decoder.Next(), OkStatus());
-    ASSERT_EQ(entry_decoder.FieldNumber(),
-              static_cast<uint32_t>(log::LogEntry::Fields::MODULE));
-    const Result<uint32_t> module =
-        protobuf::DecodeBytesToUint32(entry_decoder);
-    ASSERT_EQ(module.status(), OkStatus());
-    EXPECT_EQ(expected_entry.metadata.module(), module.value());
-  }
-  VerifyOptionallyTokenizedField(
-      entry_decoder, log::LogEntry::Fields::FILE, expected_entry.file);
-  VerifyOptionallyTokenizedField(
-      entry_decoder, log::LogEntry::Fields::THREAD, expected_entry.thread);
-}
-
-// Compares an encoded LogEntry's fields against the expected sequence ID and
-// LogEntries, and updates the total entry and drop counts. Starts comparing at
-// `expected_entries[entries_count_out]`. `expected_entries` must be in the same
-// order that messages were added to the MultiSink.
-void VerifyLogEntries(protobuf::Decoder& entries_decoder,
-                      const Vector<TestLogEntry>& expected_entries,
-                      uint32_t expected_first_entry_sequence_id,
-                      size_t& entries_count_out,
-                      uint32_t& drop_count_out) {
-  size_t entry_index = entries_count_out;
-  while (entries_decoder.Next().ok()) {
-    if (static_cast<pw::log::LogEntries::Fields>(
-            entries_decoder.FieldNumber()) ==
-        log::LogEntries::Fields::ENTRIES) {
-      ConstByteSpan entry;
-      EXPECT_EQ(entries_decoder.ReadBytes(&entry), OkStatus());
-      protobuf::Decoder entry_decoder(entry);
-      if (expected_entries.empty()) {
-        break;
-      }
-
-      ASSERT_LT(entry_index, expected_entries.size());
-
-      // Keep track of entries and drops respective counts.
-      uint32_t current_drop_count = 0;
-      VerifyLogEntry(
-          entry_decoder, expected_entries[entry_index], current_drop_count);
-      ++entry_index;
-      drop_count_out += current_drop_count;
-      if (current_drop_count == 0) {
-        ++entries_count_out;
-      }
-    } else if (static_cast<pw::log::LogEntries::Fields>(
-                   entries_decoder.FieldNumber()) ==
-               log::LogEntries::Fields::FIRST_ENTRY_SEQUENCE_ID) {
-      uint32_t first_entry_sequence_id = 0;
-      EXPECT_EQ(entries_decoder.ReadUint32(&first_entry_sequence_id),
-                OkStatus());
-      EXPECT_EQ(expected_first_entry_sequence_id, first_entry_sequence_id);
-    }
-  }
-}
-
-size_t CountLogEntries(protobuf::Decoder& entries_decoder) {
-  size_t entries_found = 0;
-  while (entries_decoder.Next().ok()) {
-    if (static_cast<pw::log::LogEntries::Fields>(
-            entries_decoder.FieldNumber()) ==
-        log::LogEntries::Fields::ENTRIES) {
-      ++entries_found;
-    }
-  }
-  return entries_found;
-}
-
-}  // namespace pw::log_rpc
diff --git a/pw_log_sink/BUILD b/pw_log_sink/BUILD
new file mode 100644
index 0000000..19306ab
--- /dev/null
+++ b/pw_log_sink/BUILD
@@ -0,0 +1,53 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_log_sink",
+    srcs = [ "log_sink.cc" ],
+    includes = [ "public" ],
+    deps = [
+        "//pw_bytes",
+        "//pw_log:facade",
+        "//pw_multisink",
+        "//pw_preprocessor",
+        "//pw_status",
+        "//pw_sync:interrupt_spin_lock",
+    ],
+    hdrs = [
+        "public/pw_log_sink/log_sink.h",
+        "public/pw_log_sink/multisink_adapter.h",
+        "public/pw_log_sink/sink.h",
+        "public_overrides/pw_log_backend/log_backend.h",
+    ]
+)
+
+pw_cc_test(
+    name = "pw_log_sink_test",
+    srcs = [ "log_sink_test.cc" ],
+    deps = [
+        ":pw_log_sink",
+        "//pw_log:protos.pwpb",
+        "//pw_unit_test",
+    ]
+)
diff --git a/pw_log_sink/BUILD.gn b/pw_log_sink/BUILD.gn
new file mode 100644
index 0000000..d7c6485
--- /dev/null
+++ b/pw_log_sink/BUILD.gn
@@ -0,0 +1,70 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/target_types.gni")
+import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
+
+config("default_config") {
+  include_dirs = [ "public" ]
+  visibility = [ ":*" ]
+}
+
+config("backend_config") {
+  include_dirs = [ "public_overrides" ]
+}
+
+pw_source_set("pw_log_sink") {
+  public_configs = [
+    ":backend_config",
+    ":default_config",
+  ]
+  public = [
+    "public/pw_log_sink/log_sink.h",
+    "public/pw_log_sink/multisink_adapter.h",
+    "public/pw_log_sink/sink.h",
+    "public_overrides/pw_log_backend/log_backend.h",
+  ]
+  sources = [ "log_sink.cc" ]
+  public_deps = [
+    "$dir_pw_bytes",
+    "$dir_pw_log:facade",
+    "$dir_pw_multisink",
+    "$dir_pw_preprocessor",
+    "$dir_pw_status",
+  ]
+  deps = [
+    "$dir_pw_log:protos.pwpb",
+    "$dir_pw_string",
+    "$dir_pw_sync:interrupt_spin_lock",
+  ]
+}
+
+pw_doc_group("docs") {
+  sources = [ "docs.rst" ]
+}
+
+pw_test("pw_log_sink_test") {
+  sources = [ "log_sink_test.cc" ]
+  deps = [
+    ":pw_log_sink",
+    "$dir_pw_log:protos.pwpb",
+  ]
+}
+
+pw_test_group("tests") {
+  tests = [ ":pw_log_sink_test" ]
+}
diff --git a/pw_log_sink/docs.rst b/pw_log_sink/docs.rst
new file mode 100644
index 0000000..0b645b3
--- /dev/null
+++ b/pw_log_sink/docs.rst
@@ -0,0 +1,7 @@
+.. _module-pw_log_sink:
+
+-----------
+pw_log_sink
+-----------
+This is a RPC-based logging backend for Pigweed. It is not ready for use, and
+is under construction.
diff --git a/pw_log_sink/log_sink.cc b/pw_log_sink/log_sink.cc
new file mode 100644
index 0000000..774c390
--- /dev/null
+++ b/pw_log_sink/log_sink.cc
@@ -0,0 +1,137 @@
+// Copyright 2021 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_log_sink/log_sink.h"
+
+#include <atomic>
+#include <cstring>
+#include <mutex>
+
+#include "pw_log/levels.h"
+#include "pw_log_proto/log.pwpb.h"
+#include "pw_protobuf/wire_format.h"
+#include "pw_status/try.h"
+#include "pw_string/string_builder.h"
+#include "pw_sync/interrupt_spin_lock.h"
+
+namespace pw::log_sink {
+namespace {
+// TODO: Make buffer sizes configurable.
+constexpr size_t kMaxMessageStringSize = 32;
+constexpr size_t kEncodeBufferSize = 128;
+
+size_t drop_count = 0;
+
+// The sink list and its corresponding lock are Meyer's singletons, to ensure
+// they are constructed before use. This enables us to use logging before C++
+// global construction has completed.
+IntrusiveList<Sink>& sink_list() {
+  static IntrusiveList<Sink> sink_list;
+  return sink_list;
+}
+
+pw::sync::InterruptSpinLock& sink_list_lock() {
+  // TODO(pwbug/304): Make lock selection configurable, some applications may
+  // not be able to tolerate interrupt jitter and may prefer a pw::sync::Mutex.
+  static pw::sync::InterruptSpinLock sink_list_lock;
+  return sink_list_lock;
+}
+
+}  // namespace
+
+// This is a fully loaded, inefficient-at-the-callsite, log implementation.
+extern "C" void pw_LogSink_Log(int level,
+                               unsigned int flags,
+                               const char* /* module_name */,
+                               const char* /* file_name */,
+                               int line_number,
+                               const char* /* function_name */,
+                               const char* message,
+                               ...) {
+  // Encode message to the LogEntry protobuf.
+  std::byte encode_buffer[kEncodeBufferSize];
+  pw::protobuf::NestedEncoder nested_encoder(encode_buffer);
+  pw::log::LogEntry::Encoder encoder(&nested_encoder);
+
+  encoder.WriteLineLevel(
+      (level & PW_LOG_LEVEL_BITMASK) |
+      ((line_number << PW_LOG_LEVEL_BITWIDTH) & ~PW_LOG_LEVEL_BITMASK));
+  encoder.WriteFlags(flags);
+
+  // TODO(pwbug/301): Insert reasonable values for thread and timestamp.
+  encoder.WriteTimestamp(0);
+
+  // Accumulate the log message in this buffer, then output it.
+  pw::StringBuffer<kMaxMessageStringSize> buffer;
+  va_list args;
+
+  va_start(args, message);
+  buffer.FormatVaList(message, args);
+  va_end(args);
+  encoder.WriteMessageString(buffer.c_str());
+  encoder.WriteThreadString("");
+
+  ConstByteSpan log_entry;
+  Status status = nested_encoder.Encode(&log_entry);
+  bool is_entry_valid = buffer.status().ok() && status.ok();
+
+  // TODO(pwbug/305): Consider using a shared buffer between users. For now,
+  // only lock after completing the encoding.
+  {
+    const std::lock_guard<pw::sync::InterruptSpinLock> lock(sink_list_lock());
+
+    // If no sinks are configured, ignore the message. When sinks are attached,
+    // they will receive this drop count to indicate logs drop to early boot.
+    // The drop count is cleared after it is sent to a sink, so sinks attached
+    // later will not receive drop counts from early boot.
+    if (sink_list().size() == 0) {
+      drop_count++;
+      return;
+    }
+
+    // If an encoding failure occurs or the constructed log entry is larger
+    // than the maximum allowed size, the log is dropped.
+    if (!is_entry_valid) {
+      drop_count++;
+    }
+
+    // Push entries to all attached sinks. This is a synchronous operation, so
+    // attached sinks should avoid blocking when processing entries. If the log
+    // entry is not valid, only the drop notification is sent to the sinks.
+    for (auto& sink : sink_list()) {
+      // The drop count is always provided before sending entries, to ensure the
+      // sink processes drops in-order.
+      if (drop_count > 0) {
+        sink.HandleDropped(drop_count);
+      }
+      if (is_entry_valid) {
+        sink.HandleEntry(log_entry);
+      }
+    }
+    // All sinks have been notified of any drops.
+    drop_count = 0;
+  }
+}
+
+void AddSink(Sink& sink) {
+  const std::lock_guard lock(sink_list_lock());
+  sink_list().push_back(sink);
+}
+
+void RemoveSink(Sink& sink) {
+  const std::lock_guard lock(sink_list_lock());
+  sink_list().remove(sink);
+}
+
+}  // namespace pw::log_sink
diff --git a/pw_log_sink/log_sink_test.cc b/pw_log_sink/log_sink_test.cc
new file mode 100644
index 0000000..52f8bbe
--- /dev/null
+++ b/pw_log_sink/log_sink_test.cc
@@ -0,0 +1,157 @@
+// Copyright 2021 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_log_sink/log_sink.h"
+
+#include <string>
+
+#include "gtest/gtest.h"
+#include "pw_log/levels.h"
+#include "pw_log_proto/log.pwpb.h"
+#include "pw_log_sink/multisink_adapter.h"
+#include "pw_multisink/drain.h"
+#include "pw_multisink/multisink.h"
+#include "pw_protobuf/decoder.h"
+
+namespace pw::log_sink {
+namespace {
+constexpr size_t kMaxTokenizedMessageSize = 512;
+constexpr char kTokenizedMessage[] = "Test Message";
+
+std::string LogMessageToString(ConstByteSpan message) {
+  pw::protobuf::Decoder log_decoder(message);
+  std::string_view log_entry_message;
+
+  EXPECT_TRUE(log_decoder.Next().ok());  // line_level - 2
+  EXPECT_TRUE(log_decoder.Next().ok());  // flags - 3
+  EXPECT_TRUE(log_decoder.Next().ok());  // timestamp - 5
+  EXPECT_TRUE(log_decoder.Next().ok());  // message_string - 16
+  EXPECT_EQ(16U, log_decoder.FieldNumber());
+  EXPECT_TRUE(log_decoder.ReadString(&log_entry_message).ok());
+
+  return std::string(log_entry_message);
+}
+
+void LogMessageForTest(const char* message) {
+  pw_LogSink_Log(0, 0, nullptr, nullptr, 0, nullptr, "%s", message);
+}
+
+void LogInvalidMessageForTest() {
+  char long_message[kMaxTokenizedMessageSize + 1];
+  memset(long_message, 'A', sizeof(long_message));
+  long_message[kMaxTokenizedMessageSize] = '\0';
+
+  pw_LogSink_Log(0, 0, nullptr, nullptr, 0, nullptr, "%s", long_message);
+}
+
+class TestSink final : public Sink {
+ public:
+  void HandleEntry(ConstByteSpan message) final {
+    last_message_string_ = LogMessageToString(message);
+    message_count_++;
+  }
+
+  void HandleDropped(uint32_t count) final { drop_count_ += count; }
+
+  void VerifyMessage(std::string tokenized_message) {
+    EXPECT_EQ(tokenized_message, last_message_string_);
+  }
+
+  uint32_t GetMessageCount() { return message_count_; }
+  uint32_t GetDropCount() { return drop_count_; }
+
+ private:
+  std::string last_message_string_;
+  uint32_t message_count_ = 0;
+  uint32_t drop_count_ = 0;
+};
+
+}  // namespace
+
+TEST(LogSink, NoSink) {
+  // Confirm that the log function does not crash when no sink is present.
+  LogMessageForTest(kTokenizedMessage);
+}
+
+TEST(LogSink, SingleSink) {
+  TestSink test_sink;
+
+  AddSink(test_sink);
+  LogMessageForTest(kTokenizedMessage);
+  test_sink.VerifyMessage(kTokenizedMessage);
+  RemoveSink(test_sink);
+}
+
+TEST(LogSink, MultipleSink) {
+  TestSink test_sink_io;
+  TestSink test_sink_bt;
+
+  AddSink(test_sink_io);
+  AddSink(test_sink_bt);
+  LogMessageForTest(kTokenizedMessage);
+  test_sink_io.VerifyMessage(kTokenizedMessage);
+  test_sink_bt.VerifyMessage(kTokenizedMessage);
+
+  RemoveSink(test_sink_io);
+  LogMessageForTest(kTokenizedMessage);
+  test_sink_io.VerifyMessage(kTokenizedMessage);
+  EXPECT_EQ(test_sink_io.GetMessageCount(), 1U);
+  EXPECT_EQ(test_sink_bt.GetMessageCount(), 2U);
+
+  RemoveSink(test_sink_bt);
+}
+
+TEST(LogSink, DroppedEntries) {
+  TestSink test_sink;
+
+  LogMessageForTest(kTokenizedMessage);
+
+  AddSink(test_sink);
+  LogMessageForTest(kTokenizedMessage);
+  EXPECT_EQ(test_sink.GetMessageCount(), 1U);
+  EXPECT_EQ(test_sink.GetDropCount(), 1U);
+
+  LogInvalidMessageForTest();
+  EXPECT_EQ(test_sink.GetMessageCount(), 1U);
+  EXPECT_EQ(test_sink.GetDropCount(), 2U);
+
+  LogMessageForTest(kTokenizedMessage);
+  EXPECT_EQ(test_sink.GetMessageCount(), 2U);
+  EXPECT_EQ(test_sink.GetDropCount(), 2U);
+
+  RemoveSink(test_sink);
+}
+
+TEST(LogSink, MultiSinkAdapter) {
+  constexpr size_t kMultiSinkBufferSize = 1024;
+  std::byte buffer[kMultiSinkBufferSize];
+  std::byte entry_buffer[kMultiSinkBufferSize];
+  pw::multisink::MultiSink multisink(buffer);
+  pw::multisink::Drain drain;
+  MultiSinkAdapter multisink_adapter(multisink);
+
+  multisink.AttachDrain(drain);
+  LogMessageForTest(kTokenizedMessage);
+
+  AddSink(multisink_adapter);
+  LogMessageForTest(kTokenizedMessage);
+
+  uint32_t drop_count = 0;
+  Result<ConstByteSpan> entry = drain.GetEntry(entry_buffer, drop_count);
+  ASSERT_TRUE(entry.ok());
+  EXPECT_EQ(LogMessageToString(entry.value()), kTokenizedMessage);
+  EXPECT_EQ(drop_count, 1U);
+}
+
+}  // namespace pw::log_sink
diff --git a/pw_log_sink/public/pw_log_sink/log_sink.h b/pw_log_sink/public/pw_log_sink/log_sink.h
new file mode 100644
index 0000000..648f5c2
--- /dev/null
+++ b/pw_log_sink/public/pw_log_sink/log_sink.h
@@ -0,0 +1,70 @@
+// Copyright 2021 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_preprocessor/arguments.h"
+#include "pw_preprocessor/compiler.h"
+#include "pw_preprocessor/util.h"
+
+PW_EXTERN_C_START
+
+// Log a message with the listed attributes.
+void pw_LogSink_Log(int level,
+                    unsigned int flags,
+                    const char* module_name,
+                    const char* file_name,
+                    int line_number,
+                    const char* function_name,
+                    const char* message,
+                    ...) PW_PRINTF_FORMAT(7, 8);
+
+PW_EXTERN_C_END
+
+// Log a message with many attributes included.
+//
+// This is the log macro frontend that funnels everything into the C handler
+// above, pw_LogSink_Log(). It's not efficient at the callsite, since it passes
+// many arguments. Additionally, the use of the __FUNC__ macro adds a static
+// const char[] variable inside functions with a log.
+//
+// TODO(pwbug/87): Reconsider the naming of this module when more is in place.
+#define PW_HANDLE_LOG(level, flags, message, ...)       \
+  do {                                                  \
+    pw_LogSink_Log((level),                             \
+                   (flags),                             \
+                   PW_LOG_MODULE_NAME,                  \
+                   __FILE__,                            \
+                   __LINE__,                            \
+                   __func__,                            \
+                   message PW_COMMA_ARGS(__VA_ARGS__)); \
+  } while (0)
+
+#ifdef __cplusplus
+
+#include <string_view>
+
+#include "pw_bytes/span.h"
+#include "pw_log_sink/sink.h"
+
+namespace pw::log_sink {
+
+// Adds sink interface to list of sinks to push messages to.
+void AddSink(Sink& sink);
+
+// Removes sink interface from list of sinks to push messages to.
+void RemoveSink(Sink& sink);
+
+}  // namespace pw::log_sink
+
+#endif  // __cplusplus
diff --git a/pw_log_sink/public/pw_log_sink/multisink_adapter.h b/pw_log_sink/public/pw_log_sink/multisink_adapter.h
new file mode 100644
index 0000000..af45cb2
--- /dev/null
+++ b/pw_log_sink/public/pw_log_sink/multisink_adapter.h
@@ -0,0 +1,45 @@
+// Copyright 2021 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_log_sink/sink.h"
+#include "pw_multisink/multisink.h"
+
+namespace pw {
+namespace log_sink {
+
+class MultiSinkAdapter final : public Sink {
+ public:
+  MultiSinkAdapter(multisink::MultiSink& multisink) : multisink_(multisink) {}
+
+  // Write an entry to the sink.
+  void HandleEntry(ConstByteSpan entry) final {
+    // Best-effort attempt to send data to the sink, so status is ignored. The
+    // multisink handles failures internally and propagates them to its drains.
+    multisink_.HandleEntry(entry);
+  }
+
+  // Notifies the sink of messages dropped before ingress. The writer may use
+  // this to signal to sinks that an entry (or entries) was lost before sending
+  // to the sink (e.g. the log sink failed to encode the message).
+  void HandleDropped(uint32_t drop_count) final {
+    multisink_.HandleDropped(drop_count);
+  }
+
+ private:
+  multisink::MultiSink& multisink_;
+};
+
+}  // namespace log_sink
+}  // namespace pw
diff --git a/pw_log_sink/public/pw_log_sink/sink.h b/pw_log_sink/public/pw_log_sink/sink.h
new file mode 100644
index 0000000..a85b5b5
--- /dev/null
+++ b/pw_log_sink/public/pw_log_sink/sink.h
@@ -0,0 +1,40 @@
+// Copyright 2021 The Pigweed Authors
+//
+// 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
+//
+//     https://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(pwbug.dev/351): This file is very similar to pw_log_multisink/sink.h,
+// which will be deprecated in a later change.
+
+#include "pw_bytes/span.h"
+#include "pw_containers/intrusive_list.h"
+
+namespace pw {
+namespace log_sink {
+
+class Sink : public IntrusiveList<Sink>::Item {
+ public:
+  virtual ~Sink() = default;
+
+  // Write an entry to the sink. This is a best-effort attempt to send data to
+  // the sink, so failures are ignored.
+  virtual void HandleEntry(ConstByteSpan entry) = 0;
+
+  // Notifies the sink of messages dropped before ingress. The writer may use
+  // this to signal to sinks that an entry (or entries) was lost before sending
+  // to the sink (e.g. the log sink failed to encode the message).
+  virtual void HandleDropped(uint32_t drop_count) = 0;
+};
+
+}  // namespace log_sink
+}  // namespace pw
diff --git a/pw_log_sink/public_overrides/pw_log_backend/log_backend.h b/pw_log_sink/public_overrides/pw_log_backend/log_backend.h
new file mode 100644
index 0000000..c696df7
--- /dev/null
+++ b/pw_log_sink/public_overrides/pw_log_backend/log_backend.h
@@ -0,0 +1,20 @@
+// Copyright 2021 The Pigweed Authors
+//
+// 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
+//
+//     https://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.
+
+// This override header merely points to the true backend, in this case the
+// basic one. The reason to redirect is to permit the use of multiple backends
+// (though only pw_log/log.h can only point to 1 backend).
+#pragma once
+
+#include "pw_log_sink/log_sink.h"
diff --git a/pw_log_string/BUILD.bazel b/pw_log_string/BUILD.bazel
deleted file mode 100644
index 74deb51..0000000
--- a/pw_log_string/BUILD.bazel
+++ /dev/null
@@ -1,55 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_facade",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_log_string",
-    hdrs = [
-        "public/pw_log_string/log_string.h",
-        "public_overrides/pw_log_backend/log_backend.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    deps = [
-        ":handler",
-        "//pw_preprocessor",
-    ],
-)
-
-pw_cc_facade(
-    name = "handler_facade",
-    hdrs = ["public/pw_log_string/handler.h"],
-    includes = ["public"],
-    deps = ["//pw_preprocessor"],
-)
-
-pw_cc_library(
-    name = "handler",
-    srcs = ["handler.cc"],
-    deps = [
-        ":handler_facade",
-        "@pigweed_config//:pw_log_string_handler_backend",
-    ],
-)
diff --git a/pw_log_string/BUILD.gn b/pw_log_string/BUILD.gn
deleted file mode 100644
index 0909a2a..0000000
--- a/pw_log_string/BUILD.gn
+++ /dev/null
@@ -1,94 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/error.gni")
-import("$dir_pw_build/facade.gni")
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_docgen/docs.gni")
-import("backend.gni")
-
-config("public_include_path") {
-  include_dirs = [ "public" ]
-}
-
-config("backend_config") {
-  include_dirs = [ "public_overrides" ]
-}
-
-# This source set only provides pw_log's backend interface by invoking the
-# :handler facade.
-pw_source_set("pw_log_string") {
-  public_configs = [
-    ":backend_config",
-    ":public_include_path",
-  ]
-  public = [
-    "public/pw_log_string/log_string.h",
-    "public_overrides/pw_log_backend/log_backend.h",
-  ]
-  public_deps = [
-    ":handler",
-    "$dir_pw_preprocessor",
-  ]
-}
-
-pw_source_set("pw_log_string.impl") {
-  deps = [ ":handler.impl" ]
-}
-
-# This facade is a C API for string based logging which may be used to back
-# pw_log or for example to mix tokenized and string based logging.
-pw_facade("handler") {
-  backend = pw_log_string_HANDLER_BACKEND
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_log_string/handler.h" ]
-  public_deps = [ "$dir_pw_preprocessor" ]
-  sources = [ "handler.cc" ]
-
-  require_link_deps = [ ":handler.impl" ]
-}
-
-# Logging is low-level and ubiquitous. Because of this, it can often cause
-# circular dependencies. This target collects dependencies from the backend that
-# cannot be used because they would cause circular deps.
-#
-# This group ("$dir_pw_log_string:handler_impl") must be listed in
-# pw_build_LINK_DEPS if pw_log_string_HANDLER_BACKEND is set.
-#
-# pw_log_string:handler backends must provide their own "impl" target that
-# collects their actual dependencies. The backend "impl" group may be empty
-# if everything can go directly in the backend target without causing circular
-# dependencies.
-if (pw_log_string_HANDLER_BACKEND != "") {
-  pw_source_set("handler.impl") {
-    deps = [ get_label_info(pw_log_string_HANDLER_BACKEND,
-                            "label_no_toolchain") + ".impl" ]
-  }
-} else {
-  pw_error("handler.impl") {
-    message =
-        string_join(" ",
-                    [
-                      "To use pw_log_string:handler, please direct",
-                      "pw_log_string_HANDLER_BACKEND to the source set that",
-                      "implements the C API.",
-                    ])
-  }
-}
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
diff --git a/pw_log_string/CMakeLists.txt b/pw_log_string/CMakeLists.txt
deleted file mode 100644
index 87dfabd..0000000
--- a/pw_log_string/CMakeLists.txt
+++ /dev/null
@@ -1,40 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-
-pw_add_module_library(pw_log_string
-  IMPLEMENTS_FACADES
-    pw_log
-  HEADERS
-    public/pw_log_string/log_string.h
-    public_overrides/pw_log_backend/log_backend.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides
-  PUBLIC_DEPS
-    pw_preprocessor
-    pw_log_string.handler
-)
-
-pw_add_facade(pw_log_string.handler
-  HEADERS
-    public/pw_log_string/handler.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_preprocessor
-  SOURCES
-    handler.cc
-)
diff --git a/pw_log_string/OWNERS b/pw_log_string/OWNERS
deleted file mode 100644
index 307b1de..0000000
--- a/pw_log_string/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-amontanez@google.com
diff --git a/pw_log_string/backend.gni b/pw_log_string/backend.gni
deleted file mode 100644
index 7d1cc1b..0000000
--- a/pw_log_string/backend.gni
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-declare_args() {
-  # The pw_log_string:message_handler backend implements the pw_Log C API
-  # which is also used by the pw_log_string module's implementation of the
-  # pw_log facade.
-  pw_log_string_HANDLER_BACKEND = ""
-}
diff --git a/pw_log_string/docs.rst b/pw_log_string/docs.rst
deleted file mode 100644
index d8f4409..0000000
--- a/pw_log_string/docs.rst
+++ /dev/null
@@ -1,36 +0,0 @@
-.. _module-pw_log_string:
-
-=============
-pw_log_string
-=============
-``pw_log_string`` is a partial backend for ``pw_log``. This backend fowards the
-``PW_LOG_*`` macros to the ``pw_log_string:handler`` facade which is backed by
-a C API. ``pw_log_string:handler`` does not implement the full C API, leaving
-projects to provide their own implementation of
-``pw_log_string_HandleMessageVaList``. See ``pw_log_basic`` for a similar
-``pw_log`` backend that also provides an implementation.
-
-As this module passes the log message, file name, and module name as a string to
-the handler function, it's relatively expensive and not well suited for
-space-constrained devices. This module is oriented towards usage on a host
-(e.g. a simulated device).
-
-Note that ``pw_log_string:handler`` may be used even when it's not used
-as the backend for ``pw_log`` via ``pw_log_string``. For example it can be
-useful to mix tokenized and string based logging in case you have a C ABI where
-tokenization can not be used on the other side.
-
----------------
-Getting started
----------------
-This module is extremely minimal to set up:
-
-1. Implement ``pw_log_string_HandleMessageVaList()``
-2. Set ``pw_log_BACKEND`` to ``"$dir_pw_log_string"``
-3. Set ``pw_log_string_HANDLER_BACKEND`` to point to the source set that
-   implements ``pw_log_string_HandleMessageVaList()``
-
-What exactly ``pw_log_string_HandleMessageVaList()`` should do is entirely up to
-the implementation. ``pw_log_basic``'s log handler is one example, but it's also
-possible to encode as protobuf and send over a TCP port, write to a file, or
-blink an LED to log as morse code.
diff --git a/pw_log_string/handler.cc b/pw_log_string/handler.cc
deleted file mode 100644
index 0578a51..0000000
--- a/pw_log_string/handler.cc
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_log_string/handler.h"
-
-#include <cstdarg>
-
-namespace pw::log_string {
-
-extern "C" void pw_log_string_HandleMessage(int level,
-                                            unsigned int flags,
-                                            const char* module_name,
-                                            const char* file_name,
-                                            int line_number,
-                                            const char* message,
-                                            ...) {
-  va_list args;
-  va_start(args, message);
-  pw_log_string_HandleMessageVaList(
-      level, flags, module_name, file_name, line_number, message, args);
-  va_end(args);
-}
-
-}  // namespace pw::log_string
diff --git a/pw_log_string/public/pw_log_string/handler.h b/pw_log_string/public/pw_log_string/handler.h
deleted file mode 100644
index f8c8443..0000000
--- a/pw_log_string/public/pw_log_string/handler.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <stdarg.h>
-
-#include "pw_preprocessor/compiler.h"
-#include "pw_preprocessor/util.h"
-
-PW_EXTERN_C_START
-
-// Invokes pw_log_string_HandleMessageVaList, this is implemented by the facade.
-void pw_log_string_HandleMessage(int level,
-                                 unsigned int flags,
-                                 const char* module_name,
-                                 const char* file_name,
-                                 int line_number,
-                                 const char* message,
-                                 ...) PW_PRINTF_FORMAT(6, 7);
-
-// Log a message with the listed attributes, this must be implemented by the
-// backend.
-void pw_log_string_HandleMessageVaList(int level,
-                                       unsigned int flags,
-                                       const char* module_name,
-                                       const char* file_name,
-                                       int line_number,
-                                       const char* message,
-                                       va_list args);
-
-PW_EXTERN_C_END
diff --git a/pw_log_string/public/pw_log_string/log_string.h b/pw_log_string/public/pw_log_string/log_string.h
deleted file mode 100644
index 4f52919..0000000
--- a/pw_log_string/public/pw_log_string/log_string.h
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_log_string/handler.h"
-#include "pw_preprocessor/arguments.h"
-
-// Log a message with many attributes included. This is a backend implementation
-// for the logging facade in pw_log/log.h.
-//
-// This is the log macro frontend that funnels everything into the C-based
-// message hangler facade, i.e. pw_log_string_HandleMessage. It's not efficient
-// at the callsite, since it passes many arguments.
-#define PW_HANDLE_LOG(level, flags, message, ...)                    \
-  do {                                                               \
-    pw_log_string_HandleMessage((level),                             \
-                                (flags),                             \
-                                PW_LOG_MODULE_NAME,                  \
-                                __FILE__,                            \
-                                __LINE__,                            \
-                                message PW_COMMA_ARGS(__VA_ARGS__)); \
-  } while (0)
diff --git a/pw_log_string/public_overrides/pw_log_backend/log_backend.h b/pw_log_string/public_overrides/pw_log_backend/log_backend.h
deleted file mode 100644
index 6bc92fe..0000000
--- a/pw_log_string/public_overrides/pw_log_backend/log_backend.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_log_string/log_string.h"
diff --git a/pw_log_tokenized/BUILD b/pw_log_tokenized/BUILD
new file mode 100644
index 0000000..41b0747
--- /dev/null
+++ b/pw_log_tokenized/BUILD
@@ -0,0 +1,70 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "headers",
+    hdrs = [
+        "public/pw_log_tokenized/config.h",
+        "public/pw_log_tokenized/log_tokenized.h",
+        "public_overrides/pw_log_backend/log_backend.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        "//pw_tokenizer",
+    ],
+)
+
+pw_cc_library(
+    name = "pw_log_tokenized",
+    deps = [
+        ":headers",
+        "//pw_log:facade",
+    ],
+)
+
+pw_cc_library(
+    name = "base64_over_hdlc",
+    srcs = ["base64_over_hdlc.cc"],
+    hdrs = ["public/pw_log_tokenized/base64_over_hdlc.h"],
+    includes = ["public"],
+    deps = [
+        "//pw_hdlc:encoder",
+        "//pw_tokenizer:base64",
+        "//pw_tokenizer:global_handler_with_payload.facade",
+    ],
+)
+
+pw_cc_test(
+    name = "test",
+    srcs = [
+        "test.cc",
+    ],
+    deps = [
+        ":headers",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_log_tokenized/BUILD.bazel b/pw_log_tokenized/BUILD.bazel
deleted file mode 100644
index 5ac34a6..0000000
--- a/pw_log_tokenized/BUILD.bazel
+++ /dev/null
@@ -1,84 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "headers",
-    hdrs = [
-        "public/pw_log_tokenized/config.h",
-        "public/pw_log_tokenized/log_tokenized.h",
-        "public/pw_log_tokenized/metadata.h",
-        "public_overrides/pw_log_backend/log_backend.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    deps = [
-        "//pw_tokenizer",
-    ],
-)
-
-pw_cc_library(
-    name = "pw_log_tokenized",
-    deps = [
-        ":headers",
-        "//pw_log:facade",
-    ],
-)
-
-pw_cc_library(
-    name = "base64_over_hdlc",
-    srcs = ["base64_over_hdlc.cc"],
-    hdrs = ["public/pw_log_tokenized/base64_over_hdlc.h"],
-    includes = ["public"],
-    deps = [
-        "//pw_hdlc:encoder",
-        "//pw_tokenizer:base64",
-        "//pw_tokenizer:global_handler_with_payload.facade",
-    ],
-)
-
-pw_cc_test(
-    name = "log_tokenized_test",
-    srcs = [
-        "log_tokenized_test.cc",
-        "log_tokenized_test_c.c",
-        "pw_log_tokenized_private/test_utils.h",
-    ],
-    deps = [
-        ":headers",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "metadata_test",
-    srcs = [
-        "metadata_test.cc",
-    ],
-    deps = [
-        ":headers",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_log_tokenized/BUILD.gn b/pw_log_tokenized/BUILD.gn
index 2109673..d51a510 100644
--- a/pw_log_tokenized/BUILD.gn
+++ b/pw_log_tokenized/BUILD.gn
@@ -28,7 +28,7 @@
   pw_log_tokenized_CONFIG = pw_build_DEFAULT_MODULE_CONFIG
 }
 
-config("public_include_path") {
+config("public_includes") {
   include_dirs = [ "public" ]
   visibility = [ ":*" ]
 }
@@ -38,52 +38,44 @@
   visibility = [ ":*" ]
 }
 
-# This target provides the backend for pw_log.
+# This target provides the log_tokenized.h header, which implements the pw_log
+# backend.
+#
+# This target depends on the pw_tokenizer facade target
+# (dir_pw_tokenizer:global_handler_with_payload.facade) to avoid circular
+# dependencies. The dependency graph for pw_log_tokenized is the following:
+#
+#   pw_log:facade <---   :pw_log_tokenized
+#        ^            \      ^       ^
+#        |             \    /         \
+#   -> pw_log -> :log_backend --> pw_tokenizer:global_handler_with_payload
+#
 pw_source_set("pw_log_tokenized") {
-  public_configs = [
-    ":backend_config",
-    ":public_include_path",
-  ]
-  public_deps = [
-    ":config",
-    ":metadata",
-    "$dir_pw_tokenizer:global_handler_with_payload.facade",
-  ]
-  public = [
-    "public/pw_log_tokenized/log_tokenized.h",
-    "public_overrides/pw_log_backend/log_backend.h",
-  ]
-}
-
-pw_source_set("metadata") {
-  public_configs = [ ":public_include_path" ]
-  public_deps = [ ":config" ]
-  public = [ "public/pw_log_tokenized/metadata.h" ]
-}
-
-pw_source_set("config") {
-  public_configs = [ ":public_include_path" ]
+  public_configs = [ ":public_includes" ]
   public_deps = [
     "$dir_pw_log:facade",
+    "$dir_pw_tokenizer:global_handler_with_payload.facade",
+    dir_pw_preprocessor,
     pw_log_tokenized_CONFIG,
   ]
-  public = [ "public/pw_log_tokenized/config.h" ]
+  public = [
+    "public/pw_log_tokenized/config.h",
+    "public/pw_log_tokenized/log_tokenized.h",
+  ]
 }
 
-# The log backend deps that might cause circular dependencies, since
-# pw_log is so ubiquitous. These deps are kept separate so they can be
-# depended on from elsewhere.
-pw_source_set("pw_log_tokenized.impl") {
-  deps = [
-    ":pw_log_tokenized",
-    "$dir_pw_tokenizer:global_handler_with_payload",
-  ]
+# This target provides the backend for pw_log.
+pw_source_set("log_backend") {
+  public_configs = [ ":backend_config" ]
+  public_deps = [ ":pw_log_tokenized" ]
+  public = [ "public_overrides/pw_log_backend/log_backend.h" ]
+  deps = [ "$dir_pw_tokenizer:global_handler_with_payload" ]
 }
 
 # This target provides a backend for pw_tokenizer that encodes tokenized logs as
 # Base64, encodes them into HDLC frames, and writes them over sys_io.
 pw_source_set("base64_over_hdlc") {
-  public_configs = [ ":public_include_path" ]
+  public_configs = [ ":public_includes" ]
   public = [ "public/pw_log_tokenized/base64_over_hdlc.h" ]
   sources = [ "base64_over_hdlc.cc" ]
   deps = [
@@ -95,27 +87,12 @@
 }
 
 pw_test_group("tests") {
-  tests = [
-    ":log_tokenized_test",
-    ":metadata_test",
-  ]
+  tests = [ ":log_tokenized_test" ]
 }
 
 pw_test("log_tokenized_test") {
-  sources = [
-    "log_tokenized_test.cc",
-    "log_tokenized_test_c.c",
-    "pw_log_tokenized_private/test_utils.h",
-  ]
-  deps = [
-    ":pw_log_tokenized",
-    dir_pw_preprocessor,
-  ]
-}
-
-pw_test("metadata_test") {
-  sources = [ "metadata_test.cc" ]
-  deps = [ ":metadata" ]
+  sources = [ "test.cc" ]
+  deps = [ ":pw_log_tokenized" ]
 }
 
 pw_doc_group("docs") {
diff --git a/pw_log_tokenized/CMakeLists.txt b/pw_log_tokenized/CMakeLists.txt
index d051f3b..1c91a64 100644
--- a/pw_log_tokenized/CMakeLists.txt
+++ b/pw_log_tokenized/CMakeLists.txt
@@ -14,80 +14,9 @@
 
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
 
-pw_add_module_config(pw_log_tokenized_CONFIG)
-
-pw_add_module_library(pw_log_tokenized.config
-  HEADERS
-    public/pw_log_tokenized/config.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_log.facade
-    ${pw_log_tokenized_CONFIG}
-)
-
-pw_add_module_library(pw_log_tokenized
-  IMPLEMENTS_FACADES
+pw_auto_add_simple_module(pw_log_tokenized
+  IMPLEMENTS_FACADE
     pw_log
-  HEADERS
-    public/pw_log_tokenized/log_tokenized.h
-    public_overrides/pw_log_backend/log_backend.h
-  PUBLIC_INCLUDES
-    public
   PUBLIC_DEPS
-    pw_log_tokenized.config
-    pw_log_tokenized.metadata
     pw_tokenizer
-  PRIVATE_DEPS
-    pw_tokenizer.global_handler_with_payload
-)
-
-pw_add_module_library(pw_log_tokenized.metadata
-  HEADERS
-    public/pw_log_tokenized/metadata.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_log.facade
-    pw_log_tokenized.config
-)
-
-# This target provides a backend for pw_tokenizer that encodes tokenized logs as
-# Base64, encodes them into HDLC frames, and writes them over sys_io.
-pw_add_module_library(pw_log_tokenized.base64_over_hdlc
-  IMPLEMENTS_FACADES
-    pw_tokenizer.global_handler_with_payload
-  HEADERS
-    public/pw_log_tokenized/base64_over_hdlc.h
-  PUBLIC_INCLUDES
-    public
-  SOURCES
-    base64_over_hdlc.cc
-  PRIVATE_DEPS
-    pw_hdlc.encoder
-    pw_stream.sys_io_stream
-    pw_tokenizer.base64
-)
-
-pw_add_test(pw_log_tokenized.log_tokenized_test
-  SOURCES
-    log_tokenized_test.cc
-    log_tokenized_test_c.c
-    pw_log_tokenized_private/test_utils.h
-  DEPS
-    pw_log_tokenized
-    pw_preprocessor
-  GROUPS
-    modules
-    pw_log_tokenized
-)
-
-pw_add_test(pw_log_tokenized.metadata_test
-  SOURCES
-    metadata_test.cc
-  DEPS
-    pw_log_tokenized.metadata
-  GROUPS
-    modules
-    pw_log_tokenized
 )
diff --git a/pw_log_tokenized/OWNERS b/pw_log_tokenized/OWNERS
deleted file mode 100644
index 05eeac9..0000000
--- a/pw_log_tokenized/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-frolv@google.com
-hepler@google.com
-keir@google.com
diff --git a/pw_log_tokenized/docs.rst b/pw_log_tokenized/docs.rst
index 1aa6267..3c100ed 100644
--- a/pw_log_tokenized/docs.rst
+++ b/pw_log_tokenized/docs.rst
@@ -39,95 +39,6 @@
 
 See the documentation for :ref:`module-pw_tokenizer` for further details.
 
-Metadata in the format string
------------------------------
-With tokenized logging, the log format string is converted to a 32-bit token.
-Regardless of how long the format string is, it's always represented by a 32-bit
-token. Because of this, metadata can be packed into the tokenized string with
-no cost.
-
-``pw_log_tokenized`` uses a simple key-value format to encode metadata in a
-format string. Each field starts with the ``■`` (U+25A0 "Black Square")
-character, followed by the key name, the ``♦`` (U+2666 "Black Diamond Suit")
-character, and then the value. The string is encoded as UTF-8. Key names are
-comprised of alphanumeric ASCII characters and underscore and start with a
-letter.
-
-.. code-block::
-
-  "■key1♦contents1■key2♦contents2■key3♦contents3"
-
-This format makes the message easily machine parseable and human readable. It is
-extremely unlikely to conflict with log message contents due to the characters
-used.
-
-``pw_log_tokenized`` uses three fields: ``msg``, ``module``, and ``file``.
-Implementations may add other fields, but they will be ignored by the
-``pw_log_tokenized`` tooling.
-
-.. code-block::
-
-  "■msg♦Hyperdrive %d set to %f■module♦engine■file♦propulsion/hyper.cc"
-
-Using key-value pairs allows placing the fields in any order.
-``pw_log_tokenized`` places the message first. This is prefered when tokenizing
-C code because the tokenizer only hashes a fixed number of characters. If the
-file were first, the long path might take most of the hashed characters,
-increasing the odds of a collision with other strings in that file. In C++, all
-characters in the string are hashed, so the order is not important.
-
-Metadata in the tokenizer payload argument
--------------------------------------------
-``pw_log_tokenized`` packs runtime-accessible metadata into a 32-bit integer
-which is passed as the "payload" argument for ``pw_log_tokenizer``'s global
-handler with payload facade. Packing this metadata into a single word rather
-than separate arguments reduces the code size significantly.
-
-Four items are packed into the payload argument:
-
-- Log level -- Used for runtime log filtering by level.
-- Line number -- Used to track where a log message originated.
-- Log flags -- Implementation-defined log flags.
-- Tokenized :c:macro:`PW_LOG_MODULE_NAME` -- Used for runtime log filtering by
-  module.
-
-Configuring metadata bit fields
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-The number of bits to use for each metadata field is configurable through macros
-in ``pw_log/config.h``. The field widths must sum to 32 bits. A field with zero
-bits allocated is excluded from the log metadata.
-
-.. c:macro:: PW_LOG_TOKENIZED_LEVEL_BITS
-
-  Bits to allocate for the log level. Defaults to :c:macro:`PW_LOG_LEVEL_BITS`
-  (3).
-
-.. c:macro:: PW_LOG_TOKENIZED_LINE_BITS
-
-  Bits to allocate for the line number. Defaults to 11 (up to line 2047). If the
-  line number is too large to be represented by this field, line is reported as
-  0.
-
-  Including the line number can slightly increase code size. Without the line
-  number, the log metadata argument is the same for all logs with the same level
-  and flags. With the line number, each metadata value is unique and must be
-  encoded as a separate word in the binary. Systems with extreme space
-  constraints may exclude line numbers by setting this macro to 0.
-
-  It is possible to include line numbers in tokenized log format strings, but
-  that is discouraged because line numbers change whenever a file is edited.
-  Passing the line number with the metadata is a lightweight way to include it.
-
-.. c:macro:: PW_LOG_TOKENIZED_FLAG_BITS
-
-  Bits to use for implementation-defined flags. Defaults to 2.
-
-.. c:macro:: PW_LOG_TOKENIZED_MODULE_BITS
-
-  Bits to use for the tokenized version of :c:macro:`PW_LOG_MODULE_NAME`.
-  Defaults to 16, which gives a ~1% probability of a collision with 37 module
-  names.
-
 Using a custom macro
 --------------------
 Applications may use their own macro instead of
@@ -135,19 +46,7 @@
 ``PW_LOG_TOKENIZED_ENCODE_MESSAGE`` config macro. This macro should take
 arguments equivalent to ``PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD``:
 
-.. c:macro:: PW_LOG_TOKENIZED_ENCODE_MESSAGE(log_metadata, message, ...)
-
-  :param log_metadata:
-
-    Packed metadata for the log message. See the Metadata_ class for how to
-    unpack the details.
-
-  :type log_metadata: pw_tokenizer_Payload
-
-  :param message: The log message format string (untokenized)
-  :type message: :c:texpr:`const char*`
-
-  .. _Metadata: https://cs.opensource.google/pigweed/pigweed/+/HEAD:pw_log_tokenized/public/pw_log_tokenized/log_tokenized.h;l=113
+  .. c:function:: PW_LOG_TOKENIZED_ENCODE_MESSAGE(pw_tokenizer_Payload log_metadata, const char* message, ...)
 
 For instructions on how to implement a custom tokenization macro, see
 :ref:`module-pw_tokenizer-custom-macro`.
diff --git a/pw_log_tokenized/log_tokenized_test.cc b/pw_log_tokenized/log_tokenized_test.cc
deleted file mode 100644
index e926749..0000000
--- a/pw_log_tokenized/log_tokenized_test.cc
+++ /dev/null
@@ -1,140 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 PW_LOG_MODULE_NAME "log module name!"
-
-// Configure the module so that the test runs against known values.
-#undef PW_LOG_TOKENIZED_LEVEL_BITS
-#undef PW_LOG_TOKENIZED_MODULE_BITS
-#undef PW_LOG_TOKENIZED_FLAG_BITS
-#undef PW_LOG_TOKENIZED_LINE_BITS
-
-#define PW_LOG_TOKENIZED_LEVEL_BITS 3
-#define PW_LOG_TOKENIZED_MODULE_BITS 16
-#define PW_LOG_TOKENIZED_FLAG_BITS 2
-#define PW_LOG_TOKENIZED_LINE_BITS 11
-
-#include "pw_log_tokenized/log_tokenized.h"
-
-#include "gtest/gtest.h"
-#include "pw_log_tokenized_private/test_utils.h"
-
-namespace pw::log_tokenized {
-namespace {
-
-TEST(LogTokenized, FormatString) {
-  PW_LOG_TOKENIZED_TO_GLOBAL_HANDLER_WITH_PAYLOAD(63, 1023, "hello %d", 1);
-  EXPECT_STREQ(last_log.format_string,
-               "■msg♦hello %d■module♦log module name!■file♦" __FILE__);
-}
-
-constexpr uintptr_t kModuleToken =
-    PW_TOKENIZER_STRING_TOKEN(PW_LOG_MODULE_NAME) &
-    ((1u << PW_LOG_TOKENIZED_MODULE_BITS) - 1);
-
-TEST(LogTokenized, LogMetadata_LevelTooLarge_Clamps) {
-  auto check_metadata = [] {
-    Metadata metadata = Metadata(last_log.metadata);
-    EXPECT_EQ(metadata.level(), 7u);
-    EXPECT_EQ(metadata.flags(), 0u);
-    EXPECT_EQ(metadata.module(), kModuleToken);
-    EXPECT_TRUE(metadata.line_number() == 55u || metadata.line_number() == 45u);
-  };
-
-  PW_LOG_TOKENIZED_TO_GLOBAL_HANDLER_WITH_PAYLOAD(8, 0, "hello");
-  check_metadata();
-
-  pw_log_tokenized_Test_LogMetadata_LevelTooLarge_Clamps();
-  check_metadata();
-}
-
-TEST(LogTokenized, LogMetadata_TooManyFlags_Truncates) {
-  auto check_metadata = [] {
-    Metadata metadata = Metadata(last_log.metadata);
-    EXPECT_EQ(metadata.level(), 1u);
-    EXPECT_EQ(metadata.flags(), 0b11u);
-    EXPECT_EQ(metadata.module(), kModuleToken);
-    EXPECT_TRUE(metadata.line_number() == 71u || metadata.line_number() == 49u);
-  };
-
-  PW_LOG_TOKENIZED_TO_GLOBAL_HANDLER_WITH_PAYLOAD(1, 0xFFFFFFFF, "hello");
-  check_metadata();
-
-  pw_log_tokenized_Test_LogMetadata_TooManyFlags_Truncates();
-  check_metadata();
-}
-
-TEST(LogTokenized, LogMetadata_VariousValues) {
-  auto check_metadata = [] {
-    Metadata metadata = Metadata(last_log.metadata);
-    EXPECT_EQ(metadata.level(), 6u);
-    EXPECT_EQ(metadata.flags(), 3u);
-    EXPECT_EQ(metadata.module(), kModuleToken);
-    EXPECT_EQ(last_log.arg_count, 1u);
-    EXPECT_TRUE(metadata.line_number() == 88u || metadata.line_number() == 53u);
-  };
-
-  PW_LOG_TOKENIZED_TO_GLOBAL_HANDLER_WITH_PAYLOAD(6, 3, "hello%s", "?");
-  check_metadata();
-
-  pw_log_tokenized_Test_LogMetadata_LogMetadata_VariousValues();
-  check_metadata();
-}
-
-TEST(LogTokenized, LogMetadata_Zero) {
-  auto check_metadata = [] {
-    Metadata metadata = Metadata(last_log.metadata);
-    EXPECT_EQ(metadata.level(), 0u);
-    EXPECT_EQ(metadata.flags(), 0u);
-    EXPECT_EQ(metadata.module(), kModuleToken);
-    EXPECT_EQ(last_log.arg_count, 0u);
-    EXPECT_TRUE(metadata.line_number() == 106u ||
-                metadata.line_number() == 57u);
-  };
-
-  PW_LOG_TOKENIZED_TO_GLOBAL_HANDLER_WITH_PAYLOAD(0, 0, "hello");
-  check_metadata();
-
-  pw_log_tokenized_Test_LogMetadata_LogMetadata_Zero();
-  check_metadata();
-}
-
-TEST(LogTokenized, LogMetadata_MaxValues) {
-#line 2047
-  PW_LOG_TOKENIZED_TO_GLOBAL_HANDLER_WITH_PAYLOAD(7, 3, "hello %d", 1);
-
-  Metadata metadata = Metadata(last_log.metadata);
-  EXPECT_EQ(metadata.line_number(), 2047u);
-  EXPECT_EQ(metadata.level(), 7u);
-  EXPECT_EQ(metadata.flags(), 3u);
-  EXPECT_EQ(metadata.module(), kModuleToken);
-  EXPECT_EQ(last_log.arg_count, 1u);
-}
-
-TEST(LogTokenized, LogMetadata_LineNumberTooLarge_IsZero) {
-#line 2048  // At 11 bits, the largest representable line is 2047
-  PW_LOG_TOKENIZED_TO_GLOBAL_HANDLER_WITH_PAYLOAD(7, 3, "hello %d", 1);
-  EXPECT_EQ(Metadata(last_log.metadata).line_number(), 0u);
-
-#line 2049
-  PW_LOG_TOKENIZED_TO_GLOBAL_HANDLER_WITH_PAYLOAD(7, 3, "hello %d", 1);
-  EXPECT_EQ(Metadata(last_log.metadata).line_number(), 0u);
-
-#line 99999
-  PW_LOG_TOKENIZED_TO_GLOBAL_HANDLER_WITH_PAYLOAD(7, 3, "hello %d", 1);
-  EXPECT_EQ(Metadata(last_log.metadata).line_number(), 0u);
-}
-
-}  // namespace
-}  // namespace pw::log_tokenized
diff --git a/pw_log_tokenized/log_tokenized_test_c.c b/pw_log_tokenized/log_tokenized_test_c.c
deleted file mode 100644
index c0b28b7..0000000
--- a/pw_log_tokenized/log_tokenized_test_c.c
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 PW_LOG_MODULE_NAME "log module name!"
-
-// Configure the module so that the test runs against known values.
-#undef PW_LOG_TOKENIZED_LEVEL_BITS
-#undef PW_LOG_TOKENIZED_MODULE_BITS
-#undef PW_LOG_TOKENIZED_FLAG_BITS
-#undef PW_LOG_TOKENIZED_LINE_BITS
-
-#define PW_LOG_TOKENIZED_LEVEL_BITS 3
-#define PW_LOG_TOKENIZED_MODULE_BITS 16
-#define PW_LOG_TOKENIZED_FLAG_BITS 2
-#define PW_LOG_TOKENIZED_LINE_BITS 11
-
-#include "pw_log_tokenized/log_tokenized.h"
-#include "pw_log_tokenized_private/test_utils.h"
-
-pw_log_tokenized_CapturedLog last_log;
-
-void pw_log_tokenized_CaptureArgs(uintptr_t payload,
-                                  size_t arg_count,
-                                  const char* message,
-                                  ...) {
-  last_log.metadata = payload;
-  last_log.format_string = message;
-  last_log.arg_count = arg_count;
-}
-
-// These functions correspond to tests in log_tokenized_test.cc. The tests call
-// these functions and check the results.
-void pw_log_tokenized_Test_LogMetadata_LevelTooLarge_Clamps(void) {
-  PW_LOG_TOKENIZED_TO_GLOBAL_HANDLER_WITH_PAYLOAD(8, 0, "hello");
-}
-
-void pw_log_tokenized_Test_LogMetadata_TooManyFlags_Truncates(void) {
-  PW_LOG_TOKENIZED_TO_GLOBAL_HANDLER_WITH_PAYLOAD(1, 0xFFFFFFFF, "hello");
-}
-
-void pw_log_tokenized_Test_LogMetadata_LogMetadata_VariousValues(void) {
-  PW_LOG_TOKENIZED_TO_GLOBAL_HANDLER_WITH_PAYLOAD(6, 3, "hello%s", "?");
-}
-
-void pw_log_tokenized_Test_LogMetadata_LogMetadata_Zero(void) {
-  PW_LOG_TOKENIZED_TO_GLOBAL_HANDLER_WITH_PAYLOAD(0, 0, "hello");
-}
diff --git a/pw_log_tokenized/metadata_test.cc b/pw_log_tokenized/metadata_test.cc
deleted file mode 100644
index 3a45cee..0000000
--- a/pw_log_tokenized/metadata_test.cc
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_log_tokenized/metadata.h"
-
-#include "gtest/gtest.h"
-
-namespace pw::log_tokenized {
-namespace {
-
-TEST(Metadata, NoLineBits) {
-  using NoLineBits = internal::GenericMetadata<6, 0, 10, 16>;
-
-  constexpr NoLineBits test1 = NoLineBits::Set<0, 0, 0>();
-  static_assert(test1.level() == 0);
-  static_assert(test1.module() == 0);
-  static_assert(test1.flags() == 0);
-  static_assert(test1.line_number() == 0);
-
-  constexpr NoLineBits test2 = NoLineBits::Set<3, 2, 1>();
-  static_assert(test2.level() == 3);
-  static_assert(test2.module() == 2);
-  static_assert(test2.flags() == 1);
-  static_assert(test2.line_number() == 0);
-
-  constexpr NoLineBits test3 = NoLineBits::Set<63, 65535, 1023>();
-  static_assert(test3.level() == 63);
-  static_assert(test3.module() == 65535);
-  static_assert(test3.flags() == 1023);
-  static_assert(test3.line_number() == 0);
-}
-
-TEST(Metadata, NoFlagBits) {
-  using NoFlagBits = internal::GenericMetadata<3, 13, 0, 16>;
-
-  constexpr NoFlagBits test1 = NoFlagBits::Set<0, 0, 0, 0>();
-  static_assert(test1.level() == 0);
-  static_assert(test1.module() == 0);
-  static_assert(test1.flags() == 0);
-  static_assert(test1.line_number() == 0);
-
-  constexpr NoFlagBits test2 = NoFlagBits::Set<3, 2, 0, 1>();
-  static_assert(test2.level() == 3);
-  static_assert(test2.module() == 2);
-  static_assert(test2.flags() == 0);
-  static_assert(test2.line_number() == 1);
-
-  constexpr NoFlagBits test3 = NoFlagBits::Set<7, 65535, 0, (1 << 13) - 1>();
-  static_assert(test3.level() == 7);
-  static_assert(test3.module() == 65535);
-  static_assert(test3.flags() == 0);
-  static_assert(test3.line_number() == (1 << 13) - 1);
-}
-
-}  // namespace
-}  // namespace pw::log_tokenized
diff --git a/pw_log_tokenized/public/pw_log_tokenized/config.h b/pw_log_tokenized/public/pw_log_tokenized/config.h
index de33510..9c92d9f 100644
--- a/pw_log_tokenized/public/pw_log_tokenized/config.h
+++ b/pw_log_tokenized/public/pw_log_tokenized/config.h
@@ -15,63 +15,39 @@
 
 #include <assert.h>
 
-#include "pw_log/levels.h"
 #include "pw_log/options.h"
+#include "pw_preprocessor/concat.h"
 
 // This macro takes the PW_LOG format string and optionally transforms it. By
-// default, pw_log_tokenized specifies three fields as key-value pairs.
+// default, the PW_LOG_MODULE_NAME is prepended to the string if present.
 #ifndef PW_LOG_TOKENIZED_FORMAT_STRING
 
-#define _PW_LOG_TOKENIZED_FIELD(name, contents) "■" name "♦" contents
+#define PW_LOG_TOKENIZED_FORMAT_STRING(string) \
+  PW_CONCAT(PW_LOG_TOKENIZED_FMT_, PW_LOG_MODULE_NAME_DEFINED)(string)
 
-#define PW_LOG_TOKENIZED_FORMAT_STRING(string)          \
-  _PW_LOG_TOKENIZED_FIELD("msg", string)                \
-  _PW_LOG_TOKENIZED_FIELD("module", PW_LOG_MODULE_NAME) \
-  _PW_LOG_TOKENIZED_FIELD("file", __FILE__)
+#define PW_LOG_TOKENIZED_FMT_0(string) string
+#define PW_LOG_TOKENIZED_FMT_1(string) PW_LOG_MODULE_NAME " " string
 
 #endif  // PW_LOG_TOKENIZED_FORMAT_STRING
 
-// The log level, line number, flag bits, and module token are packed into the
-// tokenizer's payload argument, which is typically 32 bits. These macros
-// specify the number of bits to use for each field. A field with zero bits is
-// excluded.
-
-// Bits to allocate for the log level.
+// The log level, module token, and flag bits are packed into the tokenizer's
+// payload argument, which is typically 32 bits. These macros specify the number
+// of bits to use for each field.
 #ifndef PW_LOG_TOKENIZED_LEVEL_BITS
-#define PW_LOG_TOKENIZED_LEVEL_BITS PW_LOG_LEVEL_BITS
+#define PW_LOG_TOKENIZED_LEVEL_BITS 6
 #endif  // PW_LOG_TOKENIZED_LEVEL_BITS
 
-// Bits to allocate for the line number. Defaults to 11 (up to line 2047). If
-// the line number is too large to be represented by this field, line is
-// reported as 0.
-//
-// Including the line number can slightly increase code size. Without the line
-// number, the log metadata argument is the same for all logs with the same
-// level and flags. With the line number, each metadata value is unique and must
-// be encoded as a separate word in the binary. Systems with extreme space
-// constraints may exclude line numbers by setting this macro to 0.
-//
-// It is possible to include line numbers in tokenized log format strings, but
-// that is discouraged because line numbers change whenever a file is edited.
-// Passing the line number with the metadata is a lightweight way to include it.
-#ifndef PW_LOG_TOKENIZED_LINE_BITS
-#define PW_LOG_TOKENIZED_LINE_BITS 11
-#endif  // PW_LOG_TOKENIZED_LINE_BITS
-
-// Bits to use for implementation-defined flags.
-#ifndef PW_LOG_TOKENIZED_FLAG_BITS
-#define PW_LOG_TOKENIZED_FLAG_BITS 2
-#endif  // PW_LOG_TOKENIZED_FLAG_BITS
-
-// Bits to use for the tokenized version of PW_LOG_MODULE_NAME. Defaults to 16,
-// which gives a ~1% probability of a collision with 37 module names.
 #ifndef PW_LOG_TOKENIZED_MODULE_BITS
 #define PW_LOG_TOKENIZED_MODULE_BITS 16
 #endif  // PW_LOG_TOKENIZED_MODULE_BITS
 
-static_assert((PW_LOG_TOKENIZED_LEVEL_BITS + PW_LOG_TOKENIZED_LINE_BITS +
-               PW_LOG_TOKENIZED_FLAG_BITS + PW_LOG_TOKENIZED_MODULE_BITS) == 32,
-              "Log metadata fields must use 32 bits");
+#ifndef PW_LOG_TOKENIZED_FLAG_BITS
+#define PW_LOG_TOKENIZED_FLAG_BITS 10
+#endif  // PW_LOG_TOKENIZED_FLAG_BITS
+
+static_assert((PW_LOG_TOKENIZED_LEVEL_BITS + PW_LOG_TOKENIZED_MODULE_BITS +
+               PW_LOG_TOKENIZED_FLAG_BITS) == 32,
+              "Log metadata must fit in a 32-bit integer");
 
 // The macro to use to tokenize the log and its arguments. Defaults to
 // PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD. Projects may define their own
diff --git a/pw_log_tokenized/public/pw_log_tokenized/log_tokenized.h b/pw_log_tokenized/public/pw_log_tokenized/log_tokenized.h
index 110d542..34acb74 100644
--- a/pw_log_tokenized/public/pw_log_tokenized/log_tokenized.h
+++ b/pw_log_tokenized/public/pw_log_tokenized/log_tokenized.h
@@ -18,11 +18,6 @@
 #include "pw_log_tokenized/config.h"
 #include "pw_tokenizer/tokenize_to_global_handler_with_payload.h"
 
-// TODO(hepler): Remove this include.
-#ifdef __cplusplus
-#include "pw_log_tokenized/metadata.h"
-#endif  // __cplusplus
-
 // This macro implements PW_LOG using
 // PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD or an equivalent alternate macro
 // provided by PW_LOG_TOKENIZED_ENCODE_MESSAGE. The log level, module token, and
@@ -50,54 +45,76 @@
 #define PW_LOG_TOKENIZED_TO_GLOBAL_HANDLER_WITH_PAYLOAD(                     \
     level, flags, message, ...)                                              \
   do {                                                                       \
-    _PW_TOKENIZER_CONST uintptr_t _pw_log_tokenized_module_token =           \
+    _PW_TOKENIZER_CONST uintptr_t _pw_log_module_token =                     \
         PW_TOKENIZE_STRING_MASK("pw_log_module_names",                       \
                                 ((1u << PW_LOG_TOKENIZED_MODULE_BITS) - 1u), \
                                 PW_LOG_MODULE_NAME);                         \
-    const uintptr_t _pw_log_tokenized_level = level;                         \
     PW_LOG_TOKENIZED_ENCODE_MESSAGE(                                         \
-        (_PW_LOG_TOKENIZED_LEVEL(_pw_log_tokenized_level) |                  \
-         _PW_LOG_TOKENIZED_MODULE(_pw_log_tokenized_module_token) |          \
-         _PW_LOG_TOKENIZED_FLAGS(flags) | _PW_LOG_TOKENIZED_LINE(__LINE__)), \
+        ((uintptr_t)(level) |                                                \
+         (_pw_log_module_token << PW_LOG_TOKENIZED_LEVEL_BITS) |             \
+         ((uintptr_t)(flags)                                                 \
+          << (PW_LOG_TOKENIZED_LEVEL_BITS + PW_LOG_TOKENIZED_MODULE_BITS))), \
         PW_LOG_TOKENIZED_FORMAT_STRING(message),                             \
         __VA_ARGS__);                                                        \
   } while (0)
 
-// If the level field is present, clamp it to the maximum value.
-#if PW_LOG_TOKENIZED_LEVEL_BITS == 0
-#define _PW_LOG_TOKENIZED_LEVEL(value) ((uintptr_t)0)
-#else
-#define _PW_LOG_TOKENIZED_LEVEL(value)                   \
-  (value < ((uintptr_t)1 << PW_LOG_TOKENIZED_LEVEL_BITS) \
-       ? value                                           \
-       : ((uintptr_t)1 << PW_LOG_TOKENIZED_LEVEL_BITS) - 1)
-#endif  // PW_LOG_TOKENIZED_LEVEL_BITS
+#ifdef __cplusplus
 
-// If the line number field is present, shift it to its position. Set it to zero
-// if the line number is too large for PW_LOG_TOKENIZED_LINE_BITS.
-#if PW_LOG_TOKENIZED_LINE_BITS == 0
-#define _PW_LOG_TOKENIZED_LINE(line) ((uintptr_t)0)
-#else
-#define _PW_LOG_TOKENIZED_LINE(line)                                \
-  ((uintptr_t)(line < (1 << PW_LOG_TOKENIZED_LINE_BITS) ? line : 0) \
-   << PW_LOG_TOKENIZED_LEVEL_BITS)
-#endif  // PW_LOG_TOKENIZED_LINE_BITS
+namespace pw {
+namespace log_tokenized {
+namespace internal {
 
-// If the flags field is present, mask it and shift it to its position.
-#if PW_LOG_TOKENIZED_FLAG_BITS == 0
-#define _PW_LOG_TOKENIZED_FLAGS(value) ((uintptr_t)0)
-#else
-#define _PW_LOG_TOKENIZED_FLAGS(value)                                       \
-  (((uintptr_t)(value) & (((uintptr_t)1 << PW_LOG_TOKENIZED_FLAG_BITS) - 1)) \
-   << (PW_LOG_TOKENIZED_LEVEL_BITS + PW_LOG_TOKENIZED_LINE_BITS))
-#endif  // PW_LOG_TOKENIZED_FLAG_BITS
+// This class, which is aliased to pw::log_tokenized::Metadata below, is used to
+// access the log metadata packed into the tokenizer's payload argument.
+template <unsigned kLevelBits,
+          unsigned kModuleBits,
+          unsigned kFlagBits,
+          typename T = uintptr_t>
+class GenericMetadata {
+ public:
+  template <T log_level, T module, T flags>
+  static constexpr GenericMetadata Set() {
+    static_assert(log_level < (1 << kLevelBits), "The level is too large!");
+    static_assert(module < (1 << kModuleBits), "The module is too large!");
+    static_assert(flags < (1 << kFlagBits), "The flags are too large!");
 
-// If the module field is present, shift it to its position.
-#if PW_LOG_TOKENIZED_MODULE_BITS == 0
-#define _PW_LOG_TOKENIZED_MODULE(value) ((uintptr_t)0)
-#else
-#define _PW_LOG_TOKENIZED_MODULE(value)                  \
-  ((uintptr_t)(value) << ((PW_LOG_TOKENIZED_LEVEL_BITS + \
-                           PW_LOG_TOKENIZED_LINE_BITS +  \
-                           PW_LOG_TOKENIZED_FLAG_BITS)))
-#endif  // PW_LOG_TOKENIZED_MODULE_BITS
+    return GenericMetadata(log_level | (module << kLevelBits) |
+                           (flags << (kModuleBits + kLevelBits)));
+  }
+
+  constexpr GenericMetadata(T value) : bits_(value) {}
+
+  // The log level of this message.
+  constexpr T level() const { return bits_ & Mask<kLevelBits>(); }
+
+  // The 16 bit tokenized version of the module name (PW_LOG_MODULE_NAME).
+  constexpr T module() const {
+    return (bits_ >> kLevelBits) & Mask<kModuleBits>();
+  }
+
+  // The flags provided to the log call.
+  constexpr T flags() const {
+    return (bits_ >> (kLevelBits + kModuleBits)) & Mask<kFlagBits>();
+  }
+
+ private:
+  template <int bits>
+  static constexpr T Mask() {
+    return (T(1) << bits) - 1;
+  }
+
+  T bits_;
+
+  static_assert(kLevelBits + kModuleBits + kFlagBits <= sizeof(bits_) * 8);
+};
+
+}  // namespace internal
+
+using Metadata = internal::GenericMetadata<PW_LOG_TOKENIZED_LEVEL_BITS,
+                                           PW_LOG_TOKENIZED_MODULE_BITS,
+                                           PW_LOG_TOKENIZED_FLAG_BITS>;
+
+}  // namespace log_tokenized
+}  // namespace pw
+
+#endif  // __cpluplus
diff --git a/pw_log_tokenized/public/pw_log_tokenized/metadata.h b/pw_log_tokenized/public/pw_log_tokenized/metadata.h
deleted file mode 100644
index 86e6364..0000000
--- a/pw_log_tokenized/public/pw_log_tokenized/metadata.h
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_log_tokenized/config.h"
-
-namespace pw {
-namespace log_tokenized {
-namespace internal {
-
-// Internal class for managing the metadata bit fields.
-template <typename T, unsigned kBits, unsigned kShift>
-struct BitField {
- public:
-  static constexpr T Get(T value) { return (value >> kShift) & kMask; }
-  static constexpr T Shift(T value) {
-    return (value <= kMask ? value : T(0)) << kShift;
-  }
-
- private:
-  static constexpr T kMask = (T(1) << kBits) - 1;
-};
-
-template <typename T, unsigned kShift>
-class BitField<T, 0, kShift> {
- public:
-  static constexpr T Get(T) { return 0; }
-  static constexpr T Shift(T) { return 0; }
-};
-
-// This class, which is aliased to pw::log_tokenized::Metadata below, is used to
-// access the log metadata packed into the tokenizer's payload argument.
-template <unsigned kLevelBits,
-          unsigned kLineBits,
-          unsigned kFlagBits,
-          unsigned kModuleBits,
-          typename T = uintptr_t>
-class GenericMetadata {
- public:
-  template <T log_level = 0, T module = 0, T flags = 0, T line = 0>
-  static constexpr GenericMetadata Set() {
-    static_assert(log_level < (1 << kLevelBits), "The level is too large!");
-    static_assert(line < (1 << kLineBits), "The line number is too large!");
-    static_assert(flags < (1 << kFlagBits), "The flags are too large!");
-    static_assert(module < (1 << kModuleBits), "The module is too large!");
-
-    return GenericMetadata(Level::Shift(log_level) | Module::Shift(module) |
-                           Flags::Shift(flags) | Line::Shift(line));
-  }
-
-  constexpr GenericMetadata(T value) : bits_(value) {}
-
-  // The log level of this message.
-  constexpr T level() const { return Level::Get(bits_); }
-
-  // The line number of the log call. The first line in a file is 1. If the line
-  // number is 0, it was too large to be stored.
-  constexpr T line_number() const { return Line::Get(bits_); }
-
-  // The flags provided to the log call.
-  constexpr T flags() const { return Flags::Get(bits_); }
-
-  // The 16 bit tokenized version of the module name (PW_LOG_MODULE_NAME).
-  constexpr T module() const { return Module::Get(bits_); }
-
- private:
-  using Level = BitField<T, kLevelBits, 0>;
-  using Line = BitField<T, kLineBits, kLevelBits>;
-  using Flags = BitField<T, kFlagBits, kLevelBits + kLineBits>;
-  using Module = BitField<T, kModuleBits, kLevelBits + kLineBits + kFlagBits>;
-
-  T bits_;
-
-  static_assert(kLevelBits + kLineBits + kFlagBits + kModuleBits <=
-                sizeof(bits_) * 8);
-};
-
-}  // namespace internal
-
-using Metadata = internal::GenericMetadata<PW_LOG_TOKENIZED_LEVEL_BITS,
-                                           PW_LOG_TOKENIZED_LINE_BITS,
-                                           PW_LOG_TOKENIZED_FLAG_BITS,
-                                           PW_LOG_TOKENIZED_MODULE_BITS>;
-
-}  // namespace log_tokenized
-}  // namespace pw
diff --git a/pw_log_tokenized/public_overrides/pw_log_backend/log_backend.h b/pw_log_tokenized/public_overrides/pw_log_backend/log_backend.h
index 57dedea..7b1cac1 100644
--- a/pw_log_tokenized/public_overrides/pw_log_backend/log_backend.h
+++ b/pw_log_tokenized/public_overrides/pw_log_backend/log_backend.h
@@ -21,4 +21,5 @@
 
 #define PW_HANDLE_LOG PW_LOG_TOKENIZED_TO_GLOBAL_HANDLER_WITH_PAYLOAD
 
+#define PW_LOG_LEVEL_BITS PW_LOG_TOKENIZED_LEVEL_BITS
 #define PW_LOG_FLAG_BITS PW_LOG_TOKENIZED_FLAG_BITS
diff --git a/pw_log_tokenized/pw_log_tokenized_private/test_utils.h b/pw_log_tokenized/pw_log_tokenized_private/test_utils.h
deleted file mode 100644
index dce3a3d..0000000
--- a/pw_log_tokenized/pw_log_tokenized_private/test_utils.h
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <stddef.h>
-#include <stdint.h>
-
-#include "pw_log_tokenized/log_tokenized.h"
-#include "pw_preprocessor/arguments.h"
-#include "pw_preprocessor/compiler.h"
-#include "pw_preprocessor/util.h"
-
-// Create a fake version of the tokenization macro.
-#undef PW_LOG_TOKENIZED_ENCODE_MESSAGE
-#define PW_LOG_TOKENIZED_ENCODE_MESSAGE(payload, message, ...)  \
-  pw_log_tokenized_CaptureArgs(payload,                         \
-                               PW_MACRO_ARG_COUNT(__VA_ARGS__), \
-                               message PW_COMMA_ARGS(__VA_ARGS__))
-
-PW_EXTERN_C_START
-
-typedef struct {
-  uintptr_t metadata;
-  const char* format_string;
-  size_t arg_count;
-} pw_log_tokenized_CapturedLog;
-
-extern pw_log_tokenized_CapturedLog last_log;
-
-void pw_log_tokenized_CaptureArgs(uintptr_t payload,
-                                  size_t arg_count,
-                                  const char* message,
-                                  ...) PW_PRINTF_FORMAT(3, 4);
-
-// C versions of tests.
-void pw_log_tokenized_Test_LogMetadata_LevelTooLarge_Clamps(void);
-void pw_log_tokenized_Test_LogMetadata_TooManyFlags_Truncates(void);
-void pw_log_tokenized_Test_LogMetadata_LogMetadata_VariousValues(void);
-void pw_log_tokenized_Test_LogMetadata_LogMetadata_Zero(void);
-
-PW_EXTERN_C_END
diff --git a/pw_log_tokenized/py/BUILD.gn b/pw_log_tokenized/py/BUILD.gn
index 8931cb2..f8ebd18 100644
--- a/pw_log_tokenized/py/BUILD.gn
+++ b/pw_log_tokenized/py/BUILD.gn
@@ -17,15 +17,7 @@
 import("$dir_pw_build/python.gni")
 
 pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
+  setup = [ "setup.py" ]
   sources = [ "pw_log_tokenized/__init__.py" ]
-  tests = [
-    "format_string_test.py",
-    "metadata_test.py",
-  ]
   pylintrc = "$dir_pigweed/.pylintrc"
 }
diff --git a/pw_log_tokenized/py/format_string_test.py b/pw_log_tokenized/py/format_string_test.py
deleted file mode 100644
index af49bf2..0000000
--- a/pw_log_tokenized/py/format_string_test.py
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests decoding metadata from log strings."""
-
-import unittest
-
-from pw_log_tokenized import FormatStringWithMetadata
-
-
-class TestFormatStringWithMetadata(unittest.TestCase):
-    """Tests extracting metadata from a pw_log_tokenized-style format string."""
-    def test_all_fields(self):
-        log = FormatStringWithMetadata(
-            '■msg♦hello %d■file♦__FILE__■module♦log module name!')
-        self.assertEqual(log.message, 'hello %d')
-        self.assertEqual(log.module, 'log module name!')
-        self.assertEqual(log.file, '__FILE__')
-
-    def test_different_fields(self):
-        log = FormatStringWithMetadata('■msg♦hello %d■module♦■THING♦abc123')
-        self.assertEqual(log.message, 'hello %d')
-        self.assertEqual(log.module, '')
-        self.assertEqual(log.file, '')
-        self.assertEqual(log.fields['THING'], 'abc123')
-
-    def test_no_metadata(self):
-        log = FormatStringWithMetadata('a■msg♦not formatted correctly')
-        self.assertEqual(log.message, log.raw_string)
-        self.assertEqual(log.module, '')
-        self.assertEqual(log.file, '')
-
-    def test_invalid_field_name(self):
-        log = FormatStringWithMetadata('■msg♦M♦S♦G■1abc♦abc■other♦hi')
-        self.assertEqual(log.message, 'M♦S♦G■1abc♦abc')
-        self.assertEqual(log.fields['other'], 'hi')
-
-    def test_delimiters_in_value(self):
-        log = FormatStringWithMetadata('■msg♦♦■♦■yo■module♦M♦DU■E')
-        self.assertEqual(log.message, '♦■♦■yo')
-        self.assertEqual(log.module, 'M♦DU■E')
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_log_tokenized/py/metadata_test.py b/pw_log_tokenized/py/metadata_test.py
deleted file mode 100644
index 1073a92..0000000
--- a/pw_log_tokenized/py/metadata_test.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests for the pw_log_tokenized packed metadata class."""
-
-import unittest
-
-from pw_log_tokenized import Metadata
-
-
-class TestMetadata(unittest.TestCase):
-    """Tests extracting fields from a pw_log_tokenized packed metadata value."""
-    def test_zero(self):
-        metadata = Metadata(0)
-        self.assertEqual(metadata.log_level, 0)
-        self.assertEqual(metadata.line, 0)
-        self.assertEqual(metadata.flags, 0)
-        self.assertEqual(metadata.module_token, 0)
-
-    def test_various(self):
-        metadata = Metadata(0xABCD << 16 | 1 << 14 | 1234 << 3 | 5,
-                            log_bits=3,
-                            line_bits=11,
-                            flag_bits=2,
-                            module_bits=16)
-        self.assertEqual(metadata.log_level, 5)
-        self.assertEqual(metadata.line, 1234)
-        self.assertEqual(metadata.flags, 1)
-        self.assertEqual(metadata.module_token, 0xABCD)
-
-    def test_max(self):
-        metadata = Metadata(0xFFFFFFFF,
-                            log_bits=3,
-                            line_bits=11,
-                            flag_bits=2,
-                            module_bits=16)
-        self.assertEqual(metadata.log_level, 7)
-        self.assertEqual(metadata.line, 2047)
-        self.assertEqual(metadata.flags, 3)
-        self.assertEqual(metadata.module_token, 0xFFFF)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_log_tokenized/py/pw_log_tokenized/__init__.py b/pw_log_tokenized/py/pw_log_tokenized/__init__.py
index d0cc1d1..5c46b7b 100644
--- a/pw_log_tokenized/py/pw_log_tokenized/__init__.py
+++ b/pw_log_tokenized/py/pw_log_tokenized/__init__.py
@@ -14,8 +14,6 @@
 """Tools for working with tokenized logs."""
 
 from dataclasses import dataclass
-import re
-from typing import Dict, Mapping
 
 
 def _mask(value: int, start: int, count: int) -> int:
@@ -23,57 +21,21 @@
     return (value & (mask << start)) >> start
 
 
+@dataclass(frozen=True)
 class Metadata:
-    """Parses the metadata payload used by pw_log_tokenized."""
-    def __init__(self,
-                 value: int,
-                 *,
-                 log_bits: int = 3,
-                 line_bits: int = 11,
-                 flag_bits: int = 2,
-                 module_bits: int = 16) -> None:
-        self.value = value
+    """Parses the metadata payload sent by pw_log_tokenized."""
+    _value: int
 
-        self.log_level = _mask(value, 0, log_bits)
-        self.line = _mask(value, log_bits, line_bits)
-        self.flags = _mask(value, log_bits + line_bits, flag_bits)
-        self.module_token = _mask(value, log_bits + line_bits + flag_bits,
-                                  module_bits)
+    log_bits: int = 6
+    module_bits: int = 16
+    flag_bits: int = 10
 
-    def __repr__(self) -> str:
-        return (f'{type(self).__name__}('
-                f'log_level={self.log_level}, '
-                f'line={self.line}, '
-                f'flags={self.flags}, '
-                f'module_token={self.module_token})')
+    def log_level(self) -> int:
+        return _mask(self._value, 0, self.log_bits)
 
+    def module_token(self) -> int:
+        return _mask(self._value, self.log_bits, self.module_bits)
 
-class FormatStringWithMetadata:
-    """Parses metadata from a log format string with metadata fields."""
-    _FIELD_KEY = re.compile(r'■([a-zA-Z]\w*)♦', flags=re.ASCII)
-
-    def __init__(self, string: str) -> None:
-        self.raw_string = string
-        self.fields: Dict[str, str] = {}
-
-        # Only look for fields if the raw string starts with one.
-        if self._FIELD_KEY.match(self.raw_string):
-            fields = self._FIELD_KEY.split(self.raw_string)[1:]
-            for name, value in zip(fields[::2], fields[1::2]):
-                self.fields[name] = value
-
-    @property
-    def message(self) -> str:
-        """Displays the msg field or the whole string if it is not present."""
-        return self.fields.get('msg', self.raw_string)
-
-    @property
-    def module(self) -> str:
-        return self.fields.get('module', '')
-
-    @property
-    def file(self) -> str:
-        return self.fields.get('file', '')
-
-    def __repr__(self) -> str:
-        return self.message
+    def flags(self) -> int:
+        return _mask(self._value, self.log_bits + self.module_bits,
+                     self.flag_bits)
diff --git a/pw_log_tokenized/py/pyproject.toml b/pw_log_tokenized/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/pw_log_tokenized/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/pw_log_tokenized/py/setup.cfg b/pw_log_tokenized/py/setup.cfg
deleted file mode 100644
index ed0cb53..0000000
--- a/pw_log_tokenized/py/setup.cfg
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pw_log_tokenized
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Tools for working with tokenized logs
-
-[options]
-packages = find:
-zip_safe = False
-
-[options.package_data]
-pw_log_tokenized = py.typed
diff --git a/pw_log_tokenized/py/setup.py b/pw_log_tokenized/py/setup.py
index 686aeb7..f1ca09d 100644
--- a/pw_log_tokenized/py/setup.py
+++ b/pw_log_tokenized/py/setup.py
@@ -11,8 +11,17 @@
 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 # License for the specific language governing permissions and limitations under
 # the License.
-"""pw_log_tokenized"""
+"""The pw_log_tokenized package."""
 
 import setuptools  # type: ignore
 
-setuptools.setup()  # Package definition in setup.cfg
+setuptools.setup(
+    name='pw_log_tokenized',
+    version='0.0.1',
+    author='Pigweed Authors',
+    author_email='pigweed-developers@googlegroups.com',
+    description='Tools for working with tokenized logs',
+    packages=setuptools.find_packages(),
+    package_data={'pw_log_tokenized': ['py.typed']},
+    zip_safe=False,
+)
diff --git a/pw_log_tokenized/test.cc b/pw_log_tokenized/test.cc
new file mode 100644
index 0000000..7a843f4
--- /dev/null
+++ b/pw_log_tokenized/test.cc
@@ -0,0 +1,100 @@
+// Copyright 2021 The Pigweed Authors
+//
+// 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
+//
+//     https://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 PW_LOG_MODULE_NAME "This is the log module name!"
+
+// Create a fake version of the tokenization macro.
+#undef PW_LOG_TOKENIZED_ENCODE_MESSAGE
+#define PW_LOG_TOKENIZED_ENCODE_MESSAGE(payload, message, ...) \
+  CaptureTokenizerArgs(payload,                                \
+                       PW_MACRO_ARG_COUNT(__VA_ARGS__),        \
+                       message PW_COMMA_ARGS(__VA_ARGS__))
+
+#include <cstring>
+#include <string_view>
+#include <tuple>
+
+#include "gtest/gtest.h"
+#include "pw_log_tokenized/log_tokenized.h"
+#include "pw_preprocessor/arguments.h"
+#include "pw_preprocessor/compiler.h"
+
+namespace pw::log_tokenized {
+namespace {
+
+struct {
+  Metadata metadata = Metadata(0);
+  const char* format_string = "";
+  size_t arg_count = 0;
+} last_log{};
+
+void CaptureTokenizerArgs(pw_tokenizer_Payload payload,
+                          size_t arg_count,
+                          const char* message,
+                          ...) PW_PRINTF_FORMAT(3, 4);
+
+void CaptureTokenizerArgs(pw_tokenizer_Payload payload,
+                          size_t arg_count,
+                          const char* message,
+                          ...) {
+  last_log.metadata = payload;
+  last_log.format_string = message;
+  last_log.arg_count = arg_count;
+}
+
+constexpr uintptr_t kModuleToken =
+    PW_TOKENIZER_STRING_TOKEN(PW_LOG_MODULE_NAME) &
+    ((1u << PW_LOG_TOKENIZED_MODULE_BITS) - 1);
+
+constexpr Metadata test1 = Metadata::Set<0, 0, 0>();
+static_assert(test1.level() == 0);
+static_assert(test1.module() == 0);
+static_assert(test1.flags() == 0);
+
+constexpr Metadata test2 = Metadata::Set<3, 2, 1>();
+static_assert(test2.level() == 3);
+static_assert(test2.module() == 2);
+static_assert(test2.flags() == 1);
+
+constexpr Metadata test3 = Metadata::Set<63, 65535, 1023>();
+static_assert(test3.level() == 63);
+static_assert(test3.module() == 65535);
+static_assert(test3.flags() == 1023);
+
+TEST(LogTokenized, LogMetadata_Zero) {
+  PW_LOG_TOKENIZED_TO_GLOBAL_HANDLER_WITH_PAYLOAD(0, 0, "hello");
+  EXPECT_EQ(last_log.metadata.level(), 0u);
+  EXPECT_EQ(last_log.metadata.flags(), 0u);
+  EXPECT_EQ(last_log.metadata.module(), kModuleToken);
+  EXPECT_EQ(last_log.arg_count, 0u);
+}
+
+TEST(LogTokenized, LogMetadata_DifferentValues) {
+  PW_LOG_TOKENIZED_TO_GLOBAL_HANDLER_WITH_PAYLOAD(55, 36, "hello%s", "?");
+  EXPECT_EQ(last_log.metadata.level(), 55u);
+  EXPECT_EQ(last_log.metadata.flags(), 36u);
+  EXPECT_EQ(last_log.metadata.module(), kModuleToken);
+  EXPECT_EQ(last_log.arg_count, 1u);
+}
+
+TEST(LogTokenized, LogMetadata_MaxValues) {
+  PW_LOG_TOKENIZED_TO_GLOBAL_HANDLER_WITH_PAYLOAD(63, 1023, "hello %d", 1);
+  EXPECT_EQ(last_log.metadata.level(), 63u);
+  EXPECT_EQ(last_log.metadata.flags(), 1023u);
+  EXPECT_EQ(last_log.metadata.module(), kModuleToken);
+  EXPECT_EQ(last_log.arg_count, 1u);
+}
+
+}  // namespace
+}  // namespace pw::log_tokenized
diff --git a/pw_log_zephyr/BUILD.gn b/pw_log_zephyr/BUILD.gn
deleted file mode 100644
index 68ffb75..0000000
--- a/pw_log_zephyr/BUILD.gn
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-# Zephyr only uses CMake, so this file is empty.
diff --git a/pw_log_zephyr/CMakeLists.txt b/pw_log_zephyr/CMakeLists.txt
deleted file mode 100644
index c4c7178..0000000
--- a/pw_log_zephyr/CMakeLists.txt
+++ /dev/null
@@ -1,30 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-
-if(NOT CONFIG_PIGWEED_LOG)
-  return()
-endif()
-
-pw_auto_add_simple_module(pw_log_zephyr
-  IMPLEMENTS_FACADE
-    pw_log
-  PUBLIC_DEPS
-    zephyr_interface
-  PRIVATE_DEPS
-    pw_preprocessor
-)
-pw_set_backend(pw_log pw_log_zephyr)
-zephyr_link_libraries(pw_log_zephyr)
diff --git a/pw_log_zephyr/Kconfig b/pw_log_zephyr/Kconfig
deleted file mode 100644
index 472b54c..0000000
--- a/pw_log_zephyr/Kconfig
+++ /dev/null
@@ -1,28 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-menuconfig PIGWEED_LOG
-    bool "Enable Pigweed logging library (pw_log)"
-    select PIGWEED_PREPROCESSOR
-    help
-      Once the Pigweed logging is enabled, all Pigweed logs via PW_LOG_*() will
-      go to the "pigweed" Zephyr logging module.
-
-if PIGWEED_LOG
-
-module = PIGWEED
-module-str = "pigweed"
-source "subsys/logging/Kconfig.template.log_config"
-
-endif # PIGWEED_LOG
diff --git a/pw_log_zephyr/OWNERS b/pw_log_zephyr/OWNERS
deleted file mode 100644
index 2230ec2..0000000
--- a/pw_log_zephyr/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-peress@google.com
diff --git a/pw_log_zephyr/docs.rst b/pw_log_zephyr/docs.rst
deleted file mode 100644
index 84b8db9..0000000
--- a/pw_log_zephyr/docs.rst
+++ /dev/null
@@ -1,39 +0,0 @@
-.. _module-pw_log_zephyr:
-
-================
-pw_log_zephyr
-================
-
---------
-Overview
---------
-This interrupt backend implements the ``pw_log`` facade. To enable, set
-``CONFIG_PIGWEED_LOG=y``. After that, logging can be controlled via the standard
-`Kconfig options <https://docs.zephyrproject.org/latest/reference/logging/index.html#global-kconfig-options>`_.
-All logs made through `PW_LOG_*` are logged to the Zephyr logging module
-``pigweed``.
-
-Setting the log level
----------------------
-In order to remain compatible with existing Pigweed code, the logging backend
-respects ``PW_LOG_LEVEL``. If set, the backend will translate the Pigweed log
-levels to their closest Zephyr counterparts:
-
-+---------------------------+-------------------+
-| Pigweed                   | Zephyr            |
-+===========================+===================+
-| ``PW_LOG_LEVEL_DEBUG``    | ``LOG_LEVEL_DBG`` |
-+---------------------------+-------------------+
-| ``PW_LOG_LEVEL_INFO``     | ``LOG_LEVEL_INF`` |
-+---------------------------+-------------------+
-| ``PW_LOG_LEVEL_WARN``     | ``LOG_LEVEL_WRN`` |
-+---------------------------+-------------------+
-| ``PW_LOG_LEVEL_ERROR``    | ``LOG_LEVEL_ERR`` |
-|                           |                   |
-| ``PW_LOG_LEVEL_CRITICAL`` |                   |
-|                           |                   |
-| ``PW_LOG_LEVEL_FATAL``    |                   |
-+---------------------------+-------------------+
-
-Alternatively, it is also possible to set the Zephyr logging level directly via
-``CONFIG_PIGWEED_LOG_LEVEL``.
diff --git a/pw_log_zephyr/log_zephyr.cc b/pw_log_zephyr/log_zephyr.cc
deleted file mode 100644
index 053bf54..0000000
--- a/pw_log_zephyr/log_zephyr.cc
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <logging/log.h>
-
-#include "pw_log_zephyr/config.h"
-
-// Register the Zephyr logging module.
-// This must be done exactly ONCE. The header provided in public_overrides
-// provides the call to LOG_MODULE_DECLARE for each consumer of this backend
-// which will allow the use of this module.
-LOG_MODULE_REGISTER(PW_LOG_ZEPHYR_MODULE_NAME);
diff --git a/pw_log_zephyr/public/pw_log_zephyr/config.h b/pw_log_zephyr/public/pw_log_zephyr/config.h
deleted file mode 100644
index 027aaf6..0000000
--- a/pw_log_zephyr/public/pw_log_zephyr/config.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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
-
-#define PW_LOG_ZEPHYR_MODULE_NAME pigweed
diff --git a/pw_log_zephyr/public/pw_log_zephyr/log_zephyr.h b/pw_log_zephyr/public/pw_log_zephyr/log_zephyr.h
deleted file mode 100644
index 5f0b88e..0000000
--- a/pw_log_zephyr/public/pw_log_zephyr/log_zephyr.h
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <logging/log.h>
-#include <logging/log_ctrl.h>
-
-#include "pw_log_zephyr/config.h"
-
-#ifndef PW_LOG_MODULE_NAME
-#define PW_LOG_MODULE_NAME ""
-#endif
-
-// If the consumer defined PW_LOG_LEVEL use it, otherwise fallback to the global
-// CONFIG_PIGWEED_LOG_LEVEL set by Kconfig.
-#ifdef PW_LOG_LEVEL
-#if PW_LOG_LEVEL == PW_LOG_LEVEL_DEBUG
-// Map PW_LOG_LEVEL_DEBUG to LOG_LEVEL_DBG
-#define LOG_LEVEL LOG_LEVEL_DBG
-#elif PW_LOG_LEVEL == PW_LOG_LEVEL_INFO
-// Map PW_LOG_LEVEL_INFO to LOG_LEVEL_INF
-#define LOG_LEVEL LOG_LEVEL_INF
-#elif PW_LOG_LEVEL == PW_LOG_LEVEL_WARN
-// Map PW_LOG_LEVEL_WARN to LOG_LEVEL_WRN
-#define LOG_LEVEL LOG_LEVEL_WRN
-#elif (PW_LOG_LEVEL == PW_LOG_LEVEL_ERROR) ||  \
-    (PW_LOG_LEVEL == PW_LOG_LEVEL_CRITICAL) || \
-    (PW_LOG_LEVEL == PW_LOG_LEVEL_FATAL)
-// Map PW_LOG_LEVEL_(ERROR|CRITICAL|FATAL) to LOG_LEVEL_ERR
-#define LOG_LEVEL LOG_LEVEL_ERR
-#endif
-#else
-// Default to the Kconfig value
-#define LOG_LEVEL CONFIG_PIGWEED_LOG_LEVEL
-#endif
-
-LOG_MODULE_DECLARE(PW_LOG_ZEPHYR_MODULE_NAME, LOG_LEVEL);
-
-#define PW_HANDLE_LOG(level, flags, ...)             \
-  do {                                               \
-    switch (level) {                                 \
-      case PW_LOG_LEVEL_INFO:                        \
-        LOG_INF(PW_LOG_MODULE_NAME " " __VA_ARGS__); \
-        break;                                       \
-      case PW_LOG_LEVEL_WARN:                        \
-        LOG_WRN(PW_LOG_MODULE_NAME " " __VA_ARGS__); \
-        break;                                       \
-      case PW_LOG_LEVEL_ERROR:                       \
-      case PW_LOG_LEVEL_CRITICAL:                    \
-        LOG_ERR(PW_LOG_MODULE_NAME " " __VA_ARGS__); \
-        break;                                       \
-      case PW_LOG_LEVEL_FATAL:                       \
-        LOG_ERR(PW_LOG_MODULE_NAME " " __VA_ARGS__); \
-        LOG_PANIC();                                 \
-        break;                                       \
-      case PW_LOG_LEVEL_DEBUG:                       \
-      default:                                       \
-        LOG_DBG(PW_LOG_MODULE_NAME " " __VA_ARGS__); \
-        break;                                       \
-    }                                                \
-  } while (0)
diff --git a/pw_log_zephyr/public_overrides/pw_log_backend/log_backend.h b/pw_log_zephyr/public_overrides/pw_log_backend/log_backend.h
deleted file mode 100644
index a6d363b..0000000
--- a/pw_log_zephyr/public_overrides/pw_log_backend/log_backend.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_log_zephyr/log_zephyr.h"
diff --git a/pw_malloc/BUILD b/pw_malloc/BUILD
new file mode 100644
index 0000000..f3ab4f3
--- /dev/null
+++ b/pw_malloc/BUILD
@@ -0,0 +1,50 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+# TODO(pwbug/101): Need to add support for facades/backends to Bazel.
+PW_MALLOC_BACKEND = "//pw_malloc_freelist"
+
+pw_cc_library(
+    name = "facade",
+    hdrs = [
+        "public/pw_malloc/malloc.h",
+    ],
+    includes = ["public"],
+    deps = [
+        PW_MALLOC_BACKEND + ":headers",
+    ],
+)
+
+pw_cc_library(
+    name = "pw_malloc",
+    deps = [
+        ":facade",
+        PW_MALLOC_BACKEND + ":headers",
+    ],
+)
+
+pw_cc_library(
+    name = "backend",
+    deps = [
+        PW_MALLOC_BACKEND,
+    ],
+)
diff --git a/pw_malloc/BUILD.bazel b/pw_malloc/BUILD.bazel
deleted file mode 100644
index b4c9169..0000000
--- a/pw_malloc/BUILD.bazel
+++ /dev/null
@@ -1,45 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_facade",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_facade(
-    name = "facade",
-    hdrs = [
-        "public/pw_malloc/malloc.h",
-    ],
-    includes = ["public"],
-)
-
-pw_cc_library(
-    name = "pw_malloc",
-    deps = [
-        ":facade",
-        "@pigweed_config//:pw_malloc_backend",
-    ],
-)
-
-pw_cc_library(
-    name = "backend_multiplexer",
-    visibility = ["@pigweed_config//:__pkg__"],
-    deps = ["//pw_malloc_freelist"],
-)
diff --git a/pw_malloc/OWNERS b/pw_malloc/OWNERS
deleted file mode 100644
index 3afb926..0000000
--- a/pw_malloc/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-frolv@google.com
diff --git a/pw_malloc/docs.rst b/pw_malloc/docs.rst
index 2c0357d..d927d4c 100644
--- a/pw_malloc/docs.rst
+++ b/pw_malloc/docs.rst
@@ -20,7 +20,7 @@
 This module requires the following setup:
 
   1. Chose a ``pw_malloc`` backend, or write one yourself.
-  2. If using GN build, Specify the ``pw_malloc_BACKEND`` GN build arg to point
+  2. If using GN build, Specify the ``pw_sys_io_BACKEND`` GN build arg to point
      the library that provides a ``pw_malloc`` backend.
 
 Module usage
diff --git a/pw_malloc/public/pw_malloc/malloc.h b/pw_malloc/public/pw_malloc/malloc.h
index 206ee2f..c5e1f42 100644
--- a/pw_malloc/public/pw_malloc/malloc.h
+++ b/pw_malloc/public/pw_malloc/malloc.h
@@ -13,12 +13,10 @@
 // the License.
 #pragma once
 
-#include <stdint.h>
-
 #if __cplusplus
 extern "C" {
 #endif
-void pw_MallocInit(uint8_t* heap_low_addr, uint8_t* heap_high_addr);
+void pw_MallocInit();
 #if __cplusplus
 }
 #endif
diff --git a/pw_malloc_freelist/BUILD b/pw_malloc_freelist/BUILD
new file mode 100644
index 0000000..c3ca117
--- /dev/null
+++ b/pw_malloc_freelist/BUILD
@@ -0,0 +1,59 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "headers",
+    hdrs = [
+        "public/pw_malloc_freelist/freelist_malloc.h",
+    ],
+    includes = [
+        "public",
+    ],
+)
+
+pw_cc_library(
+    name = "pw_malloc_freelist",
+    srcs = [
+        "freelist_malloc.cc",
+    ],
+    deps = [
+        ":headers",
+        "//dir_pw_allocator:block",
+        "//dir_pw_allocator:freelist_heap",
+        "//dir_pw_boot_armv7m",
+        "//dir_pw_malloc:facade",
+        "//dir_pw_preprocessor",
+    ],
+)
+
+pw_cc_test(
+    name = "freelist_malloctest",
+    srcs = [
+        "freelist_malloc_test.cc",
+    ],
+    deps = [
+        ":headers",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_malloc_freelist/BUILD.bazel b/pw_malloc_freelist/BUILD.bazel
deleted file mode 100644
index 889515d..0000000
--- a/pw_malloc_freelist/BUILD.bazel
+++ /dev/null
@@ -1,59 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "headers",
-    hdrs = [
-        "public/pw_malloc_freelist/freelist_malloc.h",
-    ],
-    includes = [
-        "public",
-    ],
-)
-
-pw_cc_library(
-    name = "pw_malloc_freelist",
-    srcs = [
-        "freelist_malloc.cc",
-    ],
-    deps = [
-        ":headers",
-        "//pw_allocator:block",
-        "//pw_allocator:freelist_heap",
-        "//pw_malloc:facade",
-        "//pw_preprocessor",
-    ],
-)
-
-pw_cc_test(
-    name = "freelist_malloc_test",
-    srcs = [
-        "freelist_malloc_test.cc",
-    ],
-    deps = [
-        ":headers",
-        ":pw_malloc_freelist",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_malloc_freelist/BUILD.gn b/pw_malloc_freelist/BUILD.gn
index dfb642b..446a46f 100644
--- a/pw_malloc_freelist/BUILD.gn
+++ b/pw_malloc_freelist/BUILD.gn
@@ -29,6 +29,7 @@
   deps = [
     "$dir_pw_allocator:block",
     "$dir_pw_allocator:freelist_heap",
+    "$dir_pw_boot_armv7m",
     "$dir_pw_malloc:facade",
     "$dir_pw_preprocessor",
   ]
diff --git a/pw_malloc_freelist/OWNERS b/pw_malloc_freelist/OWNERS
deleted file mode 100644
index 3afb926..0000000
--- a/pw_malloc_freelist/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-frolv@google.com
diff --git a/pw_malloc_freelist/freelist_malloc.cc b/pw_malloc_freelist/freelist_malloc.cc
index 73b4439..db719c4 100644
--- a/pw_malloc_freelist/freelist_malloc.cc
+++ b/pw_malloc_freelist/freelist_malloc.cc
@@ -15,6 +15,7 @@
 #include <span>
 
 #include "pw_allocator/freelist_heap.h"
+#include "pw_boot_armv7m/boot.h"
 #include "pw_malloc/malloc.h"
 #include "pw_preprocessor/compiler.h"
 #include "pw_preprocessor/util.h"
@@ -30,10 +31,12 @@
 extern "C" {
 #endif  // __cplusplus
 // Define the global heap variables.
-void pw_MallocInit(uint8_t* heap_low_addr, uint8_t* heap_high_addr) {
+void pw_MallocInit() {
+  // pw_boot_heap_low_addr and pw_boot_heap_high_addr specifies the heap region
+  // from the linker script in "pw_boot_armv7m".
   std::span<std::byte> pw_allocator_freelist_raw_heap =
-      std::span(reinterpret_cast<std::byte*>(heap_low_addr),
-                heap_high_addr - heap_low_addr);
+      std::span(reinterpret_cast<std::byte*>(&pw_boot_heap_low_addr),
+                &pw_boot_heap_high_addr - &pw_boot_heap_low_addr);
   pw_freelist_heap = new (&buf)
       pw::allocator::FreeListHeapBuffer(pw_allocator_freelist_raw_heap);
 }
diff --git a/pw_metric/BUILD b/pw_metric/BUILD
new file mode 100644
index 0000000..051fc27
--- /dev/null
+++ b/pw_metric/BUILD
@@ -0,0 +1,92 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "metric",
+    hdrs = [
+        "public/pw_metric/metric.h",
+        "public/pw_metric/global.h",
+    ],
+    includes = ["public"],
+    srcs = [ "metric.cc" ],
+    deps = [
+        "//pw_assert",
+        "//pw_containers",
+        "//pw_log",
+        "//pw_span",
+        "//pw_tokenizer",
+    ],
+)
+
+pw_cc_library(
+    name = "global",
+    hdrs = [
+        "public/pw_metric/global.h",
+    ],
+    srcs = [ "global.cc" ],
+    deps = [
+        ":metric",
+    ],
+)
+
+pw_cc_library(
+    name = "metric_service_nanopb",
+    hdrs = [
+        "public/pw_metric/metric_service_nanopb.h",
+    ],
+    srcs = [ "metric_service_nanopb.cc" ],
+    deps = [
+        ":metric",
+    ],
+)
+
+pw_cc_test(
+    name = "metric_test",
+    srcs = [
+        "metric_test.cc",
+    ],
+    deps = [
+        ":metric",
+    ],
+)
+
+pw_cc_test(
+    name = "global_test",
+    srcs = [
+        "global_test.cc",
+    ],
+    deps = [
+        ":global",
+    ],
+)
+
+pw_cc_test(
+    name = "metric_service_nanopb_test",
+    srcs = [
+        "metric_service_nanopb_test.cc",
+    ],
+    deps = [
+        ":metric_service_nanopb",
+    ],
+)
diff --git a/pw_metric/BUILD.bazel b/pw_metric/BUILD.bazel
deleted file mode 100644
index 5c293f3..0000000
--- a/pw_metric/BUILD.bazel
+++ /dev/null
@@ -1,92 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "metric",
-    srcs = ["metric.cc"],
-    hdrs = [
-        "public/pw_metric/global.h",
-        "public/pw_metric/metric.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_assert",
-        "//pw_containers",
-        "//pw_log",
-        "//pw_span",
-        "//pw_tokenizer:base64",
-    ],
-)
-
-pw_cc_library(
-    name = "global",
-    srcs = ["global.cc"],
-    hdrs = [
-        "public/pw_metric/global.h",
-    ],
-    deps = [
-        ":metric",
-    ],
-)
-
-pw_cc_library(
-    name = "metric_service_nanopb",
-    srcs = ["metric_service_nanopb.cc"],
-    hdrs = [
-        "public/pw_metric/metric_service_nanopb.h",
-    ],
-    deps = [
-        ":metric",
-    ],
-)
-
-pw_cc_test(
-    name = "metric_test",
-    srcs = [
-        "metric_test.cc",
-    ],
-    deps = [
-        ":metric",
-    ],
-)
-
-pw_cc_test(
-    name = "global_test",
-    srcs = [
-        "global_test.cc",
-    ],
-    deps = [
-        ":global",
-    ],
-)
-
-pw_cc_test(
-    name = "metric_service_nanopb_test",
-    srcs = [
-        "metric_service_nanopb_test.cc",
-    ],
-    deps = [
-        ":metric_service_nanopb",
-    ],
-)
diff --git a/pw_metric/BUILD.gn b/pw_metric/BUILD.gn
index 3fc335c..0abc449 100644
--- a/pw_metric/BUILD.gn
+++ b/pw_metric/BUILD.gn
@@ -48,7 +48,6 @@
     ":pw_metric",
     dir_pw_tokenizer,
   ]
-  deps = [ dir_pw_polyfill ]
 }
 
 ################################################################################
diff --git a/pw_metric/OWNERS b/pw_metric/OWNERS
deleted file mode 100644
index 2529495..0000000
--- a/pw_metric/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-ewout@google.com
-keir@google.com
diff --git a/pw_metric/docs.rst b/pw_metric/docs.rst
index 2b9d20e..6bdcd5a 100644
--- a/pw_metric/docs.rst
+++ b/pw_metric/docs.rst
@@ -150,7 +150,7 @@
   UART). In those cases, metrics provide a low-overhead approach to understand
   what is happening. During early boot, metrics can be incremented, then after
   boot dumping the metrics provides insights into what happened. While basic
-  counter variables can work in these contexts too, one still has to deal with
+  counter variables can work in these contexts to, one still has to deal with
   the offloading problem; which the library handles.
 
 ---------------------
@@ -308,7 +308,7 @@
       }
 
   You can also put a metric into a group with the macro. Metrics can belong to
-  strictly one group, otherwise an assertion will fail. Example:
+  strictly one group, otherwise a assertion will fail. Example:
 
   .. code::
 
@@ -653,7 +653,7 @@
     "/i2c1/gyro/resets": 24,
     "/i2c1/gyro/hangs": 1,
     "/spi1/thermocouple/reads": 242,
-    "/spi1/thermocouple/temp_celsius": 34.52,
+    "/spi1/thermocouple/temp_celcius": 34.52,
   }
 
 Note that there is no nesting of the groups; the nesting is implied from the
@@ -717,8 +717,8 @@
 
   Calls to is ``MetricService::Get`` are blocking and will send all metrics
   immediately, even though it is a server-streaming RPC. This will work fine if
-  the device doesn't have too many metrics, or doesn't have concurrent RPCs
-  like logging, but could be a problem in some cases.
+  the device doesn't have too many metics, or doesn't have concurrent RPCs like
+  logging, but could be a problem in some cases.
 
   We plan to offer an async version where the application is responsible for
   pumping the metrics into the streaming response. This gives flow control to
@@ -818,7 +818,7 @@
   metrics are enabled or disabled at compile time. This may rely on of C++20's
   support for zero-sized members to fully remove the cost.
 
-- **Async RPC** - The current RPC service exports the metrics by streaming
+- **Async RCPC** - The current RPC service exports the metrics by streaming
   them to the client in batches. However, the current solution streams all the
   metrics to completion; this may block the RPC thread. In the future we will
   have an async solution where the user is in control of flow priority.
diff --git a/pw_metric/global.cc b/pw_metric/global.cc
index 7281150..822a1d7 100644
--- a/pw_metric/global.cc
+++ b/pw_metric/global.cc
@@ -14,11 +14,9 @@
 
 #include "pw_metric/global.h"
 
-#include "pw_polyfill/language_feature_macros.h"
-
 namespace pw::metric {
 
-PW_CONSTINIT IntrusiveList<Group> global_groups;
-PW_CONSTINIT IntrusiveList<Metric> global_metrics;
+constinit IntrusiveList<Group> global_groups;
+constinit IntrusiveList<Metric> global_metrics;
 
 }  // namespace pw::metric
diff --git a/pw_metric/metric.cc b/pw_metric/metric.cc
index 8a899f4..5e99329 100644
--- a/pw_metric/metric.cc
+++ b/pw_metric/metric.cc
@@ -17,7 +17,6 @@
 #include <array>
 #include <span>
 
-#include "pw_assert/check.h"
 #include "pw_log/log.h"
 #include "pw_tokenizer/base64.h"
 
@@ -89,12 +88,7 @@
   Base64EncodedToken encoded_name(name());
   const char* indent = Indent(level);
   if (is_float()) {
-    // Variadic macros promote float to double. Explicitly cast here to
-    // acknowledge this and allow projects to use -Wdouble-promotion.
-    PW_LOG_INFO("%s \"%s\": %f,",
-                indent,
-                encoded_name.value(),
-                static_cast<double>(as_float()));
+    PW_LOG_INFO("%s \"%s\": %f,", indent, encoded_name.value(), as_float());
   } else {
     PW_LOG_INFO("%s \"%s\": %u,",
                 indent,
diff --git a/pw_metric/metric_service_nanopb.cc b/pw_metric/metric_service_nanopb.cc
index 0a1e7cc..96d246d 100644
--- a/pw_metric/metric_service_nanopb.cc
+++ b/pw_metric/metric_service_nanopb.cc
@@ -17,7 +17,7 @@
 #include <cstring>
 #include <span>
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_containers/vector.h"
 #include "pw_metric/metric.h"
 #include "pw_preprocessor/util.h"
@@ -27,8 +27,7 @@
 
 class MetricWriter {
  public:
-  MetricWriter(
-      MetricService::ServerWriter<pw_metric_MetricResponse>& response_writer)
+  MetricWriter(rpc::ServerWriter<pw_metric_MetricResponse>& response_writer)
       : response_(pw_metric_MetricResponse_init_zero),
         response_writer_(response_writer) {}
 
@@ -71,8 +70,7 @@
 
   void Flush() {
     if (response_.metrics_count) {
-      response_writer_.Write(response_)
-          .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+      response_writer_.Write(response_);
       response_ = pw_metric_MetricResponse_init_zero;
     }
   }
@@ -80,7 +78,7 @@
  private:
   pw_metric_MetricResponse response_;
   // This RPC stream writer handle must be valid for the metric writer lifetime.
-  MetricService::ServerWriter<pw_metric_MetricResponse>& response_writer_;
+  rpc::ServerWriter<pw_metric_MetricResponse>& response_writer_;
 };
 
 // Walk a metric tree recursively; passing metrics with their path (names) to a
@@ -129,7 +127,8 @@
 
 }  // namespace
 
-void MetricService::Get(const pw_metric_MetricRequest& /* request */,
+void MetricService::Get(ServerContext&,
+                        const pw_metric_MetricRequest& /* request */,
                         ServerWriter<pw_metric_MetricResponse>& response) {
   // For now, ignore the request and just stream all the metrics back.
   MetricWriter writer(response);
diff --git a/pw_metric/metric_service_nanopb_test.cc b/pw_metric/metric_service_nanopb_test.cc
index 631b1d0..5810982 100644
--- a/pw_metric/metric_service_nanopb_test.cc
+++ b/pw_metric/metric_service_nanopb_test.cc
@@ -16,13 +16,14 @@
 
 #include "gtest/gtest.h"
 #include "pw_log/log.h"
-#include "pw_rpc/nanopb/test_method_context.h"
+#include "pw_rpc/nanopb_test_method_context.h"
 
 namespace pw::metric {
 namespace {
 
-#define MetricMethodContext \
-  PW_NANOPB_TEST_METHOD_CONTEXT(MetricService, Get, 4, 256)
+#define MetricMethodContext      \
+  PW_NANOPB_TEST_METHOD_CONTEXT( \
+      MetricService, Get, 4, sizeof(pw_metric_MetricResponse))
 
 TEST(MetricService, EmptyGroupAndNoMetrics) {
   // Empty root group.
diff --git a/pw_metric/public/pw_metric/metric.h b/pw_metric/public/pw_metric/metric.h
index 1ee998d..e229a6f 100644
--- a/pw_metric/public/pw_metric/metric.h
+++ b/pw_metric/public/pw_metric/metric.h
@@ -17,6 +17,7 @@
 #include <initializer_list>
 #include <limits>
 
+#include "pw_assert/assert.h"
 #include "pw_containers/intrusive_list.h"
 #include "pw_preprocessor/arguments.h"
 #include "pw_tokenizer/tokenize.h"
diff --git a/pw_metric/public/pw_metric/metric_service_nanopb.h b/pw_metric/public/pw_metric/metric_service_nanopb.h
index c79f1df..15fd6ee 100644
--- a/pw_metric/public/pw_metric/metric_service_nanopb.h
+++ b/pw_metric/public/pw_metric/metric_service_nanopb.h
@@ -31,14 +31,14 @@
 // method is blocking, and sends all metrics at once (though batched). In the
 // future, we may switch to offering an async version where the Get() method
 // returns immediately, and someone else is responsible for pumping the queue.
-class MetricService final
-    : public pw_rpc::nanopb::MetricService::Service<MetricService> {
+class MetricService final : public generated::MetricService<MetricService> {
  public:
   MetricService(const IntrusiveList<Metric>& metrics,
                 const IntrusiveList<Group>& groups)
       : metrics_(metrics), groups_(groups) {}
 
-  void Get(const pw_metric_MetricRequest& request,
+  void Get(ServerContext&,
+           const pw_metric_MetricRequest& request,
            ServerWriter<pw_metric_MetricResponse>& response);
 
  private:
diff --git a/pw_metric/size_report/BUILD b/pw_metric/size_report/BUILD
new file mode 100644
index 0000000..f7693a9
--- /dev/null
+++ b/pw_metric/size_report/BUILD
@@ -0,0 +1,65 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_binary",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_binary(
+    name = "base",
+    srcs = ["base.cc"],
+    deps = [
+        "//pw_bloat:bloat_this_binary",
+        "//pw_log",
+        "//pw_assert",
+    ],
+)
+
+pw_cc_binary(
+    name = "one_metric",
+    srcs = ["one_metric.cc"],
+    deps = [
+        "//pw_bloat:bloat_this_binary",
+        "//pw_metric",
+        "//pw_log",
+        "//pw_assert",
+    ],
+)
+
+pw_cc_binary(
+    name = "dump",
+    srcs = ["dump.cc"],
+    deps = [
+        "//pw_bloat:bloat_this_binary",
+        "//pw_metric",
+        "//pw_log",
+        "//pw_assert",
+    ],
+)
+
+pw_cc_binary(
+    name = "more_metrics",
+    srcs = ["more_metrics.cc"],
+    deps = [
+        "//pw_bloat:bloat_this_binary",
+        "//pw_metric",
+        "//pw_log",
+        "//pw_assert",
+    ],
+)
diff --git a/pw_metric/size_report/BUILD.bazel b/pw_metric/size_report/BUILD.bazel
deleted file mode 100644
index d2dbe28..0000000
--- a/pw_metric/size_report/BUILD.bazel
+++ /dev/null
@@ -1,65 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_binary",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_binary(
-    name = "base",
-    srcs = ["base.cc"],
-    deps = [
-        "//pw_assert",
-        "//pw_bloat:bloat_this_binary",
-        "//pw_log",
-    ],
-)
-
-pw_cc_binary(
-    name = "one_metric",
-    srcs = ["one_metric.cc"],
-    deps = [
-        "//pw_assert",
-        "//pw_bloat:bloat_this_binary",
-        "//pw_log",
-        "//pw_metric:metric",
-    ],
-)
-
-pw_cc_binary(
-    name = "dump",
-    srcs = ["dump.cc"],
-    deps = [
-        "//pw_assert",
-        "//pw_bloat:bloat_this_binary",
-        "//pw_log",
-        "//pw_metric:metric",
-    ],
-)
-
-pw_cc_binary(
-    name = "more_metrics",
-    srcs = ["more_metrics.cc"],
-    deps = [
-        "//pw_assert",
-        "//pw_bloat:bloat_this_binary",
-        "//pw_log",
-        "//pw_metric:metric",
-    ],
-)
diff --git a/pw_metric/size_report/base.cc b/pw_metric/size_report/base.cc
index 5fde3b6..967201c 100644
--- a/pw_metric/size_report/base.cc
+++ b/pw_metric/size_report/base.cc
@@ -12,7 +12,7 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_bloat/bloat_this_binary.h"
 #include "pw_log/log.h"
 
diff --git a/pw_metric/size_report/dump.cc b/pw_metric/size_report/dump.cc
index 9e2dfbb..12a83fb 100644
--- a/pw_metric/size_report/dump.cc
+++ b/pw_metric/size_report/dump.cc
@@ -12,7 +12,7 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_bloat/bloat_this_binary.h"
 #include "pw_log/log.h"
 #include "pw_metric/metric.h"
diff --git a/pw_metric/size_report/more_metrics.cc b/pw_metric/size_report/more_metrics.cc
index 5d683f5..2ceb7f0 100644
--- a/pw_metric/size_report/more_metrics.cc
+++ b/pw_metric/size_report/more_metrics.cc
@@ -12,7 +12,7 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_bloat/bloat_this_binary.h"
 #include "pw_log/log.h"
 #include "pw_metric/metric.h"
diff --git a/pw_metric/size_report/one_metric.cc b/pw_metric/size_report/one_metric.cc
index 98db857..2869267 100644
--- a/pw_metric/size_report/one_metric.cc
+++ b/pw_metric/size_report/one_metric.cc
@@ -12,7 +12,7 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_bloat/bloat_this_binary.h"
 #include "pw_log/log.h"
 #include "pw_metric/metric.h"
diff --git a/pw_minimal_cpp_stdlib/BUILD b/pw_minimal_cpp_stdlib/BUILD
new file mode 100644
index 0000000..01d6276
--- /dev/null
+++ b/pw_minimal_cpp_stdlib/BUILD
@@ -0,0 +1,88 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_minimal_cpp_stdlib",
+    srcs = [
+        "public/internal/algorithm.h",
+        "public/internal/array.h",
+        "public/internal/cinttypes.h",
+        "public/internal/climits.h",
+        "public/internal/cmath.h",
+        "public/internal/cstdarg.h",
+        "public/internal/cstddef.h",
+        "public/internal/cstdint.h",
+        "public/internal/cstdio.h",
+        "public/internal/cstring.h",
+        "public/internal/initializer_list.h",
+        "public/internal/iterator.h",
+        "public/internal/limits.h",
+        "public/internal/new.h",
+        "public/internal/string_view.h",
+        "public/internal/type_traits.h",
+        "public/internal/utility.h",
+    ],
+    hdrs = [
+        "public/algorithm",
+        "public/array",
+        "public/cinttypes",
+        "public/climits",
+        "public/cmath",
+        "public/cstdarg",
+        "public/cstddef",
+        "public/cstdint",
+        "public/cstdio",
+        "public/cstring",
+        "public/initializer_list",
+        "public/iterator",
+        "public/limits",
+        "public/new",
+        "public/string_view",
+        "public/type_traits",
+        "public/utility",
+    ],
+    copts = ["-nostdinc++"],
+    includes = ["public"],
+)
+
+pw_cc_library(
+    name = "minimal_cpp_stdlib_isolated_test",
+    srcs = ["isolated_test.cc"],
+    copts = ["-nostdinc++"],
+    deps = [
+        ":pw_minimal_cpp_stdlib",
+        "//pw_preprocessor",
+    ],
+)
+
+pw_cc_test(
+    name = "test",
+    srcs = [
+        "test.cc",
+    ],
+    deps = [
+        ":pw_minimal_cpp_stdlib_isolated_test",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_minimal_cpp_stdlib/BUILD.bazel b/pw_minimal_cpp_stdlib/BUILD.bazel
deleted file mode 100644
index 4deb4ce..0000000
--- a/pw_minimal_cpp_stdlib/BUILD.bazel
+++ /dev/null
@@ -1,88 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_minimal_cpp_stdlib",
-    srcs = [
-        "public/internal/algorithm.h",
-        "public/internal/array.h",
-        "public/internal/cinttypes.h",
-        "public/internal/climits.h",
-        "public/internal/cmath.h",
-        "public/internal/cstdarg.h",
-        "public/internal/cstddef.h",
-        "public/internal/cstdint.h",
-        "public/internal/cstdio.h",
-        "public/internal/cstring.h",
-        "public/internal/initializer_list.h",
-        "public/internal/iterator.h",
-        "public/internal/limits.h",
-        "public/internal/new.h",
-        "public/internal/string_view.h",
-        "public/internal/type_traits.h",
-        "public/internal/utility.h",
-    ],
-    hdrs = [
-        "public/algorithm",
-        "public/array",
-        "public/cinttypes",
-        "public/climits",
-        "public/cmath",
-        "public/cstdarg",
-        "public/cstddef",
-        "public/cstdint",
-        "public/cstdio",
-        "public/cstring",
-        "public/initializer_list",
-        "public/iterator",
-        "public/limits",
-        "public/new",
-        "public/string_view",
-        "public/type_traits",
-        "public/utility",
-    ],
-    copts = ["-nostdinc++"],
-    includes = ["public"],
-)
-
-pw_cc_library(
-    name = "minimal_cpp_stdlib_isolated_test",
-    srcs = ["isolated_test.cc"],
-    copts = ["-nostdinc++"],
-    deps = [
-        ":pw_minimal_cpp_stdlib",
-        "//pw_preprocessor",
-    ],
-)
-
-pw_cc_test(
-    name = "test",
-    srcs = [
-        "test.cc",
-    ],
-    deps = [
-        ":minimal_cpp_stdlib_isolated_test",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_minimal_cpp_stdlib/BUILD.gn b/pw_minimal_cpp_stdlib/BUILD.gn
index bda4178..332e18c 100644
--- a/pw_minimal_cpp_stdlib/BUILD.gn
+++ b/pw_minimal_cpp_stdlib/BUILD.gn
@@ -70,14 +70,10 @@
 }
 
 pw_test_group("tests") {
-  tests = []
-  if (host_os != "win") {
-    # TODO(amontanez): pw_minimal_cpp_stdlib tests do not build on windows.
-    tests += [
-      ":minimal_cpp_stdlib_test",
-      ":standard_library_test",
-    ]
-  }
+  tests = [
+    ":minimal_cpp_stdlib_test",
+    ":standard_library_test",
+  ]
 }
 
 pw_source_set("minimal_cpp_stdlib_isolated_test") {
diff --git a/pw_minimal_cpp_stdlib/OWNERS b/pw_minimal_cpp_stdlib/OWNERS
deleted file mode 100644
index d96cbc6..0000000
--- a/pw_minimal_cpp_stdlib/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-hepler@google.com
diff --git a/pw_minimal_cpp_stdlib/isolated_test.cc b/pw_minimal_cpp_stdlib/isolated_test.cc
index e577e3f..1be9662 100644
--- a/pw_minimal_cpp_stdlib/isolated_test.cc
+++ b/pw_minimal_cpp_stdlib/isolated_test.cc
@@ -320,7 +320,6 @@
 
   MoveTester moved(std::move(copied));
   EXPECT_EQ(123, moved.magic_value);
-  // NOLINTNEXTLINE(bugprone-use-after-move)
   EXPECT_EQ(0xffff, copied.magic_value);
   EXPECT_TRUE(moved.moved);
 }
diff --git a/pw_module/OWNERS b/pw_module/OWNERS
deleted file mode 100644
index 8819b2c..0000000
--- a/pw_module/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-keir@google.com
diff --git a/pw_module/py/BUILD.gn b/pw_module/py/BUILD.gn
index 7a53ae7..da1b132 100644
--- a/pw_module/py/BUILD.gn
+++ b/pw_module/py/BUILD.gn
@@ -17,11 +17,7 @@
 import("$dir_pw_build/python.gni")
 
 pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
+  setup = [ "setup.py" ]
   sources = [
     "pw_module/__init__.py",
     "pw_module/check.py",
diff --git a/pw_module/py/pyproject.toml b/pw_module/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/pw_module/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/pw_module/py/setup.cfg b/pw_module/py/setup.cfg
deleted file mode 100644
index 22ea44d..0000000
--- a/pw_module/py/setup.cfg
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pw_module
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Meta-module for Pigweed
-
-[options]
-packages = find:
-zip_safe = False
-
-[options.package_data]
-pw_module = py.typed
diff --git a/pw_module/py/setup.py b/pw_module/py/setup.py
index 882d2cd..5957252 100644
--- a/pw_module/py/setup.py
+++ b/pw_module/py/setup.py
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2019 The Pigweed Authors
 #
 # 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
@@ -15,4 +15,13 @@
 
 import setuptools  # type: ignore
 
-setuptools.setup()  # Package definition in setup.cfg
+setuptools.setup(
+    name='pw_module',
+    version='0.0.1',
+    author='Pigweed Authors',
+    author_email='pigweed-developers@googlegroups.com',
+    description='Meta-module for Pigweed',
+    packages=setuptools.find_packages(),
+    package_data={'pw_module': ['py.typed']},
+    zip_safe=False,
+)
diff --git a/pw_multisink/BUILD b/pw_multisink/BUILD
new file mode 100644
index 0000000..de50a3c
--- /dev/null
+++ b/pw_multisink/BUILD
@@ -0,0 +1,51 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_multisink",
+    srcs = [ "drain.cc", "multisink.cc" ],
+    includes = [ "public" ],
+    deps = [
+        "//pw_assert",
+        "//pw_bytes",
+        "//pw_result",
+        "//pw_ring_buffer",
+        "//pw_varint",
+    ],
+    hdrs = [
+        "public/pw_multisink/drain.h",
+        "public/pw_multisink/multisink.h",
+    ]
+)
+
+pw_cc_test(
+    name = "multisink_test",
+    srcs = [
+        "multisink_test.cc",
+    ],
+    deps = [
+        "//pw_preprocessor",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_multisink/BUILD.bazel b/pw_multisink/BUILD.bazel
deleted file mode 100644
index acd9002..0000000
--- a/pw_multisink/BUILD.bazel
+++ /dev/null
@@ -1,127 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-load(
-    "//pw_build:selects.bzl",
-    "TARGET_COMPATIBLE_WITH_HOST_SELECT",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_multisink",
-    srcs = [
-        "multisink.cc",
-    ],
-    hdrs = [
-        "public/pw_multisink/config.h",
-        "public/pw_multisink/multisink.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_assert",
-        "//pw_bytes",
-        "//pw_containers",
-        "//pw_function",
-        "//pw_log",
-        "//pw_result",
-        "//pw_ring_buffer",
-        "//pw_sync:interrupt_spin_lock",
-        "//pw_sync:lock_annotations",
-        "//pw_sync:mutex",
-        "//pw_varint",
-    ],
-)
-
-pw_cc_library(
-    name = "util",
-    srcs = ["util.cc"],
-    hdrs = ["public/pw_multisink/util.h"],
-    includes = ["public"],
-    deps = [
-        ":pw_multisink",
-        "//pw_bytes",
-        "//pw_function",
-        "//pw_log",
-        "//pw_log:log_proto_cc.pwpb",
-        "//pw_status",
-    ],
-)
-
-pw_cc_library(
-    name = "test_thread",
-    hdrs = ["public/pw_multisink/test_thread.h"],
-    includes = ["public"],
-    deps = [
-        "//pw_thread:thread",
-    ],
-)
-
-pw_cc_test(
-    name = "multisink_test",
-    srcs = [
-        "multisink_test.cc",
-    ],
-    deps = [
-        ":pw_multisink",
-        "//pw_function",
-        "//pw_preprocessor",
-        "//pw_status",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_library(
-    name = "multisink_threaded_test",
-    srcs = [
-        "multisink_threaded_test.cc",
-    ],
-    deps = [
-        ":pw_multisink",
-        ":test_thread",
-        "//pw_string",
-        "//pw_thread:thread",
-        "//pw_thread:yield",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_library(
-    name = "stl_test_thread",
-    srcs = [
-        "stl_test_thread.cc",
-    ],
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-    deps = [
-        ":test_thread",
-        "//pw_thread:thread",
-        "//pw_thread_stl:thread",
-    ],
-)
-
-pw_cc_test(
-    name = "stl_multisink_threaded_test",
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-    deps = [
-        ":multisink_threaded_test",
-        ":stl_test_thread",
-    ],
-)
diff --git a/pw_multisink/BUILD.gn b/pw_multisink/BUILD.gn
index 842dfac..c8446d0 100644
--- a/pw_multisink/BUILD.gn
+++ b/pw_multisink/BUILD.gn
@@ -14,87 +14,34 @@
 
 import("//build_overrides/pigweed.gni")
 
-import("$dir_pw_build/module_config.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_thread/backend.gni")
 import("$dir_pw_unit_test/test.gni")
 
-declare_args() {
-  # The build target that overrides the default configuration options for this
-  # module. This should point to a source set that provides defines through a
-  # public config (which may -include a file or add defines directly).
-  pw_multisink_CONFIG = pw_build_DEFAULT_MODULE_CONFIG
-}
-
-config("public_include_path") {
+config("default_config") {
   include_dirs = [ "public" ]
   visibility = [ ":*" ]
 }
 
-pw_source_set("config") {
-  public = [ "public/pw_multisink/config.h" ]
-  public_configs = [ ":public_include_path" ]
-  public_deps = [ pw_multisink_CONFIG ]
-}
-
 pw_source_set("pw_multisink") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_multisink/multisink.h" ]
+  public_configs = [ ":default_config" ]
+  public = [
+    "public/pw_multisink/drain.h",
+    "public/pw_multisink/multisink.h",
+  ]
   public_deps = [
-    ":config",
-    "$dir_pw_sync:interrupt_spin_lock",
-    "$dir_pw_sync:lock_annotations",
-    "$dir_pw_sync:mutex",
-    dir_pw_bytes,
-    dir_pw_containers,
-    dir_pw_function,
-    dir_pw_result,
-    dir_pw_ring_buffer,
-    dir_pw_status,
+    "$dir_pw_bytes",
+    "$dir_pw_result",
+    "$dir_pw_ring_buffer",
+    "$dir_pw_status",
   ]
   deps = [
-    dir_pw_assert,
-    dir_pw_log,
-    dir_pw_varint,
+    "$dir_pw_assert",
+    "$dir_pw_varint",
   ]
-  sources = [ "multisink.cc" ]
-}
-
-pw_source_set("util") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_multisink/util.h" ]
-  public_deps = [
-    ":pw_multisink",
-    "$dir_pw_log:protos.pwpb",
-    dir_pw_status,
-  ]
-  deps = [
-    dir_pw_bytes,
-    dir_pw_function,
-  ]
-  sources = [ "util.cc" ]
-}
-
-pw_source_set("test_thread") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_multisink/test_thread.h" ]
-  public_deps = [ "$dir_pw_thread:thread" ]
-}
-
-# Tests that use threads.
-# To instantiate this test based on a thread backend, create a pw_test target
-# that depends on this pw_source_set and a pw_source_set that provides the
-# implementaiton of test_thread. See :stl_multisink_test as an example.
-pw_source_set("multisink_threaded_test") {
-  sources = [ "multisink_threaded_test.cc" ]
-  deps = [
-    ":pw_multisink",
-    ":test_thread",
-    "$dir_pw_string",
-    "$dir_pw_thread:thread",
-    "$dir_pw_thread:yield",
-    "$dir_pw_unit_test",
+  sources = [
+    "drain.cc",
+    "multisink.cc",
   ]
 }
 
@@ -104,33 +51,9 @@
 
 pw_test("multisink_test") {
   sources = [ "multisink_test.cc" ]
-  deps = [
-    ":pw_multisink",
-    dir_pw_function,
-    dir_pw_status,
-  ]
-}
-
-pw_source_set("stl_test_thread") {
-  sources = [ "stl_test_thread.cc" ]
-  deps = [
-    ":test_thread",
-    "$dir_pw_thread:thread",
-    "$dir_pw_thread_stl:thread",
-  ]
-}
-
-pw_test("stl_multisink_threaded_test") {
-  enable_if = pw_thread_THREAD_BACKEND == "$dir_pw_thread_stl:thread"
-  deps = [
-    ":multisink_threaded_test",
-    ":stl_test_thread",
-  ]
+  deps = [ ":pw_multisink" ]
 }
 
 pw_test_group("tests") {
-  tests = [
-    ":multisink_test",
-    ":stl_multisink_threaded_test",
-  ]
+  tests = [ ":multisink_test" ]
 }
diff --git a/pw_multisink/CMakeLists.txt b/pw_multisink/CMakeLists.txt
index 4d75180..083c09e 100644
--- a/pw_multisink/CMakeLists.txt
+++ b/pw_multisink/CMakeLists.txt
@@ -14,114 +14,4 @@
 
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
 
-pw_add_module_config(pw_multisink_CONFIG)
-
-pw_add_module_library(pw_multisink.config
-  HEADERS
-    public/pw_multisink/config.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    ${pw_multisink_CONFIG}
-)
-
-pw_add_module_library(pw_multisink
-  HEADERS
-    public/pw_multisink/multisink.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_bytes
-    pw_containers
-    pw_function
-    pw_multisink.config
-    pw_result
-    pw_ring_buffer
-    pw_status
-    pw_sync.interrupt_spin_lock
-    pw_sync.lock_annotations
-    pw_sync.mutex
-  SOURCES
-    multisink.cc
-  PRIVATE_DEPS
-    pw_assert
-    pw_log
-    pw_varint
-)
-
-pw_add_module_library(pw_multisink.util
-  HEADERS
-    public/pw_multisink/util.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_log.protos.pwpb
-    pw_multisink
-    pw_status
-  SOURCES
-    util.cc
-  PRIVATE_DEPS
-    pw_bytes
-    pw_function
-)
-
-pw_add_module_library(pw_multisink.test_thread
-  HEADERS
-    public/pw_multisink/test_thread.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_thread.thread
-)
-
-# Tests that use threads.
-# To instantiate this test based on a thread backend, create a pw_add_test
-# target that depends on this pw_add_module_library and a pw_add_module_library
-# that provides the implementaiton of pw_multisink.test_thread. See
-# pw_multisink.stl_multisink_test as an example.
-pw_add_module_library(pw_multisink.multisink_threaded_test
-  SOURCES
-    multisink_threaded_test.cc
-  PRIVATE_DEPS
-    pw_multisink
-    pw_multisink.test_thread
-    pw_thread.thread
-    pw_thread.yield
-    pw_unit_test
-)
-
-pw_add_test(pw_multisink.multisink_test
-  SOURCES
-    multisink_test.cc
-  DEPS
-    pw_function
-    pw_multisink
-    pw_polyfill.cstddef
-    pw_polyfill.span
-    pw_status
-  GROUPS
-    modules
-    pw_multisink
-)
-
-pw_add_module_library(pw_multisink.stl_test_thread
-  SOURCES
-    stl_test_thread.cc
-  PRIVATE_DEPS
-    pw_multisink.test_thread
-    pw_thread.thread
-    pw_thread_stl.thread
-)
-
-if("${pw_thread.thread_BACKEND}" STREQUAL "pw_thread_stl.thread")
-  pw_add_test(pw_multisink.stl_multisink_threaded_test
-    DEPS
-      pw_polyfill.cstddef
-      pw_polyfill.span
-      pw_multisink.multisink_threaded_test
-      pw_multisink.stl_test_thread
-    GROUPS
-      modules
-      pw_multisink
-  )
-endif()
+pw_auto_add_simple_module(pw_multisink)
diff --git a/pw_multisink/OWNERS b/pw_multisink/OWNERS
deleted file mode 100644
index 2281d86..0000000
--- a/pw_multisink/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-ewout@google.com
-prashanthsw@google.com
diff --git a/pw_multisink/docs.rst b/pw_multisink/docs.rst
index c8cb8f3..cf406f3 100644
--- a/pw_multisink/docs.rst
+++ b/pw_multisink/docs.rst
@@ -1,188 +1,9 @@
 .. _module-pw_multisink:
 
-============
+------------
 pw_multisink
-============
+------------
 This is an module that forwards messages to multiple attached sinks, which
 consume messages asynchronously. It is not ready for use and is under
 construction.
 
-Module Configuration Options
-============================
-The following configurations can be adjusted via compile-time configuration
-of this module, see the
-:ref:`module documentation <module-structure-compile-time-configuration>` for
-more details.
-
-.. c:macro:: PW_MULTISINK_CONFIG_LOCK_INTERRUPT_SAFE
-
-  Whether an interrupt-safe lock is used to guard multisink read/write
-  operations.
-
-  By default, this option is enabled and the multisink uses an interrupt
-  spin-lock to guard its transactions. If disabled, a mutex is used instead.
-
-  Disabling this will alter the entry precondition of the multisink,
-  requiring that it not be called from an interrupt context.
-
-Late Drain Attach
-=================
-It is possible to push entries or inform the multisink of drops before any
-drains are attached to it, allowing you to defer the creation of the drain
-further into an application. The multisink maintains the location and drop
-count of the oldest drain and will set drains to match on attachment. This
-permits drains that are attached late to still consume any entries that were
-pushed into the ring buffer, so long as those entries have not yet been evicted
-by newer entries. This may be particularly useful in early-boot scenarios where
-drain consumers may need time to initialize their output paths. Listeners are
-notified immediately when attached, to allow late drain users to consume
-existing entries. If draining in response to the notification, ensure that
-the drain is attached prior to registering the listener; attempting to drain
-when unattached will crash.
-
-.. code-block:: cpp
-
-  // Create a multisink during global construction.
-  std::byte buffer[1024];
-  MultiSink multisink(buffer);
-
-  int main() {
-    // Do some initialization work for the application that pushes information
-    // into the multisink.
-    multisink.HandleEntry("Booting up!");
-    Initialize();
-
-    multisink.HandleEntry("Prepare I/O!");
-    PrepareIO();
-
-    // Start a thread to process logs in multisink.
-    StartLoggingThread();
-  }
-
-  void StartLoggingThread() {
-    MultiSink::Drain drain;
-    multisink.AttachDrain(drain);
-
-    std::byte read_buffer[512];
-    uint32_t drop_count = 0;
-    do {
-      Result<ConstByteSpan> entry = drain.PopEntry(read_buffer, drop_count);
-      if (drop_count > 0) {
-        StringBuilder<32> sb;
-        sb.Format("Dropped %d entries.", drop_count);
-        // Note: PrintByteArray is not a provided utility function.
-        PrintByteArray(sb.as_bytes());
-      }
-
-      // Iterate through the entries, this will print out:
-      //   "Booting up!"
-      //   "Prepare I/O!"
-      //
-      // Even though the drain was attached after entries were pushed into the
-      // multisink, this drain will still be able to consume those entries.
-      //
-      // Note: PrintByteArray is not a provided utility function.
-      if (entry.status().ok()) {
-        PrintByteArray(read_buffer);
-      }
-    } while (true);
-  }
-
-Iterator
-========
-It may be useful to access the entries in the underlying buffer when no drains
-are attached or in crash contexts where dumping out all entries is desirable,
-even if those entries were previously consumed by a drain. This module provides
-an iteration class that is thread-unsafe and like standard iterators, assumes
-that the buffer is not being mutated while iterating. A
-``MultiSink::UnsafeIterationWrapper`` class that supports range-based for-loop
-usage can be acquired via ``MultiSink::UnsafeIteration()``.
-
-The iterator starts from the oldest available entry in the buffer, regardless of
-whether all attached drains have already consumed that entry. This allows the
-iterator to be used even if no drains have been previously attached.
-
-.. code-block:: cpp
-
-  // Create a multisink and a test string to push into it.
-  constexpr char kExampleEntry[] = "Example!";
-  std::byte buffer[1024];
-  MultiSink multisink(buffer);
-  MultiSink::Drain drain;
-
-  // Push an entry before a drain is attached.
-  multisink.HandleEntry(kExampleEntry);
-  multisink.HandleEntry(kExampleEntry);
-
-  // Iterate through the entries, this will print out:
-  //  "Example!"
-  //  "Example!"
-  // Note: PrintByteArray is not a provided utility function.
-  for (ConstByteSpan entry : multisink.UnsafeIteration()) {
-    PrintByteArray(entry);
-  }
-
-  // Attach a drain and consume only one of the entries.
-  std::byte read_buffer[512];
-  uint32_t drop_count = 0;
-
-  multisink.AttachDrain(drain);
-  drain.PopEntry(read_buffer, drop_count);
-
-  // !! A function causes a crash before we've read out all entries.
-  FunctionThatCrashes();
-
-  // ... Crash Context ...
-
-  // You can use a range-based for-loop to walk through all entries,
-  // even though the attached drain has consumed one of them.
-  // This will also print out:
-  //  "Example!"
-  //  "Example!"
-  for (ConstByteSpan entry : multisink.UnsafeIteration()) {
-    PrintByteArray(entry);
-  }
-
-As an alternative to using the ``UnsafeIterationWrapper``,
-``MultiSink::UnsafeForEachEntry()`` may be used to run a callback for each
-entry in the buffer. This helper also provides a way to limit the iteration to
-the ``N`` most recent entries.
-
-Peek & Pop
-==========
-A drain can peek the front multisink entry without removing it using
-`PeekEntry`, which is the same as `PopEntry` without removing the entry from the
-multisink. Once the drain is done with the peeked entry, `PopEntry` will tell
-the drain to remove the peeked entry from the multisink and advance one entry.
-
-.. code-block:: cpp
-
-  constexpr char kExampleEntry[] = "Example!";
-  std::byte buffer[1024];
-  MultiSink multisink(buffer);
-  MultiSink::Drain drain;
-
-  multisink.AttachDrain(drain);
-  multisink.HandleEntry(kExampleEntry);
-
-  std::byte read_buffer[512];
-  uint32_t drop_count = 0;
-  Result<PeekedEntry> peeked_entry = drain.PeekEntry(read_buffer, drop_count);
-  // ... Handle drop_count ...
-
-  if (peeked_entry.ok()) {
-    // Note: SendByteArray is not a provided utility function.
-    Status send_status = SendByteArray(peeked_entry.value().entry());
-    if (send_status.ok()) {
-      drain.PopEntry(peeked_entry.value());
-    } else {
-      // ... Handle send error ...
-    }
-  }
-
-Drop Counts
-===========
-The `PeekEntry` and `PopEntry` return two different drop counts, one for the
-number of entries a drain was skipped forward for providing a small buffer or
-draining too slow, and the other for entries that failed to be added to the
-MultiSink.
diff --git a/pw_multisink/drain.cc b/pw_multisink/drain.cc
new file mode 100644
index 0000000..73b935d
--- /dev/null
+++ b/pw_multisink/drain.cc
@@ -0,0 +1,51 @@
+// Copyright 2021 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_multisink/drain.h"
+
+#include "pw_assert/light.h"
+
+namespace pw {
+namespace multisink {
+
+Result<ConstByteSpan> Drain::GetEntry(ByteSpan entry,
+                                      uint32_t& drop_count_out) {
+  uint32_t entry_sequence_id = 0;
+  drop_count_out = 0;
+  const Result<ConstByteSpan> result =
+      MultiSink::GetEntry(*this, entry, entry_sequence_id);
+
+  // Exit immediately if the result isn't OK or OUT_OF_RANGE, as the
+  // entry_sequence_id cannot be used for computation. Later invocations to
+  // GetEntry will permit readers to determine how far the sequence ID moved
+  // forward.
+  if (!result.ok() && !result.status().IsOutOfRange()) {
+    return result;
+  }
+
+  // Compute the drop count delta by comparing this entry's sequence ID with the
+  // last sequence ID this drain successfully read.
+  //
+  // The drop count calculation simply computes the difference between the
+  // current and last sequence IDs. Consecutive successful reads will always
+  // differ by one at least, so it is subtracted out. If the read was not
+  // successful, the difference is not adjusted.
+  drop_count_out =
+      entry_sequence_id - last_handled_sequence_id_ - (result.ok() ? 1 : 0);
+
+  last_handled_sequence_id_ = entry_sequence_id;
+  return result;
+}
+
+}  // namespace multisink
+}  // namespace pw
diff --git a/pw_multisink/multisink.cc b/pw_multisink/multisink.cc
index b1b95f7..929eec6 100644
--- a/pw_multisink/multisink.cc
+++ b/pw_multisink/multisink.cc
@@ -15,245 +15,47 @@
 
 #include <cstring>
 
-#include "pw_assert/check.h"
-#include "pw_bytes/span.h"
-#include "pw_function/function.h"
-#include "pw_log/log.h"
-#include "pw_result/result.h"
-#include "pw_status/status.h"
+#include "pw_assert/light.h"
+#include "pw_multisink/drain.h"
 #include "pw_status/try.h"
 #include "pw_varint/varint.h"
 
 namespace pw {
 namespace multisink {
 
-void MultiSink::HandleEntry(ConstByteSpan entry) {
-  std::lock_guard lock(lock_);
-  const Status push_back_status = ring_buffer_.PushBack(entry, sequence_id_++);
-  PW_DCHECK_OK(push_back_status);
-  NotifyListeners();
-}
-
-void MultiSink::HandleDropped(uint32_t drop_count) {
-  std::lock_guard lock(lock_);
-  // Updating the sequence ID helps identify where the ingress drop happend when
-  // a drain peeks or pops.
-  sequence_id_ += drop_count;
-  total_ingress_drops_ += drop_count;
-  NotifyListeners();
-}
-
-Status MultiSink::PopEntry(Drain& drain, const Drain::PeekedEntry& entry) {
-  std::lock_guard lock(lock_);
-  PW_DCHECK_PTR_EQ(drain.multisink_, this);
-
-  // Ignore the call if the entry has been handled already.
-  if (entry.sequence_id() == drain.last_handled_sequence_id_) {
-    return OkStatus();
-  }
-
-  uint32_t next_entry_sequence_id;
-  Status peek_status = drain.reader_.PeekFrontPreamble(next_entry_sequence_id);
-  if (!peek_status.ok()) {
-    // Ignore errors if the multisink is empty.
-    if (peek_status.IsOutOfRange()) {
-      return OkStatus();
-    }
-    return peek_status;
-  }
-  if (next_entry_sequence_id == entry.sequence_id()) {
-    // A crash should not happen, since the peek was successful and `lock_` is
-    // still held, there shouldn't be any modifications to the multisink in
-    // between peeking and popping.
-    PW_CHECK_OK(drain.reader_.PopFront());
-    drain.last_handled_sequence_id_ = next_entry_sequence_id;
-  }
-  return OkStatus();
-}
-
-Result<ConstByteSpan> MultiSink::PeekOrPopEntry(
-    Drain& drain,
-    ByteSpan buffer,
-    Request request,
-    uint32_t& drop_count_out,
-    uint32_t& ingress_drop_count_out,
-    uint32_t& entry_sequence_id_out) {
+Result<ConstByteSpan> MultiSink::GetEntry(Drain& drain,
+                                          ByteSpan buffer,
+                                          uint32_t& sequence_id_out) {
   size_t bytes_read = 0;
-  entry_sequence_id_out = 0;
-  drop_count_out = 0;
-  ingress_drop_count_out = 0;
 
-  std::lock_guard lock(lock_);
-  PW_DCHECK_PTR_EQ(drain.multisink_, this);
+  // Exit immediately if there's no multisink attached to this drain.
+  if (drain.multisink_ == nullptr) {
+    return Status::FailedPrecondition();
+  }
 
-  const Status peek_status = drain.reader_.PeekFrontWithPreamble(
-      buffer, entry_sequence_id_out, bytes_read);
-
-  if (peek_status.IsOutOfRange()) {
+  const Status status =
+      drain.reader_.PeekFrontWithPreamble(buffer, sequence_id_out, bytes_read);
+  if (status.IsOutOfRange()) {
     // If the drain has caught up, report the last handled sequence ID so that
     // it can still process any dropped entries.
-    entry_sequence_id_out = sequence_id_ - 1;
-  } else if (!peek_status.ok()) {
-    // Discard the entry if the result isn't OK or OUT_OF_RANGE and exit, as the
-    // entry_sequence_id_out cannot be used for computation. Later invocations
-    // will calculate the drop count.
-    PW_CHECK(drain.reader_.PopFront().ok());
-    return peek_status;
+    sequence_id_out = drain.multisink_->sequence_id_ - 1;
+    return status;
   }
-
-  // Compute the drop count delta by comparing this entry's sequence ID with the
-  // last sequence ID this drain successfully read.
-  //
-  // The drop count calculation simply computes the difference between the
-  // current and last sequence IDs. Consecutive successful reads will always
-  // differ by one at least, so it is subtracted out. If the read was not
-  // successful, the difference is not adjusted.
-  drop_count_out = entry_sequence_id_out - drain.last_handled_sequence_id_ -
-                   (peek_status.ok() ? 1 : 0);
-
-  // Only report the ingress drop count when the drain catches up to where the
-  // drop happened, accounting only for the drops found and no more, as
-  // indicated by the gap in sequence IDs.
-  if (drop_count_out > 0) {
-    ingress_drop_count_out =
-        std::min(drop_count_out,
-                 total_ingress_drops_ - drain.last_handled_ingress_drop_count_);
-    // Remove the ingress drop count duplicated in drop_count_out.
-    drop_count_out -= ingress_drop_count_out;
-    // Check if all the ingress drops were reported.
-    drain.last_handled_ingress_drop_count_ =
-        total_ingress_drops_ > ingress_drop_count_out
-            ? total_ingress_drops_ - ingress_drop_count_out
-            : total_ingress_drops_;
-  }
-
-  // The Peek above may have failed due to OutOfRange, now that we've set the
-  // drop count see if we should return before attempting to pop.
-  if (peek_status.IsOutOfRange()) {
-    // No more entries, update the drain.
-    drain.last_handled_sequence_id_ = entry_sequence_id_out;
-    return peek_status;
-  }
-  if (request == Request::kPop) {
-    PW_CHECK(drain.reader_.PopFront().ok());
-    drain.last_handled_sequence_id_ = entry_sequence_id_out;
-  }
+  PW_CHECK(drain.reader_.PopFront().ok());
   return std::as_bytes(buffer.first(bytes_read));
 }
 
-void MultiSink::AttachDrain(Drain& drain) {
-  std::lock_guard lock(lock_);
-  PW_DCHECK_PTR_EQ(drain.multisink_, nullptr);
+Status MultiSink::AttachDrain(Drain& drain) {
+  PW_DCHECK(drain.multisink_ == nullptr);
   drain.multisink_ = this;
-
-  PW_CHECK_OK(ring_buffer_.AttachReader(drain.reader_));
-  if (&drain == &oldest_entry_drain_) {
-    drain.last_handled_sequence_id_ = sequence_id_ - 1;
-  } else {
-    drain.last_handled_sequence_id_ =
-        oldest_entry_drain_.last_handled_sequence_id_;
-  }
-  drain.last_peek_sequence_id_ = drain.last_handled_sequence_id_;
-  drain.last_handled_ingress_drop_count_ = 0;
+  drain.last_handled_sequence_id_ = sequence_id_ - 1;
+  return ring_buffer_.AttachReader(drain.reader_);
 }
 
-void MultiSink::DetachDrain(Drain& drain) {
-  std::lock_guard lock(lock_);
-  PW_DCHECK_PTR_EQ(drain.multisink_, this);
+Status MultiSink::DetachDrain(Drain& drain) {
+  PW_DCHECK(drain.multisink_ == this);
   drain.multisink_ = nullptr;
-  PW_CHECK_OK(ring_buffer_.DetachReader(drain.reader_),
-              "The drain wasn't already attached.");
-}
-
-void MultiSink::AttachListener(Listener& listener) {
-  std::lock_guard lock(lock_);
-  listeners_.push_back(listener);
-  // Notify the newly added entry, in case there are items in the sink.
-  listener.OnNewEntryAvailable();
-}
-
-void MultiSink::DetachListener(Listener& listener) {
-  std::lock_guard lock(lock_);
-  [[maybe_unused]] bool was_detached = listeners_.remove(listener);
-  PW_DCHECK(was_detached, "The listener was already attached.");
-}
-
-void MultiSink::Clear() {
-  std::lock_guard lock(lock_);
-  ring_buffer_.Clear();
-}
-
-void MultiSink::NotifyListeners() {
-  for (auto& listener : listeners_) {
-    listener.OnNewEntryAvailable();
-  }
-}
-
-Status MultiSink::UnsafeForEachEntry(
-    const Function<void(ConstByteSpan)>& callback, size_t max_num_entries) {
-  MultiSink::UnsafeIterationWrapper multisink_iteration = UnsafeIteration();
-
-  // First count the number of entries.
-  size_t num_entries = 0;
-  for ([[maybe_unused]] ConstByteSpan entry : multisink_iteration) {
-    num_entries++;
-  }
-
-  // Log up to the max number of logs to avoid overflowing the crash log
-  // writer.
-  const size_t first_logged_offset =
-      max_num_entries > num_entries ? 0 : num_entries - max_num_entries;
-  pw::multisink::MultiSink::iterator it = multisink_iteration.begin();
-  for (size_t offset = 0; it != multisink_iteration.end(); ++it, ++offset) {
-    if (offset < first_logged_offset) {
-      continue;  // Skip this log.
-    }
-    callback(*it);
-  }
-  if (!it.status().ok()) {
-    PW_LOG_WARN("Multisink corruption detected, some entries may be missing");
-    return Status::DataLoss();
-  }
-
-  return OkStatus();
-}
-
-Status MultiSink::Drain::PopEntry(const PeekedEntry& entry) {
-  PW_DCHECK_NOTNULL(multisink_);
-  return multisink_->PopEntry(*this, entry);
-}
-
-Result<MultiSink::Drain::PeekedEntry> MultiSink::Drain::PeekEntry(
-    ByteSpan buffer,
-    uint32_t& drop_count_out,
-    uint32_t& ingress_drop_count_out) {
-  PW_DCHECK_NOTNULL(multisink_);
-  uint32_t entry_sequence_id_out;
-  Result<ConstByteSpan> peek_result =
-      multisink_->PeekOrPopEntry(*this,
-                                 buffer,
-                                 Request::kPeek,
-                                 drop_count_out,
-                                 ingress_drop_count_out,
-                                 entry_sequence_id_out);
-  if (!peek_result.ok()) {
-    return peek_result.status();
-  }
-  return PeekedEntry(peek_result.value(), entry_sequence_id_out);
-}
-
-Result<ConstByteSpan> MultiSink::Drain::PopEntry(
-    ByteSpan buffer,
-    uint32_t& drop_count_out,
-    uint32_t& ingress_drop_count_out) {
-  PW_DCHECK_NOTNULL(multisink_);
-  uint32_t entry_sequence_id_out;
-  return multisink_->PeekOrPopEntry(*this,
-                                    buffer,
-                                    Request::kPop,
-                                    drop_count_out,
-                                    ingress_drop_count_out,
-                                    entry_sequence_id_out);
+  return ring_buffer_.DetachReader(drain.reader_);
 }
 
 }  // namespace multisink
diff --git a/pw_multisink/multisink_test.cc b/pw_multisink/multisink_test.cc
index 1f5bea9..4e0f553 100644
--- a/pw_multisink/multisink_test.cc
+++ b/pw_multisink/multisink_test.cc
@@ -14,555 +14,123 @@
 
 #include "pw_multisink/multisink.h"
 
-#include <array>
-#include <cstdint>
-#include <cstring>
-#include <optional>
-#include <span>
-#include <string_view>
-
 #include "gtest/gtest.h"
-#include "pw_function/function.h"
-#include "pw_status/status.h"
+#include "pw_multisink/drain.h"
 
 namespace pw::multisink {
-using Drain = MultiSink::Drain;
-using Listener = MultiSink::Listener;
-
-class CountingListener : public Listener {
- public:
-  void OnNewEntryAvailable() override { notification_count_++; }
-
-  size_t GetNotificationCount() { return notification_count_; }
-
-  void ResetNotificationCount() { notification_count_ = 0; }
-
- private:
-  size_t notification_count_ = 0;
-};
 
 class MultiSinkTest : public ::testing::Test {
  protected:
   static constexpr std::byte kMessage[] = {
       (std::byte)0xDE, (std::byte)0xAD, (std::byte)0xBE, (std::byte)0xEF};
-  static constexpr std::byte kMessageOther[] = {
-      (std::byte)0x12, (std::byte)0x34, (std::byte)0x56, (std::byte)0x78};
   static constexpr size_t kMaxDrains = 3;
-  static constexpr size_t kMaxListeners = 3;
   static constexpr size_t kEntryBufferSize = 1024;
   static constexpr size_t kBufferSize = 5 * kEntryBufferSize;
 
   MultiSinkTest() : multisink_(buffer_) {}
 
-  // Expects the peeked or popped message to equal the provided non-empty
-  // message, and the drop count to match. If `expected_message` is empty, the
-  // Pop call status expected is OUT_OF_RANGE.
-  void ExpectMessageAndDropCounts(Result<ConstByteSpan>& result,
-                                  uint32_t result_drop_count,
-                                  uint32_t result_ingress_drop_count,
-                                  std::optional<ConstByteSpan> expected_message,
-                                  uint32_t expected_drop_count,
-                                  uint32_t expected_ingress_drop_count) {
-    if (!expected_message.has_value()) {
+  void ExpectMessageAndDropCount(Drain& drain,
+                                 std::span<const std::byte> expected_message,
+                                 uint32_t expected_drop_count) {
+    uint32_t drop_count = 0;
+    Result<ConstByteSpan> result = drain.GetEntry(entry_buffer_, drop_count);
+    if (expected_message.empty()) {
       EXPECT_EQ(Status::OutOfRange(), result.status());
     } else {
-      ASSERT_EQ(result.status(), OkStatus());
-      if (!expected_message.value().empty()) {
-        ASSERT_FALSE(result.value().empty());
-        ASSERT_EQ(result.value().size_bytes(),
-                  expected_message.value().size_bytes());
-        EXPECT_EQ(memcmp(result.value().data(),
-                         expected_message.value().data(),
-                         expected_message.value().size_bytes()),
-                  0);
-      }
+      ASSERT_TRUE(result.ok());
+      EXPECT_EQ(memcmp(result.value().data(),
+                       expected_message.data(),
+                       expected_message.size_bytes()),
+                0);
     }
-    EXPECT_EQ(result_drop_count, expected_drop_count);
-    EXPECT_EQ(result_ingress_drop_count, expected_ingress_drop_count);
-  }
-
-  void VerifyPopEntry(Drain& drain,
-                      std::optional<ConstByteSpan> expected_message,
-                      uint32_t expected_drop_count,
-                      uint32_t expected_ingress_drop_count) {
-    uint32_t drop_count = 0;
-    uint32_t ingress_drop_count = 0;
-    Result<ConstByteSpan> result =
-        drain.PopEntry(entry_buffer_, drop_count, ingress_drop_count);
-    ExpectMessageAndDropCounts(result,
-                               drop_count,
-                               ingress_drop_count,
-                               expected_message,
-                               expected_drop_count,
-                               expected_ingress_drop_count);
-  }
-
-  void VerifyPeekResult(const Result<Drain::PeekedEntry>& peek_result,
-                        uint32_t result_drop_count,
-                        uint32_t result_ingress_drop_count,
-                        std::optional<ConstByteSpan> expected_message,
-                        uint32_t expected_drop_count,
-                        uint32_t expected_ingress_drop_count) {
-    if (peek_result.ok()) {
-      ASSERT_FALSE(peek_result.value().entry().empty());
-      Result<ConstByteSpan> verify_result(peek_result.value().entry());
-      ExpectMessageAndDropCounts(verify_result,
-                                 result_drop_count,
-                                 result_ingress_drop_count,
-                                 expected_message,
-                                 expected_drop_count,
-                                 expected_ingress_drop_count);
-      return;
-    }
-    if (expected_message.has_value()) {
-      // Fail since we expected OkStatus.
-      ASSERT_EQ(peek_result.status(), OkStatus());
-    }
-    EXPECT_EQ(Status::OutOfRange(), peek_result.status());
-  }
-
-  void ExpectNotificationCount(CountingListener& listener,
-                               size_t expected_notification_count) {
-    EXPECT_EQ(listener.GetNotificationCount(), expected_notification_count);
-    listener.ResetNotificationCount();
+    EXPECT_EQ(drop_count, expected_drop_count);
   }
 
   std::byte buffer_[kBufferSize];
   std::byte entry_buffer_[kEntryBufferSize];
-  CountingListener listeners_[kMaxListeners];
   Drain drains_[kMaxDrains];
   MultiSink multisink_;
 };
 
 TEST_F(MultiSinkTest, SingleDrain) {
-  multisink_.AttachDrain(drains_[0]);
-  multisink_.AttachListener(listeners_[0]);
-  ExpectNotificationCount(listeners_[0], 1u);
-  multisink_.HandleEntry(kMessage);
+  EXPECT_EQ(OkStatus(), multisink_.AttachDrain(drains_[0]));
+  EXPECT_EQ(OkStatus(), multisink_.HandleEntry(kMessage));
 
   // Single entry push and pop.
-  ExpectNotificationCount(listeners_[0], 1u);
-  VerifyPopEntry(drains_[0], kMessage, 0u, 0u);
-  // Single empty entry push and pop.
-  multisink_.HandleEntry(ConstByteSpan());
-  ExpectNotificationCount(listeners_[0], 1u);
-  VerifyPopEntry(drains_[0], ConstByteSpan(), 0u, 0u);
+  ExpectMessageAndDropCount(drains_[0], kMessage, 0u);
 
   // Multiple entries with intermittent drops.
-  multisink_.HandleEntry(kMessage);
+  EXPECT_EQ(OkStatus(), multisink_.HandleEntry(kMessage));
   multisink_.HandleDropped();
-  multisink_.HandleEntry(kMessage);
-  ExpectNotificationCount(listeners_[0], 3u);
-  VerifyPopEntry(drains_[0], kMessage, 0u, 0u);
-  VerifyPopEntry(drains_[0], kMessage, 0u, 1u);
+  EXPECT_EQ(OkStatus(), multisink_.HandleEntry(kMessage));
+  ExpectMessageAndDropCount(drains_[0], kMessage, 0u);
+  ExpectMessageAndDropCount(drains_[0], kMessage, 1u);
 
   // Send drops only.
   multisink_.HandleDropped();
-  ExpectNotificationCount(listeners_[0], 1u);
-  VerifyPopEntry(drains_[0], std::nullopt, 0u, 1u);
+  ExpectMessageAndDropCount(drains_[0], {}, 1u);
 
   // Confirm out-of-range if no entries are expected.
-  ExpectNotificationCount(listeners_[0], 0u);
-  VerifyPopEntry(drains_[0], std::nullopt, 0u, 0u);
+  ExpectMessageAndDropCount(drains_[0], {}, 0u);
 }
 
 TEST_F(MultiSinkTest, MultipleDrain) {
-  multisink_.AttachDrain(drains_[0]);
-  multisink_.AttachDrain(drains_[1]);
-  multisink_.AttachListener(listeners_[0]);
-  multisink_.AttachListener(listeners_[1]);
-  ExpectNotificationCount(listeners_[0], 1u);
-  ExpectNotificationCount(listeners_[1], 1u);
+  EXPECT_EQ(OkStatus(), multisink_.AttachDrain(drains_[0]));
+  EXPECT_EQ(OkStatus(), multisink_.AttachDrain(drains_[1]));
 
-  multisink_.HandleEntry(kMessage);
-  multisink_.HandleEntry(kMessage);
+  EXPECT_EQ(OkStatus(), multisink_.HandleEntry(kMessage));
+  EXPECT_EQ(OkStatus(), multisink_.HandleEntry(kMessage));
   multisink_.HandleDropped();
-  multisink_.HandleEntry(kMessage);
+  EXPECT_EQ(OkStatus(), multisink_.HandleEntry(kMessage));
   multisink_.HandleDropped();
 
   // Drain one drain entirely.
-  ExpectNotificationCount(listeners_[0], 5u);
-  ExpectNotificationCount(listeners_[1], 5u);
-  VerifyPopEntry(drains_[0], kMessage, 0u, 0u);
-  VerifyPopEntry(drains_[0], kMessage, 0u, 0u);
-  VerifyPopEntry(drains_[0], kMessage, 0u, 1u);
-  VerifyPopEntry(drains_[0], std::nullopt, 0u, 1u);
-  VerifyPopEntry(drains_[0], std::nullopt, 0u, 0u);
+  ExpectMessageAndDropCount(drains_[0], kMessage, 0u);
+  ExpectMessageAndDropCount(drains_[0], kMessage, 0u);
+  ExpectMessageAndDropCount(drains_[0], kMessage, 1u);
+  ExpectMessageAndDropCount(drains_[0], {}, 1u);
+  ExpectMessageAndDropCount(drains_[0], {}, 0u);
 
   // Confirm the other drain can be drained separately.
-  ExpectNotificationCount(listeners_[0], 0u);
-  ExpectNotificationCount(listeners_[1], 0u);
-  VerifyPopEntry(drains_[1], kMessage, 0u, 0u);
-  VerifyPopEntry(drains_[1], kMessage, 0u, 0u);
-  VerifyPopEntry(drains_[1], kMessage, 0u, 1u);
-  VerifyPopEntry(drains_[1], std::nullopt, 0u, 1u);
-  VerifyPopEntry(drains_[1], std::nullopt, 0u, 0u);
+  ExpectMessageAndDropCount(drains_[1], kMessage, 0u);
+  ExpectMessageAndDropCount(drains_[1], kMessage, 0u);
+  ExpectMessageAndDropCount(drains_[1], kMessage, 1u);
+  ExpectMessageAndDropCount(drains_[1], {}, 1u);
+  ExpectMessageAndDropCount(drains_[1], {}, 0u);
 }
 
-TEST_F(MultiSinkTest, LateDrainRegistration) {
-  // Drains attached after entries are pushed should still observe those entries
-  // if they have not been evicted from the ring buffer.
-  multisink_.HandleEntry(kMessage);
+TEST_F(MultiSinkTest, LateRegistration) {
+  // Confirm that entries pushed before attaching a drain are not seen by the
+  // drain.
+  EXPECT_EQ(OkStatus(), multisink_.HandleEntry(kMessage));
 
-  multisink_.AttachDrain(drains_[0]);
-  multisink_.AttachListener(listeners_[0]);
-  ExpectNotificationCount(listeners_[0], 1u);
-  VerifyPopEntry(drains_[0], kMessage, 0u, 0u);
-  VerifyPopEntry(drains_[0], std::nullopt, 0u, 0u);
-
-  multisink_.HandleEntry(kMessage);
-  ExpectNotificationCount(listeners_[0], 1u);
-  VerifyPopEntry(drains_[0], kMessage, 0u, 0u);
-  VerifyPopEntry(drains_[0], std::nullopt, 0u, 0u);
+  // The drain does not observe 'drops' as it did not see entries, and only sees
+  // the one entry that was added after attach.
+  EXPECT_EQ(OkStatus(), multisink_.AttachDrain(drains_[0]));
+  EXPECT_EQ(OkStatus(), multisink_.HandleEntry(kMessage));
+  ExpectMessageAndDropCount(drains_[0], kMessage, 0u);
+  ExpectMessageAndDropCount(drains_[0], {}, 0u);
 }
 
 TEST_F(MultiSinkTest, DynamicDrainRegistration) {
-  multisink_.AttachDrain(drains_[0]);
-  multisink_.AttachListener(listeners_[0]);
-  ExpectNotificationCount(listeners_[0], 1u);
+  EXPECT_EQ(OkStatus(), multisink_.AttachDrain(drains_[0]));
 
   multisink_.HandleDropped();
-  multisink_.HandleEntry(kMessage);
+  EXPECT_EQ(OkStatus(), multisink_.HandleEntry(kMessage));
   multisink_.HandleDropped();
-  multisink_.HandleEntry(kMessage);
+  EXPECT_EQ(OkStatus(), multisink_.HandleEntry(kMessage));
 
   // Drain out one message and detach it.
-  ExpectNotificationCount(listeners_[0], 4u);
-  VerifyPopEntry(drains_[0], kMessage, 0u, 1u);
-  multisink_.DetachDrain(drains_[0]);
-  multisink_.DetachListener(listeners_[0]);
+  ExpectMessageAndDropCount(drains_[0], kMessage, 1u);
+  EXPECT_EQ(OkStatus(), multisink_.DetachDrain(drains_[0]));
 
-  // Re-attaching the drain should reproduce the last observed message. Note
-  // that notifications are not expected, nor are drops observed before the
-  // first valid message in the buffer.
-  multisink_.AttachDrain(drains_[0]);
-  multisink_.AttachListener(listeners_[0]);
-  ExpectNotificationCount(listeners_[0], 1u);
-  VerifyPopEntry(drains_[0], kMessage, 0u, 1u);
-  VerifyPopEntry(drains_[0], kMessage, 0u, 1u);
-  VerifyPopEntry(drains_[0], std::nullopt, 0u, 0u);
+  // Reattach the drain and confirm that you only see events after attaching.
+  EXPECT_EQ(OkStatus(), multisink_.AttachDrain(drains_[0]));
+  ExpectMessageAndDropCount(drains_[0], {}, 0u);
 
-  multisink_.HandleEntry(kMessage);
-  ExpectNotificationCount(listeners_[0], 1u);
-  VerifyPopEntry(drains_[0], kMessage, 0u, 0u);
-  VerifyPopEntry(drains_[0], std::nullopt, 0u, 0u);
-}
-
-TEST_F(MultiSinkTest, TooSmallBuffer) {
-  multisink_.AttachDrain(drains_[0]);
-
-  // Insert an entry and a drop, then try to read into an insufficient buffer.
-  uint32_t drop_count = 0;
-  uint32_t ingress_drop_count = 0;
-  multisink_.HandleDropped();
-  multisink_.HandleEntry(kMessage);
-
-  // Attempting to acquire an entry with a small buffer should result in
-  // RESOURCE_EXHAUSTED and remove it.
-  Result<ConstByteSpan> result = drains_[0].PopEntry(
-      std::span(entry_buffer_, 1), drop_count, ingress_drop_count);
-  EXPECT_EQ(result.status(), Status::ResourceExhausted());
-
-  VerifyPopEntry(drains_[0], std::nullopt, 1u, 1u);
-}
-
-TEST_F(MultiSinkTest, Iterator) {
-  multisink_.AttachDrain(drains_[0]);
-
-  // Insert entries and consume them all.
-  multisink_.HandleEntry(kMessage);
-  multisink_.HandleEntry(kMessage);
-  multisink_.HandleEntry(kMessage);
-
-  VerifyPopEntry(drains_[0], kMessage, 0u, 0u);
-  VerifyPopEntry(drains_[0], kMessage, 0u, 0u);
-  VerifyPopEntry(drains_[0], kMessage, 0u, 0u);
-
-  // Confirm that the iterator still observes the messages in the ring buffer.
-  size_t iterated_entries = 0;
-  for (ConstByteSpan entry : multisink_.UnsafeIteration()) {
-    EXPECT_EQ(memcmp(entry.data(), kMessage, sizeof(kMessage)), 0);
-    iterated_entries++;
-  }
-  EXPECT_EQ(iterated_entries, 3u);
-}
-
-TEST_F(MultiSinkTest, IteratorNoDrains) {
-  // Insert entries with no drains attached. Even though there are no consumers,
-  // iterators should still walk from the oldest entry.
-  multisink_.HandleEntry(kMessage);
-  multisink_.HandleEntry(kMessage);
-  multisink_.HandleEntry(kMessage);
-
-  // Confirm that the iterator still observes the messages in the ring buffer.
-  size_t iterated_entries = 0;
-  for (ConstByteSpan entry : multisink_.UnsafeIteration()) {
-    EXPECT_EQ(memcmp(entry.data(), kMessage, sizeof(kMessage)), 0);
-    iterated_entries++;
-  }
-  EXPECT_EQ(iterated_entries, 3u);
-}
-
-TEST_F(MultiSinkTest, IteratorNoEntries) {
-  // Attach a drain, but don't add any entries.
-  multisink_.AttachDrain(drains_[0]);
-  // Confirm that the iterator has no entries.
-  MultiSink::UnsafeIterationWrapper unsafe_iterator =
-      multisink_.UnsafeIteration();
-  EXPECT_EQ(unsafe_iterator.begin(), unsafe_iterator.end());
-}
-
-TEST_F(MultiSinkTest, PeekEntryNoEntries) {
-  multisink_.AttachDrain(drains_[0]);
-
-  // Peek empty multisink.
-  uint32_t drop_count = 0;
-  uint32_t ingress_drop_count = 0;
-  auto peek_result =
-      drains_[0].PeekEntry(entry_buffer_, drop_count, ingress_drop_count);
-  VerifyPeekResult(peek_result, 0, drop_count, std::nullopt, 0, 0);
-}
-
-TEST_F(MultiSinkTest, PeekAndPop) {
-  multisink_.AttachDrain(drains_[0]);
-  multisink_.AttachDrain(drains_[1]);
-
-  // Peek entry after multisink has some entries.
-  multisink_.HandleEntry(kMessage);
-  multisink_.HandleEntry(kMessageOther);
-  uint32_t drop_count = 0;
-  uint32_t ingress_drop_count = 0;
-  auto first_peek_result =
-      drains_[0].PeekEntry(entry_buffer_, drop_count, ingress_drop_count);
-  VerifyPeekResult(
-      first_peek_result, drop_count, ingress_drop_count, kMessage, 0, 0);
-
-  // Multiple peeks must return the front message.
-  auto peek_duplicate =
-      drains_[0].PeekEntry(entry_buffer_, drop_count, ingress_drop_count);
-  VerifyPeekResult(
-      peek_duplicate, drop_count, ingress_drop_count, kMessage, 0, 0);
-  // A second drain must peek the front message.
-  auto peek_other_drain =
-      drains_[1].PeekEntry(entry_buffer_, drop_count, ingress_drop_count);
-  VerifyPeekResult(
-      peek_other_drain, drop_count, ingress_drop_count, kMessage, 0, 0);
-
-  // After a drain pops a peeked entry, the next peek call must return the next
-  // message.
-  ASSERT_EQ(drains_[0].PopEntry(first_peek_result.value()), OkStatus());
-  auto second_peek_result =
-      drains_[0].PeekEntry(entry_buffer_, drop_count, ingress_drop_count);
-  VerifyPeekResult(
-      second_peek_result, drop_count, ingress_drop_count, kMessageOther, 0, 0);
-  // Slower readers must be unchanged.
-  auto peek_other_drain_duplicate =
-      drains_[1].PeekEntry(entry_buffer_, drop_count, ingress_drop_count);
-  VerifyPeekResult(peek_other_drain_duplicate,
-                   drop_count,
-                   ingress_drop_count,
-                   kMessage,
-                   0,
-                   0);
-
-  // PopEntry prior to popping the previously peeked entry.
-  VerifyPopEntry(drains_[0], kMessageOther, 0, 0);
-  // Popping an entry already handled must not trigger errors.
-  ASSERT_EQ(drains_[0].PopEntry(second_peek_result.value()), OkStatus());
-  // Popping with an old peek context must not trigger errors.
-  ASSERT_EQ(drains_[0].PopEntry(first_peek_result.value()), OkStatus());
-
-  // Multisink is empty, pops and peeks should trigger OUT_OF_RANGE.
-  VerifyPopEntry(drains_[0], std::nullopt, 0, 0);
-  auto empty_peek_result =
-      drains_[0].PeekEntry(entry_buffer_, drop_count, ingress_drop_count);
-  VerifyPeekResult(
-      empty_peek_result, drop_count, ingress_drop_count, std::nullopt, 0, 0);
-
-  // // Slower readers must be unchanged.
-  auto peek_other_drain_unchanged =
-      drains_[1].PeekEntry(entry_buffer_, drop_count, ingress_drop_count);
-  VerifyPeekResult(peek_other_drain_unchanged,
-                   drop_count,
-                   ingress_drop_count,
-                   kMessage,
-                   0,
-                   0);
-}
-
-TEST_F(MultiSinkTest, PeekReportsIngressDropCount) {
-  multisink_.AttachDrain(drains_[0]);
-
-  // Peek entry after multisink has some entries.
-  multisink_.HandleEntry(kMessage);
-  const uint32_t ingress_drops = 10;
-  multisink_.HandleDropped(ingress_drops);
-
-  uint32_t drop_count = 0;
-  uint32_t ingress_drop_count = 0;
-  auto peek_result1 =
-      drains_[0].PeekEntry(entry_buffer_, drop_count, ingress_drop_count);
-  // No drops reported until the drain finds a gap in the sequence IDs.
-  VerifyPeekResult(
-      peek_result1, drop_count, ingress_drop_count, kMessage, 0, 0);
-
-  // Popping the peeked entry advances the drain, and a new peek will find the
-  // gap in sequence IDs.
-  ASSERT_EQ(drains_[0].PopEntry(peek_result1.value()), OkStatus());
-  auto peek_result2 =
-      drains_[0].PeekEntry(entry_buffer_, drop_count, ingress_drop_count);
-  ASSERT_EQ(peek_result2.status(), Status::OutOfRange());
-  EXPECT_EQ(drop_count, 0u);
-  EXPECT_EQ(ingress_drop_count, ingress_drops);
-}
-
-TEST_F(MultiSinkTest, PeekReportsSlowDrainDropCount) {
-  multisink_.AttachDrain(drains_[0]);
-
-  // Add entries until buffer is full and drain has to be advanced.
-  // The sequence ID takes 1 byte when less than 128.
-  const size_t max_multisink_messages = 128;
-  const size_t buffer_entry_size = kBufferSize / max_multisink_messages;
-  // Account for 1 byte of preamble (sequnce ID) and 1 byte of data size.
-  const size_t message_size = buffer_entry_size - 2;
-  std::array<std::byte, message_size> message;
-  std::memset(message.data(), 'a', message.size());
-  for (size_t i = 0; i < max_multisink_messages; ++i) {
-    multisink_.HandleEntry(message);
-  }
-
-  // At this point the buffer is full, but the sequence ID will take 1 more byte
-  // in the preamble, meaning that adding N new entries, drops N + 1 entries.
-  // Account for that offset.
-  const size_t expected_drops = 5;
-  for (size_t i = 1; i < expected_drops; ++i) {
-    multisink_.HandleEntry(message);
-  }
-
-  uint32_t drop_count = 0;
-  uint32_t ingress_drop_count = 0;
-  auto peek_result =
-      drains_[0].PeekEntry(entry_buffer_, drop_count, ingress_drop_count);
-  VerifyPeekResult(
-      peek_result, drop_count, ingress_drop_count, message, expected_drops, 0);
-}
-
-TEST_F(MultiSinkTest, IngressDropCountOverflow) {
-  multisink_.AttachDrain(drains_[0]);
-
-  // Make drain's last handled drop larger than multisink drop count, which
-  // overflowed.
-  const uint32_t drop_count_close_to_overflow =
-      std::numeric_limits<uint32_t>::max() - 3;
-  multisink_.HandleDropped(drop_count_close_to_overflow);
-  multisink_.HandleEntry(kMessage);
-
-  // Catch up drain's drop count.
-  uint32_t drop_count = 0;
-  uint32_t ingress_drop_count = 0;
-  auto peek_result1 =
-      drains_[0].PeekEntry(entry_buffer_, drop_count, ingress_drop_count);
-  VerifyPeekResult(peek_result1,
-                   drop_count,
-                   ingress_drop_count,
-                   kMessage,
-                   0,
-                   drop_count_close_to_overflow);
-  // Popping the peeked entry advances the drain, and a new peek will find the
-  // gap in sequence IDs.
-  ASSERT_EQ(drains_[0].PopEntry(peek_result1.value()), OkStatus());
-
-  // Overflow multisink's drop count.
-  const uint32_t expected_ingress_drop_count = 10;
-  multisink_.HandleDropped(expected_ingress_drop_count);
-
-  auto peek_result2 =
-      drains_[0].PeekEntry(entry_buffer_, drop_count, ingress_drop_count);
-  ASSERT_EQ(peek_result2.status(), Status::OutOfRange());
-  EXPECT_EQ(drop_count, 0u);
-  EXPECT_EQ(ingress_drop_count, expected_ingress_drop_count);
-
-  multisink_.HandleEntry(kMessage);
-  auto peek_result3 =
-      drains_[0].PeekEntry(entry_buffer_, drop_count, ingress_drop_count);
-  VerifyPeekResult(
-      peek_result3, drop_count, ingress_drop_count, kMessage, 0, 0);
-}
-
-TEST_F(MultiSinkTest, DetachedDrainReportsDropCount) {
-  multisink_.AttachDrain(drains_[0]);
-
-  const uint32_t ingress_drops = 10;
-  multisink_.HandleDropped(ingress_drops);
-  multisink_.HandleEntry(kMessage);
-  VerifyPopEntry(drains_[0], kMessage, 0, ingress_drops);
-
-  // Detaching and attaching drain should report the same drops.
-  multisink_.DetachDrain(drains_[0]);
-  multisink_.AttachDrain(drains_[0]);
-  VerifyPopEntry(drains_[0], kMessage, 0, ingress_drops);
-}
-
-TEST(UnsafeIteration, NoLimit) {
-  constexpr std::array<std::string_view, 5> kExpectedEntries{
-      "one", "two", "three", "four", "five"};
-  std::array<std::byte, 32> buffer;
-  MultiSink multisink(buffer);
-
-  for (std::string_view entry : kExpectedEntries) {
-    multisink.HandleEntry(std::as_bytes(std::span(entry)));
-  }
-
-  size_t entry_count = 0;
-  struct {
-    size_t& entry_count;
-    std::span<const std::string_view> expected_results;
-  } ctx{entry_count, kExpectedEntries};
-  auto cb = [&ctx](ConstByteSpan data) {
-    std::string_view expected_entry = ctx.expected_results[ctx.entry_count];
-    EXPECT_EQ(data.size(), expected_entry.size());
-    const int result =
-        memcmp(data.data(), expected_entry.data(), expected_entry.size());
-    EXPECT_EQ(0, result);
-    ctx.entry_count++;
-  };
-
-  EXPECT_EQ(OkStatus(), multisink.UnsafeForEachEntry(cb));
-  EXPECT_EQ(kExpectedEntries.size(), entry_count);
-}
-
-TEST(UnsafeIteration, Subset) {
-  constexpr std::array<std::string_view, 5> kExpectedEntries{
-      "one", "two", "three", "four", "five"};
-  constexpr size_t kStartOffset = 3;
-  constexpr size_t kExpectedEntriesMaxEntries =
-      kExpectedEntries.size() - kStartOffset;
-  std::array<std::byte, 32> buffer;
-  MultiSink multisink(buffer);
-
-  for (std::string_view entry : kExpectedEntries) {
-    multisink.HandleEntry(std::as_bytes(std::span(entry)));
-  }
-
-  size_t entry_count = 0;
-  struct {
-    size_t& entry_count;
-    std::span<const std::string_view> expected_results;
-  } ctx{entry_count, kExpectedEntries};
-  auto cb = [&ctx](ConstByteSpan data) {
-    std::string_view expected_entry =
-        ctx.expected_results[ctx.entry_count + kStartOffset];
-    EXPECT_EQ(data.size(), expected_entry.size());
-    const int result =
-        memcmp(data.data(), expected_entry.data(), expected_entry.size());
-    EXPECT_EQ(0, result);
-    ctx.entry_count++;
-  };
-
-  EXPECT_EQ(
-      OkStatus(),
-      multisink.UnsafeForEachEntry(cb, kExpectedEntries.size() - kStartOffset));
-  EXPECT_EQ(kExpectedEntriesMaxEntries, entry_count);
+  EXPECT_EQ(OkStatus(), multisink_.HandleEntry(kMessage));
+  ExpectMessageAndDropCount(drains_[0], kMessage, 0u);
+  ExpectMessageAndDropCount(drains_[0], {}, 0u);
 }
 
 }  // namespace pw::multisink
diff --git a/pw_multisink/multisink_threaded_test.cc b/pw_multisink/multisink_threaded_test.cc
deleted file mode 100644
index f9941b5..0000000
--- a/pw_multisink/multisink_threaded_test.cc
+++ /dev/null
@@ -1,372 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstddef>
-#include <cstdint>
-#include <span>
-
-#include "gtest/gtest.h"
-#include "pw_containers/vector.h"
-#include "pw_multisink/multisink.h"
-#include "pw_multisink/test_thread.h"
-#include "pw_string/string_builder.h"
-#include "pw_thread/thread.h"
-#include "pw_thread/yield.h"
-
-namespace pw::multisink {
-namespace {
-constexpr size_t kEntryBufferSize = sizeof("message 000");
-constexpr size_t kMaxMessageCount = 250;
-constexpr size_t kBufferSize = kMaxMessageCount * kEntryBufferSize;
-
-using MessageSpan = std::span<const StringBuffer<kEntryBufferSize>>;
-
-void CompareSentAndReceivedMessages(const MessageSpan& sent_messages,
-                                    const MessageSpan& received_messages) {
-  ASSERT_EQ(sent_messages.size(), received_messages.size());
-  for (size_t i = 0; i < sent_messages.size(); ++i) {
-    ASSERT_EQ(sent_messages[i].size(), received_messages[i].size());
-    EXPECT_EQ(std::string_view(sent_messages[i]),
-              std::string_view(received_messages[i]));
-  }
-}
-}  // namespace
-
-// Static message pool to avoid recreating messages for every test and avoids
-// using std::string.
-class MessagePool {
- public:
-  static MessagePool& Instance() {
-    static MessagePool instance;
-    return instance;
-  }
-
-  MessagePool(const MessagePool&) = delete;
-  MessagePool& operator=(const MessagePool&) = delete;
-  MessagePool(MessagePool&&) = delete;
-  MessagePool& operator=(MessagePool&&) = delete;
-
-  MessageSpan GetMessages(size_t message_count) const {
-    PW_ASSERT(message_count <= messages_.size());
-    return MessageSpan(messages_.begin(), message_count);
-  }
-
- private:
-  MessagePool() {
-    for (size_t i = 0; i < kMaxMessageCount; ++i) {
-      messages_.emplace_back();
-      messages_.back() << "message %u" << static_cast<unsigned int>(i);
-    }
-  }
-
-  Vector<StringBuffer<kEntryBufferSize>, kMaxMessageCount> messages_;
-};
-
-// Continuously reads logs from a multisink, using PopEntry() and stores copies
-// of the retrieved messages for later verification. The thread stops when the
-// the number of read messages and total drop count matches the expected count.
-class LogPopReaderThread : public thread::ThreadCore {
- public:
-  LogPopReaderThread(MultiSink& multisink,
-                     uint32_t expected_message_and_drop_count)
-      : multisink_(multisink),
-        total_drop_count_(0),
-        expected_message_and_drop_count_(expected_message_and_drop_count) {
-    PW_ASSERT(expected_message_and_drop_count_ <= kMaxMessageCount);
-  }
-
-  uint32_t drop_count() { return total_drop_count_; }
-
-  const MessageSpan received_messages() {
-    return MessageSpan(received_messages_.begin(), received_messages_.size());
-  }
-
-  void Run() override {
-    multisink_.AttachDrain(drain_);
-    ReadAllEntries();
-  };
-
-  virtual void ReadAllEntries() {
-    do {
-      uint32_t drop_count = 0;
-      uint32_t ingress_drop_count = 0;
-      const Result<ConstByteSpan> possible_entry =
-          drain_.PopEntry(entry_buffer_, drop_count, ingress_drop_count);
-      total_drop_count_ += drop_count + ingress_drop_count;
-      if (possible_entry.status().IsOutOfRange()) {
-        pw::this_thread::yield();
-        continue;
-      }
-      ASSERT_EQ(possible_entry.status(), OkStatus());
-      if (received_messages_.full()) {
-        return;
-      }
-      received_messages_.emplace_back();
-      received_messages_.back() << std::string_view(
-          reinterpret_cast<const char*>(possible_entry.value().data()),
-          possible_entry.value().size());
-      pw::this_thread::yield();
-    } while (total_drop_count_ + received_messages_.size() <
-             expected_message_and_drop_count_);
-  }
-
- protected:
-  MultiSink::Drain drain_;
-  MultiSink& multisink_;
-  std::array<std::byte, kEntryBufferSize> entry_buffer_;
-  uint32_t total_drop_count_;
-  const uint32_t expected_message_and_drop_count_;
-  Vector<StringBuffer<kEntryBufferSize>, kMaxMessageCount> received_messages_;
-};
-
-class LogPeekAndCommitReaderThread : public LogPopReaderThread {
- public:
-  LogPeekAndCommitReaderThread(MultiSink& multisink,
-                               uint32_t expected_message_and_drop_count)
-      : LogPopReaderThread(multisink, expected_message_and_drop_count) {}
-
-  virtual void ReadAllEntries() {
-    do {
-      uint32_t drop_count = 0;
-      uint32_t ingress_drop_count = 0;
-      const Result<MultiSink::Drain::PeekedEntry> possible_entry =
-          drain_.PeekEntry(entry_buffer_, drop_count, ingress_drop_count);
-      total_drop_count_ += drop_count + ingress_drop_count;
-      if (possible_entry.status().IsOutOfRange()) {
-        pw::this_thread::yield();
-        continue;
-      }
-      ASSERT_EQ(possible_entry.status(), OkStatus());
-      if (received_messages_.full()) {
-        return;
-      }
-      pw::this_thread::yield();
-      received_messages_.emplace_back();
-      received_messages_.back() << std::string_view(
-          reinterpret_cast<const char*>(possible_entry.value().entry().data()),
-          possible_entry.value().entry().size());
-      ASSERT_EQ(drain_.PopEntry(possible_entry.value()), OkStatus());
-      pw::this_thread::yield();
-    } while (total_drop_count_ + received_messages_.size() <
-             expected_message_and_drop_count_);
-  }
-};
-
-// Adds the provided messages to the shared multisink.
-class LogWriterThread : public thread::ThreadCore {
- public:
-  LogWriterThread(MultiSink& multisink, const MessageSpan& message_stack)
-      : multisink_(multisink), message_stack_(message_stack) {}
-
-  void Run() override {
-    for (const auto& message : message_stack_) {
-      multisink_.HandleEntry(
-          std::as_bytes(std::span(std::string_view(message))));
-      pw::this_thread::yield();
-    }
-  };
-
- private:
-  MultiSink& multisink_;
-  const MessageSpan& message_stack_;
-};
-
-class MultiSinkTest : public ::testing::Test {
- protected:
-  MultiSinkTest() : multisink_(buffer_) {}
-
-  std::byte buffer_[kBufferSize];
-  MultiSink multisink_;
-
- private:
-};
-
-TEST_F(MultiSinkTest, SingleWriterSingleReader) {
-  const uint32_t log_count = 100;
-  const uint32_t drop_count = 5;
-  const uint32_t expected_message_and_drop_count = log_count + drop_count;
-  const auto message_stack = MessagePool::Instance().GetMessages(log_count);
-
-  // Start reader thread.
-  LogPopReaderThread reader_thread_core(multisink_,
-                                        expected_message_and_drop_count);
-  thread::Thread reader_thread(test::MultiSinkTestThreadOptions(),
-                               reader_thread_core);
-  // Start writer thread.
-  LogWriterThread writer_thread_core(multisink_, message_stack);
-  thread::Thread writer_thread(test::MultiSinkTestThreadOptions(),
-                               writer_thread_core);
-
-  // Wait for writer thread to end.
-  writer_thread.join();
-  multisink_.HandleDropped(drop_count);
-  reader_thread.join();
-
-  EXPECT_EQ(reader_thread_core.drop_count(), drop_count);
-  CompareSentAndReceivedMessages(message_stack,
-                                 reader_thread_core.received_messages());
-}
-
-TEST_F(MultiSinkTest, SingleWriterSinglePeekAndCommitReader) {
-  const uint32_t log_count = 100;
-  const uint32_t drop_count = 5;
-  const uint32_t expected_message_and_drop_count = log_count + drop_count;
-  const auto message_stack = MessagePool::Instance().GetMessages(log_count);
-
-  // Start reader thread.
-  LogPeekAndCommitReaderThread reader_thread_core(
-      multisink_, expected_message_and_drop_count);
-  thread::Thread reader_thread(test::MultiSinkTestThreadOptions(),
-                               reader_thread_core);
-  // Start writer thread.
-  LogWriterThread writer_thread_core(multisink_, message_stack);
-  thread::Thread writer_thread(test::MultiSinkTestThreadOptions(),
-                               writer_thread_core);
-
-  // Wait for writer thread to end.
-  writer_thread.join();
-  multisink_.HandleDropped(drop_count);
-  reader_thread.join();
-
-  EXPECT_EQ(reader_thread_core.drop_count(), drop_count);
-  CompareSentAndReceivedMessages(message_stack,
-                                 reader_thread_core.received_messages());
-}
-
-TEST_F(MultiSinkTest, SingleWriterMultipleReaders) {
-  const uint32_t log_count = 100;
-  const uint32_t drop_count = 5;
-  const uint32_t expected_message_and_drop_count = log_count + drop_count;
-  const auto message_stack = MessagePool::Instance().GetMessages(log_count);
-
-  // Start reader threads.
-  LogPopReaderThread reader_thread_core1(multisink_,
-                                         expected_message_and_drop_count);
-  thread::Thread reader_thread1(test::MultiSinkTestThreadOptions(),
-                                reader_thread_core1);
-  LogPopReaderThread reader_thread_core2(multisink_,
-                                         expected_message_and_drop_count);
-  thread::Thread reader_thread2(test::MultiSinkTestThreadOptions(),
-                                reader_thread_core2);
-  LogPeekAndCommitReaderThread reader_thread_core3(
-      multisink_, expected_message_and_drop_count);
-  thread::Thread reader_thread3(test::MultiSinkTestThreadOptions(),
-                                reader_thread_core3);
-  // Start writer thread.
-  LogWriterThread writer_thread_core(multisink_, message_stack);
-  thread::Thread writer_thread(test::MultiSinkTestThreadOptions(),
-                               writer_thread_core);
-
-  // Wait for writer thread to end.
-  writer_thread.join();
-  multisink_.HandleDropped(drop_count);
-  reader_thread1.join();
-  reader_thread2.join();
-  reader_thread3.join();
-
-  EXPECT_EQ(reader_thread_core1.drop_count(), drop_count);
-  CompareSentAndReceivedMessages(message_stack,
-                                 reader_thread_core1.received_messages());
-  EXPECT_EQ(reader_thread_core2.drop_count(), drop_count);
-  CompareSentAndReceivedMessages(message_stack,
-                                 reader_thread_core2.received_messages());
-  EXPECT_EQ(reader_thread_core3.drop_count(), drop_count);
-  CompareSentAndReceivedMessages(message_stack,
-                                 reader_thread_core3.received_messages());
-}
-
-TEST_F(MultiSinkTest, MultipleWritersMultipleReaders) {
-  const uint32_t log_count = 100;
-  const uint32_t drop_count = 7;
-  const uint32_t expected_message_and_drop_count = 2 * log_count + drop_count;
-  const auto message_stack = MessagePool::Instance().GetMessages(log_count);
-
-  // Start reader threads.
-  LogPopReaderThread reader_thread_core1(multisink_,
-                                         expected_message_and_drop_count);
-  thread::Thread reader_thread1(test::MultiSinkTestThreadOptions(),
-                                reader_thread_core1);
-  LogPopReaderThread reader_thread_core2(multisink_,
-                                         expected_message_and_drop_count);
-  thread::Thread reader_thread2(test::MultiSinkTestThreadOptions(),
-                                reader_thread_core2);
-  LogPeekAndCommitReaderThread reader_thread_core3(
-      multisink_, expected_message_and_drop_count);
-  thread::Thread reader_thread3(test::MultiSinkTestThreadOptions(),
-                                reader_thread_core3);
-  // Start writer threads.
-  LogWriterThread writer_thread_core1(multisink_, message_stack);
-  thread::Thread writer_thread1(test::MultiSinkTestThreadOptions(),
-                                writer_thread_core1);
-  LogWriterThread writer_thread_core2(multisink_, message_stack);
-  thread::Thread writer_thread2(test::MultiSinkTestThreadOptions(),
-                                writer_thread_core2);
-
-  // Wait for writer thread to end.
-  writer_thread1.join();
-  writer_thread2.join();
-  multisink_.HandleDropped(drop_count);
-  reader_thread1.join();
-  reader_thread2.join();
-  reader_thread3.join();
-
-  EXPECT_EQ(reader_thread_core1.drop_count(), drop_count);
-  EXPECT_EQ(reader_thread_core2.drop_count(), drop_count);
-  EXPECT_EQ(reader_thread_core3.drop_count(), drop_count);
-  // Since we don't know the order that messages came in, we can't check them.
-  EXPECT_EQ(reader_thread_core1.received_messages().size(),
-            expected_message_and_drop_count - drop_count);
-  EXPECT_EQ(reader_thread_core2.received_messages().size(),
-            expected_message_and_drop_count - drop_count);
-  EXPECT_EQ(reader_thread_core3.received_messages().size(),
-            expected_message_and_drop_count - drop_count);
-}
-
-TEST_F(MultiSinkTest, OverflowMultisink) {
-  // Expect the multisink to overflow and readers to not fail when poping, or
-  // peeking and commiting entries.
-  const size_t log_count = kMaxMessageCount;
-  const size_t max_buffer_entry_count = 20;
-  std::byte small_multisink_buffer[max_buffer_entry_count * kEntryBufferSize];
-  MultiSink small_multisink(small_multisink_buffer);
-
-  const auto message_stack = MessagePool::Instance().GetMessages(log_count);
-
-  // Start reader threads.
-  LogPeekAndCommitReaderThread reader_thread_core1(small_multisink, log_count);
-  thread::Thread reader_thread1(test::MultiSinkTestThreadOptions(),
-                                reader_thread_core1);
-  LogPopReaderThread reader_thread_core2(small_multisink, log_count);
-  thread::Thread reader_thread2(test::MultiSinkTestThreadOptions(),
-                                reader_thread_core2);
-
-  // Start writer threads.
-  LogWriterThread writer_thread_core1(small_multisink, message_stack);
-  thread::Thread writer_thread1(test::MultiSinkTestThreadOptions(),
-                                writer_thread_core1);
-  LogWriterThread writer_thread_core2(small_multisink, message_stack);
-  thread::Thread writer_thread2(test::MultiSinkTestThreadOptions(),
-                                writer_thread_core2);
-
-  // Wait for writer thread to end.
-  writer_thread1.join();
-  writer_thread2.join();
-  reader_thread1.join();
-  reader_thread2.join();
-
-  // Verifying received messages and drop message counts is unreliable as we
-  // can't control the order threads will operate.
-}
-
-}  // namespace pw::multisink
diff --git a/pw_multisink/public/pw_multisink/config.h b/pw_multisink/public/pw_multisink/config.h
deleted file mode 100644
index 98de4a7..0000000
--- a/pw_multisink/public/pw_multisink/config.h
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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
-
-// PW_MULTISINK_LOCK_INTERRUPT_SAFE controls whether an interrupt-safe lock is
-// used when reading and writing from the underlying ring-buffer. This is
-// enabled by default, using an interrupt spin-lock instead of a mutex.
-// Disabling this alters the entry precondition of the multisink, requiring that
-// it not be invoked from an interrupt context.
-#if !defined(PW_MULTISINK_CONFIG_LOCK_INTERRUPT_SAFE)
-#define PW_MULTISINK_CONFIG_LOCK_INTERRUPT_SAFE 1
-#endif  // !defined(PW_MULTISINK_CONFIG_LOCK_INTERRUPT_SAFE)
-
-#if PW_MULTISINK_CONFIG_LOCK_INTERRUPT_SAFE
-#include "pw_sync/interrupt_spin_lock.h"
-#else  // !PW_MULTISINK_CONFIG_LOCK_INTERRUPT_SAFE
-#include "pw_sync/mutex.h"
-#endif  // PW_MULTISINK_CONFIG_LOCK_INTERRUPT_SAFE
-
-namespace pw {
-namespace multisink {
-
-#if PW_MULTISINK_CONFIG_LOCK_INTERRUPT_SAFE
-using LockType = pw::sync::InterruptSpinLock;
-#else   // !PW_MULTISINK_CONFIG_LOCK_INTERRUPT_SAFE
-using LockType = pw::sync::Mutex;
-#endif  // PW_MULTISINK_CONFIG_LOCK_INTERRUPT_SAFE
-
-}  // namespace multisink
-}  // namespace pw
diff --git a/pw_multisink/public/pw_multisink/drain.h b/pw_multisink/public/pw_multisink/drain.h
new file mode 100644
index 0000000..90aae11
--- /dev/null
+++ b/pw_multisink/public/pw_multisink/drain.h
@@ -0,0 +1,65 @@
+// Copyright 2021 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_multisink/multisink.h"
+#include "pw_status/status.h"
+
+namespace pw {
+namespace multisink {
+
+// An asynchronous reader which is attached to a MultiSink via AttachDrain.
+// Each Drain holds a PrefixedEntryRingBufferMulti::Reader and abstracts away
+// entry sequence information for clients.
+class Drain {
+ public:
+  constexpr Drain() : last_handled_sequence_id_(0), multisink_(nullptr) {}
+
+  // Returns the next available entry if it exists and acquires the latest drop
+  // count in parallel.
+  //
+  // The `drop_count_out` is set to the number of entries that were dropped
+  // since the last call to GetEntry, if the read operation was successful or
+  // indicated that no entries were available to read. If the read operation
+  // fails otherwise, the `drop_count_out` is set to zero.
+  //
+  // Drop counts are internally maintained with a 32-bit counter. If UINT32_MAX
+  // entries have been handled by the attached multisink between subsequent
+  // calls to GetEntry, the drop count will overflow and will report a lower
+  // count erroneously. Users should ensure that sinks call GetEntry
+  // at least once every UINT32_MAX entries.
+  //
+  // Return values:
+  // Ok - An entry was successfully read from the multisink. The drop_count_out
+  // is set to the count of entries that were dropped since the last call
+  // to GetEntry.
+  // FailedPrecondition - The drain must be attached to a sink.
+  // OutOfRange - No entries were available, the drop_count_out is set to the
+  // number of entries that were dropped since the last call to GetEntry.
+  // ResourceExhausted - The provided buffer was not large enough to store the
+  // next available entry.
+  // DataLoss - An entry was read from the multisink, but did not match the
+  // expected format (i.e. failed to decode).
+  // InvalidArgument - The drain is not currently associated with a multisink.
+  Result<ConstByteSpan> GetEntry(ByteSpan entry, uint32_t& drop_count_out);
+
+ protected:
+  friend MultiSink;
+  ring_buffer::PrefixedEntryRingBufferMulti::Reader reader_;
+  uint32_t last_handled_sequence_id_;
+  MultiSink* multisink_;
+};
+
+}  // namespace multisink
+}  // namespace pw
diff --git a/pw_multisink/public/pw_multisink/multisink.h b/pw_multisink/public/pw_multisink/multisink.h
index 5942304..097e4a3 100644
--- a/pw_multisink/public/pw_multisink/multisink.h
+++ b/pw_multisink/public/pw_multisink/multisink.h
@@ -13,308 +13,27 @@
 // the License.
 #pragma once
 
-#include <limits>
-#include <mutex>
-
 #include "pw_bytes/span.h"
-#include "pw_function/function.h"
-#include "pw_multisink/config.h"
 #include "pw_result/result.h"
 #include "pw_ring_buffer/prefixed_entry_ring_buffer.h"
 #include "pw_status/status.h"
-#include "pw_sync/lock_annotations.h"
 
 namespace pw {
 namespace multisink {
+class Drain;
 
 // An asynchronous single-writer multi-reader queue that ensures readers can
 // poll for dropped message counts, which is useful for logging or similar
 // scenarios where readers need to be aware of the input message sequence.
-//
-// This class is thread-safe but NOT IRQ-safe when
-// PW_MULTISINK_LOCK_INTERRUPT_SAFE is disabled.
+// TODO(pwbug/342): Support notifying readers when the queue is readable,
+// rather than requiring them to poll to check for new entries.
+// TODO(pwbug/343): Add thread-safety, separate from the thread-safety work
+// planned for the underlying ring buffer.
 class MultiSink {
  public:
-  // An asynchronous reader which is attached to a MultiSink via AttachDrain.
-  // Each Drain holds a PrefixedEntryRingBufferMulti::Reader and abstracts away
-  // entry sequence information for clients when popping.
-  class Drain {
-   public:
-    // Holds the context for a peeked entry, tha the user may pass to `PopEntry`
-    // to advance the drain.
-    class PeekedEntry {
-     public:
-      // Provides access to the peeked entry's data.
-      ConstByteSpan entry() const { return entry_; }
-
-     private:
-      friend MultiSink;
-      friend MultiSink::Drain;
-
-      constexpr PeekedEntry(ConstByteSpan entry, uint32_t sequence_id)
-          : entry_(entry), sequence_id_(sequence_id) {}
-
-      uint32_t sequence_id() const { return sequence_id_; }
-
-      const ConstByteSpan entry_;
-      const uint32_t sequence_id_;
-    };
-
-    constexpr Drain()
-        : last_handled_sequence_id_(0),
-          last_peek_sequence_id_(0),
-          last_handled_ingress_drop_count_(0),
-          multisink_(nullptr) {}
-
-    // Returns the next available entry if it exists and acquires the latest
-    // drop count in parallel.
-    //
-    // If the read operation was successful or returned OutOfRange (i.e. no
-    // entries to read) then the `drop_count_out` is set to the number of
-    // entries that were dropped since the last call to PopEntry due to
-    // advancing the drain, and `ingress_drop_count_out` is set to the number of
-    // logs that were dropped before being added to the MultiSink. Otherwise,
-    // the drop counts are set to zero, so should always be processed.
-    //
-    // Drop counts are internally maintained with a 32-bit counter. If
-    // UINT32_MAX entries have been handled by the attached multisink between
-    // subsequent calls to PopEntry, the drop count will overflow and will
-    // report a lower count erroneously. Users should ensure that sinks call
-    // PopEntry at least once every UINT32_MAX entries.
-    //
-    // Example Usage:
-    //
-    // void ProcessEntriesFromDrain(Drain& drain) {
-    //   std::array<std::byte, kEntryBufferSize> buffer;
-    //   uint32_t drop_count = 0;
-    //
-    //   // Example#1: Request the drain for a new entry.
-    //   {
-    //     const Result<ConstByteSpan> result = drain.PopEntry(buffer,
-    //                                                         drop_count);
-    //
-    //     // If a non-zero drop count is received, process them.
-    //     if (drop_count > 0) {
-    //       ProcessDropCount(drop_count);
-    //     }
-    //
-    //     // If the call was successful, process the entry that was received.
-    //     if (result.ok()) {
-    //       ProcessEntry(result.value());
-    //     }
-    //   }
-    //
-    //   // Example#2: Drain out all messages.
-    //   {
-    //     Result<ConstByteSpan> result = Status::OutOfRange();
-    //     do {
-    //       result = drain.PopEntry(buffer, drop_count);
-    //
-    //       if (drop_count > 0) {
-    //         ProcessDropCount(drop_count);
-    //       }
-    //
-    //       if (result.ok()) {
-    //         ProcessEntry(result.value());
-    //       }
-    //
-    //       // Keep trying until we hit OutOfRange. Note that a new entry may
-    //       // have arrived after the PopEntry call.
-    //     } while (!result.IsOutOfRange());
-    //   }
-    // }
-    // Precondition: the buffer data must not be corrupt, otherwise there will
-    // be a crash.
-    //
-    // Return values:
-    // OK - An entry was successfully read from the multisink.
-    // OUT_OF_RANGE - No entries were available.
-    // FAILED_PRECONDITION - The drain must be attached to a sink.
-    // RESOURCE_EXHAUSTED - The provided buffer was not large enough to store
-    // the next available entry, which was discarded.
-    Result<ConstByteSpan> PopEntry(ByteSpan buffer,
-                                   uint32_t& drop_count_out,
-                                   uint32_t& ingress_drop_count)
-        PW_LOCKS_EXCLUDED(multisink_->lock_);
-    // Overload that combines drop counts.
-    // TODO(cachinchilla): remove when downstream projects migrated to new API.
-    [[deprecated("Use PopEntry with different drop count outputs")]] Result<
-        ConstByteSpan>
-    PopEntry(ByteSpan buffer, uint32_t& drop_count_out)
-        PW_LOCKS_EXCLUDED(multisink_->lock_) {
-      uint32_t ingress_drop_count = 0;
-      Result<ConstByteSpan> result =
-          PopEntry(buffer, drop_count_out, ingress_drop_count);
-      drop_count_out += ingress_drop_count;
-      return result;
-    }
-
-    // Removes the previously peeked entry from the multisink.
-    //
-    // Example Usage:
-    //
-    //  // Peek entry to send it, and remove entry from multisink on success.
-    //  uint32_t drop_count;
-    //  const Result<PeekedEntry> peek_result =
-    //      PeekEntry(out_buffer, drop_count);
-    //  if (!peek_result.ok()) {
-    //    return peek_result.status();
-    //  }
-    //  Status send_status = UserSendFunction(peek_result.value().entry())
-    //  if (!send_status.ok())
-    //    return send_status;
-    //  }
-    //  PW_CHECK_OK(PopEntry(peek_result.value());
-    //
-    // Precondition: the buffer data must not be corrupt, otherwise there will
-    // be a crash.
-    //
-    // Return values:
-    // OK - the entry or entries were removed from the multisink succesfully.
-    // FAILED_PRECONDITION - The drain must be attached to a sink.
-    Status PopEntry(const PeekedEntry& entry)
-        PW_LOCKS_EXCLUDED(multisink_->lock_);
-
-    // Returns a copy of the next available entry if it exists and acquires the
-    // latest drop count if the drain was advanced, and the latest ingress drop
-    // count, without moving the drain forward, except if there is a
-    // RESOURCE_EXHAUSTED error when peeking, in which case the drain is
-    // automatically advanced.
-    // The `drop_count_out` follows the same logic as `PopEntry`. The user must
-    // call `PopEntry` once the data in peek was used successfully.
-    //
-    // Precondition: the buffer data must not be corrupt, otherwise there will
-    // be a crash.
-    //
-    // Return values:
-    // OK - An entry was successfully read from the multisink.
-    // OUT_OF_RANGE - No entries were available.
-    // FAILED_PRECONDITION - The drain must be attached to a sink.
-    // RESOURCE_EXHAUSTED - The provided buffer was not large enough to store
-    // the next available entry, which was discarded.
-    Result<PeekedEntry> PeekEntry(ByteSpan buffer,
-                                  uint32_t& drop_count_out,
-                                  uint32_t& ingress_drop_count)
-        PW_LOCKS_EXCLUDED(multisink_->lock_);
-
-    // Drains are not copyable or movable.
-    Drain(const Drain&) = delete;
-    Drain& operator=(const Drain&) = delete;
-    Drain(Drain&&) = delete;
-    Drain& operator=(Drain&&) = delete;
-
-   protected:
-    friend MultiSink;
-
-    // The `reader_` and `last_handled_sequence_id_` are managed by attached
-    // multisink and are guarded by `multisink_->lock_` when used.
-    ring_buffer::PrefixedEntryRingBufferMulti::Reader reader_;
-    uint32_t last_handled_sequence_id_;
-    uint32_t last_peek_sequence_id_;
-    uint32_t last_handled_ingress_drop_count_;
-    MultiSink* multisink_;
-  };
-
-  // A pure-virtual listener of a MultiSink, attached via AttachListener.
-  // MultiSink's invoke listeners when new data arrives, allowing them to
-  // schedule the draining of messages out of the MultiSink.
-  class Listener : public IntrusiveList<Listener>::Item {
-   public:
-    constexpr Listener() {}
-    virtual ~Listener() = default;
-
-    // Listeners are not copyable or movable.
-    Listener(const Listener&) = delete;
-    Listener& operator=(const Drain&) = delete;
-    Listener(Listener&&) = delete;
-    Listener& operator=(Drain&&) = delete;
-
-   protected:
-    friend MultiSink;
-
-    // Invoked by the attached multisink when a new entry or drop count is
-    // available. The multisink lock is held during this call, so neither the
-    // multisink nor it's drains can be used during this callback.
-    virtual void OnNewEntryAvailable() = 0;
-  };
-
-  class iterator {
-   public:
-    iterator& operator++() {
-      it_++;
-      return *this;
-    }
-    iterator operator++(int) {
-      iterator original = *this;
-      ++*this;
-      return original;
-    }
-
-    ConstByteSpan& operator*() {
-      entry_ = (*it_).buffer;
-      return entry_;
-    }
-    ConstByteSpan* operator->() { return &operator*(); }
-
-    constexpr bool operator==(const iterator& rhs) const {
-      return it_ == rhs.it_;
-    }
-
-    constexpr bool operator!=(const iterator& rhs) const {
-      return it_ != rhs.it_;
-    }
-
-    // Returns the status of the last iteration operation. If the iterator
-    // fails to read an entry, it will move to iterator::end() and indicate
-    // the failure reason here.
-    //
-    // Return values:
-    // OK - iteration is successful and iterator points to the next entry.
-    // DATA_LOSS - Failed to read the metadata at this location.
-    Status status() const { return it_.status(); }
-
-   private:
-    friend class MultiSink;
-
-    iterator(ring_buffer::PrefixedEntryRingBufferMulti::Reader& reader)
-        : it_(reader) {}
-    iterator() {}
-
-    ring_buffer::PrefixedEntryRingBufferMulti::iterator it_;
-    ConstByteSpan entry_;
-  };
-
-  class UnsafeIterationWrapper {
-   public:
-    using element_type = ConstByteSpan;
-    using value_type = std::remove_cv_t<ConstByteSpan>;
-    using pointer = ConstByteSpan*;
-    using reference = ConstByteSpan&;
-    using const_iterator = iterator;  // Standard alias for iterable types.
-
-    iterator begin() const { return iterator(*reader_); }
-    iterator end() const { return iterator(); }
-    const_iterator cbegin() const { return begin(); }
-    const_iterator cend() const { return end(); }
-
-   private:
-    friend class MultiSink;
-    UnsafeIterationWrapper(
-        ring_buffer::PrefixedEntryRingBufferMulti::Reader& reader)
-        : reader_(&reader) {}
-    ring_buffer::PrefixedEntryRingBufferMulti::Reader* reader_;
-  };
-
-  UnsafeIterationWrapper UnsafeIteration() PW_NO_LOCK_SAFETY_ANALYSIS {
-    return UnsafeIterationWrapper(oldest_entry_drain_.reader_);
-  }
-
   // Constructs a multisink using a ring buffer backed by the provided buffer.
-  MultiSink(ByteSpan buffer)
-      : ring_buffer_(true), sequence_id_(0), total_ingress_drops_(0) {
-    ring_buffer_.SetBuffer(buffer)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    AttachDrain(oldest_entry_drain_);
+  MultiSink(ByteSpan buffer) : ring_buffer_(true), sequence_id_(0) {
+    ring_buffer_.SetBuffer(buffer);
   }
 
   // Write an entry to the multisink. If available space is less than the
@@ -323,104 +42,65 @@
   // The sequence ID of the multisink will always increment as a result of
   // calling HandleEntry, regardless of whether pushing the entry succeeds.
   //
-  // Precondition: If PW_MULTISINK_LOCK_INTERRUPT_SAFE is disabled, this
-  // function must not be called from an interrupt context.
-  // Precondition: entry.size() <= `ring_buffer_` size
-  void HandleEntry(ConstByteSpan entry) PW_LOCKS_EXCLUDED(lock_);
+  // Return values:
+  // Ok - Entry was successfully pushed to the ring buffer.
+  // InvalidArgument - Size of data to write is zero bytes.
+  // OutOfRange - Size of data is greater than buffer size.
+  // FailedPrecondition - Buffer was not initialized.
+  Status HandleEntry(ConstByteSpan entry) {
+    return ring_buffer_.PushBack(entry, sequence_id_++);
+  }
 
   // Notifies the multisink of messages dropped before ingress. The writer
   // may use this to signal to readers that an entry (or entries) failed
   // before being sent to the multisink (e.g. the writer failed to encode
   // the message). This API increments the sequence ID of the multisink by
   // the provided `drop_count`.
-  void HandleDropped(uint32_t drop_count = 1) PW_LOCKS_EXCLUDED(lock_);
+  void HandleDropped(uint32_t drop_count = 1) { sequence_id_ += drop_count; }
 
   // Attach a drain to the multisink. Drains may not be associated with more
-  // than one multisink at a time. Drains can consume entries pushed before
-  // the drain was attached, so long as they have not yet been evicted from
-  // the underlying ring buffer.
+  // than one multisink at a time. Entries pushed before the drain was attached
+  // are not seen by the drain, so drains should be attached before entries
+  // are pushed.
   //
-  // Precondition: The drain must not be attached to a multisink.
-  void AttachDrain(Drain& drain) PW_LOCKS_EXCLUDED(lock_);
+  // Return values:
+  // Ok - Drain was successfully attached.
+  // InvalidArgument - Drain is currently associated with another multisink.
+  Status AttachDrain(Drain& drain);
 
   // Detaches a drain from the multisink. Drains may only be detached if they
   // were previously attached to this multisink.
   //
-  // Precondition: The drain must be attached to this multisink.
-  void DetachDrain(Drain& drain) PW_LOCKS_EXCLUDED(lock_);
-
-  // Attach a listener to the multisink. The listener will be notified
-  // immediately when attached, to allow late drain users to consume existing
-  // entries. If draining in response to the notification, ensure that the drain
-  // is attached prior to registering the listener; attempting to drain when
-  // unattached will crash. Once attached, listeners are invoked on all new
-  // messages.
-  //
-  // Precondition: The listener must not be attached to a multisink.
-  void AttachListener(Listener& listener) PW_LOCKS_EXCLUDED(lock_);
-
-  // Detaches a listener from the multisink.
-  //
-  // Precondition: The listener must be attached to this multisink.
-  void DetachListener(Listener& listener) PW_LOCKS_EXCLUDED(lock_);
+  // Return values:
+  // Ok - Drain was successfully detached.
+  // InvalidArgument - Drain is not currently associated with this multisink.
+  Status DetachDrain(Drain& drain);
 
   // Removes all data from the internal buffer. The multisink's sequence ID is
   // not modified, so readers may interpret this event as droppping entries.
-  void Clear() PW_LOCKS_EXCLUDED(lock_);
-
-  // Uses MultiSink's unsafe iteration to dump the contents to a user-provided
-  // callback. max_num_entries can be used to limit the dump to the N most
-  // recent entries.
-  //
-  // Returns:
-  //   OK - Successfully dumped entire multisink.
-  //   DATA_LOSS - Corruption detected, some entries may have been lost.
-  Status UnsafeForEachEntry(
-      const Function<void(ConstByteSpan)>& callback,
-      size_t max_num_entries = std::numeric_limits<size_t>::max());
+  void Clear() { ring_buffer_.Clear(); }
 
  protected:
   friend Drain;
-
-  enum class Request { kPop, kPeek };
-  // Removes the previously peeked entry from the front of the multisink.
-  Status PopEntry(Drain& drain, const Drain::PeekedEntry& entry)
-      PW_LOCKS_EXCLUDED(lock_);
-
-  // Gets a copy of the entry from the provided drain and unpacks sequence ID
-  // information. The entry is removed from the multisink when `request` is set
-  // to `Request::kPop`. Drains use this API to strip away sequence ID
-  // information for drop calculation.
-  //
-  // Precondition: the buffer data must not be corrupt, otherwise there will
-  // be a crash.
+  // Gets an entry from the provided drain and unpacks sequence ID information.
+  // Drains use this API to strip away sequence ID information for drop
+  // calculation.
   //
   // Returns:
-  // OK - An entry was successfully read from the multisink. The
-  // `drop_count_out` is set to the difference between the current sequence ID
-  // and the last handled ID.
-  // FAILED_PRECONDITION - The drain is not attached to
-  // a multisink.
-  // RESOURCE_EXHAUSTED - The provided buffer was not large enough to store
-  // the next available entry, which was discarded.
-  Result<ConstByteSpan> PeekOrPopEntry(Drain& drain,
-                                       ByteSpan buffer,
-                                       Request request,
-                                       uint32_t& drop_count_out,
-                                       uint32_t& ingress_drop_count_out,
-                                       uint32_t& entry_sequence_id_out)
-      PW_LOCKS_EXCLUDED(lock_);
+  // Ok - An entry was successfully read from the multisink. The `sequence_id`
+  // is set to the ID encoded in the oldest entry.
+  // FailedPrecondition - The drain is not attached to a multisink.
+  // ResourceExhausted - The provided buffer was not large enough to store
+  // the next available entry.
+  // DataLoss - An entry was read from the multisink, but did not contains an
+  // encoded sequence ID.
+  static Result<ConstByteSpan> GetEntry(Drain& drain,
+                                        ByteSpan buffer,
+                                        uint32_t& sequence_id_out);
 
  private:
-  // Notifies attached listeners of new entries or an updated drop count.
-  void NotifyListeners() PW_EXCLUSIVE_LOCKS_REQUIRED(lock_);
-
-  IntrusiveList<Listener> listeners_ PW_GUARDED_BY(lock_);
-  ring_buffer::PrefixedEntryRingBufferMulti ring_buffer_ PW_GUARDED_BY(lock_);
-  Drain oldest_entry_drain_ PW_GUARDED_BY(lock_);
-  uint32_t sequence_id_ PW_GUARDED_BY(lock_);
-  uint32_t total_ingress_drops_ PW_GUARDED_BY(lock_);
-  LockType lock_;
+  ring_buffer::PrefixedEntryRingBufferMulti ring_buffer_;
+  uint32_t sequence_id_ = 0;
 };
 
 }  // namespace multisink
diff --git a/pw_multisink/public/pw_multisink/test_thread.h b/pw_multisink/public/pw_multisink/test_thread.h
deleted file mode 100644
index 5cdc6de..0000000
--- a/pw_multisink/public/pw_multisink/test_thread.h
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_thread/thread.h"
-
-namespace pw::multisink::test {
-
-// Test thread used to verify the multisink.
-const thread::Options& MultiSinkTestThreadOptions();
-
-}  // namespace pw::multisink::test
diff --git a/pw_multisink/public/pw_multisink/util.h b/pw_multisink/public/pw_multisink/util.h
deleted file mode 100644
index d9a3359..0000000
--- a/pw_multisink/public/pw_multisink/util.h
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <limits>
-
-#include "pw_log/proto/log.pwpb.h"
-#include "pw_multisink/multisink.h"
-#include "pw_status/status.h"
-
-namespace pw::multisink {
-
-// Uses MultiSink's unsafe iteration to dump the contents as a series of log
-// entries. max_num_entries can be used to limit the dump to the most recent
-// entries. This can be used to dump proto-encoded logs to a
-// pw.snapshot.Snapshot.
-Status UnsafeDumpMultiSinkLogs(
-    MultiSink& sink,
-    pw::log::LogEntries::StreamEncoder& encoder,
-    size_t max_num_entries = std::numeric_limits<size_t>::max());
-
-}  // namespace pw::multisink
diff --git a/pw_multisink/stl_test_thread.cc b/pw_multisink/stl_test_thread.cc
deleted file mode 100644
index b61f3e5..0000000
--- a/pw_multisink/stl_test_thread.cc
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_multisink/test_thread.h"
-#include "pw_thread_stl/options.h"
-
-namespace pw::multisink::test {
-
-// The STL doesn't offer any options so the default constructed options are used
-// directly.
-
-const thread::Options& MultiSinkTestThreadOptions() {
-  static constexpr thread::stl::Options thread_options;
-  return thread_options;
-}
-
-}  // namespace pw::multisink::test
diff --git a/pw_multisink/util.cc b/pw_multisink/util.cc
deleted file mode 100644
index 825701d..0000000
--- a/pw_multisink/util.cc
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_multisink/util.h"
-
-#include "pw_bytes/span.h"
-#include "pw_function/function.h"
-#include "pw_multisink/multisink.h"
-#include "pw_status/status.h"
-
-namespace pw::multisink {
-
-Status UnsafeDumpMultiSinkLogs(MultiSink& sink,
-                               pw::log::LogEntries::StreamEncoder& encoder,
-                               size_t max_num_entries) {
-  auto callback = [&encoder](ConstByteSpan entry) {
-    encoder.WriteBytes(
-        static_cast<uint32_t>(pw::log::LogEntries::Fields::ENTRIES), entry);
-  };
-  return sink.UnsafeForEachEntry(callback, max_num_entries);
-}
-
-}  // namespace pw::multisink
diff --git a/pw_package/OWNERS b/pw_package/OWNERS
deleted file mode 100644
index c0fb30b..0000000
--- a/pw_package/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-mohrr@google.com
diff --git a/pw_package/docs.rst b/pw_package/docs.rst
index 78d99b8..b3055db 100644
--- a/pw_package/docs.rst
+++ b/pw_package/docs.rst
@@ -16,13 +16,6 @@
 * The dependency needs to be "installed" into the system in some manner beyond
   just extraction and thus isn't a good match for distribution with CIPD.
 
-Pigweed itself includes a number of packages that simply clone git repositories.
-In general, these should not be used by projects using Pigweed. Pigweed uses
-these packages to avoid using submodules so downstream projects don't have
-multiple copies of a given repository in their source tree. Projects using
-Pigweed should use submodules instead of packages because submodules are
-supported by much more mature tooling: git.
-
 -----
 Usage
 -----
@@ -39,7 +32,7 @@
   ``--force`` to remove the package before installing.
 
 ``pw package status <package-name>``
-  Indicates whether ``<package-name>`` is installed.
+  Indicates whether ``<packagxe-name>`` is installed.
 
 ``pw package remove <package-name>``
   Removes ``<package-name>``.
diff --git a/pw_package/py/BUILD.gn b/pw_package/py/BUILD.gn
index 1af696e..dde870b 100644
--- a/pw_package/py/BUILD.gn
+++ b/pw_package/py/BUILD.gn
@@ -17,34 +17,16 @@
 import("$dir_pw_build/python.gni")
 
 pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
+  setup = [ "setup.py" ]
   sources = [
     "pw_package/__init__.py",
     "pw_package/git_repo.py",
     "pw_package/package_manager.py",
     "pw_package/packages/__init__.py",
     "pw_package/packages/arduino_core.py",
-    "pw_package/packages/boringssl.py",
-    "pw_package/packages/chromium_verifier.py",
-    "pw_package/packages/crlset.py",
-    "pw_package/packages/freertos.py",
-    "pw_package/packages/googletest.py",
-    "pw_package/packages/mbedtls.py",
-    "pw_package/packages/micro_ecc.py",
     "pw_package/packages/nanopb.py",
-    "pw_package/packages/pico_sdk.py",
-    "pw_package/packages/protobuf.py",
-    "pw_package/packages/smartfusion_mss.py",
-    "pw_package/packages/stm32cube.py",
     "pw_package/pigweed_packages.py",
   ]
   pylintrc = "$dir_pigweed/.pylintrc"
-  python_deps = [
-    "$dir_pw_arduino_build/py",
-    "$dir_pw_stm32cube_build/py",
-  ]
+  python_deps = [ "$dir_pw_arduino_build/py" ]
 }
diff --git a/pw_package/py/pw_package/git_repo.py b/pw_package/py/pw_package/git_repo.py
index 348775d..fa9f129 100644
--- a/pw_package/py/pw_package/git_repo.py
+++ b/pw_package/py/pw_package/git_repo.py
@@ -41,21 +41,10 @@
 
 class GitRepo(pw_package.package_manager.Package):
     """Install and check status of Git repository-based packages."""
-    def __init__(self,
-                 url,
-                 *args,
-                 commit='',
-                 tag='',
-                 sparse_list=None,
-                 **kwargs):
+    def __init__(self, url, commit, *args, **kwargs):
         super().__init__(*args, **kwargs)
-        if not (commit or tag):
-            raise ValueError('git repo must specify a commit or tag')
-
         self._url = url
         self._commit = commit
-        self._tag = tag
-        self._sparse_list = sparse_list
 
     def status(self, path: pathlib.Path) -> bool:
         if not os.path.isdir(path / '.git'):
@@ -73,21 +62,8 @@
             remote = 'https://{}{}'.format(host, url.path)
 
         commit = git_stdout('rev-parse', 'HEAD', repo=path)
-        if self._commit and self._commit != commit:
-            return False
-
-        if self._tag:
-            tag = git_stdout('describe', '--tags', repo=path)
-            if self._tag != tag:
-                return False
-
-        # If it is a sparse checkout, sparse list shall match.
-        if self._sparse_list:
-            if not self.check_sparse_list(path):
-                return False
-
         status = git_stdout('status', '--porcelain=v1', repo=path)
-        return remote == self._url and not status
+        return remote == self._url and commit == self._commit and not status
 
     def install(self, path: pathlib.Path) -> None:
         # If already installed and at correct version exit now.
@@ -98,39 +74,9 @@
         if os.path.isdir(path):
             shutil.rmtree(path)
 
-        if self._sparse_list:
-            self.checkout_sparse(path)
-        else:
-            self.checkout_full(path)
-
-    def checkout_full(self, path: pathlib.Path) -> None:
         # --filter=blob:none means we don't get history, just the current
         # revision. If we later run commands that need history it will be
         # retrieved on-demand. For small repositories the effect is negligible
         # but for large repositories this should be a significant improvement.
-        if self._commit:
-            git('clone', '--filter=blob:none', self._url, path)
-            git('reset', '--hard', self._commit, repo=path)
-        elif self._tag:
-            git('clone', '-b', self._tag, '--filter=blob:none', self._url,
-                path)
-
-    def checkout_sparse(self, path: pathlib.Path) -> None:
-        # sparse checkout
-        git('init', path)
-        git('remote', 'add', 'origin', self._url, repo=path)
-        git('config', 'core.sparseCheckout', 'true', repo=path)
-
-        # Add files to checkout by editing .git/info/sparse-checkout
-        with open(path / '.git' / 'info' / 'sparse-checkout', 'w') as sparse:
-            for source in self._sparse_list:
-                sparse.write(source + '\n')
-
-        # Either pull from a commit or a tag.
-        target = self._commit if self._commit else self._tag
-        git('pull', '--depth=1', 'origin', target, repo=path)
-
-    def check_sparse_list(self, path: pathlib.Path) -> bool:
-        sparse_list = git_stdout('sparse-checkout', 'list',
-                                 repo=path).strip('\n').splitlines()
-        return set(sparse_list) == set(self._sparse_list)
+        git('clone', '--filter=blob:none', self._url, path)
+        git('reset', '--hard', self._commit, repo=path)
diff --git a/pw_package/py/pw_package/package_manager.py b/pw_package/py/pw_package/package_manager.py
index 1254121..3f1cc80 100644
--- a/pw_package/py/pw_package/package_manager.py
+++ b/pw_package/py/pw_package/package_manager.py
@@ -66,8 +66,11 @@
 _PACKAGES: Dict[str, Package] = {}
 
 
-def register(package_class: type, *args, **kwargs) -> None:
-    obj = package_class(*args, **kwargs)
+def register(package_class: type, name: str = None) -> None:
+    if name:
+        obj = package_class(name)
+    else:
+        obj = package_class()
     _PACKAGES[obj.name] = obj
 
 
diff --git a/pw_package/py/pw_package/packages/arduino_core.py b/pw_package/py/pw_package/packages/arduino_core.py
index c630c93..994c95c 100644
--- a/pw_package/py/pw_package/packages/arduino_core.py
+++ b/pw_package/py/pw_package/packages/arduino_core.py
@@ -137,5 +137,4 @@
 
 
 for arduino_core_name in core_installer.supported_cores():
-    pw_package.package_manager.register(ArduinoCore,
-                                        core_name=arduino_core_name)
+    pw_package.package_manager.register(ArduinoCore, name=arduino_core_name)
diff --git a/pw_package/py/pw_package/packages/boringssl.py b/pw_package/py/pw_package/packages/boringssl.py
deleted file mode 100644
index f791ef0..0000000
--- a/pw_package/py/pw_package/packages/boringssl.py
+++ /dev/null
@@ -1,71 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Install and check status of BoringSSL + Chromium verifier."""
-
-import os
-import pathlib
-import subprocess
-from typing import Sequence
-import pw_package.git_repo
-import pw_package.package_manager
-
-
-def boringssl_repo_path(path: pathlib.Path) -> pathlib.Path:
-    return path / 'src'
-
-
-class BoringSSL(pw_package.package_manager.Package):
-    """Install and check status of BoringSSL and chromium verifier."""
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, name='boringssl', **kwargs)
-        self._boringssl = pw_package.git_repo.GitRepo(
-            name='boringssl',
-            url=''.join([
-                'https://pigweed.googlesource.com',
-                '/third_party/boringssl/boringssl'
-            ]),
-            commit='9f55d972854d0b34dae39c7cd3679d6ada3dfd5b')
-
-    def status(self, path: pathlib.Path) -> bool:
-        if not self._boringssl.status(boringssl_repo_path(path)):
-            return False
-
-        # Check that necessary build files are generated.
-        build_files = ['BUILD.generated.gni', 'err_data.c']
-        return all(os.path.exists(path / file) for file in build_files)
-
-    def install(self, path: pathlib.Path) -> None:
-        # Checkout the library
-        repo_path = boringssl_repo_path(path)
-        self._boringssl.install(repo_path)
-
-        # BoringSSL provides a src/util/generate_build_files.py script for
-        # generating build files. Call the script after checkout so that
-        # our .gn build script can pick them up.
-        script = repo_path / 'util' / 'generate_build_files.py'
-        if not os.path.exists(script):
-            raise FileNotFoundError('Fail to find generate_build_files.py')
-        subprocess.run(['python', script, 'gn'], cwd=path)
-
-        # TODO(zyecheng): Add install logic for chromium certificate verifier.
-
-    def info(self, path: pathlib.Path) -> Sequence[str]:
-        return (
-            f'{self.name} installed in: {path}',
-            'Enable by running "gn args out" and adding this line:',
-            f'  dir_pw_third_party_boringssl = "{path}"',
-        )
-
-
-pw_package.package_manager.register(BoringSSL)
diff --git a/pw_package/py/pw_package/packages/chromium_verifier.py b/pw_package/py/pw_package/packages/chromium_verifier.py
deleted file mode 100644
index 4474a6d..0000000
--- a/pw_package/py/pw_package/packages/chromium_verifier.py
+++ /dev/null
@@ -1,159 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Install and check status of BoringSSL + Chromium verifier."""
-
-import pathlib
-from typing import Sequence
-import pw_package.git_repo
-import pw_package.package_manager
-
-# List of sources to checkout for chromium verifier.
-# The list is hand-picked. It is currently only tested locally (i.e. the list
-# compiles and can run certificate chain verification). Unittest will be added
-# in pw_tls_client that uses the this package, so that it can be used as a
-# criterion for rolling.
-CHROMIUM_VERIFIER_LIBRARY_SOURCES = [
-    'base/*',
-    '!base/check.h',
-    '!base/check_op.h',
-    '!base/logging.h',
-    'build/buildflag.h',
-    'build/write_buildflag_header.py',
-    'crypto',
-    'net/base',
-    'net/cert',
-    'net/data',
-    'net/der',
-    'testing/gtest/include',
-    'testing/gmock/include',
-    'third_party/abseil-cpp',
-    'third_party/boringssl',
-    'third_party/googletest',
-    'time/internal/cctz/include/cctz/civil_time_detail.h',
-    'url/gurl.h',
-    'url/third_party/mozilla/url_parse.h',
-    'url/url_canon.h',
-    'url/url_canon_ip.h',
-    'url/url_canon_stdstring.h',
-    'url/url_constants.h',
-    'net/test/test_certificate_data.h',
-    'net/cert/internal/path_builder_unittest.cc',
-    'third_party/modp_b64',
-]
-
-CHROMIUM_VERIFIER_UNITTEST_SOURCES = [
-    # TODO(pwbug/394): Look into in necessary unittests to port.
-    'net/cert/internal/path_builder_unittest.cc',
-]
-
-CHROMIUM_VERIFIER_SOURCES = CHROMIUM_VERIFIER_LIBRARY_SOURCES +\
-    CHROMIUM_VERIFIER_UNITTEST_SOURCES
-
-
-def chromium_verifier_repo_path(
-        chromium_verifier_install: pathlib.Path) -> pathlib.Path:
-    """Return the sub-path for repo checkout of chromium verifier"""
-    return chromium_verifier_install / 'src'
-
-
-def chromium_third_party_boringssl_repo_path(
-        chromium_verifier_repo: pathlib.Path) -> pathlib.Path:
-    """Returns the path of third_party/boringssl library in chromium repo"""
-    return chromium_verifier_repo / 'third_party' / 'boringssl' / 'src'
-
-
-def chromium_third_party_googletest_repo_path(
-        chromium_verifier_repo: pathlib.Path) -> pathlib.Path:
-    """Returns the path of third_party/googletest in chromium repo"""
-    return chromium_verifier_repo / 'third_party' / 'googletest' / 'src'
-
-
-class ChromiumVerifier(pw_package.package_manager.Package):
-    """Install and check status of Chromium Verifier"""
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, name='chromium_verifier', **kwargs)
-        self._chromium_verifier = pw_package.git_repo.GitRepo(
-            name='chromium_verifier',
-            url='https://chromium.googlesource.com/chromium/src',
-            commit='04ebce24d98339954fb1d2a67e68da7ca81ca47c',
-            sparse_list=CHROMIUM_VERIFIER_SOURCES,
-        )
-
-        # The following is for checking out necessary headers of
-        # boringssl and googletest third party libraries that chromium verifier
-        # depends on. The actual complete libraries will be separate packages.
-
-        self._boringssl = pw_package.git_repo.GitRepo(
-            name='boringssl',
-            url=''.join([
-                'https://pigweed.googlesource.com',
-                '/third_party/boringssl/boringssl'
-            ]),
-            commit='9f55d972854d0b34dae39c7cd3679d6ada3dfd5b',
-            sparse_list=['include'],
-        )
-
-        self._googletest = pw_package.git_repo.GitRepo(
-            name='googletest',
-            url=''.join([
-                'https://chromium.googlesource.com/',
-                'external/github.com/google/googletest.git',
-            ]),
-            commit='53495a2a7d6ba7e0691a7f3602e9a5324bba6e45',
-            sparse_list=[
-                'googletest/include',
-                'googlemock/include',
-            ])
-
-    def install(self, path: pathlib.Path) -> None:
-        # Checkout chromium verifier
-        chromium_repo = chromium_verifier_repo_path(path)
-        self._chromium_verifier.install(chromium_repo)
-
-        # Checkout third party boringssl headers
-        boringssl_repo = chromium_third_party_boringssl_repo_path(
-            chromium_repo)
-        self._boringssl.install(boringssl_repo)
-
-        # Checkout third party googletest headers
-        googletest_repo = chromium_third_party_googletest_repo_path(
-            chromium_repo)
-        self._googletest.install(googletest_repo)
-
-    def status(self, path: pathlib.Path) -> bool:
-        chromium_repo = chromium_verifier_repo_path(path)
-        if not self._chromium_verifier.status(chromium_repo):
-            return False
-
-        boringssl_repo = chromium_third_party_boringssl_repo_path(
-            chromium_repo)
-        if not self._boringssl.status(boringssl_repo):
-            return False
-
-        googletest_repo = chromium_third_party_googletest_repo_path(
-            chromium_repo)
-        if not self._googletest.status(googletest_repo):
-            return False
-
-        return True
-
-    def info(self, path: pathlib.Path) -> Sequence[str]:
-        return (
-            f'{self.name} installed in: {path}',
-            'Enable by running "gn args out" and adding this line:',
-            f'  dir_pw_third_party_chromium_verifier = {path}',
-        )
-
-
-pw_package.package_manager.register(ChromiumVerifier)
diff --git a/pw_package/py/pw_package/packages/crlset.py b/pw_package/py/pw_package/packages/crlset.py
deleted file mode 100644
index f17002a..0000000
--- a/pw_package/py/pw_package/packages/crlset.py
+++ /dev/null
@@ -1,89 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Install and check status of CRLSet and download chromium CRLSet."""
-
-import os
-import pathlib
-import subprocess
-from typing import Sequence
-import pw_package.git_repo
-import pw_package.package_manager
-
-
-def crlset_tools_repo_path(path: pathlib.Path) -> pathlib.Path:
-    return path / 'crlset-tools'
-
-
-def crlset_exec_path(path: pathlib.Path) -> pathlib.Path:
-    return path / 'crlset_exec'
-
-
-def crlset_file_path(path: pathlib.Path) -> pathlib.Path:
-    return path / 'crlset'
-
-
-class CRLSet(pw_package.package_manager.Package):
-    """Install and check status of CRLSet and downloaded CLRSet file."""
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, name='crlset', **kwargs)
-        self._crlset_tools = pw_package.git_repo.GitRepo(
-            name='crlset-tools',
-            url='https://github.com/agl/crlset-tools.git',
-            commit='1a1019bb500f93bc2b847a57cdbaede847649b99',
-        )
-
-    def status(self, path: pathlib.Path) -> bool:
-        if not self._crlset_tools.status(crlset_tools_repo_path(path)):
-            return False
-
-        # The executable should have been built and exist.
-        if not os.path.exists(crlset_exec_path(path)):
-            return False
-
-        # A crlset has been downloaded
-        if not os.path.exists(crlset_file_path(path)):
-            return False
-
-        return True
-
-    def install(self, path: pathlib.Path) -> None:
-        self._crlset_tools.install(crlset_tools_repo_path(path))
-
-        # Build the go tool
-        subprocess.run(
-            ['go', 'build', '-o',
-             crlset_exec_path(path), 'crlset.go'],
-            check=True,
-            cwd=crlset_tools_repo_path(path))
-
-        crlset_tools_exec = crlset_exec_path(path)
-        if not os.path.exists(crlset_tools_exec):
-            raise FileNotFoundError('Fail to find crlset executable')
-
-        # Download the latest CRLSet with the go tool
-        with open(crlset_file_path(path), 'wb') as crlset_file:
-            fetched = subprocess.run([crlset_exec_path(path), 'fetch'],
-                                     capture_output=True,
-                                     check=True).stdout
-            crlset_file.write(fetched)
-
-    def info(self, path: pathlib.Path) -> Sequence[str]:
-        return (
-            f'{self.name} installed in: {path}',
-            "Enable by running 'gn args out' and adding this line:",
-            f'  pw_tls_client_CRLSET_PATH = "{crlset_file_path(path)}"',
-        )
-
-
-pw_package.package_manager.register(CRLSet)
diff --git a/pw_package/py/pw_package/packages/freertos.py b/pw_package/py/pw_package/packages/freertos.py
deleted file mode 100644
index 1ab1270..0000000
--- a/pw_package/py/pw_package/packages/freertos.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Install and check status of FreeRTOS."""
-
-import pathlib
-from typing import Sequence
-
-import pw_package.git_repo
-import pw_package.package_manager
-
-
-class FreeRtos(pw_package.git_repo.GitRepo):
-    """Install and check status of FreeRTOS."""
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args,
-                         name='freertos',
-                         url='https://github.com/FreeRTOS/FreeRTOS-Kernel',
-                         commit='fa0f5c436ccd920be313313b7e08ba6a5513f93a',
-                         **kwargs)
-
-    def info(self, path: pathlib.Path) -> Sequence[str]:
-        return (
-            f'{self.name} installed in: {path}',
-            "Enable by running 'gn args out' and adding this line:",
-            f'  dir_pw_third_party_freertos = "{path}"',
-        )
-
-
-pw_package.package_manager.register(FreeRtos)
diff --git a/pw_package/py/pw_package/packages/googletest.py b/pw_package/py/pw_package/packages/googletest.py
deleted file mode 100644
index adfb4e8..0000000
--- a/pw_package/py/pw_package/packages/googletest.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Install and check status of googletest."""
-
-import pathlib
-from typing import Sequence
-
-import pw_package.git_repo
-import pw_package.package_manager
-
-
-class Googletest(pw_package.git_repo.GitRepo):
-    """Install and check status of googletest."""
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args,
-                         name='googletest',
-                         url='https://github.com/google/googletest',
-                         commit='073293463e1733c5e931313da1c3f1de044e1db3',
-                         **kwargs)
-
-    def info(self, path: pathlib.Path) -> Sequence[str]:
-        return (
-            f'{self.name} installed in: {path}',
-            "Enable by running 'gn args out' and adding this line:",
-            f'  dir_pw_third_party_googletest = "{path}"',
-        )
-
-
-pw_package.package_manager.register(Googletest)
diff --git a/pw_package/py/pw_package/packages/mbedtls.py b/pw_package/py/pw_package/packages/mbedtls.py
deleted file mode 100644
index a320efb..0000000
--- a/pw_package/py/pw_package/packages/mbedtls.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Install and check status of MbedTLS."""
-
-import pathlib
-from typing import Sequence
-
-import pw_package.git_repo
-import pw_package.package_manager
-
-
-class MbedTLS(pw_package.git_repo.GitRepo):
-    """Install and check status of MbedTLS."""
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args,
-                         name='mbedtls',
-                         url="".join([
-                             "https://pigweed.googlesource.com",
-                             "/third_party/github/ARMmbed/mbedtls",
-                         ]),
-                         commit='e483a77c85e1f9c1dd2eb1c5a8f552d2617fe400',
-                         **kwargs)
-
-    def info(self, path: pathlib.Path) -> Sequence[str]:
-        return (
-            f'{self.name} installed in: {path}',
-            "Enable by running 'gn args out' and adding this line:",
-            f'  dir_pw_third_party_mbedtls = "{path}"',
-        )
-
-
-pw_package.package_manager.register(MbedTLS)
diff --git a/pw_package/py/pw_package/packages/micro_ecc.py b/pw_package/py/pw_package/packages/micro_ecc.py
deleted file mode 100644
index 9a52003..0000000
--- a/pw_package/py/pw_package/packages/micro_ecc.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Install and check the status of the Micro-ECC cryptography library."""
-
-import pathlib
-from typing import Sequence
-
-import pw_package.git_repo
-import pw_package.package_manager
-
-
-class MicroECC(pw_package.git_repo.GitRepo):
-    """Install and check the status of the Micro-ECC cryptography library."""
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args,
-                         name='micro-ecc',
-                         url="".join([
-                             "https://github.com",
-                             "/kmackay/micro-ecc.git",
-                         ]),
-                         commit='24c60e243580c7868f4334a1ba3123481fe1aa48',
-                         **kwargs)
-
-    def info(self, path: pathlib.Path) -> Sequence[str]:
-        return (
-            f'{self.name} installed in: {path}',
-            "Enable by running 'gn args out' and adding this line:",
-            f'  dir_pw_third_party_micro_ecc = "{path}"',
-        )
-
-
-pw_package.package_manager.register(MicroECC)
diff --git a/pw_package/py/pw_package/packages/pico_sdk.py b/pw_package/py/pw_package/packages/pico_sdk.py
deleted file mode 100644
index 2a68fc6..0000000
--- a/pw_package/py/pw_package/packages/pico_sdk.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Install and check status of the Raspberry Pi Pico SDK."""
-
-import pathlib
-from typing import Sequence
-
-import pw_package.git_repo
-import pw_package.package_manager
-
-
-class PiPicoSdk(pw_package.git_repo.GitRepo):
-    """Install and check status of the Raspberry Pi Pico SDK."""
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args,
-                         name='pico_sdk',
-                         url='https://github.com/raspberrypi/pico-sdk',
-                         commit='2062372d203b372849d573f252cf7c6dc2800c0a',
-                         **kwargs)
-
-    def info(self, path: pathlib.Path) -> Sequence[str]:
-        return (
-            f'{self.name} installed in: {path}',
-            "Enable by running 'gn args out' and adding this line:",
-            f'  PICO_SRC_DIR = "{path}"',
-        )
-
-
-pw_package.package_manager.register(PiPicoSdk)
diff --git a/pw_package/py/pw_package/packages/protobuf.py b/pw_package/py/pw_package/packages/protobuf.py
deleted file mode 100644
index a06c493..0000000
--- a/pw_package/py/pw_package/packages/protobuf.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Install and check status of Protobuf."""
-
-import pathlib
-from typing import Sequence
-
-import pw_package.git_repo
-import pw_package.package_manager
-
-
-class Protobuf(pw_package.git_repo.GitRepo):
-    """Install and check status of Protobuf."""
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args,
-                         name='protobuf',
-                         url="https://github.com/protocolbuffers/protobuf.git",
-                         commit='15c40c6cdac2f816a56640d24a5c4c3ec0f84b00',
-                         **kwargs)
-
-    def info(self, path: pathlib.Path) -> Sequence[str]:
-        return (
-            f'{self.name} installed in: {path}',
-            "Enable by running 'gn args out' and adding this line:",
-            f'  dir_pw_third_party_protobuf = "{path}"',
-        )
-
-
-pw_package.package_manager.register(Protobuf)
diff --git a/pw_package/py/pw_package/packages/smartfusion_mss.py b/pw_package/py/pw_package/packages/smartfusion_mss.py
deleted file mode 100644
index b9dbe49..0000000
--- a/pw_package/py/pw_package/packages/smartfusion_mss.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Install and check status of SmartFusion MSS."""
-
-import pathlib
-from typing import Sequence
-
-import pw_package.git_repo
-import pw_package.package_manager
-
-
-class SmartfusionMss(pw_package.git_repo.GitRepo):
-    """Install and check status of SmartFusion MSS."""
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args,
-                         name='smartfusion_mss',
-                         url='https://github.com/seank/smartfusion_mss',
-                         commit='9f47db73d3df786eab04d082645da5e735e63d28',
-                         **kwargs)
-
-    def info(self, path: pathlib.Path) -> Sequence[str]:
-        return (
-            f'{self.name} installed in: {path}',
-            "Enable by running 'gn args out' and adding this line:",
-            f'  dir_pw_third_party_smartfusion_mss = "{path}"',
-        )
-
-
-pw_package.package_manager.register(SmartfusionMss)
diff --git a/pw_package/py/pw_package/packages/stm32cube.py b/pw_package/py/pw_package/packages/stm32cube.py
deleted file mode 100644
index e07b639..0000000
--- a/pw_package/py/pw_package/packages/stm32cube.py
+++ /dev/null
@@ -1,152 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Install and check status of stm32cube."""
-
-import pathlib
-from typing import Sequence
-
-import pw_stm32cube_build.gen_file_list
-import pw_package.git_repo
-import pw_package.package_manager
-
-# Compatible versions are listed on the README.md of each hal_driver repo
-_STM32CUBE_VERSIONS = {
-    "f0": {
-        "hal_driver_tag": "v1.7.5",
-        "cmsis_device_tag": "v2.3.5",
-        "cmsis_core_tag": "v5.4.0_cm0",
-    },
-    "f1": {
-        "hal_driver_tag": "v1.1.7",
-        "cmsis_device_tag": "v4.3.2",
-        "cmsis_core_tag": "v5.4.0_cm3",
-    },
-    "f2": {
-        "hal_driver_tag": "v1.2.6",
-        "cmsis_device_tag": "v2.2.4",
-        "cmsis_core_tag": "v5.4.0_cm3",
-    },
-    "f3": {
-        "hal_driver_tag": "v1.5.5",
-        "cmsis_device_tag": "v2.3.5",
-        "cmsis_core_tag": "v5.4.0_cm4",
-    },
-    "f4": {
-        "hal_driver_tag": "v1.7.12",
-        "cmsis_device_tag": "v2.6.6",
-        "cmsis_core_tag": "v5.4.0_cm4",
-    },
-    "f7": {
-        "hal_driver_tag": "v1.2.9",
-        "cmsis_device_tag": "v1.2.6",
-        "cmsis_core_tag": "v5.4.0_cm7",
-    },
-    "g0": {
-        "hal_driver_tag": "v1.4.1",
-        "cmsis_device_tag": "v1.4.0",
-        "cmsis_core_tag": "v5.6.0_cm0",
-    },
-    "g4": {
-        "hal_driver_tag": "v1.2.1",
-        "cmsis_device_tag": "v1.2.1",
-        "cmsis_core_tag": "v5.6.0_cm4",
-    },
-    "h7": {
-        "hal_driver_tag": "v1.10.0",
-        "cmsis_device_tag": "v1.10.0",
-        "cmsis_core_tag": "v5.6.0",
-    },
-    "l0": {
-        "hal_driver_tag": "v1.10.4",
-        "cmsis_device_tag": "v1.9.1",
-        "cmsis_core_tag": "v4.5_cm0",
-    },
-    "l1": {
-        "hal_driver_tag": "v1.4.3",
-        "cmsis_device_tag": "v2.3.1",
-        "cmsis_core_tag": "v5.4.0_cm3",
-    },
-    "l4": {
-        "hal_driver_tag": "v1.13.0",
-        "cmsis_device_tag": "v1.7.1",
-        "cmsis_core_tag": "v5.6.0_cm4",
-    },
-    "l5": {
-        "hal_driver_tag": "v1.0.4",
-        "cmsis_device_tag": "v1.0.4",
-        "cmsis_core_tag": "v5.6.0_cm33",
-    },
-    "wb": {
-        "hal_driver_tag": "v1.8.0",
-        "cmsis_device_tag": "v1.8.0",
-        "cmsis_core_tag": "v5.4.0_cm4",
-    },
-    "wl": {
-        "hal_driver_tag": "v1.0.0",
-        "cmsis_device_tag": "v1.0.0",
-        "cmsis_core_tag": "v5.6.0_cm4",
-    },
-}
-
-
-class Stm32Cube(pw_package.package_manager.Package):
-    """Install and check status of stm32cube."""
-    def __init__(self, family, tags, *args, **kwargs):
-        super().__init__(*args, name=f'stm32cube_{family}', **kwargs)
-
-        st_github_url = 'https://github.com/STMicroelectronics'
-
-        self._hal_driver = pw_package.git_repo.GitRepo(
-            name='hal_driver',
-            url=f'{st_github_url}/stm32{family}xx_hal_driver.git',
-            tag=tags['hal_driver_tag'],
-        )
-
-        self._cmsis_core = pw_package.git_repo.GitRepo(
-            name='cmsis_core',
-            url=f'{st_github_url}/cmsis_core.git',
-            tag=tags['cmsis_core_tag'],
-        )
-
-        self._cmsis_device = pw_package.git_repo.GitRepo(
-            name='cmsis_device',
-            url=f'{st_github_url}/cmsis_device_{family}.git',
-            tag=tags['cmsis_device_tag'],
-        )
-
-    def install(self, path: pathlib.Path) -> None:
-        self._hal_driver.install(path / self._hal_driver.name)
-        self._cmsis_core.install(path / self._cmsis_core.name)
-        self._cmsis_device.install(path / self._cmsis_device.name)
-
-        pw_stm32cube_build.gen_file_list.gen_file_list(path)
-
-    def status(self, path: pathlib.Path) -> bool:
-        return all([
-            self._hal_driver.status(path / self._hal_driver.name),
-            self._cmsis_core.status(path / self._cmsis_core.name),
-            self._cmsis_device.status(path / self._cmsis_device.name),
-            (path / "files.txt").is_file(),
-        ])
-
-    def info(self, path: pathlib.Path) -> Sequence[str]:
-        return (
-            f'{self.name} installed in: {path}',
-            "Enable by running 'gn args out' and adding this line:",
-            f'  dir_pw_third_party_{self.name} = "{path}"',
-        )
-
-
-for st_family, st_tags in _STM32CUBE_VERSIONS.items():
-    pw_package.package_manager.register(Stm32Cube, st_family, st_tags)
diff --git a/pw_package/py/pw_package/pigweed_packages.py b/pw_package/py/pw_package/pigweed_packages.py
index c60468c..6c39266 100644
--- a/pw_package/py/pw_package/pigweed_packages.py
+++ b/pw_package/py/pw_package/pigweed_packages.py
@@ -16,19 +16,8 @@
 import sys
 
 from pw_package import package_manager
-from pw_package.packages import arduino_core  # pylint: disable=unused-import
-from pw_package.packages import boringssl  # pylint: disable=unused-import
-from pw_package.packages import chromium_verifier  # pylint: disable=unused-import
-from pw_package.packages import crlset  # pylint: disable=unused-import
-from pw_package.packages import freertos  # pylint: disable=unused-import
-from pw_package.packages import googletest  # pylint: disable=unused-import
-from pw_package.packages import mbedtls  # pylint: disable=unused-import
-from pw_package.packages import micro_ecc  # pylint: disable=unused-import
 from pw_package.packages import nanopb
-from pw_package.packages import pico_sdk  # pylint: disable=unused-import
-from pw_package.packages import protobuf  # pylint: disable=unused-import
-from pw_package.packages import smartfusion_mss  # pylint: disable=unused-import
-from pw_package.packages import stm32cube  # pylint: disable=unused-import
+from pw_package.packages import arduino_core  # pylint: disable=unused-import
 
 
 def initialize():
diff --git a/pw_package/py/pyproject.toml b/pw_package/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/pw_package/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/pw_package/py/setup.cfg b/pw_package/py/setup.cfg
deleted file mode 100644
index c26ede4..0000000
--- a/pw_package/py/setup.cfg
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pw_package
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Tools for installing optional packages
-
-[options]
-packages = find:
-zip_safe = False
-install_requires =
-
-[options.package_data]
-pw_package = py.typed
diff --git a/pw_package/py/setup.py b/pw_package/py/setup.py
index aac9183..eb574be 100644
--- a/pw_package/py/setup.py
+++ b/pw_package/py/setup.py
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2020 The Pigweed Authors
 #
 # 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
@@ -11,8 +11,18 @@
 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 # License for the specific language governing permissions and limitations under
 # the License.
-"""pw_package"""
+"""The pw_package package."""
 
 import setuptools  # type: ignore
 
-setuptools.setup()  # Package definition in setup.cfg
+setuptools.setup(
+    name='pw_package',
+    version='0.0.1',
+    author='Pigweed Authors',
+    author_email='pigweed-developers@googlegroups.com',
+    description='Tools for installing optional packages',
+    packages=setuptools.find_packages(),
+    package_data={'pw_package': ['py.typed']},
+    zip_safe=False,
+    install_requires=[],
+)
diff --git a/pw_persistent_ram/BUILD b/pw_persistent_ram/BUILD
new file mode 100644
index 0000000..1cde0cd
--- /dev/null
+++ b/pw_persistent_ram/BUILD
@@ -0,0 +1,62 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_persistent_ram",
+    hdrs = [
+        "public/pw_persistent_ram/persistent.h",
+        "public/pw_persistent_ram/persistent_buffer.h",
+    ],
+    includes = ["public"],
+    srcs = ["persistent_buffer.cc"],
+    deps = [
+        "//pw_assert",
+        "//pw_bytes",
+        "//pw_checksum",
+        "//pw_stream",
+    ],
+)
+
+pw_cc_test(
+    name = "persistent_test",
+    srcs = [
+        "persistent_test.cc",
+    ],
+    deps = [
+        ":pw_persistent_ram",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "persistent_buffer_test",
+    srcs = [
+        "persistent_buffer_test.cc",
+    ],
+    deps = [
+        ":pw_persistent_ram",
+        "//pw_unit_test",
+        "//pw_random",
+    ],
+)
diff --git a/pw_persistent_ram/BUILD.bazel b/pw_persistent_ram/BUILD.bazel
deleted file mode 100644
index 65f5551..0000000
--- a/pw_persistent_ram/BUILD.bazel
+++ /dev/null
@@ -1,63 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_persistent_ram",
-    srcs = ["persistent_buffer.cc"],
-    hdrs = [
-        "public/pw_persistent_ram/persistent.h",
-        "public/pw_persistent_ram/persistent_buffer.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_assert",
-        "//pw_bytes",
-        "//pw_checksum",
-        "//pw_stream",
-    ],
-)
-
-pw_cc_test(
-    name = "persistent_test",
-    srcs = [
-        "persistent_test.cc",
-    ],
-    deps = [
-        ":pw_persistent_ram",
-        "//pw_random",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "persistent_buffer_test",
-    srcs = [
-        "persistent_buffer_test.cc",
-    ],
-    deps = [
-        ":pw_persistent_ram",
-        "//pw_random",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_persistent_ram/BUILD.gn b/pw_persistent_ram/BUILD.gn
index 7df09ca..f992280 100644
--- a/pw_persistent_ram/BUILD.gn
+++ b/pw_persistent_ram/BUILD.gn
@@ -35,7 +35,6 @@
     dir_pw_assert,
     dir_pw_bytes,
     dir_pw_checksum,
-    dir_pw_preprocessor,
     dir_pw_stream,
   ]
 }
diff --git a/pw_persistent_ram/CMakeLists.txt b/pw_persistent_ram/CMakeLists.txt
deleted file mode 100644
index f9ba73e..0000000
--- a/pw_persistent_ram/CMakeLists.txt
+++ /dev/null
@@ -1,55 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-
-pw_add_module_library(pw_persistent_ram
-  HEADERS
-    public/pw_persistent_ram/persistent.h
-    public/pw_persistent_ram/persistent_buffer.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_assert
-    pw_bytes
-    pw_checksum
-    pw_polyfill.cstddef
-    pw_polyfill.span
-    pw_preprocessor
-    pw_stream
-  SOURCES
-    persistent_buffer.cc
-)
-
-pw_add_test(pw_persistent_ram.persistent_test
-  SOURCES
-    persistent_test.cc
-  DEPS
-    pw_persistent_ram
-    pw_random
-  GROUPS
-    modules
-    pw_persistent_ram
-)
-
-pw_add_test(pw_persistent_ram.persistent_buffer_test
-  SOURCES
-    persistent_buffer_test.cc
-  DEPS
-    pw_persistent_ram
-    pw_random
-  GROUPS
-    modules
-    pw_persistent_ram
-)
diff --git a/pw_persistent_ram/OWNERS b/pw_persistent_ram/OWNERS
deleted file mode 100644
index b09a54f..0000000
--- a/pw_persistent_ram/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-amontanez@google.com
-ewout@google.com
diff --git a/pw_persistent_ram/docs.rst b/pw_persistent_ram/docs.rst
index 0bdb873..19384bd 100644
--- a/pw_persistent_ram/docs.rst
+++ b/pw_persistent_ram/docs.rst
@@ -38,7 +38,7 @@
 
       using pw::persistent_ram::Persistent;
 
-      PW_PLACE_IN_SECTION(".noinit") Persistent<bool> persistent_bool;
+      PW_KEEP_IN_SECTION(".noinit") Persistent<bool> persistent_bool;
 
 2. If persistent memory ranges are provided, we recommend using a struct to wrap
    the different persisted objects. This then could be checked to fit in the
@@ -47,7 +47,7 @@
 
    .. code-block:: cpp
 
-      #include "pw_assert/check.h"
+      #include "pw_assert/assert.h"
       #include "pw_persistent_ram/persistent.h"
 
       // Provided for example through a linker script.
@@ -75,8 +75,8 @@
 are initialized in RAM.
 
 The preferred way to clear Persistent RAM is to simply zero entire persistent
-RAM sections and/or memory regions. Pigweed's persistent containers have picked
-integrity checks which work with zeroed memory, meaning they do not hold a value
+RAM sections and/or memory regions. Pigweed's persistents containers have picked
+integrity checks which work with zerod memory, meaning they do not hold a value
 after zeroing. Alternatively containers can be individually cleared.
 
 The boot sequence itself is tightly coupled to the number of persistent sections
@@ -150,7 +150,7 @@
       uint16_t boot_count_;
     };
 
-    PW_PLACE_IN_SECTION(".noinit") Persistent<uint16_t> persistent_boot_count;
+    PW_KEEP_IN_SECTION(".noinit") Persistent<uint16_t> persistent_boot_count;
     BootCount boot_count(persistent_boot_count);
 
     int main() {
@@ -182,7 +182,7 @@
       char reason[kMaxReasonLength];
     }
 
-    PW_PLACE_IN_SECTION(".noinit") Persistent<LastBootInfo> persistent_crash_info;
+    PW_KEEP_IN_SECTION(".noinit") Persistent<LastBootInfo> persistent_crash_info;
 
     void HandleCrash(const char* fmt, va_list args) {
       // Once this scope ends, we know the persistent object has been updated
@@ -204,14 +204,12 @@
       if (persistent_crash_info.has_value()) {
         LogLastCrashInfo(persistent_crash_info.value());
         // Clear crash info once it has been dumped.
-        persistent_crash_info.Invalidate();
+        persistent_crash_info.reset();
       }
 
       // ... rest of main
     }
 
-.. _module-pw_persistent_ram-persistent_buffer:
-
 ------------------------------------
 pw::persistent_ram::PersistentBuffer
 ------------------------------------
diff --git a/pw_persistent_ram/persistent_buffer_test.cc b/pw_persistent_ram/persistent_buffer_test.cc
index 38c4fe9..cf72fc2 100644
--- a/pw_persistent_ram/persistent_buffer_test.cc
+++ b/pw_persistent_ram/persistent_buffer_test.cc
@@ -59,8 +59,7 @@
     auto writer = persistent.GetWriter();
     EXPECT_EQ(persistent.size(), 0u);
 
-    writer.Write(std::as_bytes(std::span(&kExpectedNumber, 1)))
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    writer.Write(std::as_bytes(std::span(&kExpectedNumber, 1)));
     ASSERT_TRUE(persistent.has_value());
 
     persistent.~PersistentBuffer();  // Emulate shutdown / global destructors.
@@ -89,15 +88,12 @@
 
     auto writer = persistent.GetWriter();
     for (size_t i = 0; i < kTestString.length(); i += kWriteSize) {
-      writer
-          .Write(kTestString.data() + i,
-                 std::min(kWriteSize, kTestString.length() - i))
-          .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+      writer.Write(kTestString.data() + i,
+                   std::min(kWriteSize, kTestString.length() - i));
     }
     // Need to manually write a null terminator since std::string_view doesn't
     // include one in the string length.
-    writer.Write(std::byte(0))
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    writer.Write(std::byte(0));
 
     persistent.~PersistentBuffer();  // Emulate shutdown / global destructors.
   }
@@ -133,8 +129,7 @@
     EXPECT_EQ(persistent.size(), 0u);
 
     // Write an integer.
-    writer.Write(std::as_bytes(std::span(&kTestNumber, 1)))
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    writer.Write(std::as_bytes(std::span(&kTestNumber, 1)));
     ASSERT_TRUE(persistent.has_value());
 
     persistent.~PersistentBuffer();  // Emulate shutdown / global destructors.
@@ -148,8 +143,7 @@
     // Write more data.
     auto writer = persistent.GetWriter();
     EXPECT_EQ(persistent.size(), sizeof(kTestNumber));
-    writer.Write(std::as_bytes(std::span<const char>(kTestString)))
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    writer.Write(std::as_bytes(std::span<const char>(kTestString)));
 
     persistent.~PersistentBuffer();  // Emulate shutdown / global destructors.
   }
diff --git a/pw_persistent_ram/persistent_test.cc b/pw_persistent_ram/persistent_test.cc
index 8c667ec..bd3de4f 100644
--- a/pw_persistent_ram/persistent_test.cc
+++ b/pw_persistent_ram/persistent_test.cc
@@ -62,7 +62,7 @@
     auto& persistent = *(new (&buffer_) Persistent<uint32_t>());
     persistent = 42u;
     EXPECT_TRUE(persistent.has_value());
-    persistent.Invalidate();
+    persistent.reset();
 
     persistent.~Persistent();  // Emulate shutdown / global destructors.
   }
diff --git a/pw_persistent_ram/public/pw_persistent_ram/persistent.h b/pw_persistent_ram/public/pw_persistent_ram/persistent.h
index 29d2247..6067d3b 100644
--- a/pw_persistent_ram/public/pw_persistent_ram/persistent.h
+++ b/pw_persistent_ram/public/pw_persistent_ram/persistent.h
@@ -19,9 +19,8 @@
 #include <type_traits>
 #include <utility>
 
-#include "pw_assert/assert.h"
+#include "pw_assert/light.h"
 #include "pw_checksum/crc16_ccitt.h"
-#include "pw_preprocessor/compiler.h"
 
 namespace pw::persistent_ram {
 
@@ -102,21 +101,18 @@
   // Assignment operator.
   template <typename U = T>
   Persistent& operator=(U&& value) {
-    contents_ = std::forward<U>(value);
+    contents_ = std::move(value);
     crc_ = CalculateCrc();
     return *this;
   }
 
   // Destroys any contained value.
-  void Invalidate() {
+  void reset() {
     // The trivial destructor is skipped as it's trivial.
     std::memset(const_cast<T*>(&contents_), 0, sizeof(contents_));
     crc_ = 0;
   }
 
-  // This is deprecated, use Invalidate() instead.
-  [[deprecated]] void reset() { Invalidate(); }
-
   // Returns true if a value is held by the Persistent.
   bool has_value() const {
     return crc_ == CalculateCrc();  // There's a value if its CRC matches.
diff --git a/pw_persistent_ram/public/pw_persistent_ram/persistent_buffer.h b/pw_persistent_ram/public/pw_persistent_ram/persistent_buffer.h
index c4c1596..3f1d4e9 100644
--- a/pw_persistent_ram/public/pw_persistent_ram/persistent_buffer.h
+++ b/pw_persistent_ram/public/pw_persistent_ram/persistent_buffer.h
@@ -32,10 +32,14 @@
 // PersistentBuffer. This object should NOT be stored in persistent RAM.
 //
 // Only one writer should be open at a given time.
-class PersistentBufferWriter : public stream::NonSeekableWriter {
+class PersistentBufferWriter : public stream::Writer {
  public:
   PersistentBufferWriter() = delete;
 
+  size_t ConservativeWriteLimit() const override {
+    return buffer_.size_bytes() - size_;
+  }
+
  private:
   template <size_t>
   friend class PersistentBuffer;
@@ -48,13 +52,6 @@
   // Implementation for writing data to this stream.
   Status DoWrite(ConstByteSpan data) override;
 
-  size_t ConservativeLimit(LimitType limit) const override {
-    if (limit == LimitType::kWrite) {
-      return buffer_.size_bytes() - size_;
-    }
-    return 0;
-  }
-
   ByteSpan buffer_;
   volatile size_t& size_;
   volatile uint16_t& checksum_;
diff --git a/pw_persistent_ram/size_report/BUILD b/pw_persistent_ram/size_report/BUILD
new file mode 100644
index 0000000..11f4091
--- /dev/null
+++ b/pw_persistent_ram/size_report/BUILD
@@ -0,0 +1,35 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_binary",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_binary(
+    name = "persistent",
+    srcs = [
+        "persistent.cc",
+        "persistent_base.cc",
+        "persistent_base_with_crc16.cc",
+    ],
+    deps = [
+        "//pw_bloat:bloat_this_binary",
+        "//pw_persistent_ram:persistent",
+    ],
+)
diff --git a/pw_persistent_ram/size_report/BUILD.bazel b/pw_persistent_ram/size_report/BUILD.bazel
deleted file mode 100644
index 614cd3b..0000000
--- a/pw_persistent_ram/size_report/BUILD.bazel
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_binary",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_binary(
-    name = "persistent",
-    srcs = [
-        "persistent.cc",
-        "persistent_base.cc",
-        "persistent_base_with_crc16.cc",
-    ],
-    deps = [
-        "//pw_bloat:bloat_this_binary",
-        "//pw_persistent_ram:persistent",
-    ],
-)
diff --git a/pw_persistent_ram/size_report/persistent.cc b/pw_persistent_ram/size_report/persistent.cc
index 7389b3a..8ffeafa 100644
--- a/pw_persistent_ram/size_report/persistent.cc
+++ b/pw_persistent_ram/size_report/persistent.cc
@@ -28,8 +28,8 @@
   // Assignment operator.
   persistent = 13u;
 
-  // Invalidate.
-  persistent.Invalidate();
+  // Reset.
+  persistent.reset();
 
   // Has value and value accesstors.
   if (persistent.has_value() && persistent.value() == 0u) {
diff --git a/pw_polyfill/BUILD b/pw_polyfill/BUILD
new file mode 100644
index 0000000..1b2f74d
--- /dev/null
+++ b/pw_polyfill/BUILD
@@ -0,0 +1,118 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+load(
+    "@bazel_embedded//toolchains/tools/include_tools:defs.bzl",
+    "cc_injected_toolchain_header_library",
+    "cc_polyfill_toolchain_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+cc_injected_toolchain_header_library(
+    name = "toolchain_injected_headers",
+    hdrs = ["language_features.h"],
+)
+
+cc_polyfill_toolchain_library(
+    name = "toolchain_polyfill_overrides",
+    hdrs = [
+        "language_features.h",
+        "public_overrides/array",
+        "public_overrides/assert.h",
+        "public_overrides/bit",
+        "public_overrides/cstddef",
+        "public_overrides/iterator",
+        "public_overrides/type_traits",
+        "public_overrides/utility",
+        "standard_library_public/pw_polyfill/standard_library/array.h",
+        "standard_library_public/pw_polyfill/standard_library/assert.h",
+        "standard_library_public/pw_polyfill/standard_library/bit.h",
+        "standard_library_public/pw_polyfill/standard_library/cstddef.h",
+        "standard_library_public/pw_polyfill/standard_library/iterator.h",
+        "standard_library_public/pw_polyfill/standard_library/namespace.h",
+        "standard_library_public/pw_polyfill/standard_library/type_traits.h",
+        "standard_library_public/pw_polyfill/standard_library/utility.h",
+    ],
+    system_includes = [
+        "public_overrides",
+        "public",
+        "standard_library_public",
+    ],
+)
+
+pw_cc_library(
+    name = "pw_polyfill",
+    hdrs = [
+        "public/pw_polyfill/language_feature_macros.h",
+        "public/pw_polyfill/standard.h",
+    ],
+    includes = ["public"],
+    deps = [":standard_library"],
+)
+
+pw_cc_library(
+    name = "overrides",
+    srcs = ["language_features.h"],
+    hdrs = [
+        "public_overrides/array",
+        "public_overrides/assert.h",
+        "public_overrides/bit",
+        "public_overrides/cstddef",
+        "public_overrides/iterator",
+        "public_overrides/type_traits",
+        "public_overrides/utility",
+    ],
+    copts = [
+        "-include",
+        "language_features.h",
+    ],
+    includes = ["public_overrides"],
+    deps = [":standard_library"],
+)
+
+pw_cc_library(
+    name = "standard_library",
+    hdrs = [
+        "standard_library_public/pw_polyfill/standard_library/array.h",
+        "standard_library_public/pw_polyfill/standard_library/assert.h",
+        "standard_library_public/pw_polyfill/standard_library/bit.h",
+        "standard_library_public/pw_polyfill/standard_library/cstddef.h",
+        "standard_library_public/pw_polyfill/standard_library/iterator.h",
+        "standard_library_public/pw_polyfill/standard_library/namespace.h",
+        "standard_library_public/pw_polyfill/standard_library/type_traits.h",
+        "standard_library_public/pw_polyfill/standard_library/utility.h",
+    ],
+    includes = ["standard_library_public"],
+    visibility = ["//visibility:private"],
+)
+
+pw_cc_test(
+    name = "default_cpp_test",
+    srcs = [
+        "test.cc",
+    ],
+    deps = [
+        ":pw_polyfill",
+        ":standard_library",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_polyfill/BUILD.bazel b/pw_polyfill/BUILD.bazel
deleted file mode 100644
index 3fcef9b..0000000
--- a/pw_polyfill/BUILD.bazel
+++ /dev/null
@@ -1,155 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-load("@rules_cc_toolchain//cc_toolchain:cc_toolchain_import.bzl", "cc_toolchain_import")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-cc_toolchain_import(
-    name = "toolchain_polyfill_overrides",
-    hdrs = [
-        "public_overrides/bit",
-        "public_overrides/cstddef",
-        "public_overrides/iterator",
-        "public_overrides/type_traits",
-        "standard_library_public/pw_polyfill/standard_library/bit.h",
-        "standard_library_public/pw_polyfill/standard_library/cstddef.h",
-        "standard_library_public/pw_polyfill/standard_library/iterator.h",
-        "standard_library_public/pw_polyfill/standard_library/namespace.h",
-        "standard_library_public/pw_polyfill/standard_library/type_traits.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-        "standard_library_public",
-    ],
-)
-
-pw_cc_library(
-    name = "pw_polyfill",
-    hdrs = [
-        "public/pw_polyfill/language_feature_macros.h",
-        "public/pw_polyfill/standard.h",
-    ],
-    includes = ["public"],
-)
-
-# TODO(pwbug/602): Deprecate this once all users have been migrated to targeted
-# polyfill deps.
-pw_cc_library(
-    name = "overrides",
-    deps = [
-        ":bit",
-        ":cstddef",
-        ":iterator",
-        ":span",
-        ":type_traits",
-    ],
-)
-
-# Provides <bit>'s std::endian.
-pw_cc_library(
-    name = "bit",
-    hdrs = [
-        "public_overrides/bit",
-        "standard_library_public/pw_polyfill/standard_library/bit.h",
-    ],
-    includes = [
-        "public_overrides",
-        "standard_library_public",
-    ],
-    deps = [":standard_library"],
-)
-
-# Provides <cstddef>'s std::byte.
-pw_cc_library(
-    name = "cstddef",
-    hdrs = [
-        "public_overrides/cstddef",
-        "standard_library_public/pw_polyfill/standard_library/cstddef.h",
-    ],
-    includes = [
-        "public_overrides",
-        "standard_library_public",
-    ],
-    deps = [":standard_library"],
-)
-
-# TODO(pwbug/603): Remove this polyfill.
-pw_cc_library(
-    name = "iterator",
-    hdrs = [
-        "public_overrides/iterator",
-        "standard_library_public/pw_polyfill/standard_library/iterator.h",
-    ],
-    includes = [
-        "public_overrides",
-        "standard_library_public",
-    ],
-    deps = [
-        ":standard_library",
-        ":type_traits",
-    ],
-)
-
-# Provides <span>.
-pw_cc_library(
-    name = "span",
-    deps = ["//pw_span"],
-)
-
-# TODO(pwbug/603): Remove this polyfill.
-pw_cc_library(
-    name = "type_traits",
-    hdrs = [
-        "public_overrides/type_traits",
-        "standard_library_public/pw_polyfill/standard_library/type_traits.h",
-    ],
-    includes = [
-        "public_overrides",
-        "standard_library_public",
-    ],
-    deps = [
-        ":cstddef",
-        ":standard_library",
-    ],
-)
-
-pw_cc_library(
-    name = "standard_library",
-    hdrs = [
-        "standard_library_public/pw_polyfill/standard_library/namespace.h",
-    ],
-    includes = ["standard_library_public"],
-    visibility = ["//pw_span:__pkg__"],
-)
-
-pw_cc_test(
-    name = "default_cpp_test",
-    srcs = [
-        "test.cc",
-    ],
-    deps = [
-        ":pw_polyfill",
-        ":standard_library",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_polyfill/BUILD.gn b/pw_polyfill/BUILD.gn
index 1c0cd4b..b173e06 100644
--- a/pw_polyfill/BUILD.gn
+++ b/pw_polyfill/BUILD.gn
@@ -18,13 +18,13 @@
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_unit_test/test.gni")
 
-config("public_include_path") {
+config("public") {
   include_dirs = [ "public" ]
   visibility = [ ":*" ]
 }
 
 pw_source_set("pw_polyfill") {
-  public_configs = [ ":public_include_path" ]
+  public_configs = [ ":public" ]
   remove_public_deps = [ "*" ]
   public_deps = [ ":standard_library" ]
   public = [
@@ -35,91 +35,62 @@
 
 config("overrides_config") {
   include_dirs = [ "public_overrides" ]
+  cflags_cc = [
+    # Use -include to include the language features header in dependent files,
+    # without requiring a #include. This allows the use of newer C++ language
+    # features in older C++ versions without an explicit include.
+    "-include",
+    rebase_path("language_features.h"),
+  ]
   visibility = [ ":*" ]
 }
 
-# TODO(pwbug/602): Remove this overrides target by migrating all users to
-# explicitly depend on the polyfill(s) they require.
-group("overrides") {
+pw_source_set("overrides") {
+  public_configs = [ ":overrides_config" ]
+  remove_public_deps = [ "*" ]
   public_deps = [
-    ":bit",
-    ":cstddef",
-    ":iterator",
-    ":span",
-    ":type_traits",
+    ":standard_library",
+    "$dir_pw_span:polyfill",
   ]
+  inputs = [
+    "public_overrides/array",
+    "public_overrides/assert.h",
+    "public_overrides/bit",
+    "public_overrides/cstddef",
+    "public_overrides/iterator",
+    "public_overrides/type_traits",
+    "public_overrides/utility",
+  ]
+  sources = [ "language_features.h" ]
 }
 
 config("standard_library_public") {
   include_dirs = [ "standard_library_public" ]
 }
 
-# Provides <bit>'s std::endian.
-pw_source_set("bit") {
-  public_configs = [
-    ":standard_library_public",
-    ":overrides_config",
-  ]
-  public_deps = [ ":standard_library" ]
-  remove_public_deps = [ "*" ]
-  inputs = [ "public_overrides/bit" ]
-  public = [ "standard_library_public/pw_polyfill/standard_library/bit.h" ]
-}
-
-# Provides <cstddef>'s std::byte.
-pw_source_set("cstddef") {
-  public_configs = [
-    ":standard_library_public",
-    ":overrides_config",
-  ]
-  public_deps = [ ":standard_library" ]
-  remove_public_deps = [ "*" ]
-  inputs = [ "public_overrides/cstddef" ]
-  public = [ "standard_library_public/pw_polyfill/standard_library/cstddef.h" ]
-}
-
-# TODO(pwbug/603): Remove this polyfill.
-pw_source_set("iterator") {
-  public_configs = [
-    ":standard_library_public",
-    ":overrides_config",
-  ]
-  public_deps = [ ":standard_library" ]
-  remove_public_deps = [ "*" ]
-  inputs = [ "public_overrides/iterator" ]
-  public = [ "standard_library_public/pw_polyfill/standard_library/iterator.h" ]
-}
-
-# Provides <span>.
-pw_source_set("span") {
-  remove_public_deps = [ "*" ]
-  public_deps = [ "$dir_pw_span:polyfill" ]
-}
-
-# TODO(pwbug/603): Remove this polyfill.
-pw_source_set("type_traits") {
-  public_configs = [
-    ":standard_library_public",
-    ":overrides_config",
-  ]
-  public_deps = [ ":standard_library" ]
-  remove_public_deps = [ "*" ]
-  inputs = [ "public_overrides/type_traits" ]
-  public =
-      [ "standard_library_public/pw_polyfill/standard_library/type_traits.h" ]
-}
-
 pw_source_set("standard_library") {
   public_configs = [ ":standard_library_public" ]
   remove_public_deps = [ "*" ]
-  public =
-      [ "standard_library_public/pw_polyfill/standard_library/namespace.h" ]
-  visibility = [ ":*" ]
+  public = [
+    "standard_library_public/pw_polyfill/standard_library/array.h",
+    "standard_library_public/pw_polyfill/standard_library/assert.h",
+    "standard_library_public/pw_polyfill/standard_library/bit.h",
+    "standard_library_public/pw_polyfill/standard_library/cstddef.h",
+    "standard_library_public/pw_polyfill/standard_library/iterator.h",
+    "standard_library_public/pw_polyfill/standard_library/namespace.h",
+    "standard_library_public/pw_polyfill/standard_library/type_traits.h",
+    "standard_library_public/pw_polyfill/standard_library/utility.h",
+  ]
+  visibility = [
+    ":overrides",
+    ":pw_polyfill",
+  ]
 }
 
 pw_test_group("tests") {
   tests = [
     ":default_cpp_test",
+    ":cpp11_test",
     ":cpp14_test",
   ]
   group_deps = [ "$dir_pw_span:tests" ]
@@ -130,6 +101,13 @@
   sources = [ "test.cc" ]
 }
 
+pw_test("cpp11_test") {
+  remove_configs = [ "$dir_pw_build:cpp17" ]
+  configs = [ "$dir_pw_build:cpp11" ]
+  sources = [ "test.cc" ]
+  deps = [ ":pw_polyfill" ]
+}
+
 pw_test("cpp14_test") {
   remove_configs = [ "$dir_pw_build:cpp17" ]
   configs = [ "$dir_pw_build:cpp14" ]
diff --git a/pw_polyfill/CMakeLists.txt b/pw_polyfill/CMakeLists.txt
index c878dd3..583b500 100644
--- a/pw_polyfill/CMakeLists.txt
+++ b/pw_polyfill/CMakeLists.txt
@@ -12,90 +12,16 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
-include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
+add_library(pw_polyfill INTERFACE)
+target_include_directories(pw_polyfill INTERFACE public standard_library_public)
 
-pw_add_module_library(pw_polyfill
-  HEADERS
-    public/pw_polyfill/language_feature_macros.h
-    public/pw_polyfill/standard.h
-  PUBLIC_INCLUDES
-    public
-)
-if(Zephyr_FOUND AND CONFIG_PIGWEED_POLYFILL)
-  zephyr_link_libraries(pw_polyfill)
-endif()
-
-# TODO(pwbug/602): Remove this overrides target by migrating all users to
-# explicitly depend on the polyfill(s) they require.
-pw_add_module_library(pw_polyfill.overrides
-  PUBLIC_DEPS
-    pw_polyfill.bit
-    pw_polyfill.cstddef
-    pw_polyfill.iterator
-    pw_polyfill.span
-    pw_polyfill.type_traits
-)
-if(Zephyr_FOUND AND CONFIG_PIGWEED_POLYFILL_OVERRIDES)
-  zephyr_link_libraries(pw_polyfill.overrides)
-endif()
-
-# Provides <bit>'s std::endian.
-pw_add_module_library(pw_polyfill.bit
-  HEADERS
-    public_overrides/bit
-    standard_library_public/pw_polyfill/standard_library/bit.h
-  PUBLIC_INCLUDES
+add_library(pw_polyfill.overrides INTERFACE)
+target_link_libraries(pw_polyfill.overrides INTERFACE pw_polyfill)
+target_include_directories(pw_polyfill.overrides
+  INTERFACE
     public_overrides
     standard_library_public
-  PUBLIC_DEPS
-    pw_polyfill.standard_library
 )
-
-# Provides <cstddef>'s std::byte.
-pw_add_module_library(pw_polyfill.cstddef
-  HEADERS
-    public_overrides/cstddef
-    standard_library_public/pw_polyfill/standard_library/cstddef.h
-  PUBLIC_INCLUDES
-    public_overrides
-    standard_library_public
-  PUBLIC_DEPS
-    pw_polyfill.standard_library
-)
-
-# TODO(pwbug/603): Remove this polyfill.
-pw_add_module_library(pw_polyfill.iterator
-  HEADERS
-    public_overrides/iterator
-    standard_library_public/pw_polyfill/standard_library/iterator.h
-  PUBLIC_INCLUDES
-    public_overrides
-    standard_library_public
-  PUBLIC_DEPS
-    pw_polyfill.standard_library
-)
-
-# Provides <span>.
-pw_add_module_library(pw_polyfill.span
-  PUBLIC_DEPS
-    pw_span
-)
-
-# TODO(pwbug/603): Remove this polyfill.
-pw_add_module_library(pw_polyfill.type_traits
-  HEADERS
-    public_overrides/type_traits
-    standard_library_public/pw_polyfill/standard_library/type_traits.h
-  PUBLIC_INCLUDES
-    public_overrides
-    standard_library_public
-  PUBLIC_DEPS
-    pw_polyfill.standard_library
-)
-
-pw_add_module_library(pw_polyfill.standard_library
-  HEADERS
-    standard_library_public/pw_polyfill/standard_library/namespace.h
-  PUBLIC_INCLUDES
-    standard_library_public
+target_compile_options(pw_polyfill.overrides INTERFACE
+    -include "${CMAKE_CURRENT_SOURCE_DIR}/language_features.h"
 )
diff --git a/pw_polyfill/Kconfig b/pw_polyfill/Kconfig
deleted file mode 100644
index c575dd8..0000000
--- a/pw_polyfill/Kconfig
+++ /dev/null
@@ -1,20 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-config PIGWEED_POLYFILL
-    bool "Enable the Pigweed polyfill library (pw_polyfill)"
-
-config PIGWEED_POLYFILL_OVERRIDES
-    bool "Enable the Pigweed polyfill overrides library (pw_polyfill.overrides)"
-    depends on PIGWEED_POLYFILL
diff --git a/pw_polyfill/OWNERS b/pw_polyfill/OWNERS
deleted file mode 100644
index d96cbc6..0000000
--- a/pw_polyfill/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-hepler@google.com
diff --git a/pw_polyfill/README.md b/pw_polyfill/README.md
index fa360fd..14128d7 100644
--- a/pw_polyfill/README.md
+++ b/pw_polyfill/README.md
@@ -1 +1 @@
-# pw\_polyfill: Backports C++17 features to C++14
+# pw\_polyfill: Backports C++17 features to C++11 and C++14
diff --git a/pw_polyfill/docs.rst b/pw_polyfill/docs.rst
index b998c84..1d35878 100644
--- a/pw_polyfill/docs.rst
+++ b/pw_polyfill/docs.rst
@@ -3,7 +3,9 @@
 ===========
 pw_polyfill
 ===========
-The ``pw_polyfill`` module backports new C++ features to C++14.
+The ``pw_polyfill`` module backports new C++ features to older C++ standards.
+When possible, features are adapted to work with in standards as old as C++11.
+Pigweed does not support C++ standards older than C++11.
 
 ------------------------------------------------
 Backport new C++ features to older C++ standards
@@ -20,32 +22,37 @@
 `#include_next <https://gcc.gnu.org/onlinedocs/cpp/Wrapper-Headers.html>`_, then
 add missing features. The backported features are only defined if they aren't
 provided by the standard header, so ``pw_polyfill`` is safe to use when
-compiling with any standard C++14 or newer.
+compiling with any standard C++11 or newer.
+
+Language features are backported or stubbed via the
+``pw_polyfill/language_features.h`` header. This header is included in files
+that depend on ``pw_polyfill`` with GCC's ``-include`` option so that no
+``#include`` statement is required.
 
 The wrapper headers are in ``public_overrides``. These are provided through the
-``"$dir_pw_polyfill"`` libraries, which the GN build adds as a
-dependency for all targets:
-
-* ``$dir_pw_polyfill:bit``
-* ``$dir_pw_polyfill:cstddef``
-* ``$dir_pw_polyfill:iterator``
-* ``$dir_pw_polyfill:span``
-* ``$dir_pw_polyfill:type_traits``
-
-To apply overrides in Bazel or CMake, depend on the targets you need such as
-``//pw_polyfill:span`` or ``pw_polyfill.span`` as an example.
+``"$dir_pw_polyfill:overrides"`` library, which the GN build adds as a
+dependency for all targets. To apply overrides in Bazel or CMake, depend on the
+``//pw_polyfill:overrides`` or ``pw_polyfill.overrides`` targets. In other build
+systems, add ``pw_polyfill/standard_library_public`` and
+``pw_polyfill/public_overrides`` as include paths, and add a ``-include`` option
+for the ``language_features.h`` header.
 
 Backported features
 ===================
-==================  ================================  ===============================  ========================================
-Header              Feature                           Level of support                 Feature test macro
-==================  ================================  ===============================  ========================================
-<bit>               std::endian                       full                             __cpp_lib_endian
-<cstdlib>           std::byte                         full                             __cpp_lib_byte
-<iterator>          std::data, std::size              full                             __cpp_lib_nonmember_container_access
-<type_traits>       std::bool_constant                full                             __cpp_lib_bool_constant
-<type_traits>       std::negation, etc.               full                             __cpp_lib_logical_traits
-==================  ================================  ===============================  ========================================
+==================  ================================  ============================================  ========================================
+Header              Feature                           Level of support                              Feature test macro
+==================  ================================  ============================================  ========================================
+<array>             std::to_array                     full                                          __cpp_lib_to_array
+<bit>               std::endian                       full                                          __cpp_lib_endian
+<cstdlib>           std::byte                         full; some operators not constexpr in C++11   __cpp_lib_byte
+<iterator>          std::data, std::size              full                                          __cpp_lib_nonmember_container_access
+<type_traits>       \*_t trait aliases                partial (can expand as needed)                __cpp_lib_transformation_trait_aliases
+<type_traits>       std::is_null_pointer              full                                          __cpp_lib_is_null_pointer
+<utilty>            std::integer_sequence & helpers   full                                          __cpp_lib_integer_sequence
+(language feature)  consteval keyword                 ignored (equivalent to constexpr)             __cpp_consteval
+(language feature)  constinit keyword                 supported in clang; ignored in GCC            __cpp_constinit
+(language feature)  static_assert with no message     full                                          __cpp_static_assert
+==================  ================================  ============================================  ========================================
 
 ----------------------------------------------------
 Adapt code to compile with different versions of C++
@@ -57,17 +64,6 @@
   - ``pw_polyfill/language_feature_macros.h`` -- provides macros for adapting
     code to work with or without newer language features
 
-Language feature macros
-=======================
-======================  ================================  ========================================  ==========================
-Macro                   Feature                           Description                               Feature test macro
-======================  ================================  ========================================  ==========================
-PW_INLINE_VARIABLE      inline variables                  inline if supported by the compiler       __cpp_inline_variables
-PW_CONSTEXPR_CPP20      constexpr in C++20                constexpr if compiling for C++20          __cplusplus >= 202002L
-PW_CONSTEVAL            consteval                         consteval if supported by the compiler    __cpp_consteval
-PW_CONSTINIT            constinit                         constinit in clang and GCC 10+            __cpp_constinit
-======================  ================================  ========================================  ==========================
-
 In GN, Bazel, or CMake, depend on ``$dir_pw_polyfill``, ``//pw_polyfill``,
 or ``pw_polyfill``, respectively, to access these features. In other build
 systems, add ``pw_polyfill/standard_library_public`` and
@@ -76,10 +72,4 @@
 -------------
 Compatibility
 -------------
-C++14
-
-Zephyr
-======
-To enable ``pw_polyfill`` for Zephyr add ``CONFIG_PIGWEED_POLYFILL=y`` to the
-project's configuration. Similarly, to enable ``pw_polyfill.overrides``, add
-``CONFIG_PIGWEED_POLYFILL_OVERRIDES=y`` to the project's configuration.
+C++11
diff --git a/pw_polyfill/language_features.h b/pw_polyfill/language_features.h
new file mode 100644
index 0000000..9527935
--- /dev/null
+++ b/pw_polyfill/language_features.h
@@ -0,0 +1,71 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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.
+
+// This file provides adapters for newer C++ language features so that they can
+// be used in older versions of C++ (though the code will not function exactly
+// the same). This file is not on an include path and is intended to be used
+// with -include when compiling C++.
+//
+// pw_polyfill/language_feature_macros.h provides macro wrappers for a few
+// specific uses of modern C++ keywords.
+#pragma once
+
+// C++11 is required for the features in this header.
+#if defined(__cplusplus) && __cplusplus >= 201103L
+
+// If consteval is not supported, use constexpr. This does not guarantee
+// compile-time execution, but works equivalently in constant expressions.
+#ifndef __cpp_consteval
+#define consteval constexpr
+#endif  // __cpp_consteval
+
+// If constinit is not supported, use a compiler attribute or omit it. If
+// omitted, the compiler may still constant initialize the variable, but there
+// is no guarantee.
+#ifndef __cpp_constinit
+#ifdef __clang__
+#define constinit [[clang::require_constant_initialization]]
+#else
+#define constinit
+#endif  // __clang__
+#endif  // __cpp_constinit
+
+// This is an adapter for supporting static_assert with a single argument in
+// C++11 or C++14. Macros don't correctly parse commas in template expressions,
+// so the static_assert arguments are passed to an overloaded C++ function. The
+// full stringified static_assert arguments are used as the message.
+#if __cpp_static_assert < 201411L
+#undef __cpp_static_assert
+#define __cpp_static_assert 201411L
+
+#define static_assert(...)                                                     \
+  static_assert(::pw::polyfill::internal::StaticAssertExpression(__VA_ARGS__), \
+                #__VA_ARGS__)
+
+namespace pw {
+namespace polyfill {
+namespace internal {
+
+constexpr bool StaticAssertExpression(bool expression) { return expression; }
+
+constexpr bool StaticAssertExpression(bool expression, const char*) {
+  return expression;
+}
+
+}  // namespace internal
+}  // namespace polyfill
+}  // namespace pw
+
+#endif  // __cpp_static_assert < 201411L
+#endif  // defined(__cplusplus) && __cplusplus >= 201103L
diff --git a/pw_polyfill/public/pw_polyfill/language_feature_macros.h b/pw_polyfill/public/pw_polyfill/language_feature_macros.h
index 87dc4e5..389493f 100644
--- a/pw_polyfill/public/pw_polyfill/language_feature_macros.h
+++ b/pw_polyfill/public/pw_polyfill/language_feature_macros.h
@@ -12,7 +12,9 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-// Macros for using C++ features in older standards.
+// Macros for adapting to older versions of C++. A few keywords (consteval,
+// constinit) are handled by pw_polfyill/language_features.h, which is directly
+// -included by users of pw_polyfill.
 #pragma once
 
 #ifdef __cpp_inline_variables
@@ -21,27 +23,9 @@
 #define PW_INLINE_VARIABLE
 #endif  // __cpp_inline_variables
 
-// Mark functions as constexpr if C++20 or newer
-#if __cplusplus >= 202002L
-#define PW_CONSTEXPR_CPP20 constexpr
+// Mark functions as constexpr if the relaxed constexpr rules are supported.
+#if __cpp_constexpr >= 201304L
+#define PW_CONSTEXPR_FUNCTION constexpr
 #else
-#define PW_CONSTEXPR_CPP20
+#define PW_CONSTEXPR_FUNCTION
 #endif  // __cpp_constexpr >= 201304L
-
-// Mark functions as consteval if supported.
-#if defined(__cpp_consteval) && __cpp_consteval >= 201811L
-#define PW_CONSTEVAL consteval
-#else
-#define PW_CONSTEVAL constexpr
-#endif  // __cpp_consteval >= 201811L
-
-// Mark functions as constinit if supported by the compiler.
-#if defined(__cpp_constinit)
-#define PW_CONSTINIT constinit
-#elif defined(__clang__)
-#define PW_CONSTINIT [[clang::require_constant_initialization]]
-#elif __GNUC__ >= 10
-#define PW_CONSTINIT __constinit
-#else
-#define PW_CONSTINIT
-#endif  // __cpp_constinit
diff --git a/pw_polyfill/public_overrides/array b/pw_polyfill/public_overrides/array
new file mode 100644
index 0000000..b3ade61
--- /dev/null
+++ b/pw_polyfill/public_overrides/array
@@ -0,0 +1,18 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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_next <array>
+
+#include "pw_polyfill/standard_library/array.h"
diff --git a/pw_polyfill/public_overrides/assert.h b/pw_polyfill/public_overrides/assert.h
new file mode 100644
index 0000000..b2f07e5
--- /dev/null
+++ b/pw_polyfill/public_overrides/assert.h
@@ -0,0 +1,18 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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_next <assert.h>
+
+#include "pw_polyfill/standard_library/assert.h"
diff --git a/pw_polyfill/public_overrides/utility b/pw_polyfill/public_overrides/utility
new file mode 100644
index 0000000..8d22206
--- /dev/null
+++ b/pw_polyfill/public_overrides/utility
@@ -0,0 +1,18 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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_next <utility>
+
+#include "pw_polyfill/standard_library/utility.h"
diff --git a/pw_polyfill/standard_library_public/pw_polyfill/standard_library/array.h b/pw_polyfill/standard_library_public/pw_polyfill/standard_library/array.h
new file mode 100644
index 0000000..441cd79
--- /dev/null
+++ b/pw_polyfill/standard_library_public/pw_polyfill/standard_library/array.h
@@ -0,0 +1,55 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 <array>
+#include <type_traits>
+#include <utility>
+
+#include "pw_polyfill/standard_library/namespace.h"
+
+#ifndef __cpp_lib_to_array
+#define __cpp_lib_to_array 201907L
+
+_PW_POLYFILL_BEGIN_NAMESPACE_STD
+
+namespace impl {
+
+template <typename T, size_t size, size_t... indices>
+constexpr array<remove_cv_t<T>, size> CopyArray(const T (&values)[size],
+                                                index_sequence<indices...>) {
+  return {{values[indices]...}};
+}
+
+template <typename T, size_t size, size_t... indices>
+constexpr array<remove_cv_t<T>, size> MoveArray(T(&&values)[size],
+                                                index_sequence<indices...>) {
+  return {{move(values[indices])...}};
+}
+
+}  // namespace impl
+
+template <typename T, size_t size>
+constexpr array<remove_cv_t<T>, size> to_array(T (&values)[size]) {
+  return impl::CopyArray(values, make_index_sequence<size>{});
+}
+
+template <typename T, size_t size>
+constexpr array<remove_cv_t<T>, size> to_array(T(&&values)[size]) {
+  return impl::MoveArray(move(values), make_index_sequence<size>{});
+}
+
+_PW_POLYFILL_END_NAMESPACE_STD
+
+#endif  // __cpp_lib_to_array
diff --git a/pw_polyfill/standard_library_public/pw_polyfill/standard_library/assert.h b/pw_polyfill/standard_library_public/pw_polyfill/standard_library/assert.h
new file mode 100644
index 0000000..30f10f6
--- /dev/null
+++ b/pw_polyfill/standard_library_public/pw_polyfill/standard_library/assert.h
@@ -0,0 +1,27 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 <assert.h>
+
+// In C11, assert.h should define static_assert as _Static_assert.
+#if !defined(__cplusplus) && !defined(static_assert)
+
+#if __STDC_VERSION__ >= 201112L
+#define static_assert _Static_assert
+#else  // _Static_assert requires C11.
+#define static_assert(...)
+#endif  // __STDC_VERSION__ >= 201112L
+
+#endif  // !defined(__cplusplus) && !defined(static_assert)
diff --git a/pw_polyfill/standard_library_public/pw_polyfill/standard_library/cstddef.h b/pw_polyfill/standard_library_public/pw_polyfill/standard_library/cstddef.h
index e8dfc9d..763224c 100644
--- a/pw_polyfill/standard_library_public/pw_polyfill/standard_library/cstddef.h
+++ b/pw_polyfill/standard_library_public/pw_polyfill/standard_library/cstddef.h
@@ -56,17 +56,27 @@
   return byte(static_cast<unsigned int>(b) >> shift);
 }
 
+#if __cpp_constexpr >= 201304L
+
 constexpr byte& operator|=(byte& l, byte r) noexcept { return l = l | r; }
 constexpr byte& operator&=(byte& l, byte r) noexcept { return l = l & r; }
 constexpr byte& operator^=(byte& l, byte r) noexcept { return l = l ^ r; }
 
+#else  // For C++11 compatiblity, these operators cannot be constexpr.
+
+inline byte& operator|=(byte& l, byte r) noexcept { return l = l | r; }
+inline byte& operator&=(byte& l, byte r) noexcept { return l = l & r; }
+inline byte& operator^=(byte& l, byte r) noexcept { return l = l ^ r; }
+
+#endif  // __cpp_constexpr >= 201304L
+
 template <typename I>
-constexpr inline byte& operator<<=(byte& b, I shift) noexcept {
+inline byte& operator<<=(byte& b, I shift) noexcept {
   return b = b << shift;
 }
 
 template <typename I>
-constexpr inline byte& operator>>=(byte& b, I shift) noexcept {
+inline byte& operator>>=(byte& b, I shift) noexcept {
   return b = b >> shift;
 }
 
diff --git a/pw_polyfill/standard_library_public/pw_polyfill/standard_library/type_traits.h b/pw_polyfill/standard_library_public/pw_polyfill/standard_library/type_traits.h
index 2cd602b..1d9b9b2 100644
--- a/pw_polyfill/standard_library_public/pw_polyfill/standard_library/type_traits.h
+++ b/pw_polyfill/standard_library_public/pw_polyfill/standard_library/type_traits.h
@@ -19,16 +19,62 @@
 
 _PW_POLYFILL_BEGIN_NAMESPACE_STD
 
+// Defines std:foo_t aliases for typename foo::type. This is a small subset of
+// <type_traits> which may be expanded as needed.
+#ifndef __cpp_lib_transformation_trait_aliases
+#define __cpp_lib_transformation_trait_aliases 201304L
+
+template <decltype(sizeof(int)) Len, decltype(sizeof(int)) Align>
+using aligned_storage_t = typename aligned_storage<Len, Align>::type;
+
+template <typename... T>
+using common_type_t = typename common_type<T...>::type;
+
+template <bool B, typename T, typename F>
+using conditional_t = typename conditional<B, T, F>::type;
+
+template <typename T>
+using decay_t = typename decay<T>::type;
+
+template <bool B, typename T = void>
+using enable_if_t = typename enable_if<B, T>::type;
+
+template <typename T>
+using make_signed_t = typename make_signed<T>::type;
+
+template <typename T>
+using make_unsigned_t = typename make_unsigned<T>::type;
+
+template <typename T>
+using remove_cv_t = typename remove_cv<T>::type;
+
+template <typename T>
+using remove_pointer_t = typename remove_pointer<T>::type;
+
+template <typename T>
+using remove_reference_t = typename remove_reference<T>::type;
+
+#endif  // __cpp_lib_transformation_trait_aliases
+
+#ifndef __cpp_lib_is_null_pointer
+#define __cpp_lib_is_null_pointer 201309L
+
+template <typename T>
+struct is_null_pointer : std::is_same<decltype(nullptr), std::remove_cv_t<T>> {
+};
+
+#endif  // __cpp_lib_is_null_pointer
+
 #ifndef __cpp_lib_bool_constant
 #define __cpp_lib_bool_constant 201505L
-template <bool kValue>
-using bool_constant = integral_constant<bool, kValue>;
+template <bool value>
+using bool_constant = integral_constant<bool, value>;
 #endif  // __cpp_lib_bool_constant
 
 #ifndef __cpp_lib_logical_traits
 #define __cpp_lib_logical_traits 201510L
-template <typename Value>
-struct negation : bool_constant<!bool(Value::value)> {};
+template <typename value>
+struct negation : bool_constant<!bool(value::value)> {};
 
 template <typename...>
 struct conjunction : std::true_type {};
diff --git a/pw_polyfill/standard_library_public/pw_polyfill/standard_library/utility.h b/pw_polyfill/standard_library_public/pw_polyfill/standard_library/utility.h
new file mode 100644
index 0000000..026068e
--- /dev/null
+++ b/pw_polyfill/standard_library_public/pw_polyfill/standard_library/utility.h
@@ -0,0 +1,57 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 <utility>
+
+#include "pw_polyfill/standard_library/namespace.h"
+
+#ifndef __cpp_lib_integer_sequence
+#define __cpp_lib_integer_sequence 201304L
+
+_PW_POLYFILL_BEGIN_NAMESPACE_STD
+
+template <typename T, T... sequence>
+struct integer_sequence {
+  static constexpr size_t size() noexcept { return sizeof...(sequence); }
+};
+
+namespace impl {
+
+// In the absence of a compiler builtin for this, have MakeSequence expand
+// recursively to enumerate all indices up to count.
+template <size_t count, typename T, T... sequence>
+struct MakeSequence : MakeSequence<count - 1, T, count - 1, sequence...> {};
+
+template <typename T, T... sequence>
+struct MakeSequence<0, T, sequence...> : std::integer_sequence<T, sequence...> {
+};
+
+}  // namespace impl
+
+template <size_t... sequence>
+using index_sequence = integer_sequence<size_t, sequence...>;
+
+template <typename T, T count>
+using make_integer_sequence = impl::MakeSequence<count, T>;
+
+template <size_t count>
+using make_index_sequence = make_integer_sequence<size_t, count>;
+
+template <typename... T>
+using index_sequence_for = make_index_sequence<sizeof...(T)>;
+
+_PW_POLYFILL_END_NAMESPACE_STD
+
+#endif  // __cpp_lib_integer_sequence
diff --git a/pw_polyfill/test.cc b/pw_polyfill/test.cc
index b761d4e..497bd3b 100644
--- a/pw_polyfill/test.cc
+++ b/pw_polyfill/test.cc
@@ -21,6 +21,7 @@
 #include "pw_polyfill/standard_library/cstddef.h"
 #include "pw_polyfill/standard_library/iterator.h"
 #include "pw_polyfill/standard_library/type_traits.h"
+#include "pw_polyfill/standard_library/utility.h"
 
 namespace pw {
 namespace polyfill {
@@ -32,7 +33,12 @@
 
 static_assert(PW_CXX_STANDARD_IS_SUPPORTED(98), "C++98 must be supported");
 static_assert(PW_CXX_STANDARD_IS_SUPPORTED(11), "C++11 must be supported");
-static_assert(PW_CXX_STANDARD_IS_SUPPORTED(14), "C++14 must be supported");
+
+#if __cplusplus >= 201402L
+static_assert(PW_CXX_STANDARD_IS_SUPPORTED(14), "C++14 must be not supported");
+#else
+static_assert(!PW_CXX_STANDARD_IS_SUPPORTED(14), "C++14 must be supported");
+#endif  // __cplusplus >= 201402L
 
 #if __cplusplus >= 201703L
 static_assert(PW_CXX_STANDARD_IS_SUPPORTED(17), "C++17 must be not supported");
@@ -40,11 +46,47 @@
 static_assert(!PW_CXX_STANDARD_IS_SUPPORTED(17), "C++17 must be supported");
 #endif  // __cplusplus >= 201703L
 
+TEST(Array, ToArray_StringLiteral) {
+  std::array<char, sizeof("literally!")> array = std::to_array("literally!");
+  EXPECT_TRUE(std::strcmp(array.data(), "literally!") == 0);
+}
+
+TEST(Array, ToArray_Inline) {
+  constexpr std::array<int, 3> kArray = std::to_array({1, 2, 3});
+  static_assert(kArray.size() == 3);
+  EXPECT_TRUE(kArray[0] == 1);
+}
+
+TEST(Array, ToArray_Array) {
+  char c_array[] = "array!";
+  std::array<char, sizeof("array!")> array = std::to_array(c_array);
+  EXPECT_TRUE(std::strcmp(array.data(), "array!") == 0);
+}
+
+struct MoveOnly {
+  MoveOnly(char ch) : value(ch) {}
+
+  MoveOnly(const MoveOnly&) = delete;
+  MoveOnly& operator=(const MoveOnly&) = delete;
+
+  MoveOnly(MoveOnly&&) = default;
+  MoveOnly& operator=(MoveOnly&&) = default;
+
+  char value;
+};
+
+TEST(Array, ToArray_MoveOnly) {
+  MoveOnly c_array[]{MoveOnly('a'), MoveOnly('b')};
+  std::array<MoveOnly, 2> array = std::to_array(std::move(c_array));
+  EXPECT_TRUE(array[0].value == 'a');
+  EXPECT_TRUE(array[1].value == 'b');
+}
+
 TEST(Bit, Endian) {
   if (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) {
-    EXPECT_EQ(std::endian::native, std::endian::big);
+    EXPECT_TRUE(std::endian::native == std::endian::big);
   } else if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) {
-    EXPECT_EQ(std::endian::native, std::endian::little);
+    EXPECT_TRUE(std::endian::native == std::endian::little);
   } else {
     FAIL();
   }
@@ -52,42 +94,41 @@
 
 TEST(Cstddef, Byte_Operators) {
   std::byte value = std::byte(0);
-  EXPECT_EQ((value | std::byte(0x12)), std::byte(0x12));
-  EXPECT_EQ((value & std::byte(0x12)), std::byte(0));
-  EXPECT_EQ((value ^ std::byte(0x12)), std::byte(0x12));
-  EXPECT_EQ(~std::byte(0), std::byte(-1));
-  EXPECT_EQ((std::byte(1) << 3), std::byte(0x8));
-  EXPECT_EQ((std::byte(0x8) >> 3), std::byte(1));
+  EXPECT_TRUE((value | std::byte(0x12)) == std::byte(0x12));
+  EXPECT_TRUE((value & std::byte(0x12)) == std::byte(0));
+  EXPECT_TRUE((value ^ std::byte(0x12)) == std::byte(0x12));
+  EXPECT_TRUE(~std::byte(0) == std::byte(-1));
+  EXPECT_TRUE((std::byte(1) << 3) == std::byte(0x8));
+  EXPECT_TRUE((std::byte(0x8) >> 3) == std::byte(1));
 }
 
 TEST(Cstddef, Byte_AssignmentOperators) {
   std::byte value = std::byte(0);
-  EXPECT_EQ((value |= std::byte(0x12)), std::byte(0x12));
-  EXPECT_EQ((value &= std::byte(0x0F)), std::byte(0x02));
-  EXPECT_EQ((value ^= std::byte(0xFF)), std::byte(0xFD));
-  EXPECT_EQ((value <<= 4), std::byte(0xD0));
-  EXPECT_EQ((value >>= 5), std::byte(0x6));
+  EXPECT_TRUE((value |= std::byte(0x12)) == std::byte(0x12));
+  EXPECT_TRUE((value &= std::byte(0x0F)) == std::byte(0x02));
+  EXPECT_TRUE((value ^= std::byte(0xFF)) == std::byte(0xFD));
+  EXPECT_TRUE((value <<= 4) == std::byte(0xD0));
+  EXPECT_TRUE((value >>= 5) == std::byte(0x6));
 }
 
 // Check that consteval is at least equivalent to constexpr.
-PW_CONSTEVAL int ConstevalFunction() { return 123; }
-static_assert(ConstevalFunction() == 123,
-              "Function should work in static_assert");
+consteval int ConstevalFunction() { return 123; }
+static_assert(ConstevalFunction() == 123);
 
 int c_array[5423] = {};
 std::array<int, 32> array;
 
 TEST(Iterator, Size) {
-  EXPECT_EQ(std::size(c_array), sizeof(c_array) / sizeof(*c_array));
-  EXPECT_EQ(std::size(array), array.size());
+  EXPECT_TRUE(std::size(c_array) == sizeof(c_array) / sizeof(*c_array));
+  EXPECT_TRUE(std::size(array) == array.size());
 }
 
 TEST(Iterator, Data) {
-  EXPECT_EQ(std::data(c_array), c_array);
-  EXPECT_EQ(std::data(array), array.data());
+  EXPECT_TRUE(std::data(c_array) == c_array);
+  EXPECT_TRUE(std::data(array) == array.data());
 }
 
-PW_CONSTINIT bool mutable_value = true;
+constinit bool mutable_value = true;
 
 TEST(Constinit, ValueIsMutable) {
   ASSERT_TRUE(mutable_value);
@@ -140,25 +181,24 @@
                 "Alias must be defined");
 }
 
-TEST(TypeTraits, LogicalTraits) {
-  static_assert(std::conjunction<std::true_type, std::true_type>::value,
-                "conjunction should be true");
-  static_assert(!std::conjunction<std::true_type, std::false_type>::value,
-                "conjunction should be false");
-  static_assert(!std::conjunction<std::false_type, std::false_type>::value,
-                "conjunction should be false");
+TEST(Utility, IntegerSequence) {
+  static_assert(std::integer_sequence<int>::size() == 0);
+  static_assert(std::integer_sequence<int, 9, 8, 7>::size() == 3);
+  static_assert(std::make_index_sequence<1>::size() == 1);
+  static_assert(std::make_index_sequence<123>::size() == 123);
+}
 
-  static_assert(std::disjunction<std::true_type, std::true_type>::value,
-                "disjunction should be true");
-  static_assert(std::disjunction<std::true_type, std::false_type>::value,
-                "disjunction should be true");
-  static_assert(!std::disjunction<std::false_type, std::false_type>::value,
-                "disjunction should be false");
+TEST(Utility, LogicalTraits) {
+  static_assert(std::conjunction<std::true_type, std::true_type>::value);
+  static_assert(!std::conjunction<std::true_type, std::false_type>::value);
+  static_assert(!std::conjunction<std::false_type, std::false_type>::value);
 
-  static_assert(!std::negation<std::true_type>::value,
-                "negation should be false");
-  static_assert(std::negation<std::false_type>::value,
-                "negation should be true");
+  static_assert(std::disjunction<std::true_type, std::true_type>::value);
+  static_assert(std::disjunction<std::true_type, std::false_type>::value);
+  static_assert(!std::disjunction<std::false_type, std::false_type>::value);
+
+  static_assert(!std::negation<std::true_type>::value);
+  static_assert(std::negation<std::false_type>::value);
 }
 
 }  // namespace
diff --git a/pw_preprocessor/BUILD b/pw_preprocessor/BUILD
new file mode 100644
index 0000000..46494d1
--- /dev/null
+++ b/pw_preprocessor/BUILD
@@ -0,0 +1,50 @@
+# Copyright 2019 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_preprocessor",
+    srcs = ["public/pw_preprocessor/internal/arg_count_impl.h"],
+    hdrs = glob(["public/pw_preprocessor/*.h"]),
+    includes = ["public"],
+)
+
+TESTS = [
+    "arguments_test",
+    "boolean_test",
+    "compiler_test",
+    "concat_test",
+    "util_test",
+]
+
+[
+    pw_cc_test(
+        name = t,
+        srcs = [t + ".cc"],
+        deps = [
+            ":pw_preprocessor",
+            "//pw_unit_test",
+        ],
+    )
+    for t in TESTS
+]
diff --git a/pw_preprocessor/BUILD.bazel b/pw_preprocessor/BUILD.bazel
deleted file mode 100644
index 76cf4ac..0000000
--- a/pw_preprocessor/BUILD.bazel
+++ /dev/null
@@ -1,66 +0,0 @@
-# Copyright 2019 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_preprocessor",
-    srcs = [
-        "public/pw_preprocessor/internal/arg_count_impl.h",
-    ],
-    hdrs = [
-        "public/pw_preprocessor/arguments.h",
-        "public/pw_preprocessor/boolean.h",
-        "public/pw_preprocessor/compiler.h",
-        "public/pw_preprocessor/concat.h",
-        "public/pw_preprocessor/util.h",
-    ],
-    includes = ["public"],
-)
-
-pw_cc_library(
-    name = "cortex_m",
-    hdrs = [
-        "public/pw_preprocessor/arch.h",
-    ],
-    includes = ["public"],
-)
-
-TESTS = [
-    "arguments_test",
-    "boolean_test",
-    "compiler_test",
-    "concat_test",
-    "util_test",
-]
-
-[
-    pw_cc_test(
-        name = t,
-        srcs = [t + ".cc"],
-        deps = [
-            ":pw_preprocessor",
-            "//pw_unit_test",
-        ],
-    )
-    for t in TESTS
-]
diff --git a/pw_preprocessor/BUILD.gn b/pw_preprocessor/BUILD.gn
index f9cc222..95459e4 100644
--- a/pw_preprocessor/BUILD.gn
+++ b/pw_preprocessor/BUILD.gn
@@ -14,24 +14,16 @@
 
 import("//build_overrides/pigweed.gni")
 
-import("$dir_pw_build/module_config.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_unit_test/test.gni")
 
-declare_args() {
-  # The build target that overrides the default configuration options for this
-  # module. This should point to a source set that provides defines through a
-  # public config (which may -include a file or add defines directly).
-  pw_preprocessor_CONFIG = pw_build_DEFAULT_MODULE_CONFIG
-}
-
-config("public_include_path") {
+config("default_config") {
   include_dirs = [ "public" ]
 }
 
 pw_source_set("pw_preprocessor") {
-  public_configs = [ ":public_include_path" ]
+  public_configs = [ ":default_config" ]
   public = [
     "public/pw_preprocessor/arguments.h",
     "public/pw_preprocessor/boolean.h",
@@ -42,12 +34,6 @@
   sources = [ "public/pw_preprocessor/internal/arg_count_impl.h" ]
 }
 
-pw_source_set("arch") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_preprocessor/arch.h" ]
-  public_deps = [ pw_preprocessor_CONFIG ]
-}
-
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
diff --git a/pw_preprocessor/CMakeLists.txt b/pw_preprocessor/CMakeLists.txt
index 1df11f9..3e893a0 100644
--- a/pw_preprocessor/CMakeLists.txt
+++ b/pw_preprocessor/CMakeLists.txt
@@ -14,21 +14,4 @@
 
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
 
-pw_add_module_library(pw_preprocessor
-  HEADERS
-    public/pw_preprocessor/arguments.h
-    public/pw_preprocessor/boolean.h
-    public/pw_preprocessor/compiler.h
-    public/pw_preprocessor/concat.h
-    public/pw_preprocessor/util.h
-    public/pw_preprocessor/internal/arg_count_impl.h
-)
-
-pw_add_module_library(pw_preprocessor.arch
-  HEADERS
-    public/pw_preprocessor/arch.h
-)
-
-if(Zephyr_FOUND AND CONFIG_PIGWEED_PREPROCESSOR)
-  zephyr_link_libraries(pw_preprocessor)
-endif()
+pw_auto_add_simple_module(pw_preprocessor)
diff --git a/pw_preprocessor/Kconfig b/pw_preprocessor/Kconfig
deleted file mode 100644
index ec5bc92..0000000
--- a/pw_preprocessor/Kconfig
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-config PIGWEED_PREPROCESSOR
-    bool "Enable Pigweed preprocessor library (pw_preprocessor)"
diff --git a/pw_preprocessor/OWNERS b/pw_preprocessor/OWNERS
deleted file mode 100644
index 34fbf1b..0000000
--- a/pw_preprocessor/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-frolv@google.com
-hepler@google.com
diff --git a/pw_preprocessor/docs.rst b/pw_preprocessor/docs.rst
index 15e9104..070a2bd 100644
--- a/pw_preprocessor/docs.rst
+++ b/pw_preprocessor/docs.rst
@@ -18,7 +18,7 @@
 Defines macros for handling variadic arguments to function-like macros. Macros
 include the following:
 
-.. c:macro:: PW_DELEGATE_BY_ARG_COUNT(name, ...)
+.. c:function:: PW_DELEGATE_BY_ARG_COUNT(name, ...)
 
   Selects and invokes a macro based on the number of arguments provided. Expands
   to ``<name><arg_count>(...)``. For example,
@@ -30,12 +30,12 @@
   .. code-block:: cpp
 
       #define ARG_PRINT(...)  PW_DELEGATE_BY_ARG_COUNT(_ARG_PRINT, __VA_ARGS__)
-      #define _ARG_PRINT0(a)        LOG_INFO("nothing!")
-      #define _ARG_PRINT1(a)        LOG_INFO("1 arg: %s", a)
-      #define _ARG_PRINT2(a, b)     LOG_INFO("2 args: %s, %s", a, b)
-      #define _ARG_PRINT3(a, b, c)  LOG_INFO("3 args: %s, %s, %s", a, b, c)
+      #define _ARG_PRINT_0(a)        LOG_INFO("nothing!")
+      #define _ARG_PRINT_1(a)        LOG_INFO("1 arg: %s", a)
+      #define _ARG_PRINT_2(a, b)     LOG_INFO("2 args: %s, %s", a, b)
+      #define _ARG_PRINT_3(a, b, c)  LOG_INFO("3 args: %s, %s, %s", a, b, c)
 
-  When used, ``ARG_PRINT`` expands to the ``_ARG_PRINT#`` macro corresponding
+  When used, ``ARG_PRINT`` expands to the ``_ARG_PRINT_#`` macro corresponding
   to the number of arguments.
 
   .. code-block:: cpp
@@ -45,7 +45,7 @@
       ARG_PRINT("a", "b");       // Outputs: 2 args: a, b
       ARG_PRINT("a", "b", "c");  // Outputs: 3 args: a, b, c
 
-.. c:macro:: PW_COMMA_ARGS(...)
+.. c:function:: PW_COMMA_ARGS(...)
 
   Expands to a comma followed by the arguments if any arguments are provided.
   Otherwise, expands to nothing. If the final argument is empty, it is omitted.
@@ -67,161 +67,6 @@
 --------------------------
 Macros for compiler-specific features, such as attributes or builtins.
 
-.. c:macro:: PW_PACKED(declaration)
-
-  Marks a struct or class as packed.
-
-.. c:macro:: PW_USED
-
-  Marks a function or object as used, ensuring code for it is generated.
-
-.. c:macro:: PW_NO_PROLOGUE
-
-  Prevents generation of a prologue or epilogue for a function. This is
-  helpful when implementing the function in assembly.
-
-.. c:macro:: PW_PRINTF_FORMAT(format_index, parameter_index)
-
-  Marks that a function declaration takes a printf-style format string and
-  variadic arguments. This allows the compiler to perform check the validity of
-  the format string and arguments. This macro must only be on the function
-  declaration, not the definition.
-
-  The format_index is index of the format string parameter and parameter_index
-  is the starting index of the variadic arguments. Indices start at 1. For C++
-  class member functions, add one to the index to account for the implicit this
-  parameter.
-
-  This example shows a function where the format string is argument 2 and the
-  varargs start at argument 3.
-
-  .. code-block:: cpp
-
-    int PrintfStyleFunction(char* buffer,
-                            const char* fmt, ...) PW_PRINTF_FORMAT(2,3);
-
-    int PrintfStyleFunction(char* buffer, const char* fmt, ...) {
-      ... implementation here ...
-    }
-
-.. c:macro:: PW_PLACE_IN_SECTION(name)
-
-  Places a variable in the specified linker section.
-
-.. c:macro:: PW_KEEP_IN_SECTION(name)
-
-  Places a variable in the specified linker section and directs the compiler
-  to keep the variable, even if it is not used. Depending on the linker
-  options, the linker may still remove this section if it is not declared in
-  the linker script and marked KEEP.
-
-.. c:macro:: PW_NO_RETURN
-
-  Indicate to the compiler that the annotated function won't return. Example:
-
-  .. code-block:: cpp
-
-    PW_NO_RETURN void HandleAssertFailure(ErrorCode error_code);
-
-
-.. c:macro:: PW_NO_INLINE
-
-  Prevents the compiler from inlining a fuction.
-
-.. c:macro:: PW_UNREACHABLE
-
-  Indicate to the compiler that the given section of code will not be reached.
-  Example:
-
-  .. code-block:: cpp
-
-    int main() {
-      InitializeBoard();
-      vendor_StartScheduler();  // Note: vendor forgot noreturn attribute.
-      PW_UNREACHABLE;
-    }
-
-
-.. c:macro:: PW_NO_SANITIZE(check)
-
-  Indicate to a sanitizer compiler runtime to skip the named check in the
-  associated function.
-  Example:
-
-  .. code-block:: cpp
-
-    uint32_t djb2(const void* buf, size_t len)
-        PW_NO_SANITIZE("unsigned-integer-overflow"){
-      uint32_t hash = 5381;
-      const uint8_t* u8 = static_cast<const uint8_t*>(buf);
-      for (size_t i = 0; i < len; ++i) {
-        hash = (hash * 33) + u8[i]; // hash * 33 + c
-      }
-      return hash;
-    }
-
-.. c:macro:: PW_HAVE_ATTRIBUTE(x)
-
-  Wrapper around `__has_attribute`, which is defined by GCC 5+ and Clang and
-  evaluates to a non zero constant integer if the attribute is supported or 0
-  if not.
-
-.. c:macro:: PW_HAVE_CPP_ATTRIBUTE(x)
-
-  Wrapper around `__has_cpp_attribute`, which was introduced in the C++20
-  standard. It is supported by compilers even if C++20 is not in use. Evaluates
-  to a non zero constant integer if the C++ attribute is supported or 0 if not.
-
-.. c:macro:: PW_PRAGMA(contents)
-
-  Expands to a _Pragma with the contents as a string. _Pragma must take a
-  single string literal; this can be used to construct a _Pragma argument.
-
-.. c:macro:: PW_WEAK
-
-  Marks a function or object as weak, allowing the definition to be overriden.
-
-  This can be useful when supporting third-party SDKs which may conditionally
-  compile in code, for example:
-
-  .. code-block:: cpp
-
-    PW_WEAK void SysTick_Handler(void) {
-      // Default interrupt handler that might be overriden.
-    }
-
-.. c:macro:: PW_ALIAS(aliased_to)
-
-  Marks a weak function as an alias to another, allowing the definition to
-  be given a default and overriden.
-
-  This can be useful when supporting third-party SDKs which may conditionally
-  compile in code, for example:
-
-  .. code-block:: cpp
-
-    // Driver handler replaced with default unless overridden.
-    void USART_DriverHandler(void) PW_ALIAS(DefaultDriverHandler);
-
-.. c:macro:: PW_ATTRIBUTE_LIFETIME_BOUND
-
-  PW_ATTRIBUTE_LIFETIME_BOUND indicates that a resource owned by a function
-  parameter or implicit object parameter is retained by the return value of the
-  annotated function (or, for a parameter of a constructor, in the value of the
-  constructed object). This attribute causes warnings to be produced if a
-  temporary object does not live long enough.
-
-  When applied to a reference parameter, the referenced object is assumed to be
-  retained by the return value of the function. When applied to a non-reference
-  parameter (for example, a pointer or a class type), all temporaries
-  referenced by the parameter are assumed to be retained by the return value of
-  the function.
-
-  See also the upstream documentation:
-  https://clang.llvm.org/docs/AttributeReference.html#lifetimebound
-
-  This is a copy of ABSL_ATTRIBUTE_LIFETIME_BOUND.
-
 Modifying compiler diagnostics
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 ``pw_preprocessor/compiler.h`` provides macros for enabling or disabling
@@ -281,8 +126,3 @@
   nothing in C
 * ``PW_EXTERN_C_START`` / ``PW_EXTERN_C_END`` -- declares an ``extern "C" { }``
   block in C++; expands to nothing in C
-
-Zephyr
-======
-To enable ``pw_preprocessor`` for Zephyr add ``CONFIG_PIGWEED_PREPROCESSOR=y``
-to the project's configuration.
diff --git a/pw_preprocessor/public/pw_preprocessor/arch.h b/pw_preprocessor/public/pw_preprocessor/arch.h
deleted file mode 100644
index 45b7ff4..0000000
--- a/pw_preprocessor/public/pw_preprocessor/arch.h
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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(pwbug/594): arch.h should be refactored out of pw_preprocessor as the
-// scope is outside of the module. The intended scope of arch.h is only to
-// provide architecture targeting and not any added utilities and capabilities.
-// Perhaps it should be placed under pw_compiler along with pwbug/593, e.g.
-// pw_compiler/arch.h?
-// Regardless, the arch defines should likely move to a trait system in Pigweed
-// before making them public defines for others to use.
-
-// WARNING: These are all internal to Pigweed, do not use these in downstream
-// projects as they expected to move and be renamed in the near future.
-
-// _PW_ARCH_ARM_V6M should be set to 1 for Cortex M0, M0+, M1.
-//
-// Defaults to 0 unless __ARM_ARCH_6M__ is defined as provided by GCC, Clang,
-// CMSIS's headers, etc.
-#if !defined(_PW_ARCH_ARM_V6M) && defined(__ARM_ARCH_6M__)
-#define _PW_ARCH_ARM_V6M 1
-#else
-#define _PW_ARCH_ARM_V6M 0
-#endif  // _PW_ARCH_ARM_V6M
-
-// _PW_ARCH_ARM_V7M should be set to 1 for Cortex M3.
-//
-// Defaults to 0 unless __ARM_ARCH_7M__ is defined as provided by GCC, Clang,
-// CMSIS's headers, etc.
-#if !defined(_PW_ARCH_ARM_V7M) && defined(__ARM_ARCH_7M__)
-#define _PW_ARCH_ARM_V7M 1
-#else
-#define _PW_ARCH_ARM_V7M 0
-#endif  // _PW_ARCH_ARM_V7M
-
-// _PW_ARCH_ARM_V7EM should be set to 1 for Cortex M4, M7.
-//
-// Defaults to 0 unless __ARM_ARCH_7EM__ is defined as provided by GCC, Clang,
-// CMSIS's headers, etc.
-#if !defined(_PW_ARCH_ARM_V7EM) && defined(__ARM_ARCH_7EM__)
-#define _PW_ARCH_ARM_V7EM 1
-#else
-#define _PW_ARCH_ARM_V7EM 0
-#endif  // _PW_ARCH_ARM_V7EM
-
-// _PW_ARCH_ARM_V8M_BASELINE should be set to 1 for Cortex M23.
-//
-// Defaults to 0 unless __ARM_ARCH_8M_BASE__ is defined as provided by GCC,
-// Clang, CMSIS's headers, etc.
-#if !defined(_PW_ARCH_ARM_V8M_BASELINE) && defined(__ARM_ARCH_8M_BASE__)
-#define _PW_ARCH_ARM_V8M_BASELINE 1
-#else
-#define _PW_ARCH_ARM_V8M_BASELINE 0
-#endif  // _PW_ARCH_ARM_V8M_BASELINE
-
-// _PW_ARCH_ARM_V8M_MAINLINE should be set to 1 for Cortex M33, M33P.
-//
-// Defaults to 0 unless __ARM_ARCH_8M_MAIN__ is defined as provided by GCC,
-// Clang, CMSIS's headers, etc.
-#if !defined(_PW_ARCH_ARM_V8M_MAINLINE) && defined(__ARM_ARCH_8M_MAIN__)
-#define _PW_ARCH_ARM_V8M_MAINLINE 1
-#else
-#define _PW_ARCH_ARM_V8M_MAINLINE 0
-#endif  // _PW_ARCH_ARM_V8M_MAINLINE
-
-// _PW_ARCH_ARM_V8_1M_MAINLINE should be set to 1 for Cortex M55.
-//
-// Defaults to 0 unless __ARM_ARCH_8_1M_MAIN__ is defined as provided by GCC,
-// Clang, CMSIS's headers, etc.
-#if !defined(_PW_ARCH_ARM_V8_1M_MAINLINE) && defined(__ARM_ARCH_8_1M_MAIN__)
-#define _PW_ARCH_ARM_V8_1M_MAINLINE 1
-#else
-#define _PW_ARCH_ARM_V8_1M_MAINLINE 0
-#endif  // _PW_ARCH_ARM_V8_1M_MAINLINE
-
-#define _PW_ARCH_ARM_CORTEX_M_ACTIVE_COUNT                   \
-  (_PW_ARCH_ARM_V6M + _PW_ARCH_ARM_V7M + _PW_ARCH_ARM_V7EM + \
-   _PW_ARCH_ARM_V8M_BASELINE + _PW_ARCH_ARM_V8M_MAINLINE +   \
-   _PW_ARCH_ARM_V8_1M_MAINLINE)
-#if _PW_ARCH_ARM_CORTEX_M_ACTIVE_COUNT > 1
-#error "More than one ARM Cortex M architecture is active."
-#elif _PW_ARCH_ARM_CORTEX_M_ACTIVE_COUNT == 1
-#define _PW_ARCH_ARM_CORTEX_M 1
-#endif
diff --git a/pw_preprocessor/public/pw_preprocessor/compiler.h b/pw_preprocessor/public/pw_preprocessor/compiler.h
index 690a061..91fbc13 100644
--- a/pw_preprocessor/public/pw_preprocessor/compiler.h
+++ b/pw_preprocessor/public/pw_preprocessor/compiler.h
@@ -18,10 +18,6 @@
 
 #include <assert.h>
 
-// TODO(pwbug/593): compiler.h should be refactored out of pw_preprocessor as
-// the scope is outside of the module. Perhaps it should be split up and placed
-// under pw_compiler, e.g. pw_compiler/attributes.h & pw_compiler/builtins.h.
-
 // Marks a struct or class as packed.
 #define PW_PACKED(declaration) declaration __attribute__((packed))
 
@@ -64,13 +60,6 @@
 #define PW_PRINTF_FORMAT(format_index, parameter_index) \
   __attribute__((format(_PW_PRINTF_FORMAT_TYPE, format_index, parameter_index)))
 
-// Places a variable in the specified linker section.
-#ifdef __APPLE__
-#define PW_PLACE_IN_SECTION(name) __attribute__((section("__DATA," name)))
-#else
-#define PW_PLACE_IN_SECTION(name) __attribute__((section(name)))
-#endif  // __APPLE__
-
 // Places a variable in the specified linker section and directs the compiler
 // to keep the variable, even if it is not used. Depending on the linker
 // options, the linker may still remove this section if it is not declared in
@@ -127,17 +116,7 @@
 #define PW_HAVE_ATTRIBUTE(x) __has_attribute(x)
 #else
 #define PW_HAVE_ATTRIBUTE(x) 0
-#endif  // __has_attribute
-
-// A function-like feature checking macro that accepts C++11 style attributes.
-// It's a wrapper around __has_cpp_attribute
-// (https://en.cppreference.com/w/cpp/feature_test), borrowed from
-// ABSL_HAVE_CPP_ATTRIBUTE. If there is no __has_cpp_attribute, evaluates to 0.
-#if defined(__cplusplus) && defined(__has_cpp_attribute)
-#define PW_HAVE_CPP_ATTRIBUTE(x) __has_cpp_attribute(x)
-#else
-#define PW_HAVE_CPP_ATTRIBUTE(x) 0
-#endif  // defined(__cplusplus) && defined(__has_cpp_attribute)
+#endif
 
 #define _PW_REQUIRE_SEMICOLON \
   static_assert(1, "This macro must be terminated with a semicolon")
@@ -172,47 +151,3 @@
 // Expands to a _Pragma with the contents as a string. _Pragma must take a
 // single string literal; this can be used to construct a _Pragma argument.
 #define PW_PRAGMA(contents) _Pragma(#contents)
-
-// Marks a function or object as weak, allowing the definition to be overriden.
-//
-// This can be useful when supporting third-party SDKs which may conditionally
-// compile in code, for example:
-//
-//   PW_WEAK void SysTick_Handler(void) {
-//     // Default interrupt handler that might be overriden.
-//   }
-#define PW_WEAK __attribute__((weak))
-
-// Marks a weak function as an alias to another, allowing the definition to
-// be given a default and overriden.
-//
-// This can be useful when supporting third-party SDKs which may conditionally
-// compile in code, for example:
-//
-//   // Driver handler replaced with default unless overridden.
-//   void USART_DriverHandler(void) PW_ALIAS(DefaultDriverHandler);
-#define PW_ALIAS(aliased_to) __attribute__((weak, alias(#aliased_to)))
-
-// PW_ATTRIBUTE_LIFETIME_BOUND indicates that a resource owned by a function
-// parameter or implicit object parameter is retained by the return value of the
-// annotated function (or, for a parameter of a constructor, in the value of the
-// constructed object). This attribute causes warnings to be produced if a
-// temporary object does not live long enough.
-//
-// When applied to a reference parameter, the referenced object is assumed to be
-// retained by the return value of the function. When applied to a non-reference
-// parameter (for example, a pointer or a class type), all temporaries
-// referenced by the parameter are assumed to be retained by the return value of
-// the function.
-//
-// See also the upstream documentation:
-// https://clang.llvm.org/docs/AttributeReference.html#lifetimebound
-//
-// This is a copy of ABSL_ATTRIBUTE_LIFETIME_BOUND.
-#if PW_HAVE_CPP_ATTRIBUTE(clang::lifetimebound)
-#define PW_ATTRIBUTE_LIFETIME_BOUND [[clang::lifetimebound]]
-#elif PW_HAVE_ATTRIBUTE(lifetimebound)
-#define PW_ATTRIBUTE_LIFETIME_BOUND __attribute__((lifetimebound))
-#else
-#define PW_ATTRIBUTE_LIFETIME_BOUND
-#endif  // PW_ATTRIBUTE_LIFETIME_BOUND
diff --git a/pw_presubmit/OWNERS b/pw_presubmit/OWNERS
deleted file mode 100644
index 73d84b4..0000000
--- a/pw_presubmit/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-hepler@google.com
-mohrr@google.com
diff --git a/pw_presubmit/docs.rst b/pw_presubmit/docs.rst
index 899f29e..921cb35 100644
--- a/pw_presubmit/docs.rst
+++ b/pw_presubmit/docs.rst
@@ -15,11 +15,9 @@
 
 The ``pw_presubmit`` module also includes ``pw format``, a tool that provides a
 unified interface for automatically formatting code in a variety of languages.
-With ``pw format``, you can format Bazel, C, C++, Python, GN, and Go code
-according to configurations defined by your project. ``pw format`` leverages
-existing tools like ``clang-format``, and it’s simple to add support for new
-languages. (Note: Bazel formatting requires ``buildifier`` to be present on your
-system. If it's not Bazel formatting passes without checking.)
+With ``pw format``, you can format C, C++, Python, GN, and Go code according to
+configurations defined by your project. ``pw format`` leverages existing tools
+like ``clang-format``, and it’s simple to add support for new languages.
 
 .. image:: docs/pw_presubmit_demo.gif
    :alt: ``pw format`` demo
@@ -55,26 +53,18 @@
 presubmit``.
 
 Setting up the command-line interface
-=====================================
+-------------------------------------
 The ``pw_presubmit.cli`` module sets up the command-line interface for a
 presubmit script. This defines a standard set of arguments for invoking
 presubmit checks. Its use is optional, but recommended.
 
 pw_presubmit.cli
-----------------
+~~~~~~~~~~~~~~~~
 .. automodule:: pw_presubmit.cli
    :members: add_arguments, run
 
-Presubmit output directory
---------------------------
-The ``pw_presubmit`` command line interface includes an ``--output-directory``
-option that specifies the working directory to use for presubmits. The default
-path is ``out/presubmit``.  A subdirectory is created for each presubmit step.
-This directory persists between presubmit runs and can be cleaned by deleting it
-or running ``pw presubmit --clean``.
-
 Presubmit checks
-================
+----------------
 A presubmit check is defined as a function or other callable. The function must
 accept one argument: a ``PresubmitContext``, which provides the paths on which
 to run. Presubmit checks communicate failure by raising an exception.
@@ -103,61 +93,15 @@
 :ref:`example script <example-script>` uses ``pw_presubmit.Programs`` to define
 ``quick`` and ``full`` programs.
 
-Existing Presubmit Checks
--------------------------
-A small number of presubmit checks are made available through ``pw_presubmit``
-modules.
-
-Code Formatting
-^^^^^^^^^^^^^^^
-Formatting checks for a variety of languages are available from
-``pw_presubmit.format_code``. These include C/C++, Java, Go, Python, GN, and
-others. All of these checks can be included by adding
-``pw_presubmit.format_code.presubmit_checks()`` to a presubmit program. These
-all use language-specific formatters like clang-format or yapf.
-
-These will suggest fixes using ``pw format --fix``.
-
-#pragma once
-^^^^^^^^^^^^
-There's a ``pragma_once`` check that confirms the first non-comment line of
-C/C++ headers is ``#pragma once``. This is enabled by adding
-``pw_presubmit.pragma_once`` to a presubmit program.
-
-Python Checks
-^^^^^^^^^^^^^
-There are two checks in the ``pw_presubmit.python_checks`` module, ``gn_pylint``
-and ``gn_python_check``. They assume there's a top-level ``python`` GN target.
-``gn_pylint`` runs Pylint and Mypy checks and ``gn_python_check`` runs Pylint,
-Mypy, and all Python tests.
-
-Inclusive Language
-^^^^^^^^^^^^^^^^^^
-.. inclusive-language: disable
-
-The inclusive language check looks for words that are typical of non-inclusive
-code, like using "master" and "slave" in place of "primary" and "secondary" or
-"sanity check" in place of "consistency check".
-
-.. inclusive-language: enable
-
-These checks can be disabled for individual lines with
-"inclusive-language: ignore" on the line in question or the line above it, or
-for entire blocks by using "inclusive-language: disable" before the block and
-"inclusive-language: enable" after the block.
-
-.. In case things get moved around in the previous paragraphs the enable line
-.. is repeated here: inclusive-language: enable.
-
 pw_presubmit
-------------
+~~~~~~~~~~~~
 .. automodule:: pw_presubmit
    :members: filter_paths, call, PresubmitFailure, Programs
 
 .. _example-script:
 
 Example
-=======
+-------
 A simple example presubmit check script follows. This can be copied-and-pasted
 to serve as a starting point for a project's presubmit check script.
 
@@ -183,24 +127,27 @@
       sys.exit(2)
 
   import pw_presubmit
-  from pw_presubmit import (
-      build,
-      cli,
-      cpp_checks,
-      environment,
-      format_code,
-      git_repo,
-      inclusive_language,
-      filter_paths,
-      python_checks,
-      PresubmitContext,
-  )
+  from pw_presubmit import build, cli, environment, format_code, git_repo
+  from pw_presubmit import python_checks, filter_paths, PresubmitContext
   from pw_presubmit.install_hook import install_hook
 
   # Set up variables for key project paths.
   PROJECT_ROOT = Path(os.environ['MY_PROJECT_ROOT'])
   PIGWEED_ROOT = PROJECT_ROOT / 'pigweed'
 
+  #
+  # Initialization
+  #
+  def init_cipd(ctx: PresubmitContext):
+      environment.init_cipd(PIGWEED_ROOT, ctx.output_dir)
+
+
+  def init_virtualenv(ctx: PresubmitContext):
+      environment.init_virtualenv(PIGWEED_ROOT,
+                                  ctx.output_dir,
+                                  setup_py_roots=[PROJECT_ROOT])
+
+
   # Rerun the build if files with these extensions change.
   _BUILD_EXTENSIONS = frozenset(
       ['.rst', '.gn', '.gni', *format_code.C_FORMAT.extensions])
@@ -230,39 +177,31 @@
   # filters with @filter_paths.
   @filter_paths(endswith='.h', exclude=PATH_EXCLUSIONS)
   def pragma_once(ctx: PresubmitContext):
-      cpp_checks.pragma_once(ctx)
+      pw_presubmit.pragma_once(ctx)
 
 
   #
   # Presubmit check programs
   #
-  OTHER = (
-      # Checks not ran by default but that should be available. These might
-      # include tests that are expensive to run or that don't yet pass.
-      build.gn_quick_check,
-  )
-
   QUICK = (
+      # Initialize an environment for running presubmit checks.
+      init_cipd,
+      init_virtualenv,
       # List some presubmit checks to run
       pragma_once,
       host_tests,
       # Use the upstream formatting checks, with custom path filters applied.
       format_code.presubmit_checks(exclude=PATH_EXCLUSIONS),
-      # Include the upstream inclusive language check.
-      inclusive_language.inclusive_language,
-      # Include just the lint-related Python checks.
-      python_checks.gn_pylint.with_filter(exclude=PATH_EXCLUSIONS),
   )
 
   FULL = (
       QUICK,  # Add all checks from the 'quick' program
       release_build,
       # Use the upstream Python checks, with custom path filters applied.
-      # Checks listed multiple times are only run once.
-      python_checks.gn_python_check.with_filter(exclude=PATH_EXCLUSIONS),
+      python_checks.all_checks(exclude=PATH_EXCLUSIONS),
   )
 
-  PROGRAMS = pw_presubmit.Programs(other=OTHER, quick=QUICK, full=FULL)
+  PROGRAMS = pw_presubmit.Programs(quick=QUICK, full=FULL)
 
 
   def run(install: bool, **presubmit_args) -> int:
diff --git a/pw_presubmit/py/BUILD.gn b/pw_presubmit/py/BUILD.gn
index 0fa08fc..cfaa150 100644
--- a/pw_presubmit/py/BUILD.gn
+++ b/pw_presubmit/py/BUILD.gn
@@ -17,19 +17,14 @@
 import("$dir_pw_build/python.gni")
 
 pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
+  setup = [ "setup.py" ]
   sources = [
     "pw_presubmit/__init__.py",
     "pw_presubmit/build.py",
     "pw_presubmit/cli.py",
-    "pw_presubmit/cpp_checks.py",
+    "pw_presubmit/environment.py",
     "pw_presubmit/format_code.py",
     "pw_presubmit/git_repo.py",
-    "pw_presubmit/inclusive_language.py",
     "pw_presubmit/install_hook.py",
     "pw_presubmit/pigweed_presubmit.py",
     "pw_presubmit/presubmit.py",
diff --git a/pw_presubmit/py/presubmit_test.py b/pw_presubmit/py/presubmit_test.py
index c4327d5..ff2a514 100755
--- a/pw_presubmit/py/presubmit_test.py
+++ b/pw_presubmit/py/presubmit_test.py
@@ -31,7 +31,7 @@
     """Tests the presubmit Programs abstraction."""
     def setUp(self):
         self._programs = presubmit.Programs(
-            first=[_fake_function_1, (), [(_fake_function_2, )]],
+            first=[_fake_function_1, (), [(_fake_function_1, )]],
             second=[_fake_function_2],
         )
 
@@ -40,7 +40,7 @@
 
     def test_access_present_members(self):
         self.assertEqual('first', self._programs['first'].name)
-        self.assertEqual((_fake_function_1, _fake_function_2),
+        self.assertEqual((_fake_function_1, _fake_function_1),
                          tuple(self._programs['first']))
 
         self.assertEqual('second', self._programs['second'].name)
diff --git a/pw_presubmit/py/pw_presubmit/build.py b/pw_presubmit/py/pw_presubmit/build.py
index 9da43e1..835a95e 100644
--- a/pw_presubmit/py/pw_presubmit/build.py
+++ b/pw_presubmit/py/pw_presubmit/build.py
@@ -14,50 +14,21 @@
 """Functions for building code during presubmit checks."""
 
 import collections
-import contextlib
 import itertools
 import json
 import logging
 import os
 from pathlib import Path
 import re
-import subprocess
-from shutil import which
 from typing import (Collection, Container, Dict, Iterable, List, Mapping, Set,
                     Tuple, Union)
 
 from pw_package import package_manager
-from pw_presubmit import (
-    call,
-    Check,
-    filter_paths,
-    format_code,
-    log_run,
-    plural,
-    PresubmitContext,
-    PresubmitFailure,
-    tools,
-)
+from pw_presubmit import call, log_run, plural, PresubmitFailure, tools
 
 _LOG = logging.getLogger(__name__)
 
 
-def bazel(ctx: PresubmitContext, cmd: str, *args: str) -> None:
-    """Invokes Bazel with some common flags set.
-
-    Intended for use with bazel build and test. May not work with others.
-    """
-    call('bazel',
-         cmd,
-         '--verbose_failures',
-         '--verbose_explanations',
-         '--worker_verbose',
-         f'--symlink_prefix={ctx.output_dir / ".bazel-"}',
-         *args,
-         cwd=ctx.root,
-         env=env_with_clang_vars())
-
-
 def install_package(root: Path, name: str) -> None:
     """Install package with given name in given path."""
     mgr = package_manager.PackageManager(root)
@@ -91,10 +62,6 @@
         # Fall-back case handles integers as well as strings that already
         # contain double quotation marks, or look like scopes or lists.
         transformed_args.append(f'{arg}={val}')
-    # Use ccache if available for faster repeat presubmit runs.
-    if which('ccache'):
-        transformed_args.append('pw_command_launcher="ccache"')
-
     return '--args=' + ' '.join(transformed_args)
 
 
@@ -103,69 +70,29 @@
            *args: str,
            gn_check: bool = True,
            gn_fail_on_unused: bool = True,
-           export_compile_commands: Union[bool, str] = True,
            **gn_arguments) -> None:
     """Runs gn gen in the specified directory with optional GN args."""
-    args_option = gn_args(**gn_arguments)
+    args_option = (gn_args(**gn_arguments), ) if gn_arguments else ()
 
     # Delete args.gn to ensure this is a clean build.
     args_gn = gn_output_dir / 'args.gn'
     if args_gn.is_file():
         args_gn.unlink()
 
-    export_commands_arg = ''
-    if export_compile_commands:
-        export_commands_arg = '--export-compile-commands'
-        if isinstance(export_compile_commands, str):
-            export_commands_arg += f'={export_compile_commands}'
-
     call('gn',
          'gen',
          gn_output_dir,
          '--color=always',
+         *(['--check'] if gn_check else []),
          *(['--fail-on-unused-args'] if gn_fail_on_unused else []),
-         *([export_commands_arg] if export_commands_arg else []),
          *args,
-         args_option,
+         *args_option,
          cwd=gn_source_dir)
 
-    if gn_check:
-        call('gn',
-             'check',
-             gn_output_dir,
-             '--check-generated',
-             '--check-system',
-             cwd=gn_source_dir)
 
-
-def ninja(directory: Path,
-          *args,
-          save_compdb=True,
-          save_graph=True,
-          **kwargs) -> None:
+def ninja(directory: Path, *args, **kwargs) -> None:
     """Runs ninja in the specified directory."""
-    if save_compdb:
-        proc = subprocess.run(
-            ['ninja', '-C', directory, '-t', 'compdb', *args],
-            capture_output=True,
-            **kwargs)
-        (directory / 'ninja.compdb').write_bytes(proc.stdout)
-
-    if save_graph:
-        proc = subprocess.run(['ninja', '-C', directory, '-t', 'graph', *args],
-                              capture_output=True,
-                              **kwargs)
-        (directory / 'ninja.graph').write_bytes(proc.stdout)
-
     call('ninja', '-C', directory, *args, **kwargs)
-    (directory / '.ninja_log').rename(directory / 'ninja.log')
-
-
-def get_gn_args(directory: Path) -> List[Dict[str, Dict[str, str]]]:
-    """Dumps GN variables to JSON."""
-    proc = subprocess.run(['gn', 'args', directory, '--list', '--json'],
-                          stdout=subprocess.PIPE)
-    return json.loads(proc.stdout)
 
 
 def cmake(source_dir: Path,
@@ -215,15 +142,7 @@
 
 
 # Finds string literals with '.' in them.
-_MAYBE_A_PATH = re.compile(
-    r'"'  # Starting double quote.
-    # Start capture group 1 - the whole filename:
-    #   File basename, a single period, file extension.
-    r'([^\n" ]+\.[^\n" ]+)'
-    # Non-capturing group 2 (optional).
-    r'(?: > [^\n"]+)?'  # pw_zip style string "input_file.txt > output_file.txt"
-    r'"'  # Ending double quote.
-)
+_MAYBE_A_PATH = re.compile(r'"([^\n"]+\.[^\n"]+)"')
 
 
 def _search_files_for_paths(build_files: Iterable[Path]) -> Iterable[Path]:
@@ -251,9 +170,9 @@
 
 
 def check_compile_commands_for_files(
-    compile_commands: Union[Path, Iterable[Path]],
-    files: Iterable[Path],
-    extensions: Collection[str] = format_code.CPP_SOURCE_EXTS,
+        compile_commands: Union[Path, Iterable[Path]],
+        files: Iterable[Path],
+        extensions: Collection[str] = ('.c', '.cc', '.cpp'),
 ) -> List[Path]:
     """Checks for paths in one or more compile_commands.json files.
 
@@ -328,52 +247,3 @@
                      '\n'.join(str(x) for x in paths))
 
     return missing
-
-
-@contextlib.contextmanager
-def test_server(executable: str, output_dir: Path):
-    """Context manager that runs a test server executable.
-
-    Args:
-        executable: name of the test server executable
-        output_dir: path to the output directory (for logs)
-    """
-
-    with open(output_dir / 'test_server.log', 'w') as outs:
-        try:
-            proc = subprocess.Popen(
-                [executable, '--verbose'],
-                stdout=outs,
-                stderr=subprocess.STDOUT,
-            )
-
-            yield
-
-        finally:
-            proc.terminate()
-
-
-@filter_paths(endswith=('.bzl', '.bazel'))
-def bazel_lint(ctx: PresubmitContext):
-    """Runs buildifier with lint on Bazel files.
-
-    Should be run after bazel_format since that will give more useful output
-    for formatting-only issues.
-    """
-
-    failure = False
-    for path in ctx.paths:
-        try:
-            call('buildifier', '--lint=warn', '--mode=check', path)
-        except PresubmitFailure:
-            failure = True
-
-    if failure:
-        raise PresubmitFailure
-
-
-@Check
-def gn_gen_check(ctx: PresubmitContext):
-    """Runs gn gen --check to enforce correct header dependencies."""
-    pw_project_root = Path(os.environ['PW_PROJECT_ROOT'])
-    gn_gen(pw_project_root, ctx.output_dir, gn_check=True)
diff --git a/pw_presubmit/py/pw_presubmit/cli.py b/pw_presubmit/py/pw_presubmit/cli.py
index c03f5cb..7469842 100644
--- a/pw_presubmit/py/pw_presubmit/cli.py
+++ b/pw_presubmit/py/pw_presubmit/cli.py
@@ -23,13 +23,6 @@
 from pw_presubmit import git_repo, presubmit
 
 _LOG = logging.getLogger(__name__)
-DEFAULT_PATH = Path('out', 'presubmit')
-
-_OUTPUT_PATH_README = '''\
-This directory was created by pw_presubmit to run presubmit checks for the
-{repo} repository. This directory is not used by the regular GN or CMake Ninja
-builds. It may be deleted safely.
-'''
 
 
 def add_path_arguments(parser) -> None:
@@ -42,22 +35,12 @@
         help=('Paths or patterns to which to restrict the checks. These are '
               'interpreted as Git pathspecs. If --base is provided, only '
               'paths changed since that commit are checked.'))
-
-    base = parser.add_mutually_exclusive_group()
-    base.add_argument(
+    parser.add_argument(
         '-b',
         '--base',
         metavar='commit',
-        default=git_repo.TRACKING_BRANCH_ALIAS,
         help=('Git revision against which to diff for changed files. '
-              'Default is the tracking branch of the current branch.'))
-    base.add_argument(
-        '--full',
-        dest='base',
-        action='store_const',
-        const=None,
-        help='Run presubmit on all files, not just changed files.')
-
+              'If none is provided, the entire repository is used.'))
     parser.add_argument(
         '-e',
         '--exclude',
@@ -123,7 +106,7 @@
     parser.add_argument(
         '--output-directory',
         type=Path,
-        help=f'Output directory (default: {"<repo root>" / DEFAULT_PATH})',
+        help='Output directory (default: <repo root>/.presubmit)',
     )
     parser.add_argument(
         '--package-root',
@@ -189,11 +172,7 @@
         repositories = [root]
 
     if output_directory is None:
-        output_directory = root / DEFAULT_PATH
-
-    output_directory.mkdir(parents=True, exist_ok=True)
-    output_directory.joinpath('README.txt').write_text(
-        _OUTPUT_PATH_README.format(repo=root))
+        output_directory = root / '.presubmit'
 
     if not package_root:
         package_root = output_directory / 'packages'
@@ -209,10 +188,14 @@
 
         return 0
 
+    if only_list_steps:
+        for step in program:
+            print(step.__name__)
+        return 0
+
     if presubmit.run(program,
                      root,
                      repositories,
-                     only_list_steps=only_list_steps,
                      output_directory=output_directory,
                      package_root=package_root,
                      **other_args):
diff --git a/pw_presubmit/py/pw_presubmit/cpp_checks.py b/pw_presubmit/py/pw_presubmit/cpp_checks.py
deleted file mode 100644
index 5e406b9..0000000
--- a/pw_presubmit/py/pw_presubmit/cpp_checks.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""C++-related checks."""
-
-from pw_presubmit import (
-    build,
-    Check,
-    format_code,
-    PresubmitContext,
-    PresubmitFailure,
-    filter_paths,
-)
-
-
-@filter_paths(endswith=format_code.CPP_HEADER_EXTS, exclude=(r'\.pb\.h$', ))
-def pragma_once(ctx: PresubmitContext) -> None:
-    """Presubmit check that ensures all header files contain '#pragma once'."""
-
-    for path in ctx.paths:
-        with open(path) as file:
-            for line in file:
-                if line.startswith('#pragma once'):
-                    break
-            else:
-                raise PresubmitFailure('#pragma once is missing!', path=path)
-
-
-@Check
-def asan(ctx: PresubmitContext) -> None:
-    build.gn_gen(ctx.root, ctx.output_dir)
-    build.ninja(ctx.output_dir, 'asan')
-
-
-@Check
-def msan(ctx: PresubmitContext) -> None:
-    build.gn_gen(ctx.root, ctx.output_dir)
-    build.ninja(ctx.output_dir, 'msan')
-
-
-@Check
-def tsan(ctx: PresubmitContext) -> None:
-    build.gn_gen(ctx.root, ctx.output_dir)
-    build.ninja(ctx.output_dir, 'tsan')
-
-
-@Check
-def ubsan(ctx: PresubmitContext) -> None:
-    build.gn_gen(ctx.root, ctx.output_dir)
-    build.ninja(ctx.output_dir, 'ubsan')
-
-
-@Check
-def runtime_sanitizers(ctx: PresubmitContext) -> None:
-    build.gn_gen(ctx.root, ctx.output_dir)
-    build.ninja(ctx.output_dir, 'runtime_sanitizers')
-
-
-def all_sanitizers():
-    return [asan, msan, tsan, ubsan, runtime_sanitizers]
diff --git a/pw_presubmit/py/pw_presubmit/environment.py b/pw_presubmit/py/pw_presubmit/environment.py
new file mode 100644
index 0000000..7af5d52
--- /dev/null
+++ b/pw_presubmit/py/pw_presubmit/environment.py
@@ -0,0 +1,91 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+"""Functions for initializing CIPD and the Pigweed virtualenv."""
+
+import logging
+import os
+from pathlib import Path
+import sys
+from typing import Iterable, Union
+
+from pw_presubmit import call
+
+_LOG = logging.getLogger(__name__)
+
+
+def init_cipd(
+    pigweed_root: Path,
+    output_directory: Path,
+    package_files: Iterable[Path] = ()) -> None:
+    """Runs CIPD."""
+
+    # TODO(mohrr): invoke by importing rather than by subprocess.
+
+    cmd = [
+        sys.executable,
+        pigweed_root.joinpath('pw_env_setup', 'py', 'pw_env_setup',
+                              'cipd_setup', 'update.py'),
+        '--install-dir', output_directory,
+    ]  # yapf: disable
+
+    final_package_files = list(
+        pigweed_root.joinpath('pw_env_setup', 'py', 'pw_env_setup',
+                              'cipd_setup').glob('*.json'))
+    final_package_files.extend(package_files)
+
+    for package_file in final_package_files:
+        cmd.extend(('--package-file', package_file))
+
+    call(*cmd)
+
+    paths = [output_directory, output_directory.joinpath('bin')]
+    for base in output_directory.glob('*'):
+        paths.append(base)
+        paths.append(base.joinpath('bin'))
+
+    paths.append(Path(os.environ['PATH']))
+
+    os.environ['PATH'] = os.pathsep.join(str(x) for x in paths)
+    _LOG.debug('PATH %s', os.environ['PATH'])
+
+
+def init_virtualenv(
+        pigweed_root: Path,
+        output_directory: Path,
+        requirements: Iterable[Union[Path, str]] = (),
+        gn_targets: Iterable[str] = (),
+) -> None:
+    """Sets up a virtualenv, assumes recent Python 3 is already installed."""
+    virtualenv_source = pigweed_root.joinpath('pw_env_setup', 'py',
+                                              'pw_env_setup',
+                                              'virtualenv_setup')
+
+    # Need to set VIRTUAL_ENV before invoking GN because the GN targets install
+    # directly to the current virtual env.
+    os.environ['VIRTUAL_ENV'] = str(output_directory)
+    os.environ['PATH'] = os.pathsep.join((
+        str(output_directory.joinpath('bin')),
+        os.environ['PATH'],
+    ))
+
+    if not gn_targets:
+        gn_targets = (f'{os.environ["PW_ROOT"]}#:python.install', )
+
+    call(
+        'python3',
+        virtualenv_source,
+        f'--venv_path={output_directory}',
+        *(f'--requirements={x}' for x in requirements),
+        *(f'--gn-target={t}' for t in gn_targets),
+    )
diff --git a/pw_presubmit/py/pw_presubmit/format_code.py b/pw_presubmit/py/pw_presubmit/format_code.py
index 623b544..19d0954 100755
--- a/pw_presubmit/py/pw_presubmit/format_code.py
+++ b/pw_presubmit/py/pw_presubmit/format_code.py
@@ -28,7 +28,6 @@
 import re
 import subprocess
 import sys
-import tempfile
 from typing import Callable, Collection, Dict, Iterable, List, NamedTuple
 from typing import Optional, Pattern, Tuple, Union
 
@@ -41,7 +40,6 @@
         os.path.abspath(__file__))))
     import pw_presubmit
 
-import pw_cli.env
 from pw_presubmit import cli, git_repo
 from pw_presubmit.tools import exclude_paths, file_summary, log_run, plural
 
@@ -112,10 +110,9 @@
     return _check_files(files, lambda path, _: _clang_format(path))
 
 
-def clang_format_fix(files: Iterable) -> Dict[Path, str]:
+def clang_format_fix(files: Iterable) -> None:
     """Fixes formatting for the provided files in place."""
     _clang_format('-i', *files)
-    return {}
 
 
 def check_gn_format(files: Iterable[Path]) -> Dict[Path, str]:
@@ -127,44 +124,9 @@
                                        check=True).stdout)
 
 
-def fix_gn_format(files: Iterable[Path]) -> Dict[Path, str]:
+def fix_gn_format(files: Iterable[Path]) -> None:
     """Fixes formatting for the provided files in place."""
     log_run(['gn', 'format', *files], check=True)
-    return {}
-
-
-def check_bazel_format(files: Iterable[Path]) -> Dict[Path, str]:
-    """Checks formatting; returns {path: diff} for files with bad formatting."""
-    errors: Dict[Path, str] = {}
-
-    def _format_temp(path: Union[Path, str], data: bytes) -> bytes:
-        # buildifier doesn't have an option to output the changed file, so
-        # copy the file to a temp location, run buildifier on it, read that
-        # modified copy, and return its contents.
-        with tempfile.TemporaryDirectory() as temp:
-            build = Path(temp) / os.path.basename(path)
-            build.write_bytes(data)
-
-            proc = log_run(['buildifier', build], capture_output=True)
-            if proc.returncode:
-                stderr = proc.stderr.decode(errors='replace')
-                stderr = stderr.replace(str(build), str(path))
-                errors[Path(path)] = stderr
-            return build.read_bytes()
-
-    result = _check_files(files, _format_temp)
-    result.update(errors)
-    return result
-
-
-def fix_bazel_format(files: Iterable[Path]) -> Dict[Path, str]:
-    """Fixes formatting for the provided files in place."""
-    errors = {}
-    for path in files:
-        proc = log_run(['buildifier', path], capture_output=True)
-        if proc.returncode:
-            errors[path] = proc.stderr.decode()
-    return errors
 
 
 def check_go_format(files: Iterable[Path]) -> Dict[Path, str]:
@@ -174,10 +136,9 @@
             ['gofmt', path], stdout=subprocess.PIPE, check=True).stdout)
 
 
-def fix_go_format(files: Iterable[Path]) -> Dict[Path, str]:
+def fix_go_format(files: Iterable[Path]) -> None:
     """Fixes formatting for the provided files in place."""
     log_run(['gofmt', '-w', *files], check=True)
-    return {}
 
 
 def _yapf(*args, **kwargs) -> subprocess.CompletedProcess:
@@ -211,10 +172,9 @@
     return errors
 
 
-def fix_py_format(files: Iterable) -> Dict[Path, str]:
+def fix_py_format(files: Iterable):
     """Fixes formatting for the provided files in place."""
     _yapf('--in-place', *files, check=True)
-    return {}
 
 
 _TRAILING_SPACE = re.compile(rb'[ \t]+$', flags=re.MULTILINE)
@@ -243,9 +203,8 @@
     return _check_trailing_space(files, fix=False)
 
 
-def fix_trailing_space(files: Iterable[Path]) -> Dict[Path, str]:
+def fix_trailing_space(files: Iterable[Path]) -> None:
     _check_trailing_space(files, fix=True)
-    return {}
 
 
 def print_format_check(errors: Dict[Path, str],
@@ -280,17 +239,13 @@
     extensions: Collection[str]
     exclude: Collection[str]
     check: Callable[[Iterable], Dict[Path, str]]
-    fix: Callable[[Iterable], Dict[Path, str]]
+    fix: Callable[[Iterable], None]
 
 
-CPP_HEADER_EXTS = frozenset(
-    ('.h', '.hpp', '.hxx', '.h++', '.hh', '.H', '.inc', '.inl'))
-CPP_SOURCE_EXTS = frozenset(('.c', '.cpp', '.cxx', '.c++', '.cc', '.C'))
-CPP_EXTS = CPP_HEADER_EXTS.union(CPP_SOURCE_EXTS)
-
-C_FORMAT: CodeFormat = CodeFormat('C and C++', CPP_EXTS,
-                                  (r'\.pb\.h$', r'\.pb\.c$'),
-                                  clang_format_check, clang_format_fix)
+C_FORMAT: CodeFormat = CodeFormat(
+    'C and C++',
+    frozenset(['.h', '.hh', '.hpp', '.c', '.cc', '.cpp', '.inc', '.inl']),
+    (r'\.pb\.h$', r'\.pb\.c$'), clang_format_check, clang_format_fix)
 
 PROTO_FORMAT: CodeFormat = CodeFormat('Protocol buffer', ('.proto', ), (),
                                       clang_format_check, clang_format_fix)
@@ -312,8 +267,8 @@
                                    fix_gn_format)
 
 # TODO(pwbug/191): Add real code formatting support for Bazel and CMake
-BAZEL_FORMAT: CodeFormat = CodeFormat('Bazel', ('BUILD', '.bazel', '.bzl'), (),
-                                      check_bazel_format, fix_bazel_format)
+BAZEL_FORMAT: CodeFormat = CodeFormat('Bazel', ('BUILD', ), (),
+                                      check_trailing_space, fix_trailing_space)
 
 CMAKE_FORMAT: CodeFormat = CodeFormat('CMake', ('CMakeLists.txt', '.cmake'),
                                       (), check_trailing_space,
@@ -391,22 +346,12 @@
 
         return collections.OrderedDict(sorted(errors.items()))
 
-    def fix(self) -> Dict[Path, str]:
+    def fix(self) -> None:
         """Fixes format errors for supported files in place."""
-        all_errors: Dict[Path, str] = {}
         for code_format, files in self._formats.items():
-            errors = code_format.fix(files)
-            if errors:
-                for path, error in errors.items():
-                    _LOG.error('Failed to format %s', path)
-                    for line in error.splitlines():
-                        _LOG.error('%s', line)
-                all_errors.update(errors)
-                continue
-
+            code_format.fix(files)
             _LOG.info('Formatted %s',
                       plural(files, code_format.language + ' file'))
-        return all_errors
 
 
 def _file_summary(files: Iterable[Union[Path, str]], base: Path) -> List[str]:
@@ -424,21 +369,11 @@
     files = [Path(path).resolve() for path in paths if os.path.isfile(path)]
     repo = git_repo.root() if git_repo.is_repo() else None
 
-    # Implement a graceful fallback in case the tracking branch isn't available.
-    if (base == git_repo.TRACKING_BRANCH_ALIAS
-            and not git_repo.tracking_branch(repo)):
-        _LOG.warning(
-            'Failed to determine the tracking branch, using --base HEAD~1 '
-            'instead of listing all files')
-        base = 'HEAD~1'
-
     # If this is a Git repo, list the original paths with git ls-files or diff.
     if repo:
-        project_root = Path(pw_cli.env.pigweed_environment().PW_PROJECT_ROOT)
         _LOG.info(
             'Formatting %s',
-            git_repo.describe_files(repo, Path.cwd(), base, paths, exclude,
-                                    project_root))
+            git_repo.describe_files(repo, Path.cwd(), base, paths, exclude))
 
         # Add files from Git and remove duplicates.
         files = sorted(
@@ -463,19 +398,13 @@
     for line in _file_summary(paths, repo if repo else Path.cwd()):
         print(line, file=sys.stderr)
 
-    check_errors = formatter.check()
-    print_format_check(check_errors, show_fix_commands=(not fix))
+    errors = formatter.check()
+    print_format_check(errors, show_fix_commands=(not fix))
 
-    if check_errors:
+    if errors:
         if fix:
-            _LOG.info('Applying formatting fixes to %d files',
-                      len(check_errors))
-            fix_errors = formatter.fix()
-            if fix_errors:
-                _LOG.info('Failed to apply formatting fixes')
-                print_format_check(fix_errors, show_fix_commands=False)
-                return 1
-
+            formatter.fix()
+            # TODO: This should perhaps check that the fixes were successful.
             _LOG.info('Formatting fixes applied successfully')
             return 0
 
diff --git a/pw_presubmit/py/pw_presubmit/git_repo.py b/pw_presubmit/py/pw_presubmit/git_repo.py
index 25b3afa..0daaaea 100644
--- a/pw_presubmit/py/pw_presubmit/git_repo.py
+++ b/pw_presubmit/py/pw_presubmit/git_repo.py
@@ -24,9 +24,6 @@
 _LOG = logging.getLogger(__name__)
 PathOrStr = Union[Path, str]
 
-TRACKING_BRANCH_ALIAS = '@{upstream}'
-_TRACKING_BRANCH_ALIASES = TRACKING_BRANCH_ALIAS, '@{u}'
-
 
 def git_stdout(*args: PathOrStr,
                show_stderr=False,
@@ -58,39 +55,6 @@
         yield git_root / file
 
 
-def tracking_branch(repo_path: Path = None) -> Optional[str]:
-    """Returns the tracking branch of the current branch.
-
-    Since most callers of this function can safely handle a return value of
-    None, suppress exceptions and return None if there is no tracking branch.
-
-    Args:
-      repo_path: repo path from which to run commands; defaults to Path.cwd()
-
-    Raises:
-      ValueError: if repo_path is not in a Git repository
-
-    Returns:
-      the remote tracking branch name or None if there is none
-    """
-    if repo_path is None:
-        repo_path = Path.cwd()
-
-    if not is_repo(repo_path or Path.cwd()):
-        raise ValueError(f'{repo_path} is not within a Git repository')
-
-    # This command should only error out if there's no upstream branch set.
-    try:
-        return git_stdout('rev-parse',
-                          '--abbrev-ref',
-                          '--symbolic-full-name',
-                          TRACKING_BRANCH_ALIAS,
-                          repo=repo_path)
-
-    except subprocess.CalledProcessError:
-        return None
-
-
 def list_files(commit: Optional[str] = None,
                pathspecs: Collection[PathOrStr] = (),
                repo_path: Optional[Path] = None) -> List[Path]:
@@ -107,16 +71,8 @@
     if repo_path is None:
         repo_path = Path.cwd()
 
-    if commit in _TRACKING_BRANCH_ALIASES:
-        commit = tracking_branch(repo_path)
-
     if commit:
-        try:
-            return sorted(_diff_names(commit, pathspecs, repo_path))
-        except subprocess.CalledProcessError:
-            _LOG.warning(
-                'Error comparing with base revision %s of %s, listing all '
-                'files instead of just changed files', commit, repo_path)
+        return sorted(_diff_names(commit, pathspecs, repo_path))
 
     return sorted(_ls_files(pathspecs, repo_path))
 
@@ -130,21 +86,8 @@
         repo = Path.cwd()
 
     # Refresh the Git index so that the diff-index command will be accurate.
-    # The `git update-index` command isn't reliable when run in parallel with
-    # other processes that may touch files in the repo directory, so retry a
-    # few times before giving up. The hallmark of this failure mode is the lack
-    # of an error message on stderr, so if we see something there we can assume
-    # it's some other issue and raise.
-    retries = 6
-    for i in range(retries):
-        try:
-            log_run(['git', '-C', repo, 'update-index', '-q', '--refresh'],
-                    capture_output=True,
-                    check=True)
-        except subprocess.CalledProcessError as err:
-            if err.stderr or i == retries - 1:
-                raise
-            continue
+    log_run(['git', '-C', repo, 'update-index', '-q', '--refresh'], check=True)
+
     # diff-index exits with 1 if there are uncommitted changes.
     return log_run(['git', '-C', repo, 'diff-index', '--quiet', 'HEAD',
                     '--']).returncode == 1
@@ -159,13 +102,6 @@
             f'under the {repo_path.resolve().relative_to(git_root.resolve())} '
             'subdirectory')
 
-    if commit in _TRACKING_BRANCH_ALIASES:
-        commit = tracking_branch(git_root)
-        if commit is None:
-            _LOG.warning(
-                'Attempted to list files changed since the remote tracking '
-                'branch, but the repo is not tracking a branch')
-
     if commit:
         yield f'that have changed since {commit}'
 
@@ -178,24 +114,16 @@
                ', '.join(p.pattern for p in exclude) + ')')
 
 
-def describe_files(git_root: Path,
-                   repo_path: Path,
-                   commit: Optional[str],
+def describe_files(git_root: Path, repo_path: Path, commit: Optional[str],
                    pathspecs: Collection[PathOrStr],
-                   exclude: Collection[Pattern],
-                   project_root: Path = None) -> str:
+                   exclude: Collection[Pattern]) -> str:
     """Completes 'Doing something to ...' for a set of files in a Git repo."""
     constraints = list(
         _describe_constraints(git_root, repo_path, commit, pathspecs, exclude))
-
-    name = git_root.name
-    if project_root and project_root != git_root:
-        name = str(git_root.relative_to(project_root))
-
     if not constraints:
-        return f'all files in the {name} repo'
+        return f'all files in the {git_root.name} repo'
 
-    msg = f'files in the {name} repo'
+    msg = f'files in the {git_root.name} repo'
     if len(constraints) == 1:
         return f'{msg} {constraints[0]}'
 
@@ -311,14 +239,3 @@
 
 def commit_message(commit: str = 'HEAD', repo: PathOrStr = '.') -> str:
     return git_stdout('log', '--format=%B', '-n1', commit, repo=repo)
-
-
-def commit_hash(rev: str = 'HEAD',
-                short: bool = True,
-                repo: PathOrStr = '.') -> str:
-    """Returns the commit hash of the revision."""
-    args = ['rev-parse']
-    if short:
-        args += ['--short']
-    args += [rev]
-    return git_stdout(*args, repo=repo)
diff --git a/pw_presubmit/py/pw_presubmit/inclusive_language.py b/pw_presubmit/py/pw_presubmit/inclusive_language.py
deleted file mode 100644
index b19f74a..0000000
--- a/pw_presubmit/py/pw_presubmit/inclusive_language.py
+++ /dev/null
@@ -1,177 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Inclusive language presubmit check."""
-
-import dataclasses
-from pathlib import Path
-import re
-from typing import Dict, List, Union
-
-from . import presubmit
-
-# List borrowed from Android:
-# https://source.android.com/setup/contribute/respectful-code
-# inclusive-language: disable
-NON_INCLUSIVE_WORDS = [
-    r'master',
-    r'slave',
-    r'(white|gr[ae]y|black)\s*(list|hat)',
-    r'craz(y|ie)',
-    r'insane',
-    r'crip+led?',
-    r'sanity',
-    r'sane',
-    r'dummy',
-    r'grandfather',
-    r's?he',
-    r'his',
-    r'her',
-    r'm[ae]n[-\s]*in[-\s]*the[-\s]*middle',
-    r'mitm',
-]
-# inclusive-language: enable
-
-# Test: master  # inclusive-language: ignore
-# Test: master
-
-
-def _process_inclusive_language(*words):
-    """Turn word list into one big regex with common inflections."""
-
-    if not words:
-        words = tuple(NON_INCLUSIVE_WORDS)
-
-    all_words = []
-    for entry in words:
-        if isinstance(entry, str):
-            all_words.append(entry)
-        elif isinstance(entry, (list, tuple)):
-            all_words.extend(entry)
-        all_words.extend(x for x in words)
-    all_words = tuple(all_words)
-
-    # Confirm each individual word compiles as a valid regex.
-    for word in all_words:
-        _ = re.compile(word)
-
-    word_boundary = (
-        r'(\b|_|(?<=[a-z])(?=[A-Z])|(?<=[0-9])(?=\w)|(?<=\w)(?=[0-9]))')
-
-    return re.compile(
-        r"({b})(?i:{w})(e?[sd]{b}|{b})".format(w='|'.join(all_words),
-                                               b=word_boundary), )
-
-
-NON_INCLUSIVE_WORDS_REGEX = _process_inclusive_language()
-
-# If seen, ignore this line and the next.
-_IGNORE = 'inclusive-language: ignore'
-
-# Ignore a whole section. Please do not change the order of these lines.
-_DISABLE = 'inclusive-language: disable'
-_ENABLE = 'inclusive-language: enable'
-
-
-@dataclasses.dataclass
-class PathMatch:
-    word: str
-
-    def __repr__(self):
-        return f'Found non-inclusive word "{self.word}" in file path'
-
-
-@dataclasses.dataclass
-class LineMatch:
-    line: int
-    word: str
-
-    def __repr__(self):
-        return f'Found non-inclusive word "{self.word}" on line {self.line}'
-
-
-@presubmit.Check
-def inclusive_language(
-    ctx: presubmit.PresubmitContext,
-    words_regex=NON_INCLUSIVE_WORDS_REGEX,
-):
-    """Presubmit check that ensures files do not contain banned words."""
-
-    found_words: Dict[Path, List[Union[PathMatch, LineMatch]]] = {}
-
-    for path in ctx.paths:
-        match = words_regex.search(str(path.relative_to(ctx.root)))
-        if match:
-            found_words.setdefault(path, [])
-            found_words[path].append(PathMatch(match.group(0)))
-
-        if path.is_symlink() or path.is_dir():
-            continue
-
-        try:
-            with open(path, 'r') as ins:
-                enabled = True
-                prev = ''
-                for i, line in enumerate(ins, start=1):
-                    if _DISABLE in line:
-                        enabled = False
-                    if _ENABLE in line:
-                        enabled = True
-
-                    # If we see the ignore line on this or the previous line we
-                    # ignore any bad words on this line.
-                    ignored = _IGNORE in prev or _IGNORE in line
-
-                    if enabled and not ignored:
-                        match = words_regex.search(line)
-
-                        if match:
-                            found_words.setdefault(path, [])
-                            found_words[path].append(
-                                LineMatch(i, match.group(0)))
-
-                    # Not using 'continue' so this line always executes.
-                    prev = line
-
-        except UnicodeDecodeError:
-            # File is not text, like a gif.
-            pass
-
-    for path, matches in found_words.items():
-        print('=' * 40)
-        print(path)
-        for match in matches:
-            print(match)
-
-    if found_words:
-        print()
-        print("""
-Individual lines can be ignored with "inclusive-language: ignore". Blocks can be
-ignored with "inclusive-language: disable" and reenabled with
-"inclusive-language: enable".
-""".strip())
-        # Re-enable just in case: inclusive-language: enable.
-
-        raise presubmit.PresubmitFailure
-
-
-def inclusive_language_checker(*words):
-    """Create banned words checker for the given list of banned words."""
-
-    regex = _process_inclusive_language(*words)
-
-    def inclusive_language(  # pylint: disable=redefined-outer-name
-        ctx: presubmit.PresubmitContext):
-        globals()['inclusive_language'](ctx, regex)
-
-    return inclusive_language
diff --git a/pw_presubmit/py/pw_presubmit/install_hook.py b/pw_presubmit/py/pw_presubmit/install_hook.py
index 62363f9..632bd92 100755
--- a/pw_presubmit/py/pw_presubmit/install_hook.py
+++ b/pw_presubmit/py/pw_presubmit/install_hook.py
@@ -60,12 +60,6 @@
         line('#!/bin/sh')
         line(f'# {hook} hook generated by {__file__}')
         line()
-        line('# Unset Git environment variables, which are set when this is ')
-        line('# run as a Git hook. These environment variables cause issues ')
-        line('# when trying to run Git commands on other repos from a ')
-        line('# submodule hook.')
-        line('unset $(git rev-parse --local-env-vars)')
-        line()
         line(command)
 
     hook_path.chmod(0o755)
diff --git a/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py b/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py
index d7b65d3..75cd910 100755
--- a/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py
+++ b/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py
@@ -16,14 +16,13 @@
 """Runs the local presubmit checks for the Pigweed repository."""
 
 import argparse
-import json
 import logging
 import os
 from pathlib import Path
 import re
-import subprocess
+import shutil
 import sys
-from typing import Sequence, IO, Tuple, Optional, Callable, List
+from typing import Sequence, IO, Tuple, Optional
 
 try:
     import pw_presubmit
@@ -36,21 +35,9 @@
 
 import pw_package.pigweed_packages
 
-from pw_presubmit import (
-    build,
-    cli,
-    cpp_checks,
-    format_code,
-    git_repo,
-    call,
-    filter_paths,
-    inclusive_language,
-    plural,
-    PresubmitContext,
-    PresubmitFailure,
-    Programs,
-    python_checks,
-)
+from pw_presubmit import build, cli, format_code, git_repo
+from pw_presubmit import call, filter_paths, plural, PresubmitContext
+from pw_presubmit import PresubmitFailure, Programs
 from pw_presubmit.install_hook import install_hook
 
 _LOG = logging.getLogger(__name__)
@@ -63,7 +50,19 @@
 
 
 def _at_all_optimization_levels(target):
-    for level in ('debug', 'size_optimized', 'speed_optimized'):
+    levels = ('debug', 'size_optimized', 'speed_optimized')
+
+    # Skip optimized host GCC builds for now, since GCC sometimes emits spurious
+    # warnings.
+    #
+    #   -02: GCC 9.3 emits spurious maybe-uninitialized warnings
+    #   -0s: GCC 8.1 (Mingw-w64) emits a spurious nonnull warning
+    #
+    # TODO(pwbug/255): Enable optimized GCC builds when this is fixed.
+    if target == 'host_gcc':
+        levels = ('debug', )
+
+    for level in levels:
         yield f'{target}_{level}'
 
 
@@ -71,9 +70,7 @@
 # Build presubmit checks
 #
 def gn_clang_build(ctx: PresubmitContext):
-    build.gn_gen(ctx.root,
-                 ctx.output_dir,
-                 pw_RUN_INTEGRATION_TESTS=(sys.platform != 'win32'))
+    build.gn_gen(ctx.root, ctx.output_dir)
     build.ninja(ctx.output_dir, *_at_all_optimization_levels('host_clang'))
 
 
@@ -94,28 +91,31 @@
 
 @filter_paths(endswith=_BUILD_EXTENSIONS)
 def gn_quick_build_check(ctx: PresubmitContext):
-    """Checks the state of the GN build by running gn gen and gn check."""
     build.gn_gen(ctx.root, ctx.output_dir)
 
+    # TODO(pwbug/255): Switch to optimized GCC builds when this is fixed.
+    # See comment in _at_all_optimization_levels() above for details.
+    optimization_level = 'size_optimized'
+    if _HOST_COMPILER == 'gcc':
+        optimization_level = 'debug'
+
+    build.ninja(ctx.output_dir, f'host_{_HOST_COMPILER}_{optimization_level}',
+                'stm32f429i_size_optimized', 'python.tests', 'python.lint')
+
 
 @filter_paths(endswith=_BUILD_EXTENSIONS)
 def gn_full_build_check(ctx: PresubmitContext):
     build.gn_gen(ctx.root, ctx.output_dir)
     build.ninja(ctx.output_dir, *_at_all_optimization_levels('stm32f429i'),
                 *_at_all_optimization_levels(f'host_{_HOST_COMPILER}'),
-                'python.tests', 'python.lint', 'docs', 'fuzzers',
-                'pw_env_setup:build_pigweed_python_source_tree')
+                'python.tests', 'python.lint', 'docs')
 
 
 @filter_paths(endswith=_BUILD_EXTENSIONS)
 def gn_full_qemu_check(ctx: PresubmitContext):
     build.gn_gen(ctx.root, ctx.output_dir)
-    build.ninja(
-        ctx.output_dir,
-        *_at_all_optimization_levels('qemu_gcc'),
-        # TODO(pwbug/321) Re-enable clang.
-        # *_at_all_optimization_levels('qemu_clang'),
-    )
+    build.ninja(ctx.output_dir, *_at_all_optimization_levels('qemu_gcc'),
+                *_at_all_optimization_levels('qemu_clang'))
 
 
 @filter_paths(endswith=_BUILD_EXTENSIONS)
@@ -125,27 +125,6 @@
 
 
 @filter_paths(endswith=_BUILD_EXTENSIONS)
-def stm32f429i(ctx: PresubmitContext):
-    build.gn_gen(ctx.root, ctx.output_dir, pw_use_test_server=True)
-    with build.test_server('stm32f429i_disc1_test_server', ctx.output_dir):
-        build.ninja(ctx.output_dir, *_at_all_optimization_levels('stm32f429i'))
-
-
-@filter_paths(endswith=_BUILD_EXTENSIONS)
-def gn_boringssl_build(ctx: PresubmitContext):
-    build.install_package(ctx.package_root, 'boringssl')
-    build.gn_gen(ctx.root,
-                 ctx.output_dir,
-                 dir_pw_third_party_boringssl='"{}"'.format(ctx.package_root /
-                                                            'boringssl'))
-    build.ninja(
-        ctx.output_dir,
-        *_at_all_optimization_levels('stm32f429i'),
-        *_at_all_optimization_levels('host_clang'),
-    )
-
-
-@filter_paths(endswith=_BUILD_EXTENSIONS)
 def gn_nanopb_build(ctx: PresubmitContext):
     build.install_package(ctx.package_root, 'nanopb')
     build.gn_gen(ctx.root,
@@ -160,50 +139,6 @@
 
 
 @filter_paths(endswith=_BUILD_EXTENSIONS)
-def gn_crypto_mbedtls_build(ctx: PresubmitContext):
-    build.install_package(ctx.package_root, 'mbedtls')
-    build.gn_gen(
-        ctx.root,
-        ctx.output_dir,
-        dir_pw_third_party_mbedtls='"{}"'.format(ctx.package_root / 'mbedtls'),
-        pw_crypto_SHA256_BACKEND='"{}"'.format(ctx.root /
-                                               'pw_crypto:sha256_mbedtls'),
-        pw_crypto_ECDSA_BACKEND='"{}"'.format(ctx.root /
-                                              'pw_crypto:ecdsa_mbedtls'))
-    build.ninja(ctx.output_dir)
-
-
-@filter_paths(endswith=_BUILD_EXTENSIONS)
-def gn_crypto_boringssl_build(ctx: PresubmitContext):
-    build.install_package(ctx.package_root, 'boringssl')
-    build.gn_gen(
-        ctx.root,
-        ctx.output_dir,
-        dir_pw_third_party_boringssl='"{}"'.format(ctx.package_root /
-                                                   'boringssl'),
-        pw_crypto_SHA256_BACKEND='"{}"'.format(ctx.root /
-                                               'pw_crypto:sha256_boringssl'),
-        pw_crypto_ECDSA_BACKEND='"{}"'.format(ctx.root /
-                                              'pw_crypto:ecdsa_boringssl'),
-    )
-    build.ninja(ctx.output_dir)
-
-
-@filter_paths(endswith=_BUILD_EXTENSIONS)
-def gn_crypto_micro_ecc_build(ctx: PresubmitContext):
-    build.install_package(ctx.package_root, 'micro-ecc')
-    build.gn_gen(
-        ctx.root,
-        ctx.output_dir,
-        dir_pw_third_party_micro_ecc='"{}"'.format(ctx.package_root /
-                                                   'micro-ecc'),
-        pw_crypto_ECDSA_BACKEND='"{}"'.format(ctx.root /
-                                              'pw_crypto:ecdsa_uecc'),
-    )
-    build.ninja(ctx.output_dir)
-
-
-@filter_paths(endswith=_BUILD_EXTENSIONS)
 def gn_teensy_build(ctx: PresubmitContext):
     build.install_package(ctx.package_root, 'teensy')
     build.gn_gen(ctx.root,
@@ -217,48 +152,6 @@
 
 
 @filter_paths(endswith=_BUILD_EXTENSIONS)
-def gn_software_update_build(ctx: PresubmitContext):
-    build.install_package(ctx.package_root, 'nanopb')
-    build.install_package(ctx.package_root, 'protobuf')
-    build.install_package(ctx.package_root, 'mbedtls')
-    build.install_package(ctx.package_root, 'micro-ecc')
-    build.gn_gen(
-        ctx.root,
-        ctx.output_dir,
-        dir_pw_third_party_protobuf='"{}"'.format(ctx.package_root /
-                                                  'protobuf'),
-        dir_pw_third_party_nanopb='"{}"'.format(ctx.package_root / 'nanopb'),
-        dir_pw_third_party_micro_ecc='"{}"'.format(ctx.package_root /
-                                                   'micro-ecc'),
-        pw_crypto_ECDSA_BACKEND='"{}"'.format(ctx.root /
-                                              'pw_crypto:ecdsa_uecc'),
-        dir_pw_third_party_mbedtls='"{}"'.format(ctx.package_root / 'mbedtls'),
-        pw_crypto_SHA256_BACKEND='"{}"'.format(ctx.root /
-                                               'pw_crypto:sha256_mbedtls'))
-    build.ninja(
-        ctx.output_dir,
-        *_at_all_optimization_levels('host_clang'),
-    )
-
-
-@filter_paths(endswith=_BUILD_EXTENSIONS)
-def gn_pw_system_demo_build(ctx: PresubmitContext):
-    build.install_package(ctx.package_root, 'freertos')
-    build.install_package(ctx.package_root, 'nanopb')
-    build.install_package(ctx.package_root, 'stm32cube_f4')
-    build.gn_gen(
-        ctx.root,
-        ctx.output_dir,
-        dir_pw_third_party_freertos='"{}"'.format(ctx.package_root /
-                                                  'freertos'),
-        dir_pw_third_party_nanopb='"{}"'.format(ctx.package_root / 'nanopb'),
-        dir_pw_third_party_stm32cube_f4='"{}"'.format(ctx.package_root /
-                                                      'stm32cube_f4'),
-    )
-    build.ninja(ctx.output_dir, 'pw_system_demo')
-
-
-@filter_paths(endswith=_BUILD_EXTENSIONS)
 def gn_qemu_build(ctx: PresubmitContext):
     build.gn_gen(ctx.root, ctx.output_dir)
     build.ninja(ctx.output_dir, *_at_all_optimization_levels('qemu_gcc'))
@@ -276,8 +169,8 @@
 
 
 def gn_host_tools(ctx: PresubmitContext):
-    build.gn_gen(ctx.root, ctx.output_dir)
-    build.ninja(ctx.output_dir, 'host_tools')
+    build.gn_gen(ctx.root, ctx.output_dir, pw_build_HOST_TOOLS=True)
+    build.ninja(ctx.output_dir, 'host')
 
 
 @filter_paths(endswith=format_code.C_FORMAT.extensions)
@@ -286,195 +179,138 @@
     build.ninja(ctx.output_dir, "fuzzers")
 
 
-def _run_cmake(ctx: PresubmitContext, toolchain='host_clang') -> None:
+@filter_paths(endswith='.py')
+def python_checks(ctx: PresubmitContext):
+    build.gn_gen(ctx.root, ctx.output_dir)
+    build.ninja(
+        ctx.output_dir,
+        ':python.lint',
+        ':python.tests',
+        ':target_support_packages.lint',
+        ':target_support_packages.tests',
+    )
+
+
+def _run_cmake(ctx: PresubmitContext) -> None:
     build.install_package(ctx.package_root, 'nanopb')
 
-    env = None
-    if 'clang' in toolchain:
-        env = build.env_with_clang_vars()
-
-    toolchain_path = ctx.root / 'pw_toolchain' / toolchain / 'toolchain.cmake'
+    toolchain = ctx.root / 'pw_toolchain' / 'host_clang' / 'toolchain.cmake'
     build.cmake(ctx.root,
                 ctx.output_dir,
-                f'-DCMAKE_TOOLCHAIN_FILE={toolchain_path}',
+                f'-DCMAKE_TOOLCHAIN_FILE={toolchain}',
                 '-DCMAKE_EXPORT_COMPILE_COMMANDS=1',
                 f'-Ddir_pw_third_party_nanopb={ctx.package_root / "nanopb"}',
                 '-Dpw_third_party_nanopb_ADD_SUBDIRECTORY=ON',
-                env=env)
+                env=build.env_with_clang_vars())
 
 
 @filter_paths(endswith=(*format_code.C_FORMAT.extensions, '.cmake',
                         'CMakeLists.txt'))
-def cmake_clang(ctx: PresubmitContext):
-    _run_cmake(ctx, toolchain='host_clang')
+def cmake_tests(ctx: PresubmitContext):
+    _run_cmake(ctx)
     build.ninja(ctx.output_dir, 'pw_apps', 'pw_run_tests.modules')
 
 
-@filter_paths(endswith=(*format_code.C_FORMAT.extensions, '.cmake',
-                        'CMakeLists.txt'))
-def cmake_gcc(ctx: PresubmitContext):
-    _run_cmake(ctx, toolchain='host_gcc')
-    build.ninja(ctx.output_dir, 'pw_apps', 'pw_run_tests.modules')
-
-
-# TODO(pwbug/180): Slowly add modules here that work with bazel until all
+# TODO: Slowly add modules here that work with bazel until all
 # modules are added. Then replace with //...
-_MODULES_THAT_BUILD_WITH_BAZEL = [
-    '//pw_allocator/...',
-    '//pw_analog/...',
-    '//pw_assert/...',
+_MODULES_THAT_WORK_WITH_BAZEL = [
     '//pw_assert_basic/...',
-    '//pw_assert_log/...',
     '//pw_base64/...',
-    '//pw_bloat/...',
     '//pw_build/...',
-    '//pw_checksum/...',
-    '//pw_chrono_embos/...',
-    '//pw_chrono_freertos/...',
     '//pw_chrono_stl/...',
-    '//pw_chrono_threadx/...',
-    '//pw_cli/...',
     '//pw_containers/...',
     '//pw_cpu_exception/...',
     '//pw_docgen/...',
     '//pw_doctor/...',
-    '//pw_env_setup/...',
-    '//pw_fuzzer/...',
-    '//pw_hex_dump/...',
     '//pw_i2c/...',
-    '//pw_interrupt/...',
-    '//pw_interrupt_cortex_m/...',
-    '//pw_libc/...',
     '//pw_log/...',
     '//pw_log_basic/...',
-    '//pw_malloc/...',
-    '//pw_malloc_freelist/...',
-    '//pw_multisink/...',
     '//pw_polyfill/...',
     '//pw_preprocessor/...',
-    '//pw_protobuf/...',
     '//pw_protobuf_compiler/...',
-    '//pw_random/...',
-    '//pw_result/...',
-    '//pw_rpc/...',
     '//pw_span/...',
     '//pw_status/...',
-    '//pw_stream/...',
-    '//pw_string/...',
-    '//pw_sync_baremetal/...',
-    '//pw_sync_embos/...',
-    '//pw_sync_freertos/...',
-    '//pw_sync_stl/...',
-    '//pw_sync_threadx/...',
     '//pw_sys_io/...',
     '//pw_sys_io_baremetal_lm3s6965evb/...',
-    '//pw_sys_io_baremetal_stm32f429/...',
     '//pw_sys_io_stdio/...',
     '//pw_thread_stl/...',
-    '//pw_tool/...',
     '//pw_toolchain/...',
-    '//pw_transfer/...',
-    '//pw_unit_test/...',
     '//pw_varint/...',
     '//pw_web_ui/...',
 ]
 
-# TODO(pwbug/180): Slowly add modules here that work with bazel until all
-# modules are added. Then replace with //...
-_MODULES_THAT_TEST_WITH_BAZEL = [
-    '//pw_allocator/...',
-    '//pw_analog/...',
-    '//pw_assert/...',
-    '//pw_base64/...',
-    '//pw_checksum/...',
-    '//pw_cli/...',
-    '//pw_containers/...',
-    '//pw_hex_dump/...',
-    '//pw_i2c/...',
-    '//pw_libc/...',
-    '//pw_log/...',
-    '//pw_multisink/...',
-    '//pw_polyfill/...',
-    '//pw_preprocessor/...',
-    '//pw_protobuf/...',
-    '//pw_protobuf_compiler/...',
-    '//pw_random/...',
-    '//pw_result/...',
-    '//pw_rpc/...',
-    '//pw_span/...',
-    '//pw_status/...',
-    '//pw_stream/...',
-    '//pw_string/...',
-    '//pw_thread_stl/...',
-    '//pw_unit_test/...',
-    '//pw_varint/...',
-    '//:buildifier_test',
-]
 
-
-@filter_paths(endswith=(*format_code.C_FORMAT.extensions, '.bazel', '.bzl',
-                        'BUILD'))
-def bazel_test(ctx: PresubmitContext) -> None:
+@filter_paths(endswith=(*format_code.C_FORMAT.extensions, '.bzl', 'BUILD'))
+def bazel_test(ctx: PresubmitContext):
     """Runs bazel test on each bazel compatible module"""
-    build.bazel(ctx, 'test', *_MODULES_THAT_TEST_WITH_BAZEL,
-                '--test_output=errors')
+
+    try:
+        call('bazel',
+             'test',
+             *_MODULES_THAT_WORK_WITH_BAZEL,
+             '--verbose_failures',
+             '--verbose_explanations',
+             '--worker_verbose',
+             '--test_output=errors',
+             cwd=ctx.root,
+             env=build.env_with_clang_vars())
+    except:
+        _LOG.info('If the Bazel build inexplicably fails while the '
+                  'other builds are passing, try deleting the Bazel cache:\n'
+                  '    rm -rf ~/.cache/bazel')
+        raise
 
 
-@filter_paths(endswith=(*format_code.C_FORMAT.extensions, '.bazel', '.bzl',
-                        'BUILD'))
-def bazel_build(ctx: PresubmitContext) -> None:
-    """Runs Bazel build on each Bazel compatible module."""
-    build.bazel(ctx, 'build', *_MODULES_THAT_BUILD_WITH_BAZEL)
+@filter_paths(endswith=(*format_code.C_FORMAT.extensions, '.bzl', 'BUILD'))
+def bazel_build(ctx: PresubmitContext):
+    """Runs Bazel build on each Bazel compatible module"""
+    try:
+        call('bazel',
+             'build',
+             *_MODULES_THAT_WORK_WITH_BAZEL,
+             '--verbose_failures',
+             '--verbose_explanations',
+             '--worker_verbose',
+             cwd=ctx.root,
+             env=build.env_with_clang_vars())
+    except:
+        _LOG.info('If the Bazel build inexplicably fails while the '
+                  'other builds are passing, try deleting the Bazel cache:\n'
+                  '    rm -rf ~/.cache/bazel')
+        raise
 
 
 #
 # General presubmit checks
 #
 
-
-def _clang_system_include_paths(lang: str) -> List[str]:
-    """Generate default system header paths.
-
-    Returns the list of system include paths used by the host
-    clang installation.
-    """
-    # Dump system include paths with preprocessor verbose.
-    command = [
-        'clang++', '-Xpreprocessor', '-v', '-x', f'{lang}', f'{os.devnull}',
-        '-fsyntax-only'
-    ]
-    process = subprocess.run(command,
-                             check=True,
-                             stdout=subprocess.PIPE,
-                             stderr=subprocess.STDOUT)
-
-    # Parse the command output to retrieve system include paths.
-    # The paths are listed one per line.
-    output = process.stdout.decode(errors='backslashreplace')
-    include_paths: List[str] = []
-    for line in output.splitlines():
-        path = line.strip()
-        if os.path.exists(path):
-            include_paths.append(f'-isystem{path}')
-
-    return include_paths
+# TODO(pwbug/45) Probably want additional checks.
+_CLANG_TIDY_CHECKS = ('modernize-use-override', )
 
 
-def edit_compile_commands(in_path: Path, out_path: Path,
-                          func: Callable[[str, str, str], str]) -> None:
-    """Edit the selected compile command file.
+@filter_paths(endswith=format_code.C_FORMAT.extensions)
+def clang_tidy(ctx: PresubmitContext):
+    build.gn_gen(ctx.root, ctx.output_dir, '--export-compile-commands')
+    build.ninja(ctx.output_dir)
+    build.ninja(ctx.output_dir, '-t', 'compdb', 'objcxx', 'cxx')
 
-    Calls the input callback on all triplets (file, directory, command) in
-    the input compile commands database. The return value replaces the old
-    compile command in the output database.
-    """
-    with open(in_path) as in_file:
-        compile_commands = json.load(in_file)
-        for item in compile_commands:
-            item['command'] = func(item['file'], item['directory'],
-                                   item['command'])
-    with open(out_path, 'w') as out_file:
-        json.dump(compile_commands, out_file, indent=2)
+    run_clang_tidy = None
+    for var in ('PW_PIGWEED_CIPD_INSTALL_DIR', 'PW_CIPD_INSTALL_DIR'):
+        if var in os.environ:
+            possibility = os.path.join(os.environ[var],
+                                       'share/clang/run-clang-tidy.py')
+            if os.path.isfile(possibility):
+                run_clang_tidy = possibility
+                break
+
+    checks = ','.join(_CLANG_TIDY_CHECKS)
+    call(
+        run_clang_tidy,
+        f'-p={ctx.output_dir}',
+        f'-checks={checks}',
+        # TODO(pwbug/45) not sure if this is needed.
+        # f'-extra-arg-before=-warnings-as-errors={checks}',
+        *ctx.paths)
 
 
 # The first line must be regex because of the '20\d\d' date
@@ -482,10 +318,7 @@
 COPYRIGHT_COMMENTS = r'(#|//| \*|REM|::)'
 COPYRIGHT_BLOCK_COMMENTS = (
     # HTML comments
-    (r'<!--', r'-->'),
-    # Jinja comments
-    (r'{#', r'#}'),
-)
+    (r'<!--', r'-->'), )
 
 COPYRIGHT_FIRST_LINE_EXCEPTIONS = (
     '#!',
@@ -514,13 +347,11 @@
     # Configuration
     r'^(?:.+/)?\..+$',
     r'\bPW_PLUGINS$',
-    r'\bconstraint.list$',
     # Metadata
     r'^docker/tag$',
     r'\bAUTHORS$',
     r'\bLICENSE$',
     r'\bOWNERS$',
-    r'\bPIGWEED_MODULES$',
     r'\brequirements.txt$',
     r'\bgo.(mod|sum)$',
     r'\bpackage.json$',
@@ -531,8 +362,6 @@
     r'\.jpg$',
     r'\.json$',
     r'\.png$',
-    r'\.svg$',
-    r'\.xml$',
     # Documentation
     r'\.md$',
     r'\.rst$',
@@ -598,9 +427,6 @@
         if path.stat().st_size == 0:
             continue  # Skip empty files
 
-        if path.is_dir():
-            continue  # Skip submodules which are included in ctx.paths.
-
         with path.open() as file:
             (comment, end_block_comment,
              line) = copyright_read_first_line(file)
@@ -639,12 +465,10 @@
 
 
 _BAZEL_SOURCES_IN_BUILD = tuple(format_code.C_FORMAT.extensions)
-_GN_SOURCES_IN_BUILD = ('setup.cfg', '.toml', '.rst', '.py',
-                        *_BAZEL_SOURCES_IN_BUILD)
+_GN_SOURCES_IN_BUILD = '.rst', '.py', *_BAZEL_SOURCES_IN_BUILD
 
 
-@filter_paths(endswith=(*_GN_SOURCES_IN_BUILD, 'BUILD', '.bzl', '.gn', '.gni'),
-              exclude=['zephyr.*/', 'android.*/'])
+@filter_paths(endswith=(*_GN_SOURCES_IN_BUILD, 'BUILD', '.bzl', '.gn', '.gni'))
 def source_is_in_build_files(ctx: PresubmitContext):
     """Checks that source files are in the GN and Bazel builds."""
     missing = build.check_builds_for_files(
@@ -750,11 +574,37 @@
         raise PresubmitFailure
 
 
-@filter_paths(endswith=(*format_code.C_FORMAT.extensions, '.py'))
 def static_analysis(ctx: PresubmitContext):
-    """Runs all available static analysis tools."""
-    build.gn_gen(ctx.root, ctx.output_dir)
-    build.ninja(ctx.output_dir, 'python.lint', 'static_analysis')
+    """Check that files pass static analyzer checks."""
+    build.gn_gen(ctx.root, ctx.output_dir,
+                 '--export-compile-commands=host_clang_debug')
+    build.ninja(ctx.output_dir, 'host_clang_debug')
+
+    compile_commands = ctx.output_dir.joinpath('compile_commands.json')
+    analyzer_output = ctx.output_dir.joinpath('analyze-build-output')
+
+    if analyzer_output.exists():
+        shutil.rmtree(analyzer_output)
+
+    call('analyze-build',
+         '--cdb',
+         compile_commands,
+         '--exclude',
+         'third_party',
+         '--output',
+         analyzer_output,
+         cwd=ctx.root,
+         env=build.env_with_clang_vars())
+
+    # Search for reports under output directory.
+    reports = list(analyzer_output.glob('*/report*'))
+    if len(reports) != 0:
+        archive = shutil.make_archive(str(analyzer_output), 'zip',
+                                      reports[0].parent)
+        _LOG.error('Static analyzer found errors: %s', archive)
+        _LOG.error('To view report, open: %s',
+                   Path(reports[0]).parent.joinpath('index.html'))
+        raise PresubmitFailure
 
 
 def renode_check(ctx: PresubmitContext):
@@ -767,65 +617,48 @@
 #
 
 OTHER_CHECKS = (
-    cpp_checks.all_sanitizers(),
+    # TODO(pwbug/45): Remove clang-tidy from OTHER_CHECKS when it passes.
+    clang_tidy,
     # Build that attempts to duplicate the build OSS-Fuzz does. Currently
     # failing.
     oss_fuzz_build,
-    # TODO(pwbug/346): Enable all Bazel tests when they're fixed.
     bazel_test,
-    cmake_clang,
-    cmake_gcc,
-    gn_boringssl_build,
-    build.gn_gen_check,
+    cmake_tests,
     gn_nanopb_build,
-    gn_crypto_mbedtls_build,
-    gn_crypto_boringssl_build,
-    gn_crypto_micro_ecc_build,
-    gn_software_update_build,
     gn_full_build_check,
     gn_full_qemu_check,
     gn_clang_build,
     gn_gcc_build,
-    gn_pw_system_demo_build,
     renode_check,
-    stm32f429i,
-)
-
-_LINTFORMAT = (
-    commit_message_format,
-    copyright_notice,
-    format_code.presubmit_checks(),
-    inclusive_language.inclusive_language.with_filter(
-        exclude=(r'\byarn.lock$', )),
-    cpp_checks.pragma_once,
-    build.bazel_lint,
-    source_is_in_build_files,
+    static_analysis,
 )
 
 LINTFORMAT = (
-    _LINTFORMAT,
-    static_analysis,
-    pw_presubmit.python_checks.check_python_versions,
-    pw_presubmit.python_checks.gn_python_lint,
+    commit_message_format,
+    copyright_notice,
+    format_code.presubmit_checks(),
+    pw_presubmit.pragma_once,
+    source_is_in_build_files,
 )
 
 QUICK = (
-    _LINTFORMAT,
+    LINTFORMAT,
+    bazel_test,
     gn_quick_build_check,
     # TODO(pwbug/141): Re-enable CMake and Bazel for Mac after we have fixed the
     # the clang issues. The problem is that all clang++ invocations need the
     # two extra flags: "-nostdc++" and "${clang_prefix}/../lib/libc++.a".
-    cmake_clang if sys.platform != 'darwin' else (),
+    cmake_tests if sys.platform != 'darwin' else (),
 )
 
 FULL = (
-    _LINTFORMAT,
+    LINTFORMAT,
     gn_host_build,
     gn_arm_build,
     gn_docs_build,
     gn_host_tools,
-    bazel_test if sys.platform == 'linux' else (),
-    bazel_build if sys.platform == 'linux' else (),
+    bazel_build,
+    bazel_test,
     # On Mac OS, system 'gcc' is a symlink to 'clang' by default, so skip GCC
     # host builds on Mac for now. Skip it on Windows too, since gn_host_build
     # already uses 'gcc' on Windows.
@@ -834,8 +667,7 @@
     gn_qemu_build if sys.platform != 'win32' else (),
     gn_qemu_clang_build if sys.platform != 'win32' else (),
     source_is_in_build_files,
-    python_checks.gn_python_check,
-    python_checks.gn_python_test_coverage,
+    python_checks,
     build_env_setup,
     # Skip gn_teensy_build if running on Windows. The Teensycore installer is
     # an exe that requires an admin role.
@@ -867,11 +699,9 @@
     """Entry point for presubmit."""
 
     if install:
-        # TODO(pwbug/209, pwbug/386) inclusive-language: disable
         install_hook(__file__, 'pre-push',
                      ['--base', 'origin/master..HEAD', '--program', 'quick'],
                      Path.cwd())
-        # TODO(pwbug/209, pwbug/386) inclusive-language: enable
         return 0
 
     return cli.run(**presubmit_args)
diff --git a/pw_presubmit/py/pw_presubmit/presubmit.py b/pw_presubmit/py/pw_presubmit/presubmit.py
index 08585d0..575fd94 100644
--- a/pw_presubmit/py/pw_presubmit/presubmit.py
+++ b/pw_presubmit/py/pw_presubmit/presubmit.py
@@ -37,8 +37,6 @@
 See pigweed_presbumit.py for an example of how to define presubmit checks.
 """
 
-from __future__ import annotations
-
 import collections
 import contextlib
 import dataclasses
@@ -54,7 +52,6 @@
 from typing import (Callable, Collection, Dict, Iterable, Iterator, List,
                     NamedTuple, Optional, Pattern, Sequence, Set, Tuple, Union)
 
-import pw_cli.env
 from pw_presubmit import git_repo, tools
 from pw_presubmit.tools import plural
 
@@ -132,7 +129,7 @@
     """A sequence of presubmit checks; basically a tuple with a name."""
     def __init__(self, name: str, steps: Iterable[Callable]):
         self.name = name
-        self._steps = tuple({s: None for s in tools.flatten(steps)})
+        self._steps = tuple(tools.flatten(steps))
 
     def __getitem__(self, i):
         return self._steps[i]
@@ -228,7 +225,7 @@
     def run(self, program: Program, keep_going: bool = False) -> bool:
         """Executes a series of presubmit checks on the paths."""
 
-        checks = self.apply_filters(program)
+        checks = self._apply_filters(program)
 
         _LOG.debug('Running %s for %s', program.title(), self._root.name)
         _print_ui(_title(f'{self._root.name}: {program.title()}'))
@@ -252,13 +249,13 @@
 
         return not failed and not skipped
 
-    def apply_filters(
-            self,
-            program: Sequence[Callable]) -> List[Tuple[Check, Sequence[Path]]]:
+    def _apply_filters(
+            self, program: Sequence[Callable]
+    ) -> List[Tuple['_Check', Sequence[Path]]]:
         """Returns list of (check, paths) for checks that should run."""
-        checks = [c if isinstance(c, Check) else Check(c) for c in program]
+        checks = [c if isinstance(c, _Check) else _Check(c) for c in program]
         filter_to_checks: Dict[_Filter,
-                               List[Check]] = collections.defaultdict(list)
+                               List[_Check]] = collections.defaultdict(list)
 
         for check in checks:
             filter_to_checks[check.filter].append(check)
@@ -267,9 +264,9 @@
         return [(c, check_to_paths[c]) for c in checks if c in check_to_paths]
 
     def _map_checks_to_paths(
-        self, filter_to_checks: Dict[_Filter, List[Check]]
-    ) -> Dict[Check, Sequence[Path]]:
-        checks_to_paths: Dict[Check, Sequence[Path]] = {}
+        self, filter_to_checks: Dict[_Filter, List['_Check']]
+    ) -> Dict['_Check', Sequence[Path]]:
+        checks_to_paths: Dict[_Check, Sequence[Path]] = {}
 
         posix_paths = tuple(p.as_posix() for p in self._relative_paths)
 
@@ -397,7 +394,6 @@
         exclude: Sequence[Pattern] = (),
         output_directory: Optional[Path] = None,
         package_root: Path = None,
-        only_list_steps: bool = False,
         keep_going: bool = False) -> bool:
     """Lists files in the current Git repo and runs a Presubmit with them.
 
@@ -421,7 +417,6 @@
         exclude: regular expressions for Posix-style paths to exclude
         output_directory: where to place output files
         package_root: where to place package files
-        only_list_steps: print step names instead of running them
         keep_going: whether to continue running checks if an error occurs
 
     Returns:
@@ -444,8 +439,7 @@
 
         _LOG.info(
             'Checking %s',
-            git_repo.describe_files(repo, repo, base, pathspecs, exclude,
-                                    root))
+            git_repo.describe_files(repo, repo, base, pathspecs, exclude))
 
     if output_directory is None:
         output_directory = root / '.presubmit'
@@ -461,22 +455,13 @@
         package_root=package_root,
     )
 
-    if only_list_steps:
-        for check, _ in presubmit.apply_filters(program):
-            print(check.name)
-        return True
-
     if not isinstance(program, Program):
         program = Program('', program)
 
     return presubmit.run(program, keep_going)
 
 
-def _make_str_tuple(value: Union[Iterable[str], str]) -> Tuple[str, ...]:
-    return tuple([value] if isinstance(value, str) else value)
-
-
-class Check:
+class _Check:
     """Wraps a presubmit check function.
 
     This class consolidates the logic for running and logging a presubmit check.
@@ -492,24 +477,9 @@
         self.filter: _Filter = path_filter
         self.always_run: bool = always_run
 
-        # Since Check wraps a presubmit function, adopt that function's name.
+        # Since _Check wraps a presubmit function, adopt that function's name.
         self.__name__ = self._check.__name__
 
-    def with_filter(
-        self,
-        *,
-        endswith: Iterable[str] = '',
-        exclude: Iterable[Union[Pattern[str], str]] = ()
-    ) -> Check:
-        endswith = self.filter.endswith
-        if endswith:
-            endswith = endswith + _make_str_tuple(endswith)
-        exclude = self.filter.exclude + tuple(re.compile(e) for e in exclude)
-
-        return Check(check_function=self._check,
-                     path_filter=_Filter(endswith=endswith, exclude=exclude),
-                     always_run=self.always_run)
-
     @property
     def name(self):
         return self.__name__
@@ -552,11 +522,11 @@
         return _Result.PASS
 
     def __call__(self, ctx: PresubmitContext, *args, **kwargs):
-        """Calling a Check calls its underlying function directly.
+        """Calling a _Check calls its underlying function directly.
 
-        This makes it possible to call functions wrapped by @filter_paths. The
-        prior filters are ignored, so new filters may be applied.
-        """
+      This makes it possible to call functions wrapped by @filter_paths. The
+      prior filters are ignored, so new filters may be applied.
+      """
         return self._check(ctx, *args, **kwargs)
 
 
@@ -586,9 +556,13 @@
              if required_args else ''))
 
 
-def filter_paths(endswith: Iterable[str] = '',
+def _make_str_tuple(value: Iterable[str]) -> Tuple[str, ...]:
+    return tuple([value] if isinstance(value, str) else value)
+
+
+def filter_paths(endswith: Iterable[str] = (''),
                  exclude: Iterable[Union[Pattern[str], str]] = (),
-                 always_run: bool = False) -> Callable[[Callable], Check]:
+                 always_run: bool = False) -> Callable[[Callable], _Check]:
     """Decorator for filtering the paths list for a presubmit check function.
 
     Path filters only apply when the function is used as a presubmit check.
@@ -604,41 +578,45 @@
         a wrapped version of the presubmit function
     """
     def filter_paths_for_function(function: Callable):
-        return Check(function,
-                     _Filter(_make_str_tuple(endswith),
-                             tuple(re.compile(e) for e in exclude)),
-                     always_run=always_run)
+        return _Check(function,
+                      _Filter(_make_str_tuple(endswith),
+                              tuple(re.compile(e) for e in exclude)),
+                      always_run=always_run)
 
     return filter_paths_for_function
 
 
+@filter_paths(endswith='.h', exclude=(r'\.pb\.h$', ))
+def pragma_once(ctx: PresubmitContext) -> None:
+    """Presubmit check that ensures all header files contain '#pragma once'."""
+
+    for path in ctx.paths:
+        with open(path) as file:
+            for line in file:
+                if line.startswith('#pragma once'):
+                    break
+            else:
+                raise PresubmitFailure('#pragma once is missing!', path=path)
+
+
 def call(*args, **kwargs) -> None:
     """Optional subprocess wrapper that causes a PresubmitFailure on errors."""
     attributes, command = tools.format_command(args, kwargs)
     _LOG.debug('[RUN] %s\n%s', attributes, command)
 
-    env = pw_cli.env.pigweed_environment()
-    kwargs['stdout'] = subprocess.PIPE
-    kwargs['stderr'] = subprocess.STDOUT
-
-    process = subprocess.Popen(args, **kwargs)
-    assert process.stdout
-
-    if env.PW_PRESUBMIT_DISABLE_SUBPROCESS_CAPTURE:
-        while True:
-            line = process.stdout.readline().decode(errors='backslashreplace')
-            if not line:
-                break
-            _LOG.info(line.rstrip())
-
-    stdout, _ = process.communicate()
-
+    process = subprocess.run(args,
+                             stdout=subprocess.PIPE,
+                             stderr=subprocess.STDOUT,
+                             **kwargs)
     logfunc = _LOG.warning if process.returncode else _LOG.debug
+
     logfunc('[FINISHED]\n%s', command)
     logfunc('[RESULT] %s with return code %d',
             'Failed' if process.returncode else 'Passed', process.returncode)
-    if stdout:
-        logfunc('[OUTPUT]\n%s', stdout.decode(errors='backslashreplace'))
+
+    output = process.stdout.decode(errors='backslashreplace')
+    if output:
+        logfunc('[OUTPUT]\n%s', output)
 
     if process.returncode:
         raise PresubmitFailure
diff --git a/pw_presubmit/py/pw_presubmit/python_checks.py b/pw_presubmit/py/pw_presubmit/python_checks.py
index c9c1b5d..1f1e0cd 100644
--- a/pw_presubmit/py/pw_presubmit/python_checks.py
+++ b/pw_presubmit/py/pw_presubmit/python_checks.py
@@ -16,13 +16,11 @@
 These checks assume that they are running in a preconfigured Python environment.
 """
 
-import json
 import logging
 import os
 from pathlib import Path
-import subprocess
 import sys
-from typing import Optional
+from typing import Callable, Iterable, List, Set, Tuple
 
 try:
     import pw_presubmit
@@ -33,150 +31,121 @@
         os.path.abspath(__file__))))
     import pw_presubmit
 
-from pw_env_setup import python_packages
-from pw_presubmit import (
-    build,
-    call,
-    Check,
-    filter_paths,
-    PresubmitContext,
-    PresubmitFailure,
-)
+from pw_presubmit import call, filter_paths
+from pw_presubmit.git_repo import python_packages_containing, list_files
+from pw_presubmit.git_repo import PythonPackage
 
 _LOG = logging.getLogger(__name__)
 
-_PYTHON_EXTENSIONS = ('.py', '.gn', '.gni')
 
-_PYTHON_IS_3_9_OR_HIGHER = sys.version_info >= (
-    3,
-    9,
+def run_module(*args, **kwargs):
+    return call('python', '-m', *args, **kwargs)
+
+
+TEST_PATTERNS = ('*_test.py', )
+
+
+@filter_paths(endswith='.py')
+def test_python_packages(ctx: pw_presubmit.PresubmitContext,
+                         patterns: Iterable[str] = TEST_PATTERNS) -> None:
+    """Finds and runs test files in Python package directories.
+
+    Finds the Python packages containing the affected paths, then searches
+    within that package for test files. All files matching the provided patterns
+    are executed with Python.
+    """
+    packages: List[PythonPackage] = []
+    for repo in ctx.repos:
+        packages += python_packages_containing(ctx.paths, repo=repo)[0]
+
+    if not packages:
+        _LOG.info('No Python packages were found.')
+        return
+
+    for package in packages:
+        for test in list_files(pathspecs=tuple(patterns),
+                               repo_path=package.root):
+            call('python', test)
+
+
+@filter_paths(endswith='.py')
+def pylint(ctx: pw_presubmit.PresubmitContext) -> None:
+    disable_checkers = [
+        # BUG(pwbug/22): Hanging indent check conflicts with YAPF 0.29. For
+        # now, use YAPF's version even if Pylint is doing the correct thing
+        # just to keep operations simpler. When YAPF upstream fixes the issue,
+        # delete this code.
+        #
+        # See also: https://github.com/google/yapf/issues/781
+        'bad-continuation',
+    ]
+    run_module(
+        'pylint',
+        '--jobs=0',
+        f'--disable={",".join(disable_checkers)}',
+        *ctx.paths,
+        cwd=ctx.root,
+    )
+
+
+@filter_paths(endswith='.py')
+def mypy(ctx: pw_presubmit.PresubmitContext) -> None:
+    """Runs mypy on all paths and their packages."""
+    packages: List[PythonPackage] = []
+    other_files: List[Path] = []
+
+    for repo, paths in ctx.paths_by_repo().items():
+        new_packages, files = python_packages_containing(paths, repo=repo)
+        packages += new_packages
+        other_files += files
+
+        for package in new_packages:
+            other_files += package.other_files
+
+    # Under some circumstances, mypy cannot check multiple Python files with the
+    # same module name. Group filenames so that no duplicates occur in the same
+    # mypy invocation. Also, omit setup.py from mypy checks.
+    filename_sets: List[Set[str]] = [set()]
+    path_sets: List[List[Path]] = [list(p.package for p in packages)]
+
+    for path in (p for p in other_files if p.name != 'setup.py'):
+        for filenames, paths in zip(filename_sets, path_sets):
+            if path.name not in filenames:
+                paths.append(path)
+                filenames.add(path.name)
+                break
+        else:
+            path_sets.append([path])
+            filename_sets.append({path.name})
+
+    env = os.environ.copy()
+    # Use this environment variable to force mypy to colorize output.
+    # See https://github.com/python/mypy/issues/7771
+    env['MYPY_FORCE_COLOR'] = '1'
+
+    for paths in path_sets:
+        run_module(
+            'mypy',
+            *paths,
+            '--pretty',
+            '--color-output',
+            '--show-error-codes',
+            # TODO(pwbug/146): Some imports from installed packages fail. These
+            # imports should be fixed and this option removed. See
+            # https://mypy.readthedocs.io/en/stable/installed_packages.html
+            '--ignore-missing-imports',
+            env=env)
+
+
+_ALL_CHECKS = (
+    test_python_packages,
+    pylint,
+    mypy,
 )
 
 
-@filter_paths(endswith=_PYTHON_EXTENSIONS)
-def gn_python_check(ctx: PresubmitContext):
-    build.gn_gen(ctx.root, ctx.output_dir)
-    build.ninja(ctx.output_dir, 'python.tests', 'python.lint')
-
-
-def _transform_lcov_file_paths(lcov_file: Path, repo_root: Path) -> str:
-    """Modify file paths in an lcov file to be relative to the repo root.
-
-    See `man geninfo` for info on the lcov format."""
-
-    lcov_input = lcov_file.read_text()
-    lcov_output = ''
-
-    if not _PYTHON_IS_3_9_OR_HIGHER:
-        return lcov_input
-
-    for line in lcov_input.splitlines():
-        if not line.startswith('SF:'):
-            lcov_output += line + '\n'
-            continue
-
-        # Get the file path after SF:
-        file_string = line[3:].rstrip()
-        source_file_path = Path(file_string)
-
-        # Attempt to map a generated Python package source file to the root
-        # source tree.
-        # pylint: disable=no-member
-        if not source_file_path.is_relative_to(  # type: ignore[attr-defined]
-                repo_root):
-            # pylint: enable=no-member
-            source_file_path = repo_root / str(source_file_path).replace(
-                'python/gen/', '').replace('py.generated_python_package/', '')
-
-        # If mapping fails don't modify this line.
-        # pylint: disable=no-member
-        if not source_file_path.is_relative_to(  # type: ignore[attr-defined]
-                repo_root):
-            # pylint: enable=no-member
-            lcov_output += line + '\n'
-            continue
-
-        source_file_path = source_file_path.relative_to(repo_root)
-        lcov_output += f'SF:{source_file_path}\n'
-
-    return lcov_output
-
-
-@filter_paths(endswith=_PYTHON_EXTENSIONS)
-def gn_python_test_coverage(ctx: PresubmitContext):
-    """Run Python tests with coverage and create reports."""
-    build.gn_gen(ctx.root, ctx.output_dir, pw_build_PYTHON_TEST_COVERAGE=True)
-    build.ninja(ctx.output_dir, 'python.tests')
-
-    # Find coverage data files
-    coverage_data_files = list(ctx.output_dir.glob('**/*.coverage'))
-    if not coverage_data_files:
-        return
-
-    # Merge coverage data files to out/.coverage
-    call(
-        'coverage',
-        'combine',
-        # Leave existing coverage files in place; by default they are deleted.
-        '--keep',
-        *coverage_data_files,
-        cwd=ctx.output_dir)
-    combined_data_file = ctx.output_dir / '.coverage'
-    _LOG.info('Coverage data saved to: %s', combined_data_file.resolve())
-
-    # Always ignore generated proto python and setup.py files.
-    coverage_omit_patterns = '--omit=*_pb2.py,*/setup.py'
-
-    # Output coverage percentage summary to the terminal of changed files.
-    changed_python_files = list(
-        str(p) for p in ctx.paths if str(p).endswith('.py'))
-    report_args = [
-        'coverage',
-        'report',
-        '--ignore-errors',
-        coverage_omit_patterns,
-    ]
-    report_args += changed_python_files
-    subprocess.run(report_args, check=False, cwd=ctx.output_dir)
-
-    # Generate a json report
-    call('coverage', 'lcov', coverage_omit_patterns, cwd=ctx.output_dir)
-    lcov_data_file = ctx.output_dir / 'coverage.lcov'
-    lcov_data_file.write_text(
-        _transform_lcov_file_paths(lcov_data_file, repo_root=ctx.root))
-    _LOG.info('Coverage lcov saved to: %s', lcov_data_file.resolve())
-
-    # Generate an html report
-    call('coverage', 'html', coverage_omit_patterns, cwd=ctx.output_dir)
-    html_report = ctx.output_dir / 'htmlcov' / 'index.html'
-    _LOG.info('Coverage html report saved to: %s', html_report.resolve())
-
-
-@filter_paths(endswith=_PYTHON_EXTENSIONS + ('.pylintrc', ))
-def gn_python_lint(ctx: pw_presubmit.PresubmitContext) -> None:
-    build.gn_gen(ctx.root, ctx.output_dir)
-    build.ninja(ctx.output_dir, 'python.lint')
-
-
-@Check
-def check_python_versions(ctx: PresubmitContext):
-    """Checks that the list of installed packages is as expected."""
-
-    build.gn_gen(ctx.root, ctx.output_dir)
-    constraint_file: Optional[str] = None
-    try:
-        for arg in build.get_gn_args(ctx.output_dir):
-            if arg['name'] == 'pw_build_PIP_CONSTRAINTS':
-                constraint_file = json.loads(
-                    arg['current']['value'])[0].strip('/')
-    except json.JSONDecodeError:
-        _LOG.warning('failed to parse GN args json')
-        return
-
-    if not constraint_file:
-        _LOG.warning('could not find pw_build_PIP_CONSTRAINTS GN arg')
-        return
-
-    with (ctx.root / constraint_file).open('r') as ins:
-        if python_packages.diff(ins) != 0:
-            raise PresubmitFailure
+def all_checks(endswith: str = '.py',
+               **filter_paths_args) -> Tuple[Callable, ...]:
+    return tuple(
+        filter_paths(endswith=endswith, **filter_paths_args)(function)
+        for function in _ALL_CHECKS)
diff --git a/pw_presubmit/py/pyproject.toml b/pw_presubmit/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/pw_presubmit/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/pw_presubmit/py/setup.cfg b/pw_presubmit/py/setup.cfg
deleted file mode 100644
index 14652af..0000000
--- a/pw_presubmit/py/setup.cfg
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pw_presubmit
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Presubmit tools and a presubmit script for Pigweed
-
-[options]
-packages = find:
-zip_safe = False
-install_requires =
-    scan-build==2.0.19
-    yapf==0.31.0
-    pw_cli
-    pw_package
-
-[options.package_data]
-pw_presubmit = py.typed
diff --git a/pw_presubmit/py/setup.py b/pw_presubmit/py/setup.py
index 4617737..0c13568 100644
--- a/pw_presubmit/py/setup.py
+++ b/pw_presubmit/py/setup.py
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2019 The Pigweed Authors
 #
 # 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
@@ -11,8 +11,23 @@
 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 # License for the specific language governing permissions and limitations under
 # the License.
-"""pw_presubmit"""
+"""The pw_presubmit package."""
 
 import setuptools  # type: ignore
 
-setuptools.setup()  # Package definition in setup.cfg
+setuptools.setup(
+    name='pw_presubmit',
+    version='0.0.1',
+    author='Pigweed Authors',
+    author_email='pigweed-developers@googlegroups.com',
+    description='Presubmit tools and a presubmit script for Pigweed',
+    install_requires=[
+        'scan-build==2.0.19',
+        'yapf==0.30.0',
+        'pw_cli',
+        'pw_package',
+    ],
+    packages=setuptools.find_packages(),
+    package_data={'pw_presubmit': ['py.typed']},
+    zip_safe=False,
+)
diff --git a/pw_protobuf/BUILD b/pw_protobuf/BUILD
new file mode 100644
index 0000000..40edf6d
--- /dev/null
+++ b/pw_protobuf/BUILD
@@ -0,0 +1,105 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "config",
+    hdrs = ["public/pw_protobuf/config.h"],
+    includes = ["public"],
+)
+
+pw_cc_library(
+    name = "pw_protobuf",
+    srcs = [
+        "decoder.cc",
+        "encoder.cc",
+        "find.cc",
+    ],
+    hdrs = [
+        "public/pw_protobuf/codegen.h",
+        "public/pw_protobuf/decoder.h",
+        "public/pw_protobuf/encoder.h",
+        "public/pw_protobuf/find.h",
+        "public/pw_protobuf/serialized_size.h",
+        "public/pw_protobuf/wire_format.h",
+    ],
+    includes = ["public"],
+    deps = [
+        ":config",
+        "//pw_span",
+        "//pw_status",
+        "//pw_varint",
+    ],
+)
+
+pw_cc_test(
+    name = "decoder_test",
+    srcs = ["decoder_test.cc"],
+    deps = [
+        ":pw_protobuf",
+        "//pw_preprocessor",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "encoder_test",
+    srcs = ["encoder_test.cc"],
+    deps = [
+        ":pw_protobuf",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "find_test",
+    srcs = ["find_test.cc"],
+    deps = [
+        ":pw_protobuf",
+        "//pw_unit_test",
+    ],
+)
+
+# TODO(frolv): Figure out how to integrate pw_protobuf codegen into Bazel.
+filegroup(
+    name = "codegen_test",
+    srcs = [
+        "codegen_test.cc",
+    ],
+)
+
+# TODO(frolv): Figure out how to add facade tests to Bazel.
+filegroup(
+    name = "varint_size_test",
+    srcs = [
+        "varint_size_test.cc",
+    ],
+)
+
+# TODO(frolv): Figure out what to do about size reports in Bazel.
+filegroup(
+    name = "size_reports",
+    srcs = glob([
+        "size_report/*.cc",
+    ]),
+)
diff --git a/pw_protobuf/BUILD.bazel b/pw_protobuf/BUILD.bazel
deleted file mode 100644
index 316576e..0000000
--- a/pw_protobuf/BUILD.bazel
+++ /dev/null
@@ -1,242 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load("@rules_proto//proto:defs.bzl", "proto_library")
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-load("//pw_fuzzer:fuzzer.bzl", "pw_cc_fuzz_test")
-load("@rules_proto_grpc//:defs.bzl", "proto_plugin")
-load("//pw_protobuf_compiler:proto.bzl", "pw_proto_library")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "config",
-    hdrs = ["public/pw_protobuf/config.h"],
-    includes = ["public"],
-)
-
-pw_cc_library(
-    name = "pw_protobuf",
-    srcs = [
-        "decoder.cc",
-        "encoder.cc",
-        "find.cc",
-        "map_utils.cc",
-        "message.cc",
-        "stream_decoder.cc",
-    ],
-    hdrs = [
-        "public/pw_protobuf/decoder.h",
-        "public/pw_protobuf/encoder.h",
-        "public/pw_protobuf/find.h",
-        "public/pw_protobuf/internal/proto_integer_base.h",
-        "public/pw_protobuf/map_utils.h",
-        "public/pw_protobuf/message.h",
-        "public/pw_protobuf/serialized_size.h",
-        "public/pw_protobuf/stream_decoder.h",
-        "public/pw_protobuf/wire_format.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":config",
-        "//pw_assert",
-        "//pw_bytes",
-        "//pw_containers:vector",
-        "//pw_polyfill:bit",
-        "//pw_polyfill:overrides",
-        "//pw_result",
-        "//pw_span",
-        "//pw_status",
-        "//pw_stream",
-        "//pw_stream:interval_reader",
-        "//pw_varint",
-        "//pw_varint:stream",
-    ],
-)
-
-pw_cc_library(
-    name = "bytes_utils",
-    hdrs = ["public/pw_protobuf/bytes_utils.h"],
-    includes = ["public"],
-    deps = [
-        ":pw_protobuf",
-        "//pw_bytes",
-        "//pw_result",
-        "//pw_status",
-    ],
-)
-
-pw_cc_test(
-    name = "decoder_test",
-    srcs = ["decoder_test.cc"],
-    deps = [
-        ":pw_protobuf",
-        "//pw_preprocessor",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "encoder_test",
-    srcs = ["encoder_test.cc"],
-    deps = [
-        ":pw_protobuf",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_fuzz_test(
-    name = "encoder_fuzz_test",
-    srcs = ["encoder_fuzzer.cc"],
-    deps = [
-        "//pw_fuzzer",
-        "//pw_protobuf",
-        "//pw_span",
-    ],
-)
-
-pw_cc_test(
-    name = "find_test",
-    srcs = ["find_test.cc"],
-    deps = [
-        ":pw_protobuf",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "serialized_size_test",
-    srcs = ["serialized_size_test.cc"],
-    deps = [
-        ":pw_protobuf",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "stream_decoder_test",
-    srcs = ["stream_decoder_test.cc"],
-    deps = [
-        ":pw_protobuf",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "map_utils_test",
-    srcs = ["map_utils_test.cc"],
-    deps = [
-        ":pw_protobuf",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "message_test",
-    srcs = ["message_test.cc"],
-    deps = [
-        ":pw_protobuf",
-        "//pw_unit_test",
-    ],
-)
-
-proto_library(
-    name = "common_protos",
-    srcs = [
-        "pw_protobuf_protos/common.proto",
-        "pw_protobuf_protos/status.proto",
-    ],
-    strip_import_prefix = "//pw_protobuf",
-)
-
-proto_library(
-    name = "codegen_test_proto",
-    srcs = [
-        "pw_protobuf_test_protos/full_test.proto",
-        "pw_protobuf_test_protos/imported.proto",
-        "pw_protobuf_test_protos/importer.proto",
-        "pw_protobuf_test_protos/non_pw_package.proto",
-        "pw_protobuf_test_protos/proto2.proto",
-        "pw_protobuf_test_protos/repeated.proto",
-    ],
-    strip_import_prefix = "//pw_protobuf",
-    deps = [":common_protos"],
-)
-
-pw_proto_library(
-    name = "codegen_test_proto_cc",
-    deps = [
-        ":codegen_test_proto",
-        ":common_protos",
-    ],
-)
-
-pw_cc_test(
-    name = "codegen_decoder_test",
-    srcs = [
-        "codegen_decoder_test.cc",
-    ],
-    deps = [
-        ":codegen_test_proto_cc.pwpb",
-        ":pw_protobuf",
-        "//pw_span",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "codegen_encoder_test",
-    srcs = [
-        "codegen_encoder_test.cc",
-    ],
-    deps = [
-        ":codegen_test_proto_cc.pwpb",
-        ":pw_protobuf",
-        "//pw_span",
-        "//pw_unit_test",
-    ],
-)
-
-# TODO(frolv): Figure out how to add facade tests to Bazel.
-filegroup(
-    name = "varint_size_test",
-    srcs = [
-        "varint_size_test.cc",
-    ],
-)
-
-# TODO(frolv): Figure out what to do about size reports in Bazel.
-filegroup(
-    name = "size_reports",
-    srcs = glob([
-        "size_report/*.cc",
-    ]),
-)
-
-proto_plugin(
-    name = "pw_cc_plugin",
-    outputs = [
-        "{protopath}.pwpb.h",
-    ],
-    protoc_plugin_name = "pwpb",
-    tool = "@pigweed//pw_protobuf/py:plugin",
-    use_built_in_shell_environment = True,
-    visibility = ["//visibility:public"],
-)
diff --git a/pw_protobuf/BUILD.gn b/pw_protobuf/BUILD.gn
index 626e5cc..ceb9947 100644
--- a/pw_protobuf/BUILD.gn
+++ b/pw_protobuf/BUILD.gn
@@ -45,51 +45,31 @@
   public_configs = [ ":public_include_path" ]
   public_deps = [
     ":config",
-    "$dir_pw_containers:vector",
-    "$dir_pw_stream:interval_reader",
-    "$dir_pw_varint:stream",
-    dir_pw_assert,
     dir_pw_bytes,
-    dir_pw_log,
     dir_pw_result,
     dir_pw_status,
-    dir_pw_stream,
     dir_pw_varint,
   ]
   public = [
+    "public/pw_protobuf/codegen.h",
     "public/pw_protobuf/decoder.h",
     "public/pw_protobuf/encoder.h",
     "public/pw_protobuf/find.h",
-    "public/pw_protobuf/internal/proto_integer_base.h",
-    "public/pw_protobuf/map_utils.h",
-    "public/pw_protobuf/message.h",
     "public/pw_protobuf/serialized_size.h",
-    "public/pw_protobuf/stream_decoder.h",
     "public/pw_protobuf/wire_format.h",
   ]
   sources = [
     "decoder.cc",
     "encoder.cc",
     "find.cc",
-    "map_utils.cc",
-    "message.cc",
-    "stream_decoder.cc",
-  ]
-}
-
-pw_source_set("bytes_utils") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_protobuf/bytes_utils.h" ]
-  public_deps = [
-    ":pw_protobuf",
-    dir_pw_bytes,
-    dir_pw_result,
-    dir_pw_status,
   ]
 }
 
 pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
+  sources = [
+    "decoding.rst",
+    "docs.rst",
+  ]
   report_deps = [
     "size_report:decoder_full",
     "size_report:decoder_incremental",
@@ -98,16 +78,11 @@
 
 pw_test_group("tests") {
   tests = [
-    ":codegen_decoder_test",
-    ":codegen_encoder_test",
+    ":codegen_test",
     ":decoder_test",
     ":encoder_test",
     ":encoder_fuzzer",
     ":find_test",
-    ":map_utils_test",
-    ":message_test",
-    ":serialized_size_test",
-    ":stream_decoder_test",
     ":varint_size_test",
   ]
 }
@@ -127,34 +102,9 @@
   sources = [ "find_test.cc" ]
 }
 
-pw_test("codegen_decoder_test") {
+pw_test("codegen_test") {
   deps = [ ":codegen_test_protos.pwpb" ]
-  sources = [ "codegen_decoder_test.cc" ]
-}
-
-pw_test("codegen_encoder_test") {
-  deps = [ ":codegen_test_protos.pwpb" ]
-  sources = [ "codegen_encoder_test.cc" ]
-}
-
-pw_test("serialized_size_test") {
-  deps = [ ":pw_protobuf" ]
-  sources = [ "serialized_size_test.cc" ]
-}
-
-pw_test("stream_decoder_test") {
-  deps = [ ":pw_protobuf" ]
-  sources = [ "stream_decoder_test.cc" ]
-}
-
-pw_test("map_utils_test") {
-  deps = [ ":pw_protobuf" ]
-  sources = [ "map_utils_test.cc" ]
-}
-
-pw_test("message_test") {
-  deps = [ ":pw_protobuf" ]
-  sources = [ "message_test.cc" ]
+  sources = [ "codegen_test.cc" ]
 }
 
 config("one_byte_varint") {
@@ -176,10 +126,7 @@
 }
 
 pw_proto_library("common_protos") {
-  sources = [
-    "pw_protobuf_protos/common.proto",
-    "pw_protobuf_protos/status.proto",
-  ]
+  sources = [ "pw_protobuf_protos/common.proto" ]
 }
 
 pw_proto_library("codegen_test_protos") {
diff --git a/pw_protobuf/CMakeLists.txt b/pw_protobuf/CMakeLists.txt
index 73987db..6d821b5 100644
--- a/pw_protobuf/CMakeLists.txt
+++ b/pw_protobuf/CMakeLists.txt
@@ -15,62 +15,16 @@
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
 include($ENV{PW_ROOT}/pw_protobuf_compiler/proto.cmake)
 
-pw_add_module_config(pw_protobuf_CONFIG)
-
-pw_add_module_library(pw_protobuf.config
-  HEADERS
-    public/pw_protobuf/config.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    ${pw_protobuf_CONFIG}
-)
-
 pw_add_module_library(pw_protobuf
-  HEADERS
-    public/pw_protobuf/decoder.h
-    public/pw_protobuf/encoder.h
-    public/pw_protobuf/find.h
-    public/pw_protobuf/internal/proto_integer_base.h
-    public/pw_protobuf/map_utils.h
-    public/pw_protobuf/message.h
-    public/pw_protobuf/serialized_size.h
-    public/pw_protobuf/stream_decoder.h
-    public/pw_protobuf/wire_format.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_assert
-    pw_bytes
-    pw_containers.vector
-    pw_protobuf.config
-    pw_polyfill.span
-    pw_polyfill.cstddef
-    pw_result
-    pw_status
-    pw_stream
-    pw_stream.interval_reader
-    pw_varint
-    pw_varint.stream
   SOURCES
     decoder.cc
     encoder.cc
     find.cc
-    map_utils.cc
-    message.cc
-    stream_decoder.cc
-)
-
-pw_add_module_library(pw_protobuf.bytes_utils
-  HEADERS
-    public/pw_protobuf/bytes_utils.h
-  PUBLIC_INCLUDES
-    public
   PUBLIC_DEPS
-    pw_protobuf
     pw_bytes
     pw_result
     pw_status
+    pw_varint
 )
 
 pw_add_test(pw_protobuf.decoder_test
@@ -103,9 +57,9 @@
     pw_protobuf
 )
 
-pw_add_test(pw_protobuf.codegen_decoder_test
+pw_add_test(pw_protobuf.codegen_test
   SOURCES
-    codegen_decoder_test.cc
+    codegen_test.cc
   DEPS
     pw_protobuf
     pw_protobuf.codegen_test_protos.pwpb
@@ -114,57 +68,6 @@
     pw_protobuf
 )
 
-pw_add_test(pw_protobuf.codegen_encoder_test
-  SOURCES
-    codegen_encoder_test.cc
-  DEPS
-    pw_protobuf
-    pw_protobuf.codegen_test_protos.pwpb
-  GROUPS
-    modules
-    pw_protobuf
-)
-
-pw_add_test(pw_protobuf.serialized_size_test
-  SOURCES
-    serialized_size_test.cc
-  DEPS
-    pw_protobuf
-  GROUPS
-    modules
-    pw_protobuf
-)
-
-pw_add_test(pw_protobuf.stream_decoder_test
-  SOURCES
-    stream_decoder_test.cc
-  DEPS
-    pw_protobuf
-  GROUPS
-    modules
-    pw_protobuf
-)
-
-pw_add_test(pw_protobuf.map_utils_test
-  SOURCES
-    map_utils_test.cc
-  DEPS
-    pw_protobuf
-  GROUPS
-    modules
-    pw_protobuf
-)
-
-pw_add_test(pw_protobuf.message_test
-  SOURCES
-    message_test.cc
-  DEPS
-    pw_protobuf
-  GROUPS
-    modules
-    pw_protobuf
-)
-
 pw_proto_library(pw_protobuf.common_protos
   SOURCES
     pw_protobuf_protos/common.proto
diff --git a/pw_protobuf/OWNERS b/pw_protobuf/OWNERS
deleted file mode 100644
index 3afb926..0000000
--- a/pw_protobuf/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-frolv@google.com
diff --git a/pw_protobuf/codegen_decoder_test.cc b/pw_protobuf/codegen_decoder_test.cc
deleted file mode 100644
index 657e6e9..0000000
--- a/pw_protobuf/codegen_decoder_test.cc
+++ /dev/null
@@ -1,936 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <array>
-#include <span>
-#include <stdexcept>
-#include <string_view>
-
-#include "gtest/gtest.h"
-#include "pw_bytes/span.h"
-#include "pw_containers/vector.h"
-#include "pw_status/status.h"
-#include "pw_status/status_with_size.h"
-#include "pw_stream/memory_stream.h"
-
-// These header files contain the code generated by the pw_protobuf plugin.
-// They are re-generated every time the tests are built and are used by the
-// tests to ensure that the interface remains consistent.
-//
-// The purpose of the tests in this file is primarily to verify that the
-// generated C++ interface is valid rather than the correctness of the
-// low-level encoder.
-#include "pw_protobuf_test_protos/full_test.pwpb.h"
-#include "pw_protobuf_test_protos/importer.pwpb.h"
-#include "pw_protobuf_test_protos/non_pw_package.pwpb.h"
-#include "pw_protobuf_test_protos/proto2.pwpb.h"
-#include "pw_protobuf_test_protos/repeated.pwpb.h"
-
-namespace pw::protobuf {
-namespace {
-
-using namespace pw::protobuf::test;
-
-TEST(Codegen, StreamDecoder) {
-  // clang-format off
-  constexpr uint8_t proto_data[] = {
-    // pigweed.magic_number
-    0x08, 0x49,
-    // pigweed.ziggy
-    0x10, 0xdd, 0x01,
-    // pigweed.error_message
-    0x2a, 0x10, 'n', 'o', 't', ' ', 'a', ' ',
-    't', 'y', 'p', 'e', 'w', 'r', 'i', 't', 'e', 'r',
-    // pigweed.bin
-    0x40, 0x01,
-    // pigweed.pigweed
-    0x3a, 0x02,
-    // pigweed.pigweed.status
-    0x08, 0x02,
-    // pigweed.proto
-    0x4a, 0x56,
-    // pigweed.proto.bin
-    0x10, 0x00,
-    // pigweed.proto.pigweed_pigweed_bin
-    0x18, 0x00,
-    // pigweed.proto.pigweed_protobuf_bin
-    0x20, 0x01,
-    // pigweed.proto.meta
-    0x2a, 0x0f,
-    // pigweed.proto.meta.file_name
-    0x0a, 0x0b, '/', 'e', 't', 'c', '/', 'p', 'a', 's', 's', 'w', 'd',
-    // pigweed.proto.meta.status
-    0x10, 0x02,
-    // pigweed.proto.nested_pigweed
-    0x0a, 0x3d,
-    // pigweed.proto.nested_pigweed.error_message
-    0x2a, 0x10, 'h', 'e', 'r', 'e', ' ', 'w', 'e', ' ',
-    'g', 'o', ' ', 'a', 'g', 'a', 'i', 'n',
-    // pigweed.proto.nested_pigweed.magic_number
-    0x08, 0xe8, 0x04,
-    // pigweed.proto.nested_pigweed.device_info
-    0x32, 0x26,
-    // pigweed.proto.nested_pigweed.device_info.attributes[0]
-    0x22, 0x10,
-    // pigweed.proto.nested_pigweed.device_info.attributes[0].key
-    0x0a, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n',
-    // pigweed.proto.nested_pigweed.device_info.attributes[0].value
-    0x12, 0x05, '5', '.', '3', '.', '1',
-    // pigweed.proto.nested_pigweed.device_info.attributes[1]
-    0x22, 0x10,
-    // pigweed.proto.nested_pigweed.device_info.attributes[1].key
-    0x0a, 0x04, 'c', 'h', 'i', 'p',
-    // pigweed.proto.nested_pigweed.device_info.attributes[1].value
-    0x12, 0x08, 'l', 'e', 'f', 't', '-', 's', 'o', 'c',
-    // pigweed.proto.nested_pigweed.device_info.status
-    0x18, 0x03,
-    // pigweed.id[0]
-    0x52, 0x02,
-    // pigweed.id[0].id
-    0x08, 0x31,
-    // pigweed.id[1]
-    0x52, 0x02,
-    // pigweed.id[1].id
-    0x08, 0x39,
-    // pigweed.id[2]
-    0x52, 0x02,
-    // pigweed.id[2].id
-    0x08, 0x4b,
-    // pigweed.id[3]
-    0x52, 0x02,
-    // pigweed.id[3].id
-    0x08, 0x67,
-    // pigweed.id[4]
-    0x52, 0x03,
-    // pigweed.id[4].id
-    0x08, 0x8d, 0x01
-
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(proto_data)));
-  Pigweed::StreamDecoder pigweed(reader);
-
-  EXPECT_EQ(pigweed.Next(), OkStatus());
-  EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::MAGIC_NUMBER);
-  Result<uint32_t> magic_number = pigweed.ReadMagicNumber();
-  EXPECT_EQ(magic_number.status(), OkStatus());
-  EXPECT_EQ(magic_number.value(), 0x49u);
-
-  EXPECT_EQ(pigweed.Next(), OkStatus());
-  EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::ZIGGY);
-  Result<int32_t> ziggy = pigweed.ReadZiggy();
-  EXPECT_EQ(ziggy.status(), OkStatus());
-  EXPECT_EQ(ziggy.value(), -111);
-
-  constexpr std::string_view kExpectedErrorMessage{"not a typewriter"};
-
-  EXPECT_EQ(pigweed.Next(), OkStatus());
-  EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::ERROR_MESSAGE);
-  std::array<char, 32> error_message{};
-  StatusWithSize error_message_status = pigweed.ReadErrorMessage(error_message);
-  EXPECT_EQ(error_message_status.status(), OkStatus());
-  EXPECT_EQ(error_message_status.size(), kExpectedErrorMessage.size());
-  EXPECT_EQ(std::memcmp(error_message.data(),
-                        kExpectedErrorMessage.data(),
-                        kExpectedErrorMessage.size()),
-            0);
-
-  EXPECT_EQ(pigweed.Next(), OkStatus());
-  EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::BIN);
-  Result<Pigweed::Protobuf::Binary> bin = pigweed.ReadBin();
-  EXPECT_EQ(bin.status(), OkStatus());
-  EXPECT_EQ(bin.value(), Pigweed::Protobuf::Binary::ZERO);
-
-  EXPECT_EQ(pigweed.Next(), OkStatus());
-  EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::PIGWEED);
-  {
-    Pigweed::Pigweed::StreamDecoder pigweed_pigweed =
-        pigweed.GetPigweedDecoder();
-
-    EXPECT_EQ(pigweed_pigweed.Next(), OkStatus());
-    EXPECT_EQ(pigweed_pigweed.Field().value(),
-              Pigweed::Pigweed::Fields::STATUS);
-    Result<Bool> pigweed_status = pigweed_pigweed.ReadStatus();
-    EXPECT_EQ(pigweed_status.status(), OkStatus());
-    EXPECT_EQ(pigweed_status.value(), Bool::FILE_NOT_FOUND);
-
-    EXPECT_EQ(pigweed_pigweed.Next(), Status::OutOfRange());
-  }
-
-  EXPECT_EQ(pigweed.Next(), OkStatus());
-  EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::PROTO);
-  {
-    Proto::StreamDecoder proto = pigweed.GetProtoDecoder();
-
-    EXPECT_EQ(proto.Next(), OkStatus());
-    EXPECT_EQ(proto.Field().value(), Proto::Fields::BIN);
-    Result<Proto::Binary> proto_bin = proto.ReadBin();
-    EXPECT_EQ(proto_bin.status(), OkStatus());
-    EXPECT_EQ(proto_bin.value(), Proto::Binary::OFF);
-
-    EXPECT_EQ(proto.Next(), OkStatus());
-    EXPECT_EQ(proto.Field().value(), Proto::Fields::PIGWEED_PIGWEED_BIN);
-    Result<Pigweed::Pigweed::Binary> proto_pigweed_bin =
-        proto.ReadPigweedPigweedBin();
-    EXPECT_EQ(proto_pigweed_bin.status(), OkStatus());
-    EXPECT_EQ(proto_pigweed_bin.value(), Pigweed::Pigweed::Binary::ZERO);
-
-    EXPECT_EQ(proto.Next(), OkStatus());
-    EXPECT_EQ(proto.Field().value(), Proto::Fields::PIGWEED_PROTOBUF_BIN);
-    Result<Pigweed::Protobuf::Binary> proto_protobuf_bin =
-        proto.ReadPigweedProtobufBin();
-    EXPECT_EQ(proto_protobuf_bin.status(), OkStatus());
-    EXPECT_EQ(proto_protobuf_bin.value(), Pigweed::Protobuf::Binary::ZERO);
-
-    EXPECT_EQ(proto.Next(), OkStatus());
-    EXPECT_EQ(proto.Field().value(), Proto::Fields::META);
-    {
-      Pigweed::Protobuf::Compiler::StreamDecoder meta = proto.GetMetaDecoder();
-
-      constexpr std::string_view kExpectedFileName{"/etc/passwd"};
-
-      EXPECT_EQ(meta.Next(), OkStatus());
-      EXPECT_EQ(meta.Field().value(),
-                Pigweed::Protobuf::Compiler::Fields::FILE_NAME);
-      std::array<char, 32> meta_file_name{};
-      StatusWithSize meta_file_name_status = meta.ReadFileName(meta_file_name);
-      EXPECT_EQ(meta_file_name_status.status(), OkStatus());
-      EXPECT_EQ(meta_file_name_status.size(), kExpectedFileName.size());
-      EXPECT_EQ(std::memcmp(meta_file_name.data(),
-                            kExpectedFileName.data(),
-                            kExpectedFileName.size()),
-                0);
-
-      EXPECT_EQ(meta.Next(), OkStatus());
-      EXPECT_EQ(meta.Field().value(),
-                Pigweed::Protobuf::Compiler::Fields::STATUS);
-      Result<Pigweed::Protobuf::Compiler::Status> meta_status =
-          meta.ReadStatus();
-      EXPECT_EQ(meta_status.status(), OkStatus());
-      EXPECT_EQ(meta_status.value(),
-                Pigweed::Protobuf::Compiler::Status::FUBAR);
-
-      EXPECT_EQ(meta.Next(), Status::OutOfRange());
-    }
-
-    EXPECT_EQ(proto.Next(), OkStatus());
-    EXPECT_EQ(proto.Field().value(), Proto::Fields::PIGWEED);
-    {
-      Pigweed::StreamDecoder proto_pigweed = proto.GetPigweedDecoder();
-
-      constexpr std::string_view kExpectedProtoErrorMessage{"here we go again"};
-
-      EXPECT_EQ(proto_pigweed.Next(), OkStatus());
-      EXPECT_EQ(proto_pigweed.Field().value(), Pigweed::Fields::ERROR_MESSAGE);
-      std::array<char, 32> proto_pigweed_error_message{};
-      StatusWithSize proto_pigweed_error_message_status =
-          proto_pigweed.ReadErrorMessage(proto_pigweed_error_message);
-      EXPECT_EQ(proto_pigweed_error_message_status.status(), OkStatus());
-      EXPECT_EQ(proto_pigweed_error_message_status.size(),
-                kExpectedProtoErrorMessage.size());
-      EXPECT_EQ(std::memcmp(proto_pigweed_error_message.data(),
-                            kExpectedProtoErrorMessage.data(),
-                            kExpectedProtoErrorMessage.size()),
-                0);
-
-      EXPECT_EQ(proto_pigweed.Next(), OkStatus());
-      EXPECT_EQ(proto_pigweed.Field().value(), Pigweed::Fields::MAGIC_NUMBER);
-      Result<uint32_t> proto_pigweed_magic_number =
-          proto_pigweed.ReadMagicNumber();
-      EXPECT_EQ(proto_pigweed_magic_number.status(), OkStatus());
-      EXPECT_EQ(proto_pigweed_magic_number.value(), 616u);
-
-      EXPECT_EQ(proto_pigweed.Next(), OkStatus());
-      EXPECT_EQ(proto_pigweed.Field().value(), Pigweed::Fields::DEVICE_INFO);
-      {
-        DeviceInfo::StreamDecoder device_info =
-            proto_pigweed.GetDeviceInfoDecoder();
-
-        EXPECT_EQ(device_info.Next(), OkStatus());
-        EXPECT_EQ(device_info.Field().value(), DeviceInfo::Fields::ATTRIBUTES);
-        {
-          KeyValuePair::StreamDecoder key_value_pair =
-              device_info.GetAttributesDecoder();
-
-          constexpr std::string_view kExpectedKey{"version"};
-          constexpr std::string_view kExpectedValue{"5.3.1"};
-
-          EXPECT_EQ(key_value_pair.Next(), OkStatus());
-          EXPECT_EQ(key_value_pair.Field().value(), KeyValuePair::Fields::KEY);
-          std::array<char, 32> key{};
-          StatusWithSize key_status = key_value_pair.ReadKey(key);
-          EXPECT_EQ(key_status.status(), OkStatus());
-          EXPECT_EQ(key_status.size(), kExpectedKey.size());
-          EXPECT_EQ(
-              std::memcmp(key.data(), kExpectedKey.data(), kExpectedKey.size()),
-              0);
-
-          EXPECT_EQ(key_value_pair.Next(), OkStatus());
-          EXPECT_EQ(key_value_pair.Field().value(),
-                    KeyValuePair::Fields::VALUE);
-          std::array<char, 32> value{};
-          StatusWithSize value_status = key_value_pair.ReadValue(value);
-          EXPECT_EQ(value_status.status(), OkStatus());
-          EXPECT_EQ(value_status.size(), kExpectedValue.size());
-          EXPECT_EQ(
-              std::memcmp(
-                  value.data(), kExpectedValue.data(), kExpectedValue.size()),
-              0);
-
-          EXPECT_EQ(key_value_pair.Next(), Status::OutOfRange());
-        }
-
-        EXPECT_EQ(device_info.Next(), OkStatus());
-        EXPECT_EQ(device_info.Field().value(), DeviceInfo::Fields::ATTRIBUTES);
-        {
-          KeyValuePair::StreamDecoder key_value_pair =
-              device_info.GetAttributesDecoder();
-
-          constexpr std::string_view kExpectedKey{"chip"};
-          constexpr std::string_view kExpectedValue{"left-soc"};
-
-          EXPECT_EQ(key_value_pair.Next(), OkStatus());
-          EXPECT_EQ(key_value_pair.Field().value(), KeyValuePair::Fields::KEY);
-          std::array<char, 32> key{};
-          StatusWithSize key_status = key_value_pair.ReadKey(key);
-          EXPECT_EQ(key_status.status(), OkStatus());
-          EXPECT_EQ(key_status.size(), kExpectedKey.size());
-          EXPECT_EQ(
-              std::memcmp(key.data(), kExpectedKey.data(), kExpectedKey.size()),
-              0);
-
-          EXPECT_EQ(key_value_pair.Next(), OkStatus());
-          EXPECT_EQ(key_value_pair.Field().value(),
-                    KeyValuePair::Fields::VALUE);
-          std::array<char, 32> value{};
-          StatusWithSize value_status = key_value_pair.ReadValue(value);
-          EXPECT_EQ(value_status.status(), OkStatus());
-          EXPECT_EQ(value_status.size(), kExpectedValue.size());
-          EXPECT_EQ(
-              std::memcmp(
-                  value.data(), kExpectedValue.data(), kExpectedValue.size()),
-              0);
-
-          EXPECT_EQ(key_value_pair.Next(), Status::OutOfRange());
-        }
-
-        EXPECT_EQ(device_info.Next(), OkStatus());
-        EXPECT_EQ(device_info.Field().value(), DeviceInfo::Fields::STATUS);
-        Result<DeviceInfo::DeviceStatus> device_info_status =
-            device_info.ReadStatus();
-        EXPECT_EQ(device_info_status.status(), OkStatus());
-        EXPECT_EQ(device_info_status.value(), DeviceInfo::DeviceStatus::PANIC);
-
-        EXPECT_EQ(device_info.Next(), Status::OutOfRange());
-      }
-
-      EXPECT_EQ(proto_pigweed.Next(), Status::OutOfRange());
-    }
-
-    EXPECT_EQ(proto.Next(), Status::OutOfRange());
-  }
-
-  for (int i = 0; i < 5; ++i) {
-    EXPECT_EQ(pigweed.Next(), OkStatus());
-    EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::ID);
-
-    Proto::ID::StreamDecoder id = pigweed.GetIdDecoder();
-
-    EXPECT_EQ(id.Next(), OkStatus());
-    EXPECT_EQ(id.Field().value(), Proto::ID::Fields::ID);
-    Result<uint32_t> id_id = id.ReadId();
-    EXPECT_EQ(id_id.status(), OkStatus());
-    EXPECT_EQ(id_id.value(), 5u * i * i + 3 * i + 49);
-
-    EXPECT_EQ(id.Next(), Status::OutOfRange());
-  }
-
-  EXPECT_EQ(pigweed.Next(), Status::OutOfRange());
-}
-
-TEST(Codegen, ResourceExhausted) {
-  // clang-format off
-  constexpr uint8_t proto_data[] = {
-    // pigweed.error_message
-    0x2a, 0x10, 'n', 'o', 't', ' ', 'a', ' ',
-    't', 'y', 'p', 'e', 'w', 'r', 'i', 't', 'e', 'r',
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(proto_data)));
-  Pigweed::StreamDecoder pigweed(reader);
-
-  EXPECT_EQ(pigweed.Next(), OkStatus());
-  EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::ERROR_MESSAGE);
-  std::array<char, 8> error_message{};
-  StatusWithSize error_message_status = pigweed.ReadErrorMessage(error_message);
-  EXPECT_EQ(error_message_status.status(), Status::ResourceExhausted());
-  EXPECT_EQ(error_message_status.size(), 0u);
-
-  EXPECT_EQ(pigweed.Next(), Status::OutOfRange());
-}
-
-TEST(Codegen, BytesReader) {
-  // clang-format off
-  constexpr uint8_t proto_data[] = {
-    // pigweed.error_message
-    0x2a, 0x10, 'n', 'o', 't', ' ', 'a', ' ',
-    't', 'y', 'p', 'e', 'w', 'r', 'i', 't', 'e', 'r',
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(proto_data)));
-  Pigweed::StreamDecoder pigweed(reader);
-
-  constexpr std::string_view kExpectedErrorMessage{"not a typewriter"};
-
-  EXPECT_EQ(pigweed.Next(), OkStatus());
-  EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::ERROR_MESSAGE);
-  {
-    StreamDecoder::BytesReader bytes_reader = pigweed.GetErrorMessageReader();
-    EXPECT_EQ(bytes_reader.field_size(), kExpectedErrorMessage.size());
-
-    std::array<std::byte, 32> error_message{};
-    Result<ByteSpan> result = bytes_reader.Read(error_message);
-    EXPECT_EQ(result.status(), OkStatus());
-    EXPECT_EQ(result.value().size(), kExpectedErrorMessage.size());
-    EXPECT_EQ(std::memcmp(result.value().data(),
-                          kExpectedErrorMessage.data(),
-                          kExpectedErrorMessage.size()),
-              0);
-
-    result = bytes_reader.Read(error_message);
-    EXPECT_EQ(result.status(), Status::OutOfRange());
-  }
-
-  EXPECT_EQ(pigweed.Next(), Status::OutOfRange());
-}
-
-TEST(Codegen, Enum) {
-  // clang-format off
-  constexpr uint8_t proto_data[] = {
-    // pigweed.bin (value value)
-    0x40, 0x01,
-    // pigweed.bin (invalid value)
-    0x40, 0xff,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(proto_data)));
-  Pigweed::StreamDecoder pigweed(reader);
-
-  EXPECT_EQ(pigweed.Next(), OkStatus());
-  EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::BIN);
-  Result<Pigweed::Protobuf::Binary> bin = pigweed.ReadBin();
-  EXPECT_EQ(bin.status(), OkStatus());
-  EXPECT_EQ(bin.value(), Pigweed::Protobuf::Binary::ZERO);
-
-  EXPECT_EQ(pigweed.Next(), OkStatus());
-  EXPECT_EQ(pigweed.Field().value(), Pigweed::Fields::BIN);
-  bin = pigweed.ReadBin();
-  EXPECT_EQ(bin.status(), Status::DataLoss());
-}
-
-TEST(Codegen, ImportedEnum) {
-  // clang-format off
-  constexpr uint8_t proto_data[] = {
-    // result.status (value value)
-    0x08, 0x01,
-    // result.status (invalid value)
-    0x08, 0xff,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(proto_data)));
-  TestResult::StreamDecoder test_result(reader);
-
-  EXPECT_EQ(test_result.Next(), OkStatus());
-  EXPECT_EQ(test_result.Field().value(), TestResult::Fields::STATUS);
-  Result<imported::Status> status = test_result.ReadStatus();
-  EXPECT_EQ(status.status(), OkStatus());
-  EXPECT_EQ(status.value(), imported::Status::NOT_OK);
-
-  EXPECT_EQ(test_result.Next(), OkStatus());
-  EXPECT_EQ(test_result.Field().value(), TestResult::Fields::STATUS);
-  status = test_result.ReadStatus();
-  EXPECT_EQ(status.status(), Status::DataLoss());
-}
-
-TEST(CodegenRepeated, NonPackedScalar) {
-  // clang-format off
-  constexpr uint8_t proto_data[] = {
-    // uint32s[], v={0, 16, 32, 48}
-    0x08, 0x00,
-    0x08, 0x10,
-    0x08, 0x20,
-    0x08, 0x30,
-    // fixed32s[]. v={0, 16, 32, 48}
-    0x35, 0x00, 0x00, 0x00, 0x00,
-    0x35, 0x10, 0x00, 0x00, 0x00,
-    0x35, 0x20, 0x00, 0x00, 0x00,
-    0x35, 0x30, 0x00, 0x00, 0x00,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(proto_data)));
-  RepeatedTest::StreamDecoder repeated_test(reader);
-
-  for (int i = 0; i < 4; ++i) {
-    EXPECT_EQ(repeated_test.Next(), OkStatus());
-    EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::UINT32S);
-
-    Result<uint32_t> result = repeated_test.ReadUint32s();
-    EXPECT_EQ(result.status(), OkStatus());
-    EXPECT_EQ(result.value(), i * 16u);
-  }
-
-  for (int i = 0; i < 4; ++i) {
-    EXPECT_EQ(repeated_test.Next(), OkStatus());
-    EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::FIXED32S);
-
-    Result<uint32_t> result = repeated_test.ReadFixed32s();
-    EXPECT_EQ(result.status(), OkStatus());
-    EXPECT_EQ(result.value(), i * 16u);
-  }
-
-  EXPECT_EQ(repeated_test.Next(), Status::OutOfRange());
-}
-
-TEST(CodegenRepeated, NonPackedScalarVector) {
-  // clang-format off
-  constexpr uint8_t proto_data[] = {
-    // uint32s[], v={0, 16, 32, 48}
-    0x08, 0x00,
-    0x08, 0x10,
-    0x08, 0x20,
-    0x08, 0x30,
-    // fixed32s[]. v={0, 16, 32, 48}
-    0x35, 0x00, 0x00, 0x00, 0x00,
-    0x35, 0x10, 0x00, 0x00, 0x00,
-    0x35, 0x20, 0x00, 0x00, 0x00,
-    0x35, 0x30, 0x00, 0x00, 0x00,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(proto_data)));
-  RepeatedTest::StreamDecoder repeated_test(reader);
-
-  pw::Vector<uint32_t, 8> uint32s{};
-
-  for (int i = 0; i < 4; ++i) {
-    EXPECT_EQ(repeated_test.Next(), OkStatus());
-    EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::UINT32S);
-
-    Status status = repeated_test.ReadUint32s(uint32s);
-    EXPECT_EQ(status, OkStatus());
-    EXPECT_EQ(uint32s.size(), i + 1u);
-  }
-
-  for (int i = 0; i < 4; ++i) {
-    EXPECT_EQ(uint32s[i], i * 16u);
-  }
-
-  pw::Vector<uint32_t, 8> fixed32s{};
-
-  for (int i = 0; i < 4; ++i) {
-    EXPECT_EQ(repeated_test.Next(), OkStatus());
-    EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::FIXED32S);
-
-    Status status = repeated_test.ReadFixed32s(fixed32s);
-    EXPECT_EQ(status, OkStatus());
-    EXPECT_EQ(fixed32s.size(), i + 1u);
-  }
-
-  for (int i = 0; i < 4; ++i) {
-    EXPECT_EQ(fixed32s[i], i * 16u);
-  }
-
-  EXPECT_EQ(repeated_test.Next(), Status::OutOfRange());
-}
-
-TEST(CodegenRepeated, NonPackedVarintScalarVectorFull) {
-  // clang-format off
-  constexpr uint8_t proto_data[] = {
-    // uint32s[], v={0, 16, 32, 48}
-    0x08, 0x00,
-    0x08, 0x10,
-    0x08, 0x20,
-    0x08, 0x30,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(proto_data)));
-  RepeatedTest::StreamDecoder repeated_test(reader);
-
-  pw::Vector<uint32_t, 2> uint32s{};
-
-  EXPECT_EQ(repeated_test.Next(), OkStatus());
-  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::UINT32S);
-  Status status = repeated_test.ReadUint32s(uint32s);
-  EXPECT_EQ(status, OkStatus());
-  EXPECT_EQ(uint32s.size(), 1u);
-
-  EXPECT_EQ(repeated_test.Next(), OkStatus());
-  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::UINT32S);
-  status = repeated_test.ReadUint32s(uint32s);
-  EXPECT_EQ(status, OkStatus());
-  EXPECT_EQ(uint32s.size(), 2u);
-
-  EXPECT_EQ(repeated_test.Next(), OkStatus());
-  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::UINT32S);
-  status = repeated_test.ReadUint32s(uint32s);
-  EXPECT_EQ(status, Status::ResourceExhausted());
-  EXPECT_EQ(uint32s.size(), 2u);
-
-  for (int i = 0; i < 2; ++i) {
-    EXPECT_EQ(uint32s[i], i * 16u);
-  }
-}
-
-TEST(CodegenRepeated, NonPackedFixedScalarVectorFull) {
-  // clang-format off
-  constexpr uint8_t proto_data[] = {
-    // fixed32s[]. v={0, 16, 32, 48}
-    0x35, 0x00, 0x00, 0x00, 0x00,
-    0x35, 0x10, 0x00, 0x00, 0x00,
-    0x35, 0x20, 0x00, 0x00, 0x00,
-    0x35, 0x30, 0x00, 0x00, 0x00,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(proto_data)));
-  RepeatedTest::StreamDecoder repeated_test(reader);
-
-  pw::Vector<uint32_t, 2> fixed32s{};
-
-  EXPECT_EQ(repeated_test.Next(), OkStatus());
-  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::FIXED32S);
-  Status status = repeated_test.ReadFixed32s(fixed32s);
-  EXPECT_EQ(status, OkStatus());
-  EXPECT_EQ(fixed32s.size(), 1u);
-
-  EXPECT_EQ(repeated_test.Next(), OkStatus());
-  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::FIXED32S);
-  status = repeated_test.ReadFixed32s(fixed32s);
-  EXPECT_EQ(status, OkStatus());
-  EXPECT_EQ(fixed32s.size(), 2u);
-
-  EXPECT_EQ(repeated_test.Next(), OkStatus());
-  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::FIXED32S);
-  status = repeated_test.ReadFixed32s(fixed32s);
-  EXPECT_EQ(status, Status::ResourceExhausted());
-  EXPECT_EQ(fixed32s.size(), 2u);
-
-  for (int i = 0; i < 2; ++i) {
-    EXPECT_EQ(fixed32s[i], i * 16u);
-  }
-}
-
-TEST(CodegenRepeated, PackedScalar) {
-  // clang-format off
-  constexpr uint8_t proto_data[] = {
-    // uint32s[], v={0, 16, 32, 48}
-    0x0a, 0x04,
-    0x00,
-    0x10,
-    0x20,
-    0x30,
-    // fixed32s[]. v={0, 16, 32, 48}
-    0x32, 0x10,
-    0x00, 0x00, 0x00, 0x00,
-    0x10, 0x00, 0x00, 0x00,
-    0x20, 0x00, 0x00, 0x00,
-    0x30, 0x00, 0x00, 0x00,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(proto_data)));
-  RepeatedTest::StreamDecoder repeated_test(reader);
-
-  EXPECT_EQ(repeated_test.Next(), OkStatus());
-  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::UINT32S);
-  std::array<uint32_t, 8> uint32s{};
-  StatusWithSize sws = repeated_test.ReadUint32s(uint32s);
-  EXPECT_EQ(sws.status(), OkStatus());
-  EXPECT_EQ(sws.size(), 4u);
-
-  for (int i = 0; i < 4; ++i) {
-    EXPECT_EQ(uint32s[i], i * 16u);
-  }
-
-  EXPECT_EQ(repeated_test.Next(), OkStatus());
-  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::FIXED32S);
-  std::array<uint32_t, 8> fixed32s{};
-  sws = repeated_test.ReadFixed32s(fixed32s);
-  EXPECT_EQ(sws.status(), OkStatus());
-  EXPECT_EQ(sws.size(), 4u);
-
-  for (int i = 0; i < 4; ++i) {
-    EXPECT_EQ(fixed32s[i], i * 16u);
-  }
-
-  EXPECT_EQ(repeated_test.Next(), Status::OutOfRange());
-}
-
-TEST(CodegenRepeated, PackedVarintScalarExhausted) {
-  // clang-format off
-  constexpr uint8_t proto_data[] = {
-    // uint32s[], v={0, 16, 32, 48}
-    0x0a, 0x04,
-    0x00,
-    0x10,
-    0x20,
-    0x30,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(proto_data)));
-  RepeatedTest::StreamDecoder repeated_test(reader);
-
-  EXPECT_EQ(repeated_test.Next(), OkStatus());
-  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::UINT32S);
-  std::array<uint32_t, 2> uint32s{};
-  StatusWithSize sws = repeated_test.ReadUint32s(uint32s);
-  EXPECT_EQ(sws.status(), Status::ResourceExhausted());
-  EXPECT_EQ(sws.size(), 2u);
-
-  for (int i = 0; i < 2; ++i) {
-    EXPECT_EQ(uint32s[i], i * 16u);
-  }
-}
-
-TEST(CodegenRepeated, PackedFixedScalarExhausted) {
-  // clang-format off
-  constexpr uint8_t proto_data[] = {
-    // fixed32s[]. v={0, 16, 32, 48}
-    0x32, 0x10,
-    0x00, 0x00, 0x00, 0x00,
-    0x10, 0x00, 0x00, 0x00,
-    0x20, 0x00, 0x00, 0x00,
-    0x30, 0x00, 0x00, 0x00,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(proto_data)));
-  RepeatedTest::StreamDecoder repeated_test(reader);
-
-  EXPECT_EQ(repeated_test.Next(), OkStatus());
-  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::FIXED32S);
-  std::array<uint32_t, 2> fixed32s{};
-  StatusWithSize sws = repeated_test.ReadFixed32s(fixed32s);
-  EXPECT_EQ(sws.status(), Status::ResourceExhausted());
-  EXPECT_EQ(sws.size(), 0u);
-}
-
-TEST(CodegenRepeated, PackedScalarVector) {
-  // clang-format off
-  constexpr uint8_t proto_data[] = {
-    // uint32s[], v={0, 16, 32, 48}
-    0x0a, 0x04,
-    0x00,
-    0x10,
-    0x20,
-    0x30,
-    // fixed32s[]. v={0, 16, 32, 48}
-    0x32, 0x10,
-    0x00, 0x00, 0x00, 0x00,
-    0x10, 0x00, 0x00, 0x00,
-    0x20, 0x00, 0x00, 0x00,
-    0x30, 0x00, 0x00, 0x00,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(proto_data)));
-  RepeatedTest::StreamDecoder repeated_test(reader);
-
-  EXPECT_EQ(repeated_test.Next(), OkStatus());
-  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::UINT32S);
-  pw::Vector<uint32_t, 8> uint32s{};
-  Status status = repeated_test.ReadUint32s(uint32s);
-  EXPECT_EQ(status, OkStatus());
-  EXPECT_EQ(uint32s.size(), 4u);
-
-  for (int i = 0; i < 4; ++i) {
-    EXPECT_EQ(uint32s[i], i * 16u);
-  }
-
-  EXPECT_EQ(repeated_test.Next(), OkStatus());
-  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::FIXED32S);
-  pw::Vector<uint32_t, 8> fixed32s{};
-  status = repeated_test.ReadFixed32s(fixed32s);
-  EXPECT_EQ(status, OkStatus());
-  EXPECT_EQ(fixed32s.size(), 4u);
-
-  for (int i = 0; i < 4; ++i) {
-    EXPECT_EQ(fixed32s[i], i * 16u);
-  }
-
-  EXPECT_EQ(repeated_test.Next(), Status::OutOfRange());
-}
-
-TEST(CodegenRepeated, PackedVarintScalarVectorFull) {
-  // clang-format off
-  constexpr uint8_t proto_data[] = {
-    // uint32s[], v={0, 16, 32, 48}
-    0x0a, 0x04,
-    0x00,
-    0x10,
-    0x20,
-    0x30,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(proto_data)));
-  RepeatedTest::StreamDecoder repeated_test(reader);
-
-  EXPECT_EQ(repeated_test.Next(), OkStatus());
-  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::UINT32S);
-  pw::Vector<uint32_t, 2> uint32s{};
-  Status status = repeated_test.ReadUint32s(uint32s);
-  EXPECT_EQ(status, Status::ResourceExhausted());
-  EXPECT_EQ(uint32s.size(), 2u);
-
-  for (int i = 0; i < 2; ++i) {
-    EXPECT_EQ(uint32s[i], i * 16u);
-  }
-}
-
-TEST(CodegenRepeated, PackedFixedScalarVectorFull) {
-  // clang-format off
-  constexpr uint8_t proto_data[] = {
-    // fixed32s[]. v={0, 16, 32, 48}
-    0x32, 0x10,
-    0x00, 0x00, 0x00, 0x00,
-    0x10, 0x00, 0x00, 0x00,
-    0x20, 0x00, 0x00, 0x00,
-    0x30, 0x00, 0x00, 0x00,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(proto_data)));
-  RepeatedTest::StreamDecoder repeated_test(reader);
-
-  EXPECT_EQ(repeated_test.Next(), OkStatus());
-  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::FIXED32S);
-  pw::Vector<uint32_t, 2> fixed32s{};
-  Status status = repeated_test.ReadFixed32s(fixed32s);
-  EXPECT_EQ(status, Status::ResourceExhausted());
-  EXPECT_EQ(fixed32s.size(), 0u);
-}
-
-TEST(CodegenRepeated, PackedScalarVectorRepeated) {
-  // clang-format off
-  constexpr uint8_t proto_data[] = {
-    // uint32s[], v={0, 16, 32, 48}
-    0x0a, 0x04,
-    0x00,
-    0x10,
-    0x20,
-    0x30,
-    // uint32s[], v={64, 80, 96, 112}
-    0x0a, 0x04,
-    0x40,
-    0x50,
-    0x60,
-    0x70,
-    // fixed32s[]. v={0, 16, 32, 48}
-    0x32, 0x10,
-    0x00, 0x00, 0x00, 0x00,
-    0x10, 0x00, 0x00, 0x00,
-    0x20, 0x00, 0x00, 0x00,
-    0x30, 0x00, 0x00, 0x00,
-    // fixed32s[]. v={64, 80, 96, 112}
-    0x32, 0x10,
-    0x40, 0x00, 0x00, 0x00,
-    0x50, 0x00, 0x00, 0x00,
-    0x60, 0x00, 0x00, 0x00,
-    0x70, 0x00, 0x00, 0x00,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(proto_data)));
-  RepeatedTest::StreamDecoder repeated_test(reader);
-
-  EXPECT_EQ(repeated_test.Next(), OkStatus());
-  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::UINT32S);
-  pw::Vector<uint32_t, 8> uint32s{};
-  Status status = repeated_test.ReadUint32s(uint32s);
-  EXPECT_EQ(status, OkStatus());
-  EXPECT_EQ(uint32s.size(), 4u);
-
-  EXPECT_EQ(repeated_test.Next(), OkStatus());
-  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::UINT32S);
-  status = repeated_test.ReadUint32s(uint32s);
-  EXPECT_EQ(status, OkStatus());
-  EXPECT_EQ(uint32s.size(), 8u);
-
-  for (int i = 0; i < 8; ++i) {
-    EXPECT_EQ(uint32s[i], i * 16u);
-  }
-
-  EXPECT_EQ(repeated_test.Next(), OkStatus());
-  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::FIXED32S);
-  pw::Vector<uint32_t, 8> fixed32s{};
-  status = repeated_test.ReadFixed32s(fixed32s);
-  EXPECT_EQ(status, OkStatus());
-  EXPECT_EQ(fixed32s.size(), 4u);
-
-  EXPECT_EQ(repeated_test.Next(), OkStatus());
-  EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::FIXED32S);
-  status = repeated_test.ReadFixed32s(fixed32s);
-  EXPECT_EQ(status, OkStatus());
-  EXPECT_EQ(fixed32s.size(), 8u);
-
-  for (int i = 0; i < 8; ++i) {
-    EXPECT_EQ(fixed32s[i], i * 16u);
-  }
-
-  EXPECT_EQ(repeated_test.Next(), Status::OutOfRange());
-}
-
-TEST(CodegenRepeated, NonScalar) {
-  // clang-format off
-  constexpr uint8_t proto_data[] = {
-    // strings[], v={"the", "quick", "brown", "fox"}
-    0x1a, 0x03, 't', 'h', 'e',
-    0x1a, 0x5, 'q',  'u', 'i', 'c', 'k',
-    0x1a, 0x5,  'b', 'r', 'o', 'w',  'n',
-    0x1a, 0x3, 'f', 'o', 'x'
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(proto_data)));
-  RepeatedTest::StreamDecoder repeated_test(reader);
-
-  constexpr std::array<std::string_view, 4> kExpectedString{
-      {{"the"}, {"quick"}, {"brown"}, {"fox"}}};
-
-  for (int i = 0; i < 4; ++i) {
-    EXPECT_EQ(repeated_test.Next(), OkStatus());
-    EXPECT_EQ(repeated_test.Field().value(), RepeatedTest::Fields::STRINGS);
-    std::array<char, 32> string{};
-    StatusWithSize sws = repeated_test.ReadStrings(string);
-    EXPECT_EQ(sws.status(), OkStatus());
-    EXPECT_EQ(sws.size(), kExpectedString[i].size());
-    EXPECT_EQ(std::memcmp(string.data(),
-                          kExpectedString[i].data(),
-                          kExpectedString[i].size()),
-              0);
-  }
-
-  EXPECT_EQ(repeated_test.Next(), Status::OutOfRange());
-}
-
-}  // namespace
-}  // namespace pw::protobuf
diff --git a/pw_protobuf/codegen_encoder_test.cc b/pw_protobuf/codegen_encoder_test.cc
deleted file mode 100644
index 93bb02d..0000000
--- a/pw_protobuf/codegen_encoder_test.cc
+++ /dev/null
@@ -1,489 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <span>
-
-#include "gtest/gtest.h"
-#include "pw_protobuf/encoder.h"
-#include "pw_stream/memory_stream.h"
-
-// These header files contain the code generated by the pw_protobuf plugin.
-// They are re-generated every time the tests are built and are used by the
-// tests to ensure that the interface remains consistent.
-//
-// The purpose of the tests in this file is primarily to verify that the
-// generated C++ interface is valid rather than the correctness of the
-// low-level encoder.
-#include "pw_protobuf_test_protos/full_test.pwpb.h"
-#include "pw_protobuf_test_protos/importer.pwpb.h"
-#include "pw_protobuf_test_protos/non_pw_package.pwpb.h"
-#include "pw_protobuf_test_protos/proto2.pwpb.h"
-#include "pw_protobuf_test_protos/repeated.pwpb.h"
-
-namespace pw::protobuf {
-namespace {
-
-using namespace pw::protobuf::test;
-
-TEST(Codegen, Codegen) {
-  std::byte encode_buffer[512];
-  std::byte temp_buffer[512];
-  stream::MemoryWriter writer(encode_buffer);
-
-  Pigweed::StreamEncoder pigweed(writer, temp_buffer);
-  pigweed.WriteMagicNumber(73)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  pigweed.WriteZiggy(-111)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  pigweed.WriteErrorMessage("not a typewriter")
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  pigweed.WriteBin(Pigweed::Protobuf::Binary::ZERO)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-
-  {
-    Pigweed::Pigweed::StreamEncoder pigweed_pigweed =
-        pigweed.GetPigweedEncoder();
-    pigweed_pigweed.WriteStatus(Bool::FILE_NOT_FOUND)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-
-    ASSERT_EQ(pigweed_pigweed.status(), OkStatus());
-  }
-
-  {
-    Proto::StreamEncoder proto = pigweed.GetProtoEncoder();
-    proto.WriteBin(Proto::Binary::OFF)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    proto.WritePigweedPigweedBin(Pigweed::Pigweed::Binary::ZERO)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    proto.WritePigweedProtobufBin(Pigweed::Protobuf::Binary::ZERO)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-
-    {
-      Pigweed::Protobuf::Compiler::StreamEncoder meta = proto.GetMetaEncoder();
-      meta.WriteFileName("/etc/passwd")
-          .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-      meta.WriteStatus(Pigweed::Protobuf::Compiler::Status::FUBAR)
-          .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    }
-
-    {
-      Pigweed::StreamEncoder nested_pigweed = proto.GetPigweedEncoder();
-      nested_pigweed.WriteErrorMessage("here we go again")
-          .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-      nested_pigweed.WriteMagicNumber(616)
-          .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-
-      {
-        DeviceInfo::StreamEncoder device_info =
-            nested_pigweed.GetDeviceInfoEncoder();
-
-        {
-          KeyValuePair::StreamEncoder attributes =
-              device_info.GetAttributesEncoder();
-          attributes.WriteKey("version")
-              .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-          attributes.WriteValue("5.3.1")
-              .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-        }
-
-        {
-          KeyValuePair::StreamEncoder attributes =
-              device_info.GetAttributesEncoder();
-          attributes.WriteKey("chip")
-              .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-          attributes.WriteValue("left-soc")
-              .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-        }
-
-        device_info.WriteStatus(DeviceInfo::DeviceStatus::PANIC)
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-      }
-    }
-  }
-
-  for (int i = 0; i < 5; ++i) {
-    Proto::ID::StreamEncoder id = pigweed.GetIdEncoder();
-    id.WriteId(5 * i * i + 3 * i + 49)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  }
-
-  // clang-format off
-  constexpr uint8_t expected_proto[] = {
-    // pigweed.magic_number
-    0x08, 0x49,
-    // pigweed.ziggy
-    0x10, 0xdd, 0x01,
-    // pigweed.error_message
-    0x2a, 0x10, 'n', 'o', 't', ' ', 'a', ' ',
-    't', 'y', 'p', 'e', 'w', 'r', 'i', 't', 'e', 'r',
-    // pigweed.bin
-    0x40, 0x01,
-    // pigweed.pigweed
-    0x3a, 0x02,
-    // pigweed.pigweed.status
-    0x08, 0x02,
-    // pigweed.proto
-    0x4a, 0x56,
-    // pigweed.proto.bin
-    0x10, 0x00,
-    // pigweed.proto.pigweed_pigweed_bin
-    0x18, 0x00,
-    // pigweed.proto.pigweed_protobuf_bin
-    0x20, 0x01,
-    // pigweed.proto.meta
-    0x2a, 0x0f,
-    // pigweed.proto.meta.file_name
-    0x0a, 0x0b, '/', 'e', 't', 'c', '/', 'p', 'a', 's', 's', 'w', 'd',
-    // pigweed.proto.meta.status
-    0x10, 0x02,
-    // pigweed.proto.nested_pigweed
-    0x0a, 0x3d,
-    // pigweed.proto.nested_pigweed.error_message
-    0x2a, 0x10, 'h', 'e', 'r', 'e', ' ', 'w', 'e', ' ',
-    'g', 'o', ' ', 'a', 'g', 'a', 'i', 'n',
-    // pigweed.proto.nested_pigweed.magic_number
-    0x08, 0xe8, 0x04,
-    // pigweed.proto.nested_pigweed.device_info
-    0x32, 0x26,
-    // pigweed.proto.nested_pigweed.device_info.attributes[0]
-    0x22, 0x10,
-    // pigweed.proto.nested_pigweed.device_info.attributes[0].key
-    0x0a, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n',
-    // pigweed.proto.nested_pigweed.device_info.attributes[0].value
-    0x12, 0x05, '5', '.', '3', '.', '1',
-    // pigweed.proto.nested_pigweed.device_info.attributes[1]
-    0x22, 0x10,
-    // pigweed.proto.nested_pigweed.device_info.attributes[1].key
-    0x0a, 0x04, 'c', 'h', 'i', 'p',
-    // pigweed.proto.nested_pigweed.device_info.attributes[1].value
-    0x12, 0x08, 'l', 'e', 'f', 't', '-', 's', 'o', 'c',
-    // pigweed.proto.nested_pigweed.device_info.status
-    0x18, 0x03,
-    // pigweed.id[0]
-    0x52, 0x02,
-    // pigweed.id[0].id
-    0x08, 0x31,
-    // pigweed.id[1]
-    0x52, 0x02,
-    // pigweed.id[1].id
-    0x08, 0x39,
-    // pigweed.id[2]
-    0x52, 0x02,
-    // pigweed.id[2].id
-    0x08, 0x4b,
-    // pigweed.id[3]
-    0x52, 0x02,
-    // pigweed.id[3].id
-    0x08, 0x67,
-    // pigweed.id[4]
-    0x52, 0x03,
-    // pigweed.id[4].id
-    0x08, 0x8d, 0x01
-  };
-  // clang-format on
-
-  ConstByteSpan result = writer.WrittenData();
-  ASSERT_EQ(pigweed.status(), OkStatus());
-  EXPECT_EQ(result.size(), sizeof(expected_proto));
-  EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
-            0);
-}
-
-TEST(Codegen, RecursiveSubmessage) {
-  std::byte encode_buffer[512];
-
-  Crate::MemoryEncoder biggest_crate(encode_buffer);
-  biggest_crate.WriteName("Huge crate")
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-
-  {
-    Crate::StreamEncoder medium_crate = biggest_crate.GetSmallerCratesEncoder();
-    medium_crate.WriteName("Medium crate")
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    {
-      Crate::StreamEncoder small_crate = medium_crate.GetSmallerCratesEncoder();
-      small_crate.WriteName("Small crate")
-          .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    }
-    {
-      Crate::StreamEncoder tiny_crate = medium_crate.GetSmallerCratesEncoder();
-      tiny_crate.WriteName("Tiny crate")
-          .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    }
-  }
-
-  // clang-format off
-  constexpr uint8_t expected_proto[] = {
-    // crate.name
-    0x0a, 0x0a, 'H', 'u', 'g', 'e', ' ', 'c', 'r', 'a', 't', 'e',
-    // crate.smaller_crate[0]
-    0x12, 0x2b,
-    // crate.smaller_crate[0].name
-    0x0a, 0x0c, 'M', 'e', 'd', 'i', 'u', 'm', ' ', 'c', 'r', 'a', 't', 'e',
-    // crate.smaller_crate[0].smaller_crate[0]
-    0x12, 0x0d,
-    // crate.smaller_crate[0].smaller_crate[0].name
-    0x0a, 0x0b, 'S', 'm', 'a', 'l', 'l', ' ', 'c', 'r', 'a', 't', 'e',
-    // crate.smaller_crate[0].smaller_crate[1]
-    0x12, 0x0c,
-    // crate.smaller_crate[0].smaller_crate[1].name
-    0x0a, 0x0a, 'T', 'i', 'n', 'y', ' ', 'c', 'r', 'a', 't', 'e',
-  };
-  // clang-format on
-
-  ConstByteSpan result(biggest_crate);
-  ASSERT_EQ(biggest_crate.status(), OkStatus());
-  EXPECT_EQ(result.size(), sizeof(expected_proto));
-  EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
-            0);
-}
-
-TEST(CodegenRepeated, NonPackedScalar) {
-  std::byte encode_buffer[32];
-
-  stream::MemoryWriter writer(encode_buffer);
-  RepeatedTest::StreamEncoder repeated_test(writer, ByteSpan());
-  for (int i = 0; i < 4; ++i) {
-    repeated_test.WriteUint32s(i * 16)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  }
-
-  for (int i = 0; i < 4; ++i) {
-    repeated_test.WriteFixed32s(i * 16)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  }
-
-  // clang-format off
-  constexpr uint8_t expected_proto[] = {
-    // uint32s[], v={0, 16, 32, 48}
-    0x08, 0x00,
-    0x08, 0x10,
-    0x08, 0x20,
-    0x08, 0x30,
-    // fixed32s[]. v={0, 16, 32, 48}
-    0x35, 0x00, 0x00, 0x00, 0x00,
-    0x35, 0x10, 0x00, 0x00, 0x00,
-    0x35, 0x20, 0x00, 0x00, 0x00,
-    0x35, 0x30, 0x00, 0x00, 0x00,
-  };
-  // clang-format on
-
-  ConstByteSpan result = writer.WrittenData();
-  ASSERT_EQ(repeated_test.status(), OkStatus());
-  EXPECT_EQ(result.size(), sizeof(expected_proto));
-  EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
-            0);
-}
-
-TEST(CodegenRepeated, PackedScalar) {
-  std::byte encode_buffer[32];
-
-  stream::MemoryWriter writer(encode_buffer);
-  RepeatedTest::StreamEncoder repeated_test(writer, ByteSpan());
-  constexpr uint32_t values[] = {0, 16, 32, 48};
-  repeated_test.WriteUint32s(values)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  repeated_test.WriteFixed32s(values)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-
-  // clang-format off
-  constexpr uint8_t expected_proto[] = {
-    // uint32s[], v={0, 16, 32, 48}
-    0x0a, 0x04,
-    0x00,
-    0x10,
-    0x20,
-    0x30,
-    // fixed32s[]. v={0, 16, 32, 48}
-    0x32, 0x10,
-    0x00, 0x00, 0x00, 0x00,
-    0x10, 0x00, 0x00, 0x00,
-    0x20, 0x00, 0x00, 0x00,
-    0x30, 0x00, 0x00, 0x00,
-  };
-  // clang-format on
-
-  ConstByteSpan result = writer.WrittenData();
-  ASSERT_EQ(repeated_test.status(), OkStatus());
-  EXPECT_EQ(result.size(), sizeof(expected_proto));
-  EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
-            0);
-}
-
-TEST(CodegenRepeated, PackedBool) {
-  std::byte encode_buffer[32];
-
-  stream::MemoryWriter writer(encode_buffer);
-  RepeatedTest::StreamEncoder repeated_test(writer, ByteSpan());
-  constexpr bool values[] = {true, false, true, true, false};
-  repeated_test.WriteBools(std::span(values))
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-
-  // clang-format off
-  constexpr uint8_t expected_proto[] = {
-    // bools[], v={true, false, true, true, false}
-    0x3a, 0x05, 0x01, 0x00, 0x01, 0x01, 0x00,
-  };
-  // clang-format on
-
-  ConstByteSpan result = writer.WrittenData();
-  ASSERT_EQ(repeated_test.status(), OkStatus());
-  EXPECT_EQ(result.size(), sizeof(expected_proto));
-  EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
-            0);
-}
-
-TEST(CodegenRepeated, PackedScalarVector) {
-  std::byte encode_buffer[32];
-
-  stream::MemoryWriter writer(encode_buffer);
-  RepeatedTest::StreamEncoder repeated_test(writer, ByteSpan());
-  const pw::Vector<uint32_t, 4> values = {0, 16, 32, 48};
-  repeated_test.WriteUint32s(values)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  repeated_test.WriteFixed32s(values)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-
-  // clang-format off
-  constexpr uint8_t expected_proto[] = {
-    // uint32s[], v={0, 16, 32, 48}
-    0x0a, 0x04,
-    0x00,
-    0x10,
-    0x20,
-    0x30,
-    // fixed32s[]. v={0, 16, 32, 48}
-    0x32, 0x10,
-    0x00, 0x00, 0x00, 0x00,
-    0x10, 0x00, 0x00, 0x00,
-    0x20, 0x00, 0x00, 0x00,
-    0x30, 0x00, 0x00, 0x00,
-  };
-  // clang-format on
-
-  ConstByteSpan result = writer.WrittenData();
-  ASSERT_EQ(repeated_test.status(), OkStatus());
-  EXPECT_EQ(result.size(), sizeof(expected_proto));
-  EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
-            0);
-}
-
-TEST(CodegenRepeated, NonScalar) {
-  std::byte encode_buffer[32];
-
-  stream::MemoryWriter writer(encode_buffer);
-  RepeatedTest::StreamEncoder repeated_test(writer, ByteSpan());
-  constexpr const char* strings[] = {"the", "quick", "brown", "fox"};
-  for (const char* s : strings) {
-    repeated_test.WriteStrings(s)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  }
-
-  constexpr uint8_t expected_proto[] = {
-      0x1a, 0x03, 't', 'h', 'e', 0x1a, 0x5, 'q',  'u', 'i', 'c', 'k',
-      0x1a, 0x5,  'b', 'r', 'o', 'w',  'n', 0x1a, 0x3, 'f', 'o', 'x'};
-  ConstByteSpan result = writer.WrittenData();
-  ASSERT_EQ(repeated_test.status(), OkStatus());
-  EXPECT_EQ(result.size(), sizeof(expected_proto));
-  EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
-            0);
-}
-
-TEST(CodegenRepeated, Message) {
-  std::byte encode_buffer[64];
-
-  RepeatedTest::MemoryEncoder repeated_test(encode_buffer);
-  for (int i = 0; i < 3; ++i) {
-    auto structs = repeated_test.GetStructsEncoder();
-    structs.WriteOne(i * 1)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    structs.WriteTwo(i * 2)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  }
-
-  // clang-format off
-  constexpr uint8_t expected_proto[] = {
-    0x2a, 0x04, 0x08, 0x00, 0x10, 0x00, 0x2a, 0x04, 0x08,
-    0x01, 0x10, 0x02, 0x2a, 0x04, 0x08, 0x02, 0x10, 0x04};
-  // clang-format on
-
-  ConstByteSpan result(repeated_test);
-  ASSERT_EQ(repeated_test.status(), OkStatus());
-  EXPECT_EQ(result.size(), sizeof(expected_proto));
-  EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
-            0);
-}
-
-TEST(Codegen, Proto2) {
-  std::byte encode_buffer[64];
-
-  Foo::MemoryEncoder foo(encode_buffer);
-  foo.WriteInteger(3).IgnoreError();  // TODO(pwbug/387): Handle Status properly
-
-  {
-    constexpr std::byte data[] = {
-        std::byte(0xde), std::byte(0xad), std::byte(0xbe), std::byte(0xef)};
-    Bar::StreamEncoder bar = foo.GetBarEncoder();
-    bar.WriteData(data)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  }
-
-  constexpr uint8_t expected_proto[] = {
-      0x08, 0x03, 0x1a, 0x06, 0x0a, 0x04, 0xde, 0xad, 0xbe, 0xef};
-
-  ConstByteSpan result(foo);
-  ASSERT_EQ(foo.status(), OkStatus());
-  EXPECT_EQ(result.size(), sizeof(expected_proto));
-  EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
-            0);
-}
-
-TEST(Codegen, Import) {
-  std::byte encode_buffer[64];
-
-  Period::MemoryEncoder period(encode_buffer);
-  {
-    imported::Timestamp::StreamEncoder start = period.GetStartEncoder();
-    start.WriteSeconds(1589501793)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    start.WriteNanoseconds(511613110)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  }
-
-  {
-    imported::Timestamp::StreamEncoder end = period.GetEndEncoder();
-    end.WriteSeconds(1589501841)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    end.WriteNanoseconds(490367432)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  }
-
-  EXPECT_EQ(period.status(), OkStatus());
-}
-
-TEST(Codegen, NonPigweedPackage) {
-  using namespace non::pigweed::package::name;
-  std::byte encode_buffer[64];
-  std::array<const int64_t, 2> repeated = {0, 1};
-  stream::MemoryWriter writer(encode_buffer);
-  Packed::StreamEncoder packed(writer, ByteSpan());
-  packed.WriteRep(std::span<const int64_t>(repeated))
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  packed.WritePacked("packed")
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-
-  EXPECT_EQ(packed.status(), OkStatus());
-}
-
-}  // namespace
-}  // namespace pw::protobuf
diff --git a/pw_protobuf/codegen_test.cc b/pw_protobuf/codegen_test.cc
new file mode 100644
index 0000000..faceb51
--- /dev/null
+++ b/pw_protobuf/codegen_test.cc
@@ -0,0 +1,318 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 <span>
+
+#include "gtest/gtest.h"
+#include "pw_protobuf/encoder.h"
+
+// These header files contain the code generated by the pw_protobuf plugin.
+// They are re-generated every time the tests are built and are used by the
+// tests to ensure that the interface remains consistent.
+//
+// The purpose of the tests in this file is primarily to verify that the
+// generated C++ interface is valid rather than the correctness of the
+// low-level encoder.
+#include "pw_protobuf_test_protos/full_test.pwpb.h"
+#include "pw_protobuf_test_protos/importer.pwpb.h"
+#include "pw_protobuf_test_protos/non_pw_package.pwpb.h"
+#include "pw_protobuf_test_protos/proto2.pwpb.h"
+#include "pw_protobuf_test_protos/repeated.pwpb.h"
+
+namespace pw::protobuf {
+namespace {
+
+using namespace pw::protobuf::test;
+
+TEST(Codegen, Codegen) {
+  std::byte encode_buffer[512];
+  NestedEncoder<20, 20> encoder(encode_buffer);
+
+  Pigweed::Encoder pigweed(&encoder);
+  pigweed.WriteMagicNumber(73);
+  pigweed.WriteZiggy(-111);
+  pigweed.WriteErrorMessage("not a typewriter");
+  pigweed.WriteBin(Pigweed::Protobuf::Binary::ZERO);
+
+  {
+    Pigweed::Pigweed::Encoder pigweed_pigweed = pigweed.GetPigweedEncoder();
+    pigweed_pigweed.WriteStatus(Bool::FILE_NOT_FOUND);
+  }
+
+  {
+    Proto::Encoder proto = pigweed.GetProtoEncoder();
+    proto.WriteBin(Proto::Binary::OFF);
+    proto.WritePigweedPigweedBin(Pigweed::Pigweed::Binary::ZERO);
+    proto.WritePigweedProtobufBin(Pigweed::Protobuf::Binary::ZERO);
+
+    {
+      Pigweed::Protobuf::Compiler::Encoder meta = proto.GetMetaEncoder();
+      meta.WriteFileName("/etc/passwd");
+      meta.WriteStatus(Pigweed::Protobuf::Compiler::Status::FUBAR);
+    }
+
+    {
+      Pigweed::Encoder nested_pigweed = proto.GetPigweedEncoder();
+      pigweed.WriteErrorMessage("here we go again");
+      pigweed.WriteMagicNumber(616);
+
+      {
+        DeviceInfo::Encoder device_info = nested_pigweed.GetDeviceInfoEncoder();
+
+        {
+          KeyValuePair::Encoder attributes = device_info.GetAttributesEncoder();
+          attributes.WriteKey("version");
+          attributes.WriteValue("5.3.1");
+        }
+
+        {
+          KeyValuePair::Encoder attributes = device_info.GetAttributesEncoder();
+          attributes.WriteKey("chip");
+          attributes.WriteValue("left-soc");
+        }
+
+        device_info.WriteStatus(DeviceInfo::DeviceStatus::PANIC);
+      }
+    }
+  }
+
+  for (int i = 0; i < 5; ++i) {
+    Proto::ID::Encoder id = pigweed.GetIdEncoder();
+    id.WriteId(5 * i * i + 3 * i + 49);
+  }
+
+  // clang-format off
+  constexpr uint8_t expected_proto[] = {
+    // pigweed.magic_number
+    0x08, 0x49,
+    // pigweed.ziggy
+    0x10, 0xdd, 0x01,
+    // pigweed.error_message
+    0x2a, 0x10, 'n', 'o', 't', ' ', 'a', ' ',
+    't', 'y', 'p', 'e', 'w', 'r', 'i', 't', 'e', 'r',
+    // pigweed.bin
+    0x40, 0x01,
+    // pigweed.pigweed
+    0x3a, 0x02,
+    // pigweed.pigweed.status
+    0x08, 0x02,
+    // pigweed.proto
+    0x4a, 0x56,
+    // pigweed.proto.bin
+    0x10, 0x00,
+    // pigweed.proto.pigweed_pigweed_bin
+    0x18, 0x00,
+    // pigweed.proto.pigweed_protobuf_bin
+    0x20, 0x01,
+    // pigweed.proto.meta
+    0x2a, 0x0f,
+    // pigweed.proto.meta.file_name
+    0x0a, 0x0b, '/', 'e', 't', 'c', '/', 'p', 'a', 's', 's', 'w', 'd',
+    // pigweed.proto.meta.status
+    0x10, 0x02,
+    // pigweed.proto.nested_pigweed
+    0x0a, 0x3d,
+    // pigweed.proto.nested_pigweed.error_message
+    0x2a, 0x10, 'h', 'e', 'r', 'e', ' ', 'w', 'e', ' ',
+    'g', 'o', ' ', 'a', 'g', 'a', 'i', 'n',
+    // pigweed.proto.nested_pigweed.magic_number
+    0x08, 0xe8, 0x04,
+    // pigweed.proto.nested_pigweed.device_info
+    0x32, 0x26,
+    // pigweed.proto.nested_pigweed.device_info.attributes[0]
+    0x22, 0x10,
+    // pigweed.proto.nested_pigweed.device_info.attributes[0].key
+    0x0a, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n',
+    // pigweed.proto.nested_pigweed.device_info.attributes[0].value
+    0x12, 0x05, '5', '.', '3', '.', '1',
+    // pigweed.proto.nested_pigweed.device_info.attributes[1]
+    0x22, 0x10,
+    // pigweed.proto.nested_pigweed.device_info.attributes[1].key
+    0x0a, 0x04, 'c', 'h', 'i', 'p',
+    // pigweed.proto.nested_pigweed.device_info.attributes[1].value
+    0x12, 0x08, 'l', 'e', 'f', 't', '-', 's', 'o', 'c',
+    // pigweed.proto.nested_pigweed.device_info.status
+    0x18, 0x03,
+    // pigweed.id[0]
+    0x52, 0x02,
+    // pigweed.id[0].id
+    0x08, 0x31,
+    // pigweed.id[1]
+    0x52, 0x02,
+    // pigweed.id[1].id
+    0x08, 0x39,
+    // pigweed.id[2]
+    0x52, 0x02,
+    // pigweed.id[2].id
+    0x08, 0x4b,
+    // pigweed.id[3]
+    0x52, 0x02,
+    // pigweed.id[3].id
+    0x08, 0x67,
+    // pigweed.id[4]
+    0x52, 0x03,
+    // pigweed.id[4].id
+    0x08, 0x8d, 0x01
+  };
+  // clang-format on
+
+  Result result = encoder.Encode();
+  ASSERT_EQ(result.status(), OkStatus());
+  EXPECT_EQ(result.value().size(), sizeof(expected_proto));
+  EXPECT_EQ(std::memcmp(
+                result.value().data(), expected_proto, sizeof(expected_proto)),
+            0);
+}
+
+TEST(CodegenRepeated, NonPackedScalar) {
+  std::byte encode_buffer[32];
+  NestedEncoder encoder(encode_buffer);
+
+  RepeatedTest::Encoder repeated_test(&encoder);
+  for (int i = 0; i < 4; ++i) {
+    repeated_test.WriteUint32s(i * 16);
+  }
+
+  constexpr uint8_t expected_proto[] = {
+      0x08, 0x00, 0x08, 0x10, 0x08, 0x20, 0x08, 0x30};
+
+  Result result = encoder.Encode();
+  ASSERT_EQ(result.status(), OkStatus());
+  EXPECT_EQ(result.value().size(), sizeof(expected_proto));
+  EXPECT_EQ(std::memcmp(
+                result.value().data(), expected_proto, sizeof(expected_proto)),
+            0);
+}
+
+TEST(CodegenRepeated, PackedScalar) {
+  std::byte encode_buffer[32];
+  NestedEncoder encoder(encode_buffer);
+
+  RepeatedTest::Encoder repeated_test(&encoder);
+  constexpr uint32_t values[] = {0, 16, 32, 48};
+  repeated_test.WriteUint32s(values);
+
+  constexpr uint8_t expected_proto[] = {0x0a, 0x04, 0x00, 0x10, 0x20, 0x30};
+  Result result = encoder.Encode();
+  ASSERT_EQ(result.status(), OkStatus());
+  EXPECT_EQ(result.value().size(), sizeof(expected_proto));
+  EXPECT_EQ(std::memcmp(
+                result.value().data(), expected_proto, sizeof(expected_proto)),
+            0);
+}
+
+TEST(CodegenRepeated, NonScalar) {
+  std::byte encode_buffer[32];
+  NestedEncoder encoder(encode_buffer);
+
+  RepeatedTest::Encoder repeated_test(&encoder);
+  constexpr const char* strings[] = {"the", "quick", "brown", "fox"};
+  for (const char* s : strings) {
+    repeated_test.WriteStrings(s);
+  }
+
+  constexpr uint8_t expected_proto[] = {
+      0x1a, 0x03, 't', 'h', 'e', 0x1a, 0x5, 'q',  'u', 'i', 'c', 'k',
+      0x1a, 0x5,  'b', 'r', 'o', 'w',  'n', 0x1a, 0x3, 'f', 'o', 'x'};
+  Result result = encoder.Encode();
+  ASSERT_EQ(result.status(), OkStatus());
+  EXPECT_EQ(result.value().size(), sizeof(expected_proto));
+  EXPECT_EQ(std::memcmp(
+                result.value().data(), expected_proto, sizeof(expected_proto)),
+            0);
+}
+
+TEST(CodegenRepeated, Message) {
+  std::byte encode_buffer[64];
+  NestedEncoder<1, 3> encoder(encode_buffer);
+
+  RepeatedTest::Encoder repeated_test(&encoder);
+  for (int i = 0; i < 3; ++i) {
+    auto structs = repeated_test.GetStructsEncoder();
+    structs.WriteOne(i * 1);
+    structs.WriteTwo(i * 2);
+  }
+
+  // clang-format off
+  constexpr uint8_t expected_proto[] = {
+    0x2a, 0x04, 0x08, 0x00, 0x10, 0x00, 0x2a, 0x04, 0x08,
+    0x01, 0x10, 0x02, 0x2a, 0x04, 0x08, 0x02, 0x10, 0x04};
+  // clang-format on
+
+  Result result = encoder.Encode();
+  ASSERT_EQ(result.status(), OkStatus());
+  EXPECT_EQ(result.value().size(), sizeof(expected_proto));
+  EXPECT_EQ(std::memcmp(
+                result.value().data(), expected_proto, sizeof(expected_proto)),
+            0);
+}
+
+TEST(Codegen, Proto2) {
+  std::byte encode_buffer[64];
+  NestedEncoder<1, 3> encoder(encode_buffer);
+
+  Foo::Encoder foo(&encoder);
+  foo.WriteInt(3);
+
+  {
+    constexpr std::byte data[] = {
+        std::byte(0xde), std::byte(0xad), std::byte(0xbe), std::byte(0xef)};
+    Bar::Encoder bar = foo.GetBarEncoder();
+    bar.WriteData(data);
+  }
+
+  constexpr uint8_t expected_proto[] = {
+      0x08, 0x03, 0x1a, 0x06, 0x0a, 0x04, 0xde, 0xad, 0xbe, 0xef};
+
+  Result result = encoder.Encode();
+  ASSERT_EQ(result.status(), OkStatus());
+  EXPECT_EQ(result.value().size(), sizeof(expected_proto));
+  EXPECT_EQ(std::memcmp(
+                result.value().data(), expected_proto, sizeof(expected_proto)),
+            0);
+}
+
+TEST(Codegen, Import) {
+  std::byte encode_buffer[64];
+  NestedEncoder<1, 3> encoder(encode_buffer);
+
+  Period::Encoder period(&encoder);
+  {
+    imported::Timestamp::Encoder start = period.GetStartEncoder();
+    start.WriteSeconds(1589501793);
+    start.WriteNanoseconds(511613110);
+  }
+
+  {
+    imported::Timestamp::Encoder end = period.GetEndEncoder();
+    end.WriteSeconds(1589501841);
+    end.WriteNanoseconds(490367432);
+  }
+
+  EXPECT_EQ(encoder.Encode().status(), OkStatus());
+}
+
+TEST(Codegen, NonPigweedPackage) {
+  using namespace non::pigweed::package::name;
+  std::byte encode_buffer[64];
+  std::array<const int64_t, 2> repeated = {0, 1};
+  NestedEncoder<1, 2> encoder(encode_buffer);
+  Packed::Encoder packed(&encoder);
+  packed.WriteRep(std::span<const int64_t>(repeated));
+  packed.WritePacked("packed");
+
+  EXPECT_EQ(encoder.Encode().status(), OkStatus());
+}
+
+}  // namespace
+}  // namespace pw::protobuf
diff --git a/pw_protobuf/decoder.cc b/pw_protobuf/decoder.cc
index 7f8c8cf..813046c 100644
--- a/pw_protobuf/decoder.cc
+++ b/pw_protobuf/decoder.cc
@@ -50,10 +50,7 @@
 uint32_t Decoder::FieldNumber() const {
   uint64_t key;
   varint::Decode(proto_, &key);
-  if (!FieldKey::IsValidKey(key)) {
-    return 0;
-  }
-  return FieldKey(key).field_number();
+  return key >> kFieldNumberShift;
 }
 
 Status Decoder::ReadUint32(uint32_t* out) {
@@ -116,15 +113,16 @@
 size_t Decoder::FieldSize() const {
   uint64_t key;
   size_t key_size = varint::Decode(proto_, &key);
-  if (key_size == 0 || !FieldKey::IsValidKey(key)) {
+  if (key_size == 0) {
     return 0;
   }
 
   std::span<const std::byte> remainder = proto_.subspan(key_size);
+  WireType wire_type = static_cast<WireType>(key & kWireTypeMask);
   uint64_t value = 0;
   size_t expected_size = 0;
 
-  switch (FieldKey(key).wire_type()) {
+  switch (wire_type) {
     case WireType::kVarint:
       expected_size = varint::Decode(remainder, &value);
       if (expected_size == 0) {
@@ -164,11 +162,8 @@
     return Status::FailedPrecondition();
   }
 
-  if (!FieldKey::IsValidKey(key)) {
-    return Status::DataLoss();
-  }
-
-  if (FieldKey(key).wire_type() != expected_type) {
+  WireType wire_type = static_cast<WireType>(key & kWireTypeMask);
+  if (wire_type != expected_type) {
     return Status::FailedPrecondition();
   }
 
diff --git a/pw_protobuf/decoder_test.cc b/pw_protobuf/decoder_test.cc
index d006312..c77e990 100644
--- a/pw_protobuf/decoder_test.cc
+++ b/pw_protobuf/decoder_test.cc
@@ -28,28 +28,22 @@
 
     switch (field_number) {
       case 1:
-        decoder.ReadInt32(&test_int32)
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        decoder.ReadInt32(&test_int32);
         break;
       case 2:
-        decoder.ReadSint32(&test_sint32)
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        decoder.ReadSint32(&test_sint32);
         break;
       case 3:
-        decoder.ReadBool(&test_bool)
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        decoder.ReadBool(&test_bool);
         break;
       case 4:
-        decoder.ReadDouble(&test_double)
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        decoder.ReadDouble(&test_double);
         break;
       case 5:
-        decoder.ReadFixed32(&test_fixed32)
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        decoder.ReadFixed32(&test_fixed32);
         break;
       case 6:
-        decoder.ReadString(&str)
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        decoder.ReadString(&str);
         std::memcpy(test_string, str.data(), str.size());
         test_string[str.size()] = '\0';
         break;
@@ -162,32 +156,6 @@
   EXPECT_EQ(decoder.Next(), Status::OutOfRange());
 }
 
-TEST(Decoder, Decode_BadFieldNumber) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // type=int32, k=1, v=42
-    0x08, 0x2a,
-    // type=int32, k=19001, v=42 (invalid field number)
-    0xc8, 0xa3, 0x09, 0x2a,
-    // type=bool, k=3, v=false
-    0x18, 0x00,
-  };
-  // clang-format on
-
-  Decoder decoder(std::as_bytes(std::span(encoded_proto)));
-  int32_t value;
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  EXPECT_EQ(decoder.FieldNumber(), 1u);
-  ASSERT_EQ(decoder.ReadInt32(&value), OkStatus());
-  EXPECT_EQ(value, 42);
-
-  // Bad field.
-  EXPECT_EQ(decoder.Next(), Status::DataLoss());
-  EXPECT_EQ(decoder.FieldNumber(), 0u);
-  EXPECT_EQ(decoder.ReadInt32(&value), Status::DataLoss());
-}
-
 TEST(CallbackDecoder, Decode) {
   CallbackDecoder decoder;
   TestDecodeHandler handler;
diff --git a/pw_protobuf/decoding.rst b/pw_protobuf/decoding.rst
new file mode 100644
index 0000000..7120f60
--- /dev/null
+++ b/pw_protobuf/decoding.rst
@@ -0,0 +1,27 @@
+.. _module-pw_protobuf-decoding:
+
+--------
+Decoding
+--------
+
+Size report
+===========
+
+Full size report
+^^^^^^^^^^^^^^^^
+
+This report demonstrates the size of using the entire decoder with all of its
+decode methods and a decode callback for a proto message containing each of the
+protobuf field types.
+
+.. include:: size_report/decoder_full
+
+
+Incremental size report
+^^^^^^^^^^^^^^^^^^^^^^^
+
+This report is generated using the full report as a base and adding some int32
+fields to the decode callback to demonstrate the incremental cost of decoding
+fields in a message.
+
+.. include:: size_report/decoder_incremental
diff --git a/pw_protobuf/docs.rst b/pw_protobuf/docs.rst
index ecc3f55..5a02779 100644
--- a/pw_protobuf/docs.rst
+++ b/pw_protobuf/docs.rst
@@ -1,8 +1,8 @@
 .. _module-pw_protobuf:
 
-===========
+-----------
 pw_protobuf
-===========
+-----------
 The protobuf module provides a lightweight interface for encoding and decoding
 the Protocol Buffer wire format.
 
@@ -10,11 +10,10 @@
 
   The protobuf module is a work in progress. Wire format encoding and decoding
   is supported, though the APIs are not final. C++ code generation exists for
-  encoding and decoding, but does not cover all message types.
+  encoding, but not decoding.
 
-------
 Design
-------
+======
 Unlike other protobuf libraries, which typically provide in-memory data
 structures to represent protobuf messages, ``pw_protobuf`` operates directly on
 the wire format and leaves data storage to the user. This has a few benefits.
@@ -28,9 +27,8 @@
 specific protobuf messages. The code generation integrates with Pigweed's GN
 build system.
 
--------------
 Configuration
--------------
+=============
 ``pw_protobuf`` supports the following configuration options.
 
 * ``PW_PROTOBUF_CFG_MAX_VARINT_SIZE``:
@@ -55,713 +53,33 @@
   | 5 bytes           | 4,294,967,295 or < 4GiB (max uint32_t) |
   +-------------------+----------------------------------------+
 
---------
-Encoding
---------
-
 Usage
 =====
-Pigweed's protobuf encoders encode directly to the wire format of a proto rather
-than staging information to a mutable datastructure. This means any writes of a
-value are final, and can't be referenced or modified as a later step in the
-encode process.
+``pw_protobuf`` splits wire format encoding and decoding operations. Links to
+the design and APIs of each are listed in below.
 
-MemoryEncoder
-=============
-A MemoryEncoder directly encodes a proto to an in-memory buffer.
+See also :ref:`module-pw_protobuf_compiler` for details on ``pw_protobuf``'s
+build system integration.
 
-.. Code:: cpp
+**pw_protobuf functionality**
 
-  // Writes a proto response to the provided buffer, returning the encode
-  // status and number of bytes written.
-  StatusWithSize WriteProtoResponse(ByteSpan response) {
-    // All proto writes are directly written to the `response` buffer.
-    MemoryEncoder encoder(response);
-    encoder.WriteUint32(kMagicNumberField, 0x1a1a2b2b);
-    encoder.WriteString(kFavoriteFood, "cookies");
-    return StatusWithSize(encoder.status(), encoder.size());
-  }
+.. toctree::
+  :maxdepth: 1
 
-StreamEncoder
-=============
-pw_protobuf's StreamEncoder class operates on pw::stream::Writer objects to
-serialized proto data. This means you can directly encode a proto to something
-like pw::sys_io without needing to build the complete message in memory first.
-
-.. Code:: cpp
-
-  #include "pw_protobuf/encoder.h"
-  #include "pw_stream/sys_io_stream.h"
-  #include "pw_bytes/span.h"
-
-  pw::stream::SysIoWriter sys_io_writer;
-  pw::protobuf::StreamEncoder my_proto_encoder(sys_io_writer,
-                                                  pw::ByteSpan());
-
-  // Once this line returns, the field has been written to the Writer.
-  my_proto_encoder.WriteInt64(kTimestampFieldNumber, system::GetUnixEpoch());
-
-  // There's no intermediate buffering when writing a string directly to a
-  // StreamEncoder.
-  my_proto_encoder.WriteString(kWelcomeMessageFieldNumber,
-                               "Welcome to Pigweed!");
-  if (!my_proto_encoder.status().ok()) {
-    PW_LOG_INFO("Failed to encode proto; %s", my_proto_encoder.status().str());
-  }
-
-Nested submessages
-==================
-Writing proto messages with nested submessages requires buffering due to
-limitations of the proto format. Every proto submessage must know the size of
-the submessage before its final serialization can begin. A streaming encoder can
-be passed a scratch buffer to use when constructing nested messages. All
-submessage data is buffered to this scratch buffer until the submessage is
-finalized. Note that the contents of this scratch buffer is not necessarily
-valid proto data, so don't try to use it directly.
-
-MemoryEncoder objects use the final destination buffer rather than relying on a
-scratch buffer. Note that this means your destination buffer might need
-additional space for overhead incurred by nesting submessages. The
-``MaxScratchBufferSize()`` helper function can be useful in estimating how much
-space to allocate to account for nested submessage encoding overhead.
-
-.. Code:: cpp
-
-  #include "pw_protobuf/encoder.h"
-  #include "pw_stream/sys_io_stream.h"
-  #include "pw_bytes/span.h"
-
-  pw::stream::SysIoWriter sys_io_writer;
-  // The scratch buffer should be at least as big as the largest nested
-  // submessage. It's a good idea to be a little generous.
-  std::byte submessage_scratch_buffer[64];
-
-  // Provide the scratch buffer to the proto encoder. The buffer's lifetime must
-  // match the lifetime of the encoder.
-  pw::protobuf::StreamEncoder my_proto_encoder(sys_io_writer,
-                                               submessage_scratch_buffer);
-
-  {
-    // Note that the parent encoder, my_proto_encoder, cannot be used until the
-    // nested encoder, nested_encoder, has been destroyed.
-    StreamEncoder nested_encoder =
-        my_proto_encoder.GetNestedEncoder(kPetsFieldNumber);
-
-    // There's intermediate buffering when writing to a nested encoder.
-    nested_encoder.WriteString(kNameFieldNumber, "Spot");
-    nested_encoder.WriteString(kPetTypeFieldNumber, "dog");
-
-    // When this scope ends, the nested encoder is serialized to the Writer.
-    // In addition, the parent encoder, my_proto_encoder, can be used again.
-  }
-
-  // If an encode error occurs when encoding the nested messages, it will be
-  // reflected at the root encoder.
-  if (!my_proto_encoder.status().ok()) {
-    PW_LOG_INFO("Failed to encode proto; %s", my_proto_encoder.status().str());
-  }
-
-.. warning::
-  When a nested submessage is created, any use of the parent encoder that
-  created the nested encoder will trigger a crash. To resume using the parent
-  encoder, destroy the submessage encoder first.
-
-Repeated Fields
-===============
-Repeated fields can be encoded a value at a time by repeatedly calling
-`WriteInt32` etc., or as a packed field by calling e.g. `WritePackedInt32` with
-a `std::span<Type>` or `WriteRepeatedInt32` with a `pw::Vector<Type>` (see
-:ref:`module-pw_containers` for details).
-
-Error Handling
-==============
-While individual write calls on a proto encoder return pw::Status objects, the
-encoder tracks all status returns and "latches" onto the first error
-encountered. This status can be accessed via ``StreamEncoder::status()``.
-
-Proto map encoding utils
-========================
-
-Some additional helpers for encoding more complex but common protobuf
-submessages (e.g. map<string, bytes>) are provided in
-``pw_protobuf/map_utils.h``.
-
-.. Note::
-  The helper API are currently in-development and may not remain stable.
-
---------
-Decoding
---------
-``pw_protobuf`` provides three decoder implementations, which are described
-below.
-
-Decoder
-=======
-The ``Decoder`` class operates on an protobuf message located in a buffer in
-memory. It provides an iterator-style API for processing a message. Calling
-``Next()`` advances the decoder to the next proto field, which can then be read
-by calling the appropriate ``Read*`` function for the field number.
-
-When reading ``bytes`` and ``string`` fields, the decoder returns a view of that
-field within the buffer; no data is copied out.
-
-.. note::
-
-  ``pw::protobuf::Decoder`` will soon be renamed ``pw::protobuf::MemoryDecoder``
-  for clarity and consistency.
-
-.. code-block:: c++
-
-  #include "pw_protobuf/decoder.h"
-  #include "pw_status/try.h"
-
-  pw::Status DecodeProtoFromBuffer(std::span<const std::byte> buffer) {
-    pw::protobuf::Decoder decoder(buffer);
-    pw::Status status;
-
-    uint32_t uint32_field;
-    std::string_view string_field;
-
-    // Iterate over the fields in the message. A return value of OK indicates
-    // that a valid field has been found and can be read. When the decoder
-    // reaches the end of the message, Next() will return OUT_OF_RANGE.
-    // Other return values indicate an error trying to decode the message.
-    while ((status = decoder.Next()).ok()) {
-      switch (decoder.FieldNumber()) {
-        case 1:
-          PW_TRY(decoder.ReadUint32(&uint32_field));
-          break;
-        case 2:
-          // The passed-in string_view will point to the contents of the string
-          // field within the buffer.
-          PW_TRY(decoder.ReadString(&string_field));
-          break;
-      }
-    }
-
-    // Do something with the fields...
-
-    return status.IsOutOfRange() ? OkStatus() : status;
-  }
-
-StreamDecoder
-=============
-Sometimes, a serialized protobuf message may be too large to fit into an
-in-memory buffer. To faciliate working with that type of data, ``pw_protobuf``
-provides a ``StreamDecoder`` which reads data from a ``pw::stream::Reader``.
-
-.. admonition:: When to use a stream decoder
-
-  The ``StreamDecoder`` should only be used in cases where the protobuf data
-  cannot be read directly from a buffer. It is unadvisable to use a
-  ``StreamDecoder`` with a ``MemoryStream`` --- the decoding operations will be
-  far less efficient than the ``Decoder``, which is optimized for in-memory
-  messages.
-
-The general usage of a ``StreamDecoder`` is similar to the basic ``Decoder``,
-with the exception of ``bytes`` and ``string`` fields, which must be copied out
-of the stream into a provided buffer.
-
-.. code-block:: c++
-
-  #include "pw_protobuf/decoder.h"
-  #include "pw_status/try.h"
-
-  pw::Status DecodeProtoFromStream(pw::stream::Reader& reader) {
-    pw::protobuf::StreamDecoder decoder(reader);
-    pw::Status status;
-
-    uint32_t uint32_field;
-    char string_field[16];
-
-    // Iterate over the fields in the message. A return value of OK indicates
-    // that a valid field has been found and can be read. When the decoder
-    // reaches the end of the message, Next() will return OUT_OF_RANGE.
-    // Other return values indicate an error trying to decode the message.
-    while ((status = decoder.Next()).ok()) {
-      // FieldNumber() returns a Result<uint32_t> as it may fail sometimes.
-      // However, FieldNumber() is guaranteed to be valid after a call to Next()
-      // that returns OK, so the value can be used directly here.
-      switch (decoder.FieldNumber().value()) {
-        case 1: {
-          Result<uint32_t> result = decoder.ReadUint32();
-          if (result.ok()) {
-            uint32_field = result.value();
-          }
-          break;
-        }
-
-        case 2:
-          // The string field is copied into the provided buffer. If the buffer
-          // is too small to fit the string, RESOURCE_EXHAUSTED is returned and
-          // the decoder is not advanced, allowing the field to be re-read.
-          PW_TRY(decoder.ReadString(string_field));
-          break;
-      }
-    }
-
-    // Do something with the fields...
-
-    return status.IsOutOfRange() ? OkStatus() : status;
-  }
-
-Where the length of the protobuf message is known in advance, the decoder can
-be prevented from reading from the stream beyond the known bounds by specifying
-the known length to the decoder:
-
-.. code-block:: c++
-
-  pw::protobuf::StreamDecoder decoder(reader, message_length);
-
-When a decoder constructed in this way goes out of scope, it will consume any
-remaining bytes in `message_length` allowing the next `Read` to be data after
-the protobuf, even when it was not fully parsed.
-
-The ``StreamDecoder`` can also return a ``StreamDecoder::BytesReader`` for
-reading bytes fields, avoiding the need to copy data out directly.
-
-.. code-block:: c++
-
-  if (decoder.FieldNumber() == 3) {
-    // bytes my_bytes_field = 3;
-    pw::protobuf::StreamDecoder::BytesReader bytes_reader =
-        decoder.GetBytesReader();
-
-    // Read data incrementally through the bytes_reader. While the reader is
-    // active, any attempts to use the decoder will result in a crash. When the
-    // reader goes out of scope, it will close itself and reactive the decoder.
-  }
-
-This reader supports seeking only if the ``StreamDecoder``'s reader supports
-seeking.
-
-If the current field is a nested protobuf message, the ``StreamDecoder`` can
-provide a decoder for the nested message. While the nested decoder is active,
-its parent decoder cannot be used.
-
-.. code-block:: c++
-
-  if (decoder.FieldNumber() == 4) {
-    pw::protobuf::StreamDecoder nested_decoder = decoder.GetNestedDecoder();
-
-    while (nested_decoder.Next().ok()) {
-      // Process the nested message.
-    }
-
-    // Once the nested decoder goes out of scope, it closes itself, and the
-    // parent decoder can be used again.
-  }
-
-Repeated Fields
----------------
-The ``StreamDecoder`` supports two encoded forms of repeated fields: value at a
-time, by repeatedly calling `ReadInt32` etc., and packed fields by calling
-e.g. `ReadPackedInt32`.
-
-Since protobuf encoders are permitted to choose either format, including
-splitting repeated fields up into multiple packed fields, ``StreamDecoder``
-also provides method `ReadRepeatedInt32` etc. methods that accept a
-``pw::Vector`` (see :ref:`module-pw_containers` for details). These methods
-correctly extend the vector for either encoding.
-
-Message
-=======
-
-The module implements a message parsing class ``Message``, in
-``pw_protobuf/message.h``, to faciliate proto message parsing and field access.
-The class provides interfaces for searching fields in a proto message and
-creating helper classes for it according to its interpreted field type, i.e.
-uint32, bytes, string, map<>, repeated etc. The class works on top of
-``StreamDecoder`` and thus requires a ``pw::stream::SeekableReader`` for proto
-message access. The following gives examples for using the class to process
-different fields in a proto message:
-
-.. code-block:: c++
-
-  // Consider the proto messages defined as follows:
-  //
-  // message Nested {
-  //   string nested_str = 1;
-  //   bytes nested_bytes = 2;
-  // }
-  //
-  // message {
-  //   uint32 integer = 1;
-  //   string str = 2;
-  //   bytes bytes = 3;
-  //   Nested nested = 4;
-  //   repeated string rep_str = 5;
-  //   repeated Nested rep_nested  = 6;
-  //   map<string, bytes> str_to_bytes = 7;
-  //   map<string, Nested> str_to_nested = 8;
-  // }
-
-  // Given a seekable `reader` that reads the top-level proto message, and
-  // a <proto_size> that gives the size of the proto message:
-  Message message(reader, proto_size);
-
-  // Parse a proto integer field
-  Uint32 integer = messasge_parser.AsUint32(1);
-  if (!integer.ok()) {
-    // handle parsing error. i.e. return integer.status().
-  }
-  uint32_t integer_value = integer.value(); // obtained the value
-
-  // Parse a string field
-  String str = message.AsString(2);
-  if (!str.ok()) {
-    // handle parsing error. i.e. return str.status();
-  }
-
-  // check string equal
-  Result<bool> str_check = str.Equal("foo");
-
-  // Parse a bytes field
-  Bytes bytes = message.AsBytes(3);
-  if (!bytes.ok()) {
-    // handle parsing error. i.e. return bytes.status();
-  }
-
-  // Get a reader to the bytes.
-  stream::IntervalReader bytes_reader = bytes.GetBytesReader();
-
-  // Parse nested message `Nested nested = 4;`
-  Message nested = message.AsMessage(4).
-  // Get the fields in the nested message.
-  String nested_str = nested.AsString(1);
-  Bytes nested_bytes = nested.AsBytes(2);
-
-  // Parse repeated field `repeated string rep_str = 5;`
-  RepeatedStrings rep_str = message.AsRepeatedString(5);
-  // Iterate through the entries. If proto is malformed when
-  // iterating, the next element (`str` in this case) will be invalid
-  // and loop will end in the iteration after.
-  for (String element : rep_str) {
-    // Check status
-    if (!str.ok()) {
-      // In the case of error, loop will end in the next iteration if
-      // continues. This is the chance for code to catch the error.
-    }
-    // Process str
-  }
-
-  // Parse repeated field `repeated Nested rep_nested = 6;`
-  RepeatedStrings rep_str = message.AsRepeatedString(6);
-  // Iterate through the entries. For iteration
-  for (Message element : rep_rep_nestedstr) {
-    // Check status
-    if (!element.ok()) {
-      // In the case of error, loop will end in the next iteration if
-      // continues. This is the chance for code to catch the error.
-    }
-    // Process element
-  }
-
-  // Parse map field `map<string, bytes> str_to_bytes = 7;`
-  StringToBytesMap str_to_bytes = message.AsStringToBytesMap(7);
-  // Access the entry by a given key value
-  Bytes bytes_for_key = str_to_bytes["key"];
-  // Or iterate through map entries
-  for (StringToBytesMapEntry entry : str_to_bytes) {
-    // Check status
-    if (!entry.ok()) {
-      // In the case of error, loop will end in the next iteration if
-      // continues. This is the chance for code to catch the error.
-    }
-    String key = entry.Key();
-    Bytes value = entry.Value();
-    // process entry
-  }
-
-  // Parse map field `map<string, Nested> str_to_nested = 8;`
-  StringToMessageMap str_to_nested = message.AsStringToBytesMap(8);
-  // Access the entry by a given key value
-  Message nested_for_key = str_to_nested["key"];
-  // Or iterate through map entries
-  for (StringToMessageMapEntry entry : str_to_nested) {
-    // Check status
-    if (!entry.ok()) {
-      // In the case of error, loop will end in the next iteration if
-      // continues. This is the chance for code to catch the error.
-      // However it is still recommended that the user breaks here.
-      break;
-    }
-    String key = entry.Key();
-    Message value = entry.Value();
-    // process entry
-  }
-
-The methods in ``Message`` for parsing a single field, i.e. everty `AsXXX()`
-method except AsRepeatedXXX() and AsStringMapXXX(), internally performs a
-linear scan of the entire proto message to find the field with the given
-field number. This can be expensive if performed multiple times, especially
-on slow reader. The same applies to the ``operator[]`` of StringToXXXXMap
-helper class. Therefore, for performance consideration, whenever possible, it
-is recommended to use the following for-range style to iterate and process
-single fields directly.
-
-
-.. code-block:: c++
-
-  for (Message::Field field : message) {
-    // Check status
-    if (!field.ok()) {
-      // In the case of error, loop will end in the next iteration if
-      // continues. This is the chance for code to catch the error.
-    }
-    if (field.field_number() == 1) {
-      Uint32 integer = field.As<Uint32>();
-      ...
-    } else if (field.field_number() == 2) {
-      String str = field.As<String>();
-      ...
-    } else if (field.field_number() == 3) {
-      Bytes bytes = field.As<Bytes>();
-      ...
-    } else if (field.field_number() == 4) {
-      Message nested = field.As<Message>();
-      ...
-    }
-  }
-
-
-.. Note::
-  The helper API are currently in-development and may not remain stable.
-
--------
-Codegen
--------
-
-pw_protobuf codegen integration is supported in GN, Bazel, and CMake.
-The codegen is a light wrapper around the ``StreamEncoder``, ``MemoryEncoder``,
-and ``StreamDecoder`` objects, providing named helper functions to write and
-read proto fields rather than requiring that field numbers are directly passed
-to an encoder.
-
-All generated messages provide a ``Fields`` enum that can be used directly for
-out-of-band encoding, or with the ``pw::protobuf::Decoder``.
-
-This module's codegen is available through the ``*.pwpb`` sub-target of a
-``pw_proto_library`` in GN, CMake, and Bazel. See :ref:`pw_protobuf_compiler's
-documentation <module-pw_protobuf_compiler>` for more information on build
-system integration for pw_protobuf codegen.
-
-Example ``BUILD.gn``:
-
-.. Code:: none
-
-  import("//build_overrides/pigweed.gni")
-
-  import("$dir_pw_build/target_types.gni")
-  import("$dir_pw_protobuf_compiler/proto.gni")
-
-  # This target controls where the *.pwpb.h headers end up on the include path.
-  # In this example, it's at "pet_daycare_protos/client.pwpb.h".
-  pw_proto_library("pet_daycare_protos") {
-    sources = [
-      "pet_daycare_protos/client.proto",
-    ]
-  }
-
-  pw_source_set("example_client") {
-    sources = [ "example_client.cc" ]
-    deps = [
-      ":pet_daycare_protos.pwpb",
-      dir_pw_bytes,
-      dir_pw_stream,
-    ]
-  }
-
-  pw_source_set("example_server") {
-    sources = [ "example_server.cc" ]
-    deps = [
-      ":pet_daycare_protos.pwpb",
-      dir_pw_bytes,
-      dir_pw_stream,
-    ]
-  }
-
-Example ``pet_daycare_protos/client.proto``:
-
-.. Code:: none
-
-  syntax = "proto3";
-  // The proto package controls the namespacing of the codegen. If this package
-  // were fuzzy.friends, the namespace for codegen would be fuzzy::friends::*.
-  package fuzzy_friends;
-
-  message Pet {
-    string name = 1;
-    string pet_type = 2;
-  }
-
-  message Client {
-    repeated Pet pets = 1;
-  }
-
-Example ``example_client.cc``:
-
-.. Code:: cpp
-
-  #include "pet_daycare_protos/client.pwpb.h"
-  #include "pw_protobuf/encoder.h"
-  #include "pw_stream/sys_io_stream.h"
-  #include "pw_bytes/span.h"
-
-  pw::stream::SysIoWriter sys_io_writer;
-  std::byte submessage_scratch_buffer[64];
-  // The constructor is the same as a pw::protobuf::StreamEncoder.
-  fuzzy_friends::Client::StreamEncoder client(sys_io_writer,
-                                              submessage_scratch_buffer);
-  {
-    fuzzy_friends::Pet::StreamEncoder pet1 = client.GetPetsEncoder();
-    pet1.WriteName("Spot");
-    pet1.WritePetType("dog");
-  }
-
-  {
-    fuzzy_friends::Pet::StreamEncoder pet2 = client.GetPetsEncoder();
-    pet2.WriteName("Slippers");
-    pet2.WritePetType("rabbit");
-  }
-
-  if (!client.status().ok()) {
-    PW_LOG_INFO("Failed to encode proto; %s", client.status().str());
-  }
-
-Example ``example_server.cc``:
-
-.. Code:: cpp
-
-  #include "pet_daycare_protos/client.pwpb.h"
-  #include "pw_protobuf/stream_decoder.h"
-  #include "pw_stream/sys_io_stream.h"
-  #include "pw_bytes/span.h"
-
-  pw::stream::SysIoReader sys_io_reader;
-  // The constructor is the same as a pw::protobuf::StreamDecoder.
-  fuzzy_friends::Client::StreamDecoder client(sys_io_reader);
-  while (client.Next().ok()) {
-    switch (client.Field().value) {
-      case fuzzy_friends::Client::Fields::PET: {
-        std::array<char, 32> name{};
-        std::array<char, 32> pet_type{};
-
-        fuzzy_friends::Pet::StreamDecoder pet = client.GetPetsDecoder();
-        while (pet.Next().ok()) {
-          switch (pet.Field().value) {
-            case fuzzy_friends::Pet::NAME:
-              pet.ReadName(name);
-              break;
-            case fuzzy_friends::Pet::TYPE:
-              pet.ReadPetType(pet_type);
-              break;
-          }
-        }
-
-        break;
-      }
-    }
-  }
-
-  if (!client.status().ok()) {
-    PW_LOG_INFO("Failed to decode proto; %s", client.status().str());
-  }
-
-Enums
-=====
-Namespaced proto enums are generated, and used as the arguments when writing
-enum fields of a proto message. When reading enum fields of a proto message,
-the enum value is validated and returned as the correct type, or
-``Status::DataLoss()`` if the decoded enum value was not given in the proto.
-
-Repeated Fields
-===============
-For encoding, the wrappers provide a `WriteFieldName` method with three
-signatures. One that encodes a single value at a time, one that encodes a packed
-field from a `std::span<Type>`, and one that encodes a packed field from a
-`pw::Vector<Type>`. All three return `Status`.
-
-For decoding, the wrappers provide a `ReadFieldName` method with three
-signatures. One that reads a single value at a time, returning a `Result<Type>`,
-one that reads a packed field into a `std::span<Type>` and returning a
-`StatusWithSize`, and one that supports all formats reading into a
-`pw::Vector<Type>` and returning `Status`.
-
------------
-Size report
------------
-
-Full size report
-================
-
-This report demonstrates the size of using the entire decoder with all of its
-decode methods and a decode callback for a proto message containing each of the
-protobuf field types.
-
-.. include:: size_report/decoder_full
-
-
-Incremental size report
-=======================
-
-This report is generated using the full report as a base and adding some int32
-fields to the decode callback to demonstrate the incremental cost of decoding
-fields in a message.
-
-.. include:: size_report/decoder_incremental
-
----------------------------
-Serialized size calculation
----------------------------
-``pw_protobuf/serialized_size.h`` provides a set of functions for calculating
-how much memory serialized protocol buffer fields require. The
-``kMaxSizeBytes*`` variables provide the maximum encoded sizes of each field
-type. The ``SizeOfField*()`` functions calculate the encoded size of a field of
-the specified type, given a particular key and, for variable length fields
-(varint or delimited), a value. The ``SizeOf*Field`` functions calculate the
-encoded size of fields with a particular wire format (delimited, varint).
-
---------------------------
-Available protobuf modules
---------------------------
-There are a handful of messages ready to be used in Pigweed projects. These are
-located in ``pw_protobuf/pw_protobuf_protos``.
-
-common.proto
-============
-Contains Empty message proto used in many RPC calls.
-
-
-status.proto
-============
-Contains the enum for pw::Status.
-
-.. Note::
- ``pw::protobuf::StatusCode`` values should not be used outside of a .proto
- file. Instead, the StatusCodes should be converted to the Status type in the
- language. In C++, this would be:
-
-  .. code-block:: c++
-
-    // Reading from a proto
-    pw::Status status = static_cast<pw::Status::Code>(proto.status_field));
-    // Writing to a proto
-    proto.status_field = static_cast<pw::protobuf::StatusCode>(status.code()));
+  decoding
 
-----------------------------------------
 Comparison with other protobuf libraries
-----------------------------------------
+========================================
 
 protobuf-lite
-=============
+^^^^^^^^^^^^^
 protobuf-lite is the official reduced-size C++ implementation of protobuf. It
 uses a restricted subset of the protobuf library's features to minimize code
 size. However, is is still around 150K in size and requires dynamic memory
 allocation, making it unsuitable for many embedded systems.
 
 nanopb
-======
+^^^^^^
 `nanopb <https://github.com/nanopb/nanopb>`_ is a commonly used embedded
 protobuf library with very small code size and full code generation. It provides
 both encoding/decoding functionality and in-memory C structs representing
diff --git a/pw_protobuf/encoder.cc b/pw_protobuf/encoder.cc
index 156469a..6ca6f6d 100644
--- a/pw_protobuf/encoder.cc
+++ b/pw_protobuf/encoder.cc
@@ -14,219 +14,203 @@
 
 #include "pw_protobuf/encoder.h"
 
-#include <cstddef>
-#include <cstring>
-#include <span>
-
-#include "pw_assert/check.h"
-#include "pw_bytes/span.h"
-#include "pw_protobuf/serialized_size.h"
-#include "pw_protobuf/wire_format.h"
-#include "pw_status/status.h"
-#include "pw_status/try.h"
-#include "pw_stream/memory_stream.h"
-#include "pw_stream/stream.h"
-#include "pw_varint/varint.h"
+#include <limits>
 
 namespace pw::protobuf {
 
-StreamEncoder StreamEncoder::GetNestedEncoder(uint32_t field_number) {
-  PW_CHECK(!nested_encoder_open());
-  PW_CHECK(ValidFieldNumber(field_number));
-
-  nested_field_number_ = field_number;
-
-  // Pass the unused space of the scratch buffer to the nested encoder to use
-  // as their scratch buffer.
-  size_t key_size =
-      varint::EncodedSize(FieldKey(field_number, WireType::kDelimited));
-  size_t reserved_size = key_size + config::kMaxVarintSize;
-  size_t max_size = std::min(memory_writer_.ConservativeWriteLimit(),
-                             writer_.ConservativeWriteLimit());
-  // Account for reserved bytes.
-  max_size = max_size > reserved_size ? max_size - reserved_size : 0;
-  // Cap based on max varint size.
-  max_size = std::min(varint::MaxValueInBytes(config::kMaxVarintSize),
-                      static_cast<uint64_t>(max_size));
-
-  ByteSpan nested_buffer;
-  if (max_size > 0) {
-    nested_buffer = ByteSpan(
-        memory_writer_.data() + reserved_size + memory_writer_.bytes_written(),
-        max_size);
-  } else {
-    nested_buffer = ByteSpan();
-  }
-  return StreamEncoder(*this, nested_buffer);
+Status Encoder::WriteUint64(uint32_t field_number, uint64_t value) {
+  std::byte* original_cursor = cursor_;
+  WriteFieldKey(field_number, WireType::kVarint);
+  WriteVarint(value);
+  return IncreaseParentSize(cursor_ - original_cursor);
 }
 
-StreamEncoder::~StreamEncoder() {
-  // If this was an invalidated StreamEncoder which cannot be used, permit the
-  // object to be cleanly destructed by doing nothing.
-  if (nested_field_number_ == kFirstReservedNumber) {
-    return;
+// Encodes a base-128 varint to the buffer.
+Status Encoder::WriteVarint(uint64_t value) {
+  if (!encode_status_.ok()) {
+    return encode_status_;
   }
 
-  PW_CHECK(
-      !nested_encoder_open(),
-      "Tried to destruct a proto encoder with an active submessage encoder");
-
-  if (parent_ != nullptr) {
-    parent_->CloseNestedMessage(*this);
+  std::span varint_buf = buffer_.last(RemainingSize());
+  if (varint_buf.empty()) {
+    encode_status_ = Status::ResourceExhausted();
+    return encode_status_;
   }
+
+  size_t written = pw::varint::EncodeLittleEndianBase128(value, varint_buf);
+  if (written == 0) {
+    encode_status_ = Status::ResourceExhausted();
+    return encode_status_;
+  }
+
+  cursor_ += written;
+  return OkStatus();
 }
 
-void StreamEncoder::CloseNestedMessage(StreamEncoder& nested) {
-  PW_DCHECK_PTR_EQ(nested.parent_,
-                   this,
-                   "CloseNestedMessage() called on the wrong Encoder parent");
-
-  // Make the nested encoder look like it has an open child to block writes for
-  // the remainder of the object's life.
-  nested.nested_field_number_ = kFirstReservedNumber;
-  nested.parent_ = nullptr;
-  // Temporarily cache the field number of the child so we can re-enable
-  // writing to this encoder.
-  uint32_t temp_field_number = nested_field_number_;
-  nested_field_number_ = 0;
-
-  // TODO(amontanez): If a submessage fails, we could optionally discard
-  // it and continue happily. For now, we'll always invalidate the entire
-  // encoder if a single submessage fails.
-  status_.Update(nested.status_);
-  if (!status_.ok()) {
-    return;
+Status Encoder::WriteRawBytes(const std::byte* ptr, size_t size) {
+  if (!encode_status_.ok()) {
+    return encode_status_;
   }
 
-  if (varint::EncodedSize(nested.memory_writer_.bytes_written()) >
-      config::kMaxVarintSize) {
-    status_ = Status::OutOfRange();
-    return;
+  if (size > RemainingSize()) {
+    encode_status_ = Status::ResourceExhausted();
+    return encode_status_;
   }
 
-  status_ = WriteLengthDelimitedField(temp_field_number,
-                                      nested.memory_writer_.WrittenData());
+  // Memmove the value into place as it's possible that it shares the encode
+  // buffer on a memory-constrained system.
+  std::memmove(cursor_, ptr, size);
+
+  cursor_ += size;
+  return OkStatus();
 }
 
-Status StreamEncoder::WriteVarintField(uint32_t field_number, uint64_t value) {
-  PW_TRY(UpdateStatusForWrite(
-      field_number, WireType::kVarint, varint::EncodedSize(value)));
+Status Encoder::Push(uint32_t field_number) {
+  if (!encode_status_.ok()) {
+    return encode_status_;
+  }
 
-  WriteVarint(FieldKey(field_number, WireType::kVarint))
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  return WriteVarint(value);
+  if (blob_count_ == blob_locations_.size() || depth_ == blob_stack_.size()) {
+    encode_status_ = Status::ResourceExhausted();
+    return encode_status_;
+  }
+
+  // Write the key for the nested field.
+  std::byte* original_cursor = cursor_;
+  if (Status status = WriteFieldKey(field_number, WireType::kDelimited);
+      !status.ok()) {
+    encode_status_ = status;
+    return status;
+  }
+
+  if (sizeof(SizeType) > RemainingSize()) {
+    // Rollback if there isn't enough space.
+    cursor_ = original_cursor;
+    encode_status_ = Status::ResourceExhausted();
+    return encode_status_;
+  }
+
+  // Update parent size with the written key.
+  PW_TRY(IncreaseParentSize(cursor_ - original_cursor));
+
+  union {
+    std::byte* cursor;
+    SizeType* size_cursor;
+  };
+
+  // Create a size entry for the new blob and append it to both the nesting
+  // stack and location list.
+  cursor = cursor_;
+  *size_cursor = 0;
+  blob_locations_[blob_count_++] = size_cursor;
+  blob_stack_[depth_++] = size_cursor;
+
+  cursor_ += sizeof(*size_cursor);
+  return OkStatus();
 }
 
-Status StreamEncoder::WriteLengthDelimitedField(uint32_t field_number,
-                                                ConstByteSpan data) {
-  PW_TRY(UpdateStatusForWrite(field_number, WireType::kDelimited, data.size()));
-  status_.Update(WriteLengthDelimitedKeyAndLengthPrefix(
-      field_number, data.size(), writer_));
-  PW_TRY(status_);
-  if (Status status = writer_.Write(data); !status.ok()) {
-    status_ = status;
+Status Encoder::Pop() {
+  if (!encode_status_.ok()) {
+    return encode_status_;
   }
-  return status_;
-}
 
-Status StreamEncoder::WriteLengthDelimitedFieldFromStream(
-    uint32_t field_number,
-    stream::Reader& bytes_reader,
-    size_t num_bytes,
-    ByteSpan stream_pipe_buffer) {
-  PW_CHECK_UINT_GT(
-      stream_pipe_buffer.size(), 0, "Transfer buffer cannot be 0 size");
-  PW_TRY(UpdateStatusForWrite(field_number, WireType::kDelimited, num_bytes));
-  status_.Update(
-      WriteLengthDelimitedKeyAndLengthPrefix(field_number, num_bytes, writer_));
-  PW_TRY(status_);
-
-  // Stream data from `bytes_reader` to `writer_`.
-  // TODO(pwbug/468): move the following logic to pw_stream/copy.h at a later
-  // time.
-  for (size_t bytes_written = 0; bytes_written < num_bytes;) {
-    const size_t chunk_size_bytes =
-        std::min(num_bytes - bytes_written, stream_pipe_buffer.size_bytes());
-    const Result<ByteSpan> read_result =
-        bytes_reader.Read(stream_pipe_buffer.data(), chunk_size_bytes);
-    status_.Update(read_result.status());
-    PW_TRY(status_);
-
-    status_.Update(writer_.Write(read_result.value()));
-    PW_TRY(status_);
-
-    bytes_written += read_result.value().size();
+  if (depth_ == 0) {
+    encode_status_ = Status::FailedPrecondition();
+    return encode_status_;
   }
 
+  // Update the parent's size with how much total space the child will take
+  // after its size field is varint encoded.
+  SizeType child_size = *blob_stack_[--depth_];
+  PW_TRY(IncreaseParentSize(child_size + VarintSizeBytes(child_size)));
+
+  // Encode the child
+  if (Status status = EncodeFrom(blob_count_ - 1).status(); !status.ok()) {
+    encode_status_ = status;
+    return encode_status_;
+  }
+  blob_count_--;
+
   return OkStatus();
 }
 
-Status StreamEncoder::WriteFixed(uint32_t field_number, ConstByteSpan data) {
-  WireType type =
-      data.size() == sizeof(uint32_t) ? WireType::kFixed32 : WireType::kFixed64;
+Result<ConstByteSpan> Encoder::Encode() { return EncodeFrom(0); }
 
-  PW_TRY(UpdateStatusForWrite(field_number, type, data.size()));
-
-  WriteVarint(FieldKey(field_number, type))
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  if (Status status = writer_.Write(data); !status.ok()) {
-    status_ = status;
-  }
-  return status_;
-}
-
-Status StreamEncoder::WritePackedFixed(uint32_t field_number,
-                                       std::span<const std::byte> values,
-                                       size_t elem_size) {
-  if (values.empty()) {
-    return status_;
+Result<ConstByteSpan> Encoder::EncodeFrom(size_t blob) {
+  if (!encode_status_.ok()) {
+    return encode_status_;
   }
 
-  PW_CHECK_NOTNULL(values.data());
-  PW_DCHECK(elem_size == sizeof(uint32_t) || elem_size == sizeof(uint64_t));
+  if (blob >= blob_count_) {
+    // If there are no nested blobs, the buffer already contains a valid proto.
+    return Result<ConstByteSpan>(buffer_.first(EncodedSize()));
+  }
 
-  PW_TRY(UpdateStatusForWrite(
-      field_number, WireType::kDelimited, values.size_bytes()));
-  WriteVarint(FieldKey(field_number, WireType::kDelimited))
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  WriteVarint(values.size_bytes())
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  union {
+    std::byte* read_cursor;
+    SizeType* size_cursor;
+  };
 
-  for (auto val_start = values.begin(); val_start != values.end();
-       val_start += elem_size) {
-    // Allocates 8 bytes so both 4-byte and 8-byte types can be encoded as
-    // little-endian for serialization.
-    std::array<std::byte, sizeof(uint64_t)> data;
-    if (std::endian::native == std::endian::little) {
-      std::copy(val_start, val_start + elem_size, std::begin(data));
+  // Starting from the first blob, encode each size field as a varint and
+  // shift all subsequent data downwards.
+  size_cursor = blob_locations_[blob];
+  std::byte* write_cursor = read_cursor;
+
+  while (read_cursor < cursor_) {
+    SizeType nested_size = *size_cursor;
+
+    std::span<std::byte> varint_buf(write_cursor, sizeof(*size_cursor));
+    size_t varint_size =
+        pw::varint::EncodeLittleEndianBase128(nested_size, varint_buf);
+
+    // Place the write cursor after the encoded varint and the read cursor at
+    // the location of the next proto field.
+    write_cursor += varint_size;
+    read_cursor += varint_buf.size();
+
+    size_t to_copy;
+
+    if (blob == blob_count_ - 1) {
+      to_copy = cursor_ - read_cursor;
     } else {
-      std::reverse_copy(val_start, val_start + elem_size, std::begin(data));
+      std::byte* end = reinterpret_cast<std::byte*>(blob_locations_[blob + 1]);
+      to_copy = end - read_cursor;
     }
-    status_.Update(writer_.Write(std::span(data).first(elem_size)));
-    PW_TRY(status_);
+
+    std::memmove(write_cursor, read_cursor, to_copy);
+    write_cursor += to_copy;
+    read_cursor += to_copy;
+
+    ++blob;
   }
-  return status_;
+
+  // Point the cursor to the end of the encoded proto.
+  cursor_ = write_cursor;
+  return Result<ConstByteSpan>(buffer_.first(EncodedSize()));
 }
 
-Status StreamEncoder::UpdateStatusForWrite(uint32_t field_number,
-                                           WireType type,
-                                           size_t data_size) {
-  PW_CHECK(!nested_encoder_open());
-  PW_TRY(status_);
-
-  if (!ValidFieldNumber(field_number)) {
-    return status_ = Status::InvalidArgument();
+Status Encoder::IncreaseParentSize(size_t size_bytes) {
+  if (!encode_status_.ok()) {
+    return encode_status_;
   }
 
-  const Result<size_t> field_size = SizeOfField(field_number, type, data_size);
-  status_.Update(field_size.status());
-  PW_TRY(status_);
-
-  if (field_size.value() > writer_.ConservativeWriteLimit()) {
-    status_ = Status::ResourceExhausted();
+  if (depth_ == 0) {
+    return OkStatus();
   }
 
-  return status_;
+  size_t current_size = *blob_stack_[depth_ - 1];
+
+  constexpr size_t max_size =
+      std::min(varint::MaxValueInBytes(sizeof(SizeType)),
+               static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()));
+
+  if (size_bytes > max_size || current_size > max_size - size_bytes) {
+    encode_status_ = Status::OutOfRange();
+    return encode_status_;
+  }
+
+  *blob_stack_[depth_ - 1] = current_size + size_bytes;
+  return OkStatus();
 }
 
 }  // namespace pw::protobuf
diff --git a/pw_protobuf/encoder_fuzzer.cc b/pw_protobuf/encoder_fuzzer.cc
index 421af65..1ede047 100644
--- a/pw_protobuf/encoder_fuzzer.cc
+++ b/pw_protobuf/encoder_fuzzer.cc
@@ -12,14 +12,15 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
+#include <pw_fuzzer/asan_interface.h>
+#include <pw_fuzzer/fuzzed_data_provider.h>
+
 #include <cstddef>
 #include <cstdint>
 #include <cstring>
 #include <span>
 #include <vector>
 
-#include "pw_fuzzer/asan_interface.h"
-#include "pw_fuzzer/fuzzed_data_provider.h"
 #include "pw_protobuf/encoder.h"
 
 namespace {
@@ -27,7 +28,8 @@
 // Encodable values. The fuzzer will iteratively choose different field types to
 // generate and encode.
 enum FieldType : uint8_t {
-  kUint32 = 0,
+  kEncodeAndClear = 0,
+  kUint32,
   kPackedUint32,
   kUint64,
   kPackedUint64,
@@ -55,7 +57,8 @@
   kBytes,
   kString,
   kPush,
-  kMaxValue = kPush,
+  kPop,
+  kMaxValue = kPop,
 };
 
 // TODO(pwbug/181): Move this to pw_fuzzer/fuzzed_data_provider.h
@@ -129,7 +132,7 @@
   size_t poisoned_length = sizeof(buffer) - unpoisoned_length;
   ASAN_POISON_MEMORY_REGION(poisoned, poisoned_length);
 
-  pw::protobuf::MemoryEncoder encoder(unpoisoned);
+  pw::protobuf::NestedEncoder encoder(unpoisoned);
 
   // Storage for generated spans
   std::vector<uint32_t> u32s;
@@ -148,174 +151,133 @@
   // set of inputs to the encoder to ensure it doesn't misbehave.
   while (provider.remaining_bytes() != 0) {
     switch (provider.ConsumeEnum<FieldType>()) {
+      case kEncodeAndClear:
+        // Special "field". Encode all the fields so far and reset the encoder.
+        encoder.Encode();
+        encoder.Clear();
+        break;
       case kUint32:
-        encoder
-            .WriteUint32(provider.ConsumeIntegral<uint32_t>(),
-                         provider.ConsumeIntegral<uint32_t>())
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        encoder.WriteUint32(provider.ConsumeIntegral<uint32_t>(),
+                            provider.ConsumeIntegral<uint32_t>());
         break;
       case kPackedUint32:
-        encoder
-            .WritePackedUint32(provider.ConsumeIntegral<uint32_t>(),
-                               ConsumeSpan<uint32_t>(&provider, &u32s))
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        encoder.WritePackedUint32(provider.ConsumeIntegral<uint32_t>(),
+                                  ConsumeSpan<uint32_t>(&provider, &u32s));
         break;
       case kUint64:
-        encoder
-            .WriteUint64(provider.ConsumeIntegral<uint32_t>(),
-                         provider.ConsumeIntegral<uint64_t>())
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        encoder.WriteUint64(provider.ConsumeIntegral<uint32_t>(),
+                            provider.ConsumeIntegral<uint64_t>());
         break;
       case kPackedUint64:
-        encoder
-            .WritePackedUint64(provider.ConsumeIntegral<uint32_t>(),
-                               ConsumeSpan<uint64_t>(&provider, &u64s))
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        encoder.WritePackedUint64(provider.ConsumeIntegral<uint32_t>(),
+                                  ConsumeSpan<uint64_t>(&provider, &u64s));
         break;
       case kInt32:
-        encoder
-            .WriteInt32(provider.ConsumeIntegral<uint32_t>(),
-                        provider.ConsumeIntegral<int32_t>())
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        encoder.WriteInt32(provider.ConsumeIntegral<uint32_t>(),
+                           provider.ConsumeIntegral<int32_t>());
         break;
       case kPackedInt32:
-        encoder
-            .WritePackedInt32(provider.ConsumeIntegral<uint32_t>(),
-                              ConsumeSpan<int32_t>(&provider, &s32s))
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        encoder.WritePackedInt32(provider.ConsumeIntegral<uint32_t>(),
+                                 ConsumeSpan<int32_t>(&provider, &s32s));
         break;
       case kInt64:
-        encoder
-            .WriteInt64(provider.ConsumeIntegral<uint32_t>(),
-                        provider.ConsumeIntegral<int64_t>())
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        encoder.WriteInt64(provider.ConsumeIntegral<uint32_t>(),
+                           provider.ConsumeIntegral<int64_t>());
         break;
       case kPackedInt64:
-        encoder
-            .WritePackedInt64(provider.ConsumeIntegral<uint32_t>(),
-                              ConsumeSpan<int64_t>(&provider, &s64s))
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        encoder.WritePackedInt64(provider.ConsumeIntegral<uint32_t>(),
+                                 ConsumeSpan<int64_t>(&provider, &s64s));
         break;
       case kSint32:
-        encoder
-            .WriteSint32(provider.ConsumeIntegral<uint32_t>(),
-                         provider.ConsumeIntegral<int32_t>())
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        encoder.WriteSint32(provider.ConsumeIntegral<uint32_t>(),
+                            provider.ConsumeIntegral<int32_t>());
         break;
       case kPackedSint32:
-        encoder
-            .WritePackedSint32(provider.ConsumeIntegral<uint32_t>(),
-                               ConsumeSpan<int32_t>(&provider, &s32s))
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        encoder.WritePackedSint32(provider.ConsumeIntegral<uint32_t>(),
+                                  ConsumeSpan<int32_t>(&provider, &s32s));
         break;
       case kSint64:
-        encoder
-            .WriteSint64(provider.ConsumeIntegral<uint32_t>(),
-                         provider.ConsumeIntegral<int64_t>())
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        encoder.WriteSint64(provider.ConsumeIntegral<uint32_t>(),
+                            provider.ConsumeIntegral<int64_t>());
         break;
       case kPackedSint64:
-        encoder
-            .WritePackedSint64(provider.ConsumeIntegral<uint32_t>(),
-                               ConsumeSpan<int64_t>(&provider, &s64s))
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        encoder.WritePackedSint64(provider.ConsumeIntegral<uint32_t>(),
+                                  ConsumeSpan<int64_t>(&provider, &s64s));
         break;
       case kBool:
-        encoder
-            .WriteBool(provider.ConsumeIntegral<uint32_t>(),
-                       provider.ConsumeBool())
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        encoder.WriteBool(provider.ConsumeIntegral<uint32_t>(),
+                          provider.ConsumeBool());
         break;
       case kFixed32:
-        encoder
-            .WriteFixed32(provider.ConsumeIntegral<uint32_t>(),
-                          provider.ConsumeIntegral<uint32_t>())
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        encoder.WriteFixed32(provider.ConsumeIntegral<uint32_t>(),
+                             provider.ConsumeIntegral<uint32_t>());
         break;
       case kPackedFixed32:
-        encoder
-            .WritePackedFixed32(provider.ConsumeIntegral<uint32_t>(),
-                                ConsumeSpan<uint32_t>(&provider, &u32s))
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        encoder.WritePackedFixed32(provider.ConsumeIntegral<uint32_t>(),
+                                   ConsumeSpan<uint32_t>(&provider, &u32s));
         break;
       case kFixed64:
-        encoder
-            .WriteFixed64(provider.ConsumeIntegral<uint32_t>(),
-                          provider.ConsumeIntegral<uint64_t>())
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        encoder.WriteFixed64(provider.ConsumeIntegral<uint32_t>(),
+                             provider.ConsumeIntegral<uint64_t>());
         break;
       case kPackedFixed64:
-        encoder
-            .WritePackedFixed64(provider.ConsumeIntegral<uint32_t>(),
-                                ConsumeSpan<uint64_t>(&provider, &u64s))
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        encoder.WritePackedFixed64(provider.ConsumeIntegral<uint32_t>(),
+                                   ConsumeSpan<uint64_t>(&provider, &u64s));
         break;
       case kSfixed32:
-        encoder
-            .WriteSfixed32(provider.ConsumeIntegral<uint32_t>(),
-                           provider.ConsumeIntegral<int32_t>())
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        encoder.WriteSfixed32(provider.ConsumeIntegral<uint32_t>(),
+                              provider.ConsumeIntegral<int32_t>());
         break;
       case kPackedSfixed32:
-        encoder
-            .WritePackedSfixed32(provider.ConsumeIntegral<uint32_t>(),
-                                 ConsumeSpan<int32_t>(&provider, &s32s))
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        encoder.WritePackedSfixed32(provider.ConsumeIntegral<uint32_t>(),
+                                    ConsumeSpan<int32_t>(&provider, &s32s));
         break;
       case kSfixed64:
-        encoder
-            .WriteSfixed64(provider.ConsumeIntegral<uint32_t>(),
-                           provider.ConsumeIntegral<int64_t>())
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        encoder.WriteSfixed64(provider.ConsumeIntegral<uint32_t>(),
+                              provider.ConsumeIntegral<int64_t>());
         break;
       case kPackedSfixed64:
-        encoder
-            .WritePackedSfixed64(provider.ConsumeIntegral<uint32_t>(),
-                                 ConsumeSpan<int64_t>(&provider, &s64s))
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        encoder.WritePackedSfixed64(provider.ConsumeIntegral<uint32_t>(),
+                                    ConsumeSpan<int64_t>(&provider, &s64s));
         break;
       case kFloat:
-        encoder
-            .WriteFloat(provider.ConsumeIntegral<uint32_t>(),
-                        provider.ConsumeFloatingPoint<float>())
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        encoder.WriteFloat(provider.ConsumeIntegral<uint32_t>(),
+                           provider.ConsumeFloatingPoint<float>());
         break;
       case kPackedFloat:
-        encoder
-            .WritePackedFloat(provider.ConsumeIntegral<uint32_t>(),
-                              ConsumeSpan<float>(&provider, &floats))
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        encoder.WritePackedFloat(provider.ConsumeIntegral<uint32_t>(),
+                                 ConsumeSpan<float>(&provider, &floats));
         break;
       case kDouble:
-        encoder
-            .WriteDouble(provider.ConsumeIntegral<uint32_t>(),
-                         provider.ConsumeFloatingPoint<double>())
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        encoder.WriteDouble(provider.ConsumeIntegral<uint32_t>(),
+                            provider.ConsumeFloatingPoint<double>());
         break;
       case kPackedDouble:
-        encoder
-            .WritePackedDouble(provider.ConsumeIntegral<uint32_t>(),
-                               ConsumeSpan<double>(&provider, &doubles))
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        encoder.WritePackedDouble(provider.ConsumeIntegral<uint32_t>(),
+                                  ConsumeSpan<double>(&provider, &doubles));
         break;
       case kBytes:
-        encoder
-            .WriteBytes(provider.ConsumeIntegral<uint32_t>(),
-                        ConsumeBytes(&provider, &bytes))
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        encoder.WriteBytes(provider.ConsumeIntegral<uint32_t>(),
+                           ConsumeBytes(&provider, &bytes));
         break;
       case kString:
-        encoder
-            .WriteString(provider.ConsumeIntegral<uint32_t>(),
-                         ConsumeString(&provider, &strings))
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        encoder.WriteString(provider.ConsumeIntegral<uint32_t>(),
+                            ConsumeString(&provider, &strings));
         break;
       case kPush:
         // Special "field". The marks the start of a nested message.
-        encoder.GetNestedEncoder(provider.ConsumeIntegral<uint32_t>());
+        encoder.Push(provider.ConsumeIntegral<uint32_t>());
+        break;
+      case kPop:
+        // Special "field". this marks the end of a nested message. No attempt
+        // is made to match pushes to pops, in order to test that the encoder
+        // behaves correctly when they are mismatched.
+        encoder.Pop();
         break;
     }
   }
+  // Ensure we call `Encode` at least once.
+  encoder.Encode();
 
   // Don't forget to unpoison for the next iteration!
   ASAN_UNPOISON_MEMORY_REGION(poisoned, poisoned_length);
diff --git a/pw_protobuf/encoder_test.cc b/pw_protobuf/encoder_test.cc
index 6417f69..bb41875 100644
--- a/pw_protobuf/encoder_test.cc
+++ b/pw_protobuf/encoder_test.cc
@@ -1,4 +1,4 @@
-// Copyright 2021 The Pigweed Authors
+// Copyright 2019 The Pigweed Authors
 //
 // 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
@@ -14,17 +14,11 @@
 
 #include "pw_protobuf/encoder.h"
 
-#include <span>
-
 #include "gtest/gtest.h"
-#include "pw_bytes/span.h"
-#include "pw_stream/memory_stream.h"
 
 namespace pw::protobuf {
 namespace {
 
-using stream::MemoryWriter;
-
 // The tests in this file use the following proto message schemas.
 //
 //   message TestProto {
@@ -54,7 +48,6 @@
 constexpr uint32_t kTestProtoRatioField = 4;
 constexpr uint32_t kTestProtoErrorMessageField = 5;
 constexpr uint32_t kTestProtoNestedField = 6;
-constexpr uint32_t kTestProtoPayloadFromStreamField = 7;
 
 constexpr uint32_t kNestedProtoHelloField = 1;
 constexpr uint32_t kNestedProtoIdField = 2;
@@ -63,14 +56,13 @@
 constexpr uint32_t kDoubleNestedProtoKeyField = 1;
 constexpr uint32_t kDoubleNestedProtoValueField = 2;
 
-TEST(StreamEncoder, EncodePrimitives) {
+TEST(Encoder, EncodePrimitives) {
   // TestProto tp;
   // tp.magic_number = 42;
   // tp.ziggy = -13;
   // tp.cycles = 0xdeadbeef8badf00d;
   // tp.ratio = 1.618034;
   // tp.error_message = "broken 💩";
-  // tp.payload_from_stream = "byreader"
 
   // Hand-encoded version of the above.
   // clang-format off
@@ -87,20 +79,13 @@
     0x2a, 0x0b, 'b', 'r', 'o', 'k', 'e', 'n', ' ',
     // poop!
     0xf0, 0x9f, 0x92, 0xa9,
-    // payload_from_stream [delimited k=7]
-    0x3a, 0x08, 'b', 'y', 'r', 'e', 'a', 'd', 'e', 'r',
   };
   // clang-format on
-  std::byte encode_buffer[64];
-  std::byte dest_buffer[64];
-  // This writer isn't necessary, it's just the most testable way to exercise
-  // a stream interface. Use a MemoryEncoder when encoding a proto directly to
-  // an in-memory buffer.
-  MemoryWriter writer(dest_buffer);
-  StreamEncoder encoder(writer, encode_buffer);
+
+  std::byte encode_buffer[32];
+  NestedEncoder encoder(encode_buffer);
 
   EXPECT_EQ(encoder.WriteUint32(kTestProtoMagicNumberField, 42), OkStatus());
-  EXPECT_EQ(writer.bytes_written(), 2u);
   EXPECT_EQ(encoder.WriteSint32(kTestProtoZiggyField, -13), OkStatus());
   EXPECT_EQ(encoder.WriteFixed64(kTestProtoCyclesField, 0xdeadbeef8badf00d),
             OkStatus());
@@ -108,25 +93,17 @@
   EXPECT_EQ(encoder.WriteString(kTestProtoErrorMessageField, "broken 💩"),
             OkStatus());
 
-  const std::string_view kReaderMessage = "byreader";
-  stream::MemoryReader msg_reader(std::as_bytes(std::span(kReaderMessage)));
-  std::byte stream_pipe_buffer[1];
-  EXPECT_EQ(encoder.WriteStringFromStream(kTestProtoPayloadFromStreamField,
-                                          msg_reader,
-                                          kReaderMessage.size(),
-                                          stream_pipe_buffer),
-            OkStatus());
-
-  ASSERT_EQ(encoder.status(), OkStatus());
-  ConstByteSpan result = writer.WrittenData();
-  EXPECT_EQ(result.size(), sizeof(encoded_proto));
-  EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
-            0);
+  Result result = encoder.Encode();
+  ASSERT_EQ(result.status(), OkStatus());
+  EXPECT_EQ(result.value().size(), sizeof(encoded_proto));
+  EXPECT_EQ(
+      std::memcmp(result.value().data(), encoded_proto, sizeof(encoded_proto)),
+      0);
 }
 
-TEST(StreamEncoder, EncodeInsufficientSpace) {
+TEST(Encoder, EncodeInsufficientSpace) {
   std::byte encode_buffer[12];
-  MemoryEncoder encoder(encode_buffer);
+  NestedEncoder encoder(encode_buffer);
 
   // 2 bytes.
   EXPECT_EQ(encoder.WriteUint32(kTestProtoMagicNumberField, 42), OkStatus());
@@ -140,38 +117,28 @@
   EXPECT_EQ(encoder.WriteFloat(kTestProtoRatioField, 1.618034),
             Status::ResourceExhausted());
 
-  ASSERT_EQ(encoder.status(), Status::ResourceExhausted());
+  ASSERT_EQ(encoder.Encode().status(), Status::ResourceExhausted());
 }
 
-TEST(StreamEncoder, EncodeInvalidArguments) {
+TEST(Encoder, EncodeInvalidArguments) {
   std::byte encode_buffer[12];
-  MemoryEncoder encoder(encode_buffer);
+  NestedEncoder encoder(encode_buffer);
 
   EXPECT_EQ(encoder.WriteUint32(kTestProtoMagicNumberField, 42), OkStatus());
   // Invalid proto field numbers.
   EXPECT_EQ(encoder.WriteUint32(0, 1337), Status::InvalidArgument());
-
-  // TODO(amontanez): Does it make sense to support this?
-  // encoder.Clear();
+  encoder.Clear();
 
   EXPECT_EQ(encoder.WriteString(1u << 31, "ha"), Status::InvalidArgument());
-
-  // TODO(amontanez): Does it make sense to support this?
-  // encoder.Clear();
+  encoder.Clear();
 
   EXPECT_EQ(encoder.WriteBool(19091, false), Status::InvalidArgument());
-  ASSERT_EQ(encoder.status(), Status::InvalidArgument());
+  ASSERT_EQ(encoder.Encode().status(), Status::InvalidArgument());
 }
 
-TEST(StreamEncoder, Nested) {
-  // This is the largest complete submessage in this test.
-  constexpr size_t kLargestSubmessageSize = 0x30;
-  constexpr size_t kScratchBufferSize =
-      MaxScratchBufferSize(kLargestSubmessageSize, 2);
-  std::byte encode_buffer[kScratchBufferSize];
-  std::byte dest_buffer[128];
-  MemoryWriter writer(dest_buffer);
-  StreamEncoder encoder(writer, encode_buffer);
+TEST(Encoder, Nested) {
+  std::byte encode_buffer[128];
+  NestedEncoder<5, 5> encoder(encode_buffer);
 
   // TestProto test_proto;
   // test_proto.magic_number = 42;
@@ -179,44 +146,40 @@
 
   {
     // NestedProto& nested_proto = test_proto.nested;
-    StreamEncoder nested_proto =
-        encoder.GetNestedEncoder(kTestProtoNestedField);
+    EXPECT_EQ(encoder.Push(kTestProtoNestedField), OkStatus());
     // nested_proto.hello = "world";
-    EXPECT_EQ(nested_proto.WriteString(kNestedProtoHelloField, "world"),
-              OkStatus());
+    EXPECT_EQ(encoder.WriteString(kNestedProtoHelloField, "world"), OkStatus());
+    // nested_proto.id = 999;
+    EXPECT_EQ(encoder.WriteUint32(kNestedProtoIdField, 999), OkStatus());
 
     {
       // DoubleNestedProto& double_nested_proto = nested_proto.append_pair();
-      StreamEncoder double_nested_proto =
-          nested_proto.GetNestedEncoder(kNestedProtoPairField);
+      EXPECT_EQ(encoder.Push(kNestedProtoPairField), OkStatus());
       // double_nested_proto.key = "version";
-      EXPECT_EQ(double_nested_proto.WriteString(kDoubleNestedProtoKeyField,
-                                                "version"),
+      EXPECT_EQ(encoder.WriteString(kDoubleNestedProtoKeyField, "version"),
                 OkStatus());
       // double_nested_proto.value = "2.9.1";
-      EXPECT_EQ(double_nested_proto.WriteString(kDoubleNestedProtoValueField,
-                                                "2.9.1"),
+      EXPECT_EQ(encoder.WriteString(kDoubleNestedProtoValueField, "2.9.1"),
                 OkStatus());
-    }  // end DoubleNestedProto
 
-    // nested_proto.id = 999;
-    EXPECT_EQ(nested_proto.WriteUint32(kNestedProtoIdField, 999), OkStatus());
+      EXPECT_EQ(encoder.Pop(), OkStatus());
+    }  // end DoubleNestedProto
 
     {
       // DoubleNestedProto& double_nested_proto = nested_proto.append_pair();
-      StreamEncoder double_nested_proto =
-          nested_proto.GetNestedEncoder(kNestedProtoPairField);
+      EXPECT_EQ(encoder.Push(kNestedProtoPairField), OkStatus());
       // double_nested_proto.key = "device";
-      EXPECT_EQ(
-          double_nested_proto.WriteString(kDoubleNestedProtoKeyField, "device"),
-          OkStatus());
-      // double_nested_proto.value = "left-soc";
-      EXPECT_EQ(double_nested_proto.WriteString(kDoubleNestedProtoValueField,
-                                                "left-soc"),
+      EXPECT_EQ(encoder.WriteString(kDoubleNestedProtoKeyField, "device"),
                 OkStatus());
-      // Rely on destructor for finalization.
+      // double_nested_proto.value = "left-soc";
+      EXPECT_EQ(encoder.WriteString(kDoubleNestedProtoValueField, "left-soc"),
+                OkStatus());
+
+      EXPECT_EQ(encoder.Pop(), OkStatus());
     }  // end DoubleNestedProto
-  }    // end NestedProto
+
+    EXPECT_EQ(encoder.Pop(), OkStatus());
+  }  // end NestedProto
 
   // test_proto.ziggy = -13;
   EXPECT_EQ(encoder.WriteSint32(kTestProtoZiggyField, -13), OkStatus());
@@ -229,14 +192,14 @@
     0x32, 0x30,
     // nested.hello
     0x0a, 0x05, 'w', 'o', 'r', 'l', 'd',
+    // nested.id
+    0x10, 0xe7, 0x07,
     // nested.pair[0] header (key, size)
     0x1a, 0x10,
     // nested.pair[0].key
     0x0a, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n',
     // nested.pair[0].value
     0x12, 0x05, '2', '.', '9', '.', '1',
-    // nested.id
-    0x10, 0xe7, 0x07,
     // nested.pair[1] header (key, size)
     0x1a, 0x12,
     // nested.pair[1].key
@@ -248,259 +211,147 @@
   };
   // clang-format on
 
-  ASSERT_EQ(encoder.status(), OkStatus());
-  ConstByteSpan result = ConstByteSpan(writer.data(), writer.bytes_written());
-  EXPECT_EQ(result.size(), sizeof(encoded_proto));
-  EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
-            0);
+  Result result = encoder.Encode();
+  ASSERT_EQ(result.status(), OkStatus());
+  EXPECT_EQ(result.value().size(), sizeof(encoded_proto));
+  EXPECT_EQ(
+      std::memcmp(result.value().data(), encoded_proto, sizeof(encoded_proto)),
+      0);
 }
 
-TEST(StreamEncoder, RepeatedField) {
+TEST(Encoder, NestedDepthLimit) {
+  std::byte encode_buffer[128];
+  NestedEncoder<2, 2> encoder(encode_buffer);
+
+  // One level of nesting.
+  EXPECT_EQ(encoder.Push(2), OkStatus());
+  // Two levels of nesting.
+  EXPECT_EQ(encoder.Push(1), OkStatus());
+  // Three levels of nesting: error!
+  EXPECT_EQ(encoder.Push(1), Status::ResourceExhausted());
+
+  // Further operations should fail.
+  EXPECT_EQ(encoder.Pop(), Status::ResourceExhausted());
+  EXPECT_EQ(encoder.Pop(), Status::ResourceExhausted());
+  EXPECT_EQ(encoder.Pop(), Status::ResourceExhausted());
+}
+
+TEST(Encoder, NestedBlobLimit) {
+  std::byte encode_buffer[128];
+  NestedEncoder<3, 3> encoder(encode_buffer);
+
+  // Write first blob.
+  EXPECT_EQ(encoder.Push(1), OkStatus());
+  EXPECT_EQ(encoder.Pop(), OkStatus());
+
+  // Write second blob.
+  EXPECT_EQ(encoder.Push(2), OkStatus());
+
+  // Write nested third blob.
+  EXPECT_EQ(encoder.Push(3), OkStatus());
+  EXPECT_EQ(encoder.Pop(), OkStatus());
+
+  // End second blob.
+  EXPECT_EQ(encoder.Pop(), OkStatus());
+
+  // Write fourth blob: OK
+  EXPECT_EQ(encoder.Push(4), OkStatus());
+  EXPECT_EQ(encoder.Pop(), OkStatus());
+}
+
+TEST(Encoder, RepeatedField) {
   std::byte encode_buffer[32];
-  MemoryEncoder encoder(encode_buffer);
+  NestedEncoder encoder(encode_buffer);
 
   // repeated uint32 values = 1;
   constexpr uint32_t values[] = {0, 50, 100, 150, 200};
   for (int i = 0; i < 5; ++i) {
-    encoder.WriteUint32(1, values[i])
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    encoder.WriteUint32(1, values[i]);
   }
 
   constexpr uint8_t encoded_proto[] = {
       0x08, 0x00, 0x08, 0x32, 0x08, 0x64, 0x08, 0x96, 0x01, 0x08, 0xc8, 0x01};
 
-  ASSERT_EQ(encoder.status(), OkStatus());
-  ConstByteSpan result(encoder);
-  EXPECT_EQ(result.size(), sizeof(encoded_proto));
-  EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
-            0);
+  Result result = encoder.Encode();
+  ASSERT_EQ(result.status(), OkStatus());
+  EXPECT_EQ(result.value().size(), sizeof(encoded_proto));
+  EXPECT_EQ(
+      std::memcmp(result.value().data(), encoded_proto, sizeof(encoded_proto)),
+      0);
 }
 
-TEST(StreamEncoder, PackedVarint) {
+TEST(Encoder, PackedVarint) {
   std::byte encode_buffer[32];
-  MemoryEncoder encoder(encode_buffer);
+  NestedEncoder encoder(encode_buffer);
 
   // repeated uint32 values = 1;
   constexpr uint32_t values[] = {0, 50, 100, 150, 200};
-  encoder.WritePackedUint32(1, values)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  encoder.WritePackedUint32(1, values);
 
   constexpr uint8_t encoded_proto[] = {
       0x0a, 0x07, 0x00, 0x32, 0x64, 0x96, 0x01, 0xc8, 0x01};
   //  key   size  v[0]  v[1]  v[2]  v[3]        v[4]
 
-  ASSERT_EQ(encoder.status(), OkStatus());
-  ConstByteSpan result(encoder);
-  EXPECT_EQ(result.size(), sizeof(encoded_proto));
-  EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
-            0);
+  Result result = encoder.Encode();
+  ASSERT_EQ(result.status(), OkStatus());
+  EXPECT_EQ(result.value().size(), sizeof(encoded_proto));
+  EXPECT_EQ(
+      std::memcmp(result.value().data(), encoded_proto, sizeof(encoded_proto)),
+      0);
 }
 
-TEST(StreamEncoder, PackedVarintInsufficientSpace) {
+TEST(Encoder, PackedVarintInsufficientSpace) {
   std::byte encode_buffer[8];
-  MemoryEncoder encoder(encode_buffer);
+  NestedEncoder encoder(encode_buffer);
 
   constexpr uint32_t values[] = {0, 50, 100, 150, 200};
-  encoder.WritePackedUint32(1, values)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  encoder.WritePackedUint32(1, values);
 
-  EXPECT_EQ(encoder.status(), Status::ResourceExhausted());
+  EXPECT_EQ(encoder.Encode().status(), Status::ResourceExhausted());
 }
 
-TEST(StreamEncoder, PackedVarintVector) {
+TEST(Encoder, PackedFixed) {
   std::byte encode_buffer[32];
-  MemoryEncoder encoder(encode_buffer);
-
-  // repeated uint32 values = 1;
-  const pw::Vector<uint32_t, 5> values = {0, 50, 100, 150, 200};
-  encoder.WriteRepeatedUint32(1, values)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-
-  constexpr uint8_t encoded_proto[] = {
-      0x0a, 0x07, 0x00, 0x32, 0x64, 0x96, 0x01, 0xc8, 0x01};
-  //  key   size  v[0]  v[1]  v[2]  v[3]        v[4]
-
-  ASSERT_EQ(encoder.status(), OkStatus());
-  ConstByteSpan result(encoder);
-  EXPECT_EQ(result.size(), sizeof(encoded_proto));
-  EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
-            0);
-}
-
-TEST(StreamEncoder, PackedVarintVectorInsufficientSpace) {
-  std::byte encode_buffer[8];
-  MemoryEncoder encoder(encode_buffer);
-
-  const pw::Vector<uint32_t, 5> values = {0, 50, 100, 150, 200};
-  encoder.WriteRepeatedUint32(1, values)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-
-  EXPECT_EQ(encoder.status(), Status::ResourceExhausted());
-}
-
-TEST(StreamEncoder, PackedBool) {
-  std::byte encode_buffer[32];
-  MemoryEncoder encoder(encode_buffer);
-
-  // repeated bool values = 1;
-  constexpr bool values[] = {true, false, true, true, false};
-  encoder.WritePackedBool(1, values)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-
-  constexpr uint8_t encoded_proto[] = {
-      0x0a, 0x05, 0x01, 0x00, 0x01, 0x01, 0x00};
-  //  key   size  v[0]  v[1]  v[2]  v[3]  v[4]
-
-  ASSERT_EQ(encoder.status(), OkStatus());
-  ConstByteSpan result(encoder);
-  EXPECT_EQ(result.size(), sizeof(encoded_proto));
-  EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
-            0);
-}
-
-TEST(StreamEncoder, PackedFixed) {
-  std::byte encode_buffer[32];
-  MemoryEncoder encoder(encode_buffer);
+  NestedEncoder encoder(encode_buffer);
 
   // repeated fixed32 values = 1;
   constexpr uint32_t values[] = {0, 50, 100, 150, 200};
-  encoder.WritePackedFixed32(1, values)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  encoder.WritePackedFixed32(1, values);
 
   // repeated fixed64 values64 = 2;
   constexpr uint64_t values64[] = {0x0102030405060708};
-  encoder.WritePackedFixed64(2, values64)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  encoder.WritePackedFixed64(2, values64);
 
   constexpr uint8_t encoded_proto[] = {
       0x0a, 0x14, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x64,
       0x00, 0x00, 0x00, 0x96, 0x00, 0x00, 0x00, 0xc8, 0x00, 0x00, 0x00,
       0x12, 0x08, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01};
 
-  ASSERT_EQ(encoder.status(), OkStatus());
-  ConstByteSpan result(encoder);
-  EXPECT_EQ(result.size(), sizeof(encoded_proto));
-  EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
-            0);
+  Result result = encoder.Encode();
+  ASSERT_EQ(result.status(), OkStatus());
+  EXPECT_EQ(result.value().size(), sizeof(encoded_proto));
+  EXPECT_EQ(
+      std::memcmp(result.value().data(), encoded_proto, sizeof(encoded_proto)),
+      0);
 }
 
-TEST(StreamEncoder, PackedFixedVector) {
+TEST(Encoder, PackedZigzag) {
   std::byte encode_buffer[32];
-  MemoryEncoder encoder(encode_buffer);
-
-  // repeated fixed32 values = 1;
-  const pw::Vector<uint32_t, 5> values = {0, 50, 100, 150, 200};
-  encoder.WriteRepeatedFixed32(1, values)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-
-  // repeated fixed64 values64 = 2;
-  const pw::Vector<uint64_t, 1> values64 = {0x0102030405060708};
-  encoder.WriteRepeatedFixed64(2, values64)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-
-  constexpr uint8_t encoded_proto[] = {
-      0x0a, 0x14, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x64,
-      0x00, 0x00, 0x00, 0x96, 0x00, 0x00, 0x00, 0xc8, 0x00, 0x00, 0x00,
-      0x12, 0x08, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01};
-
-  ASSERT_EQ(encoder.status(), OkStatus());
-  ConstByteSpan result(encoder);
-  EXPECT_EQ(result.size(), sizeof(encoded_proto));
-  EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
-            0);
-}
-
-TEST(StreamEncoder, PackedZigzag) {
-  std::byte encode_buffer[32];
-  MemoryEncoder encoder(encode_buffer);
+  NestedEncoder encoder(encode_buffer);
 
   // repeated sint32 values = 1;
   constexpr int32_t values[] = {-100, -25, -1, 0, 1, 25, 100};
-  encoder.WritePackedSint32(1, values)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  encoder.WritePackedSint32(1, values);
 
   constexpr uint8_t encoded_proto[] = {
       0x0a, 0x09, 0xc7, 0x01, 0x31, 0x01, 0x00, 0x02, 0x32, 0xc8, 0x01};
 
-  ASSERT_EQ(encoder.status(), OkStatus());
-  ConstByteSpan result(encoder);
-  EXPECT_EQ(result.size(), sizeof(encoded_proto));
-  EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
-            0);
-}
-
-TEST(StreamEncoder, PackedZigzagVector) {
-  std::byte encode_buffer[32];
-  MemoryEncoder encoder(encode_buffer);
-
-  // repeated sint32 values = 1;
-  const pw::Vector<int32_t, 7> values = {-100, -25, -1, 0, 1, 25, 100};
-  encoder.WriteRepeatedSint32(1, values)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-
-  constexpr uint8_t encoded_proto[] = {
-      0x0a, 0x09, 0xc7, 0x01, 0x31, 0x01, 0x00, 0x02, 0x32, 0xc8, 0x01};
-
-  ASSERT_EQ(encoder.status(), OkStatus());
-  ConstByteSpan result(encoder);
-  EXPECT_EQ(result.size(), sizeof(encoded_proto));
-  EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
-            0);
-}
-
-TEST(StreamEncoder, ParentUnavailable) {
-  std::byte encode_buffer[32];
-  MemoryEncoder parent(encode_buffer);
-  {
-    StreamEncoder child = parent.GetNestedEncoder(kTestProtoNestedField);
-    ASSERT_EQ(child.status(), OkStatus());
-  }
-  ASSERT_EQ(parent.status(), OkStatus());
-}
-
-TEST(StreamEncoder, NestedEncoderRequiresBuffer) {
-  MemoryEncoder parent((ByteSpan()));
-  {
-    StreamEncoder child = parent.GetNestedEncoder(kTestProtoNestedField);
-
-    ASSERT_EQ(child.status(), Status::ResourceExhausted());
-  }
-  ASSERT_EQ(parent.status(), Status::ResourceExhausted());
-}
-
-TEST(StreamEncoder, WriteTooBig) {
-  constexpr size_t kTempBufferSize = 32;
-  constexpr size_t kWriteSize = 2;
-  std::byte encode_buffer[32];
-  MemoryEncoder encoder(encode_buffer);
-  // Each write is 2 bytes. Ensure we can write 16 times.
-  for (size_t i = 0; i < kTempBufferSize; i += kWriteSize) {
-    ASSERT_EQ(encoder.WriteUint32(1, 12), OkStatus());
-  }
-  ASSERT_EQ(encoder.size(), kTempBufferSize);
-  ASSERT_EQ(encoder.WriteUint32(1, 12), Status::ResourceExhausted());
-}
-
-TEST(StreamEncoder, EmptyChildWrites) {
-  std::byte encode_buffer[32];
-  MemoryEncoder parent(encode_buffer);
-  { StreamEncoder child = parent.GetNestedEncoder(kTestProtoNestedField); }
-  ASSERT_EQ(parent.status(), OkStatus());
-  const size_t kExpectedSize =
-      varint::EncodedSize(
-          FieldKey(kTestProtoNestedField, WireType::kDelimited)) +
-      varint::EncodedSize(0);
-  ASSERT_EQ(parent.size(), kExpectedSize);
-}
-
-TEST(StreamEncoder, NestedStatusPropagates) {
-  std::byte encode_buffer[32];
-  MemoryEncoder parent(encode_buffer);
-  {
-    StreamEncoder child = parent.GetNestedEncoder(kTestProtoNestedField);
-    ASSERT_EQ(child.WriteUint32(0, 0), Status::InvalidArgument());
-  }
-  ASSERT_EQ(parent.status(), Status::InvalidArgument());
+  Result result = encoder.Encode();
+  ASSERT_EQ(result.status(), OkStatus());
+  EXPECT_EQ(result.value().size(), sizeof(encoded_proto));
+  EXPECT_EQ(
+      std::memcmp(result.value().data(), encoded_proto, sizeof(encoded_proto)),
+      0);
 }
 
 }  // namespace
diff --git a/pw_protobuf/find_test.cc b/pw_protobuf/find_test.cc
index 669e7d0..1dd4789 100644
--- a/pw_protobuf/find_test.cc
+++ b/pw_protobuf/find_test.cc
@@ -45,8 +45,7 @@
   FindDecodeHandler finder(3);
 
   decoder.set_handler(&finder);
-  decoder.Decode(std::as_bytes(std::span(encoded_proto)))
-      .IgnoreError(); // TODO(pwbug/387): Handle Status properly
+  decoder.Decode(std::as_bytes(std::span(encoded_proto)));
 
   EXPECT_TRUE(finder.found());
   EXPECT_TRUE(decoder.cancelled());
@@ -57,8 +56,7 @@
   FindDecodeHandler finder(8);
 
   decoder.set_handler(&finder);
-  decoder.Decode(std::as_bytes(std::span(encoded_proto)))
-      .IgnoreError(); // TODO(pwbug/387): Handle Status properly
+  decoder.Decode(std::as_bytes(std::span(encoded_proto)));
 
   EXPECT_FALSE(finder.found());
   EXPECT_FALSE(decoder.cancelled());
@@ -70,8 +68,7 @@
   FindDecodeHandler finder(7, &nested_finder);
 
   decoder.set_handler(&finder);
-  decoder.Decode(std::as_bytes(std::span(encoded_proto)))
-      .IgnoreError(); // TODO(pwbug/387): Handle Status properly
+  decoder.Decode(std::as_bytes(std::span(encoded_proto)));
 
   EXPECT_TRUE(finder.found());
   EXPECT_TRUE(nested_finder.found());
@@ -84,8 +81,7 @@
   FindDecodeHandler finder(7, &nested_finder);
 
   decoder.set_handler(&finder);
-  decoder.Decode(std::as_bytes(std::span(encoded_proto)))
-      .IgnoreError(); // TODO(pwbug/387): Handle Status properly
+  decoder.Decode(std::as_bytes(std::span(encoded_proto)));
 
   EXPECT_TRUE(finder.found());
   EXPECT_FALSE(nested_finder.found());
diff --git a/pw_protobuf/map_utils.cc b/pw_protobuf/map_utils.cc
deleted file mode 100644
index 27b505b..0000000
--- a/pw_protobuf/map_utils.cc
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_protobuf/map_utils.h"
-
-#include <cstddef>
-
-#include "pw_bytes/span.h"
-#include "pw_protobuf/encoder.h"
-#include "pw_protobuf/serialized_size.h"
-#include "pw_stream/stream.h"
-
-namespace pw::protobuf {
-
-// Note that a map<string, bytes> is essentially
-//
-// message Entry {
-//   string key = 1;
-//   bytes value = 2;
-// }
-//
-// message Msg {
-//   repeated Entry map_field = <field_number>;
-// }
-Status WriteProtoStringToBytesMapEntry(uint32_t field_number,
-                                       stream::Reader& key,
-                                       size_t key_size,
-                                       stream::Reader& value,
-                                       size_t value_size,
-                                       ByteSpan stream_pipe_buffer,
-                                       stream::Writer& writer) {
-  constexpr uint32_t kMapKeyFieldNumber = 1;
-  constexpr uint32_t kMapValueFieldNumber = 2;
-
-  if (!protobuf::ValidFieldNumber(field_number) ||
-      key_size >= std::numeric_limits<uint32_t>::max() ||
-      value_size >= std::numeric_limits<uint32_t>::max()) {
-    return Status::InvalidArgument();
-  }
-
-  Result<size_t> key_field_size = protobuf::SizeOfField(
-      kMapKeyFieldNumber, protobuf::WireType::kDelimited, key_size);
-  PW_TRY(key_field_size.status());
-
-  Result<size_t> value_field_size = protobuf::SizeOfField(
-      kMapValueFieldNumber, protobuf::WireType::kDelimited, value_size);
-  PW_TRY(value_field_size.status());
-
-  size_t entry_payload_total_size =
-      key_field_size.value() + value_field_size.value();
-
-  Result<size_t> entry_field_total_size = protobuf::SizeOfField(
-      field_number, protobuf::WireType::kDelimited, entry_payload_total_size);
-  PW_TRY(entry_field_total_size.status());
-
-  if (entry_field_total_size.value() > writer.ConservativeWriteLimit()) {
-    return Status::ResourceExhausted();
-  }
-
-  // Write field key and length prefix for nested message `Entry`
-  PW_TRY(protobuf::WriteLengthDelimitedKeyAndLengthPrefix(
-      field_number, entry_payload_total_size, writer));
-
-  protobuf::StreamEncoder encoder(writer, {});
-
-  // Write Entry::key
-  PW_TRY(encoder.WriteStringFromStream(
-      kMapKeyFieldNumber, key, key_size, stream_pipe_buffer));
-  // Write Entry::value
-  PW_TRY(encoder.WriteBytesFromStream(
-      kMapValueFieldNumber, value, value_size, stream_pipe_buffer));
-
-  return OkStatus();
-}
-
-}  // namespace pw::protobuf
diff --git a/pw_protobuf/map_utils_test.cc b/pw_protobuf/map_utils_test.cc
deleted file mode 100644
index d1da109..0000000
--- a/pw_protobuf/map_utils_test.cc
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_protobuf/map_utils.h"
-
-#include <string_view>
-
-#include "gtest/gtest.h"
-#include "pw_stream/memory_stream.h"
-#include "pw_stream/stream.h"
-
-#define ASSERT_OK(status) ASSERT_EQ(OkStatus(), status)
-
-namespace pw::protobuf {
-
-TEST(ProtoHelper, WriteProtoStringToBytesMapEntry) {
-  // The following defines an instance of the message below:
-  //
-  // message Maps {
-  //   map<string, string> map_a = 1;
-  //   map<string, string> map_b = 2;
-  // }
-  //
-  // where
-  //
-  // Maps.map_a['key_foo'] = 'foo_a'
-  // Maps.map_a['key_bar'] = 'bar_a'
-  //
-  // Maps.map_b['key_foo'] = 'foo_b'
-  // Maps.map_b['key_bar'] = 'bar_b'
-  //
-  // clang-format off
-  std::uint8_t encoded_proto[] = {
-    // map_a["key_bar"] = "bar_a", key = 1
-    0x0a, 0x10,
-    0x0a, 0x07, 'k', 'e', 'y', '_', 'b', 'a', 'r', // map key
-    0x12, 0x05, 'b', 'a', 'r', '_', 'a', // map value
-
-    // map_a["key_foo"] = "foo_a", key = 1
-    0x0a, 0x10,
-    0x0a, 0x07, 'k', 'e', 'y', '_', 'f', 'o', 'o',
-    0x12, 0x05, 'f', 'o', 'o', '_', 'a',
-
-    // map_b["key_foo"] = "foo_b", key = 2
-    0x12, 0x10,
-    0x0a, 0x07, 'k', 'e', 'y', '_', 'f', 'o', 'o',
-    0x12, 0x05, 'f', 'o', 'o', '_', 'b',
-
-    // map_b["key_bar"] = "bar_b", key = 2
-    0x12, 0x10,
-    0x0a, 0x07, 'k', 'e', 'y', '_', 'b', 'a', 'r',
-    0x12, 0x05, 'b', 'a', 'r', '_', 'b',
-  };
-  // clang-format on
-
-  // Now construct the same message with WriteStringToBytesMapEntry
-  std::byte dst_buffer[sizeof(encoded_proto)];
-  stream::MemoryWriter writer(dst_buffer);
-
-  const struct {
-    uint32_t field_number;
-    std::string_view key;
-    std::string_view value;
-  } kMapData[] = {
-      {1, "key_bar", "bar_a"},
-      {1, "key_foo", "foo_a"},
-      {2, "key_foo", "foo_b"},
-      {2, "key_bar", "bar_b"},
-  };
-
-  std::byte stream_pipe_buffer[1];
-  for (auto ele : kMapData) {
-    stream::MemoryReader key_reader(std::as_bytes(std::span{ele.key}));
-    stream::MemoryReader value_reader(std::as_bytes(std::span{ele.value}));
-    ASSERT_OK(WriteProtoStringToBytesMapEntry(ele.field_number,
-                                              key_reader,
-                                              ele.key.size(),
-                                              value_reader,
-                                              ele.value.size(),
-                                              stream_pipe_buffer,
-                                              writer));
-  }
-
-  ASSERT_EQ(memcmp(dst_buffer, encoded_proto, sizeof(dst_buffer)), 0);
-}
-
-TEST(ProtoHelper, WriteProtoStringToBytesMapEntryExceedsWriteLimit) {
-  // Construct an instance of the message below:
-  //
-  // message Maps {
-  //   map<string, string> map_a = 1;
-  // }
-  //
-  // where
-  //
-  // Maps.map_a['key_bar'] = 'bar_a'. The needed buffer size is 18 in this
-  // case:
-  //
-  // {
-  //   0x0a, 0x10,
-  //   0x0a, 0x07, 'k', 'e', 'y', '_', 'b', 'a', 'r',
-  //   0x12, 0x05, 'b', 'a', 'r', '_', 'a',
-  // }
-  //
-  // Use a smaller buffer.
-  std::byte encode_buffer[17];
-  stream::MemoryWriter writer(encode_buffer);
-  constexpr uint32_t kFieldNumber = 1;
-  std::string_view key = "key_bar";
-  std::string_view value = "bar_a";
-  stream::MemoryReader key_reader(std::as_bytes(std::span{key}));
-  stream::MemoryReader value_reader(std::as_bytes(std::span{value}));
-  std::byte stream_pipe_buffer[1];
-  ASSERT_EQ(
-      WriteProtoStringToBytesMapEntry(kFieldNumber,
-                                      key_reader,
-                                      key_reader.ConservativeReadLimit(),
-                                      value_reader,
-                                      value_reader.ConservativeReadLimit(),
-                                      stream_pipe_buffer,
-                                      writer),
-      Status::ResourceExhausted());
-}
-
-TEST(ProtoHelper, WriteProtoStringToBytesMapEntryInvalidArgument) {
-  std::byte encode_buffer[17];
-  stream::MemoryWriter writer(encode_buffer);
-  std::string_view key = "key_bar";
-  std::string_view value = "bar_a";
-  stream::MemoryReader key_reader(std::as_bytes(std::span{key}));
-  stream::MemoryReader value_reader(std::as_bytes(std::span{value}));
-  std::byte stream_pipe_buffer[1];
-
-  ASSERT_EQ(
-      WriteProtoStringToBytesMapEntry(19091,
-                                      key_reader,
-                                      key_reader.ConservativeReadLimit(),
-                                      value_reader,
-                                      value_reader.ConservativeReadLimit(),
-                                      stream_pipe_buffer,
-                                      writer),
-      Status::InvalidArgument());
-}
-
-}  // namespace pw::protobuf
diff --git a/pw_protobuf/message.cc b/pw_protobuf/message.cc
deleted file mode 100644
index 0128627..0000000
--- a/pw_protobuf/message.cc
+++ /dev/null
@@ -1,225 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_protobuf/message.h"
-
-#include <cstddef>
-
-#include "pw_protobuf/serialized_size.h"
-#include "pw_protobuf/stream_decoder.h"
-#include "pw_result/result.h"
-#include "pw_status/status_with_size.h"
-#include "pw_stream/interval_reader.h"
-#include "pw_stream/stream.h"
-
-namespace pw::protobuf {
-
-template <>
-Uint32 Message::Field::As<Uint32>() {
-  protobuf::StreamDecoder decoder(field_reader_.Reset());
-  PW_TRY(decoder.Next());
-  return decoder.ReadUint32();
-}
-
-template <>
-Int32 Message::Field::As<Int32>() {
-  protobuf::StreamDecoder decoder(field_reader_.Reset());
-  PW_TRY(decoder.Next());
-  return decoder.ReadInt32();
-}
-
-template <>
-Sint32 Message::Field::As<Sint32>() {
-  protobuf::StreamDecoder decoder(field_reader_.Reset());
-  PW_TRY(decoder.Next());
-  return decoder.ReadSint32();
-}
-
-template <>
-Fixed32 Message::Field::As<Fixed32>() {
-  protobuf::StreamDecoder decoder(field_reader_.Reset());
-  PW_TRY(decoder.Next());
-  return decoder.ReadFixed32();
-}
-
-template <>
-Sfixed32 Message::Field::As<Sfixed32>() {
-  protobuf::StreamDecoder decoder(field_reader_.Reset());
-  PW_TRY(decoder.Next());
-  return decoder.ReadSfixed32();
-}
-
-template <>
-Uint64 Message::Field::As<Uint64>() {
-  protobuf::StreamDecoder decoder(field_reader_.Reset());
-  PW_TRY(decoder.Next());
-  return decoder.ReadUint64();
-}
-
-template <>
-Int64 Message::Field::As<Int64>() {
-  protobuf::StreamDecoder decoder(field_reader_.Reset());
-  PW_TRY(decoder.Next());
-  return decoder.ReadInt64();
-}
-
-template <>
-Sint64 Message::Field::As<Sint64>() {
-  protobuf::StreamDecoder decoder(field_reader_.Reset());
-  PW_TRY(decoder.Next());
-  return decoder.ReadSint64();
-}
-
-template <>
-Fixed64 Message::Field::As<Fixed64>() {
-  protobuf::StreamDecoder decoder(field_reader_.Reset());
-  PW_TRY(decoder.Next());
-  return decoder.ReadFixed64();
-}
-
-template <>
-Sfixed64 Message::Field::As<Sfixed64>() {
-  protobuf::StreamDecoder decoder(field_reader_.Reset());
-  PW_TRY(decoder.Next());
-  return decoder.ReadSfixed64();
-}
-
-template <>
-Float Message::Field::As<Float>() {
-  protobuf::StreamDecoder decoder(field_reader_.Reset());
-  PW_TRY(decoder.Next());
-  return decoder.ReadFloat();
-}
-
-template <>
-Double Message::Field::As<Double>() {
-  protobuf::StreamDecoder decoder(field_reader_.Reset());
-  PW_TRY(decoder.Next());
-  return decoder.ReadDouble();
-}
-
-template <>
-Bool Message::Field::As<Bool>() {
-  protobuf::StreamDecoder decoder(field_reader_.Reset());
-  PW_TRY(decoder.Next());
-  return decoder.ReadBool();
-}
-
-Result<bool> Bytes::Equal(ConstByteSpan bytes) {
-  stream::IntervalReader bytes_reader = GetBytesReader();
-  if (bytes_reader.interval_size() != bytes.size()) {
-    return false;
-  }
-
-  std::byte buf[1];
-  for (size_t i = 0; i < bytes.size();) {
-    Result<ByteSpan> res = bytes_reader.Read(buf);
-    PW_TRY(res.status());
-    if (res.value().size() == 1) {
-      if (buf[0] != bytes[i++])
-        return false;
-    }
-  }
-
-  return true;
-}
-
-Result<bool> String::Equal(std::string_view str) {
-  return Bytes::Equal(std::as_bytes(std::span{str}));
-}
-
-Message::iterator& Message::iterator::operator++() {
-  // If this is not a valid iterator, increment it to the end iterator,
-  // so loop will end.
-  if (!ok()) {
-    reader_.Exhaust();
-    eof_ = true;
-    return *this;
-  }
-
-  // Store the starting offset of the field.
-  size_t field_start = reader_.current();
-  protobuf::StreamDecoder decoder(reader_);
-  Status status = decoder.Next();
-  if (status.IsOutOfRange()) {
-    eof_ = true;
-    return *this;
-  } else if (!status.ok()) {
-    // In the case of error, invalidate the iterator. We don't immediately
-    // move the iterator to end(), so that calling code has a chance to catch
-    // the error.
-    status_ = status;
-    current_ = Field(status_);
-    return *this;
-  }
-
-  Result<uint32_t> field_number = decoder.FieldNumber();
-  // Consume the field so that the reader will be pointing to the start
-  // of the next field, which is equivalent to the end offset of the
-  // current field.
-  status = ConsumeCurrentField(decoder);
-  if (!status.ok()) {
-    status_ = status;
-    current_ = Field(status_);
-    return *this;
-  }
-
-  // Create a Field object with the field interval.
-  current_ = Field(stream::IntervalReader(
-                       reader_.source_reader(), field_start, reader_.current()),
-                   field_number.value());
-  return *this;
-}
-
-Message::iterator Message::begin() {
-  if (!ok()) {
-    return end();
-  }
-
-  return iterator(reader_.Reset());
-}
-
-Message::iterator Message::end() {
-  // The end iterator is created by using an exahusted stream::IntervalReader,
-  // i.e. the reader is pointing at the internval end.
-  stream::IntervalReader reader_end = reader_;
-  return iterator(reader_end.Exhaust());
-}
-
-RepeatedBytes Message::AsRepeatedBytes(uint32_t field_number) {
-  return AsRepeated<Bytes>(field_number);
-}
-
-RepeatedFieldParser<String> Message::AsRepeatedStrings(uint32_t field_number) {
-  return AsRepeated<String>(field_number);
-}
-
-RepeatedFieldParser<Message> Message::AsRepeatedMessages(
-    uint32_t field_number) {
-  return AsRepeated<Message>(field_number);
-}
-
-StringMapParser<Message> Message::AsStringToMessageMap(uint32_t field_number) {
-  return AsStringMap<Message>(field_number);
-}
-
-StringMapParser<Bytes> Message::AsStringToBytesMap(uint32_t field_number) {
-  return AsStringMap<Bytes>(field_number);
-}
-
-StringMapParser<String> Message::AsStringToStringMap(uint32_t field_number) {
-  return AsStringMap<String>(field_number);
-}
-
-}  // namespace pw::protobuf
diff --git a/pw_protobuf/message_test.cc b/pw_protobuf/message_test.cc
deleted file mode 100644
index 3dab075..0000000
--- a/pw_protobuf/message_test.cc
+++ /dev/null
@@ -1,772 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_protobuf/message.h"
-
-#include "gtest/gtest.h"
-#include "pw_stream/memory_stream.h"
-
-#define ASSERT_OK(status) ASSERT_EQ(OkStatus(), status)
-
-namespace pw::protobuf {
-
-TEST(ProtoHelper, IterateMessage) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // type=uint32, k=1, v=1
-    0x08, 0x01,
-    // type=uint32, k=2, v=2
-    0x10, 0x02,
-    // type=uint32, k=3, v=3
-    0x18, 0x03,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  Message parser = Message(reader, sizeof(encoded_proto));
-
-  uint32_t count = 0;
-  for (Message::Field field : parser) {
-    ++count;
-    EXPECT_EQ(field.field_number(), count);
-    Uint32 value = field.As<Uint32>();
-    ASSERT_OK(value.status());
-    EXPECT_EQ(value.value(), count);
-  }
-
-  EXPECT_EQ(count, static_cast<uint32_t>(3));
-}
-
-TEST(ProtoHelper, MessageIterator) {
-  // clang-format off
-  std::uint8_t encoded_proto[] = {
-    // key = 1, str = "foo 1"
-    0x0a, 0x05, 'f', 'o', 'o', ' ', '1',
-    // type=uint32, k=2, v=2
-    0x10, 0x02,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  Message parser = Message(reader, sizeof(encoded_proto));
-
-  Message::iterator iter = parser.begin();
-
-  Message::iterator first = iter++;
-  ASSERT_EQ(first, first);
-  ASSERT_EQ(first->field_number(), static_cast<uint32_t>(1));
-  String str = first->As<String>();
-  ASSERT_OK(str.status());
-  Result<bool> cmp = str.Equal("foo 1");
-  ASSERT_OK(cmp.status());
-  ASSERT_TRUE(cmp.value());
-
-  Message::iterator second = iter++;
-  ASSERT_EQ(second, second);
-  ASSERT_EQ(second->field_number(), static_cast<uint32_t>(2));
-  Uint32 uint32_val = second->As<Uint32>();
-  ASSERT_OK(uint32_val.status());
-  ASSERT_EQ(uint32_val.value(), static_cast<uint32_t>(2));
-
-  ASSERT_NE(first, second);
-  ASSERT_NE(first, iter);
-  ASSERT_NE(second, iter);
-  ASSERT_EQ(iter, parser.end());
-}
-
-TEST(ProtoHelper, MessageIteratorMalformedProto) {
-  // clang-format off
-  std::uint8_t encoded_proto[] = {
-    // key = 1, str = "foo 1"
-    0x0a,0x05,'f','o','o',' ','1',
-    // key = 0, str = "foo 2" (invalid)
-    0x02,0x05,'f','o','o',' ','2',
-    // key = 3, str = "bar 1"
-    0x1a,0x05,'b','a','r',' ','1',
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  Message parser = Message(reader, sizeof(encoded_proto));
-
-  Message::iterator iter = parser.begin();
-  ASSERT_OK(iter.status());
-
-  // Second field has invalid field number
-  ASSERT_FALSE((++iter).ok());
-
-  // Attempting to increment an invalid iterator result in it being end()
-  ASSERT_EQ((++iter), parser.end());
-
-  // Test the c++ std loop behavior.
-  bool expected_ok_status[] = {true, false};
-  size_t count = 0;
-  for (Message::Field field : parser) {
-    ASSERT_EQ(field.ok(), expected_ok_status[count++]);
-  }
-  // First element ok. Second element invalid. Iteration ends in the next
-  // iteration.
-  ASSERT_EQ(count, 2ULL);
-}
-
-TEST(ProtoHelper, InvalidMessageBeginIterator) {
-  Message parser(Status::Internal());
-  ASSERT_FALSE(parser.begin().ok());
-  ASSERT_EQ(parser.begin(), parser.end());
-}
-
-TEST(ProtoHelper, AsProtoInteger) {
-  // clang-format off
-  std::uint8_t encoded_proto[] = {
-      // type: int32, k = 1, val = -123
-      0x08, 0x85, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01,
-      // type: uint32, k = 2, val = 123
-      0x10, 0x7b,
-      // type: sint32, k = 3, val = -456
-      0x18, 0x8f, 0x07,
-      // type: fixed32, k = 4, val = 268435457
-      0x25, 0x01, 0x00, 0x00, 0x10,
-      // type: sfixed32, k = 5, val = -268435457
-      0x2d, 0xff, 0xff, 0xff, 0xef,
-      // type: int64, k = 6, val = -1099511627776
-      0x30, 0x80, 0x80, 0x80, 0x80, 0x80, 0xe0, 0xff, 0xff, 0xff, 0x01,
-      // type: uint64, k = 7, val = 1099511627776
-      0x38, 0x80, 0x80, 0x80, 0x80, 0x80, 0x20,
-      // type: sint64, k = 8, val = -2199023255552
-      0x40, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f,
-      // type: fixed64, k = 9, val = 72057594037927937
-      0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
-      // type: sfixed64, k = 10, val = -72057594037927937
-      0x51, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
-      // type: float, k = 11, val = 123456.00
-      0x5d, 0x00, 0x20, 0xf1, 0x47,
-      // type: double, k = 12, val = -123456.789
-      0x61, 0xc9, 0x76, 0xbe, 0x9f, 0x0c, 0x24, 0xfe, 0xc0,
-      // type: bool, k = 13, val = true
-      0x68, 0x01,
-      // type: bool, k = 14, val = false
-      0x70, 0x00
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  Message parser = Message(reader, sizeof(encoded_proto));
-
-  {
-    Int32 value = parser.AsInt32(1);
-    ASSERT_OK(value.status());
-    ASSERT_EQ(value.value(), static_cast<int32_t>(-123));
-  }
-
-  {
-    Uint32 value = parser.AsUint32(2);
-    ASSERT_OK(value.status());
-    ASSERT_EQ(value.value(), static_cast<uint32_t>(123));
-  }
-
-  {
-    Sint32 value = parser.AsSint32(3);
-    ASSERT_OK(value.status());
-    ASSERT_EQ(value.value(), static_cast<int32_t>(-456));
-  }
-
-  {
-    Fixed32 value = parser.AsFixed32(4);
-    ASSERT_OK(value.status());
-    ASSERT_EQ(value.value(), static_cast<uint32_t>(268435457));
-  }
-
-  {
-    Sfixed32 value = parser.AsSfixed32(5);
-    ASSERT_OK(value.status());
-    ASSERT_EQ(value.value(), static_cast<int32_t>(-268435457));
-  }
-
-  {
-    Int64 value = parser.AsInt64(6);
-    ASSERT_OK(value.status());
-    ASSERT_EQ(value.value(), static_cast<int64_t>(-1099511627776));
-  }
-
-  {
-    Uint64 value = parser.AsUint64(7);
-    ASSERT_OK(value.status());
-    ASSERT_EQ(value.value(), static_cast<uint64_t>(1099511627776));
-  }
-
-  {
-    Sint64 value = parser.AsSint64(8);
-    ASSERT_OK(value.status());
-    ASSERT_EQ(value.value(), static_cast<int64_t>(-2199023255552));
-  }
-
-  {
-    Fixed64 value = parser.AsFixed64(9);
-    ASSERT_OK(value.status());
-    ASSERT_EQ(value.value(), static_cast<uint64_t>(72057594037927937));
-  }
-
-  {
-    Sfixed64 value = parser.AsSfixed64(10);
-    ASSERT_OK(value.status());
-    ASSERT_EQ(value.value(), static_cast<int64_t>(-72057594037927937));
-  }
-
-  {
-    Float value = parser.AsFloat(11);
-    ASSERT_OK(value.status());
-    ASSERT_EQ(value.value(), static_cast<float>(123456.00));
-  }
-
-  {
-    Double value = parser.AsDouble(12);
-    ASSERT_OK(value.status());
-    ASSERT_EQ(value.value(), static_cast<double>(-123456.789));
-  }
-
-  {
-    Bool value = parser.AsBool(13);
-    ASSERT_OK(value.status());
-    ASSERT_EQ(value.value(), static_cast<bool>(true));
-  }
-
-  {
-    Bool value = parser.AsBool(14);
-    ASSERT_OK(value.status());
-    ASSERT_EQ(value.value(), static_cast<bool>(false));
-  }
-}
-
-TEST(ProtoHelper, AsString) {
-  // message {
-  //   string str = 1;
-  // }
-  // clang-format off
-  std::uint8_t encoded_proto[] = {
-    // `str`, k = 1, "string"
-    0x0a, 0x06, 's', 't', 'r', 'i', 'n', 'g',
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  Message parser = Message(reader, sizeof(encoded_proto));
-
-  constexpr uint32_t kFieldNumber = 1;
-  String value = parser.AsString(kFieldNumber);
-  ASSERT_OK(value.status());
-  Result<bool> cmp = value.Equal("string");
-  ASSERT_OK(cmp.status());
-  ASSERT_TRUE(cmp.value());
-
-  cmp = value.Equal("other");
-  ASSERT_OK(cmp.status());
-  ASSERT_FALSE(cmp.value());
-
-  // The string is a prefix of the target string to compare.
-  cmp = value.Equal("string and more");
-  ASSERT_OK(cmp.status());
-  ASSERT_FALSE(cmp.value());
-
-  // The target string to compare is a sub prefix of this string
-  cmp = value.Equal("str");
-  ASSERT_OK(cmp.status());
-  ASSERT_FALSE(cmp.value());
-}
-
-TEST(ProtoHelper, AsRepeatedStrings) {
-  // Repeated field of string i.e.
-  //
-  // message RepeatedString {
-  //   repeated string msg_a = 1;
-  //   repeated string msg_b = 2;
-  // }
-  // clang-format off
-  std::uint8_t encoded_proto[] = {
-    // key = 1, str = "foo 1"
-    0x0a, 0x05, 'f', 'o', 'o', ' ', '1',
-    // key = 2, str = "foo 2"
-    0x12, 0x05, 'f', 'o', 'o', ' ', '2',
-    // key = 1, str = "bar 1"
-    0x0a, 0x05, 'b', 'a', 'r', ' ', '1',
-    // key = 2, str = "bar 2"
-    0x12, 0x05, 'b', 'a', 'r', ' ', '2',
-  };
-  // clang-format on
-
-  constexpr uint32_t kMsgAFieldNumber = 1;
-  constexpr uint32_t kMsgBFieldNumber = 2;
-  constexpr uint32_t kNonExistFieldNumber = 3;
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  Message parser = Message(reader, sizeof(encoded_proto));
-
-  // Field 'msg_a'
-  {
-    RepeatedStrings msg = parser.AsRepeatedStrings(kMsgAFieldNumber);
-    std::string_view expected[] = {
-        "foo 1",
-        "bar 1",
-    };
-
-    size_t count = 0;
-    for (String ele : msg) {
-      ASSERT_OK(ele.status());
-      Result<bool> res = ele.Equal(expected[count++]);
-      ASSERT_OK(res.status());
-      ASSERT_TRUE(res.value());
-    }
-
-    ASSERT_EQ(count, static_cast<size_t>(2));
-  }
-
-  // Field `msg_b`
-  {
-    RepeatedStrings msg = parser.AsRepeatedStrings(kMsgBFieldNumber);
-    std::string_view expected[] = {
-        "foo 2",
-        "bar 2",
-    };
-
-    size_t count = 0;
-    for (String ele : msg) {
-      ASSERT_OK(ele.status());
-      Result<bool> res = ele.Equal(expected[count++]);
-      ASSERT_OK(res.status());
-      ASSERT_TRUE(res.value());
-    }
-
-    ASSERT_EQ(count, static_cast<size_t>(2));
-  }
-
-  // non-existing field
-  {
-    RepeatedStrings msg = parser.AsRepeatedStrings(kNonExistFieldNumber);
-    size_t count = 0;
-    for ([[maybe_unused]] String ele : msg) {
-      count++;
-    }
-
-    ASSERT_EQ(count, static_cast<size_t>(0));
-  }
-}
-
-TEST(ProtoHelper, RepeatedFieldIterator) {
-  // Repeated field of string i.e.
-  //
-  // message RepeatedString {
-  //   repeated string msg = 1;
-  // }
-  // clang-format off
-  std::uint8_t encoded_proto[] = {
-    // key = 1, str = "foo 1"
-    0x0a, 0x05, 'f', 'o', 'o', ' ', '1',
-    // key = 1, str = "bar 1"
-    0x0a, 0x05, 'b', 'a', 'r', ' ', '1',
-  };
-  // clang-format on
-
-  constexpr uint32_t kFieldNumber = 1;
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  Message parser = Message(reader, sizeof(encoded_proto));
-  RepeatedStrings repeated_str = parser.AsRepeatedStrings(kFieldNumber);
-
-  RepeatedStrings::iterator iter = repeated_str.begin();
-
-  RepeatedStrings::iterator first = iter++;
-  ASSERT_EQ(first, first);
-  Result<bool> cmp = first->Equal("foo 1");
-  ASSERT_OK(cmp.status());
-  ASSERT_TRUE(cmp.value());
-
-  RepeatedStrings::iterator second = iter++;
-  ASSERT_EQ(second, second);
-  cmp = second->Equal("bar 1");
-  ASSERT_OK(cmp.status());
-  ASSERT_TRUE(cmp.value());
-
-  ASSERT_NE(first, second);
-  ASSERT_NE(first, iter);
-  ASSERT_NE(second, iter);
-  ASSERT_EQ(iter, repeated_str.end());
-}
-
-TEST(ProtoHelper, RepeatedFieldIteratorMalformedFieldID) {
-  // Repeated field of string i.e.
-  //
-  // message RepeatedString {
-  //   repeated string msg = 1;
-  // }
-  // clang-format off
-  std::uint8_t encoded_proto[] = {
-    // key = 1, str = "foo 1"
-    0x0a, 0x05, 'f', 'o', 'o', ' ', '1',
-    // key = 0, str = "foo 1" (invalid)
-    0x02, 0x05, 'f', 'o', 'o', ' ', '1',
-    // key = 1, str = "foo 1"
-    0x0a, 0x05, 'f', 'o', 'o', ' ', '1',
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  Message parser = Message(reader, sizeof(encoded_proto));
-  RepeatedStrings repeated_str = parser.AsRepeatedStrings(1);
-
-  bool expected_ok[] = {true, false};
-  size_t count = 0;
-  for (String s : repeated_str) {
-    ASSERT_EQ(s.ok(), expected_ok[count++]);
-  }
-  // Iterator becomes invalid in the second iteration. Attempting to increment
-  // causes it to become end(); Therefore, count should be incremented twice.
-  ASSERT_EQ(count, 2ULL);
-}
-
-TEST(ProtoHelper, RepeatedFieldIteratorMalformedFieldIDBeginning) {
-  // Repeated field of string i.e.
-  //
-  // message RepeatedString {
-  //   repeated string msg = 1;
-  // }
-  // clang-format off
-  std::uint8_t encoded_proto[] = {
-    // key = 0, str = "foo 1" (invalid)
-    0x02, 0x05, 'f', 'o', 'o', ' ', '1',
-    // key = 1, str = "foo 1"
-    0x0a, 0x05, 'f', 'o', 'o', ' ', '1',
-    // key = 1, str = "foo 1"
-    0x0a, 0x05, 'f', 'o', 'o', ' ', '1',
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  Message parser = Message(reader, sizeof(encoded_proto));
-  RepeatedStrings repeated_str = parser.AsRepeatedStrings(1);
-
-  bool expected_ok[] = {false};
-  size_t count = 0;
-  for (String s : repeated_str) {
-    ASSERT_EQ(s.ok(), expected_ok[count++]);
-  }
-  // Iterator becomes invalid in the second iteration. Attempting to increment
-  // causes it to become end(); Therefore, count should be incremented twice.
-  ASSERT_EQ(count, 1ULL);
-}
-
-TEST(ProtoHelper, RepeatedFieldIteratorMalformedDataLoss) {
-  // Repeated field of string i.e.
-  //
-  // message RepeatedString {
-  //   repeated string msg = 1;
-  // }
-  // clang-format off
-  std::uint8_t encoded_proto[] = {
-    // key = 1, str = "foo 1"
-    0x0a, 0x05, 'f', 'o', 'o', ' ', '1',
-    // key = 0, str = "foo 1" (invalid)
-    0x0a, 0x10, 'f', 'o', 'o', ' ', '1',
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  Message parser = Message(reader, sizeof(encoded_proto));
-  RepeatedStrings repeated_str = parser.AsRepeatedStrings(1);
-
-  bool expected_ok[] = {true, false};
-  size_t count = 0;
-  for (String s : repeated_str) {
-    ASSERT_EQ(s.ok(), expected_ok[count++]);
-  }
-  ASSERT_EQ(count, 2ULL);
-}
-
-TEST(ProtoHelper, AsMessage) {
-  // A nested message:
-  //
-  // message Contact {
-  //   string number = 1;
-  //   string email = 2;
-  // }
-  //
-  // message Person {
-  //  Contact info = 2;
-  // }
-  // clang-format off
-  std::uint8_t encoded_proto[] = {
-    // Person.info.number = "123456", .email = "foo@email.com"
-    0x12, 0x17,
-    0x0a, 0x06, '1', '2', '3', '4', '5', '6',
-    0x12, 0x0d, 'f', 'o', 'o', '@', 'e', 'm', 'a', 'i', 'l', '.', 'c', 'o', 'm',
-  };
-  // clang-format on
-
-  constexpr uint32_t kInfoFieldNumber = 2;
-  constexpr uint32_t kNumberFieldNumber = 1;
-  constexpr uint32_t kEmailFieldNumber = 2;
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  Message parser = Message(reader, sizeof(encoded_proto));
-
-  Message info = parser.AsMessage(kInfoFieldNumber);
-  ASSERT_OK(info.status());
-
-  String number = info.AsString(kNumberFieldNumber);
-  ASSERT_OK(number.status());
-  Result<bool> cmp = number.Equal("123456");
-  ASSERT_OK(cmp.status());
-  ASSERT_TRUE(cmp.value());
-
-  String email = info.AsString(kEmailFieldNumber);
-  ASSERT_OK(email.status());
-  cmp = email.Equal("foo@email.com");
-  ASSERT_OK(cmp.status());
-  ASSERT_TRUE(cmp.value());
-}
-
-TEST(ProtoHelper, AsRepeatedMessages) {
-  // message Contact {
-  //   string number = 1;
-  //   string email = 2;
-  // }
-  //
-  // message Person {
-  //  repeated Contact info = 1;
-  // }
-  // clang-format off
-  std::uint8_t encoded_proto[] = {
-    // Person.Contact.number = "12345", .email = "foo@email.com"
-    0x0a, 0x16,
-    0x0a, 0x05, '1', '2', '3', '4', '5',
-    0x12, 0x0d, 'f', 'o', 'o', '@', 'e', 'm', 'a', 'i', 'l', '.', 'c', 'o', 'm',
-
-    // Person.Contact.number = "67890", .email = "bar@email.com"
-    0x0a, 0x16,
-    0x0a, 0x05, '6', '7', '8', '9', '0',
-    0x12, 0x0d, 'b', 'a', 'r', '@', 'e', 'm', 'a', 'i', 'l', '.', 'c', 'o', 'm',
-  };
-  // clang-format on
-
-  constexpr uint32_t kInfoFieldNumber = 1;
-  constexpr uint32_t kNumberFieldNumber = 1;
-  constexpr uint32_t kEmailFieldNumber = 2;
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  Message parser = Message(reader, sizeof(encoded_proto));
-
-  RepeatedMessages messages = parser.AsRepeatedMessages(kInfoFieldNumber);
-  ASSERT_OK(messages.status());
-
-  struct {
-    std::string_view number;
-    std::string_view email;
-  } expected[] = {
-      {"12345", "foo@email.com"},
-      {"67890", "bar@email.com"},
-  };
-
-  size_t count = 0;
-  for (Message message : messages) {
-    String number = message.AsString(kNumberFieldNumber);
-    ASSERT_OK(number.status());
-    Result<bool> cmp = number.Equal(expected[count].number);
-    ASSERT_OK(cmp.status());
-    ASSERT_TRUE(cmp.value());
-
-    String email = message.AsString(kEmailFieldNumber);
-    ASSERT_OK(email.status());
-    cmp = email.Equal(expected[count].email);
-    ASSERT_OK(cmp.status());
-    ASSERT_TRUE(cmp.value());
-
-    count++;
-  }
-
-  ASSERT_EQ(count, static_cast<size_t>(2));
-}
-
-TEST(ProtoHelper, AsStringToBytesMap) {
-  // message Maps {
-  //   map<string, string> map_a = 1;
-  //   map<string, string> map_b = 2;
-  // }
-  // clang-format off
-  std::uint8_t encoded_proto[] = {
-    // map_a["key_bar"] = "bar_a", key = 1
-    0x0a, 0x10,
-    0x0a, 0x07, 'k', 'e', 'y', '_', 'b', 'a', 'r', // map key
-    0x12, 0x05, 'b', 'a', 'r', '_', 'a', // map value
-
-    // map_a["key_foo"] = "foo_a", key = 1
-    0x0a, 0x10,
-    0x0a, 0x07, 'k', 'e', 'y', '_', 'f', 'o', 'o',
-    0x12, 0x05, 'f', 'o', 'o', '_', 'a',
-
-    // map_b["key_foo"] = "foo_b", key = 2
-    0x12, 0x10,
-    0x0a, 0x07, 'k', 'e', 'y', '_', 'f', 'o', 'o',
-    0x12, 0x05, 'f', 'o', 'o', 0x5f, 0x62,
-
-    // map_b["key_bar"] = "bar_b", key = 2
-    0x12, 0x10,
-    0x0a, 0x07, 'k', 'e', 'y', '_', 'b', 'a', 'r',
-    0x12, 0x05, 'b', 'a', 'r', 0x5f, 0x62,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  Message parser = Message(reader, sizeof(encoded_proto));
-
-  {
-    // Parse field 'map_a'
-    constexpr uint32_t kFieldNumber = 1;
-    StringMapParser<String> string_map =
-        parser.AsStringToStringMap(kFieldNumber);
-
-    String value = string_map["key_foo"];
-    ASSERT_OK(value.status());
-    Result<bool> cmp = value.Equal("foo_a");
-    ASSERT_OK(cmp.status());
-    ASSERT_TRUE(cmp.value());
-
-    value = string_map["key_bar"];
-    ASSERT_OK(value.status());
-    cmp = value.Equal("bar_a");
-    ASSERT_OK(cmp.status());
-    ASSERT_TRUE(cmp.value());
-
-    // Non-existing key
-    value = string_map["non-existing"];
-    ASSERT_EQ(value.status(), Status::NotFound());
-  }
-
-  {
-    // Parse field 'map_b'
-    constexpr uint32_t kFieldNumber = 2;
-    StringMapParser<String> string_map =
-        parser.AsStringToStringMap(kFieldNumber);
-
-    String value = string_map["key_foo"];
-    ASSERT_OK(value.status());
-    Result<bool> cmp = value.Equal("foo_b");
-    ASSERT_OK(cmp.status());
-    ASSERT_TRUE(cmp.value());
-
-    value = string_map["key_bar"];
-    ASSERT_OK(value.status());
-    cmp = value.Equal("bar_b");
-    ASSERT_OK(cmp.status());
-    ASSERT_TRUE(cmp.value());
-
-    // Non-existing key
-    value = string_map["non-existing"];
-    ASSERT_EQ(value.status(), Status::NotFound());
-  }
-}
-
-TEST(ProtoHelper, AsStringToMessageMap) {
-  // message Contact {
-  //   string number = 1;
-  //   string email = 2;
-  // }
-  //
-  // message Contacts {
-  //  map<string, Contact> staffs = 1;
-  // }
-  // clang-format off
-  std::uint8_t encoded_proto[] = {
-    // staffs['bar'] = {.number = '456, .email = "bar@email.com"}
-    0x0a, 0x1b,
-    0x0a, 0x03, 0x62, 0x61, 0x72,
-    0x12, 0x14, 0x0a, 0x03, 0x34, 0x35, 0x36, 0x12, 0x0d, 0x62, 0x61, 0x72, 0x40, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x63, 0x6f, 0x6d,
-
-    // staffs['foo'] = {.number = '123', .email = "foo@email.com"}
-    0x0a, 0x1b,
-    0x0a, 0x03, 0x66, 0x6f, 0x6f,
-    0x12, 0x14, 0x0a, 0x03, 0x31, 0x32, 0x33, 0x12, 0x0d, 0x66, 0x6f, 0x6f, 0x40, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x63, 0x6f, 0x6d,
-  };
-  // clang-format on
-  constexpr uint32_t kStaffsFieldId = 1;
-  constexpr uint32_t kNumberFieldId = 1;
-  constexpr uint32_t kEmailFieldId = 2;
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  Message parser = Message(reader, sizeof(encoded_proto));
-
-  StringMapParser<Message> staffs = parser.AsStringToMessageMap(kStaffsFieldId);
-  ASSERT_OK(staffs.status());
-
-  Message foo_staff = staffs["foo"];
-  ASSERT_OK(foo_staff.status());
-  String foo_number = foo_staff.AsString(kNumberFieldId);
-  ASSERT_OK(foo_number.status());
-  Result<bool> foo_number_cmp = foo_number.Equal("123");
-  ASSERT_OK(foo_number_cmp.status());
-  ASSERT_TRUE(foo_number_cmp.value());
-  String foo_email = foo_staff.AsString(kEmailFieldId);
-  ASSERT_OK(foo_email.status());
-  Result<bool> foo_email_cmp = foo_email.Equal("foo@email.com");
-  ASSERT_OK(foo_email_cmp.status());
-  ASSERT_TRUE(foo_email_cmp.value());
-
-  Message bar_staff = staffs["bar"];
-  ASSERT_OK(bar_staff.status());
-  String bar_number = bar_staff.AsString(kNumberFieldId);
-  ASSERT_OK(bar_number.status());
-  Result<bool> bar_number_cmp = bar_number.Equal("456");
-  ASSERT_OK(bar_number_cmp.status());
-  ASSERT_TRUE(bar_number_cmp.value());
-  String bar_email = bar_staff.AsString(kEmailFieldId);
-  ASSERT_OK(bar_email.status());
-  Result<bool> bar_email_cmp = bar_email.Equal("bar@email.com");
-  ASSERT_OK(bar_email_cmp.status());
-  ASSERT_TRUE(bar_email_cmp.value());
-}
-
-TEST(ProtoHelper, AsStringToBytesMapMalformed) {
-  // message Maps {
-  //   map<string, string> map_a = 1;
-  //   map<string, string> map_b = 2;
-  // }
-  // clang-format off
-  std::uint8_t encoded_proto[] = {
-    // map_a["key_bar"] = "bar_a", key = 1
-    0x0a, 0x10,
-    0x0a, 0x07, 'k', 'e', 'y', '_', 'b', 'a', 'r', // map key
-    0x12, 0x05, 'b', 'a', 'r', '_', 'a', // map value
-
-    // map_a["key_foo"] = "foo_a", key = 0 (invalid)
-    0x02, 0x10,
-    0x0a, 0x07, 'k', 'e', 'y', '_', 'f', 'o', 'o',
-    0x12, 0x05, 'f', 'o', 'o', '_', 'a',
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  Message parser = Message(reader, sizeof(encoded_proto));
-
-  // Parse field 'map_a'
-  constexpr uint32_t kFieldNumber = 1;
-  StringMapParser<String> string_map = parser.AsStringToStringMap(kFieldNumber);
-
-  bool expected_ok_status[] = {true, false};
-  size_t count = 0;
-  for (StringToStringMapEntry entry : string_map) {
-    ASSERT_EQ(entry.ok(), expected_ok_status[count]);
-    ASSERT_EQ(entry.Key().ok(), expected_ok_status[count]);
-    ASSERT_EQ(entry.Value().ok(), expected_ok_status[count]);
-    count++;
-  }
-  ASSERT_EQ(count, 2ULL);
-}
-
-}  // namespace pw::protobuf
diff --git a/pw_protobuf/public/pw_protobuf/bytes_utils.h b/pw_protobuf/public/pw_protobuf/bytes_utils.h
deleted file mode 100644
index d7c90bf..0000000
--- a/pw_protobuf/public/pw_protobuf/bytes_utils.h
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_bytes/endian.h"
-#include "pw_bytes/span.h"
-#include "pw_protobuf/decoder.h"
-#include "pw_result/result.h"
-#include "pw_status/status.h"
-#include "pw_status/try.h"
-
-namespace pw::protobuf {
-
-// Decodes a proto message bytes field to a uint32_t value. The caller must
-// advance the decoder and check the field number prior to calling this function
-// otherwise there is undefined behavior. E.g.
-//
-//   Decoder decoder(buffer);
-//     protobuf::Decoder decoder(request);
-//   if (!decoder.Next().ok()) {
-//     //  HANDLE ERROR.
-//   }
-//   if (static_cast<MyProtoMessage::Fields>(decoder.FieldNumber()) !=
-//       MyProtoMessage::Fields::MY_FIELD) {
-//     //  HANDLE ERROR.
-//   }
-//   Result<uint32_t> result = DecodeBytesToUint32(decoder);
-//   if (result.ok()) {
-//     //  DO SOMETHING WITH result.value().
-//   }
-//
-// Returns:
-// OK - Byte entry is successfully read.
-// DATA_LOSS: Invalid protobuf data.
-// INVALID_ARGUMENT - not able to read exactly 4 bytes.
-// FAILED_PRECONDITION - no bytes were read.
-inline Result<uint32_t> DecodeBytesToUint32(Decoder& decoder) {
-  ConstByteSpan bytes_read;
-  PW_TRY(decoder.ReadBytes(&bytes_read));
-  if (bytes_read.size() != sizeof(uint32_t)) {
-    return Status::InvalidArgument();
-  }
-  uint32_t value;
-  if (!bytes::ReadInOrder(std::endian::little, bytes_read, value)) {
-    return Status::Internal();
-  }
-  return value;
-}
-
-}  // namespace pw::protobuf
diff --git a/pw_protobuf/public/pw_protobuf/codegen.h b/pw_protobuf/public/pw_protobuf/codegen.h
new file mode 100644
index 0000000..103612f
--- /dev/null
+++ b/pw_protobuf/public/pw_protobuf/codegen.h
@@ -0,0 +1,44 @@
+// Copyright 2019 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_protobuf/encoder.h"
+
+namespace pw::protobuf {
+
+// Base class for generated encoders. Stores a reference to a low-level proto
+// encoder. If representing a nested message, knows the field number of the
+// message in its parent and and automatically calls Push and Pop when on the
+// encoder when created/destroyed.
+class ProtoMessageEncoder {
+ public:
+  ProtoMessageEncoder(Encoder* encoder, uint32_t parent_field = 0)
+      : encoder_(encoder), parent_field_(parent_field) {
+    if (parent_field_ != 0) {
+      encoder_->Push(parent_field_);
+    }
+  }
+
+  ~ProtoMessageEncoder() {
+    if (parent_field_ != 0) {
+      encoder_->Pop();
+    }
+  }
+
+ protected:
+  Encoder* encoder_;
+  uint32_t parent_field_;
+};
+
+}  // namespace pw::protobuf
diff --git a/pw_protobuf/public/pw_protobuf/config.h b/pw_protobuf/public/pw_protobuf/config.h
index a31744b..c7a12eb 100644
--- a/pw_protobuf/public/pw_protobuf/config.h
+++ b/pw_protobuf/public/pw_protobuf/config.h
@@ -41,4 +41,16 @@
 
 inline constexpr size_t kMaxVarintSize = PW_PROTOBUF_CFG_MAX_VARINT_SIZE;
 
+// TODO(frolv): This converts the configured varint length to the legacy encoder
+// SizeType. Remove this with the encoder rewrite.
+#if PW_PROTOBUF_CFG_MAX_VARINT_SIZE == 1
+using SizeType = uint8_t;
+#elif PW_PROTOBUF_CFG_MAX_VARINT_SIZE == 2
+using SizeType = uint16_t;
+#elif PW_PROTOBUF_CFG_MAX_VARINT_SIZE <= 4
+using SizeType = uint32_t;
+#else
+using SizeType = uint64_t;
+#endif
+
 }  // namespace pw::protobuf::config
diff --git a/pw_protobuf/public/pw_protobuf/decoder.h b/pw_protobuf/public/pw_protobuf/decoder.h
index 6f3c944..baa076c 100644
--- a/pw_protobuf/public/pw_protobuf/decoder.h
+++ b/pw_protobuf/public/pw_protobuf/decoder.h
@@ -42,7 +42,6 @@
 //
 namespace pw::protobuf {
 
-// TODO(frolv): Rename this to MemoryDecoder to match the encoder naming.
 class Decoder {
  public:
   constexpr Decoder(std::span<const std::byte> proto)
@@ -65,12 +64,6 @@
   Status Next();
 
   // Returns the field number of the field at the current cursor position.
-  //
-  // A return value of 0 indicates that the field number is invalid. An invalid
-  // field number terminates the decode operation; any subsequent calls to
-  // Next() or Read*() will return DATA_LOSS.
-  //
-  // TODO(frolv): This should be refactored to return a Result<uint32_t>.
   uint32_t FieldNumber() const;
 
   // Reads a proto int32 value from the current cursor.
diff --git a/pw_protobuf/public/pw_protobuf/encoder.h b/pw_protobuf/public/pw_protobuf/encoder.h
index f5b4273..fbf9acf 100644
--- a/pw_protobuf/public/pw_protobuf/encoder.h
+++ b/pw_protobuf/public/pw_protobuf/encoder.h
@@ -13,815 +13,369 @@
 // the License.
 #pragma once
 
-#include <algorithm>
-#include <array>
-#include <bit>
 #include <cstddef>
 #include <cstring>
 #include <span>
-#include <string_view>
 
-#include "pw_assert/assert.h"
-#include "pw_bytes/endian.h"
 #include "pw_bytes/span.h"
-#include "pw_containers/vector.h"
 #include "pw_protobuf/config.h"
 #include "pw_protobuf/wire_format.h"
-#include "pw_status/status.h"
+#include "pw_result/result.h"
 #include "pw_status/try.h"
-#include "pw_stream/memory_stream.h"
-#include "pw_stream/stream.h"
 #include "pw_varint/varint.h"
 
 namespace pw::protobuf {
 
-// Provides a size estimate to help with sizing buffers passed to
-// StreamEncoder and MemoryEncoder objects.
-//
-// Args:
-//   max_message_size: For MemoryEncoder objects, this is the max expected size
-//     of the final proto. For StreamEncoder objects, this should be the max
-//     size of any nested proto submessage that will be built with this encoder
-//     (recursively accumulating the size from the root submessage). If your
-//     proto will encode many large submessages, this value should just be the
-//     size of the largest one.
-//  max_nested_depth: The max number of nested submessage encoders that are
-//     expected to be open simultaneously to encode this proto message.
-constexpr size_t MaxScratchBufferSize(size_t max_message_size,
-                                      size_t max_nested_depth) {
-  return max_message_size + max_nested_depth * config::kMaxVarintSize;
-}
-
-// Write a varint value to the writer.
-//
-// Args:
-//   value: The value of the varint to write
-//   writer: The writer for writing to output.
-//
-// Returns:
-// OK - varint is written successfully
-//
-// Errors encountered by the `writer` will be returned as it is.
-inline Status WriteVarint(uint64_t value, stream::Writer& writer) {
-  std::array<std::byte, varint::kMaxVarint64SizeBytes> varint_encode_buffer;
-  const size_t varint_size =
-      pw::varint::EncodeLittleEndianBase128(value, varint_encode_buffer);
-  return writer.Write(std::span(varint_encode_buffer).first(varint_size));
-}
-
-// Write the field key and length prefix for a length-delimited field. It is
-// up to the caller to ensure that this will be followed by an exact number
-// of bytes written for the field in order to form a valid proto message.
-//
-// Args:
-//   field_number: The field number for the field.
-//   payload_size: The size of the payload.
-//   writer: The output writer to write to
-//
-//
-// Returns:
-// OK - Field key is written successfully
-//
-// Errors encountered by the `writer` will be returned as it is.
-//
-// Precondition: The field_number must be a ValidFieldNumber.
-// Precondition: `data_size_bytes` must be smaller than
-//   std::numeric_limits<uint32_t>::max()
-inline Status WriteLengthDelimitedKeyAndLengthPrefix(uint32_t field_number,
-                                                     size_t payload_size,
-                                                     stream::Writer& writer) {
-  PW_TRY(WriteVarint(FieldKey(field_number, WireType::kDelimited), writer));
-  return WriteVarint(payload_size, writer);
-}
-
-// Forward declaration. StreamEncoder and MemoryEncoder are very tightly
-// coupled.
-class MemoryEncoder;
-
-// A protobuf encoder that encodes serialized proto data to a
-// pw::stream::Writer.
-class StreamEncoder {
+// A streaming protobuf encoder which encodes to a user-specified buffer.
+class Encoder {
  public:
-  // The StreamEncoder will serialize proto data to the pw::stream::Writer
-  // provided through the constructor. The scratch buffer provided is for
-  // internal use ONLY and should not be considered valid proto data.
-  //
-  // If a StreamEncoder object will be writing nested proto messages, it must
-  // provide a scratch buffer large enough to hold the largest submessage some
-  // additional overhead incurred by the encoder's implementation. It's a good
-  // idea to be generous when sizing this buffer. MaxScratchBufferSize() can be
-  // helpful in providing an estimated size for this buffer. The scratch buffer
-  // must exist for the lifetime of the StreamEncoder object.
-  //
-  // StreamEncoder objects that do not write nested proto messages can
-  // provide a zero-length scratch buffer.
-  constexpr StreamEncoder(stream::Writer& writer, ByteSpan scratch_buffer)
-      : status_(OkStatus()),
-        parent_(nullptr),
-        nested_field_number_(0),
-        memory_writer_(scratch_buffer),
-        writer_(writer) {}
+  using SizeType = config::SizeType;
 
-  // Precondition: Encoder has no active child encoder.
-  //
-  // Postcondition: If this encoder is a nested one, the parent encoder is
-  //     unlocked and proto encoding may resume on the parent.
-  ~StreamEncoder();
+  constexpr Encoder(ByteSpan buffer,
+                    std::span<SizeType*> locations,
+                    std::span<SizeType*> stack)
+      : buffer_(buffer),
+        cursor_(buffer.data()),
+        blob_locations_(locations),
+        blob_count_(0),
+        blob_stack_(stack),
+        depth_(0),
+        encode_status_(OkStatus()) {}
 
   // Disallow copy/assign to avoid confusion about who owns the buffer.
-  StreamEncoder& operator=(const StreamEncoder& other) = delete;
-  StreamEncoder(const StreamEncoder& other) = delete;
+  Encoder(const Encoder& other) = delete;
+  Encoder& operator=(const Encoder& other) = delete;
 
-  // It's not safe to move an encoder as it could cause another encoder's
-  // parent_ pointer to become invalid.
-  StreamEncoder& operator=(StreamEncoder&& other) = delete;
-
-  // Forwards the conservative write limit of the underlying
-  // pw::stream::Writer.
-  //
-  // Precondition: Encoder has no active child encoder.
-  size_t ConservativeWriteLimit() const {
-    PW_ASSERT(!nested_encoder_open());
-    return writer_.ConservativeWriteLimit();
-  }
-
-  // Creates a nested encoder with the provided field number. Once this is
-  // called, the parent encoder is locked and not available for use until the
-  // nested encoder is finalized (either explicitly or through destruction).
-  //
-  // Precondition: Encoder has no active child encoder.
-  //
-  // Postcondition: Until the nested child encoder has been destroyed, this
-  //     encoder cannot be used.
-  StreamEncoder GetNestedEncoder(uint32_t field_number);
-
-  // Returns the current encoder's status.
-  //
-  // Precondition: Encoder has no active child encoder.
-  Status status() const {
-    PW_ASSERT(!nested_encoder_open());
-    return status_;
-  }
+  // Per the protobuf specification, valid field numbers range between 1 and
+  // 2**29 - 1, inclusive. The numbers 19000-19999 are reserved for internal
+  // use.
+  constexpr static uint32_t kMaxFieldNumber = (1u << 29) - 1;
+  constexpr static uint32_t kFirstReservedNumber = 19000;
+  constexpr static uint32_t kLastReservedNumber = 19999;
 
   // Writes a proto uint32 key-value pair.
-  //
-  // Precondition: Encoder has no active child encoder.
   Status WriteUint32(uint32_t field_number, uint32_t value) {
     return WriteUint64(field_number, value);
   }
 
   // Writes a repeated uint32 using packed encoding.
-  //
-  // Precondition: Encoder has no active child encoder.
   Status WritePackedUint32(uint32_t field_number,
                            std::span<const uint32_t> values) {
-    return WritePackedVarints(field_number, values, VarintEncodeType::kNormal);
-  }
-
-  // Writes a repeated uint32 using packed encoding.
-  //
-  // Precondition: Encoder has no active child encoder.
-  Status WriteRepeatedUint32(uint32_t field_number,
-                             const pw::Vector<uint32_t>& values) {
-    return WritePackedVarints(field_number,
-                              std::span(values.data(), values.size()),
-                              VarintEncodeType::kNormal);
+    return WritePackedVarints(field_number, values, /*zigzag=*/false);
   }
 
   // Writes a proto uint64 key-value pair.
-  //
-  // Precondition: Encoder has no active child encoder.
-  Status WriteUint64(uint32_t field_number, uint64_t value) {
-    return WriteVarintField(field_number, value);
-  }
+  Status WriteUint64(uint32_t field_number, uint64_t value);
 
   // Writes a repeated uint64 using packed encoding.
-  //
-  // Precondition: Encoder has no active child encoder.
   Status WritePackedUint64(uint64_t field_number,
                            std::span<const uint64_t> values) {
-    return WritePackedVarints(field_number, values, VarintEncodeType::kNormal);
-  }
-
-  // Writes a repeated uint64 using packed encoding.
-  //
-  // Precondition: Encoder has no active child encoder.
-  Status WriteRepeatedUint64(uint32_t field_number,
-                             const pw::Vector<uint64_t>& values) {
-    return WritePackedVarints(field_number,
-                              std::span(values.data(), values.size()),
-                              VarintEncodeType::kNormal);
+    return WritePackedVarints(field_number, values, /*zigzag=*/false);
   }
 
   // Writes a proto int32 key-value pair.
-  //
-  // Precondition: Encoder has no active child encoder.
   Status WriteInt32(uint32_t field_number, int32_t value) {
     return WriteUint64(field_number, value);
   }
 
   // Writes a repeated int32 using packed encoding.
-  //
-  // Precondition: Encoder has no active child encoder.
   Status WritePackedInt32(uint32_t field_number,
                           std::span<const int32_t> values) {
     return WritePackedVarints(
         field_number,
         std::span(reinterpret_cast<const uint32_t*>(values.data()),
                   values.size()),
-        VarintEncodeType::kNormal);
-  }
-
-  // Writes a repeated int32 using packed encoding.
-  //
-  // Precondition: Encoder has no active child encoder.
-  Status WriteRepeatedInt32(uint32_t field_number,
-                            const pw::Vector<int32_t>& values) {
-    return WritePackedVarints(
-        field_number,
-        std::span(reinterpret_cast<const uint32_t*>(values.data()),
-                  values.size()),
-        VarintEncodeType::kNormal);
+        /*zigzag=*/false);
   }
 
   // Writes a proto int64 key-value pair.
-  //
-  // Precondition: Encoder has no active child encoder.
   Status WriteInt64(uint32_t field_number, int64_t value) {
     return WriteUint64(field_number, value);
   }
 
   // Writes a repeated int64 using packed encoding.
-  //
-  // Precondition: Encoder has no active child encoder.
   Status WritePackedInt64(uint32_t field_number,
                           std::span<const int64_t> values) {
     return WritePackedVarints(
         field_number,
         std::span(reinterpret_cast<const uint64_t*>(values.data()),
                   values.size()),
-        VarintEncodeType::kNormal);
-  }
-
-  // Writes a repeated int64 using packed encoding.
-  //
-  // Precondition: Encoder has no active child encoder.
-  Status WriteRepeatedInt64(uint32_t field_number,
-                            const pw::Vector<int64_t>& values) {
-    return WritePackedVarints(
-        field_number,
-        std::span(reinterpret_cast<const uint64_t*>(values.data()),
-                  values.size()),
-        VarintEncodeType::kNormal);
+        /*zigzag=*/false);
   }
 
   // Writes a proto sint32 key-value pair.
-  //
-  // Precondition: Encoder has no active child encoder.
   Status WriteSint32(uint32_t field_number, int32_t value) {
     return WriteUint64(field_number, varint::ZigZagEncode(value));
   }
 
   // Writes a repeated sint32 using packed encoding.
-  //
-  // Precondition: Encoder has no active child encoder.
   Status WritePackedSint32(uint32_t field_number,
                            std::span<const int32_t> values) {
     return WritePackedVarints(
         field_number,
         std::span(reinterpret_cast<const uint32_t*>(values.data()),
                   values.size()),
-        VarintEncodeType::kZigZag);
-  }
-
-  // Writes a repeated sint32 using packed encoding.
-  //
-  // Precondition: Encoder has no active child encoder.
-  Status WriteRepeatedSint32(uint32_t field_number,
-                             const pw::Vector<int32_t>& values) {
-    return WritePackedVarints(
-        field_number,
-        std::span(reinterpret_cast<const uint32_t*>(values.data()),
-                  values.size()),
-        VarintEncodeType::kZigZag);
+        /*zigzag=*/true);
   }
 
   // Writes a proto sint64 key-value pair.
-  //
-  // Precondition: Encoder has no active child encoder.
   Status WriteSint64(uint32_t field_number, int64_t value) {
     return WriteUint64(field_number, varint::ZigZagEncode(value));
   }
 
   // Writes a repeated sint64 using packed encoding.
-  //
-  // Precondition: Encoder has no active child encoder.
   Status WritePackedSint64(uint32_t field_number,
                            std::span<const int64_t> values) {
     return WritePackedVarints(
         field_number,
         std::span(reinterpret_cast<const uint64_t*>(values.data()),
                   values.size()),
-        VarintEncodeType::kZigZag);
-  }
-
-  // Writes a repeated sint64 using packed encoding.
-  //
-  // Precondition: Encoder has no active child encoder.
-  Status WriteRepeatedSint64(uint32_t field_number,
-                             const pw::Vector<int64_t>& values) {
-    return WritePackedVarints(
-        field_number,
-        std::span(reinterpret_cast<const uint64_t*>(values.data()),
-                  values.size()),
-        VarintEncodeType::kZigZag);
+        /*zigzag=*/true);
   }
 
   // Writes a proto bool key-value pair.
-  //
-  // Precondition: Encoder has no active child encoder.
   Status WriteBool(uint32_t field_number, bool value) {
     return WriteUint32(field_number, static_cast<uint32_t>(value));
   }
 
-  // Writes a repeated bool using packed encoding.
-  //
-  // Precondition: Encoder has no active child encoder.
-  Status WritePackedBool(uint32_t field_number, std::span<const bool> values) {
-    static_assert(sizeof(bool) == sizeof(uint8_t),
-                  "bool must be same size as uint8_t");
-    return WritePackedVarints(
-        field_number,
-        std::span(reinterpret_cast<const uint8_t*>(values.data()),
-                  values.size()),
-        VarintEncodeType::kNormal);
-  }
-
-  // Writes a repeated bool using packed encoding.
-  //
-  // Precondition: Encoder has no active child encoder.
-  Status WriteRepeatedBool(uint32_t field_number,
-                           const pw::Vector<bool>& values) {
-    static_assert(sizeof(bool) == sizeof(uint8_t),
-                  "bool must be same size as uint8_t");
-
-    return WritePackedVarints(
-        field_number,
-        std::span(reinterpret_cast<const uint8_t*>(values.data()),
-                  values.size()),
-        VarintEncodeType::kNormal);
-  }
-
   // Writes a proto fixed32 key-value pair.
-  //
-  // Precondition: Encoder has no active child encoder.
   Status WriteFixed32(uint32_t field_number, uint32_t value) {
-    std::array<std::byte, sizeof(value)> data =
-        bytes::CopyInOrder(std::endian::little, value);
-    return WriteFixed(field_number, data);
+    std::byte* original_cursor = cursor_;
+    WriteFieldKey(field_number, WireType::kFixed32);
+    WriteRawBytes(value);
+    return IncreaseParentSize(cursor_ - original_cursor);
   }
 
   // Writes a repeated fixed32 field using packed encoding.
-  //
-  // Precondition: Encoder has no active child encoder.
   Status WritePackedFixed32(uint32_t field_number,
                             std::span<const uint32_t> values) {
-    return WritePackedFixed(
-        field_number, std::as_bytes(values), sizeof(uint32_t));
-  }
-
-  // Writes a repeated fixed32 field using packed encoding.
-  //
-  // Precondition: Encoder has no active child encoder.
-  Status WriteRepeatedFixed32(uint32_t field_number,
-                              const pw::Vector<uint32_t>& values) {
-    return WritePackedFixed(
-        field_number,
-        std::as_bytes(std::span(values.data(), values.size())),
-        sizeof(uint32_t));
+    return WriteBytes(field_number, std::as_bytes(values));
   }
 
   // Writes a proto fixed64 key-value pair.
-  //
-  // Precondition: Encoder has no active child encoder.
   Status WriteFixed64(uint32_t field_number, uint64_t value) {
-    std::array<std::byte, sizeof(value)> data =
-        bytes::CopyInOrder(std::endian::little, value);
-    return WriteFixed(field_number, data);
+    std::byte* original_cursor = cursor_;
+    WriteFieldKey(field_number, WireType::kFixed64);
+    WriteRawBytes(value);
+    return IncreaseParentSize(cursor_ - original_cursor);
   }
 
   // Writes a repeated fixed64 field using packed encoding.
-  //
-  // Precondition: Encoder has no active child encoder.
   Status WritePackedFixed64(uint32_t field_number,
                             std::span<const uint64_t> values) {
-    return WritePackedFixed(
-        field_number, std::as_bytes(values), sizeof(uint64_t));
-  }
-
-  // Writes a repeated fixed64 field using packed encoding.
-  //
-  // Precondition: Encoder has no active child encoder.
-  Status WriteRepeatedFixed64(uint32_t field_number,
-                              const pw::Vector<uint64_t>& values) {
-    return WritePackedFixed(
-        field_number,
-        std::as_bytes(std::span(values.data(), values.size())),
-        sizeof(uint64_t));
+    return WriteBytes(field_number, std::as_bytes(values));
   }
 
   // Writes a proto sfixed32 key-value pair.
-  //
-  // Precondition: Encoder has no active child encoder.
   Status WriteSfixed32(uint32_t field_number, int32_t value) {
     return WriteFixed32(field_number, static_cast<uint32_t>(value));
   }
 
   // Writes a repeated sfixed32 field using packed encoding.
-  //
-  // Precondition: Encoder has no active child encoder.
   Status WritePackedSfixed32(uint32_t field_number,
                              std::span<const int32_t> values) {
-    return WritePackedFixed(
-        field_number, std::as_bytes(values), sizeof(int32_t));
-  }
-
-  // Writes a repeated fixed32 field using packed encoding.
-  //
-  // Precondition: Encoder has no active child encoder.
-  Status WriteRepeatedSfixed32(uint32_t field_number,
-                               const pw::Vector<int32_t>& values) {
-    return WritePackedFixed(
-        field_number,
-        std::as_bytes(std::span(values.data(), values.size())),
-        sizeof(int32_t));
+    return WriteBytes(field_number, std::as_bytes(values));
   }
 
   // Writes a proto sfixed64 key-value pair.
-  //
-  // Precondition: Encoder has no active child encoder.
   Status WriteSfixed64(uint32_t field_number, int64_t value) {
     return WriteFixed64(field_number, static_cast<uint64_t>(value));
   }
 
   // Writes a repeated sfixed64 field using packed encoding.
-  //
-  // Precondition: Encoder has no active child encoder.
   Status WritePackedSfixed64(uint32_t field_number,
                              std::span<const int64_t> values) {
-    return WritePackedFixed(
-        field_number, std::as_bytes(values), sizeof(int64_t));
-  }
-
-  // Writes a repeated fixed64 field using packed encoding.
-  //
-  // Precondition: Encoder has no active child encoder.
-  Status WriteRepeatedFixed64(uint32_t field_number,
-                              const pw::Vector<int64_t>& values) {
-    return WritePackedFixed(
-        field_number,
-        std::as_bytes(std::span(values.data(), values.size())),
-        sizeof(int64_t));
+    return WriteBytes(field_number, std::as_bytes(values));
   }
 
   // Writes a proto float key-value pair.
-  //
-  // Precondition: Encoder has no active child encoder.
   Status WriteFloat(uint32_t field_number, float value) {
     static_assert(sizeof(float) == sizeof(uint32_t),
                   "Float and uint32_t are not the same size");
-    uint32_t integral_value;
-    std::memcpy(&integral_value, &value, sizeof(value));
-    std::array<std::byte, sizeof(value)> data =
-        bytes::CopyInOrder(std::endian::little, integral_value);
-    return WriteFixed(field_number, data);
+    std::byte* original_cursor = cursor_;
+    WriteFieldKey(field_number, WireType::kFixed32);
+    WriteRawBytes(value);
+    return IncreaseParentSize(cursor_ - original_cursor);
   }
 
   // Writes a repeated float field using packed encoding.
-  //
-  // Precondition: Encoder has no active child encoder.
   Status WritePackedFloat(uint32_t field_number,
                           std::span<const float> values) {
-    return WritePackedFixed(field_number, std::as_bytes(values), sizeof(float));
-  }
-
-  // Writes a repeated float field using packed encoding.
-  //
-  // Precondition: Encoder has no active child encoder.
-  Status WriteRepeatedFloat(uint32_t field_number,
-                            const pw::Vector<float>& values) {
-    return WritePackedFixed(
-        field_number,
-        std::as_bytes(std::span(values.data(), values.size())),
-        sizeof(float));
+    return WriteBytes(field_number, std::as_bytes(values));
   }
 
   // Writes a proto double key-value pair.
-  //
-  // Precondition: Encoder has no active child encoder.
   Status WriteDouble(uint32_t field_number, double value) {
     static_assert(sizeof(double) == sizeof(uint64_t),
                   "Double and uint64_t are not the same size");
-    uint64_t integral_value;
-    std::memcpy(&integral_value, &value, sizeof(value));
-    std::array<std::byte, sizeof(value)> data =
-        bytes::CopyInOrder(std::endian::little, integral_value);
-    return WriteFixed(field_number, data);
+    std::byte* original_cursor = cursor_;
+    WriteFieldKey(field_number, WireType::kFixed64);
+    WriteRawBytes(value);
+    return IncreaseParentSize(cursor_ - original_cursor);
   }
 
   // Writes a repeated double field using packed encoding.
-  //
-  // Precondition: Encoder has no active child encoder.
   Status WritePackedDouble(uint32_t field_number,
                            std::span<const double> values) {
-    return WritePackedFixed(
-        field_number, std::as_bytes(values), sizeof(double));
+    return WriteBytes(field_number, std::as_bytes(values));
   }
 
-  // Writes a repeated double field using packed encoding.
-  //
-  // Precondition: Encoder has no active child encoder.
-  Status WriteRepeatedDouble(uint32_t field_number,
-                             const pw::Vector<double>& values) {
-    return WritePackedFixed(
-        field_number,
-        std::as_bytes(std::span(values.data(), values.size())),
-        sizeof(double));
-  }
-
-  // Writes a proto `bytes` field as a key-value pair. This can also be used to
-  // write a pre-encoded nested submessage directly without using a nested
-  // encoder.
-  //
-  // Precondition: Encoder has no active child encoder.
+  // Writes a proto bytes key-value pair.
   Status WriteBytes(uint32_t field_number, ConstByteSpan value) {
-    return WriteLengthDelimitedField(field_number, value);
-  }
-
-  // Writes a proto 'bytes' field from the stream bytes_reader.
-  //
-  // The payload for the value is provided through the stream::Reader
-  // `bytes_reader`. The method reads a chunk of the data from the reader using
-  // the `stream_pipe_buffer` and writes it to the encoder.
-  //
-  // Precondition: The stream_pipe_buffer.byte_size() >= 1
-  // Precondition: Encoder has no active child encoder.
-  //
-  // Returns:
-  // OK - Bytes field is written successfully.
-  // RESOURCE_EXHAUSTED - Exceeds write limits.
-  // OUT_OF_RANGE - `bytes_reader` is exhausted before `num_bytes` of
-  //                bytes is read.
-  //
-  // Other errors encountered by the writer will be returned as it is.
-  Status WriteBytesFromStream(uint32_t field_number,
-                              stream::Reader& bytes_reader,
-                              size_t num_bytes,
-                              ByteSpan stream_pipe_buffer) {
-    return WriteLengthDelimitedFieldFromStream(
-        field_number, bytes_reader, num_bytes, stream_pipe_buffer);
+    std::byte* original_cursor = cursor_;
+    WriteFieldKey(field_number, WireType::kDelimited);
+    WriteVarint(value.size_bytes());
+    WriteRawBytes(value.data(), value.size_bytes());
+    return IncreaseParentSize(cursor_ - original_cursor);
   }
 
   // Writes a proto string key-value pair.
-  //
-  // Precondition: Encoder has no active child encoder.
-  Status WriteString(uint32_t field_number, std::string_view value) {
-    return WriteBytes(field_number, std::as_bytes(std::span(value)));
+  Status WriteString(uint32_t field_number, const char* value, size_t size) {
+    return WriteBytes(field_number, std::as_bytes(std::span(value, size)));
   }
 
-  // Writes a proto string key-value pair.
-  //
-  // Precondition: Encoder has no active child encoder.
-  Status WriteString(uint32_t field_number, const char* value, size_t len) {
-    return WriteBytes(field_number, std::as_bytes(std::span(value, len)));
+  Status WriteString(uint32_t field_number, const char* value) {
+    return WriteString(field_number, value, strlen(value));
   }
 
-  // Writes a proto 'string' field from the stream bytes_reader.
-  //
-  // The payload for the value is provided through the stream::Reader
-  // `bytes_reader`. The method reads a chunk of the data from the reader using
-  // the `stream_pipe_buffer` and writes it to the encoder.
-  //
-  // Precondition: The stream_pipe_buffer.byte_size() >= 1
-  // Precondition: Encoder has no active child encoder.
-  //
-  // Returns:
-  // OK - String field is written successfully.
-  // RESOURCE_EXHAUSTED - Exceeds write limits.
-  // OUT_OF_RANGE - `bytes_reader` is exhausted before `num_bytes` of
-  //                bytes is read.
-  //
-  // Other errors encountered by the writer will be returned as it is.
-  Status WriteStringFromStream(uint32_t field_number,
-                               stream::Reader& bytes_reader,
-                               size_t num_bytes,
-                               ByteSpan stream_pipe_buffer) {
-    return WriteBytesFromStream(
-        field_number, bytes_reader, num_bytes, stream_pipe_buffer);
+  // Begins writing a sub-message with a specified field number.
+  Status Push(uint32_t field_number);
+
+  // Finishes writing a sub-message.
+  Status Pop();
+
+  // Returns the total encoded size of the proto message.
+  size_t EncodedSize() const { return cursor_ - buffer_.data(); }
+
+  // Returns the number of bytes remaining in the buffer.
+  size_t RemainingSize() const { return buffer_.size() - EncodedSize(); }
+
+  // Resets write index to the start of the buffer. This invalidates any spans
+  // obtained from Encode().
+  void Clear() {
+    cursor_ = buffer_.data();
+    encode_status_ = OkStatus();
+    blob_count_ = 0;
+    depth_ = 0;
   }
 
- protected:
-  // Specialized move constructor used only for codegen.
-  //
-  // Postcondition: The other encoder is invalidated and cannot be used as it
-  //     acts like a parent encoder with an active child encoder.
-  constexpr StreamEncoder(StreamEncoder&& other)
-      : status_(other.status_),
-        parent_(other.parent_),
-        nested_field_number_(other.nested_field_number_),
-        memory_writer_(std::move(other.memory_writer_)),
-        writer_(&other.writer_ == &other.memory_writer_ ? memory_writer_
-                                                        : other.writer_) {
-    PW_ASSERT(nested_field_number_ == 0);
-    // Make the nested encoder look like it has an open child to block writes
-    // for the remainder of the object's life.
-    other.nested_field_number_ = kFirstReservedNumber;
-    other.parent_ = nullptr;
+  // Runs a final encoding pass over the intermediary data and returns the
+  // encoded protobuf message.
+  Result<ConstByteSpan> Encode();
+
+  // DEPRECATED. Use Encode() instead.
+  // TODO(frolv): Remove this after all references to it are updated.
+  Status Encode(ConstByteSpan* out) {
+    Result result = Encode();
+    if (!result.ok()) {
+      return result.status();
+    }
+    *out = result.value();
+    return OkStatus();
   }
 
  private:
-  friend class MemoryEncoder;
-
-  enum class VarintEncodeType {
-    kNormal,
-    kZigZag,
-  };
-
-  constexpr StreamEncoder(StreamEncoder& parent, ByteSpan scratch_buffer)
-      : status_(scratch_buffer.empty() ? Status::ResourceExhausted()
-                                       : OkStatus()),
-        parent_(&parent),
-        nested_field_number_(0),
-        memory_writer_(scratch_buffer),
-        writer_(memory_writer_) {}
-
-  bool nested_encoder_open() const { return nested_field_number_ != 0; }
-
-  // CloseNestedMessage() is called on the parent encoder as part of the nested
-  // encoder destructor.
-  void CloseNestedMessage(StreamEncoder& nested);
-
-  // Implementation for encoding all varint field types.
-  Status WriteVarintField(uint32_t field_number, uint64_t value);
-
-  // Implementation for encoding all length-delimited field types.
-  Status WriteLengthDelimitedField(uint32_t field_number, ConstByteSpan data);
-
-  // Encoding of length-delimited field where payload comes from `bytes_reader`.
-  Status WriteLengthDelimitedFieldFromStream(uint32_t field_number,
-                                             stream::Reader& bytes_reader,
-                                             size_t num_bytes,
-                                             ByteSpan stream_pipe_buffer);
-
-  // Implementation for encoding all fixed-length integer types.
-  Status WriteFixed(uint32_t field_number, ConstByteSpan data);
-
-  // Encodes a base-128 varint to the buffer. This function assumes the caller
-  // has already checked UpdateStatusForWrite() to ensure the writer's
-  // conservative write limit indicates the Writer has sufficient buffer space.
-  Status WriteVarint(uint64_t value) {
-    PW_TRY(status_);
-    status_.Update(::pw::protobuf::WriteVarint(value, writer_));
-    return status_;
+  constexpr bool ValidFieldNumber(uint32_t field_number) const {
+    return field_number != 0 && field_number <= kMaxFieldNumber &&
+           !(field_number >= kFirstReservedNumber &&
+             field_number <= kLastReservedNumber);
   }
 
+  // Encodes the key for a proto field consisting of its number and wire type.
+  Status WriteFieldKey(uint32_t field_number, WireType wire_type) {
+    if (!ValidFieldNumber(field_number)) {
+      encode_status_ = Status::InvalidArgument();
+      return encode_status_;
+    }
+
+    return WriteVarint(MakeKey(field_number, wire_type));
+  }
+
+  Status WriteVarint(uint64_t value);
+
   Status WriteZigzagVarint(int64_t value) {
     return WriteVarint(varint::ZigZagEncode(value));
   }
 
-  // Writes a list of varints to the buffer in length-delimited packed encoding.
-  template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
-  Status WritePackedVarints(uint32_t field_number,
-                            std::span<T> values,
-                            VarintEncodeType encode_type) {
-    static_assert(std::is_same<T, const uint8_t>::value ||
-                      std::is_same<T, const uint32_t>::value ||
-                      std::is_same<T, const int32_t>::value ||
-                      std::is_same<T, const uint64_t>::value ||
-                      std::is_same<T, const int64_t>::value,
-                  "Packed varints must be of type bool, uint32_t, int32_t, "
-                  "uint64_t, or int64_t");
-
-    size_t payload_size = 0;
-    for (T val : values) {
-      if (encode_type == VarintEncodeType::kZigZag) {
-        int64_t integer =
-            static_cast<int64_t>(static_cast<std::make_signed_t<T>>(val));
-        payload_size += varint::EncodedSize(varint::ZigZagEncode(integer));
-      } else {
-        uint64_t integer = static_cast<uint64_t>(val);
-        payload_size += varint::EncodedSize(integer);
-      }
-    }
-
-    if (!UpdateStatusForWrite(field_number, WireType::kDelimited, payload_size)
-             .ok()) {
-      return status_;
-    }
-
-    WriteVarint(FieldKey(field_number, WireType::kDelimited))
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    WriteVarint(payload_size)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    for (T value : values) {
-      if (encode_type == VarintEncodeType::kZigZag) {
-        WriteZigzagVarint(static_cast<std::make_signed_t<T>>(value))
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-      } else {
-        WriteVarint(value)
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-      }
-    }
-
-    return status_;
+  template <typename T>
+  Status WriteRawBytes(const T& value) {
+    return WriteRawBytes(reinterpret_cast<const std::byte*>(&value),
+                         sizeof(value));
   }
 
-  // Writes a list of fixed-size types to the buffer in length-delimited
-  // packed encoding. Only float, double, uint32_t, int32_t, uint64_t, and
-  // int64_t are permitted
-  Status WritePackedFixed(uint32_t field_number,
-                          std::span<const std::byte> values,
-                          size_t elem_size);
+  Status WriteRawBytes(const std::byte* ptr, size_t size);
 
-  // Checks if a write is invalid or will cause the encoder to enter an error
-  // state, and preemptively sets this encoder's status to that error to block
-  // the write. Only the first error encountered is tracked.
-  //
-  // Precondition: Encoder has no active child encoder.
-  //
-  // Returns:
-  //   InvalidArgument: The field number provided was invalid.
-  //   ResourceExhausted: The requested write would have exceeded the
-  //     stream::Writer's conservative write limit.
-  //   Other: If any Write() operations on the stream::Writer caused an error,
-  //     that error will be repeated here.
-  Status UpdateStatusForWrite(uint32_t field_number,
-                              WireType type,
-                              size_t data_size);
+  // Writes a list of varints to the buffer in length-delimited packed encoding.
+  // If zigzag is true, zig-zag encodes each of the varints.
+  template <typename T>
+  Status WritePackedVarints(uint32_t field_number,
+                            std::span<T> values,
+                            bool zigzag) {
+    if (Status status = Push(field_number); !status.ok()) {
+      return status;
+    }
 
-  // The current encoder status. This status is only updated to reflect the
-  // first error encountered. Any further write operations are blocked when the
-  // encoder enters an error state.
-  Status status_;
+    std::byte* original_cursor = cursor_;
+    for (T value : values) {
+      if (zigzag) {
+        WriteZigzagVarint(static_cast<std::make_signed_t<T>>(value));
+      } else {
+        WriteVarint(value);
+      }
+    }
+    PW_TRY(IncreaseParentSize(cursor_ - original_cursor));
 
-  // If this is a nested encoder, this points to the encoder that created it.
-  // For user-created MemoryEncoders, parent_ points to this object as an
-  // optimization for the MemoryEncoder and nested encoders to use the same
-  // underlying buffer.
-  StreamEncoder* parent_;
+    return Pop();
+  }
 
-  // If an encoder has a child encoder open, this is the field number of that
-  // submessage. Otherwise, this is 0 to indicate no child encoder is open.
-  uint32_t nested_field_number_;
+  // Adds to the parent proto's size field in the buffer.
+  Status IncreaseParentSize(size_t size_bytes);
 
-  // This memory writer is used for staging proto submessages to the
-  // scratch_buffer.
-  stream::MemoryWriter memory_writer_;
+  // Returns the size of `n` encoded as a varint.
+  size_t VarintSizeBytes(uint64_t n) {
+    size_t size_bytes = 1;
+    while (n > 127) {
+      ++size_bytes;
+      n >>= 7;
+    }
+    return size_bytes;
+  }
 
-  // All proto encode operations are directly written to this writer.
-  stream::Writer& writer_;
+  // Do the actual (potentially partial) encoding. Also used in Pop().
+  Result<ConstByteSpan> EncodeFrom(size_t blob);
+
+  // The buffer into which the proto is encoded.
+  ByteSpan buffer_;
+  std::byte* cursor_;
+
+  // List of pointers to sub-messages' delimiting size fields.
+  std::span<SizeType*> blob_locations_;
+  size_t blob_count_;
+
+  // Stack of current nested message size locations. Push() operations add a new
+  // entry to this stack and Pop() operations remove one.
+  std::span<SizeType*> blob_stack_;
+  size_t depth_;
+
+  Status encode_status_;
 };
 
-// A protobuf encoder that writes directly to a provided buffer.
-//
-// Example:
-//
-//   // Writes a proto response to the provided buffer, returning the encode
-//   // status and number of bytes written.
-//   StatusWithSize WriteProtoResponse(ByteSpan response) {
-//     // All proto writes are directly written to the `response` buffer.
-//     MemoryEncoder encoder(response);
-//     encoder.WriteUint32(kMagicNumberField, 0x1a1a2b2b);
-//     encoder.WriteString(kFavoriteFood, "cookies");
-//     return StatusWithSize(encoder.status(), encoder.size());
-//   }
-//
-// Note: Avoid using a MemoryEncoder reference as an argument for a function.
-// The StreamEncoder is more generic.
-class MemoryEncoder : public StreamEncoder {
+// A proto encoder with its own blob stack.
+template <size_t kMaxNestedDepth = 1, size_t kMaxBlobs = kMaxNestedDepth>
+class NestedEncoder : public Encoder {
  public:
-  constexpr MemoryEncoder(ByteSpan dest) : StreamEncoder(*this, dest) {}
-
-  // Precondition: Encoder has no active child encoder.
-  //
-  // Postcondition: If this encoder is a nested one, the parent encoder is
-  //     unlocked and proto encoding may resume on the parent.
-  ~MemoryEncoder() = default;
+  NestedEncoder(ByteSpan buffer) : Encoder(buffer, blobs_, stack_) {}
 
   // Disallow copy/assign to avoid confusion about who owns the buffer.
-  MemoryEncoder(const MemoryEncoder& other) = delete;
-  MemoryEncoder& operator=(const MemoryEncoder& other) = delete;
+  NestedEncoder(const NestedEncoder& other) = delete;
+  NestedEncoder& operator=(const NestedEncoder& other) = delete;
 
-  // It's not safe to move an encoder as it could cause another encoder's
-  // parent_ pointer to become invalid.
-  MemoryEncoder& operator=(MemoryEncoder&& other) = delete;
-
-  const std::byte* data() const { return memory_writer_.data(); }
-  size_t size() const { return memory_writer_.bytes_written(); }
-
- protected:
-  // This is needed by codegen.
-  MemoryEncoder(MemoryEncoder&& other) = default;
+ private:
+  std::array<Encoder::SizeType*, kMaxBlobs> blobs_;
+  std::array<Encoder::SizeType*, kMaxNestedDepth> stack_;
 };
 
+// Explicit template argument deduction to hide warnings.
+NestedEncoder()->NestedEncoder<>;
+
 }  // namespace pw::protobuf
diff --git a/pw_protobuf/public/pw_protobuf/internal/proto_integer_base.h b/pw_protobuf/public/pw_protobuf/internal/proto_integer_base.h
deleted file mode 100644
index 2e81f3c..0000000
--- a/pw_protobuf/public/pw_protobuf/internal/proto_integer_base.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-//
-// The header provides a set of helper utils for protobuf related operations.
-// The APIs may not be finalized yet.
-
-#pragma once
-
-#include "pw_result/result.h"
-
-namespace pw::protobuf::internal {
-
-// A base class for representing parsed proto integer types or an error code
-// to indicate parsing failure.
-template <typename Integer>
-class ProtoIntegerBase {
- public:
-  constexpr ProtoIntegerBase(Result<Integer> value) : value_(value) {}
-  constexpr ProtoIntegerBase(Status status) : value_(status) {}
-
-  // TODO(pwbug/363): Migrate this to Result<> once we have StatusOr like
-  // support.
-  bool ok() { return value_.ok(); }
-  Status status() { return value_.status(); }
-  Integer value() { return value_.value(); }
-
- private:
-  Result<Integer> value_ = 0;
-};
-
-}  // namespace pw::protobuf::internal
diff --git a/pw_protobuf/public/pw_protobuf/map_utils.h b/pw_protobuf/public/pw_protobuf/map_utils.h
deleted file mode 100644
index 258a1b2..0000000
--- a/pw_protobuf/public/pw_protobuf/map_utils.h
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-//
-// The header provides a set of helper utils for protobuf related operations.
-// The APIs may not be finalized yet.
-
-#pragma once
-
-#include <cstddef>
-#include <string_view>
-
-#include "pw_assert/check.h"
-#include "pw_protobuf/stream_decoder.h"
-#include "pw_status/status.h"
-#include "pw_status/try.h"
-#include "pw_stream/stream.h"
-
-namespace pw::protobuf {
-
-// The function writes an entry for the proto map<string, bytes> field type.
-//
-// Args:
-//   field_number - The field number for the map.
-//   key - The string payload for the key value of the entry.
-//   key_size - Number of bytes in the key.
-//   value - The value payload for the entry.
-//   value_size - Number of bytes in the value.
-//   stream_pipe_buffer - A non-zero size buffer for the function to read and
-//     store data from the reader and write to the given writer.
-//   writer - The output writer to write to.
-//
-// Returns:
-// OK - Entry is successfully written.
-// RESOURCE_EXHAUSTED - Entry would exceed write limit.
-// INVALID_ARGUMENT - Field number is invalid.
-//
-// Since all length-delimited fields can be treated as `bytes`,
-// it can be used to write any string to length-delimited field map, i.e.
-// map<string, message>, map<string, bytes> etc.
-Status WriteProtoStringToBytesMapEntry(uint32_t field_number,
-                                       stream::Reader& key,
-                                       size_t key_size,
-                                       stream::Reader& value,
-                                       size_t value_size,
-                                       ByteSpan stream_pipe_buffer,
-                                       stream::Writer& writer);
-
-}  // namespace pw::protobuf
diff --git a/pw_protobuf/public/pw_protobuf/message.h b/pw_protobuf/public/pw_protobuf/message.h
deleted file mode 100644
index b3a2ac5..0000000
--- a/pw_protobuf/public/pw_protobuf/message.h
+++ /dev/null
@@ -1,627 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-//
-// The header provides a set of helper utils for protobuf related operations.
-// The APIs may not be finalized yet.
-
-#pragma once
-
-#include <cstddef>
-#include <string_view>
-
-#include "pw_assert/check.h"
-#include "pw_protobuf/internal/proto_integer_base.h"
-#include "pw_protobuf/stream_decoder.h"
-#include "pw_status/status.h"
-#include "pw_status/try.h"
-#include "pw_stream/interval_reader.h"
-#include "pw_stream/stream.h"
-
-namespace pw::protobuf {
-
-// The following defines classes that represent various parsed proto integer
-// types or an error code to indicate parsing failure.
-//
-// For normal uses, the class should be created from `class Message`. See
-// comment for `class Message` for usage.
-
-class Uint32 : public internal::ProtoIntegerBase<uint32_t> {
- public:
-  using ProtoIntegerBase<uint32_t>::ProtoIntegerBase;
-};
-
-class Int32 : public internal::ProtoIntegerBase<int32_t> {
- public:
-  using ProtoIntegerBase<int32_t>::ProtoIntegerBase;
-};
-
-class Sint32 : public internal::ProtoIntegerBase<int32_t> {
- public:
-  using ProtoIntegerBase<int32_t>::ProtoIntegerBase;
-};
-
-class Fixed32 : public internal::ProtoIntegerBase<uint32_t> {
- public:
-  using ProtoIntegerBase<uint32_t>::ProtoIntegerBase;
-};
-
-class Sfixed32 : public internal::ProtoIntegerBase<int32_t> {
- public:
-  using ProtoIntegerBase<int32_t>::ProtoIntegerBase;
-};
-
-class Uint64 : public internal::ProtoIntegerBase<uint64_t> {
- public:
-  using ProtoIntegerBase<uint64_t>::ProtoIntegerBase;
-};
-
-class Int64 : public internal::ProtoIntegerBase<int64_t> {
- public:
-  using ProtoIntegerBase<int64_t>::ProtoIntegerBase;
-};
-
-class Sint64 : public internal::ProtoIntegerBase<int64_t> {
- public:
-  using ProtoIntegerBase<int64_t>::ProtoIntegerBase;
-};
-
-class Fixed64 : public internal::ProtoIntegerBase<uint64_t> {
- public:
-  using ProtoIntegerBase<uint64_t>::ProtoIntegerBase;
-};
-
-class Sfixed64 : public internal::ProtoIntegerBase<int64_t> {
- public:
-  using ProtoIntegerBase<int64_t>::ProtoIntegerBase;
-};
-
-class Float : public internal::ProtoIntegerBase<float> {
- public:
-  using ProtoIntegerBase<float>::ProtoIntegerBase;
-};
-
-class Double : public internal::ProtoIntegerBase<double> {
- public:
-  using ProtoIntegerBase<double>::ProtoIntegerBase;
-};
-
-class Bool : public internal::ProtoIntegerBase<bool> {
- public:
-  using ProtoIntegerBase<bool>::ProtoIntegerBase;
-};
-
-// An object that represents a parsed `bytes` field or an error code. The
-// bytes are available via an stream::IntervalReader by GetBytesReader().
-//
-// For normal uses, the class should be created from `class Message`. See
-// comment for `class Message` for usage.
-class Bytes {
- public:
-  Bytes() = default;
-  Bytes(Status status) : reader_(status) {}
-  Bytes(stream::IntervalReader reader) : reader_(reader) {}
-  stream::IntervalReader GetBytesReader() { return reader_; }
-
-  // TODO(pwbug/363): Migrate this to Result<> once we have StatusOr like
-  // support.
-  bool ok() { return reader_.ok(); }
-  Status status() { return reader_.status(); }
-
-  // Check whether the bytes value equals the given `bytes`.
-  // TODO(pwbug/456): Should this return `bool`? In the case of error, is it ok
-  // to just return false?
-  Result<bool> Equal(ConstByteSpan bytes);
-
- private:
-  stream::IntervalReader reader_;
-};
-
-// An object that represents a parsed `string` field or an error code. The
-// string value is available via an stream::IntervalReader by
-// GetBytesReader().
-//
-// For normal uses, the class should be created from `class Message`. See
-// comment for `class Message` for usage.
-class String : public Bytes {
- public:
-  using Bytes::Bytes;
-
-  // Check whether the string value equals the given `str`
-  Result<bool> Equal(std::string_view str);
-};
-
-// Forward declaration of parser classes.
-template <typename FieldType>
-class RepeatedFieldParser;
-template <typename FieldType>
-class StringMapEntryParser;
-template <typename FieldType>
-class StringMapParser;
-class Message;
-
-using RepeatedBytes = RepeatedFieldParser<Bytes>;
-using RepeatedStrings = RepeatedFieldParser<String>;
-using RepeatedMessages = RepeatedFieldParser<Message>;
-using StringToBytesMapEntry = StringMapEntryParser<Bytes>;
-using StringToStringMapEntry = StringMapEntryParser<String>;
-using StringToMessageMapEntry = StringMapEntryParser<Message>;
-using StringToBytesMap = StringMapParser<Bytes>;
-using StringToStringMap = StringMapParser<String>;
-using StringToMessageMap = StringMapParser<Message>;
-
-// Message - A helper class for parsing a proto message.
-//
-// Examples:
-//
-// message Nested {
-//   string nested_str = 1;
-//   bytes nested_bytes = 2;
-// }
-//
-// message {
-//   string str = 1;
-//   bytes bytes = 2;
-//   uint32 integer = 3
-//   repeated string rep_str = 4;
-//   map<string, bytes> str_to_bytes = 5;
-//   Nested nested = 6;
-// }
-//
-//   // Given a seekable `reader` that reads the top-level proto message, and
-//   // a <size> that gives the size of the proto message:
-//   Message message(reader, <size>);
-//
-//   // Prase simple basic value fields
-//   String str = message.AsString(1); // string
-//   Bytes bytes = message.AsBytes(2); // bytes
-//   Uint32 integer = messasge_parser.AsUint32(3); // uint32 integer
-//
-//   // Parse repeated field `repeated string rep_str = 4;`
-//   RepeatedStrings rep_str = message.AsRepeatedString(4);
-//   // Iterate through the entries. If proto is malformed when
-//   // iterating, the next element (`str` in this case) will be invalid
-//   // and loop will end in the iteration after.
-//   for (String str : rep_str) {
-//     // Check status
-//     if (!str.ok()) {
-//       // In the case of error, loop will end in the next iteration if
-//       // continues. This is the chance for code to catch the error.
-//       ...
-//     }
-//     ...
-//   }
-//
-//   // Parse map field `map<string, bytes> str_to_bytes = 5;`
-//   StringToBytesMap str_to_bytes = message.AsStringToBytesMap(5);
-//
-//   // Access the entry by a given key value
-//   Bytes bytes_for_key = str_to_bytes["key"];
-//
-//   // Or iterate through map entries
-//   for (StringToBytesMapEntry entry : str_to_bytes) {
-//     if (!entry.ok()) {
-//       // In the case of error, loop will end in the next iteration if
-//       // continues. This is the chance for code to catch the error.
-//       ...
-//     }
-//     String key = entry.Key();
-//     Bytes value = entry.Value();
-//     ...
-//   }
-//
-//   // Parse nested message `Nested nested = 6;`
-//   Message nested = message.AsMessage(6).
-//   String nested_str = nested.AsString(1);
-//   Bytes nested_bytes = nested.AsBytes(2);
-//
-//   // The `AsXXX()` methods above internally traverse all the fields to find
-//   // the one with the give field number. This can be expensive if called
-//   // multiple times. Therefore, whenever possible, it is recommended to use
-//   // the following iteration to iterate and process each field directly.
-//   for (Message::Field field : message) {
-//     if (!field.ok()) {
-//       // In the case of error, loop will end in the next iteration if
-//       // continues. This is the chance for code to catch the error.
-//       ...
-//     }
-//     if (field.field_number() == 1) {
-//       String str = field.As<String>();
-//       ...
-//     } else if (field.field_number() == 2) {
-//       Bytes bytes = field.As<Bytes>();
-//       ...
-//     } else if (field.field_number() == 6) {
-//       Message nested = field.As<Message>();
-//       ...
-//     }
-//   }
-//
-// All parser objects created above internally hold the same reference
-// to `reader`. Therefore it needs to maintain valid lifespan throughout the
-// operations. The parser objects can work independently and without blocking
-// each other. All method calls and for-iterations above are re-enterable.
-class Message {
- public:
-  class Field {
-   public:
-    uint32_t field_number() { return field_number_; }
-    const stream::IntervalReader& field_reader() { return field_reader_; }
-    bool ok() { return field_reader_.ok(); }
-    Status status() { return field_reader_.status(); }
-
-    // Create a helper parser type of `FieldType` for the field.
-    // The default implementation below assumes the field is a length-delimited
-    // field. Other cases such as primitive integer uint32 will be handled by
-    // template specialization.
-    template <typename FieldType>
-    FieldType As() {
-      if (!field_reader_.ok()) {
-        return FieldType(field_reader_.status());
-      }
-
-      StreamDecoder decoder(field_reader_.Reset());
-      PW_TRY(decoder.Next());
-      Result<StreamDecoder::Bounds> payload_bounds =
-          decoder.GetLengthDelimitedPayloadBounds();
-      PW_TRY(payload_bounds.status());
-      // The bounds is relative to the given stream::IntervalReader. Convert
-      // it to the interval relative to the source_reader.
-      return FieldType(stream::IntervalReader(
-          field_reader_.source_reader(),
-          payload_bounds.value().low + field_reader_.start(),
-          payload_bounds.value().high + field_reader_.start()));
-    }
-
-   private:
-    Field() = default;
-    Field(Status status) : field_reader_(status), field_number_(0) {}
-    Field(stream::IntervalReader reader, uint32_t field_number)
-        : field_reader_(reader), field_number_(field_number) {}
-
-    stream::IntervalReader field_reader_;
-    uint32_t field_number_;
-
-    friend class Message;
-  };
-
-  class iterator {
-   public:
-    iterator& operator++();
-
-    iterator operator++(int) {
-      iterator iter = *this;
-      this->operator++();
-      return iter;
-    }
-
-    bool ok() { return status_.ok(); }
-    Status status() { return status_; }
-    Field operator*() { return current_; }
-    Field* operator->() { return &current_; }
-    bool operator!=(const iterator& other) const { return !(*this == other); }
-
-    bool operator==(const iterator& other) const {
-      return eof_ == other.eof_ && reader_ == other.reader_;
-    }
-
-   private:
-    stream::IntervalReader reader_;
-    bool eof_ = false;
-    Field current_;
-    Status status_ = OkStatus();
-
-    iterator(stream::IntervalReader reader) : reader_(reader) {
-      this->operator++();
-    }
-
-    friend class Message;
-  };
-
-  Message() = default;
-  Message(Status status) : reader_(status) {}
-  Message(stream::IntervalReader reader) : reader_(reader) {}
-  Message(stream::SeekableReader& proto_source, size_t size)
-      : reader_(proto_source, 0, size) {}
-
-  // Parse a sub-field in the message given by `field_number` as bytes.
-  Bytes AsBytes(uint32_t field_number) { return As<Bytes>(field_number); }
-
-  // Parse a sub-field in the message given by `field_number` as string.
-  String AsString(uint32_t field_number) { return As<String>(field_number); }
-
-  // Parse a sub-field in the message given by `field_number` as one of the
-  // proto integer type.
-  Int32 AsInt32(uint32_t field_number) { return As<Int32>(field_number); }
-  Sint32 AsSint32(uint32_t field_number) { return As<Sint32>(field_number); }
-  Uint32 AsUint32(uint32_t field_number) { return As<Uint32>(field_number); }
-  Fixed32 AsFixed32(uint32_t field_number) { return As<Fixed32>(field_number); }
-  Int64 AsInt64(uint32_t field_number) { return As<Int64>(field_number); }
-  Sint64 AsSint64(uint32_t field_number) { return As<Sint64>(field_number); }
-  Uint64 AsUint64(uint32_t field_number) { return As<Uint64>(field_number); }
-  Fixed64 AsFixed64(uint32_t field_number) { return As<Fixed64>(field_number); }
-
-  Sfixed32 AsSfixed32(uint32_t field_number) {
-    return As<Sfixed32>(field_number);
-  }
-
-  Sfixed64 AsSfixed64(uint32_t field_number) {
-    return As<Sfixed64>(field_number);
-  }
-
-  Float AsFloat(uint32_t field_number) { return As<Float>(field_number); }
-  Double AsDouble(uint32_t field_number) { return As<Double>(field_number); }
-
-  Bool AsBool(uint32_t field_number) { return As<Bool>(field_number); }
-
-  // Parse a sub-field in the message given by `field_number` as another
-  // message.
-  Message AsMessage(uint32_t field_number) { return As<Message>(field_number); }
-
-  // Parse a sub-field in the message given by `field_number` as `repeated
-  // string`.
-  RepeatedBytes AsRepeatedBytes(uint32_t field_number);
-
-  // Parse a sub-field in the message given by `field_number` as `repeated
-  // string`.
-  RepeatedStrings AsRepeatedStrings(uint32_t field_number);
-
-  // Parse a sub-field in the message given by `field_number` as `repeated
-  // message`.
-  RepeatedMessages AsRepeatedMessages(uint32_t field_number);
-
-  // Parse a sub-field in the message given by `field_number` as `map<string,
-  // message>`.
-  StringToMessageMap AsStringToMessageMap(uint32_t field_number);
-
-  // Parse a sub-field in the message given by `field_number` as
-  // `map<string, bytes>`.
-  StringToBytesMap AsStringToBytesMap(uint32_t field_number);
-
-  // Parse a sub-field in the message given by `field_number` as
-  // `map<string, string>`.
-  StringToStringMap AsStringToStringMap(uint32_t field_number);
-
-  // Convert the message to a Bytes that represents the raw bytes of this
-  // message. This can be used to obatained the serialized wire-format of the
-  // message.
-  Bytes ToBytes() { return Bytes(reader_.Reset()); }
-
-  // TODO(pwbug/363): Migrate this to Result<> once we have StatusOr like
-  // support.
-  bool ok() { return reader_.ok(); }
-  Status status() { return reader_.status(); }
-
-  iterator begin();
-  iterator end();
-
-  // Parse a field given by `field_number` as the target parser type
-  // `FieldType`.
-  //
-  // Note: This method assumes that the message has only 1 field with the given
-  // <field_number>. It returns the first matching it find. It does not perform
-  // value overridding or string concatenation for multiple fields with the same
-  // <field_number>.
-  //
-  // Since the method needs to traverse all fields, it can be inefficient if
-  // called multiple times exepcially on slow reader.
-  template <typename FieldType>
-  FieldType As(uint32_t field_number) {
-    for (Field field : *this) {
-      if (field.field_number() == field_number) {
-        return field.As<FieldType>();
-      }
-    }
-
-    return FieldType(Status::NotFound());
-  }
-
-  template <typename FieldType>
-  RepeatedFieldParser<FieldType> AsRepeated(uint32_t field_number) {
-    return RepeatedFieldParser<FieldType>(*this, field_number);
-  }
-
-  template <typename FieldParser>
-  StringMapParser<FieldParser> AsStringMap(uint32_t field_number) {
-    return StringMapParser<FieldParser>(*this, field_number);
-  }
-
- private:
-  stream::IntervalReader reader_;
-
-  // Consume the current field. If the field has already been processed, i.e.
-  // by calling one of the Read..() method, nothing is done. After calling this
-  // method, the reader will be pointing either to the start of the next
-  // field (i.e. the starting offset of the field key), or the end of the
-  // stream. The method is for use by Message for computing field interval.
-  static Status ConsumeCurrentField(StreamDecoder& decoder) {
-    return decoder.field_consumed_ ? OkStatus() : decoder.SkipField();
-  }
-};
-
-// The following are template specialization for proto integer types.
-template <>
-Uint32 Message::Field::As<Uint32>();
-
-template <>
-Int32 Message::Field::As<Int32>();
-
-template <>
-Sint32 Message::Field::As<Sint32>();
-
-template <>
-Fixed32 Message::Field::As<Fixed32>();
-
-template <>
-Sfixed32 Message::Field::As<Sfixed32>();
-
-template <>
-Uint64 Message::Field::As<Uint64>();
-
-template <>
-Int64 Message::Field::As<Int64>();
-
-template <>
-Sint64 Message::Field::As<Sint64>();
-
-template <>
-Fixed64 Message::Field::As<Fixed64>();
-
-template <>
-Sfixed64 Message::Field::As<Sfixed64>();
-
-template <>
-Float Message::Field::As<Float>();
-
-template <>
-Double Message::Field::As<Double>();
-
-template <>
-Bool Message::Field::As<Bool>();
-
-// A helper for parsing `repeated` field. It implements an iterator interface
-// that only iterates through the fields of a given `field_number`.
-//
-// For normal uses, the class should be created from `class Message`. See
-// comment for `class Message` for usage.
-template <typename FieldType>
-class RepeatedFieldParser {
- public:
-  class iterator {
-   public:
-    // Precondition: iter_ is not pointing to the end.
-    iterator& operator++() {
-      iter_++;
-      MoveToNext();
-      return *this;
-    }
-
-    iterator operator++(int) {
-      iterator iter = *this;
-      this->operator++();
-      return iter;
-    }
-
-    bool ok() { return iter_.ok(); }
-    Status status() { return iter_.status(); }
-    FieldType operator*() { return current_; }
-    FieldType* operator->() { return &current_; }
-    bool operator!=(const iterator& other) const { return !(*this == other); }
-    bool operator==(const iterator& other) const {
-      return &host_ == &other.host_ && iter_ == other.iter_;
-    }
-
-   private:
-    RepeatedFieldParser& host_;
-    Message::iterator iter_;
-    FieldType current_ = FieldType(Status::Unavailable());
-
-    iterator(RepeatedFieldParser& host, Message::iterator init_iter)
-        : host_(host), iter_(init_iter), current_(Status::Unavailable()) {
-      // Move to the first element of the target field number.
-      MoveToNext();
-    }
-
-    void MoveToNext() {
-      // Move the iterator to the next element with the target field number
-      for (; iter_ != host_.message_.end(); ++iter_) {
-        if (!iter_.ok() || iter_->field_number() == host_.field_number_) {
-          current_ = iter_->As<FieldType>();
-          break;
-        }
-      }
-    }
-
-    friend class RepeatedFieldParser;
-  };
-
-  // `message` -- The containing message.
-  // `field_number` -- The field number of the repeated field.
-  RepeatedFieldParser(Message& message, uint32_t field_number)
-      : message_(message), field_number_(field_number) {}
-
-  RepeatedFieldParser(Status status) : message_(status) {}
-
-  // TODO(pwbug/363): Migrate this to Result<> once we have StatusOr like
-  // support.
-  bool ok() { return message_.ok(); }
-  Status status() { return message_.status(); }
-
-  iterator begin() { return iterator(*this, message_.begin()); }
-  iterator end() { return iterator(*this, message_.end()); }
-
- private:
-  Message message_;
-  uint32_t field_number_ = 0;
-};
-
-// A helper for pasring the entry type of map<string, <value>>.
-// An entry for a proto map is essentially a message of a key(k=1) and
-// value(k=2) field, i.e.:
-//
-// message Entry {
-//   string key = 1;
-//   bytes value = 2;
-// }
-//
-// For normal uses, the class should be created from `class Message`. See
-// comment for `class Message` for usage.
-template <typename ValueParser>
-class StringMapEntryParser {
- public:
-  bool ok() { return entry_.ok(); }
-  Status status() { return entry_.status(); }
-  StringMapEntryParser(Status status) : entry_(status) {}
-  StringMapEntryParser(stream::IntervalReader reader) : entry_(reader) {}
-  String Key() { return entry_.AsString(kMapKeyFieldNumber); }
-  ValueParser Value() { return entry_.As<ValueParser>(kMapValueFieldNumber); }
-
- private:
-  static constexpr uint32_t kMapKeyFieldNumber = 1;
-  static constexpr uint32_t kMapValueFieldNumber = 2;
-  Message entry_;
-};
-
-// A helper class for parsing a string-keyed map field. i.e. map<string,
-// <value>>. The template argument `ValueParser` indicates the type the value
-// will be parsed as, i.e. String, Bytes, Uint32, Message etc.
-//
-// For normal uses, the class should be created from `class Message`. See
-// comment for `class Message` for usage.
-template <typename ValueParser>
-class StringMapParser
-    : public RepeatedFieldParser<StringMapEntryParser<ValueParser>> {
- public:
-  using RepeatedFieldParser<
-      StringMapEntryParser<ValueParser>>::RepeatedFieldParser;
-
-  // Operator overload for value access of a given key.
-  ValueParser operator[](std::string_view target) {
-    // Iterate over all entries and find the one whose key matches `target`
-    for (StringMapEntryParser<ValueParser> entry : *this) {
-      String key = entry.Key();
-      PW_TRY(key.status());
-
-      // Compare key value with the given string
-      Result<bool> cmp_res = key.Equal(target);
-      PW_TRY(cmp_res.status());
-      if (cmp_res.value()) {
-        return entry.Value();
-      }
-    }
-
-    return ValueParser(Status::NotFound());
-  }
-};
-
-}  // namespace pw::protobuf
diff --git a/pw_protobuf/public/pw_protobuf/serialized_size.h b/pw_protobuf/public/pw_protobuf/serialized_size.h
index 920bf97..d671683 100644
--- a/pw_protobuf/public/pw_protobuf/serialized_size.h
+++ b/pw_protobuf/public/pw_protobuf/serialized_size.h
@@ -40,157 +40,13 @@
 // The bool field type is backed by a varint, but has a limited value range.
 inline constexpr size_t kMaxSizeBytesBool = 1;
 
-inline constexpr size_t kMaxSizeBytesEnum = kMaxSizeBytesInt32;
-
-inline constexpr size_t kMaxSizeOfFieldNumber = varint::kMaxVarint32SizeBytes;
+inline constexpr size_t kMaxSizeOfFieldKey = varint::kMaxVarint32SizeBytes;
 
 inline constexpr size_t kMaxSizeOfLength = varint::kMaxVarint32SizeBytes;
 
-// Calculate the size of a proto field key in wire format, including the key
-// field number + wire type.
-// type).
-//
-// Args:
-//   field_number: The field number for the field.
-//
-// Returns:
-//   The size of the field key.
-//
-// Precondition: The field_number must be a ValidFieldNumber.
-template <typename T>
-constexpr size_t FieldNumberSizeBytes(T field_number) {
-  static_assert((std::is_enum<T>() || std::is_integral<T>()) &&
-                    sizeof(T) <= sizeof(uint32_t),
-                "Field numbers must be 32-bit enums or integers");
-  // The wiretype does not impact the serialized size, so use kVarint (0), which
-  // will be optimized out by the compiler.
-  return varint::EncodedSize(
-      FieldKey(static_cast<uint32_t>(field_number), WireType::kVarint));
-}
-
-// Calculates the size of a varint field (uint32/64, int32/64, sint32/64, enum).
-template <typename T, typename U>
-constexpr size_t SizeOfVarintField(T field_number, U value) {
-  return FieldNumberSizeBytes(field_number) + varint::EncodedSize(value);
-}
-
-// Calculates the size of a delimited field (string, bytes, nested message,
-// packed repeated), excluding the data itself. This accounts for the field
-// tag and length varint only. The default value for length_bytes assumes
-// the length is kMaxSizeOfLength bytes long.
-template <typename T>
-constexpr size_t SizeOfDelimitedFieldWithoutValue(
-    T field_number,
-    uint32_t length_bytes = std::numeric_limits<uint32_t>::max()) {
-  return FieldNumberSizeBytes(field_number) + varint::EncodedSize(length_bytes);
-}
-
-// Calculates the total size of a delimited field (string, bytes, nested
-// message, packed repeated), including the data itself.
-template <typename T>
-constexpr size_t SizeOfDelimitedField(T field_number, uint32_t length_bytes) {
-  return SizeOfDelimitedFieldWithoutValue(field_number, length_bytes) +
-         length_bytes;
-}
-
-// Calculates the size of a proto field in the wire format. This is the size of
-// a final serialized protobuf entry, including the key (field number + wire
-// type), encoded payload size (for length-delimited types), and data.
-//
-// Args:
-//   field_number: The field number for the field.
-//   type: The wire type of the field
-//   data_size: The size of the payload.
-//
-// Returns:
-//   The size of the field.
-//
-// Precondition: The field_number must be a ValidFieldNumber.
-// Precondition: `data_size_bytes` must be smaller than
-//   std::numeric_limits<uint32_t>::max()
-template <typename T>
-constexpr size_t SizeOfField(T field_number,
-                             WireType type,
-                             size_t data_size_bytes) {
-  if (type == WireType::kDelimited) {
-    return SizeOfDelimitedField(field_number, data_size_bytes);
-  }
-  return FieldNumberSizeBytes(field_number) + data_size_bytes;
-}
-
-// Functions for calculating the size of each type of protobuf field. Varint
-// fields (int32, uint64, etc.) accept a value argument that defaults to the
-// largest-to-encode value for the type.
-template <typename T>
-constexpr size_t SizeOfFieldFloat(T field_number) {
-  return FieldNumberSizeBytes(field_number) + sizeof(float);
-}
-template <typename T>
-constexpr size_t SizeOfFieldDouble(T field_number) {
-  return FieldNumberSizeBytes(field_number) + sizeof(double);
-}
-template <typename T>
-constexpr size_t SizeOfFieldInt32(T field_number, int32_t value = -1) {
-  return SizeOfVarintField(field_number, value);
-}
-template <typename T>
-constexpr size_t SizeOfFieldInt64(T field_number, int64_t value = -1) {
-  return SizeOfVarintField(field_number, value);
-}
-template <typename T>
-constexpr size_t SizeOfFieldSint32(
-    T field_number, int32_t value = std::numeric_limits<int32_t>::min()) {
-  return SizeOfVarintField(field_number, varint::ZigZagEncode(value));
-}
-template <typename T>
-constexpr size_t SizeOfFieldSint64(
-    T field_number, int64_t value = std::numeric_limits<int64_t>::min()) {
-  return SizeOfVarintField(field_number, varint::ZigZagEncode(value));
-}
-template <typename T>
-constexpr size_t SizeOfFieldUint32(
-    T field_number, uint32_t value = std::numeric_limits<uint32_t>::max()) {
-  return SizeOfVarintField(field_number, value);
-}
-template <typename T>
-constexpr size_t SizeOfFieldUint64(
-    T field_number, uint64_t value = std::numeric_limits<uint64_t>::max()) {
-  return SizeOfVarintField(field_number, value);
-}
-template <typename T>
-constexpr size_t SizeOfFieldFixed32(T field_number) {
-  return FieldNumberSizeBytes(field_number) + sizeof(uint32_t);
-}
-template <typename T>
-constexpr size_t SizeOfFieldFixed64(T field_number) {
-  return FieldNumberSizeBytes(field_number) + sizeof(uint64_t);
-}
-template <typename T>
-constexpr size_t SizeOfFieldSfixed32(T field_number) {
-  return FieldNumberSizeBytes(field_number) + sizeof(uint32_t);
-}
-template <typename T>
-constexpr size_t SizeOfFieldSfixed64(T field_number) {
-  return FieldNumberSizeBytes(field_number) + sizeof(uint64_t);
-}
-template <typename T>
-constexpr size_t SizeOfFieldBool(T field_number) {
-  return FieldNumberSizeBytes(field_number) + 1;
-}
-template <typename T>
-constexpr size_t SizeOfFieldString(T field_number, uint32_t length_bytes) {
-  return SizeOfDelimitedField(field_number, length_bytes);
-}
-template <typename T>
-constexpr size_t SizeOfFieldBytes(T field_number, uint32_t length_bytes) {
-  return SizeOfDelimitedField(field_number, length_bytes);
-}
-template <typename T, typename U = int32_t>
-constexpr size_t SizeOfFieldEnum(T field_number, U value = static_cast<U>(-1)) {
-  static_assert((std::is_enum<U>() || std::is_integral<U>()) &&
-                    sizeof(U) <= sizeof(uint32_t),
-                "Enum values must be 32-bit enums or integers");
-  return SizeOfFieldInt32(field_number, static_cast<int32_t>(value));
+constexpr size_t SizeOfFieldKey(uint32_t field_number) {
+  // The wiretype is ignored as this does not impact the serialized size.
+  return varint::EncodedSize(field_number << kFieldNumberShift);
 }
 
 }  // namespace pw::protobuf
diff --git a/pw_protobuf/public/pw_protobuf/stream_decoder.h b/pw_protobuf/public/pw_protobuf/stream_decoder.h
deleted file mode 100644
index 37869b3..0000000
--- a/pw_protobuf/public/pw_protobuf/stream_decoder.h
+++ /dev/null
@@ -1,715 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <array>
-#include <cstring>
-#include <limits>
-#include <span>
-#include <type_traits>
-
-#include "pw_assert/assert.h"
-#include "pw_containers/vector.h"
-#include "pw_protobuf/wire_format.h"
-#include "pw_status/status.h"
-#include "pw_status/status_with_size.h"
-#include "pw_stream/stream.h"
-#include "pw_varint/stream.h"
-#include "pw_varint/varint.h"
-
-namespace pw::protobuf {
-
-// A low-level, event-based protobuf wire format decoder that operates on a
-// stream.
-//
-// The decoder processes an encoded message by iterating over its fields. The
-// caller can extract the values of any fields it cares about.
-//
-// The decoder does not provide any in-memory data structures to represent a
-// protobuf message's data. More sophisticated APIs can be built on top of the
-// low-level decoder to provide additional functionality, if desired.
-//
-// **NOTE**
-// This decoder is intended to be used for protobuf messages which are too large
-// to fit in memory. For smaller messages, prefer the MemoryDecoder, which is
-// much more efficient.
-//
-// Example usage:
-//
-//   stream::Reader& my_stream = GetProtoStream();
-//   StreamDecoder decoder(my_stream);
-//
-//   while (decoder.Next().ok()) {
-//     // FieldNumber() will always be valid if Next() returns OK.
-//     switch (decoder.FieldNumber().value()) {
-//       case 1:
-//         Result<uint32_t> result = decoder.ReadUint32();
-//         if (result.ok()) {
-//           DoSomething(result.value());
-//         }
-//         break;
-//       // ... and other fields.
-//     }
-//   }
-//
-class StreamDecoder {
- public:
-  // stream::Reader for a bytes field in a streamed proto message.
-  //
-  // Shares the StreamDecoder's reader, limiting it to the bounds of a bytes
-  // field. If the StreamDecoder's reader does not supporting seeking, this
-  // will also not.
-  class BytesReader : public stream::RelativeSeekableReader {
-   public:
-    ~BytesReader() { decoder_.CloseBytesReader(*this); }
-
-    constexpr size_t field_size() const { return end_offset_ - start_offset_; }
-
-   private:
-    friend class StreamDecoder;
-
-    constexpr BytesReader(StreamDecoder& decoder,
-                          size_t start_offset,
-                          size_t end_offset)
-        : decoder_(decoder),
-          start_offset_(start_offset),
-          end_offset_(end_offset),
-          status_(OkStatus()) {}
-
-    constexpr BytesReader(StreamDecoder& decoder, Status status)
-        : decoder_(decoder),
-          start_offset_(0),
-          end_offset_(0),
-          status_(status) {}
-
-    StatusWithSize DoRead(ByteSpan destination) final;
-    Status DoSeek(ptrdiff_t offset, Whence origin) final;
-
-    StreamDecoder& decoder_;
-    size_t start_offset_;
-    size_t end_offset_;
-    Status status_;
-  };
-
-  constexpr StreamDecoder(stream::Reader& reader)
-      : StreamDecoder(reader, std::numeric_limits<size_t>::max()) {}
-
-  // Allow the maximum length of the protobuf to be specified to the decoder
-  // for streaming situations. When constructed in this way, the decoder will
-  // consume any remaining bytes when it goes out of scope.
-  constexpr StreamDecoder(stream::Reader& reader, size_t length)
-      : reader_(reader),
-        stream_bounds_({0, length}),
-        position_(0),
-        current_field_(kInitialFieldKey),
-        delimited_field_size_(0),
-        delimited_field_offset_(0),
-        parent_(nullptr),
-        field_consumed_(true),
-        nested_reader_open_(false),
-        status_(OkStatus()) {}
-
-  StreamDecoder(const StreamDecoder& other) = delete;
-  StreamDecoder& operator=(const StreamDecoder& other) = delete;
-
-  ~StreamDecoder();
-
-  // Advances to the next field in the proto.
-  //
-  // If Next() returns OK, there is guaranteed to be a valid protobuf field at
-  // the current position, which can then be consumed through one of the Read*
-  // methods.
-  //
-  // Return values:
-  //
-  //             OK: Advanced to a valid proto field.
-  //   OUT_OF_RANGE: Reached the end of the proto message.
-  //      DATA_LOSS: Invalid protobuf data.
-  //
-  Status Next();
-
-  // Returns the field number of the current field.
-  //
-  // Can only be called after a successful call to Next() and before any
-  // Read*() operation.
-  constexpr Result<uint32_t> FieldNumber() const {
-    if (field_consumed_) {
-      return Status::FailedPrecondition();
-    }
-
-    return status_.ok() ? current_field_.field_number()
-                        : Result<uint32_t>(status_);
-  }
-
-  //
-  // TODO(frolv): Add Status Read*(T& value) APIs alongside the Result<T> ones.
-  //
-
-  // Reads a proto int32 value from the current position.
-  Result<int32_t> ReadInt32() {
-    return ReadVarintField<int32_t>(VarintDecodeType::kNormal);
-  }
-
-  // Reads repeated int32 values from the current position using packed
-  // encoding.
-  //
-  // Returns the number of values read. In the case of error, the return value
-  // indicates the number of values successfully read, in addition to the error.
-  StatusWithSize ReadPackedInt32(std::span<int32_t> out) {
-    return ReadPackedVarintField(std::as_writable_bytes(out),
-                                 sizeof(int32_t),
-                                 VarintDecodeType::kNormal);
-  }
-
-  // Reads repeated int32 values from the current position into the vector,
-  // supporting either repeated single field elements or packed encoding.
-  Status ReadRepeatedInt32(pw::Vector<int32_t>& out) {
-    return ReadRepeatedVarintField<int32_t>(out, VarintDecodeType::kNormal);
-  }
-
-  // Reads a proto uint32 value from the current position.
-  Result<uint32_t> ReadUint32() {
-    return ReadVarintField<uint32_t>(VarintDecodeType::kUnsigned);
-  }
-
-  // Reads repeated uint32 values from the current position using packed
-  // encoding.
-  //
-  // Returns the number of values read. In the case of error, the return value
-  // indicates the number of values successfully read, in addition to the error.
-  StatusWithSize ReadPackedUint32(std::span<uint32_t> out) {
-    return ReadPackedVarintField(std::as_writable_bytes(out),
-                                 sizeof(uint32_t),
-                                 VarintDecodeType::kUnsigned);
-  }
-
-  // Reads repeated uint32 values from the current position into the vector,
-  // supporting either repeated single field elements or packed encoding.
-  Status ReadRepeatedUint32(pw::Vector<uint32_t>& out) {
-    return ReadRepeatedVarintField<uint32_t>(out, VarintDecodeType::kUnsigned);
-  }
-
-  // Reads a proto int64 value from the current position.
-  Result<int64_t> ReadInt64() {
-    return ReadVarintField<int64_t>(VarintDecodeType::kNormal);
-  }
-
-  // Reads repeated int64 values from the current position using packed
-  // encoding.
-  //
-  // Returns the number of values read. In the case of error, the return value
-  // indicates the number of values successfully read, in addition to the
-  // error.
-  StatusWithSize ReadPackedInt64(std::span<int64_t> out) {
-    return ReadPackedVarintField(std::as_writable_bytes(out),
-                                 sizeof(int64_t),
-                                 VarintDecodeType::kNormal);
-  }
-
-  // Reads repeated int64 values from the current position into the vector,
-  // supporting either repeated single field elements or packed encoding.
-  Status ReadRepeatedInt64(pw::Vector<int64_t>& out) {
-    return ReadRepeatedVarintField<int64_t>(out, VarintDecodeType::kNormal);
-  }
-
-  // Reads a proto uint64 value from the current position.
-  Result<uint64_t> ReadUint64() {
-    return ReadVarintField<uint64_t>(VarintDecodeType::kUnsigned);
-  }
-
-  // Reads repeated uint64 values from the current position using packed
-  // encoding.
-  //
-  // Returns the number of values read. In the case of error, the return value
-  // indicates the number of values successfully read, in addition to the
-  // error.
-  StatusWithSize ReadPackedUint64(std::span<uint64_t> out) {
-    return ReadPackedVarintField(std::as_writable_bytes(out),
-                                 sizeof(uint64_t),
-                                 VarintDecodeType::kUnsigned);
-  }
-
-  // Reads repeated uint64 values from the current position into the vector,
-  // supporting either repeated single field elements or packed encoding.
-  Status ReadRepeatedUint64(pw::Vector<uint64_t>& out) {
-    return ReadRepeatedVarintField<uint64_t>(out, VarintDecodeType::kUnsigned);
-  }
-
-  // Reads a proto sint32 value from the current position.
-  Result<int32_t> ReadSint32() {
-    return ReadVarintField<int32_t>(VarintDecodeType::kZigZag);
-  }
-
-  // Reads repeated sint32 values from the current position using packed
-  // encoding.
-  //
-  // Returns the number of values read. In the case of error, the return value
-  // indicates the number of values successfully read, in addition to the
-  // error.
-  StatusWithSize ReadPackedSint32(std::span<int32_t> out) {
-    return ReadPackedVarintField(std::as_writable_bytes(out),
-                                 sizeof(int32_t),
-                                 VarintDecodeType::kZigZag);
-  }
-
-  // Reads repeated sint32 values from the current position into the vector,
-  // supporting either repeated single field elements or packed encoding.
-  Status ReadRepeatedSint32(pw::Vector<int32_t>& out) {
-    return ReadRepeatedVarintField<int32_t>(out, VarintDecodeType::kZigZag);
-  }
-
-  // Reads a proto sint64 value from the current position.
-  Result<int64_t> ReadSint64() {
-    return ReadVarintField<int64_t>(VarintDecodeType::kZigZag);
-  }
-
-  // Reads repeated int64 values from the current position using packed
-  // encoding.
-  //
-  // Returns the number of values read. In the case of error, the return value
-  // indicates the number of values successfully read, in addition to the
-  // error.
-  StatusWithSize ReadPackedSint64(std::span<int64_t> out) {
-    return ReadPackedVarintField(std::as_writable_bytes(out),
-                                 sizeof(int64_t),
-                                 VarintDecodeType::kZigZag);
-  }
-
-  // Reads repeated sint64 values from the current position into the vector,
-  // supporting either repeated single field elements or packed encoding.
-  Status ReadRepeatedSint64(pw::Vector<int64_t>& out) {
-    return ReadRepeatedVarintField<int64_t>(out, VarintDecodeType::kZigZag);
-  }
-
-  // Reads a proto bool value from the current position.
-  Result<bool> ReadBool() {
-    return ReadVarintField<bool>(VarintDecodeType::kUnsigned);
-  }
-
-  // Reads repeated bool values from the current position using packed
-  // encoding.
-  //
-  // Returns the number of values read. In the case of error, the return value
-  // indicates the number of values successfully read, in addition to the
-  // error.
-  StatusWithSize ReadPackedBool(std::span<bool> out) {
-    return ReadPackedVarintField(
-        std::as_writable_bytes(out), sizeof(bool), VarintDecodeType::kUnsigned);
-  }
-
-  // Reads repeated bool values from the current position into the vector,
-  // supporting either repeated single field elements or packed encoding.
-  Status ReadRepeatedBool(pw::Vector<bool>& out) {
-    return ReadRepeatedVarintField<bool>(out, VarintDecodeType::kUnsigned);
-  }
-
-  // Reads a proto fixed32 value from the current position.
-  Result<uint32_t> ReadFixed32() { return ReadFixedField<uint32_t>(); }
-
-  // Reads repeated fixed32 values from the current position using packed
-  // encoding.
-  //
-  // Returns the number of values read.
-  StatusWithSize ReadPackedFixed32(std::span<uint32_t> out) {
-    return ReadPackedFixedField(std::as_writable_bytes(out), sizeof(uint32_t));
-  }
-
-  // Reads repeated fixed32 values from the current position into the vector,
-  // supporting either repeated single field elements or packed encoding.
-  Status ReadRepeatedFixed32(pw::Vector<uint32_t>& out) {
-    return ReadRepeatedFixedField<uint32_t>(out);
-  }
-
-  // Reads a proto fixed64 value from the current position.
-  Result<uint64_t> ReadFixed64() { return ReadFixedField<uint64_t>(); }
-
-  // Reads repeated fixed64 values from the current position using packed
-  // encoding.
-  //
-  // Returns the number of values read.
-  StatusWithSize ReadPackedFixed64(std::span<uint64_t> out) {
-    return ReadPackedFixedField(std::as_writable_bytes(out), sizeof(uint64_t));
-  }
-
-  // Reads repeated fixed64 values from the current position into the vector,
-  // supporting either repeated single field elements or packed encoding.
-  Status ReadRepeatedFixed64(pw::Vector<uint64_t>& out) {
-    return ReadRepeatedFixedField<uint64_t>(out);
-  }
-
-  // Reads a proto sfixed32 value from the current position.
-  Result<int32_t> ReadSfixed32() { return ReadFixedField<int32_t>(); }
-
-  // Reads repeated sfixed32 values from the current position using packed
-  // encoding.
-  //
-  // Returns the number of values read.
-  StatusWithSize ReadPackedSfixed32(std::span<int32_t> out) {
-    return ReadPackedFixedField(std::as_writable_bytes(out), sizeof(int32_t));
-  }
-
-  // Reads repeated sfixed32 values from the current position into the vector,
-  // supporting either repeated single field elements or packed encoding.
-  Status ReadRepeatedSfixed32(pw::Vector<int32_t>& out) {
-    return ReadRepeatedFixedField<int32_t>(out);
-  }
-
-  // Reads a proto sfixed64 value from the current position.
-  Result<int64_t> ReadSfixed64() { return ReadFixedField<int64_t>(); }
-
-  // Reads repeated sfixed64 values from the current position using packed
-  // encoding.
-  //
-  // Returns the number of values read.
-  StatusWithSize ReadPackedSfixed64(std::span<int64_t> out) {
-    return ReadPackedFixedField(std::as_writable_bytes(out), sizeof(int64_t));
-  }
-
-  // Reads repeated sfixed64 values from the current position into the vector,
-  // supporting either repeated single field elements or packed encoding.
-  Status ReadRepeatedSfixed64(pw::Vector<int64_t>& out) {
-    return ReadRepeatedFixedField<int64_t>(out);
-  }
-
-  // Reads a proto float value from the current position.
-  Result<float> ReadFloat() {
-    static_assert(sizeof(float) == sizeof(uint32_t),
-                  "Float and uint32_t must be the same size for protobufs");
-    return ReadFixedField<float>();
-  }
-
-  // Reads repeated float values from the current position using packed
-  // encoding.
-  //
-  // Returns the number of values read.
-  StatusWithSize ReadPackedFloat(std::span<float> out) {
-    static_assert(sizeof(float) == sizeof(uint32_t),
-                  "Float and uint32_t must be the same size for protobufs");
-    return ReadPackedFixedField(std::as_writable_bytes(out), sizeof(float));
-  }
-
-  // Reads repeated float values from the current position into the vector,
-  // supporting either repeated single field elements or packed encoding.
-  Status ReadRepeatedFloat(pw::Vector<float>& out) {
-    return ReadRepeatedFixedField<float>(out);
-  }
-
-  // Reads a proto double value from the current position.
-  Result<double> ReadDouble() {
-    static_assert(sizeof(double) == sizeof(uint64_t),
-                  "Double and uint64_t must be the same size for protobufs");
-    return ReadFixedField<double>();
-  }
-
-  // Reads repeated double values from the current position using packed
-  // encoding.
-  //
-  // Returns the number of values read.
-  StatusWithSize ReadPackedDouble(std::span<double> out) {
-    static_assert(sizeof(double) == sizeof(uint64_t),
-                  "Double and uint64_t must be the same size for protobufs");
-    return ReadPackedFixedField(std::as_writable_bytes(out), sizeof(double));
-  }
-
-  // Reads repeated double values from the current position into the vector,
-  // supporting either repeated single field elements or packed encoding.
-  Status ReadRepeatedDouble(pw::Vector<double>& out) {
-    return ReadRepeatedFixedField<double>(out);
-  }
-
-  // Reads a proto string value from the current position. The string is
-  // copied into the provided buffer and the read size is returned. The copied
-  // string will NOT be null terminated; this should be done manually if
-  // desired.
-  //
-  // If the buffer is too small to fit the string value, RESOURCE_EXHAUSTED is
-  // returned and no data is read. The decoder's position remains on the
-  // string field.
-  StatusWithSize ReadString(std::span<char> out) {
-    return ReadBytes(std::as_writable_bytes(out));
-  }
-
-  // Reads a proto bytes value from the current position. The value is copied
-  // into the provided buffer and the read size is returned.
-  //
-  // If the buffer is too small to fit the bytes value, RESOURCE_EXHAUSTED is
-  // returned and no data is read. The decoder's position remains on the bytes
-  // field.
-  //
-  // For larger bytes values that won't fit into memory, use GetBytesReader()
-  // to acquire a stream::Reader to the bytes instead.
-  StatusWithSize ReadBytes(std::span<std::byte> out) {
-    return ReadDelimitedField(out);
-  }
-
-  // Returns a stream::Reader to a bytes (or string) field at the current
-  // position in the protobuf.
-  //
-  // The BytesReader shares the same stream as the decoder, using RAII to manage
-  // ownership of the stream. The decoder cannot be used while the BytesStream
-  // is alive.
-  //
-  //   StreamDecoder decoder(my_stream);
-  //
-  //   while (decoder.Next().ok()) {
-  //     switch (decoder.FieldNumber()) {
-  //
-  //       // Bytes field.
-  //       case 1: {
-  //         // The BytesReader is created within a new C++ scope. While it is
-  //         // alive, the decoder cannot be used.
-  //         StreamDecoder::BytesReader reader = decoder.GetBytesReader();
-  //
-  //         // Do stuff with the reader.
-  //         reader.Read(&some_buffer);
-  //
-  //         // At the end of the scope, the reader is destructed and the
-  //         // decoder becomes usable again.
-  //         break;
-  //       }
-  //     }
-  //   }
-  //
-  // The returned decoder is seekable if the stream's decoder is seekable.
-  BytesReader GetBytesReader();
-
-  // Returns a decoder to a nested protobuf message located at the current
-  // position.
-  //
-  // The nested decoder shares the same stream as its parent, using RAII to
-  // manage ownership of the stream. The parent decoder cannot be used while the
-  // nested one is alive.
-  //
-  // See the example in GetBytesReader() above for RAII semantics and usage.
-  StreamDecoder GetNestedDecoder();
-
-  struct Bounds {
-    size_t low;
-    size_t high;
-  };
-
-  // Get the interval of the payload part of a length-delimited field. That is,
-  // the interval exluding the field key and the length prefix. The bounds are
-  // relative to the given reader.
-  Result<Bounds> GetLengthDelimitedPayloadBounds();
-
- protected:
-  // Specialized move constructor used only for codegen.
-  //
-  // Postcondition: The other decoder is invalidated and cannot be used as it
-  //     acts like a parent decoder with an active child decoder.
-  constexpr StreamDecoder(StreamDecoder&& other)
-      : reader_(other.reader_),
-        stream_bounds_(other.stream_bounds_),
-        position_(other.position_),
-        current_field_(other.current_field_),
-        delimited_field_size_(other.delimited_field_size_),
-        delimited_field_offset_(other.delimited_field_offset_),
-        parent_(other.parent_),
-        field_consumed_(other.field_consumed_),
-        nested_reader_open_(other.nested_reader_open_),
-        status_(other.status_) {
-    PW_ASSERT(!nested_reader_open_);
-    // Make the nested decoder look like it has an open child to block reads for
-    // the remainder of the object's life, and an invalid status to ensure it
-    // doesn't advance the stream on destruction.
-    other.nested_reader_open_ = true;
-    other.parent_ = nullptr;
-    other.status_ = pw::Status::Cancelled();
-  }
-
- private:
-  friend class BytesReader;
-
-  enum class VarintDecodeType {
-    kUnsigned,
-    kNormal,
-    kZigZag,
-  };
-
-  // The FieldKey class can't store an invalid key, so pick a random large key
-  // to set as the initial value. This will be overwritten the first time Next()
-  // is called, and FieldKey() fails if Next() is not called first -- ensuring
-  // that users will never see this value.
-  static constexpr FieldKey kInitialFieldKey =
-      FieldKey(20000, WireType::kVarint);
-
-  constexpr StreamDecoder(stream::Reader& reader,
-                          StreamDecoder* parent,
-                          size_t low,
-                          size_t high)
-      : reader_(reader),
-        stream_bounds_({low, high}),
-        position_(parent->position_),
-        current_field_(kInitialFieldKey),
-        delimited_field_size_(0),
-        delimited_field_offset_(0),
-        parent_(parent),
-        field_consumed_(true),
-        nested_reader_open_(false),
-        status_(OkStatus()) {}
-
-  // Creates an unusable decoder in an error state. This is required as
-  // GetNestedEncoder does not have a way to report an error in its API.
-  constexpr StreamDecoder(stream::Reader& reader,
-                          StreamDecoder* parent,
-                          Status status)
-      : reader_(reader),
-        stream_bounds_({0, std::numeric_limits<size_t>::max()}),
-        position_(0),
-        current_field_(kInitialFieldKey),
-        delimited_field_size_(0),
-        delimited_field_offset_(0),
-        parent_(parent),
-        field_consumed_(true),
-        nested_reader_open_(false),
-        status_(status) {
-    PW_ASSERT(!status.ok());
-  }
-
-  Status Advance(size_t end_position);
-
-  void CloseBytesReader(BytesReader& reader);
-  void CloseNestedDecoder(StreamDecoder& nested);
-
-  Status ReadFieldKey();
-  Status SkipField();
-
-  Status ReadVarintField(std::span<std::byte> out,
-                         VarintDecodeType decode_type);
-
-  StatusWithSize ReadOneVarint(std::span<std::byte> out,
-                               VarintDecodeType decode_type);
-
-  template <typename T>
-  Result<T> ReadVarintField(VarintDecodeType decode_type) {
-    static_assert(
-        std::is_same_v<T, bool> || std::is_same_v<T, uint32_t> ||
-            std::is_same_v<T, int32_t> || std::is_same_v<T, uint64_t> ||
-            std::is_same_v<T, int64_t>,
-        "Protobuf varints must be of type bool, uint32_t, int32_t, uint64_t, "
-        "or int64_t");
-
-    T result;
-    if (Status status = ReadVarintField(
-            std::as_writable_bytes(std::span(&result, 1)), decode_type);
-        !status.ok()) {
-      return status;
-    }
-
-    return result;
-  }
-
-  Status ReadFixedField(std::span<std::byte> out);
-
-  template <typename T>
-  Result<T> ReadFixedField() {
-    static_assert(
-        sizeof(T) == sizeof(uint32_t) || sizeof(T) == sizeof(uint64_t),
-        "Protobuf fixed-size fields must be 32- or 64-bit");
-
-    T result;
-    if (Status status =
-            ReadFixedField(std::as_writable_bytes(std::span(&result, 1)));
-        !status.ok()) {
-      return status;
-    }
-
-    return result;
-  }
-
-  StatusWithSize ReadDelimitedField(std::span<std::byte> out);
-
-  StatusWithSize ReadPackedFixedField(std::span<std::byte> out,
-                                      size_t elem_size);
-
-  StatusWithSize ReadPackedVarintField(std::span<std::byte> out,
-                                       size_t elem_size,
-                                       VarintDecodeType decode_type);
-
-  template <typename T>
-  Status ReadRepeatedFixedField(pw::Vector<T>& out) {
-    if (out.full()) {
-      return Status::ResourceExhausted();
-    }
-    const size_t old_size = out.size();
-    if (current_field_.wire_type() == WireType::kDelimited) {
-      out.resize(out.capacity());
-      const auto sws = ReadPackedFixedField(
-          std::as_writable_bytes(
-              std::span(out.data() + old_size, out.size() - old_size)),
-          sizeof(T));
-      out.resize(old_size + sws.size());
-      return sws.status();
-    } else {
-      out.resize(old_size + 1);
-      const auto status = ReadFixedField(std::as_writable_bytes(
-          std::span(out.data() + old_size, out.size() - old_size)));
-      if (!status.ok()) {
-        out.resize(old_size);
-      }
-      return status;
-    }
-  }
-
-  template <typename T>
-  Status ReadRepeatedVarintField(pw::Vector<T>& out,
-                                 VarintDecodeType decode_type) {
-    if (out.full()) {
-      return Status::ResourceExhausted();
-    }
-    const size_t old_size = out.size();
-    if (current_field_.wire_type() == WireType::kDelimited) {
-      out.resize(out.capacity());
-      const auto sws = ReadPackedVarintField(
-          std::as_writable_bytes(
-              std::span(out.data() + old_size, out.size() - old_size)),
-          sizeof(T),
-          decode_type);
-      out.resize(old_size + sws.size());
-      return sws.status();
-    } else {
-      out.resize(old_size + 1);
-      const auto status =
-          ReadVarintField(std::as_writable_bytes(std::span(
-                              out.data() + old_size, out.size() - old_size)),
-                          decode_type);
-      if (!status.ok()) {
-        out.resize(old_size);
-      }
-      return status;
-    }
-  }
-
-  Status CheckOkToRead(WireType type);
-
-  stream::Reader& reader_;
-  Bounds stream_bounds_;
-  size_t position_;
-
-  FieldKey current_field_;
-  size_t delimited_field_size_;
-  size_t delimited_field_offset_;
-
-  StreamDecoder* parent_;
-
-  bool field_consumed_;
-  bool nested_reader_open_;
-
-  Status status_;
-
-  friend class Message;
-};
-
-}  // namespace pw::protobuf
diff --git a/pw_protobuf/public/pw_protobuf/wire_format.h b/pw_protobuf/public/pw_protobuf/wire_format.h
index a222d7f..a4b73a1 100644
--- a/pw_protobuf/public/pw_protobuf/wire_format.h
+++ b/pw_protobuf/public/pw_protobuf/wire_format.h
@@ -14,32 +14,9 @@
 #pragma once
 
 #include <cstdint>
-#include <limits>
-
-#include "pw_assert/assert.h"
 
 namespace pw::protobuf {
 
-// Per the protobuf specification, valid field numbers range between 1 and
-// 2**29 - 1, inclusive. The numbers 19000-19999 are reserved for internal
-// use.
-constexpr static uint32_t kMaxFieldNumber = (1u << 29) - 1;
-constexpr static uint32_t kFirstReservedNumber = 19000;
-constexpr static uint32_t kLastReservedNumber = 19999;
-
-constexpr bool ValidFieldNumber(uint32_t field_number) {
-  return field_number != 0 && field_number <= kMaxFieldNumber &&
-         !(field_number >= kFirstReservedNumber &&
-           field_number <= kLastReservedNumber);
-}
-
-constexpr bool ValidFieldNumber(uint64_t field_number) {
-  if (field_number > std::numeric_limits<uint32_t>::max()) {
-    return false;
-  }
-  return ValidFieldNumber(static_cast<uint32_t>(field_number));
-}
-
 enum class WireType {
   kVarint = 0,
   kFixed64 = 1,
@@ -48,51 +25,11 @@
   kFixed32 = 5,
 };
 
-// Represents a protobuf field key, storing a field number and wire type.
-class FieldKey {
- public:
-  // Checks if the given encoded protobuf key is valid. Must be called before
-  // instantiating a FieldKey object with it.
-  static constexpr bool IsValidKey(uint64_t key) {
-    uint64_t field_number = key >> kFieldNumberShift;
-    uint32_t wire_type = key & kWireTypeMask;
+inline constexpr unsigned int kFieldNumberShift = 3u;
+inline constexpr unsigned int kWireTypeMask = (1u << kFieldNumberShift) - 1u;
 
-    return ValidFieldNumber(field_number) && (wire_type <= 2 || wire_type == 5);
-  }
-
-  // Creates a field key with the given field number and type.
-  //
-  // Precondition: The field number is valid.
-  constexpr FieldKey(uint32_t field_number, WireType wire_type)
-      : key_(field_number << kFieldNumberShift |
-             static_cast<uint32_t>(wire_type)) {
-    PW_DASSERT(ValidFieldNumber(field_number));
-  }
-
-  // Parses a field key from its encoded representation.
-  //
-  // Precondition: The field number is valid. Call IsValidKey(key) first.
-  constexpr FieldKey(uint32_t key) : key_(key) {
-    PW_DASSERT(ValidFieldNumber(field_number()));
-  }
-
-  constexpr operator uint32_t() { return key_; }
-
-  constexpr uint32_t field_number() const { return key_ >> kFieldNumberShift; }
-  constexpr WireType wire_type() const {
-    return static_cast<WireType>(key_ & kWireTypeMask);
-  }
-
- private:
-  static constexpr unsigned int kFieldNumberShift = 3u;
-  static constexpr unsigned int kWireTypeMask = (1u << kFieldNumberShift) - 1u;
-
-  uint32_t key_;
-};
-
-[[deprecated("Use the FieldKey class")]] constexpr uint32_t MakeKey(
-    uint32_t field_number, WireType wire_type) {
-  return FieldKey(field_number, wire_type);
+constexpr uint32_t MakeKey(uint32_t field_number, WireType wire_type) {
+  return (field_number << kFieldNumberShift | static_cast<uint32_t>(wire_type));
 }
 
 }  // namespace pw::protobuf
diff --git a/pw_protobuf/pw_protobuf_protos/status.proto b/pw_protobuf/pw_protobuf_protos/status.proto
deleted file mode 100644
index d60b693..0000000
--- a/pw_protobuf/pw_protobuf_protos/status.proto
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-syntax = "proto3";
-
-package pw.protobuf;
-
-// StatusCode must match pw::Status in pw_status.
-enum StatusCode {
-  OK = 0;
-  CANCELLED = 1;
-  UNKNOWN = 2;
-  INVALID_ARGUMENT = 3;
-  DEADLINE_EXCEEDED = 4;
-  NOT_FOUND = 5;
-  ALREADY_EXISTS = 6;
-  PERMISSION_DENIED = 7;
-  RESOURCE_EXHAUSTED = 8;
-  FAILED_PRECONDITION = 9;
-  ABORTED = 10;
-  OUT_OF_RANGE = 11;
-  UNIMPLEMENTED = 12;
-  INTERNAL = 13;
-  UNAVAILABLE = 14;
-  DATA_LOSS = 15;
-  UNAUTHENTICATED = 16;
-}
\ No newline at end of file
diff --git a/pw_protobuf/pw_protobuf_test_protos/full_test.proto b/pw_protobuf/pw_protobuf_test_protos/full_test.proto
index b8a80fc..7262d32 100644
--- a/pw_protobuf/pw_protobuf_test_protos/full_test.proto
+++ b/pw_protobuf/pw_protobuf_test_protos/full_test.proto
@@ -114,12 +114,6 @@
   repeated KeyValuePair attributes = 4;
 }
 
-// Ensure recursive submessages work.
-message Crate {
-  string name = 1;
-  repeated Crate smaller_crates = 2;
-}
-
 // This might be useful.
 message KeyValuePair {
   string key = 1;
diff --git a/pw_protobuf/pw_protobuf_test_protos/imported.proto b/pw_protobuf/pw_protobuf_test_protos/imported.proto
index 1ba1403..c375380 100644
--- a/pw_protobuf/pw_protobuf_test_protos/imported.proto
+++ b/pw_protobuf/pw_protobuf_test_protos/imported.proto
@@ -19,9 +19,3 @@
   uint64 seconds = 1;
   uint32 nanoseconds = 2;
 }
-
-enum Status {
-  UNKNOWN = 0;
-  NOT_OK = 1;
-  OK = 2;
-}
diff --git a/pw_protobuf/pw_protobuf_test_protos/importer.proto b/pw_protobuf/pw_protobuf_test_protos/importer.proto
index 00bec36..39ad8b9 100644
--- a/pw_protobuf/pw_protobuf_test_protos/importer.proto
+++ b/pw_protobuf/pw_protobuf_test_protos/importer.proto
@@ -26,7 +26,3 @@
 message Nothing {
   pw.protobuf.Empty nothing = 1;
 }
-
-message TestResult {
-  imported.Status status = 1;
-}
diff --git a/pw_protobuf/pw_protobuf_test_protos/proto2.proto b/pw_protobuf/pw_protobuf_test_protos/proto2.proto
index 58d9a87..c8d9e8e 100644
--- a/pw_protobuf/pw_protobuf_test_protos/proto2.proto
+++ b/pw_protobuf/pw_protobuf_test_protos/proto2.proto
@@ -16,7 +16,7 @@
 package pw.protobuf.test;
 
 message Foo {
-  required uint32 integer = 1;
+  required uint32 int = 1;
   optional string str = 2;
   repeated Bar bar = 3;
   optional pb pb = 4;
diff --git a/pw_protobuf/pw_protobuf_test_protos/repeated.proto b/pw_protobuf/pw_protobuf_test_protos/repeated.proto
index 590d2e0..c281db0 100644
--- a/pw_protobuf/pw_protobuf_test_protos/repeated.proto
+++ b/pw_protobuf/pw_protobuf_test_protos/repeated.proto
@@ -21,8 +21,6 @@
   repeated string strings = 3;
   repeated double doubles = 4;
   repeated Struct structs = 5;
-  repeated fixed32 fixed32s = 6;
-  repeated bool bools = 7;
 };
 
 message Struct {
diff --git a/pw_protobuf/py/BUILD.bazel b/pw_protobuf/py/BUILD.bazel
deleted file mode 100644
index c288141..0000000
--- a/pw_protobuf/py/BUILD.bazel
+++ /dev/null
@@ -1,54 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load("@rules_python//python:defs.bzl", "py_binary", "py_library")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-filegroup(
-    name = "pw_protobuf_common_sources",
-    srcs = [
-        "pw_protobuf/__init__.py",
-        "pw_protobuf/codegen_pwpb.py",
-        "pw_protobuf/output_file.py",
-        "pw_protobuf/plugin.py",
-        "pw_protobuf/proto_tree.py",
-    ],
-)
-
-py_library(
-    name = "plugin_library",
-    srcs = [":pw_protobuf_common_sources"],
-    imports = ["."],
-    deps = ["@com_google_protobuf//:protobuf_python"],
-)
-
-py_binary(
-    name = "plugin",
-    srcs = ["pw_protobuf/plugin.py"],
-    imports = ["."],
-    python_version = "PY3",
-    deps = [
-        ":plugin_library",
-        "@com_google_protobuf//:protobuf_python",
-    ],
-)
-
-py_library(
-    name = "pw_protobuf",
-    srcs = [":pw_protobuf_common_sources"],
-    deps = ["//pw_cli/py:pw_cli"],
-)
diff --git a/pw_protobuf/py/BUILD.gn b/pw_protobuf/py/BUILD.gn
index f920ef1..8f363da 100644
--- a/pw_protobuf/py/BUILD.gn
+++ b/pw_protobuf/py/BUILD.gn
@@ -17,11 +17,7 @@
 import("$dir_pw_build/python.gni")
 
 pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
+  setup = [ "setup.py" ]
   sources = [
     "pw_protobuf/__init__.py",
     "pw_protobuf/codegen_pwpb.py",
diff --git a/pw_protobuf/py/pw_protobuf/codegen_pwpb.py b/pw_protobuf/py/pw_protobuf/codegen_pwpb.py
index fc28fba..faf6bb9 100644
--- a/pw_protobuf/py/pw_protobuf/codegen_pwpb.py
+++ b/pw_protobuf/py/pw_protobuf/codegen_pwpb.py
@@ -15,13 +15,12 @@
 
 import abc
 from datetime import datetime
-import enum
 import os
 import sys
 from typing import Dict, Iterable, List, Tuple
 from typing import cast
 
-from google.protobuf import descriptor_pb2
+import google.protobuf.descriptor_pb2 as descriptor_pb2
 
 from pw_protobuf.output_file import OutputFile
 from pw_protobuf.proto_tree import ProtoEnum, ProtoMessage, ProtoMessageField
@@ -34,48 +33,8 @@
 PROTO_H_EXTENSION = '.pwpb.h'
 PROTO_CC_EXTENSION = '.pwpb.cc'
 
-PROTOBUF_NAMESPACE = '::pw::protobuf'
-
-
-class ClassType(enum.Enum):
-    """Type of class."""
-    MEMORY_ENCODER = 1
-    STREAMING_ENCODER = 2
-    # MEMORY_DECODER = 3
-    STREAMING_DECODER = 4
-
-    def base_class_name(self) -> str:
-        """Returns the base class used by this class type."""
-        if self is self.STREAMING_ENCODER:
-            return 'StreamEncoder'
-        if self is self.MEMORY_ENCODER:
-            return 'MemoryEncoder'
-        if self is self.STREAMING_DECODER:
-            return 'StreamDecoder'
-
-        raise ValueError('Unknown class type')
-
-    def codegen_class_name(self) -> str:
-        """Returns the base class used by this class type."""
-        if self is self.STREAMING_ENCODER:
-            return 'StreamEncoder'
-        if self is self.MEMORY_ENCODER:
-            return 'MemoryEncoder'
-        if self is self.STREAMING_DECODER:
-            return 'StreamDecoder'
-
-        raise ValueError('Unknown class type')
-
-    def is_encoder(self) -> bool:
-        """Returns True if this class type is an encoder."""
-        if self is self.STREAMING_ENCODER:
-            return True
-        if self is self.MEMORY_ENCODER:
-            return True
-        if self is self.STREAMING_DECODER:
-            return False
-
-        raise ValueError('Unknown class type')
+PROTOBUF_NAMESPACE = 'pw::protobuf'
+BASE_PROTO_CLASS = 'ProtoMessageEncoder'
 
 
 # protoc captures stdout, so we need to printf debug to stderr.
@@ -158,18 +117,34 @@
         scope = self._root if from_root else self._scope
         type_node = self._field.type_node()
         assert type_node is not None
-
-        # If a class method is referencing its class, the namespace provided
-        # must be from the root or it will be empty.
-        if type_node == scope:
-            scope = self._root
-
         ancestor = scope.common_ancestor(type_node)
         namespace = type_node.cpp_namespace(ancestor)
-        assert namespace
+        assert namespace is not None
         return namespace
 
 
+class SubMessageMethod(ProtoMethod):
+    """Method which returns a sub-message encoder."""
+    def name(self) -> str:
+        return 'Get{}Encoder'.format(self._field.name())
+
+    def return_type(self, from_root: bool = False) -> str:
+        return '{}::Encoder'.format(self._relative_type_namespace(from_root))
+
+    def params(self) -> List[Tuple[str, str]]:
+        return []
+
+    def body(self) -> List[str]:
+        line = 'return {}::Encoder(encoder_, {});'.format(
+            self._relative_type_namespace(), self.field_cast())
+        return [line]
+
+    # Submessage methods are not defined within the class itself because the
+    # submessage class may not yet have been defined.
+    def in_class_definition(self) -> bool:
+        return False
+
+
 class WriteMethod(ProtoMethod):
     """Base class representing an encoder write method.
 
@@ -188,8 +163,8 @@
 
     def body(self) -> List[str]:
         params = ', '.join([pair[1] for pair in self.params()])
-        line = 'return {}({}, {});'.format(self._encoder_fn(),
-                                           self.field_cast(), params)
+        line = 'return encoder_->{}({}, {});'.format(self._encoder_fn(),
+                                                     self.field_cast(), params)
         return [line]
 
     def params(self) -> List[Tuple[str, str]]:
@@ -209,8 +184,8 @@
         raise NotImplementedError()
 
 
-class PackedWriteMethod(WriteMethod):
-    """A method for a writing a packed repeated field.
+class PackedMethod(WriteMethod):
+    """A method for a packed repeated field.
 
     Same as a WriteMethod, but is only generated for repeated fields.
     """
@@ -221,166 +196,13 @@
         raise NotImplementedError()
 
 
-class ReadMethod(ProtoMethod):
-    """Base class representing an decoder read method.
-
-    Read methods have following format (for the proto field foo):
-
-        Result<{ctype}> ReadFoo({params...}) {
-          Result<uint32_t> field_number = FieldNumber();
-          PW_ASSERT(field_number.ok());
-          PW_ASSERT(field_number.value() == static_cast<uint32_t>(Fields::FOO));
-          return decoder_->Read{type}({params...});
-        }
-
-    """
-    def name(self) -> str:
-        return 'Read{}'.format(self._field.name())
-
-    def return_type(self, from_root: bool = False) -> str:
-        return '::pw::Result<{}>'.format(self._result_type())
-
-    def _result_type(self) -> str:
-        """The type returned by the deoder function.
-
-        Defined in subclasses.
-
-        e.g. 'uint32_t', 'std::span<std::byte>', etc.
-        """
-        raise NotImplementedError()
-
-    def body(self) -> List[str]:
-        lines: List[str] = []
-        lines += ['::pw::Result<uint32_t> field_number = FieldNumber();']
-        lines += ['PW_ASSERT(field_number.ok());']
-        lines += [
-            'PW_ASSERT(field_number.value() == {});'.format(self.field_cast())
-        ]
-        lines += self._decoder_body()
-        return lines
-
-    def _decoder_body(self) -> List[str]:
-        """Returns the decoder body part as a list of source code lines."""
-        params = ', '.join([pair[1] for pair in self.params()])
-        line = 'return {}({});'.format(self._decoder_fn(), params)
-        return [line]
-
-    def _decoder_fn(self) -> str:
-        """The decoder function to call.
-
-        Defined in subclasses.
-
-        e.g. 'ReadUint32', 'ReadBytes', etc.
-        """
-        raise NotImplementedError()
-
-    def params(self) -> List[Tuple[str, str]]:
-        """Method parameters, can be overriden in subclasses."""
-        return []
-
-    def in_class_definition(self) -> bool:
-        return True
-
-
-class PackedReadMethod(ReadMethod):
-    """A method for a reading a packed repeated field.
-
-    Same as ReadMethod, but is only generated for repeated fields.
-    """
-    def should_appear(self) -> bool:
-        return self._field.is_repeated()
-
-    def return_type(self, from_root: bool = False) -> str:
-        return '::pw::StatusWithSize'
-
-    def params(self) -> List[Tuple[str, str]]:
-        return [('std::span<{}>'.format(self._result_type()), 'out')]
-
-
-class PackedReadVectorMethod(ReadMethod):
-    """A method for a reading a packed repeated field.
-
-    An alternative to ReadMethod for repeated fields that appends values into
-    a pw::Vector.
-    """
-    def should_appear(self) -> bool:
-        return self._field.is_repeated()
-
-    def return_type(self, from_root: bool = False) -> str:
-        return '::pw::Status'
-
-    def params(self) -> List[Tuple[str, str]]:
-        return [('::pw::Vector<{}>&'.format(self._result_type()), 'out')]
-
-
 #
-# The following code defines write and read methods for each of the
-# complex protobuf types.
-#
-
-
-class SubMessageEncoderMethod(ProtoMethod):
-    """Method which returns a sub-message encoder."""
-    def name(self) -> str:
-        return 'Get{}Encoder'.format(self._field.name())
-
-    def return_type(self, from_root: bool = False) -> str:
-        return '{}::StreamEncoder'.format(
-            self._relative_type_namespace(from_root))
-
-    def params(self) -> List[Tuple[str, str]]:
-        return []
-
-    def body(self) -> List[str]:
-        line = 'return {}::StreamEncoder(GetNestedEncoder({}));'.format(
-            self._relative_type_namespace(), self.field_cast())
-        return [line]
-
-    # Submessage methods are not defined within the class itself because the
-    # submessage class may not yet have been defined.
-    def in_class_definition(self) -> bool:
-        return False
-
-
-class SubMessageDecoderMethod(ReadMethod):
-    """Method which returns a sub-message decoder."""
-    def name(self) -> str:
-        return 'Get{}Decoder'.format(self._field.name())
-
-    def return_type(self, from_root: bool = False) -> str:
-        return '{}::StreamDecoder'.format(
-            self._relative_type_namespace(from_root))
-
-    def _decoder_body(self) -> List[str]:
-        line = 'return {}::StreamDecoder(GetNestedDecoder());'.format(
-            self._relative_type_namespace())
-        return [line]
-
-    # Submessage methods are not defined within the class itself because the
-    # submessage class may not yet have been defined.
-    def in_class_definition(self) -> bool:
-        return False
-
-
-class BytesReaderMethod(ReadMethod):
-    """Method which returns a bytes reader."""
-    def name(self) -> str:
-        return 'Get{}Reader'.format(self._field.name())
-
-    def return_type(self, from_root: bool = False) -> str:
-        return '::pw::protobuf::StreamDecoder::BytesReader'
-
-    def _decoder_fn(self) -> str:
-        return 'GetBytesReader'
-
-
-#
-# The following code defines write and read methods for each of the
+# The following code defines write methods for each of the
 # primitive protobuf types.
 #
 
 
-class DoubleWriteMethod(WriteMethod):
+class DoubleMethod(WriteMethod):
     """Method which writes a proto double value."""
     def params(self) -> List[Tuple[str, str]]:
         return [('double', 'value')]
@@ -389,7 +211,7 @@
         return 'WriteDouble'
 
 
-class PackedDoubleWriteMethod(PackedWriteMethod):
+class PackedDoubleMethod(PackedMethod):
     """Method which writes a packed list of doubles."""
     def params(self) -> List[Tuple[str, str]]:
         return [('std::span<const double>', 'values')]
@@ -398,43 +220,7 @@
         return 'WritePackedDouble'
 
 
-class PackedDoubleWriteVectorMethod(PackedWriteMethod):
-    """Method which writes a packed vector of doubles."""
-    def params(self) -> List[Tuple[str, str]]:
-        return [('const ::pw::Vector<double>&', 'values')]
-
-    def _encoder_fn(self) -> str:
-        return 'WriteRepeatedDouble'
-
-
-class DoubleReadMethod(ReadMethod):
-    """Method which reads a proto double value."""
-    def _result_type(self) -> str:
-        return 'double'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadDouble'
-
-
-class PackedDoubleReadMethod(PackedReadMethod):
-    """Method which reads packed double values."""
-    def _result_type(self) -> str:
-        return 'double'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadPackedDouble'
-
-
-class PackedDoubleReadVectorMethod(PackedReadVectorMethod):
-    """Method which reads packed double values."""
-    def _result_type(self) -> str:
-        return 'double'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadRepeatedDouble'
-
-
-class FloatWriteMethod(WriteMethod):
+class FloatMethod(WriteMethod):
     """Method which writes a proto float value."""
     def params(self) -> List[Tuple[str, str]]:
         return [('float', 'value')]
@@ -443,7 +229,7 @@
         return 'WriteFloat'
 
 
-class PackedFloatWriteMethod(PackedWriteMethod):
+class PackedFloatMethod(PackedMethod):
     """Method which writes a packed list of floats."""
     def params(self) -> List[Tuple[str, str]]:
         return [('std::span<const float>', 'values')]
@@ -452,43 +238,7 @@
         return 'WritePackedFloat'
 
 
-class PackedFloatWriteVectorMethod(PackedWriteMethod):
-    """Method which writes a packed vector of floats."""
-    def params(self) -> List[Tuple[str, str]]:
-        return [('const ::pw::Vector<float>&', 'values')]
-
-    def _encoder_fn(self) -> str:
-        return 'WriteRepeatedFloat'
-
-
-class FloatReadMethod(ReadMethod):
-    """Method which reads a proto float value."""
-    def _result_type(self) -> str:
-        return 'float'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadFloat'
-
-
-class PackedFloatReadMethod(PackedReadMethod):
-    """Method which reads packed float values."""
-    def _result_type(self) -> str:
-        return 'float'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadPackedFloat'
-
-
-class PackedFloatReadVectorMethod(PackedReadVectorMethod):
-    """Method which reads packed float values."""
-    def _result_type(self) -> str:
-        return 'float'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadRepeatedFloat'
-
-
-class Int32WriteMethod(WriteMethod):
+class Int32Method(WriteMethod):
     """Method which writes a proto int32 value."""
     def params(self) -> List[Tuple[str, str]]:
         return [('int32_t', 'value')]
@@ -497,7 +247,7 @@
         return 'WriteInt32'
 
 
-class PackedInt32WriteMethod(PackedWriteMethod):
+class PackedInt32Method(PackedMethod):
     """Method which writes a packed list of int32."""
     def params(self) -> List[Tuple[str, str]]:
         return [('std::span<const int32_t>', 'values')]
@@ -506,43 +256,7 @@
         return 'WritePackedInt32'
 
 
-class PackedInt32WriteVectorMethod(PackedWriteMethod):
-    """Method which writes a packed vector of int32."""
-    def params(self) -> List[Tuple[str, str]]:
-        return [('const ::pw::Vector<int32_t>&', 'values')]
-
-    def _encoder_fn(self) -> str:
-        return 'WriteRepeatedInt32'
-
-
-class Int32ReadMethod(ReadMethod):
-    """Method which reads a proto int32 value."""
-    def _result_type(self) -> str:
-        return 'int32_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadInt32'
-
-
-class PackedInt32ReadMethod(PackedReadMethod):
-    """Method which reads packed int32 values."""
-    def _result_type(self) -> str:
-        return 'int32_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadPackedInt32'
-
-
-class PackedInt32ReadVectorMethod(PackedReadVectorMethod):
-    """Method which reads packed int32 values."""
-    def _result_type(self) -> str:
-        return 'int32_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadRepeatedInt32'
-
-
-class Sint32WriteMethod(WriteMethod):
+class Sint32Method(WriteMethod):
     """Method which writes a proto sint32 value."""
     def params(self) -> List[Tuple[str, str]]:
         return [('int32_t', 'value')]
@@ -551,7 +265,7 @@
         return 'WriteSint32'
 
 
-class PackedSint32WriteMethod(PackedWriteMethod):
+class PackedSint32Method(PackedMethod):
     """Method which writes a packed list of sint32."""
     def params(self) -> List[Tuple[str, str]]:
         return [('std::span<const int32_t>', 'values')]
@@ -560,43 +274,7 @@
         return 'WritePackedSint32'
 
 
-class PackedSint32WriteVectorMethod(PackedWriteMethod):
-    """Method which writes a packed vector of sint32."""
-    def params(self) -> List[Tuple[str, str]]:
-        return [('const ::pw::Vector<int32_t>&', 'values')]
-
-    def _encoder_fn(self) -> str:
-        return 'WriteRepeatedSint32'
-
-
-class Sint32ReadMethod(ReadMethod):
-    """Method which reads a proto sint32 value."""
-    def _result_type(self) -> str:
-        return 'int32_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadSint32'
-
-
-class PackedSint32ReadMethod(PackedReadMethod):
-    """Method which reads packed sint32 values."""
-    def _result_type(self) -> str:
-        return 'int32_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadPackedSint32'
-
-
-class PackedSint32ReadVectorMethod(PackedReadVectorMethod):
-    """Method which reads packed sint32 values."""
-    def _result_type(self) -> str:
-        return 'int32_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadRepeatedSint32'
-
-
-class Sfixed32WriteMethod(WriteMethod):
+class Sfixed32Method(WriteMethod):
     """Method which writes a proto sfixed32 value."""
     def params(self) -> List[Tuple[str, str]]:
         return [('int32_t', 'value')]
@@ -605,7 +283,7 @@
         return 'WriteSfixed32'
 
 
-class PackedSfixed32WriteMethod(PackedWriteMethod):
+class PackedSfixed32Method(PackedMethod):
     """Method which writes a packed list of sfixed32."""
     def params(self) -> List[Tuple[str, str]]:
         return [('std::span<const int32_t>', 'values')]
@@ -614,43 +292,7 @@
         return 'WritePackedSfixed32'
 
 
-class PackedSfixed32WriteVectorMethod(PackedWriteMethod):
-    """Method which writes a packed vector of sfixed32."""
-    def params(self) -> List[Tuple[str, str]]:
-        return [('const ::pw::Vector<int32_t>&', 'values')]
-
-    def _encoder_fn(self) -> str:
-        return 'WriteRepeatedSfixed32'
-
-
-class Sfixed32ReadMethod(ReadMethod):
-    """Method which reads a proto sfixed32 value."""
-    def _result_type(self) -> str:
-        return 'int32_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadSfixed32'
-
-
-class PackedSfixed32ReadMethod(PackedReadMethod):
-    """Method which reads packed sfixed32 values."""
-    def _result_type(self) -> str:
-        return 'int32_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadPackedSfixed32'
-
-
-class PackedSfixed32ReadVectorMethod(PackedReadVectorMethod):
-    """Method which reads packed sfixed32 values."""
-    def _result_type(self) -> str:
-        return 'int32_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadRepeatedSfixed32'
-
-
-class Int64WriteMethod(WriteMethod):
+class Int64Method(WriteMethod):
     """Method which writes a proto int64 value."""
     def params(self) -> List[Tuple[str, str]]:
         return [('int64_t', 'value')]
@@ -659,8 +301,8 @@
         return 'WriteInt64'
 
 
-class PackedInt64WriteMethod(PackedWriteMethod):
-    """Method which writes a packed list of int64."""
+class PackedInt64Method(PackedMethod):
+    """Method which writes a proto int64 value."""
     def params(self) -> List[Tuple[str, str]]:
         return [('std::span<const int64_t>', 'values')]
 
@@ -668,43 +310,7 @@
         return 'WritePackedInt64'
 
 
-class PackedInt64WriteVectorMethod(PackedWriteMethod):
-    """Method which writes a packed vector of int64."""
-    def params(self) -> List[Tuple[str, str]]:
-        return [('const ::pw::Vector<int64_t>&', 'values')]
-
-    def _encoder_fn(self) -> str:
-        return 'WriteRepeatedInt64'
-
-
-class Int64ReadMethod(ReadMethod):
-    """Method which reads a proto int64 value."""
-    def _result_type(self) -> str:
-        return 'int64_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadInt64'
-
-
-class PackedInt64ReadMethod(PackedReadMethod):
-    """Method which reads packed int64 values."""
-    def _result_type(self) -> str:
-        return 'int64_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadPackedInt64'
-
-
-class PackedInt64ReadVectorMethod(PackedReadVectorMethod):
-    """Method which reads packed int64 values."""
-    def _result_type(self) -> str:
-        return 'int64_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadRepeatedInt64'
-
-
-class Sint64WriteMethod(WriteMethod):
+class Sint64Method(WriteMethod):
     """Method which writes a proto sint64 value."""
     def params(self) -> List[Tuple[str, str]]:
         return [('int64_t', 'value')]
@@ -713,8 +319,8 @@
         return 'WriteSint64'
 
 
-class PackedSint64WriteMethod(PackedWriteMethod):
-    """Method which writes a packst list of sint64."""
+class PackedSint64Method(PackedMethod):
+    """Method which writes a proto sint64 value."""
     def params(self) -> List[Tuple[str, str]]:
         return [('std::span<const int64_t>', 'values')]
 
@@ -722,43 +328,7 @@
         return 'WritePackedSint64'
 
 
-class PackedSint64WriteVectorMethod(PackedWriteMethod):
-    """Method which writes a packed vector of sint64."""
-    def params(self) -> List[Tuple[str, str]]:
-        return [('const ::pw::Vector<int64_t>&', 'values')]
-
-    def _encoder_fn(self) -> str:
-        return 'WriteRepeatedSint64'
-
-
-class Sint64ReadMethod(ReadMethod):
-    """Method which reads a proto sint64 value."""
-    def _result_type(self) -> str:
-        return 'int64_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadSint64'
-
-
-class PackedSint64ReadMethod(PackedReadMethod):
-    """Method which reads packed sint64 values."""
-    def _result_type(self) -> str:
-        return 'int64_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadPackedSint64'
-
-
-class PackedSint64ReadVectorMethod(PackedReadVectorMethod):
-    """Method which reads packed sint64 values."""
-    def _result_type(self) -> str:
-        return 'int64_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadRepeatedSint64'
-
-
-class Sfixed64WriteMethod(WriteMethod):
+class Sfixed64Method(WriteMethod):
     """Method which writes a proto sfixed64 value."""
     def params(self) -> List[Tuple[str, str]]:
         return [('int64_t', 'value')]
@@ -767,8 +337,8 @@
         return 'WriteSfixed64'
 
 
-class PackedSfixed64WriteMethod(PackedWriteMethod):
-    """Method which writes a packed list of sfixed64."""
+class PackedSfixed64Method(PackedMethod):
+    """Method which writes a proto sfixed64 value."""
     def params(self) -> List[Tuple[str, str]]:
         return [('std::span<const int64_t>', 'values')]
 
@@ -776,43 +346,7 @@
         return 'WritePackedSfixed4'
 
 
-class PackedSfixed64WriteVectorMethod(PackedWriteMethod):
-    """Method which writes a packed vector of sfixed64."""
-    def params(self) -> List[Tuple[str, str]]:
-        return [('const ::pw::Vector<int64_t>&', 'values')]
-
-    def _encoder_fn(self) -> str:
-        return 'WriteRepeatedSfixed4'
-
-
-class Sfixed64ReadMethod(ReadMethod):
-    """Method which reads a proto sfixed64 value."""
-    def _result_type(self) -> str:
-        return 'int64_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadSfixed64'
-
-
-class PackedSfixed64ReadMethod(PackedReadMethod):
-    """Method which reads packed sfixed64 values."""
-    def _result_type(self) -> str:
-        return 'int64_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadPackedSfixed64'
-
-
-class PackedSfixed64ReadVectorMethod(PackedReadVectorMethod):
-    """Method which reads packed sfixed64 values."""
-    def _result_type(self) -> str:
-        return 'int64_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadRepeatedSfixed64'
-
-
-class Uint32WriteMethod(WriteMethod):
+class Uint32Method(WriteMethod):
     """Method which writes a proto uint32 value."""
     def params(self) -> List[Tuple[str, str]]:
         return [('uint32_t', 'value')]
@@ -821,8 +355,8 @@
         return 'WriteUint32'
 
 
-class PackedUint32WriteMethod(PackedWriteMethod):
-    """Method which writes a packed list of uint32."""
+class PackedUint32Method(PackedMethod):
+    """Method which writes a proto uint32 value."""
     def params(self) -> List[Tuple[str, str]]:
         return [('std::span<const uint32_t>', 'values')]
 
@@ -830,43 +364,7 @@
         return 'WritePackedUint32'
 
 
-class PackedUint32WriteVectorMethod(PackedWriteMethod):
-    """Method which writes a packed vector of uint32."""
-    def params(self) -> List[Tuple[str, str]]:
-        return [('const ::pw::Vector<uint32_t>&', 'values')]
-
-    def _encoder_fn(self) -> str:
-        return 'WriteRepeatedUint32'
-
-
-class Uint32ReadMethod(ReadMethod):
-    """Method which reads a proto uint32 value."""
-    def _result_type(self) -> str:
-        return 'uint32_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadUint32'
-
-
-class PackedUint32ReadMethod(PackedReadMethod):
-    """Method which reads packed uint32 values."""
-    def _result_type(self) -> str:
-        return 'uint32_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadPackedUint32'
-
-
-class PackedUint32ReadVectorMethod(PackedReadVectorMethod):
-    """Method which reads packed uint32 values."""
-    def _result_type(self) -> str:
-        return 'uint32_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadRepeatedUint32'
-
-
-class Fixed32WriteMethod(WriteMethod):
+class Fixed32Method(WriteMethod):
     """Method which writes a proto fixed32 value."""
     def params(self) -> List[Tuple[str, str]]:
         return [('uint32_t', 'value')]
@@ -875,8 +373,8 @@
         return 'WriteFixed32'
 
 
-class PackedFixed32WriteMethod(PackedWriteMethod):
-    """Method which writes a packed list of fixed32."""
+class PackedFixed32Method(PackedMethod):
+    """Method which writes a proto fixed32 value."""
     def params(self) -> List[Tuple[str, str]]:
         return [('std::span<const uint32_t>', 'values')]
 
@@ -884,43 +382,7 @@
         return 'WritePackedFixed32'
 
 
-class PackedFixed32WriteVectorMethod(PackedWriteMethod):
-    """Method which writes a packed vector of fixed32."""
-    def params(self) -> List[Tuple[str, str]]:
-        return [('const ::pw::Vector<uint32_t>&', 'values')]
-
-    def _encoder_fn(self) -> str:
-        return 'WriteRepeatedFixed32'
-
-
-class Fixed32ReadMethod(ReadMethod):
-    """Method which reads a proto fixed32 value."""
-    def _result_type(self) -> str:
-        return 'uint32_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadFixed32'
-
-
-class PackedFixed32ReadMethod(PackedReadMethod):
-    """Method which reads packed fixed32 values."""
-    def _result_type(self) -> str:
-        return 'uint32_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadPackedFixed32'
-
-
-class PackedFixed32ReadVectorMethod(PackedReadVectorMethod):
-    """Method which reads packed fixed32 values."""
-    def _result_type(self) -> str:
-        return 'uint32_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadRepeatedFixed32'
-
-
-class Uint64WriteMethod(WriteMethod):
+class Uint64Method(WriteMethod):
     """Method which writes a proto uint64 value."""
     def params(self) -> List[Tuple[str, str]]:
         return [('uint64_t', 'value')]
@@ -929,8 +391,8 @@
         return 'WriteUint64'
 
 
-class PackedUint64WriteMethod(PackedWriteMethod):
-    """Method which writes a packed list of uint64."""
+class PackedUint64Method(PackedMethod):
+    """Method which writes a proto uint64 value."""
     def params(self) -> List[Tuple[str, str]]:
         return [('std::span<const uint64_t>', 'values')]
 
@@ -938,43 +400,7 @@
         return 'WritePackedUint64'
 
 
-class PackedUint64WriteVectorMethod(PackedWriteMethod):
-    """Method which writes a packed vector of uint64."""
-    def params(self) -> List[Tuple[str, str]]:
-        return [('const ::pw::Vector<uint64_t>&', 'values')]
-
-    def _encoder_fn(self) -> str:
-        return 'WriteRepeatedUint64'
-
-
-class Uint64ReadMethod(ReadMethod):
-    """Method which reads a proto uint64 value."""
-    def _result_type(self) -> str:
-        return 'uint64_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadUint64'
-
-
-class PackedUint64ReadMethod(PackedReadMethod):
-    """Method which reads packed uint64 values."""
-    def _result_type(self) -> str:
-        return 'uint64_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadPackedUint64'
-
-
-class PackedUint64ReadVectorMethod(PackedReadVectorMethod):
-    """Method which reads packed uint64 values."""
-    def _result_type(self) -> str:
-        return 'uint64_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadRepeatedUint64'
-
-
-class Fixed64WriteMethod(WriteMethod):
+class Fixed64Method(WriteMethod):
     """Method which writes a proto fixed64 value."""
     def params(self) -> List[Tuple[str, str]]:
         return [('uint64_t', 'value')]
@@ -983,8 +409,8 @@
         return 'WriteFixed64'
 
 
-class PackedFixed64WriteMethod(PackedWriteMethod):
-    """Method which writes a packed list of fixed64."""
+class PackedFixed64Method(PackedMethod):
+    """Method which writes a proto fixed64 value."""
     def params(self) -> List[Tuple[str, str]]:
         return [('std::span<const uint64_t>', 'values')]
 
@@ -992,43 +418,7 @@
         return 'WritePackedFixed64'
 
 
-class PackedFixed64WriteVectorMethod(PackedWriteMethod):
-    """Method which writes a packed list of fixed64."""
-    def params(self) -> List[Tuple[str, str]]:
-        return [('const ::pw::Vector<uint64_t>&', 'values')]
-
-    def _encoder_fn(self) -> str:
-        return 'WriteRepeatedFixed64'
-
-
-class Fixed64ReadMethod(ReadMethod):
-    """Method which reads a proto fixed64 value."""
-    def _result_type(self) -> str:
-        return 'uint64_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadFixed64'
-
-
-class PackedFixed64ReadMethod(PackedReadMethod):
-    """Method which reads packed fixed64 values."""
-    def _result_type(self) -> str:
-        return 'uint64_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadPackedFixed64'
-
-
-class PackedFixed64ReadVectorMethod(PackedReadVectorMethod):
-    """Method which reads packed fixed64 values."""
-    def _result_type(self) -> str:
-        return 'uint64_t'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadRepeatedFixed64'
-
-
-class BoolWriteMethod(WriteMethod):
+class BoolMethod(WriteMethod):
     """Method which writes a proto bool value."""
     def params(self) -> List[Tuple[str, str]]:
         return [('bool', 'value')]
@@ -1037,43 +427,7 @@
         return 'WriteBool'
 
 
-class PackedBoolWriteMethod(PackedWriteMethod):
-    """Method which writes a packed list of bools."""
-    def params(self) -> List[Tuple[str, str]]:
-        return [('std::span<const bool>', 'values')]
-
-    def _encoder_fn(self) -> str:
-        return 'WritePackedBool'
-
-
-class PackedBoolWriteVectorMethod(PackedWriteMethod):
-    """Method which writes a packed vector of bools."""
-    def params(self) -> List[Tuple[str, str]]:
-        return [('const ::pw::Vector<bool>&', 'values')]
-
-    def _encoder_fn(self) -> str:
-        return 'WriteRepeatedBool'
-
-
-class BoolReadMethod(ReadMethod):
-    """Method which reads a proto bool value."""
-    def _result_type(self) -> str:
-        return 'bool'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadBool'
-
-
-class PackedBoolReadMethod(PackedReadMethod):
-    """Method which reads packed bool values."""
-    def _result_type(self) -> str:
-        return 'bool'
-
-    def _decoder_fn(self) -> str:
-        return 'ReadPackedBool'
-
-
-class BytesWriteMethod(WriteMethod):
+class BytesMethod(WriteMethod):
     """Method which writes a proto bytes value."""
     def params(self) -> List[Tuple[str, str]]:
         return [('std::span<const std::byte>', 'value')]
@@ -1082,19 +436,7 @@
         return 'WriteBytes'
 
 
-class BytesReadMethod(ReadMethod):
-    """Method which reads a proto bytes value."""
-    def return_type(self, from_root: bool = False) -> str:
-        return '::pw::StatusWithSize'
-
-    def params(self) -> List[Tuple[str, str]]:
-        return [('std::span<std::byte>', 'out')]
-
-    def _decoder_fn(self) -> str:
-        return 'ReadBytes'
-
-
-class StringLenWriteMethod(WriteMethod):
+class StringLenMethod(WriteMethod):
     """Method which writes a proto string value with length."""
     def params(self) -> List[Tuple[str, str]]:
         return [('const char*', 'value'), ('size_t', 'len')]
@@ -1103,34 +445,22 @@
         return 'WriteString'
 
 
-class StringWriteMethod(WriteMethod):
+class StringMethod(WriteMethod):
     """Method which writes a proto string value."""
     def params(self) -> List[Tuple[str, str]]:
-        return [('std::string_view', 'value')]
+        return [('const char*', 'value')]
 
     def _encoder_fn(self) -> str:
         return 'WriteString'
 
 
-class StringReadMethod(ReadMethod):
-    """Method which reads a proto string value."""
-    def return_type(self, from_root: bool = False) -> str:
-        return '::pw::StatusWithSize'
-
-    def params(self) -> List[Tuple[str, str]]:
-        return [('std::span<char>', 'out')]
-
-    def _decoder_fn(self) -> str:
-        return 'ReadString'
-
-
-class EnumWriteMethod(WriteMethod):
+class EnumMethod(WriteMethod):
     """Method which writes a proto enum value."""
     def params(self) -> List[Tuple[str, str]]:
         return [(self._relative_type_namespace(), 'value')]
 
     def body(self) -> List[str]:
-        line = 'return WriteUint32(' \
+        line = 'return encoder_->WriteUint32(' \
             '{}, static_cast<uint32_t>(value));'.format(self.field_cast())
         return [line]
 
@@ -1141,185 +471,61 @@
         raise NotImplementedError()
 
 
-class EnumReadMethod(ReadMethod):
-    """Method which reads a proto enum value."""
-    def _result_type(self):
-        return self._relative_type_namespace()
-
-    def _decoder_body(self) -> List[str]:
-        lines: List[str] = []
-        lines += ['::pw::Result<uint32_t> value = ReadUint32();']
-        lines += ['if (!value.ok()) {']
-        lines += ['  return value.status();']
-        lines += ['}']
-
-        name_parts = self._relative_type_namespace().split('::')
-        enum_name = name_parts.pop()
-        function_name = '::'.join(name_parts + [f'Get{enum_name}'])
-
-        lines += [f'return {function_name}(value.value());']
-        return lines
-
-
 # Mapping of protobuf field types to their method definitions.
-PROTO_FIELD_WRITE_METHODS: Dict[int, List] = {
-    descriptor_pb2.FieldDescriptorProto.TYPE_DOUBLE: [
-        DoubleWriteMethod, PackedDoubleWriteMethod,
-        PackedDoubleWriteVectorMethod
-    ],
-    descriptor_pb2.FieldDescriptorProto.TYPE_FLOAT:
-    [FloatWriteMethod, PackedFloatWriteMethod, PackedFloatWriteVectorMethod],
-    descriptor_pb2.FieldDescriptorProto.TYPE_INT32:
-    [Int32WriteMethod, PackedInt32WriteMethod, PackedInt32WriteVectorMethod],
-    descriptor_pb2.FieldDescriptorProto.TYPE_SINT32: [
-        Sint32WriteMethod, PackedSint32WriteMethod,
-        PackedSint32WriteVectorMethod
-    ],
-    descriptor_pb2.FieldDescriptorProto.TYPE_SFIXED32: [
-        Sfixed32WriteMethod, PackedSfixed32WriteMethod,
-        PackedSfixed32WriteVectorMethod
-    ],
-    descriptor_pb2.FieldDescriptorProto.TYPE_INT64:
-    [Int64WriteMethod, PackedInt64WriteMethod, PackedInt64WriteVectorMethod],
-    descriptor_pb2.FieldDescriptorProto.TYPE_SINT64: [
-        Sint64WriteMethod, PackedSint64WriteMethod,
-        PackedSint64WriteVectorMethod
-    ],
-    descriptor_pb2.FieldDescriptorProto.TYPE_SFIXED64: [
-        Sfixed64WriteMethod, PackedSfixed64WriteMethod,
-        PackedSfixed64WriteVectorMethod
-    ],
-    descriptor_pb2.FieldDescriptorProto.TYPE_UINT32: [
-        Uint32WriteMethod, PackedUint32WriteMethod,
-        PackedUint32WriteVectorMethod
-    ],
-    descriptor_pb2.FieldDescriptorProto.TYPE_FIXED32: [
-        Fixed32WriteMethod, PackedFixed32WriteMethod,
-        PackedFixed32WriteVectorMethod
-    ],
-    descriptor_pb2.FieldDescriptorProto.TYPE_UINT64: [
-        Uint64WriteMethod, PackedUint64WriteMethod,
-        PackedUint64WriteVectorMethod
-    ],
-    descriptor_pb2.FieldDescriptorProto.TYPE_FIXED64: [
-        Fixed64WriteMethod, PackedFixed64WriteMethod,
-        PackedFixed64WriteVectorMethod
-    ],
-    descriptor_pb2.FieldDescriptorProto.TYPE_BOOL:
-    [BoolWriteMethod, PackedBoolWriteMethod, PackedBoolWriteVectorMethod],
-    descriptor_pb2.FieldDescriptorProto.TYPE_BYTES: [BytesWriteMethod],
-    descriptor_pb2.FieldDescriptorProto.TYPE_STRING:
-    [StringLenWriteMethod, StringWriteMethod],
-    descriptor_pb2.FieldDescriptorProto.TYPE_MESSAGE:
-    [SubMessageEncoderMethod],
-    descriptor_pb2.FieldDescriptorProto.TYPE_ENUM: [EnumWriteMethod],
-}
-
-PROTO_FIELD_READ_METHODS: Dict[int, List] = {
+PROTO_FIELD_METHODS: Dict[int, List] = {
     descriptor_pb2.FieldDescriptorProto.TYPE_DOUBLE:
-    [DoubleReadMethod, PackedDoubleReadMethod, PackedDoubleReadVectorMethod],
+    [DoubleMethod, PackedDoubleMethod],
     descriptor_pb2.FieldDescriptorProto.TYPE_FLOAT:
-    [FloatReadMethod, PackedFloatReadMethod, PackedFloatReadVectorMethod],
+    [FloatMethod, PackedFloatMethod],
     descriptor_pb2.FieldDescriptorProto.TYPE_INT32:
-    [Int32ReadMethod, PackedInt32ReadMethod, PackedInt32ReadVectorMethod],
+    [Int32Method, PackedInt32Method],
     descriptor_pb2.FieldDescriptorProto.TYPE_SINT32:
-    [Sint32ReadMethod, PackedSint32ReadMethod, PackedSint32ReadVectorMethod],
-    descriptor_pb2.FieldDescriptorProto.TYPE_SFIXED32: [
-        Sfixed32ReadMethod, PackedSfixed32ReadMethod,
-        PackedSfixed32ReadVectorMethod
-    ],
+    [Sint32Method, PackedSint32Method],
+    descriptor_pb2.FieldDescriptorProto.TYPE_SFIXED32:
+    [Sfixed32Method, PackedSfixed32Method],
     descriptor_pb2.FieldDescriptorProto.TYPE_INT64:
-    [Int64ReadMethod, PackedInt64ReadMethod, PackedInt64ReadVectorMethod],
+    [Int64Method, PackedInt64Method],
     descriptor_pb2.FieldDescriptorProto.TYPE_SINT64:
-    [Sint64ReadMethod, PackedSint64ReadMethod, PackedSint64ReadVectorMethod],
-    descriptor_pb2.FieldDescriptorProto.TYPE_SFIXED64: [
-        Sfixed64ReadMethod, PackedSfixed64ReadMethod,
-        PackedSfixed64ReadVectorMethod
-    ],
+    [Sint64Method, PackedSint64Method],
+    descriptor_pb2.FieldDescriptorProto.TYPE_SFIXED64:
+    [Sfixed64Method, PackedSfixed64Method],
     descriptor_pb2.FieldDescriptorProto.TYPE_UINT32:
-    [Uint32ReadMethod, PackedUint32ReadMethod, PackedUint32ReadVectorMethod],
-    descriptor_pb2.FieldDescriptorProto.TYPE_FIXED32: [
-        Fixed32ReadMethod, PackedFixed32ReadMethod,
-        PackedFixed32ReadVectorMethod
-    ],
+    [Uint32Method, PackedUint32Method],
+    descriptor_pb2.FieldDescriptorProto.TYPE_FIXED32:
+    [Fixed32Method, PackedFixed32Method],
     descriptor_pb2.FieldDescriptorProto.TYPE_UINT64:
-    [Uint64ReadMethod, PackedUint64ReadMethod, PackedUint64ReadVectorMethod],
-    descriptor_pb2.FieldDescriptorProto.TYPE_FIXED64: [
-        Fixed64ReadMethod, PackedFixed64ReadMethod,
-        PackedFixed64ReadVectorMethod
+    [Uint64Method, PackedUint64Method],
+    descriptor_pb2.FieldDescriptorProto.TYPE_FIXED64:
+    [Fixed64Method, PackedFixed64Method],
+    descriptor_pb2.FieldDescriptorProto.TYPE_BOOL: [BoolMethod],
+    descriptor_pb2.FieldDescriptorProto.TYPE_BYTES: [BytesMethod],
+    descriptor_pb2.FieldDescriptorProto.TYPE_STRING: [
+        StringLenMethod, StringMethod
     ],
-    descriptor_pb2.FieldDescriptorProto.TYPE_BOOL:
-    [BoolReadMethod, PackedBoolReadMethod],
-    descriptor_pb2.FieldDescriptorProto.TYPE_BYTES:
-    [BytesReadMethod, BytesReaderMethod],
-    descriptor_pb2.FieldDescriptorProto.TYPE_STRING:
-    [StringReadMethod, BytesReaderMethod],
-    descriptor_pb2.FieldDescriptorProto.TYPE_MESSAGE:
-    [SubMessageDecoderMethod],
-    descriptor_pb2.FieldDescriptorProto.TYPE_ENUM: [EnumReadMethod],
+    descriptor_pb2.FieldDescriptorProto.TYPE_MESSAGE: [SubMessageMethod],
+    descriptor_pb2.FieldDescriptorProto.TYPE_ENUM: [EnumMethod],
 }
 
 
-def proto_field_methods(class_type: ClassType, field_type: int) -> List:
-    return (PROTO_FIELD_WRITE_METHODS[field_type] if class_type.is_encoder()
-            else PROTO_FIELD_READ_METHODS[field_type])
-
-
-def generate_class_for_message(message: ProtoMessage, root: ProtoNode,
-                               output: OutputFile,
-                               class_type: ClassType) -> None:
-    """Creates a C++ class to encode or decoder a protobuf message."""
+def generate_code_for_message(message: ProtoMessage, root: ProtoNode,
+                              output: OutputFile) -> None:
+    """Creates a C++ class for a protobuf message."""
     assert message.type() == ProtoNode.Type.MESSAGE
 
-    base_class_name = class_type.base_class_name()
-    class_name = class_type.codegen_class_name()
-
     # Message classes inherit from the base proto message class in codegen.h
     # and use its constructor.
-    base_class = f'{PROTOBUF_NAMESPACE}::{base_class_name}'
+    base_class = f'{PROTOBUF_NAMESPACE}::{BASE_PROTO_CLASS}'
     output.write_line(
-        f'class {message.cpp_namespace(root)}::{class_name} ' \
-        f': public {base_class} {{'
+        f'class {message.cpp_namespace(root)}::Encoder : public {base_class} {{'
     )
     output.write_line(' public:')
 
     with output.indent():
-        # Inherit the constructors from the base class.
-        output.write_line(f'using {base_class}::{base_class_name};')
-
-        # Declare a move constructor that takes a base class.
-        output.write_line(f'constexpr {class_name}({base_class}&& parent) '
-                          f': {base_class}(std::move(parent)) {{}}')
-
-        # Allow MemoryEncoder& to be converted to StreamEncoder&.
-        if class_type == ClassType.MEMORY_ENCODER:
-            stream_type = (
-                f'::{message.cpp_namespace()}::'
-                f'{ClassType.STREAMING_ENCODER.codegen_class_name()}')
-            output.write_line(
-                f'operator {stream_type}&() '
-                f' {{ return static_cast<{stream_type}&>('
-                f'*static_cast<{PROTOBUF_NAMESPACE}::StreamEncoder*>(this));}}'
-            )
-
-        # Add a typed Field() member to StreamDecoder
-        if class_type == ClassType.STREAMING_DECODER:
-            output.write_line()
-            output.write_line('::pw::Result<Fields> Field() {')
-            with output.indent():
-                output.write_line('::pw::Result<uint32_t> result '
-                                  '= FieldNumber();')
-                output.write_line('if (!result.ok()) {')
-                with output.indent():
-                    output.write_line('return result.status();')
-                output.write_line('}')
-                output.write_line(
-                    'return static_cast<Fields>(result.value());')
-            output.write_line('}')
+        output.write_line(f'using {BASE_PROTO_CLASS}::{BASE_PROTO_CLASS};')
 
         # Generate methods for each of the message's fields.
         for field in message.fields():
-            for method_class in proto_field_methods(class_type, field.type()):
+            for method_class in PROTO_FIELD_METHODS[field.type()]:
                 method = method_class(field, message, root)
                 if not method.should_appear():
                     continue
@@ -1345,20 +551,18 @@
 
 
 def define_not_in_class_methods(message: ProtoMessage, root: ProtoNode,
-                                output: OutputFile,
-                                class_type: ClassType) -> None:
+                                output: OutputFile) -> None:
     """Defines methods for a message class that were previously declared."""
     assert message.type() == ProtoNode.Type.MESSAGE
 
     for field in message.fields():
-        for method_class in proto_field_methods(class_type, field.type()):
+        for method_class in PROTO_FIELD_METHODS[field.type()]:
             method = method_class(field, message, root)
             if not method.should_appear() or method.in_class_definition():
                 continue
 
             output.write_line()
-            class_name = (f'{message.cpp_namespace(root)}::'
-                          f'{class_type.codegen_class_name()}')
+            class_name = f'{message.cpp_namespace(root)}::Encoder'
             method_signature = (
                 f'inline {method.return_type(from_root=True)} '
                 f'{class_name}::{method.name()}({method.param_string()})')
@@ -1369,38 +573,18 @@
             output.write_line('}')
 
 
-def generate_code_for_enum(proto_enum: ProtoEnum, root: ProtoNode,
+def generate_code_for_enum(enum: ProtoEnum, root: ProtoNode,
                            output: OutputFile) -> None:
     """Creates a C++ enum for a proto enum."""
-    assert proto_enum.type() == ProtoNode.Type.ENUM
+    assert enum.type() == ProtoNode.Type.ENUM
 
-    output.write_line(f'enum class {proto_enum.cpp_namespace(root)} {{')
+    output.write_line(f'enum class {enum.cpp_namespace(root)} {{')
     with output.indent():
-        for name, number in proto_enum.values():
+        for name, number in enum.values():
             output.write_line(f'{name} = {number},')
     output.write_line('};')
 
 
-def generate_function_for_enum(proto_enum: ProtoEnum, root: ProtoNode,
-                               output: OutputFile) -> None:
-    """Creates a C++ validation function for for a proto enum."""
-    assert proto_enum.type() == ProtoNode.Type.ENUM
-
-    enum_name = proto_enum.cpp_namespace(root)
-    output.write_line(
-        f'constexpr ::pw::Result<{enum_name}> Get{enum_name}(uint32_t value) {{'
-    )
-    with output.indent():
-        output.write_line('switch (value) {')
-        with output.indent():
-            for name, number in proto_enum.values():
-                output.write_line(
-                    f'case {number}: return {enum_name}::{name};')
-            output.write_line('default: return ::pw::Status::DataLoss();')
-        output.write_line('}')
-    output.write_line('}')
-
-
 def forward_declare(node: ProtoMessage, root: ProtoNode,
                     output: OutputFile) -> None:
     """Generates code forward-declaring entities in a message's namespace."""
@@ -1415,43 +599,17 @@
             output.write_line(f'{field.enum_name()} = {field.number()},')
     output.write_line('};')
 
-    # Declare the message's encoder classes.
+    # Declare the message's encoder class and all of its enums.
     output.write_line()
-    output.write_line('class StreamEncoder;')
-    output.write_line('class MemoryEncoder;')
-
-    # Declare the message's decoder classes.
-    output.write_line()
-    output.write_line('class StreamDecoder;')
-
-    # Declare the message's enums.
+    output.write_line('class Encoder;')
     for child in node.children():
         if child.type() == ProtoNode.Type.ENUM:
             output.write_line()
             generate_code_for_enum(cast(ProtoEnum, child), node, output)
-            output.write_line()
-            generate_function_for_enum(cast(ProtoEnum, child), node, output)
 
     output.write_line(f'}}  // namespace {namespace}')
 
 
-def generate_class_wrappers(package: ProtoNode, class_type: ClassType,
-                            output: OutputFile):
-    # Run through all messages in the file, generating a class for each.
-    for node in package:
-        if node.type() == ProtoNode.Type.MESSAGE:
-            output.write_line()
-            generate_class_for_message(cast(ProtoMessage, node), package,
-                                       output, class_type)
-
-    # Run a second pass through the classes, this time defining all of the
-    # methods which were previously only declared.
-    for node in package:
-        if node.type() == ProtoNode.Type.MESSAGE:
-            define_not_in_class_methods(cast(ProtoMessage, node), package,
-                                        output, class_type)
-
-
 def _proto_filename_to_generated_header(proto_file: str) -> str:
     """Returns the generated C++ header name for a .proto file."""
     return os.path.splitext(proto_file)[0] + PROTO_H_EXTENSION
@@ -1469,15 +627,8 @@
     output.write_line('#pragma once\n')
     output.write_line('#include <cstddef>')
     output.write_line('#include <cstdint>')
-    output.write_line('#include <span>')
-    output.write_line('#include <string_view>\n')
-    output.write_line('#include "pw_assert/assert.h"')
-    output.write_line('#include "pw_containers/vector.h"')
-    output.write_line('#include "pw_protobuf/encoder.h"')
-    output.write_line('#include "pw_protobuf/stream_decoder.h"')
-    output.write_line('#include "pw_result/result.h"')
-    output.write_line('#include "pw_status/status.h"')
-    output.write_line('#include "pw_status/status_with_size.h"')
+    output.write_line('#include <span>\n')
+    output.write_line('#include "pw_protobuf/codegen.h"')
 
     for imported_file in file_descriptor_proto.dependency:
         generated_header = _proto_filename_to_generated_header(imported_file)
@@ -1499,13 +650,20 @@
         if node.type() == ProtoNode.Type.ENUM:
             output.write_line()
             generate_code_for_enum(cast(ProtoEnum, node), package, output)
+
+    # Run through all messages in the file, generating a class for each.
+    for node in package:
+        if node.type() == ProtoNode.Type.MESSAGE:
             output.write_line()
-            generate_function_for_enum(cast(ProtoEnum, node), package, output)
+            generate_code_for_message(cast(ProtoMessage, node), package,
+                                      output)
 
-    generate_class_wrappers(package, ClassType.STREAMING_ENCODER, output)
-    generate_class_wrappers(package, ClassType.MEMORY_ENCODER, output)
-
-    generate_class_wrappers(package, ClassType.STREAMING_DECODER, output)
+    # Run a second pass through the classes, this time defining all of the
+    # methods which were previously only declared.
+    for node in package:
+        if node.type() == ProtoNode.Type.MESSAGE:
+            define_not_in_class_methods(cast(ProtoMessage, node), package,
+                                        output)
 
     if package.cpp_namespace():
         output.write_line(f'\n}}  // namespace {package.cpp_namespace()}')
diff --git a/pw_protobuf/py/pw_protobuf/plugin.py b/pw_protobuf/py/pw_protobuf/plugin.py
index c584a9a..058a29d 100755
--- a/pw_protobuf/py/pw_protobuf/plugin.py
+++ b/pw_protobuf/py/pw_protobuf/plugin.py
@@ -20,9 +20,9 @@
 
 import sys
 
-from google.protobuf.compiler import plugin_pb2
+import google.protobuf.compiler.plugin_pb2 as plugin_pb2
 
-from pw_protobuf import codegen_pwpb
+import pw_protobuf.codegen_pwpb as codegen_pwpb
 
 
 def process_proto_request(req: plugin_pb2.CodeGeneratorRequest,
diff --git a/pw_protobuf/py/pw_protobuf/proto_tree.py b/pw_protobuf/py/pw_protobuf/proto_tree.py
index 9f88855..6dedf82 100644
--- a/pw_protobuf/py/pw_protobuf/proto_tree.py
+++ b/pw_protobuf/py/pw_protobuf/proto_tree.py
@@ -20,7 +20,7 @@
 from typing import Callable, Dict, Iterator, List, Optional, Tuple, TypeVar
 from typing import cast
 
-from google.protobuf import descriptor_pb2
+import google.protobuf.descriptor_pb2 as descriptor_pb2
 
 T = TypeVar('T')  # pylint: disable=invalid-name
 
@@ -75,15 +75,8 @@
         path = '.'.join(self._attr_hierarchy(lambda node: node.name(), None))
         return path.lstrip('.')
 
-    def nanopb_fields(self) -> str:
-        """Name of the Nanopb variable that represents the proto fields."""
-        return self._nanopb_name() + '_fields'
-
-    def nanopb_struct(self) -> str:
-        """Name of the Nanopb struct for this proto."""
-        return '::' + self._nanopb_name()
-
-    def _nanopb_name(self) -> str:
+    def nanopb_name(self) -> str:
+        """Full nanopb-style name of the node."""
         name = '_'.join(self._attr_hierarchy(lambda node: node.name(), None))
         return name.lstrip('_')
 
@@ -338,19 +331,15 @@
 
         def cc_enum(self) -> str:
             """Returns the pw_rpc MethodType C++ enum for this method type."""
-            return '::pw::rpc::MethodType::' + self.value
+            return '::pw::rpc::internal::MethodType::' + self.value
 
-    def __init__(self, service: ProtoService, name: str, method_type: Type,
-                 request_type: ProtoNode, response_type: ProtoNode):
-        self._service = service
+    def __init__(self, name: str, method_type: Type, request_type: ProtoNode,
+                 response_type: ProtoNode):
         self._name = name
         self._type = method_type
         self._request_type = request_type
         self._response_type = response_type
 
-    def service(self) -> ProtoService:
-        return self._service
-
     def name(self) -> str:
         return self._name
 
@@ -358,12 +347,12 @@
         return self._type
 
     def server_streaming(self) -> bool:
-        return self._type in (self.Type.SERVER_STREAMING,
-                              self.Type.BIDIRECTIONAL_STREAMING)
+        return (self._type is self.Type.SERVER_STREAMING
+                or self._type is self.Type.BIDIRECTIONAL_STREAMING)
 
     def client_streaming(self) -> bool:
-        return self._type in (self.Type.CLIENT_STREAMING,
-                              self.Type.BIDIRECTIONAL_STREAMING)
+        return (self._type is self.Type.CLIENT_STREAMING
+                or self._type is self.Type.BIDIRECTIONAL_STREAMING)
 
     def request_type(self) -> ProtoNode:
         return self._request_type
@@ -468,7 +457,7 @@
                                              method.output_type)
 
         service.add_method(
-            ProtoServiceMethod(service, method.name, method_type, request_node,
+            ProtoServiceMethod(method.name, method_type, request_node,
                                response_node))
 
 
diff --git a/pw_protobuf/py/pyproject.toml b/pw_protobuf/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/pw_protobuf/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/pw_protobuf/py/setup.cfg b/pw_protobuf/py/setup.cfg
deleted file mode 100644
index d4bc72f..0000000
--- a/pw_protobuf/py/setup.cfg
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pw_protobuf
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Lightweight streaming protobuf implementation
-
-[options]
-packages = find:
-zip_safe = False
-install_requires = protobuf; pw_cli
-
-[options.entry_points]
-console_scripts =
-    pw_protobuf_codegen = pw_protobuf.plugin:main
-
-[options.package_data]
-pw_protobuf = py.typed
diff --git a/pw_protobuf/py/setup.py b/pw_protobuf/py/setup.py
index 7d15878..d55b53b 100644
--- a/pw_protobuf/py/setup.py
+++ b/pw_protobuf/py/setup.py
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2019 The Pigweed Authors
 #
 # 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
@@ -15,4 +15,20 @@
 
 import setuptools  # type: ignore
 
-setuptools.setup()  # Package definition in setup.cfg
+setuptools.setup(
+    name='pw_protobuf',
+    version='0.0.1',
+    author='Pigweed Authors',
+    author_email='pigweed-developers@googlegroups.com',
+    description='Lightweight streaming protobuf implementation',
+    packages=setuptools.find_packages(),
+    package_data={'pw_protobuf': ['py.typed']},
+    zip_safe=False,
+    entry_points={
+        'console_scripts': ['pw_protobuf_codegen = pw_protobuf.plugin:main']
+    },
+    install_requires=[
+        'protobuf',
+        'pw_cli',
+    ],
+)
diff --git a/pw_protobuf/serialized_size_test.cc b/pw_protobuf/serialized_size_test.cc
deleted file mode 100644
index 6de9275..0000000
--- a/pw_protobuf/serialized_size_test.cc
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_protobuf/serialized_size.h"
-
-#include <cinttypes>
-
-#include "gtest/gtest.h"
-
-namespace pw::protobuf {
-namespace {
-
-#define TEST_VARINT(type)                                           \
-  TEST(SerializedSize, type) {                                      \
-    static_assert(SizeOfField##type(1, 0) == 1 + 1,                 \
-                  #type " minimum encoded size, key 1");            \
-    static_assert(SizeOfField##type(1) == 1 + kMaxSizeBytes##type,  \
-                  #type " maximum encoded size, key 1");            \
-    static_assert(SizeOfField##type(16, 0) == 2 + 1,                \
-                  #type " minimum encoded size, key 16");           \
-    static_assert(SizeOfField##type(16) == 2 + kMaxSizeBytes##type, \
-                  #type " maximum encoded size, key 16");           \
-  }                                                                 \
-  static_assert(true, "require semicolons")
-
-#define TEST_FIXED(type)                                            \
-  TEST(SerializedSize, SizeOf##type##Field) {                       \
-    static_assert(SizeOfField##type(1) == 1 + kMaxSizeBytes##type,  \
-                  #type " key 1");                                  \
-    static_assert(SizeOfField##type(15) == 1 + kMaxSizeBytes##type, \
-                  #type " key 15");                                 \
-    static_assert(SizeOfField##type(16) == 2 + kMaxSizeBytes##type, \
-                  #type " key 16");                                 \
-    static_assert(SizeOfField##type(17) == 2 + kMaxSizeBytes##type, \
-                  #type " key 17");                                 \
-  }                                                                 \
-  static_assert(true, "require semicolons")
-
-#define TEST_DELIMITED(function)                                            \
-  TEST(SerializedSize, function) {                                          \
-    static_assert(function(1, 0) == 1 + 1 + 0, #function " key 1");         \
-    static_assert(function(1, 1) == 1 + 1 + 1, #function " key 1");         \
-    static_assert(function(1, 128) == 1 + 2 + 128, #function " key 1");     \
-    static_assert(function(1, 1000) == 1 + 2 + 1000, #function " key 1");   \
-    static_assert(function(16, 0) == 2 + 1 + 0, #function " key 16");       \
-    static_assert(function(16, 1) == 2 + 1 + 1, #function " key 16");       \
-    static_assert(function(16, 128) == 2 + 2 + 128, #function " key 16");   \
-    static_assert(function(16, 1000) == 2 + 2 + 1000, #function " key 16"); \
-  }                                                                         \
-  static_assert(true, "require semicolons")
-
-TEST(SerializedSize, SizeOfVarintField) {
-  static_assert(SizeOfVarintField(1, 0) == 1 + 1);
-  static_assert(SizeOfVarintField(1, 127) == 1 + 1);
-
-  static_assert(SizeOfVarintField(1, INT32_C(-1)) == 1 + 10);
-  static_assert(SizeOfVarintField(1, INT64_C(-1)) == 1 + 10);
-
-  static_assert(SizeOfVarintField(16, 0) == 2 + 1);
-  static_assert(SizeOfVarintField(16, 127) == 2 + 1);
-
-  static_assert(SizeOfVarintField(16, INT32_C(-1)) == 2 + 10);
-  static_assert(SizeOfVarintField(16, INT64_C(-1)) == 2 + 10);
-}
-
-TEST(SerializedSize, SizeOfDelimitedFieldWithoutValue) {
-  static_assert(SizeOfDelimitedFieldWithoutValue(1, 0) == 1 + 1);
-  static_assert(SizeOfDelimitedFieldWithoutValue(1, 1) == 1 + 1);
-  static_assert(SizeOfDelimitedFieldWithoutValue(1, 128) == 1 + 2);
-  static_assert(SizeOfDelimitedFieldWithoutValue(1, 1000) == 1 + 2);
-  static_assert(SizeOfDelimitedFieldWithoutValue(1) == 1 + 5);
-  static_assert(SizeOfDelimitedFieldWithoutValue(16, 0) == 2 + 1);
-  static_assert(SizeOfDelimitedFieldWithoutValue(16, 1) == 2 + 1);
-  static_assert(SizeOfDelimitedFieldWithoutValue(16, 128) == 2 + 2);
-  static_assert(SizeOfDelimitedFieldWithoutValue(16) == 2 + 5);
-}
-
-TEST_DELIMITED(SizeOfDelimitedField);
-
-TEST_FIXED(Float);
-TEST_FIXED(Double);
-
-TEST_VARINT(Int32);
-TEST_VARINT(Int64);
-TEST_VARINT(Sint32);
-TEST_VARINT(Sint64);
-TEST_VARINT(Uint32);
-TEST_VARINT(Uint64);
-
-TEST_FIXED(Fixed32);
-TEST_FIXED(Fixed64);
-TEST_FIXED(Sfixed32);
-TEST_FIXED(Sfixed64);
-TEST_FIXED(Bool);
-
-TEST_DELIMITED(SizeOfFieldString);
-TEST_DELIMITED(SizeOfFieldBytes);
-
-TEST_VARINT(Enum);
-
-}  // namespace
-}  // namespace pw::protobuf
diff --git a/pw_protobuf/stream_decoder.cc b/pw_protobuf/stream_decoder.cc
deleted file mode 100644
index e6a88ae..0000000
--- a/pw_protobuf/stream_decoder.cc
+++ /dev/null
@@ -1,486 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_protobuf/stream_decoder.h"
-
-#include <algorithm>
-#include <bit>
-#include <cstdint>
-#include <cstring>
-#include <limits>
-
-#include "pw_assert/check.h"
-#include "pw_status/status.h"
-#include "pw_status/status_with_size.h"
-#include "pw_status/try.h"
-#include "pw_varint/stream.h"
-#include "pw_varint/varint.h"
-
-namespace pw::protobuf {
-
-Status StreamDecoder::BytesReader::DoSeek(ptrdiff_t offset, Whence origin) {
-  PW_TRY(status_);
-  if (!decoder_.reader_.seekable()) {
-    return Status::Unimplemented();
-  }
-
-  ptrdiff_t absolute_position = std::numeric_limits<ptrdiff_t>::min();
-
-  // Convert from the position within the bytes field to the position within the
-  // proto stream.
-  switch (origin) {
-    case Whence::kBeginning:
-      absolute_position = start_offset_ + offset;
-      break;
-
-    case Whence::kCurrent:
-      absolute_position = decoder_.position_ + offset;
-      break;
-
-    case Whence::kEnd:
-      absolute_position = end_offset_ + offset;
-      break;
-  }
-
-  if (absolute_position < 0) {
-    return Status::InvalidArgument();
-  }
-
-  if (static_cast<size_t>(absolute_position) < start_offset_ ||
-      static_cast<size_t>(absolute_position) >= end_offset_) {
-    return Status::OutOfRange();
-  }
-
-  PW_TRY(decoder_.reader_.Seek(absolute_position, Whence::kBeginning));
-  decoder_.position_ = absolute_position;
-  return OkStatus();
-}
-
-StatusWithSize StreamDecoder::BytesReader::DoRead(ByteSpan destination) {
-  if (!status_.ok()) {
-    return StatusWithSize(status_, 0);
-  }
-
-  // Bound the read buffer to the size of the bytes field.
-  size_t max_length = end_offset_ - decoder_.position_;
-  if (destination.size() > max_length) {
-    destination = destination.first(max_length);
-  }
-
-  Result<ByteSpan> result = decoder_.reader_.Read(destination);
-  if (!result.ok()) {
-    return StatusWithSize(result.status(), 0);
-  }
-
-  decoder_.position_ += result.value().size();
-  return StatusWithSize(result.value().size());
-}
-
-StreamDecoder::~StreamDecoder() {
-  if (parent_ != nullptr) {
-    parent_->CloseNestedDecoder(*this);
-  } else if (stream_bounds_.high < std::numeric_limits<size_t>::max()) {
-    if (status_.ok()) {
-      // Advance the stream to the end of the bounds.
-      PW_CHECK(Advance(stream_bounds_.high).ok());
-    }
-  }
-}
-
-Status StreamDecoder::Next() {
-  PW_CHECK(!nested_reader_open_,
-           "Cannot use parent decoder while a nested one is open");
-
-  PW_TRY(status_);
-
-  if (!field_consumed_) {
-    PW_TRY(SkipField());
-  }
-
-  if (position_ >= stream_bounds_.high) {
-    return Status::OutOfRange();
-  }
-
-  status_ = ReadFieldKey();
-  return status_;
-}
-
-StreamDecoder::BytesReader StreamDecoder::GetBytesReader() {
-  Status status = CheckOkToRead(WireType::kDelimited);
-
-  if (reader_.ConservativeReadLimit() < delimited_field_size_) {
-    status.Update(Status::DataLoss());
-  }
-
-  nested_reader_open_ = true;
-
-  if (!status.ok()) {
-    return BytesReader(*this, status);
-  }
-
-  size_t low = position_;
-  size_t high = low + delimited_field_size_;
-
-  return BytesReader(*this, low, high);
-}
-
-StreamDecoder StreamDecoder::GetNestedDecoder() {
-  Status status = CheckOkToRead(WireType::kDelimited);
-
-  if (reader_.ConservativeReadLimit() < delimited_field_size_) {
-    status.Update(Status::DataLoss());
-  }
-
-  nested_reader_open_ = true;
-
-  if (!status.ok()) {
-    return StreamDecoder(reader_, this, status);
-  }
-
-  size_t low = position_;
-  size_t high = low + delimited_field_size_;
-
-  return StreamDecoder(reader_, this, low, high);
-}
-
-Status StreamDecoder::Advance(size_t end_position) {
-  if (reader_.seekable()) {
-    PW_TRY(reader_.Seek(end_position - position_, stream::Stream::kCurrent));
-    position_ = end_position;
-    return OkStatus();
-  }
-
-  while (position_ < end_position) {
-    std::byte b;
-    PW_TRY(reader_.Read(std::span(&b, 1)));
-    position_++;
-  }
-  return OkStatus();
-}
-
-void StreamDecoder::CloseBytesReader(BytesReader& reader) {
-  status_ = reader.status_;
-  if (status_.ok()) {
-    // Advance the stream to the end of the bytes field.
-    // The BytesReader already updated our position_ field as bytes were read.
-    PW_CHECK(Advance(reader.end_offset_).ok());
-  }
-
-  field_consumed_ = true;
-  nested_reader_open_ = false;
-}
-
-void StreamDecoder::CloseNestedDecoder(StreamDecoder& nested) {
-  PW_CHECK_PTR_EQ(nested.parent_, this);
-
-  nested.nested_reader_open_ = true;
-  nested.parent_ = nullptr;
-
-  status_ = nested.status_;
-  position_ = nested.position_;
-  if (status_.ok()) {
-    // Advance the stream to the end of the nested message field.
-    PW_CHECK(Advance(nested.stream_bounds_.high).ok());
-  }
-
-  field_consumed_ = true;
-  nested_reader_open_ = false;
-}
-
-Status StreamDecoder::ReadFieldKey() {
-  PW_DCHECK(field_consumed_);
-
-  uint64_t varint = 0;
-  PW_TRY_ASSIGN(size_t bytes_read, varint::Read(reader_, &varint));
-  position_ += bytes_read;
-
-  if (!FieldKey::IsValidKey(varint)) {
-    return Status::DataLoss();
-  }
-
-  current_field_ = FieldKey(varint);
-
-  if (current_field_.wire_type() == WireType::kDelimited) {
-    // Read the length varint of length-delimited fields immediately to simplify
-    // later processing of the field.
-    PW_TRY_ASSIGN(bytes_read, varint::Read(reader_, &varint));
-    position_ += bytes_read;
-
-    if (varint > std::numeric_limits<uint32_t>::max()) {
-      return Status::DataLoss();
-    }
-
-    delimited_field_size_ = varint;
-    delimited_field_offset_ = position_;
-  }
-
-  field_consumed_ = false;
-  return OkStatus();
-}
-
-Result<StreamDecoder::Bounds> StreamDecoder::GetLengthDelimitedPayloadBounds() {
-  PW_TRY(CheckOkToRead(WireType::kDelimited));
-  return StreamDecoder::Bounds{delimited_field_offset_,
-                               delimited_field_size_ + delimited_field_offset_};
-}
-
-// Consumes the current protobuf field, advancing the stream to the key of the
-// next field (if one exists).
-Status StreamDecoder::SkipField() {
-  PW_DCHECK(!field_consumed_);
-
-  size_t bytes_to_skip = 0;
-  uint64_t value = 0;
-
-  switch (current_field_.wire_type()) {
-    case WireType::kVarint: {
-      // Consume the varint field; nothing more to skip afterward.
-      PW_TRY_ASSIGN(size_t bytes_read, varint::Read(reader_, &value));
-      position_ += bytes_read;
-      break;
-    }
-    case WireType::kDelimited:
-      bytes_to_skip = delimited_field_size_;
-      break;
-
-    case WireType::kFixed32:
-      bytes_to_skip = sizeof(uint32_t);
-      break;
-
-    case WireType::kFixed64:
-      bytes_to_skip = sizeof(uint64_t);
-      break;
-  }
-
-  if (bytes_to_skip > 0) {
-    // Check if the stream has the field available. If not, report it as a
-    // DATA_LOSS since the proto is invalid (as opposed to OUT_OF_BOUNDS if we
-    // just tried to seek beyond the end).
-    if (reader_.ConservativeReadLimit() < bytes_to_skip) {
-      status_ = Status::DataLoss();
-      return status_;
-    }
-
-    PW_TRY(Advance(position_ + bytes_to_skip));
-  }
-
-  field_consumed_ = true;
-  return OkStatus();
-}
-
-Status StreamDecoder::ReadVarintField(std::span<std::byte> out,
-                                      VarintDecodeType decode_type) {
-  PW_CHECK(out.size() == sizeof(bool) || out.size() == sizeof(uint32_t) ||
-               out.size() == sizeof(uint64_t),
-           "Protobuf varints must only be used with bool, int32_t, uint32_t, "
-           "int64_t, or uint64_t");
-  PW_TRY(CheckOkToRead(WireType::kVarint));
-
-  const StatusWithSize sws = ReadOneVarint(out, decode_type);
-  if (sws.status() != Status::DataLoss())
-    field_consumed_ = true;
-  return sws.status();
-}
-
-StatusWithSize StreamDecoder::ReadOneVarint(std::span<std::byte> out,
-                                            VarintDecodeType decode_type) {
-  uint64_t value;
-  StatusWithSize sws = varint::Read(reader_, &value);
-  if (sws.IsOutOfRange()) {
-    // Out of range indicates the end of the stream. As a value is expected
-    // here, report it as a data loss and terminate the decode operation.
-    status_ = Status::DataLoss();
-    return StatusWithSize(status_, sws.size());
-  }
-  if (!sws.ok()) {
-    return sws;
-  }
-
-  position_ += sws.size();
-
-  if (out.size() == sizeof(uint64_t)) {
-    if (decode_type == VarintDecodeType::kUnsigned) {
-      std::memcpy(out.data(), &value, out.size());
-    } else {
-      const int64_t signed_value = decode_type == VarintDecodeType::kZigZag
-                                       ? varint::ZigZagDecode(value)
-                                       : static_cast<int64_t>(value);
-      std::memcpy(out.data(), &signed_value, out.size());
-    }
-  } else if (out.size() == sizeof(uint32_t)) {
-    if (decode_type == VarintDecodeType::kUnsigned) {
-      if (value > std::numeric_limits<uint32_t>::max()) {
-        return StatusWithSize(Status::OutOfRange(), sws.size());
-      }
-      std::memcpy(out.data(), &value, out.size());
-    } else {
-      const int64_t signed_value = decode_type == VarintDecodeType::kZigZag
-                                       ? varint::ZigZagDecode(value)
-                                       : static_cast<int64_t>(value);
-      if (signed_value > std::numeric_limits<int32_t>::max() ||
-          signed_value < std::numeric_limits<int32_t>::min()) {
-        return StatusWithSize(Status::OutOfRange(), sws.size());
-      }
-      std::memcpy(out.data(), &signed_value, out.size());
-    }
-  } else if (out.size() == sizeof(bool)) {
-    PW_CHECK(decode_type == VarintDecodeType::kUnsigned,
-             "Protobuf bool can never be signed");
-    std::memcpy(out.data(), &value, out.size());
-  }
-
-  return sws;
-}
-
-Status StreamDecoder::ReadFixedField(std::span<std::byte> out) {
-  WireType expected_wire_type =
-      out.size() == sizeof(uint32_t) ? WireType::kFixed32 : WireType::kFixed64;
-  PW_TRY(CheckOkToRead(expected_wire_type));
-
-  if (reader_.ConservativeReadLimit() < out.size()) {
-    status_ = Status::DataLoss();
-    return status_;
-  }
-
-  PW_TRY(reader_.Read(out));
-  position_ += out.size();
-  field_consumed_ = true;
-
-  if (std::endian::native != std::endian::little) {
-    std::reverse(out.begin(), out.end());
-  }
-
-  return OkStatus();
-}
-
-StatusWithSize StreamDecoder::ReadDelimitedField(std::span<std::byte> out) {
-  if (Status status = CheckOkToRead(WireType::kDelimited); !status.ok()) {
-    return StatusWithSize(status, 0);
-  }
-
-  if (reader_.ConservativeReadLimit() < delimited_field_size_) {
-    status_ = Status::DataLoss();
-    return StatusWithSize(status_, 0);
-  }
-
-  if (out.size() < delimited_field_size_) {
-    // Value can't fit into the provided buffer. Don't advance the cursor so
-    // that the field can be re-read with a larger buffer or through the stream
-    // API.
-    return StatusWithSize::ResourceExhausted();
-  }
-
-  Result<ByteSpan> result = reader_.Read(out.first(delimited_field_size_));
-  if (!result.ok()) {
-    return StatusWithSize(result.status(), 0);
-  }
-
-  position_ += result.value().size();
-  field_consumed_ = true;
-  return StatusWithSize(result.value().size());
-}
-
-StatusWithSize StreamDecoder::ReadPackedFixedField(std::span<std::byte> out,
-                                                   size_t elem_size) {
-  if (Status status = CheckOkToRead(WireType::kDelimited); !status.ok()) {
-    return StatusWithSize(status, 0);
-  }
-
-  if (reader_.ConservativeReadLimit() < delimited_field_size_) {
-    status_ = Status::DataLoss();
-    return StatusWithSize(status_, 0);
-  }
-
-  if (out.size() < delimited_field_size_) {
-    // Value can't fit into the provided buffer. Don't advance the cursor so
-    // that the field can be re-read with a larger buffer or through the stream
-    // API.
-    return StatusWithSize::ResourceExhausted();
-  }
-
-  Result<ByteSpan> result = reader_.Read(out.first(delimited_field_size_));
-  if (!result.ok()) {
-    return StatusWithSize(result.status(), 0);
-  }
-
-  position_ += result.value().size();
-  field_consumed_ = true;
-
-  // Decode little-endian serialized packed fields.
-  if (std::endian::native != std::endian::little) {
-    for (auto out_start = out.begin(); out_start != out.end();
-         out_start += elem_size) {
-      std::reverse(out_start, out_start + elem_size);
-    }
-  }
-
-  return StatusWithSize(result.value().size() / elem_size);
-}
-
-StatusWithSize StreamDecoder::ReadPackedVarintField(
-    std::span<std::byte> out, size_t elem_size, VarintDecodeType decode_type) {
-  PW_CHECK(elem_size == sizeof(bool) || elem_size == sizeof(uint32_t) ||
-               elem_size == sizeof(uint64_t),
-           "Protobuf varints must only be used with bool, int32_t, uint32_t, "
-           "int64_t, or uint64_t");
-
-  if (Status status = CheckOkToRead(WireType::kDelimited); !status.ok()) {
-    return StatusWithSize(status, 0);
-  }
-
-  if (reader_.ConservativeReadLimit() < delimited_field_size_) {
-    status_ = Status::DataLoss();
-    return StatusWithSize(status_, 0);
-  }
-
-  size_t bytes_read = 0;
-  size_t number_out = 0;
-  while (bytes_read < delimited_field_size_ && !out.empty()) {
-    const StatusWithSize sws = ReadOneVarint(out.first(elem_size), decode_type);
-    if (!sws.ok()) {
-      return StatusWithSize(sws.status(), number_out);
-    }
-
-    bytes_read += sws.size();
-    out = out.subspan(elem_size);
-    ++number_out;
-  }
-
-  if (bytes_read < delimited_field_size_) {
-    return StatusWithSize(Status::ResourceExhausted(), number_out);
-  }
-
-  field_consumed_ = true;
-  return StatusWithSize(OkStatus(), number_out);
-}
-
-Status StreamDecoder::CheckOkToRead(WireType type) {
-  PW_CHECK(!nested_reader_open_,
-           "Cannot read from a decoder while a nested decoder is open");
-  PW_CHECK(!field_consumed_,
-           "Attempting to read from protobuf decoder without first calling "
-           "Next()");
-
-  // Attempting to read the wrong type is typically a programmer error;
-  // however, it could also occur due to data corruption. As we don't want to
-  // crash on bad data, return NOT_FOUND here to distinguish it from other
-  // corruption cases.
-  if (current_field_.wire_type() != type) {
-    status_ = Status::NotFound();
-  }
-
-  return status_;
-}
-
-}  // namespace pw::protobuf
diff --git a/pw_protobuf/stream_decoder_test.cc b/pw_protobuf/stream_decoder_test.cc
deleted file mode 100644
index 0cff426..0000000
--- a/pw_protobuf/stream_decoder_test.cc
+++ /dev/null
@@ -1,1307 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_protobuf/stream_decoder.h"
-
-#include <array>
-
-#include "gtest/gtest.h"
-#include "pw_status/status.h"
-#include "pw_status/status_with_size.h"
-#include "pw_stream/memory_stream.h"
-#include "pw_stream/stream.h"
-
-namespace pw::protobuf {
-namespace {
-// Non-seekable wrapper for MemoryReader for testing behavior when seeking is
-// not available.
-class NonSeekableMemoryReader : public stream::NonSeekableReader {
- public:
-  explicit NonSeekableMemoryReader(stream::MemoryReader& reader)
-      : reader_(reader) {}
-
-  size_t bytes_read() const { return reader_.bytes_read(); }
-  const std::byte* data() const { return reader_.data(); }
-
- private:
-  virtual StatusWithSize DoRead(ByteSpan destination) override {
-    const pw::Result<pw::ByteSpan> result = reader_.Read(destination);
-    if (!result.ok()) {
-      return StatusWithSize(result.status(), 0);
-    }
-    return StatusWithSize(result.value().size_bytes());
-  }
-
-  stream::MemoryReader& reader_;
-};
-
-TEST(StreamDecoder, Decode) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // type=int32, k=1, v=42
-    0x08, 0x2a,
-    // type=sint32, k=2, v=-13
-    0x10, 0x19,
-    // type=bool, k=3, v=false
-    0x18, 0x00,
-    // type=double, k=4, v=3.14159
-    0x21, 0x6e, 0x86, 0x1b, 0xf0, 0xf9, 0x21, 0x09, 0x40,
-    // type=fixed32, k=5, v=0xdeadbeef
-    0x2d, 0xef, 0xbe, 0xad, 0xde,
-    // type=string, k=6, v="Hello world"
-    0x32, 0x0b, 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd',
-    // type=sfixed32, k=7, v=-50
-    0x3d, 0xce, 0xff, 0xff, 0xff,
-    // type=sfixed64, k=8, v=-1647993274
-    0x41, 0x46, 0x9e, 0xc5, 0x9d, 0xff, 0xff, 0xff, 0xff,
-    // type=float, k=9, v=2.718
-    0x4d, 0xb6, 0xf3, 0x2d, 0x40,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 1u);
-  Result<int32_t> int32 = decoder.ReadInt32();
-  ASSERT_EQ(int32.status(), OkStatus());
-  EXPECT_EQ(int32.value(), 42);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 2u);
-  Result<int32_t> sint32 = decoder.ReadSint32();
-  ASSERT_EQ(sint32.status(), OkStatus());
-  EXPECT_EQ(sint32.value(), -13);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 3u);
-  Result<bool> boolean = decoder.ReadBool();
-  ASSERT_EQ(boolean.status(), OkStatus());
-  EXPECT_FALSE(boolean.value());
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 4u);
-  Result<double> dbl = decoder.ReadDouble();
-  ASSERT_EQ(dbl.status(), OkStatus());
-  EXPECT_EQ(dbl.value(), 3.14159);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 5u);
-  Result<uint32_t> fixed32 = decoder.ReadFixed32();
-  ASSERT_EQ(fixed32.status(), OkStatus());
-  EXPECT_EQ(fixed32.value(), 0xdeadbeef);
-
-  char buffer[16];
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 6u);
-  StatusWithSize sws = decoder.ReadString(buffer);
-  ASSERT_EQ(sws.status(), OkStatus());
-  buffer[sws.size()] = '\0';
-  EXPECT_STREQ(buffer, "Hello world");
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 7u);
-  Result<int32_t> sfixed32 = decoder.ReadSfixed32();
-  ASSERT_EQ(sfixed32.status(), OkStatus());
-  EXPECT_EQ(sfixed32.value(), -50);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 8u);
-  Result<int64_t> sfixed64 = decoder.ReadSfixed64();
-  ASSERT_EQ(sfixed64.status(), OkStatus());
-  EXPECT_EQ(sfixed64.value(), -1647993274);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 9u);
-  Result<float> flt = decoder.ReadFloat();
-  ASSERT_EQ(flt.status(), OkStatus());
-  EXPECT_EQ(flt.value(), 2.718f);
-
-  EXPECT_EQ(decoder.Next(), Status::OutOfRange());
-}
-
-TEST(StreamDecoder, Decode_SkipsUnusedFields) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // type=int32, k=1, v=42
-    0x08, 0x2a,
-    // type=sint32, k=2, v=-13
-    0x10, 0x19,
-    // type=bool, k=3, v=false
-    0x18, 0x00,
-    // type=double, k=4, v=3.14159
-    0x21, 0x6e, 0x86, 0x1b, 0xf0, 0xf9, 0x21, 0x09, 0x40,
-    // type=fixed32, k=5, v=0xdeadbeef
-    0x2d, 0xef, 0xbe, 0xad, 0xde,
-    // type=string, k=6, v="Hello world"
-    0x32, 0x0b, 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd',
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader);
-
-  // Don't process any fields except for the fourth. Next should still iterate
-  // correctly despite field values not being consumed.
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(*decoder.FieldNumber(), 4u);
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  EXPECT_EQ(decoder.Next(), Status::OutOfRange());
-}
-
-TEST(StreamDecoder, Decode_NonSeekable_SkipsUnusedFields) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // type=int32, k=1, v=42
-    0x08, 0x2a,
-    // type=sint32, k=2, v=-13
-    0x10, 0x19,
-    // type=bool, k=3, v=false
-    0x18, 0x00,
-    // type=double, k=4, v=3.14159
-    0x21, 0x6e, 0x86, 0x1b, 0xf0, 0xf9, 0x21, 0x09, 0x40,
-    // type=fixed32, k=5, v=0xdeadbeef
-    0x2d, 0xef, 0xbe, 0xad, 0xde,
-    // type=string, k=6, v="Hello world"
-    0x32, 0x0b, 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd',
-  };
-  // clang-format on
-
-  // Test with a non-seekable memory reader
-  stream::MemoryReader wrapped_reader(std::as_bytes(std::span(encoded_proto)));
-  NonSeekableMemoryReader reader(wrapped_reader);
-  StreamDecoder decoder(reader);
-
-  // Don't process any fields except for the fourth. Next should still iterate
-  // correctly despite field values not being consumed.
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(*decoder.FieldNumber(), 4u);
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  EXPECT_EQ(decoder.Next(), Status::OutOfRange());
-}
-
-TEST(StreamDecoder, Decode_BadData) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // type=int32, k=1, v=42
-    0x08, 0x2a,
-    // type=sint32, k=2, value... missing
-    0x10,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(*decoder.FieldNumber(), 1u);
-  Result<int32_t> int32 = decoder.ReadInt32();
-  ASSERT_EQ(int32.status(), OkStatus());
-  EXPECT_EQ(int32.value(), 42);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(*decoder.FieldNumber(), 2u);
-  EXPECT_EQ(decoder.ReadSint32().status(), Status::DataLoss());
-
-  EXPECT_EQ(decoder.Next(), Status::DataLoss());
-}
-
-TEST(Decoder, Decode_SkipsBadFieldNumbers) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // type=int32, k=1, v=42
-    0x08, 0x2a,
-    // type=int32, k=19001, v=42 (invalid field number)
-    0xc8, 0xa3, 0x09, 0x2a,
-    // type=bool, k=3, v=false
-    0x18, 0x00,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  EXPECT_EQ(*decoder.FieldNumber(), 1u);
-  Result<int32_t> int32 = decoder.ReadInt32();
-  ASSERT_EQ(int32.status(), OkStatus());
-  EXPECT_EQ(int32.value(), 42);
-
-  // Bad field.
-  EXPECT_EQ(decoder.Next(), Status::DataLoss());
-  EXPECT_EQ(decoder.FieldNumber().status(), Status::FailedPrecondition());
-
-  EXPECT_EQ(decoder.Next(), Status::DataLoss());
-}
-
-TEST(StreamDecoder, Decode_Nested) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // type=int32, k=1, v=42
-    0x08, 0x2a,
-
-    // Submessage (bytes) key=8, length=4
-    0x32, 0x04,
-    // type=uint32, k=1, v=2
-    0x08, 0x02,
-    // type=uint32, k=2, v=7
-    0x10, 0x07,
-    // End submessage
-
-    // type=sint32, k=2, v=-13
-    0x10, 0x19,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(*decoder.FieldNumber(), 1u);
-  Result<int32_t> int32 = decoder.ReadInt32();
-  ASSERT_EQ(int32.status(), OkStatus());
-  EXPECT_EQ(int32.value(), 42);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(*decoder.FieldNumber(), 6u);
-  {
-    StreamDecoder nested = decoder.GetNestedDecoder();
-
-    EXPECT_EQ(nested.Next(), OkStatus());
-    ASSERT_EQ(*nested.FieldNumber(), 1u);
-    Result<uint32_t> uint32 = nested.ReadUint32();
-    ASSERT_EQ(uint32.status(), OkStatus());
-    EXPECT_EQ(uint32.value(), 2u);
-
-    EXPECT_EQ(nested.Next(), OkStatus());
-    ASSERT_EQ(*nested.FieldNumber(), 2u);
-    uint32 = nested.ReadUint32();
-    ASSERT_EQ(uint32.status(), OkStatus());
-    EXPECT_EQ(uint32.value(), 7u);
-
-    ASSERT_EQ(nested.Next(), Status::OutOfRange());
-  }
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(*decoder.FieldNumber(), 2u);
-  Result<int32_t> sint32 = decoder.ReadSint32();
-  ASSERT_EQ(sint32.status(), OkStatus());
-  EXPECT_EQ(sint32.value(), -13);
-
-  EXPECT_EQ(decoder.Next(), Status::OutOfRange());
-}
-
-TEST(StreamDecoder, Decode_Nested_SeeksToNextFieldOnDestruction) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // type=int32, k=1, v=42
-    0x08, 0x2a,
-
-    // Submessage (bytes) key=8, length=4
-    0x32, 0x04,
-    // type=uint32, k=1, v=2
-    0x08, 0x02,
-    // type=uint32, k=2, v=7
-    0x10, 0x07,
-    // End submessage
-
-    // type=sint32, k=2, v=-13
-    0x10, 0x19,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(*decoder.FieldNumber(), 1u);
-
-  // Create a nested encoder for the nested field, but don't use it.
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(*decoder.FieldNumber(), 6u);
-  { StreamDecoder nested = decoder.GetNestedDecoder(); }
-
-  // The root decoder should still advance to the next field after the nested
-  // decoder is closed.
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(*decoder.FieldNumber(), 2u);
-
-  EXPECT_EQ(decoder.Next(), Status::OutOfRange());
-}
-
-TEST(StreamDecoder,
-     Decode_Nested_NonSeekable_AdvancesToNextFieldOnDestruction) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // type=int32, k=1, v=42
-    0x08, 0x2a,
-
-    // Submessage (bytes) key=8, length=4
-    0x32, 0x04,
-    // type=uint32, k=1, v=2
-    0x08, 0x02,
-    // type=uint32, k=2, v=7
-    0x10, 0x07,
-    // End submessage
-
-    // type=sint32, k=2, v=-13
-    0x10, 0x19,
-  };
-  // clang-format on
-
-  // Test with a non-seekable memory reader
-  stream::MemoryReader wrapped_reader(std::as_bytes(std::span(encoded_proto)));
-  NonSeekableMemoryReader reader(wrapped_reader);
-  StreamDecoder decoder(reader);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(*decoder.FieldNumber(), 1u);
-
-  // Create a nested encoder for the nested field, but don't use it.
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(*decoder.FieldNumber(), 6u);
-  { StreamDecoder nested = decoder.GetNestedDecoder(); }
-
-  // The root decoder should still advance to the next field after the nested
-  // decoder is closed.
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(*decoder.FieldNumber(), 2u);
-
-  EXPECT_EQ(decoder.Next(), Status::OutOfRange());
-}
-
-TEST(StreamDecoder, Decode_Nested_LastField) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // type=int32, k=1, v=42
-    0x08, 0x2a,
-
-    // Submessage (bytes) key=8, length=4
-    0x32, 0x04,
-    // type=uint32, k=1, v=2
-    0x08, 0x02,
-    // type=uint32, k=2, v=7
-    0x10, 0x07,
-    // End submessage and proto
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(*decoder.FieldNumber(), 1u);
-
-  // Create a nested encoder for the nested field, which is the last field in
-  // the root proto.
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(*decoder.FieldNumber(), 6u);
-  { StreamDecoder nested = decoder.GetNestedDecoder(); }
-
-  // Root decoder should correctly terminate after the nested decoder is closed.
-  EXPECT_EQ(decoder.Next(), Status::OutOfRange());
-}
-
-TEST(StreamDecoder, Decode_Nested_MultiLevel) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // Submessage key=1, length=4
-    0x0a, 0x04,
-
-    // Sub-submessage key=1, length=2
-    0x0a, 0x02,
-    // type=uint32, k=2, v=7
-    0x10, 0x07,
-    // End sub-submessage
-
-    // End submessage
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(*decoder.FieldNumber(), 1u);
-  {
-    StreamDecoder nested = decoder.GetNestedDecoder();
-
-    EXPECT_EQ(nested.Next(), OkStatus());
-    ASSERT_EQ(*nested.FieldNumber(), 1u);
-
-    {
-      StreamDecoder double_nested = nested.GetNestedDecoder();
-
-      EXPECT_EQ(double_nested.Next(), OkStatus());
-      ASSERT_EQ(*double_nested.FieldNumber(), 2u);
-      Result<uint32_t> result = double_nested.ReadUint32();
-      ASSERT_EQ(result.status(), OkStatus());
-      EXPECT_EQ(result.value(), 7u);
-
-      EXPECT_EQ(double_nested.Next(), Status::OutOfRange());
-    }
-
-    EXPECT_EQ(nested.Next(), Status::OutOfRange());
-  }
-
-  EXPECT_EQ(decoder.Next(), Status::OutOfRange());
-}
-
-TEST(StreamDecoder, Decode_Nested_InvalidField) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // Submessage key=1, length=4
-    0x0a, 0x04,
-
-    // Oops. No data!
-  };
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(*decoder.FieldNumber(), 1u);
-  {
-    StreamDecoder nested = decoder.GetNestedDecoder();
-    EXPECT_EQ(nested.Next(), Status::DataLoss());
-  }
-
-  EXPECT_EQ(decoder.Next(), Status::DataLoss());
-}
-
-TEST(StreamDecoder, Decode_BytesReader) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // bytes key=1, length=14
-    0x0a, 0x0e,
-
-    0x00, 0x01, 0x02, 0x03,
-    0x04, 0x05, 0x06, 0x07,
-    0x08, 0x09, 0x0a, 0x0b,
-    0x0c, 0x0d,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  EXPECT_EQ(*decoder.FieldNumber(), 1u);
-  {
-    StreamDecoder::BytesReader bytes = decoder.GetBytesReader();
-    EXPECT_EQ(bytes.field_size(), 14u);
-
-    std::byte buffer[7];
-    EXPECT_EQ(bytes.Read(buffer).status(), OkStatus());
-    EXPECT_EQ(std::memcmp(buffer, encoded_proto + 2, sizeof(buffer)), 0);
-
-    EXPECT_EQ(bytes.Read(buffer).status(), OkStatus());
-    EXPECT_EQ(std::memcmp(buffer, encoded_proto + 9, sizeof(buffer)), 0);
-
-    EXPECT_EQ(bytes.Read(buffer).status(), Status::OutOfRange());
-  }
-
-  EXPECT_EQ(decoder.Next(), Status::OutOfRange());
-}
-
-TEST(StreamDecoder, Decode_BytesReader_Seek) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // bytes key=1, length=14
-    0x0a, 0x0e,
-
-    0x00, 0x01, 0x02, 0x03,
-    0x04, 0x05, 0x06, 0x07,
-    0x08, 0x09, 0x0a, 0x0b,
-    0x0c, 0x0d,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  EXPECT_EQ(*decoder.FieldNumber(), 1u);
-  {
-    StreamDecoder::BytesReader bytes = decoder.GetBytesReader();
-
-    std::byte buffer[2];
-
-    ASSERT_EQ(bytes.Seek(3), OkStatus());
-
-    EXPECT_EQ(bytes.Read(buffer).status(), OkStatus());
-    EXPECT_EQ(std::memcmp(buffer, encoded_proto + 5, sizeof(buffer)), 0);
-
-    // Bad seek offset (absolute).
-    ASSERT_EQ(bytes.Seek(15), Status::OutOfRange());
-
-    // Seek back from current position.
-    ASSERT_EQ(bytes.Seek(-4, stream::Stream::kCurrent), OkStatus());
-
-    EXPECT_EQ(bytes.Read(buffer).status(), OkStatus());
-    EXPECT_EQ(std::memcmp(buffer, encoded_proto + 3, sizeof(buffer)), 0);
-
-    // Bad seek offset (relative).
-    ASSERT_EQ(bytes.Seek(-4, stream::Stream::kCurrent), Status::OutOfRange());
-
-    // Seek from the end of the bytes field.
-    ASSERT_EQ(bytes.Seek(-2, stream::Stream::kEnd), OkStatus());
-
-    EXPECT_EQ(bytes.Read(buffer).status(), OkStatus());
-    EXPECT_EQ(std::memcmp(buffer, encoded_proto + 14, sizeof(buffer)), 0);
-
-    // Bad seek offset (end).
-    ASSERT_EQ(bytes.Seek(-15, stream::Stream::kEnd), Status::OutOfRange());
-  }
-
-  EXPECT_EQ(decoder.Next(), Status::OutOfRange());
-}
-
-TEST(StreamDecoder, Decode_BytesReader_Close) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // bytes key=1, length=14
-    0x0a, 0x0e,
-
-    0x00, 0x01, 0x02, 0x03,
-    0x04, 0x05, 0x06, 0x07,
-    0x08, 0x09, 0x0a, 0x0b,
-    0x0c, 0x0d,
-    // End bytes
-
-    // type=sint32, k=2, v=-13
-    0x10, 0x19,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  EXPECT_EQ(*decoder.FieldNumber(), 1u);
-  {
-    // Partially consume the bytes field.
-    StreamDecoder::BytesReader bytes = decoder.GetBytesReader();
-
-    std::byte buffer[2];
-    EXPECT_EQ(bytes.Read(buffer).status(), OkStatus());
-    EXPECT_EQ(std::memcmp(buffer, encoded_proto + 2, sizeof(buffer)), 0);
-  }
-
-  // Continue reading the top-level message.
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  EXPECT_EQ(*decoder.FieldNumber(), 2u);
-
-  EXPECT_EQ(decoder.Next(), Status::OutOfRange());
-}
-
-TEST(StreamDecoder, Decode_BytesReader_NonSeekable_Close) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // bytes key=1, length=14
-    0x0a, 0x0e,
-
-    0x00, 0x01, 0x02, 0x03,
-    0x04, 0x05, 0x06, 0x07,
-    0x08, 0x09, 0x0a, 0x0b,
-    0x0c, 0x0d,
-    // End bytes
-
-    // type=sint32, k=2, v=-13
-    0x10, 0x19,
-  };
-  // clang-format on
-
-  // Test with a non-seekable memory reader
-  stream::MemoryReader wrapped_reader(std::as_bytes(std::span(encoded_proto)));
-  NonSeekableMemoryReader reader(wrapped_reader);
-  StreamDecoder decoder(reader);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  EXPECT_EQ(*decoder.FieldNumber(), 1u);
-  {
-    // Partially consume the bytes field.
-    StreamDecoder::BytesReader bytes = decoder.GetBytesReader();
-
-    std::byte buffer[2];
-    EXPECT_EQ(bytes.Read(buffer).status(), OkStatus());
-    EXPECT_EQ(std::memcmp(buffer, encoded_proto + 2, sizeof(buffer)), 0);
-  }
-
-  // Continue reading the top-level message.
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  EXPECT_EQ(*decoder.FieldNumber(), 2u);
-
-  EXPECT_EQ(decoder.Next(), Status::OutOfRange());
-}
-
-TEST(StreamDecoder, Decode_BytesReader_InvalidField) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // bytes key=1, length=4
-    0x0a, 0x04,
-
-    // Oops. No data!
-  };
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(*decoder.FieldNumber(), 1u);
-  {
-    StreamDecoder::BytesReader bytes = decoder.GetBytesReader();
-    EXPECT_EQ(bytes.Seek(0), Status::DataLoss());
-
-    std::byte buffer[2];
-    EXPECT_EQ(bytes.Read(buffer).status(), Status::DataLoss());
-  }
-
-  EXPECT_EQ(decoder.Next(), Status::DataLoss());
-}
-
-TEST(StreamDecoder, GetLengthDelimitedPayloadBounds) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // bytes key=1, length=14
-    0x0a, 0x0e,
-
-    0x00, 0x01, 0x02, 0x03,
-    0x04, 0x05, 0x06, 0x07,
-    0x08, 0x09, 0x0a, 0x0b,
-    0x0c, 0x0d,
-    // End bytes
-
-    // type=sint32, k=2, v=-13
-    0x10, 0x19,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader);
-
-  ASSERT_EQ(OkStatus(), decoder.Next());
-  Result<StreamDecoder::Bounds> field_bound =
-      decoder.GetLengthDelimitedPayloadBounds();
-  ASSERT_EQ(OkStatus(), field_bound.status());
-  ASSERT_EQ(field_bound.value().low, 2ULL);
-  ASSERT_EQ(field_bound.value().high, 16ULL);
-
-  ASSERT_EQ(OkStatus(), decoder.Next());
-  ASSERT_EQ(Status::NotFound(),
-            decoder.GetLengthDelimitedPayloadBounds().status());
-}
-
-TEST(StreamDecoder, ReadDelimitedField_DoesntOverConsume) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // type=string, k=1, v="Hello world"
-    0x0a, 0x0b, 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd',
-    // type=int32, k=2, v=42
-    0x10, 0x2a,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader);
-
-  ASSERT_EQ(OkStatus(), decoder.Next());
-
-  // This buffer is much larger than the string.
-  char buffer[128];
-  const StatusWithSize size = decoder.ReadString(buffer);
-  EXPECT_EQ(size.status(), OkStatus());
-  EXPECT_EQ(size.size(), 11u);
-
-  // Make sure we can still read the next field.
-  ASSERT_EQ(OkStatus(), decoder.Next());
-  const pw::Result<int32_t> result = decoder.ReadInt32();
-  EXPECT_EQ(result.status(), OkStatus());
-  EXPECT_EQ(result.value(), 42);
-}
-
-TEST(StreamDecoder, Decode_WithLength) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // type=int32, k=1, v=42
-    0x08, 0x2a,
-    // This field is beyond the range of the protobuf:
-    // type=sint32, k=2, v=-13
-    0x10, 0x19,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader, /*length=*/2u);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 1u);
-  Result<int32_t> int32 = decoder.ReadInt32();
-  ASSERT_EQ(int32.status(), OkStatus());
-  EXPECT_EQ(int32.value(), 42);
-
-  EXPECT_EQ(decoder.Next(), Status::OutOfRange());
-}
-
-TEST(StreamDecoder, Decode_WithLength_SkipsToEnd) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // type=string, k=1, v="Hello world"
-    0x08, 0x0b, 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd',
-    // This field is beyond the range of the protobuf:
-    // type=sint32, k=2, v=-13
-    0x10, 0x19,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  {
-    StreamDecoder decoder(reader, /*length=*/13u);
-
-    EXPECT_EQ(decoder.Next(), OkStatus());
-    ASSERT_EQ(decoder.FieldNumber().value(), 1u);
-    // Don't read the value out, or advance further. Destructing the object
-    // should advance to the end of the length given.
-  }
-
-  EXPECT_EQ(reader.Tell(), 13u);
-}
-
-TEST(StreamDecoder, RepeatedField) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // type=uint32, k=1, v=0
-    0x08, 0x00,
-    // type=uint32, k=1, v=50
-    0x08, 0x32,
-    // type=uint32, k=1, v=100
-    0x08, 0x64,
-    // type=uint32, k=1, v=150
-    0x08, 0x96, 0x01,
-    // type=uint32, k=1, v=200
-    0x08, 0xc8, 0x01
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 1u);
-  Result<uint32_t> uint32 = decoder.ReadUint32();
-  ASSERT_EQ(uint32.status(), OkStatus());
-  EXPECT_EQ(uint32.value(), 0u);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 1u);
-  uint32 = decoder.ReadUint32();
-  ASSERT_EQ(uint32.status(), OkStatus());
-  EXPECT_EQ(uint32.value(), 50u);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 1u);
-  uint32 = decoder.ReadUint32();
-  ASSERT_EQ(uint32.status(), OkStatus());
-  EXPECT_EQ(uint32.value(), 100u);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 1u);
-  uint32 = decoder.ReadUint32();
-  ASSERT_EQ(uint32.status(), OkStatus());
-  EXPECT_EQ(uint32.value(), 150u);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 1u);
-  uint32 = decoder.ReadUint32();
-  ASSERT_EQ(uint32.status(), OkStatus());
-  EXPECT_EQ(uint32.value(), 200u);
-
-  EXPECT_EQ(decoder.Next(), Status::OutOfRange());
-}
-
-TEST(StreamDecoder, RepeatedFieldVector) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // type=uint32, k=1, v=0
-    0x08, 0x00,
-    // type=uint32, k=1, v=50
-    0x08, 0x32,
-    // type=uint32, k=1, v=100
-    0x08, 0x64,
-    // type=uint32, k=1, v=150
-    0x08, 0x96, 0x01,
-    // type=uint32, k=1, v=200
-    0x08, 0xc8, 0x01
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader);
-
-  pw::Vector<uint32_t, 8> uint32{};
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 1u);
-  Status status = decoder.ReadRepeatedUint32(uint32);
-  ASSERT_EQ(status, OkStatus());
-  EXPECT_EQ(uint32.size(), 1u);
-  EXPECT_EQ(uint32[0], 0u);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 1u);
-  status = decoder.ReadRepeatedUint32(uint32);
-  ASSERT_EQ(status, OkStatus());
-  EXPECT_EQ(uint32.size(), 2u);
-  EXPECT_EQ(uint32[1], 50u);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 1u);
-  status = decoder.ReadRepeatedUint32(uint32);
-  ASSERT_EQ(status, OkStatus());
-  EXPECT_EQ(uint32.size(), 3u);
-  EXPECT_EQ(uint32[2], 100u);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 1u);
-  status = decoder.ReadRepeatedUint32(uint32);
-  ASSERT_EQ(status, OkStatus());
-  EXPECT_EQ(uint32.size(), 4u);
-  EXPECT_EQ(uint32[3], 150u);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 1u);
-  status = decoder.ReadRepeatedUint32(uint32);
-  ASSERT_EQ(status, OkStatus());
-  EXPECT_EQ(uint32.size(), 5u);
-  EXPECT_EQ(uint32[4], 200u);
-
-  EXPECT_EQ(decoder.Next(), Status::OutOfRange());
-}
-
-TEST(StreamDecoder, RepeatedFieldVectorFull) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // type=uint32, k=1, v=0
-    0x08, 0x00,
-    // type=uint32, k=1, v=50
-    0x08, 0x32,
-    // type=uint32, k=1, v=100
-    0x08, 0x64,
-    // type=uint32, k=1, v=150
-    0x08, 0x96, 0x01,
-    // type=uint32, k=1, v=200
-    0x08, 0xc8, 0x01
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader);
-
-  pw::Vector<uint32_t, 2> uint32{};
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 1u);
-  Status status = decoder.ReadRepeatedUint32(uint32);
-  ASSERT_EQ(status, OkStatus());
-  EXPECT_EQ(uint32.size(), 1u);
-  EXPECT_EQ(uint32[0], 0u);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 1u);
-  status = decoder.ReadRepeatedUint32(uint32);
-  ASSERT_EQ(status, OkStatus());
-  EXPECT_EQ(uint32.size(), 2u);
-  EXPECT_EQ(uint32[1], 50u);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 1u);
-  status = decoder.ReadRepeatedUint32(uint32);
-  ASSERT_EQ(status, Status::ResourceExhausted());
-  EXPECT_EQ(uint32.size(), 2u);
-}
-
-TEST(StreamDecoder, PackedVarint) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // type=uint32[], k=1, v={0, 50, 100, 150, 200}
-    0x0a, 0x07,
-    0x00,
-    0x32,
-    0x64,
-    0x96, 0x01,
-    0xc8, 0x01
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 1u);
-  std::array<uint32_t, 8> uint32{};
-  StatusWithSize size = decoder.ReadPackedUint32(uint32);
-  ASSERT_EQ(size.status(), OkStatus());
-  EXPECT_EQ(size.size(), 5u);
-
-  EXPECT_EQ(uint32[0], 0u);
-  EXPECT_EQ(uint32[1], 50u);
-  EXPECT_EQ(uint32[2], 100u);
-  EXPECT_EQ(uint32[3], 150u);
-  EXPECT_EQ(uint32[4], 200u);
-}
-
-TEST(StreamDecoder, PackedVarintInsufficientSpace) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // type=uint32[], k=1, v={0, 50, 100, 150, 200}
-    0x0a, 0x07,
-    0x00,
-    0x32,
-    0x64,
-    0x96, 0x01,
-    0xc8, 0x01
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 1u);
-  std::array<uint32_t, 2> uint32{};
-  StatusWithSize size = decoder.ReadPackedUint32(uint32);
-  ASSERT_EQ(size.status(), Status::ResourceExhausted());
-  EXPECT_EQ(size.size(), 2u);
-
-  // Still returns values in case of error.
-  EXPECT_EQ(uint32[0], 0u);
-  EXPECT_EQ(uint32[1], 50u);
-}
-
-TEST(StreamDecoder, PackedVarintVector) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // type=uint32[], k=1, v={0, 50, 100, 150, 200}
-    0x0a, 0x07,
-    0x00,
-    0x32,
-    0x64,
-    0x96, 0x01,
-    0xc8, 0x01
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 1u);
-  pw::Vector<uint32_t, 8> uint32{};
-  Status status = decoder.ReadRepeatedUint32(uint32);
-  ASSERT_EQ(status, OkStatus());
-  EXPECT_EQ(uint32.size(), 5u);
-
-  EXPECT_EQ(uint32[0], 0u);
-  EXPECT_EQ(uint32[1], 50u);
-  EXPECT_EQ(uint32[2], 100u);
-  EXPECT_EQ(uint32[3], 150u);
-  EXPECT_EQ(uint32[4], 200u);
-}
-
-TEST(StreamDecoder, PackedVarintVectorFull) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // type=uint32[], k=1, v={0, 50, 100, 150, 200}
-    0x0a, 0x07,
-    0x00,
-    0x32,
-    0x64,
-    0x96, 0x01,
-    0xc8, 0x01
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 1u);
-  pw::Vector<uint32_t, 2> uint32{};
-  Status status = decoder.ReadRepeatedUint32(uint32);
-  ASSERT_EQ(status, Status::ResourceExhausted());
-  EXPECT_EQ(uint32.size(), 2u);
-
-  // Still returns values in case of error.
-  EXPECT_EQ(uint32[0], 0u);
-  EXPECT_EQ(uint32[1], 50u);
-}
-
-TEST(StreamDecoder, PackedZigZag) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // type=sint32[], k=1, v={-100, -25, -1, 0, 1, 25, 100}
-    0x0a, 0x09,
-    0xc7, 0x01,
-    0x31,
-    0x01,
-    0x00,
-    0x02,
-    0x32,
-    0xc8, 0x01
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 1u);
-  std::array<int32_t, 8> sint32{};
-  StatusWithSize size = decoder.ReadPackedSint32(sint32);
-  ASSERT_EQ(size.status(), OkStatus());
-  EXPECT_EQ(size.size(), 7u);
-
-  EXPECT_EQ(sint32[0], -100);
-  EXPECT_EQ(sint32[1], -25);
-  EXPECT_EQ(sint32[2], -1);
-  EXPECT_EQ(sint32[3], 0);
-  EXPECT_EQ(sint32[4], 1);
-  EXPECT_EQ(sint32[5], 25);
-  EXPECT_EQ(sint32[6], 100);
-}
-
-TEST(StreamDecoder, PackedZigZagVector) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // type=sint32[], k=1, v={-100, -25, -1, 0, 1, 25, 100}
-    0x0a, 0x09,
-    0xc7, 0x01,
-    0x31,
-    0x01,
-    0x00,
-    0x02,
-    0x32,
-    0xc8, 0x01
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 1u);
-  pw::Vector<int32_t, 8> sint32{};
-  Status status = decoder.ReadRepeatedSint32(sint32);
-  ASSERT_EQ(status, OkStatus());
-  EXPECT_EQ(sint32.size(), 7u);
-
-  EXPECT_EQ(sint32[0], -100);
-  EXPECT_EQ(sint32[1], -25);
-  EXPECT_EQ(sint32[2], -1);
-  EXPECT_EQ(sint32[3], 0);
-  EXPECT_EQ(sint32[4], 1);
-  EXPECT_EQ(sint32[5], 25);
-  EXPECT_EQ(sint32[6], 100);
-}
-
-TEST(StreamDecoder, PackedFixed) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // type=fixed32[], k=1, v={0, 50, 100, 150, 200}
-    0x0a, 0x14,
-    0x00, 0x00, 0x00, 0x00,
-    0x32, 0x00, 0x00, 0x00,
-    0x64, 0x00, 0x00, 0x00,
-    0x96, 0x00, 0x00, 0x00,
-    0xc8, 0x00, 0x00, 0x00,
-    // type=fixed64[], v=2, v={0x0102030405060708}
-    0x12, 0x08,
-    0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01,
-    // type=sfixed32[], k=3, v={0, -50, 100, -150, 200}
-    0x1a, 0x14,
-    0x00, 0x00, 0x00, 0x00,
-    0xce, 0xff, 0xff, 0xff,
-    0x64, 0x00, 0x00, 0x00,
-    0x6a, 0xff, 0xff, 0xff,
-    0xc8, 0x00, 0x00, 0x00,
-    // type=sfixed64[], v=4, v={-1647993274}
-    0x22, 0x08,
-    0x46, 0x9e, 0xc5, 0x9d, 0xff, 0xff, 0xff, 0xff,
-    // type=double[], k=5, v=3.14159
-    0x2a, 0x08,
-    0x6e, 0x86, 0x1b, 0xf0, 0xf9, 0x21, 0x09, 0x40,
-    // type=float[], k=6, v=2.718
-    0x32, 0x04,
-    0xb6, 0xf3, 0x2d, 0x40,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 1u);
-  std::array<uint32_t, 8> fixed32{};
-  StatusWithSize size = decoder.ReadPackedFixed32(fixed32);
-  ASSERT_EQ(size.status(), OkStatus());
-  EXPECT_EQ(size.size(), 5u);
-
-  EXPECT_EQ(fixed32[0], 0u);
-  EXPECT_EQ(fixed32[1], 50u);
-  EXPECT_EQ(fixed32[2], 100u);
-  EXPECT_EQ(fixed32[3], 150u);
-  EXPECT_EQ(fixed32[4], 200u);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 2u);
-  std::array<uint64_t, 8> fixed64{};
-  size = decoder.ReadPackedFixed64(fixed64);
-  ASSERT_EQ(size.status(), OkStatus());
-  EXPECT_EQ(size.size(), 1u);
-
-  EXPECT_EQ(fixed64[0], 0x0102030405060708u);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 3u);
-  std::array<int32_t, 8> sfixed32{};
-  size = decoder.ReadPackedSfixed32(sfixed32);
-  ASSERT_EQ(size.status(), OkStatus());
-  EXPECT_EQ(size.size(), 5u);
-
-  EXPECT_EQ(sfixed32[0], 0);
-  EXPECT_EQ(sfixed32[1], -50);
-  EXPECT_EQ(sfixed32[2], 100);
-  EXPECT_EQ(sfixed32[3], -150);
-  EXPECT_EQ(sfixed32[4], 200);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 4u);
-  std::array<int64_t, 8> sfixed64{};
-  size = decoder.ReadPackedSfixed64(sfixed64);
-  ASSERT_EQ(size.status(), OkStatus());
-  EXPECT_EQ(size.size(), 1u);
-
-  EXPECT_EQ(sfixed64[0], -1647993274);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 5u);
-  std::array<double, 8> dbl{};
-  size = decoder.ReadPackedDouble(dbl);
-  ASSERT_EQ(size.status(), OkStatus());
-  EXPECT_EQ(size.size(), 1u);
-
-  EXPECT_EQ(dbl[0], 3.14159);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 6u);
-  std::array<float, 8> flt{};
-  size = decoder.ReadPackedFloat(flt);
-  ASSERT_EQ(size.status(), OkStatus());
-  EXPECT_EQ(size.size(), 1u);
-
-  EXPECT_EQ(flt[0], 2.718f);
-
-  EXPECT_EQ(decoder.Next(), Status::OutOfRange());
-}
-
-TEST(StreamDecoder, PackedFixedInsufficientSpace) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // type=fixed32[], k=1, v={0, 50, 100, 150, 200}
-    0x0a, 0x14,
-    0x00, 0x00, 0x00, 0x00,
-    0x32, 0x00, 0x00, 0x00,
-    0x64, 0x00, 0x00, 0x00,
-    0x96, 0x00, 0x00, 0x00,
-    0xc8, 0x00, 0x00, 0x00,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 1u);
-  std::array<uint32_t, 2> fixed32{};
-  StatusWithSize size = decoder.ReadPackedFixed32(fixed32);
-  ASSERT_EQ(size.status(), Status::ResourceExhausted());
-}
-
-TEST(StreamDecoder, PackedFixedVector) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // type=sfixed32[], k=1, v={0, -50, 100, -150, 200}
-    0x0a, 0x14,
-    0x00, 0x00, 0x00, 0x00,
-    0xce, 0xff, 0xff, 0xff,
-    0x64, 0x00, 0x00, 0x00,
-    0x6a, 0xff, 0xff, 0xff,
-    0xc8, 0x00, 0x00, 0x00,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 1u);
-  pw::Vector<int32_t, 8> sfixed32{};
-  Status status = decoder.ReadRepeatedSfixed32(sfixed32);
-  ASSERT_EQ(status, OkStatus());
-  EXPECT_EQ(sfixed32.size(), 5u);
-
-  EXPECT_EQ(sfixed32[0], 0);
-  EXPECT_EQ(sfixed32[1], -50);
-  EXPECT_EQ(sfixed32[2], 100);
-  EXPECT_EQ(sfixed32[3], -150);
-  EXPECT_EQ(sfixed32[4], 200);
-
-  EXPECT_EQ(decoder.Next(), Status::OutOfRange());
-}
-
-TEST(StreamDecoder, PackedFixedVectorFull) {
-  // clang-format off
-  constexpr uint8_t encoded_proto[] = {
-    // type=sfixed32[], k=1, v={0, -50, 100, -150, 200}
-    0x0a, 0x14,
-    0x00, 0x00, 0x00, 0x00,
-    0xce, 0xff, 0xff, 0xff,
-    0x64, 0x00, 0x00, 0x00,
-    0x6a, 0xff, 0xff, 0xff,
-    0xc8, 0x00, 0x00, 0x00,
-  };
-  // clang-format on
-
-  stream::MemoryReader reader(std::as_bytes(std::span(encoded_proto)));
-  StreamDecoder decoder(reader);
-
-  EXPECT_EQ(decoder.Next(), OkStatus());
-  ASSERT_EQ(decoder.FieldNumber().value(), 1u);
-  pw::Vector<int32_t, 2> sfixed32{};
-  Status status = decoder.ReadRepeatedSfixed32(sfixed32);
-  ASSERT_EQ(status, Status::ResourceExhausted());
-  EXPECT_EQ(sfixed32.size(), 0u);
-}
-
-}  // namespace
-}  // namespace pw::protobuf
diff --git a/pw_protobuf/varint_size_test.cc b/pw_protobuf/varint_size_test.cc
index 3e4f61e..246b265 100644
--- a/pw_protobuf/varint_size_test.cc
+++ b/pw_protobuf/varint_size_test.cc
@@ -20,56 +20,53 @@
 namespace {
 
 TEST(Encoder, SizeTypeIsConfigured) {
-  static_assert(config::kMaxVarintSize == sizeof(uint8_t));
+  static_assert(sizeof(Encoder::SizeType) == sizeof(uint8_t));
 }
 
 TEST(Encoder, NestedWriteSmallerThanVarintSize) {
   std::array<std::byte, 256> buffer;
+  NestedEncoder<2, 2> encoder(buffer);
 
-  MemoryEncoder encoder(buffer);
+  encoder.Push(1);
+  // 1 byte key + 1 byte size + 125 byte value = 127 byte nested length.
+  EXPECT_EQ(encoder.WriteBytes(2, bytes::Initialized<125>(0xaa)), OkStatus());
+  encoder.Pop();
 
-  {
-    StreamEncoder nested = encoder.GetNestedEncoder(1);
-    // 1 byte key + 1 byte size + 125 byte value = 127 byte nested length.
-    EXPECT_EQ(nested.WriteBytes(2, bytes::Initialized<125>(0xaa)), OkStatus());
-  }
-
-  EXPECT_EQ(encoder.status(), OkStatus());
+  auto result = encoder.Encode();
+  EXPECT_EQ(result.status(), OkStatus());
 }
 
-TEST(Encoder, NestedWriteLargerThanVarintSizeReturnsResourceExhausted) {
+TEST(Encoder, NestedWriteLargerThanVarintSizeReturnsOutOfRange) {
   std::array<std::byte, 256> buffer;
+  NestedEncoder<2, 2> encoder(buffer);
 
-  MemoryEncoder encoder(buffer);
+  // Try to write a larger nested message than the max nested varint value.
+  encoder.Push(1);
+  // 1 byte key + 1 byte size + 126 byte value = 128 byte nested length.
+  EXPECT_EQ(encoder.WriteBytes(2, bytes::Initialized<126>(0xaa)),
+            Status::OutOfRange());
+  EXPECT_EQ(encoder.WriteUint32(3, 42), Status::OutOfRange());
+  encoder.Pop();
 
-  {
-    // Try to write a larger nested message than the max nested varint value.
-    StreamEncoder nested = encoder.GetNestedEncoder(1);
-    // 1 byte key + 1 byte size + 126 byte value = 128 byte nested length.
-    EXPECT_EQ(nested.WriteBytes(2, bytes::Initialized<126>(0xaa)),
-              Status::ResourceExhausted());
-    EXPECT_EQ(nested.WriteUint32(3, 42), Status::ResourceExhausted());
-  }
-
-  EXPECT_EQ(encoder.status(), Status::ResourceExhausted());
+  auto result = encoder.Encode();
+  EXPECT_EQ(result.status(), Status::OutOfRange());
 }
 
-TEST(Encoder, NestedMessageLargerThanVarintSizeReturnsResourceExhausted) {
+TEST(Encoder, NestedMessageLargerThanVarintSizeReturnsOutOfRange) {
   std::array<std::byte, 256> buffer;
+  NestedEncoder<2, 2> encoder(buffer);
 
-  MemoryEncoder encoder(buffer);
+  // Try to write a larger nested message than the max nested varint value as
+  // multiple smaller writes.
+  encoder.Push(1);
+  EXPECT_EQ(encoder.WriteBytes(2, bytes::Initialized<60>(0xaa)), OkStatus());
+  EXPECT_EQ(encoder.WriteBytes(3, bytes::Initialized<60>(0xaa)), OkStatus());
+  EXPECT_EQ(encoder.WriteBytes(4, bytes::Initialized<60>(0xaa)),
+            Status::OutOfRange());
+  encoder.Pop();
 
-  {
-    // Try to write a larger nested message than the max nested varint value as
-    // multiple smaller writes.
-    StreamEncoder nested = encoder.GetNestedEncoder(1);
-    EXPECT_EQ(nested.WriteBytes(2, bytes::Initialized<60>(0xaa)), OkStatus());
-    EXPECT_EQ(nested.WriteBytes(3, bytes::Initialized<60>(0xaa)), OkStatus());
-    EXPECT_EQ(nested.WriteBytes(4, bytes::Initialized<60>(0xaa)),
-              Status::ResourceExhausted());
-  }
-
-  EXPECT_EQ(encoder.status(), Status::ResourceExhausted());
+  auto result = encoder.Encode();
+  EXPECT_EQ(result.status(), Status::OutOfRange());
 }
 
 }  // namespace
diff --git a/pw_protobuf_compiler/BUILD b/pw_protobuf_compiler/BUILD
new file mode 100644
index 0000000..34b516e
--- /dev/null
+++ b/pw_protobuf_compiler/BUILD
@@ -0,0 +1,23 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+# TODO(frolv): Figure out how to support nanopb codegen in Bazel.
+filegroup(
+    name = "nanopb_test",
+    srcs = ["nanopb_test.cc"],
+)
diff --git a/pw_protobuf_compiler/BUILD.bazel b/pw_protobuf_compiler/BUILD.bazel
deleted file mode 100644
index 045285e..0000000
--- a/pw_protobuf_compiler/BUILD.bazel
+++ /dev/null
@@ -1,46 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-load("@com_google_protobuf//:protobuf.bzl", "py_proto_library")
-load("@rules_proto_grpc//js:defs.bzl", "js_proto_library")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-# TODO(frolv): Figure out how to support nanopb codegen in Bazel.
-filegroup(
-    name = "nanopb_test",
-    srcs = ["nanopb_test.cc"],
-)
-
-py_proto_library(
-    name = "pw_protobuf_compiler_protos",
-    srcs = [
-        "pw_protobuf_compiler_protos/nested/more_nesting/test.proto",
-        "pw_protobuf_compiler_protos/test.proto",
-    ],
-)
-
-proto_library(
-    name = "test_protos",
-    srcs = [
-        "pw_protobuf_compiler_protos/nested/more_nesting/test.proto",
-        "pw_protobuf_compiler_protos/test.proto",
-    ],
-)
-
-js_proto_library(
-    name = "test_protos_tspb",
-    protos = ["//pw_protobuf_compiler:test_protos"],
-)
diff --git a/pw_protobuf_compiler/BUILD.gn b/pw_protobuf_compiler/BUILD.gn
index 62f2819..c611c6b 100644
--- a/pw_protobuf_compiler/BUILD.gn
+++ b/pw_protobuf_compiler/BUILD.gn
@@ -51,10 +51,6 @@
 
 # PyPI Requirements needed to install Python protobuf packages.
 pw_python_requirements("protobuf_requirements") {
-  requirements = [
-    # NOTE: mypy needs to stay in sync with mypy-protobuf
-    # Currently using mypy 0.910 and mypy-protobuf 2.9
-    # This must also be specified in //pw_protobuf_compiler/py/setup.cfg
-    "mypy-protobuf==2.9",
-  ]
+  # mypy-protobuf 1.24 is required to support optional fields in proto3.
+  requirements = [ "mypy-protobuf>=2.2" ]
 }
diff --git a/pw_protobuf_compiler/CMakeLists.txt b/pw_protobuf_compiler/CMakeLists.txt
deleted file mode 100644
index 4b5a3b6..0000000
--- a/pw_protobuf_compiler/CMakeLists.txt
+++ /dev/null
@@ -1,34 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-include($ENV{PW_ROOT}/pw_protobuf_compiler/proto.cmake)
-
-if(NOT "${dir_pw_third_party_nanopb}" STREQUAL "")
-  pw_proto_library(pw_protobuf_compiler.nanopb_test_protos
-    SOURCES
-      pw_protobuf_compiler_nanopb_protos/nanopb_test.proto
-    DEPS
-      pw_third_party.nanopb.proto
-  )
-
-  pw_add_test(pw_protobuf_compiler.nanopb_test
-    SOURCES
-      nanopb_test.cc
-    DEPS
-      pw_protobuf_compiler.nanopb_test_protos.nanopb
-    GROUPS
-      pw_protobuf_compiler
-  )
-endif()
diff --git a/pw_protobuf_compiler/OWNERS b/pw_protobuf_compiler/OWNERS
deleted file mode 100644
index d96cbc6..0000000
--- a/pw_protobuf_compiler/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-hepler@google.com
diff --git a/pw_protobuf_compiler/deps.bzl b/pw_protobuf_compiler/deps.bzl
deleted file mode 100644
index 94a30ee..0000000
--- a/pw_protobuf_compiler/deps.bzl
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Fetches workspace dependencies required for //pw_protobuf"""
-
-load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
-
-def pw_protobuf_dependencies():
-    """ Fetches workspace dependencies required for //pw_protobuf """
-    if "com_google_protobuf" not in native.existing_rules():
-        http_archive(
-            name = "com_google_protobuf",
-            sha256 = "71030a04aedf9f612d2991c1c552317038c3c5a2b578ac4745267a45e7037c29",
-            strip_prefix = "protobuf-3.12.3",
-            url = "https://github.com/protocolbuffers/protobuf/archive/v3.12.3.tar.gz",
-        )
-    if "rules_proto_grpc" not in native.existing_rules():
-        http_archive(
-            name = "rules_proto_grpc",
-            sha256 = "5f0f2fc0199810c65a2de148a52ba0aff14d631d4e8202f41aff6a9d590a471b",
-            strip_prefix = "rules_proto_grpc-1.0.2",
-            urls = ["https://github.com/rules-proto-grpc/rules_proto_grpc/archive/1.0.2.tar.gz"],
-        )
diff --git a/pw_protobuf_compiler/docs.rst b/pw_protobuf_compiler/docs.rst
index 31160e8..eeb1620 100644
--- a/pw_protobuf_compiler/docs.rst
+++ b/pw_protobuf_compiler/docs.rst
@@ -30,16 +30,13 @@
 | Python      | ``python``     | Compiles using the standard Python protobuf   |
 |             |                | plugin, creating a ``pw_python_package``.     |
 +-------------+----------------+-----------------------------------------------+
-| Typescript  | ``typescript`` | Compilation is supported in Bazel via         |
-|             |                | @rules_proto_grpc. ProtoCollection provides   |
-|             |                | convience methods for proto descriptors.      |
-+-------------+----------------+-----------------------------------------------+
 
 GN template
 ===========
-This module provides a ``pw_proto_library`` GN template that defines a
-collection of protobuf files that should be compiled together. The template
-creates a sub-target for each supported generator, named
+The ``pw_proto_library`` GN template is provided by the module.
+
+It defines a collection of protobuf files that should be compiled together. The
+template creates a sub-target for each supported generator, named
 ``<target_name>.<generator>``. These sub-targets generate their respective
 protobuf code, and expose it to the build system appropriately (e.g. a
 ``pw_source_set`` for C/C++).
@@ -75,20 +72,6 @@
   //path/to/my_protos:my_protos.python.lint
   //path/to/my_protos:python.lint
 
-**Supported Codegen**
-
-GN supports the following compiled proto libraries via the specified
-sub-targets generated by a ``pw_proto_library``.
-
-* ``${target_name}.pwpb`` - Generated C++ pw_protobuf code
-* ``${target_name}.nanopb`` - Generated C++ nanopb code (requires Nanopb)
-* ``${target_name}.nanopb_rpc`` - Generated C++ Nanopb pw_rpc code (requires
-  Nanopb)
-* ``${target_name}.raw_rpc`` - Generated C++ raw pw_rpc code (no protobuf
-  library)
-* ``${target_name}.go`` - Generated GO protobuf libraries
-* ``${target_name}.python`` - Generated Python protobuf libraries
-
 **Arguments**
 
 * ``sources``: List of input .proto files.
@@ -101,7 +84,6 @@
 * ``strip_prefix``: Remove this prefix from the source protos. All source and
   input files must be nested under this path.
 * ``python_package``: Label of Python package to which to add the proto modules.
-  The .python subtarget will redirect to this package.
 
 **Example**
 
@@ -208,12 +190,9 @@
 
   pw_python_pacakge("my_package") {
     generate_setup = {
-      metadata = {
-        name = "foo"
-        version = "1.0"
-      }
+      name = "foo"
+      version = "1.0"
     }
-
     sources = [ "foo/cool_module.py" ]
     proto_library = ":my_protos"
   }
@@ -323,125 +302,3 @@
 .. code-block:: cpp
 
   #include "my_other_protos/baz.pwpb.h"
-
-**Supported Codegen**
-
-CMake supports the following compiled proto libraries via the specified
-sub-targets generated by a ``pw_proto_library``.
-
-* ``${NAME}.pwpb`` - Generated C++ pw_protobuf code
-* ``${NAME}.nanopb`` - Generated C++ nanopb code (requires Nanopb)
-* ``${NAME}.nanopb_rpc`` - Generated C++ Nanopb pw_rpc code (requires Nanopb)
-* ``${NAME}.raw_rpc`` - Generated C++ raw pw_rpc code (no protobuf library)
-
-Bazel
-=====
-Bazel provides a ``pw_proto_library`` rule with similar features as the
-GN template. The Bazel build only supports building firmware code, so
-``pw_proto_library`` does not generate a Python package. The Bazel rules differ
-slightly compared to the GN build to be more in line with what would be
-considered idiomatic in Bazel.
-
-To use Pigweeds Protobuf rules you must first pull in the required dependencies
-into your Bazel WORKSPACE file. e.g.
-
-.. code-block:: python
-
-  # WORKSPACE ...
-  load("@pigweed//pw_protobuf_compiler:deps.bzl", "pw_protobuf_dependencies")
-  pw_protobuf_dependencies()
-
-Bazel uses a different set of rules to manage proto files than it does to
-compile them. e.g.
-
-.. code-block:: python
-
-  # BUILD ...
-  load("@rules_proto//proto:defs.bzl", "proto_library")
-  load("@pigweed//pw_protobuf_compiler:proto.bzl", "pw_proto_library")
-
-  # Manages proto sources and dependencies.
-  proto_library(
-    name = "my_proto",
-    srcs = [
-      "my_protos/foo.proto",
-      "my_protos/bar.proto",
-    ]
-  )
-
-  # Compiles dependant protos to C++.
-  pw_proto_library(
-    name = "my_cc_proto",
-    deps = [":my_proto"],
-  )
-
-  # Library that depends on only pw_protobuf generated proto targets.
-  pw_cc_library(
-    name = "my_proto_only_lib",
-    srcs = ["my/proto_only.cc"],
-    deps = [":my_cc_proto.pwpb"],
-  )
-
-  # Library that depends on only Nanopb generated proto targets.
-  pw_cc_library(
-    name = "my_nanopb_only_lib",
-    srcs = ["my/nanopb_only.cc"],
-    deps = [":my_cc_proto.nanopb"],
-  )
-
-  # Library that depends on pw_protobuf and pw_rpc/raw.
-  pw_cc_library(
-    name = "my_raw_rpc_lib",
-    srcs = ["my/raw_rpc.cc"],
-    deps = [
-      ":my_cc_proto.pwpb",
-      ":my_cc_proto.raw_rpc",
-    ],
-  )
-  pw_cc_library(
-    name = "my_nanopb_rpc_lib",
-    srcs = ["my/proto_only.cc"],
-    deps = [
-      ":my_cc_proto.nanopb_rpc",
-    ],
-  )
-
-
-  # Library that depends on generated proto targets. Prefer to depend only on
-  # those generated targets ("my_lib.pwpb", "my_lib.nanopb") that are actually
-  # required. Note that the .nanopb target may not compile for some proto
-  # messages, e.g. self-referring messages;
-  # see https://github.com/nanopb/nanopb/issues/433.
-  pw_cc_library(
-    name = "my_lib",
-    srcs = ["my/lib.cc"],
-    # This target depends on all generated proto targets
-    # e.g. name.{pwpb, nanopb, raw_rpc, nanopb_rpc}
-    deps = [":my_cc_proto"],
-  )
-
-
-From ``my/lib.cc`` you can now include the generated headers.
-e.g.
-
-.. code:: cpp
-
-  #include "my_protos/bar.pwpb.h"
-  // and/or RPC headers
-  #include "my_protos/bar.raw_rpc.pb.h
-  // or
-  #include "my_protos/bar.nanopb_rpc.pb.h"
-
-
-
-**Supported Codegen**
-
-Bazel supports the following compiled proto libraries via the specified
-sub-targets generated by a ``pw_proto_library``.
-
-* ``${NAME}.pwpb`` - Generated C++ pw_protobuf code
-* ``${NAME}.nanopb`` - Generated C++ nanopb code
-* ``${NAME}.raw_rpc`` - Generated C++ raw pw_rpc code (no protobuf library)
-* ``${NAME}.nanopb_rpc`` - Generated C++ Nanopb pw_rpc code
-
-
diff --git a/pw_protobuf_compiler/proto.bzl b/pw_protobuf_compiler/proto.bzl
deleted file mode 100644
index effe3de..0000000
--- a/pw_protobuf_compiler/proto.bzl
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Embedded-friendly replacement for native.cc_proto_library."""
-
-load(
-    "//third_party/rules_proto_grpc:internal_proto.bzl",
-    _pw_proto_library = "pw_proto_library",
-)
-
-# Export internal symbols.
-pw_proto_library = _pw_proto_library
diff --git a/pw_protobuf_compiler/proto.cmake b/pw_protobuf_compiler/proto.cmake
index dbe4fe2..d05b960 100644
--- a/pw_protobuf_compiler/proto.cmake
+++ b/pw_protobuf_compiler/proto.cmake
@@ -33,7 +33,7 @@
 #   PREFIX - prefix add to the proto files
 #   STRIP_PREFIX - prefix to remove from the proto files
 #   INPUTS - files to include along with the .proto files (such as Nanopb
-#       .options files)
+#       .options files
 #
 function(pw_proto_library NAME)
   cmake_parse_arguments(PARSE_ARGV 1 arg "" "STRIP_PREFIX;PREFIX"
@@ -65,8 +65,6 @@
 
   if("${arg_STRIP_PREFIX}" STREQUAL "")
     set(arg_STRIP_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}")
-  else()
-    get_filename_component(arg_STRIP_PREFIX "${arg_STRIP_PREFIX}" ABSOLUTE)
   endif()
 
   foreach(path IN LISTS arg_SOURCES arg_INPUTS)
@@ -167,15 +165,14 @@
     OUTPUT
       ${outputs}
   )
-  add_custom_target("${TARGET}._generate.${LANGUAGE}" DEPENDS ${outputs})
-  add_dependencies("${TARGET}._generate.${LANGUAGE}" "${TARGET}._sources")
+  add_custom_target("${TARGET}" DEPENDS ${outputs})
 endfunction(_pw_generate_protos)
 
 # Internal function that creates a pwpb proto library.
 function(_pw_pwpb_library NAME SOURCES INPUTS DEPS INCLUDE_FILE OUT_DIR)
   list(TRANSFORM DEPS APPEND .pwpb)
 
-  _pw_generate_protos("${NAME}"
+  _pw_generate_protos("${NAME}._generate.pwpb"
       pwpb
       "$ENV{PW_ROOT}/pw_protobuf/py/pw_protobuf/plugin.py"
       ".pwpb.h"
@@ -189,14 +186,7 @@
   # Create the library with the generated source files.
   add_library("${NAME}.pwpb" INTERFACE)
   target_include_directories("${NAME}.pwpb" INTERFACE "${OUT_DIR}/pwpb")
-  target_link_libraries("${NAME}.pwpb"
-    INTERFACE
-      pw_build
-      pw_polyfill.cstddef
-      pw_polyfill.span
-      pw_protobuf
-      ${DEPS}
-  )
+  target_link_libraries("${NAME}.pwpb" INTERFACE pw_protobuf ${DEPS})
   add_dependencies("${NAME}.pwpb" "${NAME}._generate.pwpb")
 endfunction(_pw_pwpb_library)
 
@@ -204,7 +194,7 @@
 function(_pw_raw_rpc_library NAME SOURCES INPUTS DEPS INCLUDE_FILE OUT_DIR)
   list(TRANSFORM DEPS APPEND .raw_rpc)
 
-  _pw_generate_protos("${NAME}"
+  _pw_generate_protos("${NAME}._generate.raw_rpc"
       raw_rpc
       "$ENV{PW_ROOT}/pw_rpc/py/pw_rpc/plugin_raw.py"
       ".raw_rpc.pb.h"
@@ -220,7 +210,6 @@
   target_include_directories("${NAME}.raw_rpc" INTERFACE "${OUT_DIR}/raw_rpc")
   target_link_libraries("${NAME}.raw_rpc"
     INTERFACE
-      pw_build
       pw_rpc.raw
       pw_rpc.server
       ${DEPS}
@@ -234,12 +223,12 @@
 
   if("${dir_pw_third_party_nanopb}" STREQUAL "")
     add_custom_target("${NAME}._generate.nanopb"
-        "${CMAKE_COMMAND}" -E echo
+        cmake -E echo
             ERROR: Attempting to use pw_proto_library, but
             dir_pw_third_party_nanopb is not set. Set dir_pw_third_party_nanopb
             to the path to the Nanopb repository.
       COMMAND
-        "${CMAKE_COMMAND}" -E false
+        cmake -E false
       DEPENDS
         ${DEPS}
       SOURCES
@@ -247,42 +236,23 @@
     )
     set(generated_outputs $<TARGET_PROPERTY:pw_build.empty,SOURCES>)
   else()
-    # When compiling with the Nanopb plugin, the nanopb.proto file is already
-    # compiled internally, so skip recompiling it with protoc.
-    if("${SOURCES}" MATCHES "nanopb\\.proto")
-      add_custom_target("${NAME}._generate.nanopb")  # Nothing to do
-      add_library("${NAME}.nanopb" INTERFACE)
-      target_link_libraries("${NAME}.nanopb"
-        INTERFACE
-          pw_build
-          pw_third_party.nanopb
-          ${DEPS}
-      )
-    else()
-      _pw_generate_protos("${NAME}"
-          nanopb
-          "${dir_pw_third_party_nanopb}/generator/protoc-gen-nanopb"
-          ".pb.h;.pb.c"
-          "${INCLUDE_FILE}"
-          "${OUT_DIR}"
-          "${SOURCES}"
-          "${INPUTS}"
-          "${DEPS}"
-      )
-
-      # Create the library with the generated source files.
-      add_library("${NAME}.nanopb" EXCLUDE_FROM_ALL ${generated_outputs})
-      target_include_directories("${NAME}.nanopb" PUBLIC "${OUT_DIR}/nanopb")
-      target_link_libraries("${NAME}.nanopb" PUBLIC pw_build pw_third_party.nanopb ${DEPS})
-    endif()
-
-    add_dependencies("${NAME}.nanopb" "${NAME}._generate.nanopb")
-
-    # Ensure that nanopb_pb2.py is generated to avoid race conditions.
-    add_dependencies("${NAME}._generate.nanopb"
-        pw_third_party.nanopb.generate_proto
+    _pw_generate_protos("${NAME}._generate.nanopb"
+        nanopb
+        "${dir_pw_third_party_nanopb}/generator/protoc-gen-nanopb"
+        ".pb.h;.pb.c"
+        "${INCLUDE_FILE}"
+        "${OUT_DIR}"
+        "${SOURCES}"
+        "${INPUTS}"
+        "${DEPS}"
     )
   endif()
+
+  # Create the library with the generated source files.
+  add_library("${NAME}.nanopb" EXCLUDE_FROM_ALL ${generated_outputs})
+  target_include_directories("${NAME}.nanopb" PUBLIC "${OUT_DIR}/nanopb")
+  target_link_libraries("${NAME}.nanopb" PUBLIC pw_third_party.nanopb ${DEPS})
+  add_dependencies("${NAME}.nanopb" "${NAME}._generate.nanopb")
 endfunction(_pw_nanopb_library)
 
 # Internal function that creates a nanopb_rpc library.
@@ -290,7 +260,7 @@
   # Determine the names of the output files.
   list(TRANSFORM DEPS APPEND .nanopb_rpc)
 
-  _pw_generate_protos("${NAME}"
+  _pw_generate_protos("${NAME}._generate.nanopb_rpc"
       nanopb_rpc
       "$ENV{PW_ROOT}/pw_rpc/py/pw_rpc/plugin_nanopb.py"
       ".rpc.pb.h"
@@ -310,8 +280,6 @@
   target_link_libraries("${NAME}.nanopb_rpc"
     INTERFACE
       "${NAME}.nanopb"
-      pw_build
-      pw_rpc.nanopb.client
       pw_rpc.nanopb.method_union
       pw_rpc.server
       ${DEPS}
diff --git a/pw_protobuf_compiler/proto.gni b/pw_protobuf_compiler/proto.gni
index e6fd4a8..a6ec15e 100644
--- a/pw_protobuf_compiler/proto.gni
+++ b/pw_protobuf_compiler/proto.gni
@@ -21,7 +21,6 @@
 import("$dir_pw_build/python_action.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_third_party/nanopb/nanopb.gni")
-import("toolchain.gni")
 
 # Variables forwarded from the public pw_proto_library template to the final
 # pw_source_set.
@@ -36,7 +35,7 @@
 # This creates the internal GN target $target_name.$language._gen that compiles
 # proto files with protoc.
 template("_pw_invoke_protoc") {
-  if (current_toolchain == pw_protobuf_compiler_TOOLCHAIN) {
+  if (current_toolchain == default_toolchain) {
     if (defined(invoker.out_dir)) {
       _out_dir = invoker.out_dir
     } else {
@@ -49,8 +48,7 @@
     }
 
     _includes =
-        rebase_path(get_target_outputs(":${invoker.base_target}._includes"),
-                    root_build_dir)
+        rebase_path(get_target_outputs(":${invoker.base_target}._includes"))
 
     pw_python_action("$target_name._gen") {
       script =
@@ -70,26 +68,21 @@
         deps += [ get_label_info(dep, "label_no_toolchain") + "._gen" ]
       }
 
-      if (defined(invoker.other_deps)) {
-        deps += invoker.other_deps
-      }
-
       args = [
                "--language",
                invoker.language,
                "--include-file",
                _includes[0],
                "--compile-dir",
-               rebase_path(invoker.compile_dir, root_build_dir),
+               rebase_path(invoker.compile_dir),
                "--out-dir",
-               rebase_path(_out_dir, root_build_dir),
+               rebase_path(_out_dir),
                "--sources",
-             ] + rebase_path(invoker.sources, root_build_dir)
+             ] + rebase_path(invoker.sources)
 
       if (defined(invoker.plugin)) {
         inputs = [ invoker.plugin ]
-        args +=
-            [ "--plugin-path=" + rebase_path(invoker.plugin, root_build_dir) ]
+        args += [ "--plugin-path=" + rebase_path(invoker.plugin) ]
       }
 
       if (defined(invoker.outputs)) {
@@ -100,34 +93,15 @@
 
       if (defined(invoker.metadata)) {
         metadata = invoker.metadata
+      } else {
+        metadata = {
+          protoc_outputs = rebase_path(outputs)
+          root = [ rebase_path(_out_dir) ]
+        }
       }
     }
-
-    # Output a .json file with information about this proto library.
-    _proto_info = {
-      label = get_label_info(":${invoker.target_name}", "label_no_toolchain")
-      protoc_outputs =
-          rebase_path(get_target_outputs(":$target_name._gen"), root_build_dir)
-      root = rebase_path(_out_dir, root_build_dir)
-      package = invoker.package
-
-      nested_in_python_package = ""
-      if (defined(invoker.python_package)) {
-        nested_in_python_package =
-            get_label_info(invoker.python_package, "label_no_toolchain")
-      }
-
-      dependencies = []
-      foreach(dep, invoker.deps) {
-        dependencies +=
-            rebase_path([ get_label_info(dep, "target_gen_dir") + "/" +
-                              get_label_info(dep, "name") + ".json" ],
-                        root_build_dir)
-      }
-    }
-    write_file("$target_gen_dir/$target_name.json", _proto_info, "json")
   } else {
-    # protoc is only ever invoked from pw_protobuf_compiler_TOOLCHAIN.
+    # protoc is only ever invoked from the default toolchain.
     not_needed([ "target_name" ])
     not_needed(invoker, "*")
   }
@@ -153,14 +127,8 @@
   pw_source_set(target_name) {
     forward_variables_from(invoker, _forwarded_vars)
     public_configs = [ ":$target_name._include_path" ]
-    deps = [ ":$target_name._gen($pw_protobuf_compiler_TOOLCHAIN)" ]
-    public_deps = [
-                    "$dir_pw_containers:vector",
-                    dir_pw_assert,
-                    dir_pw_protobuf,
-                    dir_pw_result,
-                    dir_pw_status,
-                  ] + invoker.deps
+    deps = [ ":$target_name._gen($default_toolchain)" ]
+    public_deps = [ dir_pw_protobuf ] + invoker.deps
     sources = invoker.outputs
     public = filter_include(sources, [ "*.pwpb.h" ])
   }
@@ -188,16 +156,14 @@
   pw_source_set(target_name) {
     forward_variables_from(invoker, _forwarded_vars)
     public_configs = [ ":$target_name._include_path" ]
-    deps = [ ":$target_name._gen($pw_protobuf_compiler_TOOLCHAIN)" ]
+    deps = [ ":$target_name._gen($default_toolchain)" ]
     public_deps = [
                     ":${invoker.base_target}.nanopb",
                     "$dir_pw_rpc:server",
-                    "$dir_pw_rpc/nanopb:client_api",
-                    "$dir_pw_rpc/nanopb:server_api",
+                    "$dir_pw_rpc/nanopb:method_union",
                     "$dir_pw_third_party/nanopb",
                   ] + invoker.deps
     public = invoker.outputs
-    check_includes = false
   }
 }
 
@@ -209,14 +175,11 @@
   # compiled internally, so skip recompiling it with protoc.
   if (rebase_path(invoker.sources, invoker.compile_dir) == [ "nanopb.proto" ]) {
     group("$target_name._gen") {
-      deps = [
-        ":${invoker.base_target}._sources($pw_protobuf_compiler_TOOLCHAIN)",
-      ]
+      deps = [ ":${invoker.base_target}._sources" ]
     }
 
     group("$target_name") {
-      deps = invoker.deps +
-             [ ":$target_name._gen($pw_protobuf_compiler_TOOLCHAIN)" ]
+      deps = invoker.deps + [ ":$target_name._gen($default_toolchain)" ]
     }
   } else {
     # Create a target which runs protoc configured with the nanopb plugin to
@@ -225,7 +188,6 @@
       forward_variables_from(invoker, "*", _forwarded_vars)
       language = "nanopb"
       plugin = "$dir_pw_third_party_nanopb/generator/protoc-gen-nanopb"
-      other_deps = [ "$dir_pw_third_party/nanopb:generate_nanopb_proto.action" ]
     }
 
     # Create a library with the generated source files.
@@ -237,7 +199,7 @@
     pw_source_set(target_name) {
       forward_variables_from(invoker, _forwarded_vars)
       public_configs = [ ":$target_name._include_path" ]
-      deps = [ ":$target_name._gen($pw_protobuf_compiler_TOOLCHAIN)" ]
+      deps = [ ":$target_name._gen($default_toolchain)" ]
       public_deps = [ "$dir_pw_third_party/nanopb" ] + invoker.deps
       sources = invoker.outputs
       public = filter_include(sources, [ "*.pb.h" ])
@@ -267,14 +229,12 @@
   pw_source_set(target_name) {
     forward_variables_from(invoker, _forwarded_vars)
     public_configs = [ ":$target_name._include_path" ]
-    deps = [ ":$target_name._gen($pw_protobuf_compiler_TOOLCHAIN)" ]
+    deps = [ ":$target_name._gen($default_toolchain)" ]
     public_deps = [
                     "$dir_pw_rpc:server",
-                    "$dir_pw_rpc/raw:client_api",
-                    "$dir_pw_rpc/raw:server_api",
+                    "$dir_pw_rpc/raw:method_union",
                   ] + invoker.deps
     public = invoker.outputs
-    check_includes = false
   }
 }
 
@@ -299,8 +259,7 @@
   }
 
   group(target_name) {
-    deps =
-        invoker.deps + [ ":$target_name._gen($pw_protobuf_compiler_TOOLCHAIN)" ]
+    deps = invoker.deps + [ ":$target_name._gen($default_toolchain)" ]
   }
 }
 
@@ -312,42 +271,41 @@
     forward_variables_from(invoker, "*", _forwarded_vars + [ "python_package" ])
     language = "python"
     python_deps = [ "$dir_pw_protobuf_compiler:protobuf_requirements" ]
-
-    if (defined(invoker.python_package)) {
-      python_package = invoker.python_package
-    }
   }
 
   if (defined(invoker.python_package) && invoker.python_package != "") {
-    # This package is nested in another Python package. Depending on this
-    # its python subtarget is equivalent to depending on the Python package it
-    # is nested in.
-    pw_python_group(target_name) {
-      python_deps = [ invoker.python_package ]
-    }
+    # If nested in a Python package, write the package's name to a file so
+    # pw_python_package can check that the dependencies are correct.
+    write_file("${invoker.base_out_dir}/python_package.txt",
+               get_label_info(invoker.python_package, "label_no_toolchain"))
 
-    # This proto library is merged into another package, but create a target to
-    # collect its dependencies that the other package can depend on.
-    pw_python_group(target_name + "._deps") {
-      python_deps = invoker.deps
-      other_deps =
-          [ ":${invoker.target_name}._gen($pw_protobuf_compiler_TOOLCHAIN)" ]
+    # If anyone attempts to depend on this Python package, print an error.
+    pw_error(target_name) {
+      _pkg = get_label_info(invoker.python_package, "label_no_toolchain")
+      message_lines = [
+        "This proto Python package is embedded in the $_pkg Python package.",
+        "It cannot be used directly; instead, depend on $_pkg.",
+      ]
+    }
+    foreach(subtarget, pw_python_package_subtargets) {
+      group("$target_name.$subtarget") {
+        deps = [ ":${invoker.target_name}" ]
+      }
     }
   } else {
+    write_file("${invoker.base_out_dir}/python_package.txt", "")
+
     # Create a Python package with the generated source files.
     pw_python_package(target_name) {
       forward_variables_from(invoker, _forwarded_vars)
       generate_setup = {
-        metadata = {
-          name = invoker._package_dir
-          version =
-              "0.0.1"  # TODO(hepler): Need to be able to set this verison.
-        }
+        name = invoker._package_dir
+        version = "0.0.1"  # TODO(hepler): Need to be able to set this verison.
       }
       sources = invoker.outputs
       strip_prefix = "${invoker.base_out_dir}/python"
       python_deps = invoker.deps
-      other_deps = [ ":$target_name._gen($pw_protobuf_compiler_TOOLCHAIN)" ]
+      other_deps = [ ":$target_name._gen($default_toolchain)" ]
       static_analysis = []
 
       _pw_module_as_package = invoker.module_as_package != ""
@@ -382,7 +340,6 @@
 #   strip_prefix: Remove this prefix from the source protos. All source and
 #       input files must be nested under this path.
 #   python_package: Label of Python package to which to add the proto modules.
-#       The .python subtarget will redirect to this package.
 #
 template("pw_proto_library") {
   assert(defined(invoker.sources) && invoker.sources != [],
@@ -416,6 +373,25 @@
     _prefix = ""
   }
 
+  _common = {
+    base_target = target_name
+
+    # This is the output directory for all files related to this proto library.
+    # Sources are mirrored to "$base_out_dir/sources" and protoc puts outputs in
+    # "$base_out_dir/$language" by default.
+    base_out_dir =
+        get_label_info(":$target_name($default_toolchain)", "target_gen_dir") +
+        "/$target_name.proto_library"
+
+    compile_dir = "$base_out_dir/sources"
+
+    # Refer to the source files as the are mirrored to the output directory.
+    sources = []
+    foreach(file, rebase_path(invoker.sources, _source_root)) {
+      sources += [ "$compile_dir/$_prefix/$file" ]
+    }
+  }
+
   _package_dir = ""
   _source_names = []
 
@@ -471,27 +447,6 @@
     _deps = []
   }
 
-  _common = {
-    base_target = target_name
-
-    # This is the output directory for all files related to this proto library.
-    # Sources are mirrored to "$base_out_dir/sources" and protoc puts outputs in
-    # "$base_out_dir/$language" by default.
-    base_out_dir =
-        get_label_info(":$target_name($pw_protobuf_compiler_TOOLCHAIN)",
-                       "target_gen_dir") + "/$target_name.proto_library"
-
-    compile_dir = "$base_out_dir/sources"
-
-    # Refer to the source files as the are mirrored to the output directory.
-    sources = []
-    foreach(file, rebase_path(invoker.sources, _source_root)) {
-      sources += [ "$compile_dir/$_prefix/$file" ]
-    }
-
-    package = _package_dir
-  }
-
   # For each proto target, create a file which collects the base directories of
   # all of its dependencies to list as include paths to protoc.
   generated_file("$target_name._includes") {
@@ -508,24 +463,20 @@
 
     # Indicate this library's base directory for its dependents.
     metadata = {
-      protoc_includes = [ rebase_path(_common.compile_dir, root_build_dir) ]
+      protoc_includes = [ rebase_path(_common.compile_dir) ]
     }
   }
 
   # Mirror the proto sources to the output directory with the prefix added.
-  if (current_toolchain == pw_protobuf_compiler_TOOLCHAIN) {
-    pw_mirror_tree("$target_name._sources") {
-      source_root = _source_root
-      sources = invoker.sources
+  pw_mirror_tree("$target_name._sources") {
+    source_root = _source_root
+    sources = invoker.sources
 
-      if (defined(invoker.inputs)) {
-        sources += invoker.inputs
-      }
-
-      directory = "${_common.compile_dir}/$_prefix"
+    if (defined(invoker.inputs)) {
+      sources += invoker.inputs
     }
-  } else {
-    not_needed(invoker, [ "inputs" ])
+
+    directory = "${_common.compile_dir}/$_prefix"
   }
 
   # Enumerate all of the protobuf generator targets.
diff --git a/pw_protobuf_compiler/pw_nanopb_cc_library.bzl b/pw_protobuf_compiler/pw_nanopb_cc_library.bzl
deleted file mode 100644
index a5ae2d2..0000000
--- a/pw_protobuf_compiler/pw_nanopb_cc_library.bzl
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""WORK IN PROGRESS!
-
-Nanopb C++ library generating targets.
-"""
-
-# TODO(pwbug/621) Enable unused variable check.
-# buildifier: disable=unused-variable
-def pw_nanopb_cc_library(
-        name,
-        deps,
-        options = None,
-        **kwargs):
-    """Generates the nanopb C++ library.
-
-    deps: proto_library targets to convert using nanopb.
-    options: Path to the nanopb .options file. See
-      https://jpa.kapsi.fi/nanopb/docs/reference.html#proto-file-options
-      for the syntax.
-    """
-
-    # TODO(tpudlik): Implement this rule. Just a placeholder for now.
-    native.cc_library(
-        name = name,
-    )
diff --git a/pw_protobuf_compiler/pw_proto_library.bzl b/pw_protobuf_compiler/pw_proto_library.bzl
deleted file mode 100644
index f9a3048..0000000
--- a/pw_protobuf_compiler/pw_proto_library.bzl
+++ /dev/null
@@ -1,289 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""WORK IN PROGRESS!
-
-This is intended to be a replacement for the proto codegen in proto.bzl, which
-relies on the transitive proto compilation support removed from newer versions
-of rules_proto_grpc. However, the version checked in here does not yet support,
-
-1. Proto libraries with dependencies in external repositories.
-2. Proto libraries with strip_import_prefix or import_prefix attributes.
-
-In addition, nanopb proto files are not yet generated.
-
-TODO(pwbug/621): Close these gaps and start using this implementation.
-
-# Overview of implementation
-
-(If you just want to use pw_proto_library, see its docstring; this section is
-intended to orient future maintainers.)
-
-Proto code generation is carried out by the _pw_proto_library,
-_pw_raw_rpc_proto_library and _pw_nanopb_rpc_proto_library rules using aspects
-(https://docs.bazel.build/versions/main/skylark/aspects.html). A
-_pw_proto_library has a single proto_library as a dependency, but that
-proto_library may depend on other proto_library targets; as a result, the
-generated .pwpb.h file #include's .pwpb.h files generated from the dependency
-proto_libraries. The aspect propagates along the proto_library dependency
-graph, running the proto compiler on each proto_library in the original
-target's transitive dependencies, ensuring that we're not missing any .pwpb.h
-files at C++ compile time.
-
-Although we have a separate rule for each protocol compiler plugin
-(_pw_proto_library, _pw_raw_rpc_proto_library, _pw_nanopb_rpc_proto_library),
-they actually share an implementation (_pw _impl_pw_proto_library) and use
-similar aspects, all generated by _proto_compiler_aspect. The only difference
-between the rules are captured in the PIGWEED_PLUGIN dictonary and the aspect
-instantiations (_pw_proto_compiler_aspect, etc).
-
-"""
-
-load("//pw_build:pigweed.bzl", "pw_cc_library")
-load("@rules_proto//proto:defs.bzl", "ProtoInfo")
-load("//pw_protobuf_compiler:pw_nanopb_cc_library", "pw_nanopb_cc_library")
-
-def pw_proto_library(name = "", deps = [], nanopb_options = None):
-    """Generate Pigweed proto C++ code.
-
-    This is the only public symbol in this file: everything else is
-    implementation details.
-
-    Args:
-      name: The name of the target.
-      deps: proto_library targets from which to generate Pigweed C++.
-      nanopb_options: path to file containing nanopb options, if any
-        (https://jpa.kapsi.fi/nanopb/docs/reference.html#proto-file-options).
-
-    Example usage:
-
-      proto_library(
-        name = "benchmark_proto",
-        srcs = [
-          "benchmark.proto",
-        ],
-      )
-
-      pw_proto_library(
-        name = "benchmark_pw_proto",
-        deps = [":benchmark_proto"],
-      )
-
-      pw_cc_binary(
-        name = "proto_user",
-        srcs = ["proto_user.cc"],
-        deps = [":benchmark_pw_proto.pwpb"],
-      )
-
-    The pw_proto_library generates the following targets in this example:
-
-    "benchmark_pw_proto.pwpb": C++ library exposing the "benchmark.pwpb.h" header.
-    "benchmark_pw_proto.raw_rpc": C++ library exposing the "benchmark.raw_rpc.h"
-        header.
-    "benchmark_pw_proto.nanopb": C++ library exposing the "benchmark.pb.h"
-        header.
-    "benchmark_pw_proto.nanopb_rpc": C++ library exposing the
-        "benchmark.rpc.pb.h" header.
-    """
-
-    # Use nanopb to generate the pb.h and pb.c files, and the target exposing
-    # them.
-    pw_nanopb_cc_library(name + ".nanopb", deps, options = nanopb_options)
-
-    # Use Pigweed proto plugins to generate the remaining files and targets.
-    for plugin_name, info in PIGWEED_PLUGIN.items():
-        name_pb = name + "_pb." + plugin_name
-        info["compiler"](
-            name = name_pb,
-            deps = deps,
-        )
-
-        # The rpc.pb.h header depends on the generated nanopb code.
-        if info["include_nanopb_dep"]:
-            lib_deps = info["deps"] + [":" + name + ".nanopb"]
-        else:
-            lib_deps = info["deps"]
-
-        pw_cc_library(
-            name = name + "." + plugin_name,
-            hdrs = [name_pb],
-            deps = lib_deps,
-            linkstatic = 1,
-        )
-
-PwProtoInfo = provider(
-    "Returned by PW proto compilation aspect",
-    fields = {
-        "genfiles": "generated C++ header files",
-    },
-)
-
-def _get_short_path(source):
-    return source.short_path
-
-def _get_path(file):
-    return file.path
-
-def _proto_compiler_aspect_impl(target, ctx):
-    # List the files we will generate for this proto_library target.
-    genfiles = []
-    for src in target[ProtoInfo].direct_sources:
-        path = src.basename[:-len("proto")] + ctx.attr._extension
-        genfiles.append(ctx.actions.declare_file(path, sibling = src))
-
-    args = ctx.actions.args()
-    args.add("--plugin=protoc-gen-pwpb={}".format(ctx.executable._protoc_plugin.path))
-    args.add("--pwpb_out={}".format(ctx.bin_dir.path))
-    args.add_joined(
-        "--descriptor_set_in",
-        target[ProtoInfo].transitive_descriptor_sets,
-        join_with = ctx.host_configuration.host_path_separator,
-        map_each = _get_path,
-    )
-    args.add_all(target[ProtoInfo].direct_sources, map_each = _get_short_path)
-
-    ctx.actions.run(
-        inputs = depset(target[ProtoInfo].transitive_sources.to_list(), transitive = [target[ProtoInfo].transitive_descriptor_sets]),
-        progress_message = "Generating %s C++ files for %s" % (ctx.attr._extension, ctx.label.name),
-        tools = [ctx.executable._protoc_plugin],
-        outputs = genfiles,
-        executable = ctx.executable._protoc,
-        arguments = [args],
-    )
-
-    transitive_genfiles = genfiles
-    for dep in ctx.rule.attr.deps:
-        transitive_genfiles += dep[PwProtoInfo].genfiles
-    return [PwProtoInfo(genfiles = transitive_genfiles)]
-
-def _proto_compiler_aspect(extension, protoc_plugin):
-    """Returns an aspect that runs the proto compiler.
-
-    The aspect propagates through the deps of proto_library targets, running
-    the proto compiler with the specified plugin for each of their source
-    files. The proto compiler is assumed to produce one output file per input
-    .proto file. That file is placed under bazel-bin at the same path as the
-    input file, but with the specified extension (i.e., with _extension =
-    .pwpb.h, the aspect converts pw_log/log.proto into
-    bazel-bin/pw_log/log.pwpb.h).
-
-    The aspect returns a provider exposing all the File objects generated from
-    the dependency graph.
-    """
-    return aspect(
-        attr_aspects = ["deps"],
-        attrs = {
-            "_extension": attr.string(default = extension),
-            "_protoc": attr.label(
-                default = Label("@com_google_protobuf//:protoc"),
-                executable = True,
-                cfg = "host",
-            ),
-            "_protoc_plugin": attr.label(
-                default = Label(protoc_plugin),
-                executable = True,
-                cfg = "host",
-            ),
-        },
-        implementation = _proto_compiler_aspect_impl,
-    )
-
-def _impl_pw_proto_library(ctx):
-    """Implementation of the proto codegen rule.
-
-    The work of actually generating the code is done by the aspect, so here we
-    just gather up all the generated files and return them.
-    """
-
-    # Note that we don't distinguish between the files generated from the
-    # target, and the files generated from its dependencies. We return all of
-    # them together, and in pw_proto_library expose all of them as hdrs.
-    # Pigweed's plugins happen to only generate .h files, so this works, but
-    # strictly speaking we should expose only the files generated from the
-    # target itself in hdrs, and place the headers generated from dependencies
-    # in srcs. We don't perform layering_check in Pigweed, so this is not a big
-    # deal.
-    #
-    # TODO(pwbug/621): Tidy this up.
-    all_genfiles = []
-    for dep in ctx.attr.deps:
-        for f in dep[PwProtoInfo].genfiles:
-            all_genfiles.append(f)
-
-    return [DefaultInfo(files = depset(all_genfiles))]
-
-# Instantiate the aspects and rules for generating code using specific plugins.
-_pw_proto_compiler_aspect = _proto_compiler_aspect("pwpb.h", "//pw_protobuf/py:plugin")
-
-_pw_proto_library = rule(
-    implementation = _impl_pw_proto_library,
-    attrs = {
-        "deps": attr.label_list(
-            providers = [ProtoInfo],
-            aspects = [_pw_proto_compiler_aspect],
-        ),
-    },
-)
-
-_pw_raw_rpc_proto_compiler_aspect = _proto_compiler_aspect("raw_rpc.pb.h", "//pw_rpc/py:plugin_raw")
-
-_pw_raw_rpc_proto_library = rule(
-    implementation = _impl_pw_proto_library,
-    attrs = {
-        "deps": attr.label_list(
-            providers = [ProtoInfo],
-            aspects = [_pw_raw_rpc_proto_compiler_aspect],
-        ),
-    },
-)
-
-_pw_nanopb_rpc_proto_compiler_aspect = _proto_compiler_aspect("rpc.pb.h", "//pw_rpc/py:plugin_nanopb")
-
-_pw_nanopb_rpc_proto_library = rule(
-    implementation = _impl_pw_proto_library,
-    attrs = {
-        "deps": attr.label_list(
-            providers = [ProtoInfo],
-            aspects = [_pw_nanopb_rpc_proto_compiler_aspect],
-        ),
-    },
-)
-
-PIGWEED_PLUGIN = {
-    "pwpb": {
-        "compiler": _pw_proto_library,
-        "deps": [
-            "//pw_span",
-            "//pw_protobuf:pw_protobuf",
-        ],
-        "include_nanopb_dep": False,
-    },
-    "raw_rpc": {
-        "compiler": _pw_raw_rpc_proto_library,
-        "deps": [
-            "//pw_rpc",
-            "//pw_rpc/raw:client_api",
-            "//pw_rpc/raw:server_api",
-        ],
-        "include_nanopb_dep": False,
-    },
-    "nanopb_rpc": {
-        "compiler": _pw_nanopb_rpc_proto_library,
-        "deps": [
-            "//pw_rpc",
-            "//pw_rpc/nanopb:client_api",
-            "//pw_rpc/nanopb:server_api",
-        ],
-        "include_nanopb_dep": True,
-    },
-}
diff --git a/pw_protobuf_compiler/py/BUILD.bazel b/pw_protobuf_compiler/py/BUILD.bazel
deleted file mode 100644
index d2bdb60..0000000
--- a/pw_protobuf_compiler/py/BUILD.bazel
+++ /dev/null
@@ -1,61 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load("@rules_python//python:defs.bzl", "py_library")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-py_library(
-    name = "pw_protobuf_compiler",
-    srcs = [
-        "pw_protobuf_compiler/__init__.py",
-        "pw_protobuf_compiler/generate_protos.py",
-        "pw_protobuf_compiler/proto_target_invalid.py",
-        "pw_protobuf_compiler/python_protos.py",
-    ],
-    imports = ["."],
-)
-
-py_test(
-    name = "compiled_protos_test",
-    size = "small",
-    srcs = [
-        "compiled_protos_test.py",
-    ],
-    imports = [
-        # To import pw_protobuf_compiler_protos
-        "..",
-    ],
-    deps = [
-        ":pw_protobuf_compiler",
-        "//pw_protobuf_compiler:pw_protobuf_compiler_protos",
-    ],
-)
-
-# TODO(tonymd) Find out how to make protoc available in the bazel sandbox.
-# py_test(
-#     name = "python_protos_test",
-#     size = "small",
-#     srcs = [
-#         "python_protos_test.py",
-#     ],
-#     deps = [
-#         ":pw_protobuf_compiler",
-#     ],
-#     data = [
-#         "@com_google_protobuf//:protoc",
-#     ]
-# )
diff --git a/pw_protobuf_compiler/py/BUILD.gn b/pw_protobuf_compiler/py/BUILD.gn
index 0d8321e..687cb1a 100644
--- a/pw_protobuf_compiler/py/BUILD.gn
+++ b/pw_protobuf_compiler/py/BUILD.gn
@@ -18,11 +18,7 @@
 import("$dir_pw_third_party/nanopb/nanopb.gni")
 
 pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
+  setup = [ "setup.py" ]
   sources = [
     "pw_protobuf_compiler/__init__.py",
     "pw_protobuf_compiler/generate_protos.py",
diff --git a/pw_protobuf_compiler/py/pw_protobuf_compiler/generate_protos.py b/pw_protobuf_compiler/py/pw_protobuf_compiler/generate_protos.py
index add698f..10c0bb2 100644
--- a/pw_protobuf_compiler/py/pw_protobuf_compiler/generate_protos.py
+++ b/pw_protobuf_compiler/py/pw_protobuf_compiler/generate_protos.py
@@ -20,7 +20,7 @@
 import subprocess
 import sys
 import tempfile
-from typing import Callable, Dict, List, Optional, Tuple, Union
+from typing import Callable, Dict, Optional, Tuple, Union
 
 # Make sure dependencies are optional, since this script may be run when
 # installing Python package dependencies through GN.
@@ -151,9 +151,7 @@
 
     args.out_dir.mkdir(parents=True, exist_ok=True)
 
-    include_paths: List[str] = []
-    if args.include_file:
-        include_paths = [f'-I{line.strip()}' for line in args.include_file]
+    include_paths = [f'-I{line.strip()}' for line in args.include_file]
 
     wrapper_script: Optional[Path] = None
 
diff --git a/pw_protobuf_compiler/py/pw_protobuf_compiler/python_protos.py b/pw_protobuf_compiler/py/pw_protobuf_compiler/python_protos.py
index 905fe0d..9b6f6cd 100644
--- a/pw_protobuf_compiler/py/pw_protobuf_compiler/python_protos.py
+++ b/pw_protobuf_compiler/py/pw_protobuf_compiler/python_protos.py
@@ -67,7 +67,6 @@
 
 def _import_module(name: str, path: str) -> ModuleType:
     spec = importlib.util.spec_from_file_location(name, path)
-    assert spec is not None
     module = importlib.util.module_from_spec(spec)
     spec.loader.exec_module(module)  # type: ignore[union-attr]
     return module
diff --git a/pw_protobuf_compiler/py/pyproject.toml b/pw_protobuf_compiler/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/pw_protobuf_compiler/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/pw_protobuf_compiler/py/python_protos_test.py b/pw_protobuf_compiler/py/python_protos_test.py
index a6afaf9..7888c55 100755
--- a/pw_protobuf_compiler/py/python_protos_test.py
+++ b/pw_protobuf_compiler/py/python_protos_test.py
@@ -100,7 +100,6 @@
 
 
 class TestCompileAndImport(unittest.TestCase):
-    """Test compiling and importing."""
     def setUp(self):
         self._proto_dir = tempfile.TemporaryDirectory(prefix='proto_test')
         self._protos = []
diff --git a/pw_protobuf_compiler/py/setup.cfg b/pw_protobuf_compiler/py/setup.cfg
deleted file mode 100644
index 09b0f7b..0000000
--- a/pw_protobuf_compiler/py/setup.cfg
+++ /dev/null
@@ -1,34 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pw_protobuf_compiler
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Pigweed protoc wrapper
-
-[options]
-packages = find:
-zip_safe = False
-install_requires =
-    # NOTE: mypy needs to stay in sync with mypy-protobuf
-    # Currently using mypy 0.910 and mypy-protobuf 2.9
-    # This must also be specified in //pw_protobuf_compiler/BUILD.gn
-    mypy-protobuf==2.9
-    protobuf
-    pw_cli
-    types-protobuf
-
-[options.package_data]
-pw_protobuf_compiler = py.typed
diff --git a/pw_protobuf_compiler/py/setup.py b/pw_protobuf_compiler/py/setup.py
index ee28b22..b0d8657 100644
--- a/pw_protobuf_compiler/py/setup.py
+++ b/pw_protobuf_compiler/py/setup.py
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2019 The Pigweed Authors
 #
 # 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
@@ -15,4 +15,18 @@
 
 import setuptools  # type: ignore
 
-setuptools.setup()  # Package definition in setup.cfg
+setuptools.setup(
+    name='pw_protobuf_compiler',
+    version='0.0.1',
+    author='Pigweed Authors',
+    author_email='pigweed-developers@googlegroups.com',
+    description='Pigweed protoc wrapper',
+    packages=setuptools.find_packages(),
+    package_data={'pw_protobuf_compiler': ['py.typed']},
+    zip_safe=False,
+    install_requires=[
+        'mypy-protobuf',
+        'protobuf',
+        'pw_cli',
+    ],
+)
diff --git a/pw_protobuf_compiler/toolchain.gni b/pw_protobuf_compiler/toolchain.gni
deleted file mode 100644
index 606e69d..0000000
--- a/pw_protobuf_compiler/toolchain.gni
+++ /dev/null
@@ -1,23 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-declare_args() {
-  # Compiling protobufs involves mirroring .proto files to the output directory
-  # in a specific configuration and invoking protoc on them. This work is done
-  # in a single toolchain to avoid unnecessary duplication in the build.
-  pw_protobuf_compiler_TOOLCHAIN =
-      "$dir_pw_protobuf_compiler/toolchain:protocol_buffer"
-}
diff --git a/pw_protobuf_compiler/toolchain/BUILD.gn b/pw_protobuf_compiler/toolchain/BUILD.gn
deleted file mode 100644
index 6d2fd5b..0000000
--- a/pw_protobuf_compiler/toolchain/BUILD.gn
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_toolchain/non_c_toolchain.gni")
-
-# A toolchain used for compiling protocol buffers. This toolchain cannot compile
-# C/C++, and trying to use it to do so results in errors.
-pw_non_c_toolchain("protocol_buffer") {
-}
diff --git a/pw_protobuf_compiler/ts/BUILD.bazel b/pw_protobuf_compiler/ts/BUILD.bazel
deleted file mode 100644
index 020da8f..0000000
--- a/pw_protobuf_compiler/ts/BUILD.bazel
+++ /dev/null
@@ -1,70 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
-load("@npm//@bazel/jasmine:index.bzl", "jasmine_node_test")
-load("@npm//@bazel/typescript:index.bzl", "ts_library", "ts_project")
-load("ts_proto_collection.bzl", "ts_proto_collection")
-
-package(default_visibility = ["//visibility:public"])
-
-filegroup(
-    name = "ts_proto_collection_template",
-    srcs = ["ts_proto_collection.template.ts"],
-)
-
-ts_project(
-    name = "lib",
-    srcs = [
-        "index.ts",
-        "proto_collection.ts",
-    ],
-    declaration = True,
-    source_map = True,
-    deps = ["@npm//:node_modules"],  # can't use fine-grained deps
-)
-
-js_library(
-    name = "pw_protobuf_compiler",
-    package_name = "@pigweed/pw_protobuf_compiler",
-    srcs = ["package.json"],
-    deps = [":lib"],
-)
-
-ts_proto_collection(
-    name = "test_proto_collection",
-    js_proto_library = "//pw_protobuf_compiler:test_protos_tspb",
-    proto_library = "//pw_protobuf_compiler:test_protos",
-)
-
-ts_library(
-    name = "ts_proto_collection_test_lib",
-    srcs = [
-        "ts_proto_collection_test.ts",
-    ],
-    deps = [
-        ":test_proto_collection",
-        "//pw_protobuf_compiler:test_protos_tspb",
-        "//pw_rpc/ts:packet_proto_tspb",
-        "@npm//@types/google-protobuf",
-        "@npm//@types/jasmine",
-    ],
-)
-
-jasmine_node_test(
-    name = "ts_proto_collection_test",
-    srcs = [
-        ":ts_proto_collection_test_lib",
-    ],
-)
diff --git a/pw_protobuf_compiler/ts/codegen/BUILD.bazel b/pw_protobuf_compiler/ts/codegen/BUILD.bazel
deleted file mode 100644
index f1959cf..0000000
--- a/pw_protobuf_compiler/ts/codegen/BUILD.bazel
+++ /dev/null
@@ -1,40 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load("@npm//@bazel/typescript:index.bzl", "ts_library")
-load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary")
-
-package(default_visibility = ["//visibility:public"])
-
-ts_library(
-    name = "template_replacement_lib",
-    srcs = [
-        "template_replacement.ts",
-    ],
-    deps = [
-        "@//pw_rpc/ts:packet_proto_tspb",
-        "@npm//@types/argparse",
-        "@npm//@types/google-protobuf",
-        "@npm//@types/node",
-        "@npm//argparse",
-    ],
-)
-
-nodejs_binary(
-    name = "template_replacement_bin",
-    data = [
-        ":template_replacement_lib",
-    ],
-    entry_point = "template_replacement.ts",
-)
diff --git a/pw_protobuf_compiler/ts/codegen/template_replacement.bzl b/pw_protobuf_compiler/ts/codegen/template_replacement.bzl
deleted file mode 100644
index 41d62d0..0000000
--- a/pw_protobuf_compiler/ts/codegen/template_replacement.bzl
+++ /dev/null
@@ -1,61 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-"""Utility for generating TS code with string replacement."""
-
-load("@build_bazel_rules_nodejs//:providers.bzl", "run_node")
-
-def _template_replacement_impl(ctx):
-    output_file = ctx.actions.declare_file(ctx.attr.output_file)
-    descriptor_data = ctx.files.descriptor_data[0]
-    template_file = ctx.files.template_file[0]
-
-    run_node(
-        ctx,
-        executable = "_template_replacement_bin",
-        inputs = [descriptor_data, template_file],
-        outputs = [output_file],
-        arguments = [
-            "--template",
-            template_file.path,
-            "--descriptor_data",
-            descriptor_data.path,
-            "--output",
-            output_file.path,
-            "--proto_root_dir",
-            ctx.attr.proto_root_dir,
-        ],
-    )
-
-    return [DefaultInfo(files = depset([output_file]))]
-
-template_replacement = rule(
-    implementation = _template_replacement_impl,
-    attrs = {
-        "_template_replacement_bin": attr.label(
-            executable = True,
-            cfg = "exec",
-            default = Label("@//pw_protobuf_compiler/ts/codegen:template_replacement_bin"),
-        ),
-        "descriptor_data": attr.label(
-            allow_files = [".proto.bin"],
-        ),
-        "proto_root_dir": attr.string(mandatory = True),
-        "output_file": attr.string(mandatory = True),
-        "template_file": attr.label(
-            allow_files = [".ts"],
-            default = Label("@//pw_protobuf_compiler/ts:ts_proto_collection_template"),
-        ),
-    },
-)
diff --git a/pw_protobuf_compiler/ts/codegen/template_replacement.ts b/pw_protobuf_compiler/ts/codegen/template_replacement.ts
deleted file mode 100644
index 3118651..0000000
--- a/pw_protobuf_compiler/ts/codegen/template_replacement.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-#!/usr/bin/env node
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 * as fs from 'fs';
-import {ArgumentParser} from 'argparse';
-import {FileDescriptorSet} from 'google-protobuf/google/protobuf/descriptor_pb';
-// import {Message} from 'google-protobuf';
-
-const parser = new ArgumentParser({});
-parser.add_argument('--output', {
-  action: 'store',
-  required: true,
-  type: String,
-});
-parser.add_argument('--descriptor_data', {
-  action: 'store',
-  required: true,
-  type: String,
-});
-parser.add_argument('--template', {
-  action: 'store',
-  required: true,
-  type: String,
-});
-parser.add_argument('--proto_root_dir', {
-  action: 'store',
-  required: true,
-  type: String,
-});
-
-const args = parser.parse_args();
-let template = fs.readFileSync(args.template).toString();
-
-function buildModulePath(rootDir: string, fileName: string): string {
-  const name = `${rootDir}/${fileName}`;
-  return name.replace(/\.proto$/, '_pb');
-}
-
-const descriptorSetBinary = fs.readFileSync(args.descriptor_data);
-const base64DescriptorSet = descriptorSetBinary.toString('base64');
-const fileDescriptorSet = FileDescriptorSet.deserializeBinary(
-  new Buffer(descriptorSetBinary)
-);
-
-const imports = [];
-const moduleDictionary = [];
-const fileList = fileDescriptorSet.getFileList();
-for (let i = 0; i < fileList.length; i++) {
-  const file = fileList[i];
-  const modulePath = buildModulePath(args.proto_root_dir, file.getName()!);
-  const moduleName = 'proto_' + i;
-  imports.push(`import * as ${moduleName} from '${modulePath}';`);
-  const key = file.getName()!;
-  moduleDictionary.push(`['${key}', ${moduleName}],`);
-}
-
-template = template.replace(
-  '{TEMPLATE_descriptor_binary}',
-  base64DescriptorSet
-);
-template = template.replace('// TEMPLATE_proto_imports', imports.join('\n'));
-template = template.replace(
-  '// TEMPLATE_module_map',
-  moduleDictionary.join('\n')
-);
-
-fs.writeFileSync(args.output, template);
diff --git a/pw_protobuf_compiler/ts/index.ts b/pw_protobuf_compiler/ts/index.ts
deleted file mode 100644
index 6553fa3..0000000
--- a/pw_protobuf_compiler/ts/index.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-export * from './proto_collection';
diff --git a/pw_protobuf_compiler/ts/package.json b/pw_protobuf_compiler/ts/package.json
deleted file mode 100644
index 8cc2e08..0000000
--- a/pw_protobuf_compiler/ts/package.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
-  "name": "@pigweed/pw_protobuf_compiler",
-  "version": "1.0.0",
-  "main": "index.js",
-  "license": "Apache-2.0",
-  "dependencies": {
-    "@bazel/jasmine": "^4.1.0",
-    "@types/argparse": "^2.0.10",
-    "@types/google-protobuf": "^3.15.5",
-    "@types/jasmine": "^3.9.0",
-    "@types/node": "^16.10.2",
-    "argparse": "^2.0.1",
-    "base64-js": "^1.5.1",
-    "google-protobuf": "^3.19.0",
-    "jasmine": "^3.9.0",
-    "jasmine-core": "^3.9.0"
-  }
-}
diff --git a/pw_protobuf_compiler/ts/proto_collection.ts b/pw_protobuf_compiler/ts/proto_collection.ts
deleted file mode 100644
index 74a9365..0000000
--- a/pw_protobuf_compiler/ts/proto_collection.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-/** Tools for compiling and importing Javascript protos on the fly. */
-
-import {Message} from 'google-protobuf';
-import {FileDescriptorSet} from 'google-protobuf/google/protobuf/descriptor_pb';
-
-export type MessageCreator = new () => Message;
-class MessageMap extends Map<string, MessageCreator> {}
-export class ModuleMap extends Map<string, any> {}
-
-/**
- * A wrapper class of protocol buffer modules to provide convenience methods.
- */
-export class ProtoCollection {
-  private messages: MessageMap;
-
-  constructor(
-    readonly fileDescriptorSet: FileDescriptorSet,
-    modules: ModuleMap
-  ) {
-    this.messages = this.mapMessages(fileDescriptorSet, modules);
-  }
-
-  /**
-   * Creates a map between message identifier "{packageName}.{messageName}"
-   * and the Message class.
-   */
-  private mapMessages(set: FileDescriptorSet, mods: ModuleMap): MessageMap {
-    const messages = new MessageMap();
-    for (const fileDescriptor of set.getFileList()) {
-      const mod = mods.get(fileDescriptor.getName()!)!;
-      for (const messageType of fileDescriptor.getMessageTypeList()) {
-        const fullName =
-          fileDescriptor.getPackage()! + '.' + messageType.getName();
-        const message = mod[messageType.getName()!];
-        messages.set(fullName, message);
-      }
-    }
-    return messages;
-  }
-
-  /**
-   * Finds the Message class referenced by the identifier.
-   *
-   *  @param identifier String identifier of the form
-   *  "{packageName}.{messageName}" i.e: "pw.rpc.test.NewMessage".
-   */
-  getMessageCreator(identifier: string): MessageCreator | undefined {
-    return this.messages.get(identifier);
-  }
-}
diff --git a/pw_protobuf_compiler/ts/ts_proto_collection.bzl b/pw_protobuf_compiler/ts/ts_proto_collection.bzl
deleted file mode 100644
index fe9af95..0000000
--- a/pw_protobuf_compiler/ts/ts_proto_collection.bzl
+++ /dev/null
@@ -1,59 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-"""Builds a proto collection."""
-
-load("@npm//@bazel/typescript:index.bzl", "ts_library")
-load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
-load("@//pw_protobuf_compiler/ts/codegen:template_replacement.bzl", "template_replacement")
-
-def _lib(name, proto_library, js_proto_library):
-    js_proto_library_name = Label(js_proto_library).name
-    proto_root_dir = js_proto_library_name + "/" + js_proto_library_name + "_pb"
-    template_name = name + "_template"
-    template_replacement(
-        name = template_name,
-        descriptor_data = proto_library,
-        proto_root_dir = proto_root_dir,
-        output_file = "generated/ts_proto_collection.ts",
-    )
-
-    ts_library(
-        name = name + "_lib",
-        package_name = name,
-        module_name = name,
-        srcs = [template_name],
-        deps = [
-            js_proto_library,
-            "@//pw_protobuf_compiler/ts:pw_protobuf_compiler",
-            "@//pw_rpc/ts:packet_proto_tspb",
-            "@npm//@types/google-protobuf",
-            "@npm//@types/node",
-            "@npm//base64-js",
-        ],
-    )
-
-    native.filegroup(
-        name = name + "_esm",
-        srcs = [name + "_lib"],
-        output_group = "es6_sources",
-    )
-
-def ts_proto_collection(name, proto_library, js_proto_library):
-    _lib(name, proto_library, js_proto_library)
-    js_library(
-        name = name,
-        package_name = "@pigweed/" + name + "/pw_protobuf_compiler",
-        deps = [name + "_lib", name + "_esm"],
-    )
diff --git a/pw_protobuf_compiler/ts/ts_proto_collection.template.ts b/pw_protobuf_compiler/ts/ts_proto_collection.template.ts
deleted file mode 100644
index 7ec60fd..0000000
--- a/pw_protobuf_compiler/ts/ts_proto_collection.template.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-/** Tools for compiling and importing Javascript protos on the fly. */
-
-import {
-  ProtoCollection as Base,
-  ModuleMap,
-} from '@pigweed/pw_protobuf_compiler';
-import {FileDescriptorSet} from 'google-protobuf/google/protobuf/descriptor_pb';
-import * as base64 from 'base64-js';
-
-// Generated proto imports added during build
-// TEMPLATE_proto_imports
-
-const MODULE_MAP = new ModuleMap([
-  // TEMPLATE_module_map
-]);
-
-const DESCRIPTOR_BASE64_BINARY = '{TEMPLATE_descriptor_binary}';
-
-/**
- * A wrapper class of protocol buffer modules to provide convenience methods.
- */
-export class ProtoCollection extends Base {
-  constructor() {
-    const fileDescriptorSet = FileDescriptorSet.deserializeBinary(
-      base64.toByteArray(DESCRIPTOR_BASE64_BINARY)
-    );
-    super(fileDescriptorSet, MODULE_MAP);
-  }
-}
diff --git a/pw_protobuf_compiler/ts/ts_proto_collection_test.ts b/pw_protobuf_compiler/ts/ts_proto_collection_test.ts
deleted file mode 100644
index c20d766..0000000
--- a/pw_protobuf_compiler/ts/ts_proto_collection_test.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-/* eslint-env browser, jasmine */
-import 'jasmine';
-
-import {Message} from 'test_protos_tspb/test_protos_tspb_pb/pw_protobuf_compiler/pw_protobuf_compiler_protos/nested/more_nesting/test_pb';
-
-import {ProtoCollection} from 'test_proto_collection/generated/ts_proto_collection';
-
-describe('ProtoCollection', () => {
-  it('getMessageType returns message', () => {
-    const lib = new ProtoCollection();
-
-    const fetched = lib.getMessageCreator('pw.protobuf_compiler.test.Message');
-    expect(fetched).toEqual(Message);
-  });
-
-  it('getMessageType for invalid identifier returns undefined', () => {
-    const lib = new ProtoCollection();
-
-    let fetched = lib.getMessageCreator('pw');
-    expect(fetched).toBeUndefined();
-    fetched = lib.getMessageCreator('pw.test1.Garbage');
-    expect(fetched).toBeUndefined();
-  });
-});
diff --git a/pw_protobuf_compiler/ts/tsconfig.json b/pw_protobuf_compiler/ts/tsconfig.json
deleted file mode 100644
index 4ddd637..0000000
--- a/pw_protobuf_compiler/ts/tsconfig.json
+++ /dev/null
@@ -1,34 +0,0 @@
-{
-  "compilerOptions": {
-    "allowUnreachableCode": false,
-    "allowUnusedLabels": false,
-    "declaration": true,
-    "forceConsistentCasingInFileNames": true,
-    "lib": [
-      "es2018",
-      "dom",
-      "dom.iterable",
-      "esnext"
-    ],
-    "module": "commonjs",
-    "noEmitOnError": true,
-    "noFallthroughCasesInSwitch": true,
-    "noImplicitReturns": true,
-    "pretty": true,
-    "sourceMap": true,
-    "strict": true,
-    "target": "es2018",
-    "jsx": "react",
-    "plugins": [
-      {
-        "name": "@bazel/tsetse",
-        "disabledRules": [
-          "must-use-promises"
-        ]
-      }
-    ]
-  },
-  "exclude": [
-    "node_modules"
-  ]
-}
diff --git a/pw_protobuf_compiler/ts/yarn.lock b/pw_protobuf_compiler/ts/yarn.lock
deleted file mode 100644
index 50afb2b..0000000
--- a/pw_protobuf_compiler/ts/yarn.lock
+++ /dev/null
@@ -1,497 +0,0 @@
-# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
-# yarn lockfile v1
-
-
-"@bazel/jasmine@^4.1.0":
-  version "4.4.0"
-  resolved "https://registry.yarnpkg.com/@bazel/jasmine/-/jasmine-4.4.0.tgz#4f3ec3e1cc62824c9ed222e937c6ad368be22b28"
-  integrity sha512-GkpRvD6Z880g6AsLzoc/UPTPdaXsGzAUGWGefGHqWsO+tBCWmi2V8WL0yc0N6edu6QLi4EI0fxN9obH1FsU0MQ==
-  dependencies:
-    c8 "~7.5.0"
-    jasmine-reporters "~2.4.0"
-
-"@bcoe/v8-coverage@^0.2.3":
-  version "0.2.3"
-  resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
-  integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
-
-"@istanbuljs/schema@^0.1.2":
-  version "0.1.3"
-  resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98"
-  integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==
-
-"@types/argparse@^2.0.10":
-  version "2.0.10"
-  resolved "https://registry.yarnpkg.com/@types/argparse/-/argparse-2.0.10.tgz#664e84808accd1987548d888b9d21b3e9c996a6c"
-  integrity sha512-C4wahC3gz3vQtvPazrJ5ONwmK1zSDllQboiWvpMM/iOswCYfBREFnjFbq/iWKIVOCl8+m5Pk6eva6/ZSsDuIGA==
-
-"@types/google-protobuf@^3.15.5":
-  version "3.15.5"
-  resolved "https://registry.yarnpkg.com/@types/google-protobuf/-/google-protobuf-3.15.5.tgz#644b2be0f5613b1f822c70c73c6b0e0b5b5fa2ad"
-  integrity sha512-6bgv24B+A2bo9AfzReeg5StdiijKzwwnRflA8RLd1V4Yv995LeTmo0z69/MPbBDFSiZWdZHQygLo/ccXhMEDgw==
-
-"@types/is-windows@^1.0.0":
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/@types/is-windows/-/is-windows-1.0.0.tgz#1011fa129d87091e2f6faf9042d6704cdf2e7be0"
-  integrity sha512-tJ1rq04tGKuIJoWIH0Gyuwv4RQ3+tIu7wQrC0MV47raQ44kIzXSSFKfrxFUOWVRvesoF7mrTqigXmqoZJsXwTg==
-
-"@types/istanbul-lib-coverage@^2.0.1":
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
-  integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==
-
-"@types/jasmine@^3.9.0":
-  version "3.10.0"
-  resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.10.0.tgz#b55e7184102ef320c4e8fb3d64ebfac309f60bf7"
-  integrity sha512-sPHWB05cYGt7GXFkkn+03VL1533abxiA5bE8PKdr0nS3cEsOXCGjMk0sgqVwY6xkiwajoAiN3zc/7zDeXip3Pw==
-
-"@types/node@^16.10.2":
-  version "16.11.3"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.3.tgz#fad0b069ec205b0e81429c805d306d2c12e26be1"
-  integrity sha512-aIYL9Eemcecs1y77XzFGiSc+FdfN58k4J23UEe6+hynf4Wd9g4DzQPwIKL080vSMuubFqy2hWwOzCtJdc6vFKw==
-
-ansi-regex@^5.0.1:
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
-  integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
-
-ansi-styles@^4.0.0:
-  version "4.3.0"
-  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
-  integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
-  dependencies:
-    color-convert "^2.0.1"
-
-argparse@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
-  integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
-
-balanced-match@^1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
-  integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
-
-base64-js@^1.5.1:
-  version "1.5.1"
-  resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
-  integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
-
-brace-expansion@^1.1.7:
-  version "1.1.11"
-  resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
-  integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
-  dependencies:
-    balanced-match "^1.0.0"
-    concat-map "0.0.1"
-
-c8@~7.5.0:
-  version "7.5.0"
-  resolved "https://registry.yarnpkg.com/c8/-/c8-7.5.0.tgz#a69439ab82848f344a74bb25dc5dd4e867764481"
-  integrity sha512-GSkLsbvDr+FIwjNSJ8OwzWAyuznEYGTAd1pzb/Kr0FMLuV4vqYJTyjboDTwmlUNAG6jAU3PFWzqIdKrOt1D8tw==
-  dependencies:
-    "@bcoe/v8-coverage" "^0.2.3"
-    "@istanbuljs/schema" "^0.1.2"
-    find-up "^5.0.0"
-    foreground-child "^2.0.0"
-    furi "^2.0.0"
-    istanbul-lib-coverage "^3.0.0"
-    istanbul-lib-report "^3.0.0"
-    istanbul-reports "^3.0.2"
-    rimraf "^3.0.0"
-    test-exclude "^6.0.0"
-    v8-to-istanbul "^7.1.0"
-    yargs "^16.0.0"
-    yargs-parser "^20.0.0"
-
-cliui@^7.0.2:
-  version "7.0.4"
-  resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
-  integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
-  dependencies:
-    string-width "^4.2.0"
-    strip-ansi "^6.0.0"
-    wrap-ansi "^7.0.0"
-
-color-convert@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
-  integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
-  dependencies:
-    color-name "~1.1.4"
-
-color-name@~1.1.4:
-  version "1.1.4"
-  resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
-  integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
-
-concat-map@0.0.1:
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
-  integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
-
-convert-source-map@^1.6.0:
-  version "1.8.0"
-  resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369"
-  integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==
-  dependencies:
-    safe-buffer "~5.1.1"
-
-cross-spawn@^7.0.0:
-  version "7.0.3"
-  resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
-  integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
-  dependencies:
-    path-key "^3.1.0"
-    shebang-command "^2.0.0"
-    which "^2.0.1"
-
-emoji-regex@^8.0.0:
-  version "8.0.0"
-  resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
-  integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
-
-escalade@^3.1.1:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
-  integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
-
-find-up@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
-  integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
-  dependencies:
-    locate-path "^6.0.0"
-    path-exists "^4.0.0"
-
-foreground-child@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-2.0.0.tgz#71b32800c9f15aa8f2f83f4a6bd9bff35d861a53"
-  integrity sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==
-  dependencies:
-    cross-spawn "^7.0.0"
-    signal-exit "^3.0.2"
-
-fs.realpath@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
-  integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
-
-furi@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/furi/-/furi-2.0.0.tgz#13d85826a1af21acc691da6254b3888fc39f0b4a"
-  integrity sha512-uKuNsaU0WVaK/vmvj23wW1bicOFfyqSsAIH71bRZx8kA4Xj+YCHin7CJKJJjkIsmxYaPFLk9ljmjEyB7xF7WvQ==
-  dependencies:
-    "@types/is-windows" "^1.0.0"
-    is-windows "^1.0.2"
-
-get-caller-file@^2.0.5:
-  version "2.0.5"
-  resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
-  integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
-
-glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
-  integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
-  dependencies:
-    fs.realpath "^1.0.0"
-    inflight "^1.0.4"
-    inherits "2"
-    minimatch "^3.0.4"
-    once "^1.3.0"
-    path-is-absolute "^1.0.0"
-
-google-protobuf@^3.19.0:
-  version "3.19.0"
-  resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.19.0.tgz#97f474323c92f19fd6737af1bb792e396991e0b8"
-  integrity sha512-qXGAiv3OOlaJXJNeKOBKxbBAwjsxzhx+12ZdKOkZTsqsRkyiQRmr/nBkAkqnuQ8cmA9X5NVXvObQTpHVnXE2DQ==
-
-has-flag@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
-  integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
-
-html-escaper@^2.0.0:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
-  integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
-
-inflight@^1.0.4:
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
-  integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
-  dependencies:
-    once "^1.3.0"
-    wrappy "1"
-
-inherits@2:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
-  integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
-
-is-fullwidth-code-point@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
-  integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
-
-is-windows@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
-  integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
-
-isexe@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
-  integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
-
-istanbul-lib-coverage@^3.0.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3"
-  integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==
-
-istanbul-lib-report@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6"
-  integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==
-  dependencies:
-    istanbul-lib-coverage "^3.0.0"
-    make-dir "^3.0.0"
-    supports-color "^7.1.0"
-
-istanbul-reports@^3.0.2:
-  version "3.0.5"
-  resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.5.tgz#a2580107e71279ea6d661ddede929ffc6d693384"
-  integrity sha512-5+19PlhnGabNWB7kOFnuxT8H3T/iIyQzIbQMxXsURmmvKg86P2sbkrGOT77VnHw0Qr0gc2XzRaRfMZYYbSQCJQ==
-  dependencies:
-    html-escaper "^2.0.0"
-    istanbul-lib-report "^3.0.0"
-
-jasmine-core@^3.9.0, jasmine-core@~3.10.0:
-  version "3.10.0"
-  resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.10.0.tgz#1e3e4053d954eb6d0bfbb3eb859bb21a5655de45"
-  integrity sha512-XWGaJ25RUdOQnjGiLoQa9QG/R4u1e9Bk4uhLdn9F4JCBco84L4SKM52bxci4vWTSUzhmhuHNAkAHFN/6Cox9wQ==
-
-jasmine-reporters@~2.4.0:
-  version "2.4.0"
-  resolved "https://registry.yarnpkg.com/jasmine-reporters/-/jasmine-reporters-2.4.0.tgz#708c17ae70ba6671e3a930bb1b202aab80a31409"
-  integrity sha512-jxONSrBLN1vz/8zCx5YNWQSS8iyDAlXQ5yk1LuqITe4C6iXCDx5u6Q0jfNtkKhL4qLZPe69fL+AWvXFt9/x38w==
-  dependencies:
-    mkdirp "^0.5.1"
-    xmldom "^0.5.0"
-
-jasmine@^3.9.0:
-  version "3.10.0"
-  resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-3.10.0.tgz#acd3cd560a9d20d8fdad6bd2dd05867d188503f3"
-  integrity sha512-2Y42VsC+3CQCTzTwJezOvji4qLORmKIE0kwowWC+934Krn6ZXNQYljiwK5st9V3PVx96BSiDYXSB60VVah3IlQ==
-  dependencies:
-    glob "^7.1.6"
-    jasmine-core "~3.10.0"
-
-locate-path@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
-  integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
-  dependencies:
-    p-locate "^5.0.0"
-
-make-dir@^3.0.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
-  integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
-  dependencies:
-    semver "^6.0.0"
-
-minimatch@^3.0.4:
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
-  integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
-  dependencies:
-    brace-expansion "^1.1.7"
-
-minimist@^1.2.5:
-  version "1.2.5"
-  resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
-  integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
-
-mkdirp@^0.5.1:
-  version "0.5.5"
-  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
-  integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
-  dependencies:
-    minimist "^1.2.5"
-
-once@^1.3.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
-  integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
-  dependencies:
-    wrappy "1"
-
-p-limit@^3.0.2:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
-  integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
-  dependencies:
-    yocto-queue "^0.1.0"
-
-p-locate@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
-  integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
-  dependencies:
-    p-limit "^3.0.2"
-
-path-exists@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
-  integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
-
-path-is-absolute@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
-  integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
-
-path-key@^3.1.0:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
-  integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
-
-require-directory@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
-  integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
-
-rimraf@^3.0.0:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
-  integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
-  dependencies:
-    glob "^7.1.3"
-
-safe-buffer@~5.1.1:
-  version "5.1.2"
-  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
-  integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
-
-semver@^6.0.0:
-  version "6.3.0"
-  resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
-  integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
-
-shebang-command@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
-  integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
-  dependencies:
-    shebang-regex "^3.0.0"
-
-shebang-regex@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
-  integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
-
-signal-exit@^3.0.2:
-  version "3.0.5"
-  resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.5.tgz#9e3e8cc0c75a99472b44321033a7702e7738252f"
-  integrity sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==
-
-source-map@^0.7.3:
-  version "0.7.3"
-  resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
-  integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
-
-string-width@^4.1.0, string-width@^4.2.0:
-  version "4.2.3"
-  resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
-  integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
-  dependencies:
-    emoji-regex "^8.0.0"
-    is-fullwidth-code-point "^3.0.0"
-    strip-ansi "^6.0.1"
-
-strip-ansi@^6.0.0, strip-ansi@^6.0.1:
-  version "6.0.1"
-  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
-  integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
-  dependencies:
-    ansi-regex "^5.0.1"
-
-supports-color@^7.1.0:
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
-  integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
-  dependencies:
-    has-flag "^4.0.0"
-
-test-exclude@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e"
-  integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==
-  dependencies:
-    "@istanbuljs/schema" "^0.1.2"
-    glob "^7.1.4"
-    minimatch "^3.0.4"
-
-v8-to-istanbul@^7.1.0:
-  version "7.1.2"
-  resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz#30898d1a7fa0c84d225a2c1434fb958f290883c1"
-  integrity sha512-TxNb7YEUwkLXCQYeudi6lgQ/SZrzNO4kMdlqVxaZPUIUjCv6iSSypUQX70kNBSERpQ8fk48+d61FXk+tgqcWow==
-  dependencies:
-    "@types/istanbul-lib-coverage" "^2.0.1"
-    convert-source-map "^1.6.0"
-    source-map "^0.7.3"
-
-which@^2.0.1:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
-  integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
-  dependencies:
-    isexe "^2.0.0"
-
-wrap-ansi@^7.0.0:
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
-  integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
-  dependencies:
-    ansi-styles "^4.0.0"
-    string-width "^4.1.0"
-    strip-ansi "^6.0.0"
-
-wrappy@1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
-  integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
-
-xmldom@^0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.5.0.tgz#193cb96b84aa3486127ea6272c4596354cb4962e"
-  integrity sha512-Foaj5FXVzgn7xFzsKeNIde9g6aFBxTPi37iwsno8QvApmtg7KYrr+OPyRHcJF7dud2a5nGRBXK3n0dL62Gf7PA==
-
-y18n@^5.0.5:
-  version "5.0.8"
-  resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
-  integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
-
-yargs-parser@^20.0.0, yargs-parser@^20.2.2:
-  version "20.2.9"
-  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
-  integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
-
-yargs@^16.0.0:
-  version "16.2.0"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
-  integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
-  dependencies:
-    cliui "^7.0.2"
-    escalade "^3.1.1"
-    get-caller-file "^2.0.5"
-    require-directory "^2.1.1"
-    string-width "^4.2.0"
-    y18n "^5.0.5"
-    yargs-parser "^20.2.2"
-
-yocto-queue@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
-  integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
diff --git a/pw_random/BUILD b/pw_random/BUILD
new file mode 100644
index 0000000..d571850
--- /dev/null
+++ b/pw_random/BUILD
@@ -0,0 +1,41 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_random",
+    hdrs = [
+        "public/pw_random/random.h",
+        "public/pw_random/xor_shift.h",
+    ],
+    includes = ["public"],
+)
+
+pw_cc_test(
+    name = "xor_shift_test",
+    srcs = ["xor_shift_test.cc"],
+    deps = [
+        ":pw_random",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_random/BUILD.bazel b/pw_random/BUILD.bazel
deleted file mode 100644
index e41d610..0000000
--- a/pw_random/BUILD.bazel
+++ /dev/null
@@ -1,42 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_random",
-    hdrs = [
-        "public/pw_random/random.h",
-        "public/pw_random/xor_shift.h",
-    ],
-    includes = ["public"],
-)
-
-pw_cc_test(
-    name = "xor_shift_test",
-    srcs = ["xor_shift_test.cc"],
-    deps = [
-        ":pw_random",
-        "//pw_bytes",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_random/CMakeLists.txt b/pw_random/CMakeLists.txt
index 776cccd..aabdd23 100644
--- a/pw_random/CMakeLists.txt
+++ b/pw_random/CMakeLists.txt
@@ -14,25 +14,9 @@
 
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
 
-pw_add_module_library(pw_random
-  HEADERS
-    public/pw_random/random.h
-    public/pw_random/xor_shift.h
-  PUBLIC_INCLUDES
-    public
+pw_auto_add_simple_module(pw_random
   PUBLIC_DEPS
     pw_bytes
-    pw_polyfill.cstddef
-    pw_polyfill.span
+    pw_span
     pw_status
 )
-
-pw_add_test(pw_random.xor_shift_star_test
-  SOURCES
-    xor_shift_test.cc
-  DEPS
-    pw_random
-  GROUPS
-    modules
-    pw_random
-)
diff --git a/pw_random/OWNERS b/pw_random/OWNERS
deleted file mode 100644
index 307b1de..0000000
--- a/pw_random/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-amontanez@google.com
diff --git a/pw_result/BUILD b/pw_result/BUILD
new file mode 100644
index 0000000..e2d6b59
--- /dev/null
+++ b/pw_result/BUILD
@@ -0,0 +1,44 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_result",
+    hdrs = [
+        "public/pw_result/result.h",
+    ],
+    includes = ["public"],
+    deps = [
+        "//pw_assert",
+        "//pw_status",
+    ],
+)
+
+pw_cc_test(
+    name = "result_test",
+    srcs = ["result_test.cc"],
+    deps = [
+        ":pw_result",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_result/BUILD.bazel b/pw_result/BUILD.bazel
deleted file mode 100644
index e5b05c2..0000000
--- a/pw_result/BUILD.bazel
+++ /dev/null
@@ -1,54 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_result",
-    srcs = ["public/pw_result/internal/result_internal.h"],
-    hdrs = ["public/pw_result/result.h"],
-    includes = ["public"],
-    deps = [
-        "//pw_assert:facade",
-        "//pw_status",
-    ],
-)
-
-pw_cc_test(
-    name = "result_test",
-    srcs = ["result_test.cc"],
-    deps = [
-        ":pw_result",
-        "//pw_status",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "statusor_test",
-    srcs = ["statusor_test.cc"],
-    deps = [
-        ":pw_result",
-        "//pw_status",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_result/BUILD.gn b/pw_result/BUILD.gn
index 1fac1ad..31cd575 100644
--- a/pw_result/BUILD.gn
+++ b/pw_result/BUILD.gn
@@ -19,19 +19,17 @@
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_unit_test/test.gni")
 
-config("public_include_path") {
+config("default_config") {
   include_dirs = [ "public" ]
 }
 
 pw_source_set("pw_result") {
-  public_configs = [ ":public_include_path" ]
+  public_configs = [ ":default_config" ]
   public_deps = [
     "$dir_pw_assert",
-    "$dir_pw_preprocessor",
     "$dir_pw_status",
   ]
   public = [ "public/pw_result/result.h" ]
-  sources = [ "public/pw_result/internal/result_internal.h" ]
 }
 
 pw_test_group("tests") {
@@ -39,14 +37,8 @@
 }
 
 pw_test("result_test") {
-  deps = [
-    ":pw_result",
-    dir_pw_status,
-  ]
-  sources = [
-    "result_test.cc",
-    "statusor_test.cc",
-  ]
+  deps = [ ":pw_result" ]
+  sources = [ "result_test.cc" ]
 }
 
 pw_doc_group("docs") {
diff --git a/pw_result/CMakeLists.txt b/pw_result/CMakeLists.txt
index 300d12b..20b5db0 100644
--- a/pw_result/CMakeLists.txt
+++ b/pw_result/CMakeLists.txt
@@ -14,27 +14,8 @@
 
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
 
-pw_add_module_library(pw_result
-  HEADERS
-    public/pw_result/result.h
-  PUBLIC_INCLUDES
-    public
+pw_auto_add_simple_module(pw_result
   PUBLIC_DEPS
     pw_assert
     pw_status
-    pw_preprocessor
-)
-if(Zephyr_FOUND AND CONFIG_PIGWEED_RESULT)
-  zephyr_link_libraries(pw_result)
-endif()
-
-pw_add_test(pw_result.result_test
-  SOURCES
-    result_test.cc
-  DEPS
-    pw_result
-    pw_status
-  GROUPS
-    modules
-    pw_result
 )
diff --git a/pw_result/Kconfig b/pw_result/Kconfig
deleted file mode 100644
index 76e3b21..0000000
--- a/pw_result/Kconfig
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-config PIGWEED_RESULT
-    bool "Enable Pigweed result library (pw_result)"
-    select PIGWEED_ASSERT
-    select PIGWEED_STATUS
diff --git a/pw_result/OWNERS b/pw_result/OWNERS
deleted file mode 100644
index b6d319c..0000000
--- a/pw_result/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-ewout@google.com
-frolv@google.com
diff --git a/pw_result/docs.rst b/pw_result/docs.rst
index 09f9e98..bfc37cd 100644
--- a/pw_result/docs.rst
+++ b/pw_result/docs.rst
@@ -1,83 +1,11 @@
 .. _module-pw_result:
 
-=========
+---------
 pw_result
-=========
-``pw::Result<T>`` is a class template for use in returning either a
-``pw::Status`` error or an object of type ``T``.
-
-.. inclusive-language: disable
-
-``pw::Result<T>``'s implementation is closely based on Abseil's `StatusOr<T>
-class <https://github.com/abseil/abseil-cpp/blob/master/absl/status/statusor.h>`_.
-There are a few differences:
-
-.. inclusive-language: enable
-
-* ``pw::Result<T>`` uses ``pw::Status``, which is much less sophisticated than
-  ``absl::Status``.
-* ``pw::Result<T>``'s functions are ``constexpr`` and ``pw::Result<T>`` may be
-  used in ``constexpr`` statements if ``T`` is trivially destructible.
-
------
-Usage
------
-Usage of ``pw::Result<T>`` is identical to Abseil's ``absl::StatusOr<T>``.
-See Abseil's `documentation
-<https://abseil.io/docs/cpp/guides/status#returning-a-status-or-a-value>`_ and
-`usage tips <https://abseil.io/tips/181>`_ for guidance.
-
-``pw::Result<T>`` is returned from a function that may return ``pw::OkStatus()``
-and a value or an error status and no value. If ``ok()`` is true, the
-``pw::Result<T>`` contains a valid ``T``. Otherwise, it does not contain a ``T``
-and attempting to access the value is an error.
-
-``pw::Result<T>`` can be used to directly access the contained type:
-
-.. code-block:: cpp
-
-  #include "pw_result/result.h"
-
-  if (pw::Result<Foo> foo = TryCreateFoo(); foo.ok()) {
-    foo->DoBar();
-  }
-
-``pw::Result`` is compatible with ``PW_TRY`` and ``PW_TRY_ASSIGN``, for example:
-
-.. code-block:: cpp
-
-  #include "pw_status/try.h"
-  #include "pw_result/result.h"
-
-  pw::Result<int> GetAnswer();  // Example function.
-
-  pw::Status UseAnswer() {
-    const pw::Result<int> answer = GetAnswer();
-    if (!answer.ok()) {
-      return answer.status();
-    }
-    if (answer.value() == 42) {
-      WhatWasTheUltimateQuestion();
-    }
-    return pw::OkStatus();
-  }
-
-  pw::Status UseAnswerWithTry() {
-    const pw::Result<int> answer = GetAnswer();
-    PW_TRY(answer.status());
-    if (answer.value() == 42) {
-      WhatWasTheUltimateQuestion();
-    }
-    return pw::OkStatus();
-  }
-
-  pw::Status UseAnswerWithTryAssign() {
-    PW_TRY_ASSIGN(const int answer, GetAnswer());
-    if (answer == 42) {
-      WhatWasTheUltimateQuestion();
-    }
-    return pw::OkStatus();
-  }
+---------
+``pw::Result`` is a convenient wrapper around returning a Status along side some
+data when the status is OK. This is meant for returning lightweight result
+types or references to larger results.
 
 .. warning::
 
@@ -89,9 +17,12 @@
   This module is experimental. Its impact on code size and stack usage has not
   yet been profiled. Use at your own risk.
 
------------
+Compatibility
+=============
+Works with C++11, but some features require C++17.
+
 Size report
------------
+===========
 The table below showcases the difference in size between functions returning a
 Status with an output pointer, and functions returning a Result, in various
 situations.
@@ -101,9 +32,3 @@
 check if Result is suitable for you.
 
 .. include:: result_size
-
-------
-Zephyr
-------
-To enable ``pw_result`` for Zephyr add ``CONFIG_PIGWEED_RESULT=y`` to the
-project's configuration.
diff --git a/pw_result/public/pw_result/internal/result_internal.h b/pw_result/public/pw_result/internal/result_internal.h
deleted file mode 100644
index 895bfd9..0000000
--- a/pw_result/public/pw_result/internal/result_internal.h
+++ /dev/null
@@ -1,379 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <type_traits>
-#include <utility>
-
-#include "pw_assert/assert.h"
-#include "pw_status/status.h"
-
-namespace pw {
-
-template <typename T>
-class [[nodiscard]] Result;
-
-namespace internal_result {
-
-// Detects whether `U` has conversion operator to `Result<T>`, i.e. `operator
-// Result<T>()`.
-template <typename T, typename U, typename = void>
-struct HasConversionOperatorToResult : std::false_type {};
-
-template <typename T, typename U>
-void test(char (*)[sizeof(std::declval<U>().operator Result<T>())]);
-
-template <typename T, typename U>
-struct HasConversionOperatorToResult<T, U, decltype(test<T, U>(0))>
-    : std::true_type {};
-
-// Detects whether `T` is constructible or convertible from `Result<U>`.
-template <typename T, typename U>
-using IsConstructibleOrConvertibleFromResult =
-    std::disjunction<std::is_constructible<T, Result<U>&>,
-                     std::is_constructible<T, const Result<U>&>,
-                     std::is_constructible<T, Result<U>&&>,
-                     std::is_constructible<T, const Result<U>&&>,
-                     std::is_convertible<Result<U>&, T>,
-                     std::is_convertible<const Result<U>&, T>,
-                     std::is_convertible<Result<U>&&, T>,
-                     std::is_convertible<const Result<U>&&, T>>;
-
-// Detects whether `T` is constructible or convertible or assignable from
-// `Result<U>`.
-template <typename T, typename U>
-using IsConstructibleOrConvertibleOrAssignableFromResult =
-    std::disjunction<IsConstructibleOrConvertibleFromResult<T, U>,
-                     std::is_assignable<T&, Result<U>&>,
-                     std::is_assignable<T&, const Result<U>&>,
-                     std::is_assignable<T&, Result<U>&&>,
-                     std::is_assignable<T&, const Result<U>&&>>;
-
-// Detects whether direct initializing `Result<T>` from `U` is ambiguous, i.e.
-// when `U` is `Result<V>` and `T` is constructible or convertible from `V`.
-template <typename T, typename U>
-struct IsDirectInitializationAmbiguous
-    : public std::conditional_t<
-          std::is_same<std::remove_cv_t<std::remove_reference_t<U>>, U>::value,
-          std::false_type,
-          IsDirectInitializationAmbiguous<
-              T,
-              std::remove_cv_t<std::remove_reference_t<U>>>> {};
-
-template <typename T, typename V>
-struct IsDirectInitializationAmbiguous<T, Result<V>>
-    : public IsConstructibleOrConvertibleFromResult<T, V> {};
-
-// Checks against the constraints of the direction initialization, i.e. when
-// `Result<T>::Result(U&&)` should participate in overload resolution.
-template <typename T, typename U>
-using IsDirectInitializationValid = std::disjunction<
-    // Short circuits if T is basically U.
-    std::is_same<T, std::remove_cv_t<std::remove_reference_t<U>>>,
-    std::negation<std::disjunction<
-        std::is_same<Result<T>, std::remove_cv_t<std::remove_reference_t<U>>>,
-        std::is_same<Status, std::remove_cv_t<std::remove_reference_t<U>>>,
-        std::is_same<std::in_place_t,
-                     std::remove_cv_t<std::remove_reference_t<U>>>,
-        IsDirectInitializationAmbiguous<T, U>>>>;
-
-// This trait detects whether `Result<T>::operator=(U&&)` is ambiguous, which
-// is equivalent to whether all the following conditions are met:
-// 1. `U` is `Result<V>`.
-// 2. `T` is constructible and assignable from `V`.
-// 3. `T` is constructible and assignable from `U` (i.e. `Result<V>`).
-// For example, the following code is considered ambiguous:
-// (`T` is `bool`, `U` is `Result<bool>`, `V` is `bool`)
-//   Result<bool> s1 = true;  // s1.ok() && s1.ValueOrDie() == true
-//   Result<bool> s2 = false;  // s2.ok() && s2.ValueOrDie() == false
-//   s1 = s2;  // ambiguous, `s1 = s2.ValueOrDie()` or `s1 = bool(s2)`?
-template <typename T, typename U>
-struct IsForwardingAssignmentAmbiguous
-    : public std::conditional_t<
-          std::is_same<std::remove_cv_t<std::remove_reference_t<U>>, U>::value,
-          std::false_type,
-          IsForwardingAssignmentAmbiguous<
-              T,
-              std::remove_cv_t<std::remove_reference_t<U>>>> {};
-
-template <typename T, typename U>
-struct IsForwardingAssignmentAmbiguous<T, Result<U>>
-    : public IsConstructibleOrConvertibleOrAssignableFromResult<T, U> {};
-
-// Checks against the constraints of the forwarding assignment, i.e. whether
-// `Result<T>::operator(U&&)` should participate in overload resolution.
-template <typename T, typename U>
-using IsForwardingAssignmentValid = std::disjunction<
-    // Short circuits if T is basically U.
-    std::is_same<T, std::remove_cv_t<std::remove_reference_t<U>>>,
-    std::negation<std::disjunction<
-        std::is_same<Result<T>, std::remove_cv_t<std::remove_reference_t<U>>>,
-        std::is_same<Status, std::remove_cv_t<std::remove_reference_t<U>>>,
-        std::is_same<std::in_place_t,
-                     std::remove_cv_t<std::remove_reference_t<U>>>,
-        IsForwardingAssignmentAmbiguous<T, U>>>>;
-
-PW_MODIFY_DIAGNOSTICS_PUSH();
-PW_MODIFY_DIAGNOSTIC_GCC(ignored, "-Wmaybe-uninitialized");
-
-// Construct an instance of T in `p` through placement new, passing Args... to
-// the constructor.
-// This abstraction is here mostly for the gcc performance fix.
-template <typename T, typename... Args>
-void PlacementNew(void* p, Args&&... args) {
-  new (p) T(std::forward<Args>(args)...);
-}
-
-// Helper base class to hold the data and all operations.
-// We move all this to a base class to allow mixing with the appropriate
-// TraitsBase specialization.
-//
-// Pigweed addition: Specialize StatusOrData for trivially destructible types.
-// This makes a Result usable in a constexpr statement.
-//
-// Note: in C++20, this entire file can be greatly simplfied with the requires
-// statement.
-template <typename T, bool = std::is_trivially_destructible<T>::value>
-class StatusOrData;
-
-// Place the implementation of StatusOrData in a macro so it can be shared
-// between both specializations.
-#define PW_RESULT_STATUS_OR_DATA_IMPL                                          \
-  template <typename U, bool>                                                  \
-  friend class StatusOrData;                                                   \
-                                                                               \
- public:                                                                       \
-  StatusOrData() = delete;                                                     \
-                                                                               \
-  constexpr StatusOrData(const StatusOrData& other)                            \
-      : status_(other.status_), unused_() {                                    \
-    if (other.ok()) {                                                          \
-      MakeValue(other.data_);                                                  \
-    }                                                                          \
-  }                                                                            \
-                                                                               \
-  constexpr StatusOrData(StatusOrData&& other) noexcept                        \
-      : status_(std::move(other.status_)), unused_() {                         \
-    if (other.ok()) {                                                          \
-      MakeValue(std::move(other.data_));                                       \
-    }                                                                          \
-  }                                                                            \
-                                                                               \
-  template <typename U>                                                        \
-  explicit constexpr StatusOrData(const StatusOrData<U>& other) {              \
-    if (other.ok()) {                                                          \
-      MakeValue(other.data_);                                                  \
-      status_ = OkStatus();                                                    \
-    } else {                                                                   \
-      status_ = other.status_;                                                 \
-    }                                                                          \
-  }                                                                            \
-                                                                               \
-  template <typename U>                                                        \
-  explicit constexpr StatusOrData(StatusOrData<U>&& other) {                   \
-    if (other.ok()) {                                                          \
-      MakeValue(std::move(other.data_));                                       \
-      status_ = OkStatus();                                                    \
-    } else {                                                                   \
-      status_ = std::move(other.status_);                                      \
-    }                                                                          \
-  }                                                                            \
-                                                                               \
-  template <typename... Args>                                                  \
-  explicit constexpr StatusOrData(std::in_place_t, Args&&... args)             \
-      : status_(), data_(std::forward<Args>(args)...) {}                       \
-                                                                               \
-  explicit constexpr StatusOrData(const T& value) : status_(), data_(value) {} \
-  explicit constexpr StatusOrData(T&& value)                                   \
-      : status_(), data_(std::move(value)) {}                                  \
-                                                                               \
-  template <typename U,                                                        \
-            std::enable_if_t<std::is_constructible<Status, U&&>::value, int> = \
-                0>                                                             \
-  explicit constexpr StatusOrData(U&& v)                                       \
-      : status_(std::forward<U>(v)), unused_() {                               \
-    PW_ASSERT(!status_.ok());                                                  \
-  }                                                                            \
-                                                                               \
-  constexpr StatusOrData& operator=(const StatusOrData& other) {               \
-    if (this == &other) {                                                      \
-      return *this;                                                            \
-    }                                                                          \
-                                                                               \
-    if (other.ok()) {                                                          \
-      Assign(other.data_);                                                     \
-    } else {                                                                   \
-      AssignStatus(other.status_);                                             \
-    }                                                                          \
-    return *this;                                                              \
-  }                                                                            \
-                                                                               \
-  constexpr StatusOrData& operator=(StatusOrData&& other) {                    \
-    if (this == &other) {                                                      \
-      return *this;                                                            \
-    }                                                                          \
-                                                                               \
-    if (other.ok()) {                                                          \
-      Assign(std::move(other.data_));                                          \
-    } else {                                                                   \
-      AssignStatus(std::move(other.status_));                                  \
-    }                                                                          \
-    return *this;                                                              \
-  }                                                                            \
-                                                                               \
-  template <typename U>                                                        \
-  constexpr void Assign(U&& value) {                                           \
-    if (ok()) {                                                                \
-      data_ = std::forward<U>(value);                                          \
-    } else {                                                                   \
-      MakeValue(std::forward<U>(value));                                       \
-      status_ = OkStatus();                                                    \
-    }                                                                          \
-  }                                                                            \
-                                                                               \
-  template <typename U>                                                        \
-  constexpr void AssignStatus(U&& v) {                                         \
-    Clear();                                                                   \
-    status_ = static_cast<Status>(std::forward<U>(v));                         \
-    PW_ASSERT(!status_.ok());                                                  \
-  }                                                                            \
-                                                                               \
-  constexpr bool ok() const { return status_.ok(); }                           \
-                                                                               \
- protected:                                                                    \
-  union {                                                                      \
-    Status status_;                                                            \
-  };                                                                           \
-                                                                               \
-  struct Empty {};                                                             \
-  union {                                                                      \
-    Empty unused_;                                                             \
-    T data_;                                                                   \
-  };                                                                           \
-                                                                               \
-  constexpr void Clear() {                                                     \
-    if (ok()) {                                                                \
-      data_.~T();                                                              \
-    }                                                                          \
-  }                                                                            \
-                                                                               \
-  template <typename... Arg>                                                   \
-  void MakeValue(Arg&&... arg) {                                               \
-    internal_result::PlacementNew<T>(&unused_, std::forward<Arg>(arg)...);     \
-  }
-
-template <typename T>
-class StatusOrData<T, true> {
-  PW_RESULT_STATUS_OR_DATA_IMPL;
-};
-
-template <typename T>
-class StatusOrData<T, false> {
-  PW_RESULT_STATUS_OR_DATA_IMPL;
-
- public:
-  // Add a destructor since T is not trivially destructible.
-  ~StatusOrData() {
-    if (ok()) {
-      data_.~T();
-    }
-  }
-};
-
-#undef PW_RESULT_STATUS_OR_DATA_IMPL
-
-PW_MODIFY_DIAGNOSTICS_POP();
-
-// Helper base classes to allow implicitly deleted constructors and assignment
-// operators in `Result`. For example, `CopyCtorBase` will explicitly delete
-// the copy constructor when T is not copy constructible and `Result` will
-// inherit that behavior implicitly.
-template <typename T, bool = std::is_copy_constructible<T>::value>
-struct CopyCtorBase {
-  CopyCtorBase() = default;
-  CopyCtorBase(const CopyCtorBase&) = default;
-  CopyCtorBase(CopyCtorBase&&) = default;
-  CopyCtorBase& operator=(const CopyCtorBase&) = default;
-  CopyCtorBase& operator=(CopyCtorBase&&) = default;
-};
-
-template <typename T>
-struct CopyCtorBase<T, false> {
-  CopyCtorBase() = default;
-  CopyCtorBase(const CopyCtorBase&) = delete;
-  CopyCtorBase(CopyCtorBase&&) = default;
-  CopyCtorBase& operator=(const CopyCtorBase&) = default;
-  CopyCtorBase& operator=(CopyCtorBase&&) = default;
-};
-
-template <typename T, bool = std::is_move_constructible<T>::value>
-struct MoveCtorBase {
-  MoveCtorBase() = default;
-  MoveCtorBase(const MoveCtorBase&) = default;
-  MoveCtorBase(MoveCtorBase&&) = default;
-  MoveCtorBase& operator=(const MoveCtorBase&) = default;
-  MoveCtorBase& operator=(MoveCtorBase&&) = default;
-};
-
-template <typename T>
-struct MoveCtorBase<T, false> {
-  MoveCtorBase() = default;
-  MoveCtorBase(const MoveCtorBase&) = default;
-  MoveCtorBase(MoveCtorBase&&) = delete;
-  MoveCtorBase& operator=(const MoveCtorBase&) = default;
-  MoveCtorBase& operator=(MoveCtorBase&&) = default;
-};
-
-template <typename T,
-          bool = std::is_copy_constructible<T>::value&&
-              std::is_copy_assignable<T>::value>
-struct CopyAssignBase {
-  CopyAssignBase() = default;
-  CopyAssignBase(const CopyAssignBase&) = default;
-  CopyAssignBase(CopyAssignBase&&) = default;
-  CopyAssignBase& operator=(const CopyAssignBase&) = default;
-  CopyAssignBase& operator=(CopyAssignBase&&) = default;
-};
-
-template <typename T>
-struct CopyAssignBase<T, false> {
-  CopyAssignBase() = default;
-  CopyAssignBase(const CopyAssignBase&) = default;
-  CopyAssignBase(CopyAssignBase&&) = default;
-  CopyAssignBase& operator=(const CopyAssignBase&) = delete;
-  CopyAssignBase& operator=(CopyAssignBase&&) = default;
-};
-
-template <typename T,
-          bool = std::is_move_constructible<T>::value&&
-              std::is_move_assignable<T>::value>
-struct MoveAssignBase {
-  MoveAssignBase() = default;
-  MoveAssignBase(const MoveAssignBase&) = default;
-  MoveAssignBase(MoveAssignBase&&) = default;
-  MoveAssignBase& operator=(const MoveAssignBase&) = default;
-  MoveAssignBase& operator=(MoveAssignBase&&) = default;
-};
-
-template <typename T>
-struct MoveAssignBase<T, false> {
-  MoveAssignBase() = default;
-  MoveAssignBase(const MoveAssignBase&) = default;
-  MoveAssignBase(MoveAssignBase&&) = default;
-  MoveAssignBase& operator=(const MoveAssignBase&) = default;
-  MoveAssignBase& operator=(MoveAssignBase&&) = delete;
-};
-
-}  // namespace internal_result
-}  // namespace pw
diff --git a/pw_result/public/pw_result/result.h b/pw_result/public/pw_result/result.h
index fe504dc7..e529344 100644
--- a/pw_result/public/pw_result/result.h
+++ b/pw_result/public/pw_result/result.h
@@ -1,4 +1,4 @@
-// Copyright 2022 The Pigweed Authors
+// Copyright 2020 The Pigweed Authors
 //
 // 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
@@ -11,712 +11,89 @@
 // 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: result.h
-// -----------------------------------------------------------------------------
-//
-// An `Result<T>` represents a union of an `pw::Status` object and an object of
-// type `T`. The `Result<T>` will either contain an object of type `T`
-// (indicating a successful operation), or an error (of type `Status`)
-// explaining why such a value is not present.
-//
-// In general, check the success of an operation returning an `Result<T>` like
-// you would an `pw::Status` by using the `ok()` member function.
-//
-// Example:
-//
-//   Result<Foo> result = Calculation();
-//   if (result.ok()) {
-//     result->DoSomethingCool();
-//   } else {
-//     PW_LOG_ERROR("Calculation failed: %s",  result.status().str());
-//   }
 #pragma once
 
-#include <exception>
-#include <initializer_list>
-#include <new>
-#include <string>
-#include <type_traits>
-#include <utility>
+#include <algorithm>
 
-#include "pw_preprocessor/compiler.h"
-#include "pw_result/internal/result_internal.h"
+#include "pw_assert/light.h"
 #include "pw_status/status.h"
 
 namespace pw {
 
-// Returned Result objects may not be ignored.
+// A Result represents the result of an operation which can fail. It is a
+// convenient wrapper around returning a Status alongside some data when the
+// status is OK.
 template <typename T>
-class [[nodiscard]] Result;
-
-// Result<T>
-//
-// The `Result<T>` class template is a union of an `pw::Status` object and an
-// object of type `T`. The `Result<T>` models an object that is either a usable
-// object, or an error (of type `Status`) explaining why such an object is not
-// present. An `Result<T>` is typically the return value of a function which may
-// fail.
-//
-// An `Result<T>` can never hold an "OK" status; instead, the presence of an
-// object of type `T` indicates success. Instead of checking for a `kOk` value,
-// use the `Result<T>::ok()` member function. (It is for this reason, and code
-// readability, that using the `ok()` function is preferred for `Status` as
-// well.)
-//
-// Example:
-//
-//   Result<Foo> result = DoBigCalculationThatCouldFail();
-//   if (result.ok()) {
-//     result->DoSomethingCool();
-//   } else {
-//     PW_LOG_ERROR("Calculation failed: %s", result.status().str());
-//   }
-//
-// Accessing the object held by an `Result<T>` should be performed via
-// `operator*` or `operator->`, after a call to `ok()` confirms that the
-// `Result<T>` holds an object of type `T`:
-//
-// Example:
-//
-//   Result<int> i = GetCount();
-//   if (i.ok()) {
-//     updated_total += *i
-//   }
-//
-// NOTE: using `Result<T>::value()` when no valid value is present will trigger
-// a PW_ASSERT.
-//
-// Example:
-//
-//   Result<Foo> result = DoBigCalculationThatCouldFail();
-//   const Foo& foo = result.value();    // Crash/exception if no value present
-//   foo.DoSomethingCool();
-//
-// A `Result<T*>` can be constructed from a null pointer like any other pointer
-// value, and the result will be that `ok()` returns `true` and `value()`
-// returns `nullptr`. Checking the value of pointer in an `Result<T>` generally
-// requires a bit more care, to ensure both that a value is present and that
-// value is not null:
-//
-//  Result<Foo*> result = LookUpTheFoo(arg);
-//  if (!result.ok()) {
-//    PW_LOG_ERROR("Unable to look up the Foo: %s", result.status().str());
-//  } else if (*result == nullptr) {
-//    PW_LOG_ERROR("Unexpected null pointer");
-//  } else {
-//    (*result)->DoSomethingCool();
-//  }
-//
-// Example factory implementation returning Result<T>:
-//
-//  Result<Foo> FooFactory::MakeFoo(int arg) {
-//    if (arg <= 0) {
-//      return pw::Status::InvalidArgument();
-//    }
-//    return Foo(arg);
-//  }
-template <typename T>
-class Result : private internal_result::StatusOrData<T>,
-               private internal_result::CopyCtorBase<T>,
-               private internal_result::MoveCtorBase<T>,
-               private internal_result::CopyAssignBase<T>,
-               private internal_result::MoveAssignBase<T> {
-  template <typename U>
-  friend class Result;
-
-  using Base = internal_result::StatusOrData<T>;
-
+class Result {
  public:
-  // Result<T>::value_type
-  //
-  // This instance data provides a generic `value_type` member for use within
-  // generic programming. This usage is analogous to that of
-  // `optional::value_type` in the case of `std::optional`.
-  typedef T value_type;
+  constexpr Result(T&& value) : value_(std::move(value)), status_(OkStatus()) {}
+  constexpr Result(const T& value) : value_(value), status_(OkStatus()) {}
 
-  // Constructors
+  template <typename... Args>
+  constexpr Result(std::in_place_t, Args&&... args)
+      : value_(std::forward<Args>(args)...), status_(OkStatus()) {}
 
-  // Constructs a new `Result` with an `pw::Status::Unknown()` status. This
-  // constructor is marked 'explicit' to prevent usages in return values such as
-  // 'return {};', under the misconception that `Result<std::vector<int>>` will
-  // be initialized with an empty vector, instead of a `Status::Unknown()` error
-  // code.
-  explicit constexpr Result();
+  constexpr Result(Status status) : dummy_({}), status_(status) {
+    PW_ASSERT(!status_.ok());
+  }
+  constexpr Result(Status::Code code) : dummy_({}), status_(code) {
+    PW_ASSERT(!status_.ok());
+  }
 
-  // `Result<T>` is copy constructible if `T` is copy constructible.
   constexpr Result(const Result&) = default;
-  // `Result<T>` is copy assignable if `T` is copy constructible and copy
-  // assignable.
   constexpr Result& operator=(const Result&) = default;
 
-  // `Result<T>` is move constructible if `T` is move constructible.
   constexpr Result(Result&&) = default;
-  // `Result<T>` is moveAssignable if `T` is move constructible and move
-  // assignable.
   constexpr Result& operator=(Result&&) = default;
 
-  // Converting Constructors
+  constexpr Status status() const { return status_; }
+  constexpr bool ok() const { return status_.ok(); }
 
-  // Constructs a new `Result<T>` from an `pw::Result<U>`, when `T` is
-  // constructible from `U`. To avoid ambiguity, these constructors are disabled
-  // if `T` is also constructible from `Result<U>.`. This constructor is
-  // explicit if and only if the corresponding construction of `T` from `U` is
-  // explicit. (This constructor inherits its explicitness from the underlying
-  // constructor.)
-  template <
-      typename U,
-      std::enable_if_t<
-          std::conjunction<
-              std::negation<std::is_same<T, U>>,
-              std::is_constructible<T, const U&>,
-              std::is_convertible<const U&, T>,
-              std::negation<internal_result::
-                                IsConstructibleOrConvertibleFromResult<T, U>>>::
-              value,
-          int> = 0>
-  constexpr Result(const Result<U>& other)  // NOLINT
-      : Base(static_cast<const typename Result<U>::Base&>(other)) {}
-  template <
-      typename U,
-      std::enable_if_t<
-          std::conjunction<
-              std::negation<std::is_same<T, U>>,
-              std::is_constructible<T, const U&>,
-              std::negation<std::is_convertible<const U&, T>>,
-              std::negation<internal_result::
-                                IsConstructibleOrConvertibleFromResult<T, U>>>::
-              value,
-          int> = 0>
-  explicit constexpr Result(const Result<U>& other)
-      : Base(static_cast<const typename Result<U>::Base&>(other)) {}
-
-  template <
-      typename U,
-      std::enable_if_t<
-          std::conjunction<
-              std::negation<std::is_same<T, U>>,
-              std::is_constructible<T, U&&>,
-              std::is_convertible<U&&, T>,
-              std::negation<internal_result::
-                                IsConstructibleOrConvertibleFromResult<T, U>>>::
-              value,
-          int> = 0>
-  constexpr Result(Result<U>&& other)  // NOLINT
-      : Base(static_cast<typename Result<U>::Base&&>(other)) {}
-  template <
-      typename U,
-      std::enable_if_t<
-          std::conjunction<
-              std::negation<std::is_same<T, U>>,
-              std::is_constructible<T, U&&>,
-              std::negation<std::is_convertible<U&&, T>>,
-              std::negation<internal_result::
-                                IsConstructibleOrConvertibleFromResult<T, U>>>::
-              value,
-          int> = 0>
-  explicit constexpr Result(Result<U>&& other)
-      : Base(static_cast<typename Result<U>::Base&&>(other)) {}
-
-  // Converting Assignment Operators
-
-  // Creates an `Result<T>` through assignment from an
-  // `Result<U>` when:
-  //
-  //   * Both `Result<T>` and `pw::Result<U>` are OK by assigning
-  //     `U` to `T` directly.
-  //   * `Result<T>` is OK and `pw::Result<U>` contains an error
-  //      code by destroying `Result<T>`'s value and assigning from
-  //      `Result<U>'
-  //   * `Result<T>` contains an error code and `pw::Result<U>` is
-  //      OK by directly initializing `T` from `U`.
-  //   * Both `Result<T>` and `pw::Result<U>` contain an error
-  //     code by assigning the `Status` in `Result<U>` to
-  //     `Result<T>`
-  //
-  // These overloads only apply if `Result<T>` is constructible and
-  // assignable from `Result<U>` and `Result<T>` cannot be directly
-  // assigned from `Result<U>`.
-  template <typename U,
-            std::enable_if_t<
-                std::conjunction<
-                    std::negation<std::is_same<T, U>>,
-                    std::is_constructible<T, const U&>,
-                    std::is_assignable<T, const U&>,
-                    std::negation<
-                        internal_result::
-                            IsConstructibleOrConvertibleOrAssignableFromResult<
-                                T,
-                                U>>>::value,
-                int> = 0>
-  constexpr Result& operator=(const Result<U>& other) {
-    this->Assign(other);
-    return *this;
-  }
-  template <typename U,
-            std::enable_if_t<
-                std::conjunction<
-                    std::negation<std::is_same<T, U>>,
-                    std::is_constructible<T, U&&>,
-                    std::is_assignable<T, U&&>,
-                    std::negation<
-                        internal_result::
-                            IsConstructibleOrConvertibleOrAssignableFromResult<
-                                T,
-                                U>>>::value,
-                int> = 0>
-  constexpr Result& operator=(Result<U>&& other) {
-    this->Assign(std::move(other));
-    return *this;
+  constexpr T& value() & {
+    PW_ASSERT(status_.ok());
+    return value_;
   }
 
-  // Constructs a new `Result<T>` with a non-ok status. After calling this
-  // constructor, `this->ok()` will be `false` and calls to `value()` will
-  // crash, or produce an exception if exceptions are enabled.
-  //
-  // The constructor also takes any type `U` that is convertible to `Status`.
-  // This constructor is explicit if an only if `U` is not of type `Status` and
-  // the conversion from `U` to `Status` is explicit.
-  //
-  // REQUIRES: !Status(std::forward<U>(v)).ok(). This requirement is DCHECKed.
-  // In optimized builds, passing OkStatus() here will have the effect of
-  // passing Status::Internal() as a fallback.
-  template <
-      typename U = Status,
-      std::enable_if_t<
-          std::conjunction<
-              std::is_convertible<U&&, Status>,
-              std::is_constructible<Status, U&&>,
-              std::negation<std::is_same<std::decay_t<U>, Result<T>>>,
-              std::negation<std::is_same<std::decay_t<U>, T>>,
-              std::negation<std::is_same<std::decay_t<U>, std::in_place_t>>,
-              std::negation<internal_result::
-                                HasConversionOperatorToResult<T, U&&>>>::value,
-          int> = 0>
-  constexpr Result(U&& v) : Base(std::forward<U>(v)) {}
-
-  template <
-      typename U = Status,
-      std::enable_if_t<
-          std::conjunction<
-              std::negation<std::is_convertible<U&&, Status>>,
-              std::is_constructible<Status, U&&>,
-              std::negation<std::is_same<std::decay_t<U>, Result<T>>>,
-              std::negation<std::is_same<std::decay_t<U>, T>>,
-              std::negation<std::is_same<std::decay_t<U>, std::in_place_t>>,
-              std::negation<internal_result::
-                                HasConversionOperatorToResult<T, U&&>>>::value,
-          int> = 0>
-  constexpr explicit Result(U&& v) : Base(std::forward<U>(v)) {}
-
-  template <
-      typename U = Status,
-      std::enable_if_t<
-          std::conjunction<
-              std::is_convertible<U&&, Status>,
-              std::is_constructible<Status, U&&>,
-              std::negation<std::is_same<std::decay_t<U>, Result<T>>>,
-              std::negation<std::is_same<std::decay_t<U>, T>>,
-              std::negation<std::is_same<std::decay_t<U>, std::in_place_t>>,
-              std::negation<internal_result::
-                                HasConversionOperatorToResult<T, U&&>>>::value,
-          int> = 0>
-  constexpr Result& operator=(U&& v) {
-    this->AssignStatus(std::forward<U>(v));
-    return *this;
+  constexpr const T& value() const& {
+    PW_ASSERT(status_.ok());
+    return value_;
   }
 
-  // Perfect-forwarding value assignment operator.
-
-  // If `*this` contains a `T` value before the call, the contained value is
-  // assigned from `std::forward<U>(v)`; Otherwise, it is directly-initialized
-  // from `std::forward<U>(v)`.
-  // This function does not participate in overload unless:
-  // 1. `std::is_constructible_v<T, U>` is true,
-  // 2. `std::is_assignable_v<T&, U>` is true.
-  // 3. `std::is_same_v<Result<T>, std::remove_cvref_t<U>>` is false.
-  // 4. Assigning `U` to `T` is not ambiguous:
-  //  If `U` is `Result<V>` and `T` is constructible and assignable from
-  //  both `Result<V>` and `V`, the assignment is considered bug-prone and
-  //  ambiguous thus will fail to compile. For example:
-  //    Result<bool> s1 = true;  // s1.ok() && *s1 == true
-  //    Result<bool> s2 = false;  // s2.ok() && *s2 == false
-  //    s1 = s2;  // ambiguous, `s1 = *s2` or `s1 = bool(s2)`?
-  template <
-      typename U = T,
-      typename = typename std::enable_if<std::conjunction<
-          std::is_constructible<T, U&&>,
-          std::is_assignable<T&, U&&>,
-          std::disjunction<
-              std::is_same<std::remove_cv_t<std::remove_reference_t<U>>, T>,
-              std::conjunction<
-                  std::negation<std::is_convertible<U&&, Status>>,
-                  std::negation<
-                      internal_result::HasConversionOperatorToResult<T, U&&>>>>,
-          internal_result::IsForwardingAssignmentValid<T, U&&>>::value>::type>
-  constexpr Result& operator=(U&& v) {
-    this->Assign(std::forward<U>(v));
-    return *this;
+  constexpr T&& value() && {
+    PW_ASSERT(status_.ok());
+    return std::move(value_);
   }
 
-  // Constructs the inner value `T` in-place using the provided args, using the
-  // `T(args...)` constructor.
-  template <typename... Args>
-  explicit constexpr Result(std::in_place_t, Args&&... args);
-  template <typename U, typename... Args>
-  explicit constexpr Result(std::in_place_t,
-                            std::initializer_list<U> ilist,
-                            Args&&... args);
-
-  // Constructs the inner value `T` in-place using the provided args, using the
-  // `T(U)` (direct-initialization) constructor. This constructor is only valid
-  // if `T` can be constructed from a `U`. Can accept move or copy constructors.
-  //
-  // This constructor is explicit if `U` is not convertible to `T`. To avoid
-  // ambiguity, this constructor is disabled if `U` is a `Result<J>`, where
-  // `J` is convertible to `T`.
-  template <
-      typename U = T,
-      std::enable_if_t<
-          std::conjunction<
-              internal_result::IsDirectInitializationValid<T, U&&>,
-              std::is_constructible<T, U&&>,
-              std::is_convertible<U&&, T>,
-              std::disjunction<
-                  std::is_same<std::remove_cv_t<std::remove_reference_t<U>>, T>,
-                  std::conjunction<
-                      std::negation<std::is_convertible<U&&, Status>>,
-                      std::negation<
-                          internal_result::
-                              HasConversionOperatorToResult<T, U&&>>>>>::value,
-          int> = 0>
-  constexpr Result(U&& u)  // NOLINT
-      : Result(std::in_place, std::forward<U>(u)) {}
-
-  template <
-      typename U = T,
-      std::enable_if_t<
-          std::conjunction<
-              internal_result::IsDirectInitializationValid<T, U&&>,
-              std::disjunction<
-                  std::is_same<std::remove_cv_t<std::remove_reference_t<U>>, T>,
-                  std::conjunction<
-                      std::negation<std::is_constructible<Status, U&&>>,
-                      std::negation<
-                          internal_result::
-                              HasConversionOperatorToResult<T, U&&>>>>,
-              std::is_constructible<T, U&&>,
-              std::negation<std::is_convertible<U&&, T>>>::value,
-          int> = 0>
-  explicit constexpr Result(U&& u)  // NOLINT
-      : Result(std::in_place, std::forward<U>(u)) {}
-
-  // Result<T>::ok()
-  //
-  // Returns whether or not this `Result<T>` holds a `T` value. This
-  // member function is analagous to `Status::ok()` and should be used
-  // similarly to check the status of return values.
-  //
-  // Example:
-  //
-  // Result<Foo> result = DoBigCalculationThatCouldFail();
-  // if (result.ok()) {
-  //    // Handle result
-  // else {
-  //    // Handle error
-  // }
-  [[nodiscard]] constexpr bool ok() const { return this->status_.ok(); }
-
-  // Result<T>::status()
-  //
-  // Returns a reference to the current `Status` contained within the
-  // `Result<T>`. If `pw::Result<T>` contains a `T`, then this function returns
-  // `OkStatus()`.
-  constexpr const Status& status() const&;
-  constexpr Status status() &&;
-
-  // Result<T>::value()
-  //
-  // Returns a reference to the held value if `this->ok()`. Otherwise,
-  // terminates the process.
-  //
-  // If you have already checked the status using `this->ok()`, you probably
-  // want to use `operator*()` or `operator->()` to access the value instead of
-  // `value`.
-  //
-  // Note: for value types that are cheap to copy, prefer simple code:
-  //
-  //   T value = result.value();
-  //
-  // Otherwise, if the value type is expensive to copy, but can be left
-  // in the Result, simply assign to a reference:
-  //
-  //   T& value = result.value();  // or `const T&`
-  //
-  // Otherwise, if the value type supports an efficient move, it can be
-  // used as follows:
-  //
-  //   T value = std::move(result).value();
-  //
-  // The `std::move` on result instead of on the whole expression enables
-  // warnings about possible uses of the result object after the move.
-  constexpr const T& value() const& PW_ATTRIBUTE_LIFETIME_BOUND;
-  constexpr T& value() & PW_ATTRIBUTE_LIFETIME_BOUND;
-  constexpr const T&& value() const&& PW_ATTRIBUTE_LIFETIME_BOUND;
-  constexpr T&& value() && PW_ATTRIBUTE_LIFETIME_BOUND;
-
-  // Result<T>:: operator*()
-  //
-  // Returns a reference to the current value.
-  //
-  // REQUIRES: `this->ok() == true`, otherwise the behavior is undefined.
-  //
-  // Use `this->ok()` to verify that there is a current value within the
-  // `Result<T>`. Alternatively, see the `value()` member function for a
-  // similar API that guarantees crashing or throwing an exception if there is
-  // no current value.
-  constexpr const T& operator*() const& PW_ATTRIBUTE_LIFETIME_BOUND;
-  constexpr T& operator*() & PW_ATTRIBUTE_LIFETIME_BOUND;
-  constexpr const T&& operator*() const&& PW_ATTRIBUTE_LIFETIME_BOUND;
-  constexpr T&& operator*() && PW_ATTRIBUTE_LIFETIME_BOUND;
-
-  // Result<T>::operator->()
-  //
-  // Returns a pointer to the current value.
-  //
-  // REQUIRES: `this->ok() == true`, otherwise the behavior is undefined.
-  //
-  // Use `this->ok()` to verify that there is a current value.
-  constexpr const T* operator->() const PW_ATTRIBUTE_LIFETIME_BOUND;
-  constexpr T* operator->() PW_ATTRIBUTE_LIFETIME_BOUND;
-
-  // Result<T>::value_or()
-  //
-  // Returns the current value if `this->ok() == true`. Otherwise constructs a
-  // value using the provided `default_value`.
-  //
-  // Unlike `value`, this function returns by value, copying the current value
-  // if necessary. If the value type supports an efficient move, it can be used
-  // as follows:
-  //
-  //   T value = std::move(result).value_or(def);
-  //
-  // Unlike with `value`, calling `std::move()` on the result of `value_or` will
-  // still trigger a copy.
   template <typename U>
-  constexpr T value_or(U&& default_value) const&;
-  template <typename U>
-  constexpr T value_or(U&& default_value) &&;
-
-  // Result<T>::IgnoreError()
-  //
-  // Ignores any errors. This method does nothing except potentially suppress
-  // complaints from any tools that are checking that errors are not dropped on
-  // the floor.
-  constexpr void IgnoreError() const;
-
-  // Result<T>::emplace()
-  //
-  // Reconstructs the inner value T in-place using the provided args, using the
-  // T(args...) constructor. Returns reference to the reconstructed `T`.
-  template <typename... Args>
-  T& emplace(Args&&... args) {
+  constexpr T value_or(U&& default_value) const& {
     if (ok()) {
-      this->Clear();
-      this->MakeValue(std::forward<Args>(args)...);
-    } else {
-      this->MakeValue(std::forward<Args>(args)...);
-      this->status_ = OkStatus();
+      PW_MODIFY_DIAGNOSTICS_PUSH();
+      // GCC 10 emits -Wmaybe-uninitialized warnings about value_.
+      PW_MODIFY_DIAGNOSTIC_GCC(ignored, "-Wmaybe-uninitialized");
+      return value_;
+      PW_MODIFY_DIAGNOSTICS_POP();
     }
-    return this->data_;
+    return std::forward<U>(default_value);
   }
 
-  template <
-      typename U,
-      typename... Args,
-      std::enable_if_t<
-          std::is_constructible<T, std::initializer_list<U>&, Args&&...>::value,
-          int> = 0>
-  T& emplace(std::initializer_list<U> ilist, Args&&... args) {
+  template <typename U>
+  constexpr T value_or(U&& default_value) && {
     if (ok()) {
-      this->Clear();
-      this->MakeValue(ilist, std::forward<Args>(args)...);
-    } else {
-      this->MakeValue(ilist, std::forward<Args>(args)...);
-      this->status_ = OkStatus();
+      return std::move(value_);
     }
-    return this->data_;
+    return std::forward<U>(default_value);
   }
 
  private:
-  using Base::Assign;
-  template <typename U>
-  constexpr void Assign(const Result<U>& other);
-  template <typename U>
-  constexpr void Assign(Result<U>&& other);
+  struct Dummy {};
+
+  union {
+    T value_;
+
+    // Ensure that there is always a trivial constructor for the union.
+    Dummy dummy_;
+  };
+  Status status_;
 };
 
-// operator==()
-//
-// This operator checks the equality of two `Result<T>` objects.
-template <typename T>
-constexpr bool operator==(const Result<T>& lhs, const Result<T>& rhs) {
-  if (lhs.ok() && rhs.ok()) {
-    return *lhs == *rhs;
-  }
-  return lhs.status() == rhs.status();
-}
-
-// operator!=()
-//
-// This operator checks the inequality of two `Result<T>` objects.
-template <typename T>
-constexpr bool operator!=(const Result<T>& lhs, const Result<T>& rhs) {
-  return !(lhs == rhs);
-}
-
-//------------------------------------------------------------------------------
-// Implementation details for Result<T>
-//------------------------------------------------------------------------------
-
-template <typename T>
-constexpr Result<T>::Result() : Base(Status::Unknown()) {}
-
-template <typename T>
-template <typename U>
-constexpr inline void Result<T>::Assign(const Result<U>& other) {
-  if (other.ok()) {
-    this->Assign(*other);
-  } else {
-    this->AssignStatus(other.status());
-  }
-}
-
-template <typename T>
-template <typename U>
-constexpr inline void Result<T>::Assign(Result<U>&& other) {
-  if (other.ok()) {
-    this->Assign(*std::move(other));
-  } else {
-    this->AssignStatus(std::move(other).status());
-  }
-}
-template <typename T>
-template <typename... Args>
-constexpr Result<T>::Result(std::in_place_t, Args&&... args)
-    : Base(std::in_place, std::forward<Args>(args)...) {}
-
-template <typename T>
-template <typename U, typename... Args>
-constexpr Result<T>::Result(std::in_place_t,
-                            std::initializer_list<U> ilist,
-                            Args&&... args)
-    : Base(std::in_place, ilist, std::forward<Args>(args)...) {}
-
-template <typename T>
-constexpr const Status& Result<T>::status() const& {
-  return this->status_;
-}
-template <typename T>
-constexpr Status Result<T>::status() && {
-  return ok() ? OkStatus() : std::move(this->status_);
-}
-
-template <typename T>
-constexpr const T& Result<T>::value() const& {
-  PW_ASSERT(this->status_.ok());
-  return this->data_;
-}
-
-template <typename T>
-constexpr T& Result<T>::value() & {
-  PW_ASSERT(this->status_.ok());
-  return this->data_;
-}
-
-template <typename T>
-constexpr const T&& Result<T>::value() const&& {
-  PW_ASSERT(this->status_.ok());
-  return std::move(this->data_);
-}
-
-template <typename T>
-constexpr T&& Result<T>::value() && {
-  PW_ASSERT(this->status_.ok());
-  return std::move(this->data_);
-}
-
-template <typename T>
-constexpr const T& Result<T>::operator*() const& {
-  PW_ASSERT(this->status_.ok());
-  return this->data_;
-}
-
-template <typename T>
-constexpr T& Result<T>::operator*() & {
-  PW_ASSERT(this->status_.ok());
-  return this->data_;
-}
-
-template <typename T>
-constexpr const T&& Result<T>::operator*() const&& {
-  PW_ASSERT(this->status_.ok());
-  return std::move(this->data_);
-}
-
-template <typename T>
-constexpr T&& Result<T>::operator*() && {
-  PW_ASSERT(this->status_.ok());
-  return std::move(this->data_);
-}
-
-template <typename T>
-constexpr const T* Result<T>::operator->() const {
-  PW_ASSERT(this->status_.ok());
-  return &this->data_;
-}
-
-template <typename T>
-constexpr T* Result<T>::operator->() {
-  PW_ASSERT(this->status_.ok());
-  return &this->data_;
-}
-
-template <typename T>
-template <typename U>
-constexpr T Result<T>::value_or(U&& default_value) const& {
-  if (ok()) {
-    return this->data_;
-  }
-  return std::forward<U>(default_value);
-}
-
-template <typename T>
-template <typename U>
-constexpr T Result<T>::value_or(U&& default_value) && {
-  if (ok()) {
-    return std::move(this->data_);
-  }
-  return std::forward<U>(default_value);
-}
-
-template <typename T>
-constexpr void Result<T>::IgnoreError() const {
-  // no-op
-}
-
-namespace internal {
-
-template <typename T>
-constexpr Status ConvertToStatus(const Result<T>& result) {
-  return result.status();
-}
-
-template <typename T>
-constexpr T ConvertToValue(Result<T>& result) {
-  return std::move(result.value());
-}
-
-}  // namespace internal
 }  // namespace pw
diff --git a/pw_result/result_test.cc b/pw_result/result_test.cc
index b65a3cf..e9095f5 100644
--- a/pw_result/result_test.cc
+++ b/pw_result/result_test.cc
@@ -1,4 +1,4 @@
-// Copyright 2022 The Pigweed Authors
+// Copyright 2020 The Pigweed Authors
 //
 // 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
@@ -12,18 +12,9 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-// pw::Result is derived from absl::StatusOr, but has some small differences.
-// This test covers basic pw::Result functionality and as well as the features
-// supported by pw::Result that are not supported by absl::StatusOr (constexpr
-// use in particular).
-//
-// The complete, thorough pw::Result tests are in statusor_test.cc, which is
-// derived from Abseil's tests for absl::StatusOr.
-
 #include "pw_result/result.h"
 
 #include "gtest/gtest.h"
-#include "pw_status/try.h"
 
 namespace pw {
 namespace {
@@ -48,38 +39,6 @@
   EXPECT_EQ(bad.value_or(42), 42);
 }
 
-TEST(Result, Deref) {
-  struct Tester {
-    constexpr bool True() { return true; };
-    constexpr bool False() { return false; };
-  };
-
-  auto tester = Result<Tester>(Tester());
-  EXPECT_TRUE(tester.ok());
-  EXPECT_TRUE(tester->True());
-  EXPECT_FALSE(tester->False());
-  EXPECT_TRUE((*tester).True());
-  EXPECT_FALSE((*tester).False());
-  EXPECT_EQ(tester.value().True(), tester->True());
-  EXPECT_EQ(tester.value().False(), tester->False());
-}
-
-TEST(Result, ConstDeref) {
-  struct Tester {
-    constexpr bool True() const { return true; };
-    constexpr bool False() const { return false; };
-  };
-
-  const auto tester = Result<Tester>(Tester());
-  EXPECT_TRUE(tester.ok());
-  EXPECT_TRUE(tester->True());
-  EXPECT_FALSE(tester->False());
-  EXPECT_TRUE((*tester).True());
-  EXPECT_FALSE((*tester).False());
-  EXPECT_EQ(tester.value().True(), tester->True());
-  EXPECT_EQ(tester.value().False(), tester->False());
-}
-
 TEST(Result, ConstructType) {
   struct Point {
     Point(int a, int b) : x(a), y(b) {}
@@ -113,72 +72,5 @@
   EXPECT_EQ(res.status(), Status::InvalidArgument());
 }
 
-Result<bool> ReturnResult(Result<bool> result) { return result; }
-
-Status TryResultAssign(Result<bool> result) {
-  PW_TRY_ASSIGN(const bool value, ReturnResult(result));
-
-  // Any status other than OK should have already returned.
-  EXPECT_EQ(result.status(), OkStatus());
-  EXPECT_EQ(value, result.value());
-  return result.status();
-}
-
-// TODO(pwbug/363): Once pw::Result has been refactored to properly support
-// non-default move and/or copy assignment operators and/or constructors, we
-// should add explicit tests to confirm this is properly handled by
-// PW_TRY_ASSIGN.
-TEST(Result, TryAssign) {
-  EXPECT_EQ(TryResultAssign(Status::Cancelled()), Status::Cancelled());
-  EXPECT_EQ(TryResultAssign(Status::DataLoss()), Status::DataLoss());
-  EXPECT_EQ(TryResultAssign(Status::Unimplemented()), Status::Unimplemented());
-  EXPECT_EQ(TryResultAssign(false), OkStatus());
-  EXPECT_EQ(TryResultAssign(true), OkStatus());
-}
-
-struct Value {
-  int number;
-};
-
-TEST(Result, ConstexprOk) {
-  static constexpr pw::Result<Value> kResult(Value{123});
-
-  static_assert(kResult.status() == pw::OkStatus());
-  static_assert(kResult.ok());
-
-  static_assert((*kResult).number == 123);
-  static_assert((*std::move(kResult)).number == 123);
-
-  static_assert(kResult->number == 123);
-  static_assert(std::move(kResult)->number == 123);
-
-  static_assert(kResult.value().number == 123);
-  static_assert(std::move(kResult).value().number == 123);
-
-  static_assert(kResult.value_or(Value{99}).number == 123);
-  static_assert(std::move(kResult).value_or(Value{99}).number == 123);
-}
-
-TEST(Result, ConstexprNotOk) {
-  static constexpr pw::Result<Value> kResult(pw::Status::NotFound());
-
-  static_assert(kResult.status() == pw::Status::NotFound());
-  static_assert(!kResult.ok());
-
-  static_assert(kResult.value_or(Value{99}).number == 99);
-  static_assert(std::move(kResult).value_or(Value{99}).number == 99);
-}
-
-TEST(Result, ConstexprNotOkCopy) {
-  static constexpr pw::Result<Value> kResult(pw::Status::NotFound());
-  constexpr pw::Result<Value> kResultCopy(kResult);
-
-  static_assert(kResultCopy.status() == pw::Status::NotFound());
-  static_assert(!kResultCopy.ok());
-
-  static_assert(kResultCopy.value_or(Value{99}).number == 99);
-  static_assert(std::move(kResultCopy).value_or(Value{99}).number == 99);
-}
-
 }  // namespace
 }  // namespace pw
diff --git a/pw_result/size_report/BUILD b/pw_result/size_report/BUILD
new file mode 100644
index 0000000..1a67937
--- /dev/null
+++ b/pw_result/size_report/BUILD
@@ -0,0 +1,78 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_binary",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_binary(
+    name = "pointer_simple",
+    srcs = ["pointer_simple.cc"],
+    deps = [
+        "//pw_result",
+        "//pw_log",
+    ],
+)
+
+pw_cc_binary(
+    name = "result_simple",
+    srcs = ["result_simple.cc"],
+    deps = [
+        "//pw_result",
+        "//pw_log",
+    ],
+)
+
+pw_cc_binary(
+    name = "pointer_noinline",
+    srcs = ["pointer_noinline.cc"],
+    deps = [
+        "//pw_result",
+        "//pw_log",
+    ],
+)
+
+pw_cc_binary(
+    name = "result_noinline",
+    srcs = ["result_noinline.cc"],
+    deps = [
+        "//pw_result",
+        "//pw_log",
+    ],
+)
+
+pw_cc_binary(
+    name = "pointer_read",
+    srcs = ["pointer_read.cc"],
+    deps = [
+        "//pw_result",
+        "//pw_log",
+        "//pw_span",
+    ],
+)
+
+pw_cc_binary(
+    name = "result_read",
+    srcs = ["result_read.cc"],
+    deps = [
+        "//pw_result",
+        "//pw_log",
+        "//pw_span",
+    ],
+)
diff --git a/pw_result/size_report/BUILD.bazel b/pw_result/size_report/BUILD.bazel
deleted file mode 100644
index 6853da2..0000000
--- a/pw_result/size_report/BUILD.bazel
+++ /dev/null
@@ -1,80 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_binary",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_binary(
-    name = "pointer_simple",
-    srcs = ["pointer_simple.cc"],
-    deps = [
-        "//pw_log",
-        "//pw_result",
-    ],
-)
-
-pw_cc_binary(
-    name = "result_simple",
-    srcs = ["result_simple.cc"],
-    deps = [
-        "//pw_log",
-        "//pw_result",
-    ],
-)
-
-pw_cc_binary(
-    name = "pointer_noinline",
-    srcs = ["pointer_noinline.cc"],
-    deps = [
-        "//pw_log",
-        "//pw_result",
-    ],
-)
-
-pw_cc_binary(
-    name = "result_noinline",
-    srcs = ["result_noinline.cc"],
-    deps = [
-        "//pw_log",
-        "//pw_result",
-    ],
-)
-
-pw_cc_binary(
-    name = "pointer_read",
-    srcs = ["pointer_read.cc"],
-    deps = [
-        "//pw_bytes",
-        "//pw_log",
-        "//pw_result",
-        "//pw_span",
-    ],
-)
-
-pw_cc_binary(
-    name = "result_read",
-    srcs = ["result_read.cc"],
-    deps = [
-        "//pw_bytes",
-        "//pw_log",
-        "//pw_result",
-        "//pw_span",
-    ],
-)
diff --git a/pw_result/statusor_test.cc b/pw_result/statusor_test.cc
deleted file mode 100644
index 62dab32..0000000
--- a/pw_result/statusor_test.cc
+++ /dev/null
@@ -1,1735 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// These tests are a modified version of the tests for absl::StatusOr:
-// inclusive-language: disable
-// https://github.com/abseil/abseil-cpp/blob/master/absl/status/statusor_test.cc
-// inclusive-language: enable
-
-#include <any>
-#include <array>
-#include <initializer_list>
-#include <map>
-#include <memory>
-#include <string>
-#include <string_view>
-#include <type_traits>
-#include <utility>
-#include <variant>
-#include <vector>
-
-#include "gtest/gtest.h"
-#include "pw_result/result.h"
-
-namespace {
-
-#define EXPECT_OK(expression) EXPECT_EQ(::pw::OkStatus(), expression)
-#define ASSERT_OK(expression) ASSERT_EQ(::pw::OkStatus(), expression)
-
-struct CopyDetector {
-  CopyDetector() = default;
-  explicit CopyDetector(int xx) : x(xx) {}
-  CopyDetector(CopyDetector&& d) noexcept
-      : x(d.x), copied(false), moved(true) {}
-  CopyDetector(const CopyDetector& d) : x(d.x), copied(true), moved(false) {}
-  CopyDetector& operator=(const CopyDetector& c) {
-    x = c.x;
-    copied = true;
-    moved = false;
-    return *this;
-  }
-  CopyDetector& operator=(CopyDetector&& c) noexcept {
-    x = c.x;
-    copied = false;
-    moved = true;
-    return *this;
-  }
-  int x = 0;
-  bool copied = false;
-  bool moved = false;
-};
-
-// Define custom macros instead of the CopyDetectorHas matcher.
-#define EXPECT_COPY_DETECTOR_HAS(                       \
-    value, expected_x, expected_moved, expected_copied) \
-  EXPECT_EQ(value.x, expected_x);                       \
-  EXPECT_EQ(value.moved, expected_moved);               \
-  EXPECT_EQ(value.copied, expected_copied)
-
-#define EXPECT_OK_AND_COPY_DETECTOR_HAS(                                      \
-    statusor_expr, expected_x, expected_moved, expected_copied)               \
-  do {                                                                        \
-    auto&& temp_status_or = statusor_expr;                                    \
-    ASSERT_EQ(::pw::OkStatus(), temp_status_or.status());                     \
-    EXPECT_COPY_DETECTOR_HAS(                                                 \
-        temp_status_or.value(), expected_x, expected_moved, expected_copied); \
-  } while (0)
-
-#define EXPECT_OK_AND_ANY_WITH_COPY_DETECTOR_HAS(                     \
-    statusor_expr, expected_x, expected_moved, expected_copied)       \
-  do {                                                                \
-    auto&& temp_status_or = statusor_expr;                            \
-    ASSERT_EQ(::pw::OkStatus(), temp_status_or.status());             \
-    const auto& temp_any_value =                                      \
-        std::any_cast<const CopyDetector&>(temp_status_or.value());   \
-    EXPECT_COPY_DETECTOR_HAS(                                         \
-        temp_any_value, expected_x, expected_moved, expected_copied); \
-  } while (0)
-
-class Base1 {
- public:
-  virtual ~Base1() {}
-  int pad;
-};
-
-class Base2 {
- public:
-  virtual ~Base2() {}
-  int yetotherpad;
-};
-
-class Derived : public Base1, public Base2 {
- public:
-  virtual ~Derived() {}
-  int evenmorepad;
-};
-
-class CopyNoAssign {
- public:
-  explicit CopyNoAssign(int value) : foo(value) {}
-  CopyNoAssign(const CopyNoAssign& other) : foo(other.foo) {}
-  const CopyNoAssign& operator=(const CopyNoAssign&) = delete;
-
-  int foo;
-};
-
-pw::Result<std::unique_ptr<int>> ReturnUniquePtr() {
-  // Uses implicit constructor from T&&
-  return std::make_unique<int>(0);
-}
-
-TEST(Result, ElementType) {
-  static_assert(std::is_same<pw::Result<int>::value_type, int>(), "");
-  static_assert(std::is_same<pw::Result<char>::value_type, char>(), "");
-}
-
-TEST(Result, TestMoveOnlyInitialization) {
-  pw::Result<std::unique_ptr<int>> thing(ReturnUniquePtr());
-  ASSERT_TRUE(thing.ok());
-  EXPECT_EQ(0, **thing);
-  int* previous = thing->get();
-
-  thing = ReturnUniquePtr();
-  EXPECT_TRUE(thing.ok());
-  EXPECT_EQ(0, **thing);
-  EXPECT_NE(previous, thing->get());
-}
-
-TEST(Result, TestMoveOnlyValueExtraction) {
-  pw::Result<std::unique_ptr<int>> thing(ReturnUniquePtr());
-  ASSERT_TRUE(thing.ok());
-  std::unique_ptr<int> ptr = *std::move(thing);
-  EXPECT_EQ(0, *ptr);
-
-  thing = std::move(ptr);
-  ptr = std::move(*thing);
-  EXPECT_EQ(0, *ptr);
-}
-
-TEST(Result, TestMoveOnlyInitializationFromTemporaryByValueOrDie) {
-  std::unique_ptr<int> ptr(*ReturnUniquePtr());
-  EXPECT_EQ(0, *ptr);
-}
-
-TEST(Result, TestValueOrDieOverloadForConstTemporary) {
-  static_assert(
-      std::is_same<const int&&,
-                   decltype(std::declval<const pw::Result<int>&&>().value())>(),
-      "value() for const temporaries should return const T&&");
-}
-
-TEST(Result, TestMoveOnlyConversion) {
-  pw::Result<std::unique_ptr<const int>> const_thing(ReturnUniquePtr());
-  EXPECT_TRUE(const_thing.ok());
-  EXPECT_EQ(0, **const_thing);
-
-  // Test rvalue converting assignment
-  const int* const_previous = const_thing->get();
-  const_thing = ReturnUniquePtr();
-  EXPECT_TRUE(const_thing.ok());
-  EXPECT_EQ(0, **const_thing);
-  EXPECT_NE(const_previous, const_thing->get());
-}
-
-TEST(Result, TestMoveOnlyVector) {
-  // Check that pw::Result<MoveOnly> works in vector.
-  std::vector<pw::Result<std::unique_ptr<int>>> vec;
-  vec.push_back(ReturnUniquePtr());
-  vec.resize(2);
-  auto another_vec = std::move(vec);
-  EXPECT_EQ(0, **another_vec[0]);
-  EXPECT_EQ(pw::Status::Unknown(), another_vec[1].status());
-}
-
-TEST(Result, TestDefaultCtor) {
-  pw::Result<int> thing;
-  EXPECT_FALSE(thing.ok());
-  EXPECT_EQ(thing.status().code(), pw::Status::Unknown().code());
-}
-
-TEST(Result, StatusCtorForwards) {
-  pw::Status status = pw::Status::Internal();
-
-  EXPECT_EQ(pw::Result<int>(status).status(), pw::Status::Internal());
-
-  EXPECT_EQ(pw::Result<int>(std::move(status)).status(),
-            pw::Status::Internal());
-}
-
-#define EXPECT_DEATH_OR_THROW(statement, status) \
-  EXPECT_DEATH_IF_SUPPORTED(statement, status.str());
-
-TEST(ResultDeathTest, TestDefaultCtorValue) {
-  pw::Result<int> thing;
-  EXPECT_DEATH_OR_THROW(thing.value(), pw::Status::Unknown());
-  const pw::Result<int> thing2;
-  EXPECT_DEATH_OR_THROW(thing2.value(), pw::Status::Unknown());
-}
-
-TEST(ResultDeathTest, TestValueNotOk) {
-  pw::Result<int> thing(pw::Status::Cancelled());
-  EXPECT_DEATH_OR_THROW(thing.value(), pw::Status::Cancelled());
-}
-
-TEST(ResultDeathTest, TestValueNotOkConst) {
-  const pw::Result<int> thing(pw::Status::Unknown());
-  EXPECT_DEATH_OR_THROW(thing.value(), pw::Status::Unknown());
-}
-
-TEST(ResultDeathTest, TestPointerDefaultCtorValue) {
-  pw::Result<int*> thing;
-  EXPECT_DEATH_OR_THROW(thing.value(), pw::Status::Unknown());
-}
-
-TEST(ResultDeathTest, TestPointerValueNotOk) {
-  pw::Result<int*> thing(pw::Status::Cancelled());
-  EXPECT_DEATH_OR_THROW(thing.value(), pw::Status::Cancelled());
-}
-
-TEST(ResultDeathTest, TestPointerValueNotOkConst) {
-  const pw::Result<int*> thing(pw::Status::Cancelled());
-  EXPECT_DEATH_OR_THROW(thing.value(), pw::Status::Cancelled());
-}
-
-#if GTEST_HAS_DEATH_TEST
-TEST(ResultDeathTest, TestStatusCtorStatusOk) {
-  EXPECT_DEBUG_DEATH(
-      {
-        // This will DCHECK
-        pw::Result<int> thing(pw::OkStatus());
-        // In optimized mode, we are actually going to get error::INTERNAL for
-        // status here, rather than crashing, so check that.
-        EXPECT_FALSE(thing.ok());
-        EXPECT_EQ(thing.status().code(), pw::Status::Internal().code());
-      },
-      "An OK status is not a valid constructor argument");
-}
-
-TEST(ResultDeathTest, TestPointerStatusCtorStatusOk) {
-  EXPECT_DEBUG_DEATH(
-      {
-        pw::Result<int*> thing(pw::OkStatus());
-        // In optimized mode, we are actually going to get error::INTERNAL for
-        // status here, rather than crashing, so check that.
-        EXPECT_FALSE(thing.ok());
-        EXPECT_EQ(thing.status().code(), pw::Status::Internal().code());
-      },
-      "An OK status is not a valid constructor argument");
-}
-#endif
-
-TEST(Result, ValueAccessor) {
-  const int kIntValue = 110;
-  {
-    pw::Result<int> status_or(kIntValue);
-    EXPECT_EQ(kIntValue, status_or.value());
-    EXPECT_EQ(kIntValue, std::move(status_or).value());
-  }
-  {
-    pw::Result<CopyDetector> status_or(kIntValue);
-    EXPECT_OK_AND_COPY_DETECTOR_HAS(status_or, kIntValue, false, false);
-    CopyDetector copy_detector = status_or.value();
-    EXPECT_COPY_DETECTOR_HAS(copy_detector, kIntValue, false, true);
-    copy_detector = std::move(status_or).value();
-    EXPECT_COPY_DETECTOR_HAS(copy_detector, kIntValue, true, false);
-  }
-}
-
-TEST(Result, BadValueAccess) {
-  const pw::Status kError = pw::Status::Cancelled();
-  pw::Result<int> status_or(kError);
-  EXPECT_DEATH_OR_THROW(status_or.value(), kError);
-}
-
-TEST(Result, TestStatusCtor) {
-  pw::Result<int> thing(pw::Status::Cancelled());
-  EXPECT_FALSE(thing.ok());
-  EXPECT_EQ(thing.status().code(), pw::Status::Cancelled().code());
-}
-
-TEST(Result, TestValueCtor) {
-  const int kI = 4;
-  const pw::Result<int> thing(kI);
-  EXPECT_TRUE(thing.ok());
-  EXPECT_EQ(kI, *thing);
-}
-
-struct Foo {
-  const int x;
-  explicit Foo(int y) : x(y) {}
-};
-
-TEST(Result, InPlaceConstruction) {
-  pw::Result<Foo> status_or(std::in_place, 10);
-  ASSERT_TRUE(status_or.ok());
-  EXPECT_EQ(status_or->x, 10);
-}
-
-struct InPlaceHelper {
-  InPlaceHelper(std::initializer_list<int> xs, std::unique_ptr<int> yy)
-      : x(xs), y(std::move(yy)) {}
-  const std::vector<int> x;
-  std::unique_ptr<int> y;
-};
-
-TEST(Result, InPlaceInitListConstruction) {
-  pw::Result<InPlaceHelper> status_or(
-      std::in_place, {10, 11, 12}, std::make_unique<int>(13));
-  ASSERT_TRUE(status_or.ok());
-  ASSERT_EQ(status_or->x.size(), 3u);
-  EXPECT_EQ(status_or->x[0], 10);
-  EXPECT_EQ(status_or->x[1], 11);
-  EXPECT_EQ(status_or->x[2], 12);
-  EXPECT_EQ(*(status_or->y), 13);
-}
-
-TEST(Result, Emplace) {
-  pw::Result<Foo> status_or_foo(10);
-  status_or_foo.emplace(20);
-
-  ASSERT_TRUE(status_or_foo.ok());
-  EXPECT_EQ(status_or_foo->x, 20);
-
-  status_or_foo = pw::Status::InvalidArgument();
-  EXPECT_FALSE(status_or_foo.ok());
-  EXPECT_EQ(status_or_foo.status().code(),
-            pw::Status::InvalidArgument().code());
-  status_or_foo.emplace(20);
-  ASSERT_TRUE(status_or_foo.ok());
-  EXPECT_EQ(status_or_foo->x, 20);
-}
-
-TEST(Result, EmplaceInitializerList) {
-  pw::Result<InPlaceHelper> status_or(
-      std::in_place, {10, 11, 12}, std::make_unique<int>(13));
-  status_or.emplace({1, 2, 3}, std::make_unique<int>(4));
-  ASSERT_TRUE(status_or.ok());
-  ASSERT_EQ(status_or->x.size(), 3u);
-  EXPECT_EQ(status_or->x[0], 1);
-  EXPECT_EQ(status_or->x[1], 2);
-  EXPECT_EQ(status_or->x[2], 3);
-  EXPECT_EQ(*(status_or->y), 4);
-
-  status_or = pw::Status::InvalidArgument();
-  EXPECT_FALSE(status_or.ok());
-  EXPECT_EQ(status_or.status().code(), pw::Status::InvalidArgument().code());
-
-  status_or.emplace({1, 2, 3}, std::make_unique<int>(4));
-  ASSERT_TRUE(status_or.ok());
-  ASSERT_EQ(status_or->x.size(), 3u);
-  EXPECT_EQ(status_or->x[0], 1);
-  EXPECT_EQ(status_or->x[1], 2);
-  EXPECT_EQ(status_or->x[2], 3);
-  EXPECT_EQ(*(status_or->y), 4);
-}
-
-TEST(Result, TestCopyCtorStatusOk) {
-  const int kI = 4;
-  const pw::Result<int> original(kI);
-  const pw::Result<int> copy(original);
-  EXPECT_OK(copy.status());
-  EXPECT_EQ(*original, *copy);
-}
-
-TEST(Result, TestCopyCtorStatusNotOk) {
-  pw::Result<int> original(pw::Status::Cancelled());
-  pw::Result<int> copy(original);
-  EXPECT_EQ(copy.status().code(), pw::Status::Cancelled().code());
-}
-
-TEST(Result, TestCopyCtorNonAssignable) {
-  const int kI = 4;
-  CopyNoAssign value(kI);
-  pw::Result<CopyNoAssign> original(value);
-  pw::Result<CopyNoAssign> copy(original);
-  EXPECT_OK(copy.status());
-  EXPECT_EQ(original->foo, copy->foo);
-}
-
-TEST(Result, TestCopyCtorStatusOKConverting) {
-  const int kI = 4;
-  pw::Result<int> original(kI);
-  pw::Result<double> copy(original);
-  EXPECT_OK(copy.status());
-  EXPECT_EQ(*original, *copy);
-}
-
-TEST(Result, TestCopyCtorStatusNotOkConverting) {
-  pw::Result<int> original(pw::Status::Cancelled());
-  pw::Result<double> copy(original);
-  EXPECT_EQ(copy.status(), original.status());
-}
-
-TEST(Result, TestAssignmentStatusOk) {
-  // Copy assignmment
-  {
-    const auto p = std::make_shared<int>(17);
-    pw::Result<std::shared_ptr<int>> source(p);
-
-    pw::Result<std::shared_ptr<int>> target;
-    target = source;
-
-    ASSERT_TRUE(target.ok());
-    EXPECT_OK(target.status());
-    EXPECT_EQ(p, *target);
-
-    ASSERT_TRUE(source.ok());
-    EXPECT_OK(source.status());
-    EXPECT_EQ(p, *source);
-  }
-
-  // Move asssignment
-  {
-    const auto p = std::make_shared<int>(17);
-    pw::Result<std::shared_ptr<int>> source(p);
-
-    pw::Result<std::shared_ptr<int>> target;
-    target = std::move(source);
-
-    ASSERT_TRUE(target.ok());
-    EXPECT_OK(target.status());
-    EXPECT_EQ(p, *target);
-
-    ASSERT_TRUE(source.ok());  // NOLINT(bugprone-use-after-move)
-    EXPECT_OK(source.status());
-    EXPECT_EQ(nullptr, *source);
-  }
-}
-
-TEST(Result, TestAssignmentStatusNotOk) {
-  // Copy assignment
-  {
-    const pw::Status expected = pw::Status::Cancelled();
-    pw::Result<int> source(expected);
-
-    pw::Result<int> target;
-    target = source;
-
-    EXPECT_FALSE(target.ok());
-    EXPECT_EQ(expected, target.status());
-
-    EXPECT_FALSE(source.ok());
-    EXPECT_EQ(expected, source.status());
-  }
-
-  // Move assignment
-  {
-    const pw::Status expected = pw::Status::Cancelled();
-    pw::Result<int> source(expected);
-
-    pw::Result<int> target;
-    target = std::move(source);
-
-    EXPECT_FALSE(target.ok());
-    EXPECT_EQ(expected, target.status());
-
-    EXPECT_FALSE(source.ok());  // NOLINT(bugprone-use-after-move)
-    // absl::Status sets itself to INTERNAL when moved, but pw::Status does not.
-    // EXPECT_EQ(source.status().code(), pw::Status::Internal().code());
-  }
-}
-
-TEST(Result, TestAssignmentStatusOKConverting) {
-  // Copy assignment
-  {
-    const int kI = 4;
-    pw::Result<int> source(kI);
-
-    pw::Result<double> target;
-    target = source;
-
-    ASSERT_TRUE(target.ok());
-    EXPECT_OK(target.status());
-    EXPECT_EQ(kI, *target);
-
-    ASSERT_TRUE(source.ok());
-    EXPECT_OK(source.status());
-    EXPECT_EQ(kI, *source);
-  }
-
-  // Move assignment
-  {
-    const auto p = new int(17);
-    pw::Result<std::unique_ptr<int>> source(p);
-
-    pw::Result<std::shared_ptr<int>> target;
-    target = std::move(source);
-
-    ASSERT_TRUE(target.ok());
-    EXPECT_OK(target.status());
-    EXPECT_EQ(p, target->get());
-
-    ASSERT_TRUE(source.ok());  // NOLINT(bugprone-use-after-move)
-    EXPECT_OK(source.status());
-    EXPECT_EQ(nullptr, source->get());
-  }
-}
-
-// implicit_cast
-template <class T>
-struct type_identity {
-  using type = T;
-};
-
-template <typename To>
-constexpr To implicit_cast(typename type_identity<To>::type to) {
-  return to;
-}
-
-struct A {
-  int x;
-};
-
-struct ImplicitConstructibleFromA {
-  int x;
-  bool moved;
-  ImplicitConstructibleFromA(const A& a)  // NOLINT
-      : x(a.x), moved(false) {}
-  ImplicitConstructibleFromA(A&& a)  // NOLINT
-      : x(a.x), moved(true) {}
-};
-
-TEST(Result, ImplicitConvertingConstructor) {
-  auto status_or = implicit_cast<pw::Result<ImplicitConstructibleFromA>>(
-      pw::Result<A>(A{11}));
-  ASSERT_OK(status_or.status());
-  EXPECT_EQ(status_or->x, 11);
-  EXPECT_TRUE(status_or->moved);
-
-  pw::Result<A> a(A{12});
-  auto status_or_2 = implicit_cast<pw::Result<ImplicitConstructibleFromA>>(a);
-  ASSERT_OK(status_or_2.status());
-  EXPECT_EQ(status_or_2->x, 12);
-  EXPECT_FALSE(status_or_2->moved);
-}
-
-struct ExplicitConstructibleFromA {
-  int x;
-  bool moved;
-  explicit ExplicitConstructibleFromA(const A& a) : x(a.x), moved(false) {}
-  explicit ExplicitConstructibleFromA(A&& a) : x(a.x), moved(true) {}
-};
-
-TEST(Result, ExplicitConvertingConstructor) {
-  EXPECT_FALSE(
-      (std::is_convertible<const pw::Result<A>&,
-                           pw::Result<ExplicitConstructibleFromA>>::value));
-  EXPECT_FALSE(
-      (std::is_convertible<pw::Result<A>&&,
-                           pw::Result<ExplicitConstructibleFromA>>::value));
-  auto a1 = pw::Result<ExplicitConstructibleFromA>(pw::Result<A>(A{11}));
-  ASSERT_OK(a1.status());
-  EXPECT_EQ(a1->x, 11);
-  EXPECT_TRUE(a1->moved);
-
-  pw::Result<A> a(A{12});
-  auto a2 = pw::Result<ExplicitConstructibleFromA>(a);
-  ASSERT_OK(a2.status());
-  EXPECT_EQ(a2->x, 12);
-  EXPECT_FALSE(a2->moved);
-}
-
-struct ImplicitConstructibleFromBool {
-  ImplicitConstructibleFromBool(bool y) : x(y) {}  // NOLINT
-  bool x = false;
-};
-
-struct ConvertibleToBool {
-  explicit ConvertibleToBool(bool y) : x(y) {}
-  operator bool() const { return x; }  // NOLINT
-  bool x = false;
-};
-
-TEST(Result, ImplicitBooleanConstructionWithImplicitCasts) {
-  auto a = pw::Result<bool>(pw::Result<ConvertibleToBool>(true));
-  ASSERT_OK(a.status());
-  EXPECT_TRUE(*a);
-
-  auto b = pw::Result<bool>(pw::Result<ConvertibleToBool>(false));
-  ASSERT_OK(b.status());
-  EXPECT_FALSE(*b);
-
-  auto c = pw::Result<ImplicitConstructibleFromBool>(pw::Result<bool>(false));
-  ASSERT_OK(c.status());
-  EXPECT_EQ(c->x, false);
-  EXPECT_FALSE(
-      (std::is_convertible<pw::Result<ConvertibleToBool>,
-                           pw::Result<ImplicitConstructibleFromBool>>::value));
-}
-
-TEST(Result, BooleanConstructionWithImplicitCasts) {
-  auto a = pw::Result<bool>(pw::Result<ConvertibleToBool>(true));
-  ASSERT_OK(a.status());
-  EXPECT_TRUE(*a);
-
-  auto b = pw::Result<bool>(pw::Result<ConvertibleToBool>(false));
-  ASSERT_OK(b.status());
-  EXPECT_FALSE(*b);
-
-  auto c = pw::Result<ImplicitConstructibleFromBool>{pw::Result<bool>(false)};
-  ASSERT_OK(c.status());
-  EXPECT_FALSE(c->x);
-
-  auto d = pw::Result<ImplicitConstructibleFromBool>{
-      pw::Result<bool>(pw::Status::InvalidArgument())};
-  EXPECT_FALSE(d.ok());
-
-  auto e = pw::Result<ImplicitConstructibleFromBool>{
-      pw::Result<ConvertibleToBool>(ConvertibleToBool{false})};
-  ASSERT_OK(e.status());
-  EXPECT_FALSE(e->x);
-
-  auto f = pw::Result<ImplicitConstructibleFromBool>{
-      pw::Result<ConvertibleToBool>(pw::Status::InvalidArgument())};
-  EXPECT_FALSE(f.ok());
-}
-
-TEST(Result, ConstImplicitCast) {
-  auto a = implicit_cast<pw::Result<bool>>(pw::Result<const bool>(true));
-  ASSERT_OK(a.status());
-  EXPECT_TRUE(*a);
-  auto b = implicit_cast<pw::Result<bool>>(pw::Result<const bool>(false));
-  ASSERT_OK(b.status());
-  EXPECT_FALSE(*b);
-  auto c = implicit_cast<pw::Result<const bool>>(pw::Result<bool>(true));
-  ASSERT_OK(c.status());
-  EXPECT_TRUE(*c);
-  auto d = implicit_cast<pw::Result<const bool>>(pw::Result<bool>(false));
-  ASSERT_OK(d.status());
-  EXPECT_FALSE(*d);
-  auto e = implicit_cast<pw::Result<const std::string>>(
-      pw::Result<std::string>("foo"));
-  ASSERT_OK(e.status());
-  EXPECT_EQ(*e, "foo");
-  auto f = implicit_cast<pw::Result<std::string>>(
-      pw::Result<const std::string>("foo"));
-  ASSERT_OK(f.status());
-  EXPECT_EQ(*f, "foo");
-  auto g = implicit_cast<pw::Result<std::shared_ptr<const std::string>>>(
-      pw::Result<std::shared_ptr<std::string>>(
-          std::make_shared<std::string>("foo")));
-  ASSERT_OK(g.status());
-  EXPECT_EQ(*(*g), "foo");
-}
-
-TEST(Result, ConstExplicitConstruction) {
-  auto a = pw::Result<bool>(pw::Result<const bool>(true));
-  ASSERT_OK(a.status());
-  EXPECT_TRUE(*a);
-  auto b = pw::Result<bool>(pw::Result<const bool>(false));
-  ASSERT_OK(b.status());
-  EXPECT_FALSE(*b);
-  auto c = pw::Result<const bool>(pw::Result<bool>(true));
-  ASSERT_OK(c.status());
-  EXPECT_TRUE(*c);
-  auto d = pw::Result<const bool>(pw::Result<bool>(false));
-  ASSERT_OK(d.status());
-  EXPECT_FALSE(*d);
-}
-
-struct ExplicitConstructibleFromInt {
-  int x;
-  explicit ExplicitConstructibleFromInt(int y) : x(y) {}
-};
-
-TEST(Result, ExplicitConstruction) {
-  auto a = pw::Result<ExplicitConstructibleFromInt>(10);
-  ASSERT_OK(a.status());
-  EXPECT_EQ(a->x, 10);
-}
-
-TEST(Result, ImplicitConstruction) {
-  // Check implicit casting works.
-  auto status_or =
-      implicit_cast<pw::Result<std::variant<int, std::string>>>(10);
-  ASSERT_OK(status_or.status());
-  EXPECT_EQ(std::get<int>(*status_or), 10);
-}
-
-TEST(Result, ImplicitConstructionFromInitliazerList) {
-  // Note: dropping the explicit std::initializer_list<int> is not supported
-  // by pw::Result or std::optional.
-  auto status_or = implicit_cast<pw::Result<std::vector<int>>>({{10, 20, 30}});
-  ASSERT_OK(status_or.status());
-  ASSERT_EQ(status_or->size(), 3u);
-  EXPECT_EQ((*status_or)[0], 10);
-  EXPECT_EQ((*status_or)[1], 20);
-  EXPECT_EQ((*status_or)[2], 30);
-}
-
-TEST(Result, UniquePtrImplicitConstruction) {
-  auto status_or = implicit_cast<pw::Result<std::unique_ptr<Base1>>>(
-      std::make_unique<Derived>());
-  ASSERT_OK(status_or.status());
-  EXPECT_NE(status_or->get(), nullptr);
-}
-
-TEST(Result, NestedResultCopyAndMoveConstructorTests) {
-  pw::Result<pw::Result<CopyDetector>> status_or = CopyDetector(10);
-  pw::Result<pw::Result<CopyDetector>> status_error =
-      pw::Status::InvalidArgument();
-  ASSERT_OK(status_or.status());
-  EXPECT_OK_AND_COPY_DETECTOR_HAS(*status_or, 10, true, false);
-  pw::Result<pw::Result<CopyDetector>> a = status_or;
-  EXPECT_OK_AND_COPY_DETECTOR_HAS(*a, 10, false, true);
-  pw::Result<pw::Result<CopyDetector>> a_err = status_error;
-  EXPECT_FALSE(a_err.ok());
-
-  const pw::Result<pw::Result<CopyDetector>>& cref = status_or;
-  pw::Result<pw::Result<CopyDetector>> b = cref;  // NOLINT
-  ASSERT_OK(b.status());
-  EXPECT_OK_AND_COPY_DETECTOR_HAS(*b, 10, false, true);
-  const pw::Result<pw::Result<CopyDetector>>& cref_err = status_error;
-  pw::Result<pw::Result<CopyDetector>> b_err = cref_err;  // NOLINT
-  EXPECT_FALSE(b_err.ok());
-
-  pw::Result<pw::Result<CopyDetector>> c = std::move(status_or);
-  ASSERT_OK(c.status());
-  EXPECT_OK_AND_COPY_DETECTOR_HAS(*c, 10, true, false);
-  pw::Result<pw::Result<CopyDetector>> c_err = std::move(status_error);
-  EXPECT_FALSE(c_err.ok());
-}
-
-TEST(Result, NestedResultCopyAndMoveAssignment) {
-  pw::Result<pw::Result<CopyDetector>> status_or = CopyDetector(10);
-  pw::Result<pw::Result<CopyDetector>> status_error =
-      pw::Status::InvalidArgument();
-  pw::Result<pw::Result<CopyDetector>> a;
-  a = status_or;
-  ASSERT_TRUE(a.ok());
-  EXPECT_OK_AND_COPY_DETECTOR_HAS(*a, 10, false, true);
-  a = status_error;
-  EXPECT_FALSE(a.ok());
-
-  const pw::Result<pw::Result<CopyDetector>>& cref = status_or;
-  a = cref;
-  ASSERT_TRUE(a.ok());
-  EXPECT_OK_AND_COPY_DETECTOR_HAS(*a, 10, false, true);
-  const pw::Result<pw::Result<CopyDetector>>& cref_err = status_error;
-  a = cref_err;
-  EXPECT_FALSE(a.ok());
-  a = std::move(status_or);
-  ASSERT_TRUE(a.ok());
-  EXPECT_OK_AND_COPY_DETECTOR_HAS(*a, 10, true, false);
-  a = std::move(status_error);
-  EXPECT_FALSE(a.ok());
-}
-
-struct Copyable {
-  Copyable() {}
-  Copyable(const Copyable&) {}
-  Copyable& operator=(const Copyable&) { return *this; }
-};
-
-struct MoveOnly {
-  MoveOnly() {}
-  MoveOnly(MoveOnly&&) {}
-  MoveOnly& operator=(MoveOnly&&) { return *this; }
-};
-
-struct NonMovable {
-  NonMovable() {}
-  NonMovable(const NonMovable&) = delete;
-  NonMovable(NonMovable&&) = delete;
-  NonMovable& operator=(const NonMovable&) = delete;
-  NonMovable& operator=(NonMovable&&) = delete;
-};
-
-TEST(Result, CopyAndMoveAbility) {
-  EXPECT_TRUE(std::is_copy_constructible<Copyable>::value);
-  EXPECT_TRUE(std::is_copy_assignable<Copyable>::value);
-  EXPECT_TRUE(std::is_move_constructible<Copyable>::value);
-  EXPECT_TRUE(std::is_move_assignable<Copyable>::value);
-  EXPECT_FALSE(std::is_copy_constructible<MoveOnly>::value);
-  EXPECT_FALSE(std::is_copy_assignable<MoveOnly>::value);
-  EXPECT_TRUE(std::is_move_constructible<MoveOnly>::value);
-  EXPECT_TRUE(std::is_move_assignable<MoveOnly>::value);
-  EXPECT_FALSE(std::is_copy_constructible<NonMovable>::value);
-  EXPECT_FALSE(std::is_copy_assignable<NonMovable>::value);
-  EXPECT_FALSE(std::is_move_constructible<NonMovable>::value);
-  EXPECT_FALSE(std::is_move_assignable<NonMovable>::value);
-}
-
-TEST(Result, ResultAnyCopyAndMoveConstructorTests) {
-  pw::Result<std::any> status_or = CopyDetector(10);
-  pw::Result<std::any> status_error = pw::Status::InvalidArgument();
-  EXPECT_OK_AND_ANY_WITH_COPY_DETECTOR_HAS(status_or, 10, true, false);
-  pw::Result<std::any> a = status_or;
-  EXPECT_OK_AND_ANY_WITH_COPY_DETECTOR_HAS(a, 10, false, true);
-  pw::Result<std::any> a_err = status_error;
-  EXPECT_FALSE(a_err.ok());
-
-  const pw::Result<std::any>& cref = status_or;
-  // No lint for no-change copy.
-  pw::Result<std::any> b = cref;  // NOLINT
-  EXPECT_OK_AND_ANY_WITH_COPY_DETECTOR_HAS(b, 10, false, true);
-  const pw::Result<std::any>& cref_err = status_error;
-  // No lint for no-change copy.
-  pw::Result<std::any> b_err = cref_err;  // NOLINT
-  EXPECT_FALSE(b_err.ok());
-
-  pw::Result<std::any> c = std::move(status_or);
-  EXPECT_OK_AND_ANY_WITH_COPY_DETECTOR_HAS(c, 10, true, false);
-  pw::Result<std::any> c_err = std::move(status_error);
-  EXPECT_FALSE(c_err.ok());
-}
-
-TEST(Result, ResultAnyCopyAndMoveAssignment) {
-  pw::Result<std::any> status_or = CopyDetector(10);
-  pw::Result<std::any> status_error = pw::Status::InvalidArgument();
-  pw::Result<std::any> a;
-  a = status_or;
-  EXPECT_OK_AND_ANY_WITH_COPY_DETECTOR_HAS(a, 10, false, true);
-  a = status_error;
-  EXPECT_FALSE(a.ok());
-
-  const pw::Result<std::any>& cref = status_or;
-  a = cref;
-  EXPECT_OK_AND_ANY_WITH_COPY_DETECTOR_HAS(a, 10, false, true);
-  const pw::Result<std::any>& cref_err = status_error;
-  a = cref_err;
-  EXPECT_FALSE(a.ok());
-  a = std::move(status_or);
-  EXPECT_OK_AND_ANY_WITH_COPY_DETECTOR_HAS(a, 10, true, false);
-  a = std::move(status_error);
-  EXPECT_FALSE(a.ok());
-}
-
-TEST(Result, ResultCopyAndMoveTestsConstructor) {
-  pw::Result<CopyDetector> status_or(10);
-  EXPECT_OK_AND_COPY_DETECTOR_HAS(status_or, 10, false, false);
-  pw::Result<CopyDetector> a(status_or);
-  EXPECT_OK_AND_COPY_DETECTOR_HAS(a, 10, false, true);
-  const pw::Result<CopyDetector>& cref = status_or;
-  pw::Result<CopyDetector> b(cref);  // NOLINT
-  EXPECT_OK_AND_COPY_DETECTOR_HAS(b, 10, false, true);
-  pw::Result<CopyDetector> c(std::move(status_or));
-  EXPECT_OK_AND_COPY_DETECTOR_HAS(c, 10, true, false);
-}
-
-TEST(Result, ResultCopyAndMoveTestsAssignment) {
-  pw::Result<CopyDetector> status_or(10);
-  EXPECT_OK_AND_COPY_DETECTOR_HAS(status_or, 10, false, false);
-  pw::Result<CopyDetector> a;
-  a = status_or;
-  EXPECT_OK_AND_COPY_DETECTOR_HAS(a, 10, false, true);
-  const pw::Result<CopyDetector>& cref = status_or;
-  pw::Result<CopyDetector> b;
-  b = cref;
-  EXPECT_OK_AND_COPY_DETECTOR_HAS(b, 10, false, true);
-  pw::Result<CopyDetector> c;
-  c = std::move(status_or);
-  EXPECT_OK_AND_COPY_DETECTOR_HAS(c, 10, true, false);
-}
-
-TEST(Result, StdAnyAssignment) {
-  EXPECT_FALSE(
-      (std::is_assignable<pw::Result<std::any>, pw::Result<int>>::value));
-  pw::Result<std::any> status_or;
-  status_or = pw::Status::InvalidArgument();
-  EXPECT_FALSE(status_or.ok());
-}
-
-TEST(Result, ImplicitAssignment) {
-  pw::Result<std::variant<int, std::string>> status_or;
-  status_or = 10;
-  ASSERT_OK(status_or.status());
-  EXPECT_EQ(std::get<int>(*status_or), 10);
-}
-
-TEST(Result, SelfDirectInitAssignment) {
-  pw::Result<std::vector<int>> status_or = {{10, 20, 30}};
-  status_or = *status_or;
-  ASSERT_OK(status_or.status());
-  ASSERT_EQ(status_or->size(), 3u);
-  EXPECT_EQ((*status_or)[0], 10);
-  EXPECT_EQ((*status_or)[1], 20);
-  EXPECT_EQ((*status_or)[2], 30);
-}
-
-TEST(Result, ImplicitCastFromInitializerList) {
-  pw::Result<std::vector<int>> status_or = {{10, 20, 30}};
-  ASSERT_OK(status_or.status());
-  ASSERT_EQ(status_or->size(), 3u);
-  EXPECT_EQ((*status_or)[0], 10);
-  EXPECT_EQ((*status_or)[1], 20);
-  EXPECT_EQ((*status_or)[2], 30);
-}
-
-TEST(Result, UniquePtrImplicitAssignment) {
-  pw::Result<std::unique_ptr<Base1>> status_or;
-  status_or = std::make_unique<Derived>();
-  ASSERT_OK(status_or.status());
-  EXPECT_NE(status_or->get(), nullptr);
-}
-
-TEST(Result, Pointer) {
-  struct Base {};
-  struct B : public Base {};
-  struct C : private Base {};
-
-  EXPECT_TRUE((std::is_constructible<pw::Result<Base*>, B*>::value));
-  EXPECT_TRUE((std::is_convertible<B*, pw::Result<Base*>>::value));
-  EXPECT_FALSE((std::is_constructible<pw::Result<Base*>, C*>::value));
-  EXPECT_FALSE((std::is_convertible<C*, pw::Result<Base*>>::value));
-}
-
-TEST(Result, TestAssignmentStatusNotOkConverting) {
-  // Copy assignment
-  {
-    const pw::Status expected = pw::Status::Cancelled();
-    pw::Result<int> source(expected);
-
-    pw::Result<double> target;
-    target = source;
-
-    EXPECT_FALSE(target.ok());
-    EXPECT_EQ(expected, target.status());
-
-    EXPECT_FALSE(source.ok());
-    EXPECT_EQ(expected, source.status());
-  }
-
-  // Move assignment
-  {
-    const pw::Status expected = pw::Status::Cancelled();
-    pw::Result<int> source(expected);
-
-    pw::Result<double> target;
-    target = std::move(source);
-
-    EXPECT_FALSE(target.ok());
-    EXPECT_EQ(expected, target.status());
-
-    EXPECT_FALSE(source.ok());  // NOLINT(bugprone-use-after-move)
-
-    // absl::Status sets itself to INTERNAL when moved, but pw::Status does not.
-    // EXPECT_EQ(source.status().code(), pw::Status::Internal().code());
-  }
-}
-
-TEST(Result, SelfAssignment) {
-  // Copy-assignment, status OK
-  {
-    // A string long enough that it's likely to defeat any inline representation
-    // optimization.
-    const std::string long_str(128, 'a');
-
-    pw::Result<std::string> so = long_str;
-    so = *&so;
-
-    ASSERT_TRUE(so.ok());
-    EXPECT_OK(so.status());
-    EXPECT_EQ(long_str, *so);
-  }
-
-  // Copy-assignment, error status
-  {
-    pw::Result<int> so = pw::Status::NotFound();
-    so = *&so;
-
-    EXPECT_FALSE(so.ok());
-    EXPECT_EQ(so.status().code(), pw::Status::NotFound().code());
-  }
-
-  // Move-assignment with copyable type, status OK
-  {
-    pw::Result<int> so = 17;
-
-    // Fool the compiler, which otherwise complains.
-    auto& same = so;
-    so = std::move(same);
-
-    ASSERT_TRUE(so.ok());
-    EXPECT_OK(so.status());
-    EXPECT_EQ(17, *so);
-  }
-
-  // Move-assignment with copyable type, error status
-  {
-    pw::Result<int> so = pw::Status::NotFound();
-
-    // Fool the compiler, which otherwise complains.
-    auto& same = so;
-    so = std::move(same);
-
-    EXPECT_FALSE(so.ok());
-    EXPECT_EQ(so.status().code(), pw::Status::NotFound().code());
-  }
-
-  // Move-assignment with non-copyable type, status OK
-  {
-    const auto raw = new int(17);
-    pw::Result<std::unique_ptr<int>> so = std::unique_ptr<int>(raw);
-
-    // Fool the compiler, which otherwise complains.
-    auto& same = so;
-    so = std::move(same);
-
-    ASSERT_TRUE(so.ok());
-    EXPECT_OK(so.status());
-    EXPECT_EQ(raw, so->get());
-  }
-
-  // Move-assignment with non-copyable type, error status
-  {
-    pw::Result<std::unique_ptr<int>> so = pw::Status::NotFound();
-
-    // Fool the compiler, which otherwise complains.
-    auto& same = so;
-    so = std::move(same);
-
-    EXPECT_FALSE(so.ok());
-    EXPECT_EQ(so.status().code(), pw::Status::NotFound().code());
-  }
-}
-
-// These types form the overload sets of the constructors and the assignment
-// operators of `MockValue`. They distinguish construction from assignment,
-// lvalue from rvalue.
-struct FromConstructibleAssignableLvalue {};
-struct FromConstructibleAssignableRvalue {};
-struct FromImplicitConstructibleOnly {};
-struct FromAssignableOnly {};
-
-// This class is for testing the forwarding value assignments of `Result`.
-// `from_rvalue` indicates whether the constructor or the assignment taking
-// rvalue reference is called. `from_assignment` indicates whether any
-// assignment is called.
-struct MockValue {
-  // Constructs `MockValue` from `FromConstructibleAssignableLvalue`.
-  MockValue(const FromConstructibleAssignableLvalue&)  // NOLINT
-      : from_rvalue(false), assigned(false) {}
-  // Constructs `MockValue` from `FromConstructibleAssignableRvalue`.
-  MockValue(FromConstructibleAssignableRvalue&&)  // NOLINT
-      : from_rvalue(true), assigned(false) {}
-  // Constructs `MockValue` from `FromImplicitConstructibleOnly`.
-  // `MockValue` is not assignable from `FromImplicitConstructibleOnly`.
-  MockValue(const FromImplicitConstructibleOnly&)  // NOLINT
-      : from_rvalue(false), assigned(false) {}
-  // Assigns `FromConstructibleAssignableLvalue`.
-  MockValue& operator=(const FromConstructibleAssignableLvalue&) {
-    from_rvalue = false;
-    assigned = true;
-    return *this;
-  }
-  // Assigns `FromConstructibleAssignableRvalue` (rvalue only).
-  MockValue& operator=(FromConstructibleAssignableRvalue&&) {
-    from_rvalue = true;
-    assigned = true;
-    return *this;
-  }
-  // Assigns `FromAssignableOnly`, but not constructible from
-  // `FromAssignableOnly`.
-  MockValue& operator=(const FromAssignableOnly&) {
-    from_rvalue = false;
-    assigned = true;
-    return *this;
-  }
-  bool from_rvalue;
-  bool assigned;
-};
-
-// operator=(U&&)
-TEST(Result, PerfectForwardingAssignment) {
-  // U == T
-  constexpr int kValue1 = 10, kValue2 = 20;
-  pw::Result<CopyDetector> status_or;
-  CopyDetector lvalue(kValue1);
-  status_or = lvalue;
-  EXPECT_OK_AND_COPY_DETECTOR_HAS(status_or, kValue1, false, true);
-  status_or = CopyDetector(kValue2);
-  EXPECT_OK_AND_COPY_DETECTOR_HAS(status_or, kValue2, true, false);
-
-  // U != T
-  EXPECT_TRUE(
-      (std::is_assignable<pw::Result<MockValue>&,
-                          const FromConstructibleAssignableLvalue&>::value));
-  EXPECT_TRUE((std::is_assignable<pw::Result<MockValue>&,
-                                  FromConstructibleAssignableLvalue&&>::value));
-  EXPECT_FALSE(
-      (std::is_assignable<pw::Result<MockValue>&,
-                          const FromConstructibleAssignableRvalue&>::value));
-  EXPECT_TRUE((std::is_assignable<pw::Result<MockValue>&,
-                                  FromConstructibleAssignableRvalue&&>::value));
-  EXPECT_TRUE(
-      (std::is_assignable<pw::Result<MockValue>&,
-                          const FromImplicitConstructibleOnly&>::value));
-  EXPECT_FALSE((std::is_assignable<pw::Result<MockValue>&,
-                                   const FromAssignableOnly&>::value));
-
-  pw::Result<MockValue> from_lvalue(FromConstructibleAssignableLvalue{});
-  EXPECT_FALSE(from_lvalue->from_rvalue);
-  EXPECT_FALSE(from_lvalue->assigned);
-  from_lvalue = FromConstructibleAssignableLvalue{};
-  EXPECT_FALSE(from_lvalue->from_rvalue);
-  EXPECT_TRUE(from_lvalue->assigned);
-
-  pw::Result<MockValue> from_rvalue(FromConstructibleAssignableRvalue{});
-  EXPECT_TRUE(from_rvalue->from_rvalue);
-  EXPECT_FALSE(from_rvalue->assigned);
-  from_rvalue = FromConstructibleAssignableRvalue{};
-  EXPECT_TRUE(from_rvalue->from_rvalue);
-  EXPECT_TRUE(from_rvalue->assigned);
-
-  pw::Result<MockValue> from_implicit_constructible(
-      FromImplicitConstructibleOnly{});
-  EXPECT_FALSE(from_implicit_constructible->from_rvalue);
-  EXPECT_FALSE(from_implicit_constructible->assigned);
-  // construct a temporary `Result` object and invoke the `Result` move
-  // assignment operator.
-  from_implicit_constructible = FromImplicitConstructibleOnly{};
-  EXPECT_FALSE(from_implicit_constructible->from_rvalue);
-  EXPECT_FALSE(from_implicit_constructible->assigned);
-}
-
-TEST(Result, TestStatus) {
-  pw::Result<int> good(4);
-  EXPECT_TRUE(good.ok());
-  pw::Result<int> bad(pw::Status::Cancelled());
-  EXPECT_FALSE(bad.ok());
-  EXPECT_EQ(bad.status().code(), pw::Status::Cancelled().code());
-}
-
-TEST(Result, OperatorStarRefQualifiers) {
-  static_assert(
-      std::is_same<const int&,
-                   decltype(*std::declval<const pw::Result<int>&>())>(),
-      "Unexpected ref-qualifiers");
-  static_assert(
-      std::is_same<int&, decltype(*std::declval<pw::Result<int>&>())>(),
-      "Unexpected ref-qualifiers");
-  static_assert(
-      std::is_same<const int&&,
-                   decltype(*std::declval<const pw::Result<int>&&>())>(),
-      "Unexpected ref-qualifiers");
-  static_assert(
-      std::is_same<int&&, decltype(*std::declval<pw::Result<int>&&>())>(),
-      "Unexpected ref-qualifiers");
-}
-
-TEST(Result, OperatorStar) {
-  const pw::Result<std::string> const_lvalue("hello");
-  EXPECT_EQ("hello", *const_lvalue);
-
-  pw::Result<std::string> lvalue("hello");
-  EXPECT_EQ("hello", *lvalue);
-
-  // Note: Recall that std::move() is equivalent to a static_cast to an rvalue
-  // reference type.
-  const pw::Result<std::string> const_rvalue("hello");
-  EXPECT_EQ("hello", *std::move(const_rvalue));  // NOLINT
-
-  pw::Result<std::string> rvalue("hello");
-  EXPECT_EQ("hello", *std::move(rvalue));
-}
-
-TEST(Result, OperatorArrowQualifiers) {
-  static_assert(
-      std::is_same<
-          const int*,
-          decltype(std::declval<const pw::Result<int>&>().operator->())>(),
-      "Unexpected qualifiers");
-  static_assert(
-      std::is_same<int*,
-                   decltype(std::declval<pw::Result<int>&>().operator->())>(),
-      "Unexpected qualifiers");
-  static_assert(
-      std::is_same<
-          const int*,
-          decltype(std::declval<const pw::Result<int>&&>().operator->())>(),
-      "Unexpected qualifiers");
-  static_assert(
-      std::is_same<int*,
-                   decltype(std::declval<pw::Result<int>&&>().operator->())>(),
-      "Unexpected qualifiers");
-}
-
-TEST(Result, OperatorArrow) {
-  const pw::Result<std::string> const_lvalue("hello");
-  EXPECT_EQ(std::string("hello"), const_lvalue->c_str());
-
-  pw::Result<std::string> lvalue("hello");
-  EXPECT_EQ(std::string("hello"), lvalue->c_str());
-}
-
-TEST(Result, RValueStatus) {
-  pw::Result<int> so(pw::Status::NotFound());
-  const pw::Status s = std::move(so).status();
-
-  EXPECT_EQ(s.code(), pw::Status::NotFound().code());
-
-  // Check that !ok() still implies !status().ok(), even after moving out of the
-  // object. See the note on the rvalue ref-qualified status method.
-  EXPECT_FALSE(so.ok());  // NOLINT
-  EXPECT_FALSE(so.status().ok());
-
-  // absl::Status sets itself to INTERNAL when moved, but pw::Status does not.
-  // EXPECT_EQ(so.status().code(), pw::Status::Internal().code());
-}
-
-TEST(Result, TestValue) {
-  const int kI = 4;
-  pw::Result<int> thing(kI);
-  EXPECT_EQ(kI, *thing);
-}
-
-TEST(Result, TestValueConst) {
-  const int kI = 4;
-  const pw::Result<int> thing(kI);
-  EXPECT_EQ(kI, *thing);
-}
-
-TEST(Result, TestPointerDefaultCtor) {
-  pw::Result<int*> thing;
-  EXPECT_FALSE(thing.ok());
-  EXPECT_EQ(thing.status().code(), pw::Status::Unknown().code());
-}
-
-TEST(Result, TestPointerStatusCtor) {
-  pw::Result<int*> thing(pw::Status::Cancelled());
-  EXPECT_FALSE(thing.ok());
-  EXPECT_EQ(thing.status().code(), pw::Status::Cancelled().code());
-}
-
-TEST(Result, TestPointerValueCtor) {
-  const int kI = 4;
-
-  // Construction from a non-null pointer
-  {
-    pw::Result<const int*> so(&kI);
-    EXPECT_TRUE(so.ok());
-    EXPECT_OK(so.status());
-    EXPECT_EQ(&kI, *so);
-  }
-
-  // Construction from a null pointer constant
-  {
-    pw::Result<const int*> so(nullptr);
-    EXPECT_TRUE(so.ok());
-    EXPECT_OK(so.status());
-    EXPECT_EQ(nullptr, *so);
-  }
-
-  // Construction from a non-literal null pointer
-  {
-    const int* const p = nullptr;
-
-    pw::Result<const int*> so(p);
-    EXPECT_TRUE(so.ok());
-    EXPECT_OK(so.status());
-    EXPECT_EQ(nullptr, *so);
-  }
-}
-
-TEST(Result, TestPointerCopyCtorStatusOk) {
-  const int kI = 0;
-  pw::Result<const int*> original(&kI);
-  pw::Result<const int*> copy(original);
-  EXPECT_OK(copy.status());
-  EXPECT_EQ(*original, *copy);
-}
-
-TEST(Result, TestPointerCopyCtorStatusNotOk) {
-  pw::Result<int*> original(pw::Status::Cancelled());
-  pw::Result<int*> copy(original);
-  EXPECT_EQ(copy.status().code(), pw::Status::Cancelled().code());
-}
-
-TEST(Result, TestPointerCopyCtorStatusOKConverting) {
-  Derived derived;
-  pw::Result<Derived*> original(&derived);
-  pw::Result<Base2*> copy(original);
-  EXPECT_OK(copy.status());
-  EXPECT_EQ(static_cast<const Base2*>(*original), *copy);
-}
-
-TEST(Result, TestPointerCopyCtorStatusNotOkConverting) {
-  pw::Result<Derived*> original(pw::Status::Cancelled());
-  pw::Result<Base2*> copy(original);
-  EXPECT_EQ(copy.status().code(), pw::Status::Cancelled().code());
-}
-
-TEST(Result, TestPointerAssignmentStatusOk) {
-  const int kI = 0;
-  pw::Result<const int*> source(&kI);
-  pw::Result<const int*> target;
-  target = source;
-  EXPECT_OK(target.status());
-  EXPECT_EQ(*source, *target);
-}
-
-TEST(Result, TestPointerAssignmentStatusNotOk) {
-  pw::Result<int*> source(pw::Status::Cancelled());
-  pw::Result<int*> target;
-  target = source;
-  EXPECT_EQ(target.status().code(), pw::Status::Cancelled().code());
-}
-
-TEST(Result, TestPointerAssignmentStatusOKConverting) {
-  Derived derived;
-  pw::Result<Derived*> source(&derived);
-  pw::Result<Base2*> target;
-  target = source;
-  EXPECT_OK(target.status());
-  EXPECT_EQ(static_cast<const Base2*>(*source), *target);
-}
-
-TEST(Result, TestPointerAssignmentStatusNotOkConverting) {
-  pw::Result<Derived*> source(pw::Status::Cancelled());
-  pw::Result<Base2*> target;
-  target = source;
-  EXPECT_EQ(target.status(), source.status());
-}
-
-TEST(Result, TestPointerStatus) {
-  const int kI = 0;
-  pw::Result<const int*> good(&kI);
-  EXPECT_TRUE(good.ok());
-  pw::Result<const int*> bad(pw::Status::Cancelled());
-  EXPECT_EQ(bad.status().code(), pw::Status::Cancelled().code());
-}
-
-TEST(Result, TestPointerValue) {
-  const int kI = 0;
-  pw::Result<const int*> thing(&kI);
-  EXPECT_EQ(&kI, *thing);
-}
-
-TEST(Result, TestPointerValueConst) {
-  const int kI = 0;
-  const pw::Result<const int*> thing(&kI);
-  EXPECT_EQ(&kI, *thing);
-}
-
-TEST(Result, ResultVectorOfUniquePointerCanReserveAndResize) {
-  using EvilType = std::vector<std::unique_ptr<int>>;
-  static_assert(std::is_copy_constructible<EvilType>::value, "");
-  std::vector<::pw::Result<EvilType>> v(5);
-  v.reserve(v.capacity() + 10);
-  v.resize(v.capacity() + 10);
-}
-
-TEST(Result, ConstPayload) {
-  // A reduced version of a problematic type found in the wild. All of the
-  // operations below should compile.
-  pw::Result<const int> a;
-
-  // Copy-construction
-  pw::Result<const int> b(a);
-
-  // Copy-assignment
-  EXPECT_FALSE(std::is_copy_assignable<pw::Result<const int>>::value);
-
-  // Move-construction
-  pw::Result<const int> c(std::move(a));
-
-  // Move-assignment
-  EXPECT_FALSE(std::is_move_assignable<pw::Result<const int>>::value);
-}
-
-TEST(Result, MapToResultUniquePtr) {
-  // A reduced version of a problematic type found in the wild. All of the
-  // operations below should compile.
-  using MapType = std::map<std::string, pw::Result<std::unique_ptr<int>>>;
-
-  MapType a;
-
-  // Move-construction
-  MapType b(std::move(a));
-
-  // Move-assignment
-  a = std::move(b);
-}
-
-TEST(Result, ValueOrOk) {
-  const pw::Result<int> status_or = 0;
-  EXPECT_EQ(status_or.value_or(-1), 0);
-}
-
-TEST(Result, ValueOrDefault) {
-  const pw::Result<int> status_or = pw::Status::Cancelled();
-  EXPECT_EQ(status_or.value_or(-1), -1);
-}
-
-TEST(Result, MoveOnlyValueOrOk) {
-  pw::Result<std::unique_ptr<int>> status_or = std::make_unique<int>(0);
-  ASSERT_TRUE(status_or.ok());
-  auto value = std::move(status_or).value_or(std::make_unique<int>(-1));
-  EXPECT_EQ(*value, 0);
-}
-
-TEST(Result, MoveOnlyValueOrDefault) {
-  pw::Result<std::unique_ptr<int>> status_or(pw::Status::Cancelled());
-  ASSERT_FALSE(status_or.ok());
-  auto value = std::move(status_or).value_or(std::make_unique<int>(-1));
-  EXPECT_EQ(*value, -1);
-}
-
-static pw::Result<int> MakeStatus() { return 100; }
-
-TEST(Result, TestIgnoreError) { MakeStatus().IgnoreError(); }
-
-TEST(Result, EqualityOperator) {
-  constexpr int kNumCases = 4;
-  std::array<pw::Result<int>, kNumCases> group1 = {
-      pw::Result<int>(1),
-      pw::Result<int>(2),
-      pw::Result<int>(pw::Status::InvalidArgument()),
-      pw::Result<int>(pw::Status::Internal())};
-  std::array<pw::Result<int>, kNumCases> group2 = {
-      pw::Result<int>(1),
-      pw::Result<int>(2),
-      pw::Result<int>(pw::Status::InvalidArgument()),
-      pw::Result<int>(pw::Status::Internal())};
-  for (int i = 0; i < kNumCases; ++i) {
-    for (int j = 0; j < kNumCases; ++j) {
-      if (i == j) {
-        EXPECT_TRUE(group1[i] == group2[j]);
-        EXPECT_FALSE(group1[i] != group2[j]);
-      } else {
-        EXPECT_FALSE(group1[i] == group2[j]);
-        EXPECT_TRUE(group1[i] != group2[j]);
-      }
-    }
-  }
-}
-
-struct MyType {
-  bool operator==(const MyType&) const { return true; }
-};
-
-enum class ConvTraits { kNone = 0, kImplicit = 1, kExplicit = 2 };
-
-// This class has conversion operator to `Result<T>` based on value of
-// `conv_traits`.
-template <typename T, ConvTraits conv_traits = ConvTraits::kNone>
-struct ResultConversionBase {};
-
-template <typename T>
-struct ResultConversionBase<T, ConvTraits::kImplicit> {
-  operator pw::Result<T>() const& {  // NOLINT
-    return pw::Status::InvalidArgument();
-  }
-  operator pw::Result<T>() && {  // NOLINT
-    return pw::Status::InvalidArgument();
-  }
-};
-
-template <typename T>
-struct ResultConversionBase<T, ConvTraits::kExplicit> {
-  explicit operator pw::Result<T>() const& {
-    return pw::Status::InvalidArgument();
-  }
-  explicit operator pw::Result<T>() && { return pw::Status::InvalidArgument(); }
-};
-
-// This class has conversion operator to `T` based on the value of
-// `conv_traits`.
-template <typename T, ConvTraits conv_traits = ConvTraits::kNone>
-struct ConversionBase {};
-
-template <typename T>
-struct ConversionBase<T, ConvTraits::kImplicit> {
-  operator T() const& { return t; }         // NOLINT
-  operator T() && { return std::move(t); }  // NOLINT
-  T t;
-};
-
-template <typename T>
-struct ConversionBase<T, ConvTraits::kExplicit> {
-  explicit operator T() const& { return t; }
-  explicit operator T() && { return std::move(t); }
-  T t;
-};
-
-// This class has conversion operator to `pw::Status` based on the value of
-// `conv_traits`.
-template <ConvTraits conv_traits = ConvTraits::kNone>
-struct StatusConversionBase {};
-
-template <>
-struct StatusConversionBase<ConvTraits::kImplicit> {
-  operator pw::Status() const& {  // NOLINT
-    return pw::Status::Internal();
-  }
-  operator pw::Status() && {  // NOLINT
-    return pw::Status::Internal();
-  }
-};
-
-template <>
-struct StatusConversionBase<ConvTraits::kExplicit> {
-  explicit operator pw::Status() const& {  // NOLINT
-    return pw::Status::Internal();
-  }
-  explicit operator pw::Status() && {  // NOLINT
-    return pw::Status::Internal();
-  }
-};
-
-static constexpr int kConvToStatus = 1;
-static constexpr int kConvToResult = 2;
-static constexpr int kConvToT = 4;
-static constexpr int kConvExplicit = 8;
-
-constexpr ConvTraits GetConvTraits(int bit, int config) {
-  return (config & bit) == 0
-             ? ConvTraits::kNone
-             : ((config & kConvExplicit) == 0 ? ConvTraits::kImplicit
-                                              : ConvTraits::kExplicit);
-}
-
-// This class conditionally has conversion operator to `pw::Status`, `T`,
-// `Result<T>`, based on values of the template parameters.
-template <typename T, int config>
-struct CustomType
-    : ResultConversionBase<T, GetConvTraits(kConvToResult, config)>,
-      ConversionBase<T, GetConvTraits(kConvToT, config)>,
-      StatusConversionBase<GetConvTraits(kConvToStatus, config)> {};
-
-struct ConvertibleToAnyResult {
-  template <typename T>
-  operator pw::Result<T>() const {  // NOLINT
-    return pw::Status::InvalidArgument();
-  }
-};
-
-// Test the rank of overload resolution for `Result<T>` constructor and
-// assignment, from highest to lowest:
-// 1. T/Status
-// 2. U that has conversion operator to pw::Result<T>
-// 3. U that is convertible to Status
-// 4. U that is convertible to T
-TEST(Result, ConstructionFromT) {
-  // Construct pw::Result<T> from T when T is convertible to
-  // pw::Result<T>
-  {
-    ConvertibleToAnyResult v;
-    pw::Result<ConvertibleToAnyResult> statusor(v);
-    EXPECT_TRUE(statusor.ok());
-  }
-  {
-    ConvertibleToAnyResult v;
-    pw::Result<ConvertibleToAnyResult> statusor = v;
-    EXPECT_TRUE(statusor.ok());
-  }
-  // Construct pw::Result<T> from T when T is explicitly convertible to
-  // Status
-  {
-    CustomType<MyType, kConvToStatus | kConvExplicit> v;
-    pw::Result<CustomType<MyType, kConvToStatus | kConvExplicit>> statusor(v);
-    EXPECT_TRUE(statusor.ok());
-  }
-  {
-    CustomType<MyType, kConvToStatus | kConvExplicit> v;
-    pw::Result<CustomType<MyType, kConvToStatus | kConvExplicit>> statusor = v;
-    EXPECT_TRUE(statusor.ok());
-  }
-}
-
-// Construct pw::Result<T> from U when U is explicitly convertible to T
-TEST(Result, ConstructionFromTypeConvertibleToT) {
-  {
-    CustomType<MyType, kConvToT | kConvExplicit> v;
-    pw::Result<MyType> statusor(v);
-    EXPECT_TRUE(statusor.ok());
-  }
-  {
-    CustomType<MyType, kConvToT> v;
-    pw::Result<MyType> statusor = v;
-    EXPECT_TRUE(statusor.ok());
-  }
-}
-
-// Construct pw::Result<T> from U when U has explicit conversion operator to
-// pw::Result<T>
-TEST(Result, ConstructionFromTypeWithConversionOperatorToResultT) {
-  {
-    CustomType<MyType, kConvToResult | kConvExplicit> v;
-    pw::Result<MyType> statusor(v);
-    EXPECT_EQ(statusor, v.operator pw::Result<MyType>());
-  }
-  {
-    CustomType<MyType, kConvToT | kConvToResult | kConvExplicit> v;
-    pw::Result<MyType> statusor(v);
-    EXPECT_EQ(statusor, v.operator pw::Result<MyType>());
-  }
-  {
-    CustomType<MyType, kConvToResult | kConvToStatus | kConvExplicit> v;
-    pw::Result<MyType> statusor(v);
-    EXPECT_EQ(statusor, v.operator pw::Result<MyType>());
-  }
-  {
-    CustomType<MyType, kConvToT | kConvToResult | kConvToStatus | kConvExplicit>
-        v;
-    pw::Result<MyType> statusor(v);
-    EXPECT_EQ(statusor, v.operator pw::Result<MyType>());
-  }
-  {
-    CustomType<MyType, kConvToResult> v;
-    pw::Result<MyType> statusor = v;
-    EXPECT_EQ(statusor, v.operator pw::Result<MyType>());
-  }
-  {
-    CustomType<MyType, kConvToT | kConvToResult> v;
-    pw::Result<MyType> statusor = v;
-    EXPECT_EQ(statusor, v.operator pw::Result<MyType>());
-  }
-  {
-    CustomType<MyType, kConvToResult | kConvToStatus> v;
-    pw::Result<MyType> statusor = v;
-    EXPECT_EQ(statusor, v.operator pw::Result<MyType>());
-  }
-  {
-    CustomType<MyType, kConvToT | kConvToResult | kConvToStatus> v;
-    pw::Result<MyType> statusor = v;
-    EXPECT_EQ(statusor, v.operator pw::Result<MyType>());
-  }
-}
-
-TEST(Result, ConstructionFromTypeConvertibleToStatus) {
-  // Construction fails because conversion to `Status` is explicit.
-  {
-    CustomType<MyType, kConvToStatus | kConvExplicit> v;
-    pw::Result<MyType> statusor(v);
-    EXPECT_FALSE(statusor.ok());
-    EXPECT_EQ(statusor.status(), static_cast<pw::Status>(v));
-  }
-  {
-    CustomType<MyType, kConvToT | kConvToStatus | kConvExplicit> v;
-    pw::Result<MyType> statusor(v);
-    EXPECT_FALSE(statusor.ok());
-    EXPECT_EQ(statusor.status(), static_cast<pw::Status>(v));
-  }
-  {
-    CustomType<MyType, kConvToStatus> v;
-    pw::Result<MyType> statusor = v;
-    EXPECT_FALSE(statusor.ok());
-    EXPECT_EQ(statusor.status(), static_cast<pw::Status>(v));
-  }
-  {
-    CustomType<MyType, kConvToT | kConvToStatus> v;
-    pw::Result<MyType> statusor = v;
-    EXPECT_FALSE(statusor.ok());
-    EXPECT_EQ(statusor.status(), static_cast<pw::Status>(v));
-  }
-}
-
-TEST(Result, AssignmentFromT) {
-  // Assign to pw::Result<T> from T when T is convertible to
-  // pw::Result<T>
-  {
-    ConvertibleToAnyResult v;
-    pw::Result<ConvertibleToAnyResult> statusor;
-    statusor = v;
-    EXPECT_TRUE(statusor.ok());
-  }
-  // Assign to pw::Result<T> from T when T is convertible to Status
-  {
-    CustomType<MyType, kConvToStatus> v;
-    pw::Result<CustomType<MyType, kConvToStatus>> statusor;
-    statusor = v;
-    EXPECT_TRUE(statusor.ok());
-  }
-}
-
-TEST(Result, AssignmentFromTypeConvertibleToT) {
-  // Assign to pw::Result<T> from U when U is convertible to T
-  {
-    CustomType<MyType, kConvToT> v;
-    pw::Result<MyType> statusor;
-    statusor = v;
-    EXPECT_TRUE(statusor.ok());
-  }
-}
-
-TEST(Result, AssignmentFromTypeWithConversionOperatortoResultT) {
-  // Assign to pw::Result<T> from U when U has conversion operator to
-  // pw::Result<T>
-  {
-    CustomType<MyType, kConvToResult> v;
-    pw::Result<MyType> statusor;
-    statusor = v;
-    EXPECT_EQ(statusor, v.operator pw::Result<MyType>());
-  }
-  {
-    CustomType<MyType, kConvToT | kConvToResult> v;
-    pw::Result<MyType> statusor;
-    statusor = v;
-    EXPECT_EQ(statusor, v.operator pw::Result<MyType>());
-  }
-  {
-    CustomType<MyType, kConvToResult | kConvToStatus> v;
-    pw::Result<MyType> statusor;
-    statusor = v;
-    EXPECT_EQ(statusor, v.operator pw::Result<MyType>());
-  }
-  {
-    CustomType<MyType, kConvToT | kConvToResult | kConvToStatus> v;
-    pw::Result<MyType> statusor;
-    statusor = v;
-    EXPECT_EQ(statusor, v.operator pw::Result<MyType>());
-  }
-}
-
-TEST(Result, AssignmentFromTypeConvertibleToStatus) {
-  // Assign to pw::Result<T> from U when U is convertible to Status
-  {
-    CustomType<MyType, kConvToStatus> v;
-    pw::Result<MyType> statusor;
-    statusor = v;
-    EXPECT_FALSE(statusor.ok());
-    EXPECT_EQ(statusor.status(), static_cast<pw::Status>(v));
-  }
-  {
-    CustomType<MyType, kConvToT | kConvToStatus> v;
-    pw::Result<MyType> statusor;
-    statusor = v;
-    EXPECT_FALSE(statusor.ok());
-    EXPECT_EQ(statusor.status(), static_cast<pw::Status>(v));
-  }
-}
-
-}  // namespace
diff --git a/pw_ring_buffer/BUILD b/pw_ring_buffer/BUILD
new file mode 100644
index 0000000..b4af4e3
--- /dev/null
+++ b/pw_ring_buffer/BUILD
@@ -0,0 +1,51 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_ring_buffer",
+    srcs = [
+        "prefixed_entry_ring_buffer.cc",
+    ],
+    hdrs = [
+        "public/pw_ring_buffer/prefixed_entry_ring_buffer.h",
+    ],
+    includes = ["public"],
+    deps = [
+        "//pw_containers",
+        "//pw_span",
+        "//pw_status",
+        "//pw_varint",
+    ],
+)
+
+pw_cc_test(
+    name = "prefixed_entry_ring_buffer_test",
+    srcs = [
+        "prefixed_entry_ring_buffer_test.cc",
+    ],
+    deps = [
+        ":pw_ring_buffer",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_ring_buffer/BUILD.bazel b/pw_ring_buffer/BUILD.bazel
deleted file mode 100644
index 467c156..0000000
--- a/pw_ring_buffer/BUILD.bazel
+++ /dev/null
@@ -1,52 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_ring_buffer",
-    srcs = [
-        "prefixed_entry_ring_buffer.cc",
-    ],
-    hdrs = [
-        "public/pw_ring_buffer/prefixed_entry_ring_buffer.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_containers",
-        "//pw_result",
-        "//pw_span",
-        "//pw_status",
-        "//pw_varint",
-    ],
-)
-
-pw_cc_test(
-    name = "prefixed_entry_ring_buffer_test",
-    srcs = [
-        "prefixed_entry_ring_buffer_test.cc",
-    ],
-    deps = [
-        ":pw_ring_buffer",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_ring_buffer/BUILD.gn b/pw_ring_buffer/BUILD.gn
index 88db5bd..8871caa 100644
--- a/pw_ring_buffer/BUILD.gn
+++ b/pw_ring_buffer/BUILD.gn
@@ -27,7 +27,6 @@
   public_configs = [ ":default_config" ]
   public_deps = [
     "$dir_pw_containers",
-    "$dir_pw_result",
     "$dir_pw_status",
   ]
   sources = [ "prefixed_entry_ring_buffer.cc" ]
diff --git a/pw_ring_buffer/CMakeLists.txt b/pw_ring_buffer/CMakeLists.txt
deleted file mode 100644
index b866f0a..0000000
--- a/pw_ring_buffer/CMakeLists.txt
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-
-pw_add_module_library(pw_ring_buffer
-  HEADERS
-    public/pw_ring_buffer/prefixed_entry_ring_buffer.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_containers
-    pw_polyfill.cstddef
-    pw_polyfill.span
-    pw_result
-    pw_status
-  SOURCES
-    prefixed_entry_ring_buffer.cc
-  PRIVATE_DEPS
-    pw_assert
-    pw_varint
-)
-
-pw_add_test(pw_ring_buffer.prefixed_entry_ring_buffer_test
-  SOURCES
-    prefixed_entry_ring_buffer_test.cc
-  DEPS
-    pw_ring_buffer
-    pw_assert
-  GROUPS
-    modules
-    pw_ring_buffer
-)
diff --git a/pw_ring_buffer/OWNERS b/pw_ring_buffer/OWNERS
deleted file mode 100644
index 50c89a7..0000000
--- a/pw_ring_buffer/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-keir@google.com
-prashanthsw@google.com
diff --git a/pw_ring_buffer/docs.rst b/pw_ring_buffer/docs.rst
index a0efbfe..2209468 100644
--- a/pw_ring_buffer/docs.rst
+++ b/pw_ring_buffer/docs.rst
@@ -10,73 +10,7 @@
 
 Compatibility
 =============
-* C++14
-
-Iterator
-========
-In crash contexts, it may be useful to scan through a ring buffer that may
-have a mix of valid (yet to be read), stale (read), and invalid entries. The
-`PrefixedEntryRingBufferMulti::iterator` class can be used to walk through
-entries in the provided buffer.
-
-.. code-block:: cpp
-
-  // A test string to push into the buffer.
-  constexpr char kExampleEntry[] = "Example!";
-
-  // Setting up buffers and attaching a reader.
-  std::byte buffer[1024];
-  std::byte read_buffer[256];
-  PrefixedEntryRingBuffer ring_buffer(buffer);
-  PrefixedEntryRingBuffer::Reader reader;
-  ring_buffer.AttachReader(reader);
-
-  // Insert some entries and process some entries.
-  ring_buffer.PushBack(kExampleEntry);
-  ring_buffer.PushBack(kExampleEntry);
-  reader.PopFront();
-
-  // !! A function causes a crash before we've read out all entries.
-  FunctionThatCrashes();
-
-  // ... Crash Context ...
-
-  // You can use a range-based for-loop to walk through all entries.
-  for (auto entry : ring_buffer) {
-    PW_LOG_WARN("Read entry of size: %lu", entry.size());
-  }
-
-In cases where a crash has caused the ring buffer to have corrupted data, the
-iterator will progress until it sees the corrupted section and instead move to
-`iterator::end()`. The `iterator::status()` function returns a `pw::Status`
-indicating the reason the iterator reached it's end.
-
-.. code-block:: cpp
-
-   // ... Crash Context ...
-
-   using iterator = PrefixedEntryRingBufferMulti::iterator;
-
-   // Hold the iterator outside any loops to inspect it later.
-   iterator it = ring_buffer.begin();
-   for (; it != it.end(); ++it) {
-     PW_LOG_WARN("Read entry of size: %lu", it->size());
-   }
-
-   // Warn if there was a failure during iteration.
-   if (!it.status().ok()) {
-     PW_LOG_WARN("Iterator failed to read some entries!");
-   }
-
-Data corruption
-===============
-``PrefixedEntryRingBufferMulti`` offers a circular ring buffer for arbitrary
-length data entries. Some metadata bytes are added at the beginning of each
-entry to delimit the size of the entry. Unlike the iterator, the methods in
-``PrefixedEntryRingBufferMulti`` require that data in the buffer is not corrupt.
-When these methods encounter data corruption, there is no generic way to
-recover, and thus, the application crashes. Data corruption is indicative of
-other issues.
+* C++11
 
 Dependencies
 ============
diff --git a/pw_ring_buffer/prefixed_entry_ring_buffer.cc b/pw_ring_buffer/prefixed_entry_ring_buffer.cc
index f3c32b2..86bdd53 100644
--- a/pw_ring_buffer/prefixed_entry_ring_buffer.cc
+++ b/pw_ring_buffer/prefixed_entry_ring_buffer.cc
@@ -17,24 +17,20 @@
 #include <algorithm>
 #include <cstring>
 
-#include "pw_assert/assert.h"
-#include "pw_assert/check.h"
-#include "pw_status/try.h"
+#include "pw_assert/light.h"
 #include "pw_varint/varint.h"
 
 namespace pw {
 namespace ring_buffer {
 
 using std::byte;
-using Entry = PrefixedEntryRingBufferMulti::Entry;
 using Reader = PrefixedEntryRingBufferMulti::Reader;
-using iterator = PrefixedEntryRingBufferMulti::iterator;
 
 void PrefixedEntryRingBufferMulti::Clear() {
   write_idx_ = 0;
   for (Reader& reader : readers_) {
-    reader.read_idx_ = 0;
-    reader.entry_count_ = 0;
+    reader.read_idx = 0;
+    reader.entry_count = 0;
   }
 }
 
@@ -53,31 +49,26 @@
 }
 
 Status PrefixedEntryRingBufferMulti::AttachReader(Reader& reader) {
-  if (reader.buffer_ != nullptr) {
+  if (reader.buffer != nullptr) {
     return Status::InvalidArgument();
   }
-  reader.buffer_ = this;
+  reader.buffer = this;
 
-  if (readers_.empty()) {
-    reader.read_idx_ = write_idx_;
-    reader.entry_count_ = 0;
-  } else {
-    const Reader& slowest_reader = GetSlowestReader();
-    reader.read_idx_ = slowest_reader.read_idx_;
-    reader.entry_count_ = slowest_reader.entry_count_;
-  }
-
+  // Note that a newly attached reader sees the buffer as empty,
+  // and is not privy to entries pushed before being attached.
+  reader.read_idx = write_idx_;
+  reader.entry_count = 0;
   readers_.push_back(reader);
   return OkStatus();
 }
 
 Status PrefixedEntryRingBufferMulti::DetachReader(Reader& reader) {
-  if (reader.buffer_ != this) {
+  if (reader.buffer != this) {
     return Status::InvalidArgument();
   }
-  reader.buffer_ = nullptr;
-  reader.read_idx_ = 0;
-  reader.entry_count_ = 0;
+  reader.buffer = nullptr;
+  reader.read_idx = 0;
+  reader.entry_count = 0;
   readers_.remove(reader);
   return OkStatus();
 }
@@ -85,10 +76,13 @@
 Status PrefixedEntryRingBufferMulti::InternalPushBack(
     std::span<const byte> data,
     uint32_t user_preamble_data,
-    bool pop_front_if_needed) {
+    bool drop_elements_if_needed) {
   if (buffer_ == nullptr) {
     return Status::FailedPrecondition();
   }
+  if (data.size_bytes() == 0) {
+    return Status::InvalidArgument();
+  }
 
   // Prepare a single buffer that can hold both the user preamble and entry
   // length.
@@ -106,7 +100,7 @@
     return Status::OutOfRange();
   }
 
-  if (pop_front_if_needed) {
+  if (drop_elements_if_needed) {
     // PushBack() case: evict items as needed.
     // Drop old entries until we have space for the new entry.
     while (RawAvailableBytes() < total_write_bytes) {
@@ -123,7 +117,7 @@
 
   // Update all readers of the new count.
   for (Reader& reader : readers_) {
-    reader.entry_count_++;
+    reader.entry_count++;
   }
   return OkStatus();
 }
@@ -140,59 +134,49 @@
   };
 }
 
-Status PrefixedEntryRingBufferMulti::InternalPeekFront(
-    const Reader& reader, std::span<byte> data, size_t* bytes_read_out) const {
+Status PrefixedEntryRingBufferMulti::InternalPeekFront(Reader& reader,
+                                                       std::span<byte> data,
+                                                       size_t* bytes_read_out) {
   *bytes_read_out = 0;
   return InternalRead(reader, GetOutput(data, bytes_read_out), false);
 }
 
-Status PrefixedEntryRingBufferMulti::InternalPeekFront(
-    const Reader& reader, ReadOutput output) const {
+Status PrefixedEntryRingBufferMulti::InternalPeekFront(Reader& reader,
+                                                       ReadOutput output) {
   return InternalRead(reader, output, false);
 }
 
 Status PrefixedEntryRingBufferMulti::InternalPeekFrontWithPreamble(
-    const Reader& reader, std::span<byte> data, size_t* bytes_read_out) const {
+    Reader& reader, std::span<byte> data, size_t* bytes_read_out) {
   *bytes_read_out = 0;
   return InternalRead(reader, GetOutput(data, bytes_read_out), true);
 }
 
 Status PrefixedEntryRingBufferMulti::InternalPeekFrontWithPreamble(
-    const Reader& reader, ReadOutput output) const {
+    Reader& reader, ReadOutput output) {
   return InternalRead(reader, output, true);
 }
 
-Status PrefixedEntryRingBufferMulti::InternalPeekFrontPreamble(
-    const Reader& reader, uint32_t& user_preamble_out) const {
-  if (reader.entry_count_ == 0) {
-    return Status::OutOfRange();
-  }
-  // Figure out where to start reading (wrapped); accounting for preamble.
-  EntryInfo info = FrontEntryInfo(reader);
-  user_preamble_out = info.user_preamble;
-  return OkStatus();
-}
-
 // TODO(pwbug/339): Consider whether this internal templating is required, or if
 // we can simply promote GetOutput to a static function and remove the template.
 // T should be similar to Status (*read_output)(std::span<const byte>)
 template <typename T>
 Status PrefixedEntryRingBufferMulti::InternalRead(
-    const Reader& reader,
+    Reader& reader,
     T read_output,
     bool include_preamble_in_output,
-    uint32_t* user_preamble_out) const {
+    uint32_t* user_preamble_out) {
   if (buffer_ == nullptr) {
     return Status::FailedPrecondition();
   }
-  if (reader.entry_count_ == 0) {
+  if (reader.entry_count == 0) {
     return Status::OutOfRange();
   }
 
   // Figure out where to start reading (wrapped); accounting for preamble.
   EntryInfo info = FrontEntryInfo(reader);
   size_t read_bytes = info.data_bytes;
-  size_t data_read_idx = reader.read_idx_;
+  size_t data_read_idx = reader.read_idx;
   if (user_preamble_out) {
     *user_preamble_out = info.user_preamble;
   }
@@ -222,70 +206,85 @@
   // It is expected that InternalPopFrontAll is called only when there is
   // something to pop from at least one reader. If no readers exist, or all
   // readers are caught up, this function will assert.
-  size_t entry_count = GetSlowestReader().entry_count_;
-  PW_DCHECK_INT_NE(entry_count, 0);
+  size_t entry_count = GetSlowestReader().entry_count;
+  PW_DASSERT(entry_count != 0);
   // Otherwise, pop the readers that have the largest value.
   for (Reader& reader : readers_) {
-    if (reader.entry_count_ == entry_count) {
-      reader.PopFront()
-          .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    if (reader.entry_count == entry_count) {
+      reader.PopFront();
     }
   }
 }
 
-const Reader& PrefixedEntryRingBufferMulti::GetSlowestReader() const {
-  PW_DCHECK_INT_GT(readers_.size(), 0);
-  const Reader* slowest_reader = &(*readers_.begin());
-  for (const Reader& reader : readers_) {
-    if (reader.entry_count_ > slowest_reader->entry_count_) {
-      slowest_reader = &reader;
+Reader& PrefixedEntryRingBufferMulti::GetSlowestReader() {
+  // Readers are guaranteed to be before the writer pointer (the class enforces
+  // this on every read/write operation that forces the write pointer ahead of
+  // an existing reader). To determine the slowest reader, we consider three
+  // scenarios:
+  //
+  // In all below cases, WH is the write-head, and R# are readers, with R1
+  // representing the slowest reader.
+  // [[R1 R2 R3 WH]] => Right-hand writer, slowest reader is left-most reader.
+  // [[WH R1 R2 R3]] => Left-hand writer, slowest reader is left-most reader.
+  // [[R3 WH R1 R2]] => Middle-writer, slowest reader is left-most reader after
+  // writer.
+  //
+  // Formally, choose the left-most reader after the writer (ex.2,3), but if
+  // that doesn't exist, choose the left-most reader before the writer (ex.1).
+  PW_DASSERT(readers_.size() > 0);
+  Reader* slowest_reader_after_writer = nullptr;
+  Reader* slowest_reader_before_writer = nullptr;
+  for (Reader& reader : readers_) {
+    if (reader.read_idx < write_idx_) {
+      if (!slowest_reader_before_writer ||
+          reader.read_idx < slowest_reader_before_writer->read_idx) {
+        slowest_reader_before_writer = &reader;
+      }
+    } else {
+      if (!slowest_reader_after_writer ||
+          reader.read_idx < slowest_reader_after_writer->read_idx) {
+        slowest_reader_after_writer = &reader;
+      }
     }
   }
-  return *slowest_reader;
+  return *(slowest_reader_after_writer ? slowest_reader_after_writer
+                                       : slowest_reader_before_writer);
 }
 
 Status PrefixedEntryRingBufferMulti::Dering() {
-  if (buffer_ == nullptr || readers_.empty()) {
+  if (buffer_ == nullptr || readers_.size() == 0) {
     return Status::FailedPrecondition();
   }
 
   // Check if by luck we're already deringed.
-  Reader& slowest_reader = GetSlowestReaderWritable();
-  if (slowest_reader.read_idx_ == 0) {
+  Reader* slowest_reader = &GetSlowestReader();
+  if (slowest_reader->read_idx == 0) {
     return OkStatus();
   }
 
-  return InternalDering(slowest_reader);
-}
-
-Status PrefixedEntryRingBufferMulti::InternalDering(Reader& dering_reader) {
-  if (buffer_ == nullptr || readers_.empty()) {
-    return Status::FailedPrecondition();
-  }
-
   auto buffer_span = std::span(buffer_, buffer_bytes_);
   std::rotate(buffer_span.begin(),
-              buffer_span.begin() + dering_reader.read_idx_,
+              buffer_span.begin() + slowest_reader->read_idx,
               buffer_span.end());
 
   // If the new index is past the end of the buffer,
   // alias it back (wrap) to the start of the buffer.
-  if (write_idx_ < dering_reader.read_idx_) {
+  if (write_idx_ < slowest_reader->read_idx) {
     write_idx_ += buffer_bytes_;
   }
-  write_idx_ -= dering_reader.read_idx_;
+  write_idx_ -= slowest_reader->read_idx;
 
   for (Reader& reader : readers_) {
-    if (&reader == &dering_reader) {
+    if (&reader == slowest_reader) {
       continue;
     }
-    if (reader.read_idx_ < dering_reader.read_idx_) {
-      reader.read_idx_ += buffer_bytes_;
+    if (reader.read_idx < slowest_reader->read_idx) {
+      reader.read_idx += buffer_bytes_;
     }
-    reader.read_idx_ -= dering_reader.read_idx_;
+    reader.read_idx -= slowest_reader->read_idx;
   }
 
-  dering_reader.read_idx_ = 0;
+  slowest_reader->read_idx = 0;
   return OkStatus();
 }
 
@@ -293,30 +292,30 @@
   if (buffer_ == nullptr) {
     return Status::FailedPrecondition();
   }
-  if (reader.entry_count_ == 0) {
+  if (reader.entry_count == 0) {
     return Status::OutOfRange();
   }
 
   // Advance the read pointer past the front entry to the next one.
   EntryInfo info = FrontEntryInfo(reader);
   size_t entry_bytes = info.preamble_bytes + info.data_bytes;
-  size_t prev_read_idx = reader.read_idx_;
-  reader.read_idx_ = IncrementIndex(prev_read_idx, entry_bytes);
-  reader.entry_count_--;
+  size_t prev_read_idx = reader.read_idx;
+  reader.read_idx = IncrementIndex(prev_read_idx, entry_bytes);
+  reader.entry_count--;
   return OkStatus();
 }
 
 size_t PrefixedEntryRingBufferMulti::InternalFrontEntryDataSizeBytes(
-    const Reader& reader) const {
-  if (reader.entry_count_ == 0) {
+    Reader& reader) {
+  if (reader.entry_count == 0) {
     return 0;
   }
   return FrontEntryInfo(reader).data_bytes;
 }
 
 size_t PrefixedEntryRingBufferMulti::InternalFrontEntryTotalSizeBytes(
-    const Reader& reader) const {
-  if (reader.entry_count_ == 0) {
+    Reader& reader) {
+  if (reader.entry_count == 0) {
     return 0;
   }
   EntryInfo info = FrontEntryInfo(reader);
@@ -324,15 +323,7 @@
 }
 
 PrefixedEntryRingBufferMulti::EntryInfo
-PrefixedEntryRingBufferMulti::FrontEntryInfo(const Reader& reader) const {
-  Result<PrefixedEntryRingBufferMulti::EntryInfo> entry_info =
-      RawFrontEntryInfo(reader.read_idx_);
-  PW_CHECK_OK(entry_info.status());
-  return entry_info.value();
-}
-
-Result<PrefixedEntryRingBufferMulti::EntryInfo>
-PrefixedEntryRingBufferMulti::RawFrontEntryInfo(size_t source_idx) const {
+PrefixedEntryRingBufferMulti::FrontEntryInfo(Reader& reader) {
   // Entry headers consists of: (optional prefix byte, varint size, data...)
 
   // If a preamble exists, extract the varint and it's bytes in bytes.
@@ -340,22 +331,18 @@
   uint64_t user_preamble_data = 0;
   byte varint_buf[varint::kMaxVarint32SizeBytes];
   if (user_preamble_) {
-    RawRead(varint_buf, source_idx, varint::kMaxVarint32SizeBytes);
+    RawRead(varint_buf, reader.read_idx, varint::kMaxVarint32SizeBytes);
     user_preamble_bytes = varint::Decode(varint_buf, &user_preamble_data);
-    if (user_preamble_bytes == 0u) {
-      return Status::DataLoss();
-    }
+    PW_DASSERT(user_preamble_bytes != 0u);
   }
 
   // Read the entry header; extract the varint and it's bytes in bytes.
   RawRead(varint_buf,
-          IncrementIndex(source_idx, user_preamble_bytes),
+          IncrementIndex(reader.read_idx, user_preamble_bytes),
           varint::kMaxVarint32SizeBytes);
   uint64_t entry_bytes;
   size_t length_bytes = varint::Decode(varint_buf, &entry_bytes);
-  if (length_bytes == 0u) {
-    return Status::DataLoss();
-  }
+  PW_DASSERT(length_bytes != 0u);
 
   EntryInfo info = {};
   info.preamble_bytes = user_preamble_bytes + length_bytes;
@@ -366,14 +353,15 @@
 
 // Comparisons ordered for more probable early exits, assuming the reader is
 // not far behind the writer compared to the size of the ring.
-size_t PrefixedEntryRingBufferMulti::RawAvailableBytes() const {
-  // Compute slowest reader. If no readers exist, the entire buffer can be
-  // written.
-  if (readers_.empty()) {
+size_t PrefixedEntryRingBufferMulti::RawAvailableBytes() {
+  // Compute slowest reader.
+  // TODO: Alternatively, the slowest reader could be actively mantained on
+  // every read operation, but reads are more likely than writes.
+  if (readers_.size() == 0) {
     return buffer_bytes_;
   }
 
-  size_t read_idx = GetSlowestReader().read_idx_;
+  size_t read_idx = GetSlowestReader().read_idx;
   // Case: Not wrapped.
   if (read_idx < write_idx_) {
     return buffer_bytes_ - (write_idx_ - read_idx);
@@ -383,8 +371,8 @@
     return read_idx - write_idx_;
   }
   // Case: Matched read and write heads; empty or full.
-  for (const Reader& reader : readers_) {
-    if (reader.read_idx_ == read_idx && reader.entry_count_ != 0) {
+  for (Reader& reader : readers_) {
+    if (reader.read_idx == read_idx && reader.entry_count != 0) {
       return 0;
     }
   }
@@ -392,10 +380,6 @@
 }
 
 void PrefixedEntryRingBufferMulti::RawWrite(std::span<const std::byte> source) {
-  if (source.size_bytes() == 0) {
-    return;
-  }
-
   // Write until the end of the source or the backing buffer.
   size_t bytes_until_wrap = buffer_bytes_ - write_idx_;
   size_t bytes_to_copy = std::min(source.size(), bytes_until_wrap);
@@ -411,11 +395,7 @@
 
 void PrefixedEntryRingBufferMulti::RawRead(byte* destination,
                                            size_t source_idx,
-                                           size_t length_bytes) const {
-  if (length_bytes == 0) {
-    return;
-  }
-
+                                           size_t length_bytes) {
   // Read the pre-wrap bytes.
   size_t bytes_until_wrap = buffer_bytes_ - source_idx;
   size_t bytes_to_copy = std::min(length_bytes, bytes_until_wrap);
@@ -428,7 +408,7 @@
 }
 
 size_t PrefixedEntryRingBufferMulti::IncrementIndex(size_t index,
-                                                    size_t count) const {
+                                                    size_t count) {
   // Note: This doesn't use modulus (%) since the branch is cheaper, and we
   // guarantee that count will never be greater than buffer_bytes_.
   index += count;
@@ -441,59 +421,11 @@
 Status PrefixedEntryRingBufferMulti::Reader::PeekFrontWithPreamble(
     std::span<byte> data,
     uint32_t& user_preamble_out,
-    size_t& entry_bytes_read_out) const {
+    size_t& entry_bytes_read_out) {
   entry_bytes_read_out = 0;
-  return buffer_->InternalRead(
+  return buffer->InternalRead(
       *this, GetOutput(data, &entry_bytes_read_out), false, &user_preamble_out);
 }
 
-iterator& iterator::operator++() {
-  PW_DCHECK_OK(iteration_status_);
-  PW_DCHECK_INT_NE(entry_count_, 0);
-
-  Result<EntryInfo> info = ring_buffer_->RawFrontEntryInfo(read_idx_);
-  if (!info.status().ok()) {
-    SkipToEnd(info.status());
-    return *this;
-  }
-
-  // It is guaranteed that the buffer is deringed at this point.
-  read_idx_ += info.value().preamble_bytes + info.value().data_bytes;
-  entry_count_--;
-
-  if (entry_count_ == 0) {
-    SkipToEnd(OkStatus());
-    return *this;
-  }
-
-  if (read_idx_ >= ring_buffer_->TotalUsedBytes()) {
-    SkipToEnd(Status::DataLoss());
-    return *this;
-  }
-
-  info = ring_buffer_->RawFrontEntryInfo(read_idx_);
-  if (!info.status().ok()) {
-    SkipToEnd(info.status());
-    return *this;
-  }
-  return *this;
-}
-
-const Entry& iterator::operator*() const {
-  PW_DCHECK_OK(iteration_status_);
-  PW_DCHECK_INT_NE(entry_count_, 0);
-
-  Result<EntryInfo> info = ring_buffer_->RawFrontEntryInfo(read_idx_);
-  PW_DCHECK_OK(info.status());
-
-  entry_ = {
-      .buffer = std::span<const byte>(
-          ring_buffer_->buffer_ + read_idx_ + info.value().preamble_bytes,
-          info.value().data_bytes),
-      .preamble = info.value().user_preamble,
-  };
-  return entry_;
-}
-
 }  // namespace ring_buffer
 }  // namespace pw
diff --git a/pw_ring_buffer/prefixed_entry_ring_buffer_test.cc b/pw_ring_buffer/prefixed_entry_ring_buffer_test.cc
index a372455..bba5bb3 100644
--- a/pw_ring_buffer/prefixed_entry_ring_buffer_test.cc
+++ b/pw_ring_buffer/prefixed_entry_ring_buffer_test.cc
@@ -17,7 +17,7 @@
 #include <cstddef>
 #include <cstdint>
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_containers/vector.h"
 #include "pw_unit_test/framework.h"
 
@@ -26,8 +26,6 @@
 namespace pw {
 namespace ring_buffer {
 namespace {
-using Entry = PrefixedEntryRingBufferMulti::Entry;
-using iterator = PrefixedEntryRingBufferMulti::iterator;
 
 TEST(PrefixedEntryRingBuffer, NoBuffer) {
   PrefixedEntryRingBuffer ring(false);
@@ -91,6 +89,9 @@
   EXPECT_EQ(ring.EntryCount(), 0u);
   EXPECT_EQ(ring.PopFront(), Status::OutOfRange());
   EXPECT_EQ(ring.EntryCount(), 0u);
+  EXPECT_EQ(ring.PushBack(std::span(single_entry_data, 0u)),
+            Status::InvalidArgument());
+  EXPECT_EQ(ring.EntryCount(), 0u);
   EXPECT_EQ(
       ring.PushBack(std::span(single_entry_data, sizeof(test_buffer) + 5)),
       Status::OutOfRange());
@@ -334,8 +335,7 @@
       // wrapped.
       for (i = 0; i < (kTotalEntryCount * (main_loop_count % 64u)); i++) {
         memset(single_entry_buffer, i, sizeof(single_entry_buffer));
-        ring.PushBack(single_entry_buffer)
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        ring.PushBack(single_entry_buffer);
       }
     }
 
@@ -353,8 +353,7 @@
       }
 
       // The ring buffer internally pushes the varint size byte.
-      ring.PushBack(single_entry_buffer)
-          .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+      ring.PushBack(single_entry_buffer);
     }
 
     // Check values before doing the dering.
@@ -396,96 +395,37 @@
 TEST(PrefixedEntryRingBuffer, DeringNoPreload) { DeringTest(false); }
 
 template <typename T>
-Status PushBack(PrefixedEntryRingBufferMulti& ring,
-                T element,
-                uint32_t user_preamble = 0) {
+Status PushBack(PrefixedEntryRingBufferMulti& ring, T element) {
   union {
     std::array<byte, sizeof(element)> buffer;
     T item;
   } aliased;
   aliased.item = element;
-  return ring.PushBack(aliased.buffer, user_preamble);
+  return ring.PushBack(aliased.buffer);
 }
 
 template <typename T>
-Status TryPushBack(PrefixedEntryRingBufferMulti& ring,
-                   T element,
-                   uint32_t user_preamble = 0) {
+Status TryPushBack(PrefixedEntryRingBufferMulti& ring, T element) {
   union {
     std::array<byte, sizeof(element)> buffer;
     T item;
   } aliased;
   aliased.item = element;
-  return ring.TryPushBack(aliased.buffer, user_preamble);
+  return ring.TryPushBack(aliased.buffer);
 }
 
 template <typename T>
-T PeekFront(PrefixedEntryRingBufferMulti::Reader& reader,
-            uint32_t* user_preamble_out = nullptr) {
+T PeekFront(PrefixedEntryRingBufferMulti::Reader& reader) {
   union {
     std::array<byte, sizeof(T)> buffer;
     T item;
   } aliased;
   size_t bytes_read = 0;
-  uint32_t user_preamble = 0;
-  PW_CHECK_OK(
-      reader.PeekFrontWithPreamble(aliased.buffer, user_preamble, bytes_read));
+  PW_CHECK_OK(reader.PeekFront(aliased.buffer, &bytes_read));
   PW_CHECK_INT_EQ(bytes_read, sizeof(T));
-  if (user_preamble_out) {
-    *user_preamble_out = user_preamble;
-  }
   return aliased.item;
 }
 
-template <typename T>
-T GetEntry(std::span<const std::byte> lhs) {
-  union {
-    std::array<byte, sizeof(T)> buffer;
-    T item;
-  } aliased;
-  std::memcpy(aliased.buffer.data(), lhs.data(), lhs.size_bytes());
-  return aliased.item;
-}
-
-void EmptyDataPushBackTest(bool user_data) {
-  PrefixedEntryRingBuffer ring(user_data);
-  byte test_buffer[kTestBufferSize];
-  EXPECT_EQ(ring.SetBuffer(test_buffer), OkStatus());
-
-  // Push back an empty span and a non-empty span.
-  EXPECT_EQ(ring.PushBack(std::span<std::byte>(), 1u), OkStatus());
-  EXPECT_EQ(ring.EntryCount(), 1u);
-  EXPECT_EQ(ring.PushBack(single_entry_data, 2u), OkStatus());
-  EXPECT_EQ(ring.EntryCount(), 2u);
-
-  // Confirm that both entries can be read back.
-  byte entry_buffer[kTestBufferSize];
-  uint32_t user_preamble = 0;
-  size_t bytes_read = 0;
-  // Read empty span.
-  EXPECT_EQ(ring.PeekFrontWithPreamble(entry_buffer, user_preamble, bytes_read),
-            OkStatus());
-  EXPECT_EQ(user_preamble, user_data ? 1u : 0u);
-  EXPECT_EQ(bytes_read, 0u);
-  EXPECT_EQ(ring.PopFront(), OkStatus());
-  EXPECT_EQ(ring.EntryCount(), 1u);
-  // Read non-empty span.
-  EXPECT_EQ(ring.PeekFrontWithPreamble(entry_buffer, user_preamble, bytes_read),
-            OkStatus());
-  EXPECT_EQ(user_preamble, user_data ? 2u : 0u);
-  ASSERT_EQ(bytes_read, sizeof(single_entry_data));
-  EXPECT_EQ(memcmp(entry_buffer, single_entry_data, bytes_read), 0);
-  EXPECT_EQ(ring.PopFront(), OkStatus());
-  EXPECT_EQ(ring.EntryCount(), 0u);
-}
-
-TEST(PrefixedEntryRingBuffer, EmptyDataPushBackTestWithPreamble) {
-  EmptyDataPushBackTest(true);
-}
-TEST(PrefixedEntryRingBuffer, EmptyDataPushBackTestNoPreamble) {
-  EmptyDataPushBackTest(false);
-}
-
 TEST(PrefixedEntryRingBuffer, TryPushBack) {
   PrefixedEntryRingBuffer ring;
   byte test_buffer[kTestBufferSize];
@@ -517,27 +457,6 @@
   EXPECT_EQ(PeekFront<int>(ring), 100);
 }
 
-TEST(PrefixedEntryRingBuffer, Iterator) {
-  PrefixedEntryRingBuffer ring;
-  byte test_buffer[kTestBufferSize];
-  EXPECT_EQ(ring.SetBuffer(test_buffer), OkStatus());
-
-  // Fill up the ring buffer with a constant value.
-  size_t entry_count = 0;
-  while (TryPushBack<size_t>(ring, entry_count).ok()) {
-    entry_count++;
-  }
-
-  // Iterate over all entries and confirm entry count.
-  size_t validated_entries = 0;
-  for (Result<const Entry> entry_info : ring) {
-    EXPECT_TRUE(entry_info.status().ok());
-    EXPECT_EQ(GetEntry<size_t>(entry_info.value().buffer), validated_entries);
-    validated_entries++;
-  }
-  EXPECT_EQ(validated_entries, entry_count);
-}
-
 TEST(PrefixedEntryRingBufferMulti, TryPushBack) {
   PrefixedEntryRingBufferMulti ring;
   byte test_buffer[kTestBufferSize];
@@ -562,20 +481,15 @@
   }
 
   // Run fast reader twice as fast as the slow reader.
-  size_t total_used_bytes = ring.TotalUsedBytes();
   for (int i = 0; i < total_items; ++i) {
-    EXPECT_EQ(PeekFront<int>(fast_reader), i);
-    EXPECT_EQ(fast_reader.PopFront(), OkStatus());
-    EXPECT_EQ(ring.TotalUsedBytes(), total_used_bytes);
     if (i % 2 == 0) {
       EXPECT_EQ(PeekFront<int>(slow_reader), i / 2);
       EXPECT_EQ(slow_reader.PopFront(), OkStatus());
-      EXPECT_TRUE(ring.TotalUsedBytes() < total_used_bytes);
     }
-    total_used_bytes = ring.TotalUsedBytes();
+    EXPECT_EQ(PeekFront<int>(fast_reader), i);
+    EXPECT_EQ(fast_reader.PopFront(), OkStatus());
   }
   EXPECT_EQ(fast_reader.PopFront(), Status::OutOfRange());
-  EXPECT_TRUE(ring.TotalUsedBytes() > 0u);
 
   // Fill the buffer again, expect that the fast reader
   // only sees half the entries as the slow reader.
@@ -602,7 +516,6 @@
   }
   EXPECT_EQ(slow_reader.PopFront(), Status::OutOfRange());
   EXPECT_EQ(fast_reader.PopFront(), Status::OutOfRange());
-  EXPECT_EQ(ring.TotalUsedBytes(), 0u);
 }
 
 TEST(PrefixedEntryRingBufferMulti, PushBack) {
@@ -655,9 +568,9 @@
   EXPECT_EQ(ring.AttachReader(reader), OkStatus());
 
   // Fill up the ring buffer with a constant value.
-  size_t total_items = 0;
+  int total_items = 0;
   while (true) {
-    Status status = TryPushBack<size_t>(ring, total_items);
+    Status status = TryPushBack<int>(ring, 5);
     if (status.ok()) {
       total_items++;
     } else {
@@ -665,30 +578,21 @@
       break;
     }
   }
-  EXPECT_EQ(reader.EntryCount(), total_items);
+  EXPECT_EQ(reader.EntryCount(), static_cast<size_t>(total_items));
 
   // Add new reader after filling the buffer.
   EXPECT_EQ(ring.AttachReader(transient_reader), OkStatus());
-  EXPECT_EQ(transient_reader.EntryCount(), total_items);
-
-  // Confirm that the transient reader observes all values, even though it was
-  // attached after entries were pushed.
-  for (size_t i = 0; i < total_items; i++) {
-    EXPECT_EQ(PeekFront<size_t>(transient_reader), i);
-    EXPECT_EQ(transient_reader.PopFront(), OkStatus());
-  }
   EXPECT_EQ(transient_reader.EntryCount(), 0u);
 
-  // Confirm that re-attaching the reader resets it back to the oldest
-  // available entry.
+  // Push a value into the buffer and confirm the transient reader
+  // sees that value, and only that value.
+  EXPECT_EQ(PushBack<int>(ring, 1), OkStatus());
+  EXPECT_EQ(PeekFront<int>(transient_reader), 1);
+  EXPECT_EQ(transient_reader.EntryCount(), 1u);
+
+  // Confirm that detaching and attaching a reader resets its state.
   EXPECT_EQ(ring.DetachReader(transient_reader), OkStatus());
   EXPECT_EQ(ring.AttachReader(transient_reader), OkStatus());
-  EXPECT_EQ(transient_reader.EntryCount(), total_items);
-
-  for (size_t i = 0; i < total_items; i++) {
-    EXPECT_EQ(PeekFront<size_t>(transient_reader), i);
-    EXPECT_EQ(transient_reader.PopFront(), OkStatus());
-  }
   EXPECT_EQ(transient_reader.EntryCount(), 0u);
 }
 
@@ -707,185 +611,6 @@
   EXPECT_EQ(ring_one.AttachReader(reader), Status::InvalidArgument());
 }
 
-TEST(PrefixedEntryRingBufferMulti, IteratorEmptyBuffer) {
-  PrefixedEntryRingBufferMulti ring;
-  // Pick a buffer that can't contain any valid sections.
-  byte test_buffer[1] = {std::byte(0xFF)};
-
-  PrefixedEntryRingBufferMulti::Reader reader;
-  EXPECT_EQ(ring.AttachReader(reader), OkStatus());
-  EXPECT_EQ(ring.SetBuffer(test_buffer), OkStatus());
-
-  EXPECT_EQ(ring.begin(), ring.end());
-}
-
-TEST(PrefixedEntryRingBufferMulti, IteratorValidEntries) {
-  PrefixedEntryRingBufferMulti ring;
-  byte test_buffer[kTestBufferSize];
-  EXPECT_EQ(ring.SetBuffer(test_buffer), OkStatus());
-
-  PrefixedEntryRingBufferMulti::Reader reader;
-  EXPECT_EQ(ring.AttachReader(reader), OkStatus());
-
-  // Buffer only contains valid entries. This happens after populating
-  // the buffer and no entries have been read.
-  // E.g. [VALID|VALID|VALID|INVALID]
-
-  // Fill up the ring buffer with a constant value.
-  size_t entry_count = 0;
-  while (TryPushBack<size_t>(ring, entry_count).ok()) {
-    entry_count++;
-  }
-
-  // Iterate over all entries and confirm entry count.
-  size_t validated_entries = 0;
-  for (const Entry& entry_info : ring) {
-    EXPECT_EQ(GetEntry<size_t>(entry_info.buffer), validated_entries);
-    validated_entries++;
-  }
-  EXPECT_EQ(validated_entries, entry_count);
-}
-
-TEST(PrefixedEntryRingBufferMulti, IteratorValidEntriesWithPreamble) {
-  PrefixedEntryRingBufferMulti ring(true);
-  byte test_buffer[kTestBufferSize];
-  EXPECT_EQ(ring.SetBuffer(test_buffer), OkStatus());
-
-  PrefixedEntryRingBufferMulti::Reader reader;
-  EXPECT_EQ(ring.AttachReader(reader), OkStatus());
-
-  // Buffer only contains valid entries. This happens after populating
-  // the buffer and no entries have been read.
-  // E.g. [VALID|VALID|VALID|INVALID]
-
-  // Fill up the ring buffer with a constant value.
-  size_t entry_count = 0;
-  while (TryPushBack<size_t>(ring, entry_count, entry_count).ok()) {
-    entry_count++;
-  }
-
-  // Iterate over all entries and confirm entry count.
-  size_t validated_entries = 0;
-  for (const Entry& entry_info : ring) {
-    EXPECT_EQ(GetEntry<size_t>(entry_info.buffer), validated_entries);
-    EXPECT_EQ(entry_info.preamble, validated_entries);
-    validated_entries++;
-  }
-  EXPECT_EQ(validated_entries, entry_count);
-}
-
-TEST(PrefixedEntryRingBufferMulti, IteratorStaleEntries) {
-  PrefixedEntryRingBufferMulti ring;
-  byte test_buffer[kTestBufferSize];
-  EXPECT_EQ(ring.SetBuffer(test_buffer), OkStatus());
-
-  // Buffer only contains stale, valid entries. This happens when after
-  // populating the buffer, all entries are read. The buffer retains the
-  // data but has an entry count of zero.
-  // E.g. [STALE|STALE|STALE]
-  PrefixedEntryRingBufferMulti::Reader trailing_reader;
-  EXPECT_EQ(ring.AttachReader(trailing_reader), OkStatus());
-
-  PrefixedEntryRingBufferMulti::Reader reader;
-  EXPECT_EQ(ring.AttachReader(reader), OkStatus());
-
-  // Push and pop all the entries.
-  size_t entry_count = 0;
-  while (TryPushBack<size_t>(ring, entry_count).ok()) {
-    entry_count++;
-  }
-
-  while (reader.PopFront().ok()) {
-  }
-
-  // Iterate over all entries and confirm entry count.
-  size_t validated_entries = 0;
-  for (const Entry& entry_info : ring) {
-    EXPECT_EQ(GetEntry<size_t>(entry_info.buffer), validated_entries);
-    validated_entries++;
-  }
-  EXPECT_EQ(validated_entries, entry_count);
-}
-
-TEST(PrefixedEntryRingBufferMulti, IteratorValidStaleEntries) {
-  PrefixedEntryRingBufferMulti ring;
-  byte test_buffer[kTestBufferSize];
-  EXPECT_EQ(ring.SetBuffer(test_buffer), OkStatus());
-
-  // Buffer contains both valid and stale entries. This happens when after
-  // populating the buffer, only some of the entries are read.
-  // E.g. [VALID|INVALID|STALE|STALE]
-  PrefixedEntryRingBufferMulti::Reader trailing_reader;
-  EXPECT_EQ(ring.AttachReader(trailing_reader), OkStatus());
-
-  PrefixedEntryRingBufferMulti::Reader reader;
-  EXPECT_EQ(ring.AttachReader(reader), OkStatus());
-
-  // Fill the buffer with entries.
-  size_t entry_count = 0;
-  while (TryPushBack<size_t>(ring, entry_count).ok()) {
-    entry_count++;
-  }
-
-  // Pop roughly half the entries.
-  while (reader.EntryCount() > (entry_count / 2)) {
-    EXPECT_TRUE(reader.PopFront().ok());
-  }
-
-  // Iterate over all entries and confirm entry count.
-  size_t validated_entries = 0;
-  for (const Entry& entry_info : ring) {
-    EXPECT_EQ(GetEntry<size_t>(entry_info.buffer), validated_entries);
-    validated_entries++;
-  }
-  EXPECT_EQ(validated_entries, entry_count);
-}
-
-TEST(PrefixedEntryRingBufferMulti, IteratorBufferCorruption) {
-  PrefixedEntryRingBufferMulti ring;
-  byte test_buffer[kTestBufferSize];
-  EXPECT_EQ(ring.SetBuffer(test_buffer), OkStatus());
-
-  // Buffer contains partially written entries. This may happen if writing
-  // is pre-empted (e.g. a crash occurs). In this state, we expect a series
-  // of valid entries followed by an invalid entry.
-  PrefixedEntryRingBufferMulti::Reader trailing_reader;
-  EXPECT_EQ(ring.AttachReader(trailing_reader), OkStatus());
-
-  // Add one entry to capture the second entry index.
-  size_t entry_count = 0;
-  EXPECT_TRUE(TryPushBack<size_t>(ring, entry_count++).ok());
-  size_t entry_size = ring.TotalUsedBytes();
-
-  // Fill the buffer with entries.
-  while (TryPushBack<size_t>(ring, entry_count++).ok()) {
-  }
-
-  // Push another entry to move the write index forward and force the oldest
-  // reader forward. This will require the iterator to dering.
-  EXPECT_TRUE(PushBack<size_t>(ring, 0).ok());
-  EXPECT_TRUE(ring.CheckForCorruption().ok());
-
-  // The first entry is overwritten. Corrupt all data past the fifth entry.
-  // Note that because the first entry has shifted, the entry_count recorded
-  // in each entry is shifted by 1.
-  constexpr size_t valid_entries = 5;
-  size_t offset = valid_entries * entry_size;
-  memset(test_buffer + offset, 0xFF, kTestBufferSize - offset);
-  EXPECT_FALSE(ring.CheckForCorruption().ok());
-
-  // Iterate over all entries and confirm entry count.
-  size_t validated_entries = 0;
-  iterator it = ring.begin();
-  for (; it != ring.end(); it++) {
-    EXPECT_EQ(GetEntry<size_t>(it->buffer), validated_entries + 1);
-    validated_entries++;
-  }
-  // The final entry will fail to be read.
-  EXPECT_EQ(it.status(), Status::DataLoss());
-  EXPECT_EQ(validated_entries, valid_entries);
-}
-
 }  // namespace
 }  // namespace ring_buffer
 }  // namespace pw
diff --git a/pw_ring_buffer/public/pw_ring_buffer/prefixed_entry_ring_buffer.h b/pw_ring_buffer/public/pw_ring_buffer/prefixed_entry_ring_buffer.h
index a4e3490..b7b4706 100644
--- a/pw_ring_buffer/public/pw_ring_buffer/prefixed_entry_ring_buffer.h
+++ b/pw_ring_buffer/public/pw_ring_buffer/prefixed_entry_ring_buffer.h
@@ -17,7 +17,6 @@
 #include <span>
 
 #include "pw_containers/intrusive_list.h"
-#include "pw_result/result.h"
 #include "pw_status/status.h"
 
 namespace pw {
@@ -45,11 +44,8 @@
   // A reader that provides a single-reader interface into the multi-reader ring
   // buffer it has been attached to via AttachReader(). Readers maintain their
   // read position in the ring buffer as well as the remaining count of entries
-  // from that position.
-  //
-  // If no readers are currently attached, the reader starts at the current
-  // write head. If readers are currently attached, the reader is set to the
-  // location and entry count of the slowest reader in the set.
+  // from that position. Readers are only able to consume entries that were
+  // pushed after the attach operation.
   //
   // Readers can peek and pop entries similar to the single-reader interface.
   // When popping entries, although the reader moves forward and drops the
@@ -61,7 +57,7 @@
   // loss if they read slower than the writer.
   class Reader : public IntrusiveList<Reader>::Item {
    public:
-    constexpr Reader() : buffer_(nullptr), read_idx_(0), entry_count_(0) {}
+    constexpr Reader() : buffer(nullptr), read_idx(0), entry_count(0) {}
 
     // TODO(pwbug/344): Add locking to the internal functions. Who owns the
     // lock? This class? Does this class need a lock if it's not a multi-reader?
@@ -72,9 +68,6 @@
     // the provided destination std::span. The number of bytes read is written
     // to bytes_read
     //
-    // Precondition: the buffer data must not be corrupt, otherwise there will
-    // be a crash.
-    //
     // Return values:
     // OK - Data successfully read from the ring buffer.
     // FAILED_PRECONDITION - Buffer not initialized.
@@ -83,20 +76,12 @@
     // bytes than the data size of the data chunk being read.  Available
     // destination bytes were filled, remaining bytes of the data chunk were
     // ignored.
-    Status PeekFront(std::span<std::byte> data, size_t* bytes_read_out) const {
-      return buffer_->InternalPeekFront(*this, data, bytes_read_out);
+    Status PeekFront(std::span<std::byte> data, size_t* bytes_read_out) {
+      return buffer->InternalPeekFront(*this, data, bytes_read_out);
     }
 
-    Status PeekFront(ReadOutput output) const {
-      return buffer_->InternalPeekFront(*this, output);
-    }
-
-    // Peek the front entry's preamble only to avoid copying data unnecessarily.
-    //
-    // Precondition: the buffer data must not be corrupt, otherwise there will
-    // be a crash.
-    Status PeekFrontPreamble(uint32_t& user_preamble_out) const {
-      return buffer_->InternalPeekFrontPreamble(*this, user_preamble_out);
+    Status PeekFront(ReadOutput output) {
+      return buffer->InternalPeekFront(*this, output);
     }
 
     // Same as PeekFront but includes the entry's preamble of optional user
@@ -105,149 +90,52 @@
     // as it is required to determine the length populated in the span.
     Status PeekFrontWithPreamble(std::span<std::byte> data,
                                  uint32_t& user_preamble_out,
-                                 size_t& entry_bytes_read_out) const;
+                                 size_t& entry_bytes_read_out);
 
     Status PeekFrontWithPreamble(std::span<std::byte> data,
-                                 size_t* bytes_read_out) const {
-      return buffer_->InternalPeekFrontWithPreamble(
-          *this, data, bytes_read_out);
+                                 size_t* bytes_read_out) {
+      return buffer->InternalPeekFrontWithPreamble(*this, data, bytes_read_out);
     }
 
-    Status PeekFrontWithPreamble(ReadOutput output) const {
-      return buffer_->InternalPeekFrontWithPreamble(*this, output);
+    Status PeekFrontWithPreamble(ReadOutput output) {
+      return buffer->InternalPeekFrontWithPreamble(*this, output);
     }
 
     // Pop and discard the oldest stored data chunk of data from the ring
     // buffer.
     //
-    // Precondition: the buffer data must not be corrupt, otherwise there will
-    // be a crash.
-    //
     // Return values:
     // OK - Data successfully read from the ring buffer.
     // FAILED_PRECONDITION - Buffer not initialized.
     // OUT_OF_RANGE - No entries in ring buffer to pop.
-    Status PopFront() { return buffer_->InternalPopFront(*this); }
+    Status PopFront() { return buffer->InternalPopFront(*this); }
 
     // Get the size in bytes of the next chunk, not including preamble, to be
     // read.
-    //
-    // Precondition: the buffer data must not be corrupt, otherwise there will
-    // be a crash.
-    size_t FrontEntryDataSizeBytes() const {
-      return buffer_->InternalFrontEntryDataSizeBytes(*this);
+    size_t FrontEntryDataSizeBytes() {
+      return buffer->InternalFrontEntryDataSizeBytes(*this);
     }
 
     // Get the size in bytes of the next chunk, including preamble and data
     // chunk, to be read.
-    //
-    // Precondition: the buffer data must not be corrupt, otherwise there will
-    // be a crash.
-    size_t FrontEntryTotalSizeBytes() const {
-      return buffer_->InternalFrontEntryTotalSizeBytes(*this);
+    size_t FrontEntryTotalSizeBytes() {
+      return buffer->InternalFrontEntryTotalSizeBytes(*this);
     }
 
     // Get the number of variable-length entries currently in the ring buffer.
     //
     // Return value:
     // Entry count.
-    size_t EntryCount() const { return entry_count_; }
+    size_t EntryCount() { return entry_count; }
 
-   private:
+   protected:
     friend PrefixedEntryRingBufferMulti;
 
-    // Internal constructors for the iterator class to create Reader instances
-    // at specific positions. Readers constructed through this interface cannot
-    // be attached/detached from the multisink.
-    constexpr Reader(Reader& reader)
-        : Reader(reader.buffer_, reader.read_idx_, reader.entry_count_) {}
-    constexpr Reader(PrefixedEntryRingBufferMulti* buffer,
-                     size_t read_idx,
-                     size_t entry_count)
-        : buffer_(buffer), read_idx_(read_idx), entry_count_(entry_count) {}
-
-    PrefixedEntryRingBufferMulti* buffer_;
-    size_t read_idx_;
-    size_t entry_count_;
+    PrefixedEntryRingBufferMulti* buffer;
+    size_t read_idx;
+    size_t entry_count;
   };
 
-  // An entry returned by the iterator containing the byte span of the entry
-  // and preamble data (if the ring buffer was configured with a preamble).
-  struct Entry {
-    std::span<const std::byte> buffer;
-    uint32_t preamble;
-  };
-
-  // An iterator that can be used to walk through all entries from a given
-  // Reader position, without mutating the underlying buffer. This is useful in
-  // crash contexts where all available entries in the buffer must be acquired,
-  // even those that have already been consumed by all attached readers.
-  class iterator {
-   public:
-    iterator() : ring_buffer_(nullptr), read_idx_(0), entry_count_(0) {}
-    iterator(Reader& reader)
-        : ring_buffer_(reader.buffer_),
-          read_idx_(0),
-          entry_count_(reader.entry_count_) {
-      Status dering_result = ring_buffer_->InternalDering(reader);
-      PW_DASSERT(dering_result.ok());
-    }
-
-    iterator& operator++();
-    iterator operator++(int) {
-      iterator original = *this;
-      ++*this;
-      return original;
-    }
-
-    // Returns entry at current position.
-    const Entry& operator*() const;
-    const Entry* operator->() const { return &operator*(); }
-
-    constexpr bool operator==(const iterator& rhs) const {
-      return entry_count_ == rhs.entry_count_;
-    }
-
-    constexpr bool operator!=(const iterator& rhs) const {
-      return entry_count_ != rhs.entry_count_;
-    }
-
-    // Returns the status of the last iteration operation. If the iterator
-    // fails to read an entry, it will move to iterator::end() and indicate
-    // the failure reason here.
-    Status status() const { return iteration_status_; }
-
-   private:
-    static constexpr Entry kEndEntry = {
-        .buffer = std::span<const std::byte>(),
-        .preamble = 0,
-    };
-
-    void SkipToEnd(Status status) {
-      iteration_status_ = status;
-      entry_ = kEndEntry;
-      entry_count_ = 0;
-    }
-
-    PrefixedEntryRingBufferMulti* ring_buffer_;
-    size_t read_idx_;
-    size_t entry_count_;
-
-    mutable Entry entry_;
-    Status iteration_status_;
-  };
-
-  using element_type = const Entry;
-  using value_type = std::remove_cv_t<const Entry>;
-  using pointer = const Entry;
-  using reference = const Entry&;
-  using const_iterator = iterator;  // Standard alias for iterable types.
-
-  iterator begin() { return iterator(GetSlowestReaderWritable()); }
-  iterator end() { return iterator(); }
-  const_iterator cbegin() { return begin(); }
-  const_iterator cend() { return end(); }
-
   // TODO(pwbug/340): Consider changing bool to an enum, to explicitly enumerate
   // what this variable means in clients.
   PrefixedEntryRingBufferMulti(bool user_preamble = false)
@@ -263,19 +151,6 @@
   // INVALID_ARGUMENT - Argument was nullptr, size zero, or too large.
   Status SetBuffer(std::span<std::byte> buffer);
 
-  // Determines if the ring buffer has corrupted entries.
-  //
-  // Precondition: At least one reader must be attached to the ring buffer.
-  // Return values:
-  // OK - No corruption was detected.
-  // DATA_LOSS - Corruption was detected.
-  Status CheckForCorruption() {
-    iterator it = begin();
-    for (; it != end(); ++it) {
-    }
-    return it.status();
-  }
-
   // Attach reader to the ring buffer. Readers can only be attached to one
   // ring buffer at a time.
   //
@@ -306,6 +181,7 @@
   //
   // Return values:
   // OK - Data successfully written to the ring buffer.
+  // INVALID_ARGUMENT - Size of data to write is zero bytes
   // FAILED_PRECONDITION - Buffer not initialized.
   // OUT_OF_RANGE - Size of data is greater than buffer size.
   Status PushBack(std::span<const std::byte> data,
@@ -326,9 +202,6 @@
   // entry. It is only used if user_preamble was set at class construction
   // time. It is varint-encoded before insertion into the buffer.
   //
-  // Precondition: the buffer data must not be corrupt, otherwise there will
-  // be a crash.
-  //
   // Return values:
   // OK - Data successfully written to the ring buffer.
   // INVALID_ARGUMENT - Size of data to write is zero bytes
@@ -350,26 +223,22 @@
 
   // Get the size in bytes of all the current entries in the ring buffer,
   // including preamble and data chunk.
-  size_t TotalUsedBytes() const { return buffer_bytes_ - RawAvailableBytes(); }
+  size_t TotalUsedBytes() { return buffer_bytes_ - RawAvailableBytes(); }
 
   // Dering the buffer by reordering entries internally in the buffer by
   // rotating to have the oldest entry is at the lowest address/index with
-  // newest entry at the highest address. If no readers are attached, the buffer
-  // is deringed at the current write index.
+  // newest entry at the highest address.
   //
   // Return values:
   // OK - Buffer data successfully deringed.
-  // FAILED_PRECONDITION - Buffer not initialized.
+  // FAILED_PRECONDITION - Buffer not initialized, or no readers attached.
   Status Dering();
 
- private:
+ protected:
   // Read the oldest stored data chunk of data from the ring buffer to
   // the provided destination std::span. The number of bytes read is written to
   // `bytes_read_out`.
   //
-  // Precondition: the buffer data must not be corrupt, otherwise there will
-  // be a crash.
-  //
   // Return values:
   // OK - Data successfully read from the ring buffer.
   // FAILED_PRECONDITION - Buffer not initialized.
@@ -377,26 +246,20 @@
   // RESOURCE_EXHAUSTED - Destination data std::span was smaller number of bytes
   // than the data size of the data chunk being read.  Available destination
   // bytes were filled, remaining bytes of the data chunk were ignored.
-  Status InternalPeekFront(const Reader& reader,
+  Status InternalPeekFront(Reader& reader,
                            std::span<std::byte> data,
-                           size_t* bytes_read_out) const;
-  Status InternalPeekFront(const Reader& reader, ReadOutput output) const;
+                           size_t* bytes_read_out);
+  Status InternalPeekFront(Reader& reader, ReadOutput output);
 
-  Status InternalPeekFrontPreamble(const Reader& reader,
-                                   uint32_t& user_preamble_out) const;
   // Same as Read but includes the entry's preamble of optional user value and
   // the varint of the data size
-  Status InternalPeekFrontWithPreamble(const Reader& reader,
+  Status InternalPeekFrontWithPreamble(Reader& reader,
                                        std::span<std::byte> data,
-                                       size_t* bytes_read_out) const;
-  Status InternalPeekFrontWithPreamble(const Reader& reader,
-                                       ReadOutput output) const;
+                                       size_t* bytes_read_out);
+  Status InternalPeekFrontWithPreamble(Reader& reader, ReadOutput output);
 
   // Pop and discard the oldest stored data chunk of data from the ring buffer.
   //
-  // Precondition: the buffer data must not be corrupt, otherwise there will
-  // be a crash.
-  //
   // Return values:
   // OK - Data successfully read from the ring buffer.
   // FAILED_PRECONDITION - Buffer not initialized.
@@ -405,30 +268,21 @@
 
   // Get the size in bytes of the next chunk, not including preamble, to be
   // read.
-  size_t InternalFrontEntryDataSizeBytes(const Reader& reader) const;
+  size_t InternalFrontEntryDataSizeBytes(Reader& reader);
 
   // Get the size in bytes of the next chunk, including preamble and data
   // chunk, to be read.
-  size_t InternalFrontEntryTotalSizeBytes(const Reader& reader) const;
+  size_t InternalFrontEntryTotalSizeBytes(Reader& reader);
 
   // Internal version of Read used by all the public interface versions. T
   // should be of type ReadOutput.
   template <typename T>
-  Status InternalRead(const Reader& reader,
+  Status InternalRead(Reader& reader,
                       T read_output,
                       bool include_preamble_in_output,
-                      uint32_t* user_preamble_out = nullptr) const;
+                      uint32_t* user_preamble_out = nullptr);
 
-  // Dering the buffer by reordering entries internally in the buffer by
-  // rotating to have the oldest entry is at the lowest address/index with
-  // newest entry at the highest address. If no readers are attached, the buffer
-  // is deringed at the current write index.
-  //
-  // Return values:
-  // OK - Buffer data successfully deringed.
-  // FAILED_PRECONDITION - Buffer not initialized.
-  Status InternalDering(Reader& reader);
-
+ private:
   struct EntryInfo {
     size_t preamble_bytes;
     uint32_t user_preamble;
@@ -445,37 +299,22 @@
   // multiple readers if multiple are slow.
   //
   // Precondition: This function requires that at least one reader is attached
-  // and has at least one entry to pop. There will be a crash if data is
-  // corrupted.
+  // and has at least one entry to pop.
   void InternalPopFrontAll();
 
-  // Returns a the slowest reader in the list.
+  // Returns the slowest reader in the list.
   //
   // Precondition: This function requires that at least one reader is attached.
-  const Reader& GetSlowestReader() const;
-  Reader& GetSlowestReaderWritable() {
-    return const_cast<Reader&>(GetSlowestReader());
-  }
-
-  // Get info struct with the size of the preamble and data chunk for the next
-  // entry to be read. Calls RawFrontEntryInfo and asserts on failure.
-  //
-  // Precondition: the buffer data must not be corrupt, otherwise there will
-  // be a crash.
-  EntryInfo FrontEntryInfo(const Reader& reader) const;
+  Reader& GetSlowestReader();
 
   // Get info struct with the size of the preamble and data chunk for the next
   // entry to be read.
-  //
-  // Returns:
-  // OK - EntryInfo containing the next entry metadata.
-  // DATA_LOSS - Failed to read the metadata at this location.
-  Result<EntryInfo> RawFrontEntryInfo(size_t source_idx) const;
+  EntryInfo FrontEntryInfo(Reader& reader);
 
   // Get the raw number of available bytes free in the ring buffer. This is
   // not available bytes for data, since there is a variable size preamble for
   // each entry.
-  size_t RawAvailableBytes() const;
+  size_t RawAvailableBytes();
 
   // Do the basic write of the specified number of bytes starting at the last
   // write index of the ring buffer to the destination, handing any wrap-around
@@ -485,11 +324,9 @@
   // Do the basic read of the specified number of bytes starting at the given
   // index of the ring buffer to the destination, handing any wrap-around of
   // the ring buffer. This is basic, raw operation with no safety checks.
-  void RawRead(std::byte* destination,
-               size_t source_idx,
-               size_t length_bytes) const;
+  void RawRead(std::byte* destination, size_t source_idx, size_t length_bytes);
 
-  size_t IncrementIndex(size_t index, size_t count) const;
+  size_t IncrementIndex(size_t index, size_t count);
 
   std::byte* buffer_;
   size_t buffer_bytes_;
@@ -511,8 +348,7 @@
  public:
   PrefixedEntryRingBuffer(bool user_preamble = false)
       : PrefixedEntryRingBufferMulti(user_preamble) {
-    AttachReader(*this)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    AttachReader(*this);
   }
 };
 
diff --git a/pw_ring_buffer/size_report/BUILD b/pw_ring_buffer/size_report/BUILD
new file mode 100644
index 0000000..8339bc5
--- /dev/null
+++ b/pw_ring_buffer/size_report/BUILD
@@ -0,0 +1,32 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_binary",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_binary(
+    name = "ring_buffer_simple",
+    srcs = ["ring_buffer_simple.cc"],
+)
+
+pw_cc_binary(
+    name = "ring_buffer_multi",
+    srcs = ["ring_buffer_multi.cc"],
+)
diff --git a/pw_ring_buffer/size_report/BUILD.bazel b/pw_ring_buffer/size_report/BUILD.bazel
deleted file mode 100644
index b05b576..0000000
--- a/pw_ring_buffer/size_report/BUILD.bazel
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_binary",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_binary(
-    name = "ring_buffer_simple",
-    srcs = ["ring_buffer_simple.cc"],
-)
-
-pw_cc_binary(
-    name = "ring_buffer_multi",
-    srcs = ["ring_buffer_multi.cc"],
-)
diff --git a/pw_ring_buffer/size_report/ring_buffer_multi.cc b/pw_ring_buffer/size_report/ring_buffer_multi.cc
index 5570469..1e2eb72 100644
--- a/pw_ring_buffer/size_report/ring_buffer_multi.cc
+++ b/pw_ring_buffer/size_report/ring_buffer_multi.cc
@@ -35,8 +35,7 @@
   // Attach readers.
   pw::ring_buffer::PrefixedEntryRingBufferMulti::Reader readers[kReaderCount];
   for (auto& reader : readers) {
-    ring.AttachReader(reader)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    ring.AttachReader(reader);
   }
 
   // Push entries until the buffer is full.
@@ -93,8 +92,7 @@
   }
 
   for (auto& reader : readers) {
-    ring.DetachReader(reader)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    ring.DetachReader(reader);
   }
   ring.Clear();
   return 0;
diff --git a/pw_router/BUILD b/pw_router/BUILD
new file mode 100644
index 0000000..f6f33c6
--- /dev/null
+++ b/pw_router/BUILD
@@ -0,0 +1,63 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "static_router",
+    hdrs = ["public/pw_router/static_router.h"],
+    srcs = ["static_router.cc"],
+    deps = [
+        ":egress",
+        ":packet_parser",
+        "//pw_log",
+        "//pw_metric",
+        "//pw_sync:mutex",
+    ],
+)
+
+pw_cc_library(
+    name = "egress",
+    hdrs = ["public/pw_router/egress.h"],
+    deps = ["//pw_bytes"],
+)
+
+pw_cc_library(
+    name = "packet_parser",
+    hdrs = ["public/pw_router/packet_parser.h"],
+    deps = ["//pw_bytes"],
+)
+
+pw_cc_library(
+    name = "egress_function",
+    hdrs = ["public/pw_router/egress_function.h"],
+    deps = [":egress"],
+)
+
+pw_cc_test(
+    name = "static_router_test",
+    srcs = ["static_router_test.cc"],
+    deps = [
+        ":egress_function",
+        ":static_router",
+    ],
+)
diff --git a/pw_router/BUILD.bazel b/pw_router/BUILD.bazel
deleted file mode 100644
index 9047d67..0000000
--- a/pw_router/BUILD.bazel
+++ /dev/null
@@ -1,69 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "static_router",
-    srcs = ["static_router.cc"],
-    hdrs = ["public/pw_router/static_router.h"],
-    includes = ["public"],
-    deps = [
-        ":egress",
-        ":packet_parser",
-        "//pw_log",
-        "//pw_metric:metric",
-    ],
-)
-
-pw_cc_library(
-    name = "egress",
-    hdrs = ["public/pw_router/egress.h"],
-    deps = [
-        ":packet_parser",
-        "//pw_bytes",
-    ],
-)
-
-pw_cc_library(
-    name = "packet_parser",
-    hdrs = ["public/pw_router/packet_parser.h"],
-    deps = ["//pw_bytes"],
-)
-
-pw_cc_library(
-    name = "egress_function",
-    hdrs = ["public/pw_router/egress_function.h"],
-    deps = [
-        ":egress",
-        "//pw_function",
-    ],
-)
-
-pw_cc_test(
-    name = "static_router_test",
-    srcs = ["static_router_test.cc"],
-    deps = [
-        ":egress_function",
-        ":static_router",
-    ],
-)
diff --git a/pw_router/BUILD.gn b/pw_router/BUILD.gn
index 503ff41..bcf934a 100644
--- a/pw_router/BUILD.gn
+++ b/pw_router/BUILD.gn
@@ -17,6 +17,7 @@
 import("$dir_pw_bloat/bloat.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_sync/backend.gni")
 import("$dir_pw_unit_test/test.gni")
 
 config("public_include_path") {
@@ -29,6 +30,8 @@
   public_deps = [
     ":egress",
     ":packet_parser",
+    "$dir_pw_sync:lock_annotations",
+    "$dir_pw_sync:mutex",
     dir_pw_metric,
   ]
   public = [ "public/pw_router/static_router.h" ]
@@ -38,10 +41,7 @@
 pw_source_set("egress") {
   public_configs = [ ":public_include_path" ]
   public = [ "public/pw_router/egress.h" ]
-  public_deps = [
-    ":packet_parser",
-    dir_pw_bytes,
-  ]
+  public_deps = [ dir_pw_bytes ]
 }
 
 pw_source_set("packet_parser") {
@@ -53,15 +53,14 @@
 pw_source_set("egress_function") {
   public_configs = [ ":public_include_path" ]
   public = [ "public/pw_router/egress_function.h" ]
-  public_deps = [
-    ":egress",
-    dir_pw_function,
-  ]
+  public_deps = [ ":egress" ]
 }
 
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
-  report_deps = [ ":static_router_size" ]
+  # TODO(frolv): This size report can't currently be built as the docs target
+  # does not have a mutex backend.
+  # report_deps = [ ":static_router_size" ]
 }
 
 pw_test_group("tests") {
@@ -74,6 +73,7 @@
     ":static_router",
   ]
   sources = [ "static_router_test.cc" ]
+  enable_if = pw_sync_MUTEX_BACKEND != ""
 }
 
 pw_size_report("static_router_size") {
diff --git a/pw_router/CMakeLists.txt b/pw_router/CMakeLists.txt
index b9e1706..cf80e79 100644
--- a/pw_router/CMakeLists.txt
+++ b/pw_router/CMakeLists.txt
@@ -25,38 +25,23 @@
   PRIVATE_DEPS
     pw_log
 )
-if(Zephyr_FOUND AND CONFIG_PIGWEED_ROUTER_STATIC_ROUTER)
-  zephyr_link_libraries(pw_router.static_router)
-endif()
 
 pw_add_module_library(pw_router.egress
   PUBLIC_DEPS
     pw_bytes
-    pw_router.packet_parser
 )
-if(Zephyr_FOUND AND CONFIG_PIGWEED_ROUTER_EGRESS)
-  zephyr_link_libraries(pw_router.egress)
-endif()
 
 pw_add_module_library(pw_router.packet_parser
   PUBLIC_DEPS
     pw_bytes
 )
-if(Zephyr_FOUND AND CONFIG_PIGWEED_ROUTER_PACKET_PARSER)
-  zephyr_link_libraries(pw_router.packet_parser)
-endif()
 
 pw_add_module_library(pw_router.egress_function
   PUBLIC_DEPS
-    pw_function
-    pw_router.egress
+    pw_rpc.egress
 )
-if(Zephyr_FOUND AND CONFIG_PIGWEED_ROUTER_EGRESS_FUNCTION)
-  zephyr_link_libraries(pw_router.egress_function)
-endif()
 
 pw_auto_add_module_tests(pw_router
   PRIVATE_DEPS
-    pw_router.egress_function
     pw_router.static_router
 )
diff --git a/pw_router/Kconfig b/pw_router/Kconfig
deleted file mode 100644
index 090e34e..0000000
--- a/pw_router/Kconfig
+++ /dev/null
@@ -1,40 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-menuconfig PIGWEED_ROUTER
-    bool "Enable the Pigweed router library (pw_router)"
-
-if PIGWEED_ROUTER
-
-config PIGWEED_ROUTER_STATIC_ROUTER
-    bool "Enable the Pigweed static router library (pw_router.static_router)"
-    select PIGWEED_METRIC
-    select PIGWEED_ROUTER_EGRESS
-    select PIGWEED_ROUTER_PACKET_PARSER
-    select PIGWEED_SYNC_MUTEX
-    select PIGWEED_LOG
-
-config PIGWEED_ROUTER_EGRESS
-    bool "Enable the Pigweed router egress library (pw_router.egress)"
-    select PIGWEED_BYTES
-
-config PIGWEED_ROUTER_PACKET_PARSER
-    bool "Enable the Pigweed router packet parser library (pw_router.packet_parser)"
-    select PIGWEED_BYTES
-
-config PIGWEED_ROUTER_EGRESS_FUNCTION
-    bool "Enable the Pigweed router egress function library (pw_router.egress_function)"
-    select PIGWEED_RPC_EGRESS
-
-endif # PIGWEED_ROUTER
diff --git a/pw_router/OWNERS b/pw_router/OWNERS
deleted file mode 100644
index 3afb926..0000000
--- a/pw_router/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-frolv@google.com
diff --git a/pw_router/docs.rst b/pw_router/docs.rst
index aefabc9..76fec30 100644
--- a/pw_router/docs.rst
+++ b/pw_router/docs.rst
@@ -22,29 +22,6 @@
 link. Egress implementations provide a single ``SendPacket`` function, which
 takes the raw packet data and transmits it.
 
-Egresses are invoked with the ``PacketParser`` used to process the packet. This
-allows additional information to be extracted from each packet to assist with
-transmitting decisions. For example, if packets in a project include a priority,
-egresses may use it to provide quality-of-service by dropping certain packets
-under heavy load.
-
-.. code-block:: c++
-
-  Status MyEgress::SendPacket(
-      ConstByteSpan packet, const PacketParser& parser) override {
-    // Downcast the base PacketParser to the custom implementation that was
-    // passed into RoutePacket().
-    const CustomPacketParser& custom_parser =
-        static_cast<const CustomPacketParser&>(parser);
-
-    // Custom packet fields can now be accessed if necessary.
-    if (custom_parser.priority() == MyPacketPriority::kHigh) {
-      return SendHighPriorityPacket(packet);
-    }
-
-    return SendStandardPriorityPacket(packet);
-  }
-
 Some common egress implementations are provided upstream in Pigweed.
 
 StaticRouter
@@ -63,39 +40,26 @@
 
   namespace {
 
-  // Define the router egresses.
+  // Define packet parser and egresses.
+  HdlcFrameParser hdlc_parser;
   UartEgress uart_egress;
   BluetoothEgress ble_egress;
 
   // Define the routing table.
   constexpr pw::router::StaticRouter::Route routes[] = {{1, uart_egress},
                                                         {7, ble_egress}};
-  pw::router::StaticRouter router(routes);
+  pw::router::StaticRouter router(hdlc_parser, routes);
 
   }  // namespace
 
   void ProcessPacket(pw::ConstByteSpan packet) {
-    HdlcFrameParser hdlc_parser;
-    router.RoutePacket(packet, hdlc_parser);
+    router.RoutePacket(packet);
   }
 
-Size report
------------
-The following size report shows the cost of a ``StaticRouter`` with a simple
-``PacketParser`` implementation and a single route using an ``EgressFunction``.
+.. TODO(frolv): Re-enable this when the size report builds.
+.. Size report
+.. -----------
+.. The following size report shows the cost of a ``StaticRouter`` with a simple
+.. ``PacketParser`` implementation and a single route using an ``EgressFunction``.
 
-.. include:: static_router_size
-
-Zephyr
-======
-To enable ``pw_router.*`` for Zephyr add ``CONFIG_PIGWEED_ROUTER=y`` to the
-project's configuration. This will enable the Kconfig menu for the following:
-
-* ``pw_router.static_router`` which can be enabled via
-  ``CONFIG_PIGWEED_ROUTER_STATIC_ROUTER=y``.
-* ``pw_router.egress`` which can be enabled via
-  ``CONFIG_PIGWEED_ROUTER_EGRESS=y``.
-* ``pw_router.packet_parser`` which can be enabled via
-  ``CONFIG_PIGWEED_ROUTER_PACKET_PARSER=y``.
-* ``pw_router.egress_function`` which can be enabled via
-  ``CONFIG_PIGWEED_ROUTER_EGRESS_FUNCTION=y``.
+.. .. include:: static_router_size
diff --git a/pw_router/public/pw_router/egress.h b/pw_router/public/pw_router/egress.h
index cf7613b..09097e0 100644
--- a/pw_router/public/pw_router/egress.h
+++ b/pw_router/public/pw_router/egress.h
@@ -13,11 +13,9 @@
 // the License.
 #pragma once
 
-#include <optional>
 #include <span>
 
 #include "pw_bytes/span.h"
-#include "pw_router/packet_parser.h"
 #include "pw_status/status.h"
 
 namespace pw::router {
@@ -28,12 +26,10 @@
   virtual ~Egress() = default;
 
   // Sends a complete packet/frame over the transport. Returns OK on success, or
-  // an error status on failure. Invoked with the packet data and the parser
-  // from the RoutePacket() call.
+  // an error status on failure.
   //
   // TODO(frolv): Document possible return values.
-  virtual Status SendPacket(ConstByteSpan packet,
-                            const PacketParser& parser) = 0;
+  virtual Status SendPacket(ConstByteSpan packet) = 0;
 };
 
 }  // namespace pw::router
diff --git a/pw_router/public/pw_router/egress_function.h b/pw_router/public/pw_router/egress_function.h
index 83620aa..e766766 100644
--- a/pw_router/public/pw_router/egress_function.h
+++ b/pw_router/public/pw_router/egress_function.h
@@ -15,7 +15,6 @@
 
 #include <span>
 
-#include "pw_function/function.h"
 #include "pw_router/egress.h"
 
 namespace pw::router {
@@ -23,16 +22,12 @@
 // Router egress that dispatches to a free function.
 class EgressFunction final : public Egress {
  public:
-  EgressFunction(
-      Function<Status(ConstByteSpan, const PacketParser&)>&& function)
-      : func_(std::move(function)) {}
+  constexpr EgressFunction(Status (*func)(ConstByteSpan)) : func_(*func) {}
 
-  Status SendPacket(ConstByteSpan packet, const PacketParser& parser) final {
-    return func_(packet, parser);
-  }
+  Status SendPacket(ConstByteSpan packet) final { return func_(packet); }
 
  private:
-  Function<Status(ConstByteSpan, const PacketParser&)> func_;
+  Status (&func_)(ConstByteSpan);
 };
 
 }  // namespace pw::router
diff --git a/pw_router/public/pw_router/static_router.h b/pw_router/public/pw_router/static_router.h
index 1d2a07b..9c0925d 100644
--- a/pw_router/public/pw_router/static_router.h
+++ b/pw_router/public/pw_router/static_router.h
@@ -20,6 +20,8 @@
 #include "pw_router/egress.h"
 #include "pw_router/packet_parser.h"
 #include "pw_status/status.h"
+#include "pw_sync/lock_annotations.h"
+#include "pw_sync/mutex.h"
 
 namespace pw::router {
 
@@ -38,7 +40,8 @@
     Egress& egress;
   };
 
-  StaticRouter(std::span<const Route> routes) : routes_(routes) {}
+  StaticRouter(PacketParser& parser, std::span<const Route> routes)
+      : parser_(parser), routes_(routes) {}
 
   StaticRouter(const StaticRouter&) = delete;
   StaticRouter(StaticRouter&&) = delete;
@@ -60,10 +63,12 @@
   //   NOT_FOUND - No registered route for the packet.
   //   UNAVAILABLE - Route egress did not accept packet.
   //
-  Status RoutePacket(ConstByteSpan packet, PacketParser& parser);
+  Status RoutePacket(ConstByteSpan packet) PW_LOCKS_EXCLUDED(mutex_);
 
  private:
+  PacketParser& parser_ PW_GUARDED_BY(mutex_);
   const std::span<const Route> routes_;
+  sync::Mutex mutex_;
   PW_METRIC_GROUP(metrics_, "static_router");
   PW_METRIC(metrics_, parser_errors_, "parser_errors", 0u);
   PW_METRIC(metrics_, route_errors_, "route_errors", 0u);
diff --git a/pw_router/size_report/BUILD b/pw_router/size_report/BUILD
new file mode 100644
index 0000000..f90c83b
--- /dev/null
+++ b/pw_router/size_report/BUILD
@@ -0,0 +1,45 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_binary",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_binary(
+    name = "base",
+    srcs = ["base.cc"],
+    deps = [
+        "//pw_assert",
+        "//pw_bloat:bloat_this_binary",
+        "//pw_log",
+        "//pw_sys_io",
+    ],
+)
+
+pw_cc_binary(
+    name = "static_router_with_one_route",
+    srcs = ["static_router_with_one_route.cc"],
+    deps = [
+        "//pw_assert",
+        "//pw_bloat:bloat_this_binary",
+        "//pw_log",
+        "//pw_router:static_router",
+        "//pw_sys_io",
+    ],
+)
diff --git a/pw_router/size_report/BUILD.bazel b/pw_router/size_report/BUILD.bazel
deleted file mode 100644
index 25eb2bc..0000000
--- a/pw_router/size_report/BUILD.bazel
+++ /dev/null
@@ -1,46 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_binary",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_binary(
-    name = "base",
-    srcs = ["base.cc"],
-    deps = [
-        "//pw_assert",
-        "//pw_bloat:bloat_this_binary",
-        "//pw_log",
-        "//pw_sys_io",
-    ],
-)
-
-pw_cc_binary(
-    name = "static_router_with_one_route",
-    srcs = ["static_router_with_one_route.cc"],
-    deps = [
-        "//pw_assert",
-        "//pw_bloat:bloat_this_binary",
-        "//pw_log",
-        "//pw_router:egress_function",
-        "//pw_router:static_router",
-        "//pw_sys_io",
-    ],
-)
diff --git a/pw_router/size_report/BUILD.gn b/pw_router/size_report/BUILD.gn
index 6f283c0..e30430e 100644
--- a/pw_router/size_report/BUILD.gn
+++ b/pw_router/size_report/BUILD.gn
@@ -30,8 +30,5 @@
 
 pw_executable("static_router_with_one_route") {
   sources = [ "static_router_with_one_route.cc" ]
-  deps = _common_deps + [
-           "..:static_router",
-           "..:egress_function",
-         ]
+  deps = _common_deps + [ "..:static_router" ]
 }
diff --git a/pw_router/size_report/base.cc b/pw_router/size_report/base.cc
index 132616e..8d63f6f 100644
--- a/pw_router/size_report/base.cc
+++ b/pw_router/size_report/base.cc
@@ -12,7 +12,7 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_bloat/bloat_this_binary.h"
 #include "pw_log/log.h"
 #include "pw_sys_io/sys_io.h"
diff --git a/pw_router/size_report/static_router_with_one_route.cc b/pw_router/size_report/static_router_with_one_route.cc
index 1b1a983..a285954 100644
--- a/pw_router/size_report/static_router_with_one_route.cc
+++ b/pw_router/size_report/static_router_with_one_route.cc
@@ -12,7 +12,7 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_bloat/bloat_this_binary.h"
 #include "pw_log/log.h"
 #include "pw_router/egress_function.h"
@@ -54,12 +54,12 @@
   const BasicPacket* packet_;
 };
 
-pw::router::EgressFunction sys_io_egress(+[](pw::ConstByteSpan packet,
-                                             const pw::router::PacketParser&) {
+BasicPacketParser parser;
+pw::router::EgressFunction sys_io_egress(+[](pw::ConstByteSpan packet) {
   return pw::sys_io::WriteBytes(packet).status();
 });
 constexpr pw::router::StaticRouter::Route routes[] = {{1, sys_io_egress}};
-pw::router::StaticRouter router(routes);
+pw::router::StaticRouter router(parser, routes);
 
 }  // namespace
 
@@ -76,11 +76,9 @@
   pw::sys_io::ReadBytes(packet_buffer);
   pw::sys_io::WriteBytes(packet_buffer);
 
-  BasicPacketParser parser;
-
   while (true) {
     pw::sys_io::ReadBytes(packet_buffer);
-    router.RoutePacket(packet_buffer, parser);
+    router.RoutePacket(packet_buffer);
   }
 
   return static_cast<int>(packet.payload);
diff --git a/pw_router/static_router.cc b/pw_router/static_router.cc
index 580f97d..a32e74a 100644
--- a/pw_router/static_router.cc
+++ b/pw_router/static_router.cc
@@ -15,30 +15,41 @@
 #include "pw_router/static_router.h"
 
 #include <algorithm>
+#include <mutex>
 
 namespace pw::router {
 
-Status StaticRouter::RoutePacket(ConstByteSpan packet, PacketParser& parser) {
-  if (!parser.Parse(packet)) {
-    parser_errors_.Increment();
-    return Status::DataLoss();
-  }
+Status StaticRouter::RoutePacket(ConstByteSpan packet) {
+  uint32_t address;
 
-  std::optional<uint32_t> maybe_address = parser.GetDestinationAddress();
-  if (!maybe_address.has_value()) {
-    parser_errors_.Increment();
-    return Status::DataLoss();
+  {
+    // Only packet parsing is synchronized within the router; egresses must be
+    // synchronized externally.
+    std::lock_guard lock(mutex_);
+
+    if (!parser_.Parse(packet)) {
+      parser_errors_.Increment();
+      return Status::DataLoss();
+    }
+
+    std::optional<uint32_t> result = parser_.GetDestinationAddress();
+    if (!result.has_value()) {
+      parser_errors_.Increment();
+      return Status::DataLoss();
+    }
+
+    address = result.value();
   }
 
   auto route = std::find_if(routes_.begin(), routes_.end(), [&](auto r) {
-    return r.address == *maybe_address;
+    return r.address == address;
   });
   if (route == routes_.end()) {
     route_errors_.Increment();
     return Status::NotFound();
   }
 
-  if (Status status = route->egress.SendPacket(packet, parser); !status.ok()) {
+  if (Status status = route->egress.SendPacket(packet); !status.ok()) {
     egress_errors_.Increment();
     return Status::Unavailable();
   }
diff --git a/pw_router/static_router_test.cc b/pw_router/static_router_test.cc
index e800f49..72ca116 100644
--- a/pw_router/static_router_test.cc
+++ b/pw_router/static_router_test.cc
@@ -15,7 +15,6 @@
 #include "pw_router/static_router.h"
 
 #include "gtest/gtest.h"
-#include "pw_assert/check.h"
 #include "pw_router/egress_function.h"
 
 namespace pw::router {
@@ -25,16 +24,12 @@
   static constexpr uint32_t kMagic = 0x8badf00d;
 
   constexpr BasicPacket(uint32_t addr, uint64_t data)
-      : magic(kMagic), address(addr), priority(0), payload(data) {}
-
-  constexpr BasicPacket(uint32_t addr, uint32_t prio, uint64_t data)
-      : magic(kMagic), address(addr), priority(prio), payload(data) {}
+      : magic(kMagic), address(addr), payload(data) {}
 
   ConstByteSpan data() const { return std::as_bytes(std::span(this, 1)); }
 
   uint32_t magic;
   uint32_t address;
-  uint32_t priority;
   uint64_t payload;
 };
 
@@ -52,96 +47,66 @@
     return packet_->address;
   }
 
-  uint32_t priority() const {
-    PW_DCHECK_NOTNULL(packet_);
-    return packet_->priority;
-  };
-
  private:
   const BasicPacket* packet_;
 };
 
-EgressFunction GoodEgress(+[](ConstByteSpan, const PacketParser&) {
-  return OkStatus();
-});
-EgressFunction BadEgress(+[](ConstByteSpan, const PacketParser&) {
+EgressFunction GoodEgress(+[](ConstByteSpan) { return OkStatus(); });
+EgressFunction BadEgress(+[](ConstByteSpan) {
   return Status::ResourceExhausted();
 });
 
 TEST(StaticRouter, RoutePacket_RoutesToAnEgress) {
   BasicPacketParser parser;
   constexpr StaticRouter::Route routes[] = {{1, GoodEgress}, {2, BadEgress}};
-  StaticRouter router(routes);
+  StaticRouter router(parser, std::span(routes));
 
-  EXPECT_EQ(router.RoutePacket(BasicPacket(1, 0xdddd).data(), parser),
-            OkStatus());
-  EXPECT_EQ(router.RoutePacket(BasicPacket(2, 0xdddd).data(), parser),
+  EXPECT_EQ(router.RoutePacket(BasicPacket(1, 0xdddd).data()), OkStatus());
+  EXPECT_EQ(router.RoutePacket(BasicPacket(2, 0xdddd).data()),
             Status::Unavailable());
 }
 
-TEST(StaticRouter, RoutePacket_ForwardsPacketParser) {
-  uint32_t parser_priority = 0xffffffff;
-
-  EgressFunction parser_egress(
-      [&parser_priority](ConstByteSpan, const PacketParser& parser) {
-        const BasicPacketParser& basic_parser =
-            static_cast<const BasicPacketParser&>(parser);
-        parser_priority = basic_parser.priority();
-        return OkStatus();
-      });
-
-  StaticRouter::Route routes[] = {{1, parser_egress}};
-  StaticRouter router(routes);
-  BasicPacketParser parser;
-
-  EXPECT_EQ(router.RoutePacket(BasicPacket(1, 71, 0xdddd).data(), parser),
-            OkStatus());
-  EXPECT_EQ(parser_priority, 71u);
-}
-
 TEST(StaticRouter, RoutePacket_ReturnsParserError) {
   BasicPacketParser parser;
   constexpr StaticRouter::Route routes[] = {{1, GoodEgress}, {2, BadEgress}};
-  StaticRouter router(routes);
+  StaticRouter router(parser, std::span(routes));
 
   BasicPacket bad_magic(1, 0xdddd);
   bad_magic.magic = 0x1badda7a;
-  EXPECT_EQ(router.RoutePacket(bad_magic.data(), parser), Status::DataLoss());
+  EXPECT_EQ(router.RoutePacket(bad_magic.data()), Status::DataLoss());
 }
 
 TEST(StaticRouter, RoutePacket_ReturnsNotFoundOnInvalidRoute) {
   BasicPacketParser parser;
   constexpr StaticRouter::Route routes[] = {{1, GoodEgress}, {2, BadEgress}};
-  StaticRouter router(routes);
+  StaticRouter router(parser, std::span(routes));
 
-  EXPECT_EQ(router.RoutePacket(BasicPacket(42, 0xdddd).data(), parser),
+  EXPECT_EQ(router.RoutePacket(BasicPacket(42, 0xdddd).data()),
             Status::NotFound());
 }
 
 TEST(StaticRouter, RoutePacket_TracksNumberOfDrops) {
   BasicPacketParser parser;
   constexpr StaticRouter::Route routes[] = {{1, GoodEgress}, {2, BadEgress}};
-  StaticRouter router(routes);
+  StaticRouter router(parser, std::span(routes));
 
   // Good
-  EXPECT_EQ(router.RoutePacket(BasicPacket(1, 0xdddd).data(), parser),
-            OkStatus());
+  EXPECT_EQ(router.RoutePacket(BasicPacket(1, 0xdddd).data()), OkStatus());
 
   // Egress error
-  EXPECT_EQ(router.RoutePacket(BasicPacket(2, 0xdddd).data(), parser),
+  EXPECT_EQ(router.RoutePacket(BasicPacket(2, 0xdddd).data()),
             Status::Unavailable());
 
   // Parser error
   BasicPacket bad_magic(1, 0xdddd);
   bad_magic.magic = 0x1badda7a;
-  EXPECT_EQ(router.RoutePacket(bad_magic.data(), parser), Status::DataLoss());
+  EXPECT_EQ(router.RoutePacket(bad_magic.data()), Status::DataLoss());
 
   // Good
-  EXPECT_EQ(router.RoutePacket(BasicPacket(1, 0xdddd).data(), parser),
-            OkStatus());
+  EXPECT_EQ(router.RoutePacket(BasicPacket(1, 0xdddd).data()), OkStatus());
 
   // Bad route
-  EXPECT_EQ(router.RoutePacket(BasicPacket(42, 0xdddd).data(), parser),
+  EXPECT_EQ(router.RoutePacket(BasicPacket(42, 0xdddd).data()),
             Status::NotFound());
 
   EXPECT_EQ(router.dropped_packets(), 3u);
diff --git a/pw_rpc/Android.bp b/pw_rpc/Android.bp
deleted file mode 100644
index 3913566..0000000
--- a/pw_rpc/Android.bp
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-java_library {
-    name: "pw_rpc_java_client",
-    srcs: ["java/main/dev/pigweed/pw_rpc/*.java"],
-    visibility: ["//visibility:public"],
-    static_libs: [
-        "pw_rpc_packet_proto_java_lite",
-    ],
-    libs: [
-        "auto_value_annotations",
-        "guava",
-        "jsr305",
-        "libprotobuf-java-lite",
-        "pw_log_android_java",
-    ],
-    plugins: ["auto_value_plugin"],
-    sdk_version: "current",
-}
-
-java_library_static {
-    name: "pw_rpc_packet_proto_java_lite",
-    host_supported: true,
-    proto: {
-        type: "lite",
-    },
-    srcs: ["internal/packet.proto"],
-    sdk_version: "current",
-}
-
-java_library_static {
-    name: "pw_rpc_echo_proto_java_lite",
-    visibility: ["//visibility:public"],
-    host_supported: true,
-    proto: {
-        type: "lite",
-    },
-    srcs: ["echo.proto"],
-    sdk_version: "current",
-}
diff --git a/pw_rpc/BUILD b/pw_rpc/BUILD
new file mode 100644
index 0000000..b2cd789
--- /dev/null
+++ b/pw_rpc/BUILD
@@ -0,0 +1,237 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "client",
+    srcs = [
+        "base_client_call.cc",
+        "client.cc",
+    ],
+    hdrs = [
+        "public/pw_rpc/client.h",
+        "public/pw_rpc/internal/base_client_call.h",
+    ],
+    deps = [
+        ":common",
+    ],
+)
+
+pw_cc_library(
+    name = "server",
+    srcs = [
+        "base_server_writer.cc",
+        "public/pw_rpc/internal/base_server_writer.h",
+        "public/pw_rpc/internal/call.h",
+        "public/pw_rpc/internal/hash.h",
+        "public/pw_rpc/internal/method.h",
+        "public/pw_rpc/internal/method_lookup.h",
+        "public/pw_rpc/internal/method_union.h",
+        "public/pw_rpc/internal/server.h",
+        "server.cc",
+        "service.cc",
+    ],
+    hdrs = [
+        "public/pw_rpc/server.h",
+        "public/pw_rpc/server_context.h",
+        "public/pw_rpc/service.h",
+    ],
+    includes = ["public"],
+    deps = [
+        ":common",
+    ],
+)
+
+pw_cc_library(
+    name = "client_server",
+    srcs = ["client_server.cc"],
+    hdrs = ["public/pw_rpc/client_server.h"],
+    deps = [
+        ":client",
+        ":server",
+    ],
+)
+
+pw_cc_library(
+    name = "common",
+    srcs = [
+        "channel.cc",
+        "packet.cc",
+        "public/pw_rpc/internal/channel.h",
+        "public/pw_rpc/internal/config.h",
+        "public/pw_rpc/internal/method_type.h",
+        "public/pw_rpc/internal/packet.h",
+    ],
+    hdrs = [
+        "public/pw_rpc/channel.h",
+    ],
+    includes = ["public"],
+    deps = [
+        "//pw_assert",
+        "//pw_log",
+        "//pw_span",
+        "//pw_status",
+    ],
+)
+
+pw_cc_library(
+    name = "synchronized_channel_output",
+    hdrs = ["public/pw_rpc/synchronized_channel_output.h"],
+    includes = ["public"],
+    deps = [
+        ":common",
+        "//pw_sync:lock_annotations",
+        "//pw_sync:mutex",
+    ],
+)
+
+pw_cc_library(
+    name = "internal_test_utils",
+    hdrs = [
+        "public/pw_rpc/internal/test_method.h",
+        "pw_rpc_private/internal_test_utils.h",
+        "pw_rpc_private/method_impl_tester.h",
+    ],
+    visibility = ["//visibility:private"],
+    deps = [
+        ":server",
+        "//pw_span",
+    ],
+)
+
+# TODO(hepler): Cannot build nanopb-dependent code in Bazel at the moment. Need
+# to determine how best to support Nanopb builds and protobuf generation.
+filegroup(
+    name = "nanopb",
+    srcs = [
+        "nanopb/codegen_test.cc",
+        "nanopb/echo_service_test.cc",
+        "nanopb/method_lookup_test.cc",
+        "nanopb/nanopb_client_call.cc",
+        "nanopb/nanopb_client_call_test.cc",
+        "nanopb/nanopb_common.cc",
+        "nanopb/nanopb_method.cc",
+        "nanopb/nanopb_method_test.cc",
+        "nanopb/nanopb_method_union_test.cc",
+        "nanopb/public/pw_rpc/echo_service_nanopb.h",
+        "nanopb/public/pw_rpc/internal/nanopb_common.h",
+        "nanopb/public/pw_rpc/internal/nanopb_method.h",
+        "nanopb/public/pw_rpc/internal/nanopb_method_union.h",
+        "nanopb/public/pw_rpc/nanopb_client_call.h",
+        "nanopb/public/pw_rpc/nanopb_test_method_context.h",
+        "nanopb/pw_rpc_nanopb_private/internal_test_utils.h",
+        "nanopb/stub_generation_test.cc",
+    ],
+)
+
+pw_cc_test(
+    name = "base_server_writer_test",
+    srcs = [
+        "base_server_writer_test.cc",
+    ],
+    deps = [
+        ":internal_test_utils",
+        ":server",
+    ],
+)
+
+pw_cc_test(
+    name = "base_client_call_test",
+    srcs = [
+        "base_client_call_test.cc",
+    ],
+    deps = [
+        ":client",
+        ":internal_test_utils",
+    ],
+)
+
+pw_cc_test(
+    name = "client_test",
+    srcs = [
+        "client_test.cc",
+    ],
+    deps = [
+        ":client",
+        ":internal_test_utils",
+    ],
+)
+
+pw_cc_test(
+    name = "channel_test",
+    srcs = ["channel_test.cc"],
+    deps = [
+        ":server",
+        ":test_utils_test_server",
+    ],
+)
+
+pw_cc_test(
+    name = "packet_test",
+    srcs = [
+        "packet_test.cc",
+    ],
+    deps = [
+        ":server",
+    ],
+)
+
+pw_cc_test(
+    name = "client_server_test",
+    srcs = ["client_server_test.cc"],
+    deps = [
+        ":client_server",
+        "//pw_rpc/raw:method_union",
+    ],
+)
+
+proto_library(
+    name = "packet_proto",
+    srcs = [
+        "pw_rpc_protos/internal/packet.proto",
+    ],
+)
+
+pw_cc_test(
+    name = "server_test",
+    srcs = [
+        "server_test.cc",
+    ],
+    deps = [
+        ":internal_test_utils",
+        ":server",
+        "//pw_assert",
+    ],
+)
+
+pw_cc_test(
+    name = "service_test",
+    srcs = [
+        "service_test.cc",
+    ],
+    deps = [
+        ":internal_test_utils",
+        ":server",
+        "//pw_assert",
+    ],
+)
diff --git a/pw_rpc/BUILD.bazel b/pw_rpc/BUILD.bazel
deleted file mode 100644
index e29ff15..0000000
--- a/pw_rpc/BUILD.bazel
+++ /dev/null
@@ -1,351 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load("//pw_build:pigweed.bzl", "pw_cc_library", "pw_cc_test")
-load("//pw_protobuf_compiler:proto.bzl", "pw_proto_library")
-load("@com_google_protobuf//:protobuf.bzl", "py_proto_library")
-load("@rules_proto//proto:defs.bzl", "proto_library")
-load("@rules_proto_grpc//:defs.bzl", "proto_plugin")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-proto_library(
-    name = "benchmark_proto",
-    srcs = [
-        "benchmark.proto",
-    ],
-)
-
-pw_proto_library(
-    name = "benchmark_cc",
-    deps = [":benchmark_proto"],
-)
-
-pw_cc_library(
-    name = "benchmark",
-    srcs = ["benchmark.cc"],
-    hdrs = ["public/pw_rpc/benchmark.h"],
-    includes = ["public"],
-    deps = [
-        ":benchmark_cc.pwpb",
-        ":benchmark_cc.raw_rpc",
-    ],
-)
-
-# TODO(pwbug/507): Build this as a cc_binary and use it in integration tests.
-filegroup(
-    name = "test_rpc_server",
-    srcs = ["test_rpc_server.cc"],
-    # deps = [
-    #     "system_server",
-    #     ":benchmark",
-    #     "//pw_log",
-    # ],
-)
-
-pw_cc_library(
-    name = "client_server",
-    srcs = ["client_server.cc"],
-    hdrs = ["public/pw_rpc/client_server.h"],
-    deps = [":pw_rpc"],
-)
-
-pw_cc_library(
-    name = "pw_rpc",
-    srcs = [
-        "call.cc",
-        "channel.cc",
-        "channel_list.cc",
-        "client.cc",
-        "client_call.cc",
-        "endpoint.cc",
-        "packet.cc",
-        "public/pw_rpc/internal/call.h",
-        "public/pw_rpc/internal/call_context.h",
-        "public/pw_rpc/internal/channel.h",
-        "public/pw_rpc/internal/channel_list.h",
-        "public/pw_rpc/internal/client_call.h",
-        "public/pw_rpc/internal/config.h",
-        "public/pw_rpc/internal/endpoint.h",
-        "public/pw_rpc/internal/hash.h",
-        "public/pw_rpc/internal/lock.h",
-        "public/pw_rpc/internal/log_config.h",
-        "public/pw_rpc/internal/method.h",
-        "public/pw_rpc/internal/method_info.h",
-        "public/pw_rpc/internal/method_lookup.h",
-        "public/pw_rpc/internal/method_union.h",
-        "public/pw_rpc/internal/packet.h",
-        "public/pw_rpc/internal/server_call.h",
-        "public/pw_rpc/method_type.h",
-        "public/pw_rpc/writer.h",
-        "server.cc",
-        "server_call.cc",
-        "service.cc",
-    ],
-    hdrs = [
-        "public/pw_rpc/channel.h",
-        "public/pw_rpc/client.h",
-        "public/pw_rpc/internal/service_client.h",
-        "public/pw_rpc/server.h",
-        "public/pw_rpc/service.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":internal_packet_cc.pwpb",
-        "//pw_assert",
-        "//pw_bytes",
-        "//pw_containers",
-        "//pw_containers:intrusive_list",
-        "//pw_function",
-        "//pw_log",
-        "//pw_result",
-        "//pw_span",
-        "//pw_status",
-        "//pw_sync:lock_annotations",
-    ],
-)
-
-pw_cc_library(
-    name = "thread_testing",
-    hdrs = ["public/pw_rpc/thread_testing.h"],
-    includes = ["public"],
-    deps = [
-        ":internal_test_utils",
-        "//pw_assert",
-        "//pw_sync:counting_semaphore",
-    ],
-)
-
-pw_cc_library(
-    name = "internal_test_utils",
-    srcs = ["fake_channel_output.cc"],
-    hdrs = [
-        "public/pw_rpc/internal/fake_channel_output.h",
-        "public/pw_rpc/internal/method_impl_tester.h",
-        "public/pw_rpc/internal/method_info_tester.h",
-        "public/pw_rpc/internal/test_method.h",
-        "public/pw_rpc/internal/test_method_context.h",
-        "public/pw_rpc/internal/test_utils.h",
-        "public/pw_rpc/payloads_view.h",
-        "pw_rpc_private/fake_server_reader_writer.h",
-    ],
-    includes = [
-        ".",
-        "public",
-    ],
-    visibility = [":__subpackages__"],
-    deps = [
-        ":pw_rpc",
-        "//pw_assert",
-        "//pw_bytes",
-        "//pw_containers:filtered_view",
-        "//pw_containers:vector",
-        "//pw_containers:wrapped_iterator",
-        "//pw_rpc/raw:fake_channel_output",
-        "//pw_span",
-        "//pw_sync:mutex",
-    ],
-)
-
-# TODO(pwbug/507): Enable this library when logging_event_handler can be used.
-filegroup(
-    name = "integration_testing",
-    srcs = [
-        "integration_testing.cc",
-        # ],
-        # hdrs = [
-        "public/pw_rpc/integration_test_socket_client.h",
-        "public/pw_rpc/integration_testing.h",
-    ],
-    #deps = [
-    #    ":client",
-    #    "//pw_assert",
-    #    "//pw_hdlc:pw_rpc",
-    #    "//pw_hdlc:rpc_channel_output",
-    #    "//pw_log",
-    #    "//pw_stream:socket_stream",
-    #    "//pw_unit_test",
-    #    "//pw_unit_test:logging_event_handler",
-    #],
-)
-
-# TODO(pwbug/507): Add the client integration test to the build.
-filegroup(
-    name = "client_integration_test",
-    srcs = ["client_integration_test.cc"],
-)
-
-pw_cc_test(
-    name = "call_test",
-    srcs = [
-        "call_test.cc",
-    ],
-    deps = [
-        ":internal_test_utils",
-        ":pw_rpc",
-    ],
-)
-
-pw_cc_test(
-    name = "channel_test",
-    srcs = ["channel_test.cc"],
-    deps = [
-        ":internal_test_utils",
-        ":pw_rpc",
-    ],
-)
-
-pw_cc_test(
-    name = "method_test",
-    srcs = ["method_test.cc"],
-    deps = [
-        ":internal_test_utils",
-        ":pw_rpc",
-    ],
-)
-
-pw_cc_test(
-    name = "packet_test",
-    srcs = [
-        "packet_test.cc",
-    ],
-    deps = [
-        ":pw_rpc",
-    ],
-)
-
-pw_cc_test(
-    name = "client_server_test",
-    srcs = ["client_server_test.cc"],
-    deps = [
-        ":client_server",
-        ":internal_test_utils",
-        "//pw_rpc/raw:server_api",
-    ],
-)
-
-pw_cc_test(
-    name = "server_test",
-    srcs = [
-        "server_test.cc",
-    ],
-    deps = [
-        ":internal_test_utils",
-        ":pw_rpc",
-        "//pw_assert",
-    ],
-)
-
-pw_cc_test(
-    name = "service_test",
-    srcs = [
-        "service_test.cc",
-    ],
-    deps = [
-        ":internal_test_utils",
-        ":pw_rpc",
-        "//pw_assert",
-    ],
-)
-
-pw_cc_test(
-    name = "fake_channel_output_test",
-    srcs = ["fake_channel_output_test.cc"],
-    deps = [":internal_test_utils"],
-)
-
-proto_library(
-    name = "internal_packet_proto",
-    srcs = ["internal/packet.proto"],
-    visibility = [":__subpackages__"],
-)
-
-java_lite_proto_library(
-    name = "packet_proto_java_lite",
-    deps = [":internal_packet_proto"],
-)
-
-py_proto_library(
-    name = "internal_packet_proto_pb2",
-    srcs = ["internal/packet.proto"],
-)
-
-pw_proto_library(
-    name = "internal_packet_cc",
-    deps = [":internal_packet_proto"],
-)
-
-proto_library(
-    name = "pw_rpc_test_proto",
-    srcs = ["pw_rpc_test_protos/test.proto"],
-    strip_import_prefix = "//pw_rpc",
-)
-
-pw_proto_library(
-    name = "pw_rpc_test_cc",
-    deps = [":pw_rpc_test_proto"],
-)
-
-proto_plugin(
-    name = "pw_cc_plugin_raw",
-    outputs = [
-        "{protopath}.raw_rpc.pb.h",
-    ],
-    protoc_plugin_name = "raw_rpc",
-    tool = "@pigweed//pw_rpc/py:plugin_raw",
-    use_built_in_shell_environment = True,
-    visibility = ["//visibility:public"],
-)
-
-proto_plugin(
-    name = "pw_cc_plugin_nanopb_rpc",
-    outputs = [
-        "{protopath}.rpc.pb.h",
-    ],
-    protoc_plugin_name = "nanopb_rpc",
-    tool = "@pigweed//pw_rpc/py:plugin_nanopb",
-    use_built_in_shell_environment = True,
-    visibility = ["//visibility:public"],
-)
-
-proto_plugin(
-    name = "nanopb_plugin",
-    options = [
-        "--library-include-format='#include\"%s\"'",
-    ],
-    outputs = [
-        "{protopath}.pb.h",
-        "{protopath}.pb.c",
-    ],
-    separate_options_flag = True,
-    tool = "@com_github_nanopb_nanopb//:bazel_generator",
-    use_built_in_shell_environment = True,
-    visibility = ["//visibility:public"],
-)
-
-proto_library(
-    name = "echo_proto",
-    srcs = [
-        "echo.proto",
-    ],
-)
-
-pw_proto_library(
-    name = "echo_cc",
-    deps = [":echo_proto"],
-    # TODO(tpudlik): We should provide echo.options to nanopb here, but the
-    # current proto codegen implementation provides no mechanism for doing so.
-)
diff --git a/pw_rpc/BUILD.gn b/pw_rpc/BUILD.gn
index 14539c5..9f4a95f 100644
--- a/pw_rpc/BUILD.gn
+++ b/pw_rpc/BUILD.gn
@@ -15,33 +15,27 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_bloat/bloat.gni")
+import("$dir_pw_build/module_config.gni")
 import("$dir_pw_build/python.gni")
 import("$dir_pw_build/python_action.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_protobuf_compiler/proto.gni")
-import("$dir_pw_sync/backend.gni")
 import("$dir_pw_third_party/nanopb/nanopb.gni")
 import("$dir_pw_unit_test/test.gni")
-import("config.gni")
-import("internal/integration_test_ports.gni")
+
+declare_args() {
+  # The build target that overrides the default configuration options for this
+  # module. This should point to a source set that provides defines through a
+  # public config (which may -include a file or add defines directly).
+  pw_rpc_CONFIG = pw_build_DEFAULT_MODULE_CONFIG
+}
 
 config("public_include_path") {
   include_dirs = [ "public" ]
   visibility = [ ":*" ]
 }
 
-config("global_mutex_config") {
-  defines = [ "PW_RPC_USE_GLOBAL_MUTEX=1" ]
-  visibility = [ ":*" ]
-}
-
-# Set pw_rpc_CONFIG to this to enable the global mutex. If additional options
-# are needed, a config target that sets those can depend on this.
-group("use_global_mutex") {
-  public_configs = [ ":global_mutex_config" ]
-}
-
 pw_source_set("config") {
   sources = [ "public/pw_rpc/internal/config.h" ]
   public_configs = [ ":public_include_path" ]
@@ -50,59 +44,42 @@
   friend = [ "./*" ]
 }
 
-pw_source_set("log_config") {
-  sources = [ "public/pw_rpc/internal/log_config.h" ]
-  public_configs = [ ":public_include_path" ]
-  public_deps = [ ":config" ]
-  visibility = [ "./*" ]
-  friend = [ "./*" ]
-}
-
 pw_source_set("server") {
   public_configs = [ ":public_include_path" ]
   public_deps = [ ":common" ]
-  deps = [
-    ":log_config",
-    dir_pw_log,
-  ]
+  deps = [ dir_pw_log ]
   public = [
     "public/pw_rpc/server.h",
+    "public/pw_rpc/server_context.h",
     "public/pw_rpc/service.h",
   ]
   sources = [
+    "base_server_writer.cc",
+    "public/pw_rpc/internal/base_server_writer.h",
+    "public/pw_rpc/internal/call.h",
     "public/pw_rpc/internal/hash.h",
     "public/pw_rpc/internal/method.h",
     "public/pw_rpc/internal/method_lookup.h",
     "public/pw_rpc/internal/method_union.h",
-    "public/pw_rpc/internal/server_call.h",
+    "public/pw_rpc/internal/server.h",
     "server.cc",
-    "server_call.cc",
     "service.cc",
   ]
   friend = [ "./*" ]
-  allow_circular_includes_from = [ ":common" ]
 }
 
 pw_source_set("client") {
   public_configs = [ ":public_include_path" ]
-  public_deps = [
-    ":common",
-    dir_pw_result,
-  ]
-  deps = [
-    ":log_config",
-    dir_pw_log,
-  ]
+  public_deps = [ ":common" ]
+  deps = [ dir_pw_log ]
   public = [
     "public/pw_rpc/client.h",
-    "public/pw_rpc/internal/client_call.h",
-    "public/pw_rpc/internal/service_client.h",
+    "public/pw_rpc/internal/base_client_call.h",
   ]
   sources = [
+    "base_client_call.cc",
     "client.cc",
-    "client_call.cc",
   ]
-  allow_circular_includes_from = [ ":common" ]
 }
 
 pw_source_set("client_server") {
@@ -119,209 +96,72 @@
 pw_source_set("common") {
   public_configs = [ ":public_include_path" ]
   public_deps = [
-    ":config",
     ":protos.pwpb",
     "$dir_pw_containers:intrusive_list",
-    "$dir_pw_sync:lock_annotations",
     dir_pw_assert,
     dir_pw_bytes,
-    dir_pw_function,
     dir_pw_status,
   ]
-
-  if (pw_sync_MUTEX_BACKEND != "") {
-    public_deps += [ "$dir_pw_sync:mutex" ]
-  }
-
-  deps = [
-    ":log_config",
-    dir_pw_log,
-  ]
-  public = [
-    "public/pw_rpc/channel.h",
-    "public/pw_rpc/writer.h",
-  ]
+  deps = [ dir_pw_log ]
+  public = [ "public/pw_rpc/channel.h" ]
   sources = [
-    "call.cc",
     "channel.cc",
-    "channel_list.cc",
-    "endpoint.cc",
     "packet.cc",
-    "public/pw_rpc/internal/call.h",
-    "public/pw_rpc/internal/call_context.h",
     "public/pw_rpc/internal/channel.h",
-    "public/pw_rpc/internal/channel_list.h",
-    "public/pw_rpc/internal/endpoint.h",
-    "public/pw_rpc/internal/lock.h",
-    "public/pw_rpc/internal/method_info.h",
+    "public/pw_rpc/internal/method_type.h",
     "public/pw_rpc/internal/packet.h",
-    "public/pw_rpc/method_type.h",
   ]
   friend = [ "./*" ]
 }
 
-pw_source_set("benchmark") {
-  public_configs = [ ":public_include_path" ]
-  public_deps = [ ":protos.raw_rpc" ]
-  public = [ "public/pw_rpc/benchmark.h" ]
-  sources = [ "benchmark.cc" ]
-}
-
-pw_source_set("fake_channel_output") {
-  public = [
-    "public/pw_rpc/internal/fake_channel_output.h",
-    "public/pw_rpc/payloads_view.h",
-  ]
-  sources = [ "fake_channel_output.cc" ]
+pw_source_set("synchronized_channel_output") {
   public_configs = [ ":public_include_path" ]
   public_deps = [
     ":common",
-    "$dir_pw_containers:filtered_view",
-    "$dir_pw_containers:vector",
-    "$dir_pw_containers:wrapped_iterator",
+    "$dir_pw_sync:lock_annotations",
     "$dir_pw_sync:mutex",
-    dir_pw_assert,
-    dir_pw_bytes,
-    dir_pw_function,
   ]
-  deps = [ ":log_config" ]
-  visibility = [ "./*" ]
-}
-
-pw_source_set("thread_testing") {
-  public = [ "public/pw_rpc/thread_testing.h" ]
-  public_deps = [
-    ":fake_channel_output",
-    "$dir_pw_sync:counting_semaphore",
-    dir_pw_assert,
-  ]
+  public = [ "public/pw_rpc/synchronized_channel_output.h" ]
 }
 
 pw_source_set("test_utils") {
   public = [
-    "public/pw_rpc/internal/fake_channel_output.h",
-    "public/pw_rpc/internal/method_impl_tester.h",
-    "public/pw_rpc/internal/method_info_tester.h",
     "public/pw_rpc/internal/test_method.h",
-    "public/pw_rpc/internal/test_method_context.h",
-    "public/pw_rpc/internal/test_utils.h",
-    "pw_rpc_private/fake_server_reader_writer.h",
+    "pw_rpc_private/internal_test_utils.h",
+    "pw_rpc_private/method_impl_tester.h",
   ]
-  public_configs = [ ":public_include_path" ]
+  public_configs = [ ":private_includes" ]
   public_deps = [
     ":client",
     ":server",
-    "$dir_pw_containers:vector",
-    "raw:fake_channel_output",
-    "raw:server_api",
-    dir_pw_assert,
-    dir_pw_bytes,
   ]
   visibility = [ "./*" ]
 }
 
-pw_source_set("integration_testing") {
-  public = [
-    "public/pw_rpc/integration_test_socket_client.h",
-    "public/pw_rpc/integration_testing.h",
-  ]
-  sources = [ "integration_testing.cc" ]
-  public_deps = [
-    ":client",
-    "$dir_pw_hdlc:pw_rpc",
-    "$dir_pw_hdlc:rpc_channel_output",
-    "$dir_pw_stream:socket_stream",
-    "$dir_pw_unit_test:logging_event_handler",
-    dir_pw_assert,
-    dir_pw_unit_test,
-  ]
-  deps = [ dir_pw_log ]
-}
-
-pw_executable("test_rpc_server") {
-  sources = [ "test_rpc_server.cc" ]
-  deps = [
-    ":benchmark",
-    ":log_config",
-    "system_server",
-    "system_server:socket",
-    dir_pw_log,
-  ]
-}
-
-pw_executable("client_integration_test") {
-  sources = [ "client_integration_test.cc" ]
-  deps = [
-    ":client",
-    ":integration_testing",
-    ":protos.raw_rpc",
-    "$dir_pw_sync:binary_semaphore",
-    dir_pw_log,
-    dir_pw_unit_test,
-  ]
-
-  if (dir_pw_third_party_nanopb != "") {
-    deps += [ "nanopb:client_integration_test" ]
-  }
-}
-
-pw_python_action("cpp_client_server_integration_test") {
-  script = "py/pw_rpc/testing.py"
-  args = [
-    "--server",
-    "<TARGET_FILE(:test_rpc_server)>",
-    "--client",
-    "<TARGET_FILE(:client_integration_test)>",
-    "--",
-    "$pw_rpc_CPP_CLIENT_INTEGRATION_TEST_PORT",
-  ]
-  deps = [
-    ":client_integration_test",
-    ":test_rpc_server",
-  ]
-
-  stamp = true
+config("private_includes") {
+  include_dirs = [ "." ]
+  visibility = [ ":*" ]
 }
 
 pw_proto_library("protos") {
   sources = [
-    "benchmark.proto",
     "echo.proto",
     "internal/packet.proto",
   ]
-  inputs = [
-    "echo.options",
-    "benchmark.options",
-  ]
-  deps = [ "$dir_pw_protobuf:common_protos" ]
+  inputs = [ "echo.options" ]
   python_package = "py"
   prefix = "pw_rpc"
 }
 
 pw_doc_group("docs") {
-  sources = [
-    "benchmark.rst",
-    "docs.rst",
-  ]
+  sources = [ "docs.rst" ]
   inputs = [
-    "benchmark.proto",
     "echo.proto",
     "internal/packet.proto",
-    "unary_rpc.svg",
-    "unary_rpc_cancelled.svg",
-    "server_streaming_rpc.svg",
-    "server_streaming_rpc_cancelled.svg",
-    "client_streaming_rpc.svg",
-    "client_streaming_rpc_cancelled.svg",
-    "bidirectional_streaming_rpc.svg",
-    "bidirectional_streaming_rpc_cancelled.svg",
-    "request_packets.svg",
-    "response_packets.svg",
   ]
   group_deps = [
     "nanopb:docs",
     "py:docs",
-    "ts:docs",
   ]
   report_deps = [ ":server_size" ]
 }
@@ -350,11 +190,11 @@
 
 pw_test_group("tests") {
   tests = [
-    ":call_test",
+    ":base_client_call_test",
+    ":base_server_writer_test",
     ":channel_test",
+    ":client_test",
     ":client_server_test",
-    ":fake_channel_output_test",
-    ":method_test",
     ":ids_test",
     ":packet_test",
     ":server_test",
@@ -372,12 +212,12 @@
   visibility = [ "./*" ]
 }
 
-pw_test("call_test") {
+pw_test("base_server_writer_test") {
   deps = [
     ":server",
     ":test_utils",
   ]
-  sources = [ "call_test.cc" ]
+  sources = [ "base_server_writer_test.cc" ]
 }
 
 pw_test("channel_test") {
@@ -392,7 +232,7 @@
   outputs = [ "$target_gen_dir/generated_ids_test.cc" ]
 
   script = "py/tests/ids_test.py"
-  args = [ "--generate-cc-test" ] + rebase_path(outputs, root_build_dir)
+  args = [ "--generate-cc-test" ] + rebase_path(outputs)
   python_deps = [
     "$dir_pw_build/py",
     "py",
@@ -425,21 +265,29 @@
   sources = [ "service_test.cc" ]
 }
 
+pw_test("client_test") {
+  deps = [
+    ":client",
+    ":test_utils",
+  ]
+  sources = [ "client_test.cc" ]
+}
+
 pw_test("client_server_test") {
   deps = [
     ":client_server",
     ":test_utils",
-    "raw:server_api",
+    "raw:method_union",
   ]
   sources = [ "client_server_test.cc" ]
 }
 
-pw_test("method_test") {
+pw_test("base_client_call_test") {
   deps = [
-    ":server",
+    ":client",
     ":test_utils",
   ]
-  sources = [ "method_test.cc" ]
+  sources = [ "base_client_call_test.cc" ]
 }
 
 pw_test("server_test") {
@@ -451,8 +299,3 @@
   ]
   sources = [ "server_test.cc" ]
 }
-
-pw_test("fake_channel_output_test") {
-  deps = [ ":test_utils" ]
-  sources = [ "fake_channel_output_test.cc" ]
-}
diff --git a/pw_rpc/CMakeLists.txt b/pw_rpc/CMakeLists.txt
index 7d5b2cf..c3635fe 100644
--- a/pw_rpc/CMakeLists.txt
+++ b/pw_rpc/CMakeLists.txt
@@ -24,33 +24,24 @@
 
 pw_add_module_library(pw_rpc.server
   SOURCES
-    call.cc
-    endpoint.cc
+    base_server_writer.cc
     server.cc
-    server_call.cc
     service.cc
   PUBLIC_DEPS
     pw_rpc.common
   PRIVATE_DEPS
     pw_log
 )
-if(Zephyr_FOUND AND CONFIG_PIGWEED_RPC_SERVER)
-  zephyr_link_libraries(pw_rpc.server)
-endif()
 
 pw_add_module_library(pw_rpc.client
   SOURCES
+    base_client_call.cc
     client.cc
-    client_call.cc
   PUBLIC_DEPS
     pw_rpc.common
-    pw_result
   PRIVATE_DEPS
     pw_log
 )
-if(Zephyr_FOUND AND CONFIG_PIGWEED_RPC_CLIENT)
-  zephyr_link_libraries(pw_rpc.client)
-endif()
 
 pw_add_module_library(pw_rpc.client_server
   SOURCES
@@ -59,46 +50,30 @@
     pw_rpc.client
     pw_rpc.server
 )
-if(Zephyr_FOUND AND CONFIG_PIGWEED_RPC_CLIENT_SERVER)
-  zephyr_link_libraries(pw_rpc.client_server)
-endif()
 
 pw_add_module_library(pw_rpc.common
   SOURCES
     channel.cc
-    channel_list.cc
     packet.cc
   PUBLIC_DEPS
     pw_assert
     pw_bytes
     pw_containers
-    pw_function
     pw_span
     pw_status
-    pw_sync.lock_annotations
     pw_rpc.protos.pwpb
   PRIVATE_DEPS
     pw_log
 )
-if(Zephyr_FOUND AND CONFIG_PIGWEED_RPC_COMMON)
-  zephyr_link_libraries(pw_rpc.common)
-endif()
 
-if (NOT "${pw_sync.mutex_BACKEND}" STREQUAL "pw_sync.mutex.NO_BACKEND_SET" AND
-    NOT "${pw_sync.mutex_BACKEND}" STREQUAL "")
-  target_link_libraries(pw_rpc.common PUBLIC pw_sync.mutex)
-endif()
-
-pw_add_module_library(pw_rpc.test_utils
-  SOURCES
-    fake_channel_output.cc
+pw_add_module_library(pw_rpc.synchronized_channel_output
   PUBLIC_DEPS
-    pw_assert
-    pw_bytes
-    pw_rpc.client
-    pw_rpc.server
+    pw_rpc.common
+    pw_sync.mutex
 )
-target_include_directories(pw_rpc.test_utils PUBLIC .)
+
+add_library(pw_rpc.test_utils INTERFACE)
+target_include_directories(pw_rpc.test_utils INTERFACE .)
 
 pw_proto_library(pw_rpc.protos
   SOURCES
diff --git a/pw_rpc/Kconfig b/pw_rpc/Kconfig
deleted file mode 100644
index 903e320..0000000
--- a/pw_rpc/Kconfig
+++ /dev/null
@@ -1,48 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-menuconfig PIGWEED_RPC
-    bool "Pigweed RPC submodule"
-
-if PIGWEED_RPC
-
-rsource "nanopb/Kconfig"
-
-config PIGWEED_RPC_SERVER
-    bool "Enable Pigweed RPC server library (pw_rpc.server)"
-    select PIGWEED_RPC_COMMON
-    select PIGWEED_LOG
-
-config PIGWEED_RPC_CLIENT
-    bool "Enable Pigweed RPC client library (pw_rpc.client)"
-    select PIGWEED_RPC_COMMON
-    select PIGWEED_RESULT
-    select PIGWEED_LOG
-
-config PIGWEED_RPC_CLIENT_SERVER
-    bool "Enable Pigweed RPC client-server library (pw_rpc.client_server)"
-    select PIGWEED_RPC_CLIENT
-    select PIGWEED_RPC_SERVER
-
-config PIGWEED_RPC_COMMON
-    bool "Enable Pigweed RPC common library (pw_rpc.common)"
-    select PIGWEED_ASSERT
-    select PIGWEED_BYTES
-    select PIGWEED_CONTAINERS
-    select PIGWEED_FUNCTION
-    select PIGWEED_SPAN
-    select PIGWEED_STATUS
-    select PIGWEED_LOG
-
-endif # PIGWEED_RPC
diff --git a/pw_rpc/OWNERS b/pw_rpc/OWNERS
deleted file mode 100644
index 34fbf1b..0000000
--- a/pw_rpc/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-frolv@google.com
-hepler@google.com
diff --git a/pw_rpc/base_client_call.cc b/pw_rpc/base_client_call.cc
new file mode 100644
index 0000000..ec2a3d3
--- /dev/null
+++ b/pw_rpc/base_client_call.cc
@@ -0,0 +1,59 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_rpc/internal/base_client_call.h"
+
+#include "pw_rpc/client.h"
+
+namespace pw::rpc::internal {
+
+void BaseClientCall::Cancel() {
+  if (active()) {
+    channel_->Send(NewPacket(PacketType::CANCEL_SERVER_STREAM));
+  }
+}
+
+std::span<std::byte> BaseClientCall::AcquirePayloadBuffer() {
+  if (!active()) {
+    return {};
+  }
+
+  request_ = channel_->AcquireBuffer();
+  return request_.payload(NewPacket(PacketType::REQUEST));
+}
+
+Status BaseClientCall::ReleasePayloadBuffer(
+    std::span<const std::byte> payload) {
+  if (!active()) {
+    return Status::FailedPrecondition();
+  }
+
+  return channel_->Send(request_, NewPacket(PacketType::REQUEST, payload));
+}
+
+Packet BaseClientCall::NewPacket(PacketType type,
+                                 std::span<const std::byte> payload) const {
+  return Packet(type, channel_->id(), service_id_, method_id_, payload);
+}
+
+void BaseClientCall::Register() { channel_->client()->RegisterCall(*this); }
+
+void BaseClientCall::Unregister() {
+  if (active()) {
+    channel_->client()->RemoveCall(*this);
+    active_ = false;
+  }
+}
+
+}  // namespace pw::rpc::internal
diff --git a/pw_rpc/base_client_call_test.cc b/pw_rpc/base_client_call_test.cc
new file mode 100644
index 0000000..7f15146
--- /dev/null
+++ b/pw_rpc/base_client_call_test.cc
@@ -0,0 +1,72 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_rpc/internal/base_client_call.h"
+
+#include "gtest/gtest.h"
+#include "pw_rpc_private/internal_test_utils.h"
+
+namespace pw::rpc::internal {
+namespace {
+
+TEST(BaseClientCall, RegistersAndRemovesItselfFromClient) {
+  ClientContextForTest context;
+  EXPECT_EQ(context.client().active_calls(), 0u);
+
+  {
+    BaseClientCall call(&context.channel(),
+                        context.service_id(),
+                        context.method_id(),
+                        [](BaseClientCall&, const Packet&) {});
+    EXPECT_EQ(context.client().active_calls(), 1u);
+  }
+
+  EXPECT_EQ(context.client().active_calls(), 0u);
+}
+
+class FakeClientCall : public BaseClientCall {
+ public:
+  constexpr FakeClientCall(rpc::Channel* channel,
+                           uint32_t service_id,
+                           uint32_t method_id,
+                           ResponseHandler handler)
+      : BaseClientCall(channel, service_id, method_id, handler) {}
+
+  Status SendPacket(std::span<const std::byte> payload) {
+    std::span buffer = AcquirePayloadBuffer();
+    std::memcpy(buffer.data(), payload.data(), payload.size());
+    return ReleasePayloadBuffer(buffer.first(payload.size()));
+  }
+};
+
+TEST(BaseClientCall, SendsPacketWithPayload) {
+  ClientContextForTest context;
+  FakeClientCall call(&context.channel(),
+                      context.service_id(),
+                      context.method_id(),
+                      [](BaseClientCall&, const Packet&) {});
+
+  constexpr std::byte payload[]{std::byte{0x08}, std::byte{0x39}};
+  call.SendPacket(payload);
+
+  EXPECT_EQ(context.output().packet_count(), 1u);
+  Packet packet = context.output().sent_packet();
+  EXPECT_EQ(packet.channel_id(), context.channel().id());
+  EXPECT_EQ(packet.service_id(), context.service_id());
+  EXPECT_EQ(packet.method_id(), context.method_id());
+  EXPECT_EQ(std::memcmp(packet.payload().data(), payload, sizeof(payload)), 0);
+}
+
+}  // namespace
+}  // namespace pw::rpc::internal
diff --git a/pw_rpc/base_server_writer.cc b/pw_rpc/base_server_writer.cc
new file mode 100644
index 0000000..9d4423d
--- /dev/null
+++ b/pw_rpc/base_server_writer.cc
@@ -0,0 +1,112 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_rpc/internal/base_server_writer.h"
+
+#include "pw_assert/assert.h"
+#include "pw_rpc/internal/method.h"
+#include "pw_rpc/internal/packet.h"
+#include "pw_rpc/internal/server.h"
+
+namespace pw::rpc::internal {
+
+BaseServerWriter::BaseServerWriter(ServerCall& call)
+    : call_(call), state_(kOpen) {
+  call_.server().RegisterWriter(*this);
+}
+
+BaseServerWriter& BaseServerWriter::operator=(BaseServerWriter&& other) {
+  Finish();
+
+  state_ = other.state_;
+
+  if (other.open()) {
+    other.call_.server().RemoveWriter(other);
+    other.state_ = kClosed;
+
+    other.call_.server().RegisterWriter(*this);
+  }
+
+  call_ = std::move(other.call_);
+  response_ = std::move(other.response_);
+
+  return *this;
+}
+
+uint32_t BaseServerWriter::method_id() const { return call_.method().id(); }
+
+Status BaseServerWriter::Finish(Status status) {
+  if (!open()) {
+    return Status::FailedPrecondition();
+  }
+
+  // If the ServerWriter implementer or user forgets to release an acquired
+  // buffer before finishing, release it here.
+  if (!response_.empty()) {
+    ReleasePayloadBuffer();
+  }
+
+  Close();
+
+  // Send a control packet indicating that the stream (and RPC) has terminated.
+  return call_.channel().Send(Packet(PacketType::SERVER_STREAM_END,
+                                     call_.channel().id(),
+                                     call_.service().id(),
+                                     method().id(),
+                                     {},
+                                     status));
+}
+
+std::span<std::byte> BaseServerWriter::AcquirePayloadBuffer() {
+  PW_DCHECK(open());
+
+  // Only allow having one active buffer at a time.
+  if (response_.empty()) {
+    response_ = call_.channel().AcquireBuffer();
+  }
+
+  return response_.payload(ResponsePacket());
+}
+
+Status BaseServerWriter::ReleasePayloadBuffer(
+    std::span<const std::byte> payload) {
+  PW_DCHECK(open());
+  return call_.channel().Send(response_, ResponsePacket(payload));
+}
+
+Status BaseServerWriter::ReleasePayloadBuffer() {
+  PW_DCHECK(open());
+  call_.channel().Release(response_);
+  return OkStatus();
+}
+
+void BaseServerWriter::Close() {
+  if (!open()) {
+    return;
+  }
+
+  call_.server().RemoveWriter(*this);
+  state_ = kClosed;
+}
+
+Packet BaseServerWriter::ResponsePacket(
+    std::span<const std::byte> payload) const {
+  return Packet(PacketType::RESPONSE,
+                call_.channel().id(),
+                call_.service().id(),
+                method().id(),
+                payload);
+}
+
+}  // namespace pw::rpc::internal
diff --git a/pw_rpc/base_server_writer_test.cc b/pw_rpc/base_server_writer_test.cc
new file mode 100644
index 0000000..f2d9d27
--- /dev/null
+++ b/pw_rpc/base_server_writer_test.cc
@@ -0,0 +1,189 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_rpc/internal/base_server_writer.h"
+
+#include <algorithm>
+#include <array>
+#include <cstdint>
+#include <cstring>
+
+#include "gtest/gtest.h"
+#include "pw_rpc/internal/test_method.h"
+#include "pw_rpc/server_context.h"
+#include "pw_rpc/service.h"
+#include "pw_rpc_private/internal_test_utils.h"
+
+namespace pw::rpc {
+
+class TestService : public Service {
+ public:
+  constexpr TestService(uint32_t id) : Service(id, method) {}
+
+  static constexpr internal::TestMethodUnion method = internal::TestMethod(8);
+};
+
+namespace internal {
+namespace {
+
+using std::byte;
+
+TEST(BaseServerWriter, ConstructWithContext_StartsOpen) {
+  ServerContextForTest<TestService> context(TestService::method.method());
+
+  BaseServerWriter writer(context.get());
+
+  EXPECT_TRUE(writer.open());
+}
+
+TEST(BaseServerWriter, Move_ClosesOriginal) {
+  ServerContextForTest<TestService> context(TestService::method.method());
+
+  BaseServerWriter moved(context.get());
+  BaseServerWriter writer(std::move(moved));
+
+#ifndef __clang_analyzer__
+  EXPECT_FALSE(moved.open());
+#endif  // ignore use-after-move
+  EXPECT_TRUE(writer.open());
+}
+
+class FakeServerWriter : public BaseServerWriter {
+ public:
+  FakeServerWriter(ServerCall& context) : BaseServerWriter(context) {}
+
+  constexpr FakeServerWriter() = default;
+
+  Status Write(std::span<const byte> response) {
+    std::span buffer = AcquirePayloadBuffer();
+    std::memcpy(buffer.data(),
+                response.data(),
+                std::min(buffer.size(), response.size()));
+    return ReleasePayloadBuffer(buffer.first(response.size()));
+  }
+
+  ByteSpan PayloadBuffer() { return AcquirePayloadBuffer(); }
+  const Channel::OutputBuffer& output_buffer() { return buffer(); }
+};
+
+TEST(ServerWriter, DefaultConstruct_Closed) {
+  FakeServerWriter writer;
+
+  EXPECT_FALSE(writer.open());
+}
+
+TEST(ServerWriter, Construct_RegistersWithServer) {
+  ServerContextForTest<TestService> context(TestService::method.method());
+  FakeServerWriter writer(context.get());
+
+  auto& writers = context.server().writers();
+  EXPECT_FALSE(writers.empty());
+  auto it = std::find_if(
+      writers.begin(), writers.end(), [&](auto& w) { return &w == &writer; });
+  ASSERT_NE(it, writers.end());
+}
+
+TEST(ServerWriter, Destruct_RemovesFromServer) {
+  ServerContextForTest<TestService> context(TestService::method.method());
+  { FakeServerWriter writer(context.get()); }
+
+  auto& writers = context.server().writers();
+  EXPECT_TRUE(writers.empty());
+}
+
+TEST(ServerWriter, Finish_RemovesFromServer) {
+  ServerContextForTest<TestService> context(TestService::method.method());
+  FakeServerWriter writer(context.get());
+
+  EXPECT_EQ(OkStatus(), writer.Finish());
+
+  auto& writers = context.server().writers();
+  EXPECT_TRUE(writers.empty());
+}
+
+TEST(ServerWriter, Finish_SendsCancellationPacket) {
+  ServerContextForTest<TestService> context(TestService::method.method());
+  FakeServerWriter writer(context.get());
+
+  EXPECT_EQ(OkStatus(), writer.Finish());
+
+  const Packet& packet = context.output().sent_packet();
+  EXPECT_EQ(packet.type(), PacketType::SERVER_STREAM_END);
+  EXPECT_EQ(packet.channel_id(), context.channel_id());
+  EXPECT_EQ(packet.service_id(), context.service_id());
+  EXPECT_EQ(packet.method_id(), context.get().method().id());
+  EXPECT_TRUE(packet.payload().empty());
+  EXPECT_EQ(packet.status(), OkStatus());
+}
+
+TEST(ServerWriter, Finish_ReturnsStatusFromChannelSend) {
+  ServerContextForTest<TestService> context(TestService::method.method());
+  FakeServerWriter writer(context.get());
+  context.output().set_send_status(Status::Unauthenticated());
+
+  EXPECT_EQ(Status::Unauthenticated(), writer.Finish());
+}
+
+TEST(ServerWriter, Close) {
+  ServerContextForTest<TestService> context(TestService::method.method());
+  FakeServerWriter writer(context.get());
+
+  ASSERT_TRUE(writer.open());
+  EXPECT_EQ(OkStatus(), writer.Finish());
+  EXPECT_FALSE(writer.open());
+  EXPECT_EQ(Status::FailedPrecondition(), writer.Finish());
+}
+
+TEST(ServerWriter, Close_ReleasesBuffer) {
+  ServerContextForTest<TestService> context(TestService::method.method());
+  FakeServerWriter writer(context.get());
+
+  ASSERT_TRUE(writer.open());
+  auto buffer = writer.PayloadBuffer();
+  buffer[0] = std::byte{0};
+  EXPECT_FALSE(writer.output_buffer().empty());
+  EXPECT_EQ(OkStatus(), writer.Finish());
+  EXPECT_FALSE(writer.open());
+  EXPECT_TRUE(writer.output_buffer().empty());
+}
+
+TEST(ServerWriter, Open_SendsPacketWithPayload) {
+  ServerContextForTest<TestService> context(TestService::method.method());
+  FakeServerWriter writer(context.get());
+
+  constexpr byte data[] = {byte{0xf0}, byte{0x0d}};
+  ASSERT_EQ(OkStatus(), writer.Write(data));
+
+  byte encoded[64];
+  auto result = context.packet(data).Encode(encoded);
+  ASSERT_EQ(OkStatus(), result.status());
+
+  EXPECT_EQ(result.value().size(), context.output().sent_data().size());
+  EXPECT_EQ(
+      0,
+      std::memcmp(
+          encoded, context.output().sent_data().data(), result.value().size()));
+}
+
+TEST(ServerWriter, Closed_IgnoresFinish) {
+  ServerContextForTest<TestService> context(TestService::method.method());
+  FakeServerWriter writer(context.get());
+
+  EXPECT_EQ(OkStatus(), writer.Finish());
+  EXPECT_EQ(Status::FailedPrecondition(), writer.Finish());
+}
+
+}  // namespace
+}  // namespace internal
+}  // namespace pw::rpc
diff --git a/pw_rpc/benchmark.cc b/pw_rpc/benchmark.cc
deleted file mode 100644
index b906e0c..0000000
--- a/pw_rpc/benchmark.cc
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/benchmark.h"
-
-#include <algorithm>
-
-#include "pw_rpc/internal/config.h"
-
-namespace pw::rpc {
-namespace {
-
-StatusWithSize CopyBuffer(ConstByteSpan input, ByteSpan output) {
-  if (input.size() > output.size()) {
-    return pw::StatusWithSize::ResourceExhausted();
-  }
-  std::copy(input.begin(), input.end(), output.begin());
-  return pw::StatusWithSize(input.size());
-}
-
-}  // namespace
-
-void BenchmarkService::UnaryEcho(ConstByteSpan request,
-                                 RawUnaryResponder& responder) {
-  std::byte response[32];
-  StatusWithSize result = CopyBuffer(request, response);
-  responder.Finish(std::span(response).first(result.size()), result.status())
-      .IgnoreError();
-}
-
-void BenchmarkService::BidirectionalEcho(
-    RawServerReaderWriter& new_reader_writer) {
-  reader_writer_ = std::move(new_reader_writer);
-
-  reader_writer_.set_on_next([this](ConstByteSpan request) {
-    Status status = reader_writer_.Write(request);
-    if (!status.ok()) {
-      reader_writer_.Finish(status)
-          .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    }
-  });
-}
-
-}  // namespace pw::rpc
diff --git a/pw_rpc/benchmark.options b/pw_rpc/benchmark.options
deleted file mode 100644
index c7c6b90..0000000
--- a/pw_rpc/benchmark.options
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// The benchmark service is primarily intended for use with the raw API. This
-// file is provided to support testing the Nanopb client API.
-pw.rpc.Payload.payload max_size:64
diff --git a/pw_rpc/benchmark.proto b/pw_rpc/benchmark.proto
deleted file mode 100644
index 1c76986..0000000
--- a/pw_rpc/benchmark.proto
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-syntax = "proto3";
-
-package pw.rpc;
-
-service Benchmark {
-  // The server responds with the payload the client sent.
-  rpc UnaryEcho(Payload) returns (Payload);
-
-  // The server responds to each request payload the client sends. The client
-  // stops the RPC by cancelling it.
-  rpc BidirectionalEcho(stream Payload) returns (stream Payload);
-}
-
-message Payload {
-  bytes payload = 1;
-}
diff --git a/pw_rpc/benchmark.rst b/pw_rpc/benchmark.rst
deleted file mode 100644
index 6e97ac1..0000000
--- a/pw_rpc/benchmark.rst
+++ /dev/null
@@ -1,51 +0,0 @@
-.. _module-pw_rpc-benchmark:
-
-===================
-pw_rpc Benchmarking
-===================
-pw_rpc provides tools for stress testing and benchmarking a Pigweed RPC
-deployment and the transport it is running over. Two components are included:
-
-* The pw.rpc.Benchmark service and its implementation.
-* A Python module that runs tests using the Benchmark service.
-
-------------------------
-pw.rpc.Benchmark service
-------------------------
-The Benchmark service provides a low-level RPC service for sending data between
-the client and server. The service is defined in ``pw_rpc/benchmark.proto``.
-
-A raw RPC implementation of the benchmark service is provided. This
-implementation is suitable for use in any system with pw_rpc. To access this
-service, add a dependency on ``"$dir_pw_rpc:benchmark"`` in GN or
-``pw_rpc.benchmark`` in CMake. Then, include the service
-(``#include "pw_rpc/benchmark.h"``), instantiate it, and register it with your
-RPC server, like any other RPC service.
-
-The Benchmark service was designed with the Python-based benchmarking tools in
-mind, but it may be used directly to test basic RPC functionality. The service
-is well suited for use in automated integration tests or in an interactive
-console.
-
-Benchmark service
-==================
-.. literalinclude:: benchmark.proto
-  :language: protobuf
-  :lines: 14-
-
-Example
-=======
-.. code-block:: c++
-
-  #include "pw_rpc/benchmark.h"
-  #include "pw_rpc/server.h"
-
-  constexpr pw::rpc::Channel kChannels[] = { /* ... */ };
-  static pw::rpc::Server server(kChannels);
-
-  static pw::rpc::BenchmarkService benchmark_service;
-
-  void RegisterServices() {
-    server.RegisterService(benchmark_service);
-  }
-
diff --git a/pw_rpc/bidirectional_streaming_rpc.svg b/pw_rpc/bidirectional_streaming_rpc.svg
deleted file mode 100644
index fb18ea9..0000000
--- a/pw_rpc/bidirectional_streaming_rpc.svg
+++ /dev/null
@@ -1,86 +0,0 @@
-<!-- Originally created with blockdiag. See the Git history for the source. -->
-<svg height="806.3000000000001" viewBox="0 0 624 733" width="686.4000000000001" xmlns="http://www.w3.org/2000/svg" xmlns:inkspace="http://www.inkscape.org/namespaces/inkscape" xmlns:xlink="http://www.w3.org/1999/xlink" style="background-color:white">
-<defs id="defs_block">
-<filter height="1.504" id="filter_blur" inkspace:collect="always" width="1.1575" x="-0.07875" y="-0.252">
-<feGaussianBlur id="feGaussianBlur3780" inkspace:collect="always" stdDeviation="4.2"></feGaussianBlur>
-</filter>
-</defs>
-<title>Bidirectional Streaming RPC</title>
-<desc></desc>
-<rect fill="rgb(0,0,0)" height="558" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="8" x="231" y="153"></rect>
-<rect fill="rgb(0,0,0)" height="488" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="8" x="423" y="153"></rect>
-<polygon fill="rgb(0,0,0)" points="87,126 211,126 219,134 219,180 87,180 87,126" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></polygon>
-<polygon fill="rgb(0,0,0)" points="51,220 211,220 219,228 219,287 51,287 51,220" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></polygon>
-<polygon fill="rgb(0,0,0)" points="443,407 603,407 611,415 611,474 443,474 443,407" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></polygon>
-<polygon fill="rgb(0,0,0)" points="27,514 211,514 219,522 219,568 27,568 27,514" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></polygon>
-<polygon fill="rgb(0,0,0)" points="443,608 573,608 581,616 581,675 443,675 443,608" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></polygon>
-<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="171" y="46"></rect>
-<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="363" y="46"></rect>
-<path d="M 232 80 L 232 321" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="8 4"></path>
-<path d="M 232 329 L 232 353" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="2 8"></path>
-<path d="M 232 361 L 232 721" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="8 4"></path>
-<rect fill="moccasin" height="558" stroke="rgb(0,0,0)" width="8" x="228" y="147"></rect>
-<path d="M 424 80 L 424 321" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="8 4"></path>
-<path d="M 424 329 L 424 353" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="2 8"></path>
-<path d="M 424 361 L 424 721" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="8 4"></path>
-<rect fill="moccasin" height="488" stroke="rgb(0,0,0)" width="8" x="420" y="147"></rect>
-<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="168" y="40"></rect>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="36" x="232.0" y="66">client</text>
-<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="360" y="40"></rect>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="36" x="424.0" y="66">server</text>
-<path d="M 240 147 L 416 147" fill="none" stroke="rgb(0,0,0)"></path>
-<polygon fill="rgb(0,0,0)" points="408,143 416,147 408,151" stroke="rgb(0,0,0)"></polygon>
-<polygon fill="rgb(240,248,255)" points="84,120 208,120 216,128 216,174 84,174 84,120" stroke="rgb(0,0,0)"></polygon>
-<path d="M 208 120 L 208 128" fill="none" stroke="rgb(0,0,0)"></path>
-<path d="M 208 128 L 216 128" fill="none" stroke="rgb(0,0,0)"></path>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="108" x="146.0" y="133">PacketType.REQUEST</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="60" x="122.0" y="146">channel ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="60" x="122.0" y="159">service ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="54" x="119.0" y="172">method ID</text>
-<path d="M 240 247 L 416 247" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="4"></path>
-<polygon fill="rgb(0,0,0)" points="408,243 416,247 408,251" stroke="rgb(0,0,0)"></polygon>
-<polygon fill="rgb(240,248,255)" points="48,214 208,214 216,222 216,281 48,281 48,214" stroke="rgb(0,0,0)"></polygon>
-<path d="M 208 214 L 208 222" fill="none" stroke="rgb(0,0,0)"></path>
-<path d="M 208 222 L 216 222" fill="none" stroke="rgb(0,0,0)"></path>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="144" x="128.0" y="227">PacketType.CLIENT_STREAM</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="60" x="86.0" y="240">channel ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="60" x="86.0" y="253">service ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="54" x="83.0" y="266">method ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="42" x="77.0" y="279">payload</text>
-<path d="M 240 434 L 416 434" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="4"></path>
-<polygon fill="rgb(0,0,0)" points="248,430 240,434 248,438" stroke="rgb(0,0,0)"></polygon>
-<polygon fill="rgb(240,248,255)" points="440,401 600,401 608,409 608,468 440,468 440,401" stroke="rgb(0,0,0)"></polygon>
-<path d="M 600 401 L 600 409" fill="none" stroke="rgb(0,0,0)"></path>
-<path d="M 600 409 L 608 409" fill="none" stroke="rgb(0,0,0)"></path>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="144" x="520.0" y="414">PacketType.SERVER_STREAM</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="60" x="478.0" y="427">channel ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="60" x="478.0" y="440">service ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="54" x="475.0" y="453">method ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="42" x="469.0" y="466">payload</text>
-<path d="M 240 535 L 416 535" fill="none" stroke="rgb(0,0,0)"></path>
-<polygon fill="rgb(0,0,0)" points="408,531 416,535 408,539" stroke="rgb(0,0,0)"></polygon>
-<polygon fill="rgb(240,248,255)" points="24,508 208,508 216,516 216,562 24,562 24,508" stroke="rgb(0,0,0)"></polygon>
-<path d="M 208 508 L 208 516" fill="none" stroke="rgb(0,0,0)"></path>
-<path d="M 208 516 L 216 516" fill="none" stroke="rgb(0,0,0)"></path>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="168" x="116.0" y="521">PacketType.CLIENT_STREAM_END</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="60" x="62.0" y="534">channel ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="60" x="62.0" y="547">service ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="54" x="59.0" y="560">method ID</text>
-<path d="M 240 635 L 416 635" fill="none" stroke="rgb(0,0,0)"></path>
-<polygon fill="rgb(0,0,0)" points="248,631 240,635 248,639" stroke="rgb(0,0,0)"></polygon>
-<polygon fill="rgb(240,248,255)" points="440,602 570,602 578,610 578,669 440,669 440,602" stroke="rgb(0,0,0)"></polygon>
-<path d="M 570 602 L 570 610" fill="none" stroke="rgb(0,0,0)"></path>
-<path d="M 570 610 L 578 610" fill="none" stroke="rgb(0,0,0)"></path>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="114" x="505.0" y="615">PacketType.RESPONSE</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="60" x="478.0" y="628">channel ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="60" x="478.0" y="641">service ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="54" x="475.0" y="654">method ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="36" x="466.0" y="667">status</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="30" x="259.0" y="145">start</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="138" x="313.0" y="245">messages (zero or more)</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="138" x="347.0" y="432">messages (zero or more)</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="24" x="256.0" y="533">done</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="24" x="404.0" y="633">done</text>
-<rect fill="white" height="19" stroke="white" width="158" x="249" y="331"></rect>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="138" x="328.0" y="346">(messages in any order)</text>
-</svg>
diff --git a/pw_rpc/bidirectional_streaming_rpc_cancelled.svg b/pw_rpc/bidirectional_streaming_rpc_cancelled.svg
deleted file mode 100644
index a6e6b7f..0000000
--- a/pw_rpc/bidirectional_streaming_rpc_cancelled.svg
+++ /dev/null
@@ -1,94 +0,0 @@
-<!-- Originally created with blockdiag. -->
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:inkspace="http://www.inkscape.org/namespaces/inkscape" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 604 559" width="604px" height="559px">
-  <defs id="defs_block">
-    <filter height="1.504" id="filter_blur" inkspace:collect="always" width="1.1575" x="-0.07875" y="-0.252">
-      <feGaussianBlur id="feGaussianBlur3780" inkspace:collect="always" stdDeviation="4.2"/>
-    </filter>
-  </defs>
-  <title>Cancelled Bidirectional Streaming RPC</title>
-  <desc>seqdiag {
-  default_note_color = aliceblue;
-
-  client -&gt; server [
-      label = "start",
-      leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID"
-  ];
-
-  client --&gt; server [
-      noactivate,
-      label = "messages (zero or more)",
-      leftnote = "PacketType.CLIENT_STREAM\nchannel ID\nservice ID\nmethod ID\npayload"
-  ];
-
-  client &lt;-- server [
-      noactivate,
-      label = "messages (zero or more)",
-      rightnote = "PacketType.SERVER_STREAM\nchannel ID\nservice ID\nmethod ID\npayload"
-  ];
-
-  client -&gt; server [
-      noactivate,
-      label = "cancel",
-      leftnote = "PacketType.CLIENT_ERROR\nchannel ID\nservice ID\nmethod ID\nstatus=CANCELLED"
-  ];
-}</desc>
-  <rect fill="rgb(0,0,0)" height="384" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="8" x="209" y="153"/>
-  <rect fill="rgb(0,0,0)" height="384" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="8" x="401" y="153"/>
-  <polygon fill="rgb(0,0,0)" points="64,126 189,126 197,134 197,180 64,180 64,126" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"/>
-  <polygon fill="rgb(0,0,0)" points="27,220 189,220 197,228 197,287 27,287 27,220" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"/>
-  <polygon fill="rgb(0,0,0)" points="421,327 583,327 591,335 591,394 421,394 421,327" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"/>
-  <polygon fill="rgb(0,0,0)" points="33,434 189,434 197,442 197,501 33,501 33,434" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"/>
-  <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="149" y="46"/>
-  <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="341" y="46"/>
-  <path d="M 210 80 L 210 547" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="8 4"/>
-  <rect fill="moccasin" height="384" stroke="rgb(0,0,0)" width="8" x="206" y="147"/>
-  <path d="M 402 80 L 402 547" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="8 4"/>
-  <rect fill="moccasin" height="384" stroke="rgb(0,0,0)" width="8" x="398" y="147"/>
-  <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="146" y="40"/>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="37" x="210.5" y="66">client</text>
-  <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="338" y="40"/>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="37" x="402.5" y="66">server</text>
-  <path d="M 218 147 L 394 147" fill="none" stroke="rgb(0,0,0)"/>
-  <polygon fill="rgb(0,0,0)" points="386,143 394,147 386,151" stroke="rgb(0,0,0)"/>
-  <polygon fill="rgb(240,248,255)" points="61,120 186,120 194,128 194,174 61,174 61,120" stroke="rgb(0,0,0)"/>
-  <path d="M 186 120 L 186 128" fill="none" stroke="rgb(0,0,0)"/>
-  <path d="M 186 128 L 194 128" fill="none" stroke="rgb(0,0,0)"/>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="109" x="123.5" y="133">PacketType.REQUEST</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="61" x="99.5" y="146">channel ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="61" x="99.5" y="159">service ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="55" x="96.5" y="172">method ID</text>
-  <path d="M 218 247 L 394 247" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="4"/>
-  <polygon fill="rgb(0,0,0)" points="386,243 394,247 386,251" stroke="rgb(0,0,0)"/>
-  <polygon fill="rgb(240,248,255)" points="24,214 186,214 194,222 194,281 24,281 24,214" stroke="rgb(0,0,0)"/>
-  <path d="M 186 214 L 186 222" fill="none" stroke="rgb(0,0,0)"/>
-  <path d="M 186 222 L 194 222" fill="none" stroke="rgb(0,0,0)"/>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="146" x="105.0" y="227">PacketType.CLIENT_STREAM</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="61" x="62.5" y="240">channel ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="61" x="62.5" y="253">service ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="55" x="59.5" y="266">method ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="43" x="53.5" y="279">payload</text>
-  <path d="M 218 354 L 394 354" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="4"/>
-  <polygon fill="rgb(0,0,0)" points="226,350 218,354 226,358" stroke="rgb(0,0,0)"/>
-  <polygon fill="rgb(240,248,255)" points="418,321 580,321 588,329 588,388 418,388 418,321" stroke="rgb(0,0,0)"/>
-  <path d="M 580 321 L 580 329" fill="none" stroke="rgb(0,0,0)"/>
-  <path d="M 580 329 L 588 329" fill="none" stroke="rgb(0,0,0)"/>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="146" x="499.0" y="334">PacketType.SERVER_STREAM</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="61" x="456.5" y="347">channel ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="61" x="456.5" y="360">service ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="55" x="453.5" y="373">method ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="43" x="447.5" y="386">payload</text>
-  <path d="M 218 461 L 394 461" fill="none" stroke="rgb(0,0,0)"/>
-  <polygon fill="rgb(0,0,0)" points="386,457 394,461 386,465" stroke="rgb(0,0,0)"/>
-  <polygon fill="rgb(240,248,255)" points="30,428 186,428 194,436 194,495 30,495 30,428" stroke="rgb(0,0,0)"/>
-  <path d="M 186 428 L 186 436" fill="none" stroke="rgb(0,0,0)"/>
-  <path d="M 186 436 L 194 436" fill="none" stroke="rgb(0,0,0)"/>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="140" x="108.0" y="441">PacketType.CLIENT_ERROR</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="61" x="68.5" y="454">channel ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="61" x="68.5" y="467">service ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="55" x="65.5" y="480">method ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="97" x="86.5" y="493">status=CANCELLED</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="31" x="237.5" y="145">start</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="140" x="292.0" y="245">messages (zero or more)</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="140" x="324.0" y="352">messages (zero or more)</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="37" x="240.5" y="459">cancel</text>
-</svg>
diff --git a/pw_rpc/call.cc b/pw_rpc/call.cc
deleted file mode 100644
index e4afcb5..0000000
--- a/pw_rpc/call.cc
+++ /dev/null
@@ -1,122 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/internal/call.h"
-
-#include "pw_assert/check.h"
-#include "pw_rpc/client.h"
-#include "pw_rpc/internal/endpoint.h"
-#include "pw_rpc/internal/method.h"
-#include "pw_rpc/server.h"
-
-namespace pw::rpc::internal {
-
-// Creates an active client-side call, assigning it a new ID.
-Call::Call(Endpoint& client,
-           uint32_t channel_id,
-           uint32_t service_id,
-           uint32_t method_id,
-           MethodType type)
-    : Call(client,
-           client.NewCallId(),
-           channel_id,
-           service_id,
-           method_id,
-           type,
-           kClientCall) {}
-
-Call::Call(Endpoint& endpoint_ref,
-           uint32_t call_id,
-           uint32_t channel_id,
-           uint32_t service_id,
-           uint32_t method_id,
-           MethodType type,
-           CallType call_type)
-    : endpoint_(&endpoint_ref),
-      channel_id_(channel_id),
-      id_(call_id),
-      service_id_(service_id),
-      method_id_(method_id),
-      rpc_state_(kActive),
-      type_(type),
-      call_type_(call_type),
-      client_stream_state_(HasClientStream(type) ? kClientStreamActive
-                                                 : kClientStreamInactive) {
-  endpoint().RegisterCall(*this);
-}
-
-void Call::MoveFrom(Call& other) {
-  PW_DCHECK(!active_locked());
-
-  if (!other.active_locked()) {
-    return;  // Nothing else to do; this call is already closed.
-  }
-
-  // Copy all members from the other call.
-  endpoint_ = other.endpoint_;
-  channel_id_ = other.channel_id_;
-  id_ = other.id_;
-  service_id_ = other.service_id_;
-  method_id_ = other.method_id_;
-
-  rpc_state_ = other.rpc_state_;
-  type_ = other.type_;
-  call_type_ = other.call_type_;
-  client_stream_state_ = other.client_stream_state_;
-
-  on_error_ = std::move(other.on_error_);
-  on_next_ = std::move(other.on_next_);
-
-  // Mark the other call inactive, unregister it, and register this one.
-  other.rpc_state_ = kInactive;
-  other.client_stream_state_ = kClientStreamInactive;
-
-  endpoint().UnregisterCall(other);
-  endpoint().RegisterUniqueCall(*this);
-}
-
-Status Call::SendPacket(PacketType type, ConstByteSpan payload, Status status) {
-  if (!active_locked()) {
-    return Status::FailedPrecondition();
-  }
-
-  Channel* channel = endpoint_->GetInternalChannel(channel_id_);
-  if (channel == nullptr) {
-    return Status::Unavailable();
-  }
-  return channel->Send(MakePacket(type, payload, status));
-}
-
-Status Call::CloseAndSendFinalPacketLocked(PacketType type,
-                                           ConstByteSpan response,
-                                           Status status) {
-  const Status send_status = SendPacket(type, response, status);
-  UnregisterAndMarkClosed();
-  return send_status;
-}
-
-Status Call::WriteLocked(ConstByteSpan payload) {
-  return SendPacket(call_type_ == kServerCall ? PacketType::SERVER_STREAM
-                                              : PacketType::CLIENT_STREAM,
-                    payload);
-}
-
-void Call::UnregisterAndMarkClosed() {
-  if (active_locked()) {
-    endpoint().UnregisterCall(*this);
-    MarkClosed();
-  }
-}
-
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/call_test.cc b/pw_rpc/call_test.cc
deleted file mode 100644
index 208342d..0000000
--- a/pw_rpc/call_test.cc
+++ /dev/null
@@ -1,257 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/internal/call.h"
-
-#include <algorithm>
-#include <array>
-#include <cstdint>
-#include <cstring>
-
-#include "gtest/gtest.h"
-#include "pw_rpc/internal/test_method.h"
-#include "pw_rpc/internal/test_utils.h"
-#include "pw_rpc/service.h"
-#include "pw_rpc_private/fake_server_reader_writer.h"
-
-namespace pw::rpc {
-
-class TestService : public Service {
- public:
-  constexpr TestService(uint32_t id) : Service(id, method) {}
-
-  static constexpr internal::TestMethodUnion method = internal::TestMethod(8);
-};
-
-namespace internal {
-namespace {
-
-constexpr Packet kPacket(PacketType::REQUEST, 99, 16, 8);
-
-using pw::rpc::internal::test::FakeServerWriter;
-using std::byte;
-
-TEST(ServerWriter, ConstructWithContext_StartsOpen) {
-  ServerContextForTest<TestService> context(TestService::method.method());
-
-  FakeServerWriter writer(context.get());
-
-  EXPECT_TRUE(writer.active());
-}
-
-TEST(ServerWriter, Move_ClosesOriginal) {
-  ServerContextForTest<TestService> context(TestService::method.method());
-
-  FakeServerWriter moved(context.get());
-  FakeServerWriter writer(std::move(moved));
-
-#ifndef __clang_analyzer__
-  EXPECT_FALSE(moved.active());
-#endif  // ignore use-after-move
-  EXPECT_TRUE(writer.active());
-}
-
-TEST(ServerWriter, DefaultConstruct_Closed) {
-  FakeServerWriter writer;
-
-  EXPECT_FALSE(writer.active());
-}
-
-TEST(ServerWriter, Construct_RegistersWithServer) PW_NO_LOCK_SAFETY_ANALYSIS {
-  ServerContextForTest<TestService> context(TestService::method.method());
-  FakeServerWriter writer(context.get());
-
-  Call* call = context.server().FindCall(kPacket);
-  ASSERT_NE(call, nullptr);
-  EXPECT_EQ(static_cast<void*>(call), static_cast<void*>(&writer));
-}
-
-TEST(ServerWriter, Destruct_RemovesFromServer) PW_NO_LOCK_SAFETY_ANALYSIS {
-  ServerContextForTest<TestService> context(TestService::method.method());
-  { FakeServerWriter writer(context.get()); }
-
-  EXPECT_EQ(context.server().FindCall(kPacket), nullptr);
-}
-
-TEST(ServerWriter, Finish_RemovesFromServer) PW_NO_LOCK_SAFETY_ANALYSIS {
-  ServerContextForTest<TestService> context(TestService::method.method());
-  FakeServerWriter writer(context.get());
-
-  EXPECT_EQ(OkStatus(), writer.Finish());
-
-  EXPECT_EQ(context.server().FindCall(kPacket), nullptr);
-}
-
-TEST(ServerWriter, Finish_SendsResponse) {
-  ServerContextForTest<TestService> context(TestService::method.method());
-  FakeServerWriter writer(context.get());
-
-  EXPECT_EQ(OkStatus(), writer.Finish());
-
-  ASSERT_EQ(context.output().total_packets(), 1u);
-  const Packet& packet = context.output().last_packet();
-  EXPECT_EQ(packet.type(), PacketType::RESPONSE);
-  EXPECT_EQ(packet.channel_id(), context.channel_id());
-  EXPECT_EQ(packet.service_id(), context.service_id());
-  EXPECT_EQ(packet.method_id(), context.get().method().id());
-  EXPECT_TRUE(packet.payload().empty());
-  EXPECT_EQ(packet.status(), OkStatus());
-}
-
-TEST(ServerWriter, Finish_ReturnsStatusFromChannelSend) {
-  ServerContextForTest<TestService> context(TestService::method.method());
-  FakeServerWriter writer(context.get());
-  context.output().set_send_status(Status::Unauthenticated());
-
-  // All non-OK statuses are remapped to UNKNOWN.
-  EXPECT_EQ(Status::Unknown(), writer.Finish());
-}
-
-TEST(ServerWriter, Finish) {
-  ServerContextForTest<TestService> context(TestService::method.method());
-  FakeServerWriter writer(context.get());
-
-  ASSERT_TRUE(writer.active());
-  EXPECT_EQ(OkStatus(), writer.Finish());
-  EXPECT_FALSE(writer.active());
-  EXPECT_EQ(Status::FailedPrecondition(), writer.Finish());
-}
-
-TEST(ServerWriter, Open_SendsPacketWithPayload) {
-  ServerContextForTest<TestService> context(TestService::method.method());
-  FakeServerWriter writer(context.get());
-
-  constexpr byte data[] = {byte{0xf0}, byte{0x0d}};
-  ASSERT_EQ(OkStatus(), writer.Write(data));
-
-  byte encoded[64];
-  auto result = context.server_stream(data).Encode(encoded);
-  ASSERT_EQ(OkStatus(), result.status());
-
-  ConstByteSpan payload = context.output().last_packet().payload();
-  EXPECT_EQ(sizeof(data), payload.size());
-  EXPECT_EQ(0, std::memcmp(data, payload.data(), sizeof(data)));
-}
-
-TEST(ServerWriter, Closed_IgnoresFinish) {
-  ServerContextForTest<TestService> context(TestService::method.method());
-  FakeServerWriter writer(context.get());
-
-  EXPECT_EQ(OkStatus(), writer.Finish());
-  EXPECT_EQ(Status::FailedPrecondition(), writer.Finish());
-}
-
-TEST(ServerWriter, DefaultConstructor_NoClientStream) {
-  FakeServerWriter writer;
-  LockGuard lock(rpc_lock());
-  EXPECT_FALSE(writer.as_server_call().has_client_stream());
-  EXPECT_FALSE(writer.as_server_call().client_stream_open());
-}
-
-TEST(ServerWriter, Open_NoClientStream) {
-  ServerContextForTest<TestService> context(TestService::method.method());
-  FakeServerWriter writer(context.get());
-
-  LockGuard lock(rpc_lock());
-  EXPECT_FALSE(writer.as_server_call().has_client_stream());
-  EXPECT_FALSE(writer.as_server_call().client_stream_open());
-}
-
-TEST(ServerReader, DefaultConstructor_ClientStreamClosed) {
-  test::FakeServerReader reader;
-  EXPECT_FALSE(reader.as_server_call().active());
-  LockGuard lock(rpc_lock());
-  EXPECT_FALSE(reader.as_server_call().client_stream_open());
-}
-
-TEST(ServerReader, Open_ClientStreamStartsOpen) {
-  ServerContextForTest<TestService> context(TestService::method.method());
-  test::FakeServerReader reader(context.get());
-
-  LockGuard lock(rpc_lock());
-  EXPECT_TRUE(reader.as_server_call().has_client_stream());
-  EXPECT_TRUE(reader.as_server_call().client_stream_open());
-}
-
-TEST(ServerReader, Close_ClosesClientStream) {
-  ServerContextForTest<TestService> context(TestService::method.method());
-  test::FakeServerReader reader(context.get());
-
-  EXPECT_TRUE(reader.as_server_call().active());
-  rpc_lock().lock();
-  EXPECT_TRUE(reader.as_server_call().client_stream_open());
-  rpc_lock().unlock();
-  EXPECT_EQ(OkStatus(),
-            reader.as_server_call().CloseAndSendResponse(OkStatus()));
-
-  EXPECT_FALSE(reader.as_server_call().active());
-  LockGuard lock(rpc_lock());
-  EXPECT_FALSE(reader.as_server_call().client_stream_open());
-}
-
-TEST(ServerReader, EndClientStream_OnlyClosesClientStream) {
-  ServerContextForTest<TestService> context(TestService::method.method());
-  test::FakeServerReader reader(context.get());
-
-  EXPECT_TRUE(reader.active());
-  rpc_lock().lock();
-  EXPECT_TRUE(reader.as_server_call().client_stream_open());
-  reader.as_server_call().HandleClientStreamEnd();
-
-  EXPECT_TRUE(reader.active());
-  LockGuard lock(rpc_lock());
-  EXPECT_FALSE(reader.as_server_call().client_stream_open());
-}
-
-TEST(ServerReaderWriter, Move_MaintainsClientStream) {
-  ServerContextForTest<TestService> context(TestService::method.method());
-  test::FakeServerReaderWriter reader_writer(context.get());
-  test::FakeServerReaderWriter destination;
-
-  rpc_lock().lock();
-  EXPECT_FALSE(destination.as_server_call().client_stream_open());
-  rpc_lock().unlock();
-
-  destination = std::move(reader_writer);
-  LockGuard lock(rpc_lock());
-  EXPECT_TRUE(destination.as_server_call().has_client_stream());
-  EXPECT_TRUE(destination.as_server_call().client_stream_open());
-}
-
-TEST(ServerReaderWriter, Move_MovesCallbacks) {
-  ServerContextForTest<TestService> context(TestService::method.method());
-  test::FakeServerReaderWriter reader_writer(context.get());
-
-  int calls = 0;
-  reader_writer.set_on_error([&calls](Status) { calls += 1; });
-  reader_writer.set_on_next([&calls](ConstByteSpan) { calls += 1; });
-
-#if PW_RPC_CLIENT_STREAM_END_CALLBACK
-  reader_writer.set_on_client_stream_end([&calls]() { calls += 1; });
-#endif  // PW_RPC_CLIENT_STREAM_END_CALLBACK
-
-  test::FakeServerReaderWriter destination(std::move(reader_writer));
-  rpc_lock().lock();
-  destination.as_server_call().HandlePayload({});
-  rpc_lock().lock();
-  destination.as_server_call().HandleClientStreamEnd();
-  rpc_lock().lock();
-  destination.as_server_call().HandleError(Status::Unknown());
-
-  EXPECT_EQ(calls, 2 + PW_RPC_CLIENT_STREAM_END_CALLBACK);
-}
-
-}  // namespace
-}  // namespace internal
-}  // namespace pw::rpc
diff --git a/pw_rpc/channel.cc b/pw_rpc/channel.cc
index ea74168..70f4262 100644
--- a/pw_rpc/channel.cc
+++ b/pw_rpc/channel.cc
@@ -12,76 +12,34 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-// clang-format off
-#include "pw_rpc/internal/log_config.h"  // PW_LOG_* macros must be first.
-
 #include "pw_rpc/internal/channel.h"
-// clang-format on
 
-#include "pw_bytes/span.h"
 #include "pw_log/log.h"
-#include "pw_protobuf/decoder.h"
-#include "pw_rpc/internal/config.h"
+#include "pw_rpc/internal/packet.h"
 
-namespace pw::rpc {
-namespace {
+namespace pw::rpc::internal {
 
-// TODO(pwbug/615): Dynamically allocate this buffer if
-//     PW_RPC_DYNAMIC_ALLOCATION is enabled.
-std::array<std::byte, cfg::kEncodingBufferSizeBytes> encoding_buffer
-    PW_GUARDED_BY(internal::rpc_lock());
+using std::byte;
 
-}  // namespace
-
-Result<uint32_t> ExtractChannelId(ConstByteSpan packet) {
-  protobuf::Decoder decoder(packet);
-
-  while (decoder.Next().ok()) {
-    switch (static_cast<internal::RpcPacket::Fields>(decoder.FieldNumber())) {
-      case internal::RpcPacket::Fields::CHANNEL_ID: {
-        uint32_t channel_id;
-        PW_TRY(decoder.ReadUint32(&channel_id));
-        return channel_id;
-      }
-
-      default:
-        continue;
-    }
-  }
-
-  return Status::DataLoss();
+std::span<byte> Channel::OutputBuffer::payload(const Packet& packet) const {
+  const size_t reserved_size = packet.MinEncodedSizeBytes();
+  return reserved_size <= buffer_.size() ? buffer_.subspan(reserved_size)
+                                         : std::span<byte>();
 }
 
-namespace internal {
-
-ByteSpan GetPayloadBuffer() PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-  return ByteSpan(encoding_buffer)
-      .subspan(Packet::kMinEncodedSizeWithoutPayload);
-}
-
-Status Channel::Send(const Packet& packet) {
-  Result encoded = packet.Encode(encoding_buffer);
+Status Channel::Send(OutputBuffer& buffer, const internal::Packet& packet) {
+  Result encoded = packet.Encode(buffer.buffer_);
 
   if (!encoded.ok()) {
-    PW_LOG_ERROR(
-        "Failed to encode RPC packet type %u to channel %u buffer, status %u",
-        static_cast<unsigned>(packet.type()),
-        static_cast<unsigned>(id()),
-        encoded.status().code());
+    PW_LOG_ERROR("Failed to encode RPC response packet to channel %u buffer",
+                 static_cast<unsigned>(id()));
+    output().DiscardBuffer(buffer.buffer_);
+    buffer.buffer_ = {};
     return Status::Internal();
   }
 
-  Status sent = output().Send(encoded.value());
-
-  if (!sent.ok()) {
-    PW_LOG_DEBUG("Channel %u failed to send packet with status %u",
-                 static_cast<unsigned>(id()),
-                 sent.code());
-
-    return Status::Unknown();
-  }
-  return OkStatus();
+  buffer.buffer_ = {};
+  return output().SendAndReleaseBuffer(encoded.value());
 }
 
-}  // namespace internal
-}  // namespace pw::rpc
+}  // namespace pw::rpc::internal
diff --git a/pw_rpc/channel_list.cc b/pw_rpc/channel_list.cc
deleted file mode 100644
index 7cbf59f..0000000
--- a/pw_rpc/channel_list.cc
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/internal/channel_list.h"
-
-namespace pw::rpc::internal {
-
-const Channel* ChannelList::Get(uint32_t channel_id) const {
-  for (const Channel& channel : channels_) {
-    if (channel.id() == channel_id) {
-      return &channel;
-    }
-  }
-  return nullptr;
-}
-
-Status ChannelList::Add(uint32_t channel_id, ChannelOutput& output) {
-  if (Get(channel_id) != nullptr) {
-    return Status::AlreadyExists();
-  }
-
-#if PW_RPC_DYNAMIC_ALLOCATION
-  channels_.emplace_back(channel_id, &output);
-#else
-  Channel* new_channel = Get(Channel::kUnassignedChannelId);
-  if (new_channel == nullptr) {
-    return Status::ResourceExhausted();
-  }
-
-  new_channel->Configure(channel_id, output);
-#endif  // PW_RPC_DYNAMIC_ALLOCATION
-
-  return OkStatus();
-}
-
-Status ChannelList::Remove(uint32_t channel_id) {
-  Channel* channel = Get(channel_id);
-
-  if (channel == nullptr) {
-    return Status::NotFound();
-  }
-  channel->Close();
-
-#if PW_RPC_DYNAMIC_ALLOCATION
-  // Order isn't important, so move the channel to the back then pop it.
-  std::swap(*channel, channels_.back());
-  channels_.pop_back();
-#endif  // PW_RPC_DYNAMIC_ALLOCATION
-
-  return OkStatus();
-}
-
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/channel_test.cc b/pw_rpc/channel_test.cc
index b61fd8d..7687a2d 100644
--- a/pw_rpc/channel_test.cc
+++ b/pw_rpc/channel_test.cc
@@ -14,31 +14,33 @@
 
 #include "pw_rpc/channel.h"
 
-#include <cstddef>
-
 #include "gtest/gtest.h"
 #include "pw_rpc/internal/packet.h"
-#include "pw_rpc/internal/test_utils.h"
+#include "pw_rpc_private/internal_test_utils.h"
 
 namespace pw::rpc::internal {
 namespace {
 
+using std::byte;
+
 TEST(ChannelOutput, Name) {
   class NameTester : public ChannelOutput {
    public:
     NameTester(const char* name) : ChannelOutput(name) {}
-    Status Send(std::span<const std::byte>) override { return OkStatus(); }
+    std::span<std::byte> AcquireBuffer() override { return {}; }
+    Status SendAndReleaseBuffer(std::span<const std::byte>) override {
+      return OkStatus();
+    }
   };
 
   EXPECT_STREQ("hello_world", NameTester("hello_world").name());
   EXPECT_EQ(nullptr, NameTester(nullptr).name());
 }
 
-constexpr Packet kTestPacket(
-    PacketType::RESPONSE, 23, 42, 100, 0, {}, Status::NotFound());
+constexpr Packet kTestPacket(PacketType::RESPONSE, 1, 42, 100);
 const size_t kReservedSize = 2 /* type */ + 2 /* channel */ + 5 /* service */ +
                              5 /* method */ + 2 /* payload key */ +
-                             2 /* status (if not OK) */;
+                             2 /* status */;
 
 enum class ChannelId {
   kOne = 1,
@@ -56,22 +58,69 @@
   EXPECT_EQ(kReservedSize, kTestPacket.MinEncodedSizeBytes());
 }
 
-TEST(ExtractChannelId, ValidPacket) {
-  std::byte buffer[64] = {};
-  Result<ConstByteSpan> result = kTestPacket.Encode(buffer);
-  ASSERT_EQ(result.status(), OkStatus());
+TEST(Channel, OutputBuffer_EmptyBuffer) {
+  TestOutput<0> output;
+  internal::Channel channel(100, &output);
 
-  Result<uint32_t> channel_id = ExtractChannelId(*result);
-  ASSERT_EQ(channel_id.status(), OkStatus());
-  EXPECT_EQ(*channel_id, 23u);
+  Channel::OutputBuffer buffer = channel.AcquireBuffer();
+  EXPECT_TRUE(buffer.payload(kTestPacket).empty());
 }
 
-TEST(ExtractChannelId, InvalidPacket) {
-  constexpr std::byte buffer[64] = {std::byte{1}, std::byte{2}};
+TEST(Channel, OutputBuffer_TooSmall) {
+  TestOutput<kReservedSize - 1> output;
+  internal::Channel channel(100, &output);
 
-  Result<uint32_t> channel_id = ExtractChannelId(buffer);
+  Channel::OutputBuffer output_buffer = channel.AcquireBuffer();
+  EXPECT_TRUE(output_buffer.payload(kTestPacket).empty());
 
-  EXPECT_EQ(channel_id.status(), Status::DataLoss());
+  EXPECT_EQ(Status::Internal(), channel.Send(output_buffer, kTestPacket));
+}
+
+TEST(Channel, OutputBuffer_ExactFit) {
+  TestOutput<kReservedSize> output;
+  internal::Channel channel(100, &output);
+
+  Channel::OutputBuffer output_buffer(channel.AcquireBuffer());
+  const std::span payload = output_buffer.payload(kTestPacket);
+
+  EXPECT_EQ(payload.size(), output.buffer().size() - kReservedSize);
+  EXPECT_EQ(output.buffer().data() + kReservedSize, payload.data());
+
+  EXPECT_EQ(OkStatus(), channel.Send(output_buffer, kTestPacket));
+}
+
+TEST(Channel, OutputBuffer_PayloadDoesNotFit_ReportsError) {
+  TestOutput<kReservedSize> output;
+  internal::Channel channel(100, &output);
+
+  Packet packet = kTestPacket;
+  byte data[1] = {};
+  packet.set_payload(data);
+
+  EXPECT_EQ(Status::Internal(), channel.Send(packet));
+}
+
+TEST(Channel, OutputBuffer_ExtraRoom) {
+  TestOutput<kReservedSize * 3> output;
+  internal::Channel channel(100, &output);
+
+  Channel::OutputBuffer output_buffer = channel.AcquireBuffer();
+  const std::span payload = output_buffer.payload(kTestPacket);
+
+  EXPECT_EQ(payload.size(), output.buffer().size() - kReservedSize);
+  EXPECT_EQ(output.buffer().data() + kReservedSize, payload.data());
+
+  EXPECT_EQ(OkStatus(), channel.Send(output_buffer, kTestPacket));
+}
+
+TEST(Channel, OutputBuffer_ReturnsStatusFromChannelOutputSend) {
+  TestOutput<kReservedSize * 3> output;
+  internal::Channel channel(100, &output);
+
+  Channel::OutputBuffer output_buffer = channel.AcquireBuffer();
+  output.set_send_status(Status::Aborted());
+
+  EXPECT_EQ(Status::Aborted(), channel.Send(output_buffer, kTestPacket));
 }
 
 }  // namespace
diff --git a/pw_rpc/client.cc b/pw_rpc/client.cc
index 21a950d..b5f3f5d 100644
--- a/pw_rpc/client.cc
+++ b/pw_rpc/client.cc
@@ -12,86 +12,90 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-// clang-format off
-#include "pw_rpc/internal/log_config.h"  // PW_LOG_* macros must be first.
-
 #include "pw_rpc/client.h"
-// clang-format on
 
 #include "pw_log/log.h"
-#include "pw_rpc/internal/client_call.h"
 #include "pw_rpc/internal/packet.h"
-#include "pw_status/try.h"
 
 namespace pw::rpc {
 namespace {
 
+using internal::BaseClientCall;
 using internal::Packet;
 using internal::PacketType;
 
 }  // namespace
 
 Status Client::ProcessPacket(ConstByteSpan data) {
-  PW_TRY_ASSIGN(Packet packet, Endpoint::ProcessPacket(data, Packet::kClient));
-
-  // Find an existing call for this RPC, if any.
-  internal::rpc_lock().lock();
-  internal::ClientCall* call =
-      static_cast<internal::ClientCall*>(FindCall(packet));
-
-  internal::Channel* channel = GetInternalChannel(packet.channel_id());
-
-  if (channel == nullptr) {
-    internal::rpc_lock().unlock();
-    PW_LOG_WARN("RPC client received a packet for an unregistered channel");
-    return Status::Unavailable();
+  Result<Packet> result = Packet::FromBuffer(data);
+  if (!result.ok()) {
+    PW_LOG_WARN("RPC client failed to decode incoming packet");
+    return Status::DataLoss();
   }
 
-  if (call == nullptr || call->id() != packet.call_id()) {
-    // The call for the packet does not exist. If the packet is a server stream
-    // message, notify the server so that it can kill the stream. Otherwise,
-    // silently drop the packet (as it would terminate the RPC anyway).
-    if (packet.type() == PacketType::SERVER_STREAM) {
-      channel->Send(Packet::ClientError(packet, Status::FailedPrecondition()))
-          .IgnoreError();
-      PW_LOG_WARN("RPC client received stream message for an unknown call");
-    }
-    internal::rpc_lock().unlock();
-    return OkStatus();  // OK since the packet was handled
+  Packet& packet = result.value();
+
+  if (packet.destination() != Packet::kClient) {
+    return Status::InvalidArgument();
+  }
+
+  if (packet.channel_id() == Channel::kUnassignedChannelId ||
+      packet.service_id() == 0 || packet.method_id() == 0) {
+    PW_LOG_WARN("RPC client received a malformed packet");
+    return Status::DataLoss();
+  }
+
+  auto call = std::find_if(calls_.begin(), calls_.end(), [&](auto& c) {
+    return c.channel().id() == packet.channel_id() &&
+           c.service_id() == packet.service_id() &&
+           c.method_id() == packet.method_id();
+  });
+
+  auto channel = std::find_if(channels_.begin(), channels_.end(), [&](auto& c) {
+    return c.id() == packet.channel_id();
+  });
+
+  if (channel == channels_.end()) {
+    PW_LOG_WARN("RPC client received a packet for an unregistered channel");
+    return Status::NotFound();
+  }
+
+  if (call == calls_.end()) {
+    PW_LOG_WARN("RPC client received a packet for a request it did not make");
+    channel->Send(Packet::ClientError(packet, Status::FailedPrecondition()));
+    return Status::NotFound();
   }
 
   switch (packet.type()) {
     case PacketType::RESPONSE:
-      // RPCs without a server stream include a payload with the final packet.
-      if (call->has_server_stream()) {
-        static_cast<internal::StreamResponseClientCall&>(*call).HandleCompleted(
-            packet.status());
-      } else {
-        static_cast<internal::UnaryResponseClientCall&>(*call).HandleCompleted(
-            packet.payload(), packet.status());
-      }
-      break;
     case PacketType::SERVER_ERROR:
-      call->HandleError(packet.status());
+      call->HandleResponse(packet);
       break;
-    case PacketType::SERVER_STREAM:
-      if (call->has_server_stream()) {
-        call->HandlePayload(packet.payload());
-      } else {
-        // Report the error to the server so it can abort the RPC.
-        channel->Send(Packet::ClientError(packet, Status::InvalidArgument()))
-            .IgnoreError();  // Errors are logged in Channel::Send.
-        call->HandleError(Status::InvalidArgument());
-        PW_LOG_DEBUG("Received SERVER_STREAM for RPC without a server stream");
-      }
+    case PacketType::SERVER_STREAM_END:
+      call->HandleResponse(packet);
+      RemoveCall(*call);
       break;
     default:
-      internal::rpc_lock().unlock();
-      PW_LOG_WARN("pw_rpc client unable to handle packet of type %u",
-                  static_cast<unsigned>(packet.type()));
+      return Status::Unimplemented();
   }
 
-  return OkStatus();  // OK since the packet was handled
+  return OkStatus();
+}
+
+Status Client::RegisterCall(BaseClientCall& call) {
+  auto existing_call = std::find_if(calls_.begin(), calls_.end(), [&](auto& c) {
+    return c.channel().id() == call.channel().id() &&
+           c.service_id() == call.service_id() &&
+           c.method_id() == call.method_id();
+  });
+  if (existing_call != calls_.end()) {
+    PW_LOG_WARN(
+        "RPC client tried to call same method multiple times; aborting.");
+    return Status::FailedPrecondition();
+  }
+
+  calls_.push_front(call);
+  return OkStatus();
 }
 
 }  // namespace pw::rpc
diff --git a/pw_rpc/client_call.cc b/pw_rpc/client_call.cc
deleted file mode 100644
index d8e57e8..0000000
--- a/pw_rpc/client_call.cc
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/internal/client_call.h"
-
-namespace pw::rpc::internal {
-
-void ClientCall::CloseClientCall() {
-  if (client_stream_open()) {
-    CloseClientStreamLocked().IgnoreError();
-  }
-  UnregisterAndMarkClosed();
-}
-
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/client_integration_test.cc b/pw_rpc/client_integration_test.cc
deleted file mode 100644
index 94ce807..0000000
--- a/pw_rpc/client_integration_test.cc
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstring>
-
-#include "gtest/gtest.h"
-#include "pw_assert/check.h"
-#include "pw_log/log.h"
-#include "pw_rpc/benchmark.raw_rpc.pb.h"
-#include "pw_rpc/integration_testing.h"
-#include "pw_sync/binary_semaphore.h"
-
-namespace rpc_test {
-namespace {
-
-constexpr int kIterations = 3;
-
-using namespace std::chrono_literals;
-using pw::ByteSpan;
-using pw::ConstByteSpan;
-using pw::Function;
-using pw::OkStatus;
-using pw::Status;
-
-using pw::rpc::pw_rpc::raw::Benchmark;
-
-Benchmark::Client kServiceClient(pw::rpc::integration_test::client(),
-                                 pw::rpc::integration_test::kChannelId);
-
-class StringReceiver {
- public:
-  const char* Wait() {
-    PW_CHECK(sem_.try_acquire_for(1500ms));
-    return buffer_;
-  }
-
-  Function<void(ConstByteSpan, Status)> UnaryOnCompleted() {
-    return [this](ConstByteSpan data, Status) { CopyPayload(data); };
-  }
-
-  Function<void(ConstByteSpan)> OnNext() {
-    return [this](ConstByteSpan data) { CopyPayload(data); };
-  }
-
- private:
-  void CopyPayload(ConstByteSpan data) {
-    std::memset(buffer_, 0, sizeof(buffer_));
-    PW_CHECK_UINT_LE(data.size(), sizeof(buffer_));
-    std::memcpy(buffer_, data.data(), data.size());
-    sem_.release();
-  }
-
-  pw::sync::BinarySemaphore sem_;
-  char buffer_[64];
-};
-
-TEST(RawRpcIntegrationTest, Unary) {
-  for (int i = 0; i < kIterations; ++i) {
-    StringReceiver receiver;
-    pw::rpc::RawUnaryReceiver call = kServiceClient.UnaryEcho(
-        std::as_bytes(std::span("hello")), receiver.UnaryOnCompleted());
-    EXPECT_STREQ(receiver.Wait(), "hello");
-  }
-}
-
-TEST(RawRpcIntegrationTest, BidirectionalStreaming) {
-  for (int i = 0; i < kIterations; ++i) {
-    StringReceiver receiver;
-    pw::rpc::RawClientReaderWriter call =
-        kServiceClient.BidirectionalEcho(receiver.OnNext());
-
-    ASSERT_EQ(OkStatus(), call.Write(std::as_bytes(std::span("Yello"))));
-    EXPECT_STREQ(receiver.Wait(), "Yello");
-
-    ASSERT_EQ(OkStatus(), call.Write(std::as_bytes(std::span("Dello"))));
-    EXPECT_STREQ(receiver.Wait(), "Dello");
-
-    ASSERT_EQ(OkStatus(), call.Cancel());
-  }
-}
-
-}  // namespace
-}  // namespace rpc_test
-
-int main(int argc, char* argv[]) {
-  if (!pw::rpc::integration_test::InitializeClient(argc, argv).ok()) {
-    return 1;
-  }
-  return RUN_ALL_TESTS();
-}
diff --git a/pw_rpc/client_server.cc b/pw_rpc/client_server.cc
index 1268b17..f0c34ab 100644
--- a/pw_rpc/client_server.cc
+++ b/pw_rpc/client_server.cc
@@ -16,8 +16,8 @@
 
 namespace pw::rpc {
 
-Status ClientServer::ProcessPacket(ConstByteSpan packet,
-                                   ChannelOutput* interface) {
+Status ClientServer::ProcessPacket(std::span<const std::byte> packet,
+                                   ChannelOutput& interface) {
   Status status = server_.ProcessPacket(packet, interface);
   if (status.IsInvalidArgument()) {
     // INVALID_ARGUMENT indicates the packet is intended for a client.
diff --git a/pw_rpc/client_server_test.cc b/pw_rpc/client_server_test.cc
index bddcea8..5104c66 100644
--- a/pw_rpc/client_server_test.cc
+++ b/pw_rpc/client_server_test.cc
@@ -16,10 +16,10 @@
 
 #include "gtest/gtest.h"
 #include "pw_rpc/internal/packet.h"
-#include "pw_rpc/internal/test_utils.h"
-#include "pw_rpc/raw/fake_channel_output.h"
-#include "pw_rpc/raw/internal/method_union.h"
+#include "pw_rpc/internal/raw_method_union.h"
+#include "pw_rpc/server_context.h"
 #include "pw_rpc/service.h"
+#include "pw_rpc_private/internal_test_utils.h"
 
 namespace pw::rpc::internal {
 namespace {
@@ -28,11 +28,11 @@
 constexpr uint32_t kFakeServiceId = 3;
 constexpr uint32_t kFakeMethodId = 10;
 
-RawFakeChannelOutput<1> output;
+TestOutput<32> output;
 rpc::Channel channels[] = {Channel::Create<kFakeChannelId>(&output)};
 
-void FakeMethod(ConstByteSpan, RawUnaryResponder& responder) {
-  ASSERT_EQ(OkStatus(), responder.Finish({}, Status::Unimplemented()));
+StatusWithSize FakeMethod(ServerContext&, ConstByteSpan, ByteSpan) {
+  return StatusWithSize::Unimplemented();
 }
 
 class FakeService : public Service {
@@ -40,7 +40,7 @@
   FakeService(uint32_t id) : Service(id, kMethods) {}
 
   static constexpr std::array<RawMethodUnion, 1> kMethods = {
-      RawMethod::AsynchronousUnary<FakeMethod>(kFakeMethodId),
+      RawMethod::Unary<FakeMethod>(kFakeMethodId),
   };
 };
 
@@ -71,9 +71,9 @@
   Result result = packet.Encode(buffer);
   EXPECT_EQ(result.status(), OkStatus());
 
-  // No calls are registered on the client, so nothing should happen. The
-  // ProcessPacket call still returns OK since the client handled it.
-  EXPECT_EQ(client_server.ProcessPacket(result.value(), output), OkStatus());
+  // No calls are registered on the client, so this should fail.
+  EXPECT_EQ(client_server.ProcessPacket(result.value(), output),
+            Status::NotFound());
 }
 
 TEST(ClientServer, ProcessPacket_BadData) {
diff --git a/pw_rpc/client_streaming_rpc.svg b/pw_rpc/client_streaming_rpc.svg
deleted file mode 100644
index 37155fd..0000000
--- a/pw_rpc/client_streaming_rpc.svg
+++ /dev/null
@@ -1,69 +0,0 @@
-<!-- Originally created with blockdiag. See the Git history for the source. -->
-<svg height="614.9000000000001" viewBox="0 0 594 559" width="653.4000000000001" xmlns="http://www.w3.org/2000/svg" xmlns:inkspace="http://www.inkscape.org/namespaces/inkscape" xmlns:xlink="http://www.w3.org/1999/xlink" style="background-color:white">
-<defs id="defs_block">
-<filter height="1.504" id="filter_blur" inkspace:collect="always" width="1.1575" x="-0.07875" y="-0.252">
-<feGaussianBlur id="feGaussianBlur3780" inkspace:collect="always" stdDeviation="4.2"></feGaussianBlur>
-</filter>
-</defs>
-<title>Client Streaming RPC</title>
-<desc></desc>
-<rect fill="rgb(0,0,0)" height="384" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="8" x="231" y="153"></rect>
-<rect fill="rgb(0,0,0)" height="308" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="8" x="423" y="153"></rect>
-<polygon fill="rgb(0,0,0)" points="87,126 211,126 219,134 219,180 87,180 87,126" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></polygon>
-<polygon fill="rgb(0,0,0)" points="51,220 211,220 219,228 219,287 51,287 51,220" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></polygon>
-<polygon fill="rgb(0,0,0)" points="27,327 211,327 219,335 219,381 27,381 27,327" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></polygon>
-<polygon fill="rgb(0,0,0)" points="443,421 573,421 581,429 581,501 443,501 443,421" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></polygon>
-<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="171" y="46"></rect>
-<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="363" y="46"></rect>
-<path d="M 232 80 L 232 547" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="8 4"></path>
-<rect fill="moccasin" height="384" stroke="rgb(0,0,0)" width="8" x="228" y="147"></rect>
-<path d="M 424 80 L 424 547" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="8 4"></path>
-<rect fill="moccasin" height="308" stroke="rgb(0,0,0)" width="8" x="420" y="147"></rect>
-<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="168" y="40"></rect>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="36" x="232.0" y="66">client</text>
-<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="360" y="40"></rect>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="36" x="424.0" y="66">server</text>
-<path d="M 240 147 L 416 147" fill="none" stroke="rgb(0,0,0)"></path>
-<polygon fill="rgb(0,0,0)" points="408,143 416,147 408,151" stroke="rgb(0,0,0)"></polygon>
-<polygon fill="rgb(240,248,255)" points="84,120 208,120 216,128 216,174 84,174 84,120" stroke="rgb(0,0,0)"></polygon>
-<path d="M 208 120 L 208 128" fill="none" stroke="rgb(0,0,0)"></path>
-<path d="M 208 128 L 216 128" fill="none" stroke="rgb(0,0,0)"></path>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="108" x="146.0" y="133">PacketType.REQUEST</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="60" x="122.0" y="146">channel ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="60" x="122.0" y="159">service ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="54" x="119.0" y="172">method ID</text>
-<path d="M 240 247 L 416 247" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="4"></path>
-<polygon fill="rgb(0,0,0)" points="408,243 416,247 408,251" stroke="rgb(0,0,0)"></polygon>
-<polygon fill="rgb(240,248,255)" points="48,214 208,214 216,222 216,281 48,281 48,214" stroke="rgb(0,0,0)"></polygon>
-<path d="M 208 214 L 208 222" fill="none" stroke="rgb(0,0,0)"></path>
-<path d="M 208 222 L 216 222" fill="none" stroke="rgb(0,0,0)"></path>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="144" x="128.0" y="227">PacketType.CLIENT_STREAM</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="60" x="86.0" y="240">channel ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="60" x="86.0" y="253">service ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="54" x="83.0" y="266">method ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="42" x="77.0" y="279">payload</text>
-<path d="M 240 348 L 416 348" fill="none" stroke="rgb(0,0,0)"></path>
-<polygon fill="rgb(0,0,0)" points="408,344 416,348 408,352" stroke="rgb(0,0,0)"></polygon>
-<polygon fill="rgb(240,248,255)" points="24,321 208,321 216,329 216,375 24,375 24,321" stroke="rgb(0,0,0)"></polygon>
-<path d="M 208 321 L 208 329" fill="none" stroke="rgb(0,0,0)"></path>
-<path d="M 208 329 L 216 329" fill="none" stroke="rgb(0,0,0)"></path>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="168" x="116.0" y="334">PacketType.CLIENT_STREAM_END</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="60" x="62.0" y="347">channel ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="60" x="62.0" y="360">service ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="54" x="59.0" y="373">method ID</text>
-<path d="M 240 455 L 416 455" fill="none" stroke="rgb(0,0,0)"></path>
-<polygon fill="rgb(0,0,0)" points="248,451 240,455 248,459" stroke="rgb(0,0,0)"></polygon>
-<polygon fill="rgb(240,248,255)" points="440,415 570,415 578,423 578,495 440,495 440,415" stroke="rgb(0,0,0)"></polygon>
-<path d="M 570 415 L 570 423" fill="none" stroke="rgb(0,0,0)"></path>
-<path d="M 570 423 L 578 423" fill="none" stroke="rgb(0,0,0)"></path>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="114" x="505.0" y="428">PacketType.RESPONSE</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="60" x="478.0" y="441">channel ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="60" x="478.0" y="454">service ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="54" x="475.0" y="467">method ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="42" x="469.0" y="480">payload</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="36" x="466.0" y="493">status</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="30" x="259.0" y="145">start</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="138" x="313.0" y="245">messages (zero or more)</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="24" x="256.0" y="346">done</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="48" x="392.0" y="453">response</text>
-</svg>
diff --git a/pw_rpc/client_streaming_rpc_cancelled.svg b/pw_rpc/client_streaming_rpc_cancelled.svg
deleted file mode 100644
index 9542ed2..0000000
--- a/pw_rpc/client_streaming_rpc_cancelled.svg
+++ /dev/null
@@ -1,76 +0,0 @@
-<!-- Originally created with blockdiag. -->
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:inkspace="http://www.inkscape.org/namespaces/inkscape" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 598 452" width="598px" height="452px">
-  <defs id="defs_block">
-    <filter height="1.504" id="filter_blur" inkspace:collect="always" width="1.1575" x="-0.07875" y="-0.252">
-      <feGaussianBlur id="feGaussianBlur3780" inkspace:collect="always" stdDeviation="4.2"/>
-    </filter>
-  </defs>
-  <title>Cancelled Client Streaming RPC</title>
-  <desc>seqdiag {
-  default_note_color = aliceblue;
-
-  client -&gt; server [
-      label = "start",
-      leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID"
-  ];
-
-  client --&gt; server [
-      noactivate,
-      label = "messages (zero or more)",
-      leftnote = "PacketType.CLIENT_STREAM\nchannel ID\nservice ID\nmethod ID\npayload"
-  ];
-
-  client -&gt; server [
-      noactivate,
-      label = "cancel",
-      rightnote = "PacketType.CLIENT_ERROR\nchannel ID\nservice ID\nmethod ID\nstatus=CANCELLED"
-  ];
-}</desc>
-  <rect fill="rgb(0,0,0)" height="277" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="8" x="209" y="153"/>
-  <rect fill="rgb(0,0,0)" height="277" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="8" x="401" y="153"/>
-  <polygon fill="rgb(0,0,0)" points="64,126 189,126 197,134 197,180 64,180 64,126" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"/>
-  <polygon fill="rgb(0,0,0)" points="27,220 189,220 197,228 197,287 27,287 27,220" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"/>
-  <polygon fill="rgb(0,0,0)" points="421,327 577,327 585,335 585,394 421,394 421,327" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"/>
-  <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="149" y="46"/>
-  <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="341" y="46"/>
-  <path d="M 210 80 L 210 440" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="8 4"/>
-  <rect fill="moccasin" height="277" stroke="rgb(0,0,0)" width="8" x="206" y="147"/>
-  <path d="M 402 80 L 402 440" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="8 4"/>
-  <rect fill="moccasin" height="277" stroke="rgb(0,0,0)" width="8" x="398" y="147"/>
-  <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="146" y="40"/>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="37" x="210.5" y="66">client</text>
-  <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="338" y="40"/>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="37" x="402.5" y="66">server</text>
-  <path d="M 218 147 L 394 147" fill="none" stroke="rgb(0,0,0)"/>
-  <polygon fill="rgb(0,0,0)" points="386,143 394,147 386,151" stroke="rgb(0,0,0)"/>
-  <polygon fill="rgb(240,248,255)" points="61,120 186,120 194,128 194,174 61,174 61,120" stroke="rgb(0,0,0)"/>
-  <path d="M 186 120 L 186 128" fill="none" stroke="rgb(0,0,0)"/>
-  <path d="M 186 128 L 194 128" fill="none" stroke="rgb(0,0,0)"/>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="109" x="123.5" y="133">PacketType.REQUEST</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="61" x="99.5" y="146">channel ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="61" x="99.5" y="159">service ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="55" x="96.5" y="172">method ID</text>
-  <path d="M 218 247 L 394 247" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="4"/>
-  <polygon fill="rgb(0,0,0)" points="386,243 394,247 386,251" stroke="rgb(0,0,0)"/>
-  <polygon fill="rgb(240,248,255)" points="24,214 186,214 194,222 194,281 24,281 24,214" stroke="rgb(0,0,0)"/>
-  <path d="M 186 214 L 186 222" fill="none" stroke="rgb(0,0,0)"/>
-  <path d="M 186 222 L 194 222" fill="none" stroke="rgb(0,0,0)"/>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="146" x="105.0" y="227">PacketType.CLIENT_STREAM</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="61" x="62.5" y="240">channel ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="61" x="62.5" y="253">service ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="55" x="59.5" y="266">method ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="43" x="53.5" y="279">payload</text>
-  <path d="M 218 354 L 394 354" fill="none" stroke="rgb(0,0,0)"/>
-  <polygon fill="rgb(0,0,0)" points="386,350 394,354 386,358" stroke="rgb(0,0,0)"/>
-  <polygon fill="rgb(240,248,255)" points="418,321 574,321 582,329 582,388 418,388 418,321" stroke="rgb(0,0,0)"/>
-  <path d="M 574 321 L 574 329" fill="none" stroke="rgb(0,0,0)"/>
-  <path d="M 574 329 L 582 329" fill="none" stroke="rgb(0,0,0)"/>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="140" x="496.0" y="334">PacketType.CLIENT_ERROR</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="61" x="456.5" y="347">channel ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="61" x="456.5" y="360">service ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="55" x="453.5" y="373">method ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="97" x="474.5" y="386">status=CANCELLED</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="31" x="237.5" y="145">start</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="140" x="292.0" y="245">messages (zero or more)</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="37" x="240.5" y="352">cancel</text>
-</svg>
diff --git a/pw_rpc/client_test.cc b/pw_rpc/client_test.cc
new file mode 100644
index 0000000..a5993fe
--- /dev/null
+++ b/pw_rpc/client_test.cc
@@ -0,0 +1,88 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_rpc/client.h"
+
+#include "gtest/gtest.h"
+#include "pw_rpc/internal/packet.h"
+#include "pw_rpc_private/internal_test_utils.h"
+
+namespace pw::rpc {
+namespace {
+
+using internal::BaseClientCall;
+using internal::Packet;
+using internal::PacketType;
+
+class TestClientCall : public BaseClientCall {
+ public:
+  constexpr TestClientCall(Channel* channel,
+                           uint32_t service_id,
+                           uint32_t method_id)
+      : BaseClientCall(channel, service_id, method_id, ProcessPacket) {}
+
+  static void ProcessPacket(BaseClientCall& call, const Packet& packet) {
+    static_cast<TestClientCall&>(call).HandlePacket(packet);
+  }
+
+  void HandlePacket(const Packet&) { invoked_ = true; }
+
+  constexpr bool invoked() const { return invoked_; }
+
+ private:
+  bool invoked_ = false;
+};
+
+TEST(Client, ProcessPacket_InvokesARegisteredClientCall) {
+  ClientContextForTest context;
+
+  TestClientCall call(
+      &context.channel(), context.service_id(), context.method_id());
+  EXPECT_EQ(context.SendResponse(OkStatus(), {}), OkStatus());
+
+  EXPECT_TRUE(call.invoked());
+}
+
+TEST(Client, ProcessPacket_SendsClientErrorOnUnregisteredCall) {
+  ClientContextForTest context;
+
+  EXPECT_EQ(context.SendResponse(OkStatus(), {}), Status::NotFound());
+
+  ASSERT_EQ(context.output().packet_count(), 1u);
+  const Packet& packet = context.output().sent_packet();
+  EXPECT_EQ(packet.type(), PacketType::CLIENT_ERROR);
+  EXPECT_EQ(packet.channel_id(), context.channel_id());
+  EXPECT_EQ(packet.service_id(), context.service_id());
+  EXPECT_EQ(packet.method_id(), context.method_id());
+  EXPECT_TRUE(packet.payload().empty());
+  EXPECT_EQ(packet.status(), Status::FailedPrecondition());
+}
+
+TEST(Client, ProcessPacket_ReturnsDataLossOnBadPacket) {
+  ClientContextForTest context;
+
+  constexpr std::byte bad_packet[]{
+      std::byte{0xab}, std::byte{0xcd}, std::byte{0xef}};
+  EXPECT_EQ(context.client().ProcessPacket(bad_packet), Status::DataLoss());
+}
+
+TEST(Client, ProcessPacket_ReturnsInvalidArgumentOnServerPacket) {
+  ClientContextForTest context;
+  EXPECT_EQ(context.SendPacket(PacketType::REQUEST), Status::InvalidArgument());
+  EXPECT_EQ(context.SendPacket(PacketType::CANCEL_SERVER_STREAM),
+            Status::InvalidArgument());
+}
+
+}  // namespace
+}  // namespace pw::rpc
diff --git a/pw_rpc/config.gni b/pw_rpc/config.gni
deleted file mode 100644
index f497aee..0000000
--- a/pw_rpc/config.gni
+++ /dev/null
@@ -1,24 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/module_config.gni")
-
-declare_args() {
-  # The build target that overrides the default configuration options for this
-  # module. This should point to a source set that provides defines through a
-  # public config (which may -include a file or add defines directly).
-  pw_rpc_CONFIG = pw_build_DEFAULT_MODULE_CONFIG
-}
diff --git a/pw_rpc/docs.rst b/pw_rpc/docs.rst
index 25554f0..daece79 100644
--- a/pw_rpc/docs.rst
+++ b/pw_rpc/docs.rst
@@ -14,103 +14,15 @@
   :maxdepth: 1
 
   py/docs
-  ts/docs
 
 .. admonition:: Try it out!
 
   For a quick intro to ``pw_rpc``, see the
   :ref:`module-pw_hdlc-rpc-example` in the :ref:`module-pw_hdlc` module.
 
-.. warning::
+.. attention::
 
-  This documentation is under construction. Many sections are outdated or
-  incomplete. The content needs to be reorgnanized.
-
-Implementations
-===============
-Pigweed provides several client and server implementations of ``pw_rpc``.
-
-.. list-table::
-  :header-rows: 1
-
-  * - Language
-    - Server
-    - Client
-  * - C++ (raw)
-    - ✅
-    - ✅
-  * - C++ (Nanopb)
-    - ✅
-    - ✅
-  * - C++ (pw_protobuf)
-    - planned
-    - planned
-  * - Java
-    -
-    - in development
-  * - Python
-    -
-    - ✅
-  * - TypeScript
-    -
-    - in development
-
-RPC semantics
-=============
-The semantics of ``pw_rpc`` are similar to `gRPC
-<https://grpc.io/docs/what-is-grpc/core-concepts/>`_.
-
-RPC call lifecycle
-------------------
-In ``pw_rpc``, an RPC begins when the client sends a request packet. The server
-receives the request, looks up the relevant service method, then calls into the
-RPC function. The RPC is considered active until the server sends a response
-packet with the RPC's status. The client may terminate an ongoing RPC by
-cancelling it.
-
-``pw_rpc`` supports only one RPC invocation per service/method/channel. If a
-client calls an ongoing RPC on the same channel, the server cancels the ongoing
-call and reinvokes the RPC with the new request. This applies to unary and
-streaming RPCs, though the server may not have an opportunity to cancel a
-synchronously handled unary RPC before it completes. The same RPC may be invoked
-multiple times simultaneously if the invocations are on different channels.
-
-Status codes
-------------
-``pw_rpc`` call objects (``ClientReaderWriter``, ``ServerReaderWriter``, etc.)
-use certain status codes to indicate what occurred. These codes are returned
-from functions like ``Write()`` or ``Finish()``.
-
-* ``OK`` -- The operation succeeded.
-* ``UNAVAILABLE`` -- The channel is not currently registered with the server or
-  client.
-* ``UNKNOWN`` -- Sending a packet failed due to an unrecoverable
-  :cpp:func:`pw::rpc::ChannelOutput::Send` error.
-
-Unrequested responses
----------------------
-``pw_rpc`` supports sending responses to RPCs that have not yet been invoked by
-a client. This is useful in testing and in situations like an RPC that triggers
-reboot. After the reboot, the device opens the writer object and sends its
-response to the client.
-
-The C++ API for opening a server reader/writer takes the generated RPC function
-as a template parameter. The server to use, channel ID, and service instance are
-passed as arguments. The API is the same for all RPC types, except the
-appropriate reader/writer class must be used.
-
-.. code-block:: c++
-
-  // Open a ServerWriter for a server streaming RPC.
-  auto writer = RawServerWriter::Open<pw_rpc::raw::ServiceName::MethodName>(
-      server, channel_id, service_instance);
-
-  // Send some responses, even though the client has not yet called this RPC.
-  CHECK_OK(writer.Write(encoded_response_1));
-  CHECK_OK(writer.Write(encoded_response_2));
-
-  // Finish the RPC.
-  CHECK_OK(writer.Finish(OkStatus()));
+  This documentation is under construction.
 
 Creating an RPC
 ===============
@@ -207,9 +119,9 @@
 
 The generated header defines a base class for each RPC service declared in the
 ``.proto`` file. A service named ``TheService`` in package ``foo.bar`` would
-generate the following base class for Nanopb:
+generate the following base class:
 
-.. cpp:class:: template <typename Implementation> foo::bar::pw_rpc::nanopb::TheService::Service
+.. cpp:class:: template <typename Implementation> foo::bar::generated::TheService
 
 3. RPC service definition
 -------------------------
@@ -247,15 +159,17 @@
 
   namespace foo::bar {
 
-  class TheService : public pw_rpc::nanopb::TheService::Service<TheService> {
+  class TheService : public generated::TheService<TheService> {
    public:
-    pw::Status MethodOne(const foo_bar_Request& request,
+    pw::Status MethodOne(ServerContext& ctx,
+                         const foo_bar_Request& request,
                          foo_bar_Response& response) {
       // implementation
       return pw::OkStatus();
     }
 
-    void MethodTwo(const foo_bar_Request& request,
+    void MethodTwo(ServerContext& ctx,
+                   const foo_bar_Request& request,
                    ServerWriter<foo_bar_Response>& response) {
       // implementation
       response.Write(foo_bar_Response{.number = 123});
@@ -303,12 +217,13 @@
   // Declare the pw_rpc server with the HDLC channel.
   pw::rpc::Server server(channels);
 
-  foo::bar::TheService the_service;
-  pw::rpc::SomeOtherService some_other_service;
+  pw::rpc::TheService the_service;
 
   void RegisterServices() {
-    // Register the foo.bar.TheService example service and another service.
-    server.RegisterService(the_service, some_other_service);
+    // Register the foo.bar.TheService example service.
+    server.Register(the_service);
+
+    // Register other services
   }
 
   int main() {
@@ -350,40 +265,6 @@
   // will be set when the channel is used.
   pw::rpc::Channel dynamic_channel;
 
-Sometimes, the ID and output of a channel are not known at compile time as they
-depend on information stored on the physical device. To support this use case, a
-dynamically-assignable channel can be configured once at runtime with an ID and
-output.
-
-.. code-block:: cpp
-
-  // Create a dynamic channel without a compile-time ID or output.
-  pw::rpc::Channel dynamic_channel;
-
-  void Init() {
-    // Called during boot to pull the channel configuration from the system.
-    dynamic_channel.Configure(GetChannelId(), some_output);
-  }
-
-Adding and removing channels
-----------------------------
-New channels may be registered with the ``OpenChannel`` function. If dynamic
-allocation is enabled (:c:macro:`PW_RPC_DYNAMIC_ALLOCATION` is 1), any number of
-channels may be registered. If dynamic allocation is disabled, new channels may
-only be registered if there are availale channel slots in the span provided to
-the RPC endpoint at construction.
-
-A channel may be closed and unregistered with an endpoint by calling
-``ChannelClose`` on the endpoint with the corresponding channel ID.  This
-will terminate any pending calls and call their ``on_error`` callback
-with the ``ABORTED`` status.
-
-.. code-block:: cpp
-
-  // When a channel is closed, any pending calls will receive
-  // on_error callbacks with ABORTED status.
-  client->CloseChannel(1);
-
 Services
 ========
 A service is a logical grouping of RPCs defined within a .proto file. ``pw_rpc``
@@ -428,75 +309,9 @@
   static pw::rpc::EchoService echo_service;
 
   void Init() {
-    server.RegisterService(echo_service);
+    server.RegisterService(&echo_service);
   }
 
-Benchmarking and stress testing
--------------------------------
-
-.. toctree::
-  :maxdepth: 1
-  :hidden:
-
-  benchmark
-
-``pw_rpc`` provides an RPC service and Python module for stress testing and
-benchmarking a ``pw_rpc`` deployment. See :ref:`module-pw_rpc-benchmark`.
-
-Naming
-======
-
-Reserved names
---------------
-``pw_rpc`` reserves a few service method names so they can be used for generated
-classes. The following names cannnot be used for service methods:
-
-- ``Client``
-- ``Service``
-- Any reserved words in the languages ``pw_rpc`` supports (e.g. ``class``).
-
-``pw_rpc`` does not reserve any service names, but the restriction of avoiding
-reserved words in supported languages applies.
-
-Service naming style
---------------------
-``pw_rpc`` service names should use capitalized camel case and should not use
-the term "Service". Appending "Service" to a service name is redundant, similar
-to appending "Class" or "Function" to a class or function name. The
-C++ implementation class may use "Service" in its name, however.
-
-For example, a service for accessing a file system should simply be named
-``service FileSystem``, rather than ``service FileSystemService``, in the
-``.proto`` file.
-
-.. code-block:: protobuf
-
-  // file.proto
-  package pw.file;
-
-  service FileSystem {
-      rpc List(ListRequest) returns (stream ListResponse);
-  }
-
-The C++ service implementation class may append "Service" to the name.
-
-.. code-block:: cpp
-
-  // file_system_service.h
-  #include "pw_file/file.raw_rpc.pb.h"
-
-  namespace pw::file {
-
-  class FileSystemService : public pw_rpc::raw::FileSystem::Service<FileSystemService> {
-    void List(ConstByteSpan request, RawServerWriter& writer);
-  };
-
-  }
-
-For upstream Pigweed services, this naming style is a requirement. Note that
-some services created before this was established may use non-compliant
-names. For Pigweed users, this naming style is a suggestion.
-
 Protocol description
 ====================
 Pigweed RPC servers and clients communicate using ``pw_rpc`` packets. These
@@ -519,134 +334,111 @@
 
 Client-to-server packets
 ^^^^^^^^^^^^^^^^^^^^^^^^
-+-------------------+-------------------------------------+
-| packet type       | description                         |
-+===================+=====================================+
-| REQUEST           | Invoke an RPC                       |
-|                   |                                     |
-|                   | .. code-block:: text                |
-|                   |                                     |
-|                   |   - channel_id                      |
-|                   |   - service_id                      |
-|                   |   - method_id                       |
-|                   |   - payload                         |
-|                   |     (unary & server streaming only) |
-|                   |   - call_id (optional)              |
-|                   |                                     |
-+-------------------+-------------------------------------+
-| CLIENT_STREAM     | Message in a client stream          |
-|                   |                                     |
-|                   | .. code-block:: text                |
-|                   |                                     |
-|                   |   - channel_id                      |
-|                   |   - service_id                      |
-|                   |   - method_id                       |
-|                   |   - payload                         |
-|                   |   - call_id (if set in REQUEST)     |
-|                   |                                     |
-+-------------------+-------------------------------------+
-| CLIENT_STREAM_END | Client stream is complete           |
-|                   |                                     |
-|                   | .. code-block:: text                |
-|                   |                                     |
-|                   |   - channel_id                      |
-|                   |   - service_id                      |
-|                   |   - method_id                       |
-|                   |   - call_id (if set in REQUEST)     |
-|                   |                                     |
-+-------------------+-------------------------------------+
-| CLIENT_ERROR      | Abort an ongoing RPC                |
-|                   |                                     |
-|                   | .. code-block:: text                |
-|                   |                                     |
-|                   |   - channel_id                      |
-|                   |   - service_id                      |
-|                   |   - method_id                       |
-|                   |   - status                          |
-|                   |   - call_id (if set in REQUEST)     |
-|                   |                                     |
-+-------------------+-------------------------------------+
++---------------------------+----------------------------------+
+| packet type               | description                      |
++===========================+==================================+
+| REQUEST                   | RPC request                      |
+|                           |                                  |
+|                           | .. code-block:: text             |
+|                           |                                  |
+|                           |   - channel_id                   |
+|                           |   - service_id                   |
+|                           |   - method_id                    |
+|                           |   - payload                      |
+|                           |     (unless first client stream) |
+|                           |                                  |
++---------------------------+----------------------------------+
+| CLIENT_STREAM_END         | Client stream finished           |
+|                           |                                  |
+|                           | .. code-block:: text             |
+|                           |                                  |
+|                           |   - channel_id                   |
+|                           |   - service_id                   |
+|                           |   - method_id                    |
+|                           |                                  |
+|                           |                                  |
++---------------------------+----------------------------------+
+| CLIENT_ERROR              | Received unexpected packet       |
+|                           |                                  |
+|                           | .. code-block:: text             |
+|                           |                                  |
+|                           |   - channel_id                   |
+|                           |   - service_id                   |
+|                           |   - method_id                    |
+|                           |   - status                       |
++---------------------------+----------------------------------+
+| CANCEL_SERVER_STREAM      | Cancel a server stream           |
+|                           |                                  |
+|                           | .. code-block:: text             |
+|                           |                                  |
+|                           |   - channel_id                   |
+|                           |   - service_id                   |
+|                           |   - method_id                    |
+|                           |                                  |
++---------------------------+----------------------------------+
 
-**Client errors**
+**Errors**
 
 The client sends ``CLIENT_ERROR`` packets to a server when it receives a packet
-it did not request. If possible, the server should abort it.
+it did not request. If the RPC is a streaming RPC, the server should abort it.
 
-The status code indicates the type of error. The status code is logged, but all
-status codes result in the same action by the server: aborting the RPC.
+The status code indicates the type of error. If the client does not distinguish
+between the error types, it can send whichever status is most relevant. The
+status code is logged, but all status codes result in the same action by the
+server: aborting the RPC.
 
-* ``CANCELLED`` -- The client requested that the RPC be cancelled.
-* ``ABORTED`` -- The RPC was aborted due its channel being closed.
 * ``NOT_FOUND`` -- Received a packet for a service method the client does not
   recognize.
 * ``FAILED_PRECONDITION`` -- Received a packet for a service method that the
   client did not invoke.
-* ``DATA_LOSS`` -- Received a corrupt packet for a pending service method.
-* ``INVALID_ARGUMENT`` -- The server sent a packet type to an RPC that does not
-  support it (a ``SERVER_STREAM`` was sent to an RPC with no server stream).
-* ``UNAVAILABLE`` -- Received a packet for an unknown channel.
 
 Server-to-client packets
 ^^^^^^^^^^^^^^^^^^^^^^^^
-+-------------------+-------------------------------------+
-| packet type       | description                         |
-+===================+=====================================+
-| RESPONSE          | The RPC is complete                 |
-|                   |                                     |
-|                   | .. code-block:: text                |
-|                   |                                     |
-|                   |   - channel_id                      |
-|                   |   - service_id                      |
-|                   |   - method_id                       |
-|                   |   - status                          |
-|                   |   - payload                         |
-|                   |     (unary & client streaming only) |
-|                   |   - call_id (if set in REQUEST)     |
-|                   |                                     |
-+-------------------+-------------------------------------+
-| SERVER_STREAM     | Message in a server stream          |
-|                   |                                     |
-|                   | .. code-block:: text                |
-|                   |                                     |
-|                   |   - channel_id                      |
-|                   |   - service_id                      |
-|                   |   - method_id                       |
-|                   |   - payload                         |
-|                   |   - call_id (if set in REQUEST)     |
-|                   |                                     |
-+-------------------+-------------------------------------+
-| SERVER_ERROR      | Received unexpected packet          |
-|                   |                                     |
-|                   | .. code-block:: text                |
-|                   |                                     |
-|                   |   - channel_id                      |
-|                   |   - service_id (if relevant)        |
-|                   |   - method_id (if relevant)         |
-|                   |   - status                          |
-|                   |   - call_id (if set in REQUEST)     |
-|                   |                                     |
-+-------------------+-------------------------------------+
++-------------------+--------------------------------+
+| packet type       | description                    |
++===================+================================+
+| RESPONSE          | RPC response                   |
+|                   |                                |
+|                   | .. code-block:: text           |
+|                   |                                |
+|                   |   - channel_id                 |
+|                   |   - service_id                 |
+|                   |   - method_id                  |
+|                   |   - payload                    |
+|                   |   - status                     |
+|                   |     (unless in server stream)  |
++-------------------+--------------------------------+
+| SERVER_STREAM_END | Server stream and RPC finished |
+|                   |                                |
+|                   | .. code-block:: text           |
+|                   |                                |
+|                   |   - channel_id                 |
+|                   |   - service_id                 |
+|                   |   - method_id                  |
+|                   |   - status                     |
++-------------------+--------------------------------+
+| SERVER_ERROR      | Received unexpected packet     |
+|                   |                                |
+|                   | .. code-block:: text           |
+|                   |                                |
+|                   |   - channel_id                 |
+|                   |   - service_id (if relevant)   |
+|                   |   - method_id (if relevant)    |
+|                   |   - status                     |
++-------------------+--------------------------------+
 
-All server packets contain the same ``call_id`` that was set in the initial
-request made by the client, if any.
-
-**Server errors**
+**Errors**
 
 The server sends ``SERVER_ERROR`` packets when it receives a packet it cannot
 process. The client should abort any RPC for which it receives an error. The
 status field indicates the type of error.
 
 * ``NOT_FOUND`` -- The requested service or method does not exist.
-* ``FAILED_PRECONDITION`` -- A client stream or cancel packet was sent for an
-  RPC that is not pending.
-* ``INVALID_ARGUMENT`` -- The client sent a packet type to an RPC that does not
-  support it (a ``CLIENT_STREAM`` was sent to an RPC with no client stream).
+* ``FAILED_PRECONDITION`` -- Attempted to cancel an RPC that is not pending.
 * ``RESOURCE_EXHAUSTED`` -- The request came on a new channel, but a channel
   could not be allocated for it.
-* ``ABORTED`` -- The RPC was aborted due its channel being closed.
 * ``INTERNAL`` -- The server was unable to respond to an RPC due to an
   unrecoverable internal error.
-* ``UNAVAILABLE`` -- Received a packet for an unknown channel.
 
 Inovking a service method
 -------------------------
@@ -654,79 +446,250 @@
 the protocol for calling service methods of each type: unary, server streaming,
 client streaming, and bidirectional streaming.
 
-The basic flow for all RPC invocations is as follows:
-
-  * Client sends a ``REQUEST`` packet. Includes a payload for unary & server
-    streaming RPCs.
-  * For client and bidirectional streaming RPCs, the client may send any number
-    of ``CLIENT_STREAM`` packets with payloads.
-  * For server and bidirectional streaming RPCs, the server may send any number
-    of ``SERVER_STREAM`` packets.
-  * The server sends a ``RESPONSE`` packet. Includes a payload for unary &
-    client streaming RPCs. The RPC is complete.
-
-The client may cancel an ongoing RPC at any time by sending a ``CLIENT_ERROR``
-packet with status ``CANCELLED``. The server may finish an ongoing RPC at any
-time by sending the ``RESPONSE`` packet.
-
 Unary RPC
 ^^^^^^^^^
 In a unary RPC, the client sends a single request and the server sends a single
 response.
 
-.. image:: unary_rpc.svg
+.. seqdiag::
+  :scale: 110
 
-The client may attempt to cancel a unary RPC by sending a ``CLIENT_ERROR``
-packet with status ``CANCELLED``. The server sends no response to a cancelled
-RPC. If the server processes the unary RPC synchronously (the handling thread
-sends the response), it may not be possible to cancel the RPC.
+  seqdiag {
+    default_note_color = aliceblue;
 
-.. image:: unary_rpc_cancelled.svg
+    client -> server [
+        label = "request",
+        leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID\npayload"
+    ];
+
+    client <- server [
+        label = "response",
+        rightnote = "PacketType.RESPONSE\nchannel ID\nservice ID\nmethod ID\npayload\nstatus"
+    ];
+  }
 
 Server streaming RPC
 ^^^^^^^^^^^^^^^^^^^^
 In a server streaming RPC, the client sends a single request and the server
-sends any number of ``SERVER_STREAM`` packets followed by a ``RESPONSE`` packet.
+sends any number of responses followed by a ``SERVER_STREAM_END`` packet.
 
-.. image:: server_streaming_rpc.svg
+.. seqdiag::
+  :scale: 110
 
-The client may terminate a server streaming RPC by sending a ``CLIENT_STREAM``
-packet with status ``CANCELLED``. The server sends no response.
+  seqdiag {
+    default_note_color = aliceblue;
 
-.. image:: server_streaming_rpc_cancelled.svg
+    client -> server [
+        label = "request",
+        leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID\npayload"
+    ];
+
+    client <-- server [
+        noactivate,
+        label = "responses (zero or more)",
+        rightnote = "PacketType.RESPONSE\nchannel ID\nservice ID\nmethod ID\npayload"
+    ];
+
+    client <- server [
+        label = "done",
+        rightnote = "PacketType.SERVER_STREAM_END\nchannel ID\nservice ID\nmethod ID\nstatus"
+    ];
+  }
+
+Server streaming RPCs may be cancelled by the client. The client sends a
+``CANCEL_SERVER_STREAM`` packet to terminate the RPC.
+
+.. seqdiag::
+  :scale: 110
+
+  seqdiag {
+    default_note_color = aliceblue;
+
+    client -> server [
+        label = "request",
+        leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID\npayload"
+    ];
+
+    client <-- server [
+        noactivate,
+        label = "responses (zero or more)",
+        rightnote = "PacketType.RESPONSE\nchannel ID\nservice ID\nmethod ID\npayload"
+    ];
+
+    client -> server [
+        noactivate,
+        label = "cancel",
+        leftnote  = "PacketType.CANCEL_SERVER_STREAM\nchannel ID\nservice ID\nmethod ID"
+    ];
+
+    client <- server [
+        label = "done",
+        rightnote = "PacketType.SERVER_STREAM_END\nchannel ID\nservice ID\nmethod ID\nstatus"
+    ];
+  }
 
 Client streaming RPC
 ^^^^^^^^^^^^^^^^^^^^
-In a client streaming RPC, the client starts the RPC by sending a ``REQUEST``
-packet with no payload. It then sends any number of messages in
-``CLIENT_STREAM`` packets, followed by a ``CLIENT_STREAM_END``. The server sends
-a single ``RESPONSE`` to finish the RPC.
+In a client streaming RPC, the client sends any number of RPC requests followed
+by a ``CLIENT_STREAM_END`` packet. The server then sends a single response.
 
-.. image:: client_streaming_rpc.svg
+The first client-to-server RPC packet does not include a payload.
 
-The server may finish the RPC at any time by sending its ``RESPONSE`` packet,
-even if it has not yet received the ``CLIENT_STREAM_END`` packet. The client may
-terminate the RPC at any time by sending a ``CLIENT_ERROR`` packet with status
-``CANCELLED``.
+.. attention::
 
-.. image:: client_streaming_rpc_cancelled.svg
+  ``pw_rpc`` does not yet support client streaming RPCs.
+
+.. seqdiag::
+  :scale: 110
+
+  seqdiag {
+    default_note_color = aliceblue;
+
+    client -> server [
+        label = "start",
+        leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID"
+    ];
+
+    client --> server [
+        noactivate,
+        label = "requests (zero or more)",
+        leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID\npayload"
+    ];
+
+    client -> server [
+        noactivate,
+        label = "done",
+        leftnote = "PacketType.CLIENT_STREAM_END\nchannel ID\nservice ID\nmethod ID"
+    ];
+
+    client <- server [
+        label = "response",
+        rightnote = "PacketType.RESPONSE\nchannel ID\nservice ID\nmethod ID\npayload\nstatus"
+    ];
+  }
+
+The server may terminate a client streaming RPC at any time by sending its
+response packet.
+
+.. seqdiag::
+  :scale: 110
+
+  seqdiag {
+    default_note_color = aliceblue;
+
+    client -> server [
+        label = "start",
+        leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID"
+    ];
+
+    client --> server [
+        noactivate,
+        label = "requests (zero or more)",
+        leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID\npayload"
+    ];
+
+    client <- server [
+        label = "response",
+        rightnote = "PacketType.RESPONSE\nchannel ID\nservice ID\nmethod ID\npayload\nstatus"
+    ];
+  }
 
 Bidirectional streaming RPC
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
 In a bidirectional streaming RPC, the client sends any number of requests and
-the server sends any number of responses. The client invokes the RPC by sending
-a ``REQUEST`` with no payload. It sends a ``CLIENT_STREAM_END`` packet when it
-has finished sending requests. The server sends a ``RESPONSE`` packet to finish
-the RPC.
+the server sends any number of responses. The client sends a
+``CLIENT_STREAM_END`` packet when it has finished sending requests. The server
+sends a ``SERVER_STREAM_END`` packet after it receives the client's
+``CLIENT_STREAM_END`` and finished sending its responses.
 
-.. image:: bidirectional_streaming_rpc.svg
+The first client-to-server RPC packet does not include a payload.
 
-The server may finish the RPC at any time by sending the ``RESPONSE`` packet,
-even if it has not received the ``CLIENT_STREAM_END`` packet. The client may
-terminate the RPC at any time by sending a ``CLIENT_ERROR`` packet with status
-``CANCELLED``.
+.. attention::
 
-.. image:: bidirectional_streaming_rpc_cancelled.svg
+  ``pw_rpc`` does not yet support bidirectional streaming RPCs.
+
+.. seqdiag::
+  :scale: 110
+
+  seqdiag {
+    default_note_color = aliceblue;
+
+    client -> server [
+        label = "start",
+        leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID"
+    ];
+
+    client --> server [
+        noactivate,
+        label = "requests (zero or more)",
+        leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID\npayload"
+    ];
+
+    ... (messages in any order) ...
+
+    client <-- server [
+        noactivate,
+        label = "responses (zero or more)",
+        rightnote = "PacketType.RESPONSE\nchannel ID\nservice ID\nmethod ID\npayload"
+    ];
+
+    client -> server [
+        noactivate,
+        label = "done",
+        leftnote = "PacketType.CLIENT_STREAM_END\nchannel ID\nservice ID\nmethod ID"
+    ];
+
+    client <-- server [
+        noactivate,
+        label = "responses (zero or more)",
+        rightnote = "PacketType.RPC\nchannel ID\nservice ID\nmethod ID\npayload"
+    ];
+
+    client <- server [
+        label = "done",
+        rightnote = "PacketType.SERVER_STREAM_END\nchannel ID\nservice ID\nmethod ID\nstatus"
+    ];
+  }
+
+The server may terminate the RPC at any time by sending a ``SERVER_STREAM_END``
+packet with the status, even if the client has not sent its ``STREAM_END``. The
+client may cancel the RPC at any time by sending a ``CANCEL_SERVER_STREAM``
+packet.
+
+.. seqdiag::
+  :scale: 110
+
+  seqdiag {
+    default_note_color = aliceblue;
+
+    client -> server [
+        label = "start",
+        leftnote = "PacketType.RPC\nchannel ID\nservice ID\nmethod ID"
+    ];
+
+    client --> server [
+        noactivate,
+        label = "requests (zero or more)",
+        leftnote = "PacketType.RPC\nchannel ID\nservice ID\nmethod ID\npayload"
+    ];
+
+    client <-- server [
+        noactivate,
+        label = "responses (zero or more)",
+        rightnote = "PacketType.RPC\nchannel ID\nservice ID\nmethod ID\npayload"
+    ];
+
+    client -> server [
+        noactivate,
+        label = "cancel",
+        leftnote = "PacketType.CANCEL_SERVER_STREAM\nchannel ID\nservice ID\nmethod ID"
+    ];
+
+    client <- server [
+        label = "done",
+        rightnote = "PacketType.STREAM_END\nchannel ID\nservice ID\nmethod ID\nstatus"
+    ];
+  }
 
 RPC server
 ==========
@@ -763,7 +726,7 @@
 function pointer to the user-defined method implementation. They also provide
 ``static constexpr`` functions for creating each type of method. ``Method``
 implementations must satisfy the ``MethodImplTester`` test class in
-``pw_rpc/internal/method_impl_tester.h``.
+``pw_rpc_private/method_impl_tester.h``.
 
 See ``pw_rpc/internal/method.h`` for more details about ``Method``.
 
@@ -773,12 +736,51 @@
 Requests
 ~~~~~~~~
 
-.. image:: request_packets.svg
+.. blockdiag::
+
+  blockdiag {
+    packets [shape = beginpoint];
+
+    group {
+      label = "pw_rpc library";
+
+      server [label = "Server"];
+      service [label = "Service"];
+      method [label = "internal::Method"];
+    }
+
+    stubs [label = "generated services", shape = ellipse];
+    user [label = "user-defined RPCs", shape = roundedbox];
+
+    packets -> server -> service -> method -> stubs -> user;
+    packets -> server [folded];
+    method -> stubs [folded];
+  }
 
 Responses
 ~~~~~~~~~
 
-.. image:: response_packets.svg
+.. blockdiag::
+
+  blockdiag {
+    user -> stubs [folded];
+
+    group {
+      label = "pw_rpc library";
+
+      server [label = "Server"];
+      method [label = "internal::Method"];
+      channel [label = "Channel"];
+    }
+
+    stubs [label = "generated services", shape = ellipse];
+    user [label = "user-defined RPCs", shape = roundedbox];
+    packets [shape = beginpoint];
+
+    user -> stubs -> method [folded];
+    method -> server -> channel;
+    channel -> packets [folded];
+  }
 
 RPC client
 ==========
@@ -832,52 +834,6 @@
   Use ``std::move`` when passing around ``ClientCall`` objects to keep RPCs
   alive.
 
-Example
-^^^^^^^
-.. code-block:: c++
-
-  #include "pw_rpc/echo_service_nanopb.h"
-
-  namespace {
-  // Generated clients are namespaced with their proto library.
-  using EchoClient = pw_rpc::nanopb::EchoService::Client;
-
-  // RPC channel ID on which to make client calls.
-  constexpr uint32_t kDefaultChannelId = 1;
-
-  EchoClient::EchoCall echo_call;
-
-  // Callback invoked when a response is received. This is called synchronously
-  // from Client::ProcessPacket.
-  void EchoResponse(const pw_rpc_EchoMessage& response,
-                    pw::Status status) {
-    if (status.ok()) {
-      PW_LOG_INFO("Received echo response: %s", response.msg);
-    } else {
-      PW_LOG_ERROR("Echo failed with status %d",
-                   static_cast<int>(status.code()));
-    }
-  }
-
-  }  // namespace
-
-  void CallEcho(const char* message) {
-    // Create a client to call the EchoService.
-    EchoClient echo_client(my_rpc_client, kDefaultChannelId);
-
-    pw_rpc_EchoMessage request = pw_rpc_EchoMessage_init_default;
-    pw::string::Copy(message, request.msg);
-
-    // By assigning the returned ClientCall to the global echo_call, the RPC
-    // call is kept alive until it completes. When a response is received, it
-    // will be logged by the handler function and the call will complete.
-    echo_call = echo_client.Echo(request, EchoResponse);
-    if (!echo_call.active()) {
-      // The RPC call was not sent. This could occur due to, for example, an
-      // invalid channel ID. Handle if necessary.
-    }
-  }
-
 Client implementation details
 -----------------------------
 
@@ -913,253 +869,5 @@
   void ProcessRpcData(pw::ConstByteSpan packet) {
     // Calls into both the client and the server, sending the packet to the
     // appropriate one.
-    client_server.ProcessPacket(packet);
+    client_server.ProcessPacket(packet, output);
   }
-
-Testing
-=======
-``pw_rpc`` provides utilities for unit testing RPC services and client calls.
-
-Client unit testing in C++
---------------------------
-``pw_rpc`` supports invoking RPCs, simulating server responses, and checking
-what packets are sent by an RPC client in tests. Both raw and Nanopb interfaces
-are supported. Code that uses the raw API may be tested with the Nanopb test
-helpers, and vice versa.
-
-To test code that invokes RPCs, declare a ``RawClientTestContext`` or
-``NanopbClientTestContext``. These test context objects provide a
-preconfigured RPC client, channel, server fake, and buffer for encoding packets.
-These test classes are defined in ``pw_rpc/raw/client_testing.h`` and
-``pw_rpc/nanopb/client_testing.h``.
-
-Use the context's ``client()`` and ``channel()`` to invoke RPCs. Use the
-context's ``server()`` to simulate responses. To verify that the client sent the
-expected data, use the context's ``output()``, which is a ``FakeChannelOutput``.
-
-For example, the following tests a class that invokes an RPC. It checks that
-the expected data was sent and then simulates a response from the server.
-
-.. code-block:: cpp
-
-  #include "pw_rpc/raw/client_testing.h"
-
-  class ThingThatCallsRpcs {
-   public:
-    // To support injecting an RPC client for testing, classes that make RPC
-    // calls should take an RPC client and channel ID or an RPC service client
-    // (e.g. pw_rpc::raw::MyService::Client).
-    ThingThatCallsRpcs(pw::rpc::Client& client, uint32_t channel_id);
-
-    void DoSomethingThatInvokesAnRpc();
-
-    bool SetToTrueWhenRpcCompletes();
-  };
-
-  TEST(TestAThing, InvokesRpcAndHandlesResponse) {
-    RawClientTestContext context;
-    ThingThatCallsRpcs thing(context.client(), context.channel().id());
-
-    // Execute the code that invokes the MyService.TheMethod RPC.
-    things.DoSomethingThatInvokesAnRpc();
-
-    // Find and verify the payloads sent for the MyService.TheMethod RPC.
-    auto msgs = context.output().payloads<pw_rpc::raw::MyService::TheMethod>();
-    ASSERT_EQ(msgs.size(), 1u);
-
-    VerifyThatTheExpectedMessageWasSent(msgs.back());
-
-    // Send the response packet from the server and verify that the class reacts
-    // accordingly.
-    EXPECT_FALSE(thing.SetToTrueWhenRpcCompletes());
-
-    context_.server().SendResponse<pw_rpc::raw::MyService::TheMethod>(
-        final_message, OkStatus());
-
-    EXPECT_TRUE(thing.SetToTrueWhenRpcCompletes());
-  }
-
-Integration testing with ``pw_rpc``
------------------------------------
-``pw_rpc`` provides utilities to simplify writing integration tests for systems
-that communicate with ``pw_rpc``. The integration test utitilies set up a socket
-to use for IPC between an RPC server and client process.
-
-The server binary uses the system RPC server facade defined
-``pw_rpc_system_server/rpc_server.h``. The client binary uses the functions
-defined in ``pw_rpc/integration_testing.h``:
-
-.. cpp:var:: constexpr uint32_t kChannelId
-
-  The RPC channel for integration test RPCs.
-
-.. cpp:function:: pw::rpc::Client& pw::rpc::integration_test::Client()
-
- Returns the global RPC client for integration test use.
-
-.. cpp:function:: pw::Status pw::rpc::integration_test::InitializeClient(int argc, char* argv[], const char* usage_args = "PORT")
-
-  Initializes logging and the global RPC client for integration testing. Starts
-  a background thread that processes incoming.
-
-Module Configuration Options
-============================
-The following configurations can be adjusted via compile-time configuration of
-this module, see the
-:ref:`module documentation <module-structure-compile-time-configuration>` for
-more details.
-
-.. c:macro:: PW_RPC_CLIENT_STREAM_END_CALLBACK
-
-  In client and bidirectional RPCs, pw_rpc clients may signal that they have
-  finished sending requests with a CLIENT_STREAM_END packet. While this can be
-  useful in some circumstances, it is often not necessary.
-
-  This option controls whether or not include a callback that is called when
-  the client stream ends. The callback is included in all ServerReader/Writer
-  objects as a pw::Function, so may have a significant cost.
-
-  This is disabled by default.
-
-.. c:macro:: PW_RPC_NANOPB_STRUCT_MIN_BUFFER_SIZE
-
-  The Nanopb-based pw_rpc implementation allocates memory to use for Nanopb
-  structs for the request and response protobufs. The template function that
-  allocates these structs rounds struct sizes up to this value so that
-  different structs can be allocated with the same function. Structs with sizes
-  larger than this value cause an extra function to be created, which slightly
-  increases code size.
-
-  Ideally, this value will be set to the size of the largest Nanopb struct used
-  as an RPC request or response. The buffer can be stack or globally allocated
-  (see ``PW_RPC_NANOPB_STRUCT_BUFFER_STACK_ALLOCATE``).
-
-  This defaults to 64 Bytes.
-
-.. c:macro:: PW_RPC_USE_GLOBAL_MUTEX
-
-  Enable global synchronization for RPC calls. If this is set, a backend must
-  be configured for pw_sync:mutex.
-
-  This is disabled by default.
-
-.. c:macro:: PW_RPC_DYNAMIC_ALLOCATION
-
-  Whether pw_rpc should use dynamic memory allocation internally. If enabled,
-  pw_rpc dynamically allocates channels and its encoding buffers. RPC users may
-  use dynamic allocation independently of this option (e.g. to allocate pw_rpc
-  call objects).
-
-  The semantics for allocating and initializing channels change depending on
-  this option. If dynamic allocation is disabled, pw_rpc endpoints (servers or
-  clients) use an externally-allocated, fixed-size array of channels.
-  That array must include unassigned channels or existing channels must be
-  closed to add new channels.
-
-  If dynamic allocation is enabled, an span of channels may be passed to the
-  endpoint at construction, but these channels are only used to initialize its
-  internal std::vector of channels. External channel objects are NOT used by
-  the endpoint cannot be updated if dynamic allocation is enabled. No
-  unassigned channels should be passed to the endpoint; they will be ignored.
-  Any number of channels may be added to the endpoint, without closing existing
-  channels, but adding channels will use more memory.
-
-.. c:macro:: PW_RPC_CONFIG_LOG_LEVEL
-
-  The log level to use for this module. Logs below this level are omitted.
-
-  This defaults to ``PW_LOG_LEVEL_INFO``.
-
-.. c:macro:: PW_RPC_CONFIG_LOG_MODULE_NAME
-
-  The log module name to use for this module.
-
-  This defaults to ``"PW_RPC"``.
-
-.. c:macro:: PW_RPC_NANOPB_STRUCT_BUFFER_STACK_ALLOCATE
-
-  This option determines whether to allocate the Nanopb structs on the stack or
-  in a global variable. Globally allocated structs are NOT thread safe, but
-  work fine when the RPC server's ProcessPacket function is only called from
-  one thread.
-
-  This is enabled by default.
-
-Sharing server and client code
-==============================
-Streaming RPCs support writing multiple requests or responses. To facilitate
-sharing code between servers and clients, ``pw_rpc`` provides the
-``pw::rpc::Writer`` interface. On the client side, a client or bidirectional
-streaming RPC call object (``ClientWriter`` or ``ClientReaderWriter``) can be
-used as a ``pw::rpc::Writer&``. On the server side, a server or bidirectional
-streaming RPC call object (``ServerWriter`` or ``ServerReaderWriter``) can be
-used as a ``pw::rpc::Writer&``.
-
-Zephyr
-======
-To enable ``pw_rpc.*`` for Zephyr add ``CONFIG_PIGWEED_RPC=y`` to the project's
-configuration. This will enable the Kconfig menu for the following:
-
-* ``pw_rpc.server`` which can be enabled via ``CONFIG_PIGWEED_RPC_SERVER=y``.
-* ``pw_rpc.client`` which can be enabled via ``CONFIG_PIGWEED_RPC_CLIENT=y``.
-* ``pw_rpc.client_server`` which can be enabled via
-  ``CONFIG_PIGWEED_RPC_CLIENT_SERVER=y``.
-* ``pw_rpc.common` which can be enabled via ``CONFIG_PIGWEED_RPC_COMMON=y``.
-
-Encoding and sending packets
-============================
-``pw_rpc`` has to manage interactions among multiple RPC clients, servers,
-client calls, and server calls. To safely synchronize these interactions with
-minimal overhead, ``pw_rpc`` uses a single, global mutex (when
-``PW_RPC_USE_GLOBAL_MUTEX`` is enabled).
-
-Because ``pw_rpc`` uses a global mutex, it also uses a global buffer to encode
-outgoing packets. The size of the buffer is set with
-``PW_RPC_ENCODING_BUFFER_SIZE``, which defaults to 512 B.
-
-Users of ``pw_rpc`` must implement the :cpp:class:`pw::rpc::ChannelOutput`
-interface.
-
-.. _module-pw_rpc-ChannelOutput:
-.. cpp:class:: pw::rpc::ChannelOutput
-
-  ``pw_rpc`` endpoints use :cpp:class:`ChannelOutput` instances to send packets.
-  Systems that integrate pw_rpc must use one or more :cpp:class:`ChannelOutput`
-  instances.
-
-  .. cpp:member:: static constexpr size_t kUnlimited = std::numeric_limits<size_t>::max()
-
-    Value returned from :cpp:func:`MaximumTransmissionUnit` to indicate an
-    unlimited MTU.
-
-  .. cpp:function:: virtual size_t MaximumTransmissionUnit()
-
-    Returns the size of the largest packet the :cpp:class:`ChannelOutput` can
-    send. :cpp:class:`ChannelOutput` implementations should only override this
-    function if they impose a limit on the MTU. The default implementation
-    returns :cpp:member:`kUnlimited`, which indicates that there is no MTU
-    limit.
-
-  .. cpp:function:: virtual pw::Status Send(std::span<std::byte> packet)
-
-    Sends an encoded RPC packet. Returns OK if further packets may be sent, even
-    if the current packet could not be sent. Returns any other status if the
-    Channel is no longer able to send packets.
-
-    The RPC system's internal lock is held while this function is called. Avoid
-    long-running operations, since these will delay any other users of the RPC
-    system.
-
-    .. danger::
-
-      No ``pw_rpc`` APIs may be accessed in this function! Implementations MUST
-      NOT access any RPC endpoints (:cpp:class:`pw::rpc::Client`,
-      :cpp:class:`pw::rpc::Server`) or call objects
-      (:cpp:class:`pw::rpc::ServerReaderWriter`,
-      :cpp:class:`pw::rpc::ClientReaderWriter`, etc.) inside the :cpp:func:`Send`
-      function or any descendent calls. Doing so will result in deadlock! RPC APIs
-      may be used by other threads, just not within :cpp:func:`Send`.
-
-      The buffer provided in ``packet`` must NOT be accessed outside of this
-      function. It must be sent immediately or copied elsewhere before the
-      function returns.
diff --git a/pw_rpc/echo.proto b/pw_rpc/echo.proto
index f36ebbb..aacb6c0 100644
--- a/pw_rpc/echo.proto
+++ b/pw_rpc/echo.proto
@@ -15,8 +15,6 @@
 
 package pw.rpc;
 
-option java_package = "dev.pigweed.pw_rpc.proto";
-
 service EchoService {
   rpc Echo(EchoMessage) returns (EchoMessage) {}
 }
diff --git a/pw_rpc/endpoint.cc b/pw_rpc/endpoint.cc
deleted file mode 100644
index c0e5079..0000000
--- a/pw_rpc/endpoint.cc
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// clang-format off
-#include "pw_rpc/internal/log_config.h"  // PW_LOG_* macros must be first.
-
-#include "pw_rpc/internal/endpoint.h"
-// clang-format on
-
-#include "pw_log/log.h"
-#include "pw_rpc/internal/lock.h"
-
-namespace pw::rpc::internal {
-
-RpcLock& rpc_lock() {
-  static RpcLock lock;
-  return lock;
-}
-
-Endpoint::~Endpoint() {
-  // Since the calls remove themselves from the Endpoint in
-  // CloseAndSendResponse(), close responders until no responders remain.
-  while (!calls_.empty()) {
-    calls_.front().CloseAndSendResponse(OkStatus()).IgnoreError();
-  }
-}
-
-Result<Packet> Endpoint::ProcessPacket(std::span<const std::byte> data,
-                                       Packet::Destination destination) {
-  Result<Packet> result = Packet::FromBuffer(data);
-
-  if (!result.ok()) {
-    PW_LOG_WARN("Failed to decode pw_rpc packet");
-    return Status::DataLoss();
-  }
-
-  Packet& packet = *result;
-
-  if (packet.channel_id() == Channel::kUnassignedChannelId ||
-      packet.service_id() == 0 || packet.method_id() == 0) {
-    PW_LOG_WARN("Received malformed pw_rpc packet");
-    return Status::DataLoss();
-  }
-
-  if (packet.destination() != destination) {
-    return Status::InvalidArgument();
-  }
-
-  return result;
-}
-
-void Endpoint::RegisterCall(Call& call) {
-  Call* const existing_call = FindCallById(
-      call.channel_id_locked(), call.service_id(), call.method_id());
-
-  RegisterUniqueCall(call);
-
-  if (existing_call != nullptr) {
-    // TODO(pwbug/597): Ensure call object is locked when calling callback. For
-    //     on_error, could potentially move the callback and call it after the
-    //     lock is released.
-    existing_call->HandleError(Status::Cancelled());
-    rpc_lock().lock();
-  }
-}
-
-Call* Endpoint::FindCallById(uint32_t channel_id,
-                             uint32_t service_id,
-                             uint32_t method_id) {
-  for (Call& call : calls_) {
-    if (channel_id == call.channel_id_locked() &&
-        service_id == call.service_id() && method_id == call.method_id()) {
-      return &call;
-    }
-  }
-  return nullptr;
-}
-
-Status Endpoint::CloseChannel(uint32_t channel_id) {
-  LockGuard lock(rpc_lock());
-
-  Channel* channel = channels_.Get(channel_id);
-  if (channel == nullptr) {
-    return Status::NotFound();
-  }
-  channel->Close();
-
-  // Close pending calls on the channel that's going away.
-  auto previous = calls_.before_begin();
-  auto current = calls_.begin();
-
-  while (current != calls_.end()) {
-    if (channel_id == current->channel_id_locked()) {
-      current->HandleChannelClose();
-      current = calls_.erase_after(previous);  // previous stays the same
-    } else {
-      previous = current;
-      ++current;
-    }
-  }
-
-  return OkStatus();
-}
-
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/fake_channel_output.cc b/pw_rpc/fake_channel_output.cc
deleted file mode 100644
index 530a03a..0000000
--- a/pw_rpc/fake_channel_output.cc
+++ /dev/null
@@ -1,128 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// clang-format off
-#include "pw_rpc/internal/log_config.h"  // PW_LOG_* macros must be first.
-
-#include "pw_rpc/internal/fake_channel_output.h"
-// clang-format on
-
-#include "pw_assert/check.h"
-#include "pw_log/log.h"
-#include "pw_result/result.h"
-#include "pw_rpc/internal/packet.h"
-
-namespace pw::rpc::internal::test {
-
-void FakeChannelOutput::clear() {
-  LockGuard lock(mutex_);
-  payloads_.clear();
-  packets_.clear();
-  send_status_ = OkStatus();
-  return_after_packet_count_ = -1;
-}
-
-Status FakeChannelOutput::HandlePacket(std::span<const std::byte> buffer) {
-  // If the buffer is empty, this is just releasing an unused buffer.
-  if (buffer.empty()) {
-    return OkStatus();
-  }
-
-  if (return_after_packet_count_ == 0) {
-    return send_status_;
-  }
-  if (return_after_packet_count_ > 0 &&
-      return_after_packet_count_ == static_cast<int>(packets_.size())) {
-    // Disable behavior.
-    return_after_packet_count_ = -1;
-    return send_status_;
-  }
-
-  Result<Packet> result = Packet::FromBuffer(buffer);
-  PW_CHECK_OK(result.status());
-
-  PW_CHECK(!packets_.full(),
-           "Attempted to store more than %u packets. Increase the kMaxPackets "
-           "template arg to store more packets.",
-           static_cast<unsigned>(packets_.size()));
-
-  packets_.push_back(*result);
-  Packet& packet = packets_.back();
-
-  CopyPayloadToBuffer(packet);
-
-  switch (packet.type()) {
-    case PacketType::REQUEST:
-      return OkStatus();
-    case PacketType::RESPONSE:
-      total_response_packets_ += 1;
-      return OkStatus();
-    case PacketType::CLIENT_STREAM:
-      return OkStatus();
-    case PacketType::DEPRECATED_SERVER_STREAM_END:
-      PW_CRASH("Deprecated PacketType %d", static_cast<int>(packet.type()));
-    case PacketType::CLIENT_ERROR:
-      PW_LOG_WARN("FakeChannelOutput received client error: %s",
-                  packet.status().str());
-      return OkStatus();
-    case PacketType::SERVER_ERROR:
-      PW_LOG_WARN("FakeChannelOutput received server error: %s",
-                  packet.status().str());
-      return OkStatus();
-    case PacketType::DEPRECATED_CANCEL:
-    case PacketType::SERVER_STREAM:
-    case PacketType::CLIENT_STREAM_END:
-      return OkStatus();
-  }
-  PW_CRASH("Unhandled PacketType %d", static_cast<int>(result.value().type()));
-}
-
-void FakeChannelOutput::CopyPayloadToBuffer(Packet& packet) {
-  const ConstByteSpan& payload = packet.payload();
-  if (payload.empty()) {
-    return;
-  }
-
-  const size_t available_bytes = payloads_.max_size() - payloads_.size();
-  PW_CHECK_UINT_GE(available_bytes,
-                   payload.size(),
-                   "Ran out of payload buffer space. Increase "
-                   "kPayloadBufferSizeBytes (%u) or use smaller payloads.",
-                   static_cast<unsigned>(payloads_.max_size()));
-
-  const size_t start = payloads_.size();
-  payloads_.resize(payloads_.size() + payload.size());
-  std::memcpy(&payloads_[start], payload.data(), payload.size());
-  packet.set_payload(std::span(&payloads_[start], payload.size()));
-}
-
-void FakeChannelOutput::LogPackets() const {
-  LockGuard lock(mutex_);
-
-  PW_LOG_INFO("%u packets have been sent through this FakeChannelOutput",
-              static_cast<unsigned>(packets_.size()));
-
-  for (unsigned i = 0; i < packets_.size(); ++i) {
-    PW_LOG_INFO("[packet %u/%u]", i + 1, unsigned(packets_.size()));
-    PW_LOG_INFO("        type: %u", unsigned(packets_[i].type()));
-    PW_LOG_INFO("  channel_id: %u", unsigned(packets_[i].channel_id()));
-    PW_LOG_INFO("  service_id: %08x", unsigned(packets_[i].service_id()));
-    PW_LOG_INFO("   method_id: %08x", unsigned(packets_[i].method_id()));
-    PW_LOG_INFO("     call_id: %u", unsigned(packets_[i].call_id()));
-    PW_LOG_INFO("      status: %s", packets_[i].status().str());
-    PW_LOG_INFO("     payload: %u B", unsigned(packets_[i].payload().size()));
-  }
-}
-
-}  // namespace pw::rpc::internal::test
diff --git a/pw_rpc/fake_channel_output_test.cc b/pw_rpc/fake_channel_output_test.cc
deleted file mode 100644
index 202be7e..0000000
--- a/pw_rpc/fake_channel_output_test.cc
+++ /dev/null
@@ -1,208 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/internal/fake_channel_output.h"
-
-#include <array>
-#include <cstddef>
-#include <memory>
-
-#include "gtest/gtest.h"
-#include "pw_rpc/internal/channel.h"
-#include "pw_rpc/internal/lock.h"
-#include "pw_rpc/internal/packet.h"
-
-namespace pw::rpc::internal::test {
-namespace {
-
-constexpr uint32_t kChannelId = 1;
-constexpr uint32_t kServiceId = 1;
-constexpr uint32_t kMethodId = 1;
-constexpr uint32_t kCallId = 0;
-constexpr std::array<std::byte, 3> kPayload = {
-    std::byte(1), std::byte(2), std::byte(3)};
-
-class TestFakeChannelOutput final : public FakeChannelOutputBuffer<9, 128> {
- public:
-  TestFakeChannelOutput() = default;
-
-  const ConstByteSpan& last_response(MethodType type) {
-    return payloads(type, kChannelId, kServiceId, kMethodId).back();
-  }
-
-  size_t total_payloads(MethodType type) {
-    return payloads(type, kChannelId, kServiceId, kMethodId).size();
-  }
-};
-
-TEST(FakeChannelOutput, SendAndClear) {
-  constexpr MethodType type = MethodType::kServerStreaming;
-  TestFakeChannelOutput output;
-  Channel channel(kChannelId, &output);
-  const internal::Packet server_stream_packet(PacketType::SERVER_STREAM,
-                                              kChannelId,
-                                              kServiceId,
-                                              kMethodId,
-                                              kCallId,
-                                              kPayload);
-  LockGuard lock(rpc_lock());
-  ASSERT_EQ(channel.Send(server_stream_packet), OkStatus());
-  ASSERT_EQ(output.last_response(type).size(), kPayload.size());
-  EXPECT_EQ(
-      std::memcmp(
-          output.last_response(type).data(), kPayload.data(), kPayload.size()),
-      0);
-  EXPECT_EQ(output.total_payloads(type), 1u);
-  EXPECT_EQ(output.total_packets(), 1u);
-  EXPECT_FALSE(output.done());
-
-  output.clear();
-  EXPECT_EQ(output.total_payloads(type), 0u);
-  EXPECT_EQ(output.total_packets(), 0u);
-  EXPECT_FALSE(output.done());
-}
-
-TEST(FakeChannelOutput, SendAndFakeFutureResults) {
-  constexpr MethodType type = MethodType::kUnary;
-  TestFakeChannelOutput output;
-  Channel channel(kChannelId, &output);
-  const internal::Packet response_packet(PacketType::RESPONSE,
-                                         kChannelId,
-                                         kServiceId,
-                                         kMethodId,
-                                         kCallId,
-                                         kPayload);
-  LockGuard lock(rpc_lock());
-  EXPECT_EQ(channel.Send(response_packet), OkStatus());
-  EXPECT_EQ(output.total_payloads(type), 1u);
-  EXPECT_EQ(output.total_packets(), 1u);
-  EXPECT_TRUE(output.done());
-
-  // Multiple calls will return the same error status.
-  output.set_send_status(Status::Unknown());
-  EXPECT_EQ(channel.Send(response_packet), Status::Unknown());
-  EXPECT_EQ(channel.Send(response_packet), Status::Unknown());
-  EXPECT_EQ(channel.Send(response_packet), Status::Unknown());
-  EXPECT_EQ(output.total_payloads(type), 1u);
-  EXPECT_EQ(output.total_packets(), 1u);
-
-  // Turn off error status behavior.
-  output.set_send_status(OkStatus());
-  EXPECT_EQ(channel.Send(response_packet), OkStatus());
-  EXPECT_EQ(output.total_payloads(type), 2u);
-  EXPECT_EQ(output.total_packets(), 2u);
-
-  const internal::Packet server_stream_packet(PacketType::SERVER_STREAM,
-                                              kChannelId,
-                                              kServiceId,
-                                              kMethodId,
-                                              kCallId,
-                                              kPayload);
-  EXPECT_EQ(channel.Send(server_stream_packet), OkStatus());
-  ASSERT_EQ(output.last_response(type).size(), kPayload.size());
-  EXPECT_EQ(
-      std::memcmp(
-          output.last_response(type).data(), kPayload.data(), kPayload.size()),
-      0);
-  EXPECT_EQ(output.total_payloads(type), 2u);
-  EXPECT_EQ(output.total_packets(), 3u);
-  EXPECT_TRUE(output.done());
-}
-
-TEST(FakeChannelOutput, SendAndFakeSingleResult) {
-  constexpr MethodType type = MethodType::kUnary;
-  TestFakeChannelOutput output;
-  Channel channel(kChannelId, &output);
-  const internal::Packet response_packet(PacketType::RESPONSE,
-                                         kChannelId,
-                                         kServiceId,
-                                         kMethodId,
-                                         kCallId,
-                                         kPayload);
-  // Multiple calls will return the same error status.
-  const int packet_count_fail = 4;
-  output.set_send_status(Status::Unknown(), packet_count_fail);
-  LockGuard lock(rpc_lock());
-
-  for (int i = 0; i < packet_count_fail; ++i) {
-    EXPECT_EQ(channel.Send(response_packet), OkStatus());
-  }
-  EXPECT_EQ(channel.Send(response_packet), Status::Unknown());
-  for (int i = 0; i < packet_count_fail; ++i) {
-    EXPECT_EQ(channel.Send(response_packet), OkStatus());
-  }
-
-  const size_t total_response_packets =
-      static_cast<size_t>(2 * packet_count_fail);
-  EXPECT_EQ(output.total_payloads(type), total_response_packets);
-  EXPECT_EQ(output.total_packets(), total_response_packets);
-
-  // Turn off error status behavior.
-  output.set_send_status(OkStatus());
-  EXPECT_EQ(channel.Send(response_packet), OkStatus());
-  EXPECT_EQ(output.total_payloads(type), total_response_packets + 1);
-  EXPECT_EQ(output.total_packets(), total_response_packets + 1);
-}
-
-TEST(FakeChannelOutput, SendResponseUpdated) {
-  TestFakeChannelOutput output;
-  Channel channel(kChannelId, &output);
-  const internal::Packet response_packet(PacketType::RESPONSE,
-                                         kChannelId,
-                                         kServiceId,
-                                         kMethodId,
-                                         kCallId,
-                                         kPayload);
-  LockGuard lock(rpc_lock());
-  ASSERT_EQ(channel.Send(response_packet), OkStatus());
-  ASSERT_EQ(output.last_response(MethodType::kUnary).size(), kPayload.size());
-  EXPECT_EQ(std::memcmp(output.last_response(MethodType::kUnary).data(),
-                        kPayload.data(),
-                        kPayload.size()),
-            0);
-  EXPECT_EQ(output.total_payloads(MethodType::kUnary), 1u);
-  EXPECT_EQ(output.total_packets(), 1u);
-  EXPECT_TRUE(output.done());
-
-  output.clear();
-  const internal::Packet packet_empty_payload(
-      PacketType::RESPONSE, kChannelId, kServiceId, kMethodId, kCallId, {});
-  EXPECT_EQ(channel.Send(packet_empty_payload), OkStatus());
-  EXPECT_EQ(output.last_response(MethodType::kUnary).size(), 0u);
-  EXPECT_EQ(output.total_payloads(MethodType::kUnary), 1u);
-  EXPECT_EQ(output.total_packets(), 1u);
-  EXPECT_TRUE(output.done());
-
-  const internal::Packet server_stream_packet(PacketType::SERVER_STREAM,
-                                              kChannelId,
-                                              kServiceId,
-                                              kMethodId,
-                                              kCallId,
-                                              kPayload);
-  ASSERT_EQ(channel.Send(server_stream_packet), OkStatus());
-  ASSERT_EQ(output.total_payloads(MethodType::kServerStreaming), 1u);
-  ASSERT_EQ(output.last_response(MethodType::kServerStreaming).size(),
-            kPayload.size());
-  EXPECT_EQ(
-      std::memcmp(output.last_response(MethodType::kServerStreaming).data(),
-                  kPayload.data(),
-                  kPayload.size()),
-      0);
-  EXPECT_EQ(output.total_payloads(MethodType::kServerStreaming), 1u);
-  EXPECT_EQ(output.total_packets(), 2u);
-  EXPECT_TRUE(output.done());
-}
-
-}  // namespace
-}  // namespace pw::rpc::internal::test
diff --git a/pw_rpc/integration_testing.cc b/pw_rpc/integration_testing.cc
deleted file mode 100644
index 4212d4b..0000000
--- a/pw_rpc/integration_testing.cc
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/integration_testing.h"
-
-#include <limits>
-
-#include "gtest/gtest.h"
-#include "pw_log/log.h"
-#include "pw_rpc/integration_test_socket_client.h"
-#include "pw_unit_test/logging_event_handler.h"
-
-namespace pw::rpc::integration_test {
-namespace {
-
-SocketClientContext<512> context;
-unit_test::LoggingEventHandler log_test_events;
-
-}  // namespace
-
-Client& client() { return context.client(); }
-
-Status InitializeClient(int argc, char* argv[], const char* usage_args) {
-  unit_test::RegisterEventHandler(&log_test_events);
-
-  if (argc < 2) {
-    PW_LOG_INFO("Usage: %s %s", argv[0], usage_args);
-    return Status::InvalidArgument();
-  }
-
-  const int port = std::atoi(argv[1]);
-
-  if (port <= 0 || port > std::numeric_limits<uint16_t>::max()) {
-    PW_LOG_CRITICAL("Port numbers must be between 1 and 65535; %d is invalid",
-                    port);
-    return Status::InvalidArgument();
-  }
-
-  PW_LOG_INFO("Connecting to pw_rpc client at localhost:%d", port);
-  return context.Start(port);
-}
-
-}  // namespace pw::rpc::integration_test
diff --git a/pw_rpc/internal/integration_test_ports.gni b/pw_rpc/internal/integration_test_ports.gni
deleted file mode 100644
index 0e4436e..0000000
--- a/pw_rpc/internal/integration_test_ports.gni
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-# List all ports used by Pigweed RPC integration tests. These ports are listed
-# in one place to prevent accidental conflicts between tests.
-pw_rpc_PYTHON_CLIENT_CPP_SERVER_TEST_PORT = 30576
-pw_rpc_CPP_CLIENT_INTEGRATION_TEST_PORT = 30577
-pw_transfer_PYTHON_CPP_TRANSFER_TEST_PORT = 30578
-pw_transfer_CPP_CPP_TRANSFER_TEST_PORT = 30579
-pw_unit_test_RPC_SERVICE_TEST_PORT = 30580
diff --git a/pw_rpc/internal/packet.proto b/pw_rpc/internal/packet.proto
index 2862eb4..459ac94 100644
--- a/pw_rpc/internal/packet.proto
+++ b/pw_rpc/internal/packet.proto
@@ -15,7 +15,7 @@
 
 package pw.rpc.internal;
 
-option java_package = "dev.pigweed.pw_rpc.internal";
+option java_package = "dev.pigweed.pw.rpc.internal";
 
 enum PacketType {
   // To simplify identifying the origin of a packet, client-to-server packets
@@ -23,38 +23,28 @@
 
   // Client-to-server packets
 
-  // The client invokes an RPC. Always the first packet.
+  // A request from a client for a service method.
   REQUEST = 0;
 
-  // A message in a client stream. Always sent after a REQUEST and before a
-  // CLIENT_STREAM_END.
-  CLIENT_STREAM = 2;
+  // A client stream has completed.
+  CLIENT_STREAM_END = 2;
 
   // The client received a packet for an RPC it did not request.
   CLIENT_ERROR = 4;
 
-  // Deprecated, do not use. Send a CLIENT_ERROR with status CANCELLED instead.
-  // TODO(pwbug/512): Remove this packet type.
-  DEPRECATED_CANCEL = 6;
-
-  // A client stream has completed.
-  CLIENT_STREAM_END = 8;
+  // The client requests cancellation of an ongoing server stream.
+  CANCEL_SERVER_STREAM = 6;
 
   // Server-to-client packets
 
-  // The RPC has finished.
+  // A response from a server for a service method.
   RESPONSE = 1;
 
-  // Deprecated, do not use. Formerly was used as the last packet in a server
-  // stream.
-  // TODO(pwbug/512): Remove this packet type.
-  DEPRECATED_SERVER_STREAM_END = 3;
+  // A server streaming or bidirectional RPC has completed.
+  SERVER_STREAM_END = 3;
 
   // The server was unable to process a request.
   SERVER_ERROR = 5;
-
-  // A message in a server stream.
-  SERVER_STREAM = 7;
 }
 
 message RpcPacket {
@@ -76,9 +66,4 @@
 
   // Status code for the RPC response or error.
   uint32 status = 6;
-
-  // Unique identifier for the call that initiated this RPC. Optionally set by
-  // the client in the initial request and sent in all subsequent client
-  // packets; echoed by the server.
-  uint32 call_id = 7;
 }
diff --git a/pw_rpc/java/main/dev/pigweed/pw_rpc/BUILD.bazel b/pw_rpc/java/main/dev/pigweed/pw_rpc/BUILD.bazel
deleted file mode 100644
index 4747b7f..0000000
--- a/pw_rpc/java/main/dev/pigweed/pw_rpc/BUILD.bazel
+++ /dev/null
@@ -1,46 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-# Generic client for pw_rpc, Pigweed's RPC system.
-
-java_library(
-    name = "client",
-    srcs = [
-        "Call.java",
-        "Channel.java",
-        "ChannelOutputException.java",
-        "Client.java",
-        "Ids.java",
-        "Method.java",
-        "MethodClient.java",
-        "Packets.java",
-        "PendingRpc.java",
-        "RpcError.java",
-        "RpcManager.java",
-        "Service.java",
-        "Status.java",
-        "StreamObserver.java",
-        "StreamObserverCall.java",
-        "UnaryResult.java",
-    ],
-    visibility = ["//visibility:public"],
-    deps = [
-        "//pw_log/java/main/dev/pigweed/pw_log",
-        "//pw_rpc:packet_proto_java_lite",
-        "//third_party/google_auto:value",
-        "@com_google_protobuf//java/lite",
-        "@maven//:com_google_code_findbugs_jsr305",
-        "@maven//:com_google_guava_guava",
-    ],
-)
diff --git a/pw_rpc/java/main/dev/pigweed/pw_rpc/Call.java b/pw_rpc/java/main/dev/pigweed/pw_rpc/Call.java
deleted file mode 100644
index 364ec96..0000000
--- a/pw_rpc/java/main/dev/pigweed/pw_rpc/Call.java
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 dev.pigweed.pw_rpc;
-
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.protobuf.MessageLite;
-import javax.annotation.Nullable;
-
-/** Represents an ongoing RPC call. */
-public interface Call {
-  /** Cancels the RPC. Sends a cancellation packet to the server and sets error() to CANCELLED. */
-  void cancel() throws ChannelOutputException;
-
-  /** True if the RPC has not yet completed. */
-  default boolean active() {
-    return status() == null && error() == null;
-  }
-
-  /** The RPC status, if the RPC completed. */
-  @Nullable Status status();
-
-  /** The error that terminated this RPC, if any. */
-  @Nullable Status error();
-
-  /** Represents a call to a unary or client streaming RPC that uses a future. */
-  @SuppressWarnings("ShouldNotSubclass")
-  interface UnaryFuture<ResponseT extends MessageLite>
-      extends Call, ListenableFuture<UnaryResult<ResponseT>> {}
-
-  /** Represents a call to a server or bidirectional streaming RPC that uses a future. */
-  @SuppressWarnings("ShouldNotSubclass")
-  interface ServerStreamingFuture extends Call, ListenableFuture<Status> {}
-
-  /** Represents a call to a client or bidirectional streaming RPC. */
-  interface ClientStreaming<RequestT extends MessageLite> extends Call {
-    /**
-     * Sends a request to a pending client streaming RPC.
-     *
-     * <p>The semantics of send() reflect the Channel.Output implementation for this channel.
-     *
-     * @throws ChannelOutputException the Channel.Output was unable to send the request
-     * @throws RpcError the RPC is not currently active
-     */
-    void send(RequestT request) throws ChannelOutputException, RpcError;
-
-    /** Signals to the server that the client stream has completed. */
-    void finish() throws ChannelOutputException;
-  }
-
-  /** Represents a call to a client streaming RPC that uses a future. */
-  interface ClientStreamingFuture<RequestT extends MessageLite, ResponseT extends MessageLite>
-      extends ClientStreaming<RequestT>, UnaryFuture<ResponseT> {}
-
-  /** Represents a call to a bidirectional streaming RPC that uses a future. */
-  interface BidirectionalStreamingFuture<RequestT extends MessageLite>
-      extends ClientStreaming<RequestT>, ServerStreamingFuture {}
-}
diff --git a/pw_rpc/java/main/dev/pigweed/pw_rpc/Channel.java b/pw_rpc/java/main/dev/pigweed/pw_rpc/Channel.java
deleted file mode 100644
index 6cb224d..0000000
--- a/pw_rpc/java/main/dev/pigweed/pw_rpc/Channel.java
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 dev.pigweed.pw_rpc;
-
-/**
- * Represents an RPC communications channel. Each endpoint must connect to the server with a unique
- * channel ID.
- *
- * <p>The client sends outgoing packets through a Channel object. Incoming packets must match a
- * known channel ID.
- */
-public class Channel {
-  /**
-   * Writes a single packet to the output.
-   *
-   * <p>The Channel.Output implementation for the each channel determines the semantics for all
-   * client functions that send packets. For example, if there is congestion, the Channel.Output may
-   * block, enqueue the response and return immediately, or throw an exception that signals that the
-   * user should retry. Deployments should keep this in mind when implementing Channel.Outputs and
-   * interacting with RPCs.
-   */
-  public interface Output {
-    void send(byte[] packet) throws ChannelOutputException;
-  }
-
-  private final int id;
-  private final Output output;
-
-  public Channel(int id, Output output) {
-    this.id = id;
-    this.output = output;
-  }
-
-  public int id() {
-    return id;
-  }
-
-  public void send(byte[] data) throws ChannelOutputException {
-    output.send(data);
-  }
-}
diff --git a/pw_rpc/java/main/dev/pigweed/pw_rpc/ChannelOutputException.java b/pw_rpc/java/main/dev/pigweed/pw_rpc/ChannelOutputException.java
deleted file mode 100644
index 74106ee..0000000
--- a/pw_rpc/java/main/dev/pigweed/pw_rpc/ChannelOutputException.java
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 dev.pigweed.pw_rpc;
-
-/** Signals that a Channel.Output failed to send a packet. */
-public class ChannelOutputException extends Exception {
-  public ChannelOutputException() {}
-
-  public ChannelOutputException(String message) {
-    super(message);
-  }
-
-  public ChannelOutputException(String message, Throwable cause) {
-    super(message, cause);
-  }
-
-  public ChannelOutputException(Throwable cause) {
-    super(cause);
-  }
-}
diff --git a/pw_rpc/java/main/dev/pigweed/pw_rpc/Client.java b/pw_rpc/java/main/dev/pigweed/pw_rpc/Client.java
deleted file mode 100644
index a62d77d..0000000
--- a/pw_rpc/java/main/dev/pigweed/pw_rpc/Client.java
+++ /dev/null
@@ -1,272 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 dev.pigweed.pw_rpc;
-
-import com.google.protobuf.ExtensionRegistryLite;
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.MessageLite;
-import dev.pigweed.pw_log.Logger;
-import dev.pigweed.pw_rpc.internal.Packet.PacketType;
-import dev.pigweed.pw_rpc.internal.Packet.RpcPacket;
-import java.nio.ByteBuffer;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-import javax.annotation.Nullable;
-
-/**
- * A client for a pw_rpc server. Invokes RPCs through a MethodClient and handles RPC responses
- * through the processPacket function.
- */
-public class Client {
-  private static final Logger logger = Logger.forClass(Client.class);
-
-  private final Map<Integer, Channel> channels;
-  private final Map<Integer, Service> services;
-
-  private final Map<PendingRpc, MethodClient> methodClients;
-  private final RpcManager rpcs;
-
-  private final Function<PendingRpc, StreamObserver<MessageLite>> defaultObserverFactory;
-
-  /**
-   * Creates a new RPC client.
-   *
-   * @param channels supported channels, which are used to send requests to the server
-   * @param services which RPC services this client supports; used to handle encoding and decoding
-   */
-  private Client(List<Channel> channels,
-      List<Service> services,
-      Function<PendingRpc, StreamObserver<MessageLite>> defaultObserverFactory) {
-    this.channels = channels.stream().collect(Collectors.toMap(Channel::id, c -> c));
-    this.services = services.stream().collect(Collectors.toMap(Service::id, s -> s));
-
-    this.methodClients = new HashMap<>();
-    this.rpcs = new RpcManager();
-
-    this.defaultObserverFactory = defaultObserverFactory;
-  }
-
-  /**
-   * Creates a new pw_rpc client.
-   *
-   * @param channels the set of channels for the client to send requests over
-   * @param services the services to support on this client
-   * @param defaultObserverFactory function that creates a default observer for each RPC
-   * @return the new pw.rpc.Client
-   */
-  public static Client create(List<Channel> channels,
-      List<Service> services,
-      Function<PendingRpc, StreamObserver<MessageLite>> defaultObserverFactory) {
-    return new Client(channels, services, defaultObserverFactory);
-  }
-
-  /** Creates a new pw_rpc client that logs responses when no observer is provided to calls. */
-  public static Client create(List<Channel> channels, List<Service> services) {
-    return create(channels, services, (rpc) -> new StreamObserver<MessageLite>() {
-      @Override
-      public void onNext(MessageLite value) {
-        logger.atFine().log("%s received response: %s", rpc, value);
-      }
-
-      @Override
-      public void onCompleted(Status status) {
-        logger.atInfo().log("%s completed with status %s", rpc, status);
-      }
-
-      @Override
-      public void onError(Status status) {
-        logger.atWarning().log("%s terminated with error %s", rpc, status);
-      }
-    });
-  }
-
-  /**
-   * Returns a MethodClient with the given name for the provided channelID
-   *
-   * @param channelId the ID for the channel through which to invoke the RPC
-   * @param fullMethodName the method name as "package.Service.Method" or "package.Service/Method"
-   */
-  public MethodClient method(int channelId, String fullMethodName) {
-    for (char delimiter : new char[] {'/', '.'}) {
-      int index = fullMethodName.lastIndexOf(delimiter);
-      if (index != -1) {
-        return method(
-            channelId, fullMethodName.substring(0, index), fullMethodName.substring(index + 1));
-      }
-    }
-    throw new IllegalArgumentException("Invalid method name '" + fullMethodName
-        + "'; does not match required package.Service/Method format");
-  }
-
-  /**
-   * Returns a MethodClient on the provided channel using separate arguments for "package.Service"
-   * and "Method".
-   */
-  public MethodClient method(int channelId, String fullServiceName, String methodName) {
-    try {
-      return method(channelId, Ids.calculate(fullServiceName), Ids.calculate(methodName));
-    } catch (IllegalArgumentException e) {
-      // Rethrow the exception with the service and method name instead of the ID.
-      throw new IllegalArgumentException("Unknown RPC " + fullServiceName + '/' + methodName, e);
-    }
-  }
-
-  /** Returns a MethodClient with the provided service and method IDs. */
-  public MethodClient method(int channelId, int serviceId, int methodId) {
-    Channel channel = channels.get(channelId);
-    if (channel == null) {
-      throw new IllegalArgumentException("Unknown channel ID " + channelId);
-    }
-
-    Service service = services.get(serviceId);
-    if (service == null) {
-      throw new IllegalArgumentException("Unknown service ID " + serviceId);
-    }
-
-    Method method = service.methods().get(methodId);
-    if (method == null) {
-      throw new IllegalArgumentException("Unknown method ID " + methodId);
-    }
-
-    PendingRpc rpc = PendingRpc.create(channel, service, method);
-    if (!methodClients.containsKey(rpc)) {
-      methodClients.put(rpc, new MethodClient(rpcs, rpc, defaultObserverFactory.apply(rpc)));
-    }
-    return methodClients.get(rpc);
-  }
-
-  /**
-   * Processes a single RPC packet.
-   *
-   * @param data a single, binary encoded RPC packet
-   * @return true if the packet was decoded and processed by this client; returns false for invalid
-   *     packets or packets for a server or unrecognized channel
-   */
-  public boolean processPacket(byte[] data) {
-    return processPacket(ByteBuffer.wrap(data));
-  }
-
-  public boolean processPacket(ByteBuffer data) {
-    RpcPacket packet;
-    try {
-      packet = RpcPacket.parseFrom(data, ExtensionRegistryLite.getEmptyRegistry());
-    } catch (InvalidProtocolBufferException e) {
-      logger.atWarning().withCause(e).log("Failed to decode packet");
-      return false;
-    }
-
-    if (packet.getChannelId() == 0 || packet.getServiceId() == 0 || packet.getMethodId() == 0) {
-      logger.atWarning().log("Received corrupt packet with unset IDs");
-      return false;
-    }
-
-    // Packets for the server use even type values.
-    if (packet.getTypeValue() % 2 == 0) {
-      logger.atFine().log("Ignoring %s packet for server", packet.getType().name());
-      return false;
-    }
-
-    Channel channel = channels.get(packet.getChannelId());
-    if (channel == null) {
-      logger.atWarning().log("Received packet for unrecognized channel %d", packet.getChannelId());
-      return false;
-    }
-
-    PendingRpc rpc = lookupRpc(channel, packet);
-    if (rpc == null) {
-      logger.atInfo().log("Ignoring packet for unknown service method");
-      sendError(channel, packet, Status.NOT_FOUND);
-      return true; // true since the packet was handled, even though it was invalid.
-    }
-
-    // Any packet type other than SERVER_STREAM indicates that this is the last packet for this RPC.
-    StreamObserverCall<?, ?> call =
-        packet.getType().equals(PacketType.SERVER_STREAM) ? rpcs.getPending(rpc) : rpcs.clear(rpc);
-    if (call == null) {
-      logger.atFine().log(
-          "Ignoring packet for %s, which isn't pending. Pending RPCs are %s", rpc, rpcs);
-      sendError(channel, packet, Status.FAILED_PRECONDITION);
-      return true;
-    }
-
-    switch (packet.getType()) {
-      case SERVER_ERROR: {
-        Status status = decodeStatus(packet);
-        logger.atWarning().log("%s failed with error %s", rpc, status);
-        call.onError(status);
-        break;
-      }
-      case RESPONSE: {
-        Status status = decodeStatus(packet);
-        // Server streaming an unary RPCs include a payload with their response packet.
-        if (!rpc.method().isServerStreaming()) {
-          logger.atFiner().log("%s completed with status %s and %d B payload",
-              rpc,
-              status,
-              packet.getPayload().size());
-          call.onNext(packet.getPayload());
-        } else {
-          logger.atFiner().log("%s completed with status %s", rpc, status);
-        }
-        call.onCompleted(status);
-        break;
-      }
-      case SERVER_STREAM:
-        logger.atFiner().log(
-            "%s received server stream with %d B payload", rpc, packet.getPayload().size());
-        call.onNext(packet.getPayload());
-        break;
-      default:
-        logger.atWarning().log(
-            "%s received unexpected PacketType %d", rpc, packet.getType().getNumber());
-    }
-
-    return true;
-  }
-
-  private static void sendError(Channel channel, RpcPacket packet, Status status) {
-    try {
-      channel.send(Packets.error(packet, status));
-    } catch (ChannelOutputException e) {
-      logger.atWarning().withCause(e).log("Failed to send error packet");
-    }
-  }
-
-  @Nullable
-  private PendingRpc lookupRpc(Channel channel, RpcPacket packet) {
-    Service service = services.get(packet.getServiceId());
-    if (service != null) {
-      Method method = service.methods().get(packet.getMethodId());
-      if (method != null) {
-        return PendingRpc.create(channel, service, method);
-      }
-    }
-
-    return null;
-  }
-
-  private static Status decodeStatus(RpcPacket packet) {
-    Status status = Status.fromCode(packet.getStatus());
-    if (status == null) {
-      logger.atWarning().log(
-          "Illegal status code %d in packet; using Status.UNKNOWN ", packet.getStatus());
-      return Status.UNKNOWN;
-    }
-    return status;
-  }
-}
diff --git a/pw_rpc/java/main/dev/pigweed/pw_rpc/Ids.java b/pw_rpc/java/main/dev/pigweed/pw_rpc/Ids.java
deleted file mode 100644
index 34afd2f..0000000
--- a/pw_rpc/java/main/dev/pigweed/pw_rpc/Ids.java
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 dev.pigweed.pw_rpc;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-/** Calculates the service and method IDs. */
-/* package */ class Ids {
-  private static final int HASH_CONSTANT = 65599;
-
-  private Ids() {}
-
-  public static int calculate(byte[] string) {
-    int hash = string.length;
-    int coefficient = HASH_CONSTANT;
-
-    for (byte b : string) {
-      hash += coefficient * b;
-      coefficient *= HASH_CONSTANT;
-    }
-
-    return hash;
-  }
-
-  public static int calculate(String string) {
-    return calculate(string.getBytes(UTF_8));
-  }
-}
diff --git a/pw_rpc/java/main/dev/pigweed/pw_rpc/Method.java b/pw_rpc/java/main/dev/pigweed/pw_rpc/Method.java
deleted file mode 100644
index e32d955..0000000
--- a/pw_rpc/java/main/dev/pigweed/pw_rpc/Method.java
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 dev.pigweed.pw_rpc;
-
-import com.google.auto.value.AutoValue;
-import com.google.common.base.Ascii;
-import com.google.protobuf.ByteString;
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.MessageLite;
-import com.google.protobuf.Parser;
-import java.lang.reflect.InvocationTargetException;
-
-/**
- * Information about an RPC service method.
- *
- * <p>If protobuf descriptors are available, this information can be extracted from the generated
- * protobuf descriptors. The "lite" Java protobuf library does not provide descriptors, so RPC
- * services and methods must be manually declared and provided to an RPC client.
- */
-@AutoValue
-public abstract class Method {
-  public abstract Service service();
-
-  public abstract String name();
-
-  public abstract Type type();
-
-  public abstract Class<? extends MessageLite> request();
-
-  public abstract Class<? extends MessageLite> response();
-
-  public final int id() {
-    return Ids.calculate(name());
-  }
-
-  public final boolean isServerStreaming() {
-    return type().isServerStreaming();
-  }
-
-  public final boolean isClientStreaming() {
-    return type().isClientStreaming();
-  }
-
-  public final String fullName() {
-    return createFullName(service().name(), name());
-  }
-
-  public static String createFullName(String serviceName, String methodName) {
-    return serviceName + '/' + methodName;
-  }
-
-  /* package */ static Builder builder() {
-    return new AutoValue_Method.Builder();
-  }
-
-  @Override
-  public final String toString() {
-    return fullName();
-  }
-
-  /** Builds Method instances. */
-  @AutoValue.Builder
-  public abstract static class Builder {
-    abstract Builder setService(Service value);
-
-    abstract Builder setType(Type type);
-
-    abstract Builder setName(String value);
-
-    abstract Builder setRequest(Class<? extends MessageLite> value);
-
-    abstract Builder setResponse(Class<? extends MessageLite> value);
-
-    abstract Method build();
-  }
-
-  /** Decodes a response payload according to the method's response type. */
-  final MessageLite decodeResponsePayload(ByteString data) throws InvalidProtocolBufferException {
-    return decodeProtobuf(response(), data);
-  }
-
-  /** Deserializes a protobuf using the provided class. */
-  @SuppressWarnings("unchecked")
-  static MessageLite decodeProtobuf(Class<? extends MessageLite> messageType, ByteString data)
-      throws InvalidProtocolBufferException {
-    try {
-      Parser<? extends MessageLite> parser =
-          (Parser<? extends MessageLite>) messageType.getMethod("parser").invoke(null);
-      return parser.parseFrom(data);
-    } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
-      throw new LinkageError(
-          "Method was created with a protobuf class with an invalid or missing parser() method", e);
-    }
-  }
-
-  /** Which type of RPC this is: unary or server/client/bidirectional streaming. */
-  public enum Type {
-    UNARY(/* isServerStreaming= */ false, /* isClientStreaming= */ false),
-    SERVER_STREAMING(/* isServerStreaming= */ true, /* isClientStreaming= */ false),
-    CLIENT_STREAMING(/* isServerStreaming= */ false, /* isClientStreaming= */ true),
-    BIDIRECTIONAL_STREAMING(/* isServerStreaming= */ true, /* isClientStreaming= */ true);
-
-    private final boolean isServerStreaming;
-    private final boolean isClientStreaming;
-
-    Type(boolean isServerStreaming, boolean isClientStreaming) {
-      this.isServerStreaming = isServerStreaming;
-      this.isClientStreaming = isClientStreaming;
-    }
-
-    public final boolean isServerStreaming() {
-      return isServerStreaming;
-    }
-
-    public final boolean isClientStreaming() {
-      return isClientStreaming;
-    }
-
-    public final String sentenceName() {
-      return Ascii.toLowerCase(name()).replace('_', ' ');
-    }
-  }
-}
diff --git a/pw_rpc/java/main/dev/pigweed/pw_rpc/MethodClient.java b/pw_rpc/java/main/dev/pigweed/pw_rpc/MethodClient.java
deleted file mode 100644
index 010b8ea..0000000
--- a/pw_rpc/java/main/dev/pigweed/pw_rpc/MethodClient.java
+++ /dev/null
@@ -1,198 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 dev.pigweed.pw_rpc;
-
-import com.google.protobuf.MessageLite;
-import dev.pigweed.pw_rpc.StreamObserverCall.StreamResponseFuture;
-import dev.pigweed.pw_rpc.StreamObserverCall.UnaryResponseFuture;
-import java.util.function.Consumer;
-
-/**
- * Represents a method ready to be invoked on a particular RPC channel.
- *
- * <p>The Client has the concrete MethodClient as a type parameter. This allows implementations to
- * fully define the interface and semantics for RPC calls.
- */
-public class MethodClient {
-  protected final StreamObserver<? extends MessageLite> defaultObserver;
-  private final RpcManager rpcs;
-  private final PendingRpc rpc;
-
-  MethodClient(RpcManager rpcs, PendingRpc rpc, StreamObserver<MessageLite> defaultObserver) {
-    this.rpcs = rpcs;
-    this.rpc = rpc;
-    this.defaultObserver = defaultObserver;
-  }
-
-  public final Method method() {
-    return rpc.method();
-  }
-
-  /** Gives implementations access to the RpcManager shared with the Client. */
-  protected final RpcManager rpcs() {
-    return rpcs;
-  }
-
-  /** Gives implementations access to the PendingRpc this MethodClient represents. */
-  protected final PendingRpc rpc() {
-    return rpc;
-  }
-
-  /** Invokes a unary RPC. Uses the default StreamObserver for RPC events. */
-  public Call invokeUnary(MessageLite request) throws ChannelOutputException {
-    return invokeUnary(request, defaultObserver());
-  }
-
-  /** Invokes a unary RPC. Uses the provided StreamObserver for RPC events. */
-  public Call invokeUnary(MessageLite request, StreamObserver<? extends MessageLite> observer)
-      throws ChannelOutputException {
-    checkCallType(Method.Type.UNARY);
-    return StreamObserverCall.start(rpcs(), rpc(), observer, request);
-  }
-
-  /** Invokes a unary RPC with a future that collects the response. */
-  public <ResponseT extends MessageLite> Call.UnaryFuture<ResponseT> invokeUnaryFuture(
-      MessageLite request) {
-    checkCallType(Method.Type.UNARY);
-    return new UnaryResponseFuture<>(rpcs(), rpc(), request);
-  }
-
-  /**
-   * Starts a unary RPC, ignoring any errors that occur when opening. This can be used to start
-   * listening to responses to an RPC before the RPC server is available.
-   *
-   * <p>The RPC remains open until it is completed by the server with a response or error packet or
-   * cancelled.
-   */
-  public Call openUnary(MessageLite request, StreamObserver<? extends MessageLite> observer) {
-    checkCallType(Method.Type.UNARY);
-    return StreamObserverCall.open(rpcs(), rpc(), observer, request);
-  }
-
-  /** Invokes a server streaming RPC. Uses the default StreamObserver for RPC events. */
-  public Call invokeServerStreaming(MessageLite request) throws ChannelOutputException {
-    return invokeServerStreaming(request, defaultObserver());
-  }
-
-  /** Invokes a server streaming RPC. Uses the provided StreamObserver for RPC events. */
-  public Call invokeServerStreaming(MessageLite request,
-      StreamObserver<? extends MessageLite> observer) throws ChannelOutputException {
-    checkCallType(Method.Type.SERVER_STREAMING);
-    return StreamObserverCall.start(rpcs(), rpc(), observer, request);
-  }
-
-  /** Invokes a server streaming RPC with a future that collects the responses. */
-  public Call.ServerStreamingFuture invokeServerStreamingFuture(
-      MessageLite request, Consumer<? extends MessageLite> onNext) {
-    checkCallType(Method.Type.SERVER_STREAMING);
-    return new StreamResponseFuture<>(rpcs(), rpc(), onNext, request);
-  }
-
-  /**
-   * Starts a server streaming RPC, ignoring any errors that occur when opening. This can be used to
-   * start listening to responses to an RPC before the RPC server is available.
-   *
-   * <p>The RPC remains open until it is completed by the server with a response or error packet or
-   * cancelled.
-   */
-  public Call openServerStreaming(
-      MessageLite request, StreamObserver<? extends MessageLite> observer) {
-    checkCallType(Method.Type.SERVER_STREAMING);
-    return StreamObserverCall.open(rpcs(), rpc(), observer, request);
-  }
-
-  /** Invokes a client streaming RPC. Uses the default StreamObserver for RPC events. */
-  public <RequestT extends MessageLite> Call.ClientStreaming<RequestT> invokeClientStreaming()
-      throws ChannelOutputException {
-    return invokeClientStreaming(defaultObserver());
-  }
-
-  /** Invokes a client streaming RPC. Uses the provided StreamObserver for RPC events. */
-  public <RequestT extends MessageLite> Call.ClientStreaming<RequestT> invokeClientStreaming(
-      StreamObserver<? extends MessageLite> observer) throws ChannelOutputException {
-    checkCallType(Method.Type.CLIENT_STREAMING);
-    return StreamObserverCall.start(rpcs(), rpc(), observer, null);
-  }
-
-  /** Invokes a client streaming RPC with a future that collects the response. */
-  public <RequestT extends MessageLite> Call.ClientStreaming<RequestT>
-  invokeClientStreamingFuture() {
-    checkCallType(Method.Type.CLIENT_STREAMING);
-    return new UnaryResponseFuture<>(rpcs(), rpc(), null);
-  }
-
-  /**
-   * Starts a client streaming RPC, ignoring any errors that occur when opening. This can be used to
-   * start listening to responses to an RPC before the RPC server is available.
-   *
-   * <p>The RPC remains open until it is completed by the server with a response or error packet or
-   * cancelled.
-   */
-  public <RequestT extends MessageLite> Call.ClientStreaming<RequestT> openClientStreaming(
-      StreamObserver<? extends MessageLite> observer) {
-    checkCallType(Method.Type.CLIENT_STREAMING);
-    return StreamObserverCall.open(rpcs(), rpc(), observer, null);
-  }
-
-  /** Invokes a bidirectional streaming RPC. Uses the default StreamObserver for RPC events. */
-  public <RequestT extends MessageLite> Call.ClientStreaming<RequestT>
-  invokeBidirectionalStreaming() throws ChannelOutputException {
-    return invokeBidirectionalStreaming(defaultObserver());
-  }
-
-  /** Invokes a bidirectional streaming RPC. Uses the provided StreamObserver for RPC events. */
-  public <RequestT extends MessageLite> Call.ClientStreaming<RequestT> invokeBidirectionalStreaming(
-      StreamObserver<? extends MessageLite> observer) throws ChannelOutputException {
-    checkCallType(Method.Type.BIDIRECTIONAL_STREAMING);
-    return StreamObserverCall.start(rpcs(), rpc(), observer, null);
-  }
-
-  /** Invokes a bidirectional streaming RPC with a future that finishes when the RPC finishes. */
-  public <RequestT extends MessageLite, ResponseT extends MessageLite>
-      Call.BidirectionalStreamingFuture<RequestT> invokeBidirectionalStreamingFuture(
-          Consumer<ResponseT> onNext) {
-    checkCallType(Method.Type.BIDIRECTIONAL_STREAMING);
-    return new StreamResponseFuture<>(rpcs(), rpc(), onNext, null);
-  }
-
-  /**
-   * Starts a bidirectional streaming RPC, ignoring any errors that occur when opening. This can be
-   * used to start listening to responses to an RPC before the RPC server is available.
-   *
-   * <p>The RPC remains open until it is completed by the server with a response or error packet or
-   * cancelled.
-   */
-  public <RequestT extends MessageLite> Call.ClientStreaming<RequestT> openBidirectionalStreaming(
-      StreamObserver<? extends MessageLite> observer) {
-    checkCallType(Method.Type.BIDIRECTIONAL_STREAMING);
-    return StreamObserverCall.open(rpcs(), rpc(), observer, null);
-  }
-
-  @SuppressWarnings("unchecked")
-  private <ResponseT extends MessageLite> StreamObserver<ResponseT> defaultObserver() {
-    return (StreamObserver<ResponseT>) defaultObserver;
-  }
-
-  private void checkCallType(Method.Type expected) {
-    if (!rpc().method().type().equals(expected)) {
-      throw new UnsupportedOperationException(String.format(
-          "%s is a %s method, but it was invoked as a %s method. RPCs must be invoked by the"
-              + " appropriate invoke function.",
-          method().fullName(),
-          method().type().sentenceName(),
-          expected.sentenceName()));
-    }
-  }
-}
diff --git a/pw_rpc/java/main/dev/pigweed/pw_rpc/Packets.java b/pw_rpc/java/main/dev/pigweed/pw_rpc/Packets.java
deleted file mode 100644
index 3260bb1..0000000
--- a/pw_rpc/java/main/dev/pigweed/pw_rpc/Packets.java
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 dev.pigweed.pw_rpc;
-
-import com.google.protobuf.MessageLite;
-import dev.pigweed.pw_rpc.internal.Packet.PacketType;
-import dev.pigweed.pw_rpc.internal.Packet.RpcPacket;
-
-/** Encodes pw_rpc packets of various types. */
-/* package */ class Packets {
-  private Packets() {}
-
-  public static byte[] request(PendingRpc rpc, MessageLite payload) {
-    RpcPacket.Builder builder = RpcPacket.newBuilder()
-                                    .setType(PacketType.REQUEST)
-                                    .setChannelId(rpc.channel().id())
-                                    .setServiceId(rpc.service().id())
-                                    .setMethodId(rpc.method().id());
-    if (payload != null) {
-      builder.setPayload(payload.toByteString());
-    }
-    return builder.build().toByteArray();
-  }
-
-  public static byte[] cancel(PendingRpc rpc) {
-    return RpcPacket.newBuilder()
-        .setType(PacketType.CLIENT_ERROR)
-        .setChannelId(rpc.channel().id())
-        .setServiceId(rpc.service().id())
-        .setMethodId(rpc.method().id())
-        .setStatus(Status.CANCELLED.code())
-        .build()
-        .toByteArray();
-  }
-
-  public static byte[] error(RpcPacket packet, Status status) {
-    return RpcPacket.newBuilder()
-        .setType(PacketType.CLIENT_ERROR)
-        .setChannelId(packet.getChannelId())
-        .setServiceId(packet.getServiceId())
-        .setMethodId(packet.getMethodId())
-        .setStatus(status.code())
-        .build()
-        .toByteArray();
-  }
-
-  public static byte[] clientStream(PendingRpc rpc, MessageLite payload) {
-    return RpcPacket.newBuilder()
-        .setType(PacketType.CLIENT_STREAM)
-        .setChannelId(rpc.channel().id())
-        .setServiceId(rpc.service().id())
-        .setMethodId(rpc.method().id())
-        .setPayload(payload.toByteString())
-        .build()
-        .toByteArray();
-  }
-
-  public static byte[] clientStreamEnd(PendingRpc rpc) {
-    return RpcPacket.newBuilder()
-        .setType(PacketType.CLIENT_STREAM_END)
-        .setChannelId(rpc.channel().id())
-        .setServiceId(rpc.service().id())
-        .setMethodId(rpc.method().id())
-        .build()
-        .toByteArray();
-  }
-}
diff --git a/pw_rpc/java/main/dev/pigweed/pw_rpc/PendingRpc.java b/pw_rpc/java/main/dev/pigweed/pw_rpc/PendingRpc.java
deleted file mode 100644
index 59e210d..0000000
--- a/pw_rpc/java/main/dev/pigweed/pw_rpc/PendingRpc.java
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 dev.pigweed.pw_rpc;
-
-import com.google.auto.value.AutoValue;
-import java.util.Locale;
-
-/** Represents a unique RPC invocation: channel + service + method. */
-@AutoValue
-public abstract class PendingRpc {
-  public static PendingRpc create(Channel channel, Service service, Method method) {
-    return new AutoValue_PendingRpc(channel, service, method);
-  }
-
-  public abstract Channel channel();
-
-  public abstract Service service();
-
-  public abstract Method method();
-
-  @Override
-  public final String toString() {
-    return String.format(Locale.ENGLISH, "RpcCall[%s channel=%d]", method(), channel().id());
-  }
-}
diff --git a/pw_rpc/java/main/dev/pigweed/pw_rpc/RpcError.java b/pw_rpc/java/main/dev/pigweed/pw_rpc/RpcError.java
deleted file mode 100644
index e70e0cc..0000000
--- a/pw_rpc/java/main/dev/pigweed/pw_rpc/RpcError.java
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 dev.pigweed.pw_rpc;
-
-/**
- * Represents a pw_rpc-layer error.
- *
- * <p>The RPC server error status codes are documented at https://pigweed.dev/pw_rpc/.
- */
-public class RpcError extends Exception {
-  private final PendingRpc rpc;
-  private final Status status;
-
-  public RpcError(PendingRpc rpc, Status status) {
-    super(String.format("%s failed with status %s", rpc, status.name()));
-    this.rpc = rpc;
-    this.status = status;
-  }
-
-  public PendingRpc rpc() {
-    return rpc;
-  }
-
-  public Status status() {
-    return status;
-  }
-}
diff --git a/pw_rpc/java/main/dev/pigweed/pw_rpc/RpcManager.java b/pw_rpc/java/main/dev/pigweed/pw_rpc/RpcManager.java
deleted file mode 100644
index bc690bb..0000000
--- a/pw_rpc/java/main/dev/pigweed/pw_rpc/RpcManager.java
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 dev.pigweed.pw_rpc;
-
-import com.google.protobuf.MessageLite;
-import dev.pigweed.pw_log.Logger;
-import java.util.HashMap;
-import java.util.Map;
-import javax.annotation.Nullable;
-
-/** Tracks the state of service method invocations. */
-public class RpcManager {
-  private static final Logger logger = Logger.forClass(RpcManager.class);
-
-  private final Map<PendingRpc, StreamObserverCall<?, ?>> pending = new HashMap<>();
-
-  /**
-   * Invokes an RPC.
-   *
-   * @param rpc channel / service / method tuple that unique identifies this RPC
-   * @param call object for this RPC
-   * @param payload the request
-   */
-  @Nullable
-  public synchronized StreamObserverCall<?, ?> start(
-      PendingRpc rpc, StreamObserverCall<?, ?> call, @Nullable MessageLite payload)
-      throws ChannelOutputException {
-    logger.atFine().log("%s starting", rpc);
-    rpc.channel().send(Packets.request(rpc, payload));
-    return pending.put(rpc, call);
-  }
-
-  /**
-   * Invokes an RPC, but ignores errors and keeps the RPC active if the invocation fails.
-   *
-   * <p>The RPC remains open until it is closed by the server (either with a response or error
-   * packet) or cancelled.
-   */
-  @Nullable
-  public synchronized StreamObserverCall<?, ?> open(
-      PendingRpc rpc, StreamObserverCall<?, ?> call, @Nullable MessageLite payload) {
-    logger.atFine().log("%s opening", rpc);
-    try {
-      rpc.channel().send(Packets.request(rpc, payload));
-    } catch (ChannelOutputException e) {
-      logger.atFiner().withCause(e).log(
-          "Ignoring error opening %s; listening for unrequested responses", rpc);
-    }
-    return pending.put(rpc, call);
-  }
-
-  /** Cancels an ongoing RPC */
-  @Nullable
-  public synchronized StreamObserverCall<?, ?> cancel(PendingRpc rpc)
-      throws ChannelOutputException {
-    StreamObserverCall<?, ?> call = pending.remove(rpc);
-    if (call != null) {
-      logger.atFine().log("%s was cancelled", rpc);
-      rpc.channel().send(Packets.cancel(rpc));
-    }
-    return call;
-  }
-
-  @Nullable
-  public synchronized StreamObserverCall<?, ?> clientStream(PendingRpc rpc, MessageLite payload)
-      throws ChannelOutputException {
-    StreamObserverCall<?, ?> call = pending.get(rpc);
-    if (call != null) {
-      rpc.channel().send(Packets.clientStream(rpc, payload));
-    }
-    return call;
-  }
-
-  @Nullable
-  public synchronized StreamObserverCall<?, ?> clientStreamEnd(PendingRpc rpc)
-      throws ChannelOutputException {
-    StreamObserverCall<?, ?> call = pending.get(rpc);
-    if (call != null) {
-      logger.atFiner().log("%s client stream closed", rpc);
-      rpc.channel().send(Packets.clientStreamEnd(rpc));
-    }
-    return call;
-  }
-
-  @Nullable
-  public synchronized StreamObserverCall<?, ?> clear(PendingRpc rpc) {
-    return pending.remove(rpc);
-  }
-
-  @Nullable
-  public synchronized StreamObserverCall<?, ?> getPending(PendingRpc rpc) {
-    return pending.get(rpc);
-  }
-
-  @Override
-  public synchronized String toString() {
-    return "RpcManager{"
-        + "pending=" + pending + '}';
-  }
-}
diff --git a/pw_rpc/java/main/dev/pigweed/pw_rpc/Service.java b/pw_rpc/java/main/dev/pigweed/pw_rpc/Service.java
deleted file mode 100644
index 311ff6f..0000000
--- a/pw_rpc/java/main/dev/pigweed/pw_rpc/Service.java
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 dev.pigweed.pw_rpc;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.protobuf.MessageLite;
-import java.util.Arrays;
-import java.util.stream.Collectors;
-
-/** Represents an RPC service: a collection of related methods. */
-public class Service {
-  private final String name;
-  private final int id;
-  private final ImmutableMap<Integer, Method> methods;
-
-  public Service(String name, Method.Builder... methodBuilders) {
-    this.name = name;
-    this.id = Ids.calculate(name);
-    Arrays.stream(methodBuilders).forEach(m -> m.setService(this));
-    this.methods = ImmutableMap.copyOf(Arrays.stream(methodBuilders)
-                                           .map(Method.Builder::build)
-                                           .collect(Collectors.toMap(Method::id, m -> m)));
-  }
-
-  public String name() {
-    return name;
-  }
-
-  public int id() {
-    return id;
-  }
-
-  public ImmutableMap<Integer, Method> methods() {
-    return methods;
-  }
-
-  public final Method method(String name) {
-    return methods().get(Ids.calculate(name));
-  }
-
-  @Override
-  public final String toString() {
-    return name();
-  }
-
-  public static Method.Builder unaryMethod(
-      String name, Class<? extends MessageLite> request, Class<? extends MessageLite> response) {
-    return Method.builder()
-        .setType(Method.Type.UNARY)
-        .setName(name)
-        .setRequest(request)
-        .setResponse(response);
-  }
-
-  public static Method.Builder serverStreamingMethod(
-      String name, Class<? extends MessageLite> request, Class<? extends MessageLite> response) {
-    return Method.builder()
-        .setType(Method.Type.SERVER_STREAMING)
-        .setName(name)
-        .setRequest(request)
-        .setResponse(response);
-  }
-
-  public static Method.Builder clientStreamingMethod(
-      String name, Class<? extends MessageLite> request, Class<? extends MessageLite> response) {
-    return Method.builder()
-        .setType(Method.Type.CLIENT_STREAMING)
-        .setName(name)
-        .setRequest(request)
-        .setResponse(response);
-  }
-
-  public static Method.Builder bidirectionalStreamingMethod(
-      String name, Class<? extends MessageLite> request, Class<? extends MessageLite> response) {
-    return Method.builder()
-        .setType(Method.Type.BIDIRECTIONAL_STREAMING)
-        .setName(name)
-        .setRequest(request)
-        .setResponse(response);
-  }
-}
diff --git a/pw_rpc/java/main/dev/pigweed/pw_rpc/Status.java b/pw_rpc/java/main/dev/pigweed/pw_rpc/Status.java
deleted file mode 100644
index 58ef8b2..0000000
--- a/pw_rpc/java/main/dev/pigweed/pw_rpc/Status.java
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 dev.pigweed.pw_rpc;
-
-import javax.annotation.Nullable;
-
-/** Status object for RPC statuses. Must match gRPC's status codes. */
-public enum Status {
-  OK(0),
-  CANCELLED(1),
-  UNKNOWN(2),
-  INVALID_ARGUMENT(3),
-  DEADLINE_EXCEEDED(4),
-  NOT_FOUND(5),
-  ALREADY_EXISTS(6),
-  PERMISSION_DENIED(7),
-  UNAUTHENTICATED(16),
-  RESOURCE_EXHAUSTED(8),
-  FAILED_PRECONDITION(9),
-  ABORTED(10),
-  OUT_OF_RANGE(11),
-  UNIMPLEMENTED(12),
-  INTERNAL(13),
-  UNAVAILABLE(14),
-  DATA_LOSS(15);
-
-  private final int code;
-
-  private static final Status[] values = new Status[17];
-
-  static {
-    for (Status status : Status.values()) {
-      values[status.code] = status;
-    }
-  }
-
-  Status(int code) {
-    this.code = code;
-  }
-
-  public final int code() {
-    return code;
-  }
-
-  public final boolean ok() {
-    return code == 0;
-  }
-
-  @Nullable
-  public static Status fromCode(int code) {
-    return code >= 0 && code < values.length ? values[code] : null;
-  }
-}
diff --git a/pw_rpc/java/main/dev/pigweed/pw_rpc/StreamObserver.java b/pw_rpc/java/main/dev/pigweed/pw_rpc/StreamObserver.java
deleted file mode 100644
index bb5cffb..0000000
--- a/pw_rpc/java/main/dev/pigweed/pw_rpc/StreamObserver.java
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 dev.pigweed.pw_rpc;
-
-import com.google.protobuf.MessageLite;
-
-/** Receives events from an RPC stream. Used by all RPC types. */
-public interface StreamObserver<T extends MessageLite> {
-  /** Called when an RPC message is received. */
-  void onNext(T value);
-
-  /** Called when the RPC completes with the status returned from the RPC. */
-  void onCompleted(Status status);
-
-  /** Called when an RPC terminates unexpectedly due to an error. */
-  void onError(Status status);
-}
diff --git a/pw_rpc/java/main/dev/pigweed/pw_rpc/StreamObserverCall.java b/pw_rpc/java/main/dev/pigweed/pw_rpc/StreamObserverCall.java
deleted file mode 100644
index 537362c..0000000
--- a/pw_rpc/java/main/dev/pigweed/pw_rpc/StreamObserverCall.java
+++ /dev/null
@@ -1,265 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 dev.pigweed.pw_rpc;
-
-import com.google.common.util.concurrent.AbstractFuture;
-import com.google.protobuf.ByteString;
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.MessageLite;
-import dev.pigweed.pw_log.Logger;
-import dev.pigweed.pw_rpc.Call.ClientStreaming;
-import java.util.function.Consumer;
-import javax.annotation.Nullable;
-
-/**
- * Represents an ongoing RPC call.
- *
- * <p>This call class implements all features of unary, server streaming, client streaming, and
- * bidirectional streaming RPCs. It provides static methods for creating call objects for each RPC
- * type.
- *
- * @param <RequestT> request type of the RPC; used for client or bidirectional streaming RPCs
- * @param <ResponseT> response type of the RPC; used for all types of RPCs
- */
-class StreamObserverCall<RequestT extends MessageLite, ResponseT extends MessageLite>
-    implements ClientStreaming<RequestT> {
-  private static final Logger logger = Logger.forClass(StreamObserverCall.class);
-
-  private final RpcManager rpcs;
-  private final PendingRpc rpc;
-  private final StreamObserver<ResponseT> observer;
-
-  @Nullable private Status status = null;
-  @Nullable private Status error = null;
-
-  /** Base class for a Call that is a ListenableFuture. */
-  private abstract static class StreamObserverFutureCall<RequestT extends MessageLite, ResponseT
-                                                             extends MessageLite, ResultT>
-      extends AbstractFuture<ResultT>
-      implements ClientStreaming<RequestT>, StreamObserver<ResponseT> {
-    private final StreamObserverCall<RequestT, ResponseT> call;
-
-    private StreamObserverFutureCall(RpcManager rpcs, PendingRpc rpc) {
-      call = new StreamObserverCall<>(rpcs, rpc, this);
-    }
-
-    void start(@Nullable RequestT request) {
-      try {
-        call.rpcs.start(call.rpc, call, request);
-      } catch (ChannelOutputException e) {
-        call.error = Status.UNKNOWN;
-        setException(e);
-      }
-    }
-
-    @Override
-    public boolean cancel(boolean mayInterruptIfRunning) {
-      boolean result = super.cancel(mayInterruptIfRunning);
-      try {
-        call.cancel();
-      } catch (ChannelOutputException e) {
-        setException(e);
-      }
-      return result;
-    }
-
-    @Override
-    public void cancel() throws ChannelOutputException {
-      cancel(true);
-    }
-
-    @Nullable
-    @Override
-    public Status status() {
-      return call.status();
-    }
-
-    @Nullable
-    @Override
-    public Status error() {
-      return call.error();
-    }
-
-    @Override
-    public void send(RequestT request) throws ChannelOutputException, RpcError {
-      call.send(request);
-    }
-
-    @Override
-    public void finish() throws ChannelOutputException {
-      call.finish();
-    }
-
-    @Override
-    public void onError(Status status) {
-      setException(new RpcError(call.rpc, status));
-    }
-  }
-
-  /** Future-based Call class for unary and client streaming RPCs. */
-  static class UnaryResponseFuture<RequestT extends MessageLite, ResponseT extends MessageLite>
-      extends StreamObserverFutureCall<RequestT, ResponseT, UnaryResult<ResponseT>>
-      implements Call.ClientStreamingFuture<RequestT, ResponseT> {
-    @Nullable ResponseT response = null;
-
-    UnaryResponseFuture(RpcManager rpcs, PendingRpc rpc, @Nullable RequestT request) {
-      super(rpcs, rpc);
-      start(request);
-    }
-
-    @Override
-    public void onNext(ResponseT value) {
-      if (response == null) {
-        response = value;
-      } else {
-        setException(new IllegalStateException("Unary RPC received multiple responses."));
-      }
-    }
-
-    @Override
-    public void onCompleted(Status status) {
-      if (response == null) {
-        setException(new IllegalStateException("Unary RPC completed without a response payload"));
-      } else {
-        set(UnaryResult.create(response, status));
-      }
-    }
-  }
-
-  /** Future-based Call class for server and bidirectional streaming RPCs. */
-  static class StreamResponseFuture<RequestT extends MessageLite, ResponseT extends MessageLite>
-      extends StreamObserverFutureCall<RequestT, ResponseT, Status>
-      implements Call.BidirectionalStreamingFuture<RequestT> {
-    private final Consumer<ResponseT> onNext;
-
-    StreamResponseFuture(
-        RpcManager rpcs, PendingRpc rpc, Consumer<ResponseT> onNext, @Nullable RequestT request) {
-      super(rpcs, rpc);
-      this.onNext = onNext;
-      start(request);
-    }
-
-    @Override
-    public void onNext(ResponseT value) {
-      onNext.accept(value);
-    }
-
-    @Override
-    public void onCompleted(Status status) {
-      set(status);
-    }
-  }
-
-  /** Invokes the specified RPC. */
-  static <RequestT extends MessageLite, ResponseT extends MessageLite>
-      StreamObserverCall<RequestT, ResponseT> start(RpcManager rpcs,
-          PendingRpc rpc,
-          StreamObserver<ResponseT> observer,
-          @Nullable MessageLite request) throws ChannelOutputException {
-    StreamObserverCall<RequestT, ResponseT> call = new StreamObserverCall<>(rpcs, rpc, observer);
-    rpcs.start(rpc, call, request);
-    return call;
-  }
-
-  /** Invokes the specified RPC, ignoring errors that occur when the RPC is invoked. */
-  static <RequestT extends MessageLite, ResponseT extends MessageLite>
-      StreamObserverCall<RequestT, ResponseT> open(RpcManager rpcs,
-          PendingRpc rpc,
-          StreamObserver<ResponseT> observer,
-          @Nullable MessageLite request) {
-    StreamObserverCall<RequestT, ResponseT> call = new StreamObserverCall<>(rpcs, rpc, observer);
-    rpcs.open(rpc, call, request);
-    return call;
-  }
-
-  private StreamObserverCall(RpcManager rpcs, PendingRpc rpc, StreamObserver<ResponseT> observer) {
-    this.rpcs = rpcs;
-    this.rpc = rpc;
-    this.observer = observer;
-  }
-
-  @Override
-  public void cancel() throws ChannelOutputException {
-    if (active()) {
-      error = Status.CANCELLED;
-      rpcs.cancel(rpc);
-    }
-  }
-
-  @Override
-  @Nullable
-  public Status status() {
-    return status;
-  }
-
-  @Nullable
-  @Override
-  public Status error() {
-    return error;
-  }
-
-  @Override
-  public void send(RequestT request) throws ChannelOutputException, RpcError {
-    if (error != null) {
-      throw new RpcError(rpc, error);
-    }
-    if (status != null) {
-      throw new RpcError(rpc, Status.FAILED_PRECONDITION);
-    }
-    rpcs.clientStream(rpc, request);
-  }
-
-  @Override
-  public void finish() throws ChannelOutputException {
-    if (active()) {
-      rpcs.clientStreamEnd(rpc);
-    }
-  }
-
-  void onNext(ByteString payload) {
-    if (active()) {
-      ResponseT message = parseResponse(payload);
-      if (message != null) {
-        observer.onNext(message);
-      }
-    }
-  }
-
-  void onCompleted(Status status) {
-    if (active()) {
-      this.status = status;
-      observer.onCompleted(status);
-    }
-  }
-
-  void onError(Status status) {
-    if (active()) {
-      this.error = status;
-      observer.onError(status);
-    }
-  }
-
-  @SuppressWarnings("unchecked")
-  @Nullable
-  private ResponseT parseResponse(ByteString payload) {
-    try {
-      return (ResponseT) rpc.method().decodeResponsePayload(payload);
-    } catch (InvalidProtocolBufferException e) {
-      logger.atWarning().withCause(e).log(
-          "Failed to decode response for method %s; skipping packet", rpc.method().name());
-      return null;
-    }
-  }
-}
diff --git a/pw_rpc/java/main/dev/pigweed/pw_rpc/UnaryResult.java b/pw_rpc/java/main/dev/pigweed/pw_rpc/UnaryResult.java
deleted file mode 100644
index 7ca7743..0000000
--- a/pw_rpc/java/main/dev/pigweed/pw_rpc/UnaryResult.java
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 dev.pigweed.pw_rpc;
-
-import com.google.auto.value.AutoValue;
-import com.google.protobuf.MessageLite;
-
-/** Stores the results of a completed RPC call. */
-@AutoValue
-public abstract class UnaryResult<ResponseT extends MessageLite> {
-  public static <ResponseT extends MessageLite> UnaryResult<ResponseT> create(
-      ResponseT response, Status status) {
-    return new AutoValue_UnaryResult<>(response, status);
-  }
-
-  public abstract ResponseT response();
-
-  public abstract Status status();
-}
diff --git a/pw_rpc/java/test/dev/pigweed/pw_rpc/BUILD.bazel b/pw_rpc/java/test/dev/pigweed/pw_rpc/BUILD.bazel
deleted file mode 100644
index 3f63f7a..0000000
--- a/pw_rpc/java/test/dev/pigweed/pw_rpc/BUILD.bazel
+++ /dev/null
@@ -1,142 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-# Tests for the generic pw_rpc client.
-
-licenses(["notice"])
-
-java_library(
-    name = "test_client",
-    testonly = True,
-    srcs = ["TestClient.java"],
-    visibility = ["__pkg__"],
-    deps = [
-        "//pw_rpc:packet_proto_java_lite",
-        "//pw_rpc/java/main/dev/pigweed/pw_rpc:client",
-        "@com_google_protobuf//java/lite",
-        "@maven//:com_google_code_findbugs_jsr305",
-        "@maven//:com_google_guava_guava",
-    ],
-)
-
-java_test(
-    name = "ClientTest",
-    size = "small",
-    srcs = ["ClientTest.java"],
-    test_class = "dev.pigweed.pw_rpc.ClientTest",
-    deps = [
-        ":test_proto_java_proto_lite",
-        "//pw_rpc:packet_proto_java_lite",
-        "//pw_rpc/java/main/dev/pigweed/pw_rpc:client",
-        "@com_google_protobuf//java/lite",
-        "@maven//:com_google_flogger_flogger_system_backend",
-        "@maven//:com_google_guava_guava",
-        "@maven//:com_google_truth_truth",
-        "@maven//:org_mockito_mockito_core",
-    ],
-)
-
-java_test(
-    name = "IdsTest",
-    size = "small",
-    srcs = ["IdsTest.java"],
-    test_class = "dev.pigweed.pw_rpc.IdsTest",
-    deps = [
-        "//pw_rpc/java/main/dev/pigweed/pw_rpc:client",
-        "@maven//:com_google_flogger_flogger_system_backend",
-        "@maven//:com_google_truth_truth",
-    ],
-)
-
-java_test(
-    name = "PacketsTest",
-    size = "small",
-    srcs = ["PacketsTest.java"],
-    test_class = "dev.pigweed.pw_rpc.PacketsTest",
-    deps = [
-        "//pw_rpc:packet_proto_java_lite",
-        "//pw_rpc/java/main/dev/pigweed/pw_rpc:client",
-        "@com_google_protobuf//java/lite",
-        "@maven//:com_google_flogger_flogger_system_backend",
-        "@maven//:com_google_truth_truth",
-    ],
-)
-
-java_test(
-    name = "RpcManagerTest",
-    size = "small",
-    srcs = ["RpcManagerTest.java"],
-    test_class = "dev.pigweed.pw_rpc.RpcManagerTest",
-    deps = [
-        ":test_proto_java_proto_lite",
-        "//pw_rpc:packet_proto_java_lite",
-        "//pw_rpc/java/main/dev/pigweed/pw_rpc:client",
-        "@com_google_protobuf//java/lite",
-        "@maven//:com_google_flogger_flogger_system_backend",
-        "@maven//:com_google_truth_truth",
-        "@maven//:org_mockito_mockito_core",
-    ],
-)
-
-java_test(
-    name = "StreamObserverCallTest",
-    size = "small",
-    srcs = ["StreamObserverCallTest.java"],
-    test_class = "dev.pigweed.pw_rpc.StreamObserverCallTest",
-    deps = [
-        ":test_proto_java_proto_lite",
-        "//pw_rpc:packet_proto_java_lite",
-        "//pw_rpc/java/main/dev/pigweed/pw_rpc:client",
-        "@maven//:com_google_flogger_flogger_system_backend",
-        "@maven//:com_google_truth_truth",
-        "@maven//:org_mockito_mockito_core",
-    ],
-)
-
-java_test(
-    name = "StreamObserverMethodClientTest",
-    size = "small",
-    srcs = ["StreamObserverMethodClientTest.java"],
-    test_class = "dev.pigweed.pw_rpc.StreamObserverMethodClientTest",
-    deps = [
-        ":test_proto_java_proto_lite",
-        "//pw_rpc/java/main/dev/pigweed/pw_rpc:client",
-        "@com_google_protobuf//java/lite",
-        "@maven//:com_google_flogger_flogger_system_backend",
-        "@maven//:com_google_truth_truth",
-        "@maven//:org_mockito_mockito_core",
-    ],
-)
-
-test_suite(
-    name = "pw_rpc",
-    tests = [
-        ":ClientTest",
-        ":IdsTest",
-        ":PacketsTest",
-        ":RpcManagerTest",
-        ":StreamObserverCallTest",
-        ":StreamObserverMethodClientTest",
-    ],
-)
-
-proto_library(
-    name = "test_proto",
-    srcs = ["test.proto"],
-)
-
-java_lite_proto_library(
-    name = "test_proto_java_proto_lite",
-    deps = [":test_proto"],
-)
diff --git a/pw_rpc/java/test/dev/pigweed/pw_rpc/ClientTest.java b/pw_rpc/java/test/dev/pigweed/pw_rpc/ClientTest.java
deleted file mode 100644
index affffa5..0000000
--- a/pw_rpc/java/test/dev/pigweed/pw_rpc/ClientTest.java
+++ /dev/null
@@ -1,338 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 dev.pigweed.pw_rpc;
-
-import static com.google.common.truth.Truth.assertThat;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.junit.Assert.assertThrows;
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-import com.google.common.collect.ImmutableList;
-import com.google.protobuf.ExtensionRegistryLite;
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.MessageLite;
-import dev.pigweed.pw_rpc.internal.Packet.PacketType;
-import dev.pigweed.pw_rpc.internal.Packet.RpcPacket;
-import java.util.ArrayList;
-import java.util.List;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-public final class ClientTest {
-  @Rule public final MockitoRule mockito = MockitoJUnit.rule();
-
-  private static final Service SERVICE = new Service("pw.rpc.test1.TheTestService",
-      Service.unaryMethod("SomeUnary", SomeMessage.class, AnotherMessage.class),
-      Service.serverStreamingMethod("SomeServerStreaming", SomeMessage.class, AnotherMessage.class),
-      Service.clientStreamingMethod("SomeClientStreaming", SomeMessage.class, AnotherMessage.class),
-      Service.bidirectionalStreamingMethod(
-          "SomeBidiStreaming", SomeMessage.class, AnotherMessage.class));
-
-  private static final Method UNARY_METHOD = SERVICE.method("SomeUnary");
-  private static final Method SERVER_STREAMING_METHOD = SERVICE.method("SomeServerStreaming");
-  private static final Method CLIENT_STREAMING_METHOD = SERVICE.method("SomeClientStreaming");
-
-  private static final int CHANNEL_ID = 1;
-
-  private static final SomeMessage REQUEST_PAYLOAD =
-      SomeMessage.newBuilder().setMagicNumber(54321).build();
-  private static final AnotherMessage RESPONSE_PAYLOAD =
-      AnotherMessage.newBuilder()
-          .setResult(AnotherMessage.Result.FAILED_MISERABLY)
-          .setPayload("12345")
-          .build();
-
-  private Client client;
-  private List<RpcPacket> packetsSent;
-
-  @Mock private StreamObserver<AnotherMessage> observer;
-
-  private static byte[] response(String service, String method) {
-    return response(service, method, Status.OK);
-  }
-
-  private static byte[] response(String service, String method, Status status) {
-    return serverReply(
-        PacketType.RESPONSE, service, method, status, SomeMessage.getDefaultInstance());
-  }
-
-  private static byte[] response(
-      String service, String method, Status status, MessageLite payload) {
-    return serverReply(PacketType.RESPONSE, service, method, status, payload);
-  }
-
-  private static byte[] serverStream(String service, String method, MessageLite payload) {
-    return serverReply(PacketType.SERVER_STREAM, service, method, Status.OK, payload);
-  }
-
-  private static byte[] serverReply(
-      PacketType type, String service, String method, Status status, MessageLite payload) {
-    return packetBuilder(service, method)
-        .setType(type)
-        .setStatus(status.code())
-        .setPayload(payload.toByteString())
-        .build()
-        .toByteArray();
-  }
-
-  private static RpcPacket.Builder packetBuilder(String service, String method) {
-    return RpcPacket.newBuilder()
-        .setChannelId(CHANNEL_ID)
-        .setServiceId(Ids.calculate(service))
-        .setMethodId(Ids.calculate(method));
-  }
-
-  private static RpcPacket requestPacket(String service, String method, MessageLite payload) {
-    return packetBuilder(service, method)
-        .setType(PacketType.REQUEST)
-        .setPayload(payload.toByteString())
-        .build();
-  }
-
-  @Before
-  public void setup() {
-    packetsSent = new ArrayList<>();
-    client = Client.create(ImmutableList.of(new Channel(1, (data) -> {
-      try {
-        packetsSent.add(RpcPacket.parseFrom(data, ExtensionRegistryLite.getEmptyRegistry()));
-      } catch (InvalidProtocolBufferException e) {
-        fail("The client sent an invalid packet: " + e);
-      }
-    })), ImmutableList.of(SERVICE));
-  }
-
-  @Test
-  public void method_unknownMethod() {
-    assertThrows(IllegalArgumentException.class, () -> client.method(CHANNEL_ID, ""));
-    assertThrows(IllegalArgumentException.class, () -> client.method(CHANNEL_ID, "one"));
-    assertThrows(IllegalArgumentException.class, () -> client.method(CHANNEL_ID, "hello"));
-    assertThrows(
-        IllegalArgumentException.class, () -> client.method(CHANNEL_ID, "abc.Service/Method"));
-    assertThrows(IllegalArgumentException.class,
-        () -> client.method(CHANNEL_ID, "pw.rpc.test1.TheTestService/NotAnRpc").method());
-  }
-
-  @Test
-  public void method_unknownChannel() {
-    assertThrows(IllegalArgumentException.class,
-        () -> client.method(0, "pw.rpc.test1.TheTestService/SomeUnary"));
-    assertThrows(IllegalArgumentException.class,
-        () -> client.method(999, "pw.rpc.test1.TheTestService/SomeUnary"));
-  }
-
-  @Test
-  public void method_accessAsServiceSlashMethod() {
-    assertThat(client.method(CHANNEL_ID, "pw.rpc.test1.TheTestService/SomeUnary").method())
-        .isSameInstanceAs(UNARY_METHOD);
-    assertThat(
-        client.method(CHANNEL_ID, "pw.rpc.test1.TheTestService/SomeServerStreaming").method())
-        .isSameInstanceAs(SERVER_STREAMING_METHOD);
-    assertThat(
-        client.method(CHANNEL_ID, "pw.rpc.test1.TheTestService/SomeClientStreaming").method())
-        .isSameInstanceAs(CLIENT_STREAMING_METHOD);
-  }
-
-  @Test
-  public void method_accessAsServiceDotMethod() {
-    assertThat(client.method(CHANNEL_ID, "pw.rpc.test1.TheTestService.SomeUnary").method())
-        .isSameInstanceAs(UNARY_METHOD);
-    assertThat(
-        client.method(CHANNEL_ID, "pw.rpc.test1.TheTestService.SomeServerStreaming").method())
-        .isSameInstanceAs(SERVER_STREAMING_METHOD);
-    assertThat(
-        client.method(CHANNEL_ID, "pw.rpc.test1.TheTestService.SomeClientStreaming").method())
-        .isSameInstanceAs(CLIENT_STREAMING_METHOD);
-  }
-
-  @Test
-  public void method_accessAsServiceAndMethod() {
-    assertThat(client.method(CHANNEL_ID, "pw.rpc.test1.TheTestService", "SomeUnary").method())
-        .isSameInstanceAs(UNARY_METHOD);
-    assertThat(
-        client.method(CHANNEL_ID, "pw.rpc.test1.TheTestService", "SomeServerStreaming").method())
-        .isSameInstanceAs(SERVER_STREAMING_METHOD);
-    assertThat(
-        client.method(CHANNEL_ID, "pw.rpc.test1.TheTestService", "SomeClientStreaming").method())
-        .isSameInstanceAs(CLIENT_STREAMING_METHOD);
-  }
-
-  @Test
-  public void processPacket_emptyPacket_isNotProcessed() {
-    assertThat(client.processPacket(new byte[] {})).isFalse();
-  }
-
-  @Test
-  public void processPacket_invalidPacket_isNotProcessed() {
-    assertThat(client.processPacket("This is definitely not a packet!".getBytes(UTF_8))).isFalse();
-  }
-
-  @Test
-  public void processPacket_packetNotForClient_isNotProcessed() {
-    assertThat(client.processPacket(RpcPacket.newBuilder()
-                                        .setType(PacketType.REQUEST)
-                                        .setChannelId(CHANNEL_ID)
-                                        .setServiceId(123)
-                                        .setMethodId(456)
-                                        .build()
-                                        .toByteArray()))
-        .isFalse();
-  }
-
-  @Test
-  public void processPacket_unrecognizedChannel_isNotProcessed() {
-    assertThat(client.processPacket(RpcPacket.newBuilder()
-                                        .setType(PacketType.RESPONSE)
-                                        .setChannelId(CHANNEL_ID + 100)
-                                        .setServiceId(123)
-                                        .setMethodId(456)
-                                        .build()
-                                        .toByteArray()))
-        .isFalse();
-  }
-
-  @Test
-  public void processPacket_unrecognizedService_sendsError() {
-    assertThat(client.processPacket(response("pw.rpc.test1.NotAService", "SomeUnary"))).isTrue();
-
-    assertThat(packetsSent)
-        .containsExactly(packetBuilder("pw.rpc.test1.NotAService", "SomeUnary")
-                             .setType(PacketType.CLIENT_ERROR)
-                             .setStatus(Status.NOT_FOUND.code())
-                             .build());
-  }
-
-  @Test
-  public void processPacket_unrecognizedMethod_sendsError() {
-    assertThat(client.processPacket(response("pw.rpc.test1.TheTestService", "NotMethod"))).isTrue();
-
-    assertThat(packetsSent)
-        .containsExactly(packetBuilder("pw.rpc.test1.TheTestService", "NotMethod")
-                             .setType(PacketType.CLIENT_ERROR)
-                             .setStatus(Status.NOT_FOUND.code())
-                             .build());
-  }
-
-  @Test
-  public void processPacket_nonPendingMethod_sendsError() {
-    assertThat(client.processPacket(response("pw.rpc.test1.TheTestService", "SomeUnary"))).isTrue();
-
-    assertThat(packetsSent)
-        .containsExactly(packetBuilder("pw.rpc.test1.TheTestService", "SomeUnary")
-                             .setType(PacketType.CLIENT_ERROR)
-                             .setStatus(Status.FAILED_PRECONDITION.code())
-                             .build());
-  }
-
-  @Test
-  public void processPacket_serverError_abortsPending() throws Exception {
-    MethodClient method = client.method(CHANNEL_ID, "pw.rpc.test1.TheTestService", "SomeUnary");
-    Call call = method.invokeUnary(SomeMessage.getDefaultInstance());
-
-    assertThat(client.processPacket(serverReply(PacketType.SERVER_ERROR,
-                   "pw.rpc.test1.TheTestService",
-                   "SomeUnary",
-                   Status.NOT_FOUND,
-                   SomeMessage.getDefaultInstance())))
-        .isTrue();
-    assertThat(call.error()).isEqualTo(Status.NOT_FOUND);
-  }
-
-  @Test
-  public void processPacket_responseToPendingUnaryMethod_callsObserver() throws Exception {
-    MethodClient method = client.method(CHANNEL_ID, "pw.rpc.test1.TheTestService", "SomeUnary");
-
-    method.invokeUnary(REQUEST_PAYLOAD, observer);
-
-    assertThat(packetsSent)
-        .containsExactly(
-            requestPacket("pw.rpc.test1.TheTestService", "SomeUnary", REQUEST_PAYLOAD));
-
-    assertThat(
-        client.processPacket(response(
-            "pw.rpc.test1.TheTestService", "SomeUnary", Status.ALREADY_EXISTS, RESPONSE_PAYLOAD)))
-        .isTrue();
-
-    verify(observer).onNext(RESPONSE_PAYLOAD);
-    verify(observer).onCompleted(Status.ALREADY_EXISTS);
-  }
-
-  @Test
-  public void processPacket_responsesToPendingServerStreamingMethod_callsObserver()
-      throws Exception {
-    MethodClient method =
-        client.method(CHANNEL_ID, "pw.rpc.test1.TheTestService", "SomeServerStreaming");
-
-    method.invokeServerStreaming(REQUEST_PAYLOAD, observer);
-
-    assertThat(packetsSent)
-        .containsExactly(
-            requestPacket("pw.rpc.test1.TheTestService", "SomeServerStreaming", REQUEST_PAYLOAD));
-
-    assertThat(client.processPacket(serverStream(
-                   "pw.rpc.test1.TheTestService", "SomeServerStreaming", RESPONSE_PAYLOAD)))
-        .isTrue();
-
-    verify(observer).onNext(RESPONSE_PAYLOAD);
-
-    assertThat(client.processPacket(response(
-                   "pw.rpc.test1.TheTestService", "SomeServerStreaming", Status.UNAUTHENTICATED)))
-        .isTrue();
-
-    verify(observer).onCompleted(Status.UNAUTHENTICATED);
-  }
-
-  @Test
-  public void processPacket_responsePacket_completesRpc() throws Exception {
-    MethodClient method =
-        client.method(CHANNEL_ID, "pw.rpc.test1.TheTestService", "SomeServerStreaming");
-
-    method.invokeServerStreaming(REQUEST_PAYLOAD, observer);
-
-    assertThat(client.processPacket(
-                   response("pw.rpc.test1.TheTestService", "SomeServerStreaming", Status.OK)))
-        .isTrue();
-
-    verify(observer).onCompleted(Status.OK);
-
-    assertThat(client.processPacket(serverStream(
-                   "pw.rpc.test1.TheTestService", "SomeServerStreaming", RESPONSE_PAYLOAD)))
-        .isTrue();
-
-    verify(observer, never()).onNext(any());
-  }
-
-  @Test
-  @SuppressWarnings("unchecked") // No idea why, but this test causes "unchecked" warnings
-  public void streamObserverClient_create_invokeMethod() throws Exception {
-    Channel.Output mockChannelOutput = Mockito.mock(Channel.Output.class);
-    Client client = Client.create(ImmutableList.of(new Channel(1, mockChannelOutput)),
-        ImmutableList.of(SERVICE),
-        (rpc) -> Mockito.mock(StreamObserver.class));
-
-    SomeMessage payload = SomeMessage.newBuilder().setMagicNumber(99).build();
-    client.method(CHANNEL_ID, "pw.rpc.test1.TheTestService", "SomeUnary").invokeUnary(payload);
-    verify(mockChannelOutput)
-        .send(requestPacket("pw.rpc.test1.TheTestService", "SomeUnary", payload).toByteArray());
-  }
-}
diff --git a/pw_rpc/java/test/dev/pigweed/pw_rpc/IdsTest.java b/pw_rpc/java/test/dev/pigweed/pw_rpc/IdsTest.java
deleted file mode 100644
index 1440c6d..0000000
--- a/pw_rpc/java/test/dev/pigweed/pw_rpc/IdsTest.java
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 dev.pigweed.pw_rpc;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-public final class IdsTest {
-  @Test
-  public void calculate_emptyString() {
-    assertThat(Ids.calculate("")).isEqualTo(0);
-  }
-
-  @Test
-  public void calculate_singleCharacter() {
-    assertThat(Ids.calculate("")).isEqualTo(0x00000000);
-    assertThat(Ids.calculate("\0")).isEqualTo(0x00000001);
-    assertThat(Ids.calculate("\1")).isEqualTo(65600);
-    assertThat(Ids.calculate("?")).isEqualTo(4132738);
-  }
-
-  @Test
-  public void calculate_multipleCharacters() {
-    assertThat(Ids.calculate("Pigweed?")).isEqualTo(0x63D43D8C);
-    assertThat(Ids.calculate("\0\0\0\1\1\1\1")).isEqualTo(0xD3556087);
-    assertThat(Ids.calculate("Pigweed!Pigweed!Pigweed!Pigweed!Pigweed!Pigweed!"))
-        .isEqualTo(0x79AB6494);
-  }
-}
diff --git a/pw_rpc/java/test/dev/pigweed/pw_rpc/PacketsTest.java b/pw_rpc/java/test/dev/pigweed/pw_rpc/PacketsTest.java
deleted file mode 100644
index 6a1e771..0000000
--- a/pw_rpc/java/test/dev/pigweed/pw_rpc/PacketsTest.java
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 dev.pigweed.pw_rpc;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.google.protobuf.ExtensionRegistryLite;
-import dev.pigweed.pw_rpc.internal.Packet.PacketType;
-import dev.pigweed.pw_rpc.internal.Packet.RpcPacket;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-public final class PacketsTest {
-  private static final Service SERVICE =
-      new Service("Greetings", Service.unaryMethod("Hello", RpcPacket.class, RpcPacket.class));
-
-  private static final PendingRpc RPC =
-      PendingRpc.create(new Channel(123, null), SERVICE, SERVICE.method("Hello"));
-
-  private static final RpcPacket PACKET = RpcPacket.newBuilder()
-                                              .setChannelId(123)
-                                              .setServiceId(RPC.service().id())
-                                              .setMethodId(RPC.method().id())
-                                              .build();
-
-  @Test
-  public void request() throws Exception {
-    RpcPacket payload = RpcPacket.newBuilder().setType(PacketType.SERVER_STREAM).build();
-    RpcPacket packet = RpcPacket.parseFrom(
-        Packets.request(RPC, payload), ExtensionRegistryLite.getEmptyRegistry());
-    assertThat(packet).isEqualTo(
-        packet().setType(PacketType.REQUEST).setPayload(payload.toByteString()).build());
-  }
-
-  @Test
-  public void cancel() throws Exception {
-    RpcPacket packet =
-        RpcPacket.parseFrom(Packets.cancel(RPC), ExtensionRegistryLite.getEmptyRegistry());
-    assertThat(packet).isEqualTo(
-        packet().setType(PacketType.CLIENT_ERROR).setStatus(Status.CANCELLED.code()).build());
-  }
-
-  @Test
-  public void error() throws Exception {
-    RpcPacket packet = RpcPacket.parseFrom(
-        Packets.error(PACKET, Status.ALREADY_EXISTS), ExtensionRegistryLite.getEmptyRegistry());
-    assertThat(packet).isEqualTo(
-        packet().setType(PacketType.CLIENT_ERROR).setStatus(Status.ALREADY_EXISTS.code()).build());
-  }
-
-  private static RpcPacket.Builder packet() {
-    return RpcPacket.newBuilder()
-        .setChannelId(123)
-        .setServiceId(Ids.calculate("Greetings"))
-        .setMethodId(Ids.calculate("Hello"));
-  }
-}
diff --git a/pw_rpc/java/test/dev/pigweed/pw_rpc/RpcManagerTest.java b/pw_rpc/java/test/dev/pigweed/pw_rpc/RpcManagerTest.java
deleted file mode 100644
index f25c662..0000000
--- a/pw_rpc/java/test/dev/pigweed/pw_rpc/RpcManagerTest.java
+++ /dev/null
@@ -1,149 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 dev.pigweed.pw_rpc;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertThrows;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-
-import com.google.protobuf.MessageLite;
-import dev.pigweed.pw_rpc.internal.Packet.PacketType;
-import dev.pigweed.pw_rpc.internal.Packet.RpcPacket;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-public final class RpcManagerTest {
-  @Rule public final MockitoRule mockito = MockitoJUnit.rule();
-
-  private static final Service SERVICE = new Service("pw.rpc.test1.TheTestService",
-      Service.unaryMethod("SomeUnary", SomeMessage.class, SomeMessage.class),
-      Service.serverStreamingMethod("SomeServerStreaming", SomeMessage.class, SomeMessage.class),
-      Service.clientStreamingMethod("SomeClientStreaming", SomeMessage.class, SomeMessage.class),
-      Service.bidirectionalStreamingMethod(
-          "SomeBidiStreaming", SomeMessage.class, SomeMessage.class));
-
-  private static final Method METHOD = SERVICE.method("SomeUnary");
-
-  private static final SomeMessage REQUEST_PAYLOAD =
-      SomeMessage.newBuilder().setMagicNumber(1337).build();
-  private static final byte[] REQUEST = request(REQUEST_PAYLOAD);
-  private static final int CHANNEL_ID = 555;
-
-  @Mock private Channel.Output mockOutput;
-  @Mock private StreamObserverCall<MessageLite, MessageLite> call;
-
-  private PendingRpc rpc;
-  private RpcManager manager;
-
-  @Before
-  public void setup() {
-    rpc = PendingRpc.create(new Channel(CHANNEL_ID, mockOutput), SERVICE, METHOD);
-    manager = new RpcManager();
-  }
-
-  private static byte[] request(MessageLite payload) {
-    return packetBuilder()
-        .setType(PacketType.REQUEST)
-        .setPayload(payload.toByteString())
-        .build()
-        .toByteArray();
-  }
-
-  private static byte[] cancel() {
-    return packetBuilder()
-        .setType(PacketType.CLIENT_ERROR)
-        .setStatus(Status.CANCELLED.code())
-        .build()
-        .toByteArray();
-  }
-
-  private static RpcPacket.Builder packetBuilder() {
-    return RpcPacket.newBuilder()
-        .setChannelId(CHANNEL_ID)
-        .setServiceId(SERVICE.id())
-        .setMethodId(METHOD.id());
-  }
-
-  @Test
-  public void start_sendingFails_rpcNotPending() throws Exception {
-    doThrow(new ChannelOutputException()).when(mockOutput).send(any());
-
-    assertThrows(ChannelOutputException.class, () -> manager.start(rpc, call, REQUEST_PAYLOAD));
-
-    verify(mockOutput).send(REQUEST);
-    assertThat(manager.getPending(rpc)).isNull();
-  }
-
-  @Test
-  public void start_succeeds_rpcIsPending() throws Exception {
-    assertThat(manager.start(rpc, call, REQUEST_PAYLOAD)).isNull();
-
-    assertThat(manager.getPending(rpc)).isSameInstanceAs(call);
-  }
-
-  @Test
-  public void startThenCancel_rpcNotPending() throws Exception {
-    assertThat(manager.start(rpc, call, REQUEST_PAYLOAD)).isNull();
-    assertThat(manager.cancel(rpc)).isSameInstanceAs(call);
-
-    assertThat(manager.getPending(rpc)).isNull();
-  }
-
-  @Test
-  public void startThenCancel_sendsCancelPacket() throws Exception {
-    assertThat(manager.start(rpc, call, REQUEST_PAYLOAD)).isNull();
-    assertThat(manager.cancel(rpc)).isEqualTo(call);
-
-    verify(mockOutput).send(cancel());
-  }
-
-  @Test
-  public void startThenClear_sendsNothing() throws Exception {
-    verifyNoMoreInteractions(mockOutput);
-
-    assertThat(manager.start(rpc, call, REQUEST_PAYLOAD)).isNull();
-    assertThat(manager.clear(rpc)).isEqualTo(call);
-  }
-
-  @Test
-  public void clear_notPending_returnsNull() {
-    assertThat(manager.clear(rpc)).isNull();
-  }
-
-  @Test
-  public void open_sendingFails_rpcIsPending() throws Exception {
-    doThrow(new ChannelOutputException()).when(mockOutput).send(any());
-
-    assertThat(manager.open(rpc, call, REQUEST_PAYLOAD)).isNull();
-
-    verify(mockOutput).send(REQUEST);
-    assertThat(manager.getPending(rpc)).isSameInstanceAs(call);
-  }
-
-  @Test
-  public void open_success_rpcIsPending() {
-    assertThat(manager.open(rpc, call, REQUEST_PAYLOAD)).isNull();
-
-    assertThat(manager.getPending(rpc)).isSameInstanceAs(call);
-  }
-}
diff --git a/pw_rpc/java/test/dev/pigweed/pw_rpc/StreamObserverCallTest.java b/pw_rpc/java/test/dev/pigweed/pw_rpc/StreamObserverCallTest.java
deleted file mode 100644
index f4eba68..0000000
--- a/pw_rpc/java/test/dev/pigweed/pw_rpc/StreamObserverCallTest.java
+++ /dev/null
@@ -1,270 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 dev.pigweed.pw_rpc;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertThrows;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-import dev.pigweed.pw_rpc.StreamObserverCall.StreamResponseFuture;
-import dev.pigweed.pw_rpc.StreamObserverCall.UnaryResponseFuture;
-import dev.pigweed.pw_rpc.internal.Packet.PacketType;
-import dev.pigweed.pw_rpc.internal.Packet.RpcPacket;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-public final class StreamObserverCallTest {
-  @Rule public final MockitoRule mockito = MockitoJUnit.rule();
-
-  private static final Service SERVICE = new Service("pw.rpc.test1.TheTestService",
-      Service.unaryMethod("SomeUnary", SomeMessage.class, AnotherMessage.class),
-      Service.clientStreamingMethod("SomeClient", SomeMessage.class, AnotherMessage.class),
-      Service.bidirectionalStreamingMethod(
-          "SomeBidirectional", SomeMessage.class, AnotherMessage.class));
-  private static final Method METHOD = SERVICE.method("SomeUnary");
-  private static final int CHANNEL_ID = 555;
-
-  @Mock private StreamObserver<AnotherMessage> observer;
-  @Mock private Channel.Output mockOutput;
-
-  private final RpcManager rpcManager = new RpcManager();
-  private StreamObserverCall<SomeMessage, AnotherMessage> streamObserverCall;
-  private PendingRpc rpc;
-
-  private static byte[] cancel() {
-    return packetBuilder()
-        .setType(PacketType.CLIENT_ERROR)
-        .setStatus(Status.CANCELLED.code())
-        .build()
-        .toByteArray();
-  }
-
-  private static RpcPacket.Builder packetBuilder() {
-    return RpcPacket.newBuilder()
-        .setChannelId(CHANNEL_ID)
-        .setServiceId(SERVICE.id())
-        .setMethodId(METHOD.id());
-  }
-
-  @Before
-  public void createCall() throws Exception {
-    rpc = PendingRpc.create(new Channel(CHANNEL_ID, mockOutput), SERVICE, METHOD);
-    streamObserverCall = StreamObserverCall.start(rpcManager, rpc, observer, null);
-    rpcManager.start(rpc, streamObserverCall, SomeMessage.getDefaultInstance());
-  }
-
-  @Test
-  public void startsActive() {
-    assertThat(streamObserverCall.active()).isTrue();
-  }
-
-  @Test
-  public void startsWithNullStatus() {
-    assertThat(streamObserverCall.status()).isNull();
-  }
-
-  @Test
-  public void cancel_sendsCancelPacket() throws Exception {
-    streamObserverCall.cancel();
-
-    verify(mockOutput).send(cancel());
-  }
-
-  @Test
-  public void cancel_deactivates() throws Exception {
-    streamObserverCall.cancel();
-
-    assertThat(streamObserverCall.active()).isFalse();
-  }
-
-  @Test
-  public void send_sendsClientStreamPacket() throws Exception {
-    SomeMessage request = SomeMessage.newBuilder().setMagicNumber(123).build();
-    streamObserverCall.send(request);
-
-    verify(mockOutput)
-        .send(packetBuilder()
-                  .setType(PacketType.CLIENT_STREAM)
-                  .setPayload(request.toByteString())
-                  .build()
-                  .toByteArray());
-  }
-
-  @Test
-  public void send_raisesExceptionIfClosed() throws Exception {
-    streamObserverCall.cancel();
-
-    RpcError thrown = assertThrows(
-        RpcError.class, () -> streamObserverCall.send(SomeMessage.getDefaultInstance()));
-    assertThat(thrown.status()).isSameInstanceAs(Status.CANCELLED);
-  }
-
-  @Test
-  public void finish_clientStreamEndPacket() throws Exception {
-    streamObserverCall.finish();
-
-    verify(mockOutput)
-        .send(packetBuilder().setType(PacketType.CLIENT_STREAM_END).build().toByteArray());
-  }
-
-  @Test
-  public void onNext_callsObserverIfActive() {
-    streamObserverCall.onNext(AnotherMessage.getDefaultInstance().toByteString());
-
-    verify(observer).onNext(AnotherMessage.getDefaultInstance());
-  }
-
-  @Test
-  public void onNext_ignoresIfNotActive() throws Exception {
-    streamObserverCall.cancel();
-    streamObserverCall.onNext(AnotherMessage.getDefaultInstance().toByteString());
-
-    verify(observer, never()).onNext(any());
-  }
-
-  @Test
-  public void callDispatcher_onCompleted_callsObserver() {
-    streamObserverCall.onCompleted(Status.ABORTED);
-
-    verify(observer).onCompleted(Status.ABORTED);
-  }
-
-  @Test
-  public void callDispatcher_onCompleted_setsActiveAndStatus() {
-    streamObserverCall.onCompleted(Status.ABORTED);
-
-    verify(observer).onCompleted(Status.ABORTED);
-    assertThat(streamObserverCall.active()).isFalse();
-    assertThat(streamObserverCall.status()).isEqualTo(Status.ABORTED);
-  }
-
-  @Test
-  public void callDispatcher_onError_callsObserver() {
-    streamObserverCall.onError(Status.NOT_FOUND);
-
-    verify(observer).onError(Status.NOT_FOUND);
-  }
-
-  @Test
-  public void callDispatcher_onError_deactivates() {
-    streamObserverCall.onError(Status.ABORTED);
-
-    verify(observer).onError(Status.ABORTED);
-    assertThat(streamObserverCall.active()).isFalse();
-    assertThat(streamObserverCall.status()).isNull();
-  }
-
-  @Test
-  public void unaryFuture_response_setsValue() throws Exception {
-    UnaryResponseFuture<SomeMessage, AnotherMessage> call =
-        new UnaryResponseFuture<>(rpcManager, rpc, SomeMessage.getDefaultInstance());
-
-    AnotherMessage response = AnotherMessage.newBuilder().setResultValue(1138).build();
-    call.onNext(response);
-    assertThat(call.isDone()).isFalse();
-    call.onCompleted(Status.CANCELLED);
-
-    assertThat(call.isDone()).isTrue();
-    assertThat(call.get()).isEqualTo(UnaryResult.create(response, Status.CANCELLED));
-  }
-
-  @Test
-  public void unaryFuture_serverError_setsException() throws Exception {
-    UnaryResponseFuture<SomeMessage, AnotherMessage> call =
-        new UnaryResponseFuture<>(rpcManager, rpc, SomeMessage.getDefaultInstance());
-
-    call.onError(Status.NOT_FOUND);
-
-    assertThat(call.isDone()).isTrue();
-    ExecutionException exception = assertThrows(ExecutionException.class, call::get);
-    assertThat(exception).hasCauseThat().isInstanceOf(RpcError.class);
-
-    RpcError error = (RpcError) exception.getCause();
-    assertThat(error).isNotNull();
-    assertThat(error.rpc()).isEqualTo(rpc);
-    assertThat(error.status()).isEqualTo(Status.NOT_FOUND);
-  }
-
-  @Test
-  public void unaryFuture_noMessage_setsException() throws Exception {
-    UnaryResponseFuture<SomeMessage, AnotherMessage> call =
-        new UnaryResponseFuture<>(rpcManager, rpc, SomeMessage.getDefaultInstance());
-
-    call.onCompleted(Status.OK);
-
-    assertThat(call.isDone()).isTrue();
-    ExecutionException exception = assertThrows(ExecutionException.class, call::get);
-    assertThat(exception).hasCauseThat().isInstanceOf(IllegalStateException.class);
-  }
-
-  @Test
-  public void unaryFuture_multipleResponses_setsException() throws Exception {
-    UnaryResponseFuture<SomeMessage, AnotherMessage> call =
-        new UnaryResponseFuture<>(rpcManager, rpc, SomeMessage.getDefaultInstance());
-
-    AnotherMessage response = AnotherMessage.newBuilder().setResultValue(1138).build();
-    call.onNext(response);
-    call.onNext(response);
-    call.onCompleted(Status.OK);
-
-    assertThat(call.isDone()).isTrue();
-    ExecutionException exception = assertThrows(ExecutionException.class, call::get);
-    assertThat(exception).hasCauseThat().isInstanceOf(IllegalStateException.class);
-  }
-
-  @Test
-  public void bidirectionalStreamingfuture_responses_setsValue() throws Exception {
-    List<AnotherMessage> responses = new ArrayList<>();
-    StreamResponseFuture<SomeMessage, AnotherMessage> call =
-        new StreamResponseFuture<>(rpcManager, rpc, responses::add, null);
-
-    AnotherMessage message = AnotherMessage.newBuilder().setResultValue(1138).build();
-    call.onNext(message);
-    call.onNext(message);
-    assertThat(call.isDone()).isFalse();
-    call.onCompleted(Status.OK);
-
-    assertThat(call.isDone()).isTrue();
-    assertThat(call.get()).isEqualTo(Status.OK);
-    assertThat(responses).containsExactly(message, message);
-  }
-
-  @Test
-  public void bidirectionalStreamingfuture_serverError_setsException() throws Exception {
-    StreamResponseFuture<SomeMessage, AnotherMessage> call =
-        new StreamResponseFuture<>(rpcManager, rpc, (msg) -> {}, null);
-
-    call.onError(Status.NOT_FOUND);
-
-    assertThat(call.isDone()).isTrue();
-    ExecutionException exception = assertThrows(ExecutionException.class, call::get);
-    assertThat(exception).hasCauseThat().isInstanceOf(RpcError.class);
-
-    RpcError error = (RpcError) exception.getCause();
-    assertThat(error).isNotNull();
-    assertThat(error.rpc()).isEqualTo(rpc);
-    assertThat(error.status()).isEqualTo(Status.NOT_FOUND);
-  }
-}
diff --git a/pw_rpc/java/test/dev/pigweed/pw_rpc/StreamObserverMethodClientTest.java b/pw_rpc/java/test/dev/pigweed/pw_rpc/StreamObserverMethodClientTest.java
deleted file mode 100644
index 55fd88a..0000000
--- a/pw_rpc/java/test/dev/pigweed/pw_rpc/StreamObserverMethodClientTest.java
+++ /dev/null
@@ -1,190 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 dev.pigweed.pw_rpc;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertThrows;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import com.google.protobuf.MessageLite;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-public final class StreamObserverMethodClientTest {
-  private static final Service SERVICE = new Service("pw.rpc.test1.TheTestService",
-      Service.unaryMethod("SomeUnary", SomeMessage.class, AnotherMessage.class),
-      Service.serverStreamingMethod("SomeServerStreaming", SomeMessage.class, AnotherMessage.class),
-      Service.clientStreamingMethod("SomeClientStreaming", SomeMessage.class, AnotherMessage.class),
-      Service.bidirectionalStreamingMethod(
-          "SomeBidirectionalStreaming", SomeMessage.class, AnotherMessage.class));
-
-  private static final Channel CHANNEL = new Channel(1, (bytes) -> {});
-
-  private static final PendingRpc UNARY_RPC =
-      PendingRpc.create(CHANNEL, SERVICE, SERVICE.method("SomeUnary"));
-  private static final PendingRpc SERVER_STREAMING_RPC =
-      PendingRpc.create(CHANNEL, SERVICE, SERVICE.method("SomeServerStreaming"));
-  private static final PendingRpc CLIENT_STREAMING_RPC =
-      PendingRpc.create(CHANNEL, SERVICE, SERVICE.method("SomeClientStreaming"));
-  private static final PendingRpc BIDIRECTIONAL_STREAMING_RPC =
-      PendingRpc.create(CHANNEL, SERVICE, SERVICE.method("SomeBidirectionalStreaming"));
-
-  @Rule public final MockitoRule mockito = MockitoJUnit.rule();
-
-  @Mock private StreamObserver<MessageLite> defaultObserver;
-
-  private final RpcManager rpcManager = new RpcManager();
-  private MethodClient unaryMethodClient;
-  private MethodClient serverStreamingMethodClient;
-  private MethodClient clientStreamingMethodClient;
-  private MethodClient bidirectionalStreamingMethodClient;
-
-  @Before
-  public void createMethodClient() {
-    unaryMethodClient = new MethodClient(rpcManager, UNARY_RPC, defaultObserver);
-    serverStreamingMethodClient =
-        new MethodClient(rpcManager, SERVER_STREAMING_RPC, defaultObserver);
-    clientStreamingMethodClient =
-        new MethodClient(rpcManager, CLIENT_STREAMING_RPC, defaultObserver);
-    bidirectionalStreamingMethodClient =
-        new MethodClient(rpcManager, BIDIRECTIONAL_STREAMING_RPC, defaultObserver);
-  }
-
-  @Test
-  public void invokeWithNoObserver_usesDefaultObserver() throws Exception {
-    unaryMethodClient.invokeUnary(SomeMessage.getDefaultInstance());
-    AnotherMessage reply = AnotherMessage.newBuilder().setPayload("yo").build();
-    rpcManager.getPending(UNARY_RPC).onNext(reply.toByteString());
-
-    verify(defaultObserver).onNext(reply);
-  }
-
-  @Test
-  public void invoke_usesProvidedObserver() throws Exception {
-    @SuppressWarnings("unchecked")
-    StreamObserver<AnotherMessage> observer =
-        (StreamObserver<AnotherMessage>) mock(StreamObserver.class);
-
-    unaryMethodClient.invokeUnary(SomeMessage.getDefaultInstance(), observer);
-    AnotherMessage reply = AnotherMessage.newBuilder().setPayload("yo").build();
-    rpcManager.getPending(UNARY_RPC).onNext(reply.toByteString());
-
-    verify(observer).onNext(reply);
-  }
-
-  @Test
-  public void invokeUnary_startsRpc() throws Exception {
-    Call call = unaryMethodClient.invokeUnary(SomeMessage.getDefaultInstance());
-    assertThat(rpcManager.getPending(UNARY_RPC)).isSameInstanceAs(call);
-  }
-
-  @Test
-  public void openUnary_startsRpc() {
-    Call call = unaryMethodClient.openUnary(SomeMessage.getDefaultInstance(), defaultObserver);
-    assertThat(rpcManager.getPending(UNARY_RPC)).isSameInstanceAs(call);
-  }
-
-  @Test
-  public void invokeServerStreaming_startsRpc() throws Exception {
-    Call call = serverStreamingMethodClient.invokeServerStreaming(SomeMessage.getDefaultInstance());
-    assertThat(rpcManager.getPending(SERVER_STREAMING_RPC)).isSameInstanceAs(call);
-  }
-
-  @Test
-  public void openServerStreaming_startsRpc() {
-    Call call = serverStreamingMethodClient.openServerStreaming(
-        SomeMessage.getDefaultInstance(), defaultObserver);
-    assertThat(rpcManager.getPending(SERVER_STREAMING_RPC)).isSameInstanceAs(call);
-  }
-
-  @Test
-  public void invokeClientStreaming_startsRpc() throws Exception {
-    Call call = clientStreamingMethodClient.invokeClientStreaming();
-    assertThat(rpcManager.getPending(CLIENT_STREAMING_RPC)).isSameInstanceAs(call);
-  }
-
-  @Test
-  public void openClientStreaming_startsRpc() {
-    Call call = clientStreamingMethodClient.openClientStreaming(defaultObserver);
-    assertThat(rpcManager.getPending(CLIENT_STREAMING_RPC)).isSameInstanceAs(call);
-  }
-
-  @Test
-  public void invokeBidirectionalStreaming_startsRpc() throws Exception {
-    Call call = bidirectionalStreamingMethodClient.invokeBidirectionalStreaming();
-    assertThat(rpcManager.getPending(BIDIRECTIONAL_STREAMING_RPC)).isSameInstanceAs(call);
-  }
-
-  @Test
-  public void openBidirectionalStreaming_startsRpc() {
-    Call call = bidirectionalStreamingMethodClient.openBidirectionalStreaming(defaultObserver);
-    assertThat(rpcManager.getPending(BIDIRECTIONAL_STREAMING_RPC)).isSameInstanceAs(call);
-  }
-
-  @Test
-  public void invokeUnaryFuture_startsRpc() {
-    unaryMethodClient.invokeUnaryFuture(SomeMessage.getDefaultInstance());
-    assertThat(rpcManager.getPending(UNARY_RPC)).isNotNull();
-  }
-
-  @Test
-  public void invokeServerStreamingFuture_startsRpc() {
-    serverStreamingMethodClient.invokeServerStreamingFuture(
-        SomeMessage.getDefaultInstance(), (msg) -> {});
-    assertThat(rpcManager.getPending(SERVER_STREAMING_RPC)).isNotNull();
-  }
-
-  @Test
-  public void invokeClientStreamingFuture_startsRpc() {
-    clientStreamingMethodClient.invokeClientStreamingFuture();
-    assertThat(rpcManager.getPending(CLIENT_STREAMING_RPC)).isNotNull();
-  }
-
-  @Test
-  public void invokeBidirectionalStreamingFuture_startsRpc() {
-    bidirectionalStreamingMethodClient.invokeBidirectionalStreamingFuture((msg) -> {});
-    assertThat(rpcManager.getPending(BIDIRECTIONAL_STREAMING_RPC)).isNotNull();
-  }
-
-  @Test
-  public void invokeUnary_serverStreamingRpc_throwsException() {
-    assertThrows(UnsupportedOperationException.class,
-        () -> serverStreamingMethodClient.invokeUnary(SomeMessage.getDefaultInstance()));
-  }
-
-  @Test
-  public void invokeServerStreaming_unaryRpc_throwsException() {
-    assertThrows(UnsupportedOperationException.class,
-        () -> unaryMethodClient.invokeServerStreaming(SomeMessage.getDefaultInstance()));
-  }
-
-  @Test
-  public void invokeClientStreaming_bidirectionalStreamingRpc_throwsException() {
-    assertThrows(UnsupportedOperationException.class,
-        () -> bidirectionalStreamingMethodClient.invokeUnary(SomeMessage.getDefaultInstance()));
-  }
-
-  @Test
-  public void invokeBidirectionalStreaming_clientStreamingRpc_throwsException() {
-    assertThrows(UnsupportedOperationException.class,
-        () -> clientStreamingMethodClient.invokeBidirectionalStreaming());
-  }
-}
diff --git a/pw_rpc/java/test/dev/pigweed/pw_rpc/TestClient.java b/pw_rpc/java/test/dev/pigweed/pw_rpc/TestClient.java
deleted file mode 100644
index 6688e24..0000000
--- a/pw_rpc/java/test/dev/pigweed/pw_rpc/TestClient.java
+++ /dev/null
@@ -1,195 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 dev.pigweed.pw_rpc;
-
-import static java.util.Arrays.stream;
-
-import com.google.common.collect.ImmutableList;
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.google.protobuf.MessageLite;
-import dev.pigweed.pw_rpc.internal.Packet.PacketType;
-import dev.pigweed.pw_rpc.internal.Packet.RpcPacket;
-import java.util.ArrayList;
-import java.util.EnumMap;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-import javax.annotation.Nullable;
-
-/**
- * Wraps a StreamObserverMethodClient for use in tests. Provides methods for simulating the server
- * interactions with the client.
- */
-public class TestClient {
-  private static final int CHANNEL_ID = 1;
-
-  private final Client client;
-
-  private final List<RpcPacket> sentPackets = new ArrayList<>();
-  private final List<RpcPacket> enqueuedPackets = new ArrayList<>();
-  private int receiveEnqueuedPacketsAfter = 1;
-  final Map<PacketType, Integer> sentPayloadIndices = new EnumMap<>(PacketType.class);
-
-  @Nullable ChannelOutputException channelOutputException = null;
-
-  public TestClient(List<Service> services) {
-    Channel.Output channelOutput = packet -> {
-      if (channelOutputException == null) {
-        sentPackets.add(parsePacket(packet));
-      } else {
-        throw channelOutputException;
-      }
-
-      // Process any enqueued packets.
-      if (receiveEnqueuedPacketsAfter > 1) {
-        receiveEnqueuedPacketsAfter -= 1;
-        return;
-      }
-      if (!enqueuedPackets.isEmpty()) {
-        List<RpcPacket> packetsToProcess = new ArrayList<>(enqueuedPackets);
-        enqueuedPackets.clear();
-        packetsToProcess.forEach(this::processPacket);
-      }
-    };
-    client = Client.create(ImmutableList.of(new Channel(CHANNEL_ID, channelOutput)), services);
-  }
-
-  public Client client() {
-    return client;
-  }
-
-  /**
-   * Sets the exception to throw the next time a packet is sent. Set to null to accept the packet
-   * without errors.
-   *
-   * <p>When Channel.Output throws an exception, TestClient does not store those outgoing packets.
-   */
-  public void setChannelOutputException(@Nullable ChannelOutputException exception) {
-    this.channelOutputException = exception;
-  }
-
-  /** Returns all payloads that were sent since the last latestClientStreams call. */
-  public <T extends MessageLite> List<T> lastClientStreams(Class<T> payloadType) {
-    return sentPayloads(payloadType, PacketType.CLIENT_STREAM);
-  }
-
-  /** Simulates receiving SERVER_STREAM packets from the server. */
-  public void receiveServerStream(String service, String method, MessageLite... payloads) {
-    RpcPacket base = startPacket(service, method, PacketType.SERVER_STREAM).build();
-    for (MessageLite payload : payloads) {
-      processPacket(RpcPacket.newBuilder(base).setPayload(payload.toByteString()));
-    }
-  }
-
-  public void receiveServerStream(String service, String method, MessageLite.Builder... builders) {
-    receiveServerStream(service,
-        method,
-        stream(builders).map(MessageLite.Builder::build).toArray(MessageLite[] ::new));
-  }
-
-  /**
-   * Enqueues a SERVER_STREAM packet so that the client receives it after a packet is sent.
-   *
-   * @param afterPackets Wait until this many packets have been sent before the client receives
-   *     these stream packets. The minimum value (and the default) is 1.
-   */
-  public void enqueueServerStream(
-      String service, String method, int afterPackets, MessageLite... payloads) {
-    if (afterPackets < 1) {
-      throw new IllegalArgumentException("afterPackets must be at least 1");
-    }
-    if (afterPackets != 1 && receiveEnqueuedPacketsAfter != 1) {
-      throw new AssertionError(
-          "May only set afterPackets once before enqueued packets are processed");
-    }
-    receiveEnqueuedPacketsAfter = afterPackets;
-
-    RpcPacket base = startPacket(service, method, PacketType.SERVER_STREAM).build();
-    for (MessageLite payload : payloads) {
-      enqueuedPackets.add(RpcPacket.newBuilder(base).setPayload(payload.toByteString()).build());
-    }
-  }
-
-  public void enqueueServerStream(String service, String method, MessageLite... payloads) {
-    enqueueServerStream(service, method, 1, payloads);
-  }
-
-  public void enqueueServerStream(
-      String service, String method, int afterPackets, MessageLite.Builder... builders) {
-    enqueueServerStream(service,
-        method,
-        afterPackets,
-        stream(builders).map(MessageLite.Builder::build).toArray(MessageLite[] ::new));
-  }
-
-  /** Simulates receiving a SERVER_ERROR packet from the server. */
-  public void receiveServerError(String service, String method, Status error) {
-    processPacket(startPacket(service, method, PacketType.SERVER_ERROR).setStatus(error.code()));
-  }
-
-  /** Parses sent payloads for the given type of packet. */
-  private <T extends MessageLite> List<T> sentPayloads(Class<T> payloadType, PacketType type) {
-    int sentPayloadIndex = sentPayloadIndices.getOrDefault(type, 0);
-
-    // Filter only the specified packets.
-    List<T> newPayloads = sentPackets.stream()
-                              .filter(packet -> packet.getType().equals(type))
-                              .skip(sentPayloadIndex)
-                              .map(p -> parseRequestPayload(payloadType, p))
-                              .collect(Collectors.toList());
-
-    // Store the index of the last read payload. Could drop the viewed packets instead to reduce
-    // memory usage, but that probably won't matter in practice.
-    sentPayloadIndices.put(type, sentPayloadIndex + newPayloads.size());
-
-    return newPayloads;
-  }
-
-  private void processPacket(RpcPacket packet) {
-    if (!client.processPacket(packet.toByteArray())) {
-      throw new AssertionError("TestClient failed to process a packet!");
-    }
-  }
-
-  private void processPacket(RpcPacket.Builder packet) {
-    processPacket(packet.build());
-  }
-
-  private static RpcPacket.Builder startPacket(String service, String method, PacketType type) {
-    return RpcPacket.newBuilder()
-        .setType(type)
-        .setChannelId(CHANNEL_ID)
-        .setServiceId(Ids.calculate(service))
-        .setMethodId(Ids.calculate(method));
-  }
-
-  private static RpcPacket parsePacket(byte[] packet) {
-    try {
-      return RpcPacket.parseFrom(packet);
-    } catch (InvalidProtocolBufferException e) {
-      throw new AssertionError("Decoding sent packet failed", e);
-    }
-  }
-
-  private <T extends MessageLite> T parseRequestPayload(Class<T> payloadType, RpcPacket packet) {
-    try {
-      return payloadType.cast(Method.decodeProtobuf(
-          client.method(CHANNEL_ID, packet.getServiceId(), packet.getMethodId()).method().request(),
-          packet.getPayload()));
-    } catch (InvalidProtocolBufferException e) {
-      throw new AssertionError("Decoding sent packet payload failed", e);
-    }
-  }
-}
diff --git a/pw_rpc/java/test/dev/pigweed/pw_rpc/test.proto b/pw_rpc/java/test/dev/pigweed/pw_rpc/test.proto
deleted file mode 100644
index 89455c1..0000000
--- a/pw_rpc/java/test/dev/pigweed/pw_rpc/test.proto
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// Protobuf definition to use in tests.
-syntax = "proto3";
-
-package pw.rpc.test1;
-
-option java_multiple_files = true;
-option java_package = "dev.pigweed.pw_rpc";
-
-message SomeMessage {
-  uint32 magic_number = 1;
-}
-
-message AnotherMessage {
-  enum Result {
-    FAILED = 0;
-    FAILED_MISERABLY = 1;
-    I_DONT_WANT_TO_TALK_ABOUT_IT = 2;
-  }
-
-  Result result = 1;
-  string payload = 2;
-}
-
-service TheTestService {
-  // Unary RPC for testing
-  rpc SomeUnary(SomeMessage) returns (AnotherMessage) {}
-
-  // Server streaming RPC for testing
-  rpc SomeServerStreaming(SomeMessage) returns (stream AnotherMessage) {}
-
-  // Client streaming RPC for testing
-  rpc SomeClientStreaming(stream SomeMessage) returns (AnotherMessage) {}
-
-  // Bidirectional streaming RPC for testing
-  rpc SomeBidiStreaming(stream SomeMessage) returns (stream AnotherMessage) {}
-}
diff --git a/pw_rpc/method_test.cc b/pw_rpc/method_test.cc
deleted file mode 100644
index 597af2f..0000000
--- a/pw_rpc/method_test.cc
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/internal/method.h"
-
-#include <array>
-
-#include "gtest/gtest.h"
-#include "pw_rpc/internal/packet.h"
-#include "pw_rpc/internal/test_method.h"
-#include "pw_rpc/method_type.h"
-#include "pw_rpc/server.h"
-
-namespace pw::rpc::internal {
-namespace {
-
-// Test the helper functions for the MethodType enum.
-static_assert(!HasServerStream(MethodType::kUnary));
-static_assert(HasServerStream(MethodType::kServerStreaming));
-static_assert(!HasServerStream(MethodType::kClientStreaming));
-static_assert(HasServerStream(MethodType::kBidirectionalStreaming));
-
-static_assert(!HasClientStream(MethodType::kUnary));
-static_assert(!HasClientStream(MethodType::kServerStreaming));
-static_assert(HasClientStream(MethodType::kClientStreaming));
-static_assert(HasClientStream(MethodType::kBidirectionalStreaming));
-
-class TestService : public Service {
- public:
-  TestService() : Service(5678, kMethods) {}
-
-  static constexpr std::array<TestMethodUnion, 1> kMethods = {TestMethod(1234)};
-};
-
-const TestMethod& kTestMethod = TestService::kMethods.front().test_method();
-
-TEST(Method, Id) { EXPECT_EQ(kTestMethod.id(), 1234u); }
-
-TEST(Method, Invoke) {
-  class NullChannelOutput final : public ChannelOutput {
-   public:
-    constexpr NullChannelOutput() : ChannelOutput("NullChannelOutput") {}
-
-    Status Send(ConstByteSpan) override { return OkStatus(); }
-  } channel_output;
-
-  Channel channel(123, &channel_output);
-  Server server(std::span(static_cast<rpc::Channel*>(&channel), 1));
-  TestService service;
-
-  const CallContext context(server, channel.id(), service, kTestMethod, 0);
-  Packet empty_packet;
-
-  EXPECT_EQ(kTestMethod.invocations(), 0u);
-  rpc_lock().lock();
-  kTestMethod.Invoke(context, empty_packet);
-  EXPECT_EQ(kTestMethod.invocations(), 1u);
-}
-
-}  // namespace
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/nanopb/BUILD.bazel b/pw_rpc/nanopb/BUILD.bazel
deleted file mode 100644
index f5a87f6..0000000
--- a/pw_rpc/nanopb/BUILD.bazel
+++ /dev/null
@@ -1,262 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "server_api",
-    srcs = [
-        "method.cc",
-        "server_reader_writer.cc",
-    ],
-    hdrs = [
-        "public/pw_rpc/nanopb/internal/method.h",
-        "public/pw_rpc/nanopb/internal/method_union.h",
-        "public/pw_rpc/nanopb/server_reader_writer.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":common",
-        "//pw_rpc/raw:server_api",
-    ],
-)
-
-pw_cc_library(
-    name = "client_api",
-    hdrs = [
-        "public/pw_rpc/nanopb/client_reader_writer.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":common",
-    ],
-)
-
-pw_cc_library(
-    name = "common",
-    srcs = ["common.cc"],
-    hdrs = [
-        "public/pw_rpc/nanopb/internal/common.h",
-        "public/pw_rpc/nanopb/server_reader_writer.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_rpc",
-        "@com_github_nanopb_nanopb//:nanopb",
-    ],
-)
-
-pw_cc_library(
-    name = "test_method_context",
-    hdrs = [
-        "public/pw_rpc/nanopb/fake_channel_output.h",
-        "public/pw_rpc/nanopb/test_method_context.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_containers",
-        "//pw_rpc:internal_test_utils",
-    ],
-)
-
-pw_cc_library(
-    name = "client_testing",
-    hdrs = [
-        "public/pw_rpc/nanopb/client_testing.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":test_method_context",
-        "//pw_rpc",
-        "//pw_rpc/raw:client_testing",
-    ],
-)
-
-pw_cc_library(
-    name = "internal_test_utils",
-    hdrs = ["pw_rpc_nanopb_private/internal_test_utils.h"],
-    deps = ["//pw_rpc:internal_test_utils"],
-)
-
-pw_cc_library(
-    name = "echo_service",
-    hdrs = ["public/pw_rpc/echo_service_nanopb.h"],
-    deps = [
-        "//pw_rpc:echo_cc.nanopb_rpc",
-    ],
-)
-
-# TODO(pwbug/507): Enable this library when logging_event_handler can be used.
-filegroup(
-    name = "client_integration_test",
-    srcs = [
-        "client_integration_test.cc",
-    ],
-    #deps = [
-    #    "//pw_rpc:integration_testing",
-    #    "//pw_sync:binary_semaphore",
-    #    "//pw_rpc:benchmark_cc.nanopb_rpc",
-    #]
-)
-
-pw_cc_test(
-    name = "client_call_test",
-    srcs = [
-        "client_call_test.cc",
-    ],
-    deps = [
-        ":client_api",
-        ":internal_test_utils",
-        "//pw_rpc",
-        "//pw_rpc:pw_rpc_test_cc.nanopb",
-    ],
-)
-
-pw_cc_test(
-    name = "client_reader_writer_test",
-    srcs = [
-        "client_reader_writer_test.cc",
-    ],
-    deps = [
-        ":client_api",
-        ":client_testing",
-        "//pw_rpc:pw_rpc_test_cc.nanopb_rpc",
-    ],
-)
-
-pw_cc_test(
-    name = "codegen_test",
-    srcs = [
-        "codegen_test.cc",
-    ],
-    deps = [
-        ":internal_test_utils",
-        ":test_method_context",
-        "//pw_preprocessor",
-        "//pw_rpc:internal_test_utils",
-        "//pw_rpc:pw_rpc_test_cc.nanopb_rpc",
-    ],
-)
-
-pw_cc_test(
-    name = "fake_channel_output_test",
-    srcs = ["fake_channel_output_test.cc"],
-    deps = [
-        ":common",
-        ":server_api",
-        ":test_method_context",
-        "//pw_rpc:internal_test_utils",
-        "//pw_rpc:pw_rpc_test_cc.nanopb_rpc",
-    ],
-)
-
-pw_cc_test(
-    name = "method_test",
-    srcs = ["method_test.cc"],
-    deps = [
-        ":internal_test_utils",
-        ":server_api",
-        "//pw_rpc",
-        "//pw_rpc:internal_test_utils",
-        "//pw_rpc:pw_rpc_test_cc.nanopb",
-    ],
-)
-
-pw_cc_test(
-    name = "method_info_test",
-    srcs = ["method_info_test.cc"],
-    deps = [
-        "//pw_rpc",
-        "//pw_rpc:internal_test_utils",
-        "//pw_rpc:pw_rpc_test_cc.nanopb_rpc",
-    ],
-)
-
-pw_cc_test(
-    name = "method_lookup_test",
-    srcs = ["method_lookup_test.cc"],
-    deps = [
-        ":test_method_context",
-        "//pw_rpc:pw_rpc_test_cc.nanopb_rpc",
-        "//pw_rpc/raw:test_method_context",
-    ],
-)
-
-pw_cc_test(
-    name = "method_union_test",
-    srcs = ["method_union_test.cc"],
-    deps = [
-        ":internal_test_utils",
-        ":server_api",
-        "//pw_rpc:internal_test_utils",
-        "//pw_rpc:pw_rpc_test_cc.nanopb",
-    ],
-)
-
-# TODO(pwbug/628): Requires nanopb options file support to compile.
-filegroup(
-    name = "echo_service_test",
-    srcs = ["echo_service_test.cc"],
-    #     deps = [
-    #       ":echo_service",
-    #       ":test_method_context",
-    #     ],
-)
-
-pw_cc_test(
-    name = "server_reader_writer_test",
-    srcs = ["server_reader_writer_test.cc"],
-    deps = [
-        ":server_api",
-        ":test_method_context",
-        "//pw_rpc",
-        "//pw_rpc:pw_rpc_test_cc.nanopb_rpc",
-    ],
-)
-
-pw_cc_test(
-    name = "serde_test",
-    srcs = ["serde_test.cc"],
-    deps = [
-        ":common",
-        "//pw_rpc:pw_rpc_test_cc.nanopb",
-    ],
-)
-
-pw_cc_test(
-    name = "server_callback_test",
-    srcs = ["server_callback_test.cc"],
-    deps = [
-        ":test_method_context",
-        "//pw_rpc",
-        "//pw_rpc:pw_rpc_test_cc.nanopb_rpc",
-        "@com_github_nanopb_nanopb//:nanopb",
-    ],
-)
-
-pw_cc_test(
-    name = "stub_generation_test",
-    srcs = ["stub_generation_test.cc"],
-    deps = [
-        "//pw_rpc:pw_rpc_test_cc.nanopb_rpc",
-    ],
-)
diff --git a/pw_rpc/nanopb/BUILD.gn b/pw_rpc/nanopb/BUILD.gn
index a2ec001..c2e7788 100644
--- a/pw_rpc/nanopb/BUILD.gn
+++ b/pw_rpc/nanopb/BUILD.gn
@@ -24,54 +24,42 @@
   visibility = [ ":*" ]
 }
 
-pw_source_set("server_api") {
+pw_source_set("method") {
   public_configs = [ ":public" ]
-  public = [
-    "public/pw_rpc/nanopb/internal/method.h",
-    "public/pw_rpc/nanopb/internal/method_union.h",
-    "public/pw_rpc/nanopb/server_reader_writer.h",
-  ]
-  sources = [
-    "method.cc",
-    "server_reader_writer.cc",
-  ]
+  public = [ "public/pw_rpc/internal/nanopb_method.h" ]
+  sources = [ "nanopb_method.cc" ]
   public_deps = [
     ":common",
-    "$dir_pw_rpc/raw:server_api",
     "..:config",
     "..:server",
-    dir_pw_bytes,
   ]
-  deps = [
-    "..:log_config",
-    dir_pw_log,
-  ]
-  allow_circular_includes_from = [ ":common" ]
+  deps = [ dir_pw_log ]
 }
 
-pw_source_set("client_api") {
+pw_source_set("method_union") {
+  public_configs = [ ":public" ]
+  public = [ "public/pw_rpc/internal/nanopb_method_union.h" ]
+  public_deps = [
+    ":method",
+    "$dir_pw_rpc/raw:method_union",
+  ]
+}
+
+pw_source_set("client") {
   public_configs = [ ":public" ]
   public_deps = [
     ":common",
     "..:client",
-    dir_pw_function,
   ]
-  public = [ "public/pw_rpc/nanopb/client_reader_writer.h" ]
+  public = [ "public/pw_rpc/nanopb_client_call.h" ]
+  sources = [ "nanopb_client_call.cc" ]
 }
 
 pw_source_set("common") {
-  public_deps = [
-    "..:common",
-    dir_pw_bytes,
-  ]
+  public_deps = [ dir_pw_bytes ]
   public_configs = [ ":public" ]
-  deps = [
-    "..:client",
-    "..:log_config",
-    dir_pw_log,
-  ]
-  public = [ "public/pw_rpc/nanopb/internal/common.h" ]
-  sources = [ "common.cc" ]
+  public = [ "public/pw_rpc/internal/nanopb_common.h" ]
+  sources = [ "nanopb_common.cc" ]
 
   if (dir_pw_third_party_nanopb != "") {
     public_deps += [ "$dir_pw_third_party/nanopb" ]
@@ -80,27 +68,14 @@
 
 pw_source_set("test_method_context") {
   public_configs = [ ":public" ]
-  public = [
-    "public/pw_rpc/nanopb/fake_channel_output.h",
-    "public/pw_rpc/nanopb/test_method_context.h",
-  ]
+  public = [ "public/pw_rpc/nanopb_test_method_context.h" ]
   public_deps = [
-    ":server_api",
-    "..:test_utils",
+    ":method",
     dir_pw_assert,
     dir_pw_containers,
   ]
 }
 
-pw_source_set("client_testing") {
-  public = [ "public/pw_rpc/nanopb/client_testing.h" ]
-  public_deps = [
-    ":test_method_context",
-    "..:client",
-    "../raw:client_testing",
-  ]
-}
-
 pw_source_set("internal_test_utils") {
   public = [ "pw_rpc_nanopb_private/internal_test_utils.h" ]
   public_deps = []
@@ -115,18 +90,6 @@
   sources = [ "public/pw_rpc/echo_service_nanopb.h" ]
 }
 
-pw_source_set("client_integration_test") {
-  public_configs = [ ":public" ]
-  public_deps = [
-    "$dir_pw_sync:binary_semaphore",
-    "..:integration_testing",
-    "..:protos.nanopb_rpc",
-    dir_pw_assert,
-    dir_pw_unit_test,
-  ]
-  sources = [ "client_integration_test.cc" ]
-}
-
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
@@ -134,48 +97,32 @@
 pw_test_group("tests") {
   tests = [
     ":client_call_test",
-    ":client_reader_writer_test",
     ":codegen_test",
     ":echo_service_test",
-    ":fake_channel_output_test",
     ":method_lookup_test",
-    ":method_test",
-    ":method_info_test",
-    ":method_union_test",
-    ":server_callback_test",
-    ":server_reader_writer_test",
-    ":serde_test",
+    ":nanopb_method_test",
+    ":nanopb_method_union_test",
     ":stub_generation_test",
   ]
 }
 
 pw_test("client_call_test") {
   deps = [
-    ":client_api",
+    ":client",
     ":internal_test_utils",
     "..:test_protos.nanopb",
     "..:test_utils",
   ]
-  sources = [ "client_call_test.cc" ]
-  enable_if = dir_pw_third_party_nanopb != ""
-}
-
-pw_test("client_reader_writer_test") {
-  deps = [
-    ":client_api",
-    ":client_testing",
-    "..:test_protos.nanopb_rpc",
-  ]
-  sources = [ "client_reader_writer_test.cc" ]
+  sources = [ "nanopb_client_call_test.cc" ]
   enable_if = dir_pw_third_party_nanopb != ""
 }
 
 pw_test("codegen_test") {
   deps = [
-    ":client_api",
+    ":client",
     ":internal_test_utils",
-    ":server_api",
     ":test_method_context",
+    "..:server",
     "..:test_protos.nanopb_rpc",
     "..:test_utils",
   ]
@@ -183,41 +130,21 @@
   enable_if = dir_pw_third_party_nanopb != ""
 }
 
-pw_test("fake_channel_output_test") {
-  deps = [
-    ":server_api",
-    ":test_method_context",
-    "..:test_protos.nanopb_rpc",
-  ]
-  sources = [ "fake_channel_output_test.cc" ]
-  enable_if = dir_pw_third_party_nanopb != ""
-}
-
-pw_test("method_test") {
+pw_test("nanopb_method_test") {
   deps = [
     ":internal_test_utils",
-    ":server_api",
-    ":server_api",
+    ":method_union",
+    "..:server",
     "..:test_protos.nanopb",
     "..:test_utils",
   ]
-  sources = [ "method_test.cc" ]
-  enable_if = dir_pw_third_party_nanopb != ""
-}
-
-pw_test("method_info_test") {
-  deps = [
-    "..:common",
-    "..:test_protos.nanopb_rpc",
-    "..:test_utils",
-  ]
-  sources = [ "method_info_test.cc" ]
+  sources = [ "nanopb_method_test.cc" ]
   enable_if = dir_pw_third_party_nanopb != ""
 }
 
 pw_test("method_lookup_test") {
   deps = [
-    ":server_api",
+    ":method",
     ":test_method_context",
     "..:test_protos.nanopb_rpc",
     "..:test_utils",
@@ -227,56 +154,27 @@
   enable_if = dir_pw_third_party_nanopb != ""
 }
 
-pw_test("method_union_test") {
+pw_test("nanopb_method_union_test") {
   deps = [
     ":internal_test_utils",
-    ":server_api",
+    ":method_union",
     "..:test_protos.nanopb",
     "..:test_utils",
   ]
-  sources = [ "method_union_test.cc" ]
+  sources = [ "nanopb_method_union_test.cc" ]
   enable_if = dir_pw_third_party_nanopb != ""
 }
 
 pw_test("echo_service_test") {
   deps = [
     ":echo_service",
-    ":server_api",
+    ":method",
     ":test_method_context",
   ]
   sources = [ "echo_service_test.cc" ]
   enable_if = dir_pw_third_party_nanopb != ""
 }
 
-pw_test("serde_test") {
-  deps = [
-    ":server_api",
-    "..:test_protos.nanopb",
-  ]
-  sources = [ "serde_test.cc" ]
-  enable_if = dir_pw_third_party_nanopb != ""
-}
-
-pw_test("server_callback_test") {
-  deps = [
-    ":server_api",
-    ":test_method_context",
-    "..:test_protos.nanopb_rpc",
-  ]
-  sources = [ "server_callback_test.cc" ]
-  enable_if = dir_pw_third_party_nanopb != ""
-}
-
-pw_test("server_reader_writer_test") {
-  deps = [
-    ":server_api",
-    ":test_method_context",
-    "..:test_protos.nanopb_rpc",
-  ]
-  sources = [ "server_reader_writer_test.cc" ]
-  enable_if = dir_pw_third_party_nanopb != ""
-}
-
 pw_test("stub_generation_test") {
   deps = [ "..:test_protos.nanopb_rpc" ]
   sources = [ "stub_generation_test.cc" ]
diff --git a/pw_rpc/nanopb/CMakeLists.txt b/pw_rpc/nanopb/CMakeLists.txt
index 2b5d36c..87552a0 100644
--- a/pw_rpc/nanopb/CMakeLists.txt
+++ b/pw_rpc/nanopb/CMakeLists.txt
@@ -16,17 +16,13 @@
 
 pw_add_module_library(pw_rpc.nanopb.method
   SOURCES
-    method.cc
-    server_reader_writer.cc
+    nanopb_method.cc
   PUBLIC_DEPS
     pw_rpc.nanopb.common
     pw_rpc.server
   PRIVATE_DEPS
     pw_log
 )
-if(Zephyr_FOUND AND CONFIG_PIGWEED_RPC_NANOPB_METHOD)
-  zephyr_link_libraries(pw_rpc.nanopb.method)
-endif()
 
 pw_add_module_library(pw_rpc.nanopb.method_union
   PUBLIC_DEPS
@@ -36,40 +32,28 @@
   PRIVATE_DEPS
     pw_log
 )
-if(Zephyr_FOUND AND CONFIG_PIGWEED_RPC_NANOPB_METHOD_UNION)
-  zephyr_link_libraries(pw_rpc.nanopb.method_union)
-endif()
 
 pw_add_module_library(pw_rpc.nanopb.client
+  SOURCES
+    nanopb_client_call.cc
   PUBLIC_DEPS
-    pw_function
     pw_rpc.nanopb.common
     pw_rpc.common
 )
-if(Zephyr_FOUND AND CONFIG_PIGWEED_RPC_NANOPB_CLIENT)
-  zephyr_link_libraries(pw_rpc.nanopb.client)
-endif()
 
 pw_add_module_library(pw_rpc.nanopb.common
   SOURCES
-    common.cc
+    nanopb_common.cc
   PUBLIC_DEPS
     pw_bytes
-    pw_log
     pw_rpc.common
     pw_third_party.nanopb
 )
-if(Zephyr_FOUND AND CONFIG_PIGWEED_RPC_NANOPB_COMMON)
-  zephyr_link_libraries(pw_rpc.nanopb.common)
-endif()
 
 pw_add_module_library(pw_rpc.nanopb.echo_service
   PUBLIC_DEPS
     pw_rpc.protos.nanopb_rpc
 )
-if(Zephyr_FOUND AND CONFIG_PIGWEED_RPC_NANOPB_ECHO_SERVICE)
-  zephyr_link_libraries(pw_rpc.nanopb.echo_service)
-endif()
 
 pw_auto_add_module_tests(pw_rpc.nanopb
   PRIVATE_DEPS
diff --git a/pw_rpc/nanopb/Kconfig b/pw_rpc/nanopb/Kconfig
deleted file mode 100644
index 649707f..0000000
--- a/pw_rpc/nanopb/Kconfig
+++ /dev/null
@@ -1,53 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-menuconfig PIGWEED_RPC_NANOPB
-    bool "Pigweed RPC nanobp"
-
-if PIGWEED_RPC_NANOPB
-
-config PIGWEED_RPC_NANOPB_DIR
-    string "Optional 3rd party directory for nanopb"
-    help
-      The directory for the custom nanopb build rules to integrate with pigweed.
-
-config PIGWEED_RPC_NANOPB_METHOD
-    bool "Enable Pigweed RPC/Nanopb method library (pw_rpc.nanopb.method)"
-    select PIGWEED_RPC_NANOPB_COMMON
-    select PIGWEED_RPC_SERVER
-    select PIGWEED_LOG
-
-config PIGWEED_RPC_NANOPB_METHOD_UNION
-    bool "Enable Pigweed RPC/Nanopb method union library (pw_rpc.nanopb.method_union)"
-    select PIGWEED_RPC_NANOPB_METHOD
-    select PIGWEED_RPC_RAW
-    select PIGWEED_RPC_SERVER
-    select PIGWEED_LOG
-
-config PIGWEED_RPC_NANOPB_CLIENT
-    bool "Enable Pigweed RPC/Nanopb client library (pw_rpc.nanopb.client)"
-    select PIGWEED_FUNCTION
-    select PIGWEED_RPC_NANOPB_COMMON
-    select PIGWEED_RPC_COMMON
-
-config PIGWEED_RPC_NANOPB_COMMON
-    bool "Enable Pigweed RPC/Nanopb common library (pw_rpc.nanopb.common)"
-    select PIGWEED_BYTES
-    select PIGWEED_LOG
-    select PIGWEED_RPC_COMMON
-
-config PIGWEED_RPC_NANOPB_ECHO_SERVICE
-    bool "Enable Pigweed RPC/Nanopb echo service library (pw_rpc.nanopb.echo_service)"
-
-endif # PIGWEED_RPC_NANOPB
\ No newline at end of file
diff --git a/pw_rpc/nanopb/client_call_test.cc b/pw_rpc/nanopb/client_call_test.cc
deleted file mode 100644
index 8376205..0000000
--- a/pw_rpc/nanopb/client_call_test.cc
+++ /dev/null
@@ -1,394 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <optional>
-
-#include "gtest/gtest.h"
-#include "pw_rpc/internal/test_utils.h"
-#include "pw_rpc/nanopb/client_reader_writer.h"
-#include "pw_rpc_nanopb_private/internal_test_utils.h"
-#include "pw_rpc_test_protos/test.pb.h"
-
-PW_MODIFY_DIAGNOSTICS_PUSH();
-PW_MODIFY_DIAGNOSTIC(ignored, "-Wmissing-field-initializers");
-
-namespace pw::rpc {
-namespace {
-
-using internal::ClientContextForTest;
-
-constexpr uint32_t kServiceId = 16;
-constexpr uint32_t kUnaryMethodId = 111;
-constexpr uint32_t kServerStreamingMethodId = 112;
-
-class FakeGeneratedServiceClient {
- public:
-  static NanopbUnaryReceiver<pw_rpc_test_TestResponse> TestUnaryRpc(
-      Client& client,
-      uint32_t channel_id,
-      const pw_rpc_test_TestRequest& request,
-      Function<void(const pw_rpc_test_TestResponse&, Status)> on_response,
-      Function<void(Status)> on_error = nullptr) {
-    return pw::rpc::internal::
-        NanopbUnaryResponseClientCall<pw_rpc_test_TestResponse>::Start<
-            pw::rpc::NanopbUnaryReceiver<pw_rpc_test_TestResponse>>(
-            client,
-            channel_id,
-            kServiceId,
-            kUnaryMethodId,
-            internal::kNanopbMethodSerde<pw_rpc_test_TestRequest_fields,
-                                         pw_rpc_test_TestResponse_fields>,
-            std::move(on_response),
-            std::move(on_error),
-            request);
-  }
-
-  static NanopbUnaryReceiver<pw_rpc_test_TestResponse> TestAnotherUnaryRpc(
-      Client& client,
-      uint32_t channel_id,
-      const pw_rpc_test_TestRequest& request,
-      Function<void(const pw_rpc_test_TestResponse&, Status)> on_response,
-      Function<void(Status)> on_error = nullptr) {
-    return pw::rpc::internal::
-        NanopbUnaryResponseClientCall<pw_rpc_test_TestResponse>::Start<
-            pw::rpc::NanopbUnaryReceiver<pw_rpc_test_TestResponse>>(
-            client,
-            channel_id,
-            kServiceId,
-            kUnaryMethodId,
-            internal::kNanopbMethodSerde<pw_rpc_test_TestRequest_fields,
-                                         pw_rpc_test_TestResponse_fields>,
-            std::move(on_response),
-            std::move(on_error),
-            request);
-  }
-
-  static NanopbClientReader<pw_rpc_test_TestStreamResponse> TestServerStreamRpc(
-      Client& client,
-      uint32_t channel_id,
-      const pw_rpc_test_TestRequest& request,
-      Function<void(const pw_rpc_test_TestStreamResponse&)> on_response,
-      Function<void(Status)> on_stream_end,
-      Function<void(Status)> on_error = nullptr) {
-    return pw::rpc::internal::
-        NanopbStreamResponseClientCall<pw_rpc_test_TestStreamResponse>::Start<
-            pw::rpc::NanopbClientReader<pw_rpc_test_TestStreamResponse>>(
-            client,
-            channel_id,
-            kServiceId,
-            kServerStreamingMethodId,
-            internal::kNanopbMethodSerde<pw_rpc_test_TestRequest_fields,
-                                         pw_rpc_test_TestStreamResponse_fields>,
-            std::move(on_response),
-            std::move(on_stream_end),
-            std::move(on_error),
-            request);
-  }
-};
-
-TEST(NanopbClientCall, Unary_SendsRequestPacket) {
-  ClientContextForTest context;
-
-  auto call = FakeGeneratedServiceClient::TestUnaryRpc(
-      context.client(),
-      context.channel().id(),
-      {.integer = 123, .status_code = 0},
-      nullptr);
-
-  EXPECT_EQ(context.output().total_packets(), 1u);
-  auto packet = context.output().last_packet();
-  EXPECT_EQ(packet.channel_id(), context.channel().id());
-  EXPECT_EQ(packet.service_id(), kServiceId);
-  EXPECT_EQ(packet.method_id(), kUnaryMethodId);
-
-  PW_DECODE_PB(pw_rpc_test_TestRequest, sent_proto, packet.payload());
-  EXPECT_EQ(sent_proto.integer, 123);
-}
-
-class UnaryClientCall : public ::testing::Test {
- protected:
-  std::optional<Status> last_status_;
-  std::optional<Status> last_error_;
-  int responses_received_ = 0;
-  int last_response_value_ = 0;
-};
-
-TEST_F(UnaryClientCall, InvokesCallbackOnValidResponse) {
-  ClientContextForTest context;
-
-  auto call = FakeGeneratedServiceClient::TestUnaryRpc(
-      context.client(),
-      context.channel().id(),
-      {.integer = 123, .status_code = 0},
-      [this](const pw_rpc_test_TestResponse& response, Status status) {
-        ++responses_received_;
-        last_status_ = status;
-        last_response_value_ = response.value;
-      });
-
-  PW_ENCODE_PB(pw_rpc_test_TestResponse, response, .value = 42);
-  EXPECT_EQ(OkStatus(), context.SendResponse(OkStatus(), response));
-
-  ASSERT_EQ(responses_received_, 1);
-  EXPECT_EQ(last_status_, OkStatus());
-  EXPECT_EQ(last_response_value_, 42);
-}
-
-TEST_F(UnaryClientCall, DoesNothingOnNullCallback) {
-  ClientContextForTest context;
-
-  auto call = FakeGeneratedServiceClient::TestUnaryRpc(
-      context.client(),
-      context.channel().id(),
-      {.integer = 123, .status_code = 0},
-      nullptr);
-
-  PW_ENCODE_PB(pw_rpc_test_TestResponse, response, .value = 42);
-  EXPECT_EQ(OkStatus(), context.SendResponse(OkStatus(), response));
-
-  ASSERT_EQ(responses_received_, 0);
-}
-
-TEST_F(UnaryClientCall, InvokesErrorCallbackOnInvalidResponse) {
-  ClientContextForTest context;
-
-  auto call = FakeGeneratedServiceClient::TestUnaryRpc(
-      context.client(),
-      context.channel().id(),
-      {.integer = 123, .status_code = 0},
-      [this](const pw_rpc_test_TestResponse& response, Status status) {
-        ++responses_received_;
-        last_status_ = status;
-        last_response_value_ = response.value;
-      },
-      [this](Status status) { last_error_ = status; });
-
-  constexpr std::byte bad_payload[]{
-      std::byte{0xab}, std::byte{0xcd}, std::byte{0xef}};
-  EXPECT_EQ(OkStatus(), context.SendResponse(OkStatus(), bad_payload));
-
-  EXPECT_EQ(responses_received_, 0);
-  ASSERT_TRUE(last_error_.has_value());
-  EXPECT_EQ(last_error_, Status::DataLoss());
-}
-
-TEST_F(UnaryClientCall, InvokesErrorCallbackOnServerError) {
-  ClientContextForTest context;
-
-  auto call = FakeGeneratedServiceClient::TestUnaryRpc(
-      context.client(),
-      context.channel().id(),
-      {.integer = 123, .status_code = 0},
-      [this](const pw_rpc_test_TestResponse& response, Status status) {
-        ++responses_received_;
-        last_status_ = status;
-        last_response_value_ = response.value;
-      },
-      [this](Status status) { last_error_ = status; });
-
-  EXPECT_EQ(OkStatus(),
-            context.SendPacket(internal::PacketType::SERVER_ERROR,
-                               Status::NotFound()));
-
-  EXPECT_EQ(responses_received_, 0);
-  EXPECT_EQ(last_error_, Status::NotFound());
-}
-
-TEST_F(UnaryClientCall, DoesNothingOnErrorWithoutCallback) {
-  ClientContextForTest context;
-
-  auto call = FakeGeneratedServiceClient::TestUnaryRpc(
-      context.client(),
-      context.channel().id(),
-      {.integer = 123, .status_code = 0},
-      [this](const pw_rpc_test_TestResponse& response, Status status) {
-        ++responses_received_;
-        last_status_ = status;
-        last_response_value_ = response.value;
-      });
-
-  constexpr std::byte bad_payload[]{
-      std::byte{0xab}, std::byte{0xcd}, std::byte{0xef}};
-  EXPECT_EQ(OkStatus(), context.SendResponse(OkStatus(), bad_payload));
-
-  EXPECT_EQ(responses_received_, 0);
-}
-
-TEST_F(UnaryClientCall, OnlyReceivesOneResponse) {
-  ClientContextForTest context;
-
-  auto call = FakeGeneratedServiceClient::TestUnaryRpc(
-      context.client(),
-      context.channel().id(),
-      {.integer = 123, .status_code = 0},
-      [this](const pw_rpc_test_TestResponse& response, Status status) {
-        ++responses_received_;
-        last_status_ = status;
-        last_response_value_ = response.value;
-      });
-
-  PW_ENCODE_PB(pw_rpc_test_TestResponse, r1, .value = 42);
-  EXPECT_EQ(OkStatus(), context.SendResponse(Status::Unimplemented(), r1));
-  PW_ENCODE_PB(pw_rpc_test_TestResponse, r2, .value = 44);
-  EXPECT_EQ(OkStatus(), context.SendResponse(Status::OutOfRange(), r2));
-  PW_ENCODE_PB(pw_rpc_test_TestResponse, r3, .value = 46);
-  EXPECT_EQ(OkStatus(), context.SendResponse(Status::Internal(), r3));
-
-  EXPECT_EQ(responses_received_, 1);
-  EXPECT_EQ(last_status_, Status::Unimplemented());
-  EXPECT_EQ(last_response_value_, 42);
-}
-
-class ServerStreamingClientCall : public ::testing::Test {
- protected:
-  bool active_ = true;
-  std::optional<Status> stream_status_;
-  std::optional<Status> rpc_error_;
-  int responses_received_ = 0;
-  int last_response_number_ = 0;
-};
-
-TEST_F(ServerStreamingClientCall, SendsRequestPacket) {
-  ClientContextForTest<128, 99, kServiceId, kServerStreamingMethodId> context;
-
-  auto call = FakeGeneratedServiceClient::TestServerStreamRpc(
-      context.client(),
-      context.channel().id(),
-      {.integer = 71, .status_code = 0},
-      nullptr,
-      nullptr);
-
-  EXPECT_EQ(context.output().total_packets(), 1u);
-  auto packet = context.output().last_packet();
-  EXPECT_EQ(packet.channel_id(), context.channel().id());
-  EXPECT_EQ(packet.service_id(), kServiceId);
-  EXPECT_EQ(packet.method_id(), kServerStreamingMethodId);
-
-  PW_DECODE_PB(pw_rpc_test_TestRequest, sent_proto, packet.payload());
-  EXPECT_EQ(sent_proto.integer, 71);
-}
-
-TEST_F(ServerStreamingClientCall, InvokesCallbackOnValidResponse) {
-  ClientContextForTest<128, 99, kServiceId, kServerStreamingMethodId> context;
-
-  auto call = FakeGeneratedServiceClient::TestServerStreamRpc(
-      context.client(),
-      context.channel().id(),
-      {.integer = 71, .status_code = 0},
-      [this](const pw_rpc_test_TestStreamResponse& response) {
-        ++responses_received_;
-        last_response_number_ = response.number;
-      },
-      [this](Status status) {
-        active_ = false;
-        stream_status_ = status;
-      });
-
-  PW_ENCODE_PB(pw_rpc_test_TestStreamResponse, r1, .chunk = {}, .number = 11u);
-  EXPECT_EQ(OkStatus(), context.SendServerStream(r1));
-  EXPECT_TRUE(active_);
-  EXPECT_EQ(responses_received_, 1);
-  EXPECT_EQ(last_response_number_, 11);
-
-  PW_ENCODE_PB(pw_rpc_test_TestStreamResponse, r2, .chunk = {}, .number = 22u);
-  EXPECT_EQ(OkStatus(), context.SendServerStream(r2));
-  EXPECT_TRUE(active_);
-  EXPECT_EQ(responses_received_, 2);
-  EXPECT_EQ(last_response_number_, 22);
-
-  PW_ENCODE_PB(pw_rpc_test_TestStreamResponse, r3, .chunk = {}, .number = 33u);
-  EXPECT_EQ(OkStatus(), context.SendServerStream(r3));
-  EXPECT_TRUE(active_);
-  EXPECT_EQ(responses_received_, 3);
-  EXPECT_EQ(last_response_number_, 33);
-}
-
-TEST_F(ServerStreamingClientCall, InvokesStreamEndOnFinish) {
-  ClientContextForTest<128, 99, kServiceId, kServerStreamingMethodId> context;
-
-  auto call = FakeGeneratedServiceClient::TestServerStreamRpc(
-      context.client(),
-      context.channel().id(),
-      {.integer = 71, .status_code = 0},
-      [this](const pw_rpc_test_TestStreamResponse& response) {
-        ++responses_received_;
-        last_response_number_ = response.number;
-      },
-      [this](Status status) {
-        active_ = false;
-        stream_status_ = status;
-      });
-
-  PW_ENCODE_PB(pw_rpc_test_TestStreamResponse, r1, .chunk = {}, .number = 11u);
-  EXPECT_EQ(OkStatus(), context.SendServerStream(r1));
-  EXPECT_TRUE(active_);
-
-  PW_ENCODE_PB(pw_rpc_test_TestStreamResponse, r2, .chunk = {}, .number = 22u);
-  EXPECT_EQ(OkStatus(), context.SendServerStream(r2));
-  EXPECT_TRUE(active_);
-
-  // Close the stream.
-  EXPECT_EQ(OkStatus(), context.SendResponse(Status::NotFound()));
-
-  PW_ENCODE_PB(pw_rpc_test_TestStreamResponse, r3, .chunk = {}, .number = 33u);
-  EXPECT_EQ(OkStatus(), context.SendServerStream(r3));
-  EXPECT_FALSE(active_);
-
-  EXPECT_EQ(responses_received_, 2);
-}
-
-TEST_F(ServerStreamingClientCall, InvokesErrorCallbackOnInvalidResponses) {
-  ClientContextForTest<128, 99, kServiceId, kServerStreamingMethodId> context;
-
-  auto call = FakeGeneratedServiceClient::TestServerStreamRpc(
-      context.client(),
-      context.channel().id(),
-      {.integer = 71, .status_code = 0},
-      [this](const pw_rpc_test_TestStreamResponse& response) {
-        ++responses_received_;
-        last_response_number_ = response.number;
-      },
-      nullptr,
-      [this](Status error) { rpc_error_ = error; });
-
-  PW_ENCODE_PB(pw_rpc_test_TestStreamResponse, r1, .chunk = {}, .number = 11u);
-  EXPECT_EQ(OkStatus(), context.SendServerStream(r1));
-  EXPECT_TRUE(active_);
-  EXPECT_EQ(responses_received_, 1);
-  EXPECT_EQ(last_response_number_, 11);
-
-  constexpr std::byte bad_payload[]{
-      std::byte{0xab}, std::byte{0xcd}, std::byte{0xef}};
-  EXPECT_EQ(OkStatus(), context.SendServerStream(bad_payload));
-  EXPECT_EQ(responses_received_, 1);
-  ASSERT_TRUE(rpc_error_.has_value());
-  EXPECT_EQ(rpc_error_, Status::DataLoss());
-
-  PW_ENCODE_PB(pw_rpc_test_TestStreamResponse, r2, .chunk = {}, .number = 22u);
-  EXPECT_EQ(OkStatus(), context.SendServerStream(r2));
-  EXPECT_TRUE(active_);
-  EXPECT_EQ(responses_received_, 2);
-  EXPECT_EQ(last_response_number_, 22);
-
-  EXPECT_EQ(OkStatus(),
-            context.SendPacket(internal::PacketType::SERVER_ERROR,
-                               Status::NotFound()));
-  EXPECT_EQ(responses_received_, 2);
-  EXPECT_EQ(rpc_error_, Status::NotFound());
-}
-
-}  // namespace
-}  // namespace pw::rpc
-
-PW_MODIFY_DIAGNOSTICS_POP();
diff --git a/pw_rpc/nanopb/client_integration_test.cc b/pw_rpc/nanopb/client_integration_test.cc
deleted file mode 100644
index 07d3ca9..0000000
--- a/pw_rpc/nanopb/client_integration_test.cc
+++ /dev/null
@@ -1,153 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "gtest/gtest.h"
-#include "pw_assert/check.h"
-#include "pw_rpc/benchmark.rpc.pb.h"
-#include "pw_rpc/integration_testing.h"
-#include "pw_sync/binary_semaphore.h"
-
-namespace nanopb_rpc_test {
-namespace {
-
-using namespace std::chrono_literals;
-using pw::ByteSpan;
-using pw::ConstByteSpan;
-using pw::Function;
-using pw::OkStatus;
-using pw::Status;
-
-using pw::rpc::pw_rpc::nanopb::Benchmark;
-
-constexpr int kIterations = 10;
-
-class PayloadReceiver {
- public:
-  const char* Wait() {
-    PW_CHECK(sem_.try_acquire_for(1500ms));
-    return reinterpret_cast<const char*>(payload_.payload.bytes);
-  }
-
-  Function<void(const pw_rpc_Payload&, Status)> UnaryOnCompleted() {
-    return [this](const pw_rpc_Payload& data, Status) { CopyPayload(data); };
-  }
-
-  Function<void(const pw_rpc_Payload&)> OnNext() {
-    return [this](const pw_rpc_Payload& data) { CopyPayload(data); };
-  }
-
- private:
-  void CopyPayload(const pw_rpc_Payload& data) {
-    payload_ = data;
-    sem_.release();
-  }
-
-  pw::sync::BinarySemaphore sem_;
-  pw_rpc_Payload payload_ = {};
-};
-
-template <size_t kSize>
-pw_rpc_Payload Payload(const char (&string)[kSize]) {
-  static_assert(kSize <= sizeof(pw_rpc_Payload::payload));
-  pw_rpc_Payload payload{};
-  std::memcpy(payload.payload.bytes, string, kSize);
-  payload.payload.size = kSize;
-  return payload;
-}
-
-const Benchmark::Client kClient(pw::rpc::integration_test::client(),
-                                pw::rpc::integration_test::kChannelId);
-
-TEST(NanopbRpcIntegrationTest, Unary) {
-  char value[] = {"hello, world!"};
-
-  for (int i = 0; i < kIterations; ++i) {
-    PayloadReceiver receiver;
-
-    value[0] = static_cast<char>(i);
-    pw::rpc::NanopbUnaryReceiver call =
-        kClient.UnaryEcho(Payload(value), receiver.UnaryOnCompleted());
-    ASSERT_STREQ(receiver.Wait(), value);
-  }
-}
-
-TEST(NanopbRpcIntegrationTest, Unary_ReuseCall) {
-  pw::rpc::NanopbUnaryReceiver<pw_rpc_Payload> call;
-  char value[] = {"O_o "};
-
-  for (int i = 0; i < kIterations; ++i) {
-    PayloadReceiver receiver;
-
-    value[sizeof(value) - 2] = static_cast<char>(i);
-    call = kClient.UnaryEcho(Payload(value), receiver.UnaryOnCompleted());
-    ASSERT_STREQ(receiver.Wait(), value);
-  }
-}
-
-TEST(NanopbRpcIntegrationTest, Unary_DiscardCalls) {
-  constexpr int iterations = PW_RPC_USE_GLOBAL_MUTEX ? 10000 : 1;
-  for (int i = 0; i < iterations; ++i) {
-    kClient.UnaryEcho(Payload("O_o"));
-  }
-}
-
-TEST(NanopbRpcIntegrationTest, BidirectionalStreaming_MoveCalls) {
-  for (int i = 0; i < kIterations; ++i) {
-    PayloadReceiver receiver;
-    pw::rpc::NanopbClientReaderWriter call =
-        kClient.BidirectionalEcho(receiver.OnNext());
-
-    ASSERT_EQ(OkStatus(), call.Write(Payload("Yello")));
-    ASSERT_STREQ(receiver.Wait(), "Yello");
-
-    pw::rpc::NanopbClientReaderWriter<pw_rpc_Payload, pw_rpc_Payload> new_call =
-        std::move(call);
-
-    EXPECT_EQ(Status::FailedPrecondition(), call.Write(Payload("Dello")));
-
-    ASSERT_EQ(OkStatus(), new_call.Write(Payload("Dello")));
-    ASSERT_STREQ(receiver.Wait(), "Dello");
-
-    call = std::move(new_call);
-
-    EXPECT_EQ(Status::FailedPrecondition(), new_call.Write(Payload("Dello")));
-
-    ASSERT_EQ(OkStatus(), call.Write(Payload("???")));
-    ASSERT_STREQ(receiver.Wait(), "???");
-
-    EXPECT_EQ(OkStatus(), call.Cancel());
-    EXPECT_EQ(Status::FailedPrecondition(), new_call.Cancel());
-  }
-}
-
-TEST(NanopbRpcIntegrationTest, BidirectionalStreaming_ReuseCall) {
-  pw::rpc::NanopbClientReaderWriter<pw_rpc_Payload, pw_rpc_Payload> call;
-
-  for (int i = 0; i < kIterations; ++i) {
-    PayloadReceiver receiver;
-    call = kClient.BidirectionalEcho(receiver.OnNext());
-
-    ASSERT_EQ(OkStatus(), call.Write(Payload("Yello")));
-    ASSERT_STREQ(receiver.Wait(), "Yello");
-
-    ASSERT_EQ(OkStatus(), call.Write(Payload("Dello")));
-    ASSERT_STREQ(receiver.Wait(), "Dello");
-
-    ASSERT_EQ(OkStatus(), call.Write(Payload("???")));
-    ASSERT_STREQ(receiver.Wait(), "???");
-  }
-}
-
-}  // namespace
-}  // namespace nanopb_rpc_test
diff --git a/pw_rpc/nanopb/client_reader_writer_test.cc b/pw_rpc/nanopb/client_reader_writer_test.cc
deleted file mode 100644
index 2e8b8fc..0000000
--- a/pw_rpc/nanopb/client_reader_writer_test.cc
+++ /dev/null
@@ -1,240 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/nanopb/client_reader_writer.h"
-
-#include <optional>
-
-#include "gtest/gtest.h"
-#include "pw_rpc/nanopb/client_testing.h"
-#include "pw_rpc_test_protos/test.rpc.pb.h"
-
-PW_MODIFY_DIAGNOSTICS_PUSH();
-PW_MODIFY_DIAGNOSTIC(ignored, "-Wmissing-field-initializers");
-
-namespace pw::rpc {
-namespace {
-
-using test::pw_rpc::nanopb::TestService;
-
-void FailIfCalled(Status) { FAIL(); }
-template <typename T>
-void FailIfOnNextCalled(const T&) {
-  FAIL();
-}
-template <typename T>
-void FailIfOnCompletedCalled(const T&, Status) {
-  FAIL();
-}
-
-TEST(NanopbUnaryReceiver, DefaultConstructed) {
-  NanopbUnaryReceiver<pw_rpc_test_TestResponse> call;
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Cancel());
-
-  call.set_on_completed([](const pw_rpc_test_TestResponse&, Status) {});
-  call.set_on_error([](Status) {});
-}
-
-TEST(NanopbClientWriter, DefaultConstructed) {
-  NanopbClientWriter<pw_rpc_test_TestRequest, pw_rpc_test_TestStreamResponse>
-      call;
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Write({}));
-  EXPECT_EQ(Status::FailedPrecondition(), call.Cancel());
-  EXPECT_EQ(Status::FailedPrecondition(), call.CloseClientStream());
-
-  call.set_on_completed([](const pw_rpc_test_TestStreamResponse&, Status) {});
-  call.set_on_error([](Status) {});
-}
-
-TEST(NanopbClientReader, DefaultConstructed) {
-  NanopbClientReader<pw_rpc_test_TestStreamResponse> call;
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Cancel());
-
-  call.set_on_completed([](Status) {});
-  call.set_on_next([](const pw_rpc_test_TestStreamResponse&) {});
-  call.set_on_error([](Status) {});
-}
-
-TEST(NanopbClientReaderWriter, DefaultConstructed) {
-  NanopbClientReaderWriter<pw_rpc_test_TestRequest,
-                           pw_rpc_test_TestStreamResponse>
-      call;
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Write({}));
-  EXPECT_EQ(Status::FailedPrecondition(), call.Cancel());
-  EXPECT_EQ(Status::FailedPrecondition(), call.CloseClientStream());
-
-  call.set_on_completed([](Status) {});
-  call.set_on_next([](const pw_rpc_test_TestStreamResponse&) {});
-  call.set_on_error([](Status) {});
-}
-
-TEST(NanopbUnaryReceiver, Closed) {
-  NanopbClientTestContext ctx;
-  NanopbUnaryReceiver<pw_rpc_test_TestResponse> call =
-      TestService::TestUnaryRpc(
-          ctx.client(),
-          ctx.channel().id(),
-          {},
-          FailIfOnCompletedCalled<pw_rpc_test_TestResponse>,
-          FailIfCalled);
-  ASSERT_EQ(OkStatus(), call.Cancel());
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Cancel());
-
-  call.set_on_completed([](const pw_rpc_test_TestResponse&, Status) {});
-  call.set_on_error([](Status) {});
-}
-
-TEST(NanopbClientWriter, Closed) {
-  NanopbClientTestContext ctx;
-  NanopbClientWriter<pw_rpc_test_TestRequest, pw_rpc_test_TestStreamResponse>
-      call = TestService::TestClientStreamRpc(
-          ctx.client(),
-          ctx.channel().id(),
-          FailIfOnCompletedCalled<pw_rpc_test_TestStreamResponse>,
-          FailIfCalled);
-  ASSERT_EQ(OkStatus(), call.Cancel());
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Write({}));
-  EXPECT_EQ(Status::FailedPrecondition(), call.Cancel());
-  EXPECT_EQ(Status::FailedPrecondition(), call.CloseClientStream());
-
-  call.set_on_completed([](const pw_rpc_test_TestStreamResponse&, Status) {});
-  call.set_on_error([](Status) {});
-}
-
-TEST(NanopbClientReader, Closed) {
-  NanopbClientTestContext ctx;
-  NanopbClientReader<pw_rpc_test_TestStreamResponse> call =
-      TestService::TestServerStreamRpc(
-          ctx.client(),
-          ctx.channel().id(),
-          {},
-          FailIfOnNextCalled<pw_rpc_test_TestStreamResponse>,
-          FailIfCalled,
-          FailIfCalled);
-  ASSERT_EQ(OkStatus(), call.Cancel());
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Cancel());
-
-  call.set_on_completed([](Status) {});
-  call.set_on_next([](const pw_rpc_test_TestStreamResponse&) {});
-  call.set_on_error([](Status) {});
-}
-
-TEST(NanopbClientReaderWriter, Closed) {
-  NanopbClientTestContext ctx;
-  NanopbClientReaderWriter<pw_rpc_test_TestRequest,
-                           pw_rpc_test_TestStreamResponse>
-      call = TestService::TestBidirectionalStreamRpc(
-          ctx.client(),
-          ctx.channel().id(),
-          FailIfOnNextCalled<pw_rpc_test_TestStreamResponse>,
-          FailIfCalled,
-          FailIfCalled);
-  ASSERT_EQ(OkStatus(), call.Cancel());
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Write({}));
-  EXPECT_EQ(Status::FailedPrecondition(), call.Cancel());
-  EXPECT_EQ(Status::FailedPrecondition(), call.CloseClientStream());
-
-  call.set_on_completed([](Status) {});
-  call.set_on_next([](const pw_rpc_test_TestStreamResponse&) {});
-  call.set_on_error([](Status) {});
-}
-
-TEST(NanopbUnaryReceiver, CallbacksMoveCorrectly) {
-  NanopbClientTestContext ctx;
-
-  struct {
-    pw_rpc_test_TestResponse payload = {.value = 12345678};
-    std::optional<Status> status;
-  } reply;
-
-  NanopbUnaryReceiver<pw_rpc_test_TestResponse> call_2;
-  {
-    NanopbUnaryReceiver call_1 = TestService::TestUnaryRpc(
-        ctx.client(),
-        ctx.channel().id(),
-        {},
-        [&reply](const pw_rpc_test_TestResponse& response, Status status) {
-          reply.payload = response;
-          reply.status = status;
-        });
-
-    call_2 = std::move(call_1);
-  }
-
-  ctx.server().SendResponse<TestService::TestUnaryRpc>({.value = 9000},
-                                                       Status::NotFound());
-  EXPECT_EQ(reply.payload.value, 9000);
-  EXPECT_EQ(reply.status, Status::NotFound());
-}
-
-TEST(NanopbClientReaderWriter, CallbacksMoveCorrectly) {
-  NanopbClientTestContext ctx;
-
-  pw_rpc_test_TestStreamResponse payload = {.chunk = {}, .number = 13579};
-
-  NanopbClientReaderWriter<pw_rpc_test_TestRequest,
-                           pw_rpc_test_TestStreamResponse>
-      call_2;
-  {
-    NanopbClientReaderWriter call_1 = TestService::TestBidirectionalStreamRpc(
-        ctx.client(),
-        ctx.channel().id(),
-        [&payload](const pw_rpc_test_TestStreamResponse& response) {
-          payload = response;
-        });
-
-    call_2 = std::move(call_1);
-  }
-
-  ctx.server().SendServerStream<TestService::TestBidirectionalStreamRpc>(
-      {.chunk = {}, .number = 5050});
-  EXPECT_EQ(payload.number, 5050u);
-}
-
-}  // namespace
-}  // namespace pw::rpc
-
-PW_MODIFY_DIAGNOSTICS_POP();
diff --git a/pw_rpc/nanopb/codegen_test.cc b/pw_rpc/nanopb/codegen_test.cc
index 5d27c1d..5b7241a 100644
--- a/pw_rpc/nanopb/codegen_test.cc
+++ b/pw_rpc/nanopb/codegen_test.cc
@@ -1,4 +1,4 @@
-// Copyright 2021 The Pigweed Authors
+// Copyright 2020 The Pigweed Authors
 //
 // 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
@@ -13,72 +13,40 @@
 // the License.
 
 #include "gtest/gtest.h"
-#include "pw_preprocessor/compiler.h"
 #include "pw_rpc/internal/hash.h"
-#include "pw_rpc/internal/test_utils.h"
-#include "pw_rpc/nanopb/test_method_context.h"
+#include "pw_rpc/nanopb_test_method_context.h"
 #include "pw_rpc_nanopb_private/internal_test_utils.h"
+#include "pw_rpc_private/internal_test_utils.h"
 #include "pw_rpc_test_protos/test.rpc.pb.h"
 
-PW_MODIFY_DIAGNOSTICS_PUSH();
-PW_MODIFY_DIAGNOSTIC(ignored, "-Wmissing-field-initializers");
-
 namespace pw::rpc {
 namespace test {
 
-class TestService final
-    : public pw_rpc::nanopb::TestService::Service<TestService> {
+class TestService final : public generated::TestService<TestService> {
  public:
-  Status TestUnaryRpc(const pw_rpc_test_TestRequest& request,
-                      pw_rpc_test_TestResponse& response) {
+  Status TestRpc(ServerContext&,
+                 const pw_rpc_test_TestRequest& request,
+                 pw_rpc_test_TestResponse& response) {
     response.value = request.integer + 1;
     return static_cast<Status::Code>(request.status_code);
   }
 
-  void TestAnotherUnaryRpc(
-      const pw_rpc_test_TestRequest& request,
-      NanopbUnaryResponder<pw_rpc_test_TestResponse>& responder) {
-    pw_rpc_test_TestResponse response{};
-    EXPECT_EQ(OkStatus(),
-              responder.Finish(response, TestUnaryRpc(request, response)));
-  }
-
-  static void TestServerStreamRpc(
+  static void TestStreamRpc(
+      ServerContext&,
       const pw_rpc_test_TestRequest& request,
       ServerWriter<pw_rpc_test_TestStreamResponse>& writer) {
     for (int i = 0; i < request.integer; ++i) {
-      EXPECT_EQ(
-          OkStatus(),
-          writer.Write({.chunk = {}, .number = static_cast<uint32_t>(i)}));
+      writer.Write({.chunk = {}, .number = static_cast<uint32_t>(i)});
     }
 
-    EXPECT_EQ(OkStatus(),
-              writer.Finish(static_cast<Status::Code>(request.status_code)));
+    writer.Finish(static_cast<Status::Code>(request.status_code));
   }
-
-  void TestClientStreamRpc(
-      ServerReader<pw_rpc_test_TestRequest, pw_rpc_test_TestStreamResponse>&
-          new_reader) {
-    reader = std::move(new_reader);
-  }
-
-  void TestBidirectionalStreamRpc(
-      ServerReaderWriter<pw_rpc_test_TestRequest,
-                         pw_rpc_test_TestStreamResponse>& new_reader_writer) {
-    reader_writer = std::move(new_reader_writer);
-  }
-
-  ServerReader<pw_rpc_test_TestRequest, pw_rpc_test_TestStreamResponse> reader;
-  ServerReaderWriter<pw_rpc_test_TestRequest, pw_rpc_test_TestStreamResponse>
-      reader_writer;
 };
 
 }  // namespace test
 
 namespace {
 
-using internal::ClientContextForTest;
-
 TEST(NanopbCodegen, CompilesProperly) {
   test::TestService service;
   EXPECT_EQ(service.id(), internal::Hash("pw.rpc.test.TestService"));
@@ -86,7 +54,7 @@
 }
 
 TEST(NanopbCodegen, Server_InvokeUnaryRpc) {
-  PW_NANOPB_TEST_METHOD_CONTEXT(test::TestService, TestUnaryRpc) context;
+  PW_NANOPB_TEST_METHOD_CONTEXT(test::TestService, TestRpc) context;
 
   EXPECT_EQ(OkStatus(),
             context.call({.integer = 123, .status_code = OkStatus().code()}));
@@ -99,32 +67,20 @@
   EXPECT_EQ(1000, context.response().value);
 }
 
-TEST(NanopbCodegen, Server_InvokeAsyncUnaryRpc) {
-  PW_NANOPB_TEST_METHOD_CONTEXT(test::TestService, TestAnotherUnaryRpc) context;
-
-  context.call({.integer = 123, .status_code = OkStatus().code()});
-
-  EXPECT_EQ(OkStatus(), context.status());
-  EXPECT_EQ(124, context.response().value);
-
-  context.call(
-      {.integer = 999, .status_code = Status::InvalidArgument().code()});
-  EXPECT_EQ(Status::InvalidArgument(), context.status());
-  EXPECT_EQ(1000, context.response().value);
-}
-
-TEST(NanopbCodegen, Server_InvokeServerStreamingRpc) {
-  PW_NANOPB_TEST_METHOD_CONTEXT(test::TestService, TestServerStreamRpc) context;
+TEST(NanopbCodegen, Server_InvokeStreamingRpc) {
+  PW_NANOPB_TEST_METHOD_CONTEXT(test::TestService, TestStreamRpc) context;
 
   context.call({.integer = 0, .status_code = Status::Aborted().code()});
 
   EXPECT_EQ(Status::Aborted(), context.status());
   EXPECT_TRUE(context.done());
-  EXPECT_EQ(context.total_responses(), 0u);
+  EXPECT_TRUE(context.responses().empty());
+  EXPECT_EQ(0u, context.total_responses());
 
   context.call({.integer = 4, .status_code = OkStatus().code()});
 
   ASSERT_EQ(4u, context.responses().size());
+  ASSERT_EQ(4u, context.total_responses());
 
   for (size_t i = 0; i < context.responses().size(); ++i) {
     EXPECT_EQ(context.responses()[i].number, i);
@@ -133,256 +89,103 @@
   EXPECT_EQ(OkStatus().code(), context.status());
 }
 
-TEST(NanopbCodegen, Server_InvokeServerStreamingRpc_ManualWriting) {
-  PW_NANOPB_TEST_METHOD_CONTEXT(test::TestService, TestServerStreamRpc, 4)
-  context;
+TEST(NanopbCodegen,
+     Server_InvokeStreamingRpc_ContextKeepsFixedNumberOfResponses) {
+  PW_NANOPB_TEST_METHOD_CONTEXT(test::TestService, TestStreamRpc, 3) context;
 
-  ASSERT_EQ(4u, context.max_packets());
+  ASSERT_EQ(3u, context.responses().max_size());
+
+  context.call({.integer = 5, .status_code = Status::NotFound().code()});
+
+  ASSERT_EQ(3u, context.responses().size());
+  ASSERT_EQ(5u, context.total_responses());
+
+  EXPECT_EQ(context.responses()[0].number, 0u);
+  EXPECT_EQ(context.responses()[1].number, 1u);
+  EXPECT_EQ(context.responses()[2].number, 4u);
+}
+
+TEST(NanopbCodegen, Server_InvokeStreamingRpc_ManualWriting) {
+  PW_NANOPB_TEST_METHOD_CONTEXT(test::TestService, TestStreamRpc, 3) context;
+
+  ASSERT_EQ(3u, context.responses().max_size());
 
   auto writer = context.writer();
 
-  EXPECT_EQ(OkStatus(), writer.Write({.chunk = {}, .number = 3}));
-  EXPECT_EQ(OkStatus(), writer.Write({.chunk = {}, .number = 6}));
-  EXPECT_EQ(OkStatus(), writer.Write({.chunk = {}, .number = 9}));
+  writer.Write({.chunk = {}, .number = 3});
+  writer.Write({.chunk = {}, .number = 6});
+  writer.Write({.chunk = {}, .number = 9});
 
   EXPECT_FALSE(context.done());
 
-  EXPECT_EQ(OkStatus(), writer.Finish(Status::Cancelled()));
+  writer.Finish(Status::Cancelled());
   ASSERT_TRUE(context.done());
   EXPECT_EQ(Status::Cancelled(), context.status());
 
   ASSERT_EQ(3u, context.responses().size());
+  ASSERT_EQ(3u, context.total_responses());
 
   EXPECT_EQ(context.responses()[0].number, 3u);
   EXPECT_EQ(context.responses()[1].number, 6u);
   EXPECT_EQ(context.responses()[2].number, 9u);
 }
 
-TEST(NanopbCodegen, Server_InvokeClientStreamingRpc) {
-  PW_NANOPB_TEST_METHOD_CONTEXT(test::TestService, TestClientStreamRpc) context;
-
-  context.call();
-
-  pw_rpc_test_TestRequest request = {};
-  context.service().reader.set_on_next(
-      [&request](const pw_rpc_test_TestRequest& req) { request = req; });
-
-  context.SendClientStream({.integer = -99, .status_code = 10});
-  EXPECT_EQ(request.integer, -99);
-  EXPECT_EQ(request.status_code, 10u);
-
-  ASSERT_EQ(OkStatus(),
-            context.service().reader.Finish({.chunk = {}, .number = 3},
-                                            Status::Unimplemented()));
-  EXPECT_EQ(Status::Unimplemented(), context.status());
-  EXPECT_EQ(context.response().number, 3u);
-}
-
-TEST(NanopbCodegen, Server_InvokeBidirectionalStreamingRpc) {
-  PW_NANOPB_TEST_METHOD_CONTEXT(test::TestService, TestBidirectionalStreamRpc)
-  context;
-
-  context.call();
-
-  pw_rpc_test_TestRequest request = {};
-  context.service().reader_writer.set_on_next(
-      [&request](const pw_rpc_test_TestRequest& req) { request = req; });
-
-  context.SendClientStream({.integer = -99, .status_code = 10});
-  EXPECT_EQ(request.integer, -99);
-  EXPECT_EQ(request.status_code, 10u);
-
-  ASSERT_EQ(OkStatus(),
-            context.service().reader_writer.Write({.chunk = {}, .number = 2}));
-  EXPECT_EQ(context.responses()[0].number, 2u);
-
-  ASSERT_EQ(OkStatus(),
-            context.service().reader_writer.Finish(Status::NotFound()));
-  EXPECT_EQ(Status::NotFound(), context.status());
-}
-
-TEST(NanopbCodegen, ClientCall_DefaultConstructor) {
-  NanopbUnaryReceiver<pw_rpc_test_TestResponse> unary_call;
-  NanopbClientReader<pw_rpc_test_TestStreamResponse> server_streaming_call;
-}
-
-using TestServiceClient = test::pw_rpc::nanopb::TestService::Client;
+using TestServiceClient = test::nanopb::TestServiceClient;
+using internal::TestServerStreamingResponseHandler;
+using internal::TestUnaryResponseHandler;
 
 TEST(NanopbCodegen, Client_InvokesUnaryRpcWithCallback) {
-  constexpr uint32_t kServiceId = internal::Hash("pw.rpc.test.TestService");
-  constexpr uint32_t kMethodId = internal::Hash("TestUnaryRpc");
+  constexpr uint32_t service_id = internal::Hash("pw.rpc.test.TestService");
+  constexpr uint32_t method_id = internal::Hash("TestRpc");
 
-  ClientContextForTest<128, 99, kServiceId, kMethodId> context;
+  ClientContextForTest<128, 128, 99, service_id, method_id> context;
+  TestUnaryResponseHandler<pw_rpc_test_TestResponse> handler;
 
-  TestServiceClient test_client(context.client(), context.channel().id());
-
-  struct {
-    Status last_status = Status::Unknown();
-    int response_value = -1;
-  } result;
-
-  auto call = test_client.TestUnaryRpc(
-      {.integer = 123, .status_code = 0},
-      [&result](const pw_rpc_test_TestResponse& response, Status status) {
-        result.last_status = status;
-        result.response_value = response.value;
-      });
-
-  EXPECT_TRUE(call.active());
-
-  EXPECT_EQ(context.output().total_packets(), 1u);
-  auto packet =
-      static_cast<const internal::test::FakeChannelOutput&>(context.output())
-          .last_packet();
+  auto call = TestServiceClient::TestRpc(
+      context.channel(), {.integer = 123, .status_code = 0}, handler);
+  EXPECT_EQ(context.output().packet_count(), 1u);
+  auto packet = context.output().sent_packet();
   EXPECT_EQ(packet.channel_id(), context.channel().id());
-  EXPECT_EQ(packet.service_id(), kServiceId);
-  EXPECT_EQ(packet.method_id(), kMethodId);
+  EXPECT_EQ(packet.service_id(), service_id);
+  EXPECT_EQ(packet.method_id(), method_id);
   PW_DECODE_PB(pw_rpc_test_TestRequest, sent_proto, packet.payload());
   EXPECT_EQ(sent_proto.integer, 123);
 
   PW_ENCODE_PB(pw_rpc_test_TestResponse, response, .value = 42);
-  EXPECT_EQ(OkStatus(), context.SendResponse(OkStatus(), response));
-  EXPECT_EQ(result.last_status, OkStatus());
-  EXPECT_EQ(result.response_value, 42);
-
-  EXPECT_FALSE(call.active());
+  context.SendResponse(OkStatus(), response);
+  ASSERT_EQ(handler.responses_received(), 1u);
+  EXPECT_EQ(handler.last_status(), OkStatus());
+  EXPECT_EQ(handler.last_response().value, 42);
 }
 
 TEST(NanopbCodegen, Client_InvokesServerStreamingRpcWithCallback) {
-  constexpr uint32_t kServiceId = internal::Hash("pw.rpc.test.TestService");
-  constexpr uint32_t kMethodId = internal::Hash("TestServerStreamRpc");
+  constexpr uint32_t service_id = internal::Hash("pw.rpc.test.TestService");
+  constexpr uint32_t method_id = internal::Hash("TestStreamRpc");
 
-  ClientContextForTest<128, 99, kServiceId, kMethodId> context;
+  ClientContextForTest<128, 128, 99, service_id, method_id> context;
+  TestServerStreamingResponseHandler<pw_rpc_test_TestStreamResponse> handler;
 
-  TestServiceClient test_client(context.client(), context.channel().id());
-
-  struct {
-    bool active = true;
-    Status stream_status = Status::Unknown();
-    int response_value = -1;
-  } result;
-
-  auto call = test_client.TestServerStreamRpc(
-      {.integer = 123, .status_code = 0},
-      [&result](const pw_rpc_test_TestStreamResponse& response) {
-        result.active = true;
-        result.response_value = response.number;
-      },
-      [&result](Status status) {
-        result.active = false;
-        result.stream_status = status;
-      });
-
-  EXPECT_TRUE(call.active());
-
-  EXPECT_EQ(context.output().total_packets(), 1u);
-  auto packet =
-      static_cast<const internal::test::FakeChannelOutput&>(context.output())
-          .last_packet();
+  auto call = TestServiceClient::TestStreamRpc(
+      context.channel(), {.integer = 123, .status_code = 0}, handler);
+  EXPECT_EQ(context.output().packet_count(), 1u);
+  auto packet = context.output().sent_packet();
   EXPECT_EQ(packet.channel_id(), context.channel().id());
-  EXPECT_EQ(packet.service_id(), kServiceId);
-  EXPECT_EQ(packet.method_id(), kMethodId);
+  EXPECT_EQ(packet.service_id(), service_id);
+  EXPECT_EQ(packet.method_id(), method_id);
   PW_DECODE_PB(pw_rpc_test_TestRequest, sent_proto, packet.payload());
   EXPECT_EQ(sent_proto.integer, 123);
 
   PW_ENCODE_PB(
       pw_rpc_test_TestStreamResponse, response, .chunk = {}, .number = 11u);
-  EXPECT_EQ(OkStatus(), context.SendServerStream(response));
-  EXPECT_TRUE(result.active);
-  EXPECT_EQ(result.response_value, 11);
+  context.SendResponse(OkStatus(), response);
+  ASSERT_EQ(handler.responses_received(), 1u);
+  EXPECT_EQ(handler.last_response().number, 11u);
 
-  EXPECT_EQ(OkStatus(), context.SendResponse(Status::NotFound()));
-  EXPECT_FALSE(result.active);
-  EXPECT_EQ(result.stream_status, Status::NotFound());
-}
-
-TEST(NanopbCodegen, Client_StaticMethod_InvokesUnaryRpcWithCallback) {
-  constexpr uint32_t kServiceId = internal::Hash("pw.rpc.test.TestService");
-  constexpr uint32_t kMethodId = internal::Hash("TestUnaryRpc");
-
-  ClientContextForTest<128, 99, kServiceId, kMethodId> context;
-
-  struct {
-    Status last_status = Status::Unknown();
-    int response_value = -1;
-  } result;
-
-  auto call = test::pw_rpc::nanopb::TestService::TestUnaryRpc(
-      context.client(),
-      context.channel().id(),
-      {.integer = 123, .status_code = 0},
-      [&result](const pw_rpc_test_TestResponse& response, Status status) {
-        result.last_status = status;
-        result.response_value = response.value;
-      });
-
-  EXPECT_TRUE(call.active());
-
-  EXPECT_EQ(context.output().total_packets(), 1u);
-  auto packet =
-      static_cast<const internal::test::FakeChannelOutput&>(context.output())
-          .last_packet();
-  EXPECT_EQ(packet.channel_id(), context.channel().id());
-  EXPECT_EQ(packet.service_id(), kServiceId);
-  EXPECT_EQ(packet.method_id(), kMethodId);
-  PW_DECODE_PB(pw_rpc_test_TestRequest, sent_proto, packet.payload());
-  EXPECT_EQ(sent_proto.integer, 123);
-
-  PW_ENCODE_PB(pw_rpc_test_TestResponse, response, .value = 42);
-  EXPECT_EQ(OkStatus(), context.SendResponse(OkStatus(), response));
-  EXPECT_EQ(result.last_status, OkStatus());
-  EXPECT_EQ(result.response_value, 42);
-}
-
-TEST(NanopbCodegen, Client_StaticMethod_InvokesServerStreamingRpcWithCallback) {
-  constexpr uint32_t kServiceId = internal::Hash("pw.rpc.test.TestService");
-  constexpr uint32_t kMethodId = internal::Hash("TestServerStreamRpc");
-
-  ClientContextForTest<128, 99, kServiceId, kMethodId> context;
-
-  struct {
-    bool active = true;
-    Status stream_status = Status::Unknown();
-    int response_value = -1;
-  } result;
-
-  auto call = test::pw_rpc::nanopb::TestService::TestServerStreamRpc(
-      context.client(),
-      context.channel().id(),
-      {.integer = 123, .status_code = 0},
-      [&result](const pw_rpc_test_TestStreamResponse& response) {
-        result.active = true;
-        result.response_value = response.number;
-      },
-      [&result](Status status) {
-        result.active = false;
-        result.stream_status = status;
-      });
-
-  EXPECT_TRUE(call.active());
-
-  EXPECT_EQ(context.output().total_packets(), 1u);
-  auto packet =
-      static_cast<const internal::test::FakeChannelOutput&>(context.output())
-          .last_packet();
-  EXPECT_EQ(packet.channel_id(), context.channel().id());
-  EXPECT_EQ(packet.service_id(), kServiceId);
-  EXPECT_EQ(packet.method_id(), kMethodId);
-  PW_DECODE_PB(pw_rpc_test_TestRequest, sent_proto, packet.payload());
-  EXPECT_EQ(sent_proto.integer, 123);
-
-  PW_ENCODE_PB(
-      pw_rpc_test_TestStreamResponse, response, .chunk = {}, .number = 11u);
-  EXPECT_EQ(OkStatus(), context.SendServerStream(response));
-  EXPECT_TRUE(result.active);
-  EXPECT_EQ(result.response_value, 11);
-
-  EXPECT_EQ(OkStatus(), context.SendResponse(Status::NotFound()));
-  EXPECT_FALSE(result.active);
-  EXPECT_EQ(result.stream_status, Status::NotFound());
+  context.SendPacket(internal::PacketType::SERVER_STREAM_END,
+                     Status::NotFound());
+  EXPECT_FALSE(handler.active());
+  EXPECT_EQ(handler.status(), Status::NotFound());
 }
 
 }  // namespace
 }  // namespace pw::rpc
-
-PW_MODIFY_DIAGNOSTICS_POP();
diff --git a/pw_rpc/nanopb/common.cc b/pw_rpc/nanopb/common.cc
deleted file mode 100644
index a571ebe..0000000
--- a/pw_rpc/nanopb/common.cc
+++ /dev/null
@@ -1,144 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// clang-format off
-#include "pw_rpc/internal/log_config.h" // PW_LOG_* macros must be first.
-
-#include "pw_rpc/nanopb/internal/common.h"
-// clang-format on
-
-#include "pb_decode.h"
-#include "pb_encode.h"
-#include "pw_assert/check.h"
-#include "pw_log/log.h"
-#include "pw_result/result.h"
-#include "pw_rpc/internal/client_call.h"
-#include "pw_rpc/nanopb/server_reader_writer.h"
-#include "pw_status/try.h"
-
-namespace pw::rpc::internal {
-namespace {
-
-// Nanopb 3 uses pb_field_s and Nanopb 4 uses pb_msgdesc_s for fields. The
-// Nanopb version macro is difficult to use, so deduce the correct type from the
-// pb_decode function.
-template <typename DecodeFunction>
-struct NanopbTraits;
-
-template <typename FieldsType>
-struct NanopbTraits<bool(pb_istream_t*, FieldsType, void*)> {
-  using Fields = FieldsType;
-};
-
-using Fields = typename NanopbTraits<decltype(pb_decode)>::Fields;
-
-Result<ByteSpan> EncodeToPayloadBuffer(const void* payload, NanopbSerde serde)
-    PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-  ByteSpan payload_buffer = GetPayloadBuffer();
-  StatusWithSize result = serde.Encode(payload, payload_buffer);
-  if (!result.ok()) {
-    return result.status();
-  }
-  return payload_buffer.first(result.size());
-}
-
-}  // namespace
-
-// PB_NO_ERRMSG is used in pb_decode.h and pb_encode.h to enable or disable the
-// errmsg member of the istream and ostream structs. If errmsg is available, use
-// it to give more detailed log messages.
-#ifdef PB_NO_ERRMSG
-
-#define PW_RPC_LOG_NANOPB_FAILURE(msg, stream) PW_LOG_ERROR(msg)
-
-#else
-
-#define PW_RPC_LOG_NANOPB_FAILURE(msg, stream) \
-  PW_LOG_ERROR(msg ": %s", stream.errmsg)
-
-#endif  // PB_NO_ERRMSG
-
-StatusWithSize NanopbSerde::Encode(const void* proto_struct,
-                                   ByteSpan buffer) const {
-  auto output = pb_ostream_from_buffer(
-      reinterpret_cast<pb_byte_t*>(buffer.data()), buffer.size());
-  if (!pb_encode(&output, static_cast<Fields>(fields_), proto_struct)) {
-    PW_RPC_LOG_NANOPB_FAILURE("Nanopb protobuf encode failed", output);
-    return StatusWithSize::Internal();
-  }
-  return StatusWithSize(output.bytes_written);
-}
-
-StatusWithSize NanopbSerde::EncodedSizeBytes(const void* proto_struct) const {
-  size_t encoded_size = 0;
-  return pb_get_encoded_size(&encoded_size, fields_, proto_struct)
-             ? StatusWithSize(encoded_size)
-             : StatusWithSize::Unknown();
-}
-
-bool NanopbSerde::Decode(ConstByteSpan buffer, void* proto_struct) const {
-  auto input = pb_istream_from_buffer(
-      reinterpret_cast<const pb_byte_t*>(buffer.data()), buffer.size());
-  bool result = pb_decode(&input, static_cast<Fields>(fields_), proto_struct);
-  if (!result) {
-    PW_RPC_LOG_NANOPB_FAILURE("Nanopb protobuf decode failed", input);
-  }
-  return result;
-}
-
-#undef PW_RPC_LOG_NANOPB_FAILURE
-
-void NanopbSendInitialRequest(ClientCall& call,
-                              NanopbSerde serde,
-                              const void* payload) {
-  PW_DCHECK(call.active_locked());
-
-  Result<ByteSpan> result = EncodeToPayloadBuffer(payload, serde);
-
-  if (result.ok()) {
-    call.SendInitialClientRequest(*result);
-  } else {
-    call.HandleError(result.status());
-  }
-}
-
-Status NanopbSendStream(Call& call, const void* payload, NanopbSerde serde) {
-  LockGuard lock(rpc_lock());
-  if (!call.active_locked()) {
-    return Status::FailedPrecondition();
-  }
-
-  Result<ByteSpan> result = EncodeToPayloadBuffer(payload, serde);
-
-  PW_TRY(result.status());
-  return call.WriteLocked(*result);
-}
-
-Status SendFinalResponse(NanopbServerCall& call,
-                         const void* payload,
-                         const Status status) {
-  LockGuard lock(rpc_lock());
-  if (!call.active_locked()) {
-    return Status::FailedPrecondition();
-  }
-
-  Result<ByteSpan> result =
-      EncodeToPayloadBuffer(payload, call.serde().response());
-  if (!result.ok()) {
-    return call.CloseAndSendServerErrorLocked(Status::Internal());
-  }
-  return call.CloseAndSendResponseLocked(*result, status);
-}
-
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/nanopb/docs.rst b/pw_rpc/nanopb/docs.rst
index 5ed7010..0d61766 100644
--- a/pw_rpc/nanopb/docs.rst
+++ b/pw_rpc/nanopb/docs.rst
@@ -10,7 +10,7 @@
 =====
 To enable nanopb code generation, the build argument
 ``dir_pw_third_party_nanopb`` must be set to point to a local nanopb
-installation. Nanopb 0.4 is recommended, but Nanopb 0.3 is also supported.
+installation.
 
 Define a ``pw_proto_library`` containing the .proto file defining your service
 (and optionally other related protos), then depend on the ``nanopb_rpc``
@@ -27,7 +27,7 @@
     sources = [ "chat_protos/chat_service.proto" ]
   }
 
-  # Library that implements the Chat service.
+  # Library that implements the ChatService.
   pw_source_set("chat_service") {
     sources = [
       "chat_service.cc",
@@ -43,7 +43,7 @@
 
 Generated code API
 ==================
-All examples in this document use the following RPC service definition.
+Take the following RPC service as an example.
 
 .. code:: protobuf
 
@@ -51,7 +51,7 @@
 
   syntax = "proto3";
 
-  service Chat {
+  service ChatService {
     // Returns information about a chatroom.
     rpc GetRoomInformation(RoomInfoRequest) returns (RoomInfoResponse) {}
 
@@ -69,7 +69,7 @@
 Server-side
 -----------
 A C++ class is generated for each service in the .proto file. The class is
-located within a special ``pw_rpc::nanopb`` sub-namespace of the file's package.
+located within a special ``generated`` sub-namespace of the file's package.
 
 The generated class is a base class which must be derived to implement the
 service's methods. The base class is templated on the derived class.
@@ -78,7 +78,7 @@
 
   #include "chat_protos/chat_service.rpc.pb.h"
 
-  class ChatService final : public pw_rpc::nanopb::Chat::Service<ChatService> {
+  class ChatService final : public generated::ChatService<ChatService> {
    public:
     // Implementations of the service's RPC methods; see below.
   };
@@ -91,7 +91,7 @@
 
 .. code:: c++
 
-  pw::Status GetRoomInformation(pw::rpc::
+  pw::Status GetRoomInformation(pw::rpc::ServerContext& ctx,
                                 const RoomInfoRequest& request,
                                 RoomInfoResponse& response);
 
@@ -102,7 +102,7 @@
 
 .. code:: c++
 
-  void ListUsersInRoom(pw::rpc::
+  void ListUsersInRoom(pw::rpc::ServerContext& ctx,
                        const ListUsersRequest& request,
                        pw::rpc::ServerWriter<ListUsersResponse>& writer);
 
@@ -140,85 +140,151 @@
 Client-side
 -----------
 A corresponding client class is generated for every service defined in the proto
-file. To allow multiple types of clients to exist, it is placed under the
-``pw_rpc::nanopb`` namespace. The ``Client`` class is nested under
-``pw_rpc::nanopb::ServiceName``. For example, the ``Chat`` service would create
-``pw_rpc::nanopb::Chat::Client``.
+file. Like the service class, it is placed under the ``generated`` namespace.
+The class is named after the service, with a ``Client`` suffix. For example, the
+``ChatService`` would create a ``generated::ChatServiceClient``.
 
-Service clients are instantiated with a reference to the RPC client through
-which they will send requests, and the channel ID they will use.
+The client class contains static methods to call each of the service's methods.
+It is not meant to be instantiated. The signatures for the methods all follow
+the same format, taking a channel through which to communicate, the initial
+request struct, and a response handler.
 
 .. code-block:: c++
 
-  // Nested under pw_rpc::nanopb::ServiceName.
-  class Client {
+  static NanopbClientCall<UnaryResponseHandler<RoomInfoResponse>>
+  GetRoomInformation(Channel& channel,
+                     const RoomInfoRequest& request,
+                     UnaryResponseHandler<RoomInfoResponse> handler);
+
+The ``NanopbClientCall`` object returned by the RPC invocation stores the active
+RPC's context. For more information on ``ClientCall`` objects, refer to the
+:ref:`core RPC documentation <module-pw_rpc-making-calls>`.
+
+Response handlers
+^^^^^^^^^^^^^^^^^
+RPC responses are sent back to the caller through a response handler object.
+These are classes with virtual callback functions implemented by the RPC caller
+to handle RPC events.
+
+There are two types of response handlers: unary and server-streaming, which are
+used depending whether the method's responses are a stream or not.
+
+Unary / client streaming RPC
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+A ``UnaryResponseHandler`` is used by methods where the server returns a single
+response. It contains a callback for the response, which is only called once.
+
+.. code-block:: c++
+
+  template <typename Response>
+  class UnaryResponseHandler {
    public:
-    Client(::pw::rpc::Client& client, uint32_t channel_id);
+    virtual ~UnaryResponseHandler() = default;
 
-    GetRoomInformationCall GetRoomInformation(
-        const RoomInfoRequest& request,
-        ::pw::Function<void(Status, const RoomInfoResponse&)> on_response,
-        ::pw::Function<void(Status)> on_rpc_error = nullptr);
+    // Called when the response is received from the server with the method's
+    // status and the deserialized response struct.
+    virtual void ReceivedResponse(Status status, const Response& response) = 0;
 
-    // ...and more (see below).
+    // Called when an error occurs internally in the RPC client or server.
+    virtual void RpcError(Status) {}
   };
 
-RPCs can also be invoked individually as free functions:
+.. cpp:class:: template <typename Response> UnaryResponseHandler
+
+  A handler for RPC methods which return a single response (i.e. unary and
+  client streaming).
+
+.. cpp:function:: virtual void UnaryResponseHandler::ReceivedResponse(Status status, const Response& response)
+
+  Callback invoked when the response is recieved from the server. Guaranteed to
+  only be called once.
+
+.. cpp:function:: virtual void UnaryResponseHandler::RpcError(Status status)
+
+  Callback invoked if an internal error occurs in the RPC system. Optional;
+  defaults to a no-op.
+
+**Example implementation**
 
 .. code-block:: c++
 
-    GetRoomInformationCall call = pw_rpc::nanopb::Chat::GetRoomInformation(
-        client, channel_id, request, on_response, on_rpc_error);
+  class RoomInfoHandler : public UnaryResponseHandler<RoomInfoResponse> {
+   public:
+    void ReceivedResponse(Status status,
+                          const RoomInfoResponse& response) override {
+      if (status.ok()) {
+        response_ = response;
+      }
+    }
 
-The client class has member functions for each method defined within the
-service's protobuf descriptor. The arguments to these methods vary depending on
-the type of RPC. Each method returns a ``NanopbClientCall`` object which stores
-the context of the ongoing RPC call. For more information on ``ClientCall``
-objects, refer to the :ref:`core RPC docs <module-pw_rpc-making-calls>`. The
-type of the returned object is complex, so it is aliased using the method
-name.
+    constexpr RoomInfoResponse& response() { return response_; }
 
-.. admonition:: Callback invocation
+   private:
+    RoomInfoResponse response_;
+  };
 
-  RPC callbacks are invoked synchronously from ``Client::ProcessPacket``.
+Server streaming / bidirectional streaming RPC
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+For methods which return a response stream, a ``ServerStreamingResponseHandler``
+is used.
 
-Method APIs
-^^^^^^^^^^^
-The arguments provided when invoking a method depend on its type.
+.. code:: c++
 
-Unary RPC
-~~~~~~~~~
-A unary RPC call takes the request struct and a callback to invoke when a
-response is received. The callback receives the RPC's status and response
-struct.
+  class ServerStreamingResponseHandler {
+   public:
+    virtual ~ServerStreamingResponseHandler() = default;
 
-An optional second callback can be provided to handle internal errors.
+    // Called on every response received from the server with the deserialized
+    // response struct.
+    virtual void ReceivedResponse(const Response& response) = 0;
+
+    // Called when the server ends the stream with the overall RPC status.
+    virtual void Complete(Status status) = 0;
+
+    // Called when an error occurs internally in the RPC client or server.
+    virtual void RpcError(Status) {}
+  };
+
+.. cpp:class:: template <typename Response> ServerStreamingResponseHandler
+
+  A handler for RPC methods which return zero or more responses (i.e. server
+  and bidirectional streaming).
+
+.. cpp:function:: virtual void ServerStreamingResponseHandler::ReceivedResponse(const Response& response)
+
+  Callback invoked whenever a response is received from the server.
+
+.. cpp:function:: virtual void ServerStreamingResponseHandler::Complete(Status status)
+
+  Callback invoked when the server ends the stream, with the overall status for
+  the RPC.
+
+.. cpp:function:: virtual void ServerStreamingResponseHandler::RpcError(Status status)
+
+  Callback invoked if an internal error occurs in the RPC system. Optional;
+  defaults to a no-op.
+
+**Example implementation**
 
 .. code-block:: c++
 
-  GetRoomInformationCall GetRoomInformation(
-      const RoomInfoRequest& request,
-      ::pw::Function<void(const RoomInfoResponse&, Status)> on_response,
-      ::pw::Function<void(Status)> on_rpc_error = nullptr);
+  class ChatHandler : public UnaryResponseHandler<ChatMessage> {
+   public:
+    void ReceivedResponse(const ChatMessage& response) override {
+      gui_.RenderChatMessage(response);
+    }
 
-Server streaming RPC
-~~~~~~~~~~~~~~~~~~~~
-A server streaming RPC call takes the initial request struct and two callbacks.
-The first is invoked on every stream response received, and the second is
-invoked once the stream is complete with its overall status.
+    void Complete(Status status) override {
+      client_.Exit(status);
+    }
 
-An optional third callback can be provided to handle internal errors.
-
-.. code-block:: c++
-
-  ListUsersInRoomCall ListUsersInRoom(
-      const ListUsersRequest& request,
-      ::pw::Function<void(const ListUsersResponse&)> on_response,
-      ::pw::Function<void(Status)> on_stream_end,
-      ::pw::Function<void(Status)> on_rpc_error = nullptr);
+   private:
+    ChatGui& gui_;
+    ChatClient& client_;
+  };
 
 Example usage
-^^^^^^^^^^^^^
+~~~~~~~~~~~~~
 The following example demonstrates how to call an RPC method using a nanopb
 service client and receive the response.
 
@@ -227,55 +293,24 @@
   #include "chat_protos/chat_service.rpc.pb.h"
 
   namespace {
-
-    using ChatClient = pw_rpc::nanopb::Chat::Client;
-
     MyChannelOutput output;
-    pw::rpc::Channel channels[] = {pw::rpc::Channel::Create<1>(&output)};
+    pw::rpc::Channel channels[] = {pw::rpc::Channel::Create<0>(&output)};
     pw::rpc::Client client(channels);
-
-    // Callback function for GetRoomInformation.
-    void LogRoomInformation(const RoomInfoResponse& response, Status status);
-
-  }  // namespace
+  }
 
   void InvokeSomeRpcs() {
-    // Instantiate a service client to call Chat service methods on channel 1.
-    ChatClient chat_client(client, 1);
+    RoomInfoHandler handler;
 
     // The RPC will remain active as long as `call` is alive.
-    auto call = chat_client.GetRoomInformation(
-        {.room = "pigweed"}, LogRoomInformation);
-    if (!call.active()) {
-      // The invocation may fail. This could occur due to an invalid channel ID,
-      // for example. The failure status is forwarded to the to call's
-      // on_rpc_error callback.
-      return;
-    }
+    auto call = ChatServiceClient::GetRoomInformation(channels[0],
+                                                      {.room = "pigweed"},
+                                                      handler);
 
-    // For simplicity, block until the call completes. An actual implementation
-    // would likely std::move the call somewhere to keep it active while doing
-    // other work.
+    // For simplicity, block here. An actual implementation would likely
+    // std::move the call somewhere to keep it active while doing other work.
     while (call.active()) {
       Wait();
     }
 
-    // Do other stuff now that we have the room information.
+    DoStuff(handler.response());
   }
-
-Zephyr
-======
-To enable ``pw_rpc.nanopb.*`` for Zephyr add ``CONFIG_PIGWEED_RPC_NANOPB=y`` to
-the project's configuration. This will enable the Kconfig menu for the
-following:
-
-* ``pw_rpc.nanopb.method`` which can be enabled via
-  ``CONFIG_PIGWEED_RPC_NANOPB_METHOD=y``.
-* ``pw_rpc.nanopb.method_union`` which can be enabled via
-  ``CONFIG_PIGWEED_RPC_NANOPB_METHOD_UNION=y``.
-* ``pw_rpc.nanopb.client`` which can be enabled via
-  ``CONFIG_PIGWEED_RPC_NANOPB_CLIENT=y``.
-* ``pw_rpc.nanopb.common`` which can be enabled via
-  ``CONFIG_PIGWEED_RPC_NANOPB_COMMON=y``.
-* ``pw_rpc.nanopb.echo_service`` which can be enabled via
-  ``CONFIG_PIGWEED_RPC_NANOPB_ECHO_SERVICE=y``.
diff --git a/pw_rpc/nanopb/echo_service_test.cc b/pw_rpc/nanopb/echo_service_test.cc
index 4d134c2..11a0608 100644
--- a/pw_rpc/nanopb/echo_service_test.cc
+++ b/pw_rpc/nanopb/echo_service_test.cc
@@ -14,7 +14,7 @@
 
 #include "gtest/gtest.h"
 #include "pw_rpc/echo_service_nanopb.h"
-#include "pw_rpc/nanopb/test_method_context.h"
+#include "pw_rpc/nanopb_test_method_context.h"
 
 namespace pw::rpc {
 namespace {
diff --git a/pw_rpc/nanopb/fake_channel_output_test.cc b/pw_rpc/nanopb/fake_channel_output_test.cc
deleted file mode 100644
index f21b5dc..0000000
--- a/pw_rpc/nanopb/fake_channel_output_test.cc
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/nanopb/fake_channel_output.h"
-
-#include <array>
-#include <cstddef>
-#include <memory>
-
-#include "gtest/gtest.h"
-#include "pw_rpc/internal/channel.h"
-#include "pw_rpc/internal/packet.h"
-#include "pw_rpc_test_protos/test.rpc.pb.h"
-
-namespace pw::rpc::internal::test {
-namespace {
-
-using rpc::test::pw_rpc::nanopb::TestService;
-using Info = internal::MethodInfo<TestService::TestUnaryRpc>;
-
-TEST(NanopbFakeChannelOutput, Requests) {
-  NanopbFakeChannelOutput<1> output;
-
-  std::byte payload_buffer[32] = {};
-  constexpr Info::Request request{.integer = -100, .status_code = 5};
-  const StatusWithSize payload =
-      Info::serde().EncodeRequest(&request, payload_buffer);
-  ASSERT_TRUE(payload.ok());
-
-  std::array<std::byte, 128> buffer;
-
-  auto packet = Packet(PacketType::REQUEST,
-                       1,
-                       Info::kServiceId,
-                       Info::kMethodId,
-                       999,
-                       std::span(payload_buffer, payload.size()))
-                    .Encode(buffer);
-  ASSERT_TRUE(packet.ok());
-
-  ASSERT_EQ(OkStatus(), output.Send(std::span(buffer).first(packet->size())));
-
-  ASSERT_TRUE(output.responses<TestService::TestUnaryRpc>().empty());
-  ASSERT_EQ(output.requests<TestService::TestUnaryRpc>().size(), 1u);
-
-  Info::Request sent = output.requests<TestService::TestUnaryRpc>().front();
-  EXPECT_EQ(sent.integer, -100);
-  EXPECT_EQ(sent.status_code, 5u);
-}
-
-TEST(NanopbFakeChannelOutput, Responses) {
-  NanopbFakeChannelOutput<1> output;
-
-  std::byte payload_buffer[32] = {};
-  constexpr Info::Response response{.value = -9876, .repeated_field = {}};
-  const StatusWithSize payload =
-      Info::serde().EncodeResponse(&response, payload_buffer);
-  ASSERT_TRUE(payload.ok());
-
-  std::array<std::byte, 128> buffer;
-
-  auto packet = Packet(PacketType::RESPONSE,
-                       1,
-                       Info::kServiceId,
-                       Info::kMethodId,
-                       999,
-                       std::span(payload_buffer, payload.size()))
-                    .Encode(buffer);
-  ASSERT_TRUE(packet.ok());
-
-  ASSERT_EQ(OkStatus(), output.Send(std::span(buffer).first(packet->size())));
-
-  ASSERT_EQ(output.responses<TestService::TestUnaryRpc>().size(), 1u);
-  ASSERT_TRUE(output.requests<TestService::TestUnaryRpc>().empty());
-
-  Info::Response sent = output.responses<TestService::TestUnaryRpc>().front();
-  EXPECT_EQ(sent.value, -9876);
-}
-
-}  // namespace
-}  // namespace pw::rpc::internal::test
diff --git a/pw_rpc/nanopb/method.cc b/pw_rpc/nanopb/method.cc
deleted file mode 100644
index ed38f39..0000000
--- a/pw_rpc/nanopb/method.cc
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// clang-format off
-#include "pw_rpc/internal/log_config.h" // PW_LOG_* macros must be first.
-
-#include "pw_rpc/nanopb/internal/method.h"
-// clang-format on
-
-#include "pb_decode.h"
-#include "pb_encode.h"
-#include "pw_log/log.h"
-#include "pw_rpc/internal/packet.h"
-
-namespace pw::rpc {
-
-namespace internal {
-
-void NanopbMethod::CallSynchronousUnary(const CallContext& context,
-                                        const Packet& request,
-                                        void* request_struct,
-                                        void* response_struct) const {
-  if (!DecodeRequest(context, request, request_struct)) {
-    rpc_lock().unlock();
-    return;
-  }
-
-  NanopbServerCall responder(context, MethodType::kUnary);
-  rpc_lock().unlock();
-  const Status status = function_.synchronous_unary(
-      context.service(), request_struct, response_struct);
-  responder.SendUnaryResponse(response_struct, status).IgnoreError();
-}
-
-void NanopbMethod::CallUnaryRequest(const CallContext& context,
-                                    MethodType type,
-                                    const Packet& request,
-                                    void* request_struct) const {
-  if (!DecodeRequest(context, request, request_struct)) {
-    rpc_lock().unlock();
-    return;
-  }
-
-  NanopbServerCall server_writer(context, type);
-  rpc_lock().unlock();
-  function_.unary_request(context.service(), request_struct, server_writer);
-}
-
-bool NanopbMethod::DecodeRequest(const CallContext& context,
-                                 const Packet& request,
-                                 void* proto_struct) const {
-  if (serde_.DecodeRequest(request.payload(), proto_struct)) {
-    return true;
-  }
-
-  // The channel is known to exist. It was found when the request was processed
-  // and the lock has been held since, so GetInternalChannel cannot fail.
-  static_cast<internal::Channel*>(
-      context.server().GetInternalChannel(context.channel_id()))
-      ->Send(Packet::ServerError(request, Status::DataLoss()))
-      .IgnoreError();
-  PW_LOG_WARN("Nanopb failed to decode request payload from channel %u",
-              unsigned(context.channel_id()));
-  return false;
-}
-
-}  // namespace internal
-}  // namespace pw::rpc
diff --git a/pw_rpc/nanopb/method_info_test.cc b/pw_rpc/nanopb/method_info_test.cc
deleted file mode 100644
index f200a12..0000000
--- a/pw_rpc/nanopb/method_info_test.cc
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/internal/method_info.h"
-
-#include "gtest/gtest.h"
-#include "pw_rpc/internal/method_info_tester.h"
-#include "pw_rpc_test_protos/test.rpc.pb.h"
-
-namespace pw::rpc::internal {
-namespace {
-
-using GeneratedService = pw::rpc::test::pw_rpc::nanopb::TestService;
-
-class TestService final : public GeneratedService::Service<TestService> {
- public:
-  Status TestUnaryRpc(const pw_rpc_test_TestRequest&,
-                      pw_rpc_test_TestResponse&) {
-    return OkStatus();
-  }
-
-  void TestAnotherUnaryRpc(const pw_rpc_test_TestRequest&,
-                           NanopbUnaryResponder<pw_rpc_test_TestResponse>&) {}
-
-  static void TestServerStreamRpc(
-      const pw_rpc_test_TestRequest&,
-      ServerWriter<pw_rpc_test_TestStreamResponse>&) {}
-
-  void TestClientStreamRpc(
-      ServerReader<pw_rpc_test_TestRequest, pw_rpc_test_TestStreamResponse>&) {}
-
-  void TestBidirectionalStreamRpc(
-      ServerReaderWriter<pw_rpc_test_TestRequest,
-                         pw_rpc_test_TestStreamResponse>&) {}
-};
-
-static_assert(MethodInfoTests<GeneratedService, TestService>().Pass());
-
-}  // namespace
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/nanopb/method_lookup_test.cc b/pw_rpc/nanopb/method_lookup_test.cc
index 7d45879..02137c8 100644
--- a/pw_rpc/nanopb/method_lookup_test.cc
+++ b/pw_rpc/nanopb/method_lookup_test.cc
@@ -13,146 +13,68 @@
 // the License.
 
 #include "gtest/gtest.h"
-#include "pw_rpc/nanopb/test_method_context.h"
-#include "pw_rpc/raw/test_method_context.h"
+#include "pw_rpc/nanopb_test_method_context.h"
+#include "pw_rpc/raw_test_method_context.h"
 #include "pw_rpc_test_protos/test.rpc.pb.h"
 
 namespace pw::rpc {
 namespace {
 
-class MixedService1
-    : public test::pw_rpc::nanopb::TestService::Service<MixedService1> {
+class MixedService1 : public test::generated::TestService<MixedService1> {
  public:
-  void TestUnaryRpc(ConstByteSpan, RawUnaryResponder& responder) {
-    std::byte response[5] = {};
-    ASSERT_EQ(OkStatus(), responder.Finish(response, OkStatus()));
+  StatusWithSize TestRpc(ServerContext&, ConstByteSpan, ByteSpan) {
+    return StatusWithSize(123);
   }
 
-  void TestAnotherUnaryRpc(const pw_rpc_test_TestRequest&,
-                           NanopbUnaryResponder<pw_rpc_test_TestResponse>&) {
-    called_async_unary_method = true;
+  void TestStreamRpc(ServerContext&,
+                     const pw_rpc_test_TestRequest&,
+                     ServerWriter<pw_rpc_test_TestStreamResponse>&) {
+    called_streaming_method = true;
   }
 
-  void TestServerStreamRpc(const pw_rpc_test_TestRequest&,
-                           ServerWriter<pw_rpc_test_TestStreamResponse>&) {
-    called_server_streaming_method = true;
-  }
-
-  void TestClientStreamRpc(RawServerReader&) {
-    called_client_streaming_method = true;
-  }
-
-  void TestBidirectionalStreamRpc(
-      ServerReaderWriter<pw_rpc_test_TestRequest,
-                         pw_rpc_test_TestStreamResponse>&) {
-    called_bidirectional_streaming_method = true;
-  }
-
-  bool called_async_unary_method = false;
-  bool called_server_streaming_method = false;
-  bool called_client_streaming_method = false;
-  bool called_bidirectional_streaming_method = false;
+  bool called_streaming_method = false;
 };
 
-class MixedService2
-    : public test::pw_rpc::nanopb::TestService::Service<MixedService2> {
+class MixedService2 : public test::generated::TestService<MixedService2> {
  public:
-  Status TestUnaryRpc(const pw_rpc_test_TestRequest&,
-                      pw_rpc_test_TestResponse&) {
+  Status TestRpc(ServerContext&,
+                 const pw_rpc_test_TestRequest&,
+                 pw_rpc_test_TestResponse&) {
     return Status::Unauthenticated();
   }
 
-  void TestAnotherUnaryRpc(ConstByteSpan, RawUnaryResponder&) {
-    called_async_unary_method = true;
+  void TestStreamRpc(ServerContext&, ConstByteSpan, RawServerWriter&) {
+    called_streaming_method = true;
   }
 
-  void TestServerStreamRpc(ConstByteSpan, RawServerWriter&) {
-    called_server_streaming_method = true;
-  }
-
-  void TestClientStreamRpc(
-      ServerReader<pw_rpc_test_TestRequest, pw_rpc_test_TestStreamResponse>&) {
-    called_client_streaming_method = true;
-  }
-
-  void TestBidirectionalStreamRpc(RawServerReaderWriter&) {
-    called_bidirectional_streaming_method = true;
-  }
-
-  bool called_async_unary_method = false;
-  bool called_server_streaming_method = false;
-  bool called_client_streaming_method = false;
-  bool called_bidirectional_streaming_method = false;
+  bool called_streaming_method = false;
 };
 
-TEST(MixedService1, CallRawMethod_SyncUnary) {
-  PW_RAW_TEST_METHOD_CONTEXT(MixedService1, TestUnaryRpc) context;
+TEST(MixedService1, CallRawMethod) {
+  PW_RAW_TEST_METHOD_CONTEXT(MixedService1, TestRpc) context;
+  StatusWithSize sws = context.call({});
+  EXPECT_TRUE(sws.ok());
+  EXPECT_EQ(123u, sws.size());
+}
+
+TEST(MixedService1, CallNanopbMethod) {
+  PW_NANOPB_TEST_METHOD_CONTEXT(MixedService1, TestStreamRpc) context;
+  ASSERT_FALSE(context.service().called_streaming_method);
   context.call({});
-  EXPECT_EQ(OkStatus(), context.status());
-  EXPECT_EQ(5u, context.response().size());
+  EXPECT_TRUE(context.service().called_streaming_method);
 }
 
-TEST(MixedService1, CallNanopbMethod_AsyncUnary) {
-  PW_NANOPB_TEST_METHOD_CONTEXT(MixedService1, TestAnotherUnaryRpc) context;
-  ASSERT_FALSE(context.service().called_async_unary_method);
-  context.call({});
-  EXPECT_TRUE(context.service().called_async_unary_method);
-}
-
-TEST(MixedService1, CallNanopbMethod_ServerStreaming) {
-  PW_NANOPB_TEST_METHOD_CONTEXT(MixedService1, TestServerStreamRpc) context;
-  ASSERT_FALSE(context.service().called_server_streaming_method);
-  context.call({});
-  EXPECT_TRUE(context.service().called_server_streaming_method);
-}
-
-TEST(MixedService1, CallRawMethod_ClientStreaming) {
-  PW_RAW_TEST_METHOD_CONTEXT(MixedService1, TestClientStreamRpc) context;
-  ASSERT_FALSE(context.service().called_client_streaming_method);
-  context.call();
-  EXPECT_TRUE(context.service().called_client_streaming_method);
-}
-
-TEST(MixedService1, CallNanopbMethod_BidirectionalStreaming) {
-  PW_NANOPB_TEST_METHOD_CONTEXT(MixedService1, TestBidirectionalStreamRpc)
-  context;
-  ASSERT_FALSE(context.service().called_bidirectional_streaming_method);
-  context.call();
-  EXPECT_TRUE(context.service().called_bidirectional_streaming_method);
-}
-
-TEST(MixedService2, CallNanopbMethod_SyncUnary) {
-  PW_NANOPB_TEST_METHOD_CONTEXT(MixedService2, TestUnaryRpc) context;
+TEST(MixedService2, CallNanopbMethod) {
+  PW_NANOPB_TEST_METHOD_CONTEXT(MixedService2, TestRpc) context;
   Status status = context.call({});
   EXPECT_EQ(Status::Unauthenticated(), status);
 }
 
-TEST(MixedService2, CallRawMethod_AsyncUnary) {
-  PW_RAW_TEST_METHOD_CONTEXT(MixedService2, TestAnotherUnaryRpc) context;
-  ASSERT_FALSE(context.service().called_async_unary_method);
+TEST(MixedService2, CallRawMethod) {
+  PW_RAW_TEST_METHOD_CONTEXT(MixedService2, TestStreamRpc) context;
+  ASSERT_FALSE(context.service().called_streaming_method);
   context.call({});
-  EXPECT_TRUE(context.service().called_async_unary_method);
-}
-
-TEST(MixedService2, CallRawMethod_ServerStreaming) {
-  PW_RAW_TEST_METHOD_CONTEXT(MixedService2, TestServerStreamRpc) context;
-  ASSERT_FALSE(context.service().called_server_streaming_method);
-  context.call({});
-  EXPECT_TRUE(context.service().called_server_streaming_method);
-}
-
-TEST(MixedService2, CallNanopbMethod_ClientStreaming) {
-  PW_NANOPB_TEST_METHOD_CONTEXT(MixedService2, TestClientStreamRpc) context;
-  ASSERT_FALSE(context.service().called_client_streaming_method);
-  context.call();
-  EXPECT_TRUE(context.service().called_client_streaming_method);
-}
-
-TEST(MixedService2, CallRawMethod_BidirectionalStreaming) {
-  PW_RAW_TEST_METHOD_CONTEXT(MixedService2, TestBidirectionalStreamRpc) context;
-  ASSERT_FALSE(context.service().called_bidirectional_streaming_method);
-  context.call();
-  EXPECT_TRUE(context.service().called_bidirectional_streaming_method);
+  EXPECT_TRUE(context.service().called_streaming_method);
 }
 
 }  // namespace
diff --git a/pw_rpc/nanopb/method_test.cc b/pw_rpc/nanopb/method_test.cc
deleted file mode 100644
index cf19fe0..0000000
--- a/pw_rpc/nanopb/method_test.cc
+++ /dev/null
@@ -1,415 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/nanopb/internal/method.h"
-
-#include <array>
-
-#include "gtest/gtest.h"
-#include "pw_rpc/internal/lock.h"
-#include "pw_rpc/internal/method_impl_tester.h"
-#include "pw_rpc/internal/test_utils.h"
-#include "pw_rpc/nanopb/internal/method_union.h"
-#include "pw_rpc/service.h"
-#include "pw_rpc_nanopb_private/internal_test_utils.h"
-#include "pw_rpc_test_protos/test.pb.h"
-
-PW_MODIFY_DIAGNOSTICS_PUSH();
-PW_MODIFY_DIAGNOSTIC(ignored, "-Wmissing-field-initializers");
-
-namespace pw::rpc::internal {
-namespace {
-
-using std::byte;
-
-struct FakePb {};
-
-// Create a fake service for use with the MethodImplTester.
-class TestNanopbService final : public Service {
- public:
-  // Unary signatures
-
-  Status Unary(const FakePb&, FakePb&) { return Status(); }
-
-  static Status StaticUnary(const FakePb&, FakePb&) { return Status(); }
-
-  void AsyncUnary(const FakePb&, NanopbUnaryResponder<FakePb>&) {}
-
-  static void StaticAsyncUnary(const FakePb&, NanopbUnaryResponder<FakePb>&) {}
-
-  Status UnaryWrongArg(FakePb&, FakePb&) { return Status(); }
-
-  static void StaticUnaryVoidReturn(const FakePb&, FakePb&) {}
-
-  // Server streaming signatures
-
-  void ServerStreaming(const FakePb&, NanopbServerWriter<FakePb>&) {}
-
-  static void StaticServerStreaming(const FakePb&,
-                                    NanopbServerWriter<FakePb>&) {}
-
-  int ServerStreamingBadReturn(const FakePb&, NanopbServerWriter<FakePb>&) {
-    return 5;
-  }
-
-  static void StaticServerStreamingMissingArg(NanopbServerWriter<FakePb>&) {}
-
-  // Client streaming signatures
-
-  void ClientStreaming(NanopbServerReader<FakePb, FakePb>&) {}
-
-  static void StaticClientStreaming(NanopbServerReader<FakePb, FakePb>&) {}
-
-  int ClientStreamingBadReturn(NanopbServerReader<FakePb, FakePb>&) {
-    return 0;
-  }
-
-  static void StaticClientStreamingMissingArg() {}
-
-  // Bidirectional streaming signatures
-
-  void BidirectionalStreaming(NanopbServerReaderWriter<FakePb, FakePb>&) {}
-
-  static void StaticBidirectionalStreaming(
-      NanopbServerReaderWriter<FakePb, FakePb>&) {}
-
-  int BidirectionalStreamingBadReturn(
-      NanopbServerReaderWriter<FakePb, FakePb>&) {
-    return 0;
-  }
-
-  static void StaticBidirectionalStreamingMissingArg() {}
-};
-
-struct WrongPb;
-
-// Test matches() rejects incorrect request/response types.
-// clang-format off
-static_assert(!NanopbMethod::template matches<&TestNanopbService::Unary, WrongPb, FakePb>());
-static_assert(!NanopbMethod::template matches<&TestNanopbService::Unary, FakePb, WrongPb>());
-static_assert(!NanopbMethod::template matches<&TestNanopbService::Unary, WrongPb, WrongPb>());
-static_assert(!NanopbMethod::template matches<&TestNanopbService::StaticUnary, FakePb, WrongPb>());
-
-static_assert(!NanopbMethod::template matches<&TestNanopbService::ServerStreaming, WrongPb, FakePb>());
-static_assert(!NanopbMethod::template matches<&TestNanopbService::StaticServerStreaming, FakePb, WrongPb>());
-
-static_assert(!NanopbMethod::template matches<&TestNanopbService::ClientStreaming, WrongPb, FakePb>());
-static_assert(!NanopbMethod::template matches<&TestNanopbService::StaticClientStreaming, FakePb, WrongPb>());
-
-static_assert(!NanopbMethod::template matches<&TestNanopbService::BidirectionalStreaming, WrongPb, FakePb>());
-static_assert(!NanopbMethod::template matches<&TestNanopbService::StaticBidirectionalStreaming, FakePb, WrongPb>());
-// clang-format on
-
-static_assert(MethodImplTests<NanopbMethod, TestNanopbService>().Pass(
-    MatchesTypes<FakePb, FakePb>(),
-    std::tuple<const NanopbMethodSerde&>(
-        kNanopbMethodSerde<nullptr, nullptr>)));
-
-template <typename Impl>
-class FakeServiceBase : public Service {
- public:
-  FakeServiceBase(uint32_t id) : Service(id, kMethods) {}
-
-  static constexpr std::array<NanopbMethodUnion, 5> kMethods = {
-      NanopbMethod::SynchronousUnary<&Impl::DoNothing>(
-          10u,
-          kNanopbMethodSerde<pw_rpc_test_Empty_fields,
-                             pw_rpc_test_Empty_fields>),
-      NanopbMethod::AsynchronousUnary<&Impl::AddFive>(
-          11u,
-          kNanopbMethodSerde<pw_rpc_test_TestRequest_fields,
-                             pw_rpc_test_TestResponse_fields>),
-      NanopbMethod::ServerStreaming<&Impl::StartStream>(
-          12u,
-          kNanopbMethodSerde<pw_rpc_test_TestRequest_fields,
-                             pw_rpc_test_TestResponse_fields>),
-      NanopbMethod::ClientStreaming<&Impl::ClientStream>(
-          13u,
-          kNanopbMethodSerde<pw_rpc_test_TestRequest_fields,
-                             pw_rpc_test_TestResponse_fields>),
-      NanopbMethod::BidirectionalStreaming<&Impl::BidirectionalStream>(
-          14u,
-          kNanopbMethodSerde<pw_rpc_test_TestRequest_fields,
-                             pw_rpc_test_TestResponse_fields>)};
-};
-
-class FakeService : public FakeServiceBase<FakeService> {
- public:
-  FakeService(uint32_t id) : FakeServiceBase(id) {}
-
-  Status DoNothing(const pw_rpc_test_Empty&, pw_rpc_test_Empty&) {
-    return Status::Unknown();
-  }
-
-  void AddFive(const pw_rpc_test_TestRequest& request,
-               NanopbUnaryResponder<pw_rpc_test_TestResponse>& responder) {
-    last_request = request;
-
-    if (fail_to_encode_async_unary_response) {
-      pw_rpc_test_TestResponse response = pw_rpc_test_TestResponse_init_default;
-      response.repeated_field.funcs.encode =
-          [](pb_ostream_t*, const pb_field_t*, void* const*) { return false; };
-      ASSERT_EQ(OkStatus(), responder.Finish(response, Status::NotFound()));
-    } else {
-      ASSERT_EQ(
-          OkStatus(),
-          responder.Finish({.value = static_cast<int32_t>(request.integer + 5)},
-                           Status::Unauthenticated()));
-    }
-  }
-
-  void StartStream(const pw_rpc_test_TestRequest& request,
-                   NanopbServerWriter<pw_rpc_test_TestResponse>& writer) {
-    last_request = request;
-    last_writer = std::move(writer);
-  }
-
-  void ClientStream(NanopbServerReader<pw_rpc_test_TestRequest,
-                                       pw_rpc_test_TestResponse>& reader) {
-    last_reader = std::move(reader);
-  }
-
-  void BidirectionalStream(
-      NanopbServerReaderWriter<pw_rpc_test_TestRequest,
-                               pw_rpc_test_TestResponse>& reader_writer) {
-    last_reader_writer = std::move(reader_writer);
-  }
-
-  bool fail_to_encode_async_unary_response = false;
-
-  pw_rpc_test_TestRequest last_request;
-  NanopbServerWriter<pw_rpc_test_TestResponse> last_writer;
-  NanopbServerReader<pw_rpc_test_TestRequest, pw_rpc_test_TestResponse>
-      last_reader;
-  NanopbServerReaderWriter<pw_rpc_test_TestRequest, pw_rpc_test_TestResponse>
-      last_reader_writer;
-};
-
-constexpr const NanopbMethod& kSyncUnary =
-    std::get<0>(FakeServiceBase<FakeService>::kMethods).nanopb_method();
-constexpr const NanopbMethod& kAsyncUnary =
-    std::get<1>(FakeServiceBase<FakeService>::kMethods).nanopb_method();
-constexpr const NanopbMethod& kServerStream =
-    std::get<2>(FakeServiceBase<FakeService>::kMethods).nanopb_method();
-constexpr const NanopbMethod& kClientStream =
-    std::get<3>(FakeServiceBase<FakeService>::kMethods).nanopb_method();
-constexpr const NanopbMethod& kBidirectionalStream =
-    std::get<4>(FakeServiceBase<FakeService>::kMethods).nanopb_method();
-
-TEST(NanopbMethod, AsyncUnaryRpc_SendsResponse) {
-  PW_ENCODE_PB(
-      pw_rpc_test_TestRequest, request, .integer = 123, .status_code = 0);
-
-  ServerContextForTest<FakeService> context(kAsyncUnary);
-  rpc_lock().lock();
-  kAsyncUnary.Invoke(context.get(), context.request(request));
-
-  const Packet& response = context.output().last_packet();
-  EXPECT_EQ(response.status(), Status::Unauthenticated());
-
-  // Field 1 (encoded as 1 << 3) with 128 as the value.
-  constexpr std::byte expected[]{
-      std::byte{0x08}, std::byte{0x80}, std::byte{0x01}};
-
-  EXPECT_EQ(sizeof(expected), response.payload().size());
-  EXPECT_EQ(0,
-            std::memcmp(expected, response.payload().data(), sizeof(expected)));
-
-  EXPECT_EQ(123, context.service().last_request.integer);
-}
-
-TEST(NanopbMethod, SyncUnaryRpc_InvalidPayload_SendsError) {
-  std::array<byte, 8> bad_payload{byte{0xFF}, byte{0xAA}, byte{0xDD}};
-
-  ServerContextForTest<FakeService> context(kSyncUnary);
-  rpc_lock().lock();
-  kSyncUnary.Invoke(context.get(), context.request(bad_payload));
-
-  const Packet& packet = context.output().last_packet();
-  EXPECT_EQ(PacketType::SERVER_ERROR, packet.type());
-  EXPECT_EQ(Status::DataLoss(), packet.status());
-  EXPECT_EQ(context.service_id(), packet.service_id());
-  EXPECT_EQ(kSyncUnary.id(), packet.method_id());
-}
-
-TEST(NanopbMethod, AsyncUnaryRpc_ResponseEncodingFails_SendsInternalError) {
-  constexpr int64_t value = 0x7FFFFFFF'FFFFFF00ll;
-  PW_ENCODE_PB(
-      pw_rpc_test_TestRequest, request, .integer = value, .status_code = 0);
-
-  ServerContextForTest<FakeService> context(kAsyncUnary);
-  context.service().fail_to_encode_async_unary_response = true;
-
-  rpc_lock().lock();
-  kAsyncUnary.Invoke(context.get(), context.request(request));
-
-  const Packet& packet = context.output().last_packet();
-  EXPECT_EQ(PacketType::SERVER_ERROR, packet.type());
-  EXPECT_EQ(Status::Internal(), packet.status());
-  EXPECT_EQ(context.service_id(), packet.service_id());
-  EXPECT_EQ(kAsyncUnary.id(), packet.method_id());
-
-  EXPECT_EQ(value, context.service().last_request.integer);
-}
-
-TEST(NanopbMethod, ServerStreamingRpc_SendsNothingWhenInitiallyCalled) {
-  PW_ENCODE_PB(
-      pw_rpc_test_TestRequest, request, .integer = 555, .status_code = 0);
-
-  ServerContextForTest<FakeService> context(kServerStream);
-
-  rpc_lock().lock();
-  kServerStream.Invoke(context.get(), context.request(request));
-
-  EXPECT_EQ(0u, context.output().total_packets());
-  EXPECT_EQ(555, context.service().last_request.integer);
-}
-
-TEST(NanopbMethod, ServerWriter_SendsResponse) {
-  ServerContextForTest<FakeService> context(kServerStream);
-
-  rpc_lock().lock();
-  kServerStream.Invoke(context.get(), context.request({}));
-
-  EXPECT_EQ(OkStatus(), context.service().last_writer.Write({.value = 100}));
-
-  PW_ENCODE_PB(pw_rpc_test_TestResponse, payload, .value = 100);
-  std::array<byte, 128> encoded_response = {};
-  auto encoded = context.server_stream(payload).Encode(encoded_response);
-  ASSERT_EQ(OkStatus(), encoded.status());
-
-  ConstByteSpan sent_payload = context.output().last_packet().payload();
-  EXPECT_TRUE(std::equal(payload.begin(),
-                         payload.end(),
-                         sent_payload.begin(),
-                         sent_payload.end()));
-}
-
-TEST(NanopbMethod, ServerWriter_WriteWhenClosed_ReturnsFailedPrecondition) {
-  ServerContextForTest<FakeService> context(kServerStream);
-
-  rpc_lock().lock();
-  kServerStream.Invoke(context.get(), context.request({}));
-
-  EXPECT_EQ(OkStatus(), context.service().last_writer.Finish());
-  EXPECT_TRUE(context.service()
-                  .last_writer.Write({.value = 100})
-                  .IsFailedPrecondition());
-}
-
-TEST(NanopbMethod, ServerWriter_WriteAfterMoved_ReturnsFailedPrecondition) {
-  ServerContextForTest<FakeService> context(kServerStream);
-
-  rpc_lock().lock();
-  kServerStream.Invoke(context.get(), context.request({}));
-  NanopbServerWriter<pw_rpc_test_TestResponse> new_writer =
-      std::move(context.service().last_writer);
-
-  EXPECT_EQ(OkStatus(), new_writer.Write({.value = 100}));
-
-  EXPECT_EQ(Status::FailedPrecondition(),
-            context.service().last_writer.Write({.value = 100}));
-  EXPECT_EQ(Status::FailedPrecondition(),
-            context.service().last_writer.Finish());
-
-  EXPECT_EQ(OkStatus(), new_writer.Finish());
-}
-
-TEST(NanopbMethod, ServerStreamingRpc_ResponseEncodingFails_InternalError) {
-  ServerContextForTest<FakeService> context(kServerStream);
-
-  rpc_lock().lock();
-  kServerStream.Invoke(context.get(), context.request({}));
-
-  EXPECT_EQ(OkStatus(), context.service().last_writer.Write({}));
-
-  pw_rpc_test_TestResponse response = pw_rpc_test_TestResponse_init_default;
-  response.repeated_field.funcs.encode =
-      [](pb_ostream_t*, const pb_field_t*, void* const*) { return false; };
-  EXPECT_EQ(Status::Internal(), context.service().last_writer.Write(response));
-}
-
-TEST(NanopbMethod, ServerReader_HandlesRequests) {
-  ServerContextForTest<FakeService> context(kClientStream);
-
-  rpc_lock().lock();
-  kClientStream.Invoke(context.get(), context.request({}));
-
-  pw_rpc_test_TestRequest request_struct{};
-  context.service().last_reader.set_on_next(
-      [&request_struct](const pw_rpc_test_TestRequest& req) {
-        request_struct = req;
-      });
-
-  PW_ENCODE_PB(
-      pw_rpc_test_TestRequest, request, .integer = 1 << 30, .status_code = 9);
-  std::array<byte, 128> encoded_request = {};
-  auto encoded = context.client_stream(request).Encode(encoded_request);
-  ASSERT_EQ(OkStatus(), encoded.status());
-  ASSERT_EQ(OkStatus(),
-            context.server().ProcessPacket(*encoded, context.output()));
-
-  EXPECT_EQ(request_struct.integer, 1 << 30);
-  EXPECT_EQ(request_struct.status_code, 9u);
-}
-
-TEST(NanopbMethod, ServerReaderWriter_WritesResponses) {
-  ServerContextForTest<FakeService> context(kBidirectionalStream);
-
-  rpc_lock().lock();
-  kBidirectionalStream.Invoke(context.get(), context.request({}));
-
-  EXPECT_EQ(OkStatus(),
-            context.service().last_reader_writer.Write({.value = 100}));
-
-  PW_ENCODE_PB(pw_rpc_test_TestResponse, payload, .value = 100);
-  std::array<byte, 128> encoded_response = {};
-  auto encoded = context.server_stream(payload).Encode(encoded_response);
-  ASSERT_EQ(OkStatus(), encoded.status());
-
-  ConstByteSpan sent_payload = context.output().last_packet().payload();
-  EXPECT_TRUE(std::equal(payload.begin(),
-                         payload.end(),
-                         sent_payload.begin(),
-                         sent_payload.end()));
-}
-
-TEST(NanopbMethod, ServerReaderWriter_HandlesRequests) {
-  ServerContextForTest<FakeService> context(kBidirectionalStream);
-
-  rpc_lock().lock();
-  kBidirectionalStream.Invoke(context.get(), context.request({}));
-
-  pw_rpc_test_TestRequest request_struct{};
-  context.service().last_reader_writer.set_on_next(
-      [&request_struct](const pw_rpc_test_TestRequest& req) {
-        request_struct = req;
-      });
-
-  PW_ENCODE_PB(
-      pw_rpc_test_TestRequest, request, .integer = 1 << 29, .status_code = 8);
-  std::array<byte, 128> encoded_request = {};
-  auto encoded = context.client_stream(request).Encode(encoded_request);
-  ASSERT_EQ(OkStatus(), encoded.status());
-  ASSERT_EQ(OkStatus(),
-            context.server().ProcessPacket(*encoded, context.output()));
-
-  EXPECT_EQ(request_struct.integer, 1 << 29);
-  EXPECT_EQ(request_struct.status_code, 8u);
-}
-
-}  // namespace
-}  // namespace pw::rpc::internal
-
-PW_MODIFY_DIAGNOSTICS_POP();
diff --git a/pw_rpc/nanopb/method_union_test.cc b/pw_rpc/nanopb/method_union_test.cc
deleted file mode 100644
index 6eacb59..0000000
--- a/pw_rpc/nanopb/method_union_test.cc
+++ /dev/null
@@ -1,169 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/nanopb/internal/method_union.h"
-
-#include <array>
-
-#include "gtest/gtest.h"
-#include "pw_rpc/internal/test_utils.h"
-#include "pw_rpc_nanopb_private/internal_test_utils.h"
-#include "pw_rpc_test_protos/test.pb.h"
-
-namespace pw::rpc::internal {
-namespace {
-
-using std::byte;
-
-template <typename Implementation>
-class FakeGeneratedService : public Service {
- public:
-  constexpr FakeGeneratedService(uint32_t id) : Service(id, kMethods) {}
-
-  static constexpr std::array<NanopbMethodUnion, 4> kMethods = {
-      GetNanopbOrRawMethodFor<&Implementation::DoNothing,
-                              MethodType::kUnary,
-                              pw_rpc_test_Empty,
-                              pw_rpc_test_Empty>(
-          10u,
-          kNanopbMethodSerde<pw_rpc_test_Empty_fields,
-                             pw_rpc_test_Empty_fields>),
-      GetNanopbOrRawMethodFor<&Implementation::RawStream,
-                              MethodType::kServerStreaming,
-                              pw_rpc_test_TestRequest,
-                              pw_rpc_test_TestResponse>(
-          11u,
-          kNanopbMethodSerde<pw_rpc_test_TestRequest_fields,
-                             pw_rpc_test_TestResponse_fields>),
-      GetNanopbOrRawMethodFor<&Implementation::AddFive,
-                              MethodType::kUnary,
-                              pw_rpc_test_TestRequest,
-                              pw_rpc_test_TestResponse>(
-          12u,
-          kNanopbMethodSerde<pw_rpc_test_TestRequest_fields,
-                             pw_rpc_test_TestResponse_fields>),
-      GetNanopbOrRawMethodFor<&Implementation::StartStream,
-                              MethodType::kServerStreaming,
-                              pw_rpc_test_TestRequest,
-                              pw_rpc_test_TestResponse>(
-          13u,
-          kNanopbMethodSerde<pw_rpc_test_TestRequest_fields,
-                             pw_rpc_test_TestResponse_fields>),
-  };
-};
-
-class FakeGeneratedServiceImpl
-    : public FakeGeneratedService<FakeGeneratedServiceImpl> {
- public:
-  FakeGeneratedServiceImpl(uint32_t id) : FakeGeneratedService(id) {}
-
-  Status AddFive(const pw_rpc_test_TestRequest& request,
-                 pw_rpc_test_TestResponse& response) {
-    last_request = request;
-    response.value = request.integer + 5;
-    return Status::Unauthenticated();
-  }
-
-  void DoNothing(ConstByteSpan, RawUnaryResponder& responder) {
-    ASSERT_EQ(OkStatus(), responder.Finish({}, Status::Unknown()));
-  }
-
-  void RawStream(ConstByteSpan, RawServerWriter& writer) {
-    last_raw_writer = std::move(writer);
-  }
-
-  void StartStream(const pw_rpc_test_TestRequest& request,
-                   NanopbServerWriter<pw_rpc_test_TestResponse>& writer) {
-    last_request = request;
-    last_writer = std::move(writer);
-  }
-
-  pw_rpc_test_TestRequest last_request;
-  NanopbServerWriter<pw_rpc_test_TestResponse> last_writer;
-  RawServerWriter last_raw_writer;
-};
-
-TEST(NanopbMethodUnion, Raw_CallsUnaryMethod) {
-  const Method& method =
-      std::get<0>(FakeGeneratedServiceImpl::kMethods).method();
-  ServerContextForTest<FakeGeneratedServiceImpl> context(method);
-  rpc_lock().lock();
-  method.Invoke(context.get(), context.request({}));
-
-  const Packet& response = context.output().last_packet();
-  EXPECT_EQ(response.status(), Status::Unknown());
-}
-
-TEST(NanopbMethodUnion, Raw_CallsServerStreamingMethod) {
-  PW_ENCODE_PB(
-      pw_rpc_test_TestRequest, request, .integer = 555, .status_code = 0);
-
-  const Method& method =
-      std::get<1>(FakeGeneratedServiceImpl::kMethods).method();
-  ServerContextForTest<FakeGeneratedServiceImpl> context(method);
-
-  rpc_lock().lock();
-  method.Invoke(context.get(), context.request(request));
-
-  EXPECT_TRUE(context.service().last_raw_writer.active());
-  EXPECT_EQ(OkStatus(), context.service().last_raw_writer.Finish());
-  EXPECT_EQ(context.output().last_packet().type(), PacketType::RESPONSE);
-}
-
-TEST(NanopbMethodUnion, Nanopb_CallsUnaryMethod) {
-  PW_ENCODE_PB(
-      pw_rpc_test_TestRequest, request, .integer = 123, .status_code = 3);
-
-  const Method& method =
-      std::get<2>(FakeGeneratedServiceImpl::kMethods).method();
-  ServerContextForTest<FakeGeneratedServiceImpl> context(method);
-  rpc_lock().lock();
-  method.Invoke(context.get(), context.request(request));
-
-  const Packet& response = context.output().last_packet();
-  EXPECT_EQ(response.status(), Status::Unauthenticated());
-
-  // Field 1 (encoded as 1 << 3) with 128 as the value.
-  constexpr std::byte expected[]{
-      std::byte{0x08}, std::byte{0x80}, std::byte{0x01}};
-
-  EXPECT_EQ(sizeof(expected), response.payload().size());
-  EXPECT_EQ(0,
-            std::memcmp(expected, response.payload().data(), sizeof(expected)));
-
-  EXPECT_EQ(123, context.service().last_request.integer);
-  EXPECT_EQ(3u, context.service().last_request.status_code);
-}
-
-TEST(NanopbMethodUnion, Nanopb_CallsServerStreamingMethod) {
-  PW_ENCODE_PB(
-      pw_rpc_test_TestRequest, request, .integer = 555, .status_code = 0);
-
-  const Method& method =
-      std::get<3>(FakeGeneratedServiceImpl::kMethods).method();
-  ServerContextForTest<FakeGeneratedServiceImpl> context(method);
-
-  rpc_lock().lock();
-  method.Invoke(context.get(), context.request(request));
-
-  EXPECT_EQ(555, context.service().last_request.integer);
-  EXPECT_TRUE(context.service().last_writer.active());
-
-  EXPECT_EQ(OkStatus(), context.service().last_writer.Finish());
-  EXPECT_EQ(context.output().last_packet().type(), PacketType::RESPONSE);
-}
-
-}  // namespace
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/nanopb/nanopb_client_call.cc b/pw_rpc/nanopb/nanopb_client_call.cc
new file mode 100644
index 0000000..8b4cc2d
--- /dev/null
+++ b/pw_rpc/nanopb/nanopb_client_call.cc
@@ -0,0 +1,33 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_rpc/nanopb_client_call.h"
+
+namespace pw::rpc {
+namespace internal {
+
+Status BaseNanopbClientCall::SendRequest(const void* request_struct) {
+  std::span<std::byte> buffer = AcquirePayloadBuffer();
+
+  StatusWithSize sws = serde_.EncodeRequest(buffer, request_struct);
+  if (!sws.ok()) {
+    ReleasePayloadBuffer({});
+    return sws.status();
+  }
+
+  return ReleasePayloadBuffer(buffer.first(sws.size()));
+}
+
+}  // namespace internal
+}  // namespace pw::rpc
diff --git a/pw_rpc/nanopb/nanopb_client_call_test.cc b/pw_rpc/nanopb/nanopb_client_call_test.cc
new file mode 100644
index 0000000..171ae28
--- /dev/null
+++ b/pw_rpc/nanopb/nanopb_client_call_test.cc
@@ -0,0 +1,248 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_rpc/nanopb_client_call.h"
+
+#include "gtest/gtest.h"
+#include "pw_rpc_nanopb_private/internal_test_utils.h"
+#include "pw_rpc_private/internal_test_utils.h"
+#include "pw_rpc_test_protos/test.pb.h"
+
+namespace pw::rpc {
+namespace {
+
+constexpr uint32_t kServiceId = 16;
+constexpr uint32_t kUnaryMethodId = 111;
+constexpr uint32_t kServerStreamingMethodId = 112;
+
+class FakeGeneratedServiceClient {
+ public:
+  static NanopbClientCall<UnaryResponseHandler<pw_rpc_test_TestResponse>>
+  TestRpc(Channel& channel,
+          const pw_rpc_test_TestRequest& request,
+          UnaryResponseHandler<pw_rpc_test_TestResponse>& callback) {
+    auto call = NanopbClientCall(&channel,
+                                 kServiceId,
+                                 kUnaryMethodId,
+                                 callback,
+                                 pw_rpc_test_TestRequest_fields,
+                                 pw_rpc_test_TestResponse_fields);
+    call.SendRequest(&request);
+    return call;
+  }
+
+  static NanopbClientCall<
+      ServerStreamingResponseHandler<pw_rpc_test_TestStreamResponse>>
+  TestStreamRpc(Channel& channel,
+                const pw_rpc_test_TestRequest& request,
+                ServerStreamingResponseHandler<pw_rpc_test_TestStreamResponse>&
+                    callback) {
+    auto call = NanopbClientCall(&channel,
+                                 kServiceId,
+                                 kServerStreamingMethodId,
+                                 callback,
+                                 pw_rpc_test_TestRequest_fields,
+                                 pw_rpc_test_TestStreamResponse_fields);
+    call.SendRequest(&request);
+    return call;
+  }
+};
+
+using internal::TestServerStreamingResponseHandler;
+using internal::TestUnaryResponseHandler;
+
+TEST(NanopbClientCall, Unary_SendsRequestPacket) {
+  ClientContextForTest context;
+  TestUnaryResponseHandler<pw_rpc_test_TestResponse> handler;
+
+  auto call = FakeGeneratedServiceClient::TestRpc(
+      context.channel(), {.integer = 123, .status_code = 0}, handler);
+
+  EXPECT_EQ(context.output().packet_count(), 1u);
+  auto packet = context.output().sent_packet();
+  EXPECT_EQ(packet.channel_id(), context.channel().id());
+  EXPECT_EQ(packet.service_id(), kServiceId);
+  EXPECT_EQ(packet.method_id(), kUnaryMethodId);
+
+  PW_DECODE_PB(pw_rpc_test_TestRequest, sent_proto, packet.payload());
+  EXPECT_EQ(sent_proto.integer, 123);
+}
+
+TEST(NanopbClientCall, Unary_InvokesCallbackOnValidResponse) {
+  ClientContextForTest context;
+  TestUnaryResponseHandler<pw_rpc_test_TestResponse> handler;
+
+  auto call = FakeGeneratedServiceClient::TestRpc(
+      context.channel(), {.integer = 123, .status_code = 0}, handler);
+
+  PW_ENCODE_PB(pw_rpc_test_TestResponse, response, .value = 42);
+  context.SendResponse(OkStatus(), response);
+
+  ASSERT_EQ(handler.responses_received(), 1u);
+  EXPECT_EQ(handler.last_status(), OkStatus());
+  EXPECT_EQ(handler.last_response().value, 42);
+}
+
+TEST(NanopbClientCall, Unary_InvokesErrorCallbackOnInvalidResponse) {
+  ClientContextForTest context;
+  TestUnaryResponseHandler<pw_rpc_test_TestResponse> handler;
+
+  auto call = FakeGeneratedServiceClient::TestRpc(
+      context.channel(), {.integer = 123, .status_code = 0}, handler);
+
+  constexpr std::byte bad_payload[]{
+      std::byte{0xab}, std::byte{0xcd}, std::byte{0xef}};
+  context.SendResponse(OkStatus(), bad_payload);
+
+  EXPECT_EQ(handler.responses_received(), 0u);
+  EXPECT_EQ(handler.rpc_error(), Status::DataLoss());
+}
+
+TEST(NanopbClientCall, Unary_InvokesErrorCallbackOnServerError) {
+  ClientContextForTest context;
+  TestUnaryResponseHandler<pw_rpc_test_TestResponse> handler;
+
+  auto call = FakeGeneratedServiceClient::TestRpc(
+      context.channel(), {.integer = 123, .status_code = 0}, handler);
+
+  context.SendPacket(internal::PacketType::SERVER_ERROR, Status::NotFound());
+
+  EXPECT_EQ(handler.responses_received(), 0u);
+  EXPECT_EQ(handler.rpc_error(), Status::NotFound());
+}
+
+TEST(NanopbClientCall, Unary_OnlyReceivesOneResponse) {
+  ClientContextForTest context;
+  TestUnaryResponseHandler<pw_rpc_test_TestResponse> handler;
+
+  auto call = FakeGeneratedServiceClient::TestRpc(
+      context.channel(), {.integer = 123, .status_code = 0}, handler);
+
+  PW_ENCODE_PB(pw_rpc_test_TestResponse, r1, .value = 42);
+  context.SendResponse(Status::Unimplemented(), r1);
+  PW_ENCODE_PB(pw_rpc_test_TestResponse, r2, .value = 44);
+  context.SendResponse(Status::OutOfRange(), r2);
+  PW_ENCODE_PB(pw_rpc_test_TestResponse, r3, .value = 46);
+  context.SendResponse(Status::Internal(), r3);
+
+  EXPECT_EQ(handler.responses_received(), 1u);
+  EXPECT_EQ(handler.last_status(), Status::Unimplemented());
+  EXPECT_EQ(handler.last_response().value, 42);
+}
+
+TEST(NanopbClientCall, ServerStreaming_SendsRequestPacket) {
+  ClientContextForTest<128, 128, 99, kServiceId, kServerStreamingMethodId>
+      context;
+  TestServerStreamingResponseHandler<pw_rpc_test_TestStreamResponse> handler;
+
+  auto call = FakeGeneratedServiceClient::TestStreamRpc(
+      context.channel(), {.integer = 71, .status_code = 0}, handler);
+
+  EXPECT_EQ(context.output().packet_count(), 1u);
+  auto packet = context.output().sent_packet();
+  EXPECT_EQ(packet.channel_id(), context.channel().id());
+  EXPECT_EQ(packet.service_id(), kServiceId);
+  EXPECT_EQ(packet.method_id(), kServerStreamingMethodId);
+
+  PW_DECODE_PB(pw_rpc_test_TestRequest, sent_proto, packet.payload());
+  EXPECT_EQ(sent_proto.integer, 71);
+}
+
+TEST(NanopbClientCall, ServerStreaming_InvokesCallbackOnValidResponse) {
+  ClientContextForTest<128, 128, 99, kServiceId, kServerStreamingMethodId>
+      context;
+  TestServerStreamingResponseHandler<pw_rpc_test_TestStreamResponse> handler;
+
+  auto call = FakeGeneratedServiceClient::TestStreamRpc(
+      context.channel(), {.integer = 71, .status_code = 0}, handler);
+
+  PW_ENCODE_PB(pw_rpc_test_TestStreamResponse, r1, .chunk = {}, .number = 11u);
+  context.SendResponse(OkStatus(), r1);
+  EXPECT_TRUE(handler.active());
+  EXPECT_EQ(handler.responses_received(), 1u);
+  EXPECT_EQ(handler.last_response().number, 11u);
+
+  PW_ENCODE_PB(pw_rpc_test_TestStreamResponse, r2, .chunk = {}, .number = 22u);
+  context.SendResponse(OkStatus(), r2);
+  EXPECT_TRUE(handler.active());
+  EXPECT_EQ(handler.responses_received(), 2u);
+  EXPECT_EQ(handler.last_response().number, 22u);
+
+  PW_ENCODE_PB(pw_rpc_test_TestStreamResponse, r3, .chunk = {}, .number = 33u);
+  context.SendResponse(OkStatus(), r3);
+  EXPECT_TRUE(handler.active());
+  EXPECT_EQ(handler.responses_received(), 3u);
+  EXPECT_EQ(handler.last_response().number, 33u);
+}
+
+TEST(NanopbClientCall, ServerStreaming_ClosesOnFinish) {
+  ClientContextForTest<128, 128, 99, kServiceId, kServerStreamingMethodId>
+      context;
+  TestServerStreamingResponseHandler<pw_rpc_test_TestStreamResponse> handler;
+
+  auto call = FakeGeneratedServiceClient::TestStreamRpc(
+      context.channel(), {.integer = 71, .status_code = 0}, handler);
+
+  PW_ENCODE_PB(pw_rpc_test_TestStreamResponse, r1, .chunk = {}, .number = 11u);
+  context.SendResponse(OkStatus(), r1);
+  EXPECT_TRUE(handler.active());
+
+  PW_ENCODE_PB(pw_rpc_test_TestStreamResponse, r2, .chunk = {}, .number = 22u);
+  context.SendResponse(OkStatus(), r2);
+  EXPECT_TRUE(handler.active());
+
+  // Close the stream.
+  context.SendPacket(internal::PacketType::SERVER_STREAM_END,
+                     Status::NotFound());
+
+  PW_ENCODE_PB(pw_rpc_test_TestStreamResponse, r3, .chunk = {}, .number = 33u);
+  context.SendResponse(OkStatus(), r3);
+  EXPECT_FALSE(handler.active());
+
+  EXPECT_EQ(handler.responses_received(), 2u);
+}
+
+TEST(NanopbClientCall, ServerStreaming_InvokesErrorCallbackOnInvalidResponses) {
+  ClientContextForTest<128, 128, 99, kServiceId, kServerStreamingMethodId>
+      context;
+  TestServerStreamingResponseHandler<pw_rpc_test_TestStreamResponse> handler;
+
+  auto call = FakeGeneratedServiceClient::TestStreamRpc(
+      context.channel(), {.integer = 71, .status_code = 0}, handler);
+
+  PW_ENCODE_PB(pw_rpc_test_TestStreamResponse, r1, .chunk = {}, .number = 11u);
+  context.SendResponse(OkStatus(), r1);
+  EXPECT_TRUE(handler.active());
+  EXPECT_EQ(handler.responses_received(), 1u);
+  EXPECT_EQ(handler.last_response().number, 11u);
+
+  constexpr std::byte bad_payload[]{
+      std::byte{0xab}, std::byte{0xcd}, std::byte{0xef}};
+  context.SendResponse(OkStatus(), bad_payload);
+  EXPECT_EQ(handler.responses_received(), 1u);
+  EXPECT_EQ(handler.rpc_error(), Status::DataLoss());
+
+  PW_ENCODE_PB(pw_rpc_test_TestStreamResponse, r2, .chunk = {}, .number = 22u);
+  context.SendResponse(OkStatus(), r2);
+  EXPECT_TRUE(handler.active());
+  EXPECT_EQ(handler.responses_received(), 2u);
+  EXPECT_EQ(handler.last_response().number, 22u);
+
+  context.SendPacket(internal::PacketType::SERVER_ERROR, Status::NotFound());
+  EXPECT_EQ(handler.responses_received(), 2u);
+  EXPECT_EQ(handler.rpc_error(), Status::NotFound());
+}
+
+}  // namespace
+}  // namespace pw::rpc
diff --git a/pw_rpc/nanopb/nanopb_common.cc b/pw_rpc/nanopb/nanopb_common.cc
new file mode 100644
index 0000000..4f90e1b
--- /dev/null
+++ b/pw_rpc/nanopb/nanopb_common.cc
@@ -0,0 +1,55 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_rpc/internal/nanopb_common.h"
+
+#include "pb_decode.h"
+#include "pb_encode.h"
+
+namespace pw::rpc::internal {
+
+// Nanopb 3 uses pb_field_s and Nanopb 4 uses pb_msgdesc_s for fields. The
+// Nanopb version macro is difficult to use, so deduce the correct type from the
+// pb_decode function.
+template <typename DecodeFunction>
+struct NanopbTraits;
+
+template <typename FieldsType>
+struct NanopbTraits<bool(pb_istream_t*, FieldsType, void*)> {
+  using Fields = FieldsType;
+};
+
+using Fields = typename NanopbTraits<decltype(pb_decode)>::Fields;
+
+StatusWithSize NanopbMethodSerde::Encode(NanopbMessageDescriptor fields,
+                                         ByteSpan buffer,
+                                         const void* proto_struct) const {
+  auto output = pb_ostream_from_buffer(
+      reinterpret_cast<pb_byte_t*>(buffer.data()), buffer.size());
+  if (!pb_encode(&output, static_cast<Fields>(fields), proto_struct)) {
+    return StatusWithSize::Internal();
+  }
+
+  return StatusWithSize(output.bytes_written);
+}
+
+bool NanopbMethodSerde::Decode(NanopbMessageDescriptor fields,
+                               void* proto_struct,
+                               ConstByteSpan buffer) const {
+  auto input = pb_istream_from_buffer(
+      reinterpret_cast<const pb_byte_t*>(buffer.data()), buffer.size());
+  return pb_decode(&input, static_cast<Fields>(fields), proto_struct);
+}
+
+}  // namespace pw::rpc::internal
diff --git a/pw_rpc/nanopb/nanopb_method.cc b/pw_rpc/nanopb/nanopb_method.cc
new file mode 100644
index 0000000..c86837d
--- /dev/null
+++ b/pw_rpc/nanopb/nanopb_method.cc
@@ -0,0 +1,97 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_rpc/internal/nanopb_method.h"
+
+#include "pb_decode.h"
+#include "pb_encode.h"
+#include "pw_log/log.h"
+#include "pw_rpc/internal/packet.h"
+
+namespace pw::rpc::internal {
+
+using std::byte;
+
+void NanopbMethod::CallUnary(ServerCall& call,
+                             const Packet& request,
+                             void* request_struct,
+                             void* response_struct) const {
+  if (!DecodeRequest(call.channel(), request, request_struct)) {
+    return;
+  }
+
+  const Status status = function_.unary(call, request_struct, response_struct);
+  SendResponse(call.channel(), request, response_struct, status);
+}
+
+void NanopbMethod::CallServerStreaming(ServerCall& call,
+                                       const Packet& request,
+                                       void* request_struct) const {
+  if (!DecodeRequest(call.channel(), request, request_struct)) {
+    return;
+  }
+
+  internal::BaseServerWriter server_writer(call);
+  function_.server_streaming(call, request_struct, server_writer);
+}
+
+bool NanopbMethod::DecodeRequest(Channel& channel,
+                                 const Packet& request,
+                                 void* proto_struct) const {
+  if (serde_.DecodeRequest(proto_struct, request.payload())) {
+    return true;
+  }
+
+  PW_LOG_WARN("Nanopb failed to decode request payload from channel %u",
+              unsigned(channel.id()));
+  channel.Send(Packet::ServerError(request, Status::DataLoss()));
+  return false;
+}
+
+void NanopbMethod::SendResponse(Channel& channel,
+                                const Packet& request,
+                                const void* response_struct,
+                                Status status) const {
+  Channel::OutputBuffer response_buffer = channel.AcquireBuffer();
+  std::span payload_buffer = response_buffer.payload(request);
+
+  StatusWithSize encoded = EncodeResponse(response_struct, payload_buffer);
+
+  if (encoded.ok()) {
+    Packet response = Packet::Response(request);
+
+    response.set_payload(payload_buffer.first(encoded.size()));
+    response.set_status(status);
+    pw::Status send_status = channel.Send(response_buffer, response);
+    if (send_status.ok()) {
+      return;
+    }
+
+    PW_LOG_WARN("Failed to send response packet for channel %u, status %u",
+                unsigned(channel.id()),
+                send_status.code());
+
+    // Re-acquire the buffer to encode an error packet.
+    response_buffer = channel.AcquireBuffer();
+  } else {
+    PW_LOG_WARN(
+        "Nanopb failed to encode response packet for channel %u, status %u",
+        unsigned(channel.id()),
+        encoded.status().code());
+  }
+  channel.Send(response_buffer,
+               Packet::ServerError(request, Status::Internal()));
+}
+
+}  // namespace pw::rpc::internal
diff --git a/pw_rpc/nanopb/nanopb_method_test.cc b/pw_rpc/nanopb/nanopb_method_test.cc
new file mode 100644
index 0000000..d38c461
--- /dev/null
+++ b/pw_rpc/nanopb/nanopb_method_test.cc
@@ -0,0 +1,312 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_rpc/internal/nanopb_method.h"
+
+#include <array>
+
+#include "gtest/gtest.h"
+#include "pw_rpc/internal/nanopb_method_union.h"
+#include "pw_rpc/server_context.h"
+#include "pw_rpc/service.h"
+#include "pw_rpc_nanopb_private/internal_test_utils.h"
+#include "pw_rpc_private/internal_test_utils.h"
+#include "pw_rpc_private/method_impl_tester.h"
+#include "pw_rpc_test_protos/test.pb.h"
+
+namespace pw::rpc::internal {
+namespace {
+
+using std::byte;
+
+struct FakePb {};
+
+// Create a fake service for use with the MethodImplTester.
+class TestNanopbService final : public Service {
+ public:
+  Status Unary(ServerContext&, const FakePb&, FakePb&) { return Status(); }
+
+  static Status StaticUnary(ServerContext&, const FakePb&, FakePb&) {
+    return Status();
+  }
+
+  void ServerStreaming(ServerContext&, const FakePb&, ServerWriter<FakePb>&) {}
+
+  static void StaticServerStreaming(ServerContext&,
+                                    const FakePb&,
+                                    ServerWriter<FakePb>&) {}
+
+  Status UnaryWrongArg(ServerContext&, FakePb&, FakePb&) { return Status(); }
+
+  static void StaticUnaryVoidReturn(ServerContext&, const FakePb&, FakePb&) {}
+
+  int ServerStreamingBadReturn(ServerContext&,
+                               const FakePb&,
+                               ServerWriter<FakePb>&) {
+    return 5;
+  }
+
+  static void StaticServerStreamingMissingArg(const FakePb&,
+                                              ServerWriter<FakePb>&) {}
+};
+
+// Test that the matches() function matches valid signatures.
+static_assert(NanopbMethod::template matches<&TestNanopbService::Unary,
+                                             FakePb,
+                                             FakePb>());
+static_assert(
+    NanopbMethod::template matches<&TestNanopbService::ServerStreaming,
+                                   FakePb,
+                                   FakePb>());
+static_assert(NanopbMethod::template matches<&TestNanopbService::StaticUnary,
+                                             FakePb,
+                                             FakePb>());
+static_assert(
+    NanopbMethod::template matches<&TestNanopbService::StaticServerStreaming,
+                                   FakePb,
+                                   FakePb>());
+
+// Test that the matches() function does not match the wrong method type.
+static_assert(!NanopbMethod::template matches<&TestNanopbService::UnaryWrongArg,
+                                              FakePb,
+                                              FakePb>());
+static_assert(
+    !NanopbMethod::template matches<&TestNanopbService::StaticUnaryVoidReturn,
+                                    FakePb,
+                                    FakePb>());
+static_assert(!NanopbMethod::template matches<
+              &TestNanopbService::ServerStreamingBadReturn,
+              FakePb,
+              FakePb>());
+static_assert(!NanopbMethod::template matches<
+              &TestNanopbService::StaticServerStreamingMissingArg,
+              FakePb,
+              FakePb>());
+
+struct WrongPb;
+
+// Test matches() rejects incorrect request/response types.
+static_assert(!NanopbMethod::template matches<&TestNanopbService::Unary,
+                                              WrongPb,
+                                              FakePb>());
+static_assert(!NanopbMethod::template matches<&TestNanopbService::Unary,
+                                              FakePb,
+                                              WrongPb>());
+static_assert(!NanopbMethod::template matches<&TestNanopbService::Unary,
+                                              WrongPb,
+                                              WrongPb>());
+static_assert(
+    !NanopbMethod::template matches<&TestNanopbService::ServerStreaming,
+                                    WrongPb,
+                                    FakePb>());
+
+static_assert(!NanopbMethod::template matches<&TestNanopbService::StaticUnary,
+                                              FakePb,
+                                              WrongPb>());
+static_assert(
+    !NanopbMethod::template matches<&TestNanopbService::StaticServerStreaming,
+                                    FakePb,
+                                    WrongPb>());
+
+TEST(MethodImplTester, NanopbMethod) {
+  constexpr MethodImplTester<NanopbMethod, TestNanopbService, nullptr, nullptr>
+      method_tester;
+  EXPECT_TRUE(method_tester.MethodImplIsValid());
+}
+
+pw_rpc_test_TestRequest last_request;
+ServerWriter<pw_rpc_test_TestResponse> last_writer;
+
+Status AddFive(ServerContext&,
+               const pw_rpc_test_TestRequest& request,
+               pw_rpc_test_TestResponse& response) {
+  last_request = request;
+  response.value = request.integer + 5;
+  return Status::Unauthenticated();
+}
+
+Status DoNothing(ServerContext&, const pw_rpc_test_Empty&, pw_rpc_test_Empty&) {
+  return Status::Unknown();
+}
+
+void StartStream(ServerContext&,
+                 const pw_rpc_test_TestRequest& request,
+                 ServerWriter<pw_rpc_test_TestResponse>& writer) {
+  last_request = request;
+  last_writer = std::move(writer);
+}
+
+class FakeService : public Service {
+ public:
+  FakeService(uint32_t id) : Service(id, kMethods) {}
+
+  static constexpr std::array<NanopbMethodUnion, 3> kMethods = {
+      NanopbMethod::Unary<DoNothing>(
+          10u, pw_rpc_test_Empty_fields, pw_rpc_test_Empty_fields),
+      NanopbMethod::Unary<AddFive>(
+          11u, pw_rpc_test_TestRequest_fields, pw_rpc_test_TestResponse_fields),
+      NanopbMethod::ServerStreaming<StartStream>(
+          12u, pw_rpc_test_TestRequest_fields, pw_rpc_test_TestResponse_fields),
+  };
+};
+
+TEST(NanopbMethod, UnaryRpc_SendsResponse) {
+  PW_ENCODE_PB(
+      pw_rpc_test_TestRequest, request, .integer = 123, .status_code = 0);
+
+  const NanopbMethod& method =
+      std::get<1>(FakeService::kMethods).nanopb_method();
+  ServerContextForTest<FakeService> context(method);
+  method.Invoke(context.get(), context.packet(request));
+
+  const Packet& response = context.output().sent_packet();
+  EXPECT_EQ(response.status(), Status::Unauthenticated());
+
+  // Field 1 (encoded as 1 << 3) with 128 as the value.
+  constexpr std::byte expected[]{
+      std::byte{0x08}, std::byte{0x80}, std::byte{0x01}};
+
+  EXPECT_EQ(sizeof(expected), response.payload().size());
+  EXPECT_EQ(0,
+            std::memcmp(expected, response.payload().data(), sizeof(expected)));
+
+  EXPECT_EQ(123, last_request.integer);
+}
+
+TEST(NanopbMethod, UnaryRpc_InvalidPayload_SendsError) {
+  std::array<byte, 8> bad_payload{byte{0xFF}, byte{0xAA}, byte{0xDD}};
+
+  const NanopbMethod& method =
+      std::get<0>(FakeService::kMethods).nanopb_method();
+  ServerContextForTest<FakeService> context(method);
+  method.Invoke(context.get(), context.packet(bad_payload));
+
+  const Packet& packet = context.output().sent_packet();
+  EXPECT_EQ(PacketType::SERVER_ERROR, packet.type());
+  EXPECT_EQ(Status::DataLoss(), packet.status());
+  EXPECT_EQ(context.service_id(), packet.service_id());
+  EXPECT_EQ(method.id(), packet.method_id());
+}
+
+TEST(NanopbMethod, UnaryRpc_BufferTooSmallForResponse_SendsInternalError) {
+  constexpr int64_t value = 0x7FFFFFFF'FFFFFF00ll;
+  PW_ENCODE_PB(
+      pw_rpc_test_TestRequest, request, .integer = value, .status_code = 0);
+
+  const NanopbMethod& method =
+      std::get<1>(FakeService::kMethods).nanopb_method();
+  // Output buffer is too small for the response, but can fit an error packet.
+  ServerContextForTest<FakeService, 22> context(method);
+  ASSERT_LT(context.output().buffer_size(),
+            context.packet(request).MinEncodedSizeBytes() + request.size() + 1);
+
+  method.Invoke(context.get(), context.packet(request));
+
+  const Packet& packet = context.output().sent_packet();
+  EXPECT_EQ(PacketType::SERVER_ERROR, packet.type());
+  EXPECT_EQ(Status::Internal(), packet.status());
+  EXPECT_EQ(context.service_id(), packet.service_id());
+  EXPECT_EQ(method.id(), packet.method_id());
+
+  EXPECT_EQ(value, last_request.integer);
+}
+
+TEST(NanopbMethod, ServerStreamingRpc_SendsNothingWhenInitiallyCalled) {
+  PW_ENCODE_PB(
+      pw_rpc_test_TestRequest, request, .integer = 555, .status_code = 0);
+
+  const NanopbMethod& method =
+      std::get<2>(FakeService::kMethods).nanopb_method();
+  ServerContextForTest<FakeService> context(method);
+
+  method.Invoke(context.get(), context.packet(request));
+
+  EXPECT_EQ(0u, context.output().packet_count());
+  EXPECT_EQ(555, last_request.integer);
+}
+
+TEST(NanopbMethod, ServerWriter_SendsResponse) {
+  const NanopbMethod& method =
+      std::get<2>(FakeService::kMethods).nanopb_method();
+  ServerContextForTest<FakeService> context(method);
+
+  method.Invoke(context.get(), context.packet({}));
+
+  EXPECT_EQ(OkStatus(), last_writer.Write({.value = 100}));
+
+  PW_ENCODE_PB(pw_rpc_test_TestResponse, payload, .value = 100);
+  std::array<byte, 128> encoded_response = {};
+  auto encoded = context.packet(payload).Encode(encoded_response);
+  ASSERT_EQ(OkStatus(), encoded.status());
+
+  ASSERT_EQ(encoded.value().size(), context.output().sent_data().size());
+  EXPECT_EQ(0,
+            std::memcmp(encoded.value().data(),
+                        context.output().sent_data().data(),
+                        encoded.value().size()));
+}
+
+TEST(NanopbMethod, ServerWriter_WriteWhenClosed_ReturnsFailedPrecondition) {
+  const NanopbMethod& method =
+      std::get<2>(FakeService::kMethods).nanopb_method();
+  ServerContextForTest<FakeService> context(method);
+
+  method.Invoke(context.get(), context.packet({}));
+
+  EXPECT_EQ(OkStatus(), last_writer.Finish());
+  EXPECT_TRUE(last_writer.Write({.value = 100}).IsFailedPrecondition());
+}
+
+TEST(NanopbMethod, ServerWriter_WriteAfterMoved_ReturnsFailedPrecondition) {
+  const NanopbMethod& method =
+      std::get<2>(FakeService::kMethods).nanopb_method();
+  ServerContextForTest<FakeService> context(method);
+
+  method.Invoke(context.get(), context.packet({}));
+  ServerWriter<pw_rpc_test_TestResponse> new_writer = std::move(last_writer);
+
+  EXPECT_EQ(OkStatus(), new_writer.Write({.value = 100}));
+
+  EXPECT_EQ(Status::FailedPrecondition(), last_writer.Write({.value = 100}));
+  EXPECT_EQ(Status::FailedPrecondition(), last_writer.Finish());
+
+  EXPECT_EQ(OkStatus(), new_writer.Finish());
+}
+
+TEST(NanopbMethod,
+     ServerStreamingRpc_ServerWriterBufferTooSmall_InternalError) {
+  const NanopbMethod& method =
+      std::get<2>(FakeService::kMethods).nanopb_method();
+
+  constexpr size_t kNoPayloadPacketSize = 2 /* type */ + 2 /* channel */ +
+                                          5 /* service */ + 5 /* method */ +
+                                          2 /* payload */ + 2 /* status */;
+
+  // Make the buffer barely fit a packet with no payload.
+  ServerContextForTest<FakeService, kNoPayloadPacketSize> context(method);
+
+  // Verify that the encoded size of a packet with an empty payload is correct.
+  std::array<byte, 128> encoded_response = {};
+  auto encoded = context.packet({}).Encode(encoded_response);
+  ASSERT_EQ(OkStatus(), encoded.status());
+  ASSERT_EQ(kNoPayloadPacketSize, encoded.value().size());
+
+  method.Invoke(context.get(), context.packet({}));
+
+  EXPECT_EQ(OkStatus(), last_writer.Write({}));  // Barely fits
+  EXPECT_EQ(Status::Internal(), last_writer.Write({.value = 1}));  // Too big
+}
+
+}  // namespace
+}  // namespace pw::rpc::internal
diff --git a/pw_rpc/nanopb/nanopb_method_union_test.cc b/pw_rpc/nanopb/nanopb_method_union_test.cc
new file mode 100644
index 0000000..d1bfd7f
--- /dev/null
+++ b/pw_rpc/nanopb/nanopb_method_union_test.cc
@@ -0,0 +1,160 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_rpc/internal/nanopb_method_union.h"
+
+#include <array>
+
+#include "gtest/gtest.h"
+#include "pw_rpc_nanopb_private/internal_test_utils.h"
+#include "pw_rpc_private/internal_test_utils.h"
+#include "pw_rpc_test_protos/test.pb.h"
+
+namespace pw::rpc::internal {
+namespace {
+
+using std::byte;
+
+template <typename Implementation>
+class FakeGeneratedService : public Service {
+ public:
+  constexpr FakeGeneratedService(uint32_t id) : Service(id, kMethods) {}
+
+  static constexpr std::array<NanopbMethodUnion, 4> kMethods = {
+      GetNanopbOrRawMethodFor<&Implementation::DoNothing,
+                              MethodType::kUnary,
+                              pw_rpc_test_Empty,
+                              pw_rpc_test_Empty>(
+          10u, pw_rpc_test_Empty_fields, pw_rpc_test_Empty_fields),
+      GetNanopbOrRawMethodFor<&Implementation::RawStream,
+                              MethodType::kServerStreaming,
+                              pw_rpc_test_TestRequest,
+                              pw_rpc_test_TestResponse>(
+          11u, pw_rpc_test_TestRequest_fields, pw_rpc_test_TestResponse_fields),
+      GetNanopbOrRawMethodFor<&Implementation::AddFive,
+                              MethodType::kUnary,
+                              pw_rpc_test_TestRequest,
+                              pw_rpc_test_TestResponse>(
+          12u, pw_rpc_test_TestRequest_fields, pw_rpc_test_TestResponse_fields),
+      GetNanopbOrRawMethodFor<&Implementation::StartStream,
+                              MethodType::kServerStreaming,
+                              pw_rpc_test_TestRequest,
+                              pw_rpc_test_TestResponse>(
+          13u, pw_rpc_test_TestRequest_fields, pw_rpc_test_TestResponse_fields),
+  };
+};
+
+pw_rpc_test_TestRequest last_request;
+ServerWriter<pw_rpc_test_TestResponse> last_writer;
+RawServerWriter last_raw_writer;
+
+class FakeGeneratedServiceImpl
+    : public FakeGeneratedService<FakeGeneratedServiceImpl> {
+ public:
+  FakeGeneratedServiceImpl(uint32_t id) : FakeGeneratedService(id) {}
+
+  Status AddFive(ServerContext&,
+                 const pw_rpc_test_TestRequest& request,
+                 pw_rpc_test_TestResponse& response) {
+    last_request = request;
+    response.value = request.integer + 5;
+    return Status::Unauthenticated();
+  }
+
+  StatusWithSize DoNothing(ServerContext&, ConstByteSpan, ByteSpan) {
+    return StatusWithSize::Unknown();
+  }
+
+  void RawStream(ServerContext&, ConstByteSpan, RawServerWriter& writer) {
+    last_raw_writer = std::move(writer);
+  }
+
+  void StartStream(ServerContext&,
+                   const pw_rpc_test_TestRequest& request,
+                   ServerWriter<pw_rpc_test_TestResponse>& writer) {
+    last_request = request;
+    last_writer = std::move(writer);
+  }
+};
+
+TEST(NanopbMethodUnion, Raw_CallsUnaryMethod) {
+  const Method& method =
+      std::get<0>(FakeGeneratedServiceImpl::kMethods).method();
+  ServerContextForTest<FakeGeneratedServiceImpl> context(method);
+  method.Invoke(context.get(), context.packet({}));
+
+  const Packet& response = context.output().sent_packet();
+  EXPECT_EQ(response.status(), Status::Unknown());
+}
+
+TEST(NanopbMethodUnion, Raw_CallsServerStreamingMethod) {
+  PW_ENCODE_PB(
+      pw_rpc_test_TestRequest, request, .integer = 555, .status_code = 0);
+
+  const Method& method =
+      std::get<1>(FakeGeneratedServiceImpl::kMethods).method();
+  ServerContextForTest<FakeGeneratedServiceImpl> context(method);
+
+  method.Invoke(context.get(), context.packet(request));
+
+  EXPECT_TRUE(last_raw_writer.open());
+  EXPECT_EQ(OkStatus(), last_raw_writer.Finish());
+  EXPECT_EQ(context.output().sent_packet().type(),
+            PacketType::SERVER_STREAM_END);
+}
+
+TEST(NanopbMethodUnion, Nanopb_CallsUnaryMethod) {
+  PW_ENCODE_PB(
+      pw_rpc_test_TestRequest, request, .integer = 123, .status_code = 3);
+
+  const Method& method =
+      std::get<2>(FakeGeneratedServiceImpl::kMethods).method();
+  ServerContextForTest<FakeGeneratedServiceImpl> context(method);
+  method.Invoke(context.get(), context.packet(request));
+
+  const Packet& response = context.output().sent_packet();
+  EXPECT_EQ(response.status(), Status::Unauthenticated());
+
+  // Field 1 (encoded as 1 << 3) with 128 as the value.
+  constexpr std::byte expected[]{
+      std::byte{0x08}, std::byte{0x80}, std::byte{0x01}};
+
+  EXPECT_EQ(sizeof(expected), response.payload().size());
+  EXPECT_EQ(0,
+            std::memcmp(expected, response.payload().data(), sizeof(expected)));
+
+  EXPECT_EQ(123, last_request.integer);
+  EXPECT_EQ(3u, last_request.status_code);
+}
+
+TEST(NanopbMethodUnion, Nanopb_CallsServerStreamingMethod) {
+  PW_ENCODE_PB(
+      pw_rpc_test_TestRequest, request, .integer = 555, .status_code = 0);
+
+  const Method& method =
+      std::get<3>(FakeGeneratedServiceImpl::kMethods).method();
+  ServerContextForTest<FakeGeneratedServiceImpl> context(method);
+
+  method.Invoke(context.get(), context.packet(request));
+
+  EXPECT_EQ(555, last_request.integer);
+  EXPECT_TRUE(last_writer.open());
+
+  EXPECT_EQ(OkStatus(), last_writer.Finish());
+  EXPECT_EQ(context.output().sent_packet().type(),
+            PacketType::SERVER_STREAM_END);
+}
+
+}  // namespace
+}  // namespace pw::rpc::internal
diff --git a/pw_rpc/nanopb/public/pw_rpc/echo_service_nanopb.h b/pw_rpc/nanopb/public/pw_rpc/echo_service_nanopb.h
index 707eb5a..54f8e7f 100644
--- a/pw_rpc/nanopb/public/pw_rpc/echo_service_nanopb.h
+++ b/pw_rpc/nanopb/public/pw_rpc/echo_service_nanopb.h
@@ -19,10 +19,11 @@
 
 namespace pw::rpc {
 
-class EchoService final
-    : public pw_rpc::nanopb::EchoService::Service<EchoService> {
+class EchoService final : public generated::EchoService<EchoService> {
  public:
-  Status Echo(const pw_rpc_EchoMessage& request, pw_rpc_EchoMessage& response) {
+  Status Echo(ServerContext&,
+              const pw_rpc_EchoMessage& request,
+              pw_rpc_EchoMessage& response) {
     std::strncpy(response.msg, request.msg, sizeof(response.msg));
     return OkStatus();
   }
diff --git a/pw_rpc/nanopb/public/pw_rpc/internal/nanopb_common.h b/pw_rpc/nanopb/public/pw_rpc/internal/nanopb_common.h
new file mode 100644
index 0000000..de31463
--- /dev/null
+++ b/pw_rpc/nanopb/public/pw_rpc/internal/nanopb_common.h
@@ -0,0 +1,63 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_bytes/span.h"
+#include "pw_status/status_with_size.h"
+
+namespace pw::rpc::internal {
+
+// Use a void* to cover both Nanopb 3's pb_field_s and Nanopb 4's pb_msgdesc_s.
+using NanopbMessageDescriptor = const void*;
+
+// Serializer/deserializer for nanopb message request and response structs in an
+// RPC method.
+class NanopbMethodSerde {
+ public:
+  constexpr NanopbMethodSerde(NanopbMessageDescriptor request_fields,
+                              NanopbMessageDescriptor response_fields)
+      : request_fields_(request_fields), response_fields_(response_fields) {}
+
+  StatusWithSize EncodeRequest(ByteSpan buffer,
+                               const void* proto_struct) const {
+    return Encode(request_fields_, buffer, proto_struct);
+  }
+  StatusWithSize EncodeResponse(ByteSpan buffer,
+                                const void* proto_struct) const {
+    return Encode(response_fields_, buffer, proto_struct);
+  }
+
+  bool DecodeRequest(void* proto_struct, ConstByteSpan buffer) const {
+    return Decode(request_fields_, proto_struct, buffer);
+  }
+  bool DecodeResponse(void* proto_struct, ConstByteSpan buffer) const {
+    return Decode(response_fields_, proto_struct, buffer);
+  }
+
+ private:
+  // Encodes a nanopb protobuf struct to serialized wire format.
+  StatusWithSize Encode(NanopbMessageDescriptor fields,
+                        ByteSpan buffer,
+                        const void* proto_struct) const;
+
+  // Decodes a serialized protobuf to a nanopb struct.
+  bool Decode(NanopbMessageDescriptor fields,
+              void* proto_struct,
+              ConstByteSpan buffer) const;
+
+  NanopbMessageDescriptor request_fields_;
+  NanopbMessageDescriptor response_fields_;
+};
+
+}  // namespace pw::rpc::internal
diff --git a/pw_rpc/nanopb/public/pw_rpc/internal/nanopb_method.h b/pw_rpc/nanopb/public/pw_rpc/internal/nanopb_method.h
new file mode 100644
index 0000000..6944e4a
--- /dev/null
+++ b/pw_rpc/nanopb/public/pw_rpc/internal/nanopb_method.h
@@ -0,0 +1,322 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <span>
+#include <type_traits>
+
+#include "pw_rpc/internal/base_server_writer.h"
+#include "pw_rpc/internal/config.h"
+#include "pw_rpc/internal/method.h"
+#include "pw_rpc/internal/method_type.h"
+#include "pw_rpc/internal/nanopb_common.h"
+#include "pw_rpc/server_context.h"
+#include "pw_status/status.h"
+#include "pw_status/status_with_size.h"
+
+namespace pw::rpc {
+
+// Define the Nanopb version of the the ServerWriter class.
+template <typename T>
+class ServerWriter : public internal::BaseServerWriter {
+ public:
+  // Allow default construction so that users can declare a variable into which
+  // to move ServerWriters from RPC calls.
+  constexpr ServerWriter() = default;
+
+  ServerWriter(ServerWriter&&) = default;
+  ServerWriter& operator=(ServerWriter&&) = default;
+
+  // Writes a response struct. Returns the following Status codes:
+  //
+  //   OK - the response was successfully sent
+  //   FAILED_PRECONDITION - the writer is closed
+  //   INTERNAL - pw_rpc was unable to encode the Nanopb protobuf
+  //   other errors - the ChannelOutput failed to send the packet; the error
+  //       codes are determined by the ChannelOutput implementation
+  //
+  Status Write(const T& response);
+};
+
+namespace internal {
+
+class NanopbMethod;
+class Packet;
+
+// MethodTraits specialization for a static unary method.
+template <typename RequestType, typename ResponseType>
+struct MethodTraits<Status (*)(
+    ServerContext&, const RequestType&, ResponseType&)> {
+  using Implementation = NanopbMethod;
+  using Request = RequestType;
+  using Response = ResponseType;
+
+  static constexpr MethodType kType = MethodType::kUnary;
+  static constexpr bool kServerStreaming = false;
+  static constexpr bool kClientStreaming = false;
+};
+
+// MethodTraits specialization for a unary method.
+template <typename T, typename RequestType, typename ResponseType>
+struct MethodTraits<Status (T::*)(
+    ServerContext&, const RequestType&, ResponseType&)>
+    : public MethodTraits<Status (*)(
+          ServerContext&, const RequestType&, ResponseType&)> {
+  using Service = T;
+};
+
+// MethodTraits specialization for a static server streaming method.
+template <typename RequestType, typename ResponseType>
+struct MethodTraits<void (*)(
+    ServerContext&, const RequestType&, ServerWriter<ResponseType>&)> {
+  using Implementation = NanopbMethod;
+  using Request = RequestType;
+  using Response = ResponseType;
+
+  static constexpr MethodType kType = MethodType::kServerStreaming;
+  static constexpr bool kServerStreaming = true;
+  static constexpr bool kClientStreaming = false;
+};
+
+// MethodTraits specialization for a server streaming method.
+template <typename T, typename RequestType, typename ResponseType>
+struct MethodTraits<void (T::*)(
+    ServerContext&, const RequestType&, ServerWriter<ResponseType>&)>
+    : public MethodTraits<void (*)(
+          ServerContext&, const RequestType&, ServerWriter<ResponseType>&)> {
+  using Service = T;
+};
+
+template <auto method>
+using Request = typename MethodTraits<decltype(method)>::Request;
+
+template <auto method>
+using Response = typename MethodTraits<decltype(method)>::Response;
+
+// The NanopbMethod class invokes user-defined service methods. When a
+// pw::rpc::Server receives an RPC request packet, it looks up the matching
+// NanopbMethod instance and calls its Invoke method, which eventually calls
+// into the user-defined RPC function.
+//
+// A NanopbMethod instance is created for each user-defined RPC in the pw_rpc
+// generated code. The NanopbMethod stores a pointer to the RPC function, a
+// pointer to an "invoker" function that calls that function, and pointers to
+// the Nanopb descriptors used to encode and decode request and response
+// structs.
+class NanopbMethod : public Method {
+ public:
+  template <auto method, typename RequestType, typename ResponseType>
+  static constexpr bool matches() {
+    return std::is_same_v<MethodImplementation<method>, NanopbMethod> &&
+           std::is_same_v<RequestType, Request<method>> &&
+           std::is_same_v<ResponseType, Response<method>>;
+  }
+
+  // Creates a NanopbMethod for a unary RPC.
+  template <auto method>
+  static constexpr NanopbMethod Unary(uint32_t id,
+                                      NanopbMessageDescriptor request,
+                                      NanopbMessageDescriptor response) {
+    // Define a wrapper around the user-defined function that takes the
+    // request and response protobuf structs as void*. This wrapper is stored
+    // generically in the Function union, defined below.
+    //
+    // In optimized builds, the compiler inlines the user-defined function into
+    // this wrapper, elminating any overhead.
+    constexpr UnaryFunction wrapper =
+        [](ServerCall& call, const void* req, void* resp) {
+          return CallMethodImplFunction<method>(
+              call,
+              *static_cast<const Request<method>*>(req),
+              *static_cast<Response<method>*>(resp));
+        };
+    return NanopbMethod(id,
+                        UnaryInvoker<AllocateSpaceFor<Request<method>>(),
+                                     AllocateSpaceFor<Response<method>>()>,
+                        Function{.unary = wrapper},
+                        request,
+                        response);
+  }
+
+  // Creates a NanopbMethod for a server-streaming RPC.
+  template <auto method>
+  static constexpr NanopbMethod ServerStreaming(
+      uint32_t id,
+      NanopbMessageDescriptor request,
+      NanopbMessageDescriptor response) {
+    // Define a wrapper around the user-defined function that takes the request
+    // struct as void* and a BaseServerWriter instead of the templated
+    // ServerWriter class. This wrapper is stored generically in the Function
+    // union, defined below.
+    constexpr ServerStreamingFunction wrapper =
+        [](ServerCall& call, const void* req, BaseServerWriter& writer) {
+          return CallMethodImplFunction<method>(
+              call,
+              *static_cast<const Request<method>*>(req),
+              static_cast<ServerWriter<Response<method>>&>(writer));
+        };
+    return NanopbMethod(
+        id,
+        ServerStreamingInvoker<AllocateSpaceFor<Request<method>>()>,
+        Function{.server_streaming = wrapper},
+        request,
+        response);
+  }
+
+  // Represents an invalid method. Used to reduce error message verbosity.
+  static constexpr NanopbMethod Invalid() {
+    return {0, InvalidInvoker, {}, nullptr, nullptr};
+  }
+
+  // Encodes a response protobuf with Nanopb to the provided buffer.
+  StatusWithSize EncodeResponse(const void* proto_struct,
+                                std::span<std::byte> buffer) const {
+    return serde_.EncodeResponse(buffer, proto_struct);
+  }
+
+  // Decodes a response protobuf with Nanopb to the provided buffer. For testing
+  // use.
+  bool DecodeResponse(std::span<const std::byte> response,
+                      void* proto_struct) const {
+    return serde_.DecodeResponse(proto_struct, response);
+  }
+
+ private:
+  // Generic version of the unary RPC function signature:
+  //
+  //   Status(ServerCall&, const Request&, Response&)
+  //
+  using UnaryFunction = Status (*)(ServerCall&,
+                                   const void* request,
+                                   void* response);
+
+  // Generic version of the server streaming RPC function signature:
+  //
+  //   Status(ServerCall&, const Request&, ServerWriter<Response>&)
+  //
+  using ServerStreamingFunction = void (*)(ServerCall&,
+                                           const void* request,
+                                           BaseServerWriter& writer);
+
+  // The Function union stores a pointer to a generic version of the
+  // user-defined RPC function. Using a union instead of void* avoids
+  // reinterpret_cast, which keeps this class fully constexpr.
+  union Function {
+    UnaryFunction unary;
+    ServerStreamingFunction server_streaming;
+    // TODO(hepler): Add client_streaming and bidi_streaming
+  };
+
+  // Allocates space for a struct. Rounds up to a reasonable minimum size to
+  // avoid generating unnecessary copies of the invoker functions.
+  template <typename T>
+  static constexpr size_t AllocateSpaceFor() {
+    return std::max(sizeof(T), cfg::kNanopbStructMinBufferSize);
+  }
+
+  constexpr NanopbMethod(uint32_t id,
+                         Invoker invoker,
+                         Function function,
+                         NanopbMessageDescriptor request,
+                         NanopbMessageDescriptor response)
+      : Method(id, invoker), function_(function), serde_(request, response) {}
+
+  void CallUnary(ServerCall& call,
+                 const Packet& request,
+                 void* request_struct,
+                 void* response_struct) const;
+
+  void CallServerStreaming(ServerCall& call,
+                           const Packet& request,
+                           void* request_struct) const;
+
+  // TODO(hepler): Add CallClientStreaming and CallBidiStreaming
+
+  // Invoker function for unary RPCs. Allocates request and response structs by
+  // size, with maximum alignment, to avoid generating unnecessary copies of
+  // this function for each request/response type.
+  template <size_t kRequestSize, size_t kResponseSize>
+  static void UnaryInvoker(const Method& method,
+                           ServerCall& call,
+                           const Packet& request) {
+    _PW_RPC_NANOPB_STRUCT_STORAGE_CLASS
+    std::aligned_storage_t<kRequestSize, alignof(std::max_align_t)>
+        request_struct{};
+    _PW_RPC_NANOPB_STRUCT_STORAGE_CLASS
+    std::aligned_storage_t<kResponseSize, alignof(std::max_align_t)>
+        response_struct{};
+
+    static_cast<const NanopbMethod&>(method).CallUnary(
+        call, request, &request_struct, &response_struct);
+  }
+
+  // Invoker function for server streaming RPCs. Allocates space for a request
+  // struct. Ignores the payload buffer since resposnes are sent through the
+  // ServerWriter.
+  template <size_t kRequestSize>
+  static void ServerStreamingInvoker(const Method& method,
+                                     ServerCall& call,
+                                     const Packet& request) {
+    _PW_RPC_NANOPB_STRUCT_STORAGE_CLASS
+    std::aligned_storage_t<kRequestSize, alignof(std::max_align_t)>
+        request_struct{};
+
+    static_cast<const NanopbMethod&>(method).CallServerStreaming(
+        call, request, &request_struct);
+  }
+
+  // Decodes a request protobuf with Nanopb to the provided buffer. Sends an
+  // error packet if the request failed to decode.
+  bool DecodeRequest(Channel& channel,
+                     const Packet& request,
+                     void* proto_struct) const;
+
+  // Encodes a response and sends it over the provided channel.
+  void SendResponse(Channel& channel,
+                    const Packet& request,
+                    const void* response_struct,
+                    Status status) const;
+
+  // Stores the user-defined RPC in a generic wrapper.
+  Function function_;
+
+  // Serde used to encode and decode Nanopb structs.
+  NanopbMethodSerde serde_;
+};
+
+}  // namespace internal
+
+template <typename T>
+Status ServerWriter<T>::Write(const T& response) {
+  if (!open()) {
+    return Status::FailedPrecondition();
+  }
+
+  std::span<std::byte> buffer = AcquirePayloadBuffer();
+
+  if (auto result =
+          static_cast<const internal::NanopbMethod&>(method()).EncodeResponse(
+              &response, buffer);
+      result.ok()) {
+    return ReleasePayloadBuffer(buffer.first(result.size()));
+  }
+
+  ReleasePayloadBuffer();
+  return Status::Internal();
+}
+
+}  // namespace pw::rpc
diff --git a/pw_rpc/nanopb/public/pw_rpc/internal/nanopb_method_union.h b/pw_rpc/nanopb/public/pw_rpc/internal/nanopb_method_union.h
new file mode 100644
index 0000000..da4ec30
--- /dev/null
+++ b/pw_rpc/nanopb/public/pw_rpc/internal/nanopb_method_union.h
@@ -0,0 +1,60 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_bytes/span.h"
+#include "pw_rpc/internal/method_union.h"
+#include "pw_rpc/internal/nanopb_method.h"
+#include "pw_rpc/internal/raw_method_union.h"
+
+namespace pw::rpc::internal {
+
+// Method union which holds either a nanopb or a raw method.
+class NanopbMethodUnion : public MethodUnion {
+ public:
+  constexpr NanopbMethodUnion(RawMethod&& method)
+      : impl_({.raw = std::move(method)}) {}
+  constexpr NanopbMethodUnion(NanopbMethod&& method)
+      : impl_({.nanopb = std::move(method)}) {}
+
+  constexpr const Method& method() const { return impl_.method; }
+  constexpr const RawMethod& raw_method() const { return impl_.raw; }
+  constexpr const NanopbMethod& nanopb_method() const { return impl_.nanopb; }
+
+ private:
+  union {
+    Method method;
+    RawMethod raw;
+    NanopbMethod nanopb;
+  } impl_;
+};
+
+// Returns either a raw or nanopb method object, depending on the implemented
+// function's signature.
+template <auto method, MethodType type, typename Request, typename Response>
+constexpr auto GetNanopbOrRawMethodFor(
+    uint32_t id,
+    [[maybe_unused]] NanopbMessageDescriptor request_fields,
+    [[maybe_unused]] NanopbMessageDescriptor response_fields) {
+  if constexpr (RawMethod::matches<method>()) {
+    return GetMethodFor<method, RawMethod, type>(id);
+  } else if constexpr (NanopbMethod::matches<method, Request, Response>()) {
+    return GetMethodFor<method, NanopbMethod, type>(
+        id, request_fields, response_fields);
+  } else {
+    return InvalidMethod<method, type, RawMethod>(id);
+  }
+};
+
+}  // namespace pw::rpc::internal
diff --git a/pw_rpc/nanopb/public/pw_rpc/nanopb/client_reader_writer.h b/pw_rpc/nanopb/public/pw_rpc/nanopb/client_reader_writer.h
deleted file mode 100644
index 61f5a73..0000000
--- a/pw_rpc/nanopb/public/pw_rpc/nanopb/client_reader_writer.h
+++ /dev/null
@@ -1,392 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// This file defines the ServerReaderWriter, ServerReader, and ServerWriter
-// classes for the Nanopb RPC interface. These classes are used for
-// bidirectional, client, and server streaming RPCs.
-#pragma once
-
-#include "pw_rpc/channel.h"
-#include "pw_rpc/internal/client_call.h"
-#include "pw_rpc/internal/lock.h"
-#include "pw_rpc/nanopb/internal/common.h"
-
-namespace pw::rpc {
-namespace internal {
-
-// Base class for unary and client streaming calls.
-template <typename Response>
-class NanopbUnaryResponseClientCall : public UnaryResponseClientCall {
- public:
-  template <typename CallType, typename... Request>
-  static CallType Start(Endpoint& client,
-                        uint32_t channel_id,
-                        uint32_t service_id,
-                        uint32_t method_id,
-                        const NanopbMethodSerde& serde,
-                        Function<void(const Response&, Status)>&& on_completed,
-                        Function<void(Status)>&& on_error,
-                        const Request&... request) {
-    rpc_lock().lock();
-    CallType call(client, channel_id, service_id, method_id, serde);
-
-    call.set_on_completed_locked(std::move(on_completed));
-    call.set_on_error_locked(std::move(on_error));
-
-    if constexpr (sizeof...(Request) == 0u) {
-      call.SendInitialClientRequest({});
-    } else {
-      NanopbSendInitialRequest(call, serde.request(), &request...);
-    }
-    return call;
-  }
-
- protected:
-  constexpr NanopbUnaryResponseClientCall() = default;
-
-  NanopbUnaryResponseClientCall(internal::Endpoint& client,
-                                uint32_t channel_id,
-                                uint32_t service_id,
-                                uint32_t method_id,
-                                MethodType type,
-                                const NanopbMethodSerde& serde)
-      : UnaryResponseClientCall(
-            client, channel_id, service_id, method_id, type),
-        serde_(&serde) {}
-
-  NanopbUnaryResponseClientCall(NanopbUnaryResponseClientCall&& other)
-      PW_LOCKS_EXCLUDED(rpc_lock()) {
-    *this = std::move(other);
-  }
-
-  NanopbUnaryResponseClientCall& operator=(
-      NanopbUnaryResponseClientCall&& other) PW_LOCKS_EXCLUDED(rpc_lock()) {
-    LockGuard lock(rpc_lock());
-    MoveUnaryResponseClientCallFrom(other);
-    serde_ = other.serde_;
-    set_on_completed_locked(std::move(other.nanopb_on_completed_));
-    return *this;
-  }
-
-  void set_on_completed(
-      Function<void(const Response& response, Status)>&& on_completed)
-      PW_LOCKS_EXCLUDED(rpc_lock()) {
-    LockGuard lock(rpc_lock());
-    set_on_completed_locked(std::move(on_completed));
-  }
-
-  Status SendClientStream(const void* payload) PW_LOCKS_EXCLUDED(rpc_lock()) {
-    if (!active()) {
-      return Status::FailedPrecondition();
-    }
-    return NanopbSendStream(*this, payload, serde_->request());
-  }
-
- private:
-  void set_on_completed_locked(
-      Function<void(const Response& response, Status)>&& on_completed)
-      PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    nanopb_on_completed_ = std::move(on_completed);
-
-    UnaryResponseClientCall::set_on_completed_locked(
-        [this](ConstByteSpan payload, Status status) {
-          if (nanopb_on_completed_) {
-            Response response_struct{};
-            if (serde_->DecodeResponse(payload, &response_struct)) {
-              nanopb_on_completed_(response_struct, status);
-            } else {
-              // TODO(hepler): This should send a DATA_LOSS error and call the
-              //     error callback.
-              rpc_lock().lock();
-              CallOnError(Status::DataLoss());
-            }
-          }
-        });
-  }
-
-  const NanopbMethodSerde* serde_;
-  Function<void(const Response&, Status)> nanopb_on_completed_;
-};
-
-// Base class for server and bidirectional streaming calls.
-template <typename Response>
-class NanopbStreamResponseClientCall : public StreamResponseClientCall {
- public:
-  template <typename CallType, typename... Request>
-  static CallType Start(Endpoint& client,
-                        uint32_t channel_id,
-                        uint32_t service_id,
-                        uint32_t method_id,
-                        const NanopbMethodSerde& serde,
-                        Function<void(const Response&)>&& on_next,
-                        Function<void(Status)>&& on_completed,
-                        Function<void(Status)>&& on_error,
-                        const Request&... request) {
-    rpc_lock().lock();
-    CallType call(client, channel_id, service_id, method_id, serde);
-
-    call.set_on_next_locked(std::move(on_next));
-    call.set_on_completed_locked(std::move(on_completed));
-    call.set_on_error_locked(std::move(on_error));
-
-    if constexpr (sizeof...(Request) == 0u) {
-      call.SendInitialClientRequest({});
-    } else {
-      NanopbSendInitialRequest(call, serde.request(), &request...);
-    }
-    return call;
-  }
-
- protected:
-  constexpr NanopbStreamResponseClientCall() = default;
-
-  NanopbStreamResponseClientCall(NanopbStreamResponseClientCall&& other)
-      PW_LOCKS_EXCLUDED(rpc_lock()) {
-    *this = std::move(other);
-  }
-
-  NanopbStreamResponseClientCall& operator=(
-      NanopbStreamResponseClientCall&& other) PW_LOCKS_EXCLUDED(rpc_lock()) {
-    LockGuard lock(rpc_lock());
-    MoveStreamResponseClientCallFrom(other);
-    serde_ = other.serde_;
-    set_on_next_locked(std::move(other.nanopb_on_next_));
-    return *this;
-  }
-
-  NanopbStreamResponseClientCall(internal::Endpoint& client,
-                                 uint32_t channel_id,
-                                 uint32_t service_id,
-                                 uint32_t method_id,
-                                 MethodType type,
-                                 const NanopbMethodSerde& serde)
-      : StreamResponseClientCall(
-            client, channel_id, service_id, method_id, type),
-        serde_(&serde) {}
-
-  Status SendClientStream(const void* payload) {
-    if (!active()) {
-      return Status::FailedPrecondition();
-    }
-    return NanopbSendStream(*this, payload, serde_->request());
-  }
-
-  void set_on_next(Function<void(const Response& response)>&& on_next)
-      PW_LOCKS_EXCLUDED(rpc_lock()) {
-    LockGuard lock(rpc_lock());
-    set_on_next_locked(std::move(on_next));
-  }
-
- private:
-  void set_on_next_locked(Function<void(const Response& response)>&& on_next)
-      PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    nanopb_on_next_ = std::move(on_next);
-
-    internal::Call::set_on_next_locked([this](ConstByteSpan payload) {
-      if (nanopb_on_next_) {
-        Response response_struct{};
-        if (serde_->DecodeResponse(payload, &response_struct)) {
-          nanopb_on_next_(response_struct);
-        } else {
-          // TODO(hepler): This should send a DATA_LOSS error and call the
-          //     error callback.
-          rpc_lock().lock();
-          CallOnError(Status::DataLoss());
-        }
-      }
-    });
-  }
-
-  const NanopbMethodSerde* serde_;
-  Function<void(const Response&)> nanopb_on_next_;
-};
-
-}  // namespace internal
-
-// The NanopbClientReaderWriter is used to send and receive messages in a
-// bidirectional streaming RPC.
-template <typename Request, typename ResponseType>
-class NanopbClientReaderWriter
-    : private internal::NanopbStreamResponseClientCall<ResponseType> {
- public:
-  using Response = ResponseType;
-
-  constexpr NanopbClientReaderWriter() = default;
-
-  NanopbClientReaderWriter(NanopbClientReaderWriter&&) = default;
-  NanopbClientReaderWriter& operator=(NanopbClientReaderWriter&&) = default;
-
-  using internal::Call::active;
-  using internal::Call::channel_id;
-
-  // Writes a response struct. Returns the following Status codes:
-  //
-  //   OK - the response was successfully sent
-  //   FAILED_PRECONDITION - the writer is closed
-  //   INTERNAL - pw_rpc was unable to encode the Nanopb protobuf
-  //   other errors - the ChannelOutput failed to send the packet; the error
-  //       codes are determined by the ChannelOutput implementation
-  //
-  Status Write(const Request& request) {
-    return internal::NanopbStreamResponseClientCall<Response>::SendClientStream(
-        &request);
-  }
-
-  using internal::Call::Cancel;
-  using internal::Call::CloseClientStream;
-
-  // Functions for setting RPC event callbacks.
-  using internal::Call::set_on_error;
-  using internal::StreamResponseClientCall::set_on_completed;
-  using internal::NanopbStreamResponseClientCall<Response>::set_on_next;
-
- private:
-  friend class internal::NanopbStreamResponseClientCall<Response>;
-
-  NanopbClientReaderWriter(internal::Endpoint& client,
-                           uint32_t channel_id_value,
-                           uint32_t service_id,
-                           uint32_t method_id,
-                           const internal::NanopbMethodSerde& serde)
-      : internal::NanopbStreamResponseClientCall<Response>(
-            client,
-            channel_id_value,
-            service_id,
-            method_id,
-            MethodType::kBidirectionalStreaming,
-            serde) {}
-};
-
-// The NanopbClientReader is used to receive messages and send a response in a
-// client streaming RPC.
-template <typename ResponseType>
-class NanopbClientReader
-    : private internal::NanopbStreamResponseClientCall<ResponseType> {
- public:
-  using Response = ResponseType;
-
-  constexpr NanopbClientReader() = default;
-
-  NanopbClientReader(NanopbClientReader&&) = default;
-  NanopbClientReader& operator=(NanopbClientReader&&) = default;
-
-  using internal::Call::active;
-  using internal::Call::channel_id;
-
-  // Functions for setting RPC event callbacks.
-  using internal::NanopbStreamResponseClientCall<Response>::set_on_next;
-  using internal::Call::set_on_error;
-  using internal::StreamResponseClientCall::set_on_completed;
-
-  using internal::Call::Cancel;
-
- private:
-  friend class internal::NanopbStreamResponseClientCall<Response>;
-
-  NanopbClientReader(internal::Endpoint& client,
-                     uint32_t channel_id_value,
-                     uint32_t service_id,
-                     uint32_t method_id,
-                     const internal::NanopbMethodSerde& serde)
-      : internal::NanopbStreamResponseClientCall<Response>(
-            client,
-            channel_id_value,
-            service_id,
-            method_id,
-            MethodType::kServerStreaming,
-            serde) {}
-};
-
-// The NanopbClientWriter is used to send responses in a server streaming RPC.
-template <typename Request, typename ResponseType>
-class NanopbClientWriter
-    : private internal::NanopbUnaryResponseClientCall<ResponseType> {
- public:
-  using Response = ResponseType;
-
-  constexpr NanopbClientWriter() = default;
-
-  NanopbClientWriter(NanopbClientWriter&&) = default;
-  NanopbClientWriter& operator=(NanopbClientWriter&&) = default;
-
-  using internal::Call::active;
-  using internal::Call::channel_id;
-
-  // Functions for setting RPC event callbacks.
-  using internal::NanopbUnaryResponseClientCall<Response>::set_on_completed;
-  using internal::Call::set_on_error;
-
-  Status Write(const Request& request) {
-    return internal::NanopbUnaryResponseClientCall<Response>::SendClientStream(
-        &request);
-  }
-
-  using internal::Call::Cancel;
-  using internal::Call::CloseClientStream;
-
- private:
-  friend class internal::NanopbUnaryResponseClientCall<Response>;
-
-  NanopbClientWriter(internal::Endpoint& client,
-                     uint32_t channel_id_value,
-                     uint32_t service_id,
-                     uint32_t method_id,
-                     const internal::NanopbMethodSerde& serde)
-      : internal::NanopbUnaryResponseClientCall<Response>(
-            client,
-            channel_id_value,
-            service_id,
-            method_id,
-            MethodType::kClientStreaming,
-            serde) {}
-};
-
-// The NanopbUnaryReceiver is used to receive a response in a unary RPC.
-template <typename ResponseType>
-class NanopbUnaryReceiver
-    : private internal::NanopbUnaryResponseClientCall<ResponseType> {
- public:
-  using Response = ResponseType;
-
-  constexpr NanopbUnaryReceiver() = default;
-
-  NanopbUnaryReceiver(NanopbUnaryReceiver&&) = default;
-  NanopbUnaryReceiver& operator=(NanopbUnaryReceiver&&) = default;
-
-  using internal::Call::active;
-  using internal::Call::channel_id;
-
-  // Functions for setting RPC event callbacks.
-  using internal::NanopbUnaryResponseClientCall<Response>::set_on_completed;
-  using internal::Call::set_on_error;
-
-  using internal::Call::Cancel;
-
- private:
-  friend class internal::NanopbUnaryResponseClientCall<Response>;
-
-  NanopbUnaryReceiver(internal::Endpoint& client,
-                      uint32_t channel_id_value,
-                      uint32_t service_id,
-                      uint32_t method_id,
-                      const internal::NanopbMethodSerde& serde)
-      : internal::NanopbUnaryResponseClientCall<Response>(client,
-                                                          channel_id_value,
-                                                          service_id,
-                                                          method_id,
-                                                          MethodType::kUnary,
-                                                          serde) {}
-};
-
-}  // namespace pw::rpc
diff --git a/pw_rpc/nanopb/public/pw_rpc/nanopb/client_testing.h b/pw_rpc/nanopb/public/pw_rpc/nanopb/client_testing.h
deleted file mode 100644
index d0e0898..0000000
--- a/pw_rpc/nanopb/public/pw_rpc/nanopb/client_testing.h
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstddef>
-#include <cstdint>
-
-#include "pw_bytes/span.h"
-#include "pw_rpc/client.h"
-#include "pw_rpc/internal/method_info.h"
-#include "pw_rpc/nanopb/fake_channel_output.h"
-#include "pw_rpc/raw/client_testing.h"
-
-namespace pw::rpc {
-
-// TODO(pwbug/477): Document the client testing APIs.
-
-// Sends packets to an RPC client as if it were a pw_rpc server. Accepts
-// payloads as Nanopb structs.
-class NanopbFakeServer : public FakeServer {
- private:
-  template <auto kMethod>
-  using Response = typename internal::MethodInfo<kMethod>::Response;
-
- public:
-  using FakeServer::FakeServer;
-
-  // Sends a response packet for a server or bidirectional streaming RPC to the
-  // client.
-  template <auto kMethod>
-  void SendResponse(Status status) const {
-    FakeServer::SendResponse<kMethod>(status);
-  }
-
-  // Sends a response packet for a unary or client streaming streaming RPC to
-  // the client.
-  template <auto kMethod,
-            size_t kEncodeBufferSizeBytes = 2 * sizeof(Response<kMethod>)>
-  void SendResponse(const Response<kMethod>& payload, Status status) const {
-    std::byte buffer[kEncodeBufferSizeBytes] = {};
-    FakeServer::SendResponse<kMethod>(EncodeResponse<kMethod>(&payload, buffer),
-                                      status);
-  }
-
-  // Sends a stream packet for a server or bidirectional streaming RPC to the
-  // client.
-  template <auto kMethod,
-            size_t kEncodeBufferSizeBytes = 2 * sizeof(Response<kMethod>)>
-  void SendServerStream(const Response<kMethod>& payload) const {
-    std::byte buffer[kEncodeBufferSizeBytes] = {};
-    FakeServer::SendServerStream<kMethod>(
-        EncodeResponse<kMethod>(&payload, buffer));
-  }
-
- private:
-  template <auto kMethod>
-  static ConstByteSpan EncodeResponse(const void* payload, ByteSpan buffer) {
-    const StatusWithSize result =
-        internal::MethodInfo<kMethod>::serde().EncodeResponse(payload, buffer);
-    PW_ASSERT(result.ok());
-    return std::span(buffer).first(result.size());
-  }
-};
-
-// Instantiates a NanopbFakeServer, Client, Channel, and NanopbFakeChannelOutput
-// for testing RPC client calls. These components may be used individually, but
-// are instantiated together for convenience.
-template <size_t kMaxPackets = 10,
-          size_t kPacketEncodeBufferSizeBytes = 128,
-          size_t kPayloadsBufferSizeBytes = 256>
-class NanopbClientTestContext {
- public:
-  constexpr NanopbClientTestContext()
-      : channel_(Channel::Create<kDefaultChannelId>(&channel_output_)),
-        client_(std::span(&channel_, 1)),
-        packet_buffer_{},
-        fake_server_(
-            channel_output_, client_, kDefaultChannelId, packet_buffer_) {}
-
-  const Channel& channel() const { return channel_; }
-  Channel& channel() { return channel_; }
-
-  const NanopbFakeServer& server() const { return fake_server_; }
-  NanopbFakeServer& server() { return fake_server_; }
-
-  const Client& client() const { return client_; }
-  Client& client() { return client_; }
-
-  const auto& output() const { return channel_output_; }
-
- private:
-  static constexpr uint32_t kDefaultChannelId = 1;
-
-  NanopbFakeChannelOutput<kMaxPackets, kPayloadsBufferSizeBytes>
-      channel_output_;
-  Channel channel_;
-  Client client_;
-  std::byte packet_buffer_[kPacketEncodeBufferSizeBytes];
-  NanopbFakeServer fake_server_;
-};
-
-}  // namespace pw::rpc
diff --git a/pw_rpc/nanopb/public/pw_rpc/nanopb/fake_channel_output.h b/pw_rpc/nanopb/public/pw_rpc/nanopb/fake_channel_output.h
deleted file mode 100644
index 360cfc1..0000000
--- a/pw_rpc/nanopb/public/pw_rpc/nanopb/fake_channel_output.h
+++ /dev/null
@@ -1,182 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstddef>
-#include <cstdint>
-
-#include "pw_assert/assert.h"
-#include "pw_bytes/span.h"
-#include "pw_containers/vector.h"
-#include "pw_containers/wrapped_iterator.h"
-#include "pw_rpc/internal/fake_channel_output.h"
-#include "pw_rpc/nanopb/internal/common.h"
-#include "pw_rpc/nanopb/internal/method.h"
-
-namespace pw::rpc {
-namespace internal::test::nanopb {
-
-// Forward declare for a friend statement.
-template <typename, auto, uint32_t, size_t, size_t>
-class NanopbInvocationContext;
-
-}  // namespace internal::test::nanopb
-
-// Supports iterating over payloads as decoded Nanopb structs.
-template <typename Payload>
-class NanopbPayloadsView {
- public:
-  class iterator : public containers::WrappedIterator<iterator,
-                                                      PayloadsView::iterator,
-                                                      Payload> {
-   public:
-    // Access the payload (rather than packet) with operator*.
-    Payload operator*() const {
-      Payload payload{};
-      PW_ASSERT(serde_.Decode(Base::value(), &payload));
-      return payload;
-    }
-
-   private:
-    friend class NanopbPayloadsView;
-
-    constexpr iterator(const PayloadsView::iterator& it,
-                       const internal::NanopbSerde& serde)
-        : Base(it), serde_(serde) {}
-
-    internal::NanopbSerde serde_;
-  };
-
-  Payload operator[](size_t index) const {
-    Payload payload{};
-    PW_ASSERT(serde_.Decode(view_[index], &payload));
-    return payload;
-  }
-
-  size_t size() const { return view_.size(); }
-  bool empty() const { return view_.empty(); }
-
-  // Returns the first/last payload for the RPC. size() must be > 0.
-  Payload front() const { return *begin(); }
-  Payload back() const { return *std::prev(end()); }
-
-  iterator begin() const { return iterator(view_.begin(), serde_); }
-  iterator end() const { return iterator(view_.end(), serde_); }
-
- private:
-  using Base =
-      containers::WrappedIterator<iterator, PayloadsView::iterator, Payload>;
-
-  template <size_t, size_t>
-  friend class NanopbFakeChannelOutput;
-
-  template <typename... Args>
-  NanopbPayloadsView(const internal::NanopbSerde& serde, Args&&... args)
-      : view_(args...), serde_(serde) {}
-
-  PayloadsView view_;
-  internal::NanopbSerde serde_;
-};
-
-// A ChannelOutput implementation that stores the outgoing payloads and status.
-template <size_t kMaxPackets, size_t kPayloadsBufferSizeBytes = 128>
-class NanopbFakeChannelOutput final
-    : public internal::test::FakeChannelOutputBuffer<kMaxPackets,
-                                                     kPayloadsBufferSizeBytes> {
- private:
-  template <auto kMethod>
-  using Request = typename internal::MethodInfo<kMethod>::Request;
-  template <auto kMethod>
-  using Response = typename internal::MethodInfo<kMethod>::Response;
-
- public:
-  NanopbFakeChannelOutput() = default;
-
-  // Iterates over request payloads from request or client stream packets.
-  //
-  // !!! WARNING !!!
-  //
-  // Access to the FakeChannelOutput through the NanopbPayloadsView is NOT
-  // synchronized! The NanopbPayloadsView is immediately invalidated if any
-  // thread accesses the FakeChannelOutput.
-  template <auto kMethod>
-  NanopbPayloadsView<Request<kMethod>> requests(
-      uint32_t channel_id = Channel::kUnassignedChannelId) const {
-    constexpr internal::PacketType packet_type =
-        HasClientStream(internal::MethodInfo<kMethod>::kType)
-            ? internal::PacketType::CLIENT_STREAM
-            : internal::PacketType::REQUEST;
-    return NanopbPayloadsView<Request<kMethod>>(
-        internal::MethodInfo<kMethod>::serde().request(),
-        Base::packets(),
-        packet_type,
-        packet_type,
-        channel_id,
-        internal::MethodInfo<kMethod>::kServiceId,
-        internal::MethodInfo<kMethod>::kMethodId);
-  }
-
-  // Iterates over response payloads from response or server stream packets.
-  //
-  // !!! WARNING !!!
-  //
-  // Access to the FakeChannelOutput through the NanopbPayloadsView is NOT
-  // synchronized! The NanopbPayloadsView is immediately invalidated if any
-  // thread accesses the FakeChannelOutput.
-  template <auto kMethod>
-  NanopbPayloadsView<Response<kMethod>> responses(
-      uint32_t channel_id = Channel::kUnassignedChannelId) const {
-    constexpr internal::PacketType packet_type =
-        HasServerStream(internal::MethodInfo<kMethod>::kType)
-            ? internal::PacketType::SERVER_STREAM
-            : internal::PacketType::RESPONSE;
-    return NanopbPayloadsView<Response<kMethod>>(
-        internal::MethodInfo<kMethod>::serde().response(),
-        Base::packets(),
-        packet_type,
-        packet_type,
-        channel_id,
-        internal::MethodInfo<kMethod>::kServiceId,
-        internal::MethodInfo<kMethod>::kMethodId);
-  }
-
-  template <auto kMethod>
-  Response<kMethod> last_response() const {
-    NanopbPayloadsView<Response<kMethod>> payloads = responses<kMethod>();
-    PW_ASSERT(!payloads.empty());
-    return payloads.back();
-  }
-
- private:
-  template <typename, auto, uint32_t, size_t, size_t>
-  friend class internal::test::nanopb::NanopbInvocationContext;
-
-  using Base =
-      internal::test::FakeChannelOutputBuffer<kMaxPackets,
-                                              kPayloadsBufferSizeBytes>;
-
-  using internal::test::FakeChannelOutput::last_packet;
-
-  template <typename T>
-  NanopbPayloadsView<T> payload_structs(const internal::NanopbSerde& serde,
-                                        MethodType type,
-                                        uint32_t channel_id,
-                                        uint32_t service_id,
-                                        uint32_t method_id) const {
-    return NanopbPayloadsView<T>(
-        serde, Base::packets(), type, channel_id, service_id, method_id);
-  }
-};
-
-}  // namespace pw::rpc
diff --git a/pw_rpc/nanopb/public/pw_rpc/nanopb/internal/common.h b/pw_rpc/nanopb/public/pw_rpc/nanopb/internal/common.h
deleted file mode 100644
index ca69c8f..0000000
--- a/pw_rpc/nanopb/public/pw_rpc/nanopb/internal/common.h
+++ /dev/null
@@ -1,119 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pb_common.h"
-#include "pw_bytes/span.h"
-#include "pw_rpc/internal/lock.h"
-#include "pw_status/status_with_size.h"
-
-namespace pw::rpc::internal {
-
-// Nanopb 0.3 uses pb_field_t, but Nanopb 4 uses pb_msgdesc_t. Determine which
-// type to use by deducing it from the pb_field_iter_begin function.
-template <typename PbFieldIterBeginFunction>
-struct NanopbDescriptorTraits;
-
-template <typename T>
-struct NanopbDescriptorTraits<bool(pb_field_iter_t*, T, void*)> {
-  using Type = T;
-};
-
-using NanopbMessageDescriptor =
-    NanopbDescriptorTraits<decltype(pb_field_iter_begin)>::Type;
-
-// Serializer/deserializer for a Nanopb protobuf message.
-class NanopbSerde {
- public:
-  explicit constexpr NanopbSerde(NanopbMessageDescriptor fields)
-      : fields_(fields) {}
-
-  NanopbSerde(const NanopbSerde&) = default;
-  NanopbSerde& operator=(const NanopbSerde&) = default;
-
-  // Encodes a Nanopb protobuf struct to the serialized wire format.
-  StatusWithSize Encode(const void* proto_struct, ByteSpan buffer) const;
-
-  // Calculates the encoded size of the provided protobuf struct without
-  // actually encoding it.
-  StatusWithSize EncodedSizeBytes(const void* proto_struct) const;
-
-  // Decodes a serialized protobuf to a Nanopb struct.
-  bool Decode(ConstByteSpan buffer, void* proto_struct) const;
-
- private:
-  NanopbMessageDescriptor fields_;
-};
-
-// Serializer/deserializer for Nanopb message request and response structs in an
-// RPC method.
-class NanopbMethodSerde {
- public:
-  constexpr NanopbMethodSerde(NanopbMessageDescriptor request_fields,
-                              NanopbMessageDescriptor response_fields)
-      : request_fields_(request_fields), response_fields_(response_fields) {}
-
-  NanopbMethodSerde(const NanopbMethodSerde&) = delete;
-  NanopbMethodSerde& operator=(const NanopbMethodSerde&) = delete;
-
-  StatusWithSize EncodeRequest(const void* proto_struct,
-                               ByteSpan buffer) const {
-    return request_fields_.Encode(proto_struct, buffer);
-  }
-  StatusWithSize EncodeResponse(const void* proto_struct,
-                                ByteSpan buffer) const {
-    return response_fields_.Encode(proto_struct, buffer);
-  }
-
-  bool DecodeRequest(ConstByteSpan buffer, void* proto_struct) const {
-    return request_fields_.Decode(buffer, proto_struct);
-  }
-  bool DecodeResponse(ConstByteSpan buffer, void* proto_struct) const {
-    return response_fields_.Decode(buffer, proto_struct);
-  }
-
-  const NanopbSerde& request() const { return request_fields_; }
-  const NanopbSerde& response() const { return response_fields_; }
-
- private:
-  NanopbSerde request_fields_;
-  NanopbSerde response_fields_;
-};
-
-template <NanopbMessageDescriptor kRequest, NanopbMessageDescriptor kResponse>
-inline constexpr NanopbMethodSerde kNanopbMethodSerde(kRequest, kResponse);
-
-class Call;
-class ClientCall;
-class NanopbServerCall;
-
-// [Client] Encodes and sends the initial request message for the call.
-// active() must be true.
-void NanopbSendInitialRequest(ClientCall& call,
-                              NanopbSerde serde,
-                              const void* payload)
-    PW_UNLOCK_FUNCTION(rpc_lock());
-
-// [Client/Server] Encodes and sends a client or server stream message.
-// active() must be true.
-Status NanopbSendStream(Call& call, const void* payload, NanopbSerde serde)
-    PW_LOCKS_EXCLUDED(rpc_lock());
-
-// [Server] Encodes and sends the final response message.
-// Returns Status::FailedPrecondition if active() is false.
-Status SendFinalResponse(NanopbServerCall& call,
-                         const void* payload,
-                         Status status) PW_LOCKS_EXCLUDED(rpc_lock());
-
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/nanopb/public/pw_rpc/nanopb/internal/method.h b/pw_rpc/nanopb/public/pw_rpc/nanopb/internal/method.h
deleted file mode 100644
index fb84fc1..0000000
--- a/pw_rpc/nanopb/public/pw_rpc/nanopb/internal/method.h
+++ /dev/null
@@ -1,420 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <algorithm>
-#include <cstddef>
-#include <cstdint>
-#include <span>
-#include <type_traits>
-
-#include "pw_function/function.h"
-#include "pw_rpc/internal/config.h"
-#include "pw_rpc/internal/lock.h"
-#include "pw_rpc/internal/method.h"
-#include "pw_rpc/method_type.h"
-#include "pw_rpc/nanopb/internal/common.h"
-#include "pw_rpc/nanopb/server_reader_writer.h"
-#include "pw_status/status.h"
-#include "pw_status/status_with_size.h"
-
-namespace pw::rpc::internal {
-
-class NanopbMethod;
-class Packet;
-
-// Expected function signatures for user-implemented RPC functions.
-template <typename Request, typename Response>
-using NanopbSynchronousUnary = Status(const Request&, Response&);
-
-template <typename Request, typename Response>
-using NanopbAsynchronousUnary = void(const Request&,
-                                     NanopbUnaryResponder<Response>&);
-
-template <typename Request, typename Response>
-using NanopbServerStreaming = void(const Request&,
-                                   NanopbServerWriter<Response>&);
-
-template <typename Request, typename Response>
-using NanopbClientStreaming = void(NanopbServerReader<Request, Response>&);
-
-template <typename Request, typename Response>
-using NanopbBidirectionalStreaming =
-    void(NanopbServerReaderWriter<Request, Response>&);
-
-// MethodTraits specialization for a static synchronous unary method.
-template <typename Req, typename Resp>
-struct MethodTraits<NanopbSynchronousUnary<Req, Resp>*> {
-  using Implementation = NanopbMethod;
-  using Request = Req;
-  using Response = Resp;
-
-  static constexpr MethodType kType = MethodType::kUnary;
-  static constexpr bool kSynchronous = true;
-
-  static constexpr bool kServerStreaming = false;
-  static constexpr bool kClientStreaming = false;
-};
-
-// MethodTraits specialization for a synchronous unary method.
-template <typename T, typename Req, typename Resp>
-struct MethodTraits<NanopbSynchronousUnary<Req, Resp>(T::*)>
-    : MethodTraits<NanopbSynchronousUnary<Req, Resp>*> {
-  using Service = T;
-};
-
-// MethodTraits specialization for a static asynchronous unary method.
-template <typename Req, typename Resp>
-struct MethodTraits<NanopbAsynchronousUnary<Req, Resp>*>
-    : MethodTraits<NanopbSynchronousUnary<Req, Resp>*> {
-  static constexpr bool kSynchronous = false;
-};
-
-// MethodTraits specialization for an asynchronous unary method.
-template <typename T, typename Req, typename Resp>
-struct MethodTraits<NanopbAsynchronousUnary<Req, Resp>(T::*)>
-    : MethodTraits<NanopbSynchronousUnary<Req, Resp>(T::*)> {
-  static constexpr bool kSynchronous = false;
-};
-
-// MethodTraits specialization for a static server streaming method.
-template <typename Req, typename Resp>
-struct MethodTraits<NanopbServerStreaming<Req, Resp>*> {
-  using Implementation = NanopbMethod;
-  using Request = Req;
-  using Response = Resp;
-
-  static constexpr MethodType kType = MethodType::kServerStreaming;
-  static constexpr bool kServerStreaming = true;
-  static constexpr bool kClientStreaming = false;
-};
-
-// MethodTraits specialization for a server streaming method.
-template <typename T, typename Req, typename Resp>
-struct MethodTraits<NanopbServerStreaming<Req, Resp>(T::*)>
-    : MethodTraits<NanopbServerStreaming<Req, Resp>*> {
-  using Service = T;
-};
-
-// MethodTraits specialization for a static server streaming method.
-template <typename Req, typename Resp>
-struct MethodTraits<NanopbClientStreaming<Req, Resp>*> {
-  using Implementation = NanopbMethod;
-  using Request = Req;
-  using Response = Resp;
-
-  static constexpr MethodType kType = MethodType::kClientStreaming;
-  static constexpr bool kServerStreaming = false;
-  static constexpr bool kClientStreaming = true;
-};
-
-// MethodTraits specialization for a server streaming method.
-template <typename T, typename Req, typename Resp>
-struct MethodTraits<NanopbClientStreaming<Req, Resp>(T::*)>
-    : MethodTraits<NanopbClientStreaming<Req, Resp>*> {
-  using Service = T;
-};
-
-// MethodTraits specialization for a static server streaming method.
-template <typename Req, typename Resp>
-struct MethodTraits<NanopbBidirectionalStreaming<Req, Resp>*> {
-  using Implementation = NanopbMethod;
-  using Request = Req;
-  using Response = Resp;
-
-  static constexpr MethodType kType = MethodType::kBidirectionalStreaming;
-  static constexpr bool kServerStreaming = true;
-  static constexpr bool kClientStreaming = true;
-};
-
-// MethodTraits specialization for a server streaming method.
-template <typename T, typename Req, typename Resp>
-struct MethodTraits<NanopbBidirectionalStreaming<Req, Resp>(T::*)>
-    : MethodTraits<NanopbBidirectionalStreaming<Req, Resp>*> {
-  using Service = T;
-};
-
-// The NanopbMethod class invokes user-defined service methods. When a
-// pw::rpc::Server receives an RPC request packet, it looks up the matching
-// NanopbMethod instance and calls its Invoke method, which eventually calls
-// into the user-defined RPC function.
-//
-// A NanopbMethod instance is created for each user-defined RPC in the pw_rpc
-// generated code. The NanopbMethod stores a pointer to the RPC function, a
-// pointer to an "invoker" function that calls that function, and pointers to
-// the Nanopb descriptors used to encode and decode request and response
-// structs.
-class NanopbMethod : public Method {
- public:
-  template <auto kMethod, typename RequestType, typename ResponseType>
-  static constexpr bool matches() {
-    return std::is_same_v<MethodImplementation<kMethod>, NanopbMethod> &&
-           std::is_same_v<RequestType, Request<kMethod>> &&
-           std::is_same_v<ResponseType, Response<kMethod>>;
-  }
-
-  // Creates a NanopbMethod for a synchronous unary RPC.
-  template <auto kMethod>
-  static constexpr NanopbMethod SynchronousUnary(
-      uint32_t id, const NanopbMethodSerde& serde) {
-    // Define a wrapper around the user-defined function that takes the
-    // request and response protobuf structs as void*. This wrapper is stored
-    // generically in the Function union, defined below.
-    //
-    // In optimized builds, the compiler inlines the user-defined function into
-    // this wrapper, elminating any overhead.
-    constexpr SynchronousUnaryFunction wrapper =
-        [](Service& service, const void* req, void* resp) {
-          return CallMethodImplFunction<kMethod>(
-              service,
-              *static_cast<const Request<kMethod>*>(req),
-              *static_cast<Response<kMethod>*>(resp));
-        };
-    return NanopbMethod(
-        id,
-        SynchronousUnaryInvoker<AllocateSpaceFor<Request<kMethod>>(),
-                                AllocateSpaceFor<Response<kMethod>>()>,
-        Function{.synchronous_unary = wrapper},
-        serde);
-  }
-
-  // Creates a NanopbMethod for an asynchronous unary RPC.
-  template <auto kMethod>
-  static constexpr NanopbMethod AsynchronousUnary(
-      uint32_t id, const NanopbMethodSerde& serde) {
-    // Define a wrapper around the user-defined function that takes the
-    // request and response protobuf structs as void*. This wrapper is stored
-    // generically in the Function union, defined below.
-    //
-    // In optimized builds, the compiler inlines the user-defined function into
-    // this wrapper, elminating any overhead.
-    constexpr UnaryRequestFunction wrapper =
-        [](Service& service, const void* req, NanopbServerCall& resp) {
-          return CallMethodImplFunction<kMethod>(
-              service,
-              *static_cast<const Request<kMethod>*>(req),
-              static_cast<NanopbUnaryResponder<Response<kMethod>>&>(resp));
-        };
-    return NanopbMethod(
-        id,
-        AsynchronousUnaryInvoker<AllocateSpaceFor<Request<kMethod>>()>,
-        Function{.unary_request = wrapper},
-        serde);
-  }
-
-  // Creates a NanopbMethod for a server-streaming RPC.
-  template <auto kMethod>
-  static constexpr NanopbMethod ServerStreaming(
-      uint32_t id, const NanopbMethodSerde& serde) {
-    // Define a wrapper around the user-defined function that takes the request
-    // struct as void* and a NanopbServerCall instead of the
-    // templated NanopbServerWriter class. This wrapper is stored generically in
-    // the Function union, defined below.
-    constexpr UnaryRequestFunction wrapper =
-        [](Service& service, const void* req, NanopbServerCall& writer) {
-          return CallMethodImplFunction<kMethod>(
-              service,
-              *static_cast<const Request<kMethod>*>(req),
-              static_cast<NanopbServerWriter<Response<kMethod>>&>(writer));
-        };
-    return NanopbMethod(
-        id,
-        ServerStreamingInvoker<AllocateSpaceFor<Request<kMethod>>()>,
-        Function{.unary_request = wrapper},
-        serde);
-  }
-
-  // Creates a NanopbMethod for a client-streaming RPC.
-  template <auto kMethod>
-  static constexpr NanopbMethod ClientStreaming(
-      uint32_t id, const NanopbMethodSerde& serde) {
-    constexpr StreamRequestFunction wrapper = [](Service& service,
-                                                 NanopbServerCall& reader) {
-      return CallMethodImplFunction<kMethod>(
-          service,
-          static_cast<NanopbServerReader<Request<kMethod>, Response<kMethod>>&>(
-              reader));
-    };
-    return NanopbMethod(id,
-                        ClientStreamingInvoker<Request<kMethod>>,
-                        Function{.stream_request = wrapper},
-                        serde);
-  }
-
-  // Creates a NanopbMethod for a bidirectional-streaming RPC.
-  template <auto kMethod>
-  static constexpr NanopbMethod BidirectionalStreaming(
-      uint32_t id, const NanopbMethodSerde& serde) {
-    constexpr StreamRequestFunction wrapper =
-        [](Service& service, NanopbServerCall& reader_writer) {
-          return CallMethodImplFunction<kMethod>(
-              service,
-              static_cast<NanopbServerReaderWriter<Request<kMethod>,
-                                                   Response<kMethod>>&>(
-                  reader_writer));
-        };
-    return NanopbMethod(id,
-                        BidirectionalStreamingInvoker<Request<kMethod>>,
-                        Function{.stream_request = wrapper},
-                        serde);
-  }
-
-  // Represents an invalid method. Used to reduce error message verbosity.
-  static constexpr NanopbMethod Invalid() {
-    return {0, InvalidInvoker, {}, NanopbMethodSerde(nullptr, nullptr)};
-  }
-
-  // Give access to the serializer/deserializer object for converting requests
-  // and responses between the wire format and Nanopb structs.
-  const NanopbMethodSerde& serde() const { return serde_; }
-
- private:
-  // Generic function signature for synchronous unary RPCs.
-  using SynchronousUnaryFunction = Status (*)(Service&,
-                                              const void* request,
-                                              void* response);
-
-  // Generic function signature for asynchronous unary and server streaming
-  // RPCs.
-  using UnaryRequestFunction = void (*)(Service&,
-                                        const void* request,
-                                        NanopbServerCall& writer);
-
-  // Generic function signature for client and bidirectional streaming RPCs.
-  using StreamRequestFunction = void (*)(Service&,
-                                         NanopbServerCall& reader_writer);
-
-  // The Function union stores a pointer to a generic version of the
-  // user-defined RPC function. Using a union instead of void* avoids
-  // reinterpret_cast, which keeps this class fully constexpr.
-  union Function {
-    SynchronousUnaryFunction synchronous_unary;
-    UnaryRequestFunction unary_request;
-    StreamRequestFunction stream_request;
-  };
-
-  // Allocates space for a struct. Rounds up to a reasonable minimum size to
-  // avoid generating unnecessary copies of the invoker functions.
-  template <typename T>
-  static constexpr size_t AllocateSpaceFor() {
-    return std::max(sizeof(T), cfg::kNanopbStructMinBufferSize);
-  }
-
-  constexpr NanopbMethod(uint32_t id,
-                         Invoker invoker,
-                         Function function,
-                         const NanopbMethodSerde& serde)
-      : Method(id, invoker), function_(function), serde_(serde) {}
-
-  void CallSynchronousUnary(const CallContext& context,
-                            const Packet& request,
-                            void* request_struct,
-                            void* response_struct) const
-      PW_UNLOCK_FUNCTION(rpc_lock());
-
-  void CallUnaryRequest(const CallContext& context,
-                        MethodType type,
-                        const Packet& request,
-                        void* request_struct) const
-      PW_UNLOCK_FUNCTION(rpc_lock());
-
-  // Invoker function for synchronous unary RPCs. Allocates request and response
-  // structs by size, with maximum alignment, to avoid generating unnecessary
-  // copies of this function for each request/response type.
-  template <size_t kRequestSize, size_t kResponseSize>
-  static void SynchronousUnaryInvoker(const CallContext& context,
-                                      const Packet& request)
-      PW_UNLOCK_FUNCTION(rpc_lock()) {
-    _PW_RPC_NANOPB_STRUCT_STORAGE_CLASS
-    std::aligned_storage_t<kRequestSize, alignof(std::max_align_t)>
-        request_struct{};
-    _PW_RPC_NANOPB_STRUCT_STORAGE_CLASS
-    std::aligned_storage_t<kResponseSize, alignof(std::max_align_t)>
-        response_struct{};
-
-    static_cast<const NanopbMethod&>(context.method())
-        .CallSynchronousUnary(
-            context, request, &request_struct, &response_struct);
-  }
-
-  // Invoker function for asynchronous unary RPCs. Allocates space for a request
-  // struct. Ignores the payload buffer since resposnes are sent through the
-  // NanopbUnaryResponder.
-  template <size_t kRequestSize>
-  static void AsynchronousUnaryInvoker(const CallContext& context,
-                                       const Packet& request)
-      PW_UNLOCK_FUNCTION(rpc_lock()) {
-    _PW_RPC_NANOPB_STRUCT_STORAGE_CLASS
-    std::aligned_storage_t<kRequestSize, alignof(std::max_align_t)>
-        request_struct{};
-
-    static_cast<const NanopbMethod&>(context.method())
-        .CallUnaryRequest(
-            context, MethodType::kUnary, request, &request_struct);
-  }
-
-  // Invoker function for server streaming RPCs. Allocates space for a request
-  // struct. Ignores the payload buffer since resposnes are sent through the
-  // NanopbServerWriter.
-  template <size_t kRequestSize>
-  static void ServerStreamingInvoker(const CallContext& context,
-                                     const Packet& request)
-      PW_UNLOCK_FUNCTION(rpc_lock()) {
-    _PW_RPC_NANOPB_STRUCT_STORAGE_CLASS
-    std::aligned_storage_t<kRequestSize, alignof(std::max_align_t)>
-        request_struct{};
-
-    static_cast<const NanopbMethod&>(context.method())
-        .CallUnaryRequest(
-            context, MethodType::kServerStreaming, request, &request_struct);
-  }
-
-  // Invoker function for client streaming RPCs.
-  template <typename Request>
-  static void ClientStreamingInvoker(const CallContext& context, const Packet&)
-      PW_UNLOCK_FUNCTION(rpc_lock()) {
-    BaseNanopbServerReader<Request> reader(context,
-                                           MethodType::kClientStreaming);
-    rpc_lock().unlock();
-    static_cast<const NanopbMethod&>(context.method())
-        .function_.stream_request(context.service(), reader);
-  }
-
-  // Invoker function for bidirectional streaming RPCs.
-  template <typename Request>
-  static void BidirectionalStreamingInvoker(const CallContext& context,
-                                            const Packet&)
-      PW_UNLOCK_FUNCTION(rpc_lock()) {
-    BaseNanopbServerReader<Request> reader_writer(
-        context, MethodType::kBidirectionalStreaming);
-    rpc_lock().unlock();
-    static_cast<const NanopbMethod&>(context.method())
-        .function_.stream_request(context.service(), reader_writer);
-  }
-
-  // Decodes a request protobuf with Nanopb to the provided buffer. Sends an
-  // error packet if the request failed to decode.
-  bool DecodeRequest(const CallContext& context,
-                     const Packet& request,
-                     void* proto_struct) const
-      PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock());
-
-  // Stores the user-defined RPC in a generic wrapper.
-  Function function_;
-
-  // Serde used to encode and decode Nanopb structs.
-  const NanopbMethodSerde& serde_;
-};
-
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/nanopb/public/pw_rpc/nanopb/internal/method_union.h b/pw_rpc/nanopb/public/pw_rpc/nanopb/internal/method_union.h
deleted file mode 100644
index aa2a672..0000000
--- a/pw_rpc/nanopb/public/pw_rpc/nanopb/internal/method_union.h
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_bytes/span.h"
-#include "pw_rpc/internal/method_union.h"
-#include "pw_rpc/nanopb/internal/common.h"
-#include "pw_rpc/nanopb/internal/method.h"
-#include "pw_rpc/raw/internal/method_union.h"
-
-namespace pw::rpc::internal {
-
-// Method union which holds either a nanopb or a raw method.
-class NanopbMethodUnion : public MethodUnion {
- public:
-  constexpr NanopbMethodUnion(RawMethod&& method)
-      : impl_({.raw = std::move(method)}) {}
-  constexpr NanopbMethodUnion(NanopbMethod&& method)
-      : impl_({.nanopb = std::move(method)}) {}
-
-  constexpr const Method& method() const { return impl_.method; }
-  constexpr const RawMethod& raw_method() const { return impl_.raw; }
-  constexpr const NanopbMethod& nanopb_method() const { return impl_.nanopb; }
-
- private:
-  union {
-    Method method;
-    RawMethod raw;
-    NanopbMethod nanopb;
-  } impl_;
-};
-
-// Returns either a raw or nanopb method object, depending on the implemented
-// function's signature.
-template <auto kMethod, MethodType kType, typename Request, typename Response>
-constexpr auto GetNanopbOrRawMethodFor(
-    uint32_t id, [[maybe_unused]] const NanopbMethodSerde& serde) {
-  if constexpr (RawMethod::matches<kMethod>()) {
-    return GetMethodFor<kMethod, RawMethod, kType>(id);
-  } else if constexpr (NanopbMethod::matches<kMethod, Request, Response>()) {
-    return GetMethodFor<kMethod, NanopbMethod, kType>(id, serde);
-  } else {
-    return InvalidMethod<kMethod, kType, RawMethod>(id);
-  }
-};
-
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/nanopb/public/pw_rpc/nanopb/server_reader_writer.h b/pw_rpc/nanopb/public/pw_rpc/nanopb/server_reader_writer.h
deleted file mode 100644
index b52c25f..0000000
--- a/pw_rpc/nanopb/public/pw_rpc/nanopb/server_reader_writer.h
+++ /dev/null
@@ -1,385 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// This file defines the ServerReaderWriter, ServerReader, and ServerWriter
-// classes for the Nanopb RPC interface. These classes are used for
-// bidirectional, client, and server streaming RPCs.
-#pragma once
-
-#include "pw_bytes/span.h"
-#include "pw_rpc/channel.h"
-#include "pw_rpc/internal/lock.h"
-#include "pw_rpc/internal/method_info.h"
-#include "pw_rpc/internal/method_lookup.h"
-#include "pw_rpc/internal/server_call.h"
-#include "pw_rpc/nanopb/internal/common.h"
-#include "pw_rpc/server.h"
-
-namespace pw::rpc {
-namespace internal {
-
-// Forward declarations for internal classes needed in friend statements.
-class NanopbMethod;
-
-namespace test {
-
-template <typename, typename, uint32_t>
-class InvocationContext;
-
-}  // namespace test
-
-class NanopbServerCall : public internal::ServerCall {
- public:
-  constexpr NanopbServerCall() : serde_(nullptr) {}
-
-  NanopbServerCall(const CallContext& context, MethodType type);
-
-  Status SendUnaryResponse(const void* payload, Status status)
-      PW_LOCKS_EXCLUDED(rpc_lock()) {
-    return SendFinalResponse(*this, payload, status);
-  }
-
-  const NanopbMethodSerde& serde() const { return *serde_; }
-
- protected:
-  NanopbServerCall(NanopbServerCall&& other) PW_LOCKS_EXCLUDED(rpc_lock()) {
-    *this = std::move(other);
-  }
-
-  NanopbServerCall& operator=(NanopbServerCall&& other)
-      PW_LOCKS_EXCLUDED(rpc_lock()) {
-    internal::LockGuard lock(internal::rpc_lock());
-    MoveNanopbServerCallFrom(other);
-    return *this;
-  }
-
-  void MoveNanopbServerCallFrom(NanopbServerCall& other)
-      PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    MoveServerCallFrom(other);
-    serde_ = other.serde_;
-  }
-
-  Status SendServerStream(const void* payload) PW_LOCKS_EXCLUDED(rpc_lock());
-
-  bool DecodeRequest(ConstByteSpan payload, void* request_struct) const {
-    return serde_->DecodeRequest(payload, request_struct);
-  }
-
- private:
-  const NanopbMethodSerde* serde_;
-};
-
-// The BaseNanopbServerReader serves as the base for the ServerReader and
-// ServerReaderWriter classes. It adds a callback templated on the request
-// struct type. It is templated on the Request type only.
-template <typename Request>
-class BaseNanopbServerReader : public NanopbServerCall {
- public:
-  BaseNanopbServerReader(const internal::CallContext& context, MethodType type)
-      : NanopbServerCall(context, type) {}
-
- protected:
-  constexpr BaseNanopbServerReader() = default;
-
-  BaseNanopbServerReader(BaseNanopbServerReader&& other)
-      PW_LOCKS_EXCLUDED(rpc_lock()) {
-    *this = std::move(other);
-  }
-
-  BaseNanopbServerReader& operator=(BaseNanopbServerReader&& other)
-      PW_LOCKS_EXCLUDED(rpc_lock()) {
-    internal::LockGuard lock(internal::rpc_lock());
-    MoveNanopbServerCallFrom(other);
-    set_on_next_locked(std::move(other.nanopb_on_next_));
-    return *this;
-  }
-
-  void set_on_next(Function<void(const Request& request)>&& on_next)
-      PW_LOCKS_EXCLUDED(rpc_lock()) {
-    internal::LockGuard lock(internal::rpc_lock());
-    set_on_next_locked(std::move(on_next));
-  }
-
- private:
-  void set_on_next_locked(Function<void(const Request& request)>&& on_next)
-      PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    nanopb_on_next_ = std::move(on_next);
-
-    internal::Call::set_on_next_locked([this](ConstByteSpan payload) {
-      if (nanopb_on_next_) {
-        Request request_struct{};
-        if (DecodeRequest(payload, &request_struct)) {
-          nanopb_on_next_(request_struct);
-        }
-      }
-    });
-  }
-
-  Function<void(const Request&)> nanopb_on_next_;
-};
-
-}  // namespace internal
-
-// The NanopbServerReaderWriter is used to send and receive messages in a Nanopb
-// bidirectional streaming RPC.
-//
-// These classes use private inheritance to hide the internal::Call API while
-// allow direct use of its public and protected functions.
-template <typename Request, typename Response>
-class NanopbServerReaderWriter
-    : private internal::BaseNanopbServerReader<Request> {
- public:
-  // Creates a NanopbServerReaderWriter that is ready to send responses for a
-  // particular RPC. This can be used for testing or to send responses to an RPC
-  // that has not been started by a client.
-  template <auto kMethod, typename ServiceImpl>
-  [[nodiscard]] static NanopbServerReaderWriter Open(Server& server,
-                                                     uint32_t channel_id,
-                                                     ServiceImpl& service) {
-    using Info = internal::MethodInfo<kMethod>;
-    static_assert(std::is_same_v<Request, typename Info::Request>,
-                  "The request type of a NanopbServerReaderWriter must match "
-                  "the method.");
-    static_assert(std::is_same_v<Response, typename Info::Response>,
-                  "The response type of a NanopbServerReaderWriter must match "
-                  "the method.");
-    internal::LockGuard lock(internal::rpc_lock());
-    return {server.OpenContext<kMethod, MethodType::kBidirectionalStreaming>(
-        channel_id,
-        service,
-        internal::MethodLookup::GetNanopbMethod<ServiceImpl,
-                                                Info::kMethodId>())};
-  }
-
-  constexpr NanopbServerReaderWriter() = default;
-
-  NanopbServerReaderWriter(NanopbServerReaderWriter&&) = default;
-  NanopbServerReaderWriter& operator=(NanopbServerReaderWriter&&) = default;
-
-  using internal::Call::active;
-  using internal::Call::channel_id;
-
-  // Writes a response struct. Returns the following Status codes:
-  //
-  //   OK - the response was successfully sent
-  //   FAILED_PRECONDITION - the writer is closed
-  //   INTERNAL - pw_rpc was unable to encode the Nanopb protobuf
-  //   other errors - the ChannelOutput failed to send the packet; the error
-  //       codes are determined by the ChannelOutput implementation
-  //
-  Status Write(const Response& response) {
-    return internal::NanopbServerCall::SendServerStream(&response);
-  }
-
-  Status Finish(Status status = OkStatus()) {
-    return internal::Call::CloseAndSendResponse(status);
-  }
-
-  // Functions for setting RPC event callbacks.
-  using internal::Call::set_on_error;
-  using internal::ServerCall::set_on_client_stream_end;
-  using internal::BaseNanopbServerReader<Request>::set_on_next;
-
- private:
-  friend class internal::NanopbMethod;
-
-  template <typename, typename, uint32_t>
-  friend class internal::test::InvocationContext;
-
-  NanopbServerReaderWriter(const internal::CallContext& context)
-      : internal::BaseNanopbServerReader<Request>(
-            context, MethodType::kBidirectionalStreaming) {}
-};
-
-// The NanopbServerReader is used to receive messages and send a response in a
-// Nanopb client streaming RPC.
-template <typename Request, typename Response>
-class NanopbServerReader : private internal::BaseNanopbServerReader<Request> {
- public:
-  // Creates a NanopbServerReader that is ready to send a response to a
-  // particular RPC. This can be used for testing or to finish an RPC that has
-  // not been started by the client.
-  template <auto kMethod, typename ServiceImpl>
-  [[nodiscard]] static NanopbServerReader Open(Server& server,
-                                               uint32_t channel_id,
-                                               ServiceImpl& service) {
-    using Info = internal::MethodInfo<kMethod>;
-    static_assert(
-        std::is_same_v<Request, typename Info::Request>,
-        "The request type of a NanopbServerReader must match the method.");
-    static_assert(
-        std::is_same_v<Response, typename Info::Response>,
-        "The response type of a NanopbServerReader must match the method.");
-    internal::LockGuard lock(internal::rpc_lock());
-    return {server.OpenContext<kMethod, MethodType::kClientStreaming>(
-        channel_id,
-        service,
-        internal::MethodLookup::GetNanopbMethod<ServiceImpl,
-                                                Info::kMethodId>())};
-  }
-
-  // Allow default construction so that users can declare a variable into which
-  // to move NanopbServerReaders from RPC calls.
-  constexpr NanopbServerReader() = default;
-
-  NanopbServerReader(NanopbServerReader&&) = default;
-  NanopbServerReader& operator=(NanopbServerReader&&) = default;
-
-  using internal::Call::active;
-  using internal::Call::channel_id;
-
-  // Functions for setting RPC event callbacks.
-  using internal::Call::set_on_error;
-  using internal::ServerCall::set_on_client_stream_end;
-  using internal::BaseNanopbServerReader<Request>::set_on_next;
-
-  Status Finish(const Response& response, Status status = OkStatus()) {
-    return internal::NanopbServerCall::SendUnaryResponse(&response, status);
-  }
-
- private:
-  friend class internal::NanopbMethod;
-
-  template <typename, typename, uint32_t>
-  friend class internal::test::InvocationContext;
-
-  NanopbServerReader(const internal::CallContext& context)
-      : internal::BaseNanopbServerReader<Request>(
-            context, MethodType::kClientStreaming) {}
-};
-
-// The NanopbServerWriter is used to send responses in a Nanopb server streaming
-// RPC.
-template <typename Response>
-class NanopbServerWriter : private internal::NanopbServerCall {
- public:
-  // Creates a NanopbServerWriter that is ready to send responses for a
-  // particular RPC. This can be used for testing or to send responses to an RPC
-  // that has not been started by a client.
-  template <auto kMethod, typename ServiceImpl>
-  [[nodiscard]] static NanopbServerWriter Open(Server& server,
-                                               uint32_t channel_id,
-                                               ServiceImpl& service) {
-    using Info = internal::MethodInfo<kMethod>;
-    static_assert(
-        std::is_same_v<Response, typename Info::Response>,
-        "The response type of a NanopbServerWriter must match the method.");
-    internal::LockGuard lock(internal::rpc_lock());
-    return {server.OpenContext<kMethod, MethodType::kServerStreaming>(
-        channel_id,
-        service,
-        internal::MethodLookup::GetNanopbMethod<ServiceImpl,
-                                                Info::kMethodId>())};
-  }
-
-  // Allow default construction so that users can declare a variable into which
-  // to move ServerWriters from RPC calls.
-  constexpr NanopbServerWriter() = default;
-
-  NanopbServerWriter(NanopbServerWriter&&) = default;
-  NanopbServerWriter& operator=(NanopbServerWriter&&) = default;
-
-  using internal::Call::active;
-  using internal::Call::channel_id;
-
-  // Writes a response struct. Returns the following Status codes:
-  //
-  //   OK - the response was successfully sent
-  //   FAILED_PRECONDITION - the writer is closed
-  //   INTERNAL - pw_rpc was unable to encode the Nanopb protobuf
-  //   other errors - the ChannelOutput failed to send the packet; the error
-  //       codes are determined by the ChannelOutput implementation
-  //
-  Status Write(const Response& response) {
-    return internal::NanopbServerCall::SendServerStream(&response);
-  }
-
-  Status Finish(Status status = OkStatus()) {
-    return internal::Call::CloseAndSendResponse(status);
-  }
-
-  using internal::Call::set_on_error;
-  using internal::ServerCall::set_on_client_stream_end;
-
- private:
-  friend class internal::NanopbMethod;
-
-  template <typename, typename, uint32_t>
-  friend class internal::test::InvocationContext;
-
-  NanopbServerWriter(const internal::CallContext& context)
-      : internal::NanopbServerCall(context, MethodType::kServerStreaming) {}
-};
-
-template <typename Response>
-class NanopbUnaryResponder : private internal::NanopbServerCall {
- public:
-  // Creates a NanopbUnaryResponder that is ready to send a response for a
-  // particular RPC. This can be used for testing or to send responses to an RPC
-  // that has not been started by a client.
-  template <auto kMethod, typename ServiceImpl>
-  [[nodiscard]] static NanopbUnaryResponder Open(Server& server,
-                                                 uint32_t channel_id,
-                                                 ServiceImpl& service) {
-    using Info = internal::MethodInfo<kMethod>;
-    static_assert(
-        std::is_same_v<Response, typename Info::Response>,
-        "The response type of a NanopbUnaryResponder must match the method.");
-    internal::LockGuard lock(internal::rpc_lock());
-    return {server.OpenContext<kMethod, MethodType::kUnary>(
-        channel_id,
-        service,
-        internal::MethodLookup::GetNanopbMethod<ServiceImpl,
-                                                Info::kMethodId>())};
-  }
-
-  // Allow default construction so that users can declare a variable into which
-  // to move ServerWriters from RPC calls.
-  constexpr NanopbUnaryResponder() = default;
-
-  NanopbUnaryResponder(NanopbUnaryResponder&&) = default;
-  NanopbUnaryResponder& operator=(NanopbUnaryResponder&&) = default;
-
-  using internal::Call::active;
-  using internal::Call::channel_id;
-
-  // Sends the response. Returns the following Status codes:
-  //
-  //   OK - the response was successfully sent
-  //   FAILED_PRECONDITION - the writer is closed
-  //   INTERNAL - pw_rpc was unable to encode the Nanopb protobuf
-  //   other errors - the ChannelOutput failed to send the packet; the error
-  //       codes are determined by the ChannelOutput implementation
-  //
-  Status Finish(const Response& response, Status status = OkStatus()) {
-    return internal::NanopbServerCall::SendUnaryResponse(&response, status);
-  }
-
-  using internal::Call::set_on_error;
-  using internal::ServerCall::set_on_client_stream_end;
-
- private:
-  friend class internal::NanopbMethod;
-
-  template <typename, typename, uint32_t>
-  friend class internal::test::InvocationContext;
-
-  NanopbUnaryResponder(const internal::CallContext& context)
-      : internal::NanopbServerCall(context, MethodType::kUnary) {}
-};
-// TODO(hepler): "pw::rpc::ServerWriter" should not be specific to Nanopb.
-template <typename T>
-using ServerWriter = NanopbServerWriter<T>;
-
-}  // namespace pw::rpc
diff --git a/pw_rpc/nanopb/public/pw_rpc/nanopb/test_method_context.h b/pw_rpc/nanopb/public/pw_rpc/nanopb/test_method_context.h
deleted file mode 100644
index 972cb62..0000000
--- a/pw_rpc/nanopb/public/pw_rpc/nanopb/test_method_context.h
+++ /dev/null
@@ -1,391 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <utility>
-
-#include "pw_assert/assert.h"
-#include "pw_bytes/span.h"
-#include "pw_containers/vector.h"
-#include "pw_preprocessor/arguments.h"
-#include "pw_rpc/internal/hash.h"
-#include "pw_rpc/internal/method_lookup.h"
-#include "pw_rpc/internal/test_method_context.h"
-#include "pw_rpc/nanopb/fake_channel_output.h"
-#include "pw_rpc/nanopb/internal/method.h"
-
-namespace pw::rpc {
-
-// Declares a context object that may be used to invoke an RPC. The context is
-// declared with the name of the implemented service and the method to invoke.
-// The RPC can then be invoked with the call method.
-//
-// For a unary RPC, context.call(request) returns the status, and the response
-// struct can be accessed via context.response().
-//
-//   PW_NANOPB_TEST_METHOD_CONTEXT(my::CoolService, TheMethod) context;
-//   EXPECT_EQ(OkStatus(), context.call({.some_arg = 123}));
-//   EXPECT_EQ(500, context.response().some_response_value);
-//
-// For a unary RPC with repeated fields in the response, nanopb uses a
-// pb_callback_t field called when parsing the response as many times as the
-// field is present in the protobuf. To set the pb_callback_t fields create the
-// Response struct and pass it to the response method:
-//
-//   PW_NANOPB_TEST_METHOD_CONTEXT(my::CoolService, TheMethod) context;
-//   EXPECT_EQ(OkStatus(), context.call({.some_arg = 123}));
-//
-//   TheMethodResponse response{};
-//   response.repeated_field.funcs.decode = +[](pb_istream_t* stream,
-//                                              const pb_field_iter_t* field,
-//                                              void** arg) -> bool {
-//     ... decode the field from stream with pb_decode* functions ...
-//     EXPECT_EQ(submsg.some_field, 123);
-//     return true;
-//   };
-//   response.repeated_field.arg = &some_context_passed_decode_if_needed;
-//   context.response(response);  // Callbacks called from here.
-//
-// For a server streaming RPC, context.call(request) invokes the method. As in a
-// normal RPC, the method completes when the ServerWriter's Finish method is
-// called (or it goes out of scope).
-//
-//   PW_NANOPB_TEST_METHOD_CONTEXT(my::CoolService, TheStreamingMethod) context;
-//   context.call({.some_arg = 123});
-//
-//   EXPECT_TRUE(context.done());  // Check that the RPC completed
-//   EXPECT_EQ(OkStatus(), context.status());  // Check the status
-//
-//   EXPECT_EQ(3u, context.responses().size());
-//   EXPECT_EQ(123, context.responses()[0].value); // check individual responses
-//
-//   for (const MyResponse& response : context.responses()) {
-//     // iterate over the responses
-//   }
-//
-// PW_NANOPB_TEST_METHOD_CONTEXT forwards its constructor arguments to the
-// underlying serivce. For example:
-//
-//   PW_NANOPB_TEST_METHOD_CONTEXT(MyService, Go) context(service, args);
-//
-// PW_NANOPB_TEST_METHOD_CONTEXT takes one optional argument:
-//
-//   size_t kMaxPackets: maximum packets to store
-//
-// Example:
-//
-//   PW_NANOPB_TEST_METHOD_CONTEXT(MyService, BestMethod, 3, 256) context;
-//   ASSERT_EQ(3u, context.responses().max_size());
-//
-#define PW_NANOPB_TEST_METHOD_CONTEXT(service, method, ...)             \
-  ::pw::rpc::NanopbTestMethodContext<service,                           \
-                                     &service::method,                  \
-                                     ::pw::rpc::internal::Hash(#method) \
-                                         PW_COMMA_ARGS(__VA_ARGS__)>
-template <typename Service,
-          auto kMethod,
-          uint32_t kMethodId,
-          size_t kMaxPackets = 6,
-          size_t kPayloadsBufferSizeBytes = 256>
-class NanopbTestMethodContext;
-
-// Internal classes that implement NanopbTestMethodContext.
-namespace internal::test::nanopb {
-
-// Collects everything needed to invoke a particular RPC.
-template <typename Service,
-          auto kMethod,
-          uint32_t kMethodId,
-          size_t kMaxPackets,
-          size_t kPayloadsBufferSizeBytes>
-class NanopbInvocationContext
-    : public InvocationContext<
-          NanopbFakeChannelOutput<kMaxPackets, kPayloadsBufferSizeBytes>,
-          Service,
-          kMethodId> {
- public:
-  using Request = internal::Request<kMethod>;
-  using Response = internal::Response<kMethod>;
-
-  // Gives access to the RPC's most recent response.
-  Response response() const {
-    Response response{};
-    PW_ASSERT(kMethodInfo.serde().DecodeResponse(Base::responses().back(),
-                                                 &response));
-    return response;
-  }
-
-  // Gives access to the RPC's most recent response using pased Response object
-  // to parse the nanopb. Use this version when you need to set pb_callback_t
-  // fields in the Response object before parsing.
-  void response(Response& response) const {
-    PW_ASSERT(kMethodInfo.serde().DecodeResponse(Base::responses().back(),
-                                                 &response));
-  }
-
-  NanopbPayloadsView<Response> responses() const {
-    return Base::output().template payload_structs<Response>(
-        kMethodInfo.serde().response(),
-        MethodTraits<decltype(kMethod)>::kType,
-        Base::channel_id(),
-        Base::service().id(),
-        kMethodId);
-  }
-
- protected:
-  template <typename... Args>
-  NanopbInvocationContext(Args&&... args)
-      : Base(kMethodInfo,
-             MethodTraits<decltype(kMethod)>::kType,
-             std::forward<Args>(args)...) {}
-
-  template <size_t kEncodingBufferSizeBytes = 128>
-  void SendClientStream(const Request& request) PW_LOCKS_EXCLUDED(rpc_lock()) {
-    std::array<std::byte, kEncodingBufferSizeBytes> buffer;
-    Base::SendClientStream(std::span(buffer).first(
-        kMethodInfo.serde().EncodeRequest(&request, buffer).size()));
-  }
-
- private:
-  using Base = InvocationContext<
-      NanopbFakeChannelOutput<kMaxPackets, kPayloadsBufferSizeBytes>,
-      Service,
-      kMethodId>;
-
-  static constexpr NanopbMethod kMethodInfo =
-      MethodLookup::GetNanopbMethod<Service, kMethodId>();
-};
-
-// Method invocation context for a unary RPC. Returns the status in
-// call_context() and provides the response through the response() method.
-template <typename Service,
-          auto kMethod,
-          uint32_t kMethodId,
-          size_t kPayloadsBufferSizeBytes>
-class UnaryContext : public NanopbInvocationContext<Service,
-                                                    kMethod,
-                                                    kMethodId,
-                                                    1,
-                                                    kPayloadsBufferSizeBytes> {
- private:
-  using Base = NanopbInvocationContext<Service,
-                                       kMethod,
-                                       kMethodId,
-                                       1,
-                                       kPayloadsBufferSizeBytes>;
-
- public:
-  using Request = typename Base::Request;
-  using Response = typename Base::Response;
-
-  template <typename... Args>
-  UnaryContext(Args&&... args) : Base(std::forward<Args>(args)...) {}
-
-  // Invokes the RPC with the provided request. Returns the status.
-  auto call(const Request& request) {
-    if constexpr (MethodTraits<decltype(kMethod)>::kSynchronous) {
-      Base::output().clear();
-
-      NanopbUnaryResponder<Response> responder =
-          Base::template GetResponder<NanopbUnaryResponder<Response>>();
-      Response response = {};
-      Status status =
-          CallMethodImplFunction<kMethod>(Base::service(), request, response);
-      PW_ASSERT(responder.Finish(response, status).ok());
-      return status;
-
-    } else {
-      Base::template call<kMethod, NanopbUnaryResponder<Response>>(request);
-    }
-  }
-};
-
-// Method invocation context for a server streaming RPC.
-template <typename Service,
-          auto kMethod,
-          uint32_t kMethodId,
-          size_t kMaxPackets,
-          size_t kPayloadsBufferSizeBytes>
-class ServerStreamingContext
-    : public NanopbInvocationContext<Service,
-                                     kMethod,
-                                     kMethodId,
-                                     kMaxPackets,
-                                     kPayloadsBufferSizeBytes> {
- private:
-  using Base = NanopbInvocationContext<Service,
-                                       kMethod,
-                                       kMethodId,
-                                       kMaxPackets,
-                                       kPayloadsBufferSizeBytes>;
-
- public:
-  using Request = typename Base::Request;
-  using Response = typename Base::Response;
-
-  template <typename... Args>
-  ServerStreamingContext(Args&&... args) : Base(std::forward<Args>(args)...) {}
-
-  // Invokes the RPC with the provided request.
-  void call(const Request& request) {
-    Base::template call<kMethod, NanopbServerWriter<Response>>(request);
-  }
-
-  // Returns a server writer which writes responses into the context's buffer.
-  // This should not be called alongside call(); use one or the other.
-  NanopbServerWriter<Response> writer() {
-    return Base::template GetResponder<NanopbServerWriter<Response>>();
-  }
-};
-
-// Method invocation context for a client streaming RPC.
-template <typename Service,
-          auto kMethod,
-          uint32_t kMethodId,
-          size_t kMaxPackets,
-          size_t kPayloadsBufferSizeBytes>
-class ClientStreamingContext
-    : public NanopbInvocationContext<Service,
-                                     kMethod,
-                                     kMethodId,
-                                     kMaxPackets,
-                                     kPayloadsBufferSizeBytes> {
- private:
-  using Base = NanopbInvocationContext<Service,
-                                       kMethod,
-                                       kMethodId,
-                                       kMaxPackets,
-                                       kPayloadsBufferSizeBytes>;
-
- public:
-  using Request = typename Base::Request;
-  using Response = typename Base::Response;
-
-  template <typename... Args>
-  ClientStreamingContext(Args&&... args) : Base(std::forward<Args>(args)...) {}
-
-  // Invokes the RPC.
-  void call() {
-    Base::template call<kMethod, NanopbServerReader<Request, Response>>();
-  }
-
-  // Returns a server reader which writes responses into the context's buffer.
-  // This should not be called alongside call(); use one or the other.
-  NanopbServerReader<Request, Response> reader() {
-    return Base::template GetResponder<NanopbServerReader<Request, Response>>();
-  }
-
-  // Allow sending client streaming packets.
-  using Base::SendClientStream;
-  using Base::SendClientStreamEnd;
-};
-
-// Method invocation context for a bidirectional streaming RPC.
-template <typename Service,
-          auto kMethod,
-          uint32_t kMethodId,
-          size_t kMaxPackets,
-          size_t kPayloadsBufferSizeBytes>
-class BidirectionalStreamingContext
-    : public NanopbInvocationContext<Service,
-                                     kMethod,
-                                     kMethodId,
-                                     kMaxPackets,
-                                     kPayloadsBufferSizeBytes> {
- private:
-  using Base = NanopbInvocationContext<Service,
-                                       kMethod,
-                                       kMethodId,
-                                       kMaxPackets,
-                                       kPayloadsBufferSizeBytes>;
-
- public:
-  using Request = typename Base::Request;
-  using Response = typename Base::Response;
-
-  template <typename... Args>
-  BidirectionalStreamingContext(Args&&... args)
-      : Base(std::forward<Args>(args)...) {}
-
-  // Invokes the RPC.
-  void call() {
-    Base::template call<kMethod, NanopbServerReaderWriter<Request, Response>>();
-  }
-
-  // Returns a server reader which writes responses into the context's buffer.
-  // This should not be called alongside call(); use one or the other.
-  NanopbServerReaderWriter<Request, Response> reader_writer() {
-    return Base::template GetResponder<
-        NanopbServerReaderWriter<Request, Response>>();
-  }
-
-  // Allow sending client streaming packets.
-  using Base::SendClientStream;
-  using Base::SendClientStreamEnd;
-};
-
-// Alias to select the type of the context object to use based on which type of
-// RPC it is for.
-template <typename Service,
-          auto kMethod,
-          uint32_t kMethodId,
-          size_t kMaxPackets,
-          size_t kPayloadsBufferSizeBytes>
-using Context = std::tuple_element_t<
-    static_cast<size_t>(internal::MethodTraits<decltype(kMethod)>::kType),
-    std::tuple<
-        UnaryContext<Service, kMethod, kMethodId, kPayloadsBufferSizeBytes>,
-        ServerStreamingContext<Service,
-                               kMethod,
-                               kMethodId,
-                               kMaxPackets,
-                               kPayloadsBufferSizeBytes>,
-        ClientStreamingContext<Service,
-                               kMethod,
-                               kMethodId,
-                               kMaxPackets,
-                               kPayloadsBufferSizeBytes>,
-        BidirectionalStreamingContext<Service,
-                                      kMethod,
-                                      kMethodId,
-                                      kMaxPackets,
-                                      kPayloadsBufferSizeBytes>>>;
-
-}  // namespace internal::test::nanopb
-
-template <typename Service,
-          auto kMethod,
-          uint32_t kMethodId,
-          size_t kMaxPackets,
-          size_t kPayloadsBufferSizeBytes>
-class NanopbTestMethodContext
-    : public internal::test::nanopb::Context<Service,
-                                             kMethod,
-                                             kMethodId,
-                                             kMaxPackets,
-                                             kPayloadsBufferSizeBytes> {
- public:
-  // Forwards constructor arguments to the service class.
-  template <typename... ServiceArgs>
-  NanopbTestMethodContext(ServiceArgs&&... service_args)
-      : internal::test::nanopb::Context<Service,
-                                        kMethod,
-                                        kMethodId,
-                                        kMaxPackets,
-                                        kPayloadsBufferSizeBytes>(
-            std::forward<ServiceArgs>(service_args)...) {}
-};
-
-}  // namespace pw::rpc
diff --git a/pw_rpc/nanopb/public/pw_rpc/nanopb_client_call.h b/pw_rpc/nanopb/public/pw_rpc/nanopb_client_call.h
new file mode 100644
index 0000000..44e8475
--- /dev/null
+++ b/pw_rpc/nanopb/public/pw_rpc/nanopb_client_call.h
@@ -0,0 +1,185 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 <new>
+
+#include "pw_bytes/span.h"
+#include "pw_rpc/internal/base_client_call.h"
+#include "pw_rpc/internal/method_type.h"
+#include "pw_rpc/internal/nanopb_common.h"
+#include "pw_status/status.h"
+
+namespace pw::rpc {
+
+// Response handler callback for unary RPC methods.
+template <typename Response>
+class UnaryResponseHandler {
+ public:
+  virtual ~UnaryResponseHandler() = default;
+
+  // Called when the response is received from the server with the method's
+  // status and the deserialized response struct.
+  virtual void ReceivedResponse(Status status, const Response& response) = 0;
+
+  // Called when an error occurs internally in the RPC client or server.
+  virtual void RpcError(Status) {}
+};
+
+// Response handler callbacks for server streaming RPC methods.
+template <typename Response>
+class ServerStreamingResponseHandler {
+ public:
+  virtual ~ServerStreamingResponseHandler() = default;
+
+  // Called on every response received from the server with the deserialized
+  // response struct.
+  virtual void ReceivedResponse(const Response& response) = 0;
+
+  // Called when the server ends the stream with the overall RPC status.
+  virtual void Complete(Status status) = 0;
+
+  // Called when an error occurs internally in the RPC client or server.
+  virtual void RpcError(Status) {}
+};
+
+namespace internal {
+
+// Non-templated nanopb base class providing protobuf encoding and decoding.
+class BaseNanopbClientCall : public BaseClientCall {
+ public:
+  Status SendRequest(const void* request_struct);
+
+ protected:
+  constexpr BaseNanopbClientCall(
+      rpc::Channel* channel,
+      uint32_t service_id,
+      uint32_t method_id,
+      ResponseHandler handler,
+      internal::NanopbMessageDescriptor request_fields,
+      internal::NanopbMessageDescriptor response_fields)
+      : BaseClientCall(channel, service_id, method_id, handler),
+        serde_(request_fields, response_fields) {}
+
+  constexpr const internal::NanopbMethodSerde& serde() const { return serde_; }
+
+ private:
+  internal::NanopbMethodSerde serde_;
+};
+
+template <typename Callback>
+struct CallbackTraits {};
+
+template <typename ResponseType>
+struct CallbackTraits<UnaryResponseHandler<ResponseType>> {
+  using Response = ResponseType;
+
+  static constexpr MethodType kType = MethodType::kUnary;
+};
+
+template <typename ResponseType>
+struct CallbackTraits<ServerStreamingResponseHandler<ResponseType>> {
+  using Response = ResponseType;
+
+  static constexpr MethodType kType = MethodType::kServerStreaming;
+};
+
+}  // namespace internal
+
+template <typename Callback>
+class NanopbClientCall : public internal::BaseNanopbClientCall {
+ public:
+  constexpr NanopbClientCall(Channel* channel,
+                             uint32_t service_id,
+                             uint32_t method_id,
+                             Callback& callback,
+                             internal::NanopbMessageDescriptor request_fields,
+                             internal::NanopbMessageDescriptor response_fields)
+      : BaseNanopbClientCall(channel,
+                             service_id,
+                             method_id,
+                             &ResponseHandler,
+                             request_fields,
+                             response_fields),
+        callback_(callback) {}
+
+ private:
+  using Traits = internal::CallbackTraits<Callback>;
+  using Response = typename Traits::Response;
+
+  // Buffer into which the nanopb struct is decoded. Its contents are unknown,
+  // so it is aligned to maximum alignment to accommodate any type.
+  using ResponseBuffer =
+      std::aligned_storage_t<sizeof(Response), alignof(std::max_align_t)>;
+
+  friend class Client;
+
+  static void ResponseHandler(internal::BaseClientCall& call,
+                              const internal::Packet& packet) {
+    static_cast<NanopbClientCall<Callback>&>(call).HandleResponse(packet);
+  }
+
+  void HandleResponse(const internal::Packet& packet) {
+    if constexpr (Traits::kType == internal::MethodType::kUnary) {
+      InvokeUnaryCallback(packet);
+    }
+    if constexpr (Traits::kType == internal::MethodType::kServerStreaming) {
+      InvokeServerStreamingCallback(packet);
+    }
+  }
+
+  void InvokeUnaryCallback(const internal::Packet& packet) {
+    if (packet.type() == internal::PacketType::SERVER_ERROR) {
+      callback_.RpcError(packet.status());
+      return;
+    }
+
+    ResponseBuffer response_struct{};
+
+    if (serde().DecodeResponse(&response_struct, packet.payload())) {
+      callback_.ReceivedResponse(
+          packet.status(),
+          *std::launder(reinterpret_cast<Response*>(&response_struct)));
+    } else {
+      callback_.RpcError(Status::DataLoss());
+    }
+
+    Unregister();
+  }
+
+  void InvokeServerStreamingCallback(const internal::Packet& packet) {
+    if (packet.type() == internal::PacketType::SERVER_ERROR) {
+      callback_.RpcError(packet.status());
+      return;
+    }
+
+    if (packet.type() == internal::PacketType::SERVER_STREAM_END) {
+      callback_.Complete(packet.status());
+      return;
+    }
+
+    ResponseBuffer response_struct{};
+
+    if (serde().DecodeResponse(&response_struct, packet.payload())) {
+      callback_.ReceivedResponse(
+          *std::launder(reinterpret_cast<Response*>(&response_struct)));
+    } else {
+      callback_.RpcError(Status::DataLoss());
+    }
+  }
+
+  Callback& callback_;
+};
+
+}  // namespace pw::rpc
diff --git a/pw_rpc/nanopb/public/pw_rpc/nanopb_test_method_context.h b/pw_rpc/nanopb/public/pw_rpc/nanopb_test_method_context.h
new file mode 100644
index 0000000..1dbc1e1
--- /dev/null
+++ b/pw_rpc/nanopb/public/pw_rpc/nanopb_test_method_context.h
@@ -0,0 +1,325 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 <utility>
+
+#include "pw_assert/light.h"
+#include "pw_containers/vector.h"
+#include "pw_preprocessor/arguments.h"
+#include "pw_rpc/channel.h"
+#include "pw_rpc/internal/hash.h"
+#include "pw_rpc/internal/method_lookup.h"
+#include "pw_rpc/internal/nanopb_method.h"
+#include "pw_rpc/internal/packet.h"
+#include "pw_rpc/internal/server.h"
+
+namespace pw::rpc {
+
+// Declares a context object that may be used to invoke an RPC. The context is
+// declared with the name of the implemented service and the method to invoke.
+// The RPC can then be invoked with the call method.
+//
+// For a unary RPC, context.call(request) returns the status, and the response
+// struct can be accessed via context.response().
+//
+//   PW_NANOPB_TEST_METHOD_CONTEXT(my::CoolService, TheMethod) context;
+//   EXPECT_EQ(OkStatus(), context.call({.some_arg = 123}));
+//   EXPECT_EQ(500, context.response().some_response_value);
+//
+// For a server streaming RPC, context.call(request) invokes the method. As in a
+// normal RPC, the method completes when the ServerWriter's Finish method is
+// called (or it goes out of scope).
+//
+//   PW_NANOPB_TEST_METHOD_CONTEXT(my::CoolService, TheStreamingMethod) context;
+//   context.call({.some_arg = 123});
+//
+//   EXPECT_TRUE(context.done());  // Check that the RPC completed
+//   EXPECT_EQ(OkStatus(), context.status());  // Check the status
+//
+//   EXPECT_EQ(3u, context.responses().size());
+//   EXPECT_EQ(123, context.responses()[0].value); // check individual responses
+//
+//   for (const MyResponse& response : context.responses()) {
+//     // iterate over the responses
+//   }
+//
+// PW_NANOPB_TEST_METHOD_CONTEXT forwards its constructor arguments to the
+// underlying serivce. For example:
+//
+//   PW_NANOPB_TEST_METHOD_CONTEXT(MyService, Go) context(service, args);
+//
+// PW_NANOPB_TEST_METHOD_CONTEXT takes two optional arguments:
+//
+//   size_t kMaxResponse: maximum responses to store; ignored unless streaming
+//   size_t kOutputSizeBytes: buffer size; must be large enough for a packet
+//
+// Example:
+//
+//   PW_NANOPB_TEST_METHOD_CONTEXT(MyService, BestMethod, 3, 256) context;
+//   ASSERT_EQ(3u, context.responses().max_size());
+//
+#define PW_NANOPB_TEST_METHOD_CONTEXT(service, method, ...)              \
+  ::pw::rpc::NanopbTestMethodContext<service,                            \
+                                     &service::method,                   \
+                                     ::pw::rpc::internal::Hash(#method), \
+                                     ##__VA_ARGS__>
+template <typename Service,
+          auto method,
+          uint32_t kMethodId,
+          size_t kMaxResponse = 4,
+          size_t kOutputSizeBytes = 128>
+class NanopbTestMethodContext;
+
+// Internal classes that implement NanopbTestMethodContext.
+namespace internal::test::nanopb {
+
+// A ChannelOutput implementation that stores the outgoing payloads and status.
+template <typename Response>
+class MessageOutput final : public ChannelOutput {
+ public:
+  MessageOutput(const internal::NanopbMethod& method,
+                Vector<Response>& responses,
+                std::span<std::byte> buffer)
+      : ChannelOutput("internal::test::nanopb::MessageOutput"),
+        method_(method),
+        responses_(responses),
+        buffer_(buffer) {
+    clear();
+  }
+
+  Status last_status() const { return last_status_; }
+  void set_last_status(Status status) { last_status_ = status; }
+
+  size_t total_responses() const { return total_responses_; }
+
+  bool stream_ended() const { return stream_ended_; }
+
+  void clear();
+
+ private:
+  std::span<std::byte> AcquireBuffer() override { return buffer_; }
+
+  Status SendAndReleaseBuffer(std::span<const std::byte> buffer) override;
+
+  const internal::NanopbMethod& method_;
+  Vector<Response>& responses_;
+  std::span<std::byte> buffer_;
+  size_t total_responses_;
+  bool stream_ended_;
+  Status last_status_;
+};
+
+// Collects everything needed to invoke a particular RPC.
+template <typename Service,
+          auto method,
+          uint32_t kMethodId,
+          size_t kMaxResponse,
+          size_t kOutputSize>
+struct InvocationContext {
+  using Request = internal::Request<method>;
+  using Response = internal::Response<method>;
+
+  template <typename... Args>
+  InvocationContext(Args&&... args)
+      : output(MethodLookup::GetNanopbMethod<Service, kMethodId>(),
+               responses,
+               buffer),
+        channel(Channel::Create<123>(&output)),
+        server(std::span(&channel, 1)),
+        service(std::forward<Args>(args)...),
+        call(static_cast<internal::Server&>(server),
+             static_cast<internal::Channel&>(channel),
+             service,
+             MethodLookup::GetNanopbMethod<Service, kMethodId>()) {}
+
+  MessageOutput<Response> output;
+
+  rpc::Channel channel;
+  rpc::Server server;
+  Service service;
+  Vector<Response, kMaxResponse> responses;
+  std::array<std::byte, kOutputSize> buffer = {};
+
+  internal::ServerCall call;
+};
+
+// Method invocation context for a unary RPC. Returns the status in call() and
+// provides the response through the response() method.
+template <typename Service, auto method, uint32_t kMethodId, size_t kOutputSize>
+class UnaryContext {
+ private:
+  InvocationContext<Service, method, kMethodId, 1, kOutputSize> ctx_;
+
+ public:
+  using Request = typename decltype(ctx_)::Request;
+  using Response = typename decltype(ctx_)::Response;
+
+  template <typename... Args>
+  UnaryContext(Args&&... args) : ctx_(std::forward<Args>(args)...) {}
+
+  Service& service() { return ctx_.service; }
+
+  // Invokes the RPC with the provided request. Returns the status.
+  Status call(const Request& request) {
+    ctx_.output.clear();
+    ctx_.responses.emplace_back();
+    ctx_.responses.back() = {};
+    return CallMethodImplFunction<method>(
+        ctx_.call, request, ctx_.responses.back());
+  }
+
+  // Gives access to the RPC's response.
+  const Response& response() const {
+    PW_ASSERT(ctx_.responses.size() > 0u);
+    return ctx_.responses.back();
+  }
+};
+
+// Method invocation context for a server streaming RPC.
+template <typename Service,
+          auto method,
+          uint32_t kMethodId,
+          size_t kMaxResponse,
+          size_t kOutputSize>
+class ServerStreamingContext {
+ private:
+  InvocationContext<Service, method, kMethodId, kMaxResponse, kOutputSize> ctx_;
+
+ public:
+  using Request = typename decltype(ctx_)::Request;
+  using Response = typename decltype(ctx_)::Response;
+
+  template <typename... Args>
+  ServerStreamingContext(Args&&... args) : ctx_(std::forward<Args>(args)...) {}
+
+  Service& service() { return ctx_.service; }
+
+  // Invokes the RPC with the provided request.
+  void call(const Request& request) {
+    ctx_.output.clear();
+    internal::BaseServerWriter server_writer(ctx_.call);
+    return CallMethodImplFunction<method>(
+        ctx_.call,
+        request,
+        static_cast<ServerWriter<Response>&>(server_writer));
+  }
+
+  // Returns a server writer which writes responses into the context's buffer.
+  // This should not be called alongside call(); use one or the other.
+  ServerWriter<Response> writer() {
+    ctx_.output.clear();
+    internal::BaseServerWriter server_writer(ctx_.call);
+    return std::move(static_cast<ServerWriter<Response>&>(server_writer));
+  }
+
+  // Returns the responses that have been recorded. The maximum number of
+  // responses is responses().max_size(). responses().back() is always the most
+  // recent response, even if total_responses() > responses().max_size().
+  const Vector<Response>& responses() const { return ctx_.responses; }
+
+  // The total number of responses sent, which may be larger than
+  // responses.max_size().
+  size_t total_responses() const { return ctx_.output.total_responses(); }
+
+  // True if the stream has terminated.
+  bool done() const { return ctx_.output.stream_ended(); }
+
+  // The status of the stream. Only valid if done() is true.
+  Status status() const {
+    PW_ASSERT(done());
+    return ctx_.output.last_status();
+  }
+};
+
+// Alias to select the type of the context object to use based on which type of
+// RPC it is for.
+template <typename Service,
+          auto method,
+          uint32_t kMethodId,
+          size_t kMaxResponse,
+          size_t kOutputSize>
+using Context = std::tuple_element_t<
+    static_cast<size_t>(internal::MethodTraits<decltype(method)>::kType),
+    std::tuple<UnaryContext<Service, method, kMethodId, kOutputSize>,
+               ServerStreamingContext<Service,
+                                      method,
+                                      kMethodId,
+                                      kMaxResponse,
+                                      kOutputSize>
+               // TODO(hepler): Support client and bidi streaming
+               >>;
+
+template <typename Response>
+void MessageOutput<Response>::clear() {
+  responses_.clear();
+  total_responses_ = 0;
+  stream_ended_ = false;
+  last_status_ = Status::Unknown();
+}
+
+template <typename Response>
+Status MessageOutput<Response>::SendAndReleaseBuffer(
+    std::span<const std::byte> buffer) {
+  PW_ASSERT(!stream_ended_);
+  PW_ASSERT(buffer.data() == buffer_.data());
+
+  if (buffer.empty()) {
+    return OkStatus();
+  }
+
+  Result<internal::Packet> result = internal::Packet::FromBuffer(buffer);
+  PW_ASSERT(result.ok());
+
+  last_status_ = result.value().status();
+
+  switch (result.value().type()) {
+    case internal::PacketType::RESPONSE:
+      // If we run out of space, the back message is always the most recent.
+      responses_.emplace_back();
+      responses_.back() = {};
+      PW_ASSERT(
+          method_.DecodeResponse(result.value().payload(), &responses_.back()));
+      total_responses_ += 1;
+      break;
+    case internal::PacketType::SERVER_STREAM_END:
+      stream_ended_ = true;
+      break;
+    default:
+      PW_CRASH("Unhandled PacketType");
+  }
+  return OkStatus();
+}
+
+}  // namespace internal::test::nanopb
+
+template <typename Service,
+          auto method,
+          uint32_t kMethodId,
+          size_t kMaxResponse,
+          size_t kOutputSizeBytes>
+class NanopbTestMethodContext
+    : public internal::test::nanopb::
+          Context<Service, method, kMethodId, kMaxResponse, kOutputSizeBytes> {
+ public:
+  // Forwards constructor arguments to the service class.
+  template <typename... ServiceArgs>
+  NanopbTestMethodContext(ServiceArgs&&... service_args)
+      : internal::test::nanopb::
+            Context<Service, method, kMethodId, kMaxResponse, kOutputSizeBytes>(
+                std::forward<ServiceArgs>(service_args)...) {}
+};
+
+}  // namespace pw::rpc
diff --git a/pw_rpc/nanopb/pw_rpc_nanopb_private/internal_test_utils.h b/pw_rpc/nanopb/pw_rpc_nanopb_private/internal_test_utils.h
index 40efe85..1cd8df7 100644
--- a/pw_rpc/nanopb/pw_rpc_nanopb_private/internal_test_utils.h
+++ b/pw_rpc/nanopb/pw_rpc_nanopb_private/internal_test_utils.h
@@ -17,6 +17,7 @@
 
 #include "pb_decode.h"
 #include "pb_encode.h"
+#include "pw_rpc/nanopb_client_call.h"
 
 namespace pw::rpc::internal {
 
@@ -37,11 +38,11 @@
       ::pw::rpc::internal::EncodeProtobuf<proto, proto##_fields>( \
           proto{__VA_ARGS__}, _pb_buffer_##unique)
 
-template <typename T, auto kFields>
+template <typename T, auto fields>
 std::span<const std::byte> EncodeProtobuf(const T& protobuf,
                                           std::span<pb_byte_t> buffer) {
   auto output = pb_ostream_from_buffer(buffer.data(), buffer.size());
-  EXPECT_TRUE(pb_encode(&output, kFields, &protobuf));
+  EXPECT_TRUE(pb_encode(&output, fields, &protobuf));
   return std::as_bytes(buffer.first(output.bytes_written));
 }
 
@@ -53,10 +54,67 @@
                 buffer.size()),                                    \
       result);
 
-template <typename T, auto kFields>
+template <typename T, auto fields>
 void DecodeProtobuf(std::span<const pb_byte_t> buffer, T& protobuf) {
   auto input = pb_istream_from_buffer(buffer.data(), buffer.size());
-  EXPECT_TRUE(pb_decode(&input, kFields, &protobuf));
+  EXPECT_TRUE(pb_decode(&input, fields, &protobuf));
 }
 
+// Client response handler for a unary RPC invocation which captures the
+// response it receives.
+template <typename Response>
+class TestUnaryResponseHandler : public UnaryResponseHandler<Response> {
+ public:
+  void ReceivedResponse(Status status, const Response& response) override {
+    last_status_ = status;
+    last_response_ = response;
+    ++responses_received_;
+  }
+
+  void RpcError(Status status) override { rpc_error_ = status; }
+
+  constexpr Status last_status() const { return last_status_; }
+  constexpr const Response& last_response() const& { return last_response_; }
+  constexpr size_t responses_received() const { return responses_received_; }
+  constexpr Status rpc_error() const { return rpc_error_; }
+
+ private:
+  Status last_status_;
+  Response last_response_;
+  size_t responses_received_ = 0;
+  Status rpc_error_;
+};
+
+// Client response handler for a unary RPC invocation which stores information
+// about the state of the stream.
+template <typename Response>
+class TestServerStreamingResponseHandler
+    : public ServerStreamingResponseHandler<Response> {
+ public:
+  void ReceivedResponse(const Response& response) override {
+    last_response_ = response;
+    ++responses_received_;
+  }
+
+  void Complete(Status status) override {
+    active_ = false;
+    status_ = status;
+  }
+
+  void RpcError(Status status) override { rpc_error_ = status; }
+
+  constexpr bool active() const { return active_; }
+  constexpr Status status() const { return status_; }
+  constexpr const Response& last_response() const& { return last_response_; }
+  constexpr size_t responses_received() const { return responses_received_; }
+  constexpr Status rpc_error() const { return rpc_error_; }
+
+ private:
+  Status status_;
+  Response last_response_;
+  size_t responses_received_ = 0;
+  bool active_ = true;
+  Status rpc_error_;
+};
+
 }  // namespace pw::rpc::internal
diff --git a/pw_rpc/nanopb/serde_test.cc b/pw_rpc/nanopb/serde_test.cc
deleted file mode 100644
index aa0ee87..0000000
--- a/pw_rpc/nanopb/serde_test.cc
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "gtest/gtest.h"
-#include "pw_rpc/nanopb/internal/common.h"
-#include "pw_rpc_test_protos/test.pb.h"
-
-namespace pw::rpc::internal {
-namespace {
-
-constexpr NanopbSerde kTestRequest(pw_rpc_test_TestRequest_fields);
-constexpr pw_rpc_test_TestRequest kProto{.integer = 3, .status_code = 0};
-
-TEST(NanopbSerde, Encode) {
-  std::byte buffer[32] = {};
-
-  StatusWithSize result = kTestRequest.Encode(&kProto, buffer);
-  EXPECT_EQ(OkStatus(), result.status());
-  EXPECT_EQ(result.size(), 2u);
-  EXPECT_EQ(buffer[0], std::byte{1} << 3);
-  EXPECT_EQ(buffer[1], std::byte{3});
-}
-
-TEST(NanopbSerde, Encode_TooSmall) {
-  std::byte buffer[1] = {};
-  EXPECT_EQ(Status::Internal(), kTestRequest.Encode(&kProto, buffer).status());
-}
-
-TEST(NanopbSerde, EncodedSize) {
-  StatusWithSize result = kTestRequest.EncodedSizeBytes(&kProto);
-  EXPECT_EQ(OkStatus(), result.status());
-  EXPECT_EQ(result.size(), 2u);
-}
-
-TEST(NanopbSerde, Decode) {
-  constexpr std::byte buffer[]{std::byte{1} << 3, std::byte{3}};
-  pw_rpc_test_TestRequest proto = {};
-
-  EXPECT_TRUE(kTestRequest.Decode(buffer, &proto));
-
-  EXPECT_EQ(3, proto.integer);
-  EXPECT_EQ(0u, proto.status_code);
-}
-
-}  // namespace
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/nanopb/server_callback_test.cc b/pw_rpc/nanopb/server_callback_test.cc
deleted file mode 100644
index 41bfd57..0000000
--- a/pw_rpc/nanopb/server_callback_test.cc
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <array>
-
-#include "gtest/gtest.h"
-#include "pb_decode.h"
-#include "pb_encode.h"
-#include "pw_rpc/nanopb/test_method_context.h"
-#include "pw_rpc/service.h"
-#include "pw_rpc_test_protos/test.rpc.pb.h"
-
-namespace pw::rpc {
-
-class TestServiceImpl final
-    : public test::pw_rpc::nanopb::TestService::Service<TestServiceImpl> {
- public:
-  Status TestUnaryRpc(const pw_rpc_test_TestRequest&,
-                      pw_rpc_test_TestResponse&) {
-    return OkStatus();
-  }
-
-  Status TestAnotherUnaryRpc(const pw_rpc_test_TestRequest&,
-                             pw_rpc_test_TestResponse& response) {
-    typedef std::array<uint32_t, 3> ArgType;
-    // The values array needs to be kept in memory until after this method call
-    // returns since the response is not encoded until after returning from this
-    // method.
-    static const ArgType values = {7, 8, 9};
-    response.repeated_field.funcs.encode = +[](pb_ostream_t* stream,
-                                               const pb_field_t* field,
-                                               void* const* arg) -> bool {
-      // Note: nanopb passes the pointer to the repeated_filed.arg member as
-      // arg, not its contents.
-      for (auto elem : *static_cast<const ArgType*>(*arg)) {
-        if (!pb_encode_tag_for_field(stream, field) ||
-            !pb_encode_varint(stream, elem))
-          return false;
-      }
-      return true;
-    };
-    response.repeated_field.arg = const_cast<ArgType*>(&values);
-    return OkStatus();
-  }
-
-  void TestServerStreamRpc(
-      const pw_rpc_test_TestRequest&,
-      NanopbServerWriter<pw_rpc_test_TestStreamResponse>&) {}
-
-  void TestClientStreamRpc(
-      NanopbServerReader<pw_rpc_test_TestRequest,
-                         pw_rpc_test_TestStreamResponse>&) {}
-
-  void TestBidirectionalStreamRpc(
-      NanopbServerReaderWriter<pw_rpc_test_TestRequest,
-                               pw_rpc_test_TestStreamResponse>&) {}
-};
-
-TEST(NanopbTestMethodContext, ResponseWithCallbacks) {
-  PW_NANOPB_TEST_METHOD_CONTEXT(TestServiceImpl, TestAnotherUnaryRpc) ctx;
-  ASSERT_EQ(ctx.call(pw_rpc_test_TestRequest_init_default), OkStatus());
-
-  // Calling response() without an argument returns a Response struct from a
-  // newly decoded one, without any callbacks set.
-  EXPECT_EQ(ctx.response().repeated_field.arg, nullptr);
-
-  // To decode a response object that requires to set pb_callback_t members,
-  // pass it to the response() method as a parameter.
-  constexpr size_t kMaxNumValues = 4;
-  struct DecoderContext {
-    uint32_t num_calls = 0;
-    uint32_t values[kMaxNumValues];
-    bool failed = false;
-  } decoder_context;
-
-  pw_rpc_test_TestResponse response = pw_rpc_test_TestResponse_init_default;
-  response.repeated_field.funcs.decode = +[](pb_istream_t* stream,
-                                             const pb_field_t* /* field */,
-                                             void** arg) -> bool {
-    DecoderContext* dec_ctx = static_cast<DecoderContext*>(*arg);
-    uint64_t value;
-    if (!pb_decode_varint(stream, &value)) {
-      dec_ctx->failed = true;
-      return false;
-    }
-    if (dec_ctx->num_calls < kMaxNumValues) {
-      dec_ctx->values[dec_ctx->num_calls] = value;
-    }
-    dec_ctx->num_calls++;
-    return true;
-  };
-  response.repeated_field.arg = &decoder_context;
-  ctx.response(response);
-
-  EXPECT_FALSE(decoder_context.failed);
-  EXPECT_EQ(3u, decoder_context.num_calls);
-  EXPECT_EQ(7u, decoder_context.values[0]);
-  EXPECT_EQ(8u, decoder_context.values[1]);
-  EXPECT_EQ(9u, decoder_context.values[2]);
-}
-
-}  // namespace pw::rpc
diff --git a/pw_rpc/nanopb/server_reader_writer.cc b/pw_rpc/nanopb/server_reader_writer.cc
deleted file mode 100644
index f0517bc..0000000
--- a/pw_rpc/nanopb/server_reader_writer.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/nanopb/server_reader_writer.h"
-
-#include "pw_rpc/nanopb/internal/method.h"
-
-namespace pw::rpc::internal {
-
-NanopbServerCall::NanopbServerCall(const CallContext& context, MethodType type)
-    : internal::ServerCall(context, type),
-      serde_(&static_cast<const internal::NanopbMethod&>(context.method())
-                  .serde()) {}
-
-Status NanopbServerCall::SendServerStream(const void* payload) {
-  if (!active()) {
-    return Status::FailedPrecondition();
-  }
-  return NanopbSendStream(*this, payload, serde_->response());
-}
-
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/nanopb/server_reader_writer_test.cc b/pw_rpc/nanopb/server_reader_writer_test.cc
deleted file mode 100644
index 98a3b52..0000000
--- a/pw_rpc/nanopb/server_reader_writer_test.cc
+++ /dev/null
@@ -1,294 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/nanopb/server_reader_writer.h"
-
-#include "gtest/gtest.h"
-#include "pw_rpc/nanopb/fake_channel_output.h"
-#include "pw_rpc/nanopb/test_method_context.h"
-#include "pw_rpc/service.h"
-#include "pw_rpc_test_protos/test.rpc.pb.h"
-
-namespace pw::rpc {
-
-class TestServiceImpl final
-    : public test::pw_rpc::nanopb::TestService::Service<TestServiceImpl> {
- public:
-  Status TestUnaryRpc(const pw_rpc_test_TestRequest&,
-                      pw_rpc_test_TestResponse&) {
-    return OkStatus();
-  }
-
-  void TestAnotherUnaryRpc(const pw_rpc_test_TestRequest&,
-                           NanopbUnaryResponder<pw_rpc_test_TestResponse>&) {}
-
-  void TestServerStreamRpc(
-      const pw_rpc_test_TestRequest&,
-      NanopbServerWriter<pw_rpc_test_TestStreamResponse>&) {}
-
-  void TestClientStreamRpc(
-      NanopbServerReader<pw_rpc_test_TestRequest,
-                         pw_rpc_test_TestStreamResponse>&) {}
-
-  void TestBidirectionalStreamRpc(
-      NanopbServerReaderWriter<pw_rpc_test_TestRequest,
-                               pw_rpc_test_TestStreamResponse>&) {}
-};
-
-template <auto kMethod>
-struct ReaderWriterTestContext {
-  using Info = internal::MethodInfo<kMethod>;
-
-  static constexpr uint32_t kChannelId = 1;
-
-  ReaderWriterTestContext()
-      : channel(Channel::Create<kChannelId>(&output)),
-        server(std::span(&channel, 1)) {}
-
-  TestServiceImpl service;
-  NanopbFakeChannelOutput<4> output;
-  Channel channel;
-  Server server;
-};
-
-using test::pw_rpc::nanopb::TestService;
-
-TEST(NanopbUnaryResponder, DefaultConstructed) {
-  NanopbUnaryResponder<pw_rpc_test_TestResponse> call;
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Finish({}, OkStatus()));
-
-  call.set_on_error([](Status) {});
-}
-
-TEST(NanopbServerWriter, DefaultConstructed) {
-  NanopbServerWriter<pw_rpc_test_TestStreamResponse> call;
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Write({}));
-  EXPECT_EQ(Status::FailedPrecondition(), call.Finish(OkStatus()));
-
-  call.set_on_error([](Status) {});
-}
-
-TEST(NanopbServerReader, DefaultConstructed) {
-  NanopbServerReader<pw_rpc_test_TestRequest, pw_rpc_test_TestStreamResponse>
-      call;
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Finish({}, OkStatus()));
-
-  call.set_on_next([](const pw_rpc_test_TestRequest&) {});
-  call.set_on_error([](Status) {});
-}
-
-TEST(NanopbServerReaderWriter, DefaultConstructed) {
-  NanopbServerReaderWriter<pw_rpc_test_TestRequest,
-                           pw_rpc_test_TestStreamResponse>
-      call;
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Write({}));
-  EXPECT_EQ(Status::FailedPrecondition(), call.Finish(OkStatus()));
-
-  call.set_on_next([](const pw_rpc_test_TestRequest&) {});
-  call.set_on_error([](Status) {});
-}
-
-TEST(NanopbUnaryResponder, Closed) {
-  ReaderWriterTestContext<TestService::TestUnaryRpc> ctx;
-  NanopbUnaryResponder call =
-      NanopbUnaryResponder<pw_rpc_test_TestResponse>::Open<
-          TestService::TestUnaryRpc>(ctx.server, ctx.channel.id(), ctx.service);
-  ASSERT_EQ(OkStatus(), call.Finish({}, OkStatus()));
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Finish({}, OkStatus()));
-
-  call.set_on_error([](Status) {});
-}
-
-TEST(NanopbServerWriter, Closed) {
-  ReaderWriterTestContext<TestService::TestServerStreamRpc> ctx;
-  NanopbServerWriter call =
-      NanopbServerWriter<pw_rpc_test_TestStreamResponse>::Open<
-          TestService::TestServerStreamRpc>(
-          ctx.server, ctx.channel.id(), ctx.service);
-  ASSERT_EQ(OkStatus(), call.Finish(OkStatus()));
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Write({}));
-  EXPECT_EQ(Status::FailedPrecondition(), call.Finish(OkStatus()));
-
-  call.set_on_error([](Status) {});
-}
-
-TEST(NanopbServerReader, Closed) {
-  ReaderWriterTestContext<TestService::TestClientStreamRpc> ctx;
-  NanopbServerReader call = NanopbServerReader<pw_rpc_test_TestRequest,
-                                               pw_rpc_test_TestStreamResponse>::
-      Open<TestService::TestClientStreamRpc>(
-          ctx.server, ctx.channel.id(), ctx.service);
-  ASSERT_EQ(OkStatus(), call.Finish({}, OkStatus()));
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Finish({}, OkStatus()));
-
-  call.set_on_next([](const pw_rpc_test_TestRequest&) {});
-  call.set_on_error([](Status) {});
-}
-
-TEST(NanopbServerReaderWriter, Closed) {
-  ReaderWriterTestContext<TestService::TestBidirectionalStreamRpc> ctx;
-  NanopbServerReaderWriter call =
-      NanopbServerReaderWriter<pw_rpc_test_TestRequest,
-                               pw_rpc_test_TestStreamResponse>::
-          Open<TestService::TestBidirectionalStreamRpc>(
-              ctx.server, ctx.channel.id(), ctx.service);
-  ASSERT_EQ(OkStatus(), call.Finish(OkStatus()));
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Write({}));
-  EXPECT_EQ(Status::FailedPrecondition(), call.Finish(OkStatus()));
-
-  call.set_on_next([](const pw_rpc_test_TestRequest&) {});
-  call.set_on_error([](Status) {});
-}
-
-TEST(NanopbUnaryResponder, Open_ReturnsUsableResponder) {
-  ReaderWriterTestContext<TestService::TestUnaryRpc> ctx;
-  NanopbUnaryResponder responder =
-      NanopbUnaryResponder<pw_rpc_test_TestResponse>::Open<
-          TestService::TestUnaryRpc>(ctx.server, ctx.channel.id(), ctx.service);
-
-  ASSERT_EQ(OkStatus(),
-            responder.Finish({.value = 4321, .repeated_field = {}}));
-
-  EXPECT_EQ(ctx.output.last_response<TestService::TestUnaryRpc>().value, 4321);
-  EXPECT_EQ(ctx.output.last_status(), OkStatus());
-}
-
-TEST(NanopbServerWriter, Open_ReturnsUsableWriter) {
-  ReaderWriterTestContext<TestService::TestServerStreamRpc> ctx;
-  NanopbServerWriter responder =
-      NanopbServerWriter<pw_rpc_test_TestStreamResponse>::Open<
-          TestService::TestServerStreamRpc>(
-          ctx.server, ctx.channel.id(), ctx.service);
-
-  ASSERT_EQ(OkStatus(), responder.Write({.chunk = {}, .number = 321}));
-  ASSERT_EQ(OkStatus(), responder.Finish());
-
-  EXPECT_EQ(ctx.output.last_response<TestService::TestServerStreamRpc>().number,
-            321u);
-  EXPECT_EQ(ctx.output.last_status(), OkStatus());
-}
-
-TEST(NanopbServerReader, Open_ReturnsUsableReader) {
-  ReaderWriterTestContext<TestService::TestClientStreamRpc> ctx;
-  NanopbServerReader responder =
-      NanopbServerReader<pw_rpc_test_TestRequest,
-                         pw_rpc_test_TestStreamResponse>::
-          Open<TestService::TestClientStreamRpc>(
-              ctx.server, ctx.channel.id(), ctx.service);
-
-  ASSERT_EQ(OkStatus(), responder.Finish({.chunk = {}, .number = 321}));
-
-  EXPECT_EQ(ctx.output.last_response<TestService::TestClientStreamRpc>().number,
-            321u);
-}
-
-TEST(NanopbServerReaderWriter, Open_ReturnsUsableReaderWriter) {
-  ReaderWriterTestContext<TestService::TestBidirectionalStreamRpc> ctx;
-  NanopbServerReaderWriter responder =
-      NanopbServerReaderWriter<pw_rpc_test_TestRequest,
-                               pw_rpc_test_TestStreamResponse>::
-          Open<TestService::TestBidirectionalStreamRpc>(
-              ctx.server, ctx.channel.id(), ctx.service);
-
-  ASSERT_EQ(OkStatus(), responder.Write({.chunk = {}, .number = 321}));
-  ASSERT_EQ(OkStatus(), responder.Finish(Status::NotFound()));
-
-  EXPECT_EQ(ctx.output.last_response<TestService::TestBidirectionalStreamRpc>()
-                .number,
-            321u);
-  EXPECT_EQ(ctx.output.last_status(), Status::NotFound());
-}
-
-TEST(RawServerReaderWriter, Open_UnknownChannel) {
-  ReaderWriterTestContext<TestService::TestBidirectionalStreamRpc> ctx;
-  ASSERT_EQ(OkStatus(), ctx.server.CloseChannel(ctx.kChannelId));
-
-  NanopbServerReaderWriter call =
-      NanopbServerReaderWriter<pw_rpc_test_TestRequest,
-                               pw_rpc_test_TestStreamResponse>::
-          Open<TestService::TestBidirectionalStreamRpc>(
-              ctx.server, ctx.kChannelId, ctx.service);
-
-  EXPECT_TRUE(call.active());
-  EXPECT_EQ(call.channel_id(), ctx.kChannelId);
-  EXPECT_EQ(Status::Unavailable(), call.Write({}));
-
-  ASSERT_EQ(OkStatus(), ctx.server.OpenChannel(ctx.kChannelId, ctx.output));
-
-  EXPECT_EQ(OkStatus(), call.Write({}));
-  EXPECT_TRUE(call.active());
-
-  EXPECT_EQ(OkStatus(), call.Finish());
-  EXPECT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-}
-
-TEST(NanopbServerReader, CallbacksMoveCorrectly) {
-  PW_NANOPB_TEST_METHOD_CONTEXT(TestServiceImpl, TestClientStreamRpc) ctx;
-
-  NanopbServerReader call_1 = ctx.reader();
-
-  ASSERT_TRUE(call_1.active());
-
-  pw_rpc_test_TestRequest received_request = {.integer = 12345678,
-                                              .status_code = 1};
-
-  call_1.set_on_next([&received_request](const pw_rpc_test_TestRequest& value) {
-    received_request = value;
-  });
-
-  NanopbServerReader<pw_rpc_test_TestRequest, pw_rpc_test_TestStreamResponse>
-      call_2;
-  call_2 = std::move(call_1);
-
-  constexpr pw_rpc_test_TestRequest request{.integer = 600613,
-                                            .status_code = 2};
-  ctx.SendClientStream(request);
-  EXPECT_EQ(request.integer, received_request.integer);
-  EXPECT_EQ(request.status_code, received_request.status_code);
-}
-
-}  // namespace pw::rpc
diff --git a/pw_rpc/packet.cc b/pw_rpc/packet.cc
index de37ab1..3739370 100644
--- a/pw_rpc/packet.cc
+++ b/pw_rpc/packet.cc
@@ -18,6 +18,8 @@
 
 namespace pw::rpc::internal {
 
+using std::byte;
+
 Result<Packet> Packet::FromBuffer(ConstByteSpan data) {
   Packet packet;
   Status status;
@@ -30,44 +32,33 @@
     switch (field) {
       case RpcPacket::Fields::TYPE: {
         uint32_t value;
-        // A decode error will propagate from Next() and terminate the loop.
-        decoder.ReadUint32(&value).IgnoreError();
+        decoder.ReadUint32(&value);
         packet.set_type(static_cast<PacketType>(value));
         break;
       }
 
       case RpcPacket::Fields::CHANNEL_ID:
-        // A decode error will propagate from Next() and terminate the loop.
-        decoder.ReadUint32(&packet.channel_id_).IgnoreError();
+        decoder.ReadUint32(&packet.channel_id_);
         break;
 
       case RpcPacket::Fields::SERVICE_ID:
-        // A decode error will propagate from Next() and terminate the loop.
-        decoder.ReadFixed32(&packet.service_id_).IgnoreError();
+        decoder.ReadFixed32(&packet.service_id_);
         break;
 
       case RpcPacket::Fields::METHOD_ID:
-        // A decode error will propagate from Next() and terminate the loop.
-        decoder.ReadFixed32(&packet.method_id_).IgnoreError();
+        decoder.ReadFixed32(&packet.method_id_);
         break;
 
       case RpcPacket::Fields::PAYLOAD:
-        // A decode error will propagate from Next() and terminate the loop.
-        decoder.ReadBytes(&packet.payload_).IgnoreError();
+        decoder.ReadBytes(&packet.payload_);
         break;
 
       case RpcPacket::Fields::STATUS: {
         uint32_t value;
-        // A decode error will propagate from Next() and terminate the loop.
-        decoder.ReadUint32(&value).IgnoreError();
+        decoder.ReadUint32(&value);
         packet.set_status(static_cast<Status::Code>(value));
         break;
       }
-
-      case RpcPacket::Fields::CALL_ID:
-        // A decode error will propagate from Next() and terminate the loop.
-        decoder.ReadUint32(&packet.call_id_).IgnoreError();
-        break;
     }
   }
 
@@ -75,42 +66,23 @@
     return status;
   }
 
-  // TODO(pwbug/512): CANCEL is equivalent to CLIENT_ERROR with status
-  //     CANCELLED. Remove this workaround when CANCEL is removed.
-  if (packet.type() == PacketType::DEPRECATED_CANCEL) {
-    packet.set_status(Status::Cancelled());
-  }
-
   return packet;
 }
 
 Result<ConstByteSpan> Packet::Encode(ByteSpan buffer) const {
-  RpcPacket::MemoryEncoder rpc_packet(buffer);
+  pw::protobuf::NestedEncoder encoder(buffer);
+  RpcPacket::Encoder rpc_packet(&encoder);
 
   // The payload is encoded first, as it may share the encode buffer.
-  if (!payload_.empty()) {
-    rpc_packet.WritePayload(payload_).IgnoreError();
-  }
+  rpc_packet.WritePayload(payload_);
 
-  rpc_packet.WriteType(type_).IgnoreError();
-  rpc_packet.WriteChannelId(channel_id_).IgnoreError();
-  rpc_packet.WriteServiceId(service_id_).IgnoreError();
-  rpc_packet.WriteMethodId(method_id_).IgnoreError();
+  rpc_packet.WriteType(type_);
+  rpc_packet.WriteChannelId(channel_id_);
+  rpc_packet.WriteServiceId(service_id_);
+  rpc_packet.WriteMethodId(method_id_);
+  rpc_packet.WriteStatus(status_.code());
 
-  // Status code 0 is OK. In protobufs, 0 is the default int value, so skip
-  // encoding it to save two bytes in the output.
-  if (status_.code() != 0) {
-    rpc_packet.WriteStatus(status_.code()).IgnoreError();
-  }
-
-  if (call_id_ != 0) {
-    rpc_packet.WriteCallId(call_id_).IgnoreError();
-  }
-
-  if (rpc_packet.status().ok()) {
-    return ConstByteSpan(rpc_packet);
-  }
-  return rpc_packet.status();
+  return encoder.Encode();
 }
 
 size_t Packet::MinEncodedSizeBytes() const {
@@ -124,7 +96,7 @@
   // Packet type always takes two bytes to encode (varint key + varint enum).
   reserved_size += 2;
 
-  // Status field takes up to two bytes to encode (varint key + varint status).
+  // Status field always takes two bytes to encode (varint key + varint status).
   reserved_size += 2;
 
   // Payload field takes at least two bytes to encode (varint key + length).
diff --git a/pw_rpc/packet_test.cc b/pw_rpc/packet_test.cc
index 1edff7a..534fb87 100644
--- a/pw_rpc/packet_test.cc
+++ b/pw_rpc/packet_test.cc
@@ -16,19 +16,19 @@
 
 #include "gtest/gtest.h"
 #include "pw_bytes/array.h"
+#include "pw_protobuf/codegen.h"
 #include "pw_protobuf/wire_format.h"
 
 namespace pw::rpc::internal {
 namespace {
 
-using protobuf::FieldKey;
 using std::byte;
 
 constexpr auto kPayload = bytes::Array<0x82, 0x02, 0xff, 0xff>();
 
 constexpr auto kEncoded = bytes::Array<
     // Payload
-    uint32_t(FieldKey(5, protobuf::WireType::kDelimited)),
+    MakeKey(5, protobuf::WireType::kDelimited),
     0x04,
     0x82,
     0x02,
@@ -36,34 +36,30 @@
     0xff,
 
     // Packet type
-    uint32_t(FieldKey(1, protobuf::WireType::kVarint)),
+    MakeKey(1, protobuf::WireType::kVarint),
     1,  // RESPONSE
 
     // Channel ID
-    uint32_t(FieldKey(2, protobuf::WireType::kVarint)),
+    MakeKey(2, protobuf::WireType::kVarint),
     1,
 
     // Service ID
-    uint32_t(FieldKey(3, protobuf::WireType::kFixed32)),
+    MakeKey(3, protobuf::WireType::kFixed32),
     42,
     0,
     0,
     0,
 
     // Method ID
-    uint32_t(FieldKey(4, protobuf::WireType::kFixed32)),
+    MakeKey(4, protobuf::WireType::kFixed32),
     100,
     0,
     0,
     0,
 
-    // Status (not encoded if it is zero)
-    // FieldKey(6, protobuf::WireType::kVarint),
-    // 0x00
-
-    // Call ID
-    uint32_t(FieldKey(7, protobuf::WireType::kVarint)),
-    7>();
+    // Status
+    MakeKey(6, protobuf::WireType::kVarint),
+    0x00>();
 
 // Test that a default-constructed packet sets its members to the default
 // protobuf values.
@@ -77,7 +73,7 @@
 TEST(Packet, Encode) {
   byte buffer[64];
 
-  Packet packet(PacketType::RESPONSE, 1, 42, 100, 7, kPayload);
+  Packet packet(PacketType::RESPONSE, 1, 42, 100, kPayload);
 
   auto result = packet.Encode(buffer);
   ASSERT_EQ(OkStatus(), result.status());
@@ -88,7 +84,7 @@
 TEST(Packet, Encode_BufferTooSmall) {
   byte buffer[2];
 
-  Packet packet(PacketType::RESPONSE, 1, 42, 100, 0, kPayload);
+  Packet packet(PacketType::RESPONSE, 1, 42, 100, kPayload);
 
   auto result = packet.Encode(buffer);
   EXPECT_EQ(Status::ResourceExhausted(), result.status());
@@ -103,7 +99,6 @@
   EXPECT_EQ(1u, packet.channel_id());
   EXPECT_EQ(42u, packet.service_id());
   EXPECT_EQ(100u, packet.method_id());
-  EXPECT_EQ(7u, packet.call_id());
   ASSERT_EQ(sizeof(kPayload), packet.payload().size());
   EXPECT_EQ(
       0,
@@ -122,7 +117,6 @@
   packet.set_channel_id(12);
   packet.set_service_id(0xdeadbeef);
   packet.set_method_id(0x03a82921);
-  packet.set_call_id(33);
   packet.set_payload(payload);
   packet.set_status(Status::Unavailable());
 
@@ -139,7 +133,6 @@
   EXPECT_EQ(decoded.channel_id(), packet.channel_id());
   EXPECT_EQ(decoded.service_id(), packet.service_id());
   EXPECT_EQ(decoded.method_id(), packet.method_id());
-  EXPECT_EQ(decoded.call_id(), packet.call_id());
   ASSERT_EQ(decoded.payload().size(), packet.payload().size());
   EXPECT_EQ(std::memcmp(decoded.payload().data(),
                         packet.payload().data(),
diff --git a/pw_rpc/public/pw_rpc/benchmark.h b/pw_rpc/public/pw_rpc/benchmark.h
deleted file mode 100644
index 7ac4513..0000000
--- a/pw_rpc/public/pw_rpc/benchmark.h
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/benchmark.raw_rpc.pb.h"
-
-namespace pw::rpc {
-
-// RPC service with low-level RPCs for transmitting data. Used for benchmarking
-// and testing.
-class BenchmarkService
-    : public pw_rpc::raw::Benchmark::Service<BenchmarkService> {
- public:
-  static void UnaryEcho(ConstByteSpan request, RawUnaryResponder& responder);
-
-  void BidirectionalEcho(RawServerReaderWriter& reader_writer);
-
- private:
-  RawServerReaderWriter reader_writer_;
-};
-
-}  // namespace pw::rpc
diff --git a/pw_rpc/public/pw_rpc/channel.h b/pw_rpc/public/pw_rpc/channel.h
index 8495956..a8823ea 100644
--- a/pw_rpc/public/pw_rpc/channel.h
+++ b/pw_rpc/public/pw_rpc/channel.h
@@ -14,28 +14,23 @@
 #pragma once
 
 #include <cstdint>
-#include <limits>
 #include <span>
 #include <type_traits>
 
-#include "pw_assert/assert.h"
-#include "pw_bytes/span.h"
-#include "pw_result/result.h"
-#include "pw_rpc/internal/lock.h"
+#include "pw_assert/light.h"
 #include "pw_status/status.h"
 
 namespace pw::rpc {
+namespace internal {
 
-// Extracts the channel ID from a pw_rpc packet. Returns DATA_LOSS if the
-// packet is corrupt and the channel ID could not be found.
-Result<uint32_t> ExtractChannelId(ConstByteSpan packet);
+class BaseClientCall;
+
+}  // namespace internal
+
+class Client;
 
 class ChannelOutput {
  public:
-  // Returned from MaximumTransmissionUnit() to indicate that this ChannelOutput
-  // imposes no limits on the MTU.
-  static constexpr size_t kUnlimited = std::numeric_limits<size_t>::max();
-
   // Creates a channel output with the provided name. The name is used for
   // logging only.
   constexpr ChannelOutput(const char* name) : name_(name) {}
@@ -44,32 +39,22 @@
 
   constexpr const char* name() const { return name_; }
 
-  // Returns the maximum transmission unit that this ChannelOutput supports. If
-  // the ChannelOutput imposes no limit on the MTU, this function returns
-  // ChannelOutput::kUnlimited.
-  virtual size_t MaximumTransmissionUnit() { return kUnlimited; }
+  // Acquire a buffer into which to write an outgoing RPC packet. The
+  // implementation is expected to handle synchronization if necessary.
+  virtual std::span<std::byte> AcquireBuffer() = 0;
 
-  // Sends an encoded RPC packet. Returns OK if further packets may be sent,
-  // even if the current packet could not be sent. Returns any other status if
-  // the Channel is no longer able to send packets.
+  // Sends the contents of a buffer previously obtained from AcquireBuffer().
+  // This may be called with an empty span, in which case the buffer should be
+  // released without sending any data.
   //
-  // The RPC system’s internal lock is held while this function is called. Avoid
-  // long-running operations, since these will delay any other users of the RPC
-  // system.
-  //
-  // !!! DANGER !!!
-  //
-  // No pw_rpc APIs may be accessed in this function! Implementations MUST NOT
-  // access any RPC endpoints (pw::rpc::Client, pw::rpc::Server) or call objects
-  // (pw::rpc::ServerReaderWriter, pw::rpc::ClientReaderWriter, etc.) inside the
-  // Send() function or any descendent calls. Doing so will result in deadlock!
-  // RPC APIs may be used by other threads, just not within Send().
-  //
-  // The buffer provided in packet must NOT be accessed outside of this
-  // function. It must be sent immediately or copied elsewhere before the
-  // function returns.
-  virtual Status Send(std::span<const std::byte> buffer)
-      PW_EXCLUSIVE_LOCKS_REQUIRED(internal::rpc_lock()) = 0;
+  // Returns OK if the operation succeeded, or an implementation-defined Status
+  // value if there was an error. The implementation must NOT return
+  // FAILED_PRECONDITION or INTERNAL, which are reserved by pw_rpc.
+  virtual Status SendAndReleaseBuffer(std::span<const std::byte> buffer) = 0;
+
+  void DiscardBuffer(std::span<const std::byte> buffer) {
+    SendAndReleaseBuffer(buffer.first(0));
+  }
 
  private:
   const char* name_;
@@ -79,6 +64,10 @@
  public:
   static constexpr uint32_t kUnassignedChannelId = 0;
 
+  // Creates a dynamically assignable channel without a set ID or output.
+  constexpr Channel()
+      : id_(kUnassignedChannelId), output_(nullptr), client_(nullptr) {}
+
   // Creates a channel with a static ID. The channel's output can also be
   // static, or it can set to null to allow dynamically opening connections
   // through the channel.
@@ -101,63 +90,12 @@
     return Create<static_cast<uint32_t>(kIntId)>(output);
   }
 
-  // Creates a dynamically assignable channel without a set ID or output.
-  constexpr Channel() : id_(kUnassignedChannelId), output_(nullptr) {}
-
-  // TODO(pwbug/620): Remove the Configure and set_channel_output functions.
-  //     Users should call CloseChannel() / OpenChannel() to change a channel.
-  //     This ensures calls are properly update and works consistently between
-  //     static and dynamic channel allocation.
-
-  // Manually configures a dynamically-assignable channel with a specified ID
-  // and output. This is useful when a channel's parameters are not known until
-  // runtime. This can only be called once per channel.
-  template <typename UnusedType = void>
-  constexpr void Configure(uint32_t id, ChannelOutput& output) {
-    static_assert(
-        !cfg::kDynamicAllocationEnabled<UnusedType>,
-        "Configure() may not be used if PW_RPC_DYNAMIC_ALLOCATION is "
-        "enabled. Call CloseChannel/OpenChannel on the endpoint instead.");
-    PW_ASSERT(id_ == kUnassignedChannelId);
-    PW_ASSERT(id != kUnassignedChannelId);
-    id_ = id;
-    output_ = &output;
-  }
-
-  // Configure using an enum value channel ID.
-  template <typename T,
-            typename = std::enable_if_t<std::is_enum_v<T>>,
-            typename U = std::underlying_type_t<T>>
-  constexpr void Configure(T id, ChannelOutput& output) {
-    static_assert(
-        !cfg::kDynamicAllocationEnabled<T>,
-        "Configure() may not be used if PW_RPC_DYNAMIC_ALLOCATION is enabled. "
-        "Call CloseChannel/OpenChannel on the endpoint instead.");
-    static_assert(sizeof(U) <= sizeof(uint32_t));
-    const U kIntId = static_cast<U>(id);
-    PW_ASSERT(kIntId > 0);
-    return Configure<T>(static_cast<uint32_t>(kIntId), output);
-  }
-
-  // Reconfigures a channel with a new output. Depending on the output's
-  // implementatation, there might be unintended behavior if the output is in
-  // use.
-  template <typename UnusedType = void>
-  constexpr void set_channel_output(ChannelOutput& output) {
-    static_assert(
-        !cfg::kDynamicAllocationEnabled<UnusedType>,
-        "set_channel_output() may not be used if PW_RPC_DYNAMIC_ALLOCATION is "
-        "enabled. Call CloseChannel/OpenChannel on the endpoint instead.");
-    PW_ASSERT(id_ != kUnassignedChannelId);
-    output_ = &output;
-  }
-
   constexpr uint32_t id() const { return id_; }
   constexpr bool assigned() const { return id_ != kUnassignedChannelId; }
 
  protected:
   constexpr Channel(uint32_t id, ChannelOutput* output)
-      : id_(id), output_(output) {
+      : id_(id), output_(output), client_(nullptr) {
     PW_ASSERT(id != kUnassignedChannelId);
   }
 
@@ -166,17 +104,16 @@
     return *output_;
   }
 
-  void set_channel_id(uint32_t channel_id) { id_ = channel_id; }
-
-  constexpr void Close() {
-    PW_ASSERT(id_ != kUnassignedChannelId);
-    id_ = kUnassignedChannelId;
-    output_ = nullptr;
-  }
-
  private:
+  friend class internal::BaseClientCall;
+  friend class Client;
+
+  constexpr Client* client() const { return client_; }
+  constexpr void set_client(Client* client) { client_ = client; }
+
   uint32_t id_;
   ChannelOutput* output_;
+  Client* client_;
 };
 
 }  // namespace pw::rpc
diff --git a/pw_rpc/public/pw_rpc/client.h b/pw_rpc/public/pw_rpc/client.h
index db78298..6e7def1 100644
--- a/pw_rpc/public/pw_rpc/client.h
+++ b/pw_rpc/public/pw_rpc/client.h
@@ -17,18 +17,22 @@
 #include <span>
 
 #include "pw_bytes/span.h"
-#include "pw_rpc/channel.h"
+#include "pw_rpc/internal/base_client_call.h"
 #include "pw_rpc/internal/channel.h"
-#include "pw_rpc/internal/endpoint.h"
-#include "pw_rpc/internal/lock.h"
 
 namespace pw::rpc {
 
-class Client : public internal::Endpoint {
+class Client {
  public:
   // Creates a client that uses a set of RPC channels. Channels can be shared
   // between a client and a server, but not between multiple clients.
-  _PW_RPC_CONSTEXPR Client(std::span<Channel> channels) : Endpoint(channels) {}
+  constexpr Client(std::span<Channel> channels)
+      : channels_(static_cast<internal::Channel*>(channels.data()),
+                  channels.size()) {
+    for (Channel& channel : channels_) {
+      channel.set_client(this);
+    };
+  }
 
   // Processes an incoming RPC packet. The packet may be an RPC response or a
   // control packet, the result of which is processed in this function. Returns
@@ -37,15 +41,22 @@
   //   OK - The packet was processed by the client.
   //   DATA_LOSS - Failed to decode the packet.
   //   INVALID_ARGUMENT - The packet is intended for a server, not a client.
-  //   UNAVAILABLE - No RPC channel with the requested ID was found.
+  //   NOT_FOUND - The packet belongs to an unknown RPC call.
+  //   UNIMPLEMENTED - Received a type of packet that the client doesn't know
+  //       how to handle.
   //
-  Status ProcessPacket(ConstByteSpan data)
-      PW_LOCKS_EXCLUDED(internal::rpc_lock());
+  Status ProcessPacket(ConstByteSpan data);
+
+  size_t active_calls() const { return calls_.size(); }
 
  private:
-  // Remove these internal::Endpoint functions from the public interface.
-  using Endpoint::active_call_count;
-  using Endpoint::GetInternalChannel;
+  friend class internal::BaseClientCall;
+
+  Status RegisterCall(internal::BaseClientCall& call);
+  void RemoveCall(const internal::BaseClientCall& call) { calls_.remove(call); }
+
+  std::span<internal::Channel> channels_;
+  IntrusiveList<internal::BaseClientCall> calls_;
 };
 
 }  // namespace pw::rpc
diff --git a/pw_rpc/public/pw_rpc/client_server.h b/pw_rpc/public/pw_rpc/client_server.h
index 92f99da..219ed67 100644
--- a/pw_rpc/public/pw_rpc/client_server.h
+++ b/pw_rpc/public/pw_rpc/client_server.h
@@ -22,27 +22,17 @@
 // a device needs to function as both.
 class ClientServer {
  public:
-  _PW_RPC_CONSTEXPR ClientServer(std::span<Channel> channels)
+  constexpr ClientServer(std::span<Channel> channels)
       : client_(channels), server_(channels) {}
 
   // Sends a packet to either the client or the server, depending on its type.
-  //
-  // ProcessPacket optionally accepts a ChannelOutput as a second argument. If
-  // provided, the server will be able to dynamically assign channels as
-  // requests come in instead of requiring channels to be known at compile time.
-  Status ProcessPacket(ConstByteSpan packet) {
-    return ProcessPacket(packet, nullptr);
-  }
-  Status ProcessPacket(ConstByteSpan packet, ChannelOutput& interface) {
-    return ProcessPacket(packet, &interface);
-  }
+  Status ProcessPacket(std::span<const std::byte> packet,
+                       ChannelOutput& interface);
 
   constexpr Client& client() { return client_; }
   constexpr Server& server() { return server_; }
 
  private:
-  Status ProcessPacket(ConstByteSpan packet, ChannelOutput* interface);
-
   Client client_;
   Server server_;
 };
diff --git a/pw_rpc/public/pw_rpc/integration_test_socket_client.h b/pw_rpc/public/pw_rpc/integration_test_socket_client.h
deleted file mode 100644
index 40db2ae..0000000
--- a/pw_rpc/public/pw_rpc/integration_test_socket_client.h
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <span>
-#include <thread>
-
-#include "pw_hdlc/rpc_channel.h"
-#include "pw_hdlc/rpc_packets.h"
-#include "pw_rpc/integration_testing.h"
-#include "pw_status/try.h"
-#include "pw_stream/socket_stream.h"
-
-namespace pw::rpc::integration_test {
-
-// Wraps an RPC client with a socket stream and a channel configured to use it.
-// Useful for integration tests that run across a socket.
-template <size_t kMaxTransmissionUnit>
-class SocketClientContext {
- public:
-  constexpr SocketClientContext()
-      : channel_output_(stream_, hdlc::kDefaultRpcAddress, "socket"),
-        channel_(Channel::Create<kChannelId>(&channel_output_)),
-        client_(std::span(&channel_, 1)) {}
-
-  Client& client() { return client_; }
-
-  // Connects to the specified host:port and starts a background thread to read
-  // packets from the socket.
-  Status Start(const char* host, uint16_t port) {
-    PW_TRY(stream_.Connect(host, port));
-    std::thread{&SocketClientContext::ProcessPackets, this}.detach();
-    return OkStatus();
-  }
-
-  // Calls Start for localhost.
-  Status Start(uint16_t port) { return Start("localhost", port); }
-
- private:
-  void ProcessPackets();
-
-  stream::SocketStream stream_;
-  hdlc::RpcChannelOutput channel_output_;
-  Channel channel_;
-  Client client_;
-};
-
-template <size_t kMaxTransmissionUnit>
-void SocketClientContext<kMaxTransmissionUnit>::ProcessPackets() {
-  std::byte decode_buffer[kMaxTransmissionUnit];
-  hdlc::Decoder decoder(decode_buffer);
-
-  while (true) {
-    std::byte byte[1];
-    Result<ByteSpan> read = stream_.Read(byte);
-
-    if (!read.ok() || read->size() == 0u) {
-      continue;
-    }
-
-    if (auto result = decoder.Process(*byte); result.ok()) {
-      hdlc::Frame& frame = result.value();
-      if (frame.address() == hdlc::kDefaultRpcAddress) {
-        PW_ASSERT(client_.ProcessPacket(frame.data()).ok());
-      }
-    }
-  }
-}
-
-}  // namespace pw::rpc::integration_test
diff --git a/pw_rpc/public/pw_rpc/integration_testing.h b/pw_rpc/public/pw_rpc/integration_testing.h
deleted file mode 100644
index 3f80049..0000000
--- a/pw_rpc/public/pw_rpc/integration_testing.h
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/client.h"
-#include "pw_status/status.h"
-
-namespace pw::rpc::integration_test {
-
-// The RPC channel for integration test RPCs.
-inline constexpr uint32_t kChannelId = 1;
-
-// Returns the global RPC client for integration test use.
-Client& client();
-
-// Initializes logging and the global RPC client for integration testing. Starts
-// a background thread that processes incoming.
-Status InitializeClient(int argc,
-                        char* argv[],
-                        const char* usage_args = "PORT");
-
-}  // namespace pw::rpc::integration_test
diff --git a/pw_rpc/public/pw_rpc/internal/base_client_call.h b/pw_rpc/public/pw_rpc/internal/base_client_call.h
new file mode 100644
index 0000000..24acafc
--- /dev/null
+++ b/pw_rpc/public/pw_rpc/internal/base_client_call.h
@@ -0,0 +1,84 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_assert/light.h"
+#include "pw_containers/intrusive_list.h"
+#include "pw_rpc/internal/channel.h"
+#include "pw_rpc/internal/packet.h"
+#include "pw_status/status.h"
+
+namespace pw::rpc::internal {
+
+// Base class representing an active client-side RPC call. Implementations
+// derive from this class and provide a packet handler function which is
+// called with a reference to the ClientCall object and the received packet.
+class BaseClientCall : public IntrusiveList<BaseClientCall>::Item {
+ public:
+  using ResponseHandler = void (*)(BaseClientCall&, const Packet&);
+
+  constexpr BaseClientCall(rpc::Channel* channel,
+                           uint32_t service_id,
+                           uint32_t method_id,
+                           ResponseHandler handler)
+      : channel_(static_cast<Channel*>(channel)),
+        service_id_(service_id),
+        method_id_(method_id),
+        handler_(handler),
+        active_(true) {
+    PW_ASSERT(channel_ != nullptr);
+    Register();
+  }
+
+  ~BaseClientCall() { Unregister(); }
+
+  BaseClientCall(const BaseClientCall&) = delete;
+  BaseClientCall& operator=(const BaseClientCall&) = delete;
+
+  BaseClientCall(BaseClientCall&& other) { *this = std::move(other); }
+  BaseClientCall& operator=(BaseClientCall&& other);
+
+  constexpr bool active() const { return active_; }
+
+  void Cancel();
+
+ protected:
+  constexpr Channel& channel() const { return *channel_; }
+  constexpr uint32_t service_id() const { return service_id_; }
+  constexpr uint32_t method_id() const { return method_id_; }
+
+  std::span<std::byte> AcquirePayloadBuffer();
+  Status ReleasePayloadBuffer(std::span<const std::byte> payload);
+
+  void Unregister();
+
+ private:
+  friend class rpc::Client;
+
+  void Register();
+
+  void HandleResponse(const Packet& packet) { handler_(*this, packet); }
+
+  Packet NewPacket(PacketType type,
+                   std::span<const std::byte> payload = {}) const;
+
+  Channel* channel_;
+  uint32_t service_id_;
+  uint32_t method_id_;
+  Channel::OutputBuffer request_;
+  ResponseHandler handler_;
+  bool active_;
+};
+
+}  // namespace pw::rpc::internal
diff --git a/pw_rpc/public/pw_rpc/internal/base_server_writer.h b/pw_rpc/public/pw_rpc/internal/base_server_writer.h
new file mode 100644
index 0000000..895c1b8
--- /dev/null
+++ b/pw_rpc/public/pw_rpc/internal/base_server_writer.h
@@ -0,0 +1,97 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 <cstddef>
+#include <span>
+#include <utility>
+
+#include "pw_containers/intrusive_list.h"
+#include "pw_rpc/internal/call.h"
+#include "pw_rpc/internal/channel.h"
+#include "pw_rpc/internal/method.h"
+#include "pw_rpc/service.h"
+#include "pw_status/status.h"
+
+namespace pw::rpc {
+
+class Server;
+
+namespace internal {
+
+class Packet;
+
+// Internal ServerWriter base class. ServerWriters are used to stream responses.
+// Implementations must provide a derived class that provides the interface for
+// sending responses.
+class BaseServerWriter : public IntrusiveList<BaseServerWriter>::Item {
+ public:
+  BaseServerWriter(ServerCall& call);
+
+  BaseServerWriter(const BaseServerWriter&) = delete;
+
+  BaseServerWriter(BaseServerWriter&& other) : state_(kClosed) {
+    *this = std::move(other);
+  }
+
+  ~BaseServerWriter() { Finish(); }
+
+  BaseServerWriter& operator=(const BaseServerWriter&) = delete;
+
+  BaseServerWriter& operator=(BaseServerWriter&& other);
+
+  // True if the ServerWriter is active and ready to send responses.
+  bool open() const { return state_ == kOpen; }
+
+  uint32_t channel_id() const { return call_.channel().id(); }
+  uint32_t service_id() const { return call_.service().id(); }
+  uint32_t method_id() const;
+
+  // Closes the ServerWriter, if it is open.
+  Status Finish(Status status = OkStatus());
+
+ protected:
+  constexpr BaseServerWriter() : state_{kClosed} {}
+
+  const Method& method() const { return call_.method(); }
+
+  const Channel& channel() const { return call_.channel(); }
+
+  constexpr const Channel::OutputBuffer& buffer() const { return response_; }
+
+  // Acquires a buffer into which to write a payload. The BaseServerWriter MUST
+  // be open when this is called!
+  std::span<std::byte> AcquirePayloadBuffer();
+
+  // Releases the buffer, sending a packet with the specified payload. The
+  // BaseServerWriter MUST be open when this is called!
+  Status ReleasePayloadBuffer(std::span<const std::byte> payload);
+
+  // Releases the buffer without sending a packet.
+  Status ReleasePayloadBuffer();
+
+ private:
+  friend class rpc::Server;
+
+  void Close();
+
+  Packet ResponsePacket(std::span<const std::byte> payload = {}) const;
+
+  ServerCall call_;
+  Channel::OutputBuffer response_;
+  enum { kClosed, kOpen } state_;
+};
+
+}  // namespace internal
+}  // namespace pw::rpc
diff --git a/pw_rpc/public/pw_rpc/internal/call.h b/pw_rpc/public/pw_rpc/internal/call.h
index 8edff6f..bdfa56c 100644
--- a/pw_rpc/public/pw_rpc/internal/call.h
+++ b/pw_rpc/public/pw_rpc/internal/call.h
@@ -1,4 +1,4 @@
-// Copyright 2021 The Pigweed Authors
+// Copyright 2020 The Pigweed Authors
 //
 // 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
@@ -13,348 +13,76 @@
 // the License.
 #pragma once
 
-#include <cassert>
 #include <cstddef>
-#include <span>
-#include <utility>
+#include <cstdint>
 
-#include "pw_containers/intrusive_list.h"
-#include "pw_function/function.h"
-#include "pw_rpc/internal/call_context.h"
+#include "pw_assert/assert.h"
 #include "pw_rpc/internal/channel.h"
-#include "pw_rpc/internal/lock.h"
-#include "pw_rpc/internal/method.h"
-#include "pw_rpc/internal/packet.h"
-#include "pw_rpc/method_type.h"
-#include "pw_rpc/service.h"
-#include "pw_status/status.h"
-#include "pw_sync/lock_annotations.h"
 
 namespace pw::rpc {
 
-class Writer;
+class ServerContext;
+class Service;
 
 namespace internal {
 
-class Endpoint;
-class Packet;
+class Method;
+class Server;
 
-// Internal RPC Call class. The Call is used to respond to any type of RPC.
-// Public classes like ServerWriters inherit from it with private inheritance
-// and provide a public API for their use case. The Call's public API is used by
-// the Server and Client classes.
+// Collects information for an ongoing RPC being processed by the server.
+// The Server creates a ServerCall object to represent a method invocation. The
+// ServerCall is copied into a ServerWriter or ServerReader for streaming RPCs.
 //
-// Private inheritance is used in place of composition or more complex
-// inheritance hierarchy so that these objects all inherit from a common
-// IntrusiveList::Item object. Private inheritance also gives the derived classs
-// full control over their interfaces.
-class Call : public IntrusiveList<Call>::Item {
+// ServerCall is a strictly internal class. ServerContext is the public
+// interface to the internal::ServerCall.
+class ServerCall {
  public:
-  Call(const Call&) = delete;
+  constexpr ServerCall()
+      : server_(nullptr),
+        channel_(nullptr),
+        service_(nullptr),
+        method_(nullptr) {}
 
-  // Move support is provided to derived classes through the MoveFrom function.
-  Call(Call&&) = delete;
+  constexpr ServerCall(Server& server,
+                       Channel& channel,
+                       Service& service,
+                       const internal::Method& method)
+      : server_(&server),
+        channel_(&channel),
+        service_(&service),
+        method_(&method) {}
 
-  Call& operator=(const Call&) = delete;
-  Call& operator=(Call&&) = delete;
+  constexpr ServerCall(const ServerCall&) = default;
+  constexpr ServerCall& operator=(const ServerCall&) = default;
 
-  // True if the Call is active and ready to send responses.
-  [[nodiscard]] bool active() const PW_LOCKS_EXCLUDED(rpc_lock()) {
-    LockGuard lock(rpc_lock());
-    return active_locked();
+  // Access the ServerContext for this call. Defined in pw_rpc/server_context.h.
+  ServerContext& context();
+
+  Server& server() const {
+    PW_DCHECK_NOTNULL(server_);
+    return *server_;
   }
 
-  [[nodiscard]] bool active_locked() const
-      PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    return rpc_state_ == kActive;
+  Channel& channel() const {
+    PW_DCHECK_NOTNULL(channel_);
+    return *channel_;
   }
 
-  uint32_t id() const PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) { return id_; }
-
-  uint32_t channel_id() const PW_LOCKS_EXCLUDED(rpc_lock()) {
-    LockGuard lock(rpc_lock());
-    return channel_id_locked();
-  }
-  uint32_t channel_id_locked() const PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    return channel_id_;
-  }
-  uint32_t service_id() const PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    return service_id_;
-  }
-  uint32_t method_id() const PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    return method_id_;
+  Service& service() const {
+    PW_DCHECK_NOTNULL(service_);
+    return *service_;
   }
 
-  // Closes the Call and sends a RESPONSE packet, if it is active. Returns the
-  // status from sending the packet, or FAILED_PRECONDITION if the Call is not
-  // active.
-  Status CloseAndSendResponse(ConstByteSpan response, Status status)
-      PW_LOCKS_EXCLUDED(rpc_lock()) {
-    LockGuard lock(rpc_lock());
-    return CloseAndSendResponseLocked(response, status);
+  const internal::Method& method() const {
+    PW_DCHECK_NOTNULL(method_);
+    return *method_;
   }
 
-  Status CloseAndSendResponseLocked(ConstByteSpan response, Status status)
-      PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    return CloseAndSendFinalPacketLocked(
-        PacketType::RESPONSE, response, status);
-  }
-
-  Status CloseAndSendResponse(Status status) PW_LOCKS_EXCLUDED(rpc_lock()) {
-    return CloseAndSendResponse({}, status);
-  }
-
-  Status CloseAndSendServerErrorLocked(Status error)
-      PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    return CloseAndSendFinalPacketLocked(PacketType::SERVER_ERROR, {}, error);
-  }
-
-  // Public call that ends the client stream for a client call.
-  Status CloseClientStream() PW_LOCKS_EXCLUDED(rpc_lock()) {
-    LockGuard lock(rpc_lock());
-    return CloseClientStreamLocked();
-  }
-
-  // Internal call that closes the client stream.
-  Status CloseClientStreamLocked() PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    client_stream_state_ = kClientStreamInactive;
-    return SendPacket(PacketType::CLIENT_STREAM_END, {}, {});
-  }
-
-  // Sends a payload in either a server or client stream packet.
-  Status Write(ConstByteSpan payload) PW_LOCKS_EXCLUDED(rpc_lock()) {
-    LockGuard lock(rpc_lock());
-    return WriteLocked(payload);
-  }
-
-  Status WriteLocked(ConstByteSpan payload)
-      PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock());
-
-  // Sends the initial request for a client call. If the request fails, the call
-  // is closed.
-  void SendInitialClientRequest(ConstByteSpan payload)
-      PW_UNLOCK_FUNCTION(rpc_lock()) {
-    // TODO(pwbug/597): Ensure the call object is locked before releasing the
-    //     RPC mutex.
-    if (const Status status = SendPacket(PacketType::REQUEST, payload);
-        !status.ok()) {
-      HandleError(status);
-    } else {
-      rpc_lock().unlock();
-    }
-  }
-
-  // Whenever a payload arrives (in a server/client stream or in a response),
-  // call the on_next_ callback.
-  // Precondition: rpc_lock() must be held.
-  void HandlePayload(ConstByteSpan message) const
-      PW_UNLOCK_FUNCTION(rpc_lock()) {
-    const bool invoke = on_next_ != nullptr;
-    // TODO(pwbug/597): Ensure on_next_ is properly guarded.
-    rpc_lock().unlock();
-
-    if (invoke) {
-      on_next_(message);
-    }
-  }
-
-  // Handles an error condition for the call. This closes the call and calls the
-  // on_error callback, if set.
-  void HandleError(Status status) PW_UNLOCK_FUNCTION(rpc_lock()) {
-    UnregisterAndMarkClosed();
-    CallOnError(status);
-  }
-
-  // Aborts the RPC because its channel was closed. Does NOT unregister the
-  // call! The calls are removed when iterating over the list in the endpoint.
-  void HandleChannelClose() PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    // Locking here is problematic because CallOnError releases rpc_lock().
-    //
-    // pwbug/597 must be addressed before the locking here can be cleaned up.
-    MarkClosed();
-
-    CallOnError(Status::Aborted());
-
-    // Re-lock rpc_lock().
-    rpc_lock().lock();
-  }
-
-  bool has_client_stream() const PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    return HasClientStream(type_);
-  }
-
-  bool has_server_stream() const PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    return HasServerStream(type_);
-  }
-
-  bool client_stream_open() const PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    return client_stream_state_ == kClientStreamActive;
-  }
-
-  // Keep this public so the Nanopb implementation can set it from a helper
-  // function.
-  void set_on_next(Function<void(ConstByteSpan)>&& on_next)
-      PW_LOCKS_EXCLUDED(rpc_lock()) {
-    LockGuard lock(rpc_lock());
-    set_on_next_locked(std::move(on_next));
-  }
-
- protected:
-  // Creates an inactive Call.
-  constexpr Call()
-      : endpoint_{},
-        channel_id_{},
-        id_{},
-        service_id_{},
-        method_id_{},
-        rpc_state_{},
-        type_{},
-        call_type_{},
-        client_stream_state_ {}
-  {}
-
-  // Creates an active server-side Call.
-  Call(const CallContext& context, MethodType type)
-      : Call(context.server(),
-             context.call_id(),
-             context.channel_id(),
-             context.service().id(),
-             context.method().id(),
-             type,
-             kServerCall) {}
-
-  // Creates an active client-side Call.
-  Call(Endpoint& client,
-       uint32_t channel_id,
-       uint32_t service_id,
-       uint32_t method_id,
-       MethodType type);
-
-  // This call must be in a closed state when this is called.
-  void MoveFrom(Call& other) PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock());
-
-  Endpoint& endpoint() const PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    return *endpoint_;
-  }
-
-  void set_on_next_locked(Function<void(ConstByteSpan)>&& on_next)
-      PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    on_next_ = std::move(on_next);
-  }
-
-  void set_on_error(Function<void(Status)>&& on_error)
-      PW_LOCKS_EXCLUDED(rpc_lock()) {
-    LockGuard lock(rpc_lock());
-    set_on_error_locked(std::move(on_error));
-  }
-
-  void set_on_error_locked(Function<void(Status)>&& on_error)
-      PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    on_error_ = std::move(on_error);
-  }
-
-  // Calls the on_error callback without closing the RPC. This is used when the
-  // call has already completed.
-  void CallOnError(Status error) PW_UNLOCK_FUNCTION(rpc_lock()) {
-    const bool invoke = on_error_ != nullptr;
-
-    // TODO(pwbug/597): Ensure on_error_ is properly guarded.
-    rpc_lock().unlock();
-    if (invoke) {
-      on_error_(error);
-    }
-  }
-
-  void MarkClientStreamCompleted() PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    client_stream_state_ = kClientStreamInactive;
-  }
-
-  Status CloseAndSendResponseLocked(Status status)
-      PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    return CloseAndSendFinalPacketLocked(PacketType::RESPONSE, {}, status);
-  }
-
-  // Cancels an RPC. For client calls only.
-  Status Cancel() PW_LOCKS_EXCLUDED(rpc_lock()) {
-    LockGuard lock(rpc_lock());
-    return CloseAndSendFinalPacketLocked(
-        PacketType::CLIENT_ERROR, {}, Status::Cancelled());
-  }
-
-  // Unregisters the RPC from the endpoint & marks as closed. The call may be
-  // active or inactive when this is called.
-  void UnregisterAndMarkClosed() PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock());
-
-  // Define conversions to the generic server/client RPC writer class. These
-  // functions are defined in pw_rpc/writer.h after the Writer class is defined.
-  constexpr operator Writer&();
-  constexpr operator const Writer&() const;
-
  private:
-  enum CallType : bool { kServerCall, kClientCall };
-
-  // Common constructor for server & client calls.
-  Call(Endpoint& endpoint,
-       uint32_t id,
-       uint32_t channel_id,
-       uint32_t service_id,
-       uint32_t method_id,
-       MethodType type,
-       CallType call_type);
-
-  Packet MakePacket(PacketType type,
-                    ConstByteSpan payload,
-                    Status status = OkStatus()) const
-      PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    return Packet(type,
-                  channel_id_locked(),
-                  service_id(),
-                  method_id(),
-                  id_,
-                  payload,
-                  status);
-  }
-
-  void MarkClosed() PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    channel_id_ = Channel::kUnassignedChannelId;
-    rpc_state_ = kInactive;
-    client_stream_state_ = kClientStreamInactive;
-  }
-
-  // Sends a payload with the specified type. The payload may either be in a
-  // previously acquired buffer or in a standalone buffer.
-  //
-  // Returns FAILED_PRECONDITION if the call is not active().
-  Status SendPacket(PacketType type,
-                    ConstByteSpan payload,
-                    Status status = OkStatus())
-      PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock());
-
-  Status CloseAndSendFinalPacketLocked(PacketType type,
-                                       ConstByteSpan response,
-                                       Status status)
-      PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock());
-
-  internal::Endpoint* endpoint_ PW_GUARDED_BY(rpc_lock());
-  uint32_t channel_id_ PW_GUARDED_BY(rpc_lock());
-  uint32_t id_ PW_GUARDED_BY(rpc_lock());
-  uint32_t service_id_ PW_GUARDED_BY(rpc_lock());
-  uint32_t method_id_ PW_GUARDED_BY(rpc_lock());
-
-  enum : bool { kInactive, kActive } rpc_state_ PW_GUARDED_BY(rpc_lock());
-  MethodType type_ PW_GUARDED_BY(rpc_lock());
-  CallType call_type_ PW_GUARDED_BY(rpc_lock());
-  enum : bool {
-    kClientStreamInactive,
-    kClientStreamActive,
-  } client_stream_state_ PW_GUARDED_BY(rpc_lock());
-
-  // Called when the RPC is terminated due to an error.
-  Function<void(Status error)> on_error_;
-
-  // Called when a request is received. Only used for RPCs with client streams.
-  // The raw payload buffer is passed to the callback.
-  Function<void(ConstByteSpan payload)> on_next_;
+  Server* server_;
+  Channel* channel_;
+  Service* service_;
+  const internal::Method* method_;
 };
 
 }  // namespace internal
diff --git a/pw_rpc/public/pw_rpc/internal/call_context.h b/pw_rpc/public/pw_rpc/internal/call_context.h
deleted file mode 100644
index cc15cbd..0000000
--- a/pw_rpc/public/pw_rpc/internal/call_context.h
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstddef>
-#include <cstdint>
-
-#include "pw_rpc/internal/channel.h"
-
-namespace pw::rpc {
-
-class Service;
-
-namespace internal {
-
-class Endpoint;
-class Method;
-
-// The Server creates a CallContext object to represent a method invocation. The
-// CallContext is used to initialize a call object for the RPC.
-class CallContext {
- public:
-  constexpr CallContext(Endpoint& server,
-                        uint32_t channel_id,
-                        Service& service,
-                        const internal::Method& method,
-                        uint32_t call_id)
-      : server_(server),
-        channel_id_(channel_id),
-        service_(service),
-        method_(method),
-        call_id_(call_id) {}
-
-  constexpr Endpoint& server() const { return server_; }
-
-  constexpr const uint32_t& channel_id() const { return channel_id_; }
-
-  constexpr Service& service() const { return service_; }
-
-  constexpr const internal::Method& method() const { return method_; }
-
-  constexpr const uint32_t& call_id() const { return call_id_; }
-
-  // For testing use only
-  void set_channel_id(uint32_t channel_id) { channel_id_ = channel_id; }
-
- private:
-  Endpoint& server_;
-  uint32_t channel_id_;
-  Service& service_;
-  const internal::Method& method_;
-  uint32_t call_id_;
-};
-
-}  // namespace internal
-}  // namespace pw::rpc
diff --git a/pw_rpc/public/pw_rpc/internal/channel.h b/pw_rpc/public/pw_rpc/internal/channel.h
index 2d62cc6..077ed6a 100644
--- a/pw_rpc/public/pw_rpc/internal/channel.h
+++ b/pw_rpc/public/pw_rpc/internal/channel.h
@@ -16,17 +16,12 @@
 #include <span>
 
 #include "pw_assert/assert.h"
-#include "pw_bytes/span.h"
 #include "pw_rpc/channel.h"
-#include "pw_rpc/internal/lock.h"
-#include "pw_rpc/internal/packet.h"
 #include "pw_status/status.h"
 
 namespace pw::rpc::internal {
 
-// Returns a portion of the encoding buffer that may be used to encode an
-// outgoing payload.
-ByteSpan GetPayloadBuffer() PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock());
+class Packet;
 
 class Channel : public rpc::Channel {
  public:
@@ -35,13 +30,60 @@
   constexpr Channel(uint32_t id, ChannelOutput* output)
       : rpc::Channel(id, output) {}
 
-  // Allow closing a channel for internal API users.
-  using rpc::Channel::Close;
+  class OutputBuffer {
+   public:
+    constexpr OutputBuffer() = default;
 
-  // Allow setting the channel ID for tests.
-  using rpc::Channel::set_channel_id;
+    OutputBuffer(const OutputBuffer&) = delete;
 
-  Status Send(const Packet& packet) PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock());
+    OutputBuffer(OutputBuffer&& other) { *this = std::move(other); }
+
+    ~OutputBuffer() { PW_DCHECK(buffer_.empty()); }
+
+    OutputBuffer& operator=(const OutputBuffer&) = delete;
+
+    OutputBuffer& operator=(OutputBuffer&& other) {
+      PW_DCHECK(buffer_.empty());
+      buffer_ = other.buffer_;
+      other.buffer_ = {};
+      return *this;
+    }
+
+    // Returns a portion of this OutputBuffer to use as the packet payload.
+    std::span<std::byte> payload(const Packet& packet) const;
+
+    bool Contains(std::span<const std::byte> buffer) const {
+      return buffer.data() >= buffer_.data() &&
+             buffer.data() + buffer.size() <= buffer_.data() + buffer_.size();
+    }
+
+    bool empty() const { return buffer_.empty(); }
+
+   private:
+    friend class Channel;
+
+    explicit constexpr OutputBuffer(std::span<std::byte> buffer)
+        : buffer_(buffer) {}
+
+    std::span<std::byte> buffer_;
+  };
+
+  // Acquires a buffer for the packet.
+  OutputBuffer AcquireBuffer() const {
+    return OutputBuffer(output().AcquireBuffer());
+  }
+
+  Status Send(const internal::Packet& packet) {
+    OutputBuffer buffer = AcquireBuffer();
+    return Send(buffer, packet);
+  }
+
+  Status Send(OutputBuffer& output, const internal::Packet& packet);
+
+  void Release(OutputBuffer& buffer) {
+    output().DiscardBuffer(buffer.buffer_);
+    buffer.buffer_ = {};
+  }
 };
 
 }  // namespace pw::rpc::internal
diff --git a/pw_rpc/public/pw_rpc/internal/channel_list.h b/pw_rpc/public/pw_rpc/internal/channel_list.h
deleted file mode 100644
index c21224b..0000000
--- a/pw_rpc/public/pw_rpc/internal/channel_list.h
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <span>
-#include <vector>
-
-#include "pw_rpc/internal/channel.h"
-#include "pw_rpc/internal/config.h"
-
-namespace pw::rpc::internal {
-
-#if PW_RPC_DYNAMIC_ALLOCATION && !defined(__cpp_lib_constexpr_vector)
-#define _PW_RPC_CONSTEXPR
-#else
-#define _PW_RPC_CONSTEXPR constexpr
-#endif  // PW_RPC_DYNAMIC_ALLOCATION && !defined(__cpp_lib_constexpr_vector)
-
-class ChannelList {
- public:
-  _PW_RPC_CONSTEXPR ChannelList(std::span<Channel> channels)
-      : channels_(channels.begin(), channels.end()) {}
-
-  // Returns the first channel with the matching ID or nullptr if none match.
-  // Except for Channel::kUnassignedChannelId, there should be no duplicate
-  // channels.
-  const Channel* Get(uint32_t channel_id) const;
-
-  Channel* Get(uint32_t channel_id) {
-    return const_cast<Channel*>(
-        static_cast<const ChannelList&>(*this).Get(channel_id));
-  }
-
-  // Adds the channel with the requested ID to the list. Returns:
-  //
-  //   OK - the channel was added
-  //   ALREADY_EXISTS - a channel with this ID is already present; remove it
-  //       first
-  //   RESOURCE_EXHAUSTED - no unassigned channels are available; only possible
-  //       if PW_RPC_DYNAMIC_ALLOCATION is disabled
-  //
-  Status Add(uint32_t channel_id, ChannelOutput& output);
-
-  // Removes the channel with the requested ID. Returns:
-  //
-  //   OK - the channel was removed
-  //   NOT_FOUND - no channel with the provided ID was found
-  //
-  Status Remove(uint32_t channel_id);
-
-#if PW_RPC_DYNAMIC_ALLOCATION
-  std::vector<Channel> channels_;
-#else
-  std::span<Channel> channels_;
-#endif  // PW_RPC_DYNAMIC_ALLOCATION
-};
-
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/public/pw_rpc/internal/client_call.h b/pw_rpc/public/pw_rpc/internal/client_call.h
deleted file mode 100644
index c4d9c10..0000000
--- a/pw_rpc/public/pw_rpc/internal/client_call.h
+++ /dev/null
@@ -1,212 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_bytes/span.h"
-#include "pw_function/function.h"
-#include "pw_rpc/internal/call.h"
-#include "pw_rpc/internal/lock.h"
-
-namespace pw::rpc::internal {
-
-// A Call object, as used by an RPC client.
-class ClientCall : public Call {
- public:
-  ~ClientCall() PW_LOCKS_EXCLUDED(rpc_lock()) {
-    rpc_lock().lock();
-    CloseClientCall();
-    rpc_lock().unlock();
-  }
-
- protected:
-  constexpr ClientCall() = default;
-
-  ClientCall(Endpoint& client,
-             uint32_t channel_id,
-             uint32_t service_id,
-             uint32_t method_id,
-             MethodType type)
-      : Call(client, channel_id, service_id, method_id, type) {}
-
-  // Sends CLIENT_STREAM_END if applicable, releases any held payload buffer,
-  // and marks the call as closed.
-  void CloseClientCall() PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock());
-
-  void MoveClientCallFrom(ClientCall& other)
-      PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    CloseClientCall();
-    MoveFrom(other);
-  }
-};
-
-// Unary response client calls receive both a payload and the status in their
-// on_completed callback. The on_next callback is not used.
-class UnaryResponseClientCall : public ClientCall {
- public:
-  template <typename CallType>
-  static CallType Start(Endpoint& client,
-                        uint32_t channel_id,
-                        uint32_t service_id,
-                        uint32_t method_id,
-                        Function<void(ConstByteSpan, Status)>&& on_completed,
-                        Function<void(Status)>&& on_error,
-                        ConstByteSpan request) PW_LOCKS_EXCLUDED(rpc_lock()) {
-    rpc_lock().lock();
-    CallType call(client, channel_id, service_id, method_id);
-    call.set_on_completed_locked(std::move(on_completed));
-    call.set_on_error_locked(std::move(on_error));
-
-    call.SendInitialClientRequest(request);
-    return call;
-  }
-
-  void HandleCompleted(ConstByteSpan response, Status status)
-      PW_UNLOCK_FUNCTION(rpc_lock()) {
-    const bool invoke_callback = on_completed_ != nullptr;
-    UnregisterAndMarkClosed();
-
-    rpc_lock().unlock();
-    if (invoke_callback) {
-      on_completed_(response, status);
-    }
-  }
-
- protected:
-  constexpr UnaryResponseClientCall() = default;
-
-  UnaryResponseClientCall(Endpoint& client,
-                          uint32_t channel_id,
-                          uint32_t service_id,
-                          uint32_t method_id,
-                          MethodType type)
-      : ClientCall(client, channel_id, service_id, method_id, type) {}
-
-  UnaryResponseClientCall(UnaryResponseClientCall&& other) {
-    *this = std::move(other);
-  }
-
-  UnaryResponseClientCall& operator=(UnaryResponseClientCall&& other)
-      PW_LOCKS_EXCLUDED(rpc_lock()) {
-    LockGuard lock(rpc_lock());
-    MoveUnaryResponseClientCallFrom(other);
-    return *this;
-  }
-
-  void MoveUnaryResponseClientCallFrom(UnaryResponseClientCall& other)
-      PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    MoveClientCallFrom(other);
-    on_completed_ = std::move(other.on_completed_);
-  }
-
-  void set_on_completed(Function<void(ConstByteSpan, Status)>&& on_completed)
-      PW_LOCKS_EXCLUDED(rpc_lock()) {
-    // TODO(pwbug/597): Ensure on_completed_ is properly guarded.
-    LockGuard lock(rpc_lock());
-    set_on_completed_locked(std::move(on_completed));
-  }
-
-  void set_on_completed_locked(
-      Function<void(ConstByteSpan, Status)>&& on_completed)
-      PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    on_completed_ = std::move(on_completed);
-  }
-
- private:
-  using internal::ClientCall::set_on_next;  // Not used in unary response calls.
-
-  Function<void(ConstByteSpan, Status)> on_completed_;
-};
-
-// Stream response client calls only receive the status in their on_completed
-// callback. Payloads are sent through the on_next callback.
-class StreamResponseClientCall : public ClientCall {
- public:
-  template <typename CallType>
-  static CallType Start(Endpoint& client,
-                        uint32_t channel_id,
-                        uint32_t service_id,
-                        uint32_t method_id,
-                        Function<void(ConstByteSpan)>&& on_next,
-                        Function<void(Status)>&& on_completed,
-                        Function<void(Status)>&& on_error,
-                        ConstByteSpan request) {
-    rpc_lock().lock();
-    CallType call(client, channel_id, service_id, method_id);
-
-    call.set_on_next_locked(std::move(on_next));
-    call.set_on_completed_locked(std::move(on_completed));
-    call.set_on_error_locked(std::move(on_error));
-
-    call.SendInitialClientRequest(request);
-    return call;
-  }
-
-  void HandleCompleted(Status status) PW_UNLOCK_FUNCTION(rpc_lock()) {
-    const bool invoke_callback = on_completed_ != nullptr;
-
-    UnregisterAndMarkClosed();
-    rpc_lock().unlock();
-
-    // TODO(pwbug/597): Ensure on_completed_ is properly guarded.
-    if (invoke_callback) {
-      on_completed_(status);
-    }
-  }
-
- protected:
-  constexpr StreamResponseClientCall() = default;
-
-  StreamResponseClientCall(Endpoint& client,
-                           uint32_t channel_id,
-                           uint32_t service_id,
-                           uint32_t method_id,
-                           MethodType type)
-      : ClientCall(client, channel_id, service_id, method_id, type) {}
-
-  StreamResponseClientCall(StreamResponseClientCall&& other) {
-    *this = std::move(other);
-  }
-
-  StreamResponseClientCall& operator=(StreamResponseClientCall&& other)
-      PW_LOCKS_EXCLUDED(rpc_lock()) {
-    LockGuard lock(rpc_lock());
-    MoveStreamResponseClientCallFrom(other);
-    return *this;
-  }
-
-  void MoveStreamResponseClientCallFrom(StreamResponseClientCall& other)
-      PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    MoveClientCallFrom(other);
-    on_completed_ = std::move(other.on_completed_);
-  }
-
-  void set_on_completed(Function<void(Status)>&& on_completed)
-      PW_LOCKS_EXCLUDED(rpc_lock()) {
-    // TODO(pwbug/597): Ensure on_completed_ is properly guarded.
-    LockGuard lock(rpc_lock());
-    set_on_completed_locked(std::move(on_completed));
-  }
-
-  void set_on_completed_locked(Function<void(Status)>&& on_completed)
-      PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    on_completed_ = std::move(on_completed);
-  }
-
- private:
-  Function<void(Status)> on_completed_;
-};
-
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/public/pw_rpc/internal/config.h b/pw_rpc/public/pw_rpc/internal/config.h
index 42e4ebb..aa9d9ee 100644
--- a/pw_rpc/public/pw_rpc/internal/config.h
+++ b/pw_rpc/public/pw_rpc/internal/config.h
@@ -16,18 +16,6 @@
 #pragma once
 
 #include <cstddef>
-#include <type_traits>
-
-// In client and bidirectional RPCs, pw_rpc clients may signal that they have
-// finished sending requests with a CLIENT_STREAM_END packet. While this can be
-// useful in some circumstances, it is often not necessary.
-//
-// This option controls whether or not include a callback that is called when
-// the client stream ends. The callback is included in all ServerReader/Writer
-// objects as a pw::Function, so may have a significant cost.
-#ifndef PW_RPC_CLIENT_STREAM_END_CALLBACK
-#define PW_RPC_CLIENT_STREAM_END_CALLBACK 0
-#endif  // PW_RPC_CLIENT_STREAM_END_CALLBACK
 
 // The Nanopb-based pw_rpc implementation allocates memory to use for Nanopb
 // structs for the request and response protobufs. The template function that
@@ -43,75 +31,14 @@
 #define PW_RPC_NANOPB_STRUCT_MIN_BUFFER_SIZE 64
 #endif  // PW_RPC_NANOPB_STRUCT_MIN_BUFFER_SIZE
 
-// Enable global synchronization for RPC calls. If this is set, a backend must
-// be configured for pw_sync:mutex.
-#ifndef PW_RPC_USE_GLOBAL_MUTEX
-#define PW_RPC_USE_GLOBAL_MUTEX 0
-#endif  // PW_RPC_USE_GLOBAL_MUTEX
-
-// Whether pw_rpc should use dynamic memory allocation internally. If enabled,
-// pw_rpc dynamically allocates channels and its encoding buffers. RPC users may
-// use dynamic allocation independently of this option (e.g. to allocate pw_rpc
-// call objects).
-//
-// The semantics for allocating and initializing channels change depending on
-// this option. If dynamic allocation is disabled, pw_rpc endpoints (servers or
-// clients) use an externally-allocated, fixed-size array of channels.
-// That array must include unassigned channels or existing channels must be
-// closed to add new channels.
-//
-// If dynamic allocation is enabled, an span of channels may be passed to the
-// endpoint at construction, but these channels are only used to initialize its
-// internal std::vector of channels. External channel objects are NOT used by
-// the endpoint cannot be updated if dynamic allocation is enabled. No
-// unassigned channels should be passed to the endpoint; they will be ignored.
-// Any number of channels may be added to the endpoint, without closing existing
-// channels, but adding channels will use more memory.
-#ifndef PW_RPC_DYNAMIC_ALLOCATION
-#define PW_RPC_DYNAMIC_ALLOCATION 0
-#endif  // PW_RPC_DYNAMIC_ALLOCATION
-
-#if PW_RPC_DYNAMIC_ALLOCATION && defined(PW_RPC_ENCODING_BUFFER_SIZE_BYTES)
-static_assert(false,
-              "PW_RPC_ENCODING_BUFFER_SIZE_BYTES cannot be set if "
-              "PW_RPC_DYNAMIC_ALLOCATION is enabled");
-#endif  // PW_RPC_DYNAMIC_ALLOCATION && PW_RPC_ENCODING_BUFFER_SIZE_BYTES
-
-// Size of the global RPC packet encoding buffer in bytes.
-#ifndef PW_RPC_ENCODING_BUFFER_SIZE_BYTES
-#define PW_RPC_ENCODING_BUFFER_SIZE_BYTES 512
-#endif  // PW_RPC_ENCODING_BUFFER_SIZE_BYTES
-
-// The log level to use for this module. Logs below this level are omitted.
-#ifndef PW_RPC_CONFIG_LOG_LEVEL
-#define PW_RPC_CONFIG_LOG_LEVEL PW_LOG_LEVEL_INFO
-#endif  // PW_RPC_CONFIG_LOG_LEVEL
-
-// The log module name to use for this module.
-#ifndef PW_RPC_CONFIG_LOG_MODULE_NAME
-#define PW_RPC_CONFIG_LOG_MODULE_NAME "PW_RPC"
-#endif  // PW_RPC_CONFIG_LOG_MODULE_NAME
-
 namespace pw::rpc::cfg {
 
-template <typename...>
-constexpr std::bool_constant<PW_RPC_CLIENT_STREAM_END_CALLBACK>
-    kClientStreamEndCallbackEnabled;
-
-template <typename...>
-constexpr std::bool_constant<PW_RPC_DYNAMIC_ALLOCATION>
-    kDynamicAllocationEnabled;
-
 inline constexpr size_t kNanopbStructMinBufferSize =
     PW_RPC_NANOPB_STRUCT_MIN_BUFFER_SIZE;
 
-inline constexpr size_t kEncodingBufferSizeBytes =
-    PW_RPC_ENCODING_BUFFER_SIZE_BYTES;
+}  // namespace pw::rpc::cfg
 
 #undef PW_RPC_NANOPB_STRUCT_MIN_BUFFER_SIZE
-#undef PW_RPC_ENCODING_BUFFER_SIZE_BYTES
-
-}  // namespace pw::rpc::cfg
 
 // This option determines whether to allocate the Nanopb structs on the stack or
 // in a global variable. Globally allocated structs are NOT thread safe, but
diff --git a/pw_rpc/public/pw_rpc/internal/endpoint.h b/pw_rpc/public/pw_rpc/internal/endpoint.h
deleted file mode 100644
index cbbb938..0000000
--- a/pw_rpc/public/pw_rpc/internal/endpoint.h
+++ /dev/null
@@ -1,133 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <span>
-
-#include "pw_containers/intrusive_list.h"
-#include "pw_result/result.h"
-#include "pw_rpc/internal/call.h"
-#include "pw_rpc/internal/channel.h"
-#include "pw_rpc/internal/channel_list.h"
-#include "pw_rpc/internal/lock.h"
-#include "pw_rpc/internal/packet.h"
-#include "pw_sync/lock_annotations.h"
-
-namespace pw::rpc::internal {
-
-// Manages a list of channels and a list of ongoing calls for either a server or
-// client.
-//
-// For clients, calls start when they send a REQUEST packet to a server. For
-// servers, calls start when the REQUEST packet is received. In either case,
-// calls add themselves to the Endpoint's list when they're started and
-// remove themselves when they complete. Calls do this through their associated
-// Server or Client object, which derive from Endpoint.
-class Endpoint {
- public:
-  ~Endpoint();
-
-  // Creates a channel with the provided ID and ChannelOutput, if a channel slot
-  // is available or can be allocated (if PW_RPC_DYNAMIC_ALLOCATION is enabled).
-  // Returns:
-  //
-  //   OK - the channel was opened successfully
-  //   ALREADY_EXISTS - a channel with this ID is already present; remove it
-  //       first
-  //   RESOURCE_EXHAUSTED - no unassigned channels are available and
-  //       PW_RPC_DYNAMIC_ALLOCATION is disabled
-  //
-  Status OpenChannel(uint32_t id, ChannelOutput& interface)
-      PW_LOCKS_EXCLUDED(rpc_lock()) {
-    LockGuard lock(rpc_lock());
-    return channels_.Add(id, interface);
-  }
-
-  // Closes a channel and terminates any pending calls on that channel.
-  // If the calls are client requests, their on_error callback will be
-  // called with the ABORTED status.
-  Status CloseChannel(uint32_t channel_id) PW_LOCKS_EXCLUDED(rpc_lock());
-
-  // For internal use only: returns the number calls in the RPC calls list.
-  size_t active_call_count() const PW_LOCKS_EXCLUDED(rpc_lock()) {
-    LockGuard lock(rpc_lock());
-    return calls_.size();
-  }
-
-  // For internal use only: finds an internal::Channel with this ID or nullptr
-  // if none matches.
-  Channel* GetInternalChannel(uint32_t channel_id)
-      PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    return channels_.Get(channel_id);
-  }
-
- protected:
-  _PW_RPC_CONSTEXPR Endpoint(std::span<rpc::Channel> channels)
-      : channels_(std::span(static_cast<internal::Channel*>(channels.data()),
-                            channels.size())),
-        next_call_id_(0) {}
-
-  // Parses an RPC packet and sets ongoing_call to the matching call, if any.
-  // Returns the parsed packet or an error.
-  Result<Packet> ProcessPacket(std::span<const std::byte> data,
-                               Packet::Destination destination)
-      PW_LOCKS_EXCLUDED(rpc_lock());
-
-  // Finds a call object for an ongoing call associated with this packet, if
-  // any. Returns nullptr if no matching call exists.
-  Call* FindCall(const Packet& packet) PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    return FindCallById(
-        packet.channel_id(), packet.service_id(), packet.method_id());
-  }
-
- private:
-  // Give Call access to the register/unregister functions.
-  friend class Call;
-
-  // Returns an ID that can be assigned to a new call.
-  uint32_t NewCallId() PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    // Call IDs are varint encoded. Limit the varint size to 2 bytes (14 usable
-    // bits).
-    constexpr uint32_t kMaxCallId = 1 << 14;
-    return (++next_call_id_) % kMaxCallId;
-  }
-
-  // Adds a call to the internal call registry. If a matching call already
-  // exists, it is cancelled locally (on_error called, no packet sent).
-  void RegisterCall(Call& call) PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock());
-
-  // Registers a call that is known to be unique. The calls list is NOT checked
-  // for existing calls.
-  void RegisterUniqueCall(Call& call) PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    calls_.push_front(call);
-  }
-
-  // Removes the provided call from the call registry.
-  void UnregisterCall(const Call& call)
-      PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock()) {
-    calls_.remove(call);
-  }
-
-  Call* FindCallById(uint32_t channel_id,
-                     uint32_t service_id,
-                     uint32_t method_id)
-      PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock());
-
-  ChannelList channels_ PW_GUARDED_BY(rpc_lock());
-  IntrusiveList<Call> calls_ PW_GUARDED_BY(rpc_lock());
-
-  uint32_t next_call_id_ PW_GUARDED_BY(rpc_lock());
-};
-
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/public/pw_rpc/internal/fake_channel_output.h b/pw_rpc/public/pw_rpc/internal/fake_channel_output.h
deleted file mode 100644
index cef049d..0000000
--- a/pw_rpc/public/pw_rpc/internal/fake_channel_output.h
+++ /dev/null
@@ -1,250 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstddef>
-#include <iterator>
-#include <limits>
-
-#include "pw_bytes/span.h"
-#include "pw_containers/vector.h"
-#include "pw_function/function.h"
-#include "pw_rpc/channel.h"
-#include "pw_rpc/internal/lock.h"
-#include "pw_rpc/internal/method_info.h"
-#include "pw_rpc/internal/packet.h"
-#include "pw_rpc/method_type.h"
-#include "pw_rpc/payloads_view.h"
-#include "pw_sync/lock_annotations.h"
-
-namespace pw::rpc {
-
-class FakeServer;
-
-namespace internal::test {
-
-// A ChannelOutput implementation that stores outgoing packets.
-class FakeChannelOutput : public ChannelOutput {
- public:
-  FakeChannelOutput(const FakeChannelOutput&) = delete;
-  FakeChannelOutput(FakeChannelOutput&&) = delete;
-
-  FakeChannelOutput& operator=(const FakeChannelOutput&) = delete;
-  FakeChannelOutput& operator=(FakeChannelOutput&&) = delete;
-
-  Status last_status() const PW_LOCKS_EXCLUDED(mutex_) {
-    LockGuard lock(mutex_);
-    PW_ASSERT(total_response_packets_ > 0);
-    return packets_.back().status();
-  }
-
-  // Returns a view of the payloads seen for this RPC.
-  //
-  // !!! WARNING !!!
-  //
-  // Access to the FakeChannelOutput through the PayloadsView is NOT
-  // synchronized! The PayloadsView is immediately invalidated if any thread
-  // accesses the FakeChannelOutput.
-  template <auto kMethod>
-  PayloadsView payloads(uint32_t channel_id = Channel::kUnassignedChannelId)
-      const PW_LOCKS_EXCLUDED(mutex_) {
-    LockGuard lock(mutex_);
-    return PayloadsView(packets_,
-                        MethodInfo<kMethod>::kType,
-                        channel_id,
-                        MethodInfo<kMethod>::kServiceId,
-                        MethodInfo<kMethod>::kMethodId);
-  }
-
-  PayloadsView payloads(MethodType type,
-                        uint32_t channel_id,
-                        uint32_t service_id,
-                        uint32_t method_id) const PW_LOCKS_EXCLUDED(mutex_) {
-    LockGuard lock(mutex_);
-    return PayloadsView(packets_, type, channel_id, service_id, method_id);
-  }
-
-  // Returns a view of the final statuses seen for this RPC. Only relevant for
-  // checking packets sent by a server.
-  //
-  // !!! WARNING !!!
-  //
-  // Access to the FakeChannelOutput through the StatusView is NOT
-  // synchronized! The StatusView is immediately invalidated if any thread
-  // accesses the FakeChannelOutput.
-  template <auto kMethod>
-  StatusView completions(uint32_t channel_id = Channel::kUnassignedChannelId)
-      const PW_LOCKS_EXCLUDED(mutex_) {
-    LockGuard lock(mutex_);
-    return StatusView(packets_,
-                      internal::PacketType::RESPONSE,
-                      internal::PacketType::RESPONSE,
-                      channel_id,
-                      MethodInfo<kMethod>::kServiceId,
-                      MethodInfo<kMethod>::kMethodId);
-  }
-
-  // Returns a view of the pw_rpc server or client errors seen for this RPC.
-  //
-  // !!! WARNING !!!
-  //
-  // Access to the FakeChannelOutput through the StatusView is NOT
-  // synchronized! The StatusView is immediately invalidated if any thread
-  // accesses the FakeChannelOutput.
-  template <auto kMethod>
-  StatusView errors(uint32_t channel_id = Channel::kUnassignedChannelId) const
-      PW_LOCKS_EXCLUDED(mutex_) {
-    LockGuard lock(mutex_);
-    return StatusView(packets_,
-                      internal::PacketType::CLIENT_ERROR,
-                      internal::PacketType::SERVER_ERROR,
-                      channel_id,
-                      MethodInfo<kMethod>::kServiceId,
-                      MethodInfo<kMethod>::kMethodId);
-  }
-
-  // Returns a view of the client stream end packets seen for this RPC. Only
-  // relevant for checking packets sent by a client.
-  template <auto kMethod>
-  size_t client_stream_end_packets(
-      uint32_t channel_id = Channel::kUnassignedChannelId) const
-      PW_LOCKS_EXCLUDED(mutex_) {
-    LockGuard lock(mutex_);
-    return internal::test::PacketsView(
-               packets_,
-               internal::test::PacketFilter(
-                   internal::PacketType::CLIENT_STREAM_END,
-                   internal::PacketType::CLIENT_STREAM_END,
-                   channel_id,
-                   MethodInfo<kMethod>::kServiceId,
-                   MethodInfo<kMethod>::kMethodId))
-        .size();
-  }
-
-  // The maximum number of packets this FakeChannelOutput can store. Attempting
-  // to store more packets than this is an error.
-  size_t max_packets() const PW_LOCKS_EXCLUDED(mutex_) {
-    LockGuard lock(mutex_);
-    return packets_.max_size();
-  }
-
-  // The total number of packets that have been sent.
-  size_t total_packets() const PW_LOCKS_EXCLUDED(mutex_) {
-    LockGuard lock(mutex_);
-    return packets_.size();
-  }
-
-  // Set to true if a RESPONSE packet is seen.
-  bool done() const PW_LOCKS_EXCLUDED(mutex_) {
-    LockGuard lock(mutex_);
-    return total_response_packets_ > 0;
-  }
-
-  // Clears and resets the FakeChannelOutput.
-  void clear() PW_LOCKS_EXCLUDED(mutex_);
-
-  // Returns `status` for all future Send calls. Enables packet processing if
-  // `status` is OK.
-  void set_send_status(Status status) PW_LOCKS_EXCLUDED(mutex_) {
-    LockGuard lock(mutex_);
-    send_status_ = status;
-    return_after_packet_count_ = status.ok() ? -1 : 0;
-  }
-
-  // Returns `status` once after the specified positive number of packets.
-  void set_send_status(Status status, int return_after_packet_count)
-      PW_LOCKS_EXCLUDED(mutex_) {
-    LockGuard lock(mutex_);
-    PW_ASSERT(!status.ok());
-    PW_ASSERT(return_after_packet_count > 0);
-    send_status_ = status;
-    return_after_packet_count_ = return_after_packet_count;
-  }
-
-  // Logs which packets have been sent for debugging purposes.
-  void LogPackets() const PW_LOCKS_EXCLUDED(mutex_);
-
-  // Processes buffer according to packet type and `return_after_packet_count_`
-  // value as follows:
-  // When positive, returns `send_status_` once,
-  // When equals 0, returns `send_status_` in all future calls,
-  // When negative, ignores `send_status_` processes buffer.
-  Status Send(ConstByteSpan buffer) final PW_LOCKS_EXCLUDED(mutex_) {
-    LockGuard lock(mutex_);
-    const Status status = HandlePacket(buffer);
-    if (on_send_ != nullptr) {
-      on_send_(buffer, status);
-    }
-    return status;
-  }
-
-  // Gives access to the last received internal::Packet. This is hidden by the
-  // raw/Nanopb implementations, since it gives access to an internal class.
-  const Packet& last_packet() const PW_LOCKS_EXCLUDED(mutex_) {
-    LockGuard lock(mutex_);
-    PW_ASSERT(!packets_.empty());
-    return packets_.back();
-  }
-
-  // The on_send callback is called every time Send() is called. It is passed
-  // the contents of the packet and the status to be returned from Send().
-  //
-  // DANGER: Do NOT call any FakeChannelOutput functions or functions that call
-  // FakeChannelOutput functions. That will result in infinite recursion or
-  // deadlocks.
-  void set_on_send(Function<void(ConstByteSpan, Status)>&& on_send)
-      PW_LOCKS_EXCLUDED(mutex_) {
-    LockGuard lock(mutex_);
-    on_send_ = std::move(on_send);
-  }
-
- protected:
-  FakeChannelOutput(Vector<Packet>& packets, Vector<std::byte>& payloads)
-      : ChannelOutput("pw::rpc::internal::test::FakeChannelOutput"),
-        packets_(packets),
-        payloads_(payloads) {}
-
-  const Vector<Packet>& packets() const { return packets_; }
-
- private:
-  friend class rpc::FakeServer;
-
-  Status HandlePacket(ConstByteSpan buffer) PW_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
-  void CopyPayloadToBuffer(Packet& packet) PW_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
-
-  int return_after_packet_count_ PW_GUARDED_BY(mutex_) = -1;
-  unsigned total_response_packets_ PW_GUARDED_BY(mutex_) = 0;
-
-  Vector<Packet>& packets_ PW_GUARDED_BY(mutex_);
-  Vector<std::byte>& payloads_ PW_GUARDED_BY(mutex_);
-  Status send_status_ PW_GUARDED_BY(mutex_) = OkStatus();
-  Function<void(ConstByteSpan, Status)> on_send_ PW_GUARDED_BY(mutex_);
-
-  mutable RpcLock mutex_;
-};
-
-// Adds the packet output buffer to a FakeChannelOutput.
-template <size_t kMaxPackets, size_t kPayloadsBufferSizeBytes>
-class FakeChannelOutputBuffer : public FakeChannelOutput {
- protected:
-  FakeChannelOutputBuffer()
-      : FakeChannelOutput(packets_array_, payloads_array_), payloads_array_ {}
-  {}
-
-  Vector<std::byte, kPayloadsBufferSizeBytes> payloads_array_;
-  Vector<Packet, kMaxPackets> packets_array_;
-};
-
-}  // namespace internal::test
-}  // namespace pw::rpc
diff --git a/pw_rpc/public/pw_rpc/internal/lock.h b/pw_rpc/public/pw_rpc/internal/lock.h
deleted file mode 100644
index 50c53d5..0000000
--- a/pw_rpc/public/pw_rpc/internal/lock.h
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/internal/config.h"
-#include "pw_sync/lock_annotations.h"
-
-#if PW_RPC_USE_GLOBAL_MUTEX
-
-#include <mutex>
-
-#include "pw_sync/mutex.h"  // nogncheck
-
-#endif  // PW_RPC_USE_GLOBAL_MUTEX
-
-namespace pw::rpc::internal {
-
-#if PW_RPC_USE_GLOBAL_MUTEX
-
-using RpcLock = sync::Mutex;
-using LockGuard = std::lock_guard<RpcLock>;
-
-#else
-
-class PW_LOCKABLE("pw::rpc::internal::RpcLock") RpcLock {
- public:
-  constexpr void lock() PW_EXCLUSIVE_LOCK_FUNCTION() {}
-  constexpr void unlock() PW_UNLOCK_FUNCTION() {}
-};
-
-class PW_SCOPED_LOCKABLE LockGuard {
- public:
-  // [[maybe_unused]] needs to be after the parameter to workaround a gcc bug
-  // context: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81429
-  constexpr LockGuard(RpcLock& mutex [[maybe_unused]])
-      PW_EXCLUSIVE_LOCK_FUNCTION(mutex) {}
-
-  ~LockGuard() PW_UNLOCK_FUNCTION() = default;
-};
-
-#endif  // PW_RPC_USE_GLOBAL_MUTEX
-
-RpcLock& rpc_lock();
-
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/public/pw_rpc/internal/log_config.h b/pw_rpc/public/pw_rpc/internal/log_config.h
deleted file mode 100644
index 76e493a..0000000
--- a/pw_rpc/public/pw_rpc/internal/log_config.h
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// Configuration macros for the pw_rpc module.
-#pragma once
-
-#include "pw_rpc/internal/config.h"
-
-#define PW_LOG_LEVEL PW_RPC_CONFIG_LOG_LEVEL
-#define PW_LOG_MODULE_NAME PW_RPC_CONFIG_LOG_MODULE_NAME
diff --git a/pw_rpc/public/pw_rpc/internal/method.h b/pw_rpc/public/pw_rpc/internal/method.h
index 58fb4e3..fec137c 100644
--- a/pw_rpc/public/pw_rpc/internal/method.h
+++ b/pw_rpc/public/pw_rpc/internal/method.h
@@ -17,14 +17,9 @@
 #include <cstdint>
 #include <utility>
 
-#include "pw_rpc/internal/call_context.h"
-#include "pw_rpc/internal/lock.h"
+#include "pw_rpc/internal/call.h"
 
-namespace pw::rpc {
-
-class Service;
-
-namespace internal {
+namespace pw::rpc::internal {
 
 class Packet;
 
@@ -33,15 +28,15 @@
 /*
 class MethodImpl : public Method {
   // True if the provided function signature is valid for this method impl.
-  template <auto kMethod>
+  template <auto method>
   static constexpr bool matches();
 
   // Creates a unary method instance.
-  template <auto kMethod>
+  template <auto method>
   static constexpr MethodImpl Unary(uint32_t id, [optional args]);
 
   // Creates a server streaming method instance.
-  template <auto kMethod>
+  template <auto method>
   static constexpr MethodImpl ServerStreaming(uint32_t id, [optional args]);
 
   // Creates a client streaming method instance.
@@ -57,7 +52,7 @@
 };
 */
 // Method implementations must pass a test that uses the MethodImplTester class
-// in pw_rpc/internal/method_impl_tester.h.
+// in pw_rpc_private/method_impl_tester.h.
 class Method {
  public:
   constexpr uint32_t id() const { return id_; }
@@ -66,18 +61,16 @@
   // calls the invoker function, which handles the RPC request and response
   // according to the RPC type and protobuf implementation and calls the
   // user-defined RPC function.
-  //
-  // The rpc_lock() must be held through creating the call object and released
-  // before calling into the RPC body.
-  void Invoke(const CallContext& context, const Packet& request) const
-      PW_UNLOCK_FUNCTION(rpc_lock()) PW_NO_LOCK_SAFETY_ANALYSIS {
-    return invoker_(context, request);  // The invoker must unlock rpc_lock().
+  void Invoke(ServerCall& call, const Packet& request) const {
+    return invoker_(*this, call, request);
   }
 
  protected:
-  using Invoker = void (&)(const CallContext&, const Packet&);
+  using Invoker = void (&)(const Method&, ServerCall&, const Packet&);
 
-  static constexpr void InvalidInvoker(const CallContext&, const Packet&) {}
+  static constexpr void InvalidInvoker(const Method&,
+                                       ServerCall&,
+                                       const Packet&) {}
 
   constexpr Method(uint32_t id, Invoker invoker) : id_(id), invoker_(invoker) {}
 
@@ -86,9 +79,9 @@
   Invoker invoker_;
 };
 
-// MethodTraits inspects an RPC implementation function. It determines which
-// Method API is in use and the type of the RPC based on the function signature.
-// pw_rpc Method implementations specialize MethodTraits for each RPC type.
+// Traits struct that determines the type of an RPC service method from its
+// signature. Derived Methods should provide specializations for their method
+// types.
 template <typename Method>
 struct MethodTraits {
   // Specializations must set Implementation as an alias for their method
@@ -100,7 +93,7 @@
 
   // Specializations for member function types must set Service to an alias to
   // for the implemented service class.
-  // using Service = (derived service class);
+  using Service = rpc::Service;
 
   // Specializations may provide the C++ types of the requests and responses if
   // relevant.
@@ -108,29 +101,33 @@
   using Response = void;
 };
 
-template <auto kMethod>
+template <auto method>
 using MethodImplementation =
-    typename MethodTraits<decltype(kMethod)>::Implementation;
+    typename MethodTraits<decltype(method)>::Implementation;
 
-template <auto kMethod>
-using Request = typename MethodTraits<decltype(kMethod)>::Request;
-
-template <auto kMethod>
-using Response = typename MethodTraits<decltype(kMethod)>::Response;
-
-// Function that calls a user-defined RPC function on the given Service.
-template <auto kMethod, typename... Args>
-constexpr auto CallMethodImplFunction(Service& service, Args&&... args) {
+// Function that calls a user-defined method implementation function from a
+// ServerCall object.
+template <auto method, typename... Args>
+constexpr auto CallMethodImplFunction(ServerCall& call, Args&&... args) {
   // If the method impl is a member function, deduce the type of the
   // user-defined service from it, then call the method on the service.
-  if constexpr (std::is_member_function_pointer_v<decltype(kMethod)>) {
-    return (static_cast<typename MethodTraits<decltype(kMethod)>::Service&>(
-                service).*
-            kMethod)(std::forward<Args>(args)...);
+  if constexpr (std::is_member_function_pointer_v<decltype(method)>) {
+    return (static_cast<typename MethodTraits<decltype(method)>::Service&>(
+                call.service()).*
+            method)(call.context(), std::forward<Args>(args)...);
   } else {
-    return kMethod(std::forward<Args>(args)...);
+    return method(call.context(), std::forward<Args>(args)...);
   }
 }
 
-}  // namespace internal
-}  // namespace pw::rpc
+// Identifies a base class from a member function it defines. This should be
+// used with decltype to retrieve the base service class.
+template <typename T, typename U>
+T BaseFromMember(U T::*);
+
+// The base generated service of an RPC service class.
+template <typename Service>
+using GeneratedService =
+    decltype(BaseFromMember(&Service::_PwRpcInternalGeneratedBase));
+
+}  // namespace pw::rpc::internal
diff --git a/pw_rpc/public/pw_rpc/internal/method_impl_tester.h b/pw_rpc/public/pw_rpc/internal/method_impl_tester.h
deleted file mode 100644
index 2b411d2..0000000
--- a/pw_rpc/public/pw_rpc/internal/method_impl_tester.h
+++ /dev/null
@@ -1,270 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <type_traits>
-
-#include "gtest/gtest.h"
-#include "pw_rpc/internal/packet.h"
-#include "pw_rpc/raw/internal/method.h"
-
-namespace pw::rpc::internal {
-
-template <typename...>
-struct MatchesTypes {};
-
-// This class tests Method implementation classes and MethodTraits
-// specializations. It verifies that they provide the expected functions and
-// that they correctly identify and construct the various method types.
-//
-// The TestService class must inherit from Service and provide the following
-// methods with valid signatures for RPCs:
-//
-//   - Unary: synchronous unary RPC member function
-//   - StaticUnary: synchronous unary RPC static member function
-//   - AsyncUnary: asynchronous unary RPC member function
-//   - StaticAsyncUnary: asynchronous unary RPC static member function
-//   - ServerStreaming: server streaming RPC member function
-//   - StaticServerStreaming: server streaming static RPC member function
-//   - ClientStreaming: client streaming RPC member function
-//   - StaticClientStreaming: client streaming static RPC member function
-//   - BidirectionalStreaming: bidirectional streaming RPC member function
-//   - StaticBidirectionalStreaming: bidirectional streaming static RPC
-//         member function
-//
-template <typename MethodImpl, typename TestService>
-class MethodImplTests {
- public:
-  template <typename... ExtraTypes, typename... CreationArgs>
-  constexpr bool Pass(
-      const MatchesTypes<ExtraTypes...>& = {},
-      const std::tuple<CreationArgs...>& creation_args = {}) const {
-    return Matches<ExtraTypes...>().Pass() && Type().Pass() &&
-           Creation().Pass(creation_args);
-  }
-
- private:
-  template <typename... ExtraTypes>
-  struct Matches {
-    constexpr bool Pass() const { return true; }
-
-    // Test that the matches() function matches valid signatures.
-    static_assert(
-        MethodImpl::template matches<&TestService::Unary, ExtraTypes...>());
-    static_assert(MethodImpl::template matches<&TestService::StaticUnary,
-                                               ExtraTypes...>());
-
-    static_assert(MethodImpl::template matches<&TestService::AsyncUnary,
-                                               ExtraTypes...>());
-    static_assert(MethodImpl::template matches<&TestService::StaticAsyncUnary,
-                                               ExtraTypes...>());
-
-    static_assert(MethodImpl::template matches<&TestService::ServerStreaming,
-                                               ExtraTypes...>());
-    static_assert(
-        MethodImpl::template matches<&TestService::StaticServerStreaming,
-                                     ExtraTypes...>());
-
-    static_assert(MethodImpl::template matches<&TestService::ClientStreaming,
-                                               ExtraTypes...>());
-    static_assert(
-        MethodImpl::template matches<&TestService::StaticClientStreaming,
-                                     ExtraTypes...>());
-
-    static_assert(
-        MethodImpl::template matches<&TestService::BidirectionalStreaming,
-                                     ExtraTypes...>());
-    static_assert(
-        MethodImpl::template matches<&TestService::StaticBidirectionalStreaming,
-                                     ExtraTypes...>());
-
-    // Test that the matches() function does not match the wrong method type.
-    static_assert(!MethodImpl::template matches<&TestService::UnaryWrongArg,
-                                                ExtraTypes...>());
-    static_assert(
-        !MethodImpl::template matches<&TestService::StaticUnaryVoidReturn,
-                                      ExtraTypes...>());
-
-    static_assert(
-        !MethodImpl::template matches<&TestService::ServerStreamingBadReturn,
-                                      ExtraTypes...>());
-    static_assert(!MethodImpl::template matches<
-                  &TestService::StaticServerStreamingMissingArg,
-                  ExtraTypes...>());
-
-    static_assert(
-        !MethodImpl::template matches<&TestService::ClientStreamingBadReturn,
-                                      ExtraTypes...>());
-    static_assert(!MethodImpl::template matches<
-                  &TestService::StaticClientStreamingMissingArg,
-                  ExtraTypes...>());
-
-    static_assert(!MethodImpl::template matches<
-                  &TestService::BidirectionalStreamingBadReturn,
-                  ExtraTypes...>());
-    static_assert(!MethodImpl::template matches<
-                  &TestService::StaticBidirectionalStreamingMissingArg,
-                  ExtraTypes...>());
-  };
-
-  // Check that MethodTraits resolves to the correct value for kType.
-  struct Type {
-    constexpr bool Pass() const { return true; }
-
-    // Don't check kSynchronous for Unary since not all method implementations
-    // support synchronous unary.
-    static_assert(MethodTraits<decltype(&TestService::Unary)>::kType ==
-                  MethodType::kUnary);
-    static_assert(MethodTraits<decltype(&TestService::StaticUnary)>::kType ==
-                  MethodType::kUnary);
-    static_assert(MethodTraits<decltype(&TestService::AsyncUnary)>::kType ==
-                  MethodType::kUnary);
-    static_assert(
-        !MethodTraits<decltype(&TestService::AsyncUnary)>::kSynchronous);
-    static_assert(
-        MethodTraits<decltype(&TestService::StaticAsyncUnary)>::kType ==
-        MethodType::kUnary);
-    static_assert(
-        !MethodTraits<decltype(&TestService::StaticAsyncUnary)>::kSynchronous);
-
-    static_assert(
-        MethodTraits<decltype(&TestService::ServerStreaming)>::kType ==
-        MethodType::kServerStreaming);
-    static_assert(
-        MethodTraits<decltype(&TestService::StaticServerStreaming)>::kType ==
-        MethodType::kServerStreaming);
-
-    static_assert(
-        MethodTraits<decltype(&TestService::ClientStreaming)>::kType ==
-        MethodType::kClientStreaming);
-    static_assert(
-        MethodTraits<decltype(&TestService::StaticClientStreaming)>::kType ==
-        MethodType::kClientStreaming);
-
-    static_assert(
-        MethodTraits<decltype(&TestService::BidirectionalStreaming)>::kType ==
-        MethodType::kBidirectionalStreaming);
-    static_assert(
-        MethodTraits<
-            decltype(&TestService::StaticBidirectionalStreaming)>::kType ==
-        MethodType::kBidirectionalStreaming);
-  };
-
-  // Test method creation.
-  class Creation {
-   public:
-    template <typename... Args>
-    constexpr bool Pass(const std::tuple<Args...>& args) const {
-      return AsyncUnaryMethod(args).id() == 3 &&
-             StaticAsyncUnaryMethod(args).id() == 4 &&
-             ServerStreamingMethod(args).id() == 5 &&
-             StaticServerStreamingMethod(args).id() == 6 &&
-             ClientStreamingMethod(args).id() == 7 &&
-             StaticClientStreamingMethod(args).id() == 8 &&
-             BidirectionalStreamingMethod(args).id() == 9 &&
-             StaticBidirectionalStreamingMethod(args).id() == 10 &&
-             InvalidMethod().id() == 0;
-    }
-
-   private:
-    // Do not check synchronous unary since not all method implementations
-    // support it.
-
-    template <typename... Args>
-    constexpr MethodImpl AsyncUnaryMethod(
-        const std::tuple<Args...>& args) const {
-      return Call(
-          MethodImpl::template AsynchronousUnary<&TestService::AsyncUnary>,
-          3,
-          args);
-    }
-
-    template <typename... Args>
-    constexpr MethodImpl StaticAsyncUnaryMethod(
-        const std::tuple<Args...>& args) const {
-      return Call(MethodImpl::template AsynchronousUnary<
-                      &TestService::StaticAsyncUnary>,
-                  4,
-                  args);
-    }
-
-    template <typename... Args>
-    constexpr MethodImpl ServerStreamingMethod(
-        const std::tuple<Args...>& args) const {
-      return Call(
-          MethodImpl::template ServerStreaming<&TestService::ServerStreaming>,
-          5,
-          args);
-    }
-
-    template <typename... Args>
-    constexpr MethodImpl StaticServerStreamingMethod(
-        const std::tuple<Args...>& args) const {
-      return Call(MethodImpl::template ServerStreaming<
-                      &TestService::StaticServerStreaming>,
-                  6,
-                  args);
-    }
-
-    template <typename... Args>
-    constexpr MethodImpl ClientStreamingMethod(
-        const std::tuple<Args...>& args) const {
-      return Call(
-          MethodImpl::template ClientStreaming<&TestService::ClientStreaming>,
-          7,
-          args);
-    }
-
-    template <typename... Args>
-    constexpr MethodImpl StaticClientStreamingMethod(
-        const std::tuple<Args...>& args) const {
-      return Call(MethodImpl::template ClientStreaming<
-                      &TestService::StaticClientStreaming>,
-                  8,
-                  args);
-    }
-
-    template <typename... Args>
-    constexpr MethodImpl BidirectionalStreamingMethod(
-        const std::tuple<Args...>& args) const {
-      return Call(MethodImpl::template BidirectionalStreaming<
-                      &TestService::BidirectionalStreaming>,
-                  9,
-                  args);
-    }
-
-    template <typename... Args>
-    constexpr MethodImpl StaticBidirectionalStreamingMethod(
-        const std::tuple<Args...>& args) const {
-      return Call(MethodImpl::template BidirectionalStreaming<
-                      &TestService::StaticBidirectionalStreaming>,
-                  10,
-                  args);
-    }
-
-    // Test that there is an Invalid method creation function.
-    constexpr MethodImpl InvalidMethod() const { return MethodImpl::Invalid(); }
-
-    // Invokes the method creation function with the ID and extra args.
-    template <typename Function, typename... Args>
-    static constexpr MethodImpl Call(Function&& function,
-                                     uint32_t id,
-                                     const std::tuple<Args...>& args) {
-      return std::apply(function, std::tuple_cat(std::tuple(id), args));
-    }
-  };
-};
-
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/public/pw_rpc/internal/method_info.h b/pw_rpc/public/pw_rpc/internal/method_info.h
deleted file mode 100644
index d849066..0000000
--- a/pw_rpc/public/pw_rpc/internal/method_info.h
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <type_traits>
-
-namespace pw::rpc::internal {
-
-template <auto>
-constexpr std::false_type kIsGeneratedRpcFunction{};
-
-// The MethodInfo class provides metadata about an RPC. This class is
-// specialized for the static RPC client functions in the generated code. This
-// makes it possible for pw_rpc to access method metadata with a simple API. The
-// user passes the method name (e.g. pw_rpc::raw::MyService::MyMethod) as a
-// template parameter, and pw_rpc can extract information like method and
-// service IDs at compile time.
-template <auto kRpcFunction>
-struct MethodInfo {
-  // MethodInfo specializations always provide the service and method IDs and a
-  // function that returns the implementation method for this RPC given a
-  // service implementation class.
-  static constexpr uint32_t kServiceId = 0;
-  static constexpr uint32_t kMethodId = 0;
-
-  template <typename ServiceImpl>
-  static constexpr void Function() {}
-
-  static_assert(kIsGeneratedRpcFunction<kRpcFunction>,
-                "The provided argument is not a generated pw_rpc function. "
-                "Pass a pw_rpc function such as "
-                "my_pkg::pw_rpc::raw:::MyService::MyMethod instead.");
-};
-
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/public/pw_rpc/internal/method_info_tester.h b/pw_rpc/public/pw_rpc/internal/method_info_tester.h
deleted file mode 100644
index 6e80099..0000000
--- a/pw_rpc/public/pw_rpc/internal/method_info_tester.h
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/internal/hash.h"
-#include "pw_rpc/internal/method_info.h"
-
-namespace pw::rpc::internal {
-
-// Tests the MethodTraits specializations for test.proto for any implementation.
-template <typename GeneratedClass, typename ServiceImpl>
-class MethodInfoTests {
- public:
-  constexpr bool Pass() const {
-    return Ids().Pass() && MethodFunction().Pass();
-  }
-
- private:
-  struct Ids {
-    constexpr bool Pass() const { return true; }
-
-#define PW_RPC_TEST_METHOD_INFO_IDS(function)                             \
-  static_assert(MethodInfo<GeneratedClass::function>::kServiceId ==       \
-                    Hash("pw.rpc.test.TestService"),                      \
-                #function " service ID doesn't match!");                  \
-  static_assert(                                                          \
-      MethodInfo<GeneratedClass::function>::kMethodId == Hash(#function), \
-      #function " method ID doesn't match!")
-
-    PW_RPC_TEST_METHOD_INFO_IDS(TestUnaryRpc);
-    PW_RPC_TEST_METHOD_INFO_IDS(TestAnotherUnaryRpc);
-    PW_RPC_TEST_METHOD_INFO_IDS(TestServerStreamRpc);
-    PW_RPC_TEST_METHOD_INFO_IDS(TestClientStreamRpc);
-    PW_RPC_TEST_METHOD_INFO_IDS(TestBidirectionalStreamRpc);
-#undef PW_RPC_TEST_METHOD_INFO_IDS
-  };
-
-  static_assert(MethodInfo<GeneratedClass::TestClientStreamRpc>::kServiceId !=
-                    Hash("TestService"),
-                "Wrong service name should not match");
-  static_assert(
-      MethodInfo<GeneratedClass::TestBidirectionalStreamRpc>::kMethodId !=
-          Hash("TestUnaryRpc"),
-      "Wrong method name should not match");
-
-  struct MethodFunction {
-    constexpr bool Pass() const { return true; }
-
-#define PW_RPC_TEST_METHOD_INFO_FUNCTION(function)                       \
-  static_assert(MethodInfo<GeneratedClass::function>::template Function< \
-                    ServiceImpl>() == &ServiceImpl::function)
-
-    PW_RPC_TEST_METHOD_INFO_FUNCTION(TestUnaryRpc);
-    PW_RPC_TEST_METHOD_INFO_FUNCTION(TestAnotherUnaryRpc);
-    PW_RPC_TEST_METHOD_INFO_FUNCTION(TestServerStreamRpc);
-    PW_RPC_TEST_METHOD_INFO_FUNCTION(TestClientStreamRpc);
-    PW_RPC_TEST_METHOD_INFO_FUNCTION(TestBidirectionalStreamRpc);
-#undef PW_RPC_TEST_METHOD_INFO_FUNCTION
-  };
-};
-
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/public/pw_rpc/internal/method_lookup.h b/pw_rpc/public/pw_rpc/internal/method_lookup.h
index be07455..7383a01 100644
--- a/pw_rpc/public/pw_rpc/internal/method_lookup.h
+++ b/pw_rpc/public/pw_rpc/internal/method_lookup.h
@@ -25,8 +25,6 @@
 // union member in a constant expression, so this results in a compiler error.
 class MethodLookup {
  public:
-  MethodLookup() = delete;
-
   template <typename Service, uint32_t kMethodId>
   static constexpr const auto& GetRawMethod() {
     const auto& method = GetMethodUnion<Service, kMethodId>().raw_method();
@@ -51,11 +49,12 @@
   }
 
   template <typename Service>
-  static constexpr typename decltype(Service::kPwRpcMethods)::const_pointer
-  GetMethodUnionPointer(uint32_t kMethodId) {
-    for (size_t i = 0; i < Service::kPwRpcMethodIds.size(); ++i) {
-      if (Service::kPwRpcMethodIds[i] == kMethodId) {
-        return &Service::kPwRpcMethods[i];
+  static constexpr
+      typename decltype(GeneratedService<Service>::kMethods)::const_pointer
+      GetMethodUnionPointer(uint32_t kMethodId) {
+    for (size_t i = 0; i < GeneratedService<Service>::kMethodIds.size(); ++i) {
+      if (GeneratedService<Service>::kMethodIds[i] == kMethodId) {
+        return &GeneratedService<Service>::kMethods[i];
       }
     }
     return nullptr;
diff --git a/pw_rpc/public/pw_rpc/internal/method_type.h b/pw_rpc/public/pw_rpc/internal/method_type.h
new file mode 100644
index 0000000..9a44e7a
--- /dev/null
+++ b/pw_rpc/public/pw_rpc/internal/method_type.h
@@ -0,0 +1,25 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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
+
+namespace pw::rpc::internal {
+
+enum class MethodType {
+  kUnary,
+  kServerStreaming,
+  kClientStreaming,
+  kBidirectionalStreaming,
+};
+
+}  // namespace pw::rpc::internal
diff --git a/pw_rpc/public/pw_rpc/internal/method_union.h b/pw_rpc/public/pw_rpc/internal/method_union.h
index 4b176db..154667d 100644
--- a/pw_rpc/public/pw_rpc/internal/method_union.h
+++ b/pw_rpc/public/pw_rpc/internal/method_union.h
@@ -17,7 +17,7 @@
 #include <utility>
 
 #include "pw_rpc/internal/method.h"
-#include "pw_rpc/method_type.h"
+#include "pw_rpc/internal/method_type.h"
 
 namespace pw::rpc::internal {
 
@@ -67,37 +67,37 @@
       " RPC, but its function signature is not correct. The function "    \
       "signature is determined by the protobuf library in use, but " type \
       " RPC implementations generally take the form:",                    \
-      return_type " MethodName(" args ")")
+      return_type " MethodName(ServerContext&, " args ")")
 
 // This function is called if an RPC method implementation's signature is not
 // correct. It triggers a static_assert with an error message tailored to the
 // expected RPC type.
-template <auto kMethod,
-          MethodType kExpected,
-          typename InvalidImpl = MethodImplementation<kMethod>>
+template <auto method,
+          MethodType expected,
+          typename InvalidImpl = MethodImplementation<method>>
 constexpr auto InvalidMethod(uint32_t) {
-  if constexpr (kExpected == MethodType::kUnary) {
+  if constexpr (expected == MethodType::kUnary) {
     static_assert(
-        kCheckMethodSignature<decltype(kMethod)>,
+        kCheckMethodSignature<decltype(method)>,
         _PW_RPC_FUNCTION_ERROR("unary", "Status", "Request, Response"));
-  } else if constexpr (kExpected == MethodType::kServerStreaming) {
+  } else if constexpr (expected == MethodType::kServerStreaming) {
     static_assert(
-        kCheckMethodSignature<decltype(kMethod)>,
+        kCheckMethodSignature<decltype(method)>,
         _PW_RPC_FUNCTION_ERROR(
             "server streaming", "void", "Request, ServerWriter<Response>&"));
-  } else if constexpr (kExpected == MethodType::kClientStreaming) {
+  } else if constexpr (expected == MethodType::kClientStreaming) {
     static_assert(
-        kCheckMethodSignature<decltype(kMethod)>,
+        kCheckMethodSignature<decltype(method)>,
         _PW_RPC_FUNCTION_ERROR(
-            "client streaming", "void", "ServerReader<Request, Request>&"));
-  } else if constexpr (kExpected == MethodType::kBidirectionalStreaming) {
-    static_assert(
-        kCheckMethodSignature<decltype(kMethod)>,
-        _PW_RPC_FUNCTION_ERROR("bidirectional streaming",
-                               "void",
-                               "ServerReaderWriter<Request, Response>&"));
+            "client streaming", "Status", "ServerReader<Request>&, Response"));
+  } else if constexpr (expected == MethodType::kBidirectionalStreaming) {
+    static_assert(kCheckMethodSignature<decltype(method)>,
+                  _PW_RPC_FUNCTION_ERROR(
+                      "bidirectional streaming",
+                      "void",
+                      "ServerReader<Request>&, ServerWriter<Response>&"));
   } else {
-    static_assert(kCheckMethodSignature<decltype(kMethod)>,
+    static_assert(kCheckMethodSignature<decltype(method)>,
                   "Unsupported MethodType");
   }
   return InvalidImpl::Invalid();
@@ -108,26 +108,20 @@
 
 // This function checks the type of the method and calls the appropriate
 // function to create the method instance.
-template <auto kMethod, typename MethodImpl, MethodType kType, typename... Args>
+template <auto method, typename MethodImpl, MethodType type, typename... Args>
 constexpr auto GetMethodFor(uint32_t id, Args&&... args) {
-  if constexpr (MethodTraits<decltype(kMethod)>::kType != kType) {
-    return InvalidMethod<kMethod, kType>(id);
-  } else if constexpr (kType == MethodType::kUnary) {
-    if constexpr (MethodTraits<decltype(kMethod)>::kSynchronous) {
-      return MethodImpl::template SynchronousUnary<kMethod>(
-          id, std::forward<Args>(args)...);
-    } else {
-      return MethodImpl::template AsynchronousUnary<kMethod>(
-          id, std::forward<Args>(args)...);
-    }
-  } else if constexpr (kType == MethodType::kServerStreaming) {
-    return MethodImpl::template ServerStreaming<kMethod>(
+  if constexpr (MethodTraits<decltype(method)>::kType != type) {
+    return InvalidMethod<method, type>(id);
+  } else if constexpr (type == MethodType::kUnary) {
+    return MethodImpl::template Unary<method>(id, std::forward<Args>(args)...);
+  } else if constexpr (type == MethodType::kServerStreaming) {
+    return MethodImpl::template ServerStreaming<method>(
         id, std::forward<Args>(args)...);
-  } else if constexpr (kType == MethodType::kClientStreaming) {
-    return MethodImpl::template ClientStreaming<kMethod>(
+  } else if constexpr (type == MethodType::kClientStreaming) {
+    return MethodImpl::template ClientStreaming<method>(
         id, std::forward<Args>(args)...);
-  } else if constexpr (kType == MethodType::kBidirectionalStreaming) {
-    return MethodImpl::template BidirectionalStreaming<kMethod>(
+  } else if constexpr (type == MethodType::kBidirectionalStreaming) {
+    return MethodImpl::template BidirectionalStreaming<method>(
         id, std::forward<Args>(args)...);
   } else {
     static_assert(kCheckMethodSignature<MethodImpl>, "Invalid MethodType");
diff --git a/pw_rpc/public/pw_rpc/internal/packet.h b/pw_rpc/public/pw_rpc/internal/packet.h
index 279c487..2004d70 100644
--- a/pw_rpc/public/pw_rpc/internal/packet.h
+++ b/pw_rpc/public/pw_rpc/internal/packet.h
@@ -18,7 +18,6 @@
 #include <span>
 
 #include "pw_bytes/span.h"
-#include "pw_protobuf/serialized_size.h"
 #include "pw_rpc/internal/packet.pwpb.h"
 #include "pw_status/status_with_size.h"
 
@@ -28,17 +27,6 @@
  public:
   static constexpr uint32_t kUnassignedId = 0;
 
-  static constexpr size_t kMinEncodedSizeWithoutPayload =
-      protobuf::SizeOfFieldEnum(RpcPacket::Fields::TYPE, 7) +
-      protobuf::SizeOfFieldUint32(RpcPacket::Fields::CHANNEL_ID) +
-      protobuf::SizeOfFieldUint32(RpcPacket::Fields::SERVICE_ID) +
-      protobuf::SizeOfFieldUint32(RpcPacket::Fields::METHOD_ID) +
-      protobuf::SizeOfDelimitedFieldWithoutValue(RpcPacket::Fields::PAYLOAD) +
-      protobuf::SizeOfFieldUint32(RpcPacket::Fields::STATUS,
-                                  Status::Unauthenticated().code()) +
-      protobuf::SizeOfFieldUint32(RpcPacket::Fields::CALL_ID);
-  ;
-
   // Parses a packet from a protobuf message. Missing or malformed fields take
   // their default values.
   static Result<Packet> FromBuffer(ConstByteSpan data);
@@ -51,7 +39,6 @@
                   request.channel_id(),
                   request.service_id(),
                   request.method_id(),
-                  request.call_id(),
                   {},
                   status);
   }
@@ -63,7 +50,6 @@
                   packet.channel_id(),
                   packet.service_id(),
                   packet.method_id(),
-                  packet.call_id(),
                   {},
                   status);
   }
@@ -75,7 +61,6 @@
                   packet.channel_id(),
                   packet.service_id(),
                   packet.method_id(),
-                  packet.call_id(),
                   {},
                   status);
   }
@@ -88,14 +73,12 @@
                    uint32_t channel_id,
                    uint32_t service_id,
                    uint32_t method_id,
-                   uint32_t call_id = kUnassignedId,
                    ConstByteSpan payload = {},
                    Status status = OkStatus())
       : type_(type),
         channel_id_(channel_id),
         service_id_(service_id),
         method_id_(method_id),
-        call_id_(call_id),
         payload_(payload),
         status_(status) {}
 
@@ -105,9 +88,6 @@
   // Determines the space required to encode the packet proto fields for a
   // response, excluding the payload. This may be used to split the buffer into
   // reserved space and available space for the payload.
-  //
-  // This method allocates two bytes for the status. Status code 0 (OK) is not
-  // encoded since 0 is the default value.
   size_t MinEncodedSizeBytes() const;
 
   enum Destination : bool { kServer, kClient };
@@ -120,9 +100,8 @@
   constexpr uint32_t channel_id() const { return channel_id_; }
   constexpr uint32_t service_id() const { return service_id_; }
   constexpr uint32_t method_id() const { return method_id_; }
-  constexpr uint32_t call_id() const { return call_id_; }
   constexpr const ConstByteSpan& payload() const { return payload_; }
-  constexpr const Status& status() const { return status_; }
+  constexpr Status status() const { return status_; }
 
   constexpr void set_type(PacketType type) { type_ = type; }
   constexpr void set_channel_id(uint32_t channel_id) {
@@ -132,7 +111,6 @@
     service_id_ = service_id;
   }
   constexpr void set_method_id(uint32_t method_id) { method_id_ = method_id; }
-  constexpr void set_call_id(uint32_t call_id) { call_id_ = call_id; }
   constexpr void set_payload(ConstByteSpan payload) { payload_ = payload; }
   constexpr void set_status(Status status) { status_ = status; }
 
@@ -141,7 +119,6 @@
   uint32_t channel_id_;
   uint32_t service_id_;
   uint32_t method_id_;
-  uint32_t call_id_;
   ConstByteSpan payload_;
   Status status_;
 };
diff --git a/pw_rpc/public/pw_rpc/internal/server.h b/pw_rpc/public/pw_rpc/internal/server.h
new file mode 100644
index 0000000..e260636
--- /dev/null
+++ b/pw_rpc/public/pw_rpc/internal/server.h
@@ -0,0 +1,33 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_rpc/server.h"
+
+namespace pw::rpc::internal {
+
+class Server : public rpc::Server {
+ public:
+  Server() = delete;
+
+  void RegisterWriter(BaseServerWriter& writer) {
+    writers().push_front(writer);
+  }
+
+  void RemoveWriter(const BaseServerWriter& writer) {
+    writers().remove(writer);
+  }
+};
+
+}  // namespace pw::rpc::internal
diff --git a/pw_rpc/public/pw_rpc/internal/server_call.h b/pw_rpc/public/pw_rpc/internal/server_call.h
deleted file mode 100644
index c7a9c53..0000000
--- a/pw_rpc/public/pw_rpc/internal/server_call.h
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_function/function.h"
-#include "pw_rpc/internal/call.h"
-#include "pw_rpc/internal/config.h"
-#include "pw_rpc/internal/lock.h"
-
-namespace pw::rpc::internal {
-
-// A Call object, as used by an RPC server.
-class ServerCall : public Call {
- public:
-  void HandleClientStreamEnd() PW_UNLOCK_FUNCTION(rpc_lock()) {
-    MarkClientStreamCompleted();
-    // TODO(pwbug/597): Ensure on_client_stream_end_ is properly guarded.
-    rpc_lock().unlock();
-
-#if PW_RPC_CLIENT_STREAM_END_CALLBACK
-    if (on_client_stream_end_) {
-      on_client_stream_end_();
-    }
-#endif  // PW_RPC_CLIENT_STREAM_END_CALLBACK
-  }
-
- protected:
-  constexpr ServerCall() = default;
-
-  ServerCall(ServerCall&& other) { *this = std::move(other); }
-
-  ~ServerCall() {
-    // Any errors are logged in Channel::Send.
-    CloseAndSendResponse(OkStatus()).IgnoreError();
-  }
-
-  // Version of operator= used by the raw call classes.
-  ServerCall& operator=(ServerCall&& other) PW_LOCKS_EXCLUDED(rpc_lock()) {
-    LockGuard lock(rpc_lock());
-    MoveServerCallFrom(other);
-    return *this;
-  }
-
-  void MoveServerCallFrom(ServerCall& other)
-      PW_EXCLUSIVE_LOCKS_REQUIRED(rpc_lock());
-
-  ServerCall(const CallContext& context, MethodType type)
-      : Call(context, type) {}
-
-  // set_on_client_stream_end is templated so that it can be conditionally
-  // disabled with a helpful static_assert message.
-  template <typename UnusedType = void>
-  void set_on_client_stream_end(
-      [[maybe_unused]] Function<void()>&& on_client_stream_end) {
-    // TODO(pwbug/597): Ensure on_client_stream_end_ is properly guarded.
-    static_assert(
-        cfg::kClientStreamEndCallbackEnabled<UnusedType>,
-        "The client stream end callback is disabled, so "
-        "set_on_client_stream_end cannot be called. To enable the client end "
-        "callback, set PW_RPC_CLIENT_STREAM_END_CALLBACK to 1.");
-#if PW_RPC_CLIENT_STREAM_END_CALLBACK
-    on_client_stream_end_ = std::move(on_client_stream_end);
-#endif  // PW_RPC_CLIENT_STREAM_END_CALLBACK
-  }
-
- private:
-#if PW_RPC_CLIENT_STREAM_END_CALLBACK
-  // Called when a client stream completes.
-  Function<void()> on_client_stream_end_;
-#endif  // PW_RPC_CLIENT_STREAM_END_CALLBACK
-};
-
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/public/pw_rpc/internal/service_client.h b/pw_rpc/public/pw_rpc/internal/service_client.h
deleted file mode 100644
index c3b2b4d..0000000
--- a/pw_rpc/public/pw_rpc/internal/service_client.h
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/client.h"
-
-namespace pw::rpc::internal {
-
-// This class serves as the base for generated RpcService::Client classes.
-// Generated service classes extend this and add a member function for each
-// service method.
-class ServiceClient {
- public:
-  constexpr ServiceClient(const ServiceClient&) = default;
-  constexpr ServiceClient& operator=(const ServiceClient&) = default;
-
-  constexpr Client& client() const { return *client_; }
-
-  constexpr uint32_t channel_id() const { return channel_id_; }
-
- protected:
-  constexpr ServiceClient(Client& client, uint32_t channel_id)
-      : client_(&client), channel_id_(channel_id) {}
-
- private:
-  Client* client_;
-  uint32_t channel_id_;
-};
-
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/public/pw_rpc/internal/test_method.h b/pw_rpc/public/pw_rpc/internal/test_method.h
index bdf1435..9acc26f 100644
--- a/pw_rpc/public/pw_rpc/internal/test_method.h
+++ b/pw_rpc/public/pw_rpc/internal/test_method.h
@@ -17,12 +17,10 @@
 #include <cstring>
 #include <span>
 
-#include "pw_rpc/internal/lock.h"
 #include "pw_rpc/internal/method.h"
 #include "pw_rpc/internal/method_union.h"
 #include "pw_rpc/internal/packet.h"
-#include "pw_rpc/internal/server_call.h"
-#include "pw_rpc/method_type.h"
+#include "pw_rpc/server_context.h"
 #include "pw_status/status_with_size.h"
 
 namespace pw::rpc::internal {
@@ -31,65 +29,22 @@
 // channel ID, request, and payload buffer, and optionally provides a response.
 class TestMethod : public Method {
  public:
-  class FakeServerCall : public ServerCall {
-   public:
-    constexpr FakeServerCall() = default;
-    FakeServerCall(const CallContext& context, MethodType type)
-        : ServerCall(context, type) {}
-
-    FakeServerCall(FakeServerCall&&) = default;
-    FakeServerCall& operator=(FakeServerCall&&) = default;
-
-    using internal::Call::set_on_error;
-  };
-
-  constexpr TestMethod(uint32_t id, MethodType type = MethodType::kUnary)
-      : Method(id, GetInvoker(type)),
-        last_channel_id_(0),
-        invocations_(0),
-        move_to_call_(nullptr) {}
+  constexpr TestMethod(uint32_t id)
+      : Method(id, InvokeForTest), last_channel_id_(0) {}
 
   uint32_t last_channel_id() const { return last_channel_id_; }
   const Packet& last_request() const { return last_request_; }
-  size_t invocations() const { return invocations_; }
 
-  // Sets a call object into which to move the call object when the RPC is
-  // invoked. This keeps the RPC active until the provided call object is
-  // finished or goes out of scope.
-  void keep_call_active(FakeServerCall& move_to_call) const {
-    move_to_call_ = &move_to_call;
-  }
+  void set_response(std::span<const std::byte> payload) { response_ = payload; }
+  void set_status(Status status) { response_status_ = status; }
 
  private:
-  template <MethodType kType>
-  static void InvokeForTest(const CallContext& context, const Packet& request)
-      PW_UNLOCK_FUNCTION(rpc_lock()) {
-    const auto& test_method = static_cast<const TestMethod&>(context.method());
-    test_method.last_channel_id_ = context.channel_id();
+  static void InvokeForTest(const Method& method,
+                            ServerCall& call,
+                            const Packet& request) {
+    const auto& test_method = static_cast<const TestMethod&>(method);
+    test_method.last_channel_id_ = call.channel().id();
     test_method.last_request_ = request;
-    test_method.invocations_ += 1;
-
-    // Create a call object so it registers / unregisters with the server.
-    FakeServerCall fake_call(context, kType);
-
-    rpc_lock().unlock();
-
-    if (test_method.move_to_call_ != nullptr) {
-      *test_method.move_to_call_ = std::move(fake_call);
-    }
-  }
-
-  static constexpr Invoker GetInvoker(MethodType type) {
-    switch (type) {
-      case MethodType::kUnary:
-        return InvokeForTest<MethodType::kUnary>;
-      case MethodType::kServerStreaming:
-        return InvokeForTest<MethodType::kServerStreaming>;
-      case MethodType::kClientStreaming:
-        return InvokeForTest<MethodType::kClientStreaming>;
-      case MethodType::kBidirectionalStreaming:
-        return InvokeForTest<MethodType::kBidirectionalStreaming>;
-    };
   }
 
   // Make these mutable so they can be set in the Invoke method, which is const.
@@ -97,8 +52,6 @@
   // allows tests to verify that the Method is invoked correctly.
   mutable uint32_t last_channel_id_;
   mutable Packet last_request_;
-  mutable size_t invocations_;
-  mutable FakeServerCall* move_to_call_;
 
   std::span<const std::byte> response_;
   Status response_status_;
diff --git a/pw_rpc/public/pw_rpc/internal/test_method_context.h b/pw_rpc/public/pw_rpc/internal/test_method_context.h
deleted file mode 100644
index a8e5461..0000000
--- a/pw_rpc/public/pw_rpc/internal/test_method_context.h
+++ /dev/null
@@ -1,171 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstddef>
-
-#include "pw_assert/assert.h"
-#include "pw_rpc/channel.h"
-#include "pw_rpc/internal/fake_channel_output.h"
-#include "pw_rpc/internal/method.h"
-#include "pw_rpc/internal/packet.h"
-#include "pw_rpc/server.h"
-
-namespace pw::rpc::internal::test {
-
-// Collects everything needed to invoke a particular RPC.
-template <typename Output, typename Service, uint32_t kMethodId>
-class InvocationContext {
- public:
-  InvocationContext(const InvocationContext&) = delete;
-  InvocationContext(InvocationContext&&) = delete;
-
-  InvocationContext& operator=(const InvocationContext&) = delete;
-  InvocationContext& operator=(InvocationContext&&) = delete;
-
-  Service& service() { return service_; }
-  const Service& service() const { return service_; }
-
-  // Sets the channel ID, which defaults to an arbitrary value.
-  void set_channel_id(uint32_t channel_id) {
-    PW_ASSERT(channel_id != Channel::kUnassignedChannelId);
-
-    // If using dynamic allocation, the channel objects are owned by the
-    // endpoint. The external channel is only used to initialize the endpoint's
-    // channels vector. To update that channel, remove and re-add the channel.
-    PW_ASSERT(server_.CloseChannel(context_.channel_id()).ok());
-    PW_ASSERT(server_.OpenChannel(channel_id, output_).ok());
-
-    channel_ = Channel(channel_id, &output_);
-    context_.set_channel_id(channel_id);
-  }
-
-  size_t total_responses() const { return responses().size(); }
-
-  size_t max_packets() const { return output_.max_packets(); }
-
-  // Returns the responses that have been recorded. The maximum number of
-  // responses is responses().max_size(). responses().back() is always the most
-  // recent response, even if total_responses() > responses().max_size().
-  auto responses() const {
-    return output().payloads(
-        method_type_, channel_.id(), service().id(), kMethodId);
-  }
-
-  // True if the RPC has completed.
-  bool done() const { return output_.done(); }
-
-  // The status of the stream. Only valid if done() is true.
-  Status status() const {
-    PW_ASSERT(done());
-    return output_.last_status();
-  }
-
-  void SendClientError(Status error) {
-    std::byte packet[kNoPayloadPacketSizeBytes];
-    PW_ASSERT(server_
-                  .ProcessPacket(Packet(PacketType::CLIENT_ERROR,
-                                        channel_.id(),
-                                        service_.id(),
-                                        kMethodId,
-                                        0,
-                                        {},
-                                        error)
-                                     .Encode(packet)
-                                     .value(),
-                                 output_)
-                  .ok());
-  }
-
-  const Output& output() const { return output_; }
-  Output& output() { return output_; }
-
- protected:
-  // Constructs the invocation context. The args for the ChannelOutput type are
-  // passed in a std::tuple. The args for the Service are forwarded directly
-  // from the callsite.
-  template <typename... ServiceArgs>
-  InvocationContext(const Method& method,
-                    MethodType method_type,
-                    ServiceArgs&&... service_args)
-      : method_type_(method_type),
-        channel_(123, &output_),
-        server_(std::span(static_cast<rpc::Channel*>(&channel_), 1)),
-        service_(std::forward<ServiceArgs>(service_args)...),
-        context_(server_, channel_.id(), service_, method, 0) {
-    server_.RegisterService(service_);
-  }
-
-  uint32_t channel_id() const { return channel_.id(); }
-
-  template <size_t kMaxPayloadSize = 32>
-  void SendClientStream(ConstByteSpan payload) {
-    std::byte packet[kNoPayloadPacketSizeBytes + 3 + kMaxPayloadSize];
-    PW_ASSERT(server_
-                  .ProcessPacket(Packet(PacketType::CLIENT_STREAM,
-                                        channel_.id(),
-                                        service_.id(),
-                                        kMethodId,
-                                        0,
-                                        payload)
-                                     .Encode(packet)
-                                     .value(),
-                                 output_)
-                  .ok());
-  }
-
-  void SendClientStreamEnd() {
-    std::byte packet[kNoPayloadPacketSizeBytes];
-    PW_ASSERT(server_
-                  .ProcessPacket(Packet(PacketType::CLIENT_STREAM_END,
-                                        channel_.id(),
-                                        service_.id(),
-                                        kMethodId)
-                                     .Encode(packet)
-                                     .value(),
-                                 output_)
-                  .ok());
-  }
-
-  // Invokes the RPC, optionally with a request argument.
-  template <auto kMethod, typename T, typename... RequestArg>
-  void call(RequestArg&&... request) {
-    static_assert(sizeof...(request) <= 1);
-    output_.clear();
-    T responder = GetResponder<T>();
-    CallMethodImplFunction<kMethod>(
-        service(), std::forward<RequestArg>(request)..., responder);
-  }
-
-  template <typename T>
-  T GetResponder() {
-    return T(call_context());
-  }
-
-  const internal::CallContext& call_context() const { return context_; }
-
- private:
-  static constexpr size_t kNoPayloadPacketSizeBytes =
-      2 /* type */ + 2 /* channel */ + 5 /* service */ + 5 /* method */ +
-      2 /* status */;
-
-  const MethodType method_type_;
-  Output output_;
-  Channel channel_;
-  rpc::Server server_;
-  Service service_;
-  internal::CallContext context_;
-};
-
-}  // namespace pw::rpc::internal::test
diff --git a/pw_rpc/public/pw_rpc/internal/test_utils.h b/pw_rpc/public/pw_rpc/internal/test_utils.h
deleted file mode 100644
index e3f8be5..0000000
--- a/pw_rpc/public/pw_rpc/internal/test_utils.h
+++ /dev/null
@@ -1,157 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// Internal-only testing utilities. public/pw_rpc/test_method_context.h provides
-// improved public-facing utilities for testing RPC services.
-#pragma once
-
-#include <array>
-#include <cstddef>
-#include <cstdint>
-#include <span>
-
-#include "gtest/gtest.h"
-#include "pw_assert/assert.h"
-#include "pw_rpc/client.h"
-#include "pw_rpc/internal/channel.h"
-#include "pw_rpc/internal/method.h"
-#include "pw_rpc/internal/packet.h"
-#include "pw_rpc/raw/fake_channel_output.h"
-#include "pw_rpc/server.h"
-
-namespace pw::rpc::internal {
-
-// Version of the Server with extra methods exposed for testing.
-class TestServer : public Server {
- public:
-  using Server::FindCall;
-};
-
-template <typename Service, uint32_t kChannelId = 99, uint32_t kServiceId = 16>
-class ServerContextForTest {
- public:
-  static constexpr uint32_t channel_id() { return kChannelId; }
-  static constexpr uint32_t service_id() { return kServiceId; }
-
-  ServerContextForTest(const internal::Method& method)
-      : channel_(Channel::Create<kChannelId>(&output_)),
-        server_(std::span(&channel_, 1)),
-        service_(kServiceId),
-        context_(
-            static_cast<Server&>(server_), channel_.id(), service_, method, 0) {
-    server_.RegisterService(service_);
-  }
-
-  // Create packets for this context's channel, service, and method.
-  internal::Packet request(std::span<const std::byte> payload) const {
-    return internal::Packet(internal::PacketType::REQUEST,
-                            kChannelId,
-                            kServiceId,
-                            context_.method().id(),
-                            0,
-                            payload);
-  }
-
-  internal::Packet response(Status status,
-                            std::span<const std::byte> payload = {}) const {
-    return internal::Packet(internal::PacketType::RESPONSE,
-                            kChannelId,
-                            kServiceId,
-                            context_.method().id(),
-                            0,
-                            payload,
-                            status);
-  }
-
-  internal::Packet server_stream(std::span<const std::byte> payload) const {
-    return internal::Packet(internal::PacketType::SERVER_STREAM,
-                            kChannelId,
-                            kServiceId,
-                            context_.method().id(),
-                            0,
-                            payload);
-  }
-
-  internal::Packet client_stream(std::span<const std::byte> payload) const {
-    return internal::Packet(internal::PacketType::CLIENT_STREAM,
-                            kChannelId,
-                            kServiceId,
-                            context_.method().id(),
-                            0,
-                            payload);
-  }
-
-  const internal::CallContext& get() { return context_; }
-  internal::test::FakeChannelOutput& output() { return output_; }
-  TestServer& server() { return static_cast<TestServer&>(server_); }
-  Service& service() { return service_; }
-
- private:
-  RawFakeChannelOutput<5> output_;
-  rpc::Channel channel_;
-  rpc::Server server_;
-  Service service_;
-
-  const internal::CallContext context_;
-};
-
-template <size_t kInputBufferSize = 128,
-          uint32_t kChannelId = 99,
-          uint32_t kServiceId = 16,
-          uint32_t kMethodId = 111>
-class ClientContextForTest {
- public:
-  static constexpr uint32_t channel_id() { return kChannelId; }
-  static constexpr uint32_t service_id() { return kServiceId; }
-  static constexpr uint32_t method_id() { return kMethodId; }
-
-  ClientContextForTest()
-      : channel_(Channel::Create<kChannelId>(&output_)),
-        client_(std::span(&channel_, 1)) {}
-
-  const internal::test::FakeChannelOutput& output() const { return output_; }
-  Channel& channel() { return static_cast<Channel&>(channel_); }
-  Client& client() { return client_; }
-
-  // Sends a packet to be processed by the client. Returns the client's
-  // ProcessPacket status.
-  Status SendPacket(internal::PacketType type,
-                    Status status = OkStatus(),
-                    std::span<const std::byte> payload = {}) {
-    uint32_t call_id =
-        output().total_packets() > 0 ? output().last_packet().call_id() : 0;
-
-    internal::Packet packet(
-        type, kChannelId, kServiceId, kMethodId, call_id, payload, status);
-    std::byte buffer[kInputBufferSize];
-    Result result = packet.Encode(buffer);
-    EXPECT_EQ(result.status(), OkStatus());
-    return client_.ProcessPacket(result.value_or(ConstByteSpan()));
-  }
-
-  Status SendResponse(Status status, std::span<const std::byte> payload = {}) {
-    return SendPacket(internal::PacketType::RESPONSE, status, payload);
-  }
-
-  Status SendServerStream(std::span<const std::byte> payload) {
-    return SendPacket(internal::PacketType::SERVER_STREAM, OkStatus(), payload);
-  }
-
- private:
-  RawFakeChannelOutput<5> output_;
-  rpc::Channel channel_;
-  Client client_;
-};
-
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/public/pw_rpc/method_type.h b/pw_rpc/public/pw_rpc/method_type.h
deleted file mode 100644
index 4a7b307..0000000
--- a/pw_rpc/public/pw_rpc/method_type.h
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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
-
-namespace pw::rpc {
-
-enum class MethodType : unsigned char {
-  kUnary = 0b00,
-  kServerStreaming = 0b01,
-  kClientStreaming = 0b10,
-  kBidirectionalStreaming = 0b11,
-};
-
-// True for unary and server streaming RPCs.
-constexpr bool HasServerStream(MethodType type) {
-  return (static_cast<unsigned>(type) &
-          static_cast<unsigned>(MethodType::kServerStreaming)) != 0;
-}
-
-// True for client and bidirectional streaming RPCs.
-constexpr bool HasClientStream(MethodType type) {
-  return (static_cast<unsigned>(type) &
-          static_cast<unsigned>(MethodType::kClientStreaming)) != 0;
-}
-
-}  // namespace pw::rpc
diff --git a/pw_rpc/public/pw_rpc/payloads_view.h b/pw_rpc/public/pw_rpc/payloads_view.h
deleted file mode 100644
index 3d4934f..0000000
--- a/pw_rpc/public/pw_rpc/payloads_view.h
+++ /dev/null
@@ -1,261 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// The classes in this file facilitate testing RPC services. The main class is
-// PayloadsView, which iterates over the payloads sent by an RPC service or
-// client. This allows verifying that code that invokes RPCs or an RPC service
-// implementation sends the expected requests or responses.
-//
-// This code is inteded for testing, not for deployment.
-#pragma once
-
-#include <tuple>
-
-#include "pw_containers/filtered_view.h"
-#include "pw_containers/vector.h"
-#include "pw_containers/wrapped_iterator.h"
-#include "pw_rpc/channel.h"
-#include "pw_rpc/internal/method_info.h"
-#include "pw_rpc/internal/packet.h"
-#include "pw_rpc/method_type.h"
-
-namespace pw::rpc {
-namespace internal::test {
-
-class FakeChannelOutput;
-
-// Finds packets of a specified type for a particular method.
-class PacketFilter {
- public:
-  // Use Channel::kUnassignedChannelId to ignore the channel.
-  constexpr PacketFilter(PacketType packet_type_1,
-                         PacketType packet_type_2,
-                         uint32_t channel_id,
-                         uint32_t service_id,
-                         uint32_t method_id)
-      : packet_type_1_(packet_type_1),
-        packet_type_2_(packet_type_2),
-        channel_id_(channel_id),
-        service_id_(service_id),
-        method_id_(method_id) {}
-
-  constexpr bool operator()(const Packet& packet) const {
-    return (packet.type() == packet_type_1_ ||
-            packet.type() == packet_type_2_) &&
-           (channel_id_ == Channel::kUnassignedChannelId ||
-            packet.channel_id() == channel_id_) &&
-           packet.service_id() == service_id_ &&
-           packet.method_id() == method_id_;
-  }
-
- private:
-  // Support filtering on two packet types to handle reading both client and
-  // server streams for bidirectional streams.
-  PacketType packet_type_1_;
-  PacketType packet_type_2_;
-  uint32_t channel_id_;
-  uint32_t service_id_;
-  uint32_t method_id_;
-};
-
-using PacketsView = containers::FilteredView<Vector<Packet>, PacketFilter>;
-
-}  // namespace internal::test
-
-// Returns the payloads for a particular RPC in a Vector of RPC packets.
-//
-// Adapts a FilteredView of packets to return payloads instead of packets.
-class PayloadsView {
- public:
-  class iterator : public containers::WrappedIterator<
-                       iterator,
-                       internal::test::PacketsView::iterator,
-                       ConstByteSpan> {
-   public:
-    constexpr iterator() = default;
-
-    // Access the payload (rather than packet) with operator* and operator->.
-    const ConstByteSpan& operator*() const { return value().payload(); }
-    const ConstByteSpan* operator->() const { return &value().payload(); }
-
-   private:
-    friend class PayloadsView;
-
-    constexpr iterator(const internal::test::PacketsView::iterator& it)
-        : containers::WrappedIterator<iterator,
-                                      internal::test::PacketsView::iterator,
-                                      ConstByteSpan>(it) {}
-  };
-
-  using const_iterator = iterator;
-
-  const ConstByteSpan& operator[](size_t index) const {
-    auto it = begin();
-    std::advance(it, index);
-    return *it;
-  }
-
-  // Number of payloads for the specified RPC.
-  size_t size() const { return view_.size(); }
-
-  bool empty() const { return begin() == end(); }
-
-  // Returns the first/last payload for the RPC. size() must be > 0.
-  const ConstByteSpan& front() const { return *begin(); }
-  const ConstByteSpan& back() const { return *std::prev(end()); }
-
-  iterator begin() const { return iterator(view_.begin()); }
-  iterator end() const { return iterator(view_.end()); }
-
- private:
-  friend class internal::test::FakeChannelOutput;
-
-  template <typename>
-  friend class NanopbPayloadsView;
-
-  template <auto kMethod>
-  using MethodInfo = internal::MethodInfo<kMethod>;
-
-  using PacketType = internal::PacketType;
-
-  template <auto kMethod>
-  static constexpr PayloadsView For(const Vector<internal::Packet>& packets,
-                                    uint32_t channel_id) {
-    constexpr auto kTypes = PacketTypesWithPayload(MethodInfo<kMethod>::kType);
-    return PayloadsView(packets,
-                        std::get<0>(kTypes),
-                        std::get<1>(kTypes),
-                        channel_id,
-                        MethodInfo<kMethod>::kServiceId,
-                        MethodInfo<kMethod>::kMethodId);
-  }
-
-  constexpr PayloadsView(const Vector<internal::Packet>& packets,
-                         MethodType method_type,
-                         uint32_t channel_id,
-                         uint32_t service_id,
-                         uint32_t method_id)
-      : PayloadsView(packets,
-                     std::get<0>(PacketTypesWithPayload(method_type)),
-                     std::get<1>(PacketTypesWithPayload(method_type)),
-                     channel_id,
-                     service_id,
-                     method_id) {}
-
-  constexpr PayloadsView(const Vector<internal::Packet>& packets,
-                         PacketType packet_type_1,
-                         PacketType packet_type_2,
-                         uint32_t channel_id,
-                         uint32_t service_id,
-                         uint32_t method_id)
-      : view_(packets,
-              internal::test::PacketFilter(packet_type_1,
-                                           packet_type_2,
-                                           channel_id,
-                                           service_id,
-                                           method_id)) {}
-
-  static constexpr std::tuple<PacketType, PacketType> PacketTypesWithPayload(
-      MethodType method_type) {
-    switch (method_type) {
-      case MethodType::kUnary:
-        return {PacketType::REQUEST, PacketType::RESPONSE};
-      case MethodType::kServerStreaming:
-        return {PacketType::REQUEST, PacketType::SERVER_STREAM};
-      case MethodType::kClientStreaming:
-        return {PacketType::CLIENT_STREAM, PacketType::RESPONSE};
-      case MethodType::kBidirectionalStreaming:
-        return {PacketType::CLIENT_STREAM, PacketType::SERVER_STREAM};
-    }
-
-// Workaround for GCC 8 bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86678
-#if defined(__GNUC__) && __GNUC__ < 9
-    return {};
-#else
-    PW_ASSERT(false);
-#endif  // defined(__GNUC__) && __GNUC__ < 9
-  }
-
-  internal::test::PacketsView view_;
-};
-
-// Class for iterating over RPC statuses associated witha particular RPC. This
-// is used to iterate over the user RPC statuses and or protocol errors for a
-// particular RPC.
-class StatusView {
- public:
-  class iterator : public containers::WrappedIterator<
-                       iterator,
-                       internal::test::PacketsView::iterator,
-                       Status> {
-   public:
-    constexpr iterator() = default;
-
-    // Access the status (rather than packet) with operator* and operator->.
-    const Status& operator*() const { return value().status(); }
-    const Status* operator->() const { return &value().status(); }
-
-   private:
-    friend class StatusView;
-
-    constexpr iterator(const internal::test::PacketsView::iterator& it)
-        : containers::WrappedIterator<iterator,
-                                      internal::test::PacketsView::iterator,
-                                      Status>(it) {}
-  };
-
-  using const_iterator = iterator;
-
-  const Status& operator[](size_t index) const {
-    auto it = begin();
-    std::advance(it, index);
-    return *it;
-  }
-
-  // Number of statuses in this view.
-  size_t size() const { return view_.size(); }
-  bool empty() const { return begin() == end(); }
-
-  // Returns the first/last payload for the RPC. size() must be > 0.
-  const Status& front() const { return *begin(); }
-  const Status& back() const { return *std::prev(end()); }
-
-  iterator begin() const { return iterator(view_.begin()); }
-  iterator end() const { return iterator(view_.end()); }
-
- private:
-  friend class internal::test::FakeChannelOutput;
-
-  template <auto kMethod>
-  using MethodInfo = internal::MethodInfo<kMethod>;
-
-  using PacketType = internal::PacketType;
-
-  constexpr StatusView(const Vector<internal::Packet>& packets,
-                       PacketType packet_type_1,
-                       PacketType packet_type_2,
-                       uint32_t channel_id,
-                       uint32_t service_id,
-                       uint32_t method_id)
-      : view_(packets,
-              internal::test::PacketFilter(packet_type_1,
-                                           packet_type_2,
-                                           channel_id,
-                                           service_id,
-                                           method_id)) {}
-
-  internal::test::PacketsView view_;
-};
-
-}  // namespace pw::rpc
diff --git a/pw_rpc/public/pw_rpc/server.h b/pw_rpc/public/pw_rpc/server.h
index 69eae07..51e3f36 100644
--- a/pw_rpc/public/pw_rpc/server.h
+++ b/pw_rpc/public/pw_rpc/server.h
@@ -19,38 +19,25 @@
 
 #include "pw_containers/intrusive_list.h"
 #include "pw_rpc/channel.h"
+#include "pw_rpc/internal/base_server_writer.h"
 #include "pw_rpc/internal/channel.h"
-#include "pw_rpc/internal/endpoint.h"
-#include "pw_rpc/internal/lock.h"
 #include "pw_rpc/internal/method.h"
-#include "pw_rpc/internal/method_info.h"
-#include "pw_rpc/internal/server_call.h"
 #include "pw_rpc/service.h"
 #include "pw_status/status.h"
 
 namespace pw::rpc {
 
-class Server : public internal::Endpoint {
+class Server {
  public:
-  _PW_RPC_CONSTEXPR Server(std::span<Channel> channels) : Endpoint(channels) {}
+  constexpr Server(std::span<Channel> channels)
+      : channels_(static_cast<internal::Channel*>(channels.data()),
+                  channels.size()) {}
 
-  // Registers one or more services with the server. This should not be called
-  // directly with a Service; instead, use a generated class which inherits
-  // from it.
-  //
-  // This function may be called with any number of services. Combining
-  // registration into fewer calls is preferred so the RPC mutex is only
-  // locked/unlocked once.
-  template <typename... OtherServices>
-  void RegisterService(Service& service, OtherServices&... services)
-      PW_LOCKS_EXCLUDED(internal::rpc_lock()) {
-    internal::LockGuard lock(internal::rpc_lock());
-    services_.push_front(service);  // Register the first service
+  ~Server();
 
-    // Register any additional services by expanding the parameter pack. This
-    // is a fold expression of the comma operator.
-    (services_.push_front(services), ...);
-  }
+  // Registers a service with the server. This should not be called directly
+  // with a Service; instead, use a generated class which inherits from it.
+  void RegisterService(Service& service) { services_.push_front(service); }
 
   // Processes an RPC packet. The packet may contain an RPC request or a control
   // packet, the result of which is processed in this function. Returns whether
@@ -59,93 +46,29 @@
   //   OK - The packet was processed by the server.
   //   DATA_LOSS - Failed to decode the packet.
   //   INVALID_ARGUMENT - The packet is intended for a client, not a server.
-  //   UNAVAILABLE - No RPC channel with the requested ID was found.
   //
-  // ProcessPacket optionally accepts a ChannelOutput as a second argument. If
-  // provided, the server respond on that interface if an unknown channel is
-  // requested.
-  Status ProcessPacket(ConstByteSpan packet_data)
-      PW_LOCKS_EXCLUDED(internal::rpc_lock()) {
-    return ProcessPacket(packet_data, nullptr);
-  }
-  Status ProcessPacket(ConstByteSpan packet_data, ChannelOutput& interface)
-      PW_LOCKS_EXCLUDED(internal::rpc_lock()) {
-    return ProcessPacket(packet_data, &interface);
-  }
+  Status ProcessPacket(std::span<const std::byte> packet,
+                       ChannelOutput& interface);
+
+  constexpr size_t channel_count() const { return channels_.size(); }
+
+ protected:
+  IntrusiveList<internal::BaseServerWriter>& writers() { return writers_; }
 
  private:
-  friend class internal::Call;
-  friend class ClientServer;
-
-  // Give call classes access to OpenContext.
-  friend class RawServerReaderWriter;
-  friend class RawServerWriter;
-  friend class RawServerReader;
-  friend class RawUnaryResponder;
-
-  template <typename, typename>
-  friend class NanopbServerReaderWriter;
-  template <typename>
-  friend class NanopbServerWriter;
-  template <typename, typename>
-  friend class NanopbServerReader;
-  template <typename>
-  friend class NanopbUnaryResponder;
-
-  // Creates a call context for a particular RPC. Unlike the CallContext
-  // constructor, this function checks the type of RPC at compile time.
-  template <auto kMethod,
-            MethodType kExpected,
-            typename ServiceImpl,
-            typename MethodImpl>
-  internal::CallContext OpenContext(uint32_t channel_id,
-                                    ServiceImpl& service,
-                                    const MethodImpl& method)
-      PW_EXCLUSIVE_LOCKS_REQUIRED(internal::rpc_lock()) {
-    using Info = internal::MethodInfo<kMethod>;
-    if constexpr (kExpected == MethodType::kUnary) {
-      static_assert(
-          Info::kType == kExpected,
-          "UnaryResponder objects may only be opened for unary RPCs.");
-    } else if constexpr (kExpected == MethodType::kServerStreaming) {
-      static_assert(
-          Info::kType == kExpected,
-          "ServerWriters may only be opened for server streaming RPCs.");
-    } else if constexpr (kExpected == MethodType::kClientStreaming) {
-      static_assert(
-          Info::kType == kExpected,
-          "ServerReaders may only be opened for client streaming RPCs.");
-    } else if constexpr (kExpected == MethodType::kBidirectionalStreaming) {
-      static_assert(Info::kType == kExpected,
-                    "ServerReaderWriters may only be opened for bidirectional "
-                    "streaming RPCs.");
-    }
-
-    // Unrequested RPCs always use 0 as the call ID. When an actual request is
-    // sent, the call will be replaced with its real ID.
-    constexpr uint32_t kOpenCallId = 0;
-
-    return internal::CallContext(
-        *this, channel_id, service, method, kOpenCallId);
-  }
-
-  Status ProcessPacket(ConstByteSpan packet_data, ChannelOutput* interface)
-      PW_LOCKS_EXCLUDED(internal::rpc_lock());
-
   std::tuple<Service*, const internal::Method*> FindMethod(
-      const internal::Packet& packet)
-      PW_EXCLUSIVE_LOCKS_REQUIRED(internal::rpc_lock());
+      const internal::Packet& packet);
 
-  void HandleClientStreamPacket(const internal::Packet& packet,
-                                internal::Channel& channel,
-                                internal::ServerCall* call) const
-      PW_UNLOCK_FUNCTION(internal::rpc_lock());
+  void HandleCancelPacket(const internal::Packet& request,
+                          internal::Channel& channel);
+  void HandleClientError(const internal::Packet& packet);
 
-  // Remove these internal::Endpoint functions from the public interface.
-  using Endpoint::active_call_count;
-  using Endpoint::GetInternalChannel;
+  internal::Channel* FindChannel(uint32_t id) const;
+  internal::Channel* AssignChannel(uint32_t id, ChannelOutput& interface);
 
-  IntrusiveList<Service> services_ PW_GUARDED_BY(internal::rpc_lock());
+  std::span<internal::Channel> channels_;
+  IntrusiveList<Service> services_;
+  IntrusiveList<internal::BaseServerWriter> writers_;
 };
 
 }  // namespace pw::rpc
diff --git a/pw_rpc/public/pw_rpc/server_context.h b/pw_rpc/public/pw_rpc/server_context.h
new file mode 100644
index 0000000..91e47d1
--- /dev/null
+++ b/pw_rpc/public/pw_rpc/server_context.h
@@ -0,0 +1,53 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 <cstddef>
+#include <cstdint>
+
+#include "pw_rpc/internal/call.h"
+
+namespace pw::rpc {
+
+// The ServerContext collects context for an RPC being invoked on a server. The
+// ServerContext is passed into RPC functions and is user-facing.
+//
+// The ServerContext is a public-facing view of the internal::ServerCall class.
+// It uses inheritance to avoid copying or creating an extra reference to the
+// underlying ServerCall. Private inheritance prevents exposing the
+// internal-facing ServerCall interface.
+class ServerContext : private internal::ServerCall {
+ public:
+  // Returns the ID for the channel this RPC is using.
+  uint32_t channel_id() const { return channel().id(); }
+
+  constexpr ServerContext() = delete;
+
+  constexpr ServerContext(const ServerContext&) = delete;
+  constexpr ServerContext& operator=(const ServerContext&) = delete;
+
+  constexpr ServerContext(ServerContext&&) = delete;
+  constexpr ServerContext& operator=(ServerContext&&) = delete;
+
+  friend class internal::ServerCall;  // Allow down-casting from ServerCall.
+};
+
+namespace internal {
+
+inline ServerContext& ServerCall::context() {
+  return static_cast<ServerContext&>(*this);
+}
+
+}  // namespace internal
+}  // namespace pw::rpc
diff --git a/pw_rpc/public/pw_rpc/service.h b/pw_rpc/public/pw_rpc/service.h
index a4f2dbf..4e1a1a0 100644
--- a/pw_rpc/public/pw_rpc/service.h
+++ b/pw_rpc/public/pw_rpc/service.h
@@ -32,12 +32,6 @@
 // size of the concrete MethodUnion object.
 class Service : public IntrusiveList<Service>::Item {
  public:
-  Service(const Service&) = delete;
-  Service(Service&&) = delete;
-
-  Service& operator=(const Service&) = delete;
-  Service& operator=(Service&&) = delete;
-
   uint32_t id() const { return id_; }
 
  protected:
diff --git a/pw_rpc/public/pw_rpc/synchronized_channel_output.h b/pw_rpc/public/pw_rpc/synchronized_channel_output.h
new file mode 100644
index 0000000..c67fe19
--- /dev/null
+++ b/pw_rpc/public/pw_rpc/synchronized_channel_output.h
@@ -0,0 +1,52 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 <algorithm>
+
+#include "pw_rpc/channel.h"
+#include "pw_sync/lock_annotations.h"
+#include "pw_sync/mutex.h"
+
+namespace pw::rpc {
+
+// Wraps an RPC ChannelOutput implementation with a mutex to synchronize its
+// acquire and release buffer operations. This can be used to allow a simple
+// ChannelOutput implementation to run in multi-threaded contexts. More complex
+// implementations may want to roll their own synchronization.
+template <typename BaseChannelOutput>
+class PW_LOCKABLE("pw::rpc::SynchronizedChannelOutput")
+    SynchronizedChannelOutput final : public BaseChannelOutput {
+ public:
+  template <typename... Args>
+  constexpr SynchronizedChannelOutput(sync::Mutex& mutex, Args&&... args)
+      : BaseChannelOutput(std::forward<Args>(args)...), mutex_(mutex) {}
+
+  std::span<std::byte> AcquireBuffer() final PW_EXCLUSIVE_LOCK_FUNCTION() {
+    mutex_.lock();
+    return BaseChannelOutput::AcquireBuffer();
+  }
+
+  Status SendAndReleaseBuffer(std::span<const std::byte> buffer) final
+      PW_UNLOCK_FUNCTION() {
+    Status status = BaseChannelOutput::SendAndReleaseBuffer(buffer);
+    mutex_.unlock();
+    return status;
+  }
+
+ private:
+  sync::Mutex& mutex_;
+};
+
+}  // namespace pw::rpc
diff --git a/pw_rpc/public/pw_rpc/thread_testing.h b/pw_rpc/public/pw_rpc/thread_testing.h
deleted file mode 100644
index eb723a2..0000000
--- a/pw_rpc/public/pw_rpc/thread_testing.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <chrono>
-
-#include "pw_assert/assert.h"
-#include "pw_rpc/internal/fake_channel_output.h"
-#include "pw_sync/counting_semaphore.h"
-
-namespace pw::rpc::test {
-
-// Wait until the provided RawFakeChannelOutput or NanopbFakeChannelOutput
-// receives the specified number of packets.
-template <unsigned kTimeoutSeconds = 10, typename Function>
-void WaitForPackets(internal::test::FakeChannelOutput& output,
-                    int count,
-                    Function&& run_before) {
-  sync::CountingSemaphore sem;
-  output.set_on_send([&sem](ConstByteSpan, Status) { sem.release(); });
-
-  run_before();
-
-  for (int i = 0; i < count; ++i) {
-    PW_ASSERT(sem.try_acquire_for(std::chrono::seconds(kTimeoutSeconds)));
-  }
-
-  output.set_on_send(nullptr);
-}
-
-}  // namespace pw::rpc::test
diff --git a/pw_rpc/public/pw_rpc/writer.h b/pw_rpc/public/pw_rpc/writer.h
deleted file mode 100644
index e7e7c41..0000000
--- a/pw_rpc/public/pw_rpc/writer.h
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/internal/call.h"
-
-namespace pw::rpc {
-
-// The Writer class allows writing requests or responses to a streaming RPC.
-// ClientWriter, ClientReaderWriter, ServerWriter, and ServerReaderWriter
-// classes can be used as a generic Writer.
-class Writer : private internal::Call {
- public:
-  // Writers cannot be created directly. They may only be used as a reference to
-  // an existing call object.
-  Writer() = delete;
-
-  Writer(const Writer&) = delete;
-  Writer(Writer&&) = delete;
-
-  Writer& operator=(const Writer&) = delete;
-  Writer& operator=(Writer&&) = delete;
-
-  using internal::Call::active;
-  using internal::Call::channel_id;
-
-  using internal::Call::Write;
-
- private:
-  friend class internal::Call;
-};
-
-namespace internal {
-
-constexpr Call::operator Writer&() { return static_cast<Writer&>(*this); }
-
-constexpr Call::operator const Writer&() const {
-  return static_cast<const Writer&>(*this);
-}
-
-}  // namespace internal
-}  // namespace pw::rpc
diff --git a/pw_rpc/pw_rpc_private/fake_server_reader_writer.h b/pw_rpc/pw_rpc_private/fake_server_reader_writer.h
deleted file mode 100644
index 44c359b..0000000
--- a/pw_rpc/pw_rpc_private/fake_server_reader_writer.h
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_bytes/span.h"
-#include "pw_rpc/internal/call_context.h"
-#include "pw_rpc/internal/server_call.h"
-#include "pw_rpc/method_type.h"
-
-namespace pw::rpc::internal::test {
-
-// Fake server reader/writer classes for testing use. These also serve as a
-// model for how the RPC implementations (raw, pwpb, Nanopb) structure their
-// reader/writer classes.
-//
-// Readers/writers use an unusual inheritance hierarchy. Rather than having the
-// ServerReaderWriter inherit from both the Reader and Writer classes, the
-// readers and writers inherit from it, but hide the unsupported functionality.
-// A ReaderWriter defines conversions to Reader and Writer, so it acts as if it
-// inherited from both. This approach is unusual but necessary to have all
-// classes use a single IntrusiveList::Item base and to avoid virtual methods or
-// virtual inheritance.
-//
-// Call's public API is intended for rpc::Server, so hide the public methods
-// with private inheritance.
-class FakeServerReaderWriter : private internal::ServerCall {
- public:
-  constexpr FakeServerReaderWriter() = default;
-
-  // On a real reader/writer, this constructor would not be exposed.
-  FakeServerReaderWriter(const CallContext& context,
-                         MethodType type = MethodType::kBidirectionalStreaming)
-      : ServerCall(context, type) {}
-
-  FakeServerReaderWriter(FakeServerReaderWriter&&) = default;
-  FakeServerReaderWriter& operator=(FakeServerReaderWriter&&) = default;
-
-  // Pull in protected functions from the hidden Call base as needed.
-  using Call::active;
-  using Call::set_on_error;
-  using Call::set_on_next;
-  using ServerCall::set_on_client_stream_end;
-
-  Status Finish(Status status = OkStatus()) {
-    return CloseAndSendResponse(status);
-  }
-
-  using Call::Write;
-
-  // Expose a few additional methods for test use.
-  ServerCall& as_server_call() { return *this; }
-};
-
-class FakeServerWriter : private FakeServerReaderWriter {
- public:
-  constexpr FakeServerWriter() = default;
-
-  FakeServerWriter(const CallContext& context)
-      : FakeServerReaderWriter(context, MethodType::kServerStreaming) {}
-  FakeServerWriter(FakeServerWriter&&) = default;
-
-  // Common reader/writer functions.
-  using FakeServerReaderWriter::active;
-  using FakeServerReaderWriter::Finish;
-  using FakeServerReaderWriter::set_on_error;
-  using FakeServerReaderWriter::Write;
-
-  // Functions for test use.
-  using FakeServerReaderWriter::as_server_call;
-};
-
-class FakeServerReader : private FakeServerReaderWriter {
- public:
-  constexpr FakeServerReader() = default;
-
-  FakeServerReader(const CallContext& context)
-      : FakeServerReaderWriter(context, MethodType::kClientStreaming) {}
-
-  FakeServerReader(FakeServerReader&&) = default;
-
-  using FakeServerReaderWriter::active;
-  using FakeServerReaderWriter::as_server_call;
-};
-
-}  // namespace pw::rpc::internal::test
diff --git a/pw_rpc/pw_rpc_private/internal_test_utils.h b/pw_rpc/pw_rpc_private/internal_test_utils.h
new file mode 100644
index 0000000..ef0b468
--- /dev/null
+++ b/pw_rpc/pw_rpc_private/internal_test_utils.h
@@ -0,0 +1,169 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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.
+
+// Internal-only testing utilities. public/pw_rpc/test_method_context.h provides
+// improved public-facing utilities for testing RPC services.
+#pragma once
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <span>
+
+#include "pw_assert/light.h"
+#include "pw_rpc/client.h"
+#include "pw_rpc/internal/channel.h"
+#include "pw_rpc/internal/method.h"
+#include "pw_rpc/internal/packet.h"
+#include "pw_rpc/internal/server.h"
+
+namespace pw::rpc {
+
+template <size_t kOutputBufferSize>
+class TestOutput : public ChannelOutput {
+ public:
+  static constexpr size_t buffer_size() { return kOutputBufferSize; }
+
+  constexpr TestOutput(const char* name = "TestOutput")
+      : ChannelOutput(name), sent_data_{} {}
+
+  std::span<std::byte> AcquireBuffer() override { return buffer_; }
+
+  Status SendAndReleaseBuffer(std::span<const std::byte> buffer) override {
+    if (buffer.empty()) {
+      return OkStatus();
+    }
+
+    PW_ASSERT(buffer.data() == buffer_.data());
+
+    packet_count_ += 1;
+    sent_data_ = buffer;
+    Result<internal::Packet> result = internal::Packet::FromBuffer(sent_data_);
+    EXPECT_EQ(OkStatus(), result.status());
+    sent_packet_ = result.value_or(internal::Packet());
+    return send_status_;
+  }
+
+  std::span<const std::byte> buffer() const { return buffer_; }
+
+  size_t packet_count() const { return packet_count_; }
+
+  void set_send_status(Status status) { send_status_ = status; }
+
+  const std::span<const std::byte>& sent_data() const { return sent_data_; }
+  const internal::Packet& sent_packet() const {
+    EXPECT_GT(packet_count_, 0u);
+    return sent_packet_;
+  }
+
+ private:
+  std::array<std::byte, buffer_size()> buffer_;
+  std::span<const std::byte> sent_data_;
+  internal::Packet sent_packet_;
+  size_t packet_count_ = 0;
+  Status send_status_;
+};
+
+// Version of the internal::Server with extra methods exposed for testing.
+class TestServer : public internal::Server {
+ public:
+  using internal::Server::writers;
+};
+
+template <typename Service,
+          size_t kOutputBufferSize = 128,
+          uint32_t kChannelId = 99,
+          uint32_t kServiceId = 16>
+class ServerContextForTest {
+ public:
+  static constexpr uint32_t channel_id() { return kChannelId; }
+  static constexpr uint32_t service_id() { return kServiceId; }
+
+  ServerContextForTest(const internal::Method& method)
+      : channel_(Channel::Create<kChannelId>(&output_)),
+        server_(std::span(&channel_, 1)),
+        service_(kServiceId),
+        context_(static_cast<internal::Server&>(server_),
+                 static_cast<internal::Channel&>(channel_),
+                 service_,
+                 method) {
+    server_.RegisterService(service_);
+  }
+
+  // Creates a response packet for this context's channel, service, and method.
+  internal::Packet packet(std::span<const std::byte> payload) const {
+    return internal::Packet(internal::PacketType::RESPONSE,
+                            kChannelId,
+                            kServiceId,
+                            context_.method().id(),
+                            payload,
+                            OkStatus());
+  }
+
+  internal::ServerCall& get() { return context_; }
+  auto& output() { return output_; }
+  TestServer& server() { return static_cast<TestServer&>(server_); }
+
+ private:
+  TestOutput<kOutputBufferSize> output_;
+  Channel channel_;
+  Server server_;
+  Service service_;
+
+  internal::ServerCall context_;
+};
+
+template <size_t kOutputBufferSize = 128,
+          size_t input_buffer_size = 128,
+          uint32_t kChannelId = 99,
+          uint32_t kServiceId = 16,
+          uint32_t kMethodId = 111>
+class ClientContextForTest {
+ public:
+  static constexpr uint32_t channel_id() { return kChannelId; }
+  static constexpr uint32_t service_id() { return kServiceId; }
+  static constexpr uint32_t method_id() { return kMethodId; }
+
+  ClientContextForTest()
+      : channel_(Channel::Create<kChannelId>(&output_)),
+        client_(std::span(&channel_, 1)) {}
+
+  const auto& output() const { return output_; }
+  Channel& channel() { return channel_; }
+  Client& client() { return client_; }
+
+  // Sends a packet to be processed by the client. Returns the client's
+  // ProcessPacket status.
+  Status SendPacket(internal::PacketType type,
+                    Status status = OkStatus(),
+                    std::span<const std::byte> payload = {}) {
+    internal::Packet packet(
+        type, kChannelId, kServiceId, kMethodId, payload, status);
+    std::byte buffer[input_buffer_size];
+    Result result = packet.Encode(buffer);
+    EXPECT_EQ(result.status(), OkStatus());
+    return client_.ProcessPacket(result.value_or(ConstByteSpan()));
+  }
+
+  Status SendResponse(Status status, std::span<const std::byte> payload) {
+    return SendPacket(internal::PacketType::RESPONSE, status, payload);
+  }
+
+ private:
+  TestOutput<kOutputBufferSize> output_;
+  Channel channel_;
+  Client client_;
+};
+
+}  // namespace pw::rpc
diff --git a/pw_rpc/pw_rpc_private/method_impl_tester.h b/pw_rpc/pw_rpc_private/method_impl_tester.h
new file mode 100644
index 0000000..8140d0b
--- /dev/null
+++ b/pw_rpc/pw_rpc_private/method_impl_tester.h
@@ -0,0 +1,90 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 <type_traits>
+
+#include "gtest/gtest.h"
+#include "pw_rpc/internal/packet.h"
+#include "pw_rpc/internal/raw_method.h"
+#include "pw_rpc/server_context.h"
+
+namespace pw::rpc::internal {
+namespace {
+
+// This class tests Method implementation classes and MethodTraits
+// specializations. It verifies that they provide the expected functions and
+// that they correctly identify and construct the various method types.
+//
+// The TestService class must inherit from Service and provide the following
+// methods with valid signatures for RPCs:
+//
+//   - Unary: a valid unary RPC member function
+//   - StaticUnary: valid unary RPC static member function
+//   - ServerStreaming: valid server streaming RPC member function
+//   - StaticServerStreaming: valid server streaming static RPC member function
+//
+// The class must also provide methods with errors as described in their names:
+//
+//   - UnaryWrongArg
+//   - StaticUnaryVoidReturn
+//   - ServerStreamingBadReturn
+//   - StaticServerStreamingMissingArg
+//
+template <typename MethodImpl, typename TestService, auto... extra_method_args>
+struct MethodImplTester {
+  // Test the MethodTraits::kType member.
+  static_assert(MethodTraits<decltype(&TestService::Unary)>::kType ==
+                MethodType::kUnary);
+  static_assert(MethodTraits<decltype(&TestService::StaticUnary)>::kType ==
+                MethodType::kUnary);
+  static_assert(MethodTraits<decltype(&TestService::ServerStreaming)>::kType ==
+                MethodType::kServerStreaming);
+  static_assert(
+      MethodTraits<decltype(&TestService::StaticServerStreaming)>::kType ==
+      MethodType::kServerStreaming);
+
+  // Test method creation.
+  static constexpr MethodImpl kUnaryMethod =
+      MethodImpl::template Unary<&TestService::Unary>(1, extra_method_args...);
+  static_assert(kUnaryMethod.id() == 1);
+
+  static constexpr MethodImpl kStaticUnaryMethod =
+      MethodImpl::template Unary<&TestService::StaticUnary>(
+          2, extra_method_args...);
+  static_assert(kStaticUnaryMethod.id() == 2);
+
+  static constexpr MethodImpl kServerStreamingMethod =
+      MethodImpl::template ServerStreaming<&TestService::ServerStreaming>(
+          3, extra_method_args...);
+  static_assert(kServerStreamingMethod.id() == 3);
+
+  static constexpr MethodImpl kStaticServerStreamingMethod =
+      MethodImpl::template ServerStreaming<&TestService::StaticServerStreaming>(
+          4, extra_method_args...);
+  static_assert(kStaticServerStreamingMethod.id() == 4);
+
+  // Test that there is an Invalid method creation function.
+  static constexpr MethodImpl kInvalidMethod = MethodImpl::Invalid();
+  static_assert(kInvalidMethod.id() == 0);
+
+  // Provide a method that tests can call to ensure this class is instantiated.
+  bool MethodImplIsValid() const {
+    return true;  // If this class compiles, the MethodImpl passes this test.
+  }
+};
+
+}  // namespace
+}  // namespace pw::rpc::internal
diff --git a/pw_rpc/pw_rpc_test_protos/test.proto b/pw_rpc/pw_rpc_test_protos/test.proto
index 20169ea..20ab3a2 100644
--- a/pw_rpc/pw_rpc_test_protos/test.proto
+++ b/pw_rpc/pw_rpc_test_protos/test.proto
@@ -22,7 +22,6 @@
 
 message TestResponse {
   int32 value = 1;
-  repeated uint32 repeated_field = 2;
 }
 
 message TestStreamResponse {
@@ -33,10 +32,6 @@
 message Empty {}
 
 service TestService {
-  rpc TestUnaryRpc(TestRequest) returns (TestResponse);
-  rpc TestAnotherUnaryRpc(TestRequest) returns (TestResponse);
-  rpc TestServerStreamRpc(TestRequest) returns (stream TestStreamResponse);
-  rpc TestClientStreamRpc(stream TestRequest) returns (TestStreamResponse);
-  rpc TestBidirectionalStreamRpc(stream TestRequest)
-      returns (stream TestStreamResponse);
+  rpc TestRpc(TestRequest) returns (TestResponse) {}
+  rpc TestStreamRpc(TestRequest) returns (stream TestStreamResponse) {}
 }
diff --git a/pw_rpc/py/BUILD.bazel b/pw_rpc/py/BUILD.bazel
deleted file mode 100644
index 4d9d454..0000000
--- a/pw_rpc/py/BUILD.bazel
+++ /dev/null
@@ -1,120 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load("@rules_python//python:defs.bzl", "py_binary")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-filegroup(
-    name = "pw_rpc_common_sources",
-    srcs = [
-        "pw_rpc/callback_client/__init__.py",
-        "pw_rpc/callback_client/call.py",
-        "pw_rpc/callback_client/errors.py",
-        "pw_rpc/callback_client/impl.py",
-        "pw_rpc/codegen.py",
-        "pw_rpc/codegen_nanopb.py",
-        "pw_rpc/codegen_raw.py",
-        "pw_rpc/console_tools/__init__.py",
-        "pw_rpc/console_tools/console.py",
-        "pw_rpc/console_tools/functions.py",
-        "pw_rpc/console_tools/watchdog.py",
-        "pw_rpc/descriptors.py",
-        "pw_rpc/ids.py",
-        "pw_rpc/packets.py",
-        "pw_rpc/plugin.py",
-        "pw_rpc/plugin_nanopb.py",
-        "pw_rpc/plugin_raw.py",
-    ],
-)
-
-py_binary(
-    name = "plugin_raw",
-    srcs = [":pw_rpc_common_sources"],
-    imports = ["."],
-    main = "pw_rpc/plugin_raw.py",
-    python_version = "PY3",
-    deps = [
-        "//pw_protobuf/py:plugin_library",
-        "//pw_protobuf_compiler/py:pw_protobuf_compiler",
-        "//pw_status/py:pw_status",
-        "@com_google_protobuf//:protobuf_python",
-    ],
-)
-
-py_binary(
-    name = "plugin_nanopb",
-    srcs = [":pw_rpc_common_sources"],
-    imports = ["."],
-    main = "pw_rpc/plugin_nanopb.py",
-    python_version = "PY3",
-    deps = [
-        "//pw_protobuf/py:plugin_library",
-        "//pw_protobuf_compiler/py:pw_protobuf_compiler",
-        "//pw_status/py:pw_status",
-        "@com_google_protobuf//:protobuf_python",
-    ],
-)
-
-py_library(
-    name = "pw_rpc",
-    srcs = [
-        "pw_rpc/__init__.py",
-        "pw_rpc/client.py",
-        ":pw_rpc_common_sources",
-    ],
-    # TODO(tonymd): Figure out the right way to handle generated protos
-    # data = [
-    #     # Copy packet_pb2.py
-    #     #   From: //pw_rpc/internal/packet_pb2.py
-    #     #   To:   //pw_rpc/py/pw_rpc/internal/packet_pb2.py
-    #     ":copy_packet_pb2",
-    # ],
-    imports = ["."],
-    deps = [
-        "//pw_protobuf/py:pw_protobuf",
-        "//pw_protobuf_compiler/py:pw_protobuf_compiler",
-        "//pw_status/py:pw_status",
-    ],
-)
-
-# TODO(tonymd): Figure out the right way to handle generated protos
-# load("@bazel_skylib//rules:copy_file.bzl", "copy_file")
-# copy_file(
-#     name = "copy_packet_pb2",
-#     src = "//pw_rpc:internal_packet_proto_pb2",
-#     out = "pw_rpc/internal/packet_pb2.py",
-#     allow_symlink = True,
-# )
-
-# TODO(tonymd): Figure out the right way to handle generated protos
-# py_test(
-#     name = "client_test",
-#     size = "small",
-#     srcs = [
-#         "tests/client_test.py",
-#     ],
-#     data = [
-#         # Copy packet_pb2.py
-#         #   From: //pw_rpc/internal/packet_pb2.py
-#         #   To:   //pw_rpc/py/pw_rpc/internal/packet_pb2.py
-#         ":copy_packet_pb2",
-#     ],
-#     deps = [
-#         ":pw_rpc",
-#         "//pw_rpc:internal_packet_proto_pb2",
-#     ],
-# )
diff --git a/pw_rpc/py/BUILD.gn b/pw_rpc/py/BUILD.gn
index c9080f8..71faaee 100644
--- a/pw_rpc/py/BUILD.gn
+++ b/pw_rpc/py/BUILD.gn
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2020 The Pigweed Authors
 #
 # 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
@@ -16,25 +16,16 @@
 
 import("$dir_pw_build/python.gni")
 import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_rpc/internal/integration_test_ports.gni")
 
 pw_python_package("py") {
   generate_setup = {
-    metadata = {
-      name = "pw_rpc"
-      version = "0.0.1"
-    }
-    options = {
-      install_requires = [ "protobuf" ]
-    }
+    name = "pw_rpc"
+    version = "0.0.1"
   }
 
   sources = [
     "pw_rpc/__init__.py",
-    "pw_rpc/callback_client/__init__.py",
-    "pw_rpc/callback_client/call.py",
-    "pw_rpc/callback_client/errors.py",
-    "pw_rpc/callback_client/impl.py",
+    "pw_rpc/callback_client.py",
     "pw_rpc/client.py",
     "pw_rpc/codegen.py",
     "pw_rpc/codegen_nanopb.py",
@@ -49,14 +40,12 @@
     "pw_rpc/plugin.py",
     "pw_rpc/plugin_nanopb.py",
     "pw_rpc/plugin_raw.py",
-    "pw_rpc/testing.py",
   ]
   tests = [
     "tests/callback_client_test.py",
     "tests/client_test.py",
     "tests/console_tools/console_tools_test.py",
     "tests/console_tools/functions_test.py",
-    "tests/console_tools/watchdog_test.py",
     "tests/descriptors_test.py",
     "tests/ids_test.py",
     "tests/packets_test.py",
@@ -75,23 +64,3 @@
   sources = [ "docs.rst" ]
   other_deps = [ ":py" ]
 }
-
-pw_python_script("python_client_cpp_server_test") {
-  sources = [ "tests/python_client_cpp_server_test.py" ]
-  python_deps = [
-    ":py",
-    "$dir_pw_hdlc/py",
-    "$dir_pw_status/py",
-  ]
-  pylintrc = "$dir_pigweed/.pylintrc"
-
-  action = {
-    args = [
-      "--port=$pw_rpc_PYTHON_CLIENT_CPP_SERVER_TEST_PORT",
-      "--test-server-command",
-      "<TARGET_FILE(..:test_rpc_server)>",
-    ]
-    deps = [ "..:test_rpc_server" ]
-    stamp = true
-  }
-}
diff --git a/pw_rpc/py/docs.rst b/pw_rpc/py/docs.rst
index 2c0e306..5aeea46 100644
--- a/pw_rpc/py/docs.rst
+++ b/pw_rpc/py/docs.rst
@@ -14,29 +14,10 @@
 
 pw_rpc.callback_client
 ======================
-.. automodule:: pw_rpc.callback_client
+.. autoclass:: pw_rpc.callback_client.Impl
   :members:
-    UnaryResponse,
-    StreamResponse,
-    UnaryCall,
-    ServerStreamingCall,
-    ClientStreamingCall,
-    BidirectionalStreamingCall,
-
-pw_rpc.descriptors
-==================
-.. automodule:: pw_rpc.descriptors
-  :members:
-    Channel,
-    ChannelManipulator,
 
 pw_rpc.console_tools
 ====================
 .. automodule:: pw_rpc.console_tools
-  :members:
-     ClientInfo,
-     Context,
-     Watchdog,
-     alias_deprecated_command,
-     flattened_rpc_completions,
-     help_as_repr,
+  :members: Context, ClientInfo, Watchdog, help_as_repr
diff --git a/pw_rpc/py/pw_rpc/__init__.py b/pw_rpc/py/pw_rpc/__init__.py
index ff1f871..1f1e72e 100644
--- a/pw_rpc/py/pw_rpc/__init__.py
+++ b/pw_rpc/py/pw_rpc/__init__.py
@@ -14,4 +14,4 @@
 """Package for calling Pigweed RPCs from Python."""
 
 from pw_rpc.client import Client
-from pw_rpc.descriptors import Channel, ChannelManipulator
+from pw_rpc.descriptors import Channel
diff --git a/pw_rpc/py/pw_rpc/callback_client.py b/pw_rpc/py/pw_rpc/callback_client.py
new file mode 100644
index 0000000..ea7fad8
--- /dev/null
+++ b/pw_rpc/py/pw_rpc/callback_client.py
@@ -0,0 +1,511 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+"""Defines a callback-based RPC ClientImpl to use with pw_rpc.Client.
+
+callback_client.Impl supports invoking RPCs synchronously or asynchronously.
+Asynchronous invocations use a callback.
+
+Synchronous invocations look like a function call:
+
+  status, response = client.channel(1).call.MyServer.MyUnary(some_field=123)
+
+  # Streaming calls return an iterable of responses
+  for reply in client.channel(1).call.MyService.MyServerStreaming(request):
+      pass
+
+Asynchronous invocations pass a callback in addition to the request. The
+callback must be a callable that accepts a status and a payload, either of
+which may be None. The Status is only set when the RPC is completed.
+
+  callback = lambda status, payload: print('Response:', status, payload)
+
+  call = client.channel(1).call.MyServer.MyUnary.invoke(
+      callback, some_field=123)
+
+  call = client.channel(1).call.MyService.MyServerStreaming.invoke(
+      callback, request):
+
+When invoking a method, requests may be provided as a message object or as
+kwargs for the message fields (but not both).
+"""
+
+import enum
+import inspect
+import logging
+import queue
+import textwrap
+import threading
+from typing import Any, Callable, Iterator, NamedTuple, Union, Optional
+
+from pw_protobuf_compiler.python_protos import proto_repr
+from pw_status import Status
+
+from pw_rpc import client, descriptors
+from pw_rpc.client import PendingRpc, PendingRpcs
+from pw_rpc.descriptors import Channel, Method, Service
+
+_LOG = logging.getLogger(__name__)
+
+
+class UseDefault(enum.Enum):
+    """Marker for args that should use a default value, when None is valid."""
+    VALUE = 0
+
+
+OptionalTimeout = Union[UseDefault, float, None]
+
+ResponseCallback = Callable[[PendingRpc, Any], Any]
+CompletionCallback = Callable[[PendingRpc, Status], Any]
+ErrorCallback = Callable[[PendingRpc, Status], Any]
+
+
+class _Callbacks(NamedTuple):
+    response: ResponseCallback
+    completion: CompletionCallback
+    error: ErrorCallback
+
+
+def _default_response(rpc: PendingRpc, response: Any) -> None:
+    _LOG.info('%s response: %s', rpc, response)
+
+
+def _default_completion(rpc: PendingRpc, status: Status) -> None:
+    _LOG.info('%s finished: %s', rpc, status)
+
+
+def _default_error(rpc: PendingRpc, status: Status) -> None:
+    _LOG.error('%s error: %s', rpc, status)
+
+
+class _MethodClient:
+    """A method that can be invoked for a particular channel."""
+    def __init__(self, client_impl: 'Impl', rpcs: PendingRpcs,
+                 channel: Channel, method: Method,
+                 default_timeout_s: Optional[float]):
+        self._impl = client_impl
+        self._rpcs = rpcs
+        self._rpc = PendingRpc(channel, method.service, method)
+        self.default_timeout_s: Optional[float] = default_timeout_s
+
+    @property
+    def channel(self) -> Channel:
+        return self._rpc.channel
+
+    @property
+    def method(self) -> Method:
+        return self._rpc.method
+
+    @property
+    def service(self) -> Service:
+        return self._rpc.service
+
+    def invoke(self,
+               request: Any,
+               response: ResponseCallback = _default_response,
+               completion: CompletionCallback = _default_completion,
+               error: ErrorCallback = _default_error,
+               *,
+               override_pending: bool = True,
+               keep_open: bool = False) -> '_AsyncCall':
+        """Invokes an RPC with callbacks."""
+        self._rpcs.send_request(self._rpc,
+                                request,
+                                _Callbacks(response, completion, error),
+                                override_pending=override_pending,
+                                keep_open=keep_open)
+        return _AsyncCall(self._rpcs, self._rpc)
+
+    def __repr__(self) -> str:
+        return self.help()
+
+    def __call__(self):
+        raise NotImplementedError('Implemented by derived classes')
+
+    def help(self) -> str:
+        """Returns a help message about this RPC."""
+        function_call = self.method.full_name + '('
+
+        docstring = inspect.getdoc(self.__call__)
+        assert docstring is not None
+
+        annotation = inspect.Signature.from_callable(self).return_annotation
+        if isinstance(annotation, type):
+            annotation = annotation.__name__
+
+        arg_sep = f',\n{" " * len(function_call)}'
+        return (
+            f'{function_call}'
+            f'{arg_sep.join(descriptors.field_help(self.method.request_type))})'
+            f'\n\n{textwrap.indent(docstring, "  ")}\n\n'
+            f'  Returns {annotation}.')
+
+
+class RpcTimeout(Exception):
+    def __init__(self, rpc: PendingRpc, timeout: Optional[float]):
+        super().__init__(
+            f'No response received for {rpc.method} after {timeout} s')
+        self.rpc = rpc
+        self.timeout = timeout
+
+
+class RpcError(Exception):
+    def __init__(self, rpc: PendingRpc, status: Status):
+        if status is Status.NOT_FOUND:
+            msg = ': the RPC server does not support this RPC'
+        else:
+            msg = ''
+
+        super().__init__(f'{rpc.method} failed with error {status}{msg}')
+        self.rpc = rpc
+        self.status = status
+
+
+class _AsyncCall:
+    """Represents an ongoing callback-based call."""
+
+    # TODO(hepler): Consider alternatives (futures) and/or expand functionality.
+
+    def __init__(self, rpcs: PendingRpcs, rpc: PendingRpc):
+        self._rpc = rpc
+        self._rpcs = rpcs
+
+    def cancel(self) -> bool:
+        return self._rpcs.send_cancel(self._rpc)
+
+    def __enter__(self) -> '_AsyncCall':
+        return self
+
+    def __exit__(self, exc_type, exc_value, traceback) -> None:
+        self.cancel()
+
+
+class StreamingResponses:
+    """Used to iterate over a queue.SimpleQueue."""
+    def __init__(self, method_client: _MethodClient,
+                 responses: queue.SimpleQueue,
+                 default_timeout_s: OptionalTimeout):
+        self._method_client = method_client
+        self._queue = responses
+        self.status: Optional[Status] = None
+
+        if default_timeout_s is UseDefault.VALUE:
+            self.default_timeout_s = self._method_client.default_timeout_s
+        else:
+            self.default_timeout_s = default_timeout_s
+
+    @property
+    def method(self) -> Method:
+        return self._method_client.method
+
+    def cancel(self) -> None:
+        self._method_client._rpcs.send_cancel(self._method_client._rpc)  # pylint: disable=protected-access
+
+    def responses(self,
+                  *,
+                  block: bool = True,
+                  timeout_s: OptionalTimeout = UseDefault.VALUE) -> Iterator:
+        """Returns an iterator of stream responses.
+
+        Args:
+          timeout_s: timeout in seconds; None blocks indefinitely
+        """
+        if timeout_s is UseDefault.VALUE:
+            timeout_s = self.default_timeout_s
+
+        try:
+            while True:
+                response = self._queue.get(block, timeout_s)
+
+                if isinstance(response, Exception):
+                    raise response
+
+                if isinstance(response, Status):
+                    self.status = response
+                    return
+
+                yield response
+        except queue.Empty:
+            self.cancel()
+            raise RpcTimeout(self._method_client._rpc, timeout_s)  # pylint: disable=protected-access
+        except:
+            self.cancel()
+            raise
+
+    def __iter__(self):
+        return self.responses()
+
+    def __repr__(self) -> str:
+        return f'{type(self).__name__}({self.method})'
+
+
+def _method_client_docstring(method: Method) -> str:
+    return f'''\
+Class that invokes the {method.full_name} {method.type.sentence_name()} RPC.
+
+Calling this directly invokes the RPC synchronously. The RPC can be invoked
+asynchronously using the invoke method.
+'''
+
+
+def _function_docstring(method: Method) -> str:
+    return f'''\
+Invokes the {method.full_name} {method.type.sentence_name()} RPC.
+
+This function accepts either the request protobuf fields as keyword arguments or
+a request protobuf as a positional argument.
+'''
+
+
+def _update_function_signature(method: Method, function: Callable) -> None:
+    """Updates the name, docstring, and parameters to match a method."""
+    function.__name__ = method.full_name
+    function.__doc__ = _function_docstring(method)
+
+    # In order to have good tab completion and help messages, update the
+    # function signature to accept only keyword arguments for the proto message
+    # fields. This doesn't actually change the function signature -- it just
+    # updates how it appears when inspected.
+    sig = inspect.signature(function)
+
+    params = [next(iter(sig.parameters.values()))]  # Get the "self" parameter
+    params += method.request_parameters()
+    params.append(
+        inspect.Parameter('pw_rpc_timeout_s', inspect.Parameter.KEYWORD_ONLY))
+    function.__signature__ = sig.replace(  # type: ignore[attr-defined]
+        parameters=params)
+
+
+class UnaryResponse(NamedTuple):
+    """Result of invoking a unary RPC: status and response."""
+    status: Status
+    response: Any
+
+    def __repr__(self) -> str:
+        return f'({self.status}, {proto_repr(self.response)})'
+
+
+class _UnaryResponseHandler:
+    """Tracks the state of an ongoing synchronous unary RPC call."""
+    def __init__(self, rpc: PendingRpc):
+        self._rpc = rpc
+        self._response: Any = None
+        self._status: Optional[Status] = None
+        self._error: Optional[RpcError] = None
+        self._event = threading.Event()
+
+    def on_response(self, _: PendingRpc, response: Any) -> None:
+        self._response = response
+
+    def on_completion(self, _: PendingRpc, status: Status) -> None:
+        self._status = status
+        self._event.set()
+
+    def on_error(self, _: PendingRpc, status: Status) -> None:
+        self._error = RpcError(self._rpc, status)
+        self._event.set()
+
+    def wait(self, timeout_s: Optional[float]) -> UnaryResponse:
+        if not self._event.wait(timeout_s):
+            raise RpcTimeout(self._rpc, timeout_s)
+
+        if self._error is not None:
+            raise self._error
+
+        assert self._status is not None
+        return UnaryResponse(self._status, self._response)
+
+
+def _unary_method_client(client_impl: 'Impl', rpcs: PendingRpcs,
+                         channel: Channel, method: Method,
+                         default_timeout: Optional[float]) -> _MethodClient:
+    """Creates an object used to call a unary method."""
+    def call(self: _MethodClient,
+             _rpc_request_proto=None,
+             *,
+             pw_rpc_timeout_s=UseDefault.VALUE,
+             **request_fields) -> UnaryResponse:
+
+        handler = _UnaryResponseHandler(self._rpc)  # pylint: disable=protected-access
+        self.invoke(
+            self.method.get_request(_rpc_request_proto, request_fields),
+            handler.on_response, handler.on_completion, handler.on_error)
+
+        if pw_rpc_timeout_s is UseDefault.VALUE:
+            pw_rpc_timeout_s = self.default_timeout_s
+
+        return handler.wait(pw_rpc_timeout_s)
+
+    _update_function_signature(method, call)
+
+    # The MethodClient class is created dynamically so that the __call__ method
+    # can be configured differently for each method.
+    method_client_type = type(
+        f'{method.name}_UnaryMethodClient', (_MethodClient, ),
+        dict(__call__=call, __doc__=_method_client_docstring(method)))
+    return method_client_type(client_impl, rpcs, channel, method,
+                              default_timeout)
+
+
+def _server_streaming_method_client(client_impl: 'Impl', rpcs: PendingRpcs,
+                                    channel: Channel, method: Method,
+                                    default_timeout: Optional[float]):
+    """Creates an object used to call a server streaming method."""
+    def call(self: _MethodClient,
+             _rpc_request_proto=None,
+             *,
+             pw_rpc_timeout_s=UseDefault.VALUE,
+             **request_fields) -> StreamingResponses:
+        responses: queue.SimpleQueue = queue.SimpleQueue()
+        self.invoke(
+            self.method.get_request(_rpc_request_proto, request_fields),
+            lambda _, response: responses.put(response),
+            lambda _, status: responses.put(status),
+            lambda rpc, status: responses.put(RpcError(rpc, status)))
+        return StreamingResponses(self, responses, pw_rpc_timeout_s)
+
+    _update_function_signature(method, call)
+
+    # The MethodClient class is created dynamically so that the __call__ method
+    # can be configured differently for each method type.
+    method_client_type = type(
+        f'{method.name}_ServerStreamingMethodClient', (_MethodClient, ),
+        dict(__call__=call, __doc__=_method_client_docstring(method)))
+    return method_client_type(client_impl, rpcs, channel, method,
+                              default_timeout)
+
+
+class ClientStreamingMethodClient(_MethodClient):
+    def __call__(self):
+        raise NotImplementedError
+
+    def invoke(self,
+               request: Any,
+               response: ResponseCallback = _default_response,
+               completion: CompletionCallback = _default_completion,
+               error: ErrorCallback = _default_error,
+               *,
+               override_pending: bool = True,
+               keep_open: bool = False) -> _AsyncCall:
+        raise NotImplementedError
+
+
+class BidirectionalStreamingMethodClient(_MethodClient):
+    def __call__(self):
+        raise NotImplementedError
+
+    def invoke(self,
+               request: Any,
+               response: ResponseCallback = _default_response,
+               completion: CompletionCallback = _default_completion,
+               error: ErrorCallback = _default_error,
+               *,
+               override_pending: bool = True,
+               keep_open: bool = False) -> _AsyncCall:
+        raise NotImplementedError
+
+
+class Impl(client.ClientImpl):
+    """Callback-based ClientImpl."""
+    def __init__(self,
+                 default_unary_timeout_s: Optional[float] = 1.0,
+                 default_stream_timeout_s: Optional[float] = 1.0):
+        super().__init__()
+        self._default_unary_timeout_s = default_unary_timeout_s
+        self._default_stream_timeout_s = default_stream_timeout_s
+
+    @property
+    def default_unary_timeout_s(self) -> Optional[float]:
+        return self._default_unary_timeout_s
+
+    @property
+    def default_stream_timeout_s(self) -> Optional[float]:
+        return self._default_stream_timeout_s
+
+    def method_client(self, channel: Channel, method: Method) -> _MethodClient:
+        """Returns an object that invokes a method using the given chanel."""
+
+        if method.type is Method.Type.UNARY:
+            return _unary_method_client(self, self.rpcs, channel, method,
+                                        self.default_unary_timeout_s)
+
+        if method.type is Method.Type.SERVER_STREAMING:
+            return _server_streaming_method_client(
+                self, self.rpcs, channel, method,
+                self.default_stream_timeout_s)
+
+        if method.type is Method.Type.CLIENT_STREAMING:
+            return ClientStreamingMethodClient(self, self.rpcs, channel,
+                                               method,
+                                               self.default_unary_timeout_s)
+
+        if method.type is Method.Type.BIDIRECTIONAL_STREAMING:
+            return BidirectionalStreamingMethodClient(
+                self, self.rpcs, channel, method,
+                self.default_stream_timeout_s)
+
+        raise AssertionError(f'Unknown method type {method.type}')
+
+    def handle_response(self,
+                        rpc: PendingRpc,
+                        context,
+                        payload,
+                        *,
+                        args: tuple = (),
+                        kwargs: dict = None) -> None:
+        """Invokes the callback associated with this RPC.
+
+        Any additional positional and keyword args passed through
+        Client.process_packet are forwarded to the callback.
+        """
+        if kwargs is None:
+            kwargs = {}
+
+        try:
+            context.response(rpc, payload, *args, **kwargs)
+        except:  # pylint: disable=bare-except
+            self.rpcs.send_cancel(rpc)
+            _LOG.exception('Response callback %s for %s raised exception',
+                           context.response, rpc)
+
+    def handle_completion(self,
+                          rpc: PendingRpc,
+                          context,
+                          status: Status,
+                          *,
+                          args: tuple = (),
+                          kwargs: dict = None):
+        if kwargs is None:
+            kwargs = {}
+
+        try:
+            context.completion(rpc, status, *args, **kwargs)
+        except:  # pylint: disable=bare-except
+            _LOG.exception('Completion callback %s for %s raised exception',
+                           context.completion, rpc)
+
+    def handle_error(self,
+                     rpc: PendingRpc,
+                     context,
+                     status: Status,
+                     *,
+                     args: tuple = (),
+                     kwargs: dict = None) -> None:
+        if kwargs is None:
+            kwargs = {}
+
+        try:
+            context.error(rpc, status, *args, **kwargs)
+        except:  # pylint: disable=bare-except
+            _LOG.exception('Error callback %s for %s raised exception',
+                           context.error, rpc)
diff --git a/pw_rpc/py/pw_rpc/callback_client/__init__.py b/pw_rpc/py/pw_rpc/callback_client/__init__.py
deleted file mode 100644
index 1527d63..0000000
--- a/pw_rpc/py/pw_rpc/callback_client/__init__.py
+++ /dev/null
@@ -1,103 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Defines a callback-based RPC ClientImpl to use with pw_rpc.Client.
-
-callback_client.Impl supports invoking RPCs synchronously or asynchronously.
-Asynchronous invocations use a callback.
-
-Synchronous invocations look like a function call. When invoking a unary or
-server streaming RPC, the request may be provided as a message object or as
-keyword arguments for the message fields (but not both).
-
-.. code-block:: python
-
-  status, response = client.channel(1).rpcs.MyServer.MyUnary(some_field=123)
-
-  # Calls with a server stream return a status and a list of responses.
-  status, responses = rpcs.MyService.MyServerStreaming(Request(some_field=123))
-
-Synchronous client and bidirectional streaming calls accept an iterable of
-requests to send.
-
-.. code-block:: python
-
-  requests = [Request(a=1), Request(b=2)]
-  status, response = rpcs.MyService.MyClientStreaming(requests)
-
-  requests = [Request(a=1), Request(b=2)]
-  status, responses = rpcs.MyService.MyBidirectionalStreaming(requests)
-
-Synchronous invocations block until the RPC completes or times out. The calls
-use the default timeout provided when the callback_client.Impl() is created, or
-a timeout passed in through the pw_rpc_timeout_s argument. A timeout of None
-means to wait indefinitely for a response.
-
-Asynchronous invocations immediately return a call object. Callbacks may be
-provided for the three RPC events:
-
-  * on_next(call_object, response) - called for each response
-  * on_completed(call_object, status) - called when the RPC completes
-  * on_error(call_object, error) - called if the RPC terminates due to an error
-
-The default callbacks simply log the events. If a user-provided callback throws
-an exception, that exception is logged and raised when the user calls functions
-on the call object.
-
-Unary and client streaming RPCs are invoked asynchronously by calling invoke on
-the method object. invoke takes the callbacks. The request may be provided
-either as a constructed protobuf or as a dict of proto fields in the
-request_args parameter.
-
-.. code-block:: python
-
-  # Pass the request as a protobuf and provide callbacks for all RPC events.
-  rpc = client.channel(1).call.MyService.MyServerStreaming
-  call = rpc.invoke(rpc.request(a=1), on_next_cb, on_completed_cb, on_error_cb)
-
-  # Create the request from the provided keyword args. Provide a callback for
-  # responses, but simply log the other RPC events.
-  call = client.channel(1).call.MyServer.MyUnary.invoke(
-      request_args=dict(a=1, b=2), on_next=lambda _, reply: process_this(reply))
-
-For client and bidirectional streaming RPCs, requests are sent with the send
-method. The finish_and_wait method finishes the client stream. It optionally
-takes an iterable for responses to send before closing the stream.
-
-.. code-block:: python
-
-  # Start the call using callbacks.
-  call = client.channel(1).rpcs.MyServer.MyClientStream.invoke(on_error=err_cb)
-
-  # Send a single client stream request.
-  call.send(some_field=123)
-
-  # Send the requests, close the stream, then wait for the RPC to complete.
-  stream_responses = call.finish_and_wait([RequestType(some_field=123), ...])
-"""
-
-from pw_rpc.callback_client.call import (
-    OptionalTimeout,
-    UseDefault,
-    UnaryResponse,
-    StreamResponse,
-    UnaryCall,
-    ServerStreamingCall,
-    ClientStreamingCall,
-    BidirectionalStreamingCall,
-    OnNextCallback,
-    OnCompletedCallback,
-    OnErrorCallback,
-)
-from pw_rpc.callback_client.errors import RpcError, RpcTimeout
-from pw_rpc.callback_client.impl import Impl
diff --git a/pw_rpc/py/pw_rpc/callback_client/call.py b/pw_rpc/py/pw_rpc/callback_client/call.py
deleted file mode 100644
index abe1083..0000000
--- a/pw_rpc/py/pw_rpc/callback_client/call.py
+++ /dev/null
@@ -1,340 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Classes for handling ongoing RPC calls."""
-
-import enum
-import logging
-import math
-import queue
-from typing import (Any, Callable, Iterable, Iterator, NamedTuple, Union,
-                    Optional, Sequence, TypeVar)
-
-from pw_protobuf_compiler.python_protos import proto_repr
-from pw_status import Status
-from google.protobuf.message import Message
-
-from pw_rpc.callback_client.errors import RpcTimeout, RpcError
-from pw_rpc.client import PendingRpc, PendingRpcs
-from pw_rpc.descriptors import Method
-
-_LOG = logging.getLogger(__package__)
-
-
-class UseDefault(enum.Enum):
-    """Marker for args that should use a default value, when None is valid."""
-    VALUE = 0
-
-
-CallType = TypeVar('CallType', 'UnaryCall', 'ServerStreamingCall',
-                   'ClientStreamingCall', 'BidirectionalStreamingCall')
-
-OnNextCallback = Callable[[CallType, Any], Any]
-OnCompletedCallback = Callable[[CallType, Any], Any]
-OnErrorCallback = Callable[[CallType, Any], Any]
-
-OptionalTimeout = Union[UseDefault, float, None]
-
-
-class UnaryResponse(NamedTuple):
-    """Result from a unary or client streaming RPC: status and response."""
-    status: Status
-    response: Any
-
-    def __repr__(self) -> str:
-        return f'({self.status}, {proto_repr(self.response)})'
-
-
-class StreamResponse(NamedTuple):
-    """Results from a server or bidirectional streaming RPC."""
-    status: Status
-    responses: Sequence[Any]
-
-    def __repr__(self) -> str:
-        return (f'({self.status}, '
-                f'[{", ".join(proto_repr(r) for r in self.responses)}])')
-
-
-class Call:
-    """Represents an in-progress or completed RPC call."""
-    def __init__(self, rpcs: PendingRpcs, rpc: PendingRpc,
-                 default_timeout_s: Optional[float],
-                 on_next: Optional[OnNextCallback],
-                 on_completed: Optional[OnCompletedCallback],
-                 on_error: Optional[OnErrorCallback]) -> None:
-        self._rpcs = rpcs
-        self._rpc = rpc
-        self.default_timeout_s = default_timeout_s
-
-        self.status: Optional[Status] = None
-        self.error: Optional[Status] = None
-        self._callback_exception: Optional[Exception] = None
-        self._responses: list = []
-        self._response_queue: queue.SimpleQueue = queue.SimpleQueue()
-
-        self.on_next = on_next or Call._default_response
-        self.on_completed = on_completed or Call._default_completion
-        self.on_error = on_error or Call._default_error
-
-    def _invoke(self, request: Optional[Message], ignore_errors: bool) -> None:
-        """Calls the RPC. This must be called immediately after __init__."""
-        previous = self._rpcs.send_request(self._rpc,
-                                           request,
-                                           self,
-                                           ignore_errors=ignore_errors,
-                                           override_pending=True)
-
-        # TODO(hepler): Remove the cancel_duplicate_calls option.
-        if (self._rpcs.cancel_duplicate_calls and  # type: ignore[attr-defined]
-                previous is not None and not previous.completed()):
-            previous._handle_error(Status.CANCELLED)  # pylint: disable=protected-access
-
-    def _default_response(self, response: Message) -> None:
-        _LOG.debug('%s received response: %s', self._rpc, response)
-
-    def _default_completion(self, status: Status) -> None:
-        _LOG.info('%s completed: %s', self._rpc, status)
-
-    def _default_error(self, error: Status) -> None:
-        _LOG.warning('%s terminated due to an error: %s', self._rpc, error)
-
-    @property
-    def method(self) -> Method:
-        return self._rpc.method
-
-    def completed(self) -> bool:
-        """True if the RPC call has completed, successfully or from an error."""
-        return self.status is not None or self.error is not None
-
-    def _send_client_stream(self, request_proto: Optional[Message],
-                            request_fields: dict) -> None:
-        """Sends a client to the server in the client stream.
-
-        Sending a client stream packet on a closed RPC raises an exception.
-        """
-        self._check_errors()
-
-        if self.status is not None:
-            raise RpcError(self._rpc, Status.FAILED_PRECONDITION)
-
-        self._rpcs.send_client_stream(
-            self._rpc, self.method.get_request(request_proto, request_fields))
-
-    def _finish_client_stream(self, requests: Iterable[Message]) -> None:
-        for request in requests:
-            self._send_client_stream(request, {})
-
-        if not self.completed():
-            self._rpcs.send_client_stream_end(self._rpc)
-
-    def _unary_wait(self, timeout_s: OptionalTimeout) -> UnaryResponse:
-        """Waits until the RPC has completed."""
-        for _ in self._get_responses(timeout_s=timeout_s):
-            pass
-
-        assert self.status is not None and self._responses
-        return UnaryResponse(self.status, self._responses[-1])
-
-    def _stream_wait(self, timeout_s: OptionalTimeout) -> StreamResponse:
-        """Waits until the RPC has completed."""
-        for _ in self._get_responses(timeout_s=timeout_s):
-            pass
-
-        assert self.status is not None
-        return StreamResponse(self.status, self._responses)
-
-    def _get_responses(self,
-                       *,
-                       count: int = None,
-                       timeout_s: OptionalTimeout) -> Iterator:
-        """Returns an iterator of stream responses.
-
-        Args:
-          count: Responses to read before returning; None reads all
-          timeout_s: max time in seconds to wait between responses; 0 doesn't
-              block, None blocks indefinitely
-        """
-        self._check_errors()
-
-        if self.completed() and self._response_queue.empty():
-            return
-
-        if timeout_s is UseDefault.VALUE:
-            timeout_s = self.default_timeout_s
-
-        remaining = math.inf if count is None else count
-
-        try:
-            while remaining:
-                response = self._response_queue.get(True, timeout_s)
-
-                self._check_errors()
-
-                if response is None:
-                    return
-
-                yield response
-                remaining -= 1
-        except queue.Empty:
-            raise RpcTimeout(self._rpc, timeout_s)
-
-    def cancel(self) -> bool:
-        """Cancels the RPC; returns whether the RPC was active."""
-        if self.completed():
-            return False
-
-        self.error = Status.CANCELLED
-        return self._rpcs.send_cancel(self._rpc)
-
-    def _check_errors(self) -> None:
-        if self._callback_exception:
-            raise self._callback_exception
-
-        if self.error:
-            raise RpcError(self._rpc, self.error)
-
-    def _handle_response(self, response: Any) -> None:
-        # TODO(frolv): These lists could grow very large for persistent
-        # streaming RPCs such as logs. The size should be limited.
-        self._responses.append(response)
-        self._response_queue.put(response)
-
-        self._invoke_callback('on_next', response)
-
-    def _handle_completion(self, status: Status) -> None:
-        self.status = status
-        self._response_queue.put(None)
-
-        self._invoke_callback('on_completed', status)
-
-    def _handle_error(self, error: Status) -> None:
-        self.error = error
-        self._response_queue.put(None)
-
-        self._invoke_callback('on_error', error)
-
-    def _invoke_callback(self, callback_name: str, arg: Any) -> None:
-        """Invokes a user-provided callback function for an RPC event."""
-
-        # Catch and log any exceptions from the user-provided callback so that
-        # exceptions don't terminate the thread handling RPC packets.
-        callback: Callable[[Call, Any], None] = getattr(self, callback_name)
-
-        try:
-            callback(self, arg)
-        except Exception as callback_exception:  # pylint: disable=broad-except
-            msg = (f'The {callback_name} callback ({callback}) for '
-                   f'{self._rpc} raised an exception')
-            _LOG.exception(msg)
-
-            self._callback_exception = RuntimeError(msg)
-            self._callback_exception.__cause__ = callback_exception
-
-    def __enter__(self) -> 'Call':
-        return self
-
-    def __exit__(self, exc_type, exc_value, traceback) -> None:
-        self.cancel()
-
-    def __repr__(self) -> str:
-        return f'{type(self).__name__}({self.method})'
-
-
-class UnaryCall(Call):
-    """Tracks the state of a unary RPC call."""
-    @property
-    def response(self) -> Any:
-        return self._responses[-1] if self._responses else None
-
-    def wait(self,
-             timeout_s: OptionalTimeout = UseDefault.VALUE) -> UnaryResponse:
-        return self._unary_wait(timeout_s)
-
-
-class ServerStreamingCall(Call):
-    """Tracks the state of a server streaming RPC call."""
-    @property
-    def responses(self) -> Sequence:
-        return self._responses
-
-    def wait(self,
-             timeout_s: OptionalTimeout = UseDefault.VALUE) -> StreamResponse:
-        return self._stream_wait(timeout_s)
-
-    def get_responses(
-            self,
-            *,
-            count: int = None,
-            timeout_s: OptionalTimeout = UseDefault.VALUE) -> Iterator:
-        return self._get_responses(count=count, timeout_s=timeout_s)
-
-    def __iter__(self) -> Iterator:
-        return self.get_responses()
-
-
-class ClientStreamingCall(Call):
-    """Tracks the state of a client streaming RPC call."""
-    @property
-    def response(self) -> Any:
-        return self._responses[-1] if self._responses else None
-
-    # TODO(hepler): Use / to mark the first arg as positional-only
-    #     when when Python 3.7 support is no longer required.
-    def send(self,
-             _rpc_request_proto: Message = None,
-             **request_fields) -> None:
-        """Sends client stream request to the server."""
-        self._send_client_stream(_rpc_request_proto, request_fields)
-
-    def finish_and_wait(
-            self,
-            requests: Iterable[Message] = (),
-            *,
-            timeout_s: OptionalTimeout = UseDefault.VALUE) -> UnaryResponse:
-        """Ends the client stream and waits for the RPC to complete."""
-        self._finish_client_stream(requests)
-        return self._unary_wait(timeout_s)
-
-
-class BidirectionalStreamingCall(Call):
-    """Tracks the state of a bidirectional streaming RPC call."""
-    @property
-    def responses(self) -> Sequence:
-        return self._responses
-
-    # TODO(hepler): Use / to mark the first arg as positional-only
-    #     when when Python 3.7 support is no longer required.
-    def send(self,
-             _rpc_request_proto: Message = None,
-             **request_fields) -> None:
-        """Sends a message to the server in the client stream."""
-        self._send_client_stream(_rpc_request_proto, request_fields)
-
-    def finish_and_wait(
-            self,
-            requests: Iterable[Message] = (),
-            *,
-            timeout_s: OptionalTimeout = UseDefault.VALUE) -> StreamResponse:
-        """Ends the client stream and waits for the RPC to complete."""
-        self._finish_client_stream(requests)
-        return self._stream_wait(timeout_s)
-
-    def get_responses(
-            self,
-            *,
-            count: int = None,
-            timeout_s: OptionalTimeout = UseDefault.VALUE) -> Iterator:
-        return self._get_responses(count=count, timeout_s=timeout_s)
-
-    def __iter__(self) -> Iterator:
-        return self.get_responses()
diff --git a/pw_rpc/py/pw_rpc/callback_client/errors.py b/pw_rpc/py/pw_rpc/callback_client/errors.py
deleted file mode 100644
index 0e898e6..0000000
--- a/pw_rpc/py/pw_rpc/callback_client/errors.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Exceptions for RPC-related errors."""
-
-from typing import Optional
-
-from pw_status import Status
-
-from pw_rpc.client import PendingRpc
-
-
-class RpcTimeout(Exception):
-    def __init__(self, rpc: PendingRpc, timeout: Optional[float]):
-        super().__init__(
-            f'No response received for {rpc.method} after {timeout} s')
-        self.rpc = rpc
-        self.timeout = timeout
-
-
-class RpcError(Exception):
-    def __init__(self, rpc: PendingRpc, status: Status):
-        """Raised when there is an RPC-layer error."""
-        if status is Status.NOT_FOUND:
-            msg = ': the RPC server does not support this RPC'
-        elif status is Status.DATA_LOSS:
-            msg = ': an error occurred while decoding the RPC payload'
-        else:
-            msg = ''
-
-        super().__init__(f'{rpc.method} failed with error {status}{msg}')
-        self.rpc = rpc
-        self.status = status
diff --git a/pw_rpc/py/pw_rpc/callback_client/impl.py b/pw_rpc/py/pw_rpc/callback_client/impl.py
deleted file mode 100644
index 59a185e..0000000
--- a/pw_rpc/py/pw_rpc/callback_client/impl.py
+++ /dev/null
@@ -1,447 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""The callback-based pw_rpc client implementation."""
-
-import inspect
-import logging
-import textwrap
-from typing import Any, Callable, Dict, Iterable, Optional, Type
-
-from pw_status import Status
-from google.protobuf.message import Message
-
-from pw_rpc import client, descriptors
-from pw_rpc.client import PendingRpc, PendingRpcs
-from pw_rpc.descriptors import Channel, Method, Service
-
-from pw_rpc.callback_client.call import (
-    UseDefault,
-    OptionalTimeout,
-    CallType,
-    UnaryResponse,
-    StreamResponse,
-    Call,
-    UnaryCall,
-    ServerStreamingCall,
-    ClientStreamingCall,
-    BidirectionalStreamingCall,
-    OnNextCallback,
-    OnCompletedCallback,
-    OnErrorCallback,
-)
-
-_LOG = logging.getLogger(__package__)
-
-
-class _MethodClient:
-    """A method that can be invoked for a particular channel."""
-    def __init__(self, client_impl: 'Impl', rpcs: PendingRpcs,
-                 channel: Channel, method: Method,
-                 default_timeout_s: Optional[float]) -> None:
-        self._impl = client_impl
-        self._rpcs = rpcs
-        self._rpc = PendingRpc(channel, method.service, method)
-        self.default_timeout_s: Optional[float] = default_timeout_s
-
-    @property
-    def channel(self) -> Channel:
-        return self._rpc.channel
-
-    @property
-    def method(self) -> Method:
-        return self._rpc.method
-
-    @property
-    def service(self) -> Service:
-        return self._rpc.service
-
-    @property
-    def request(self) -> type:
-        """Returns the request proto class."""
-        return self.method.request_type
-
-    @property
-    def response(self) -> type:
-        """Returns the response proto class."""
-        return self.method.response_type
-
-    def __repr__(self) -> str:
-        return self.help()
-
-    def help(self) -> str:
-        """Returns a help message about this RPC."""
-        function_call = self.method.full_name + '('
-
-        docstring = inspect.getdoc(self.__call__)  # type: ignore[operator] # pylint: disable=no-member
-        assert docstring is not None
-
-        annotation = inspect.Signature.from_callable(self).return_annotation  # type: ignore[arg-type] # pylint: disable=line-too-long
-        if isinstance(annotation, type):
-            annotation = annotation.__name__
-
-        arg_sep = f',\n{" " * len(function_call)}'
-        return (
-            f'{function_call}'
-            f'{arg_sep.join(descriptors.field_help(self.method.request_type))})'
-            f'\n\n{textwrap.indent(docstring, "  ")}\n\n'
-            f'  Returns {annotation}.')
-
-    def _start_call(self,
-                    call_type: Type[CallType],
-                    request: Optional[Message],
-                    timeout_s: OptionalTimeout,
-                    on_next: Optional[OnNextCallback],
-                    on_completed: Optional[OnCompletedCallback],
-                    on_error: Optional[OnErrorCallback],
-                    ignore_errors: bool = False) -> CallType:
-        """Creates the Call object and invokes the RPC using it."""
-        if timeout_s is UseDefault.VALUE:
-            timeout_s = self.default_timeout_s
-
-        call = call_type(self._rpcs, self._rpc, timeout_s, on_next,
-                         on_completed, on_error)
-        call._invoke(request, ignore_errors)  # pylint: disable=protected-access
-        return call
-
-    def _client_streaming_call_type(self,
-                                    base: Type[CallType]) -> Type[CallType]:
-        """Creates a client or bidirectional stream call type.
-
-        Applies the signature from the request protobuf to the send method.
-        """
-        def send(self,
-                 _rpc_request_proto: Message = None,
-                 **request_fields) -> None:
-            ClientStreamingCall.send(self, _rpc_request_proto,
-                                     **request_fields)
-
-        _apply_protobuf_signature(self.method, send)
-
-        return type(f'{self.method.name}_{base.__name__}', (base, ),
-                    dict(send=send))
-
-
-def _function_docstring(method: Method) -> str:
-    return f'''\
-Invokes the {method.full_name} {method.type.sentence_name()} RPC.
-
-This function accepts either the request protobuf fields as keyword arguments or
-a request protobuf as a positional argument.
-'''
-
-
-def _update_call_method(method: Method, function: Callable) -> None:
-    """Updates the name, docstring, and parameters to match a method."""
-    function.__name__ = method.full_name
-    function.__doc__ = _function_docstring(method)
-    _apply_protobuf_signature(method, function)
-
-
-def _apply_protobuf_signature(method: Method, function: Callable) -> None:
-    """Update a function signature to accept proto arguments.
-
-    In order to have good tab completion and help messages, update the function
-    signature to accept only keyword arguments for the proto message fields.
-    This doesn't actually change the function signature -- it just updates how
-    it appears when inspected.
-    """
-    sig = inspect.signature(function)
-
-    params = [next(iter(sig.parameters.values()))]  # Get the "self" parameter
-    params += method.request_parameters()
-    params.append(
-        inspect.Parameter('pw_rpc_timeout_s', inspect.Parameter.KEYWORD_ONLY))
-
-    function.__signature__ = sig.replace(  # type: ignore[attr-defined]
-        parameters=params)
-
-
-class _UnaryMethodClient(_MethodClient):
-    def invoke(self,
-               request: Message = None,
-               on_next: OnNextCallback = None,
-               on_completed: OnCompletedCallback = None,
-               on_error: OnErrorCallback = None,
-               *,
-               request_args: Dict[str, Any] = None,
-               timeout_s: OptionalTimeout = UseDefault.VALUE) -> UnaryCall:
-        """Invokes the unary RPC and returns a call object."""
-        return self._start_call(UnaryCall,
-                                self.method.get_request(request, request_args),
-                                timeout_s, on_next, on_completed, on_error)
-
-    def open(self,
-             request: Message = None,
-             on_next: OnNextCallback = None,
-             on_completed: OnCompletedCallback = None,
-             on_error: OnErrorCallback = None,
-             *,
-             request_args: Dict[str, Any] = None) -> UnaryCall:
-        """Invokes the unary RPC and returns a call object."""
-        return self._start_call(UnaryCall,
-                                self.method.get_request(request, request_args),
-                                None, on_next, on_completed, on_error, True)
-
-
-class _ServerStreamingMethodClient(_MethodClient):
-    def invoke(
-            self,
-            request: Message = None,
-            on_next: OnNextCallback = None,
-            on_completed: OnCompletedCallback = None,
-            on_error: OnErrorCallback = None,
-            *,
-            request_args: Dict[str, Any] = None,
-            timeout_s: OptionalTimeout = UseDefault.VALUE
-    ) -> ServerStreamingCall:
-        """Invokes the server streaming RPC and returns a call object."""
-        return self._start_call(ServerStreamingCall,
-                                self.method.get_request(request, request_args),
-                                timeout_s, on_next, on_completed, on_error)
-
-    def open(self,
-             request: Message = None,
-             on_next: OnNextCallback = None,
-             on_completed: OnCompletedCallback = None,
-             on_error: OnErrorCallback = None,
-             *,
-             request_args: Dict[str, Any] = None) -> ServerStreamingCall:
-        """Returns a call object for the RPC, even if the RPC cannot be invoked.
-
-        Can be used to listen for responses from an RPC server that may yet be
-        available.
-        """
-        return self._start_call(ServerStreamingCall,
-                                self.method.get_request(request, request_args),
-                                None, on_next, on_completed, on_error, True)
-
-
-class _ClientStreamingMethodClient(_MethodClient):
-    def invoke(
-            self,
-            on_next: OnNextCallback = None,
-            on_completed: OnCompletedCallback = None,
-            on_error: OnErrorCallback = None,
-            *,
-            timeout_s: OptionalTimeout = UseDefault.VALUE
-    ) -> ClientStreamingCall:
-        """Invokes the client streaming RPC and returns a call object"""
-        return self._start_call(
-            self._client_streaming_call_type(ClientStreamingCall), None,
-            timeout_s, on_next, on_completed, on_error, True)
-
-    def open(self,
-             on_next: OnNextCallback = None,
-             on_completed: OnCompletedCallback = None,
-             on_error: OnErrorCallback = None) -> ClientStreamingCall:
-        """Returns a call object for the RPC, even if the RPC cannot be invoked.
-
-        Can be used to listen for responses from an RPC server that may yet be
-        available.
-        """
-        return self._start_call(
-            self._client_streaming_call_type(ClientStreamingCall), None, None,
-            on_next, on_completed, on_error, True)
-
-    def __call__(
-            self,
-            requests: Iterable[Message] = (),
-            *,
-            timeout_s: OptionalTimeout = UseDefault.VALUE) -> UnaryResponse:
-        return self.invoke().finish_and_wait(requests, timeout_s=timeout_s)
-
-
-class _BidirectionalStreamingMethodClient(_MethodClient):
-    def invoke(
-        self,
-        on_next: OnNextCallback = None,
-        on_completed: OnCompletedCallback = None,
-        on_error: OnErrorCallback = None,
-        *,
-        timeout_s: OptionalTimeout = UseDefault.VALUE
-    ) -> BidirectionalStreamingCall:
-        """Invokes the bidirectional streaming RPC and returns a call object."""
-        return self._start_call(
-            self._client_streaming_call_type(BidirectionalStreamingCall), None,
-            timeout_s, on_next, on_completed, on_error)
-
-    def open(self,
-             on_next: OnNextCallback = None,
-             on_completed: OnCompletedCallback = None,
-             on_error: OnErrorCallback = None) -> BidirectionalStreamingCall:
-        """Returns a call object for the RPC, even if the RPC cannot be invoked.
-
-        Can be used to listen for responses from an RPC server that may yet be
-        available.
-        """
-        return self._start_call(
-            self._client_streaming_call_type(BidirectionalStreamingCall), None,
-            None, on_next, on_completed, on_error, True)
-
-    def __call__(
-            self,
-            requests: Iterable[Message] = (),
-            *,
-            timeout_s: OptionalTimeout = UseDefault.VALUE) -> StreamResponse:
-        return self.invoke().finish_and_wait(requests, timeout_s=timeout_s)
-
-
-def _method_client_docstring(method: Method) -> str:
-    return f'''\
-Class that invokes the {method.full_name} {method.type.sentence_name()} RPC.
-
-Calling this directly invokes the RPC synchronously. The RPC can be invoked
-asynchronously using the invoke method.
-'''
-
-
-class Impl(client.ClientImpl):
-    """Callback-based ClientImpl, for use with pw_rpc.Client."""
-    def __init__(self,
-                 default_unary_timeout_s: float = None,
-                 default_stream_timeout_s: float = None,
-                 cancel_duplicate_calls: bool = True) -> None:
-        super().__init__()
-        self._default_unary_timeout_s = default_unary_timeout_s
-        self._default_stream_timeout_s = default_stream_timeout_s
-
-        # Temporary workaround for clients that rely on mulitple in-flight
-        # instances of an RPC on the same channel, which is not supported.
-        # TODO(hepler): Remove this option when clients have updated.
-        self._cancel_duplicate_calls = cancel_duplicate_calls
-
-    @property
-    def default_unary_timeout_s(self) -> Optional[float]:
-        return self._default_unary_timeout_s
-
-    @property
-    def default_stream_timeout_s(self) -> Optional[float]:
-        return self._default_stream_timeout_s
-
-    def method_client(self, channel: Channel, method: Method) -> _MethodClient:
-        """Returns an object that invokes a method using the given chanel."""
-
-        # Temporarily attach the cancel_duplicate_calls option to the
-        # PendingRpcs object.
-        # TODO(hepler): Remove this workaround.
-        self.rpcs.cancel_duplicate_calls = (  # type: ignore[attr-defined]
-            self._cancel_duplicate_calls)
-
-        if method.type is Method.Type.UNARY:
-            return self._create_unary_method_client(
-                channel, method, self.default_unary_timeout_s)
-
-        if method.type is Method.Type.SERVER_STREAMING:
-            return self._create_server_streaming_method_client(
-                channel, method, self.default_stream_timeout_s)
-
-        if method.type is Method.Type.CLIENT_STREAMING:
-            return self._create_method_client(_ClientStreamingMethodClient,
-                                              channel, method,
-                                              self.default_unary_timeout_s)
-
-        if method.type is Method.Type.BIDIRECTIONAL_STREAMING:
-            return self._create_method_client(
-                _BidirectionalStreamingMethodClient, channel, method,
-                self.default_stream_timeout_s)
-
-        raise AssertionError(f'Unknown method type {method.type}')
-
-    def _create_method_client(self, base: type, channel: Channel,
-                              method: Method,
-                              default_timeout_s: Optional[float], **fields):
-        """Creates a _MethodClient derived class customized for the method."""
-        method_client_type = type(
-            f'{method.name}{base.__name__}', (base, ),
-            dict(__doc__=_method_client_docstring(method), **fields))
-        return method_client_type(self, self.rpcs, channel, method,
-                                  default_timeout_s)
-
-    def _create_unary_method_client(
-            self, channel: Channel, method: Method,
-            default_timeout_s: Optional[float]) -> _UnaryMethodClient:
-        """Creates a _UnaryMethodClient with a customized __call__ method."""
-
-        # TODO(hepler): Use / to mark the first arg as positional-only
-        #     when when Python 3.7 support is no longer required.
-        def call(self: _UnaryMethodClient,
-                 _rpc_request_proto: Message = None,
-                 *,
-                 pw_rpc_timeout_s: OptionalTimeout = UseDefault.VALUE,
-                 **request_fields) -> UnaryResponse:
-            return self.invoke(
-                self.method.get_request(_rpc_request_proto,
-                                        request_fields)).wait(pw_rpc_timeout_s)
-
-        _update_call_method(method, call)
-        return self._create_method_client(_UnaryMethodClient,
-                                          channel,
-                                          method,
-                                          default_timeout_s,
-                                          __call__=call)
-
-    def _create_server_streaming_method_client(
-            self, channel: Channel, method: Method,
-            default_timeout_s: Optional[float]
-    ) -> _ServerStreamingMethodClient:
-        """Creates _ServerStreamingMethodClient with custom __call__ method."""
-
-        # TODO(hepler): Use / to mark the first arg as positional-only
-        #     when when Python 3.7 support is no longer required.
-        def call(self: _ServerStreamingMethodClient,
-                 _rpc_request_proto: Message = None,
-                 *,
-                 pw_rpc_timeout_s: OptionalTimeout = UseDefault.VALUE,
-                 **request_fields) -> StreamResponse:
-            return self.invoke(
-                self.method.get_request(_rpc_request_proto,
-                                        request_fields)).wait(pw_rpc_timeout_s)
-
-        _update_call_method(method, call)
-        return self._create_method_client(_ServerStreamingMethodClient,
-                                          channel,
-                                          method,
-                                          default_timeout_s,
-                                          __call__=call)
-
-    def handle_response(self,
-                        rpc: PendingRpc,
-                        context: Call,
-                        payload,
-                        *,
-                        args: tuple = (),
-                        kwargs: dict = None) -> None:
-        """Invokes the callback associated with this RPC."""
-        assert not args and not kwargs, 'Forwarding args & kwargs not supported'
-        context._handle_response(payload)  # pylint: disable=protected-access
-
-    def handle_completion(self,
-                          rpc: PendingRpc,
-                          context: Call,
-                          status: Status,
-                          *,
-                          args: tuple = (),
-                          kwargs: dict = None):
-        assert not args and not kwargs, 'Forwarding args & kwargs not supported'
-        context._handle_completion(status)  # pylint: disable=protected-access
-
-    def handle_error(self,
-                     rpc: PendingRpc,
-                     context: Call,
-                     status: Status,
-                     *,
-                     args: tuple = (),
-                     kwargs: dict = None) -> None:
-        assert not args and not kwargs, 'Forwarding args & kwargs not supported'
-        context._handle_error(status)  # pylint: disable=protected-access
diff --git a/pw_rpc/py/pw_rpc/client.py b/pw_rpc/py/pw_rpc/client.py
index 182434d..e990bce 100644
--- a/pw_rpc/py/pw_rpc/client.py
+++ b/pw_rpc/py/pw_rpc/client.py
@@ -16,17 +16,17 @@
 import abc
 from dataclasses import dataclass
 import logging
-from typing import (Any, Callable, Collection, Dict, Iterable, Iterator,
-                    NamedTuple, Optional)
+from typing import (Any, Collection, Dict, Iterable, Iterator, NamedTuple,
+                    Optional)
 
-from google.protobuf.message import DecodeError, Message
+from google.protobuf.message import DecodeError
 from pw_status import Status
 
 from pw_rpc import descriptors, packets
 from pw_rpc.descriptors import Channel, Service, Method
 from pw_rpc.internal.packet_pb2 import PacketType, RpcPacket
 
-_LOG = logging.getLogger(__package__)
+_LOG = logging.getLogger(__name__)
 
 
 class Error(Exception):
@@ -44,8 +44,9 @@
 
 
 class _PendingRpcMetadata:
-    def __init__(self, context: object):
+    def __init__(self, context: Any, keep_open: bool):
         self.context = context
+        self.keep_open = keep_open
 
 
 class PendingRpcs:
@@ -55,85 +56,48 @@
 
     def request(self,
                 rpc: PendingRpc,
-                request: Optional[Message],
-                context: object,
-                override_pending: bool = True) -> bytes:
+                request,
+                context,
+                override_pending: bool = True,
+                keep_open: bool = False) -> bytes:
         """Starts the provided RPC and returns the encoded packet to send."""
         # Ensure that every context is a unique object by wrapping it in a list.
-        self.open(rpc, context, override_pending)
+        self.open(rpc, context, override_pending, keep_open)
+        _LOG.debug('Starting %s', rpc)
         return packets.encode_request(rpc, request)
 
     def send_request(self,
                      rpc: PendingRpc,
-                     request: Optional[Message],
-                     context: object,
-                     *,
-                     ignore_errors: bool = False,
-                     override_pending: bool = False) -> Any:
-        """Starts the provided RPC and sends the request packet to the channel.
-
-        Returns:
-          the previous context object or None
-        """
-        previous = self.open(rpc, context, override_pending)
-        packet = packets.encode_request(rpc, request)
-
-        # TODO(hepler): Remove `type: ignore[misc]` below when
-        #     https://github.com/python/mypy/issues/10711 is fixed.
-        if ignore_errors:
-            try:
-                rpc.channel.output(packet)  # type: ignore[misc]
-            except Exception as err:  # pylint: disable=broad-except
-                _LOG.debug('Ignoring exception when starting RPC: %s', err)
-        else:
-            rpc.channel.output(packet)  # type: ignore[misc]
-
-        return previous
+                     request,
+                     context,
+                     override_pending: bool = False,
+                     keep_open: bool = False) -> None:
+        """Calls request and sends the resulting packet to the channel."""
+        # TODO(hepler): Remove `type: ignore` on this and similar lines when
+        #     https://github.com/python/mypy/issues/5485 is fixed
+        rpc.channel.output(  # type: ignore
+            self.request(rpc, request, context, override_pending, keep_open))
 
     def open(self,
              rpc: PendingRpc,
-             context: object,
-             override_pending: bool = False) -> Any:
+             context,
+             override_pending: bool = False,
+             keep_open: bool = False) -> None:
         """Creates a context for an RPC, but does not invoke it.
 
         open() can be used to receive streaming responses to an RPC that was not
         invoked by this client. For example, a server may stream logs with a
         server streaming RPC prior to any clients invoking it.
-
-        Returns:
-          the previous context object or None
         """
-        _LOG.debug('Starting %s', rpc)
-        metadata = _PendingRpcMetadata(context)
+        metadata = _PendingRpcMetadata(context, keep_open)
 
         if override_pending:
-            previous = self._pending.get(rpc)
             self._pending[rpc] = metadata
-            return None if previous is None else previous.context
-
-        if self._pending.setdefault(rpc, metadata) is not metadata:
+        elif self._pending.setdefault(rpc, metadata) is not metadata:
             # If the context was not added, the RPC was already pending.
             raise Error(f'Sent request for {rpc}, but it is already pending! '
                         'Cancel the RPC before invoking it again')
 
-        return None
-
-    def send_client_stream(self, rpc: PendingRpc, message: Message) -> None:
-        if rpc not in self._pending:
-            raise Error(
-                f'Attempt to send client stream for inactive RPC {rpc}')
-
-        rpc.channel.output(  # type: ignore
-            packets.encode_client_stream(rpc, message))
-
-    def send_client_stream_end(self, rpc: PendingRpc) -> None:
-        if rpc not in self._pending:
-            raise Error(
-                f'Attempt to send client stream end for inactive RPC {rpc}')
-
-        rpc.channel.output(  # type: ignore
-            packets.encode_client_stream_end(rpc))
-
     def cancel(self, rpc: PendingRpc) -> Optional[bytes]:
         """Cancels the RPC. Returns the CANCEL packet to send.
 
@@ -168,6 +132,10 @@
         if status is None:
             return self._pending[rpc].context
 
+        if self._pending[rpc].keep_open:
+            _LOG.debug('%s finished with status %s; keeping open', rpc, status)
+            return self._pending[rpc].context
+
         _LOG.debug('%s finished with status %s', rpc, status)
         return self._pending.pop(rpc).context
 
@@ -279,25 +247,27 @@
 
 
 def _decode_status(rpc: PendingRpc, packet) -> Optional[Status]:
-    if packet.type == PacketType.SERVER_STREAM:
+    # Server streaming RPC packets never have a status; all other packets do.
+    if packet.type == PacketType.RESPONSE and rpc.method.server_streaming:
         return None
 
     try:
         return Status(packet.status)
     except ValueError:
         _LOG.warning('Illegal status code %d for %s', packet.status, rpc)
-        return Status.UNKNOWN
+
+    return None
 
 
-def _decode_payload(rpc: PendingRpc, packet) -> Optional[Message]:
-    if packet.type == PacketType.SERVER_ERROR:
-        return None
-
-    # Server streaming RPCs do not send a payload with their RESPONSE packet.
-    if packet.type == PacketType.RESPONSE and rpc.method.server_streaming:
-        return None
-
-    return packets.decode_payload(packet, rpc.method.response_type)
+def _decode_payload(rpc: PendingRpc, packet):
+    if packet.type == PacketType.RESPONSE:
+        try:
+            return packets.decode_payload(packet, rpc.method.response_type)
+        except DecodeError as err:
+            _LOG.warning('Failed to decode %s response for %s: %s',
+                         rpc.method.response_type.DESCRIPTOR.full_name,
+                         rpc.method.full_name, err)
+    return None
 
 
 @dataclass(frozen=True, eq=False)
@@ -356,38 +326,10 @@
                 f'services={[str(s) for s in self.services()]})')
 
 
-def _update_for_backwards_compatibility(rpc: PendingRpc,
-                                        packet: RpcPacket) -> None:
-    """Adapts server streaming RPC packets to the updated protocol if needed."""
-    # The protocol changes only affect server streaming RPCs.
-    if rpc.method.type is not Method.Type.SERVER_STREAMING:
-        return
-
-    # SERVER_STREAM_END packets are deprecated. They are equivalent to a
-    # RESPONSE packet.
-    if packet.type == PacketType.DEPRECATED_SERVER_STREAM_END:
-        packet.type = PacketType.RESPONSE
-        return
-
-    # Prior to the introduction of SERVER_STREAM packets, RESPONSE packets with
-    # a payload were used instead. If a non-zero payload is present, assume this
-    # RESPONSE is equivalent to a SERVER_STREAM packet.
-    #
-    # Note that the payload field is not 'optional', so an empty payload is
-    # equivalent to a payload that happens to encode to zero bytes. This would
-    # only affect server streaming RPCs on the old protocol that intentionally
-    # send empty payloads, which will not be an issue in practice.
-    if packet.type == PacketType.RESPONSE and packet.payload:
-        packet.type = PacketType.SERVER_STREAM
-
-
 class Client:
     """Sends requests and handles responses for a set of channels.
 
     RPC invocations occur through a ChannelClient.
-
-    Users may set an optional response_callback that is called before processing
-    every response or server stream RPC packet.
     """
     @classmethod
     def from_modules(cls, impl: ClientImpl, channels: Iterable[Channel],
@@ -412,10 +354,6 @@
             for channel in channels
         }
 
-        # Optional function called before processing every non-error RPC packet.
-        self.response_callback: Optional[Callable[
-            [PendingRpc, Any, Optional[Status]], Any]] = None
-
     def channel(self, channel_id: int = None) -> ChannelClient:
         """Returns a ChannelClient, which is used to call RPCs on a channel.
 
@@ -481,42 +419,28 @@
         try:
             rpc = self._look_up_service_and_method(packet, channel_client)
         except ValueError as err:
-            _send_client_error(channel_client, packet, Status.NOT_FOUND)
+            channel_client.channel.output(  # type: ignore
+                packets.encode_client_error(packet, Status.NOT_FOUND))
             _LOG.warning('%s', err)
             return Status.OK
 
-        _update_for_backwards_compatibility(rpc, packet)
+        status = _decode_status(rpc, packet)
 
-        if packet.type not in (PacketType.RESPONSE, PacketType.SERVER_STREAM,
+        if packet.type not in (PacketType.RESPONSE,
+                               PacketType.SERVER_STREAM_END,
                                PacketType.SERVER_ERROR):
             _LOG.error('%s: unexpected PacketType %s', rpc, packet.type)
             _LOG.debug('Packet:\n%s', packet)
             return Status.OK
 
-        status = _decode_status(rpc, packet)
-
-        try:
-            payload = _decode_payload(rpc, packet)
-        except DecodeError as err:
-            _send_client_error(channel_client, packet, Status.DATA_LOSS)
-            _LOG.warning('Failed to decode %s response for %s: %s',
-                         rpc.method.response_type.DESCRIPTOR.full_name,
-                         rpc.method.full_name, err)
-            _LOG.debug('Raw payload: %s', packet.payload)
-
-            # Make this an error packet so the error handler is called.
-            packet.type = PacketType.SERVER_ERROR
-            status = Status.DATA_LOSS
-
-        # If set, call the response callback with non-error packets.
-        if self.response_callback and packet.type != PacketType.SERVER_ERROR:
-            self.response_callback(rpc, payload, status)  # pylint: disable=not-callable
+        payload = _decode_payload(rpc, packet)
 
         try:
             context = self._impl.rpcs.get_pending(rpc, status)
         except KeyError:
-            _send_client_error(channel_client, packet,
-                               Status.FAILED_PRECONDITION)
+            channel_client.channel.output(  # type: ignore
+                packets.encode_client_error(packet,
+                                            Status.FAILED_PRECONDITION))
             _LOG.debug('Discarding response for %s, which is not pending', rpc)
             return Status.OK
 
@@ -564,11 +488,3 @@
     def __repr__(self) -> str:
         return (f'pw_rpc.Client(channels={list(self._channels_by_id)}, '
                 f'services={[s.full_name for s in self.services]})')
-
-
-def _send_client_error(client: ChannelClient, packet: RpcPacket,
-                       error: Status) -> None:
-    # Never send responses to SERVER_ERRORs.
-    if packet.type != PacketType.SERVER_ERROR:
-        client.channel.output(  # type: ignore
-            packets.encode_client_error(packet, error))
diff --git a/pw_rpc/py/pw_rpc/codegen.py b/pw_rpc/py/pw_rpc/codegen.py
index 44b10af..957a893 100644
--- a/pw_rpc/py/pw_rpc/codegen.py
+++ b/pw_rpc/py/pw_rpc/codegen.py
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2020 The Pigweed Authors
 #
 # 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
@@ -16,14 +16,15 @@
 import abc
 from datetime import datetime
 import os
-from typing import cast, Any, Iterable, Union
+from typing import cast, Any, Callable, Iterable
 
 from pw_protobuf.output_file import OutputFile
 from pw_protobuf.proto_tree import ProtoNode, ProtoService, ProtoServiceMethod
-from pw_rpc import ids
+
+import pw_rpc.ids
 
 PLUGIN_NAME = 'pw_rpc_codegen'
-PLUGIN_VERSION = '0.3.0'
+PLUGIN_VERSION = '0.2.0'
 
 RPC_NAMESPACE = '::pw::rpc'
 
@@ -34,316 +35,143 @@
 STUB_WRITER_TODO = (
     '// TODO: Send responses with the writer as appropriate for your '
     'application')
-STUB_READER_TODO = (
-    '// TODO: Set the client stream callback and send a response as '
-    'appropriate for your application')
-STUB_READER_WRITER_TODO = (
-    '// TODO: Set the client stream callback and send responses as '
-    'appropriate for your application')
+
+ServerWriterGenerator = Callable[[OutputFile], None]
+MethodGenerator = Callable[[ProtoServiceMethod, int, OutputFile], None]
+ServiceGenerator = Callable[[ProtoService, ProtoNode, OutputFile], None]
+IncludesGenerator = Callable[[Any, ProtoNode], Iterable[str]]
 
 
-def get_id(item: Union[ProtoService, ProtoServiceMethod]) -> str:
-    name = item.proto_path() if isinstance(item, ProtoService) else item.name()
-    return f'0x{ids.calculate(name):08x}'
-
-
-def client_call_type(method: ProtoServiceMethod, prefix: str) -> str:
-    """Returns Client ReaderWriter/Reader/Writer/Recevier for the call."""
-    if method.type() is ProtoServiceMethod.Type.UNARY:
-        call_class = 'UnaryReceiver'
-    elif method.type() is ProtoServiceMethod.Type.SERVER_STREAMING:
-        call_class = 'ClientReader'
-    elif method.type() is ProtoServiceMethod.Type.CLIENT_STREAMING:
-        call_class = 'ClientWriter'
-    elif method.type() is ProtoServiceMethod.Type.BIDIRECTIONAL_STREAMING:
-        call_class = 'ClientReaderWriter'
-    else:
-        raise NotImplementedError(f'Unknown {method.type()}')
-
-    return f'{RPC_NAMESPACE}::{prefix}{call_class}'
-
-
-class CodeGenerator(abc.ABC):
-    """Generates RPC code for services and clients."""
-    def __init__(self, output_filename: str) -> None:
-        self.output = OutputFile(output_filename)
-
-    def indent(self, amount: int = OutputFile.INDENT_WIDTH) -> Any:
-        """Indents the output. Use in a with block."""
-        return self.output.indent(amount)
-
-    def line(self, value: str = '') -> None:
-        """Writes a line to the output."""
-        self.output.write_line(value)
-
-    def indented_list(self, *args: str, end: str = ',') -> None:
-        """Outputs each arg one per line; adds end to teh last arg."""
-        with self.indent(4):
-            for arg in args[:-1]:
-                self.line(arg + ',')
-
-            self.line(args[-1] + end)
-
-    @abc.abstractmethod
-    def name(self) -> str:
-        """Name of the pw_rpc implementation."""
-
-    @abc.abstractmethod
-    def method_union_name(self) -> str:
-        """Name of the MethodUnion class to use."""
-
-    @abc.abstractmethod
-    def includes(self, proto_file_name: str) -> Iterable[str]:
-        """Yields #include lines."""
-
-    @abc.abstractmethod
-    def service_aliases(self) -> None:
-        """Generates reader/writer aliases."""
-
-    @abc.abstractmethod
-    def method_descriptor(self, method: ProtoServiceMethod) -> None:
-        """Generates code for a service method."""
-
-    @abc.abstractmethod
-    def client_member_function(self, method: ProtoServiceMethod) -> None:
-        """Generates the client code for the Client member functions."""
-
-    @abc.abstractmethod
-    def client_static_function(self, method: ProtoServiceMethod) -> None:
-        """Generates method static functions that instantiate a Client."""
-
-    def method_info_specialization(self, method: ProtoServiceMethod) -> None:
-        """Generates impl-specific additions to the MethodInfo specialization.
-
-        May be empty if the generator has nothing to add to the MethodInfo.
-        """
-
-    def private_additions(self, service: ProtoService) -> None:
-        """Additions to the private section of the outer generated class."""
-
-
-def generate_package(file_descriptor_proto, proto_package: ProtoNode,
-                     gen: CodeGenerator) -> None:
+def package(file_descriptor_proto, proto_package: ProtoNode,
+            output: OutputFile, includes: IncludesGenerator,
+            service: ServiceGenerator, client: ServiceGenerator) -> None:
     """Generates service and client code for a package."""
     assert proto_package.type() == ProtoNode.Type.PACKAGE
 
-    gen.line(f'// {os.path.basename(gen.output.name())} automatically '
-             f'generated by {PLUGIN_NAME} {PLUGIN_VERSION}')
-    gen.line(f'// on {datetime.now().isoformat()}')
-    gen.line('// clang-format off')
-    gen.line('#pragma once\n')
+    output.write_line(f'// {os.path.basename(output.name())} automatically '
+                      f'generated by {PLUGIN_NAME} {PLUGIN_VERSION}')
+    output.write_line(f'// on {datetime.now().isoformat()}')
+    output.write_line('// clang-format off')
+    output.write_line('#pragma once\n')
 
-    gen.line('#include <array>')
-    gen.line('#include <cstdint>')
-    gen.line('#include <type_traits>\n')
+    output.write_line('#include <array>')
+    output.write_line('#include <cstdint>')
+    output.write_line('#include <type_traits>\n')
 
     include_lines = [
-        '#include "pw_rpc/internal/method_info.h"',
         '#include "pw_rpc/internal/method_lookup.h"',
-        '#include "pw_rpc/internal/service_client.h"',
-        '#include "pw_rpc/method_type.h"',
+        '#include "pw_rpc/server_context.h"',
         '#include "pw_rpc/service.h"',
     ]
-    include_lines += gen.includes(file_descriptor_proto.name)
+    include_lines += includes(file_descriptor_proto, proto_package)
 
     for include_line in sorted(include_lines):
-        gen.line(include_line)
+        output.write_line(include_line)
 
-    gen.line()
+    output.write_line()
 
     if proto_package.cpp_namespace():
         file_namespace = proto_package.cpp_namespace()
         if file_namespace.startswith('::'):
             file_namespace = file_namespace[2:]
 
-        gen.line(f'namespace {file_namespace} {{')
-    else:
-        file_namespace = ''
+        output.write_line(f'namespace {file_namespace} {{')
 
-    gen.line(f'namespace pw_rpc::{gen.name()} {{')
-    gen.line()
+    for node in proto_package:
+        if node.type() == ProtoNode.Type.SERVICE:
+            service(cast(ProtoService, node), proto_package, output)
+            client(cast(ProtoService, node), proto_package, output)
 
-    services = [
-        cast(ProtoService, node) for node in proto_package
-        if node.type() == ProtoNode.Type.SERVICE
-    ]
-
-    for service in services:
-        _generate_service_and_client(gen, service)
-
-    gen.line()
-    gen.line(f'}}  // namespace pw_rpc::{gen.name()}\n')
-
-    if file_namespace:
-        gen.line('}  // namespace ' + file_namespace)
-
-    gen.line()
-    gen.line('// Specialize MethodInfo for each RPC to provide metadata at '
-             'compile time.')
-    for service in services:
-        _generate_info(gen, file_namespace, service)
+    if proto_package.cpp_namespace():
+        output.write_line(f'}}  // namespace {file_namespace}')
 
 
-def _generate_service_and_client(gen: CodeGenerator,
-                                 service: ProtoService) -> None:
-    gen.line('// Wrapper class that namespaces server and client code for '
-             'this RPC service.')
-    gen.line(f'class {service.name()} final {{')
-    gen.line(' public:')
+def service_class(service: ProtoService, root: ProtoNode, output: OutputFile,
+                  server_writer_alias: ServerWriterGenerator,
+                  method_union: str,
+                  method_descriptor: MethodGenerator) -> None:
+    """Generates a C++ derived class for a nanopb RPC service."""
 
-    with gen.indent():
-        gen.line(f'{service.name()}() = delete;')
-        gen.line()
-
-        _generate_service(gen, service)
-
-        gen.line()
-
-        _generate_client(gen, service)
-
-    gen.line(' private:')
-
-    with gen.indent():
-        gen.line(f'// Hash of "{service.proto_path()}".')
-        gen.line(f'static constexpr uint32_t kServiceId = {get_id(service)};')
-
-    gen.line('};')
-
-
-def _check_method_name(method: ProtoServiceMethod) -> None:
-    if method.name() in ('Service', 'Client'):
-        raise ValueError(
-            f'"{method.service().proto_path()}.{method.name()}" is not a '
-            f'valid method name! The name "{method.name()}" is reserved '
-            'for internal use by pw_rpc.')
-
-
-def _generate_client(gen: CodeGenerator, service: ProtoService) -> None:
-    gen.line('// The Client is used to invoke RPCs for this service.')
-    gen.line(f'class Client final : public {RPC_NAMESPACE}::internal::'
-             'ServiceClient {')
-    gen.line(' public:')
-
-    with gen.indent():
-        gen.line(f'constexpr Client({RPC_NAMESPACE}::Client& client,'
-                 ' uint32_t channel_id)')
-        gen.line('    : ServiceClient(client, channel_id) {}')
-
-        for method in service.methods():
-            gen.line()
-            gen.client_member_function(method)
-
-    gen.line('};')
-    gen.line()
-
-    gen.line('// Static functions for invoking RPCs on a pw_rpc server. '
-             'These functions are ')
-    gen.line('// equivalent to instantiating a Client and calling the '
-             'corresponding RPC.')
-    for method in service.methods():
-        _check_method_name(method)
-        gen.client_static_function(method)
-        gen.line()
-
-
-def _generate_info(gen: CodeGenerator, namespace: str,
-                   service: ProtoService) -> None:
-    """Generates MethodInfo for each method."""
-    service_id = get_id(service)
-    info = f'struct {RPC_NAMESPACE.lstrip(":")}::internal::MethodInfo'
-
-    for method in service.methods():
-        gen.line('template <>')
-        gen.line(f'{info}<{namespace}::pw_rpc::{gen.name()}::'
-                 f'{service.name()}::{method.name()}> {{')
-
-        with gen.indent():
-            gen.line(f'static constexpr uint32_t kServiceId = {service_id};')
-            gen.line(f'static constexpr uint32_t kMethodId = '
-                     f'{get_id(method)};')
-            gen.line(f'static constexpr {RPC_NAMESPACE}::MethodType kType = '
-                     f'{method.type().cc_enum()};')
-            gen.line()
-
-            gen.line('template <typename ServiceImpl>')
-            gen.line('static constexpr auto Function() {')
-
-            with gen.indent():
-                gen.line(f'return &ServiceImpl::{method.name()};')
-
-            gen.line('}')
-
-            gen.method_info_specialization(method)
-
-        gen.line('};')
-        gen.line()
-
-
-def _generate_service(gen: CodeGenerator, service: ProtoService) -> None:
-    """Generates a C++ class for an RPC service."""
+    output.write_line('namespace generated {')
 
     base_class = f'{RPC_NAMESPACE}::Service'
-    gen.line('// The RPC service base class.')
-    gen.line(
-        '// Inherit from this to implement an RPC service for a pw_rpc server.'
-    )
-    gen.line('template <typename Implementation>')
-    gen.line(f'class Service : public {base_class} {{')
-    gen.line(' public:')
+    output.write_line('\ntemplate <typename Implementation>')
+    output.write_line(
+        f'class {service.cpp_namespace(root)} : public {base_class} {{')
+    output.write_line(' public:')
 
-    with gen.indent():
-        gen.service_aliases()
+    with output.indent():
+        output.write_line(
+            f'using ServerContext = {RPC_NAMESPACE}::ServerContext;')
+        server_writer_alias(output)
+        output.write_line()
 
-        gen.line()
-        gen.line(f'static constexpr const char* name() '
-                 f'{{ return "{service.name()}"; }}')
+        output.write_line(f'constexpr {service.name()}()')
+        output.write_line(f'    : {base_class}(kServiceId, kMethods) {{}}')
 
-        gen.line()
+        output.write_line()
+        output.write_line(
+            f'{service.name()}(const {service.name()}&) = delete;')
+        output.write_line(f'{service.name()}& operator='
+                          f'(const {service.name()}&) = delete;')
 
-    gen.line(' protected:')
+        output.write_line()
+        output.write_line(f'static constexpr const char* name() '
+                          f'{{ return "{service.name()}"; }}')
 
-    with gen.indent():
-        gen.line('constexpr Service() : '
-                 f'{base_class}(kServiceId, kPwRpcMethods) {{}}')
+        output.write_line()
+        output.write_line(
+            '// Used by MethodLookup to identify the generated service base.')
+        output.write_line(
+            'constexpr void _PwRpcInternalGeneratedBase() const {}')
 
-    gen.line()
-    gen.line(' private:')
+    service_name_hash = pw_rpc.ids.calculate(service.proto_path())
+    output.write_line('\n private:')
 
-    with gen.indent():
-        gen.line('friend class ::pw::rpc::internal::MethodLookup;')
-        gen.line()
+    with output.indent():
+        output.write_line('friend class ::pw::rpc::internal::MethodLookup;\n')
+        output.write_line(f'// Hash of "{service.proto_path()}".')
+        output.write_line(
+            f'static constexpr uint32_t kServiceId = 0x{service_name_hash:08x};'
+        )
+
+        output.write_line()
 
         # Generate the method table
-        gen.line('static constexpr std::array<'
-                 f'{RPC_NAMESPACE}::internal::{gen.method_union_name()},'
-                 f' {len(service.methods())}> kPwRpcMethods = {{')
+        output.write_line('static constexpr std::array<'
+                          f'{RPC_NAMESPACE}::internal::{method_union},'
+                          f' {len(service.methods())}> kMethods = {{')
 
-        with gen.indent(4):
+        with output.indent(4):
             for method in service.methods():
-                gen.method_descriptor(method)
+                method_descriptor(method, pw_rpc.ids.calculate(method.name()),
+                                  output)
 
-        gen.line('};\n')
+        output.write_line('};\n')
 
         # Generate the method lookup table
-        _method_lookup_table(gen, service)
+        _method_lookup_table(service, output)
 
-    gen.line('};')
+    output.write_line('};')
+
+    output.write_line('\n}  // namespace generated\n')
 
 
-def _method_lookup_table(gen: CodeGenerator, service: ProtoService) -> None:
+def _method_lookup_table(service: ProtoService, output: OutputFile) -> None:
     """Generates array of method IDs for looking up methods at compile time."""
-    gen.line('static constexpr std::array<uint32_t, '
-             f'{len(service.methods())}> kPwRpcMethodIds = {{')
+    output.write_line('static constexpr std::array<uint32_t, '
+                      f'{len(service.methods())}> kMethodIds = {{')
 
-    with gen.indent(4):
+    with output.indent(4):
         for method in service.methods():
-            gen.line(f'{get_id(method)},  // Hash of "{method.name()}"')
+            method_id = pw_rpc.ids.calculate(method.name())
+            output.write_line(
+                f'0x{method_id:08x},  // Hash of "{method.name()}"')
 
-    gen.line('};')
+    output.write_line('};\n')
 
 
 class StubGenerator(abc.ABC):
-    """Generates stub method implementations that can be copied-and-pasted."""
     @abc.abstractmethod
     def unary_signature(self, method: ProtoServiceMethod, prefix: str) -> str:
         """Returns the signature of this unary method."""
@@ -367,46 +195,17 @@
         output.write_line(STUB_WRITER_TODO)
         output.write_line('static_cast<void>(writer);')
 
-    @abc.abstractmethod
-    def client_streaming_signature(self, method: ProtoServiceMethod,
-                                   prefix: str) -> str:
-        """Returns the signature of this client streaming method."""
 
-    def client_streaming_stub(  # pylint: disable=no-self-use
-            self, unused_method: ProtoServiceMethod,
-            output: OutputFile) -> None:
-        """Returns the stub for this client streaming method."""
-        output.write_line(STUB_READER_TODO)
-        output.write_line('static_cast<void>(reader);')
-
-    @abc.abstractmethod
-    def bidirectional_streaming_signature(self, method: ProtoServiceMethod,
-                                          prefix: str) -> str:
-        """Returns the signature of this bidirectional streaming method."""
-
-    def bidirectional_streaming_stub(  # pylint: disable=no-self-use
-            self, unused_method: ProtoServiceMethod,
-            output: OutputFile) -> None:
-        """Returns the stub for this bidirectional streaming method."""
-        output.write_line(STUB_READER_WRITER_TODO)
-        output.write_line('static_cast<void>(reader_writer);')
-
-
-def _select_stub_methods(gen: StubGenerator, method: ProtoServiceMethod):
+def _select_stub_methods(generator: StubGenerator, method: ProtoServiceMethod):
     if method.type() is ProtoServiceMethod.Type.UNARY:
-        return gen.unary_signature, gen.unary_stub
+        return generator.unary_signature, generator.unary_stub
 
     if method.type() is ProtoServiceMethod.Type.SERVER_STREAMING:
-        return gen.server_streaming_signature, gen.server_streaming_stub
+        return (generator.server_streaming_signature,
+                generator.server_streaming_stub)
 
-    if method.type() is ProtoServiceMethod.Type.CLIENT_STREAMING:
-        return gen.client_streaming_signature, gen.client_streaming_stub
-
-    if method.type() is ProtoServiceMethod.Type.BIDIRECTIONAL_STREAMING:
-        return (gen.bidirectional_streaming_signature,
-                gen.bidirectional_streaming_stub)
-
-    raise NotImplementedError(f'Unrecognized method type {method.type()}')
+    raise NotImplementedError(
+        'Client and bidirectional streaming not yet implemented')
 
 
 _STUBS_COMMENT = r'''
@@ -430,7 +229,7 @@
 '''
 
 
-def package_stubs(proto_package: ProtoNode, gen: CodeGenerator,
+def package_stubs(proto_package: ProtoNode, output: OutputFile,
                   stub_generator: StubGenerator) -> None:
     """Generates the RPC stubs for a package."""
     if proto_package.cpp_namespace():
@@ -438,8 +237,8 @@
         if file_ns.startswith('::'):
             file_ns = file_ns[2:]
 
-        start_ns = lambda: gen.line(f'namespace {file_ns} {{\n')
-        finish_ns = lambda: gen.line(f'}}  // namespace {file_ns}\n')
+        start_ns = lambda: output.write_line(f'namespace {file_ns} {{\n')
+        finish_ns = lambda: output.write_line(f'}}  // namespace {file_ns}\n')
     else:
         start_ns = finish_ns = lambda: None
 
@@ -448,70 +247,71 @@
         if node.type() == ProtoNode.Type.SERVICE
     ]
 
-    gen.line('#ifdef _PW_RPC_COMPILE_GENERATED_SERVICE_STUBS')
-    gen.line(_STUBS_COMMENT)
+    output.write_line('#ifdef _PW_RPC_COMPILE_GENERATED_SERVICE_STUBS')
+    output.write_line(_STUBS_COMMENT)
 
-    gen.line(f'#include "{gen.output.name()}"\n')
+    output.write_line(f'#include "{output.name()}"\n')
 
     start_ns()
 
     for node in services:
-        _service_declaration_stub(node, gen, stub_generator)
+        _generate_service_class(node, output, stub_generator)
 
-    gen.line()
+    output.write_line()
 
     finish_ns()
 
     start_ns()
 
     for node in services:
-        _service_definition_stub(node, gen, stub_generator)
-        gen.line()
+        _generate_service_stubs(node, output, stub_generator)
+        output.write_line()
 
     finish_ns()
 
-    gen.line('#endif  // _PW_RPC_COMPILE_GENERATED_SERVICE_STUBS')
+    output.write_line('#endif  // _PW_RPC_COMPILE_GENERATED_SERVICE_STUBS')
 
 
-def _service_declaration_stub(service: ProtoService, gen: CodeGenerator,
-                              stub_generator: StubGenerator) -> None:
-    gen.line(f'// Implementation class for {service.proto_path()}.')
-    gen.line(f'class {service.name()} : public pw_rpc::{gen.name()}::'
-             f'{service.name()}::Service<{service.name()}> {{')
+def _generate_service_class(service: ProtoService, output: OutputFile,
+                            stub_generator: StubGenerator) -> None:
+    output.write_line(f'// Implementation class for {service.proto_path()}.')
+    output.write_line(
+        f'class {service.name()} '
+        f': public generated::{service.name()}<{service.name()}> {{')
 
-    gen.line(' public:')
+    output.write_line(' public:')
 
-    with gen.indent():
+    with output.indent():
         blank_line = False
 
         for method in service.methods():
             if blank_line:
-                gen.line()
+                output.write_line()
             else:
                 blank_line = True
 
             signature, _ = _select_stub_methods(stub_generator, method)
 
-            gen.line(signature(method, '') + ';')
+            output.write_line(signature(method, '') + ';')
 
-    gen.line('};\n')
+    output.write_line('};\n')
 
 
-def _service_definition_stub(service: ProtoService, gen: CodeGenerator,
-                             stub_generator: StubGenerator) -> None:
-    gen.line(f'// Method definitions for {service.proto_path()}.')
+def _generate_service_stubs(service: ProtoService, output: OutputFile,
+                            stub_generator: StubGenerator) -> None:
+    output.write_line(f'// Method definitions for {service.proto_path()}.')
 
     blank_line = False
 
     for method in service.methods():
         if blank_line:
-            gen.line()
+            output.write_line()
         else:
             blank_line = True
 
         signature, stub = _select_stub_methods(stub_generator, method)
 
-        gen.line(signature(method, f'{service.name()}::') + ' {')
-        with gen.indent():
-            stub(method, gen.output)
-        gen.line('}')
+        output.write_line(signature(method, f'{service.name()}::') + ' {')
+        with output.indent():
+            stub(method, output)
+        output.write_line('}')
diff --git a/pw_rpc/py/pw_rpc/codegen_nanopb.py b/pw_rpc/py/pw_rpc/codegen_nanopb.py
index 463de4f..183856c 100644
--- a/pw_rpc/py/pw_rpc/codegen_nanopb.py
+++ b/pw_rpc/py/pw_rpc/codegen_nanopb.py
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2020 The Pigweed Authors
 #
 # 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
@@ -14,27 +14,20 @@
 """This module generates the code for nanopb-based pw_rpc services."""
 
 import os
-from typing import Iterable, NamedTuple, Optional
+from typing import Iterable, Iterator
 
 from pw_protobuf.output_file import OutputFile
-from pw_protobuf.proto_tree import ProtoServiceMethod
+from pw_protobuf.proto_tree import ProtoNode, ProtoService, ProtoServiceMethod
 from pw_protobuf.proto_tree import build_node_tree
 from pw_rpc import codegen
-from pw_rpc.codegen import (client_call_type, get_id, CodeGenerator,
-                            RPC_NAMESPACE)
+from pw_rpc.codegen import RPC_NAMESPACE
+import pw_rpc.ids
 
 PROTO_H_EXTENSION = '.pb.h'
 PROTO_CC_EXTENSION = '.pb.cc'
 NANOPB_H_EXTENSION = '.pb.h'
 
 
-def _serde(method: ProtoServiceMethod) -> str:
-    """Returns the NanopbMethodSerde for this method."""
-    return (f'{RPC_NAMESPACE}::internal::kNanopbMethodSerde<'
-            f'{method.request_type().nanopb_fields()}, '
-            f'{method.response_type().nanopb_fields()}>')
-
-
 def _proto_filename_to_nanopb_header(proto_file: str) -> str:
     """Returns the generated nanopb header name for a .proto file."""
     return os.path.splitext(proto_file)[0] + NANOPB_H_EXTENSION
@@ -46,172 +39,135 @@
     return f'{filename}.rpc{PROTO_H_EXTENSION}'
 
 
-def _client_call(method: ProtoServiceMethod) -> str:
-    template_args = []
+def _generate_method_descriptor(method: ProtoServiceMethod, method_id: int,
+                                output: OutputFile) -> None:
+    """Generates a nanopb method descriptor for an RPC method."""
 
-    if method.client_streaming():
-        template_args.append(method.request_type().nanopb_struct())
+    req_fields = f'{method.request_type().nanopb_name()}_fields'
+    res_fields = f'{method.response_type().nanopb_name()}_fields'
+    impl_method = f'&Implementation::{method.name()}'
 
-    template_args.append(method.response_type().nanopb_struct())
-
-    return f'{client_call_type(method, "Nanopb")}<{", ".join(template_args)}>'
+    output.write_line(
+        f'{RPC_NAMESPACE}::internal::GetNanopbOrRawMethodFor<{impl_method}, '
+        f'{method.type().cc_enum()}, '
+        f'{method.request_type().nanopb_name()}, '
+        f'{method.response_type().nanopb_name()}>(')
+    with output.indent(4):
+        output.write_line(f'0x{method_id:08x},  // Hash of "{method.name()}"')
+        output.write_line(f'{req_fields},')
+        output.write_line(f'{res_fields}),')
 
 
-def _function(method: ProtoServiceMethod) -> str:
-    return f'{_client_call(method)} {method.name()}'
+def _generate_server_writer_alias(output: OutputFile) -> None:
+    output.write_line('template <typename T>')
+    output.write_line('using ServerWriter = ::pw::rpc::ServerWriter<T>;')
 
 
-def _user_args(method: ProtoServiceMethod) -> Iterable[str]:
-    if not method.client_streaming():
-        yield f'const {method.request_type().nanopb_struct()}& request'
+def _generate_code_for_service(service: ProtoService, root: ProtoNode,
+                               output: OutputFile) -> None:
+    """Generates a C++ derived class for a nanopb RPC service."""
+    codegen.service_class(service, root, output, _generate_server_writer_alias,
+                          'NanopbMethodUnion', _generate_method_descriptor)
 
-    response = method.response_type().nanopb_struct()
 
-    if method.server_streaming():
-        yield f'::pw::Function<void(const {response}&)>&& on_next = nullptr'
-        yield '::pw::Function<void(::pw::Status)>&& on_completed = nullptr'
+def _generate_code_for_client_method(method: ProtoServiceMethod,
+                                     output: OutputFile) -> None:
+    """Outputs client code for a single RPC method."""
+
+    req = method.request_type().nanopb_name()
+    res = method.response_type().nanopb_name()
+    method_id = pw_rpc.ids.calculate(method.name())
+
+    if method.type() == ProtoServiceMethod.Type.UNARY:
+        callback = f'{RPC_NAMESPACE}::UnaryResponseHandler<{res}>'
+    elif method.type() == ProtoServiceMethod.Type.SERVER_STREAMING:
+        callback = f'{RPC_NAMESPACE}::ServerStreamingResponseHandler<{res}>'
     else:
-        yield (f'::pw::Function<void(const {response}&, ::pw::Status)>&& '
-               'on_completed = nullptr')
+        raise NotImplementedError(
+            'Only unary and server streaming RPCs are currently supported')
 
-    yield '::pw::Function<void(::pw::Status)>&& on_error = nullptr'
+    output.write_line()
+    output.write_line(f'static NanopbClientCall<\n    {callback}>')
+    output.write_line(f'{method.name()}({RPC_NAMESPACE}::Channel& channel,')
+    with output.indent(len(method.name()) + 1):
+        output.write_line(f'const {req}& request,')
+        output.write_line(f'{callback}& callback) {{')
+
+    with output.indent():
+        output.write_line(f'NanopbClientCall<{callback}>')
+        output.write_line('    call(&channel,')
+        with output.indent(9):
+            output.write_line('kServiceId,')
+            output.write_line(
+                f'0x{method_id:08x},  // Hash of "{method.name()}"')
+            output.write_line('callback,')
+            output.write_line(f'{req}_fields,')
+            output.write_line(f'{res}_fields);')
+        output.write_line('call.SendRequest(&request);')
+        output.write_line('return call;')
+
+    output.write_line('}')
 
 
-class NanopbCodeGenerator(CodeGenerator):
-    """Generates an RPC service and client using the Nanopb API."""
-    def name(self) -> str:
-        return 'nanopb'
+def _generate_code_for_client(service: ProtoService, root: ProtoNode,
+                              output: OutputFile) -> None:
+    """Outputs client code for an RPC service."""
 
-    def method_union_name(self) -> str:
-        return 'NanopbMethodUnion'
+    output.write_line('namespace nanopb {')
 
-    def includes(self, proto_file_name: str) -> Iterable[str]:
-        yield '#include "pw_rpc/nanopb/client_reader_writer.h"'
-        yield '#include "pw_rpc/nanopb/internal/method_union.h"'
-        yield '#include "pw_rpc/nanopb/server_reader_writer.h"'
+    class_name = f'{service.cpp_namespace(root)}Client'
+    output.write_line(f'\nclass {class_name} {{')
+    output.write_line(' public:')
 
-        # Include the corresponding nanopb header file for this proto file, in
-        # which the file's messages and enums are generated. All other files
-        # imported from the .proto file are #included in there.
-        nanopb_header = _proto_filename_to_nanopb_header(proto_file_name)
-        yield f'#include "{nanopb_header}"'
+    with output.indent():
+        output.write_line('template <typename T>')
+        output.write_line(
+            f'using NanopbClientCall = {RPC_NAMESPACE}::NanopbClientCall<T>;')
 
-    def service_aliases(self) -> None:
-        self.line('template <typename Response>')
-        self.line('using ServerWriter = '
-                  f'{RPC_NAMESPACE}::NanopbServerWriter<Response>;')
-        self.line('template <typename Request, typename Response>')
-        self.line('using ServerReader = '
-                  f'{RPC_NAMESPACE}::NanopbServerReader<Request, Response>;')
-        self.line('template <typename Request, typename Response>')
-        self.line(
-            'using ServerReaderWriter = '
-            f'{RPC_NAMESPACE}::NanopbServerReaderWriter<Request, Response>;')
+        output.write_line('')
+        output.write_line(f'{class_name}() = delete;')
 
-    def method_descriptor(self, method: ProtoServiceMethod) -> None:
-        self.line(f'{RPC_NAMESPACE}::internal::'
-                  f'GetNanopbOrRawMethodFor<&Implementation::{method.name()}, '
-                  f'{method.type().cc_enum()}, '
-                  f'{method.request_type().nanopb_struct()}, '
-                  f'{method.response_type().nanopb_struct()}>(')
-        with self.indent(4):
-            self.line(f'{get_id(method)},  // Hash of "{method.name()}"')
-            self.line(f'{_serde(method)}),')
+        for method in service.methods():
+            _generate_code_for_client_method(method, output)
 
-    def client_member_function(self, method: ProtoServiceMethod) -> None:
-        """Outputs client code for a single RPC method."""
+    service_name_hash = pw_rpc.ids.calculate(service.proto_path())
+    output.write_line('\n private:')
 
-        self.line(f'{_function(method)}(')
-        self.indented_list(*_user_args(method), end=') const {')
+    with output.indent():
+        output.write_line(f'// Hash of "{service.proto_path()}".')
+        output.write_line(
+            f'static constexpr uint32_t kServiceId = 0x{service_name_hash:08x};'
+        )
 
-        with self.indent():
-            client_call = _client_call(method)
-            base = 'Stream' if method.server_streaming() else 'Unary'
-            self.line(f'return {RPC_NAMESPACE}::internal::'
-                      f'Nanopb{base}ResponseClientCall<'
-                      f'{method.response_type().nanopb_struct()}>::'
-                      f'Start<{client_call}>(')
+    output.write_line('};')
 
-            service_client = RPC_NAMESPACE + '::internal::ServiceClient'
-
-            args = [
-                f'{service_client}::client()',
-                f'{service_client}::channel_id()',
-                'kServiceId',
-                get_id(method),
-                _serde(method),
-            ]
-            if method.server_streaming():
-                args.append('std::move(on_next)')
-
-            args.append('std::move(on_completed)')
-            args.append('std::move(on_error)')
-
-            if not method.client_streaming():
-                args.append('request')
-
-            self.indented_list(*args, end=');')
-
-        self.line('}')
-
-    def client_static_function(self, method: ProtoServiceMethod) -> None:
-        self.line(f'static {_function(method)}(')
-        self.indented_list(f'{RPC_NAMESPACE}::Client& client',
-                           'uint32_t channel_id',
-                           *_user_args(method),
-                           end=') {')
-
-        with self.indent():
-            self.line(f'return Client(client, channel_id).{method.name()}(')
-
-            args = []
-
-            if not method.client_streaming():
-                args.append('request')
-
-            if method.server_streaming():
-                args.append('std::move(on_next)')
-
-            self.indented_list(*args,
-                               'std::move(on_completed)',
-                               'std::move(on_error)',
-                               end=');')
-
-        self.line('}')
-
-    def method_info_specialization(self, method: ProtoServiceMethod) -> None:
-        self.line()
-        self.line(f'using Request = {method.request_type().nanopb_struct()};')
-        self.line(
-            f'using Response = {method.response_type().nanopb_struct()};')
-        self.line()
-        self.line(f'static constexpr const {RPC_NAMESPACE}::internal::'
-                  'NanopbMethodSerde& serde() {')
-        with self.indent():
-            self.line(f'return {_serde(method)};')
-        self.line('}')
+    output.write_line('\n}  // namespace nanopb\n')
 
 
-class _CallbackFunction(NamedTuple):
-    """Represents a callback function parameter in a client RPC call."""
+def includes(proto_file, unused_package: ProtoNode) -> Iterator[str]:
+    yield '#include "pw_rpc/internal/nanopb_method_union.h"'
+    yield '#include "pw_rpc/nanopb_client_call.h"'
 
-    function_type: str
-    name: str
-    default_value: Optional[str] = None
+    # Include the corresponding nanopb header file for this proto file, in which
+    # the file's messages and enums are generated. All other files imported from
+    # the .proto file are #included in there.
+    nanopb_header = _proto_filename_to_nanopb_header(proto_file.name)
+    yield f'#include "{nanopb_header}"'
 
-    def __str__(self):
-        param = f'::pw::Function<{self.function_type}>&& {self.name}'
-        if self.default_value:
-            param += f' = {self.default_value}'
-        return param
+
+def _generate_code_for_package(proto_file, package: ProtoNode,
+                               output: OutputFile) -> None:
+    """Generates code for a header file corresponding to a .proto file."""
+
+    codegen.package(proto_file, package, output, includes,
+                    _generate_code_for_service, _generate_code_for_client)
 
 
 class StubGenerator(codegen.StubGenerator):
-    """Generates Nanopb RPC stubs."""
     def unary_signature(self, method: ProtoServiceMethod, prefix: str) -> str:
-        return (f'::pw::Status {prefix}{method.name()}( '
-                f'const {method.request_type().nanopb_struct()}& request, '
-                f'{method.response_type().nanopb_struct()}& response)')
+        return (f'pw::Status {prefix}{method.name()}(ServerContext&, '
+                f'const {method.request_type().nanopb_name()}& request, '
+                f'{method.response_type().nanopb_name()}& response)')
 
     def unary_stub(self, method: ProtoServiceMethod,
                    output: OutputFile) -> None:
@@ -219,26 +175,14 @@
         output.write_line('static_cast<void>(request);')
         output.write_line(codegen.STUB_RESPONSE_TODO)
         output.write_line('static_cast<void>(response);')
-        output.write_line('return ::pw::Status::Unimplemented();')
+        output.write_line('return pw::Status::Unimplemented();')
 
     def server_streaming_signature(self, method: ProtoServiceMethod,
                                    prefix: str) -> str:
         return (
-            f'void {prefix}{method.name()}( '
-            f'const {method.request_type().nanopb_struct()}& request, '
-            f'ServerWriter<{method.response_type().nanopb_struct()}>& writer)')
-
-    def client_streaming_signature(self, method: ProtoServiceMethod,
-                                   prefix: str) -> str:
-        return (f'void {prefix}{method.name()}( '
-                f'ServerReader<{method.request_type().nanopb_struct()}, '
-                f'{method.response_type().nanopb_struct()}>& reader)')
-
-    def bidirectional_streaming_signature(self, method: ProtoServiceMethod,
-                                          prefix: str) -> str:
-        return (f'void {prefix}{method.name()}( '
-                f'ServerReaderWriter<{method.request_type().nanopb_struct()}, '
-                f'{method.response_type().nanopb_struct()}>& reader_writer)')
+            f'void {prefix}{method.name()}(ServerContext&, '
+            f'const {method.request_type().nanopb_name()}& request, '
+            f'ServerWriter<{method.response_type().nanopb_name()}>& writer)')
 
 
 def process_proto_file(proto_file) -> Iterable[OutputFile]:
@@ -246,9 +190,10 @@
 
     _, package_root = build_node_tree(proto_file)
     output_filename = _proto_filename_to_generated_header(proto_file.name)
-    generator = NanopbCodeGenerator(output_filename)
-    codegen.generate_package(proto_file, package_root, generator)
+    output_file = OutputFile(output_filename)
+    _generate_code_for_package(proto_file, package_root, output_file)
 
-    codegen.package_stubs(package_root, generator, StubGenerator())
+    output_file.write_line()
+    codegen.package_stubs(package_root, output_file, StubGenerator())
 
-    return [generator.output]
+    return [output_file]
diff --git a/pw_rpc/py/pw_rpc/codegen_raw.py b/pw_rpc/py/pw_rpc/codegen_raw.py
index 15bc15b..51fee92 100644
--- a/pw_rpc/py/pw_rpc/codegen_raw.py
+++ b/pw_rpc/py/pw_rpc/codegen_raw.py
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2020 The Pigweed Authors
 #
 # 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
@@ -17,11 +17,10 @@
 from typing import Iterable
 
 from pw_protobuf.output_file import OutputFile
-from pw_protobuf.proto_tree import ProtoServiceMethod
+from pw_protobuf.proto_tree import ProtoNode, ProtoService, ProtoServiceMethod
 from pw_protobuf.proto_tree import build_node_tree
 from pw_rpc import codegen
-from pw_rpc.codegen import (client_call_type, get_id, CodeGenerator,
-                            RPC_NAMESPACE)
+from pw_rpc.codegen import RPC_NAMESPACE
 
 PROTO_H_EXTENSION = '.pb.h'
 
@@ -38,139 +37,75 @@
     return f'{filename}.raw_rpc.stub{PROTO_H_EXTENSION}'
 
 
-def _function(method: ProtoServiceMethod) -> str:
-    return f'{client_call_type(method, "Raw")} {method.name()}'
+def _generate_method_descriptor(method: ProtoServiceMethod, method_id: int,
+                                output: OutputFile) -> None:
+    """Generates a method descriptor for a raw RPC method."""
+
+    impl_method = f'&Implementation::{method.name()}'
+
+    output.write_line(
+        f'{RPC_NAMESPACE}::internal::GetRawMethodFor<{impl_method}, '
+        f'{method.type().cc_enum()}>(')
+    output.write_line(f'    0x{method_id:08x}),  // Hash of "{method.name()}"')
 
 
-def _user_args(method: ProtoServiceMethod) -> Iterable[str]:
-    if not method.client_streaming():
-        yield '::pw::ConstByteSpan request'
-
-    if method.server_streaming():
-        yield '::pw::Function<void(::pw::ConstByteSpan)>&& on_next = nullptr'
-        yield '::pw::Function<void(::pw::Status)>&& on_completed = nullptr'
-    else:
-        yield ('::pw::Function<void(::pw::ConstByteSpan, ::pw::Status)>&& '
-               'on_completed = nullptr')
-
-    yield '::pw::Function<void(::pw::Status)>&& on_error = nullptr'
+def _generate_server_writer_alias(output: OutputFile) -> None:
+    output.write_line(
+        f'using RawServerWriter = {RPC_NAMESPACE}::RawServerWriter;')
 
 
-class RawCodeGenerator(CodeGenerator):
-    """Generates an RPC service and client using the raw buffers API."""
-    def name(self) -> str:
-        return 'raw'
+def _generate_code_for_client(unused_service: ProtoService,
+                              unused_root: ProtoNode,
+                              output: OutputFile) -> None:
+    """Outputs client code for an RPC service."""
+    output.write_line('// Raw RPC clients are not yet implemented.\n')
 
-    def method_union_name(self) -> str:
-        return 'RawMethodUnion'
 
-    def includes(self, unused_proto_file_name: str) -> Iterable[str]:
-        yield '#include "pw_rpc/raw/client_reader_writer.h"'
-        yield '#include "pw_rpc/raw/internal/method_union.h"'
-        yield '#include "pw_rpc/raw/server_reader_writer.h"'
+def _generate_code_for_service(service: ProtoService, root: ProtoNode,
+                               output: OutputFile) -> None:
+    """Generates a C++ base class for a raw RPC service."""
+    codegen.service_class(service, root, output, _generate_server_writer_alias,
+                          'RawMethodUnion', _generate_method_descriptor)
 
-    def service_aliases(self) -> None:
-        self.line(f'using RawServerWriter = {RPC_NAMESPACE}::RawServerWriter;')
-        self.line(f'using RawServerReader = {RPC_NAMESPACE}::RawServerReader;')
-        self.line('using RawServerReaderWriter = '
-                  f'{RPC_NAMESPACE}::RawServerReaderWriter;')
 
-    def method_descriptor(self, method: ProtoServiceMethod) -> None:
-        impl_method = f'&Implementation::{method.name()}'
+def _generate_code_for_package(proto_file, package: ProtoNode,
+                               output: OutputFile) -> None:
+    """Generates code for a header file corresponding to a .proto file."""
+    includes = lambda *_: ['#include "pw_rpc/internal/raw_method_union.h"']
 
-        self.line(f'{RPC_NAMESPACE}::internal::GetRawMethodFor<{impl_method}, '
-                  f'{method.type().cc_enum()}>(')
-        self.line(f'    {get_id(method)}),  // Hash of "{method.name()}"')
-
-    def client_member_function(self, method: ProtoServiceMethod) -> None:
-        self.line(f'{_function(method)}(')
-        self.indented_list(*_user_args(method), end=') const {')
-
-        with self.indent():
-            base = 'Stream' if method.server_streaming() else 'Unary'
-            self.line(f'return {RPC_NAMESPACE}::internal::'
-                      f'{base}ResponseClientCall::'
-                      f'Start<{client_call_type(method, "Raw")}>(')
-
-            service_client = RPC_NAMESPACE + '::internal::ServiceClient'
-            arg = ['std::move(on_next)'] if method.server_streaming() else []
-
-            self.indented_list(
-                f'{service_client}::client()',
-                f'{service_client}::channel_id()',
-                'kServiceId',
-                get_id(method),
-                *arg,
-                'std::move(on_completed)',
-                'std::move(on_error)',
-                '{}' if method.client_streaming() else 'request',
-                end=');')
-
-        self.line('}')
-
-    def client_static_function(self, method: ProtoServiceMethod) -> None:
-        self.line(f'static {_function(method)}(')
-        self.indented_list(f'{RPC_NAMESPACE}::Client& client',
-                           'uint32_t channel_id',
-                           *_user_args(method),
-                           end=') {')
-
-        with self.indent():
-            self.line(f'return Client(client, channel_id).{method.name()}(')
-
-            args = []
-
-            if not method.client_streaming():
-                args.append('request')
-
-            if method.server_streaming():
-                args.append('std::move(on_next)')
-
-            self.indented_list(*args,
-                               'std::move(on_completed)',
-                               'std::move(on_error)',
-                               end=');')
-
-        self.line('}')
+    codegen.package(proto_file, package, output, includes,
+                    _generate_code_for_service, _generate_code_for_client)
 
 
 class StubGenerator(codegen.StubGenerator):
     def unary_signature(self, method: ProtoServiceMethod, prefix: str) -> str:
-        return (f'void {prefix}{method.name()}(pw::ConstByteSpan request, '
-                'pw::rpc::RawUnaryResponder& responder)')
+        return (f'pw::StatusWithSize {prefix}{method.name()}(ServerContext&, '
+                'pw::ConstByteSpan request, pw::ByteSpan response)')
 
     def unary_stub(self, method: ProtoServiceMethod,
                    output: OutputFile) -> None:
         output.write_line(codegen.STUB_REQUEST_TODO)
         output.write_line('static_cast<void>(request);')
         output.write_line(codegen.STUB_RESPONSE_TODO)
-        output.write_line('static_cast<void>(responder);')
+        output.write_line('static_cast<void>(response);')
+        output.write_line('return pw::StatusWithSize::Unimplemented();')
 
     def server_streaming_signature(self, method: ProtoServiceMethod,
                                    prefix: str) -> str:
 
-        return (f'void {prefix}{method.name()}('
+        return (f'void {prefix}{method.name()}(ServerContext&, '
                 'pw::ConstByteSpan request, RawServerWriter& writer)')
 
-    def client_streaming_signature(self, method: ProtoServiceMethod,
-                                   prefix: str) -> str:
-        return f'void {prefix}{method.name()}(RawServerReader& reader)'
-
-    def bidirectional_streaming_signature(self, method: ProtoServiceMethod,
-                                          prefix: str) -> str:
-        return (f'void {prefix}{method.name()}('
-                'RawServerReaderWriter& reader_writer)')
-
 
 def process_proto_file(proto_file) -> Iterable[OutputFile]:
     """Generates code for a single .proto file."""
 
     _, package_root = build_node_tree(proto_file)
     output_filename = _proto_filename_to_generated_header(proto_file.name)
+    output_file = OutputFile(output_filename)
+    _generate_code_for_package(proto_file, package_root, output_file)
 
-    generator = RawCodeGenerator(output_filename)
-    codegen.generate_package(proto_file, package_root, generator)
+    output_file.write_line()
+    codegen.package_stubs(package_root, output_file, StubGenerator())
 
-    codegen.package_stubs(package_root, generator, StubGenerator())
-
-    return [generator.output]
+    return [output_file]
diff --git a/pw_rpc/py/pw_rpc/console_tools/__init__.py b/pw_rpc/py/pw_rpc/console_tools/__init__.py
index 0732ac3..3f157f1 100644
--- a/pw_rpc/py/pw_rpc/console_tools/__init__.py
+++ b/pw_rpc/py/pw_rpc/console_tools/__init__.py
@@ -13,8 +13,6 @@
 # the License.
 """Utilities for building tools that interact with pw_rpc."""
 
-from pw_rpc.console_tools.console import (Context, CommandHelper, ClientInfo,
-                                          flattened_rpc_completions,
-                                          alias_deprecated_command)
+from pw_rpc.console_tools.console import Context, CommandHelper, ClientInfo
 from pw_rpc.console_tools.functions import help_as_repr
 from pw_rpc.console_tools.watchdog import Watchdog
diff --git a/pw_rpc/py/pw_rpc/console_tools/console.py b/pw_rpc/py/pw_rpc/console_tools/console.py
index 2dcdad6..29797f5 100644
--- a/pw_rpc/py/pw_rpc/console_tools/console.py
+++ b/pw_rpc/py/pw_rpc/console_tools/console.py
@@ -14,7 +14,6 @@
 """Utilities for creating an interactive console."""
 
 from collections import defaultdict
-import functools
 from itertools import chain
 import inspect
 import textwrap
@@ -109,41 +108,6 @@
     rpc_client: pw_rpc.Client
 
 
-def flattened_rpc_completions(
-    client_info_list: Collection[ClientInfo], ) -> Dict[str, str]:
-    """Create a flattened list of rpc commands for repl auto-completion.
-
-    This gathers all rpc commands from a set of ClientInfo variables and
-    produces a flattened list of valid rpc commands to run in an RPC
-    console. This is useful for passing into
-    prompt_toolkit.completion.WordCompleter.
-
-    Args:
-      client_info_list: List of ClientInfo variables
-
-    Returns:
-      Dict of flattened rpc commands as keys, and 'RPC' as values.
-      For example: ::
-
-        {
-            'device.rpcs.pw.rpc.EchoService.Echo': 'RPC,
-            'device.rpcs.pw.rpc.BatteryService.GetBatteryStatus': 'RPC',
-        }
-    """
-    rpc_list = list(
-        chain.from_iterable([
-            '{}.rpcs.{}'.format(c.name, a.full_name)
-            for a in c.rpc_client.methods()
-        ] for c in client_info_list))
-
-    # Dict should contain completion text as keys and descriptions as values.
-    custom_word_completions = {
-        flattened_rpc_name: 'RPC'
-        for flattened_rpc_name in rpc_list
-    }
-    return custom_word_completions
-
-
 class Context:
     """The Context class is used to set up an interactive RPC console.
 
@@ -217,10 +181,6 @@
         # Call set_target to set up for the default target.
         self.set_target(self.current_client)
 
-    def flattened_rpc_completions(self):
-        """Create a flattened list of rpc commands for repl auto-completion."""
-        return flattened_rpc_completions(self.client_info)
-
     def variables(self) -> Dict[str, Any]:
         """Returns a mapping of names to variables for use in an RPC console."""
         return self._variables
@@ -257,71 +217,3 @@
             self.protos.packages[package_name]._add_item(rpcs)  # pylint: disable=protected-access
 
         self.current_client = selected_client
-
-
-def _create_command_alias(command: Any, name: str, message: str) -> object:
-    """Wraps __call__, __getattr__, and __repr__ to print a message."""
-    @functools.wraps(command.__call__)
-    def print_message_and_call(_, *args, **kwargs):
-        print(message)
-        return command(*args, **kwargs)
-
-    def getattr_and_print_message(_, name: str) -> Any:
-        attr = getattr(command, name)
-        print(message)
-        return attr
-
-    return type(
-        name, (),
-        dict(__call__=print_message_and_call,
-             __getattr__=getattr_and_print_message,
-             __repr__=lambda _: message))()
-
-
-def _access_in_dict_or_namespace(item, name: str, create_if_missing: bool):
-    """Gets name as either a key or attribute on item."""
-    try:
-        return item[name]
-    except KeyError:
-        if create_if_missing:
-            try:
-                item[name] = types.SimpleNamespace()
-                return item[name]
-            except TypeError:
-                pass
-    except TypeError:
-        pass
-
-    if create_if_missing and not hasattr(item, name):
-        setattr(item, name, types.SimpleNamespace())
-
-    return getattr(item, name)
-
-
-def _access_names(item, names: Iterable[str], create_if_missing: bool):
-    for name in names:
-        item = _access_in_dict_or_namespace(item, name, create_if_missing)
-
-    return item
-
-
-def alias_deprecated_command(variables: Any, old_name: str,
-                             new_name: str) -> None:
-    """Adds an alias for an old command that redirects to the new command.
-
-    The deprecated command prints a message then invokes the new command.
-    """
-    # Get the new command.
-    item = _access_names(variables,
-                         new_name.split('.'),
-                         create_if_missing=False)
-
-    # Create a wrapper to the new comamnd with the old name.
-    wrapper = _create_command_alias(
-        item, old_name,
-        f'WARNING: {old_name} is DEPRECATED; use {new_name} instead')
-
-    # Add the wrapper to the variables with the old command's name.
-    name_parts = old_name.split('.')
-    item = _access_names(variables, name_parts[:-1], create_if_missing=True)
-    setattr(item, name_parts[-1], wrapper)
diff --git a/pw_rpc/py/pw_rpc/console_tools/watchdog.py b/pw_rpc/py/pw_rpc/console_tools/watchdog.py
index c4b9e3d..bcc9ffe 100644
--- a/pw_rpc/py/pw_rpc/console_tools/watchdog.py
+++ b/pw_rpc/py/pw_rpc/console_tools/watchdog.py
@@ -65,18 +65,13 @@
         self._watchdog.daemon = True
         self._watchdog.start()
 
-    def reset(self) -> bool:
-        """Resets the timeout; calls the on_reset callback if expired.
-
-        Returns True if was expired.
-        """
+    def reset(self) -> None:
+        """Resets the timeout; calls the on_reset callback if expired."""
         if self.expired:
             self.expired = False
             self._on_reset()
-            return True
 
         self.start()
-        return False
 
     def _timeout_expired(self) -> None:
         if self.expired:
diff --git a/pw_rpc/py/pw_rpc/descriptors.py b/pw_rpc/py/pw_rpc/descriptors.py
index 57ba984..a771b8f 100644
--- a/pw_rpc/py/pw_rpc/descriptors.py
+++ b/pw_rpc/py/pw_rpc/descriptors.py
@@ -13,12 +13,11 @@
 # the License.
 """Types representing the basic pw_rpc concepts: channel, service, method."""
 
-import abc
 from dataclasses import dataclass
 import enum
 from inspect import Parameter
 from typing import (Any, Callable, Collection, Dict, Generic, Iterable,
-                    Iterator, Optional, Tuple, TypeVar, Union)
+                    Iterator, Tuple, TypeVar, Union)
 
 from google.protobuf import descriptor_pb2, message_factory
 from google.protobuf.descriptor import (FieldDescriptor, MethodDescriptor,
@@ -38,57 +37,6 @@
         return f'Channel({self.id})'
 
 
-class ChannelManipulator(abc.ABC):
-    """A a pipe interface that may manipulate packets before they're sent.
-
-    ``ChannelManipulator``s allow application-specific packet handling to be
-    injected into the packet processing pipeline for an ingress or egress
-    channel-like pathway. This is particularly useful for integration testing
-    resilience to things like packet loss on a usually-reliable transport. RPC
-    server integrations (e.g. ``HdlcRpcLocalServerAndClient``) may provide an
-    opportunity to inject a ``ChannelManipulator`` for this use case.
-
-    A ``ChannelManipulator`` should not modify send_packet, as the consumer of a
-    ``ChannelManipulator`` will use ``send_packet`` to insert the provided
-    ``ChannelManipulator`` into a packet processing path.
-
-    For example:
-
-    .. code-block:: python
-
-      class PacketLogger(ChannelManipulator):
-          def process_and_send(self, packet: bytes) -> None:
-              _LOG.debug('Received packet with payload: %s', str(packet))
-              self.send_packet(packet)
-
-
-      packet_logger = PacketLogger()
-
-      # Configure actual send command.
-      packet_logger.send_packet = socket.sendall
-
-      # Route the output channel through the PacketLogger channel manipulator.
-      channels = tuple(Channel(_DEFAULT_CHANNEL, packet_logger))
-
-      # Create a RPC client.
-      client = HdlcRpcClient(socket.read, protos, channels, stdout)
-    """
-    def __init__(self):
-        self.send_packet: Callable[[bytes], Any] = lambda _: None
-
-    @abc.abstractmethod
-    def process_and_send(self, packet: bytes) -> None:
-        """Processes an incoming packet before optionally sending it.
-
-        Implementations of this method may send the processed packet, multiple
-        packets, or no packets at all via the registered `send_packet()`
-        handler.
-        """
-
-    def __call__(self, data: bytes) -> None:
-        self.process_and_send(data)
-
-
 @dataclass(frozen=True, eq=False)
 class Service:
     """Describes an RPC service."""
@@ -265,17 +213,13 @@
 
         return self.Type.UNARY
 
-    def get_request(self, proto: Optional[Message],
-                    proto_kwargs: Optional[Dict[str, Any]]) -> Message:
+    def get_request(self, proto, proto_kwargs: Dict[str, Any]):
         """Returns a request_type protobuf message.
 
         The client implementation may use this to support providing a request
         as either a message object or as keyword arguments for the message's
         fields (but not both).
         """
-        if proto_kwargs is None:
-            proto_kwargs = {}
-
         if proto and proto_kwargs:
             proto_str = repr(proto).strip() or "''"
             raise TypeError(
diff --git a/pw_rpc/py/pw_rpc/packets.py b/pw_rpc/py/pw_rpc/packets.py
index d3e25b7..3f15468 100644
--- a/pw_rpc/py/pw_rpc/packets.py
+++ b/pw_rpc/py/pw_rpc/packets.py
@@ -13,15 +13,13 @@
 # the License.
 """Functions for working with pw_rpc packets."""
 
-from typing import Optional
-
 from google.protobuf import message
 from pw_status import Status
 
 from pw_rpc.internal import packet_pb2
 
 
-def decode(data: bytes) -> packet_pb2.RpcPacket:
+def decode(data: bytes):
     packet = packet_pb2.RpcPacket()
     packet.MergeFromString(data)
     return packet
@@ -37,15 +35,15 @@
     return tuple(item if isinstance(item, int) else item.id for item in rpc)
 
 
-def encode_request(rpc: tuple, request: Optional[message.Message]) -> bytes:
+def encode_request(rpc: tuple, request: message.Message) -> bytes:
     channel, service, method = _ids(rpc)
-    payload = request.SerializeToString() if request is not None else bytes()
 
-    return packet_pb2.RpcPacket(type=packet_pb2.PacketType.REQUEST,
-                                channel_id=channel,
-                                service_id=service,
-                                method_id=method,
-                                payload=payload).SerializeToString()
+    return packet_pb2.RpcPacket(
+        type=packet_pb2.PacketType.REQUEST,
+        channel_id=channel,
+        service_id=service,
+        method_id=method,
+        payload=request.SerializeToString()).SerializeToString()
 
 
 def encode_response(rpc: tuple, response: message.Message) -> bytes:
@@ -59,18 +57,7 @@
         payload=response.SerializeToString()).SerializeToString()
 
 
-def encode_client_stream(rpc: tuple, request: message.Message) -> bytes:
-    channel, service, method = _ids(rpc)
-
-    return packet_pb2.RpcPacket(
-        type=packet_pb2.PacketType.CLIENT_STREAM,
-        channel_id=channel,
-        service_id=service,
-        method_id=method,
-        payload=request.SerializeToString()).SerializeToString()
-
-
-def encode_client_error(packet: packet_pb2.RpcPacket, status: Status) -> bytes:
+def encode_client_error(packet, status: Status) -> bytes:
     return packet_pb2.RpcPacket(type=packet_pb2.PacketType.CLIENT_ERROR,
                                 channel_id=packet.channel_id,
                                 service_id=packet.service_id,
@@ -80,21 +67,12 @@
 
 def encode_cancel(rpc: tuple) -> bytes:
     channel, service, method = _ids(rpc)
-    return packet_pb2.RpcPacket(type=packet_pb2.PacketType.CLIENT_ERROR,
-                                status=Status.CANCELLED.value,
-                                channel_id=channel,
-                                service_id=service,
-                                method_id=method).SerializeToString()
+    return packet_pb2.RpcPacket(
+        type=packet_pb2.PacketType.CANCEL_SERVER_STREAM,
+        channel_id=channel,
+        service_id=service,
+        method_id=method).SerializeToString()
 
 
-def encode_client_stream_end(rpc: tuple) -> bytes:
-    channel, service, method = _ids(rpc)
-
-    return packet_pb2.RpcPacket(type=packet_pb2.PacketType.CLIENT_STREAM_END,
-                                channel_id=channel,
-                                service_id=service,
-                                method_id=method).SerializeToString()
-
-
-def for_server(packet: packet_pb2.RpcPacket) -> bool:
+def for_server(packet):
     return packet.type % 2 == 0
diff --git a/pw_rpc/py/pw_rpc/plugin.py b/pw_rpc/py/pw_rpc/plugin.py
index 30d6160..44337d9 100644
--- a/pw_rpc/py/pw_rpc/plugin.py
+++ b/pw_rpc/py/pw_rpc/plugin.py
@@ -16,10 +16,10 @@
 import enum
 import sys
 
-from google.protobuf.compiler import plugin_pb2
+import google.protobuf.compiler.plugin_pb2 as plugin_pb2
 
-from pw_rpc import codegen_nanopb
-from pw_rpc import codegen_raw
+import pw_rpc.codegen_nanopb as codegen_nanopb
+import pw_rpc.codegen_raw as codegen_raw
 
 
 class Codegen(enum.Enum):
diff --git a/pw_rpc/py/pw_rpc/testing.py b/pw_rpc/py/pw_rpc/testing.py
deleted file mode 100644
index a9ab4d3..0000000
--- a/pw_rpc/py/pw_rpc/testing.py
+++ /dev/null
@@ -1,111 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Utilities for testing pw_rpc."""
-
-import argparse
-import subprocess
-import sys
-import tempfile
-import time
-from typing import Optional, Sequence
-
-TEMP_DIR_MARKER = '(pw_rpc:CREATE_TEMP_DIR)'
-
-
-def parse_test_server_args(
-        parser: argparse.ArgumentParser = None) -> argparse.Namespace:
-    """Parses arguments for running a Python-based integration test."""
-    if parser is None:
-        parser = argparse.ArgumentParser(
-            description=sys.modules['__main__'].__doc__)
-
-    parser.add_argument('--test-server-command',
-                        nargs='+',
-                        required=True,
-                        help='Command that starts the test server.')
-    parser.add_argument(
-        '--port',
-        type=int,
-        required=True,
-        help=('The port to use to connect to the test server. This value is '
-              'passed to the test server as the last argument.'))
-    parser.add_argument('unittest_args',
-                        nargs=argparse.REMAINDER,
-                        help='Arguments after "--" are passed to unittest.')
-
-    args = parser.parse_args()
-
-    # Append the port number to the test server command.
-    args.test_server_command.append(str(args.port))
-
-    # Make the script name argv[0] and drop the "--".
-    args.unittest_args = sys.argv[:1] + args.unittest_args[1:]
-
-    return args
-
-
-def _parse_subprocess_integration_test_args() -> argparse.Namespace:
-    parser = argparse.ArgumentParser(
-        description='Executes a test between two subprocesses')
-    parser.add_argument('--client', required=True, help='Client binary to run')
-    parser.add_argument('--server', required=True, help='Server binary to run')
-    parser.add_argument(
-        'common_args',
-        metavar='-- ...',
-        nargs=argparse.REMAINDER,
-        help=('Arguments to pass to both the server and client; '
-              f'pass {TEMP_DIR_MARKER} to generate a temporary directory'))
-
-    args = parser.parse_args()
-
-    if not args.common_args or args.common_args[0] != '--':
-        parser.error('The common arguments must start with "--"')
-
-    args.common_args.pop(0)
-
-    return args
-
-
-def execute_integration_test(server: str,
-                             client: str,
-                             common_args: Sequence[str],
-                             setup_time_s: float = 0.2) -> int:
-    temp_dir: Optional[tempfile.TemporaryDirectory] = None
-
-    if TEMP_DIR_MARKER in common_args:
-        temp_dir = tempfile.TemporaryDirectory(prefix='pw_rpc_test_')
-        common_args = [
-            temp_dir.name if a == TEMP_DIR_MARKER else a for a in common_args
-        ]
-
-    try:
-        server_process = subprocess.Popen([server, *common_args])
-        # TODO(pwbug/508): Replace this delay with some sort of IPC.
-        time.sleep(setup_time_s)
-
-        result = subprocess.run([client, *common_args]).returncode
-
-        server_process.terminate()
-        server_process.communicate()
-    finally:
-        if temp_dir:
-            temp_dir.cleanup()
-
-    return result
-
-
-if __name__ == '__main__':
-    sys.exit(
-        execute_integration_test(
-            **vars(_parse_subprocess_integration_test_args())))
diff --git a/pw_rpc/py/tests/callback_client_test.py b/pw_rpc/py/tests/callback_client_test.py
index b06939f..ed7ec9c 100755
--- a/pw_rpc/py/tests/callback_client_test.py
+++ b/pw_rpc/py/tests/callback_client_test.py
@@ -16,7 +16,7 @@
 
 import unittest
 from unittest import mock
-from typing import Any, List, Optional, Tuple
+from typing import List, Tuple
 
 from pw_protobuf_compiler import python_protos
 from pw_status import Status
@@ -53,39 +53,34 @@
 """
 
 
-def _message_bytes(msg) -> bytes:
-    return msg if isinstance(msg, bytes) else msg.SerializeToString()
+def _rpc(method_stub):
+    return client.PendingRpc(method_stub.channel, method_stub.method.service,
+                             method_stub.method)
 
 
-class _CallbackClientImplTestBase(unittest.TestCase):
-    """Supports writing tests that require responses from an RPC server."""
-    def setUp(self) -> None:
+class CallbackClientImplTest(unittest.TestCase):
+    """Tests the callback_client as used within a pw_rpc Client."""
+    def setUp(self):
         self._protos = python_protos.Library.from_strings(TEST_PROTO_1)
         self._request = self._protos.packages.pw.test1.SomeMessage
 
         self._client = client.Client.from_modules(
-            callback_client.Impl(), [client.Channel(1, self._handle_packet)],
+            callback_client.Impl(), [client.Channel(1, self._handle_request)],
             self._protos.modules())
         self._service = self._client.channel(1).rpcs.pw.test1.PublicService
 
-        self.requests: List[packet_pb2.RpcPacket] = []
+        self._last_request: packet_pb2.RpcPacket = None
         self._next_packets: List[Tuple[bytes, Status]] = []
-        self.send_responses_after_packets: float = 1
-
-        self.output_exception: Optional[Exception] = None
-
-    def last_request(self) -> packet_pb2.RpcPacket:
-        assert self.requests
-        return self.requests[-1]
+        self._send_responses_on_request = True
 
     def _enqueue_response(self,
                           channel_id: int,
                           method=None,
                           status: Status = Status.OK,
-                          payload=b'',
+                          response=b'',
                           *,
                           ids: Tuple[int, int] = None,
-                          process_status=Status.OK) -> None:
+                          process_status=Status.OK):
         if method:
             assert ids is None
             service_id, method_id = method.service.id, method.id
@@ -93,80 +88,156 @@
             assert ids is not None and method is None
             service_id, method_id = ids
 
-        self._next_packets.append((packet_pb2.RpcPacket(
-            type=packet_pb2.PacketType.RESPONSE,
-            channel_id=channel_id,
-            service_id=service_id,
-            method_id=method_id,
-            status=status.value,
-            payload=_message_bytes(payload)).SerializeToString(),
-                                   process_status))
+        if isinstance(response, bytes):
+            payload = response
+        else:
+            payload = response.SerializeToString()
 
-    def _enqueue_server_stream(self,
-                               channel_id: int,
-                               method,
-                               response,
-                               process_status=Status.OK) -> None:
-        self._next_packets.append((packet_pb2.RpcPacket(
-            type=packet_pb2.PacketType.SERVER_STREAM,
-            channel_id=channel_id,
-            service_id=method.service.id,
-            method_id=method.id,
-            payload=_message_bytes(response)).SerializeToString(),
-                                   process_status))
+        self._next_packets.append(
+            (packet_pb2.RpcPacket(type=packet_pb2.PacketType.RESPONSE,
+                                  channel_id=channel_id,
+                                  service_id=service_id,
+                                  method_id=method_id,
+                                  status=status.value,
+                                  payload=payload).SerializeToString(),
+             process_status))
+
+    def _enqueue_stream_end(self,
+                            channel_id: int,
+                            method,
+                            status: Status = Status.OK,
+                            process_status=Status.OK):
+        self._next_packets.append(
+            (packet_pb2.RpcPacket(type=packet_pb2.PacketType.SERVER_STREAM_END,
+                                  channel_id=channel_id,
+                                  service_id=method.service.id,
+                                  method_id=method.id,
+                                  status=status.value).SerializeToString(),
+             process_status))
 
     def _enqueue_error(self,
                        channel_id: int,
-                       service,
                        method,
                        status: Status,
-                       process_status=Status.OK) -> None:
-        self._next_packets.append((packet_pb2.RpcPacket(
-            type=packet_pb2.PacketType.SERVER_ERROR,
-            channel_id=channel_id,
-            service_id=service if isinstance(service, int) else service.id,
-            method_id=method if isinstance(method, int) else method.id,
-            status=status.value).SerializeToString(), process_status))
+                       process_status=Status.OK):
+        self._next_packets.append(
+            (packet_pb2.RpcPacket(type=packet_pb2.PacketType.SERVER_ERROR,
+                                  channel_id=channel_id,
+                                  service_id=method.service.id,
+                                  method_id=method.id,
+                                  status=status.value).SerializeToString(),
+             process_status))
 
-    def _handle_packet(self, data: bytes) -> None:
-        if self.output_exception:
-            raise self.output_exception  # pylint: disable=raising-bad-type
-
-        self.requests.append(packets.decode(data))
-
-        if self.send_responses_after_packets > 1:
-            self.send_responses_after_packets -= 1
+    def _handle_request(self, data: bytes):
+        # Disable this method to prevent infinite recursion if processing the
+        # packet happens to send another packet.
+        if not self._send_responses_on_request:
             return
 
-        self._process_enqueued_packets()
+        self._send_responses_on_request = False
 
-    def _process_enqueued_packets(self) -> None:
-        # Set send_responses_after_packets to infinity to prevent potential
-        # infinite recursion when a packet causes another packet to send.
-        send_after_count = self.send_responses_after_packets
-        self.send_responses_after_packets = float('inf')
+        self._last_request = packets.decode(data)
 
         for packet, status in self._next_packets:
             self.assertIs(status, self._client.process_packet(packet))
 
         self._next_packets.clear()
-        self.send_responses_after_packets = send_after_count
+        self._send_responses_on_request = True
 
-    def _sent_payload(self, message_type: type) -> Any:
+    def _sent_payload(self, message_type):
+        self.assertIsNotNone(self._last_request)
         message = message_type()
-        message.ParseFromString(self.last_request().payload)
+        message.ParseFromString(self._last_request.payload)
         return message
 
+    def test_invoke_unary_rpc(self):
+        method = self._service.SomeUnary.method
 
-class CallbackClientImplTest(_CallbackClientImplTestBase):
-    """Tests the callback_client.Impl client implementation."""
-    def test_callback_exceptions_suppressed(self) -> None:
+        for _ in range(3):
+            self._enqueue_response(1, method, Status.ABORTED,
+                                   method.response_type(payload='0_o'))
+
+            status, response = self._service.SomeUnary(
+                method.request_type(magic_number=6))
+
+            self.assertEqual(
+                6,
+                self._sent_payload(method.request_type).magic_number)
+
+            self.assertIs(Status.ABORTED, status)
+            self.assertEqual('0_o', response.payload)
+
+    def test_invoke_unary_rpc_keep_open(self) -> None:
+        method = self._service.SomeUnary.method
+
+        payload_1 = method.response_type(payload='-_-')
+        payload_2 = method.response_type(payload='0_o')
+
+        self._enqueue_response(1, method, Status.ABORTED, payload_1)
+
+        replies: list = []
+        enqueue_replies = lambda _, reply: replies.append(reply)
+
+        self._service.SomeUnary.invoke(method.request_type(magic_number=6),
+                                       enqueue_replies,
+                                       enqueue_replies,
+                                       keep_open=True)
+
+        self.assertEqual([payload_1, Status.ABORTED], replies)
+
+        # Send another packet and make sure it is processed even though the RPC
+        # terminated.
+        self._client.process_packet(
+            packet_pb2.RpcPacket(
+                type=packet_pb2.PacketType.RESPONSE,
+                channel_id=1,
+                service_id=method.service.id,
+                method_id=method.id,
+                status=Status.OK.value,
+                payload=payload_2.SerializeToString()).SerializeToString())
+
+        self.assertEqual([payload_1, Status.ABORTED, payload_2, Status.OK],
+                         replies)
+
+    def test_invoke_unary_rpc_with_callback(self):
+        method = self._service.SomeUnary.method
+
+        for _ in range(3):
+            self._enqueue_response(1, method, Status.ABORTED,
+                                   method.response_type(payload='0_o'))
+
+            callback = mock.Mock()
+            self._service.SomeUnary.invoke(self._request(magic_number=5),
+                                           callback, callback)
+
+            callback.assert_has_calls([
+                mock.call(_rpc(self._service.SomeUnary),
+                          method.response_type(payload='0_o')),
+                mock.call(_rpc(self._service.SomeUnary), Status.ABORTED)
+            ])
+
+            self.assertEqual(
+                5,
+                self._sent_payload(method.request_type).magic_number)
+
+    def test_unary_rpc_server_error(self):
+        method = self._service.SomeUnary.method
+
+        for _ in range(3):
+            self._enqueue_error(1, method, Status.NOT_FOUND)
+
+            with self.assertRaises(callback_client.RpcError) as context:
+                self._service.SomeUnary(method.request_type(magic_number=6))
+
+            self.assertIs(context.exception.status, Status.NOT_FOUND)
+
+    def test_invoke_unary_rpc_callback_exceptions_suppressed(self):
         stub = self._service.SomeUnary
 
         self._enqueue_response(1, stub.method)
         exception_msg = 'YOU BROKE IT O-]-<'
 
-        with self.assertLogs(callback_client.__package__, 'ERROR') as logs:
+        with self.assertLogs(callback_client.__name__, 'ERROR') as logs:
             stub.invoke(self._request(),
                         mock.Mock(side_effect=Exception(exception_msg)))
 
@@ -177,7 +248,114 @@
         status, _ = stub()
         self.assertIs(status, Status.UNKNOWN)
 
-    def test_ignore_bad_packets_with_pending_rpc(self) -> None:
+    def test_invoke_unary_rpc_with_callback_cancel(self):
+        callback = mock.Mock()
+
+        for _ in range(3):
+            call = self._service.SomeUnary.invoke(
+                self._request(magic_number=55), callback)
+
+            self.assertIsNotNone(self._last_request)
+            self._last_request = None
+
+            # Try to invoke the RPC again before cancelling, without overriding
+            # pending RPCs.
+            with self.assertRaises(client.Error):
+                self._service.SomeUnary.invoke(self._request(magic_number=56),
+                                               callback,
+                                               override_pending=False)
+
+            self.assertTrue(call.cancel())
+            self.assertFalse(call.cancel())  # Already cancelled, returns False
+
+            # Unary RPCs do not send a cancel request to the server.
+            self.assertIsNone(self._last_request)
+
+        callback.assert_not_called()
+
+    def test_reinvoke_unary_rpc(self):
+        for _ in range(3):
+            self._last_request = None
+            self._service.SomeUnary.invoke(self._request(magic_number=55),
+                                           override_pending=True)
+            self.assertEqual(self._last_request.type,
+                             packet_pb2.PacketType.REQUEST)
+
+    def test_invoke_server_streaming(self):
+        method = self._service.SomeServerStreaming.method
+
+        rep1 = method.response_type(payload='!!!')
+        rep2 = method.response_type(payload='?')
+
+        for _ in range(3):
+            self._enqueue_response(1, method, response=rep1)
+            self._enqueue_response(1, method, response=rep2)
+            self._enqueue_stream_end(1, method, Status.ABORTED)
+
+            self.assertEqual(
+                [rep1, rep2],
+                list(self._service.SomeServerStreaming(magic_number=4)))
+
+            self.assertEqual(
+                4,
+                self._sent_payload(method.request_type).magic_number)
+
+    def test_invoke_server_streaming_with_callbacks(self):
+        method = self._service.SomeServerStreaming.method
+
+        rep1 = method.response_type(payload='!!!')
+        rep2 = method.response_type(payload='?')
+
+        for _ in range(3):
+            self._enqueue_response(1, method, response=rep1)
+            self._enqueue_response(1, method, response=rep2)
+            self._enqueue_stream_end(1, method, Status.ABORTED)
+
+            callback = mock.Mock()
+            self._service.SomeServerStreaming.invoke(
+                self._request(magic_number=3), callback, callback)
+
+            rpc = _rpc(self._service.SomeServerStreaming)
+            callback.assert_has_calls([
+                mock.call(rpc, method.response_type(payload='!!!')),
+                mock.call(rpc, method.response_type(payload='?')),
+                mock.call(rpc, Status.ABORTED),
+            ])
+
+            self.assertEqual(
+                3,
+                self._sent_payload(method.request_type).magic_number)
+
+    def test_invoke_server_streaming_with_callback_cancel(self):
+        stub = self._service.SomeServerStreaming
+
+        resp = stub.method.response_type(payload='!!!')
+        self._enqueue_response(1, stub.method, response=resp)
+
+        callback = mock.Mock()
+        call = stub.invoke(self._request(magic_number=3), callback)
+        callback.assert_called_once_with(
+            _rpc(stub), stub.method.response_type(payload='!!!'))
+
+        callback.reset_mock()
+
+        call.cancel()
+
+        self.assertEqual(self._last_request.type,
+                         packet_pb2.PacketType.CANCEL_SERVER_STREAM)
+
+        # Ensure the RPC can be called after being cancelled.
+        self._enqueue_response(1, stub.method, response=resp)
+        self._enqueue_stream_end(1, stub.method, Status.OK)
+
+        call = stub.invoke(self._request(magic_number=3), callback, callback)
+
+        callback.assert_has_calls([
+            mock.call(_rpc(stub), stub.method.response_type(payload='!!!')),
+            mock.call(_rpc(stub), Status.OK),
+        ])
+
+    def test_ignore_bad_packets_with_pending_rpc(self):
         method = self._service.SomeUnary.method
         service_id = method.service.id
 
@@ -203,30 +381,7 @@
         self.assertIs(Status.OK, status)
         self.assertEqual('', response.payload)
 
-    def test_server_error_for_unknown_call_sends_no_errors(self) -> None:
-        method = self._service.SomeUnary.method
-        service_id = method.service.id
-
-        # Unknown channel
-        self._enqueue_error(999,
-                            service_id,
-                            method,
-                            Status.NOT_FOUND,
-                            process_status=Status.NOT_FOUND)
-        # Bad service
-        self._enqueue_error(1, 999, method.id, Status.INVALID_ARGUMENT)
-        # Bad method
-        self._enqueue_error(1, service_id, 999, Status.INVALID_ARGUMENT)
-        # For RPC not pending
-        self._enqueue_error(1, service_id,
-                            self._service.SomeBidiStreaming.method.id,
-                            Status.NOT_FOUND)
-
-        self._process_enqueued_packets()
-
-        self.assertEqual(self.requests, [])
-
-    def test_exception_if_payload_fails_to_decode(self) -> None:
+    def test_pass_none_if_payload_fails_to_decode(self):
         method = self._service.SomeUnary.method
 
         self._enqueue_response(1,
@@ -235,22 +390,21 @@
                                b'INVALID DATA!!!',
                                process_status=Status.OK)
 
-        with self.assertRaises(callback_client.RpcError) as context:
-            self._service.SomeUnary(magic_number=6)
+        status, response = self._service.SomeUnary(magic_number=6)
+        self.assertIs(status, Status.OK)
+        self.assertIsNone(response)
 
-        self.assertIs(context.exception.status, Status.DATA_LOSS)
-
-    def test_rpc_help_contains_method_name(self) -> None:
+    def test_rpc_help_contains_method_name(self):
         rpc = self._service.SomeUnary
         self.assertIn(rpc.method.full_name, rpc.help())
 
-    def test_default_timeouts_set_on_impl(self) -> None:
+    def test_default_timeouts_set_on_impl(self):
         impl = callback_client.Impl(None, 1.5)
 
         self.assertEqual(impl.default_unary_timeout_s, None)
         self.assertEqual(impl.default_stream_timeout_s, 1.5)
 
-    def test_default_timeouts_set_for_all_rpcs(self) -> None:
+    def test_default_timeouts_set_for_all_rpcs(self):
         rpc_client = client.Client.from_modules(callback_client.Impl(
             99, 100), [client.Channel(1, lambda *a, **b: None)],
                                                 self._protos.modules())
@@ -261,709 +415,28 @@
         self.assertEqual(
             rpcs.pw.test1.PublicService.SomeServerStreaming.default_timeout_s,
             100)
-        self.assertEqual(
-            rpcs.pw.test1.PublicService.SomeClientStreaming.default_timeout_s,
-            99)
-        self.assertEqual(
-            rpcs.pw.test1.PublicService.SomeBidiStreaming.default_timeout_s,
-            100)
 
-    def test_rpc_provides_request_type(self) -> None:
-        self.assertIs(self._service.SomeUnary.request,
-                      self._service.SomeUnary.method.request_type)
-
-    def test_rpc_provides_response_type(self) -> None:
-        self.assertIs(self._service.SomeUnary.request,
-                      self._service.SomeUnary.method.request_type)
-
-
-class UnaryTest(_CallbackClientImplTestBase):
-    """Tests for invoking a unary RPC."""
-    def setUp(self) -> None:
-        super().setUp()
-        self.rpc = self._service.SomeUnary
-        self.method = self.rpc.method
-
-    def test_blocking_call(self) -> None:
-        for _ in range(3):
-            self._enqueue_response(1, self.method, Status.ABORTED,
-                                   self.method.response_type(payload='0_o'))
-
-            status, response = self._service.SomeUnary(
-                self.method.request_type(magic_number=6))
-
-            self.assertEqual(
-                6,
-                self._sent_payload(self.method.request_type).magic_number)
-
-            self.assertIs(Status.ABORTED, status)
-            self.assertEqual('0_o', response.payload)
-
-    def test_nonblocking_call(self) -> None:
-        for _ in range(3):
-            self._enqueue_response(1, self.method, Status.ABORTED,
-                                   self.method.response_type(payload='0_o'))
-
-            callback = mock.Mock()
-            call = self.rpc.invoke(self._request(magic_number=5), callback,
-                                   callback)
-
-            callback.assert_has_calls([
-                mock.call(call, self.method.response_type(payload='0_o')),
-                mock.call(call, Status.ABORTED)
-            ])
-
-            self.assertEqual(
-                5,
-                self._sent_payload(self.method.request_type).magic_number)
-
-    def test_open(self) -> None:
-        self.output_exception = IOError('something went wrong sending!')
-
-        for _ in range(3):
-            self._enqueue_response(1, self.method, Status.ABORTED,
-                                   self.method.response_type(payload='0_o'))
-
-            callback = mock.Mock()
-            call = self.rpc.open(self._request(magic_number=5), callback,
-                                 callback)
-            self.assertEqual(self.requests, [])
-
-            self._process_enqueued_packets()
-
-            callback.assert_has_calls([
-                mock.call(call, self.method.response_type(payload='0_o')),
-                mock.call(call, Status.ABORTED)
-            ])
-
-    def test_blocking_server_error(self) -> None:
-        for _ in range(3):
-            self._enqueue_error(1, self.method.service, self.method,
-                                Status.NOT_FOUND)
-
-            with self.assertRaises(callback_client.RpcError) as context:
-                self._service.SomeUnary(
-                    self.method.request_type(magic_number=6))
-
-            self.assertIs(context.exception.status, Status.NOT_FOUND)
-
-    def test_nonblocking_cancel(self) -> None:
-        callback = mock.Mock()
-
-        for _ in range(3):
-            call = self._service.SomeUnary.invoke(
-                self._request(magic_number=55), callback)
-
-            self.assertGreater(len(self.requests), 0)
-            self.requests.clear()
-
-            self.assertTrue(call.cancel())
-            self.assertFalse(call.cancel())  # Already cancelled, returns False
-
-            # Unary RPCs do not send a cancel request to the server.
-            self.assertFalse(self.requests)
-
-        callback.assert_not_called()
-
-    def test_nonblocking_with_request_args(self) -> None:
-        self.rpc.invoke(request_args=dict(magic_number=1138))
-        self.assertEqual(
-            self._sent_payload(self.rpc.request).magic_number, 1138)
-
-    def test_blocking_timeout_as_argument(self) -> None:
+    def test_timeout_unary(self):
         with self.assertRaises(callback_client.RpcTimeout):
             self._service.SomeUnary(pw_rpc_timeout_s=0.0001)
 
-    def test_blocking_timeout_set_default(self) -> None:
+    def test_timeout_unary_set_default(self):
         self._service.SomeUnary.default_timeout_s = 0.0001
 
         with self.assertRaises(callback_client.RpcTimeout):
             self._service.SomeUnary()
 
-    def test_nonblocking_duplicate_calls_first_is_cancelled(self) -> None:
-        first_call = self.rpc.invoke()
-        self.assertFalse(first_call.completed())
-
-        second_call = self.rpc.invoke()
-
-        self.assertIs(first_call.error, Status.CANCELLED)
-        self.assertFalse(second_call.completed())
-
-    def test_nonblocking_exception_in_callback(self) -> None:
-        exception = ValueError('something went wrong!')
-
-        self._enqueue_response(1, self.method, Status.OK)
-
-        call = self.rpc.invoke(on_completed=mock.Mock(side_effect=exception))
-
-        with self.assertRaises(RuntimeError) as context:
-            call.wait()
-
-        self.assertEqual(context.exception.__cause__, exception)
-
-
-class ServerStreamingTest(_CallbackClientImplTestBase):
-    """Tests for server streaming RPCs."""
-    def setUp(self) -> None:
-        super().setUp()
-        self.rpc = self._service.SomeServerStreaming
-        self.method = self.rpc.method
-
-    def test_blocking_call(self) -> None:
-        rep1 = self.method.response_type(payload='!!!')
-        rep2 = self.method.response_type(payload='?')
-
-        for _ in range(3):
-            self._enqueue_server_stream(1, self.method, rep1)
-            self._enqueue_server_stream(1, self.method, rep2)
-            self._enqueue_response(1, self.method, Status.ABORTED)
-
-            self.assertEqual(
-                [rep1, rep2],
-                self._service.SomeServerStreaming(magic_number=4).responses)
-
-            self.assertEqual(
-                4,
-                self._sent_payload(self.method.request_type).magic_number)
-
-    def test_deprecated_packet_format(self) -> None:
-        rep1 = self.method.response_type(payload='!!!')
-        rep2 = self.method.response_type(payload='?')
-
-        for _ in range(3):
-            # The original packet format used RESPONSE packets for the server
-            # stream and a SERVER_STREAM_END packet as the last packet. These
-            # are converted to SERVER_STREAM packets followed by a RESPONSE.
-            self._enqueue_response(1, self.method, payload=rep1)
-            self._enqueue_response(1, self.method, payload=rep2)
-
-            self._next_packets.append((packet_pb2.RpcPacket(
-                type=packet_pb2.PacketType.DEPRECATED_SERVER_STREAM_END,
-                channel_id=1,
-                service_id=self.method.service.id,
-                method_id=self.method.id,
-                status=Status.INVALID_ARGUMENT.value).SerializeToString(),
-                                       Status.OK))
-
-            status, replies = self._service.SomeServerStreaming(magic_number=4)
-            self.assertEqual([rep1, rep2], replies)
-            self.assertIs(status, Status.INVALID_ARGUMENT)
-
-            self.assertEqual(
-                4,
-                self._sent_payload(self.method.request_type).magic_number)
-
-    def test_nonblocking_call(self) -> None:
-        rep1 = self.method.response_type(payload='!!!')
-        rep2 = self.method.response_type(payload='?')
-
-        for _ in range(3):
-            self._enqueue_server_stream(1, self.method, rep1)
-            self._enqueue_server_stream(1, self.method, rep2)
-            self._enqueue_response(1, self.method, Status.ABORTED)
-
-            callback = mock.Mock()
-            call = self.rpc.invoke(self._request(magic_number=3), callback,
-                                   callback)
-
-            callback.assert_has_calls([
-                mock.call(call, self.method.response_type(payload='!!!')),
-                mock.call(call, self.method.response_type(payload='?')),
-                mock.call(call, Status.ABORTED),
-            ])
-
-            self.assertEqual(
-                3,
-                self._sent_payload(self.method.request_type).magic_number)
-
-    def test_open(self) -> None:
-        self.output_exception = IOError('something went wrong sending!')
-        rep1 = self.method.response_type(payload='!!!')
-        rep2 = self.method.response_type(payload='?')
-
-        for _ in range(3):
-            self._enqueue_server_stream(1, self.method, rep1)
-            self._enqueue_server_stream(1, self.method, rep2)
-            self._enqueue_response(1, self.method, Status.ABORTED)
-
-            callback = mock.Mock()
-            call = self.rpc.open(self._request(magic_number=3), callback,
-                                 callback)
-            self.assertEqual(self.requests, [])
-
-            self._process_enqueued_packets()
-
-            callback.assert_has_calls([
-                mock.call(call, self.method.response_type(payload='!!!')),
-                mock.call(call, self.method.response_type(payload='?')),
-                mock.call(call, Status.ABORTED),
-            ])
-
-    def test_nonblocking_cancel(self) -> None:
-        resp = self.rpc.method.response_type(payload='!!!')
-        self._enqueue_server_stream(1, self.rpc.method, resp)
-
-        callback = mock.Mock()
-        call = self.rpc.invoke(self._request(magic_number=3), callback)
-        callback.assert_called_once_with(
-            call, self.rpc.method.response_type(payload='!!!'))
-
-        callback.reset_mock()
-
-        call.cancel()
-
-        self.assertEqual(self.last_request().type,
-                         packet_pb2.PacketType.CLIENT_ERROR)
-        self.assertEqual(self.last_request().status, Status.CANCELLED.value)
-
-        # Ensure the RPC can be called after being cancelled.
-        self._enqueue_server_stream(1, self.method, resp)
-        self._enqueue_response(1, self.method, Status.OK)
-
-        call = self.rpc.invoke(self._request(magic_number=3), callback,
-                               callback)
-
-        callback.assert_has_calls([
-            mock.call(call, self.method.response_type(payload='!!!')),
-            mock.call(call, Status.OK),
-        ])
-
-    def test_nonblocking_with_request_args(self) -> None:
-        self.rpc.invoke(request_args=dict(magic_number=1138))
-        self.assertEqual(
-            self._sent_payload(self.rpc.request).magic_number, 1138)
-
-    def test_blocking_timeout(self) -> None:
+    def test_timeout_server_streaming_iteration(self):
+        responses = self._service.SomeServerStreaming(pw_rpc_timeout_s=0.0001)
         with self.assertRaises(callback_client.RpcTimeout):
-            self._service.SomeServerStreaming(pw_rpc_timeout_s=0.0001)
-
-    def test_nonblocking_iteration_timeout(self) -> None:
-        call = self._service.SomeServerStreaming.invoke(timeout_s=0.0001)
-        with self.assertRaises(callback_client.RpcTimeout):
-            for _ in call:
+            for _ in responses:
                 pass
 
-    def test_nonblocking_duplicate_calls_first_is_cancelled(self) -> None:
-        first_call = self.rpc.invoke()
-        self.assertFalse(first_call.completed())
-
-        second_call = self.rpc.invoke()
-
-        self.assertIs(first_call.error, Status.CANCELLED)
-        self.assertFalse(second_call.completed())
-
-    def test_nonblocking_iterate_over_count(self) -> None:
-        reply = self.method.response_type(payload='!?')
-
-        for _ in range(4):
-            self._enqueue_server_stream(1, self.method, reply)
-
-        call = self.rpc.invoke()
-
-        self.assertEqual(list(call.get_responses(count=1)), [reply])
-        self.assertEqual(next(iter(call)), reply)
-        self.assertEqual(list(call.get_responses(count=2)), [reply, reply])
-
-    def test_nonblocking_iterate_after_completed_doesnt_block(self) -> None:
-        reply = self.method.response_type(payload='!?')
-        self._enqueue_server_stream(1, self.method, reply)
-        self._enqueue_response(1, self.method, Status.OK)
-
-        call = self.rpc.invoke()
-
-        self.assertEqual(list(call.get_responses()), [reply])
-        self.assertEqual(list(call.get_responses()), [])
-        self.assertEqual(list(call), [])
-
-
-class ClientStreamingTest(_CallbackClientImplTestBase):
-    """Tests for client streaming RPCs."""
-    def setUp(self) -> None:
-        super().setUp()
-        self.rpc = self._service.SomeClientStreaming
-        self.method = self.rpc.method
-
-    def test_blocking_call(self) -> None:
-        requests = [
-            self.method.request_type(magic_number=123),
-            self.method.request_type(magic_number=456),
-        ]
-
-        # Send after len(requests) and the client stream end packet.
-        self.send_responses_after_packets = 3
-        response = self.method.response_type(payload='yo')
-        self._enqueue_response(1, self.method, Status.OK, response)
-
-        results = self.rpc(requests)
-        self.assertIs(results.status, Status.OK)
-        self.assertEqual(results.response, response)
-
-    def test_blocking_server_error(self) -> None:
-        requests = [self.method.request_type(magic_number=123)]
-
-        # Send after len(requests) and the client stream end packet.
-        self._enqueue_error(1, self.method.service, self.method,
-                            Status.NOT_FOUND)
-
-        with self.assertRaises(callback_client.RpcError) as context:
-            self.rpc(requests)
-
-        self.assertIs(context.exception.status, Status.NOT_FOUND)
-
-    def test_nonblocking_call(self) -> None:
-        """Tests a successful client streaming RPC ended by the server."""
-        payload_1 = self.method.response_type(payload='-_-')
-
-        for _ in range(3):
-            stream = self._service.SomeClientStreaming.invoke()
-            self.assertFalse(stream.completed())
-
-            stream.send(magic_number=31)
-            self.assertIs(packet_pb2.PacketType.CLIENT_STREAM,
-                          self.last_request().type)
-            self.assertEqual(
-                31,
-                self._sent_payload(self.method.request_type).magic_number)
-            self.assertFalse(stream.completed())
-
-            # Enqueue the server response to be sent after the next message.
-            self._enqueue_response(1, self.method, Status.OK, payload_1)
-
-            stream.send(magic_number=32)
-            self.assertIs(packet_pb2.PacketType.CLIENT_STREAM,
-                          self.last_request().type)
-            self.assertEqual(
-                32,
-                self._sent_payload(self.method.request_type).magic_number)
-
-            self.assertTrue(stream.completed())
-            self.assertIs(Status.OK, stream.status)
-            self.assertIsNone(stream.error)
-            self.assertEqual(payload_1, stream.response)
-
-    def test_open(self) -> None:
-        self.output_exception = IOError('something went wrong sending!')
-        payload = self.method.response_type(payload='-_-')
-
-        for _ in range(3):
-            self._enqueue_response(1, self.method, Status.OK, payload)
-
-            callback = mock.Mock()
-            call = self.rpc.open(callback, callback, callback)
-            self.assertEqual(self.requests, [])
-
-            self._process_enqueued_packets()
-
-            callback.assert_has_calls([
-                mock.call(call, payload),
-                mock.call(call, Status.OK),
-            ])
-
-    def test_nonblocking_finish(self) -> None:
-        """Tests a client streaming RPC ended by the client."""
-        payload_1 = self.method.response_type(payload='-_-')
-
-        for _ in range(3):
-            stream = self._service.SomeClientStreaming.invoke()
-            self.assertFalse(stream.completed())
-
-            stream.send(magic_number=37)
-            self.assertIs(packet_pb2.PacketType.CLIENT_STREAM,
-                          self.last_request().type)
-            self.assertEqual(
-                37,
-                self._sent_payload(self.method.request_type).magic_number)
-            self.assertFalse(stream.completed())
-
-            # Enqueue the server response to be sent after the next message.
-            self._enqueue_response(1, self.method, Status.OK, payload_1)
-
-            stream.finish_and_wait()
-            self.assertIs(packet_pb2.PacketType.CLIENT_STREAM_END,
-                          self.last_request().type)
-
-            self.assertTrue(stream.completed())
-            self.assertIs(Status.OK, stream.status)
-            self.assertIsNone(stream.error)
-            self.assertEqual(payload_1, stream.response)
-
-    def test_nonblocking_cancel(self) -> None:
-        for _ in range(3):
-            stream = self._service.SomeClientStreaming.invoke()
-            stream.send(magic_number=37)
-
-            self.assertTrue(stream.cancel())
-            self.assertIs(packet_pb2.PacketType.CLIENT_ERROR,
-                          self.last_request().type)
-            self.assertIs(Status.CANCELLED.value, self.last_request().status)
-            self.assertFalse(stream.cancel())
-
-            self.assertTrue(stream.completed())
-            self.assertIs(stream.error, Status.CANCELLED)
-
-    def test_nonblocking_server_error(self) -> None:
-        for _ in range(3):
-            stream = self._service.SomeClientStreaming.invoke()
-
-            self._enqueue_error(1, self.method.service, self.method,
-                                Status.INVALID_ARGUMENT)
-            stream.send(magic_number=2**32 - 1)
-
-            with self.assertRaises(callback_client.RpcError) as context:
-                stream.finish_and_wait()
-
-            self.assertIs(context.exception.status, Status.INVALID_ARGUMENT)
-
-    def test_nonblocking_server_error_after_stream_end(self) -> None:
-        for _ in range(3):
-            stream = self._service.SomeClientStreaming.invoke()
-
-            # Error will be sent in response to the CLIENT_STREAM_END packet.
-            self._enqueue_error(1, self.method.service, self.method,
-                                Status.INVALID_ARGUMENT)
-
-            with self.assertRaises(callback_client.RpcError) as context:
-                stream.finish_and_wait()
-
-            self.assertIs(context.exception.status, Status.INVALID_ARGUMENT)
-
-    def test_nonblocking_send_after_cancelled(self) -> None:
-        call = self._service.SomeClientStreaming.invoke()
-        self.assertTrue(call.cancel())
-
-        with self.assertRaises(callback_client.RpcError) as context:
-            call.send(payload='hello')
-
-        self.assertIs(context.exception.status, Status.CANCELLED)
-
-    def test_nonblocking_finish_after_completed(self) -> None:
-        reply = self.method.response_type(payload='!?')
-        self._enqueue_response(1, self.method, Status.UNAVAILABLE, reply)
-
-        call = self.rpc.invoke()
-        result = call.finish_and_wait()
-        self.assertEqual(result.response, reply)
-
-        self.assertEqual(result, call.finish_and_wait())
-        self.assertEqual(result, call.finish_and_wait())
-
-    def test_nonblocking_finish_after_error(self) -> None:
-        self._enqueue_error(1, self.method.service, self.method,
-                            Status.UNAVAILABLE)
-
-        call = self.rpc.invoke()
-
-        for _ in range(3):
-            with self.assertRaises(callback_client.RpcError) as context:
-                call.finish_and_wait()
-
-            self.assertIs(context.exception.status, Status.UNAVAILABLE)
-            self.assertIs(call.error, Status.UNAVAILABLE)
-            self.assertIsNone(call.response)
-
-    def test_nonblocking_duplicate_calls_first_is_cancelled(self) -> None:
-        first_call = self.rpc.invoke()
-        self.assertFalse(first_call.completed())
-
-        second_call = self.rpc.invoke()
-
-        self.assertIs(first_call.error, Status.CANCELLED)
-        self.assertFalse(second_call.completed())
-
-
-class BidirectionalStreamingTest(_CallbackClientImplTestBase):
-    """Tests for bidirectional streaming RPCs."""
-    def setUp(self) -> None:
-        super().setUp()
-        self.rpc = self._service.SomeBidiStreaming
-        self.method = self.rpc.method
-
-    def test_blocking_call(self) -> None:
-        requests = [
-            self.method.request_type(magic_number=123),
-            self.method.request_type(magic_number=456),
-        ]
-
-        # Send after len(requests) and the client stream end packet.
-        self.send_responses_after_packets = 3
-        self._enqueue_response(1, self.method, Status.NOT_FOUND)
-
-        results = self.rpc(requests)
-        self.assertIs(results.status, Status.NOT_FOUND)
-        self.assertFalse(results.responses)
-
-    def test_blocking_server_error(self) -> None:
-        requests = [self.method.request_type(magic_number=123)]
-
-        # Send after len(requests) and the client stream end packet.
-        self._enqueue_error(1, self.method.service, self.method,
-                            Status.NOT_FOUND)
-
-        with self.assertRaises(callback_client.RpcError) as context:
-            self.rpc(requests)
-
-        self.assertIs(context.exception.status, Status.NOT_FOUND)
-
-    def test_nonblocking_call(self) -> None:
-        """Tests a bidirectional streaming RPC ended by the server."""
-        rep1 = self.method.response_type(payload='!!!')
-        rep2 = self.method.response_type(payload='?')
-
-        for _ in range(3):
-            responses: list = []
-            stream = self._service.SomeBidiStreaming.invoke(
-                lambda _, res, responses=responses: responses.append(res))
-            self.assertFalse(stream.completed())
-
-            stream.send(magic_number=55)
-            self.assertIs(packet_pb2.PacketType.CLIENT_STREAM,
-                          self.last_request().type)
-            self.assertEqual(
-                55,
-                self._sent_payload(self.method.request_type).magic_number)
-            self.assertFalse(stream.completed())
-            self.assertEqual([], responses)
-
-            self._enqueue_server_stream(1, self.method, rep1)
-            self._enqueue_server_stream(1, self.method, rep2)
-
-            stream.send(magic_number=66)
-            self.assertIs(packet_pb2.PacketType.CLIENT_STREAM,
-                          self.last_request().type)
-            self.assertEqual(
-                66,
-                self._sent_payload(self.method.request_type).magic_number)
-            self.assertFalse(stream.completed())
-            self.assertEqual([rep1, rep2], responses)
-
-            self._enqueue_response(1, self.method, Status.OK)
-
-            stream.send(magic_number=77)
-            self.assertTrue(stream.completed())
-            self.assertEqual([rep1, rep2], responses)
-
-            self.assertIs(Status.OK, stream.status)
-            self.assertIsNone(stream.error)
-
-    def test_open(self) -> None:
-        self.output_exception = IOError('something went wrong sending!')
-        rep1 = self.method.response_type(payload='!!!')
-        rep2 = self.method.response_type(payload='?')
-
-        for _ in range(3):
-            self._enqueue_server_stream(1, self.method, rep1)
-            self._enqueue_server_stream(1, self.method, rep2)
-            self._enqueue_response(1, self.method, Status.OK)
-
-            callback = mock.Mock()
-            call = self.rpc.open(callback, callback, callback)
-            self.assertEqual(self.requests, [])
-
-            self._process_enqueued_packets()
-
-            callback.assert_has_calls([
-                mock.call(call, self.method.response_type(payload='!!!')),
-                mock.call(call, self.method.response_type(payload='?')),
-                mock.call(call, Status.OK),
-            ])
-
-    @mock.patch('pw_rpc.callback_client.call.Call._default_response')
-    def test_nonblocking(self, callback) -> None:
-        """Tests a bidirectional streaming RPC ended by the server."""
-        reply = self.method.response_type(payload='This is the payload!')
-        self._enqueue_server_stream(1, self.method, reply)
-
-        self._service.SomeBidiStreaming.invoke()
-
-        callback.assert_called_once_with(mock.ANY, reply)
-
-    def test_nonblocking_server_error(self) -> None:
-        rep1 = self.method.response_type(payload='!!!')
-
-        for _ in range(3):
-            responses: list = []
-            stream = self._service.SomeBidiStreaming.invoke(
-                lambda _, res, responses=responses: responses.append(res))
-            self.assertFalse(stream.completed())
-
-            self._enqueue_server_stream(1, self.method, rep1)
-
-            stream.send(magic_number=55)
-            self.assertFalse(stream.completed())
-            self.assertEqual([rep1], responses)
-
-            self._enqueue_error(1, self.method.service, self.method,
-                                Status.OUT_OF_RANGE)
-
-            stream.send(magic_number=99999)
-            self.assertTrue(stream.completed())
-            self.assertEqual([rep1], responses)
-
-            self.assertIsNone(stream.status)
-            self.assertIs(Status.OUT_OF_RANGE, stream.error)
-
-            with self.assertRaises(callback_client.RpcError) as context:
-                stream.finish_and_wait()
-            self.assertIs(context.exception.status, Status.OUT_OF_RANGE)
-
-    def test_nonblocking_server_error_after_stream_end(self) -> None:
-        for _ in range(3):
-            stream = self._service.SomeBidiStreaming.invoke()
-
-            # Error will be sent in response to the CLIENT_STREAM_END packet.
-            self._enqueue_error(1, self.method.service, self.method,
-                                Status.INVALID_ARGUMENT)
-
-            with self.assertRaises(callback_client.RpcError) as context:
-                stream.finish_and_wait()
-
-            self.assertIs(context.exception.status, Status.INVALID_ARGUMENT)
-
-    def test_nonblocking_send_after_cancelled(self) -> None:
-        call = self._service.SomeBidiStreaming.invoke()
-        self.assertTrue(call.cancel())
-
-        with self.assertRaises(callback_client.RpcError) as context:
-            call.send(payload='hello')
-
-        self.assertIs(context.exception.status, Status.CANCELLED)
-
-    def test_nonblocking_finish_after_completed(self) -> None:
-        reply = self.method.response_type(payload='!?')
-        self._enqueue_server_stream(1, self.method, reply)
-        self._enqueue_response(1, self.method, Status.UNAVAILABLE)
-
-        call = self.rpc.invoke()
-        result = call.finish_and_wait()
-        self.assertEqual(result.responses, [reply])
-
-        self.assertEqual(result, call.finish_and_wait())
-        self.assertEqual(result, call.finish_and_wait())
-
-    def test_nonblocking_finish_after_error(self) -> None:
-        reply = self.method.response_type(payload='!?')
-        self._enqueue_server_stream(1, self.method, reply)
-        self._enqueue_error(1, self.method.service, self.method,
-                            Status.UNAVAILABLE)
-
-        call = self.rpc.invoke()
-
-        for _ in range(3):
-            with self.assertRaises(callback_client.RpcError) as context:
-                call.finish_and_wait()
-
-            self.assertIs(context.exception.status, Status.UNAVAILABLE)
-            self.assertIs(call.error, Status.UNAVAILABLE)
-            self.assertEqual(call.responses, [reply])
-
-    def test_nonblocking_duplicate_calls_first_is_cancelled(self) -> None:
-        first_call = self.rpc.invoke()
-        self.assertFalse(first_call.completed())
-
-        second_call = self.rpc.invoke()
-
-        self.assertIs(first_call.error, Status.CANCELLED)
-        self.assertFalse(second_call.completed())
+    def test_timeout_server_streaming_responses(self):
+        responses = self._service.SomeServerStreaming()
+        with self.assertRaises(callback_client.RpcTimeout):
+            for _ in responses.responses(timeout_s=0.0001):
+                pass
 
 
 if __name__ == '__main__':
diff --git a/pw_rpc/py/tests/client_test.py b/pw_rpc/py/tests/client_test.py
index e071252..6fcfd60 100755
--- a/pw_rpc/py/tests/client_test.py
+++ b/pw_rpc/py/tests/client_test.py
@@ -285,26 +285,6 @@
                       method_id=method.id,
                       status=Status.FAILED_PRECONDITION.value))
 
-    def test_process_packet_non_pending_calls_response_callback(self) -> None:
-        method = self._client.method('pw.test1.PublicService.SomeUnary')
-        reply = method.response_type(payload='hello')
-
-        def response_callback(rpc: client.PendingRpc, message,
-                              status: Optional[Status]) -> None:
-            self.assertEqual(
-                rpc,
-                client.PendingRpc(
-                    self._client.channel(1).channel, method.service, method))
-            self.assertEqual(message, reply)
-            self.assertIs(status, Status.OK)
-
-        self._client.response_callback = response_callback
-
-        self.assertIs(
-            self._client.process_packet(
-                packets.encode_response((1, method.service, method), reply)),
-            Status.OK)
-
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/pw_rpc/py/tests/console_tools/console_tools_test.py b/pw_rpc/py/tests/console_tools/console_tools_test.py
index 36285db..3dbb5ce 100755
--- a/pw_rpc/py/tests/console_tools/console_tools_test.py
+++ b/pw_rpc/py/tests/console_tools/console_tools_test.py
@@ -12,18 +12,72 @@
 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 # License for the specific language governing permissions and limitations under
 # the License.
-"""Tests the pw_rpc.console_tools.console module."""
+"""Tests encoding HDLC frames."""
 
-import types
 import unittest
+from unittest import mock
 
 import pw_status
 
 from pw_protobuf_compiler import python_protos
 import pw_rpc
 from pw_rpc import callback_client
-from pw_rpc.console_tools.console import (CommandHelper, Context, ClientInfo,
-                                          alias_deprecated_command)
+from pw_rpc.console_tools import CommandHelper, Context, ClientInfo, Watchdog
+
+
+class TestWatchdog(unittest.TestCase):
+    """Tests the Watchdog class."""
+    def setUp(self) -> None:
+        self._reset = mock.Mock()
+        self._expiration = mock.Mock()
+        self._while_expired = mock.Mock()
+
+        self._watchdog = Watchdog(self._reset, self._expiration,
+                                  self._while_expired, 99999)
+
+    def _trigger_timeout(self) -> None:
+        # Don't wait for the timeout -- that's too flaky. Call the internal
+        # timeout function instead.
+        self._watchdog._timeout_expired()  # pylint: disable=protected-access
+
+    def test_expiration_callbacks(self) -> None:
+        self._watchdog.start()
+
+        self._expiration.not_called()
+
+        self._trigger_timeout()
+
+        self._expiration.assert_called_once_with()
+        self._while_expired.assert_not_called()
+
+        self._trigger_timeout()
+
+        self._expiration.assert_called_once_with()
+        self._while_expired.assert_called_once_with()
+
+        self._trigger_timeout()
+
+        self._expiration.assert_called_once_with()
+        self._while_expired.assert_called()
+
+    def test_reset_not_called_unless_expires(self) -> None:
+        self._watchdog.start()
+        self._watchdog.reset()
+
+        self._reset.assert_not_called()
+        self._expiration.assert_not_called()
+        self._while_expired.assert_not_called()
+
+    def test_reset_called_if_expired(self) -> None:
+        self._watchdog.start()
+        self._trigger_timeout()
+
+        self._watchdog.reset()
+
+        self._trigger_timeout()
+
+        self._reset.assert_called_once_with()
+        self._expiration.assert_called()
 
 
 class TestCommandHelper(unittest.TestCase):
@@ -175,31 +229,5 @@
         self.assertTrue(called_derived_set_target)
 
 
-class TestAliasDeprecatedCommand(unittest.TestCase):
-    def test_wraps_command_to_new_package(self) -> None:
-        variables = {'abc': types.SimpleNamespace(command=lambda: 123)}
-        alias_deprecated_command(variables, 'xyz.one.two.three', 'abc.command')
-
-        self.assertEqual(variables['xyz'].one.two.three(), 123)
-
-    def test_wraps_command_to_existing_package(self) -> None:
-        variables = {
-            'abc': types.SimpleNamespace(NewCmd=lambda: 456),
-            'one': types.SimpleNamespace(),
-        }
-        alias_deprecated_command(variables, 'one.two.OldCmd', 'abc.NewCmd')
-
-        self.assertEqual(variables['one'].two.OldCmd(), 456)
-
-    def test_error_if_new_command_does_not_exist(self) -> None:
-        variables = {
-            'abc': types.SimpleNamespace(),
-            'one': types.SimpleNamespace(),
-        }
-
-        with self.assertRaises(AttributeError):
-            alias_deprecated_command(variables, 'one.two.OldCmd', 'abc.NewCmd')
-
-
 if __name__ == '__main__':
     unittest.main()
diff --git a/pw_rpc/py/tests/console_tools/watchdog_test.py b/pw_rpc/py/tests/console_tools/watchdog_test.py
deleted file mode 100644
index 9bc203c..0000000
--- a/pw_rpc/py/tests/console_tools/watchdog_test.py
+++ /dev/null
@@ -1,79 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests the Watchdog module."""
-
-import unittest
-from unittest import mock
-
-from pw_rpc.console_tools import Watchdog
-
-
-class TestWatchdog(unittest.TestCase):
-    """Tests the Watchdog class."""
-    def setUp(self) -> None:
-        self._reset = mock.Mock()
-        self._expiration = mock.Mock()
-        self._while_expired = mock.Mock()
-
-        self._watchdog = Watchdog(self._reset, self._expiration,
-                                  self._while_expired, 99999)
-
-    def _trigger_timeout(self) -> None:
-        # Don't wait for the timeout -- that's too flaky. Call the internal
-        # timeout function instead.
-        self._watchdog._timeout_expired()  # pylint: disable=protected-access
-
-    def test_expiration_callbacks(self) -> None:
-        self._watchdog.start()
-
-        self._expiration.not_called()
-
-        self._trigger_timeout()
-
-        self._expiration.assert_called_once_with()
-        self._while_expired.assert_not_called()
-
-        self._trigger_timeout()
-
-        self._expiration.assert_called_once_with()
-        self._while_expired.assert_called_once_with()
-
-        self._trigger_timeout()
-
-        self._expiration.assert_called_once_with()
-        self._while_expired.assert_called()
-
-    def test_reset_not_called_unless_expires(self) -> None:
-        self._watchdog.start()
-        self._watchdog.reset()
-
-        self._reset.assert_not_called()
-        self._expiration.assert_not_called()
-        self._while_expired.assert_not_called()
-
-    def test_reset_called_if_expired(self) -> None:
-        self._watchdog.start()
-        self._trigger_timeout()
-
-        self._watchdog.reset()
-
-        self._trigger_timeout()
-
-        self._reset.assert_called_once_with()
-        self._expiration.assert_called()
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_rpc/py/tests/descriptors_test.py b/pw_rpc/py/tests/descriptors_test.py
index 9b3bf93..516d5e3 100644
--- a/pw_rpc/py/tests/descriptors_test.py
+++ b/pw_rpc/py/tests/descriptors_test.py
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2020 The Pigweed Authors
 #
 # 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
@@ -62,10 +62,6 @@
             self._method.get_request(self._method.request_type(),
                                      {'magic_number': 1})
 
-    def test_get_request_neither_message_nor_kwargs(self):
-        self.assertEqual(self._method.request_type(),
-                         self._method.get_request(None, None))
-
     def test_get_request_with_wrong_type(self):
         with self.assertRaisesRegex(TypeError, r'pw\.test1\.SomeMessage'):
             self._method.get_request('a str!', {})
diff --git a/pw_rpc/py/tests/packets_test.py b/pw_rpc/py/tests/packets_test.py
index 452ee90..4bf4dc1 100755
--- a/pw_rpc/py/tests/packets_test.py
+++ b/pw_rpc/py/tests/packets_test.py
@@ -58,11 +58,10 @@
 
         self.assertEqual(
             packet,
-            RpcPacket(type=PacketType.CLIENT_ERROR,
+            RpcPacket(type=PacketType.CANCEL_SERVER_STREAM,
                       channel_id=9,
                       service_id=8,
-                      method_id=7,
-                      status=Status.CANCELLED.value))
+                      method_id=7))
 
     def test_encode_client_error(self):
         data = packets.encode_client_error(_TEST_REQUEST, Status.NOT_FOUND)
diff --git a/pw_rpc/py/tests/python_client_cpp_server_test.py b/pw_rpc/py/tests/python_client_cpp_server_test.py
deleted file mode 100755
index db9d35c..0000000
--- a/pw_rpc/py/tests/python_client_cpp_server_test.py
+++ /dev/null
@@ -1,90 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests using the callback client for pw_rpc."""
-
-from typing import List, Tuple
-import unittest
-
-import pw_hdlc.rpc
-from pw_rpc import benchmark_pb2, testing
-from pw_status import Status
-
-ITERATIONS = 50
-
-
-class RpcIntegrationTest(unittest.TestCase):
-    """Calls RPCs on an RPC server through a socket."""
-    test_server_command: Tuple[str, ...] = ()
-    port: int
-
-    def setUp(self) -> None:
-        self._context = pw_hdlc.rpc.HdlcRpcLocalServerAndClient(
-            self.test_server_command, self.port, [benchmark_pb2])
-        self.rpcs = self._context.client.channel(1).rpcs
-
-    def tearDown(self) -> None:
-        self._context.close()
-
-    def test_unary(self) -> None:
-        for i in range(ITERATIONS):
-            payload = f'O_o #{i}'.encode()
-            status, reply = self.rpcs.pw.rpc.Benchmark.UnaryEcho(
-                payload=payload)
-            self.assertIs(status, Status.OK)
-            self.assertEqual(reply.payload, payload)
-
-    def test_bidirectional(self) -> None:
-        with self.rpcs.pw.rpc.Benchmark.BidirectionalEcho.invoke() as call:
-            responses = call.get_responses()
-
-            for i in range(ITERATIONS):
-                payload = f'O_o #{i}'.encode()
-                call.send(benchmark_pb2.Payload(payload=payload))
-
-                self.assertEqual(next(responses).payload, payload)
-
-    def test_bidirectional_call_twice(self) -> None:
-        rpc = self.rpcs.pw.rpc.Benchmark.BidirectionalEcho
-
-        for _ in range(ITERATIONS):
-            first_call = rpc.invoke()
-            first_call.send(payload=b'abc')
-            self.assertEqual(next(iter(first_call)),
-                             rpc.response(payload=b'abc'))
-            self.assertFalse(first_call.completed())
-
-            second_call = rpc.invoke()
-            second_call.send(payload=b'123')
-            self.assertEqual(next(iter(second_call)),
-                             rpc.response(payload=b'123'))
-
-            self.assertIs(first_call.error, Status.CANCELLED)
-            self.assertEqual(first_call.responses,
-                             [rpc.response(payload=b'abc')])
-
-            self.assertFalse(second_call.completed())
-            self.assertEqual(second_call.responses,
-                             [rpc.response(payload=b'123')])
-
-
-def _main(test_server_command: List[str], port: int,
-          unittest_args: List[str]) -> None:
-    RpcIntegrationTest.test_server_command = tuple(test_server_command)
-    RpcIntegrationTest.port = port
-    unittest.main(argv=unittest_args)
-
-
-if __name__ == '__main__':
-    _main(**vars(testing.parse_test_server_args()))
diff --git a/pw_rpc/raw/BUILD b/pw_rpc/raw/BUILD
new file mode 100644
index 0000000..d983b14
--- /dev/null
+++ b/pw_rpc/raw/BUILD
@@ -0,0 +1,101 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "method",
+    srcs = [
+        "raw_method.cc",
+    ],
+    hdrs = [
+        "public/pw_rpc/internal/raw_method.h",
+    ],
+    deps = [
+        "//pw_bytes",
+        "//pw_rpc:server",
+    ],
+)
+
+pw_cc_library(
+    name = "method_union",
+    hdrs = [
+        "public/pw_rpc/internal/raw_method_union.h",
+    ],
+    deps = [
+        ":method",
+    ],
+)
+
+pw_cc_library(
+    name = "test_method_context",
+    hdrs = [
+        "public/pw_rpc/raw_test_method_context.h",
+    ],
+    deps = [
+        ":method_union",
+        "//pw_assert",
+        "//pw_containers",
+    ],
+)
+
+pw_cc_test(
+    name = "codegen_test",
+    srcs = [
+        "codegen_test.cc",
+    ],
+    deps = [
+        ":method_union",
+        "//pw_protobuf",
+    ],
+)
+
+pw_cc_test(
+    name = "raw_method_test",
+    srcs = [
+        "raw_method_test.cc",
+    ],
+    deps = [
+        ":method_union",
+        "//pw_protobuf",
+        "//pw_rpc:internal_test_utils",
+    ],
+)
+
+pw_cc_test(
+    name = "raw_method_union_test",
+    srcs = [
+        "raw_method_union_test.cc",
+    ],
+    deps = [
+        ":method_union",
+        "//pw_protobuf",
+        "//pw_rpc:internal_test_utils",
+    ],
+)
+
+pw_cc_test(
+    name = "stub_generation_test",
+    srcs = ["stub_generation_test.cc"],
+    # TODO(hepler): Figure out proto BUILD integration.
+    # deps = ["..:test_protos.raw_rpc"],
+)
diff --git a/pw_rpc/raw/BUILD.bazel b/pw_rpc/raw/BUILD.bazel
deleted file mode 100644
index fcad0a6..0000000
--- a/pw_rpc/raw/BUILD.bazel
+++ /dev/null
@@ -1,188 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "server_api",
-    srcs = [
-        "method.cc",
-    ],
-    hdrs = [
-        "public/pw_rpc/raw/internal/method.h",
-        "public/pw_rpc/raw/internal/method_union.h",
-        "public/pw_rpc/raw/server_reader_writer.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_bytes",
-        "//pw_rpc",
-        "//pw_rpc:internal_packet_cc.pwpb",
-        "//pw_status",
-    ],
-)
-
-pw_cc_library(
-    name = "client_api",
-    hdrs = ["public/pw_rpc/raw/client_reader_writer.h"],
-    includes = ["public"],
-    deps = [
-        "//pw_bytes",
-        "//pw_rpc",
-        "//pw_rpc:internal_packet_cc.pwpb",
-    ],
-)
-
-pw_cc_library(
-    name = "fake_channel_output",
-    hdrs = ["public/pw_rpc/raw/fake_channel_output.h"],
-    includes = ["public"],
-    deps = [
-        ":server_api",
-        "//pw_assert",
-        "//pw_containers",
-    ],
-)
-
-pw_cc_library(
-    name = "test_method_context",
-    hdrs = ["public/pw_rpc/raw/test_method_context.h"],
-    includes = ["public"],
-    deps = [
-        ":fake_channel_output",
-        ":server_api",
-        "//pw_assert",
-        "//pw_containers:vector",
-        "//pw_preprocessor",
-        "//pw_rpc",
-        "//pw_rpc:internal_test_utils",
-    ],
-)
-
-pw_cc_library(
-    name = "client_testing",
-    srcs = ["client_testing.cc"],
-    hdrs = ["public/pw_rpc/raw/client_testing.h"],
-    deps = [
-        ":test_method_context",
-        "//pw_assert",
-        "//pw_bytes",
-        "//pw_log",
-        "//pw_rpc",
-    ],
-)
-
-pw_cc_test(
-    name = "client_test",
-    srcs = [
-        "client_test.cc",
-    ],
-    deps = [
-        ":client_api",
-        ":client_testing",
-        "//pw_rpc:internal_test_utils",
-    ],
-)
-
-pw_cc_test(
-    name = "client_reader_writer_test",
-    srcs = ["client_reader_writer_test.cc"],
-    deps = [
-        ":client_api",
-        ":client_testing",
-        "//pw_rpc:pw_rpc_test_cc.raw_rpc",
-    ],
-)
-
-pw_cc_test(
-    name = "codegen_test",
-    srcs = [
-        "codegen_test.cc",
-    ],
-    deps = [
-        ":client_api",
-        ":client_testing",
-        ":server_api",
-        ":test_method_context",
-        "//pw_protobuf",
-        "//pw_rpc:internal_test_utils",
-        "//pw_rpc:pw_rpc_test_cc.pwpb",
-        "//pw_rpc:pw_rpc_test_cc.raw_rpc",
-    ],
-)
-
-pw_cc_test(
-    name = "method_test",
-    srcs = [
-        "method_test.cc",
-    ],
-    deps = [
-        ":server_api",
-        "//pw_protobuf",
-        "//pw_rpc:internal_test_utils",
-        "//pw_rpc:pw_rpc_test_cc.pwpb",
-    ],
-)
-
-pw_cc_test(
-    name = "method_info_test",
-    srcs = [
-        "method_info_test.cc",
-    ],
-    deps = [
-        "//pw_rpc:internal_test_utils",
-        "//pw_rpc:pw_rpc_test_cc.raw_rpc",
-    ],
-)
-
-pw_cc_test(
-    name = "method_union_test",
-    srcs = [
-        "method_union_test.cc",
-    ],
-    deps = [
-        ":server_api",
-        "//pw_protobuf",
-        "//pw_rpc:internal_test_utils",
-        "//pw_rpc:pw_rpc_test_cc.pwpb",
-    ],
-)
-
-pw_cc_test(
-    name = "server_reader_writer_test",
-    srcs = ["server_reader_writer_test.cc"],
-    deps = [
-        ":test_method_context",
-        "//pw_rpc:internal_test_utils",
-        "//pw_rpc:pw_rpc_test_cc.pwpb",
-        "//pw_rpc:pw_rpc_test_cc.raw_rpc",
-    ],
-)
-
-pw_cc_test(
-    name = "stub_generation_test",
-    srcs = ["stub_generation_test.cc"],
-    deps = [
-        "//pw_rpc:pw_rpc_test_cc.pwpb",
-        "//pw_rpc:pw_rpc_test_cc.raw_rpc",
-    ],
-)
diff --git a/pw_rpc/raw/BUILD.gn b/pw_rpc/raw/BUILD.gn
index cad3bd6..e2f4cfa 100644
--- a/pw_rpc/raw/BUILD.gn
+++ b/pw_rpc/raw/BUILD.gn
@@ -12,84 +12,55 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
+# gn-format disable
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_unit_test/test.gni")
-
 config("public") {
   include_dirs = [ "public" ]
   visibility = [ ":*" ]
 }
 
-pw_source_set("server_api") {
+pw_source_set("method") {
   public_configs = [ ":public" ]
-  public = [
-    "public/pw_rpc/raw/internal/method.h",
-    "public/pw_rpc/raw/internal/method_union.h",
-    "public/pw_rpc/raw/server_reader_writer.h",
-  ]
-  sources = [ "method.cc" ]
+  public = [ "public/pw_rpc/internal/raw_method.h" ]
+  sources = [ "raw_method.cc" ]
   public_deps = [
     "..:server",
     dir_pw_bytes,
   ]
+  deps = [ dir_pw_log ]
 }
 
-pw_source_set("client_api") {
+pw_source_set("method_union") {
   public_configs = [ ":public" ]
-  public = [ "public/pw_rpc/raw/client_reader_writer.h" ]
-  public_deps = [
-    "..:client",
-    dir_pw_bytes,
-  ]
-}
-
-pw_source_set("fake_channel_output") {
-  public = [ "public/pw_rpc/raw/fake_channel_output.h" ]
-  public_configs = [ ":public" ]
-  public_deps = [ "..:fake_channel_output" ]
+  public = [ "public/pw_rpc/internal/raw_method_union.h" ]
+  public_deps = [ ":method" ]
 }
 
 pw_source_set("test_method_context") {
   public_configs = [ ":public" ]
-  public = [ "public/pw_rpc/raw/test_method_context.h" ]
+  public = [ "public/pw_rpc/raw_test_method_context.h" ]
   public_deps = [
-    ":fake_channel_output",
-    ":server_api",
-    "..:test_utils",
+    ":method",
     dir_pw_assert,
     dir_pw_containers,
   ]
 }
 
-pw_source_set("client_testing") {
-  public = [ "public/pw_rpc/raw/client_testing.h" ]
-  sources = [ "client_testing.cc" ]
-  public_deps = [
-    ":fake_channel_output",
-    "..:client",
-  ]
-  deps = [ "..:log_config" ]
-}
-
 pw_test_group("tests") {
   tests = [
     ":codegen_test",
-    ":client_test",
-    ":client_reader_writer_test",
-    ":method_test",
-    ":method_info_test",
-    ":method_union_test",
-    ":server_reader_writer_test",
+    ":raw_method_test",
+    ":raw_method_union_test",
     ":stub_generation_test",
   ]
 }
 
 pw_test("codegen_test") {
   deps = [
-    ":client_testing",
     ":test_method_context",
     "..:test_protos.pwpb",
     "..:test_protos.raw_rpc",
@@ -98,61 +69,26 @@
   sources = [ "codegen_test.cc" ]
 }
 
-pw_test("client_test") {
+pw_test("raw_method_test") {
   deps = [
-    ":client_api",
-    ":client_testing",
-    "..:test_utils",
-  ]
-  sources = [ "client_test.cc" ]
-}
-
-pw_test("client_reader_writer_test") {
-  deps = [
-    ":client_api",
-    ":client_testing",
-    "..:test_protos.raw_rpc",
-  ]
-  sources = [ "client_reader_writer_test.cc" ]
-}
-
-pw_test("method_test") {
-  deps = [
-    ":server_api",
+    ":method",
+    ":method_union",
     "..:test_protos.pwpb",
     "..:test_protos.raw_rpc",
     "..:test_utils",
     dir_pw_protobuf,
   ]
-  sources = [ "method_test.cc" ]
+  sources = [ "raw_method_test.cc" ]
 }
 
-pw_test("method_info_test") {
+pw_test("raw_method_union_test") {
   deps = [
-    "..:common",
-    "..:test_protos.raw_rpc",
-    "..:test_utils",
-  ]
-  sources = [ "method_info_test.cc" ]
-}
-
-pw_test("method_union_test") {
-  deps = [
-    ":server_api",
+    ":method_union",
     "..:test_protos.pwpb",
     "..:test_utils",
     dir_pw_protobuf,
   ]
-  sources = [ "method_union_test.cc" ]
-}
-
-pw_test("server_reader_writer_test") {
-  deps = [
-    ":server_api",
-    ":test_method_context",
-    "..:test_protos.raw_rpc",
-  ]
-  sources = [ "server_reader_writer_test.cc" ]
+  sources = [ "raw_method_union_test.cc" ]
 }
 
 pw_test("stub_generation_test") {
diff --git a/pw_rpc/raw/CMakeLists.txt b/pw_rpc/raw/CMakeLists.txt
index 8a9c7ad..f0e1225 100644
--- a/pw_rpc/raw/CMakeLists.txt
+++ b/pw_rpc/raw/CMakeLists.txt
@@ -16,7 +16,6 @@
 
 pw_auto_add_simple_module(pw_rpc.raw
   PUBLIC_DEPS
-    pw_log
     pw_rpc.client
     pw_rpc.common
     pw_rpc.server
diff --git a/pw_rpc/raw/client_reader_writer_test.cc b/pw_rpc/raw/client_reader_writer_test.cc
deleted file mode 100644
index df62de2..0000000
--- a/pw_rpc/raw/client_reader_writer_test.cc
+++ /dev/null
@@ -1,333 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/raw/client_reader_writer.h"
-
-#include "gtest/gtest.h"
-#include "pw_rpc/raw/client_testing.h"
-#include "pw_rpc/writer.h"
-#include "pw_rpc_test_protos/test.raw_rpc.pb.h"
-
-namespace pw::rpc {
-namespace {
-
-using test::pw_rpc::raw::TestService;
-
-void FailIfCalled(Status) { FAIL(); }
-void FailIfOnNextCalled(ConstByteSpan) { FAIL(); }
-void FailIfOnCompletedCalled(ConstByteSpan, Status) { FAIL(); }
-
-TEST(RawUnaryReceiver, DefaultConstructed) {
-  RawUnaryReceiver call;
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Cancel());
-
-  call.set_on_completed([](ConstByteSpan, Status) {});
-  call.set_on_error([](Status) {});
-}
-
-TEST(RawClientWriter, DefaultConstructed) {
-  RawClientWriter call;
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Write({}));
-  EXPECT_EQ(Status::FailedPrecondition(), call.Cancel());
-  EXPECT_EQ(Status::FailedPrecondition(), call.CloseClientStream());
-
-  call.set_on_completed([](ConstByteSpan, Status) {});
-  call.set_on_error([](Status) {});
-}
-
-TEST(RawClientReader, DefaultConstructed) {
-  RawClientReader call;
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Cancel());
-
-  call.set_on_completed([](Status) {});
-  call.set_on_next([](ConstByteSpan) {});
-  call.set_on_error([](Status) {});
-}
-
-TEST(RawClientReaderWriter, DefaultConstructed) {
-  RawClientReaderWriter call;
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Write({}));
-  EXPECT_EQ(Status::FailedPrecondition(), call.Cancel());
-  EXPECT_EQ(Status::FailedPrecondition(), call.CloseClientStream());
-
-  call.set_on_completed([](Status) {});
-  call.set_on_next([](ConstByteSpan) {});
-  call.set_on_error([](Status) {});
-}
-
-TEST(RawUnaryReceiver, Closed) {
-  RawClientTestContext ctx;
-  RawUnaryReceiver call = TestService::TestUnaryRpc(ctx.client(),
-                                                    ctx.channel().id(),
-                                                    {},
-                                                    FailIfOnCompletedCalled,
-                                                    FailIfCalled);
-  ASSERT_EQ(OkStatus(), call.Cancel());
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Cancel());
-
-  call.set_on_completed([](ConstByteSpan, Status) {});
-  call.set_on_error([](Status) {});
-}
-
-TEST(RawClientWriter, Closed) {
-  RawClientTestContext ctx;
-  RawClientWriter call = TestService::TestClientStreamRpc(
-      ctx.client(), ctx.channel().id(), FailIfOnCompletedCalled, FailIfCalled);
-  ASSERT_EQ(OkStatus(), call.Cancel());
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Write({}));
-  EXPECT_EQ(Status::FailedPrecondition(), call.Cancel());
-  EXPECT_EQ(Status::FailedPrecondition(), call.CloseClientStream());
-
-  call.set_on_completed([](ConstByteSpan, Status) {});
-  call.set_on_error([](Status) {});
-}
-
-TEST(RawClientReader, Closed) {
-  RawClientTestContext ctx;
-  RawClientReader call = TestService::TestServerStreamRpc(ctx.client(),
-                                                          ctx.channel().id(),
-                                                          {},
-                                                          FailIfOnNextCalled,
-                                                          FailIfCalled,
-                                                          FailIfCalled);
-  ASSERT_EQ(OkStatus(), call.Cancel());
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Cancel());
-
-  call.set_on_completed([](Status) {});
-  call.set_on_next([](ConstByteSpan) {});
-  call.set_on_error([](Status) {});
-}
-
-TEST(RawClientReaderWriter, Closed) {
-  RawClientTestContext ctx;
-  RawClientReaderWriter call =
-      TestService::TestBidirectionalStreamRpc(ctx.client(),
-                                              ctx.channel().id(),
-                                              FailIfOnNextCalled,
-                                              FailIfCalled,
-                                              FailIfCalled);
-  ASSERT_EQ(OkStatus(), call.Cancel());
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Write({}));
-  EXPECT_EQ(Status::FailedPrecondition(), call.Cancel());
-  EXPECT_EQ(Status::FailedPrecondition(), call.CloseClientStream());
-
-  call.set_on_completed([](Status) {});
-  call.set_on_next([](ConstByteSpan) {});
-  call.set_on_error([](Status) {});
-}
-
-TEST(RawClientReaderWriter, Move_InactiveToActive_EndsClientStream) {
-  RawClientTestContext ctx;
-
-  RawClientReaderWriter active_call =
-      TestService::TestBidirectionalStreamRpc(ctx.client(),
-                                              ctx.channel().id(),
-                                              FailIfOnNextCalled,
-                                              FailIfCalled,
-                                              FailIfCalled);
-
-  ASSERT_EQ(ctx.output().total_packets(), 1u);  // Sent the request
-
-  RawClientReaderWriter inactive_call;
-
-  active_call = std::move(inactive_call);
-
-  EXPECT_EQ(ctx.output().total_packets(), 2u);  // Sent CLIENT_STREAM_END
-  EXPECT_EQ(
-      ctx.output()
-          .client_stream_end_packets<TestService::TestBidirectionalStreamRpc>(),
-      1u);
-
-  EXPECT_FALSE(active_call.active());
-  // NOLINTNEXTLINE(bugprone-use-after-move)
-  EXPECT_FALSE(inactive_call.active());
-}
-
-TEST(RawUnaryReceiver, Move_InactiveToActive_SilentlyCloses) {
-  RawClientTestContext ctx;
-
-  RawUnaryReceiver active_call =
-      TestService::TestUnaryRpc(ctx.client(),
-                                ctx.channel().id(),
-                                {},
-                                FailIfOnCompletedCalled,
-                                FailIfCalled);
-
-  ASSERT_EQ(ctx.output().total_packets(), 1u);  // Sent the request
-
-  RawUnaryReceiver inactive_call;
-
-  active_call = std::move(inactive_call);
-
-  EXPECT_EQ(ctx.output().total_packets(), 1u);  // No more packets
-
-  EXPECT_FALSE(active_call.active());
-  // NOLINTNEXTLINE(bugprone-use-after-move)
-  EXPECT_FALSE(inactive_call.active());
-}
-
-TEST(RawUnaryReceiver, Move_ActiveToActive) {
-  RawClientTestContext ctx;
-
-  RawUnaryReceiver active_call_1 =
-      TestService::TestUnaryRpc(ctx.client(), ctx.channel().id(), {});
-
-  RawUnaryReceiver active_call_2 =
-      TestService::TestAnotherUnaryRpc(ctx.client(), ctx.channel().id(), {});
-
-  ASSERT_EQ(ctx.output().total_packets(), 2u);  // Sent the requests
-  ASSERT_TRUE(active_call_1.active());
-  ASSERT_TRUE(active_call_2.active());
-
-  active_call_2 = std::move(active_call_1);
-
-  EXPECT_EQ(ctx.output().total_packets(), 2u);  // No more packets
-
-  // NOLINTNEXTLINE(bugprone-use-after-move)
-  EXPECT_FALSE(active_call_1.active());
-  EXPECT_TRUE(active_call_2.active());
-}
-
-TEST(RawClientReaderWriter, NewCallCancelsPreviousAndCallsErrorCallback) {
-  RawClientTestContext ctx;
-
-  Status error;
-  RawClientReaderWriter active_call_1 = TestService::TestBidirectionalStreamRpc(
-      ctx.client(),
-      ctx.channel().id(),
-      FailIfOnNextCalled,
-      FailIfCalled,
-      [&error](Status status) { error = status; });
-
-  ASSERT_TRUE(active_call_1.active());
-
-  RawClientReaderWriter active_call_2 =
-      TestService::TestBidirectionalStreamRpc(ctx.client(), ctx.channel().id());
-
-  EXPECT_FALSE(active_call_1.active());
-  EXPECT_TRUE(active_call_2.active());
-  EXPECT_EQ(error, Status::Cancelled());
-}
-
-TEST(RawClientReader, NoClientStream_OutOfScope_SilentlyCloses) {
-  RawClientTestContext ctx;
-
-  {
-    RawClientReader call = TestService::TestServerStreamRpc(ctx.client(),
-                                                            ctx.channel().id(),
-                                                            {},
-                                                            FailIfOnNextCalled,
-                                                            FailIfCalled,
-                                                            FailIfCalled);
-    ASSERT_EQ(ctx.output().total_packets(), 1u);  // Sent the request
-  }
-
-  EXPECT_EQ(ctx.output().total_packets(), 1u);  // No more packets
-}
-
-TEST(RawClientWriter, WithClientStream_OutOfScope_SendsClientStreamEnd) {
-  RawClientTestContext ctx;
-
-  {
-    RawClientWriter call =
-        TestService::TestClientStreamRpc(ctx.client(),
-                                         ctx.channel().id(),
-                                         FailIfOnCompletedCalled,
-                                         FailIfCalled);
-    ASSERT_EQ(ctx.output().total_packets(), 1u);  // Sent the request
-  }
-
-  EXPECT_EQ(ctx.output().total_packets(), 2u);  // Sent CLIENT_STREAM_END
-  EXPECT_EQ(ctx.output()
-                .client_stream_end_packets<TestService::TestClientStreamRpc>(),
-            1u);
-}
-
-constexpr const char kWriterData[] = "20X6";
-
-void WriteAsWriter(Writer& writer) {
-  ASSERT_TRUE(writer.active());
-  ASSERT_EQ(writer.channel_id(), RawClientTestContext<>::kDefaultChannelId);
-
-  EXPECT_EQ(OkStatus(), writer.Write(std::as_bytes(std::span(kWriterData))));
-}
-
-TEST(RawClientWriter, UsableAsWriter) {
-  RawClientTestContext ctx;
-  RawClientWriter call = TestService::TestClientStreamRpc(
-      ctx.client(), ctx.channel().id(), FailIfOnCompletedCalled, FailIfCalled);
-
-  WriteAsWriter(call);
-
-  EXPECT_STREQ(reinterpret_cast<const char*>(
-                   ctx.output()
-                       .payloads<TestService::TestClientStreamRpc>()
-                       .back()
-                       .data()),
-               kWriterData);
-}
-
-TEST(RawClientReaderWriter, UsableAsWriter) {
-  RawClientTestContext ctx;
-  RawClientReaderWriter call =
-      TestService::TestBidirectionalStreamRpc(ctx.client(),
-                                              ctx.channel().id(),
-                                              FailIfOnNextCalled,
-                                              FailIfCalled,
-                                              FailIfCalled);
-
-  WriteAsWriter(call);
-
-  EXPECT_STREQ(reinterpret_cast<const char*>(
-                   ctx.output()
-                       .payloads<TestService::TestBidirectionalStreamRpc>()
-                       .back()
-                       .data()),
-               kWriterData);
-}
-
-}  // namespace
-}  // namespace pw::rpc
diff --git a/pw_rpc/raw/client_test.cc b/pw_rpc/raw/client_test.cc
deleted file mode 100644
index 6175c78..0000000
--- a/pw_rpc/raw/client_test.cc
+++ /dev/null
@@ -1,241 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/client.h"
-
-#include <optional>
-
-#include "gtest/gtest.h"
-#include "pw_rpc/internal/client_call.h"
-#include "pw_rpc/internal/packet.h"
-#include "pw_rpc/raw/client_testing.h"
-
-namespace pw::rpc {
-
-void UnaryMethod();
-void BidirectionalStreamMethod();
-
-template <>
-struct internal::MethodInfo<UnaryMethod> {
-  static constexpr uint32_t kServiceId = 100;
-  static constexpr uint32_t kMethodId = 200;
-  static constexpr MethodType kType = MethodType::kUnary;
-};
-
-template <>
-struct internal::MethodInfo<BidirectionalStreamMethod> {
-  static constexpr uint32_t kServiceId = 100;
-  static constexpr uint32_t kMethodId = 300;
-  static constexpr MethodType kType = MethodType::kBidirectionalStreaming;
-};
-
-namespace {
-
-template <auto kMethod, typename Call, typename Context>
-Call MakeCall(Context& context) {
-  return Call(context.client(),
-              context.channel().id(),
-              internal::MethodInfo<kMethod>::kServiceId,
-              internal::MethodInfo<kMethod>::kMethodId,
-              internal::MethodInfo<kMethod>::kType);
-}
-
-class TestStreamCall : public internal::StreamResponseClientCall {
- public:
-  TestStreamCall(Client& client,
-                 uint32_t channel_id,
-                 uint32_t service_id,
-                 uint32_t method_id,
-                 MethodType type)
-      : StreamResponseClientCall(
-            client, channel_id, service_id, method_id, type),
-        payload(nullptr) {
-    set_on_next([this](ConstByteSpan string) {
-      payload = reinterpret_cast<const char*>(string.data());
-    });
-    set_on_completed([this](Status status) { completed = status; });
-    set_on_error([this](Status status) { error = status; });
-  }
-
-  const char* payload;
-  std::optional<Status> completed;
-  std::optional<Status> error;
-};
-
-class TestUnaryCall : public internal::UnaryResponseClientCall {
- public:
-  TestUnaryCall(Client& client,
-                uint32_t channel_id,
-                uint32_t service_id,
-                uint32_t method_id,
-                MethodType type)
-      : UnaryResponseClientCall(
-            client, channel_id, service_id, method_id, type),
-        payload(nullptr) {
-    set_on_completed([this](ConstByteSpan string, Status status) {
-      payload = reinterpret_cast<const char*>(string.data());
-      completed = status;
-    });
-    set_on_error([this](Status status) { error = status; });
-  }
-
-  const char* payload;
-  std::optional<Status> completed;
-  std::optional<Status> error;
-};
-
-TEST(Client, ProcessPacket_InvokesUnaryCallbacks) {
-  RawClientTestContext context;
-  TestUnaryCall call = MakeCall<UnaryMethod, TestUnaryCall>(context);
-  internal::rpc_lock().lock();
-  call.SendInitialClientRequest({});
-
-  ASSERT_NE(call.completed, OkStatus());
-
-  context.server().SendResponse<UnaryMethod>(
-      std::as_bytes(std::span("you nary?!?")), OkStatus());
-
-  ASSERT_NE(call.payload, nullptr);
-  EXPECT_STREQ(call.payload, "you nary?!?");
-  EXPECT_EQ(call.completed, OkStatus());
-}
-
-TEST(Client, ProcessPacket_InvokesStreamCallbacks) {
-  RawClientTestContext context;
-  auto call = MakeCall<BidirectionalStreamMethod, TestStreamCall>(context);
-  internal::rpc_lock().lock();
-  call.SendInitialClientRequest({});
-
-  context.server().SendServerStream<BidirectionalStreamMethod>(
-      std::as_bytes(std::span("<=>")));
-
-  ASSERT_NE(call.payload, nullptr);
-  EXPECT_STREQ(call.payload, "<=>");
-
-  context.server().SendResponse<BidirectionalStreamMethod>(Status::NotFound());
-
-  EXPECT_EQ(call.completed, Status::NotFound());
-}
-
-TEST(Client, ProcessPacket_InvokesErrorCallback) {
-  RawClientTestContext context;
-  auto call = MakeCall<BidirectionalStreamMethod, TestStreamCall>(context);
-  internal::rpc_lock().lock();
-  call.SendInitialClientRequest({});
-
-  context.server().SendServerError<BidirectionalStreamMethod>(
-      Status::Aborted());
-
-  EXPECT_EQ(call.error, Status::Aborted());
-}
-
-TEST(Client, ProcessPacket_SendsClientErrorOnUnregisteredServerStream) {
-  RawClientTestContext context;
-  context.server().SendServerStream<BidirectionalStreamMethod>({});
-
-  StatusView errors = context.output().errors<BidirectionalStreamMethod>();
-  ASSERT_EQ(errors.size(), 1u);
-  EXPECT_EQ(errors.front(), Status::FailedPrecondition());
-}
-
-TEST(Client, ProcessPacket_NonServerStreamOnUnregisteredCall_SendsNothing) {
-  RawClientTestContext context;
-  context.server().SendServerError<UnaryMethod>(Status::NotFound());
-  EXPECT_EQ(context.output().total_packets(), 0u);
-
-  context.server().SendResponse<UnaryMethod>({}, Status::Unavailable());
-  EXPECT_EQ(context.output().total_packets(), 0u);
-}
-
-TEST(Client, ProcessPacket_ReturnsDataLossOnBadPacket) {
-  RawClientTestContext context;
-
-  constexpr std::byte bad_packet[]{
-      std::byte{0xab}, std::byte{0xcd}, std::byte{0xef}};
-  EXPECT_EQ(context.client().ProcessPacket(bad_packet), Status::DataLoss());
-}
-
-TEST(Client, ProcessPacket_ReturnsInvalidArgumentOnServerPacket) {
-  RawClientTestContext context;
-
-  std::byte encoded[64];
-  Result<std::span<const std::byte>> result =
-      internal::Packet(internal::PacketType::REQUEST, 1, 2, 3).Encode(encoded);
-  ASSERT_TRUE(result.ok());
-
-  EXPECT_EQ(context.client().ProcessPacket(*result), Status::InvalidArgument());
-}
-
-const Channel* GetChannel(internal::Endpoint& endpoint, uint32_t id) {
-  internal::LockGuard lock(internal::rpc_lock());
-  return endpoint.GetInternalChannel(id);
-}
-
-TEST(Client, CloseChannel_NoCalls) {
-  RawClientTestContext ctx;
-  ASSERT_NE(nullptr, GetChannel(ctx.client(), ctx.kDefaultChannelId));
-  EXPECT_EQ(OkStatus(), ctx.client().CloseChannel(ctx.kDefaultChannelId));
-  EXPECT_EQ(nullptr, GetChannel(ctx.client(), ctx.kDefaultChannelId));
-  EXPECT_EQ(ctx.output().total_packets(), 0u);
-}
-
-TEST(Client, CloseChannel_UnknownChannel) {
-  RawClientTestContext ctx;
-  ASSERT_EQ(nullptr, GetChannel(ctx.client(), 13579));
-  EXPECT_EQ(Status::NotFound(), ctx.client().CloseChannel(13579));
-}
-
-TEST(Client, CloseChannel_CallsErrorCallback) {
-  RawClientTestContext ctx;
-  TestUnaryCall call = MakeCall<UnaryMethod, TestUnaryCall>(ctx);
-  internal::rpc_lock().lock();
-  call.SendInitialClientRequest({});
-
-  ASSERT_NE(call.completed, OkStatus());
-  ASSERT_EQ(1u,
-            static_cast<internal::Endpoint&>(ctx.client()).active_call_count());
-
-  EXPECT_EQ(OkStatus(), ctx.client().CloseChannel(1));
-
-  EXPECT_EQ(0u,
-            static_cast<internal::Endpoint&>(ctx.client()).active_call_count());
-  ASSERT_EQ(call.error, Status::Aborted());  // set by the on_error callback
-}
-
-TEST(Client, OpenChannel_UnusedSlot) {
-  RawClientTestContext ctx;
-  ASSERT_EQ(OkStatus(), ctx.client().CloseChannel(1));
-  ASSERT_EQ(nullptr, GetChannel(ctx.client(), 9));
-
-  EXPECT_EQ(OkStatus(), ctx.client().OpenChannel(9, ctx.output()));
-
-  EXPECT_NE(nullptr, GetChannel(ctx.client(), 9));
-}
-
-TEST(Client, OpenChannel_AlreadyExists) {
-  RawClientTestContext ctx;
-  ASSERT_NE(nullptr, GetChannel(ctx.client(), 1));
-  EXPECT_EQ(Status::AlreadyExists(), ctx.client().OpenChannel(1, ctx.output()));
-}
-
-TEST(Client, OpenChannel_AdditionalSlot) {
-  RawClientTestContext ctx;
-
-  constexpr Status kExpected =
-      PW_RPC_DYNAMIC_ALLOCATION == 0 ? Status::ResourceExhausted() : OkStatus();
-  EXPECT_EQ(kExpected, ctx.client().OpenChannel(19823, ctx.output()));
-}
-
-}  // namespace
-}  // namespace pw::rpc
diff --git a/pw_rpc/raw/client_testing.cc b/pw_rpc/raw/client_testing.cc
deleted file mode 100644
index e3b8f53..0000000
--- a/pw_rpc/raw/client_testing.cc
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// clang-format off
-#include "pw_rpc/internal/log_config.h" // PW_LOG_* macros must be first.
-
-#include "pw_rpc/raw/client_testing.h"
-// clang-format on
-
-#include "pw_assert/check.h"
-#include "pw_log/log.h"
-#include "pw_rpc/client.h"
-
-namespace pw::rpc {
-
-void FakeServer::CheckProcessPacket(internal::PacketType type,
-                                    uint32_t service_id,
-                                    uint32_t method_id,
-                                    ConstByteSpan payload,
-                                    Status status) const {
-  if (Status process_packet_status =
-          ProcessPacket(type, service_id, method_id, payload, status);
-      !process_packet_status.ok()) {
-    PW_LOG_CRITICAL("Failed to process packet in pw::rpc::FakeServer");
-    PW_LOG_CRITICAL(
-        "Packet contents\ntype: %u\nchannel_id: %u\nservice_id: %08x\n"
-        "method_id: %08x\npayload: %u bytes\nstatus: %s",
-        static_cast<unsigned>(type),
-        static_cast<unsigned>(channel_id_),
-        static_cast<unsigned>(service_id),
-        static_cast<unsigned>(method_id),
-        static_cast<unsigned>(payload.size()),
-        status.str());
-    PW_CHECK_OK(process_packet_status);
-  }
-}
-
-Status FakeServer::ProcessPacket(internal::PacketType type,
-                                 uint32_t service_id,
-                                 uint32_t method_id,
-                                 ConstByteSpan payload,
-                                 Status status) const {
-  auto view = internal::test::PacketsView(
-      output_.packets(),
-      internal::test::PacketFilter(internal::PacketType::REQUEST,
-                                   internal::PacketType::RESPONSE,
-                                   channel_id_,
-                                   service_id,
-                                   method_id));
-
-  // Re-use the call ID of the most recent packet for this RPC.
-  uint32_t call_id = view.empty() ? 0 : view.back().call_id();
-
-  auto packet_encoding_result =
-      internal::Packet(
-          type, channel_id_, service_id, method_id, call_id, payload, status)
-          .Encode(packet_buffer_);
-  PW_CHECK_OK(packet_encoding_result.status());
-  return client_.ProcessPacket(*packet_encoding_result);
-}
-
-}  // namespace pw::rpc
diff --git a/pw_rpc/raw/codegen_test.cc b/pw_rpc/raw/codegen_test.cc
index 3318ac4..520ae4e 100644
--- a/pw_rpc/raw/codegen_test.cc
+++ b/pw_rpc/raw/codegen_test.cc
@@ -12,135 +12,54 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-#include <optional>
-
 #include "gtest/gtest.h"
 #include "pw_protobuf/decoder.h"
 #include "pw_rpc/internal/hash.h"
-#include "pw_rpc/raw/client_testing.h"
-#include "pw_rpc/raw/test_method_context.h"
+#include "pw_rpc/raw_test_method_context.h"
 #include "pw_rpc_test_protos/test.pwpb.h"
 #include "pw_rpc_test_protos/test.raw_rpc.pb.h"
 
 namespace pw::rpc {
-namespace {
-
-Vector<std::byte, 64> EncodeRequest(int integer, Status status) {
-  Vector<std::byte, 64> buffer(64);
-  test::TestRequest::MemoryEncoder test_request(buffer);
-
-  EXPECT_EQ(OkStatus(), test_request.WriteInteger(integer));
-  EXPECT_EQ(OkStatus(), test_request.WriteStatusCode(status.code()));
-
-  EXPECT_EQ(OkStatus(), test_request.status());
-  buffer.resize(test_request.size());
-  return buffer;
-}
-
-Vector<std::byte, 64> EncodeResponse(int number) {
-  Vector<std::byte, 64> buffer(64);
-  test::TestStreamResponse::MemoryEncoder test_response(buffer);
-
-  EXPECT_EQ(OkStatus(), test_response.WriteNumber(number));
-
-  EXPECT_EQ(OkStatus(), test_response.status());
-  buffer.resize(test_response.size());
-  return buffer;
-}
-
-}  // namespace
-
 namespace test {
 
-class TestService final
-    : public pw_rpc::raw::TestService::Service<TestService> {
+class TestService final : public generated::TestService<TestService> {
  public:
-  static void TestUnaryRpc(ConstByteSpan request,
-                           RawUnaryResponder& responder) {
+  static StatusWithSize TestRpc(ServerContext&,
+                                ConstByteSpan request,
+                                ByteSpan response) {
     int64_t integer;
     Status status;
 
     if (!DecodeRequest(request, integer, status)) {
-      ASSERT_EQ(OkStatus(), responder.Finish({}, Status::DataLoss()));
-      return;
+      return StatusWithSize::DataLoss();
     }
 
-    std::byte response[64] = {};
-    TestResponse::MemoryEncoder test_response(response);
-    EXPECT_EQ(OkStatus(), test_response.WriteValue(integer + 1));
+    protobuf::NestedEncoder encoder(response);
+    TestResponse::Encoder test_response(&encoder);
+    test_response.WriteValue(integer + 1);
 
-    ASSERT_EQ(OkStatus(),
-              responder.Finish(std::span(response).first(test_response.size()),
-                               status));
+    return StatusWithSize(status, encoder.Encode().value().size());
   }
 
-  void TestAnotherUnaryRpc(ConstByteSpan request,
-                           RawUnaryResponder& responder) {
-    if (request.empty()) {
-      last_responder_ = std::move(responder);
-    } else {
-      TestUnaryRpc(request, responder);
-    }
-  }
-
-  void TestServerStreamRpc(ConstByteSpan request, RawServerWriter& writer) {
+  void TestStreamRpc(ServerContext&,
+                     ConstByteSpan request,
+                     RawServerWriter& writer) {
     int64_t integer;
     Status status;
 
     ASSERT_TRUE(DecodeRequest(request, integer, status));
     for (int i = 0; i < integer; ++i) {
-      std::byte buffer[32] = {};
-      TestStreamResponse::MemoryEncoder test_stream_response(buffer);
-      EXPECT_EQ(OkStatus(), test_stream_response.WriteNumber(i));
-      EXPECT_EQ(OkStatus(), writer.Write(test_stream_response));
+      ByteSpan buffer = writer.PayloadBuffer();
+      protobuf::NestedEncoder encoder(buffer);
+      TestStreamResponse::Encoder test_stream_response(&encoder);
+      test_stream_response.WriteNumber(i);
+      writer.Write(encoder.Encode().value());
     }
 
-    EXPECT_EQ(OkStatus(), writer.Finish(status));
+    writer.Finish(status);
   }
 
-  void TestClientStreamRpc(RawServerReader& reader) {
-    last_reader_ = std::move(reader);
-
-    last_reader_.set_on_next([this](ConstByteSpan payload) {
-      EXPECT_EQ(OkStatus(),
-                last_reader_.Finish(EncodeResponse(ReadInteger(payload)),
-                                    Status::Unauthenticated()));
-    });
-  }
-
-  void TestBidirectionalStreamRpc(RawServerReaderWriter& reader_writer) {
-    last_reader_writer_ = std::move(reader_writer);
-
-    last_reader_writer_.set_on_next([this](ConstByteSpan payload) {
-      EXPECT_EQ(
-          OkStatus(),
-          last_reader_writer_.Write(EncodeResponse(ReadInteger(payload))));
-      EXPECT_EQ(OkStatus(), last_reader_writer_.Finish(Status::NotFound()));
-    });
-  }
-
-  RawUnaryResponder& last_responder() { return last_responder_; }
-
  private:
-  static uint32_t ReadInteger(ConstByteSpan request) {
-    uint32_t integer = 0;
-
-    protobuf::Decoder decoder(request);
-    while (decoder.Next().ok()) {
-      switch (static_cast<TestRequest::Fields>(decoder.FieldNumber())) {
-        case TestRequest::Fields::INTEGER:
-          EXPECT_EQ(OkStatus(), decoder.ReadUint32(&integer));
-          break;
-        case TestRequest::Fields::STATUS_CODE:
-          break;
-        default:
-          ADD_FAILURE();
-      }
-    }
-
-    return integer;
-  }
-
   static bool DecodeRequest(ConstByteSpan request,
                             int64_t& integer,
                             Status& status) {
@@ -170,27 +89,29 @@
     EXPECT_TRUE(has_status);
     return has_integer && has_status;
   }
-
-  RawUnaryResponder last_responder_;
-  RawServerReader last_reader_;
-  RawServerReaderWriter last_reader_writer_;
 };
 
 }  // namespace test
 
 namespace {
 
-TEST(RawCodegen, Server_CompilesProperly) {
+TEST(RawCodegen, CompilesProperly) {
   test::TestService service;
   EXPECT_EQ(service.id(), internal::Hash("pw.rpc.test.TestService"));
   EXPECT_STREQ(service.name(), "TestService");
 }
 
 TEST(RawCodegen, Server_InvokeUnaryRpc) {
-  PW_RAW_TEST_METHOD_CONTEXT(test::TestService, TestUnaryRpc) context;
+  PW_RAW_TEST_METHOD_CONTEXT(test::TestService, TestRpc) context;
 
-  context.call(EncodeRequest(123, OkStatus()));
-  EXPECT_EQ(OkStatus(), context.status());
+  std::byte buffer[64];
+  protobuf::NestedEncoder encoder(buffer);
+  test::TestRequest::Encoder test_request(&encoder);
+  test_request.WriteInteger(123);
+  test_request.WriteStatusCode(OkStatus().code());
+
+  auto sws = context.call(encoder.Encode().value());
+  EXPECT_EQ(OkStatus(), sws.status());
 
   protobuf::Decoder decoder(context.response());
 
@@ -198,106 +119,24 @@
     switch (static_cast<test::TestResponse::Fields>(decoder.FieldNumber())) {
       case test::TestResponse::Fields::VALUE: {
         int32_t value;
-        EXPECT_EQ(OkStatus(), decoder.ReadInt32(&value));
+        decoder.ReadInt32(&value);
         EXPECT_EQ(value, 124);
         break;
       }
-      case test::TestResponse::Fields::REPEATED_FIELD:
-        break;  // Ignore this field
     }
   }
 }
 
-TEST(RawCodegen, Server_InvokeAsyncUnaryRpc) {
-  PW_RAW_TEST_METHOD_CONTEXT(test::TestService, TestAnotherUnaryRpc) context;
-
-  context.call(EncodeRequest(123, OkStatus()));
-  EXPECT_EQ(OkStatus(), context.status());
-
-  protobuf::Decoder decoder(context.response());
-
-  while (decoder.Next().ok()) {
-    switch (static_cast<test::TestResponse::Fields>(decoder.FieldNumber())) {
-      case test::TestResponse::Fields::VALUE: {
-        int32_t value;
-        EXPECT_EQ(OkStatus(), decoder.ReadInt32(&value));
-        EXPECT_EQ(value, 124);
-        break;
-      }
-      case test::TestResponse::Fields::REPEATED_FIELD:
-        break;  // Ignore this field
-    }
-  }
-}
-
-TEST(RawCodegen, Server_HandleError) {
-  PW_RAW_TEST_METHOD_CONTEXT(test::TestService, TestAnotherUnaryRpc) ctx;
-
-  ASSERT_FALSE(ctx.service().last_responder().active());
-  ctx.call({});
-  ASSERT_TRUE(ctx.service().last_responder().active());
-
-  ctx.SendClientError(Status::Unimplemented());
-
-  EXPECT_FALSE(ctx.service().last_responder().active());
-}
-
-TEST(RawCodegen, Server_Finish) {
-  PW_RAW_TEST_METHOD_CONTEXT(test::TestService, TestAnotherUnaryRpc) ctx;
-
-  ASSERT_FALSE(ctx.service().last_responder().active());
-  ctx.call({});
-  ASSERT_TRUE(ctx.service().last_responder().active());
-
-  EXPECT_EQ(OkStatus(), ctx.service().last_responder().Finish({}));
-  EXPECT_FALSE(ctx.service().last_responder().active());
-}
-
-TEST(RawCodegen, Server_MoveCalls) {
-  // Create two call objects on different channels so they are unique.
-  PW_RAW_TEST_METHOD_CONTEXT(test::TestService, TestAnotherUnaryRpc) ctx;
-
-  RawUnaryResponder call;
-
-  ctx.call({});
-
-  EXPECT_TRUE(ctx.service().last_responder().active());
-  EXPECT_FALSE(call.active());
-
-  call = std::move(ctx.service().last_responder());
-
-  EXPECT_FALSE(ctx.service().last_responder().active());
-  EXPECT_TRUE(call.active());
-}
-
-TEST(RawCodegen, Server_MoveBetweenActiveCallsWithBuffers) {
-  // Create two call objects on different channels so they are unique.
-  PW_RAW_TEST_METHOD_CONTEXT(test::TestService, TestAnotherUnaryRpc) ctx_1;
-  ctx_1.set_channel_id(1);
-
-  PW_RAW_TEST_METHOD_CONTEXT(test::TestService, TestAnotherUnaryRpc) ctx_2;
-  ctx_2.set_channel_id(2);
-
-  ctx_1.call({});
-  ctx_2.call({});
-
-  ctx_1.service().last_responder() =
-      std::move(ctx_2.service().last_responder());
-
-  ASSERT_TRUE(ctx_1.service().last_responder().active());
-  ASSERT_FALSE(ctx_2.service().last_responder().active());
-
-  ctx_2.service().last_responder() =
-      std::move(ctx_1.service().last_responder());
-
-  ASSERT_FALSE(ctx_1.service().last_responder().active());
-  ASSERT_TRUE(ctx_2.service().last_responder().active());
-}
-
 TEST(RawCodegen, Server_InvokeServerStreamingRpc) {
-  PW_RAW_TEST_METHOD_CONTEXT(test::TestService, TestServerStreamRpc) context;
+  PW_RAW_TEST_METHOD_CONTEXT(test::TestService, TestStreamRpc) context;
 
-  context.call(EncodeRequest(5, Status::Unauthenticated()));
+  std::byte buffer[64];
+  protobuf::NestedEncoder encoder(buffer);
+  test::TestRequest::Encoder test_request(&encoder);
+  test_request.WriteInteger(5);
+  test_request.WriteStatusCode(Status::Unauthenticated().code());
+
+  context.call(encoder.Encode().value());
   EXPECT_TRUE(context.done());
   EXPECT_EQ(Status::Unauthenticated(), context.status());
   EXPECT_EQ(context.total_responses(), 5u);
@@ -308,7 +147,7 @@
         static_cast<test::TestStreamResponse::Fields>(decoder.FieldNumber())) {
       case test::TestStreamResponse::Fields::NUMBER: {
         int32_t value;
-        EXPECT_EQ(OkStatus(), decoder.ReadInt32(&value));
+        decoder.ReadInt32(&value);
         EXPECT_EQ(value, 4);
         break;
       }
@@ -319,317 +158,5 @@
   }
 }
 
-int32_t ReadResponseNumber(ConstByteSpan data) {
-  int32_t value = -1;
-  protobuf::Decoder decoder(data);
-  while (decoder.Next().ok()) {
-    switch (
-        static_cast<test::TestStreamResponse::Fields>(decoder.FieldNumber())) {
-      case test::TestStreamResponse::Fields::NUMBER: {
-        EXPECT_EQ(OkStatus(), decoder.ReadInt32(&value));
-        break;
-      }
-      default:
-        ADD_FAILURE();
-        break;
-    }
-  }
-
-  return value;
-}
-
-TEST(RawCodegen, Server_InvokeClientStreamingRpc) {
-  PW_RAW_TEST_METHOD_CONTEXT(test::TestService, TestClientStreamRpc) ctx;
-
-  ctx.call();
-  ctx.SendClientStream(EncodeRequest(123, OkStatus()));
-
-  ASSERT_TRUE(ctx.done());
-  EXPECT_EQ(Status::Unauthenticated(), ctx.status());
-  EXPECT_EQ(ctx.total_responses(), 1u);
-  EXPECT_EQ(ReadResponseNumber(ctx.responses().back()), 123);
-}
-
-TEST(RawCodegen, Server_InvokeBidirectionalStreamingRpc) {
-  PW_RAW_TEST_METHOD_CONTEXT(test::TestService, TestBidirectionalStreamRpc)
-  ctx;
-
-  ctx.call();
-  ctx.SendClientStream(EncodeRequest(456, OkStatus()));
-
-  ASSERT_TRUE(ctx.done());
-  EXPECT_EQ(Status::NotFound(), ctx.status());
-  ASSERT_EQ(ctx.total_responses(), 1u);
-  EXPECT_EQ(ReadResponseNumber(ctx.responses().back()), 456);
-}
-
-TEST(RawCodegen, Client_ClientClass) {
-  RawClientTestContext context;
-
-  test::pw_rpc::raw::TestService::Client service_client(context.client(),
-                                                        context.channel().id());
-
-  EXPECT_EQ(service_client.channel_id(), context.channel().id());
-  EXPECT_EQ(&service_client.client(), &context.client());
-}
-
-class RawCodegenClientTest : public ::testing::Test {
- protected:
-  RawCodegenClientTest()
-      : service_client_(context_.client(), context_.channel().id()) {}
-
-  // Assumes the payload is a null-terminated string, not a protobuf.
-  Function<void(ConstByteSpan)> OnNext() {
-    return [this](ConstByteSpan c_string) { CopyPayload(c_string); };
-  }
-
-  Function<void(Status)> OnCompleted() {
-    return [this](Status status) { status_ = status; };
-  }
-
-  Function<void(ConstByteSpan, Status)> UnaryOnCompleted() {
-    return [this](ConstByteSpan c_string, Status status) {
-      CopyPayload(c_string);
-      status_ = status;
-    };
-  }
-
-  Function<void(Status)> OnError() {
-    return [this](Status error) { error_ = error; };
-  }
-
-  RawClientTestContext<> context_;
-
-  test::pw_rpc::raw::TestService::Client service_client_;
-
-  // Store the payload as a null-terminated string for convenience. Use nullptr
-  // for an empty payload.
-  std::optional<const char*> payload_;
-  std::optional<Status> status_;
-  std::optional<Status> error_;
-
- private:
-  void CopyPayload(ConstByteSpan c_string) {
-    ASSERT_LE(c_string.size(), sizeof(buffer_));
-
-    if (c_string.empty()) {
-      payload_ = nullptr;
-    } else {
-      std::memcpy(buffer_, c_string.data(), c_string.size());
-      payload_ = buffer_;
-    }
-  }
-
-  char buffer_[64];
-};
-
-TEST_F(RawCodegenClientTest, InvokeUnaryRpc_Ok) {
-  RawUnaryReceiver call = test::pw_rpc::raw::TestService::TestUnaryRpc(
-      context_.client(),
-      context_.channel().id(),
-      std::as_bytes(std::span("This is the request")),
-      UnaryOnCompleted(),
-      OnError());
-
-  context_.server().SendResponse<test::pw_rpc::raw::TestService::TestUnaryRpc>(
-      std::as_bytes(std::span("(ㆆ_ㆆ)")), OkStatus());
-
-  ASSERT_TRUE(payload_.has_value());
-  EXPECT_STREQ(payload_.value(), "(ㆆ_ㆆ)");
-  EXPECT_EQ(status_, OkStatus());
-  EXPECT_FALSE(error_.has_value());
-}
-
-TEST_F(RawCodegenClientTest, InvokeUnaryRpc_Error) {
-  RawUnaryReceiver call = service_client_.TestUnaryRpc(
-      std::as_bytes(std::span("This is the request")),
-      UnaryOnCompleted(),
-      OnError());
-
-  context_.server()
-      .SendServerError<test::pw_rpc::raw::TestService::TestUnaryRpc>(
-          Status::NotFound());
-
-  EXPECT_FALSE(payload_.has_value());
-  EXPECT_FALSE(status_.has_value());
-  EXPECT_EQ(error_, Status::NotFound());
-}
-
-TEST_F(RawCodegenClientTest, InvokeServerStreamRpc_Ok) {
-  RawClientReader call = test::pw_rpc::raw::TestService::TestServerStreamRpc(
-      context_.client(),
-      context_.channel().id(),
-      std::as_bytes(std::span("This is the request")),
-      OnNext(),
-      OnCompleted(),
-      OnError());
-
-  context_.server()
-      .SendServerStream<test::pw_rpc::raw::TestService::TestServerStreamRpc>(
-          std::as_bytes(std::span("(⌐□_□)")));
-
-  ASSERT_TRUE(payload_.has_value());
-  EXPECT_STREQ(payload_.value(), "(⌐□_□)");
-
-  context_.server()
-      .SendServerStream<test::pw_rpc::raw::TestService::TestServerStreamRpc>(
-          std::as_bytes(std::span("(o_O)")));
-
-  EXPECT_STREQ(payload_.value(), "(o_O)");
-
-  context_.server()
-      .SendResponse<test::pw_rpc::raw::TestService::TestServerStreamRpc>(
-          Status::InvalidArgument());
-
-  EXPECT_EQ(status_, Status::InvalidArgument());
-  EXPECT_FALSE(error_.has_value());
-}
-
-TEST_F(RawCodegenClientTest, InvokeServerStreamRpc_Error) {
-  RawClientReader call = service_client_.TestServerStreamRpc(
-      std::as_bytes(std::span("This is the request")),
-      OnNext(),
-      OnCompleted(),
-      OnError());
-
-  context_.server()
-      .SendServerError<test::pw_rpc::raw::TestService::TestServerStreamRpc>(
-          Status::FailedPrecondition());
-
-  EXPECT_FALSE(payload_.has_value());
-  EXPECT_FALSE(status_.has_value());
-  EXPECT_EQ(error_, Status::FailedPrecondition());
-}
-
-TEST_F(RawCodegenClientTest, InvokeClientStreamRpc_Ok) {
-  RawClientWriter call = test::pw_rpc::raw::TestService::TestClientStreamRpc(
-      context_.client(),
-      context_.channel().id(),
-      UnaryOnCompleted(),
-      OnError());
-
-  EXPECT_EQ(OkStatus(), call.Write(std::as_bytes(std::span("(•‿•)"))));
-  EXPECT_STREQ(
-      reinterpret_cast<const char*>(
-          context_.output()
-              .payloads<test::pw_rpc::raw::TestService::TestClientStreamRpc>()
-              .back()
-              .data()),
-      "(•‿•)");
-
-  context_.server()
-      .SendResponse<test::pw_rpc::raw::TestService::TestClientStreamRpc>(
-          std::as_bytes(std::span("(⌐□_□)")), Status::InvalidArgument());
-
-  ASSERT_TRUE(payload_.has_value());
-  EXPECT_STREQ(payload_.value(), "(⌐□_□)");
-  EXPECT_EQ(status_, Status::InvalidArgument());
-  EXPECT_FALSE(error_.has_value());
-}
-
-TEST_F(RawCodegenClientTest, ClientStream_Finish) {
-  RawClientWriter call = test::pw_rpc::raw::TestService::TestClientStreamRpc(
-      context_.client(),
-      context_.channel().id(),
-      UnaryOnCompleted(),
-      OnError());
-
-  context_.server()
-      .SendResponse<test::pw_rpc::raw::TestService::TestClientStreamRpc>(
-          {}, OkStatus());
-
-  ASSERT_TRUE(payload_.has_value());
-  EXPECT_EQ(payload_.value(), nullptr);
-  EXPECT_EQ(status_, OkStatus());
-}
-
-TEST_F(RawCodegenClientTest, ClientStream_Cancel) {
-  RawClientWriter call = test::pw_rpc::raw::TestService::TestClientStreamRpc(
-      context_.client(),
-      context_.channel().id(),
-      UnaryOnCompleted(),
-      OnError());
-
-  EXPECT_EQ(call.Cancel(), OkStatus());
-  EXPECT_FALSE(call.active());
-}
-
-TEST_F(RawCodegenClientTest, ClientStream_Move) {
-  RawClientWriter call = test::pw_rpc::raw::TestService::TestClientStreamRpc(
-      context_.client(),
-      context_.channel().id(),
-      UnaryOnCompleted(),
-      OnError());
-
-  EXPECT_EQ(OkStatus(), call.CloseClientStream());
-
-  RawClientWriter call_2;
-
-  call = std::move(call_2);
-  EXPECT_FALSE(call.active());
-}
-
-TEST_F(RawCodegenClientTest, InvokeClientStreamRpc_Error) {
-  RawClientWriter call =
-      service_client_.TestClientStreamRpc(UnaryOnCompleted(), OnError());
-
-  context_.server()
-      .SendServerError<test::pw_rpc::raw::TestService::TestClientStreamRpc>(
-          Status::FailedPrecondition());
-
-  EXPECT_FALSE(payload_.has_value());
-  EXPECT_FALSE(status_.has_value());
-  EXPECT_EQ(error_, Status::FailedPrecondition());
-}
-
-TEST_F(RawCodegenClientTest, InvokeBidirectionalStreamRpc_Ok) {
-  RawClientReaderWriter call =
-      test::pw_rpc::raw::TestService::TestBidirectionalStreamRpc(
-          context_.client(),
-          context_.channel().id(),
-          OnNext(),
-          OnCompleted(),
-          OnError());
-
-  EXPECT_EQ(OkStatus(), call.Write(std::as_bytes(std::span("(•‿•)"))));
-  EXPECT_STREQ(
-      reinterpret_cast<const char*>(
-          context_.output()
-              .payloads<
-                  test::pw_rpc::raw::TestService::TestBidirectionalStreamRpc>()
-              .back()
-              .data()),
-      "(•‿•)");
-
-  context_.server()
-      .SendServerStream<
-          test::pw_rpc::raw::TestService::TestBidirectionalStreamRpc>(
-          std::as_bytes(std::span("(⌐□_□)")));
-
-  ASSERT_TRUE(payload_.has_value());
-  EXPECT_STREQ(payload_.value(), "(⌐□_□)");
-
-  context_.server()
-      .SendResponse<test::pw_rpc::raw::TestService::TestBidirectionalStreamRpc>(
-          Status::InvalidArgument());
-
-  EXPECT_EQ(status_, Status::InvalidArgument());
-  EXPECT_FALSE(error_.has_value());
-}
-
-TEST_F(RawCodegenClientTest, InvokeBidirectionalStreamRpc_Error) {
-  RawClientReaderWriter call = service_client_.TestBidirectionalStreamRpc(
-      OnNext(), OnCompleted(), OnError());
-
-  context_.server()
-      .SendServerError<
-          test::pw_rpc::raw::TestService::TestBidirectionalStreamRpc>(
-          Status::Internal());
-
-  EXPECT_FALSE(payload_.has_value());
-  EXPECT_FALSE(status_.has_value());
-  EXPECT_EQ(error_, Status::Internal());
-}
-
 }  // namespace
 }  // namespace pw::rpc
diff --git a/pw_rpc/raw/method.cc b/pw_rpc/raw/method.cc
deleted file mode 100644
index 11ae696..0000000
--- a/pw_rpc/raw/method.cc
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/raw/internal/method.h"
-
-#include <cstddef>
-#include <cstring>
-
-#include "pw_rpc/internal/packet.h"
-
-namespace pw::rpc::internal {
-
-void RawMethod::SynchronousUnaryInvoker(const CallContext& context,
-                                        const Packet& request) {
-  RawUnaryResponder responder(context);
-  rpc_lock().unlock();
-  // TODO(hepler): Remove support for raw synchronous unary methods. Unlike
-  //     synchronous Nanopb methods, they provide little value compared to
-  //     asynchronous unary methods. For now, just provide a fixed buffer on the
-  //     stack.
-  std::byte payload_buffer[64] = {};
-
-  StatusWithSize sws =
-      static_cast<const RawMethod&>(context.method())
-          .function_.synchronous_unary(
-              context.service(), request.payload(), std::span(payload_buffer));
-
-  responder.Finish(std::span(payload_buffer, sws.size()), sws.status())
-      .IgnoreError();
-}
-
-void RawMethod::AsynchronousUnaryInvoker(const CallContext& context,
-                                         const Packet& request) {
-  RawUnaryResponder responder(context);
-  rpc_lock().unlock();
-  static_cast<const RawMethod&>(context.method())
-      .function_.asynchronous_unary(
-          context.service(), request.payload(), responder);
-}
-
-void RawMethod::ServerStreamingInvoker(const CallContext& context,
-                                       const Packet& request) {
-  RawServerWriter server_writer(context);
-  rpc_lock().unlock();
-  static_cast<const RawMethod&>(context.method())
-      .function_.server_streaming(
-          context.service(), request.payload(), server_writer);
-}
-
-void RawMethod::ClientStreamingInvoker(const CallContext& context,
-                                       const Packet&) {
-  RawServerReader reader(context);
-  rpc_lock().unlock();
-  static_cast<const RawMethod&>(context.method())
-      .function_.stream_request(context.service(), reader);
-}
-
-void RawMethod::BidirectionalStreamingInvoker(const CallContext& context,
-                                              const Packet&) {
-  RawServerReaderWriter reader_writer(context);
-  rpc_lock().unlock();
-  static_cast<const RawMethod&>(context.method())
-      .function_.stream_request(context.service(), reader_writer);
-}
-
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/raw/method_info_test.cc b/pw_rpc/raw/method_info_test.cc
deleted file mode 100644
index 80817de..0000000
--- a/pw_rpc/raw/method_info_test.cc
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/internal/method_info.h"
-
-#include "gtest/gtest.h"
-#include "pw_rpc/internal/method_info_tester.h"
-#include "pw_rpc_test_protos/test.raw_rpc.pb.h"
-
-namespace pw::rpc::internal {
-namespace {
-
-class TestService final
-    : public pw::rpc::test::pw_rpc::raw::TestService::Service<TestService> {
- public:
-  static StatusWithSize TestUnaryRpc(ConstByteSpan, ByteSpan) {
-    return StatusWithSize(0);
-  }
-
-  void TestAnotherUnaryRpc(ConstByteSpan, RawUnaryResponder&) {}
-
-  void TestServerStreamRpc(ConstByteSpan, RawServerWriter&) {}
-
-  void TestClientStreamRpc(RawServerReader&) {}
-
-  void TestBidirectionalStreamRpc(RawServerReaderWriter&) {}
-};
-
-static_assert(
-    MethodInfoTests<pw::rpc::test::pw_rpc::raw::TestService, TestService>()
-        .Pass());
-
-}  // namespace
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/raw/method_test.cc b/pw_rpc/raw/method_test.cc
deleted file mode 100644
index 43382dc..0000000
--- a/pw_rpc/raw/method_test.cc
+++ /dev/null
@@ -1,327 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/raw/internal/method.h"
-
-#include <array>
-
-#include "gtest/gtest.h"
-#include "pw_bytes/array.h"
-#include "pw_protobuf/decoder.h"
-#include "pw_protobuf/encoder.h"
-#include "pw_rpc/internal/config.h"
-#include "pw_rpc/internal/method_impl_tester.h"
-#include "pw_rpc/internal/test_utils.h"
-#include "pw_rpc/raw/internal/method_union.h"
-#include "pw_rpc/service.h"
-#include "pw_rpc_test_protos/test.pwpb.h"
-
-namespace pw::rpc::internal {
-namespace {
-
-namespace TestRequest = ::pw::rpc::test::TestRequest;
-namespace TestResponse = ::pw::rpc::test::TestResponse;
-
-// Create a fake service for use with the MethodImplTester.
-class TestRawService final : public Service {
- public:
-  // Unary signatures
-
-  void Unary(ConstByteSpan, RawUnaryResponder&) {}
-
-  static void StaticUnary(ConstByteSpan, RawUnaryResponder&) {}
-
-  void AsyncUnary(ConstByteSpan, RawUnaryResponder&) {}
-
-  static void StaticAsyncUnary(ConstByteSpan, RawUnaryResponder&) {}
-
-  void UnaryWrongArg(ConstByteSpan, ConstByteSpan) {}
-
-  // Server streaming signatures
-
-  void ServerStreaming(ConstByteSpan, RawServerWriter&) {}
-
-  static void StaticServerStreaming(ConstByteSpan, RawServerWriter&) {}
-
-  static void StaticUnaryVoidReturn(ConstByteSpan, ByteSpan) {}
-
-  Status ServerStreamingBadReturn(ConstByteSpan, RawServerWriter&) {
-    return Status();
-  }
-
-  static void StaticServerStreamingMissingArg(RawServerWriter&) {}
-
-  // Client streaming signatures
-
-  void ClientStreaming(RawServerReader&) {}
-
-  static void StaticClientStreaming(RawServerReader&) {}
-
-  int ClientStreamingBadReturn(RawServerReader&) { return 0; }
-
-  static void StaticClientStreamingMissingArg() {}
-
-  // Bidirectional streaming signatures
-
-  void BidirectionalStreaming(RawServerReaderWriter&) {}
-
-  static void StaticBidirectionalStreaming(RawServerReaderWriter&) {}
-
-  int BidirectionalStreamingBadReturn(RawServerReaderWriter&) { return 0; }
-
-  static void StaticBidirectionalStreamingMissingArg() {}
-};
-
-static_assert(MethodImplTests<RawMethod, TestRawService>().Pass());
-
-template <typename Impl>
-class FakeServiceBase : public Service {
- public:
-  FakeServiceBase(uint32_t id) : Service(id, kMethods) {}
-
-  static constexpr std::array<RawMethodUnion, 5> kMethods = {
-      RawMethod::AsynchronousUnary<&Impl::DoNothing>(10u),
-      RawMethod::AsynchronousUnary<&Impl::AddFive>(11u),
-      RawMethod::ServerStreaming<&Impl::StartStream>(12u),
-      RawMethod::ClientStreaming<&Impl::ClientStream>(13u),
-      RawMethod::BidirectionalStreaming<&Impl::BidirectionalStream>(14u),
-  };
-};
-
-class FakeService : public FakeServiceBase<FakeService> {
- public:
-  FakeService(uint32_t id) : FakeServiceBase(id) {}
-
-  void DoNothing(ConstByteSpan, RawUnaryResponder& responder) {
-    ASSERT_EQ(OkStatus(), responder.Finish({}, Status::Unknown()));
-  }
-
-  void AddFive(ConstByteSpan request, RawUnaryResponder& responder) {
-    DecodeRawTestRequest(request);
-
-    std::array<std::byte, 32> response;
-    TestResponse::MemoryEncoder test_response(response);
-    EXPECT_EQ(OkStatus(), test_response.WriteValue(last_request.integer + 5));
-    ConstByteSpan payload(test_response);
-
-    ASSERT_EQ(OkStatus(),
-              responder.Finish(std::span(response).first(payload.size()),
-                               Status::Unauthenticated()));
-  }
-
-  void StartStream(ConstByteSpan request, RawServerWriter& writer) {
-    DecodeRawTestRequest(request);
-    last_writer = std::move(writer);
-  }
-
-  void ClientStream(RawServerReader& reader) {
-    last_reader = std::move(reader);
-  }
-
-  void BidirectionalStream(RawServerReaderWriter& reader_writer) {
-    last_reader_writer = std::move(reader_writer);
-  }
-
-  void DecodeRawTestRequest(ConstByteSpan request) {
-    protobuf::Decoder decoder(request);
-
-    while (decoder.Next().ok()) {
-      TestRequest::Fields field =
-          static_cast<TestRequest::Fields>(decoder.FieldNumber());
-
-      switch (field) {
-        case TestRequest::Fields::INTEGER:
-          ASSERT_EQ(OkStatus(), decoder.ReadInt64(&last_request.integer));
-          break;
-        case TestRequest::Fields::STATUS_CODE:
-          ASSERT_EQ(OkStatus(), decoder.ReadUint32(&last_request.status_code));
-          break;
-      }
-    }
-  };
-
-  struct {
-    int64_t integer;
-    uint32_t status_code;
-  } last_request;
-
-  RawServerWriter last_writer;
-  RawServerReader last_reader;
-  RawServerReaderWriter last_reader_writer;
-};
-
-constexpr const RawMethod& kAsyncUnary0 =
-    std::get<0>(FakeServiceBase<FakeService>::kMethods).raw_method();
-constexpr const RawMethod& kAsyncUnary1 =
-    std::get<1>(FakeServiceBase<FakeService>::kMethods).raw_method();
-constexpr const RawMethod& kServerStream =
-    std::get<2>(FakeServiceBase<FakeService>::kMethods).raw_method();
-constexpr const RawMethod& kClientStream =
-    std::get<3>(FakeServiceBase<FakeService>::kMethods).raw_method();
-constexpr const RawMethod& kBidirectionalStream =
-    std::get<4>(FakeServiceBase<FakeService>::kMethods).raw_method();
-
-TEST(RawMethod, AsyncUnaryRpc1_SendsResponse) {
-  std::byte buffer[16];
-  stream::MemoryWriter writer(buffer);
-  TestRequest::StreamEncoder test_request(writer, ByteSpan());
-  ASSERT_EQ(OkStatus(), test_request.WriteInteger(456));
-  ASSERT_EQ(OkStatus(), test_request.WriteStatusCode(7));
-
-  ServerContextForTest<FakeService> context(kAsyncUnary1);
-  rpc_lock().lock();
-  kAsyncUnary1.Invoke(context.get(), context.request(writer.WrittenData()));
-
-  EXPECT_EQ(context.service().last_request.integer, 456);
-  EXPECT_EQ(context.service().last_request.status_code, 7u);
-
-  const Packet& response = context.output().last_packet();
-  EXPECT_EQ(response.status(), Status::Unauthenticated());
-
-  protobuf::Decoder decoder(response.payload());
-  ASSERT_TRUE(decoder.Next().ok());
-  int64_t value;
-  EXPECT_EQ(decoder.ReadInt64(&value), OkStatus());
-  EXPECT_EQ(value, 461);
-}
-
-TEST(RawMethod, AsyncUnaryRpc0_SendsResponse) {
-  ServerContextForTest<FakeService> context(kAsyncUnary0);
-
-  rpc_lock().lock();
-  kAsyncUnary0.Invoke(context.get(), context.request({}));
-
-  const Packet& packet = context.output().last_packet();
-  EXPECT_EQ(PacketType::RESPONSE, packet.type());
-  EXPECT_EQ(Status::Unknown(), packet.status());
-  EXPECT_EQ(context.service_id(), packet.service_id());
-  EXPECT_EQ(kAsyncUnary0.id(), packet.method_id());
-}
-
-TEST(RawMethod, ServerStreamingRpc_SendsNothingWhenInitiallyCalled) {
-  std::byte buffer[16];
-  stream::MemoryWriter writer(buffer);
-  TestRequest::StreamEncoder test_request(writer, ByteSpan());
-  ASSERT_EQ(OkStatus(), test_request.WriteInteger(777));
-  ASSERT_EQ(OkStatus(), test_request.WriteStatusCode(2));
-
-  ServerContextForTest<FakeService> context(kServerStream);
-  rpc_lock().lock();
-  kServerStream.Invoke(context.get(), context.request(writer.WrittenData()));
-
-  EXPECT_EQ(0u, context.output().total_packets());
-  EXPECT_EQ(777, context.service().last_request.integer);
-  EXPECT_EQ(2u, context.service().last_request.status_code);
-  EXPECT_TRUE(context.service().last_writer.active());
-  EXPECT_EQ(OkStatus(), context.service().last_writer.Finish());
-}
-
-TEST(RawMethod, ServerReader_HandlesRequests) {
-  ServerContextForTest<FakeService> context(kClientStream);
-  rpc_lock().lock();
-  kClientStream.Invoke(context.get(), context.request({}));
-
-  ConstByteSpan request;
-  context.service().last_reader.set_on_next(
-      [&request](ConstByteSpan req) { request = req; });
-
-  constexpr const char kRequestValue[] = "This is a request payload!!!";
-  std::array<std::byte, 128> encoded_request = {};
-  auto encoded = context.client_stream(std::as_bytes(std::span(kRequestValue)))
-                     .Encode(encoded_request);
-  ASSERT_EQ(OkStatus(), encoded.status());
-  ASSERT_EQ(OkStatus(),
-            context.server().ProcessPacket(*encoded, context.output()));
-
-  EXPECT_STREQ(reinterpret_cast<const char*>(request.data()), kRequestValue);
-}
-
-TEST(RawMethod, ServerReaderWriter_WritesResponses) {
-  ServerContextForTest<FakeService> context(kBidirectionalStream);
-  rpc_lock().lock();
-  kBidirectionalStream.Invoke(context.get(), context.request({}));
-
-  constexpr const char kRequestValue[] = "O_o";
-  const auto kRequestBytes = std::as_bytes(std::span(kRequestValue));
-  EXPECT_EQ(OkStatus(),
-            context.service().last_reader_writer.Write(kRequestBytes));
-
-  std::array<std::byte, 128> encoded_response = {};
-  auto encoded = context.server_stream(kRequestBytes).Encode(encoded_response);
-  ASSERT_EQ(OkStatus(), encoded.status());
-
-  ConstByteSpan sent_payload = context.output().last_packet().payload();
-  EXPECT_TRUE(std::equal(kRequestBytes.begin(),
-                         kRequestBytes.end(),
-                         sent_payload.begin(),
-                         sent_payload.end()));
-}
-
-TEST(RawServerWriter, Write_SendsPayload) {
-  ServerContextForTest<FakeService> context(kServerStream);
-  rpc_lock().lock();
-  kServerStream.Invoke(context.get(), context.request({}));
-
-  constexpr auto data = bytes::Array<0x0d, 0x06, 0xf0, 0x0d>();
-  EXPECT_EQ(context.service().last_writer.Write(data), OkStatus());
-
-  const internal::Packet& packet = context.output().last_packet();
-  EXPECT_EQ(packet.type(), internal::PacketType::SERVER_STREAM);
-  EXPECT_EQ(packet.channel_id(), context.channel_id());
-  EXPECT_EQ(packet.service_id(), context.service_id());
-  EXPECT_EQ(packet.method_id(), context.get().method().id());
-  EXPECT_EQ(std::memcmp(packet.payload().data(), data.data(), data.size()), 0);
-  EXPECT_EQ(packet.status(), OkStatus());
-}
-
-TEST(RawServerWriter, Write_EmptyBuffer) {
-  ServerContextForTest<FakeService> context(kServerStream);
-  rpc_lock().lock();
-  kServerStream.Invoke(context.get(), context.request({}));
-
-  ASSERT_EQ(context.service().last_writer.Write({}), OkStatus());
-
-  const internal::Packet& packet = context.output().last_packet();
-  EXPECT_EQ(packet.type(), internal::PacketType::SERVER_STREAM);
-  EXPECT_EQ(packet.channel_id(), context.channel_id());
-  EXPECT_EQ(packet.service_id(), context.service_id());
-  EXPECT_EQ(packet.method_id(), context.get().method().id());
-  EXPECT_TRUE(packet.payload().empty());
-  EXPECT_EQ(packet.status(), OkStatus());
-}
-
-TEST(RawServerWriter, Write_Closed_ReturnsFailedPrecondition) {
-  ServerContextForTest<FakeService> context(kServerStream);
-  rpc_lock().lock();
-  kServerStream.Invoke(context.get(), context.request({}));
-
-  EXPECT_EQ(OkStatus(), context.service().last_writer.Finish());
-  constexpr auto data = bytes::Array<0x0d, 0x06, 0xf0, 0x0d>();
-  EXPECT_EQ(context.service().last_writer.Write(data),
-            Status::FailedPrecondition());
-}
-
-TEST(RawServerWriter, Write_PayloadTooLargeForEncodingBuffer_ReturnsInternal) {
-  ServerContextForTest<FakeService> context(kServerStream);
-  rpc_lock().lock();
-  kServerStream.Invoke(context.get(), context.request({}));
-
-  // A kEncodingBufferSizeBytes payload will never fit in the encoding buffer.
-  static constexpr std::array<std::byte, cfg::kEncodingBufferSizeBytes>
-      kBigData = {};
-  EXPECT_EQ(context.service().last_writer.Write(kBigData), Status::Internal());
-}
-
-}  // namespace
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/raw/method_union_test.cc b/pw_rpc/raw/method_union_test.cc
deleted file mode 100644
index a464787..0000000
--- a/pw_rpc/raw/method_union_test.cc
+++ /dev/null
@@ -1,150 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/raw/internal/method_union.h"
-
-#include <array>
-
-#include "gtest/gtest.h"
-#include "pw_bytes/array.h"
-#include "pw_protobuf/decoder.h"
-#include "pw_protobuf/encoder.h"
-#include "pw_rpc/internal/test_utils.h"
-#include "pw_rpc/service.h"
-#include "pw_rpc_test_protos/test.pwpb.h"
-
-namespace pw::rpc::internal {
-namespace {
-
-namespace TestRequest = ::pw::rpc::test::TestRequest;
-namespace TestResponse = ::pw::rpc::test::TestResponse;
-
-template <typename Implementation>
-class FakeGeneratedService : public Service {
- public:
-  constexpr FakeGeneratedService(uint32_t id) : Service(id, kMethods) {}
-
-  static constexpr std::array<RawMethodUnion, 3> kMethods = {
-      GetRawMethodFor<&Implementation::DoNothing, MethodType::kUnary>(10u),
-      GetRawMethodFor<&Implementation::AddFive, MethodType::kUnary>(11u),
-      GetRawMethodFor<&Implementation::StartStream,
-                      MethodType::kServerStreaming>(12u),
-  };
-};
-
-class FakeGeneratedServiceImpl
-    : public FakeGeneratedService<FakeGeneratedServiceImpl> {
- public:
-  FakeGeneratedServiceImpl(uint32_t id) : FakeGeneratedService(id) {}
-
-  void DoNothing(ConstByteSpan, RawUnaryResponder&) {}
-
-  void AddFive(ConstByteSpan request, RawUnaryResponder& responder) {
-    DecodeRawTestRequest(request);
-
-    std::byte response[32] = {};
-    TestResponse::MemoryEncoder test_response(response);
-    ASSERT_EQ(OkStatus(), test_response.WriteValue(last_request.integer + 5));
-
-    ASSERT_EQ(OkStatus(),
-              responder.Finish(std::span(response).first(test_response.size()),
-                               Status::Unauthenticated()));
-  }
-
-  void StartStream(ConstByteSpan request, RawServerWriter& writer) {
-    DecodeRawTestRequest(request);
-    last_writer = std::move(writer);
-  }
-
-  struct {
-    int64_t integer;
-    uint32_t status_code;
-  } last_request;
-  RawServerWriter last_writer;
-
- private:
-  void DecodeRawTestRequest(ConstByteSpan request) {
-    protobuf::Decoder decoder(request);
-
-    while (decoder.Next().ok()) {
-      TestRequest::Fields field =
-          static_cast<TestRequest::Fields>(decoder.FieldNumber());
-
-      switch (field) {
-        case TestRequest::Fields::INTEGER:
-          decoder.ReadInt64(&last_request.integer)
-              .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-          break;
-        case TestRequest::Fields::STATUS_CODE:
-          decoder.ReadUint32(&last_request.status_code)
-              .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-          break;
-      }
-    }
-  }
-};
-
-TEST(RawMethodUnion, InvokesUnary) {
-  std::byte buffer[16];
-
-  TestRequest::MemoryEncoder test_request(buffer);
-  test_request.WriteInteger(456)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  test_request.WriteStatusCode(7)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-
-  const Method& method =
-      std::get<1>(FakeGeneratedServiceImpl::kMethods).method();
-  ServerContextForTest<FakeGeneratedServiceImpl> context(method);
-  rpc_lock().lock();
-  method.Invoke(context.get(), context.request(test_request));
-
-  EXPECT_EQ(context.service().last_request.integer, 456);
-  EXPECT_EQ(context.service().last_request.status_code, 7u);
-
-  const Packet& response = context.output().last_packet();
-  EXPECT_EQ(response.status(), Status::Unauthenticated());
-
-  protobuf::Decoder decoder(response.payload());
-  ASSERT_TRUE(decoder.Next().ok());
-  int64_t value;
-  EXPECT_EQ(decoder.ReadInt64(&value), OkStatus());
-  EXPECT_EQ(value, 461);
-}
-
-TEST(RawMethodUnion, InvokesServerStreaming) {
-  std::byte buffer[16];
-
-  TestRequest::MemoryEncoder test_request(buffer);
-  test_request.WriteInteger(777)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  test_request.WriteStatusCode(2)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-
-  const Method& method =
-      std::get<2>(FakeGeneratedServiceImpl::kMethods).method();
-  ServerContextForTest<FakeGeneratedServiceImpl> context(method);
-
-  rpc_lock().lock();
-  method.Invoke(context.get(), context.request(test_request));
-
-  EXPECT_EQ(0u, context.output().total_packets());
-  EXPECT_EQ(777, context.service().last_request.integer);
-  EXPECT_EQ(2u, context.service().last_request.status_code);
-  EXPECT_TRUE(context.service().last_writer.active());
-  EXPECT_EQ(OkStatus(), context.service().last_writer.Finish());
-}
-
-}  // namespace
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/raw/public/pw_rpc/internal/raw_method.h b/pw_rpc/raw/public/pw_rpc/internal/raw_method.h
new file mode 100644
index 0000000..6d2e898
--- /dev/null
+++ b/pw_rpc/raw/public/pw_rpc/internal/raw_method.h
@@ -0,0 +1,149 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_bytes/span.h"
+#include "pw_rpc/internal/base_server_writer.h"
+#include "pw_rpc/internal/method.h"
+#include "pw_rpc/internal/method_type.h"
+#include "pw_status/status_with_size.h"
+
+namespace pw::rpc {
+
+class RawServerWriter : public internal::BaseServerWriter {
+ public:
+  RawServerWriter() = default;
+  RawServerWriter(RawServerWriter&&) = default;
+  RawServerWriter& operator=(RawServerWriter&&) = default;
+
+  ~RawServerWriter();
+
+  // Returns a buffer in which a response payload can be built.
+  ByteSpan PayloadBuffer() { return AcquirePayloadBuffer(); }
+
+  // Sends a response packet with the given raw payload. The payload can either
+  // be in the buffer previously acquired from PayloadBuffer(), or an arbitrary
+  // external buffer.
+  Status Write(ConstByteSpan response);
+};
+
+namespace internal {
+
+// A RawMethod is a method invoker which does not perform any automatic protobuf
+// serialization or deserialization. The implementer is given the raw binary
+// payload of incoming requests, and is responsible for encoding responses to a
+// provided buffer. This is intended for use in methods which would have large
+// protobuf data structure overhead to lower stack usage, or in methods packing
+// responses up to a channel's MTU.
+class RawMethod : public Method {
+ public:
+  template <auto method>
+  static constexpr bool matches() {
+    return std::is_same_v<MethodImplementation<method>, RawMethod>;
+  }
+
+  template <auto method>
+  static constexpr RawMethod Unary(uint32_t id) {
+    constexpr UnaryFunction wrapper =
+        [](ServerCall& call, ConstByteSpan req, ByteSpan res) {
+          return CallMethodImplFunction<method>(call, req, res);
+        };
+    return RawMethod(id, UnaryInvoker, Function{.unary = wrapper});
+  }
+
+  template <auto method>
+  static constexpr RawMethod ServerStreaming(uint32_t id) {
+    constexpr ServerStreamingFunction wrapper =
+        [](ServerCall& call, ConstByteSpan request, BaseServerWriter& writer) {
+          CallMethodImplFunction<method>(
+              call, request, static_cast<RawServerWriter&>(writer));
+        };
+    return RawMethod(
+        id, ServerStreamingInvoker, Function{.server_streaming = wrapper});
+  }
+
+  // Represents an invalid method. Used to reduce error message verbosity.
+  static constexpr RawMethod Invalid() { return {0, InvalidInvoker, {}}; }
+
+ private:
+  using UnaryFunction = StatusWithSize (*)(ServerCall&,
+                                           ConstByteSpan,
+                                           ByteSpan);
+
+  using ServerStreamingFunction = void (*)(ServerCall&,
+                                           ConstByteSpan,
+                                           BaseServerWriter&);
+  union Function {
+    UnaryFunction unary;
+    ServerStreamingFunction server_streaming;
+    // TODO(frolv): Support client and bidirectional streaming.
+  };
+
+  constexpr RawMethod(uint32_t id, Invoker invoker, Function function)
+      : Method(id, invoker), function_(function) {}
+
+  static void UnaryInvoker(const Method& method,
+                           ServerCall& call,
+                           const Packet& request) {
+    static_cast<const RawMethod&>(method).CallUnary(call, request);
+  }
+
+  static void ServerStreamingInvoker(const Method& method,
+                                     ServerCall& call,
+                                     const Packet& request) {
+    static_cast<const RawMethod&>(method).CallServerStreaming(call, request);
+  }
+
+  void CallUnary(ServerCall& call, const Packet& request) const;
+  void CallServerStreaming(ServerCall& call, const Packet& request) const;
+
+  // Stores the user-defined RPC in a generic wrapper.
+  Function function_;
+};
+
+// MethodTraits specialization for a static raw unary method.
+template <>
+struct MethodTraits<StatusWithSize (*)(
+    ServerContext&, ConstByteSpan, ByteSpan)> {
+  using Implementation = RawMethod;
+  static constexpr MethodType kType = MethodType::kUnary;
+};
+
+// MethodTraits specialization for a raw unary method.
+template <typename T>
+struct MethodTraits<StatusWithSize (T::*)(
+    ServerContext&, ConstByteSpan, ByteSpan)> {
+  using Implementation = RawMethod;
+  static constexpr MethodType kType = MethodType::kUnary;
+  using Service = T;
+};
+
+// MethodTraits specialization for a static raw server streaming method.
+template <>
+struct MethodTraits<void (*)(ServerContext&, ConstByteSpan, RawServerWriter&)> {
+  using Implementation = RawMethod;
+  static constexpr MethodType kType = MethodType::kServerStreaming;
+};
+
+// MethodTraits specialization for a raw server streaming method.
+template <typename T>
+struct MethodTraits<void (T::*)(
+    ServerContext&, ConstByteSpan, RawServerWriter&)> {
+  using Implementation = RawMethod;
+  static constexpr MethodType kType = MethodType::kServerStreaming;
+  using Service = T;
+};
+
+}  // namespace internal
+}  // namespace pw::rpc
diff --git a/pw_rpc/raw/public/pw_rpc/internal/raw_method_union.h b/pw_rpc/raw/public/pw_rpc/internal/raw_method_union.h
new file mode 100644
index 0000000..7b377d7
--- /dev/null
+++ b/pw_rpc/raw/public/pw_rpc/internal/raw_method_union.h
@@ -0,0 +1,50 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_bytes/span.h"
+#include "pw_rpc/internal/method_union.h"
+#include "pw_rpc/internal/raw_method.h"
+
+namespace pw::rpc::internal {
+
+// MethodUnion which stores only a raw method. For use in fully raw RPC
+// services, without any additional memory overhead.
+class RawMethodUnion : public MethodUnion {
+ public:
+  constexpr RawMethodUnion(RawMethod&& method)
+      : impl_({.raw = std::move(method)}) {}
+
+  constexpr const Method& method() const { return impl_.method; }
+  constexpr const RawMethod& raw_method() const { return impl_.raw; }
+
+ private:
+  union {
+    Method method;
+    RawMethod raw;
+  } impl_;
+};
+
+// Deduces the type of an implemented service method from its signature, and
+// returns the appropriate MethodUnion object to invoke it.
+template <auto method, MethodType type>
+constexpr RawMethod GetRawMethodFor(uint32_t id) {
+  if constexpr (RawMethod::matches<method>()) {
+    return GetMethodFor<method, RawMethod, type>(id);
+  } else {
+    return InvalidMethod<method, type, RawMethod>(id);
+  }
+};
+
+}  // namespace pw::rpc::internal
diff --git a/pw_rpc/raw/public/pw_rpc/raw/client_reader_writer.h b/pw_rpc/raw/public/pw_rpc/raw/client_reader_writer.h
deleted file mode 100644
index 5e21a59..0000000
--- a/pw_rpc/raw/public/pw_rpc/raw/client_reader_writer.h
+++ /dev/null
@@ -1,165 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// This file defines the ServerReaderWriter, ServerReader, and ServerWriter
-// classes for the raw RPC interface. These classes are used for bidirectional,
-// client, and server streaming RPCs.
-#pragma once
-
-#include "pw_bytes/span.h"
-#include "pw_rpc/channel.h"
-#include "pw_rpc/internal/client_call.h"
-#include "pw_rpc/writer.h"
-
-namespace pw::rpc {
-
-// Sends requests and handles responses for a bidirectional streaming RPC.
-//
-// These classes use private inheritance to hide the internal::Call API while
-// allow direct use of its public and protected functions.
-class RawClientReaderWriter : private internal::StreamResponseClientCall {
- public:
-  constexpr RawClientReaderWriter() = default;
-
-  RawClientReaderWriter(RawClientReaderWriter&&) = default;
-  RawClientReaderWriter& operator=(RawClientReaderWriter&&) = default;
-
-  using internal::Call::active;
-  using internal::Call::channel_id;
-
-  // Functions for setting the callbacks.
-  using internal::StreamResponseClientCall::set_on_completed;
-  using internal::StreamResponseClientCall::set_on_error;
-  using internal::StreamResponseClientCall::set_on_next;
-
-  // Sends a stream request packet with the given raw payload.
-  using internal::Call::Write;
-
-  // Notifies the server that no further client stream messages will be sent.
-  using internal::Call::CloseClientStream;
-
-  // Cancels this RPC.
-  using internal::Call::Cancel;
-
-  // Allow use as a generic RPC Writer.
-  using internal::Call::operator Writer&;
-  using internal::Call::operator const Writer&;
-
- protected:
-  friend class internal::StreamResponseClientCall;
-
-  RawClientReaderWriter(internal::Endpoint& client,
-                        uint32_t channel_id,
-                        uint32_t service_id,
-                        uint32_t method_id,
-                        MethodType type = MethodType::kBidirectionalStreaming)
-      : StreamResponseClientCall(
-            client, channel_id, service_id, method_id, type) {}
-};
-
-// Handles responses for a server streaming RPC.
-class RawClientReader : private internal::StreamResponseClientCall {
- public:
-  constexpr RawClientReader() = default;
-
-  RawClientReader(RawClientReader&&) = default;
-  RawClientReader& operator=(RawClientReader&&) = default;
-
-  using internal::StreamResponseClientCall::active;
-  using internal::StreamResponseClientCall::channel_id;
-
-  using internal::StreamResponseClientCall::set_on_completed;
-  using internal::StreamResponseClientCall::set_on_error;
-  using internal::StreamResponseClientCall::set_on_next;
-
-  using internal::Call::Cancel;
-
- private:
-  friend class internal::StreamResponseClientCall;
-
-  RawClientReader(internal::Endpoint& client,
-                  uint32_t channel_id,
-                  uint32_t service_id,
-                  uint32_t method_id)
-      : StreamResponseClientCall(client,
-                                 channel_id,
-                                 service_id,
-                                 method_id,
-                                 MethodType::kServerStreaming) {}
-};
-
-// Sends requests and handles the response for a client streaming RPC.
-class RawClientWriter : private internal::UnaryResponseClientCall {
- public:
-  constexpr RawClientWriter() = default;
-
-  RawClientWriter(RawClientWriter&&) = default;
-  RawClientWriter& operator=(RawClientWriter&&) = default;
-
-  using internal::UnaryResponseClientCall::active;
-  using internal::UnaryResponseClientCall::channel_id;
-
-  using internal::UnaryResponseClientCall::set_on_completed;
-  using internal::UnaryResponseClientCall::set_on_error;
-
-  using internal::Call::Cancel;
-  using internal::Call::CloseClientStream;
-  using internal::Call::Write;
-
-  // Allow use as a generic RPC Writer.
-  using internal::Call::operator Writer&;
-  using internal::Call::operator const Writer&;
-
- private:
-  friend class internal::UnaryResponseClientCall;
-
-  RawClientWriter(internal::Endpoint& client,
-                  uint32_t channel_id,
-                  uint32_t service_id,
-                  uint32_t method_id)
-      : UnaryResponseClientCall(client,
-                                channel_id,
-                                service_id,
-                                method_id,
-                                MethodType::kClientStreaming) {}
-};
-
-// Handles the response for to unary RPC.
-class RawUnaryReceiver : private internal::UnaryResponseClientCall {
- public:
-  constexpr RawUnaryReceiver() = default;
-
-  RawUnaryReceiver(RawUnaryReceiver&&) = default;
-  RawUnaryReceiver& operator=(RawUnaryReceiver&&) = default;
-
-  using internal::UnaryResponseClientCall::active;
-  using internal::UnaryResponseClientCall::channel_id;
-
-  using internal::UnaryResponseClientCall::set_on_completed;
-  using internal::UnaryResponseClientCall::set_on_error;
-
-  using internal::UnaryResponseClientCall::Cancel;
-
- private:
-  friend class internal::UnaryResponseClientCall;
-
-  RawUnaryReceiver(internal::Endpoint& client,
-                   uint32_t channel_id,
-                   uint32_t service_id,
-                   uint32_t method_id)
-      : UnaryResponseClientCall(
-            client, channel_id, service_id, method_id, MethodType::kUnary) {}
-};
-
-}  // namespace pw::rpc
diff --git a/pw_rpc/raw/public/pw_rpc/raw/client_testing.h b/pw_rpc/raw/public/pw_rpc/raw/client_testing.h
deleted file mode 100644
index 436e59e..0000000
--- a/pw_rpc/raw/public/pw_rpc/raw/client_testing.h
+++ /dev/null
@@ -1,142 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstddef>
-#include <cstdint>
-#include <type_traits>
-
-#include "pw_bytes/span.h"
-#include "pw_rpc/client.h"
-#include "pw_rpc/internal/method_info.h"
-#include "pw_rpc/internal/packet.h"
-#include "pw_rpc/method_type.h"
-#include "pw_rpc/raw/fake_channel_output.h"
-
-namespace pw::rpc {
-
-// TODO(pwbug/477): Document the client testing APIs.
-
-// Sends packets to an RPC client as if it were a pw_rpc server.
-class FakeServer {
- public:
-  constexpr FakeServer(internal::test::FakeChannelOutput& output,
-                       Client& client,
-                       uint32_t channel_id,
-                       ByteSpan packet_buffer)
-      : output_(output),
-        client_(client),
-        channel_id_(channel_id),
-        packet_buffer_(packet_buffer) {}
-
-  // Sends a response packet for a server or bidirectional streaming RPC to the
-  // client.
-  template <auto kMethod,
-            typename = std::enable_if_t<
-                HasServerStream(internal::MethodInfo<kMethod>::kType)>>
-  void SendResponse(Status status) const {
-    SendPacket<kMethod>(internal::PacketType::RESPONSE, {}, status);
-  }
-
-  // Sends a response packet for a unary or client streaming streaming RPC to
-  // the client.
-  template <auto kMethod,
-            typename = std::enable_if_t<
-                !HasServerStream(internal::MethodInfo<kMethod>::kType)>>
-  void SendResponse(ConstByteSpan payload, Status status) const {
-    SendPacket<kMethod>(internal::PacketType::RESPONSE, payload, status);
-  }
-
-  // Sends a stream packet for a server or bidirectional streaming RPC to the
-  // client.
-  template <auto kMethod>
-  void SendServerStream(ConstByteSpan payload) const {
-    static_assert(HasServerStream(internal::MethodInfo<kMethod>::kType),
-                  "Only server and bidirectional streaming methods can receive "
-                  "server stream packets");
-    SendPacket<kMethod>(internal::PacketType::SERVER_STREAM, payload);
-  }
-
-  // Sends a server error packet to the client.
-  template <auto kMethod>
-  void SendServerError(Status error) const {
-    SendPacket<kMethod>(internal::PacketType::SERVER_ERROR, {}, error);
-  }
-
- private:
-  template <auto kMethod>
-  void SendPacket(internal::PacketType type,
-                  ConstByteSpan payload = {},
-                  Status status = OkStatus()) const {
-    using Info = internal::MethodInfo<kMethod>;
-    CheckProcessPacket(
-        type, Info::kServiceId, Info::kMethodId, payload, status);
-  }
-
-  void CheckProcessPacket(internal::PacketType type,
-                          uint32_t service_id,
-                          uint32_t method_id,
-                          ConstByteSpan payload,
-                          Status status) const;
-
-  Status ProcessPacket(internal::PacketType type,
-                       uint32_t service_id,
-                       uint32_t method_id,
-                       ConstByteSpan payload,
-                       Status status) const;
-
-  internal::test::FakeChannelOutput& output_;
-  Client& client_;
-  const uint32_t channel_id_;
-  ByteSpan packet_buffer_;  // For encoding packets sent by the server
-};
-
-// Instantiates a FakeServer, Client, Channel, and RawFakeChannelOutput for
-// testing RPC client calls. These components may be used individually, but are
-// instantiated together for convenience.
-template <size_t kMaxPackets = 10,
-          size_t kPacketEncodeBufferSizeBytes = 128,
-          size_t kPayloadsBufferSizeBytes = 256>
-class RawClientTestContext {
- public:
-  static constexpr uint32_t kDefaultChannelId = 1;
-
-  constexpr RawClientTestContext()
-      : channel_(Channel::Create<kDefaultChannelId>(&channel_output_)),
-        client_(std::span(&channel_, 1)),
-        packet_buffer_{},
-        fake_server_(
-            channel_output_, client_, kDefaultChannelId, packet_buffer_) {}
-
-  const Channel& channel() const { return channel_; }
-  Channel& channel() { return channel_; }
-
-  const FakeServer& server() const { return fake_server_; }
-  FakeServer& server() { return fake_server_; }
-
-  const Client& client() const { return client_; }
-  Client& client() { return client_; }
-
-  const auto& output() const { return channel_output_; }
-  auto& output() { return channel_output_; }
-
- private:
-  RawFakeChannelOutput<kMaxPackets, kPayloadsBufferSizeBytes> channel_output_;
-  Channel channel_;
-  Client client_;
-  std::byte packet_buffer_[kPacketEncodeBufferSizeBytes];
-  FakeServer fake_server_;
-};
-
-}  // namespace pw::rpc
diff --git a/pw_rpc/raw/public/pw_rpc/raw/fake_channel_output.h b/pw_rpc/raw/public/pw_rpc/raw/fake_channel_output.h
deleted file mode 100644
index bc52925..0000000
--- a/pw_rpc/raw/public/pw_rpc/raw/fake_channel_output.h
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstddef>
-
-#include "pw_rpc/internal/fake_channel_output.h"
-
-namespace pw::rpc {
-
-// A ChannelOutput implementation that stores the outgoing payloads and status.
-template <size_t kMaxPackets, size_t kPayloadsBufferSizeBytes = 128>
-class RawFakeChannelOutput final
-    : public internal::test::FakeChannelOutputBuffer<kMaxPackets,
-                                                     kPayloadsBufferSizeBytes> {
- public:
-  RawFakeChannelOutput() = default;
-
- private:
-  using internal::test::FakeChannelOutput::last_packet;
-};
-
-}  // namespace pw::rpc
diff --git a/pw_rpc/raw/public/pw_rpc/raw/internal/method.h b/pw_rpc/raw/public/pw_rpc/raw/internal/method.h
deleted file mode 100644
index 0ab0222..0000000
--- a/pw_rpc/raw/public/pw_rpc/raw/internal/method.h
+++ /dev/null
@@ -1,229 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_bytes/span.h"
-#include "pw_rpc/internal/method.h"
-#include "pw_rpc/method_type.h"
-#include "pw_rpc/raw/server_reader_writer.h"
-#include "pw_rpc/service.h"
-#include "pw_status/status_with_size.h"
-
-namespace pw::rpc::internal {
-
-// A RawMethod is a method invoker which does not perform any automatic protobuf
-// serialization or deserialization. The implementer is given the raw binary
-// payload of incoming requests, and is responsible for encoding responses to a
-// provided buffer. This is intended for use in methods which would have large
-// protobuf data structure overhead to lower stack usage, or in methods packing
-// responses up to a channel's MTU.
-class RawMethod : public Method {
- public:
-  template <auto kMethod>
-  static constexpr bool matches() {
-    return std::is_same_v<MethodImplementation<kMethod>, RawMethod>;
-  }
-
-  template <auto kMethod>
-  static constexpr RawMethod SynchronousUnary(uint32_t id) {
-    static_assert(sizeof(kMethod) != sizeof(kMethod),
-                  "Raw synchronous unary methods are not supported. "
-                  "Use an asynchronous unary method instead: "
-                  "void MethodName(pw::ConstByteSpan request, "
-                  "pw::rpc::RawUnaryResponder& responder)");
-    return {id, InvalidInvoker, {}};
-  }
-
-  template <auto kMethod>
-  static constexpr RawMethod AsynchronousUnary(uint32_t id) {
-    constexpr AsynchronousUnaryFunction wrapper =
-        [](Service& service, ConstByteSpan req, RawUnaryResponder& responder) {
-          return CallMethodImplFunction<kMethod>(service, req, responder);
-        };
-    return RawMethod(
-        id, AsynchronousUnaryInvoker, Function{.asynchronous_unary = wrapper});
-  }
-
-  template <auto kMethod>
-  static constexpr RawMethod ServerStreaming(uint32_t id) {
-    constexpr ServerStreamingFunction wrapper =
-        [](Service& service, ConstByteSpan request, RawServerWriter& writer) {
-          return CallMethodImplFunction<kMethod>(service, request, writer);
-        };
-    return RawMethod(
-        id, ServerStreamingInvoker, Function{.server_streaming = wrapper});
-  }
-
-  template <auto kMethod>
-  static constexpr RawMethod ClientStreaming(uint32_t id) {
-    constexpr StreamRequestFunction wrapper =
-        [](Service& service, RawServerReaderWriter& reader) {
-          return CallMethodImplFunction<kMethod>(
-              service, static_cast<RawServerReader&>(reader));
-        };
-    return RawMethod(
-        id, ClientStreamingInvoker, Function{.stream_request = wrapper});
-  }
-
-  template <auto kMethod>
-  static constexpr RawMethod BidirectionalStreaming(uint32_t id) {
-    constexpr StreamRequestFunction wrapper =
-        [](Service& service, RawServerReaderWriter& reader_writer) {
-          return CallMethodImplFunction<kMethod>(service, reader_writer);
-        };
-    return RawMethod(
-        id, BidirectionalStreamingInvoker, Function{.stream_request = wrapper});
-  }
-
-  // Represents an invalid method. Used to reduce error message verbosity.
-  static constexpr RawMethod Invalid() { return {0, InvalidInvoker, {}}; }
-
- private:
-  // Wraps the user-defined functions.
-  using SynchronousUnaryFunction = StatusWithSize (*)(Service&,
-                                                      ConstByteSpan,
-                                                      ByteSpan);
-
-  using AsynchronousUnaryFunction = void (*)(Service&,
-                                             ConstByteSpan,
-                                             RawUnaryResponder&);
-
-  using ServerStreamingFunction = void (*)(Service&,
-                                           ConstByteSpan,
-                                           RawServerWriter&);
-
-  using StreamRequestFunction = void (*)(Service&, RawServerReaderWriter&);
-
-  union Function {
-    SynchronousUnaryFunction synchronous_unary;
-    AsynchronousUnaryFunction asynchronous_unary;
-    ServerStreamingFunction server_streaming;
-    StreamRequestFunction stream_request;
-  };
-
-  constexpr RawMethod(uint32_t id, Invoker invoker, Function function)
-      : Method(id, invoker), function_(function) {}
-
-  static void SynchronousUnaryInvoker(const CallContext& context,
-                                      const Packet& request)
-      PW_UNLOCK_FUNCTION(rpc_lock());
-
-  static void AsynchronousUnaryInvoker(const CallContext& context,
-                                       const Packet& request)
-      PW_UNLOCK_FUNCTION(rpc_lock());
-
-  static void ServerStreamingInvoker(const CallContext& context,
-                                     const Packet& request)
-      PW_UNLOCK_FUNCTION(rpc_lock());
-
-  static void ClientStreamingInvoker(const CallContext& context, const Packet&)
-      PW_UNLOCK_FUNCTION(rpc_lock());
-
-  static void BidirectionalStreamingInvoker(const CallContext& context,
-                                            const Packet&)
-      PW_UNLOCK_FUNCTION(rpc_lock());
-
-  // Stores the user-defined RPC.
-  Function function_;
-};
-
-// Expected function signatures for user-implemented RPC functions.
-using RawSynchronousUnary = StatusWithSize(ConstByteSpan, ByteSpan);
-using RawAsynchronousUnary = void(ConstByteSpan, RawUnaryResponder&);
-using RawServerStreaming = void(ConstByteSpan, RawServerWriter&);
-using RawClientStreaming = void(RawServerReader&);
-using RawBidirectionalStreaming = void(RawServerReaderWriter&);
-
-// MethodTraits specialization for a static synchronous raw unary method.
-template <>
-struct MethodTraits<RawSynchronousUnary*> {
-  using Implementation = RawMethod;
-
-  static constexpr MethodType kType = MethodType::kUnary;
-  static constexpr bool kSynchronous = true;
-
-  static constexpr bool kServerStreaming = false;
-  static constexpr bool kClientStreaming = false;
-};
-
-// MethodTraits specialization for a synchronous raw unary method.
-template <typename T>
-struct MethodTraits<RawSynchronousUnary(T::*)>
-    : MethodTraits<RawSynchronousUnary*> {
-  using Service = T;
-};
-
-// MethodTraits specialization for a static asynchronous raw unary method.
-template <>
-struct MethodTraits<RawAsynchronousUnary*>
-    : MethodTraits<RawSynchronousUnary*> {
-  static constexpr bool kSynchronous = false;
-};
-
-// MethodTraits specialization for an asynchronous raw unary method.
-template <typename T>
-struct MethodTraits<RawAsynchronousUnary(T::*)>
-    : MethodTraits<RawSynchronousUnary(T::*)> {
-  static constexpr bool kSynchronous = false;
-};
-
-// MethodTraits specialization for a static raw server streaming method.
-template <>
-struct MethodTraits<RawServerStreaming*> {
-  using Implementation = RawMethod;
-  static constexpr MethodType kType = MethodType::kServerStreaming;
-  static constexpr bool kServerStreaming = true;
-  static constexpr bool kClientStreaming = false;
-};
-
-// MethodTraits specialization for a raw server streaming method.
-template <typename T>
-struct MethodTraits<RawServerStreaming(T::*)>
-    : MethodTraits<RawServerStreaming*> {
-  using Service = T;
-};
-
-// MethodTraits specialization for a static raw client streaming method.
-template <>
-struct MethodTraits<RawClientStreaming*> {
-  using Implementation = RawMethod;
-  static constexpr MethodType kType = MethodType::kClientStreaming;
-  static constexpr bool kServerStreaming = false;
-  static constexpr bool kClientStreaming = true;
-};
-
-// MethodTraits specialization for a raw client streaming method.
-template <typename T>
-struct MethodTraits<RawClientStreaming(T::*)>
-    : MethodTraits<RawClientStreaming*> {
-  using Service = T;
-};
-
-// MethodTraits specialization for a static raw bidirectional streaming method.
-template <>
-struct MethodTraits<RawBidirectionalStreaming*> {
-  using Implementation = RawMethod;
-  static constexpr MethodType kType = MethodType::kBidirectionalStreaming;
-  static constexpr bool kServerStreaming = true;
-  static constexpr bool kClientStreaming = true;
-};
-
-// MethodTraits specialization for a raw bidirectional streaming method.
-template <typename T>
-struct MethodTraits<RawBidirectionalStreaming(T::*)>
-    : MethodTraits<RawBidirectionalStreaming*> {
-  using Service = T;
-};
-
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/raw/public/pw_rpc/raw/internal/method_union.h b/pw_rpc/raw/public/pw_rpc/raw/internal/method_union.h
deleted file mode 100644
index d3eb2bc..0000000
--- a/pw_rpc/raw/public/pw_rpc/raw/internal/method_union.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_bytes/span.h"
-#include "pw_rpc/internal/method_union.h"
-#include "pw_rpc/raw/internal/method.h"
-
-namespace pw::rpc::internal {
-
-// MethodUnion which stores only a raw method. For use in fully raw RPC
-// services, without any additional memory overhead.
-class RawMethodUnion : public MethodUnion {
- public:
-  constexpr RawMethodUnion(RawMethod&& method)
-      : impl_({.raw = std::move(method)}) {}
-
-  constexpr const Method& method() const { return impl_.method; }
-  constexpr const RawMethod& raw_method() const { return impl_.raw; }
-
- private:
-  union {
-    Method method;
-    RawMethod raw;
-  } impl_;
-};
-
-// Deduces the type of an implemented service method from its signature, and
-// returns the appropriate MethodUnion object to invoke it.
-template <auto kMethod, MethodType kType>
-constexpr RawMethod GetRawMethodFor(uint32_t id) {
-  if constexpr (RawMethod::matches<kMethod>()) {
-    return GetMethodFor<kMethod, RawMethod, kType>(id);
-  } else {
-    return InvalidMethod<kMethod, kType, RawMethod>(id);
-  }
-};
-
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/raw/public/pw_rpc/raw/server_reader_writer.h b/pw_rpc/raw/public/pw_rpc/raw/server_reader_writer.h
deleted file mode 100644
index 8ff018e..0000000
--- a/pw_rpc/raw/public/pw_rpc/raw/server_reader_writer.h
+++ /dev/null
@@ -1,238 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// This file defines the ServerReaderWriter, ServerReader, and ServerWriter
-// classes for the raw RPC interface. These classes are used for bidirectional,
-// client, and server streaming RPCs.
-#pragma once
-
-#include "pw_bytes/span.h"
-#include "pw_rpc/channel.h"
-#include "pw_rpc/internal/method_info.h"
-#include "pw_rpc/internal/method_lookup.h"
-#include "pw_rpc/internal/server_call.h"
-#include "pw_rpc/server.h"
-#include "pw_rpc/writer.h"
-
-namespace pw::rpc {
-namespace internal {
-
-// Forward declarations for internal classes needed in friend statements.
-class RawMethod;
-
-namespace test {
-
-template <typename, typename, uint32_t>
-class InvocationContext;
-
-}  // namespace test
-}  // namespace internal
-
-class RawServerReader;
-class RawServerWriter;
-
-// The RawServerReaderWriter is used to send and receive messages in a raw
-// bidirectional streaming RPC.
-class RawServerReaderWriter : private internal::ServerCall {
- public:
-  constexpr RawServerReaderWriter() = default;
-
-  RawServerReaderWriter(RawServerReaderWriter&&) = default;
-  RawServerReaderWriter& operator=(RawServerReaderWriter&&) = default;
-
-  // Creates a RawServerReaderWriter that is ready to send responses for a
-  // particular RPC. This can be used for testing or to send responses to an RPC
-  // that has not been started by a client.
-  template <auto kMethod, typename ServiceImpl>
-  [[nodiscard]] static RawServerReaderWriter Open(Server& server,
-                                                  uint32_t channel_id,
-                                                  ServiceImpl& service) {
-    internal::LockGuard lock(internal::rpc_lock());
-    return {server.OpenContext<kMethod, MethodType::kBidirectionalStreaming>(
-        channel_id,
-        service,
-        internal::MethodLookup::GetRawMethod<
-            ServiceImpl,
-            internal::MethodInfo<kMethod>::kMethodId>())};
-  }
-
-  using internal::Call::active;
-  using internal::Call::channel_id;
-
-  // Functions for setting the callbacks.
-  using internal::Call::set_on_error;
-  using internal::Call::set_on_next;
-  using internal::ServerCall::set_on_client_stream_end;
-
-  // Sends a response packet with the given raw payload.
-  using internal::Call::Write;
-
-  Status Finish(Status status = OkStatus()) {
-    return CloseAndSendResponse(status);
-  }
-
-  // Allow use as a generic RPC Writer.
-  using internal::Call::operator Writer&;
-  using internal::Call::operator const Writer&;
-
- protected:
-  RawServerReaderWriter(const internal::CallContext& context,
-                        MethodType type = MethodType::kBidirectionalStreaming)
-      : internal::ServerCall(context, type) {}
-
-  using internal::Call::CloseAndSendResponse;
-
- private:
-  friend class internal::RawMethod;  // Needed to construct
-
-  template <typename, typename, uint32_t>
-  friend class internal::test::InvocationContext;
-};
-
-// The RawServerReader is used to receive messages and send a response in a
-// raw client streaming RPC.
-class RawServerReader : private RawServerReaderWriter {
- public:
-  // Creates a RawServerReader that is ready to send a response to a particular
-  // RPC. This can be used for testing or to finish an RPC that has not been
-  // started by the client.
-  template <auto kMethod, typename ServiceImpl>
-  [[nodiscard]] static RawServerReader Open(Server& server,
-                                            uint32_t channel_id,
-                                            ServiceImpl& service) {
-    internal::LockGuard lock(internal::rpc_lock());
-    return {server.OpenContext<kMethod, MethodType::kClientStreaming>(
-        channel_id,
-        service,
-        internal::MethodLookup::GetRawMethod<
-            ServiceImpl,
-            internal::MethodInfo<kMethod>::kMethodId>())};
-  }
-
-  constexpr RawServerReader() = default;
-
-  RawServerReader(RawServerReader&&) = default;
-  RawServerReader& operator=(RawServerReader&&) = default;
-
-  using RawServerReaderWriter::active;
-  using RawServerReaderWriter::channel_id;
-
-  using RawServerReaderWriter::set_on_client_stream_end;
-  using RawServerReaderWriter::set_on_error;
-  using RawServerReaderWriter::set_on_next;
-
-  Status Finish(ConstByteSpan response, Status status = OkStatus()) {
-    return CloseAndSendResponse(response, status);
-  }
-
- private:
-  friend class internal::RawMethod;  // Needed for conversions from ReaderWriter
-
-  template <typename, typename, uint32_t>
-  friend class internal::test::InvocationContext;
-
-  RawServerReader(const internal::CallContext& context)
-      : RawServerReaderWriter(context, MethodType::kClientStreaming) {}
-};
-
-// The RawServerWriter is used to send responses in a raw server streaming RPC.
-class RawServerWriter : private RawServerReaderWriter {
- public:
-  // Creates a RawServerWriter that is ready to send responses for a particular
-  // RPC. This can be used for testing or to send responses to an RPC that has
-  // not been started by a client.
-  template <auto kMethod, typename ServiceImpl>
-  [[nodiscard]] static RawServerWriter Open(Server& server,
-                                            uint32_t channel_id,
-                                            ServiceImpl& service) {
-    internal::LockGuard lock(internal::rpc_lock());
-    return {server.OpenContext<kMethod, MethodType::kServerStreaming>(
-        channel_id,
-        service,
-        internal::MethodLookup::GetRawMethod<
-            ServiceImpl,
-            internal::MethodInfo<kMethod>::kMethodId>())};
-  }
-
-  constexpr RawServerWriter() = default;
-
-  RawServerWriter(RawServerWriter&&) = default;
-  RawServerWriter& operator=(RawServerWriter&&) = default;
-
-  using RawServerReaderWriter::active;
-  using RawServerReaderWriter::channel_id;
-
-  using RawServerReaderWriter::set_on_error;
-
-  using RawServerReaderWriter::Finish;
-  using RawServerReaderWriter::Write;
-
-  // Allow use as a generic RPC Writer.
-  using internal::Call::operator Writer&;
-  using internal::Call::operator const Writer&;
-
- private:
-  template <typename, typename, uint32_t>
-  friend class internal::test::InvocationContext;
-
-  friend class internal::RawMethod;
-
-  RawServerWriter(const internal::CallContext& context)
-      : RawServerReaderWriter(context, MethodType::kServerStreaming) {}
-};
-
-// The RawUnaryResponder is used to send a response in a raw unary RPC.
-class RawUnaryResponder : private RawServerReaderWriter {
- public:
-  // Creates a RawUnaryResponder that is ready to send responses for a
-  // particular RPC. This can be used for testing or to send responses to an RPC
-  // that has not been started by a client.
-  template <auto kMethod, typename ServiceImpl>
-  [[nodiscard]] static RawUnaryResponder Open(Server& server,
-                                              uint32_t channel_id,
-                                              ServiceImpl& service) {
-    internal::LockGuard lock(internal::rpc_lock());
-    return {server.OpenContext<kMethod, MethodType::kUnary>(
-        channel_id,
-        service,
-        internal::MethodLookup::GetRawMethod<
-            ServiceImpl,
-            internal::MethodInfo<kMethod>::kMethodId>())};
-  }
-
-  constexpr RawUnaryResponder() = default;
-
-  RawUnaryResponder(RawUnaryResponder&&) = default;
-  RawUnaryResponder& operator=(RawUnaryResponder&&) = default;
-
-  using RawServerReaderWriter::active;
-  using RawServerReaderWriter::channel_id;
-
-  using RawServerReaderWriter::set_on_error;
-
-  Status Finish(ConstByteSpan response, Status status = OkStatus()) {
-    return CloseAndSendResponse(response, status);
-  }
-
- private:
-  template <typename, typename, uint32_t>
-  friend class internal::test::InvocationContext;
-
-  friend class internal::RawMethod;
-
-  RawUnaryResponder(const internal::CallContext& context)
-      : RawServerReaderWriter(context, MethodType::kUnary) {}
-};
-
-}  // namespace pw::rpc
diff --git a/pw_rpc/raw/public/pw_rpc/raw/test_method_context.h b/pw_rpc/raw/public/pw_rpc/raw/test_method_context.h
deleted file mode 100644
index e913066..0000000
--- a/pw_rpc/raw/public/pw_rpc/raw/test_method_context.h
+++ /dev/null
@@ -1,264 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <type_traits>
-
-#include "pw_assert/assert.h"
-#include "pw_containers/vector.h"
-#include "pw_preprocessor/arguments.h"
-#include "pw_rpc/channel.h"
-#include "pw_rpc/internal/hash.h"
-#include "pw_rpc/internal/method_lookup.h"
-#include "pw_rpc/internal/packet.h"
-#include "pw_rpc/internal/test_method_context.h"
-#include "pw_rpc/raw/fake_channel_output.h"
-#include "pw_rpc/raw/internal/method.h"
-
-namespace pw::rpc {
-
-// Declares a context object that may be used to invoke an RPC. The context is
-// declared with the name of the implemented service and the method to invoke.
-// The RPC can then be invoked with the call method.
-//
-// For a unary RPC, context.call(request) returns the status, and the response
-// struct can be accessed via context.response().
-//
-//   PW_RAW_TEST_METHOD_CONTEXT(my::CoolService, TheMethod) context;
-//   EXPECT_EQ(OkStatus(), context.call(encoded_request).status());
-//   EXPECT_EQ(0,
-//             std::memcmp(encoded_response,
-//                         context.response().data(),
-//                         sizeof(encoded_response)));
-//
-// For a server streaming RPC, context.call(request) invokes the method. As in a
-// normal RPC, the method completes when the ServerWriter's Finish method is
-// called (or it goes out of scope).
-//
-//   PW_RAW_TEST_METHOD_CONTEXT(my::CoolService, TheStreamingMethod) context;
-//   context.call(encoded_response);
-//
-//   EXPECT_TRUE(context.done());  // Check that the RPC completed
-//   EXPECT_EQ(OkStatus(), context.status());  // Check the status
-//
-//   EXPECT_EQ(3u, context.responses().size());
-//   ByteSpan& response = context.responses()[0];  // check individual responses
-//
-//   for (ByteSpan& response : context.responses()) {
-//     // iterate over the responses
-//   }
-//
-// PW_RAW_TEST_METHOD_CONTEXT forwards its constructor arguments to the
-// underlying service. For example:
-//
-//   PW_RAW_TEST_METHOD_CONTEXT(MyService, Go) context(service, args);
-//
-// PW_RAW_TEST_METHOD_CONTEXT takes one optional arguments:
-//
-//   size_t kMaxPackets: maximum packets to store
-//
-// Example:
-//
-//   PW_RAW_TEST_METHOD_CONTEXT(MyService, BestMethod, 3, 256) context;
-//   ASSERT_EQ(3u, context.responses().max_size());
-//
-#define PW_RAW_TEST_METHOD_CONTEXT(service, method, ...)             \
-  ::pw::rpc::RawTestMethodContext<service,                           \
-                                  &service::method,                  \
-                                  ::pw::rpc::internal::Hash(#method) \
-                                      PW_COMMA_ARGS(__VA_ARGS__)>
-template <typename Service,
-          auto kMethod,
-          uint32_t kMethodId,
-          size_t kMaxPackets = 6>
-class RawTestMethodContext;
-
-// Internal classes that implement RawTestMethodContext.
-namespace internal::test::raw {
-
-inline constexpr size_t kPayloadsBufferSizeBytes = 256;
-
-// Collects everything needed to invoke a particular RPC.
-template <typename Service,
-          auto kMethod,
-          uint32_t kMethodId,
-          size_t kMaxPackets>
-class RawInvocationContext
-    : public InvocationContext<
-          RawFakeChannelOutput<kMaxPackets, kPayloadsBufferSizeBytes>,
-          Service,
-          kMethodId> {
- public:
-  // Gives access to the RPC's most recent response.
-  const ConstByteSpan& response() const { return Base::responses().back(); }
-
- protected:
-  template <typename... Args>
-  RawInvocationContext(Args&&... args)
-      : Base(MethodLookup::GetRawMethod<Service, kMethodId>(),
-             MethodTraits<decltype(kMethod)>::kType,
-             std::forward<Args>(args)...) {}
-
- private:
-  using Base = InvocationContext<
-      RawFakeChannelOutput<kMaxPackets, kPayloadsBufferSizeBytes>,
-      Service,
-      kMethodId>;
-};
-
-// Method invocation context for a unary RPC. Returns the status in call() and
-// provides the response through the response() method.
-template <typename Service, auto kMethod, uint32_t kMethodId>
-class UnaryContext
-    : public RawInvocationContext<Service, kMethod, kMethodId, 1> {
-  using Base = RawInvocationContext<Service, kMethod, kMethodId, 1>;
-
- public:
-  template <typename... Args>
-  UnaryContext(Args&&... args) : Base(std::forward<Args>(args)...) {}
-
-  // Invokes the RPC with the provided request. Returns RPC's StatusWithSize.
-  template <size_t kSynchronousResponseBufferSizeBytes = 64>
-  auto call(ConstByteSpan request) {
-    if constexpr (MethodTraits<decltype(kMethod)>::kSynchronous) {
-      Base::output().clear();
-
-      auto responder = Base::template GetResponder<RawUnaryResponder>();
-      std::byte response[kSynchronousResponseBufferSizeBytes] = {};
-      auto sws = CallMethodImplFunction<kMethod>(
-          Base::service(), request, std::span(response));
-      PW_ASSERT(
-          responder.Finish(std::span(response).first(sws.size()), sws.status())
-              .ok());
-      return sws;
-    } else {
-      Base::template call<kMethod, RawUnaryResponder>(request);
-    }
-  }
-};
-
-// Method invocation context for a server streaming RPC.
-template <typename Service,
-          auto kMethod,
-          uint32_t kMethodId,
-          size_t kMaxPackets>
-class ServerStreamingContext
-    : public RawInvocationContext<Service, kMethod, kMethodId, kMaxPackets> {
-  using Base = RawInvocationContext<Service, kMethod, kMethodId, kMaxPackets>;
-
- public:
-  template <typename... Args>
-  ServerStreamingContext(Args&&... args) : Base(std::forward<Args>(args)...) {}
-
-  // Invokes the RPC with the provided request.
-  void call(ConstByteSpan request) {
-    Base::template call<kMethod, RawServerWriter>(request);
-  }
-
-  // Returns a server writer which writes responses into the context's buffer.
-  // This should not be called alongside call(); use one or the other.
-  RawServerWriter writer() {
-    return Base::template GetResponder<RawServerWriter>();
-  }
-};
-
-// Method invocation context for a client streaming RPC.
-template <typename Service,
-          auto kMethod,
-          uint32_t kMethodId,
-          size_t kMaxPackets>
-class ClientStreamingContext
-    : public RawInvocationContext<Service, kMethod, kMethodId, kMaxPackets> {
-  using Base = RawInvocationContext<Service, kMethod, kMethodId, kMaxPackets>;
-
- public:
-  template <typename... Args>
-  ClientStreamingContext(Args&&... args) : Base(std::forward<Args>(args)...) {}
-
-  // Invokes the RPC.
-  void call() { Base::template call<kMethod, RawServerReader>(); }
-
-  // Returns a reader/writer which writes responses into the context's buffer.
-  // This should not be called alongside call(); use one or the other.
-  RawServerReader reader() {
-    return Base::template GetResponder<RawServerReader>();
-  }
-
-  // Allow sending client streaming packets.
-  using Base::SendClientStream;
-  using Base::SendClientStreamEnd;
-};
-
-// Method invocation context for a bidirectional streaming RPC.
-template <typename Service,
-          auto kMethod,
-          uint32_t kMethodId,
-          size_t kMaxPackets>
-class BidirectionalStreamingContext
-    : public RawInvocationContext<Service, kMethod, kMethodId, kMaxPackets> {
-  using Base = RawInvocationContext<Service, kMethod, kMethodId, kMaxPackets>;
-
- public:
-  template <typename... Args>
-  BidirectionalStreamingContext(Args&&... args)
-      : Base(std::forward<Args>(args)...) {}
-
-  // Invokes the RPC.
-  void call() { Base::template call<kMethod, RawServerReaderWriter>(); }
-
-  // Returns a reader/writer which writes responses into the context's buffer.
-  // This should not be called alongside call(); use one or the other.
-  RawServerReaderWriter reader_writer() {
-    return Base::template GetResponder<RawServerReaderWriter>();
-  }
-
-  // Allow sending client streaming packets.
-  using Base::SendClientStream;
-  using Base::SendClientStreamEnd;
-};
-
-// Alias to select the type of the context object to use based on which type of
-// RPC it is for.
-template <typename Service,
-          auto kMethod,
-          uint32_t kMethodId,
-          size_t kMaxPackets>
-using Context = std::tuple_element_t<
-    static_cast<size_t>(MethodTraits<decltype(kMethod)>::kType),
-    std::tuple<UnaryContext<Service, kMethod, kMethodId>,
-               ServerStreamingContext<Service, kMethod, kMethodId, kMaxPackets>,
-               ClientStreamingContext<Service, kMethod, kMethodId, kMaxPackets>,
-               BidirectionalStreamingContext<Service,
-                                             kMethod,
-                                             kMethodId,
-                                             kMaxPackets>>>;
-
-}  // namespace internal::test::raw
-
-template <typename Service,
-          auto kMethod,
-          uint32_t kMethodId,
-          size_t kMaxPackets>
-class RawTestMethodContext
-    : public internal::test::raw::
-          Context<Service, kMethod, kMethodId, kMaxPackets> {
- public:
-  // Forwards constructor arguments to the service class.
-  template <typename... ServiceArgs>
-  RawTestMethodContext(ServiceArgs&&... service_args)
-      : internal::test::raw::Context<Service, kMethod, kMethodId, kMaxPackets>(
-            std::forward<ServiceArgs>(service_args)...) {}
-};
-
-}  // namespace pw::rpc
diff --git a/pw_rpc/raw/public/pw_rpc/raw_test_method_context.h b/pw_rpc/raw/public/pw_rpc/raw_test_method_context.h
new file mode 100644
index 0000000..321ea61
--- /dev/null
+++ b/pw_rpc/raw/public/pw_rpc/raw_test_method_context.h
@@ -0,0 +1,324 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 <type_traits>
+
+#include "pw_assert/light.h"
+#include "pw_bytes/span.h"
+#include "pw_containers/vector.h"
+#include "pw_rpc/channel.h"
+#include "pw_rpc/internal/hash.h"
+#include "pw_rpc/internal/method_lookup.h"
+#include "pw_rpc/internal/packet.h"
+#include "pw_rpc/internal/raw_method.h"
+#include "pw_rpc/internal/server.h"
+
+namespace pw::rpc {
+
+// Declares a context object that may be used to invoke an RPC. The context is
+// declared with the name of the implemented service and the method to invoke.
+// The RPC can then be invoked with the call method.
+//
+// For a unary RPC, context.call(request) returns the status, and the response
+// struct can be accessed via context.response().
+//
+//   PW_RAW_TEST_METHOD_CONTEXT(my::CoolService, TheMethod) context;
+//   EXPECT_EQ(OkStatus(), context.call(encoded_request).status());
+//   EXPECT_EQ(0,
+//             std::memcmp(encoded_response,
+//                         context.response().data(),
+//                         sizeof(encoded_response)));
+//
+// For a server streaming RPC, context.call(request) invokes the method. As in a
+// normal RPC, the method completes when the ServerWriter's Finish method is
+// called (or it goes out of scope).
+//
+//   PW_RAW_TEST_METHOD_CONTEXT(my::CoolService, TheStreamingMethod) context;
+//   context.call(encoded_response);
+//
+//   EXPECT_TRUE(context.done());  // Check that the RPC completed
+//   EXPECT_EQ(OkStatus(), context.status());  // Check the status
+//
+//   EXPECT_EQ(3u, context.responses().size());
+//   ByteSpan& response = context.responses()[0];  // check individual responses
+//
+//   for (ByteSpan& response : context.responses()) {
+//     // iterate over the responses
+//   }
+//
+// PW_RAW_TEST_METHOD_CONTEXT forwards its constructor arguments to the
+// underlying service. For example:
+//
+//   PW_RAW_TEST_METHOD_CONTEXT(MyService, Go) context(service, args);
+//
+// PW_RAW_TEST_METHOD_CONTEXT takes two optional arguments:
+//
+//   size_t kMaxResponse: maximum responses to store; ignored unless streaming
+//   size_t kOutputSizeBytes: buffer size; must be large enough for a packet
+//
+// Example:
+//
+//   PW_RAW_TEST_METHOD_CONTEXT(MyService, BestMethod, 3, 256) context;
+//   ASSERT_EQ(3u, context.responses().max_size());
+//
+#define PW_RAW_TEST_METHOD_CONTEXT(service, method, ...)              \
+  ::pw::rpc::RawTestMethodContext<service,                            \
+                                  &service::method,                   \
+                                  ::pw::rpc::internal::Hash(#method), \
+                                  ##__VA_ARGS__>
+template <typename Service,
+          auto method,
+          uint32_t kMethodId,
+          size_t kMaxResponse = 4,
+          size_t kOutputSizeBytes = 128>
+class RawTestMethodContext;
+
+// Internal classes that implement RawTestMethodContext.
+namespace internal::test::raw {
+
+// A ChannelOutput implementation that stores the outgoing payloads and status.
+template <size_t kOutputSize>
+class MessageOutput final : public ChannelOutput {
+ public:
+  using ResponseBuffer = std::array<std::byte, kOutputSize>;
+
+  MessageOutput(Vector<ByteSpan>& responses,
+                Vector<ResponseBuffer>& buffers,
+                ByteSpan packet_buffer)
+      : ChannelOutput("internal::test::raw::MessageOutput"),
+        responses_(responses),
+        buffers_(buffers),
+        packet_buffer_(packet_buffer) {
+    clear();
+  }
+
+  Status last_status() const { return last_status_; }
+  void set_last_status(Status status) { last_status_ = status; }
+
+  size_t total_responses() const { return total_responses_; }
+
+  bool stream_ended() const { return stream_ended_; }
+
+  void clear() {
+    responses_.clear();
+    buffers_.clear();
+    total_responses_ = 0;
+    stream_ended_ = false;
+    last_status_ = Status::Unknown();
+  }
+
+ private:
+  ByteSpan AcquireBuffer() override { return packet_buffer_; }
+
+  Status SendAndReleaseBuffer(std::span<const std::byte> buffer) override;
+
+  Vector<ByteSpan>& responses_;
+  Vector<ResponseBuffer>& buffers_;
+  ByteSpan packet_buffer_;
+  size_t total_responses_;
+  bool stream_ended_;
+  Status last_status_;
+};
+
+// Collects everything needed to invoke a particular RPC.
+template <typename Service,
+          uint32_t kMethodId,
+          size_t kMaxResponse,
+          size_t kOutputSize>
+struct InvocationContext {
+  template <typename... Args>
+  InvocationContext(Args&&... args)
+      : output(responses, buffers, packet_buffer),
+        channel(Channel::Create<123>(&output)),
+        server(std::span(&channel, 1)),
+        service(std::forward<Args>(args)...),
+        call(static_cast<internal::Server&>(server),
+             static_cast<internal::Channel&>(channel),
+             service,
+             MethodLookup::GetRawMethod<Service, kMethodId>()) {}
+
+  using ResponseBuffer = std::array<std::byte, kOutputSize>;
+
+  MessageOutput<kOutputSize> output;
+  rpc::Channel channel;
+  rpc::Server server;
+  Service service;
+  Vector<ByteSpan, kMaxResponse> responses;
+  Vector<ResponseBuffer, kMaxResponse> buffers;
+  std::array<std::byte, kOutputSize> packet_buffer = {};
+  internal::ServerCall call;
+};
+
+// Method invocation context for a unary RPC. Returns the status in call() and
+// provides the response through the response() method.
+template <typename Service, auto method, uint32_t kMethodId, size_t kOutputSize>
+class UnaryContext {
+ private:
+  using Context = InvocationContext<Service, kMethodId, 1, kOutputSize>;
+  Context ctx_;
+
+ public:
+  template <typename... Args>
+  UnaryContext(Args&&... args) : ctx_(std::forward<Args>(args)...) {}
+
+  Service& service() { return ctx_.service; }
+
+  // Invokes the RPC with the provided request. Returns RPC's StatusWithSize.
+  StatusWithSize call(ConstByteSpan request) {
+    ctx_.output.clear();
+    ctx_.buffers.emplace_back();
+    ctx_.buffers.back() = {};
+    ctx_.responses.emplace_back();
+    auto& response = ctx_.responses.back();
+    response = {ctx_.buffers.back().data(), ctx_.buffers.back().size()};
+    auto sws = CallMethodImplFunction<method>(ctx_.call, request, response);
+    response = response.first(sws.size());
+    return sws;
+  }
+
+  // Gives access to the RPC's response.
+  ConstByteSpan response() const {
+    PW_ASSERT(ctx_.responses.size() > 0u);
+    return ctx_.responses.back();
+  }
+};
+
+// Method invocation context for a server streaming RPC.
+template <typename Service,
+          auto method,
+          uint32_t kMethodId,
+          size_t kMaxResponse,
+          size_t kOutputSize>
+class ServerStreamingContext {
+ private:
+  using Context =
+      InvocationContext<Service, kMethodId, kMaxResponse, kOutputSize>;
+  Context ctx_;
+
+ public:
+  template <typename... Args>
+  ServerStreamingContext(Args&&... args) : ctx_(std::forward<Args>(args)...) {}
+
+  Service& service() { return ctx_.service; }
+
+  // Invokes the RPC with the provided request.
+  void call(ConstByteSpan request) {
+    ctx_.output.clear();
+    BaseServerWriter server_writer(ctx_.call);
+    return CallMethodImplFunction<method>(
+        ctx_.call, request, static_cast<RawServerWriter&>(server_writer));
+  }
+
+  // Returns a server writer which writes responses into the context's buffer.
+  // This should not be called alongside call(); use one or the other.
+  RawServerWriter writer() {
+    ctx_.output.clear();
+    BaseServerWriter server_writer(ctx_.call);
+    return std::move(static_cast<RawServerWriter&>(server_writer));
+  }
+
+  // Returns the responses that have been recorded. The maximum number of
+  // responses is responses().max_size(). responses().back() is always the most
+  // recent response, even if total_responses() > responses().max_size().
+  const Vector<ByteSpan>& responses() const { return ctx_.responses; }
+
+  // The total number of responses sent, which may be larger than
+  // responses.max_size().
+  size_t total_responses() const { return ctx_.output.total_responses(); }
+
+  // True if the stream has terminated.
+  bool done() const { return ctx_.output.stream_ended(); }
+
+  // The status of the stream. Only valid if done() is true.
+  Status status() const {
+    PW_ASSERT(done());
+    return ctx_.output.last_status();
+  }
+};
+
+// Alias to select the type of the context object to use based on which type of
+// RPC it is for.
+template <typename Service,
+          auto method,
+          uint32_t kMethodId,
+          size_t kMaxResponse,
+          size_t kOutputSize>
+using Context = std::tuple_element_t<
+    static_cast<size_t>(MethodTraits<decltype(method)>::kType),
+    std::tuple<UnaryContext<Service, method, kMethodId, kOutputSize>,
+               ServerStreamingContext<Service,
+                                      method,
+                                      kMethodId,
+                                      kMaxResponse,
+                                      kOutputSize>
+               // TODO(hepler): Support client and bidi streaming
+               >>;
+
+template <size_t kOutputSize>
+Status MessageOutput<kOutputSize>::SendAndReleaseBuffer(
+    std::span<const std::byte> buffer) {
+  PW_ASSERT(!stream_ended_);
+  PW_ASSERT(buffer.data() == packet_buffer_.data());
+
+  if (buffer.empty()) {
+    return OkStatus();
+  }
+
+  Result<internal::Packet> result = internal::Packet::FromBuffer(buffer);
+  PW_ASSERT(result.ok());
+
+  last_status_ = result.value().status();
+
+  switch (result.value().type()) {
+    case internal::PacketType::RESPONSE: {
+      // If we run out of space, the back message is always the most recent.
+      buffers_.emplace_back();
+      buffers_.back() = {};
+      auto response = result.value().payload();
+      std::memcpy(&buffers_.back(), response.data(), response.size());
+      responses_.emplace_back();
+      responses_.back() = {buffers_.back().data(), response.size()};
+      total_responses_ += 1;
+      break;
+    }
+    case internal::PacketType::SERVER_STREAM_END:
+      stream_ended_ = true;
+      break;
+    default:
+      PW_CRASH("Unhandled PacketType");
+  }
+  return OkStatus();
+}
+
+}  // namespace internal::test::raw
+
+template <typename Service,
+          auto method,
+          uint32_t kMethodId,
+          size_t kMaxResponse,
+          size_t kOutputSizeBytes>
+class RawTestMethodContext
+    : public internal::test::raw::
+          Context<Service, method, kMethodId, kMaxResponse, kOutputSizeBytes> {
+ public:
+  // Forwards constructor arguments to the service class.
+  template <typename... ServiceArgs>
+  RawTestMethodContext(ServiceArgs&&... service_args)
+      : internal::test::raw::
+            Context<Service, method, kMethodId, kMaxResponse, kOutputSizeBytes>(
+                std::forward<ServiceArgs>(service_args)...) {}
+};
+
+}  // namespace pw::rpc
diff --git a/pw_rpc/raw/raw_method.cc b/pw_rpc/raw/raw_method.cc
new file mode 100644
index 0000000..7fe7f80
--- /dev/null
+++ b/pw_rpc/raw/raw_method.cc
@@ -0,0 +1,77 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_rpc/internal/raw_method.h"
+
+#include <cstring>
+
+#include "pw_log/log.h"
+#include "pw_rpc/internal/packet.h"
+
+namespace pw::rpc {
+
+RawServerWriter::~RawServerWriter() {
+  if (!buffer().empty()) {
+    ReleasePayloadBuffer();
+  }
+}
+
+Status RawServerWriter::Write(ConstByteSpan response) {
+  if (!open()) {
+    return Status::FailedPrecondition();
+  }
+
+  if (buffer().Contains(response)) {
+    return ReleasePayloadBuffer(response);
+  }
+
+  std::span<std::byte> buffer = AcquirePayloadBuffer();
+
+  if (response.size() > buffer.size()) {
+    ReleasePayloadBuffer();
+    return Status::OutOfRange();
+  }
+
+  std::memcpy(buffer.data(), response.data(), response.size());
+  return ReleasePayloadBuffer(buffer.first(response.size()));
+}
+
+namespace internal {
+
+void RawMethod::CallUnary(ServerCall& call, const Packet& request) const {
+  Channel::OutputBuffer response_buffer = call.channel().AcquireBuffer();
+  std::span payload_buffer = response_buffer.payload(request);
+
+  StatusWithSize sws = function_.unary(call, request.payload(), payload_buffer);
+  Packet response = Packet::Response(request);
+
+  response.set_payload(payload_buffer.first(sws.size()));
+  response.set_status(sws.status());
+  if (call.channel().Send(response_buffer, response).ok()) {
+    return;
+  }
+
+  PW_LOG_WARN("Failed to send response packet for channel %u",
+              unsigned(call.channel().id()));
+  call.channel().Send(Packet::ServerError(request, Status::Internal()));
+}
+
+void RawMethod::CallServerStreaming(ServerCall& call,
+                                    const Packet& request) const {
+  internal::BaseServerWriter server_writer(call);
+  function_.server_streaming(call, request.payload(), server_writer);
+}
+
+}  // namespace internal
+}  // namespace pw::rpc
diff --git a/pw_rpc/raw/raw_method_test.cc b/pw_rpc/raw/raw_method_test.cc
new file mode 100644
index 0000000..fd60781
--- /dev/null
+++ b/pw_rpc/raw/raw_method_test.cc
@@ -0,0 +1,268 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_rpc/internal/raw_method.h"
+
+#include <array>
+
+#include "gtest/gtest.h"
+#include "pw_bytes/array.h"
+#include "pw_protobuf/decoder.h"
+#include "pw_protobuf/encoder.h"
+#include "pw_rpc/internal/raw_method_union.h"
+#include "pw_rpc/server_context.h"
+#include "pw_rpc/service.h"
+#include "pw_rpc_private/internal_test_utils.h"
+#include "pw_rpc_private/method_impl_tester.h"
+#include "pw_rpc_test_protos/test.pwpb.h"
+
+namespace pw::rpc::internal {
+namespace {
+
+// Create a fake service for use with the MethodImplTester.
+class TestRawService final : public Service {
+ public:
+  StatusWithSize Unary(ServerContext&, ConstByteSpan, ByteSpan) {
+    return StatusWithSize(0);
+  }
+
+  static StatusWithSize StaticUnary(ServerContext&, ConstByteSpan, ByteSpan) {
+    return StatusWithSize(0);
+  }
+
+  void ServerStreaming(ServerContext&, ConstByteSpan, RawServerWriter&) {}
+
+  static void StaticServerStreaming(ServerContext&,
+                                    ConstByteSpan,
+                                    RawServerWriter&) {}
+
+  StatusWithSize UnaryWrongArg(ServerContext&, ConstByteSpan, ConstByteSpan) {
+    return StatusWithSize(0);
+  }
+
+  static void StaticUnaryVoidReturn(ServerContext&, ConstByteSpan, ByteSpan) {}
+
+  Status ServerStreamingBadReturn(ServerContext&,
+                                  ConstByteSpan,
+                                  RawServerWriter&) {
+    return Status();
+  }
+
+  static void StaticServerStreamingMissingArg(ConstByteSpan, RawServerWriter&) {
+  }
+};
+
+// Test that the matches() function matches valid signatures.
+static_assert(RawMethod::template matches<&TestRawService::Unary>());
+static_assert(RawMethod::template matches<&TestRawService::ServerStreaming>());
+static_assert(RawMethod::template matches<&TestRawService::StaticUnary>());
+static_assert(
+    RawMethod::template matches<&TestRawService::StaticServerStreaming>());
+
+// Test that the matches() function does not match the wrong method type.
+static_assert(!RawMethod::template matches<&TestRawService::UnaryWrongArg>());
+static_assert(
+    !RawMethod::template matches<&TestRawService::StaticUnaryVoidReturn>());
+static_assert(
+    !RawMethod::template matches<&TestRawService::ServerStreamingBadReturn>());
+static_assert(!RawMethod::template matches<
+              &TestRawService::StaticServerStreamingMissingArg>());
+
+TEST(MethodImplTester, RawMethod) {
+  constexpr MethodImplTester<RawMethod, TestRawService> method_tester;
+  EXPECT_TRUE(method_tester.MethodImplIsValid());
+}
+
+struct {
+  int64_t integer;
+  uint32_t status_code;
+} last_request;
+
+RawServerWriter last_writer;
+
+void DecodeRawTestRequest(ConstByteSpan request) {
+  protobuf::Decoder decoder(request);
+
+  while (decoder.Next().ok()) {
+    test::TestRequest::Fields field =
+        static_cast<test::TestRequest::Fields>(decoder.FieldNumber());
+
+    switch (field) {
+      case test::TestRequest::Fields::INTEGER:
+        decoder.ReadInt64(&last_request.integer);
+        break;
+      case test::TestRequest::Fields::STATUS_CODE:
+        decoder.ReadUint32(&last_request.status_code);
+        break;
+    }
+  }
+};
+
+StatusWithSize AddFive(ServerContext&,
+                       ConstByteSpan request,
+                       ByteSpan response) {
+  DecodeRawTestRequest(request);
+
+  protobuf::NestedEncoder encoder(response);
+  test::TestResponse::Encoder test_response(&encoder);
+  test_response.WriteValue(last_request.integer + 5);
+  ConstByteSpan payload;
+  encoder.Encode(&payload);
+
+  return StatusWithSize::Unauthenticated(payload.size());
+}
+
+void StartStream(ServerContext&,
+                 ConstByteSpan request,
+                 RawServerWriter& writer) {
+  DecodeRawTestRequest(request);
+  last_writer = std::move(writer);
+}
+
+class FakeService : public Service {
+ public:
+  FakeService(uint32_t id) : Service(id, kMethods) {}
+
+  static constexpr std::array<RawMethodUnion, 2> kMethods = {
+      RawMethod::Unary<AddFive>(10u),
+      RawMethod::ServerStreaming<StartStream>(11u),
+  };
+};
+
+TEST(RawMethod, UnaryRpc_SendsResponse) {
+  std::byte buffer[16];
+  protobuf::NestedEncoder encoder(buffer);
+  test::TestRequest::Encoder test_request(&encoder);
+  test_request.WriteInteger(456);
+  test_request.WriteStatusCode(7);
+
+  const RawMethod& method = std::get<0>(FakeService::kMethods).raw_method();
+  ServerContextForTest<FakeService> context(method);
+  method.Invoke(context.get(), context.packet(encoder.Encode().value()));
+
+  EXPECT_EQ(last_request.integer, 456);
+  EXPECT_EQ(last_request.status_code, 7u);
+
+  const Packet& response = context.output().sent_packet();
+  EXPECT_EQ(response.status(), Status::Unauthenticated());
+
+  protobuf::Decoder decoder(response.payload());
+  ASSERT_TRUE(decoder.Next().ok());
+  int64_t value;
+  EXPECT_EQ(decoder.ReadInt64(&value), OkStatus());
+  EXPECT_EQ(value, 461);
+}
+
+TEST(RawMethod, ServerStreamingRpc_SendsNothingWhenInitiallyCalled) {
+  std::byte buffer[16];
+  protobuf::NestedEncoder encoder(buffer);
+  test::TestRequest::Encoder test_request(&encoder);
+  test_request.WriteInteger(777);
+  test_request.WriteStatusCode(2);
+
+  const RawMethod& method = std::get<1>(FakeService::kMethods).raw_method();
+  ServerContextForTest<FakeService> context(method);
+
+  method.Invoke(context.get(), context.packet(encoder.Encode().value()));
+
+  EXPECT_EQ(0u, context.output().packet_count());
+  EXPECT_EQ(777, last_request.integer);
+  EXPECT_EQ(2u, last_request.status_code);
+  EXPECT_TRUE(last_writer.open());
+  EXPECT_EQ(OkStatus(), last_writer.Finish());
+}
+
+TEST(RawServerWriter, Write_SendsPreviouslyAcquiredBuffer) {
+  const RawMethod& method = std::get<1>(FakeService::kMethods).raw_method();
+  ServerContextForTest<FakeService> context(method);
+
+  method.Invoke(context.get(), context.packet({}));
+
+  auto buffer = last_writer.PayloadBuffer();
+
+  constexpr auto data = bytes::Array<0x0d, 0x06, 0xf0, 0x0d>();
+  std::memcpy(buffer.data(), data.data(), data.size());
+
+  EXPECT_EQ(last_writer.Write(buffer.first(data.size())), OkStatus());
+
+  const internal::Packet& packet = context.output().sent_packet();
+  EXPECT_EQ(packet.type(), internal::PacketType::RESPONSE);
+  EXPECT_EQ(packet.channel_id(), context.channel_id());
+  EXPECT_EQ(packet.service_id(), context.service_id());
+  EXPECT_EQ(packet.method_id(), context.get().method().id());
+  EXPECT_EQ(std::memcmp(packet.payload().data(), data.data(), data.size()), 0);
+  EXPECT_EQ(packet.status(), OkStatus());
+}
+
+TEST(RawServerWriter, Write_SendsExternalBuffer) {
+  const RawMethod& method = std::get<1>(FakeService::kMethods).raw_method();
+  ServerContextForTest<FakeService> context(method);
+
+  method.Invoke(context.get(), context.packet({}));
+
+  constexpr auto data = bytes::Array<0x0d, 0x06, 0xf0, 0x0d>();
+  EXPECT_EQ(last_writer.Write(data), OkStatus());
+
+  const internal::Packet& packet = context.output().sent_packet();
+  EXPECT_EQ(packet.type(), internal::PacketType::RESPONSE);
+  EXPECT_EQ(packet.channel_id(), context.channel_id());
+  EXPECT_EQ(packet.service_id(), context.service_id());
+  EXPECT_EQ(packet.method_id(), context.get().method().id());
+  EXPECT_EQ(std::memcmp(packet.payload().data(), data.data(), data.size()), 0);
+  EXPECT_EQ(packet.status(), OkStatus());
+}
+
+TEST(RawServerWriter, Write_Closed_ReturnsFailedPrecondition) {
+  const RawMethod& method = std::get<1>(FakeService::kMethods).raw_method();
+  ServerContextForTest<FakeService> context(method);
+
+  method.Invoke(context.get(), context.packet({}));
+
+  EXPECT_EQ(OkStatus(), last_writer.Finish());
+  constexpr auto data = bytes::Array<0x0d, 0x06, 0xf0, 0x0d>();
+  EXPECT_EQ(last_writer.Write(data), Status::FailedPrecondition());
+}
+
+TEST(RawServerWriter, Write_BufferTooSmall_ReturnsOutOfRange) {
+  const RawMethod& method = std::get<1>(FakeService::kMethods).raw_method();
+  ServerContextForTest<FakeService, 16> context(method);
+
+  method.Invoke(context.get(), context.packet({}));
+
+  constexpr auto data =
+      bytes::Array<0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16>();
+  EXPECT_EQ(last_writer.Write(data), Status::OutOfRange());
+}
+
+TEST(RawServerWriter,
+     Destructor_ReleasesAcquiredBufferWithoutSendingAndCloses) {
+  const RawMethod& method = std::get<1>(FakeService::kMethods).raw_method();
+  ServerContextForTest<FakeService> context(method);
+
+  method.Invoke(context.get(), context.packet({}));
+
+  {
+    RawServerWriter writer = std::move(last_writer);
+    auto buffer = writer.PayloadBuffer();
+    buffer[0] = std::byte{'!'};
+    // Don't release the buffer.
+  }
+
+  auto output = context.output();
+  EXPECT_EQ(output.packet_count(), 1u);
+  EXPECT_EQ(output.sent_packet().type(), PacketType::SERVER_STREAM_END);
+}
+
+}  // namespace
+}  // namespace pw::rpc::internal
diff --git a/pw_rpc/raw/raw_method_union_test.cc b/pw_rpc/raw/raw_method_union_test.cc
new file mode 100644
index 0000000..f048366
--- /dev/null
+++ b/pw_rpc/raw/raw_method_union_test.cc
@@ -0,0 +1,146 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_rpc/internal/raw_method_union.h"
+
+#include <array>
+
+#include "gtest/gtest.h"
+#include "pw_bytes/array.h"
+#include "pw_protobuf/decoder.h"
+#include "pw_protobuf/encoder.h"
+#include "pw_rpc/server_context.h"
+#include "pw_rpc/service.h"
+#include "pw_rpc_private/internal_test_utils.h"
+#include "pw_rpc_test_protos/test.pwpb.h"
+
+namespace pw::rpc::internal {
+namespace {
+
+template <typename Implementation>
+class FakeGeneratedService : public Service {
+ public:
+  constexpr FakeGeneratedService(uint32_t id) : Service(id, kMethods) {}
+
+  static constexpr std::array<RawMethodUnion, 3> kMethods = {
+      GetRawMethodFor<&Implementation::DoNothing, MethodType::kUnary>(10u),
+      GetRawMethodFor<&Implementation::AddFive, MethodType::kUnary>(11u),
+      GetRawMethodFor<&Implementation::StartStream,
+                      MethodType::kServerStreaming>(12u),
+  };
+};
+
+struct {
+  int64_t integer;
+  uint32_t status_code;
+} last_request;
+RawServerWriter last_writer;
+
+class FakeGeneratedServiceImpl
+    : public FakeGeneratedService<FakeGeneratedServiceImpl> {
+ public:
+  FakeGeneratedServiceImpl(uint32_t id) : FakeGeneratedService(id) {}
+
+  StatusWithSize DoNothing(ServerContext&, ConstByteSpan, ByteSpan) {
+    return StatusWithSize::Unknown();
+  }
+
+  StatusWithSize AddFive(ServerContext&,
+                         ConstByteSpan request,
+                         ByteSpan response) {
+    DecodeRawTestRequest(request);
+
+    protobuf::NestedEncoder encoder(response);
+    test::TestResponse::Encoder test_response(&encoder);
+    test_response.WriteValue(last_request.integer + 5);
+    ConstByteSpan payload;
+    encoder.Encode(&payload);
+
+    return StatusWithSize::Unauthenticated(payload.size());
+  }
+
+  void StartStream(ServerContext&,
+                   ConstByteSpan request,
+                   RawServerWriter& writer) {
+    DecodeRawTestRequest(request);
+    last_writer = std::move(writer);
+  }
+
+ private:
+  void DecodeRawTestRequest(ConstByteSpan request) {
+    protobuf::Decoder decoder(request);
+
+    while (decoder.Next().ok()) {
+      test::TestRequest::Fields field =
+          static_cast<test::TestRequest::Fields>(decoder.FieldNumber());
+
+      switch (field) {
+        case test::TestRequest::Fields::INTEGER:
+          decoder.ReadInt64(&last_request.integer);
+          break;
+        case test::TestRequest::Fields::STATUS_CODE:
+          decoder.ReadUint32(&last_request.status_code);
+          break;
+      }
+    }
+  }
+};
+
+TEST(RawMethodUnion, InvokesUnary) {
+  std::byte buffer[16];
+  protobuf::NestedEncoder encoder(buffer);
+  test::TestRequest::Encoder test_request(&encoder);
+  test_request.WriteInteger(456);
+  test_request.WriteStatusCode(7);
+
+  const Method& method =
+      std::get<1>(FakeGeneratedServiceImpl::kMethods).method();
+  ServerContextForTest<FakeGeneratedServiceImpl> context(method);
+  method.Invoke(context.get(), context.packet(encoder.Encode().value()));
+
+  EXPECT_EQ(last_request.integer, 456);
+  EXPECT_EQ(last_request.status_code, 7u);
+
+  const Packet& response = context.output().sent_packet();
+  EXPECT_EQ(response.status(), Status::Unauthenticated());
+
+  protobuf::Decoder decoder(response.payload());
+  ASSERT_TRUE(decoder.Next().ok());
+  int64_t value;
+  EXPECT_EQ(decoder.ReadInt64(&value), OkStatus());
+  EXPECT_EQ(value, 461);
+}
+
+TEST(RawMethodUnion, InvokesServerStreaming) {
+  std::byte buffer[16];
+  protobuf::NestedEncoder encoder(buffer);
+  test::TestRequest::Encoder test_request(&encoder);
+  test_request.WriteInteger(777);
+  test_request.WriteStatusCode(2);
+
+  const Method& method =
+      std::get<2>(FakeGeneratedServiceImpl::kMethods).method();
+  ServerContextForTest<FakeGeneratedServiceImpl> context(method);
+
+  method.Invoke(context.get(), context.packet(encoder.Encode().value()));
+
+  EXPECT_EQ(0u, context.output().packet_count());
+  EXPECT_EQ(777, last_request.integer);
+  EXPECT_EQ(2u, last_request.status_code);
+  EXPECT_TRUE(last_writer.open());
+  EXPECT_EQ(OkStatus(), last_writer.Finish());
+}
+
+}  // namespace
+}  // namespace pw::rpc::internal
diff --git a/pw_rpc/raw/server_reader_writer_test.cc b/pw_rpc/raw/server_reader_writer_test.cc
deleted file mode 100644
index d6a35f8..0000000
--- a/pw_rpc/raw/server_reader_writer_test.cc
+++ /dev/null
@@ -1,430 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/raw/server_reader_writer.h"
-
-#include "gtest/gtest.h"
-#include "pw_rpc/internal/lock.h"
-#include "pw_rpc/raw/fake_channel_output.h"
-#include "pw_rpc/service.h"
-#include "pw_rpc/writer.h"
-#include "pw_rpc_test_protos/test.raw_rpc.pb.h"
-
-namespace pw::rpc {
-
-class TestServiceImpl final
-    : public test::pw_rpc::raw::TestService::Service<TestServiceImpl> {
- public:
-  static void TestUnaryRpc(ConstByteSpan, RawUnaryResponder&) {}
-
-  void TestAnotherUnaryRpc(ConstByteSpan, RawUnaryResponder&) {}
-
-  void TestServerStreamRpc(ConstByteSpan, RawServerWriter&) {}
-
-  void TestClientStreamRpc(RawServerReader&) {}
-
-  void TestBidirectionalStreamRpc(RawServerReaderWriter&) {}
-};
-
-struct ReaderWriterTestContext {
-  static constexpr uint32_t kChannelId = 1;
-
-  ReaderWriterTestContext()
-      : channel(Channel::Create<kChannelId>(&output)),
-        server(std::span(&channel, 1)) {}
-
-  TestServiceImpl service;
-  RawFakeChannelOutput<4> output;
-  Channel channel;
-  Server server;
-};
-
-using test::pw_rpc::raw::TestService;
-
-TEST(RawUnaryResponder, DefaultConstructed) {
-  RawUnaryResponder call;
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Finish({}, OkStatus()));
-
-  call.set_on_error([](Status) {});
-}
-
-TEST(RawServerWriter, DefaultConstructed) {
-  RawServerWriter call;
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Write({}));
-  EXPECT_EQ(Status::FailedPrecondition(), call.Finish(OkStatus()));
-
-  call.set_on_error([](Status) {});
-}
-
-TEST(RawServerReader, DefaultConstructed) {
-  RawServerReader call;
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Finish({}, OkStatus()));
-
-  call.set_on_next([](ConstByteSpan) {});
-  call.set_on_error([](Status) {});
-}
-
-TEST(RawServerReaderWriter, DefaultConstructed) {
-  RawServerReaderWriter call;
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Write({}));
-  EXPECT_EQ(Status::FailedPrecondition(), call.Finish(Status::Cancelled()));
-
-  call.set_on_next([](ConstByteSpan) {});
-  call.set_on_error([](Status) {});
-}
-
-TEST(RawUnaryResponder, Closed) {
-  ReaderWriterTestContext ctx;
-  RawUnaryResponder call = RawUnaryResponder::Open<TestService::TestUnaryRpc>(
-      ctx.server, ctx.channel.id(), ctx.service);
-  ASSERT_EQ(OkStatus(), call.Finish({}, OkStatus()));
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Finish({}, OkStatus()));
-
-  call.set_on_error([](Status) {});
-}
-
-TEST(RawServerWriter, Closed) {
-  ReaderWriterTestContext ctx;
-  RawServerWriter call =
-      RawServerWriter::Open<TestService::TestServerStreamRpc>(
-          ctx.server, ctx.channel.id(), ctx.service);
-  ASSERT_EQ(OkStatus(), call.Finish(OkStatus()));
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Write({}));
-  EXPECT_EQ(Status::FailedPrecondition(), call.Finish(OkStatus()));
-
-  call.set_on_error([](Status) {});
-}
-
-TEST(RawServerReader, Closed) {
-  ReaderWriterTestContext ctx;
-  RawServerReader call =
-      RawServerReader::Open<TestService::TestClientStreamRpc>(
-          ctx.server, ctx.channel.id(), ctx.service);
-  ASSERT_EQ(OkStatus(), call.Finish({}, OkStatus()));
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Finish({}, OkStatus()));
-
-  call.set_on_next([](ConstByteSpan) {});
-  call.set_on_error([](Status) {});
-}
-
-TEST(RawServerReaderWriter, Closed) {
-  ReaderWriterTestContext ctx;
-  RawServerReaderWriter call =
-      RawServerReaderWriter::Open<TestService::TestBidirectionalStreamRpc>(
-          ctx.server, ctx.channel.id(), ctx.service);
-  ASSERT_EQ(OkStatus(), call.Finish(OkStatus()));
-
-  ASSERT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-
-  EXPECT_EQ(Status::FailedPrecondition(), call.Write({}));
-  EXPECT_EQ(Status::FailedPrecondition(), call.Finish(Status::Cancelled()));
-
-  call.set_on_next([](ConstByteSpan) {});
-  call.set_on_error([](Status) {});
-}
-
-TEST(RawUnaryResponder, Open_ReturnsUsableResponder) {
-  ReaderWriterTestContext ctx;
-  RawUnaryResponder call = RawUnaryResponder::Open<TestService::TestUnaryRpc>(
-      ctx.server, ctx.channel.id(), ctx.service);
-
-  EXPECT_EQ(call.channel_id(), ctx.channel.id());
-  EXPECT_EQ(OkStatus(),
-            call.Finish(std::as_bytes(std::span("hello from pw_rpc"))));
-
-  EXPECT_STREQ(
-      reinterpret_cast<const char*>(
-          ctx.output.payloads<TestService::TestUnaryRpc>().back().data()),
-      "hello from pw_rpc");
-}
-
-TEST(RawServerReaderWriter, Open_UnknownChannel) {
-  ReaderWriterTestContext ctx;
-  ASSERT_EQ(OkStatus(), ctx.server.CloseChannel(ctx.kChannelId));
-
-  RawServerReaderWriter call =
-      RawServerReaderWriter::Open<TestService::TestBidirectionalStreamRpc>(
-          ctx.server, ctx.kChannelId, ctx.service);
-
-  EXPECT_TRUE(call.active());
-  EXPECT_EQ(call.channel_id(), ctx.kChannelId);
-  EXPECT_EQ(Status::Unavailable(), call.Write({}));
-
-  ASSERT_EQ(OkStatus(), ctx.server.OpenChannel(ctx.kChannelId, ctx.output));
-
-  EXPECT_EQ(OkStatus(), call.Write({}));
-  EXPECT_TRUE(call.active());
-
-  EXPECT_EQ(OkStatus(), call.Finish());
-  EXPECT_FALSE(call.active());
-  EXPECT_EQ(call.channel_id(), Channel::kUnassignedChannelId);
-}
-
-TEST(RawUnaryResponder, Open_MultipleTimes_CancelsPrevious) {
-  ReaderWriterTestContext ctx;
-
-  RawUnaryResponder one = RawUnaryResponder::Open<TestService::TestUnaryRpc>(
-      ctx.server, ctx.channel.id(), ctx.service);
-
-  ASSERT_TRUE(one.active());
-
-  RawUnaryResponder two = RawUnaryResponder::Open<TestService::TestUnaryRpc>(
-      ctx.server, ctx.channel.id(), ctx.service);
-
-  ASSERT_FALSE(one.active());
-  ASSERT_TRUE(two.active());
-}
-
-TEST(RawServerWriter, Open_ReturnsUsableWriter) {
-  ReaderWriterTestContext ctx;
-  RawServerWriter call =
-      RawServerWriter::Open<TestService::TestServerStreamRpc>(
-          ctx.server, ctx.channel.id(), ctx.service);
-
-  EXPECT_EQ(call.channel_id(), ctx.channel.id());
-  EXPECT_EQ(OkStatus(), call.Write(std::as_bytes(std::span("321"))));
-
-  EXPECT_STREQ(reinterpret_cast<const char*>(
-                   ctx.output.payloads<TestService::TestServerStreamRpc>()
-                       .back()
-                       .data()),
-               "321");
-}
-
-TEST(RawServerReader, Open_ReturnsUsableReader) {
-  ReaderWriterTestContext ctx;
-  RawServerReader call =
-      RawServerReader::Open<TestService::TestClientStreamRpc>(
-          ctx.server, ctx.channel.id(), ctx.service);
-
-  EXPECT_EQ(call.channel_id(), ctx.channel.id());
-  EXPECT_EQ(OkStatus(),
-            call.Finish(std::as_bytes(std::span("This is a message"))));
-
-  EXPECT_STREQ(reinterpret_cast<const char*>(
-                   ctx.output.payloads<TestService::TestClientStreamRpc>()
-                       .back()
-                       .data()),
-               "This is a message");
-}
-
-TEST(RawServerReaderWriter, Open_ReturnsUsableReaderWriter) {
-  ReaderWriterTestContext ctx;
-  RawServerReaderWriter call =
-      RawServerReaderWriter::Open<TestService::TestBidirectionalStreamRpc>(
-          ctx.server, ctx.channel.id(), ctx.service);
-
-  EXPECT_EQ(call.channel_id(), ctx.channel.id());
-  EXPECT_EQ(OkStatus(), call.Write(std::as_bytes(std::span("321"))));
-
-  EXPECT_STREQ(
-      reinterpret_cast<const char*>(
-          ctx.output.payloads<TestService::TestBidirectionalStreamRpc>()
-              .back()
-              .data()),
-      "321");
-}
-
-TEST(RawUnaryResponder, Move_FinishesActiveCall) {
-  ReaderWriterTestContext ctx;
-  RawUnaryResponder active_call =
-      RawUnaryResponder::Open<TestService::TestUnaryRpc>(
-          ctx.server, ctx.channel.id(), ctx.service);
-
-  RawUnaryResponder inactive_call;
-
-  active_call = std::move(inactive_call);
-
-  const auto completions = ctx.output.completions<TestService::TestUnaryRpc>();
-  ASSERT_EQ(completions.size(), 1u);
-  EXPECT_EQ(completions.back(), OkStatus());
-}
-
-TEST(RawUnaryResponder, Move_DifferentActiveCalls_ClosesFirstOnly) {
-  ReaderWriterTestContext ctx;
-  RawUnaryResponder active_call =
-      RawUnaryResponder::Open<TestService::TestUnaryRpc>(
-          ctx.server, ctx.channel.id(), ctx.service);
-
-  RawUnaryResponder new_active_call =
-      RawUnaryResponder::Open<TestService::TestAnotherUnaryRpc>(
-          ctx.server, ctx.channel.id(), ctx.service);
-
-  EXPECT_TRUE(active_call.active());
-  EXPECT_TRUE(new_active_call.active());
-
-  active_call = std::move(new_active_call);
-
-  const auto completions = ctx.output.completions<TestService::TestUnaryRpc>();
-  ASSERT_EQ(completions.size(), 1u);
-  EXPECT_EQ(completions.back(), OkStatus());
-
-  EXPECT_TRUE(
-      ctx.output.completions<TestService::TestAnotherUnaryRpc>().empty());
-}
-
-TEST(RawUnaryResponder, ReplaceActiveCall_DoesNotFinishCall) {
-  ReaderWriterTestContext ctx;
-  RawUnaryResponder active_call =
-      RawUnaryResponder::Open<TestService::TestUnaryRpc>(
-          ctx.server, ctx.channel.id(), ctx.service);
-
-  RawUnaryResponder new_active_call =
-      RawUnaryResponder::Open<TestService::TestUnaryRpc>(
-          ctx.server, ctx.channel.id(), ctx.service);
-
-  active_call = std::move(new_active_call);
-
-  ASSERT_TRUE(ctx.output.completions<TestService::TestUnaryRpc>().empty());
-
-  constexpr const char kData[] = "Some data!";
-  EXPECT_EQ(OkStatus(),
-            active_call.Finish(std::as_bytes(std::span(kData)),
-                               Status::InvalidArgument()));
-
-  EXPECT_STREQ(
-      reinterpret_cast<const char*>(
-          ctx.output.payloads<TestService::TestUnaryRpc>().back().data()),
-      kData);
-
-  const auto completions = ctx.output.completions<TestService::TestUnaryRpc>();
-  ASSERT_EQ(completions.size(), 1u);
-  EXPECT_EQ(completions.back(), Status::InvalidArgument());
-}
-
-TEST(RawUnaryResponder, OutOfScope_FinishesActiveCall) {
-  ReaderWriterTestContext ctx;
-
-  {
-    RawUnaryResponder call = RawUnaryResponder::Open<TestService::TestUnaryRpc>(
-        ctx.server, ctx.channel.id(), ctx.service);
-    ASSERT_TRUE(ctx.output.completions<TestService::TestUnaryRpc>().empty());
-  }
-
-  const auto completions = ctx.output.completions<TestService::TestUnaryRpc>();
-  ASSERT_EQ(completions.size(), 1u);
-  EXPECT_EQ(completions.back(), OkStatus());
-}
-
-TEST(RawServerWriter, Move_InactiveToActive_FinishesActiveCall) {
-  ReaderWriterTestContext ctx;
-  RawServerWriter active_call =
-      RawServerWriter::Open<TestService::TestServerStreamRpc>(
-          ctx.server, ctx.channel.id(), ctx.service);
-
-  RawServerWriter inactive_call;
-
-  active_call = std::move(inactive_call);
-
-  const auto completions =
-      ctx.output.completions<TestService::TestServerStreamRpc>();
-  ASSERT_EQ(completions.size(), 1u);
-  EXPECT_EQ(completions.back(), OkStatus());
-}
-
-TEST(RawServerWriter, ReplaceActiveCall_DoesNotFinishCall) {
-  ReaderWriterTestContext ctx;
-  RawServerWriter active_call =
-      RawServerWriter::Open<TestService::TestServerStreamRpc>(
-          ctx.server, ctx.channel.id(), ctx.service);
-
-  RawServerWriter new_active_call =
-      RawServerWriter::Open<TestService::TestServerStreamRpc>(
-          ctx.server, ctx.channel.id(), ctx.service);
-
-  active_call = std::move(new_active_call);
-
-  ASSERT_TRUE(
-      ctx.output.completions<TestService::TestServerStreamRpc>().empty());
-
-  constexpr const char kData[] = "Some data!";
-  EXPECT_EQ(OkStatus(), active_call.Write(std::as_bytes(std::span(kData))));
-
-  EXPECT_STREQ(reinterpret_cast<const char*>(
-                   ctx.output.payloads<TestService::TestServerStreamRpc>()
-                       .back()
-                       .data()),
-               kData);
-}
-
-constexpr const char kWriterData[] = "20X6";
-
-void WriteAsWriter(Writer& writer) {
-  ASSERT_TRUE(writer.active());
-  ASSERT_EQ(writer.channel_id(), ReaderWriterTestContext::kChannelId);
-
-  EXPECT_EQ(OkStatus(), writer.Write(std::as_bytes(std::span(kWriterData))));
-}
-
-TEST(RawServerWriter, UsableAsWriter) {
-  ReaderWriterTestContext ctx;
-  RawServerWriter call =
-      RawServerWriter::Open<TestService::TestServerStreamRpc>(
-          ctx.server, ctx.channel.id(), ctx.service);
-
-  WriteAsWriter(call);
-
-  EXPECT_STREQ(reinterpret_cast<const char*>(
-                   ctx.output.payloads<TestService::TestServerStreamRpc>()
-                       .back()
-                       .data()),
-               kWriterData);
-}
-
-TEST(RawServerReaderWriter, UsableAsWriter) {
-  ReaderWriterTestContext ctx;
-  RawServerReaderWriter call =
-      RawServerReaderWriter::Open<TestService::TestBidirectionalStreamRpc>(
-          ctx.server, ctx.channel.id(), ctx.service);
-
-  WriteAsWriter(call);
-
-  EXPECT_STREQ(
-      reinterpret_cast<const char*>(
-          ctx.output.payloads<TestService::TestBidirectionalStreamRpc>()
-              .back()
-              .data()),
-      kWriterData);
-}
-
-}  // namespace pw::rpc
diff --git a/pw_rpc/request_packets.svg b/pw_rpc/request_packets.svg
deleted file mode 100644
index 7a54d8d..0000000
--- a/pw_rpc/request_packets.svg
+++ /dev/null
@@ -1,42 +0,0 @@
-<!-- Originally created with blockdiag. See the Git history for the source. -->
-<svg height="280" viewBox="0 0 640 280" width="640" xmlns="http://www.w3.org/2000/svg" xmlns:inkspace="http://www.inkscape.org/namespaces/inkscape" xmlns:xlink="http://www.w3.org/1999/xlink" style="background-color:white">
-<defs id="defs_block">
-<filter height="1.504" id="filter_blur" inkspace:collect="always" width="1.1575" x="-0.07875" y="-0.252">
-<feGaussianBlur id="feGaussianBlur3780" inkspace:collect="always" stdDeviation="4.2"></feGaussianBlur>
-</filter>
-</defs>
-<title>pw_rpc Requests</title>
-<desc></desc>
-<rect fill="rgb(243,152,0)" height="60" style="filter:url(#filter_blur)" width="528" x="56" y="110"></rect>
-<ellipse cx="131" cy="66" fill="rgb(0,0,0)" rx="8.0" ry="8.0" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></ellipse>
-<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="67" y="126"></rect>
-<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="259" y="126"></rect>
-<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="451" y="126"></rect>
-<ellipse cx="131" cy="226" fill="rgb(0,0,0)" rx="64.0" ry="20.0" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></ellipse>
-<path d="M 267 206 L 379 206 A8,8 0 0 1 387 214 L 387 238 A8,8 0 0 1 379 246 L 267 246 A8,8 0 0 1 259 238 L 259 214 A8,8 0 0 1 267 206" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></path>
-<ellipse cx="128" cy="60" fill="rgb(0,0,0)" rx="8.0" ry="8.0" stroke="rgb(0,0,0)"></ellipse>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="42" x="157.0" y="56">packets</text>
-<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="64" y="120"></rect>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="36" x="128.0" y="146">Server</text>
-<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="256" y="120"></rect>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="42" x="320.0" y="146">Service</text>
-<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="448" y="120"></rect>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="96" x="512.0" y="146">internal::Method</text>
-<ellipse cx="128" cy="220" fill="rgb(255,255,255)" rx="64.0" ry="20.0" stroke="rgb(0,0,0)"></ellipse>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="108" x="128.0" y="226">generated services</text>
-<path d="M 264 200 L 376 200 A8,8 0 0 1 384 208 L 384 232 A8,8 0 0 1 376 240 L 264 240 A8,8 0 0 1 256 232 L 256 208 A8,8 0 0 1 264 200" fill="rgb(255,255,255)" stroke="rgb(0,0,0)"></path>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="102" x="320.0" y="226">user-defined RPCs</text>
-<path d="M 128 68 L 128 112" fill="none" stroke="rgb(0,0,0)"></path>
-<polygon fill="rgb(0,0,0)" points="128,119 124,112 132,112 128,119" stroke="rgb(0,0,0)"></polygon>
-<path d="M 192 220 L 248 220" fill="none" stroke="rgb(0,0,0)"></path>
-<polygon fill="rgb(0,0,0)" points="255,220 248,216 248,224 255,220" stroke="rgb(0,0,0)"></polygon>
-<path d="M 192 140 L 248 140" fill="none" stroke="rgb(0,0,0)"></path>
-<polygon fill="rgb(0,0,0)" points="255,140 248,136 248,144 255,140" stroke="rgb(0,0,0)"></polygon>
-<path d="M 384 140 L 440 140" fill="none" stroke="rgb(0,0,0)"></path>
-<polygon fill="rgb(0,0,0)" points="447,140 440,136 440,144 447,140" stroke="rgb(0,0,0)"></polygon>
-<path d="M 512 160 L 512 180" fill="none" stroke="rgb(0,0,0)"></path>
-<path d="M 128 180 L 512 180" fill="none" stroke="rgb(0,0,0)"></path>
-<path d="M 128 180 L 128 192" fill="none" stroke="rgb(0,0,0)"></path>
-<polygon fill="rgb(0,0,0)" points="128,199 124,192 132,192 128,199" stroke="rgb(0,0,0)"></polygon>
-<rect class="highlighted" width="68.9688" x="285.766" y="106" height="12"></rect><text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="84" x="320.0" y="116"><tspan>pw_rpc</tspan> library</text>
-</svg>
diff --git a/pw_rpc/response_packets.svg b/pw_rpc/response_packets.svg
deleted file mode 100644
index 065132e..0000000
--- a/pw_rpc/response_packets.svg
+++ /dev/null
@@ -1,42 +0,0 @@
-<!-- Originally created with blockdiag. See the Git history for the source. -->
-<svg height="360" viewBox="0 0 640 360" width="640" xmlns="http://www.w3.org/2000/svg" xmlns:inkspace="http://www.inkscape.org/namespaces/inkscape" xmlns:xlink="http://www.w3.org/1999/xlink" style="background-color:white">
-<defs id="defs_block">
-<filter height="1.504" id="filter_blur" inkspace:collect="always" width="1.1575" x="-0.07875" y="-0.252">
-<feGaussianBlur id="feGaussianBlur3780" inkspace:collect="always" stdDeviation="4.2"></feGaussianBlur>
-</filter>
-</defs>
-<title>pw_rpc Responses</title>
-<desc></desc>
-<rect fill="rgb(243,152,0)" height="60" style="filter:url(#filter_blur)" width="528" x="56" y="190"></rect>
-<path d="M 75 46 L 187 46 A8,8 0 0 1 195 54 L 195 78 A8,8 0 0 1 187 86 L 75 86 A8,8 0 0 1 67 78 L 67 54 A8,8 0 0 1 75 46" fill="rgb(0,0,0)" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></path>
-<ellipse cx="131" cy="146" fill="rgb(0,0,0)" rx="64.0" ry="20.0" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></ellipse>
-<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="259" y="206"></rect>
-<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="67" y="206"></rect>
-<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="451" y="206"></rect>
-<ellipse cx="131" cy="306" fill="rgb(0,0,0)" rx="8.0" ry="8.0" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></ellipse>
-<path d="M 72 40 L 184 40 A8,8 0 0 1 192 48 L 192 72 A8,8 0 0 1 184 80 L 72 80 A8,8 0 0 1 64 72 L 64 48 A8,8 0 0 1 72 40" fill="rgb(255,255,255)" stroke="rgb(0,0,0)"></path>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="102" x="128.0" y="66">user-defined RPCs</text>
-<ellipse cx="128" cy="140" fill="rgb(255,255,255)" rx="64.0" ry="20.0" stroke="rgb(0,0,0)"></ellipse>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="108" x="128.0" y="146">generated services</text>
-<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="256" y="200"></rect>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="36" x="320.0" y="226">Server</text>
-<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="64" y="200"></rect>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="96" x="128.0" y="226">internal::Method</text>
-<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="448" y="200"></rect>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="42" x="512.0" y="226">Channel</text>
-<ellipse cx="128" cy="300" fill="rgb(0,0,0)" rx="8.0" ry="8.0" stroke="rgb(0,0,0)"></ellipse>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="42" x="157.0" y="296">packets</text>
-<path d="M 128 80 L 128 112" fill="none" stroke="rgb(0,0,0)"></path>
-<polygon fill="rgb(0,0,0)" points="128,119 124,112 132,112 128,119" stroke="rgb(0,0,0)"></polygon>
-<path d="M 128 160 L 128 192" fill="none" stroke="rgb(0,0,0)"></path>
-<polygon fill="rgb(0,0,0)" points="128,199 124,192 132,192 128,199" stroke="rgb(0,0,0)"></polygon>
-<path d="M 384 220 L 440 220" fill="none" stroke="rgb(0,0,0)"></path>
-<polygon fill="rgb(0,0,0)" points="447,220 440,216 440,224 447,220" stroke="rgb(0,0,0)"></polygon>
-<path d="M 192 220 L 248 220" fill="none" stroke="rgb(0,0,0)"></path>
-<polygon fill="rgb(0,0,0)" points="255,220 248,216 248,224 255,220" stroke="rgb(0,0,0)"></polygon>
-<path d="M 512 240 L 512 260" fill="none" stroke="rgb(0,0,0)"></path>
-<path d="M 128 260 L 512 260" fill="none" stroke="rgb(0,0,0)"></path>
-<path d="M 128 260 L 128 284" fill="none" stroke="rgb(0,0,0)"></path>
-<polygon fill="rgb(0,0,0)" points="128,291 124,284 132,284 128,291" stroke="rgb(0,0,0)"></polygon>
-<rect class="highlighted" width="68.9688" x="285.766" y="186" height="12"></rect><text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="84" x="320.0" y="196"><tspan>pw_rpc</tspan> library</text>
-</svg>
diff --git a/pw_rpc/server.cc b/pw_rpc/server.cc
index d7634ab..59da7d2 100644
--- a/pw_rpc/server.cc
+++ b/pw_rpc/server.cc
@@ -12,101 +12,113 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-// clang-format off
-#include "pw_rpc/internal/log_config.h" // PW_LOG_* macros must be first.
-
 #include "pw_rpc/server.h"
-// clang-format on
 
 #include <algorithm>
 
 #include "pw_log/log.h"
-#include "pw_rpc/internal/endpoint.h"
 #include "pw_rpc/internal/packet.h"
+#include "pw_rpc/internal/server.h"
+#include "pw_rpc/server_context.h"
 
 namespace pw::rpc {
 namespace {
 
+using std::byte;
+
 using internal::Packet;
 using internal::PacketType;
 
+bool DecodePacket(ChannelOutput& interface,
+                  std::span<const byte> data,
+                  Packet& packet) {
+  Result<Packet> result = Packet::FromBuffer(data);
+  if (!result.ok()) {
+    PW_LOG_WARN("Failed to decode packet on interface %s", interface.name());
+    return false;
+  }
+
+  packet = result.value();
+
+  // If the packet is malformed, don't try to process it.
+  if (packet.channel_id() == Channel::kUnassignedChannelId ||
+      packet.service_id() == 0 || packet.method_id() == 0) {
+    PW_LOG_WARN("Received incomplete packet on interface %s", interface.name());
+
+    // Only send an ERROR response if a valid channel ID was provided.
+    if (packet.channel_id() != Channel::kUnassignedChannelId) {
+      internal::Channel temp_channel(packet.channel_id(), &interface);
+      temp_channel.Send(Packet::ServerError(packet, Status::DataLoss()));
+    }
+    return false;
+  }
+
+  return true;
+}
+
 }  // namespace
 
-Status Server::ProcessPacket(ConstByteSpan packet_data,
-                             ChannelOutput* interface) {
-  PW_TRY_ASSIGN(Packet packet,
-                Endpoint::ProcessPacket(packet_data, Packet::kServer));
+Server::~Server() {
+  // Since the writers remove themselves from the server in Finish(), remove the
+  // first writer until no writers remain.
+  while (!writers_.empty()) {
+    writers_.front().Finish();
+  }
+}
 
-  internal::rpc_lock().lock();
-  internal::ServerCall* const call =
-      static_cast<internal::ServerCall*>(FindCall(packet));
+Status Server::ProcessPacket(std::span<const byte> data,
+                             ChannelOutput& interface) {
+  Packet packet;
+  if (!DecodePacket(interface, data, packet)) {
+    return Status::DataLoss();
+  }
 
-  // Verbose log for debugging.
-  // PW_LOG_DEBUG("RPC server received packet type %u for %u:%08x/%08x",
-  //              static_cast<unsigned>(packet.type()),
-  //              static_cast<unsigned>(packet.channel_id()),
-  //              static_cast<unsigned>(packet.service_id()),
-  //              static_cast<unsigned>(packet.method_id()));
+  if (packet.destination() != Packet::kServer) {
+    return Status::InvalidArgument();
+  }
 
-  internal::Channel* channel = GetInternalChannel(packet.channel_id());
+  internal::Channel* channel = FindChannel(packet.channel_id());
   if (channel == nullptr) {
-    // If an interface was provided, respond with a SERVER_ERROR to indicate
-    // that the channel is not available on this server. Don't send responses to
-    // error messages, though, to avoid potential infinite cycles.
-    if (interface != nullptr && packet.type() != PacketType::CLIENT_ERROR) {
-      internal::Channel(packet.channel_id(), interface)
-          .Send(Packet::ServerError(packet, Status::Unavailable()))
-          .IgnoreError();
+    // If the requested channel doesn't exist, try to dynamically assign one.
+    channel = AssignChannel(packet.channel_id(), interface);
+    if (channel == nullptr) {
+      // If a channel can't be assigned, send a RESOURCE_EXHAUSTED error.
+      internal::Channel temp_channel(packet.channel_id(), &interface);
+      temp_channel.Send(
+          Packet::ServerError(packet, Status::ResourceExhausted()));
+      return OkStatus();  // OK since the packet was handled
     }
-
-    internal::rpc_lock().unlock();
-    PW_LOG_WARN("RPC server received packet for unknown channel %u",
-                static_cast<unsigned>(packet.channel_id()));
-    return Status::Unavailable();
   }
 
   const auto [service, method] = FindMethod(packet);
 
   if (method == nullptr) {
-    // Don't send responses to errors to avoid infinite error cycles.
-    if (packet.type() != PacketType::CLIENT_ERROR) {
-      channel->Send(Packet::ServerError(packet, Status::NotFound()))
-          .IgnoreError();
-    }
-    internal::rpc_lock().unlock();
-    return OkStatus();  // OK since the packet was handled.
+    channel->Send(Packet::ServerError(packet, Status::NotFound()));
+    return OkStatus();
   }
 
   switch (packet.type()) {
     case PacketType::REQUEST: {
-      // If the REQUEST is for an ongoing RPC, the existing call will be
-      // cancelled when the new call object is created.
-      const internal::CallContext context(
-          *this, channel->id(), *service, *method, packet.call_id());
-      method->Invoke(context, packet);
+      internal::ServerCall call(
+          static_cast<internal::Server&>(*this), *channel, *service, *method);
+      method->Invoke(call, packet);
       break;
     }
-    case PacketType::CLIENT_STREAM:
-      HandleClientStreamPacket(packet, *channel, call);
+    case PacketType::CLIENT_STREAM_END:
+      // TODO(hepler): Support client streaming RPCs.
       break;
     case PacketType::CLIENT_ERROR:
-    case PacketType::DEPRECATED_CANCEL:
-      if (call != nullptr && call->id() == packet.call_id()) {
-        call->HandleError(packet.status());
-      } else {
-        internal::rpc_lock().unlock();
-      }
+      HandleClientError(packet);
       break;
-    case PacketType::CLIENT_STREAM_END:
-      HandleClientStreamPacket(packet, *channel, call);
+    case PacketType::CANCEL_SERVER_STREAM:
+      HandleCancelPacket(packet, *channel);
       break;
     default:
-      internal::rpc_lock().unlock();
-      PW_LOG_WARN("pw_rpc server unable to handle packet of type %u",
+      channel->Send(Packet::ServerError(packet, Status::Unimplemented()));
+      PW_LOG_WARN("Unable to handle packet of type %u",
                   unsigned(packet.type()));
   }
-
-  return OkStatus();  // OK since the packet was handled
+  return OkStatus();
 }
 
 std::tuple<Service*, const internal::Method*> Server::FindMethod(
@@ -123,40 +135,55 @@
   return {&(*service), service->FindMethod(packet.method_id())};
 }
 
-void Server::HandleClientStreamPacket(const internal::Packet& packet,
-                                      internal::Channel& channel,
-                                      internal::ServerCall* call) const {
-  if (call == nullptr || call->id() != packet.call_id()) {
-    channel.Send(Packet::ServerError(packet, Status::FailedPrecondition()))
-        .IgnoreError();  // Errors are logged in Channel::Send.
-    internal::rpc_lock().unlock();
-    PW_LOG_DEBUG(
-        "Received client stream packet for %u:%08x/%08x, which is not pending",
-        static_cast<unsigned>(packet.channel_id()),
-        static_cast<unsigned>(packet.service_id()),
-        static_cast<unsigned>(packet.method_id()));
-    return;
+void Server::HandleCancelPacket(const Packet& packet,
+                                internal::Channel& channel) {
+  auto writer = std::find_if(writers_.begin(), writers_.end(), [&](auto& w) {
+    return w.channel_id() == packet.channel_id() &&
+           w.service_id() == packet.service_id() &&
+           w.method_id() == packet.method_id();
+  });
+
+  if (writer == writers_.end()) {
+    channel.Send(Packet::ServerError(packet, Status::FailedPrecondition()));
+    PW_LOG_WARN("Received CANCEL packet for method that is not pending");
+  } else {
+    writer->Finish(Status::Cancelled());
+  }
+}
+
+void Server::HandleClientError(const Packet& packet) {
+  // A client error indicates that the client received a packet that it did not
+  // expect. If the packet belongs to a streaming RPC, cancel the stream without
+  // sending a final SERVER_STREAM_END packet.
+  auto writer = std::find_if(writers_.begin(), writers_.end(), [&](auto& w) {
+    return w.channel_id() == packet.channel_id() &&
+           w.service_id() == packet.service_id() &&
+           w.method_id() == packet.method_id();
+  });
+
+  if (writer != writers_.end()) {
+    writer->Close();
+  }
+}
+
+internal::Channel* Server::FindChannel(uint32_t id) const {
+  for (internal::Channel& c : channels_) {
+    if (c.id() == id) {
+      return &c;
+    }
+  }
+  return nullptr;
+}
+
+internal::Channel* Server::AssignChannel(uint32_t id,
+                                         ChannelOutput& interface) {
+  internal::Channel* channel = FindChannel(Channel::kUnassignedChannelId);
+  if (channel == nullptr) {
+    return nullptr;
   }
 
-  if (!call->has_client_stream()) {
-    channel.Send(Packet::ServerError(packet, Status::InvalidArgument()))
-        .IgnoreError();  // Errors are logged in Channel::Send.
-    internal::rpc_lock().unlock();
-    return;
-  }
-
-  if (!call->client_stream_open()) {
-    channel.Send(Packet::ServerError(packet, Status::FailedPrecondition()))
-        .IgnoreError();  // Errors are logged in Channel::Send.
-    internal::rpc_lock().unlock();
-    return;
-  }
-
-  if (packet.type() == PacketType::CLIENT_STREAM) {
-    call->HandlePayload(packet.payload());
-  } else {  // Handle PacketType::CLIENT_STREAM_END.
-    call->HandleClientStreamEnd();
-  }
+  *channel = internal::Channel(id, &interface);
+  return channel;
 }
 
 }  // namespace pw::rpc
diff --git a/pw_rpc/server_call.cc b/pw_rpc/server_call.cc
deleted file mode 100644
index 602ad83..0000000
--- a/pw_rpc/server_call.cc
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/internal/server_call.h"
-
-namespace pw::rpc::internal {
-
-void ServerCall::MoveServerCallFrom(ServerCall& other) {
-  // If this call is active, finish it first.
-  if (active_locked()) {
-    CloseAndSendResponseLocked(OkStatus()).IgnoreError();
-  }
-
-  MoveFrom(other);
-
-#if PW_RPC_CLIENT_STREAM_END_CALLBACK
-  on_client_stream_end_ = std::move(other.on_client_stream_end_);
-#endif  // PW_RPC_CLIENT_STREAM_END_CALLBACK
-}
-
-}  // namespace pw::rpc::internal
diff --git a/pw_rpc/server_streaming_rpc.svg b/pw_rpc/server_streaming_rpc.svg
deleted file mode 100644
index e153a0a..0000000
--- a/pw_rpc/server_streaming_rpc.svg
+++ /dev/null
@@ -1,58 +0,0 @@
-<!-- Originally created with blockdiag. See the Git history for the source. -->
-<svg height="511.50000000000006" viewBox="0 0 564 465" width="620.4000000000001" xmlns="http://www.w3.org/2000/svg" xmlns:inkspace="http://www.inkscape.org/namespaces/inkscape" xmlns:xlink="http://www.w3.org/1999/xlink" style="background-color:white">
-<defs id="defs_block">
-<filter height="1.504" id="filter_blur" inkspace:collect="always" width="1.1575" x="-0.07875" y="-0.252">
-<feGaussianBlur id="feGaussianBlur3780" inkspace:collect="always" stdDeviation="4.2"></feGaussianBlur>
-</filter>
-</defs>
-<title>Server Streaming RPC</title>
-<desc></desc>
-<rect fill="rgb(0,0,0)" height="284" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="8" x="171" y="159"></rect>
-<rect fill="rgb(0,0,0)" height="214" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="8" x="363" y="159"></rect>
-<polygon fill="rgb(0,0,0)" points="27,126 151,126 159,134 159,193 27,193 27,126" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></polygon>
-<polygon fill="rgb(0,0,0)" points="383,233 543,233 551,241 551,300 383,300 383,233" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></polygon>
-<polygon fill="rgb(0,0,0)" points="383,340 513,340 521,348 521,407 383,407 383,340" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></polygon>
-<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="111" y="46"></rect>
-<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="303" y="46"></rect>
-<path d="M 172 80 L 172 453" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="8 4"></path>
-<rect fill="moccasin" height="284" stroke="rgb(0,0,0)" width="8" x="168" y="153"></rect>
-<path d="M 364 80 L 364 453" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="8 4"></path>
-<rect fill="moccasin" height="214" stroke="rgb(0,0,0)" width="8" x="360" y="153"></rect>
-<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="108" y="40"></rect>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="36" x="172.0" y="66">client</text>
-<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="300" y="40"></rect>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="36" x="364.0" y="66">server</text>
-<path d="M 180 153 L 356 153" fill="none" stroke="rgb(0,0,0)"></path>
-<polygon fill="rgb(0,0,0)" points="348,149 356,153 348,157" stroke="rgb(0,0,0)"></polygon>
-<polygon fill="rgb(240,248,255)" points="24,120 148,120 156,128 156,187 24,187 24,120" stroke="rgb(0,0,0)"></polygon>
-<path d="M 148 120 L 148 128" fill="none" stroke="rgb(0,0,0)"></path>
-<path d="M 148 128 L 156 128" fill="none" stroke="rgb(0,0,0)"></path>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="108" x="86.0" y="133">PacketType.REQUEST</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="60" x="62.0" y="146">channel ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="60" x="62.0" y="159">service ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="54" x="59.0" y="172">method ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="42" x="53.0" y="185">payload</text>
-<path d="M 180 260 L 356 260" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="4"></path>
-<polygon fill="rgb(0,0,0)" points="188,256 180,260 188,264" stroke="rgb(0,0,0)"></polygon>
-<polygon fill="rgb(240,248,255)" points="380,227 540,227 548,235 548,294 380,294 380,227" stroke="rgb(0,0,0)"></polygon>
-<path d="M 540 227 L 540 235" fill="none" stroke="rgb(0,0,0)"></path>
-<path d="M 540 235 L 548 235" fill="none" stroke="rgb(0,0,0)"></path>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="144" x="460.0" y="240">PacketType.SERVER_STREAM</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="60" x="418.0" y="253">channel ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="60" x="418.0" y="266">service ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="54" x="415.0" y="279">method ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="42" x="409.0" y="292">payload</text>
-<path d="M 180 367 L 356 367" fill="none" stroke="rgb(0,0,0)"></path>
-<polygon fill="rgb(0,0,0)" points="188,363 180,367 188,371" stroke="rgb(0,0,0)"></polygon>
-<polygon fill="rgb(240,248,255)" points="380,334 510,334 518,342 518,401 380,401 380,334" stroke="rgb(0,0,0)"></polygon>
-<path d="M 510 334 L 510 342" fill="none" stroke="rgb(0,0,0)"></path>
-<path d="M 510 342 L 518 342" fill="none" stroke="rgb(0,0,0)"></path>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="114" x="445.0" y="347">PacketType.RESPONSE</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="60" x="418.0" y="360">channel ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="60" x="418.0" y="373">service ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="54" x="415.0" y="386">method ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="36" x="406.0" y="399">status</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="42" x="205.0" y="151">request</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="138" x="287.0" y="258">messages (zero or more)</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="24" x="344.0" y="365">done</text>
-</svg>
diff --git a/pw_rpc/server_streaming_rpc_cancelled.svg b/pw_rpc/server_streaming_rpc_cancelled.svg
deleted file mode 100644
index d7432da..0000000
--- a/pw_rpc/server_streaming_rpc_cancelled.svg
+++ /dev/null
@@ -1,77 +0,0 @@
-<!-- Originally created with blockdiag. -->
-<svg viewBox="0 0 598 465" xmlns="http://www.w3.org/2000/svg" xmlns:inkspace="http://www.inkscape.org/namespaces/inkscape" xmlns:xlink="http://www.w3.org/1999/xlink" width="598px" height="465px">
-  <defs id="defs_block">
-    <filter height="1.504" id="filter_blur" inkspace:collect="always" width="1.1575" x="-0.07875" y="-0.252">
-      <feGaussianBlur id="feGaussianBlur3780" inkspace:collect="always" stdDeviation="4.2" />
-    </filter>
-  </defs>
-  <title>Cancelled Server Streaming RPC</title>
-  <desc>seqdiag {
-  default_note_color = aliceblue;
-
-  client -&gt; server [
-      label = "request",
-      leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID\npayload"
-  ];
-
-  client &lt;-- server [
-      noactivate,
-      label = "messages (zero or more)",
-      rightnote = "PacketType.SERVER_STREAM\nchannel ID\nservice ID\nmethod ID\npayload"
-  ];
-
-  client -&gt; server [
-      noactivate,
-      label = "cancel",
-      leftnote  = "PacketType.CLIENT_ERROR\nchannel ID\nservice ID\nmethod ID\nstatus=CANCELLED"
-  ];
-}</desc>
-  <rect fill="rgb(0,0,0)" height="284" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="8" x="203" y="159" />
-  <rect fill="rgb(0,0,0)" height="284" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="8" x="395" y="159" />
-  <polygon fill="rgb(0,0,0)" points="58,126 183,126 191,134 191,193 58,193 58,126" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" />
-  <polygon fill="rgb(0,0,0)" points="415,233 577,233 585,241 585,300 415,300 415,233" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" />
-  <polygon fill="rgb(0,0,0)" points="27,340 183,340 191,348 191,407 27,407 27,340" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" />
-  <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="143" y="46" />
-  <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="335" y="46" />
-  <path d="M 204 80 L 204 453" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="8 4" />
-  <rect fill="moccasin" height="284" stroke="rgb(0,0,0)" width="8" x="200" y="153" />
-  <path d="M 396 80 L 396 453" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="8 4" />
-  <rect fill="moccasin" height="284" stroke="rgb(0,0,0)" width="8" x="392" y="153" />
-  <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="140" y="40" />
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="37" x="204.5" y="66">client</text>
-  <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="332" y="40" />
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="37" x="396.5" y="66">server</text>
-  <path d="M 212 153 L 388 153" fill="none" stroke="rgb(0,0,0)" />
-  <polygon fill="rgb(0,0,0)" points="380,149 388,153 380,157" stroke="rgb(0,0,0)" />
-  <polygon fill="rgb(240,248,255)" points="55,120 180,120 188,128 188,187 55,187 55,120" stroke="rgb(0,0,0)" />
-  <path d="M 180 120 L 180 128" fill="none" stroke="rgb(0,0,0)" />
-  <path d="M 180 128 L 188 128" fill="none" stroke="rgb(0,0,0)" />
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="109" x="117.5" y="133">PacketType.REQUEST</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="61" x="93.5" y="146">channel ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="61" x="93.5" y="159">service ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="55" x="90.5" y="172">method ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="43" x="84.5" y="185">payload</text>
-  <path d="M 212 260 L 388 260" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="4" />
-  <polygon fill="rgb(0,0,0)" points="220,256 212,260 220,264" stroke="rgb(0,0,0)" />
-  <polygon fill="rgb(240,248,255)" points="412,227 574,227 582,235 582,294 412,294 412,227" stroke="rgb(0,0,0)" />
-  <path d="M 574 227 L 574 235" fill="none" stroke="rgb(0,0,0)" />
-  <path d="M 574 235 L 582 235" fill="none" stroke="rgb(0,0,0)" />
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="146" x="493.0" y="240">PacketType.SERVER_STREAM</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="61" x="450.5" y="253">channel ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="61" x="450.5" y="266">service ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="55" x="447.5" y="279">method ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="43" x="441.5" y="292">payload</text>
-  <path d="M 212 367 L 388 367" fill="none" stroke="rgb(0,0,0)" />
-  <polygon fill="rgb(0,0,0)" points="380,363 388,367 380,371" stroke="rgb(0,0,0)" />
-  <polygon fill="rgb(240,248,255)" points="24,334 180,334 188,342 188,401 24,401 24,334" stroke="rgb(0,0,0)" />
-  <path d="M 180 334 L 180 342" fill="none" stroke="rgb(0,0,0)" />
-  <path d="M 180 342 L 188 342" fill="none" stroke="rgb(0,0,0)" />
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="140" x="102.0" y="347">PacketType.CLIENT_ERROR</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="61" x="62.5" y="360">channel ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="61" x="62.5" y="373">service ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="55" x="59.5" y="386">method ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="97" x="80.5" y="399">status=CANCELLED</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="43" x="237.5" y="151">request</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="140" x="318.0" y="258">messages (zero or more)</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="37" x="234.5" y="365">cancel</text>
-</svg>
diff --git a/pw_rpc/server_test.cc b/pw_rpc/server_test.cc
index e51c3ae..685e4fc 100644
--- a/pw_rpc/server_test.cc
+++ b/pw_rpc/server_test.cc
@@ -18,13 +18,12 @@
 #include <cstdint>
 
 #include "gtest/gtest.h"
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_rpc/internal/method.h"
 #include "pw_rpc/internal/packet.h"
 #include "pw_rpc/internal/test_method.h"
-#include "pw_rpc/internal/test_utils.h"
 #include "pw_rpc/service.h"
-#include "pw_rpc_private/fake_server_reader_writer.h"
+#include "pw_rpc_private/internal_test_utils.h"
 
 namespace pw::rpc {
 namespace {
@@ -41,7 +40,7 @@
   TestService(uint32_t service_id)
       : Service(service_id, methods_),
         methods_{
-            TestMethod(100, MethodType::kBidirectionalStreaming),
+            TestMethod(100),
             TestMethod(200),
         } {}
 
@@ -59,14 +58,6 @@
   std::array<TestMethodUnion, 2> methods_;
 };
 
-class EmptyService : public Service {
- public:
-  constexpr EmptyService() : Service(200, methods_) {}
-
- private:
-  static constexpr std::array<TestMethodUnion, 0> methods_ = {};
-};
-
 class BasicServer : public ::testing::Test {
  protected:
   static constexpr byte kDefaultPayload[] = {
@@ -79,75 +70,37 @@
             Channel(),  // available for assignment
         },
         server_(channels_),
-        service_1_(1),
-        service_42_(42) {
-    server_.RegisterService(service_1_, service_42_, empty_service_);
+        service_(42) {
+    server_.RegisterService(service_);
   }
 
-  std::span<const byte> EncodePacket(
+  std::span<const byte> EncodeRequest(
       PacketType type,
       uint32_t channel_id,
       uint32_t service_id,
       uint32_t method_id,
-      std::span<const byte> payload = kDefaultPayload,
-      Status status = OkStatus()) {
-    auto result =
-        Packet(type, channel_id, service_id, method_id, 0, payload, status)
-            .Encode(request_buffer_);
+      std::span<const byte> payload = kDefaultPayload) {
+    auto result = Packet(type, channel_id, service_id, method_id, payload)
+                      .Encode(request_buffer_);
     EXPECT_EQ(OkStatus(), result.status());
     return result.value_or(ConstByteSpan());
   }
 
-  std::span<const byte> EncodeCancel(uint32_t channel_id = 1,
-                                     uint32_t service_id = 42,
-                                     uint32_t method_id = 100) {
-    return EncodePacket(PacketType::CLIENT_ERROR,
-                        channel_id,
-                        service_id,
-                        method_id,
-                        {},
-                        Status::Cancelled());
-  }
-
-  template <typename T = ConstByteSpan>
-  ConstByteSpan PacketForRpc(PacketType type,
-                             Status status = OkStatus(),
-                             T&& payload = {}) {
-    return EncodePacket(
-        type, 1, 42, 100, std::as_bytes(std::span(payload)), status);
-  }
-
-  RawFakeChannelOutput<2> output_;
+  TestOutput<128> output_;
   std::array<Channel, 3> channels_;
   Server server_;
-  TestService service_1_;
-  TestService service_42_;
-  EmptyService empty_service_;
+  TestService service_;
 
  private:
   byte request_buffer_[64];
 };
 
-TEST_F(BasicServer, ProcessPacket_ValidMethodInService1_InvokesMethod) {
+TEST_F(BasicServer, ProcessPacket_ValidMethod_InvokesMethod) {
   EXPECT_EQ(OkStatus(),
-            server_.ProcessPacket(EncodePacket(PacketType::REQUEST, 1, 1, 100),
-                                  output_));
+            server_.ProcessPacket(
+                EncodeRequest(PacketType::REQUEST, 1, 42, 100), output_));
 
-  const TestMethod& method = service_1_.method(100);
-  EXPECT_EQ(1u, method.last_channel_id());
-  ASSERT_EQ(sizeof(kDefaultPayload), method.last_request().payload().size());
-  EXPECT_EQ(std::memcmp(kDefaultPayload,
-                        method.last_request().payload().data(),
-                        method.last_request().payload().size()),
-            0);
-}
-
-TEST_F(BasicServer, ProcessPacket_ValidMethodInService42_InvokesMethod) {
-  EXPECT_EQ(OkStatus(),
-            server_.ProcessPacket(EncodePacket(PacketType::REQUEST, 1, 42, 200),
-                                  output_));
-
-  const TestMethod& method = service_42_.method(200);
+  const TestMethod& method = service_.method(100);
   EXPECT_EQ(1u, method.last_channel_id());
   ASSERT_EQ(sizeof(kDefaultPayload), method.last_request().payload().size());
   EXPECT_EQ(std::memcmp(kDefaultPayload,
@@ -158,67 +111,60 @@
 
 TEST_F(BasicServer, ProcessPacket_IncompletePacket_NothingIsInvoked) {
   EXPECT_EQ(Status::DataLoss(),
-            server_.ProcessPacket(EncodePacket(PacketType::REQUEST, 0, 42, 101),
+            server_.ProcessPacket(
+                EncodeRequest(PacketType::REQUEST, 0, 42, 101), output_));
+  EXPECT_EQ(Status::DataLoss(),
+            server_.ProcessPacket(EncodeRequest(PacketType::REQUEST, 1, 0, 101),
                                   output_));
   EXPECT_EQ(Status::DataLoss(),
-            server_.ProcessPacket(EncodePacket(PacketType::REQUEST, 1, 0, 101),
-                                  output_));
-  EXPECT_EQ(Status::DataLoss(),
-            server_.ProcessPacket(EncodePacket(PacketType::REQUEST, 1, 42, 0),
+            server_.ProcessPacket(EncodeRequest(PacketType::REQUEST, 1, 42, 0),
                                   output_));
 
-  EXPECT_EQ(0u, service_42_.method(100).last_channel_id());
-  EXPECT_EQ(0u, service_42_.method(200).last_channel_id());
+  EXPECT_EQ(0u, service_.method(100).last_channel_id());
+  EXPECT_EQ(0u, service_.method(200).last_channel_id());
 }
 
 TEST_F(BasicServer, ProcessPacket_NoChannel_SendsNothing) {
   EXPECT_EQ(Status::DataLoss(),
-            server_.ProcessPacket(EncodePacket(PacketType::REQUEST, 0, 42, 101),
-                                  output_));
+            server_.ProcessPacket(
+                EncodeRequest(PacketType::REQUEST, 0, 42, 101), output_));
 
-  EXPECT_EQ(output_.total_packets(), 0u);
+  EXPECT_EQ(output_.packet_count(), 0u);
 }
 
-TEST_F(BasicServer, ProcessPacket_NoService_SendsNothing) {
+TEST_F(BasicServer, ProcessPacket_NoService_SendsDataLoss) {
   EXPECT_EQ(Status::DataLoss(),
-            server_.ProcessPacket(EncodePacket(PacketType::REQUEST, 1, 0, 101),
+            server_.ProcessPacket(EncodeRequest(PacketType::REQUEST, 1, 0, 101),
                                   output_));
 
-  EXPECT_EQ(output_.total_packets(), 0u);
+  EXPECT_EQ(output_.sent_packet().type(), PacketType::SERVER_ERROR);
+  EXPECT_EQ(output_.sent_packet().status(), Status::DataLoss());
 }
 
-TEST_F(BasicServer, ProcessPacket_NoMethod_SendsNothing) {
+TEST_F(BasicServer, ProcessPacket_NoMethod_SendsDataLoss) {
   EXPECT_EQ(Status::DataLoss(),
-            server_.ProcessPacket(EncodePacket(PacketType::REQUEST, 1, 42, 0),
+            server_.ProcessPacket(EncodeRequest(PacketType::REQUEST, 1, 42, 0),
                                   output_));
 
-  EXPECT_EQ(output_.total_packets(), 0u);
+  EXPECT_EQ(output_.sent_packet().type(), PacketType::SERVER_ERROR);
+  EXPECT_EQ(output_.sent_packet().status(), Status::DataLoss());
 }
 
 TEST_F(BasicServer, ProcessPacket_InvalidMethod_NothingIsInvoked) {
   EXPECT_EQ(OkStatus(),
-            server_.ProcessPacket(EncodePacket(PacketType::REQUEST, 1, 42, 101),
-                                  output_));
-
-  EXPECT_EQ(0u, service_42_.method(100).last_channel_id());
-  EXPECT_EQ(0u, service_42_.method(200).last_channel_id());
-}
-
-TEST_F(BasicServer, ProcessPacket_ClientErrorWithInvalidMethod_NoResponse) {
-  EXPECT_EQ(OkStatus(),
             server_.ProcessPacket(
-                EncodePacket(PacketType::CLIENT_ERROR, 1, 42, 101), output_));
+                EncodeRequest(PacketType::REQUEST, 1, 42, 101), output_));
 
-  EXPECT_EQ(0u, output_.total_packets());
+  EXPECT_EQ(0u, service_.method(100).last_channel_id());
+  EXPECT_EQ(0u, service_.method(200).last_channel_id());
 }
 
 TEST_F(BasicServer, ProcessPacket_InvalidMethod_SendsError) {
   EXPECT_EQ(OkStatus(),
-            server_.ProcessPacket(EncodePacket(PacketType::REQUEST, 1, 42, 27),
+            server_.ProcessPacket(EncodeRequest(PacketType::REQUEST, 1, 42, 27),
                                   output_));
 
-  const Packet& packet =
-      static_cast<internal::test::FakeChannelOutput&>(output_).last_packet();
+  const Packet& packet = output_.sent_packet();
   EXPECT_EQ(packet.type(), PacketType::SERVER_ERROR);
   EXPECT_EQ(packet.channel_id(), 1u);
   EXPECT_EQ(packet.service_id(), 42u);
@@ -228,11 +174,10 @@
 
 TEST_F(BasicServer, ProcessPacket_InvalidService_SendsError) {
   EXPECT_EQ(OkStatus(),
-            server_.ProcessPacket(EncodePacket(PacketType::REQUEST, 1, 43, 27),
+            server_.ProcessPacket(EncodeRequest(PacketType::REQUEST, 1, 43, 27),
                                   output_));
 
-  const Packet& packet =
-      static_cast<internal::test::FakeChannelOutput&>(output_).last_packet();
+  const Packet& packet = output_.sent_packet();
   EXPECT_EQ(packet.type(), PacketType::SERVER_ERROR);
   EXPECT_EQ(packet.channel_id(), 1u);
   EXPECT_EQ(packet.service_id(), 43u);  // No service ID 43
@@ -240,297 +185,127 @@
   EXPECT_EQ(packet.status(), Status::NotFound());
 }
 
-TEST_F(BasicServer, ProcessPacket_UnassignedChannel) {
-  EXPECT_EQ(Status::Unavailable(),
+TEST_F(BasicServer, ProcessPacket_UnassignedChannel_AssignsToAvailableSlot) {
+  TestOutput<128> unassigned_output;
+  EXPECT_EQ(OkStatus(),
             server_.ProcessPacket(
-                EncodePacket(PacketType::REQUEST, /*channel_id=*/99, 42, 27)));
+                EncodeRequest(PacketType::REQUEST, /*channel_id=*/99, 42, 100),
+                unassigned_output));
+  EXPECT_EQ(channels_[2].id(), 99u);
 }
 
 TEST_F(BasicServer,
-       ProcessPacket_UnassignedChannel_SendsUnavailableToProvidedInterface) {
-  EXPECT_EQ(Status::Unavailable(),
+       ProcessPacket_UnassignedChannel_SendsResourceExhaustedIfCannotAssign) {
+  channels_[2] = Channel::Create<3>(&output_);  // Occupy only available channel
+
+  EXPECT_EQ(OkStatus(),
             server_.ProcessPacket(
-                EncodePacket(PacketType::REQUEST, /*channel_id=*/99, 42, 27),
+                EncodeRequest(PacketType::REQUEST, /*channel_id=*/99, 42, 27),
                 output_));
 
-  const Packet& packet =
-      static_cast<internal::test::FakeChannelOutput&>(output_).last_packet();
-  EXPECT_EQ(packet.status(), Status::Unavailable());
+  const Packet& packet = output_.sent_packet();
+  EXPECT_EQ(packet.status(), Status::ResourceExhausted());
   EXPECT_EQ(packet.channel_id(), 99u);
   EXPECT_EQ(packet.service_id(), 42u);
   EXPECT_EQ(packet.method_id(), 27u);
 }
 
-TEST_F(BasicServer, ProcessPacket_ClientErrorOnUnassignedChannel_NoResponse) {
-  channels_[2] = Channel::Create<3>(&output_);  // Occupy only available channel
-
-  EXPECT_EQ(
-      Status::Unavailable(),
-      server_.ProcessPacket(
-          EncodePacket(PacketType::CLIENT_ERROR, /*channel_id=*/99, 42, 27),
-          output_));
-
-  EXPECT_EQ(0u, output_.total_packets());
-}
-
-TEST_F(BasicServer, ProcessPacket_Cancel_MethodNotActive_SendsNothing) {
+TEST_F(BasicServer, ProcessPacket_Cancel_MethodNotActive_SendsError) {
   // Set up a fake ServerWriter representing an ongoing RPC.
   EXPECT_EQ(OkStatus(),
-            server_.ProcessPacket(EncodeCancel(1, 42, 100), output_));
+            server_.ProcessPacket(
+                EncodeRequest(PacketType::CANCEL_SERVER_STREAM, 1, 42, 100),
+                output_));
 
-  EXPECT_EQ(output_.total_packets(), 0u);
-}
-
-const Channel* GetChannel(internal::Endpoint& endpoint, uint32_t id) {
-  internal::LockGuard lock(internal::rpc_lock());
-  return endpoint.GetInternalChannel(id);
-}
-
-TEST_F(BasicServer, CloseChannel_NoCalls) {
-  EXPECT_NE(nullptr, GetChannel(server_, 2));
-  EXPECT_EQ(OkStatus(), server_.CloseChannel(2));
-  EXPECT_EQ(nullptr, GetChannel(server_, 2));
-  ASSERT_EQ(output_.total_packets(), 0u);
-}
-
-TEST_F(BasicServer, CloseChannel_UnknownChannel) {
-  ASSERT_EQ(nullptr, GetChannel(server_, 13579));
-  EXPECT_EQ(Status::NotFound(), server_.CloseChannel(13579));
-}
-
-TEST_F(BasicServer, CloseChannel_PendingCall) {
-  EXPECT_NE(nullptr, GetChannel(server_, 1));
-  EXPECT_EQ(static_cast<internal::Endpoint&>(server_).active_call_count(), 0u);
-
-  internal::TestMethod::FakeServerCall call;
-  service_42_.method(100).keep_call_active(call);
-
-  EXPECT_EQ(OkStatus(),
-            server_.ProcessPacket(EncodePacket(PacketType::REQUEST, 1, 42, 100),
-                                  output_));
-
-  Status on_error_status;
-  call.set_on_error(
-      [&on_error_status](Status error) { on_error_status = error; });
-
-  ASSERT_TRUE(call.active());
-  EXPECT_EQ(static_cast<internal::Endpoint&>(server_).active_call_count(), 1u);
-
-  EXPECT_EQ(OkStatus(), server_.CloseChannel(1));
-  EXPECT_EQ(nullptr, GetChannel(server_, 1));
-
-  EXPECT_EQ(static_cast<internal::Endpoint&>(server_).active_call_count(), 0u);
-
-  // Should call on_error, but not send a packet since the channel is closed.
-  EXPECT_EQ(Status::Aborted(), on_error_status);
-  ASSERT_EQ(output_.total_packets(), 0u);
-}
-
-TEST_F(BasicServer, OpenChannel_UnusedSlot) {
-  const std::span request = EncodePacket(PacketType::REQUEST, 9, 42, 100);
-  EXPECT_EQ(Status::Unavailable(), server_.ProcessPacket(request, output_));
-
-  EXPECT_EQ(OkStatus(), server_.OpenChannel(9, output_));
-  EXPECT_EQ(OkStatus(), server_.ProcessPacket(request, output_));
-
-  const Packet& packet =
-      static_cast<internal::test::FakeChannelOutput&>(output_).last_packet();
-  EXPECT_EQ(packet.type(), PacketType::RESPONSE);
-  EXPECT_EQ(packet.channel_id(), 9u);
+  const Packet& packet = output_.sent_packet();
+  EXPECT_EQ(packet.type(), PacketType::SERVER_ERROR);
+  EXPECT_EQ(packet.channel_id(), 1u);
   EXPECT_EQ(packet.service_id(), 42u);
   EXPECT_EQ(packet.method_id(), 100u);
-}
-
-TEST_F(BasicServer, OpenChannel_AlreadyExists) {
-  ASSERT_NE(nullptr, GetChannel(server_, 1));
-  EXPECT_EQ(Status::AlreadyExists(), server_.OpenChannel(1, output_));
-}
-
-TEST_F(BasicServer, OpenChannel_AdditionalSlot) {
-  EXPECT_EQ(OkStatus(), server_.OpenChannel(3, output_));
-
-  constexpr Status kExpected =
-      PW_RPC_DYNAMIC_ALLOCATION == 0 ? Status::ResourceExhausted() : OkStatus();
-  EXPECT_EQ(kExpected, server_.OpenChannel(19823, output_));
-}
-
-class BidiMethod : public BasicServer {
- protected:
-  BidiMethod()
-      : responder_(internal::CallContext(server_,
-                                         channels_[0].id(),
-                                         service_42_,
-                                         service_42_.method(100),
-                                         0)) {
-    ASSERT_TRUE(responder_.active());
-  }
-
-  internal::test::FakeServerReaderWriter responder_;
-};
-
-TEST_F(BidiMethod, DuplicateCall_CancelsExistingThenCallsAgain) {
-  int cancelled = 0;
-  responder_.set_on_error([&cancelled](Status error) {
-    if (error.IsCancelled()) {
-      cancelled += 1;
-    }
-  });
-
-  const TestMethod& method = service_42_.method(100);
-  ASSERT_EQ(method.invocations(), 0u);
-
-  EXPECT_EQ(OkStatus(),
-            server_.ProcessPacket(PacketForRpc(PacketType::REQUEST), output_));
-
-  EXPECT_EQ(cancelled, 1);
-  EXPECT_EQ(method.invocations(), 1u);
-}
-
-TEST_F(BidiMethod, Cancel_ClosesServerWriter) {
-  EXPECT_EQ(OkStatus(), server_.ProcessPacket(EncodeCancel(), output_));
-
-  EXPECT_FALSE(responder_.active());
-}
-
-TEST_F(BidiMethod, Cancel_SendsNoResponse) {
-  EXPECT_EQ(OkStatus(), server_.ProcessPacket(EncodeCancel(), output_));
-
-  EXPECT_EQ(output_.total_packets(), 0u);
-}
-
-TEST_F(BidiMethod, ClientError_ClosesServerWriterWithoutResponse) {
-  ASSERT_EQ(
-      OkStatus(),
-      server_.ProcessPacket(PacketForRpc(PacketType::CLIENT_ERROR), output_));
-
-  EXPECT_FALSE(responder_.active());
-  EXPECT_EQ(output_.total_packets(), 0u);
-}
-
-TEST_F(BidiMethod, ClientError_CallsOnErrorCallback) {
-  Status status = Status::Unknown();
-  responder_.set_on_error([&status](Status error) { status = error; });
-
-  ASSERT_EQ(OkStatus(),
-            server_.ProcessPacket(PacketForRpc(PacketType::CLIENT_ERROR,
-                                               Status::Unauthenticated()),
-                                  output_));
-
-  EXPECT_EQ(status, Status::Unauthenticated());
-}
-
-TEST_F(BidiMethod, Cancel_CallsOnErrorCallback) {
-  Status status = Status::Unknown();
-  responder_.set_on_error([&status](Status error) { status = error; });
-
-  ASSERT_EQ(OkStatus(), server_.ProcessPacket(EncodeCancel(), output_));
-  EXPECT_EQ(status, Status::Cancelled());
-}
-
-TEST_F(BidiMethod, Cancel_IncorrectChannel_SendsNothing) {
-  EXPECT_EQ(OkStatus(),
-            server_.ProcessPacket(EncodeCancel(2, 42, 100), output_));
-
-  EXPECT_EQ(output_.total_packets(), 0u);
-  EXPECT_TRUE(responder_.active());
-}
-
-TEST_F(BidiMethod, Cancel_IncorrectService_SendsNothing) {
-  EXPECT_EQ(OkStatus(),
-            server_.ProcessPacket(EncodeCancel(1, 43, 100), output_));
-  EXPECT_EQ(output_.total_packets(), 0u);
-  EXPECT_TRUE(responder_.active());
-}
-
-TEST_F(BidiMethod, Cancel_IncorrectMethod_SendsNothing) {
-  EXPECT_EQ(OkStatus(),
-            server_.ProcessPacket(EncodeCancel(1, 42, 101), output_));
-  EXPECT_EQ(output_.total_packets(), 0u);
-  EXPECT_TRUE(responder_.active());
-}
-
-TEST_F(BidiMethod, ClientStream_CallsCallback) {
-  ConstByteSpan data = std::as_bytes(std::span("?"));
-  responder_.set_on_next([&data](ConstByteSpan payload) { data = payload; });
-
-  ASSERT_EQ(OkStatus(),
-            server_.ProcessPacket(
-                PacketForRpc(PacketType::CLIENT_STREAM, {}, "hello"), output_));
-
-  EXPECT_EQ(output_.total_packets(), 0u);
-  EXPECT_STREQ(reinterpret_cast<const char*>(data.data()), "hello");
-}
-
-#if PW_RPC_CLIENT_STREAM_END_CALLBACK
-
-TEST_F(BidiMethod, ClientStreamEnd_CallsCallback) {
-  bool called = false;
-  responder_.set_on_client_stream_end([&called]() { called = true; });
-
-  ASSERT_EQ(OkStatus(),
-            server_.ProcessPacket(PacketForRpc(PacketType::CLIENT_STREAM_END),
-                                  output_));
-
-  EXPECT_EQ(output_.total_packets(), 0u);
-  EXPECT_TRUE(called);
-}
-
-TEST_F(BidiMethod, ClientStreamEnd_ErrorWhenClosed) {
-  const auto end = PacketForRpc(PacketType::CLIENT_STREAM_END);
-  ASSERT_EQ(OkStatus(), server_.ProcessPacket(end, output_));
-
-  bool called = false;
-  responder_.set_on_client_stream_end([&called]() { called = true; });
-
-  ASSERT_EQ(OkStatus(), server_.ProcessPacket(end, output_));
-
-  ASSERT_EQ(output_.total_packets(), 1u);
-  const Packet& packet =
-      static_cast<internal::test::FakeChannelOutput&>(output_).last_packet();
-  EXPECT_EQ(packet.type(), PacketType::SERVER_ERROR);
   EXPECT_EQ(packet.status(), Status::FailedPrecondition());
 }
 
-#endif  // PW_RPC_CLIENT_STREAM_END_CALLBACK
-
-class ServerStreamingMethod : public BasicServer {
+class MethodPending : public BasicServer {
  protected:
-  ServerStreamingMethod()
-      : call_(server_,
-              channels_[0].id(),
-              service_42_,
-              service_42_.method(100),
-              0),
-        responder_(call_) {
-    ASSERT_TRUE(responder_.active());
+  MethodPending()
+      : call_(static_cast<internal::Server&>(server_),
+              static_cast<internal::Channel&>(channels_[0]),
+              service_,
+              service_.method(100)),
+        writer_(call_) {
+    ASSERT_TRUE(writer_.open());
   }
 
-  internal::CallContext call_;
-  internal::test::FakeServerWriter responder_;
+  internal::ServerCall call_;
+  internal::BaseServerWriter writer_;
 };
 
-TEST_F(ServerStreamingMethod, ClientStream_InvalidArgumentError) {
-  ASSERT_EQ(
-      OkStatus(),
-      server_.ProcessPacket(PacketForRpc(PacketType::CLIENT_STREAM), output_));
+TEST_F(MethodPending, ProcessPacket_Cancel_ClosesServerWriter) {
+  EXPECT_EQ(OkStatus(),
+            server_.ProcessPacket(
+                EncodeRequest(PacketType::CANCEL_SERVER_STREAM, 1, 42, 100),
+                output_));
 
-  ASSERT_EQ(output_.total_packets(), 1u);
-  const Packet& packet =
-      static_cast<internal::test::FakeChannelOutput&>(output_).last_packet();
-  EXPECT_EQ(packet.type(), PacketType::SERVER_ERROR);
-  EXPECT_EQ(packet.status(), Status::InvalidArgument());
+  EXPECT_FALSE(writer_.open());
 }
 
-TEST_F(ServerStreamingMethod, ClientStreamEnd_InvalidArgumentError) {
-  ASSERT_EQ(OkStatus(),
-            server_.ProcessPacket(PacketForRpc(PacketType::CLIENT_STREAM_END),
-                                  output_));
+TEST_F(MethodPending, ProcessPacket_Cancel_SendsStreamEndPacket) {
+  EXPECT_EQ(OkStatus(),
+            server_.ProcessPacket(
+                EncodeRequest(PacketType::CANCEL_SERVER_STREAM, 1, 42, 100),
+                output_));
 
-  ASSERT_EQ(output_.total_packets(), 1u);
-  const Packet& packet =
-      static_cast<internal::test::FakeChannelOutput&>(output_).last_packet();
-  EXPECT_EQ(packet.type(), PacketType::SERVER_ERROR);
-  EXPECT_EQ(packet.status(), Status::InvalidArgument());
+  const Packet& packet = output_.sent_packet();
+  EXPECT_EQ(packet.type(), PacketType::SERVER_STREAM_END);
+  EXPECT_EQ(packet.channel_id(), 1u);
+  EXPECT_EQ(packet.service_id(), 42u);
+  EXPECT_EQ(packet.method_id(), 100u);
+  EXPECT_TRUE(packet.payload().empty());
+  EXPECT_EQ(packet.status(), Status::Cancelled());
+}
+
+TEST_F(MethodPending,
+       ProcessPacket_ClientError_ClosesServerWriterWithoutStreamEnd) {
+  EXPECT_EQ(OkStatus(),
+            server_.ProcessPacket(
+                EncodeRequest(PacketType::CLIENT_ERROR, 1, 42, 100), output_));
+
+  EXPECT_FALSE(writer_.open());
+  EXPECT_EQ(output_.packet_count(), 0u);
+}
+
+TEST_F(MethodPending, ProcessPacket_Cancel_IncorrectChannel) {
+  EXPECT_EQ(OkStatus(),
+            server_.ProcessPacket(
+                EncodeRequest(PacketType::CANCEL_SERVER_STREAM, 2, 42, 100),
+                output_));
+
+  EXPECT_EQ(output_.sent_packet().type(), PacketType::SERVER_ERROR);
+  EXPECT_EQ(output_.sent_packet().status(), Status::FailedPrecondition());
+  EXPECT_TRUE(writer_.open());
+}
+
+TEST_F(MethodPending, ProcessPacket_Cancel_IncorrectService) {
+  EXPECT_EQ(OkStatus(),
+            server_.ProcessPacket(
+                EncodeRequest(PacketType::CANCEL_SERVER_STREAM, 1, 43, 100),
+                output_));
+
+  EXPECT_EQ(output_.sent_packet().type(), PacketType::SERVER_ERROR);
+  EXPECT_EQ(output_.sent_packet().status(), Status::NotFound());
+  EXPECT_EQ(output_.sent_packet().service_id(), 43u);
+  EXPECT_EQ(output_.sent_packet().method_id(), 100u);
+  EXPECT_TRUE(writer_.open());
+}
+
+TEST_F(MethodPending, ProcessPacket_CancelIncorrectMethod) {
+  EXPECT_EQ(OkStatus(),
+            server_.ProcessPacket(
+                EncodeRequest(PacketType::CANCEL_SERVER_STREAM, 1, 42, 101),
+                output_));
+  EXPECT_EQ(output_.sent_packet().type(), PacketType::SERVER_ERROR);
+  EXPECT_EQ(output_.sent_packet().status(), Status::NotFound());
+  EXPECT_TRUE(writer_.open());
 }
 
 }  // namespace
diff --git a/pw_rpc/service_test.cc b/pw_rpc/service_test.cc
index 174de72..959323f 100644
--- a/pw_rpc/service_test.cc
+++ b/pw_rpc/service_test.cc
@@ -28,7 +28,9 @@
 
 namespace {
 
-void InvokeIt(const internal::CallContext&, const internal::Packet&) {}
+void InvokeIt(const internal::Method&,
+              internal::ServerCall&,
+              const internal::Packet&) {}
 
 class ServiceTestMethod : public internal::Method {
  public:
diff --git a/pw_rpc/size_report/BUILD b/pw_rpc/size_report/BUILD
new file mode 100644
index 0000000..e4e46ca
--- /dev/null
+++ b/pw_rpc/size_report/BUILD
@@ -0,0 +1,55 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_binary",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_binary(
+    name = "base",
+    srcs = ["base.cc"],
+    deps = [
+        "//pw_assert",
+        "//pw_bloat:bloat_this_binary",
+        "//pw_log",
+        "//pw_rpc:server",
+        "//pw_sys_io",
+    ],
+)
+
+pw_cc_binary(
+    name = "server_only",
+    srcs = ["server_only.cc"],
+    deps = [
+        "//pw_assert",
+        "//pw_bloat:bloat_this_binary",
+        "//pw_log",
+        "//pw_rpc:server",
+        "//pw_sys_io",
+    ],
+)
+
+# TODO(frolv): Figure out how to add third-party nanopb to Bazel.
+filegroup(
+    name = "nanopb_reports",
+    srcs = [
+        "base_with_nanopb.cc",
+        "server_with_echo_service.cc",
+    ],
+)
diff --git a/pw_rpc/size_report/BUILD.bazel b/pw_rpc/size_report/BUILD.bazel
deleted file mode 100644
index ade1cf2..0000000
--- a/pw_rpc/size_report/BUILD.bazel
+++ /dev/null
@@ -1,55 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_binary",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_binary(
-    name = "base",
-    srcs = ["base.cc"],
-    deps = [
-        "//pw_assert",
-        "//pw_bloat:bloat_this_binary",
-        "//pw_log",
-        "//pw_rpc",
-        "//pw_sys_io",
-    ],
-)
-
-pw_cc_binary(
-    name = "server_only",
-    srcs = ["server_only.cc"],
-    deps = [
-        "//pw_assert",
-        "//pw_bloat:bloat_this_binary",
-        "//pw_log",
-        "//pw_rpc",
-        "//pw_sys_io",
-    ],
-)
-
-# TODO(frolv): Figure out how to add third-party nanopb to Bazel.
-filegroup(
-    name = "nanopb_reports",
-    srcs = [
-        "base_with_nanopb.cc",
-        "server_with_echo_service.cc",
-    ],
-)
diff --git a/pw_rpc/size_report/base.cc b/pw_rpc/size_report/base.cc
index 76043ab..031afee 100644
--- a/pw_rpc/size_report/base.cc
+++ b/pw_rpc/size_report/base.cc
@@ -12,7 +12,7 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_bloat/bloat_this_binary.h"
 #include "pw_log/log.h"
 #include "pw_sys_io/sys_io.h"
@@ -27,10 +27,8 @@
   PW_LOG_INFO("We care about optimizing: %d", *unoptimizable);
 
   std::byte packet_buffer[128];
-  pw::sys_io::ReadBytes(packet_buffer)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  pw::sys_io::WriteBytes(packet_buffer)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  pw::sys_io::ReadBytes(packet_buffer);
+  pw::sys_io::WriteBytes(packet_buffer);
 
   return static_cast<int>(packet_buffer[92]);
 }
diff --git a/pw_rpc/size_report/base_with_nanopb.cc b/pw_rpc/size_report/base_with_nanopb.cc
index 26d9605..a5333db 100644
--- a/pw_rpc/size_report/base_with_nanopb.cc
+++ b/pw_rpc/size_report/base_with_nanopb.cc
@@ -14,7 +14,7 @@
 
 #include "pb_decode.h"
 #include "pb_encode.h"
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_bloat/bloat_this_binary.h"
 #include "pw_log/log.h"
 #include "pw_sys_io/sys_io.h"
diff --git a/pw_rpc/size_report/server_only.cc b/pw_rpc/size_report/server_only.cc
index aa466e6..b57102b 100644
--- a/pw_rpc/size_report/server_only.cc
+++ b/pw_rpc/size_report/server_only.cc
@@ -12,7 +12,7 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_bloat/bloat_this_binary.h"
 #include "pw_log/log.h"
 #include "pw_rpc/server.h"
@@ -24,9 +24,15 @@
  public:
   Output() : ChannelOutput("output") {}
 
-  pw::Status Send(std::span<const std::byte> buffer) override {
+  std::span<std::byte> AcquireBuffer() override { return buffer_; }
+
+  pw::Status SendAndReleaseBuffer(std::span<const std::byte> buffer) override {
+    PW_DCHECK_PTR_EQ(buffer.data(), buffer_);
     return pw::sys_io::WriteBytes(buffer).status();
   }
+
+ private:
+  std::byte buffer_[128];
 };
 
 namespace my_product {
@@ -45,13 +51,10 @@
   PW_LOG_INFO("We care about optimizing: %d", *unoptimizable);
 
   std::byte packet_buffer[128];
-  pw::sys_io::ReadBytes(packet_buffer)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  pw::sys_io::WriteBytes(packet_buffer)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  pw::sys_io::ReadBytes(packet_buffer);
+  pw::sys_io::WriteBytes(packet_buffer);
 
-  my_product::server.ProcessPacket(packet_buffer, my_product::output)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  my_product::server.ProcessPacket(packet_buffer, my_product::output);
 
   return static_cast<int>(packet_buffer[92]);
 }
diff --git a/pw_rpc/size_report/server_with_echo_service.cc b/pw_rpc/size_report/server_with_echo_service.cc
index 79e7ca9..9b2f4cf 100644
--- a/pw_rpc/size_report/server_with_echo_service.cc
+++ b/pw_rpc/size_report/server_with_echo_service.cc
@@ -14,7 +14,7 @@
 
 #include "pb_decode.h"
 #include "pb_encode.h"
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_bloat/bloat_this_binary.h"
 #include "pw_log/log.h"
 #include "pw_rpc/echo_service_nanopb.h"
@@ -27,9 +27,15 @@
  public:
   Output() : ChannelOutput("output") {}
 
-  pw::Status Send(std::span<const std::byte> buffer) override {
+  std::span<std::byte> AcquireBuffer() override { return buffer_; }
+
+  pw::Status SendAndReleaseBuffer(std::span<const std::byte> buffer) override {
+    PW_DCHECK_PTR_EQ(buffer.data(), buffer_);
     return pw::sys_io::WriteBytes(buffer).status();
   }
+
+ private:
+  std::byte buffer_[128];
 };
 
 namespace my_product {
diff --git a/pw_rpc/system_server/BUILD b/pw_rpc/system_server/BUILD
new file mode 100644
index 0000000..253c803
--- /dev/null
+++ b/pw_rpc/system_server/BUILD
@@ -0,0 +1,42 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])
+
+pw_cc_library(
+    name = "facade",
+    hdrs = ["public/pw_rpc_system_server/rpc_server.h"],
+    includes = ["public"],
+    deps = [
+        "//pw_span",
+        "//pw_status",
+    ],
+)
+
+pw_cc_library(
+    name = "system_server",
+    hdrs = ["public/pw_rpc_system_server/rpc_server.h"],
+    deps = [
+        ":facade",
+        "//pw_span",
+        "//pw_status",
+    ],
+)
diff --git a/pw_rpc/system_server/BUILD.bazel b/pw_rpc/system_server/BUILD.bazel
deleted file mode 100644
index f89e97f..0000000
--- a/pw_rpc/system_server/BUILD.bazel
+++ /dev/null
@@ -1,59 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "facade",
-    hdrs = ["public/pw_rpc_system_server/rpc_server.h"],
-    includes = ["public"],
-    deps = [
-        "//pw_rpc",
-        "//pw_span",
-        "//pw_status",
-    ],
-)
-
-pw_cc_library(
-    name = "system_server",
-    hdrs = [
-        "public/pw_rpc_system_server/rpc_server.h",
-        "public/pw_rpc_system_server/socket.h",
-    ],
-    deps = [
-        ":facade",
-        "//pw_rpc",
-        "//pw_span",
-        "//pw_status",
-        "//pw_stream",
-        "@pigweed_config//:pw_rpc_system_server_backend",
-    ],
-)
-
-pw_cc_library(
-    name = "system_server_backend_multiplexer",
-    visibility = ["@pigweed_config//:__pkg__"],
-    deps = select({
-        "//pw_build/constraints/board:stm32f429i-disc1": ["//targets/stm32f429i_disc1:system_rpc_server"],
-        "//pw_build/constraints/board:mimxrt595_evk": ["//targets/mimxrt595_evk:system_rpc_server"],
-        "//conditions:default": ["//targets/host:system_rpc_server"],
-    }),
-)
diff --git a/pw_rpc/system_server/BUILD.gn b/pw_rpc/system_server/BUILD.gn
index e6721d6..c420352 100644
--- a/pw_rpc/system_server/BUILD.gn
+++ b/pw_rpc/system_server/BUILD.gn
@@ -26,12 +26,9 @@
 pw_facade("system_server") {
   backend = pw_rpc_system_server_BACKEND
   public_configs = [ ":public_includes" ]
-  public_deps = [ "$dir_pw_rpc:server" ]
+  public_deps = [
+    "$dir_pw_rpc:server",
+    "$dir_pw_stream",
+  ]
   public = [ "public/pw_rpc_system_server/rpc_server.h" ]
 }
-
-pw_source_set("socket") {
-  public_configs = [ ":public_includes" ]
-  public = [ "public/pw_rpc_system_server/socket.h" ]
-  deps = [ ":system_server" ]
-}
diff --git a/pw_rpc/system_server/public/pw_rpc_system_server/rpc_server.h b/pw_rpc/system_server/public/pw_rpc_system_server/rpc_server.h
index d062610..73f2dcd 100644
--- a/pw_rpc/system_server/public/pw_rpc_system_server/rpc_server.h
+++ b/pw_rpc/system_server/public/pw_rpc_system_server/rpc_server.h
@@ -14,6 +14,7 @@
 #pragma once
 
 #include "pw_rpc/server.h"
+#include "pw_stream/stream.h"
 
 namespace pw::rpc::system_server {
 
diff --git a/pw_rpc/system_server/public/pw_rpc_system_server/socket.h b/pw_rpc/system_server/public/pw_rpc_system_server/socket.h
deleted file mode 100644
index 6476335..0000000
--- a/pw_rpc/system_server/public/pw_rpc_system_server/socket.h
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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>
-
-namespace pw::rpc::system_server {
-
-// Sets the port to use for pw::rpc::system_server backends that use sockets.
-void set_socket_port(uint16_t port);
-
-}  // namespace pw::rpc::system_server
diff --git a/pw_rpc/test_rpc_server.cc b/pw_rpc/test_rpc_server.cc
deleted file mode 100644
index 91b9688..0000000
--- a/pw_rpc/test_rpc_server.cc
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_rpc/internal/log_config.h"
-// PW_LOG_* macros must be first.
-
-#include "pw_assert/check.h"
-#include "pw_log/log.h"
-#include "pw_rpc/benchmark.h"
-#include "pw_rpc_system_server/rpc_server.h"
-#include "pw_rpc_system_server/socket.h"
-
-namespace {
-
-pw::rpc::BenchmarkService benchmark_service;
-
-}  // namespace
-
-int main(int argc, char* argv[]) {
-  if (argc != 2) {
-    PW_LOG_ERROR("Usage: %s PORT", argv[0]);
-    return 1;
-  }
-
-  pw::rpc::system_server::set_socket_port(std::atoi(argv[1]));
-  pw::rpc::system_server::Init();
-  pw::rpc::system_server::Server().RegisterService(benchmark_service);
-
-  PW_CHECK_OK(pw::rpc::system_server::Start());
-
-  return 0;
-}
diff --git a/pw_rpc/ts/BUILD.bazel b/pw_rpc/ts/BUILD.bazel
deleted file mode 100644
index ef3bc64..0000000
--- a/pw_rpc/ts/BUILD.bazel
+++ /dev/null
@@ -1,106 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
-load("@npm//@bazel/typescript:index.bzl", "ts_library", "ts_project")
-load("@npm//@bazel/jasmine:index.bzl", "jasmine_node_test")
-load("@rules_proto_grpc//js:defs.bzl", "js_proto_library")
-load("//pw_protobuf_compiler/ts:ts_proto_collection.bzl", "ts_proto_collection")
-
-package(default_visibility = ["//visibility:public"])
-
-ts_project(
-    name = "lib",
-    srcs = [
-        "call.ts",
-        "client.ts",
-        "descriptors.ts",
-        "hash.ts",
-        "index.ts",
-        "method.ts",
-        "packets.ts",
-        "rpc_classes.ts",
-    ],
-    declaration = True,
-    source_map = True,
-    deps = [
-        ":packet_proto_tspb",
-        "//pw_protobuf_compiler/ts:pw_protobuf_compiler",
-        "//pw_status/ts:pw_status",
-        "@npm//@types/google-protobuf",
-        "@npm//wait-queue",
-    ],
-)
-
-js_library(
-    name = "pw_rpc",
-    package_name = "@pigweed/pw_rpc",
-    srcs = ["package.json"],
-    deps = [":lib"],
-)
-
-ts_proto_collection(
-    name = "rpc_proto_collection",
-    js_proto_library = "@pigweed//pw_rpc/ts:test_protos_tspb",
-    proto_library = "@pigweed//pw_rpc/ts:test_protos",
-)
-
-ts_library(
-    name = "rpc_test_lib",
-    srcs = [
-        "call_test.ts",
-        "client_test.ts",
-        "descriptors_test.ts",
-        "packets_test.ts",
-    ],
-    data = [
-        ":test_protos",
-    ],
-    deps = [
-        ":lib",
-        ":packet_proto_tspb",
-        ":rpc_proto_collection",
-        ":test_protos_tspb",
-        "//pw_protobuf_compiler/ts:pw_protobuf_compiler",
-        "//pw_status/ts:pw_status",
-        "@npm//@types/google-protobuf",
-        "@npm//@types/jasmine",
-        "@npm//@types/node",
-    ],
-)
-
-jasmine_node_test(
-    name = "rpc_test",
-    srcs = [
-        ":rpc_test_lib",
-    ],
-)
-
-proto_library(
-    name = "test_protos",
-    srcs = [
-        "test.proto",
-        "test2.proto",
-    ],
-)
-
-js_proto_library(
-    name = "test_protos_tspb",
-    protos = [":test_protos"],
-)
-
-js_proto_library(
-    name = "packet_proto_tspb",
-    protos = ["//pw_rpc:internal_packet_proto"],
-)
diff --git a/pw_rpc/ts/BUILD.gn b/pw_rpc/ts/BUILD.gn
deleted file mode 100644
index 58c1734..0000000
--- a/pw_rpc/ts/BUILD.gn
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/python.gni")
-import("$dir_pw_docgen/docs.gni")
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
diff --git a/pw_rpc/ts/call.ts b/pw_rpc/ts/call.ts
deleted file mode 100644
index e084fbf..0000000
--- a/pw_rpc/ts/call.ts
+++ /dev/null
@@ -1,308 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 {Status} from '@pigweed/pw_status';
-import {Message} from 'google-protobuf';
-
-import WaitQueue = require('wait-queue');
-
-import {PendingCalls, Rpc} from './rpc_classes';
-
-export type Callback = (a: any) => any;
-
-class RpcError extends Error {
-  status: Status;
-
-  constructor(rpc: Rpc, status: Status) {
-    let message = '';
-    if (status === Status.NOT_FOUND) {
-      message = ': the RPC server does not support this RPC';
-    } else if (status === Status.DATA_LOSS) {
-      message = ': an error occurred while decoding the RPC payload';
-    }
-
-    super(`${rpc.method.name} failed with error ${Status[status]}${message}`);
-    this.status = status;
-  }
-}
-
-class RpcTimeout extends Error {
-  readonly rpc: Rpc;
-  readonly timeoutMs: number;
-
-  constructor(rpc: Rpc, timeoutMs: number) {
-    super(`${rpc.method.name} timed out after ${timeoutMs} ms`);
-    this.rpc = rpc;
-    this.timeoutMs = timeoutMs;
-  }
-}
-
-/** Represent an in-progress or completed RPC call. */
-export class Call {
-  // Responses ordered by arrival time. Undefined signifies stream completion.
-  private responseQueue = new WaitQueue<Message | undefined>();
-  protected responses: Message[] = [];
-
-  private rpcs: PendingCalls;
-  private rpc: Rpc;
-
-  private onNext: Callback;
-  private onCompleted: Callback;
-  private onError: Callback;
-
-  status?: Status;
-  error?: Status;
-  callbackException?: Error;
-
-  constructor(
-    rpcs: PendingCalls,
-    rpc: Rpc,
-    onNext: Callback,
-    onCompleted: Callback,
-    onError: Callback
-  ) {
-    this.rpcs = rpcs;
-    this.rpc = rpc;
-
-    this.onNext = onNext;
-    this.onCompleted = onCompleted;
-    this.onError = onError;
-  }
-
-  /* Calls the RPC. This must be called immediately after construction. */
-  invoke(request?: Message, ignoreErrors = false): void {
-    const previous = this.rpcs.sendRequest(
-      this.rpc,
-      this,
-      ignoreErrors,
-      request
-    );
-
-    if (previous !== undefined && !previous.completed) {
-      previous.handleError(Status.CANCELLED);
-    }
-  }
-
-  get completed(): boolean {
-    return this.status !== undefined || this.error !== undefined;
-  }
-
-  private invokeCallback(func: () => {}) {
-    try {
-      func();
-    } catch (err: unknown) {
-      if (err instanceof Error) {
-        console.error(
-          `An exception was raised while invoking a callback: ${err}`
-        );
-        this.callbackException = err;
-      }
-      console.error(`Unexpected item thrown while invoking callback: ${err}`);
-    }
-  }
-
-  handleResponse(response: Message): void {
-    this.responses.push(response);
-    this.responseQueue.push(response);
-    this.invokeCallback(() => this.onNext(response));
-  }
-
-  handleCompletion(status: Status) {
-    this.status = status;
-    this.responseQueue.push(undefined);
-    this.invokeCallback(() => this.onCompleted(status));
-  }
-
-  handleError(error: Status): void {
-    this.error = error;
-    this.responseQueue.push(undefined);
-    this.invokeCallback(() => this.onError(error));
-  }
-
-  private async queuePopWithTimeout(
-    timeoutMs: number
-  ): Promise<Message | undefined> {
-    return new Promise(async (resolve, reject) => {
-      let timeoutExpired = false;
-      const timeoutWatcher = setTimeout(() => {
-        timeoutExpired = true;
-        reject(new RpcTimeout(this.rpc, timeoutMs));
-      }, timeoutMs);
-      const response = await this.responseQueue.shift();
-      if (timeoutExpired) {
-        this.responseQueue.unshift(response);
-        return;
-      }
-      clearTimeout(timeoutWatcher);
-      resolve(response);
-    });
-  }
-
-  /**
-   * Yields responses up the specified count as they are added.
-   *
-   * Throws an error as soon as it is received even if there are still
-   * responses in the queue.
-   *
-   * Usage
-   * ```
-   * for await (const response of call.getResponses(5)) {
-   *  console.log(response);
-   * }
-   * ```
-   *
-   * @param {number} count The number of responses to read before returning.
-   *    If no value is specified, getResponses will block until the stream
-   *    either ends or hits an error.
-   * @param {number} timeout The number of milliseconds to wait for a response
-   *    before throwing an error.
-   */
-  async *getResponses(
-    count?: number,
-    timeoutMs?: number
-  ): AsyncGenerator<Message> {
-    this.checkErrors();
-
-    if (this.completed && this.responseQueue.length == 0) {
-      return;
-    }
-
-    let remaining = count ?? Number.POSITIVE_INFINITY;
-    while (remaining > 0) {
-      const response =
-        timeoutMs === undefined
-          ? await this.responseQueue.shift()
-          : await this.queuePopWithTimeout(timeoutMs!);
-      this.checkErrors();
-      if (response === undefined) {
-        return;
-      }
-      yield response!;
-      remaining -= 1;
-    }
-  }
-
-  cancel(): boolean {
-    if (this.completed) {
-      return false;
-    }
-
-    this.error = Status.CANCELLED;
-    return this.rpcs.sendCancel(this.rpc);
-  }
-
-  private checkErrors(): void {
-    if (this.callbackException !== undefined) {
-      throw this.callbackException;
-    }
-    if (this.error !== undefined) {
-      throw new RpcError(this.rpc, this.error);
-    }
-  }
-
-  protected async unaryWait(timeoutMs?: number): Promise<[Status, Message]> {
-    for await (const response of this.getResponses(1, timeoutMs)) {
-    }
-    if (this.status === undefined) {
-      throw Error('Unexpected undefined status at end of stream');
-    }
-    if (this.responses.length !== 1) {
-      throw Error(`Unexpected number of responses: ${this.responses.length}`);
-    }
-    return [this.status!, this.responses[0]];
-  }
-
-  protected async streamWait(timeoutMs?: number): Promise<[Status, Message[]]> {
-    for await (const response of this.getResponses(undefined, timeoutMs)) {
-    }
-    if (this.status === undefined) {
-      throw Error('Unexpected undefined status at end of stream');
-    }
-    return [this.status!, this.responses];
-  }
-
-  protected sendClientStream(request: Message) {
-    this.checkErrors();
-    if (this.status !== undefined) {
-      throw new RpcError(this.rpc, Status.FAILED_PRECONDITION);
-    }
-    this.rpcs.sendClientStream(this.rpc, request);
-  }
-
-  protected finishClientStream(requests: Message[]) {
-    for (const request of requests) {
-      this.sendClientStream(request);
-    }
-
-    if (!this.completed) {
-      this.rpcs.sendClientStreamEnd(this.rpc);
-    }
-  }
-}
-
-/** Tracks the state of a unary RPC call. */
-export class UnaryCall extends Call {
-  /** Awaits the server response */
-  async complete(timeoutMs?: number): Promise<[Status, Message]> {
-    return await this.unaryWait(timeoutMs);
-  }
-}
-
-/** Tracks the state of a client streaming RPC call. */
-export class ClientStreamingCall extends Call {
-  /** Gets the last server message, if it exists */
-  get response(): Message | undefined {
-    return this.responses.length > 0
-      ? this.responses[this.responses.length - 1]
-      : undefined;
-  }
-
-  /** Sends a message from the client. */
-  send(request: Message) {
-    this.sendClientStream(request);
-  }
-
-  /** Ends the client stream and waits for the RPC to complete. */
-  async finishAndWait(
-    requests: Message[] = [],
-    timeoutMs?: number
-  ): Promise<[Status, Message[]]> {
-    this.finishClientStream(requests);
-    return await this.streamWait(timeoutMs);
-  }
-}
-
-/** Tracks the state of a server streaming RPC call. */
-export class ServerStreamingCall extends Call {
-  complete(timeoutMs?: number): Promise<[Status, Message[]]> {
-    return this.streamWait(timeoutMs);
-  }
-}
-
-/** Tracks the state of a bidirectional streaming RPC call. */
-export class BidirectionalStreamingCall extends Call {
-  /** Sends a message from the client. */
-  send(request: Message) {
-    this.sendClientStream(request);
-  }
-
-  /** Ends the client stream and waits for the RPC to complete. */
-  async finishAndWait(
-    requests: Array<Message> = [],
-    timeoutMs?: number
-  ): Promise<[Status, Array<Message>]> {
-    this.finishClientStream(requests);
-    return await this.streamWait(timeoutMs);
-  }
-}
diff --git a/pw_rpc/ts/call_test.ts b/pw_rpc/ts/call_test.ts
deleted file mode 100644
index 36d72b1..0000000
--- a/pw_rpc/ts/call_test.ts
+++ /dev/null
@@ -1,134 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-/* eslint-env browser, jasmine */
-import 'jasmine';
-
-import {SomeMessage} from 'test_protos_tspb/test_protos_tspb_pb/pw_rpc/ts/test2_pb';
-
-import {Call} from './call';
-import {Channel, Method, Service} from './descriptors';
-import {PendingCalls, Rpc} from './rpc_classes';
-
-class FakeRpc {
-  readonly channel: any = undefined;
-  readonly service: any = undefined;
-  readonly method: any = undefined;
-
-  idSet: [number, number, number] = [1, 2, 3];
-  idString = '1.2.3';
-}
-
-describe('Call', () => {
-  let call: Call;
-
-  beforeEach(() => {
-    const noop = () => {};
-    const pendingCalls = new PendingCalls();
-    const channel = jasmine.createSpy();
-    const rpc = new FakeRpc();
-    call = new Call(pendingCalls, rpc, noop, noop, noop);
-  });
-
-  function newMessage(magicNumber = 1): SomeMessage {
-    const message = new SomeMessage();
-    message.setMagicNumber(magicNumber);
-    return message;
-  }
-
-  it('getResponse returns all responses.', async () => {
-    const message1 = newMessage(1);
-    const message2 = newMessage(2);
-    const message3 = newMessage(3);
-
-    // Queue three responses
-    call.handleResponse(message1);
-    call.handleResponse(message2);
-    call.handleResponse(message3);
-
-    let responses = call.getResponses(2);
-    expect((await responses.next()).value).toEqual(message1);
-    expect((await responses.next()).value).toEqual(message2);
-    expect((await responses.next()).done).toBeTrue();
-
-    responses = call.getResponses(1);
-    expect((await responses.next()).value).toEqual(message3);
-    expect((await responses.next()).done).toBeTrue();
-  });
-
-  it('getResponse early returns on stream end.', async () => {
-    const message = newMessage();
-    const responses = call.getResponses(2);
-
-    // Queue one response and an early completion.
-    call.handleResponse(message);
-    call.handleCompletion(0);
-
-    expect((await responses.next()).value).toEqual(message);
-    expect((await responses.next()).done).toBeTrue();
-  });
-
-  it('getResponse promise is rejected on stream error.', async () => {
-    const message = newMessage();
-    const responses = call.getResponses(3);
-
-    call.handleResponse(message);
-    expect((await responses.next()).value).toEqual(message);
-
-    call.handleResponse(message);
-    call.handleError(1);
-
-    // Promise is rejected as soon as an error is received, even if there is a
-    // response in the queue.
-    await expectAsync(responses.next()).toBeRejected();
-  });
-
-  it('getResponse waits if queue is empty', async () => {
-    const message1 = newMessage(1);
-    const message2 = newMessage(2);
-    const responses = call.getResponses(2);
-
-    // Queue two responses after a small delay
-    setTimeout(() => {
-      call.handleResponse(message1);
-      call.handleResponse(message2);
-      call.handleCompletion(0);
-      expect(call.completed).toBeTrue();
-    }, 200);
-
-    expect(call.completed).toBeFalse();
-    expect((await responses.next()).value).toEqual(message1);
-    expect((await responses.next()).value).toEqual(message2);
-    expect((await responses.next()).done).toBeTrue();
-  });
-
-  it('getResponse without count fetches all results', async () => {
-    const message1 = newMessage(1);
-    const message2 = newMessage(2);
-    const responses = call.getResponses();
-
-    call.handleResponse(message1);
-    expect((await responses.next()).value).toEqual(message1);
-
-    setTimeout(() => {
-      call.handleResponse(message2);
-      call.handleCompletion(0);
-      expect(call.completed).toBeTrue();
-    }, 200);
-
-    expect(call.completed).toBeFalse();
-    expect((await responses.next()).value).toEqual(message2);
-    expect((await responses.next()).done).toBeTrue();
-  });
-});
diff --git a/pw_rpc/ts/client.ts b/pw_rpc/ts/client.ts
deleted file mode 100644
index 0e44b4d..0000000
--- a/pw_rpc/ts/client.ts
+++ /dev/null
@@ -1,307 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-/** Provides a pw_rpc client for TypeScript. */
-
-import {ProtoCollection} from '@pigweed/pw_protobuf_compiler';
-import {Status} from '@pigweed/pw_status';
-import {Message} from 'google-protobuf';
-import {
-  PacketType,
-  RpcPacket,
-} from 'packet_proto_tspb/packet_proto_tspb_pb/pw_rpc/internal/packet_pb';
-
-import {Channel, Service} from './descriptors';
-import {MethodStub, methodStubFactory} from './method';
-import * as packets from './packets';
-import {PendingCalls, Rpc} from './rpc_classes';
-
-/**
- * Object for managing RPC service and contained methods.
- */
-export class ServiceClient {
-  private service: Service;
-  private methods: MethodStub[] = [];
-  private methodsByName = new Map<string, MethodStub>();
-
-  constructor(client: Client, channel: Channel, service: Service) {
-    this.service = service;
-    const methods = service.methods;
-    methods.forEach(method => {
-      const stub = methodStubFactory(client.rpcs, channel, method);
-      this.methods.push(stub);
-      this.methodsByName.set(method.name, stub);
-    });
-  }
-
-  method(methodName: string): MethodStub | undefined {
-    return this.methodsByName.get(methodName);
-  }
-
-  get id(): number {
-    return this.service.id;
-  }
-}
-
-/**
- * Object for managing RPC channel and contained services.
- */
-export class ChannelClient {
-  readonly channel: Channel;
-  private services = new Map<string, ServiceClient>();
-
-  constructor(client: Client, channel: Channel, services: Service[]) {
-    this.channel = channel;
-    services.forEach(service => {
-      const serviceClient = new ServiceClient(client, this.channel, service);
-      this.services.set(service.name, serviceClient);
-    });
-  }
-
-  /**
-   * Find a service client via its full name.
-   *
-   * For example:
-   * `service = client.channel().service('the.package.FooService');`
-   */
-  service(serviceName: string): ServiceClient | undefined {
-    return this.services.get(serviceName);
-  }
-
-  /**
-   * Find a method stub via its full name.
-   *
-   * For example:
-   * `method = client.channel().methodStub('the.package.AService.AMethod');`
-   */
-  methodStub(name: string): MethodStub | undefined {
-    const index = name.lastIndexOf('.');
-    if (index <= 0) {
-      console.error(`Malformed method name: ${name}`);
-      return undefined;
-    }
-    const serviceName = name.slice(0, index);
-    const methodName = name.slice(index + 1);
-    const method = this.service(serviceName)?.method(methodName);
-    if (method === undefined) {
-      console.error(`Method not found: ${name}`);
-      return undefined;
-    }
-    return method;
-  }
-}
-
-/**
- * RPCs are invoked through a MethodStub. These can be found by name via
- * methodStub(string name).
- *
- * ```
- * method = client.channel(1).methodStub('the.package.FooService.SomeMethod')
- * call = method.invoke(request);
- * ```
- */
-export class Client {
-  private channelsById = new Map<number, ChannelClient>();
-  readonly rpcs: PendingCalls;
-  readonly services = new Map<number, Service>();
-
-  constructor(channels: Channel[], services: Service[]) {
-    this.rpcs = new PendingCalls();
-    services.forEach(service => {
-      this.services.set(service.id, service);
-    });
-
-    channels.forEach(channel => {
-      this.channelsById.set(
-        channel.id,
-        new ChannelClient(this, channel, services)
-      );
-    });
-  }
-
-  /**
-   * Creates a client from a set of Channels and a library of Protos.
-   *
-   * @param {Channel[]} channels List of possible channels to use.
-   * @param {ProtoCollection} protoSet ProtoCollection containing protos
-   *     defining RPC services
-   * and methods.
-   */
-  static fromProtoSet(channels: Channel[], protoSet: ProtoCollection): Client {
-    let services: Service[] = [];
-    const descriptors = protoSet.fileDescriptorSet.getFileList();
-    descriptors.forEach(fileDescriptor => {
-      const packageName = fileDescriptor.getPackage()!;
-      fileDescriptor.getServiceList().forEach(serviceDescriptor => {
-        services = services.concat(
-          new Service(serviceDescriptor, protoSet, packageName)
-        );
-      });
-    });
-
-    return new Client(channels, services);
-  }
-
-  /**
-   * Finds the channel with the provided id. Returns undefined if there are no
-   * channels or no channel with a matching id.
-   *
-   * @param {number?} id If no id is specified, returns the first channel.
-   */
-  channel(id?: number): ChannelClient | undefined {
-    if (id === undefined) {
-      return this.channelsById.values().next().value;
-    }
-    return this.channelsById.get(id);
-  }
-
-  /**
-   * Creates a new RPC object holding channel, method, and service info.
-   * Returns undefined if the service or method does not exist.
-   */
-  private rpc(
-    packet: RpcPacket,
-    channelClient: ChannelClient
-  ): Rpc | undefined {
-    const service = this.services.get(packet.getServiceId());
-    if (service == undefined) {
-      return undefined;
-    }
-    const method = service.methods.get(packet.getMethodId());
-    if (method == undefined) {
-      return undefined;
-    }
-    return new Rpc(channelClient.channel, service, method);
-  }
-
-  private decodeStatus(rpc: Rpc, packet: RpcPacket): Status | undefined {
-    if (packet.getType() === PacketType.SERVER_STREAM) {
-      return;
-    }
-    return packet.getStatus();
-  }
-
-  private decodePayload(rpc: Rpc, packet: RpcPacket): Message | undefined {
-    if (packet.getType() === PacketType.SERVER_ERROR) {
-      return undefined;
-    }
-
-    if (
-      packet.getType() === PacketType.RESPONSE &&
-      rpc.method.serverStreaming
-    ) {
-      return undefined;
-    }
-
-    const payload = packet.getPayload_asU8();
-    return packets.decodePayload(payload, rpc.method.responseType);
-  }
-
-  private sendClientError(
-    client: ChannelClient,
-    packet: RpcPacket,
-    error: Status
-  ) {
-    client.channel.send(packets.encodeClientError(packet, error));
-  }
-
-  /**
-   * Processes an incoming packet.
-   *
-   * @param {Uint8Array} rawPacketData binary data for a pw_rpc packet.
-   * @return {Status} The status of processing the packet.
-   *    - OK: the packet was processed by the client
-   *    - DATA_LOSS: the packet could not be decoded
-   *    - INVALID_ARGUMENT: the packet is for a server, not a client
-   *    - NOT_FOUND: the packet's channel ID is not known to this client
-   */
-  processPacket(rawPacketData: Uint8Array): Status {
-    let packet;
-    try {
-      packet = packets.decode(rawPacketData);
-    } catch (err) {
-      console.warn(`Failed to decode packet: ${err}`);
-      console.debug(`Raw packet: ${rawPacketData}`);
-      return Status.DATA_LOSS;
-    }
-
-    if (packets.forServer(packet)) {
-      return Status.INVALID_ARGUMENT;
-    }
-
-    const channelClient = this.channelsById.get(packet.getChannelId());
-    if (channelClient == undefined) {
-      console.warn(`Unrecognized channel ID: ${packet.getChannelId()}`);
-      return Status.NOT_FOUND;
-    }
-
-    const rpc = this.rpc(packet, channelClient);
-    if (rpc == undefined) {
-      this.sendClientError(channelClient, packet, Status.NOT_FOUND);
-      console.warn('rpc service/method not found');
-      return Status.OK;
-    }
-
-    if (
-      packet.getType() !== PacketType.RESPONSE &&
-      packet.getType() !== PacketType.SERVER_STREAM &&
-      packet.getType() !== PacketType.SERVER_ERROR
-    ) {
-      console.error(`${rpc}: Unexpected packet type ${packet.getType()}`);
-      console.debug(`Packet: ${packet}`);
-      return Status.OK;
-    }
-
-    let status = this.decodeStatus(rpc, packet);
-    let payload;
-    try {
-      payload = this.decodePayload(rpc, packet);
-    } catch (error) {
-      this.sendClientError(channelClient, packet, Status.DATA_LOSS);
-      console.warn(`Failed to decode response: ${error}`);
-      console.debug(`Raw payload: ${packet.getPayload()}`);
-
-      // Make this an error packet so the error handler is called.
-      packet.setType(PacketType.SERVER_ERROR);
-      status = Status.DATA_LOSS;
-    }
-
-    const call = this.rpcs.getPending(rpc, status);
-    if (call === undefined) {
-      this.sendClientError(channelClient, packet, Status.FAILED_PRECONDITION);
-      console.debug(`Discarding response for ${rpc}, which is not pending`);
-      return Status.OK;
-    }
-
-    if (packet.getType() === PacketType.SERVER_ERROR) {
-      if (status === Status.OK) {
-        throw 'Unexpected OK status on SERVER_ERROR';
-      }
-      if (status === undefined) {
-        throw 'Missing status on SERVER_ERROR';
-      }
-      console.warn(`${rpc}: invocation failed with status: ${Status[status]}`);
-      call.handleError(status);
-      return Status.OK;
-    }
-
-    if (payload !== undefined) {
-      call.handleResponse(payload);
-    }
-    if (status !== undefined) {
-      call.handleCompletion(status);
-    }
-    return Status.OK;
-  }
-}
diff --git a/pw_rpc/ts/client_test.ts b/pw_rpc/ts/client_test.ts
deleted file mode 100644
index e040e86..0000000
--- a/pw_rpc/ts/client_test.ts
+++ /dev/null
@@ -1,922 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-/* eslint-env browser, jasmine */
-import 'jasmine';
-
-import {Status} from '@pigweed/pw_status';
-import {MessageCreator} from '@pigweed/pw_protobuf_compiler';
-import {Message} from 'google-protobuf';
-import {
-  PacketType,
-  RpcPacket,
-} from 'packet_proto_tspb/packet_proto_tspb_pb/pw_rpc/internal/packet_pb';
-import {ProtoCollection} from 'rpc_proto_collection/generated/ts_proto_collection';
-import {
-  Request,
-  Response,
-} from 'test_protos_tspb/test_protos_tspb_pb/pw_rpc/ts/test_pb';
-
-import {Client} from './client';
-import {Channel, Method} from './descriptors';
-import {
-  BidirectionalStreamingMethodStub,
-  ClientStreamingMethodStub,
-  ServerStreamingMethodStub,
-  UnaryMethodStub,
-} from './method';
-import * as packets from './packets';
-
-const TEST_PROTO_PATH = 'pw_rpc/ts/test_protos-descriptor-set.proto.bin';
-
-describe('Client', () => {
-  let protoCollection: ProtoCollection;
-  let client: Client;
-  let lastPacketSent: RpcPacket;
-
-  beforeEach(() => {
-    protoCollection = new ProtoCollection();
-    const channels = [new Channel(1, savePacket), new Channel(5)];
-    client = Client.fromProtoSet(channels, protoCollection);
-  });
-
-  function savePacket(packetBytes: Uint8Array): void {
-    lastPacketSent = RpcPacket.deserializeBinary(packetBytes);
-  }
-
-  it('channel returns undefined for empty list', () => {
-    const channels = Array<Channel>();
-    const emptyChannelClient = Client.fromProtoSet(channels, protoCollection);
-    expect(emptyChannelClient.channel()).toBeUndefined();
-  });
-
-  it('fetches channel or returns undefined', () => {
-    expect(client.channel(1)!.channel.id).toEqual(1);
-    expect(client.channel(5)!.channel.id).toEqual(5);
-    expect(client.channel()!.channel.id).toEqual(1);
-    expect(client.channel(2)).toBeUndefined();
-  });
-
-  it('ChannelClient fetches method by name', () => {
-    const channel = client.channel()!;
-    const stub = channel.methodStub('pw.rpc.test1.TheTestService.SomeUnary')!;
-    expect(stub.method.name).toEqual('SomeUnary');
-  });
-
-  it('ChannelClient for unknown name returns undefined', () => {
-    const channel = client.channel()!;
-    expect(channel.methodStub('')).toBeUndefined();
-    expect(
-      channel.methodStub('pw.rpc.test1.Garbage.SomeUnary')
-    ).toBeUndefined();
-    expect(
-      channel.methodStub('pw.rpc.test1.TheTestService.Garbage')
-    ).toBeUndefined();
-  });
-
-  it('processPacket with invalid proto data', () => {
-    const textEncoder = new TextEncoder();
-    const data = textEncoder.encode('NOT a packet!');
-    expect(client.processPacket(data)).toEqual(Status.DATA_LOSS);
-  });
-
-  it('processPacket not for client', () => {
-    const packet = new RpcPacket();
-    packet.setType(PacketType.REQUEST);
-    const processStatus = client.processPacket(packet.serializeBinary());
-    expect(processStatus).toEqual(Status.INVALID_ARGUMENT);
-  });
-
-  it('processPacket for unrecognized channel', () => {
-    const packet = packets.encodeResponse([123, 456, 789], new Request());
-    expect(client.processPacket(packet)).toEqual(Status.NOT_FOUND);
-  });
-
-  it('processPacket for unrecognized service', () => {
-    const packet = packets.encodeResponse([1, 456, 789], new Request());
-    const status = client.processPacket(packet);
-    expect(client.processPacket(packet)).toEqual(Status.OK);
-
-    expect(lastPacketSent.getChannelId()).toEqual(1);
-    expect(lastPacketSent.getServiceId()).toEqual(456);
-    expect(lastPacketSent.getMethodId()).toEqual(789);
-    expect(lastPacketSent.getType()).toEqual(PacketType.CLIENT_ERROR);
-    expect(lastPacketSent.getStatus()).toEqual(Status.NOT_FOUND);
-  });
-
-  it('processPacket for unrecognized method', () => {
-    const service = client.services.values().next().value;
-
-    const packet = packets.encodeResponse([1, service.id, 789], new Request());
-    const status = client.processPacket(packet);
-    expect(client.processPacket(packet)).toEqual(Status.OK);
-
-    expect(lastPacketSent.getChannelId()).toEqual(1);
-    expect(lastPacketSent.getServiceId()).toEqual(service.id);
-    expect(lastPacketSent.getMethodId()).toEqual(789);
-    expect(lastPacketSent.getType()).toEqual(PacketType.CLIENT_ERROR);
-    expect(lastPacketSent.getStatus()).toEqual(Status.NOT_FOUND);
-  });
-
-  it('processPacket for non-pending method', () => {
-    const service = client.services.values().next().value;
-    const method = service.methods.values().next().value;
-
-    const packet = packets.encodeResponse(
-      [1, service.id, method.id],
-      new Request()
-    );
-    const status = client.processPacket(packet);
-    expect(client.processPacket(packet)).toEqual(Status.OK);
-
-    expect(lastPacketSent.getChannelId()).toEqual(1);
-    expect(lastPacketSent.getServiceId()).toEqual(service.id);
-    expect(lastPacketSent.getMethodId()).toEqual(method.id);
-    expect(lastPacketSent.getType()).toEqual(PacketType.CLIENT_ERROR);
-    expect(lastPacketSent.getStatus()).toEqual(Status.FAILED_PRECONDITION);
-  });
-});
-
-describe('RPC', () => {
-  let protoCollection: ProtoCollection;
-  let client: Client;
-  let lastPacketSent: RpcPacket | undefined;
-  let requests: RpcPacket[] = [];
-  let nextPackets: [Uint8Array, Status][] = [];
-  let responseLock = false;
-  let sendResponsesAfterPackets = 0;
-  let outputException: Error | undefined;
-
-  beforeEach(async () => {
-    protoCollection = new ProtoCollection();
-    const channels = [new Channel(1, handlePacket), new Channel(2, () => {})];
-    client = Client.fromProtoSet(channels, protoCollection);
-    lastPacketSent = undefined;
-    requests = [];
-    nextPackets = [];
-    responseLock = false;
-    sendResponsesAfterPackets = 0;
-    outputException = undefined;
-  });
-
-  function newRequest(magicNumber = 123): Message {
-    const request = new Request();
-    request.setMagicNumber(magicNumber);
-    return request;
-  }
-
-  function newResponse(payload = '._.'): Message {
-    const response = new Response();
-    response.setPayload(payload);
-    return response;
-  }
-
-  function enqueueResponse(
-    channelId: number,
-    method: Method,
-    status: Status,
-    response?: Message
-  ) {
-    const packet = new RpcPacket();
-    packet.setType(PacketType.RESPONSE);
-    packet.setChannelId(channelId);
-    packet.setServiceId(method.service.id);
-    packet.setMethodId(method.id);
-    packet.setStatus(status);
-    if (response === undefined) {
-      packet.setPayload(new Uint8Array());
-    } else {
-      packet.setPayload(response.serializeBinary());
-    }
-    nextPackets.push([packet.serializeBinary(), Status.OK]);
-  }
-
-  function enqueueServerStream(
-    channelId: number,
-    method: Method,
-    response: Message,
-    status: Status = Status.OK
-  ) {
-    const packet = new RpcPacket();
-    packet.setType(PacketType.SERVER_STREAM);
-    packet.setChannelId(channelId);
-    packet.setServiceId(method.service.id);
-    packet.setMethodId(method.id);
-    packet.setPayload(response.serializeBinary());
-    packet.setStatus(status);
-    nextPackets.push([packet.serializeBinary(), status]);
-  }
-
-  function enqueueError(
-    channelId: number,
-    method: Method,
-    status: Status,
-    processStatus: Status
-  ) {
-    const packet = new RpcPacket();
-    packet.setType(PacketType.SERVER_ERROR);
-    packet.setChannelId(channelId);
-    packet.setServiceId(method.service.id);
-    packet.setMethodId(method.id);
-    packet.setStatus(status);
-
-    nextPackets.push([packet.serializeBinary(), processStatus]);
-  }
-
-  function lastRequest(): RpcPacket {
-    if (requests.length == 0) {
-      throw Error('Tried to fetch request from empty list');
-    }
-    return requests[requests.length - 1];
-  }
-
-  function sentPayload(messageType: typeof Message): any {
-    return messageType.deserializeBinary(lastRequest().getPayload_asU8());
-  }
-
-  function handlePacket(data: Uint8Array): void {
-    if (outputException !== undefined) {
-      throw outputException;
-    }
-    requests.push(packets.decode(data));
-
-    if (sendResponsesAfterPackets > 1) {
-      sendResponsesAfterPackets -= 1;
-      return;
-    }
-
-    processEnqueuedPackets();
-  }
-
-  function processEnqueuedPackets(): void {
-    // Avoid infinite recursion when processing a packet causes another packet
-    // to send.
-    responseLock = true;
-    for (const [packet, status] of nextPackets) {
-      expect(client.processPacket(packet)).toEqual(status);
-    }
-    nextPackets = [];
-    responseLock = false;
-  }
-
-  describe('Unary', () => {
-    let unaryStub: UnaryMethodStub;
-
-    beforeEach(async () => {
-      unaryStub = client
-        .channel()
-        ?.methodStub(
-          'pw.rpc.test1.TheTestService.SomeUnary'
-        )! as UnaryMethodStub;
-    });
-
-    it('blocking call', async () => {
-      for (let i = 0; i < 3; i++) {
-        enqueueResponse(
-          1,
-          unaryStub.method,
-          Status.ABORTED,
-          newResponse('0_o')
-        );
-        const [status, response] = await unaryStub.call(newRequest(6));
-
-        expect(sentPayload(Request).getMagicNumber()).toEqual(6);
-        expect(status).toEqual(Status.ABORTED);
-        expect(response).toEqual(newResponse('0_o'));
-      }
-    });
-
-    it('nonblocking call', () => {
-      for (let i = 0; i < 3; i++) {
-        const response = newResponse('hello world');
-        enqueueResponse(1, unaryStub.method, Status.ABORTED, response);
-
-        const onNext = jasmine.createSpy();
-        const onCompleted = jasmine.createSpy();
-        const onError = jasmine.createSpy();
-        const call = unaryStub.invoke(
-          newRequest(5),
-          onNext,
-          onCompleted,
-          onError
-        );
-
-        expect(sentPayload(Request).getMagicNumber()).toEqual(5);
-        expect(onNext).toHaveBeenCalledOnceWith(response);
-        expect(onError).not.toHaveBeenCalled();
-        expect(onCompleted).toHaveBeenCalledOnceWith(Status.ABORTED);
-      }
-    });
-
-    it('open', () => {
-      outputException = Error('Error should be ignored');
-
-      for (let i = 0; i < 3; i++) {
-        const response = newResponse('hello world');
-        enqueueResponse(1, unaryStub.method, Status.ABORTED, response);
-
-        const onNext = jasmine.createSpy();
-        const onCompleted = jasmine.createSpy();
-        const onError = jasmine.createSpy();
-        unaryStub.open(newRequest(5), onNext, onCompleted, onError);
-        expect(requests).toHaveSize(0);
-
-        processEnqueuedPackets();
-
-        expect(onNext).toHaveBeenCalledOnceWith(response);
-        expect(onError).not.toHaveBeenCalled();
-        expect(onCompleted).toHaveBeenCalledOnceWith(Status.ABORTED);
-      }
-    });
-
-    it('blocking server error', async () => {
-      for (let i = 0; i < 3; i++) {
-        enqueueError(1, unaryStub.method, Status.NOT_FOUND, Status.OK);
-
-        try {
-          await unaryStub.call(newRequest());
-          fail('call expected to fail');
-        } catch (e: any) {
-          expect(e.status).toBe(Status.NOT_FOUND);
-        }
-      }
-    });
-
-    it('nonblocking call cancel', () => {
-      for (let i = 0; i < 3; i++) {
-        const onNext = jasmine.createSpy();
-        const call = unaryStub.invoke(newRequest(), onNext);
-
-        expect(requests.length).toBeGreaterThan(0);
-        requests = [];
-
-        expect(call.cancel()).toBeTrue();
-        expect(call.cancel()).toBeFalse();
-        expect(onNext).not.toHaveBeenCalled();
-      }
-    });
-
-    it('blocking call with timeout', async () => {
-      try {
-        await unaryStub.call(newRequest(), 10);
-        fail('Promise should not be resolve');
-      } catch (err: any) {
-        expect(err.timeoutMs).toEqual(10);
-      }
-    });
-
-    it('nonblocking duplicate calls first is cancelled', () => {
-      const firstCall = unaryStub.invoke(newRequest());
-      expect(firstCall.completed).toBeFalse();
-
-      const secondCall = unaryStub.invoke(newRequest());
-      expect(firstCall.error).toEqual(Status.CANCELLED);
-      expect(secondCall.completed).toBeFalse();
-    });
-
-    it('nonblocking exception in callback', () => {
-      const errorCallback = () => {
-        throw Error('Something went wrong!');
-      };
-
-      enqueueResponse(1, unaryStub.method, Status.OK);
-      const call = unaryStub.invoke(newRequest(), errorCallback);
-      expect(call.callbackException!.name).toEqual('Error');
-      expect(call.callbackException!.message).toEqual('Something went wrong!');
-    });
-  });
-
-  describe('ServerStreaming', () => {
-    let serverStreaming: ServerStreamingMethodStub;
-
-    beforeEach(async () => {
-      serverStreaming = client
-        .channel()
-        ?.methodStub(
-          'pw.rpc.test1.TheTestService.SomeServerStreaming'
-        )! as ServerStreamingMethodStub;
-    });
-
-    it('non-blocking call', () => {
-      const response1 = newResponse('!!!');
-      const response2 = newResponse('?');
-
-      for (let i = 0; i < 3; i++) {
-        enqueueServerStream(1, serverStreaming.method, response1);
-        enqueueServerStream(1, serverStreaming.method, response2);
-        enqueueResponse(1, serverStreaming.method, Status.ABORTED);
-
-        const onNext = jasmine.createSpy();
-        const onCompleted = jasmine.createSpy();
-        const onError = jasmine.createSpy();
-        serverStreaming.invoke(newRequest(4), onNext, onCompleted, onError);
-
-        expect(onNext).toHaveBeenCalledWith(response1);
-        expect(onNext).toHaveBeenCalledWith(response2);
-        expect(onError).not.toHaveBeenCalled();
-        expect(onCompleted).toHaveBeenCalledOnceWith(Status.ABORTED);
-
-        expect(
-          sentPayload(serverStreaming.method.requestType).getMagicNumber()
-        ).toEqual(4);
-      }
-    });
-
-    it('open', () => {
-      outputException = Error('Error should be ignored');
-      const response1 = newResponse('!!!');
-      const response2 = newResponse('?');
-
-      for (let i = 0; i < 3; i++) {
-        enqueueServerStream(1, serverStreaming.method, response1);
-        enqueueServerStream(1, serverStreaming.method, response2);
-        enqueueResponse(1, serverStreaming.method, Status.ABORTED);
-
-        const onNext = jasmine.createSpy();
-        const onCompleted = jasmine.createSpy();
-        const onError = jasmine.createSpy();
-        const call = serverStreaming.open(
-          newRequest(3),
-          onNext,
-          onCompleted,
-          onError
-        );
-
-        expect(requests).toHaveSize(0);
-        processEnqueuedPackets();
-
-        expect(onNext).toHaveBeenCalledWith(response1);
-        expect(onNext).toHaveBeenCalledWith(response2);
-        expect(onError).not.toHaveBeenCalled();
-        expect(onCompleted).toHaveBeenCalledOnceWith(Status.ABORTED);
-      }
-    });
-
-    it('blocking timeout', async () => {
-      try {
-        await serverStreaming.call(newRequest(), 10);
-        fail('Promise should not be resolve');
-      } catch (err: any) {
-        expect(err.timeoutMs).toEqual(10);
-      }
-    });
-
-    it('non-blocking cancel', () => {
-      const testResponse = newResponse('!!!');
-      enqueueServerStream(1, serverStreaming.method, testResponse);
-
-      const onNext = jasmine.createSpy();
-      const onCompleted = jasmine.createSpy();
-      const onError = jasmine.createSpy();
-      let call = serverStreaming.invoke(newRequest(3), onNext);
-      expect(onNext).toHaveBeenCalledOnceWith(testResponse);
-
-      onNext.calls.reset();
-
-      call.cancel();
-      expect(lastRequest().getType()).toEqual(PacketType.CLIENT_ERROR);
-      expect(lastRequest().getStatus()).toEqual(Status.CANCELLED);
-
-      // Ensure the RPC can be called after being cancelled.
-      enqueueServerStream(1, serverStreaming.method, testResponse);
-      enqueueResponse(1, serverStreaming.method, Status.OK);
-      call = serverStreaming.invoke(newRequest(), onNext, onCompleted, onError);
-      expect(onNext).toHaveBeenCalledWith(testResponse);
-      expect(onError).not.toHaveBeenCalled();
-      expect(onCompleted).toHaveBeenCalledOnceWith(Status.OK);
-    });
-  });
-
-  describe('ClientStreaming', () => {
-    let clientStreaming: ClientStreamingMethodStub;
-
-    beforeEach(async () => {
-      clientStreaming = client
-        .channel()
-        ?.methodStub(
-          'pw.rpc.test1.TheTestService.SomeClientStreaming'
-        )! as ClientStreamingMethodStub;
-    });
-
-    it('non-blocking call', () => {
-      const testResponse = newResponse('-.-');
-
-      for (let i = 0; i < 3; i++) {
-        const onNext = jasmine.createSpy();
-        const stream = clientStreaming.invoke(onNext);
-        expect(stream.completed).toBeFalse();
-
-        stream.send(newRequest(31));
-        expect(lastRequest().getType()).toEqual(PacketType.CLIENT_STREAM);
-        expect(sentPayload(Request).getMagicNumber()).toEqual(31);
-        expect(stream.completed).toBeFalse();
-
-        // Enqueue the server response to be sent after the next message.
-        enqueueResponse(1, clientStreaming.method, Status.OK, testResponse);
-
-        stream.send(newRequest(32));
-        expect(lastRequest().getType()).toEqual(PacketType.CLIENT_STREAM);
-        expect(sentPayload(Request).getMagicNumber()).toEqual(32);
-
-        expect(onNext).toHaveBeenCalledOnceWith(testResponse);
-        expect(stream.completed).toBeTrue();
-        expect(stream.status).toEqual(Status.OK);
-        expect(stream.error).toBeUndefined();
-      }
-    });
-
-    it('open', () => {
-      outputException = Error('Error should be ignored');
-      const response = newResponse('!!!');
-
-      for (let i = 0; i < 3; i++) {
-        enqueueResponse(1, clientStreaming.method, Status.OK, response);
-
-        const onNext = jasmine.createSpy();
-        const onCompleted = jasmine.createSpy();
-        const onError = jasmine.createSpy();
-        const call = clientStreaming.open(onNext, onCompleted, onError);
-        expect(requests).toHaveSize(0);
-
-        processEnqueuedPackets();
-
-        expect(onNext).toHaveBeenCalledWith(response);
-        expect(onError).not.toHaveBeenCalled();
-        expect(onCompleted).toHaveBeenCalledOnceWith(Status.OK);
-      }
-    });
-
-    it('blocking timeout', async () => {
-      try {
-        await clientStreaming.call([newRequest()], 10);
-        fail('Promise should not be resolve');
-      } catch (err: any) {
-        expect(err.timeoutMs).toEqual(10);
-      }
-    });
-
-    it('non-blocking call ended by client', () => {
-      const testResponse = newResponse('0.o');
-      for (let i = 0; i < 3; i++) {
-        const onNext = jasmine.createSpy();
-        const stream = clientStreaming.invoke(onNext);
-        expect(stream.completed).toBeFalse();
-
-        stream.send(newRequest(31));
-        expect(lastRequest().getType()).toEqual(PacketType.CLIENT_STREAM);
-        expect(sentPayload(Request).getMagicNumber()).toEqual(31);
-        expect(stream.completed).toBeFalse();
-
-        // Enqueue the server response to be sent after the next message.
-        enqueueResponse(1, clientStreaming.method, Status.OK, testResponse);
-
-        stream.finishAndWait();
-        expect(lastRequest().getType()).toEqual(PacketType.CLIENT_STREAM_END);
-
-        expect(onNext).toHaveBeenCalledOnceWith(testResponse);
-        expect(stream.completed).toBeTrue();
-        expect(stream.status).toEqual(Status.OK);
-        expect(stream.error).toBeUndefined();
-      }
-    });
-
-    it('non-blocking call cancelled', () => {
-      for (let i = 0; i < 3; i++) {
-        const stream = clientStreaming.invoke();
-        stream.send(newRequest());
-
-        expect(stream.cancel()).toBeTrue();
-        expect(lastRequest().getType()).toEqual(PacketType.CLIENT_ERROR);
-        expect(lastRequest().getStatus()).toEqual(Status.CANCELLED);
-        expect(stream.cancel()).toBeFalse();
-        expect(stream.completed).toBeTrue();
-        expect(stream.error).toEqual(Status.CANCELLED);
-      }
-    });
-
-    it('non-blocking call server error', async () => {
-      for (let i = 0; i < 3; i++) {
-        const stream = clientStreaming.invoke();
-        enqueueError(
-          1,
-          clientStreaming.method,
-          Status.INVALID_ARGUMENT,
-          Status.OK
-        );
-
-        stream.send(newRequest());
-
-        await stream
-          .finishAndWait()
-          .then(() => {
-            fail('Promise should not be resolved');
-          })
-          .catch(reason => {
-            expect(reason.status).toEqual(Status.INVALID_ARGUMENT);
-          });
-      }
-    });
-
-    it('non-blocking call server error after stream end', async () => {
-      for (let i = 0; i < 3; i++) {
-        const stream = clientStreaming.invoke();
-        // Error will be sent in response to the CLIENT_STREAM_END packet.
-        enqueueError(
-          1,
-          clientStreaming.method,
-          Status.INVALID_ARGUMENT,
-          Status.OK
-        );
-
-        await stream
-          .finishAndWait()
-          .then(() => {
-            fail('Promise should not be resolved');
-          })
-          .catch(reason => {
-            expect(reason.status).toEqual(Status.INVALID_ARGUMENT);
-          });
-      }
-    });
-
-    it('non-blocking call send after cancelled', () => {
-      const stream = clientStreaming.invoke();
-      expect(stream.cancel()).toBeTrue();
-
-      expect(() => stream.send(newRequest())).toThrowMatching(
-        error => error.status === Status.CANCELLED
-      );
-    });
-
-    it('non-blocking finish after completed', async () => {
-      const enqueuedResponse = newResponse('?!');
-      enqueueResponse(
-        1,
-        clientStreaming.method,
-        Status.UNAVAILABLE,
-        enqueuedResponse
-      );
-
-      const stream = clientStreaming.invoke();
-      const result = await stream.finishAndWait();
-      expect(result[1]).toEqual([enqueuedResponse]);
-
-      expect(await stream.finishAndWait()).toEqual(result);
-      expect(await stream.finishAndWait()).toEqual(result);
-    });
-
-    it('non-blocking finish after error', async () => {
-      enqueueError(1, clientStreaming.method, Status.UNAVAILABLE, Status.OK);
-      const stream = clientStreaming.invoke();
-
-      for (let i = 0; i < 3; i++) {
-        await stream
-          .finishAndWait()
-          .then(() => {
-            fail('Promise should not be resolved');
-          })
-          .catch(reason => {
-            expect(reason.status).toEqual(Status.UNAVAILABLE);
-            expect(stream.error).toEqual(Status.UNAVAILABLE);
-            expect(stream.response).toBeUndefined();
-          });
-      }
-    });
-
-    it('non-blocking duplicate calls first is cancelled', () => {
-      const firstCall = clientStreaming.invoke();
-      expect(firstCall.completed).toBeFalse();
-
-      const secondCall = clientStreaming.invoke();
-      expect(firstCall.error).toEqual(Status.CANCELLED);
-      expect(secondCall.completed).toBeFalse();
-    });
-  });
-
-  describe('BidirectionalStreaming', () => {
-    let bidiStreaming: BidirectionalStreamingMethodStub;
-
-    beforeEach(async () => {
-      bidiStreaming = client
-        .channel()
-        ?.methodStub(
-          'pw.rpc.test1.TheTestService.SomeBidiStreaming'
-        )! as BidirectionalStreamingMethodStub;
-    });
-
-    it('blocking call', async () => {
-      const testRequests = [newRequest(123), newRequest(456)];
-
-      sendResponsesAfterPackets = 3;
-      enqueueResponse(1, bidiStreaming.method, Status.NOT_FOUND);
-
-      const results = await bidiStreaming.call(testRequests);
-      expect(results[0]).toEqual(Status.NOT_FOUND);
-      expect(results[1]).toEqual([]);
-    });
-
-    it('blocking server error', async () => {
-      const testRequests = [newRequest(123)];
-      enqueueError(1, bidiStreaming.method, Status.NOT_FOUND, Status.OK);
-
-      await bidiStreaming
-        .call(testRequests)
-        .then(() => {
-          fail('Promise should not be resolved');
-        })
-        .catch(reason => {
-          expect(reason.status).toEqual(Status.NOT_FOUND);
-        });
-    });
-
-    it('non-blocking call', () => {
-      const rep1 = newResponse('!!!');
-      const rep2 = newResponse('?');
-
-      for (let i = 0; i < 3; i++) {
-        const testResponses: Array<Message> = [];
-        const stream = bidiStreaming.invoke(response => {
-          testResponses.push(response);
-        });
-        expect(stream.completed).toBeFalse();
-
-        stream.send(newRequest(55));
-        expect(lastRequest().getType()).toEqual(PacketType.CLIENT_STREAM);
-        expect(sentPayload(Request).getMagicNumber()).toEqual(55);
-        expect(stream.completed).toBeFalse();
-        expect(testResponses).toEqual([]);
-
-        enqueueServerStream(1, bidiStreaming.method, rep1);
-        enqueueServerStream(1, bidiStreaming.method, rep2);
-
-        stream.send(newRequest(66));
-        expect(lastRequest().getType()).toEqual(PacketType.CLIENT_STREAM);
-        expect(sentPayload(Request).getMagicNumber()).toEqual(66);
-        expect(stream.completed).toBeFalse();
-        expect(testResponses).toEqual([rep1, rep2]);
-
-        enqueueResponse(1, bidiStreaming.method, Status.OK);
-
-        stream.send(newRequest(77));
-        expect(stream.completed).toBeTrue();
-        expect(testResponses).toEqual([rep1, rep2]);
-        expect(stream.status).toEqual(Status.OK);
-        expect(stream.error).toBeUndefined();
-      }
-    });
-
-    it('open', () => {
-      outputException = Error('Error should be ignored');
-      const response1 = newResponse('!!!');
-      const response2 = newResponse('?');
-
-      for (let i = 0; i < 3; i++) {
-        enqueueServerStream(1, bidiStreaming.method, response1);
-        enqueueServerStream(1, bidiStreaming.method, response2);
-        enqueueResponse(1, bidiStreaming.method, Status.OK);
-
-        const onNext = jasmine.createSpy();
-        const onCompleted = jasmine.createSpy();
-        const onError = jasmine.createSpy();
-        const call = bidiStreaming.open(onNext, onCompleted, onError);
-        expect(requests).toHaveSize(0);
-
-        processEnqueuedPackets();
-
-        expect(onNext).toHaveBeenCalledWith(response1);
-        expect(onNext).toHaveBeenCalledWith(response2);
-        expect(onError).not.toHaveBeenCalled();
-        expect(onCompleted).toHaveBeenCalledOnceWith(Status.OK);
-      }
-    });
-
-    it('blocking timeout', async () => {
-      try {
-        await bidiStreaming.call([newRequest()], 10);
-        fail('Promise should not be resolve');
-      } catch (err: any) {
-        expect(err.timeoutMs).toEqual(10);
-      }
-    });
-
-    it('non-blocking server error', async () => {
-      const response = newResponse('!!!');
-
-      for (let i = 0; i < 3; i++) {
-        const testResponses: Array<Message> = [];
-        const stream = bidiStreaming.invoke(response => {
-          testResponses.push(response);
-        });
-        expect(stream.completed).toBeFalse();
-
-        enqueueServerStream(1, bidiStreaming.method, response);
-
-        stream.send(newRequest(55));
-        expect(stream.completed).toBeFalse();
-        expect(testResponses).toEqual([response]);
-
-        enqueueError(1, bidiStreaming.method, Status.OUT_OF_RANGE, Status.OK);
-
-        stream.send(newRequest(999));
-        expect(stream.completed).toBeTrue();
-        expect(testResponses).toEqual([response]);
-        expect(stream.status).toBeUndefined();
-        expect(stream.error).toEqual(Status.OUT_OF_RANGE);
-
-        await stream
-          .finishAndWait()
-          .then(() => {
-            fail('Promise should not be resolved');
-          })
-          .catch(reason => {
-            expect(reason.status).toEqual(Status.OUT_OF_RANGE);
-          });
-      }
-    });
-    it('non-blocking server error after stream end', async () => {
-      for (let i = 0; i < 3; i++) {
-        const stream = bidiStreaming.invoke();
-
-        // Error is sent in response to CLIENT_STREAM_END packet.
-        enqueueError(
-          1,
-          bidiStreaming.method,
-          Status.INVALID_ARGUMENT,
-          Status.OK
-        );
-
-        await stream
-          .finishAndWait()
-          .then(() => {
-            fail('Promise should not be resolved');
-          })
-          .catch(reason => {
-            expect(reason.status).toEqual(Status.INVALID_ARGUMENT);
-          });
-      }
-    });
-
-    it('non-blocking send after cancelled', async () => {
-      const stream = bidiStreaming.invoke();
-      expect(stream.cancel()).toBeTrue();
-
-      try {
-        stream.send(newRequest());
-        fail('send should have failed');
-      } catch (e: any) {
-        expect(e.status).toBe(Status.CANCELLED);
-      }
-    });
-
-    it('non-blocking finish after completed', async () => {
-      const response = newResponse('!?');
-      enqueueServerStream(1, bidiStreaming.method, response);
-      enqueueResponse(1, bidiStreaming.method, Status.UNAVAILABLE);
-
-      const stream = bidiStreaming.invoke();
-      const result = await stream.finishAndWait();
-      expect(result[1]).toEqual([response]);
-
-      expect(await stream.finishAndWait()).toEqual(result);
-      expect(await stream.finishAndWait()).toEqual(result);
-    });
-
-    it('non-blocking finish after error', async () => {
-      const response = newResponse('!?');
-      enqueueServerStream(1, bidiStreaming.method, response);
-      enqueueError(1, bidiStreaming.method, Status.UNAVAILABLE, Status.OK);
-
-      const stream = bidiStreaming.invoke();
-
-      for (let i = 0; i < 3; i++) {
-        await stream
-          .finishAndWait()
-          .then(() => {
-            fail('Promise should not be resolved');
-          })
-          .catch(reason => {
-            expect(reason.status).toEqual(Status.UNAVAILABLE);
-            expect(stream.error).toEqual(Status.UNAVAILABLE);
-          });
-      }
-    });
-    it('non-blocking duplicate calls first is cancelled', () => {
-      const firstCall = bidiStreaming.invoke();
-      expect(firstCall.completed).toBeFalse();
-      const secondCall = bidiStreaming.invoke();
-      expect(firstCall.error).toEqual(Status.CANCELLED);
-      expect(secondCall.completed).toBeFalse();
-    });
-  });
-});
diff --git a/pw_rpc/ts/descriptors.ts b/pw_rpc/ts/descriptors.ts
deleted file mode 100644
index e36bee1..0000000
--- a/pw_rpc/ts/descriptors.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 {ProtoCollection} from '@pigweed/pw_protobuf_compiler';
-import {
-  MethodDescriptorProto,
-  ServiceDescriptorProto,
-} from 'google-protobuf/google/protobuf/descriptor_pb';
-
-import {hash} from './hash';
-
-interface ChannelOutput {
-  (data: Uint8Array): void;
-}
-
-export class Channel {
-  readonly id: number;
-  private output: ChannelOutput;
-
-  constructor(id: number, output: ChannelOutput = () => {}) {
-    this.id = id;
-    this.output = output;
-  }
-
-  send(data: Uint8Array) {
-    this.output(data);
-  }
-}
-
-/** Describes an RPC service. */
-export class Service {
-  readonly name: string;
-  readonly id: number;
-  readonly methods = new Map<number, Method>();
-  readonly methodsByName = new Map<string, Method>();
-
-  constructor(
-    descriptor: ServiceDescriptorProto,
-    protoCollection: ProtoCollection,
-    packageName: string
-  ) {
-    this.name = packageName + '.' + descriptor.getName()!;
-    this.id = hash(this.name);
-    descriptor
-      .getMethodList()
-      .forEach((methodDescriptor: MethodDescriptorProto) => {
-        const method = new Method(methodDescriptor, protoCollection, this);
-        this.methods.set(method.id, method);
-        this.methodsByName.set(method.name, method);
-      });
-  }
-}
-
-export enum MethodType {
-  UNARY,
-  SERVER_STREAMING,
-  CLIENT_STREAMING,
-  BIDIRECTIONAL_STREAMING,
-}
-
-/** Describes an RPC method. */
-export class Method {
-  readonly service: Service;
-  readonly name: string;
-  readonly id: number;
-  readonly clientStreaming: boolean;
-  readonly serverStreaming: boolean;
-  readonly requestType: any;
-  readonly responseType: any;
-
-  constructor(
-    descriptor: MethodDescriptorProto,
-    protoCollection: ProtoCollection,
-    service: Service
-  ) {
-    this.name = descriptor.getName()!;
-    this.id = hash(this.name);
-    this.service = service;
-    this.serverStreaming = descriptor.getServerStreaming()!;
-    this.clientStreaming = descriptor.getClientStreaming()!;
-
-    const requestTypePath = descriptor.getInputType()!;
-    const responseTypePath = descriptor.getOutputType()!;
-
-    // Remove leading period if it exists.
-    this.requestType = protoCollection.getMessageCreator(
-      requestTypePath.replace(/^\./, '')
-    )!;
-    this.responseType = protoCollection.getMessageCreator(
-      responseTypePath.replace(/^\./, '')
-    )!;
-  }
-
-  get type(): MethodType {
-    if (this.clientStreaming && this.serverStreaming) {
-      return MethodType.BIDIRECTIONAL_STREAMING;
-    } else if (this.clientStreaming && !this.serverStreaming) {
-      return MethodType.CLIENT_STREAMING;
-    } else if (!this.clientStreaming && this.serverStreaming) {
-      return MethodType.SERVER_STREAMING;
-    } else if (!this.clientStreaming && !this.serverStreaming) {
-      return MethodType.UNARY;
-    }
-    throw Error('Unhandled streaming condition');
-  }
-}
diff --git a/pw_rpc/ts/descriptors_test.ts b/pw_rpc/ts/descriptors_test.ts
deleted file mode 100644
index 8af4f10..0000000
--- a/pw_rpc/ts/descriptors_test.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-/* eslint-env browser, jasmine */
-import 'jasmine';
-
-import {ProtoCollection} from 'rpc_proto_collection/generated/ts_proto_collection';
-import {
-  Request,
-  Response,
-} from 'test_protos_tspb/test_protos_tspb_pb/pw_rpc/ts/test_pb';
-
-import * as descriptors from './descriptors';
-
-const TEST_PROTO_PATH = 'pw_rpc/ts/test_protos-descriptor-set.proto.bin';
-
-describe('Descriptors', () => {
-  it('parses from ServiceDescriptor binary', async () => {
-    const protoCollection = new ProtoCollection();
-    const fd = protoCollection.fileDescriptorSet.getFileList()[0];
-    const sd = fd.getServiceList()[0];
-    const service = new descriptors.Service(
-      sd,
-      protoCollection,
-      fd.getPackage()!
-    );
-
-    expect(service.name).toEqual('pw.rpc.test1.TheTestService');
-    expect(service.methods.size).toEqual(4);
-
-    const unaryMethod = service.methodsByName.get('SomeUnary')!;
-    expect(unaryMethod.name).toEqual('SomeUnary');
-    expect(unaryMethod.clientStreaming).toBeFalse();
-    expect(unaryMethod.serverStreaming).toBeFalse();
-    expect(unaryMethod.service).toEqual(service);
-    expect(unaryMethod.requestType).toEqual(Request);
-    expect(unaryMethod.responseType).toEqual(Response);
-
-    const someBidiStreaming = service.methodsByName.get('SomeBidiStreaming')!;
-    expect(someBidiStreaming.name).toEqual('SomeBidiStreaming');
-    expect(someBidiStreaming.clientStreaming).toBeTrue();
-    expect(someBidiStreaming.serverStreaming).toBeTrue();
-    expect(someBidiStreaming.service).toEqual(service);
-    expect(someBidiStreaming.requestType).toEqual(Request);
-    expect(someBidiStreaming.responseType).toEqual(Response);
-  });
-});
diff --git a/pw_rpc/ts/docs.rst b/pw_rpc/ts/docs.rst
deleted file mode 100644
index 8d719fc..0000000
--- a/pw_rpc/ts/docs.rst
+++ /dev/null
@@ -1,204 +0,0 @@
-.. _module-pw_rpc-ts:
-
--------------------------
-pw_rpc Typescript package
--------------------------
-The ``pw_rpc`` Typescript package makes it possible to call Pigweed RPCs from
-Typescript. The package includes client library to facilitate handling RPCs.
-
-This package is currently a work in progress.
-
-Creating an RPC Client
-======================
-The RPC client is instantiated from a list of channels and a set of protos.
-
-.. code-block:: typescript
-
-  const testProtoPath = 'pw_rpc/ts/test_protos-descriptor-set.proto.bin';
-  const lib = await Library.fromFileDescriptorSet(
-    testProtoPath, 'test_protos_tspb');
-  const channels = [new Channel(1, savePacket), new Channel(5)];
-  const client = Client.fromProtoSet(channels, lib);
-
-  function savePacket(packetBytes: Uint8Array): void {
-    const packet = RpcPacket.deserializeBinary(packetBytes);
-    ...
-  }
-
-The proto library must match the proto build rules. The first argument
-corresponds with the location of the ``proto_library`` build rule that generates
-a descriptor set for all src protos. The second argument corresponds with the
-name of the ``js_proto_library`` build rule that generates javascript based on
-the descriptor set. For instance, the previous example corresponds with the
-following build file: ``pw_rpc/ts/BUILD.bazel``.
-
-.. code-block::
-
-  proto_library(
-      name = "test_protos",
-      srcs = [
-          "test.proto",
-          "test2.proto",
-      ],
-  )
-
-  js_proto_library(
-      name = "test_protos_tspb",
-      protos = [":test_protos"],
-  )
-
-Finding an RPC Method
-=====================
-Once the client is instantiated with the correct proto library, the target RPC
-method is found by searching based on the full name:
-``{packageName}.{serviceName}.{methodName}``
-
-.. code-block:: typescript
-
-  const channel = client.channel()!;
-  unaryStub = channel.methodStub('pw.rpc.test1.TheTestService.SomeUnary')!
-      as UnaryMethodStub;
-
-The four possible RPC stubs are ``UnaryMethodStub``,
-``ServerStreamingMethodStub``, ``ClientStreamingMethodStub``, and
-``BidirectionalStreamingMethodStub``.  Note that ``channel.methodStub()``
-returns a general stub. Since each stub type has different invoke
-parameters, the general stub should be typecast before using.
-
-Invoke an RPC with callbacks
-============================
-
-.. code-block:: typescript
-
-  invoke(request?: Message,
-      onNext: Callback = () => {},
-      onCompleted: Callback = () => {},
-      onError: Callback = () => {}): Call
-
-All RPC methods can be invoked with a set of callbacks that are triggered when
-either a response is received, the RPC is completed, or an error occurs. The
-example below demonstrates registering these callbacks on a Bidirectional RPC.
-Other RPC types can be invoked in a similar fashion. The request parameter may
-differ slightly between RPC types.
-
-.. code-block:: typescript
-
-  bidiRpc = client.channel()?.methodStub(
-      'pw.rpc.test1.TheTestService.SomeBidi')!
-      as BidirectionalStreamingMethodStub;
-
-  // Configure callback functions
-  const onNext = (response: Message) => {
-    console.log(response);
-  }
-  const onComplete = (status: Status) => {
-    console.log('completed!');
-  }
-  const onError = (error: Error) => {
-    console.log();
-  }
-
-  bidiRpc.invoke(request, onNext, onComplete, onError);
-
-Open an RPC: ignore initial errors
-=====================================
-
-Open allows you to start and register an RPC without crashing on errors. This
-is useful for starting an RPC before the server is ready. For instance, starting
-a logging RPC while the device is booting.
-
-.. code-block:: typescript
-
-  open(request?: Message,
-      onNext: Callback = () => {},
-      onCompleted: Callback = () => {},
-      onError: Callback = () => {}): Call
-
-Blocking RPCs: promise API
-==========================
-
-Each MethodStub type provides an call() function that allows sending requests
-and awaiting responses through the promise API. The timeout field is optional.
-If no timeout is specified, the RPC will wait indefinitely.
-
-Unary RPC
----------
-.. code-block:: typescript
-
-  unaryRpc = client.channel()?.methodStub(
-      'pw.rpc.test1.TheTestService.SomeUnary')!
-      as UnaryMethodStub;
-  const request = new unaryRpc.requestType();
-  request.setFooProperty(4);
-  const timeout = 2000 // 2 seconds
-  const [status, response] = await unaryRpc.call(request, timeout);
-
-Server Streaming RPC
---------------------
-.. code-block:: typescript
-
-  serverStreamRpc = client.channel()?.methodStub(
-      'pw.rpc.test1.TheTestService.SomeServerStreaming')!
-      as ServerStreamingMethodStub;
-
-  const call = serverStreamRpc.invoke();
-  const timeout = 2000
-  for await (const response of call.getResponses(2, timeout)) {
-   console.log(response);
-  }
-  const responses = call.getResponse() // All responses until stream end.
-  while (!responses.done) {
-    console.log(await responses.value());
-  }
-
-
-Client Streaming RPC
---------------------
-.. code-block:: typescript
-
-  clientStreamRpc = client.channel()!.methodStub(
-    'pw.rpc.test1.TheTestService.SomeClientStreaming')!
-    as ClientStreamingMethodStub;
-  clientStreamRpc.invoke();
-  const request = new clientStreamRpc.method.requestType();
-  request.setFooProperty('foo_test');
-  clientStreamRpc.send(request);
-
-  // Send three more requests, end the stream, and wait for a response.
-  const timeout = 2000 // 2 seconds
-  request.finishAndWait([request, request, request], timeout)
-      .then(() => {
-        console.log('Client stream finished successfully');
-      })
-      .catch((reason) => {
-        console.log(`Client stream error: ${reason}`);
-      });
-
-Bidirectional Stream RPC
-------------------------
-.. code-block:: typescript
-
-  bidiStreamingRpc = client.channel()!.methodStub(
-    'pw.rpc.test1.TheTestService.SomeBidiStreaming')!
-    as BidirectionalStreamingMethodStub;
-  bidiStreamingRpc.invoke();
-  const request = new bidiStreamingRpc.method.requestType();
-  request.setFooProperty('foo_test');
-
-  // Send requests
-  bidiStreamingRpc.send(request);
-
-  // Receive responses
-  const timeout = 2000 // 2 seconds
-  for await (const response of call.getResponses(1, timeout)) {
-   console.log(response);
-  }
-
-  // Send three more requests, end the stream, and wait for a response.
-  request.finishAndWait([request, request, request], timeout)
-      .then(() => {
-        console.log('Bidirectional stream finished successfully');
-      })
-      .catch((reason) => {
-        console.log(`Bidirectional stream error: ${reason}`);
-      });
diff --git a/pw_rpc/ts/hash.ts b/pw_rpc/ts/hash.ts
deleted file mode 100644
index eaf76b3..0000000
--- a/pw_rpc/ts/hash.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-/** This module defines the string to ID hash used in pw_rpc. */
-
-const HASH_CONSTANT = 65599;
-
-/**
- * Hash by 65599
- *
- * This is the same hash function that is used in Python pw_rpc. It is chosen
- * due to its simplicity.
- */
-export function hash(input: string): number {
-  let hashValue = input.length;
-  let coefficient = HASH_CONSTANT;
-  for (let i = 0; i < input.length; i++) {
-    const ord = input.charCodeAt(i);
-    hashValue = (hashValue + coefficient * ord) % 2 ** 32;
-    coefficient = (coefficient * HASH_CONSTANT) % 2 ** 32;
-  }
-  return hashValue;
-}
diff --git a/pw_rpc/ts/index.ts b/pw_rpc/ts/index.ts
deleted file mode 100644
index 1dcdbed..0000000
--- a/pw_rpc/ts/index.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-export * from './call';
-export * from './client';
-export * from './descriptors';
-export * from './hash';
-export * from './method';
-export * from './packets';
-export * from './rpc_classes';
diff --git a/pw_rpc/ts/method.ts b/pw_rpc/ts/method.ts
deleted file mode 100644
index 7f10287..0000000
--- a/pw_rpc/ts/method.ts
+++ /dev/null
@@ -1,218 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 {Status} from '@pigweed/pw_status';
-import {Message} from 'google-protobuf';
-
-import {
-  BidirectionalStreamingCall,
-  Call,
-  Callback,
-  ClientStreamingCall,
-  ServerStreamingCall,
-  UnaryCall,
-} from './call';
-import {Channel, Method, MethodType, Service} from './descriptors';
-import {PendingCalls, Rpc} from './rpc_classes';
-
-export function methodStubFactory(
-  rpcs: PendingCalls,
-  channel: Channel,
-  method: Method
-): MethodStub {
-  switch (method.type) {
-    case MethodType.BIDIRECTIONAL_STREAMING:
-      return new BidirectionalStreamingMethodStub(rpcs, channel, method);
-    case MethodType.CLIENT_STREAMING:
-      return new ClientStreamingMethodStub(rpcs, channel, method);
-    case MethodType.SERVER_STREAMING:
-      return new ServerStreamingMethodStub(rpcs, channel, method);
-    case MethodType.UNARY:
-      return new UnaryMethodStub(rpcs, channel, method);
-  }
-}
-
-export abstract class MethodStub {
-  readonly method: Method;
-  readonly rpcs: PendingCalls;
-  readonly rpc: Rpc;
-  private channel: Channel;
-
-  constructor(rpcs: PendingCalls, channel: Channel, method: Method) {
-    this.method = method;
-    this.rpcs = rpcs;
-    this.channel = channel;
-    this.rpc = new Rpc(channel, method.service, method);
-  }
-
-  get id(): number {
-    return this.method.id;
-  }
-}
-
-export class UnaryMethodStub extends MethodStub {
-  invoke(
-    request: Message,
-    onNext: Callback = () => {},
-    onCompleted: Callback = () => {},
-    onError: Callback = () => {}
-  ): UnaryCall {
-    const call = new UnaryCall(
-      this.rpcs,
-      this.rpc,
-      onNext,
-      onCompleted,
-      onError
-    );
-    call.invoke(request);
-    return call;
-  }
-
-  open(
-    request: Message,
-    onNext: Callback = () => {},
-    onCompleted: Callback = () => {},
-    onError: Callback = () => {}
-  ): UnaryCall {
-    const call = new UnaryCall(
-      this.rpcs,
-      this.rpc,
-      onNext,
-      onCompleted,
-      onError
-    );
-    call.invoke(request, true);
-    return call;
-  }
-
-  async call(request: Message, timeout?: number): Promise<[Status, Message]> {
-    return await this.invoke(request).complete(timeout);
-  }
-}
-
-export class ServerStreamingMethodStub extends MethodStub {
-  invoke(
-    request?: Message,
-    onNext: Callback = () => {},
-    onCompleted: Callback = () => {},
-    onError: Callback = () => {}
-  ): ServerStreamingCall {
-    const call = new ServerStreamingCall(
-      this.rpcs,
-      this.rpc,
-      onNext,
-      onCompleted,
-      onError
-    );
-    call.invoke(request);
-    return call;
-  }
-
-  open(
-    request: Message,
-    onNext: Callback = () => {},
-    onCompleted: Callback = () => {},
-    onError: Callback = () => {}
-  ): UnaryCall {
-    const call = new UnaryCall(
-      this.rpcs,
-      this.rpc,
-      onNext,
-      onCompleted,
-      onError
-    );
-    call.invoke(request, true);
-    return call;
-  }
-
-  call(request?: Message, timeout?: number): Promise<[Status, Message[]]> {
-    return this.invoke(request).complete(timeout);
-  }
-}
-
-export class ClientStreamingMethodStub extends MethodStub {
-  invoke(
-    onNext: Callback = () => {},
-    onCompleted: Callback = () => {},
-    onError: Callback = () => {}
-  ): ClientStreamingCall {
-    const call = new ClientStreamingCall(
-      this.rpcs,
-      this.rpc,
-      onNext,
-      onCompleted,
-      onError
-    );
-    call.invoke();
-    return call;
-  }
-
-  open(
-    onNext: Callback = () => {},
-    onCompleted: Callback = () => {},
-    onError: Callback = () => {}
-  ): ClientStreamingCall {
-    const call = new ClientStreamingCall(
-      this.rpcs,
-      this.rpc,
-      onNext,
-      onCompleted,
-      onError
-    );
-    call.invoke(undefined, true);
-    return call;
-  }
-
-  async call(requests: Array<Message> = [], timeout?: number) {
-    return this.invoke().finishAndWait(requests, timeout);
-  }
-}
-
-export class BidirectionalStreamingMethodStub extends MethodStub {
-  invoke(
-    onNext: Callback = () => {},
-    onCompleted: Callback = () => {},
-    onError: Callback = () => {}
-  ): BidirectionalStreamingCall {
-    const call = new BidirectionalStreamingCall(
-      this.rpcs,
-      this.rpc,
-      onNext,
-      onCompleted,
-      onError
-    );
-    call.invoke();
-    return call;
-  }
-
-  open(
-    onNext: Callback = () => {},
-    onCompleted: Callback = () => {},
-    onError: Callback = () => {}
-  ): BidirectionalStreamingCall {
-    const call = new BidirectionalStreamingCall(
-      this.rpcs,
-      this.rpc,
-      onNext,
-      onCompleted,
-      onError
-    );
-    call.invoke(undefined, true);
-    return call;
-  }
-
-  async call(requests: Array<Message> = [], timeout?: number) {
-    return this.invoke().finishAndWait(requests, timeout);
-  }
-}
diff --git a/pw_rpc/ts/package.json b/pw_rpc/ts/package.json
deleted file mode 100644
index 7648957..0000000
--- a/pw_rpc/ts/package.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
-  "name": "@pigweed/pw_rpc",
-  "version": "1.0.0",
-  "main": "index.js",
-  "license": "Apache-2.0",
-  "dependencies": {
-    "@bazel/jasmine": "^4.1.0",
-    "@pigweed/pw_protobuf_compiler": "link:../../pw_protobuf_compiler/ts",
-    "@pigweed/pw_status": "link:../../pw_status/ts",
-    "@types/google-protobuf": "^3.15.5",
-    "@types/jasmine": "^3.9.0",
-    "@types/node": "^16.10.2",
-    "jasmine": "^3.9.0",
-    "jasmine-core": "^3.9.0",
-    "wait-queue": "^1.1.4"
-  }
-}
diff --git a/pw_rpc/ts/packets.ts b/pw_rpc/ts/packets.ts
deleted file mode 100644
index 4cd414b..0000000
--- a/pw_rpc/ts/packets.ts
+++ /dev/null
@@ -1,103 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-/** Functions for working with pw_rpc packets. */
-
-import {Message} from 'google-protobuf';
-import {MethodDescriptorProto} from 'google-protobuf/google/protobuf/descriptor_pb';
-import * as packetPb from 'packet_proto_tspb/packet_proto_tspb_pb/pw_rpc/internal/packet_pb';
-import {Status} from '@pigweed/pw_status';
-
-// Channel, Service, Method
-type idSet = [number, number, number];
-
-export function decode(data: Uint8Array): packetPb.RpcPacket {
-  return packetPb.RpcPacket.deserializeBinary(data);
-}
-
-export function decodePayload(payload: Uint8Array, payloadType: any): Message {
-  const message = payloadType.deserializeBinary(payload);
-  return message;
-}
-
-export function forServer(packet: packetPb.RpcPacket): boolean {
-  return packet.getType() % 2 == 0;
-}
-
-export function encodeClientError(
-  packet: packetPb.RpcPacket,
-  status: Status
-): Uint8Array {
-  const errorPacket = new packetPb.RpcPacket();
-  errorPacket.setType(packetPb.PacketType.CLIENT_ERROR);
-  errorPacket.setChannelId(packet.getChannelId());
-  errorPacket.setMethodId(packet.getMethodId());
-  errorPacket.setServiceId(packet.getServiceId());
-  errorPacket.setStatus(status);
-  return errorPacket.serializeBinary();
-}
-
-export function encodeClientStream(ids: idSet, message: Message): Uint8Array {
-  const streamPacket = new packetPb.RpcPacket();
-  streamPacket.setType(packetPb.PacketType.CLIENT_STREAM);
-  streamPacket.setChannelId(ids[0]);
-  streamPacket.setServiceId(ids[1]);
-  streamPacket.setMethodId(ids[2]);
-  streamPacket.setPayload(message.serializeBinary());
-  return streamPacket.serializeBinary();
-}
-
-export function encodeClientStreamEnd(ids: idSet): Uint8Array {
-  const streamEnd = new packetPb.RpcPacket();
-  streamEnd.setType(packetPb.PacketType.CLIENT_STREAM_END);
-  streamEnd.setChannelId(ids[0]);
-  streamEnd.setServiceId(ids[1]);
-  streamEnd.setMethodId(ids[2]);
-  return streamEnd.serializeBinary();
-}
-
-export function encodeRequest(ids: idSet, request?: Message): Uint8Array {
-  const payload: Uint8Array =
-    typeof request !== 'undefined'
-      ? request.serializeBinary()
-      : new Uint8Array();
-
-  const packet = new packetPb.RpcPacket();
-  packet.setType(packetPb.PacketType.REQUEST);
-  packet.setChannelId(ids[0]);
-  packet.setServiceId(ids[1]);
-  packet.setMethodId(ids[2]);
-  packet.setPayload(payload);
-  return packet.serializeBinary();
-}
-
-export function encodeResponse(ids: idSet, response: Message): Uint8Array {
-  const packet = new packetPb.RpcPacket();
-  packet.setType(packetPb.PacketType.RESPONSE);
-  packet.setChannelId(ids[0]);
-  packet.setServiceId(ids[1]);
-  packet.setMethodId(ids[2]);
-  packet.setPayload(response.serializeBinary());
-  return packet.serializeBinary();
-}
-
-export function encodeCancel(ids: idSet): Uint8Array {
-  const packet = new packetPb.RpcPacket();
-  packet.setType(packetPb.PacketType.CLIENT_ERROR);
-  packet.setStatus(Status.CANCELLED);
-  packet.setChannelId(ids[0]);
-  packet.setServiceId(ids[1]);
-  packet.setMethodId(ids[2]);
-  return packet.serializeBinary();
-}
diff --git a/pw_rpc/ts/packets_test.ts b/pw_rpc/ts/packets_test.ts
deleted file mode 100644
index d214518..0000000
--- a/pw_rpc/ts/packets_test.ts
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-/* eslint-env browser, jasmine */
-import 'jasmine';
-
-import {
-  PacketType,
-  RpcPacket,
-} from 'packet_proto_tspb/packet_proto_tspb_pb/pw_rpc/internal/packet_pb';
-import {Status} from '@pigweed/pw_status';
-
-import * as packets from './packets';
-
-function addTestData(packet: RpcPacket) {
-  const payload = new RpcPacket();
-  payload.setStatus(321);
-  packet.setChannelId(1);
-  packet.setServiceId(2);
-  packet.setMethodId(3);
-  packet.setPayload(payload.serializeBinary());
-}
-
-describe('Packets', () => {
-  beforeEach(() => {});
-
-  it('encodeRequest sets packet fields', () => {
-    const goldenRequest = new RpcPacket();
-    goldenRequest.setType(PacketType.REQUEST);
-    addTestData(goldenRequest);
-
-    const dataPacket = new RpcPacket();
-    dataPacket.setStatus(321);
-    const data = packets.encodeRequest([1, 2, 3], dataPacket);
-    const packet = RpcPacket.deserializeBinary(data);
-
-    expect(packet.toObject()).toEqual(goldenRequest.toObject());
-  });
-
-  it('encodeResponse sets packet fields', () => {
-    const goldenResponse = new RpcPacket();
-    goldenResponse.setType(PacketType.RESPONSE);
-    addTestData(goldenResponse);
-
-    const dataPacket = new RpcPacket();
-    dataPacket.setStatus(321);
-    const data = packets.encodeResponse([1, 2, 3], dataPacket);
-    const packet = RpcPacket.deserializeBinary(data);
-
-    expect(packet.toObject()).toEqual(goldenResponse.toObject());
-  });
-
-  it('encodesClientError sets packet fields', () => {
-    const packet = new RpcPacket();
-    packet.setType(PacketType.REQUEST);
-    addTestData(packet);
-    const data = packets.encodeClientError(packet, Status.NOT_FOUND);
-    const errorPacket = RpcPacket.deserializeBinary(data);
-
-    const golden = new RpcPacket();
-    golden.setType(PacketType.CLIENT_ERROR);
-    golden.setChannelId(1);
-    golden.setServiceId(2);
-    golden.setMethodId(3);
-    golden.setStatus(Status.NOT_FOUND);
-
-    expect(errorPacket.toObject()).toEqual(golden.toObject());
-  });
-
-  it('encodeCancel sets packet fields', () => {
-    const goldenCancel = new RpcPacket();
-    goldenCancel.setType(PacketType.CLIENT_ERROR);
-    goldenCancel.setStatus(Status.CANCELLED);
-    goldenCancel.setChannelId(1);
-    goldenCancel.setServiceId(2);
-    goldenCancel.setMethodId(3);
-
-    const data = packets.encodeCancel([1, 2, 3]);
-    const packet = RpcPacket.deserializeBinary(data);
-    expect(packet.toObject()).toEqual(goldenCancel.toObject());
-  });
-
-  it('decode with serialized request returns request', () => {
-    const request = new RpcPacket();
-    request.setType(PacketType.REQUEST);
-    addTestData(request);
-
-    expect(request.toObject()).toEqual(
-      packets.decode(request.serializeBinary()).toObject()
-    );
-  });
-
-  it('forServer correctly handles RESPONSE and REQUEST types', () => {
-    const request = new RpcPacket();
-    request.setType(PacketType.REQUEST);
-    const response = new RpcPacket();
-    response.setType(PacketType.RESPONSE);
-
-    expect(packets.forServer(request)).toBeTrue();
-    expect(packets.forServer(response)).toBeFalse();
-  });
-});
diff --git a/pw_rpc/ts/rpc_classes.ts b/pw_rpc/ts/rpc_classes.ts
deleted file mode 100644
index e29362d..0000000
--- a/pw_rpc/ts/rpc_classes.ts
+++ /dev/null
@@ -1,150 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 {Message} from 'google-protobuf';
-import {Status} from '@pigweed/pw_status';
-
-import {Call} from './call';
-import {Channel, Method, Service} from './descriptors';
-import * as packets from './packets';
-
-/** Data class for a pending RPC call. */
-export class Rpc {
-  readonly channel: Channel;
-  readonly service: Service;
-  readonly method: Method;
-
-  constructor(channel: Channel, service: Service, method: Method) {
-    this.channel = channel;
-    this.service = service;
-    this.method = method;
-  }
-
-  /** Returns channel service method id tuple */
-  get idSet(): [number, number, number] {
-    return [this.channel.id, this.service.id, this.method.id];
-  }
-
-  /**
-   * Returns a string sequence to uniquely identify channel, service, and
-   * method. This can be used to hash the Rpc.
-   *
-   * For example: "12346789.23452345.12341234"
-   */
-  get idString(): string {
-    return `${this.channel.id}.${this.service.id}.${this.method.id}`;
-  }
-
-  toString(): string {
-    return (
-      `${this.service.name}.${this.method.name} on channel ` +
-      `${this.channel.id}`
-    );
-  }
-}
-
-/** Tracks pending RPCs and encodes outgoing RPC packets. */
-export class PendingCalls {
-  pending: Map<string, Call> = new Map();
-
-  /** Starts the provided RPC and returns the encoded packet to send. */
-  request(rpc: Rpc, request: Message, call: Call): Uint8Array {
-    this.open(rpc, call);
-    console.log(`Starting ${rpc}`);
-    return packets.encodeRequest(rpc.idSet, request);
-  }
-
-  /** Calls request and sends the resulting packet to the channel. */
-  sendRequest(
-    rpc: Rpc,
-    call: Call,
-    ignoreError: boolean,
-    request?: Message
-  ): Call | undefined {
-    const previous = this.open(rpc, call);
-    const packet = packets.encodeRequest(rpc.idSet, request);
-    try {
-      rpc.channel.send(packet);
-    } catch (error) {
-      if (!ignoreError) {
-        throw error;
-      }
-    }
-    return previous;
-  }
-
-  /**
-   * Creates a call for an RPC, but does not invoke it.
-   *
-   * open() can be used to receive streaming responses to an RPC that was not
-   * invoked by this client. For example, a server may stream logs with a
-   * server streaming RPC prior to any clients invoking it.
-   */
-  open(rpc: Rpc, call: Call): Call | undefined {
-    console.debug(`Starting ${rpc}`);
-    const previous = this.pending.get(rpc.idString);
-    this.pending.set(rpc.idString, call);
-    return previous;
-  }
-
-  sendClientStream(rpc: Rpc, message: Message) {
-    if (this.getPending(rpc) === undefined) {
-      throw new Error(`Attempt to send client stream for inactive RPC: ${rpc}`);
-    }
-    rpc.channel.send(packets.encodeClientStream(rpc.idSet, message));
-  }
-
-  sendClientStreamEnd(rpc: Rpc) {
-    if (this.getPending(rpc) === undefined) {
-      throw new Error(`Attempt to send client stream for inactive RPC: ${rpc}`);
-    }
-    rpc.channel.send(packets.encodeClientStreamEnd(rpc.idSet));
-  }
-
-  /** Cancels the RPC. Returns the CANCEL packet to send. */
-  cancel(rpc: Rpc): Uint8Array | undefined {
-    console.debug(`Cancelling ${rpc}`);
-    this.pending.delete(rpc.idString);
-    if (rpc.method.clientStreaming && rpc.method.serverStreaming) {
-      return undefined;
-    }
-    return packets.encodeCancel(rpc.idSet);
-  }
-
-  /** Calls cancel and sends the cancel packet, if any, to the channel. */
-  sendCancel(rpc: Rpc): boolean {
-    let packet: Uint8Array | undefined;
-    try {
-      packet = this.cancel(rpc);
-    } catch (err) {
-      return false;
-    }
-
-    if (packet !== undefined) {
-      rpc.channel.send(packet);
-    }
-    return true;
-  }
-
-  /** Gets the pending RPC's call. If status is set, clears the RPC. */
-  getPending(rpc: Rpc, status?: Status): Call | undefined {
-    if (status === undefined) {
-      return this.pending.get(rpc.idString);
-    }
-
-    const call = this.pending.get(rpc.idString);
-    this.pending.delete(rpc.idString);
-    return call;
-  }
-}
diff --git a/pw_rpc/ts/test.proto b/pw_rpc/ts/test.proto
deleted file mode 100644
index 8079205..0000000
--- a/pw_rpc/ts/test.proto
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-syntax = "proto3";
-
-package pw.rpc.test1;
-
-message Request {
-  uint32 magic_number = 1;
-}
-
-message Response {
-  enum Result {
-    FAILED = 0;
-    FAILED_MISERABLY = 1;
-    I_DONT_WANT_TO_TALK_ABOUT_IT = 2;
-  }
-
-  Result result = 1;
-  string payload = 2;
-}
-
-service TheTestService {
-  // Unary RPC for testing
-  rpc SomeUnary(Request) returns (Response) {}
-
-  // Server streaming RPC for testing
-  rpc SomeServerStreaming(Request) returns (stream Response) {}
-
-  // Client streaming RPC for testing
-  rpc SomeClientStreaming(stream Request) returns (Response) {}
-
-  // Bidirectional streaming RPC for testing
-  rpc SomeBidiStreaming(stream Request) returns (stream Response) {}
-}
diff --git a/pw_rpc/ts/test2.proto b/pw_rpc/ts/test2.proto
deleted file mode 100644
index 479a2eb..0000000
--- a/pw_rpc/ts/test2.proto
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-syntax = "proto2";
-
-package pw.test2;
-
-message SomeMessage {
-  optional float magic_number = 1;
-}
-
-message AnotherMessage {}
-
-service Alpha {
-  rpc Unary(SomeMessage) returns (AnotherMessage) {}
-}
-
-service Bravo {
-  rpc BidiStreaming(stream SomeMessage) returns (stream AnotherMessage) {}
-}
diff --git a/pw_rpc/ts/tsconfig.json b/pw_rpc/ts/tsconfig.json
deleted file mode 100644
index 4ddd637..0000000
--- a/pw_rpc/ts/tsconfig.json
+++ /dev/null
@@ -1,34 +0,0 @@
-{
-  "compilerOptions": {
-    "allowUnreachableCode": false,
-    "allowUnusedLabels": false,
-    "declaration": true,
-    "forceConsistentCasingInFileNames": true,
-    "lib": [
-      "es2018",
-      "dom",
-      "dom.iterable",
-      "esnext"
-    ],
-    "module": "commonjs",
-    "noEmitOnError": true,
-    "noFallthroughCasesInSwitch": true,
-    "noImplicitReturns": true,
-    "pretty": true,
-    "sourceMap": true,
-    "strict": true,
-    "target": "es2018",
-    "jsx": "react",
-    "plugins": [
-      {
-        "name": "@bazel/tsetse",
-        "disabledRules": [
-          "must-use-promises"
-        ]
-      }
-    ]
-  },
-  "exclude": [
-    "node_modules"
-  ]
-}
diff --git a/pw_rpc/ts/yarn.lock b/pw_rpc/ts/yarn.lock
deleted file mode 100644
index f38b2fb..0000000
--- a/pw_rpc/ts/yarn.lock
+++ /dev/null
@@ -1,510 +0,0 @@
-# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
-# yarn lockfile v1
-
-
-"@bazel/jasmine@^4.1.0":
-  version "4.3.0"
-  resolved "https://registry.yarnpkg.com/@bazel/jasmine/-/jasmine-4.3.0.tgz#d2dd29deb56cffae2b3bd7be706fb1b3dd532fc7"
-  integrity sha512-lROo6iAdyqmqVNe8M5or6Vkzcn5wyBSI4MJBqqLJVjejhlhU6Mg27j1xC+VJPlnQkiEyeHLV5WNndp50ROivSw==
-  dependencies:
-    c8 "~7.5.0"
-    jasmine-reporters "~2.4.0"
-
-"@bcoe/v8-coverage@^0.2.3":
-  version "0.2.3"
-  resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
-  integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
-
-"@istanbuljs/schema@^0.1.2":
-  version "0.1.3"
-  resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98"
-  integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==
-
-"@pigweed/pw_protobuf_compiler@link:../../pw_protobuf_compiler/ts":
-  version "0.0.0"
-  uid ""
-
-"@pigweed/pw_status@link:../../pw_status/ts":
-  version "0.0.0"
-  uid ""
-
-"@types/argparse@^2.0.10":
-  version "2.0.10"
-  resolved "https://registry.yarnpkg.com/@types/argparse/-/argparse-2.0.10.tgz#664e84808accd1987548d888b9d21b3e9c996a6c"
-  integrity sha512-C4wahC3gz3vQtvPazrJ5ONwmK1zSDllQboiWvpMM/iOswCYfBREFnjFbq/iWKIVOCl8+m5Pk6eva6/ZSsDuIGA==
-
-"@types/google-protobuf@^3.15.5":
-  version "3.15.5"
-  resolved "https://registry.yarnpkg.com/@types/google-protobuf/-/google-protobuf-3.15.5.tgz#644b2be0f5613b1f822c70c73c6b0e0b5b5fa2ad"
-  integrity sha512-6bgv24B+A2bo9AfzReeg5StdiijKzwwnRflA8RLd1V4Yv995LeTmo0z69/MPbBDFSiZWdZHQygLo/ccXhMEDgw==
-
-"@types/is-windows@^1.0.0":
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/@types/is-windows/-/is-windows-1.0.0.tgz#1011fa129d87091e2f6faf9042d6704cdf2e7be0"
-  integrity sha512-tJ1rq04tGKuIJoWIH0Gyuwv4RQ3+tIu7wQrC0MV47raQ44kIzXSSFKfrxFUOWVRvesoF7mrTqigXmqoZJsXwTg==
-
-"@types/istanbul-lib-coverage@^2.0.1":
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
-  integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==
-
-"@types/jasmine@^3.9.0":
-  version "3.9.1"
-  resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.9.1.tgz#94c65ee8bf9d24d9e1d84abaed57b6e0da8b49de"
-  integrity sha512-PVpjh8S8lqKFKurWSKdFATlfBHGPzgy0PoDdzQ+rr78jTQ0aacyh9YndzZcAUPxhk4kRujItFFGQdUJ7flHumw==
-
-"@types/node@^16.10.2":
-  version "16.10.2"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-16.10.2.tgz#5764ca9aa94470adb4e1185fe2e9f19458992b2e"
-  integrity sha512-zCclL4/rx+W5SQTzFs9wyvvyCwoK9QtBpratqz2IYJ3O8Umrn0m3nsTv0wQBk9sRGpvUe9CwPDrQFB10f1FIjQ==
-
-ansi-regex@^5.0.1:
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
-  integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
-
-ansi-styles@^4.0.0:
-  version "4.3.0"
-  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
-  integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
-  dependencies:
-    color-convert "^2.0.1"
-
-argparse@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
-  integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
-
-balanced-match@^1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
-  integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
-
-base64-js@^1.5.1:
-  version "1.5.1"
-  resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
-  integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
-
-brace-expansion@^1.1.7:
-  version "1.1.11"
-  resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
-  integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
-  dependencies:
-    balanced-match "^1.0.0"
-    concat-map "0.0.1"
-
-c8@~7.5.0:
-  version "7.5.0"
-  resolved "https://registry.yarnpkg.com/c8/-/c8-7.5.0.tgz#a69439ab82848f344a74bb25dc5dd4e867764481"
-  integrity sha512-GSkLsbvDr+FIwjNSJ8OwzWAyuznEYGTAd1pzb/Kr0FMLuV4vqYJTyjboDTwmlUNAG6jAU3PFWzqIdKrOt1D8tw==
-  dependencies:
-    "@bcoe/v8-coverage" "^0.2.3"
-    "@istanbuljs/schema" "^0.1.2"
-    find-up "^5.0.0"
-    foreground-child "^2.0.0"
-    furi "^2.0.0"
-    istanbul-lib-coverage "^3.0.0"
-    istanbul-lib-report "^3.0.0"
-    istanbul-reports "^3.0.2"
-    rimraf "^3.0.0"
-    test-exclude "^6.0.0"
-    v8-to-istanbul "^7.1.0"
-    yargs "^16.0.0"
-    yargs-parser "^20.0.0"
-
-cliui@^7.0.2:
-  version "7.0.4"
-  resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
-  integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
-  dependencies:
-    string-width "^4.2.0"
-    strip-ansi "^6.0.0"
-    wrap-ansi "^7.0.0"
-
-color-convert@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
-  integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
-  dependencies:
-    color-name "~1.1.4"
-
-color-name@~1.1.4:
-  version "1.1.4"
-  resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
-  integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
-
-concat-map@0.0.1:
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
-  integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
-
-convert-source-map@^1.6.0:
-  version "1.8.0"
-  resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369"
-  integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==
-  dependencies:
-    safe-buffer "~5.1.1"
-
-cross-spawn@^7.0.0:
-  version "7.0.3"
-  resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
-  integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
-  dependencies:
-    path-key "^3.1.0"
-    shebang-command "^2.0.0"
-    which "^2.0.1"
-
-emoji-regex@^8.0.0:
-  version "8.0.0"
-  resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
-  integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
-
-escalade@^3.1.1:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
-  integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
-
-find-up@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
-  integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
-  dependencies:
-    locate-path "^6.0.0"
-    path-exists "^4.0.0"
-
-foreground-child@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-2.0.0.tgz#71b32800c9f15aa8f2f83f4a6bd9bff35d861a53"
-  integrity sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==
-  dependencies:
-    cross-spawn "^7.0.0"
-    signal-exit "^3.0.2"
-
-fs.realpath@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
-  integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
-
-furi@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/furi/-/furi-2.0.0.tgz#13d85826a1af21acc691da6254b3888fc39f0b4a"
-  integrity sha512-uKuNsaU0WVaK/vmvj23wW1bicOFfyqSsAIH71bRZx8kA4Xj+YCHin7CJKJJjkIsmxYaPFLk9ljmjEyB7xF7WvQ==
-  dependencies:
-    "@types/is-windows" "^1.0.0"
-    is-windows "^1.0.2"
-
-get-caller-file@^2.0.5:
-  version "2.0.5"
-  resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
-  integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
-
-glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
-  integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
-  dependencies:
-    fs.realpath "^1.0.0"
-    inflight "^1.0.4"
-    inherits "2"
-    minimatch "^3.0.4"
-    once "^1.3.0"
-    path-is-absolute "^1.0.0"
-
-google-protobuf@^3.19.0:
-  version "3.19.0"
-  resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.19.0.tgz#97f474323c92f19fd6737af1bb792e396991e0b8"
-  integrity sha512-qXGAiv3OOlaJXJNeKOBKxbBAwjsxzhx+12ZdKOkZTsqsRkyiQRmr/nBkAkqnuQ8cmA9X5NVXvObQTpHVnXE2DQ==
-
-has-flag@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
-  integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
-
-html-escaper@^2.0.0:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
-  integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
-
-inflight@^1.0.4:
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
-  integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
-  dependencies:
-    once "^1.3.0"
-    wrappy "1"
-
-inherits@2:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
-  integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
-
-is-fullwidth-code-point@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
-  integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
-
-is-windows@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
-  integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
-
-isexe@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
-  integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
-
-istanbul-lib-coverage@^3.0.0:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.1.tgz#e8900b3ed6069759229cf30f7067388d148aeb5e"
-  integrity sha512-GvCYYTxaCPqwMjobtVcVKvSHtAGe48MNhGjpK8LtVF8K0ISX7hCKl85LgtuaSneWVyQmaGcW3iXVV3GaZSLpmQ==
-
-istanbul-lib-report@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6"
-  integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==
-  dependencies:
-    istanbul-lib-coverage "^3.0.0"
-    make-dir "^3.0.0"
-    supports-color "^7.1.0"
-
-istanbul-reports@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.2.tgz#d593210e5000683750cb09fc0644e4b6e27fd53b"
-  integrity sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==
-  dependencies:
-    html-escaper "^2.0.0"
-    istanbul-lib-report "^3.0.0"
-
-jasmine-core@^3.9.0, jasmine-core@~3.9.0:
-  version "3.9.0"
-  resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.9.0.tgz#09a3c8169fe98ec69440476d04a0e4cb4d59e452"
-  integrity sha512-Tv3kVbPCGVrjsnHBZ38NsPU3sDOtNa0XmbG2baiyJqdb5/SPpDO6GVwJYtUryl6KB4q1Ssckwg612ES9Z0dreQ==
-
-jasmine-reporters@~2.4.0:
-  version "2.4.0"
-  resolved "https://registry.yarnpkg.com/jasmine-reporters/-/jasmine-reporters-2.4.0.tgz#708c17ae70ba6671e3a930bb1b202aab80a31409"
-  integrity sha512-jxONSrBLN1vz/8zCx5YNWQSS8iyDAlXQ5yk1LuqITe4C6iXCDx5u6Q0jfNtkKhL4qLZPe69fL+AWvXFt9/x38w==
-  dependencies:
-    mkdirp "^0.5.1"
-    xmldom "^0.5.0"
-
-jasmine@^3.9.0:
-  version "3.9.0"
-  resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-3.9.0.tgz#286c4f9f88b69defc24acf3989af5533d5c6a0e6"
-  integrity sha512-JgtzteG7xnqZZ51fg7N2/wiQmXon09szkALcRMTgCMX4u/m17gVJFjObnvw5FXkZOWuweHPaPRVB6DI2uN0wVA==
-  dependencies:
-    glob "^7.1.6"
-    jasmine-core "~3.9.0"
-
-locate-path@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
-  integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
-  dependencies:
-    p-locate "^5.0.0"
-
-make-dir@^3.0.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
-  integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
-  dependencies:
-    semver "^6.0.0"
-
-minimatch@^3.0.4:
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
-  integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
-  dependencies:
-    brace-expansion "^1.1.7"
-
-minimist@^1.2.5:
-  version "1.2.5"
-  resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
-  integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
-
-mkdirp@^0.5.1:
-  version "0.5.5"
-  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
-  integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
-  dependencies:
-    minimist "^1.2.5"
-
-once@^1.3.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
-  integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
-  dependencies:
-    wrappy "1"
-
-p-limit@^3.0.2:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
-  integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
-  dependencies:
-    yocto-queue "^0.1.0"
-
-p-locate@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
-  integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
-  dependencies:
-    p-limit "^3.0.2"
-
-path-exists@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
-  integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
-
-path-is-absolute@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
-  integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
-
-path-key@^3.1.0:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
-  integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
-
-require-directory@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
-  integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
-
-rimraf@^3.0.0:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
-  integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
-  dependencies:
-    glob "^7.1.3"
-
-safe-buffer@~5.1.1:
-  version "5.1.2"
-  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
-  integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
-
-semver@^6.0.0:
-  version "6.3.0"
-  resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
-  integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
-
-shebang-command@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
-  integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
-  dependencies:
-    shebang-regex "^3.0.0"
-
-shebang-regex@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
-  integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
-
-signal-exit@^3.0.2:
-  version "3.0.5"
-  resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.5.tgz#9e3e8cc0c75a99472b44321033a7702e7738252f"
-  integrity sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==
-
-source-map@^0.7.3:
-  version "0.7.3"
-  resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
-  integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
-
-string-width@^4.1.0, string-width@^4.2.0:
-  version "4.2.3"
-  resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
-  integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
-  dependencies:
-    emoji-regex "^8.0.0"
-    is-fullwidth-code-point "^3.0.0"
-    strip-ansi "^6.0.1"
-
-strip-ansi@^6.0.0, strip-ansi@^6.0.1:
-  version "6.0.1"
-  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
-  integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
-  dependencies:
-    ansi-regex "^5.0.1"
-
-supports-color@^7.1.0:
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
-  integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
-  dependencies:
-    has-flag "^4.0.0"
-
-test-exclude@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e"
-  integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==
-  dependencies:
-    "@istanbuljs/schema" "^0.1.2"
-    glob "^7.1.4"
-    minimatch "^3.0.4"
-
-v8-to-istanbul@^7.1.0:
-  version "7.1.2"
-  resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz#30898d1a7fa0c84d225a2c1434fb958f290883c1"
-  integrity sha512-TxNb7YEUwkLXCQYeudi6lgQ/SZrzNO4kMdlqVxaZPUIUjCv6iSSypUQX70kNBSERpQ8fk48+d61FXk+tgqcWow==
-  dependencies:
-    "@types/istanbul-lib-coverage" "^2.0.1"
-    convert-source-map "^1.6.0"
-    source-map "^0.7.3"
-
-wait-queue@^1.1.4:
-  version "1.1.4"
-  resolved "https://registry.yarnpkg.com/wait-queue/-/wait-queue-1.1.4.tgz#344f9bdd6e011ddc0bb1e3252eeb41234f7a8a85"
-  integrity sha512-/VdMghiBDG/Ch43ZRp3d8OSd8A0dx8hfkBO7AfWCDzMn2blHquMf+3gqHHhYcggSBpKf7VTzA939bb0DevYKBA==
-
-which@^2.0.1:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
-  integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
-  dependencies:
-    isexe "^2.0.0"
-
-wrap-ansi@^7.0.0:
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
-  integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
-  dependencies:
-    ansi-styles "^4.0.0"
-    string-width "^4.1.0"
-    strip-ansi "^6.0.0"
-
-wrappy@1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
-  integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
-
-xmldom@^0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.5.0.tgz#193cb96b84aa3486127ea6272c4596354cb4962e"
-  integrity sha512-Foaj5FXVzgn7xFzsKeNIde9g6aFBxTPi37iwsno8QvApmtg7KYrr+OPyRHcJF7dud2a5nGRBXK3n0dL62Gf7PA==
-
-y18n@^5.0.5:
-  version "5.0.8"
-  resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
-  integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
-
-yargs-parser@^20.0.0, yargs-parser@^20.2.2:
-  version "20.2.9"
-  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
-  integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
-
-yargs@^16.0.0:
-  version "16.2.0"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
-  integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
-  dependencies:
-    cliui "^7.0.2"
-    escalade "^3.1.1"
-    get-caller-file "^2.0.5"
-    require-directory "^2.1.1"
-    string-width "^4.2.0"
-    y18n "^5.0.5"
-    yargs-parser "^20.2.2"
-
-yocto-queue@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
-  integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
diff --git a/pw_rpc/unary_rpc.svg b/pw_rpc/unary_rpc.svg
deleted file mode 100644
index 1a0a28d..0000000
--- a/pw_rpc/unary_rpc.svg
+++ /dev/null
@@ -1,47 +0,0 @@
-<!-- Originally created with blockdiag. See the Git history for the source. -->
-<svg height="408.1" viewBox="0 0 534 371" width="587.4000000000001" xmlns="http://www.w3.org/2000/svg" xmlns:inkspace="http://www.inkscape.org/namespaces/inkscape" xmlns:xlink="http://www.w3.org/1999/xlink" style="background-color:white">
-<defs id="defs_block">
-<filter height="1.504" id="filter_blur" inkspace:collect="always" width="1.1575" x="-0.07875" y="-0.252">
-<feGaussianBlur id="feGaussianBlur3780" inkspace:collect="always" stdDeviation="4.2"></feGaussianBlur>
-</filter>
-</defs>
-<title>Unary RPC</title>
-<desc></desc>
-<rect fill="rgb(0,0,0)" height="190" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="8" x="171" y="159"></rect>
-<rect fill="rgb(0,0,0)" height="114" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="8" x="363" y="159"></rect>
-<polygon fill="rgb(0,0,0)" points="27,126 151,126 159,134 159,193 27,193 27,126" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></polygon>
-<polygon fill="rgb(0,0,0)" points="383,233 513,233 521,241 521,313 383,313 383,233" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></polygon>
-<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="111" y="46"></rect>
-<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="303" y="46"></rect>
-<path d="M 172 80 L 172 359" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="8 4"></path>
-<rect fill="moccasin" height="190" stroke="rgb(0,0,0)" width="8" x="168" y="153"></rect>
-<path d="M 364 80 L 364 359" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="8 4"></path>
-<rect fill="moccasin" height="114" stroke="rgb(0,0,0)" width="8" x="360" y="153"></rect>
-<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="108" y="40"></rect>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="36" x="172.0" y="66">client</text>
-<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="300" y="40"></rect>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="36" x="364.0" y="66">server</text>
-<path d="M 180 153 L 356 153" fill="none" stroke="rgb(0,0,0)"></path>
-<polygon fill="rgb(0,0,0)" points="348,149 356,153 348,157" stroke="rgb(0,0,0)"></polygon>
-<polygon fill="rgb(240,248,255)" points="24,120 148,120 156,128 156,187 24,187 24,120" stroke="rgb(0,0,0)"></polygon>
-<path d="M 148 120 L 148 128" fill="none" stroke="rgb(0,0,0)"></path>
-<path d="M 148 128 L 156 128" fill="none" stroke="rgb(0,0,0)"></path>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="108" x="86.0" y="133">PacketType.REQUEST</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="60" x="62.0" y="146">channel ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="60" x="62.0" y="159">service ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="54" x="59.0" y="172">method ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="42" x="53.0" y="185">payload</text>
-<path d="M 180 267 L 356 267" fill="none" stroke="rgb(0,0,0)"></path>
-<polygon fill="rgb(0,0,0)" points="188,263 180,267 188,271" stroke="rgb(0,0,0)"></polygon>
-<polygon fill="rgb(240,248,255)" points="380,227 510,227 518,235 518,307 380,307 380,227" stroke="rgb(0,0,0)"></polygon>
-<path d="M 510 227 L 510 235" fill="none" stroke="rgb(0,0,0)"></path>
-<path d="M 510 235 L 518 235" fill="none" stroke="rgb(0,0,0)"></path>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="114" x="445.0" y="240">PacketType.RESPONSE</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="60" x="418.0" y="253">channel ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="60" x="418.0" y="266">service ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="54" x="415.0" y="279">method ID</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="42" x="409.0" y="292">payload</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="36" x="406.0" y="305">status</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="42" x="205.0" y="151">request</text>
-<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="48" x="332.0" y="265">response</text>
-</svg>
diff --git a/pw_rpc/unary_rpc_cancelled.svg b/pw_rpc/unary_rpc_cancelled.svg
deleted file mode 100644
index c253780..0000000
--- a/pw_rpc/unary_rpc_cancelled.svg
+++ /dev/null
@@ -1,59 +0,0 @@
-<!-- Originally created with blockdiag. -->
-<svg viewBox="0 0 524 358" xmlns="http://www.w3.org/2000/svg" xmlns:inkspace="http://www.inkscape.org/namespaces/inkscape" xmlns:xlink="http://www.w3.org/1999/xlink" width="524px" height="358px">
-  <defs id="defs_block">
-    <filter height="1.504" id="filter_blur" inkspace:collect="always" width="1.1575" x="-0.07875" y="-0.252">
-      <feGaussianBlur id="feGaussianBlur3780" inkspace:collect="always" stdDeviation="4.2" />
-    </filter>
-  </defs>
-  <title>Cancelled Unary RPC</title>
-  <desc>seqdiag {
-  default_note_color = aliceblue;
-
-  client -&gt; server [
-      label = "request",
-      leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID\npayload"
-  ];
-
-  client -&gt; server [
-      noactivate,
-      label = "cancel",
-      leftnote  = "PacketType.CLIENT_ERROR\nchannel ID\nservice ID\nmethod ID\nstatus=CANCELLED"
-  ];
-}</desc>
-  <rect fill="rgb(0,0,0)" height="177" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="8" x="203" y="159" />
-  <rect fill="rgb(0,0,0)" height="177" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="8" x="395" y="159" />
-  <polygon fill="rgb(0,0,0)" points="58,126 183,126 191,134 191,193 58,193 58,126" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" />
-  <polygon fill="rgb(0,0,0)" points="27,233 183,233 191,241 191,300 27,300 27,233" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" />
-  <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="143" y="46" />
-  <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="335" y="46" />
-  <path d="M 204 80 L 204 346" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="8 4" />
-  <rect fill="moccasin" height="177" stroke="rgb(0,0,0)" width="8" x="200" y="153" />
-  <path d="M 396 80 L 396 346" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="8 4" />
-  <rect fill="moccasin" height="177" stroke="rgb(0,0,0)" width="8" x="392" y="153" />
-  <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="140" y="40" />
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="37" x="204.5" y="66">client</text>
-  <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="332" y="40" />
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="37" x="396.5" y="66">server</text>
-  <path d="M 212 153 L 388 153" fill="none" stroke="rgb(0,0,0)" />
-  <polygon fill="rgb(0,0,0)" points="380,149 388,153 380,157" stroke="rgb(0,0,0)" />
-  <polygon fill="rgb(240,248,255)" points="55,120 180,120 188,128 188,187 55,187 55,120" stroke="rgb(0,0,0)" />
-  <path d="M 180 120 L 180 128" fill="none" stroke="rgb(0,0,0)" />
-  <path d="M 180 128 L 188 128" fill="none" stroke="rgb(0,0,0)" />
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="109" x="117.5" y="133">PacketType.REQUEST</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="61" x="93.5" y="146">channel ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="61" x="93.5" y="159">service ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="55" x="90.5" y="172">method ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="43" x="84.5" y="185">payload</text>
-  <path d="M 212 260 L 388 260" fill="none" stroke="rgb(0,0,0)" />
-  <polygon fill="rgb(0,0,0)" points="380,256 388,260 380,264" stroke="rgb(0,0,0)" />
-  <polygon fill="rgb(240,248,255)" points="24,227 180,227 188,235 188,294 24,294 24,227" stroke="rgb(0,0,0)" />
-  <path d="M 180 227 L 180 235" fill="none" stroke="rgb(0,0,0)" />
-  <path d="M 180 235 L 188 235" fill="none" stroke="rgb(0,0,0)" />
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="140" x="102.0" y="240">PacketType.CLIENT_ERROR</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="61" x="62.5" y="253">channel ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="61" x="62.5" y="266">service ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="55" x="59.5" y="279">method ID</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="97" x="80.5" y="292">status=CANCELLED</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="43" x="237.5" y="151">request</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="37" x="234.5" y="258">cancel</text>
-</svg>
diff --git a/pw_snapshot/BUILD.bazel b/pw_snapshot/BUILD.bazel
deleted file mode 100644
index 38816e9..0000000
--- a/pw_snapshot/BUILD.bazel
+++ /dev/null
@@ -1,80 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "uuid",
-    srcs = [
-        "uuid.cc",
-    ],
-    hdrs = [
-        "public/pw_snapshot/uuid.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":metadata_proto",
-        "//pw_bytes",
-        "//pw_protobuf",
-        "//pw_status",
-    ],
-)
-
-proto_library(
-    name = "metadata_proto",
-    srcs = [
-        "pw_snapshot_protos/snapshot_metadata.proto",
-    ],
-)
-
-proto_library(
-    name = "snapshot_proto",
-    srcs = [
-        "pw_snapshot_protos/snapshot.proto",
-    ],
-    deps = [
-        ":metadata_proto",
-    ],
-)
-
-# TODO(pwbug/366): pw_protobuf codegen doesn't work for Bazel yet.
-filegroup(
-    name = "cpp_compile_test",
-    srcs = [
-        "cpp_compile_test.cc",
-    ],
-)
-
-pw_cc_test(
-    name = "uuid_test",
-    srcs = [
-        "uuid_test.cc",
-    ],
-    deps = [
-        ":metadata_proto",
-        ":uuid",
-        "//pw_bytes",
-        "//pw_protobuf",
-        "//pw_result",
-        "//pw_status",
-    ],
-)
diff --git a/pw_snapshot/BUILD.gn b/pw_snapshot/BUILD.gn
deleted file mode 100644
index 4a6c8c1..0000000
--- a/pw_snapshot/BUILD.gn
+++ /dev/null
@@ -1,108 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_protobuf_compiler/proto.gni")
-import("$dir_pw_unit_test/test.gni")
-
-config("public_include_path") {
-  include_dirs = [ "public" ]
-  visibility = [ ":*" ]
-}
-
-pw_source_set("uuid") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_snapshot/uuid.h" ]
-  public_deps = [
-    dir_pw_bytes,
-    dir_pw_result,
-    dir_pw_status,
-  ]
-  deps = [
-    ":metadata_proto.pwpb",
-    dir_pw_protobuf,
-  ]
-  sources = [ "uuid.cc" ]
-}
-
-group("pw_snapshot") {
-  deps = [
-    ":metadata_proto",
-    ":snapshot_proto",
-  ]
-}
-
-# This proto library only contains the snapshot_metadata.proto. Typically this
-# should be a dependency of snapshot-like protos.
-pw_proto_library("metadata_proto") {
-  sources = [ "pw_snapshot_protos/snapshot_metadata.proto" ]
-  strip_prefix = "pw_snapshot_protos"
-  prefix = "pw_snapshot_metadata_proto"
-  deps = [ "$dir_pw_tokenizer:proto" ]
-}
-
-# This proto provides the complete "Snapshot" proto, which depends on various
-# proto libraries throughout Pigweed. This is the proto library to use when
-# building/reading snapshots.
-pw_proto_library("snapshot_proto") {
-  sources = [ "pw_snapshot_protos/snapshot.proto" ]
-  deps = [
-    ":metadata_proto",
-    "$dir_pw_cpu_exception_cortex_m:cpu_state_protos",
-    "$dir_pw_log:protos",
-    "$dir_pw_thread:protos",
-  ]
-}
-
-pw_doc_group("docs") {
-  inputs = [ "images/generic_crash_flow.svg" ]
-  sources = [
-    "design_discussion.rst",
-    "docs.rst",
-    "module_usage.rst",
-    "proto_format.rst",
-    "setup.rst",
-  ]
-}
-
-pw_test_group("tests") {
-  tests = [
-    ":cpp_compile_test",
-    ":uuid_test",
-  ]
-}
-
-# An empty test to ensure the proto libraries compile correctly.
-pw_test("cpp_compile_test") {
-  sources = [ "cpp_compile_test.cc" ]
-  deps = [
-    ":snapshot_proto.pwpb",
-    dir_pw_protobuf,
-  ]
-}
-
-pw_test("uuid_test") {
-  sources = [ "uuid_test.cc" ]
-  deps = [
-    ":metadata_proto.pwpb",
-    ":uuid",
-    dir_pw_bytes,
-    dir_pw_protobuf,
-    dir_pw_result,
-    dir_pw_status,
-  ]
-}
diff --git a/pw_snapshot/CMakeLists.txt b/pw_snapshot/CMakeLists.txt
deleted file mode 100644
index 1ba3355..0000000
--- a/pw_snapshot/CMakeLists.txt
+++ /dev/null
@@ -1,93 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-include($ENV{PW_ROOT}/pw_protobuf_compiler/proto.cmake)
-
-pw_add_module_library(pw_snapshot.uuid
-  HEADERS
-    public/pw_snapshot/uuid.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_polyfill.span
-    pw_polyfill.cstddef
-    pw_result
-    pw_bytes
-  SOURCES
-    uuid.cc
-  PRIVATE_DEPS
-    pw_protobuf
-    pw_snapshot.metadata_proto.pwpb
-)
-
-pw_add_module_library(pw_snapshot
-  PUBLIC_DEPS
-    pw_snapshot.metadata_proto
-    pw_snapshot.snapshot_proto
-)
-
-# This proto library only contains the snapshot_metadata.proto. Typically this
-# should be a dependency of snapshot-like protos.
-pw_proto_library(pw_snapshot.metadata_proto
-  SOURCES
-    pw_snapshot_protos/snapshot_metadata.proto
-  STRIP_PREFIX
-    pw_snapshot_protos
-  PREFIX
-    pw_snapshot_metadata_proto
-  DEPS
-    pw_tokenizer.proto
-)
-
-# This proto provides the complete "Snapshot" proto, which depends on various
-# proto libraries throughout Pigweed. This is the proto library to use when
-# building/reading snapshots.
-pw_proto_library(pw_snapshot.snapshot_proto
-  SOURCES
-    pw_snapshot_protos/snapshot.proto
-  DEPS
-    pw_snapshot.metadata_proto
-    pw_cpu_exception_cortex_m.cpu_state_protos
-    pw_log.protos
-    pw_thread.protos
-)
-
-pw_add_test(pw_snapshot.cpp_compile_test
-  SOURCES
-    cpp_compile_test.cc
-  DEPS
-    pw_protobuf
-    pw_snapshot.snapshot_proto.pwpb
-  GROUPS
-    modules
-    pw_snapshot
-)
-
-pw_add_test(pw_snapshot.uuid_test
-  SOURCES
-    uuid_test.cc
-  DEPS
-    pw_bytes
-    pw_polyfill.cstddef
-    pw_polyfill.span
-    pw_protobuf
-    pw_result
-    pw_snapshot.metadata_proto.pwpb
-    pw_snapshot.uuid
-    pw_status
-  GROUPS
-    modules
-    pw_snapshot
-)
diff --git a/pw_snapshot/OWNERS b/pw_snapshot/OWNERS
deleted file mode 100644
index 307b1de..0000000
--- a/pw_snapshot/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-amontanez@google.com
diff --git a/pw_snapshot/cpp_compile_test.cc b/pw_snapshot/cpp_compile_test.cc
deleted file mode 100644
index f9bec87..0000000
--- a/pw_snapshot/cpp_compile_test.cc
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <span>
-
-#include "gtest/gtest.h"
-#include "pw_protobuf/encoder.h"
-#include "pw_snapshot_protos/snapshot.pwpb.h"
-
-namespace pw::snapshot {
-namespace {
-
-TEST(Status, CompileTest) {
-  constexpr size_t kMaxProtoSize = 256;
-  std::byte encode_buffer[kMaxProtoSize];
-  std::byte submessage_buffer[kMaxProtoSize];
-
-  stream::MemoryWriter writer(encode_buffer);
-  Snapshot::StreamEncoder snapshot_encoder(writer, submessage_buffer);
-  {
-    Metadata::StreamEncoder metadata_encoder =
-        snapshot_encoder.GetMetadataEncoder();
-    metadata_encoder
-        .WriteReason(
-            std::as_bytes(std::span("It just died, I didn't do anything")))
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    metadata_encoder.WriteFatal(true)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    metadata_encoder.WriteProjectName(std::as_bytes(std::span("smart-shoe")))
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    metadata_encoder.WriteDeviceName(std::as_bytes(std::span("smart-shoe-p1")))
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-  }
-  ASSERT_TRUE(snapshot_encoder.status().ok());
-}
-
-}  // namespace
-}  // namespace pw::snapshot
diff --git a/pw_snapshot/design_discussion.rst b/pw_snapshot/design_discussion.rst
deleted file mode 100644
index 5113846..0000000
--- a/pw_snapshot/design_discussion.rst
+++ /dev/null
@@ -1,57 +0,0 @@
-.. _module-pw_snapshot-design_discussion:
-
-=================
-Design Discussion
-=================
-There were a handful of key requirements going into the design of pw_snapshot:
-
-* **Pre-established file format** - Building and maintaining tooling to support
-  parsing binary snapshot data is a high maintenance burden that detracts from
-  the appeal of a pre-existing widely known/supported format.
-* **Incremental writing** - Needing to build an entire snapshot before
-  committing it as a finished file is a big limitation on embedded devices where
-  RAM is often very constrained. It is important that a snapshot can be built in
-  smaller in-memory segments that can be committed incrementally to a larger
-  sink (e.g. UART, off-chip flash).
-* **Extensible** - Pigweed doesn't know everything users might want to capture
-  in a snapshot. It's important that users have ways to include their own
-  information into snapshots with minimal friction.
-* **Relatively compact** - It's important that snapshots can contain useful
-  information even when they are limited to a few hundred bytes in size.
-
-Why Proto?
-==========
-Protobufs are widely used and supported across many languages and platforms.
-This greatly reduces the encode/decode tooling maintenance introduced by using
-custom or unstructured formats. While using a format like JSON provides
-similarly wide tooling support, encoding the same information as a proto
-significantly reduces the final file size.
-
-While protobuffer messages aren't truly streamable (i.e. can be written without
-any intermediate buffers) due to how message nesting works, a large message can
-be incrementally written as long as there's enough buffer space for encoding the
-largest single sub-message in the proto.
-
-Why overlay multiple protos?
-============================
-Proto 2 supported a feature called "extensions" that explicitly allowed this
-behavior. While proto 3 removed this feature, it doesn't disallow the old
-behavior of serializing two 'overlayed' protos to the same data stream. Proto 3
-recommends using an "Any" proto instead of extensions, as it is more explicit
-and eliminates the issue of collisions in proto messages. Unfortunately, proto
-'Any' messages introduce unacceptable overhead. For a single integer that would
-encode to a few bytes using extensions, an Any submessage quickly expands to
-tens of bytes.
-
-pw_snapshot's proto format takes advantage of "extensions" from proto 2 without
-explicitly relying on the feature. To reduce the risk of colissions and maximize
-encoding efficiency, certain ranges are reserved to allow Pigweed to grow while
-ensuring downstream customers have equivalent flexibility when using the
-Snapshot proto format.
-
-Why no file header?
-===================
-Right now it's assumed that anything that is storing or transferring a
-serialized snapshot implicitly tracks its size (and a checksum, if desired).
-While a container format might be introduced independently, pw_snapshot focuses
-on treating an encoded snapshot as raw serialized proto data.
diff --git a/pw_snapshot/docs.rst b/pw_snapshot/docs.rst
deleted file mode 100644
index dd4c3e9..0000000
--- a/pw_snapshot/docs.rst
+++ /dev/null
@@ -1,63 +0,0 @@
-.. _module-pw_snapshot:
-
-===========
-pw_snapshot
-===========
-
-.. warning::
-
-  This module is unstable and under active development. The snapshot proto
-  format may see breaking changes as it stabilizes.
-
-``pw_snapshot`` provides a storage format and associated tooling for capturing a
-device's system state at a given point in time for analysis at a later time.
-This is particularly useful for capturing information at crash time to provide
-context to the cause of the crash. Outside of crash reporting, snapshots can be
-used to debug anomalies that don't result in crashes by treating snapshotting as
-a heavyweight alternative to tracing, logging-based dump commands, or other
-on-demand system state capturing.
-
-
-.. toctree::
-  :maxdepth: 1
-
-  setup
-  module_usage
-  proto_format
-  design_discussion
-
-------------------
-Life of a Snapshot
-------------------
-A "snapshot" is just a `proto message
-<https://cs.opensource.google/pigweed/pigweed/+/HEAD:pw_snapshot/pw_snapshot_protos/snapshot.proto>`_
-with many optional fields that describe a device's state at the time the
-snapshot was captured. The serialized proto can then be stored and transfered
-like a file so it can be analyzed at a later time.
-
-#. **Snapshot capture triggered** - The device encounters a condition that
-   indicates a snapshot should be captured. This could be through a crash
-   handler, or through other developer-specified entry points.
-#. **Device "pauses"** - In order to capture system state, the device must
-   temporarily disable the thread scheduler and regular servicing of interrupts
-   to prevent the system state from churning while it is captured.
-#. **Snapshot captured** - The device collects information throughout the
-   system through a project-provided snapshot collection logic flow. This data
-   is stored as a serialized Snapshot proto message for later retrieval.
-#. **Device resumes** - After a snapshot is stored, the device resumes normal
-   execution. In a crash handler, the device will usually reboot instead of
-   returning to normal execution.
-#. **Snapshot retrieved from device** - During normal device operation, stored
-   snapshots are retrieved from a device by a client that is interested in
-   analyzing the snapshot, or forwarding it elsewhere to be analyzed.
-#. **Snapshot analyzed** - Finally, analysis tooling is run on the captured
-   snapshot proto to produce human readable dumps (akin to a crash report).
-   Alternatively, the data can be ingested by a server to act as a cloud crash
-   reporting endpoint. The structured form of a snapshot enables common
-   cloud-based crash reporting needs like version filtering, crash signatures,
-   de-duplication, and binary-matched symbolization.
-
-While Pigweed provides libraries for each part of a snapshot's lifecycle, the
-glue that puts all these pieces together is project specific. Please see the
-section on :ref:`Setting up a Snapshot Pipeline<module-pw_snapshot-setup>` for
-more information on how to bring up snapshot support for your project.
diff --git a/pw_snapshot/images/generic_crash_flow.svg b/pw_snapshot/images/generic_crash_flow.svg
deleted file mode 100644
index 8f71890..0000000
--- a/pw_snapshot/images/generic_crash_flow.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:lucid="lucid" width="741.39" height="1221.75"><g transform="translate(0 -118.25)" lucid:page-tab-id="0_0"><path d="M148 180c17.67 0 32 17.9 32 40s-14.33 40-32 40H52c-17.67 0-32-17.9-32-40s14.33-40 32-40z" stroke="#5e5e5e" stroke-width="3" fill="#fff"/><use xlink:href="#a" transform="matrix(1,0,0,1,25,185) translate(1.018518518518519 39.65277777777778)"/><use xlink:href="#b" transform="matrix(1,0,0,1,25,185) translate(53.98148148148148 39.65277777777778)"/><path d="M348 180c17.67 0 32 17.9 32 40s-14.33 40-32 40h-96c-17.67 0-32-17.9-32-40s14.33-40 32-40z" stroke="#5e5e5e" stroke-width="3" fill="#fff"/><use xlink:href="#c" transform="matrix(1,0,0,1,225,185) translate(10.308641975308646 39.65277777777778)"/><use xlink:href="#d" transform="matrix(1,0,0,1,225,185) translate(79.38271604938272 39.65277777777778)"/><path d="M20 311.33c0-3.3 2.7-6 6-6h148c3.3 0 6 2.7 6 6V374c0 3.3-2.7 6-6 6H26c-3.3 0-6-2.7-6-6z" stroke="#5e5e5e" stroke-width="3" fill="#fff"/><use xlink:href="#e" transform="matrix(1,0,0,1,32,317.33333333333337) translate(31.055555555555557 17.77777777777778)"/><use xlink:href="#f" transform="matrix(1,0,0,1,32,317.33333333333337) translate(21.796296296296298 44.44444444444444)"/><path d="M220 311.33c0-3.3 2.7-6 6-6h148c3.3 0 6 2.7 6 6V374c0 3.3-2.7 6-6 6H226c-3.3 0-6-2.7-6-6z" stroke="#5e5e5e" stroke-width="3" fill="#fff"/><use xlink:href="#e" transform="matrix(1,0,0,1,232,317.33333333333326) translate(31.055555555555557 17.77777777777778)"/><use xlink:href="#f" transform="matrix(1,0,0,1,232,317.33333333333326) translate(21.796296296296298 44.44444444444444)"/><path d="M100 262.5v22.83" stroke="#5e5e5e" stroke-width="2" fill="none"/><path d="M101 262.53h-2v-1.03h2z" fill="#5e5e5e"/><path d="M100 300.6l-4.64-14.27h9.28z" stroke="#5e5e5e" stroke-width="2" fill="#5e5e5e"/><path d="M300 262.5v22.83" stroke="#5e5e5e" stroke-width="2" fill="none"/><path d="M301 262.53h-2v-1.03h2z" fill="#5e5e5e"/><path d="M300 300.6l-4.64-14.27h9.28z" stroke="#5e5e5e" stroke-width="2" fill="#5e5e5e"/><path d="M100 382.5V434c0 3.3 2.7 6 6 6h88c3.3 0 6 2.7 6 6v34" stroke="#5e5e5e" stroke-width="2" fill="none"/><path d="M101 382.53h-2v-1.03h2z" fill="#5e5e5e"/><path d="M200 495.26L195.36 481h9.28z" stroke="#5e5e5e" stroke-width="2" fill="#5e5e5e"/><path d="M300 382.5V434c0 3.3-2.7 6-6 6h-88c-3.3 0-6 2.7-6 6v34" stroke="#5e5e5e" stroke-width="2" fill="none"/><path d="M301 382.53h-2v-1.03h2z" fill="#5e5e5e"/><path d="M200 495.26L195.36 481h9.28z" stroke="#5e5e5e" stroke-width="2" fill="#5e5e5e"/><path d="M248 500c17.67 0 32 17.9 32 40s-14.33 40-32 40h-96c-17.67 0-32-17.9-32-40s14.33-40 32-40z" stroke="#5e5e5e" stroke-width="3" fill="#fff"/><use xlink:href="#g" transform="matrix(1,0,0,1,125,505) translate(5.375370370370357 26.52777777777778)"/><use xlink:href="#h" transform="matrix(1,0,0,1,125,505) translate(70.68401234567901 26.52777777777778)"/><use xlink:href="#i" transform="matrix(1,0,0,1,125,505) translate(47.87537037037037 53.19444444444445)"/><path d="M195.54 644c2.46-2.2 6.46-2.2 8.92 0l91.08 82c2.46 2.2 2.46 5.8 0 8l-91.08 82c-2.46 2.2-6.46 2.2-8.92 0l-91.08-82c-2.46-2.2-2.46-5.8 0-8z" stroke="#5e5e5e" stroke-width="3" fill="#fff"/><use xlink:href="#j" transform="matrix(1,0,0,1,105,645) translate(37.628456790123444 65.59027777777777)"/><use xlink:href="#k" transform="matrix(1,0,0,1,105,645) translate(85.71487654320987 65.59027777777777)"/><use xlink:href="#l" transform="matrix(1,0,0,1,105,645) translate(37.011172839506166 92.25694444444446)"/><use xlink:href="#m" transform="matrix(1,0,0,1,105,645) translate(97.44327160493827 92.25694444444446)"/><use xlink:href="#n" transform="matrix(1,0,0,1,105,645) translate(48.73956790123456 118.92361111111111)"/><path d="M474.13 918.88l3.42.93 2.53 2.54.92 3.4v76.43h-2v-76.15l-.7-2.65-1.8-1.77-2.63-.72H355.3v-2zm-272.42-2.5l1.8 1.8 2.63.7H324.7v2H205.86l-3.42-.92-2.53-2.53-.92-3.4v-93.94h2v93.65z" fill="#5e5e5e"/><path d="M200.6 819.15l.4-.1v1.07h-2v-1.1zM480 1017.43l-4.64-14.26h9.28z" fill="#5e5e5e"/><path d="M480 1020.67l-6-18.5h12zm-3.26-16.5l3.26 10.03 3.26-10.03z" fill="#5e5e5e"/><use xlink:href="#o" transform="matrix(1,0,0,1,324.69135802469134,909.2158999385243) translate(0 14.222222222222223)"/><path d="M561.4 176.75c0-3.3 2.68-6 6-6h148c3.3 0 6 2.7 6 6v86.5c0 3.3-2.7 6-6 6h-148c-3.32 0-6-2.7-6-6z" stroke="#5e5e5e" stroke-width="3" fill="#fff"/><use xlink:href="#p" transform="matrix(1,0,0,1,573.3888749604126,182.75) translate(28 17.77777777777778)"/><use xlink:href="#q" transform="matrix(1,0,0,1,573.3888749604126,182.75) translate(16.271604938271608 44.44444444444444)"/><use xlink:href="#r" transform="matrix(1,0,0,1,573.3888749604126,182.75) translate(32.25925925925926 71.11111111111111)"/><path d="M200 582.5v39.9" stroke="#5e5e5e" stroke-width="2" fill="none"/><path d="M201 582.53h-2v-1.03h2z" fill="#5e5e5e"/><path d="M200 637.67l-4.64-14.27h9.28z" stroke="#5e5e5e" stroke-width="2" fill="#5e5e5e"/><path d="M428 1240c17.67 0 32 17.9 32 40s-14.33 40-32 40h-96c-17.67 0-32-17.9-32-40s14.33-40 32-40z" stroke="#5e5e5e" stroke-width="3" fill="#fff"/><use xlink:href="#s" transform="matrix(1,0,0,1,305,1245) translate(46.01851851851852 39.65277777777778)"/><path d="M628 1240c17.67 0 32 17.9 32 40s-14.33 40-32 40h-96c-17.67 0-32-17.9-32-40s14.33-40 32-40z" stroke="#5e5e5e" stroke-width="3" fill="#fff"/><use xlink:href="#t" transform="matrix(1,0,0,1,505,1245) translate(15.869197530864193 24.34027777777778)"/><use xlink:href="#u" transform="matrix(1,0,0,1,505,1245) translate(92.22722222222222 24.34027777777778)"/><use xlink:href="#v" transform="matrix(1,0,0,1,505,1245) translate(2.9988271604938266 51.00694444444445)"/><use xlink:href="#w" transform="matrix(1,0,0,1,505,1245) translate(55.900061728395066 51.00694444444445)"/><use xlink:href="#x" transform="matrix(1,0,0,1,505,1245) translate(87.9370987654321 51.00694444444445)"/><path d="M475.2 1023.6c2.65-2 6.95-2 9.6 0l70.4 52.8c2.65 2 2.65 5.2 0 7.2l-70.4 52.8c-2.65 2-6.95 2-9.6 0l-70.4-52.8c-2.65-2-2.65-5.2 0-7.2z" stroke="#5e5e5e" stroke-width="3" fill="#fff"/><use xlink:href="#y" transform="matrix(1,0,0,1,405,1025) translate(39.85067901234567 59.02777777777778)"/><path d="M415.1 1189.9h-28.14l-2.64.73-1.78 1.77-.7 2.65V1220h-2v-25.22l.9-3.4 2.54-2.54 3.42-.93h28.4zm65.9-6.85l-.92 3.4-2.53 2.54-3.42.9h-28.4v-2h28.14l2.64-.7 1.8-1.78.7-2.64v-42.45h2z" fill="#5e5e5e"/><path d="M481 1139.35v1h-2v-1.04zM380.83 1235.26L376.2 1221h9.26z" fill="#5e5e5e"/><path d="M380.83 1238.5l-6-18.5h12zm-3.26-16.5l3.26 10.03 3.26-10.03z" fill="#5e5e5e"/><use xlink:href="#o" transform="matrix(1,0,0,1,415.10607400656374,1178.2480493152057) translate(0 14.222222222222223)"/><path d="M574.13 1187.9l3.42.94 2.53 2.53.92 3.4V1220h-2v-24.95l-.7-2.65-1.8-1.77-2.63-.72H541.8v-2zm-92.42-2.48l1.8 1.78 2.63.7h32.07v2h-32.33l-3.42-.9-2.53-2.54-.92-3.4v-42.73h2v42.45z" fill="#5e5e5e"/><path d="M481 1139.35v1h-2v-1.04zM580 1235.26l-4.64-14.26h9.28z" fill="#5e5e5e"/><path d="M580 1238.5l-6-18.5h12zm-3.26-16.5l3.26 10.03 3.26-10.03z" fill="#5e5e5e"/><use xlink:href="#z" transform="matrix(1,0,0,1,518.1975308641976,1178.2480493152057) translate(0 14.222222222222223)"/><path d="M561.4 326c0-3.3 2.68-6 6-6h148c3.3 0 6 2.7 6 6v86.5c0 3.3-2.7 6-6 6h-148c-3.32 0-6-2.7-6-6z" stroke="#5e5e5e" stroke-width="3" fill="#fff"/><use xlink:href="#A" transform="matrix(1,0,0,1,573.3888749604126,332) translate(21.827160493827165 17.77777777777778)"/><use xlink:href="#B" transform="matrix(1,0,0,1,573.3888749604126,332) translate(108 17.77777777777778)"/><use xlink:href="#C" transform="matrix(1,0,0,1,573.3888749604126,332) translate(29.79012345679012 44.44444444444444)"/><use xlink:href="#D" transform="matrix(1,0,0,1,573.3888749604126,332) translate(30.99382716049383 71.11111111111111)"/><path d="M641.4 271.75V300" stroke="#5e5e5e" stroke-width="2" fill="none"/><path d="M642.4 271.78h-2v-1.03h2z" fill="#5e5e5e"/><path d="M641.4 315.26L636.74 301h9.27z" stroke="#5e5e5e" stroke-width="2" fill="#5e5e5e"/><path d="M561.4 475.25c0-3.3 2.68-6 6-6h148c3.3 0 6 2.7 6 6v57.25c0 3.3-2.7 6-6 6h-148c-3.32 0-6-2.7-6-6z" stroke="#5e5e5e" stroke-width="3" fill="#fff"/><use xlink:href="#E" transform="matrix(1,0,0,1,573.3888749604126,481.25) translate(24.203703703703702 17.77777777777778)"/><use xlink:href="#F" transform="matrix(1,0,0,1,573.3888749604126,481.25) translate(21.086419753086417 44.44444444444444)"/><path d="M641.4 421v28.25" stroke="#5e5e5e" stroke-width="2" fill="none"/><path d="M642.4 421.03h-2V420h2z" fill="#5e5e5e"/><path d="M641.4 464.5l-4.65-14.25h9.27z" stroke="#5e5e5e" stroke-width="2" fill="#5e5e5e"/><path d="M561.4 596.75c0-3.3 2.68-6 6-6h148c3.3 0 6 2.7 6 6V654c0 3.3-2.7 6-6 6h-148c-3.32 0-6-2.7-6-6z" stroke="#5e5e5e" stroke-width="3" fill="#fff"/><g><use xlink:href="#G" transform="matrix(1,0,0,1,573.3888749604126,602.75) translate(29.851851851851855 17.77777777777778)"/><use xlink:href="#H" transform="matrix(1,0,0,1,573.3888749604126,602.75) translate(12.475308641975317 44.44444444444444)"/><use xlink:href="#I" transform="matrix(1,0,0,1,573.3888749604126,602.75) translate(80.31481481481481 44.44444444444444)"/></g><path d="M641.4 541v29.75" stroke="#5e5e5e" stroke-width="2" fill="none"/><path d="M642.4 541.03h-2V540h2z" fill="#5e5e5e"/><path d="M641.4 586l-4.65-14.25h9.27z" stroke="#5e5e5e" stroke-width="2" fill="#5e5e5e"/><path d="M431 724.13l-.92 3.42-2.53 2.53-3.42.92h-124.2v-2h123.94l2.64-.7 1.8-1.8.7-2.63V380.14h2zm207.94-594.96l2.53 2.53.92 3.42v15.63h-2v-15.37l-.73-2.64-1.77-1.78-2.64-.7H436.13l-2.64.7-1.8 1.78-.7 2.64V358.8h-2V135.13l.92-3.42 2.53-2.53 3.42-.92h199.65z" fill="#5e5e5e"/><path d="M299.94 731h-1.23l.22-1.02-.2-.98h1.22zM641.4 166l-4.65-14.25h9.27z" fill="#5e5e5e"/><path d="M641.4 169.25l-6.02-18.5h12.02zm-3.27-16.5l3.26 10.03 3.25-10.03z" fill="#5e5e5e"/><g><use xlink:href="#z" transform="matrix(1,0,0,1,418.1975308641975,358.80500406968014) translate(0 14.222222222222223)"/></g><path d="M561.4 712.67c0-3.32 2.68-6 6-6h148c3.3 0 6 2.68 6 6v92c0 3.3-2.7 6-6 6h-148c-3.32 0-6-2.7-6-6z" stroke="#5e5e5e" stroke-width="3" fill="#fff"/><g><use xlink:href="#J" transform="matrix(1,0,0,1,573.3888749604126,718.6666666666666) translate(47.660493827160494 17.77777777777778)"/><use xlink:href="#K" transform="matrix(1,0,0,1,573.3888749604126,718.6666666666666) translate(16.209876543209866 44.44444444444444)"/><use xlink:href="#L" transform="matrix(1,0,0,1,573.3888749604126,718.6666666666666) translate(22.382716049382715 71.11111111111111)"/></g><path d="M641.4 662.5v24.16" stroke="#5e5e5e" stroke-width="2" fill="none"/><path d="M642.4 662.53h-2v-1.03h2z" fill="#5e5e5e"/><path d="M641.4 701.93l-4.65-14.27h9.27z" stroke="#5e5e5e" stroke-width="2" fill="#5e5e5e"/><path d="M641.4 813.17V914c0 3.3-2.7 6-6 6H486c-3.3 0-6 2.7-6 6v76.17" stroke="#5e5e5e" stroke-width="2" fill="none"/><path d="M642.4 813.2h-2v-1.03h2z" fill="#5e5e5e"/><path d="M480 1017.43l-4.64-14.26h9.28z" stroke="#5e5e5e" stroke-width="2" fill="#5e5e5e"/><defs><path fill="#333" d="M212-179c-10-28-35-45-73-45-59 0-87 40-87 99 0 60 29 101 89 101 43 0 62-24 78-52l27 14C228-24 195 4 139 4 59 4 22-46 18-125c-6-104 99-153 187-111 19 9 31 26 39 46" id="M"/><path fill="#333" d="M30-248c87 1 191-15 191 75 0 78-77 80-158 76V0H30v-248zm33 125c57 0 124 11 124-50 0-59-68-47-124-48v98" id="N"/><path fill="#333" d="M232-93c-1 65-40 97-104 97C67 4 28-28 28-90v-158h33c8 89-33 224 67 224 102 0 64-133 71-224h33v155" id="O"/><g id="a"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#M"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,15.987654320987653,0)" xlink:href="#N"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,30.80246913580247,0)" xlink:href="#O"/></g><path fill="#333" d="M100-194c63 0 86 42 84 106H49c0 40 14 67 53 68 26 1 43-12 49-29l28 8c-11 28-37 45-77 45C44 4 14-33 15-96c1-61 26-98 85-98zm52 81c6-60-76-77-97-28-3 7-6 17-6 28h103" id="P"/><path fill="#333" d="M141 0L90-78 38 0H4l68-98-65-92h35l48 74 47-74h35l-64 92 68 98h-35" id="Q"/><path fill="#333" d="M96-169c-40 0-48 33-48 73s9 75 48 75c24 0 41-14 43-38l32 2c-6 37-31 61-74 61-59 0-76-41-82-99-10-93 101-131 147-64 4 7 5 14 7 22l-32 3c-4-21-16-35-41-35" id="R"/><path fill="#333" d="M115-194c55 1 70 41 70 98S169 2 115 4C84 4 66-9 55-30l1 105H24l-1-265h31l2 30c10-21 28-34 59-34zm-8 174c40 0 45-34 45-75s-6-73-45-74c-42 0-51 32-51 76 0 43 10 73 51 73" id="S"/><path fill="#333" d="M59-47c-2 24 18 29 38 22v24C64 9 27 4 27-40v-127H5v-23h24l9-43h21v43h35v23H59v120" id="T"/><path fill="#333" d="M24-231v-30h32v30H24zM24 0v-190h32V0H24" id="U"/><path fill="#333" d="M100-194c62-1 85 37 85 99 1 63-27 99-86 99S16-35 15-95c0-66 28-99 85-99zM99-20c44 1 53-31 53-75 0-43-8-75-51-75s-53 32-53 75 10 74 51 75" id="V"/><path fill="#333" d="M117-194c89-4 53 116 60 194h-32v-121c0-31-8-49-39-48C34-167 62-67 57 0H25l-1-190h30c1 10-1 24 2 32 11-22 29-35 61-36" id="W"/><g id="b"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#P"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,12.345679012345679,0)" xlink:href="#Q"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,23.45679012345679,0)" xlink:href="#R"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,34.5679012345679,0)" xlink:href="#P"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,46.913580246913575,0)" xlink:href="#S"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,59.25925925925925,0)" xlink:href="#T"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,65.43209876543209,0)" xlink:href="#U"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,70.30864197530863,0)" xlink:href="#V"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,82.65432098765432,0)" xlink:href="#W"/></g><path fill="#333" d="M205 0l-28-72H64L36 0H1l101-248h38L239 0h-34zm-38-99l-47-123c-12 45-31 82-46 123h93" id="X"/><path fill="#333" d="M135-143c-3-34-86-38-87 0 15 53 115 12 119 90S17 21 10-45l28-5c4 36 97 45 98 0-10-56-113-15-118-90-4-57 82-63 122-42 12 7 21 19 24 35" id="Y"/><path fill="#333" d="M114-163C36-179 61-72 57 0H25l-1-190h30c1 12-1 29 2 39 6-27 23-49 58-41v29" id="Z"/><g id="c"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#X"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,14.814814814814813,0)" xlink:href="#Y"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,25.925925925925924,0)" xlink:href="#Y"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,37.03703703703704,0)" xlink:href="#P"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,49.382716049382715,0)" xlink:href="#Z"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,56.72839506172839,0)" xlink:href="#T"/></g><path fill="#333" d="M101-234c-31-9-42 10-38 44h38v23H63V0H32v-167H5v-23h27c-7-52 17-82 69-68v24" id="aa"/><path fill="#333" d="M141-36C126-15 110 5 73 4 37 3 15-17 15-53c-1-64 63-63 125-63 3-35-9-54-41-54-24 1-41 7-42 31l-33-3c5-37 33-52 76-52 45 0 72 20 72 64v82c-1 20 7 32 28 27v20c-31 9-61-2-59-35zM48-53c0 20 12 33 32 33 41-3 63-29 60-74-43 2-92-5-92 41" id="ab"/><path fill="#333" d="M24 0v-261h32V0H24" id="ac"/><path fill="#333" d="M84 4C-5 8 30-112 23-190h32v120c0 31 7 50 39 49 72-2 45-101 50-169h31l1 190h-30c-1-10 1-25-2-33-11 22-28 36-60 37" id="ad"/><g id="d"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#aa"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,6.172839506172839,0)" xlink:href="#ab"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,18.51851851851852,0)" xlink:href="#U"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,23.395061728395063,0)" xlink:href="#ac"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,28.271604938271608,0)" xlink:href="#ad"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,40.617283950617285,0)" xlink:href="#Z"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,47.96296296296296,0)" xlink:href="#P"/></g><path fill="#333" d="M30-248c118-7 216 8 213 122C240-48 200 0 122 0H30v-248zM63-27c89 8 146-16 146-99s-60-101-146-95v194" id="ae"/><path fill="#333" d="M115-194c53 0 69 39 70 98 0 66-23 100-70 100C84 3 66-7 56-30L54 0H23l1-261h32v101c10-23 28-34 59-34zm-8 174c40 0 45-34 45-75 0-40-5-75-45-74-42 0-51 32-51 76 0 43 10 73 51 73" id="af"/><g id="e"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#ae"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,15.987654320987653,0)" xlink:href="#U"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,20.864197530864196,0)" xlink:href="#Y"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,31.975308641975307,0)" xlink:href="#ab"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,44.32098765432099,0)" xlink:href="#af"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,56.666666666666664,0)" xlink:href="#ac"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,61.54320987654321,0)" xlink:href="#P"/></g><g id="f"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#U"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,4.876543209876543,0)" xlink:href="#W"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,17.22222222222222,0)" xlink:href="#T"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,23.39506172839506,0)" xlink:href="#P"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,35.74074074074074,0)" xlink:href="#Z"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,43.08641975308642,0)" xlink:href="#Z"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,50.432098765432094,0)" xlink:href="#ad"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,62.77777777777777,0)" xlink:href="#S"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,75.12345679012346,0)" xlink:href="#T"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,81.29629629629629,0)" xlink:href="#Y"/></g><path fill="#333" d="M106-169C34-169 62-67 57 0H25v-261h32l-1 103c12-21 28-36 61-36 89 0 53 116 60 194h-32v-121c2-32-8-49-39-48" id="ag"/><g id="g"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#M"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,15.987654320987653,0)" xlink:href="#Z"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,23.333333333333332,0)" xlink:href="#ab"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,35.67901234567901,0)" xlink:href="#Y"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,46.79012345679013,0)" xlink:href="#ag"/></g><path fill="#333" d="M85-194c31 0 48 13 60 33l-1-100h32l1 261h-30c-2-10 0-23-3-31C134-8 116 4 85 4 32 4 16-35 15-94c0-66 23-100 70-100zm9 24c-40 0-46 34-46 75 0 40 6 74 45 74 42 0 51-32 51-76 0-42-9-74-50-73" id="ah"/><g id="h"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#ag"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,12.345679012345679,0)" xlink:href="#ab"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,24.691358024691358,0)" xlink:href="#W"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,37.03703703703704,0)" xlink:href="#ah"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,49.382716049382715,0)" xlink:href="#ac"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,54.25925925925926,0)" xlink:href="#P"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,66.60493827160494,0)" xlink:href="#Z"/></g><path fill="#333" d="M177-190C167-65 218 103 67 71c-23-6-38-20-44-43l32-5c15 47 100 32 89-28v-30C133-14 115 1 83 1 29 1 15-40 15-95c0-56 16-97 71-98 29-1 48 16 59 35 1-10 0-23 2-32h30zM94-22c36 0 50-32 50-73 0-42-14-75-50-75-39 0-46 34-46 75s6 73 46 73" id="ai"/><g id="i"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#af"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,12.345679012345679,0)" xlink:href="#P"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,24.691358024691358,0)" xlink:href="#ai"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,37.03703703703704,0)" xlink:href="#U"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,41.91358024691358,0)" xlink:href="#W"/></g><path fill="#333" d="M240 0l2-218c-23 76-54 145-80 218h-23L58-218 59 0H30v-248h44l77 211c21-75 51-140 76-211h43V0h-30" id="aj"/><g id="j"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#aj"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,18.456790123456788,0)" xlink:href="#ab"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,30.80246913580247,0)" xlink:href="#Q"/></g><g id="k"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#W"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,12.345679012345679,0)" xlink:href="#P"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,24.691358024691358,0)" xlink:href="#Y"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,35.80246913580247,0)" xlink:href="#T"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,41.97530864197531,0)" xlink:href="#P"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,54.32098765432099,0)" xlink:href="#ah"/></g><g id="l"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#R"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,11.11111111111111,0)" xlink:href="#Z"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,18.45679012345679,0)" xlink:href="#ab"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,30.80246913580247,0)" xlink:href="#Y"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,41.91358024691358,0)" xlink:href="#ag"/></g><g id="m"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#ah"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,12.345679012345679,0)" xlink:href="#P"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,24.691358024691358,0)" xlink:href="#S"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,37.03703703703704,0)" xlink:href="#T"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,43.20987654320988,0)" xlink:href="#ag"/></g><path fill="#333" d="M103-251c84 0 111 97 45 133-19 10-37 24-39 52H78c0-63 77-55 77-114 0-30-21-42-52-43-32 0-53 17-56 46l-32-2c7-45 34-72 88-72zM77 0v-35h34V0H77" id="ak"/><g id="n"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#Z"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,7.345679012345679,0)" xlink:href="#P"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,19.691358024691358,0)" xlink:href="#ab"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,32.03703703703704,0)" xlink:href="#R"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,43.14814814814815,0)" xlink:href="#ag"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,55.49382716049383,0)" xlink:href="#P"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,67.8395061728395,0)" xlink:href="#ah"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,80.18518518518519,0)" xlink:href="#ak"/></g><path fill="#333" d="M146-102V0H94v-102L6-248h54l60 105 60-105h54" id="al"/><path fill="#333" d="M185-48c-13 30-37 53-82 52C43 2 14-33 14-96s30-98 90-98c62 0 83 45 84 108H66c0 31 8 55 39 56 18 0 30-7 34-22zm-45-69c5-46-57-63-70-21-2 6-4 13-4 21h74" id="am"/><path fill="#333" d="M137-138c1-29-70-34-71-4 15 46 118 7 119 86 1 83-164 76-172 9l43-7c4 19 20 25 44 25 33 8 57-30 24-41C81-84 22-81 20-136c-2-80 154-74 161-7" id="an"/><g id="o"><use transform="matrix(0.04938271604938272,0,0,0.04938271604938272,0,0)" xlink:href="#al"/><use transform="matrix(0.04938271604938272,0,0,0.04938271604938272,10.864197530864198,0)" xlink:href="#am"/><use transform="matrix(0.04938271604938272,0,0,0.04938271604938272,20.74074074074074,0)" xlink:href="#an"/></g><path fill="#333" d="M33 0v-248h34V0H33" id="ao"/><path fill="#333" d="M9 0v-24l116-142H16v-24h144v24L44-24h123V0H9" id="ap"/><g id="p"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#ao"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,6.172839506172839,0)" xlink:href="#W"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,18.51851851851852,0)" xlink:href="#U"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,23.395061728395063,0)" xlink:href="#T"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,29.567901234567902,0)" xlink:href="#U"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,34.44444444444444,0)" xlink:href="#ab"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,46.79012345679012,0)" xlink:href="#ac"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,51.666666666666664,0)" xlink:href="#U"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,56.54320987654321,0)" xlink:href="#ap"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,67.65432098765432,0)" xlink:href="#P"/></g><path fill="#333" d="M16-82v-28h88v28H16" id="aq"/><path fill="#333" d="M210-169c-67 3-38 105-44 169h-31v-121c0-29-5-50-35-48C34-165 62-65 56 0H25l-1-190h30c1 10-1 24 2 32 10-44 99-50 107 0 11-21 27-35 58-36 85-2 47 119 55 194h-31v-121c0-29-5-49-35-48" id="ar"/><g id="q"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#R"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,11.11111111111111,0)" xlink:href="#Z"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,18.45679012345679,0)" xlink:href="#ab"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,30.80246913580247,0)" xlink:href="#Y"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,41.91358024691358,0)" xlink:href="#ag"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,54.25925925925926,0)" xlink:href="#aq"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,61.60493827160494,0)" xlink:href="#T"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,67.77777777777777,0)" xlink:href="#U"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,72.65432098765432,0)" xlink:href="#ar"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,91.1111111111111,0)" xlink:href="#P"/></g><g id="r"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#ac"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,4.876543209876543,0)" xlink:href="#V"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,17.22222222222222,0)" xlink:href="#ai"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,29.5679012345679,0)" xlink:href="#ai"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,41.913580246913575,0)" xlink:href="#U"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,46.79012345679012,0)" xlink:href="#W"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,59.1358024691358,0)" xlink:href="#ai"/></g><path fill="#333" d="M233-177c-1 41-23 64-60 70L243 0h-38l-65-103H63V0H30v-248c88 3 205-21 203 71zM63-129c60-2 137 13 137-47 0-61-80-42-137-45v92" id="as"/><g id="s"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#as"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,15.987654320987653,0)" xlink:href="#P"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,28.333333333333332,0)" xlink:href="#Y"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,39.44444444444444,0)" xlink:href="#P"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,51.79012345679012,0)" xlink:href="#T"/></g><g id="t"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#ao"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,6.172839506172839,0)" xlink:href="#W"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,18.51851851851852,0)" xlink:href="#U"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,23.395061728395063,0)" xlink:href="#aa"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,29.567901234567902,0)" xlink:href="#U"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,34.44444444444444,0)" xlink:href="#W"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,46.79012345679012,0)" xlink:href="#U"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,51.666666666666664,0)" xlink:href="#T"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,57.839506172839506,0)" xlink:href="#P"/></g><g id="u"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#ac"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,4.876543209876543,0)" xlink:href="#V"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,17.22222222222222,0)" xlink:href="#V"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,29.5679012345679,0)" xlink:href="#S"/></g><path fill="#333" d="M87 75C49 33 22-17 22-94c0-76 28-126 65-167h31c-38 41-64 92-64 168S80 34 118 75H87" id="at"/><path fill="#333" d="M206 0h-36l-40-164L89 0H53L-1-190h32L70-26l43-164h34l41 164 42-164h31" id="au"/><g id="v"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#at"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,7.345679012345679,0)" xlink:href="#au"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,23.333333333333332,0)" xlink:href="#ab"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,35.67901234567901,0)" xlink:href="#U"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,40.55555555555556,0)" xlink:href="#T"/></g><g id="w"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#aa"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,6.172839506172839,0)" xlink:href="#V"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,18.51851851851852,0)" xlink:href="#Z"/></g><path fill="#333" d="M185-189c-5-48-123-54-124 2 14 75 158 14 163 119 3 78-121 87-175 55-17-10-28-26-33-46l33-7c5 56 141 63 141-1 0-78-155-14-162-118-5-82 145-84 179-34 5 7 8 16 11 25" id="av"/><path fill="#333" d="M266 0h-40l-56-210L115 0H75L2-248h35L96-30l15-64 43-154h32l59 218 59-218h35" id="aw"/><path fill="#333" d="M33-261c38 41 65 92 65 168S71 34 33 75H2C39 34 66-17 66-93S39-220 2-261h31" id="ax"/><g id="x"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#av"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,14.814814814814813,0)" xlink:href="#aw"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,35.74074074074073,0)" xlink:href="#ae"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,51.728395061728385,0)" xlink:href="#ax"/></g><g id="y"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#as"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,15.987654320987653,0)" xlink:href="#P"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,28.333333333333332,0)" xlink:href="#Y"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,39.44444444444444,0)" xlink:href="#P"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,51.79012345679012,0)" xlink:href="#T"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,57.96296296296296,0)" xlink:href="#ak"/></g><path fill="#333" d="M175 0L67-191c6 58 2 128 3 191H24v-248h59L193-55c-6-58-2-129-3-193h46V0h-61" id="ay"/><path fill="#333" d="M110-194c64 0 96 36 96 99 0 64-35 99-97 99-61 0-95-36-95-99 0-62 34-99 96-99zm-1 164c35 0 45-28 45-65 0-40-10-65-43-65-34 0-45 26-45 65 0 36 10 65 43 65" id="az"/><g id="z"><use transform="matrix(0.04938271604938272,0,0,0.04938271604938272,0,0)" xlink:href="#ay"/><use transform="matrix(0.04938271604938272,0,0,0.04938271604938272,12.790123456790125,0)" xlink:href="#az"/></g><g id="A"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#ao"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,6.172839506172839,0)" xlink:href="#W"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,18.51851851851852,0)" xlink:href="#U"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,23.395061728395063,0)" xlink:href="#T"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,29.567901234567902,0)" xlink:href="#U"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,34.44444444444444,0)" xlink:href="#ab"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,46.79012345679012,0)" xlink:href="#ac"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,51.666666666666664,0)" xlink:href="#U"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,56.54320987654321,0)" xlink:href="#ap"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,67.65432098765432,0)" xlink:href="#P"/></g><path fill="#333" d="M0 4l72-265h28L28 4H0" id="aA"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#aA" id="B"/><g id="C"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#ab"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,12.345679012345679,0)" xlink:href="#ac"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,17.22222222222222,0)" xlink:href="#ac"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,22.098765432098766,0)" xlink:href="#V"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,34.44444444444444,0)" xlink:href="#R"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,45.55555555555556,0)" xlink:href="#ab"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,57.901234567901234,0)" xlink:href="#T"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,64.07407407407408,0)" xlink:href="#P"/></g><g id="D"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#Y"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,11.11111111111111,0)" xlink:href="#T"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,17.28395061728395,0)" xlink:href="#V"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,29.629629629629626,0)" xlink:href="#Z"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,36.9753086419753,0)" xlink:href="#ab"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,49.32098765432098,0)" xlink:href="#ai"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,61.66666666666666,0)" xlink:href="#P"/></g><g id="E"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#N"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,14.814814814814813,0)" xlink:href="#V"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,27.160493827160494,0)" xlink:href="#S"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,39.50617283950617,0)" xlink:href="#ad"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,51.85185185185185,0)" xlink:href="#ac"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,56.72839506172839,0)" xlink:href="#ab"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,69.07407407407408,0)" xlink:href="#T"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,75.24691358024691,0)" xlink:href="#P"/></g><g id="F"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#av"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,14.814814814814813,0)" xlink:href="#W"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,27.160493827160494,0)" xlink:href="#ab"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,39.50617283950617,0)" xlink:href="#S"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,51.85185185185185,0)" xlink:href="#Y"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,62.96296296296296,0)" xlink:href="#ag"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,75.30864197530865,0)" xlink:href="#V"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,87.65432098765433,0)" xlink:href="#T"/></g><g id="G"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#M"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,15.987654320987653,0)" xlink:href="#V"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,28.333333333333332,0)" xlink:href="#ar"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,46.79012345679012,0)" xlink:href="#ar"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,65.24691358024691,0)" xlink:href="#U"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,70.12345679012346,0)" xlink:href="#T"/></g><g id="H"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#Y"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,11.11111111111111,0)" xlink:href="#T"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,17.28395061728395,0)" xlink:href="#V"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,29.629629629629626,0)" xlink:href="#Z"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,36.9753086419753,0)" xlink:href="#P"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,49.32098765432098,0)" xlink:href="#ah"/></g><g id="I"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#ah"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,12.345679012345679,0)" xlink:href="#ab"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,24.691358024691358,0)" xlink:href="#T"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,30.864197530864196,0)" xlink:href="#ab"/></g><g id="J"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#as"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,15.987654320987653,0)" xlink:href="#ad"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,28.333333333333332,0)" xlink:href="#W"/></g><g id="K"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#S"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,12.345679012345679,0)" xlink:href="#V"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,24.691358024691358,0)" xlink:href="#Y"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,35.80246913580247,0)" xlink:href="#T"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,41.97530864197531,0)" xlink:href="#aq"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,49.32098765432099,0)" xlink:href="#R"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,60.4320987654321,0)" xlink:href="#Z"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,67.77777777777779,0)" xlink:href="#ab"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,80.12345679012347,0)" xlink:href="#Y"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,91.23456790123458,0)" xlink:href="#ag"/></g><path fill="#333" d="M143 0L79-87 56-68V0H24v-261h32v163l83-92h37l-77 82L181 0h-38" id="aB"/><g id="L"><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,0,0)" xlink:href="#R"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,11.11111111111111,0)" xlink:href="#ab"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,23.45679012345679,0)" xlink:href="#ac"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,28.333333333333336,0)" xlink:href="#ac"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,33.20987654320988,0)" xlink:href="#af"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,45.55555555555556,0)" xlink:href="#ab"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,57.901234567901234,0)" xlink:href="#R"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,69.01234567901234,0)" xlink:href="#aB"/><use transform="matrix(0.06172839506172839,0,0,0.06172839506172839,80.12345679012346,0)" xlink:href="#Y"/></g></defs></g></svg>
\ No newline at end of file
diff --git a/pw_snapshot/module_usage.rst b/pw_snapshot/module_usage.rst
deleted file mode 100644
index f020fd7..0000000
--- a/pw_snapshot/module_usage.rst
+++ /dev/null
@@ -1,211 +0,0 @@
-.. _module-pw_snapshot-module_usage:
-
-============
-Module Usage
-============
-Right now, pw_snapshot just dictates a *format*. That means there is no provided
-system information collection integration, underlying storage, or transport
-mechanism to fetch a snapshot from a device. These must be set up independently
-by your project.
-
--------------------
-Building a Snapshot
--------------------
-Even though a Snapshot is just a proto message, the potential size of the proto
-makes it important to consider the encoder.
-
-Nanopb is a popular encoder for embedded devices, it's impractical to use
-with the pw_snapshot proto. Nanopb works by generating in-memory structs that
-represent the protobuf message. Repeated, optional, and variable-length fields
-increase the size of the in-memory struct. The struct representation
-of snapshot-like protos can quickly near 10KB in size. Allocating 10KB
-
-Pigweed's pw_protobuf is a better choice as its design is centered around
-incrementally writing a proto directly to the final wire format. If you only
-write a few fields in a snapshot, you can do so with minimal memory overhead.
-
-.. code-block:: cpp
-
-  #include "pw_bytes/span.h"
-  #include "pw_protobuf/encoder.h"
-  #include "pw_snapshot_protos/snapshot.pwpb.h"
-  #include "pw_status/status.h"
-  #include "pw_stream/stream.h"
-
-  pw::Status EncodeSnapshot(pw::stream::Writer& writer,
-                            pw::ByteSpan submessage_encode_buffer,
-                            const CrashInfo &crash_info) {
-    // Create a snapshot proto encoder.
-    pw::snapshot::Snapshot::StreamEncoder snapshot_encoder(
-        writer, submessage_encode_buffer);
-    {  // This scope is required to handle RAII behavior of the submessage.
-      // Start writing the Metadata submessage.
-      pw::snapshot::Metadata::StreamEncoder metadata_encoder =
-          snapshot_encoder.GetMetadataEncoder();
-      metadata_encoder.WriteReason(EncodeReasonLog(crash_info));
-      metadata_encoder.WriteFatal(true);
-      metadata_encoder.WriteProjectName(std::as_bytes(std::span("smart-shoe")));
-      metadata_encoder.WriteDeviceName(
-          std::as_bytes(std::span("smart-shoe-p1")));
-    }
-    return proto_encoder.status();
-  }
-
--------------------
-Custom Project Data
--------------------
-There are two main ways to add custom project-specific data to a snapshot. Tags
-are the simplest way to capture small snippets of information that require
-no or minimal post-processing. For more complex data, it's usually more
-practical to extend the Snapshot proto.
-
-Tags
-====
-Adding a key/value pair to the tags map is straightforward when using
-pw_protobuf.
-
-.. code-block:: cpp
-
-  {
-    pw::Snapshot::TagsEntry::StreamEncoder tags_encoder =
-        snapshot_encoder.GetTagsEncoder();
-    tags_encoder.WriteKey("BtState");
-    tags_encoder.WriteValue("connected");
-  }
-
-Extending the Proto
-===================
-Extending the Snapshot proto relies on proto behavior details that are explained
-in the :ref:`Snapshot Proto Format<module-pw_snapshot-proto_format>`. Extending
-the snapshot proto is as simple as defining a proto message that **only**
-declares fields with numbers that are reserved by the Snapshot proto for
-downstream projects. When encoding your snapshot, you can then write both the
-upstream Snapshot proto and your project's custom extension proto message to the
-same proto encoder.
-
-The upstream snapshot tooling will ignore any project-specific proto data,
-the proto data can be decoded a second time using a project-specific proto. At
-that point, any handling logic of the project-specific data would have to be
-done as part of project-specific tooling.
-
--------------------
-Analyzing Snapshots
--------------------
-Snapshots can be processed for analysis using the ``pw_snapshot.process`` python
-tool. This tool turns a binary snapshot proto into human readable, actionable
-information. As some snapshot fields may optionally be tokenized, a
-pw_tokenizer database or ELF file with embedded pw_tokenizer tokens may
-optionally be passed to the tool to detokenize applicable fields.
-
-.. code-block:: sh
-
-  # Example invocation, which dumps to stdout by default.
-  $ python -m pw_snapshot.processor path/to/serialized_snapshot.bin
-
-
-          ____ _       __    _____ _   _____    ____  _____ __  ______  ______
-         / __ \ |     / /   / ___// | / /   |  / __ \/ ___// / / / __ \/_  __/
-        / /_/ / | /| / /    \__ \/  |/ / /| | / /_/ /\__ \/ /_/ / / / / / /
-       / ____/| |/ |/ /    ___/ / /|  / ___ |/ ____/___/ / __  / /_/ / / /
-      /_/     |__/|__/____/____/_/ |_/_/  |_/_/    /____/_/ /_/\____/ /_/
-                    /_____/
-
-
-                              ▪▄▄▄ ▄▄▄· ▄▄▄▄▄ ▄▄▄· ▄ ·
-                              █▄▄▄▐█ ▀█ • █▌ ▐█ ▀█ █
-                              █ ▪ ▄█▀▀█   █. ▄█▀▀█ █
-                              ▐▌ .▐█ ▪▐▌ ▪▐▌·▐█ ▪▐▌▐▌
-                              ▀    ▀  ▀ ·  ▀  ▀  ▀ .▀▀
-
-  Device crash cause:
-      ../examples/example_rpc.cc: Assert failed: 1+1 == 42
-
-  Project name:      gShoe
-  Device:            GSHOE-QUANTUM_CORE-REV_0.1
-  Device FW version: QUANTUM_CORE-0.1.325-e4a84b1a
-  FW build UUID:     ad2d39258c1bc487f07ca7e04991a836fdf7d0a0
-  Snapshot UUID:     8481bb12a162164f5c74855f6d94ea1a
-
-  Thread State
-    2 threads running, Main Stack (Handler Mode) active at the time of capture.
-                       ~~~~~~~~~~~~~~~~~~~~~~~~~
-
-  Thread (INTERRUPT_HANDLER): Main Stack (Handler Mode) <-- [ACTIVE]
-  Est CPU usage: unknown
-  Stack info
-    Stack used:   0x2001b000 - 0x2001ae20 (480 bytes)
-    Stack limits: 0x2001b000 - 0x???????? (size unknown)
-
-  Thread (RUNNING): Idle
-  Est CPU usage: unknown
-  Stack info
-    Stack used:   0x2001ac00 - 0x2001ab0c (244 bytes, 47.66%)
-    Stack limits: 0x2001ac00 - 0x2001aa00 (512 bytes)
-
----------------------
-Symbolizing Addresses
----------------------
-The snapshot processor tool has built-in support for symbolization of some data
-embedded into snapshots. Taking advantage of this requires the use of a
-project-provided ``SymbolizerMatcher`` callback. This is used by the snapshot
-processor to understand which ELF file should be used to symbolize which
-snapshot in cases where a snapshot has related snapshots embedded inside of it.
-
-Here's an example implementation that uses the device name:
-
-.. code-block:: py
-
-  # Given a firmware bundle directory, determine the ELF file associated with
-  # the provided snapshot.
-  def _snapshot_symbolizer_matcher(fw_bundle_dir: Path,
-                                   snapshot: snapshot_pb2.Snapshot
-      ) -> Symbolizer:
-      metadata = MetadataProcessor(snapshot.metadata, DETOKENIZER)
-      if metadata.device_name().startswith('GSHOE_MAIN_CORE'):
-          return LlvmSymbolizer(fw_bundle_dir / 'main.elf')
-      if metadata.device_name().startswith('GSHOE_SENSOR_CORE'):
-          return LlvmSymbolizer(fw_bundle_dir / 'sensors.elf')
-      return LlvmSymbolizer()
-
-
-  # A project specific wrapper to decode snapshots that provides a detokenizer
-  # and ElfMatcher.
-  def decode_snapshots(snapshot: bytes, fw_bundle_dir: Path) -> str:
-
-      # This is the actual ElfMatcher, which wraps the helper in a lambda that
-      # captures the passed firmware artifacts directory.
-      matcher: processor.SymbolizerMatcher = (
-          lambda snapshot: _snapshot_symbolizer_matcher(
-              fw_bundle_dir, snapshot))
-      return processor.process_snapshots(snapshot, DETOKENIZER, matcher)
-
--------------
-C++ Utilities
--------------
-
-UUID utilities
-==============
-Snapshot UUIDs are used to uniquely identify snapshots. Pigweed strongly
-recommends using randomly generated data as a snapshot UUID. The
-more entropy and random bits, the lower the probability that two devices will
-produce the same UUID for a snapshot. 16 bytes should be sufficient for most
-projects, so this module provides ``UuidSpan`` and ``ConstUuidSpan`` types that
-can be helpful for referring to UUID-sized byte spans.
-
-Reading a snapshot's UUID
--------------------------
-An in-memory snapshot's UUID may be read using ``ReadUuidFromSnapshot()``.
-
-.. code-block:: cpp
-
-  void NotifyNewSnapshot(ConstByteSpan snapshot) {
-    std::array<std::byte, pw::snapshot::kUuidSizeBytes> uuid;
-    pw::Result<pw::ConstByteSpan> result =
-        pw::snapshot::ReadUuidFromSnapshot(snapshot, uuid);
-    if (!result.ok()) {
-      PW_LOG_ERROR("Failed to read UUID from new snapshot, error code %d",
-                   static_cast<int>(result.status().code()));
-      return;
-    }
-    LogNewSnapshotUuid(result.value());
-  }
diff --git a/pw_snapshot/proto_format.rst b/pw_snapshot/proto_format.rst
deleted file mode 100644
index 14e450d..0000000
--- a/pw_snapshot/proto_format.rst
+++ /dev/null
@@ -1,91 +0,0 @@
-.. _module-pw_snapshot-proto_format:
-
-=====================
-Snapshot Proto Format
-=====================
-The Snapshot proto format depends on proto handling properties that directly
-inform how the proto should be used. If two proto messages use field numbers
-in a mutually exclusive manner, they can be encoded to the same buffer, and
-decoded independently without any errors. An example is illustrated below:
-
-.. code-block::
-
-  // This message uses field numbers 1, 2, and 3.
-  message BasicLog {
-    string message = 1;
-    LogLevel level = 2;
-    int64 timestamp = 3;
-  }
-
-  // This message uses field numbers 16 and 17, which are mutually exclusive
-  // to the numbers used in BasicLog.
-  message ExtendedLog {
-    string file_name = 16;
-    uint32 line_nubmer = 17;
-  }
-
-In the above example, a BasicLog and ExtendedLog can be encoded to the same
-buffer and then be decoded without causing any problems. What breaks
-this is when either of the two messages above are updated to use the same field
-number for different types. This can be ameliorated by specifying reserved
-field number ranges as shown below:
-
-.. code-block::
-
-  message BasicLog {
-    string message = 1;
-    LogLevel level = 2;
-    int64 timestamp = 3;
-
-    // ExtendedLog uses these field numbers. These field numbers should never
-    // be used by BasicLog.
-    reserved 16 to max;
-  }
-
-  message ExtendedLog {
-    // BasicLog uses these field numbers. These field numbers should never
-    // be used by ExtendedLog.
-    reserved 1 to 15;
-
-    string file_name = 16;
-    uint32 line_nubmer = 17;
-  }
-
-This is exactly how the Snapshot proto is set up. While a SnapshotMetadata proto
-message provides a good portion of the valuable snapshot contents, the larger
-Snapshot message specifies field numbers and reserved field ranges to clarify
-which field numbers are left to a project for declaring a mutually exclusive
-project-specific proto message that can be used to augment the data natively
-supported by the upstream proto.
-
---------------------
-Module-Specific Data
---------------------
-Some upstream Pigweed modules provide a ``Snapshot*`` message that overlays the
-main Snapshot message. These messages share a field number with the Snapshot
-proto, which lets modules provide a helper that populates a subset of the
-Snapshot proto without explicitly depending on the Snapshot proto.
-
-Example:
-.. code-block::
-
-  // snapshot.proto
-  message Snapshot {
-    ...
-    // Information about allocated Thread.
-    repeated pw.thread.Thread threads = 18;
-  }
-
-  // thread.proto
-
-  // This message overlays the pw.snapshot.Snapshot proto. It's valid to encode
-  // this message to the same sink that a Snapshot proto is being written to.
-  message SnapshotThread {
-    // Thread information.
-    repeated pw.thread.Thread threads = 18;
-  }
-
-It is **critical** that the SnapshotThread message is in sync with the larger
-Snapshot proto. If the type or field numbers are different, the proto decode
-will fail. If the semantics are different, the decode will likely succeed but
-might provide misleading information.
diff --git a/pw_snapshot/public/pw_snapshot/uuid.h b/pw_snapshot/public/pw_snapshot/uuid.h
deleted file mode 100644
index 9946a36..0000000
--- a/pw_snapshot/public/pw_snapshot/uuid.h
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstddef>
-#include <span>
-
-#include "pw_bytes/span.h"
-#include "pw_result/result.h"
-
-namespace pw::snapshot {
-
-// Snapshot UUIDs are expected to be 128-bit.
-//
-// Note this is not strictly enforced anywhere, this is pure for convenience.
-inline constexpr size_t kUuidSizeBytes = 16;
-
-using UuidSpan = std::span<std::byte, kUuidSizeBytes>;
-using ConstUuidSpan = std::span<const std::byte, kUuidSizeBytes>;
-
-// Reads the snapshot UUID from an in memory snapshot, if present, and returns
-// the subspan of `output` that contains the read snapshot.
-//
-// Returns:
-//   OK - UUID found, status with size indicates number of bytes written to
-//     `output`.
-//   RESOURCE_EXHUASTED - UUID found, but `output` was too small to fit it.
-//   NOT_FOUND - No snapshot UUID found in the provided snapshot.
-Result<ConstByteSpan> ReadUuidFromSnapshot(ConstByteSpan snapshot,
-                                           UuidSpan output);
-
-}  // namespace pw::snapshot
diff --git a/pw_snapshot/pw_snapshot_protos/snapshot.proto b/pw_snapshot/pw_snapshot_protos/snapshot.proto
deleted file mode 100644
index 309dfaa..0000000
--- a/pw_snapshot/pw_snapshot_protos/snapshot.proto
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-syntax = "proto3";
-
-package pw.snapshot;
-
-option java_package = "pw.snapshot.proto";
-option java_outer_classname = "Snapshot";
-
-import "pw_cpu_exception_cortex_m_protos/cpu_state.proto";
-import "pw_log/proto/log.proto";
-import "pw_thread_protos/thread.proto";
-import "pw_snapshot_metadata_proto/snapshot_metadata.proto";
-
-// The Snapshot proto is a list that dictates field numbers that should
-// be used when serializing proto messages into a "Snapshot" that can be
-// ingested by Pigweed's upstream tooling.
-//
-// There are various field number ranges that are marked for "upstream" use,
-// and others that are marked for "pigweed users". This allows a user to
-// define a parallel proto that defines product-specific messages using mutually
-// exclusive field numbers:
-//
-//   MySnapshot {
-//     // Use a project-specific logging proto format.
-//     repeated MyLogFormat = 8;
-//
-//     // Pigweed's snapshot doesn't support my custom RTOS, so write that to
-//     // a field number reserved for downstream projects.
-//     MyCustomRtosInfo = 22;
-//   }
-//
-// Writing both proto messages to the same proto encoder is valid because the
-// field nubmers are mutually exclusive. This prevents collisions that would
-// break a proto decode. The final message will have to be decoded twice; once
-// as a pw.snapshot.Snapshot and once as the project-specific message.
-message Snapshot {
-  repeated pw.log.LogEntry logs = 1;
-
-  // RESERVED FOR PIGWEED. These field numbers are reserved strictly for things
-  // that are very generally useful and high in count. Downstream projects may
-  // NOT write to these fields.
-  // Encodes to a single byte of tag overhead.
-  reserved 2 to 7;
-
-  // RESERVED FOR USERS. These field numbers should be used for writing
-  // project-specific proto messages that repeat in high quantity to reduce
-  // proto encoding overhead. Pigweed must not write to these fields.
-  // Encodes to a single byte of tag overhead.
-  reserved 8 to 15;
-
-  // Note: Proto tags 16-2047 encode with two bytes of overhead.
-  Metadata metadata = 16;
-
-  // Other data that should be highlighted in this crash. This field may have
-  // entries added to it during a decode.
-  map<string, string> tags = 17;
-
-  repeated pw.thread.Thread threads = 18;
-
-  // If a device has multiple cores, it may be useful to collect an associated
-  // snapshot for attached cores when a snapshot collection is triggered on one
-  // core. By embedding one or more snapshots into a snapshot, the snapshots are
-  // considered associated.
-  repeated Snapshot related_snapshots = 19;
-
-  pw.cpu_exception.cortex_m.ArmV7mCpuState armv7m_cpu_state = 20;
-
-  // Platform-specific binary trace data region. Binary trace data is using
-  // pw_trace_tokenized buffer format for stored data.
-  bytes trace_data = 21;
-
-  // RESERVED FOR PIGWEED. Downstream projects may NOT write to these fields.
-  // Encodes to two bytes of tag overhead.
-  reserved 22 to 1031;
-
-  // RESERVED FOR USERS. Encodes to two or more bytes of tag overhead.
-  reserved 1032 to max;
-}
diff --git a/pw_snapshot/pw_snapshot_protos/snapshot_metadata.proto b/pw_snapshot/pw_snapshot_protos/snapshot_metadata.proto
deleted file mode 100644
index dabf49b..0000000
--- a/pw_snapshot/pw_snapshot_protos/snapshot_metadata.proto
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-syntax = "proto3";
-
-package pw.snapshot;
-
-import "pw_tokenizer/proto/options.proto";
-
-option java_package = "pw.snapshot.proto";
-option java_outer_classname = "Snapshot";
-
-message CpuArchitecture {
-  enum Enum {
-    UNKNOWN = 0;
-    ARMV6M = 1;
-    ARMV7M = 2;
-    ARMV8M = 3;
-  }
-}
-
-message Metadata {
-  // A relatively unique descriptive reason for what triggered the snapshot
-  // capture. This should either be human readable text, or tokenized data
-  // (e.g. base-64 encoded or binary data).
-  //
-  // Examples:
-  //   Null-pointer dereference
-  //   [main.cc:22] True is not false!
-  //   STACK_OVERFLOW
-  bytes reason = 1 [(tokenizer.format) = TOKENIZATION_OPTIONAL];
-
-  // Whether or not the snapshot was captured due to a crash of some kind.
-  bool fatal = 2;
-
-  // Project name to assist in identifying where to redirect this snapshot. A
-  // single project might have multiple devices that can produce snapshots.
-  bytes project_name = 3 [(tokenizer.format) = TOKENIZATION_OPTIONAL];
-
-  // Version characters must be alphanumeric, punctuation, and space. This
-  // string is case-sensitive. This should either be human readable text, or
-  // tokenized data.
-  //
-  // Examples:
-  //   "codename-local-[build_id]"
-  //   "codename-release-193"
-  string software_version = 4;
-
-  // UUID associated with the build for the software version.
-  bytes software_build_uuid = 5;
-
-  // String containing the specific device name. This should be as specific as
-  // possible, detailing hardware revision, and distinguishing different cores
-  // in a multi-core device. Snapshots aggregated as related_snapshots should
-  // include information that distinguishes the source of the snapshot. This
-  // should either be human readable text, or tokenized data.
-  //
-  // Examples:
-  //   "propellerhat-evk"
-  //   "gshoe-sensor-core-pvt"
-  //   "alarm-clock-dsp-p1"
-  bytes device_name = 6 [(tokenizer.format) = TOKENIZATION_OPTIONAL];
-
-  // 128-bit UUID for this snapshot, used to help with de-duplication.
-  bytes snapshot_uuid = 7;
-
-  // The architecture of the CPU that generated this report.
-  CpuArchitecture.Enum cpu_arch = 8;
-}
-
-// This message overlays the pw.snapshot.Snapshot proto. It's valid to encode
-// this message to the same sink that a Snapshot proto is being written to.
-message SnapshotBasicInfo {
-  Metadata metadata = 16;
-  map<string, string> tags = 17;
-}
diff --git a/pw_snapshot/py/BUILD.gn b/pw_snapshot/py/BUILD.gn
deleted file mode 100644
index 51cb683..0000000
--- a/pw_snapshot/py/BUILD.gn
+++ /dev/null
@@ -1,76 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/python.gni")
-import("$dir_pw_docgen/docs.gni")
-
-pw_python_package("pw_snapshot_metadata") {
-  generate_setup = {
-    metadata = {
-      name = "pw_snapshot_metadata"
-      version = "0.0.1"
-    }
-  }
-
-  sources = [
-    "pw_snapshot_metadata/__init__.py",
-    "pw_snapshot_metadata/metadata.py",
-  ]
-  python_deps = [
-    "$dir_pw_log_tokenized/py",
-    "$dir_pw_tokenizer/py",
-    "..:metadata_proto.python",
-  ]
-  pylintrc = "$dir_pigweed/.pylintrc"
-}
-
-pw_python_package("pw_snapshot") {
-  generate_setup = {
-    metadata = {
-      name = "pw_snapshot"
-      version = "0.0.1"
-    }
-  }
-  sources = [
-    "generate_example_snapshot.py",
-    "pw_snapshot/__init__.py",
-    "pw_snapshot/processor.py",
-  ]
-  tests = [ "metadata_test.py" ]
-  python_deps = [
-    ":pw_snapshot_metadata",
-    "$dir_pw_build_info/py",
-    "$dir_pw_cpu_exception_cortex_m/py",
-    "$dir_pw_symbolizer/py",
-    "$dir_pw_thread:protos.python",
-    "$dir_pw_thread/py",
-    "$dir_pw_tokenizer/py",
-    "..:snapshot_proto.python",
-  ]
-  pylintrc = "$dir_pigweed/.pylintrc"
-}
-
-pw_python_group("py") {
-  python_deps = [
-    ":pw_snapshot",
-    ":pw_snapshot_metadata",
-  ]
-}
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-  other_deps = [ ":py" ]
-}
diff --git a/pw_snapshot/py/generate_example_snapshot.py b/pw_snapshot/py/generate_example_snapshot.py
deleted file mode 100644
index 11a0b33..0000000
--- a/pw_snapshot/py/generate_example_snapshot.py
+++ /dev/null
@@ -1,84 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Generates an example snapshot useful for updating documentation."""
-
-import argparse
-import sys
-from typing import TextIO
-from pw_snapshot_protos import snapshot_pb2
-from pw_snapshot import processor
-from pw_thread_protos import thread_pb2
-
-
-def _add_threads(snapshot: snapshot_pb2.Snapshot) -> snapshot_pb2.Snapshot:
-    # Build example idle thread.
-    thread = thread_pb2.Thread()
-    thread.name = 'Idle'.encode()
-    thread.stack_start_pointer = 0x2001ac00
-    thread.stack_end_pointer = 0x2001aa00
-    thread.stack_pointer = 0x2001ab0c
-    thread.state = thread_pb2.ThreadState.Enum.RUNNING
-    snapshot.threads.append(thread)
-
-    # Build example interrupt handler thread.
-    thread = thread_pb2.Thread()
-    thread.name = 'Main Stack (Handler Mode)'.encode()
-    thread.active = True
-    thread.stack_start_pointer = 0x2001b000
-    thread.stack_pointer = 0x2001ae20
-    thread.state = thread_pb2.ThreadState.Enum.INTERRUPT_HANDLER
-    snapshot.threads.append(thread)
-
-    return snapshot
-
-
-def _main(out_file: TextIO):
-    snapshot = snapshot_pb2.Snapshot()
-
-    snapshot.metadata.reason = (
-        '■msg♦Assert failed: 1+1 == 42'
-        '■file♦../examples/example_rpc.cc').encode('utf-8')
-    snapshot.metadata.fatal = True
-    snapshot.metadata.project_name = 'gShoe'.encode('utf-8')
-    snapshot.metadata.software_version = 'QUANTUM_CORE-0.1.325-e4a84b1a'
-    snapshot.metadata.software_build_uuid = (
-        b'\xAD\x2D\x39\x25\x8C\x1B\xC4\x87'
-        b'\xF0\x7C\xA7\xE0\x49\x91\xA8\x36'
-        b'\xFD\xF7\xD0\xA0')
-    snapshot.metadata.device_name = 'GSHOE-QUANTUM_CORE-REV_0.1'.encode(
-        'utf-8')
-    snapshot.metadata.snapshot_uuid = (b'\x84\x81\xBB\x12\xA1\x62\x16\x4F'
-                                       b'\x5C\x74\x85\x5F\x6D\x94\xEA\x1A')
-
-    # Add some thread-related info.
-    snapshot = _add_threads(snapshot)
-
-    serialized_snapshot = snapshot.SerializeToString()
-    out_file.write(processor.process_snapshots(serialized_snapshot))
-
-
-def _parse_args():
-    parser = argparse.ArgumentParser(description=__doc__)
-    parser.add_argument('--out-file',
-                        '-o',
-                        type=argparse.FileType('w'),
-                        help='File to output serialized snapshot to.')
-
-    return parser.parse_args()
-
-
-if __name__ == '__main__':
-    _main(**vars(_parse_args()))
-    sys.exit(0)
diff --git a/pw_snapshot/py/metadata_test.py b/pw_snapshot/py/metadata_test.py
deleted file mode 100644
index 3152833..0000000
--- a/pw_snapshot/py/metadata_test.py
+++ /dev/null
@@ -1,155 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests for snapshot metadata processing."""
-
-import base64
-import unittest
-import pw_tokenizer
-from pw_snapshot_metadata.metadata import MetadataProcessor, process_snapshot
-from pw_snapshot_protos import snapshot_pb2
-from pw_tokenizer import tokens
-
-
-class MetadataProcessorTest(unittest.TestCase):
-    """Tests that the metadata processor produces expected results."""
-    def setUp(self):
-        super().setUp()
-        self.detok = pw_tokenizer.Detokenizer(
-            tokens.Database([
-                tokens.TokenizedStringEntry(0x3A9BC4C3,
-                                            'Assert failed: 1+1 == 42'),
-                tokens.TokenizedStringEntry(0x01170923, 'gShoe'),
-            ]))
-
-        snapshot = snapshot_pb2.Snapshot()
-        snapshot.metadata.reason = b'\xc3\xc4\x9b\x3a'
-        snapshot.metadata.project_name = b'$' + base64.b64encode(
-            b'\x23\x09\x17\x01')
-        snapshot.metadata.device_name = b'hyper-fast-gshoe'
-        snapshot.metadata.software_version = 'gShoe-debug-1.2.1-6f23412b+'
-        snapshot.metadata.snapshot_uuid = b'\x00\x00\x00\x01'
-
-        self.snapshot = snapshot
-
-    def test_reason_tokenized(self):
-        meta = MetadataProcessor(self.snapshot.metadata, self.detok)
-        self.assertEqual(meta.reason(), 'Assert failed: 1+1 == 42')
-
-    def test_reason_log_format(self):
-        self.snapshot.metadata.reason = (
-            '■msg♦Assert failed :('
-            '■file♦rpc_services/crash.cc').encode('utf-8')
-        meta = MetadataProcessor(self.snapshot.metadata, self.detok)
-        self.assertEqual(meta.reason(),
-                         'rpc_services/crash.cc: Assert failed :(')
-
-    def test_project_name_tokenized(self):
-        meta = MetadataProcessor(self.snapshot.metadata, self.detok)
-        self.assertEqual(meta.project_name(), 'gShoe')
-
-    def test_device_name_not_tokenized(self):
-        meta = MetadataProcessor(self.snapshot.metadata, self.detok)
-        self.assertEqual(meta.device_name(), 'hyper-fast-gshoe')
-
-    def test_default_non_fatal(self):
-        meta = MetadataProcessor(self.snapshot.metadata, self.detok)
-        self.assertFalse(meta.is_fatal())
-
-    def test_fw_version(self):
-        meta = MetadataProcessor(self.snapshot.metadata, self.detok)
-        self.assertEqual(meta.device_fw_version(),
-                         'gShoe-debug-1.2.1-6f23412b+')
-
-    def test_snapshot_uuid(self):
-        meta = MetadataProcessor(self.snapshot.metadata, self.detok)
-        self.assertEqual(meta.snapshot_uuid(), '00000001')
-
-    def test_fw_uuid_default(self):
-        meta = MetadataProcessor(self.snapshot.metadata, self.detok)
-        self.assertEqual(meta.fw_build_uuid(), '')
-
-    def test_as_str(self):
-        meta = MetadataProcessor(self.snapshot.metadata, self.detok)
-        expected = '\n'.join((
-            'Snapshot capture reason:',
-            '    Assert failed: 1+1 == 42',
-            '',
-            'Project name:      gShoe',
-            'Device:            hyper-fast-gshoe',
-            'Device FW version: gShoe-debug-1.2.1-6f23412b+',
-            'Snapshot UUID:     00000001',
-        ))
-        self.assertEqual(expected, str(meta))
-
-    def test_as_str_fatal(self):
-        self.snapshot.metadata.fatal = True
-        meta = MetadataProcessor(self.snapshot.metadata, self.detok)
-        expected = '\n'.join((
-            '                            ▪▄▄▄ ▄▄▄· ▄▄▄▄▄ ▄▄▄· ▄ ·',
-            '                            █▄▄▄▐█ ▀█ • █▌ ▐█ ▀█ █',
-            '                            █ ▪ ▄█▀▀█   █. ▄█▀▀█ █',
-            '                            ▐▌ .▐█ ▪▐▌ ▪▐▌·▐█ ▪▐▌▐▌',
-            '                            ▀    ▀  ▀ ·  ▀  ▀  ▀ .▀▀',
-            '',
-            'Device crash cause:',
-            '    Assert failed: 1+1 == 42',
-            '',
-            'Project name:      gShoe',
-            'Device:            hyper-fast-gshoe',
-            'Device FW version: gShoe-debug-1.2.1-6f23412b+',
-            'Snapshot UUID:     00000001',
-        ))
-        self.assertEqual(expected, str(meta))
-
-    def test_no_reason(self):
-        snapshot = snapshot_pb2.Snapshot()
-        snapshot.metadata.fatal = True
-        meta = MetadataProcessor(snapshot.metadata, self.detok)
-        meta.set_pretty_format_width(40)
-        expected = '\n'.join((
-            '        ▪▄▄▄ ▄▄▄· ▄▄▄▄▄ ▄▄▄· ▄ ·',
-            '        █▄▄▄▐█ ▀█ • █▌ ▐█ ▀█ █',
-            '        █ ▪ ▄█▀▀█   █. ▄█▀▀█ █',
-            '        ▐▌ .▐█ ▪▐▌ ▪▐▌·▐█ ▪▐▌▐▌',
-            '        ▀    ▀  ▀ ·  ▀  ▀  ▀ .▀▀',
-            '',
-            'Device crash cause:',
-            '    UNKNOWN (field missing)',
-            '',
-        ))
-        self.assertEqual(expected, str(meta))
-
-    def test_serialized_snapshot(self):
-        self.snapshot.tags['type'] = 'obviously a crash'
-        expected = '\n'.join((
-            'Snapshot capture reason:',
-            '    Assert failed: 1+1 == 42',
-            '',
-            'Project name:      gShoe',
-            'Device:            hyper-fast-gshoe',
-            'Device FW version: gShoe-debug-1.2.1-6f23412b+',
-            'Snapshot UUID:     00000001',
-            '',
-            'Tags:',
-            '  type: obviously a crash',
-            '',
-        ))
-        self.assertEqual(
-            expected,
-            process_snapshot(self.snapshot.SerializeToString(), self.detok))
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_snapshot/py/pw_snapshot/__init__.py b/pw_snapshot/py/pw_snapshot/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/pw_snapshot/py/pw_snapshot/__init__.py
+++ /dev/null
diff --git a/pw_snapshot/py/pw_snapshot/processor.py b/pw_snapshot/py/pw_snapshot/processor.py
deleted file mode 100644
index d96691028..0000000
--- a/pw_snapshot/py/pw_snapshot/processor.py
+++ /dev/null
@@ -1,189 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tool for processing and outputting Snapshot protos as text"""
-
-import argparse
-import functools
-import logging
-import sys
-from pathlib import Path
-from typing import Optional, BinaryIO, TextIO, Callable
-import pw_tokenizer
-import pw_cpu_exception_cortex_m
-import pw_build_info.build_id
-from pw_snapshot_metadata import metadata
-from pw_snapshot_protos import snapshot_pb2
-from pw_symbolizer import LlvmSymbolizer, Symbolizer
-from pw_thread import thread_analyzer
-
-_LOG = logging.getLogger('snapshot_processor')
-
-_BRANDING = """
-        ____ _       __    _____ _   _____    ____  _____ __  ______  ______
-       / __ \\ |     / /   / ___// | / /   |  / __ \\/ ___// / / / __ \\/_  __/
-      / /_/ / | /| / /    \\__ \\/  |/ / /| | / /_/ /\\__ \\/ /_/ / / / / / /
-     / ____/| |/ |/ /    ___/ / /|  / ___ |/ ____/___/ / __  / /_/ / / /
-    /_/     |__/|__/____/____/_/ |_/_/  |_/_/    /____/_/ /_/\\____/ /_/
-                  /_____/
-
-"""
-
-# Deprecated, use SymbolizerMatcher. Will be removed shortly.
-ElfMatcher = Callable[[snapshot_pb2.Snapshot], Optional[Path]]
-
-# Symbolizers are useful for turning addresses into source code locations and
-# function names. As a single snapshot may contain embedded snapshots from
-# multiple devices, there's a need to match ELF files to the correct snapshot to
-# correctly symbolize addresses.
-#
-# A SymbolizerMatcher is a function that takes a snapshot and investigates its
-# metadata (often build ID, device name, or the version string) to determine
-# whether a Symbolizer may be loaded with a suitable ELF file for symbolization.
-SymbolizerMatcher = Callable[[snapshot_pb2.Snapshot], Symbolizer]
-
-
-def process_snapshot(
-        serialized_snapshot: bytes,
-        detokenizer: Optional[pw_tokenizer.Detokenizer] = None,
-        elf_matcher: Optional[ElfMatcher] = None,
-        symbolizer_matcher: Optional[SymbolizerMatcher] = None) -> str:
-    """Processes a single snapshot."""
-
-    output = [_BRANDING]
-
-    captured_metadata = metadata.process_snapshot(serialized_snapshot,
-                                                  detokenizer)
-    if captured_metadata:
-        output.append(captured_metadata)
-
-    # Open a symbolizer.
-    snapshot = snapshot_pb2.Snapshot()
-    snapshot.ParseFromString(serialized_snapshot)
-
-    if symbolizer_matcher is not None:
-        symbolizer = symbolizer_matcher(snapshot)
-    elif elf_matcher is not None:
-        symbolizer = LlvmSymbolizer(elf_matcher(snapshot))
-    else:
-        symbolizer = LlvmSymbolizer()
-
-    cortex_m_cpu_state = pw_cpu_exception_cortex_m.process_snapshot(
-        serialized_snapshot, symbolizer)
-    if cortex_m_cpu_state:
-        output.append(cortex_m_cpu_state)
-
-    thread_info = thread_analyzer.process_snapshot(serialized_snapshot,
-                                                   detokenizer, symbolizer)
-    if thread_info:
-        output.append(thread_info)
-
-    # Check and emit the number of related snapshots embedded in this snapshot.
-    if snapshot.related_snapshots:
-        snapshot_count = len(snapshot.related_snapshots)
-        plural = 's' if snapshot_count > 1 else ''
-        output.extend((
-            f'This snapshot contains {snapshot_count} related snapshot{plural}',
-            '',
-        ))
-
-    return '\n'.join(output)
-
-
-def process_snapshots(
-        serialized_snapshot: bytes,
-        detokenizer: Optional[pw_tokenizer.Detokenizer] = None,
-        elf_matcher: Optional[ElfMatcher] = None,
-        user_processing_callback: Optional[Callable[[bytes], str]] = None,
-        symbolizer_matcher: Optional[SymbolizerMatcher] = None) -> str:
-    """Processes a snapshot that may have multiple embedded snapshots."""
-    output = []
-    # Process the top-level snapshot.
-    output.append(
-        process_snapshot(serialized_snapshot, detokenizer, elf_matcher,
-                         symbolizer_matcher))
-
-    # If the user provided a custom processing callback, call it on each
-    # snapshot.
-    if user_processing_callback is not None:
-        output.append(user_processing_callback(serialized_snapshot))
-
-    # Process any related snapshots that were embedded in this one.
-    snapshot = snapshot_pb2.Snapshot()
-    snapshot.ParseFromString(serialized_snapshot)
-    for nested_snapshot in snapshot.related_snapshots:
-        output.append('\n[' + '=' * 78 + ']\n')
-        output.append(
-            str(
-                process_snapshots(nested_snapshot.SerializeToString(),
-                                  detokenizer, elf_matcher,
-                                  user_processing_callback,
-                                  symbolizer_matcher)))
-
-    return '\n'.join(output)
-
-
-def _snapshot_symbolizer_matcher(
-        artifacts_dir: Path,
-        snapshot: snapshot_pb2.Snapshot) -> LlvmSymbolizer:
-    matching_elf: Optional[Path] = pw_build_info.build_id.find_matching_elf(
-        snapshot.metadata.software_build_uuid, artifacts_dir)
-    if not matching_elf:
-        _LOG.error('Error: No matching ELF found for GNU build ID %s.',
-                   snapshot.metadata.software_build_uuid.hex())
-    return LlvmSymbolizer(matching_elf)
-
-
-def _load_and_dump_snapshots(in_file: BinaryIO, out_file: TextIO,
-                             token_db: Optional[TextIO],
-                             artifacts_dir: Optional[Path]):
-    detokenizer = None
-    if token_db:
-        detokenizer = pw_tokenizer.Detokenizer(token_db)
-    symbolizer_matcher: Optional[SymbolizerMatcher] = None
-    if artifacts_dir:
-        symbolizer_matcher = functools.partial(_snapshot_symbolizer_matcher,
-                                               artifacts_dir)
-    out_file.write(
-        process_snapshots(serialized_snapshot=in_file.read(),
-                          detokenizer=detokenizer,
-                          symbolizer_matcher=symbolizer_matcher))
-
-
-def _parse_args():
-    parser = argparse.ArgumentParser(description='Decode Pigweed snapshots')
-    parser.add_argument('in_file',
-                        type=argparse.FileType('rb'),
-                        help='Binary snapshot file')
-    parser.add_argument(
-        '--out-file',
-        '-o',
-        default='-',
-        type=argparse.FileType('wb'),
-        help='File to output decoded snapshots to. Defaults to stdout.')
-    parser.add_argument(
-        '--token-db',
-        type=argparse.FileType('r'),
-        help='Token database or ELF file to use for detokenization.')
-    parser.add_argument(
-        '--artifacts-dir',
-        type=Path,
-        help=('Directory to recursively search for matching ELF files to use '
-              'for symbolization.'))
-    return parser.parse_args()
-
-
-if __name__ == '__main__':
-    logging.basicConfig(format='%(message)s', level=logging.INFO)
-    _load_and_dump_snapshots(**vars(_parse_args()))
-    sys.exit(0)
diff --git a/pw_snapshot/py/pw_snapshot/py.typed b/pw_snapshot/py/pw_snapshot/py.typed
deleted file mode 100644
index e69de29..0000000
--- a/pw_snapshot/py/pw_snapshot/py.typed
+++ /dev/null
diff --git a/pw_snapshot/py/pw_snapshot_metadata/__init__.py b/pw_snapshot/py/pw_snapshot_metadata/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/pw_snapshot/py/pw_snapshot_metadata/__init__.py
+++ /dev/null
diff --git a/pw_snapshot/py/pw_snapshot_metadata/metadata.py b/pw_snapshot/py/pw_snapshot_metadata/metadata.py
deleted file mode 100644
index ea39380..0000000
--- a/pw_snapshot/py/pw_snapshot_metadata/metadata.py
+++ /dev/null
@@ -1,138 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Library to assist processing Snapshot Metadata protos into text"""
-
-from typing import Optional, List, Mapping
-import pw_log_tokenized
-import pw_tokenizer
-from pw_tokenizer import proto as proto_detokenizer
-from pw_snapshot_metadata_proto import snapshot_metadata_pb2
-
-_PRETTY_FORMAT_DEFAULT_WIDTH = 80
-
-
-def _process_tags(tags: Mapping[str, str]) -> Optional[str]:
-    """Outputs snapshot tags as a multi-line string."""
-    if not tags:
-        return None
-
-    output: List[str] = ['Tags:']
-    for key, value in tags.items():
-        output.append(f'  {key}: {value}')
-
-    return '\n'.join(output)
-
-
-def process_snapshot(serialized_snapshot: bytes,
-                     tokenizer_db: Optional[pw_tokenizer.Detokenizer]) -> str:
-    """Processes snapshot metadata and tags, producing a multi-line string."""
-    snapshot = snapshot_metadata_pb2.SnapshotBasicInfo()
-    snapshot.ParseFromString(serialized_snapshot)
-
-    output: List[str] = []
-
-    if snapshot.HasField('metadata'):
-        output.extend((
-            str(MetadataProcessor(snapshot.metadata, tokenizer_db)),
-            '',
-        ))
-
-    if snapshot.tags:
-        tags = _process_tags(snapshot.tags)
-        if tags:
-            output.append(tags)
-        # Trailing blank line for spacing.
-        output.append('')
-
-    return '\n'.join(output)
-
-
-class MetadataProcessor:
-    """This class simplifies dumping contents of a snapshot Metadata message."""
-    def __init__(self, metadata: snapshot_metadata_pb2.Metadata,
-                 tokenizer_db: Optional[pw_tokenizer.Detokenizer]):
-        self._metadata = metadata
-        self._tokenizer_db = (tokenizer_db if tokenizer_db is not None else
-                              pw_tokenizer.Detokenizer(None))
-        self._format_width = _PRETTY_FORMAT_DEFAULT_WIDTH
-        proto_detokenizer.detokenize_fields(self._tokenizer_db, self._metadata)
-
-    def is_fatal(self) -> bool:
-        return self._metadata.fatal
-
-    def reason(self) -> str:
-        if not self._metadata.reason:
-            return 'UNKNOWN (field missing)'
-
-        log = pw_log_tokenized.FormatStringWithMetadata(
-            self._metadata.reason.decode())
-
-        return f'{log.file}: {log.message}' if log.file else log.message
-
-    def project_name(self) -> str:
-        return self._metadata.project_name.decode()
-
-    def device_name(self) -> str:
-        return self._metadata.device_name.decode()
-
-    def device_fw_version(self) -> str:
-        return self._metadata.software_version
-
-    def snapshot_uuid(self) -> str:
-        return self._metadata.snapshot_uuid.hex()
-
-    def fw_build_uuid(self) -> str:
-        return self._metadata.software_build_uuid.hex()
-
-    def set_pretty_format_width(self, width: int):
-        """Sets the centered width of the FATAL text for a formatted output."""
-        self._format_width = width
-
-    def __str__(self) -> str:
-        """outputs a pw.snapshot.Metadata proto as a multi-line string."""
-        output: List[str] = []
-        if self._metadata.fatal:
-            output.extend((
-                '▪▄▄▄ ▄▄▄· ▄▄▄▄▄ ▄▄▄· ▄ ·'.center(self._format_width).rstrip(),
-                '█▄▄▄▐█ ▀█ • █▌ ▐█ ▀█ █  '.center(self._format_width).rstrip(),
-                '█ ▪ ▄█▀▀█   █. ▄█▀▀█ █  '.center(self._format_width).rstrip(),
-                '▐▌ .▐█ ▪▐▌ ▪▐▌·▐█ ▪▐▌▐▌ '.center(self._format_width).rstrip(),
-                '▀    ▀  ▀ ·  ▀  ▀  ▀ .▀▀'.center(self._format_width).rstrip(),
-                '',
-                'Device crash cause:',
-            ))
-        else:
-            output.append('Snapshot capture reason:')
-
-        output.extend((
-            '    ' + self.reason(),
-            '',
-        ))
-
-        if self._metadata.project_name:
-            output.append(f'Project name:      {self.project_name()}')
-
-        if self._metadata.device_name:
-            output.append(f'Device:            {self.device_name()}')
-
-        if self._metadata.software_version:
-            output.append(f'Device FW version: {self.device_fw_version()}')
-
-        if self._metadata.software_build_uuid:
-            output.append(f'FW build UUID:     {self.fw_build_uuid()}')
-
-        if self._metadata.snapshot_uuid:
-            output.append(f'Snapshot UUID:     {self.snapshot_uuid()}')
-
-        return '\n'.join(output)
diff --git a/pw_snapshot/py/pw_snapshot_metadata/py.typed b/pw_snapshot/py/pw_snapshot_metadata/py.typed
deleted file mode 100644
index e69de29..0000000
--- a/pw_snapshot/py/pw_snapshot_metadata/py.typed
+++ /dev/null
diff --git a/pw_snapshot/setup.rst b/pw_snapshot/setup.rst
deleted file mode 100644
index a2a7690..0000000
--- a/pw_snapshot/setup.rst
+++ /dev/null
@@ -1,326 +0,0 @@
-.. _module-pw_snapshot-setup:
-
-==============================
-Setting up a Snapshot Pipeline
-==============================
-
--------------------
-Crash Handler Setup
--------------------
-The Snapshot proto was designed first and foremost as a crash reporting format.
-This section covers how to set up a crash handler to capture Snapshots.
-
-.. image:: images/generic_crash_flow.svg
-  :width: 600
-  :alt: Generic crash handler flow
-
-A typical crash handler has two entry points:
-
-1. A software entry path through developer-written ASSERT() or CHECK() calls
-   that indicate a device should go down for a crash if a condition is not met.
-2. A hardware-triggered exception handler path that is initiated when a CPU
-   encounters a fault signal (invalid memory access, bad instruction, etc.).
-
-Before deferring to a common crash handler, these entry paths should disable
-interrupts to force the system into a single-threaded execution mode. This
-prevents other threads from operating on potentially bad data or clobbering
-system state that could be useful for debugging.
-
-The first step in a crash handler should always be a check for nested crashes to
-prevent infinitely recursive crashes. Once it's deemed it's safe to continue,
-the crash handler can re-initialize logging, initialize storage for crash report
-capture, and then build a snapshot to later be retrieved from the device. Once
-the crash report collection process is complete, some post-crash callbacks can
-be run on a best-effort basis to clean up the system before rebooting. For
-devices with debug port access, it's helpful to optionally hold the device in
-an infinite loop rather than resetting to allow developers to access the device
-via a hardware debugger.
-
-Assert Handler Setup
-====================
-:ref:`pw_assert <module-pw_assert>` is Pigweed's entry point for software
-crashes. Route any existing assert functions through pw_assert to centralize the
-software crash path. You’ll need to create a :ref:`pw_assert backend
-<module-pw_assert-backend_api>` or a custom :ref:`pw_assert_basic handler
-<module-pw_assert_basic-custom_handler>` to pass collected information to a more
-sophisticated crash handler. One way to do this is to collect the data into a
-statically allocated struct that is passed to a common crash handler. It’s
-important to immediately disable interrupts to prevent the system from doing
-other things while in an impacted state.
-
-.. code-block:: cpp
-
-  // This can be be directly accessed by a crash handler
-  static CrashData crash_data;
-  extern "C" void pw_assert_basic_HandleFailure(const char* file_name,
-                                                int line_number,
-                                                const char* format,
-                                                ...) {
-    // Always disable interrupts first! How this is done depends
-    // on your platform.
-    __disable_irq();
-
-    va_list args;
-    va_start(args, format);
-    crash_data.file_name = file_name;
-    crash_data.line_number = line_number;
-    crash_data.reason_fmt = format;
-    crash_data.reason_args = &args;
-    crash_data.cpu_state = nullptr;
-
-    HandleCrash(crash_data);
-    PW_UNREACHABLE;
-  }
-
-Exception Handler Setup
-=======================
-:ref:`pw_cpu_exception <module-pw_cpu_exception>` is Pigweed's recommended entry
-point for CPU-triggered faults (divide by zero, invalid memory access, etc.).
-You will need to provide a definition for pw_cpu_exception_DefaultHandler() that
-passes the exception state produced by pw_cpu_exception to your common crash
-handler.
-
-.. code-block:: cpp
-
-  static CrashData crash_data;
-  // This helper turns a format string to a va_list that can be used by the
-  // common crash handling path.
-  void HandleExceptionWithString(pw_cpu_exception_State& state,
-                                 const char* fmt,
-                                 ...) {
-    va_list args;
-    va_start(args, fmt);
-    crash_data.cpu_state = state;
-    crash_data.file_name = nullptr;
-    crash_data.reason_fmt = fmt;
-    crash_data.reason_args = &args;
-
-    HandleCrash(crash_data);
-    PW_UNREACHABLE;
-  }
-
-  extern "C" void pw_cpu_exception_DefaultHandler(
-      pw_cpu_exception_State* state) {
-    // Always disable interrupts first! How this is done depends
-    // on your platform.
-    __disable_irq();
-
-    crash_data.state = cpu_state;
-    // The CFSR is an extremely useful register for understanding ARMv7-M and
-    // ARMv8-M CPU faults. Other architectures should put something else here.
-    HandleExceptionWithString(crash_data,
-                              "Exception encountered, cfsr=0x%",
-                              cpu_state->extended.cfsr);
-  }
-
-Common Crash Handler Setup
-==========================
-To minimize duplication of crash handling logic, it's good practice to route the
-pw_assert and pw_cpu_exception handlers to a common crash handling codepath.
-Ensure you can pass both pw_cpu_exception's CPU state and pw_assert's assert
-information to the shared handler.
-
-.. code-block:: cpp
-
-  struct CrashData {
-    pw_cpu_exception_State *cpu_state;
-    const char *reason_fmt;
-    const va_list *reason_args;
-    const char *file_name;
-    int line_number;
-  };
-
-  // This function assumes interrupts are properly disabled BEFORE it is called.
-  [[noreturn]] void HandleCrash(CrashData& crash_info) {
-    // Handle crash
-  }
-
-In the crash handler your project can re-initialize a minimal subset of the
-system needed to safely capture a snapshot before rebooting the device. The
-remainder of this section focuses on ways you can improve the reliability and
-usability of your project's crash handler.
-
-Check for Nested Crashes
-------------------------
-It’s important to include crash handler checks that prevent infinite recursive
-nesting of crashes. Maintain a static variable that checks the crash nesting
-depth. After one or two nested crashes, abort crash handling entirely and reset
-the device or sit in an infinite loop to wait for a hardware debugger to attach.
-It’s simpler to put this logic at the beginning of the shared crash handler, but
-if your assert/exception handlers are complex it might be safer to inject the
-checks earlier in both codepaths.
-
-.. code-block:: cpp
-
-  [[noreturn]] void HandleCrash(CrashData &crash_info) {
-    static size_t crash_depth = 0;
-    if (crash_depth > kMaxCrashDepth) {
-      Abort(/*run_callbacks=*/false);
-    }
-    crash_depth++;
-    ...
-  }
-
-Re-initialize Logging (Optional)
---------------------------------
-Logging can be helpful for debugging your crash handler, but depending on your
-device/system design may be challenging to safely support at crash time. To
-re-initialize logging, you’ll need to re-construct C++ objects and re-initialize
-any systems/hardware in the logging codepath. You may even need an entirely
-separate logging pipeline that is single-threaded and interrupt-safe. Depending
-on your system’s design, this may be difficult to set up.
-
-Reinitialize Dependencies
--------------------------
-It's good practice to design a crash handler that can run before C++ static
-constructors have run. This means any initialization (whether manual or through
-constructors) that your crash handler depends on should be manually invoked at
-crash time. If an initialization step might not be safe, evaluate if it's
-possible to omit the dependency.
-
-System Cleanup
---------------
-After collecting a snapshot, some parts of your system may benefit from some
-cleanup before explicitly resetting a device. This might include flushing
-buffers or safely shutting down attached hardware. The order of shutdown should
-be deterministic, keeping in mind that any of these steps may have the potential
-of causing a nested crash that skips the remainder of the handlers and forces
-the device to immediately reset.
-
-----------------------
-Snapshot Storage Setup
-----------------------
-Use a storage class with a ``pw::stream::Writer`` interface to simplify
-capturing a pw_snapshot proto. This can be a :ref:`pw::BlobStore
-<module-pw_blob_store>`, an in-memory buffer that is flushed to flash, or a
-:ref:`pw::PersistentBuffer <module-pw_persistent_ram-persistent_buffer>` that
-lives in persistent memory. It's good practice to use lazy initialization for
-storage objects used by your Snapshot capture codepath.
-
-.. code-block:: cpp
-
-  // Persistent RAM objects are highly available. They don't rely on
-  // their constructor being run, and require no initialization.
-  PW_PLACE_IN_SECTION(".noinit")
-  pw::persistent_ram::PersistentBuffer<2048> persistent_snapshot;
-
-  void CaptureSnapshot(CrashInfo& crash_info) {
-    ...
-    persistent_snapshot.clear();
-    PersistentBufferWriter& writer = persistent_snapshot.GetWriter();
-    ...
-  }
-
-----------------------
-Snapshot Capture Setup
-----------------------
-
-.. note::
-
-  These instructions do not yet use the ``pw::protobuf::StreamEncoder``.
-
-Capturing a snapshot is as simple as encoding any other proto message. Some
-modules provide helper functions that will populate parts of a Snapshot, which
-eases the burden of custom work that must be set up uniquely for each project.
-
-Capture Reason
-==============
-A snapshot's "reason" should be considered the single most important field in a
-captured snapshot. If a snapshot capture was triggered by a crash, this should
-be the assert string. Other entry paths should describe here why the snapshot
-was captured ("Host communication buffer full!", "Exception encountered at
-0x00000004", etc.).
-
-.. code-block:: cpp
-
-  Status CaptureSnapshot(CrashData& crash_info) {
-    // Temporary buffer for encoding "reason" to.
-    static std::byte temp_buffer[500];
-    // Temporary buffer to encode serialized proto to before dumping to the
-    // final ``pw::stream::Writer``.
-    static std::byte proto_encode_buffer[512];
-    ...
-    pw::protobuf::NestedEncoder<kMaxDepth> proto_encoder(proto_encode_buffer);
-    pw::snapshot::Snapshot::Encoder snapshot_encoder(&proto_encoder);
-    size_t length = snprintf(temp_buffer,
-                             sizeof(temp_buffer,
-                             crash_info.reason_fmt),
-                             *crash_info.reason_args);
-    snapshot_encoder.WriteReason(temp_buffer, length));
-
-    // Final encode and write.
-    Result<ConstByteSpan> encoded_proto = proto_encoder.Encode();
-    PW_TRY(encoded_proto.status());
-    PW_TRY(writer.Write(encoded_proto.value()));
-    ...
-  }
-
-Capture CPU State
-=================
-When using pw_cpu_exception, exceptions will automatically collect CPU state
-that can be directly dumped into a snapshot. As it's not always easy to describe
-a CPU exception in a single "reason" string, this captures the information
-needed to more verbosely automatically generate a descriptive reason at analysis
-time once the snapshot is retrieved from the device.
-
-.. code-block:: cpp
-
-  Status CaptureSnapshot(CrashData& crash_info) {
-    ...
-
-    proto_encoder.clear();
-
-    // Write CPU state.
-    if (crash_info.cpu_state) {
-      PW_TRY(DumpCpuStateProto(snapshot_encoder.GetArmv7mCpuStateEncoder(),
-                               *crash_info.cpu_state));
-
-      // Final encode and write.
-      Result<ConstByteSpan> encoded_proto = proto_encoder.Encode();
-      PW_TRY(encoded_proto.status());
-      PW_TRY(writer.Write(encoded_proto.value()));
-    }
-  }
-
------------------------
-Snapshot Transfer Setup
------------------------
-Pigweed’s pw_rpc system is well suited for retrieving a snapshot from a device.
-Pigweed does not yet provide a generalized transfer service for moving files
-to/from a device. When this feature is added to Pigweed, this section will be
-updated to include guidance for connecting a storage system to a transfer
-service.
-
-----------------------
-Snapshot Tooling Setup
-----------------------
-When using the upstream ``Snapshot`` proto, you can directly use
-``pw_snapshot.process`` to process snapshots into human-readable dumps. If
-you've opted to extend Pigweed's snapshot proto, you'll likely want to extend
-the processing tooling to handle custom project data as well. This can be done
-by creating a light wrapper around
-``pw_snapshot.processor.process_snapshots()``.
-
-.. code-block:: python
-
-  def _process_hw_failures(serialized_snapshot: bytes) -> str:
-      """Custom handler that checks wheel state."""
-      wheel_state = wheel_state_pb2.WheelStateSnapshot()
-      output = []
-      wheel_state.ParseFromString(serialized_snapshot)
-
-      if len(wheel_state.wheels) != 2:
-          output.append(f'Expected 2 wheels, found {len(wheel_state.wheels)}')
-
-      if len(wheel_state.wheels) < 2:
-          output.append('Wheels fell off!')
-
-      # And more...
-
-      return '\n'.join(output)
-
-
-  def process_my_snapshots(serialized_snapshot: bytes) -> str:
-      """Runs the snapshot processor with a custom callback."""
-      return pw_snaphsot.processor.process_snapshots(
-          serialized_snapshot, user_processing_callback=_process_hw_failures)
diff --git a/pw_snapshot/uuid.cc b/pw_snapshot/uuid.cc
deleted file mode 100644
index 420d5e3..0000000
--- a/pw_snapshot/uuid.cc
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_snapshot/uuid.h"
-
-#include <cstddef>
-#include <span>
-
-#include "pw_bytes/span.h"
-#include "pw_protobuf/decoder.h"
-#include "pw_result/result.h"
-#include "pw_snapshot_metadata_proto/snapshot_metadata.pwpb.h"
-#include "pw_status/try.h"
-
-namespace pw::snapshot {
-
-using protobuf::Decoder;
-
-Result<ConstByteSpan> ReadUuidFromSnapshot(ConstByteSpan snapshot,
-                                           UuidSpan output) {
-  Decoder decoder(snapshot);
-  ConstByteSpan metadata;
-  while (decoder.Next().ok()) {
-    if (decoder.FieldNumber() ==
-        static_cast<uint32_t>(
-            pw::snapshot::SnapshotBasicInfo::Fields::METADATA)) {
-      PW_TRY(decoder.ReadBytes(&metadata));
-      break;
-    }
-  }
-  if (metadata.empty()) {
-    return Status::NotFound();
-  }
-
-  // Start to read from the metadata.
-  decoder.Reset(metadata);
-  ConstByteSpan snapshot_uuid;
-  while (decoder.Next().ok()) {
-    if (decoder.FieldNumber() ==
-        static_cast<uint32_t>(pw::snapshot::Metadata::Fields::SNAPSHOT_UUID)) {
-      PW_TRY(decoder.ReadBytes(&snapshot_uuid));
-      break;
-    }
-  }
-  if (snapshot_uuid.empty()) {
-    return Status::NotFound();
-  }
-  if (snapshot_uuid.size_bytes() > output.size_bytes()) {
-    return Status::ResourceExhausted();
-  }
-
-  memcpy(output.data(), snapshot_uuid.data(), snapshot_uuid.size_bytes());
-  return ConstByteSpan(output.first(snapshot_uuid.size_bytes()));
-}
-
-}  // namespace pw::snapshot
diff --git a/pw_snapshot/uuid_test.cc b/pw_snapshot/uuid_test.cc
deleted file mode 100644
index 795071e..0000000
--- a/pw_snapshot/uuid_test.cc
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_snapshot/uuid.h"
-
-#include <array>
-#include <span>
-
-#include "gtest/gtest.h"
-#include "pw_bytes/span.h"
-#include "pw_protobuf/encoder.h"
-#include "pw_result/result.h"
-#include "pw_snapshot_metadata_proto/snapshot_metadata.pwpb.h"
-#include "pw_status/status.h"
-
-namespace pw::snapshot {
-namespace {
-
-ConstByteSpan EncodeSnapshotWithUuid(ConstByteSpan uuid, ByteSpan dest) {
-  SnapshotBasicInfo::MemoryEncoder snapshot_encoder(dest);
-  {
-    Metadata::StreamEncoder metadata_encoder =
-        snapshot_encoder.GetMetadataEncoder();
-    EXPECT_EQ(OkStatus(), metadata_encoder.WriteSnapshotUuid(uuid));
-  }
-  EXPECT_EQ(OkStatus(), snapshot_encoder.status());
-
-  return snapshot_encoder;
-}
-
-TEST(ReadUuid, ReadUuid) {
-  const std::array<uint8_t, 8> kExpectedUuid = {
-      0x1F, 0x8F, 0xBF, 0xC4, 0x86, 0x0E, 0xED, 0xD4};
-  std::array<std::byte, 16> snapshot_buffer;
-  ConstByteSpan snapshot = EncodeSnapshotWithUuid(
-      std::as_bytes(std::span(kExpectedUuid)), snapshot_buffer);
-
-  std::array<std::byte, kUuidSizeBytes> uuid_dest;
-  Result<ConstByteSpan> result = ReadUuidFromSnapshot(snapshot, uuid_dest);
-  EXPECT_EQ(OkStatus(), result.status());
-  EXPECT_EQ(kExpectedUuid.size(), result->size());
-  EXPECT_EQ(0, memcmp(result->data(), kExpectedUuid.data(), result->size()));
-}
-
-TEST(ReadUuid, NoUuid) {
-  std::array<std::byte, 16> snapshot_buffer;
-
-  // Write some snapshot metadata, but no UUID.
-  SnapshotBasicInfo::MemoryEncoder snapshot_encoder(snapshot_buffer);
-  {
-    Metadata::StreamEncoder metadata_encoder =
-        snapshot_encoder.GetMetadataEncoder();
-    EXPECT_EQ(OkStatus(), metadata_encoder.WriteFatal(true));
-  }
-  EXPECT_EQ(OkStatus(), snapshot_encoder.status());
-
-  ConstByteSpan snapshot(snapshot_encoder);
-  std::array<std::byte, kUuidSizeBytes> uuid_dest;
-  Result<ConstByteSpan> result = ReadUuidFromSnapshot(snapshot, uuid_dest);
-  EXPECT_EQ(Status::NotFound(), result.status());
-}
-
-TEST(ReadUuid, UndersizedBuffer) {
-  const std::array<uint8_t, 17> kExpectedUuid = {0xF4,
-                                                 0x1B,
-                                                 0xE1,
-                                                 0x2D,
-                                                 0x10,
-                                                 0x9B,
-                                                 0xB2,
-                                                 0x1A,
-                                                 0x88,
-                                                 0xE0,
-                                                 0xC4,
-                                                 0x77,
-                                                 0xCA,
-                                                 0x18,
-                                                 0x83,
-                                                 0xB5,
-                                                 0xBB};
-  std::array<std::byte, 32> snapshot_buffer;
-  ConstByteSpan snapshot = EncodeSnapshotWithUuid(
-      std::as_bytes(std::span(kExpectedUuid)), snapshot_buffer);
-
-  std::array<std::byte, kUuidSizeBytes> uuid_dest;
-  Result<ConstByteSpan> result = ReadUuidFromSnapshot(snapshot, uuid_dest);
-  EXPECT_EQ(Status::ResourceExhausted(), result.status());
-}
-
-}  // namespace
-}  // namespace pw::snapshot
diff --git a/pw_software_update/BUILD.bazel b/pw_software_update/BUILD.bazel
deleted file mode 100644
index ef586fa..0000000
--- a/pw_software_update/BUILD.bazel
+++ /dev/null
@@ -1,96 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load("@rules_proto//proto:defs.bzl", "proto_library")
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-proto_library(
-    name = "update_bundle_proto",
-    srcs = [
-        "pw_software_update_protos/tuf.proto",
-        "pw_software_update_protos/update_bundle.proto",
-    ],
-    strip_import_prefix = "//pw_software_update",
-)
-
-pw_cc_library(
-    name = "update_bundle",
-    srcs = [
-        "manifest_accessor.cc",
-        "update_bundle_accessor.cc",
-    ],
-    hdrs = [
-        "public/pw_software_update/bundled_update_backend.h",
-        "public/pw_software_update/config.h",
-        "public/pw_software_update/manifest_accessor.h",
-        "public/pw_software_update/update_bundle_accessor.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_blob_store",
-        "//pw_kvs",
-        "//pw_log",
-        "//pw_protobuf",
-        "//pw_status",
-        "//pw_stream",
-        "//pw_string",
-    ],
-)
-
-pw_cc_library(
-    name = "bundled_update_service",
-    srcs = ["bundled_update_service.cc"],
-    hdrs = ["public/pw_software_update/bundled_update_service.h"],
-    includes = ["public"],
-    deps = [
-        ":update_bundle",
-        ":update_bundle_proto",
-        "//pw_log",
-        "//pw_result",
-        "//pw_status",
-        "//pw_sync:borrow",
-        "//pw_sync:lock_annotations",
-        "//pw_sync:mutex",
-        "//pw_sync:string",
-        "//pw_tokenizer",
-        "//pw_work_queue",
-    ],
-)
-
-pw_cc_test(
-    name = "update_bundle_test",
-    srcs = ["update_bundle_test.cc"],
-    deps = [
-        ":update_bundle",
-        "//pw_kvs:fake_flash_test_key_value_store",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "bundled_update_service_test",
-    srcs = ["bundled_update_service_test.cc"],
-    deps = [
-        ":bundled_update_service",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_software_update/BUILD.gn b/pw_software_update/BUILD.gn
deleted file mode 100644
index f59438b..0000000
--- a/pw_software_update/BUILD.gn
+++ /dev/null
@@ -1,190 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/module_config.gni")
-import("$dir_pw_crypto/backend.gni")
-import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_protobuf_compiler/proto.gni")
-import("$dir_pw_third_party/nanopb/nanopb.gni")
-import("$dir_pw_third_party/protobuf/protobuf.gni")
-import("$dir_pw_thread/backend.gni")
-import("$dir_pw_unit_test/test.gni")
-
-declare_args() {
-  # The build target that overrides the default configuration options for this
-  # module. This should point to a source set that provides defines through a
-  # public config (which may -include a file or add defines directly).
-  pw_software_update_CONFIG = pw_build_DEFAULT_MODULE_CONFIG
-}
-
-config("public_include_path") {
-  include_dirs = [ "public" ]
-  visibility = [ ":*" ]
-}
-
-pw_source_set("config") {
-  public = [ "public/pw_software_update/config.h" ]
-  public_configs = [ ":public_include_path" ]
-  public_deps = [ pw_software_update_CONFIG ]
-}
-
-if (dir_pw_third_party_protobuf != "") {
-  pw_proto_library("protos") {
-    deps = [
-      "$dir_pw_protobuf:common_protos",
-      "$dir_pw_third_party/protobuf:wellknown_types",
-      "$dir_pw_tokenizer:proto",
-    ]
-    sources = [
-      "bundled_update.proto",
-      "tuf.proto",
-      "update_bundle.proto",
-    ]
-    inputs = [ "bundled_update.options" ]
-    prefix = "pw_software_update"
-    python_package = "py"
-  }
-} else {
-  # placeholder target to allow py package to build
-  pw_proto_library("protos") {
-    deps = [
-      "$dir_pw_protobuf:common_protos",
-      "$dir_pw_tokenizer:proto",
-    ]
-    sources = [
-      "bundled_update.proto",
-      "tuf.proto",
-      "update_bundle.proto",
-    ]
-    inputs = [ "bundled_update.options" ]
-    prefix = "pw_software_update"
-    python_package = "py"
-  }
-}
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
-
-if (pw_crypto_SHA256_BACKEND != "" && pw_crypto_ECDSA_BACKEND != "") {
-  pw_source_set("update_bundle") {
-    public_configs = [ ":public_include_path" ]
-    public_deps = [
-      "$dir_pw_crypto:ecdsa",
-      "$dir_pw_crypto:sha256",
-      "$dir_pw_stream:interval_reader",
-      dir_pw_blob_store,
-      dir_pw_kvs,
-      dir_pw_protobuf,
-      dir_pw_result,
-      dir_pw_status,
-      dir_pw_stream,
-    ]
-    public = [
-      "public/pw_software_update/bundled_update_backend.h",
-      "public/pw_software_update/manifest_accessor.h",
-      "public/pw_software_update/update_bundle_accessor.h",
-    ]
-    deps = [
-      ":config",
-      ":protos.pwpb",
-      dir_pw_log,
-      dir_pw_string,
-    ]
-    sources = [
-      "manifest_accessor.cc",
-      "update_bundle_accessor.cc",
-    ]
-  }
-} else {
-  group("update_bundle") {
-  }
-}
-
-if (dir_pw_third_party_nanopb != "" && dir_pw_third_party_protobuf != "") {
-  pw_source_set("bundled_update_service") {
-    public_configs = [ ":public_include_path" ]
-    public_deps = [
-      ":protos.nanopb_rpc",
-      ":update_bundle",
-      dir_pw_result,
-      dir_pw_status,
-      dir_pw_work_queue,
-    ]
-    deps = [
-      ":config",
-      ":protos.pwpb",
-      "$dir_pw_sync:borrow",
-      "$dir_pw_sync:lock_annotations",
-      "$dir_pw_sync:mutex",
-      dir_pw_log,
-      dir_pw_string,
-      dir_pw_tokenizer,
-    ]
-    public = [ "public/pw_software_update/bundled_update_service.h" ]
-    sources = [ "bundled_update_service.cc" ]
-  }
-} else {
-  group("bundled_update_service") {
-  }
-}
-
-pw_python_action("generate_test_bundle") {
-  header_output = "$target_gen_dir/$target_name/test_bundles.h"
-  script = "py/pw_software_update/generate_test_bundle.py"
-  python_deps = [
-    ":protos.python",
-    "py",
-  ]
-  outputs = [ header_output ]
-  args = [ rebase_path(header_output) ]
-}
-
-config("generated_test_bundle_include") {
-  _generated_outputs = get_target_outputs(":generate_test_bundle")
-  include_dirs = [ get_path_info(_generated_outputs[0], "dir") ]
-}
-
-all_dependency_met =
-    dir_pw_third_party_nanopb != "" && dir_pw_third_party_protobuf != "" &&
-    pw_thread_THREAD_BACKEND != "" && pw_crypto_SHA256_BACKEND != "" &&
-    pw_crypto_ECDSA_BACKEND != ""
-
-pw_test("update_bundle_test") {
-  enable_if = all_dependency_met
-  sources = [ "update_bundle_test.cc" ]
-  public_deps = [
-    ":bundled_update_service",
-    ":generate_test_bundle",
-    ":update_bundle",
-    "$dir_pw_kvs:fake_flash",
-    "$dir_pw_kvs:fake_flash_test_key_value_store",
-  ]
-  configs = [ ":generated_test_bundle_include" ]
-}
-
-pw_test_group("tests") {
-  tests = [
-    ":bundled_update_service_test",
-    ":update_bundle_test",
-  ]
-}
-
-pw_test("bundled_update_service_test") {
-  enable_if = all_dependency_met
-  sources = [ "bundled_update_service_test.cc" ]
-  public_deps = [ ":bundled_update_service" ]
-}
diff --git a/pw_software_update/OWNERS b/pw_software_update/OWNERS
deleted file mode 100644
index cec7abd..0000000
--- a/pw_software_update/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-alizhang@google.com
-ewout@google.com
diff --git a/pw_software_update/README.md b/pw_software_update/README.md
deleted file mode 100644
index 5ef440f..0000000
--- a/pw_software_update/README.md
+++ /dev/null
@@ -1 +0,0 @@
-Software update module
\ No newline at end of file
diff --git a/pw_software_update/bundled_update.options b/pw_software_update/bundled_update.options
deleted file mode 100644
index 9ca6d6f..0000000
--- a/pw_software_update/bundled_update.options
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-pw.software_update.BundledUpdateStatus.bundle_filename max_size:32
-pw.software_update.BundledUpdateStatus.note max_size:32
-pw.software_update.StartRequest.bundle_filename max_size:32
diff --git a/pw_software_update/bundled_update.proto b/pw_software_update/bundled_update.proto
deleted file mode 100644
index 697e3dd..0000000
--- a/pw_software_update/bundled_update.proto
+++ /dev/null
@@ -1,182 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-syntax = "proto3";
-
-package pw.software_update;
-
-import "pw_protobuf_protos/common.proto";
-import "pw_tokenizer/proto/options.proto";
-import "google/protobuf/any.proto";
-
-message BundledUpdateState {
-  enum Enum {
-    UNKNOWN = 0;  // Not an expected state in the OTA system; only for proto.
-
-    // Valid methods in this state: Start()
-    //
-    // Transition:
-    //   Start() succeeds --> TRANSFERRING
-    //   Start() fails    --> FINISHED
-    INACTIVE = 1;
-
-    // Valid methods in this state: GetStatus(), Abort().
-    //
-    // Transitions:
-    //   Transfer completes --> TRANSFERRED
-    //   Transfer fails     --> FINISHED
-    //   Abort() called     --> ABORTING
-    TRANSFERRING = 2;
-
-    // Valid methods in this state: GetStatus(), Abort(), Verify().
-    //
-    // Transitions:
-    //   Verify() called    --> VERIFYING
-    //   Apply() called     --> VERIFYING
-    //   Abort() called     --> ABORTING
-    TRANSFERRED = 3;
-
-    // Valid methods in this state: GetStatus(), Abort().
-    //
-    // Transitions:
-    //   Verifying finished --> VERIFIED
-    //   Verifying failed   --> FINISHED
-    //   Abort() called     --> ABORTING
-    VERIFYING = 4;
-
-    // Valid methods in this state: GetStatus(), Abort(), Apply().
-    //
-    // Transitions:
-    //   Apply() called --> APPLYING
-    //   Abort() called --> ABORTING
-    VERIFIED = 5;
-
-    // Valid methods in this state: GetStatus().
-    //
-    // Transitions:
-    //   Apply() finished --> FINISHED; may require persisting across a reboot.
-    //   Apply() failed   --> FINISHED; with error set.
-    APPLYING = 6;
-
-    // Valid methods in this state: GetStatus().
-    //
-    // Transitions:
-    //   Abort finishes --> FINISHED
-    //   Abort fails    --> FINISHED
-    ABORTING = 7;
-
-    // Valid methods in this state: GetStatus(), Reset().
-    //
-    // Terminal state indicating a finished update; whether successful or
-    // not. Additional termination information available in completion_state
-    // and possibly note.
-    //
-    // Transitions:
-    //   Reset() succeeds --> INACTIVE
-    //   Reset() fails    --> FINISHED
-    FINISHED = 8;
-  }
-}
-
-message BundledUpdateResult {
-  enum Enum {
-    UNKNOWN = 0;
-    SUCCESS = 1;
-    UNKNOWN_ERROR = 2;
-    ABORTED = 3;
-    TRANSFER_FAILED = 4;
-    VERIFY_FAILED = 5;
-    APPLY_FAILED = 6;
-  }
-}
-
-message BundledUpdateStatus {
-  BundledUpdateState.Enum state = 1;
-
-  optional BundledUpdateResult.Enum result = 2;
-
-  // This is the percentage of estimated progress for the current update
-  // state in hundreths of a percent. (e.g. 5.00% = 500u)
-  optional uint32 current_state_progress_hundreth_percent = 3;
-
-  // If present, the active transfer ID for the update.
-  optional uint32 transfer_id = 4;
-
-  // The name of the update bundle. Not present when in INACTIVE state. This is
-  // useful for enabling resuming of transfers across reboots or disconnects,
-  // without transferring an expensive manifest.
-  optional string bundle_filename = 5;
-
-  // Additional information related to the state may be provided here.
-  // Examples: "Failed verifying: ml_model.bin", "Flash partition couldn't be
-  // acquired and was busy", etc. Can provide more granular information than
-  // just the completion result.
-  optional bytes note = 6 [(tokenizer.format) = TOKENIZATION_OPTIONAL];
-
-  // Custom application data.
-  optional google.protobuf.Any extended_status = 7;
-}
-
-message StartRequest {
-  // If present, the filename for the staged file. This should persist across
-  // reboots, and will be returned from GetStatus() until either the update
-  // applies or is aborted.
-  optional string bundle_filename = 1;
-}
-
-// TODO(pwbug/478): Add documentation and details of the API contract.
-service BundledUpdate {
-  // TODO: Offer GetCurrentManifest & GetStagedManifest() methods that leverage
-  // pw_transfer to upload the manifest off the device, to enable host logic.
-
-  // Get current status of software update.
-  rpc GetStatus(pw.protobuf.Empty) returns (BundledUpdateStatus);
-
-  // Start a software update. Do any device-specific tasks needed to be ready
-  // for update. Open pw_transfer channel used for staging bundle.
-  // Returned status includes the transfer ID to use for bundle transfer.
-  //
-  // Precondition: Device update state must be INACTIVE.
-  rpc Start(StartRequest) returns (BundledUpdateStatus);
-
-  // Manually set the bundle status as transferred. It can be used to recover
-  // a previously finished transfer or used when transfer happens on a side
-  // channel without involvement of pw_transfer
-  //
-  // Precondition: Device update state must be INACTIVE or TRANSFERRING
-  rpc SetTransferred(pw.protobuf.Empty) returns (BundledUpdateStatus);
-
-  // Verify the bundle in the staging area. Returns immediately, but
-  // verification runs asynchronously. Poll for completion with GetStatus().
-  //
-  // Precondition: Device update state must be TRANSFERRED.
-  rpc Verify(pw.protobuf.Empty) returns (BundledUpdateStatus);
-
-  // Apply the staged update. The update process will verify the staged bundle
-  // or ensure that it was verified, apply the bundle, and in some cases reboot
-  // the device. During this process, the device may be slow to respond.
-  //
-  // The RPC is async; callers must fetch status with GetStatus().
-  //
-  // Precondition: Device update state must be TRANSFERRED or VERIFIED.
-  rpc Apply(pw.protobuf.Empty) returns (BundledUpdateStatus);
-
-  // Abort any current software update in progress.
-  // Precondition: Device update state must not be INACTIVE or FINISHED.
-  rpc Abort(pw.protobuf.Empty) returns (BundledUpdateStatus);
-
-  // Reset after a finished update. Device goes into INACTIVE state after.
-  //
-  // Precondition: Device update state must be FINISHED.
-  rpc Reset(pw.protobuf.Empty) returns (BundledUpdateStatus);
-}
diff --git a/pw_software_update/bundled_update_service.cc b/pw_software_update/bundled_update_service.cc
deleted file mode 100644
index cbbc67e..0000000
--- a/pw_software_update/bundled_update_service.cc
+++ /dev/null
@@ -1,538 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 PW_LOG_MODULE_NAME "PWSU"
-#define PW_LOG_LEVEL PW_LOG_LEVEL_WARN
-
-#include "pw_software_update/bundled_update_service.h"
-
-#include <mutex>
-#include <string_view>
-
-#include "pw_log/log.h"
-#include "pw_result/result.h"
-#include "pw_software_update/config.h"
-#include "pw_software_update/manifest_accessor.h"
-#include "pw_software_update/update_bundle.pwpb.h"
-#include "pw_status/status.h"
-#include "pw_status/status_with_size.h"
-#include "pw_status/try.h"
-#include "pw_string/string_builder.h"
-#include "pw_string/util.h"
-#include "pw_sync/borrow.h"
-#include "pw_sync/mutex.h"
-#include "pw_tokenizer/tokenize.h"
-
-namespace pw::software_update {
-namespace {
-using BorrowedStatus =
-    sync::BorrowedPointer<pw_software_update_BundledUpdateStatus, sync::Mutex>;
-using BundledUpdateState = pw_software_update_BundledUpdateState_Enum;
-using BundledUpdateStatus = pw_software_update_BundledUpdateStatus;
-
-// TODO(keir): Convert all the CHECKs in the RPC service to gracefully report
-// errors.
-#define SET_ERROR(res, message, ...)                                          \
-  do {                                                                        \
-    PW_LOG_ERROR(message, __VA_ARGS__);                                       \
-    if (!IsFinished()) {                                                      \
-      Finish(res);                                                            \
-      {                                                                       \
-        BorrowedStatus borrowed_status = status_.acquire();                   \
-        size_t note_size = sizeof(borrowed_status->note.bytes);               \
-        PW_TOKENIZE_TO_BUFFER(                                                \
-            borrowed_status->note.bytes, &(note_size), message, __VA_ARGS__); \
-        borrowed_status->note.size = note_size;                               \
-        borrowed_status->has_note = true;                                     \
-      }                                                                       \
-    }                                                                         \
-  } while (false)
-}  // namespace
-
-Status BundledUpdateService::GetStatus(const pw_protobuf_Empty&,
-                                       BundledUpdateStatus& response) {
-  response = *status_.acquire();
-  return OkStatus();
-}
-
-Status BundledUpdateService::Start(
-    const pw_software_update_StartRequest& request,
-    BundledUpdateStatus& response) {
-  std::lock_guard lock(mutex_);
-  // Check preconditions.
-  const BundledUpdateState state = status_.acquire()->state;
-  if (state != pw_software_update_BundledUpdateState_Enum_INACTIVE) {
-    SET_ERROR(pw_software_update_BundledUpdateResult_Enum_UNKNOWN_ERROR,
-              "Start() can only be called from INACTIVE state. "
-              "Current state: %d. Abort() then Reset() must be called first",
-              static_cast<int>(state));
-    response = *status_.acquire();
-    return Status::FailedPrecondition();
-  }
-
-  {
-    BorrowedStatus borrowed_status = status_.acquire();
-    PW_DCHECK(!borrowed_status->has_transfer_id);
-    PW_DCHECK(!borrowed_status->has_result);
-    PW_DCHECK(borrowed_status->current_state_progress_hundreth_percent == 0);
-    PW_DCHECK(borrowed_status->bundle_filename[0] == '\0');
-    PW_DCHECK(borrowed_status->note.size == 0);
-  }
-
-  // Notify the backend of pending transfer.
-  if (const Status status = backend_.BeforeUpdateStart(); !status.ok()) {
-    SET_ERROR(pw_software_update_BundledUpdateResult_Enum_UNKNOWN_ERROR,
-              "Backend error on BeforeUpdateStart()");
-    response = *status_.acquire();
-    return status;
-  }
-
-  // Enable bundle transfer.
-  Result<uint32_t> possible_transfer_id =
-      backend_.EnableBundleTransferHandler(string::ClampedCString(
-          request.bundle_filename, sizeof(request.bundle_filename)));
-  if (!possible_transfer_id.ok()) {
-    SET_ERROR(pw_software_update_BundledUpdateResult_Enum_TRANSFER_FAILED,
-              "Couldn't enable bundle transfer");
-    response = *status_.acquire();
-    return possible_transfer_id.status();
-  }
-
-  // Update state.
-  {
-    BorrowedStatus borrowed_status = status_.acquire();
-    borrowed_status->transfer_id = possible_transfer_id.value();
-    borrowed_status->has_transfer_id = true;
-    if (request.has_bundle_filename) {
-      const StatusWithSize sws =
-          string::Copy(request.bundle_filename,
-                       borrowed_status->bundle_filename,
-                       sizeof(borrowed_status->bundle_filename));
-      PW_DCHECK_OK(sws.status(),
-                   "bundle_filename options max_sizes do not match");
-      borrowed_status->has_bundle_filename = true;
-    }
-    borrowed_status->state =
-        pw_software_update_BundledUpdateState_Enum_TRANSFERRING;
-    response = *borrowed_status;
-  }
-  return OkStatus();
-}
-
-Status BundledUpdateService::SetTransferred(const pw_protobuf_Empty&,
-                                            BundledUpdateStatus& response) {
-  const BundledUpdateState state = status_.acquire()->state;
-
-  if (state != pw_software_update_BundledUpdateState_Enum_TRANSFERRING &&
-      state != pw_software_update_BundledUpdateState_Enum_INACTIVE) {
-    SET_ERROR(pw_software_update_BundledUpdateResult_Enum_UNKNOWN_ERROR,
-              "SetTransferred() can only be called from TRANSFERRING or "
-              "INACTIVE state. State: %d",
-              static_cast<int>(state));
-    response = *status_.acquire();
-    return OkStatus();
-  }
-
-  NotifyTransferSucceeded();
-
-  response = *status_.acquire();
-  return OkStatus();
-}
-
-// TODO: Check for "ABORTING" state and bail if it's set.
-void BundledUpdateService::DoVerify() {
-  std::lock_guard guard(mutex_);
-  const BundledUpdateState state = status_.acquire()->state;
-
-  if (state == pw_software_update_BundledUpdateState_Enum_VERIFIED) {
-    return;  // Already done!
-  }
-
-  // Ensure we're in the right state.
-  if (state != pw_software_update_BundledUpdateState_Enum_TRANSFERRED) {
-    SET_ERROR(pw_software_update_BundledUpdateResult_Enum_VERIFY_FAILED,
-              "DoVerify() must be called from TRANSFERRED state. State: %d",
-              static_cast<int>(state));
-    return;
-  }
-
-  status_.acquire()->state =
-      pw_software_update_BundledUpdateState_Enum_VERIFYING;
-
-  // Notify backend about pending verify.
-  if (const Status status = backend_.BeforeBundleVerify(); !status.ok()) {
-    SET_ERROR(pw_software_update_BundledUpdateResult_Enum_VERIFY_FAILED,
-              "Backend::BeforeBundleVerify() failed");
-    return;
-  }
-
-  // Do the actual verify.
-  Status status = bundle_.OpenAndVerify();
-  if (!status.ok()) {
-    SET_ERROR(pw_software_update_BundledUpdateResult_Enum_VERIFY_FAILED,
-              "Bundle::OpenAndVerify() failed");
-    return;
-  }
-  bundle_open_ = true;
-
-  // Have the backend verify the user_manifest if present.
-  if (!backend_.VerifyManifest(bundle_.GetManifest()).ok()) {
-    SET_ERROR(pw_software_update_BundledUpdateResult_Enum_VERIFY_FAILED,
-              "Backend::VerifyUserManifest() failed");
-    return;
-  }
-
-  // Notify backend we're done verifying.
-  status = backend_.AfterBundleVerified();
-  if (!status.ok()) {
-    SET_ERROR(pw_software_update_BundledUpdateResult_Enum_VERIFY_FAILED,
-              "Backend::AfterBundleVerified() failed");
-    return;
-  }
-  status_.acquire()->state =
-      pw_software_update_BundledUpdateState_Enum_VERIFIED;
-}
-
-Status BundledUpdateService::Verify(const pw_protobuf_Empty&,
-                                    BundledUpdateStatus& response) {
-  std::lock_guard lock(mutex_);
-  const BundledUpdateState state = status_.acquire()->state;
-
-  // Already done? Bail.
-  if (state == pw_software_update_BundledUpdateState_Enum_VERIFIED) {
-    PW_LOG_DEBUG("Skipping verify since already verified");
-    return OkStatus();
-  }
-
-  // TODO: Remove the transferring permitted state here ASAP.
-  // Ensure we're in the right state.
-  if ((state != pw_software_update_BundledUpdateState_Enum_TRANSFERRING) &&
-      (state != pw_software_update_BundledUpdateState_Enum_TRANSFERRED)) {
-    SET_ERROR(pw_software_update_BundledUpdateResult_Enum_VERIFY_FAILED,
-              "Verify() must be called from TRANSFERRED state. State: %d",
-              static_cast<int>(state));
-    response = *status_.acquire();
-    return Status::FailedPrecondition();
-  }
-
-  // TODO: We should probably make this mode idempotent.
-  // Already doing what was asked? Bail.
-  if (work_enqueued_) {
-    PW_LOG_DEBUG("Verification is already active");
-    return OkStatus();
-  }
-
-  // The backend's ApplyReboot as part of DoApply() shall be configured
-  // such that this RPC can send out the reply before the device reboots.
-  const Status status = work_queue_.PushWork([this] {
-    {
-      std::lock_guard y_lock(this->mutex_);
-      PW_DCHECK(this->work_enqueued_);
-    }
-    this->DoVerify();
-    {
-      std::lock_guard y_lock(this->mutex_);
-      this->work_enqueued_ = false;
-    }
-  });
-  if (!status.ok()) {
-    SET_ERROR(pw_software_update_BundledUpdateResult_Enum_VERIFY_FAILED,
-              "Unable to equeue apply to work queue");
-    response = *status_.acquire();
-    return status;
-  }
-  work_enqueued_ = true;
-
-  response = *status_.acquire();
-  return OkStatus();
-}
-
-Status BundledUpdateService::Apply(const pw_protobuf_Empty&,
-                                   BundledUpdateStatus& response) {
-  std::lock_guard lock(mutex_);
-  const BundledUpdateState state = status_.acquire()->state;
-
-  // We do not wait to go into a finished error state if we're already
-  // applying, instead just let them know that yes we are working on it --
-  // hold on.
-  if (state == pw_software_update_BundledUpdateState_Enum_APPLYING) {
-    PW_LOG_DEBUG("Apply is already active");
-    return OkStatus();
-  }
-
-  if ((state != pw_software_update_BundledUpdateState_Enum_TRANSFERRED) &&
-      (state != pw_software_update_BundledUpdateState_Enum_VERIFIED)) {
-    SET_ERROR(pw_software_update_BundledUpdateResult_Enum_APPLY_FAILED,
-              "Apply() must be called from TRANSFERRED or VERIFIED state. "
-              "State: %d",
-              static_cast<int>(state));
-    return Status::FailedPrecondition();
-  }
-
-  // TODO: We should probably make these all idempotent properly.
-  if (work_enqueued_) {
-    PW_LOG_DEBUG("Apply is already active");
-    return OkStatus();
-  }
-
-  // The backend's ApplyReboot as part of DoApply() shall be configured
-  // such that this RPC can send out the reply before the device reboots.
-  const Status status = work_queue_.PushWork([this] {
-    {
-      std::lock_guard y_lock(this->mutex_);
-      PW_DCHECK(this->work_enqueued_);
-    }
-    // Error reporting is handled in DoVerify and DoApply.
-    this->DoVerify();
-    this->DoApply();
-    {
-      std::lock_guard y_lock(this->mutex_);
-      this->work_enqueued_ = false;
-    }
-  });
-  if (!status.ok()) {
-    SET_ERROR(pw_software_update_BundledUpdateResult_Enum_APPLY_FAILED,
-              "Unable to equeue apply to work queue");
-    response = *status_.acquire();
-    return status;
-  }
-  work_enqueued_ = true;
-
-  return OkStatus();
-}
-
-void BundledUpdateService::DoApply() {
-  std::lock_guard guard(mutex_);
-  const BundledUpdateState state = status_.acquire()->state;
-
-  PW_LOG_DEBUG("Attempting to apply the update");
-  if (state != pw_software_update_BundledUpdateState_Enum_VERIFIED) {
-    SET_ERROR(pw_software_update_BundledUpdateResult_Enum_APPLY_FAILED,
-              "Apply() must be called from VERIFIED state. State: %d",
-              static_cast<int>(state));
-    return;
-  }
-
-  status_.acquire()->state =
-      pw_software_update_BundledUpdateState_Enum_APPLYING;
-
-  if (const Status status = backend_.BeforeApply(); !status.ok()) {
-    SET_ERROR(pw_software_update_BundledUpdateResult_Enum_APPLY_FAILED,
-              "BeforeApply() returned unsuccessful result: %d",
-              static_cast<int>(status.code()));
-    return;
-  }
-
-  // In order to report apply progress, quickly scan to see how many bytes
-  // will be applied.
-  Result<uint64_t> total_payload_bytes = bundle_.GetTotalPayloadSize();
-  PW_CHECK_OK(total_payload_bytes.status());
-  size_t target_file_bytes_to_apply =
-      static_cast<size_t>(total_payload_bytes.value());
-
-  protobuf::RepeatedMessages target_files =
-      bundle_.GetManifest().GetTargetFiles();
-  PW_CHECK_OK(target_files.status());
-
-  size_t target_file_bytes_applied = 0;
-  for (pw::protobuf::Message file_name : target_files) {
-    std::array<std::byte, MAX_TARGET_NAME_LENGTH> buf = {};
-    protobuf::String name = file_name.AsString(static_cast<uint32_t>(
-        pw::software_update::TargetFile::Fields::FILE_NAME));
-    PW_CHECK_OK(name.status());
-    const Result<ByteSpan> read_result = name.GetBytesReader().Read(buf);
-    PW_CHECK_OK(read_result.status());
-    const ConstByteSpan file_name_span = read_result.value();
-    const std::string_view file_name_view(
-        reinterpret_cast<const char*>(file_name_span.data()),
-        file_name_span.size_bytes());
-    if (file_name_view.compare(kUserManifestTargetFileName) == 0) {
-      continue;  // user_manifest is not applied by the backend.
-    }
-    // Try to get an IntervalReader for the current file.
-    stream::IntervalReader file_reader =
-        bundle_.GetTargetPayload(file_name_view);
-    if (file_reader.status().IsNotFound()) {
-      PW_LOG_INFO(
-          "Contents of file %s missing from bundle; ignoring",
-          pw::MakeString<MAX_TARGET_NAME_LENGTH>(file_name_view).c_str());
-      continue;
-    }
-    if (!file_reader.ok()) {
-      SET_ERROR(pw_software_update_BundledUpdateResult_Enum_APPLY_FAILED,
-                "Could not open contents of file %s from bundle; "
-                "aborting update apply phase",
-                static_cast<int>(file_reader.status().code()));
-      return;
-    }
-
-    const size_t bundle_offset = file_reader.start();
-    if (const Status status = backend_.ApplyTargetFile(
-            file_name_view, file_reader, bundle_offset);
-        !status.ok()) {
-      SET_ERROR(pw_software_update_BundledUpdateResult_Enum_APPLY_FAILED,
-                "Failed to apply target file: %d",
-                static_cast<int>(status.code()));
-      return;
-    }
-    target_file_bytes_applied += file_reader.interval_size();
-    const uint32_t progress_hundreth_percent =
-        (static_cast<uint64_t>(target_file_bytes_applied) * 100 * 100) /
-        target_file_bytes_to_apply;
-    PW_LOG_DEBUG("Apply progress: %zu/%zu Bytes (%ld%%)",
-                 target_file_bytes_applied,
-                 target_file_bytes_to_apply,
-                 static_cast<unsigned long>(progress_hundreth_percent / 100));
-    {
-      BorrowedStatus borrowed_status = status_.acquire();
-      borrowed_status->current_state_progress_hundreth_percent =
-          progress_hundreth_percent;
-      borrowed_status->has_current_state_progress_hundreth_percent = true;
-    }
-  }
-
-  // TODO(davidrogers): Add new APPLY_REBOOTING to distinguish between pre and
-  // post reboot.
-
-  // Finalize the apply.
-  if (const Status status = backend_.ApplyReboot(); !status.ok()) {
-    SET_ERROR(pw_software_update_BundledUpdateResult_Enum_APPLY_FAILED,
-              "Failed to do the apply reboot: %d",
-              static_cast<int>(status.code()));
-    return;
-  }
-
-  // TODO(davidrogers): Move this to MaybeFinishApply() once available.
-  Finish(pw_software_update_BundledUpdateResult_Enum_SUCCESS);
-}
-
-Status BundledUpdateService::Abort(const pw_protobuf_Empty&,
-                                   BundledUpdateStatus& response) {
-  std::lock_guard lock(mutex_);
-  const BundledUpdateState state = status_.acquire()->state;
-
-  if (state == pw_software_update_BundledUpdateState_Enum_APPLYING) {
-    return Status::FailedPrecondition();
-  }
-
-  if (state == pw_software_update_BundledUpdateState_Enum_INACTIVE ||
-      state == pw_software_update_BundledUpdateState_Enum_FINISHED) {
-    SET_ERROR(pw_software_update_BundledUpdateResult_Enum_UNKNOWN_ERROR,
-              "Tried to abort when already INACTIVE or FINISHED");
-    return Status::FailedPrecondition();
-  }
-  // TODO: Switch abort to async; this state change isn't externally visible.
-  status_.acquire()->state =
-      pw_software_update_BundledUpdateState_Enum_ABORTING;
-
-  SET_ERROR(pw_software_update_BundledUpdateResult_Enum_ABORTED,
-            "Update abort requested");
-  response = *status_.acquire();
-  return OkStatus();
-}
-
-Status BundledUpdateService::Reset(const pw_protobuf_Empty&,
-                                   BundledUpdateStatus& response) {
-  std::lock_guard lock(mutex_);
-  const BundledUpdateState state = status_.acquire()->state;
-
-  if (state == pw_software_update_BundledUpdateState_Enum_INACTIVE) {
-    return OkStatus();  // Already done.
-  }
-
-  if (state != pw_software_update_BundledUpdateState_Enum_FINISHED) {
-    SET_ERROR(
-        pw_software_update_BundledUpdateResult_Enum_UNKNOWN_ERROR,
-        "Reset() must be called from FINISHED or INACTIVE state. State: %d",
-        static_cast<int>(state));
-    response = *status_.acquire();
-    return Status::FailedPrecondition();
-  }
-
-  {
-    *status_.acquire() = {
-        .state = pw_software_update_BundledUpdateState_Enum_INACTIVE};
-  }
-
-  // Reset the bundle.
-  if (bundle_open_) {
-    // TODO: Revisit whether this is recoverable; maybe eliminate CHECK.
-    PW_CHECK_OK(bundle_.Close());
-    bundle_open_ = false;
-  }
-
-  response = *status_.acquire();
-  return OkStatus();
-}
-
-void BundledUpdateService::NotifyTransferSucceeded() {
-  std::lock_guard lock(mutex_);
-  const BundledUpdateState state = status_.acquire()->state;
-
-  if (state != pw_software_update_BundledUpdateState_Enum_TRANSFERRING) {
-    // This can happen if the update gets Abort()'d during the transfer and
-    // the transfer completes successfully.
-    PW_LOG_WARN(
-        "Got transfer succeeded notification when not in TRANSFERRING state. "
-        "State: %d",
-        static_cast<int>(state));
-  }
-
-  const bool transfer_ongoing = status_.acquire()->has_transfer_id;
-  if (transfer_ongoing) {
-    backend_.DisableBundleTransferHandler();
-    status_.acquire()->has_transfer_id = false;
-  } else {
-    PW_LOG_WARN("No ongoing transfer found, forcefully set TRANSFERRED.");
-  }
-
-  status_.acquire()->state =
-      pw_software_update_BundledUpdateState_Enum_TRANSFERRED;
-}
-
-void BundledUpdateService::Finish(
-    pw_software_update_BundledUpdateResult_Enum result) {
-  if (result == pw_software_update_BundledUpdateResult_Enum_SUCCESS) {
-    BorrowedStatus borrowed_status = status_.acquire();
-    borrowed_status->current_state_progress_hundreth_percent = 0;
-    borrowed_status->has_current_state_progress_hundreth_percent = false;
-  } else {
-    // In the case of error, notify backend that we're about to abort the
-    // software update.
-    PW_CHECK_OK(backend_.BeforeUpdateAbort());
-  }
-
-  // Turn down the transfer if one is in progress.
-  const bool transfer_ongoing = status_.acquire()->has_transfer_id;
-  if (transfer_ongoing) {
-    backend_.DisableBundleTransferHandler();
-  }
-  status_.acquire()->has_transfer_id = false;
-
-  // Close out any open bundles.
-  if (bundle_open_) {
-    // TODO: Revisit this check; may be able to recover.
-    PW_CHECK_OK(bundle_.Close());
-    bundle_open_ = false;
-  }
-  {
-    BorrowedStatus borrowed_status = status_.acquire();
-    borrowed_status->state =
-        pw_software_update_BundledUpdateState_Enum_FINISHED;
-    borrowed_status->result = result;
-    borrowed_status->has_result = true;
-  }
-}
-
-}  // namespace pw::software_update
diff --git a/pw_software_update/bundled_update_service_test.cc b/pw_software_update/bundled_update_service_test.cc
deleted file mode 100644
index 9fe93f4..0000000
--- a/pw_software_update/bundled_update_service_test.cc
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_software_update/bundled_update_service.h"
-
-#include "gtest/gtest.h"
-
-TEST(BundledUpdateService, Compiles) {}
diff --git a/pw_software_update/docs.rst b/pw_software_update/docs.rst
deleted file mode 100644
index fb19278..0000000
--- a/pw_software_update/docs.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-.. _module-pw_software_update:
-
--------------------
-pw_software_update
--------------------
-
-This modules provides software update functionality.
-
-.. warning::
-  This module is under construction, not ready for use, and the documentation
-  is incomplete.
diff --git a/pw_software_update/manifest_accessor.cc b/pw_software_update/manifest_accessor.cc
deleted file mode 100644
index 14e7ad1..0000000
--- a/pw_software_update/manifest_accessor.cc
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_software_update/manifest_accessor.h"
-
-#include "pw_software_update/config.h"
-#include "pw_software_update/update_bundle.pwpb.h"
-#include "pw_software_update/update_bundle_accessor.h"
-
-namespace pw::software_update {
-
-ManifestAccessor ManifestAccessor::FromBundle(protobuf::Message bundle) {
-  protobuf::Message targets_metadata =
-      bundle
-          .AsStringToMessageMap(static_cast<uint32_t>(
-              UpdateBundle::Fields::TARGETS_METADATA))[kTopLevelTargetsName]
-          .AsMessage(static_cast<uint32_t>(
-              SignedTargetsMetadata::Fields::SERIALIZED_TARGETS_METADATA));
-
-  protobuf::Bytes user_manifest =
-      bundle.AsStringToBytesMap(static_cast<uint32_t>(
-          UpdateBundle::Fields::TARGET_PAYLOADS))[kUserManifestTargetFileName];
-
-  return ManifestAccessor(targets_metadata, user_manifest);
-}
-
-ManifestAccessor ManifestAccessor::FromManifest(protobuf::Message manifest) {
-  protobuf::Message targets_metadata =
-      manifest.AsStringToMessageMap(static_cast<uint32_t>(
-          Manifest::Fields::TARGETS_METADATA))[kTopLevelTargetsName];
-
-  protobuf::Bytes user_manifest =
-      manifest.AsBytes(static_cast<uint32_t>(Manifest::Fields::USER_MANIFEST));
-
-  return ManifestAccessor(targets_metadata, user_manifest);
-}
-
-protobuf::RepeatedMessages ManifestAccessor::GetTargetFiles() {
-  PW_TRY(status());
-  return targets_metadata_.AsRepeatedMessages(
-      static_cast<uint32_t>(TargetsMetadata::Fields::TARGET_FILES));
-}
-
-protobuf::Uint32 ManifestAccessor::GetVersion() {
-  PW_TRY(status());
-  return targets_metadata_
-      .AsMessage(
-          static_cast<uint32_t>(TargetsMetadata::Fields::COMMON_METADATA))
-      .AsUint32(static_cast<uint32_t>(CommonMetadata::Fields::VERSION));
-}
-
-Status ManifestAccessor::Export(stream::Writer& writer) {
-  PW_TRY(status());
-
-  // Write out the targets metadata map.
-  stream::MemoryReader name_reader(
-      std::as_bytes(std::span(kTopLevelTargetsName)));
-  stream::IntervalReader metadata_reader =
-      targets_metadata_.ToBytes().GetBytesReader();
-  std::byte stream_pipe_buffer[WRITE_MANIFEST_STREAM_PIPE_BUFFER_SIZE];
-  PW_TRY(protobuf::WriteProtoStringToBytesMapEntry(
-      static_cast<uint32_t>(Manifest::Fields::TARGETS_METADATA),
-      name_reader,
-      kTopLevelTargetsName.size(),
-      metadata_reader,
-      metadata_reader.interval_size(),
-      stream_pipe_buffer,
-      writer));
-
-  // The user manifest is optional, write it out if available().
-  stream::IntervalReader user_manifest_reader = user_manifest_.GetBytesReader();
-  if (user_manifest_reader.ok()) {
-    protobuf::StreamEncoder encoder(writer, {});
-    PW_TRY(encoder.WriteBytesFromStream(
-        static_cast<uint32_t>(Manifest::Fields::USER_MANIFEST),
-        user_manifest_reader,
-        user_manifest_reader.interval_size(),
-        stream_pipe_buffer));
-  }
-
-  return OkStatus();
-}
-
-protobuf::Message ManifestAccessor::GetTargetFile(protobuf::String name) {
-  PW_TRY(status());
-
-  std::array<std::byte, MAX_TARGET_NAME_LENGTH> name_buf = {};
-
-  stream::IntervalReader name_reader = name.GetBytesReader();
-  PW_TRY(name_reader.status());
-
-  if (name_reader.interval_size() > name_buf.size()) {
-    return Status::OutOfRange();
-  }
-
-  Result<ByteSpan> read_result = name_reader.Read(name_buf);
-  PW_TRY(read_result.status());
-
-  const ConstByteSpan name_span = read_result.value();
-  const std::string_view name_view(
-      reinterpret_cast<const char*>(name_span.data()), name_span.size_bytes());
-
-  return GetTargetFile(name_view);
-}
-
-protobuf::Message ManifestAccessor::GetTargetFile(std::string_view name) {
-  PW_TRY(status());
-
-  for (protobuf::Message target_file : GetTargetFiles()) {
-    protobuf::String target_name = target_file.AsString(
-        static_cast<uint32_t>(TargetFile::Fields::FILE_NAME));
-    Result<bool> compare_result = target_name.Equal(name);
-    PW_TRY(compare_result.status());
-    if (compare_result.value()) {
-      return target_file;
-    }
-  }
-
-  return Status::NotFound();
-}
-
-}  // namespace pw::software_update
diff --git a/pw_software_update/public/pw_software_update/bundled_update_backend.h b/pw_software_update/public/pw_software_update/bundled_update_backend.h
deleted file mode 100644
index ae80e06..0000000
--- a/pw_software_update/public/pw_software_update/bundled_update_backend.h
+++ /dev/null
@@ -1,232 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_result/result.h"
-#include "pw_software_update/manifest_accessor.h"
-#include "pw_software_update/update_bundle_accessor.h"
-#include "pw_status/status.h"
-#include "pw_stream/interval_reader.h"
-#include "pw_stream/stream.h"
-
-namespace pw::software_update {
-
-// TODO(pwbug/478): update documentation for backend api contract
-class BundledUpdateBackend {
- public:
-  virtual ~BundledUpdateBackend() = default;
-
-  // Perform optional, product-specific validations to the specified target
-  // file, using whatever metadata available in manifest.
-  //
-  // This is called for each target file after the standard verification has
-  // passed.
-  virtual Status VerifyTargetFile(
-      [[maybe_unused]] ManifestAccessor manifest,
-      [[maybe_unused]] std::string_view target_file_name) {
-    // TODO(backend): Perform any additional product-specific validations.
-    // It is safe to assume the target's payload has passed standard
-    // verification.
-    return OkStatus();
-  };
-
-  // Perform any product-specific tasks needed before starting update sequence.
-  virtual Status BeforeUpdateStart() { return OkStatus(); };
-
-  // Attempts to enable the transfer service transfer handler, returning the
-  // transfer_id if successful. This is invoked after BeforeUpdateStart();
-  virtual Result<uint32_t> EnableBundleTransferHandler(
-      std::string_view bundle_filename) = 0;
-
-  // Disables the transfer service transfer handler. This is invoked after
-  // either BeforeUpdateAbort() or BeforeBundleVerify().
-  virtual void DisableBundleTransferHandler() = 0;
-
-  // Perform any product-specific abort tasks before marking the update as
-  // aborted in bundled updater.  This should set any downstream state to a
-  // default no-update-pending state.
-  // TODO: Revisit invariants; should this instead be "Abort()"? This is called
-  // for all error paths in the service and needs to reset. Furthermore, should
-  // this be async?
-  virtual Status BeforeUpdateAbort() { return OkStatus(); };
-
-  // Perform any product-specific tasks needed before starting verification.
-  virtual Status BeforeBundleVerify() { return OkStatus(); };
-
-  // Perform any product-specific bundle verification tasks (e.g. hw version
-  // match check), done after TUF bundle verification process.
-  virtual Status VerifyManifest(
-      [[maybe_unused]] ManifestAccessor manifest_accessor) {
-    return OkStatus();
-  };
-
-  // Perform product-specific tasks after all bundle verifications are complete.
-  virtual Status AfterBundleVerified() { return OkStatus(); };
-
-  // Perform any product-specific tasks before apply sequence started
-  virtual Status BeforeApply() { return OkStatus(); };
-
-  // Get status information from update backend. This will not be called when
-  // BundledUpdater is in a step where it has entire control with no operation
-  // handed over to update backend.
-  virtual int64_t GetStatus() { return 0; }
-
-  // Update the specific target file on the device.
-  virtual Status ApplyTargetFile(std::string_view target_file_name,
-                                 stream::Reader& target_payload,
-                                 size_t update_bundle_offset) = 0;
-
-  // Backend to probe the device manifest and prepare a ready-to-go reader
-  // for it. See the comments to `GetCurrentManfestReader()` for more context.
-  virtual Status BeforeManifestRead() {
-    // Todo(backend):
-    // 1. Probe device to see if a well-formed manifest already exists.
-    // 2. If not, return `Status::NotFound()`. Note this will cause
-    //    anti-rollback to skip. So please don't always return
-    //    `Status::NotFound()`!
-    // 3. If yes, instantiate and activate a reader for the manifest!
-    // 4. Return any unexpected condition as errors but note this will cause
-    //    the current software update session to abort.
-    return OkStatus();
-  }
-
-  // Backend to provide a ready-to-go reader for the on-device manifest blob.
-  // This function is called after a successful `BeforeManifestRead()`,
-  // potentially more than once.
-  //
-  // This manifest blob is a serialized `message Manifest{...}` as defined in
-  // update_bundle.proto.
-  //
-  // This manifest blob is ALWAYS and EXCLUSIVELY persisted by a successful
-  // software update. Thus it may not available before the first software
-  // update, in which case `BeforeManifestRead()` should've returned
-  // `Status::NotFound()`.
-  //
-  // This manifest contains trusted metadata of all software currently running
-  // on the device and used for anti-rollback checks. It MUST NOT be tampered
-  // by factory resets, flashing, or any other means other than software
-  // updates.
-  virtual Result<stream::SeekableReader*> GetCurrentManifestReader() {
-    // Todo(backend):
-    // 1. Double check if a ready-to-go reader has been prepared by
-    //    `BeforeManifestRead()`.
-    // 2. If yes (expected), return the reader.
-    // 3. If not (unexpected), return `Status::FailedPrecondition()`.
-    return Status::Unimplemented();
-  }
-
-  // TODO(alizhang): Deprecate GetCurrentManifestReader in favor of
-  // `GetManifestReader()`.
-  virtual Result<stream::SeekableReader*> GetManifestReader() {
-    return GetCurrentManifestReader();
-  }
-
-  // Backend to prepare for on-device manifest update, e.g. make necessary
-  // efforts to ready the manifest writer. The manifest writer is used to
-  // persist a new manifest on-device following a successful software update.
-  // Manifest writing is never mixed with reading (i.e. reader and writer are
-  // used sequentially).
-  virtual Status BeforeManifestWrite() {
-    // Todo(backend):
-    // 1. Instantiate and activate a manifest writer pointing at a persistent
-    //    storage that at least could survive a factory data reset (FDR), if not
-    //    tamper-resistant.
-    return OkStatus();
-  }
-
-  // Backend to provide a ready-to-go writer for the on-device manifest blob.
-  // This function is called after a successful `BeforeManifestWrite()`,
-  // potentially more than once.
-  //
-  // This manifest blob is a serialized `message Manifest{...}` as defined in
-  // update_bundle.proto.
-  //
-  // This manifest blob is ALWAYS and EXCLUSIVELY persisted by a successful
-  // software update.
-  //
-  // This manifest contains trusted metadata of all software currently running
-  // on the device and used for anti-rollback checks. It MUST NOT be tampered
-  // by factory resets, flashing, or any other means other than software
-  // updates.
-  virtual Result<stream::Writer*> GetManifestWriter() {
-    // Todo(backend):
-    // 1. Double check a writer is ready to go as result of
-    //    `BeforeManifestWrite()`.
-    // 2. If yes (expected), simply return the writer.
-    // 3. If not (unexpected), return `Status::FailedPrecondition()`.
-    return Status::Unimplemented();
-  }
-
-  // Backend to finish up manifest writing.
-  virtual Status AfterManifestWrite() {
-    // Todo(backend):
-    // Protect the newly persisted manifest blob. This is to make manifest
-    // probing / reading easier and more reliable. This could involve taking
-    // a measurement (e.g. checksum) and storing that measurement in a
-    // FDR-safe tag, replicating the manifest in a backup location if the
-    // backing media is unreliable (e.g. raw NAND) etc.
-    //
-    // It is safe to assume the writing has been successful in this function.
-    return OkStatus();
-  }
-
-  // Do any work needed to finish the apply of the update and do a required
-  // reboot of the device!
-  //
-  // NOTE: If successful this method does not return and reboots the device, it
-  // only returns on failure to finalize.
-  //
-  // NOTE: ApplyReboot shall be configured such to allow pending RPC or logs to
-  // send out the reply before the device reboots.
-  virtual Status ApplyReboot() = 0;
-
-  // Do any work needed to finalize the update including optionally doing a
-  // reboot of the device! The software update state and breadcrumbs are not
-  // cleaned up until this method returns OK.
-  //
-  // This method is called after the reboot done as part of ApplyReboot().
-  //
-  // If this method does an optional reboot, it will be called again after the
-  // reboot.
-  //
-  // NOTE: PostRebootFinalize shall be configured such to allow pending RPC or
-  // logs to send out the reply before the device reboots.
-  virtual Status PostRebootFinalize() { return OkStatus(); }
-
-  // Get reader of the device's root metadata.
-  //
-  // This method MUST return a valid root metadata once verified OTA is enabled.
-  // An invalid or corrupted root metadata will result in permanent OTA
-  // failures.
-  virtual Result<stream::SeekableReader*> GetRootMetadataReader() {
-    return Status::Unimplemented();
-  };
-
-  // Write a given root metadata to persistent storage in a failsafe manner.
-  //
-  // The updating must be atomic/fail-safe. An invalid or corrupted root
-  // metadata will result in permanent OTA failures.
-  //
-  // TODO(pwbug/456): Investigate whether we should get a writer i.e.
-  // GetRootMetadataWriter() instead of passing a reader.
-  virtual Status SafelyPersistRootMetadata(
-      [[maybe_unused]] stream::IntervalReader root_metadata) {
-    return Status::Unimplemented();
-  };
-};
-
-}  // namespace pw::software_update
diff --git a/pw_software_update/public/pw_software_update/bundled_update_service.h b/pw_software_update/public/pw_software_update/bundled_update_service.h
deleted file mode 100644
index d517d22..0000000
--- a/pw_software_update/public/pw_software_update/bundled_update_service.h
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_software_update/bundled_update.rpc.pb.h"
-#include "pw_software_update/bundled_update_backend.h"
-#include "pw_software_update/update_bundle_accessor.h"
-#include "pw_status/status.h"
-#include "pw_sync/borrow.h"
-#include "pw_sync/lock_annotations.h"
-#include "pw_sync/mutex.h"
-#include "pw_work_queue/work_queue.h"
-
-namespace pw::software_update {
-
-// Implementation class for pw.software_update.BundledUpdate.
-// See bundled_update.proto for RPC method documentation.
-class BundledUpdateService
-    : public pw_rpc::nanopb::BundledUpdate::Service<BundledUpdateService> {
- public:
-  BundledUpdateService(UpdateBundleAccessor& bundle,
-                       BundledUpdateBackend& backend,
-                       work_queue::WorkQueue& work_queue)
-      : unsafe_status_{.state =
-                           pw_software_update_BundledUpdateState_Enum_INACTIVE},
-        status_(unsafe_status_, status_mutex_),
-        backend_(backend),
-        bundle_(bundle),
-        bundle_open_(false),
-        work_queue_(work_queue),
-        work_enqueued_(false) {}
-
-  Status GetStatus(const pw_protobuf_Empty& request,
-                   pw_software_update_BundledUpdateStatus& response);
-
-  // Sync
-  Status Start(const pw_software_update_StartRequest& request,
-               pw_software_update_BundledUpdateStatus& response);
-
-  // Sync
-  Status SetTransferred(const pw_protobuf_Empty& request,
-                        pw_software_update_BundledUpdateStatus& response);
-
-  // Async
-  Status Verify(const pw_protobuf_Empty& request,
-                pw_software_update_BundledUpdateStatus& response);
-
-  // Async
-  Status Apply(const pw_protobuf_Empty& request,
-               pw_software_update_BundledUpdateStatus& response);
-
-  // Currently sync, should be async.
-  // TODO: Make this async to support aborting verify/apply.
-  Status Abort(const pw_protobuf_Empty& request,
-               pw_software_update_BundledUpdateStatus& response);
-
-  // Sync
-  Status Reset(const pw_protobuf_Empty& request,
-               pw_software_update_BundledUpdateStatus& response);
-
-  // Notify the service that the bundle transfer has completed. The service has
-  // no way to know when the bundle transfer completes, so users must invoke
-  // this method in their transfer completion handler.
-  //
-  // After this call, the service will be in TRANSFERRED state if and only if
-  // it was in the TRANSFERRING state.
-  void NotifyTransferSucceeded();
-
-  // TODO(davidrogers) Add a MaybeFinishApply() method that is called after
-  // reboot to finish any need apply and verify work.
-
-  // TODO:
-  // VerifyProgress - to update % complete.
-  // ApplyProgress - to update % complete.
-
- private:
-  // Top-level lock for OTA state coherency. May be held for extended periods.
-  sync::Mutex mutex_;
-  BundledUpdateBackend& backend_ PW_GUARDED_BY(mutex_);
-  UpdateBundleAccessor& bundle_ PW_GUARDED_BY(mutex_);
-  bool bundle_open_ PW_GUARDED_BY(mutex_);
-  work_queue::WorkQueue& work_queue_ PW_GUARDED_BY(mutex_);
-  bool work_enqueued_ PW_GUARDED_BY(mutex_);
-
-  // Nested lock for safe status updates and queries.
-  sync::Mutex status_mutex_ PW_ACQUIRED_AFTER(mutex_);
-  pw_software_update_BundledUpdateStatus unsafe_status_
-      PW_GUARDED_BY(status_mutex_);
-  sync::Borrowable<pw_software_update_BundledUpdateStatus, sync::Mutex> status_;
-
-  void DoVerify() PW_LOCKS_EXCLUDED(status_mutex_);
-  void DoApply() PW_LOCKS_EXCLUDED(status_mutex_);
-  void Finish(_pw_software_update_BundledUpdateResult_Enum result)
-      PW_EXCLUSIVE_LOCKS_REQUIRED(mutex_) PW_LOCKS_EXCLUDED(status_mutex_);
-  bool IsFinished() PW_EXCLUSIVE_LOCKS_REQUIRED(mutex_)
-      PW_LOCKS_EXCLUDED(status_mutex_) {
-    return status_.acquire()->state ==
-           pw_software_update_BundledUpdateState_Enum_FINISHED;
-  }
-};
-
-}  // namespace pw::software_update
diff --git a/pw_software_update/public/pw_software_update/config.h b/pw_software_update/public/pw_software_update/config.h
deleted file mode 100644
index 3eaf18c..0000000
--- a/pw_software_update/public/pw_software_update/config.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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
-
-// The size of the buffer to create on stack for streaming manifest data from
-// the bundle reader.
-#define WRITE_MANIFEST_STREAM_PIPE_BUFFER_SIZE 8
-
-// The maximum allowed length of a target name.
-#define MAX_TARGET_NAME_LENGTH 32
-
-// The maximum allowed payload size in bytes. This is used to mitigate DoS
-// attacks.
-#ifndef PW_SOFTWARE_UPDATE_MAX_TARGET_PAYLOAD_SIZE
-#define PW_SOFTWARE_UPDATE_MAX_TARGET_PAYLOAD_SIZE (100 * 1024 * 1024)
-#endif  // PW_SOFTWARE_UPDATE_MAX_TARGET_PAYLOAD_SIZE
-
-// Not recommended. Disable compilation of bundle verification.
-#ifndef PW_SOFTWARE_UPDATE_DISABLE_BUNDLE_VERIFICATION
-#define PW_SOFTWARE_UPDATE_DISABLE_BUNDLE_VERIFICATION (false)
-#endif  // PW_SOFTWARE_UPDATE_DISABLE_BUNDLE_VERIFICATION
-
-// Whether to support bundle "personalization", which is a feature that
-// strips some or all target files that a device claims to already have from an
-// incoming bundle in order to improve performance.
-#ifndef PW_SOFTWARE_UPDATE_WITH_PERSONALIZATION
-#define PW_SOFTWARE_UPDATE_WITH_PERSONALIZATION (true)
-#endif  // PW_SOFTWARE_UPDATE_WITH_PERSONALIZATION
\ No newline at end of file
diff --git a/pw_software_update/public/pw_software_update/manifest_accessor.h b/pw_software_update/public/pw_software_update/manifest_accessor.h
deleted file mode 100644
index 05a75cc..0000000
--- a/pw_software_update/public/pw_software_update/manifest_accessor.h
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_protobuf/message.h"
-#include "pw_result/result.h"
-#include "pw_stream/stream.h"
-
-namespace pw::software_update {
-// ManifestAccessor exposes manifest information from either a *verified* update
-// bundle (`message UpdateBundle`) or a *trusted* on-device manifest
-// (`message Manifest`).
-//
-// Instantiation MUST go-through the UpdateBundleAccessor class. e.g.:
-//
-// ManifestAccessor manifest = bundle.GetManifest();
-// PW_TRY(manifest.status());  // Fails if `bundle` is not yet verified.
-class ManifestAccessor {
- public:
-  ManifestAccessor() = default;
-
-  Status status() { return targets_metadata_.status(); }
-  bool ok() { return status().ok(); }
-
-  // Retrieves the "user manifest" blob, which is product specific and optional.
-  pw::stream::IntervalReader GetUserManifest() {
-    return user_manifest_.GetBytesReader();
-  }
-
-  // Enumerates all target files as a list of `message TargetFile{...}`.
-  protobuf::RepeatedMessages GetTargetFiles();
-
-  // Given a name, return a `message TargetFile{...}` descriptor.
-  protobuf::Message GetTargetFile(protobuf::String name);
-  protobuf::Message GetTargetFile(std::string_view name);
-
-  // Returns the manifest version number.
-  protobuf::Uint32 GetVersion();
-
-  // TODO(alizhang): Deprecate WriteManifest() once backend code has changed
-  // to UpdateBundleAccessor::PersistManifest() where the backend is given
-  // chances to prepare and release the manifest writer.
-  Status WriteManifest(stream::Writer& writer) { return Export(writer); }
-
- private:
-  friend class UpdateBundleAccessor;
-
-  protobuf::Message targets_metadata_;
-  protobuf::Bytes user_manifest_;
-
-  ManifestAccessor(Status status) : targets_metadata_(status) {}
-  ManifestAccessor(protobuf::Message targets_metadata,
-                   protobuf::Bytes user_manifest)
-      : targets_metadata_(targets_metadata), user_manifest_(user_manifest){};
-
-  // Constructs a `ManifestAccessor` from an update bundle.
-  static ManifestAccessor FromBundle(protobuf::Message bundle);
-
-  // Constructs a `ManifestAccessor` from a saved `message Manifest{...}`.
-  static ManifestAccessor FromManifest(protobuf::Message manifest);
-
-  // Exports a serialized `message Manifest{...}`.
-  Status Export(stream::Writer& writer);
-};
-
-}  // namespace pw::software_update
diff --git a/pw_software_update/public/pw_software_update/update_bundle_accessor.h b/pw_software_update/public/pw_software_update/update_bundle_accessor.h
deleted file mode 100644
index d2b80c0..0000000
--- a/pw_software_update/public/pw_software_update/update_bundle_accessor.h
+++ /dev/null
@@ -1,215 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstddef>
-
-#include "pw_blob_store/blob_store.h"
-#include "pw_protobuf/map_utils.h"
-#include "pw_protobuf/message.h"
-#include "pw_software_update/bundled_update_backend.h"
-#include "pw_software_update/manifest_accessor.h"
-
-namespace pw::software_update {
-class BundledUpdateBackend;
-
-// Name of the top-level Targets metadata.
-constexpr std::string_view kTopLevelTargetsName = "targets";
-
-// Name of the "user manifest" target file. The "user manifest" is a product
-// specific blob that is opaque to upstream but need to be passed around in
-// manifest handling (for now).
-constexpr std::string_view kUserManifestTargetFileName = "user_manifest";
-
-// UpdateBundleAccessor is the trusted custodian of a staged incoming update
-// bundle.
-//
-// It takes exclusive ownership of the blob_store that represents a staged,
-// *untrusted* bundle, and presents convenient and *trusted* accessors.
-//
-// ALL ACCESS to the staged update bundle MUST GO THROUGH the
-// `UpdateBundleAccessor`.
-class UpdateBundleAccessor {
- public:
-  // UpdateBundleAccessor
-  // blob_store - The staged incoming software update bundle.
-  // backend - Project-specific BundledUpdateBackend.
-  // disable_verification - Disable verification.
-  constexpr UpdateBundleAccessor(blob_store::BlobStore& blob_store,
-                                 BundledUpdateBackend& backend,
-                                 bool disable_verification = false)
-      : blob_store_(blob_store),
-        blob_store_reader_(blob_store_),
-        backend_(backend),
-        disable_verification_(disable_verification) {}
-
-  // Opens and verifies the software update bundle.
-  //
-  // Verification covers the following:
-  //
-  // 1. If a Root metadata is included with the incoming bundle, the Root
-  //    metadata will be verified and used as the new Root metadata to verify
-  //    other metadata in the bundle.
-  //
-  // 2. The Targets metadata is verified using the Root metadata.
-  //
-  // 3. All target payloads referenced in the Targets metadata are verified.
-  //
-  // Limitations and customizations (compared to standard TUF):
-  //
-  // 1. Does not yet support arbitrary Root key rotations. Which means
-  //    There is only one (reliable) chance to rotate the Root key for all
-  //    devices. Rotation of the Targets key is still unlimited.
-  // 2. Timestamp and Snapshot metadata are not used or supported.
-  // 3. Assumes a single top-level Targets metadata and no delegations.
-  // 4. The top-level Targets metadata doubles as the software update
-  //    "manifest". Anti-rollback IS supported via the Targets metadata version.
-  // 5. Supports "personalization", where the staged bundle may have been
-  //    stripped of any target payloads that the device already have. For those
-  //    personalized-out targets, verification relies on the cached manifest of
-  //    a previous successful update to verify target length and hash.
-  //
-  // Returns:
-  // OK - Bundle was successfully opened and verified.
-  Status OpenAndVerify();
-
-  // Closes the bundle by invalidating the verification and closing
-  // the reader to release the read-only lock
-  //
-  // Returns:
-  // OK - success.
-  // DATA_LOSS - Error writing data or fail to verify written data.
-  Status Close();
-
-  // Writes out the manifest of the staged bundle via a backend-supplied writer.
-  //
-  // Returns:
-  // FAILED_PRECONDITION - Bundle is not open and verified.
-  // TODO(pwbug/456): Add other error codes if necessary.
-  Status PersistManifest();
-
-  // Returns a reader for the (verified) payload bytes of a specified target
-  // file.
-  //
-  // Returns:
-  // A reader instance for the target file.
-  // TODO(pwbug/456): Figure out a way to propagate error.
-  stream::IntervalReader GetTargetPayload(std::string_view target_name);
-  stream::IntervalReader GetTargetPayload(protobuf::String target_name);
-
-  // Exposes "manifest" information from the incoming update bundle once it has
-  // passed verification.
-  ManifestAccessor GetManifest();
-
-  // Returns the total number of bytes of all target payloads listed in the
-  // manifest *AND* exists in the bundle.
-  Result<uint64_t> GetTotalPayloadSize();
-
- private:
-  blob_store::BlobStore& blob_store_;
-  blob_store::BlobStore::BlobReader blob_store_reader_;
-  BundledUpdateBackend& backend_;
-  protobuf::Message bundle_;
-  // The current, cached, trusted `SignedRootMetadata{}`.
-  protobuf::Message trusted_root_;
-  bool disable_verification_;
-  bool bundle_verified_ = false;
-
-  // Opens the bundle for read-only access and readies the parser.
-  Status DoOpen();
-
-  // Performs TUF and downstream custom verification.
-  Status DoVerify();
-
-  // The method checks whether the update bundle contains a root metadata
-  // different from the on-device one. If it does, it performs the following
-  // verification and upgrade flow:
-  //
-  // 1. Verify the signatures according to the on-device trusted
-  //    root metadata obtained from the backend.
-  // 2. Verify content of the new root metadata, including:
-  //    1) Check role magic field.
-  //    2) Check signature requirement. Specifically, check that no key is
-  //       reused across different roles and keys are unique in the same
-  //       requirement.
-  //    3) Check key mapping. Specifically, check that all keys are unique,
-  //       ECDSA keys, and the key ids are exactly the SHA256 of `key type +
-  //       key scheme + key value`.
-  // 3. Verify the signatures against the new root metadata.
-  // 4. Check rollback.
-  // 5. Update on-device root metadata.
-  Status UpgradeRoot();
-
-  // The method verifies the top-level targets metadata against the trusted
-  // root. The verification includes the following:
-  //
-  // 1. Verify the signatures of the targets metadata.
-  // 2. Check the content of the targets metadata.
-  // 3. Check rollback against the version from on-device manifest, if one
-  //    exists (the manifest may be reset in the case of key rotation).
-  Status VerifyTargetsMetadata();
-
-  // A helper to get the on-device trusted root metadata. It returns an
-  // instance of SignedRootMetadata proto message.
-  protobuf::Message GetOnDeviceTrustedRoot();
-
-  // A helper to get an accessor to the on-device manifest. The on-device
-  // manifest is a serialized `message Manifest{...}` that represents the
-  // current running firmware of the device. The on-device manifest storage
-  // MUST meet the following requirements.
-  //
-  // 1. MUST NOT get wiped by a factory reset, otherwise a FDR can be used
-  //    to circumvent anti-rollback check.
-  // 2. MUST be kept in-sync with the actual firmware on-device. If any
-  //    mechanism is used to modify the firmware (e.g. via flashing), the
-  //    on-device manifest MUST be updated to reflect the change as well.
-  //    The on-device manifest CAN be erased if updating it is too cumbersome
-  //    BUT ONLY ON DEV DEVICES as erasing the on-device manifest defeats
-  //    anti-rollback.
-  // 3. MUST be integrity-protected and checked. Corrupted on-device manifest
-  //    cannot be used as it may brick a device as a result of anti-rollback
-  //    check. Integrity check is added and enforced by the backend via
-  //    `BundledUpdateBackend` callbacks.
-  //
-  ManifestAccessor GetOnDeviceManifest();
-
-  // Verify all targets referenced in the manifest (Targets metadata) has a
-  // payload blob either within the bundle or on-device, in both cases
-  // measuring up to the length and hash recorded in the manifest.
-  Status VerifyTargetsPayloads();
-
-  // Verify a target specified by name measures up to the expected length and
-  // SHA256 hash. Additionally call the backend to perform any product-specific
-  // validations.
-  Status VerifyTargetPayload(ManifestAccessor manifest,
-                             std::string_view name,
-                             protobuf::Uint64 expected_length,
-                             protobuf::Bytes expected_sha256);
-
-  // For a target the payload of which is included in the bundle, verify
-  // it measures up to the expected length and sha256 hash.
-  Status VerifyInBundleTargetPayload(protobuf::Uint64 expected_length,
-                                     protobuf::Bytes expected_sha256,
-                                     stream::IntervalReader payload_reader);
-
-  // For a target with no corresponding payload in the bundle, verify
-  // its on-device payload bytes measures up to the expected length and sha256
-  // hash.
-  Status VerifyOutOfBundleTargetPayload(std::string_view name,
-                                        protobuf::Uint64 expected_length,
-                                        protobuf::Bytes expected_sha256);
-};
-
-}  // namespace pw::software_update
diff --git a/pw_software_update/py/BUILD.gn b/pw_software_update/py/BUILD.gn
deleted file mode 100644
index 6450989..0000000
--- a/pw_software_update/py/BUILD.gn
+++ /dev/null
@@ -1,53 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/python.gni")
-
-pw_python_package("py") {
-  generate_setup = {
-    metadata = {
-      name = "pw_software_update"
-      version = "0.0.1"
-    }
-    options = {
-      install_requires = [ "cryptography" ]
-    }
-  }
-
-  sources = [
-    "pw_software_update/__init__.py",
-    "pw_software_update/dev_sign.py",
-    "pw_software_update/generate_test_bundle.py",
-    "pw_software_update/keys.py",
-    "pw_software_update/metadata.py",
-    "pw_software_update/root_metadata.py",
-    "pw_software_update/update_bundle.py",
-    "pw_software_update/verify.py",
-  ]
-
-  tests = [
-    "dev_sign_test.py",
-    "keys_test.py",
-    "metadata_test.py",
-    "root_metadata_test.py",
-    "update_bundle_test.py",
-    "verify_test.py",
-  ]
-
-  pylintrc = "$dir_pigweed/.pylintrc"
-
-  proto_library = "..:protos"
-}
diff --git a/pw_software_update/py/dev_sign_test.py b/pw_software_update/py/dev_sign_test.py
deleted file mode 100644
index 5149542..0000000
--- a/pw_software_update/py/dev_sign_test.py
+++ /dev/null
@@ -1,87 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Unit tests for pw_software_update/dev_sign.py."""
-
-import unittest
-
-from pw_software_update.dev_sign import sign_root_metadata, sign_update_bundle
-from pw_software_update.root_metadata import (gen_root_metadata, RootKeys,
-                                              TargetsKeys)
-from pw_software_update.tuf_pb2 import SignedRootMetadata, SignedTargetsMetadata
-from pw_software_update.update_bundle_pb2 import UpdateBundle
-
-
-class RootMetadataSigningTest(unittest.TestCase):
-    """Test Root Metadata signing."""
-    def setUp(self) -> None:
-        self.root_key = (
-            b'-----BEGIN PRIVATE KEY-----\n'
-            b'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgyk3DEQdl346MS5N/'
-            b'quNEneJa4HxkJBETGzlEEKkCmZOhRANCAAThdY5PejbtM2p6HtgXs/7YSsvPMWZz'
-            b'9Ui1gAEKrDseHnPzC02MbKjQadRIFZ4hKDcsyz9aM6QKLCNrCOqYjw6t'
-            b'\n-----END PRIVATE KEY-----\n')
-
-        self.root_key_public = (
-            b'-----BEGIN PUBLIC KEY-----\n'
-            b'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4XWOT3o27TNqeh7YF7P+2ErLzzFm'
-            b'c/VItYABCqw7Hh5z8wtNjGyo0GnUSBWeISg3LMs/WjOkCiwjawjqmI8OrQ=='
-            b'\n-----END PUBLIC KEY-----\n')
-
-        self.targets_key_public = (
-            b'-----BEGIN PUBLIC KEY-----\n'
-            b'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9UM6qRZJ0gIWwLjo8tjbrrBTlKXg'
-            b'ukwVjOlnguSSiYMrN4MDqMlNDnaJgLvcCuiNUKHu9Oj1DG1i6ckNdE4VTA=='
-            b'\n-----END PUBLIC KEY-----\n')
-
-        root_metadata = gen_root_metadata(
-            RootKeys([self.root_key_public]),
-            TargetsKeys([self.targets_key_public]))
-        self.root_metadata = SignedRootMetadata(
-            serialized_root_metadata=root_metadata.SerializeToString())
-
-        self.another_signing_key = (
-            b'-----BEGIN PRIVATE KEY-----\n'
-            b'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg5OIalt8DcZYeEf/4'
-            b'5/iIX6jqM0I5t4dScAdcmgNF9vKhRANCAAQdMBqcn//pXIwss9nLEVjz+4Mz4oVt'
-            b'hKTFLqlzwKHZngL/0IyH6FC+4bq5CnR43WADPAyJHo18V0NCO/8QFQ2c'
-            b'\n-----END PRIVATE KEY-----\n')
-
-    def test_typical_signing(self):
-        signed = sign_root_metadata(self.root_metadata, self.root_key)
-        self.assertEqual(len(signed.signatures), 1)
-        signed = sign_root_metadata(signed, self.another_signing_key)
-        self.assertEqual(len(signed.signatures), 2)
-
-
-class BundleSigningTest(unittest.TestCase):
-    """Test UpdateBundle signing."""
-    def setUp(self):
-        self.targets_key = (
-            b'-----BEGIN PRIVATE KEY-----\n'
-            b'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkMEZ0u84HzC51nhh'
-            b'f2ZykPj6WfAjBxXVWndjVdn6bh6hRANCAAT1QzqpFknSAhbAuOjy2NuusFOUpeC6'
-            b'TBWM6WeC5JKJgys3gwOoyU0OdomAu9wK6I1Qoe706PUMbWLpyQ10ThVM'
-            b'\n-----END PRIVATE KEY-----\n')
-
-        self.update_bundle = UpdateBundle(targets_metadata=dict(
-            targets=SignedTargetsMetadata(
-                serialized_targets_metadata=b'blahblah')))
-
-    def test_typical_signing(self):
-        signed = sign_update_bundle(self.update_bundle, self.targets_key)
-        self.assertEqual(len(signed.targets_metadata['targets'].signatures), 1)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_software_update/py/keys_test.py b/pw_software_update/py/keys_test.py
deleted file mode 100644
index 214b62d..0000000
--- a/pw_software_update/py/keys_test.py
+++ /dev/null
@@ -1,193 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Unit tests for pw_software_update/keys.py."""
-
-from pathlib import Path
-import tempfile
-import unittest
-
-from pw_software_update import keys
-from pw_software_update.tuf_pb2 import Key, KeyType, KeyScheme
-
-
-class KeyGenTest(unittest.TestCase):
-    """Test the generation of keys."""
-    def test_ecdsa_keygen(self):
-        """Test ECDSA key generation."""
-        with tempfile.TemporaryDirectory() as tempdir_name:
-            temp_root = Path(tempdir_name)
-            private_key_filename = (temp_root / 'test_key')
-            public_key_filename = (temp_root / 'test_key.pub')
-
-            keys.gen_ecdsa_keypair(private_key_filename)
-
-            self.assertTrue(private_key_filename.exists())
-            self.assertTrue(public_key_filename.exists())
-            public_key = keys.import_ecdsa_public_key(
-                public_key_filename.read_bytes())
-            self.assertEqual(public_key.key.key_type,
-                             KeyType.ECDSA_SHA2_NISTP256)
-            self.assertEqual(public_key.key.scheme,
-                             KeyScheme.ECDSA_SHA2_NISTP256_SCHEME)
-
-
-class KeyIdTest(unittest.TestCase):
-    """Test Key ID generations """
-    def test_256bit_length(self):
-        key_id = keys.gen_key_id(
-            Key(key_type=KeyType.ECDSA_SHA2_NISTP256,
-                scheme=KeyScheme.ECDSA_SHA2_NISTP256_SCHEME,
-                keyval=b'public_key bytes'))
-        self.assertEqual(len(key_id), 32)
-
-    def test_different_keyval(self):
-        key1 = Key(key_type=KeyType.ECDSA_SHA2_NISTP256,
-                   scheme=KeyScheme.ECDSA_SHA2_NISTP256_SCHEME,
-                   keyval=b'key 1 bytes')
-        key2 = Key(key_type=KeyType.ECDSA_SHA2_NISTP256,
-                   scheme=KeyScheme.ECDSA_SHA2_NISTP256_SCHEME,
-                   keyval=b'key 2 bytes')
-
-        key1_id, key2_id = keys.gen_key_id(key1), keys.gen_key_id(key2)
-        self.assertNotEqual(key1_id, key2_id)
-
-    def test_different_key_type(self):
-        key1 = Key(key_type=KeyType.RSA,
-                   scheme=KeyScheme.ECDSA_SHA2_NISTP256_SCHEME,
-                   keyval=b'key bytes')
-        key2 = Key(key_type=KeyType.ECDSA_SHA2_NISTP256,
-                   scheme=KeyScheme.ECDSA_SHA2_NISTP256_SCHEME,
-                   keyval=b'key bytes')
-
-        key1_id, key2_id = keys.gen_key_id(key1), keys.gen_key_id(key2)
-        self.assertNotEqual(key1_id, key2_id)
-
-    def test_different_scheme(self):
-        key1 = Key(key_type=KeyType.ECDSA_SHA2_NISTP256,
-                   scheme=KeyScheme.ECDSA_SHA2_NISTP256_SCHEME,
-                   keyval=b'key bytes')
-        key2 = Key(key_type=KeyType.ECDSA_SHA2_NISTP256,
-                   scheme=KeyScheme.ED25519_SCHEME,
-                   keyval=b'key bytes')
-
-        key1_id, key2_id = keys.gen_key_id(key1), keys.gen_key_id(key2)
-        self.assertNotEqual(key1_id, key2_id)
-
-
-class KeyImportTest(unittest.TestCase):
-    """Test key importing"""
-    def setUp(self):
-        # Generated with:
-        # $> openssl ecparam -name prime256v1 -genkey -noout -out priv.pem
-        # $> openssl ec -in priv.pem -pubout -out pub.pem
-        # $> cat pub.pem
-        self.valid_nistp256_pem_bytes = (
-            b'-----BEGIN PUBLIC KEY-----\n'
-            b'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKmK5mJwMV7eimA6MfFQL2q6KbZDr'
-            b'SnWwoeHvXB/aZBnwF422OLifuOuMjEUEHrNMmoekcua+ulHW41X3AgbvIw==\n'
-            b'-----END PUBLIC KEY-----\n')
-
-        # Generated with:
-        # $> openssl ecparam -name secp384r1 -genkey -noout -out priv.pem
-        # $> openssl ec -in priv.pem -pubout -out pub.pem
-        # $> cat pub.pem
-        self.valid_secp384r1_pem_bytes = (
-            b'-----BEGIN PUBLIC KEY-----\n'
-            b'MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE6xs+TEjb2/vIzs4AzSm2CSUWpJMCPAts'
-            b'e+gwvGwFrr2bXKHVLNCxr5/Va6rD0nDmB2NOiJwAXX1Z8CB5wqLLB31emCBFRb5i'
-            b'1LjZu8Bp3hrWOL7uvXer8uExnSfTKAoT\n'
-            b'-----END PUBLIC KEY-----\n')
-
-        # Replaces "MF" with "MM"
-        self.tampered_nistp256_pem_bytes = (
-            b'-----BEGIN PUBLIC KEY-----\n'
-            b'MMkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKmK5mJwMV7eimA6MfFQL2q6KbZDr'
-            b'SnWwoeHvXB/aZBnwF422OLifuOuMjEUEHrNMmoekcua+ulHW41X3AgbvIw==\n'
-            b'-----END PUBLIC KEY-----\n')
-
-        self.rsa_2048_pem_bytes = (
-            b'-----BEGIN PUBLIC KEY-----\n'
-            b'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsu0+ol90Ri2BQ5TE9ife'
-            b'6aAmAUMzvAD2b3cnWaTBGXKpi7O9PKnfKbMVf/nJcWsyw2Bj8uStx3oV98U6owLO'
-            b'vsQwyFKVgLZdrXo2qv0L6ljBfCLJxnDhjesEV/oG04dwdN7qyPwAZtpVBCrC7Qi8'
-            b'2rkTnzTQi/1slUxRjliDDhgEdqP7dHbCr7QXNIAA0HFRiOqYmHGD7HNKl67iYmAX'
-            b'd/Jv8GfZL/ykZstP6Ow1/ByP1ZKvrZvg2iXjC686hZXiMJLqmp0sIqLire82oW+8'
-            b'XFc1uyr1j20m+NI5Siy0G3RbfPXrVKyXIgAYPW12+a/BXR9SrqYJYcWwuOGbHZCM'
-            b'pwIDAQAB\n'
-            b'-----END PUBLIC KEY-----\n')
-
-    def test_valid_nistp256_key(self):
-        keys.import_ecdsa_public_key(self.valid_nistp256_pem_bytes)
-
-    def test_tampered_nistp256_key(self):
-        with self.assertRaises(ValueError):
-            keys.import_ecdsa_public_key(self.tampered_nistp256_pem_bytes)
-
-    def test_non_ec_key(self):
-        with self.assertRaises(TypeError):
-            keys.import_ecdsa_public_key(self.rsa_2048_pem_bytes)
-
-    def test_wrong_curve(self):
-        with self.assertRaises(TypeError):
-            keys.import_ecdsa_public_key(self.valid_secp384r1_pem_bytes)
-
-
-class SignatureVerificationTest(unittest.TestCase):
-    """ECDSA signing and verification test."""
-    def setUp(self):
-        # Generated with:
-        # $> openssl ecparam -name prime256v1 -genkey -noout -out priv.pem
-        # $> openssl ec -in priv.pem -pubout -out pub.pem
-        # $> cat priv.pem pub.pem
-        self.private_key_pem = (
-            b'-----BEGIN EC PRIVATE KEY-----\n'
-            b'MHcCAQEEIH9u1n4qAT59f7KRRl/ZB0Y/BUfS4blba+LONlF4s3ltoAoGCCqGSM49'
-            b'AwEHoUQDQgAEgKf3kY9Hi3hxIyqm2EkfqQvJkCijjlJSmEAJ1oAp0Godi5x2af+m'
-            b'cSNuBjpRcC8iW8x1/gizqyWlfAVrZV0XdA==\n'
-            b'-----END EC PRIVATE KEY-----\n')
-        self.public_key_pem = (
-            b'-----BEGIN PUBLIC KEY-----\n'
-            b'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgKf3kY9Hi3hxIyqm2EkfqQvJkCij'
-            b'jlJSmEAJ1oAp0Godi5x2af+mcSNuBjpRcC8iW8x1/gizqyWlfAVrZV0XdA==\n'
-            b'-----END PUBLIC KEY-----\n')
-
-        self.message = b'Hello Pigweed!'
-        self.tampered_message = b'Hell0 Pigweed!'
-
-    def test_good_signature(self):
-        sig = keys.create_ecdsa_signature(self.message, self.private_key_pem)
-        self.assertTrue(
-            keys.verify_ecdsa_signature(
-                sig.sig, self.message,
-                keys.import_ecdsa_public_key(self.public_key_pem).key))
-
-    def test_tampered_message(self):
-        sig = keys.create_ecdsa_signature(self.message, self.private_key_pem)
-        self.assertFalse(
-            keys.verify_ecdsa_signature(
-                sig.sig, self.tampered_message,
-                keys.import_ecdsa_public_key(self.public_key_pem).key))
-
-    def test_tampered_signature(self):
-        sig = keys.create_ecdsa_signature(self.message, self.private_key_pem)
-        tampered_sig = bytearray(sig.sig)
-        tampered_sig[0] ^= 1
-        self.assertFalse(
-            keys.verify_ecdsa_signature(
-                tampered_sig, self.message,
-                keys.import_ecdsa_public_key(self.public_key_pem).key))
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_software_update/py/metadata_test.py b/pw_software_update/py/metadata_test.py
deleted file mode 100644
index fd54b12..0000000
--- a/pw_software_update/py/metadata_test.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Unit tests for pw_software_update/metadata.py."""
-
-import unittest
-
-from pw_software_update import metadata
-from pw_software_update.tuf_pb2 import HashFunction
-
-
-class GenTargetsMetadataTest(unittest.TestCase):
-    """Test the generation of targets metadata."""
-    def test_multiple_targets(self):
-        """Checks that multiple targets generates multiple TargetFiles."""
-        target_payloads = {
-            'foo': b'\x1e\xe7',
-            'bar': b'\x12\x34',
-        }
-        targets_metadata = metadata.gen_targets_metadata(
-            target_payloads, (HashFunction.SHA256, ), version=42)
-        self.assertEqual(2, len(targets_metadata.target_files))
-        self.assertEqual(metadata.RoleType.TARGETS.value,
-                         targets_metadata.common_metadata.role)
-        self.assertEqual(42, targets_metadata.common_metadata.version)
-
-
-class GenHashesTest(unittest.TestCase):
-    """Test the generation of hashes."""
-    def test_sha256(self):
-        """Checks that SHA256 hashes are computed and stored properly."""
-        data = b'\x1e\xe7'
-        sha256_hash = metadata.gen_hashes(data, (HashFunction.SHA256, ))[0]
-        self.assertEqual(
-            '9f36ce605a3b28110d2a25ec36bdfff86059086cbd53c9efc1428ef01070515d',
-            sha256_hash.hash.hex())
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_software_update/py/pw_software_update/__init__.py b/pw_software_update/py/pw_software_update/__init__.py
deleted file mode 100644
index a643e27..0000000
--- a/pw_software_update/py/pw_software_update/__init__.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""pw_software_update"""
diff --git a/pw_software_update/py/pw_software_update/dev_sign.py b/pw_software_update/py/pw_software_update/dev_sign.py
deleted file mode 100644
index 95e728e..0000000
--- a/pw_software_update/py/pw_software_update/dev_sign.py
+++ /dev/null
@@ -1,112 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Metadata signing facilities."""
-
-import argparse
-from pathlib import Path
-
-from pw_software_update import keys
-from pw_software_update.tuf_pb2 import SignedRootMetadata
-from pw_software_update.update_bundle_pb2 import UpdateBundle
-
-
-def sign_root_metadata(root_metadata: SignedRootMetadata,
-                       root_key_pem: bytes) -> SignedRootMetadata:
-    """Signs or re-signs a Root Metadata.
-
-    Args:
-      root_metadata: A SignedRootMetadata to be signed/re-signed.
-      root_key_pem: The Root signing key in PEM.
-    """
-
-    signature = keys.create_ecdsa_signature(
-        root_metadata.serialized_root_metadata, root_key_pem)
-    root_metadata.signatures.append(signature)
-
-    return root_metadata
-
-
-def sign_update_bundle(bundle: UpdateBundle,
-                       targets_key_pem: bytes) -> UpdateBundle:
-    """Signs or re-signs an update bundle.
-
-    Args:
-      bundle: An UpdateBundle to be signed/re-signed.
-      targets_key_pem: The targets signing key in PEM.
-    """
-    bundle.targets_metadata['targets'].signatures.append(
-        keys.create_ecdsa_signature(
-            bundle.targets_metadata['targets'].serialized_targets_metadata,
-            targets_key_pem))
-    return bundle
-
-
-def parse_args():
-    """Parse CLI arguments."""
-    parser = argparse.ArgumentParser(description=__doc__)
-
-    parser.add_argument('--root-metadata',
-                        type=Path,
-                        required=False,
-                        help='Path to the root metadata to be signed')
-
-    parser.add_argument('--bundle',
-                        type=Path,
-                        required=False,
-                        help='Path to the bundle to be signed')
-
-    parser.add_argument(
-        '--output',
-        type=Path,
-        required=False,
-        help=('Path to save the signed root metadata or bundle '
-              'to; Defaults to the input path if unspecified'))
-
-    parser.add_argument('--key',
-                        type=Path,
-                        required=True,
-                        help='Path to the signing key')
-
-    args = parser.parse_args()
-
-    if not (args.root_metadata or args.bundle):
-        parser.error(
-            'either "--root-metadata" or "--bundle" must be specified')
-    if args.root_metadata and args.bundle:
-        parser.error('"--root-metadata" and "--bundle" are mutually exclusive')
-
-    return args
-
-
-def main(root_metadata: Path, bundle: Path, key: Path, output: Path) -> None:
-    """Signs or re-signs a root metadata or an update bundle."""
-    if root_metadata:
-        signed_root_metadata = sign_root_metadata(
-            SignedRootMetadata.FromString(root_metadata.read_bytes()),
-            key.read_bytes())
-
-        if not output:
-            output = root_metadata
-        output.write_bytes(signed_root_metadata.SerializeToString())
-    else:
-        signed_bundle = sign_update_bundle(
-            UpdateBundle.FromString(bundle.read_bytes()), key.read_bytes())
-
-        if not output:
-            output = bundle
-        output.write_bytes(signed_bundle.SerializeToString())
-
-
-if __name__ == '__main__':
-    main(**vars(parse_args()))
diff --git a/pw_software_update/py/pw_software_update/generate_test_bundle.py b/pw_software_update/py/pw_software_update/generate_test_bundle.py
deleted file mode 100644
index 304e0bd..0000000
--- a/pw_software_update/py/pw_software_update/generate_test_bundle.py
+++ /dev/null
@@ -1,441 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Script for generating test bundles"""
-
-import argparse
-import subprocess
-import sys
-from typing import Dict
-
-from pw_software_update import dev_sign, keys, metadata, root_metadata
-from pw_software_update.update_bundle_pb2 import Manifest, UpdateBundle
-from pw_software_update.tuf_pb2 import (RootMetadata, SignedRootMetadata,
-                                        TargetsMetadata, SignedTargetsMetadata)
-
-from cryptography.hazmat.primitives.asymmetric import ec
-from cryptography.hazmat.primitives import serialization
-
-HEADER = """// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_bytes/span.h"
-
-"""
-
-TEST_DEV_KEY = """-----BEGIN PRIVATE KEY-----
-MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgVgMQBOTJyx1xOafy
-WTs2VkACf7Uo3RbP9Vun+oKXtMihRANCAATV7XJljxeUs2z2wqM5Q/kohAra1620
-zXT90N9a3UR+IHksTd1OA7wFq220IQB/e4eVzbcOprN0MMMuSgXMxL8p
------END PRIVATE KEY-----"""
-
-TEST_PROD_KEY = """-----BEGIN PRIVATE KEY-----
-MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg73MLNmB/fPNX75Pl
-YdynPtJkM2gGOWfIcHDuwuxSQmqhRANCAARpvjrXkjG2Fp+ZgREtxeTBBmJmWGS9
-8Ny2tXY+Qggzl77G7wvCNF5+koz7ecsV6sKjK+dFiAXOIdqlga7p2j0A
------END PRIVATE KEY-----"""
-
-TEST_TARGETS_DEV_KEY = """-----BEGIN PRIVATE KEY-----
-MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQggRCrido5vZOnkULH
-sxQDt9Qoe/TlEKoqa1bhO1HFbi6hRANCAASVwdXbGWM7+f/r+Z2W6Dbd7CQA0Cbb
-pkBv5PnA+DZnCkFhLW2kTn89zQv8W1x4m9maoINp9QPXQ4/nXlrVHqDg
------END PRIVATE KEY-----"""
-
-TEST_TARGETS_PROD_KEY = """-----BEGIN PRIVATE KEY-----
-MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgx2VdB2EsUKghuLMG
-RmxzqX2jnLTq5pxsFgO5Rrf5jlehRANCAASVijeDpemxVSlgZOOW0yvwE5QkXkq0
-geWonkusMP0+MXopnmN0QlpgaCnG40TSr/W+wFjRmNCklL4dXk01oCwD
------END PRIVATE KEY-----"""
-
-TEST_ROOT_VERSION = 2
-TEST_TARGETS_VERSION = 2
-
-USER_MANIFEST_FILE_NAME = 'user_manifest'
-
-TARGET_FILES = {
-    'file1': 'file 1 content'.encode(),
-    'file2': 'file 2 content'.encode(),
-    USER_MANIFEST_FILE_NAME: 'user manfiest content'.encode(),
-}
-
-
-def byte_array_declaration(data: bytes, name: str) -> str:
-    """Generates a byte C array declaration for a byte array"""
-    type_name = '[[maybe_unused]] const std::byte'
-    byte_str = ''.join([f'std::byte{{0x{b:02x}}},' for b in data])
-    array_body = f'{{{byte_str}}}'
-    return f'{type_name} {name}[] = {array_body};'
-
-
-def proto_array_declaration(proto, name: str) -> str:
-    """Generates a byte array declaration for a proto"""
-    return byte_array_declaration(proto.SerializeToString(), name)
-
-
-def private_key_public_pem_bytes(key: ec.EllipticCurvePrivateKey) -> bytes:
-    """Serializes the public part of a private key in PEM format"""
-    return key.public_key().public_bytes(
-        serialization.Encoding.PEM,
-        serialization.PublicFormat.SubjectPublicKeyInfo)
-
-
-def private_key_private_pem_bytes(key: ec.EllipticCurvePrivateKey) -> bytes:
-    """Serializes the private part of a private key in PEM format"""
-    return key.private_bytes(encoding=serialization.Encoding.PEM,
-                             format=serialization.PrivateFormat.PKCS8,
-                             encryption_algorithm=serialization.NoEncryption())
-
-
-class Bundle:
-    """A helper for test UpdateBundle generation"""
-    def __init__(self):
-        self._root_dev_key = serialization.load_pem_private_key(
-            TEST_DEV_KEY.encode(), None)
-        self._root_prod_key = serialization.load_pem_private_key(
-            TEST_PROD_KEY.encode(), None)
-        self._targets_dev_key = serialization.load_pem_private_key(
-            TEST_TARGETS_DEV_KEY.encode(), None)
-        self._targets_prod_key = serialization.load_pem_private_key(
-            TEST_TARGETS_PROD_KEY.encode(), None)
-        self._payloads: Dict[str, bytes] = {}
-        # Adds some update files.
-        for key, value in TARGET_FILES.items():
-            self.add_payload(key, value)
-
-    def add_payload(self, name: str, payload: bytes) -> None:
-        """Adds a payload to the bundle"""
-        self._payloads[name] = payload
-
-    def generate_dev_root_metadata(self) -> RootMetadata:
-        """Generates a root metadata with the dev key"""
-        # The dev root metadata contains both the prod and the dev public key,
-        # so that it can rotate to prod. But it will only use a dev targets
-        # key.
-        return root_metadata.gen_root_metadata(
-            root_metadata.RootKeys([
-                private_key_public_pem_bytes(self._root_dev_key),
-                private_key_public_pem_bytes(self._root_prod_key),
-            ]),
-            root_metadata.TargetsKeys(
-                [private_key_public_pem_bytes(self._targets_dev_key)]),
-            TEST_ROOT_VERSION)
-
-    def generate_prod_root_metadata(self) -> RootMetadata:
-        """Generates a root metadata with the prod key"""
-        # The prod root metadta contains only the prod public key and uses the
-        # prod targets key
-        return root_metadata.gen_root_metadata(
-            root_metadata.RootKeys(
-                [private_key_public_pem_bytes(self._root_prod_key)]),
-            root_metadata.TargetsKeys(
-                [private_key_public_pem_bytes(self._targets_prod_key)]),
-            TEST_ROOT_VERSION)
-
-    def generate_dev_signed_root_metadata(self) -> SignedRootMetadata:
-        """Generates a dev signed root metadata"""
-        signed_root = SignedRootMetadata()
-        root_metadata_proto = self.generate_dev_root_metadata()
-        signed_root.serialized_root_metadata = \
-            root_metadata_proto.SerializeToString()
-        return dev_sign.sign_root_metadata(
-            signed_root, private_key_private_pem_bytes(self._root_dev_key))
-
-    def generate_prod_signed_root_metadata(
-            self,
-            root_metadata_proto: RootMetadata = None) -> SignedRootMetadata:
-        """Generates a root metadata signed by the prod key"""
-        if not root_metadata_proto:
-            root_metadata_proto = self.generate_prod_root_metadata()
-
-        signed_root = SignedRootMetadata(
-            serialized_root_metadata=root_metadata_proto.SerializeToString())
-
-        return dev_sign.sign_root_metadata(
-            signed_root, private_key_private_pem_bytes(self._root_prod_key))
-
-    def generate_targets_metadata(self) -> TargetsMetadata:
-        """Generates the targets metadata"""
-        targets = metadata.gen_targets_metadata(self._payloads,
-                                                metadata.DEFAULT_HASHES,
-                                                TEST_TARGETS_VERSION)
-        return targets
-
-    def generate_unsigned_bundle(
-            self,
-            targets_metadata: TargetsMetadata = None,
-            signed_root_metadata: SignedRootMetadata = None) -> UpdateBundle:
-        """Generate an unsigned (targets metadata) update bundle"""
-        bundle = UpdateBundle()
-
-        if not targets_metadata:
-            targets_metadata = self.generate_targets_metadata()
-
-        if signed_root_metadata:
-            bundle.root_metadata.CopyFrom(signed_root_metadata)
-
-        bundle.targets_metadata['targets'].CopyFrom(
-            SignedTargetsMetadata(serialized_targets_metadata=targets_metadata.
-                                  SerializeToString()))
-
-        for name, payload in self._payloads.items():
-            bundle.target_payloads[name] = payload
-
-        return bundle
-
-    def generate_dev_signed_bundle(
-            self,
-            targets_metadata_override: TargetsMetadata = None,
-            signed_root_metadata: SignedRootMetadata = None) -> UpdateBundle:
-        """Generate a dev signed update bundle"""
-        return dev_sign.sign_update_bundle(
-            self.generate_unsigned_bundle(targets_metadata_override,
-                                          signed_root_metadata),
-            private_key_private_pem_bytes(self._targets_dev_key))
-
-    def generate_prod_signed_bundle(
-            self,
-            targets_metadata_override: TargetsMetadata = None,
-            signed_root_metadata: SignedRootMetadata = None) -> UpdateBundle:
-        """Generate a prod signed update bundle"""
-        # The targets metadata in a prod signed bundle can only be verified
-        # by a prod signed root. Because it is signed by the prod targets key.
-        # The prod signed root however, can be verified by a dev root.
-        return dev_sign.sign_update_bundle(
-            self.generate_unsigned_bundle(targets_metadata_override,
-                                          signed_root_metadata),
-            private_key_private_pem_bytes(self._targets_prod_key))
-
-    def generate_manifest(self) -> Manifest:
-        """Generates the manifest"""
-        manifest = Manifest()
-        manifest.targets_metadata['targets'].CopyFrom(
-            self.generate_targets_metadata())
-        if USER_MANIFEST_FILE_NAME in self._payloads:
-            manifest.user_manifest = self._payloads[USER_MANIFEST_FILE_NAME]
-        return manifest
-
-
-def parse_args():
-    """Setup argparse."""
-    parser = argparse.ArgumentParser()
-    parser.add_argument("output_header",
-                        help="output path of the generated C header")
-    return parser.parse_args()
-
-
-def main() -> int:
-    """Main"""
-    # TODO(pwbug/456): Refactor the code so that each test bundle generation
-    # is done in a separate function or script.
-    # pylint: disable=too-many-locals
-    args = parse_args()
-
-    test_bundle = Bundle()
-
-    dev_signed_root = test_bundle.generate_dev_signed_root_metadata()
-    dev_signed_bundle = test_bundle.generate_dev_signed_bundle()
-    dev_signed_bundle_with_root = test_bundle.generate_dev_signed_bundle(
-        signed_root_metadata=dev_signed_root)
-    unsigned_bundle_with_root = test_bundle.generate_unsigned_bundle(
-        signed_root_metadata=dev_signed_root)
-    manifest_proto = test_bundle.generate_manifest()
-    prod_signed_root = \
-        test_bundle.generate_prod_signed_root_metadata()
-    prod_signed_bundle = test_bundle.generate_prod_signed_bundle(
-        None, prod_signed_root)
-    dev_signed_bundle_with_prod_root = test_bundle.generate_dev_signed_bundle(
-        signed_root_metadata=prod_signed_root)
-
-    # Generates a prod root metadata that fails signature verification against
-    # the dev root (i.e. it has a bad prod signature). This is done by making
-    # a bad prod signature.
-    bad_prod_signature = test_bundle.generate_prod_root_metadata()
-    signed_bad_prod_signature = \
-        test_bundle\
-            .generate_prod_signed_root_metadata(
-                bad_prod_signature)
-    # Compromises the signature.
-    signed_bad_prod_signature.signatures[0].sig = b'1' * 64
-    signed_bad_prod_signature_bundle = test_bundle.generate_prod_signed_bundle(
-        None, signed_bad_prod_signature)
-
-    # Generates a prod root metadtata that fails to verify itself. Specifically,
-    # the prod signature cannot be verified by the key in the incoming root
-    # metadata. This is done by dev signing a prod root metadata.
-    signed_mismatched_root_key_and_signature = SignedRootMetadata(
-        serialized_root_metadata=test_bundle.generate_prod_root_metadata(
-        ).SerializeToString())
-    dev_root_key = serialization.load_pem_private_key(TEST_DEV_KEY.encode(),
-                                                      None)
-    signature = keys.create_ecdsa_signature(
-        signed_mismatched_root_key_and_signature.serialized_root_metadata,
-        private_key_private_pem_bytes(dev_root_key))  # type: ignore
-    signed_mismatched_root_key_and_signature.signatures.append(signature)
-    mismatched_root_key_and_signature_bundle = test_bundle\
-        .generate_prod_signed_bundle(None,
-                                     signed_mismatched_root_key_and_signature)
-
-    # Generates a prod root metadata with rollback attempt.
-    root_rollback = test_bundle.generate_prod_root_metadata()
-    root_rollback.common_metadata.version = TEST_ROOT_VERSION - 1
-    signed_root_rollback = test_bundle.\
-        generate_prod_signed_root_metadata(root_rollback)
-    root_rollback_bundle = test_bundle.generate_prod_signed_bundle(
-        None, signed_root_rollback)
-
-    # Generates a bundle with a bad target signature.
-    bad_targets_siganture = test_bundle.generate_prod_signed_bundle(
-        None, prod_signed_root)
-    # Compromises the signature.
-    bad_targets_siganture.targets_metadata['targets'].signatures[
-        0].sig = b'1' * 64
-
-    # Generates a bundle with rollback attempt
-    targets_rollback = test_bundle.generate_targets_metadata()
-    targets_rollback.common_metadata.version = TEST_TARGETS_VERSION - 1
-    targets_rollback_bundle = test_bundle.generate_prod_signed_bundle(
-        targets_rollback, prod_signed_root)
-
-    # Generate bundles with mismatched hash
-    mismatched_hash_targets_bundles = []
-    # Generate bundles with mismatched file length
-    mismatched_length_targets_bundles = []
-    # Generate bundles with missing hash
-    missing_hash_targets_bundles = []
-    # Generate bundles with personalized out payload
-    personalized_out_bundles = []
-    # For each of the two files in `TARGET_FILES`, we generate a number of
-    # bundles each of which modify the target in the following way
-    # respectively:
-    # 1. Compromise its sha256 hash value in the targets metadata, so as to
-    #    test hash verification logic.
-    # 2. Remove the hashes, to trigger verification failure cause by missing
-    #    hashes.
-    # 3. Compromise the file length in the targets metadata.
-    # 4. Remove the payload to emulate being personalized out, so as to test
-    #    that it does not cause verification failure.
-    for idx, payload_file in enumerate(TARGET_FILES.items()):
-        mismatched_hash_targets = test_bundle.generate_targets_metadata()
-        mismatched_hash_targets.target_files[idx].hashes[0].hash = b'0' * 32
-        mismatched_hash_targets_bundle = test_bundle\
-            .generate_prod_signed_bundle(
-                mismatched_hash_targets, prod_signed_root)
-        mismatched_hash_targets_bundles.append(mismatched_hash_targets_bundle)
-
-        mismatched_length_targets = test_bundle.generate_targets_metadata()
-        mismatched_length_targets.target_files[idx].length = 1
-        mismatched_length_targets_bundle = test_bundle\
-            .generate_prod_signed_bundle(
-                mismatched_length_targets, prod_signed_root)
-        mismatched_length_targets_bundles.append(
-            mismatched_length_targets_bundle)
-
-        missing_hash_targets = test_bundle.generate_targets_metadata()
-        missing_hash_targets.target_files[idx].hashes.pop()
-        missing_hash_targets_bundle = test_bundle.generate_prod_signed_bundle(
-            missing_hash_targets, prod_signed_root)
-        missing_hash_targets_bundles.append(missing_hash_targets_bundle)
-
-        file_name, _ = payload_file
-        personalized_out_bundle = test_bundle.generate_prod_signed_bundle(
-            None, prod_signed_root)
-        personalized_out_bundle.target_payloads.pop(file_name)
-        personalized_out_bundles.append(personalized_out_bundle)
-
-    with open(args.output_header, 'w') as header:
-        header.write(HEADER)
-        header.write(
-            proto_array_declaration(dev_signed_bundle, 'kTestDevBundle'))
-        header.write(
-            proto_array_declaration(dev_signed_bundle_with_root,
-                                    'kTestDevBundleWithRoot'))
-        header.write(
-            proto_array_declaration(unsigned_bundle_with_root,
-                                    'kTestUnsignedBundleWithRoot'))
-        header.write(
-            proto_array_declaration(dev_signed_bundle_with_prod_root,
-                                    'kTestDevBundleWithProdRoot'))
-        header.write(
-            proto_array_declaration(manifest_proto, 'kTestBundleManifest'))
-        header.write(proto_array_declaration(dev_signed_root,
-                                             'kDevSignedRoot'))
-        header.write(
-            proto_array_declaration(prod_signed_bundle, 'kTestProdBundle'))
-        header.write(
-            proto_array_declaration(mismatched_root_key_and_signature_bundle,
-                                    'kTestMismatchedRootKeyAndSignature'))
-        header.write(
-            proto_array_declaration(signed_bad_prod_signature_bundle,
-                                    'kTestBadProdSignature'))
-        header.write(
-            proto_array_declaration(bad_targets_siganture,
-                                    'kTestBadTargetsSignature'))
-        header.write(
-            proto_array_declaration(targets_rollback_bundle,
-                                    'kTestTargetsRollback'))
-        header.write(
-            proto_array_declaration(root_rollback_bundle, 'kTestRootRollback'))
-
-        for idx, mismatched_hash_bundle in enumerate(
-                mismatched_hash_targets_bundles):
-            header.write(
-                proto_array_declaration(
-                    mismatched_hash_bundle,
-                    f'kTestBundleMismatchedTargetHashFile{idx}'))
-
-        for idx, missing_hash_bundle in enumerate(
-                missing_hash_targets_bundles):
-            header.write(
-                proto_array_declaration(
-                    missing_hash_bundle,
-                    f'kTestBundleMissingTargetHashFile{idx}'))
-
-        for idx, mismatched_length_bundle in enumerate(
-                mismatched_length_targets_bundles):
-            header.write(
-                proto_array_declaration(
-                    mismatched_length_bundle,
-                    f'kTestBundleMismatchedTargetLengthFile{idx}'))
-
-        for idx, personalized_out_bundle in enumerate(
-                personalized_out_bundles):
-            header.write(
-                proto_array_declaration(
-                    personalized_out_bundle,
-                    f'kTestBundlePersonalizedOutFile{idx}'))
-    subprocess.run([
-        'clang-format',
-        '-i',
-        args.output_header,
-    ], check=True)
-    # TODO(pwbug/456): Refactor the code so that each test bundle generation
-    # is done in a separate function or script.
-    # pylint: enable=too-many-locals
-    return 0
-
-
-if __name__ == "__main__":
-    sys.exit(main())
diff --git a/pw_software_update/py/pw_software_update/keys.py b/pw_software_update/py/pw_software_update/keys.py
deleted file mode 100644
index c9a72d3..0000000
--- a/pw_software_update/py/pw_software_update/keys.py
+++ /dev/null
@@ -1,157 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Facilities for keys generation, importing, signing and verification.
-
-IMPORTANT: THESE FACILITIES ARE FOR LOCAL NON-PRODUCTION USE ONLY!!
-
-These are not suited for production use because:
-
-1. The private keys are not generated without ANY supervision or authorization.
-2. The private keys are not stored securely.
-3. The underlying crypto library is not audited.
-"""
-
-import argparse
-import hashlib
-from pathlib import Path
-
-from cryptography.hazmat.primitives import hashes
-from cryptography.hazmat.primitives.asymmetric import ec
-from cryptography.hazmat.primitives.asymmetric.utils import (
-    decode_dss_signature, encode_dss_signature)
-from cryptography.hazmat.primitives.serialization import (
-    Encoding, NoEncryption, PrivateFormat, PublicFormat, load_pem_private_key,
-    load_pem_public_key)
-
-from pw_software_update.tuf_pb2 import (Key, KeyMapping, KeyScheme, KeyType,
-                                        Signature)
-
-
-def parse_args():
-    """Parse CLI arguments."""
-    parser = argparse.ArgumentParser(description=__doc__)
-    parser.add_argument('-o',
-                        '--out',
-                        type=Path,
-                        required=True,
-                        help='Output path for the generated key')
-    return parser.parse_args()
-
-
-def gen_ecdsa_keypair(out: Path) -> None:
-    """Generates and writes to disk a NIST-P256 EC key pair.
-
-    Args:
-      out: The path to write the private key to. The public key is written
-        to the same path as the private key using the suffix '.pub'.
-    """
-    private_key = ec.generate_private_key(ec.SECP256R1())
-    public_key = private_key.public_key()
-    private_pem = private_key.private_bytes(
-        encoding=Encoding.PEM,
-        format=PrivateFormat.PKCS8,
-        encryption_algorithm=NoEncryption())
-    public_pem = public_key.public_bytes(
-        encoding=Encoding.PEM, format=PublicFormat.SubjectPublicKeyInfo)
-
-    out.write_bytes(private_pem)
-    public_out = (out.parent / f'{out.name}.pub')
-    public_out.write_bytes(public_pem)
-
-
-def gen_key_id(key: Key) -> bytes:
-    """Computes the key ID of a Key object."""
-    sha = hashlib.sha256()
-    sha.update(key.key_type.to_bytes(1, 'big'))
-    sha.update(key.scheme.to_bytes(1, 'big'))
-    sha.update(key.keyval)
-    return sha.digest()
-
-
-def import_ecdsa_public_key(pem: bytes) -> KeyMapping:
-    """Imports an EC NIST-P256 public key in pem format."""
-    ec_key = load_pem_public_key(pem)
-
-    if not isinstance(ec_key, ec.EllipticCurvePublicKey):
-        raise TypeError(
-            f'Not an elliptic curve public key type: {type(ec_key)}.'
-            'Try generate a key with gen_ecdsa_keypair()?')
-
-    # pylint: disable=no-member
-    if not (ec_key.curve.name == 'secp256r1' and ec_key.key_size == 256):
-        raise TypeError(f'Unsupported curve: {ec_key.curve.name}.'
-                        'Try generate a key with gen_ecdsa_keypair()?')
-    # pylint: enable=no-member
-
-    tuf_key = Key(key_type=KeyType.ECDSA_SHA2_NISTP256,
-                  scheme=KeyScheme.ECDSA_SHA2_NISTP256_SCHEME,
-                  keyval=ec_key.public_bytes(Encoding.X962,
-                                             PublicFormat.UncompressedPoint))
-    return KeyMapping(key_id=gen_key_id(tuf_key), key=tuf_key)
-
-
-def create_ecdsa_signature(data: bytes, key: bytes) -> Signature:
-    """Creates an ECDSA-SHA2-NISTP256 signature."""
-    ec_key = load_pem_private_key(key, password=None)
-    if not isinstance(ec_key, ec.EllipticCurvePrivateKey):
-        raise TypeError(f'Not an elliptic curve private key: {type(ec_key)}.'
-                        'Try generate a key with gen_ecdsa_keypair()?')
-
-    tuf_key = Key(key_type=KeyType.ECDSA_SHA2_NISTP256,
-                  scheme=KeyScheme.ECDSA_SHA2_NISTP256_SCHEME,
-                  keyval=ec_key.public_key().public_bytes(
-                      Encoding.X962, PublicFormat.UncompressedPoint))
-
-    der_signature = ec_key.sign(data, ec.ECDSA(hashes.SHA256()))  # pylint: disable=no-value-for-parameter
-    int_r, int_s = decode_dss_signature(der_signature)
-    sig_bytes = int_r.to_bytes(32, 'big') + int_s.to_bytes(32, 'big')
-
-    return Signature(key_id=gen_key_id(tuf_key), sig=sig_bytes)
-
-
-def verify_ecdsa_signature(sig: bytes, data: bytes, key: Key) -> bool:
-    """Verifies an ECDSA-SHA2-NISTP256 signature with a given public key.
-
-    Args:
-      sig: the ECDSA signature as raw bytes (r||s).
-      data: the message as plain text.
-      key: the ECDSA-NISTP256 public key.
-
-    Returns:
-      True if the signature is verified. False otherwise.
-    """
-    ec_key = ec.EllipticCurvePublicKey.from_encoded_point(
-        ec.SECP256R1(), key.keyval)
-    try:
-        dss_sig = encode_dss_signature(int.from_bytes(sig[:32], 'big'),
-                                       int.from_bytes(sig[-32:], 'big'))
-        ec_key.verify(dss_sig, data, ec.ECDSA(hashes.SHA256()))
-    except:  # pylint: disable=bare-except
-        return False
-
-    return True
-
-
-def main(out: Path) -> None:
-    """Generates and writes to disk key pairs for development use."""
-
-    # Currently only supports the "ecdsa-sha2-nistp256" key scheme.
-    #
-    # TODO(alizhang): Add support for "rsassa-pss-sha256" and "ed25519" key
-    # schemes.
-    gen_ecdsa_keypair(out)
-
-
-if __name__ == '__main__':
-    main(**vars(parse_args()))
diff --git a/pw_software_update/py/pw_software_update/metadata.py b/pw_software_update/py/pw_software_update/metadata.py
deleted file mode 100644
index 5390cfb..0000000
--- a/pw_software_update/py/pw_software_update/metadata.py
+++ /dev/null
@@ -1,76 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Facilities for generating TUF target metadata."""
-
-import enum
-import hashlib
-from typing import Dict, Iterable
-
-from pw_software_update.tuf_pb2 import (CommonMetadata, Hash, HashFunction,
-                                        TargetFile, TargetsMetadata)
-
-HASH_FACTORIES = {
-    HashFunction.SHA256: hashlib.sha256,
-}
-DEFAULT_HASHES = (HashFunction.SHA256, )
-DEFAULT_SPEC_VERSION = "0.0.1"
-DEFAULT_METADATA_VERSION = 0
-
-
-class RoleType(enum.Enum):
-    """Set of allowed TUF metadata role types."""
-    ROOT = 'root'
-    TARGETS = 'targets'
-
-
-def gen_common_metadata(
-        role: RoleType,
-        spec_version: str = DEFAULT_SPEC_VERSION,
-        version: int = DEFAULT_METADATA_VERSION) -> CommonMetadata:
-    """Generates CommonMetadata."""
-    return CommonMetadata(role=role.value,
-                          spec_version=spec_version,
-                          version=version)
-
-
-def gen_targets_metadata(
-    target_payloads: Dict[str, bytes],
-    hash_funcs: Iterable['HashFunction.V'] = DEFAULT_HASHES,
-    version: int = DEFAULT_METADATA_VERSION,
-) -> TargetsMetadata:
-    """Generates TargetsMetadata the given target payloads."""
-    target_files = []
-    for target_file_name, target_payload in target_payloads.items():
-        target_files.append(
-            TargetFile(file_name=target_file_name,
-                       length=len(target_payload),
-                       hashes=gen_hashes(target_payload, hash_funcs)))
-
-    common_metadata = gen_common_metadata(RoleType.TARGETS, version=version)
-    return TargetsMetadata(common_metadata=common_metadata,
-                           target_files=target_files)
-
-
-def gen_hashes(data: bytes,
-               hash_funcs: Iterable['HashFunction.V']) -> Iterable[Hash]:
-    """Computes all the specified hashes over the data."""
-    result = []
-    for func in hash_funcs:
-        if func == HashFunction.UNKNOWN_HASH_FUNCTION:
-            raise ValueError(
-                'UNKNOWN_HASH_FUNCTION cannot be used to generate hashes.')
-        digest = HASH_FACTORIES[func](data).digest()
-        result.append(Hash(function=func, hash=digest))
-
-    return result
diff --git a/pw_software_update/py/pw_software_update/root_metadata.py b/pw_software_update/py/pw_software_update/root_metadata.py
deleted file mode 100644
index db7d59a..0000000
--- a/pw_software_update/py/pw_software_update/root_metadata.py
+++ /dev/null
@@ -1,99 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Facilities for generating the 'root' metadata."""
-
-import argparse
-from pathlib import Path
-from typing import Iterable, List, NewType
-
-from pw_software_update import keys, metadata
-from pw_software_update.tuf_pb2 import (RootMetadata, SignedRootMetadata,
-                                        SignatureRequirement)
-
-RootKeys = NewType('RootKeys', List[bytes])
-TargetsKeys = NewType('TargetsKeys', List[bytes])
-
-
-def gen_root_metadata(root_key_pems: RootKeys,
-                      targets_key_pems: TargetsKeys,
-                      version: int = 1) -> RootMetadata:
-    """Generates a RootMetadata.
-
-    Args:
-      root_key_pems: list of root public keys in PEM format.
-      targets_key_pems: list of targets keys in PEM format.
-      version: Version number for rollback checks.
-    """
-    common = metadata.gen_common_metadata(metadata.RoleType.ROOT,
-                                          version=version)
-
-    root_keys = [keys.import_ecdsa_public_key(pem) for pem in root_key_pems]
-    targets_keys = [
-        keys.import_ecdsa_public_key(pem) for pem in targets_key_pems
-    ]
-
-    return RootMetadata(common_metadata=common,
-                        consistent_snapshot=False,
-                        keys=root_keys + targets_keys,
-                        root_signature_requirement=SignatureRequirement(
-                            key_ids=[k.key_id for k in root_keys],
-                            threshold=1),
-                        targets_signature_requirement=SignatureRequirement(
-                            key_ids=[k.key_id for k in targets_keys],
-                            threshold=1))
-
-
-def parse_args():
-    """Parse CLI arguments."""
-    parser = argparse.ArgumentParser(description=__doc__)
-
-    parser.add_argument('-o',
-                        '--out',
-                        type=Path,
-                        required=True,
-                        help='Output path for the generated root metadata')
-
-    parser.add_argument('--version',
-                        type=int,
-                        default=1,
-                        help='Canonical version number for rollback checks')
-
-    parser.add_argument('--root-key',
-                        type=Path,
-                        required=True,
-                        nargs='+',
-                        help='Public key filename for the "Root" role')
-
-    parser.add_argument('--targets-key',
-                        type=Path,
-                        required=True,
-                        nargs='+',
-                        help='Public key filename for the "Targets" role')
-    return parser.parse_args()
-
-
-def main(out: Path, root_key: Iterable[Path], targets_key: Iterable[Path],
-         version: int) -> None:
-    """Generates and writes to disk an unsigned SignedRootMetadata."""
-
-    root_metadata = gen_root_metadata(
-        RootKeys([k.read_bytes() for k in root_key]),
-        TargetsKeys([k.read_bytes() for k in targets_key]), version)
-    signed = SignedRootMetadata(
-        serialized_root_metadata=root_metadata.SerializeToString())
-    out.write_bytes(signed.SerializeToString())
-
-
-if __name__ == '__main__':
-    main(**vars(parse_args()))
diff --git a/pw_software_update/py/pw_software_update/update_bundle.py b/pw_software_update/py/pw_software_update/update_bundle.py
deleted file mode 100644
index 978f988..0000000
--- a/pw_software_update/py/pw_software_update/update_bundle.py
+++ /dev/null
@@ -1,217 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Generate and serialize update bundles."""
-
-import argparse
-import logging
-import os
-from pathlib import Path
-import shutil
-from typing import Dict, Iterable, Optional, Tuple
-
-from pw_software_update import metadata
-from pw_software_update.tuf_pb2 import SignedRootMetadata, SignedTargetsMetadata
-from pw_software_update.update_bundle_pb2 import UpdateBundle
-
-_LOG = logging.getLogger(__package__)
-
-
-def targets_from_directory(
-        root_dir: Path,
-        exclude: Iterable[Path] = tuple(),
-        remap_paths: Optional[Dict[Path, str]] = None) -> Dict[str, Path]:
-    """Given a directory on dist, generate a dict of target names to files.
-
-    Args:
-      root_dir: Directory to crawl for targets.
-      exclude: Paths relative to root_dir to exclude as targets.
-      remap_paths: Custom target names to use for targets.
-
-    Each file in the input directory will be read in as a target file, unless
-    its path (relative to the TUF repo root) is among the excludes.
-
-    Default behavior is to treat root_dir-relative paths as the strings to use
-    as targets file names, but remapping can be used to change a target file
-    name to any string. If some remappings are provided but a file is found that
-    does not have a remapping, a warning will be logged. If a remap is declared
-    for a file that does not exist, FileNotFoundError will be raised.
-    """
-    if not root_dir.is_dir():
-        raise ValueError(
-            f'Cannot generate TUF targets from {root_dir}; not a directory.')
-    targets = {}
-    for path in root_dir.glob('**/*'):
-        if path.is_dir():
-            continue
-        rel_path = path.relative_to(root_dir)
-        if rel_path in exclude:
-            continue
-        target_name = str(rel_path.as_posix())
-        if remap_paths:
-            if rel_path in remap_paths:
-                target_name = remap_paths[rel_path]
-            else:
-                _LOG.warning('Some remaps defined, but not "%s"', target_name)
-        targets[target_name] = path
-
-    if remap_paths is not None:
-        for original_path, new_target_file_name in remap_paths.items():
-            if new_target_file_name not in targets:
-                raise FileNotFoundError(
-                    f'Unable to remap "{original_path}" to'
-                    f' "{new_target_file_name}"; file not found in root dir.')
-
-    return targets
-
-
-def gen_unsigned_update_bundle(
-        targets: Dict[Path, str],
-        persist: Optional[Path] = None,
-        targets_metadata_version: int = metadata.DEFAULT_METADATA_VERSION,
-        root_metadata: SignedRootMetadata = None) -> UpdateBundle:
-    """Given a set of targets, generates an unsigned UpdateBundle.
-
-    Args:
-      targets: A dict mapping payload Paths to their target names.
-      persist: If not None, persist the raw TUF repository to this directory.
-      targets_metadata_version: version number for the targets metadata.
-      root_metadata: Optional signed Root metadata.
-
-    The input targets will be treated as an ephemeral TUF repository for the
-    purposes of building an UpdateBundle instance. This approach differs
-    slightly from the normal concept of a TUF repository, which is typically a
-    directory on disk. For ease in debugging raw repository contents, the
-    `persist` argument can be supplied. If a persist Path is supplied, the TUF
-    repository will be persisted to disk at that location.
-
-    NOTE: If path separator characters (like '/') are used in target names, then
-    persisting the repository to disk via the 'persist' argument will create the
-    corresponding directory structure.
-
-    NOTE: If a root metadata is included, the client is expected to first
-    upgrade its on-device trusted root metadata before verifying the rest of
-    the bundle.
-    """
-    if persist:
-        if persist.exists() and not persist.is_dir():
-            raise ValueError(f'TUF repo cannot be persisted to "{persist}";'
-                             ' file exists and is not a directory.')
-        if persist.exists():
-            shutil.rmtree(persist)
-
-        os.makedirs(persist)
-
-    target_payloads = {}
-    for path, target_name in targets.items():
-        target_payloads[target_name] = path.read_bytes()
-        if persist:
-            target_persist_path = persist / target_name
-            os.makedirs(target_persist_path.parent, exist_ok=True)
-            shutil.copy(path, target_persist_path)
-
-    targets_metadata = metadata.gen_targets_metadata(
-        target_payloads, version=targets_metadata_version)
-    unsigned_targets_metadata = SignedTargetsMetadata(
-        serialized_targets_metadata=targets_metadata.SerializeToString())
-
-    return UpdateBundle(
-        root_metadata=root_metadata,
-        targets_metadata=dict(targets=unsigned_targets_metadata),
-        target_payloads=target_payloads)
-
-
-def parse_target_arg(target_arg: str) -> Tuple[Path, str]:
-    """Parse an individual target string passed in to the --targets argument.
-
-    Target strings take the following form:
-      "FILE_PATH > TARGET_NAME"
-
-    For example:
-      "fw_images/main_image.bin > main"
-    """
-    try:
-        file_path_str, target_name = target_arg.split('>')
-        return Path(file_path_str.strip()), target_name.strip()
-    except ValueError as err:
-        raise ValueError('Targets must be strings of the form:\n'
-                         '  "FILE_PATH > TARGET_NAME"') from err
-
-
-def parse_args() -> argparse.Namespace:
-    """Parse CLI arguments."""
-    parser = argparse.ArgumentParser(description=__doc__)
-    parser.add_argument('-t',
-                        '--targets',
-                        type=str,
-                        nargs='+',
-                        required=True,
-                        help='Strings defining targets to bundle')
-    parser.add_argument('-o',
-                        '--out',
-                        type=Path,
-                        required=True,
-                        help='Output path for serialized UpdateBundle')
-    parser.add_argument('--persist',
-                        type=Path,
-                        default=None,
-                        help=('If provided, TUF repo will be persisted to disk'
-                              ' at this path for debugging'))
-    parser.add_argument('--targets-metadata-version',
-                        type=int,
-                        default=metadata.DEFAULT_METADATA_VERSION,
-                        help='Version number for the targets metadata')
-    parser.add_argument('--targets-metadata-version-file',
-                        type=Path,
-                        default=None,
-                        help='Read version number string from this file. When '
-                        'provided, content of this file supersede '
-                        '--targets-metadata-version')
-    parser.add_argument('--signed-root-metadata',
-                        type=Path,
-                        default=None,
-                        help='Path to the signed Root metadata')
-    return parser.parse_args()
-
-
-def main(targets: Iterable[str],
-         out: Path,
-         persist: Path = None,
-         targets_metadata_version: int = metadata.DEFAULT_METADATA_VERSION,
-         targets_metadata_version_file: Path = None,
-         signed_root_metadata: Path = None) -> None:
-    """Generates an UpdateBundle and serializes it to disk."""
-    target_dict = {}
-    for target_arg in targets:
-        path, target_name = parse_target_arg(target_arg)
-        target_dict[path] = target_name
-
-    root_metadata = None
-    if signed_root_metadata:
-        root_metadata = SignedRootMetadata.FromString(
-            signed_root_metadata.read_bytes())
-
-    if targets_metadata_version_file:
-        with targets_metadata_version_file.open() as version_file:
-            targets_metadata_version = int(version_file.read().strip())
-
-    bundle = gen_unsigned_update_bundle(target_dict, persist,
-                                        targets_metadata_version,
-                                        root_metadata)
-
-    out.write_bytes(bundle.SerializeToString())
-
-
-if __name__ == '__main__':
-    logging.basicConfig()
-    main(**vars(parse_args()))
diff --git a/pw_software_update/py/pw_software_update/verify.py b/pw_software_update/py/pw_software_update/verify.py
deleted file mode 100644
index 7eca73e..0000000
--- a/pw_software_update/py/pw_software_update/verify.py
+++ /dev/null
@@ -1,398 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Facilities to verify an update bundle."""
-
-import argparse
-import inspect
-import logging
-from pathlib import Path
-import sys
-from typing import Iterable
-
-from pw_software_update import keys, metadata
-from pw_software_update.tuf_pb2 import (RootMetadata, SignedRootMetadata,
-                                        SignedTargetsMetadata, TargetsMetadata)
-from pw_software_update.update_bundle_pb2 import UpdateBundle
-
-_LOG = logging.getLogger(__package__)
-
-
-def log_progress(message: str,
-                 indent_offset: int = -5,
-                 indent_str: str = '  '):
-    """Logs verification progress.
-
-    The default indent offset is chosen per actual output of 'python -m verify'.
-    """
-    indentation = 2 * (len(inspect.stack(0)) + indent_offset)
-    _LOG.info('%s%s', indent_str * indentation, message)
-
-
-class VerificationError(Exception):
-    """Raised upon any verification error."""
-
-
-def lint_root_metadata(root: RootMetadata) -> Iterable[str]:
-    """Checks a RootMetadata for content or format errors.
-
-    Returns:
-      A list of all errors found.
-    """
-    errors = []
-
-    # Check role type first-thing to deter chosen-ciphertext attacks.
-    log_progress('Checking role type')
-    if root.common_metadata.role != metadata.RoleType.ROOT.value:
-        errors.append('Role type is not "root"')
-
-    # Check keys database.
-    log_progress('Checking keys database')
-    for entry in root.keys:
-        if not entry.key_id:
-            errors.append('Missing key_id in keys list')
-        elif not entry.key.keyval:
-            errors.append(f'Key {entry.key_id.hex()} does not have a value')
-        elif not entry.key_id == keys.gen_key_id(entry.key):
-            errors.append(f'Key id "{entry.key_id.hex()}" cannot be derived'
-                          f'from key content')
-
-    # Check root signature requirement.
-    log_progress('Checking root signature requirement')
-    root_sig_req = root.root_signature_requirement
-    if not root_sig_req.threshold:
-        errors.append('Root signature threshold not set')
-
-    if len(root_sig_req.key_ids) < root_sig_req.threshold:
-        errors.append(
-            f'Insufficient root keys: '
-            f'{len(root_sig_req.key_ids)} < {root_sig_req.threshold}')
-
-    for key_id in root_sig_req.key_ids:
-        if key_id not in [km.key_id for km in root.keys]:
-            errors.append(f'Unregistered root key: {key_id.hex()}')
-
-    # Check targets signature requirement.
-    log_progress('Checking targets signature requirement')
-    targets_sig_req = root.targets_signature_requirement
-    if not targets_sig_req.threshold:
-        errors.append('Targets signature threshold not set')
-
-    if len(targets_sig_req.key_ids) < targets_sig_req.threshold:
-        errors.append(
-            f'Insufficient Targets keys: '
-            f'{len(targets_sig_req.key_ids)} < {targets_sig_req.threshold}')
-
-    for key_id in targets_sig_req.key_ids:
-        if key_id not in [km.key_id for km in root.keys]:
-            errors.append(f'Unregistered targets key: {key_id.hex()}')
-
-    # Make sure no two roles share the same key.
-    log_progress('Checking for key sharing')
-    for key_id in targets_sig_req.key_ids:
-        if key_id in root_sig_req.key_ids:
-            errors.append(f'Targets shares the same key: "{key_id.hex()}"')
-
-    return errors
-
-
-def verify_root_metadata_signatures(incoming: SignedRootMetadata,
-                                    trusted: RootMetadata) -> None:
-    """Verifies the signatures of an incoming root metadata.
-
-    Verifies the signatures of an incoming root metadata against signature
-    requirements from the trusted root metadata.
-
-    Raises:
-      VerificationError if `incoming` is incorrectly or insufficiently signed.
-    """
-    sig_requirement = trusted.root_signature_requirement
-
-    log_progress(f'Total={len(incoming.signatures)}, '
-                 f'threshold={sig_requirement.threshold}')
-    good_signature_count = 0
-    for sig in incoming.signatures:
-        if sig.key_id not in sig_requirement.key_ids:
-            continue
-
-        key = None
-        for key_mapping in trusted.keys:
-            if key_mapping.key_id == sig.key_id:
-                key = key_mapping.key
-                break
-        if not key:
-            raise VerificationError(f'Invalid key_id: {sig.key_id.hex()}.')
-
-        if not keys.verify_ecdsa_signature(
-                sig.sig, incoming.serialized_root_metadata, key):
-            raise VerificationError('Invalid signature, key_id={sig.key_id}.')
-
-        good_signature_count += 1
-
-    log_progress(f'Verified: {good_signature_count}')
-    if good_signature_count < sig_requirement.threshold:
-        raise VerificationError('Not enough good signatures.')
-
-
-def verify_root_metadata(incoming: SignedRootMetadata,
-                         trusted: RootMetadata) -> bool:
-    """Verifies an incoming root metadata against a trusted root metadata.
-
-    Returns:
-      A boolean flag indicating if the targets metadata has been rotated.
-
-    Raises:
-      VerificationError if the incoming root is incorrectly formatted,
-      insufficiently signed, or rolling back to an older version.
-    """
-    # Verify the incoming is signed with a threshold of keys specified in the
-    # trusted root metadata.
-    log_progress('Checking signatures against current root')
-    verify_root_metadata_signatures(incoming, trusted)
-
-    # Now that we've verified the signer of the incoming root, check its content
-    # before parsing it to guard against chosen-ciphertext attacks.
-    log_progress('Checking content')
-    lint_errors = lint_root_metadata(
-        RootMetadata.FromString(incoming.serialized_root_metadata))
-    if lint_errors:
-        log_progress(f'Lint errors: {lint_errors}')
-        raise VerificationError('Malformed root metadata.')
-
-    # Verify the target is signed with a threshold of keys specified in the
-    # target root metadata.
-    log_progress('Checking signatures against current root')
-    verify_root_metadata_signatures(
-        incoming, RootMetadata.FromString(incoming.serialized_root_metadata))
-
-    # Check rollback attack.
-    log_progress('Checking for version rollback')
-    incoming_meta = RootMetadata.FromString(incoming.serialized_root_metadata)
-    new_ver = incoming_meta.common_metadata.version
-    cur_ver = trusted.common_metadata.version
-    if new_ver < cur_ver:
-        raise VerificationError(
-            f'Root metadata version rollback ({cur_ver}->{new_ver}) detected!')
-
-    # Any signature requirement change indicates a targets key rotation.
-    new_sig_req = incoming_meta.targets_signature_requirement
-    cur_sig_req = trusted.targets_signature_requirement
-    targets_key_rotated = not (
-        set(new_sig_req.key_ids) == set(cur_sig_req.key_ids)
-        and new_sig_req.threshold == cur_sig_req.threshold)
-    log_progress(f'Targets key rotation: {targets_key_rotated}')
-    return targets_key_rotated
-
-
-def lint_targets_metadata(meta: TargetsMetadata) -> Iterable[str]:
-    """Checks a targets metadata for format errors.
-
-    Returns:
-      A list of all errors found.
-    """
-    errors = []
-
-    # Always check the role type first to guard against chosen-ciphertext
-    # attacks.
-    log_progress("Checking role type")
-    if meta.common_metadata.role != metadata.RoleType.TARGETS.value:
-        errors.append(
-            f'Role type is not "targets" but "{meta.common_metadata.role}"')
-
-    for file in meta.target_files:
-        if not file.file_name:
-            errors.append('Target file missing a name')
-        if not file.hashes:
-            errors.append('Target file missing hashes')
-
-    return errors
-
-
-def verify_targets_metadata(signed: SignedTargetsMetadata,
-                            root: RootMetadata) -> None:
-    """Verifies a targets metadata is sufficiently signed and well-formed.
-
-    Raises:
-      VerificationError if the targets metadata is insufficiently signed or
-        malformed.
-    """
-    sig_requirement = root.targets_signature_requirement
-    log_progress(f'Checking signatures: total={len(signed.signatures)}, '
-                 f'threshold={sig_requirement.threshold}')
-    good_signatures_count = 0
-    for sig in signed.signatures:
-        # Ignore extraneous signatures.
-        if sig.key_id not in sig_requirement.key_ids:
-            continue
-
-        # Extract the public key associated with sig.key_id. There is one and
-        # only one way to derive a key_id from a key object, which has been
-        # previously verified as part of root metadata verification.
-        key = None
-        for key_mapping in root.keys:
-            if key_mapping.key_id == sig.key_id:
-                key = key_mapping.key
-                break
-        if not key:
-            raise VerificationError(
-                f'No such key_id in root: {sig.key_id.hex()}.')
-
-        if not keys.verify_ecdsa_signature(
-                sig=sig.sig, data=signed.serialized_targets_metadata, key=key):
-            raise VerificationError(
-                f'Invalid signature, key_id={sig.key_id.hex()}.')
-
-        good_signatures_count += 1
-
-    log_progress(f'Verified signatures: {good_signatures_count}')
-    if good_signatures_count < sig_requirement.threshold:
-        raise VerificationError(
-            f'Not enough good signatures: {good_signatures_count} < '
-            f'{sig_requirement.threshold}.')
-
-    log_progress('Checking content')
-    lint_errors = lint_targets_metadata(
-        TargetsMetadata.FromString(signed.serialized_targets_metadata))
-    if lint_errors:
-        log_progress(f'Lint errors: {lint_errors}')
-        raise VerificationError('Malformed targets metadata.')
-
-
-def verify_bundle(incoming: UpdateBundle, trusted: UpdateBundle) -> None:
-    """Verifies an incoming TUF bundle against metadata in `trusted`.
-
-    Raises VerificationError upon the first verification failure.
-    """
-
-    # Root metadata in `trusted` is our trust anchor.
-    if not trusted.HasField('root_metadata'):
-        raise VerificationError('Trusted bundle missing root metadata')
-    trusted_root = RootMetadata.FromString(
-        trusted.root_metadata.serialized_root_metadata)
-
-    # Check the contents of the trusted root metadata. This is optional
-    # in practice as we generally trust what is provisioned in the factory.
-    log_progress('Checking content of the trusted root metadata')
-    lint_errors = lint_root_metadata(trusted_root)
-    if lint_errors:
-        log_progress(f'Lint errors: {lint_errors}')
-        raise VerificationError('Malformed root metadata.')
-
-    # If the incoming bundle includes a root metadata, verify it using the
-    # current trusted root metadata and set the current trusted root to the
-    # new root upon successful verification.
-
-    # Record whether the new root metadata rotates the targets key. This
-    # information is used later to perform or skip targets metadata version
-    # rollback check.
-    targets_key_rotated = False
-    incoming_root = incoming.root_metadata
-    if incoming_root:
-        log_progress('Verifying incoming root metadata')
-        targets_key_rotated = verify_root_metadata(incoming=incoming_root,
-                                                   trusted=trusted_root)
-        log_progress('Upgrading trust to the incoming root metadata')
-        trusted_root = RootMetadata.FromString(
-            incoming_root.serialized_root_metadata)
-
-    log_progress('Verifying targets metadata')
-    signed_targets_metadata = incoming.targets_metadata['targets']
-    verify_targets_metadata(signed_targets_metadata, trusted_root)
-
-    # Unless the targets signing key has been rotated, check for version
-    # rollback attack.
-    targets_metadata = TargetsMetadata.FromString(
-        signed_targets_metadata.serialized_targets_metadata)
-    if not targets_key_rotated:
-        log_progress('Checking targets metadata for version rollback')
-        new_ver = targets_metadata.common_metadata.version
-        cur_ver = TargetsMetadata.FromString(
-            trusted.targets_metadata['targets'].serialized_targets_metadata
-        ).common_metadata.version
-        if new_ver < cur_ver:
-            raise VerificationError(
-                f'Targets metadata rolling back: {cur_ver} '
-                f'-> {new_ver}.')
-
-    # Verify all files listed in the targets metadata exist along with the
-    # correct sizes and hashes.
-    for file in targets_metadata.target_files:
-        log_progress(f'Verifying target file: "{file.file_name}"')
-
-        payload = incoming.target_payloads[file.file_name]
-        if file.length != len(payload):
-            raise VerificationError(f'Wrong file size for {file.file_name}: '
-                                    f'expected: {file.length}, '
-                                    f'got: {len(payload)}.')
-
-        if not file.hashes:
-            raise VerificationError(f'Missing hashes for: {file.file_name}.')
-        calculated_hashes = metadata.gen_hashes(
-            payload, [h.function for h in file.hashes])
-        if list(calculated_hashes) != list(file.hashes):
-            raise VerificationError(
-                f'Mismatched hashes for: {file.file_name}.')
-
-
-def parse_args():
-    """Parse CLI arguments."""
-    parser = argparse.ArgumentParser(description=__doc__)
-
-    parser.add_argument('--incoming',
-                        type=Path,
-                        required=True,
-                        help='Path to the TUF bundle to be verified')
-
-    parser.add_argument('--trusted',
-                        type=Path,
-                        help=('Path to the TUF bundle to be trusted; '
-                              'defaults to the value of `--incoming` '
-                              'if unspecified.'))
-
-    return parser.parse_args()
-
-
-def main(incoming: Path, trusted: Path) -> int:
-    """Verifies an incoming TUF bundle against metadata in `trusted`.
-
-    Verifies an incoming TUF bundle against metadata from a given trusted
-    bundle. If `trusted` is not specified, the target bundle itself will
-    be used as the trusted bundle.
-
-    Returns:
-      0 on success, non-zero otherwise.
-    """
-    log_progress(f'Verifying: {incoming}')
-    incoming_bundle = UpdateBundle.FromString(incoming.read_bytes())
-
-    is_self_verification = (not trusted)
-    if is_self_verification:
-        trusted_bundle = incoming_bundle
-        log_progress('(self-verification)')
-    else:
-        trusted_bundle = UpdateBundle.FromString(trusted.read_bytes())
-
-    try:
-        verify_bundle(incoming_bundle, trusted_bundle)
-    except VerificationError as error:
-        log_progress(f'Verification failed: {error}')
-        return 1
-
-    log_progress('Verification passed.')
-    return 0
-
-
-if __name__ == '__main__':
-    logging.basicConfig(format='%(message)s', level=logging.INFO)
-    exit_code = main(**vars(parse_args()))
-    sys.exit(exit_code)
diff --git a/pw_software_update/py/root_metadata_test.py b/pw_software_update/py/root_metadata_test.py
deleted file mode 100644
index a9afeeb..0000000
--- a/pw_software_update/py/root_metadata_test.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Unit tests for pw_software_update/root_metadata.py."""
-
-import unittest
-
-from pw_software_update import metadata
-from pw_software_update.root_metadata import (RootKeys, TargetsKeys,
-                                              gen_root_metadata)
-
-
-class GenRootMetadataTest(unittest.TestCase):
-    """Test the generation of root metadata."""
-    def setUp(self):
-        self.root_key_public = (
-            b'-----BEGIN PUBLIC KEY-----\n'
-            b'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4XWOT3o27TNqeh7YF7P+2ErLzzFm'
-            b'c/VItYABCqw7Hh5z8wtNjGyo0GnUSBWeISg3LMs/WjOkCiwjawjqmI8OrQ=='
-            b'\n-----END PUBLIC KEY-----\n')
-
-        self.targets_key_public = (
-            b'-----BEGIN PUBLIC KEY-----\n'
-            b'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9UM6qRZJ0gIWwLjo8tjbrrBTlKXg'
-            b'ukwVjOlnguSSiYMrN4MDqMlNDnaJgLvcCuiNUKHu9Oj1DG1i6ckNdE4VTA=='
-            b'\n-----END PUBLIC KEY-----\n')
-
-    def test_multiple_keys(self) -> None:
-        """Checks that multiple keys generates multiple KeyMappings and
-        SignatureRequirements."""
-        root_metadata = gen_root_metadata(RootKeys([self.root_key_public]),
-                                          TargetsKeys(
-                                              [self.targets_key_public]),
-                                          version=42)
-
-        self.assertEqual(len(root_metadata.keys), 2)
-        self.assertEqual(len(root_metadata.root_signature_requirement.key_ids),
-                         1)
-        self.assertEqual(root_metadata.root_signature_requirement.threshold, 1)
-        self.assertEqual(
-            len(root_metadata.targets_signature_requirement.key_ids), 1)
-        self.assertEqual(root_metadata.targets_signature_requirement.threshold,
-                         1)
-        self.assertEqual(root_metadata.common_metadata.version, 42)
-        self.assertEqual(root_metadata.common_metadata.role,
-                         metadata.RoleType.ROOT.value)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_software_update/py/update_bundle_test.py b/pw_software_update/py/update_bundle_test.py
deleted file mode 100644
index a4279f6..0000000
--- a/pw_software_update/py/update_bundle_test.py
+++ /dev/null
@@ -1,201 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Unit tests for pw_software_update/update_bundle.py."""
-
-from pathlib import Path
-import tempfile
-import unittest
-
-from pw_software_update import update_bundle
-from pw_software_update.tuf_pb2 import SignedRootMetadata, TargetsMetadata
-
-
-class TargetsFromDirectoryTest(unittest.TestCase):
-    """Test turning a directory into TUF targets."""
-    def test_excludes(self):
-        """Checks that excludes are excluded."""
-        with tempfile.TemporaryDirectory() as tempdir_name:
-            temp_root = Path(tempdir_name)
-            foo_path = temp_root / 'foo.bin'
-            bar_path = temp_root / 'bar.bin'
-            baz_path = temp_root / 'baz.bin'
-            qux_path = temp_root / 'qux.exe'
-            for path in (foo_path, bar_path, baz_path, qux_path):
-                path.touch()
-
-            targets = update_bundle.targets_from_directory(
-                temp_root, exclude=(Path('foo.bin'), Path('baz.bin')))
-
-            self.assertNotIn('foo.bin', targets)
-            self.assertEqual(bar_path, targets['bar.bin'])
-            self.assertNotIn('baz.bin', targets)
-            self.assertEqual(qux_path, targets['qux.exe'])
-
-    def test_excludes_and_remapping(self):
-        """Checks that remapping works, even in combination with excludes."""
-        with tempfile.TemporaryDirectory() as tempdir_name:
-            temp_root = Path(tempdir_name)
-            foo_path = temp_root / 'foo.bin'
-            bar_path = temp_root / 'bar.bin'
-            baz_path = temp_root / 'baz.bin'
-            qux_path = temp_root / 'qux.exe'
-            remap_paths = {
-                Path('foo.bin'): 'main',
-                Path('bar.bin'): 'backup',
-                Path('baz.bin'): 'tertiary',
-            }
-            for path in (foo_path, bar_path, baz_path, qux_path):
-                path.touch()
-
-            targets = update_bundle.targets_from_directory(
-                temp_root,
-                exclude=(Path('qux.exe'), ),
-                remap_paths=remap_paths)
-
-            self.assertEqual(foo_path, targets['main'])
-            self.assertEqual(bar_path, targets['backup'])
-            self.assertEqual(baz_path, targets['tertiary'])
-            self.assertNotIn('qux.exe', targets)
-
-    def test_incomplete_remapping_logs(self):
-        """Checks that incomplete remappings log warnings."""
-        with tempfile.TemporaryDirectory() as tempdir_name:
-            temp_root = Path(tempdir_name)
-            foo_path = temp_root / 'foo.bin'
-            bar_path = temp_root / 'bar.bin'
-            foo_path.touch()
-            bar_path.touch()
-            remap_paths = {Path('foo.bin'): 'main'}
-
-            with self.assertLogs(level='WARNING') as log:
-                update_bundle.targets_from_directory(
-                    temp_root,
-                    exclude=(Path('qux.exe'), ),
-                    remap_paths=remap_paths)
-
-                self.assertIn('Some remaps defined, but not "bar.bin"',
-                              log.output[0])
-
-    def test_remap_of_missing_file(self):
-        """Checks that remapping a missing file raises an error."""
-        with tempfile.TemporaryDirectory() as tempdir_name:
-            temp_root = Path(tempdir_name)
-            foo_path = temp_root / 'foo.bin'
-            foo_path.touch()
-            remap_paths = {
-                Path('foo.bin'): 'main',
-                Path('bar.bin'): 'backup',
-            }
-
-            with self.assertRaises(FileNotFoundError):
-                update_bundle.targets_from_directory(temp_root,
-                                                     remap_paths=remap_paths)
-
-
-class GenUnsignedUpdateBundleTest(unittest.TestCase):
-    """Test the generation of unsigned update bundles."""
-    def test_bundle_generation(self):
-        """Tests basic creation of an UpdateBundle."""
-        with tempfile.TemporaryDirectory() as tempdir_name:
-            temp_root = Path(tempdir_name)
-            foo_path = temp_root / 'foo.bin'
-            bar_path = temp_root / 'bar.bin'
-            baz_path = temp_root / 'baz.bin'
-            qux_path = temp_root / 'subdir' / 'qux.exe'
-            foo_bytes = b'\xf0\x0b\xa4'
-            bar_bytes = b'\x0b\xa4\x99'
-            baz_bytes = b'\xba\x59\x06'
-            qux_bytes = b'\x8a\xf3\x12'
-            foo_path.write_bytes(foo_bytes)
-            bar_path.write_bytes(bar_bytes)
-            baz_path.write_bytes(baz_bytes)
-            (temp_root / 'subdir').mkdir()
-            qux_path.write_bytes(qux_bytes)
-            targets = {
-                foo_path: 'foo',
-                bar_path: 'bar',
-                baz_path: 'baz',
-                qux_path: 'qux',
-            }
-            serialized_root_metadata_bytes = b'\x12\x34\x56\x78'
-
-            bundle = update_bundle.gen_unsigned_update_bundle(
-                targets,
-                targets_metadata_version=42,
-                root_metadata=SignedRootMetadata(
-                    serialized_root_metadata=serialized_root_metadata_bytes))
-
-            self.assertEqual(foo_bytes, bundle.target_payloads['foo'])
-            self.assertEqual(bar_bytes, bundle.target_payloads['bar'])
-            self.assertEqual(baz_bytes, bundle.target_payloads['baz'])
-            self.assertEqual(qux_bytes, bundle.target_payloads['qux'])
-            targets_metadata = TargetsMetadata.FromString(
-                bundle.targets_metadata['targets'].serialized_targets_metadata)
-            self.assertEqual(targets_metadata.common_metadata.version, 42)
-            self.assertEqual(serialized_root_metadata_bytes,
-                             bundle.root_metadata.serialized_root_metadata)
-
-    def test_persist_to_disk(self):
-        """Tests persisting the TUF repo to disk for debugging"""
-        with tempfile.TemporaryDirectory() as tempdir_name:
-            temp_root = Path(tempdir_name)
-            foo_path = temp_root / 'foo.bin'
-            bar_path = temp_root / 'bar.bin'
-            baz_path = temp_root / 'baz.bin'
-            qux_path = temp_root / 'subdir' / 'qux.exe'
-            foo_bytes = b'\xf0\x0b\xa4'
-            bar_bytes = b'\x0b\xa4\x99'
-            baz_bytes = b'\xba\x59\x06'
-            qux_bytes = b'\x8a\xf3\x12'
-            foo_path.write_bytes(foo_bytes)
-            bar_path.write_bytes(bar_bytes)
-            baz_path.write_bytes(baz_bytes)
-            (temp_root / 'subdir').mkdir()
-            qux_path.write_bytes(qux_bytes)
-            targets = {
-                foo_path: 'foo',
-                bar_path: 'bar',
-                baz_path: 'baz',
-                qux_path: 'subdir/qux',
-            }
-            persist_path = temp_root / 'persisted'
-
-            update_bundle.gen_unsigned_update_bundle(targets,
-                                                     persist=persist_path)
-
-            self.assertEqual(foo_bytes, (persist_path / 'foo').read_bytes())
-            self.assertEqual(bar_bytes, (persist_path / 'bar').read_bytes())
-            self.assertEqual(baz_bytes, (persist_path / 'baz').read_bytes())
-            self.assertEqual(qux_bytes,
-                             (persist_path / 'subdir' / 'qux').read_bytes())
-
-
-class ParseTargetArgTest(unittest.TestCase):
-    """Test the parsing of target argument strings."""
-    def test_valid_arg(self):
-        """Checks that valid remap strings are parsed correctly."""
-        file_path, target_name = update_bundle.parse_target_arg(
-            'foo.bin > main')
-
-        self.assertEqual(Path('foo.bin'), file_path)
-        self.assertEqual('main', target_name)
-
-    def test_invalid_arg_raises(self):
-        """Checks that invalid remap string raise an error."""
-        with self.assertRaises(ValueError):
-            update_bundle.parse_target_arg('foo.bin main')
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_software_update/py/verify_test.py b/pw_software_update/py/verify_test.py
deleted file mode 100644
index a19cee0..0000000
--- a/pw_software_update/py/verify_test.py
+++ /dev/null
@@ -1,222 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Unit tests for pw_software_update/dev_sign.py."""
-
-from dataclasses import dataclass
-from pathlib import Path
-import tempfile
-from typing import NamedTuple
-import unittest
-
-from pw_software_update import dev_sign, root_metadata, update_bundle
-from pw_software_update.verify import VerificationError, verify_bundle
-from pw_software_update.tuf_pb2 import SignedRootMetadata
-from pw_software_update.update_bundle_pb2 import UpdateBundle
-
-
-def gen_unsigned_bundle(signed_root_metadata: SignedRootMetadata = None,
-                        targets_metadata_version: int = 0) -> UpdateBundle:
-    """Generates an unsigned test bundle."""
-    with tempfile.TemporaryDirectory() as tempdir_name:
-        targets_root = Path(tempdir_name)
-        foo_path = targets_root / 'foo.bin'
-        bar_path = targets_root / 'bar.bin'
-        baz_path = targets_root / 'baz.bin'
-        qux_path = targets_root / 'subdir' / 'qux.exe'
-        foo_bytes = b'\xf0\x0b\xa4'
-        bar_bytes = b'\x0b\xa4\x99'
-        baz_bytes = b'\xba\x59\x06'
-        qux_bytes = b'\x8a\xf3\x12'
-        foo_path.write_bytes(foo_bytes)
-        bar_path.write_bytes(bar_bytes)
-        baz_path.write_bytes(baz_bytes)
-        (targets_root / 'subdir').mkdir()
-        qux_path.write_bytes(qux_bytes)
-        targets = {
-            foo_path: 'foo',
-            bar_path: 'bar',
-            baz_path: 'baz',
-            qux_path: 'qux',
-        }
-        return update_bundle.gen_unsigned_update_bundle(
-            targets,
-            root_metadata=signed_root_metadata,
-            targets_metadata_version=targets_metadata_version)
-
-
-class TestKey(NamedTuple):
-    """A test key pair"""
-    public: bytes
-    private: bytes
-
-
-@dataclass
-class BundleOptions:
-    """Parameters used in test bundle generations."""
-    root_key_version: int = 0
-    root_metadata_version: int = 0
-    targets_key_version: int = 0
-    targets_metadata_version: int = 0
-
-
-def gen_signed_bundle(options: BundleOptions) -> UpdateBundle:
-    """Generates a test bundle per given options."""
-    # Root keys look up table: version->TestKey
-    root_keys = {
-        0:
-        TestKey(
-            private=(
-                b'-----BEGIN PRIVATE KEY-----\n'
-                b'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgyk3DEQdl'
-                b'346MS5N/quNEneJa4HxkJBETGzlEEKkCmZOhRANCAAThdY5PejbtM2p6'
-                b'HtgXs/7YSsvPMWZz9Ui1gAEKrDseHnPzC02MbKjQadRIFZ4hKDcsyz9a'
-                b'M6QKLCNrCOqYjw6t'
-                b'\n-----END PRIVATE KEY-----\n'),
-            public=(
-                b'-----BEGIN PUBLIC KEY-----\n'
-                b'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4XWOT3o27TNqeh7YF7P+2'
-                b'ErLzzFmc/VItYABCqw7Hh5z8wtNjGyo0GnUSBWeISg3LMs/WjOkCiwjaw'
-                b'jqmI8OrQ=='
-                b'\n-----END PUBLIC KEY-----\n')),
-        1:
-        TestKey(
-            private=(
-                b'-----BEGIN PRIVATE KEY-----\n'
-                b'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgE3MRbMxo'
-                b'Gv3I/Ok/0qE8GV/mQuIbZo9kk+AsJnYetQ6hRANCAAQ5UhycwdcfYe34'
-                b'NpmG32t0klnKlrUbk3LyvYLq5uDWG2MfP3L0ciNFsEnW7vHpqqjKsoru'
-                b'Qt30G10K7D+reC77'
-                b'\n-----END PRIVATE KEY-----\n'),
-            public=(
-                b'-----BEGIN PUBLIC KEY-----\n'
-                b'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOVIcnMHXH2Ht+DaZht9rd'
-                b'JJZypa1G5Ny8r2C6ubg1htjHz9y9HIjRbBJ1u7x6aqoyrKK7kLd9BtdCu'
-                b'w/q3gu+w=='
-                b'\n-----END PUBLIC KEY-----\n'))
-    }
-
-    # Targets keys look up table: version->TestKey
-    targets_keys = {
-        0:
-        TestKey(
-            private=(
-                b'-----BEGIN PRIVATE KEY-----\n'
-                b'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkMEZ0u84'
-                b'HzC51nhhf2ZykPj6WfAjBxXVWndjVdn6bh6hRANCAAT1QzqpFknSAhbA'
-                b'uOjy2NuusFOUpeC6TBWM6WeC5JKJgys3gwOoyU0OdomAu9wK6I1Qoe70'
-                b'6PUMbWLpyQ10ThVM'
-                b'\n-----END PRIVATE KEY-----\n'),
-            public=(
-                b'-----BEGIN PUBLIC KEY-----\n'
-                b'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9UM6qRZJ0gIWwLjo8tjbr'
-                b'rBTlKXgukwVjOlnguSSiYMrN4MDqMlNDnaJgLvcCuiNUKHu9Oj1DG1i6c'
-                b'kNdE4VTA=='
-                b'\n-----END PUBLIC KEY-----\n')),
-        1:
-        TestKey(
-            private=(
-                b'-----BEGIN PRIVATE KEY-----\n'
-                b'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg+Q+u2KoO'
-                b'CwpY1HEKDTIjQXmTlxhoo3gVkE7nrtHhMemhRANCAASgc+0AHCfUxoHy'
-                b'+ZkSslLvMufiDqGPABvfuKzHd0wUWs2Y0eIvQc7tsBP0bcuJsFuxvL6a'
-                b'8Ek7y3kUmFWVL01v'
-                b'\n-----END PRIVATE KEY-----\n'),
-            public=(
-                b'-----BEGIN PUBLIC KEY-----\n'
-                b'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoHPtABwn1MaB8vmZErJS7'
-                b'zLn4g6hjwAb37isx3dMFFrNmNHiL0HO7bAT9G3LibBbsby+mvBJO8t5FJ'
-                b'hVlS9Nbw=='
-                b'\n-----END PUBLIC KEY-----\n'))
-    }
-
-    unsigned_root = root_metadata.gen_root_metadata(
-        root_metadata.RootKeys([root_keys[options.root_key_version].public]),
-        root_metadata.TargetsKeys(
-            [targets_keys[options.targets_key_version].public]),
-        version=options.root_metadata_version)
-
-    serialized_root = unsigned_root.SerializeToString()
-    signed_root = SignedRootMetadata(serialized_root_metadata=serialized_root)
-    signed_root = dev_sign.sign_root_metadata(
-        signed_root, root_keys[options.root_key_version].private)
-    # Additionaly sign the root metadata with the previous version of root key
-    # to enable upgrading from the previous root.
-    if options.root_key_version > 0:
-        signed_root = dev_sign.sign_root_metadata(
-            signed_root, root_keys[options.root_key_version - 1].private)
-
-    unsigned_bundle = gen_unsigned_bundle(
-        signed_root_metadata=signed_root,
-        targets_metadata_version=options.targets_metadata_version)
-    signed_bundle = dev_sign.sign_update_bundle(
-        unsigned_bundle, targets_keys[options.targets_key_version].private)
-
-    return signed_bundle
-
-
-class VerifyBundleTest(unittest.TestCase):
-    """Bundle verification test cases."""
-    def test_self_verification(self):  # pylint: disable=no-self-use
-        incoming = gen_signed_bundle(BundleOptions())
-        verify_bundle(incoming, trusted=incoming)
-
-    def test_root_key_rotation(self):  # pylint: disable=no-self-use
-        trusted = gen_signed_bundle(BundleOptions(root_key_version=0))
-        incoming = gen_signed_bundle(BundleOptions(root_key_version=1))
-        verify_bundle(incoming, trusted)
-
-    def test_root_metadata_anti_rollback(self):
-        trusted = gen_signed_bundle(BundleOptions(root_metadata_version=1))
-        incoming = gen_signed_bundle(BundleOptions(root_metadata_version=0))
-        with self.assertRaises(VerificationError):
-            verify_bundle(incoming, trusted)
-
-    def test_root_metadata_anti_rollback_with_key_rotation(self):
-        trusted = gen_signed_bundle(
-            BundleOptions(root_key_version=0, root_metadata_version=1))
-        incoming = gen_signed_bundle(
-            BundleOptions(root_key_version=1, root_metadata_version=0))
-        # Anti-rollback enforced regardless of key rotation.
-        with self.assertRaises(VerificationError):
-            verify_bundle(incoming, trusted)
-
-    def test_missing_root(self):
-        incoming = gen_signed_bundle(BundleOptions())
-        incoming.ClearField('root_metadata')
-        with self.assertRaises(VerificationError):
-            verify_bundle(incoming, trusted=incoming)
-
-    def test_targets_key_rotation(self):  # pylint: disable=no-self-use
-        trusted = gen_signed_bundle(BundleOptions(targets_key_version=0))
-        incoming = gen_signed_bundle(BundleOptions(targets_key_version=1))
-        verify_bundle(incoming, trusted)
-
-    def test_targets_metadata_anti_rollback(self):
-        trusted = gen_signed_bundle(BundleOptions(targets_metadata_version=1))
-        incoming = gen_signed_bundle(BundleOptions(targets_metadata_version=0))
-        with self.assertRaises(VerificationError):
-            verify_bundle(incoming, trusted)
-
-    def test_targets_fastforward_recovery(self):  # pylint: disable=no-self-use
-        trusted = gen_signed_bundle(
-            BundleOptions(targets_key_version=0, targets_metadata_version=999))
-        # Revoke key and bring back the metadata version.
-        incoming = gen_signed_bundle(
-            BundleOptions(targets_key_version=1, targets_metadata_version=0))
-        # Anti-rollback is not enforced upon key rotation.
-        verify_bundle(incoming, trusted)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_software_update/tuf.proto b/pw_software_update/tuf.proto
deleted file mode 100644
index 74cfe3b..0000000
--- a/pw_software_update/tuf.proto
+++ /dev/null
@@ -1,332 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-//
-// Implementation of metadata formats specified in TUF Specification.
-// See https://theupdateframework.github.io/specification/latest/
-
-syntax = "proto3";
-
-package pw.software_update;
-
-import "google/protobuf/timestamp.proto";
-
-// Metadata for a particular TUF role (e.g. targets metadata).
-// Was TufMetadata
-message SignedRootMetadata {
-  // Serialized RootMetadata message that is the data portion of the metadata.
-  bytes serialized_root_metadata = 1;
-
-  // Signature of the canonical form of the role's serialized metadata
-  // (serialized_root_metadata).
-  repeated Signature signatures = 2;
-}
-
-message SignedTimestampMetadata {
-  // Serialized TimestampMetadata message that is the data portion of the
-  // metadata.
-  bytes serialized_timestamp_metadata = 1;
-
-  // Signature of the canonical form of the role's serialized metadata
-  // (serialized_timestamp_metadata).
-  repeated Signature signatures = 2;
-}
-
-message SignedSnapshotMetadata {
-  // Serialized SnapshotMetadata message that is the data portion of the
-  // metadata.
-  bytes serialized_snapshot_metadata = 1;
-
-  // Signature of the canonical form of the role's serialized metadata
-  // (serialized_snapshot_metadata).
-  repeated Signature signatures = 2;
-}
-
-message SignedTargetsMetadata {
-  // Serialized TargetsMetadata message that is the data portion of the
-  // metadata.
-  bytes serialized_targets_metadata = 1;
-
-  // Signature of the canonical form of the role's serialized metadata
-  // (serialized_targets_metadata).
-  repeated Signature signatures = 2;
-}
-
-message CommonMetadata {
-  // Version number of the TUF Specification.
-  // Follows the Semantic Versioning 2.0.0 (semver) format. Metadata is
-  // written according to this version, and clients MUST verify that
-  // "spec_version" matches the expected version number.
-  // E.g. "1.0.0".
-  string spec_version = 1;
-
-  // Metadata file version.
-  // Clients MUST NOT replace a metadata file with a version number less than
-  // the one currently trusted.
-  uint32 version = 2;
-
-  // Expiration time for the metadata.
-  // Indicates when this metadata should be considered expired and no longer
-  // trusted by clients. Notice the TUF Specification defines this as a JSON
-  // string following the ISO 8601 standard. The expected format of the date and
-  // time string is "YYYY-MM-DDTHH:MM:SSZ". Time is always in UTC, and the "Z"
-  // time zone designator is attached to indicate a zero UTC offset.
-  // E.g. "2030-08-26T16:48:27Z".
-  optional google.protobuf.Timestamp expires = 3;
-
-  // Role type for the metadata.
-  // Indicates the type of the metadata. Valid values are 'root', 'targets',
-  // 'snapshot' and 'timestamp' as defined in the TUF spec, though we don't
-  // plan to support 'mirrors'.
-  //
-  // This field serves as a "magic code" that identifies a particular type of
-  // a metadata. During verification, the client is expected to check this
-  // field against the expected role type immediately after verifying the
-  // signatures of a metadata. This can be considered a "confidence booster"
-  // in the absence of canonical protobuf -- i.e. it makes the various
-  // `serialized_x_metadata` fields more tamper resistant.
-  optional string role = 4;
-}
-
-// This content is signed.
-message RootMetadata {
-  CommonMetadata common_metadata = 1;
-
-  // Whether the repo supports consistent snapshots. If the repo has frequent
-  // updates, you should set this to true.
-  bool consistent_snapshot = 2;
-
-  // Map from Keyid to Key.
-  // Keyid is a unique identifier that identifies a cryptographic key.
-  // Contains all of cryptographic keys used by this repository.
-  repeated KeyMapping keys = 3;
-
-  // KeyConfig is the list of keys use for a particular role and the threshold.
-  // Threshold is number of keys of that role whose signatures are required in
-  // order to consider a file as being properly signed by that role.
-  SignatureRequirement root_signature_requirement = 4;
-  SignatureRequirement timestamp_signature_requirement = 5;
-  SignatureRequirement snapshot_signature_requirement = 6;
-  SignatureRequirement targets_signature_requirement = 7;
-
-  // This is NOT a part of the TUF Specification.
-  reserved 8 to 31;  // Reserved for TUF Specification changes.
-
-  reserved 32 to 64;  // Reserved for future Pigweed usage.
-
-  reserved 65 to 255;  // Reserved for project-specific usage.
-}
-
-// The timestamp role is used for freshness check of the snapshot. Any
-// project-specific update metadata should go in the top-level targets_metadata
-// or with the TargetFile information
-message TimestampMetadata {
-  CommonMetadata common_metadata = 1;
-
-  // Only one snapshot_metadata is used per timestamp.
-  MetadataFile snapshot_metadata = 2;
-
-  // This is NOT a part of the TUF Specification.
-  reserved 3 to 31;  // Reserved for TUF Specification changes.
-
-  reserved 32 to 64;  // Reserved for future Pigweed usage.
-
-  reserved 65 to 255;  // Reserved for project-specific usage.
-}
-
-// The snapshot role is used to ensure that the collection of targets_metadata
-// files is securely consistent (no target metadata mix and match). Any
-// project-specific update metadata should go in the top-level targets_metadata
-// or with the TargetFile information
-message SnapshotMetadata {
-  CommonMetadata common_metadata = 1;
-
-  // Map from Target metadata file name to MetadataFile.
-  // File name can be an arbitrary name or a full file name with relative path.
-  // This map should contain an entry for the top level targets role and all
-  // delegated roles.
-  repeated MetadataFile targets_metadata = 2;
-
-  // This is NOT a part of the TUF Specification.
-  reserved 3 to 31;  // Reserved for TUF Specification changes.
-
-  reserved 32 to 64;  // Reserved for future Pigweed usage.
-
-  reserved 65 to 255;  // Reserved for project-specific usage.
-}
-
-// The targets role describes the target files that comprise the software
-// update. Targets metadata is organized in to a top-level targets metadata file
-// and optional multiple deligated targets metadata files
-//
-// The top-level targets metatdata is the correct place to put any
-// project-specific build version information, including build ID, hardware rev,
-// etc.
-message TargetsMetadata {
-  CommonMetadata common_metadata = 1;
-
-  // Collection of target file information
-  repeated TargetFile target_files = 2;
-  // Target file name can be an arbitrary name or a path that describes where
-  // the file lives relative to the base directory of the repository, e.g.
-  // "path/to/amber_tools/0".
-
-  // TODO: When it is time to support delegation, add delegation information
-  // here.
-
-  // This is NOT a part of the TUF Specification.
-  reserved 9 to 31;  // Reserved for TUF Specification changes.
-
-  reserved 32 to 64;  // Reserved for future Pigweed usage.
-
-  reserved 65 to 255;  // Reserved for project-specific usage.
-}
-
-message Signature {
-  // Identifier of the key, which is bytes of the SHA-256 hash of the
-  // canonical form of the key.
-  bytes key_id = 1;
-
-  // The signature of the canonical form of the role's serialized metadata
-  // (serialized_{root,timestamp,snapshot,targets}_metadata).
-  bytes sig = 2;
-}
-
-message KeyMapping {
-  // Identifier of the key, which is bytes of the SHA-256 hash of the
-  // canonical form of the key.
-  bytes key_id = 1;
-
-  // Cryptographic key
-  Key key = 2;
-}
-
-// Identifies an asymmetric cryptographic key.
-message Key {
-  // Denotes a public key signature system, such as RSA or ECDSA.
-  KeyType key_type = 1;
-
-  // Denotes the signature scheme corresponding to the key type. For example:
-  // "rsassa-pss-sha256" or "ecdsa-sha2-nistp256".
-  KeyScheme scheme = 2;
-
-  // Stores the serialized public key for this cryptographic algorithm.
-  bytes keyval = 3;
-}
-
-// The set of cryptographic keys used by a specific role. For example, list of
-// key_ids used by the top level role "root".
-message SignatureRequirement {
-  // Set of Keyid's.
-  // Keyid is a unique identifier that identifies a cryptographic key.
-  // E.g. "f2d5020d08aea06a0a9192eb6a4f549e17032ebefa1aa9ac167c1e3e727930d6".
-  repeated bytes key_ids = 1;
-
-  // Threshold of signatures required to trust given file.
-  // In other words; the number of keys of that role whose signatures are
-  // required in order to consider a file as being properly signed by that role.
-  uint32 threshold = 2;
-}
-
-enum HashFunction {
-  // Never use this in any TUF metadata.
-  UNKNOWN_HASH_FUNCTION = 0;
-
-  SHA256 = 1;
-}
-
-message Hash {
-  HashFunction function = 1;
-  // Digest of the cryptographic hash function computed on the target file.
-  bytes hash = 2;
-}
-
-// Descriptor for a file stored in this repository. Linked to from target
-// metadata.
-message TargetFile {
-  // Target file name can be an arbitrary name or a path that describes where
-  // the file lives relative to the base directory of the repository, e.g.
-  // "path/to/amber_tools/0".
-  string file_name = 1;
-
-  // Size of the target file (element payload) in bytes. This the size as stored
-  // in the bundle. The final applied size can be different due to optional
-  // compression.
-  uint64 length = 2;
-
-  // Map from algorithm name to Hash.
-  // Algorithm name is the name of a cryptographic hash function. E.g. "sha256".
-  // The Hash string is the hex digest of the cryptographic function computed on
-  // the target file. E.g.
-  // "65b8c67f51c993d898250f40aa57a317d854900b3a04895464313e48785440da".
-  repeated Hash hashes = 3;
-
-  // This is NOT a part of the TUF Specification.
-  reserved 4 to 15;  // Reserved for TUF Specification changes.
-
-  reserved 16 to 31;  // Reserved for future Pigweed usage.
-
-  reserved 32 to 255;  // Reserved for future project-specific usage.
-}
-
-message MetadataFile {
-  // Target file name can be an arbitrary name or a path that describes where
-  // the file lives relative to the base directory of the repository, e.g.
-  // "path/to/target/0".
-  optional string file_name = 1;
-
-  // Metadata file version. E.g. 3.
-  uint32 version = 2;
-
-  // Size of the target file in bytes.
-  optional uint64 length = 3;
-
-  // Map from algorithm name to Hash.
-  // Algorithm name is the name of a cryptographic hash function. E.g. "sha256".
-  // The Hash is the hex digest of the cryptographic function computed on the
-  // target file. E.g.
-  // "65b8c67f51c993d898250f40aa57a317d854900b3a04895464313e48785440da".
-  repeated Hash hashes = 4;
-}
-
-enum KeyType {
-  // Never use this in any TUF metadata.
-  UNKNOWN_KEY_TYPE = 0;
-
-  RSA = 1;
-
-  ED25519 = 2;
-
-  ECDSA_SHA2_NISTP256 = 3;
-}
-
-enum KeyScheme {
-  // Never use this in any TUF metadata.
-  UNKNOWN_KEY_SCHEME = 0;
-
-  // RSA Probabilistic signature scheme with appendix.
-  // The underlying hash function is SHA256.
-  // In TUF Specification, this is referred to as "rsassa-pss-sha256".
-  RSASSA_PSS_SHA256_SCHEME = 1;
-
-  // Elliptic Curve digital signature algorithm based on Twisted Edwards curves.
-  // See https://ed25519.cr.yp.to/.
-  // In TUF Specification, it is referred to as "ed25519".
-  ED25519_SCHEME = 2;
-
-  // Elliptic Curve Digital Signature Algorithm with NIST P-256 curve signing
-  // and SHA-256 hashing. See
-  // https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm In
-  // TUF Specification, it is referred to as "ecdsa-sha2-nistp256".
-  ECDSA_SHA2_NISTP256_SCHEME = 3;
-}
diff --git a/pw_software_update/update_bundle.proto b/pw_software_update/update_bundle.proto
deleted file mode 100644
index 190e8b5..0000000
--- a/pw_software_update/update_bundle.proto
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-syntax = "proto3";
-
-package pw.software_update;
-
-import "pw_software_update/tuf.proto";
-
-message UpdateBundle {
-  // The timestamp role is used for freshness check of the snapshot. Any
-  // project-specific update metadata should go in the top-level
-  // targets_metadata or with the TargetFile information
-  optional SignedTimestampMetadata timestamp_metadata = 1;
-
-  // The snapshot role is used to ensure that the collection of targets_metadata
-  // files is securely consistent (no target metadata mix and match). Any
-  // project-specific update metadata should go in the top-level
-  // targets_metadata or with the TargetFile information
-  optional SignedSnapshotMetadata snapshot_metadata = 2;
-
-  // Map of target metadata name to target metadata.
-  // Target metadata name can be an arbitrary name or a path that describes
-  // where the file lives relative to the base directory of the repository, as
-  // described in the snapshot metadata. e.g. "path/to/target/0".
-  map<string, SignedTargetsMetadata> targets_metadata = 3;
-
-  // Map of target file name to target payload bytes.
-  // Target file name can be an arbitrary name or a path that describes where
-  // the file lives relative to the base directory of the repository, as
-  // described in the target metadata. e.g. "path/to/amber_tools/0".
-  map<string, bytes> target_payloads = 4;
-
-  // If present, a client will attempt to upgrade its on-device trusted root
-  // metadata to the root metadata included in the bundle, following the
-  // standard "Update the root role" flow specified in the TUF spec, but
-  // without "version climbing".
-  //
-  // The exact steps are:
-  // 1. Check if there is a root metadata in the bundle.
-  // 2. If the root metadata IS NOT included, assume on-device root metadata
-  //    is up-to-date and continue with the rest of metadata verification.
-  // 3. If the root metadata IS included, verify the new root metadata using
-  //    the on-device root metadata.
-  // 4. If the verification is successful, persist new root metadata and
-  //    continue with the rest of metadata verification. Otherwise abort the
-  //    update session.
-  //
-  // The key deviation from standard flow is the client assumes it can always
-  // directly upgrade to the single new root metadata in the update bundle,
-  // without any step-stone history root metadata. This works only because
-  // we are not supporting (more than 1) root key rotations.
-  optional SignedRootMetadata root_metadata = 5;
-}
-
-// Update bundle metadata
-// Designed to inform the update server what the device currently has in-place.
-// Also used to persist the TUF metadata for use in the verification process.
-// Stored manifest is only written/erased by the update service. In all other
-// contexts the stored manifest is considered read-only.
-message Manifest {
-  map<string, TargetsMetadata> targets_metadata = 1;
-
-  // Insert user manifest target file content here
-  optional bytes user_manifest = 2;
-}
diff --git a/pw_software_update/update_bundle_accessor.cc b/pw_software_update/update_bundle_accessor.cc
deleted file mode 100644
index 453f932..0000000
--- a/pw_software_update/update_bundle_accessor.cc
+++ /dev/null
@@ -1,824 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 PW_LOG_MODULE_NAME "PWSU"
-#define PW_LOG_LEVEL PW_LOG_LEVEL_WARN
-
-#include "pw_software_update/update_bundle_accessor.h"
-
-#include <cstddef>
-#include <cstring>
-#include <string_view>
-
-#include "pw_crypto/ecdsa.h"
-#include "pw_crypto/sha256.h"
-#include "pw_log/log.h"
-#include "pw_protobuf/message.h"
-#include "pw_result/result.h"
-#include "pw_software_update/config.h"
-#include "pw_software_update/manifest_accessor.h"
-#include "pw_software_update/update_bundle.pwpb.h"
-#include "pw_stream/interval_reader.h"
-#include "pw_stream/memory_stream.h"
-#include "pw_string/string_builder.h"
-
-namespace pw::software_update {
-namespace {
-
-Result<bool> VerifyEcdsaSignature(protobuf::Bytes public_key,
-                                  ConstByteSpan digest,
-                                  protobuf::Bytes signature) {
-  // TODO(pwbug/456): Move this logic into an variant of the API in
-  // pw_crypto:ecdsa that takes readers as inputs.
-  std::byte public_key_bytes[65];
-  std::byte signature_bytes[64];
-  stream::IntervalReader key_reader = public_key.GetBytesReader();
-  stream::IntervalReader sig_reader = signature.GetBytesReader();
-  PW_TRY(key_reader.Read(public_key_bytes));
-  PW_TRY(sig_reader.Read(signature_bytes));
-  Status status = crypto::ecdsa::VerifyP256Signature(
-      public_key_bytes, digest, signature_bytes);
-  if (!status.ok()) {
-    return false;
-  }
-
-  return true;
-}
-
-// Convert an integer from [0, 16) to a hex char
-char IntToHex(uint8_t val) {
-  PW_ASSERT(val < 16);
-  return val >= 10 ? (val - 10) + 'a' : val + '0';
-}
-
-void LogKeyId(ConstByteSpan key_id) {
-  char key_id_str[pw::crypto::sha256::kDigestSizeBytes * 2 + 1] = {0};
-  for (size_t i = 0; i < pw::crypto::sha256::kDigestSizeBytes; i++) {
-    uint8_t value = std::to_integer<uint8_t>(key_id[i]);
-    key_id_str[i * 2] = IntToHex((value >> 4) & 0xf);
-    key_id_str[i * 2 + 1] = IntToHex(value & 0xf);
-  }
-
-  PW_LOG_DEBUG("key_id: %s", key_id_str);
-}
-
-// Verifies signatures of a TUF metadata.
-Status VerifyMetadataSignatures(protobuf::Bytes message,
-                                protobuf::RepeatedMessages signatures,
-                                protobuf::Message signature_requirement,
-                                protobuf::StringToMessageMap key_mapping) {
-  // Gets the threshold -- at least `threshold` number of signatures must
-  // pass verification in order to trust this metadata.
-  protobuf::Uint32 threshold = signature_requirement.AsUint32(
-      static_cast<uint32_t>(SignatureRequirement::Fields::THRESHOLD));
-  PW_TRY(threshold.status());
-
-  // Gets the ids of keys that are allowed for verifying the signatures.
-  protobuf::RepeatedBytes allowed_key_ids =
-      signature_requirement.AsRepeatedBytes(
-          static_cast<uint32_t>(SignatureRequirement::Fields::KEY_IDS));
-  PW_TRY(allowed_key_ids.status());
-
-  // Verifies the signatures. Check that at least `threshold` number of
-  // signatures can be verified using the allowed keys.
-  size_t verified_count = 0;
-  size_t total_signatures = 0;
-  for (protobuf::Message signature : signatures) {
-    total_signatures++;
-    protobuf::Bytes key_id =
-        signature.AsBytes(static_cast<uint32_t>(Signature::Fields::KEY_ID));
-    PW_TRY(key_id.status());
-
-    // Reads the key id into a buffer, so that we can check whether it is
-    // listed as allowed and look up the key value later.
-    std::byte key_id_buf[pw::crypto::sha256::kDigestSizeBytes];
-    stream::IntervalReader key_id_reader = key_id.GetBytesReader();
-    Result<ByteSpan> key_id_read_res = key_id_reader.Read(key_id_buf);
-    PW_TRY(key_id_read_res.status());
-    if (key_id_read_res.value().size() != sizeof(key_id_buf)) {
-      return Status::Internal();
-    }
-
-    // Verify that the `key_id` is listed in `allowed_key_ids`.
-    // Note that the function assumes that the key id is properly derived
-    // from the key (via sha256).
-    bool key_id_is_allowed = false;
-    for (protobuf::Bytes trusted : allowed_key_ids) {
-      Result<bool> key_id_equal = trusted.Equal(key_id_buf);
-      PW_TRY(key_id_equal.status());
-      if (key_id_equal.value()) {
-        key_id_is_allowed = true;
-        break;
-      }
-    }
-
-    if (!key_id_is_allowed) {
-      PW_LOG_DEBUG("Skipping a key id not listed in allowed key ids");
-      LogKeyId(key_id_buf);
-      continue;
-    }
-
-    // Retrieves the signature bytes.
-    protobuf::Bytes sig =
-        signature.AsBytes(static_cast<uint32_t>(Signature::Fields::SIG));
-    PW_TRY(sig.status());
-
-    // Extracts the key type, scheme and value information.
-    std::string_view key_id_str(reinterpret_cast<const char*>(key_id_buf),
-                                sizeof(key_id_buf));
-    protobuf::Message key_info = key_mapping[key_id_str];
-    PW_TRY(key_info.status());
-
-    protobuf::Bytes key_val =
-        key_info.AsBytes(static_cast<uint32_t>(Key::Fields::KEYVAL));
-    PW_TRY(key_val.status());
-
-    // The function assume that all keys are ECDSA keys. This is guaranteed
-    // by the fact that all trusted roots have undergone content check.
-
-    // computes the sha256 hash
-    std::byte sha256_digest[32];
-    stream::IntervalReader bytes_reader = message.GetBytesReader();
-    PW_TRY(crypto::sha256::Hash(bytes_reader, sha256_digest));
-    Result<bool> res = VerifyEcdsaSignature(key_val, sha256_digest, sig);
-    PW_TRY(res.status());
-    if (res.value()) {
-      verified_count++;
-      if (verified_count == threshold.value()) {
-        return OkStatus();
-      }
-    }
-  }
-
-  if (total_signatures == 0) {
-    // For self verification to tell apart unsigned bundles.
-    return Status::NotFound();
-  }
-
-  PW_LOG_ERROR("Insufficient signatures. Requires at least %u, verified %u",
-               threshold.value(),
-               verified_count);
-  return Status::Unauthenticated();
-}
-
-// Verifies the signatures of a signed new root metadata against a given
-// trusted root. The helper function extracts the corresponding key maping
-// signature requirement, signatures from the trusted root and passes them
-// to VerifyMetadataSignatures().
-//
-// Precondition: The trusted root metadata has undergone content validity check.
-Result<bool> VerifyRootMetadataSignatures(protobuf::Message trusted_root,
-                                          protobuf::Message new_root) {
-  // Retrieves the trusted root metadata content message.
-  protobuf::Message trusted = trusted_root.AsMessage(static_cast<uint32_t>(
-      SignedRootMetadata::Fields::SERIALIZED_ROOT_METADATA));
-  PW_TRY(trusted.status());
-
-  // Retrieves the serialized new root metadata bytes.
-  protobuf::Bytes serialized = new_root.AsBytes(static_cast<uint32_t>(
-      SignedRootMetadata::Fields::SERIALIZED_ROOT_METADATA));
-  PW_TRY(serialized.status());
-
-  // Gets the key mapping from the trusted root metadata.
-  protobuf::StringToMessageMap key_mapping = trusted.AsStringToMessageMap(
-      static_cast<uint32_t>(RootMetadata::Fields::KEYS));
-  PW_TRY(key_mapping.status());
-
-  // Gets the signatures of the new root.
-  protobuf::RepeatedMessages signatures = new_root.AsRepeatedMessages(
-      static_cast<uint32_t>(SignedRootMetadata::Fields::SIGNATURES));
-  PW_TRY(signatures.status());
-
-  // Gets the signature requirement from the trusted root metadata.
-  protobuf::Message signature_requirement = trusted.AsMessage(
-      static_cast<uint32_t>(RootMetadata::Fields::ROOT_SIGNATURE_REQUIREMENT));
-  PW_TRY(signature_requirement.status());
-
-  // Verifies the signatures.
-  PW_TRY(VerifyMetadataSignatures(
-      serialized, signatures, signature_requirement, key_mapping));
-  return true;
-}
-
-Result<uint32_t> GetMetadataVersion(protobuf::Message& metadata,
-                                    uint32_t common_metatdata_field_number) {
-  // message [Root|Targets]Metadata {
-  //   ...
-  //   CommonMetadata common_metadata = <field_number>;
-  //   ...
-  // }
-  //
-  // message CommonMetadata {
-  //   ...
-  //   uint32 version = <field_number>;
-  //   ...
-  // }
-  protobuf::Message common_metadata =
-      metadata.AsMessage(common_metatdata_field_number);
-  PW_TRY(common_metadata.status());
-  protobuf::Uint32 res = common_metadata.AsUint32(
-      static_cast<uint32_t>(software_update::CommonMetadata::Fields::VERSION));
-  PW_TRY(res.status());
-  return res.value();
-}
-
-// Reads a protobuf::String into a buffer and returns a std::string_view.
-Result<std::string_view> ReadProtoString(protobuf::String str,
-                                         std::span<char> buffer) {
-  stream::IntervalReader reader = str.GetBytesReader();
-  if (reader.interval_size() > buffer.size()) {
-    return Status::ResourceExhausted();
-  }
-
-  Result<ByteSpan> res = reader.Read(std::as_writable_bytes(buffer));
-  PW_TRY(res.status());
-  return std::string_view(buffer.data(), res.value().size());
-}
-
-}  // namespace
-
-Status UpdateBundleAccessor::OpenAndVerify() {
-  if (Status status = DoOpen(); !status.ok()) {
-    PW_LOG_ERROR("Failed to open staged bundle");
-    return status;
-  }
-
-  if (Status status = DoVerify(); !status.ok()) {
-    PW_LOG_ERROR("Failed to verified staged bundle");
-    Close();
-    return status;
-  }
-
-  return OkStatus();
-}
-
-Result<uint64_t> UpdateBundleAccessor::GetTotalPayloadSize() {
-  protobuf::RepeatedMessages manifested_targets =
-      GetManifest().GetTargetFiles();
-  PW_TRY(manifested_targets.status());
-
-  protobuf::StringToBytesMap bundled_payloads = bundle_.AsStringToBytesMap(
-      static_cast<uint32_t>(UpdateBundle::Fields::TARGET_PAYLOADS));
-  PW_TRY(bundled_payloads.status());
-
-  uint64_t total_bytes;
-  std::array<std::byte, MAX_TARGET_NAME_LENGTH> name_buffer = {};
-  for (protobuf::Message target : manifested_targets) {
-    protobuf::String target_name =
-        target.AsString(static_cast<uint32_t>(TargetFile::Fields::FILE_NAME));
-
-    stream::IntervalReader name_reader = target_name.GetBytesReader();
-    PW_TRY(name_reader.status());
-    if (name_reader.interval_size() > name_buffer.size()) {
-      return Status::OutOfRange();
-    }
-
-    Result<ByteSpan> read_result = name_reader.Read(name_buffer);
-    PW_TRY(read_result.status());
-
-    ConstByteSpan name_span = read_result.value();
-    std::string_view name_view(reinterpret_cast<const char*>(name_span.data()),
-                               name_span.size_bytes());
-
-    if (!bundled_payloads[name_view].ok()) {
-      continue;
-    }
-    protobuf::Uint64 target_length =
-        target.AsUint64(static_cast<uint32_t>(TargetFile::Fields::LENGTH));
-    PW_TRY(target_length.status());
-    total_bytes += target_length.value();
-  }
-
-  return total_bytes;
-}
-
-// Get the target element corresponding to `target_file`
-stream::IntervalReader UpdateBundleAccessor::GetTargetPayload(
-    std::string_view target_name) {
-  protobuf::Message manifest_entry = GetManifest().GetTargetFile(target_name);
-  PW_TRY(manifest_entry.status());
-
-  protobuf::StringToBytesMap payloads_map = bundle_.AsStringToBytesMap(
-      static_cast<uint32_t>(UpdateBundle::Fields::TARGET_PAYLOADS));
-  return payloads_map[target_name].GetBytesReader();
-}
-
-// Get the target element corresponding to `target_file`
-stream::IntervalReader UpdateBundleAccessor::GetTargetPayload(
-    protobuf::String target_name) {
-  char name_buf[MAX_TARGET_NAME_LENGTH] = {0};
-  Result<std::string_view> name_view = ReadProtoString(target_name, name_buf);
-  PW_TRY(name_view.status());
-  return GetTargetPayload(name_view.value());
-}
-
-Status UpdateBundleAccessor::PersistManifest() {
-  ManifestAccessor manifest = GetManifest();
-  // GetManifest() fails if the bundle is yet to be verified.
-  PW_TRY(manifest.status());
-
-  // Notify backend to prepare to receive a new manifest.
-  PW_TRY(backend_.BeforeManifestWrite());
-
-  Result<stream::Writer*> writer = backend_.GetManifestWriter();
-  PW_TRY(writer.status());
-  PW_CHECK_NOTNULL(writer.value());
-
-  PW_TRY(manifest.Export(*writer.value()));
-
-  // Notify backend we are done writing. Backend should finalize
-  // (seal the box).
-  PW_TRY(backend_.AfterManifestWrite());
-
-  return OkStatus();
-}
-
-Status UpdateBundleAccessor::Close() {
-  bundle_verified_ = false;
-  return blob_store_reader_.IsOpen() ? blob_store_reader_.Close() : OkStatus();
-}
-
-Status UpdateBundleAccessor::DoOpen() {
-  PW_TRY(blob_store_.Init());
-  PW_TRY(blob_store_reader_.Open());
-  bundle_ = protobuf::Message(blob_store_reader_,
-                              blob_store_reader_.ConservativeReadLimit());
-  if (!bundle_.ok()) {
-    blob_store_reader_.Close();
-    return bundle_.status();
-  }
-  return OkStatus();
-}
-
-Status UpdateBundleAccessor::DoVerify() {
-#if PW_SOFTWARE_UPDATE_DISABLE_BUNDLE_VERIFICATION
-  PW_LOG_WARN("Bundle verification is compiled out.");
-  bundle_verified_ = true;
-  return OkStatus();
-#else   // PW_SOFTWARE_UPDATE_DISABLE_BUNDLE_VERIFICATION
-  bundle_verified_ = false;
-
-  // Verify and upgrade the on-device trust to the incoming root metadata if
-  // one is included.
-  if (Status status = UpgradeRoot(); !status.ok()) {
-    PW_LOG_ERROR("Failed to upgrade to Root in staged bundle");
-    return status;
-  }
-
-  // TODO(pwbug/456): Verify the targets metadata against the current trusted
-  // root.
-  if (Status status = VerifyTargetsMetadata(); !status.ok()) {
-    PW_LOG_ERROR("Failed to verify Targets metadata");
-    return status;
-  }
-
-  // TODO(pwbug/456): Investigate whether targets payload verification should
-  // be performed here or deferred until a specific target is requested.
-  if (Status status = VerifyTargetsPayloads(); !status.ok()) {
-    PW_LOG_ERROR("Failed to verify all manifested payloads");
-    return status;
-  }
-
-  // TODO(pwbug/456): Invoke the backend to do downstream verification of the
-  // bundle (e.g. compatibility and manifest completeness checks).
-
-  bundle_verified_ = true;
-  return OkStatus();
-#endif  // PW_SOFTWARE_UPDATE_DISABLE_BUNDLE_VERIFICATION
-}
-
-protobuf::Message UpdateBundleAccessor::GetOnDeviceTrustedRoot() {
-  Result<stream::SeekableReader*> res = backend_.GetRootMetadataReader();
-  if (!(res.ok() && res.value())) {
-    PW_LOG_ERROR("Failed to get on-device Root metadata");
-    return res.status();
-  }
-  // Seek to the beginning so that ConservativeReadLimit() returns the correct
-  // value.
-  PW_TRY(res.value()->Seek(0, stream::Stream::Whence::kBeginning));
-  return protobuf::Message(*res.value(), res.value()->ConservativeReadLimit());
-}
-
-ManifestAccessor UpdateBundleAccessor::GetOnDeviceManifest() {
-  // Notify backend to check if an on-device manifest exists and is valid and if
-  // yes, prepare a ready-to-go reader.
-  PW_TRY(backend_.BeforeManifestRead());
-
-  Result<stream::SeekableReader*> manifest_reader =
-      backend_.GetManifestReader();
-  PW_TRY(manifest_reader.status());
-  PW_CHECK_NOTNULL(manifest_reader.value());
-
-  // In case `backend_.BeforeManifestRead()` forgot to reset the reader.
-  PW_TRY(manifest_reader.value()->Seek(0, stream::Stream::Whence::kBeginning));
-
-  return ManifestAccessor::FromManifest(
-      protobuf::Message(*manifest_reader.value(),
-                        manifest_reader.value()->ConservativeReadLimit()));
-}
-
-Status UpdateBundleAccessor::UpgradeRoot() {
-  protobuf::Message new_root = bundle_.AsMessage(
-      static_cast<uint32_t>(UpdateBundle::Fields::ROOT_METADATA));
-
-  // Try self-verification even if verification is disabled by the caller. This
-  // minimizes surprises when the caller do decide to turn on verification.
-  bool self_verifying = disable_verification_;
-
-  // Choose and cache the root metadata to trust.
-  trusted_root_ = self_verifying ? new_root : GetOnDeviceTrustedRoot();
-
-  if (!new_root.status().ok()) {
-    // Don't bother upgrading if not found or invalid.
-    PW_LOG_WARN("Incoming root metadata not found or invalid");
-    return OkStatus();
-  }
-
-  // A valid trust anchor is required onwards from here.
-  PW_TRY(trusted_root_.status());
-
-  // TODO(pwbug/456): Check whether the bundle contains a root metadata that
-  // is different from the on-device trusted root.
-
-  // Verify the signatures against the trusted root metadata.
-  Result<bool> verify_res =
-      VerifyRootMetadataSignatures(trusted_root_, new_root);
-  if (!(verify_res.status().ok() && verify_res.value())) {
-    PW_LOG_ERROR("Failed to verify incoming root against the current root");
-    return Status::Unauthenticated();
-  }
-
-  // TODO(pwbug/456): Verifiy the content of the new root metadata, including:
-  //    1) Check role magic field.
-  //    2) Check signature requirement. Specifically, check that no key is
-  //       reused across different roles and keys are unique in the same
-  //       requirement.
-  //    3) Check key mapping. Specifically, check that all keys are unique,
-  //       ECDSA keys, and the key ids are exactly the SHA256 of `key type +
-  //       key scheme + key value`.
-
-  // Verify the signatures against the new root metadata.
-  verify_res = VerifyRootMetadataSignatures(new_root, new_root);
-  if (!(verify_res.status().ok() && verify_res.value())) {
-    PW_LOG_ERROR("Fail to verify incoming root against itself");
-    return Status::Unauthenticated();
-  }
-
-  // TODO(pwbug/456): Check rollback.
-  // Retrieves the trusted root metadata content message.
-  protobuf::Message trusted_root_content =
-      trusted_root_.AsMessage(static_cast<uint32_t>(
-          SignedRootMetadata::Fields::SERIALIZED_ROOT_METADATA));
-  PW_TRY(trusted_root_content.status());
-  Result<uint32_t> trusted_root_version = GetMetadataVersion(
-      trusted_root_content,
-      static_cast<uint32_t>(RootMetadata::Fields::COMMON_METADATA));
-  PW_TRY(trusted_root_version.status());
-
-  // Retrieves the serialized new root metadata message.
-  protobuf::Message new_root_content = new_root.AsMessage(static_cast<uint32_t>(
-      SignedRootMetadata::Fields::SERIALIZED_ROOT_METADATA));
-  PW_TRY(new_root_content.status());
-  Result<uint32_t> new_root_version = GetMetadataVersion(
-      new_root_content,
-      static_cast<uint32_t>(RootMetadata::Fields::COMMON_METADATA));
-  PW_TRY(new_root_version.status());
-
-  if (trusted_root_version.value() > new_root_version.value()) {
-    PW_LOG_ERROR("Root attempts to rollback from %u to %u",
-                 trusted_root_version.value(),
-                 new_root_version.value());
-    return Status::Unauthenticated();
-  }
-
-  if (!self_verifying) {
-    // Persist the root immediately after it is successfully verified. This is
-    // to make sure the trust anchor is up-to-date in storage as soon as
-    // we are confident. Although targets metadata and product-specific
-    // verification have not been done yet. They should be independent from and
-    // not gate the upgrade of root key. This allows timely revokation of
-    // compromise keys.
-    stream::IntervalReader new_root_reader =
-        new_root.ToBytes().GetBytesReader();
-    PW_TRY(backend_.SafelyPersistRootMetadata(new_root_reader));
-  }
-
-  // TODO(pwbug/456): Implement key change detection to determine whether
-  // rotation has occured or not. Delete the persisted targets metadata version
-  // if any of the targets keys has been rotated.
-
-  return OkStatus();
-}
-
-Status UpdateBundleAccessor::VerifyTargetsMetadata() {
-  bool self_verifying = disable_verification_;
-
-  if (self_verifying && !trusted_root_.status().ok()) {
-    PW_LOG_WARN(
-        "Self-verification won't verify Targets metadata because there is no "
-        "root");
-    return OkStatus();
-  }
-
-  // A valid trust anchor is required from now on.
-  PW_TRY(trusted_root_.status());
-
-  // Retrieve the signed targets metadata map.
-  //
-  // message UpdateBundle {
-  //   ...
-  //   map<string, SignedTargetsMetadata> target_metadata = <id>;
-  //   ...
-  // }
-  protobuf::StringToMessageMap signed_targets_metadata_map =
-      bundle_.AsStringToMessageMap(
-          static_cast<uint32_t>(UpdateBundle::Fields::TARGETS_METADATA));
-  PW_TRY(signed_targets_metadata_map.status());
-
-  // The top-level targets metadata is identified by key name "targets" in the
-  // map.
-  protobuf::Message signed_top_level_targets_metadata =
-      signed_targets_metadata_map[kTopLevelTargetsName];
-  PW_TRY(signed_top_level_targets_metadata.status());
-
-  // Retrieve the serialized metadata.
-  //
-  // message SignedTargetsMetadata {
-  //   ...
-  //   bytes serialized_target_metadata = <id>;
-  //   ...
-  // }
-  protobuf::Message top_level_targets_metadata =
-      signed_top_level_targets_metadata.AsMessage(static_cast<uint32_t>(
-          SignedTargetsMetadata::Fields::SERIALIZED_TARGETS_METADATA));
-
-  // Get the sigantures from the signed targets metadata.
-  protobuf::RepeatedMessages signatures =
-      signed_top_level_targets_metadata.AsRepeatedMessages(
-          static_cast<uint32_t>(SignedTargetsMetadata::Fields::SIGNATURES));
-  PW_TRY(signatures.status());
-
-  // Retrieve the trusted root metadata message.
-  protobuf::Message trusted_root =
-      trusted_root_.AsMessage(static_cast<uint32_t>(
-          SignedRootMetadata::Fields::SERIALIZED_ROOT_METADATA));
-  PW_TRY(trusted_root.status());
-
-  // Get the key_mapping from the trusted root metadata.
-  protobuf::StringToMessageMap key_mapping = trusted_root.AsStringToMessageMap(
-      static_cast<uint32_t>(RootMetadata::Fields::KEYS));
-  PW_TRY(key_mapping.status());
-
-  // Get the targest metadtata siganture requirement from the trusted root.
-  protobuf::Message signature_requirement =
-      trusted_root.AsMessage(static_cast<uint32_t>(
-          RootMetadata::Fields::TARGETS_SIGNATURE_REQUIREMENT));
-  PW_TRY(signature_requirement.status());
-
-  // Verify the sigantures
-  Status sig_res =
-      VerifyMetadataSignatures(top_level_targets_metadata.ToBytes(),
-                               signatures,
-                               signature_requirement,
-                               key_mapping);
-
-  if (self_verifying && sig_res.IsNotFound()) {
-    PW_LOG_WARN("Self-verification ignoring unsigned bundle");
-    return OkStatus();
-  }
-
-  if (!sig_res.ok()) {
-    PW_LOG_ERROR("Targets Metadata failed signature verification");
-    return Status::Unauthenticated();
-  }
-
-  // TODO(pwbug/456): Check targets metadtata content.
-
-  if (self_verifying) {
-    // Don't bother because it does not matter.
-    PW_LOG_WARN("Self verification does not do Targets metadata anti-rollback");
-    return OkStatus();
-  }
-
-  // Anti-rollback check.
-  ManifestAccessor device_manifest = GetOnDeviceManifest();
-  if (device_manifest.status().IsNotFound()) {
-    PW_LOG_WARN("Skipping OTA anti-rollback due to absent device manifest");
-    return OkStatus();
-  }
-
-  protobuf::Uint32 current_version = device_manifest.GetVersion();
-  PW_TRY(current_version.status());
-
-  // Retrieves the version from the new metadata
-  Result<uint32_t> new_version = GetMetadataVersion(
-      top_level_targets_metadata,
-      static_cast<uint32_t>(
-          software_update::TargetsMetadata::Fields::COMMON_METADATA));
-  PW_TRY(new_version.status());
-  if (current_version.value() > new_version.value()) {
-    PW_LOG_ERROR("Blocking Targets metadata rollback from %u to %u",
-                 current_version.value(),
-                 new_version.value());
-    return Status::Unauthenticated();
-  }
-
-  return OkStatus();
-}
-
-Status UpdateBundleAccessor::VerifyTargetsPayloads() {
-  ManifestAccessor bundle_manifest = ManifestAccessor::FromBundle(bundle_);
-  PW_TRY(bundle_manifest.status());
-
-  // Target file descriptors (pathname, length, hash, etc.) listed in the bundle
-  // manifest.
-  protobuf::RepeatedMessages target_files = bundle_manifest.GetTargetFiles();
-  PW_TRY(target_files.status());
-
-  // Verify length and SHA256 hash for each file listed in the manifest.
-  for (protobuf::Message target_file : target_files) {
-    // Extract target file name in the form of a `std::string_view`.
-    protobuf::String name_proto = target_file.AsString(
-        static_cast<uint32_t>(TargetFile::Fields::FILE_NAME));
-    PW_TRY(name_proto.status());
-    char name_buf[MAX_TARGET_NAME_LENGTH] = {0};
-    Result<std::string_view> target_name =
-        ReadProtoString(name_proto, name_buf);
-    PW_TRY(target_name.status());
-
-    // Get target length.
-    protobuf::Uint64 target_length =
-        target_file.AsUint64(static_cast<uint32_t>(TargetFile::Fields::LENGTH));
-    PW_TRY(target_length.status());
-    if (target_length.value() > PW_SOFTWARE_UPDATE_MAX_TARGET_PAYLOAD_SIZE) {
-      PW_LOG_ERROR("Target payload too big. Maximum is %llu bytes",
-                   PW_SOFTWARE_UPDATE_MAX_TARGET_PAYLOAD_SIZE);
-      return Status::OutOfRange();
-    }
-
-    // Get target SHA256 hash.
-    protobuf::Bytes target_sha256 = Status::NotFound();
-    protobuf::RepeatedMessages hashes = target_file.AsRepeatedMessages(
-        static_cast<uint32_t>(TargetFile::Fields::HASHES));
-    for (protobuf::Message hash : hashes) {
-      protobuf::Uint32 hash_function =
-          hash.AsUint32(static_cast<uint32_t>(Hash::Fields::FUNCTION));
-      PW_TRY(hash_function.status());
-
-      if (hash_function.value() ==
-          static_cast<uint32_t>(HashFunction::SHA256)) {
-        target_sha256 = hash.AsBytes(static_cast<uint32_t>(Hash::Fields::HASH));
-        break;
-      }
-    }
-    PW_TRY(target_sha256.status());
-
-    if (Status status = VerifyTargetPayload(
-            bundle_manifest, target_name.value(), target_length, target_sha256);
-        !status.ok()) {
-      PW_LOG_ERROR("Target: %s failed verification",
-                   pw::MakeString(target_name.value()).c_str());
-      return status;
-    }
-  }  // for each target file in manifest.
-
-  return OkStatus();
-}
-
-Status UpdateBundleAccessor::VerifyTargetPayload(
-    ManifestAccessor manifest,
-    std::string_view target_name,
-    protobuf::Uint64 expected_length,
-    protobuf::Bytes expected_sha256) {
-  protobuf::StringToBytesMap payloads_map = bundle_.AsStringToBytesMap(
-      static_cast<uint32_t>(UpdateBundle::Fields::TARGET_PAYLOADS));
-  stream::IntervalReader payload_reader =
-      payloads_map[target_name].GetBytesReader();
-
-  Status status;
-
-  if (payload_reader.ok()) {
-    status = VerifyInBundleTargetPayload(
-        expected_length, expected_sha256, payload_reader);
-  } else {
-    status = VerifyOutOfBundleTargetPayload(
-        target_name, expected_length, expected_sha256);
-  }
-
-  // TODO(alizhang): Notify backend to do additional checks by calling
-  // backend_.VerifyTargetFile(...).
-  return status;
-}
-
-// TODO(alizhang): Add unit tests for all failure conditions.
-Status UpdateBundleAccessor::VerifyOutOfBundleTargetPayload(
-    std::string_view target_name,
-    protobuf::Uint64 expected_length,
-    protobuf::Bytes expected_sha256) {
-#if PW_SOFTWARE_UPDATE_WITH_PERSONALIZATION
-  // The target payload is "personalized out". We we can't take a measurement
-  // without backend help. For now we will check against the device manifest
-  // which contains a cached measurement of the last software update.
-  ManifestAccessor device_manifest = GetOnDeviceManifest();
-  if (!device_manifest.ok()) {
-    PW_LOG_ERROR(
-        "Can't verify personalized-out target because on-device manifest is "
-        "not found");
-    return Status::Unauthenticated();
-  }
-
-  protobuf::Message cached = device_manifest.GetTargetFile(target_name);
-  if (!cached.ok()) {
-    PW_LOG_ERROR(
-        "Can't verify personalized-out target because it is not found from "
-        "on-device manifest");
-    return Status::Unauthenticated();
-  }
-
-  protobuf::Uint64 cached_length =
-      cached.AsUint64(static_cast<uint32_t>(TargetFile::Fields::LENGTH));
-  PW_TRY(cached_length.status());
-  if (cached_length.value() != expected_length.value()) {
-    PW_LOG_ERROR("Personalized-out target has bad length: %llu, expected: %llu",
-                 cached_length.value(),
-                 expected_length.value());
-    return Status::Unauthenticated();
-  }
-
-  protobuf::Bytes cached_sha256 = Status::NotFound();
-  protobuf::RepeatedMessages hashes = cached.AsRepeatedMessages(
-      static_cast<uint32_t>(TargetFile::Fields::HASHES));
-  for (protobuf::Message hash : hashes) {
-    protobuf::Uint32 hash_function =
-        hash.AsUint32(static_cast<uint32_t>(Hash::Fields::FUNCTION));
-    PW_TRY(hash_function.status());
-
-    if (hash_function.value() == static_cast<uint32_t>(HashFunction::SHA256)) {
-      cached_sha256 = hash.AsBytes(static_cast<uint32_t>(Hash::Fields::HASH));
-      break;
-    }
-  }
-  std::byte sha256[crypto::sha256::kDigestSizeBytes] = {};
-  PW_TRY(cached_sha256.GetBytesReader().Read(sha256));
-
-  Result<bool> hash_equal = expected_sha256.Equal(sha256);
-  PW_TRY(hash_equal.status());
-  if (!hash_equal.value()) {
-    PW_LOG_ERROR("Personalized-out target has a bad hash");
-    return Status::Unauthenticated();
-  }
-
-  return OkStatus();
-#else
-  PW_LOG_ERROR("Target file %s not found in bundle", target_name);
-  return Status::Unauthenticated();
-#endif  // PW_SOFTWARE_UPDATE_WITH_PERSONALIZATION
-}
-
-Status UpdateBundleAccessor::VerifyInBundleTargetPayload(
-    protobuf::Uint64 expected_length,
-    protobuf::Bytes expected_sha256,
-    stream::IntervalReader payload_reader) {
-  // If the target payload is included in the bundle, simply take a
-  // measurement.
-  uint64_t actual_length = payload_reader.interval_size();
-  if (actual_length != expected_length.value()) {
-    PW_LOG_ERROR("Wrong payload length. Expected: %llu, actual: %llu",
-                 expected_length.value(),
-                 actual_length);
-    return Status::Unauthenticated();
-  }
-
-  std::byte actual_sha256[crypto::sha256::kDigestSizeBytes] = {};
-  PW_TRY(crypto::sha256::Hash(payload_reader, actual_sha256));
-  Result<bool> hash_equal = expected_sha256.Equal(actual_sha256);
-  PW_TRY(hash_equal.status());
-  if (!hash_equal.value()) {
-    PW_LOG_ERROR("Wrong payload sha256 hash");
-    return Status::Unauthenticated();
-  }
-
-  return OkStatus();
-}
-
-ManifestAccessor UpdateBundleAccessor::GetManifest() {
-  if (!bundle_verified_) {
-    PW_LOG_DEBUG("Bundled has not passed verification yet");
-    return Status::FailedPrecondition();
-  }
-
-  return ManifestAccessor::FromBundle(bundle_);
-}
-
-}  // namespace pw::software_update
diff --git a/pw_software_update/update_bundle_test.cc b/pw_software_update/update_bundle_test.cc
deleted file mode 100644
index 927c0e6..0000000
--- a/pw_software_update/update_bundle_test.cc
+++ /dev/null
@@ -1,523 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <array>
-
-#include "gtest/gtest.h"
-#include "pw_kvs/fake_flash_memory.h"
-#include "pw_kvs/test_key_value_store.h"
-#include "pw_software_update/bundled_update_backend.h"
-#include "pw_software_update/update_bundle_accessor.h"
-#include "pw_stream/memory_stream.h"
-#include "test_bundles.h"
-
-#define ASSERT_OK(status) ASSERT_EQ(OkStatus(), status)
-#define ASSERT_FAIL(status) ASSERT_NE(OkStatus(), status)
-
-namespace pw::software_update {
-namespace {
-
-constexpr size_t kBufferSize = 256;
-static constexpr size_t kFlashAlignment = 16;
-constexpr size_t kSectorSize = 2048;
-constexpr size_t kSectorCount = 2;
-constexpr size_t kMetadataBufferSize =
-    blob_store::BlobStore::BlobWriter::RequiredMetadataBufferSize(0);
-
-class TestBundledUpdateBackend final : public BundledUpdateBackend {
- public:
-  TestBundledUpdateBackend()
-      : manifest_reader_({}), trusted_root_memory_reader_({}) {}
-
-  Status ApplyReboot() override { return Status::Unimplemented(); }
-  Status PostRebootFinalize() override { return OkStatus(); }
-
-  Status ApplyTargetFile(std::string_view, stream::Reader&, size_t) override {
-    return OkStatus();
-  }
-
-  Result<uint32_t> EnableBundleTransferHandler(std::string_view) override {
-    return 0;
-  }
-
-  void DisableBundleTransferHandler() override {}
-
-  void SetTrustedRoot(ConstByteSpan trusted_root) {
-    trusted_root_memory_reader_ = stream::MemoryReader(trusted_root);
-    trusted_root_reader_ = stream::IntervalReader(
-        trusted_root_memory_reader_,
-        0,
-        trusted_root_memory_reader_.ConservativeReadLimit());
-  }
-
-  void SetCurrentManifest(ConstByteSpan current_manifest) {
-    manifest_reader_ = stream::MemoryReader(current_manifest);
-  }
-
-  void SetManifestWriter(stream::Writer* writer) { manifest_writer_ = writer; }
-
-  virtual Result<stream::SeekableReader*> GetRootMetadataReader() override {
-    return &trusted_root_reader_;
-  };
-
-  Status BeforeManifestRead() override {
-    before_manifest_read_called_ = true;
-    if (manifest_reader_.ConservativeReadLimit() > 0) {
-      return OkStatus();
-    }
-    return Status::NotFound();
-  };
-
-  bool BeforeManifestReadCalled() { return before_manifest_read_called_; }
-
-  Result<stream::SeekableReader*> GetManifestReader() override {
-    return &manifest_reader_;
-  }
-
-  Status BeforeManifestWrite() override {
-    before_manifest_write_called_ = true;
-    return (manifest_writer_) ? OkStatus() : Status::NotFound();
-  }
-
-  bool BeforeManifestWriteCalled() { return before_manifest_write_called_; }
-
-  Status AfterManifestWrite() override {
-    after_manifest_write_called_ = true;
-    return OkStatus();
-  }
-
-  bool AfterManifestWriteCalled() { return after_manifest_write_called_; }
-
-  Result<stream::Writer*> GetManifestWriter() override {
-    return manifest_writer_;
-  }
-
-  virtual Status SafelyPersistRootMetadata(
-      [[maybe_unused]] stream::IntervalReader root_metadata) override {
-    new_root_persisted_ = true;
-    trusted_root_reader_ = root_metadata;
-    return OkStatus();
-  };
-
-  bool IsNewRootPersisted() const { return new_root_persisted_; }
-
- private:
-  stream::IntervalReader trusted_root_reader_;
-  stream::MemoryReader manifest_reader_;
-  stream::Writer* manifest_writer_ = nullptr;
-  bool before_manifest_read_called_ = false;
-  bool before_manifest_write_called_ = false;
-  bool after_manifest_write_called_ = false;
-  bool new_root_persisted_ = false;
-  size_t backend_verified_files_ = 0;
-
-  // A memory reader for buffer passed by SetTrustedRoot(). This will be used
-  // to back `trusted_root_reader_`
-  stream::MemoryReader trusted_root_memory_reader_;
-};
-
-class UpdateBundleTest : public testing::Test {
- public:
-  UpdateBundleTest()
-      : blob_flash_(kFlashAlignment),
-        blob_partition_(&blob_flash_),
-        bundle_blob_("TestBundle",
-                     blob_partition_,
-                     nullptr,
-                     kvs::TestKvs(),
-                     kBufferSize) {}
-
-  blob_store::BlobStoreBuffer<kBufferSize>& bundle_blob() {
-    return bundle_blob_;
-  }
-
-  TestBundledUpdateBackend& backend() { return backend_; }
-
-  void StageTestBundle(ConstByteSpan bundle_data) {
-    ASSERT_OK(bundle_blob_.Init());
-    blob_store::BlobStore::BlobWriter blob_writer(bundle_blob(),
-                                                  metadata_buffer_);
-    ASSERT_OK(blob_writer.Open());
-    ASSERT_OK(blob_writer.Write(bundle_data));
-    ASSERT_OK(blob_writer.Close());
-  }
-
-  // A helper to verify that all bundle operations are disallowed because
-  // the bundle is not open or verified.
-  void VerifyAllBundleOperationsDisallowed(
-      UpdateBundleAccessor& update_bundle) {
-    // We need to check specificially that failure is due to rejecting
-    // unverified/unopen bundle, not anything else.
-    ASSERT_EQ(update_bundle.GetManifest().status(),
-              Status::FailedPrecondition());
-    ASSERT_EQ(update_bundle.GetTargetPayload("any").status(),
-              Status::FailedPrecondition());
-    ASSERT_EQ(update_bundle.GetTargetPayload(protobuf::String({})).status(),
-              Status::FailedPrecondition());
-    ASSERT_EQ(update_bundle.PersistManifest(), Status::FailedPrecondition());
-    ASSERT_EQ(update_bundle.GetTotalPayloadSize().status(),
-              Status::FailedPrecondition());
-  }
-
-  // A helper to verify that UpdateBundleAccessor::OpenAndVerify() fails and
-  // that all bundle operations are disallowed as a result. Also check whether
-  // root metadata should be expected to be persisted.
-  void CheckOpenAndVerifyFail(UpdateBundleAccessor& update_bundle,
-                              bool expect_new_root_persisted) {
-    ASSERT_FALSE(backend().IsNewRootPersisted());
-    ASSERT_FAIL(update_bundle.OpenAndVerify());
-    ASSERT_EQ(backend().IsNewRootPersisted(), expect_new_root_persisted);
-    VerifyAllBundleOperationsDisallowed(update_bundle);
-
-    ASSERT_OK(update_bundle.Close());
-    VerifyAllBundleOperationsDisallowed(update_bundle);
-  }
-
- private:
-  kvs::FakeFlashMemoryBuffer<kSectorSize, kSectorCount> blob_flash_;
-  kvs::FlashPartition blob_partition_;
-  blob_store::BlobStoreBuffer<kBufferSize> bundle_blob_;
-  std::array<std::byte, kMetadataBufferSize> metadata_buffer_;
-  TestBundledUpdateBackend backend_;
-};
-
-}  // namespace
-
-TEST_F(UpdateBundleTest, GetTargetPayload) {
-  backend().SetTrustedRoot(kDevSignedRoot);
-  StageTestBundle(kTestDevBundle);
-  UpdateBundleAccessor update_bundle(bundle_blob(), backend());
-
-  ASSERT_OK(update_bundle.OpenAndVerify());
-
-  {
-    stream::IntervalReader res = update_bundle.GetTargetPayload("file1");
-    ASSERT_OK(res.status());
-
-    const char kExpectedContent[] = "file 1 content";
-    char read_buffer[sizeof(kExpectedContent) + 1] = {0};
-    ASSERT_TRUE(res.Read(read_buffer, sizeof(kExpectedContent)).ok());
-    ASSERT_STREQ(read_buffer, kExpectedContent);
-  }
-
-  {
-    stream::IntervalReader res = update_bundle.GetTargetPayload("file2");
-    ASSERT_OK(res.status());
-
-    const char kExpectedContent[] = "file 2 content";
-    char read_buffer[sizeof(kExpectedContent) + 1] = {0};
-    ASSERT_TRUE(res.Read(read_buffer, sizeof(kExpectedContent)).ok());
-    ASSERT_STREQ(read_buffer, kExpectedContent);
-  }
-
-  {
-    stream::IntervalReader res = update_bundle.GetTargetPayload("non-exist");
-    ASSERT_EQ(res.status(), Status::NotFound());
-  }
-}
-
-TEST_F(UpdateBundleTest, PersistManifest) {
-  backend().SetTrustedRoot(kDevSignedRoot);
-  StageTestBundle(kTestDevBundle);
-  UpdateBundleAccessor update_bundle(bundle_blob(), backend());
-
-  ASSERT_OK(update_bundle.OpenAndVerify());
-
-  std::byte manifest_buffer[sizeof(kTestBundleManifest)] = {};
-  stream::MemoryWriter manifest_writer(manifest_buffer);
-  backend().SetManifestWriter(&manifest_writer);
-  ASSERT_FALSE(backend().BeforeManifestWriteCalled());
-  ASSERT_FALSE(backend().AfterManifestWriteCalled());
-  ASSERT_OK(update_bundle.PersistManifest());
-  ASSERT_TRUE(backend().BeforeManifestWriteCalled());
-  ASSERT_TRUE(backend().AfterManifestWriteCalled());
-
-  ASSERT_EQ(
-      memcmp(manifest_buffer, kTestBundleManifest, sizeof(kTestBundleManifest)),
-      0);
-}
-
-TEST_F(UpdateBundleTest, PersistManifestFailIfNotVerified) {
-  backend().SetTrustedRoot(kDevSignedRoot);
-  StageTestBundle(kTestBadProdSignature);
-  UpdateBundleAccessor update_bundle(bundle_blob(), backend());
-
-  ASSERT_FAIL(update_bundle.OpenAndVerify());
-
-  std::byte manifest_buffer[sizeof(kTestBundleManifest)];
-  stream::MemoryWriter manifest_writer(manifest_buffer);
-  backend().SetManifestWriter(&manifest_writer);
-  ASSERT_FALSE(backend().BeforeManifestWriteCalled());
-  ASSERT_FALSE(backend().AfterManifestWriteCalled());
-  ASSERT_FAIL(update_bundle.PersistManifest());
-  ASSERT_FALSE(backend().BeforeManifestWriteCalled());
-  ASSERT_FALSE(backend().AfterManifestWriteCalled());
-}
-
-TEST_F(UpdateBundleTest, SelfVerificationWithIncomingRoot) {
-  StageTestBundle(kTestDevBundleWithRoot);
-  UpdateBundleAccessor update_bundle(
-      bundle_blob(), backend(), /* disable_verification = */ true);
-
-  ASSERT_OK(update_bundle.OpenAndVerify());
-  // Self verification must not persist anything.
-  ASSERT_FALSE(backend().IsNewRootPersisted());
-
-  // Manifest persisting should be allowed as well.
-  std::byte manifest_buffer[sizeof(kTestBundleManifest)];
-  stream::MemoryWriter manifest_writer(manifest_buffer);
-  backend().SetManifestWriter(&manifest_writer);
-  ASSERT_OK(update_bundle.PersistManifest());
-
-  ASSERT_EQ(
-      memcmp(manifest_buffer, kTestBundleManifest, sizeof(kTestBundleManifest)),
-      0);
-}
-
-TEST_F(UpdateBundleTest, SelfVerificationWithoutIncomingRoot) {
-  StageTestBundle(kTestDevBundle);
-  UpdateBundleAccessor update_bundle(
-      bundle_blob(), backend(), /* disable_verification = */ true);
-
-  ASSERT_OK(update_bundle.OpenAndVerify());
-}
-
-TEST_F(UpdateBundleTest, SelfVerificationWithMessedUpRoot) {
-  StageTestBundle(kTestDevBundleWithProdRoot);
-  UpdateBundleAccessor update_bundle(
-      bundle_blob(), backend(), /* disable_verification = */ true);
-
-  ASSERT_FAIL(update_bundle.OpenAndVerify());
-}
-
-TEST_F(UpdateBundleTest, SelfVerificationChecksMissingHashes) {
-  StageTestBundle(kTestBundleMissingTargetHashFile0);
-  UpdateBundleAccessor update_bundle(
-      bundle_blob(), backend(), /* disable_verification = */ true);
-
-  ASSERT_FAIL(update_bundle.OpenAndVerify());
-}
-
-TEST_F(UpdateBundleTest, SelfVerificationChecksBadHashes) {
-  StageTestBundle(kTestBundleMismatchedTargetHashFile0);
-  UpdateBundleAccessor update_bundle(
-      bundle_blob(), backend(), /* disable_verification = */ true);
-
-  ASSERT_FAIL(update_bundle.OpenAndVerify());
-}
-
-TEST_F(UpdateBundleTest, SelfVerificationIgnoresUnsignedBundle) {
-  StageTestBundle(kTestUnsignedBundleWithRoot);
-  UpdateBundleAccessor update_bundle(
-      bundle_blob(), backend(), /* disable_verification = */ true);
-
-  ASSERT_OK(update_bundle.OpenAndVerify());
-}
-
-TEST_F(UpdateBundleTest, OpenAndVerifySucceedsWithAllVerification) {
-  backend().SetTrustedRoot(kDevSignedRoot);
-  backend().SetCurrentManifest(kTestBundleManifest);
-  StageTestBundle(kTestProdBundle);
-  UpdateBundleAccessor update_bundle(bundle_blob(), backend());
-
-  ASSERT_FALSE(backend().IsNewRootPersisted());
-  ASSERT_FALSE(backend().BeforeManifestReadCalled());
-  ASSERT_OK(update_bundle.OpenAndVerify());
-  ASSERT_TRUE(backend().IsNewRootPersisted());
-  ASSERT_TRUE(backend().BeforeManifestReadCalled());
-
-  ASSERT_OK(update_bundle.Close());
-  VerifyAllBundleOperationsDisallowed(update_bundle);
-}
-
-TEST_F(UpdateBundleTest,
-       OpenAndVerifyWithoutIncomingRootSucceedsWithAllVerification) {
-  backend().SetTrustedRoot(kDevSignedRoot);
-  backend().SetCurrentManifest(kTestBundleManifest);
-  // kTestDevBundle does not contain an incoming root. See
-  // pw_software_update/py/pw_software_update/generate_test_bundle.py for
-  // detail of generation.
-  StageTestBundle(kTestDevBundle);
-  UpdateBundleAccessor update_bundle(bundle_blob(), backend());
-
-  ASSERT_FALSE(backend().IsNewRootPersisted());
-  ASSERT_FALSE(backend().BeforeManifestReadCalled());
-  ASSERT_OK(update_bundle.OpenAndVerify());
-  ASSERT_FALSE(backend().IsNewRootPersisted());
-  ASSERT_TRUE(backend().BeforeManifestReadCalled());
-
-  ASSERT_OK(update_bundle.Close());
-  VerifyAllBundleOperationsDisallowed(update_bundle);
-}
-
-TEST_F(UpdateBundleTest, OpenAndVerifyFailsOnMismatchedRootKeyAndSignature) {
-  backend().SetTrustedRoot(kDevSignedRoot);
-  backend().SetCurrentManifest(kTestBundleManifest);
-  // kTestMismatchedRootKeyAndSignature has a dev root metadata that is
-  // prod signed. The root metadata will not be able to verify itself.
-  // See pw_software_update/py/pw_software_update/generate_test_bundle.py for
-  // detail of generation.
-  StageTestBundle(kTestMismatchedRootKeyAndSignature);
-  UpdateBundleAccessor update_bundle(bundle_blob(), backend());
-  CheckOpenAndVerifyFail(update_bundle, false);
-}
-
-TEST_F(UpdateBundleTest, OpenAndVerifyFailsOnBadProdSignature) {
-  backend().SetTrustedRoot(kDevSignedRoot);
-  backend().SetCurrentManifest(kTestBundleManifest);
-  StageTestBundle(kTestBadProdSignature);
-  UpdateBundleAccessor update_bundle(bundle_blob(), backend());
-  CheckOpenAndVerifyFail(update_bundle, false);
-}
-
-TEST_F(UpdateBundleTest, OpenAndVerifyFailsOnBadTargetsSignature) {
-  backend().SetTrustedRoot(kDevSignedRoot);
-  backend().SetCurrentManifest(kTestBundleManifest);
-  StageTestBundle(kTestBadTargetsSignature);
-  UpdateBundleAccessor update_bundle(bundle_blob(), backend());
-  CheckOpenAndVerifyFail(update_bundle, true);
-}
-
-TEST_F(UpdateBundleTest, OpenAndVerifyFailsOnBadTargetsRollBack) {
-  backend().SetTrustedRoot(kDevSignedRoot);
-  backend().SetCurrentManifest(kTestBundleManifest);
-  StageTestBundle(kTestTargetsRollback);
-  UpdateBundleAccessor update_bundle(bundle_blob(), backend());
-  CheckOpenAndVerifyFail(update_bundle, true);
-}
-
-TEST_F(UpdateBundleTest, OpenAndVerifySucceedsWithoutExistingManifest) {
-  backend().SetTrustedRoot(kDevSignedRoot);
-  StageTestBundle(kTestProdBundle);
-  UpdateBundleAccessor update_bundle(bundle_blob(), backend());
-
-  ASSERT_FALSE(backend().IsNewRootPersisted());
-  ASSERT_OK(update_bundle.OpenAndVerify());
-  ASSERT_TRUE(backend().IsNewRootPersisted());
-}
-
-TEST_F(UpdateBundleTest, OpenAndVerifyFailsOnRootRollback) {
-  backend().SetTrustedRoot(kDevSignedRoot);
-  backend().SetCurrentManifest(kTestBundleManifest);
-  StageTestBundle(kTestRootRollback);
-  UpdateBundleAccessor update_bundle(bundle_blob(), backend());
-  CheckOpenAndVerifyFail(update_bundle, false);
-}
-
-TEST_F(UpdateBundleTest, OpenAndVerifyFailsOnMismatchedTargetHashFile0) {
-  backend().SetTrustedRoot(kDevSignedRoot);
-  backend().SetCurrentManifest(kTestBundleManifest);
-  // `kTestBundleMismatchedTargetHashFile0` is auto generated by
-  // pw_software_update/py/pw_software_update/generate_test_bundle.py.
-  // The hash value for file 0 in the targets metadata is made incorrect.
-  StageTestBundle(kTestBundleMismatchedTargetHashFile0);
-  UpdateBundleAccessor update_bundle(bundle_blob(), backend());
-  CheckOpenAndVerifyFail(update_bundle, true);
-}
-
-TEST_F(UpdateBundleTest, OpenAndVerifyFailsOnMismatchedTargetHashFile1) {
-  backend().SetTrustedRoot(kDevSignedRoot);
-  backend().SetCurrentManifest(kTestBundleManifest);
-  // `kTestBundleMismatchedTargetHashFile1` is auto generated by
-  // pw_software_update/py/pw_software_update/generate_test_bundle.py
-  // The hash value for file 1 in the targets metadata is made incorrect.
-  StageTestBundle(kTestBundleMismatchedTargetHashFile1);
-  UpdateBundleAccessor update_bundle(bundle_blob(), backend());
-  CheckOpenAndVerifyFail(update_bundle, true);
-}
-
-TEST_F(UpdateBundleTest, OpenAndVerifyFailsOnMissingTargetHashFile0) {
-  backend().SetTrustedRoot(kDevSignedRoot);
-  backend().SetCurrentManifest(kTestBundleManifest);
-  // `kTestBundleMismatchedTargetHashFile0` is auto generated by
-  // pw_software_update/py/pw_software_update/generate_test_bundle.py.
-  // The hash value for file 0 is removed.
-  StageTestBundle(kTestBundleMissingTargetHashFile0);
-  UpdateBundleAccessor update_bundle(bundle_blob(), backend());
-  CheckOpenAndVerifyFail(update_bundle, true);
-}
-
-TEST_F(UpdateBundleTest, OpenAndVerifyFailsOnMissingTargetHashFile1) {
-  backend().SetTrustedRoot(kDevSignedRoot);
-  backend().SetCurrentManifest(kTestBundleManifest);
-  // `kTestBundleMismatchedTargetHashFile1` is auto generated by
-  // pw_software_update/py/pw_software_update/generate_test_bundle.py
-  // The hash value for file 1 is removed.
-  StageTestBundle(kTestBundleMissingTargetHashFile1);
-  UpdateBundleAccessor update_bundle(bundle_blob(), backend());
-  CheckOpenAndVerifyFail(update_bundle, true);
-}
-
-TEST_F(UpdateBundleTest, OpenAndVerifyFailsOnMismatchedTargetLengthFile0) {
-  backend().SetTrustedRoot(kDevSignedRoot);
-  backend().SetCurrentManifest(kTestBundleManifest);
-  // `kTestBundleMismatchedTargetLengthFile0` is auto generated by
-  // pw_software_update/py/pw_software_update/generate_test_bundle.py.
-  // The length value for file 0 in the targets metadata is made incorrect (1).
-  StageTestBundle(kTestBundleMismatchedTargetLengthFile0);
-  UpdateBundleAccessor update_bundle(bundle_blob(), backend());
-  CheckOpenAndVerifyFail(update_bundle, true);
-}
-
-TEST_F(UpdateBundleTest, OpenAndVerifyFailsOnMismatchedTargetLengthFile1) {
-  backend().SetTrustedRoot(kDevSignedRoot);
-  backend().SetCurrentManifest(kTestBundleManifest);
-  // `kTestBundleMismatchedTargetLengthFile1` is auto generated by
-  // pw_software_update/py/pw_software_update/generate_test_bundle.py.
-  // The length value for file 0 in the targets metadata is made incorrect (1).
-  StageTestBundle(kTestBundleMismatchedTargetLengthFile1);
-  UpdateBundleAccessor update_bundle(bundle_blob(), backend());
-  CheckOpenAndVerifyFail(update_bundle, true);
-}
-
-TEST_F(UpdateBundleTest, OpenAndVerifySucceedsWithPersonalizedOutFile0) {
-  backend().SetTrustedRoot(kDevSignedRoot);
-  backend().SetCurrentManifest(kTestBundleManifest);
-  // `kTestBundlePersonalizedOutFile0` is auto generated by
-  // pw_software_update/py/pw_software_update/generate_test_bundle.py
-  // The payload for file 0 is removed from the bundle to emulate being
-  // personalized out.
-  StageTestBundle(kTestBundlePersonalizedOutFile0);
-  UpdateBundleAccessor update_bundle(bundle_blob(), backend());
-
-  ASSERT_OK(update_bundle.OpenAndVerify());
-}
-
-TEST_F(UpdateBundleTest, OpenAndVerifySucceedsWithPersonalizedOutFile1) {
-  backend().SetTrustedRoot(kDevSignedRoot);
-  backend().SetCurrentManifest(kTestBundleManifest);
-  // `kTestBundlePersonalizedOutFile1` is auto generated by
-  // pw_software_update/py/pw_software_update/generate_test_bundle.py
-  // The payload for file 1 is removed from the bundle to emulate being
-  // personalized out.
-  StageTestBundle(kTestBundlePersonalizedOutFile1);
-  UpdateBundleAccessor update_bundle(bundle_blob(), backend());
-
-  ASSERT_OK(update_bundle.OpenAndVerify());
-}
-
-TEST_F(UpdateBundleTest,
-       PersonalizationVerificationFailsWithoutDeviceManifest) {
-  backend().SetTrustedRoot(kDevSignedRoot);
-  // `kTestBundlePersonalizedOutFile0` is auto generated by
-  // pw_software_update/py/pw_software_update/generate_test_bundle.py
-  // The payload for file 0 is removed from the bundle to emulate being
-  // personalized out.
-  StageTestBundle(kTestBundlePersonalizedOutFile0);
-  UpdateBundleAccessor update_bundle(bundle_blob(), backend());
-
-  ASSERT_FAIL(update_bundle.OpenAndVerify());
-}
-
-}  // namespace pw::software_update
diff --git a/pw_span/BUILD b/pw_span/BUILD
new file mode 100644
index 0000000..6788a6b
--- /dev/null
+++ b/pw_span/BUILD
@@ -0,0 +1,43 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_span",
+    srcs = ["public/pw_span/internal/span.h"],
+    hdrs = ["public_overrides/span"],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = ["//pw_polyfill"],
+)
+
+pw_cc_test(
+    name = "span_test",
+    srcs = ["span_test.cc"],
+    deps = [
+        ":pw_span",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_span/BUILD.bazel b/pw_span/BUILD.bazel
deleted file mode 100644
index 94919c0..0000000
--- a/pw_span/BUILD.bazel
+++ /dev/null
@@ -1,46 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_span",
-    srcs = ["public/pw_span/internal/span.h"],
-    hdrs = ["public_overrides/span"],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    deps = [
-        "//pw_polyfill",
-        "//pw_polyfill:standard_library",
-    ],
-)
-
-pw_cc_test(
-    name = "span_test",
-    srcs = ["span_test.cc"],
-    deps = [
-        ":pw_span",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_span/CMakeLists.txt b/pw_span/CMakeLists.txt
index a53fd0d..9b22950 100644
--- a/pw_span/CMakeLists.txt
+++ b/pw_span/CMakeLists.txt
@@ -14,13 +14,5 @@
 
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
 
-pw_auto_add_simple_module(pw_span
-  PUBLIC_DEPS
-    pw_polyfill
-    pw_polyfill.standard_library
-)
-target_include_directories(pw_span INTERFACE public_overrides)
-
-if(Zephyr_FOUND AND CONFIG_PIGWEED_SPAN)
-  zephyr_link_libraries(pw_span)
-endif()
+pw_auto_add_simple_module(pw_span PUBLIC_DEPS pw_polyfill)
+target_include_directories(pw_span PUBLIC public_overrides)
diff --git a/pw_span/Kconfig b/pw_span/Kconfig
deleted file mode 100644
index 34ef658..0000000
--- a/pw_span/Kconfig
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-config PIGWEED_SPAN
-    bool "Enable the Pigweed span library (pw_span)"
-    select PIGWEED_POLYFILL
diff --git a/pw_span/OWNERS b/pw_span/OWNERS
deleted file mode 100644
index d96cbc6..0000000
--- a/pw_span/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-hepler@google.com
diff --git a/pw_span/docs.rst b/pw_span/docs.rst
index 63d44ba..2987ce4 100644
--- a/pw_span/docs.rst
+++ b/pw_span/docs.rst
@@ -68,9 +68,4 @@
 
 Compatibility
 =============
-Works with C++14, but some features require C++17.
-
-Zephyr
-======
-To enable ``pw_span`` for Zephyr add ``CONFIG_PIGWEED_SPAN=y`` to the project's
-configuration.
+Works with C++11, but some features require C++17.
diff --git a/pw_span/public/pw_span/internal/span.h b/pw_span/public/pw_span/internal/span.h
index 4e37611..d6b6a41 100644
--- a/pw_span/public/pw_span/internal/span.h
+++ b/pw_span/public/pw_span/internal/span.h
@@ -320,7 +320,7 @@
   constexpr span(const span<U, OtherExtent>& other)
       : span(other.data(), other.size()) {}
 
-  constexpr span& operator=(const span& other) noexcept = default;
+  PW_CONSTEXPR_FUNCTION span& operator=(const span& other) noexcept = default;
   ~span() noexcept = default;
 
   // [span.sub], span subviews
diff --git a/pw_spi/BUILD.bazel b/pw_spi/BUILD.bazel
deleted file mode 100644
index 217f7d4..0000000
--- a/pw_spi/BUILD.bazel
+++ /dev/null
@@ -1,101 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "initiator",
-    hdrs = [
-        "public/pw_spi/initiator.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_assert",
-        "//pw_bytes",
-        "//pw_status",
-    ],
-)
-
-pw_cc_library(
-    name = "chip_selector",
-    hdrs = [
-        "public/pw_spi/chip_selector.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_status",
-    ],
-)
-
-pw_cc_library(
-    name = "device",
-    hdrs = [
-        "public/pw_spi/device.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":chip_selector",
-        ":initiator",
-        "//pw_bytes",
-        "//pw_chrono:system_clock",
-        "//pw_status",
-        "//pw_sync:borrow",
-    ],
-)
-
-pw_cc_test(
-    name = "spi_test",
-    srcs = [
-        "spi_test.cc",
-    ],
-    deps = [
-        ":device",
-        "//pw_sync:mutex",
-        "//pw_unit_test",
-    ],
-)
-
-# Linux-specific spidev implementation.
-pw_cc_library(
-    name = "linux_spi",
-    srcs = ["linux_spi.cc"],
-    hdrs = ["public/pw_spi/linux_spi.h"],
-    includes = ["public"],
-    deps = [
-        ":device",
-        "//pw_bytes",
-        "//pw_chrono:system_clock",
-        "//pw_log",
-        "//pw_status",
-        "//pw_sync:borrow",
-        "//pw_sync:mutex",
-    ],
-)
-
-pw_cc_test(
-    name = "linux_spi_test",
-    srcs = ["linux_spi_test.cc"],
-    deps = [
-        ":device",
-        ":linux_spi",
-    ],
-)
diff --git a/pw_spi/BUILD.gn b/pw_spi/BUILD.gn
deleted file mode 100644
index 3c46fc9..0000000
--- a/pw_spi/BUILD.gn
+++ /dev/null
@@ -1,103 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_unit_test/test.gni")
-
-config("public_include_path") {
-  include_dirs = [ "public" ]
-}
-
-group("pw_spi") {
-  deps = [
-    ":chip_selector",
-    ":device",
-    ":initiator",
-  ]
-  if (host_os == "linux") {
-    deps += [ ":linux_spi" ]
-  }
-}
-
-pw_source_set("initiator") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_spi/initiator.h" ]
-  public_deps = [
-    "$dir_pw_assert",
-    "$dir_pw_bytes",
-    "$dir_pw_status",
-  ]
-}
-
-pw_source_set("chip_selector") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_spi/chip_selector.h" ]
-  public_deps = [ "$dir_pw_status" ]
-}
-
-pw_source_set("device") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_spi/device.h" ]
-  public_deps = [
-    ":chip_selector",
-    ":initiator",
-    "$dir_pw_bytes",
-    "$dir_pw_status",
-    "$dir_pw_sync:borrow",
-  ]
-}
-
-# Linux-specific spidev implementation.
-pw_source_set("linux_spi") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_spi/linux_spi.h" ]
-  public_deps = [
-    ":device",
-    "$dir_pw_bytes",
-    "$dir_pw_log",
-    "$dir_pw_status",
-    "$dir_pw_sync:borrow",
-    "$dir_pw_sync:mutex",
-  ]
-  sources = [ "linux_spi.cc" ]
-}
-
-pw_test_group("tests") {
-  tests = [ ":spi_test" ]
-}
-
-pw_test("spi_test") {
-  sources = [ "spi_test.cc" ]
-  deps = [
-    ":device",
-    "$dir_pw_sync:mutex",
-  ]
-}
-
-# Linux tests currently only work on a target with spidev support and a SPI endpoint
-# mounted at /dev/spidev0.0
-pw_test("linux_spi_test") {
-  sources = [ "linux_spi_test.cc" ]
-  deps = [
-    ":device",
-    ":linux_spi",
-  ]
-}
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
diff --git a/pw_spi/docs.rst b/pw_spi/docs.rst
deleted file mode 100644
index 6332e11..0000000
--- a/pw_spi/docs.rst
+++ /dev/null
@@ -1,338 +0,0 @@
-.. _module-pw_spi:
-
-======
-pw_spi
-======
-Pigweed's SPI module provides a set of interfaces for communicating with SPI
-peripherals attached to a target.
-
---------
-Overview
---------
-The ``pw_spi`` module provides a series of interfaces that facilitate the
-development of SPI peripheral drivers that are abstracted from the target's
-SPI hardware implementation.  The interface consists of three main classes:
-
-- ``pw::spi::Initiator`` - Interface for configuring a SPI bus, and using it
-  to transmit and receive data.
-- ``pw::spi::ChipSelector`` - Interface for enabling/disabling a SPI
-  peripheral attached to the bus.
-- ``pw::spi::Device`` - primary HAL interface used to interact with a SPI
-  peripheral.
-
-``pw_spi`` relies on a target-specific implementations of
-``pw::spi::Initiator`` and ``pw::spi::ChipSelector`` to be defined, and
-injected into ``pw::spi::Device`` objects which are used to communicate with a
-given peripheral attached to a target's SPI bus.
-
-Example - Constructing a SPI Device:
-
-.. code-block:: cpp
-
-   constexpr pw::spi::Config kConfig = {
-       .polarity = pw::spi::ClockPolarity::kActiveHigh,
-       .phase = pw::spi::ClockPhase::kRisingEdge,
-       .bits_per_word = pw::spi::BitsPerWord(8),
-       .bit_order = pw::spi::BitOrder::kLsbFirst,
-   };
-
-   auto initiator = pw::spi::MyInitator();
-   auto selector = pw::spi::MyChipSelector();
-   auto borrowable_initiator = pw::sync::Borrowable<Initiator&>(initiator);
-
-   auto device = pw::spi::Device(borrowable_initiator, kConfig, selector);
-
-This example demonstrates the construction of a ``pw::spi::Device`` from its
-object dependencies and configuration data; where ``MyDevice`` and
-`MyChipSelector`` are concrete implementations of the ``pw::spi::Initiator``
-and ``pw::spi::ChipSelector`` interfaces, respectively.
-
-The use of ``pw::sync::Borrowable`` in the interface provides a
-mutual-exclusion wrapper for the the injected ``pw::spi::Initiator``, ensuring
-that transactions cannot be interrupted or corrupted by other concurrent
-workloads making use of the same SPI bus.
-
-Once constructed, the ``device`` object can then be passed to functions used to
-perform SPI transfers with a target peripheral.
-
-Example - Performing a Transfer:
-
-.. code-block:: cpp
-
-   pw::Result<SensorData> ReadSensorData(pw::spi::Device& device) {
-     std::array<std::byte, 16> raw_sensor_data;
-     constexpr std::array<std::byte, 2> kAccelReportCommand = {
-         std::byte{0x13}, std::byte{0x37}};
-
-     // This device supports full-duplex transfers
-     PW_TRY(device.WriteRead(kAccelReportCommand, raw_sensor_data));
-     return UnpackSensorData(raw_sensor_data);
-   }
-
-The ``ReadSensorData()`` function implements a driver function for a contrived
-SPI accelerometer.  The function performs a full-duplex transfer with the
-device to read its current data.
-
-As this function relies on the ``device`` object that abstracts the details
-of bus-access and chip-selection, the function is portable to any target
-that implements its underlying interfaces.
-
-Example - Performing a Multi-part Transaction:
-
-.. code-block:: cpp
-
-   pw::Result<SensorData> ReadSensorData(pw::spi::Device& device) {
-     std::array<std::byte, 16> raw_sensor_data;
-     constexpr std::array<std::byte, 2> kAccelReportCommand = {
-         std::byte{0x13}, std::byte{0x37}};
-
-     // Creation of the RAII `transaction` acquires exclusive access to the bus
-     std::optional<pw::spi::Device::Transaction> transaction =
-       device.StartTransaction(pw::spi::ChipSelectBehavior::kPerTransaction);
-     if (!transaction.has_value()) {
-       return pw::Status::Unknown();
-     )
-
-     // This device only supports half-duplex transfers
-     PW_TRY(transaction->Write(kAccelReportCommand));
-     PW_TRY(transaction->Read(raw_sensor_data))
-
-     return UnpackSensorData(raw_sensor_data);
-
-     // Destruction of RAII `transaction` object releases lock on the bus
-   }
-
-The code above is similar to the previous example, but makes use of the
-``Transaction`` API in ``pw::spi::Device`` to perform separate, half-duplex
-``Write()`` and ``Read()`` transfers, as is required by the sensor in this
-examplre.
-
-The use of the RAII ``transaction`` object in this example guarantees that
-no other thread can perform transfers on the same SPI bus
-(``pw::spi::Initiator``) until it goes out-of-scope.
-
-------------------
-pw::spi Interfaces
-------------------
-The SPI API consists of the following components:
-
-- The ``pw::spi::Initiator`` interface, and its associated configuration data
-  structs.
-- The ``pw::spi::ChipSelector`` interface.
-- The ``pw::spi::Device`` class.
-
-pw::spi::Initiator
-------------------
-.. inclusive-language: disable
-
-The common interface for configuring a SPI bus, and initiating transfers using
-it.
-
-A concrete implementation of this interface class *must* be defined in order
-to use ``pw_spi`` with a specific target.
-
-The ``spi::Initiator`` object configures the SPI bus to communicate with a
-defined set of common bus parameters that include:
-
-- clock polarity/phase
-- bits-per-word (between 3-32 bits)
-- bit ordering (LSB or MSB first)
-
-These bus configuration parameters are aggregated in the ``pw::spi::Config``
-structure, and passed to the ``pw::spi::Initiator`` via its ``Configure()``
-method.
-
-.. Note:
-
-   Throughtout ``pw_spi``, the terms "controller" and "peripheral" are used to
-   describe the two roles SPI devices can implement.  These terms correspond
-   to the  "master" and "slave" roles described in legacy documentation
-   related to the SPI protocol.
-
-   ``pw_spi`` only supports SPI transfers where the target implements the
-   "controller" role, and does not support the target acting in the
-   "peripheral" role.
-
-.. inclusive-language: enable
-
-.. cpp:class:: pw::spi::Initiator
-
-   .. cpp:function:: Status Configure(const Config& config)
-
-      Configure the SPI bus to coummunicate using a specific set of properties,
-      including the clock polarity, clock phase, bit-order, and bits-per-word.
-
-      Returns OkStatus() on success, and implementation-specific values on
-      failure conditions
-
-   .. cpp:function:: Status WriteRead(ConstByteSpan write_buffer, ByteSpan read_buffer) = 0;
-
-      Perform a synchronous read/write operation on the SPI bus.  Data from the
-      `write_buffer` object is written to the bus, while the `read_buffer` is
-      populated with incoming data on the bus.  The operation will ensure that
-      all requested data is written-to and read-from the bus. In the event the
-      read buffer is smaller than the write buffer (or zero-size), any
-      additional input bytes are discarded. In the event the write buffer is
-      smaller than the read buffer (or zero size), the output is padded with
-      0-bits for the remainder of the transfer.
-
-      Returns OkStatus() on success, and implementation-specific values on
-      failure.
-
-pw::spi::ChipSelector
----------------------
-The ChipSelector class provides an abstract interface for controlling the
-chip-select signal associated with a specific SPI peripheral.
-
-This interface provides a ``SetActive()`` method, which activates/deactivates
-the device based on the value of the `active` parameter.  The associated
-``Activate()`` and ``Deactivate()`` methods are utility wrappers for
-``SetActive(true)`` and ``SetActive(false)``, respectively.
-
-A concrete implementation of this interface class must be provided in order to
-use the SPI HAL to communicate with a peripheral.
-
-.. Note::
-
-   `Active` does not imply a specific logic-level; it is left to the
-   implementor to correctly map logic-levels to the device's active/inactive
-   states.
-
-.. cpp:class:: pw::spi::ChipSelector
-
-   .. cpp:function:: Status SetActive(bool active)
-
-      SetActive sets the state of the chip-select signal to the value
-      represented by the `active` parameter.  Passing a value of `true` will
-      activate the chip-select signal, and `false` will deactive the
-      chip-select signal.
-
-      Returns OkStatus() on success, and implementation-specific values on
-      failure.
-
-   .. cpp:function:: Status Activate()
-
-      Helper method to activate the chip-select signal
-
-      Returns OkStatus() on success, and implementation-specific values on
-      failure.
-
-   .. cpp:function:: Status Deactivate()
-
-      Helper method to deactivate the chip-select signal
-
-      Returns OkStatus() on success, and implementation-specific values on
-      failure.
-
-pw::spi::Device
----------------
-This is primary object used by a client to interact with a target SPI device.
-It provides a wrapper for an injected ``pw::spi::Initator`` object, using
-its methods to configure the bus and perform individual SPI transfers.  The
-injected ``pw::spi::ChipSelector`` object is used internally to activate and
-de-actviate the device on-demand from within the data transfer methods.
-
-The ``Read()``/``Write()``/``WriteRead()`` methods provide support for
-performing inidividual transfers:  ``Read()`` and ``Write()`` perform
-half-duplex operations, where ``WriteRead()`` provides support for
-full-duplex transfers.
-
-The ``StartTransaction()`` method provides support for performing multi-part
-transfers consisting of a series of ``Read()``/``Write()``/``WriteRead()``
-calls, during which the caller is guaranteed exclusive access to the
-underlying bus.  The ``Transaction`` objects returned from this method
-implements the RAII layer providing exclusive access to the bus; exclusive
-access locking is released when the ``Transaction`` object is destroyed/goes
-out of scope.
-
-Mutual-exclusion to the ``pw::spi::Initiator`` object is provided by the use of
-the ``pw::sync::Borrowable`` object, where the ``pw::spi::Initiator`` object is
-"borrowed" for the duration of a transaction.
-
-.. cpp:class:: pw::spi::Device
-
-   .. cpp:function:: Status Read(Bytespan read_buffer)
-
-      Synchronously read data from the SPI peripheral until the provided
-      `read_buffer` is full.
-      This call will configure the bus and activate/deactive chip select
-      for the transfer
-
-      Note: This call will block in the event that other clients are currently
-      performing transactions using the same SPI Initiator.
-
-      Returns OkStatus() on success, and implementation-specific values on
-      failure.
-
-   .. cpp:function:: Status Write(ConstByteSpan write_buffer)
-
-      Synchronously write the contents of `write_buffer` to the SPI peripheral.
-      This call will configure the bus and activate/deactive chip select
-      for the transfer
-
-      Note: This call will block in the event that other clients are currently
-      performing transactions using the same SPI Initiator.
-
-      Returns OkStatus() on success, and implementation-specific values on
-      failure.
-
-   .. cpp:function:: Status WriteRead(ConstByteSpan write_buffer, ByteSpan read_buffer)
-
-      Perform a synchronous read/write transfer with the SPI peripheral. Data
-      from the `write_buffer` object is written to the bus, while the
-      `read_buffer` is populated with incoming data on the bus.  In the event
-      the read buffer is smaller than the write buffer (or zero-size), any
-      additional input bytes are discarded. In the event the write buffer is
-      smaller than the read buffer (or zero size), the output is padded with
-      0-bits for the remainder of the transfer.
-      This call will configure the bus and activate/deactive chip select
-      for the transfer
-
-      Note: This call will block in the event that other clients are currently
-      performing transactions using the same SPI Initiator.
-
-      Returns OkStatus() on success, and implementation-specific values on
-      failure.
-
-   .. cpp:function:: Transaction StartTransaction(ChipSelectBehavior behavior)
-
-      Begin a transaction with the SPI device.  This creates an RAII
-      `Transaction` object that ensures that only one entity can access the
-      underlying SPI bus (Initiator) for the object's duration. The `behavior`
-      parameter provides a means for a client to select how the chip-select
-      signal will be applied on Read/Write/WriteRead calls taking place with
-      the Transaction object. A value of `kPerWriteRead` will activate/deactive
-      chip-select on each operation, while `kPerTransaction` will hold the
-      chip-select active for the duration of the Transaction object.
-
-.. cpp:class:: pw::spi::Device::Transaction
-
-   .. cpp:function:: Status Read(Bytespan read_buffer)
-
-      Synchronously read data from the SPI peripheral until the provided
-      `read_buffer` is full.
-
-      Returns OkStatus() on success, and implementation-specific values on
-      failure.
-
-   .. cpp:function:: Status Write(ConstByteSpan write_buffer)
-
-      Synchronously write the contents of `write_buffer` to the SPI peripheral
-
-      Returns OkStatus() on success, and implementation-specific values on
-      failure.
-
-   .. cpp:function:: Status WriteRead(ConstByteSpan write_buffer, ByteSpan read_buffer)
-
-      Perform a synchronous read/write transfer on the SPI bus.  Data from the
-      `write_buffer` object is written to the bus, while the `read_buffer` is
-      populated with incoming data on the bus.  The operation will ensure that
-      all requested data is written-to and read-from the bus. In the event the
-      read buffer is smaller than the write buffer (or zero-size), any
-      additional input bytes are discarded. In the event the write buffer is
-      smaller than the read buffer (or zero size), the output is padded with
-      0-bits for the remainder of the transfer.
-
-      Returns OkStatus() on success, and implementation-specific values on
-      failure.
-
diff --git a/pw_spi/linux_spi.cc b/pw_spi/linux_spi.cc
deleted file mode 100644
index 89a6c07..0000000
--- a/pw_spi/linux_spi.cc
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_spi/linux_spi.h"
-
-#include <fcntl.h>
-#include <linux/spi/spidev.h>
-#include <linux/types.h>
-#include <sys/ioctl.h>
-#include <unistd.h>
-
-#include "pw_log/log.h"
-#include "pw_spi/chip_selector.h"
-#include "pw_spi/device.h"
-#include "pw_spi/initiator.h"
-#include "pw_status/try.h"
-
-namespace pw::spi {
-
-LinuxInitiator::~LinuxInitiator() {
-  if (fd_ >= 0) {
-    close(fd_);
-  }
-}
-
-Status LinuxInitiator::LazyInit() {
-  if (fd_ >= 0) {
-    return OkStatus();
-  }
-  fd_ = open(path_, O_RDWR | O_EXCL);
-  if (fd_ < 0) {
-    PW_LOG_ERROR("Unable to open SPI device %s for read/write", path_);
-    return Status::Unavailable();
-  }
-  return OkStatus();
-}
-
-Status LinuxInitiator::Configure(const Config& config) {
-  PW_TRY(LazyInit());
-
-  // Map clock polarity/phase to Linux userspace equivalents
-  uint32_t mode = 0;
-  if (config.polarity == ClockPolarity::kActiveLow) {
-    mode |= SPI_CPOL;  // Clock polarity -- signal is high when idle
-  }
-  if (config.phase == ClockPhase::kFallingEdge) {
-    mode |= SPI_CPHA;  // Clock phase -- latch on falling edge
-  }
-  if (ioctl(fd_, SPI_IOC_WR_MODE32, &mode) < 0) {
-    PW_LOG_ERROR("Unable to set SPI mode");
-    return Status::InvalidArgument();
-  }
-
-  // Configure LSB/MSB first
-  uint8_t lsb_first = 0;
-  if (config.bit_order == BitOrder::kLsbFirst) {
-    lsb_first = 1;  // non-zero value indicates LSB first
-  }
-  if (ioctl(fd_, SPI_IOC_WR_LSB_FIRST, &lsb_first) < 0) {
-    PW_LOG_ERROR("Unable to set SPI LSB");
-    return Status::InvalidArgument();
-  }
-
-  // Configure bits-per-word
-  uint8_t bits_per_word = config.bits_per_word();
-  if (ioctl(fd_, SPI_IOC_WR_BITS_PER_WORD, &bits_per_word) < 0) {
-    PW_LOG_ERROR("Unable to set SPI Bits Per Word");
-    return Status::InvalidArgument();
-  }
-
-  // Configure maximum bus speed
-  if (ioctl(fd_, SPI_IOC_WR_MAX_SPEED_HZ, &max_speed_hz_) < 0) {
-    PW_LOG_ERROR("Unable to set SPI Max Speed");
-    return Status::InvalidArgument();
-  }
-
-  return OkStatus();
-}
-
-Status LinuxInitiator::WriteRead(ConstByteSpan write_buffer,
-                                 ByteSpan read_buffer) {
-  PW_TRY(LazyInit());
-
-  // Configure a full-duplex transfer using ioctl()
-  struct spi_ioc_transfer transaction[2];
-  memset(transaction, 0, sizeof(transaction));
-  transaction[0].tx_buf = reinterpret_cast<uintptr_t>(write_buffer.data());
-  transaction[0].len = write_buffer.size();
-
-  transaction[1].rx_buf = reinterpret_cast<uintptr_t>(read_buffer.data());
-  transaction[1].len = read_buffer.size();
-
-  if (ioctl(fd_, SPI_IOC_MESSAGE(2), transaction) < 0) {
-    PW_LOG_ERROR("Unable to perform SPI transfer");
-    return Status::Unknown();
-  }
-
-  return OkStatus();
-}
-
-Status LinuxChipSelector::SetActive(bool /*active*/) {
-  // Note: For Linux' SPI userspace support, chip-select control is not exposed
-  // directly to the user.  This limits our ability to use the SPI HAL to do
-  // composite (multi read-write) transactions with the PW SPI HAL, as Linux
-  // performs composite transactions with a single ioctl() call using an array
-  // of descriptors provided as a parameter -- there's no way of separating
-  // individual operations from userspace.  This could be addressed with a
-  // direct "Composite" transaction HAL API, or by using a raw GPIO
-  // to control of chip select from userspace (which is not common practice).
-  return OkStatus();
-}
-
-}  // namespace pw::spi
diff --git a/pw_spi/linux_spi_test.cc b/pw_spi/linux_spi_test.cc
deleted file mode 100644
index 23a881b..0000000
--- a/pw_spi/linux_spi_test.cc
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_spi/linux_spi.h"
-
-#include <array>
-#include <optional>
-
-#include "gtest/gtest.h"
-#include "pw_spi/chip_selector.h"
-#include "pw_spi/device.h"
-#include "pw_spi/initiator.h"
-#include "pw_status/status.h"
-#include "pw_sync/borrow.h"
-
-namespace pw::spi {
-namespace {
-
-const pw::spi::Config kConfig = {.polarity = ClockPolarity::kActiveHigh,
-                                 .phase = ClockPhase::kFallingEdge,
-                                 .bits_per_word = BitsPerWord(8),
-                                 .bit_order = BitOrder::kMsbFirst};
-
-class LinuxSpi : public ::testing::Test {
- public:
-  LinuxSpi()
-      : initiator_(LinuxInitiator("/dev/spidev0.0", 1000000)),
-        chip_selector_(),
-        initiator_lock_(),
-        borrowable_initiator_(initiator_, initiator_lock_),
-        device_(borrowable_initiator_, kConfig, chip_selector_) {}
-
-  Device& device() { return device_; }
-
- private:
-  LinuxInitiator initiator_;
-  LinuxChipSelector chip_selector_;
-  sync::VirtualMutex initiator_lock_;
-  sync::Borrowable<Initiator> borrowable_initiator_;
-  [[maybe_unused]] Device device_;
-};
-
-TEST_F(LinuxSpi, StartTransaction_Succeeds) {
-  // arrange
-  std::optional<Device::Transaction> transaction =
-      device().StartTransaction(ChipSelectBehavior::kPerWriteRead);
-
-  // act
-
-  // assert
-  EXPECT_TRUE(transaction.has_value());
-}
-
-TEST_F(LinuxSpi, HalfDuplexTransaction_Succeeds) {
-  // arrange
-  std::optional<Device::Transaction> transaction =
-      device().StartTransaction(ChipSelectBehavior::kPerWriteRead);
-
-  // act
-  ASSERT_TRUE(transaction.has_value());
-
-  std::array write_data{std::byte(1), std::byte(2), std::byte(3), std::byte(4)};
-  auto write_status = transaction->Write(ConstByteSpan(write_data));
-
-  std::array read_data{std::byte(1), std::byte(2), std::byte(3), std::byte(4)};
-  auto read_status = transaction->Read(read_data);
-
-  // assert
-  EXPECT_TRUE(write_status.ok());
-  EXPECT_TRUE(read_status.ok());
-}
-
-TEST_F(LinuxSpi, FullDuplexTransaction_Succeeds) {
-  // arrange
-  std::optional<Device::Transaction> transaction =
-      device().StartTransaction(ChipSelectBehavior::kPerWriteRead);
-
-  // act
-  ASSERT_TRUE(transaction.has_value());
-
-  std::array write_data{std::byte(1), std::byte(2), std::byte(3), std::byte(4)};
-  std::array read_data{std::byte(0), std::byte(0), std::byte(0), std::byte(0)};
-  auto wr_status = transaction->WriteRead(ConstByteSpan(write_data), read_data);
-
-  // assert
-  EXPECT_TRUE(wr_status.ok());
-}
-
-}  // namespace
-}  // namespace pw::spi
diff --git a/pw_spi/public/pw_spi/chip_selector.h b/pw_spi/public/pw_spi/chip_selector.h
deleted file mode 100644
index 4792623..0000000
--- a/pw_spi/public/pw_spi/chip_selector.h
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_status/status.h"
-
-namespace pw::spi {
-
-// Configuration data used to determine how the Chip Select signal is controlled
-// throughout a transaction.
-// `kPerWriteRead` indicates that the chip-select signal should be
-// activated/deactivated between calls to WriteRead()
-enum class ChipSelectBehavior : uint8_t {
-  kPerWriteRead,
-  kPerTransaction,
-};
-
-// The ChipSelector class provides an abstract interface for controlling the
-// chip-select signal associated with a specific SPI peripheral.
-class ChipSelector {
- public:
-  virtual ~ChipSelector() = default;
-
-  // SetActive sets the state of the chip-select signal to the value represented
-  // by the `active` parameter.  Passing a value of `true` will activate the
-  // chip-select signal, and `false` will deactive the chip-select signal.
-  // Returns OkStatus() on success, and implementation-specific values on
-  // failure.
-  virtual Status SetActive(bool active) = 0;
-
-  // Helper method to activate the chip-select signal
-  // Returns OkStatus() on success, and implementation-specific values on
-  // failure.
-  Status Activate() { return SetActive(true); }
-
-  // Helper method to deactivate the chip-select signal
-  // Returns OkStatus() on success, and implementation-specific values on
-  // failure.
-  Status Deactivate() { return SetActive(false); }
-};
-
-}  // namespace pw::spi
diff --git a/pw_spi/public/pw_spi/device.h b/pw_spi/public/pw_spi/device.h
deleted file mode 100644
index db07e9c..0000000
--- a/pw_spi/public/pw_spi/device.h
+++ /dev/null
@@ -1,208 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_bytes/span.h"
-#include "pw_spi/chip_selector.h"
-#include "pw_spi/initiator.h"
-#include "pw_status/status.h"
-#include "pw_status/try.h"
-#include "pw_sync/borrow.h"
-
-namespace pw::spi {
-
-// The Device class enables data transfer with a specific SPI peripheral.
-// This class combines an Initiator (representing the physical SPI bus), its
-// configuration data, and the ChipSelector object to uniquely address a device.
-// Transfers to a selected initiator are guarded against concurrent access
-// through the use of the `Borrowable` object.
-class Device {
- public:
-  Device(sync::Borrowable<Initiator>& initiator,
-         const Config config,
-         ChipSelector& selector)
-      : initiator_(initiator), config_(config), selector_(selector) {}
-
-  ~Device() = default;
-
-  // Synchronously read data from the SPI peripheral until the provided
-  // `read_buffer` is full.
-  // This call will configure the bus and activate/deactive chip select
-  // for the transfer
-  //
-  // Note: This call will block in the event that other clients are currently
-  // performing transactions using the same SPI Initiator.
-  // Returns OkStatus() on success, and implementation-specific values on
-  // failure.
-  Status Read(ByteSpan read_buffer) { return WriteRead({}, read_buffer); }
-
-  // Synchronously write the contents of `write_buffer` to the SPI peripheral.
-  // This call will configure the bus and activate/deactive chip select
-  // for the transfer
-  //
-  // Note: This call will block in the event that other clients are currently
-  // performing transactions using the same SPI Initiator.
-  // Returns OkStatus() on success, and implementation-specific values on
-  // failure.
-  Status Write(ConstByteSpan write_buffer) {
-    return WriteRead(write_buffer, {});
-  }
-
-  // Perform a synchronous read/write transfer with the SPI peripheral. Data
-  // from the `write_buffer` object is written to the bus, while the
-  // `read_buffer` is populated with incoming data on the bus.  In the event
-  // the read buffer is smaller than the write buffer (or zero-size), any
-  // additional input bytes are discarded. In the event the write buffer is
-  // smaller than the read buffer (or zero size), the output is padded with
-  // 0-bits for the remainder of the transfer.
-  // This call will configure the bus and activate/deactive chip select
-  // for the transfer
-  //
-  // Note: This call will block in the event that other clients
-  // are currently performing transactions using the same SPI Initiator.
-  // Returns OkStatus() on success, and implementation-specific values on
-  // failure.
-  Status WriteRead(ConstByteSpan write_buffer, ByteSpan read_buffer) {
-    return StartTransaction(ChipSelectBehavior::kPerWriteRead)
-        .WriteRead(write_buffer, read_buffer);
-  }
-
-  // RAII Object providing exclusive access to the SPI device.  Enables
-  // thread-safe Read()/Write()/WriteRead() operations, as well as composite
-  // operations consisting of multiple, uninterrupted transfers, with
-  // configurable chip-select behavior.
-  class Transaction final {
-   public:
-    Transaction() = delete;
-    ~Transaction() {
-      if ((selector_ != nullptr) &&
-          (behavior_ == ChipSelectBehavior::kPerTransaction) &&
-          (!first_write_read_)) {
-        selector_->Deactivate()
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-      }
-    }
-
-    // Transaction objects are moveable but not copyable
-    Transaction(Transaction&& other)
-        : initiator_(std::move(other.initiator_)),
-          config_(other.config_),
-          selector_(other.selector_),
-          behavior_(other.behavior_),
-          first_write_read_(other.first_write_read_) {
-      other.selector_ = nullptr;
-    };
-
-    Transaction& operator=(Transaction&& other) {
-      initiator_ = std::move(other.initiator_);
-      config_ = other.config_;
-      selector_ = other.selector_;
-      other.selector_ = nullptr;
-      behavior_ = other.behavior_;
-      first_write_read_ = other.first_write_read_;
-      return *this;
-    }
-
-    Transaction(const Transaction&) = delete;
-    Transaction& operator=(const Transaction&) = delete;
-
-    // Synchronously read data from the SPI peripheral until the provided
-    // `read_buffer` is full.
-    //
-    // Returns OkStatus() on success, and implementation-specific values on
-    // failure.
-    Status Read(ByteSpan read_buffer) { return WriteRead({}, read_buffer); }
-
-    // Synchronously write the contents of `write_buffer` to the SPI peripheral
-    //
-    // Returns OkStatus() on success, and implementation-specific values on
-    // failure.
-    Status Write(ConstByteSpan write_buffer) {
-      return WriteRead(write_buffer, {});
-    }
-
-    // Perform a synchronous read/write transfer on the SPI bus.  Data from the
-    // `write_buffer` object is written to the bus, while the `read_buffer` is
-    // populated with incoming data on the bus.  The operation will ensure that
-    // all requested data is written-to and read-from the bus. In the event the
-    // read buffer is smaller than the write buffer (or zero-size), any
-    // additional input bytes are discarded. In the event the write buffer is
-    // smaller than the read buffer (or zero size), the output is padded with
-    // 0-bits for the remainder of the transfer.
-    //
-    // Returns OkStatus() on success, and implementation-specific values on
-    // failure.
-    Status WriteRead(ConstByteSpan write_buffer, ByteSpan read_buffer) {
-      // Lazy-init: Configure the SPI bus when performing the first transfer in
-      // a transaction.
-      if (first_write_read_) {
-        PW_TRY(initiator_->Configure(config_));
-      }
-
-      if ((behavior_ == ChipSelectBehavior::kPerWriteRead) ||
-          (first_write_read_)) {
-        PW_TRY(selector_->Activate());
-        first_write_read_ = false;
-      }
-
-      auto status = initiator_->WriteRead(write_buffer, read_buffer);
-
-      if (behavior_ == ChipSelectBehavior::kPerWriteRead) {
-        PW_TRY(selector_->Deactivate());
-      }
-
-      return status;
-    }
-
-   private:
-    friend Device;
-    explicit Transaction(sync::BorrowedPointer<Initiator> initiator,
-                         const Config& config,
-                         ChipSelector& selector,
-                         ChipSelectBehavior& behavior)
-        : initiator_(std::move(initiator)),
-          config_(config),
-          selector_(&selector),
-          behavior_(behavior),
-          first_write_read_(true) {}
-
-    sync::BorrowedPointer<Initiator> initiator_;
-    Config config_;
-    ChipSelector* selector_;
-    ChipSelectBehavior behavior_;
-    bool first_write_read_;
-  };
-
-  // Begin a transaction with the SPI device.  This creates an RAII
-  // `Transaction` object that ensures that only one entity can access the
-  // underlying SPI bus (Initiator) for the object's duration. The `behavior`
-  // parameter provides a means for a client to select how the chip-select
-  // signal will be applied on Read/Write/WriteRead calls taking place with the
-  // Transaction object. A value of `kPerWriteRead` will activate/deactive
-  // chip-select on each operation, while `kPerTransaction` will hold the
-  // chip-select active for the duration of the Transaction object.
-  Transaction StartTransaction(ChipSelectBehavior behavior) {
-    return Transaction(initiator_.acquire(), config_, selector_, behavior);
-  }
-
- private:
-  sync::Borrowable<Initiator>& initiator_;
-  const Config config_;
-  ChipSelector& selector_;
-};
-
-}  // namespace pw::spi
diff --git a/pw_spi/public/pw_spi/initiator.h b/pw_spi/public/pw_spi/initiator.h
deleted file mode 100644
index cc826c2..0000000
--- a/pw_spi/public/pw_spi/initiator.h
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <type_traits>
-
-#include "pw_assert/assert.h"
-#include "pw_bytes/span.h"
-#include "pw_status/status.h"
-
-namespace pw::spi {
-
-// ClockPolarity is a configuration parameter that specifies whether a SPI
-// bus clock signal is active low, or active high.
-enum class ClockPolarity : uint8_t {
-  kActiveHigh,  // Corresponds to CPOL = 0
-  kActiveLow,   // Corresponds to CPOL = 1
-};
-
-// ClockPhase is a configuration parameter that specifies whether the
-// phase of the SPI bus clock is rising edge or falling edge.
-enum class ClockPhase : uint8_t {
-  kRisingEdge,   // Corresponds to CPHA = 0
-  kFallingEdge,  // Corresponds to CPHA = 1
-};
-
-// Configuration parameter, specifying the bit order for data clocked over the
-// SPI bus; whether least-significant bit first, or most-significant bit first
-enum class BitOrder : uint8_t {
-  kLsbFirst,
-  kMsbFirst,
-};
-
-// Configuration object used to represent the number of bits in a SPI
-// data word. Devices typically use 8-bit words, although values of 3-32
-// are sometimes specified for bus-level optimizations.  Values outside
-// this range are considered an error.
-class BitsPerWord {
- public:
-  constexpr BitsPerWord(uint8_t data_bits) : data_bits_(data_bits) {
-    PW_ASSERT(data_bits_ >= 3 && data_bits_ <= 32);
-  }
-
-  uint8_t operator()() const { return data_bits_; }
-
- private:
-  uint8_t data_bits_;
-};
-
-// This struct contains the necessary configuration details required to
-// initialize a SPI bus for communication with a target device.
-struct Config {
-  ClockPolarity polarity;
-  ClockPhase phase;
-  BitsPerWord bits_per_word;
-  BitOrder bit_order;
-};
-static_assert(sizeof(Config) == sizeof(uint32_t),
-              "Ensure that the config struct fits in 32-bits");
-
-// The Inititor class provides an abstract interface used to configure and
-// transmit data using a SPI bus.
-class Initiator {
- public:
-  virtual ~Initiator() = default;
-
-  // Configure the SPI bus to communicate with peripherals using a given set of
-  // properties, including the clock polarity, clock phase, bit-order, and
-  // bits-per-wrod.
-  // Returns OkStatus() on success, and implementation-specific values on
-  // failure.
-  virtual Status Configure(const Config& config) = 0;
-
-  // Perform a synchronous read/write operation on the SPI bus.  Data from the
-  // `write_buffer` object is written to the bus, while the `read_buffer` is
-  // populated with incoming data on the bus.  The operation will ensure that
-  // all requested data is written-to and read-from the bus. In the event the
-  // read buffer is smaller than the write buffer (or zero-size), any additional
-  // input bytes are discarded. In the event the write buffer is smaller than
-  // the read buffer (or zero size), the output is padded with 0-bits for the
-  // remainder of the transfer.
-  // Returns OkStatus() on success, and implementation-specific values on
-  // failure.
-  virtual Status WriteRead(ConstByteSpan write_buffer,
-                           ByteSpan read_buffer) = 0;
-};
-
-}  // namespace pw::spi
diff --git a/pw_spi/public/pw_spi/linux_spi.h b/pw_spi/public/pw_spi/linux_spi.h
deleted file mode 100644
index 27bfc1b..0000000
--- a/pw_spi/public/pw_spi/linux_spi.h
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <fcntl.h>
-#include <linux/spi/spidev.h>
-#include <linux/types.h>
-#include <sys/ioctl.h>
-#include <unistd.h>
-
-#include "pw_log/log.h"
-#include "pw_spi/chip_selector.h"
-#include "pw_spi/device.h"
-#include "pw_spi/initiator.h"
-namespace pw::spi {
-
-// Linux userspace implementation of the SPI Initiator
-class LinuxInitiator : public Initiator {
- public:
-  // Configure the Linux Initiator object for use with a bus specified by path,
-  // with a maximum bus-speed (in hz).
-  constexpr LinuxInitiator(const char* path, uint32_t max_speed_hz)
-      : path_(path), max_speed_hz_(max_speed_hz), fd_(-1) {}
-  ~LinuxInitiator();
-
-  // Implements pw::spi::Initiator
-  Status Configure(const Config& config) override;
-  Status WriteRead(ConstByteSpan write_buffer, ByteSpan read_buffer) override;
-
- private:
-  Status LazyInit();
-
-  const char* path_;
-  uint32_t max_speed_hz_;
-  int fd_;
-};
-
-// Linux userspace implementation of SPI ChipSelector
-class LinuxChipSelector : public ChipSelector {
- public:
-  Status SetActive(bool active) override;
-};
-
-}  // namespace pw::spi
diff --git a/pw_spi/spi_test.cc b/pw_spi/spi_test.cc
deleted file mode 100644
index 21e5178..0000000
--- a/pw_spi/spi_test.cc
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <array>
-#include <optional>
-
-#include "gtest/gtest.h"
-#include "pw_spi/chip_selector.h"
-#include "pw_spi/device.h"
-#include "pw_spi/initiator.h"
-#include "pw_status/status.h"
-#include "pw_sync/borrow.h"
-#include "pw_sync/mutex.h"
-
-namespace pw::spi {
-namespace {
-
-const pw::spi::Config kConfig = {.polarity = ClockPolarity::kActiveHigh,
-                                 .phase = ClockPhase::kFallingEdge,
-                                 .bits_per_word = BitsPerWord(8),
-                                 .bit_order = BitOrder::kMsbFirst};
-
-class SpiTestDevice : public ::testing::Test {
- public:
-  SpiTestDevice()
-      : initiator_(),
-        chip_selector_(),
-        initiator_lock_(),
-        borrowable_initiator_(initiator_, initiator_lock_),
-        device_(borrowable_initiator_, kConfig, chip_selector_) {}
-
- private:
-  // Stub SPI Initiator/ChipSelect objects, used to exercise public API surface.
-  class TestInitiator : public Initiator {
-   public:
-    Status Configure(const Config& /*config */) override { return OkStatus(); };
-    Status WriteRead(ConstByteSpan /* write_buffer */,
-                     ByteSpan /* read_buffer */) override {
-      return OkStatus();
-    };
-  };
-
-  class TestChipSelector : public ChipSelector {
-   public:
-    Status SetActive(bool /*active*/) override { return OkStatus(); }
-  };
-
-  TestInitiator initiator_;
-  TestChipSelector chip_selector_;
-  sync::VirtualMutex initiator_lock_;
-  sync::Borrowable<Initiator> borrowable_initiator_;
-  Device device_;
-};
-
-// Simple test ensuring the SPI HAL compiles
-TEST_F(SpiTestDevice, CompilationSucceeds) {
-  // arrange
-  // act
-  // assert
-  EXPECT_TRUE(true);
-}
-
-}  // namespace
-}  // namespace pw::spi
diff --git a/pw_status/BUILD b/pw_status/BUILD
new file mode 100644
index 0000000..6d28e38
--- /dev/null
+++ b/pw_status/BUILD
@@ -0,0 +1,64 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_status",
+    srcs = ["status.cc"],
+    hdrs = [
+        "public/pw_status/status.h",
+        "public/pw_status/status_with_size.h",
+        "public/pw_status/try.h",
+    ],
+    includes = ["public"],
+)
+
+pw_cc_test(
+    name = "status_test",
+    srcs = [
+        "status_test_c.c",
+        "status_test.cc",
+    ],
+    deps = [
+        ":pw_status",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "status_with_size_test",
+    srcs = ["status_with_size_test.cc"],
+    deps = [
+        ":pw_status",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "try_test",
+    srcs = ["try_test.cc"],
+    deps = [
+        ":pw_status",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_status/BUILD.bazel b/pw_status/BUILD.bazel
deleted file mode 100644
index 2d2c2c9..0000000
--- a/pw_status/BUILD.bazel
+++ /dev/null
@@ -1,67 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_status",
-    srcs = [
-        "public/pw_status/internal/config.h",
-        "status.cc",
-    ],
-    hdrs = [
-        "public/pw_status/status.h",
-        "public/pw_status/status_with_size.h",
-        "public/pw_status/try.h",
-    ],
-    includes = ["public"],
-)
-
-pw_cc_test(
-    name = "status_test",
-    srcs = [
-        "status_test.cc",
-        "status_test_c.c",
-    ],
-    deps = [
-        ":pw_status",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "status_with_size_test",
-    srcs = ["status_with_size_test.cc"],
-    deps = [
-        ":pw_status",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "try_test",
-    srcs = ["try_test.cc"],
-    deps = [
-        ":pw_status",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_status/BUILD.gn b/pw_status/BUILD.gn
index 157472b..c1d7086 100644
--- a/pw_status/BUILD.gn
+++ b/pw_status/BUILD.gn
@@ -14,45 +14,22 @@
 
 import("//build_overrides/pigweed.gni")
 
-import("$dir_pw_build/module_config.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_unit_test/test.gni")
 
-declare_args() {
-  # The build target that overrides the default configuration options for this
-  # module. This should point to a source set that provides defines through a
-  # public config (which may -include a file or add defines directly).
-  pw_status_CONFIG = pw_build_DEFAULT_MODULE_CONFIG
-}
-
-config("public_include_path") {
+config("default_config") {
   include_dirs = [ "public" ]
-  visibility = [ ":*" ]
 }
 
 pw_source_set("pw_status") {
-  public_configs = [ ":public_include_path" ]
-  public_deps = [ pw_status_CONFIG ]
+  public_configs = [ ":default_config" ]
   public = [
     "public/pw_status/status.h",
     "public/pw_status/status_with_size.h",
     "public/pw_status/try.h",
   ]
-  sources = [
-    "public/pw_status/internal/config.h",
-    "status.cc",
-  ]
-}
-
-config("check_if_used_config") {
-  defines = [ "PW_STATUS_CFG_CHECK_IF_USED=1" ]
-  visibility = [ ":*" ]
-}
-
-# Use this for pw_status_CONFIG to require pw::Status objects to be used.
-pw_source_set("check_if_used") {
-  public_configs = [ ":check_if_used_config" ]
+  sources = [ "status.cc" ]
 }
 
 pw_test_group("tests") {
diff --git a/pw_status/CMakeLists.txt b/pw_status/CMakeLists.txt
index 09c5f94..3c5a0f0 100644
--- a/pw_status/CMakeLists.txt
+++ b/pw_status/CMakeLists.txt
@@ -14,66 +14,4 @@
 
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
 
-pw_add_module_config(pw_status_CONFIG)
-
-pw_add_module_library(pw_status.config
-  HEADERS
-    public/pw_status/internal/config.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    ${pw_thread_CONFIG}
-)
-
-pw_add_module_library(pw_status
-  HEADERS
-    public/pw_status/status.h
-    public/pw_status/status_with_size.h
-    public/pw_status/try.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_status.config
-  SOURCES
-    status.cc
-)
-if(Zephyr_FOUND AND CONFIG_PIGWEED_STATUS)
-  zephyr_link_libraries(pw_status)
-endif()
-
-# Use this for pw_status_CONFIG to require pw::Status objects to be used.
-pw_add_module_library(pw_status.check_if_used
-  PUBLIC_DEFINES
-    PW_STATUS_CFG_CHECK_IF_USED=1
-)
-
-pw_add_test(pw_status.status_test
-  SOURCES
-    status_test.cc
-    status_test_c.c
-  DEPS
-    pw_status
-  GROUPS
-    modules
-    pw_status
-)
-
-pw_add_test(pw_status.status_with_size_test
-  SOURCES
-    status_with_size_test.cc
-  DEPS
-    pw_status
-  GROUPS
-    modules
-    pw_status
-)
-
-pw_add_test(pw_status.try_test
-  SOURCES
-    try_test.cc
-  DEPS
-    pw_status
-  GROUPS
-    modules
-    pw_status
-)
+pw_auto_add_simple_module(pw_status)
diff --git a/pw_status/Kconfig b/pw_status/Kconfig
deleted file mode 100644
index 9fd180a..0000000
--- a/pw_status/Kconfig
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-config PIGWEED_STATUS
-    bool "Enable the Pigweed status library (pw_status)"
diff --git a/pw_status/OWNERS b/pw_status/OWNERS
deleted file mode 100644
index e9a6c58..0000000
--- a/pw_status/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-ewout@google.com
-hepler@google.com
diff --git a/pw_status/docs.rst b/pw_status/docs.rst
index 997ca53..0f01ea8 100644
--- a/pw_status/docs.rst
+++ b/pw_status/docs.rst
@@ -13,12 +13,12 @@
 object that wraps a status code.
 
 ``pw::Status`` uses Google's standard status codes (see the `Google APIs
-repository <https://github.com/googleapis/googleapis/blob/HEAD/google/rpc/code.proto>`_).
+repository <https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto>`_).
 These codes are used extensively in Google projects including `Abseil
 <https://abseil.io>`_ (`status/status.h
-<https://cs.opensource.google/abseil/abseil-cpp/+/HEAD:absl/status/status.h>`_
+<https://cs.opensource.google/abseil/abseil-cpp/+/master:absl/status/status.h>`_
 ) and `gRPC <https://grpc.io>`_ (`doc/statuscodes.md
-<https://github.com/grpc/grpc/blob/HEAD/doc/statuscodes.md>`_).
+<https://github.com/grpc/grpc/blob/master/doc/statuscodes.md>`_).
 
 An OK ``Status`` is created by the ``pw::OkStatus`` function or by the default
 ``Status`` constructor.  Non-OK ``Status`` is created with a static member
@@ -165,60 +165,19 @@
   // the authentication and try again.
   pw::Status::Unauthenticated()
 
-.. note::
-  Status enumerations are also supported for Python and Typescript.
+.. attention::
 
-Tracking the first error encountered
-------------------------------------
-In some contexts it is useful to track the first error encountered while
-allowing execution to continue. Manually writing out ``if`` statements to check
-and then assign quickly becomes verbose, and doesn't explicitly highlight the
-intended behavior of "latching" to the first error.
+  Some code may use all-caps status values such as ``Status::UNKNOWN`` instead
+  of ``Status::Unknown()``. These all-caps status codes are deprecated and will
+  be removed in the future. Do not use them; use the functions above instead.
 
-  .. code-block:: cpp
+  The all-caps status aliases were deprecated because they do not comply with
+  the style guide and potentially conflict with macro definitions. For example,
+  projects might define an ``INTERNAL`` macro, which would prevent ``status.h``
+  or code that uses ``Status::INTERNAL`` from compiling.
 
-    Status overall_status;
-    for (Sector& sector : sectors) {
-      Status erase_status = sector.Erase();
-      if (!overall_status.ok()) {
-        overall_status = erase_status;
-      }
-
-      if (erase_status.ok()) {
-        Status header_write_status = sector.WriteHeader();
-        if (!overall_status.ok()) {
-          overall_status = header_write_status;
-        }
-      }
-    }
-    return overall_status;
-
-``pw::Status`` has an ``Update()`` helper function that does exactly this to
-reduce visual clutter and succinctly highlight the intended behavior.
-
-  .. code-block:: cpp
-
-    Status overall_status;
-    for (Sector& sector : sectors) {
-      Status erase_status = sector.Erase();
-      overall_status.Update(erase_status);
-
-      if (erase_status.ok()) {
-        overall_status.Update(sector.WriteHeader());
-      }
-    }
-    return overall_status;
-
-Unused result warnings
-----------------------
-If the ``PW_STATUS_CFG_CHECK_IF_USED`` option is enabled, ``pw::Status`` objects
-returned from function calls must be used or it is a compilation error. To
-silence these warnings call ``IgnoreError()`` on the returned status object.
-
-``PW_STATUS_CFG_CHECK_IF_USED`` defaults to off. Pigweed compiles with this
-option enabled, but projects that use Pigweed will need to be updated to compile
-with this option. After all projects have migrated, unused result warnings will
-be enabled unconditionally.
+  The Python tool ``pw_status/update_style.py`` may be used to migrate code in a
+  Git repo to the new status style.
 
 C compatibility
 ---------------
@@ -294,9 +253,4 @@
 
 Compatibility
 =============
-C++14
-
-Zephyr
-======
-To enable ``pw_status`` for Zephyr add ``CONFIG_PIGWEED_STATUS=y`` to the
-project's configuration.
+C++11
diff --git a/pw_status/public/pw_status/internal/config.h b/pw_status/public/pw_status/internal/config.h
deleted file mode 100644
index 62db941..0000000
--- a/pw_status/public/pw_status/internal/config.h
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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
-
-// Controls whether to check if pw::Status values are used. Ununsed Status
-// values cause compilation warnings / errors. Calling the nop IgnoreError()
-// function silences these warnings.
-#ifndef PW_STATUS_CFG_CHECK_IF_USED
-#define PW_STATUS_CFG_CHECK_IF_USED 0
-#endif  // PW_STATUS_CFG_CHECK_IF_USED
-
-// Set internal macro that optionally adds the [[nodiscard]] attribute.
-#if PW_STATUS_CFG_CHECK_IF_USED
-#define _PW_STATUS_NO_DISCARD [[nodiscard]]
-#else
-#define _PW_STATUS_NO_DISCARD
-#endif  // PW_STATUS_CFG_CHECK_IF_USED
diff --git a/pw_status/public/pw_status/status.h b/pw_status/public/pw_status/status.h
index 8ed5330..ae3b852 100644
--- a/pw_status/public/pw_status/status.h
+++ b/pw_status/public/pw_status/status.h
@@ -13,8 +13,6 @@
 // the License.
 #pragma once
 
-#include "pw_status/internal/config.h"
-
 #ifdef __cplusplus
 extern "C" {
 #endif  // __cplusplus
@@ -187,7 +185,7 @@
 // The Status class is a thin, zero-cost abstraction around the pw_Status enum.
 // It initializes to OkStatus() by default and adds ok() and str() methods.
 // Implicit conversions are permitted between pw_Status and pw::Status.
-class _PW_STATUS_NO_DISCARD Status {
+class Status {
  public:
   using Code = pw_Status;
 
@@ -305,20 +303,6 @@
     return code_ == PW_STATUS_UNAUTHENTICATED;
   }
 
-  // Updates this Status to the provided Status IF this status is OK. This is
-  // useful for tracking the first encountered error, as calls to this helper
-  // will not change one error status to another error status.
-  constexpr void Update(Status other) {
-    if (ok()) {
-      code_ = other.code();
-    }
-  }
-
-  // Ignores any errors. This method does nothing except potentially suppress
-  // complaints from any tools that are checking that errors are not dropped on
-  // the floor.
-  constexpr void IgnoreError() const {}
-
   // Returns a null-terminated string representation of the Status.
   [[nodiscard]] const char* str() const { return pw_StatusString(code_); }
 
diff --git a/pw_status/public/pw_status/status_with_size.h b/pw_status/public/pw_status/status_with_size.h
index 9dfb952..48b1496 100644
--- a/pw_status/public/pw_status/status_with_size.h
+++ b/pw_status/public/pw_status/status_with_size.h
@@ -45,7 +45,7 @@
 //      At least for ARMv7-M, the returned struct is created on the stack, which
 //      increases code size.
 //
-class _PW_STATUS_NO_DISCARD StatusWithSize {
+class StatusWithSize {
  public:
   static constexpr StatusWithSize Cancelled(size_t size = 0) {
     return StatusWithSize(Status::Cancelled(), size);
@@ -114,22 +114,15 @@
   constexpr StatusWithSize& operator=(const StatusWithSize&) = default;
 
   // Returns the size. The size is always present, even if status() is an error.
-  [[nodiscard]] constexpr size_t size() const { return size_ & kSizeMask; }
+  constexpr size_t size() const { return size_ & kSizeMask; }
 
   // The maximum valid value for size.
-  [[nodiscard]] static constexpr size_t max_size() { return kSizeMask; }
+  static constexpr size_t max_size() { return kSizeMask; }
 
   // True if status() == OkStatus().
-  [[nodiscard]] constexpr bool ok() const {
-    return (size_ & kStatusMask) == 0u;
-  }
+  constexpr bool ok() const { return (size_ & kStatusMask) == 0u; }
 
-  // Ignores any errors. This method does nothing except potentially suppress
-  // complaints from any tools that are checking that errors are not dropped on
-  // the floor.
-  constexpr void IgnoreError() const {}
-
-  [[nodiscard]] constexpr Status status() const {
+  constexpr Status status() const {
     return static_cast<Status::Code>((size_ & kStatusMask) >> kStatusShift);
   }
 
diff --git a/pw_status/public/pw_status/try.h b/pw_status/public/pw_status/try.h
index 38ab26e..76e7a19 100644
--- a/pw_status/public/pw_status/try.h
+++ b/pw_status/public/pw_status/try.h
@@ -13,8 +13,6 @@
 // the License.
 #pragma once
 
-#include <utility>
-
 #include "pw_status/status.h"
 #include "pw_status/status_with_size.h"
 
@@ -37,7 +35,7 @@
   if (!result.ok()) {                               \
     return ::pw::internal::ConvertToStatus(result); \
   }                                                 \
-  lhs = ::pw::internal::ConvertToValue(result);
+  lhs = std::move(result.size())
 
 // Macro for cleanly working with Status or StatusWithSize objects in functions
 // that return StatusWithSize.
@@ -61,10 +59,6 @@
   return status_with_size.status();
 }
 
-constexpr size_t ConvertToValue(StatusWithSize status_with_size) {
-  return status_with_size.size();
-}
-
 constexpr StatusWithSize ConvertToStatusWithSize(Status status) {
   return StatusWithSize(status, 0);
 }
diff --git a/pw_status/py/BUILD.bazel b/pw_status/py/BUILD.bazel
deleted file mode 100644
index 5c12770..0000000
--- a/pw_status/py/BUILD.bazel
+++ /dev/null
@@ -1,25 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load("@rules_python//python:defs.bzl", "py_library")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-py_library(
-    name = "pw_status",
-    srcs = ["pw_status/__init__.py"],
-    imports = ["."],
-)
diff --git a/pw_status/py/BUILD.gn b/pw_status/py/BUILD.gn
index 5bd0b69..d7ee453 100644
--- a/pw_status/py/BUILD.gn
+++ b/pw_status/py/BUILD.gn
@@ -17,11 +17,7 @@
 import("$dir_pw_build/python.gni")
 
 pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
+  setup = [ "setup.py" ]
   sources = [ "pw_status/__init__.py" ]
   pylintrc = "$dir_pigweed/.pylintrc"
 }
diff --git a/pw_status/py/pw_status/__init__.py b/pw_status/py/pw_status/__init__.py
index 7fcc19e..6d22bda 100644
--- a/pw_status/py/pw_status/__init__.py
+++ b/pw_status/py/pw_status/__init__.py
@@ -25,6 +25,7 @@
     NOT_FOUND = 5
     ALREADY_EXISTS = 6
     PERMISSION_DENIED = 7
+    UNAUTHENTICATED = 16
     RESOURCE_EXHAUSTED = 8
     FAILED_PRECONDITION = 9
     ABORTED = 10
@@ -33,7 +34,6 @@
     INTERNAL = 13
     UNAVAILABLE = 14
     DATA_LOSS = 15
-    UNAUTHENTICATED = 16
 
     def ok(self) -> bool:
         return self is self.OK
diff --git a/pw_status/py/pyproject.toml b/pw_status/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/pw_status/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/pw_status/py/setup.cfg b/pw_status/py/setup.cfg
deleted file mode 100644
index 5d7d081..0000000
--- a/pw_status/py/setup.cfg
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pw_status
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Pigweed Status object
-
-[options]
-packages = find:
-zip_safe = False
-
-[options.package_data]
-pw_status = py.typed
diff --git a/pw_status/py/setup.py b/pw_status/py/setup.py
index 69875d9..13faaaf 100644
--- a/pw_status/py/setup.py
+++ b/pw_status/py/setup.py
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2020 The Pigweed Authors
 #
 # 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
@@ -15,4 +15,13 @@
 
 import setuptools  # type: ignore
 
-setuptools.setup()  # Package definition in setup.cfg
+setuptools.setup(
+    name='pw_status',
+    version='0.0.1',
+    author='Pigweed Authors',
+    author_email='pigweed-developers@googlegroups.com',
+    description='Pigweed Status object',
+    packages=setuptools.find_packages(),
+    package_data={'pw_status': ['py.typed']},
+    zip_safe=False,
+)
diff --git a/pw_status/status_test.cc b/pw_status/status_test.cc
index 33102b7..0bf72f5 100644
--- a/pw_status/status_test.cc
+++ b/pw_status/status_test.cc
@@ -156,16 +156,6 @@
   EXPECT_STREQ("INVALID STATUS", Status(kInvalidCode).str());
 }
 
-TEST(Status, Update) {
-  Status status;
-  status.Update(Status::Cancelled());
-  EXPECT_EQ(status, Status::Cancelled());
-  status.Update(OkStatus());
-  EXPECT_EQ(status, Status::Cancelled());
-  status.Update(Status::NotFound());
-  EXPECT_EQ(status, Status::Cancelled());
-}
-
 // Functions for executing the C pw_Status tests.
 extern "C" {
 
diff --git a/pw_status/ts/BUILD.bazel b/pw_status/ts/BUILD.bazel
deleted file mode 100644
index fe368da..0000000
--- a/pw_status/ts/BUILD.bazel
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
-load("@npm//@bazel/typescript:index.bzl", "ts_project")
-
-package(default_visibility = ["//visibility:public"])
-
-ts_project(
-    name = "lib",
-    srcs = [
-        "index.ts",
-        "status.ts",
-    ],
-    declaration = True,
-    source_map = True,
-    deps = ["@npm//:node_modules"],  # can't use fine-grained deps
-)
-
-js_library(
-    name = "pw_status",
-    package_name = "@pigweed/pw_status",
-    srcs = ["package.json"],
-    deps = [":lib"],
-)
diff --git a/pw_status/ts/index.ts b/pw_status/ts/index.ts
deleted file mode 100644
index 8352b77..0000000
--- a/pw_status/ts/index.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-export * from './status';
diff --git a/pw_status/ts/package.json b/pw_status/ts/package.json
deleted file mode 100644
index 37cedac..0000000
--- a/pw_status/ts/package.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "name": "@pigweed/pw_status",
-  "version": "1.0.0",
-  "main": "index.js",
-  "license": "Apache-2.0",
-  "dependencies": {
-  }
-}
diff --git a/pw_status/ts/status.ts b/pw_status/ts/status.ts
deleted file mode 100644
index 5b4c0ba..0000000
--- a/pw_status/ts/status.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-/** Pigweed Status class; mirrors pw::Status. */
-
-export enum Status {
-  OK = 0,
-  CANCELLED = 1,
-  UNKNOWN = 2,
-  INVALID_ARGUMENT = 3,
-  DEADLINE_EXCEEDED = 4,
-  NOT_FOUND = 5,
-  ALREADY_EXISTS = 6,
-  PERMISSION_DENIED = 7,
-  RESOURCE_EXHAUSTED = 8,
-  FAILED_PRECONDITION = 9,
-  ABORTED = 10,
-  OUT_OF_RANGE = 11,
-  UNIMPLEMENTED = 12,
-  INTERNAL = 13,
-  UNAVAILABLE = 14,
-  DATA_LOSS = 15,
-  UNAUTHENTICATED = 16,
-}
diff --git a/pw_status/ts/tsconfig.json b/pw_status/ts/tsconfig.json
deleted file mode 100644
index 4ddd637..0000000
--- a/pw_status/ts/tsconfig.json
+++ /dev/null
@@ -1,34 +0,0 @@
-{
-  "compilerOptions": {
-    "allowUnreachableCode": false,
-    "allowUnusedLabels": false,
-    "declaration": true,
-    "forceConsistentCasingInFileNames": true,
-    "lib": [
-      "es2018",
-      "dom",
-      "dom.iterable",
-      "esnext"
-    ],
-    "module": "commonjs",
-    "noEmitOnError": true,
-    "noFallthroughCasesInSwitch": true,
-    "noImplicitReturns": true,
-    "pretty": true,
-    "sourceMap": true,
-    "strict": true,
-    "target": "es2018",
-    "jsx": "react",
-    "plugins": [
-      {
-        "name": "@bazel/tsetse",
-        "disabledRules": [
-          "must-use-promises"
-        ]
-      }
-    ]
-  },
-  "exclude": [
-    "node_modules"
-  ]
-}
diff --git a/pw_status/ts/yarn.lock b/pw_status/ts/yarn.lock
deleted file mode 100644
index fb57ccd..0000000
--- a/pw_status/ts/yarn.lock
+++ /dev/null
@@ -1,4 +0,0 @@
-# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
-# yarn lockfile v1
-
-
diff --git a/pw_stm32cube_build/BUILD.gn b/pw_stm32cube_build/BUILD.gn
deleted file mode 100644
index 9a6699a..0000000
--- a/pw_stm32cube_build/BUILD.gn
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_docgen/docs.gni")
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
diff --git a/pw_stm32cube_build/OWNERS b/pw_stm32cube_build/OWNERS
deleted file mode 100644
index fb4f20a..0000000
--- a/pw_stm32cube_build/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-vars@google.com
diff --git a/pw_stm32cube_build/docs.rst b/pw_stm32cube_build/docs.rst
deleted file mode 100644
index 6321495..0000000
--- a/pw_stm32cube_build/docs.rst
+++ /dev/null
@@ -1,219 +0,0 @@
-.. _module-pw_stm32cube_build:
-
-------------------
-pw_stm32cube_build
-------------------
-
-The ``pw_stm32cube_build`` module provides helper utilities for building a
-target with the stm32cube HAL and/or the stm32cube initialization code.
-
-The actual GN build files and headers live in ``third_party/stm32cube`` but
-are documented here. The rationale for keeping the build files in `third_party`
-is that code depending on stm32cube can clearly see that their dependency is on
-third party, not pigweed code.
-
-STM32Cube directory setup
-=========================
-Each stm32 product family (ex. F4, L5, etc.) has its own stm32cube libraries.
-This integration depends on ST's 3 core  `MCU Components`_ instead of their
-monolithic `MCU Package`. The components are the hal_driver, cmsis_core, and
-cmsis_device. All of these repos exist on `ST's GitHub page`_. Compatible
-version tags are specified on the ``README.md`` of each MCU component.
-Within a single directory, the following directory/file names are required.
-
-=============== =============================================
-Dir/File Name     Description
-=============== =============================================
-hal_driver/       checkout of ``stm32{family}xx_hal_driver``
-cmsis_device/     checkout of ``cmsis_device_{family}``
-cmsis_core/       checkout of ``cmsis_core``
-files.txt         list of files generated by `gen_file_list`_
-=============== =============================================
-
-pw_package
-----------
-The stm32cube directory can alternatively be setup using ``pw_package``. This
-will automatically download compatible repos into the expected folders and
-generate the ``files.txt``.
-
-.. code-block:: bash
-
-  pw package install stm32cube_{family}
-
-GN build
-========
-The primary ``pw_source_set`` for this integration is
-``$dir_pw_third_party/stm32cube:stm32cube``. This source set includes all of
-the HAL, init code, and templates, depending on value of the `GN args`_.
-
-Headers
--------
-``$dir_pw_third_party/stm32cube:stm32cube`` contains the following primary
-headers that external targets / applications would care about.
-
-``{family}.h``
-^^^^^^^^^^^^^^
-ex. ``stm32f4xx.h``, ``stm32l5xx.h``
-
-This is the primary HAL header provided by stm32cube. It includes the entire
-HAL and all product specific defines.
-
-``stm32cube/stm32cube.h``
-^^^^^^^^^^^^^^^^^^^^^^^^^
-This is a convenience define provided by this integration. It simply includes
-``{family}.h``.
-
-This is useful because there is a lot of commonality between the HAL's of the
-different stm32 families. Although the API's are not guaranteed to be
-compatible, many basic API's often are (ex. GPIO, UART, etc.). This common
-header allows for stm32 family agnostic modules (ex. ``pw_sys_io_stm32``, which
-could work with most, if not all families).
-
-``stm32cube/init.h``
-^^^^^^^^^^^^^^^^^^^^
-As described in the inject_init_ section, if you decide to use the built in
-init functionality, a pre main init function call, ``pw_stm32cube_Init()``, is
-injected into ST's startup scripts.
-
-This header contains the ``pw_stm32cube_Init()`` function declaration. It
-should be included and implemented by target init code.
-
-GN args
--------
-The stm32cube GN build arguments are defined in
-``$dir_pw_third_party/stm32cube/stm32cube.gni``.
-
-``dir_pw_third_party_stm32cube_xx``
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-These should be set to point to the stm32cube directory for each family that
-you need to build for. These are optional to set and are only provided for
-convenience if you need to build for multiple families in the same project.
-
-``dir_pw_third_party_stm32cube``
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-This needs to point to the stm32cube directory for the current build.
-
-For multi target projects, the standard practice to set this for each target:
-
-.. code-block:: text
-
-  dir_pw_third_party_stm32cube = dir_pw_third_party_stm32cube_f4
-
-
-``pw_third_party_stm32cube_PRODUCT``
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-The product specified in as much detail as possible.
-ex. ``stm32f429zit``, ``stm32l552ze``, ``stm32f207zg``, etc.
-
-``pw_third_party_stm32cube_CONFIG``
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-The pw_source_set that provides ``stm32{family}xx_hal_conf.h``. The default
-uses the in-tree ``stm32{family}xx_hal_conf_template.h``.
-
-``pw_third_party_stm32cube_TIMEBASE``
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-The pw_source_set containing the timebase. The default uses the in-tree
-``stm32{family}xx_hal_timebase_tim_template.c``.
-
-``pw_third_party_stm32cube_CMSIS_INIT``
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-The pw_source_set containing the cmsis init logic. The default uses the in-tree
-``system_stm32{family}xx.c``.
-
-``pw_third_party_stm32cube_CORE_INIT``
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-pw_source_set containing the core initialization logic. This normally includes
-a ``startup_stm32{...}.s`` + a dependent ``pw_linker_script``. The default
-``core_init_template`` uses the upstream startup and linker script matching
-``pw_third_party_stm32cube_PRODUCT``. If set to "", you must provide your own
-linker/startup logic somewhere else in the build.
-
-stm32cube_builder
-=================
-``stm32cube_builder`` is utility that contains the backend scripts used by
-``pw_package/stm32cube`` and the GN build scripts in ``third_party/stm32cube``
-to interact with the stm32cube repos. You should only need to interact with
-``stm32cube_builder`` directly if you are doing something custom, like
-using git submodules instead of pw_package, forking the stm32cube libraries,
-interfacing with a different build system, or using your own init.
-
-gen_file_list
--------------
-Build systems like GN are unable to depend on arbitrary directories. Instead,
-they must have dependencies on specific files. The HAL for each stm32 product
-family has different filenames, so ``files.txt`` was created as a workaround.
-``files.txt`` is a basic list of all the files in the stm32cube directory with
-relavent file extensions. The build system only directly depends on this list,
-which must be updated everytime the underlying repos are updated.
-
-This command will generate ``files.txt`` for correctly structured stm32cube
-directories.
-
-.. code-block:: bash
-
-  stm32cube_builder gen_file_list /path/to/stm32cube_dir
-
-find_files
-----------
-Within each stm32 family, there are specific products. Although most of the
-HAL is common between products, the init code is almost always different.
-``find_files`` looks for all of the files relevant to a particular product
-within a stm32cube directory.
-
-The product string should be specified in as much detail as possible because
-there are sometimes different defines or init code for submembers of products.
-
-Ex. ``stm32f412cx``, ``stm32f412rx``, ``stm32f412vx``, and ``stm32f412zx`` all
-have different init logic, while all ``stm32f439xx`` have the same init.
-
-``find_files`` only ever looks for init (linker + startup scripts) if the
-``--init`` flag is provided.
-
-The output is currently only provided in the GN 'scope' format to stdout.
-The following variables are output: ``family``, ``product_define``,
-``sources``, ``headers``, ``include_dirs``, and the following three if
-``--init`` is specified: ``startup``, ``gcc_linker``, ``iar_linker``.
-
-.. code-block:: bash
-
-  stm32cube_builder find_files /path/to/stm32cube_dir stm32{family}{product} [--init]
-
-inject_init
------------
-ST provides init assembly files for every product in ``cmsis_device``. This is
-helpful for getting up and running quickly, but they directly call into
-``main()`` before initializing the hardware / peripherals. This is because they
-expect to do that initialization in ``main()``, then call into the user
-application. Upstream Pigweed unit tests expect at least ``sys_io`` to be
-initialized before ``main()`` is called.
-
-This command injects a call to ``pw_stm32cube_Init()`` immediately before the
-call to ``main()``. This function should be implemented by the target to do
-whatever init is necessary (hal init, sys_io init, clock configuration, etc.)
-
-``inject_init`` takes in an ST assembly script and outputs the same script with
-the pre main init call. The output is printed to stdout, or to the specified
-``--out-startup-path``.
-
-.. code-block:: bash
-
-  stm32cube_builder inject_init /path/to/startup.s [--out-startup-path /path/to/new_startup.s]
-
-icf_to_ld
----------
-Pigweed primarily uses GCC for its Cortex-M builds. However, ST only provides
-IAR linker scripts in ``cmsis_device`` for most product families. This script
-converts from ST's IAR linker script format (.icf) to a basic GCC linker
-script (.ld). This is a very basic converter that only works with exactly how
-ST currently formats their .icf files.
-
-The output .ld files only contain ``RAM`` and ``FLASH`` sections. Anything more
-complicated will require hand customized .ld scripts. Output is printed to
-stdout or the specified ``--ld-path``.
-
-.. code-block:: bash
-
-  stm32cube_builder inject_init /path/to/iar_linker.icf [--ld-path /path/to/gcc_linker.ld]
-
-.. _`MCU Components`: https://github.com/STMicroelectronics/STM32Cube_MCU_Overall_Offer#stm32cube-mcu-components
-.. _`ST's GitHub page`: https://github.com/STMicroelectronics
diff --git a/pw_stm32cube_build/py/BUILD.gn b/pw_stm32cube_build/py/BUILD.gn
deleted file mode 100644
index 04c2c95..0000000
--- a/pw_stm32cube_build/py/BUILD.gn
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/python.gni")
-
-pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
-  sources = [
-    "pw_stm32cube_build/__init__.py",
-    "pw_stm32cube_build/__main__.py",
-    "pw_stm32cube_build/find_files.py",
-    "pw_stm32cube_build/gen_file_list.py",
-    "pw_stm32cube_build/icf_to_ld.py",
-    "pw_stm32cube_build/inject_init.py",
-  ]
-  tests = [
-    "tests/find_files_test.py",
-    "tests/icf_to_ld_test.py",
-    "tests/inject_init_test.py",
-  ]
-  pylintrc = "$dir_pigweed/.pylintrc"
-}
diff --git a/pw_stm32cube_build/py/pw_stm32cube_build/__init__.py b/pw_stm32cube_build/py/pw_stm32cube_build/__init__.py
deleted file mode 100644
index bdc367b..0000000
--- a/pw_stm32cube_build/py/pw_stm32cube_build/__init__.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""This package provides tooling specific to the stm32cube."""
diff --git a/pw_stm32cube_build/py/pw_stm32cube_build/__main__.py b/pw_stm32cube_build/py/pw_stm32cube_build/__main__.py
deleted file mode 100644
index fff71ba..0000000
--- a/pw_stm32cube_build/py/pw_stm32cube_build/__main__.py
+++ /dev/null
@@ -1,83 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Command line interface for stm32cube_builder."""
-
-import argparse
-import pathlib
-import sys
-
-from pw_stm32cube_build import find_files, gen_file_list, icf_to_ld, inject_init
-
-
-def _parse_args() -> argparse.Namespace:
-    """Setup argparse and parse command line args."""
-    parser = argparse.ArgumentParser()
-
-    subparsers = parser.add_subparsers(dest='command',
-                                       metavar='<command>',
-                                       required=True)
-
-    gen_file_list_parser = subparsers.add_parser(
-        'gen_file_list', help='generate files.txt for stm32cube directory')
-    gen_file_list_parser.add_argument('stm32cube_dir', type=pathlib.Path)
-
-    find_files_parser = subparsers.add_parser(
-        'find_files', help='find files in stm32cube directory')
-    find_files_parser.add_argument('stm32cube_dir', type=pathlib.Path)
-    find_files_parser.add_argument('product_str')
-    find_files_parser.add_argument('--init',
-                                   default=False,
-                                   action='store_true')
-
-    icf_to_ld_parser = subparsers.add_parser(
-        'icf_to_ld', help='convert stm32cube .icf linker files to .ld')
-    icf_to_ld_parser.add_argument('icf_path', type=pathlib.Path)
-    icf_to_ld_parser.add_argument('--ld-path',
-                                  nargs=1,
-                                  default=None,
-                                  type=pathlib.Path)
-
-    inject_init_parser = subparsers.add_parser(
-        'inject_init', help='inject `pw_stm32cube_Init()` into startup_*.s')
-    inject_init_parser.add_argument('in_startup_path', type=pathlib.Path)
-    inject_init_parser.add_argument('--out-startup-path',
-                                    nargs=1,
-                                    default=None,
-                                    type=pathlib.Path)
-
-    return parser.parse_args()
-
-
-def main():
-    """Main command line function."""
-    args = _parse_args()
-
-    if args.command == 'gen_file_list':
-        gen_file_list.gen_file_list(args.stm32cube_dir)
-    elif args.command == 'find_files':
-        find_files.find_files(args.stm32cube_dir, args.product_str, args.init)
-    elif args.command == 'icf_to_ld':
-        icf_to_ld.icf_to_ld(args.icf_path,
-                            args.ld_path[0] if args.ld_path else None)
-    elif args.command == 'inject_init':
-        inject_init.inject_init(
-            args.in_startup_path,
-            args.out_startup_path[0] if args.out_startup_path else None)
-
-    sys.exit(0)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/pw_stm32cube_build/py/pw_stm32cube_build/find_files.py b/pw_stm32cube_build/py/pw_stm32cube_build/find_files.py
deleted file mode 100644
index 6e233ef..0000000
--- a/pw_stm32cube_build/py/pw_stm32cube_build/find_files.py
+++ /dev/null
@@ -1,299 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Finds files for a given product."""
-
-from typing import Any, List, Optional, Set, Tuple
-
-import pathlib
-import re
-
-
-def parse_product_str(product_str: str) -> Tuple[str, Set[str], str]:
-    """Parses provided product string.
-
-    Args:
-        product_str: target supplied product string.
-
-    Returns:
-        (family, defines, name) where
-            `family` is the stm32 product family (ex. 'stm32l5xx')
-            `defines` is a list of potential product defines for the HAL.
-                There can be multiple because some products use a subfamily
-                for their define.
-                (ex. The only stm32f411 define is `STM32F411xE`)
-                The correct define can be validated using `select_define()`
-            `name` is the standardized name for the product string.
-                (ex. product_str = 'STM32F429', name = 'stm32f429xx')
-                This is the product name expected by the filename matching
-                functions (`match_filename()`, etc.)
-
-    Raises:
-        ValueError if the product string does not start with 'stm32' or specify
-            at least the chip model (9 chars).
-    """
-    product_name = product_str.lower()
-    if not product_name.startswith('stm32'):
-        raise ValueError("Product string must start with 'stm32'")
-
-    if len(product_name) < 9:
-        raise ValueError(
-            "Product string too short. Must specify at least the chip model.")
-
-    family = product_name[:7] + 'xx'
-    name = product_name
-
-    # Pad the full name with 'x' to reach the max expected length.
-    name = product_name.ljust(11, 'x')
-
-    # This generates all potential define suffixes for a given product name
-    # This is required because some boards have more specific defines
-    # ex. STM32F411xE, while most others are generic, ex. STM32F439xx
-    # So if the user specifies `stm32f207zgt6u`, this should generate the
-    # following as potential defines
-    #  STM32F207xx, STM32F207Zx, STM32F207xG, STM32F207ZG
-    define_suffixes = ['xx']
-    if name[9] != 'x':
-        define_suffixes.append(name[9].upper() + 'x')
-    if name[10] != 'x':
-        define_suffixes.append('x' + name[10].upper())
-    if name[9] != 'x' and name[10] != 'x':
-        define_suffixes.append(name[9:11].upper())
-
-    defines = set(map(lambda x: product_name[:9].upper() + x, define_suffixes))
-    return (family, defines, name)
-
-
-def select_define(defines: Set[str], family_header: str) -> str:
-    """Selects valid define from set of potential defines.
-
-    Looks for the defines in the family header to pick the correct one.
-
-    Args:
-        defines: set of defines provided by `parse_product_str`
-        family_header: `{family}.h` read into a string
-
-    Returns:
-        A single valid define
-
-    Raises:
-        ValueError if exactly one define is not found.
-    """
-    valid_defines = list(
-        filter(
-            lambda x: f'defined({x})' in family_header or f'defined ({x})' in
-            family_header, defines))
-
-    if len(valid_defines) != 1:
-        raise ValueError("Unable to select a valid define")
-
-    return valid_defines[0]
-
-
-def match_filename(product_name: str, filename: str):
-    """Matches linker and startup filenames with product name.
-
-    Args:
-        product_name: the name standardized by `parse_product_str`
-        filename: a linker or startup filename
-
-    Returns:
-        True if the filename could be associated with the product.
-        False otherwise.
-    """
-    stm32_parts = list(
-        filter(lambda x: x.startswith('stm32'),
-               re.split(r'\.|_', filename.lower())))
-
-    if len(stm32_parts) != 1:
-        return False
-
-    pattern = stm32_parts[0].replace('x', '.')
-
-    return re.match(pattern, product_name) is not None
-
-
-def find_linker_files(
-    product_name: str, files: List[str], stm32cube_path: pathlib.Path
-) -> Tuple[Optional[pathlib.Path], Optional[pathlib.Path]]:
-    """Finds linker file for the given product.
-
-    This searches `files` for linker scripts by name.
-
-    Args:
-        product_name: the name standardized by `parse_product_str`
-        files: list of file paths
-        stm32cube_path: the root path that `files` entries are relative to
-
-    Returns:
-        (gcc_linker, iar_linker) where gcc_linker / iar_linker are paths to a
-            linker file or None
-
-    Raises:
-        ValueError if `product_name` matches with no linker files, or with
-            multiple .ld/.icf files.
-    """
-    linker_files = list(
-        filter(
-            lambda x:
-            (x.endswith('.ld') or x.endswith('.icf')) and '_flash.' in x.lower(
-            ), files))
-    matching_linker_files = list(
-        filter(lambda x: match_filename(product_name,
-                                        pathlib.Path(x).name), linker_files))
-
-    matching_ld_files = list(
-        filter(lambda x: x.endswith('.ld'), matching_linker_files))
-    matching_icf_files = list(
-        filter(lambda x: x.endswith('.icf'), matching_linker_files))
-
-    if len(matching_ld_files) > 1 or len(matching_icf_files) > 1:
-        raise ValueError(
-            f'Too many linker file matches for {product_name}.' +
-            ' Provide a more specific product string or your own linker script'
-        )
-    if not matching_ld_files and not matching_icf_files:
-        raise ValueError(f'No linker script matching {product_name} found')
-
-    return (stm32cube_path /
-            matching_ld_files[0] if matching_ld_files else None,
-            stm32cube_path /
-            matching_icf_files[0] if matching_icf_files else None)
-
-
-def find_startup_file(product_name: str, files: List[str],
-                      stm32cube_path: pathlib.Path) -> pathlib.Path:
-    """Finds startup file for the given product.
-
-    Searches for gcc startup files.
-
-    Args:
-        product_name: the name standardized by `parse_product_str`
-        files: list of file paths
-        stm32cube_path: the root path that `files` entries are relative to
-
-    Returns:
-        Path to matching startup file
-
-    Raises:
-        ValueError if no / > 1 matching startup files found.
-    """
-    # ST provides startup files for gcc, iar, and arm compilers. They have the
-    # same filenames, so this looks for a 'gcc' folder in the path.
-    matching_startup_files = list(
-        filter(
-            lambda f: '/gcc/' in f and f.endswith('.s') and match_filename(
-                product_name, f), files))
-
-    if not matching_startup_files:
-        raise ValueError(f'No matching startup file found for {product_name}')
-    if len(matching_startup_files) == 1:
-        return stm32cube_path / matching_startup_files[0]
-
-    raise ValueError(
-        f'Multiple matching startup files found for {product_name}')
-
-
-_INCLUDE_DIRS = [
-    'hal_driver/Inc',
-    'hal_driver/Inc/Legacy',
-    'cmsis_device/Include',
-    'cmsis_core/Include',
-    'cmsis_core/DSP/Include',
-]
-
-
-def get_include_dirs(stm32cube_path: pathlib.Path) -> List[pathlib.Path]:
-    """Get HAL include directories."""
-    return list(map(lambda f: stm32cube_path / f, _INCLUDE_DIRS))
-
-
-def get_sources_and_headers(
-        files: List[str],
-        stm32cube_path: pathlib.Path) -> Tuple[List[str], List[str]]:
-    """Gets list of all sources and headers needed to build the stm32cube hal.
-
-    Args:
-        files: list of file paths
-        stm32cube_path: the root path that `files` entries are relative to
-
-    Returns:
-        (sources, headers) where
-            `sources` is a list of absolute paths to all core (non-template)
-                sources needed for the hal
-            `headers` is a list of absolute paths to all needed headers
-    """
-    source_files = filter(
-        lambda f: f.startswith('hal_driver/Src') and f.endswith('.c') and
-        'template' not in f, files)
-
-    header_files = filter(
-        lambda f: (any(f.startswith(dir)
-                       for dir in _INCLUDE_DIRS)) and f.endswith('.h'), files)
-
-    rebase_path = lambda f: str(stm32cube_path / f)
-    return list(map(rebase_path,
-                    source_files)), list(map(rebase_path, header_files))
-
-
-def parse_files_txt(stm32cube_path: pathlib.Path) -> List[str]:
-    """Reads files.txt into list."""
-    with open(stm32cube_path / 'files.txt', 'r') as files:
-        return list(
-            filter(lambda x: not x.startswith('#'),
-                   map(lambda f: f.strip(), files.readlines())))
-
-
-def _gn_str_out(name: str, val: Any):
-    """Outputs scoped string in GN format."""
-    print(f'{name} = "{val}"')
-
-
-def _gn_list_str_out(name: str, val: List[Any]):
-    """Outputs list of strings in GN format with correct escaping."""
-    list_str = ','.join('"' + str(x).replace('"', r'\"').replace('$', r'\$') +
-                        '"' for x in val)
-    print(f'{name} = [{list_str}]')
-
-
-def find_files(stm32cube_path: pathlib.Path, product_str: str, init: bool):
-    """Generates and outputs the required GN args for the build."""
-    file_list = parse_files_txt(stm32cube_path)
-
-    include_dirs = get_include_dirs(stm32cube_path)
-    sources, headers = get_sources_and_headers(file_list, stm32cube_path)
-    (family, defines, name) = parse_product_str(product_str)
-
-    family_header_path = list(
-        filter(lambda p: p.endswith(f'/{family}.h'), headers))[0]
-
-    with open(family_header_path, 'rb') as family_header:
-        family_header_str = family_header.read().decode('utf-8',
-                                                        errors='ignore')
-
-    define = select_define(defines, family_header_str)
-
-    _gn_str_out('family', family)
-    _gn_str_out('product_define', define)
-    _gn_list_str_out('sources', sources)
-    _gn_list_str_out('headers', headers)
-    _gn_list_str_out('include_dirs', include_dirs)
-
-    if init:
-        startup_file_path = find_startup_file(name, file_list, stm32cube_path)
-        gcc_linker, iar_linker = find_linker_files(name, file_list,
-                                                   stm32cube_path)
-
-        _gn_str_out('startup', startup_file_path)
-        _gn_str_out('gcc_linker', gcc_linker if gcc_linker else '')
-        _gn_str_out('iar_linker', iar_linker if iar_linker else '')
diff --git a/pw_stm32cube_build/py/pw_stm32cube_build/gen_file_list.py b/pw_stm32cube_build/py/pw_stm32cube_build/gen_file_list.py
deleted file mode 100644
index 89d40d4..0000000
--- a/pw_stm32cube_build/py/pw_stm32cube_build/gen_file_list.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Generates a list of relevant files present in a stm32cube source package."""
-
-from typing import List
-
-import pathlib
-
-
-def gen_file_list(stm32cube_dir: pathlib.Path):
-    """Generates `files.txt` for stm32cube directories
-
-    The paths in `files.txt` are relative paths to the files in the 'posix'
-    path format.
-
-    Args:
-        stm32cube_dir: stm32cube directory containing 'hal_driver',
-            'cmsis_core' and 'cmsis_device
-
-    Raises
-        AssertionError if the provided directory is invalid
-    """
-
-    assert (stm32cube_dir / 'hal_driver').is_dir(), 'hal_driver not found'
-    assert (stm32cube_dir / 'cmsis_core').is_dir(), 'cmsis_core not found'
-    assert (stm32cube_dir / 'cmsis_device').is_dir(), 'cmsis_device not found'
-
-    file_paths: List[pathlib.Path] = []
-    file_paths.extend(stm32cube_dir.glob("**/*.h"))
-    file_paths.extend(stm32cube_dir.glob("**/*.c"))
-    file_paths.extend(stm32cube_dir.glob("**/*.s"))
-    file_paths.extend(stm32cube_dir.glob("**/*.ld"))
-    file_paths.extend(stm32cube_dir.glob("**/*.icf"))
-
-    #TODO: allow arbitrary path for generated file list
-    with open(stm32cube_dir / "files.txt", "w") as out_file:
-        out_file.write('# Generated by pw_stm32cube_build/gen_file_list\n')
-        for file_path in file_paths:
-            out_file.write(
-                file_path.relative_to(stm32cube_dir).as_posix() + '\n')
diff --git a/pw_stm32cube_build/py/pw_stm32cube_build/icf_to_ld.py b/pw_stm32cube_build/py/pw_stm32cube_build/icf_to_ld.py
deleted file mode 100644
index fafede0..0000000
--- a/pw_stm32cube_build/py/pw_stm32cube_build/icf_to_ld.py
+++ /dev/null
@@ -1,288 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Converts ST generated .icf linker files into basic .ld linker files"""
-
-from typing import Dict, Optional, Tuple
-
-import pathlib
-
-
-def parse_icf(icf_file: str) -> Tuple[Dict, Dict]:
-    """Parse ICF linker file.
-
-    ST only provides .icf linker files for many products, so there is a need
-    to generate basic GCC compatible .ld files for all products.
-    This parses the basic features from the .icf format well enough to work
-    for the ST's .icf files that exist in `cmsis_device`
-
-    Args:
-        icf_file: .icf linker file read into a string
-
-    Returns:
-        (regions, blocks) where
-            `regions` is a map from region_name -> (start_hex, end_hex)
-            `blocks` is a map from block_name -> {feature_1: val_1,...}
-
-    Raises:
-        IndexError if .icf is malformed (at least compared to how ST makes them)
-    """
-    symbols = {}
-    regions = {}  # region: (start_addr, end_addr)
-    blocks = {}
-    for line in icf_file.split('\n'):
-        line = line.strip()
-        if line == '' or line.startswith('/*') or line.startswith('//'):
-            continue
-        tokens = line.split()
-        if len(tokens) < 2:
-            continue
-        if tokens[0] == 'define':
-            if tokens[1] == 'symbol':
-                symbols[tokens[2]] = tokens[4].strip(';')
-            elif tokens[1] == 'region':
-                regions[tokens[2].split('_')[0]] = (tokens[5],
-                                                    tokens[7].strip('];'))
-            elif tokens[1] == 'block':
-                blocks[tokens[2]] = {
-                    tokens[4]: tokens[6].strip(','),
-                    tokens[7]: tokens[9]
-                }
-    parsed_regions = {
-        region: (symbols[start] if start in symbols else start,
-                 symbols[end] if end in symbols else end)
-        for region, (start, end) in regions.items()
-    }
-
-    parsed_blocks = {
-        name:
-        {k: symbols[v] if v in symbols else v
-         for k, v in fields.items()}
-        for name, fields in blocks.items()
-    }
-
-    return (parsed_regions, parsed_blocks)
-
-
-def icf_regions_to_ld_regions(icf_regions: Dict) -> Dict:
-    """Converts .icf regions to .ld regions
-
-    The .icf format specifies the beginning and end of each region, while
-    .ld expects the beginning and a length string.
-
-    Args:
-        icf_regions: icf_regions parsed with `parse_icf()`
-
-    Returns:
-        A map from `region_name` -> (start_hex, length_str)
-    """
-    ld_regions = {}
-    for region, (start, end) in icf_regions.items():
-        start_dec = int(start, 16)
-        end_dec = int(end, 16)
-        length = end_dec - start_dec + 1
-        length_str = str(length)
-        if length % 1024 == 0:
-            length_str = f'{int(length/1024)}K'
-
-        # Some icf scripts incorrectly have an exclusive region end.
-        # This corrects for that.
-        elif (length - 1) % 1024 == 0:
-            length_str = f'{int((length-1)/1024)}K'
-
-        # ST's gcc linker scripts generally use FLASH instead of ROM
-        if region == 'ROM':
-            region = 'FLASH'
-
-        ld_regions[region] = (start, length_str)
-
-    return ld_regions
-
-
-def create_ld(ld_regions: Dict, blocks: Dict) -> str:
-    """Create .ld file from template.
-
-    This creates a barebones .ld file that *should* work for most single core
-    stm32 families. It only contains regions for RAM and FLASH.
-
-    This template can be bypassed in GN if a more sophisticated linker file
-    is required.
-
-    Args:
-        ld_regions: generated by `icf_regions_to_ld_regions()`
-        blocks: generated by `parse_icf`
-
-    Returns:
-        a string linker file with the RAM/FLASH specified by the given reginos.
-
-    Raises:
-        KeyError if ld_regions does not contain 'RAM' and 'FLASH'
-    """
-    return f"""\
-ENTRY(Reset_Handler)
-_estack = ORIGIN(RAM) + LENGTH(RAM);
-
-_Min_Heap_Size = {blocks['HEAP']['size']};
-_Min_Stack_Size = {blocks['CSTACK']['size']};
-
-MEMORY
-{{
-  RAM (xrw) : ORIGIN = {ld_regions['RAM'][0]}, LENGTH = {ld_regions['RAM'][1]}
-  FLASH (rx) : ORIGIN = {ld_regions['FLASH'][0]}, LENGTH = {ld_regions['FLASH'][1]}
-}}
-
-SECTIONS
-{{
-  .isr_vector :
-  {{
-    . = ALIGN(8);
-    KEEP(*(.isr_vector))
-    . = ALIGN(8);
-  }} >FLASH
-
-  .text :
-  {{
-    . = ALIGN(8);
-    *(.text)
-    *(.text*)
-    *(.glue_7)
-    *(.glue_7t)
-    *(.eh_frame)
-
-    KEEP (*(.init))
-    KEEP (*(.fini))
-
-    . = ALIGN(8);
-    _etext = .;
-  }} >FLASH
-
-  .rodata :
-  {{
-    . = ALIGN(8);
-    *(.rodata)
-    *(.rodata*)
-    . = ALIGN(8);
-  }} >FLASH
-
-  .ARM.extab   : {{
-    . = ALIGN(8);
-    *(.ARM.extab* .gnu.linkonce.armextab.*)
-    . = ALIGN(8);
-  }} >FLASH
-
-  .ARM : {{
-    . = ALIGN(8);
-    __exidx_start = .;
-    *(.ARM.exidx*)
-    __exidx_end = .;
-    . = ALIGN(8);
-  }} >FLASH
-
-  .preinit_array     :
-  {{
-    . = ALIGN(8);
-    PROVIDE_HIDDEN (__preinit_array_start = .);
-    KEEP (*(.preinit_array*))
-    PROVIDE_HIDDEN (__preinit_array_end = .);
-    . = ALIGN(8);
-  }} >FLASH
-
-  .init_array :
-  {{
-    . = ALIGN(8);
-    PROVIDE_HIDDEN (__init_array_start = .);
-    KEEP (*(SORT(.init_array.*)))
-    KEEP (*(.init_array*))
-    PROVIDE_HIDDEN (__init_array_end = .);
-    . = ALIGN(8);
-  }} >FLASH
-
-  .fini_array :
-  {{
-    . = ALIGN(8);
-    PROVIDE_HIDDEN (__fini_array_start = .);
-    KEEP (*(SORT(.fini_array.*)))
-    KEEP (*(.fini_array*))
-    PROVIDE_HIDDEN (__fini_array_end = .);
-    . = ALIGN(8);
-  }} >FLASH
-
-  _sidata = LOADADDR(.data);
-  .data :
-  {{
-    . = ALIGN(8);
-    _sdata = .;
-    *(.data)
-    *(.data*)
-    . = ALIGN(8);
-    _edata = .;
-  }} >RAM AT> FLASH
-
-  . = ALIGN(8);
-  .bss :
-  {{
-    _sbss = .;
-    __bss_start__ = _sbss;
-    *(.bss)
-    *(.bss*)
-    *(COMMON)
-
-    . = ALIGN(8);
-    _ebss = .;
-    __bss_end__ = _ebss;
-  }} >RAM
-
-  ._user_heap_stack :
-  {{
-    . = ALIGN(8);
-    PROVIDE ( end = . );
-    PROVIDE ( _end = . );
-    . = . + _Min_Heap_Size;
-    . = . + _Min_Stack_Size;
-    . = ALIGN(8);
-  }} >RAM
-
-  /DISCARD/ :
-  {{
-    libc.a ( * )
-    libm.a ( * )
-    libgcc.a ( * )
-  }}
-
-  .ARM.attributes 0 : {{ *(.ARM.attributes) }}
-}}
-    """
-
-
-def icf_to_ld(icf_path: pathlib.Path, ld_path: Optional[pathlib.Path]):
-    """Convert icf file into an ld file.
-
-    Note: This only works for ST generated .icf files.
-
-    Args:
-        icf_path: path to .icf file to convert
-        ld_path: path to write generated .ld file or None.
-                 If None, the .ld file is written to stdout.
-    """
-    with open(icf_path, 'rb') as icf_file:
-        icf_str = icf_file.read().decode('utf-8', errors='ignore')
-
-    icf_regions, blocks = parse_icf(icf_str)
-    ld_regions = icf_regions_to_ld_regions(icf_regions)
-    ld_str = create_ld(ld_regions, blocks)
-
-    if ld_path:
-        with open(ld_path, 'w') as ld_file:
-            ld_file.write(ld_str)
-    else:
-        print(ld_str)
diff --git a/pw_stm32cube_build/py/pw_stm32cube_build/inject_init.py b/pw_stm32cube_build/py/pw_stm32cube_build/inject_init.py
deleted file mode 100644
index 8a68cb6..0000000
--- a/pw_stm32cube_build/py/pw_stm32cube_build/inject_init.py
+++ /dev/null
@@ -1,67 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Injects pre main init to ST startup scripts."""
-
-from typing import Optional
-
-import pathlib
-import re
-
-
-def add_pre_main_init(startup: str) -> str:
-    """Add pw_stm32cube_Init call to startup file
-
-    The stm32cube startup files directly call main(), while pigweed expects to
-    do some setup before main is called. This could include sys_io or system
-    clock initialization.
-
-    This adds a call to `pw_stm32cube_Init()` immediately before the call to
-    `main()`
-
-    Args:
-        startup: The startup script read into a string
-
-    Returns:
-        A new startup script with the `pw_stm32cube_Init()` call added.
-
-    Raises:
-        ValueError if the `main()` call is not found in `startup`
-    """
-    match = re.search(r'\s*bl\s+main', startup)
-    if match is None:
-        raise ValueError("`bl main` not found in startup script")
-
-    return startup[:match.start(
-    )] + '\nbl pw_stm32cube_Init' + startup[match.start():]
-
-
-def inject_init(startup_in: pathlib.Path, startup_out: Optional[pathlib.Path]):
-    """Injects pw_stm32cube_Init before main in given ST startup script.
-
-    Args:
-        startup_in: path to startup_*.s file
-        startup_out: path to write generated startup file or None.
-                    If None, output startup script printed to stdout
-    """
-    with open(startup_in, 'rb') as startup_in_file:
-        startup_in_str = startup_in_file.read().decode('utf-8',
-                                                       errors='ignore')
-
-    startup_out_str = add_pre_main_init(startup_in_str)
-
-    if startup_out:
-        with open(startup_out, 'w') as startup_out_file:
-            startup_out_file.write(startup_out_str)
-    else:
-        print(startup_out_str)
diff --git a/pw_stm32cube_build/py/pw_stm32cube_build/py.typed b/pw_stm32cube_build/py/pw_stm32cube_build/py.typed
deleted file mode 100644
index e69de29..0000000
--- a/pw_stm32cube_build/py/pw_stm32cube_build/py.typed
+++ /dev/null
diff --git a/pw_stm32cube_build/py/pyproject.toml b/pw_stm32cube_build/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/pw_stm32cube_build/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/pw_stm32cube_build/py/setup.cfg b/pw_stm32cube_build/py/setup.cfg
deleted file mode 100644
index 620ebf4..0000000
--- a/pw_stm32cube_build/py/setup.cfg
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pw_stm32cube_build
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Python scripts for stm32cube targets
-
-[options]
-packages = find:
-zip_safe = False
-install_requires =
-
-[options.entry_points]
-console_scripts =
-    stm32cube_builder = pw_stm32cube_build.__main__:main
-
-[options.package_data]
-pw_stm32cube_build = py.typed
diff --git a/pw_stm32cube_build/py/setup.py b/pw_stm32cube_build/py/setup.py
deleted file mode 100644
index 1e2ab2a..0000000
--- a/pw_stm32cube_build/py/setup.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""pw_stm32cube_build"""
-
-import setuptools  # type: ignore
-
-setuptools.setup()  # Package definition in setup.cfg
diff --git a/pw_stm32cube_build/py/tests/find_files_test.py b/pw_stm32cube_build/py/tests/find_files_test.py
deleted file mode 100644
index 882c3bf..0000000
--- a/pw_stm32cube_build/py/tests/find_files_test.py
+++ /dev/null
@@ -1,303 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Generate File List Tests."""
-
-import pathlib
-import unittest
-
-from pw_stm32cube_build import find_files
-
-
-class ParseProductStringTest(unittest.TestCase):
-    """parse_product_str tests."""
-    def test_start_with_stm32(self):
-        with self.assertRaises(ValueError):
-            find_files.parse_product_str('f439zit')
-
-    def test_specify_chip(self):
-        with self.assertRaises(ValueError):
-            find_files.parse_product_str('stm32f43')
-
-    def test_stm32f412zx(self):
-        (family, defines, name) = find_files.parse_product_str('stm32f412zx')
-
-        self.assertEqual(family, 'stm32f4xx')
-        self.assertEqual(defines, {'STM32F412xx', 'STM32F412Zx'})
-        self.assertEqual(name, 'stm32f412zx')
-
-    def test_stm32f439xx(self):
-        (family, defines, name) = find_files.parse_product_str('STM32F439xx')
-
-        self.assertEqual(family, 'stm32f4xx')
-        self.assertEqual(defines, {'STM32F439xx'})
-        self.assertEqual(name, 'stm32f439xx')
-
-    def test_stm32f439(self):
-        (family, defines, name) = find_files.parse_product_str('STM32F439')
-
-        self.assertEqual(family, 'stm32f4xx')
-        self.assertEqual(defines, {'STM32F439xx'})
-        self.assertEqual(name, 'stm32f439xx')
-
-    def test_stm32f439xi(self):
-        (family, defines, name) = find_files.parse_product_str('STM32F439xI')
-
-        self.assertEqual(family, 'stm32f4xx')
-        self.assertEqual(defines, {'STM32F439xx', 'STM32F439xI'})
-        self.assertEqual(name, 'stm32f439xi')
-
-    def test_stm32f439zit6u(self):
-        (family, defines,
-         name) = find_files.parse_product_str('stm32f439zit6u')
-
-        self.assertEqual(family, 'stm32f4xx')
-        self.assertEqual(
-            defines,
-            {'STM32F439xx', 'STM32F439Zx', 'STM32F439xI', 'STM32F439ZI'})
-        self.assertEqual(name, 'stm32f439zit6u')
-
-    def test_stm32l552zet(self):
-        (family, defines, name) = find_files.parse_product_str('stm32l552zet')
-
-        self.assertEqual(family, 'stm32l5xx')
-        self.assertEqual(
-            defines,
-            {'STM32L552xx', 'STM32L552Zx', 'STM32L552xE', 'STM32L552ZE'})
-        self.assertEqual(name, 'stm32l552zet')
-
-    def test_stm32l552xc(self):
-        (family, defines, name) = find_files.parse_product_str('stm32l552xc')
-
-        self.assertEqual(family, 'stm32l5xx')
-        self.assertEqual(defines, {'STM32L552xx', 'STM32L552xC'})
-        self.assertEqual(name, 'stm32l552xc')
-
-    def test_stm32wb5m(self):
-        (family, defines, name) = find_files.parse_product_str('stm32wb5m')
-
-        self.assertEqual(family, 'stm32wbxx')
-        self.assertEqual(defines, {'STM32WB5Mxx'})
-        self.assertEqual(name, 'stm32wb5mxx')
-
-
-class SelectDefineTest(unittest.TestCase):
-    """select_define tests."""
-    def test_stm32f412zx_not_found(self):
-        with self.assertRaises(ValueError):
-            find_files.select_define({'STM32F412xx', 'STM32F412Zx'}, "")
-
-    def test_stm32f412zx_found(self):
-        define = find_files.select_define(
-            {'STM32F412xx', 'STM32F412Zx'},
-            "asdf\nfdas\n#if defined(STM32F412Zx)\n")
-        self.assertEqual(define, 'STM32F412Zx')
-
-    def test_stm32f412zx_multiple_found(self):
-        with self.assertRaises(ValueError):
-            find_files.select_define({
-                'STM32F412xx', 'STM32F412Zx'
-            }, "asdf\n#if defined (STM32F412xx)\n#elif defined(STM32F412Zx)\n")
-
-
-class MatchFilenameTest(unittest.TestCase):
-    """match_filename tests."""
-    def test_stm32f412zx(self):
-        # Match should fail if product name is not specific enough
-        self.assertTrue(
-            find_files.match_filename('stm32f412zx', 'stm32f412zx_flash.icf'))
-        self.assertFalse(
-            find_files.match_filename('stm32f412xx', 'stm32f412zx_flash.icf'))
-        self.assertTrue(
-            find_files.match_filename('stm32f412zx', 'startup_stm32f412zx.s'))
-        self.assertFalse(
-            find_files.match_filename('stm32f412xx', 'startup_stm32f429zx.s'))
-
-    def test_stm32f439xx(self):
-        self.assertTrue(
-            find_files.match_filename('stm32f439xx', 'stm32f439xx_flash.icf'))
-        self.assertFalse(
-            find_files.match_filename('stm32f439xx', 'stm32f429xx_flash.icf'))
-        self.assertTrue(
-            find_files.match_filename('stm32f439xx', 'startup_stm32f439xx.s'))
-        self.assertFalse(
-            find_files.match_filename('stm32f439xx', 'startup_stm32f429xx.s'))
-
-    def test_stm32f439xi(self):
-        self.assertTrue(
-            find_files.match_filename('stm32f439xi', 'stm32f439xx_flash.icf'))
-        self.assertFalse(
-            find_files.match_filename('stm32f439xi', 'stm32f429xx_flash.icf'))
-        self.assertTrue(
-            find_files.match_filename('stm32f439xi', 'startup_stm32f439xx.s'))
-        self.assertFalse(
-            find_files.match_filename('stm32f439xi', 'startup_stm32f429xx.s'))
-
-    def test_stm32l552zet(self):
-        self.assertTrue(
-            find_files.match_filename('stm32l552zet', 'STM32L552xE_FLASH.ld'))
-        self.assertTrue(
-            find_files.match_filename('stm32l552zet', 'STM32L552xx_FLASH.ld'))
-        self.assertFalse(
-            find_files.match_filename('stm32l552zet', 'STM32L552xC_FLASH.ld'))
-        self.assertTrue(
-            find_files.match_filename('stm32l552zet', 'stm32l552xe_flash.icf'))
-        self.assertFalse(
-            find_files.match_filename('stm32l552zet', 'stm32l552xc_flash.icf'))
-        self.assertTrue(
-            find_files.match_filename('stm32l552zet', 'startup_stm32l552xx.s'))
-        self.assertFalse(
-            find_files.match_filename('stm32l552zet', 'startup_stm32l562xx.s'))
-
-
-class FindLinkerFilesTest(unittest.TestCase):
-    """find_linker_files tests."""
-    TEST_PATH = pathlib.Path('/test/path')
-
-    def test_stm32f439xx(self):
-        files = [
-            'path/to/stm32f439xx_flash.icf',
-            'other/path/to/stm32f439xx_sram.icf'
-        ]
-        gcc_linker, iar_linker = find_files.find_linker_files(
-            'stm32f439xx', files, self.TEST_PATH)
-
-        self.assertEqual(gcc_linker, None)
-        self.assertEqual(iar_linker, self.TEST_PATH / files[0])
-
-    def test_stm32f439xx_find_ld(self):
-        files = [
-            'path/to/stm32f439xx_flash.icf',
-            'other/path/to/stm32f439xx_sram.icf',
-            'path/to/STM32F439xx_FLASH.ld',
-        ]
-        gcc_linker, iar_linker = find_files.find_linker_files(
-            'stm32f439xx', files, self.TEST_PATH)
-
-        self.assertEqual(gcc_linker, self.TEST_PATH / files[2])
-        self.assertEqual(iar_linker, self.TEST_PATH / files[0])
-
-    def test_stm32f439xc_error_multiple_matching_ld(self):
-        files = [
-            'path/to/stm32f439xx_flash.icf',
-            'other/path/to/stm32f439xx_sram.icf',
-            'other/path/to/STM32F439xI_FLASH.ld',
-            'path/to/STM32F439xx_FLASH.ld',
-        ]
-        with self.assertRaises(ValueError):
-            find_files.find_linker_files('stm32f439xi', files, self.TEST_PATH)
-
-    def test_stm32f439xc_error_multiple_matching_icf(self):
-        files = [
-            'path/to/stm32f439xx_flash.icf',
-            'other/path/to/stm32f439xc_flash.icf',
-        ]
-        with self.assertRaises(ValueError):
-            find_files.find_linker_files('stm32f439xc', files, self.TEST_PATH)
-
-    def test_stm32f439xc_error_none_found(self):
-        files = [
-            'path/to/stm32f439xc_flash.icf',
-            'other/path/to/stm32f439xc_flash.icf',
-        ]
-        with self.assertRaises(ValueError):
-            find_files.find_linker_files('stm32f439xx', files, self.TEST_PATH)
-
-    # ignore secure and nonsecure variants for the M33 boards
-    def test_stm32l552xe_ignore_s_ns(self):
-        files = [
-            'iar/linker/stm32l552xe_flash_ns.icf',
-            'iar/linker/stm32l552xe_flash_s.icf',
-            'iar/linker/stm32l552xe_flash.icf',
-            'gcc/linker/STM32L552xE_FLASH_ns.ld',
-            'gcc/linker/STM32L552xE_FLASH_s.ld',
-            'gcc/linker/STM32L552xE_FLASH.ld',
-        ]
-        gcc_linker, iar_linker = find_files.find_linker_files(
-            'stm32l552xe', files, self.TEST_PATH)
-
-        self.assertEqual(gcc_linker, self.TEST_PATH / files[-1])
-        self.assertEqual(iar_linker, self.TEST_PATH / files[2])
-
-
-class FindStartupFileTest(unittest.TestCase):
-    """find_startup_file tests."""
-    TEST_PATH = pathlib.Path('/test/path')
-
-    def test_stm32f439xx_none_found(self):
-        files = [
-            'path/to/stm32f439xx_flash.icf',
-            'other/path/to/stm32f439xx_sram.icf',
-            'path/iar/startup_stm32f439xx.s',
-        ]
-        with self.assertRaises(ValueError):
-            find_files.find_startup_file('stm32f439xx', files, self.TEST_PATH)
-
-    def test_stm32f439xx(self):
-        files = [
-            'path/to/stm32f439xx_flash.icf',
-            'other/path/to/stm32f439xx_sram.icf',
-            'path/iar/startup_stm32f439xx.s',
-            'path/gcc/startup_stm32f439xx.s',
-        ]
-        startup_file = find_files.find_startup_file('stm32f439xx', files,
-                                                    self.TEST_PATH)
-
-        self.assertEqual(startup_file, self.TEST_PATH / files[3])
-
-    def test_stm32f439xx_multiple_found(self):
-        files = [
-            'path/to/stm32f439xx_flash.icf',
-            'other/path/to/stm32f439xx_sram.icf',
-            'path/gcc/startup_stm32f439xc.s',
-            'path/gcc/startup_stm32f439xx.s',
-        ]
-        with self.assertRaises(ValueError):
-            find_files.find_startup_file('stm32f439xc', files, self.TEST_PATH)
-
-
-class GetSourceAndHeadersTest(unittest.TestCase):
-    """test_sources_and_headers tests."""
-    def test_sources_and_headers(self):
-        files = [
-            'random/header.h',
-            'random/source.c',
-            'cmsis_core/Include/core_cm4.h',
-            'cmsis_device/Include/stm32f4xx.h',
-            'cmsis_device/Include/stm32f439xx.h',
-            'hal_driver/Inc/stm32f4xx_hal_eth.h',
-            'hal_driver/Src/stm32f4xx_hal_adc.c',
-            'hal_driver/Inc/stm32f4xx_hal.h',
-            'hal_driver/Src/stm32f4xx_hal_timebase_tim_template.c',
-            'hal_driver/Src/stm32f4xx_hal_eth.c',
-        ]
-        path = pathlib.Path('/test/path/to/stm32cube')
-        sources, headers = find_files.get_sources_and_headers(files, path)
-        self.assertSetEqual(
-            set([
-                str(path / 'hal_driver/Src/stm32f4xx_hal_adc.c'),
-                str(path / 'hal_driver/Src/stm32f4xx_hal_eth.c'),
-            ]), set(sources))
-        self.assertSetEqual(
-            set([
-                str(path / 'cmsis_core/Include/core_cm4.h'),
-                str(path / 'cmsis_device/Include/stm32f4xx.h'),
-                str(path / 'cmsis_device/Include/stm32f439xx.h'),
-                str(path / 'hal_driver/Inc/stm32f4xx_hal_eth.h'),
-                str(path / 'hal_driver/Inc/stm32f4xx_hal.h'),
-            ]), set(headers))
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_stm32cube_build/py/tests/icf_to_ld_test.py b/pw_stm32cube_build/py/tests/icf_to_ld_test.py
deleted file mode 100644
index 3d76d91..0000000
--- a/pw_stm32cube_build/py/tests/icf_to_ld_test.py
+++ /dev/null
@@ -1,179 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Finds files for a given product."""
-
-import unittest
-
-from pw_stm32cube_build import icf_to_ld
-
-
-class ParseIcfTest(unittest.TestCase):
-    """parse_icf tests."""
-    TEST_ICF_1 = """
-/*test comments*/
-// some other comments
-define symbol __ICFEDIT_intvec_start__ = 0x08000000;
-/*-Memory Regions-*/
-define symbol __ICFEDIT_region_ROM_start__   = 0x08000000;
-define symbol __ICFEDIT_region_ROM_end__     = 0x0807FFFF;
-define symbol __ICFEDIT_region_RAM_start__   = 0x20000000;
-define symbol __ICFEDIT_region_RAM_end__     = 0x2002FFFF;
-
-/*-Sizes-*/
-define symbol __ICFEDIT_size_cstack__ = 0x400;
-define symbol __ICFEDIT_size_heap__   = 0x200;
-/**** End of ICF editor section. ###ICF###*/
-
-define symbol __region_SRAM1_start__  = 0x20000000;
-define symbol __region_SRAM1_end__    = 0x2002FFFF;
-define symbol __region_SRAM2_start__  = 0x20030000;
-define symbol __region_SRAM2_end__    = 0x2003FFFF;
-
-define memory mem with size = 4G;
-define region ROM_region      = mem:[from __ICFEDIT_region_ROM_start__   to __ICFEDIT_region_ROM_end__];
-define region RAM_region      = mem:[from __ICFEDIT_region_RAM_start__   to __ICFEDIT_region_RAM_end__];
-define region SRAM1_region    = mem:[from __region_SRAM1_start__   to __region_SRAM1_end__];
-define region SRAM2_region    = mem:[from __region_SRAM2_start__   to __region_SRAM2_end__];
-
-define block CSTACK    with alignment = 8, size = __ICFEDIT_size_cstack__   { };
-define block HEAP      with alignment = 8, size = __ICFEDIT_size_heap__     { };
-
-initialize by copy { readwrite };
-do not initialize  { section .noinit };
-
-place at address mem:__ICFEDIT_intvec_start__ { readonly section .intvec };
-
-place in ROM_region   { readonly };
-place in RAM_region   { readwrite,
-                        block CSTACK, block HEAP };
-place in SRAM1_region { };
-place in SRAM2_region { };
-"""
-
-    TEST_ICF_2 = """
-/*test comments*/
-// some other comments
-/*-Specials-*/
-define symbol __ICFEDIT_intvec_start__ = 0x08000000;
-/*-Memory Regions-*/
-define symbol __ICFEDIT_region_ROM_start__    = 0x08000000;
-define symbol __ICFEDIT_region_ROM_end__      = 0x081FFFFF;
-define symbol __ICFEDIT_region_RAM_start__    = 0x20000000;
-define symbol __ICFEDIT_region_RAM_end__      = 0x2002FFFF;
-define symbol __ICFEDIT_region_CCMRAM_start__ = 0x10000000;
-define symbol __ICFEDIT_region_CCMRAM_end__   = 0x1000FFFF;
-/*-Sizes-*/
-define symbol __ICFEDIT_size_cstack__ = 0x400;
-define symbol __ICFEDIT_size_heap__   = 0x200;
-/**** End of ICF editor section. ###ICF###*/
-
-
-define memory mem with size = 4G;
-define region ROM_region      = mem:[from __ICFEDIT_region_ROM_start__   to __ICFEDIT_region_ROM_end__];
-define region RAM_region      = mem:[from __ICFEDIT_region_RAM_start__   to __ICFEDIT_region_RAM_end__];
-define region CCMRAM_region   = mem:[from __ICFEDIT_region_CCMRAM_start__   to __ICFEDIT_region_CCMRAM_end__];
-
-define block CSTACK    with alignment = 8, size = __ICFEDIT_size_cstack__   { };
-define block HEAP      with alignment = 8, size = __ICFEDIT_size_heap__     { };
-
-initialize by copy { readwrite };
-do not initialize  { section .noinit };
-
-place at address mem:__ICFEDIT_intvec_start__ { readonly section .intvec };
-
-place in ROM_region   { readonly };
-place in RAM_region   { readwrite,
-                        block CSTACK, block HEAP };
-"""
-
-    def test_parse_icf_2(self):
-        regions, blocks = icf_to_ld.parse_icf(self.TEST_ICF_2)
-
-        self.assertEqual(
-            {
-                'ROM': ('0x08000000', '0x081FFFFF'),
-                'RAM': ('0x20000000', '0x2002FFFF'),
-                'CCMRAM': ('0x10000000', '0x1000FFFF'),
-            }, regions)
-
-        self.assertEqual(
-            {
-                'CSTACK': {
-                    'alignment': '8',
-                    'size': '0x400'
-                },
-                'HEAP': {
-                    'alignment': '8',
-                    'size': '0x200'
-                },
-            }, blocks)
-
-
-class IcfRegionsToLdRegionsTest(unittest.TestCase):
-    """icf_regions_to_ld_regions tests."""
-    def test_icf_region(self):
-        ld_regions = icf_to_ld.icf_regions_to_ld_regions({
-            'ROM': ('0x08000000', '0x081FFFFF'),
-            'RAM': ('0x20000000', '0x2002FFFF'),
-            'CCMRAM': ('0x10000000', '0x1000FFFF'),
-        })
-
-        self.assertEqual(
-            {
-                'FLASH': ('0x08000000', '2048K'),
-                'RAM': ('0x20000000', '192K'),
-                'CCMRAM': ('0x10000000', '64K'),
-            }, ld_regions)
-
-    def test_icf_region_off_by_one(self):
-        ld_regions = icf_to_ld.icf_regions_to_ld_regions({
-            'ROM': ('0x08000000', '0x080FFFFF'),
-            'RAM': ('0x20000000', '0x20020000'),
-        })
-
-        self.assertEqual(
-            {
-                'FLASH': ('0x08000000', '1024K'),
-                'RAM': ('0x20000000', '128K'),
-            }, ld_regions)
-
-
-class CreateLdTest(unittest.TestCase):
-    """create_ld tests."""
-    def test_create_ld(self):
-        ld_str = icf_to_ld.create_ld(
-            {
-                'FLASH': ('0x08000000', '2048K'),
-                'RAM': ('0x20000000', '192K'),
-                'CCMRAM': ('0x10000000', '64K'),
-            }, {
-                'CSTACK': {
-                    'alignment': '8',
-                    'size': '0x400'
-                },
-                'HEAP': {
-                    'alignment': '8',
-                    'size': '0x200'
-                },
-            })
-
-        self.assertTrue(
-            'RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K' in ld_str)
-        self.assertTrue(
-            'FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K' in ld_str)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_stm32cube_build/py/tests/inject_init_test.py b/pw_stm32cube_build/py/tests/inject_init_test.py
deleted file mode 100644
index b08cf35..0000000
--- a/pw_stm32cube_build/py/tests/inject_init_test.py
+++ /dev/null
@@ -1,103 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Finds files for a given product."""
-
-import unittest
-
-from pw_stm32cube_build import inject_init
-
-
-class AddPreMainInitTest(unittest.TestCase):
-    """add_pre_main_init tests."""
-    def test_spaces(self):
-        startup = '\n'.join([
-            '/* Call the clock system intitialization function.*/',
-            '  bl  SystemInit   ',
-            '/* Call static constructors */',
-            '    bl __libc_init_array',
-            '/* Call the application\'s entry point.*/',
-            '  bl  main',
-            '  bx  lr    ',
-            '.size  Reset_Handler, .-Reset_Handler',
-        ])
-
-        new_startup = inject_init.add_pre_main_init(startup)
-
-        self.assertEqual(
-            new_startup, '\n'.join([
-                '/* Call the clock system intitialization function.*/',
-                '  bl  SystemInit   ',
-                '/* Call static constructors */',
-                '    bl __libc_init_array',
-                '/* Call the application\'s entry point.*/',
-                'bl pw_stm32cube_Init',
-                '  bl  main',
-                '  bx  lr    ',
-                '.size  Reset_Handler, .-Reset_Handler',
-            ]))
-
-    def test_tabs(self):
-        startup = '\n'.join([
-            'LoopFillZerobss:',
-            '	ldr	r3, = _ebss',
-            '	cmp	r2, r3',
-            '	bcc	FillZerobss',
-            ''
-            '/* Call static constructors */',
-            '  bl __libc_init_array',
-            '/* Call the application\'s entry point.*/',
-            '	bl	main',
-            '',
-            'LoopForever:',
-            '    b LoopForever',
-        ])
-
-        new_startup = inject_init.add_pre_main_init(startup)
-
-        self.assertEqual(
-            new_startup, '\n'.join([
-                'LoopFillZerobss:',
-                '	ldr	r3, = _ebss',
-                '	cmp	r2, r3',
-                '	bcc	FillZerobss',
-                ''
-                '/* Call static constructors */',
-                '  bl __libc_init_array',
-                '/* Call the application\'s entry point.*/',
-                'bl pw_stm32cube_Init',
-                '	bl	main',
-                '',
-                'LoopForever:',
-                '    b LoopForever',
-            ]))
-
-    def test_main_not_found(self):
-        startup = '\n'.join([
-            '/* Call the clock system intitialization function.*/',
-            '  bl  SystemInit   ',
-            '/* Call static constructors */',
-            '    bl __libc_init_array',
-            '/* Call the application\'s entry point.*/',
-            '  bl  application_entry',
-            '  bx  lr    ',
-            '.size  Reset_Handler, .-Reset_Handler',
-        ])
-
-        with self.assertRaises(ValueError):
-            inject_init.add_pre_main_init(startup)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_stream/BUILD b/pw_stream/BUILD
new file mode 100644
index 0000000..996c044
--- /dev/null
+++ b/pw_stream/BUILD
@@ -0,0 +1,85 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_stream",
+    srcs = [
+        "buffered_stream.cc",
+        "memory_stream.cc",
+    ],
+    hdrs = [
+        "public/pw_stream/memory_stream.h",
+        "public/pw_stream/null_stream.h",
+        "public/pw_stream/stream.h",
+    ],
+    includes = ["public"],
+    deps = [
+        "//pw_assert",
+        "//pw_bytes",
+        "//pw_result",
+        "//pw_span",
+        "//pw_status",
+    ],
+)
+
+pw_cc_library(
+    name = "pw_stream_socket",
+    srcs = ["socket_stream.cc"],
+    hdrs = ["public/pw_stream/socket_stream.h"],
+    deps = [
+        "//pw_stream",
+        "//pw_sys_io",
+    ],
+)
+
+pw_cc_library(
+    name = "pw_stream_sys_io",
+    hdrs = ["public/pw_stream/sys_io_stream.h"],
+    deps = [
+        "//pw_stream",
+        "//pw_sys_io",
+    ],
+)
+
+pw_cc_test(
+    name = "memory_stream_test",
+    srcs = [
+        "memory_stream_test.cc",
+    ],
+    deps = [
+        ":pw_stream",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "stream_test",
+    srcs = [
+        "stream_test.cc",
+    ],
+    deps = [
+        ":pw_stream",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_stream/BUILD.bazel b/pw_stream/BUILD.bazel
deleted file mode 100644
index 0d716a6..0000000
--- a/pw_stream/BUILD.bazel
+++ /dev/null
@@ -1,118 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_stream",
-    srcs = [
-        "memory_stream.cc",
-    ],
-    hdrs = [
-        "public/pw_stream/memory_stream.h",
-        "public/pw_stream/null_stream.h",
-        "public/pw_stream/seek.h",
-        "public/pw_stream/stream.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_assert",
-        "//pw_bytes",
-        "//pw_polyfill",
-        "//pw_polyfill:iterator",
-        "//pw_result",
-        "//pw_span",
-        "//pw_status",
-    ],
-)
-
-pw_cc_library(
-    name = "socket_stream",
-    srcs = ["socket_stream.cc"],
-    hdrs = ["public/pw_stream/socket_stream.h"],
-    deps = [
-        ":pw_stream",
-        "//pw_log",
-        "//pw_sys_io",
-    ],
-)
-
-pw_cc_library(
-    name = "sys_io_stream",
-    hdrs = ["public/pw_stream/sys_io_stream.h"],
-    deps = [
-        "//pw_stream",
-        "//pw_sys_io",
-    ],
-)
-
-pw_cc_library(
-    name = "std_file_stream",
-    srcs = ["std_file_stream.cc"],
-    hdrs = ["public/pw_stream/std_file_stream.h"],
-    deps = [":pw_stream"],
-)
-
-pw_cc_library(
-    name = "interval_reader",
-    srcs = ["interval_reader.cc"],
-    hdrs = ["public/pw_stream/interval_reader.h"],
-    deps = [":pw_stream"],
-)
-
-pw_cc_test(
-    name = "memory_stream_test",
-    srcs = ["memory_stream_test.cc"],
-    deps = [
-        ":pw_stream",
-        "//pw_assert",
-        "//pw_status",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "seek_test",
-    srcs = ["seek_test.cc"],
-    deps = [
-        ":pw_stream",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "stream_test",
-    srcs = ["stream_test.cc"],
-    deps = [
-        ":pw_stream",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "interval_reader_test",
-    srcs = ["interval_reader_test.cc"],
-    deps = [
-        ":interval_reader",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_stream/BUILD.gn b/pw_stream/BUILD.gn
index 1f6a9da..a1607a1 100644
--- a/pw_stream/BUILD.gn
+++ b/pw_stream/BUILD.gn
@@ -28,14 +28,12 @@
   public = [
     "public/pw_stream/memory_stream.h",
     "public/pw_stream/null_stream.h",
-    "public/pw_stream/seek.h",
     "public/pw_stream/stream.h",
   ]
   sources = [ "memory_stream.cc" ]
   public_deps = [
     dir_pw_assert,
     dir_pw_bytes,
-    dir_pw_polyfill,
     dir_pw_result,
     dir_pw_status,
   ]
@@ -43,8 +41,7 @@
 
 pw_source_set("socket_stream") {
   public_configs = [ ":public_include_path" ]
-  public_deps = [ ":pw_stream" ]
-  deps = [ dir_pw_log ]
+  public_deps = [ "$dir_pw_stream" ]
   sources = [ "socket_stream.cc" ]
   public = [ "public/pw_stream/socket_stream.h" ]
 }
@@ -58,33 +55,13 @@
   public = [ "public/pw_stream/sys_io_stream.h" ]
 }
 
-pw_source_set("std_file_stream") {
-  public_configs = [ ":public_include_path" ]
-  public_deps = [ ":pw_stream" ]
-  public = [ "public/pw_stream/std_file_stream.h" ]
-  sources = [ "std_file_stream.cc" ]
-}
-
-pw_source_set("interval_reader") {
-  public_configs = [ ":public_include_path" ]
-  public_deps = [
-    ":pw_stream",
-    dir_pw_assert,
-    dir_pw_status,
-  ]
-  public = [ "public/pw_stream/interval_reader.h" ]
-  sources = [ "interval_reader.cc" ]
-}
-
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
 
 pw_test_group("tests") {
   tests = [
-    ":interval_reader_test",
     ":memory_stream_test",
-    ":seek_test",
     ":stream_test",
   ]
 }
@@ -94,17 +71,7 @@
   deps = [ ":pw_stream" ]
 }
 
-pw_test("seek_test") {
-  sources = [ "seek_test.cc" ]
-  deps = [ ":pw_stream" ]
-}
-
 pw_test("stream_test") {
   sources = [ "stream_test.cc" ]
   deps = [ ":pw_stream" ]
 }
-
-pw_test("interval_reader_test") {
-  sources = [ "interval_reader_test.cc" ]
-  deps = [ ":interval_reader" ]
-}
diff --git a/pw_stream/CMakeLists.txt b/pw_stream/CMakeLists.txt
index a149210..55a9f96 100644
--- a/pw_stream/CMakeLists.txt
+++ b/pw_stream/CMakeLists.txt
@@ -15,110 +15,25 @@
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
 
 pw_add_module_library(pw_stream
-  HEADERS
-    public/pw_stream/memory_stream.h
-    public/pw_stream/null_stream.h
-    public/pw_stream/seek.h
-    public/pw_stream/stream.h
-  PUBLIC_INCLUDES
-    public
   SOURCES
     memory_stream.cc
   PUBLIC_DEPS
     pw_assert
     pw_bytes
-    pw_polyfill
-    pw_polyfill.span
     pw_result
+    pw_span
     pw_status
 )
-if(Zephyr_FOUND AND CONFIG_PIGWEED_STREAM)
-  zephyr_link_libraries(pw_stream)
-endif()
 
 pw_add_module_library(pw_stream.socket_stream
-  HEADERS
-    public/pw_stream/socket_stream.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_stream
   SOURCES
     socket_stream.cc
   PRIVATE_DEPS
-    pw_log
+    pw_stream
 )
 
 pw_add_module_library(pw_stream.sys_io_stream
-  HEADERS
-    public/pw_stream/sys_io_stream.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
+  PRIVATE_DEPS
     pw_stream
     pw_sys_io
 )
-
-pw_add_module_library(pw_stream.std_file_stream
-  HEADERS
-    public/pw_stream/std_file_stream.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_stream
-  SOURCES
-    std_file_stream.cc
-)
-
-pw_add_module_library(pw_stream.interval_reader
-  HEADERS
-    public/pw_stream/interval_reader.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_assert
-    pw_status
-    pw_stream
-  SOURCES
-    interval_reader.cc
-)
-
-pw_add_test(pw_stream.memory_stream_test
-  SOURCES
-    memory_stream_test.cc
-  DEPS
-    pw_stream
-  GROUPS
-    modules
-    pw_stream
-)
-
-pw_add_test(pw_stream.seek_test
-  SOURCES
-    seek_test.cc
-  DEPS
-    pw_stream
-  GROUPS
-    modules
-    pw_stream
-)
-
-pw_add_test(pw_stream.stream_test
-  SOURCES
-    stream_test.cc
-  DEPS
-    pw_stream
-  GROUPS
-    modules
-    pw_stream
-)
-
-pw_add_test(pw_stream.interval_reader_test
-  SOURCES
-    interval_reader_test.cc
-  DEPS
-    pw_stream.interval_reader
-  GROUPS
-    modules
-    pw_stream
-)
diff --git a/pw_stream/Kconfig b/pw_stream/Kconfig
deleted file mode 100644
index 8b8bb23..0000000
--- a/pw_stream/Kconfig
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-config PIGWEED_STREAM
-    bool "Enable Pigweed stream library (pw_stream)"
-    select PIGWEED_ASSERT
-    select PIGWEED_BYTES
-    select PIGWEED_RESULT
-    select PIGWEED_SPAN
-    select PIGWEED_STATUS
diff --git a/pw_stream/docs.rst b/pw_stream/docs.rst
index 951e9d4..2fcd644 100644
--- a/pw_stream/docs.rst
+++ b/pw_stream/docs.rst
@@ -1,10 +1,8 @@
 .. _module-pw_stream:
 
-.. cpp:namespace-push:: pw::stream
-
-=========
+---------
 pw_stream
-=========
+---------
 
 ``pw_stream`` provides a foundational interface for streaming data from one part
 of a system to another. In the simplest use cases, this is basically a memcpy
@@ -12,9 +10,8 @@
 hand, the flexibility of this interface means a ``pw_stream`` could terminate is
 something more complex, like a UART stream or flash memory.
 
---------
 Overview
---------
+========
 At the most basic level, ``pw_stream``'s interfaces provide very simple handles
 to enabling streaming data from one location in a system to an endpoint.
 
@@ -22,419 +19,74 @@
 
 .. code-block:: cpp
 
-  Status DumpSensorData(pw::stream::Writer& writer) {
+  void DumpSensorData(pw::stream::Writer& writer) {
     static char temp[64];
     ImuSample imu_sample;
     imu.GetSample(&info);
     size_t bytes_written = imu_sample.AsCsv(temp, sizeof(temp));
-    return writer.Write(temp, bytes_written);
+    writer.Write(temp, bytes_written);
   }
 
 In this example, ``DumpSensorData()`` only cares that it has access to a
-:cpp:class:`Writer` that it can use to stream data to using ``Writer::Write()``.
-The :cpp:class:`Writer` itself can be backed by anything that can act as a data
-"sink."
+``Writer`` that it can use to stream data to using ``Writer::Write()``. The
+``Writer`` itself can be backed by anything that can act as a data "sink."
 
----------------------
-pw::stream Interfaces
----------------------
-There are three basic capabilities of a stream:
 
- * Reading -- Bytes can be read from the stream.
- * Writing -- Bytes can be written to the stream.
- * Seeking -- The position in the stream can be changed.
-
-``pw_stream`` provides a family of stream classes with different capabilities.
-The most basic class, :cpp:class:`Stream` guarantees no functionality, while the
-most capable class, :cpp:class:`SeekableReaderWriter` supports reading, writing,
-and seeking.
-
-Usage overview
-==============
-
-+-------------------------------------------+-----------------+------------------------------+
-| pw::stream Interfaces                     | Accept in APIs? | Extend to create new stream? |
-+===========================================+=================+==============================+
-| :cpp:class:`Stream`                       | ❌              | ❌                           |
-+-------------------------------------------+-----------------+------------------------------+
-| :cpp:class:`Reader`                       | ✅              | ❌                           |
-|                                           |                 |                              |
-| :cpp:class:`Writer`                       |                 |                              |
-|                                           |                 |                              |
-| :cpp:class:`ReaderWriter`                 |                 |                              |
-+-------------------------------------------+-----------------+------------------------------+
-| :cpp:class:`SeekableReader`               | ✅              | ✅                           |
-|                                           |                 |                              |
-| :cpp:class:`SeekableWriter`               |                 |                              |
-|                                           |                 |                              |
-| :cpp:class:`SeekableReaderWriter`         |                 |                              |
-+-------------------------------------------+-----------------+------------------------------+
-| :cpp:class:`RelativeSeekableReader`       | ✅ (rarely)     | ✅                           |
-|                                           |                 |                              |
-| :cpp:class:`RelativeSeekableWriter`       |                 |                              |
-|                                           |                 |                              |
-| :cpp:class:`RelativeSeekableReaderWriter` |                 |                              |
-+-------------------------------------------+-----------------+------------------------------+
-| :cpp:class:`NonSeekableReader`            | ❌              | ✅                           |
-|                                           |                 |                              |
-| :cpp:class:`NonSeekableWriter`            |                 |                              |
-|                                           |                 |                              |
-| :cpp:class:`NonSeekableReaderWriter`      |                 |                              |
-+-------------------------------------------+-----------------+------------------------------+
-
-Interface documentation
-=======================
-Summary documentation for the ``pw_stream`` interfaces is below. See the API
-comments in `pw_stream/public/pw_stream/stream.h
-<https://cs.opensource.google/pigweed/pigweed/+/main:pw_stream/public/pw_stream/stream.h>`_
-for full details.
-
-.. cpp:class:: Stream
-
-  A generic stream that may support reading, writing, and seeking, but makes no
-  guarantees about whether any operations are supported. Stream serves as the
-  base for the Reader, Writer, and ReaderWriter interfaces.
-
-  Stream cannot be extended directly. Instead, work with one of the derived
-  classes that explicitly supports the required functionality. Stream should
-  almost never be used in APIs; accept a derived class with the required
-  capabilities instead.
-
-  All Stream methods are blocking. They return when the requested operation
-  completes.
-
-  **Public methods**
-
-  .. cpp:function:: bool readable() const
-
-    True if :cpp:func:`Read` is supported.
-
-  .. cpp:function:: bool writable() const
-
-    True if :cpp:func:`Write` is supported.
-
-  .. cpp:function:: bool seekable() const
-
-    True if :cpp:func:`Seek` is supported.
-
-  .. cpp:function:: Result<ByteSpan> Read(ByteSpan buffer)
-  .. cpp:function:: Result<ByteSpan> Read(void* buffer, size_t size_bytes)
-
-    Reads data from the stream into the provided buffer, if supported. As many
-    bytes as are available up to the buffer size are copied into the buffer.
-    Remaining bytes may by read in subsequent calls.
-
-    Returns:
-
-      * OK - Between 1 and dest.size_bytes() were successfully read. Returns
-        the span of read bytes.
-      * UNIMPLEMENTED - This stream does not support writing.
-      * FAILED_PRECONDITION - The Reader  is not in state to read data.
-      * RESOURCE_EXHAUSTED - Unable to read any bytes at this time. No bytes
-        read. Try again once bytes become available.
-      * OUT_OF_RANGE - Reader has been exhausted, similar to EOF. No bytes were
-        read, no more will be read.
-
-  .. cpp:function:: Status Write(ConstByteSpan data)
-
-    Writes the provided data to the stream, if supported.
-
-    Returns:
-
-      * OK - Data was successfully accepted by the stream.
-      * UNIMPLEMENTED - This stream does not support writing.
-      * FAILED_PRECONDITION - The writer is not in a state to accept data.
-      * RESOURCE_EXHAUSTED - The writer was unable to write all of requested data
-        at this time. No data was written.
-      * OUT_OF_RANGE - The Writer has been exhausted, similar to EOF. No data was
-        written; no more will be written.
-
-
-  .. cpp:function:: Status Seek(ptrdiff_t offset, Whence origin = kBeginning)
-
-    Changes the current read & write position in the stream, if supported.
-
-    Returns:
-
-      * OK - Successfully updated the position.
-      * UNIMPLEMENTED - Seeking is not supported for this stream.
-      * OUT_OF_RANGE - Attempted to seek beyond the bounds of the stream. The
-        position is unchanged.
-
-  .. cpp:function:: size_t Tell() const
-
-    Returns the current read & write position in the stream, if supported.
-    Returns ``Stream::kUnknownPosition`` (``size_t(-1)``) if unsupported.
-
-  .. cpp:function:: size_t ConservativeReadLimit() const
-
-    Likely minimum bytes available to read. Returns ``kUnlimited``
-    (``size_t(-1)``) if there is no limit or it is unknown.
-
-  .. cpp:function:: size_t ConservativeWriteLimit() const
-
-    Likely minimum bytes available to write. Returns ``kUnlimited``
-    (``size_t(-1)``) if there is no limit or it is unknown.
-
-  **Private virtual methods**
-
-  Stream's public methods are non-virtual. The public methods call private
-  virtual methods that are implemented by derived classes.
-
-  .. cpp:function:: private virtual StatusWithSize DoRead(ByteSpan destination)
-
-    Virtual :cpp:func:`Read` function implemented by derived classes.
-
-  .. cpp:function:: private virtual Status DoWrite(ConstByteSpan data)
-
-    Virtual :cpp:func:`Write` function implemented by derived classes.
-
-  .. cpp:function:: private virtual Status DoSeek(ptrdiff_t offset, Whence origin)
-
-    Virtual :cpp:func:`Seek` function implemented by derived classes.
-
-  .. cpp:function:: private virtual size_t DoTell() const
-
-    Virtual :cpp:func:`Tell` function optionally implemented by derived classes.
-    The default implementation always returns ``kUnknownPosition``.
-
-  .. cpp:function:: private virtual size_t ConservativeLimit(LimitType limit_type)
-
-    Virtual function optionally implemented by derived classes that is used for
-    :cpp:func:`ConservativeReadLimit` and :cpp:func:`ConservativeWriteLimit`.
-    The default implementation returns ``kUnlimited`` or ``0`` depending on
-    whether the stream is readable/writable.
-
-Reader interfaces
------------------
-.. cpp:class:: Reader : public Stream
-
-   A Stream that supports writing but not reading. The Write() method is hidden.
-
-   Use in APIs when:
-     * Must read from, but not write to, a stream.
-     * May or may not need seeking. Use a SeekableReader& if seeking is
-       required.
-
-   Inherit from when:
-     * Reader cannot be extended directly. Instead, extend SeekableReader,
-       NonSeekableReader, or (rarely) RelativeSeekableReader, as appropriate.
-
-   A Reader may or may not support seeking. Check seekable() or try calling
-   Seek() to determine if the stream is seekable.
-
-.. cpp:class:: SeekableReader : public RelativeSeekableReader
-
-   A Reader that fully supports seeking.
-
-   Use in APIs when:
-     * Absolute seeking is required. Use Reader& if seeking is not required or
-       seek failures can be handled gracefully.
-
-   Inherit from when:
-     * Implementing a reader that supports absolute seeking.
-
-.. cpp:class:: RelativeSeekableReader : public Reader
-
-   A Reader that at least partially supports seeking. Seeking within some range
-   of the current position works, but seeking beyond that or from other origins
-   may or may not be supported. The extent to which seeking is possible is NOT
-   exposed by this API.
-
-   Use in APIs when:
-     * Relative seeking is required. Usage in APIs should be rare; generally
-       Reader should be used instead.
-
-   Inherit from when:
-     * Implementing a Reader that can only support seeking near the current
-       position.
-
-   A buffered Reader that only supports seeking within its buffer is a good
-   example of a RelativeSeekableReader.
-
-.. cpp:class:: NonSeekableReader : public Reader
-
-   A Reader that does not support seeking. The Seek() method is hidden.
-
-   Use in APIs when:
-     * Do NOT use in APIs! If seeking is not required, use Reader& instead.
-
-   Inherit from when:
-     * Implementing a Reader that does not support seeking.
-
-Writer interfaces
------------------
-.. cpp:class:: Writer : public Stream
-
-   A Stream that supports writing but not reading. The Read() method is hidden.
-
-   Use in APIs when:
-     * Must write to, but not read from, a stream.
-     * May or may not need seeking. Use a SeekableWriter& if seeking is
-       required.
-
-   Inherit from when:
-     * Writer cannot be extended directly. Instead, extend SeekableWriter,
-       NonSeekableWriter, or (rarely) RelativeSeekableWriter, as appropriate.
-
-   A Writer may or may not support seeking. Check seekable() or try calling
-   Seek() to determine if the stream is seekable.
-
-.. cpp:class:: SeekableWriter : public RelativeSeekableWriter
-
-   A Writer that fully supports seeking.
-
-   Use in APIs when:
-     * Absolute seeking is required. Use Writer& if seeking is not required or
-       seek failures can be handled gracefully.
-
-   Inherit from when:
-     * Implementing a writer that supports absolute seeking.
-
-
-.. cpp:class:: RelativeSeekableWriter : public Writer
-
-   A Writer that at least partially supports seeking. Seeking within some range
-   of the current position works, but seeking beyond that or from other origins
-   may or may not be supported. The extent to which seeking is possible is NOT
-   exposed by this API.
-
-   Use in APIs when:
-     * Relative seeking is required. Usage in APIs should be rare; generally
-       Writer should be used instead.
-
-   Inherit from when:
-     * Implementing a Writer that can only support seeking near the current
-       position.
-
-   A buffered Writer that only supports seeking within its buffer is a good
-   example of a RelativeSeekableWriter.
-
-.. cpp:class:: NonSeekableWriter : public Writer
-
-   A Writer that does not support seeking. The Seek() method is hidden.
-
-   Use in APIs when:
-     * Do NOT use in APIs! If seeking is not required, use Writer& instead.
-
-   Inherit from when:
-     * Implementing a Writer that does not support seeking.
-
-ReaderWriter interfaces
------------------------
-.. cpp:class:: ReaderWriter : public Stream
-
-   A Stream that supports both reading and writing.
-
-   Use in APIs when:
-     * Must both read from and write to a stream.
-     * May or may not need seeking. Use a SeekableReaderWriter& if seeking is
-       required.
-
-   Inherit from when:
-     * Cannot extend ReaderWriter directly. Instead, extend
-       SeekableReaderWriter, NonSeekableReaderWriter, or (rarely)
-       RelativeSeekableReaderWriter, as appropriate.
-
-   A ReaderWriter may or may not support seeking. Check seekable() or try
-   calling Seek() to determine if the stream is seekable.
-
-.. cpp:class:: SeekableReaderWriter : public RelativeSeekableReaderWriter
-
-   A ReaderWriter that fully supports seeking.
-
-   Use in APIs when:
-     * Absolute seeking is required. Use ReaderWriter& if seeking is not
-       required or seek failures can be handled gracefully.
-
-   Inherit from when:
-     * Implementing a writer that supports absolute seeking.
-
-.. cpp:class:: RelativeSeekableReaderWriter : public ReaderWriter
-
-   A ReaderWriter that at least partially supports seeking. Seeking within some
-   range of the current position works, but seeking beyond that or from other
-   origins may or may not be supported. The extent to which seeking is possible
-   is NOT exposed by this API.
-
-   Use in APIs when:
-     * Relative seeking is required. Usage in APIs should be rare; generally
-       ReaderWriter should be used instead.
-
-   Inherit from when:
-     * Implementing a ReaderWriter that can only support seeking near the
-       current position.
-
-   A buffered ReaderWriter that only supports seeking within its buffer is a
-   good example of a RelativeSeekableReaderWriter.
-
-.. cpp:class:: NonSeekableReaderWriter : public ReaderWriter
-
-   A ReaderWriter that does not support seeking. The Seek() method is hidden.
-
-   Use in APIs when:
-     * Do NOT use in APIs! If seeking is not required, use ReaderWriter&
-       instead.
-
-   Inherit from when:
-     * Implementing a ReaderWriter that does not support seeking.
-
----------------
-Implementations
----------------
-``pw_stream`` includes a few stream implementations for general use.
-
-.. cpp:class:: MemoryWriter : public SeekableWriter
-
-  The ``MemoryWriter`` class implements the :cpp:class:`Writer` interface by
-  backing the data destination with an **externally-provided** memory buffer.
-  ``MemoryWriterBuffer`` extends ``MemoryWriter`` to internally provide a memory
-  buffer.
-
-  The ``MemoryWriter`` can be accessed like a standard C++ container. The
-  contents grow as data is written.
-
-.. cpp:class:: MemoryReader : public SeekableReader
-
-  The ``MemoryReader`` class implements the :cpp:class:`Reader` interface by
-  backing the data source with an **externally-provided** memory buffer.
-
-.. cpp:class:: NullReaderWriter : public SeekableReaderWriter
-
-  ``NullReaderWriter`` is a no-op stream implementation, similar to
-  ``/dev/null``. Writes are always dropped. Reads always return
-  ``OUT_OF_RANGE``. Seeks have no effect.
-
-.. cpp:class:: StdFileWriter : public SeekableWriter
-
-  ``StdFileWriter`` wraps an ``std::ofstream`` with the :cpp:class:`Writer`
-  interface.
-
-.. cpp:class:: StdFileReader : public SeekableReader
-
-  ``StdFileReader`` wraps an ``std::ifstream`` with the :cpp:class:`Reader`
-  interface.
-
+pw::stream::Writer
 ------------------
+This is the foundational stream ``Writer`` abstract class. Any class that wishes
+to implement the ``Writer`` interface **must** provide a ``DoWrite()``
+implementation. Note that ``Write()`` itself is **not** virtual, and should not
+be overridden.
+
+Buffering
+^^^^^^^^^
+If any buffering occurs in a ``Writer`` and data must be flushed before it is
+fully committed to the sink, a ``Writer`` implementation is resposible for any
+``Flush()`` capability.
+
+pw::stream::Reader
+------------------
+This is the foundational stream ``Reader`` abstract class. Any class that wishes
+to implement the ``Reader`` interface **must** provide a ``DoRead()``
+implementation. Note that ``Read()`` itself is **not** virtual, and should not
+be overridden.
+
+pw::stream::MemoryWriter
+------------------------
+The ``MemoryWriter`` class implements the ``Writer`` interface by backing the
+data destination with an **externally-provided** memory buffer.
+``MemoryWriterBuffer`` extends ``MemoryWriter`` to internally provide a memory
+buffer.
+
+pw::stream::MemoryReader
+------------------------
+The ``MemoryReader`` class implements the ``Reader`` interface by backing the
+data source with an **externally-provided** memory buffer.
+
+pw::stream::NullWriter
+------------------------
+The ``NullWriter`` class implements the ``Writer`` interface by dropping all
+requested data writes, similar to ``/dev/null``.
+
 Why use pw_stream?
-------------------
+==================
 
 Standard API
-============
+------------
 ``pw_stream`` provides a standard way for classes to express that they have the
 ability to write data. Writing to one sink versus another sink is a matter of
-just passing a reference to the appropriate :cpp:class:`Writer`.
+just passing a reference to the appropriate ``Writer``.
 
 As an example, imagine dumping sensor data. If written against a random HAL
 or one-off class, there's porting work required to write to a different sink
 (imagine writing over UART vs dumping to flash memory). Building a "dumping"
-implementation against the :cpp:class:`Writer` interface prevents a dependency
-on a bespoke API that would require porting work.
+implementation against the ``Writer`` interface prevents a dependency from
+forming on an artisainal API that would require porting work.
 
-Similarly, after building a :cpp:class:`Writer` implementation for a Sink that
-data could be dumped to, that same :cpp:class:`Writer` can be reused for other
-contexts that already write data to the :cpp:class:`pw::stream::Writer`
-interface.
+Similarly, after building a ``Writer`` implementation for a Sink that data
+could be dumped to, that same ``Writer`` can be reused for other contexts that
+already write data to the ``pw::stream::Writer`` interface.
 
 Before:
 
@@ -454,31 +106,30 @@
 .. code-block:: cpp
 
   // Reusable; no more Uart dependency!
-  Status DumpSensorData(Writer& writer) {
+  void DumpSensorData(Writer& writer) {
     static char temp[64];
     ImuSample imu_sample;
     imu.GetSample(&info);
     size_t bytes_written = imu_sample.AsCsv(temp, sizeof(temp));
-    return writer.Write(temp, bytes_written);
+    writer.Write(temp, bytes_written);
   }
 
 Reduce intermediate buffers
-===========================
+---------------------------
 Often functions that write larger blobs of data request a buffer is passed as
-the destination that data should be written to. This *requires* a buffer to be
+the destination that data should be written to. This *requires* a buffer is
 allocated, even if the data only exists in that buffer for a very short period
-of time before it's written somewhere else.
+of time before it's  written somewhere else.
 
 In situations where data read from somewhere will immediately be written
-somewhere else, a :cpp:class:`Writer` interface can cut out the middleman
-buffer.
+somewhere else, a ``Writer`` interface can cut out the middleman buffer.
 
 Before:
 
 .. code-block:: cpp
 
   // Requires an intermediate buffer to write the data as CSV.
-  void DumpSensorData(Uart& uart) {
+  void DumpSensorData(Uart* uart) {
     char temp[64];
     ImuSample imu_sample;
     imu.GetSample(&info);
@@ -492,23 +143,23 @@
 
   // Both DumpSensorData() and RawSample::AsCsv() use a Writer, eliminating the
   // need for an intermediate buffer.
-  Status DumpSensorData(Writer& writer) {
+  void DumpSensorData(Writer* writer) {
     RawSample imu_sample;
     imu.GetSample(&info);
-    return imu_sample.AsCsv(writer);
+    imu_sample.AsCsv(writer);
   }
 
 Prevent buffer overflow
-=======================
+-----------------------
 When copying data from one buffer to another, there must be checks to ensure the
 copy does not overflow the destination buffer. As this sort of logic is
 duplicated throughout a codebase, there's more opportunities for bound-checking
 bugs to sneak in. ``Writers`` manage this logic internally rather than pushing
 the bounds checking to the code that is moving or writing the data.
 
-Similarly, since only the :cpp:class:`Writer` has access to any underlying
-buffers, it's harder for functions that share a :cpp:class:`Writer` to
-accidentally clobber data written by others using the same buffer.
+Similarly, since only the ``Writer`` has access to any underlying buffers, it's
+harder for functions that share a ``Writer`` to accidentally clobber data
+written by others using the same buffer.
 
 Before:
 
@@ -518,7 +169,7 @@
                      span<std::byte> dest) {
     Header header;
     if (dest.size_bytes() + payload.size_bytes() < sizeof(Header)) {
-      return Status::ResourceExhausted();
+      return Status::RESOURCE_EXHAUSTED;
     }
     header.dest = dest;
     header.src = DeviceId();
@@ -546,105 +197,15 @@
     return writer.Write(payload);
   }
 
-------------
-Design notes
-------------
-
-Sync & Flush
-============
-The :cpp:class:`pw::stream::Stream` API does not include ``Sync()`` or
-``Flush()`` functions. There no mechanism in the :cpp:class:`Stream` API to
-synchronize a :cpp:class:`Reader`'s potentially buffered input with its
-underlying data source. This must be handled by the implementation if required.
-Similarly, the :cpp:class:`Writer` implementation is responsible for flushing
-any buffered data to the sink.
-
-``Flush()`` and ``Sync()`` were excluded from :cpp:class:`Stream` for a few
-reasons:
-
-  * The semantics of when to call ``Flush()``/``Sync()`` on the stream are
-    unclear. The presence of these methods complicates using a
-    :cpp:class:`Reader` or :cpp:class:`Writer`.
-  * Adding one or two additional virtual calls increases the size of all
-    :cpp:class:`Stream` vtables.
-
-Class hierarchy
-===============
-All ``pw_stream`` classes inherit from a single, common base with all possible
-functionality: :cpp:class:`pw::stream::Stream`. This structure has
-some similarities with Python's `io module
-<https://docs.python.org/3/library/io.html>`_ and C#'s `Stream class
-<https://docs.microsoft.com/en-us/dotnet/api/system.io.stream>`_.
-
-An alternative approach is to have the reading, writing, and seeking portions of
-the interface provided by different entities. This is how Go's `io
-<https://pkg.go.dev/io package>`_ and C++'s `input/output library
-<https://en.cppreference.com/w/cpp/io>`_ are structured.
-
-We chose to use a single base class for a few reasons:
-
-* The inheritance hierarchy is simple and linear. Despite the linear
-  hierarchy, combining capabilities is natural with classes like
-  :cpp:class:`ReaderWriter`.
-
-  In C++, separate interfaces for each capability requires either a complex
-  virtual inheritance hierarchy or entirely separate hierarchies for each
-  capability. Separate hierarchies can become cumbersome when trying to
-  combine multiple capabilities. A :cpp:class:`SeekableReaderWriter` would
-  have to implement three different interfaces, which means three different
-  vtables and three vtable pointers in each instance.
-* Stream capabilities are clearly expressed in the type system, while
-  naturally supporting optional functionality. A :cpp:class:`Reader` may
-  or may not support :cpp:func:`Stream::Seek`. Applications that can handle
-  seek failures gracefully way use seek on any :cpp:class:`Reader`. If seeking
-  is strictly necessary, an API can accept a :cpp:class:`SeekableReader`
-  instead.
-
-  Expressing optional functionality in the type system is cumbersome when
-  there are distinct interfaces for each capability. ``Reader``, ``Writer``,
-  and ``Seeker`` interfaces would not be sufficient. To match the flexibility
-  of the current structure, there would have to be separate optional versions
-  of each interface, and classes for various combinations. :cpp:class:`Stream`
-  would be an "OptionalReaderOptionalWriterOptionalSeeker" in this model.
-* Code reuse is maximized. For example, a single
-  :cpp:func:`Stream::ConservativeLimit` implementation supports many stream
-  implementations.
-
-Virtual interfaces
+Why NOT pw_stream?
 ==================
-``pw_stream`` uses virtual functions. Virtual functions enable runtime
-polymorphism. The same code can be used with any stream implementation.
+pw_stream provides a virtual interface. This inherently has more overhead than
+a regular function call. In extremely performance-sensitive contexts, a virtual
+interface might not provide enough utility to justify the performance cost.
 
-Virtual functions have inherently has more overhead than a regular function
-call. However, this is true of any polymorphic API. Using a C-style ``struct``
-of function pointers makes different trade-offs but still has more overhead than
-a regular function call.
-
-For many use cases, the overhead of virtual calls insignificant. However, in
-some extremely performance-sensitive contexts, the flexibility of the virtual
-interface may not justify the performance cost.
-
-Asynchronous APIs
-=================
-At present, ``pw_stream`` is synchronous. All :cpp:class:`Stream` API calls are
-expected to block until the operation is complete. This might be undesirable
-for slow operations, like writing to NOR flash.
-
-Pigweed has not yet established a pattern for asynchronous C++ APIs. The
-:cpp:class:`Stream` class may be extended in the future to add asynchronous
-capabilities, or a separate ``AsyncStream`` could be created.
-
-------------
 Dependencies
-------------
-  * :ref:`module-pw_assert`
-  * :ref:`module-pw_preprocessor`
-  * :ref:`module-pw_status`
-  * :ref:`module-pw_span`
-
-.. cpp:namespace-pop::
-
-Zephyr
-======
-To enable ``pw_stream`` for Zephyr add ``CONFIG_PIGWEED_STREAM=y`` to the
-project's configuration.
+============
+  * ``pw_assert`` module
+  * ``pw_preprocessor`` module
+  * ``pw_status`` module
+  * ``pw_span`` module
diff --git a/pw_stream/interval_reader.cc b/pw_stream/interval_reader.cc
deleted file mode 100644
index cbe4d7a..0000000
--- a/pw_stream/interval_reader.cc
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_stream/interval_reader.h"
-
-#include "pw_assert/check.h"
-
-namespace pw::stream {
-
-StatusWithSize IntervalReader::DoRead(ByteSpan destination) {
-  if (!source_reader_) {
-    return StatusWithSize(Status::FailedPrecondition(), 0);
-  }
-
-  if (!status_.ok()) {
-    return StatusWithSize(status_, 0);
-  }
-
-  if (current_ == end_) {
-    return StatusWithSize::OutOfRange();
-  }
-
-  // Seek the source reader to the `current_` offset of this IntervalReader
-  // before reading.
-  Status status = source_reader_->Seek(current_, Whence::kBeginning);
-  if (!status.ok()) {
-    return StatusWithSize(status, 0);
-  }
-
-  size_t to_read = std::min(destination.size(), end_ - current_);
-  Result<ByteSpan> res = source_reader_->Read(destination.first(to_read));
-  if (!res.ok()) {
-    return StatusWithSize(res.status(), 0);
-  }
-
-  current_ += res.value().size();
-  return StatusWithSize(res.value().size());
-}
-
-Status IntervalReader::DoSeek(ptrdiff_t offset, Whence origin) {
-  ptrdiff_t absolute_position = std::numeric_limits<ptrdiff_t>::min();
-
-  // Convert from the position within the interval to the position within the
-  // source reader stream.
-  switch (origin) {
-    case Whence::kBeginning:
-      absolute_position = offset + start_;
-      break;
-
-    case Whence::kCurrent:
-      absolute_position = current_ + offset;
-      break;
-
-    case Whence::kEnd:
-      absolute_position = end_ + offset;
-      break;
-  }
-
-  if (absolute_position < 0) {
-    return Status::InvalidArgument();
-  }
-
-  if (static_cast<size_t>(absolute_position) < start_ ||
-      static_cast<size_t>(absolute_position) > end_) {
-    return Status::InvalidArgument();
-  }
-
-  current_ = absolute_position;
-  return OkStatus();
-}
-
-};  // namespace pw::stream
diff --git a/pw_stream/interval_reader_test.cc b/pw_stream/interval_reader_test.cc
deleted file mode 100644
index 663e45b..0000000
--- a/pw_stream/interval_reader_test.cc
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_stream/interval_reader.h"
-
-#include "gtest/gtest.h"
-#include "pw_result/result.h"
-#include "pw_stream/memory_stream.h"
-
-namespace pw::stream {
-namespace {
-
-TEST(IntervalReader, IntervalReaderRead) {
-  std::uint8_t data[] = {0, 1, 2, 3, 4, 5, 6, 7, 9, 10};
-  stream::MemoryReader reader(std::as_bytes(std::span(data)));
-  IntervalReader reader_first_half(reader, 0, 5);
-  IntervalReader reader_second_half(reader, 5, 10);
-
-  // Read second half
-  std::byte read_buf[5];
-  Result<ByteSpan> res = reader_second_half.Read(read_buf);
-  ASSERT_EQ(res.status(), OkStatus());
-  ASSERT_EQ(res.value().size(), sizeof(read_buf));
-  ASSERT_EQ(memcmp(read_buf, data + 5, 5), 0);
-  ASSERT_EQ(reader_second_half.Read(read_buf).status(), Status::OutOfRange());
-
-  // Read first half. They should be independent and do not interfere each
-  // other.
-  res = reader_first_half.Read(read_buf);
-  ASSERT_EQ(res.status(), OkStatus());
-  ASSERT_EQ(res.value().size(), sizeof(read_buf));
-  ASSERT_EQ(memcmp(read_buf, data, 5), 0);
-  ASSERT_EQ(reader_first_half.Read(read_buf).status(), Status::OutOfRange());
-
-  // Reset the cursor and the reader should read from the beginning.
-  res = reader_second_half.Reset().Read(read_buf);
-  ASSERT_EQ(res.status(), OkStatus());
-  ASSERT_EQ(res.value().size(), sizeof(read_buf));
-  ASSERT_EQ(memcmp(read_buf, data + 5, 5), 0);
-  ASSERT_EQ(reader_second_half.Read(read_buf).status(), Status::OutOfRange());
-}
-
-TEST(IntervalReader, IntervalReaderSeek) {
-  std::uint8_t data[] = {0, 1, 2, 3, 4, 5, 6, 7, 9, 10};
-  stream::MemoryReader reader(std::as_bytes(std::span(data)));
-  IntervalReader interval_reader(reader, 0, 10);
-
-  // Absolute seeking.
-  std::byte read_buf[5];
-  ASSERT_EQ(interval_reader.Seek(5), OkStatus());
-  Result<ByteSpan> res = interval_reader.Read(read_buf);
-  ASSERT_EQ(res.status(), OkStatus());
-  ASSERT_EQ(res.value().size(), sizeof(read_buf));
-  ASSERT_EQ(memcmp(read_buf, data + 5, 5), 0);
-  ASSERT_EQ(interval_reader.Read(read_buf).status(), Status::OutOfRange());
-
-  // Relative seek.
-  ASSERT_EQ(interval_reader.Seek(-10, stream::Stream::kCurrent), OkStatus());
-  res = interval_reader.Read(read_buf);
-  ASSERT_EQ(res.status(), OkStatus());
-  ASSERT_EQ(res.value().size(), sizeof(read_buf));
-  ASSERT_EQ(memcmp(read_buf, data, 5), 0);
-
-  // Seeking from the end.
-  ASSERT_EQ(interval_reader.Seek(-5, stream::Stream::kEnd), OkStatus());
-  res = interval_reader.Read(read_buf);
-  ASSERT_EQ(res.status(), OkStatus());
-  ASSERT_EQ(res.value().size(), sizeof(read_buf));
-  ASSERT_EQ(memcmp(read_buf, data + 5, 5), 0);
-  ASSERT_EQ(interval_reader.Read(read_buf).status(), Status::OutOfRange());
-
-  // Seeking to the end is allowed
-  ASSERT_EQ(interval_reader.Seek(0, stream::Stream::kEnd), OkStatus());
-  ASSERT_EQ(interval_reader.Read(read_buf).status(), Status::OutOfRange());
-}
-
-}  // namespace
-}  // namespace pw::stream
diff --git a/pw_stream/memory_stream.cc b/pw_stream/memory_stream.cc
index 1a0de51..0a44810 100644
--- a/pw_stream/memory_stream.cc
+++ b/pw_stream/memory_stream.cc
@@ -30,34 +30,22 @@
   }
 
   size_t bytes_to_write = data.size_bytes();
-  if (bytes_to_write == 0) {
-    // Calling memmove with a null pointer is undefined behavior, even when zero
-    // bytes are moved. We must return early here to avoid performing such a
-    // call when data is an empty span.
-    return OkStatus();
-  }
-  std::memmove(dest_.data() + position_, data.data(), bytes_to_write);
-  position_ += bytes_to_write;
+  std::memcpy(dest_.data() + bytes_written_, data.data(), bytes_to_write);
+  bytes_written_ += bytes_to_write;
 
   return OkStatus();
 }
 
 StatusWithSize MemoryReader::DoRead(ByteSpan dest) {
-  if (source_.size_bytes() == position_) {
+  if (source_.size_bytes() == bytes_read_) {
     return StatusWithSize::OutOfRange();
   }
 
   size_t bytes_to_read =
-      std::min(dest.size_bytes(), source_.size_bytes() - position_);
-  if (bytes_to_read == 0) {
-    // Calling memcpy with a null pointer is undefined behavior, even when zero
-    // bytes are copied. We must return early here to avoid performing such a
-    // call when the dest span is empty.
-    return StatusWithSize(0);
-  }
+      std::min(dest.size_bytes(), source_.size_bytes() - bytes_read_);
 
-  std::memcpy(dest.data(), source_.data() + position_, bytes_to_read);
-  position_ += bytes_to_read;
+  std::memcpy(dest.data(), source_.data() + bytes_read_, bytes_to_read);
+  bytes_read_ += bytes_to_read;
 
   return StatusWithSize(bytes_to_read);
 }
diff --git a/pw_stream/memory_stream_test.cc b/pw_stream/memory_stream_test.cc
index 04fa8fb..b4a1395 100644
--- a/pw_stream/memory_stream_test.cc
+++ b/pw_stream/memory_stream_test.cc
@@ -31,33 +31,19 @@
 
 constexpr TestStruct kExpectedStruct = {.day = 18, .month = 5, .year = 2020};
 
-class MemoryWriterTest : public ::testing::Test {
- protected:
-  MemoryWriterTest() : memory_buffer_ {}
-  {}
-  std::array<std::byte, kSinkBufferSize> memory_buffer_;
-};
+std::array<std::byte, kSinkBufferSize> memory_buffer;
 
-TEST_F(MemoryWriterTest, BytesWritten) {
-  MemoryWriter memory_writer(memory_buffer_);
+TEST(MemoryWriter, BytesWritten) {
+  MemoryWriter memory_writer(memory_buffer);
   EXPECT_EQ(memory_writer.bytes_written(), 0u);
   Status status =
       memory_writer.Write(&kExpectedStruct, sizeof(kExpectedStruct));
   EXPECT_EQ(status, OkStatus());
   EXPECT_EQ(memory_writer.bytes_written(), sizeof(kExpectedStruct));
-}
+}  // namespace
 
-TEST_F(MemoryWriterTest, BytesWrittenOnConstruction) {
-  constexpr size_t bytes_written = kSinkBufferSize / 2;
-  std::memset(memory_buffer_.data(), 1u, bytes_written);
-  MemoryWriter memory_writer(memory_buffer_, bytes_written);
-  EXPECT_EQ(memory_writer.bytes_written(), bytes_written);
-  EXPECT_EQ(memcmp(memory_writer.data(), memory_buffer_.data(), bytes_written),
-            0);
-}
-
-TEST_F(MemoryWriterTest, ValidateContents) {
-  MemoryWriter memory_writer(memory_buffer_);
+TEST(MemoryWriter, ValidateContents) {
+  MemoryWriter memory_writer(memory_buffer);
   EXPECT_TRUE(
       memory_writer.Write(&kExpectedStruct, sizeof(kExpectedStruct)).ok());
 
@@ -68,14 +54,14 @@
   EXPECT_EQ(memcmp(&temp, &kExpectedStruct, sizeof(kExpectedStruct)), 0);
 }
 
-TEST_F(MemoryWriterTest, MultipleWrites) {
+TEST(MemoryWriter, MultipleWrites) {
   constexpr size_t kTempBufferSize = 72;
   std::byte buffer[kTempBufferSize] = {};
 
-  for (std::byte& value : memory_buffer_) {
+  for (std::byte& value : memory_buffer) {
     value = std::byte(0);
   }
-  MemoryWriter memory_writer(memory_buffer_);
+  MemoryWriter memory_writer(memory_buffer);
 
   size_t counter = 0;
   while (memory_writer.ConservativeWriteLimit() >= kTempBufferSize) {
@@ -93,21 +79,21 @@
   EXPECT_EQ(memory_writer.bytes_written(), counter);
 
   counter = 0;
-  for (const std::byte& value : memory_writer) {
+  for (const std::byte& value : memory_writer.WrittenData()) {
     EXPECT_EQ(value, std::byte(counter++));
   }
 }
 
-TEST_F(MemoryWriterTest, FullWriter) {
+TEST(MemoryWriter, FullWriter) {
   constexpr size_t kTempBufferSize = 32;
   std::byte buffer[kTempBufferSize] = {};
   const int fill_byte = 0x25;
   memset(buffer, fill_byte, sizeof(buffer));
 
-  for (std::byte& value : memory_buffer_) {
+  for (std::byte& value : memory_buffer) {
     value = std::byte(0);
   }
-  MemoryWriter memory_writer(memory_buffer_);
+  MemoryWriter memory_writer(memory_buffer);
 
   while (memory_writer.ConservativeWriteLimit() > 0) {
     size_t bytes_to_write =
@@ -119,23 +105,23 @@
   EXPECT_EQ(memory_writer.ConservativeWriteLimit(), 0u);
 
   EXPECT_EQ(memory_writer.Write(std::span(buffer)), Status::OutOfRange());
-  EXPECT_EQ(memory_writer.bytes_written(), memory_buffer_.size());
+  EXPECT_EQ(memory_writer.bytes_written(), memory_buffer.size());
 
-  for (const std::byte& value : memory_writer) {
+  for (const std::byte& value : memory_writer.WrittenData()) {
     EXPECT_EQ(value, std::byte(fill_byte));
   }
 }
 
-TEST_F(MemoryWriterTest, EmptyData) {
+TEST(MemoryWriter, EmptyData) {
   std::byte buffer[5] = {};
 
-  MemoryWriter memory_writer(memory_buffer_);
+  MemoryWriter memory_writer(memory_buffer);
   EXPECT_EQ(memory_writer.Write(buffer, 0), OkStatus());
   EXPECT_EQ(memory_writer.bytes_written(), 0u);
 }
 
-TEST_F(MemoryWriterTest, ValidateContents_SingleByteWrites) {
-  MemoryWriter memory_writer(memory_buffer_);
+TEST(MemoryWriter, ValidateContents_SingleByteWrites) {
+  MemoryWriter memory_writer(memory_buffer);
   EXPECT_TRUE(memory_writer.Write(std::byte{0x01}).ok());
   EXPECT_EQ(memory_writer.bytes_written(), 1u);
   EXPECT_EQ(memory_writer.data()[0], std::byte{0x01});
@@ -145,66 +131,17 @@
   EXPECT_EQ(memory_writer.data()[1], std::byte{0x7E});
 }
 
-TEST_F(MemoryWriterTest, OverlappingBuffer) {
-  constexpr std::string_view kTestString("This is staged into the same buffer");
-  // Write at a five-byte offset from the start of the destination buffer.
-  std::byte* const kOverlappingStart = memory_buffer_.data() + 5;
-  std::memcpy(kOverlappingStart, kTestString.data(), kTestString.size());
-  MemoryWriter memory_writer(memory_buffer_);
-  EXPECT_TRUE(memory_writer.Write(kOverlappingStart, kTestString.size()).ok());
-  EXPECT_TRUE(memory_writer.Write(std::byte(0)).ok());
-  EXPECT_EQ(memory_writer.bytes_written(), kTestString.size() + 1);
-
-  EXPECT_STREQ(reinterpret_cast<const char*>(memory_writer.data()),
-               kTestString.data());
-}
-
-TEST_F(MemoryWriterTest, Clear) {
-  MemoryWriter writer(memory_buffer_);
-  EXPECT_EQ(OkStatus(), writer.Write(std::byte{1}));
-  ASSERT_FALSE(writer.empty());
-  writer.clear();
-  EXPECT_TRUE(writer.empty());
-
-  EXPECT_EQ(OkStatus(), writer.Write(std::byte{99}));
-  EXPECT_EQ(writer[0], std::byte{99});
-}
-
-TEST_F(MemoryWriterTest, Seek_To0) {
-  MemoryWriter writer(memory_buffer_);
-  EXPECT_EQ(OkStatus(), writer.Seek(0));
-}
-
-TEST_F(MemoryWriterTest, Tell_StartsAt0) {
-  MemoryWriter writer(memory_buffer_);
-  EXPECT_EQ(0u, writer.Tell());
-}
-
-TEST_F(MemoryWriterTest, Tell_UpdatesOnSeek) {
-  MemoryWriter writer(memory_buffer_);
-  ASSERT_EQ(OkStatus(), writer.Seek(2, Stream::kCurrent));
-  EXPECT_EQ(2u, writer.Tell());
-}
-
-TEST_F(MemoryWriterTest, Tell_UpdatesOnRead) {
-  MemoryWriter writer(memory_buffer_);
-  std::byte buffer[4] = {};
-  ASSERT_EQ(OkStatus(), writer.Write(buffer));
-  EXPECT_EQ(4u, writer.Tell());
-}
-
 #define TESTING_CHECK_FAILURES_IS_SUPPORTED 0
 #if TESTING_CHECK_FAILURES_IS_SUPPORTED
 
 // TODO(amontanez): Ensure that this test triggers an assert.
-TEST_F(MemoryWriterTest, NullPointer) {
-  MemoryWriter memory_writer(memory_buffer_);
+TEST(MemoryWriter, NullPointer) {
+  MemoryWriter memory_writer(memory_buffer);
   memory_writer.Write(nullptr, 21);
 }
 
 // TODO(davidrogers): Ensure that this test triggers an assert.
 TEST(MemoryReader, NullSpan) {
-  std::byte memory_buffer[32];
   ByteSpan dest(nullptr, 5);
   MemoryReader memory_reader(memory_buffer);
   memory_reader.Read(dest);
@@ -212,7 +149,6 @@
 
 // TODO(davidrogers): Ensure that this test triggers an assert.
 TEST(MemoryReader, NullPointer) {
-  std::byte memory_buffer[32];
   MemoryReader memory_reader(memory_buffer);
   memory_reader.Read(nullptr, 21);
 }
@@ -332,40 +268,5 @@
   }
 }
 
-TEST(MemoryReader, Seek) {
-  constexpr std::string_view data = "0123456789";
-  MemoryReader reader(std::as_bytes(std::span(data)));
-
-  char buffer[5] = {};  // Leave a null terminator at the end.
-  ASSERT_EQ(OkStatus(), reader.Read(buffer, sizeof(buffer) - 1).status());
-  EXPECT_STREQ(buffer, "0123");
-
-  ASSERT_EQ(OkStatus(), reader.Seek(1));
-  ASSERT_EQ(OkStatus(), reader.Read(buffer, sizeof(buffer) - 1).status());
-  EXPECT_STREQ(buffer, "1234");
-
-  ASSERT_EQ(OkStatus(), reader.Seek(0));
-  ASSERT_EQ(OkStatus(), reader.Read(buffer, sizeof(buffer) - 1).status());
-  EXPECT_STREQ(buffer, "0123");
-}
-
-TEST(MemoryReader, Tell_StartsAt0) {
-  MemoryReader reader(std::as_bytes(std::span("\3\2\1")));
-  EXPECT_EQ(0u, reader.Tell());
-}
-
-TEST(MemoryReader, Tell_UpdatesOnSeek) {
-  MemoryReader reader(std::as_bytes(std::span("\3\2\1")));
-  ASSERT_EQ(OkStatus(), reader.Seek(2, Stream::kCurrent));
-  EXPECT_EQ(2u, reader.Tell());
-}
-
-TEST(MemoryReader, Tell_UpdatesOnRead) {
-  MemoryReader reader(std::as_bytes(std::span("\3\2\1")));
-  std::byte buffer[4];
-  ASSERT_EQ(OkStatus(), reader.Read(buffer).status());
-  EXPECT_EQ(4u, reader.Tell());
-}
-
 }  // namespace
 }  // namespace pw::stream
diff --git a/pw_stream/public/pw_stream/interval_reader.h b/pw_stream/public/pw_stream/interval_reader.h
deleted file mode 100644
index 775beac..0000000
--- a/pw_stream/public/pw_stream/interval_reader.h
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-//
-// The header provides a set of helper utils for protobuf related operations.
-// The APIs may not be finalized yet.
-
-#pragma once
-
-#include <cstddef>
-#include <string_view>
-
-#include "pw_assert/assert.h"
-#include "pw_status/status.h"
-#include "pw_stream/stream.h"
-
-namespace pw::stream {
-
-// A reader wrapper that reads from a sub-interval of a given seekable
-// source reader. The IntervalReader tracks and maintains its own read offset.
-// It seeks the source reader to its current read offset before reading. In
-// this way, multiple IntervalReader can share the same source reader without
-// interfereing each other.
-//
-// The reader additionally embedds a `Status` to indicate whether itself
-// is valid. This is a workaround for Reader not being compatibile with
-// Result<>. TODO(pwbug/363): Migrate this to Result<> once we have StatusOr
-// like support.
-class IntervalReader : public SeekableReader {
- public:
-  constexpr IntervalReader() : status_(Status::Unavailable()) {}
-
-  // Create an IntervalReader with an error status.
-  constexpr IntervalReader(Status status) : status_(status) {
-    PW_ASSERT(!status.ok());
-  }
-
-  // source_reader -- The source reader to read from.
-  // start -- starting offset to read in `source_reader`
-  // end -- ending offset in `source_reader`.
-  constexpr IntervalReader(SeekableReader& source_reader,
-                           size_t start,
-                           size_t end)
-      : source_reader_(&source_reader),
-        start_(start),
-        end_(end),
-        current_(start) {}
-
-  // Reset the read offset to the start of the interval
-  IntervalReader& Reset() {
-    current_ = start_;
-    return *this;
-  }
-
-  // Move the read offset to the end of the intetrval;
-  IntervalReader& Exhaust() {
-    current_ = end_;
-    return *this;
-  }
-
-  // Get a reference to the source reader.
-  SeekableReader& source_reader() { return *source_reader_; }
-  size_t start() const { return start_; }
-  size_t end() const { return end_; }
-  size_t current() const { return current_; }
-  size_t interval_size() const { return end_ - start_; }
-  bool ok() const { return status_.ok(); }
-  Status status() const { return status_; }
-
-  // For iterator comparison in Message.
-  bool operator==(const IntervalReader& other) const {
-    return source_reader_ == other.source_reader_ && start_ == other.start_ &&
-           end_ == other.end_ && current_ == other.current_;
-  }
-
- private:
-  StatusWithSize DoRead(ByteSpan destination) final;
-  Status DoSeek(ptrdiff_t offset, Whence origin) final;
-  size_t DoTell() const final { return current_ - start_; }
-  size_t ConservativeLimit(LimitType limit) const override {
-    if (limit == LimitType::kRead) {
-      return end_ - current_;
-    }
-    return 0;
-  }
-
-  SeekableReader* source_reader_ = nullptr;
-  size_t start_ = 0;
-  size_t end_ = 0;
-  size_t current_ = 0;
-  Status status_ = OkStatus();
-};
-
-}  // namespace pw::stream
diff --git a/pw_stream/public/pw_stream/memory_stream.h b/pw_stream/public/pw_stream/memory_stream.h
index 2b9e60c..948b1d3 100644
--- a/pw_stream/public/pw_stream/memory_stream.h
+++ b/pw_stream/public/pw_stream/memory_stream.h
@@ -19,102 +19,57 @@
 
 #include "pw_bytes/span.h"
 #include "pw_result/result.h"
-#include "pw_stream/seek.h"
 #include "pw_stream/stream.h"
 
 namespace pw::stream {
 
-class MemoryWriter : public SeekableWriter {
+class MemoryWriter : public Writer {
  public:
-  using difference_type = ptrdiff_t;
-  using reference = const std::byte&;
-  using const_reference = const std::byte&;
-  using pointer = const std::byte*;
-  using const_pointer = const std::byte*;
-  using iterator = const std::byte*;
-  using const_iterator = const std::byte*;
+  MemoryWriter(ByteSpan dest) : dest_(dest) {}
 
-  constexpr MemoryWriter(ByteSpan dest) : dest_(dest) {}
+  size_t bytes_written() const { return bytes_written_; }
 
-  // Construct writer with prepopulated data in the buffer. The number of
-  // pre-written bytes is provided as `bytes_written`.
-  //
-  // Precondition: The number of pre-written bytes must not be greater than the
-  // size of the provided buffer.
-  constexpr MemoryWriter(ByteSpan dest, size_t bytes_written)
-      : dest_(dest), position_(bytes_written) {
-    PW_ASSERT(position_ <= dest.size_bytes());
+  size_t ConservativeWriteLimit() const override {
+    return dest_.size_bytes() - bytes_written_;
   }
 
-  ConstByteSpan WrittenData() const { return dest_.first(position_); }
+  ConstByteSpan WrittenData() const { return dest_.first(bytes_written_); }
 
-  void clear() { position_ = 0; }
-
-  std::byte* data() { return dest_.data(); }
   const std::byte* data() const { return dest_.data(); }
 
-  const std::byte& operator[](size_t index) const { return dest_[index]; }
-
-  [[nodiscard]] bool empty() const { return size() == 0u; }
-
-  size_t size() const { return position_; }
-  size_t bytes_written() const { return size(); }
-
-  size_t capacity() const { return dest_.size(); }
-
-  const std::byte* begin() const { return dest_.begin(); }
-  const std::byte* end() const { return dest_.begin() + position_; }
-
  private:
-  size_t ConservativeLimit(LimitType type) const override {
-    return type == LimitType::kWrite ? dest_.size_bytes() - position_ : 0;
-  }
-
   // Implementation for writing data to this stream.
   //
   // If the in-memory buffer is exhausted in the middle of a write, this will
   // perform a partial write and Status::ResourceExhausted() will be returned.
-  Status DoWrite(ConstByteSpan data) final;
-
-  Status DoSeek(ptrdiff_t offset, Whence origin) final {
-    return CalculateSeek(offset, origin, dest_.size(), position_);
-  }
-
-  size_t DoTell() const final { return position_; }
+  Status DoWrite(ConstByteSpan data) override;
 
   ByteSpan dest_;
-  size_t position_ = 0;
+  size_t bytes_written_ = 0;
 };
 
 template <size_t kSizeBytes>
 class MemoryWriterBuffer final : public MemoryWriter {
  public:
-  constexpr MemoryWriterBuffer() : MemoryWriter(buffer_) {}
+  MemoryWriterBuffer() : MemoryWriter(buffer_) {}
 
  private:
   std::array<std::byte, kSizeBytes> buffer_;
 };
 
-class MemoryReader final : public SeekableReader {
+class MemoryReader final : public Reader {
  public:
-  constexpr MemoryReader(ConstByteSpan source)
-      : source_(source), position_(0) {}
+  MemoryReader(ConstByteSpan source) : source_(source), bytes_read_(0) {}
 
-  size_t bytes_read() const { return position_; }
+  size_t ConservativeReadLimit() const override {
+    return source_.size_bytes() - bytes_read_;
+  }
+
+  size_t bytes_read() const { return bytes_read_; }
 
   const std::byte* data() const { return source_.data(); }
 
  private:
-  size_t ConservativeLimit(LimitType type) const override {
-    return type == LimitType::kRead ? source_.size_bytes() - position_ : 0;
-  }
-
-  Status DoSeek(ptrdiff_t offset, Whence origin) override {
-    return CalculateSeek(offset, origin, source_.size(), position_);
-  }
-
-  size_t DoTell() const override { return position_; }
-
   // Implementation for reading data from this stream.
   //
   // If the in-memory buffer does not have enough remaining bytes for what was
@@ -122,7 +77,7 @@
   StatusWithSize DoRead(ByteSpan dest) override;
 
   ConstByteSpan source_;
-  size_t position_;
+  size_t bytes_read_;
 };
 
 }  // namespace pw::stream
diff --git a/pw_stream/public/pw_stream/null_stream.h b/pw_stream/public/pw_stream/null_stream.h
index a6272f3..b3db9c5 100644
--- a/pw_stream/public/pw_stream/null_stream.h
+++ b/pw_stream/public/pw_stream/null_stream.h
@@ -17,28 +17,23 @@
 #include <span>
 
 #include "pw_bytes/span.h"
-#include "pw_polyfill/language_feature_macros.h"
 #include "pw_status/status.h"
 #include "pw_status/status_with_size.h"
 #include "pw_stream/stream.h"
 
 namespace pw::stream {
 
-// Stream that silently drops written data and returns nothing on reads, similar
-// to /dev/null.
-class NullStream final : public SeekableReaderWriter {
- public:
-  // Gives access to a global NullStream instance. It is not necessary to have
-  // multiple NullStream instances since they have no state and do nothing.
-  static NullStream& Instance() {
-    PW_CONSTINIT static NullStream stream;
-    return stream;
-  }
-
+// Stream writer which quietly drops all of the data, similar to /dev/null.
+class NullWriter final : public Writer {
  private:
   Status DoWrite(ConstByteSpan) final { return OkStatus(); }
+};
+
+// Stream reader which never reads any bytes. Always returns OUT_OF_RANGE, which
+// indicates there is no more data to read.
+class NullReader final : public Reader {
+ private:
   StatusWithSize DoRead(ByteSpan) final { return StatusWithSize::OutOfRange(); }
-  Status DoSeek(ptrdiff_t, Whence) final { return OkStatus(); }
 };
 
 }  // namespace pw::stream
diff --git a/pw_stream/public/pw_stream/seek.h b/pw_stream/public/pw_stream/seek.h
deleted file mode 100644
index 597dea5..0000000
--- a/pw_stream/public/pw_stream/seek.h
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// Defines functions for implementing seeking in a stream.
-#pragma once
-
-#include <cstddef>
-
-#include "pw_stream/stream.h"
-
-namespace pw::stream {
-
-// Adds a seek offset to the specified origin.
-constexpr ptrdiff_t ResolveSeekOffset(ptrdiff_t offset,
-                                      Stream::Whence origin,
-                                      size_t end_position,
-                                      size_t current_position) {
-  switch (origin) {
-    case Stream::kBeginning:
-      return offset;
-    case Stream::kCurrent:
-      return static_cast<ptrdiff_t>(current_position) + offset;
-    case Stream::kEnd:
-    default:
-      return static_cast<ptrdiff_t>(end_position) + offset;
-  }
-}
-
-// Implements seek for a class that supports absolute position changes. The
-// new position is calculated and assigned to the provided position variable.
-//
-// Returns OUT_OF_RANGE for seeks to a negative position or past the end.
-constexpr Status CalculateSeek(ptrdiff_t offset,
-                               Stream::Whence origin,
-                               size_t end_position,
-                               size_t& current_position) {
-  const ptrdiff_t new_position =
-      ResolveSeekOffset(offset, origin, end_position, current_position);
-
-  if (new_position < 0 || static_cast<size_t>(new_position) > end_position) {
-    return Status::OutOfRange();
-  }
-
-  current_position = static_cast<size_t>(new_position);
-  return OkStatus();
-}
-
-}  // namespace pw::stream
diff --git a/pw_stream/public/pw_stream/socket_stream.h b/pw_stream/public/pw_stream/socket_stream.h
index 058b810..1ab1161 100644
--- a/pw_stream/public/pw_stream/socket_stream.h
+++ b/pw_stream/public/pw_stream/socket_stream.h
@@ -13,20 +13,28 @@
 // the License.
 #pragma once
 
+#include <arpa/inet.h>
 #include <netinet/in.h>
+#include <stdio.h>
+#include <sys/socket.h>
+#include <unistd.h>
 
-#include <cstdint>
+#include <array>
+#include <cstddef>
+#include <limits>
 #include <span>
 
 #include "pw_stream/stream.h"
 
 namespace pw::stream {
 
-class SocketStream : public NonSeekableReaderWriter {
- public:
-  constexpr SocketStream() = default;
+static constexpr int kExitCode = -1;
+static constexpr int kInvalidFd = -1;
 
-  ~SocketStream() { Close(); }
+class SocketStream : public Writer, public Reader {
+ public:
+  explicit SocketStream() {}
+  ~SocketStream();
 
   // Listen to the port and return after a client is connected
   Status Serve(uint16_t port);
@@ -39,8 +47,6 @@
   void Close();
 
  private:
-  static constexpr int kInvalidFd = -1;
-
   Status DoWrite(std::span<const std::byte> data) override;
 
   StatusWithSize DoRead(ByteSpan dest) override;
@@ -48,7 +54,7 @@
   uint16_t listen_port_ = 0;
   int socket_fd_ = kInvalidFd;
   int conn_fd_ = kInvalidFd;
-  struct sockaddr_in sockaddr_client_ = {};
+  struct sockaddr_in sockaddr_client_;
 };
 
 }  // namespace pw::stream
diff --git a/pw_stream/public/pw_stream/std_file_stream.h b/pw_stream/public/pw_stream/std_file_stream.h
deleted file mode 100644
index c1f9fbc..0000000
--- a/pw_stream/public/pw_stream/std_file_stream.h
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <fstream>
-
-#include "pw_stream/stream.h"
-
-namespace pw::stream {
-
-// Wraps an std::ifstream with the Reader interface.
-class StdFileReader final : public stream::SeekableReader {
- public:
-  StdFileReader(const char* path) : stream_(path, std::ios::binary) {}
-
-  void Close() { stream_.close(); }
-
- private:
-  StatusWithSize DoRead(ByteSpan dest) override;
-  Status DoSeek(ptrdiff_t offset, Whence origin) override;
-
-  std::ifstream stream_;
-};
-
-// Wraps an std::ofstream with the Writer interface.
-class StdFileWriter final : public stream::SeekableWriter {
- public:
-  StdFileWriter(const char* path)
-      : stream_(path, std::ios::binary | std::ios::trunc) {}
-
-  void Close() { stream_.close(); }
-
- private:
-  Status DoWrite(ConstByteSpan data) override;
-  Status DoSeek(ptrdiff_t offset, Whence origin) override;
-
-  std::ofstream stream_;
-};
-
-}  // namespace pw::stream
diff --git a/pw_stream/public/pw_stream/stream.h b/pw_stream/public/pw_stream/stream.h
index 31fdb33..2fcde9c 100644
--- a/pw_stream/public/pw_stream/stream.h
+++ b/pw_stream/public/pw_stream/stream.h
@@ -1,4 +1,4 @@
-// Copyright 2021 The Pigweed Authors
+// Copyright 2020 The Pigweed Authors
 //
 // 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
@@ -17,7 +17,7 @@
 #include <cstddef>
 #include <span>
 
-#include "pw_assert/assert.h"
+#include "pw_assert/light.h"
 #include "pw_bytes/span.h"
 #include "pw_result/result.h"
 #include "pw_status/status.h"
@@ -25,102 +25,25 @@
 
 namespace pw::stream {
 
-// A generic stream that may support reading, writing, and seeking, but makes no
-// guarantees about whether any operations are supported. Unsupported functions
-// return Status::Unimplemented(). Stream serves as the base for the Reader,
-// Writer, and ReaderWriter interfaces.
-//
-// Stream should NOT be used or extended directly. Instead, work with one of the
-// derived classes that explicitly supports the required functionality.
-class Stream {
+// General-purpose writer interface.
+class Writer {
  public:
-  // Positions from which to seek.
-  enum Whence : uint8_t {
-    // Seek from the beginning of the stream. The offset is a direct offset into
-    // the data.
-    kBeginning = 0b001,
+  // There are no requirements around if or how a `Writer` should call Flush()
+  // at the time of object destruction.
+  virtual ~Writer() = default;
 
-    // Seek from the current position in the stream. The offset is added to the
-    // current position. Use a negative offset to seek backwards.
-    //
-    // Implementations may only support seeking within a limited range from
-    // the current position.
-    kCurrent = 0b010,
-
-    // Seek from the end of the stream. The offset is added to the end
-    // position. Use a negative offset to seek backwards from the end.
-    kEnd = 0b100,
-  };
-
-  // Value returned from read/write limit if unlimited.
-  static constexpr size_t kUnlimited = std::numeric_limits<size_t>::max();
-
-  // Returned by Tell() if getting the position is not supported.
-  static constexpr size_t kUnknownPosition = std::numeric_limits<size_t>::max();
-
-  virtual ~Stream() = default;
-
-  // True if reading is supported. If false, Read() returns UNIMPLEMENTED.
-  constexpr bool readable() const { return readable_; }
-
-  // True if writing is supported. If false, Write() returns UNIMPLEMENTED.
-  constexpr bool writable() const { return writable_; }
-
-  // True if the stream supports seeking.
-  constexpr bool seekable() const { return seekability_ != Seekability::kNone; }
-
-  // True if the stream supports seeking from the specified origin.
-  constexpr bool seekable(Whence origin) const {
-    return (static_cast<uint8_t>(seekability_) & origin) != 0u;
-  }
-
-  // Reads data from this stream. If any number of bytes are read returns OK
-  // with a span of the bytes read.
-  //
-  // If the reader has been exhausted and is and can no longer read additional
-  // bytes it will return OUT_OF_RANGE. This is similar to end-of-file (EOF).
-  // Read will only return OUT_OF_RANGE if ConservativeReadLimit() is and will
-  // remain zero. A Read operation that is successful and also exhausts the
-  // reader returns OK, with all following calls returning OUT_OF_RANGE.
-  //
-  // Derived classes should NOT try to override these public read methods.
-  // Instead, provide an implementation by overriding DoRead().
-  //
-  // Returns:
-  //
-  //   OK - Between 1 and dest.size_bytes() were successfully read. Returns the
-  //       span of read bytes.
-  //   UNIMPLEMENTED - This stream does not support reading.
-  //   FAILED_PRECONDITION - The Reader  is not in state to read data.
-  //   RESOURCE_EXHAUSTED - Unable to read any bytes at this time. No bytes
-  //       read. Try again once bytes become available.
-  //   OUT_OF_RANGE - Reader has been exhausted, similar to EOF. No bytes were
-  //       read, no more will be read.
-  //
-  Result<ByteSpan> Read(ByteSpan dest) {
-    PW_DASSERT(dest.empty() || dest.data() != nullptr);
-    StatusWithSize result = DoRead(dest);
-
-    if (result.ok()) {
-      return dest.first(result.size());
-    }
-    return result.status();
-  }
-  Result<ByteSpan> Read(void* dest, size_t size_bytes) {
-    return Read(std::span(static_cast<std::byte*>(dest), size_bytes));
-  }
-
-  // Writes data to this stream. Data is not guaranteed to be fully written out
-  // to final resting place on Write return.
+  // Write data to this stream Writer. Data is
+  // not guaranteed to be fully written out to final resting place on Write
+  // return.
   //
   // If the writer is unable to fully accept the input data size it will abort
   // the write and return RESOURCE_EXHAUSTED.
   //
   // If the writer has been exhausted and is and can no longer accept additional
-  // bytes it will return OUT_OF_RANGE. This is similar to end-of-file (EOF).
-  // Write will only return OUT_OF_RANGE if ConservativeWriteLimit() is and will
-  // remain zero. A Write operation that is successful and also exhausts the
-  // writer returns OK, with all following calls returning OUT_OF_RANGE. When
+  // bytes it will return OUT_OF_RANGE. This is similar to EndOfFile. Write will
+  // only return OUT_OF_RANGE if ConservativeWriteLimit() is and will remain
+  // zero. A Write operation that is successful and also exhausts the writer
+  // returns OK, with all following calls returning OUT_OF_RANGE. When
   // ConservativeWriteLimit() is greater than zero, a Write that is a number of
   // bytes beyond what will exhaust the Write will abort and return
   // RESOURCE_EXHAUSTED rather than OUT_OF_RANGE because the writer is still
@@ -131,14 +54,12 @@
   //
   // Returns:
   //
-  //   OK - Data was successfully accepted by the stream.
-  //   UNIMPLEMENTED - This stream does not support writing.
-  //   FAILED_PRECONDITION - The writer is not in a state to accept data.
-  //   RESOURCE_EXHAUSTED - The writer was unable to write all of requested data
-  //       at this time. No data was written.
-  //   OUT_OF_RANGE - The Writer has been exhausted, similar to EOF. No data was
-  //       written; no more will be written.
-  //
+  // OK - successful write/enqueue of data.
+  // FAILED_PRECONDITION - writer unable/not in state to accept data.
+  // RESOURCE_EXHAUSTED - unable to write all of requested data at this time. No
+  //     data written.
+  // OUT_OF_RANGE - Writer has been exhausted, similar to EOF. No data written,
+  //     no more will be written.
   Status Write(ConstByteSpan data) {
     PW_DASSERT(data.empty() || data.data() != nullptr);
     return DoWrite(data);
@@ -148,465 +69,79 @@
   }
   Status Write(const std::byte b) { return Write(&b, 1); }
 
-  // Changes the current position in the stream for both reading and writing, if
-  // supported.
-  //
-  // Seeking to a negative offset is invalid. The behavior when seeking beyond
-  // the end of a stream is determined by the implementation. The implementation
-  // could fail with OUT_OF_RANGE or append bytes to the stream.
-  //
-  // Returns:
-  //
-  //   OK - Successfully updated the position.
-  //   UNIMPLEMENTED - Seeking is not supported for this stream.
-  //   OUT_OF_RANGE - Attempted to seek beyond the bounds of the stream. The
-  //       position is unchanged.
-  //
-  Status Seek(ptrdiff_t offset, Whence origin = kBeginning) {
-    return DoSeek(offset, origin);
-  }
-
-  // Returns the current position in the stream, if supported. The position is
-  // the offset from the beginning of the stream. Returns
-  // Stream::kUnknownPosition if the position is unknown.
-  //
-  // Streams that support seeking from the beginning always support Tell().
-  // Other streams may or may not support Tell().
-  size_t Tell() const { return DoTell(); }
-
-  // Liklely (not guaranteed) minimum bytes available to read at this time.
-  // This number is advisory and not guaranteed to read full number of requested
-  // bytes or without a RESOURCE_EXHAUSTED or OUT_OF_RANGE. As Reader
-  // processes/handles/receives enqueued data or other contexts read data this
-  // number can go up or down for some Readers.
-  //
-  // Returns zero if, in the current state, Read() would not return
-  // OkStatus().
-  //
-  // Returns kUnlimited if the implementation imposes no limits on read sizes.
-  size_t ConservativeReadLimit() const {
-    return ConservativeLimit(LimitType::kRead);
-  }
-
-  // Likely (not guaranteed) minimum bytes available to write at this time.
-  // This number is advisory and not guaranteed to write without a
+  // Probable (not guaranteed) minimum number of bytes at this time that can be
+  // written. This number is advisory and not guaranteed to write without a
   // RESOURCE_EXHAUSTED or OUT_OF_RANGE. As Writer processes/handles enqueued of
   // other contexts write data this number can go up or down for some Writers.
   // Returns zero if, in the current state, Write() would not return
   // OkStatus().
   //
-  // Returns kUnlimited if the implementation has no limits on write sizes.
-  size_t ConservativeWriteLimit() const {
-    return ConservativeLimit(LimitType::kWrite);
+  // Returns std::numeric_limits<size_t>::max() if the implementation has no
+  // limits on write sizes.
+  virtual size_t ConservativeWriteLimit() const {
+    return std::numeric_limits<size_t>::max();
   }
 
- protected:
-  // Used to indicate the type of limit being queried in ConservativeLimit.
-  enum class LimitType : bool { kRead, kWrite };
-
  private:
-  // The Stream class should not be extended directly, so its constructor is
-  // private. To implement a new Stream, extend one of its derived classes.
-  friend class Reader;
-  friend class RelativeSeekableReader;
-  friend class SeekableReader;
-  friend class NonSeekableReader;
-
-  friend class Writer;
-  friend class RelativeSeekableWriter;
-  friend class SeekableWriter;
-  friend class NonSeekableWriter;
-
-  friend class ReaderWriter;
-  friend class RelativeSeekableReaderWriter;
-  friend class SeekableReaderWriter;
-  friend class NonSeekableReaderWriter;
-
-  // Seekability expresses the origins from which the stream always supports
-  // seeking. Seeking from other origins may work, but is not guaranteed.
-  //
-  // Seekability is implemented as a bitfield of Whence values.
-  enum class Seekability : uint8_t {
-    // No type of seeking is supported.
-    kNone = 0,
-
-    // Seeking from the current position is supported, but the range may be
-    // limited. For example, a buffered stream might support seeking within the
-    // buffered data, but not before or after.
-    kRelative = kCurrent,
-
-    // The stream supports random access anywhere within the stream.
-    kAbsolute = kBeginning | kCurrent | kEnd,
-  };
-
-  constexpr Stream(bool readable, bool writable, Seekability seekability)
-      : readable_(readable), writable_(writable), seekability_(seekability) {}
-
-  // These are the virtual methods that are implemented by derived classes. The
-  // public API functions call these virtual functions.
-  virtual StatusWithSize DoRead(ByteSpan destination) = 0;
-
   virtual Status DoWrite(ConstByteSpan data) = 0;
+};
 
-  virtual Status DoSeek(ptrdiff_t offset, Whence origin) = 0;
+// General-purpose reader interface
+class Reader {
+ public:
+  virtual ~Reader() = default;
 
-  virtual size_t DoTell() const { return kUnknownPosition; }
+  // Read data from this stream Reader. If any number of bytes are read return
+  // OK with a span of the actual byte read.
+  //
+  // If the reader has been exhausted and is and can no longer read additional
+  // bytes it will return OUT_OF_RANGE. This is similar to EndOfFile. Read will
+  // only return OUT_OF_RANGE if ConservativeReadLimit() is and will remain
+  // zero. A Read operation that is successful and also exhausts the reader
+  // returns OK, with all following calls returning OUT_OF_RANGE.
+  //
+  // Derived classes should NOT try to override these public read methods.
+  // Instead, provide an implementation by overriding DoRead().
+  //
+  // Returns:
+  //
+  // OK with span of bytes read - success, between 1 and dest.size_bytes() were
+  //     read.
+  // FAILED_PRECONDITION - Reader unable/not in state to read data.
+  // RESOURCE_EXHAUSTED - unable to read any bytes at this time. No bytes read.
+  //     Try again once bytes become available.
+  // OUT_OF_RANGE - Reader has been exhausted, similar to EOF. No bytes read, no
+  //     more will be read.
+  Result<ByteSpan> Read(ByteSpan dest) {
+    PW_DASSERT(dest.empty() || dest.data() != nullptr);
+    StatusWithSize result = DoRead(dest);
 
-  virtual size_t ConservativeLimit(LimitType limit_type) const {
-    if (limit_type == LimitType::kRead) {
-      return readable() ? kUnlimited : 0;
+    if (result.ok()) {
+      return dest.first(result.size());
+    } else {
+      return result.status();
     }
-    return writable() ? kUnlimited : 0;
+  }
+  Result<ByteSpan> Read(void* dest, size_t size_bytes) {
+    return Read(std::span(static_cast<std::byte*>(dest), size_bytes));
   }
 
-  bool readable_;
-  bool writable_;
-  Seekability seekability_;
-};
-
-// A Stream that supports reading but not writing. The Write() method is hidden.
-//
-// Use in APIs when:
-//   * Must read from, but not write to, a stream.
-//   * May or may not need seeking. Use a SeekableReader& if seeking is
-//     required.
-//
-// Inherit from when:
-//   * Reader cannot be extended directly. Instead, extend SeekableReader,
-//     NonSeekableReader, or (rarely) RelativeSeekableReader, as appropriate.
-//
-// A Reader may or may not support seeking. Check seekable() or try calling
-// Seek() to determine if the stream is seekable.
-class Reader : public Stream {
- private:
-  friend class RelativeSeekableReader;
-  friend class NonSeekableReader;
-
-  constexpr Reader(Seekability seekability)
-      : Stream(true, false, seekability) {}
-
-  using Stream::Write;
-
-  Status DoWrite(ConstByteSpan) final { return Status::Unimplemented(); }
-};
-
-// A Reader that supports at least relative seeking within some range of the
-// current position. Seeking beyond that or from other origins may or may not be
-// supported. The extent to which seeking is possible is NOT exposed by this
-// API.
-//
-// Use in APIs when:
-//   * Relative seeking is required. Usage in APIs should be rare; generally
-//     Reader should be used instead.
-//
-// Inherit from when:
-//   * Implementing a Reader that can only support seeking near the current
-//     position.
-//
-// A buffered Reader that only supports seeking within its buffer is a good
-// example of a RelativeSeekableReader.
-class RelativeSeekableReader : public Reader {
- protected:
-  constexpr RelativeSeekableReader()
-      : RelativeSeekableReader(Seekability::kRelative) {}
+  // Probable (not guaranteed) minimum number of bytes at this time that can be
+  // read. This number is advisory and not guaranteed to read full number of
+  // requested bytes or without a RESOURCE_EXHAUSTED or OUT_OF_RANGE. As Reader
+  // processes/handles/receives enqueued data or other contexts read data this
+  // number can go up or down for some Readers.
+  // Returns zero if, in the current state, Read() would not return
+  // OkStatus().
+  //
+  // Returns std::numeric_limits<size_t>::max() if the implementation imposes no
+  // limits on read sizes.
+  virtual size_t ConservativeReadLimit() const {
+    return std::numeric_limits<size_t>::max();
+  }
 
  private:
-  friend class SeekableReader;
-
-  constexpr RelativeSeekableReader(Seekability seekability)
-      : Reader(seekability) {}
-};
-
-// A Reader that fully supports seeking.
-//
-// Use in APIs when:
-//   * Absolute seeking is required. Use Reader& if seeking is not required or
-//     seek failures can be handled gracefully.
-//
-// Inherit from when:
-//   * Implementing a reader that supports absolute seeking.
-//
-class SeekableReader : public RelativeSeekableReader {
- protected:
-  constexpr SeekableReader() : RelativeSeekableReader(Seekability::kAbsolute) {}
-};
-
-// A Reader that does not support seeking. The Seek() method is hidden.
-//
-// Use in APIs when:
-//   * Do NOT use in APIs! If seeking is not required, use Reader& instead.
-//
-// Inherit from when:
-//   * Implementing a Reader that does not support seeking.
-//
-class NonSeekableReader : public Reader {
- protected:
-  constexpr NonSeekableReader() : Reader(Seekability::kNone) {}
-
- private:
-  using Reader::Seek;
-
-  Status DoSeek(ptrdiff_t, Whence) final { return Status::Unimplemented(); }
-};
-
-// A Stream that supports writing but not reading. The Read() method is hidden.
-//
-// Use in APIs when:
-//   * Must write to, but not read from, a stream.
-//   * May or may not need seeking. Use a SeekableWriter& if seeking is
-//     required.
-//
-// Inherit from when:
-//   * Writer cannot be extended directly. Instead, extend SeekableWriter,
-//     NonSeekableWriter, or (rarely) RelativeSeekableWriter, as appropriate.
-//
-// A Writer may or may not support seeking. Check seekable() or try calling
-// Seek() to determine if the stream is seekable.
-class Writer : public Stream {
- private:
-  friend class RelativeSeekableWriter;
-  friend class NonSeekableWriter;
-
-  constexpr Writer(Seekability seekability)
-      : Stream(false, true, seekability) {}
-
-  using Stream::Read;
-
-  StatusWithSize DoRead(ByteSpan) final {
-    return StatusWithSize::Unimplemented();
-  }
-};
-
-// A Writer that supports at least relative seeking within some range of the
-// current position. Seeking beyond that or from other origins may or may not be
-// supported. The extent to which seeking is possible is NOT exposed by this
-// API.
-//
-// Use in APIs when:
-//   * Relative seeking is required. Usage in APIs should be rare; generally
-//     Writer should be used instead.
-//
-// Inherit from when:
-//   * Implementing a Writer that can only support seeking near the current
-//     position.
-//
-// A buffered Writer that only supports seeking within its buffer is a good
-// example of a RelativeSeekableWriter.
-class RelativeSeekableWriter : public Writer {
- protected:
-  constexpr RelativeSeekableWriter()
-      : RelativeSeekableWriter(Seekability::kRelative) {}
-
- private:
-  friend class SeekableWriter;
-
-  constexpr RelativeSeekableWriter(Seekability seekability)
-      : Writer(seekability) {}
-};
-
-// A Writer that fully supports seeking.
-//
-// Use in APIs when:
-//   * Absolute seeking is required. Use Writer& if seeking is not required or
-//     seek failures can be handled gracefully.
-//
-// Inherit from when:
-//   * Implementing a writer that supports absolute seeking.
-//
-class SeekableWriter : public RelativeSeekableWriter {
- protected:
-  constexpr SeekableWriter() : RelativeSeekableWriter(Seekability::kAbsolute) {}
-};
-
-// A Writer that does not support seeking. The Seek() method is hidden.
-//
-// Use in APIs when:
-//   * Do NOT use in APIs! If seeking is not required, use Writer& instead.
-//
-// Inherit from when:
-//   * Implementing a Writer that does not support seeking.
-//
-class NonSeekableWriter : public Writer {
- protected:
-  constexpr NonSeekableWriter() : Writer(Seekability::kNone) {}
-
- private:
-  using Writer::Seek;
-
-  Status DoSeek(ptrdiff_t, Whence) final { return Status::Unimplemented(); }
-};
-
-// A Stream that supports both reading and writing.
-//
-// Use in APIs when:
-//   * Must both read from and write to a stream.
-//   * May or may not need seeking. Use a SeekableReaderWriter& if seeking is
-//     required.
-//
-// Inherit from when:
-//   * Cannot extend ReaderWriter directly. Instead, extend
-//     SeekableReaderWriter, NonSeekableReaderWriter, or (rarely)
-//     RelativeSeekableReaderWriter, as appropriate.
-//
-// A ReaderWriter may or may not support seeking. Check seekable() or try
-// calling Seek() to determine if the stream is seekable.
-class ReaderWriter : public Stream {
- public:
-  // ReaderWriters may be used as Readers.
-  constexpr Reader& as_reader() {
-    return static_cast<Reader&>(static_cast<Stream&>(*this));
-  }
-  constexpr const Reader& as_reader() const {
-    return static_cast<const Reader&>(static_cast<const Stream&>(*this));
-  }
-
-  constexpr operator Reader&() { return as_reader(); }
-  constexpr operator const Reader&() const { return as_reader(); }
-
-  // ReaderWriters may be used as Writers.
-  constexpr Writer& as_writer() {
-    return static_cast<Writer&>(static_cast<Stream&>(*this));
-  }
-  constexpr const Writer& as_writer() const {
-    return static_cast<const Writer&>(static_cast<const Stream&>(*this));
-  }
-
-  constexpr operator Writer&() { return as_writer(); }
-  constexpr operator const Writer&() const { return as_writer(); }
-
- private:
-  friend class RelativeSeekableReaderWriter;
-  friend class NonSeekableReaderWriter;
-
-  constexpr ReaderWriter(Seekability seekability)
-      : Stream(true, true, seekability) {}
-};
-
-// A ReaderWriter that supports at least relative seeking within some range of
-// the current position. Seeking beyond that or from other origins may or may
-// not be supported. The extent to which seeking is possible is NOT exposed by
-// this API.
-//
-// Use in APIs when:
-//   * Relative seeking is required. Usage in APIs should be rare; generally
-//     ReaderWriter should be used instead.
-//
-// Inherit from when:
-//   * Implementing a ReaderWriter that can only support seeking near the
-//     current position.
-//
-// A buffered ReaderWriter that only supports seeking within its buffer is a
-// good example of a RelativeSeekableReaderWriter.
-class RelativeSeekableReaderWriter : public ReaderWriter {
- public:
-  // RelativeSeekableReaderWriters may be used as RelativeSeekableReaders or
-  // RelativeSeekableWriters.
-  constexpr operator RelativeSeekableReader&() {
-    return static_cast<RelativeSeekableReader&>(static_cast<Stream&>(*this));
-  }
-  constexpr operator const RelativeSeekableReader&() const {
-    return static_cast<const RelativeSeekableReader&>(
-        static_cast<const Stream&>(*this));
-  }
-  constexpr operator RelativeSeekableWriter&() {
-    return static_cast<RelativeSeekableWriter&>(static_cast<Stream&>(*this));
-  }
-  constexpr operator const RelativeSeekableWriter&() const {
-    return static_cast<const RelativeSeekableWriter&>(
-        static_cast<const Stream&>(*this));
-  }
-
- protected:
-  constexpr RelativeSeekableReaderWriter()
-      : ReaderWriter(Seekability::kRelative) {}
-
- private:
-  friend class SeekableReaderWriter;
-
-  constexpr RelativeSeekableReaderWriter(Seekability seekability)
-      : ReaderWriter(seekability) {}
-};
-
-// A ReaderWriter that fully supports seeking.
-//
-// Use in APIs when:
-//   * Absolute seeking is required. Use ReaderWriter& if seeking is not
-//     required or seek failures can be handled gracefully.
-//
-// Inherit from when:
-//   * Implementing a writer that supports absolute seeking.
-//
-class SeekableReaderWriter : public RelativeSeekableReaderWriter {
- public:
-  // SeekableReaderWriters may be used as SeekableReaders.
-  constexpr SeekableReader& as_seekable_reader() {
-    return static_cast<SeekableReader&>(static_cast<Stream&>(*this));
-  }
-  constexpr const SeekableReader& as_seekable_reader() const {
-    return static_cast<const SeekableReader&>(
-        static_cast<const Stream&>(*this));
-  }
-
-  constexpr operator SeekableReader&() { return as_seekable_reader(); }
-  constexpr operator const SeekableReader&() const {
-    return as_seekable_reader();
-  }
-
-  // SeekableReaderWriters may be used as SeekableWriters.
-  constexpr SeekableWriter& as_seekable_writer() {
-    return static_cast<SeekableWriter&>(static_cast<Stream&>(*this));
-  }
-  constexpr const SeekableWriter& as_seekable_writer() const {
-    return static_cast<const SeekableWriter&>(
-        static_cast<const Stream&>(*this));
-  }
-
-  constexpr operator SeekableWriter&() { return as_seekable_writer(); }
-  constexpr operator const SeekableWriter&() const {
-    return as_seekable_writer();
-  }
-
- protected:
-  constexpr SeekableReaderWriter()
-      : RelativeSeekableReaderWriter(Seekability::kAbsolute) {}
-};
-
-// A ReaderWriter that does not support seeking. The Seek() method is hidden.
-//
-// Use in APIs when:
-//   * Do NOT use in APIs! If seeking is not required, use ReaderWriter&
-//     instead.
-//
-// Inherit from when:
-//   * Implementing a ReaderWriter that does not support seeking.
-//
-class NonSeekableReaderWriter : public ReaderWriter {
- public:
-  // NonSeekableReaderWriters may be used as either NonSeekableReaders or
-  // NonSeekableWriters. Note that NonSeekableReaderWriter& generally should not
-  // be used in APIs, which should accept ReaderWriter& instead.
-  constexpr operator NonSeekableReader&() {
-    return static_cast<NonSeekableReader&>(static_cast<Stream&>(*this));
-  }
-  constexpr operator const NonSeekableReader&() const {
-    return static_cast<const NonSeekableReader&>(
-        static_cast<const Stream&>(*this));
-  }
-  constexpr operator NonSeekableWriter&() {
-    return static_cast<NonSeekableWriter&>(static_cast<Stream&>(*this));
-  }
-  constexpr operator const NonSeekableWriter&() const {
-    return static_cast<const NonSeekableWriter&>(
-        static_cast<const Stream&>(*this));
-  }
-
- protected:
-  constexpr NonSeekableReaderWriter() : ReaderWriter(Seekability::kNone) {}
-
- private:
-  using ReaderWriter::Seek;
-
-  Status DoSeek(ptrdiff_t, Whence) final { return Status::Unimplemented(); }
+  virtual StatusWithSize DoRead(ByteSpan dest) = 0;
 };
 
 }  // namespace pw::stream
diff --git a/pw_stream/public/pw_stream/sys_io_stream.h b/pw_stream/public/pw_stream/sys_io_stream.h
index 443cbc0..446f5a4 100644
--- a/pw_stream/public/pw_stream/sys_io_stream.h
+++ b/pw_stream/public/pw_stream/sys_io_stream.h
@@ -23,14 +23,14 @@
 
 namespace pw::stream {
 
-class SysIoWriter : public NonSeekableWriter {
+class SysIoWriter : public Writer {
  private:
   Status DoWrite(std::span<const std::byte> data) override {
     return pw::sys_io::WriteBytes(data).status();
   }
 };
 
-class SysIoReader : public NonSeekableReader {
+class SysIoReader : public Reader {
  private:
   StatusWithSize DoRead(ByteSpan dest) override {
     return pw::sys_io::ReadBytes(dest);
diff --git a/pw_stream/seek_test.cc b/pw_stream/seek_test.cc
deleted file mode 100644
index bc2f982..0000000
--- a/pw_stream/seek_test.cc
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_stream/seek.h"
-
-#include "gtest/gtest.h"
-
-namespace pw::stream {
-namespace {
-
-TEST(ResolveSeekOffset, Beginning) {
-  EXPECT_EQ(ResolveSeekOffset(123, Stream::kBeginning, 0, 0), 123);
-}
-
-TEST(ResolveSeekOffset, Current) {
-  EXPECT_EQ(ResolveSeekOffset(-10, Stream::kCurrent, 0, 100), 90);
-}
-
-TEST(ResolveSeekOffset, End) {
-  EXPECT_EQ(ResolveSeekOffset(-10, Stream::kEnd, 100, 0), 90);
-}
-
-TEST(CalculateSeek, Beginning) {
-  size_t position = 2;
-  EXPECT_EQ(OkStatus(), CalculateSeek(35, Stream::kBeginning, 100, position));
-  EXPECT_EQ(position, 35u);
-}
-
-TEST(CalculateSeek, Beginning_SeekToBeginning) {
-  size_t position = 99;
-  EXPECT_EQ(OkStatus(), CalculateSeek(0, Stream::kBeginning, 100, position));
-  EXPECT_EQ(position, 0u);
-}
-
-TEST(CalculateSeek, Beginning_SeekToEnd) {
-  size_t position = 0;
-  EXPECT_EQ(OkStatus(), CalculateSeek(100, Stream::kBeginning, 100, position));
-  EXPECT_EQ(position, 100u);
-}
-
-TEST(CalculateSeek, Beginning_SeekNegative_OutOfRange) {
-  size_t position = 2;
-  EXPECT_EQ(Status::OutOfRange(),
-            CalculateSeek(-1, Stream::kBeginning, 100, position));
-  EXPECT_EQ(position, 2u);
-}
-
-TEST(CalculateSeek, Beginning_SeekPastEnd_OutOfRange) {
-  size_t position = 2;
-  EXPECT_EQ(Status::OutOfRange(),
-            CalculateSeek(101, Stream::kBeginning, 100, position));
-  EXPECT_EQ(position, 2u);
-}
-
-TEST(CalculateSeek, Current) {
-  size_t position = 100;
-  EXPECT_EQ(OkStatus(), CalculateSeek(1, Stream::kCurrent, 234, position));
-  EXPECT_EQ(position, 101u);
-}
-
-TEST(CalculateSeek, Current_SeekToBeginning) {
-  size_t position = 99;
-  EXPECT_EQ(OkStatus(), CalculateSeek(-99, Stream::kCurrent, 100, position));
-  EXPECT_EQ(position, 0u);
-}
-
-TEST(CalculateSeek, Current_SeekToEnd) {
-  size_t position = 10;
-  EXPECT_EQ(OkStatus(), CalculateSeek(90, Stream::kCurrent, 1000, position));
-  EXPECT_EQ(position, 100u);
-}
-
-TEST(CalculateSeek, Current_SeekNegative_OutOfRange) {
-  size_t position = 2;
-  EXPECT_EQ(Status::OutOfRange(),
-            CalculateSeek(-3, Stream::kCurrent, 100, position));
-  EXPECT_EQ(position, 2u);
-}
-
-TEST(CalculateSeek, Current_SeekPastEnd_OutOfRange) {
-  size_t position = 2;
-  EXPECT_EQ(Status::OutOfRange(),
-            CalculateSeek(99, Stream::kCurrent, 100, position));
-  EXPECT_EQ(position, 2u);
-}
-
-TEST(CalculateSeek, End) {
-  size_t position = 100;
-  EXPECT_EQ(OkStatus(), CalculateSeek(-1, Stream::kEnd, 234, position));
-  EXPECT_EQ(position, 233u);
-}
-
-TEST(CalculateSeek, End_SeekToBeginning) {
-  size_t position = 50;
-  EXPECT_EQ(OkStatus(), CalculateSeek(-100, Stream::kEnd, 100, position));
-  EXPECT_EQ(position, 0u);
-}
-
-TEST(CalculateSeek, End_SeekToEnd) {
-  size_t position = 10;
-  EXPECT_EQ(OkStatus(), CalculateSeek(0, Stream::kEnd, 1000, position));
-  EXPECT_EQ(position, 1000u);
-}
-
-TEST(CalculateSeek, End_SeekNegative_OutOfRange) {
-  size_t position = 2;
-  EXPECT_EQ(Status::OutOfRange(),
-            CalculateSeek(-101, Stream::kEnd, 100, position));
-  EXPECT_EQ(position, 2u);
-}
-
-TEST(CalculateSeek, End_SeekPastEnd_OutOfRange) {
-  size_t position = 2;
-  EXPECT_EQ(Status::OutOfRange(),
-            CalculateSeek(1, Stream::kEnd, 100, position));
-  EXPECT_EQ(position, 2u);
-}
-
-}  // namespace
-}  // namespace pw::stream
diff --git a/pw_stream/socket_stream.cc b/pw_stream/socket_stream.cc
index 016af92..034a3fb 100644
--- a/pw_stream/socket_stream.cc
+++ b/pw_stream/socket_stream.cc
@@ -13,59 +13,35 @@
 // the License.
 
 #include "pw_stream/socket_stream.h"
-
-#include <arpa/inet.h>
-#include <unistd.h>
-
-#include <cstring>
-
-#include "pw_log/log.h"
-
 namespace pw::stream {
-namespace {
 
-constexpr uint32_t kMaxConcurrentUser = 1;
-constexpr const char* kLocalhostAddress = "127.0.0.1";
+static constexpr uint32_t kMaxConcurrentUser = 1;
+static constexpr char kLocalhostAddress[] = "127.0.0.1";
 
-}  // namespace
+SocketStream::~SocketStream() { Close(); }
 
 // Listen to the port and return after a client is connected
 Status SocketStream::Serve(uint16_t port) {
   listen_port_ = port;
   socket_fd_ = socket(AF_INET, SOCK_STREAM, 0);
   if (socket_fd_ == kInvalidFd) {
-    PW_LOG_ERROR("Failed to create socket: %s", std::strerror(errno));
-    return Status::Unknown();
+    return Status::Internal();
   }
 
-  struct sockaddr_in addr = {};
+  struct sockaddr_in addr;
   addr.sin_family = AF_INET;
   addr.sin_port = htons(listen_port_);
   addr.sin_addr.s_addr = INADDR_ANY;
 
-  // Configure the socket to allow reusing the address. Closing a socket does
-  // not immediately close it. Instead, the socket waits for some period of time
-  // before it is actually closed. Setting SO_REUSEADDR allows this socket to
-  // bind to an address that may still be in use by a recently closed socket.
-  // Without this option, running a program multiple times in series may fail
-  // unexpectedly.
-  constexpr int value = 1;
-
-  if (setsockopt(socket_fd_, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(int)) <
-      0) {
-    PW_LOG_WARN("Failed to set SO_REUSEADDR: %s", std::strerror(errno));
+  int result =
+      bind(socket_fd_, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr));
+  if (result < 0) {
+    return Status::Internal();
   }
 
-  if (bind(socket_fd_, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
-    PW_LOG_ERROR("Failed to bind socket to localhost:%hu: %s",
-                 listen_port_,
-                 std::strerror(errno));
-    return Status::Unknown();
-  }
-
-  if (listen(socket_fd_, kMaxConcurrentUser) < 0) {
-    PW_LOG_ERROR("Failed to listen to socket: %s", std::strerror(errno));
-    return Status::Unknown();
+  result = listen(socket_fd_, kMaxConcurrentUser);
+  if (result < 0) {
+    return Status::Internal();
   }
 
   socklen_t len = sizeof(sockaddr_client_);
@@ -73,7 +49,7 @@
   conn_fd_ =
       accept(socket_fd_, reinterpret_cast<sockaddr*>(&sockaddr_client_), &len);
   if (conn_fd_ < 0) {
-    return Status::Unknown();
+    return Status::Internal();
   }
   return OkStatus();
 }
@@ -85,18 +61,17 @@
   addr.sin_family = AF_INET;
   addr.sin_port = htons(port);
 
-  if (host == nullptr || std::strcmp(host, "localhost") == 0) {
+  if (host == nullptr) {
     host = kLocalhostAddress;
   }
 
   if (inet_pton(AF_INET, host, &addr.sin_addr) <= 0) {
-    PW_LOG_ERROR("Failed to configure connection address for socket");
-    return Status::InvalidArgument();
+    return Status::Unknown();
   }
 
-  if (connect(conn_fd_, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
-    PW_LOG_ERROR(
-        "Failed to connect to %s:%d: %s", host, port, std::strerror(errno));
+  int result = connect(
+      conn_fd_, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr));
+  if (result < 0) {
     return Status::Unknown();
   }
 
@@ -118,8 +93,8 @@
 Status SocketStream::DoWrite(std::span<const std::byte> data) {
   ssize_t bytes_sent = send(conn_fd_, data.data(), data.size_bytes(), 0);
 
-  if (bytes_sent < 0 || static_cast<size_t>(bytes_sent) != data.size()) {
-    return Status::Unknown();
+  if (bytes_sent < 0 || static_cast<uint64_t>(bytes_sent) != data.size()) {
+    return Status::Internal();
   }
   return OkStatus();
 }
@@ -127,7 +102,7 @@
 StatusWithSize SocketStream::DoRead(ByteSpan dest) {
   ssize_t bytes_rcvd = recv(conn_fd_, dest.data(), dest.size_bytes(), 0);
   if (bytes_rcvd < 0) {
-    return StatusWithSize::Unknown();
+    return StatusWithSize::Internal();
   }
   return StatusWithSize(bytes_rcvd);
 }
diff --git a/pw_stream/std_file_stream.cc b/pw_stream/std_file_stream.cc
deleted file mode 100644
index f934413..0000000
--- a/pw_stream/std_file_stream.cc
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_stream/std_file_stream.h"
-
-namespace pw::stream {
-namespace {
-
-std::ios::seekdir WhenceToSeekDir(Stream::Whence whence) {
-  switch (whence) {
-    case Stream::Whence::kBeginning:
-      return std::ios::beg;
-    case Stream::Whence::kCurrent:
-      return std::ios::cur;
-    case Stream::Whence::kEnd:
-      return std::ios::end;
-  }
-}
-
-}  // namespace
-
-StatusWithSize StdFileReader::DoRead(ByteSpan dest) {
-  stream_.peek();  // Peek to set EOF if at the end of the file.
-  if (stream_.eof()) {
-    return StatusWithSize::OutOfRange();
-  }
-
-  stream_.read(reinterpret_cast<char*>(dest.data()), dest.size());
-  if (stream_.bad()) {
-    return StatusWithSize::Unknown();
-  }
-
-  return StatusWithSize(stream_.gcount());
-}
-
-Status StdFileReader::DoSeek(ptrdiff_t offset, Whence origin) {
-  if (!stream_.seekg(offset, WhenceToSeekDir(origin))) {
-    return Status::Unknown();
-  }
-  return OkStatus();
-}
-
-Status StdFileWriter::DoWrite(ConstByteSpan data) {
-  if (stream_.eof()) {
-    return Status::OutOfRange();
-  }
-
-  if (stream_.write(reinterpret_cast<const char*>(data.data()), data.size())) {
-    return OkStatus();
-  }
-
-  return Status::Unknown();
-}
-
-Status StdFileWriter::DoSeek(ptrdiff_t offset, Whence origin) {
-  if (!stream_.seekp(offset, WhenceToSeekDir(origin))) {
-    return Status::Unknown();
-  }
-  return OkStatus();
-}
-
-}  // namespace pw::stream
diff --git a/pw_stream/stream_test.cc b/pw_stream/stream_test.cc
index a33270e..fad335a 100644
--- a/pw_stream/stream_test.cc
+++ b/pw_stream/stream_test.cc
@@ -1,4 +1,4 @@
-// Copyright 2021 The Pigweed Authors
+// Copyright 2020 The Pigweed Authors
 //
 // 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
@@ -22,197 +22,15 @@
 namespace pw::stream {
 namespace {
 
-static_assert(sizeof(Stream) <= 2 * sizeof(void*),
-              "Stream should be no larger than two pointers (vtable pointer & "
-              "packed members)");
-
-class TestNonSeekableReader : public NonSeekableReader {
- private:
-  StatusWithSize DoRead(ByteSpan) override { return StatusWithSize(0); }
-};
-
-class TestRelativeSeekableReader : public RelativeSeekableReader {
- private:
-  StatusWithSize DoRead(ByteSpan) override { return StatusWithSize(0); }
-  Status DoSeek(ptrdiff_t, Whence) override { return Status(); }
-};
-
-class TestSeekableReader : public SeekableReader {
- private:
-  StatusWithSize DoRead(ByteSpan) override { return StatusWithSize(0); }
-  Status DoSeek(ptrdiff_t, Whence) override { return Status(); }
-};
-
-class TestNonSeekableWriter : public NonSeekableWriter {
- private:
-  Status DoWrite(ConstByteSpan) override { return OkStatus(); }
-};
-
-class TestRelativeSeekableWriter : public RelativeSeekableWriter {
- private:
-  Status DoWrite(ConstByteSpan) override { return OkStatus(); }
-  Status DoSeek(ptrdiff_t, Whence) override { return OkStatus(); }
-};
-
-class TestSeekableWriter : public SeekableWriter {
- private:
-  Status DoWrite(ConstByteSpan) override { return OkStatus(); }
-  Status DoSeek(ptrdiff_t, Whence) override { return OkStatus(); }
-};
-
-class TestNonSeekableReaderWriter : public NonSeekableReaderWriter {
- private:
-  StatusWithSize DoRead(ByteSpan) override { return StatusWithSize(0); }
-  Status DoWrite(ConstByteSpan) override { return OkStatus(); }
-};
-
-class TestRelativeSeekableReaderWriter : public RelativeSeekableReaderWriter {
- private:
-  StatusWithSize DoRead(ByteSpan) override { return StatusWithSize(0); }
-  Status DoWrite(ConstByteSpan) override { return OkStatus(); }
-  Status DoSeek(ptrdiff_t, Whence) override { return OkStatus(); }
-};
-
-class TestSeekableReaderWriter : public SeekableReaderWriter {
- private:
-  StatusWithSize DoRead(ByteSpan) override { return StatusWithSize(0); }
-  Status DoWrite(ConstByteSpan) override { return OkStatus(); }
-  Status DoSeek(ptrdiff_t, Whence) override { return OkStatus(); }
-};
-
-// Test ReaderWriter conversions to Reader/Writer.
-// clang-format off
-static_assert(std::is_convertible<TestNonSeekableReaderWriter, Reader&>());
-static_assert(std::is_convertible<TestNonSeekableReaderWriter, Writer&>());
-static_assert(!std::is_convertible<TestNonSeekableReaderWriter, RelativeSeekableReader&>());
-static_assert(!std::is_convertible<TestNonSeekableReaderWriter, RelativeSeekableWriter&>());
-static_assert(!std::is_convertible<TestNonSeekableReaderWriter, SeekableWriter&>());
-static_assert(!std::is_convertible<TestNonSeekableReaderWriter, SeekableReader&>());
-
-static_assert(std::is_convertible<TestRelativeSeekableReaderWriter, Reader&>());
-static_assert(std::is_convertible<TestRelativeSeekableReaderWriter, Writer&>());
-static_assert(std::is_convertible<TestRelativeSeekableReaderWriter, RelativeSeekableReader&>());
-static_assert(std::is_convertible<TestRelativeSeekableReaderWriter, RelativeSeekableWriter&>());
-static_assert(!std::is_convertible<TestRelativeSeekableReaderWriter, SeekableWriter&>());
-static_assert(!std::is_convertible<TestRelativeSeekableReaderWriter, SeekableReader&>());
-
-static_assert(std::is_convertible<TestSeekableReaderWriter, Reader&>());
-static_assert(std::is_convertible<TestSeekableReaderWriter, Writer&>());
-static_assert(std::is_convertible<TestSeekableReaderWriter, RelativeSeekableReader&>());
-static_assert(std::is_convertible<TestSeekableReaderWriter, RelativeSeekableWriter&>());
-static_assert(std::is_convertible<TestSeekableReaderWriter, SeekableWriter&>());
-static_assert(std::is_convertible<TestSeekableReaderWriter, SeekableReader&>());
-// clang-format on
-
-constexpr uint8_t kSeekable =
-    Stream::kBeginning | Stream::kCurrent | Stream::kEnd;
-constexpr uint8_t kRelativeSeekable = Stream::kCurrent;
-constexpr uint8_t kNonSeekable = 0;
-
-enum Readable : bool { kNonReadable = false, kReadable = true };
-enum Writable : bool { kNonWritable = false, kWritable = true };
-
-template <typename T, Readable readable, Writable writable, uint8_t seekable>
-void TestStreamImpl() {
-  T derived_stream;
-  Stream& stream = derived_stream;
-
-  // Check stream properties
-  ASSERT_EQ(writable, stream.writable());
-  ASSERT_EQ(readable, stream.readable());
-
-  ASSERT_EQ((seekable & Stream::kBeginning) != 0,
-            stream.seekable(Stream::kBeginning));
-  ASSERT_EQ((seekable & Stream::kCurrent) != 0,
-            stream.seekable(Stream::kCurrent));
-  ASSERT_EQ((seekable & Stream::kEnd) != 0, stream.seekable(Stream::kEnd));
-
-  ASSERT_EQ(seekable != kNonSeekable, stream.seekable());
-
-  // Check Read()/Write()/Seek()
-  ASSERT_EQ(readable ? OkStatus() : Status::Unimplemented(),
-            stream.Read({}).status());
-  ASSERT_EQ(writable ? OkStatus() : Status::Unimplemented(), stream.Write({}));
-  ASSERT_EQ(seekable ? OkStatus() : Status::Unimplemented(), stream.Seek(0));
-
-  // Check ConservativeLimits()
-  ASSERT_EQ(readable ? Stream::kUnlimited : 0, stream.ConservativeReadLimit());
-  ASSERT_EQ(writable ? Stream::kUnlimited : 0, stream.ConservativeWriteLimit());
+TEST(Stream, DefaultConservativeWriteLimit) {
+  NullWriter stream;
+  EXPECT_EQ(stream.ConservativeWriteLimit(),
+            std::numeric_limits<size_t>::max());
 }
 
-TEST(Stream, NonSeekableReader) {
-  TestStreamImpl<TestNonSeekableReader,
-                 kReadable,
-                 kNonWritable,
-                 kNonSeekable>();
-}
-
-TEST(Stream, RelativeSeekableReader) {
-  TestStreamImpl<TestRelativeSeekableReader,
-                 kReadable,
-                 kNonWritable,
-                 kRelativeSeekable>();
-}
-
-TEST(Stream, SeekableReader) {
-  TestStreamImpl<TestSeekableReader, kReadable, kNonWritable, kSeekable>();
-}
-
-TEST(Stream, NonSeekableWriter) {
-  TestStreamImpl<TestNonSeekableWriter,
-                 kNonReadable,
-                 kWritable,
-                 kNonSeekable>();
-}
-
-TEST(Stream, RelativeSeekableWriter) {
-  TestStreamImpl<TestRelativeSeekableWriter,
-                 kNonReadable,
-                 kWritable,
-                 kRelativeSeekable>();
-}
-
-TEST(Stream, SeekableWriter) {
-  TestStreamImpl<TestSeekableWriter, kNonReadable, kWritable, kSeekable>();
-}
-
-TEST(Stream, NonSeekableReaderWriter) {
-  TestStreamImpl<TestNonSeekableReaderWriter,
-                 kReadable,
-                 kWritable,
-                 kNonSeekable>();
-}
-
-TEST(Stream, RelativeSeekableReaderWriter) {
-  TestStreamImpl<TestRelativeSeekableReaderWriter,
-                 kReadable,
-                 kWritable,
-                 kRelativeSeekable>();
-}
-
-TEST(Stream, SeekableReaderWriter) {
-  TestStreamImpl<TestSeekableReaderWriter, kReadable, kWritable, kSeekable>();
-}
-
-TEST(NullStream, DefaultConservativeWriteLimit) {
-  NullStream stream;
-  EXPECT_EQ(stream.ConservativeWriteLimit(), Stream::kUnlimited);
-}
-
-TEST(NullStream, DefaultConservativeReadLimit) {
-  NullStream stream;
-  EXPECT_EQ(stream.ConservativeReadLimit(), Stream::kUnlimited);
-}
-
-TEST(NullStream, DefaultConservativeReadWriteLimit) {
-  NullStream stream;
-  EXPECT_EQ(stream.ConservativeWriteLimit(), Stream::kUnlimited);
-  EXPECT_EQ(stream.ConservativeReadLimit(), Stream::kUnlimited);
-}
-
-TEST(NullStream, DefaultTell) {
-  NullStream stream;
-  EXPECT_EQ(stream.Tell(), Stream::kUnknownPosition);
+TEST(Stream, DefaultConservativeReadLimit) {
+  NullReader stream;
+  EXPECT_EQ(stream.ConservativeReadLimit(), std::numeric_limits<size_t>::max());
 }
 
 }  // namespace
diff --git a/pw_string/BUILD b/pw_string/BUILD
new file mode 100644
index 0000000..0347936
--- /dev/null
+++ b/pw_string/BUILD
@@ -0,0 +1,92 @@
+# Copyright 2019 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_string",
+    srcs = [
+        "format.cc",
+        "string_builder.cc",
+        "type_to_string.cc",
+    ],
+    hdrs = [
+        "public/pw_string/format.h",
+        "public/pw_string/string_builder.h",
+        "public/pw_string/to_string.h",
+        "public/pw_string/type_to_string.h",
+        "public/pw_string/util.h",
+    ],
+    includes = ["public"],
+    deps = [
+        "//pw_preprocessor",
+        "//pw_span",
+        "//pw_status",
+    ],
+)
+
+pw_cc_test(
+    name = "format_test",
+    srcs = ["format_test.cc"],
+    deps = [
+        ":pw_string",
+        "//pw_span",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "type_to_string_test",
+    srcs = ["type_to_string_test.cc"],
+    deps = [
+        ":pw_string",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "string_builder_test",
+    srcs = ["string_builder_test.cc"],
+    deps = [
+        ":pw_string",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "to_string_test",
+    srcs = ["to_string_test.cc"],
+    deps = [
+        ":pw_string",
+        "//pw_status",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "util_test",
+    srcs = ["util_test.cc"],
+    deps = [
+        ":pw_string",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_string/BUILD.bazel b/pw_string/BUILD.bazel
deleted file mode 100644
index 663bb4f..0000000
--- a/pw_string/BUILD.bazel
+++ /dev/null
@@ -1,95 +0,0 @@
-# Copyright 2019 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_string",
-    srcs = [
-        "format.cc",
-        "string_builder.cc",
-        "type_to_string.cc",
-    ],
-    hdrs = [
-        "public/pw_string/format.h",
-        "public/pw_string/internal/length.h",
-        "public/pw_string/string_builder.h",
-        "public/pw_string/to_string.h",
-        "public/pw_string/type_to_string.h",
-        "public/pw_string/util.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_assert:facade",
-        "//pw_preprocessor",
-        "//pw_result",
-        "//pw_span",
-        "//pw_status",
-    ],
-)
-
-pw_cc_test(
-    name = "format_test",
-    srcs = ["format_test.cc"],
-    deps = [
-        ":pw_string",
-        "//pw_span",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "type_to_string_test",
-    srcs = ["type_to_string_test.cc"],
-    deps = [
-        ":pw_string",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "string_builder_test",
-    srcs = ["string_builder_test.cc"],
-    deps = [
-        ":pw_string",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "to_string_test",
-    srcs = ["to_string_test.cc"],
-    deps = [
-        ":pw_string",
-        "//pw_status",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "util_test",
-    srcs = ["util_test.cc"],
-    deps = [
-        ":pw_string",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_string/BUILD.gn b/pw_string/BUILD.gn
index 0742938..bf59ff2 100644
--- a/pw_string/BUILD.gn
+++ b/pw_string/BUILD.gn
@@ -19,15 +19,14 @@
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_unit_test/test.gni")
 
-config("public_include_path") {
+config("default_config") {
   include_dirs = [ "public" ]
 }
 
 pw_source_set("pw_string") {
-  public_configs = [ ":public_include_path" ]
+  public_configs = [ ":default_config" ]
   public = [
     "public/pw_string/format.h",
-    "public/pw_string/internal/length.h",
     "public/pw_string/string_builder.h",
     "public/pw_string/to_string.h",
     "public/pw_string/type_to_string.h",
@@ -39,9 +38,7 @@
     "type_to_string.cc",
   ]
   public_deps = [
-    "$dir_pw_assert",
     "$dir_pw_preprocessor",
-    "$dir_pw_result",
     "$dir_pw_status",
   ]
 }
diff --git a/pw_string/CMakeLists.txt b/pw_string/CMakeLists.txt
index 6103ddc..92a14a4 100644
--- a/pw_string/CMakeLists.txt
+++ b/pw_string/CMakeLists.txt
@@ -14,77 +14,9 @@
 
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
 
-pw_add_module_library(pw_string
-  HEADERS
-    public/pw_string/format.h
-    public/pw_string/internal/length.h
-    public/pw_string/string_builder.h
-    public/pw_string/to_string.h
-    public/pw_string/type_to_string.h
-    public/pw_string/util.h
-  PUBLIC_INCLUDES
-    public
+pw_auto_add_simple_module(pw_string
   PUBLIC_DEPS
-    pw_assert
-    pw_polyfill.span
     pw_preprocessor
-    pw_result
+    pw_span
     pw_status
-  SOURCES
-    format.cc
-    string_builder.cc
-    type_to_string.cc
-)
-if(Zephyr_FOUND AND CONFIG_PIGWEED_STRING)
-  zephyr_link_libraries(pw_string)
-endif()
-
-pw_add_test(pw_string.format_test
-  SOURCES
-    format_test.cc
-  DEPS
-    pw_string
-  GROUPS
-    modules
-    pw_string
-)
-
-pw_add_test(pw_string.string_builder_test
-  SOURCES
-    string_builder_test.cc
-  DEPS
-    pw_string
-  GROUPS
-    modules
-    pw_string
-)
-
-pw_add_test(pw_string.to_string_test
-  SOURCES
-    to_string_test.cc
-  DEPS
-    pw_string
-  GROUPS
-    modules
-    pw_string
-)
-
-pw_add_test(pw_string.type_to_string_test
-  SOURCES
-    type_to_string_test.cc
-  DEPS
-    pw_string
-  GROUPS
-    modules
-    pw_string
-)
-
-pw_add_test(pw_string.util_test
-  SOURCES
-    util_test.cc
-  DEPS
-    pw_string
-  GROUPS
-    modules
-    pw_string
 )
diff --git a/pw_string/Kconfig b/pw_string/Kconfig
deleted file mode 100644
index fffa12a..0000000
--- a/pw_string/Kconfig
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-config PIGWEED_STRING
-    bool "Enable Pigweed string library (pw_string)"
-    select PIGWEED_ASSERT
-    select PIGWEED_PREPROCESSOR
-    select PIGWEED_RESULT
-    select PIGWEED_SPAN
-    select PIGWEED_STATUS
diff --git a/pw_string/OWNERS b/pw_string/OWNERS
deleted file mode 100644
index e9a6c58..0000000
--- a/pw_string/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-ewout@google.com
-hepler@google.com
diff --git a/pw_string/docs.rst b/pw_string/docs.rst
index c44f580..467908b 100644
--- a/pw_string/docs.rst
+++ b/pw_string/docs.rst
@@ -1,8 +1,8 @@
 .. _module-pw_string:
 
-=========
+---------
 pw_string
-=========
+---------
 String manipulation is a very common operation, but the standard C and C++
 string libraries have drawbacks. The C++ functions are easy-to-use and powerful,
 but require too much flash and memory for many embedded projects. The C string
@@ -15,14 +15,10 @@
 functions eliminates issues related to buffer overflow or missing null
 terminators.
 
--------------
 Compatibility
--------------
+=============
 C++17
 
------
-Usage
------
 pw::string::Format
 ==================
 The ``pw::string::Format`` and ``pw::string::FormatVaList`` functions provide
@@ -38,92 +34,12 @@
 
 .. include:: format_size_report
 
-Safe Length Checking
-====================
-This module provides two safer alternatives to ``std::strlen`` in case the
-string is extremely long and/or potentially not null-terminated.
-
-First, a constexpr alternative to C11's ``strnlen_s`` is offerred through
-:cpp:func:`pw::string::ClampedCString`. This does not return a length by
-design and instead returns a string_view which does not require
-null-termination.
-
-Second, a constexpr specialized form is offered where null termination is
-required through :cpp:func:`pw::string::NullTerminatedLength`. This will only
-return a length if the string is null-terminated.
-
-.. cpp:function:: constexpr std::string_view pw::string::ClampedCString(std::span<const char> str)
-.. cpp:function:: constexpr std::string_view pw::string::ClampedCString(const char* str, size_t max_len)
-
-   Safe alternative to the string_view constructor to avoid the risk of an
-   unbounded implicit or explicit use of strlen.
-
-   This is strongly recommended over using something like C11's strnlen_s as
-   a string_view does not require null-termination.
-
-.. cpp:function:: constexpr pw::Result<size_t> pw::string::NullTerminatedLength(std::span<const char> str)
-.. cpp:function:: pw::Result<size_t> pw::string::NullTerminatedLength(const char* str, size_t max_len)
-
-   Safe alternative to strlen to calculate the null-terminated length of the
-   string within the specified span, excluding the null terminator. Like C11's
-   strnlen_s, the scan for the null-terminator is bounded.
-
-   Returns:
-     null-terminated length of the string excluding the null terminator.
-     OutOfRange - if the string is not null-terminated.
-
-   Precondition: The string shall be at a valid pointer.
-
-pw::string::Copy
-================
-The ``pw::string::Copy`` functions provide a safer alternative to
-``std::strncpy`` as it always null-terminates whenever the destination
-buffer has a non-zero size.
-
-.. cpp:function:: StatusWithSize Copy(const std::string_view& source, std::span<char> dest)
-.. cpp:function:: StatusWithSize Copy(const char* source, std::span<char> dest)
-.. cpp:function:: StatusWithSize Copy(const char* source, char* dest, size_t num)
-
-   Copies the source string to the dest, truncating if the full string does not
-   fit. Always null terminates if dest.size() or num > 0.
-
-   Returns the number of characters written, excluding the null terminator. If
-   the string is truncated, the status is ResourceExhausted.
-
-   Precondition: The destination and source shall not overlap.
-   Precondition: The source shall be a valid pointer.
-
 pw::StringBuilder
 =================
 ``pw::StringBuilder`` facilitates building formatted strings in a fixed-size
 buffer. It is designed to give the flexibility of ``std::string`` and
 ``std::ostringstream``, but with a small footprint.
 
-.. code-block:: cpp
-
-  #include "pw_log/log.h"
-  #include "pw_string/string_builder.h"
-
-  pw::Status LogProducedData(std::string_view func_name,
-                             std::span<const std::byte> data) {
-    pw::StringBuffer<42> sb;
-
-    // Append a std::string_view to the buffer.
-    sb << func_name;
-
-    // Append a format string to the buffer.
-    sb.Format(" produced %d bytes of data: ", static_cast<int>(data.data()));
-
-    // Append bytes as hex to the buffer.
-    sb << data;
-
-    // Log the final string.
-    PW_LOG_DEBUG("%s", sb.c_str());
-
-    // Errors encountered while mutating the string builder are tracked.
-    return sb.status();
-  }
-
 Supporting custom types with StringBuilder
 ------------------------------------------
 As with ``std::ostream``, StringBuilder supports printing custom types by
@@ -158,7 +74,7 @@
 
   template <>
   StatusWithSize ToString<MyStatus>(MyStatus value, std::span<char> buffer) {
-    return Copy(MyStatusString(value), buffer);
+    return CopyString(MyStatusString(value), buffer);
   }
 
   }  // namespace pw
@@ -181,14 +97,8 @@
 
 .. include:: string_builder_size_report
 
------------
 Future work
------------
+===========
 * StringBuilder's fixed size cost can be dramatically reduced by limiting
   support for 64-bit integers.
 * Consider integrating with the tokenizer module.
-
-Zephyr
-======
-To enable ``pw_string`` for Zephyr add ``CONFIG_PIGWEED_STRING=y`` to the
-project's configuration.
diff --git a/pw_string/public/pw_string/internal/length.h b/pw_string/public/pw_string/internal/length.h
deleted file mode 100644
index a338517..0000000
--- a/pw_string/public/pw_string/internal/length.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstddef>
-
-namespace pw {
-namespace string {
-namespace internal {
-
-// Calculates the length of a null-terminated string up to the specified maximum
-// length. If str is nullptr, returns 0.
-//
-// This function is a constexpr version of C11's strnlen_s.
-//
-// WARNING: this will return a length even if the string is NOT null-terminated!
-// ClampedCString and NullTerminatedLength are recommended which are built on
-// top of this.
-constexpr size_t ClampedLength(const char* str, size_t max_len) {
-  size_t length = 0;
-
-  if (str != nullptr) {
-    for (; length < max_len; ++length) {
-      if (str[length] == '\0') {
-        break;
-      }
-    }
-  }
-
-  return length;
-}
-
-}  // namespace internal
-}  // namespace string
-}  // namespace pw
diff --git a/pw_string/public/pw_string/string_builder.h b/pw_string/public/pw_string/string_builder.h
index 220abf1..c85e6be 100644
--- a/pw_string/public/pw_string/string_builder.h
+++ b/pw_string/public/pw_string/string_builder.h
@@ -65,7 +65,7 @@
 //
 //   template <>
 //   StatusWithSize ToString<MyStatus>(MyStatus value, std::span<char> buffer) {
-//     return Copy(MyStatusString(value), buffer);
+//     return CopyString(MyStatusString(value), buffer);
 //   }
 //
 //   }  // namespace pw
@@ -190,8 +190,6 @@
     // gives smaller code size.
     if constexpr (std::is_convertible_v<T, std::string_view>) {
       append(value);
-    } else if constexpr (std::is_convertible_v<T, std::span<const std::byte>>) {
-      WriteBytes(value);
     } else {
       HandleStatusWithSize(ToString(value, buffer_.subspan(size_)));
     }
@@ -243,8 +241,6 @@
   void CopySizeAndStatus(const StringBuilder& other);
 
  private:
-  void WriteBytes(std::span<const std::byte> data);
-
   size_t ResizeAndTerminate(size_t chars_to_append);
 
   void HandleStatusWithSize(StatusWithSize written);
diff --git a/pw_string/public/pw_string/to_string.h b/pw_string/public/pw_string/to_string.h
index 1979375..463005f 100644
--- a/pw_string/public/pw_string/to_string.h
+++ b/pw_string/public/pw_string/to_string.h
@@ -68,7 +68,7 @@
   if constexpr (std::is_same_v<std::remove_cv_t<T>, bool>) {
     return string::BoolToString(value, buffer);
   } else if constexpr (std::is_same_v<std::remove_cv_t<T>, char>) {
-    return string::Copy(std::string_view(&value, 1), buffer);
+    return string::CopyString(std::string_view(&value, 1), buffer);
   } else if constexpr (std::is_integral_v<T>) {
     return string::IntToString(value, buffer);
   } else if constexpr (std::is_enum_v<T>) {
@@ -76,7 +76,7 @@
   } else if constexpr (std::is_floating_point_v<T>) {
     return string::FloatAsIntToString(value, buffer);
   } else if constexpr (std::is_convertible_v<T, std::string_view>) {
-    return string::CopyStringOrNull(value, buffer);
+    return string::CopyString(value, buffer);
   } else if constexpr (std::is_pointer_v<std::remove_cv_t<T>> ||
                        std::is_null_pointer_v<T>) {
     return string::PointerToString(value, buffer);
@@ -89,7 +89,7 @@
 // ToString overloads for Pigweed types. To override ToString for a custom type,
 // specialize the ToString template function.
 inline StatusWithSize ToString(Status status, std::span<char> buffer) {
-  return string::Copy(status.str(), buffer);
+  return string::CopyString(status.str(), buffer);
 }
 
 inline StatusWithSize ToString(pw_Status status, std::span<char> buffer) {
@@ -97,7 +97,7 @@
 }
 
 inline StatusWithSize ToString(std::byte byte, std::span<char> buffer) {
-  return string::IntToHexString(static_cast<unsigned>(byte), buffer, 2);
+  return string::IntToHexString(static_cast<unsigned>(byte), buffer);
 }
 
 }  // namespace pw
diff --git a/pw_string/public/pw_string/type_to_string.h b/pw_string/public/pw_string/type_to_string.h
index 9c32120..2841a60 100644
--- a/pw_string/public/pw_string/type_to_string.h
+++ b/pw_string/public/pw_string/type_to_string.h
@@ -23,7 +23,6 @@
 #include <type_traits>
 
 #include "pw_status/status_with_size.h"
-#include "pw_string/util.h"
 
 namespace pw::string {
 
@@ -108,46 +107,36 @@
 // CopyEntireString.
 StatusWithSize PointerToString(const void* pointer, std::span<char> buffer);
 
-// Specialized form of pw::string::Copy which supports nullptr values.
-//
 // Copies the string to the buffer, truncating if the full string does not fit.
 // Always null terminates if buffer.size() > 0.
 //
-// If value is a nullptr, then "(null)" is used as a fallback.
-//
 // Returns the number of characters written, excluding the null terminator. If
 // the string is truncated, the status is RESOURCE_EXHAUSTED.
-inline StatusWithSize CopyStringOrNull(const std::string_view& value,
-                                       std::span<char> buffer) {
-  return Copy(value, buffer);
-}
-inline StatusWithSize CopyStringOrNull(const char* value,
-                                       std::span<char> buffer) {
+StatusWithSize CopyString(const std::string_view& value,
+                          std::span<char> buffer);
+
+inline StatusWithSize CopyString(const char* value, std::span<char> buffer) {
   if (value == nullptr) {
     return PointerToString(value, buffer);
   }
-  return Copy(value, buffer);
+  return CopyString(std::string_view(value), buffer);
 }
 
 // Copies the string to the buffer, if the entire string fits. Always null
 // terminates if buffer.size() > 0.
 //
-// If value is a nullptr, then "(null)" is used as a fallback.
-//
 // Returns the number of characters written, excluding the null terminator. If
 // the full string does not fit, only a null terminator is written and the
 // status is RESOURCE_EXHAUSTED.
-StatusWithSize CopyEntireStringOrNull(const std::string_view& value,
-                                      std::span<char> buffer);
+StatusWithSize CopyEntireString(const std::string_view& value,
+                                std::span<char> buffer);
 
-// Same as the string_view form of CopyEntireString, except that if value is a
-// nullptr, then "(null)" is used as a fallback.
-inline StatusWithSize CopyEntireStringOrNull(const char* value,
-                                             std::span<char> buffer) {
+inline StatusWithSize CopyEntireString(const char* value,
+                                       std::span<char> buffer) {
   if (value == nullptr) {
     return PointerToString(value, buffer);
   }
-  return CopyEntireStringOrNull(std::string_view(value), buffer);
+  return CopyEntireString(std::string_view(value), buffer);
 }
 
 // This function is a fallback that is called if by ToString if no overload
diff --git a/pw_string/public/pw_string/util.h b/pw_string/public/pw_string/util.h
index 0b81867..37c6c0b 100644
--- a/pw_string/public/pw_string/util.h
+++ b/pw_string/public/pw_string/util.h
@@ -1,4 +1,4 @@
-// Copyright 2021 The Pigweed Authors
+// Copyright 2019 The Pigweed Authors
 //
 // 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
@@ -14,90 +14,27 @@
 #pragma once
 
 #include <cstddef>
-#include <span>
-#include <string_view>
-
-#include "pw_assert/assert.h"
-#include "pw_result/result.h"
-#include "pw_status/status.h"
-#include "pw_status/status_with_size.h"
-#include "pw_string/internal/length.h"
 
 namespace pw {
 namespace string {
 
-// Safe alternative to the string_view constructor to avoid the risk of an
-// unbounded implicit or explicit use of strlen.
+// Calculates the length of a null-terminated string up to the specified maximum
+// length. If str is nullptr, returns 0.
 //
-// This is strongly recommended over using something like C11's strnlen_s as
-// a string_view does not require null-termination.
-constexpr std::string_view ClampedCString(std::span<const char> str) {
-  return std::string_view(str.data(),
-                          internal::ClampedLength(str.data(), str.size()));
-}
+// This function is a constexpr version of C11's strnlen_s.
+constexpr size_t Length(const char* str, size_t max_len) {
+  size_t length = 0;
 
-constexpr std::string_view ClampedCString(const char* str, size_t max_len) {
-  return ClampedCString(std::span<const char>(str, max_len));
-}
-
-// Safe alternative to strlen to calculate the null-terminated length of the
-// string within the specified span, excluding the null terminator. Like C11's
-// strnlen_s, the scan for the null-terminator is bounded.
-//
-// Returns:
-//   null-terminated length of the string excluding the null terminator.
-//   OutOfRange - if the string is not null-terminated.
-//
-// Precondition: The string shall be at a valid pointer.
-constexpr pw::Result<size_t> NullTerminatedLength(std::span<const char> str) {
-  PW_DASSERT(str.data() != nullptr);
-
-  const size_t length = internal::ClampedLength(str.data(), str.size());
-  if (length == str.size()) {
-    return Status::OutOfRange();
+  if (str != nullptr) {
+    for (; length < max_len; ++length) {
+      if (str[length] == '\0') {
+        break;
+      }
+    }
   }
 
   return length;
 }
 
-constexpr pw::Result<size_t> NullTerminatedLength(const char* str,
-                                                  size_t max_len) {
-  return NullTerminatedLength(std::span<const char>(str, max_len));
-}
-
-// Copies the source string to the dest, truncating if the full string does not
-// fit. Always null terminates if dest.size() or num > 0.
-//
-// Returns the number of characters written, excluding the null terminator. If
-// the string is truncated, the status is ResourceExhausted.
-//
-// Precondition: The destination and source shall not overlap.
-// Precondition: The source shall be a valid pointer.
-PW_CONSTEXPR_CPP20 inline StatusWithSize Copy(const std::string_view& source,
-                                              std::span<char> dest) {
-  if (dest.empty()) {
-    return StatusWithSize::ResourceExhausted();
-  }
-
-  const size_t copied = source.copy(dest.data(), dest.size() - 1);
-  dest[copied] = '\0';
-
-  return StatusWithSize(
-      copied == source.size() ? OkStatus() : Status::ResourceExhausted(),
-      copied);
-}
-
-PW_CONSTEXPR_CPP20 inline StatusWithSize Copy(const char* source,
-                                              std::span<char> dest) {
-  PW_DASSERT(source != nullptr);
-  return Copy(ClampedCString(source, dest.size()), dest);
-}
-
-PW_CONSTEXPR_CPP20 inline StatusWithSize Copy(const char* source,
-                                              char* dest,
-                                              size_t num) {
-  return Copy(source, std::span<char>(dest, num));
-}
-
 }  // namespace string
 }  // namespace pw
diff --git a/pw_string/size_report/BUILD b/pw_string/size_report/BUILD
new file mode 100644
index 0000000..7c6b474
--- /dev/null
+++ b/pw_string/size_report/BUILD
@@ -0,0 +1,154 @@
+# Copyright 2019 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_binary",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_binary(
+    name = "single_write_snprintf",
+    srcs = ["format_single.cc"],
+    copts = ["-DUSE_FORMAT=0"],
+    deps = [
+        "//pw_bloat:bloat_this_binary",
+        "//pw_string",
+    ],
+)
+
+pw_cc_binary(
+    name = "single_write_format",
+    srcs = ["format_single.cc"],
+    copts = ["-DUSE_FORMAT=1"],
+    deps = [
+        "//pw_bloat:bloat_this_binary",
+        "//pw_string",
+    ],
+)
+
+pw_cc_binary(
+    name = "multiple_writes_snprintf",
+    srcs = ["format_multiple.cc"],
+    copts = ["-DUSE_FORMAT=0"],
+    deps = [
+        "//pw_bloat:bloat_this_binary",
+        "//pw_string",
+    ],
+)
+
+pw_cc_binary(
+    name = "multiple_writes_format",
+    srcs = ["format_multiple.cc"],
+    copts = ["-DUSE_FORMAT=1"],
+    deps = [
+        "//pw_bloat:bloat_this_binary",
+        "//pw_string",
+    ],
+)
+
+pw_cc_binary(
+    name = "many_writes_snprintf",
+    srcs = ["format_many_without_error_handling.cc"],
+    copts = ["-DUSE_FORMAT=0"],
+    deps = [
+        "//pw_bloat:bloat_this_binary",
+        "//pw_string",
+    ],
+)
+
+pw_cc_binary(
+    name = "many_writes_format",
+    srcs = ["format_many_without_error_handling.cc"],
+    copts = ["-DUSE_FORMAT=1"],
+    deps = [
+        "//pw_bloat:bloat_this_binary",
+        "//pw_string",
+    ],
+)
+
+pw_cc_binary(
+    name = "build_string_with_snprintf_no_base_snprintf",
+    srcs = ["string_builder_size_report.cc"],
+    copts = [
+        "-DUSE_STRING_BUILDER=0",
+        "-DPROVIDE_BASE_SNPRINTF=0",
+    ],
+    deps = [
+        "//pw_bloat:bloat_this_binary",
+        "//pw_string",
+    ],
+)
+
+pw_cc_binary(
+    name = "build_string_with_string_builder_no_base_snprintf",
+    srcs = ["string_builder_size_report.cc"],
+    copts = [
+        "-DUSE_STRING_BUILDER=1",
+        "-DPROVIDE_BASE_SNPRINTF=0",
+    ],
+    deps = [
+        "//pw_bloat:bloat_this_binary",
+        "//pw_string",
+    ],
+)
+
+pw_cc_binary(
+    name = "build_string_with_snprintf",
+    srcs = ["string_builder_size_report.cc"],
+    copts = [
+        "-DUSE_STRING_BUILDER=0",
+        "-DPROVIDE_BASE_SNPRINTF=1",
+    ],
+    deps = [
+        "//pw_bloat:bloat_this_binary",
+        "//pw_string",
+    ],
+)
+
+pw_cc_binary(
+    name = "build_string_with_string_builder",
+    srcs = ["string_builder_size_report.cc"],
+    copts = [
+        "-DUSE_STRING_BUILDER=1",
+        "-DPROVIDE_BASE_SNPRINTF=1",
+    ],
+    deps = [
+        "//pw_bloat:bloat_this_binary",
+        "//pw_string",
+    ],
+)
+
+pw_cc_binary(
+    name = "build_string_incremental_with_snprintf",
+    srcs = ["string_builder_size_report_incremental.cc"],
+    copts = ["-DUSE_STRING_BUILDER=0"],
+    deps = [
+        "//pw_bloat:bloat_this_binary",
+        "//pw_string",
+    ],
+)
+
+pw_cc_binary(
+    name = "build_string_incremental_with_string_builder",
+    srcs = ["string_builder_size_report_incremental.cc"],
+    copts = ["-DUSE_STRING_BUILDER=1"],
+    deps = [
+        "//pw_bloat:bloat_this_binary",
+        "//pw_string",
+    ],
+)
diff --git a/pw_string/size_report/BUILD.bazel b/pw_string/size_report/BUILD.bazel
deleted file mode 100644
index 21c7737..0000000
--- a/pw_string/size_report/BUILD.bazel
+++ /dev/null
@@ -1,154 +0,0 @@
-# Copyright 2019 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_binary",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_binary(
-    name = "single_write_snprintf",
-    srcs = ["format_single.cc"],
-    copts = ["-DUSE_FORMAT=0"],
-    deps = [
-        "//pw_bloat:bloat_this_binary",
-        "//pw_string",
-    ],
-)
-
-pw_cc_binary(
-    name = "single_write_format",
-    srcs = ["format_single.cc"],
-    copts = ["-DUSE_FORMAT=1"],
-    deps = [
-        "//pw_bloat:bloat_this_binary",
-        "//pw_string",
-    ],
-)
-
-pw_cc_binary(
-    name = "multiple_writes_snprintf",
-    srcs = ["format_multiple.cc"],
-    copts = ["-DUSE_FORMAT=0"],
-    deps = [
-        "//pw_bloat:bloat_this_binary",
-        "//pw_string",
-    ],
-)
-
-pw_cc_binary(
-    name = "multiple_writes_format",
-    srcs = ["format_multiple.cc"],
-    copts = ["-DUSE_FORMAT=1"],
-    deps = [
-        "//pw_bloat:bloat_this_binary",
-        "//pw_string",
-    ],
-)
-
-pw_cc_binary(
-    name = "many_writes_snprintf",
-    srcs = ["format_many_without_error_handling.cc"],
-    copts = ["-DUSE_FORMAT=0"],
-    deps = [
-        "//pw_bloat:bloat_this_binary",
-        "//pw_string",
-    ],
-)
-
-pw_cc_binary(
-    name = "many_writes_format",
-    srcs = ["format_many_without_error_handling.cc"],
-    copts = ["-DUSE_FORMAT=1"],
-    deps = [
-        "//pw_bloat:bloat_this_binary",
-        "//pw_string",
-    ],
-)
-
-pw_cc_binary(
-    name = "build_string_with_snprintf_no_base_snprintf",
-    srcs = ["string_builder_size_report.cc"],
-    copts = [
-        "-DUSE_STRING_BUILDER=0",
-        "-DPROVIDE_BASE_SNPRINTF=0",
-    ],
-    deps = [
-        "//pw_bloat:bloat_this_binary",
-        "//pw_string",
-    ],
-)
-
-pw_cc_binary(
-    name = "build_string_with_string_builder_no_base_snprintf",
-    srcs = ["string_builder_size_report.cc"],
-    copts = [
-        "-DUSE_STRING_BUILDER=1",
-        "-DPROVIDE_BASE_SNPRINTF=0",
-    ],
-    deps = [
-        "//pw_bloat:bloat_this_binary",
-        "//pw_string",
-    ],
-)
-
-pw_cc_binary(
-    name = "build_string_with_snprintf",
-    srcs = ["string_builder_size_report.cc"],
-    copts = [
-        "-DUSE_STRING_BUILDER=0",
-        "-DPROVIDE_BASE_SNPRINTF=1",
-    ],
-    deps = [
-        "//pw_bloat:bloat_this_binary",
-        "//pw_string",
-    ],
-)
-
-pw_cc_binary(
-    name = "build_string_with_string_builder",
-    srcs = ["string_builder_size_report.cc"],
-    copts = [
-        "-DUSE_STRING_BUILDER=1",
-        "-DPROVIDE_BASE_SNPRINTF=1",
-    ],
-    deps = [
-        "//pw_bloat:bloat_this_binary",
-        "//pw_string",
-    ],
-)
-
-pw_cc_binary(
-    name = "build_string_incremental_with_snprintf",
-    srcs = ["string_builder_size_report_incremental.cc"],
-    copts = ["-DUSE_STRING_BUILDER=0"],
-    deps = [
-        "//pw_bloat:bloat_this_binary",
-        "//pw_string",
-    ],
-)
-
-pw_cc_binary(
-    name = "build_string_incremental_with_string_builder",
-    srcs = ["string_builder_size_report_incremental.cc"],
-    copts = ["-DUSE_STRING_BUILDER=1"],
-    deps = [
-        "//pw_bloat:bloat_this_binary",
-        "//pw_string",
-    ],
-)
diff --git a/pw_string/size_report/format_many_without_error_handling.cc b/pw_string/size_report/format_many_without_error_handling.cc
index 915e935..7a5ab2d 100644
--- a/pw_string/size_report/format_many_without_error_handling.cc
+++ b/pw_string/size_report/format_many_without_error_handling.cc
@@ -26,9 +26,7 @@
 
 #include "pw_string/format.h"
 
-#define FORMAT_CASE(...)                  \
-  pw::string::Format(buffer, __VA_ARGS__) \
-      .IgnoreError()  // TODO(pwbug/387): Handle Status properly
+#define FORMAT_CASE(...) pw::string::Format(buffer, __VA_ARGS__)
 
 #else  // std::snprintf
 
diff --git a/pw_string/size_report/format_single.cc b/pw_string/size_report/format_single.cc
index d3ea99a..6afee11 100644
--- a/pw_string/size_report/format_single.cc
+++ b/pw_string/size_report/format_single.cc
@@ -49,7 +49,7 @@
                 get_buffer_2,
                 get_size)
       .size();
-#else   // std::snprintf
+#else  // std::snprintf
   if (buffer_size == 0u) {
     return 0;
   }
diff --git a/pw_string/string_builder.cc b/pw_string/string_builder.cc
index 9317cc2..60879a9 100644
--- a/pw_string/string_builder.cc
+++ b/pw_string/string_builder.cc
@@ -29,14 +29,14 @@
 }
 
 StringBuilder& StringBuilder::append(size_t count, char ch) {
-  char* const append_destination = buffer_.data() + size_;
-  std::fill_n(append_destination, ResizeAndTerminate(count), ch);
+  char* const append_destination = &buffer_[size_];
+  std::memset(append_destination, ch, ResizeAndTerminate(count));
   return *this;
 }
 
 StringBuilder& StringBuilder::append(const char* str, size_t count) {
-  char* const append_destination = buffer_.data() + size_;
-  std::copy_n(str, ResizeAndTerminate(count), append_destination);
+  char* const append_destination = &buffer_[size_];
+  std::memcpy(append_destination, str, ResizeAndTerminate(count));
   return *this;
 }
 
@@ -44,7 +44,7 @@
   // Use buffer_.size() - size() as the maximum length so that strings too long
   // to fit in the buffer will request one character too many, which sets the
   // status to RESOURCE_EXHAUSTED.
-  return append(string::ClampedCString(str, buffer_.size() - size()));
+  return append(str, string::Length(str, buffer_.size() - size()));
 }
 
 StringBuilder& StringBuilder::append(const std::string_view& str) {
@@ -100,16 +100,6 @@
   return *this;
 }
 
-void StringBuilder::WriteBytes(std::span<const std::byte> data) {
-  if (size() + data.size() * 2 > max_size()) {
-    SetErrorStatus(Status::ResourceExhausted());
-  } else {
-    for (std::byte val : data) {
-      *this << val;
-    }
-  }
-}
-
 void StringBuilder::CopySizeAndStatus(const StringBuilder& other) {
   size_ = other.size_;
   status_ = other.status_;
diff --git a/pw_string/string_builder_test.cc b/pw_string/string_builder_test.cc
index 1e0dc09..0b092ad 100644
--- a/pw_string/string_builder_test.cc
+++ b/pw_string/string_builder_test.cc
@@ -203,12 +203,6 @@
   EXPECT_STREQ("???????", sb.data());
 }
 
-TEST(StringBuilder, Append_Chars_ToEmpty) {
-  StringBuilder sb(std::span<char>{});
-
-  EXPECT_EQ(Status::ResourceExhausted(), sb.append(1, '?').last_status());
-}
-
 TEST(StringBuilder, Append_PartialCString) {
   StringBuffer<12> sb;
   EXPECT_TRUE(sb.append("123456", 4).ok());
@@ -379,35 +373,6 @@
   EXPECT_STREQ("hi!", buffer.data());
 }
 
-TEST(StringBuilder, StreamOutput_ByteArray) {
-  StringBuffer<7> buffer;
-  std::array<std::byte, 3> data{
-      {std::byte(0xc8), std::byte(0x02), std::byte(0x41)}};
-  buffer << data;
-  EXPECT_EQ(buffer.status(), OkStatus());
-  EXPECT_STREQ("c80241", buffer.data());
-}
-
-TEST(StringBuilder, StreamOutput_ByteSpan) {
-  StringBuffer<11> buffer;
-  std::array<std::byte, 5> data{{std::byte(0),
-                                 std::byte(0xc8),
-                                 std::byte(0x02),
-                                 std::byte(0x41),
-                                 std::byte(0xe0)}};
-  buffer << std::as_bytes(std::span(data));
-  EXPECT_EQ(buffer.status(), OkStatus());
-  EXPECT_STREQ("00c80241e0", buffer.data());
-}
-
-TEST(StringBuilder, StreamOutput_ByteSpanOutOfSpace) {
-  StringBuffer<4> buffer;
-  std::array<uint8_t, 3> data{{0xc8, 0x02, 0x41}};
-  buffer << std::as_bytes(std::span(data));
-  EXPECT_EQ(buffer.status(), Status::ResourceExhausted());
-  EXPECT_STREQ("", buffer.data());
-}
-
 TEST(StringBuffer, Assign) {
   StringBuffer<10> one;
   StringBuffer<10> two;
diff --git a/pw_string/type_to_string.cc b/pw_string/type_to_string.cc
index 8b70dc3..4872504 100644
--- a/pw_string/type_to_string.cc
+++ b/pw_string/type_to_string.cc
@@ -153,8 +153,7 @@
   if (std::isfinite(value) &&
       std::abs(value) <
           static_cast<float>(std::numeric_limits<int64_t>::max())) {
-    return IntToString<int64_t>(static_cast<int64_t>(std::roundf(value)),
-                                buffer);
+    return IntToString<int64_t>(std::round(value), buffer);
   }
 
   // Otherwise, print inf or NaN, if they fit.
@@ -171,18 +170,32 @@
 }
 
 StatusWithSize BoolToString(bool value, std::span<char> buffer) {
-  return CopyEntireStringOrNull(value ? "true" : "false", buffer);
+  return CopyEntireString(value ? "true" : "false", buffer);
 }
 
 StatusWithSize PointerToString(const void* pointer, std::span<char> buffer) {
   if (pointer == nullptr) {
-    return CopyEntireStringOrNull(kNullPointerString, buffer);
+    return CopyEntireString(kNullPointerString, buffer);
   }
   return IntToHexString(reinterpret_cast<uintptr_t>(pointer), buffer);
 }
 
-StatusWithSize CopyEntireStringOrNull(const std::string_view& value,
-                                      std::span<char> buffer) {
+StatusWithSize CopyString(const std::string_view& value,
+                          std::span<char> buffer) {
+  if (buffer.empty()) {
+    return StatusWithSize::ResourceExhausted();
+  }
+
+  const size_t copied = value.copy(buffer.data(), buffer.size() - 1);
+  buffer[copied] = '\0';
+
+  return StatusWithSize(
+      copied == value.size() ? OkStatus() : Status::ResourceExhausted(),
+      copied);
+}
+
+StatusWithSize CopyEntireString(const std::string_view& value,
+                                std::span<char> buffer) {
   if (value.size() >= buffer.size()) {
     return HandleExhaustedBuffer(buffer);
   }
diff --git a/pw_string/type_to_string_test.cc b/pw_string/type_to_string_test.cc
index ffadc27..4deb657 100644
--- a/pw_string/type_to_string_test.cc
+++ b/pw_string/type_to_string_test.cc
@@ -407,84 +407,71 @@
   EXPECT_STREQ("", buffer_);
 }
 
-class CopyStringOrNullTest : public TestWithBuffer {};
+class CopyStringTest : public TestWithBuffer {};
 
 using namespace std::literals::string_view_literals;
 
-TEST_F(CopyStringOrNullTest, NullSource_WritesNullPointerString) {
-  EXPECT_EQ(kNullPointerString.size(),
-            CopyStringOrNull(nullptr, buffer_).size());
-  EXPECT_EQ(kNullPointerString, buffer_);
-}
-
-TEST_F(CopyStringOrNullTest, EmptyStringView_WritesNullTerminator) {
-  EXPECT_EQ(0u, CopyStringOrNull("", buffer_).size());
+TEST_F(CopyStringTest, EmptyStringView_WritesNullTerminator) {
+  EXPECT_EQ(0u, CopyString("", buffer_).size());
   EXPECT_EQ('\0', buffer_[0]);
 }
 
-TEST_F(CopyStringOrNullTest, EmptyBuffer_WritesNothing) {
-  auto result = CopyStringOrNull("Hello", std::span(buffer_, 0));
+TEST_F(CopyStringTest, EmptyBuffer_WritesNothing) {
+  auto result = CopyString("Hello", std::span(buffer_, 0));
   EXPECT_EQ(0u, result.size());
   EXPECT_FALSE(result.ok());
   EXPECT_STREQ(kStartingString, buffer_);
 }
 
-TEST_F(CopyStringOrNullTest, TooSmall_Truncates) {
-  auto result = CopyStringOrNull("Hi!", std::span(buffer_, 3));
+TEST_F(CopyStringTest, TooSmall_Truncates) {
+  auto result = CopyString("Hi!", std::span(buffer_, 3));
   EXPECT_EQ(2u, result.size());
   EXPECT_FALSE(result.ok());
   EXPECT_STREQ("Hi", buffer_);
 }
 
-TEST_F(CopyStringOrNullTest, ExactFit) {
-  auto result = CopyStringOrNull("Hi!", std::span(buffer_, 4));
+TEST_F(CopyStringTest, ExactFit) {
+  auto result = CopyString("Hi!", std::span(buffer_, 4));
   EXPECT_EQ(3u, result.size());
   EXPECT_TRUE(result.ok());
   EXPECT_STREQ("Hi!", buffer_);
 }
 
-TEST_F(CopyStringOrNullTest, NullTerminatorsInString) {
-  ASSERT_EQ(4u, CopyStringOrNull("\0!\0\0"sv, std::span(buffer_, 5)).size());
+TEST_F(CopyStringTest, NullTerminatorsInString) {
+  ASSERT_EQ(4u, CopyString("\0!\0\0"sv, std::span(buffer_, 5)).size());
   EXPECT_EQ("\0!\0\0"sv, std::string_view(buffer_, 4));
 }
 
-class CopyEntireStringOrNullTest : public TestWithBuffer {};
+class CopyEntireStringTest : public TestWithBuffer {};
 
-TEST_F(CopyEntireStringOrNullTest, NullSource_WritesNullPointerString) {
-  EXPECT_EQ(kNullPointerString.size(),
-            CopyEntireStringOrNull(nullptr, buffer_).size());
-  EXPECT_EQ(kNullPointerString, buffer_);
-}
-
-TEST_F(CopyEntireStringOrNullTest, EmptyStringView_WritesNullTerminator) {
-  EXPECT_EQ(0u, CopyEntireStringOrNull("", buffer_).size());
+TEST_F(CopyEntireStringTest, EmptyStringView_WritesNullTerminator) {
+  EXPECT_EQ(0u, CopyEntireString("", buffer_).size());
   EXPECT_EQ('\0', buffer_[0]);
 }
 
-TEST_F(CopyEntireStringOrNullTest, EmptyBuffer_WritesNothing) {
-  auto result = CopyEntireStringOrNull("Hello", std::span(buffer_, 0));
+TEST_F(CopyEntireStringTest, EmptyBuffer_WritesNothing) {
+  auto result = CopyEntireString("Hello", std::span(buffer_, 0));
   EXPECT_EQ(0u, result.size());
   EXPECT_FALSE(result.ok());
   EXPECT_STREQ(kStartingString, buffer_);
 }
 
-TEST_F(CopyEntireStringOrNullTest, TooSmall_WritesNothing) {
-  auto result = CopyEntireStringOrNull("Hi!", std::span(buffer_, 3));
+TEST_F(CopyEntireStringTest, TooSmall_WritesNothing) {
+  auto result = CopyEntireString("Hi!", std::span(buffer_, 3));
   EXPECT_EQ(0u, result.size());
   EXPECT_FALSE(result.ok());
   EXPECT_STREQ("", buffer_);
 }
 
-TEST_F(CopyEntireStringOrNullTest, ExactFit) {
-  auto result = CopyEntireStringOrNull("Hi!", std::span(buffer_, 4));
+TEST_F(CopyEntireStringTest, ExactFit) {
+  auto result = CopyEntireString("Hi!", std::span(buffer_, 4));
   EXPECT_EQ(3u, result.size());
   EXPECT_TRUE(result.ok());
   EXPECT_STREQ("Hi!", buffer_);
 }
 
-TEST_F(CopyEntireStringOrNullTest, NullTerminatorsInString) {
-  ASSERT_EQ(4u,
-            CopyEntireStringOrNull("\0!\0\0"sv, std::span(buffer_, 5)).size());
+TEST_F(CopyEntireStringTest, NullTerminatorsInString) {
+  ASSERT_EQ(4u, CopyEntireString("\0!\0\0"sv, std::span(buffer_, 5)).size());
   EXPECT_EQ("\0!\0\0"sv, std::string_view(buffer_, 4));
 }
 
diff --git a/pw_string/util_test.cc b/pw_string/util_test.cc
index b22749c..3815d3e 100644
--- a/pw_string/util_test.cc
+++ b/pw_string/util_test.cc
@@ -1,4 +1,4 @@
-// Copyright 2021 The Pigweed Authors
+// Copyright 2019 The Pigweed Authors
 //
 // 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
@@ -19,127 +19,23 @@
 namespace pw::string {
 namespace {
 
-using namespace std::literals::string_view_literals;
+TEST(Length, Nullptr_Returns0) { EXPECT_EQ(0u, Length(nullptr, 100)); }
 
-TEST(ClampedLength, Nullptr_Returns0) {
-  EXPECT_EQ(0u, internal::ClampedLength(nullptr, 100));
+TEST(Length, EmptyString_Returns0) {
+  EXPECT_EQ(0u, Length("", 0));
+  EXPECT_EQ(0u, Length("", 100));
 }
 
-TEST(ClampedLength, EmptyString_Returns0) {
-  EXPECT_EQ(0u, internal::ClampedLength("", 0));
-  EXPECT_EQ(0u, internal::ClampedLength("", 100));
+TEST(Length, MaxLongerThanString_ReturnsStrlen) {
+  EXPECT_EQ(5u, Length("12345", 100));
 }
 
-TEST(ClampedLength, MaxLongerThanString_ReturnsStrlen) {
-  EXPECT_EQ(5u, internal::ClampedLength("12345", 100));
+TEST(Length, StringMaxLongerThanMax_ReturnsMax) {
+  EXPECT_EQ(0u, Length("12345", 0));
+  EXPECT_EQ(4u, Length("12345", 4));
 }
 
-TEST(ClampedLength, StringMaxLongerThanMax_ReturnsMax) {
-  EXPECT_EQ(0u, internal::ClampedLength("12345", 0));
-  EXPECT_EQ(4u, internal::ClampedLength("12345", 4));
-}
-
-TEST(ClampedLength, LengthEqualsMax) {
-  EXPECT_EQ(5u, internal::ClampedLength("12345", 5));
-}
-
-TEST(ClampedCString, NullPtr_ReturnsEmpty) {
-  EXPECT_TRUE(ClampedCString(nullptr, 100).empty());
-}
-
-TEST(ClampedCString, EmptyString_Returns0) {
-  EXPECT_TRUE(ClampedCString("", 0).empty());
-  EXPECT_TRUE(ClampedCString("", 100).empty());
-}
-
-TEST(ClampedCString, MaxLongerThanString_ReturnsStr) {
-  static constexpr char kInput[] = "12345";
-  const std::string_view result = ClampedCString(kInput, 100);
-  EXPECT_EQ(result.size(), strlen(kInput));
-  EXPECT_EQ(result.data(), &kInput[0]);
-}
-
-TEST(ClampedCString, StringMaxLongerThanMax_ClampsView) {
-  static constexpr char kInput[] = "12345";
-
-  EXPECT_TRUE(ClampedCString(kInput, 0).empty());
-
-  const std::string_view result = ClampedCString(kInput, 4);
-  EXPECT_EQ(result.size(), 4u);
-  EXPECT_EQ(result.data(), &kInput[0]);
-}
-
-TEST(ClampedCString, FullStringView) {
-  static constexpr char kInput[] = "12345";
-  const std::string_view result = ClampedCString(kInput);
-  EXPECT_EQ(result.size(), strlen(kInput));
-  EXPECT_EQ(result.data(), &kInput[0]);
-}
-
-TEST(NullTerminatedLength, EmptyString_RequiresNullTerminator) {
-  EXPECT_TRUE(NullTerminatedLength("", 0).status().IsOutOfRange());
-
-  ASSERT_TRUE(NullTerminatedLength("", 100).status().ok());
-  EXPECT_EQ(0u, NullTerminatedLength("", 100).value());
-}
-
-TEST(NullTerminatedLength, MaxLongerThanString_ReturnsStrlen) {
-  ASSERT_TRUE(NullTerminatedLength("12345", 100).status().ok());
-  EXPECT_EQ(5u, NullTerminatedLength("12345", 100).value());
-}
-
-TEST(NullTerminatedLength, StringMaxLongerThanMax_Fails) {
-  EXPECT_TRUE(NullTerminatedLength("12345", 0).status().IsOutOfRange());
-  EXPECT_TRUE(NullTerminatedLength("12345", 4).status().IsOutOfRange());
-}
-
-TEST(NullTerminatedLength, LengthEqualsMax) {
-  static constexpr char kInput[] = "12345";
-  ASSERT_TRUE(NullTerminatedLength(kInput).ok());
-  EXPECT_EQ(5u, NullTerminatedLength(kInput).value());
-}
-
-class TestWithBuffer : public ::testing::Test {
- protected:
-  static constexpr char kStartingString[] = "!@#$%^&*()!@#$%^&*()";
-
-  TestWithBuffer() { std::memcpy(buffer_, kStartingString, sizeof(buffer_)); }
-
-  char buffer_[sizeof(kStartingString)];
-};
-
-class CopyTest : public TestWithBuffer {};
-
-TEST_F(CopyTest, EmptyStringView_WritesNullTerminator) {
-  EXPECT_EQ(0u, Copy("", buffer_).size());
-  EXPECT_EQ('\0', buffer_[0]);
-}
-
-TEST_F(CopyTest, EmptyBuffer_WritesNothing) {
-  auto result = Copy("Hello", std::span(buffer_, 0));
-  EXPECT_EQ(0u, result.size());
-  EXPECT_FALSE(result.ok());
-  EXPECT_STREQ(kStartingString, buffer_);
-}
-
-TEST_F(CopyTest, TooSmall_Truncates) {
-  auto result = Copy("Hi!", std::span(buffer_, 3));
-  EXPECT_EQ(2u, result.size());
-  EXPECT_FALSE(result.ok());
-  EXPECT_STREQ("Hi", buffer_);
-}
-
-TEST_F(CopyTest, ExactFit) {
-  auto result = Copy("Hi!", std::span(buffer_, 4));
-  EXPECT_EQ(3u, result.size());
-  EXPECT_TRUE(result.ok());
-  EXPECT_STREQ("Hi!", buffer_);
-}
-
-TEST_F(CopyTest, NullTerminatorsInString) {
-  ASSERT_EQ(4u, Copy("\0!\0\0"sv, std::span(buffer_, 5)).size());
-  EXPECT_EQ("\0!\0\0"sv, std::string_view(buffer_, 4));
-}
+TEST(Length, LengthEqualsMax) { EXPECT_EQ(5u, Length("12345", 5)); }
 
 }  // namespace
 }  // namespace pw::string
diff --git a/pw_symbolizer/BUILD.bazel b/pw_symbolizer/BUILD.bazel
deleted file mode 100644
index f8b38d4..0000000
--- a/pw_symbolizer/BUILD.bazel
+++ /dev/null
@@ -1,25 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-# This is only used for the python tests.
-filegroup(
-    name = "symbolizer_test",
-    srcs = [
-        "py/symbolizer_test.cc",
-    ],
-)
diff --git a/pw_symbolizer/BUILD.gn b/pw_symbolizer/BUILD.gn
deleted file mode 100644
index 9a6699a..0000000
--- a/pw_symbolizer/BUILD.gn
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_docgen/docs.gni")
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
diff --git a/pw_symbolizer/OWNERS b/pw_symbolizer/OWNERS
deleted file mode 100644
index 307b1de..0000000
--- a/pw_symbolizer/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-amontanez@google.com
diff --git a/pw_symbolizer/docs.rst b/pw_symbolizer/docs.rst
deleted file mode 100644
index 5d01216..0000000
--- a/pw_symbolizer/docs.rst
+++ /dev/null
@@ -1,90 +0,0 @@
-.. _module-pw_symbolizer:
-
-=============
-pw_symbolizer
-=============
-
-.. warning::
-  This module is under construction and may not be ready for use.
-
-pw_symbolizer provides python-based tooling for symbolizing addresses emitted by
-on-device firmware.
-
------
-Usage
------
-Symbolizer
-==========
-The ``Symbolizer`` abstract base class is an interface for translating addresses
-to human-readable source locations. Different architectures and operating
-systems can require vastly different implementations, so this interface is
-provided to allow Pigweed tooling to symbolize addresses without requiring
-Pigweed to provide explicit support for all possible implementations.
-
-``Symbolizer`` Also provides a helper function for producing nicely formatted
-stack trace style dumps.
-
-.. code:: py
-
-  import pw_symbolizer
-
-  symbolizer = pw_symbolizer.LlvmSymbolizer(Path('device_fw.elf'))
-  print(symbolizer.dump_stack_trace(backtrace_addresses))
-
-Which produces output like this:
-
-.. code-block:: none
-
-  Stack Trace (most recent call first):
-     1: at device::system::logging_thread_context (0x08004BE0)
-        in threads.cc:0
-     2: at device::system::logging_thread (0x0800B508)
-        in ??:?
-     3: at device::system::logging_thread_context (0x08004CB8)
-        in threads.cc:0
-     4: at device::system::logging_thread (0x0800B3C0)
-        in ??:?
-     5: at device::system::logging_thread (0x0800B508)
-        in ??:?
-     6: at (0x0800BAF7)
-        in ??:?
-     7: at common::log::LoggingThread::Run() (0x0800BAD1)
-        in out/common/log/logging_thread.cc:26
-     8: at pw::thread::threadx::Context::ThreadEntryPoint(unsigned long) (0x0800539D)
-        in out/pigweed/pw_thread_threadx/thread.cc:41
-     9: at device::system::logging_thread_context (0x08004CB8)
-        in threads.cc:0
-    10: at device::system::logging_thread_context (0x08004BE0)
-        in threads.cc:0
-
-FakeSymbolizer
-==============
-The ``FakeSymbolizer`` is utility class that implements the ``Symbolizer``
-interface with a fixed database of address to ``Symbol`` mappings. This is
-useful for testing, or as a no-op ``Symbolizer``.
-
-.. code:: py
-
-  import pw_symbolizer
-
-  known_symbols = (
-      pw_symbolizer.Symbol(0x0800A200, 'foo()', 'src/foo.c', 41),
-      pw_symbolizer.Symbol(0x08000004, 'boot_entry()', 'src/vector_table.c', 5),
-  )
-  symbolizer = pw_symbolizer.FakeSymbolizer(known_symbols)
-  sym = symbolizer.symbolize(0x0800A200)
-  print(f'This fake symbolizer knows about: {sym}')
-
-LlvmSymbolizer
-==============
-The ``LlvmSymbolizer`` is a python layer that wraps ``llvm-symbolizer`` to
-produce symbols from provided addresses. This module will only work if
-``llvm-symbolizer`` is available on the system ``PATH``.
-
-.. code:: py
-
-  import pw_symbolizer
-
-  symbolizer = pw_symbolizer.LlvmSymbolizer(Path('device_fw.elf'))
-  sym = symbolizer.symbolize(0x2000ac21)
-  print(f'You have a bug here: {sym}')
diff --git a/pw_symbolizer/py/BUILD.gn b/pw_symbolizer/py/BUILD.gn
deleted file mode 100644
index 46dea1c..0000000
--- a/pw_symbolizer/py/BUILD.gn
+++ /dev/null
@@ -1,45 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/python.gni")
-import("$dir_pw_docgen/docs.gni")
-
-pw_python_package("py") {
-  generate_setup = {
-    metadata = {
-      name = "pw_symbolizer"
-      version = "0.0.1"
-    }
-  }
-
-  sources = [
-    "pw_symbolizer/__init__.py",
-    "pw_symbolizer/llvm_symbolizer.py",
-    "pw_symbolizer/symbolizer.py",
-  ]
-
-  tests = [ "symbolizer_test.py" ]
-
-  # This is harder to test on mac/windows due to differences in how debug info
-  # is generated.
-  if (host_os == "linux") {
-    inputs = [ "symbolizer_test.cc" ]
-    tests += [ "llvm_symbolizer_test.py" ]
-    python_test_deps = [ "$dir_pw_cli/py" ]
-  }
-
-  pylintrc = "$dir_pigweed/.pylintrc"
-}
diff --git a/pw_symbolizer/py/llvm_symbolizer_test.py b/pw_symbolizer/py/llvm_symbolizer_test.py
deleted file mode 100644
index 9eb43b3..0000000
--- a/pw_symbolizer/py/llvm_symbolizer_test.py
+++ /dev/null
@@ -1,87 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests for pw_symbolizer's llvm-symbolizer based symbolization."""
-
-import subprocess
-import tempfile
-import unittest
-import json
-from pathlib import Path
-import pw_symbolizer
-
-_MODULE_PY_DIR = Path(__file__).parent.resolve()
-_CPP_TEST_FILE_NAME = 'symbolizer_test.cc'
-
-_COMPILER = 'clang++'
-
-
-class TestSymbolizer(unittest.TestCase):
-    """Unit tests for binary symbolization."""
-    def _test_symbolization_results(self, expected_symbols, symbolizer):
-        for expected_symbol in expected_symbols:
-            result = symbolizer.symbolize(expected_symbol['Address'])
-            self.assertEqual(result.name, expected_symbol['Expected'])
-            self.assertEqual(result.address, expected_symbol['Address'])
-
-            # Objects sometimes don't have a file/line number for some
-            # reason.
-            if not expected_symbol['IsObj']:
-                self.assertEqual(result.file, _CPP_TEST_FILE_NAME)
-                self.assertEqual(result.line, expected_symbol['Line'])
-
-    def test_symbolization(self):
-        """Tests that the symbolizer can symbolize addresses properly."""
-        with tempfile.TemporaryDirectory() as exe_dir:
-            exe_file = Path(exe_dir) / 'print_expected_symbols'
-
-            # Compiles a binary that prints symbol addresses and expected
-            # results as JSON.
-            cmd = [
-                _COMPILER,
-                _CPP_TEST_FILE_NAME,
-                '-gfull',
-                f'-ffile-prefix-map={_MODULE_PY_DIR}=',
-                '-std=c++17',
-                '-o',
-                exe_file,
-            ]
-
-            process = subprocess.run(cmd,
-                                     stdout=subprocess.PIPE,
-                                     stderr=subprocess.STDOUT,
-                                     cwd=_MODULE_PY_DIR)
-            self.assertEqual(process.returncode, 0)
-
-            process = subprocess.run([exe_file],
-                                     stdout=subprocess.PIPE,
-                                     stderr=subprocess.STDOUT)
-            self.assertEqual(process.returncode, 0)
-
-            expected_symbols = [
-                json.loads(line)
-                for line in process.stdout.decode().splitlines()
-            ]
-
-            symbolizer = pw_symbolizer.LlvmSymbolizer(exe_file)
-            self._test_symbolization_results(expected_symbols, symbolizer)
-
-            # Test backwards compatibility with older versions of
-            # llvm-symbolizer.
-            symbolizer = pw_symbolizer.LlvmSymbolizer(exe_file,
-                                                      force_legacy=True)
-            self._test_symbolization_results(expected_symbols, symbolizer)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_symbolizer/py/pw_symbolizer/__init__.py b/pw_symbolizer/py/pw_symbolizer/__init__.py
deleted file mode 100644
index 65d3cad..0000000
--- a/pw_symbolizer/py/pw_symbolizer/__init__.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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 that supports address symbolization of device firmware."""
-
-from pw_symbolizer.symbolizer import Symbolizer, Symbol, FakeSymbolizer
-from pw_symbolizer.llvm_symbolizer import LlvmSymbolizer
diff --git a/pw_symbolizer/py/pw_symbolizer/llvm_symbolizer.py b/pw_symbolizer/py/pw_symbolizer/llvm_symbolizer.py
deleted file mode 100644
index 3362172..0000000
--- a/pw_symbolizer/py/pw_symbolizer/llvm_symbolizer.py
+++ /dev/null
@@ -1,154 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""A symbolizer based on llvm-symbolizer."""
-
-import shutil
-import subprocess
-import threading
-import json
-from typing import Optional, Tuple
-from pathlib import Path
-from pw_symbolizer import symbolizer
-
-
-class LlvmSymbolizer(symbolizer.Symbolizer):
-    """A symbolizer that wraps llvm-symbolizer."""
-    def __init__(self, binary: Optional[Path] = None, force_legacy=False):
-        # Lets destructor return cleanly if the binary is not found.
-        self._symbolizer = None
-        if shutil.which('llvm-symbolizer') is None:
-            raise FileNotFoundError(
-                'llvm-symbolizer not installed. Run bootstrap, or download '
-                'LLVM (https://github.com/llvm/llvm-project/releases/) and add '
-                'the tools to your system PATH')
-
-        # Prefer JSON output as it's easier to decode.
-        if force_legacy:
-            self._json_mode = False
-        else:
-            self._json_mode = LlvmSymbolizer._is_json_compatibile()
-
-        if binary is not None:
-            if not binary.exists():
-                raise FileNotFoundError(binary)
-
-            output_style = 'JSON' if self._json_mode else 'LLVM'
-            cmd = [
-                'llvm-symbolizer',
-                '--no-inlines',
-                '--demangle',
-                '--functions',
-                f'--output-style={output_style}',
-                '--exe',
-                str(binary),
-            ]
-            self._symbolizer = subprocess.Popen(cmd,
-                                                stdout=subprocess.PIPE,
-                                                stdin=subprocess.PIPE)
-
-            self._lock: threading.Lock = threading.Lock()
-
-    def __del__(self):
-        if self._symbolizer:
-            self._symbolizer.terminate()
-
-    @staticmethod
-    def _is_json_compatibile() -> bool:
-        """Checks llvm-symbolizer to ensure compatibility"""
-        result = subprocess.run(('llvm-symbolizer', '--help'),
-                                stdout=subprocess.PIPE,
-                                stdin=subprocess.PIPE)
-        for line in result.stdout.decode().splitlines():
-            if '--output-style' in line and 'JSON' in line:
-                return True
-
-        return False
-
-    @staticmethod
-    def _read_json_symbol(address, stdout) -> symbolizer.Symbol:
-        """Reads a single symbol from llvm-symbolizer's JSON output mode."""
-        results = json.loads(stdout.readline().decode())
-        # The symbol resolution should give us at least one symbol, even
-        # if it's largely empty.
-        assert len(results["Symbol"]) > 0
-
-        # Get the first symbol.
-        symbol = results["Symbol"][0]
-
-        return symbolizer.Symbol(address=address,
-                                 name=symbol['FunctionName'],
-                                 file=symbol['FileName'],
-                                 line=symbol['Line'])
-
-    @staticmethod
-    def _llvm_output_line_splitter(file_and_line: str) -> Tuple[str, int]:
-        split = file_and_line.split(':')
-        # LLVM file name output is as follows:
-        #   path/to/src.c:123:1
-        # Where the last number is the discriminator, the second to last the
-        # line number, and all leading characters the file name. For now,
-        # this class ignores discriminators.
-        line_number_str = split[-2]
-        file = ':'.join(split[:-2])
-
-        if not line_number_str:
-            raise ValueError(f'Bad symbol format: {file_and_line}')
-
-        # For unknown file names, mark as blank.
-        if file.startswith('?'):
-            return ('', 0)
-
-        return (file, int(line_number_str))
-
-    @staticmethod
-    def _read_llvm_symbol(address, stdout) -> symbolizer.Symbol:
-        """Reads a single symbol from llvm-symbolizer's LLVM output mode."""
-        symbol = stdout.readline().decode().strip()
-        file_and_line = stdout.readline().decode().strip()
-
-        # Might have gotten multiple symbol matches, drop all of the other ones.
-        # The results of a symbol are denoted by an empty newline.
-        while stdout.readline().decode() != '\n':
-            pass
-
-        if symbol.startswith('?'):
-            return symbolizer.Symbol(address)
-
-        file, line_number = LlvmSymbolizer._llvm_output_line_splitter(
-            file_and_line)
-
-        return symbolizer.Symbol(address, symbol, file, line_number)
-
-    def symbolize(self, address: int) -> symbolizer.Symbol:
-        """Symbolizes an address using the loaded ELF file."""
-        if not self._symbolizer:
-            return symbolizer.Symbol(address=address, name='', file='', line=0)
-
-        with self._lock:
-            if self._symbolizer.returncode is not None:
-                raise ValueError('llvm-symbolizer closed unexpectedly')
-
-            stdin = self._symbolizer.stdin
-            stdout = self._symbolizer.stdout
-
-            assert stdin is not None
-            assert stdout is not None
-
-            stdin.write(f'0x{address:08X}\n'.encode())
-            stdin.flush()
-
-            if self._json_mode:
-                return LlvmSymbolizer._read_json_symbol(address, stdout)
-
-            return LlvmSymbolizer._read_llvm_symbol(address, stdout)
diff --git a/pw_symbolizer/py/pw_symbolizer/py.typed b/pw_symbolizer/py/pw_symbolizer/py.typed
deleted file mode 100644
index e69de29..0000000
--- a/pw_symbolizer/py/pw_symbolizer/py.typed
+++ /dev/null
diff --git a/pw_symbolizer/py/pw_symbolizer/symbolizer.py b/pw_symbolizer/py/pw_symbolizer/symbolizer.py
deleted file mode 100644
index 56ce07b..0000000
--- a/pw_symbolizer/py/pw_symbolizer/symbolizer.py
+++ /dev/null
@@ -1,101 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Utilities for address symbolization."""
-
-import abc
-from typing import Iterable, List
-from dataclasses import dataclass
-
-
-@dataclass(frozen=True)
-class Symbol:
-    """Symbols produced by a symbolizer."""
-    address: int
-    name: str = ''
-    file: str = ''
-    line: int = 0
-
-    def to_string(self, max_filename_len: int = 0) -> str:
-        if not self.name:
-            name = f'0x{self.address:08X}'
-        else:
-            name = self.name
-
-        return f'{name} ({self.file_and_line(max_filename_len)})'
-
-    def file_and_line(self, max_filename_len: int = 0) -> str:
-        """Returns a file/line number string, with question marks if unknown."""
-
-        if not self.file:
-            return '??:?'
-
-        if max_filename_len and len(self.file) > max_filename_len:
-            return f'[...]{self.file[-max_filename_len:]}:{self.line}'
-
-        return f'{self.file}:{self.line}'
-
-    def __str__(self):
-        return self.to_string()
-
-
-class Symbolizer(abc.ABC):
-    """An interface for symbolizing addresses."""
-    @abc.abstractmethod
-    def symbolize(self, address: int) -> Symbol:
-        """Symbolizes an address using a loaded binary or symbol database."""
-
-    def dump_stack_trace(self,
-                         addresses,
-                         most_recent_first: bool = True) -> str:
-        """Symbolizes and dumps a list of addresses as a stack trace.
-
-        most_recent_first controls the hint provided at the top of the stack
-        trace. If call stack depth increases with each element in the input
-        list, most_recent_first should be false.
-        """
-        order: str = 'first' if most_recent_first else 'last'
-
-        stack_trace: List[str] = []
-        stack_trace.append(f'Stack Trace (most recent call {order}):')
-
-        max_width = len(str(len(addresses)))
-        for i, address in enumerate(addresses):
-            depth = i + 1
-            symbol = self.symbolize(address)
-
-            if symbol.name:
-                sym_desc = f'{symbol.name} (0x{symbol.address:08X})'
-            else:
-                sym_desc = f'(0x{symbol.address:08X})'
-
-            stack_trace.append(f'  {depth:>{max_width}}: at {sym_desc}')
-            stack_trace.append(f'      in {symbol.file_and_line()}')
-
-        return '\n'.join(stack_trace)
-
-
-class FakeSymbolizer(Symbolizer):
-    """A fake symbolizer that only knows a fixed set of symbols."""
-    def __init__(self, known_symbols: Iterable[Symbol] = None):
-        if known_symbols is not None:
-            self._db = {sym.address: sym for sym in known_symbols}
-        else:
-            self._db = {}
-
-    def symbolize(self, address: int) -> Symbol:
-        """Symbolizes a fixed symbol database."""
-        if address in self._db:
-            return self._db[address]
-
-        return Symbol(address)
diff --git a/pw_symbolizer/py/symbolizer_test.cc b/pw_symbolizer/py/symbolizer_test.cc
deleted file mode 100644
index a02c1f4..0000000
--- a/pw_symbolizer/py/symbolizer_test.cc
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cinttypes>
-#include <cstdint>
-#include <cstdio>
-
-namespace pw::symbolizer::test {
-
-struct Fish {
-  bool has_legs;
-  bool tastes_like_chicken;
-};
-
-Fish gerald;
-
-}  // namespace pw::symbolizer::test
-
-void PrintExpectedObjSymbol(void* address, const char* expected) {
-  uintptr_t val = reinterpret_cast<uintptr_t>(address);
-  printf("{\"Address\":%" PRIuPTR ",\"Expected\":\"%s\",\"IsObj\":true}\n",
-         val,
-         expected);
-}
-
-namespace {
-
-void GoWild() {
-  volatile int counter = 0;
-  for (int i = 0; i < 123; i++) {
-    counter += i * counter;
-  }
-}
-
-}  // namespace
-
-namespace another::one {
-
-void PrintExpectedFuncSymbol(void* address, const char* expected, int line) {
-  uintptr_t val = reinterpret_cast<uintptr_t>(address);
-  printf("{\"Address\":%" PRIuPTR
-         ",\"Expected\":\"%s\",\"Line\": %d,\"IsObj\":false}\n",
-         val,
-         expected,
-         line);
-}
-
-}  // namespace another::one
-
-extern "C" long pw_extern_long = 42;
-
-int main() {
-  PrintExpectedObjSymbol(&pw::symbolizer::test::gerald,
-                         "pw::symbolizer::test::gerald");
-  PrintExpectedObjSymbol(&pw::symbolizer::test::gerald.tastes_like_chicken,
-                         "pw::symbolizer::test::gerald");
-  another::one::PrintExpectedFuncSymbol(
-      reinterpret_cast<void*>(&another::one::PrintExpectedFuncSymbol),
-      "another::one::PrintExpectedFuncSymbol(void*, char const*, int)",
-      50);
-
-  another::one::PrintExpectedFuncSymbol(
-      reinterpret_cast<void*>(&GoWild), "(anonymous namespace)::GoWild()", 39);
-
-  PrintExpectedObjSymbol(&pw_extern_long, "pw_extern_long");
-
-  another::one::PrintExpectedFuncSymbol(
-      reinterpret_cast<void*>(&main), "main", 63);
-  return 0;
-}
diff --git a/pw_symbolizer/py/symbolizer_test.py b/pw_symbolizer/py/symbolizer_test.py
deleted file mode 100644
index 4d25846..0000000
--- a/pw_symbolizer/py/symbolizer_test.py
+++ /dev/null
@@ -1,85 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests for pw_symbolizer's python tooling."""
-
-import unittest
-import pw_symbolizer
-
-
-class TestSymbolFormatting(unittest.TestCase):
-    """Tests Symbol objects to validate formatted output."""
-    def test_blank_symbol(self):
-        sym = pw_symbolizer.Symbol(address=0x00000000,
-                                   name='',
-                                   file='',
-                                   line=0)
-        self.assertEqual('??:?', sym.file_and_line())
-        self.assertEqual('0x00000000 (??:?)', str(sym))
-
-    def test_default_symbol(self):
-        sym = pw_symbolizer.Symbol(address=0x0000a400)
-        self.assertEqual('??:?', sym.file_and_line())
-        self.assertEqual('0x0000A400 (??:?)', str(sym))
-
-    def test_to_str(self):
-        sym = pw_symbolizer.Symbol(address=0x12345678,
-                                   name='idle_thread_context',
-                                   file='device/system/threads.cc',
-                                   line=59)
-        self.assertEqual('device/system/threads.cc:59', sym.file_and_line())
-        self.assertEqual('idle_thread_context (device/system/threads.cc:59)',
-                         str(sym))
-
-    def test_truncated_filename(self):
-        sym = pw_symbolizer.Symbol(address=0x12345678,
-                                   name='idle_thread_context',
-                                   file='device/system/threads.cc',
-                                   line=59)
-        self.assertEqual('idle_thread_context ([...]stem/threads.cc:59)',
-                         sym.to_string(max_filename_len=15))
-
-
-class TestFakeSymbolizer(unittest.TestCase):
-    """Tests the FakeSymbolizer class."""
-    def test_empty_db(self):
-        symbolizer = pw_symbolizer.FakeSymbolizer()
-        symbol = symbolizer.symbolize(0x404)
-        self.assertEqual(symbol.address, 0x404)
-        self.assertEqual(symbol.name, '')
-        self.assertEqual(symbol.file, '')
-
-    def test_db_with_entries(self):
-        known_symbols = (
-            pw_symbolizer.Symbol(0x404, 'do_a_flip(int n)', 'source/tricks.cc',
-                                 1403),
-            pw_symbolizer.Symbol(0xffffffff, 'a_variable_here_would_be_funny',
-                                 'source/globals.cc', 21),
-        )
-        symbolizer = pw_symbolizer.FakeSymbolizer(known_symbols)
-
-        symbol = symbolizer.symbolize(0x404)
-        self.assertEqual(symbol.address, 0x404)
-        self.assertEqual(symbol.name, 'do_a_flip(int n)')
-        self.assertEqual(symbol.file, 'source/tricks.cc')
-        self.assertEqual(symbol.line, 1403)
-
-        symbol = symbolizer.symbolize(0xffffffff)
-        self.assertEqual(symbol.address, 0xffffffff)
-        self.assertEqual(symbol.name, 'a_variable_here_would_be_funny')
-        self.assertEqual(symbol.file, 'source/globals.cc')
-        self.assertEqual(symbol.line, 21)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_sync/BUILD b/pw_sync/BUILD
new file mode 100644
index 0000000..e0a4741
--- /dev/null
+++ b/pw_sync/BUILD
@@ -0,0 +1,271 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+# TODO(pwbug/101): Need to add support for facades/backends to Bazel.
+PW_SYNC_BINARY_SEMAPHORE_BACKEND = "//pw_sync_stl:binary_semaphore"
+PW_SYNC_COUNTING_SEMAPHORE_BACKEND = "//pw_sync_stl:counting_semaphore"
+PW_SYNC_MUTEX_BACKEND = "//pw_sync_stl:mutex"
+PW_SYNC_TIMED_MUTEX_BACKEND = "//pw_sync_stl:timed_mutex"
+PW_SYNC_INTERRUPT_SPIN_LOCK_BACKEND = "//pw_sync_stl:interrupt_spin_lock"
+
+pw_cc_library(
+    name = "binary_semaphore_facade",
+    hdrs = [
+        "public/pw_sync/binary_semaphore.h",
+    ],
+    includes = ["public"],
+    srcs = [
+        "binary_semaphore.cc"
+    ],
+    deps = [
+        PW_SYNC_BINARY_SEMAPHORE_BACKEND + "_headers",
+        "//pw_chrono:system_clock",
+        "//pw_preprocessor",
+    ],
+)
+
+pw_cc_library(
+    name = "binary_semaphore",
+    deps = [
+        ":binary_semaphore_facade",
+        PW_SYNC_BINARY_SEMAPHORE_BACKEND + "_headers",
+    ],
+)
+
+pw_cc_library(
+    name = "binary_semaphore_backend",
+    deps = [
+       PW_SYNC_BINARY_SEMAPHORE_BACKEND,
+    ],
+)
+
+pw_cc_library(
+    name = "counting_semaphore_facade",
+    hdrs = [
+        "public/pw_sync/counting_semaphore.h",
+    ],
+    includes = ["public"],
+    srcs = [
+        "counting_semaphore.cc"
+    ],
+    deps = [
+        PW_SYNC_COUNTING_SEMAPHORE_BACKEND + "_headers",
+        "//pw_chrono:system_clock",
+        "//pw_preprocessor",
+    ],
+)
+
+pw_cc_library(
+    name = "counting_semaphore",
+    deps = [
+        ":counting_semaphore_facade",
+        PW_SYNC_COUNTING_SEMAPHORE_BACKEND + "_headers",
+    ],
+)
+
+pw_cc_library(
+    name = "counting_semaphore_backend",
+    deps = [
+       PW_SYNC_COUNTING_SEMAPHORE_BACKEND,
+    ],
+)
+
+pw_cc_library(
+    name = "lock_annotations",
+    hdrs = [
+        "public/pw_sync/lock_annotations.h",
+    ],
+    includes = ["public"],
+    deps = [
+        "//pw_preprocessor",
+    ],
+)
+
+pw_cc_library(
+    name = "mutex_facade",
+    hdrs = [
+        "public/pw_sync/mutex.h",
+    ],
+    includes = ["public"],
+    srcs = [
+        "mutex.cc"
+    ],
+    deps = [
+		    ":lock_annotations",
+        "//pw_preprocessor",
+        PW_SYNC_MUTEX_BACKEND + "_headers",
+    ],
+)
+
+pw_cc_library(
+    name = "mutex",
+    deps = [
+        ":mutex_facade",
+        PW_SYNC_MUTEX_BACKEND + "_headers",
+    ],
+)
+
+pw_cc_library(
+    name = "mutex_backend",
+    deps = [
+       PW_SYNC_MUTEX_BACKEND,
+    ],
+)
+
+pw_cc_library(
+    name = "timed_mutex_facade",
+    hdrs = [
+        "public/pw_sync/timed_mutex.h",
+    ],
+    includes = ["public"],
+    srcs = [
+        "timed_mutex.cc"
+    ],
+    deps = [
+		    ":lock_annotations",
+        ":mutex_facade",
+        "//pw_chrono:system_clock",
+        "//pw_preprocessor",
+        PW_SYNC_TIMED_MUTEX_BACKEND + "_headers",
+    ],
+)
+
+pw_cc_library(
+    name = "timed_mutex",
+    deps = [
+        ":timed_mutex_facade",
+        PW_SYNC_TIMED_MUTEX_BACKEND + "_headers",
+    ],
+)
+
+pw_cc_library(
+    name = "timed_mutex_backend",
+    deps = [
+       PW_SYNC_TIMED_MUTEX_BACKEND,
+    ],
+)
+
+pw_cc_library(
+    name = "interrupt_spin_lock_facade",
+    hdrs = [
+        "public/pw_sync/interrupt_spin_lock.h",
+    ],
+    includes = ["public"],
+    srcs = [
+        "interrupt_spin_lock.cc"
+    ],
+    deps = [
+		    ":lock_annotations",
+        "//pw_preprocessor",
+        PW_SYNC_INTERRUPT_SPIN_LOCK_BACKEND + "_headers",
+    ],
+)
+
+pw_cc_library(
+    name = "interrupt_spin_lock",
+    deps = [
+        ":interrupt_spin_lock_facade",
+        PW_SYNC_INTERRUPT_SPIN_LOCK_BACKEND + "_headers",
+    ],
+)
+
+pw_cc_library(
+    name = "interrupt_spin_lock_backend",
+    deps = [
+       PW_SYNC_INTERRUPT_SPIN_LOCK_BACKEND,
+    ],
+)
+
+pw_cc_library(
+    name = "yield_core",
+    hdrs = [
+        "public/pw_sync/yield_core.h",
+    ],
+    includes = ["public"],
+)
+
+pw_cc_test(
+    name = "binary_semaphore_facade_test",
+    srcs = [
+        "binary_semaphore_facade_test.cc",
+        "binary_semaphore_facade_test_c.c",
+    ],
+    deps = [
+        ":binary_semaphore",
+        "//pw_preprocessor",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "counting_semaphore_facade_test",
+    srcs = [
+        "counting_semaphore_facade_test.cc",
+        "counting_semaphore_facade_test_c.c",
+    ],
+    deps = [
+        ":counting_semaphore",
+        "//pw_preprocessor",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "mutex_facade_test",
+    srcs = [
+        "mutex_facade_test.cc",
+        "mutex_facade_test_c.c",
+    ],
+    deps = [
+        ":mutex",
+        "//pw_preprocessor",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "timed_mutex_facade_test",
+    srcs = [
+        "timed_mutex_facade_test.cc",
+        "timed_mutex_facade_test_c.c",
+    ],
+    deps = [
+        ":timed_mutex",
+        "//pw_preprocessor",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "interrupt_spin_lock_facade_test",
+    srcs = [
+        "interrupt_spin_lock_facade_test.cc",
+        "interrupt_spin_lock_facade_test_c.c",
+    ],
+    deps = [
+        ":interrupt_spin_lock",
+        "//pw_preprocessor",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_sync/BUILD.bazel b/pw_sync/BUILD.bazel
deleted file mode 100644
index 0b6da70..0000000
--- a/pw_sync/BUILD.bazel
+++ /dev/null
@@ -1,464 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_facade",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-load(
-    "//pw_build:selects.bzl",
-    "TARGET_COMPATIBLE_WITH_HOST_SELECT",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_facade(
-    name = "binary_semaphore_facade",
-    hdrs = [
-        "public/pw_sync/binary_semaphore.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_chrono:system_clock",
-        "//pw_preprocessor",
-    ],
-)
-
-pw_cc_library(
-    name = "binary_semaphore",
-    srcs = [
-        "binary_semaphore.cc",
-    ],
-    deps = [
-        ":binary_semaphore_facade",
-        "@pigweed_config//:pw_sync_binary_semaphore_backend",
-    ],
-)
-
-pw_cc_library(
-    name = "binary_semaphore_backend_multiplexer",
-    visibility = ["@pigweed_config//:__pkg__"],
-    deps = select({
-        "@platforms//os:none": ["//pw_sync_baremetal:binary_semaphore"],
-        "//pw_build/constraints/rtos:embos": ["//pw_sync_embos:binary_semaphore"],
-        "//pw_build/constraints/rtos:freertos": ["//pw_sync_freertos:binary_semaphore"],
-        "//pw_build/constraints/rtos:threadx": ["//pw_sync_threadx:binary_semaphore"],
-        "//conditions:default": ["//pw_sync_stl:binary_semaphore"],
-    }),
-)
-
-pw_cc_facade(
-    name = "counting_semaphore_facade",
-    hdrs = [
-        "public/pw_sync/counting_semaphore.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_chrono:system_clock",
-        "//pw_preprocessor",
-    ],
-)
-
-pw_cc_library(
-    name = "counting_semaphore",
-    srcs = [
-        "counting_semaphore.cc",
-    ],
-    deps = [
-        ":counting_semaphore_facade",
-        "@pigweed_config//:pw_sync_counting_semaphore_backend",
-    ],
-)
-
-pw_cc_library(
-    name = "counting_semaphore_backend_multiplexer",
-    visibility = ["@pigweed_config//:__pkg__"],
-    deps = select({
-        "@platforms//os:none": ["//pw_sync_baremetal:counting_semaphore"],
-        "//pw_build/constraints/rtos:embos": ["//pw_sync_embos:counting_semaphore"],
-        "//pw_build/constraints/rtos:freertos": ["//pw_sync_freertos:counting_semaphore"],
-        "//pw_build/constraints/rtos:threadx": ["//pw_sync_threadx:counting_semaphore"],
-        "//conditions:default": ["//pw_sync_stl:counting_semaphore"],
-    }),
-)
-
-pw_cc_library(
-    name = "lock_annotations",
-    hdrs = [
-        "public/pw_sync/lock_annotations.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_preprocessor",
-    ],
-)
-
-pw_cc_library(
-    name = "borrow",
-    hdrs = [
-        "public/pw_sync/borrow.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":lock_annotations",
-        ":virtual_basic_lockable",
-        "//pw_assert",
-    ],
-)
-
-pw_cc_library(
-    name = "virtual_basic_lockable",
-    hdrs = [
-        "public/pw_sync/virtual_basic_lockable.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":lock_annotations",
-        "//pw_polyfill",
-    ],
-)
-
-pw_cc_facade(
-    name = "mutex_facade",
-    hdrs = [
-        "public/pw_sync/mutex.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":lock_annotations",
-        ":virtual_basic_lockable",
-        "//pw_preprocessor",
-    ],
-)
-
-pw_cc_library(
-    name = "mutex",
-    srcs = [
-        "mutex.cc",
-    ],
-    deps = [
-        ":mutex_facade",
-        "@pigweed_config//:pw_sync_mutex_backend",
-    ],
-)
-
-pw_cc_library(
-    name = "mutex_backend_multiplexer",
-    visibility = ["@pigweed_config//:__pkg__"],
-    deps = select({
-        "@platforms//os:none": ["//pw_sync_baremetal:mutex"],
-        "//pw_build/constraints/rtos:embos": ["//pw_sync_embos:mutex"],
-        "//pw_build/constraints/rtos:freertos": ["//pw_sync_freertos:mutex"],
-        "//pw_build/constraints/rtos:threadx": ["//pw_sync_threadx:mutex"],
-        "//conditions:default": ["//pw_sync_stl:mutex"],
-    }),
-)
-
-pw_cc_facade(
-    name = "timed_mutex_facade",
-    hdrs = [
-        "public/pw_sync/timed_mutex.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":lock_annotations",
-        ":mutex_facade",
-        ":virtual_basic_lockable",
-        "//pw_chrono:system_clock",
-        "//pw_preprocessor",
-    ],
-)
-
-pw_cc_library(
-    name = "timed_mutex",
-    srcs = [
-        "timed_mutex.cc",
-    ],
-    deps = [
-        ":mutex",
-        ":timed_mutex_facade",
-        ":virtual_basic_lockable",
-        "@pigweed_config//:pw_sync_timed_mutex_backend",
-    ],
-)
-
-pw_cc_library(
-    name = "timed_mutex_backend_multiplexer",
-    visibility = ["@pigweed_config//:__pkg__"],
-    deps = select({
-        "@platforms//os:none": ["//pw_sync_baremetal:timed_mutex"],
-        "//pw_build/constraints/rtos:embos": ["//pw_sync_embos:timed_mutex"],
-        "//pw_build/constraints/rtos:freertos": ["//pw_sync_freertos:timed_mutex"],
-        "//pw_build/constraints/rtos:threadx": ["//pw_sync_threadx:timed_mutex"],
-        "//conditions:default": ["//pw_sync_stl:timed_mutex"],
-    }),
-)
-
-pw_cc_facade(
-    name = "interrupt_spin_lock_facade",
-    hdrs = [
-        "public/pw_sync/interrupt_spin_lock.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":lock_annotations",
-        ":virtual_basic_lockable",
-        "//pw_preprocessor",
-    ],
-)
-
-pw_cc_library(
-    name = "interrupt_spin_lock",
-    srcs = [
-        "interrupt_spin_lock.cc",
-    ],
-    deps = [
-        ":interrupt_spin_lock_facade",
-        "@pigweed_config//:pw_sync_interrupt_spin_lock_backend",
-    ],
-)
-
-pw_cc_library(
-    name = "interrupt_spin_lock_backend_multiplexer",
-    visibility = ["@pigweed_config//:__pkg__"],
-    deps = select({
-        "@platforms//os:none": ["//pw_sync_baremetal:interrupt_spin_lock"],
-        "//pw_build/constraints/rtos:embos": ["//pw_sync_embos:interrupt_spin_lock"],
-        "//pw_build/constraints/rtos:freertos": ["//pw_sync_freertos:interrupt_spin_lock"],
-        "//pw_build/constraints/rtos:threadx": ["//pw_sync_threadx:interrupt_spin_lock"],
-        "//conditions:default": ["//pw_sync_stl:interrupt_spin_lock"],
-    }),
-)
-
-pw_cc_facade(
-    name = "thread_notification_facade",
-    hdrs = [
-        "public/pw_sync/thread_notification.h",
-    ],
-    includes = ["public"],
-)
-
-pw_cc_library(
-    name = "thread_notification",
-    deps = [
-        ":thread_notification_facade",
-        "@pigweed_config//:pw_sync_thread_notification_backend",
-    ],
-)
-
-pw_cc_library(
-    name = "thread_notification_backend_multiplexer",
-    visibility = ["@pigweed_config//:__pkg__"],
-    deps = select({
-        "//conditions:default": ["//pw_sync:binary_semaphore_thread_notification_backend"],
-    }),
-)
-
-pw_cc_facade(
-    name = "timed_thread_notification_facade",
-    hdrs = [
-        "public/pw_sync/timed_thread_notification.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":thread_notification_facade",
-        "//pw_chrono:system_clock",
-    ],
-)
-
-pw_cc_library(
-    name = "timed_thread_notification",
-    deps = [
-        ":thread_notification",
-        ":timed_thread_notification_facade",
-        "@pigweed_config//:pw_sync_timed_thread_notification_backend",
-    ],
-)
-
-pw_cc_library(
-    name = "timed_thread_notification_backend_multiplexer",
-    visibility = ["@pigweed_config//:__pkg__"],
-    deps = select({
-        "//conditions:default": ["//pw_sync:binary_semaphore_timed_thread_notification_backend"],
-    }),
-)
-
-pw_cc_library(
-    name = "binary_semaphore_thread_notification_backend_headers",
-    hdrs = [
-        "public/pw_sync/backends/binary_semaphore_thread_notification_inline.h",
-        "public/pw_sync/backends/binary_semaphore_thread_notification_native.h",
-        "public_overrides/pw_sync_backend/thread_notification_inline.h",
-        "public_overrides/pw_sync_backend/thread_notification_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-    deps = [":binary_semaphore"],
-)
-
-pw_cc_library(
-    name = "binary_semaphore_thread_notification_backend",
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-    deps = [
-        ":binary_semaphore_facade",
-        ":binary_semaphore_thread_notification_backend_headers",
-        ":thread_notification_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "binary_semaphore_timed_thread_notification_backend_headers",
-    hdrs = [
-        "public/pw_sync/backends/binary_semaphore_timed_thread_notification_inline.h",
-        "public_overrides/pw_sync_backend/timed_thread_notification_inline.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-    deps = [
-        ":binary_semaphore_thread_notification_backend_headers",
-        "//pw_chrono:system_clock",
-    ],
-)
-
-pw_cc_library(
-    name = "binary_semaphore_timed_thread_notification_backend",
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-    deps = [
-        ":binary_semaphore_thread_notification_backend",
-        ":binary_semaphore_timed_thread_notification_backend_headers",
-        "//pw_sync:timed_thread_notification_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "yield_core",
-    hdrs = [
-        "public/pw_sync/yield_core.h",
-    ],
-    includes = ["public"],
-)
-
-pw_cc_test(
-    name = "borrow_test",
-    srcs = [
-        "borrow_test.cc",
-    ],
-    deps = [
-        ":borrow",
-        ":virtual_basic_lockable",
-        "//pw_assert",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "binary_semaphore_facade_test",
-    srcs = [
-        "binary_semaphore_facade_test.cc",
-        "binary_semaphore_facade_test_c.c",
-    ],
-    deps = [
-        ":binary_semaphore",
-        "//pw_preprocessor",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "counting_semaphore_facade_test",
-    srcs = [
-        "counting_semaphore_facade_test.cc",
-        "counting_semaphore_facade_test_c.c",
-    ],
-    deps = [
-        ":counting_semaphore",
-        "//pw_preprocessor",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "mutex_facade_test",
-    srcs = [
-        "mutex_facade_test.cc",
-        "mutex_facade_test_c.c",
-    ],
-    deps = [
-        ":mutex",
-        "//pw_preprocessor",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "timed_mutex_facade_test",
-    srcs = [
-        "timed_mutex_facade_test.cc",
-        "timed_mutex_facade_test_c.c",
-    ],
-    deps = [
-        ":timed_mutex",
-        "//pw_chrono:system_clock",
-        "//pw_preprocessor",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "interrupt_spin_lock_facade_test",
-    srcs = [
-        "interrupt_spin_lock_facade_test.cc",
-        "interrupt_spin_lock_facade_test_c.c",
-    ],
-    deps = [
-        ":interrupt_spin_lock",
-        "//pw_preprocessor",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "thread_notification_facade_test",
-    srcs = [
-        "thread_notification_facade_test.cc",
-        "thread_notification_facade_test_c.c",
-    ],
-    deps = [
-        ":thread_notification",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "timed_thread_notification_facade_test",
-    srcs = [
-        "timed_thread_notification_facade_test.cc",
-        "timed_thread_notification_facade_test_c.c",
-    ],
-    deps = [
-        ":timed_thread_notification",
-        "//pw_chrono:system_clock",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_sync/BUILD.gn b/pw_sync/BUILD.gn
index 81b7003..db702a6 100644
--- a/pw_sync/BUILD.gn
+++ b/pw_sync/BUILD.gn
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2020 The Pigweed Authors
 #
 # 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
@@ -16,7 +16,6 @@
 
 import("$dir_pw_build/facade.gni")
 import("$dir_pw_build/target_types.gni")
-import("$dir_pw_chrono/backend.gni")
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_unit_test/test.gni")
 import("backend.gni")
@@ -26,11 +25,6 @@
   visibility = [ ":*" ]
 }
 
-config("backend_config") {
-  include_dirs = [ "public_overrides" ]
-  visibility = [ ":*" ]
-}
-
 pw_facade("binary_semaphore") {
   backend = pw_sync_BINARY_SEMAPHORE_BACKEND
   public_configs = [ ":public_include_path" ]
@@ -59,32 +53,12 @@
   public_deps = [ "$dir_pw_preprocessor" ]
 }
 
-pw_source_set("borrow") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_sync/borrow.h" ]
-  public_deps = [
-    ":lock_annotations",
-    ":virtual_basic_lockable",
-    dir_pw_assert,
-  ]
-}
-
-pw_source_set("virtual_basic_lockable") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_sync/virtual_basic_lockable.h" ]
-  public_deps = [
-    ":lock_annotations",
-    dir_pw_polyfill,
-  ]
-}
-
 pw_facade("mutex") {
   backend = pw_sync_MUTEX_BACKEND
   public_configs = [ ":public_include_path" ]
   public = [ "public/pw_sync/mutex.h" ]
   public_deps = [
     ":lock_annotations",
-    ":virtual_basic_lockable",
     "$dir_pw_preprocessor",
   ]
   sources = [ "mutex.cc" ]
@@ -96,7 +70,6 @@
   public = [ "public/pw_sync/timed_mutex.h" ]
   public_deps = [
     ":mutex",
-    ":virtual_basic_lockable",
     "$dir_pw_chrono:system_clock",
     "$dir_pw_preprocessor",
   ]
@@ -109,65 +82,11 @@
   public = [ "public/pw_sync/interrupt_spin_lock.h" ]
   public_deps = [
     ":lock_annotations",
-    ":virtual_basic_lockable",
     "$dir_pw_preprocessor",
   ]
   sources = [ "interrupt_spin_lock.cc" ]
 }
 
-pw_facade("thread_notification") {
-  backend = pw_sync_THREAD_NOTIFICATION_BACKEND
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_sync/thread_notification.h" ]
-}
-
-pw_facade("timed_thread_notification") {
-  backend = pw_sync_TIMED_THREAD_NOTIFICATION_BACKEND
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_sync/timed_thread_notification.h" ]
-  public_deps = [
-    ":thread_notification",
-    "$dir_pw_chrono:system_clock",
-  ]
-}
-
-# This target provides the backend for pw::sync::ThreadNotification based on
-# pw::sync::BinarySemaphore.
-pw_source_set("binary_semaphore_thread_notification_backend") {
-  public_configs = [
-    ":public_include_path",
-    ":backend_config",
-  ]
-  public = [
-    "public/pw_sync/backends/binary_semaphore_thread_notification_inline.h",
-    "public/pw_sync/backends/binary_semaphore_thread_notification_native.h",
-    "public_overrides/pw_sync_backend/thread_notification_inline.h",
-    "public_overrides/pw_sync_backend/thread_notification_native.h",
-  ]
-  public_deps = [
-    ":binary_semaphore",
-    ":thread_notification.facade",
-  ]
-}
-
-# This target provides the backend for pw::sync::TimedThreadNotification based
-# on pw::sync::BinarySemaphore.
-pw_source_set("binary_semaphore_timed_thread_notification_backend") {
-  public_configs = [
-    ":public_include_path",
-    ":backend_config",
-  ]
-  public = [
-    "public/pw_sync/backends/binary_semaphore_timed_thread_notification_inline.h",
-    "public_overrides/pw_sync_backend/timed_thread_notification_inline.h",
-  ]
-  public_deps = [
-    ":binary_semaphore_thread_notification_backend",
-    ":timed_thread_notification.facade",
-    "$dir_pw_chrono:system_clock",
-  ]
-}
-
 pw_source_set("yield_core") {
   public = [ "public/pw_sync/yield_core.h" ]
   public_configs = [ ":public_include_path" ]
@@ -175,23 +94,11 @@
 
 pw_test_group("tests") {
   tests = [
-    ":borrow_test",
     ":binary_semaphore_facade_test",
     ":counting_semaphore_facade_test",
     ":mutex_facade_test",
     ":timed_mutex_facade_test",
     ":interrupt_spin_lock_facade_test",
-    ":thread_notification_facade_test",
-    ":timed_thread_notification_facade_test",
-  ]
-}
-
-pw_test("borrow_test") {
-  sources = [ "borrow_test.cc" ]
-  deps = [
-    ":borrow",
-    ":virtual_basic_lockable",
-    dir_pw_assert,
   ]
 }
 
@@ -260,24 +167,6 @@
   ]
 }
 
-pw_test("thread_notification_facade_test") {
-  enable_if = pw_sync_THREAD_NOTIFICATION_BACKEND != ""
-  sources = [ "thread_notification_facade_test.cc" ]
-  deps = [
-    ":thread_notification",
-    pw_sync_THREAD_NOTIFICATION_BACKEND,
-  ]
-}
-
-pw_test("timed_thread_notification_facade_test") {
-  enable_if = pw_sync_TIMED_THREAD_NOTIFICATION_BACKEND != ""
-  sources = [ "timed_thread_notification_facade_test.cc" ]
-  deps = [
-    ":timed_thread_notification",
-    pw_sync_TIMED_THREAD_NOTIFICATION_BACKEND,
-  ]
-}
-
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
diff --git a/pw_sync/CMakeLists.txt b/pw_sync/CMakeLists.txt
index 312d6c2..69c553e 100644
--- a/pw_sync/CMakeLists.txt
+++ b/pw_sync/CMakeLists.txt
@@ -14,261 +14,10 @@
 
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
 
-pw_add_facade(pw_sync.binary_semaphore
-  HEADERS
-    public/pw_sync/binary_semaphore.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_chrono.system_clock
-    pw_preprocessor
-  SOURCES
-    binary_semaphore.cc
-)
-
-pw_add_facade(pw_sync.counting_semaphore
-  HEADERS
-    public/pw_sync/counting_semaphore.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_chrono.system_clock
-    pw_preprocessor
-  SOURCES
-    counting_semaphore.cc
-)
-
-pw_add_module_library(pw_sync.lock_annotations
-  HEADERS
-    public/pw_sync/lock_annotations.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_preprocessor
-)
-
-pw_add_module_library(pw_sync.borrow
-  HEADERS
-    public/pw_sync/borrow.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_assert
-    pw_sync.lock_annotations
-    pw_sync.virtual_basic_lockable
-)
-
-pw_add_module_library(pw_sync.virtual_basic_lockable
-  HEADERS
-    public/pw_sync/virtual_basic_lockable.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_polyfill
-    pw_sync.lock_annotations
-)
-
 pw_add_facade(pw_sync.mutex
-  HEADERS
-    public/pw_sync/mutex.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_sync.lock_annotations
-    pw_sync.virtual_basic_lockable
-    pw_preprocessor
   SOURCES
     mutex.cc
-)
-
-pw_add_facade(pw_sync.timed_mutex
-  HEADERS
-    public/pw_sync/timed_mutex.h
-  PUBLIC_INCLUDES
-    public
   PUBLIC_DEPS
     pw_chrono.system_clock
     pw_preprocessor
-    pw_sync.mutex
-    pw_sync.virtual_basic_lockable
-  SOURCES
-    timed_mutex.cc
 )
-
-pw_add_facade(pw_sync.interrupt_spin_lock
-  HEADERS
-    public/pw_sync/interrupt_spin_lock.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_sync.lock_annotations
-    pw_sync.virtual_basic_lockable
-    pw_preprocessor
-  SOURCES
-    interrupt_spin_lock.cc
-)
-
-pw_add_facade(pw_sync.thread_notification
-  HEADERS
-    public/pw_sync/thread_notification.h
-  PUBLIC_INCLUDES
-    public
-)
-
-pw_add_facade(pw_sync.timed_thread_notification
-  HEADERS
-    public/pw_sync/timed_thread_notification.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_sync.thread_notification
-    pw_chrono.system_clock
-)
-
-# This target provides the backend for pw::sync::ThreadNotification based on
-# pw::sync::BinarySemaphore.
-pw_add_module_library(pw_sync.binary_semaphore_thread_notification_backend
-  HEADERS
-    public/pw_sync/backends/binary_semaphore_thread_notification_inline.h
-    public/pw_sync/backends/binary_semaphore_thread_notification_native.h
-    public_overrides/pw_sync_backend/thread_notification_inline.h
-    public_overrides/pw_sync_backend/thread_notification_native.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides
-  PUBLIC_DEPS
-    pw_sync.binary_semaphore
-)
-
-# This target provides the backend for pw::sync::TimedThreadNotification based
-# on pw::sync::BinarySemaphore.
-pw_add_module_library(pw_sync.binary_semaphore_timed_thread_notification_backend
-  HEADERS
-    public/pw_sync/backends/binary_semaphore_timed_thread_notification_inline.h
-    public_overrides/pw_sync_backend/timed_thread_notification_inline.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides
-  PUBLIC_DEPS
-    pw_chrono.system_clock
-    pw_sync.binary_semaphore_thread_notification_backend
-)
-
-pw_add_module_library(pw_sync.yield_core
-  HEADERS
-    public/pw_sync/yield_core.h
-  PUBLIC_INCLUDES
-    public
-)
-
-pw_add_test(pw_sync.borrow_test
-  SOURCES
-    borrow_test.cc
-  DEPS
-    pw_assert
-    pw_sync.borrow
-    pw_sync.virtual_basic_lockable
-  GROUPS
-    modules
-    pw_sync
-)
-
-if(NOT "${pw_sync.binary_semaphore_BACKEND}" STREQUAL
-   "pw_sync.binary_semaphore.NO_BACKEND_SET")
-  pw_add_test(pw_sync.binary_semaphore_facade_test
-    SOURCES
-      binary_semaphore_facade_test.cc
-      binary_semaphore_facade_test_c.c
-    DEPS
-      pw_preprocessor
-      pw_sync.binary_semaphore
-    GROUPS
-      modules
-      pw_sync
-  )
-endif()
-
-if(NOT "${pw_sync.counting_semaphore_BACKEND}" STREQUAL
-   "pw_sync.counting_semaphore.NO_BACKEND_SET")
-  pw_add_test(pw_sync.counting_semaphore_facade_test
-    SOURCES
-      counting_semaphore_facade_test.cc
-      counting_semaphore_facade_test_c.c
-    DEPS
-      pw_preprocessor
-      pw_sync.counting_semaphore
-    GROUPS
-      modules
-      pw_sync
-  )
-endif()
-
-if(NOT "${pw_sync.mutex_BACKEND}" STREQUAL "pw_sync.mutex.NO_BACKEND_SET")
-  pw_add_test(pw_sync.mutex_facade_test
-    SOURCES
-      mutex_facade_test.cc
-      mutex_facade_test_c.c
-    DEPS
-      pw_preprocessor
-      pw_sync.mutex
-    GROUPS
-      modules
-      pw_sync
-  )
-endif()
-
-if(NOT "${pw_sync.timed_mutex_BACKEND}" STREQUAL
-   "pw_sync.timed_mutex.NO_BACKEND_SET")
-  pw_add_test(pw_sync.timed_mutex_facade_test
-    SOURCES
-      timed_mutex_facade_test.cc
-      timed_mutex_facade_test_c.c
-    DEPS
-      pw_preprocessor
-      pw_sync.timed_mutex
-    GROUPS
-      modules
-      pw_sync
-  )
-endif()
-
-if(NOT "${pw_sync.interrupt_spin_lock_BACKEND}" STREQUAL
-   "pw_sync.interrupt_spin_lock.NO_BACKEND_SET")
-  pw_add_test(pw_sync.interrupt_spin_lock_facade_test
-    SOURCES
-      interrupt_spin_lock_facade_test.cc
-      interrupt_spin_lock_facade_test_c.c
-    DEPS
-      pw_preprocessor
-      pw_sync.interrupt_spin_lock
-    GROUPS
-      modules
-      pw_sync
-  )
-endif()
-
-if(NOT "${pw_sync.thread_notification_BACKEND}" STREQUAL
-   "pw_sync.thread_notification.NO_BACKEND_SET")
-  pw_add_test(pw_sync.thread_notification_facade_test
-    SOURCES
-      thread_notification_facade_test.cc
-    DEPS
-      pw_sync.thread_notification
-    GROUPS
-      modules
-      pw_sync
-  )
-endif()
-
-if(NOT "${pw_sync.timed_thread_notification_BACKEND}" STREQUAL
-   "pw_sync.timed_thread_notification.NO_BACKEND_SET")
-  pw_add_test(pw_sync.timed_thread_notification_facade_test
-    SOURCES
-      timed_thread_notification_facade_test.cc
-    DEPS
-      pw_sync.timed_thread_notification
-    GROUPS
-      modules
-      pw_sync
-  )
-endif()
diff --git a/pw_sync/OWNERS b/pw_sync/OWNERS
deleted file mode 100644
index e9a6c58..0000000
--- a/pw_sync/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-ewout@google.com
-hepler@google.com
diff --git a/pw_sync/backend.gni b/pw_sync/backend.gni
index 9712a45..8f6605a 100644
--- a/pw_sync/backend.gni
+++ b/pw_sync/backend.gni
@@ -28,12 +28,6 @@
   # Backend for the pw_sync module's interrupt spin lock.
   pw_sync_INTERRUPT_SPIN_LOCK_BACKEND = ""
 
-  # Backend for the pw_sync module's thread notification.
-  pw_sync_THREAD_NOTIFICATION_BACKEND = ""
-
-  # Backend for the pw_sync module's timed thread notification.
-  pw_sync_TIMED_THREAD_NOTIFICATION_BACKEND = ""
-
   # Whether the GN asserts should be silenced in ensuring that a compatible
   # backend for pw_chrono_SYSTEM_CLOCK_BACKEND is chosen.
   # Set to true to disable the asserts.
diff --git a/pw_sync/binary_semaphore.cc b/pw_sync/binary_semaphore.cc
index 37786fc..5500964 100644
--- a/pw_sync/binary_semaphore.cc
+++ b/pw_sync/binary_semaphore.cc
@@ -33,15 +33,15 @@
 
 extern "C" bool pw_sync_BinarySemaphore_TryAcquireFor(
     pw_sync_BinarySemaphore* semaphore,
-    pw_chrono_SystemClock_Duration timeout) {
-  return semaphore->try_acquire_for(SystemClock::duration(timeout.ticks));
+    pw_chrono_SystemClock_Duration for_at_least) {
+  return semaphore->try_acquire_for(SystemClock::duration(for_at_least.ticks));
 }
 
 extern "C" bool pw_sync_BinarySemaphore_TryAcquireUntil(
     pw_sync_BinarySemaphore* semaphore,
-    pw_chrono_SystemClock_TimePoint deadline) {
+    pw_chrono_SystemClock_TimePoint until_at_least) {
   return semaphore->try_acquire_until(SystemClock::time_point(
-      SystemClock::duration(deadline.duration_since_epoch.ticks)));
+      SystemClock::duration(until_at_least.duration_since_epoch.ticks)));
 }
 
 extern "C" ptrdiff_t pw_sync_BinarySemaphore_Max(void) {
diff --git a/pw_sync/binary_semaphore_facade_test.cc b/pw_sync/binary_semaphore_facade_test.cc
index 7f9ce30..b3d136f 100644
--- a/pw_sync/binary_semaphore_facade_test.cc
+++ b/pw_sync/binary_semaphore_facade_test.cc
@@ -32,10 +32,11 @@
 void pw_sync_BinarySemaphore_CallAcquire(pw_sync_BinarySemaphore* semaphore);
 bool pw_sync_BinarySemaphore_CallTryAcquire(pw_sync_BinarySemaphore* semaphore);
 bool pw_sync_BinarySemaphore_CallTryAcquireFor(
-    pw_sync_BinarySemaphore* semaphore, pw_chrono_SystemClock_Duration timeout);
+    pw_sync_BinarySemaphore* semaphore,
+    pw_chrono_SystemClock_Duration for_at_least);
 bool pw_sync_BinarySemaphore_CallTryAcquireUntil(
     pw_sync_BinarySemaphore* semaphore,
-    pw_chrono_SystemClock_TimePoint deadline);
+    pw_chrono_SystemClock_TimePoint until_at_least);
 ptrdiff_t pw_sync_BinarySemaphore_CallMax(void);
 
 }  // extern "C"
@@ -78,92 +79,36 @@
   EXPECT_FALSE(release_semaphore.try_acquire());
 }
 
-TEST(BinarySemaphore, TryAcquireForFull) {
+TEST(BinarySemaphore, TryAcquireFor) {
   BinarySemaphore semaphore;
   semaphore.release();
 
-  // Ensure it doesn't block and succeeds if not empty.
   SystemClock::time_point before = SystemClock::now();
   EXPECT_TRUE(semaphore.try_acquire_for(kRoundedArbitraryDuration));
   SystemClock::duration time_elapsed = SystemClock::now() - before;
   EXPECT_LT(time_elapsed, kRoundedArbitraryDuration);
-}
-
-TEST(BinarySemaphore, TryAcquireForEmptyPositiveTimeout) {
-  BinarySemaphore semaphore;
 
   // Ensure it blocks and fails when empty.
-  SystemClock::time_point before = SystemClock::now();
+  before = SystemClock::now();
   EXPECT_FALSE(semaphore.try_acquire_for(kRoundedArbitraryDuration));
-  SystemClock::duration time_elapsed = SystemClock::now() - before;
+  time_elapsed = SystemClock::now() - before;
   EXPECT_GE(time_elapsed, kRoundedArbitraryDuration);
 }
 
-TEST(BinarySemaphore, TryAcquireForEmptyZeroLengthTimeout) {
-  BinarySemaphore semaphore;
-
-  // Ensure it doesn't block and fails when empty and a zero length duration is
-  // used.
-  SystemClock::time_point before = SystemClock::now();
-  EXPECT_FALSE(semaphore.try_acquire_for(SystemClock::duration::zero()));
-  SystemClock::duration time_elapsed = SystemClock::now() - before;
-  EXPECT_LT(time_elapsed, kRoundedArbitraryDuration);
-}
-
-TEST(BinarySemaphore, TryAcquireForEmptyNegativeTimeout) {
-  BinarySemaphore semaphore;
-
-  // Ensure it doesn't block and fails when empty and a negative duration is
-  // used.
-  SystemClock::time_point before = SystemClock::now();
-  EXPECT_FALSE(semaphore.try_acquire_for(-kRoundedArbitraryDuration));
-  SystemClock::duration time_elapsed = SystemClock::now() - before;
-  EXPECT_LT(time_elapsed, kRoundedArbitraryDuration);
-}
-
-TEST(BinarySemaphore, TryAcquireUntilFull) {
+TEST(BinarySemaphore, TryAcquireUntil) {
   BinarySemaphore semaphore;
   semaphore.release();
 
-  // Ensure it doesn't block and succeeds if not empty.
-  SystemClock::time_point deadline =
+  const SystemClock::time_point deadline =
       SystemClock::now() + kRoundedArbitraryDuration;
   EXPECT_TRUE(semaphore.try_acquire_until(deadline));
   EXPECT_LT(SystemClock::now(), deadline);
-}
-
-TEST(BinarySemaphore, TryAcquireUntilEmptyFutureDeadline) {
-  BinarySemaphore semaphore;
 
   // Ensure it blocks and fails when empty.
-  SystemClock::time_point deadline =
-      SystemClock::now() + kRoundedArbitraryDuration;
   EXPECT_FALSE(semaphore.try_acquire_until(deadline));
   EXPECT_GE(SystemClock::now(), deadline);
 }
 
-TEST(BinarySemaphore, TryAcquireUntilEmptyCurrentDeadline) {
-  BinarySemaphore semaphore;
-
-  // Ensure it doesn't block and fails when empty and now is used.
-  SystemClock::time_point deadline =
-      SystemClock::now() + kRoundedArbitraryDuration;
-  EXPECT_FALSE(semaphore.try_acquire_until(SystemClock::now()));
-  EXPECT_LT(SystemClock::now(), deadline);
-}
-
-TEST(BinarySemaphore, TryAcquireUntilEmptyPastDeadline) {
-  BinarySemaphore semaphore;
-
-  // Ensure it doesn't block and fails when empty and a timestamp in the past is
-  // used.
-  SystemClock::time_point deadline =
-      SystemClock::now() + kRoundedArbitraryDuration;
-  EXPECT_FALSE(semaphore.try_acquire_until(SystemClock::now() -
-                                           kRoundedArbitraryDuration));
-  EXPECT_LT(SystemClock::now(), deadline);
-}
-
 TEST(BinarySemaphore, EmptyInitialStateInC) {
   BinarySemaphore semaphore;
   EXPECT_FALSE(pw_sync_BinarySemaphore_CallTryAcquire(&semaphore));
@@ -178,118 +123,47 @@
   EXPECT_FALSE(pw_sync_BinarySemaphore_CallTryAcquire(&semaphore));
 }
 
-TEST(BinarySemaphore, TryAcquireForFullInC) {
+TEST(BinarySemaphore, TryAcquireForInC) {
   BinarySemaphore semaphore;
   pw_sync_BinarySemaphore_CallRelease(&semaphore);
 
-  // Ensure it doesn't block and succeeds if not empty.
   pw_chrono_SystemClock_TimePoint before = pw_chrono_SystemClock_Now();
   ASSERT_TRUE(pw_sync_BinarySemaphore_CallTryAcquireFor(
       &semaphore, kRoundedArbitraryDurationInC));
   pw_chrono_SystemClock_Duration time_elapsed =
       pw_chrono_SystemClock_TimeElapsed(before, pw_chrono_SystemClock_Now());
   EXPECT_LT(time_elapsed.ticks, kRoundedArbitraryDurationInC.ticks);
-}
-
-TEST(BinarySemaphore, TryAcquireForEmptyPositiveTimeoutInC) {
-  BinarySemaphore semaphore;
 
   // Ensure it blocks and fails when empty.
-  pw_chrono_SystemClock_TimePoint before = pw_chrono_SystemClock_Now();
+  before = pw_chrono_SystemClock_Now();
   EXPECT_FALSE(pw_sync_BinarySemaphore_CallTryAcquireFor(
       &semaphore, kRoundedArbitraryDurationInC));
-  pw_chrono_SystemClock_Duration time_elapsed =
+  time_elapsed =
       pw_chrono_SystemClock_TimeElapsed(before, pw_chrono_SystemClock_Now());
   EXPECT_GE(time_elapsed.ticks, kRoundedArbitraryDurationInC.ticks);
 }
 
-TEST(BinarySemaphore, TryAcquireForEmptyZeroLengthTimeoutInC) {
-  BinarySemaphore semaphore;
-
-  // Ensure it doesn't block and fails when empty and a zero length duration is
-  // used.
-  pw_chrono_SystemClock_TimePoint before = pw_chrono_SystemClock_Now();
-  EXPECT_FALSE(pw_sync_BinarySemaphore_CallTryAcquireFor(
-      &semaphore, PW_SYSTEM_CLOCK_MS(0)));
-  pw_chrono_SystemClock_Duration time_elapsed =
-      pw_chrono_SystemClock_TimeElapsed(before, pw_chrono_SystemClock_Now());
-  EXPECT_LT(time_elapsed.ticks, kRoundedArbitraryDurationInC.ticks);
-}
-
-TEST(BinarySemaphore, TryAcquireForEmptyNegativeTimeoutInC) {
-  BinarySemaphore semaphore;
-
-  // Ensure it doesn't block and fails when empty and a negative duration is
-  // used.
-  pw_chrono_SystemClock_TimePoint before = pw_chrono_SystemClock_Now();
-  EXPECT_FALSE(pw_sync_BinarySemaphore_CallTryAcquireFor(
-      &semaphore, PW_SYSTEM_CLOCK_MS(-kRoundedArbitraryDurationInC.ticks)));
-  pw_chrono_SystemClock_Duration time_elapsed =
-      pw_chrono_SystemClock_TimeElapsed(before, pw_chrono_SystemClock_Now());
-  EXPECT_LT(time_elapsed.ticks, kRoundedArbitraryDurationInC.ticks);
-}
-
-TEST(BinarySemaphore, TryAcquireUntilFullInC) {
+TEST(BinarySemaphore, TryAcquireUntilInC) {
   BinarySemaphore semaphore;
   pw_sync_BinarySemaphore_CallRelease(&semaphore);
 
   pw_chrono_SystemClock_TimePoint deadline;
-  deadline.duration_since_epoch.ticks =
-      pw_chrono_SystemClock_Now().duration_since_epoch.ticks +
-      kRoundedArbitraryDurationInC.ticks;
+  deadline.duration_since_epoch = {
+      .ticks = pw_chrono_SystemClock_Now().duration_since_epoch.ticks +
+               kRoundedArbitraryDurationInC.ticks,
+  };
   ASSERT_TRUE(
       pw_sync_BinarySemaphore_CallTryAcquireUntil(&semaphore, deadline));
   EXPECT_LT(pw_chrono_SystemClock_Now().duration_since_epoch.ticks,
             deadline.duration_since_epoch.ticks);
-}
-
-TEST(BinarySemaphore, TryAcquireUntilEmptyFutureDeadlineInC) {
-  BinarySemaphore semaphore;
 
   // Ensure it blocks and fails when empty.
-  pw_chrono_SystemClock_TimePoint deadline;
-  deadline.duration_since_epoch.ticks =
-      pw_chrono_SystemClock_Now().duration_since_epoch.ticks +
-      kRoundedArbitraryDurationInC.ticks;
   EXPECT_FALSE(
       pw_sync_BinarySemaphore_CallTryAcquireUntil(&semaphore, deadline));
   EXPECT_GE(pw_chrono_SystemClock_Now().duration_since_epoch.ticks,
             deadline.duration_since_epoch.ticks);
 }
 
-TEST(BinarySemaphore, TryAcquireUntilEmptyCurrentDeadlineInC) {
-  BinarySemaphore semaphore;
-
-  // Ensure it doesn't block and fails when empty and now is used.
-  pw_chrono_SystemClock_TimePoint deadline;
-  deadline.duration_since_epoch.ticks =
-      pw_chrono_SystemClock_Now().duration_since_epoch.ticks +
-      kRoundedArbitraryDurationInC.ticks;
-  EXPECT_FALSE(pw_sync_BinarySemaphore_CallTryAcquireUntil(
-      &semaphore, pw_chrono_SystemClock_Now()));
-  EXPECT_LT(pw_chrono_SystemClock_Now().duration_since_epoch.ticks,
-            deadline.duration_since_epoch.ticks);
-}
-
-TEST(BinarySemaphore, TryAcquireUntilEmptyPastDeadlineInC) {
-  BinarySemaphore semaphore;
-
-  // Ensure it doesn't block and fails when empty and a timestamp in the past is
-  // used.
-  pw_chrono_SystemClock_TimePoint deadline;
-  deadline.duration_since_epoch.ticks =
-      pw_chrono_SystemClock_Now().duration_since_epoch.ticks +
-      kRoundedArbitraryDurationInC.ticks;
-  pw_chrono_SystemClock_TimePoint old_timestamp;
-  old_timestamp.duration_since_epoch.ticks =
-      pw_chrono_SystemClock_Now().duration_since_epoch.ticks -
-      kRoundedArbitraryDurationInC.ticks;
-  EXPECT_FALSE(
-      pw_sync_BinarySemaphore_CallTryAcquireUntil(&semaphore, old_timestamp));
-  EXPECT_LT(pw_chrono_SystemClock_Now().duration_since_epoch.ticks,
-            deadline.duration_since_epoch.ticks);
-}
-
 TEST(BinarySemaphore, MaxInC) {
   EXPECT_EQ(BinarySemaphore::max(), pw_sync_BinarySemaphore_Max());
 }
diff --git a/pw_sync/binary_semaphore_facade_test_c.c b/pw_sync/binary_semaphore_facade_test_c.c
index c86b465..22dc7f6 100644
--- a/pw_sync/binary_semaphore_facade_test_c.c
+++ b/pw_sync/binary_semaphore_facade_test_c.c
@@ -34,14 +34,14 @@
 
 bool pw_sync_BinarySemaphore_CallTryAcquireFor(
     pw_sync_BinarySemaphore* semaphore,
-    pw_chrono_SystemClock_Duration timeout) {
-  return pw_sync_BinarySemaphore_TryAcquireFor(semaphore, timeout);
+    pw_chrono_SystemClock_Duration for_at_least) {
+  return pw_sync_BinarySemaphore_TryAcquireFor(semaphore, for_at_least);
 }
 
 bool pw_sync_BinarySemaphore_CallTryAcquireUntil(
     pw_sync_BinarySemaphore* semaphore,
-    pw_chrono_SystemClock_TimePoint deadline) {
-  return pw_sync_BinarySemaphore_TryAcquireUntil(semaphore, deadline);
+    pw_chrono_SystemClock_TimePoint until_at_least) {
+  return pw_sync_BinarySemaphore_TryAcquireUntil(semaphore, until_at_least);
 }
 
 ptrdiff_t pw_sync_BinarySemaphore_CallMax(void) {
diff --git a/pw_sync/borrow_test.cc b/pw_sync/borrow_test.cc
deleted file mode 100644
index a3d6ba3..0000000
--- a/pw_sync/borrow_test.cc
+++ /dev/null
@@ -1,316 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_sync/borrow.h"
-
-#include <chrono>
-#include <ratio>
-
-#include "gtest/gtest.h"
-#include "pw_assert/check.h"
-#include "pw_sync/virtual_basic_lockable.h"
-
-namespace pw::sync {
-namespace {
-
-template <typename Lock>
-class BorrowableTest : public ::testing::Test {
- protected:
-  static constexpr int kInitialValue = 42;
-
-  BorrowableTest()
-      : foo_{.value = kInitialValue}, borrowable_foo_(foo_, lock_) {}
-
-  void SetUp() override {
-    EXPECT_FALSE(lock_.locked());  // Ensure it's not locked on construction.
-  }
-
-  struct Foo {
-    int value;
-  };
-  Lock lock_;
-  Foo foo_;
-  Borrowable<Foo, Lock> borrowable_foo_;
-};
-
-class BasicLockable : public VirtualBasicLockable {
- public:
-  virtual ~BasicLockable() = default;
-
-  bool locked() const { return locked_; }
-
- protected:
-  bool locked_ = false;
-
- private:
-  void DoLockOperation(Operation operation) override {
-    switch (operation) {
-      case Operation::kLock:
-        PW_CHECK(!locked_, "Recursive lock detected");
-        locked_ = true;
-        return;
-
-      case Operation::kUnlock:
-      default:
-        PW_CHECK(locked_, "Unlock while unlocked detected");
-        locked_ = false;
-        return;
-    }
-  }
-};
-
-using BorrowableBasicLockableTest = BorrowableTest<BasicLockable>;
-
-TEST_F(BorrowableBasicLockableTest, Acquire) {
-  {
-    BorrowedPointer<Foo, BasicLockable> borrowed_foo =
-        borrowable_foo_.acquire();
-    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
-    EXPECT_EQ(borrowed_foo->value, kInitialValue);
-    borrowed_foo->value = 13;
-  }
-  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
-  EXPECT_EQ(foo_.value, 13);
-}
-
-TEST_F(BorrowableBasicLockableTest, RepeatedAcquire) {
-  {
-    BorrowedPointer<Foo, BasicLockable> borrowed_foo =
-        borrowable_foo_.acquire();
-    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
-    EXPECT_EQ(borrowed_foo->value, kInitialValue);
-    borrowed_foo->value = 13;
-  }
-  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
-  {
-    BorrowedPointer<Foo, BasicLockable> borrowed_foo =
-        borrowable_foo_.acquire();
-    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
-    EXPECT_EQ(borrowed_foo->value, 13);
-  }
-  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
-}
-
-TEST_F(BorrowableBasicLockableTest, Moveable) {
-  Borrowable<Foo, BasicLockable> borrowable_foo = std::move(borrowable_foo_);
-  {
-    BorrowedPointer<Foo, BasicLockable> borrowed_foo = borrowable_foo.acquire();
-    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
-    EXPECT_EQ(borrowed_foo->value, kInitialValue);
-    borrowed_foo->value = 13;
-  }
-  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
-}
-
-TEST_F(BorrowableBasicLockableTest, Copyable) {
-  const Borrowable<Foo, BasicLockable>& other = borrowable_foo_;
-  Borrowable<Foo, BasicLockable> borrowable_foo(other);
-  {
-    BorrowedPointer<Foo, BasicLockable> borrowed_foo = borrowable_foo.acquire();
-    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
-    EXPECT_EQ(borrowed_foo->value, kInitialValue);
-    borrowed_foo->value = 13;
-  }
-  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
-}
-
-class Lockable : public BasicLockable {
- public:
-  bool try_lock() {
-    if (locked()) {
-      return false;
-    }
-    locked_ = true;
-    return true;
-  }
-};
-
-using BorrowableLockableTest = BorrowableTest<Lockable>;
-
-TEST_F(BorrowableLockableTest, Acquire) {
-  {
-    BorrowedPointer<Foo, Lockable> borrowed_foo = borrowable_foo_.acquire();
-    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
-    EXPECT_EQ(borrowed_foo->value, kInitialValue);
-    borrowed_foo->value = 13;
-  }
-  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
-  EXPECT_EQ(foo_.value, 13);
-}
-
-TEST_F(BorrowableLockableTest, RepeatedAcquire) {
-  {
-    BorrowedPointer<Foo, Lockable> borrowed_foo = borrowable_foo_.acquire();
-    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
-    EXPECT_EQ(borrowed_foo->value, kInitialValue);
-    borrowed_foo->value = 13;
-  }
-  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
-  {
-    BorrowedPointer<Foo, Lockable> borrowed_foo = borrowable_foo_.acquire();
-    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
-    EXPECT_EQ(borrowed_foo->value, 13);
-  }
-  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
-}
-
-TEST_F(BorrowableLockableTest, TryAcquireSuccess) {
-  {
-    std::optional<BorrowedPointer<Foo, Lockable>> maybe_borrowed_foo =
-        borrowable_foo_.try_acquire();
-    ASSERT_TRUE(maybe_borrowed_foo.has_value());
-    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
-    EXPECT_EQ(maybe_borrowed_foo.value()->value, kInitialValue);
-  }
-  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
-}
-
-TEST_F(BorrowableLockableTest, TryAcquireFailure) {
-  lock_.lock();
-  EXPECT_TRUE(lock_.locked());
-  {
-    std::optional<BorrowedPointer<Foo, Lockable>> maybe_borrowed_foo =
-        borrowable_foo_.try_acquire();
-    EXPECT_FALSE(maybe_borrowed_foo.has_value());
-  }
-  EXPECT_TRUE(lock_.locked());
-  lock_.unlock();
-}
-
-struct Clock {
-  using rep = int64_t;
-  using period = std::micro;
-  using duration = std::chrono::duration<rep, period>;
-  using time_point = std::chrono::time_point<Clock>;
-};
-
-class TimedLockable : public Lockable {
- public:
-  bool try_lock() {
-    if (locked()) {
-      return false;
-    }
-    locked_ = true;
-    return true;
-  }
-
-  bool try_lock_for(const Clock::duration&) { return try_lock(); }
-  bool try_lock_until(const Clock::time_point&) { return try_lock(); }
-};
-
-using BorrowableTimedLockableTest = BorrowableTest<TimedLockable>;
-
-TEST_F(BorrowableTimedLockableTest, Acquire) {
-  {
-    BorrowedPointer<Foo, TimedLockable> borrowed_foo =
-        borrowable_foo_.acquire();
-    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
-    EXPECT_EQ(borrowed_foo->value, kInitialValue);
-    borrowed_foo->value = 13;
-  }
-  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
-  EXPECT_EQ(foo_.value, 13);
-}
-
-TEST_F(BorrowableTimedLockableTest, RepeatedAcquire) {
-  {
-    BorrowedPointer<Foo, TimedLockable> borrowed_foo =
-        borrowable_foo_.acquire();
-    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
-    EXPECT_EQ(borrowed_foo->value, kInitialValue);
-    borrowed_foo->value = 13;
-  }
-  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
-  {
-    BorrowedPointer<Foo, TimedLockable> borrowed_foo =
-        borrowable_foo_.acquire();
-    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
-    EXPECT_EQ(borrowed_foo->value, 13);
-  }
-  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
-}
-
-TEST_F(BorrowableTimedLockableTest, TryAcquireSuccess) {
-  {
-    std::optional<BorrowedPointer<Foo, TimedLockable>> maybe_borrowed_foo =
-        borrowable_foo_.try_acquire();
-    ASSERT_TRUE(maybe_borrowed_foo.has_value());
-    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
-    EXPECT_EQ(maybe_borrowed_foo.value()->value, kInitialValue);
-  }
-  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
-}
-
-TEST_F(BorrowableTimedLockableTest, TryAcquireFailure) {
-  lock_.lock();
-  EXPECT_TRUE(lock_.locked());
-  {
-    std::optional<BorrowedPointer<Foo, TimedLockable>> maybe_borrowed_foo =
-        borrowable_foo_.try_acquire();
-    EXPECT_FALSE(maybe_borrowed_foo.has_value());
-  }
-  EXPECT_TRUE(lock_.locked());
-  lock_.unlock();
-}
-
-TEST_F(BorrowableTimedLockableTest, TryAcquireForSuccess) {
-  {
-    std::optional<BorrowedPointer<Foo, TimedLockable>> maybe_borrowed_foo =
-        borrowable_foo_.try_acquire_for(std::chrono::seconds(0));
-    ASSERT_TRUE(maybe_borrowed_foo.has_value());
-    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
-    EXPECT_EQ(maybe_borrowed_foo.value()->value, kInitialValue);
-  }
-  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
-}
-
-TEST_F(BorrowableTimedLockableTest, TryAcquireForFailure) {
-  lock_.lock();
-  EXPECT_TRUE(lock_.locked());
-  {
-    std::optional<BorrowedPointer<Foo, TimedLockable>> maybe_borrowed_foo =
-        borrowable_foo_.try_acquire_for(std::chrono::seconds(0));
-    EXPECT_FALSE(maybe_borrowed_foo.has_value());
-  }
-  EXPECT_TRUE(lock_.locked());
-  lock_.unlock();
-}
-
-TEST_F(BorrowableTimedLockableTest, TryAcquireUntilSuccess) {
-  {
-    std::optional<BorrowedPointer<Foo, TimedLockable>> maybe_borrowed_foo =
-        borrowable_foo_.try_acquire_until(
-            Clock::time_point(std::chrono::seconds(0)));
-    ASSERT_TRUE(maybe_borrowed_foo.has_value());
-    EXPECT_TRUE(lock_.locked());  // Ensure the lock is held.
-    EXPECT_EQ(maybe_borrowed_foo.value()->value, kInitialValue);
-  }
-  EXPECT_FALSE(lock_.locked());  // Ensure the lock is released.
-}
-
-TEST_F(BorrowableTimedLockableTest, TryAcquireUntilFailure) {
-  lock_.lock();
-  EXPECT_TRUE(lock_.locked());
-  {
-    std::optional<BorrowedPointer<Foo, TimedLockable>> maybe_borrowed_foo =
-        borrowable_foo_.try_acquire_until(
-            Clock::time_point(std::chrono::seconds(0)));
-    EXPECT_FALSE(maybe_borrowed_foo.has_value());
-  }
-  EXPECT_TRUE(lock_.locked());
-  lock_.unlock();
-}
-
-}  // namespace
-}  // namespace pw::sync
diff --git a/pw_sync/counting_semaphore.cc b/pw_sync/counting_semaphore.cc
index 4b486b3..1b7ad3f 100644
--- a/pw_sync/counting_semaphore.cc
+++ b/pw_sync/counting_semaphore.cc
@@ -38,15 +38,15 @@
 
 extern "C" bool pw_sync_CountingSemaphore_TryAcquireFor(
     pw_sync_CountingSemaphore* semaphore,
-    pw_chrono_SystemClock_Duration timeout) {
-  return semaphore->try_acquire_for(SystemClock::duration(timeout.ticks));
+    pw_chrono_SystemClock_Duration for_at_least) {
+  return semaphore->try_acquire_for(SystemClock::duration(for_at_least.ticks));
 }
 
 extern "C" bool pw_sync_CountingSemaphore_TryAcquireUntil(
     pw_sync_CountingSemaphore* semaphore,
-    pw_chrono_SystemClock_TimePoint deadline) {
+    pw_chrono_SystemClock_TimePoint until_at_least) {
   return semaphore->try_acquire_until(SystemClock::time_point(
-      SystemClock::duration(deadline.duration_since_epoch.ticks)));
+      SystemClock::duration(until_at_least.duration_since_epoch.ticks)));
 }
 
 extern "C" ptrdiff_t pw_sync_CountingSemaphore_Max(void) {
diff --git a/pw_sync/counting_semaphore_facade_test.cc b/pw_sync/counting_semaphore_facade_test.cc
index 2fcc983..d086a90 100644
--- a/pw_sync/counting_semaphore_facade_test.cc
+++ b/pw_sync/counting_semaphore_facade_test.cc
@@ -38,10 +38,10 @@
     pw_sync_CountingSemaphore* semaphore);
 bool pw_sync_CountingSemaphore_CallTryAcquireFor(
     pw_sync_CountingSemaphore* semaphore,
-    pw_chrono_SystemClock_Duration timeout);
+    pw_chrono_SystemClock_Duration for_at_least);
 bool pw_sync_CountingSemaphore_CallTryAcquireUntil(
     pw_sync_CountingSemaphore* semaphore,
-    pw_chrono_SystemClock_TimePoint deadline);
+    pw_chrono_SystemClock_TimePoint until_at_least);
 ptrdiff_t pw_sync_CountingSemaphore_CallMax(void);
 
 }  // extern "C"
@@ -97,91 +97,36 @@
   EXPECT_FALSE(semaphore.try_acquire());
 }
 
-TEST(CountingSemaphore, TryAcquireForFull) {
+TEST(CountingSemaphore, TryAcquireFor) {
   CountingSemaphore semaphore;
   semaphore.release();
 
-  // Ensure it doesn't block and succeeds if not empty.
   SystemClock::time_point before = SystemClock::now();
   EXPECT_TRUE(semaphore.try_acquire_for(kRoundedArbitraryDuration));
   SystemClock::duration time_elapsed = SystemClock::now() - before;
   EXPECT_LT(time_elapsed, kRoundedArbitraryDuration);
-}
-
-TEST(CountingSemaphore, TryAcquireForEmptyPositiveTimeout) {
-  CountingSemaphore semaphore;
 
   // Ensure it blocks and fails when empty.
-  SystemClock::time_point before = SystemClock::now();
+  before = SystemClock::now();
   EXPECT_FALSE(semaphore.try_acquire_for(kRoundedArbitraryDuration));
-  SystemClock::duration time_elapsed = SystemClock::now() - before;
+  time_elapsed = SystemClock::now() - before;
   EXPECT_GE(time_elapsed, kRoundedArbitraryDuration);
 }
 
-TEST(CountingSemaphore, TryAcquireForEmptyZeroLengthTimeout) {
-  CountingSemaphore semaphore;
-
-  // Ensure it doesn't block and fails when empty and a zero length duration is
-  // used.
-  SystemClock::time_point before = SystemClock::now();
-  EXPECT_FALSE(semaphore.try_acquire_for(SystemClock::duration::zero()));
-  SystemClock::duration time_elapsed = SystemClock::now() - before;
-  EXPECT_LT(time_elapsed, kRoundedArbitraryDuration);
-}
-
-TEST(CountingSemaphore, TryAcquireForEmptyNegativeTimeout) {
-  CountingSemaphore semaphore;
-
-  // Ensure it doesn't block and fails when empty and a negative duration is
-  // used.
-  SystemClock::time_point before = SystemClock::now();
-  EXPECT_FALSE(semaphore.try_acquire_for(-kRoundedArbitraryDuration));
-  SystemClock::duration time_elapsed = SystemClock::now() - before;
-  EXPECT_LT(time_elapsed, kRoundedArbitraryDuration);
-}
-
-TEST(CountingSemaphore, TryAcquireUntilFull) {
+TEST(CountingSemaphore, TryAcquireUntil) {
   CountingSemaphore semaphore;
   semaphore.release();
 
-  // Ensure it doesn't block and succeeds if not empty.
-  SystemClock::time_point deadline =
+  const SystemClock::time_point deadline =
       SystemClock::now() + kRoundedArbitraryDuration;
   EXPECT_TRUE(semaphore.try_acquire_until(deadline));
   EXPECT_LT(SystemClock::now(), deadline);
-}
-
-TEST(CountingSemaphore, TryAcquireUntilEmptyFutureDeadline) {
-  CountingSemaphore semaphore;
 
   // Ensure it blocks and fails when empty.
-  SystemClock::time_point deadline =
-      SystemClock::now() + kRoundedArbitraryDuration;
   EXPECT_FALSE(semaphore.try_acquire_until(deadline));
   EXPECT_GE(SystemClock::now(), deadline);
 }
 
-TEST(CountingSemaphore, TryAcquireUntilEmptyCurrentDeadline) {
-  CountingSemaphore semaphore;
-
-  // Ensure it doesn't block and fails when empty and now is used.
-  SystemClock::time_point deadline =
-      SystemClock::now() + kRoundedArbitraryDuration;
-  EXPECT_FALSE(semaphore.try_acquire_until(SystemClock::now()));
-  EXPECT_LT(SystemClock::now(), deadline);
-}
-
-TEST(CountingSemaphore, TryAcquireUntilEmptyPastDeadline) {
-  CountingSemaphore semaphore;
-  // Ensure it doesn't block and fails when empty and a timestamp in the past is
-  // used.
-  SystemClock::time_point deadline =
-      SystemClock::now() + kRoundedArbitraryDuration;
-  EXPECT_FALSE(semaphore.try_acquire_until(SystemClock::now() -
-                                           kRoundedArbitraryDuration));
-  EXPECT_LT(SystemClock::now(), deadline);
-}
-
 TEST(CountingSemaphore, EmptyInitialStateInC) {
   CountingSemaphore semaphore;
   EXPECT_FALSE(pw_sync_CountingSemaphore_CallTryAcquire(&semaphore));
@@ -208,119 +153,47 @@
   EXPECT_FALSE(pw_sync_CountingSemaphore_CallTryAcquire(&semaphore));
 }
 
-TEST(CountingSemaphore, TryAcquireForFullInC) {
+TEST(CountingSemaphore, TryAcquireForInC) {
   CountingSemaphore semaphore;
   pw_sync_CountingSemaphore_CallRelease(&semaphore);
 
-  // Ensure it doesn't block and succeeds if not empty.
   pw_chrono_SystemClock_TimePoint before = pw_chrono_SystemClock_Now();
   ASSERT_TRUE(pw_sync_CountingSemaphore_CallTryAcquireFor(
       &semaphore, kRoundedArbitraryDurationInC));
   pw_chrono_SystemClock_Duration time_elapsed =
       pw_chrono_SystemClock_TimeElapsed(before, pw_chrono_SystemClock_Now());
   EXPECT_LT(time_elapsed.ticks, kRoundedArbitraryDurationInC.ticks);
-}
-
-TEST(CountingSemaphore, TryAcquireForEmptyPositiveTimeoutInC) {
-  CountingSemaphore semaphore;
 
   // Ensure it blocks and fails when empty.
-  pw_chrono_SystemClock_TimePoint before = pw_chrono_SystemClock_Now();
+  before = pw_chrono_SystemClock_Now();
   EXPECT_FALSE(pw_sync_CountingSemaphore_CallTryAcquireFor(
       &semaphore, kRoundedArbitraryDurationInC));
-  pw_chrono_SystemClock_Duration time_elapsed =
+  time_elapsed =
       pw_chrono_SystemClock_TimeElapsed(before, pw_chrono_SystemClock_Now());
   EXPECT_GE(time_elapsed.ticks, kRoundedArbitraryDurationInC.ticks);
 }
 
-TEST(CountingSemaphore, TryAcquireForEmptyZeroLengthTimeoutInC) {
-  CountingSemaphore semaphore;
-
-  // Ensure it doesn't block and fails when empty and a zero length duration is
-  // used.
-  pw_chrono_SystemClock_TimePoint before = pw_chrono_SystemClock_Now();
-  EXPECT_FALSE(pw_sync_CountingSemaphore_CallTryAcquireFor(
-      &semaphore, PW_SYSTEM_CLOCK_MS(0)));
-  pw_chrono_SystemClock_Duration time_elapsed =
-      pw_chrono_SystemClock_TimeElapsed(before, pw_chrono_SystemClock_Now());
-  EXPECT_LT(time_elapsed.ticks, kRoundedArbitraryDurationInC.ticks);
-}
-
-TEST(CountingSemaphore, TryAcquireForEmptyNegativeTimeoutInC) {
-  CountingSemaphore semaphore;
-
-  // Ensure it doesn't block and fails when empty and a negative duration is
-  // used.
-  pw_chrono_SystemClock_TimePoint before = pw_chrono_SystemClock_Now();
-  EXPECT_FALSE(pw_sync_CountingSemaphore_CallTryAcquireFor(
-      &semaphore, PW_SYSTEM_CLOCK_MS(-kRoundedArbitraryDurationInC.ticks)));
-  pw_chrono_SystemClock_Duration time_elapsed =
-      pw_chrono_SystemClock_TimeElapsed(before, pw_chrono_SystemClock_Now());
-  EXPECT_LT(time_elapsed.ticks, kRoundedArbitraryDurationInC.ticks);
-}
-
-TEST(CountingSemaphore, TryAcquireUntilFullInC) {
+TEST(CountingSemaphore, TryAcquireUntilInC) {
   CountingSemaphore semaphore;
   pw_sync_CountingSemaphore_CallRelease(&semaphore);
 
-  // Ensure it doesn't block and succeeds if not empty.
   pw_chrono_SystemClock_TimePoint deadline;
-  deadline.duration_since_epoch.ticks =
-      pw_chrono_SystemClock_Now().duration_since_epoch.ticks +
-      kRoundedArbitraryDurationInC.ticks;
+  deadline.duration_since_epoch = {
+      .ticks = pw_chrono_SystemClock_Now().duration_since_epoch.ticks +
+               kRoundedArbitraryDurationInC.ticks,
+  };
   ASSERT_TRUE(
       pw_sync_CountingSemaphore_CallTryAcquireUntil(&semaphore, deadline));
   EXPECT_LT(pw_chrono_SystemClock_Now().duration_since_epoch.ticks,
             deadline.duration_since_epoch.ticks);
-}
-
-TEST(CountingSemaphore, TryAcquireUntilEmptyFutureDeadlineInC) {
-  CountingSemaphore semaphore;
 
   // Ensure it blocks and fails when empty.
-  pw_chrono_SystemClock_TimePoint deadline;
-  deadline.duration_since_epoch.ticks =
-      pw_chrono_SystemClock_Now().duration_since_epoch.ticks +
-      kRoundedArbitraryDurationInC.ticks;
   EXPECT_FALSE(
       pw_sync_CountingSemaphore_CallTryAcquireUntil(&semaphore, deadline));
   EXPECT_GE(pw_chrono_SystemClock_Now().duration_since_epoch.ticks,
             deadline.duration_since_epoch.ticks);
 }
 
-TEST(CountingSemaphore, TryAcquireUntilEmptyCurrentDeadlineInC) {
-  CountingSemaphore semaphore;
-
-  // Ensure it doesn't block and fails when empty and now is used.
-  pw_chrono_SystemClock_TimePoint deadline;
-  deadline.duration_since_epoch.ticks =
-      pw_chrono_SystemClock_Now().duration_since_epoch.ticks +
-      kRoundedArbitraryDurationInC.ticks;
-  EXPECT_FALSE(pw_sync_CountingSemaphore_CallTryAcquireUntil(
-      &semaphore, pw_chrono_SystemClock_Now()));
-  EXPECT_LT(pw_chrono_SystemClock_Now().duration_since_epoch.ticks,
-            deadline.duration_since_epoch.ticks);
-}
-
-TEST(CountingSemaphore, TryAcquireUntilEmptyPastDeadlineInC) {
-  CountingSemaphore semaphore;
-
-  // Ensure it doesn't block and fails when empty and a timestamp in the past is
-  // used.
-  pw_chrono_SystemClock_TimePoint deadline;
-  deadline.duration_since_epoch.ticks =
-      pw_chrono_SystemClock_Now().duration_since_epoch.ticks +
-      kRoundedArbitraryDurationInC.ticks;
-  pw_chrono_SystemClock_TimePoint old_timestamp;
-  old_timestamp.duration_since_epoch.ticks =
-      pw_chrono_SystemClock_Now().duration_since_epoch.ticks -
-      kRoundedArbitraryDurationInC.ticks;
-  EXPECT_FALSE(
-      pw_sync_CountingSemaphore_CallTryAcquireUntil(&semaphore, old_timestamp));
-  EXPECT_LT(pw_chrono_SystemClock_Now().duration_since_epoch.ticks,
-            deadline.duration_since_epoch.ticks);
-}
-
 TEST(CountingSemaphore, MaxInC) {
   EXPECT_EQ(CountingSemaphore::max(), pw_sync_CountingSemaphore_Max());
 }
diff --git a/pw_sync/counting_semaphore_facade_test_c.c b/pw_sync/counting_semaphore_facade_test_c.c
index 8cf1789..93db6d1 100644
--- a/pw_sync/counting_semaphore_facade_test_c.c
+++ b/pw_sync/counting_semaphore_facade_test_c.c
@@ -41,14 +41,14 @@
 
 bool pw_sync_CountingSemaphore_CallTryAcquireFor(
     pw_sync_CountingSemaphore* semaphore,
-    pw_chrono_SystemClock_Duration timeout) {
-  return pw_sync_CountingSemaphore_TryAcquireFor(semaphore, timeout);
+    pw_chrono_SystemClock_Duration for_at_least) {
+  return pw_sync_CountingSemaphore_TryAcquireFor(semaphore, for_at_least);
 }
 
 bool pw_sync_CountingSemaphore_CallTryAcquireUntil(
     pw_sync_CountingSemaphore* semaphore,
-    pw_chrono_SystemClock_TimePoint deadline) {
-  return pw_sync_CountingSemaphore_TryAcquireUntil(semaphore, deadline);
+    pw_chrono_SystemClock_TimePoint until_at_least) {
+  return pw_sync_CountingSemaphore_TryAcquireUntil(semaphore, until_at_least);
 }
 
 ptrdiff_t pw_sync_CountingSemaphore_CallMax(void) {
diff --git a/pw_sync/docs.rst b/pw_sync/docs.rst
index 201cdf0..f2959c8 100644
--- a/pw_sync/docs.rst
+++ b/pw_sync/docs.rst
@@ -7,6 +7,10 @@
 and/or interrupts through signaling primitives and critical section lock
 primitives.
 
+.. contents::
+   :local:
+   :depth: 2
+
 .. Warning::
   This module is still under construction, the API is not yet stable.
 
@@ -78,7 +82,7 @@
 
   .. cpp:function:: bool try_lock()
 
-     Tries to lock the mutex in a non-blocking manner.
+     Attempts to lock the mutex in a non-blocking manner.
      Returns true if the mutex was successfully acquired.
 
      **Precondition:** The lock isn't already held by this thread. Recursive
@@ -215,7 +219,7 @@
 
 Note that the ``TimedMutex`` is a derived ``Mutex`` class, meaning that
 a ``TimedMutex`` can be used by someone who needs the basic ``Mutex``. This is
-in contrast to the C++ STL's
+in stark contrast to the C++ STL's
 `std::timed_mutex <https://en.cppreference.com/w/cpp/thread/timed_mutex>`_.
 
 
@@ -249,26 +253,25 @@
 
   .. cpp:function:: bool try_lock()
 
-     Tries to lock the mutex in a non-blocking manner.
+     Attempts to lock the mutex in a non-blocking manner.
      Returns true if the mutex was successfully acquired.
 
      **Precondition:** The lock isn't already held by this thread. Recursive
      locking is undefined behavior.
 
+  .. cpp:function:: bool try_lock_for(chrono::SystemClock::duration for_at_least)
 
-  .. cpp:function:: bool try_lock_for(const chrono::SystemClock::duration& timeout)
-
-     Tries to lock the mutex. Blocks until specified the timeout has elapsed or
-     the lock is acquired, whichever comes first.
+     Attempts to lock the mutex where, if needed, blocking for at least the
+     specified duration.
      Returns true if the mutex was successfully acquired.
 
      **Precondition:** The lock isn't already held by this thread. Recursive
      locking is undefined behavior.
 
-  .. cpp:function:: bool try_lock_until(const chrono::SystemClock::time_point& deadline)
+  .. cpp:function:: bool try_lock_until(chrono::SystemClock::time_point until_at_least)
 
-     Tries to lock the mutex. Blocks until specified deadline has been reached
-     or the lock is acquired, whichever comes first.
+     Attempts to lock the mutex where, if needed, blocking until at least the
+     specified time_point.
      Returns true if the mutex was successfully acquired.
 
      **Precondition:** The lock isn't already held by this thread. Recursive
@@ -372,11 +375,11 @@
 
   Invokes the ``TimedMutex::try_lock`` member function on the given ``mutex``.
 
-.. cpp:function:: bool pw_sync_TimedMutex_TryLockFor(pw_sync_TimedMutex* mutex, pw_chrono_SystemClock_Duration timeout)
+.. cpp:function:: bool pw_sync_TimedMutex_TryLockFor(pw_sync_TimedMutex* mutex, pw_chrono_SystemClock_Duration for_at_least)
 
   Invokes the ``TimedMutex::try_lock_for`` member function on the given ``mutex``.
 
-.. cpp:function:: bool pw_sync_TimedMutex_TryLockUntil(pw_sync_TimedMutex* mutex, pw_chrono_SystemClock_TimePoint deadline)
+.. cpp:function:: bool pw_sync_TimedMutex_TryLockUntil(pw_sync_TimedMutex* mutex, pw_chrono_SystemClock_TimePoint until_at_least)
 
   Invokes the ``TimedMutex::try_lock_until`` member function on the given ``mutex``.
 
@@ -486,7 +489,7 @@
 
   .. cpp:function:: bool try_lock()
 
-      Tries to lock the spinlock in a non-blocking manner.
+      Attempts to lock the spinlock in a non-blocking manner.
       Returns true if the spinlock was successfully acquired.
 
       **Precondition:** Recursive locking is undefined behavior.
@@ -948,248 +951,6 @@
    Documents functions that dynamically check to see if a lock is held, and fail
    if it is not held.
 
------------------------------
-Critical Section Lock Helpers
------------------------------
-
-Virtual Lock Interfaces
-=======================
-Virtual lock interfaces can be useful when lock selection cannot be templated.
-
-Why use virtual locks?
-----------------------
-Virtual locks enable depending on locks without templating implementation code
-on the type, while retaining flexibility with respect to the concrete lock type.
-Pigweed tries to avoid pushing policy on to users, and virtual locks are one way
-to accomplish that without templating everything.
-
-A case when virtual locks are useful is when the concrete lock type changes at
-run time. For example, access to flash may be protected at run time by an
-internal mutex, however at crash time we may want to switch to a no-op lock. A
-virtual lock interface could be used here to minimize the code-size cost that
-would occur otherwise if the flash driver were templated.
-
-VirtualBasicLock
-----------------
-The ``VirtualBasicLock`` interface meets the
-`BasicLockable <https://en.cppreference.com/w/cpp/named_req/BasicLockable>`_ C++
-named requirement. Our critical section lock primitives offer optional virtual
-versions, including:
-
-* ``pw::sync::VirtualMutex``
-* ``pw::sync::VirtualTimedMutex``
-* ``pw::sync::VirtualInterruptSpinLock``
-
-Borrowable
-==========
-The Borrowable is a helper construct that enables callers to borrow an object
-which is guarded by a lock, enabling a containerized style of external locking.
-
-Users who need access to the guarded object can ask to acquire a
-``BorrowedPointer`` which permits access while the lock is held.
-
-This class is compatible with locks which comply with
-`BasicLockable <https://en.cppreference.com/w/cpp/named_req/BasicLockable>`_,
-`Lockable <https://en.cppreference.com/w/cpp/named_req/Lockable>`_, and
-`TimedLockable <https://en.cppreference.com/w/cpp/named_req/TimedLockable>`_
-C++ named requirements.
-
-By default the selected lock type is a ``pw::sync::VirtualBasicLockable``. If
-this virtual interface is used, the templated lock parameter can be skipped.
-
-External vs Internal locking
-----------------------------
-Before we explain why Borrowable is useful, it's important to understand the
-trade-offs when deciding on using internal and/or external locking.
-
-Internal locking is when the lock is hidden from the caller entirely and is used
-internally to the API. For example:
-
-.. code-block:: cpp
-
-  class BankAccount {
-   public:
-    void Deposit(int amount) {
-      std::lock_guard lock(mutex_);
-      balance_ += amount;
-    }
-
-    void Withdraw(int amount) {
-      std::lock_guard lock(mutex_);
-      balance_ -= amount;
-    }
-
-    void Balance() const {
-      std::lock_guard lock(mutex_);
-      return balance_;
-    }
-
-   private:
-    int balance_ PW_GUARDED_BY(mutex_);
-    pw::sync::Mutex mutex_;
-  };
-
-Internal locking guarantees that any concurrent calls to its public member
-functions don't corrupt an instance of that class. This is typically ensured by
-having each member function acquire a lock on the object upon entry. This way,
-for any instance, there can only be one member function call active at any
-moment, serializing the operations.
-
-One common issue that pops up is that member functions may have to call other
-member functions which also require locks. This typically results in a
-duplication of the public API into an internal mirror where the lock is already
-held. This along with having to modify every thread-safe public member function
-may results in an increased code size.
-
-However, with the per-method locking approach, it is not possible to perform a
-multi-method thread-safe transaction. For example, what if we only wanted to
-withdraw money if the balance was high enough? With the current API there would
-be a risk that money is withdrawn after we've checked the balance.
-
-This is usually why external locking is used. This is when the lock is exposed
-to the caller and may be used externally to the public API. External locking
-can take may forms which may even include mixing internal and external locking.
-In its most simplistic form it is an external lock used along side each
-instance, e.g.:
-
-.. code-block:: cpp
-
-  class BankAccount {
-   public:
-    void Deposit(int amount) {
-      balance_ += amount;
-    }
-
-    void Withdraw(int amount) {
-      balance_ -= amount;
-    }
-
-    void Balance() const {
-      return balance_;
-    }
-
-   private:
-    int balance_;
-  };
-
-  pw::sync::Mutex bobs_account_mutex;
-  BankAccount bobs_account PW_GUARDED_BY(bobs_account_mutex);
-
-The lock is acquired before the bank account is used for a transaction. In
-addition, we do not have to modify every public function and its trivial to
-call other public member functions from a public member function. However, as
-you can imagine instantiating and passing around the instances and their locks
-can become error prone.
-
-This is why ``Borrowable`` exists.
-
-Why use Borrowable?
--------------------
-``Borrowable`` offers code-size efficient way to enable external locking that is
-easy and safe to use. It is effectively a container which holds references to a
-protected instance and its lock which provides RAII-style access.
-
-.. code-block:: cpp
-
-  pw::sync::Mutex bobs_account_mutex;
-  BankAccount bobs_account PW_GUARDED_BY(bobs_account_mutex);
-  pw::sync::Borrowable<BankAccount, pw::sync::Mutex> bobs_acount(
-      bobs_account, bobs_account_mutex);
-
-This construct is useful when sharing objects or data which are transactional in
-nature where making individual operations threadsafe is insufficient. See the
-section on internal vs external locking tradeoffs above.
-
-It can also offer a code-size and stack-usage efficient way to separate timeout
-constraints between the acquiring of the shared object and timeouts used for the
-shared object's API. For example, imagine you have an I2c bus which is used by
-several threads and you'd like to specify an ACK timeout of 50ms. It'd be ideal
-if the duration it takes to gain exclusive access to the I2c bus does not eat
-into the ACK timeout you'd like to use for the transaction. Borrowable can help
-you do exactly this if you provide access to the I2c bus through a
-``Borrowable``.
-
-C++
----
-.. cpp:class:: template <typename GuardedType, typename Lock = pw::sync::VirtualBasicLockable> pw::sync::BorrowedPointer
-
-  The BorrowedPointer is an RAII handle which wraps a pointer to a borrowed
-  object along with a held lock which is guarding the object. When destroyed,
-  the lock is released.
-
-  This object is moveable, but not copyable.
-
-  .. cpp:function:: GuardedType* operator->()
-
-     Provides access to the borrowed object's members.
-
-  .. cpp:function:: GuardedType& operator*()
-
-     Provides access to the borrowed object directly.
-
-     **Warning:** The member of pointer member access operator, operator->(), is
-     recommended over this API as this is prone to leaking references. However,
-     this is sometimes necessary.
-
-     **Warning:** Be careful not to leak references to the borrowed object.
-
-.. cpp:class:: template <typename GuardedType, typename Lock = pw::sync::VirtualBasicLockable> pw::sync::Borrowable
-
-  .. cpp:function:: BorrowedPointer<GuardedType, Lock> acquire()
-
-     Blocks indefinitely until the object can be borrowed. Failures are fatal.
-
-  .. cpp:function:: std::optional<BorrowedPointer<GuardedType, Lock>> try_acquire()
-
-     Tries to borrow the object in a non-blocking manner. Returns a
-     BorrowedPointer on success, otherwise std::nullopt (nothing).
-
-  .. cpp:function:: template <class Rep, class Period> std::optional<BorrowedPointer<GuardedType, Lock>> try_acquire_for(std::chrono::duration<Rep, Period> timeout)
-
-     Tries to borrow the object. Blocks until the specified timeout has elapsed
-     or the object has been borrowed, whichever comes first. Returns a
-     BorrowedPointer on success, otherwise std::nullopt (nothing).
-
-  .. cpp:function:: template <class Rep, class Period> std::optional<BorrowedPointer<GuardedType, Lock>> try_acquire_until(std::chrono::duration<Rep, Period> deadline)
-
-     Tries to borrow the object. Blocks until the specified deadline has been
-     reached or the object has been borrowed, whichever comes first. Returns a
-     BorrowedPointer on success, otherwise std::nullopt (nothing).
-
-Example in C++
-^^^^^^^^^^^^^^
-
-.. code-block:: cpp
-
-  #include <chrono>
-
-  #include "pw_bytes/span.h"
-  #include "pw_i2c/initiator.h"
-  #include "pw_status/try.h"
-  #include "pw_status/result.h"
-  #include "pw_sync/borrow.h"
-  #include "pw_sync/mutex.h"
-
-  class ExampleI2c : public pw::i2c::Initiator;
-
-  pw::sync::VirtualMutex i2c_mutex;
-  ExampleI2c i2c;
-  pw::sync::Borrowable<ExampleI2c> borrowable_i2c(i2c, i2c_mutex);
-
-  pw::Result<ConstByteSpan> ReadI2cData(ByteSpan buffer) {
-    // Block indefinitely waiting to borrow the i2c bus.
-    pw::sync::BorrowedPointer<ExampleI2c> borrowed_i2c =
-        borrowable_i2c.acquire();
-
-    // Execute a sequence of transactions to get the needed data.
-    PW_TRY(borrowed_i2c->WriteFor(kFirstWrite, std::chrono::milliseconds(50)));
-    PW_TRY(borrowed_i2c->WriteReadFor(kSecondWrite, buffer,
-                                      std::chrono::milliseconds(10)));
-
-    // Borrowed i2c pointer is returned when the scope exits.
-    return buffer;
-  }
-
 --------------------
 Signaling Primitives
 --------------------
@@ -1202,326 +963,18 @@
 build any signaling primitive based on other native signaling primitives, this
 may come with non-trivial added overhead in ROM, RAM, and execution efficiency.
 
-For this reason, Pigweed intends to provide some simpler signaling primitives
+For this reason, Pigweed intends to provide some "simpler" signaling primitives
 which exist to solve a narrow programming need but can be implemented as
 efficiently as possible for the platform that it is used on.
 
 This simpler but highly portable class of signaling primitives is intended to
 ensure that a portability efficiency tradeoff does not have to be made up front.
-Today this is class of simpler signaling primitives is limited to the
-``pw::sync::ThreadNotification`` and ``pw::sync::TimedThreadNotification``.
-
-ThreadNotification
-==================
-The ThreadNotification is a synchronization primitive that can be used to
-permit a SINGLE thread to block and consume a latching, saturating
-notification from multiple notifiers.
-
-.. Note::
-  Although only a single thread can block on a ThreadNotification at a time,
-  many instances may be used by a single thread just like binary semaphores.
-  This is in contrast to some native RTOS APIs, such as direct task
-  notifications, which re-use the same state within a thread's context.
-
-.. Warning::
-  This is a single consumer/waiter, multiple producer/notifier API!
-  The acquire APIs must only be invoked by a single consuming thread. As a
-  result, having multiple threads receiving notifications via the acquire API
-  is unsupported.
-
-This is effectively a subset of the ``pw::sync::BinarySemaphore`` API, except
-that only a single thread can be notified and block at a time.
-
-The single consumer aspect of the API permits the use of a smaller and/or
-faster native APIs such as direct thread signaling. This should be
+For example we intend to provide a ``pw::sync::Notification`` facade which
+permits a singler consumer to block until an event occurs. This should be
 backed by the most efficient native primitive for a target, regardless of
 whether that is a semaphore, event flag group, condition variable, or something
 else.
 
-Generic BinarySemaphore-based Backend
--------------------------------------
-This module provides a generic backend for ``pw::sync::ThreadNotification`` via
-``pw_sync:binary_semaphore_thread_notification`` which uses a
-``pw::sync::BinarySemaphore`` as the backing primitive. See
-:ref:`BinarySemaphore <module-pw_sync-binary-semaphore>` for backend
-availability.
-
-Optimized Backend
------------------
-.. list-table::
-
-  * - *Supported on*
-    - *Optimized backend module*
-  * - FreeRTOS
-    - ``pw_sync_freertos:thread_notification``
-  * - ThreadX
-    - Not possible, use ``pw_sync:binary_semaphore_thread_notification``
-  * - embOS
-    - Not needed, use ``pw_sync:binary_semaphore_thread_notification``
-  * - STL
-    - Not planned, use ``pw_sync:binary_semaphore_thread_notification``
-  * - Baremetal
-    - Planned
-  * - Zephyr
-    - Planned
-  * - CMSIS-RTOS API v2 & RTX5
-    - Planned
-
-C++
----
-.. cpp:class:: pw::sync::ThreadNotification
-
-  .. cpp:function:: void acquire()
-
-     Blocks indefinitely until the thread is notified, i.e. until the
-     notification latch can be cleared because it was set.
-
-     Clears the notification latch.
-
-     **IMPORTANT:** This should only be used by a single consumer thread.
-
-  .. cpp:function:: bool try_acquire()
-
-     Returns whether the thread has been notified, i.e. whether the notificion
-     latch was set and resets the latch regardless.
-
-     Clears the notification latch.
-
-     Returns true if the thread was notified, meaning the the internal latch was
-     reset successfully.
-
-     **IMPORTANT:** This should only be used by a single consumer thread.
-
-  .. cpp:function:: void release()
-
-     Notifies the thread in a saturating manner, setting the notification latch.
-
-     Raising the notification multiple time without it being acquired by the
-     consuming thread is equivalent to raising the notification once to the
-     thread. The notification is latched in case the thread was not waiting at
-     the time.
-
-     This is IRQ and thread safe.
-
-  .. list-table::
-
-    * - *Safe to use in context*
-      - *Thread*
-      - *Interrupt*
-      - *NMI*
-    * - ``ThreadNotification::ThreadNotification``
-      - ✔
-      -
-      -
-    * - ``ThreadNotification::~ThreadNotification``
-      - ✔
-      -
-      -
-    * - ``void ThreadNotification::acquire``
-      - ✔
-      -
-      -
-    * - ``bool ThreadNotification::try_acquire``
-      - ✔
-      -
-      -
-    * - ``void ThreadNotification::release``
-      - ✔
-      - ✔
-      -
-
-Examples in C++
-^^^^^^^^^^^^^^^
-.. code-block:: cpp
-
-  #include "pw_sync/thread_notification.h"
-  #include "pw_thread/thread_core.h"
-
-  class FooHandler() : public pw::thread::ThreadCore {
-   // Public API invoked by other threads and/or interrupts.
-   void NewFooAvailable() {
-     new_foo_notification_.release();
-   }
-
-   private:
-    pw::sync::ThreadNotification new_foo_notification_;
-
-    // Thread function.
-    void Run() override {
-      while (true) {
-        new_foo_notification_.acquire();
-        HandleFoo();
-      }
-    }
-
-    void HandleFoo();
-  }
-
-TimedThreadNotification
-=======================
-The TimedThreadNotification is an extension of the ThreadNotification which
-offers timeout and deadline based semantics.
-
-.. Warning::
-  This is a single consumer/waiter, multiple producer/notifier API!
-  The acquire APIs must only be invoked by a single consuming thread. As a
-  result, having multiple threads receiving notifications via the acquire API
-  is unsupported.
-
-Generic BinarySemaphore-based Backend
--------------------------------------
-This module provides a generic backend for ``pw::sync::TimedThreadNotification``
-via ``pw_sync:binary_semaphore_timed_thread_notification`` which uses a
-``pw::sync::BinarySemaphore`` as the backing primitive. See
-:ref:`BinarySemaphore <module-pw_sync-binary-semaphore>` for backend
-availability.
-
-Optimized Backend
------------------
-.. list-table::
-
-  * - *Supported on*
-    - *Backend module*
-  * - FreeRTOS
-    - ``pw_sync_freertos:timed_thread_notification``
-  * - ThreadX
-    - Not possible, use ``pw_sync:binary_semaphore_timed_thread_notification``
-  * - embOS
-    - Not needed, use ``pw_sync:binary_semaphore_timed_thread_notification``
-  * - STL
-    - Not planned, use ``pw_sync:binary_semaphore_timed_thread_notification``
-  * - Zephyr
-    - Planned
-  * - CMSIS-RTOS API v2 & RTX5
-    - Planned
-
-C++
----
-.. cpp:class:: pw::sync::TimedThreadNotification
-
-  .. cpp:function:: void acquire()
-
-     Blocks indefinitely until the thread is notified, i.e. until the
-     notification latch can be cleared because it was set.
-
-     Clears the notification latch.
-
-     **IMPORTANT:** This should only be used by a single consumer thread.
-
-  .. cpp:function:: bool try_acquire()
-
-     Returns whether the thread has been notified, i.e. whether the notificion
-     latch was set and resets the latch regardless.
-
-     Clears the notification latch.
-
-     Returns true if the thread was notified, meaning the the internal latch was
-     reset successfully.
-
-     **IMPORTANT:** This should only be used by a single consumer thread.
-
-  .. cpp:function:: void release()
-
-     Notifies the thread in a saturating manner, setting the notification latch.
-
-     Raising the notification multiple time without it being acquired by the
-     consuming thread is equivalent to raising the notification once to the
-     thread. The notification is latched in case the thread was not waiting at
-     the time.
-
-     This is IRQ and thread safe.
-
-  .. cpp:function:: bool try_acquire_for(chrono::SystemClock::duration timeout)
-
-     Blocks until the specified timeout duration has elapsed or the thread
-     has been notified (i.e. notification latch can be cleared because it was
-     set), whichever comes first.
-
-     Clears the notification latch.
-
-     Returns true if the thread was notified, meaning the the internal latch was
-     reset successfully.
-
-     **IMPORTANT:** This should only be used by a single consumer thread.
-
-  .. cpp:function:: bool try_acquire_until(chrono::SystemClock::time_point deadline)
-
-     Blocks until the specified deadline time has been reached the thread has
-     been notified (i.e. notification latch can be cleared because it was set),
-     whichever comes first.
-
-     Clears the notification latch.
-
-     Returns true if the thread was notified, meaning the the internal latch was
-     reset successfully.
-
-     **IMPORTANT:** This should only be used by a single consumer thread.
-
-  .. list-table::
-
-    * - *Safe to use in context*
-      - *Thread*
-      - *Interrupt*
-      - *NMI*
-    * - ``TimedThreadNotification::TimedThreadNotification``
-      - ✔
-      -
-      -
-    * - ``TimedThreadNotification::~TimedThreadNotification``
-      - ✔
-      -
-      -
-    * - ``void TimedThreadNotification::acquire``
-      - ✔
-      -
-      -
-    * - ``bool TimedThreadNotification::try_acquire``
-      - ✔
-      -
-      -
-    * - ``bool TimedThreadNotification::try_acquire_for``
-      - ✔
-      -
-      -
-    * - ``bool TimedThreadNotification::try_acquire_until``
-      - ✔
-      -
-      -
-    * - ``void TimedThreadNotification::release``
-      - ✔
-      - ✔
-      -
-
-Examples in C++
-^^^^^^^^^^^^^^^
-.. code-block:: cpp
-
-  #include "pw_sync/timed_thread_notification.h"
-  #include "pw_thread/thread_core.h"
-
-  class FooHandler() : public pw::thread::ThreadCore {
-   // Public API invoked by other threads and/or interrupts.
-   void NewFooAvailable() {
-     new_foo_notification_.release();
-   }
-
-   private:
-    pw::sync::TimedThreadNotification new_foo_notification_;
-
-    // Thread function.
-    void Run() override {
-      while (true) {
-        if (new_foo_notification_.try_acquire_for(kNotificationTimeout)) {
-          HandleFoo();
-        }
-        DoOtherStuff();
-      }
-    }
-
-    void HandleFoo();
-    void DoOtherStuff();
-  }
-
 CountingSemaphore
 =================
 The CountingSemaphore is a synchronization primitive that can be used for
@@ -1534,12 +987,8 @@
 
 The CountingSemaphore is initialized to being empty or having no tokens.
 
-The entire API is thread safe, but only a subset is interrupt safe.
-
-.. Note::
-  If there is only a single consuming thread, we recommend using a
-  ThreadNotification instead which can be much more efficient on some RTOSes
-  such as FreeRTOS.
+The entire API is thread safe, but only a subset is interrupt safe. None of it
+is NMI safe.
 
 .. Warning::
   Releasing multiple tokens is often not natively supported, meaning you may
@@ -1563,134 +1012,6 @@
   * - CMSIS-RTOS API v2 & RTX5
     - Planned
 
-C++
----
-.. cpp:class:: pw::sync::CountingSemaphore
-
-  .. cpp:function:: void acquire()
-
-     Decrements the internal counter by 1 or blocks indefinitely until it can.
-     This is thread safe, but not IRQ safe.
-
-  .. cpp:function:: bool try_acquire() noexcept
-
-     Tries to decrement by the internal counter by 1 without blocking.
-     Returns true if the internal counter was decremented successfully.
-     This is thread and IRQ safe.
-
-  .. cpp:function:: bool try_acquire_for(chrono::SystemClock::duration timeout)
-
-     Tries to decrement the internal counter by 1. Blocks until the specified
-     timeout has elapsed or the counter was decremented by 1, whichever comes
-     first.
-     Returns true if the internal counter was decremented successfully.
-     This is thread safe, but not IRQ safe.
-
-  .. cpp:function:: bool try_acquire_until(chrono::SystemClock::time_point deadline)
-
-     Tries to decrement the internal counter by 1. Blocks until the specified
-     deadline has been reached or the counter was decremented  by 1, whichever
-     comes first.
-     Returns true if the internal counter was decremented successfully.
-     This is thread safe, but not IRQ safe.
-
-  .. cpp:function:: void release(ptrdiff_t update = 1)
-
-     Atomically increments the internal counter by the value of update.
-     Any thread(s) waiting for the counter to be greater than 0, i.e.
-     blocked in acquire, will subsequently be unblocked.
-     This is thread and IRQ safe.
-
-     **Precondition:** update >= 0
-
-     **Precondition:** update <= max() - counter
-
-  .. cpp:function:: static constexpr ptrdiff_t max() noexcept
-
-     Returns the internal counter's maximum possible value.
-
-  .. list-table::
-
-    * - *Safe to use in context*
-      - *Thread*
-      - *Interrupt*
-      - *NMI*
-    * - ``CountingSemaphore::CountingSemaphore``
-      - ✔
-      -
-      -
-    * - ``CountingSemaphore::~CountingSemaphore``
-      - ✔
-      -
-      -
-    * - ``void CountingSemaphore::acquire``
-      - ✔
-      -
-      -
-    * - ``bool CountingSemaphore::try_acquire``
-      - ✔
-      - ✔
-      -
-    * - ``bool CountingSemaphore::try_acquire_for``
-      - ✔
-      -
-      -
-    * - ``bool CountingSemaphore::try_acquire_until``
-      - ✔
-      -
-      -
-    * - ``void CountingSemaphore::release``
-      - ✔
-      - ✔
-      -
-    * - ``void CountingSemaphore::max``
-      - ✔
-      - ✔
-      - ✔
-
-Examples in C++
-^^^^^^^^^^^^^^^
-As an example, a counting sempahore can be useful to run periodic tasks at
-frequencies near or higher than the system clock tick rate in a way which lets
-you detect whether you ever fall behind.
-
-.. code-block:: cpp
-
-  #include "pw_sync/counting_semaphore.h"
-  #include "pw_thread/thread_core.h"
-
-  class PeriodicWorker() : public pw::thread::ThreadCore {
-   // Public API invoked by a higher frequency timer interrupt.
-   void TimeToExecute() {
-     periodic_run_semaphore_.release();
-   }
-
-   private:
-    pw::sync::CountingSemaphore periodic_run_semaphore_;
-
-    // Thread function.
-    void Run() override {
-      while (true) {
-        size_t behind_by_n_cycles = 0;
-        periodic_run_semaphore_.acquire(); // Wait to run until it's time.
-        while (periodic_run_semaphore_.try_acquire()) {
-          ++behind_by_n_cycles;
-        }
-        if (behind_by_n_cycles > 0) {
-          PW_LOG_WARNING("Not keeping up, behind by %d cycles",
-                         behind_by_n_cycles);
-        }
-        DoPeriodicWork();
-      }
-    }
-
-    void DoPeriodicWork();
-  }
-
-
-
-.. _module-pw_sync-binary-semaphore:
-
 BinarySemaphore
 ===============
 BinarySemaphore is a specialization of CountingSemaphore with an arbitrary token
@@ -1702,13 +1023,8 @@
 
 The BinarySemaphore is initialized to being empty or having no tokens.
 
-The entire API is thread safe, but only a subset is interrupt safe.
-
-.. Note::
-  If there is only a single consuming thread, we recommend using a
-  ThreadNotification instead which can be much more efficient on some RTOSes
-  such as FreeRTOS.
-
+The entire API is thread safe, but only a subset is interrupt safe. None of it
+is NMI safe.
 
 .. list-table::
 
@@ -1727,130 +1043,16 @@
   * - CMSIS-RTOS API v2 & RTX5
     - Planned
 
-C++
----
-.. cpp:class:: pw::sync::BinarySemaphore
+Coming Soon
+===========
+We are intending to provide facades for:
 
-  .. cpp:function:: void acquire()
+* ``pw::sync::Notification``: A portable abstraction to allow threads to receive
+  notification of a single occurrence of a single event.
 
-     Decrements the internal counter to 0 or blocks indefinitely until it can.
-     This is thread safe, but not IRQ safe.
-
-  .. cpp:function:: bool try_acquire() noexcept
-
-     Tries to decrement by the internal counter to 0 without blocking.
-     Returns true if the internal counter was decremented successfully.
-     This is thread and IRQ safe.
-
-  .. cpp:function:: bool try_acquire_for(chrono::SystemClock::duration timeout)
-
-     Tries to decrement the internal counter to 0. Blocks until the specified
-     timeout has elapsed or the counter was decremented to 0, whichever comes
-     first.
-     Returns true if the internal counter was decremented successfully.
-     This is thread safe, but not IRQ safe.
-
-  .. cpp:function:: bool try_acquire_until(chrono::SystemClock::time_point deadline)
-
-     Tries to decrement the internal counter to 0. Blocks until the specified
-     deadline has been reached or the counter was decremented to 0, whichever
-     comes first.
-     Returns true if the internal counter was decremented successfully.
-     This is thread safe, but not IRQ safe.
-
-  .. cpp:function:: void release()
-
-     Atomically increments the internal counter by 1.
-     Any thread(s) waiting for the counter to be greater than 0, i.e.
-     blocked in acquire, will subsequently be unblocked.
-     This is thread and IRQ safe.
-
-     There exists an overflow risk if one releases more than max() times
-     between acquires because many RTOS implementations internally
-     increment the counter past one where it is only cleared when acquired.
-
-     **Precondition:** 1 <= max() - counter
-
-  .. cpp:function:: static constexpr ptrdiff_t max() noexcept
-
-     Returns the internal counter's maximum possible value.
-
-  .. list-table::
-
-    * - *Safe to use in context*
-      - *Thread*
-      - *Interrupt*
-      - *NMI*
-    * - ``BinarySemaphore::BinarySemaphore``
-      - ✔
-      -
-      -
-    * - ``BinarySemaphore::~BinarySemaphore``
-      - ✔
-      -
-      -
-    * - ``void BinarySemaphore::acquire``
-      - ✔
-      -
-      -
-    * - ``bool BinarySemaphore::try_acquire``
-      - ✔
-      - ✔
-      -
-    * - ``bool BinarySemaphore::try_acquire_for``
-      - ✔
-      -
-      -
-    * - ``bool BinarySemaphore::try_acquire_until``
-      - ✔
-      -
-      -
-    * - ``void BinarySemaphore::release``
-      - ✔
-      - ✔
-      -
-    * - ``void BinarySemaphore::max``
-      - ✔
-      - ✔
-      - ✔
-
-Examples in C++
-^^^^^^^^^^^^^^^
-.. code-block:: cpp
-
-  #include "pw_sync/binary_semaphore.h"
-  #include "pw_thread/thread_core.h"
-
-  class FooHandler() : public pw::thread::ThreadCore {
-   // Public API invoked by other threads and/or interrupts.
-   void NewFooAvailable() {
-     new_foo_semaphore_.release();
-   }
-
-   private:
-    pw::sync::BinarySemaphore new_foo_semaphore_;
-
-    // Thread function.
-    void Run() override {
-      while (true) {
-        if (new_foo_semaphore_.try_acquire_for(kNotificationTimeout)) {
-          HandleFoo();
-        }
-        DoOtherStuff();
-      }
-    }
-
-    void HandleFoo();
-    void DoOtherStuff();
-  }
-
-Conditional Variables
-=====================
-We've decided for now to skip on conditional variables. These are constructs,
-which are typically not natively available on RTOSes. CVs would have to be
-backed by a multiple hidden semaphore(s) in addition to the explicit public
-mutex. In other words a CV typically ends up as a a composition of
-synchronization primitives on RTOSes. That being said, one could implement them
-using our semaphore and mutex layers and we may consider providing this in the
-future. However for most of our resource constrained customers they will mostly
-likely be using semaphores more often than CVs.
+* ``pw::sync::EventGroup`` A facade for a common primitive on RTOSes like
+  FreeRTOS, RTX5, ThreadX, and embOS which permit threads and interrupts to
+  signal up to 32 events. This permits others threads to be notified when either
+  any or some combination of these events have been signaled. This is frequently
+  used as an alternative to a set of binary semaphore(s). This is not supported
+  natively on Zephyr.
diff --git a/pw_sync/interrupt_spin_lock_facade_test.cc b/pw_sync/interrupt_spin_lock_facade_test.cc
index fa3a723..0d8a059 100644
--- a/pw_sync/interrupt_spin_lock_facade_test.cc
+++ b/pw_sync/interrupt_spin_lock_facade_test.cc
@@ -37,14 +37,13 @@
   interrupt_spin_lock.unlock();
 }
 
-// TODO(pwbug/291): Add real concurrency tests once we have pw::thread on SMP
-// systems given that uniprocessor systems cannot fail to acquire an ISL.
+// TODO(pwbug/291): Add real concurrency tests once we have pw::thread.
 
 InterruptSpinLock static_interrupt_spin_lock;
 TEST(InterruptSpinLock, LockUnlockStatic) {
   static_interrupt_spin_lock.lock();
-  // TODO(pwbug/291): Ensure other cores fail to lock when its locked.
-  // EXPECT_FALSE(static_interrupt_spin_lock.try_lock());
+  // Ensure it fails to lock when already held.
+  EXPECT_FALSE(static_interrupt_spin_lock.try_lock());
   static_interrupt_spin_lock.unlock();
 }
 
@@ -53,28 +52,12 @@
   const bool locked = interrupt_spin_lock.try_lock();
   EXPECT_TRUE(locked);
   if (locked) {
-    // TODO(pwbug/291): Ensure other cores fail to lock when its locked.
-    // EXPECT_FALSE(interrupt_spin_lock.try_lock());
+    // Ensure it fails to lock when already held.
+    EXPECT_FALSE(interrupt_spin_lock.try_lock());
     interrupt_spin_lock.unlock();
   }
 }
 
-TEST(VirtualInterruptSpinLock, LockUnlock) {
-  pw::sync::VirtualInterruptSpinLock interrupt_spin_lock;
-  interrupt_spin_lock.lock();
-  // TODO(pwbug/291): Ensure other cores fail to lock when its locked.
-  // EXPECT_FALSE(interrupt_spin_lock.try_lock());
-  interrupt_spin_lock.unlock();
-}
-
-VirtualInterruptSpinLock static_virtual_interrupt_spin_lock;
-TEST(VirtualInterruptSpinLock, LockUnlockStatic) {
-  static_virtual_interrupt_spin_lock.lock();
-  // TODO(pwbug/291): Ensure other cores fail to lock when its locked.
-  // EXPECT_FALSE(static_virtual_interrupt_spin_lock.try_lock());
-  static_virtual_interrupt_spin_lock.unlock();
-}
-
 TEST(InterruptSpinLock, LockUnlockInC) {
   pw::sync::InterruptSpinLock interrupt_spin_lock;
   pw_sync_InterruptSpinLock_CallLock(&interrupt_spin_lock);
@@ -84,8 +67,8 @@
 TEST(InterruptSpinLock, TryLockUnlockInC) {
   pw::sync::InterruptSpinLock interrupt_spin_lock;
   ASSERT_TRUE(pw_sync_InterruptSpinLock_CallTryLock(&interrupt_spin_lock));
-  // TODO(pwbug/291): Ensure other cores fail to lock when its locked.
-  // EXPECT_FALSE(pw_sync_InterruptSpinLock_CallTryLock(&interrupt_spin_lock));
+  // Ensure it fails to lock when already held.
+  EXPECT_FALSE(pw_sync_InterruptSpinLock_CallTryLock(&interrupt_spin_lock));
   pw_sync_InterruptSpinLock_CallUnlock(&interrupt_spin_lock);
 }
 
diff --git a/pw_sync/mutex_facade_test.cc b/pw_sync/mutex_facade_test.cc
index 771b003..6788536 100644
--- a/pw_sync/mutex_facade_test.cc
+++ b/pw_sync/mutex_facade_test.cc
@@ -58,22 +58,6 @@
   }
 }
 
-TEST(VirtualMutex, LockUnlock) {
-  pw::sync::VirtualMutex mutex;
-  mutex.lock();
-  // TODO(pwbug/291): Ensure it fails to lock when already held.
-  // EXPECT_FALSE(mutex.try_lock());
-  mutex.unlock();
-}
-
-VirtualMutex static_virtual_mutex;
-TEST(VirtualMutex, LockUnlockStatic) {
-  static_virtual_mutex.lock();
-  // TODO(pwbug/291): Ensure it fails to lock when already held.
-  // EXPECT_FALSE(static_virtual_mutex.try_lock());
-  static_virtual_mutex.unlock();
-}
-
 TEST(Mutex, LockUnlockInC) {
   pw::sync::Mutex mutex;
   pw_sync_Mutex_CallLock(&mutex);
diff --git a/pw_sync/public/pw_sync/backends/binary_semaphore_thread_notification_inline.h b/pw_sync/public/pw_sync/backends/binary_semaphore_thread_notification_inline.h
deleted file mode 100644
index c67a591..0000000
--- a/pw_sync/public/pw_sync/backends/binary_semaphore_thread_notification_inline.h
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_sync/thread_notification.h"
-
-namespace pw::sync {
-
-inline ThreadNotification::ThreadNotification() : native_type_() {}
-
-inline ThreadNotification::~ThreadNotification() {}
-
-inline void ThreadNotification::acquire() { native_type_.acquire(); }
-
-inline bool ThreadNotification::try_acquire() {
-  return native_type_.try_acquire();
-}
-
-inline void ThreadNotification::release() { native_type_.release(); }
-
-inline ThreadNotification::native_handle_type
-ThreadNotification::native_handle() {
-  return native_type_;
-}
-
-}  // namespace pw::sync
diff --git a/pw_sync/public/pw_sync/backends/binary_semaphore_thread_notification_native.h b/pw_sync/public/pw_sync/backends/binary_semaphore_thread_notification_native.h
deleted file mode 100644
index 616b9af..0000000
--- a/pw_sync/public/pw_sync/backends/binary_semaphore_thread_notification_native.h
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_sync/binary_semaphore.h"
-
-namespace pw::sync::backend {
-
-using NativeThreadNotification = BinarySemaphore;
-using NativeThreadNotificationHandle = BinarySemaphore&;
-
-}  // namespace pw::sync::backend
diff --git a/pw_sync/public/pw_sync/backends/binary_semaphore_timed_thread_notification_inline.h b/pw_sync/public/pw_sync/backends/binary_semaphore_timed_thread_notification_inline.h
deleted file mode 100644
index a61f8a6..0000000
--- a/pw_sync/public/pw_sync/backends/binary_semaphore_timed_thread_notification_inline.h
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_chrono/system_clock.h"
-#include "pw_sync/timed_thread_notification.h"
-
-namespace pw::sync {
-
-inline bool TimedThreadNotification::try_acquire_for(
-    chrono::SystemClock::duration timeout) {
-  return native_handle().try_acquire_for(timeout);
-}
-
-inline bool TimedThreadNotification::try_acquire_until(
-    chrono::SystemClock::time_point deadline) {
-  return native_handle().try_acquire_until(deadline);
-}
-
-}  // namespace pw::sync
diff --git a/pw_sync/public/pw_sync/binary_semaphore.h b/pw_sync/public/pw_sync/binary_semaphore.h
index a051605..b7e1c99 100644
--- a/pw_sync/public/pw_sync/binary_semaphore.h
+++ b/pw_sync/public/pw_sync/binary_semaphore.h
@@ -47,40 +47,37 @@
   BinarySemaphore& operator=(const BinarySemaphore&) = delete;
   BinarySemaphore& operator=(BinarySemaphore&&) = delete;
 
-  // Atomically increments the internal counter by 1.
-  // Any thread(s) waiting for the counter to be greater than 0, i.e.
-  // blocked in acquire, will subsequently be unblocked.
-  // This is thread and IRQ safe.
+  // Atomically increments the internal counter by 1 up to max_count.
+  // Any thread(s) waiting for the counter to be greater than 0,
+  // such as due to being blocked in acquire, will subsequently be unblocked.
+  // This is IRQ safe.
   //
-  // There exists an overflow risk if one releases more than max() times
-  // between acquires because many RTOS implementations internally
-  // increment the counter past one where it is only cleared when acquired.
-  //
-  // Precondition: 1 <= max() - counter
+  // PRECONDITIONS:
+  //   1 <= max() - counter
   void release();
 
   // Decrements the internal counter to 0 or blocks indefinitely until it can.
-  // This is thread safe, but not IRQ safe.
+  // This is thread safe.
+
+  //   update <= max() - counter
   void acquire();
 
-  // Tries to decrement by the internal counter to 0 without blocking.
+  // Attempts to decrement by the internal counter to 0 without blocking.
   // Returns true if the internal counter was reset successfully.
-  // This is thread and IRQ safe.
+  // This is IRQ safe.
   bool try_acquire() noexcept;
 
-  // Tries to decrement the internal counter to 0. Blocks until the specified
-  // timeout has elapsed or the counter was decremented to 0, whichever comes
-  // first.
+  // Attempts to decrement the internal counter to 0 where, if needed, blocking
+  // for at least the specified duration.
   // Returns true if the internal counter was decremented successfully.
-  // This is thread safe, but not IRQ safe.
-  bool try_acquire_for(chrono::SystemClock::duration timeout);
+  // This is thread safe.
+  bool try_acquire_for(chrono::SystemClock::duration for_at_least);
 
-  // Tries to decrement the internal counter to 0. Blocks until the specified
-  // deadline has been reached or the counter was decremented to 0, whichever
-  // comes first.
+  // Attempts to decrement the internal counter to 0 where, if needed, blocking
+  // until at least the specified time point.
   // Returns true if the internal counter was decremented successfully.
-  // This is thread safe, but not IRQ safe.
-  bool try_acquire_until(chrono::SystemClock::time_point deadline);
+  // This is thread safe.
+  bool try_acquire_until(chrono::SystemClock::time_point until_at_least);
 
   static constexpr ptrdiff_t max() noexcept {
     return backend::kBinarySemaphoreMaxValue;
@@ -111,10 +108,11 @@
 void pw_sync_BinarySemaphore_Acquire(pw_sync_BinarySemaphore* semaphore);
 bool pw_sync_BinarySemaphore_TryAcquire(pw_sync_BinarySemaphore* semaphore);
 bool pw_sync_BinarySemaphore_TryAcquireFor(
-    pw_sync_BinarySemaphore* semaphore, pw_chrono_SystemClock_Duration timeout);
+    pw_sync_BinarySemaphore* semaphore,
+    pw_chrono_SystemClock_Duration for_at_least);
 bool pw_sync_BinarySemaphore_TryAcquireUntil(
     pw_sync_BinarySemaphore* semaphore,
-    pw_chrono_SystemClock_TimePoint deadline);
+    pw_chrono_SystemClock_TimePoint until_at_least);
 ptrdiff_t pw_sync_BinarySemaphore_Max(void);
 
 PW_EXTERN_C_END
diff --git a/pw_sync/public/pw_sync/borrow.h b/pw_sync/public/pw_sync/borrow.h
deleted file mode 100644
index da5d0d9..0000000
--- a/pw_sync/public/pw_sync/borrow.h
+++ /dev/null
@@ -1,151 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <chrono>
-#include <optional>
-#include <type_traits>
-
-#include "pw_assert/assert.h"
-#include "pw_sync/lock_annotations.h"
-#include "pw_sync/virtual_basic_lockable.h"
-
-namespace pw::sync {
-
-// The BorrowedPointer is an RAII handle which wraps a pointer to a borrowed
-// object along with a held lock which is guarding the object. When destroyed,
-// the lock is released.
-template <typename GuardedType, typename Lock = pw::sync::VirtualBasicLockable>
-class BorrowedPointer {
- public:
-  // Release the lock on destruction.
-  ~BorrowedPointer() {
-    if (lock_ != nullptr) {
-      lock_->unlock();
-    }
-  }
-
-  // This object is moveable, but not copyable.
-  //
-  // Postcondition: The other BorrowedPointer is no longer valid and will assert
-  //     if the GuardedType is accessed.
-  BorrowedPointer(BorrowedPointer&& other)
-      : lock_(other.lock_), object_(other.object_) {
-    other.lock_ = nullptr;
-    other.object_ = nullptr;
-  }
-  BorrowedPointer& operator=(BorrowedPointer&& other) {
-    lock_ = other.lock_;
-    object_ = other.object_;
-    other.lock_ = nullptr;
-    other.object_ = nullptr;
-    return *this;
-  }
-  BorrowedPointer(const BorrowedPointer&) = delete;
-  BorrowedPointer& operator=(const BorrowedPointer&) = delete;
-
-  // Provides access to the borrowed object's members.
-  GuardedType* operator->() {
-    PW_ASSERT(object_ != nullptr);  // Ensure this isn't a stale moved instance.
-    return object_;
-  }
-
-  // Provides access to the borrowed object directly.
-  //
-  // NOTE: The member of pointer member access operator, operator->(), is
-  // recommended over this API as this is prone to leaking references. However,
-  // this is sometimes necessary.
-  //
-  // WARNING: Be careful not to leak references to the borrowed object!
-  GuardedType& operator*() {
-    PW_ASSERT(object_ != nullptr);  // Ensure this isn't a stale moved instance.
-    return *object_;
-  }
-
- private:
-  // Allow BorrowedPointer creation inside of Borrowable's acquire methods.
-  template <typename G, typename L>
-  friend class Borrowable;
-
-  constexpr BorrowedPointer(Lock& lock, GuardedType& object)
-      : lock_(&lock), object_(&object) {}
-
-  Lock* lock_;
-  GuardedType* object_;
-};
-
-// The Borrowable is a helper construct that enables callers to borrow an object
-// which is guarded by a lock.
-//
-// Users who need access to the guarded object can ask to acquire a
-// BorrowedPointer which permits access while the lock is held.
-//
-// This class is compatible with locks which comply with BasicLockable,
-// Lockable, and TimedLockable C++ named requirements.
-template <typename GuardedType, typename Lock = pw::sync::VirtualBasicLockable>
-class Borrowable {
- public:
-  constexpr Borrowable(GuardedType& object, Lock& lock) noexcept
-      : lock_(&lock), object_(&object) {}
-
-  Borrowable(const Borrowable&) = default;
-  Borrowable& operator=(const Borrowable&) = default;
-  Borrowable(Borrowable&& other) = default;
-  Borrowable& operator=(Borrowable&& other) = default;
-
-  // Blocks indefinitely until the object can be borrowed. Failures are fatal.
-  BorrowedPointer<GuardedType, Lock> acquire() PW_NO_LOCK_SAFETY_ANALYSIS {
-    lock_->lock();
-    return BorrowedPointer<GuardedType, Lock>(*lock_, *object_);
-  }
-
-  // Tries to borrow the object in a non-blocking manner. Returns a
-  // BorrowedPointer on success, otherwise std::nullopt (nothing).
-  std::optional<BorrowedPointer<GuardedType, Lock>> try_acquire() {
-    if (!lock_->try_lock()) {
-      return std::nullopt;
-    }
-    return BorrowedPointer<GuardedType, Lock>(*lock_, *object_);
-  }
-
-  // Tries to borrow the object. Blocks until the specified timeout has elapsed
-  // or the object has been borrowed, whichever comes first. Returns a
-  // BorrowedPointer on success, otherwise std::nullopt (nothing).
-  template <class Rep, class Period>
-  std::optional<BorrowedPointer<GuardedType, Lock>> try_acquire_for(
-      std::chrono::duration<Rep, Period> timeout) {
-    if (!lock_->try_lock_for(timeout)) {
-      return std::nullopt;
-    }
-    return BorrowedPointer<GuardedType, Lock>(*lock_, *object_);
-  }
-
-  // Tries to borrow the object. Blocks until the specified deadline has passed
-  // or the object has been borrowed, whichever comes first. Returns a
-  // BorrowedPointer on success, otherwise std::nullopt (nothing).
-  template <class Clock, class Duration>
-  std::optional<BorrowedPointer<GuardedType, Lock>> try_acquire_until(
-      std::chrono::time_point<Clock, Duration> deadline) {
-    if (!lock_->try_lock_until(deadline)) {
-      return std::nullopt;
-    }
-    return BorrowedPointer<GuardedType, Lock>(*lock_, *object_);
-  }
-
- private:
-  Lock* lock_;
-  GuardedType* object_;
-};
-
-}  // namespace pw::sync
diff --git a/pw_sync/public/pw_sync/counting_semaphore.h b/pw_sync/public/pw_sync/counting_semaphore.h
index 875ef97..23fbb21 100644
--- a/pw_sync/public/pw_sync/counting_semaphore.h
+++ b/pw_sync/public/pw_sync/counting_semaphore.h
@@ -49,36 +49,35 @@
   CountingSemaphore& operator=(CountingSemaphore&&) = delete;
 
   // Atomically increments the internal counter by the value of update.
-  // Any thread(s) waiting for the counter to be greater than 0, i.e. blocked
-  // in acquire, will subsequently be unblocked.
+  // Any thread(s) waiting for the counter to be greater than 0, i.e.
+  // blocked in acquire, will subsequently be unblocked.
   // This is IRQ safe.
   //
-  // Precondition: update >= 0
-  // Precondition: update <= max() - counter
+  // PRECONDITIONS:
+  //   update >= 0
+  //   update <= max() - counter
   void release(ptrdiff_t update = 1);
 
   // Decrements the internal counter by 1 or blocks indefinitely until it can.
-  // This is thread safe, but not IRQ safe.
+  // This is thread safe.
   void acquire();
 
-  // Tries to decrement by the internal counter by 1 without blocking.
+  // Attempts to decrement by the internal counter by 1 without blocking.
   // Returns true if the internal counter was decremented successfully.
   // This is IRQ safe.
   bool try_acquire() noexcept;
 
-  // Tries to decrement the internal counter by 1. Blocks until the specified
-  // timeout has elapsed or the counter was decremented by 1, whichever comes
-  // first.
+  // Attempts to decrement the internal counter by 1 where, if needed, blocking
+  // for at least the specified duration.
   // Returns true if the internal counter was decremented successfully.
-  // This is thread safe, but not IRQ safe.
-  bool try_acquire_for(chrono::SystemClock::duration timeout);
+  // This is thread safe.
+  bool try_acquire_for(chrono::SystemClock::duration for_at_least);
 
-  // Tries to decrement the internal counter by 1. Blocks until the specified
-  // deadline has been reached or the counter was decremented by 1, whichever
-  // comes first.
+  // Attempts to decrement the internal counter by 1 where, if needed, blocking
+  // until at least the specified time point.
   // Returns true if the internal counter was decremented successfully.
-  // This is thread safe, but not IRQ safe.
-  bool try_acquire_until(chrono::SystemClock::time_point deadline);
+  // This is thread safe.
+  bool try_acquire_until(chrono::SystemClock::time_point until_at_least);
 
   static constexpr ptrdiff_t max() noexcept {
     return backend::kCountingSemaphoreMaxValue;
@@ -112,10 +111,10 @@
 bool pw_sync_CountingSemaphore_TryAcquire(pw_sync_CountingSemaphore* semaphore);
 bool pw_sync_CountingSemaphore_TryAcquireFor(
     pw_sync_CountingSemaphore* semaphore,
-    pw_chrono_SystemClock_Duration timeout);
+    pw_chrono_SystemClock_Duration for_at_least);
 bool pw_sync_CountingSemaphore_TryAcquireUntil(
     pw_sync_CountingSemaphore* semaphore,
-    pw_chrono_SystemClock_TimePoint deadline);
+    pw_chrono_SystemClock_TimePoint until_at_least);
 ptrdiff_t pw_sync_CountingSemaphore_Max(void);
 
 PW_EXTERN_C_END
diff --git a/pw_sync/public/pw_sync/interrupt_spin_lock.h b/pw_sync/public/pw_sync/interrupt_spin_lock.h
index d9b75f7..8c1e7f2 100644
--- a/pw_sync/public/pw_sync/interrupt_spin_lock.h
+++ b/pw_sync/public/pw_sync/interrupt_spin_lock.h
@@ -20,7 +20,6 @@
 
 #ifdef __cplusplus
 
-#include "pw_sync/virtual_basic_lockable.h"
 #include "pw_sync_backend/interrupt_spin_lock_native.h"
 
 namespace pw::sync {
@@ -61,7 +60,7 @@
   // Precondition: Recursive locking is undefined behavior.
   void lock() PW_EXCLUSIVE_LOCK_FUNCTION();
 
-  // Tries to lock the spinlock in a non-blocking manner.
+  // Attempts to lock the spinlock in a non-blocking manner.
   // Returns true if the spinlock was successfully acquired.
   //
   // Precondition: Recursive locking is undefined behavior.
@@ -80,34 +79,6 @@
   backend::NativeInterruptSpinLock native_type_;
 };
 
-class PW_LOCKABLE("pw::sync::VirtualInterruptSpinLock")
-    VirtualInterruptSpinLock final : public VirtualBasicLockable {
- public:
-  VirtualInterruptSpinLock() = default;
-
-  VirtualInterruptSpinLock(const VirtualInterruptSpinLock&) = delete;
-  VirtualInterruptSpinLock(VirtualInterruptSpinLock&&) = delete;
-  VirtualInterruptSpinLock& operator=(const VirtualInterruptSpinLock&) = delete;
-  VirtualInterruptSpinLock& operator=(VirtualInterruptSpinLock&&) = delete;
-
-  InterruptSpinLock& interrupt_spin_lock() { return interrupt_spin_lock_; }
-
- private:
-  void DoLockOperation(Operation operation) override
-      PW_NO_LOCK_SAFETY_ANALYSIS {
-    switch (operation) {
-      case Operation::kLock:
-        return interrupt_spin_lock_.lock();
-
-      case Operation::kUnlock:
-      default:
-        return interrupt_spin_lock_.unlock();
-    }
-  }
-
-  InterruptSpinLock interrupt_spin_lock_;
-};
-
 }  // namespace pw::sync
 
 #include "pw_sync_backend/interrupt_spin_lock_inline.h"
diff --git a/pw_sync/public/pw_sync/mutex.h b/pw_sync/public/pw_sync/mutex.h
index eefbed6..a2da0f7 100644
--- a/pw_sync/public/pw_sync/mutex.h
+++ b/pw_sync/public/pw_sync/mutex.h
@@ -20,7 +20,6 @@
 
 #ifdef __cplusplus
 
-#include "pw_sync/virtual_basic_lockable.h"
 #include "pw_sync_backend/mutex_native.h"
 
 namespace pw::sync {
@@ -69,46 +68,11 @@
 
   native_handle_type native_handle();
 
- protected:
-  // Expose the NativeMutex directly to derived classes (TimedMutex) in
-  // case implementations use different types for backend::NativeMutex and
-  // native_handle().
-  backend::NativeMutex& native_type() { return native_type_; }
-  const backend::NativeMutex& native_type() const { return native_type_; }
-
  private:
   // This may be a wrapper around a native type with additional members.
   backend::NativeMutex native_type_;
 };
 
-class PW_LOCKABLE("pw::sync::VirtualMutex") VirtualMutex final
-    : public VirtualBasicLockable {
- public:
-  VirtualMutex() = default;
-
-  VirtualMutex(const VirtualMutex&) = delete;
-  VirtualMutex(VirtualMutex&&) = delete;
-  VirtualMutex& operator=(const VirtualMutex&) = delete;
-  VirtualMutex& operator=(VirtualMutex&&) = delete;
-
-  Mutex& mutex() { return mutex_; }
-
- private:
-  void DoLockOperation(Operation operation) override
-      PW_NO_LOCK_SAFETY_ANALYSIS {
-    switch (operation) {
-      case Operation::kLock:
-        return mutex_.lock();
-
-      case Operation::kUnlock:
-      default:
-        return mutex_.unlock();
-    }
-  }
-
-  Mutex mutex_;
-};
-
 }  // namespace pw::sync
 
 #include "pw_sync_backend/mutex_inline.h"
diff --git a/pw_sync/public/pw_sync/thread_notification.h b/pw_sync/public/pw_sync/thread_notification.h
deleted file mode 100644
index ba8cf58..0000000
--- a/pw_sync/public/pw_sync/thread_notification.h
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_sync_backend/thread_notification_native.h"
-
-namespace pw::sync {
-
-// The ThreadNotification is a synchronization primitive that can be used to
-// permit a SINGLE thread to block and consume a latching, saturating
-// notification from multiple notifiers.
-//
-// IMPORTANT: This is a single consumer/waiter, multiple producer/notifier API!
-// The acquire APIs must only be invoked by a single consuming thread. As a
-// result, having multiple threads receiving notifications via the acquire API
-// is unsupported.
-//
-// This is effectively a subset of a binary semaphore API, except that only a
-// single thread can be notified and block at a time.
-//
-// The single consumer aspect of the API permits the use of a smaller and/or
-// faster native APIs such as direct thread signaling.
-class ThreadNotification {
- public:
-  using native_handle_type = backend::NativeThreadNotificationHandle;
-
-  ThreadNotification();
-  ~ThreadNotification();
-  ThreadNotification(const ThreadNotification&) = delete;
-  ThreadNotification(ThreadNotification&&) = delete;
-  ThreadNotification& operator=(const ThreadNotification&) = delete;
-  ThreadNotification& operator=(ThreadNotification&&) = delete;
-
-  // Blocks indefinitely until the thread is notified, i.e. until the
-  // notification latch can be cleared because it was set.
-  //
-  // Clears the notification latch.
-  //
-  // IMPORTANT: This should only be used by a single consumer thread.
-  void acquire();
-
-  // Returns whether the thread has been notified, i.e. whether the notificion
-  // latch was set and resets the latch regardless.
-  //
-  // Clears the notification latch.
-  //
-  // Returns true if the thread was notified, meaning the the internal latch was
-  // reset successfully.
-  //
-  // IMPORTANT: This should only be used by a single consumer thread.
-  bool try_acquire();
-
-  // Notifies the thread in a saturating manner, setting the notification latch.
-  //
-  // Raising the notification multiple time without it being acquired by the
-  // consuming thread is equivalent to raising the notification once to the
-  // thread. The notification is latched in case the thread was not waiting at
-  // the time.
-  //
-  // This is IRQ and thread safe.
-  void release();
-
-  native_handle_type native_handle();
-
- private:
-  // This may be a wrapper around a native type with additional members.
-  backend::NativeThreadNotification native_type_;
-};
-
-}  // namespace pw::sync
-
-#include "pw_sync_backend/thread_notification_inline.h"
diff --git a/pw_sync/public/pw_sync/timed_mutex.h b/pw_sync/public/pw_sync/timed_mutex.h
index eb03284..2f44a78 100644
--- a/pw_sync/public/pw_sync/timed_mutex.h
+++ b/pw_sync/public/pw_sync/timed_mutex.h
@@ -22,8 +22,6 @@
 
 #ifdef __cplusplus
 
-#include "pw_sync/virtual_basic_lockable.h"
-
 namespace pw::sync {
 
 // The TimedMutex is a synchronization primitive that can be used to protect
@@ -46,55 +44,27 @@
   TimedMutex& operator=(const TimedMutex&) = delete;
   TimedMutex& operator=(TimedMutex&&) = delete;
 
-  // Tries to lock the mutex. Blocks until specified the timeout has elapsed or
-  // the lock is acquired, whichever comes first.
+  // Attempts to lock the mutex where, if needed, blocking for at least the
+  // specified duration.
   // Returns true if the mutex was successfully acquired.
   //
   // PRECONDITION:
   //   The lock isn't already held by this thread. Recursive locking is
   //   undefined behavior.
-  bool try_lock_for(chrono::SystemClock::duration timeout)
+  bool try_lock_for(chrono::SystemClock::duration for_at_least)
       PW_EXCLUSIVE_TRYLOCK_FUNCTION(true);
 
-  // Tries to lock the mutex. Blocks until specified deadline has been reached
-  // or the lock is acquired, whichever comes first.
+  // Attempts to lock the mutex where, if needed, blocking until at least the
+  // specified time_point.
   // Returns true if the mutex was successfully acquired.
   //
   // PRECONDITION:
   //   The lock isn't already held by this thread. Recursive locking is
   //   undefined behavior.
-  bool try_lock_until(chrono::SystemClock::time_point deadline)
+  bool try_lock_until(chrono::SystemClock::time_point until_at_least)
       PW_EXCLUSIVE_TRYLOCK_FUNCTION(true);
 };
 
-class PW_LOCKABLE("pw::sync::VirtualTimedMutex") VirtualTimedMutex final
-    : public VirtualBasicLockable {
- public:
-  VirtualTimedMutex() = default;
-
-  VirtualTimedMutex(const VirtualTimedMutex&) = delete;
-  VirtualTimedMutex(VirtualTimedMutex&&) = delete;
-  VirtualTimedMutex& operator=(const VirtualTimedMutex&) = delete;
-  VirtualTimedMutex& operator=(VirtualTimedMutex&&) = delete;
-
-  TimedMutex& timed_mutex() { return timed_mutex_; }
-
- private:
-  void DoLockOperation(Operation operation) override
-      PW_NO_LOCK_SAFETY_ANALYSIS {
-    switch (operation) {
-      case Operation::kLock:
-        return timed_mutex_.lock();
-
-      case Operation::kUnlock:
-      default:
-        return timed_mutex_.unlock();
-    }
-  }
-
-  TimedMutex timed_mutex_;
-};
-
 }  // namespace pw::sync
 
 #include "pw_sync_backend/timed_mutex_inline.h"
@@ -114,11 +84,11 @@
 bool pw_sync_TimedMutex_TryLock(pw_sync_TimedMutex* mutex)
     PW_NO_LOCK_SAFETY_ANALYSIS;
 bool pw_sync_TimedMutex_TryLockFor(pw_sync_TimedMutex* mutex,
-                                   pw_chrono_SystemClock_Duration timeout)
+                                   pw_chrono_SystemClock_Duration for_at_least)
     PW_NO_LOCK_SAFETY_ANALYSIS;
-bool pw_sync_TimedMutex_TryLockUntil(pw_sync_TimedMutex* mutex,
-                                     pw_chrono_SystemClock_TimePoint deadline)
-    PW_NO_LOCK_SAFETY_ANALYSIS;
+bool pw_sync_TimedMutex_TryLockUntil(
+    pw_sync_TimedMutex* mutex,
+    pw_chrono_SystemClock_TimePoint until_at_least) PW_NO_LOCK_SAFETY_ANALYSIS;
 void pw_sync_TimedMutex_Unlock(pw_sync_TimedMutex* mutex)
     PW_NO_LOCK_SAFETY_ANALYSIS;
 
diff --git a/pw_sync/public/pw_sync/timed_thread_notification.h b/pw_sync/public/pw_sync/timed_thread_notification.h
deleted file mode 100644
index 3e17db1..0000000
--- a/pw_sync/public/pw_sync/timed_thread_notification.h
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_chrono/system_clock.h"
-#include "pw_sync/thread_notification.h"
-
-namespace pw::sync {
-
-// The TimedThreadNotification is a synchronization primitive that can be used
-// to permit a SINGLE thread to block and consume a latching, saturating
-// notification from  multiple notifiers.
-//
-// IMPORTANT: This is a single consumer/waiter, multiple producer/notifier API!
-// The acquire APIs must only be invoked by a single consuming thread. As a
-// result, having multiple threads receiving notifications via the acquire API
-// is unsupported.
-//
-// This is effectively a subset of a binary semaphore API, except that only a
-// single thread can be notified and block at a time.
-//
-// The single consumer aspect of the API permits the use of a smaller and/or
-// faster native APIs such as direct thread signaling.
-class TimedThreadNotification : public ThreadNotification {
- public:
-  TimedThreadNotification() = default;
-  ~TimedThreadNotification() = default;
-  TimedThreadNotification(const TimedThreadNotification&) = delete;
-  TimedThreadNotification(TimedThreadNotification&&) = delete;
-  TimedThreadNotification& operator=(const TimedThreadNotification&) = delete;
-  TimedThreadNotification& operator=(TimedThreadNotification&&) = delete;
-
-  // Blocks until the specified timeout duration has elapsed or the thread
-  // has been notified (i.e. notification latch can be cleared because it was
-  // set), whichever comes first.
-  //
-  // Clears the notification latch.
-  //
-  // Returns true if the thread was notified, meaning the the internal latch was
-  // reset successfully.
-  //
-  // IMPORTANT: This should only be used by a single consumer thread.
-  bool try_acquire_for(chrono::SystemClock::duration timeout);
-
-  // Blocks until the specified deadline time has been reached the thread has
-  // been notified (i.e. notification latch can be cleared because it was set),
-  // whichever comes first.
-  //
-  // Clears the notification latch.
-  //
-  // Returns true if the thread was notified, meaning the the internal latch was
-  // reset successfully.
-  //
-  // IMPORTANT: This should only be used by a single consumer thread.
-  bool try_acquire_until(chrono::SystemClock::time_point deadline);
-};
-
-}  // namespace pw::sync
-
-#include "pw_sync_backend/timed_thread_notification_inline.h"
diff --git a/pw_sync/public/pw_sync/virtual_basic_lockable.h b/pw_sync/public/pw_sync/virtual_basic_lockable.h
deleted file mode 100644
index 4593e07..0000000
--- a/pw_sync/public/pw_sync/virtual_basic_lockable.h
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_polyfill/language_feature_macros.h"
-#include "pw_sync/lock_annotations.h"
-
-namespace pw::sync {
-
-// The VirtualBasicLockable is a virtual lock abstraction for locks which meet
-// the C++ named BasicLockable requirements of lock() and unlock().
-//
-// This virtual indirection is useful in case you need configurable lock
-// selection in a portable module where the final type is not defined upstream
-// and ergo module configuration cannot be used or in case the lock type is not
-// fixed at compile time, for example to support run time and crash time use of
-// an object without incurring the code size hit for templating the object.
-class PW_LOCKABLE("pw::sync::VirtualBasicLockable") VirtualBasicLockable {
- public:
-  void lock() PW_EXCLUSIVE_LOCK_FUNCTION() {
-    DoLockOperation(Operation::kLock);
-  };
-
-  void unlock() PW_UNLOCK_FUNCTION() { DoLockOperation(Operation::kUnlock); };
-
- protected:
-  ~VirtualBasicLockable() = default;
-
-  enum class Operation {
-    kLock,
-    kUnlock,
-  };
-
- private:
-  // Uses a single virtual method with an enum to minimize the vtable cost per
-  // implementation of VirtualBasicLockable.
-  virtual void DoLockOperation(Operation operation) = 0;
-};
-
-// The NoOpLock is a type of VirtualBasicLockable that does nothing, i.e. lock
-// operations are no-ops.
-class PW_LOCKABLE("pw::sync::NoOpLock") NoOpLock final
-    : public VirtualBasicLockable {
- public:
-  constexpr NoOpLock() {}
-  NoOpLock(const NoOpLock&) = delete;
-  NoOpLock(NoOpLock&&) = delete;
-  NoOpLock& operator=(const NoOpLock&) = delete;
-  NoOpLock& operator=(NoOpLock&&) = delete;
-
-  // Gives access to a global NoOpLock instance. It is not necessary to have
-  // multiple NoOpLock instances since they have no state and do nothing.
-  static NoOpLock& Instance() {
-    PW_CONSTINIT static NoOpLock lock;
-    return lock;
-  }
-
- private:
-  void DoLockOperation(Operation) override {}
-};
-
-}  // namespace pw::sync
diff --git a/pw_sync/public_overrides/pw_sync_backend/thread_notification_inline.h b/pw_sync/public_overrides/pw_sync_backend/thread_notification_inline.h
deleted file mode 100644
index 0e3a4a8..0000000
--- a/pw_sync/public_overrides/pw_sync_backend/thread_notification_inline.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_sync/backends/binary_semaphore_thread_notification_inline.h"
diff --git a/pw_sync/public_overrides/pw_sync_backend/thread_notification_native.h b/pw_sync/public_overrides/pw_sync_backend/thread_notification_native.h
deleted file mode 100644
index 6830910..0000000
--- a/pw_sync/public_overrides/pw_sync_backend/thread_notification_native.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_sync/backends/binary_semaphore_thread_notification_native.h"
diff --git a/pw_sync/public_overrides/pw_sync_backend/timed_thread_notification_inline.h b/pw_sync/public_overrides/pw_sync_backend/timed_thread_notification_inline.h
deleted file mode 100644
index 4297196..0000000
--- a/pw_sync/public_overrides/pw_sync_backend/timed_thread_notification_inline.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_sync/backends/binary_semaphore_timed_thread_notification_inline.h"
diff --git a/pw_sync/thread_notification_facade_test.cc b/pw_sync/thread_notification_facade_test.cc
deleted file mode 100644
index c8f6eaa..0000000
--- a/pw_sync/thread_notification_facade_test.cc
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <chrono>
-
-#include "gtest/gtest.h"
-#include "pw_sync/thread_notification.h"
-
-namespace pw::sync {
-namespace {
-
-TEST(ThreadNotification, EmptyInitialState) {
-  ThreadNotification notification;
-  EXPECT_FALSE(notification.try_acquire());
-}
-
-// TODO(pwbug/291): Add real concurrency tests.
-
-TEST(ThreadNotification, Release) {
-  ThreadNotification notification;
-  notification.release();
-  notification.release();
-  notification.acquire();
-  // Ensure it fails when empty.
-  EXPECT_FALSE(notification.try_acquire());
-}
-
-ThreadNotification empty_initial_notification;
-TEST(ThreadNotification, EmptyInitialStateStatic) {
-  EXPECT_FALSE(empty_initial_notification.try_acquire());
-}
-
-ThreadNotification raise_notification;
-TEST(ThreadNotification, ReleaseStatic) {
-  raise_notification.release();
-  raise_notification.release();
-  raise_notification.acquire();
-  // Ensure it fails when empty.
-  EXPECT_FALSE(raise_notification.try_acquire());
-}
-
-}  // namespace
-}  // namespace pw::sync
diff --git a/pw_sync/timed_mutex.cc b/pw_sync/timed_mutex.cc
index 22b29ae..512d4c4 100644
--- a/pw_sync/timed_mutex.cc
+++ b/pw_sync/timed_mutex.cc
@@ -25,14 +25,14 @@
 }
 
 extern "C" bool pw_sync_TimedMutex_TryLockFor(
-    pw_sync_TimedMutex* mutex, pw_chrono_SystemClock_Duration timeout) {
-  return mutex->try_lock_for(SystemClock::duration(timeout.ticks));
+    pw_sync_TimedMutex* mutex, pw_chrono_SystemClock_Duration for_at_least) {
+  return mutex->try_lock_for(SystemClock::duration(for_at_least.ticks));
 }
 
 extern "C" bool pw_sync_TimedMutex_TryLockUntil(
-    pw_sync_TimedMutex* mutex, pw_chrono_SystemClock_TimePoint deadline) {
+    pw_sync_TimedMutex* mutex, pw_chrono_SystemClock_TimePoint until_at_least) {
   return mutex->try_lock_until(SystemClock::time_point(
-      SystemClock::duration(deadline.duration_since_epoch.ticks)));
+      SystemClock::duration(until_at_least.duration_since_epoch.ticks)));
 }
 
 extern "C" void pw_sync_TimedMutex_Unlock(pw_sync_TimedMutex* mutex) {
diff --git a/pw_sync/timed_mutex_facade_test.cc b/pw_sync/timed_mutex_facade_test.cc
index 75ba2b6..ad4696e 100644
--- a/pw_sync/timed_mutex_facade_test.cc
+++ b/pw_sync/timed_mutex_facade_test.cc
@@ -29,10 +29,10 @@
 // Functions defined in mutex_facade_test_c.c which call the API from C.
 void pw_sync_TimedMutex_CallLock(pw_sync_TimedMutex* mutex);
 bool pw_sync_TimedMutex_CallTryLock(pw_sync_TimedMutex* mutex);
-bool pw_sync_TimedMutex_CallTryLockFor(pw_sync_TimedMutex* mutex,
-                                       pw_chrono_SystemClock_Duration timeout);
+bool pw_sync_TimedMutex_CallTryLockFor(
+    pw_sync_TimedMutex* mutex, pw_chrono_SystemClock_Duration for_at_least);
 bool pw_sync_TimedMutex_CallTryLockUntil(
-    pw_sync_TimedMutex* mutex, pw_chrono_SystemClock_TimePoint deadline);
+    pw_sync_TimedMutex* mutex, pw_chrono_SystemClock_TimePoint until_at_least);
 void pw_sync_TimedMutex_CallUnlock(pw_sync_TimedMutex* mutex);
 
 }  // extern "C"
@@ -50,17 +50,17 @@
 TEST(TimedMutex, LockUnlock) {
   pw::sync::TimedMutex mutex;
   mutex.lock();
-  mutex.unlock();
-  // TODO(pwbug/291): Ensure it fails to lock when already held by someone else.
+  // TODO(pwbug/291): Ensure it fails to lock when already held.
   // EXPECT_FALSE(mutex.try_lock());
+  mutex.unlock();
 }
 
 TimedMutex static_mutex;
 TEST(TimedMutex, LockUnlockStatic) {
   static_mutex.lock();
-  static_mutex.unlock();
-  // TODO(pwbug/291): Ensure it fails to lock when already held by someone else.
+  // TODO(pwbug/291): Ensure it fails to lock when already held.
   // EXPECT_FALSE(static_mutex.try_lock());
+  static_mutex.unlock();
 }
 
 TEST(TimedMutex, TryLockUnlock) {
@@ -68,11 +68,10 @@
   const bool locked = mutex.try_lock();
   EXPECT_TRUE(locked);
   if (locked) {
+    // TODO(pwbug/291): Ensure it fails to lock when already held.
     // EXPECT_FALSE(mutex.try_lock());
     mutex.unlock();
   }
-  // TODO(pwbug/291): Ensure it fails to lock when already held by someone
-  // else.
 }
 
 TEST(TimedMutex, TryLockUnlockFor) {
@@ -84,14 +83,15 @@
   if (locked) {
     SystemClock::duration time_elapsed = SystemClock::now() - before;
     EXPECT_LT(time_elapsed, kRoundedArbitraryDuration);
+
+    // TODO(pwbug/291): Ensure it blocks fails to lock when already held.
+    // before = SystemClock::now();
+    // EXPECT_FALSE(mutex.try_lock_for(kRoundedArbitraryDuration));
+    // time_elapsed = SystemClock::now() - before;
+    /// EXPECT_GE(time_elapsed, kRoundedArbitraryDuration);
+
     mutex.unlock();
   }
-  // TODO(pwbug/291): Ensure it blocks and fails to lock when already held by
-  // someone else.
-  // TODO(pwbug/291): Ensure it does not block and fails to lock when already
-  // held by someone else and a zero length duration is used.
-  // TODO(pwbug/291): Ensure it does not block and fails to lock when already
-  // held by someone else and a negative duration is used.
 }
 
 TEST(TimedMutex, TryLockUnlockUntil) {
@@ -103,30 +103,15 @@
   EXPECT_TRUE(locked);
   if (locked) {
     EXPECT_LT(SystemClock::now(), deadline);
+
+    // TODO(pwbug/291): Ensure it blocks fails to lock when already held.
+    // EXPECT_FALSE(
+    //     mutex.try_lock_until(SystemClock::now() +
+    //     kRoundedArbitraryDuration));
+    // EXPECT_GE(SystemClock::now(), deadline);
+
     mutex.unlock();
   }
-  // TODO(pwbug/291): Ensure it blocks and fails to lock when already held by
-  // someone else.
-  // TODO(pwbug/291): Ensure it does not block and fails to lock when already
-  // held by someone else and now is used.
-  // TODO(pwbug/291): Ensure it does not block and fails to lock when already
-  // held by someone else and a timestamp in the past is used.
-}
-
-TEST(VirtualTimedMutex, LockUnlock) {
-  pw::sync::VirtualTimedMutex mutex;
-  mutex.lock();
-  // TODO(pwbug/291): Ensure it fails to lock when already held by someone else.
-  // EXPECT_FALSE(mutex.try_lock());
-  mutex.unlock();
-}
-
-VirtualTimedMutex static_virtual_mutex;
-TEST(VirtualTimedMutex, LockUnlockStatic) {
-  static_virtual_mutex.lock();
-  // TODO(pwbug/291): Ensure it fails to lock when already held by someone else.
-  // EXPECT_FALSE(static_virtual_mutex.try_lock());
-  static_virtual_mutex.unlock();
 }
 
 TEST(TimedMutex, LockUnlockInC) {
@@ -138,7 +123,7 @@
 TEST(TimedMutex, TryLockUnlockInC) {
   pw::sync::TimedMutex mutex;
   ASSERT_TRUE(pw_sync_TimedMutex_CallTryLock(&mutex));
-  // TODO(pwbug/291): Ensure it fails to lock when already held by someone else.
+  // TODO(pwbug/291): Ensure it fails to lock when already held.
   // EXPECT_FALSE(pw_sync_TimedMutex_CallTryLock(&mutex));
   pw_sync_TimedMutex_CallUnlock(&mutex);
 }
@@ -152,13 +137,17 @@
   pw_chrono_SystemClock_Duration time_elapsed =
       pw_chrono_SystemClock_TimeElapsed(before, pw_chrono_SystemClock_Now());
   EXPECT_LT(time_elapsed.ticks, kRoundedArbitraryDurationInC.ticks);
+
+  // TODO(pwbug/291): Ensure it blocks fails to lock when already held.
+  // before = pw_chrono_SystemClock_Now();
+  // EXPECT_FALSE(
+  //     pw_sync_TimedMutex_CallTryLockFor(&mutex,
+  //     kRoundedArbitraryDurationInC));
+  // time_elapsed =
+  //    pw_chrono_SystemClock_TimeElapsed(before, pw_chrono_SystemClock_Now());
+  // EXPECT_GE(time_elapsed.ticks, kRoundedArbitraryDurationInC.ticks);
+
   pw_sync_TimedMutex_CallUnlock(&mutex);
-  // TODO(pwbug/291): Ensure it blocks and fails to lock when already held by
-  // someone else.
-  // TODO(pwbug/291): Ensure it does not block and fails to lock when already
-  // held by someone else and a zero length duration is used.
-  // TODO(pwbug/291): Ensure it does not block and fails to lock when already
-  // held by someone else and a negative duration is used.
 }
 
 TEST(TimedMutex, TryLockUnlockUntilInC) {
@@ -170,13 +159,13 @@
   ASSERT_TRUE(pw_sync_TimedMutex_CallTryLockUntil(&mutex, deadline));
   EXPECT_LT(pw_chrono_SystemClock_Now().duration_since_epoch.ticks,
             deadline.duration_since_epoch.ticks);
+
+  // TODO(pwbug/291): Ensure it blocks fails to lock when already held.
+  // EXPECT_FALSE(pw_sync_TimedMutex_CallTryLockUntil(&mutex, deadline));
+  // EXPECT_GE(pw_chrono_SystemClock_Now().duration_since_epoch.ticks,
+  //           deadline.duration_since_epoch.ticks);
+
   pw_sync_TimedMutex_CallUnlock(&mutex);
-  // TODO(pwbug/291): Ensure it blocks and fails to lock when already held by
-  // someone else.
-  // TODO(pwbug/291): Ensure it does not block and fails to lock when already
-  // held by someone else and now is used.
-  // TODO(pwbug/291): Ensure it does not block and fails to lock when already
-  // held by someone else and a timestamp in the past is used.
 }
 
 }  // namespace
diff --git a/pw_sync/timed_mutex_facade_test_c.c b/pw_sync/timed_mutex_facade_test_c.c
index 04e819e..50f5dcb 100644
--- a/pw_sync/timed_mutex_facade_test_c.c
+++ b/pw_sync/timed_mutex_facade_test_c.c
@@ -27,14 +27,14 @@
   return pw_sync_TimedMutex_TryLock(mutex);
 }
 
-bool pw_sync_TimedMutex_CallTryLockFor(pw_sync_TimedMutex* mutex,
-                                       pw_chrono_SystemClock_Duration timeout) {
-  return pw_sync_TimedMutex_TryLockFor(mutex, timeout);
+bool pw_sync_TimedMutex_CallTryLockFor(
+    pw_sync_TimedMutex* mutex, pw_chrono_SystemClock_Duration for_at_least) {
+  return pw_sync_TimedMutex_TryLockFor(mutex, for_at_least);
 }
 
 bool pw_sync_TimedMutex_CallTryLockUntil(
-    pw_sync_TimedMutex* mutex, pw_chrono_SystemClock_TimePoint deadline) {
-  return pw_sync_TimedMutex_TryLockUntil(mutex, deadline);
+    pw_sync_TimedMutex* mutex, pw_chrono_SystemClock_TimePoint until_at_least) {
+  return pw_sync_TimedMutex_TryLockUntil(mutex, until_at_least);
 }
 
 void pw_sync_TimedMutex_CallUnlock(pw_sync_TimedMutex* mutex) {
diff --git a/pw_sync/timed_thread_notification_facade_test.cc b/pw_sync/timed_thread_notification_facade_test.cc
deleted file mode 100644
index 84bb415..0000000
--- a/pw_sync/timed_thread_notification_facade_test.cc
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <chrono>
-
-#include "gtest/gtest.h"
-#include "pw_chrono/system_clock.h"
-#include "pw_sync/timed_thread_notification.h"
-
-using pw::chrono::SystemClock;
-using namespace std::chrono_literals;
-
-namespace pw::sync {
-namespace {
-
-// We can't control the SystemClock's period configuration, so just in case
-// duration cannot be accurately expressed in integer ticks, round the
-// duration up.
-constexpr SystemClock::duration kRoundedArbitraryDuration =
-    SystemClock::for_at_least(42ms);
-
-TEST(TimedThreadNotification, EmptyInitialState) {
-  TimedThreadNotification notification;
-  EXPECT_FALSE(notification.try_acquire());
-}
-
-// TODO(pwbug/291): Add real concurrency tests.
-
-TEST(TimedThreadNotification, Release) {
-  TimedThreadNotification notification;
-  notification.release();
-  notification.release();
-  notification.acquire();
-  // Ensure it fails when not notified.
-  EXPECT_FALSE(notification.try_acquire());
-}
-
-TimedThreadNotification empty_initial_notification;
-TEST(TimedThreadNotification, EmptyInitialStateStatic) {
-  EXPECT_FALSE(empty_initial_notification.try_acquire());
-}
-
-TimedThreadNotification raise_notification;
-TEST(TimedThreadNotification, ReleaseStatic) {
-  raise_notification.release();
-  raise_notification.release();
-  raise_notification.acquire();
-  // Ensure it fails when not notified.
-  EXPECT_FALSE(raise_notification.try_acquire());
-}
-
-TEST(TimedThreadNotification, TryAcquireForNotified) {
-  TimedThreadNotification notification;
-  notification.release();
-
-  // Ensure it doesn't block and succeeds when notified.
-  SystemClock::time_point before = SystemClock::now();
-  EXPECT_TRUE(notification.try_acquire_for(kRoundedArbitraryDuration));
-  SystemClock::duration time_elapsed = SystemClock::now() - before;
-  EXPECT_LT(time_elapsed, kRoundedArbitraryDuration);
-}
-
-TEST(TimedThreadNotification, TryAcquireForNotNotifiedPositiveTimeout) {
-  TimedThreadNotification notification;
-
-  // Ensure it blocks and fails when not notified for the full timeout.
-  SystemClock::time_point before = SystemClock::now();
-  EXPECT_FALSE(notification.try_acquire_for(kRoundedArbitraryDuration));
-  SystemClock::duration time_elapsed = SystemClock::now() - before;
-  EXPECT_GE(time_elapsed, kRoundedArbitraryDuration);
-}
-
-TEST(TimedThreadNotification, TryAcquireForNotNotifiedZeroLengthTimeout) {
-  TimedThreadNotification notification;
-
-  // Ensure it doesn't block when a zero length duration is used.
-  SystemClock::time_point before = SystemClock::now();
-  EXPECT_FALSE(notification.try_acquire_for(SystemClock::duration::zero()));
-  SystemClock::duration time_elapsed = SystemClock::now() - before;
-  EXPECT_LT(time_elapsed, kRoundedArbitraryDuration);
-}
-
-TEST(TimedThreadNotification, TryAcquireForNotNotifiedNegativeTimeout) {
-  TimedThreadNotification notification;
-
-  // Ensure it doesn't block when a negative duration is used.
-  SystemClock::time_point before = SystemClock::now();
-  EXPECT_FALSE(notification.try_acquire_for(-kRoundedArbitraryDuration));
-  SystemClock::duration time_elapsed = SystemClock::now() - before;
-  EXPECT_LT(time_elapsed, kRoundedArbitraryDuration);
-}
-
-TEST(TimedThreadNotification, TryAcquireUntilNotified) {
-  TimedThreadNotification notification;
-  notification.release();
-
-  // Ensure it doesn't block and succeeds when notified.
-  SystemClock::time_point deadline =
-      SystemClock::now() + kRoundedArbitraryDuration;
-  EXPECT_TRUE(notification.try_acquire_until(deadline));
-  EXPECT_LT(SystemClock::now(), deadline);
-}
-
-TEST(TimedThreadNotification, TryAcquireUntilNotNotifiedFutureDeadline) {
-  TimedThreadNotification notification;
-
-  // Ensure it blocks and fails when not notified.
-  SystemClock::time_point deadline =
-      SystemClock::now() + kRoundedArbitraryDuration;
-  EXPECT_FALSE(notification.try_acquire_until(deadline));
-  EXPECT_GE(SystemClock::now(), deadline);
-}
-
-TEST(TimedThreadNotification, TryAcquireUntilNotNotifiedCurrentDeadline) {
-  TimedThreadNotification notification;
-
-  // Ensure it doesn't block when now is used.
-  SystemClock::time_point deadline =
-      SystemClock::now() + kRoundedArbitraryDuration;
-  EXPECT_FALSE(notification.try_acquire_until(SystemClock::now()));
-  EXPECT_LT(SystemClock::now(), deadline);
-}
-
-TEST(TimedThreadNotification, TryAcquireUntilNotNotifiedPastDeadline) {
-  TimedThreadNotification notification;
-
-  // Ensure it doesn't block when a timestamp in the past is used.
-  SystemClock::time_point deadline =
-      SystemClock::now() + kRoundedArbitraryDuration;
-  EXPECT_FALSE(notification.try_acquire_until(SystemClock::now() -
-                                              kRoundedArbitraryDuration));
-  EXPECT_LT(SystemClock::now(), deadline);
-}
-
-}  // namespace
-}  // namespace pw::sync
diff --git a/pw_sync_baremetal/BUILD b/pw_sync_baremetal/BUILD
new file mode 100644
index 0000000..16774df
--- /dev/null
+++ b/pw_sync_baremetal/BUILD
@@ -0,0 +1,46 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "interrupt_spin_lock_headers",
+    hdrs = [
+        "public/pw_sync_baremetal/interrupt_spin_lock_inline.h",
+        "public/pw_sync_baremetal/interrupt_spin_lock_native.h",
+        "public_overrides/pw_sync_backend/interrupt_spin_lock_inline.h",
+        "public_overrides/pw_sync_backend/interrupt_spin_lock_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+)
+
+pw_cc_library(
+    name = "interrupt_spin_lock",
+    deps = [
+        ":interrupt_spin_lock_headers",
+        "//pw_assert",
+        "//pw_sync:interrupt_spin_lock_facade",
+        "//pw_sync:yield_core",
+    ],
+)
diff --git a/pw_sync_baremetal/BUILD.bazel b/pw_sync_baremetal/BUILD.bazel
deleted file mode 100644
index e23c3c7..0000000
--- a/pw_sync_baremetal/BUILD.bazel
+++ /dev/null
@@ -1,73 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "interrupt_spin_lock_headers",
-    hdrs = [
-        "public/pw_sync_baremetal/interrupt_spin_lock_inline.h",
-        "public/pw_sync_baremetal/interrupt_spin_lock_native.h",
-        "public_overrides/pw_sync_backend/interrupt_spin_lock_inline.h",
-        "public_overrides/pw_sync_backend/interrupt_spin_lock_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = ["@platforms//os:none"],
-)
-
-pw_cc_library(
-    name = "interrupt_spin_lock",
-    target_compatible_with = ["@platforms//os:none"],
-    deps = [
-        ":interrupt_spin_lock_headers",
-        "//pw_assert",
-        "//pw_sync:interrupt_spin_lock_facade",
-        "//pw_sync:yield_core",
-    ],
-)
-
-pw_cc_library(
-    name = "mutex_headers",
-    hdrs = [
-        "public/pw_sync_baremetal/mutex_inline.h",
-        "public/pw_sync_baremetal/mutex_native.h",
-        "public_overrides/pw_sync_backend/mutex_inline.h",
-        "public_overrides/pw_sync_backend/mutex_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = ["@platforms//os:none"],
-)
-
-pw_cc_library(
-    name = "mutex",
-    target_compatible_with = ["@platforms//os:none"],
-    deps = [
-        ":mutex_headers",
-        "//pw_assert",
-        "//pw_sync:mutex_facade",
-    ],
-)
diff --git a/pw_sync_baremetal/BUILD.gn b/pw_sync_baremetal/BUILD.gn
index e862556..881e807 100644
--- a/pw_sync_baremetal/BUILD.gn
+++ b/pw_sync_baremetal/BUILD.gn
@@ -33,8 +33,7 @@
 # asserts if it is unavailable. It does not perform interrupt masking or disable
 # global interrupts, so this implementation does not support simultaneous
 # multi-threaded environments including IRQs, and is only meant to prevent
-# data corruption. This implementation is not yet set up to support hardware
-# multi-threading (SMP, SMT, etc).
+# data corruption.
 pw_source_set("interrupt_spin_lock") {
   public_configs = [
     ":public_include_path",
@@ -53,27 +52,6 @@
   ]
 }
 
-# This target provides the backend for pw::sync::Mutex.
-# The provided implementation makes a single attempt to acquire the lock and
-# asserts if it is unavailable. This implementation is not yet set up to support
-# hardware multi-threading (SMP, SMT, etc).
-pw_source_set("mutex") {
-  public_configs = [
-    ":public_include_path",
-    ":backend_config",
-  ]
-  public = [
-    "public/pw_sync_baremetal/mutex_inline.h",
-    "public/pw_sync_baremetal/mutex_native.h",
-    "public_overrides/pw_sync_backend/mutex_inline.h",
-    "public_overrides/pw_sync_backend/mutex_native.h",
-  ]
-  public_deps = [
-    "$dir_pw_assert",
-    "$dir_pw_sync:mutex.facade",
-  ]
-}
-
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
diff --git a/pw_sync_baremetal/OWNERS b/pw_sync_baremetal/OWNERS
deleted file mode 100644
index e9a6c58..0000000
--- a/pw_sync_baremetal/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-ewout@google.com
-hepler@google.com
diff --git a/pw_sync_baremetal/docs.rst b/pw_sync_baremetal/docs.rst
index 8276791..1fbd99e 100644
--- a/pw_sync_baremetal/docs.rst
+++ b/pw_sync_baremetal/docs.rst
@@ -1,28 +1,11 @@
 .. _module-pw_sync_baremetal:
 
-=================
+-----------------
 pw_sync_baremetal
-=================
+-----------------
 This is a set of backends for pw_sync that works on baremetal targets. It is not
-ready for use, and is under construction.
-
-.. note::
-  All constructs in this baremetal backend do not support hardware multi-threading
-  (SMP, SMT, etc).
-
-.. warning::
-  It does not perform interrupt masking or disable global interrupts. This is not
-  safe to use yet!
-
--------------------------------------
-pw_sync_baremetal's InterruptSpinLock
--------------------------------------
-The interrupt spin-lock implementation makes a single attempt to acquire the lock
-and asserts if it is unavailable. It does not perform interrupt masking or disable global
-interrupts.
-
--------------------------
-pw_sync_baremetal's Mutex
--------------------------
-The mutex implementation makes a single attempt to acquire the lock and asserts if
-it is unavailable.
+ready for use, and is under construction. The provided implementation makes a
+single attempt to acquire the lock and asserts if it is unavailable. It does not
+perform interrupt masking or disable global interrupts, so this implementation
+does not support simultaneous multi-threaded environments including IRQs, and is
+only meant to prevent data corruption.
diff --git a/pw_sync_baremetal/public/pw_sync_baremetal/interrupt_spin_lock_inline.h b/pw_sync_baremetal/public/pw_sync_baremetal/interrupt_spin_lock_inline.h
index 505cef3..369c956 100644
--- a/pw_sync_baremetal/public/pw_sync_baremetal/interrupt_spin_lock_inline.h
+++ b/pw_sync_baremetal/public/pw_sync_baremetal/interrupt_spin_lock_inline.h
@@ -13,7 +13,7 @@
 // the License.
 #pragma once
 
-#include "pw_assert/assert.h"
+#include "pw_assert/light.h"
 #include "pw_sync/interrupt_spin_lock.h"
 #include "pw_sync/yield_core.h"
 
diff --git a/pw_sync_baremetal/public/pw_sync_baremetal/mutex_inline.h b/pw_sync_baremetal/public/pw_sync_baremetal/mutex_inline.h
deleted file mode 100644
index 70a10c2..0000000
--- a/pw_sync_baremetal/public/pw_sync_baremetal/mutex_inline.h
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_assert/assert.h"
-#include "pw_sync/mutex.h"
-
-namespace pw::sync {
-
-inline Mutex::Mutex() : native_type_() {}
-
-inline Mutex::~Mutex() {}
-
-inline void Mutex::lock() { PW_ASSERT(try_lock()); }
-
-inline bool Mutex::try_lock() {
-  return !native_type_.test_and_set(std::memory_order_acquire);
-}
-
-inline void Mutex::unlock() {
-  PW_ASSERT(native_type_.test_and_set(std::memory_order_acquire));
-  native_type_.clear(std::memory_order_release);
-}
-
-inline Mutex::native_handle_type Mutex::native_handle() { return native_type_; }
-
-}  // namespace pw::sync
diff --git a/pw_sync_baremetal/public/pw_sync_baremetal/mutex_native.h b/pw_sync_baremetal/public/pw_sync_baremetal/mutex_native.h
deleted file mode 100644
index f5d6e13..0000000
--- a/pw_sync_baremetal/public/pw_sync_baremetal/mutex_native.h
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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>
-
-namespace pw::sync::backend {
-
-using NativeMutex = std::atomic_flag;
-using NativeMutexHandle = std::atomic_flag&;
-
-}  // namespace pw::sync::backend
diff --git a/pw_sync_baremetal/public_overrides/pw_sync_backend/mutex_inline.h b/pw_sync_baremetal/public_overrides/pw_sync_backend/mutex_inline.h
deleted file mode 100644
index f55dcc2..0000000
--- a/pw_sync_baremetal/public_overrides/pw_sync_backend/mutex_inline.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_sync_baremetal/mutex_inline.h"
diff --git a/pw_sync_baremetal/public_overrides/pw_sync_backend/mutex_native.h b/pw_sync_baremetal/public_overrides/pw_sync_backend/mutex_native.h
deleted file mode 100644
index 9d6b9e0..0000000
--- a/pw_sync_baremetal/public_overrides/pw_sync_backend/mutex_native.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_sync_baremetal/mutex_native.h"
diff --git a/pw_sync_embos/BUILD b/pw_sync_embos/BUILD
new file mode 100644
index 0000000..da66e9e
--- /dev/null
+++ b/pw_sync_embos/BUILD
@@ -0,0 +1,174 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "binary_semaphore_headers",
+    hdrs = [
+        "public/pw_sync_embos/binary_semaphore_inline.h",
+        "public/pw_sync_embos/binary_semaphore_native.h",
+        "public_overrides/pw_sync_backend/binary_semaphore_inline.h",
+        "public_overrides/pw_sync_backend/binary_semaphore_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        # TODO(pwbug/317): This should depend on embOS but our third parties
+        # currently do not have Bazel support.
+        "//pw_chrono:system_clock",
+        "//pw_chrono_embos:system_clock_headers",
+    ],
+)
+
+pw_cc_library(
+    name = "binary_semaphore",
+    srcs = [
+        "binary_semaphore.cc",
+    ],
+    deps = [
+        ":binary_semaphore_headers",
+        "//pw_interrupt:context",
+        "//pw_sync:binary_semaphore_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "counting_semaphore_headers",
+    hdrs = [
+        "public/pw_sync_embos/counting_semaphore_inline.h",
+        "public/pw_sync_embos/counting_semaphore_native.h",
+        "public_overrides/pw_sync_backend/counting_semaphore_inline.h",
+        "public_overrides/pw_sync_backend/counting_semaphore_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        # TODO(pwbug/317): This should depend on embOS but our third parties
+        # currently do not have Bazel support.
+        "//pw_chrono:system_clock",
+        "//pw_chrono_embos:system_clock_headers",
+    ],
+)
+
+pw_cc_library(
+    name = "counting_semaphore",
+    srcs = [
+        "counting_semaphore.cc",
+    ],
+    deps = [
+        ":counting_semaphore_headers",
+        "//pw_interrupt:context",
+        "//pw_sync:counting_semaphore_facade",
+    ],
+)
+
+
+pw_cc_library(
+    name = "mutex_headers",
+    hdrs = [
+        "public/pw_sync_embos/mutex_inline.h",
+        "public/pw_sync_embos/mutex_native.h",
+        "public_overrides/pw_sync_backend/mutex_inline.h",
+        "public_overrides/pw_sync_backend/mutex_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        # TODO(pwbug/317): This should depend on embOS but our third parties
+        # currently do not have Bazel support.
+        "//pw_sync:mutex_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "mutex",
+    deps = [
+        ":mutex_headers",
+        "//pw_sync:mutex_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "timed_mutex_headers",
+    hdrs = [
+        "public/pw_sync_embos/timed_mutex_inline.h",
+        "public_overrides/pw_sync_backend/timed_mutex_inline.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        # TODO(pwbug/317): This should depend on embOS but our third parties
+        # currently do not have Bazel support.
+        "//pw_chrono:system_clock",
+        "//pw_sync:timed_mutex_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "timed_mutex",
+    srcs = [
+        "timed_mutex.cc",
+    ],
+    deps = [
+        ":timed_mutex_headers",
+        "//pw_interrupt:context",
+        "//pw_sync:timed_mutex_facade",
+        "//pw_chrono_embos:system_clock_headers",
+    ],
+)
+
+
+pw_cc_library(
+    name = "interrupt_spin_lock_headers",
+    hdrs = [
+        "public/pw_sync_embos/interrupt_spin_lock_inline.h",
+        "public/pw_sync_embos/interrupt_spin_lock_native.h",
+        "public_overrides/pw_sync_backend/interrupt_spin_lock_inline.h",
+        "public_overrides/pw_sync_backend/interrupt_spin_lock_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    # TODO(pwbug/317): This should depend on embOS but our third parties
+    # currently do not have Bazel support.
+)
+
+pw_cc_library(
+    name = "interrupt_spin_lock",
+    srcs = [
+        "interrupt_spin_lock.cc",
+    ],
+    deps = [
+        ":interrupt_spin_lock_headers",
+        "//pw_sync:interrupt_spin_lock_facade",
+    ],
+)
+
diff --git a/pw_sync_embos/BUILD.bazel b/pw_sync_embos/BUILD.bazel
deleted file mode 100644
index 23ad1ff..0000000
--- a/pw_sync_embos/BUILD.bazel
+++ /dev/null
@@ -1,201 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "binary_semaphore_headers",
-    hdrs = [
-        "public/pw_sync_embos/binary_semaphore_inline.h",
-        "public/pw_sync_embos/binary_semaphore_native.h",
-        "public_overrides/pw_sync_backend/binary_semaphore_inline.h",
-        "public_overrides/pw_sync_backend/binary_semaphore_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:embos",
-    ],
-    deps = [
-        # TODO(pwbug/317): This should depend on embOS but our third parties
-        # currently do not have Bazel support.
-        "//pw_chrono:system_clock",
-        "//pw_chrono_embos:system_clock_headers",
-    ],
-)
-
-pw_cc_library(
-    name = "binary_semaphore",
-    srcs = [
-        "binary_semaphore.cc",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:embos",
-    ],
-    deps = [
-        ":binary_semaphore_headers",
-        "//pw_interrupt:context",
-        "//pw_sync:binary_semaphore_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "counting_semaphore_headers",
-    hdrs = [
-        "public/pw_sync_embos/counting_semaphore_inline.h",
-        "public/pw_sync_embos/counting_semaphore_native.h",
-        "public_overrides/pw_sync_backend/counting_semaphore_inline.h",
-        "public_overrides/pw_sync_backend/counting_semaphore_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:embos",
-    ],
-    deps = [
-        # TODO(pwbug/317): This should depend on embOS but our third parties
-        # currently do not have Bazel support.
-        "//pw_chrono:system_clock",
-        "//pw_chrono_embos:system_clock_headers",
-    ],
-)
-
-pw_cc_library(
-    name = "counting_semaphore",
-    srcs = [
-        "counting_semaphore.cc",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:embos",
-    ],
-    deps = [
-        ":counting_semaphore_headers",
-        "//pw_interrupt:context",
-        "//pw_sync:counting_semaphore_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "mutex_headers",
-    hdrs = [
-        "public/pw_sync_embos/mutex_inline.h",
-        "public/pw_sync_embos/mutex_native.h",
-        "public_overrides/pw_sync_backend/mutex_inline.h",
-        "public_overrides/pw_sync_backend/mutex_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:embos",
-    ],
-    deps = [
-        # TODO(pwbug/317): This should depend on embOS but our third parties
-        # currently do not have Bazel support.
-        "//pw_sync:mutex_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "mutex",
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:embos",
-    ],
-    deps = [
-        ":mutex_headers",
-        "//pw_sync:mutex_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "timed_mutex_headers",
-    hdrs = [
-        "public/pw_sync_embos/timed_mutex_inline.h",
-        "public_overrides/pw_sync_backend/timed_mutex_inline.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:embos",
-    ],
-    deps = [
-        # TODO(pwbug/317): This should depend on embOS but our third parties
-        # currently do not have Bazel support.
-        "//pw_chrono:system_clock",
-        "//pw_sync:timed_mutex_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "timed_mutex",
-    srcs = [
-        "timed_mutex.cc",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:embos",
-    ],
-    deps = [
-        ":timed_mutex_headers",
-        "//pw_chrono_embos:system_clock_headers",
-        "//pw_interrupt:context",
-        "//pw_sync:timed_mutex_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "interrupt_spin_lock_headers",
-    hdrs = [
-        "public/pw_sync_embos/interrupt_spin_lock_inline.h",
-        "public/pw_sync_embos/interrupt_spin_lock_native.h",
-        "public_overrides/pw_sync_backend/interrupt_spin_lock_inline.h",
-        "public_overrides/pw_sync_backend/interrupt_spin_lock_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:embos",
-    ],
-    # TODO(pwbug/317): This should depend on embOS but our third parties
-    # currently do not have Bazel support.
-)
-
-pw_cc_library(
-    name = "interrupt_spin_lock",
-    srcs = [
-        "interrupt_spin_lock.cc",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:embos",
-    ],
-    deps = [
-        ":interrupt_spin_lock_headers",
-        "//pw_sync:interrupt_spin_lock_facade",
-    ],
-)
diff --git a/pw_sync_embos/BUILD.gn b/pw_sync_embos/BUILD.gn
index c399748..043ae72 100644
--- a/pw_sync_embos/BUILD.gn
+++ b/pw_sync_embos/BUILD.gn
@@ -14,7 +14,6 @@
 
 import("//build_overrides/pigweed.gni")
 
-import("$dir_pw_build/error.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_chrono/backend.gni")
 import("$dir_pw_docgen/docs.gni")
@@ -29,15 +28,6 @@
   visibility = [ ":*" ]
 }
 
-pw_build_assert("check_system_clock_backend") {
-  condition =
-      pw_chrono_SYSTEM_CLOCK_BACKEND == "" ||
-      pw_chrono_SYSTEM_CLOCK_BACKEND == "$dir_pw_chrono_embos:system_clock"
-  message = "The embOS pw_sync backends only work with the " +
-            "embOS pw::chrono::SystemClock backend."
-  visibility = [ ":*" ]
-}
-
 # This target provides the backend for pw::sync::BinarySemaphore.
 pw_source_set("binary_semaphore") {
   public_configs = [
@@ -58,10 +48,12 @@
     "$dir_pw_third_party/embos",
   ]
   sources = [ "binary_semaphore.cc" ]
-  deps = [
-    ":check_system_clock_backend",
-    "$dir_pw_sync:binary_semaphore.facade",
-  ]
+  deps = [ "$dir_pw_sync:binary_semaphore.facade" ]
+  assert(
+      pw_chrono_SYSTEM_CLOCK_BACKEND == "" ||
+          pw_chrono_SYSTEM_CLOCK_BACKEND == "$dir_pw_chrono_embos:system_clock",
+      "The embOS pw::sync::BinarySemaphore backend only works with the " +
+          "embOS pw::chrono::SystemClock backend.")
 }
 
 # This target provides the backend for pw::sync::CountingSemaphore.
@@ -84,10 +76,12 @@
     "$dir_pw_third_party/embos",
   ]
   sources = [ "counting_semaphore.cc" ]
-  deps = [
-    ":check_system_clock_backend",
-    "$dir_pw_sync:counting_semaphore.facade",
-  ]
+  deps = [ "$dir_pw_sync:counting_semaphore.facade" ]
+  assert(
+      pw_chrono_SYSTEM_CLOCK_BACKEND == "" ||
+          pw_chrono_SYSTEM_CLOCK_BACKEND == "$dir_pw_chrono_embos:system_clock",
+      "The embOS pw::sync::CountingSemaphore backend only works with " +
+          "the embOS pw::chrono::SystemClock backend.")
 }
 
 # This target provides the backend for pw::sync::Mutex.
@@ -126,12 +120,16 @@
   ]
   sources = [ "timed_mutex.cc" ]
   deps = [
-    ":check_system_clock_backend",
     "$dir_pw_assert",
     "$dir_pw_chrono_embos:system_clock",
     "$dir_pw_interrupt:context",
     "$dir_pw_third_party/embos",
   ]
+  assert(
+      pw_chrono_SYSTEM_CLOCK_BACKEND == "" ||
+          pw_chrono_SYSTEM_CLOCK_BACKEND == "$dir_pw_chrono_embos:system_clock",
+      "The embOS pw::sync::Mutex backend only works with the embOS " +
+          "pw::chrono::SystemClock backend.")
 }
 
 # This target provides the backend for pw::sync::InterruptSpinLock.
diff --git a/pw_sync_embos/OWNERS b/pw_sync_embos/OWNERS
deleted file mode 100644
index e9a6c58..0000000
--- a/pw_sync_embos/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-ewout@google.com
-hepler@google.com
diff --git a/pw_sync_embos/binary_semaphore.cc b/pw_sync_embos/binary_semaphore.cc
index 9c1ce91..7f77afe 100644
--- a/pw_sync_embos/binary_semaphore.cc
+++ b/pw_sync_embos/binary_semaphore.cc
@@ -17,7 +17,7 @@
 #include <algorithm>
 
 #include "RTOS.h"
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_chrono/system_clock.h"
 #include "pw_chrono_embos/system_clock_constants.h"
 #include "pw_interrupt/context.h"
@@ -26,34 +26,27 @@
 
 namespace pw::sync {
 
-bool BinarySemaphore::try_acquire_for(SystemClock::duration timeout) {
-  // Enforce the pw::sync::BinarySemaphore IRQ contract.
+bool BinarySemaphore::try_acquire_for(SystemClock::duration for_at_least) {
   PW_DCHECK(!interrupt::InInterruptContext());
 
   // Use non-blocking try_acquire for negative and zero length durations.
-  if (timeout <= SystemClock::duration::zero()) {
+  if (for_at_least <= SystemClock::duration::zero()) {
     return try_acquire();
   }
 
-  // In case the timeout is too long for us to express through the native
-  // embOS API, we repeatedly wait with shorter durations. Note that on a tick
-  // based kernel we cannot tell how far along we are on the current tick, ergo
-  // we add one whole tick to the final duration. However, this also means that
-  // the loop must ensure that timeout + 1 is less than the max timeout.
-  constexpr SystemClock::duration kMaxTimeoutMinusOne =
-      pw::chrono::embos::kMaxTimeout - SystemClock::duration(1);
-  while (timeout > kMaxTimeoutMinusOne) {
-    if (OS_WaitCSemaTimed(&native_type_,
-                          static_cast<OS_TIME>(kMaxTimeoutMinusOne.count()))) {
-      return true;
-    }
-    timeout -= kMaxTimeoutMinusOne;
-  }
-
   // On a tick based kernel we cannot tell how far along we are on the current
   // tick, ergo we add one whole tick to the final duration.
+  constexpr SystemClock::duration kMaxTimeoutMinusOne =
+      pw::chrono::embos::kMaxTimeout - SystemClock::duration(1);
+  while (for_at_least > kMaxTimeoutMinusOne) {
+    if (OS_WaitCSemaTimed(&native_type_,
+                          static_cast<OS_TIME>(kMaxTimeoutMinusOne.count()))) {
+      return true;
+    }
+    for_at_least -= kMaxTimeoutMinusOne;
+  }
   return OS_WaitCSemaTimed(&native_type_,
-                           static_cast<OS_TIME>(timeout.count() + 1));
+                           static_cast<OS_TIME>(for_at_least.count() + 1));
 }
 
 }  // namespace pw::sync
diff --git a/pw_sync_embos/counting_semaphore.cc b/pw_sync_embos/counting_semaphore.cc
index edd4951..a2dd40e 100644
--- a/pw_sync_embos/counting_semaphore.cc
+++ b/pw_sync_embos/counting_semaphore.cc
@@ -17,7 +17,7 @@
 #include <algorithm>
 
 #include "RTOS.h"
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_chrono/system_clock.h"
 #include "pw_chrono_embos/system_clock_constants.h"
 #include "pw_interrupt/context.h"
@@ -40,33 +40,27 @@
   }
 }
 
-bool CountingSemaphore::try_acquire_for(SystemClock::duration timeout) {
-  // Enforce the pw::sync::CountingSemaphore IRQ contract.
+bool CountingSemaphore::try_acquire_for(SystemClock::duration for_at_least) {
   PW_DCHECK(!interrupt::InInterruptContext());
 
   // Use non-blocking try_acquire for negative and zero length durations.
-  if (timeout <= SystemClock::duration::zero()) {
+  if (for_at_least <= SystemClock::duration::zero()) {
     return try_acquire();
   }
 
-  // In case the timeout is too long for us to express through the native
-  // embOS API, we repeatedly wait with shorter durations. Note that on a tick
-  // based kernel we cannot tell how far along we are on the current tick, ergo
-  // we add one whole tick to the final duration. However, this also means that
-  // the loop must ensure that timeout + 1 is less than the max timeout.
+  // On a tick based kernel we cannot tell how far along we are on the current
+  // tick, ergo we add one whole tick to the final duration.
   constexpr SystemClock::duration kMaxTimeoutMinusOne =
       pw::chrono::embos::kMaxTimeout - SystemClock::duration(1);
-  while (timeout > kMaxTimeoutMinusOne) {
+  while (for_at_least > kMaxTimeoutMinusOne) {
     if (OS_WaitCSemaTimed(&native_type_,
                           static_cast<OS_TIME>(kMaxTimeoutMinusOne.count()))) {
       return true;
     }
-    timeout -= kMaxTimeoutMinusOne;
+    for_at_least -= kMaxTimeoutMinusOne;
   }
-  // On a tick based kernel we cannot tell how far along we are on the current
-  // tick, ergo we add one whole tick to the final duration.
   return OS_WaitCSemaTimed(&native_type_,
-                           static_cast<OS_TIME>(timeout.count() + 1));
+                           static_cast<OS_TIME>(for_at_least.count() + 1));
 }
 
 }  // namespace pw::sync
diff --git a/pw_sync_embos/docs.rst b/pw_sync_embos/docs.rst
index 2bc847d..19e1636 100644
--- a/pw_sync_embos/docs.rst
+++ b/pw_sync_embos/docs.rst
@@ -1,45 +1,8 @@
 .. _module-pw_sync_embos:
 
-=============
+-------------
 pw_sync_embos
-=============
-This is a set of backends for pw_sync based on embOS v4.
+-------------
+This is a set of backends for pw_sync based on embOS v4. It is not ready for
+use, and is under construction.
 
---------------------------------
-Critical Section Lock Primitives
---------------------------------
-
-Mutex & TimedMutex
-==================
-The embOS v4 backend for the Mutex and TimedMutex use ``OS_RSEMA`` as the
-underlying type. It is created using ``OS_CreateRSema`` as part of the
-constructors and cleaned up using ``OS_DeleteRSema`` in the destructors.
-
-InterruptSpinLock
-=================
-The embOS v4 backend for InterruptSpinLock is backed by a ``bool`` which permits
-these objects to detect accidental recursive locking.
-
-This object uses ``OS_IncDI`` and ``OS_DecRI`` to mask interrupts which enables
-the critical section. In addition, ``OS_SuspendAllTasks`` and
-``OS_ResumeAllSuspendedTasks`` are used to to prevent accidental thread context
-switches while the InterruptSpinLock is locked.
-
---------------------
-Signaling Primitives
---------------------
-
-ThreadNotification & TimedThreadNotification
-============================================
-The native embOS v4 implementation of its semaphores (``OS_CSEMA``) is very
-efficient, ergo we recommend using the binary semaphore backends for
-ThreadNotifications:
-- ``pw_sync:binary_semaphore_thread_notification_backend``
-- ``pw_sync:binary_semaphore_timed_thread_notification_backend``
-
-BinarySemaphore & CountingSemaphore
-===================================
-The embOS v4 backends for the BinarySemaphore and CountingSemaphore use
-``OS_CSEMA`` as the underlying type. It is created using ``OS_CreateCSema`` as
-part of the constructor and cleaned up using ``OS_DeleteCSema`` in the
-destructor.
diff --git a/pw_sync_embos/interrupt_spin_lock.cc b/pw_sync_embos/interrupt_spin_lock.cc
index b3c5213..9d17ba6 100644
--- a/pw_sync_embos/interrupt_spin_lock.cc
+++ b/pw_sync_embos/interrupt_spin_lock.cc
@@ -15,28 +15,31 @@
 #include "pw_sync/interrupt_spin_lock.h"
 
 #include "RTOS.h"
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 
 namespace pw::sync {
 
 void InterruptSpinLock::lock() {
-  // Mask interrupts.
   OS_IncDI();
-
-  // Disable task switching to ensure kernel APIs cannot switch to other tasks
-  // which could then end up deadlocking recursively on this same lock.
-  OS_SuspendAllTasks();
-
   // We can't deadlock here so crash instead.
-  PW_DCHECK(!native_type_.locked,
-            "Recursive InterruptSpinLock::lock() detected");
-  native_type_.locked = true;
+  PW_CHECK(!native_type_.locked.load(std::memory_order_relaxed),
+           "Recursive InterruptSpinLock::lock() detected");
+  native_type_.locked.store(true, std::memory_order_relaxed);
+}
+
+bool InterruptSpinLock::try_lock() {
+  OS_IncDI();
+  if (native_type_.locked.load(std::memory_order_relaxed)) {
+    OS_DecRI();  // Already locked, restore interrupts and bail out.
+    return false;
+  }
+  native_type_.locked.store(true, std::memory_order_relaxed);
+  return true;
 }
 
 void InterruptSpinLock::unlock() {
-  native_type_.locked = false;
-  OS_ResumeAllSuspendedTasks();  // Restore task switching.
-  OS_DecRI();                    // Restore interrupts.
+  native_type_.locked.store(false, std::memory_order_relaxed);
+  OS_DecRI();
 }
 
 }  // namespace pw::sync
diff --git a/pw_sync_embos/public/pw_sync_embos/binary_semaphore_inline.h b/pw_sync_embos/public/pw_sync_embos/binary_semaphore_inline.h
index 943b65e..d8abf72 100644
--- a/pw_sync_embos/public/pw_sync_embos/binary_semaphore_inline.h
+++ b/pw_sync_embos/public/pw_sync_embos/binary_semaphore_inline.h
@@ -14,7 +14,7 @@
 #pragma once
 
 #include "RTOS.h"
-#include "pw_assert/assert.h"
+#include "pw_assert/light.h"
 #include "pw_chrono/system_clock.h"
 #include "pw_chrono_embos/system_clock_constants.h"
 #include "pw_interrupt/context.h"
@@ -31,8 +31,7 @@
 inline void BinarySemaphore::release() { OS_SignalCSemaMax(&native_type_, 1); }
 
 inline void BinarySemaphore::acquire() {
-  // Enforce the pw::sync::BinarySemaphore IRQ contract.
-  PW_DASSERT(!interrupt::InInterruptContext());
+  PW_ASSERT(!interrupt::InInterruptContext());
   OS_WaitCSema(&native_type_);
 }
 
@@ -41,10 +40,10 @@
 }
 
 inline bool BinarySemaphore::try_acquire_until(
-    chrono::SystemClock::time_point deadline) {
+    chrono::SystemClock::time_point until_at_least) {
   // Note that if this deadline is in the future, it will get rounded up by
   // one whole tick due to how try_acquire_for is implemented.
-  return try_acquire_for(deadline - chrono::SystemClock::now());
+  return try_acquire_for(until_at_least - chrono::SystemClock::now());
 }
 
 inline BinarySemaphore::native_handle_type BinarySemaphore::native_handle() {
diff --git a/pw_sync_embos/public/pw_sync_embos/counting_semaphore_inline.h b/pw_sync_embos/public/pw_sync_embos/counting_semaphore_inline.h
index 8f29d4e..55ad150 100644
--- a/pw_sync_embos/public/pw_sync_embos/counting_semaphore_inline.h
+++ b/pw_sync_embos/public/pw_sync_embos/counting_semaphore_inline.h
@@ -14,7 +14,7 @@
 #pragma once
 
 #include "RTOS.h"
-#include "pw_assert/assert.h"
+#include "pw_assert/light.h"
 #include "pw_chrono/system_clock.h"
 #include "pw_chrono_embos/system_clock_constants.h"
 #include "pw_interrupt/context.h"
@@ -31,8 +31,7 @@
 }
 
 inline void CountingSemaphore::acquire() {
-  // Enforce the pw::sync::CountingSemaphore IRQ contract.
-  PW_DASSERT(!interrupt::InInterruptContext());
+  PW_ASSERT(!interrupt::InInterruptContext());
   OS_WaitCSema(&native_type_);
 }
 
@@ -41,10 +40,10 @@
 }
 
 inline bool CountingSemaphore::try_acquire_until(
-    chrono::SystemClock::time_point deadline) {
+    chrono::SystemClock::time_point until_at_least) {
   // Note that if this deadline is in the future, it will get rounded up by
   // one whole tick due to how try_acquire_for is implemented.
-  return try_acquire_for(deadline - chrono::SystemClock::now());
+  return try_acquire_for(until_at_least - chrono::SystemClock::now());
 }
 
 inline CountingSemaphore::native_handle_type
diff --git a/pw_sync_embos/public/pw_sync_embos/interrupt_spin_lock_inline.h b/pw_sync_embos/public/pw_sync_embos/interrupt_spin_lock_inline.h
index eddc549..0484b3c 100644
--- a/pw_sync_embos/public/pw_sync_embos/interrupt_spin_lock_inline.h
+++ b/pw_sync_embos/public/pw_sync_embos/interrupt_spin_lock_inline.h
@@ -18,18 +18,11 @@
 namespace pw::sync {
 
 constexpr InterruptSpinLock::InterruptSpinLock()
-    : native_type_{.locked = false} {}
+    : native_type_{.locked{false}} {}
 
 inline InterruptSpinLock::native_handle_type
 InterruptSpinLock::native_handle() {
   return native_type_;
 }
 
-inline bool InterruptSpinLock::try_lock() {
-  // This backend does not support SMP and on a uniprocesor we cannot actually
-  // fail to acquire the lock. Recursive locking is already detected by lock().
-  lock();
-  return true;
-}
-
 }  // namespace pw::sync
diff --git a/pw_sync_embos/public/pw_sync_embos/interrupt_spin_lock_native.h b/pw_sync_embos/public/pw_sync_embos/interrupt_spin_lock_native.h
index 548b67a..3c91f64 100644
--- a/pw_sync_embos/public/pw_sync_embos/interrupt_spin_lock_native.h
+++ b/pw_sync_embos/public/pw_sync_embos/interrupt_spin_lock_native.h
@@ -13,12 +13,14 @@
 // the License.
 #pragma once
 
+#include <atomic>
+
 #include "RTOS.h"
 
 namespace pw::sync::backend {
 
 struct NativeInterruptSpinLock {
-  bool locked;  // Used to detect recursion.
+  std::atomic<bool> locked;  // Used to detect recursion.
 };
 using NativeInterruptSpinLockHandle = NativeInterruptSpinLock&;
 
diff --git a/pw_sync_embos/public/pw_sync_embos/mutex_inline.h b/pw_sync_embos/public/pw_sync_embos/mutex_inline.h
index 78ad100..9f8c147 100644
--- a/pw_sync_embos/public/pw_sync_embos/mutex_inline.h
+++ b/pw_sync_embos/public/pw_sync_embos/mutex_inline.h
@@ -14,7 +14,7 @@
 #pragma once
 
 #include "RTOS.h"
-#include "pw_assert/assert.h"
+#include "pw_assert/light.h"
 #include "pw_interrupt/context.h"
 #include "pw_sync/mutex.h"
 
@@ -25,27 +25,18 @@
 inline Mutex::~Mutex() { OS_DeleteRSema(&native_type_); }
 
 inline void Mutex::lock() {
-  // Enforce the pw::sync::Mutex IRQ contract.
-  PW_DASSERT(!interrupt::InInterruptContext());
+  PW_ASSERT(!interrupt::InInterruptContext());
   const int lock_count = OS_Use(&native_type_);
-  PW_DASSERT(lock_count == 1);  // Recursive locking is not permitted.
+  PW_ASSERT(lock_count == 1);  // Recursive locking is not permitted.
 }
 
 inline bool Mutex::try_lock() {
-  // Enforce the pw::sync::Mutex IRQ contract.
-  PW_DASSERT(!interrupt::InInterruptContext());
-  if (OS_Request(&native_type_) == 0) {
-    return false;
-  }
-
-  // Recursive locking is not permitted.
-  PW_DASSERT(OS_GetSemaValue(&native_type_) == 1);
-  return true;
+  PW_ASSERT(!interrupt::InInterruptContext());
+  return OS_Request(&native_type_) != 0;
 }
 
 inline void Mutex::unlock() {
-  // Enforce the pw::sync::Mutex IRQ contract.
-  PW_DASSERT(!interrupt::InInterruptContext());
+  PW_ASSERT(!interrupt::InInterruptContext());
   OS_Unuse(&native_type_);
 }
 
diff --git a/pw_sync_embos/public/pw_sync_embos/timed_mutex_inline.h b/pw_sync_embos/public/pw_sync_embos/timed_mutex_inline.h
index d5ef4e7..2ea7bff 100644
--- a/pw_sync_embos/public/pw_sync_embos/timed_mutex_inline.h
+++ b/pw_sync_embos/public/pw_sync_embos/timed_mutex_inline.h
@@ -19,10 +19,10 @@
 namespace pw::sync {
 
 inline bool TimedMutex::try_lock_until(
-    chrono::SystemClock::time_point deadline) {
+    chrono::SystemClock::time_point until_at_least) {
   // Note that if this deadline is in the future, it will get rounded up by
   // one whole tick due to how try_lock_for is implemented.
-  return try_lock_for(deadline - chrono::SystemClock::now());
+  return try_lock_for(until_at_least - chrono::SystemClock::now());
 }
 
 }  // namespace pw::sync
diff --git a/pw_sync_embos/timed_mutex.cc b/pw_sync_embos/timed_mutex.cc
index 5bacc10..c08bef4 100644
--- a/pw_sync_embos/timed_mutex.cc
+++ b/pw_sync_embos/timed_mutex.cc
@@ -17,7 +17,7 @@
 #include <algorithm>
 
 #include "RTOS.h"
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_chrono/system_clock.h"
 #include "pw_chrono_embos/system_clock_constants.h"
 #include "pw_interrupt/context.h"
@@ -26,36 +26,30 @@
 
 namespace pw::sync {
 
-bool TimedMutex::try_lock_for(SystemClock::duration timeout) {
-  // Enforce the pw::sync::TimedMutex IRQ contract.
+bool TimedMutex::try_lock_for(SystemClock::duration for_at_least) {
   PW_DCHECK(!interrupt::InInterruptContext());
 
   // Use non-blocking try_lock for negative and zero length durations.
-  if (timeout <= SystemClock::duration::zero()) {
+  if (for_at_least <= SystemClock::duration::zero()) {
     return try_lock();
   }
 
-  // In case the timeout is too long for us to express through the native
-  // embOS API, we repeatedly wait with shorter durations. Note that on a tick
-  // based kernel we cannot tell how far along we are on the current tick, ergo
-  // we add one whole tick to the final duration. However, this also means that
-  // the loop must ensure that timeout + 1 is less than the max timeout.
+  // On a tick based kernel we cannot tell how far along we are on the current
+  // tick, ergo we add one whole tick to the final duration.
   constexpr SystemClock::duration kMaxTimeoutMinusOne =
       pw::chrono::embos::kMaxTimeout - SystemClock::duration(1);
-  while (timeout > kMaxTimeoutMinusOne) {
+  while (for_at_least > kMaxTimeoutMinusOne) {
     const int lock_count = OS_UseTimed(
         &native_handle(), static_cast<OS_TIME>(kMaxTimeoutMinusOne.count()));
     if (lock_count != 0) {
-      PW_DCHECK_UINT_EQ(1, lock_count, "Recursive locking is not permitted");
+      PW_CHECK_UINT_EQ(1, lock_count, "Recursive locking is not permitted");
       return true;
     }
-    timeout -= kMaxTimeoutMinusOne;
+    for_at_least -= kMaxTimeoutMinusOne;
   }
-  // On a tick based kernel we cannot tell how far along we are on the current
-  // tick, ergo we add one whole tick to the final duration.
-  const int lock_count =
-      OS_UseTimed(&native_handle(), static_cast<OS_TIME>(timeout.count() + 1));
-  PW_DCHECK_UINT_LE(1, lock_count, "Recursive locking is not permitted");
+  const int lock_count = OS_UseTimed(
+      &native_handle(), static_cast<OS_TIME>(for_at_least.count() + 1));
+  PW_CHECK_UINT_LE(1, lock_count, "Recursive locking is not permitted");
   return lock_count == 1;
 }
 
diff --git a/pw_sync_freertos/BUILD b/pw_sync_freertos/BUILD
new file mode 100644
index 0000000..7ad2759
--- /dev/null
+++ b/pw_sync_freertos/BUILD
@@ -0,0 +1,172 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "binary_semaphore_headers",
+    hdrs = [
+        "public/pw_sync_freertos/binary_semaphore_inline.h",
+        "public/pw_sync_freertos/binary_semaphore_native.h",
+        "public_overrides/pw_sync_backend/binary_semaphore_inline.h",
+        "public_overrides/pw_sync_backend/binary_semaphore_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        # TODO: This should depend on FreeRTOS but our third parties currently
+        # do not have Bazel support.
+        "//pw_chrono:system_clock",
+        "//pw_chrono_freertos:system_clock_headers",
+    ],
+)
+
+pw_cc_library(
+    name = "binary_semaphore",
+    srcs = [
+        "binary_semaphore.cc",
+    ],
+    deps = [
+        ":binary_semaphore_headers",
+        "//pw_interrupt:context",
+        "//pw_sync:binary_semaphore_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "counting_semaphore_headers",
+    hdrs = [
+        "public/pw_sync_freertos/counting_semaphore_inline.h",
+        "public/pw_sync_freertos/counting_semaphore_native.h",
+        "public_overrides/pw_sync_backend/counting_semaphore_inline.h",
+        "public_overrides/pw_sync_backend/counting_semaphore_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        # TODO: This should depend on FreeRTOS but our third parties currently
+        # do not have Bazel support.
+        "//pw_chrono:system_clock",
+        "//pw_chrono_freertos:system_clock_headers",
+    ],
+)
+
+pw_cc_library(
+    name = "counting_semaphore",
+    srcs = [
+        "counting_semaphore.cc",
+    ],
+    deps = [
+        ":counting_semaphore_headers",
+        "//pw_interrupt:context",
+        "//pw_sync:counting_semaphore_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "mutex_headers",
+    hdrs = [
+        "public/pw_sync_freertos/mutex_inline.h",
+        "public/pw_sync_freertos/mutex_native.h",
+        "public_overrides/pw_sync_backend/mutex_inline.h",
+        "public_overrides/pw_sync_backend/mutex_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        # TODO: This should depend on FreeRTOS but our third parties currently
+        # do not have Bazel support.
+        "//pw_sync:mutex_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "mutex",
+    deps = [
+        ":mutex_headers",
+        "//pw_sync:mutex_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "timed_mutex_headers",
+    hdrs = [
+        "public/pw_sync_freertos/timed_mutex_inline.h",
+        "public_overrides/pw_sync_backend/timed_mutex_inline.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        # TODO: This should depend on FreeRTOS but our third parties currently
+        # do not have Bazel support.
+        "//pw_chrono:system_clock",
+        "//pw_chrono_freertos:system_clock_headers",
+        "//pw_sync:timed_mutex_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "timed_mutex",
+    srcs = [
+        "timed_mutex.cc",
+    ],
+    deps = [
+        ":timed_mutex_headers",
+        "//pw_interrupt:context",
+        "//pw_sync:timed_mutex_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "interrupt_spin_lock_headers",
+    hdrs = [
+        "public/pw_sync_freertos/interrupt_spin_lock_inline.h",
+        "public/pw_sync_freertos/interrupt_spin_lock_native.h",
+        "public_overrides/pw_sync_backend/interrupt_spin_lock_inline.h",
+        "public_overrides/pw_sync_backend/interrupt_spin_lock_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    # TODO: This should depend on FreeRTOS but our third parties currently
+    # do not have Bazel support.
+)
+
+pw_cc_library(
+    name = "interrupt_spin_lock",
+    srcs = [
+        "interrupt_spin_lock.cc",
+    ],
+    deps = [
+        ":interrupt_spin_lock_headers",
+        "//pw_interrupt:context",
+        "//pw_sync:interrupt_spin_lock_facade",
+    ],
+)
diff --git a/pw_sync_freertos/BUILD.bazel b/pw_sync_freertos/BUILD.bazel
deleted file mode 100644
index aca4016..0000000
--- a/pw_sync_freertos/BUILD.bazel
+++ /dev/null
@@ -1,286 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "binary_semaphore_headers",
-    hdrs = [
-        "public/pw_sync_freertos/binary_semaphore_inline.h",
-        "public/pw_sync_freertos/binary_semaphore_native.h",
-        "public_overrides/pw_sync_backend/binary_semaphore_inline.h",
-        "public_overrides/pw_sync_backend/binary_semaphore_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:freertos",
-    ],
-    deps = [
-        # TODO(pwbug/317): This should depend on FreeRTOS but our third parties currently
-        # do not have Bazel support.
-        "//pw_assert",
-        "//pw_chrono:system_clock",
-        "//pw_chrono_freertos:system_clock_headers",
-    ],
-)
-
-pw_cc_library(
-    name = "binary_semaphore",
-    srcs = [
-        "binary_semaphore.cc",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:freertos",
-    ],
-    deps = [
-        ":binary_semaphore_headers",
-        "//pw_assert",
-        "//pw_interrupt:context",
-        "//pw_sync:binary_semaphore_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "counting_semaphore_headers",
-    hdrs = [
-        "public/pw_sync_freertos/counting_semaphore_inline.h",
-        "public/pw_sync_freertos/counting_semaphore_native.h",
-        "public_overrides/pw_sync_backend/counting_semaphore_inline.h",
-        "public_overrides/pw_sync_backend/counting_semaphore_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:freertos",
-    ],
-    deps = [
-        # TODO(pwbug/317): This should depend on FreeRTOS but our third parties currently
-        # do not have Bazel support.
-        "//pw_assert",
-        "//pw_sync:counting_semaphore_facade",
-        "//pw_chrono:system_clock",
-        "//pw_chrono_freertos:system_clock_headers",
-    ],
-)
-
-pw_cc_library(
-    name = "counting_semaphore",
-    srcs = [
-        "counting_semaphore.cc",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:freertos",
-    ],
-    deps = [
-        ":counting_semaphore_headers",
-        "//pw_assert",
-        "//pw_interrupt:context",
-        "//pw_sync:counting_semaphore_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "mutex_headers",
-    hdrs = [
-        "public/pw_sync_freertos/mutex_inline.h",
-        "public/pw_sync_freertos/mutex_native.h",
-        "public_overrides/pw_sync_backend/mutex_inline.h",
-        "public_overrides/pw_sync_backend/mutex_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:freertos",
-    ],
-    deps = [
-        # TODO(pwbug/317): This should depend on FreeRTOS but our third parties currently
-        # do not have Bazel support.
-        "//pw_assert",
-    ],
-)
-
-pw_cc_library(
-    name = "mutex",
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:freertos",
-    ],
-    deps = [
-        ":mutex_headers",
-        "//pw_sync:mutex_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "thread_notification_headers",
-    hdrs = [
-        "public/pw_sync_freertos/config.h",
-        "public/pw_sync_freertos/thread_notification_inline.h",
-        "public/pw_sync_freertos/thread_notification_native.h",
-        "public_overrides/thread_notification/pw_sync_backend/thread_notification_inline.h",
-        "public_overrides/thread_notification/pw_sync_backend/thread_notification_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides/thread_notification",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:freertos",
-    ],
-    deps = [
-        # TODO(pwbug/317): This should depend on FreeRTOS but our third parties
-        # currently do not have Bazel support.
-        "//pw_interrupt:context",
-    ],
-)
-
-pw_cc_library(
-    name = "thread_notification",
-    srcs = [
-        "thread_notification.cc",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:freertos",
-    ],
-    deps = [
-        ":thread_notification_headers",
-        "//pw_assert",
-        "//pw_interrupt:context",
-        "//pw_sync:thread_notification_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "timed_thread_notification_headers",
-    hdrs = [
-        "public/pw_sync_freertos/timed_thread_notification_inline.h",
-        "public_overrides/timed_thread_notification/pw_sync_backend/timed_thread_notification_inline.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides/timed_thread_notification",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:freertos",
-    ],
-    deps = [
-        # TODO(pwbug/317): This should depend on FreeRTOS but our third parties
-        # currently do not have Bazel support.
-        "//pw_chrono:system_clock",
-        "//pw_sync:timed_thread_notification_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "timed_thread_notification",
-    srcs = [
-        "timed_thread_notification.cc",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:freertos",
-    ],
-    deps = [
-        ":timed_thread_notification_headers",
-        "//pw_assert",
-        "//pw_chrono_freertos:system_clock_headers",
-        "//pw_interrupt:context",
-        "//pw_sync:timed_thread_notification_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "timed_mutex_headers",
-    hdrs = [
-        "public/pw_sync_freertos/timed_mutex_inline.h",
-        "public_overrides/pw_sync_backend/timed_mutex_inline.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:freertos",
-    ],
-    deps = [
-        # TODO(pwbug/317): This should depend on FreeRTOS but our third parties
-        # currently do not have Bazel support.
-        "//pw_chrono:system_clock",
-        "//pw_sync:timed_mutex_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "timed_mutex",
-    srcs = [
-        "timed_mutex.cc",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:freertos",
-    ],
-    deps = [
-        ":timed_mutex_headers",
-        "//pw_assert",
-        "//pw_chrono_freertos:system_clock_headers",
-        "//pw_interrupt:context",
-        "//pw_sync:timed_mutex_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "interrupt_spin_lock_headers",
-    hdrs = [
-        "public/pw_sync_freertos/interrupt_spin_lock_inline.h",
-        "public/pw_sync_freertos/interrupt_spin_lock_native.h",
-        "public_overrides/pw_sync_backend/interrupt_spin_lock_inline.h",
-        "public_overrides/pw_sync_backend/interrupt_spin_lock_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:freertos",
-    ],
-    # TODO(pwbug/317): This should depend on FreeRTOS but our third parties
-    # currently do not have Bazel support.
-)
-
-pw_cc_library(
-    name = "interrupt_spin_lock",
-    srcs = [
-        "interrupt_spin_lock.cc",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:freertos",
-    ],
-    deps = [
-        ":interrupt_spin_lock_headers",
-        "//pw_assert",
-        "//pw_interrupt:context",
-        "//pw_sync:interrupt_spin_lock_facade",
-    ],
-)
diff --git a/pw_sync_freertos/BUILD.gn b/pw_sync_freertos/BUILD.gn
index c560d90..425ef4a 100644
--- a/pw_sync_freertos/BUILD.gn
+++ b/pw_sync_freertos/BUILD.gn
@@ -14,19 +14,10 @@
 
 import("//build_overrides/pigweed.gni")
 
-import("$dir_pw_build/error.gni")
-import("$dir_pw_build/module_config.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_chrono/backend.gni")
 import("$dir_pw_docgen/docs.gni")
 
-declare_args() {
-  # The build target that overrides the default configuration options for this
-  # module. This should point to a source set that provides defines through a
-  # public config (which may -include a file or add defines directly).
-  pw_sync_freertos_CONFIG = pw_build_DEFAULT_MODULE_CONFIG
-}
-
 config("public_include_path") {
   include_dirs = [ "public" ]
   visibility = [ ":*" ]
@@ -37,24 +28,6 @@
   visibility = [ ":*" ]
 }
 
-pw_source_set("config") {
-  public = [ "public/pw_sync_freertos/config.h" ]
-  public_configs = [ ":public_include_path" ]
-  public_deps = [
-    "$dir_pw_third_party/freertos",
-    pw_sync_freertos_CONFIG,
-  ]
-}
-
-pw_build_assert("check_system_clock_backend") {
-  condition =
-      pw_chrono_SYSTEM_CLOCK_BACKEND == "" ||
-      pw_chrono_SYSTEM_CLOCK_BACKEND == "$dir_pw_chrono_freertos:system_clock"
-  message = "The FreeRTOS pw_sync backends only work with the FreeRTOS " +
-            "pw::chrono::SystemClock backend."
-  visibility = [ ":*" ]
-}
-
 # This target provides the backend for pw::sync::BinarySemaphore.
 pw_source_set("binary_semaphore") {
   public_configs = [
@@ -75,10 +48,12 @@
     "$dir_pw_third_party/freertos",
   ]
   sources = [ "binary_semaphore.cc" ]
-  deps = [
-    ":check_system_clock_backend",
-    "$dir_pw_sync:binary_semaphore.facade",
-  ]
+  deps = [ "$dir_pw_sync:binary_semaphore.facade" ]
+  assert(pw_chrono_SYSTEM_CLOCK_BACKEND == "" ||
+             pw_chrono_SYSTEM_CLOCK_BACKEND ==
+                 "$dir_pw_chrono_freertos:system_clock",
+         "The FreeRTOS pw::sync::BinarySemaphore backend only works with the " +
+             "FreeRTOS pw::chrono::SystemClock backend.")
 }
 
 # This target provides the backend for pw::sync::CountingSemaphore.
@@ -101,10 +76,12 @@
     "$dir_pw_third_party/freertos",
   ]
   sources = [ "counting_semaphore.cc" ]
-  deps = [
-    ":check_system_clock_backend",
-    "$dir_pw_sync:counting_semaphore.facade",
-  ]
+  deps = [ "$dir_pw_sync:counting_semaphore.facade" ]
+  assert(pw_chrono_SYSTEM_CLOCK_BACKEND == "" ||
+             pw_chrono_SYSTEM_CLOCK_BACKEND ==
+                 "$dir_pw_chrono_freertos:system_clock",
+         "The FreeRTOS pw::sync::CountingSemaphore backend only works with " +
+             "the FreeRTOS pw::chrono::SystemClock backend.")
 }
 
 # This target provides the backend for pw::sync::Mutex.
@@ -143,73 +120,16 @@
   ]
   sources = [ "timed_mutex.cc" ]
   deps = [
-    ":check_system_clock_backend",
     "$dir_pw_assert",
     "$dir_pw_chrono_freertos:system_clock",
     "$dir_pw_interrupt:context",
     "$dir_pw_third_party/freertos",
   ]
-}
-
-config("public_overrides_thread_notification_include_path") {
-  include_dirs = [ "public_overrides/thread_notification" ]
-  visibility = [ ":thread_notification" ]
-}
-
-# This target provides the backend for pw::sync::ThreadNotification based on
-# task notifications.
-pw_source_set("thread_notification") {
-  public_configs = [
-    ":public_include_path",
-    ":public_overrides_thread_notification_include_path",
-  ]
-  public = [
-    "public/pw_sync_freertos/thread_notification_inline.h",
-    "public/pw_sync_freertos/thread_notification_native.h",
-    "public_overrides/thread_notification/pw_sync_backend/thread_notification_inline.h",
-    "public_overrides/thread_notification/pw_sync_backend/thread_notification_native.h",
-  ]
-  public_deps = [
-    "$dir_pw_interrupt:context",
-    "$dir_pw_sync:thread_notification.facade",
-    "$dir_pw_third_party/freertos",
-  ]
-  sources = [ "thread_notification.cc" ]
-  deps = [
-    ":config",
-    dir_pw_assert,
-  ]
-}
-
-config("public_overrides_timed_thread_notification_include_path") {
-  include_dirs = [ "public_overrides/timed_thread_notification" ]
-  visibility = [ ":timed_thread_notification" ]
-}
-
-# This target provides the backend for pw::sync::TimedThreadNotification based
-# on task notifications.
-pw_source_set("timed_thread_notification") {
-  public_configs = [
-    ":public_include_path",
-    ":public_overrides_timed_thread_notification_include_path",
-  ]
-  public = [
-    "public/pw_sync_freertos/timed_thread_notification_inline.h",
-    "public_overrides/timed_thread_notification/pw_sync_backend/timed_thread_notification_inline.h",
-  ]
-  public_deps = [
-    "$dir_pw_chrono:system_clock",
-    "$dir_pw_sync:timed_thread_notification.facade",
-  ]
-  sources = [ "timed_thread_notification.cc" ]
-  deps = [
-    ":check_system_clock_backend",
-    ":config",
-    "$dir_pw_assert",
-    "$dir_pw_chrono_freertos:system_clock",
-    "$dir_pw_interrupt:context",
-    "$dir_pw_third_party/freertos",
-  ]
+  assert(pw_chrono_SYSTEM_CLOCK_BACKEND == "" ||
+             pw_chrono_SYSTEM_CLOCK_BACKEND ==
+                 "$dir_pw_chrono_freertos:system_clock",
+         "The FreeRTOS pw::sync::Mutex backend only works with the FreeRTOS " +
+             "pw::chrono::SystemClock backend.")
 }
 
 # This target provides the backend for pw::sync::InterruptSpinLock.
@@ -230,6 +150,7 @@
     "$dir_pw_assert",
     "$dir_pw_interrupt:context",
     "$dir_pw_sync:interrupt_spin_lock.facade",
+    "$dir_pw_third_party/freertos",
   ]
 }
 
diff --git a/pw_sync_freertos/CMakeLists.txt b/pw_sync_freertos/CMakeLists.txt
deleted file mode 100644
index 6139c39..0000000
--- a/pw_sync_freertos/CMakeLists.txt
+++ /dev/null
@@ -1,177 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-
-pw_add_module_config(pw_sync_freertos_CONFIG)
-
-pw_add_module_library(pw_sync_freertos.config
-  HEADERS
-    public/pw_sync_freertos/config.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_third_party.freertos
-    ${pw_sync_freertos_CONFIG}
-)
-
-# TODO(ewout): Add system_clock backend compatibility check like in GN.
-
-# This target provides the backend for pw::sync::BinarySemaphore.
-pw_add_module_library(pw_sync_freertos.binary_semaphore
-  IMPLEMENTS_FACADES
-    pw_sync.binary_semaphore
-  HEADERS
-    public/pw_sync_freertos/binary_semaphore_inline.h
-    public/pw_sync_freertos/binary_semaphore_native.h
-    public_overrides/pw_sync_backend/binary_semaphore_inline.h
-    public_overrides/pw_sync_backend/binary_semaphore_native.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides
-  PUBLIC_DEPS
-    pw_assert
-    pw_chrono.system_clock
-    pw_chrono_freertos.system_clock
-    pw_interrupt.context
-    pw_third_party.freertos
-  SOURCES
-    binary_semaphore.cc
-)
-
-# This target provides the backend for pw::sync::CountingSemaphore.
-pw_add_module_library(pw_sync_freertos.counting_semaphore
-  IMPLEMENTS_FACADES
-    pw_sync.counting_semaphore
-  HEADERS
-    public/pw_sync_freertos/counting_semaphore_inline.h
-    public/pw_sync_freertos/counting_semaphore_native.h
-    public_overrides/pw_sync_backend/counting_semaphore_inline.h
-    public_overrides/pw_sync_backend/counting_semaphore_native.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides
-  PUBLIC_DEPS
-    pw_assert
-    pw_chrono.system_clock
-    pw_chrono_freertos.system_clock
-    pw_interrupt.context
-    pw_third_party.freertos
-  SOURCES
-    counting_semaphore.cc
-)
-
-# This target provides the backend for pw::sync::Mutex.
-pw_add_module_library(pw_sync_freertos.mutex
-  IMPLEMENTS_FACADES
-    pw_sync.mutex
-  HEADERS
-    public/pw_sync_freertos/mutex_inline.h
-    public/pw_sync_freertos/mutex_native.h
-    public_overrides/pw_sync_backend/mutex_inline.h
-    public_overrides/pw_sync_backend/mutex_native.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides
-  PUBLIC_DEPS
-    pw_assert
-    pw_interrupt.context
-    pw_third_party.freertos
-)
-
-# This target provides the backend for pw::sync::TimedMutex.
-pw_add_module_library(pw_sync_freertos.timed_mutex
-  IMPLEMENTS_FACADES
-    pw_sync.timed_mutex
-  HEADERS
-    public/pw_sync_freertos/timed_mutex_inline.h
-    public_overrides/pw_sync_backend/timed_mutex_inline.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides
-  PUBLIC_DEPS
-    pw_chrono.system_clock
-  SOURCES
-    timed_mutex.cc
-  PRIVATE_DEPS
-    pw_assert
-    pw_chrono_freertos.system_clock
-    pw_interrupt.context
-    pw_third_party.freertos
-)
-
-# This target provides the backend for pw::sync::ThreadNotification.
-pw_add_module_library(pw_sync_freertos.thread_notification
-  IMPLEMENTS_FACADES
-    pw_sync.thread_notification
-  HEADERS
-    public/pw_sync_freertos/thread_notification_inline.h
-    public/pw_sync_freertos/thread_notification_native.h
-    public_overrides/thread_notification/pw_sync_backend/thread_notification_inline.h
-    public_overrides/thread_notification/pw_sync_backend/thread_notification_native.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides/thread_notification
-  PUBLIC_DEPS
-    pw_interrupt.context
-    pw_third_party.freertos
-  SOURCES
-    thread_notification.cc
-  PRIVATE_DEPS
-    pw_assert
-    pw_sync_freertos.config
-)
-
-# This target provides the backend for pw::sync::TimedThreadNotification.
-pw_add_module_library(pw_sync_freertos.timed_thread_notification
-  IMPLEMENTS_FACADES
-    pw_sync.timed_thread_notification
-  HEADERS
-    public/pw_sync_freertos/timed_thread_notification_inline.h
-    public_overrides/timed_thread_notification/pw_sync_backend/timed_thread_notification_inline.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides/timed_thread_notification
-  PUBLIC_DEPS
-    pw_chrono.system_clock
-  SOURCES
-    timed_thread_notification.cc
-  PRIVATE_DEPS
-    pw_sync_freertos.config
-    pw_assert
-    pw_chrono_freertos.system_clock
-    pw_interrupt.context
-    pw_third_party.freertos
-)
-
-# This target provides the backend for pw::sync::InterruptSpinLock.
-pw_add_module_library(pw_sync_freertos.interrupt_spin_lock
-  IMPLEMENTS_FACADES
-    pw_sync.interrupt_spin_lock
-  HEADERS
-    public/pw_sync_freertos/interrupt_spin_lock_inline.h
-    public/pw_sync_freertos/interrupt_spin_lock_native.h
-    public_overrides/pw_sync_backend/interrupt_spin_lock_inline.h
-    public_overrides/pw_sync_backend/interrupt_spin_lock_native.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides
-  PUBLIC_DEPS
-    pw_third_party.freertos
-  SOURCES
-    interrupt_spin_lock.cc
-  PRIVATE_DEPS
-    pw_assert
-    pw_interrupt.context
-)
diff --git a/pw_sync_freertos/OWNERS b/pw_sync_freertos/OWNERS
deleted file mode 100644
index e9a6c58..0000000
--- a/pw_sync_freertos/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-ewout@google.com
-hepler@google.com
diff --git a/pw_sync_freertos/binary_semaphore.cc b/pw_sync_freertos/binary_semaphore.cc
index 9b27d5b..ef32b83 100644
--- a/pw_sync_freertos/binary_semaphore.cc
+++ b/pw_sync_freertos/binary_semaphore.cc
@@ -17,7 +17,7 @@
 #include <algorithm>
 
 #include "FreeRTOS.h"
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_chrono/system_clock.h"
 #include "pw_chrono_freertos/system_clock_constants.h"
 #include "pw_interrupt/context.h"
@@ -33,34 +33,29 @@
 
 }  // namespace
 
-bool BinarySemaphore::try_acquire_for(SystemClock::duration timeout) {
-  // Enforce the pw::sync::BinarySemaphore IRQ contract.
+bool BinarySemaphore::try_acquire_for(SystemClock::duration for_at_least) {
   PW_DCHECK(!interrupt::InInterruptContext());
 
   // Use non-blocking try_acquire for negative and zero length durations.
-  if (timeout <= SystemClock::duration::zero()) {
+  if (for_at_least <= SystemClock::duration::zero()) {
     return try_acquire();
   }
 
-  // In case the timeout is too long for us to express through the native
-  // FreeRTOS API, we repeatedly wait with shorter durations. Note that on a
-  // tick based kernel we cannot tell how far along we are on the current tick,
-  // ergo we add one whole tick to the final duration. However, this also means
-  // that the loop must ensure that timeout + 1 is less than the max timeout.
+  // On a tick based kernel we cannot tell how far along we are on the current
+  // tick, ergo we add one whole tick to the final duration.
   constexpr SystemClock::duration kMaxTimeoutMinusOne =
       pw::chrono::freertos::kMaxTimeout - SystemClock::duration(1);
-  while (timeout > kMaxTimeoutMinusOne) {
-    if (xSemaphoreTake(reinterpret_cast<SemaphoreHandle_t>(&native_type_),
+  while (for_at_least > kMaxTimeoutMinusOne) {
+    if (xSemaphoreTake(&native_type_,
                        static_cast<TickType_t>(kMaxTimeoutMinusOne.count())) ==
         pdTRUE) {
       return true;
     }
-    timeout -= kMaxTimeoutMinusOne;
+    for_at_least -= kMaxTimeoutMinusOne;
   }
-  // On a tick based kernel we cannot tell how far along we are on the current
-  // tick, ergo we add one whole tick to the final duration.
-  return xSemaphoreTake(reinterpret_cast<SemaphoreHandle_t>(&native_type_),
-                        static_cast<TickType_t>(timeout.count() + 1)) == pdTRUE;
+  return xSemaphoreTake(&native_type_,
+                        static_cast<TickType_t>(for_at_least.count() + 1)) ==
+         pdTRUE;
 }
 
 }  // namespace pw::sync
diff --git a/pw_sync_freertos/counting_semaphore.cc b/pw_sync_freertos/counting_semaphore.cc
index 8845cb0..3b57397 100644
--- a/pw_sync_freertos/counting_semaphore.cc
+++ b/pw_sync_freertos/counting_semaphore.cc
@@ -17,7 +17,7 @@
 #include <algorithm>
 
 #include "FreeRTOS.h"
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_chrono/system_clock.h"
 #include "pw_chrono_freertos/system_clock_constants.h"
 #include "pw_interrupt/context.h"
@@ -40,49 +40,42 @@
   if (interrupt::InInterruptContext()) {
     for (; update > 0; --update) {
       BaseType_t woke_higher_task = pdFALSE;
-      const BaseType_t result = xSemaphoreGiveFromISR(
-          reinterpret_cast<SemaphoreHandle_t>(&native_type_),
-          &woke_higher_task);
+      const BaseType_t result =
+          xSemaphoreGiveFromISR(&native_type_, &woke_higher_task);
       PW_DCHECK_UINT_EQ(result, pdTRUE, "Overflowed counting semaphore.");
       portYIELD_FROM_ISR(woke_higher_task);
     }
   } else {  // Task context
     for (; update > 0; --update) {
-      const BaseType_t result =
-          xSemaphoreGive(reinterpret_cast<SemaphoreHandle_t>(&native_type_));
+      const BaseType_t result = xSemaphoreGive(&native_type_);
       PW_DCHECK_UINT_EQ(result, pdTRUE, "Overflowed counting semaphore.");
     }
   }
 }
 
-bool CountingSemaphore::try_acquire_for(SystemClock::duration timeout) {
-  // Enforce the pw::sync::CountingSemaphore IRQ contract.
+bool CountingSemaphore::try_acquire_for(SystemClock::duration for_at_least) {
   PW_DCHECK(!interrupt::InInterruptContext());
 
   // Use non-blocking try_acquire for negative and zero length durations.
-  if (timeout <= SystemClock::duration::zero()) {
+  if (for_at_least <= SystemClock::duration::zero()) {
     return try_acquire();
   }
 
-  // In case the timeout is too long for us to express through the native
-  // FreeRTOS API, we repeatedly wait with shorter durations. Note that on a
-  // tick based kernel we cannot tell how far along we are on the current tick,
-  // ergo we add one whole tick to the final duration. However, this also means
-  // that the loop must ensure that timeout + 1 is less than the max timeout.
+  // On a tick based kernel we cannot tell how far along we are on the current
+  // tick, ergo we add one whole tick to the final duration.
   constexpr SystemClock::duration kMaxTimeoutMinusOne =
       pw::chrono::freertos::kMaxTimeout - SystemClock::duration(1);
-  while (timeout > kMaxTimeoutMinusOne) {
-    if (xSemaphoreTake(reinterpret_cast<SemaphoreHandle_t>(&native_type_),
+  while (for_at_least > kMaxTimeoutMinusOne) {
+    if (xSemaphoreTake(&native_type_,
                        static_cast<TickType_t>(kMaxTimeoutMinusOne.count())) ==
         pdTRUE) {
       return true;
     }
-    timeout -= kMaxTimeoutMinusOne;
+    for_at_least -= kMaxTimeoutMinusOne;
   }
-  // On a tick based kernel we cannot tell how far along we are on the current
-  // tick, ergo we add one whole tick to the final duration.
-  return xSemaphoreTake(reinterpret_cast<SemaphoreHandle_t>(&native_type_),
-                        static_cast<TickType_t>(timeout.count() + 1)) == pdTRUE;
+  return xSemaphoreTake(&native_type_,
+                        static_cast<TickType_t>(for_at_least.count() + 1)) ==
+         pdTRUE;
 }
 
 }  // namespace pw::sync
diff --git a/pw_sync_freertos/docs.rst b/pw_sync_freertos/docs.rst
index a4fd591..0053f02 100644
--- a/pw_sync_freertos/docs.rst
+++ b/pw_sync_freertos/docs.rst
@@ -1,150 +1,8 @@
 .. _module-pw_sync_freertos:
 
-================
+----------------
 pw_sync_freertos
-================
-This is a set of backends for pw_sync based on FreeRTOS.
-
---------------------------------
-Critical Section Lock Primitives
---------------------------------
-
-Mutex & TimedMutex
-==================
-The FreeRTOS backend for the Mutex and TimedMutex use ``StaticSemaphore_t`` as
-the underlying type. It is created using ``xSemaphoreCreateMutexStatic`` as part
-of the constructors and cleaned up using ``vSemaphoreDelete`` in the
-destructors.
-
-.. Note::
-  Static allocation support is required in your FreeRTOS configuration, i.e.
-  ``configSUPPORT_STATIC_ALLOCATION == 1``.
-
-InterruptSpinLock
-=================
-The FreeRTOS backend for InterruptSpinLock is backed by ``UBaseType_t`` and a
-``bool`` which permits these objects to stash the saved interrupt mask and to
-detect accidental recursive locking.
-
-This object uses ``taskENTER_CRITICAL_FROM_ISR`` and
-``taskEXIT_CRITICAL_FROM_ISR`` from interrupt contexts and
-``taskENTER_CRITICAL`` and ``taskEXIT_CRITICAL`` from other contexts.
-
---------------------
-Signaling Primitives
---------------------
-
-ThreadNotification & TimedThreadNotification
-============================================
-An optimized FreeRTOS backend for the ThreadNotification and
-TimedThreadNotification is provided using Task Notifications. It is backed by a
-``TaskHandle_t`` and a ``bool`` which permits these objects to track the
-notification value outside of the task's TCB (AKA FreeRTOS Task Notification
-State and Value).
-
-.. Warning::
-  By default this backend uses the task notification at index 0, just like
-  FreeRTOS Stream and Message Buffers. If you want to maintain the state of a
-  task notification across blocking acquiring calls to ThreadNotifications, then
-  you must do one of the following:
-
-  1. Adjust ``PW_SYNC_FREERTOS_CONFIG_THREAD_NOTIFICATION_INDEX`` to an index
-     which does not collide with existing incompatible use.
-  2. Migrate your existing use of task notifications away from index 0.
-  3. Do not use this optimized backend and instead use the binary semaphore
-     backends for ThreadNotifications
-     (``pw_sync:binary_semaphore_thread_notification_backend``).
-
-  You are using any of the following Task Notification APIs, it means you are
-  using notification indices:
-
-  - ``xTaskNotify`` / ``xTaskNotifyIndexed``
-  - ``xTaskNotifyFromISR`` / ``xTaskNotifyIndexedFromISR``
-  - ``xTaskNotifyGive`` / ``xTaskNotifyGiveIndexed``
-  - ``xTaskNotifyGiveFromISR`` / ``xTaskNotifyGiveIndexedFromISR``
-  - ``xTaskNotifyAndQuery`` / ``xTaskNotifyAndQueryIndexed``
-  - ``xTaskNotifyAndQueryFromISR`` / ``xTaskNotifyAndQueryIndexedFromISR``
-  - ``ulTaskNotifyTake`` / ``ulTaskNotifyTakeIndexed``
-  - ``xTaskNotifyWait`` / ``xTaskNotifyWaitIndexed``
-  - ``xTaskNotifyStateClear`` / ``xTaskNotifyStateClearIndexed``
-  - ``ulTaskNotifyValueClear`` / ``ulTaskNotifyValueClearIndexed``
-
-  APIs without ``Indexed`` in the name use index 0 implicitly.
-
-  Prior to FreeRTOS V10.4.0 each task had a single "notification index", and all
-  task notification API functions operated on that implicit index of 0.
-
-This backend is compatible with sharing the notification index
-with native FreeRTOS
-`Stream and Message Buffers <https://www.freertos.org/RTOS-task-notifications.html>`_
-at index 0.
-
-Just like FreeRTOS Stream and Message Buffers, this backend uses the task
-notification index only within callsites where the task must block until a
-notification is received or a timeout occurs. The notification index's state is
-always cleaned up before returning. The notification index is never used when
-the acquiring task is not going to block.
-
-.. Note::
-  Task notification support is required in your FreeRTOS configuration, i.e.
-  ``configUSE_TASK_NOTIFICATIONS == 1``.
-
-Design Notes
-------------
-You may ask, why are Task Notifications used at all given the risk associated
-with global notification index allocations? It turns out there's no other
-lightweight mechanism to unblock a task in FreeRTOS.
-
-Task suspension (i.e. ``vTaskSuspend``, ``vTaskResume``, &
-``vTaskResumeFromISR``) seems like a good fit, however ``xTaskResumeAll`` does
-not participate in reference counting and will wake up all suspended tasks
-whether you want it to or not.
-
-Lastly, there's also ``xTaskAbortDelay`` but there is no interrupt safe
-equivalent of this API. Note that it uses ``vTaskSuspendAll`` internally for
-the critical section which is not interrupt safe. If in the future an interrupt
-safe version of this API is offerred, then this would be a great alternative!
-
-Lastly, we want to briefly explain how Task Notifications actually work in
-FreeRTOS to show why you cannot directly share notification indeces even if the
-bits used in the ``ulNotifiedValue`` are unique. This is a very common source of
-bugs when using FreeRTOS and partially why Pigweed does not recommend using the
-native Task Notification APIs directly.
-
-FreeRTOS Task Notifications use a task's TCB's ``ucNotifyState`` to capture the
-notification state even when the task is not blocked. This state transitions
-``taskNOT_WAITING_NOTIFICATION`` to ``task_NOTIFICATION_RECEIVED`` if the task
-ever notified. This notification state is used to determine whether the next
-task notification wait call should block, irrespective of the notification
-value.
-
-In order to enable this optimized backend, native task notifications are only
-used when the task needs to block. If a timeout occurs the task unregisters for
-notifications and clears the notification state before returning. This exact
-mechanism is used by FreeRTOS internally for their Stream and Message Buffer
-implementations.
-
-BinarySemaphore
-===============
-The FreeRTOS backend for the BinarySemaphore uses ``StaticSemaphore_t`` as the
-underlying type. It is created using ``xSemaphoreCreateBinaryStatic`` as part
-of the constructor and cleaned up using ``vSemaphoreDelete`` in the destructor.
-
-.. Note::
-  Static allocation support is required in your FreeRTOS configuration, i.e.
-  ``configSUPPORT_STATIC_ALLOCATION == 1``.
-
-CountingSemaphore
-=================
-The FreeRTOS backend for the CountingSemaphore uses ``StaticSemaphore_t`` as the
-underlying type. It is created using ``xSemaphoreCreateCountingStatic`` as part
-of the constructor and cleaned up using ``vSemaphoreDelete`` in the destructor.
-
-.. Note::
-  Counting semaphore support is required in your FreeRTOS configuration, i.e.
-  ``configUSE_COUNTING_SEMAPHORES == 1``.
-.. Note::
-  Static allocation support is required in your FreeRTOS configuration, i.e.
-  ``configSUPPORT_STATIC_ALLOCATION == 1``.
-
+----------------
+This is a set of backends for pw_sync based on FreeRTOS. It is not ready for
+use, and is under construction.
 
diff --git a/pw_sync_freertos/interrupt_spin_lock.cc b/pw_sync_freertos/interrupt_spin_lock.cc
index a82b602..7c96aa8 100644
--- a/pw_sync_freertos/interrupt_spin_lock.cc
+++ b/pw_sync_freertos/interrupt_spin_lock.cc
@@ -14,7 +14,7 @@
 
 #include "pw_sync/interrupt_spin_lock.h"
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_interrupt/context.h"
 #include "task.h"
 
@@ -27,13 +27,34 @@
     taskENTER_CRITICAL();
   }
   // We can't deadlock here so crash instead.
-  PW_DCHECK(!native_type_.locked,
-            "Recursive InterruptSpinLock::lock() detected");
-  native_type_.locked = true;
+  PW_CHECK(!native_type_.locked.load(std::memory_order_relaxed),
+           "Recursive InterruptSpinLock::lock() detected");
+  native_type_.locked.store(true, std::memory_order_relaxed);
+}
+
+bool InterruptSpinLock::try_lock() {
+  if (interrupt::InInterruptContext()) {
+    UBaseType_t saved_interrupt_mask = taskENTER_CRITICAL_FROM_ISR();
+    if (native_type_.locked.load(std::memory_order_relaxed)) {
+      // Already locked, restore interrupts and bail out.
+      taskEXIT_CRITICAL_FROM_ISR(saved_interrupt_mask);
+      return false;
+    }
+    native_type_.saved_interrupt_mask = saved_interrupt_mask;
+  } else {  // Task context
+    taskENTER_CRITICAL();
+    if (native_type_.locked.load(std::memory_order_relaxed)) {
+      // ALready locked, restore interrupts and bail out.
+      taskEXIT_CRITICAL();
+      return false;
+    }
+  }
+  native_type_.locked.store(true, std::memory_order_relaxed);
+  return true;
 }
 
 void InterruptSpinLock::unlock() {
-  native_type_.locked = false;
+  native_type_.locked.store(false, std::memory_order_relaxed);
   if (interrupt::InInterruptContext()) {
     taskEXIT_CRITICAL_FROM_ISR(native_type_.saved_interrupt_mask);
   } else {  // Task context
diff --git a/pw_sync_freertos/public/pw_sync_freertos/binary_semaphore_inline.h b/pw_sync_freertos/public/pw_sync_freertos/binary_semaphore_inline.h
index e72ff8e..143ef0f 100644
--- a/pw_sync_freertos/public/pw_sync_freertos/binary_semaphore_inline.h
+++ b/pw_sync_freertos/public/pw_sync_freertos/binary_semaphore_inline.h
@@ -14,7 +14,7 @@
 #pragma once
 
 #include "FreeRTOS.h"
-#include "pw_assert/assert.h"
+#include "pw_assert/light.h"
 #include "pw_chrono/system_clock.h"
 #include "pw_chrono_freertos/system_clock_constants.h"
 #include "pw_interrupt/context.h"
@@ -27,7 +27,7 @@
   const SemaphoreHandle_t handle = xSemaphoreCreateBinaryStatic(&native_type_);
   // This should never fail since the pointer provided was not null and it
   // should return a pointer to the StaticSemaphore_t.
-  PW_DASSERT(handle == reinterpret_cast<SemaphoreHandle_t>(&native_type_));
+  PW_DASSERT(handle == &native_type_);
 }
 
 inline BinarySemaphore::~BinarySemaphore() { vSemaphoreDelete(&native_type_); }
@@ -36,28 +36,25 @@
   if (interrupt::InInterruptContext()) {
     BaseType_t woke_higher_task = pdFALSE;
     // It's perfectly fine if the semaphore already has a count of 1.
-    [[maybe_unused]] BaseType_t already_full = xSemaphoreGiveFromISR(
-        reinterpret_cast<SemaphoreHandle_t>(&native_type_), &woke_higher_task);
+    [[maybe_unused]] BaseType_t already_full =
+        xSemaphoreGiveFromISR(&native_type_, &woke_higher_task);
     portYIELD_FROM_ISR(woke_higher_task);
   } else {  // Task context
     // It's perfectly fine if the semaphore already has a count of 1.
-    [[maybe_unused]] BaseType_t already_full =
-        xSemaphoreGive(reinterpret_cast<SemaphoreHandle_t>(&native_type_));
+    [[maybe_unused]] BaseType_t already_full = xSemaphoreGive(&native_type_);
   }
 }
 
 inline void BinarySemaphore::acquire() {
-  // Enforce the pw::sync::BinarySemaphore IRQ contract.
-  PW_DASSERT(!interrupt::InInterruptContext());
+  PW_ASSERT(!interrupt::InInterruptContext());
 #if INCLUDE_vTaskSuspend == 1  // This means portMAX_DELAY is indefinite.
-  const BaseType_t result = xSemaphoreTake(
-      reinterpret_cast<SemaphoreHandle_t>(&native_type_), portMAX_DELAY);
+  const BaseType_t result = xSemaphoreTake(&native_type_, portMAX_DELAY);
   PW_DASSERT(result == pdTRUE);
 #else
   // In case we need to block for longer than the FreeRTOS delay can represent
   // repeatedly hit take until success.
-  while (xSemaphoreTake(reinterpret_cast<SemaphoreHandle_t>(&native_type_),
-                        chrono::freertos::kMaxTimeout.count()) == pdFALSE) {
+  while (xSemaphoreTake(&native_type_, chrono::freertos::kMaxTimeout.count()) ==
+         pdFALSE) {
   }
 #endif  // INCLUDE_vTaskSuspend
 }
@@ -65,23 +62,21 @@
 inline bool BinarySemaphore::try_acquire() noexcept {
   if (interrupt::InInterruptContext()) {
     BaseType_t woke_higher_task = pdFALSE;
-    const bool success = xSemaphoreTakeFromISR(
-                             reinterpret_cast<SemaphoreHandle_t>(&native_type_),
-                             &woke_higher_task) == pdTRUE;
+    const bool success =
+        xSemaphoreTakeFromISR(&native_type_, &woke_higher_task) == pdTRUE;
     portYIELD_FROM_ISR(woke_higher_task);
     return success;
   }
 
   // Task Context
-  return xSemaphoreTake(reinterpret_cast<SemaphoreHandle_t>(&native_type_),
-                        0) == pdTRUE;
+  return xSemaphoreTake(&native_type_, 0) == pdTRUE;
 }
 
 inline bool BinarySemaphore::try_acquire_until(
-    chrono::SystemClock::time_point deadline) {
+    chrono::SystemClock::time_point until_at_least) {
   // Note that if this deadline is in the future, it will get rounded up by
   // one whole tick due to how try_acquire_for is implemented.
-  return try_acquire_for(deadline - chrono::SystemClock::now());
+  return try_acquire_for(until_at_least - chrono::SystemClock::now());
 }
 
 inline BinarySemaphore::native_handle_type BinarySemaphore::native_handle() {
diff --git a/pw_sync_freertos/public/pw_sync_freertos/config.h b/pw_sync_freertos/public/pw_sync_freertos/config.h
deleted file mode 100644
index 6bd34e3..0000000
--- a/pw_sync_freertos/public/pw_sync_freertos/config.h
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-// Configuration macros for the tokenizer module.
-#pragma once
-
-#include "FreeRTOS.h"
-
-// Just like FreeRTOS Stream and Message Buffers, by default the optimized
-// pw::sync::ThreadNotification uses the task notification at array index 0.
-// This optimized backend is compatible with sharing the notification with
-// Stream and Message Buffers and any other users which ONLY notify the task
-// while the task is blocked and the task consumes the notification state
-// before returning.
-//
-// The task's TCB uses a ucNotifyState which captures notification state even
-// when the task is not waiting (taskNOT_WAITING_NOTIFICATION vs
-// task_NOTIFICATION_RECEIVED). This notification state is used to determine
-// whether the next task notification wait call should block, irrespective of
-// the notification value. This means that one must ensure not just that
-// the bits in the ulNotifiedValue are mutually exclusive, but also that the
-// notification state is mutually exclusive!
-#ifndef PW_SYNC_FREERTOS_CONFIG_THREAD_NOTIFICATION_INDEX
-#define PW_SYNC_FREERTOS_CONFIG_THREAD_NOTIFICATION_INDEX 0
-#endif  // PW_SYNC_FREERTOS_CONFIG_THREAD_NOTIFICATION_INDEX
-
-namespace pw::sync::freertos::config {
-
-inline constexpr UBaseType_t kThreadNotificationIndex =
-    PW_SYNC_FREERTOS_CONFIG_THREAD_NOTIFICATION_INDEX;
-#ifdef configTASK_NOTIFICATION_ARRAY_ENTRIES
-// Ensure the index fits within the FreeRTOS configuration of the task
-// notification array.
-static_assert(kThreadNotificationIndex < configTASK_NOTIFICATION_ARRAY_ENTRIES);
-#else   // !defined(configTASK_NOTIFICATION_ARRAY_ENTRIES)
-// This version of FreeRTOS does not support multiple task notifications, ensure
-// the index is the default of 0.
-static_assert(kThreadNotificationIndex == 0);
-#endif  // configTASK_NOTIFICATION_ARRAY_ENTRIES
-
-}  // namespace pw::sync::freertos::config
diff --git a/pw_sync_freertos/public/pw_sync_freertos/counting_semaphore_inline.h b/pw_sync_freertos/public/pw_sync_freertos/counting_semaphore_inline.h
index 809ddd8..e5fb377 100644
--- a/pw_sync_freertos/public/pw_sync_freertos/counting_semaphore_inline.h
+++ b/pw_sync_freertos/public/pw_sync_freertos/counting_semaphore_inline.h
@@ -14,7 +14,7 @@
 #pragma once
 
 #include "FreeRTOS.h"
-#include "pw_assert/assert.h"
+#include "pw_assert/light.h"
 #include "pw_chrono/system_clock.h"
 #include "pw_chrono_freertos/system_clock_constants.h"
 #include "pw_interrupt/context.h"
@@ -28,25 +28,23 @@
       xSemaphoreCreateCountingStatic(max(), 0, &native_type_);
   // This should never fail since the pointer provided was not null and it
   // should return a pointer to the StaticSemaphore_t.
-  PW_DASSERT(handle == reinterpret_cast<SemaphoreHandle_t>(&native_type_));
+  PW_DASSERT(handle == &native_type_);
 }
 
 inline CountingSemaphore::~CountingSemaphore() {
-  vSemaphoreDelete(reinterpret_cast<SemaphoreHandle_t>(&native_type_));
+  vSemaphoreDelete(&native_type_);
 }
 
 inline void CountingSemaphore::acquire() {
-  // Enforce the pw::sync::CountingSemaphore IRQ contract.
-  PW_DASSERT(!interrupt::InInterruptContext());
+  PW_ASSERT(!interrupt::InInterruptContext());
 #if INCLUDE_vTaskSuspend == 1  // This means portMAX_DELAY is indefinite.
-  const BaseType_t result = xSemaphoreTake(
-      reinterpret_cast<SemaphoreHandle_t>(&native_type_), portMAX_DELAY);
+  const BaseType_t result = xSemaphoreTake(&native_type_, portMAX_DELAY);
   PW_DASSERT(result == pdTRUE);
 #else
   // In case we need to block for longer than the FreeRTOS delay can represent
   // repeatedly hit take until success.
-  while (xSemaphoreTake(reinterpret_cast<SemaphoreHandle_t>(&native_type_),
-                        chrono::freertos::kMaxTimeout.count()) == pdFALSE) {
+  while (xSemaphoreTake(&native_type_, chrono::freertos::kMaxTimeout.count()) ==
+         pdFALSE) {
   }
 #endif  // INCLUDE_vTaskSuspend
 }
@@ -54,23 +52,21 @@
 inline bool CountingSemaphore::try_acquire() noexcept {
   if (interrupt::InInterruptContext()) {
     BaseType_t woke_higher_task = pdFALSE;
-    const bool success = xSemaphoreTakeFromISR(
-                             reinterpret_cast<SemaphoreHandle_t>(&native_type_),
-                             &woke_higher_task) == pdTRUE;
+    const bool success =
+        xSemaphoreTakeFromISR(&native_type_, &woke_higher_task) == pdTRUE;
     portYIELD_FROM_ISR(woke_higher_task);
     return success;
   }
 
   // Task Context
-  return xSemaphoreTake(reinterpret_cast<SemaphoreHandle_t>(&native_type_),
-                        0) == pdTRUE;
+  return xSemaphoreTake(&native_type_, 0) == pdTRUE;
 }
 
 inline bool CountingSemaphore::try_acquire_until(
-    chrono::SystemClock::time_point deadline) {
+    chrono::SystemClock::time_point until_at_least) {
   // Note that if this deadline is in the future, it will get rounded up by
   // one whole tick due to how try_acquire_for is implemented.
-  return try_acquire_for(deadline - chrono::SystemClock::now());
+  return try_acquire_for(until_at_least - chrono::SystemClock::now());
 }
 
 inline CountingSemaphore::native_handle_type
diff --git a/pw_sync_freertos/public/pw_sync_freertos/interrupt_spin_lock_inline.h b/pw_sync_freertos/public/pw_sync_freertos/interrupt_spin_lock_inline.h
index 98079ec..8bbd4cf 100644
--- a/pw_sync_freertos/public/pw_sync_freertos/interrupt_spin_lock_inline.h
+++ b/pw_sync_freertos/public/pw_sync_freertos/interrupt_spin_lock_inline.h
@@ -18,18 +18,11 @@
 namespace pw::sync {
 
 constexpr InterruptSpinLock::InterruptSpinLock()
-    : native_type_{.locked = false, .saved_interrupt_mask = 0} {}
+    : native_type_{.locked{false}, .saved_interrupt_mask = 0} {}
 
 inline InterruptSpinLock::native_handle_type
 InterruptSpinLock::native_handle() {
   return native_type_;
 }
 
-inline bool InterruptSpinLock::try_lock() {
-  // This backend does not support SMP and on a uniprocesor we cannot actually
-  // fail to acquire the lock. Recursive locking is already detected by lock().
-  lock();
-  return true;
-}
-
 }  // namespace pw::sync
diff --git a/pw_sync_freertos/public/pw_sync_freertos/interrupt_spin_lock_native.h b/pw_sync_freertos/public/pw_sync_freertos/interrupt_spin_lock_native.h
index e7f0e8f..ac476dc 100644
--- a/pw_sync_freertos/public/pw_sync_freertos/interrupt_spin_lock_native.h
+++ b/pw_sync_freertos/public/pw_sync_freertos/interrupt_spin_lock_native.h
@@ -13,12 +13,14 @@
 // the License.
 #pragma once
 
+#include <atomic>
+
 #include "FreeRTOS.h"
 
 namespace pw::sync::backend {
 
 struct NativeInterruptSpinLock {
-  bool locked;  // Used to detect recursion.
+  std::atomic<bool> locked;  // Used to detect recursion.
   UBaseType_t saved_interrupt_mask;
 };
 using NativeInterruptSpinLockHandle = NativeInterruptSpinLock&;
diff --git a/pw_sync_freertos/public/pw_sync_freertos/mutex_inline.h b/pw_sync_freertos/public/pw_sync_freertos/mutex_inline.h
index a153ed5..92f7c07 100644
--- a/pw_sync_freertos/public/pw_sync_freertos/mutex_inline.h
+++ b/pw_sync_freertos/public/pw_sync_freertos/mutex_inline.h
@@ -14,7 +14,7 @@
 #pragma once
 
 #include "FreeRTOS.h"
-#include "pw_assert/assert.h"
+#include "pw_assert/light.h"
 #include "pw_interrupt/context.h"
 #include "pw_sync/mutex.h"
 #include "semphr.h"
@@ -33,42 +33,34 @@
   const SemaphoreHandle_t handle = xSemaphoreCreateMutexStatic(&native_type_);
   // This should never fail since the pointer provided was not null and it
   // should return a pointer to the StaticSemaphore_t.
-  PW_DASSERT(handle == reinterpret_cast<SemaphoreHandle_t>(&native_type_));
+  PW_DASSERT(handle == &native_type_);
 }
 
-inline Mutex::~Mutex() {
-  vSemaphoreDelete(reinterpret_cast<SemaphoreHandle_t>(&native_type_));
-}
+inline Mutex::~Mutex() { vSemaphoreDelete(&native_type_); }
 
 inline void Mutex::lock() {
-  // Enforce the pw::sync::Mutex IRQ contract.
-  PW_DASSERT(!interrupt::InInterruptContext());
+  PW_ASSERT(!interrupt::InInterruptContext());
 #if INCLUDE_vTaskSuspend == 1  // This means portMAX_DELAY is indefinite.
-  const BaseType_t result = xSemaphoreTake(
-      reinterpret_cast<SemaphoreHandle_t>(&native_type_), portMAX_DELAY);
+  const BaseType_t result = xSemaphoreTake(&native_type_, portMAX_DELAY);
   PW_DASSERT(result == pdTRUE);
 #else
   // In case we need to block for longer than the FreeRTOS delay can represent
   // repeatedly hit take until success.
-  while (xSemaphoreTake(reinterpret_cast<SemaphoreHandle_t>(&native_type_),
-                        chrono::freertos::kMaxTimeout.count()) == pdFALSE) {
+  while (xSemaphoreTake(&native_type_, chrono::freertos::kMaxTimeout.count()) ==
+         pdFALSE) {
   }
 #endif  // INCLUDE_vTaskSuspend
 }
 
 inline bool Mutex::try_lock() {
-  // Enforce the pw::sync::Mutex IRQ contract.
-  PW_DASSERT(!interrupt::InInterruptContext());
-  return xSemaphoreTake(reinterpret_cast<SemaphoreHandle_t>(&native_type_),
-                        0) == pdTRUE;
+  PW_ASSERT(!interrupt::InInterruptContext());
+  return xSemaphoreTake(&native_type_, 0) == pdTRUE;
 }
 
 inline void Mutex::unlock() {
-  // Enforce the pw::sync::Mutex IRQ contract.
-  PW_DASSERT(!interrupt::InInterruptContext());
+  PW_ASSERT(!interrupt::InInterruptContext());
   // Unlocking only fails if it was not locked first.
-  PW_ASSERT(xSemaphoreGive(
-                reinterpret_cast<SemaphoreHandle_t>(&native_type_)) == pdTRUE);
+  PW_ASSERT(xSemaphoreGive(&native_type_) == pdTRUE);
 }
 
 inline Mutex::native_handle_type Mutex::native_handle() { return native_type_; }
diff --git a/pw_sync_freertos/public/pw_sync_freertos/thread_notification_inline.h b/pw_sync_freertos/public/pw_sync_freertos/thread_notification_inline.h
deleted file mode 100644
index cf45f21..0000000
--- a/pw_sync_freertos/public/pw_sync_freertos/thread_notification_inline.h
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "FreeRTOS.h"
-#include "pw_assert/assert.h"
-#include "pw_interrupt/context.h"
-#include "pw_sync/thread_notification.h"
-#include "task.h"
-
-namespace pw::sync {
-namespace backend {
-
-static_assert(configUSE_TASK_NOTIFICATIONS != 0,
-              "Task Notifications aren't enabled.");
-
-}  // namespace backend
-
-inline ThreadNotification::ThreadNotification()
-    : native_type_{
-          .blocked_thread = nullptr,
-          .notified = false,
-      } {}
-
-inline ThreadNotification::~ThreadNotification() = default;
-
-inline bool ThreadNotification::try_acquire() {
-  // Enforce the pw::sync::ThreadNotification IRQ contract.
-  PW_DASSERT(!interrupt::InInterruptContext());
-  taskENTER_CRITICAL();
-  const bool notified = native_type_.notified;
-  native_type_.notified = false;
-  taskEXIT_CRITICAL();
-  return notified;
-}
-
-inline ThreadNotification::native_handle_type
-ThreadNotification::native_handle() {
-  return native_type_;
-}
-
-}  // namespace pw::sync
diff --git a/pw_sync_freertos/public/pw_sync_freertos/thread_notification_native.h b/pw_sync_freertos/public/pw_sync_freertos/thread_notification_native.h
deleted file mode 100644
index 9cc0a6a..0000000
--- a/pw_sync_freertos/public/pw_sync_freertos/thread_notification_native.h
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "FreeRTOS.h"
-#include "task.h"
-
-namespace pw::sync::backend {
-
-struct NativeThreadNotification {
-  TaskHandle_t blocked_thread;
-  bool notified;
-};
-using NativeThreadNotificationHandle = NativeThreadNotification&;
-
-}  // namespace pw::sync::backend
diff --git a/pw_sync_freertos/public/pw_sync_freertos/timed_mutex_inline.h b/pw_sync_freertos/public/pw_sync_freertos/timed_mutex_inline.h
index 92f5ef2..bc114b9 100644
--- a/pw_sync_freertos/public/pw_sync_freertos/timed_mutex_inline.h
+++ b/pw_sync_freertos/public/pw_sync_freertos/timed_mutex_inline.h
@@ -19,10 +19,10 @@
 namespace pw::sync {
 
 inline bool TimedMutex::try_lock_until(
-    chrono::SystemClock::time_point deadline) {
+    chrono::SystemClock::time_point until_at_least) {
   // Note that if this deadline is in the future, it will get rounded up by
   // one whole tick due to how try_lock_for is implemented.
-  return try_lock_for(deadline - chrono::SystemClock::now());
+  return try_lock_for(until_at_least - chrono::SystemClock::now());
 }
 
 }  // namespace pw::sync
diff --git a/pw_sync_freertos/public/pw_sync_freertos/timed_thread_notification_inline.h b/pw_sync_freertos/public/pw_sync_freertos/timed_thread_notification_inline.h
deleted file mode 100644
index 671fe4d..0000000
--- a/pw_sync_freertos/public/pw_sync_freertos/timed_thread_notification_inline.h
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_chrono/system_clock.h"
-#include "pw_sync/timed_thread_notification.h"
-
-namespace pw::sync {
-
-inline bool TimedThreadNotification::try_acquire_until(
-    chrono::SystemClock::time_point deadline) {
-  // Note that if this deadline is in the future, it will get rounded up by
-  // one whole tick due to how try_lock_for is implemented.
-  return try_acquire_for(deadline - chrono::SystemClock::now());
-}
-
-}  // namespace pw::sync
diff --git a/pw_sync_freertos/public_overrides/thread_notification/pw_sync_backend/thread_notification_inline.h b/pw_sync_freertos/public_overrides/thread_notification/pw_sync_backend/thread_notification_inline.h
deleted file mode 100644
index fdd5089..0000000
--- a/pw_sync_freertos/public_overrides/thread_notification/pw_sync_backend/thread_notification_inline.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_sync_freertos/thread_notification_inline.h"
diff --git a/pw_sync_freertos/public_overrides/thread_notification/pw_sync_backend/thread_notification_native.h b/pw_sync_freertos/public_overrides/thread_notification/pw_sync_backend/thread_notification_native.h
deleted file mode 100644
index 2798150..0000000
--- a/pw_sync_freertos/public_overrides/thread_notification/pw_sync_backend/thread_notification_native.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_sync_freertos/thread_notification_native.h"
diff --git a/pw_sync_freertos/public_overrides/timed_thread_notification/pw_sync_backend/timed_thread_notification_inline.h b/pw_sync_freertos/public_overrides/timed_thread_notification/pw_sync_backend/timed_thread_notification_inline.h
deleted file mode 100644
index d2c3882..0000000
--- a/pw_sync_freertos/public_overrides/timed_thread_notification/pw_sync_backend/timed_thread_notification_inline.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_sync_freertos/timed_thread_notification_inline.h"
diff --git a/pw_sync_freertos/thread_notification.cc b/pw_sync_freertos/thread_notification.cc
deleted file mode 100644
index 9fc5f08..0000000
--- a/pw_sync_freertos/thread_notification.cc
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_sync/thread_notification.h"
-
-#include "FreeRTOS.h"
-#include "pw_assert/check.h"
-#include "pw_interrupt/context.h"
-#include "pw_sync_freertos/config.h"
-#include "task.h"
-
-namespace pw::sync {
-namespace {
-
-BaseType_t WaitForNotification(TickType_t xTicksToWait) {
-#ifdef configTASK_NOTIFICATION_ARRAY_ENTRIES
-  return xTaskNotifyWaitIndexed(
-      pw::sync::freertos::config::kThreadNotificationIndex,
-      0,        // Clear no bits on entry.
-      0,        // Clear no bits on exit.
-      nullptr,  // Don't care about the notification value.
-      xTicksToWait);
-#else   // !configTASK_NOTIFICATION_ARRAY_ENTRIES
-  return xTaskNotifyWait(0,        // Clear no bits on entry.
-                         0,        // Clear no bits on exit.
-                         nullptr,  // Don't care about the notification value.
-                         xTicksToWait);
-#endif  // configTASK_NOTIFICATION_ARRAY_ENTRIES
-}
-
-}  // namespace
-
-void ThreadNotification::acquire() {
-  // Enforce the pw::sync::ThreadNotification IRQ contract.
-  PW_DCHECK(!interrupt::InInterruptContext());
-
-  // Enforce that only a single thread can block at a time.
-  PW_DCHECK(native_type_.blocked_thread == nullptr);
-
-  // Ensure that no one forgot to clean up nor corrupted the task notification
-  // state in the TCB.
-  PW_DCHECK(xTaskNotifyStateClear(nullptr) == pdFALSE);
-
-  taskENTER_CRITICAL();
-  if (native_type_.notified) {
-    native_type_.notified = false;
-    taskEXIT_CRITICAL();
-    return;
-  }
-  // Not notified yet, set the task handle for a one-time notification.
-  native_type_.blocked_thread = xTaskGetCurrentTaskHandle();
-  taskEXIT_CRITICAL();
-
-#if INCLUDE_vTaskSuspend == 1  // This means portMAX_DELAY is indefinite.
-  const BaseType_t result = WaitForNotification(portMAX_DELAY);
-  PW_DCHECK_UINT_EQ(result, pdTRUE);
-#else   // INCLUDE_vTaskSuspend != 1
-  while (WaitForNotification(portMAX_DELAY) == pdFALSE) {
-  }
-#endif  // INCLUDE_vTaskSuspend
-
-  taskENTER_CRITICAL();
-  // The task handle was cleared by the notifier.
-  // Note that this may hide another notification, however this is considered
-  // a form of notification saturation just like as if this happened before
-  // acquire() was invoked.
-  native_type_.notified = false;
-  taskEXIT_CRITICAL();
-}
-
-void ThreadNotification::release() {
-  if (!interrupt::InInterruptContext()) {  // Task context
-    taskENTER_CRITICAL();
-    if (native_type_.blocked_thread != nullptr) {
-#ifdef configTASK_NOTIFICATION_ARRAY_ENTRIES
-      xTaskNotifyIndexed(native_type_.blocked_thread,
-                         pw::sync::freertos::config::kThreadNotificationIndex,
-                         0u,
-                         eNoAction);
-#else   // !configTASK_NOTIFICATION_ARRAY_ENTRIES
-      xTaskNotify(native_type_.blocked_thread, 0u, eNoAction);
-#endif  // configTASK_NOTIFICATION_ARRAY_ENTRIES
-
-      native_type_.blocked_thread = nullptr;
-    }
-    native_type_.notified = true;
-    taskEXIT_CRITICAL();
-    return;
-  }
-
-  // Interrupt context
-  const UBaseType_t saved_interrupt_mask = taskENTER_CRITICAL_FROM_ISR();
-  if (native_type_.blocked_thread != nullptr) {
-    BaseType_t woke_higher_task = pdFALSE;
-
-#ifdef configTASK_NOTIFICATION_ARRAY_ENTRIES
-    xTaskNotifyIndexedFromISR(
-        native_type_.blocked_thread,
-        pw::sync::freertos::config::kThreadNotificationIndex,
-        0u,
-        eNoAction,
-        &woke_higher_task);
-#else   // !configTASK_NOTIFICATION_ARRAY_ENTRIES
-    xTaskNotifyFromISR(
-        native_type_.blocked_thread, 0u, eNoAction, &woke_higher_task);
-#endif  // configTASK_NOTIFICATION_ARRAY_ENTRIES
-
-    native_type_.blocked_thread = nullptr;
-    portYIELD_FROM_ISR(woke_higher_task);
-  }
-  native_type_.notified = true;
-  taskEXIT_CRITICAL_FROM_ISR(saved_interrupt_mask);
-}
-
-}  // namespace pw::sync
diff --git a/pw_sync_freertos/timed_mutex.cc b/pw_sync_freertos/timed_mutex.cc
index 897f7ba..0143b84 100644
--- a/pw_sync_freertos/timed_mutex.cc
+++ b/pw_sync_freertos/timed_mutex.cc
@@ -17,7 +17,7 @@
 #include <algorithm>
 
 #include "FreeRTOS.h"
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_chrono/system_clock.h"
 #include "pw_chrono_freertos/system_clock_constants.h"
 #include "pw_interrupt/context.h"
@@ -27,34 +27,29 @@
 
 namespace pw::sync {
 
-bool TimedMutex::try_lock_for(SystemClock::duration timeout) {
-  // Enforce the pw::sync::TimedMutex IRQ contract.
+bool TimedMutex::try_lock_for(SystemClock::duration for_at_least) {
   PW_DCHECK(!interrupt::InInterruptContext());
 
   // Use non-blocking try_acquire for negative and zero length durations.
-  if (timeout <= SystemClock::duration::zero()) {
+  if (for_at_least <= SystemClock::duration::zero()) {
     return try_lock();
   }
 
-  // In case the timeout is too long for us to express through the native
-  // FreeRTOS API, we repeatedly wait with shorter durations. Note that on a
-  // tick based kernel we cannot tell how far along we are on the current tick,
-  // ergo we add one whole tick to the final duration. However, this also means
-  // that the loop must ensure that timeout + 1 is less than the max timeout.
+  // On a tick based kernel we cannot tell how far along we are on the current
+  // tick, ergo we add one whole tick to the final duration.
   constexpr SystemClock::duration kMaxTimeoutMinusOne =
       pw::chrono::freertos::kMaxTimeout - SystemClock::duration(1);
-  while (timeout > kMaxTimeoutMinusOne) {
-    if (xSemaphoreTake(reinterpret_cast<SemaphoreHandle_t>(&native_handle()),
+  while (for_at_least > kMaxTimeoutMinusOne) {
+    if (xSemaphoreTake(&native_handle(),
                        static_cast<TickType_t>(kMaxTimeoutMinusOne.count())) ==
         pdTRUE) {
       return true;
     }
-    timeout -= kMaxTimeoutMinusOne;
+    for_at_least -= kMaxTimeoutMinusOne;
   }
-  // On a tick based kernel we cannot tell how far along we are on the current
-  // tick, ergo we add one whole tick to the final duration.
-  return xSemaphoreTake(reinterpret_cast<SemaphoreHandle_t>(&native_handle()),
-                        static_cast<TickType_t>(timeout.count() + 1)) == pdTRUE;
+  return xSemaphoreTake(&native_handle(),
+                        static_cast<TickType_t>(for_at_least.count() + 1)) ==
+         pdTRUE;
 }
 
 }  // namespace pw::sync
diff --git a/pw_sync_freertos/timed_thread_notification.cc b/pw_sync_freertos/timed_thread_notification.cc
deleted file mode 100644
index e6d82bb..0000000
--- a/pw_sync_freertos/timed_thread_notification.cc
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_sync/timed_thread_notification.h"
-
-#include "FreeRTOS.h"
-#include "pw_assert/check.h"
-#include "pw_chrono/system_clock.h"
-#include "pw_chrono_freertos/system_clock_constants.h"
-#include "pw_interrupt/context.h"
-#include "pw_sync_freertos/config.h"
-#include "task.h"
-
-using pw::chrono::SystemClock;
-
-namespace pw::sync {
-namespace {
-
-BaseType_t WaitForNotification(TickType_t xTicksToWait) {
-#ifdef configTASK_NOTIFICATION_ARRAY_ENTRIES
-  return xTaskNotifyWaitIndexed(
-      pw::sync::freertos::config::kThreadNotificationIndex,
-      0,        // Clear no bits on entry.
-      0,        // Clear no bits on exit.
-      nullptr,  // Don't care about the notification value.
-      xTicksToWait);
-#else   // !configTASK_NOTIFICATION_ARRAY_ENTRIES
-  return xTaskNotifyWait(0,        // Clear no bits on entry.
-                         0,        // Clear no bits on exit.
-                         nullptr,  // Don't care about the notification value.
-                         xTicksToWait);
-#endif  // configTASK_NOTIFICATION_ARRAY_ENTRIES
-}
-
-}  // namespace
-
-bool TimedThreadNotification::try_acquire_for(SystemClock::duration timeout) {
-  // Enforce the pw::sync::TImedThreadNotification IRQ contract.
-  PW_DCHECK(!interrupt::InInterruptContext());
-
-  // Enforce that only a single thread can block at a time.
-  PW_DCHECK(native_handle().blocked_thread == nullptr);
-
-  // Ensure that no one forgot to clean up nor corrupted the task notification
-  // state in the TCB.
-  PW_DCHECK(xTaskNotifyStateClear(nullptr) == pdFALSE);
-
-  taskENTER_CRITICAL();
-  {
-    const bool notified = native_handle().notified;
-    // Don't block for negative or zero length durations.
-    if (notified || (timeout <= SystemClock::duration::zero())) {
-      native_handle().notified = false;
-      taskEXIT_CRITICAL();
-      return notified;
-    }
-    // Not notified yet, set the task handle for a one-time notification.
-    native_handle().blocked_thread = xTaskGetCurrentTaskHandle();
-  }
-  taskEXIT_CRITICAL();
-
-  const bool notified = [&]() {
-    // In case the timeout is too long for us to express through the native
-    // FreeRTOS API, we repeatedly wait with shorter durations. Note that on a
-    // tick based kernel we cannot tell how far along we are on the current
-    // tick, ergo we add one whole tick to the final duration. However, this
-    // also means that the loop must ensure that timeout + 1 is less than the
-    // max timeout.
-    constexpr SystemClock::duration kMaxTimeoutMinusOne =
-        pw::chrono::freertos::kMaxTimeout - SystemClock::duration(1);
-    // In case the timeout is too long for us to express through the native
-    // FreeRTOS API, we repeatedly wait with shorter durations.
-    while (timeout > kMaxTimeoutMinusOne) {
-      if (WaitForNotification(
-              static_cast<TickType_t>(kMaxTimeoutMinusOne.count())) == pdTRUE) {
-        return true;
-      }
-      timeout -= kMaxTimeoutMinusOne;
-    }
-
-    // On a tick based kernel we cannot tell how far along we are on the current
-    // tick, ergo we add one whole tick to the final duration.
-    return WaitForNotification(static_cast<TickType_t>(timeout.count() + 1)) ==
-           pdTRUE;
-  }();
-
-  taskENTER_CRITICAL();
-  if (notified) {
-    // Note that this may hide another notification, however this is considered
-    // a form of notification saturation just like as if this happened before
-    // acquire() was invoked.
-    native_handle().notified = false;
-    // The task handle and notification state were cleared by the notifier.
-  } else {
-    // Note that we do NOT want to clear the notified value so the next call
-    // can detect the notification which came after we timed out but before this
-    // critical section.
-    //
-    // However, we do need to clear the task handle if we weren't notified and
-    // the notification state in case we were notified to ensure we can block
-    // in the future.
-    native_handle().blocked_thread = nullptr;
-#ifdef configTASK_NOTIFICATION_ARRAY_ENTRIES
-    xTaskNotifyStateClearIndexed(
-        nullptr, pw::sync::freertos::config::kThreadNotificationIndex);
-#else   // !configTASK_NOTIFICATION_ARRAY_ENTRIES
-    xTaskNotifyStateClear(nullptr);
-#endif  // configTASK_NOTIFICATION_ARRAY_ENTRIES
-  }
-  taskEXIT_CRITICAL();
-  return notified;
-}
-
-}  // namespace pw::sync
diff --git a/pw_sync_stl/BUILD b/pw_sync_stl/BUILD
new file mode 100644
index 0000000..c2f39a7
--- /dev/null
+++ b/pw_sync_stl/BUILD
@@ -0,0 +1,152 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "binary_semaphore_headers",
+    hdrs = [
+        "public/pw_sync_stl/binary_semaphore_inline.h",
+        "public/pw_sync_stl/binary_semaphore_native.h",
+        "public_overrides/pw_sync_backend/binary_semaphore_inline.h",
+        "public_overrides/pw_sync_backend/binary_semaphore_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        "//pw_chrono:system_clock",
+    ],
+)
+
+pw_cc_library(
+    name = "binary_semaphore",
+    srcs = [
+        "binary_semaphore.cc",
+    ],
+    deps = [
+        ":binary_semaphore_headers",
+        "//pw_chrono:system_clock",
+        "//pw_sync:binary_semaphore_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "counting_semaphore_headers",
+    hdrs = [
+        "public/pw_sync_stl/counting_semaphore_inline.h",
+        "public/pw_sync_stl/counting_semaphore_native.h",
+        "public_overrides/pw_sync_backend/counting_semaphore_inline.h",
+        "public_overrides/pw_sync_backend/counting_semaphore_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        "//pw_chrono:system_clock",
+    ],
+)
+
+pw_cc_library(
+    name = "counting_semaphore",
+    srcs = [
+        "counting_semaphore.cc",
+    ],
+    deps = [
+        ":counting_semaphore_headers",
+        "//pw_chrono:system_clock",
+        "//pw_sync:counting_semaphore_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "mutex_headers",
+    hdrs = [
+        "public/pw_sync_stl/mutex_inline.h",
+        "public/pw_sync_stl/mutex_native.h",
+        "public_overrides/pw_sync_backend/mutex_inline.h",
+        "public_overrides/pw_sync_backend/mutex_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        "//pw_sync:mutex_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "mutex",
+    deps = [
+        ":mutex_headers",
+        "//pw_sync:mutex_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "timed_mutex_headers",
+    hdrs = [
+        "public/pw_sync_stl/timed_mutex_inline.h",
+        "public_overrides/pw_sync_backend/timed_mutex_inline.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        "//pw_chrono:system_clock",
+        "//pw_sync:timed_mutex_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "timed_mutex",
+    deps = [
+        ":timed_mutex_headers",
+        "//pw_sync:timed_mutex_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "interrupt_spin_lock_headers",
+    hdrs = [
+        "public/pw_sync_stl/interrupt_spin_lock_inline.h",
+        "public/pw_sync_stl/interrupt_spin_lock_native.h",
+        "public_overrides/pw_sync_backend/interrupt_spin_lock_inline.h",
+        "public_overrides/pw_sync_backend/interrupt_spin_lock_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+)
+
+pw_cc_library(
+    name = "interrupt_spin_lock",
+    deps = [
+        ":interrupt_spin_lock_headers",
+        "//pw_sync:interrupt_spin_lock_facade",
+        "//pw_sync:yield_core",
+    ],
+)
diff --git a/pw_sync_stl/BUILD.bazel b/pw_sync_stl/BUILD.bazel
deleted file mode 100644
index e3cb3a0..0000000
--- a/pw_sync_stl/BUILD.bazel
+++ /dev/null
@@ -1,170 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-load(
-    "//pw_build:selects.bzl",
-    "TARGET_COMPATIBLE_WITH_HOST_SELECT",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "binary_semaphore_headers",
-    hdrs = [
-        "public/pw_sync_stl/binary_semaphore_inline.h",
-        "public/pw_sync_stl/binary_semaphore_native.h",
-        "public_overrides/pw_sync_backend/binary_semaphore_inline.h",
-        "public_overrides/pw_sync_backend/binary_semaphore_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-    deps = [
-        "//pw_chrono:system_clock",
-    ],
-)
-
-pw_cc_library(
-    name = "binary_semaphore",
-    srcs = [
-        "binary_semaphore.cc",
-    ],
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-    deps = [
-        ":binary_semaphore_headers",
-        "//pw_assert",
-        "//pw_chrono:system_clock",
-        "//pw_sync:binary_semaphore_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "counting_semaphore_headers",
-    hdrs = [
-        "public/pw_sync_stl/counting_semaphore_inline.h",
-        "public/pw_sync_stl/counting_semaphore_native.h",
-        "public_overrides/pw_sync_backend/counting_semaphore_inline.h",
-        "public_overrides/pw_sync_backend/counting_semaphore_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-    deps = [
-        "//pw_chrono:system_clock",
-    ],
-)
-
-pw_cc_library(
-    name = "counting_semaphore",
-    srcs = [
-        "counting_semaphore.cc",
-    ],
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-    deps = [
-        ":counting_semaphore_headers",
-        "//pw_assert",
-        "//pw_chrono:system_clock",
-        "//pw_sync:counting_semaphore_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "mutex_headers",
-    hdrs = [
-        "public/pw_sync_stl/mutex_inline.h",
-        "public/pw_sync_stl/mutex_native.h",
-        "public_overrides/pw_sync_backend/mutex_inline.h",
-        "public_overrides/pw_sync_backend/mutex_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-    deps = ["//pw_sync:mutex_facade"],
-)
-
-pw_cc_library(
-    name = "mutex",
-    srcs = ["mutex.cc"],
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-    deps = [
-        ":mutex_headers",
-        "//pw_assert",
-        "//pw_sync:mutex_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "timed_mutex_headers",
-    hdrs = [
-        "public/pw_sync_stl/timed_mutex_inline.h",
-        "public_overrides/pw_sync_backend/timed_mutex_inline.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-    deps = [
-        "//pw_chrono:system_clock",
-    ],
-)
-
-pw_cc_library(
-    name = "timed_mutex",
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-    deps = [
-        ":timed_mutex_headers",
-        "//pw_sync:timed_mutex_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "interrupt_spin_lock_headers",
-    hdrs = [
-        "public/pw_sync_stl/interrupt_spin_lock_inline.h",
-        "public/pw_sync_stl/interrupt_spin_lock_native.h",
-        "public_overrides/pw_sync_backend/interrupt_spin_lock_inline.h",
-        "public_overrides/pw_sync_backend/interrupt_spin_lock_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-    deps = [
-        "//pw_sync:yield_core",
-    ],
-)
-
-pw_cc_library(
-    name = "interrupt_spin_lock",
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-    deps = [
-        ":interrupt_spin_lock_headers",
-        "//pw_sync:interrupt_spin_lock_facade",
-        "//pw_sync:yield_core",
-    ],
-)
diff --git a/pw_sync_stl/BUILD.gn b/pw_sync_stl/BUILD.gn
index 42aea99..0b7e56b 100644
--- a/pw_sync_stl/BUILD.gn
+++ b/pw_sync_stl/BUILD.gn
@@ -14,7 +14,6 @@
 
 import("//build_overrides/pigweed.gni")
 
-import("$dir_pw_build/error.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_chrono/backend.gni")
 import("$dir_pw_docgen/docs.gni")
@@ -29,15 +28,6 @@
   visibility = [ ":*" ]
 }
 
-pw_build_assert("check_system_clock_backend") {
-  condition =
-      pw_chrono_SYSTEM_CLOCK_BACKEND == "" ||
-      pw_chrono_SYSTEM_CLOCK_BACKEND == "$dir_pw_chrono_stl:system_clock"
-  message = "The STL pw_sync backends only work with the STL " +
-            "pw::chrono::SystemClock backend."
-  visibility = [ ":*" ]
-}
-
 # This target provides the backend for pw::sync::BinarySemaphore.
 pw_source_set("binary_semaphore_backend") {
   public_configs = [
@@ -52,11 +42,15 @@
   ]
   sources = [ "binary_semaphore.cc" ]
   deps = [
-    ":check_system_clock_backend",
     "$dir_pw_assert",
     "$dir_pw_chrono:system_clock",
     "$dir_pw_sync:binary_semaphore.facade",
   ]
+  assert(
+      pw_chrono_SYSTEM_CLOCK_BACKEND == "" ||
+          pw_chrono_SYSTEM_CLOCK_BACKEND == "$dir_pw_chrono_stl:system_clock",
+      "The STL pw::sync::BinarySemaphore backend only works with the " +
+          "STL pw::chrono::SystemClock backend.")
 }
 
 # This target provides the backend for pw::sync::CountingSemaphore.
@@ -73,11 +67,15 @@
   ]
   sources = [ "counting_semaphore.cc" ]
   deps = [
-    ":check_system_clock_backend",
     "$dir_pw_assert",
     "$dir_pw_chrono:system_clock",
     "$dir_pw_sync:counting_semaphore.facade",
   ]
+  assert(
+      pw_chrono_SYSTEM_CLOCK_BACKEND == "" ||
+          pw_chrono_SYSTEM_CLOCK_BACKEND == "$dir_pw_chrono_stl:system_clock",
+      "The STL pw::sync::CountingSemaphore backend only works with the " +
+          "STL pw::chrono::SystemClock backend.")
 }
 
 # This target provides the backend for pw::sync::Mutex.
@@ -93,9 +91,6 @@
     "public_overrides/pw_sync_backend/mutex_native.h",
   ]
   public_deps = [ "$dir_pw_sync:mutex.facade" ]
-  deps = [ dir_pw_assert ]
-
-  sources = [ "mutex.cc" ]
 }
 
 # This target provides the backend for pw::sync::TimedMutex.
@@ -110,7 +105,11 @@
     "public_overrides/pw_sync_backend/timed_mutex_inline.h",
   ]
   public_deps = [ "$dir_pw_chrono:system_clock" ]
-  deps = [ ":check_system_clock_backend" ]
+  assert(
+      pw_chrono_SYSTEM_CLOCK_BACKEND == "" ||
+          pw_chrono_SYSTEM_CLOCK_BACKEND == "$dir_pw_chrono_stl:system_clock",
+      "The STL pw::sync::TimedMutex backend only works with the STL " +
+          "pw::chrono::SystemClock backend.")
 }
 
 # This target provides the backend for pw::sync::InterruptSpinLock.
diff --git a/pw_sync_stl/CMakeLists.txt b/pw_sync_stl/CMakeLists.txt
index e2d4433..bf8d343 100644
--- a/pw_sync_stl/CMakeLists.txt
+++ b/pw_sync_stl/CMakeLists.txt
@@ -14,88 +14,7 @@
 
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
 
-# This target provides the backend for pw::sync::BinarySemaphore.
-pw_add_module_library(pw_sync_stl.binary_semaphore_backend
-  IMPLEMENTS_FACADES
-    pw_sync.binary_semaphore
-  HEADERS
-    public/pw_sync_stl/binary_semaphore_inline.h
-    public/pw_sync_stl/binary_semaphore_native.h
-    public_overrides/pw_sync_backend/binary_semaphore_inline.h
-    public_overrides/pw_sync_backend/binary_semaphore_native.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides
-  SOURCES
-    binary_semaphore.cc
-  PRIVATE_DEPS
-    pw_assert
-    pw_chrono.system_clock
-)
-
-# This target provides the backend for pw::sync::CountingSemaphore.
-pw_add_module_library(pw_sync_stl.counting_semaphore_backend
-  IMPLEMENTS_FACADES
-    pw_sync.counting_semaphore
-  HEADERS
-    public/pw_sync_stl/counting_semaphore_inline.h
-    public/pw_sync_stl/counting_semaphore_native.h
-    public_overrides/pw_sync_backend/counting_semaphore_inline.h
-    public_overrides/pw_sync_backend/counting_semaphore_native.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides
-  SOURCES
-    counting_semaphore.cc
-  PRIVATE_DEPS
-    pw_assert
-    pw_chrono.system_clock
-)
-
-# This target provides the backend for pw::sync::Mutex.
 pw_add_module_library(pw_sync_stl.mutex_backend
   IMPLEMENTS_FACADES
     pw_sync.mutex
-  HEADERS
-    public/pw_sync_stl/mutex_inline.h
-    public/pw_sync_stl/mutex_native.h
-    public_overrides/pw_sync_backend/mutex_inline.h
-    public_overrides/pw_sync_backend/mutex_native.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides
-  SOURCES
-    mutex.cc
-  PRIVATE_DEPS
-    pw_assert
-)
-
-# This target provides the backend for pw::sync::TimedMutex.
-pw_add_module_library(pw_sync_stl.timed_mutex_backend
-  IMPLEMENTS_FACADES
-    pw_sync.timed_mutex
-  HEADERS
-    public/pw_sync_stl/timed_mutex_inline.h
-    public_overrides/pw_sync_backend/timed_mutex_inline.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides
-  PUBLIC_DEPS
-    pw_sync.mutex
-    pw_chrono.system_clock
-)
-
-pw_add_module_library(pw_sync_stl.interrupt_spin_lock
-  IMPLEMENTS_FACADES
-    pw_sync.interrupt_spin_lock
-  HEADERS
-    public/pw_sync_stl/interrupt_spin_lock_inline.h
-    public/pw_sync_stl/interrupt_spin_lock_native.h
-    public_overrides/pw_sync_backend/interrupt_spin_lock_inline.h
-    public_overrides/pw_sync_backend/interrupt_spin_lock_native.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides
-  PUBLIC_DEPS
-    pw_sync.yield_core
 )
diff --git a/pw_sync_stl/OWNERS b/pw_sync_stl/OWNERS
deleted file mode 100644
index e9a6c58..0000000
--- a/pw_sync_stl/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-ewout@google.com
-hepler@google.com
diff --git a/pw_sync_stl/binary_semaphore.cc b/pw_sync_stl/binary_semaphore.cc
index 02ab509..fdff483 100644
--- a/pw_sync_stl/binary_semaphore.cc
+++ b/pw_sync_stl/binary_semaphore.cc
@@ -14,7 +14,7 @@
 
 #include "pw_sync/binary_semaphore.h"
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 
 using pw::chrono::SystemClock;
 
@@ -42,10 +42,11 @@
   return false;
 }
 
-bool BinarySemaphore::try_acquire_until(SystemClock::time_point deadline) {
+bool BinarySemaphore::try_acquire_until(
+    SystemClock::time_point until_at_least) {
   std::unique_lock lock(native_type_.mutex);
   if (native_type_.condition.wait_until(
-          lock, deadline, [&] { return native_type_.count != 0; })) {
+          lock, until_at_least, [&] { return native_type_.count != 0; })) {
     native_type_.count = 0;
     return true;
   }
diff --git a/pw_sync_stl/counting_semaphore.cc b/pw_sync_stl/counting_semaphore.cc
index 95de84b..629d0dc 100644
--- a/pw_sync_stl/counting_semaphore.cc
+++ b/pw_sync_stl/counting_semaphore.cc
@@ -14,7 +14,7 @@
 
 #include "pw_sync/counting_semaphore.h"
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 
 using pw::chrono::SystemClock;
 
@@ -45,10 +45,11 @@
   return false;
 }
 
-bool CountingSemaphore::try_acquire_until(SystemClock::time_point deadline) {
+bool CountingSemaphore::try_acquire_until(
+    SystemClock::time_point until_at_least) {
   std::unique_lock lock(native_type_.mutex);
   if (native_type_.condition.wait_until(
-          lock, deadline, [&] { return native_type_.count != 0; })) {
+          lock, until_at_least, [&] { return native_type_.count != 0; })) {
     --native_type_.count;
     return true;
   }
diff --git a/pw_sync_stl/mutex.cc b/pw_sync_stl/mutex.cc
deleted file mode 100644
index 177dc06..0000000
--- a/pw_sync_stl/mutex.cc
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_sync/mutex.h"
-
-#include "pw_assert/check.h"
-#include "pw_sync_stl/mutex_native.h"
-
-namespace pw::sync {
-
-Mutex::~Mutex() {
-  PW_CHECK(!native_type_.locked, "Mutex was locked when it went out of scope");
-}
-
-namespace backend {
-
-void NativeMutex::SetLockedState(bool new_state) {
-  PW_CHECK_UINT_NE(locked,
-                   new_state,
-                   "Called %slock(), but the mutex is already in that state",
-                   new_state ? "" : "un");
-  locked = new_state;
-}
-
-}  // namespace backend
-}  // namespace pw::sync
diff --git a/pw_sync_stl/public/pw_sync_stl/binary_semaphore_inline.h b/pw_sync_stl/public/pw_sync_stl/binary_semaphore_inline.h
index 1123e73..ca5a1e3 100644
--- a/pw_sync_stl/public/pw_sync_stl/binary_semaphore_inline.h
+++ b/pw_sync_stl/public/pw_sync_stl/binary_semaphore_inline.h
@@ -24,14 +24,14 @@
 inline BinarySemaphore::~BinarySemaphore() {}
 
 inline bool BinarySemaphore::try_acquire_for(
-    chrono::SystemClock::duration timeout) {
+    chrono::SystemClock::duration for_at_least) {
   // Due to spurious condition variable wakeups we prefer not to use wait_for()
   // as we may grossly extend the effective deadline after a spruious wakeup.
   // Ergo we instead use the derived deadline which can be re-used many times
   // without shifting the effective deadline. For more details on spurious
   // wakeups and CVs on Windows and POSIX see:
   //   https://en.wikipedia.org/wiki/Spurious_wakeup
-  return try_acquire_until(chrono::SystemClock::TimePointAfterAtLeast(timeout));
+  return try_acquire_until(chrono::SystemClock::now() + for_at_least);
 }
 
 inline BinarySemaphore::native_handle_type BinarySemaphore::native_handle() {
diff --git a/pw_sync_stl/public/pw_sync_stl/counting_semaphore_inline.h b/pw_sync_stl/public/pw_sync_stl/counting_semaphore_inline.h
index 466b374..f29df4b 100644
--- a/pw_sync_stl/public/pw_sync_stl/counting_semaphore_inline.h
+++ b/pw_sync_stl/public/pw_sync_stl/counting_semaphore_inline.h
@@ -16,6 +16,8 @@
 #include "pw_chrono/system_clock.h"
 #include "pw_sync/counting_semaphore.h"
 
+using pw::chrono::SystemClock;
+
 namespace pw::sync {
 
 inline CountingSemaphore::CountingSemaphore()
@@ -24,10 +26,9 @@
 inline CountingSemaphore::~CountingSemaphore() {}
 
 inline bool CountingSemaphore::try_acquire_for(
-    pw::chrono::SystemClock::duration timeout) {
+    chrono::SystemClock::duration for_at_least) {
   // Due to spurious condition variable wakeups this cannot use wait_for().
-  return try_acquire_until(
-      pw::chrono::SystemClock::TimePointAfterAtLeast(timeout));
+  return try_acquire_until(chrono::SystemClock::now() + for_at_least);
 }
 
 inline CountingSemaphore::native_handle_type
diff --git a/pw_sync_stl/public/pw_sync_stl/mutex_inline.h b/pw_sync_stl/public/pw_sync_stl/mutex_inline.h
index 005d089..88548c4 100644
--- a/pw_sync_stl/public/pw_sync_stl/mutex_inline.h
+++ b/pw_sync_stl/public/pw_sync_stl/mutex_inline.h
@@ -19,27 +19,14 @@
 
 inline Mutex::Mutex() : native_type_() {}
 
-inline void Mutex::lock() {
-  native_handle().lock();
-  native_type_.SetLockedState(true);
-}
+inline Mutex::~Mutex() {}
 
-inline bool Mutex::try_lock() {
-  if (native_handle().try_lock()) {
-    native_type_.SetLockedState(true);
-    return true;
-  }
-  return false;
-}
+inline void Mutex::lock() { native_type_.lock(); }
 
-inline void Mutex::unlock() {
-  native_type_.SetLockedState(false);
-  native_handle().unlock();
-}
+inline bool Mutex::try_lock() { return native_type_.try_lock(); }
 
-// Return a std::timed_mutex instead of the customized NativeMutex class.
-inline Mutex::native_handle_type Mutex::native_handle() {
-  return native_type_.mutex;
-}
+inline void Mutex::unlock() { native_type_.unlock(); }
+
+inline Mutex::native_handle_type Mutex::native_handle() { return native_type_; }
 
 }  // namespace pw::sync
diff --git a/pw_sync_stl/public/pw_sync_stl/mutex_native.h b/pw_sync_stl/public/pw_sync_stl/mutex_native.h
index c90f44e..fb04d56 100644
--- a/pw_sync_stl/public/pw_sync_stl/mutex_native.h
+++ b/pw_sync_stl/public/pw_sync_stl/mutex_native.h
@@ -17,19 +17,7 @@
 
 namespace pw::sync::backend {
 
-// The NativeMutex class adds a flag that tracks whether the std::timed_mutex is
-// locked. The C++ standard states that misusing a mutex is undefined behavior,
-// so library implementations may simply ignore misuse. This ensures misuse hits
-// a PW_CHECK.
-struct NativeMutex {
-  // The locked state is tracked in a variable. This function asserts if the
-  // state is inconsistent (e.g. unlocking an unlocked mutex).
-  void SetLockedState(bool new_state);
-
-  std::timed_mutex mutex;
-  bool locked = false;
-};
-
+using NativeMutex = std::timed_mutex;
 using NativeMutexHandle = std::timed_mutex&;
 
 }  // namespace pw::sync::backend
diff --git a/pw_sync_stl/public/pw_sync_stl/timed_mutex_inline.h b/pw_sync_stl/public/pw_sync_stl/timed_mutex_inline.h
index 6ea86eb..5bfd340 100644
--- a/pw_sync_stl/public/pw_sync_stl/timed_mutex_inline.h
+++ b/pw_sync_stl/public/pw_sync_stl/timed_mutex_inline.h
@@ -18,21 +18,14 @@
 
 namespace pw::sync {
 
-inline bool TimedMutex::try_lock_for(chrono::SystemClock::duration timeout) {
-  if (native_handle().try_lock_for(timeout)) {
-    native_type().SetLockedState(true);
-    return true;
-  }
-  return false;
+inline bool TimedMutex::try_lock_for(
+    chrono::SystemClock::duration for_at_least) {
+  return native_handle().try_lock_for(for_at_least);
 }
 
 inline bool TimedMutex::try_lock_until(
-    chrono::SystemClock::time_point deadline) {
-  if (native_handle().try_lock_until(deadline)) {
-    native_type().SetLockedState(true);
-    return true;
-  }
-  return false;
+    chrono::SystemClock::time_point until_at_least) {
+  return native_handle().try_lock_until(until_at_least);
 }
 
 }  // namespace pw::sync
diff --git a/pw_sync_threadx/BUILD b/pw_sync_threadx/BUILD
new file mode 100644
index 0000000..b9129f9
--- /dev/null
+++ b/pw_sync_threadx/BUILD
@@ -0,0 +1,171 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "binary_semaphore_headers",
+    hdrs = [
+        "public/pw_sync_threadx/binary_semaphore_inline.h",
+        "public/pw_sync_threadx/binary_semaphore_native.h",
+        "public_overrides/pw_sync_backend/binary_semaphore_inline.h",
+        "public_overrides/pw_sync_backend/binary_semaphore_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        # TODO: This should depend on ThreadX but our third parties currently
+        # do not have Bazel support.
+        "//pw_chrono:system_clock",
+    ],
+)
+
+pw_cc_library(
+    name = "binary_semaphore",
+    srcs = [
+        "binary_semaphore.cc",
+    ],
+    deps = [
+        ":binary_semaphore_headers",
+        "//pw_chrono_threadx:system_clock_headers",
+        "//pw_interrupt:context",
+        "//pw_sync:binary_semaphore_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "counting_semaphore_headers",
+    hdrs = [
+        "public/pw_sync_threadx/counting_semaphore_inline.h",
+        "public/pw_sync_threadx/counting_semaphore_native.h",
+        "public_overrides/pw_sync_backend/counting_semaphore_inline.h",
+        "public_overrides/pw_sync_backend/counting_semaphore_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        # TODO: This should depend on ThreadX but our third parties currently
+        # do not have Bazel support.
+        "//pw_chrono:system_clock",
+    ],
+)
+
+pw_cc_library(
+    name = "counting_semaphore",
+    srcs = [
+        "counting_semaphore.cc",
+    ],
+    deps = [
+        ":counting_semaphore_headers",
+        "//pw_chrono_threadx:system_clock_headers",
+        "//pw_interrupt:context",
+        "//pw_sync:counting_semaphore_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "mutex_headers",
+    hdrs = [
+        "public/pw_sync_threadx/mutex_inline.h",
+        "public/pw_sync_threadx/mutex_native.h",
+        "public_overrides/pw_sync_backend/mutex_inline.h",
+        "public_overrides/pw_sync_backend/mutex_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        # TODO: This should depend on ThreadX but our third parties currently
+        # do not have Bazel support.
+        "//pw_sync:mutex_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "mutex",
+    deps = [
+        ":mutex_headers",
+        "//pw_sync:mutex_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "timed_mutex_headers",
+    hdrs = [
+        "public/pw_sync_threadx/timed_mutex_inline.h",
+        "public_overrides/pw_sync_backend/timed_mutex_inline.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        # TODO: This should depend on ThreadX but our third parties currently
+        # do not have Bazel support.
+        "//pw_chrono:system_clock",
+        "//pw_sync:timed_mutex_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "timed_mutex",
+    srcs = [
+        "timed_mutex.cc",
+    ],
+    deps = [
+        ":timed_mutex_headers",
+        "//pw_chrono_threadx:system_clock_headers",
+        "//pw_interrupt:context",
+        "//pw_sync:timed_mutex_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "interrupt_spin_lock_headers",
+    hdrs = [
+        "public/pw_sync_threadx/interrupt_spin_lock_inline.h",
+        "public/pw_sync_threadx/interrupt_spin_lock_native.h",
+        "public_overrides/pw_sync_backend/interrupt_spin_lock_inline.h",
+        "public_overrides/pw_sync_backend/interrupt_spin_lock_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    # TODO: This should depend on ThreadX but our third parties currently
+    # do not have Bazel support.
+)
+
+pw_cc_library(
+    name = "interrupt_spin_lock",
+    srcs = [
+        "interrupt_spin_lock.cc",
+    ],
+    deps = [
+        ":interrupt_spin_lock_headers",
+        "//pw_sync:interrupt_spin_lock_facade",
+    ],
+)
diff --git a/pw_sync_threadx/BUILD.bazel b/pw_sync_threadx/BUILD.bazel
deleted file mode 100644
index cb78f9e..0000000
--- a/pw_sync_threadx/BUILD.bazel
+++ /dev/null
@@ -1,202 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "binary_semaphore_headers",
-    hdrs = [
-        "public/pw_sync_threadx/binary_semaphore_inline.h",
-        "public/pw_sync_threadx/binary_semaphore_native.h",
-        "public_overrides/pw_sync_backend/binary_semaphore_inline.h",
-        "public_overrides/pw_sync_backend/binary_semaphore_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:threadx",
-    ],
-    deps = [
-        # TODO(pwbug/317): This should depend on ThreadX but our third parties
-        # currently do not have Bazel support.
-        "//pw_chrono:system_clock",
-    ],
-)
-
-pw_cc_library(
-    name = "binary_semaphore",
-    srcs = [
-        "binary_semaphore.cc",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:threadx",
-    ],
-    deps = [
-        ":binary_semaphore_headers",
-        "//pw_chrono_threadx:system_clock_headers",
-        "//pw_interrupt:context",
-        "//pw_sync:binary_semaphore_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "counting_semaphore_headers",
-    hdrs = [
-        "public/pw_sync_threadx/counting_semaphore_inline.h",
-        "public/pw_sync_threadx/counting_semaphore_native.h",
-        "public_overrides/pw_sync_backend/counting_semaphore_inline.h",
-        "public_overrides/pw_sync_backend/counting_semaphore_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:threadx",
-    ],
-    deps = [
-        # TODO(pwbug/317): This should depend on ThreadX but our third parties
-        # currently do not have Bazel support.
-        # do not have Bazel support.
-        "//pw_chrono:system_clock",
-    ],
-)
-
-pw_cc_library(
-    name = "counting_semaphore",
-    srcs = [
-        "counting_semaphore.cc",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:threadx",
-    ],
-    deps = [
-        ":counting_semaphore_headers",
-        "//pw_chrono_threadx:system_clock_headers",
-        "//pw_interrupt:context",
-        "//pw_sync:counting_semaphore_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "mutex_headers",
-    hdrs = [
-        "public/pw_sync_threadx/mutex_inline.h",
-        "public/pw_sync_threadx/mutex_native.h",
-        "public_overrides/pw_sync_backend/mutex_inline.h",
-        "public_overrides/pw_sync_backend/mutex_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:threadx",
-    ],
-    deps = [
-        # TODO(pwbug/317): This should depend on ThreadX but our third parties currently
-        # do not have Bazel support.
-        "//pw_sync:mutex_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "mutex",
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:threadx",
-    ],
-    deps = [
-        ":mutex_headers",
-        "//pw_sync:mutex_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "timed_mutex_headers",
-    hdrs = [
-        "public/pw_sync_threadx/timed_mutex_inline.h",
-        "public_overrides/pw_sync_backend/timed_mutex_inline.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:threadx",
-    ],
-    deps = [
-        # TODO(pwbug/317): This should depend on ThreadX but our third parties currently
-        # do not have Bazel support.
-        "//pw_chrono:system_clock",
-        "//pw_sync:timed_mutex_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "timed_mutex",
-    srcs = [
-        "timed_mutex.cc",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:threadx",
-    ],
-    deps = [
-        ":timed_mutex_headers",
-        "//pw_chrono_threadx:system_clock_headers",
-        "//pw_interrupt:context",
-        "//pw_sync:timed_mutex_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "interrupt_spin_lock_headers",
-    hdrs = [
-        "public/pw_sync_threadx/interrupt_spin_lock_inline.h",
-        "public/pw_sync_threadx/interrupt_spin_lock_native.h",
-        "public_overrides/pw_sync_backend/interrupt_spin_lock_inline.h",
-        "public_overrides/pw_sync_backend/interrupt_spin_lock_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    # TODO(pwbug/317): This should depend on ThreadX but our third parties currently
-    # do not have Bazel support.
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:threadx",
-    ],
-)
-
-pw_cc_library(
-    name = "interrupt_spin_lock",
-    srcs = [
-        "interrupt_spin_lock.cc",
-    ],
-    target_compatible_with = [
-        "//pw_build/constraints/rtos:threadx",
-    ],
-    deps = [
-        ":interrupt_spin_lock_headers",
-        "//pw_sync:interrupt_spin_lock_facade",
-    ],
-)
diff --git a/pw_sync_threadx/BUILD.gn b/pw_sync_threadx/BUILD.gn
index 524c478..b02d757 100644
--- a/pw_sync_threadx/BUILD.gn
+++ b/pw_sync_threadx/BUILD.gn
@@ -14,7 +14,6 @@
 
 import("//build_overrides/pigweed.gni")
 
-import("$dir_pw_build/error.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_chrono/backend.gni")
 import("$dir_pw_docgen/docs.gni")
@@ -30,15 +29,6 @@
   visibility = [ ":*" ]
 }
 
-pw_build_assert("check_system_clock_backend") {
-  condition =
-      pw_sync_OVERRIDE_SYSTEM_CLOCK_BACKEND_CHECK ||
-      pw_chrono_SYSTEM_CLOCK_BACKEND == "$dir_pw_chrono_threadx:system_clock"
-  message = "The ThreadX pw::sync::BinarySemaphore backend only works with " +
-            "the ThreadX pw::chrono::SystemClock backend."
-  visibility = [ ":*" ]
-}
-
 # This target provides the backend for pw::sync::Mutex.
 pw_source_set("mutex") {
   public_configs = [
@@ -80,10 +70,15 @@
     ]
     sources = [ "binary_semaphore.cc" ]
     deps = [
-      ":check_system_clock_backend",
       "$dir_pw_sync:binary_semaphore.facade",
       pw_chrono_SYSTEM_CLOCK_BACKEND,
     ]
+    assert(
+        pw_sync_OVERRIDE_SYSTEM_CLOCK_BACKEND_CHECK ||
+            pw_chrono_SYSTEM_CLOCK_BACKEND ==
+                "$dir_pw_chrono_threadx:system_clock",
+        "The ThreadX pw::sync::BinarySemaphore backend only works with the " +
+            "ThreadX pw::chrono::SystemClock backend.")
   }
 
   # This target provides the backend for pw::sync::CountingSemaphore.
@@ -106,10 +101,14 @@
     ]
     sources = [ "counting_semaphore.cc" ]
     deps = [
-      ":check_system_clock_backend",
       "$dir_pw_sync:counting_semaphore.facade",
       pw_chrono_SYSTEM_CLOCK_BACKEND,
     ]
+    assert(pw_sync_OVERRIDE_SYSTEM_CLOCK_BACKEND_CHECK ||
+               pw_chrono_SYSTEM_CLOCK_BACKEND ==
+                   "$dir_pw_chrono_threadx:system_clock",
+           "The ThreadX pw::sync::CountingSemaphore backend only works with " +
+               "the ThreadX pw::chrono::SystemClock backend.")
   }
 
   # This target provides the backend for pw::sync::TimedMutex.
@@ -128,12 +127,16 @@
     ]
     sources = [ "timed_mutex.cc" ]
     deps = [
-      ":check_system_clock_backend",
       "$dir_pw_assert",
       "$dir_pw_interrupt:context",
       "$dir_pw_third_party/threadx",
       pw_chrono_SYSTEM_CLOCK_BACKEND,
     ]
+    assert(pw_sync_OVERRIDE_SYSTEM_CLOCK_BACKEND_CHECK ||
+               pw_chrono_SYSTEM_CLOCK_BACKEND ==
+                   "$dir_pw_chrono_threadx:system_clock",
+           "The ThreadX pw::sync::Mutex backend only works with the ThreadX " +
+               "pw::chrono::SystemClock backend.")
   }
 }
 
@@ -154,7 +157,6 @@
   sources = [ "interrupt_spin_lock.cc" ]
   deps = [
     "$dir_pw_assert",
-    "$dir_pw_interrupt:context",
     "$dir_pw_sync:interrupt_spin_lock.facade",
   ]
 }
diff --git a/pw_sync_threadx/OWNERS b/pw_sync_threadx/OWNERS
deleted file mode 100644
index e9a6c58..0000000
--- a/pw_sync_threadx/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-ewout@google.com
-hepler@google.com
diff --git a/pw_sync_threadx/binary_semaphore.cc b/pw_sync_threadx/binary_semaphore.cc
index 18c3ad7..5e16fa9 100644
--- a/pw_sync_threadx/binary_semaphore.cc
+++ b/pw_sync_threadx/binary_semaphore.cc
@@ -16,7 +16,7 @@
 
 #include <algorithm>
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_chrono/system_clock.h"
 #include "pw_chrono_threadx/system_clock_constants.h"
 #include "pw_interrupt/context.h"
@@ -26,23 +26,20 @@
 
 namespace pw::sync {
 
-bool BinarySemaphore::try_acquire_for(SystemClock::duration timeout) {
+bool BinarySemaphore::try_acquire_for(SystemClock::duration for_at_least) {
   // Enforce the pw::sync::BinarySemaphore IRQ contract.
   PW_DCHECK(!interrupt::InInterruptContext());
 
   // Use non-blocking try_acquire for negative and zero length durations.
-  if (timeout <= SystemClock::duration::zero()) {
+  if (for_at_least <= SystemClock::duration::zero()) {
     return try_acquire();
   }
 
-  // In case the timeout is too long for us to express through the native
-  // ThreadX API, we repeatedly wait with shorter durations. Note that on a tick
-  // based kernel we cannot tell how far along we are on the current tick, ergo
-  // we add one whole tick to the final duration. However, this also means that
-  // the loop must ensure that timeout + 1 is less than the max timeout.
+  // On a tick based kernel we cannot tell how far along we are on the current
+  // tick, ergo we add one whole tick to the final duration.
   constexpr SystemClock::duration kMaxTimeoutMinusOne =
       pw::chrono::threadx::kMaxTimeout - SystemClock::duration(1);
-  while (timeout > kMaxTimeoutMinusOne) {
+  while (for_at_least > kMaxTimeoutMinusOne) {
     const UINT result = tx_semaphore_get(
         &native_type_, static_cast<ULONG>(kMaxTimeoutMinusOne.count()));
     if (result != TX_NO_INSTANCE) {
@@ -50,12 +47,10 @@
       PW_CHECK_UINT_EQ(TX_SUCCESS, result);
       return true;
     }
-    timeout -= kMaxTimeoutMinusOne;
+    for_at_least -= kMaxTimeoutMinusOne;
   }
-  // On a tick based kernel we cannot tell how far along we are on the current
-  // tick, ergo we add one whole tick to the final duration.
-  const UINT result =
-      tx_semaphore_get(&native_type_, static_cast<ULONG>(timeout.count() + 1));
+  const UINT result = tx_semaphore_get(
+      &native_type_, static_cast<ULONG>(for_at_least.count() + 1));
   if (result == TX_NO_INSTANCE) {
     return false;  // We timed out, there's still no token.
   }
diff --git a/pw_sync_threadx/counting_semaphore.cc b/pw_sync_threadx/counting_semaphore.cc
index 1ac4c0a..e2e5c86 100644
--- a/pw_sync_threadx/counting_semaphore.cc
+++ b/pw_sync_threadx/counting_semaphore.cc
@@ -16,7 +16,7 @@
 
 #include <algorithm>
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_chrono/system_clock.h"
 #include "pw_chrono_threadx/system_clock_constants.h"
 #include "pw_interrupt/context.h"
@@ -26,23 +26,20 @@
 
 namespace pw::sync {
 
-bool CountingSemaphore::try_acquire_for(SystemClock::duration timeout) {
+bool CountingSemaphore::try_acquire_for(SystemClock::duration for_at_least) {
   // Enforce the pw::sync::CountingSemaphore IRQ contract.
   PW_DCHECK(!interrupt::InInterruptContext());
 
   // Use non-blocking try_acquire for negative and zero length durations.
-  if (timeout <= SystemClock::duration::zero()) {
+  if (for_at_least <= SystemClock::duration::zero()) {
     return try_acquire();
   }
 
-  // In case the timeout is too long for us to express through the native
-  // ThreadX API, we repeatedly wait with shorter durations. Note that on a tick
-  // based kernel we cannot tell how far along we are on the current tick, ergo
-  // we add one whole tick to the final duration. However, this also means that
-  // the loop must ensure that timeout + 1 is less than the max timeout.
+  // On a tick based kernel we cannot tell how far along we are on the current
+  // tick, ergo we add one whole tick to the final duration.
   constexpr SystemClock::duration kMaxTimeoutMinusOne =
       pw::chrono::threadx::kMaxTimeout - SystemClock::duration(1);
-  while (timeout > kMaxTimeoutMinusOne) {
+  while (for_at_least > kMaxTimeoutMinusOne) {
     const UINT result = tx_semaphore_get(
         &native_type_, static_cast<ULONG>(kMaxTimeoutMinusOne.count()));
     if (result != TX_NO_INSTANCE) {
@@ -50,12 +47,10 @@
       PW_CHECK_UINT_EQ(TX_SUCCESS, result);
       return true;
     }
-    timeout -= kMaxTimeoutMinusOne;
+    for_at_least -= kMaxTimeoutMinusOne;
   }
-  // On a tick based kernel we cannot tell how far along we are on the current
-  // tick, ergo we add one whole tick to the final duration.
-  const UINT result =
-      tx_semaphore_get(&native_type_, static_cast<ULONG>(timeout.count() + 1));
+  const UINT result = tx_semaphore_get(
+      &native_type_, static_cast<ULONG>(for_at_least.count() + 1));
   if (result == TX_NO_INSTANCE) {
     return false;  // We timed out, there's still no token.
   }
diff --git a/pw_sync_threadx/docs.rst b/pw_sync_threadx/docs.rst
index ec17537..b6fac08 100644
--- a/pw_sync_threadx/docs.rst
+++ b/pw_sync_threadx/docs.rst
@@ -1,63 +1,13 @@
 .. _module-pw_sync_threadx:
 
-===============
+---------------
 pw_sync_threadx
-===============
-This is a set of backends for pw_sync based on ThreadX.
+---------------
+This is a set of backends for pw_sync based on ThreadX. It is not ready for use,
+and is under construction.
 
 It is possible, if necessary, to use pw_sync_threadx without using the Pigweed
 provided pw_chrono_threadx in case the ThreadX time API (``tx_time_get()``)) is
 not available (i.e. ``TX_NO_TIMER`` is set). You are responsible for ensuring
 that the chrono backend provided has counts which match the ThreadX tick based
 API.
-
---------------------------------
-Critical Section Lock Primitives
---------------------------------
-
-Mutex & TimedMutex
-==================
-The ThreadX backend for the Mutex and TimedMutex use ``TX_MUTEX`` as the
-underlying type. It is created using ``tx_mutex_create`` as part of the
-constructors and cleaned up using ``tx_mutex_delete`` in the destructors.
-
-InterruptSpinLock
-=================
-The ThreadX backend for InterruptSpinLock is backed by an ``enum class`` and
-two ``UINT`` which permits these objects to detect accidental recursive locking
-and unlocking contexts.
-
-This object uses ``tx_interrupt_control`` to enable critical sections. In
-addition, ``tx_thread_preemption_change`` is used to prevent accidental thread
-context switches while the InterruptSpinLock is held by a thread.
-
-.. Warning::
-  This backend does not support SMP yet as there's no internal lock to spin on.
-
---------------------
-Signaling Primitives
---------------------
-
-ThreadNotification & TimedThreadNotification
-============================================
-The native ThreadX API does cover direct thread signaling and ergo we recommend
-using the binary semaphore backends for ThreadNotifications:
-- ``pw_sync:binary_semaphore_thread_notification_backend``
-- ``pw_sync:binary_semaphore_timed_thread_notification_backend``
-
-Background Information
-----------------------
-Although one may be tempted to use ``tx_thread_sleep`` and
-``tx_thread_wait_abort`` to implement direct thread notifications with ThreadX,
-this unfortunately cannot work. Between the blocking thread setting its
-``TX_THREAD*`` handle and actually executing ``tx_thread_sleep`` there will
-always exist a race condition. Another thread and/or interrupt may attempt
-to invoke ``tx_thread_wait_abort`` before the blocking thread has executed
-``tx_thread_sleep`` meaning the wait abort would fail.
-
-BinarySemaphore & CountingSemaphore
-===================================
-The ThreadX backends for the BinarySemaphore and CountingSemaphore use
-``TX_SEMAPHORE`` as the underlying type. It is created using
-``tx_semaphore_create`` as part of the constructor and cleaned up using
-``tx_semaphore_delete`` in the destructor.
diff --git a/pw_sync_threadx/interrupt_spin_lock.cc b/pw_sync_threadx/interrupt_spin_lock.cc
index 8968a6e..96d5c54 100644
--- a/pw_sync_threadx/interrupt_spin_lock.cc
+++ b/pw_sync_threadx/interrupt_spin_lock.cc
@@ -14,77 +14,43 @@
 
 #include "pw_sync/interrupt_spin_lock.h"
 
-#include "pw_assert/check.h"
-#include "pw_interrupt/context.h"
+#include "pw_assert/assert.h"
 #include "tx_api.h"
 
 namespace pw::sync {
-namespace {
-
-using State = backend::NativeInterruptSpinLock::State;
-
-}  // namespace
 
 void InterruptSpinLock::lock() {
   // In order to be pw::sync::InterruptSpinLock compliant, mask the interrupts
   // before attempting to grab the internal spin lock.
   native_type_.saved_interrupt_mask = tx_interrupt_control(TX_INT_DISABLE);
 
-  const bool in_interrupt = interrupt::InInterruptContext();
-
-  // Disable thread switching to ensure kernel APIs cannot switch to other
-  // threads which could then end up deadlocking recursively on this same lock.
-  if (!in_interrupt) {
-    TX_THREAD* current_thread = tx_thread_identify();
-    // During init, i.e. tx_application_define, there may not be a thread yet.
-    if (current_thread != nullptr) {
-      // Disable thread switching by raising the preemption threshold to the
-      // highest priority value of 0.
-      UINT preemption_success = tx_thread_preemption_change(
-          tx_thread_identify(), 0, &native_type_.saved_preemption_threshold);
-      PW_DCHECK_UINT_EQ(
-          TX_SUCCESS, preemption_success, "Failed to disable thread switching");
-    }
-  }
-
   // This implementation is not set up to support SMP, meaning we cannot
   // deadlock here due to the global interrupt lock, so we crash on recursion
   // on a specific spinlock instead.
-  PW_DCHECK_UINT_EQ(native_type_.state,
-                    State::kUnlocked,
-                    "Recursive InterruptSpinLock::lock() detected");
+  PW_CHECK(!native_type_.locked.load(std::memory_order_relaxed),
+           "Recursive InterruptSpinLock::lock() detected");
 
-  native_type_.state =
-      in_interrupt ? State::kLockedFromInterrupt : State::kLockedFromThread;
+  native_type_.locked.store(true, std::memory_order_relaxed);
+}
+
+bool InterruptSpinLock::try_lock() {
+  // In order to be pw::sync::InterruptSpinLock compliant, mask the interrupts
+  // before attempting to grab the internal spin lock.
+  UINT saved_interrupt_mask = tx_interrupt_control(TX_INT_DISABLE);
+
+  if (native_type_.locked.load(std::memory_order_relaxed)) {
+    // Already locked, restore interrupts and bail out.
+    tx_interrupt_control(saved_interrupt_mask);
+    return false;
+  }
+
+  native_type_.saved_interrupt_mask = saved_interrupt_mask;
+  native_type_.locked.store(true, std::memory_order_relaxed);
+  return true;
 }
 
 void InterruptSpinLock::unlock() {
-  const bool in_interrupt = interrupt::InInterruptContext();
-
-  const State expected_state =
-      in_interrupt ? State::kLockedFromInterrupt : State::kLockedFromThread;
-  PW_CHECK_UINT_EQ(
-      native_type_.state,
-      expected_state,
-      "InterruptSpinLock::unlock() was called from a different context "
-      "compared to the lock()");
-
-  native_type_.state = State::kUnlocked;
-
-  if (!in_interrupt) {
-    TX_THREAD* current_thread = tx_thread_identify();
-    // During init, i.e. tx_application_define, there may not be a thread yet.
-    if (current_thread != nullptr) {
-      // Restore thread switching.
-      UINT unused = 0;
-      UINT preemption_success =
-          tx_thread_preemption_change(tx_thread_identify(),
-                                      native_type_.saved_preemption_threshold,
-                                      &unused);
-      PW_DCHECK_UINT_EQ(
-          TX_SUCCESS, preemption_success, "Failed to restore thread switching");
-    }
-  }
+  native_type_.locked.store(false, std::memory_order_relaxed);
   tx_interrupt_control(native_type_.saved_interrupt_mask);
 }
 
diff --git a/pw_sync_threadx/public/pw_sync_threadx/binary_semaphore_inline.h b/pw_sync_threadx/public/pw_sync_threadx/binary_semaphore_inline.h
index 2027563..844a6e6 100644
--- a/pw_sync_threadx/public/pw_sync_threadx/binary_semaphore_inline.h
+++ b/pw_sync_threadx/public/pw_sync_threadx/binary_semaphore_inline.h
@@ -13,7 +13,7 @@
 // the License.
 #pragma once
 
-#include "pw_assert/assert.h"
+#include "pw_assert/light.h"
 #include "pw_chrono/system_clock.h"
 #include "pw_interrupt/context.h"
 #include "pw_sync/binary_semaphore.h"
@@ -60,10 +60,10 @@
 }
 
 inline bool BinarySemaphore::try_acquire_until(
-    chrono::SystemClock::time_point deadline) {
+    chrono::SystemClock::time_point until_at_least) {
   // Note that if this deadline is in the future, it will get rounded up by
   // one whole tick due to how try_acquire_for is implemented.
-  return try_acquire_for(deadline - chrono::SystemClock::now());
+  return try_acquire_for(until_at_least - chrono::SystemClock::now());
 }
 
 inline BinarySemaphore::native_handle_type BinarySemaphore::native_handle() {
diff --git a/pw_sync_threadx/public/pw_sync_threadx/counting_semaphore_inline.h b/pw_sync_threadx/public/pw_sync_threadx/counting_semaphore_inline.h
index 30c6e58..7125e01 100644
--- a/pw_sync_threadx/public/pw_sync_threadx/counting_semaphore_inline.h
+++ b/pw_sync_threadx/public/pw_sync_threadx/counting_semaphore_inline.h
@@ -15,7 +15,7 @@
 
 #include <algorithm>
 
-#include "pw_assert/assert.h"
+#include "pw_assert/light.h"
 #include "pw_chrono/system_clock.h"
 #include "pw_interrupt/context.h"
 #include "pw_sync/counting_semaphore.h"
@@ -61,10 +61,10 @@
 }
 
 inline bool CountingSemaphore::try_acquire_until(
-    chrono::SystemClock::time_point deadline) {
+    chrono::SystemClock::time_point until_at_least) {
   // Note that if this deadline is in the future, it will get rounded up by
   // one whole tick due to how try_acquire_for is implemented.
-  return try_acquire_for(deadline - chrono::SystemClock::now());
+  return try_acquire_for(until_at_least - chrono::SystemClock::now());
 }
 
 inline CountingSemaphore::native_handle_type
diff --git a/pw_sync_threadx/public/pw_sync_threadx/interrupt_spin_lock_inline.h b/pw_sync_threadx/public/pw_sync_threadx/interrupt_spin_lock_inline.h
index d2d8889..8bbd4cf 100644
--- a/pw_sync_threadx/public/pw_sync_threadx/interrupt_spin_lock_inline.h
+++ b/pw_sync_threadx/public/pw_sync_threadx/interrupt_spin_lock_inline.h
@@ -18,20 +18,11 @@
 namespace pw::sync {
 
 constexpr InterruptSpinLock::InterruptSpinLock()
-    : native_type_{.state = backend::NativeInterruptSpinLock::State::kUnlocked,
-                   .saved_interrupt_mask = 0,
-                   .saved_preemption_threshold = 0} {}
+    : native_type_{.locked{false}, .saved_interrupt_mask = 0} {}
 
 inline InterruptSpinLock::native_handle_type
 InterruptSpinLock::native_handle() {
   return native_type_;
 }
 
-inline bool InterruptSpinLock::try_lock() {
-  // This backend does not support SMP and on a uniprocesor we cannot actually
-  // fail to acquire the lock. Recursive locking is already detected by lock().
-  lock();
-  return true;
-}
-
 }  // namespace pw::sync
diff --git a/pw_sync_threadx/public/pw_sync_threadx/interrupt_spin_lock_native.h b/pw_sync_threadx/public/pw_sync_threadx/interrupt_spin_lock_native.h
index a522eb3..cf6ceb8 100644
--- a/pw_sync_threadx/public/pw_sync_threadx/interrupt_spin_lock_native.h
+++ b/pw_sync_threadx/public/pw_sync_threadx/interrupt_spin_lock_native.h
@@ -13,24 +13,15 @@
 // the License.
 #pragma once
 
+#include <atomic>
+
 #include "tx_api.h"
 
 namespace pw::sync::backend {
 
-// This currently does not support SMP as there's no actual lock to spin on
-// internally, see the pw_sync_threadx docs for more details.
-#ifdef TX_SMP_CORE_ID
-#error "This backend does not support SMP versions of ThreadX yet!"
-#endif  // defined(TX_SMP_CORE_ID)
 struct NativeInterruptSpinLock {
-  enum class State {
-    kUnlocked = 0,  // This must be 0 to ensure it is bss eligible.
-    kLockedFromInterrupt = 1,
-    kLockedFromThread = 2,
-  };
-  State state;  // Used to detect recursion and interrupt context escapes.
+  std::atomic<bool> locked;  // Used to detect recursion.
   UINT saved_interrupt_mask;
-  UINT saved_preemption_threshold;
 };
 using NativeInterruptSpinLockHandle = NativeInterruptSpinLock&;
 
diff --git a/pw_sync_threadx/public/pw_sync_threadx/mutex_inline.h b/pw_sync_threadx/public/pw_sync_threadx/mutex_inline.h
index 680c8ee..564b24e 100644
--- a/pw_sync_threadx/public/pw_sync_threadx/mutex_inline.h
+++ b/pw_sync_threadx/public/pw_sync_threadx/mutex_inline.h
@@ -13,7 +13,7 @@
 // the License.
 #pragma once
 
-#include "pw_assert/assert.h"
+#include "pw_assert/light.h"
 #include "pw_interrupt/context.h"
 #include "pw_sync/mutex.h"
 #include "tx_api.h"
@@ -23,14 +23,6 @@
 
 inline constexpr char kMutexName[] = "pw::Mutex";
 
-// Precondition: The mutex must be held when calling this!
-inline bool NotRecursivelyHeld(TX_MUTEX& mutex) {
-  // Don't use tx_mutex_info_get which goes into a critical section to read
-  // this count. Given that this is only called while the mutex is held, nothing
-  // can mutate this count and ergo we access the control block directly.
-  return mutex.tx_mutex_ownership_count == 1;
-}
-
 }  // namespace backend
 
 inline Mutex::Mutex() : native_type_() {
@@ -45,29 +37,24 @@
 
 inline void Mutex::lock() {
   // Enforce the pw::sync::Mutex IRQ contract.
-  PW_DASSERT(!interrupt::InInterruptContext());
+  PW_ASSERT(!interrupt::InInterruptContext());
   PW_ASSERT(tx_mutex_get(&native_type_, TX_WAIT_FOREVER) == TX_SUCCESS);
-
-  // Recursive locking is not supported.
-  PW_DASSERT(backend::NotRecursivelyHeld(native_type_));
 }
 
 inline bool Mutex::try_lock() {
   // Enforce the pw::sync::Mutex IRQ contract.
-  PW_DASSERT(!interrupt::InInterruptContext());
+  PW_ASSERT(!interrupt::InInterruptContext());
   const UINT result = tx_mutex_get(&native_type_, TX_NO_WAIT);
   if (result == TX_NOT_AVAILABLE) {
     return false;
   }
   PW_ASSERT(result == TX_SUCCESS);
-  // Recursive locking is not supported.
-  PW_DASSERT(backend::NotRecursivelyHeld(native_type_));
   return true;
 }
 
 inline void Mutex::unlock() {
   // Enforce the pw::sync::Mutex IRQ contract.
-  PW_DASSERT(!interrupt::InInterruptContext());
+  PW_ASSERT(!interrupt::InInterruptContext());
   PW_ASSERT(tx_mutex_put(&native_type_) == TX_SUCCESS);
 }
 
diff --git a/pw_sync_threadx/public/pw_sync_threadx/timed_mutex_inline.h b/pw_sync_threadx/public/pw_sync_threadx/timed_mutex_inline.h
index 92f5ef2..8644046 100644
--- a/pw_sync_threadx/public/pw_sync_threadx/timed_mutex_inline.h
+++ b/pw_sync_threadx/public/pw_sync_threadx/timed_mutex_inline.h
@@ -18,11 +18,11 @@
 
 namespace pw::sync {
 
-inline bool TimedMutex::try_lock_until(
-    chrono::SystemClock::time_point deadline) {
+inline bool Mutex::try_lock_until(
+    chrono::SystemClock::time_point until_at_least) {
   // Note that if this deadline is in the future, it will get rounded up by
   // one whole tick due to how try_lock_for is implemented.
-  return try_lock_for(deadline - chrono::SystemClock::now());
+  return try_lock_for(until_at_least - chrono::SystemClock::now());
 }
 
 }  // namespace pw::sync
diff --git a/pw_sync_threadx/timed_mutex.cc b/pw_sync_threadx/timed_mutex.cc
index 785f228..92b14ed 100644
--- a/pw_sync_threadx/timed_mutex.cc
+++ b/pw_sync_threadx/timed_mutex.cc
@@ -16,7 +16,7 @@
 
 #include <algorithm>
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_chrono/system_clock.h"
 #include "pw_chrono_threadx/system_clock_constants.h"
 #include "pw_interrupt/context.h"
@@ -26,42 +26,34 @@
 
 namespace pw::sync {
 
-bool TimedMutex::try_lock_for(SystemClock::duration timeout) {
+bool TimedMutex::try_lock_for(SystemClock::duration for_at_least) {
   // Enforce the pw::sync::TimedMutex IRQ contract.
   PW_DCHECK(!interrupt::InInterruptContext());
 
   // Use non-blocking try_lock for negative or zero length durations.
-  if (timeout <= SystemClock::duration::zero()) {
+  if (for_at_least <= SystemClock::duration::zero()) {
     return try_lock();
   }
 
-  // In case the timeout is too long for us to express through the native
-  // ThreadX API, we repeatedly wait with shorter durations. Note that on a tick
-  // based kernel we cannot tell how far along we are on the current tick, ergo
-  // we add one whole tick to the final duration. However, this also means that
-  // the loop must ensure that timeout + 1 is less than the max timeout.
+  // On a tick based kernel we cannot tell how far along we are on the current
+  // tick, ergo we add one whole tick to the final duration.
   constexpr SystemClock::duration kMaxTimeoutMinusOne =
       pw::chrono::threadx::kMaxTimeout - SystemClock::duration(1);
-  while (timeout > kMaxTimeoutMinusOne) {
+  while (for_at_least > kMaxTimeoutMinusOne) {
     const UINT result = tx_mutex_get(
-        &native_handle(), static_cast<ULONG>(kMaxTimeoutMinusOne.count()));
+        &native_type_, static_cast<ULONG>(kMaxTimeoutMinusOne.count()));
     if (result != TX_NOT_AVAILABLE) {
       PW_CHECK_UINT_EQ(TX_SUCCESS, result);
       return true;
     }
-    timeout -= kMaxTimeoutMinusOne;
+    for_at_least -= kMaxTimeoutMinusOne;
   }
-  // On a tick based kernel we cannot tell how far along we are on the current
-  // tick, ergo we add one whole tick to the final duration.
   const UINT result =
-      tx_mutex_get(&native_handle(), static_cast<ULONG>(timeout.count() + 1));
+      tx_mutex_get(&native_type_, static_cast<ULONG>(for_at_least.count() + 1));
   if (result == TX_NOT_AVAILABLE) {
     return false;
   }
   PW_CHECK_UINT_EQ(TX_SUCCESS, result);
-
-  PW_DCHECK(backend::NotRecursivelyHeld(native_handle()),
-            "Recursive locking is not supported");
   return true;
 }
 
diff --git a/pw_sync_zephyr/BUILD.gn b/pw_sync_zephyr/BUILD.gn
deleted file mode 100644
index 9a6699a..0000000
--- a/pw_sync_zephyr/BUILD.gn
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_docgen/docs.gni")
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
diff --git a/pw_sync_zephyr/CMakeLists.txt b/pw_sync_zephyr/CMakeLists.txt
deleted file mode 100644
index b035dc5..0000000
--- a/pw_sync_zephyr/CMakeLists.txt
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-
-if(CONFIG_PIGWEED_SYNC_MUTEX)
-  pw_add_module_library(pw_sync_zephyr.mutex_backend
-    IMPLEMENTS_FACADES
-      pw_sync.mutex
-  )
-  pw_set_backend(pw_sync.mutex pw_sync_zephyr.mutex_backend)
-  zephyr_link_libraries(pw_sync_zephyr.mutex_backend)
-  zephyr_link_interface(pw_sync_zephyr.mutex_backend)
-endif()
-
-if(CONFIG_PIGWEED_SYNC_BINARY_SEMAPHORE)
-  pw_add_module_library(pw_sync_zephyr.binary_semaphore_backend
-    IMPLEMENTS_FACADES
-      pw_sync.binary_semaphore
-    SOURCES
-      binary_semaphore.cc
-  )
-  pw_set_backend(pw_sync.binary_semaphore pw_sync_zephyr.binary_semaphore_backend)
-  zephyr_link_libraries(pw_sync_zephyr.binary_semaphore_backend)
-  zephyr_link_interface(pw_sync_zephyr.binary_semaphore_backend)
-endif()
diff --git a/pw_sync_zephyr/Kconfig b/pw_sync_zephyr/Kconfig
deleted file mode 100644
index 2ca2e7d..0000000
--- a/pw_sync_zephyr/Kconfig
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-menuconfig PIGWEED_SYNC
-    bool "Enable Pigweed's sync module (pw_sync)"
-
-if PIGWEED_SYNC
-
-config PIGWEED_SYNC_MUTEX
-    bool "Enable Pigweed mutex library (pw_sync.mutex)"
-    select PIGWEED_CHRONO_SYSTEM_CLOCK
-    select PIGWEED_POLYFILL
-    select PIGWEED_PREPROCESSOR
-
-config PIGWEED_SYNC_BINARY_SEMAPHORE
-    bool "Enable Pigweed binary semaphore library (pw_sync.binary_semaphore)"
-    select PIGWEED_CHRONO_SYSTEM_CLOCK
-    select PIGWEED_PREPROCESSOR
-
-endif # PIGWEED_SYNC
diff --git a/pw_sync_zephyr/OWNERS b/pw_sync_zephyr/OWNERS
deleted file mode 100644
index 2230ec2..0000000
--- a/pw_sync_zephyr/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-peress@google.com
diff --git a/pw_sync_zephyr/binary_semaphore.cc b/pw_sync_zephyr/binary_semaphore.cc
deleted file mode 100644
index 0828137..0000000
--- a/pw_sync_zephyr/binary_semaphore.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_sync/binary_semaphore.h"
-
-#include <kernel.h>
-
-namespace pw::sync {
-
-bool BinarySemaphore::try_acquire_for(chrono::SystemClock::duration timeout) {
-  // Use non-blocking try_acquire for negative and zero length durations.
-  if (timeout <= chrono::SystemClock::duration::zero()) {
-    return try_acquire();
-  }
-
-#ifndef CONFIG_TIMEOUT_64BIT
-  constexpr chrono::SystemClock::duration kMaxTimeoutMinusOne =
-      chrono::SystemClock::duration(K_FOREVER.ticks - 1);
-
-  while (timeout > kMaxTimeoutMinusOne) {
-    if (k_sem_take(&native_type_, K_TICKS(kMaxTimeoutMinusOne.count())) == 0) {
-      return true;
-    }
-    timeout -= kMaxTimeoutMinusOne;
-  }
-#endif  // CONFIG_TIMEOUT_64BIT
-
-  return k_sem_take(&native_type_, K_TICKS(timeout.count())) == 0;
-}
-
-}  // namespace pw::sync
diff --git a/pw_sync_zephyr/docs.rst b/pw_sync_zephyr/docs.rst
deleted file mode 100644
index 705c144..0000000
--- a/pw_sync_zephyr/docs.rst
+++ /dev/null
@@ -1,16 +0,0 @@
-.. _module-pw_sync_zephyr:
-
-================
-pw_sync_zephyr
-================
-
---------
-Overview
---------
-This sync backend implements the ``pw_sync`` facade. To enable, set
-``CONFIG_PIGWEED_SYNC=y``. After that, specific submodules can be enabled via
-the Kconfig menu.
-
-* ``pw_sync.mutex`` can be enabled via ``CONFIG_PIGWEED_SYNC_MUTEX``.
-* ``pw_sync.binary_semaphore`` can be enabled via
-  ``CONFIG_PIGWEED_SYNC_BINARY_SEMAPHORE``.
diff --git a/pw_sync_zephyr/public/pw_sync_zephyr/binary_semaphore_inline.h b/pw_sync_zephyr/public/pw_sync_zephyr/binary_semaphore_inline.h
deleted file mode 100644
index 9afe7ce..0000000
--- a/pw_sync_zephyr/public/pw_sync_zephyr/binary_semaphore_inline.h
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <kernel.h>
-
-#include "pw_assert/assert.h"
-#include "pw_chrono/system_clock.h"
-#include "pw_interrupt/context.h"
-#include "pw_sync/binary_semaphore.h"
-
-namespace pw::sync {
-
-inline BinarySemaphore::BinarySemaphore() : native_type_() {
-  constexpr unsigned int kInitialCount = 0;
-  k_sem_init(&native_type_, kInitialCount, backend::kBinarySemaphoreMaxValue);
-}
-
-inline BinarySemaphore::~BinarySemaphore() = default;
-
-inline void BinarySemaphore::release() {
-  PW_DASSERT(!interrupt::InInterruptContext());
-  k_sem_give(&native_type_);
-}
-
-inline void BinarySemaphore::acquire() {
-  PW_DASSERT(!interrupt::InInterruptContext());
-  PW_ASSERT(k_sem_take(&native_type_, K_FOREVER) == 0);
-}
-
-inline bool BinarySemaphore::try_acquire() noexcept {
-  return k_sem_take(&native_type_, K_NO_WAIT) == 0;
-}
-
-inline bool BinarySemaphore::try_acquire_until(
-    chrono::SystemClock::time_point deadline) {
-  // Note that if this deadline is in the future, it will get rounded up by
-  // one whole tick due to how try_acquire_for is implemented.
-  return try_acquire_for(deadline - chrono::SystemClock::now());
-}
-
-inline BinarySemaphore::native_handle_type BinarySemaphore::native_handle() {
-  return native_type_;
-}
-
-}  // namespace pw::sync
diff --git a/pw_sync_zephyr/public/pw_sync_zephyr/binary_semaphore_native.h b/pw_sync_zephyr/public/pw_sync_zephyr/binary_semaphore_native.h
deleted file mode 100644
index 946a934..0000000
--- a/pw_sync_zephyr/public/pw_sync_zephyr/binary_semaphore_native.h
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <kernel.h>
-
-namespace pw::sync::backend {
-
-using NativeBinarySemaphore = struct k_sem;
-using NativeBinarySemaphoreHandle = NativeBinarySemaphore&;
-
-inline constexpr ptrdiff_t kBinarySemaphoreMaxValue = 1;
-
-}  // namespace pw::sync::backend
diff --git a/pw_sync_zephyr/public/pw_sync_zephyr/mutex_inline.h b/pw_sync_zephyr/public/pw_sync_zephyr/mutex_inline.h
deleted file mode 100644
index cbc9772..0000000
--- a/pw_sync_zephyr/public/pw_sync_zephyr/mutex_inline.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <kernel.h>
-
-#include "pw_assert/assert.h"
-#include "pw_interrupt/context.h"
-#include "pw_sync/mutex.h"
-
-namespace pw::sync {
-
-inline Mutex::Mutex() : native_type_() { k_mutex_init(&native_type_); }
-
-inline Mutex::~Mutex() = default;
-
-inline void Mutex::lock() {
-  PW_DASSERT(!interrupt::InInterruptContext());
-  k_mutex_lock(&native_type_, K_FOREVER);
-  PW_DASSERT(native_type_.lock_count == 1);
-}
-
-inline bool Mutex::try_lock() {
-  PW_DASSERT(!interrupt::InInterruptContext());
-
-  bool result = k_mutex_lock(&native_type_, K_NO_WAIT) == 0;
-  PW_DASSERT(native_type_.lock_count <= 1);
-
-  return result;
-}
-
-inline void Mutex::unlock() {
-  PW_DASSERT(!interrupt::InInterruptContext());
-  PW_ASSERT(k_mutex_unlock(&native_type_) == 0);
-}
-
-inline Mutex::native_handle_type Mutex::native_handle() { return native_type_; }
-
-}  // namespace pw::sync
diff --git a/pw_sync_zephyr/public/pw_sync_zephyr/mutex_native.h b/pw_sync_zephyr/public/pw_sync_zephyr/mutex_native.h
deleted file mode 100644
index e1099e9..0000000
--- a/pw_sync_zephyr/public/pw_sync_zephyr/mutex_native.h
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <kernel.h>
-
-namespace pw::sync::backend {
-
-using NativeMutex = struct k_mutex;
-using NativeMutexHandle = NativeMutex&;
-
-}  // namespace pw::sync::backend
diff --git a/pw_sync_zephyr/public_overrides/pw_sync_backend/binary_semaphore_inline.h b/pw_sync_zephyr/public_overrides/pw_sync_backend/binary_semaphore_inline.h
deleted file mode 100644
index 73cd194..0000000
--- a/pw_sync_zephyr/public_overrides/pw_sync_backend/binary_semaphore_inline.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_sync_zephyr/binary_semaphore_inline.h"
diff --git a/pw_sync_zephyr/public_overrides/pw_sync_backend/binary_semaphore_native.h b/pw_sync_zephyr/public_overrides/pw_sync_backend/binary_semaphore_native.h
deleted file mode 100644
index 8cf9cbd..0000000
--- a/pw_sync_zephyr/public_overrides/pw_sync_backend/binary_semaphore_native.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_sync_zephyr/binary_semaphore_native.h"
diff --git a/pw_sync_zephyr/public_overrides/pw_sync_backend/mutex_inline.h b/pw_sync_zephyr/public_overrides/pw_sync_backend/mutex_inline.h
deleted file mode 100644
index 1bcdb2a..0000000
--- a/pw_sync_zephyr/public_overrides/pw_sync_backend/mutex_inline.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_sync_zephyr/mutex_inline.h"
diff --git a/pw_sync_zephyr/public_overrides/pw_sync_backend/mutex_native.h b/pw_sync_zephyr/public_overrides/pw_sync_backend/mutex_native.h
deleted file mode 100644
index f02bbb7..0000000
--- a/pw_sync_zephyr/public_overrides/pw_sync_backend/mutex_native.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_sync_zephyr/mutex_native.h"
diff --git a/pw_sys_io/BUILD b/pw_sys_io/BUILD
new file mode 100644
index 0000000..11e8201
--- /dev/null
+++ b/pw_sys_io/BUILD
@@ -0,0 +1,55 @@
+# Copyright 2019 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])
+
+pw_cc_library(
+    name = "facade",
+    hdrs = ["public/pw_sys_io/sys_io.h"],
+    includes = ["public"],
+    deps = [
+        "//pw_span",
+        "//pw_status",
+    ],
+)
+
+pw_cc_library(
+    name = "default_putget_bytes",
+    srcs = ["sys_io.cc"],
+    deps = [
+        ":facade",
+        "//pw_span",
+        "//pw_status",
+    ],
+)
+
+pw_cc_library(
+    name = "pw_sys_io",
+    hdrs = ["public/pw_sys_io/sys_io.h"],
+    deps = [
+        ":facade",
+        "//pw_span",
+        "//pw_status",
+        # For now, hard-code to depend on stdio until bazel build is updated
+        # to support multiple target configurations.
+        "//pw_sys_io_stdio",
+    ],
+)
diff --git a/pw_sys_io/BUILD.bazel b/pw_sys_io/BUILD.bazel
deleted file mode 100644
index cf1ecc3..0000000
--- a/pw_sys_io/BUILD.bazel
+++ /dev/null
@@ -1,64 +0,0 @@
-# Copyright 2019 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_facade",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_facade(
-    name = "facade",
-    hdrs = ["public/pw_sys_io/sys_io.h"],
-    includes = ["public"],
-    deps = [
-        "//pw_span",
-        "//pw_status",
-    ],
-)
-
-pw_cc_library(
-    name = "default_putget_bytes",
-    srcs = ["sys_io.cc"],
-    deps = [
-        ":facade",
-        "//pw_span",
-        "//pw_status",
-    ],
-)
-
-pw_cc_library(
-    name = "pw_sys_io",
-    hdrs = ["public/pw_sys_io/sys_io.h"],
-    deps = [
-        ":facade",
-        "//pw_span",
-        "//pw_status",
-        "@pigweed_config//:pw_sys_io_backend",
-    ],
-)
-
-pw_cc_library(
-    name = "backend_multiplexer",
-    visibility = ["@pigweed_config//:__pkg__"],
-    deps = select({
-        "//pw_build/constraints/chipset:stm32f429": ["@pigweed//pw_sys_io_baremetal_stm32f429"],
-        "//pw_build/constraints/chipset:lm3s6965evb": ["@pigweed//pw_sys_io_baremetal_lm3s6965evb"],
-        "//conditions:default": ["@pigweed//pw_sys_io_stdio"],
-    }),
-)
diff --git a/pw_sys_io/BUILD.gn b/pw_sys_io/BUILD.gn
index a15e999..47feb43 100644
--- a/pw_sys_io/BUILD.gn
+++ b/pw_sys_io/BUILD.gn
@@ -19,13 +19,13 @@
 import("$dir_pw_docgen/docs.gni")
 import("backend.gni")
 
-config("public_include_path") {
+config("default_config") {
   include_dirs = [ "public" ]
 }
 
 pw_facade("pw_sys_io") {
   backend = pw_sys_io_BACKEND
-  public_configs = [ ":public_include_path" ]
+  public_configs = [ ":default_config" ]
   public_deps = [ dir_pw_status ]
   public = [ "public/pw_sys_io/sys_io.h" ]
 }
diff --git a/pw_sys_io/OWNERS b/pw_sys_io/OWNERS
deleted file mode 100644
index 8773962..0000000
--- a/pw_sys_io/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-amontanez@google.com
-hepler@google.com
diff --git a/pw_sys_io/docs.rst b/pw_sys_io/docs.rst
index eaf60ba..3a1a6c4 100644
--- a/pw_sys_io/docs.rst
+++ b/pw_sys_io/docs.rst
@@ -30,7 +30,7 @@
 =====
 This module requires relatively minimal setup:
 
-  1. Choose a ``pw_sys_io`` backend, or write one yourself.
+  1. Chose a ``pw_sys_io`` backend, or write one yourself.
   2. If using GN build, Specify the ``pw_sys_io_BACKEND`` GN build arg to point
      the library that provides a ``pw_sys_io`` backend.
 
diff --git a/pw_sys_io_arduino/BUILD b/pw_sys_io_arduino/BUILD
new file mode 100644
index 0000000..3735a73
--- /dev/null
+++ b/pw_sys_io_arduino/BUILD
@@ -0,0 +1,33 @@
+# Copyright 2019 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_sys_io_arduino",
+    srcs = ["sys_io_arduino.cc"],
+    hdrs = ["public/pw_sys_io_arduino/init.h"],
+    deps = [
+        "//pw_boot_armv7m",
+        "//pw_preprocessor",
+        "//pw_sys_io",
+    ]
+)
diff --git a/pw_sys_io_arduino/BUILD.bazel b/pw_sys_io_arduino/BUILD.bazel
deleted file mode 100644
index c7da849..0000000
--- a/pw_sys_io_arduino/BUILD.bazel
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright 2019 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_sys_io_arduino",
-    srcs = ["sys_io_arduino.cc"],
-    hdrs = ["public/pw_sys_io_arduino/init.h"],
-    deps = [
-        "//pw_preprocessor",
-        "//pw_sys_io",
-    ],
-)
diff --git a/pw_sys_io_arduino/OWNERS b/pw_sys_io_arduino/OWNERS
deleted file mode 100644
index 8773962..0000000
--- a/pw_sys_io_arduino/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-amontanez@google.com
-hepler@google.com
diff --git a/pw_sys_io_arduino/public/pw_sys_io_arduino/init.h b/pw_sys_io_arduino/public/pw_sys_io_arduino/init.h
index f496037..d4262c5 100644
--- a/pw_sys_io_arduino/public/pw_sys_io_arduino/init.h
+++ b/pw_sys_io_arduino/public/pw_sys_io_arduino/init.h
@@ -18,6 +18,6 @@
 PW_EXTERN_C_START
 
 // The actual implement of PreMainInit() in sys_io_BACKEND.
-void pw_sys_io_arduino_Init();
+void pw_sys_io_Init();
 
 PW_EXTERN_C_END
diff --git a/pw_sys_io_arduino/sys_io_arduino.cc b/pw_sys_io_arduino/sys_io_arduino.cc
index 3a77800..4709ad5 100644
--- a/pw_sys_io_arduino/sys_io_arduino.cc
+++ b/pw_sys_io_arduino/sys_io_arduino.cc
@@ -20,7 +20,7 @@
 #include "pw_preprocessor/compiler.h"
 #include "pw_sys_io/sys_io.h"
 
-extern "C" void pw_sys_io_arduino_Init() { Serial.begin(115200); }
+extern "C" void pw_sys_io_Init() { Serial.begin(115200); }
 
 namespace pw::sys_io {
 
diff --git a/pw_sys_io_baremetal_lm3s6965evb/BUILD b/pw_sys_io_baremetal_lm3s6965evb/BUILD
new file mode 100644
index 0000000..85fe01a
--- /dev/null
+++ b/pw_sys_io_baremetal_lm3s6965evb/BUILD
@@ -0,0 +1,33 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+		"pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_sys_io_baremetal_lm3s6965evb",
+    hdrs = ["public/pw_sys_io_baremetal_lm3s6965evb/init.h"],
+    srcs = ["sys_io_baremetal.cc"],
+		deps = [
+        "//pw_preprocessor",
+				"//pw_sys_io",
+		],
+)
+
diff --git a/pw_sys_io_baremetal_lm3s6965evb/BUILD.bazel b/pw_sys_io_baremetal_lm3s6965evb/BUILD.bazel
deleted file mode 100644
index b3ea1c2..0000000
--- a/pw_sys_io_baremetal_lm3s6965evb/BUILD.bazel
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_sys_io_baremetal_lm3s6965evb",
-    srcs = ["sys_io_baremetal.cc"],
-    hdrs = ["public/pw_sys_io_baremetal_lm3s6965evb/init.h"],
-    target_compatible_with = [
-        "//pw_build/constraints/chipset:lm3s6965evb",
-        "@platforms//os:none",
-    ],
-    deps = [
-        "//pw_boot_cortex_m:armv7m",
-        "//pw_preprocessor",
-        "//pw_sys_io:facade",
-    ],
-)
diff --git a/pw_sys_io_baremetal_lm3s6965evb/BUILD.gn b/pw_sys_io_baremetal_lm3s6965evb/BUILD.gn
index 1fa0ecf..5ff5e86 100644
--- a/pw_sys_io_baremetal_lm3s6965evb/BUILD.gn
+++ b/pw_sys_io_baremetal_lm3s6965evb/BUILD.gn
@@ -25,7 +25,7 @@
   public_configs = [ ":default_config" ]
   public = [ "public/pw_sys_io_baremetal_lm3s6965evb/init.h" ]
   public_deps = [
-    "$dir_pw_boot_cortex_m:armv7m",
+    "$dir_pw_boot_armv7m",
     "$dir_pw_preprocessor",
   ]
   deps = [
diff --git a/pw_sys_io_baremetal_lm3s6965evb/OWNERS b/pw_sys_io_baremetal_lm3s6965evb/OWNERS
deleted file mode 100644
index 8773962..0000000
--- a/pw_sys_io_baremetal_lm3s6965evb/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-amontanez@google.com
-hepler@google.com
diff --git a/pw_sys_io_baremetal_lm3s6965evb/public/pw_sys_io_baremetal_lm3s6965evb/init.h b/pw_sys_io_baremetal_lm3s6965evb/public/pw_sys_io_baremetal_lm3s6965evb/init.h
index e8dcaba..d4262c5 100644
--- a/pw_sys_io_baremetal_lm3s6965evb/public/pw_sys_io_baremetal_lm3s6965evb/init.h
+++ b/pw_sys_io_baremetal_lm3s6965evb/public/pw_sys_io_baremetal_lm3s6965evb/init.h
@@ -18,6 +18,6 @@
 PW_EXTERN_C_START
 
 // The actual implement of PreMainInit() in sys_io_BACKEND.
-void pw_sys_io_lm3s6965evb_Init();
+void pw_sys_io_Init();
 
 PW_EXTERN_C_END
diff --git a/pw_sys_io_baremetal_lm3s6965evb/sys_io_baremetal.cc b/pw_sys_io_baremetal_lm3s6965evb/sys_io_baremetal.cc
index 0964d0c..ca70d97 100644
--- a/pw_sys_io_baremetal_lm3s6965evb/sys_io_baremetal.cc
+++ b/pw_sys_io_baremetal_lm3s6965evb/sys_io_baremetal.cc
@@ -69,7 +69,7 @@
 
 }  // namespace
 
-extern "C" void pw_sys_io_lm3s6965evb_Init() {
+extern "C" void pw_sys_io_Init() {
   rcgc1 |= kRcgcUart0EnableMask;
   for (volatile int i = 0; i < 3; ++i) {
     // We must wait after enabling uart.
diff --git a/pw_sys_io_baremetal_stm32f429/BUILD b/pw_sys_io_baremetal_stm32f429/BUILD
new file mode 100644
index 0000000..80b0280
--- /dev/null
+++ b/pw_sys_io_baremetal_stm32f429/BUILD
@@ -0,0 +1,36 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_sys_io_baremetal_stm32f429",
+    srcs = ["sys_io_baremetal.cc"],
+    hdrs = ["public/pw_sys_io_baremetal_stm32f429/init.h"],
+    target_compatible_with = [
+        "//pw_build/constraints/boards:stm32f429i-disc1",
+    ],
+    deps = [
+        "//pw_boot_armv7m",
+        "//pw_preprocessor",
+        "//pw_sys_io",
+    ],
+)
diff --git a/pw_sys_io_baremetal_stm32f429/BUILD.bazel b/pw_sys_io_baremetal_stm32f429/BUILD.bazel
deleted file mode 100644
index d392531..0000000
--- a/pw_sys_io_baremetal_stm32f429/BUILD.bazel
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_sys_io_baremetal_stm32f429",
-    srcs = ["sys_io_baremetal.cc"],
-    hdrs = ["public/pw_sys_io_baremetal_stm32f429/init.h"],
-    target_compatible_with = [
-        "//pw_build/constraints/chipset:stm32f429",
-        "@platforms//os:none",
-    ],
-    deps = [
-        "//pw_boot_cortex_m:armv7m",
-        "//pw_preprocessor",
-        "//pw_sys_io",
-    ],
-)
diff --git a/pw_sys_io_baremetal_stm32f429/BUILD.gn b/pw_sys_io_baremetal_stm32f429/BUILD.gn
index c9eb3b2..c611ccb 100644
--- a/pw_sys_io_baremetal_stm32f429/BUILD.gn
+++ b/pw_sys_io_baremetal_stm32f429/BUILD.gn
@@ -25,7 +25,7 @@
   public_configs = [ ":default_config" ]
   public = [ "public/pw_sys_io_baremetal_stm32f429/init.h" ]
   public_deps = [
-    "$dir_pw_boot_cortex_m:armv7m",
+    "$dir_pw_boot_armv7m",
     "$dir_pw_preprocessor",
   ]
   deps = [
diff --git a/pw_sys_io_baremetal_stm32f429/OWNERS b/pw_sys_io_baremetal_stm32f429/OWNERS
deleted file mode 100644
index 8773962..0000000
--- a/pw_sys_io_baremetal_stm32f429/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-amontanez@google.com
-hepler@google.com
diff --git a/pw_sys_io_baremetal_stm32f429/public/pw_sys_io_baremetal_stm32f429/init.h b/pw_sys_io_baremetal_stm32f429/public/pw_sys_io_baremetal_stm32f429/init.h
index cdff801..d4262c5 100644
--- a/pw_sys_io_baremetal_stm32f429/public/pw_sys_io_baremetal_stm32f429/init.h
+++ b/pw_sys_io_baremetal_stm32f429/public/pw_sys_io_baremetal_stm32f429/init.h
@@ -18,6 +18,6 @@
 PW_EXTERN_C_START
 
 // The actual implement of PreMainInit() in sys_io_BACKEND.
-void pw_sys_io_stm32f429_Init();
+void pw_sys_io_Init();
 
 PW_EXTERN_C_END
diff --git a/pw_sys_io_baremetal_stm32f429/sys_io_baremetal.cc b/pw_sys_io_baremetal_stm32f429/sys_io_baremetal.cc
index 60c4727..b5b1792 100644
--- a/pw_sys_io_baremetal_stm32f429/sys_io_baremetal.cc
+++ b/pw_sys_io_baremetal_stm32f429/sys_io_baremetal.cc
@@ -87,7 +87,7 @@
 
 // USART configuration flags for config1 register.
 // Note: a large number of configuration flags have been omitted as they default
-// to reasonable values and we don't need to change them.
+// to sane values and we don't need to change them.
 constexpr uint32_t kReceiveEnable = 0x1 << 2;
 constexpr uint32_t kTransmitEnable = 0x1 << 3;
 constexpr uint32_t kReadDataReady = 0x1u << 5;
@@ -130,7 +130,7 @@
 
 }  // namespace
 
-extern "C" void pw_sys_io_stm32f429_Init() {
+extern "C" void pw_sys_io_Init() {
   // Enable 'A' GIPO clocks.
   platform_rcc.ahb1_config |= kGpioAEnable;
 
diff --git a/pw_sys_io_emcraft_sf2/BUILD.bazel b/pw_sys_io_emcraft_sf2/BUILD.bazel
deleted file mode 100644
index fb10a78..0000000
--- a/pw_sys_io_emcraft_sf2/BUILD.bazel
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_sys_io_emcraft_sf2",
-    srcs = [
-        "pw_sys_io_emcraft_sf2_private/config.h",
-        "sys_io_emcraft_sf2.cc",
-    ],
-    hdrs = ["public/pw_sys_io_emcraft_sf2/init.h"],
-    target_compatible_with = [
-        "@platforms//os:none",
-    ],
-    deps = [
-        "//pw_boot_cortex_m:armv7m",
-        "//pw_preprocessor",
-        "//pw_sys_io",
-    ],
-)
diff --git a/pw_sys_io_emcraft_sf2/BUILD.gn b/pw_sys_io_emcraft_sf2/BUILD.gn
deleted file mode 100644
index 236722f..0000000
--- a/pw_sys_io_emcraft_sf2/BUILD.gn
+++ /dev/null
@@ -1,60 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/module_config.gni")
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_third_party/smartfusion_mss/mss.gni")
-
-declare_args() {
-  # The build target that overrides the default configuration options for this
-  # module. This should point to a source set that provides defines through a
-  # public config (which may -include a file or add defines directly).
-  pw_sys_io_emcraft_sf2_CONFIG = pw_build_DEFAULT_MODULE_CONFIG
-}
-
-config("public_includes") {
-  include_dirs = [ "public" ]
-}
-
-pw_source_set("config") {
-  public_deps = [ pw_sys_io_emcraft_sf2_CONFIG ]
-  public = [ "pw_sys_io_emcraft_sf2_private/config.h" ]
-  visibility = [ ":*" ]
-}
-
-pw_source_set("pw_sys_io_emcraft_sf2") {
-  public_configs = [ ":public_includes" ]
-  public_deps = [
-    "$dir_pw_preprocessor",
-    "$dir_pw_third_party/smartfusion_mss",
-  ]
-  if (dir_pw_third_party_smartfusion_mss != "") {
-    public_deps += [ "$dir_pw_third_party/smartfusion_mss" ]
-  }
-  public = [ "public/pw_sys_io_emcraft_sf2/init.h" ]
-  sources = [ "sys_io_emcraft_sf2.cc" ]
-  deps = [
-    ":config",
-    "$dir_pw_status",
-    "$dir_pw_sys_io:default_putget_bytes",
-    "$dir_pw_sys_io:facade",
-  ]
-}
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
diff --git a/pw_sys_io_emcraft_sf2/docs.rst b/pw_sys_io_emcraft_sf2/docs.rst
deleted file mode 100644
index 66ef6c9..0000000
--- a/pw_sys_io_emcraft_sf2/docs.rst
+++ /dev/null
@@ -1,44 +0,0 @@
-.. _module-pw_sys_io_emcraft_sf2:
-
----------------------
-pw_sys_io_emcraft_sf2
----------------------
-
-``pw_sys_io_emcraft_sf2`` implements the ``pw_sys_io`` facade over
-UART.
-
-The Emcraft SF2 sys IO backend provides a UART driver layer that allows
-applications built against the ``pw_sys_io`` interface to run on a
-SmartFusion/2 chip and do simple input/output via UART. However, this should
-work with all Smartfusion/2 variations.
-
-This backend allows you to configure which UART to use. The point of it is to
-provide bare-minimum platform code needed to do UART reads/writes.
-
-Setup
-=====
-This module requires relatively minimal setup:
-
-  1. Write code against the ``pw_sys_io`` facade.
-  2. Specify the ``dir_pw_sys_io_backend`` GN global variable to point to this
-     backend.
-  3. pw_sys_io_Init() provided by this module needs to be called in early boot
-     to get pw_sys_io into a working state.
-  4. Build an executable with a main() function using a toolchain that
-     supports Cortex-M3.
-
-.. note::
-  This module provides early firmware init, so it will conflict with other
-  modules that do any early device init.
-
-Module usage
-============
-After building an executable that utilizes this backend, flash the
-produced .elf binary to the development board. Then, using a serial
-communication terminal like minicom/screen (Linux/Mac) or TeraTerm (Windows),
-connect to the device at a baud rate of 57600 (8N1).
-
-Dependencies
-============
-  * ``pw_sys_io`` facade
-  * ``pw_preprocessor`` module
diff --git a/pw_sys_io_emcraft_sf2/public/pw_sys_io_emcraft_sf2/init.h b/pw_sys_io_emcraft_sf2/public/pw_sys_io_emcraft_sf2/init.h
deleted file mode 100644
index ccfd61e..0000000
--- a/pw_sys_io_emcraft_sf2/public/pw_sys_io_emcraft_sf2/init.h
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_preprocessor/util.h"
-
-PW_EXTERN_C_START
-
-// The actual implement of PreMainInit() in sys_io_BACKEND.
-void pw_sys_io_Init(void);
-
-PW_EXTERN_C_END
diff --git a/pw_sys_io_emcraft_sf2/pw_sys_io_emcraft_sf2_private/config.h b/pw_sys_io_emcraft_sf2/pw_sys_io_emcraft_sf2_private/config.h
deleted file mode 100644
index fb3db3a..0000000
--- a/pw_sys_io_emcraft_sf2/pw_sys_io_emcraft_sf2_private/config.h
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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
-
-// Defaults to USART1 on the SmartFusion2, but can be overridden.
-
-// The USART peripheral number to use. (1 for USART1, 2 for USART2, etc.)
-#ifndef PW_SYS_IO_EMCRAFT_SF2_USART_NUM
-#define PW_SYS_IO_EMCRAFT_SF2_USART_NUM 1
-#endif  // PW_SYS_IO_EMCRAFT_SF2_USART_NUM
diff --git a/pw_sys_io_emcraft_sf2/sys_io_emcraft_sf2.cc b/pw_sys_io_emcraft_sf2/sys_io_emcraft_sf2.cc
deleted file mode 100644
index d94a621..0000000
--- a/pw_sys_io_emcraft_sf2/sys_io_emcraft_sf2.cc
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cinttypes>
-
-#include "mss_gpio/mss_gpio.h"
-#include "mss_uart/mss_uart.h"
-#include "pw_preprocessor/concat.h"
-#include "pw_status/status.h"
-#include "pw_sys_io/sys_io.h"
-#include "pw_sys_io_emcraft_sf2_private/config.h"
-
-namespace {
-
-// LEDs GPIOs
-
-constexpr mss_gpio_id_t kDs3LedGPIO = MSS_GPIO_1;
-constexpr mss_gpio_id_t kDs4LEDGPIO = MSS_GPIO_2;
-constexpr uint32_t kDs3LedMask = MSS_GPIO_1_MASK;
-constexpr uint32_t kDs4LedMask = MSS_GPIO_2_MASK;
-
-constexpr uint32_t kReadDataReady = 0x1u;
-
-}  // namespace
-
-extern "C" void pw_sys_io_Init() {
-  // Configure MSS GPIOs.
-#if SF2_MSS_NO_BOOTLOADER
-  MSS_GPIO_init();
-#endif
-
-  MSS_GPIO_config(kDs3LedGPIO, MSS_GPIO_OUTPUT_MODE);
-  MSS_GPIO_config(kDs4LEDGPIO, MSS_GPIO_OUTPUT_MODE);
-  // Set LEDs to initial app state
-  MSS_GPIO_set_outputs(MSS_GPIO_get_outputs() | kDs4LedMask);
-
-  // Initialize the UART0 controller (57600, 8N1)
-  // Due to a HW eratta in SF2, we need to run at 57600 for
-  // in-system-programming mode. If we are not upgrading FPGA or flash then we
-  // can use a faster BAUD.
-  MSS_UART_init(
-      &g_mss_uart0,
-      MSS_UART_57600_BAUD,
-      MSS_UART_DATA_8_BITS | MSS_UART_NO_PARITY | MSS_UART_ONE_STOP_BIT);
-}
-
-// This whole implementation is very inefficient because it uses the synchronous
-// polling UART API and only reads / writes 1 byte at a time.
-namespace pw::sys_io {
-
-Status ReadByte(std::byte* dest) {
-  while (true) {
-    if (TryReadByte(dest).ok()) {
-      return OkStatus();
-    }
-  }
-}
-
-Status TryReadByte(std::byte* dest) {
-  if (!(g_mss_uart0.hw_reg->LSR & kReadDataReady)) {
-    return Status::Unavailable();
-  }
-
-  *dest = static_cast<std::byte>(g_mss_uart0.hw_reg->RBR);
-  return OkStatus();
-}
-
-Status WriteByte(std::byte b) {
-  // Wait for TX buffer to be empty. When the buffer is empty, we can write
-  // a value to be dumped out of UART.
-  const uint8_t pbuff = (uint8_t)b;
-
-  MSS_UART_polled_tx(&g_mss_uart0, &pbuff, 1);
-  return OkStatus();
-}
-
-// Writes a string using pw::sys_io, and add newline characters at the end.
-StatusWithSize WriteLine(const std::string_view& s) {
-  size_t chars_written = 0;
-  StatusWithSize result = WriteBytes(std::as_bytes(std::span(s)));
-  if (!result.ok()) {
-    return result;
-  }
-  chars_written += result.size();
-
-  // Write trailing newline.
-  result = WriteBytes(std::as_bytes(std::span("\r\n", 2)));
-  chars_written += result.size();
-
-  return StatusWithSize(OkStatus(), chars_written);
-}
-
-}  // namespace pw::sys_io
diff --git a/pw_sys_io_mcuxpresso/BUILD.bazel b/pw_sys_io_mcuxpresso/BUILD.bazel
deleted file mode 100644
index a91e095..0000000
--- a/pw_sys_io_mcuxpresso/BUILD.bazel
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_sys_io_mcuxpresso",
-    srcs = ["sys_io.cc"],
-    hdrs = ["public/pw_sys_io_mcuxpresso/init.h"],
-    deps = [
-        "//pw_preprocessor",
-        "//pw_sys_io",
-    ],
-)
diff --git a/pw_sys_io_mcuxpresso/BUILD.gn b/pw_sys_io_mcuxpresso/BUILD.gn
deleted file mode 100644
index 8c06717..0000000
--- a/pw_sys_io_mcuxpresso/BUILD.gn
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright 2019 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_third_party/mcuxpresso/mcuxpresso.gni")
-
-config("default_config") {
-  include_dirs = [ "public" ]
-}
-
-if (pw_third_party_mcuxpresso_SDK != "") {
-  pw_source_set("pw_sys_io_mcuxpresso") {
-    public_configs = [ ":default_config" ]
-    public = [ "public/pw_sys_io_mcuxpresso/init.h" ]
-    public_deps = [
-      "$dir_pw_boot",
-      "$dir_pw_preprocessor",
-    ]
-    deps = [
-      "$dir_pw_sys_io:default_putget_bytes",
-      "$dir_pw_sys_io:facade",
-      pw_third_party_mcuxpresso_SDK,
-    ]
-    sources = [ "sys_io.cc" ]
-  }
-}
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
diff --git a/pw_sys_io_mcuxpresso/OWNERS b/pw_sys_io_mcuxpresso/OWNERS
deleted file mode 100644
index 8773962..0000000
--- a/pw_sys_io_mcuxpresso/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-amontanez@google.com
-hepler@google.com
diff --git a/pw_sys_io_mcuxpresso/docs.rst b/pw_sys_io_mcuxpresso/docs.rst
deleted file mode 100644
index a237611..0000000
--- a/pw_sys_io_mcuxpresso/docs.rst
+++ /dev/null
@@ -1,37 +0,0 @@
-.. _module-pw_sys_io_mcuxpresso:
-
-====================
-pw_sys_io_mcuxpresso
-====================
-``pw_sys_io_mcuxpresso`` implements the ``pw_sys_io`` facade using the
-NXP MCUXpresso SDK.
-
-The implementation is based on the debug console component.
-
-Setup
-=====
-This module requires a little setup:
-
- 1. Use ``pw_build_mcuxpresso`` to create a ``pw_source_set`` for an
-    MCUXpresso SDK.
- 2. Include the debug console component in this SDK definition.
- 3. Specify the ``pw_third_party_mcuxpresso_SDK`` GN global variable to specify
-    the name of this source set.
- 4. Use a target that calls ``pw_sys_io_mcuxpresso_Init`` in
-    ``pw_boot_PreMainInit`` or similar.
-
-The name of the SDK source set must be set in the
-"pw_third_party_mcuxpresso_SDK" GN arg
-
-Configuration
-=============
-The configuration of the module can be adjusted via compile-time configuration
-of the MCUXpresso source set, see the
-:ref:`documentation <module-pw_build_mcuxpresso>` for more details.
-
-.. c:macro:: DEBUG_CONSOLE_TRANSFER_NON_BLOCKING
-
-  Whether the MCUXpresso debug console supports non-blocking transfers. The
-  default will depend on your SDK configuration.
-
-  Enabling this adds support for ``pw::sys_io::TryReadByte``.
diff --git a/pw_sys_io_mcuxpresso/public/pw_sys_io_mcuxpresso/init.h b/pw_sys_io_mcuxpresso/public/pw_sys_io_mcuxpresso/init.h
deleted file mode 100644
index 601b9d0..0000000
--- a/pw_sys_io_mcuxpresso/public/pw_sys_io_mcuxpresso/init.h
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_preprocessor/util.h"
-
-PW_EXTERN_C_START
-
-// The actual implement of PreMainInit() in sys_io_BACKEND.
-void pw_sys_io_mcuxpresso_Init(void);
-
-PW_EXTERN_C_END
diff --git a/pw_sys_io_mcuxpresso/sys_io.cc b/pw_sys_io_mcuxpresso/sys_io.cc
deleted file mode 100644
index 62b2c24..0000000
--- a/pw_sys_io_mcuxpresso/sys_io.cc
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_sys_io/sys_io.h"
-
-#include <cinttypes>
-#include <string_view>
-
-#include "board.h"
-#include "fsl_common.h"
-#include "fsl_debug_console.h"
-#include "pw_status/status.h"
-#include "pw_status/status_with_size.h"
-
-extern "C" void pw_sys_io_mcuxpresso_Init(void) { BOARD_InitDebugConsole(); }
-
-namespace pw::sys_io {
-
-// Wait for a byte to read on USART0. This blocks until a byte is read. This is
-// extremely inefficient as it requires the target to burn CPU cycles polling to
-// see if a byte is ready yet.
-Status ReadByte(std::byte* dest) {
-  *dest = static_cast<std::byte>(DbgConsole_Getchar());
-  return OkStatus();
-}
-
-#if DEBUG_CONSOLE_TRANSFER_NON_BLOCKING
-// Read a byte from USART0 if one is available.
-Status TryReadByte(std::byte* dest) {
-  char ch = 0;
-  status_t status = DbgConsole_TryGetchar(&ch);
-  if (status != kStatus_Success) {
-    return Status::Unavailable();
-  }
-
-  *dest = static_cast<std::byte>(ch);
-  return OkStatus();
-}
-#else   // !DEBUG_CONSOLE_TRANSFER_NONBLOCKING
-Status TryReadByte(std::byte* /*dest*/) { return Status::Unimplemented(); }
-#endif  // DEBUG_CONSOLE_TRANSFER_NONBLOCKING
-
-// Send a byte over USART0. Since this blocks on every byte, it's rather
-// inefficient. At the default baud rate of 115200, one byte blocks the CPU for
-// ~87 micro seconds. This means it takes only 10 bytes to block the CPU for
-// 1ms!
-Status WriteByte(std::byte b) {
-  int ch = static_cast<int>(b);
-  int len = DbgConsole_Putchar(ch);
-  return len == 1 ? OkStatus() : Status::Unknown();
-}
-
-// Writes a string using pw::sys_io, and add newline characters at the end.
-StatusWithSize WriteLine(const std::string_view& s) {
-  size_t chars_written = 0;
-  StatusWithSize result = WriteBytes(std::as_bytes(std::span(s)));
-  if (!result.ok()) {
-    return result;
-  }
-  chars_written += result.size();
-
-  // Write trailing newline.
-  result = WriteBytes(std::as_bytes(std::span("\r\n", 2)));
-  chars_written += result.size();
-
-  return StatusWithSize(result.status(), chars_written);
-}
-
-}  // namespace pw::sys_io
diff --git a/pw_sys_io_stdio/BUILD b/pw_sys_io_stdio/BUILD
new file mode 100644
index 0000000..333bd54
--- /dev/null
+++ b/pw_sys_io_stdio/BUILD
@@ -0,0 +1,31 @@
+# Copyright 2019 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_sys_io_stdio",
+    srcs = ["sys_io.cc"],
+    deps = [
+        "//pw_sys_io:default_putget_bytes",
+        "//pw_sys_io:facade",
+    ],
+)
diff --git a/pw_sys_io_stdio/BUILD.bazel b/pw_sys_io_stdio/BUILD.bazel
deleted file mode 100644
index 23d5103..0000000
--- a/pw_sys_io_stdio/BUILD.bazel
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright 2019 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-load(
-    "//pw_build:selects.bzl",
-    "TARGET_COMPATIBLE_WITH_HOST_SELECT",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_sys_io_stdio",
-    srcs = ["sys_io.cc"],
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-    deps = [
-        "//pw_sys_io:default_putget_bytes",
-        "//pw_sys_io:facade",
-    ],
-)
diff --git a/pw_sys_io_stdio/OWNERS b/pw_sys_io_stdio/OWNERS
deleted file mode 100644
index 8773962..0000000
--- a/pw_sys_io_stdio/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-amontanez@google.com
-hepler@google.com
diff --git a/pw_sys_io_stm32cube/BUILD.bazel b/pw_sys_io_stm32cube/BUILD.bazel
deleted file mode 100644
index 1146a5e..0000000
--- a/pw_sys_io_stm32cube/BUILD.bazel
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_sys_io_stm32cube",
-    srcs = [
-        "pw_sys_io_stm32cube_private/config.h",
-        "sys_io.cc",
-    ],
-    hdrs = ["public/pw_sys_io_stm32cube/init.h"],
-    deps = [
-        "//pw_preprocessor",
-        "//pw_status",
-        "//pw_sys_io",
-        "//third_party/stm32cube",
-    ],
-)
diff --git a/pw_sys_io_stm32cube/BUILD.gn b/pw_sys_io_stm32cube/BUILD.gn
deleted file mode 100644
index edf540a..0000000
--- a/pw_sys_io_stm32cube/BUILD.gn
+++ /dev/null
@@ -1,59 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/module_config.gni")
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_third_party/stm32cube/stm32cube.gni")
-
-declare_args() {
-  # The build target that overrides the default configuration options for this
-  # module. This should point to a source set that provides defines through a
-  # public config (which may -include a file or add defines directly).
-  pw_sys_io_stm32cube_CONFIG = pw_build_DEFAULT_MODULE_CONFIG
-}
-
-config("public_includes") {
-  include_dirs = [ "public" ]
-}
-
-pw_source_set("config") {
-  public_deps = [ pw_sys_io_stm32cube_CONFIG ]
-  public = [ "pw_sys_io_stm32cube_private/config.h" ]
-  visibility = [ ":*" ]
-}
-
-pw_source_set("pw_sys_io_stm32cube") {
-  public_configs = [ ":public_includes" ]
-  public_deps = [
-    "$dir_pw_preprocessor",
-    "$dir_pw_status",
-  ]
-  if (dir_pw_third_party_stm32cube != "") {
-    public_deps += [ "$dir_pw_third_party/stm32cube" ]
-  }
-  public = [ "public/pw_sys_io_stm32cube/init.h" ]
-  sources = [ "sys_io.cc" ]
-  deps = [
-    ":config",
-    "$dir_pw_sys_io:default_putget_bytes",
-    "$dir_pw_sys_io:facade",
-  ]
-}
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
diff --git a/pw_sys_io_stm32cube/OWNERS b/pw_sys_io_stm32cube/OWNERS
deleted file mode 100644
index 307b1de..0000000
--- a/pw_sys_io_stm32cube/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-amontanez@google.com
diff --git a/pw_sys_io_stm32cube/docs.rst b/pw_sys_io_stm32cube/docs.rst
deleted file mode 100644
index cd0fdbe..0000000
--- a/pw_sys_io_stm32cube/docs.rst
+++ /dev/null
@@ -1,74 +0,0 @@
-.. _module-pw_sys_io_stm32cube:
-
--------------------
-pw_sys_io_stm32cube
--------------------
-
-``pw_sys_io_stm32cube`` implements the ``pw_sys_io`` facade over UART using
-the STM32Cube HAL to support an assortment of boards from STMicroelectronics.
-
-The UART baud rate is fixed at 115200 (8N1).
-
-Setup
-=====
-This module requires relatively minimal setup:
-
-  1. Write code against the ``pw_sys_io`` facade.
-  2. Specify the ``dir_pw_sys_io_backend`` GN global variable to point to this
-     backend.
-  3. Call ``pw_sys_io_Init()`` during init so the UART is properly initialized
-     and configured.
-
-For devices other than the STM32F429I-DISC1, this module will need to be
-configured to use the appropriate GPIO pins and USART peripheral.
-
-Module Configuration Options
-============================
-The following configurations can be adjusted via compile-time configuration of
-this module, see the
-:ref:`module documentation <module-structure-compile-time-configuration>` for
-more details.
-
-.. c:macro:: PW_SYS_IO_STM32CUBE_USART_NUM
-
-  The USART peripheral number to use. (1 for USART1, 2 for USART2, etc.)
-
-.. c:macro:: PW_SYS_IO_STM32CUBE_GPIO_PORT
-
-  The port that the USART peripheral TX/RX pins are on. (e.g. to use A9/A10
-  pins for TX and RX, respectively, set this to A)
-
-  This defaults to 64 Bytes.
-
-.. c:macro:: PW_SYS_IO_STM32CUBE_GPIO_TX_PIN
-
-  The pin index to use for USART transmission within the port set by
-  ``PW_SYS_IO_STM32CUBE_GPIO_PORT``.
-
-.. c:macro:: PW_SYS_IO_STM32CUBE_GPIO_RX_PIN
-
-  The pin index to use for USART reception within the port set by
-  ``PW_SYS_IO_STM32CUBE_GPIO_PORT``.
-
-Module usage
-============
-After building an executable that utilizes this backend, flash the
-produced .elf binary to the development board. Then, using a serial
-communication terminal like minicom/screen (Linux/Mac) or TeraTerm (Windows),
-connect to the device at a baud rate of 115200 (8N1). If you're not using a
-STM32F429I-DISC1 development board, manually connect a USB-to-serial TTL adapter
-to pins ``PA9`` (MCU TX) and ``PA10`` (MCU RX), making sure to match logic
-levels (e.g. 3.3V versus 1.8V).
-
-Sample connection diagram
--------------------------
-
-.. code-block:: text
-
-  --USB Serial--+    +-----STM32F429 MCU-----
-                |    |
-             TX o--->o PA10/USART1_RX
-                |    |
-             RX o<---o PA9/USART1_TX
-                |    |
-  --------------+    +-----------------------
diff --git a/pw_sys_io_stm32cube/public/pw_sys_io_stm32cube/init.h b/pw_sys_io_stm32cube/public/pw_sys_io_stm32cube/init.h
deleted file mode 100644
index d2dd464..0000000
--- a/pw_sys_io_stm32cube/public/pw_sys_io_stm32cube/init.h
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_preprocessor/util.h"
-
-PW_EXTERN_C_START
-
-// The actual implement of PreMainInit() in sys_io_BACKEND.
-void pw_sys_io_Init(void);
-
-PW_EXTERN_C_END
diff --git a/pw_sys_io_stm32cube/pw_sys_io_stm32cube_private/config.h b/pw_sys_io_stm32cube/pw_sys_io_stm32cube_private/config.h
deleted file mode 100644
index a48d481..0000000
--- a/pw_sys_io_stm32cube/pw_sys_io_stm32cube_private/config.h
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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
-
-// Defaults to USART1 on the STM32F429xx, but can be overridden.
-
-// The USART peripheral number to use. (1 for USART1, 2 for USART2, etc.)
-#ifndef PW_SYS_IO_STM32CUBE_USART_NUM
-#define PW_SYS_IO_STM32CUBE_USART_NUM 1
-#endif  // PW_SYS_IO_STM32CUBE_USART_NUM
-
-// The port that the USART peripheral TX/RX pins are on. (e.g. to use A9/A10
-// pins for TX and RX, respectively, set this to A)
-#ifndef PW_SYS_IO_STM32CUBE_GPIO_PORT
-#define PW_SYS_IO_STM32CUBE_GPIO_PORT A
-#endif  // PW_SYS_IO_STM32CUBE_GPIO_PORT
-
-// The pin index to use for USART transmission within the port set by
-// PW_SYS_IO_STM32CUBE_GPIO_PORT.
-#ifndef PW_SYS_IO_STM32CUBE_GPIO_TX_PIN
-#define PW_SYS_IO_STM32CUBE_GPIO_TX_PIN 9
-#endif  // PW_SYS_IO_STM32CUBE_GPIO_TX_PIN
-
-// The pin index to use for USART reception within the port set by
-// PW_SYS_IO_STM32CUBE_GPIO_PORT.
-#ifndef PW_SYS_IO_STM32CUBE_GPIO_RX_PIN
-#define PW_SYS_IO_STM32CUBE_GPIO_RX_PIN 10
-#endif  // PW_SYS_IO_STM32CUBE_GPIO_RX_PIN
diff --git a/pw_sys_io_stm32cube/sys_io.cc b/pw_sys_io_stm32cube/sys_io.cc
deleted file mode 100644
index e9e6d7e..0000000
--- a/pw_sys_io_stm32cube/sys_io.cc
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_sys_io/sys_io.h"
-
-#include <cinttypes>
-
-#include "pw_preprocessor/concat.h"
-#include "pw_status/status.h"
-#include "pw_sys_io_stm32cube_private/config.h"
-#include "stm32cube/stm32cube.h"
-
-// These macros remap config options to the various STM32Cube HAL macro names.
-
-// USART_INSTANCE defined to USARTn, where n is the USART peripheral index.
-#define USART_INSTANCE PW_CONCAT(USART, PW_SYS_IO_STM32CUBE_USART_NUM)
-
-// USART_GPIO_ALTERNATE_FUNC defined to GPIO_AF7_USARTn, where n is the USART
-// peripheral index.
-#define USART_GPIO_ALTERNATE_FUNC \
-  PW_CONCAT(GPIO_AF7_USART, PW_SYS_IO_STM32CUBE_USART_NUM)
-
-// USART_GPIO_PORT defined to GPIOx, where x is the GPIO port letter that the
-// TX/RX pins are on.
-#define USART_GPIO_PORT PW_CONCAT(GPIO, PW_SYS_IO_STM32CUBE_GPIO_PORT)
-#define USART_GPIO_TX_PIN PW_CONCAT(GPIO_PIN_, PW_SYS_IO_STM32CUBE_GPIO_TX_PIN)
-#define USART_GPIO_RX_PIN PW_CONCAT(GPIO_PIN_, PW_SYS_IO_STM32CUBE_GPIO_RX_PIN)
-
-// USART_GPIO_PORT_ENABLE defined to __HAL_RCC_GPIOx_CLK_ENABLE, where x is the
-// GPIO port letter that the TX/RX pins are on.
-#define USART_GPIO_PORT_ENABLE \
-  PW_CONCAT(__HAL_RCC_GPIO, PW_SYS_IO_STM32CUBE_GPIO_PORT, _CLK_ENABLE)
-
-// USART_ENABLE defined to __HAL_RCC_USARTn_CLK_ENABLE, where n is the USART
-// peripheral index.
-#define USART_ENABLE \
-  PW_CONCAT(__HAL_RCC_USART, PW_SYS_IO_STM32CUBE_USART_NUM, _CLK_ENABLE)
-
-static UART_HandleTypeDef uart;
-
-extern "C" void pw_sys_io_Init() {
-  GPIO_InitTypeDef GPIO_InitStruct = {};
-
-  USART_ENABLE();
-  USART_GPIO_PORT_ENABLE();
-
-  GPIO_InitStruct.Pin = USART_GPIO_TX_PIN | USART_GPIO_RX_PIN;
-  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
-  GPIO_InitStruct.Pull = GPIO_NOPULL;
-  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
-  GPIO_InitStruct.Alternate = USART_GPIO_ALTERNATE_FUNC;
-  HAL_GPIO_Init(USART_GPIO_PORT, &GPIO_InitStruct);
-
-  uart.Instance = USART_INSTANCE;
-  uart.Init.BaudRate = 115200;
-  uart.Init.WordLength = UART_WORDLENGTH_8B;
-  uart.Init.StopBits = UART_STOPBITS_1;
-  uart.Init.Parity = UART_PARITY_NONE;
-  uart.Init.Mode = UART_MODE_TX_RX;
-  uart.Init.HwFlowCtl = UART_HWCONTROL_NONE;
-  uart.Init.OverSampling = UART_OVERSAMPLING_16;
-  HAL_UART_Init(&uart);
-}
-
-// This whole implementation is very inefficient because it uses the synchronous
-// polling UART API and only reads / writes 1 byte at a time.
-namespace pw::sys_io {
-Status ReadByte(std::byte* dest) {
-  if (HAL_UART_Receive(
-          &uart, reinterpret_cast<uint8_t*>(dest), 1, HAL_MAX_DELAY) !=
-      HAL_OK) {
-    return Status::ResourceExhausted();
-  }
-  return OkStatus();
-}
-
-Status TryReadByte(std::byte* dest) { return Status::Unimplemented(); }
-
-Status WriteByte(std::byte b) {
-  if (HAL_UART_Transmit(
-          &uart, reinterpret_cast<uint8_t*>(&b), 1, HAL_MAX_DELAY) != HAL_OK) {
-    return Status::ResourceExhausted();
-  }
-  return OkStatus();
-}
-
-// Writes a string using pw::sys_io, and add newline characters at the end.
-StatusWithSize WriteLine(const std::string_view& s) {
-  size_t chars_written = 0;
-  StatusWithSize result = WriteBytes(std::as_bytes(std::span(s)));
-  if (!result.ok()) {
-    return result;
-  }
-  chars_written += result.size();
-
-  // Write trailing newline.
-  result = WriteBytes(std::as_bytes(std::span("\r\n", 2)));
-  chars_written += result.size();
-
-  return StatusWithSize(OkStatus(), chars_written);
-}
-
-}  // namespace pw::sys_io
diff --git a/pw_sys_io_zephyr/BUILD.gn b/pw_sys_io_zephyr/BUILD.gn
deleted file mode 100644
index 9a6699a..0000000
--- a/pw_sys_io_zephyr/BUILD.gn
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_docgen/docs.gni")
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
diff --git a/pw_sys_io_zephyr/CMakeLists.txt b/pw_sys_io_zephyr/CMakeLists.txt
deleted file mode 100644
index 4d1955f..0000000
--- a/pw_sys_io_zephyr/CMakeLists.txt
+++ /dev/null
@@ -1,28 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-
-if(NOT CONFIG_PIGWEED_SYS_IO)
-  return()
-endif()
-
-pw_auto_add_simple_module(pw_sys_io_zephyr
-  IMPLEMENTS_FACADE
-    pw_sys_io
-  PUBLIC_DEPS
-    zephyr_interface
-)
-pw_set_backend(pw_sys_io pw_sys_io_zephyr)
-zephyr_link_libraries(pw_sys_io_zephyr)
diff --git a/pw_sys_io_zephyr/Kconfig b/pw_sys_io_zephyr/Kconfig
deleted file mode 100644
index 6fd75b7..0000000
--- a/pw_sys_io_zephyr/Kconfig
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-config PIGWEED_SYS_IO
-    bool "Enable the Zephyr system IO module"
-    select PIGWEED_SPAN
-    select PIGWEED_STATUS
-    help
-      The system I/O module uses the Zephyr console under the hood to perform
-      I/O operations.
-
-if PIGWEED_SYS_IO
-
-config PIGWEED_SYS_IO_INIT_PRIORITY
-    int "The initialization priority of the system I/O module"
-    default 1
-    help
-      The system I/O module uses the APPLICATION initialization level. Use this
-      config to set the priority.
-
-config PIGWEED_SYS_IO_USB
-    bool "Use the USB for I/O"
-
-endif # PIGWEED_SYS_IO
diff --git a/pw_sys_io_zephyr/OWNERS b/pw_sys_io_zephyr/OWNERS
deleted file mode 100644
index 2230ec2..0000000
--- a/pw_sys_io_zephyr/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-peress@google.com
diff --git a/pw_sys_io_zephyr/docs.rst b/pw_sys_io_zephyr/docs.rst
deleted file mode 100644
index 6ae4841..0000000
--- a/pw_sys_io_zephyr/docs.rst
+++ /dev/null
@@ -1,17 +0,0 @@
-.. _module-pw_sys_io_zephyr:
-
-================
-pw_sys_io_zephyr
-================
-
---------
-Overview
---------
-This sys I/O backend implements the ``pw_sys_io`` facade. To enable, set
-``CONFIG_PIGWEED_SYS_IO=y``. Once enabled, I/O operations will be routed to
-Zephyr's console. Additionally, it is possible to enable the USB subsystem
-by setting ``CONFIG_PIGWEED_SYS_IO_USB=y``.
-
-The I/O backend initializes during Zephyr's ``APPLICATION`` level and uses
-``CONFIG_PIGWEED_SYS_IO_INIT_PRIORITY`` to set the priority level. This config
-value defaults to 1, but is configurable via Kconfig.
diff --git a/pw_sys_io_zephyr/sys_io.cc b/pw_sys_io_zephyr/sys_io.cc
deleted file mode 100644
index fd9255e..0000000
--- a/pw_sys_io_zephyr/sys_io.cc
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_sys_io/sys_io.h"
-
-#include <console/console.h>
-#include <init.h>
-#include <usb/usb_device.h>
-#include <zephyr.h>
-
-static int sys_io_init(const struct device* dev) {
-  int err;
-  ARG_UNUSED(dev);
-
-  if (IS_ENABLED(CONFIG_PIGWEED_SYS_IO_USB)) {
-    err = usb_enable(nullptr);
-    if (err) {
-      return err;
-    }
-  }
-  err = console_init();
-  return err;
-}
-
-SYS_INIT(sys_io_init, APPLICATION, CONFIG_PIGWEED_SYS_IO_INIT_PRIORITY);
-
-namespace pw::sys_io {
-
-Status ReadByte(std::byte* dest) {
-  if (dest == nullptr) {
-    return Status::InvalidArgument();
-  }
-
-  const int c = console_getchar();
-  *dest = static_cast<std::byte>(c);
-
-  return c < 0 ? Status::FailedPrecondition() : OkStatus();
-}
-
-Status TryReadByte(std::byte* dest) {
-  if (dest == nullptr) {
-    return Status::InvalidArgument();
-  }
-
-  uint8_t byte;
-  int result = console_read(nullptr, &byte, 1);
-
-  if (result >= 0) {
-    *dest = static_cast<std::byte>(byte);
-    return OkStatus();
-  }
-
-  return Status::Unavailable();
-}
-
-Status WriteByte(std::byte b) {
-  return console_putchar(static_cast<char>(b)) < 0
-             ? Status::FailedPrecondition()
-             : OkStatus();
-}
-
-StatusWithSize WriteLine(const std::string_view& s) {
-  size_t chars_written = 0;
-  StatusWithSize size_result = WriteBytes(std::as_bytes(std::span(s)));
-  if (!size_result.ok()) {
-    return size_result;
-  }
-  chars_written += size_result.size();
-
-  // Write trailing newline character.
-  Status result = WriteByte(static_cast<std::byte>('\n'));
-  if (result.ok()) {
-    chars_written++;
-  }
-
-  return StatusWithSize(result, chars_written);
-}
-
-}  // namespace pw::sys_io
diff --git a/pw_system/BUILD.bazel b/pw_system/BUILD.bazel
deleted file mode 100644
index 0a16c57..0000000
--- a/pw_system/BUILD.bazel
+++ /dev/null
@@ -1,225 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_binary",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-# WARNING: Many of the dependencies in this file are missing and need to be
-# added/updated. This is provided as a starting point, but currently does not
-# work.
-
-pw_cc_library(
-    name = "config",
-    hdrs = [
-        "public/pw_system/config.h",
-    ],
-)
-
-pw_cc_library(
-    name = "log",
-    srcs = [
-        "log.cc",
-    ],
-    hdrs = [
-        "pw_system_private/log.h",
-    ],
-    deps = [
-        ":config",
-        ":rpc_server",
-        "//pw_log_rpc:log_service",
-        "//pw_log_rpc:rpc_log_drain",
-        "//pw_log_rpc:rpc_log_drain_thread",
-        "//pw_multisink",
-        "//pw_sync:lock_annotations",
-        "//pw_sync:mutex",
-    ],
-)
-
-pw_cc_library(
-    name = "log_backend",
-    srcs = [
-        "log_backend.cc",
-    ],
-    deps = [
-        ":config",
-        ":log",
-        "//pw_bytes",
-        "//pw_chrono:system_clock",
-        "//pw_log:facade",
-        "//pw_log:proto_utils",
-        "//pw_log_string:handler_facade",
-        "//pw_log_tokenized:metadata",
-        "//pw_multisink",
-        "//pw_result",
-        "//pw_string",
-        "//pw_sync:interrupt_spin_lock",
-        "//pw_sync:lock_annotations",
-        "//pw_tokenizer",
-    ],
-)
-
-pw_cc_library(
-    name = "rpc_server",
-    hdrs = [
-        "public/pw_system/rpc_server.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":config",
-        ":hdlc_rpc_server",
-    ],
-)
-
-pw_cc_library(
-    name = "hdlc_rpc_server",
-    srcs = [
-        "hdlc_rpc_server.cc",
-    ],
-    includes = ["public"],
-    deps = [
-        ":io",
-        ":rpc_server",
-        ":target_io",
-        "//pw_assert",
-        "//pw_hdlc:pw_rpc",
-        "//pw_hdlc:rpc_channel_output",
-        "//pw_sync:mutex",
-        "//pw_thread:thread_core",
-    ],
-)
-
-pw_cc_library(
-    name = "io",
-    hdrs = [
-        "public/pw_system/io.h",
-    ],
-    deps = [
-        "//pw_stream",
-    ],
-)
-
-pw_cc_library(
-    name = "init",
-    srcs = [
-        "init.cc",
-    ],
-    hdrs = [
-        "public/pw_system/init.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":log",
-        ":rpc_server",
-        "//pw_rpc/nanopb:echo_service",
-        "//pw_thread:thread",
-    ],
-)
-
-pw_cc_library(
-    name = "work_queue",
-    srcs = [
-        "work_queue.cc",
-    ],
-    hdrs = [
-        "public/pw_system/work_queue.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_work_queue",
-    ],
-)
-
-pw_cc_library(
-    name = "target_io",
-    srcs = [
-        "target_io.cc",
-    ],
-    includes = ["public"],
-    deps = [
-        ":io",
-        "//pw_stream",
-        "//pw_stream:sys_io_stream",
-    ],
-)
-
-pw_cc_library(
-    name = "socket_target_io",
-    srcs = [
-        "socket_target_io.cc",
-    ],
-    includes = ["public"],
-    deps = [
-        ":config",
-        ":io",
-        "//pw_assert",
-        "//pw_stream",
-        "//pw_stream:socket_stream",
-    ],
-)
-
-pw_cc_library(
-    name = "target_hooks",
-    hdrs = [
-        "public/pw_system/target_hooks.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_thread:thread",
-    ],
-)
-
-pw_cc_library(
-    name = "stl_target_hooks",
-    srcs = [
-        "stl_target_hooks.cc",
-    ],
-    deps = [
-        "//pw_thread:sleep",
-        "//pw_thread:thread",
-        "//pw_thread_stl:thread",
-    ],
-)
-
-pw_cc_library(
-    name = "freertos_target_hooks",
-    srcs = [
-        "freertos_target_hooks.cc",
-    ],
-
-    # TODO(pwbug/317): This should depend on FreeRTOS but our third parties
-    # currently do not have Bazel support.
-    deps = [
-        "//pw_thread:thread",
-        "//pw_thread_freertos:thread",
-    ],
-)
-
-pw_cc_binary(
-    name = "system_example",
-    srcs = ["example_user_app_init.cc"],
-    deps = [
-        ":init",
-        ":io",
-        ":target_hooks",
-        "//pw_stream",
-        "//pw_stream:sys_io_stream",
-    ],
-)
diff --git a/pw_system/BUILD.gn b/pw_system/BUILD.gn
deleted file mode 100644
index fd5cb5a..0000000
--- a/pw_system/BUILD.gn
+++ /dev/null
@@ -1,251 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pigweed/third_party/freertos/freertos.gni")
-import("$dir_pigweed/third_party/nanopb/nanopb.gni")
-import("$dir_pigweed/third_party/smartfusion_mss/mss.gni")
-import("$dir_pigweed/third_party/stm32cube/stm32cube.gni")
-import("$dir_pw_build/error.gni")
-import("$dir_pw_build/facade.gni")
-import("$dir_pw_build/module_config.gni")
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_docgen/docs.gni")
-import("backend.gni")
-
-declare_args() {
-  # The build target that overrides the default configuration options for this
-  # module. This should point to a source set that provides defines through a
-  # public config (which may -include a file or add defines directly).
-  pw_system_CONFIG = pw_build_DEFAULT_MODULE_CONFIG
-}
-
-config("public_include_path") {
-  include_dirs = [ "public" ]
-}
-
-pw_source_set("config") {
-  sources = [ "public/pw_system/config.h" ]
-  public_configs = [ ":public_include_path" ]
-  public_deps = [ pw_system_CONFIG ]
-  visibility = [ "./*" ]
-  friend = [ "./*" ]
-}
-
-group("pw_system") {
-  public_deps = [
-    ":init",
-    ":io",
-    ":log",
-    ":rpc_server",
-    ":work_queue",
-  ]
-  deps = [ ":target_hooks" ]
-}
-
-pw_source_set("log") {
-  public_configs = [ ":public_include_path" ]
-  sources = [
-    "log.cc",
-    "pw_system_private/log.h",
-  ]
-  public_deps = [
-    "$dir_pw_log_rpc:log_service",
-    "$dir_pw_log_rpc:rpc_log_drain_thread",
-    "$dir_pw_multisink",
-  ]
-  deps = [
-    ":config",
-    ":rpc_server",
-    "$dir_pw_log_rpc:rpc_log_drain",
-    "$dir_pw_sync:lock_annotations",
-    "$dir_pw_sync:mutex",
-  ]
-}
-
-# There is no public part to this backend which does not cause circular
-# dependencies, there is only the pw_build_LINK_DEPS "log_backend.impl".
-pw_source_set("log_backend") {
-}
-
-pw_source_set("log_backend.impl") {
-  sources = [ "log_backend.cc" ]
-  deps = [
-    ":config",
-    ":log",
-    "$dir_pw_bytes",
-    "$dir_pw_chrono:system_clock",
-    "$dir_pw_log:proto_utils",
-    "$dir_pw_log:pw_log.facade",
-    "$dir_pw_log_string:handler.facade",
-    "$dir_pw_log_tokenized:metadata",
-    "$dir_pw_multisink",
-    "$dir_pw_result",
-    "$dir_pw_string",
-    "$dir_pw_sync:interrupt_spin_lock",
-    "$dir_pw_sync:lock_annotations",
-    "$dir_pw_tokenizer",
-    "$dir_pw_tokenizer:global_handler_with_payload.facade",
-  ]
-}
-
-pw_facade("rpc_server") {
-  backend = pw_system_RPC_SERVER_BACKEND
-  public = [ "public/pw_system/rpc_server.h" ]
-  public_configs = [ ":public_include_path" ]
-  public_deps = [
-    ":config",
-    "$dir_pw_thread:thread_core",
-  ]
-}
-
-pw_facade("io") {
-  backend = pw_system_IO_BACKEND
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_system/io.h" ]
-  public_deps = [ "$dir_pw_stream" ]
-}
-
-pw_source_set("init") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_system/init.h" ]
-  sources = [ "init.cc" ]
-  deps = [
-    ":log",
-    ":rpc_server",
-    ":target_hooks.facade",
-    ":work_queue",
-    "$dir_pw_rpc/nanopb:echo_service",
-    "$dir_pw_thread:thread",
-  ]
-}
-
-pw_source_set("hdlc_rpc_server") {
-  sources = [ "hdlc_rpc_server.cc" ]
-  deps = [
-    ":config",
-    ":io",
-    ":rpc_server.facade",
-    "$dir_pw_assert",
-    "$dir_pw_hdlc:pw_rpc",
-    "$dir_pw_hdlc:rpc_channel_output",
-    "$dir_pw_log",
-    "$dir_pw_sync:mutex",
-  ]
-}
-
-pw_source_set("work_queue") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_system/work_queue.h" ]
-  sources = [ "work_queue.cc" ]
-  public_deps = [ "$dir_pw_work_queue" ]
-  deps = [ ":config" ]
-}
-
-pw_source_set("sys_io_target_io") {
-  sources = [ "target_io.cc" ]
-  deps = [
-    ":io.facade",
-    "$dir_pw_stream",
-    "$dir_pw_stream:sys_io_stream",
-  ]
-}
-
-pw_source_set("socket_target_io") {
-  sources = [ "socket_target_io.cc" ]
-  deps = [
-    ":config",
-    ":io.facade",
-    "$dir_pw_assert",
-    "$dir_pw_stream",
-    "$dir_pw_stream:socket_stream",
-  ]
-}
-
-pw_facade("target_hooks") {
-  backend = pw_system_TARGET_HOOKS_BACKEND
-  public = [ "public/pw_system/target_hooks.h" ]
-  public_deps = [ "$dir_pw_thread:thread" ]
-  public_configs = [ ":public_include_path" ]
-}
-
-if (pw_system_TARGET_HOOKS_BACKEND == "") {
-  # Do nothing, prevents errors from trying to parse pw_system_TARGET_HOOKS_BACKEND as a
-  # build target when it's unset.
-} else if (get_label_info(pw_system_TARGET_HOOKS_BACKEND,
-                          "label_no_toolchain") ==
-           get_label_info(":stl_target_hooks", "label_no_toolchain")) {
-  pw_source_set("stl_target_hooks") {
-    deps = [
-      ":init",
-      "$dir_pw_log",
-      "$dir_pw_thread:sleep",
-      "$dir_pw_thread:thread",
-      "$dir_pw_thread_stl:thread",
-    ]
-    sources = [ "stl_target_hooks.cc" ]
-  }
-} else if (get_label_info(pw_system_TARGET_HOOKS_BACKEND,
-                          "label_no_toolchain") ==
-           get_label_info(":freertos_target_hooks", "label_no_toolchain")) {
-  pw_source_set("freertos_target_hooks") {
-    deps = [
-      ":init",
-      "$dir_pw_third_party/freertos",
-      "$dir_pw_thread:thread",
-      "$dir_pw_thread_freertos:thread",
-    ]
-    sources = [ "freertos_target_hooks.cc" ]
-  }
-}
-
-pw_executable("system_example") {
-  sources = [ "example_user_app_init.cc" ]
-  deps = [
-    ":pw_system",
-    "$dir_pw_log",
-    "$dir_pw_thread:sleep",
-  ]
-}
-
-if (dir_pw_third_party_nanopb != "") {
-  group("system_examples") {
-    deps = [ ":system_example($dir_pigweed/targets/host_device_simulator:host_device_simulator.speed_optimized)" ]
-    if (dir_pw_third_party_stm32cube_f4 != "" &&
-        dir_pw_third_party_freertos != "") {
-      deps += [ ":system_example($dir_pigweed/targets/stm32f429i_disc1_stm32cube:stm32f429i_disc1_stm32cube.size_optimized)" ]
-    }
-    if (dir_pw_third_party_smartfusion_mss != "" &&
-        dir_pw_third_party_freertos != "") {
-      deps += [
-        ":system_example($dir_pigweed/targets/emcraft_sf2_som:emcraft_sf2_som.size_optimized)",
-        ":system_example($dir_pigweed/targets/emcraft_sf2_som:emcraft_sf2_som.speed_optimized)",
-        ":system_example($dir_pigweed/targets/emcraft_sf2_som:emcraft_sf2_som_debug.debug)",
-      ]
-    }
-  }
-} else {
-  pw_error("system_examples") {
-    message_lines = [
-      "Building the pw_system examples requires Nanopb.",
-      "Nanopb can be installed by running the command below and then following the prompted setup steps:",
-      "   pw package install nanopb",
-    ]
-  }
-}
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
diff --git a/pw_system/CMakeLists.txt b/pw_system/CMakeLists.txt
deleted file mode 100644
index e63e71f..0000000
--- a/pw_system/CMakeLists.txt
+++ /dev/null
@@ -1,159 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-
-# WARNING: Many of the dependencies in this file are missing and need to be
-# added/updated. This is provided as a starting point, but currently does not
-# work.
-
-pw_add_module_library(pw_system.config
-  HEADERS
-    public/pw_system/config.h
-)
-
-pw_add_module_library(pw_system.log
-  PUBLIC_DEPS
-    pw_log_rpc.log_service
-    pw_log_rpc.rpc_log_drain_thread
-    pw_multisink
-  PRIVATE_DEPS
-    pw_system.config
-    pw_system.rpc_server
-    pw_log_rpc.rpc_log_drain
-    pw_sync.lock_annotations
-    pw_sync.mutex
-  HEADERS
-    pw_system_private/log.h
-  SOURCES
-    log.cc
-)
-
-pw_add_module_library(pw_system.log_backend
-  PRIVATE_DEPS
-    pw_system.config
-    pw_system.log
-    pw_bytes
-    pw_chrono.system_clock
-    pw_log.facade
-    pw_log.proto_utils
-    pw_log_string.handler.facade
-    pw_log_tokenized.metadata
-    pw_multisink
-    pw_result
-    pw_sync.interrupt_spin_lock
-    pw_sync.lock_annotations
-    pw_tokenizer
-    pw_tokenizer.global_handler_with_payload.facade
-  SOURCES
-    log_backend.cc
-)
-
-pw_add_facade(pw_system.rpc_server
-  PUBLIC_DEPS
-    pw_system.config
-    pw_thread.thread_core
-  HEADERS
-    public/pw_system/rpc_server.h
-)
-
-pw_add_module_library(pw_system.hdlc_rpc_server
-  PRIVATE_DEPS
-    pw_assert
-    pw_hdlc.pw_rpc
-    pw_hdlc.rpc_channel_output
-    pw_sync.mutex
-    pw_system.config
-    pw_system.io
-    pw_system.rpc_server.facade
-    pw_system.target_io
-    pw_thread.thread_core
-  SOURCES
-    hdlc_rpc_server.cc
-)
-
-pw_add_module_library(pw_system.io
-  HEADERS
-    public/pw_system/io.h
-  PUBLIC_DEPS
-    pw_stream
-)
-
-pw_add_module_library(pw_system.init
-  PRIVATE_DEPS
-        pw_system.log
-        pw_system.rpc_server
-        pw_rpc.nanopb.echo_service
-        pw_thread.thread
-  SOURCES
-    init.cc
-  HEADERS
-    public/pw_system/init.h
-)
-
-pw_add_module_library(pw_system.work_queue
-  PRIVATE_DEPS
-    pw_work_queue
-  SOURCES
-    work_queue.cc
-  HEADERS
-    public/pw_system/work_queue.h
-)
-
-pw_add_module_library(pw_system.target_io
-  PRIVATE_DEPS
-    pw_system.io
-    pw_stream
-    pw_stream.sys_io_stream
-  SOURCES
-    target_io.cc
-)
-
-pw_add_module_library(pw_system.target_hooks
-  PUBLIC_DEPS
-    pw_thread
-  HEADERS
-    public/pw_system/target_hooks.h
-)
-
-pw_add_module_library(pw_system.stl_target_hooks
-  PRIVATE_DEPS
-    pw_thread.sleep
-    pw_thread.thread
-    pw_thread_stl.thread
-
-  SOURCES
-    stl_target_hooks.cc
-)
-
-pw_add_module_library(pw_system.freertos_target_hooks
-  SOURCES
-    freertos_target_hooks.cc
-  PRIVATE_DEPS
-    pw_thread.thread
-    pw_thread_freertos.thread
-    # TODO(pwbug/317): This should depend on FreeRTOS but our third parties
-    # currently do not have CMake support.
-)
-
-pw_add_module_library(pw_system.system_example
-  PRIVATE_DEPS
-    pw_system.init
-    pw_system.io
-    pw_system.target_hooks
-    pw_stream
-    pw_stream.sys_io_stream
-  SOURCES
-    example_user_app_init.cc
-)
diff --git a/pw_system/OWNERS b/pw_system/OWNERS
deleted file mode 100644
index 621c582..0000000
--- a/pw_system/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-amontanez@google.com
-cachinchilla@google.com
-keir@google.com
-tonymd@google.com
diff --git a/pw_system/backend.gni b/pw_system/backend.gni
deleted file mode 100644
index 3b4d470..0000000
--- a/pw_system/backend.gni
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-declare_args() {
-  # The pw_system backend that provides thread options for the appropriate
-  # scheduler.
-  #
-  # There's no default backend as this is target/os specific. pw_system_target
-  # can automatically configure this for your project.
-  pw_system_TARGET_HOOKS_BACKEND = ""
-
-  # The pw_system backend that provides read/write streams for RPC and logging.
-  #
-  # There's no default backend as this is target specific. pw_system_target
-  # can automatically configure this for your project.
-  pw_system_IO_BACKEND = ""
-
-  # The pw_system backend that provides the system RPC server.
-  #
-  # This defaults to a single-channel HDLC server provided by pw_system
-  # when using a pw_system_target.
-  pw_system_RPC_SERVER_BACKEND = ""
-}
diff --git a/pw_system/docs.rst b/pw_system/docs.rst
deleted file mode 100644
index b9572b9..0000000
--- a/pw_system/docs.rst
+++ /dev/null
@@ -1,148 +0,0 @@
-.. _module-pw_system:
-
-=========
-pw_system
-=========
-.. warning::
-  This module is an early work-in-progress towards an opinionated framework for
-  new projects built on Pigweed. It is under active development, so stay tuned!
-
-pw_system is quite different from typical Pigweed modules. Rather than providing
-a single slice of vertical functionality, pw_system pulls together many modules
-across Pigweed to construct a working system with RPC, Logging, an OS
-Abstraction layer, and more. pw_system exists to greatly simplify the process
-of starting a new project using Pigweed by drastically reducing the required
-configuration space required to go from first signs of on-device life to a more
-sophisticated production-ready system.
-
-Trying out pw_system
-====================
-If you'd like to give pw_system a spin and have a STM32F429I Discovery board,
-refer to the board's
-:ref:`target documentation<target-stm32f429i-disc1-stm32cube>` for instructions
-on how to build the demo and try things out
-
-If you don't have a discovery board, there's a simulated device variation that
-you can run on your local machine with no additional hardware. Check out the
-steps for trying this out :ref:`here<target-host-device-simulator>`.
-
-Target Bringup
-==============
-Bringing up a new device is as easy as 1-2-3! (Kidding, this is a work in
-progress)
-
-#. **Create a ``pw_system_target`` in your GN build.**
-   This is what will control the configuration of your target from a build
-   system level. This includes which compiler will be used, what architecture
-   flags will be used, which backends will be used, and more. A large quantity
-   of configuration will be pre-set to work with pw_system after you select the
-   CPU and scheduler your target will use, but your target will likely need to
-   set a few other things to get to a fully working state.
-#. **Write target-specific initialization.**
-   Most embedded devices require a linker script, manual initialization of
-   memory, and some clock initialization. pw_system leaves this to users to
-   implement as the exact initialization sequence can be very project-specific.
-   All that's required is that after early memory initialization and clock
-   configuration is complete, your target initialization should call
-   ``pw::system::Init()`` and then start the RTOS scheduler (e.g.
-   ``vTaskStartScheduler()``).
-#. **Implement ``pw::system::UserAppInit()`` in your application.**
-   This is where most of your project's application-specific logic goes. This
-   could be starting threads, registering RPC services, turning on Bluetooth,
-   or more. In ``UserAppInit()``, the RTOS will be running so you're free to use
-   OS primitives and use features that rely on threading (e.g. RPC, logging).
-
-Pigweed's ``stm32f429i_disc1_stm32cube`` target demonstrates what's required by
-the first two steps. The third step is where you get to decide how to turn your
-new platform into a project that does something cool! It might be as simple as
-a blinking LED, or something more complex like a Bluetooth device that brews you
-a cup of coffee whenever ``pw watch`` kicks off a new build.
-
-.. note::
-  Because of the nature of the hard-coded conditions in ``pw_system_target``,
-  you may find that some options are missing for various RTOSes and
-  architectures. The design of the GN integration is still a work-in-progress
-  to improve the scalability of this, but in the meantime the Pigweed team
-  welcomes contributions to expand the breadth of RTOSes and architectures
-  supported as ``pw_system_target``\s.
-
-GN Target Toolchain Template
-============================
-This module includes a target toolchain template called ``pw_system_target``
-that reduces the amount of work required to declare a target toolchain with
-pre-selected backends for pw_log, pw_assert, pw_malloc, pw_thread, and more.
-The configurability and extensibility of this template is relatively limited,
-as this template serves as a "one-size-fits-all" starting point rather than
-being foundational infrastructure.
-
-.. code-block::
-
-  # Declare a toolchain with suggested, compiler, compiler flags, and default
-  # backends.
-  pw_system_target("stm32f429i_disc1_stm32cube_size_optimized") {
-    # These options drive the logic for automatic configuration by this
-    # template.
-    cpu = PW_SYSTEM_CPU.CORTEX_M4F
-    scheduler = PW_SYSTEM_SCHEDULER.FREERTOS
-
-    # The pre_init source set provides things like the interrupt vector table,
-    # pre-main init, and provision of FreeRTOS hooks.
-    link_deps = [ "$dir_pigweed/targets/stm32f429i_disc1_stm32cube:pre_init" ]
-
-    # These are hardware-specific options that set up this particular board.
-    # These are declared in ``declare_args()`` blocks throughout Pigweed. Any
-    # build arguments set by the user will be overridden by these settings.
-    build_args = {
-      pw_third_party_freertos_CONFIG = "$dir_pigweed/targets/stm32f429i_disc1_stm32cube:stm32f4xx_freertos_config"
-      pw_third_party_freertos_PORT = "$dir_pw_third_party/freertos:arm_cm4f"
-      pw_sys_io_BACKEND = dir_pw_sys_io_stm32cube
-      dir_pw_third_party_stm32cube = dir_pw_third_party_stm32cube_f4
-      pw_third_party_stm32cube_PRODUCT = "STM32F429xx"
-      pw_third_party_stm32cube_CONFIG =
-          "//targets/stm32f429i_disc1_stm32cube:stm32f4xx_hal_config"
-      pw_third_party_stm32cube_CORE_INIT = ""
-      pw_boot_cortex_m_LINK_CONFIG_DEFINES = [
-        "PW_BOOT_FLASH_BEGIN=0x08000200",
-        "PW_BOOT_FLASH_SIZE=2048K",
-        "PW_BOOT_HEAP_SIZE=7K",
-        "PW_BOOT_MIN_STACK_SIZE=1K",
-        "PW_BOOT_RAM_BEGIN=0x20000000",
-        "PW_BOOT_RAM_SIZE=192K",
-        "PW_BOOT_VECTOR_TABLE_BEGIN=0x08000000",
-        "PW_BOOT_VECTOR_TABLE_SIZE=512",
-      ]
-    }
-  }
-
-  # Example for the Emcraft SmartFusion2 system-on-module
-  pw_system_target("emcraft_sf2_som_size_optimized") {
-    cpu = PW_SYSTEM_CPU.CORTEX_M3
-    scheduler = PW_SYSTEM_SCHEDULER.FREERTOS
-
-    link_deps = [ "$dir_pigweed/targets/emcraft_sf2_som:pre_init" ]
-    build_args = {
-      pw_log_BACKEND = dir_pw_log_basic #dir_pw_log_tokenized
-      pw_tokenizer_GLOBAL_HANDLER_WITH_PAYLOAD_BACKEND = "//pw_system:log"
-      pw_third_party_freertos_CONFIG = "$dir_pigweed/targets/emcraft_sf2_som:sf2_freertos_config"
-      pw_third_party_freertos_PORT = "$dir_pw_third_party/freertos:arm_cm3"
-      pw_sys_io_BACKEND = dir_pw_sys_io_emcraft_sf2
-      dir_pw_third_party_smartfusion_mss = dir_pw_third_party_smartfusion_mss_exported
-      pw_third_party_stm32cube_CONFIG =
-          "//targets/emcraft_sf2_som:sf2_mss_hal_config"
-      pw_third_party_stm32cube_CORE_INIT = ""
-      pw_boot_cortex_m_LINK_CONFIG_DEFINES = [
-        "PW_BOOT_FLASH_BEGIN=0x00000200",
-        "PW_BOOT_FLASH_SIZE=200K",
-
-        # TODO(pwbug/219): Currently "pw_tokenizer/detokenize_test" requires at
-        # least 6K bytes in heap when using pw_malloc_freelist. The heap size
-        # required for tests should be investigated.
-        "PW_BOOT_HEAP_SIZE=7K",
-        "PW_BOOT_MIN_STACK_SIZE=1K",
-        "PW_BOOT_RAM_BEGIN=0x20000000",
-        "PW_BOOT_RAM_SIZE=64K",
-        "PW_BOOT_VECTOR_TABLE_BEGIN=0x00000000",
-        "PW_BOOT_VECTOR_TABLE_SIZE=512",
-      ]
-    }
-  }
diff --git a/pw_system/example_user_app_init.cc b/pw_system/example_user_app_init.cc
deleted file mode 100644
index 93c8415..0000000
--- a/pw_system/example_user_app_init.cc
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_log/log.h"
-#include "pw_thread/sleep.h"
-namespace pw::system {
-
-// This will run once after pw::system::Init() completes. This callback must
-// return or it will block the work queue.
-void UserAppInit() { PW_LOG_INFO("Pigweed is fun!"); }
-
-}  // namespace pw::system
diff --git a/pw_system/freertos_backends.gni b/pw_system/freertos_backends.gni
deleted file mode 100644
index 0426c82..0000000
--- a/pw_system/freertos_backends.gni
+++ /dev/null
@@ -1,34 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-PW_SYSTEM_FREERTOS_BACKENDS = {
-  pw_chrono_SYSTEM_CLOCK_BACKEND = "$dir_pw_chrono_freertos:system_clock"
-  pw_chrono_SYSTEM_TIMER_BACKEND = "$dir_pw_chrono_freertos:system_timer"
-  pw_sync_BINARY_SEMAPHORE_BACKEND = "$dir_pw_sync_freertos:binary_semaphore"
-  pw_sync_THREAD_NOTIFICATION_BACKEND =
-      "$dir_pw_sync_freertos:thread_notification"
-  pw_sync_TIMED_THREAD_NOTIFICATION_BACKEND =
-      "$dir_pw_sync_freertos:timed_thread_notification"
-  pw_sync_MUTEX_BACKEND = "$dir_pw_sync_freertos:mutex"
-  pw_sync_TIMED_MUTEX_BACKEND = "$dir_pw_sync_freertos:timed_mutex"
-  pw_sync_INTERRUPT_SPIN_LOCK_BACKEND =
-      "$dir_pw_sync_freertos:interrupt_spin_lock"
-  pw_thread_ID_BACKEND = "$dir_pw_thread_freertos:id"
-  pw_thread_SLEEP_BACKEND = "$dir_pw_thread_freertos:sleep"
-  pw_thread_THREAD_BACKEND = "$dir_pw_thread_freertos:thread"
-  pw_thread_YIELD_BACKEND = "$dir_pw_thread_freertos:yield"
-  pw_system_TARGET_HOOKS_BACKEND = "$dir_pw_system:freertos_target_hooks"
-}
diff --git a/pw_system/freertos_target_hooks.cc b/pw_system/freertos_target_hooks.cc
deleted file mode 100644
index 04c7005..0000000
--- a/pw_system/freertos_target_hooks.cc
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "FreeRTOS.h"
-#include "pw_system/init.h"
-#include "pw_thread/detached_thread.h"
-#include "pw_thread/thread.h"
-#include "pw_thread_freertos/context.h"
-#include "pw_thread_freertos/options.h"
-
-namespace pw::system {
-
-// Low to high priorities.
-enum class ThreadPriority : UBaseType_t {
-  kWorkQueue = tskIDLE_PRIORITY + 1,
-  // TODO(amontanez): These should ideally be at different priority levels, but
-  // there's synchronization issues when they are.
-  kLog = kWorkQueue,
-  kRpc = kWorkQueue,
-  kNumPriorities,
-};
-
-static_assert(static_cast<UBaseType_t>(ThreadPriority::kNumPriorities) <=
-              configMAX_PRIORITIES);
-
-static constexpr size_t kLogThreadStackWorkds = 1024;
-static thread::freertos::StaticContextWithStack<kLogThreadStackWorkds>
-    log_thread_context;
-const thread::Options& LogThreadOptions() {
-  static constexpr auto options =
-      pw::thread::freertos::Options()
-          .set_name("LogThread")
-          .set_static_context(log_thread_context)
-          .set_priority(static_cast<UBaseType_t>(ThreadPriority::kLog));
-  return options;
-}
-
-static constexpr size_t kRpcThreadStackWorkds = 512;
-static thread::freertos::StaticContextWithStack<kRpcThreadStackWorkds>
-    rpc_thread_context;
-const thread::Options& RpcThreadOptions() {
-  static constexpr auto options =
-      pw::thread::freertos::Options()
-          .set_name("RpcThread")
-          .set_static_context(rpc_thread_context)
-          .set_priority(static_cast<UBaseType_t>(ThreadPriority::kRpc));
-  return options;
-}
-
-static constexpr size_t kWorkQueueThreadStackWorkds = 512;
-static thread::freertos::StaticContextWithStack<kWorkQueueThreadStackWorkds>
-    work_queue_thread_context;
-const thread::Options& WorkQueueThreadOptions() {
-  static constexpr auto options =
-      pw::thread::freertos::Options()
-          .set_name("WorkQueueThread")
-          .set_static_context(work_queue_thread_context)
-          .set_priority(static_cast<UBaseType_t>(ThreadPriority::kWorkQueue));
-  return options;
-}
-
-}  // namespace pw::system
diff --git a/pw_system/hdlc_rpc_server.cc b/pw_system/hdlc_rpc_server.cc
deleted file mode 100644
index fe45826..0000000
--- a/pw_system/hdlc_rpc_server.cc
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <array>
-#include <cstddef>
-#include <cstdint>
-#include <cstdio>
-
-#include "pw_assert/check.h"
-#include "pw_hdlc/rpc_channel.h"
-#include "pw_hdlc/rpc_packets.h"
-#include "pw_log/log.h"
-#include "pw_sync/mutex.h"
-#include "pw_system/config.h"
-#include "pw_system/io.h"
-#include "pw_system/rpc_server.h"
-
-namespace pw::system {
-namespace {
-
-constexpr size_t kMaxTransmissionUnit = PW_SYSTEM_MAX_TRANSMISSION_UNIT;
-
-hdlc::RpcChannelOutput hdlc_channel_output(GetWriter(),
-                                           PW_SYSTEM_DEFAULT_RPC_HDLC_ADDRESS,
-                                           "HDLC channel");
-rpc::Channel channels[] = {
-    rpc::Channel::Create<kDefaultRpcChannelId>(&hdlc_channel_output)};
-rpc::Server server(channels);
-
-// Declare a buffer for decoding incoming HDLC frames.
-std::array<std::byte, kMaxTransmissionUnit> input_buffer;
-hdlc::Decoder decoder(input_buffer);
-
-std::array<std::byte, 1> data;
-
-}  // namespace
-
-rpc::Server& GetRpcServer() { return server; }
-
-class RpcDispatchThread final : public thread::ThreadCore {
- public:
-  RpcDispatchThread() = default;
-  RpcDispatchThread(const RpcDispatchThread&) = delete;
-  RpcDispatchThread(RpcDispatchThread&&) = delete;
-  RpcDispatchThread& operator=(const RpcDispatchThread&) = delete;
-  RpcDispatchThread& operator=(RpcDispatchThread&&) = delete;
-
-  void Run() override {
-    PW_LOG_INFO("Running RPC server");
-    while (true) {
-      auto ret_val = GetReader().Read(data);
-      if (ret_val.ok()) {
-        for (std::byte byte : ret_val.value()) {
-          if (auto result = decoder.Process(byte); result.ok()) {
-            hdlc::Frame& frame = result.value();
-            if (frame.address() == PW_SYSTEM_DEFAULT_RPC_HDLC_ADDRESS) {
-              server.ProcessPacket(frame.data(), hdlc_channel_output);
-            }
-          }
-        }
-      }
-    }
-  }
-};
-
-thread::ThreadCore& GetRpcDispatchThread() {
-  static RpcDispatchThread rpc_dispatch_thread;
-  return rpc_dispatch_thread;
-}
-
-}  // namespace pw::system
diff --git a/pw_system/init.cc b/pw_system/init.cc
deleted file mode 100644
index 2157786..0000000
--- a/pw_system/init.cc
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_system/init.h"
-
-#include "pw_log/log.h"
-#include "pw_rpc/echo_service_nanopb.h"
-#include "pw_system/rpc_server.h"
-#include "pw_system/target_hooks.h"
-#include "pw_system/work_queue.h"
-#include "pw_system_private/log.h"
-#include "pw_thread/detached_thread.h"
-
-namespace pw::system {
-namespace {
-rpc::EchoService echo_service;
-
-void InitImpl() {
-  PW_LOG_INFO("System init");
-
-  // Setup logging.
-  const Status status = GetLogThread().OpenUnrequestedLogStream(
-      kDefaultRpcChannelId, GetRpcServer(), GetLogService());
-  if (!status.ok()) {
-    PW_LOG_ERROR("Error opening unrequested log streams %d",
-                 static_cast<int>(status.code()));
-  }
-
-  PW_LOG_INFO("Registering RPC services");
-  GetRpcServer().RegisterService(echo_service);
-  GetRpcServer().RegisterService(GetLogService());
-
-  PW_LOG_INFO("Starting threads");
-  // Start threads.
-  thread::DetachedThread(system::LogThreadOptions(), GetLogThread());
-  thread::DetachedThread(system::RpcThreadOptions(), GetRpcDispatchThread());
-
-  GetWorkQueue().CheckPushWork(UserAppInit);
-}
-
-}  // namespace
-
-void Init() {
-  thread::DetachedThread(system::WorkQueueThreadOptions(), GetWorkQueue());
-  GetWorkQueue().CheckPushWork(InitImpl);
-}
-
-}  // namespace pw::system
diff --git a/pw_system/log.cc b/pw_system/log.cc
deleted file mode 100644
index 7cfbd82..0000000
--- a/pw_system/log.cc
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_system_private/log.h"
-
-#include <array>
-#include <cstddef>
-
-#include "pw_log_rpc/rpc_log_drain.h"
-#include "pw_log_rpc/rpc_log_drain_map.h"
-#include "pw_multisink/multisink.h"
-#include "pw_sync/lock_annotations.h"
-#include "pw_sync/mutex.h"
-#include "pw_system/config.h"
-#include "pw_system/rpc_server.h"
-
-namespace pw::system {
-namespace {
-
-using log_rpc::RpcLogDrain;
-
-// Storage container for MultiSink used for deferred logging.
-std::array<std::byte, PW_SYSTEM_LOG_BUFFER_SIZE> log_buffer;
-
-// To save RAM, share the mutex and buffer between drains, since drains are
-// flushed sequentially.
-sync::Mutex drains_mutex;
-// Buffer to decode and remove entries from log buffer, to send to a drain.
-std::array<std::byte, PW_SYSTEM_MAX_LOG_ENTRY_SIZE> log_decode_buffer
-    PW_GUARDED_BY(drains_mutex);
-
-std::array<RpcLogDrain, 1> drains{{
-    RpcLogDrain(kDefaultRpcChannelId,
-                log_decode_buffer,
-                drains_mutex,
-                RpcLogDrain::LogDrainErrorHandling::kIgnoreWriterErrors),
-}};
-
-log_rpc::RpcLogDrainMap drain_map(drains);
-
-// TODO(amontanez): Is there a helper to subtract RPC overhead?
-constexpr size_t kMaxPackedLogMessagesSize =
-    PW_SYSTEM_MAX_TRANSMISSION_UNIT - 32;
-
-std::array<std::byte, kMaxPackedLogMessagesSize> log_packing_buffer;
-
-}  // namespace
-
-// Deferred log buffer, for storing log entries while logging_thread_ streams
-// them independently.
-multisink::MultiSink& GetMultiSink() {
-  static multisink::MultiSink multisink(log_buffer);
-  return multisink;
-}
-
-log_rpc::RpcLogDrainThread& GetLogThread() {
-  static log_rpc::RpcLogDrainThread logging_thread(
-      GetMultiSink(), drain_map, log_packing_buffer);
-  return logging_thread;
-}
-
-log_rpc::LogService& GetLogService() {
-  static log_rpc::LogService log_service(drain_map);
-  return log_service;
-}
-
-}  // namespace pw::system
diff --git a/pw_system/log_backend.cc b/pw_system/log_backend.cc
deleted file mode 100644
index e08ba2e..0000000
--- a/pw_system/log_backend.cc
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <array>
-#include <cstddef>
-#include <mutex>
-
-#include "pw_bytes/span.h"
-#include "pw_chrono/system_clock.h"
-#include "pw_log/proto_utils.h"
-#include "pw_log_string/handler.h"
-#include "pw_log_tokenized/metadata.h"
-#include "pw_multisink/multisink.h"
-#include "pw_result/result.h"
-#include "pw_string/string_builder.h"
-#include "pw_sync/interrupt_spin_lock.h"
-#include "pw_sync/lock_annotations.h"
-#include "pw_system/config.h"
-#include "pw_system_private/log.h"
-#include "pw_tokenizer/tokenize_to_global_handler_with_payload.h"
-
-namespace pw::system {
-namespace {
-
-// Buffer used to encode each log entry before saving into log buffer.
-sync::InterruptSpinLock log_encode_lock;
-std::array<std::byte, PW_SYSTEM_MAX_LOG_ENTRY_SIZE> log_encode_buffer
-    PW_GUARDED_BY(log_encode_lock);
-
-// String-only logs may need to be formatted first. This buffer is required
-// so the format string may be passed to the proto log encode.
-std::array<std::byte, PW_SYSTEM_MAX_LOG_ENTRY_SIZE> log_format_buffer
-    PW_GUARDED_BY(log_encode_lock);
-
-const int64_t boot_time_count =
-    pw::chrono::SystemClock::now().time_since_epoch().count();
-
-}  // namespace
-
-// Provides time since boot in units defined by the target's pw_chrono backend.
-int64_t GetTimestamp() {
-  return pw::chrono::SystemClock::now().time_since_epoch().count() -
-         boot_time_count;
-}
-
-// Implementation for tokenized log handling. This will be optimized out for
-// devices that only use string logging.
-extern "C" void pw_tokenizer_HandleEncodedMessageWithPayload(
-    pw_tokenizer_Payload payload, const uint8_t message[], size_t size_bytes) {
-  log_tokenized::Metadata metadata = payload;
-  const int64_t timestamp = GetTimestamp();
-
-  std::lock_guard lock(log_encode_lock);
-  Result<ConstByteSpan> encoded_log_result = log::EncodeTokenizedLog(
-      metadata, message, size_bytes, timestamp, log_encode_buffer);
-  if (!encoded_log_result.ok()) {
-    GetMultiSink().HandleDropped();
-    return;
-  }
-  GetMultiSink().HandleEntry(encoded_log_result.value());
-}
-
-// Implementation for string log handling. This will be optimized out for
-// devices that only use tokenized logging.
-extern "C" void pw_log_string_HandleMessageVaList(int level,
-                                                  unsigned int flags,
-                                                  const char* module_name,
-                                                  const char* file_name,
-                                                  int line_number,
-                                                  const char* message,
-                                                  va_list args) {
-  const int64_t timestamp = GetTimestamp();
-
-  std::lock_guard lock(log_encode_lock);
-  StringBuilder message_builder(log_format_buffer);
-  message_builder.FormatVaList(message, args);
-
-  Result<ConstByteSpan> encoded_log_result =
-      log::EncodeLog(level,
-                     flags,
-                     module_name,
-                     /*thread_name=*/{},
-                     file_name,
-                     line_number,
-                     timestamp,
-                     message_builder.view(),
-                     log_encode_buffer);
-  if (!encoded_log_result.ok()) {
-    GetMultiSink().HandleDropped();
-    return;
-  }
-  GetMultiSink().HandleEntry(encoded_log_result.value());
-}
-
-}  // namespace pw::system
diff --git a/pw_system/public/pw_system/config.h b/pw_system/public/pw_system/config.h
deleted file mode 100644
index e7021eb..0000000
--- a/pw_system/public/pw_system/config.h
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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
-
-// PW_SYSTEM_LOG_BUFFER_SIZE is the log buffer size which determines how many
-// log entries can be buffered prior to streaming them.
-//
-// Defaults to 4KiB.
-#ifndef PW_SYSTEM_LOG_BUFFER_SIZE
-#define PW_SYSTEM_LOG_BUFFER_SIZE 4096
-#endif  // PW_SYSTEM_LOG_BUFFER_SIZE
-
-// PW_SYSTEM_MAX_LOG_ENTRY_SIZE limits the proto-encoded log entry size. This
-// value might depend on a target interface's MTU.
-//
-// Defaults to 512B.
-#ifndef PW_SYSTEM_MAX_LOG_ENTRY_SIZE
-#define PW_SYSTEM_MAX_LOG_ENTRY_SIZE 512
-#endif  // PW_SYSTEM_MAX_LOG_ENTRY_SIZE
-
-// PW_SYSTEM_MAX_TRANSMISSION_UNIT target's MTU.
-//
-// Defaults to 512B.
-#ifndef PW_SYSTEM_MAX_TRANSMISSION_UNIT
-#define PW_SYSTEM_MAX_TRANSMISSION_UNIT 512
-#endif  // PW_SYSTEM_MAX_TRANSMISSION_UNIT
-
-// PW_SYSTEM_DEFAULT_CHANNEL_ID RPC channel ID to host.
-//
-// Defaults to 1.
-#ifndef PW_SYSTEM_DEFAULT_CHANNEL_ID
-#define PW_SYSTEM_DEFAULT_CHANNEL_ID 1
-#endif  // PW_SYSTEM_DEFAULT_CHANNEL_ID
-
-// PW_SYSTEM_DEFAULT_RPC_HDLC_ADDRESS RPC HDLC default address.
-//
-// Defaults to 82.
-#ifndef PW_SYSTEM_DEFAULT_RPC_HDLC_ADDRESS
-#define PW_SYSTEM_DEFAULT_RPC_HDLC_ADDRESS 82
-#endif  // PW_SYSTEM_DEFAULT_RPC_HDLC_ADDRESS
-
-// PW_SYSTEM_WORK_QUEUE_MAX_ENTRIES specifies the maximum number of work queue
-// entries that may be staged at once.
-//
-// Defaults to 32.
-#ifndef PW_SYSTEM_WORK_QUEUE_MAX_ENTRIES
-#define PW_SYSTEM_WORK_QUEUE_MAX_ENTRIES 32
-#endif  // PW_SYSTEM_WORK_QUEUE_MAX_ENTRIES
-
-// PW_SYSTEM_SOCKET_IO_PORT specifies the port number to use for the socket
-// stream implementation of pw_system's I/O interface.
-//
-// Defaults to 33000.
-#ifndef PW_SYSTEM_SOCKET_IO_PORT
-#define PW_SYSTEM_SOCKET_IO_PORT 33000
-#endif  // PW_SYSTEM_SOCKET_IO_PORT
diff --git a/pw_system/public/pw_system/init.h b/pw_system/public/pw_system/init.h
deleted file mode 100644
index 49dc64f..0000000
--- a/pw_system/public/pw_system/init.h
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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
-
-namespace pw::system {
-
-// This function should be called after all required platform initialization is
-// complete, but before the scheduler is started. This function WILL return so
-// the caller may start the scheduler if needed.
-//
-// Init will start logging, RPC, and work queue threads, and do any
-// initialization required for those systems. Note that this initialization is
-// largely not synchronous: only the work queue thread is dispatched, and the
-// remainder of the initialization is added as a work queue item so it can be
-// run in the normal context of a running scheduler/OS. This means RPC and
-// logging will not be fully initialized until after that first work queue item
-// is complete.
-//
-// To run something after pw_system's initialization is complete,
-// simply add a callback to the work queue after calling pw::system::Init()
-// rather than directly calling the function itself.
-void Init();
-
-}  // namespace pw::system
diff --git a/pw_system/public/pw_system/io.h b/pw_system/public/pw_system/io.h
deleted file mode 100644
index 1481eed..0000000
--- a/pw_system/public/pw_system/io.h
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_stream/stream.h"
-
-namespace pw::system {
-
-stream::Reader& GetReader();
-stream::Writer& GetWriter();
-
-}  // namespace pw::system
diff --git a/pw_system/public/pw_system/rpc_server.h b/pw_system/public/pw_system/rpc_server.h
deleted file mode 100644
index 3918ded..0000000
--- a/pw_system/public/pw_system/rpc_server.h
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_system/config.h"
-#include "pw_thread/thread_core.h"
-
-namespace pw::system {
-
-// This is the default channel used by the pw_system RPC server. Some other
-// parts of pw_system (e.g. logging) use this channel ID as the default
-// destination for unrequested data streams.
-inline constexpr uint32_t kDefaultRpcChannelId = PW_SYSTEM_DEFAULT_CHANNEL_ID;
-
-rpc::Server& GetRpcServer();
-
-thread::ThreadCore& GetRpcDispatchThread();
-
-}  // namespace pw::system
diff --git a/pw_system/public/pw_system/target_hooks.h b/pw_system/public/pw_system/target_hooks.h
deleted file mode 100644
index 58460ca..0000000
--- a/pw_system/public/pw_system/target_hooks.h
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_thread/thread.h"
-
-namespace pw::system {
-
-const thread::Options& LogThreadOptions();
-
-const thread::Options& RpcThreadOptions();
-
-const thread::Options& WorkQueueThreadOptions();
-
-// This will run once after pw::system::Init() completes. This callback must
-// return or it will block the work queue.
-//
-// This is the first thing run in a threaded context (specifically on the work
-// queue thread).
-void UserAppInit();
-
-}  // namespace pw::system
diff --git a/pw_system/public/pw_system/work_queue.h b/pw_system/public/pw_system/work_queue.h
deleted file mode 100644
index 9a4e2e8..0000000
--- a/pw_system/public/pw_system/work_queue.h
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_work_queue/work_queue.h"
-
-namespace pw::system {
-
-work_queue::WorkQueue& GetWorkQueue();
-
-}  // namespace pw::system
diff --git a/pw_system/pw_system_private/log.h b/pw_system/pw_system_private/log.h
deleted file mode 100644
index 85cf0c2..0000000
--- a/pw_system/pw_system_private/log.h
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_log_rpc/log_service.h"
-#include "pw_log_rpc/rpc_log_drain_thread.h"
-#include "pw_multisink/multisink.h"
-
-namespace pw::system {
-
-log_rpc::LogService& GetLogService();
-log_rpc::RpcLogDrainThread& GetLogThread();
-multisink::MultiSink& GetMultiSink();
-
-}  // namespace pw::system
diff --git a/pw_system/py/BUILD.gn b/pw_system/py/BUILD.gn
deleted file mode 100644
index ec9e08d..0000000
--- a/pw_system/py/BUILD.gn
+++ /dev/null
@@ -1,41 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/python.gni")
-
-pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
-  sources = [
-    "pw_system/__init__.py",
-    "pw_system/console.py",
-    "pw_system/device.py",
-  ]
-  python_deps = [
-    "$dir_pw_cli/py",
-    "$dir_pw_console/py",
-    "$dir_pw_hdlc/py",
-    "$dir_pw_protobuf_compiler/py",
-    "$dir_pw_rpc/py",
-    "$dir_pw_tokenizer/py",
-  ]
-  inputs = []
-
-  pylintrc = "$dir_pigweed/.pylintrc"
-}
diff --git a/pw_system/py/pw_system/__init__.py b/pw_system/py/pw_system/__init__.py
deleted file mode 100644
index 486d1ed..0000000
--- a/pw_system/py/pw_system/__init__.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
diff --git a/pw_system/py/pw_system/console.py b/pw_system/py/pw_system/console.py
deleted file mode 100644
index 7843837..0000000
--- a/pw_system/py/pw_system/console.py
+++ /dev/null
@@ -1,304 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Console for interacting with devices using HDLC.
-
-To start the console, provide a serial port as the --device argument and paths
-or globs for .proto files that define the RPC services to support:
-
-  python -m pw_hdlc.rpc_console --device /dev/ttyUSB0 sample.proto
-
-This starts an IPython console for communicating with the connected device. A
-few variables are predefined in the interactive console. These include:
-
-    rpcs   - used to invoke RPCs
-    device - the serial device used for communication
-    client - the pw_rpc.Client
-    protos - protocol buffer messages indexed by proto package
-
-An example echo RPC command:
-
-  rpcs.pw.rpc.EchoService.Echo(msg="hello!")
-"""
-
-import argparse
-import datetime
-import glob
-from inspect import cleandoc
-import logging
-from pathlib import Path
-import sys
-from types import ModuleType
-from typing import (
-    Any,
-    Collection,
-    Iterable,
-    Iterator,
-    List,
-    Optional,
-    Union,
-)
-import socket
-
-import serial  # type: ignore
-
-import pw_cli.log
-import pw_console.python_logging
-from pw_console import PwConsoleEmbed
-from pw_console.pyserial_wrapper import SerialWithLogging
-from pw_console.plugins.bandwidth_toolbar import BandwidthToolbar
-
-from pw_log.proto import log_pb2
-from pw_rpc.console_tools.console import flattened_rpc_completions
-from pw_system.device import Device
-from pw_tokenizer.detokenize import AutoUpdatingDetokenizer
-
-_LOG = logging.getLogger('tools')
-_DEVICE_LOG = logging.getLogger('rpc_device')
-
-PW_RPC_MAX_PACKET_SIZE = 256
-SOCKET_SERVER = 'localhost'
-SOCKET_PORT = 33000
-MKFIFO_MODE = 0o666
-
-
-def _parse_args():
-    """Parses and returns the command line arguments."""
-    parser = argparse.ArgumentParser(description=__doc__)
-    group = parser.add_mutually_exclusive_group(required=True)
-    group.add_argument('-d', '--device', help='the serial port to use')
-    parser.add_argument('-b',
-                        '--baudrate',
-                        type=int,
-                        default=115200,
-                        help='the baud rate to use')
-    parser.add_argument(
-        '--serial-debug',
-        action='store_true',
-        help=('Enable debug log tracing of all data passed through'
-              'pyserial read and write.'))
-    parser.add_argument(
-        '-o',
-        '--output',
-        type=argparse.FileType('wb'),
-        default=sys.stdout.buffer,
-        help=('The file to which to write device output (HDLC channel 1); '
-              'provide - or omit for stdout.'))
-    parser.add_argument('--logfile', help='Console debug log file.')
-    group.add_argument('-s',
-                       '--socket-addr',
-                       type=str,
-                       help='use socket to connect to server, type default for\
-            localhost:33000, or manually input the server address:port')
-    parser.add_argument("--token-databases",
-                        metavar='elf_or_token_database',
-                        nargs="+",
-                        type=Path,
-                        help="Path to tokenizer database csv file(s).")
-    parser.add_argument('--config-file',
-                        type=Path,
-                        help='Path to a pw_console yaml config file.')
-    parser.add_argument('--proto-globs',
-                        nargs='+',
-                        help='glob pattern for .proto files')
-    parser.add_argument('-v',
-                        '--verbose',
-                        action='store_true',
-                        help='Enables debug logging when set')
-    return parser.parse_args()
-
-
-def _expand_globs(globs: Iterable[str]) -> Iterator[Path]:
-    for pattern in globs:
-        for file in glob.glob(pattern, recursive=True):
-            yield Path(file)
-
-
-def _start_ipython_terminal(device: Device,
-                            serial_debug: bool = False,
-                            config_file_path: Optional[Path] = None) -> None:
-    """Starts an interactive IPython terminal with preset variables."""
-    local_variables = dict(
-        client=device.client,
-        device=device,
-        rpcs=device.rpcs,
-        protos=device.client.protos.packages,
-        # Include the active pane logger for creating logs in the repl.
-        DEVICE_LOG=_DEVICE_LOG,
-        LOG=logging.getLogger(),
-    )
-
-    welcome_message = cleandoc("""
-        Welcome to the Pigweed Console!
-
-        Help: Press F1 or click the [Help] menu
-        To move focus: Press Shift-Tab or click on a window
-
-        Example Python commands:
-
-          device.rpcs.pw.rpc.EchoService.Echo(msg='hello!')
-          LOG.warning('Message appears in Host Logs window.')
-          DEVICE_LOG.warning('Message appears in Device Logs window.')
-    """)
-
-    client_info = device.info()
-    completions = flattened_rpc_completions([client_info])
-
-    log_windows = {
-        'Device Logs': [_DEVICE_LOG],
-        'Host Logs': [logging.getLogger()],
-    }
-    if serial_debug:
-        log_windows['Serial Debug'] = [
-            logging.getLogger('pw_console.serial_debug_logger')
-        ]
-
-    interactive_console = PwConsoleEmbed(
-        global_vars=local_variables,
-        local_vars=None,
-        loggers=log_windows,
-        repl_startup_message=welcome_message,
-        help_text=__doc__,
-        config_file_path=config_file_path,
-    )
-    interactive_console.hide_windows('Host Logs')
-    interactive_console.add_sentence_completer(completions)
-    if serial_debug:
-        interactive_console.add_bottom_toolbar(BandwidthToolbar())
-
-    # Setup Python logger propagation
-    interactive_console.setup_python_logging()
-
-    # Don't send device logs to the root logger.
-    _DEVICE_LOG.propagate = False
-
-    interactive_console.embed()
-
-
-class SocketClientImpl:
-    def __init__(self, config: str):
-        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        socket_server = ''
-        socket_port = 0
-
-        if config == 'default':
-            socket_server = SOCKET_SERVER
-            socket_port = SOCKET_PORT
-        else:
-            socket_server, socket_port_str = config.split(':')
-            socket_port = int(socket_port_str)
-        self.socket.connect((socket_server, socket_port))
-
-    def write(self, data: bytes):
-        self.socket.sendall(data)
-
-    def read(self, num_bytes: int = PW_RPC_MAX_PACKET_SIZE):
-        return self.socket.recv(num_bytes)
-
-
-def console(device: str,
-            baudrate: int,
-            proto_globs: Collection[str],
-            token_databases: Collection[Path],
-            socket_addr: str,
-            logfile: str,
-            output: Any,
-            serial_debug: bool = False,
-            config_file: Optional[Path] = None,
-            verbose: bool = False) -> int:
-    """Starts an interactive RPC console for HDLC."""
-    # argparse.FileType doesn't correctly handle '-' for binary files.
-    if output is sys.stdout:
-        output = sys.stdout.buffer
-
-    if not logfile:
-        # Create a temp logfile to prevent logs from appearing over stdout. This
-        # would corrupt the prompt toolkit UI.
-        logfile = pw_console.python_logging.create_temp_log_file()
-
-    log_level = logging.DEBUG if verbose else logging.INFO
-    pw_cli.log.install(log_level, True, False, logfile)
-    _DEVICE_LOG.setLevel(log_level)
-    _LOG.setLevel(log_level)
-
-    detokenizer = None
-    if token_databases:
-        detokenizer = AutoUpdatingDetokenizer(*token_databases)
-        detokenizer.show_errors = True
-
-    if not proto_globs:
-        proto_globs = ['**/*.proto']
-
-    protos: List[Union[ModuleType, Path]] = list(_expand_globs(proto_globs))
-
-    # Append compiled log.proto library to avoid include errors when manually
-    # provided, and shadowing errors due to ordering when the default global
-    # search path is used.
-    protos.append(log_pb2)
-
-    if not protos:
-        _LOG.critical('No .proto files were found with %s',
-                      ', '.join(proto_globs))
-        _LOG.critical('At least one .proto file is required')
-        return 1
-
-    _LOG.debug('Found %d .proto files found with %s', len(protos),
-               ', '.join(proto_globs))
-
-    serial_impl = serial.Serial
-    if serial_debug:
-        serial_impl = SerialWithLogging
-
-    timestamp_decoder = None
-    if socket_addr is None:
-        serial_device = serial_impl(
-            device,
-            baudrate,
-            timeout=0,  # Non-blocking mode
-        )
-        read = lambda: serial_device.read(8192)
-        write = serial_device.write
-
-        # Overwrite decoder for serial device.
-        def milliseconds_to_string(timestamp):
-            """Parses milliseconds since boot to a human-readable string."""
-            return str(datetime.timedelta(seconds=timestamp / 1e3))[:-3]
-
-        timestamp_decoder = milliseconds_to_string
-    else:
-        try:
-            socket_device = SocketClientImpl(socket_addr)
-            read = socket_device.read
-            write = socket_device.write
-        except ValueError:
-            _LOG.exception('Failed to initialize socket at %s', socket_addr)
-            return 1
-
-    device_client = Device(1,
-                           read,
-                           write,
-                           protos,
-                           detokenizer,
-                           timestamp_decoder=timestamp_decoder,
-                           rpc_timeout_s=5)
-
-    _start_ipython_terminal(device_client, serial_debug, config_file)
-    return 0
-
-
-def main() -> int:
-    return console(**vars(_parse_args()))
-
-
-if __name__ == '__main__':
-    sys.exit(main())
diff --git a/pw_system/py/pw_system/device.py b/pw_system/py/pw_system/device.py
deleted file mode 100644
index 1288f68..0000000
--- a/pw_system/py/pw_system/device.py
+++ /dev/null
@@ -1,170 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Device classes to interact with targets via RPC."""
-
-import datetime
-import logging
-from pathlib import Path
-from types import ModuleType
-from typing import Any, Callable, List, Union, Optional
-
-from pw_hdlc.rpc import HdlcRpcClient, default_channels
-import pw_log_tokenized
-
-from pw_log.proto import log_pb2
-from pw_rpc import callback_client, console_tools
-from pw_status import Status
-from pw_tokenizer.detokenize import Detokenizer
-from pw_tokenizer.proto import decode_optionally_tokenized
-
-# Internal log for troubleshooting this tool (the console).
-_LOG = logging.getLogger('tools')
-DEFAULT_DEVICE_LOGGER = logging.getLogger('rpc_device')
-
-
-class Device:
-    """Represents an RPC Client for a device running a Pigweed target.
-
-    The target must have and RPC support, RPC logging.
-    Note: use this class as a base for specialized device representations.
-    """
-    def __init__(self,
-                 channel_id: int,
-                 read,
-                 write,
-                 proto_library: List[Union[ModuleType, Path]],
-                 detokenizer: Optional[Detokenizer],
-                 timestamp_decoder: Optional[Callable[[int], str]],
-                 rpc_timeout_s=5):
-        self.channel_id = channel_id
-        self.protos = proto_library
-        self.detokenizer = detokenizer
-
-        self.logger = DEFAULT_DEVICE_LOGGER
-        self.logger.setLevel(logging.DEBUG)  # Allow all device logs through.
-        self.timestamp_decoder = timestamp_decoder
-        self._expected_log_sequence_id = 0
-
-        callback_client_impl = callback_client.Impl(
-            default_unary_timeout_s=rpc_timeout_s,
-            default_stream_timeout_s=None,
-        )
-        self.client = HdlcRpcClient(
-            read,
-            self.protos,
-            default_channels(write),
-            lambda data: self.logger.info("%s", str(data)),
-            client_impl=callback_client_impl)
-
-        # Start listening to logs as soon as possible.
-        self.listen_to_log_stream()
-
-    def info(self) -> console_tools.ClientInfo:
-        return console_tools.ClientInfo('device', self.rpcs,
-                                        self.client.client)
-
-    @property
-    def rpcs(self) -> Any:
-        """Returns an object for accessing services on the specified channel."""
-        return next(iter(self.client.client.channels())).rpcs
-
-    def listen_to_log_stream(self):
-        """Opens a log RPC for the device's unrequested log stream.
-
-        The RPCs remain open until the server cancels or closes them, either
-        with a response or error packet.
-        """
-        self.rpcs.pw.log.Logs.Listen.open(
-            on_next=lambda _, log_entries_proto: self.
-            _log_entries_proto_parser(log_entries_proto),
-            on_completed=lambda _, status: _LOG.info(
-                'Log stream completed with status: %s', status),
-            on_error=lambda _, error: self._handle_log_stream_error(error))
-
-    def _handle_log_stream_error(self, error: Status):
-        """Resets the log stream RPC on error to avoid losing logs."""
-        _LOG.error('Log stream error: %s', error)
-
-        # Only re-request logs if the RPC was not cancelled by the client.
-        if error != Status.CANCELLED:
-            self.listen_to_log_stream()
-
-    def _handle_log_drop_count(self, drop_count: int, reason: str):
-        log_text = 'log' if drop_count == 1 else 'logs'
-        message = f'Dropped {drop_count} {log_text} due to {reason}'
-        self._emit_device_log(logging.WARNING, '', '', '', message)
-
-    def _check_for_dropped_logs(self, log_entries_proto: log_pb2.LogEntries):
-        # Count log messages received that don't use the dropped field.
-        messages_received = sum(1 if not log_proto.dropped else 0
-                                for log_proto in log_entries_proto.entries)
-        dropped_log_count = (log_entries_proto.first_entry_sequence_id -
-                             self._expected_log_sequence_id)
-        self._expected_log_sequence_id = (
-            log_entries_proto.first_entry_sequence_id + messages_received)
-        if dropped_log_count > 0:
-            self._handle_log_drop_count(dropped_log_count, 'loss at transport')
-        elif dropped_log_count < 0:
-            _LOG.error('Log sequence ID is smaller than expected')
-
-    def _log_entries_proto_parser(self, log_entries_proto: log_pb2.LogEntries):
-        self._check_for_dropped_logs(log_entries_proto)
-        for log_proto in log_entries_proto.entries:
-            decoded_timestamp = self.decode_timestamp(log_proto.timestamp)
-            # Parse level and convert to logging module level number.
-            level = (log_proto.line_level & 0x7) * 10
-            if self.detokenizer:
-                message = str(
-                    decode_optionally_tokenized(self.detokenizer,
-                                                log_proto.message))
-            else:
-                message = log_proto.message.decode("utf-8")
-            log = pw_log_tokenized.FormatStringWithMetadata(message)
-
-            # Handle dropped count.
-            if log_proto.dropped:
-                drop_reason = log_proto.message.decode("utf-8").lower(
-                ) if log_proto.message else 'enqueue failure on device'
-                self._handle_log_drop_count(log_proto.dropped, drop_reason)
-                continue
-            self._emit_device_log(level, '', decoded_timestamp, log.module,
-                                  log.message, **dict(log.fields))
-
-    def _emit_device_log(self, level: int, source_name: str, timestamp: str,
-                         module_name: str, message: str, **metadata_fields):
-        # Fields used for console table view
-        fields = metadata_fields
-        fields['source_name'] = source_name
-        fields['timestamp'] = timestamp
-        fields['msg'] = message
-        fields['module'] = module_name
-
-        # Format used for file or stdout logging.
-        self.logger.log(level,
-                        '[%s] %s %s%s',
-                        source_name,
-                        timestamp,
-                        f'{module_name} '.lstrip(),
-                        message,
-                        extra=dict(extra_metadata_fields=fields))
-
-    def decode_timestamp(self, timestamp: int) -> str:
-        """Decodes timestamp to a human-readable value.
-
-        Defaults to interpreting the input timestamp as nanoseconds since boot.
-        Devices can override this to match their timestamp units.
-        """
-        if self.timestamp_decoder:
-            return self.timestamp_decoder(timestamp)
-        return str(datetime.timedelta(seconds=timestamp / 1e9))[:-3]
diff --git a/pw_system/py/pyproject.toml b/pw_system/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/pw_system/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/pw_system/py/setup.cfg b/pw_system/py/setup.cfg
deleted file mode 100644
index b71b3b1..0000000
--- a/pw_system/py/setup.cfg
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pw_system
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Pigweed System
-
-[options]
-packages = find:
-zip_safe = False
-install_requires =
-    pw_cli
-    pw_console
-    pw_hdlc
-    pw_protobuf_compiler
-    pw_rpc
-    pw_tokenizer
-
-[options.entry_points]
-console_scripts = pw-system-console = pw_system.console:main
-
-[options.package_data]
-pw_system =
-    py.typed
diff --git a/pw_system/py/setup.py b/pw_system/py/setup.py
deleted file mode 100644
index 7308b8c..0000000
--- a/pw_system/py/setup.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""pw_console"""
-
-import setuptools  # type: ignore
-
-setuptools.setup()  # Package definition in setup.cfg
diff --git a/pw_system/socket_target_io.cc b/pw_system/socket_target_io.cc
deleted file mode 100644
index 55f1054..0000000
--- a/pw_system/socket_target_io.cc
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstdio>
-#include <mutex>
-
-#include "pw_assert/check.h"
-#include "pw_stream/socket_stream.h"
-#include "pw_stream/stream.h"
-#include "pw_system/config.h"
-#include "pw_system/io.h"
-
-namespace pw::system {
-namespace {
-
-constexpr uint16_t kPort = PW_SYSTEM_SOCKET_IO_PORT;
-
-stream::SocketStream& GetStream() {
-  static bool running = false;
-  static std::mutex socket_open_lock;
-  static stream::SocketStream socket_stream;
-  std::lock_guard guard(socket_open_lock);
-  if (!running) {
-    printf("Awaiting connection on port %d\n", static_cast<int>(kPort));
-    PW_CHECK_OK(socket_stream.Serve(kPort));
-    printf("Client connected\n");
-    running = true;
-  }
-  return socket_stream;
-}
-
-}  // namespace
-
-stream::Reader& GetReader() { return GetStream(); }
-stream::Writer& GetWriter() { return GetStream(); }
-
-}  // namespace pw::system
diff --git a/pw_system/stl_backends.gni b/pw_system/stl_backends.gni
deleted file mode 100644
index 7956f6a..0000000
--- a/pw_system/stl_backends.gni
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-PW_SYSTEM_STL_BACKENDS = {
-  pw_chrono_SYSTEM_CLOCK_BACKEND = "$dir_pw_chrono_stl:system_clock"
-  pw_chrono_SYSTEM_TIMER_BACKEND = "$dir_pw_chrono_stl:system_timer"
-  pw_sync_INTERRUPT_SPIN_LOCK_BACKEND = "$dir_pw_sync_stl:interrupt_spin_lock"
-  pw_sync_BINARY_SEMAPHORE_BACKEND = "$dir_pw_sync_stl:binary_semaphore_backend"
-  pw_sync_COUNTING_SEMAPHORE_BACKEND =
-      "$dir_pw_sync_stl:counting_semaphore_backend"
-  pw_sync_MUTEX_BACKEND = "$dir_pw_sync_stl:mutex_backend"
-  pw_sync_TIMED_MUTEX_BACKEND = "$dir_pw_sync_stl:timed_mutex_backend"
-  pw_sync_THREAD_NOTIFICATION_BACKEND =
-      "$dir_pw_sync:binary_semaphore_thread_notification_backend"
-  pw_sync_TIMED_THREAD_NOTIFICATION_BACKEND =
-      "$dir_pw_sync:binary_semaphore_timed_thread_notification_backend"
-  pw_sync_INTERRUPT_SPIN_LOCK_BACKEND = "$dir_pw_sync_stl:interrupt_spin_lock"
-  pw_thread_ID_BACKEND = "$dir_pw_thread_stl:id"
-  pw_thread_SLEEP_BACKEND = "$dir_pw_thread_stl:sleep"
-  pw_thread_THREAD_BACKEND = "$dir_pw_thread_stl:thread"
-  pw_thread_YIELD_BACKEND = "$dir_pw_thread_stl:yield"
-  pw_system_TARGET_HOOKS_BACKEND = "$dir_pw_system:stl_target_hooks"
-}
diff --git a/pw_system/stl_target_hooks.cc b/pw_system/stl_target_hooks.cc
deleted file mode 100644
index f9d7fef..0000000
--- a/pw_system/stl_target_hooks.cc
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 PW_LOG_MODULE_NAME "SYS"
-
-#include "pw_log/log.h"
-#include "pw_system/init.h"
-#include "pw_thread/sleep.h"
-#include "pw_thread/thread.h"
-#include "pw_thread_stl/options.h"
-
-namespace pw::system {
-
-const thread::Options& LogThreadOptions() {
-  static thread::stl::Options log_thread_options;
-  return log_thread_options;
-}
-
-const thread::Options& RpcThreadOptions() {
-  static thread::stl::Options rpc_thread_options;
-  return rpc_thread_options;
-}
-
-const thread::Options& WorkQueueThreadOptions() {
-  static thread::stl::Options work_queue_thread_options;
-  return work_queue_thread_options;
-}
-
-}  // namespace pw::system
-
-extern "C" int main() {
-  pw::system::Init();
-  // Sleep loop rather than return on this thread so the process isn't closed.
-  while (true) {
-    pw::this_thread::sleep_for(std::chrono::seconds(10));
-    // It's hard to tell that simulator is alive and working since nothing is
-    // logging after initial "boot," so for now log a line occasionally so
-    // users can see that the simulator is alive and well.
-    PW_LOG_INFO("Simulated device is still alive");
-    // TODO(amontanez): This thread should probably have a way to exit.
-  }
-}
diff --git a/pw_system/system_target.gni b/pw_system/system_target.gni
deleted file mode 100644
index 8fe9d43..0000000
--- a/pw_system/system_target.gni
+++ /dev/null
@@ -1,306 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_assert/backend.gni")
-import("$dir_pw_bloat/bloat.gni")
-import("$dir_pw_boot/backend.gni")
-import("$dir_pw_build/cc_library.gni")
-import("$dir_pw_chrono/backend.gni")
-import("$dir_pw_interrupt/backend.gni")
-import("$dir_pw_log/backend.gni")
-import("$dir_pw_log_string/backend.gni")
-import("$dir_pw_malloc/backend.gni")
-import("$dir_pw_sync/backend.gni")
-import("$dir_pw_sys_io/backend.gni")
-import("$dir_pw_thread/backend.gni")
-import("$dir_pw_toolchain/arm_gcc/toolchains.gni")
-import("$dir_pw_toolchain/generate_toolchain.gni")
-import("$dir_pw_toolchain/host_clang/toolchains.gni")
-import("$dir_pw_toolchain/host_gcc/toolchains.gni")
-import("$dir_pw_unit_test/test.gni")
-import("backend.gni")
-import("freertos_backends.gni")
-import("stl_backends.gni")
-
-# This scope is essentially an enum for pw_system_target's `cpu` selection.
-PW_SYSTEM_CPU = {
-  CORTEX_M4F = "cortex-m4f"
-  CORTEX_M3 = "cortex-m3"
-  CORTEX_M7F = "cortex-m7f"
-
-  # Native builds for the host CPU.
-  NATIVE = "native"
-}
-
-# This scope is essentially an enum for pw_system_target's `scheduler`
-# selection.
-PW_SYSTEM_SCHEDULER = {
-  FREERTOS = "freertos"
-
-  # Native uses the host OS's native scheduler and OS primitives as provided
-  # through the Standard Template Library.
-  NATIVE = "native"
-}
-
-# Defines a target toolchain, automatically setting many required build
-# arguments to simplify instantiation of a target.
-#
-# Args:
-#  cpu: (required) The architecture to target.
-#    Supported choices: PW_SYSTEM_CPU.CORTEX_M7F, PW_SYSTEM_CPU.CORTEX_M4F, PW_SYSTEM_CPU.CORTEX_M3, PW_SYSTEM_CPU.NATIVE
-#  scheduler: (required) The scheduler implementation and API to use for this
-#    target.
-#    Supported choices: PW_SYSTEM_SCHEDULER.FREERTOS, PW_SYSTEM_SCHEDULER.NATIVE
-#  use_pw_malloc: Whether or not to replace the default malloc implementation
-#    with pw_malloc. Defaults enabled for supported targets.
-#  link_deps: Additional link-time dependencies required for all executables.
-#    This is a list of source sets.
-#  build_args: Additional overrides for GN build arguments.
-#  global_configs: Configs that will be globally applied to all pw_source_set,
-#    pw_static_library, and pw_executable targets.
-template("pw_system_target") {
-  _OPTIMIZATION_LEVELS = {
-    SIZE_OPTIMIZED = "size_optimized"
-    SPEED_OPTIMIZED = "speed_optimized"
-    DEBUG = "debug"
-  }
-
-  # Generic defaults.
-  _default_configs = [ "$dir_pw_build:extra_strict_warnings" ]
-  if (defined(invoker.global_configs)) {
-    foreach(cfg, invoker.global_configs) {
-      _default_configs += [ get_path_info(cfg, "abspath") ]
-    }
-  }
-
-  _link_deps = [
-    "$dir_pw_assert:impl",
-    "$dir_pw_log:impl",
-  ]
-  if (defined(invoker.link_deps)) {
-    _link_deps += invoker.link_deps
-  }
-  _final_binary_extension = ""
-
-  _default_build_args = {
-    pw_system_RPC_SERVER_BACKEND = "$dir_pw_system:hdlc_rpc_server"
-    pw_system_IO_BACKEND = "$dir_pw_system:sys_io_target_io"
-
-    # TODO(amontanez): This should be set to pw_assert_log ASAP.
-    pw_assert_BACKEND = dir_pw_assert_basic
-
-    # TODO(amontanez): This should be set to pw_log_tokenized when support
-    # is added.
-    pw_log_BACKEND = dir_pw_log_basic
-
-    pw_rpc_CONFIG = "$dir_pw_rpc:use_global_mutex"
-
-    # TODO(amontanez): This should be set to a "$dir_pw_unit_test:rpc_main"
-    # when RPC is working.
-    pw_unit_test_MAIN = "$dir_pw_unit_test:logging_main"
-  }
-
-  # Populate architecture-specific build args.
-  assert(
-      defined(invoker.cpu),
-      "Please select a `cpu` for $target_name. Options: PW_SYSTEM_CPU.CORTEX_M7, PW_SYSTEM_CPU.CORTEX_M4F, PW_SYSTEM_CPU.CORTEX_M3, PW_SYSTEM_CPU.NATIVE")
-  if (invoker.cpu == PW_SYSTEM_CPU.CORTEX_M7F) {
-    _current_cpu = "arm"
-    _default_configs += [ "$dir_pw_toolchain/arm_gcc:enable_float_printf" ]
-    _arch_build_args = {
-      pw_bloat_BLOATY_CONFIG = "$dir_pw_boot_cortex_m/bloaty_config.bloaty"
-      pw_boot_BACKEND = "$dir_pw_boot_cortex_m:armv7m"
-      pw_interrupt_CONTEXT_BACKEND = "$dir_pw_interrupt_cortex_m:context_armv7m"
-    }
-
-    _final_binary_extension = ".elf"
-
-    _toolchains = [
-      {
-        toolchain_base = pw_toolchain_arm_gcc.cortex_m7f_debug
-        level_name = _OPTIMIZATION_LEVELS.DEBUG
-      },
-      {
-        toolchain_base = pw_toolchain_arm_gcc.cortex_m7f_size_optimized
-        level_name = _OPTIMIZATION_LEVELS.SIZE_OPTIMIZED
-      },
-      {
-        toolchain_base = pw_toolchain_arm_gcc.cortex_m7f_speed_optimized
-        level_name = _OPTIMIZATION_LEVELS.SPEED_OPTIMIZED
-      },
-    ]
-  } else if (invoker.cpu == PW_SYSTEM_CPU.CORTEX_M4F) {
-    _current_cpu = "arm"
-    _default_configs += [ "$dir_pw_toolchain/arm_gcc:enable_float_printf" ]
-    _arch_build_args = {
-      pw_bloat_BLOATY_CONFIG = "$dir_pw_boot_cortex_m/bloaty_config.bloaty"
-      pw_boot_BACKEND = "$dir_pw_boot_cortex_m:armv7m"
-      pw_interrupt_CONTEXT_BACKEND = "$dir_pw_interrupt_cortex_m:context_armv7m"
-    }
-
-    _final_binary_extension = ".elf"
-
-    _toolchains = [
-      {
-        toolchain_base = pw_toolchain_arm_gcc.cortex_m4f_debug
-        level_name = _OPTIMIZATION_LEVELS.DEBUG
-      },
-      {
-        toolchain_base = pw_toolchain_arm_gcc.cortex_m4f_size_optimized
-        level_name = _OPTIMIZATION_LEVELS.SIZE_OPTIMIZED
-      },
-      {
-        toolchain_base = pw_toolchain_arm_gcc.cortex_m4f_speed_optimized
-        level_name = _OPTIMIZATION_LEVELS.SPEED_OPTIMIZED
-      },
-    ]
-  } else if (invoker.cpu == PW_SYSTEM_CPU.CORTEX_M3) {
-    _current_cpu = "arm"
-    _arch_build_args = {
-      pw_bloat_BLOATY_CONFIG = "$dir_pw_boot_cortex_m/bloaty_config.bloaty"
-      pw_boot_BACKEND = "$dir_pw_boot_cortex_m:armv7m"
-      pw_interrupt_CONTEXT_BACKEND = "$dir_pw_interrupt_cortex_m:context_armv7m"
-    }
-
-    _final_binary_extension = ".elf"
-
-    _toolchains = [
-      {
-        toolchain_base = pw_toolchain_arm_gcc.cortex_m3_debug
-        level_name = _OPTIMIZATION_LEVELS.DEBUG
-      },
-      {
-        toolchain_base = pw_toolchain_arm_gcc.cortex_m3_size_optimized
-        level_name = _OPTIMIZATION_LEVELS.SIZE_OPTIMIZED
-      },
-      {
-        toolchain_base = pw_toolchain_arm_gcc.cortex_m3_speed_optimized
-        level_name = _OPTIMIZATION_LEVELS.SPEED_OPTIMIZED
-      },
-    ]
-  } else if (invoker.cpu == PW_SYSTEM_CPU.NATIVE) {
-    _current_cpu = host_cpu
-    _arch_build_args = {
-      pw_log_BACKEND = dir_pw_log_string
-      pw_log_string_HANDLER_BACKEND = "$dir_pw_system:log_backend"
-      pw_sys_io_BACKEND = "$dir_pw_sys_io_stdio"
-      pw_system_IO_BACKEND = "$dir_pw_system:socket_target_io"
-    }
-    _link_deps += [ "$dir_pw_log_string:handler.impl" ]
-
-    if (host_os != "win") {
-      _toolchains = [
-        {
-          toolchain_base = pw_toolchain_host_clang.debug
-          level_name = _OPTIMIZATION_LEVELS.DEBUG
-        },
-        {
-          toolchain_base = pw_toolchain_host_clang.size_optimized
-          level_name = _OPTIMIZATION_LEVELS.SIZE_OPTIMIZED
-        },
-        {
-          toolchain_base = pw_toolchain_host_clang.speed_optimized
-          level_name = _OPTIMIZATION_LEVELS.SPEED_OPTIMIZED
-        },
-      ]
-    } else {
-      _toolchains = [
-        {
-          toolchain_base = pw_toolchain_host_gcc.debug
-          level_name = _OPTIMIZATION_LEVELS.DEBUG
-        },
-        {
-          toolchain_base = pw_toolchain_host_gcc.size_optimized
-          level_name = _OPTIMIZATION_LEVELS.SIZE_OPTIMIZED
-        },
-        {
-          toolchain_base = pw_toolchain_host_gcc.speed_optimized
-          level_name = _OPTIMIZATION_LEVELS.SPEED_OPTIMIZED
-        },
-      ]
-    }
-  }
-  assert(defined(_arch_build_args),
-         "Unknown cpu choice for $target_name: `${invoker.cpu}`")
-
-  # Populate OS-specific build args.
-  assert(
-      defined(invoker.scheduler),
-      "Please select an `scheduler` for $target_name. Options: PW_SYSTEM_SCHEDULER.FREERTOS, PW_SYSTEM_SCHEDULER.NATIVE")
-  if (invoker.scheduler == PW_SYSTEM_SCHEDULER.FREERTOS) {
-    _current_os = "freertos"
-    _os_build_args = PW_SYSTEM_FREERTOS_BACKENDS
-  } else if (invoker.scheduler == PW_SYSTEM_SCHEDULER.NATIVE) {
-    _current_os = host_os
-    _os_build_args = PW_SYSTEM_STL_BACKENDS
-  }
-  assert(defined(_os_build_args),
-         "Unknown scheduler choice for $target_name: `${invoker.scheduler}`")
-
-  # Configure malloc defaults.
-  _use_pw_malloc = false
-  if (defined(invoker.use_pw_malloc)) {
-    _use_pw_malloc = invoker.use_pw_malloc
-  } else if (invoker.cpu != PW_SYSTEM_CPU.NATIVE) {
-    _use_pw_malloc = true
-  }
-
-  if (_use_pw_malloc) {
-    _default_configs += [ "$dir_pw_malloc:pw_malloc_wrapper_config" ]
-    _link_deps += [ dir_pw_malloc ]
-    _malloc_build_args = {
-      pw_malloc_BACKEND = dir_pw_malloc_freelist
-    }
-  } else {
-    _malloc_build_args = {
-    }
-  }
-
-  foreach(toolchain_and_level, _toolchains) {
-    # Clear from previous iteration.
-    _base = {
-    }
-    _base = toolchain_and_level.toolchain_base
-
-    generate_toolchain("${target_name}.${toolchain_and_level.level_name}") {
-      forward_variables_from(_base,
-                             "*",
-                             [
-                               "defaults",
-                               "name",
-                             ])
-      final_binary_extension = _final_binary_extension
-      defaults = {
-        current_os = _current_os
-        current_cpu = _current_cpu
-        forward_variables_from(_base.defaults, "*")
-        forward_variables_from(_default_build_args, "*")
-        forward_variables_from(_arch_build_args, "*")
-        forward_variables_from(_os_build_args, "*")
-        forward_variables_from(_malloc_build_args, "*")
-        default_configs += _default_configs
-        if (!defined(pw_build_LINK_DEPS)) {
-          pw_build_LINK_DEPS = []
-        }
-        pw_build_LINK_DEPS += _link_deps
-
-        if (defined(invoker.build_args)) {
-          forward_variables_from(invoker.build_args, "*")
-        }
-      }
-    }
-  }
-}
diff --git a/pw_system/target_io.cc b/pw_system/target_io.cc
deleted file mode 100644
index 4bbba5d..0000000
--- a/pw_system/target_io.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_stream/stream.h"
-#include "pw_stream/sys_io_stream.h"
-#include "pw_system/io.h"
-
-namespace pw::system {
-namespace {
-
-stream::SysIoWriter writer;
-stream::SysIoReader reader;
-
-}  // namespace
-
-stream::Reader& GetReader() { return reader; }
-stream::Writer& GetWriter() { return writer; }
-
-}  // namespace pw::system
diff --git a/pw_system/work_queue.cc b/pw_system/work_queue.cc
deleted file mode 100644
index 1c7a054..0000000
--- a/pw_system/work_queue.cc
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_system/work_queue.h"
-
-#include "pw_system/config.h"
-#include "pw_work_queue/work_queue.h"
-
-namespace pw::system {
-
-// TODO(pwbug/590): Consider switching this to a "NoDestroy" wrapped type to
-// allow the static destructor to be optimized out.
-work_queue::WorkQueue& GetWorkQueue() {
-  static constexpr size_t kMaxWorkQueueEntries =
-      PW_SYSTEM_WORK_QUEUE_MAX_ENTRIES;
-  static pw::work_queue::WorkQueueWithBuffer<kMaxWorkQueueEntries> work_queue;
-  return work_queue;
-}
-
-}  // namespace pw::system
diff --git a/pw_target_runner/OWNERS b/pw_target_runner/OWNERS
deleted file mode 100644
index 3afb926..0000000
--- a/pw_target_runner/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-frolv@google.com
diff --git a/pw_target_runner/docs.rst b/pw_target_runner/docs.rst
index 25b210d..28f20e9 100644
--- a/pw_target_runner/docs.rst
+++ b/pw_target_runner/docs.rst
@@ -56,7 +56,7 @@
   runner {
     command: "stm32f429i_disc1_unit_test_runner"
     args: "--openocd-config"
-    args: "targets/stm32f429i_disc1/py/stm32f429i_disc1_utils/openocd_stm32f4xx.cfg"
+    args: "targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/openocd_stm32f4xx.cfg"
     args: "--serial"
     args: "066DFF575051717867013127"
   }
@@ -64,7 +64,7 @@
   runner {
     command: "stm32f429i_disc1_unit_test_runner"
     args: "--openocd-config"
-    args: "targets/stm32f429i_disc1/py/stm32f429i_disc1_utils/openocd_stm32f4xx.cfg"
+    args: "targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/openocd_stm32f4xx.cfg"
     args: "--serial"
     args: "0667FF494849887767196023"
   }
diff --git a/pw_thread/BUILD b/pw_thread/BUILD
new file mode 100644
index 0000000..71957c0
--- /dev/null
+++ b/pw_thread/BUILD
@@ -0,0 +1,221 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+# TODO(pwbug/101): Need to add support for facades/backends to Bazel.
+PW_THREAD_ID_BACKEND = "//pw_thread_stl:id"
+PW_THREAD_SLEEP_BACKEND = "//pw_thread_stl:sleep"
+PW_THREAD_THREAD_BACKEND = "//pw_thread_stl:thread"
+PW_THREAD_YIELD_BACKEND = "//pw_thread_stl:yield"
+
+pw_cc_library(
+    name = "id_facade",
+    hdrs = [
+        "public/pw_thread/id.h",
+    ],
+    includes = ["public"],
+    deps = [
+        PW_THREAD_ID_BACKEND + "_headers",
+    ],
+)
+
+pw_cc_library(
+    name = "id",
+    deps = [
+        ":id_facade",
+        PW_THREAD_ID_BACKEND + "_headers",
+    ],
+)
+
+pw_cc_library(
+    name = "id_backend",
+    deps = [
+       PW_THREAD_ID_BACKEND,
+    ],
+)
+
+pw_cc_library(
+    name = "sleep_facade",
+    hdrs = [
+        "public/pw_thread/sleep.h",
+    ],
+    includes = ["public"],
+    srcs = [
+        "sleep.cc"
+    ],
+    deps = [
+        PW_THREAD_SLEEP_BACKEND + "_headers",
+        "//pw_chrono:system_clock",
+        "//pw_preprocessor",
+    ],
+)
+
+pw_cc_library(
+    name = "sleep",
+    deps = [
+        ":sleep_facade",
+        PW_THREAD_SLEEP_BACKEND + "_headers",
+    ],
+)
+
+pw_cc_library(
+    name = "sleep_backend",
+    deps = [
+       PW_THREAD_SLEEP_BACKEND,
+    ],
+)
+
+pw_cc_library(
+    name = "thread_facade",
+    hdrs = [
+        "public/pw_thread/thread.h",
+    ],
+    includes = ["public"],
+    deps = [
+        ":id_facade",
+        PW_THREAD_THREAD_BACKEND + "_headers",
+    ],
+)
+
+pw_cc_library(
+    name = "thread",
+    deps = [
+        ":thread_facade",
+        ":thread_core",
+        PW_THREAD_THREAD_BACKEND + "_headers",
+    ],
+    srcs = [
+        "thread.cc"
+    ],
+)
+
+pw_cc_library(
+    name = "thread_backend",
+    deps = [
+       PW_THREAD_THREAD_BACKEND,
+    ],
+)
+
+pw_cc_library(
+    name = "thread_core",
+    hdrs = [
+        "public/pw_thread/thread_core.h",
+    ],
+    includes = ["public"],
+)
+
+pw_cc_library(
+    name = "yield_facade",
+    hdrs = [
+        "public/pw_thread/yield.h",
+    ],
+    includes = ["public"],
+    srcs = [
+        "yield.cc"
+    ],
+    deps = [
+        PW_THREAD_YIELD_BACKEND + "_headers",
+        "//pw_preprocessor",
+    ],
+)
+
+pw_cc_library(
+    name = "yield",
+    deps = [
+        ":yield_facade",
+        PW_THREAD_YIELD_BACKEND + "_headers",
+    ],
+)
+
+pw_cc_library(
+    name = "yield_backend",
+    deps = [
+       PW_THREAD_YIELD_BACKEND,
+    ],
+)
+
+pw_cc_library(
+    name = "test_threads_header",
+    hdrs = [
+        "public/pw_thread/test_threads.h",
+    ],
+    deps = [
+        ":thread",
+    ],
+)
+
+# To instantiate this as a pw_cc_test, depend on this pw_cc_library and the
+# pw_cc_library which implements the backend for test_threads_header. See
+# //pw_thread:thread_backend_test as an example.
+pw_cc_library(
+    name = "thread_facade_test",
+    srcs = [
+        "thread_facade_test.cc",
+    ],
+    deps = [
+        ":thread",
+        ":id",
+        ":test_threads_header",
+        "//pw_chrono:system_clock",
+        "//pw_sync:binary_semaphore",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "id_facade_test",
+    srcs = [
+        "id_facade_test.cc",
+    ],
+    deps = [
+        ":id",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "sleep_facade_test",
+    srcs = [
+        "sleep_facade_test.cc",
+        "sleep_facade_test_c.c",
+    ],
+    deps = [
+        ":sleep",
+        "//pw_chrono:system_clock",
+        "//pw_preprocessor",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "yield_facade_test",
+    srcs = [
+        "yield_facade_test.cc",
+        "yield_facade_test_c.c",
+    ],
+    deps = [
+        ":yield",
+        "//pw_preprocessor",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_thread/BUILD.bazel b/pw_thread/BUILD.bazel
deleted file mode 100644
index 6b2daa6..0000000
--- a/pw_thread/BUILD.bazel
+++ /dev/null
@@ -1,251 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_facade",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_facade(
-    name = "id_facade",
-    hdrs = [
-        "public/pw_thread/id.h",
-    ],
-    includes = ["public"],
-)
-
-pw_cc_library(
-    name = "id",
-    deps = [
-        ":id_facade",
-        "@pigweed_config//:pw_thread_id_backend",
-    ],
-)
-
-pw_cc_library(
-    name = "id_backend_multiplexer",
-    visibility = ["@pigweed_config//:__pkg__"],
-    deps = select({
-        "//pw_build/constraints/rtos:embos": ["//pw_thread_embos:id"],
-        "//pw_build/constraints/rtos:freertos": ["//pw_thread_freertos:id"],
-        "//pw_build/constraints/rtos:threadx": ["//pw_thread_threadx:id"],
-        "//conditions:default": ["//pw_thread_stl:id"],
-    }),
-)
-
-pw_cc_facade(
-    name = "sleep_facade",
-    hdrs = [
-        "public/pw_thread/sleep.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_chrono:system_clock",
-        "//pw_preprocessor",
-    ],
-)
-
-pw_cc_library(
-    name = "sleep",
-    srcs = [
-        "sleep.cc",
-    ],
-    deps = [
-        ":id",
-        ":sleep_facade",
-        "@pigweed_config//:pw_thread_sleep_backend",
-    ],
-)
-
-pw_cc_library(
-    name = "sleep_backend_multiplexer",
-    visibility = ["@pigweed_config//:__pkg__"],
-    deps = select({
-        "//pw_build/constraints/rtos:embos": ["//pw_thread_embos:sleep"],
-        "//pw_build/constraints/rtos:freertos": ["//pw_thread_freertos:sleep"],
-        "//pw_build/constraints/rtos:threadx": ["//pw_thread_threadx:sleep"],
-        "//conditions:default": ["//pw_thread_stl:sleep"],
-    }),
-)
-
-pw_cc_facade(
-    name = "thread_facade",
-    hdrs = [
-        "public/pw_thread/detached_thread.h",
-        "public/pw_thread/thread.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":id_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "thread",
-    srcs = [
-        "thread.cc",
-    ],
-    hdrs = ["public/pw_thread/config.h"],
-    includes = ["public"],
-    deps = [
-        ":id",
-        ":thread_core",
-        ":thread_facade",
-        "@pigweed_config//:pw_thread_thread_backend",
-    ],
-)
-
-pw_cc_library(
-    name = "thread_backend_multiplexer",
-    visibility = ["@pigweed_config//:__pkg__"],
-    deps = select({
-        "//pw_build/constraints/rtos:embos": ["//pw_thread_embos:thread"],
-        "//pw_build/constraints/rtos:freertos": ["//pw_thread_freertos:thread"],
-        "//pw_build/constraints/rtos:threadx": ["//pw_thread_threadx:thread"],
-        "//conditions:default": ["//pw_thread_stl:thread"],
-    }),
-)
-
-pw_cc_library(
-    name = "thread_core",
-    hdrs = [
-        "public/pw_thread/thread_core.h",
-    ],
-    includes = ["public"],
-)
-
-pw_cc_facade(
-    name = "yield_facade",
-    hdrs = [
-        "public/pw_thread/yield.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_preprocessor",
-    ],
-)
-
-pw_cc_library(
-    name = "yield",
-    srcs = [
-        "yield.cc",
-    ],
-    deps = [
-        ":id",
-        ":yield_facade",
-        "@pigweed_config//:pw_thread_yield_backend",
-    ],
-)
-
-pw_cc_library(
-    name = "yield_backend_multiplexer",
-    visibility = ["@pigweed_config//:__pkg__"],
-    deps = select({
-        "//pw_build/constraints/rtos:embos": ["//pw_thread_embos:yield"],
-        "//pw_build/constraints/rtos:freertos": ["//pw_thread_freertos:yield"],
-        "//pw_build/constraints/rtos:threadx": ["//pw_thread_threadx:yield"],
-        "//conditions:default": ["//pw_thread_stl:yield"],
-    }),
-)
-
-pw_cc_library(
-    name = "snapshot",
-    srcs = [
-        "snapshot.cc",
-    ],
-    hdrs = [
-        "public/pw_thread/snapshot.h",
-    ],
-    deps = [
-        ":util",
-        "//pw_bytes",
-        "//pw_function",
-        "//pw_log",
-        "//pw_protobuf",
-        "//pw_status",
-        "//pw_thread:protos",
-    ],
-)
-
-pw_cc_library(
-    name = "test_threads_header",
-    hdrs = [
-        "public/pw_thread/test_threads.h",
-    ],
-    deps = [
-        ":thread",
-    ],
-)
-
-# To instantiate this as a pw_cc_test, depend on this pw_cc_library and the
-# pw_cc_library which implements the backend for test_threads_header. See
-# //pw_thread:thread_backend_test as an example.
-pw_cc_library(
-    name = "thread_facade_test",
-    srcs = [
-        "thread_facade_test.cc",
-    ],
-    deps = [
-        ":id",
-        ":test_threads_header",
-        ":thread",
-        "//pw_chrono:system_clock",
-        "//pw_sync:binary_semaphore",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "id_facade_test",
-    srcs = [
-        "id_facade_test.cc",
-    ],
-    deps = [
-        ":id",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "sleep_facade_test",
-    srcs = [
-        "sleep_facade_test.cc",
-        "sleep_facade_test_c.c",
-    ],
-    deps = [
-        ":sleep",
-        "//pw_chrono:system_clock",
-        "//pw_preprocessor",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "yield_facade_test",
-    srcs = [
-        "yield_facade_test.cc",
-        "yield_facade_test_c.c",
-    ],
-    deps = [
-        ":yield",
-        "//pw_preprocessor",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_thread/BUILD.gn b/pw_thread/BUILD.gn
index 69a9c86..ccd04f3 100644
--- a/pw_thread/BUILD.gn
+++ b/pw_thread/BUILD.gn
@@ -15,32 +15,16 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_build/facade.gni")
-import("$dir_pw_build/module_config.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_protobuf_compiler/proto.gni")
 import("$dir_pw_unit_test/test.gni")
 import("backend.gni")
 
-declare_args() {
-  # The build target that overrides the default configuration options for this
-  # module. This should point to a source set that provides defines through a
-  # public config (which may -include a file or add defines directly).
-  pw_thread_CONFIG = pw_build_DEFAULT_MODULE_CONFIG
-}
-
 config("public_include_path") {
   include_dirs = [ "public" ]
   visibility = [ ":*" ]
 }
 
-pw_source_set("config") {
-  public_deps = [ pw_thread_CONFIG ]
-  public = [ "public/pw_thread/config.h" ]
-  public_configs = [ ":public_include_path" ]
-  visibility = [ ":*" ]
-}
-
 pw_facade("id") {
   backend = pw_thread_ID_BACKEND
   public_configs = [ ":public_include_path" ]
@@ -61,10 +45,7 @@
 pw_facade("thread") {
   backend = pw_thread_THREAD_BACKEND
   public_configs = [ ":public_include_path" ]
-  public = [
-    "public/pw_thread/detached_thread.h",
-    "public/pw_thread/thread.h",
-  ]
+  public = [ "public/pw_thread/thread.h" ]
   public_deps = [
     ":id",
     ":thread_core",
@@ -85,23 +66,6 @@
   sources = [ "yield.cc" ]
 }
 
-pw_source_set("snapshot") {
-  public_configs = [ ":public_include_path" ]
-  public_deps = [
-    "$dir_pw_thread:protos.pwpb",
-    dir_pw_bytes,
-    dir_pw_function,
-    dir_pw_protobuf,
-    dir_pw_status,
-  ]
-  public = [ "public/pw_thread/snapshot.h" ]
-  sources = [ "snapshot.cc" ]
-  deps = [
-    ":config",
-    dir_pw_log,
-  ]
-}
-
 pw_test_group("tests") {
   tests = [
     ":id_facade_test",
@@ -129,26 +93,28 @@
   ]
 }
 
-pw_source_set("test_threads") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_thread/test_threads.h" ]
-  public_deps = [ ":thread" ]
-}
+if (pw_thread_THREAD_BACKEND != "") {
+  pw_source_set("test_threads") {
+    public_configs = [ ":public_include_path" ]
+    public = [ "public/pw_thread/test_threads.h" ]
+    public_deps = [ ":thread" ]
+  }
 
-# To instantiate this facade test based on a selected backend to provide
-# test_threads you can create a pw_test target which depends on this
-# pw_source_set and a pw_source_set which provides the implementation of
-# test_threads. See "$dir_pw_thread_stl:thread_backend_test" as an example.
-pw_source_set("thread_facade_test") {
-  sources = [ "thread_facade_test.cc" ]
-  deps = [
-    ":id",
-    ":sleep",
-    ":test_threads",
-    ":thread",
-    "$dir_pw_sync:binary_semaphore",
-    dir_pw_unit_test,
-  ]
+  # To instantiate this facade test based on a selected backend to provide
+  # test_threads you can create a pw_test target which depends on this
+  # pw_source_set and a pw_source_set which provides the implementation of
+  # test_threads. See "$dir_pw_thread_stl:thread_backend_test" as an example.
+  pw_source_set("thread_facade_test") {
+    sources = [ "thread_facade_test.cc" ]
+    deps = [
+      ":id",
+      ":sleep",
+      ":test_threads",
+      ":thread",
+      "$dir_pw_sync:binary_semaphore",
+      dir_pw_unit_test,
+    ]
+  }
 }
 
 pw_test("yield_facade_test") {
@@ -163,11 +129,6 @@
   ]
 }
 
-pw_proto_library("protos") {
-  sources = [ "pw_thread_protos/thread.proto" ]
-  deps = [ "$dir_pw_tokenizer:proto" ]
-}
-
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
diff --git a/pw_thread/CMakeLists.txt b/pw_thread/CMakeLists.txt
deleted file mode 100644
index 0ad8e33..0000000
--- a/pw_thread/CMakeLists.txt
+++ /dev/null
@@ -1,169 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-
-pw_add_module_config(pw_thread_CONFIG)
-
-pw_add_module_library(pw_thread.config
-  HEADERS
-    public/pw_thread/config.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    ${pw_thread_CONFIG}
-)
-
-pw_add_facade(pw_thread.id
-  HEADERS
-    public/pw_thread/id.h
-  PUBLIC_INCLUDES
-    public
-)
-
-pw_add_facade(pw_thread.sleep
-  HEADERS
-    public/pw_thread/sleep.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_chrono.system_clock
-    pw_preprocessor
-  SOURCES
-    sleep.cc
-)
-
-pw_add_facade(pw_thread.thread
-  HEADERS
-    public/pw_thread/detached_thread.h
-    public/pw_thread/thread.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_thread.thread_core
-    pw_thread.id
-  SOURCES
-    thread.cc
-)
-
-pw_add_module_library(pw_thread.thread_core
-  HEADERS
-    public/pw_thread/thread_core.h
-  PUBLIC_INCLUDES
-    public
-)
-
-pw_add_facade(pw_thread.yield
-  HEADERS
-    public/pw_thread/yield.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_preprocessor
-  SOURCES
-    yield.cc
-)
-
-pw_add_module_library(pw_thread.snapshot
-  HEADERS
-    public/pw_thread/snapshot.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_bytes
-    pw_function
-    pw_protobuf
-    pw_status
-    pw_thread.protos.pwpb
-  SOURCES
-    snapshot.cc
-  PRIVATE_DEPS
-    pw_thread.config
-    pw_log
-)
-
-pw_proto_library(pw_thread.protos
-  SOURCES
-    pw_thread_protos/thread.proto
-  DEPS
-    pw_tokenizer.proto
-)
-
-if(NOT "${pw_thread.id_BACKEND}" STREQUAL "pw_thread.id.NO_BACKEND_SET")
-  pw_add_test(pw_thread.id_facade_test
-    SOURCES
-      id_facade_test.cc
-    DEPS
-      pw_thread.id
-    GROUPS
-      modules
-      pw_thread
-  )
-endif()
-
-if((NOT "${pw_thread.id_BACKEND}" STREQUAL "pw_thread.id.NO_BACKEND_SET") AND
-   (NOT "${pw_thread.sleep_BACKEND}" STREQUAL "pw_thread.sleep.NO_BACKEND_SET"))
-  pw_add_test(pw_thread.sleep_facade_test
-    SOURCES
-      sleep_facade_test.cc
-      sleep_facade_test_c.c
-    DEPS
-      pw_chrono.system_clock
-      pw_thread.id
-      pw_thread.sleep
-    GROUPS
-      modules
-      pw_thread
-  )
-endif()
-
-pw_add_module_library(pw_thread.test_threads
-  HEADERS
-    public/pw_thread/test_threads.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_thread.thread
-)
-
-# To instantiate this facade test based on a selected backend to provide
-# test_threads you can create a pw_add_test target which depends on this
-# target and a target which provides the implementation of
-# test_threads. See pw_thread_stl.thread_backend_test as an example.
-pw_add_module_library(pw_thread.thread_facade_test
-  SOURCES
-    thread_facade_test.cc
-  PRIVATE_DEPS
-    pw_thread.id
-    pw_thread.sleep
-    pw_thread.test_threads
-    pw_thread.thread
-    pw_sync.binary_semaphore
-    pw_unit_test
-)
-
-if((NOT "${pw_thread.id_BACKEND}" STREQUAL "pw_thread.id.NO_BACKEND_SET") AND
-   (NOT "${pw_thread.yield_BACKEND}" STREQUAL "pw_thread.yield.NO_BACKEND_SET"))
-  pw_add_test(pw_thread.yield_facade_test
-    SOURCES
-      yield_facade_test.cc
-      yield_facade_test_c.c
-    DEPS
-      pw_thread.id
-      pw_thread.yield
-    GROUPS
-      modules
-      pw_thread
-  )
-endif()
diff --git a/pw_thread/OWNERS b/pw_thread/OWNERS
deleted file mode 100644
index e9a6c58..0000000
--- a/pw_thread/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-ewout@google.com
-hepler@google.com
diff --git a/pw_thread/docs.rst b/pw_thread/docs.rst
index ebc77b5..03b1a5a 100644
--- a/pw_thread/docs.rst
+++ b/pw_thread/docs.rst
@@ -6,146 +6,14 @@
 The ``pw_thread`` module contains utilities for thread creation and thread
 execution.
 
+.. contents::
+   :local:
+   :depth: 2
+
 .. Warning::
   This module is still under construction, the API is not yet stable.
 
 ---------------
-Thread Sleeping
----------------
-C++
-===
-.. cpp:function:: void pw::this_thread::sleep_for(chrono::SystemClock::duration sleep_duration)
-
-   Blocks the execution of the current thread for at least the specified
-   duration. This function may block for longer due to scheduling or resource
-   contention delays.
-
-   A sleep duration of 0 will at minimum yield, meaning it will provide a hint
-   to the implementation to reschedule the execution of threads, allowing other
-   threads to run.
-
-   **Precondition:** This can only be called from a thread, meaning the
-   scheduler is running.
-
-.. cpp:function:: void pw::this_thread::sleep_until(chrono::SystemClock::time_point wakeup_time)
-
-   Blocks the execution of the current thread until at least the specified
-   time has been reached. This function may block for longer due to scheduling
-   or resource contention delays.
-
-   A sleep deadline in the past up to the current time will at minimum yield
-   meaning it will provide a hint to the implementation to reschedule the
-   execution of threads, allowing other threads to run.
-
-   **Precondition:** This can only be called from a thread, meaning the
-   scheduler is running.
-
-Examples in C++
----------------
-.. code-block:: cpp
-
-  #include <chrono>
-
-  #include "pw_chrono/system_clock.h"
-  #include "pw_thread/sleep.h"
-
-  using std::literals::chrono_literals::ms;
-
-  void FunctionInvokedByThread() {
-    pw::this_thread::sleep_for(42ms);
-  }
-
-  void AnotherFunctionInvokedByThread() {
-    pw::this_thread::sleep_until(pw::chrono::SystemClock::now() + 42ms);
-  }
-
-C
-=
-.. cpp:function:: void pw_this_thread_SleepFor(pw_chrono_SystemClock_Duration sleep_duration)
-
-   Invokes ``pw::this_thread::sleep_until(sleep_duration)``.
-
-.. cpp:function:: void pw_this_thread_SleepUntil(pw_chrono_SystemClock_TimePoint wakeup_time)
-
-   Invokes ``pw::this_thread::sleep_until(wakeup_time)``.
-
-
----------------
-Thread Yielding
----------------
-C++
-===
-.. cpp:function:: void pw::this_thread::yield() noexcept
-
-   Provides a hint to the implementation to reschedule the execution of threads,
-   allowing other threads to run.
-
-   The exact behavior of this function depends on the implementation, in
-   particular on the mechanics of the OS scheduler in use and the state of the
-   system.
-
-   **Precondition:** This can only be called from a thread, meaning the
-   scheduler is running.
-
-Example in C++
----------------
-.. code-block:: cpp
-
-  #include "pw_thread/yield.h"
-
-  void FunctionInvokedByThread() {
-    pw::this_thread::yield();
-  }
-
-C
-=
-.. cpp:function:: void pw_this_thread_Yield(void)
-
-   Invokes ``pw::this_thread::yield()``.
-
----------------------
-Thread Identification
----------------------
-The class ``pw::thread::Id`` is a lightweight, trivially copyable class that
-serves as a unique identifier of Thread objects.
-
-Instances of this class may also hold the special distinct value that does
-not represent any thread. Once a thread has finished, the value of its
-Thread::id may be reused by another thread.
-
-This class is designed for use as key in associative containers, both ordered
-and unordered.
-
-Although the current API is similar to C++11 STL
-`std::thread::id <https://en.cppreference.com/w/cpp/thread/thread/id>`_, it is
-missing the required hashing and streaming operators and may diverge further in
-the future.
-
-A thread's identification (``pw::thread::Id``) can be acquired only in C++ in
-one of two ways:
-
-1) Using the ``pw::thread::Thread`` handle's ``pw::thread::Id get_id() const``
-   method.
-2) While executing the thread using
-   ``pw::thread::Id pw::this_thread::get_id() noexcept``.
-
-.. cpp:function:: pw::thread::Id pw::this_thread::get_id() noexcept
-
-   This is thread safe, not IRQ safe. It is implementation defined whether this
-   is safe before the scheduler has started.
-
-
-Example
-=======
-.. code-block:: cpp
-
-  #include "pw_thread/id.h"
-
-  void FunctionInvokedByThread() {
-    const pw::thread::Id my_id = pw::this_thread::get_id();
-  }
-
----------------
 Thread Creation
 ---------------
 The class ``pw::thread::Thread`` can represent a single thread of execution.
@@ -159,13 +27,6 @@
 ``pw::thread::ThreadCore`` objects and functions which match the
 ``pw::thread::Thread::ThreadRoutine`` signature.
 
-We recognize that the C++11's STL ``std::thread``` API has some drawbacks where
-it is easy to forget to join or detach the thread handle. Because of this, we
-offer helper wrappers like the ``pw::thread::DetachedThread``. Soon we will
-extend this by also adding a ``pw::thread::JoiningThread`` helper wrapper which
-will also have a lighter weight C++20 ``std::jthread`` like cooperative
-cancellation contract to make joining safer and easier.
-
 Threads may begin execution immediately upon construction of the associated
 thread object (pending any OS scheduling delays), starting at the top-level
 function provided as a constructor argument. The return value of the
@@ -190,7 +51,7 @@
   * - ThreadX
     - :ref:`module-pw_thread_threadx`
   * - embOS
-    - :ref:`module-pw_thread_embos`
+    - Planned
   * - STL
     - :ref:`module-pw_thread_stl`
   * - Zephyr
@@ -198,16 +59,6 @@
   * - CMSIS-RTOS API v2 & RTX5
     - Planned
 
-Module Configuration Options
-============================
-The following configurations can be adjusted via compile-time configuration of
-this module, see the
-:ref:`module documentation <module-structure-compile-time-configuration>` for
-more details.
-
-.. c:macro:: PW_THREAD_CONFIG_LOG_LEVEL
-
-  The log level to use for this module. Logs below this level are omitted.
 
 Options
 =======
@@ -234,85 +85,12 @@
 
 Please see the thread creation backend documentation for how their Options work.
 
-Portable Thread Creation
-========================
-Due to the fact that ``pw::thread::Options`` cannot be created in portable code,
-some extra work must be done in order to permit portable thread creation.
-Namely, a reference to the portable ``pw::thread::Options`` base class interface
-must be provided through a header or extern which points to an instantiation in
-non-portable code.
-
-This can be most easily done through a facade and set of backends. This approach
-can be powerful; enabling multithreaded unit/integration testing which can run
-on both the host and on a device with the device's exact thread options.
-
-Alternatively, it can also be be injected at build time by instantiating backend
-specific build rule which share the same common portable source file(s) but
-select backend specific source files and/or dependencies which provide the
-non-portable option instantiations.
-
-As an example, let's say we want to create a thread on the host and on a device
-running FreeRTOS. They could use a facade which contains a ``threads.h`` header
-with the following contents:
-
-.. code-block:: cpp
-
-  // Contents of my_app/threads.h
-  #pragma once
-
-  #include "pw_thread/options.h"
-
-  namespace my_app {
-
-  const pw::thread::Options& HellowWorldThreadOptions();
-
-  }  // namespace my_app
-
-This could then be backed by two different backend implementations based on
-the thread backend. For example for the STL the backend's ``stl_threads.cc``
-source file may look something like:
-
-.. code-block:: cpp
-
-  // Contents of my_app/stl_threads.cc
-  #include "my_app/threads.h"
-  #include "pw_thread_stl/options.h"
-
-  namespace my_app {
-
-  const pw::thread::Options& HelloWorldThreadOptions() {
-    static constexpr auto options = pw::thread::stl::Options();
-    return options;
-  }
-
-  }  // namespace my_app
-
-While for FreeRTOS the backend's ``freertos_threads.cc`` source file may look
-something like:
-
-.. code-block:: cpp
-
-  // Contents of my_app/freertos_threads.cc
-  #include "FreeRTOS.h"
-  #include "my_app/threads.h"
-  #include "pw_thread_freertos/context.h"
-  #include "pw_thread_freertos/options.h"
-  #include "task.h"
-
-  namespace my_app {
-
-  StaticContextWithStack<kHelloWorldStackWords> hello_world_thread_context;
-  const pw::thread::Options& HelloWorldThreadOptions() {
-    static constexpr auto options =
-        pw::thread::freertos::Options()
-            .set_name("HelloWorld")
-            .set_static_context(hello_world_thread_context)
-            .set_priority(kHelloWorldThreadPriority);
-    return options;
-  }
-
-  }  // namespace my_app
-
+.. Note::
+  Options have a default constructor, however default options are not portable!
+  Default options can only work if threads are dynamically allocated by default,
+  meaning default options cannot work on backends which require static thread
+  allocations. In addition on some schedulers, default options will not work
+  for other reasons.
 
 Detaching & Joining
 ===================
@@ -334,27 +112,6 @@
   A constructed ``pw::thread::Thread`` which represents a thread of execution
   must be EITHER detached or joined, else the destructor will assert!
 
-DetachedThread
-==============
-To make it slightly easier and cleaner to spawn detached threads without having
-to worry about thread handles, a wrapper ``DetachedThread()`` function is
-provided which creates a ``Thread`` and immediately detaches it. For example
-instead of:
-
-.. code-block:: cpp
-
-  Thread(options, foo).detach();
-
-You can instead use this helper wrapper to:
-
-.. code-block:: cpp
-
-   DetachedThread(options, foo);
-
-The arguments are directly forwarded to the Thread constructor and ergo exactly
-match the Thread constuctor arguments for creating a thread of execution.
-
-
 ThreadRoutine & ThreadCore
 ==========================
 Threads must either be invoked through a
@@ -435,33 +192,3 @@
   Because the thread may start after the pw::Thread creation, an object which
   implements the ThreadCore MUST meet or exceed the lifetime of its thread of
   execution!
-
------------------------
-pw_snapshot integration
------------------------
-``pw_thread`` provides some light, optional integration with pw_snapshot through
-helper functions for populating a ``pw::thread::Thread`` proto. Some of these
-are directly integrated into the RTOS thread backends to simplify the thread
-state capturing for snapshots.
-
-SnapshotStack()
-===============
-The ``SnapshotStack()`` helper captures stack metadata (stack pointer and
-bounds) into a ``pw::thread::Thread`` proto. After the stack bounds are
-captured, execution is passed off to the thread stack collection callback to
-capture a backtrace or stack dump. Note that this function does NOT capture the
-thread name: that metadata is only required in cases where a stack overflow or
-underflow is detected.
-
-Python processor
-================
-Threads captured as a Thread proto message can be dumped or further analyzed
-using using ``pw_thread``'s Python module. This is directly integrated into
-pw_snapshot's processor tool to automatically provide rich thread state dumps.
-
-The ``ThreadSnapshotAnalyzer`` class may also be used directly to identify the
-currently running thread and produce symbolized thread dumps.
-
-.. Warning::
-  Snapshot integration is a work-in-progress and may see significant API
-  changes.
diff --git a/pw_thread/public/pw_thread/config.h b/pw_thread/public/pw_thread/config.h
deleted file mode 100644
index c2c0adc..0000000
--- a/pw_thread/public/pw_thread/config.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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
-
-// The log level to use for this module. Logs below this level are omitted.
-#ifndef PW_THREAD_CONFIG_LOG_LEVEL
-#define PW_THREAD_CONFIG_LOG_LEVEL PW_LOG_LEVEL_DEBUG
-#endif  // PW_THREAD_CONFIG_LOG_LEVEL
diff --git a/pw_thread/public/pw_thread/detached_thread.h b/pw_thread/public/pw_thread/detached_thread.h
deleted file mode 100644
index 03b43cc..0000000
--- a/pw_thread/public/pw_thread/detached_thread.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_thread/thread.h"
-
-namespace pw::thread {
-
-// Creates a detached a thread of execution.
-//
-// This is a very simple helper wrapper around Thread to help the common case of
-// creating a Thread which is immediately detached. For example instead of:
-//
-//   Thread(options, foo).detach();
-//
-// You can instead use this helper wrapper to:
-//
-//   DetachedThread(options, foo);
-//
-// The arguments are directly forwarded to the Thread constructor and ergo
-// exactly match the Thread constuctor arguments for creating a thread of
-// execution.
-template <class... Args>
-void DetachedThread(const Options& options, Args&&... args) {
-  Thread(options, std::forward<Args>(args)...).detach();
-}
-
-}  // namespace pw::thread
diff --git a/pw_thread/public/pw_thread/id.h b/pw_thread/public/pw_thread/id.h
index 36f082a..ef7c1f0 100644
--- a/pw_thread/public/pw_thread/id.h
+++ b/pw_thread/public/pw_thread/id.h
@@ -17,12 +17,12 @@
 
 namespace pw::thread {
 
-// The class thread::Id is a lightweight, trivially copyable class that serves
+// The class thread::id is a lightweight, trivially copyable class that serves
 // as a unique identifier of Thread objects.
 //
 // Instances of this class may also hold the special distinct value that does
-// not represent any thread. Once a thread has finished, the value of its
-// thread::Id may be reused by another thread.
+// not represent any thread. Once a thread has finished, the value of
+// Thread::id may be reused by another thread.
 //
 // This class is designed for use as key in associative containers, both
 // ordered and unordered.
diff --git a/pw_thread/public/pw_thread/sleep.h b/pw_thread/public/pw_thread/sleep.h
index 4321484..4e1fb48 100644
--- a/pw_thread/public/pw_thread/sleep.h
+++ b/pw_thread/public/pw_thread/sleep.h
@@ -28,21 +28,19 @@
 // to the implementation to reschedule the execution of threads, allowing other
 // threads to run.
 //
-// Precondition: This can only be called from a thread, meaning the scheduler
-// is running.
-void sleep_for(chrono::SystemClock::duration sleep_duration);
+// This can only be called from a thread, meaning the scheduler is running.
+void sleep_for(chrono::SystemClock::duration for_at_least);
 
 // Blocks the execution of the current thread until at least the specified
-// time has been reached. This function may block for longer due to scheduling
-// or resource contention delays.
+// deadline. This function may block for longer due to scheduling or resource
+// contention delays.
 //
 // A sleep deadline in the past up to the current time will at minimum yield
 // meaning it will provide a hint to the implementation to reschedule the
 // execution of threads, allowing other threads to run.
 //
-// Precondition: This can only be called from a thread, meaning the scheduler
-// is running.
-void sleep_until(chrono::SystemClock::time_point wakeup_time);
+// This can only be called from a thread, meaning the scheduler is running.
+void sleep_until(chrono::SystemClock::time_point until_at_least);
 
 }  // namespace pw::this_thread
 
@@ -55,7 +53,7 @@
 
 PW_EXTERN_C_START
 
-void pw_this_thread_SleepFor(pw_chrono_SystemClock_Duration sleep_duration);
-void pw_this_thread_SleepUntil(pw_chrono_SystemClock_TimePoint wakeup_time);
+void pw_this_thread_SleepFor(pw_chrono_SystemClock_Duration for_at_least);
+void pw_this_thread_SleepUntil(pw_chrono_SystemClock_TimePoint until_at_least);
 
 PW_EXTERN_C_END
diff --git a/pw_thread/public/pw_thread/snapshot.h b/pw_thread/public/pw_thread/snapshot.h
deleted file mode 100644
index dfa96de..0000000
--- a/pw_thread/public/pw_thread/snapshot.h
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_bytes/span.h"
-#include "pw_function/function.h"
-#include "pw_protobuf/encoder.h"
-#include "pw_status/status.h"
-#include "pw_thread_protos/thread.pwpb.h"
-
-namespace pw::thread {
-
-// Stack dump callback functions populate a thread's raw_backtrace or raw_stack
-// field. This should encode either raw_backtrace or raw_stack to the provided
-// Thread stream encoder.
-using ProcessThreadStackCallback =
-    Function<Status(Thread::StreamEncoder&, ConstByteSpan)>;
-
-struct StackContext {
-  std::string_view thread_name;
-  uintptr_t stack_low_addr;
-  uintptr_t stack_high_addr;
-  uintptr_t stack_pointer;
-  std::optional<uintptr_t> stack_pointer_est_peak;
-};
-
-// Takes the provided StackContext, and writes stack context to the provided
-// Thread encoder. After stack context is captured, the thread_stack_callback is
-// invoked to capture either the raw_stack or raw_backtrace to the same encoder.
-//
-// Captures the following proto fields:
-//   pw.thread.Thread:
-//     stack_start_pointer
-//     stack_end_pointer
-//     stack_pointer
-Status SnapshotStack(const StackContext& stack,
-                     Thread::StreamEncoder& encoder,
-                     const ProcessThreadStackCallback& thread_stack_callback);
-
-}  // namespace pw::thread
diff --git a/pw_thread/public/pw_thread/thread.h b/pw_thread/public/pw_thread/thread.h
index aa9271c..f609fed 100644
--- a/pw_thread/public/pw_thread/thread.h
+++ b/pw_thread/public/pw_thread/thread.h
@@ -157,13 +157,6 @@
   //
   // Postcondition: After calling detach *this no longer owns any thread.
   void join();
-#else
-  template <typename kUnusedType = void>
-  void join() {
-    static_assert(kJoiningEnabled<kUnusedType>,
-                  "The selected pw_thread_THREAD backend does not have join() "
-                  "enabled (AKA PW_THREAD_JOINING_ENABLED = 1)");
-  }
 #endif  // PW_THREAD_JOINING_ENABLED
 
   // Separates the thread of execution from the thread object, allowing
@@ -181,10 +174,6 @@
   native_handle_type native_handle();
 
  private:
-  template <typename...>
-  static constexpr std::bool_constant<PW_THREAD_JOINING_ENABLED>
-      kJoiningEnabled = {};
-
   // Note that just like std::thread, this is effectively just a pointer or
   // reference to the native thread -- this does not contain any memory needed
   // for the thread to execute.
diff --git a/pw_thread/public/pw_thread/yield.h b/pw_thread/public/pw_thread/yield.h
index 976b7ce..fc6d89d 100644
--- a/pw_thread/public/pw_thread/yield.h
+++ b/pw_thread/public/pw_thread/yield.h
@@ -26,8 +26,7 @@
 // particular on the mechanics of the OS scheduler in use and the state of the
 // system.
 //
-// Precondition: This can only be called from a thread, meaning the scheduler
-// must be running.
+// This can only be called from a thread, meaning the scheduler is running.
 void yield() noexcept;
 
 }  // namespace pw::this_thread
diff --git a/pw_thread/pw_thread_protos/thread.proto b/pw_thread/pw_thread_protos/thread.proto
deleted file mode 100644
index bcfbb09..0000000
--- a/pw_thread/pw_thread_protos/thread.proto
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-syntax = "proto3";
-
-package pw.thread;
-
-import "pw_tokenizer/proto/options.proto";
-
-option java_package = "pw.thread.proto";
-option java_outer_classname = "Thread";
-
-message ThreadState {
-  enum Enum {
-    // Thread state is invalid or cannot be expressed by this enum.
-    UNKNOWN = 0;
-    // Interrupt handling is often done on a stack that isn't associated with a
-    // true RTOS thread. This state indicates the provided thread info is for an
-    // interrupt handler.
-    INTERRUPT_HANDLER = 1;
-    // This is the currently active thread as marked by the RTOS. In crashes in
-    // interrupt contexts, this isn’t necessarily the thread that crashed.
-    RUNNING = 2;
-    // Thread is ready to run, but isn’t currently running.
-    READY = 3;
-    // The thread is not ready to run, and will not be ready to run until it is
-    // explicitly resumed.
-    SUSPENDED = 4;
-    // The thread is waiting on something before it can run again.
-    BLOCKED = 5;
-    // The thread is either not yet initialized, or has terminated. In other
-    // words, this thread is a suspended thread that cannot be unsuspended.
-    INACTIVE = 6;
-  }
-}
-
-message Thread {
-  // Thread names must be unique; this allows extensions of Snapshot to augment
-  // threads with additional data. This should either be human readable text, or
-  // tokenized data (e.g. base-64 encoded or binary data).
-  bytes name = 1 [(tokenizer.format) = TOKENIZATION_OPTIONAL];
-
-  // This field has been deprecatdin favor of using the state enum to report
-  // RUNNING or INTERRUPT_CONTEXT to mark them as active.
-  //
-  // Whether or not this thread is the thread is the currently active context
-  // at the time of capture. For multi-thread dumps, this field should only be
-  // set on ONE thread.
-  bool active = 2 [deprecated = true];
-
-  // A summarized thread state. RTOS-specific extensions of the Thread message
-  // may provide more specific thread state information.
-  ThreadState.Enum state = 3;
-
-  // Contents of a stack trace. It is expected that this stack is pre-walked,
-  // and contains addresses. Most recent stack events are at the beginning of
-  // the captured stack trace.
-  repeated uint64 raw_backtrace = 4;
-
-  // Results of symbolizing stack_entries. This is usually not provided by the
-  // device, but instead by server/host side processing.
-  repeated string symbolized_backtrace = 5;
-
-  // This should contain the raw contents of the thread's stack. This might not
-  // match stack_size. It can be larger due to a stack overflow, or smaller due
-  // to the implementation deciding to only capture a portion of the stack.
-  // Partial stack captures are typically a result of storage/memory
-  // limitations.
-  bytes raw_stack = 6;
-
-  // The address this thread's stack pointer began at. For descending stacks,
-  // this is the highest address of the stack bounds. For ascending stacks, this
-  // is the lowest address of the stack bounds.
-  optional uint64 stack_start_pointer = 7;
-
-  // The furthest permitted address from where this thread's stack pointer
-  // began. For descending stacks, this is the lowest address of the stack
-  // bounds. For ascending stacks, this is the highest address of the stack
-  // bounds.
-  optional uint64 stack_end_pointer = 8;
-
-  // The current stack pointer of this thread.
-  optional uint64 stack_pointer = 9;
-
-  // CPU usage info. This is the percentage of CPU time the thread has been
-  // active in hundredths of a percent. (e.g. 5.00% = 500u)
-  optional uint32 cpu_usage_hundredths = 10;
-
-  // The address of highest estimated currently used in the thread stack.
-  // Percentage of bytes used can be calculated by:
-  // (stack_estimate_max_addr-stack_start_pointer) /
-  // (stack_end_pointer-stack_start_pointer) * 100%
-  optional uint64 stack_pointer_est_peak = 11;
-}
-
-// This message overlays the pw.snapshot.Snapshot proto. It's valid to encode
-// this message to the same sink that a Snapshot proto is being written to.
-message SnapshotThreadInfo {
-  repeated pw.thread.Thread threads = 18;
-}
diff --git a/pw_thread/py/BUILD.gn b/pw_thread/py/BUILD.gn
deleted file mode 100644
index f2d4bb4..0000000
--- a/pw_thread/py/BUILD.gn
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/python.gni")
-import("$dir_pw_docgen/docs.gni")
-
-pw_python_package("py") {
-  generate_setup = {
-    metadata = {
-      name = "pw_thread"
-      version = "0.0.1"
-    }
-  }
-
-  sources = [
-    "pw_thread/__init__.py",
-    "pw_thread/thread_analyzer.py",
-  ]
-  tests = [ "thread_analyzer_test.py" ]
-  python_deps = [
-    "$dir_pw_symbolizer/py",
-    "$dir_pw_tokenizer/py",
-    "..:protos.python",
-  ]
-  pylintrc = "$dir_pigweed/.pylintrc"
-}
diff --git a/pw_thread/py/pw_thread/__init__.py b/pw_thread/py/pw_thread/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/pw_thread/py/pw_thread/__init__.py
+++ /dev/null
diff --git a/pw_thread/py/pw_thread/py.typed b/pw_thread/py/pw_thread/py.typed
deleted file mode 100644
index e69de29..0000000
--- a/pw_thread/py/pw_thread/py.typed
+++ /dev/null
diff --git a/pw_thread/py/pw_thread/thread_analyzer.py b/pw_thread/py/pw_thread/thread_analyzer.py
deleted file mode 100644
index 37a3495..0000000
--- a/pw_thread/py/pw_thread/thread_analyzer.py
+++ /dev/null
@@ -1,253 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Library to analyze and dump Thread protos and Thread snapshots into text."""
-
-from typing import Optional, List, Mapping
-import pw_tokenizer
-from pw_symbolizer import LlvmSymbolizer, Symbolizer
-from pw_tokenizer import proto as proto_detokenizer
-from pw_thread_protos import thread_pb2
-
-THREAD_STATE_TO_STRING: Mapping[int, str] = {
-    thread_pb2.ThreadState.Enum.UNKNOWN: 'UNKNOWN',
-    thread_pb2.ThreadState.Enum.INTERRUPT_HANDLER: 'INTERRUPT_HANDLER',
-    thread_pb2.ThreadState.Enum.RUNNING: 'RUNNING',
-    thread_pb2.ThreadState.Enum.READY: 'READY',
-    thread_pb2.ThreadState.Enum.SUSPENDED: 'SUSPENDED',
-    thread_pb2.ThreadState.Enum.BLOCKED: 'BLOCKED',
-    thread_pb2.ThreadState.Enum.INACTIVE: 'INACTIVE',
-}
-
-
-def process_snapshot(serialized_snapshot: bytes,
-                     tokenizer_db: Optional[pw_tokenizer.Detokenizer] = None,
-                     symbolizer: Optional[Symbolizer] = None) -> str:
-    """Processes snapshot threads, producing a multi-line string."""
-    captured_threads = thread_pb2.SnapshotThreadInfo()
-    captured_threads.ParseFromString(serialized_snapshot)
-    if symbolizer is None:
-        symbolizer = LlvmSymbolizer()
-
-    return str(
-        ThreadSnapshotAnalyzer(captured_threads, tokenizer_db, symbolizer))
-
-
-class ThreadInfo:
-    """Provides CPU and stack information that can be inferred from a Thread."""
-
-    _UNKNOWN_VALUE_STR = '0x' + '?' * 8
-
-    def __init__(self, thread: thread_pb2.Thread):
-        self._thread = thread
-
-    def _cpu_used_str(self) -> str:
-        if not self._thread.HasField('cpu_usage_hundredths'):
-            return 'unknown'
-        cpu_last_percent = (self._thread.cpu_usage_hundredths / 100)
-        return f'{cpu_last_percent:.2f}%'
-
-    def _stack_size_limit_limit_str(self) -> str:
-        if not self.has_stack_size_limit():
-            return 'size unknown'
-
-        return f'{self.stack_size_limit()} bytes'
-
-    def _stack_used_str(self) -> str:
-        if not self.has_stack_used():
-            return 'size unknown'
-
-        used_str = f'{self.stack_used()} bytes'
-        if not self.has_stack_size_limit():
-            return used_str
-        used_str += f', {100*self.stack_used()/self.stack_size_limit():.2f}%'
-        return used_str
-
-    def _stack_pointer_est_peak_str(self) -> str:
-        if not self.has_stack_pointer_est_peak():
-            return 'size unknown'
-
-        high_used_str = f'{self.stack_pointer_est_peak()} bytes'
-        if not self.has_stack_size_limit():
-            return high_used_str
-        high_water_mark_percent = (100 * self.stack_pointer_est_peak() /
-                                   self.stack_size_limit())
-        high_used_str += f', {high_water_mark_percent:.2f}%'
-        return high_used_str
-
-    def _stack_used_range_str(self) -> str:
-        start_str = (f'0x{self._thread.stack_start_pointer:08x}'
-                     if self._thread.HasField('stack_start_pointer') else
-                     ThreadInfo._UNKNOWN_VALUE_STR)
-        end_str = (f'0x{self._thread.stack_pointer:08x}'
-                   if self._thread.HasField('stack_pointer') else
-                   ThreadInfo._UNKNOWN_VALUE_STR)
-
-        # TODO(amontanez): Would be nice to represent stack growth direction.
-        return f'{start_str} - {end_str} ({self._stack_used_str()})'
-
-    def _stack_limit_range_str(self) -> str:
-        start_str = (f'0x{self._thread.stack_start_pointer:08x}'
-                     if self._thread.HasField('stack_start_pointer') else
-                     ThreadInfo._UNKNOWN_VALUE_STR)
-        end_str = (f'0x{self._thread.stack_end_pointer:08x}'
-                   if self._thread.HasField('stack_end_pointer') else
-                   ThreadInfo._UNKNOWN_VALUE_STR)
-
-        # TODO(amontanez): Would be nice to represent stack growth direction.
-        return f'{start_str} - {end_str} ({self._stack_size_limit_limit_str()})'
-
-    def _stack_pointer_str(self) -> str:
-        return (f'0x{self._thread.stack_end_pointer:08x}'
-                if self._thread.HasField('stack_pointer') else
-                ThreadInfo._UNKNOWN_VALUE_STR)
-
-    def has_stack_size_limit(self) -> bool:
-        """Returns true if there's enough info to calculate stack size."""
-        return (self._thread.HasField('stack_start_pointer')
-                and self._thread.HasField('stack_end_pointer'))
-
-    def stack_size_limit(self) -> int:
-        """Returns the stack size limit in bytes.
-
-        Precondition:
-            has_stack_size_limit() must be true.
-        """
-        assert self.has_stack_size_limit(), 'Missing stack size information'
-        return abs(self._thread.stack_start_pointer -
-                   self._thread.stack_end_pointer)
-
-    def has_stack_used(self) -> bool:
-        """Returns true if there's enough info to calculate stack usage."""
-        return (self._thread.HasField('stack_start_pointer')
-                and self._thread.HasField('stack_pointer'))
-
-    def stack_used(self) -> int:
-        """Returns the stack usage in bytes.
-
-        Precondition:
-            has_stack_used() must be true.
-        """
-        assert self.has_stack_used(), 'Missing stack usage information'
-        return abs(self._thread.stack_start_pointer -
-                   self._thread.stack_pointer)
-
-    def has_stack_pointer_est_peak(self) -> bool:
-        """Returns true if there's enough info to calculate estimate
-        used stack.
-        """
-        return (self._thread.HasField('stack_start_pointer')
-                and self._thread.HasField('stack_pointer_est_peak'))
-
-    def stack_pointer_est_peak(self) -> int:
-        """Returns the max estimated used stack usage in bytes.
-
-        Precondition:
-            has_stack_estimated_used_bytes() must be true.
-        """
-        assert self.has_stack_pointer_est_peak(), 'Missing stack est. peak'
-        return abs(self._thread.stack_start_pointer -
-                   self._thread.stack_pointer_est_peak)
-
-    def __str__(self) -> str:
-        output = [
-            f'Est CPU usage: {self._cpu_used_str()}',
-            'Stack info',
-            f'  Current usage:   {self._stack_used_range_str()}',
-            f'  Est peak usage:  {self._stack_pointer_est_peak_str()}',
-            f'  Stack limits:    {self._stack_limit_range_str()}',
-        ]
-        return '\n'.join(output)
-
-
-class ThreadSnapshotAnalyzer:
-    """This class simplifies dumping contents of a snapshot Metadata message."""
-    def __init__(self,
-                 threads: thread_pb2.SnapshotThreadInfo,
-                 tokenizer_db: Optional[pw_tokenizer.Detokenizer] = None,
-                 symbolizer: Optional[Symbolizer] = None):
-        self._threads = threads.threads
-        self._tokenizer_db = (tokenizer_db if tokenizer_db is not None else
-                              pw_tokenizer.Detokenizer(None))
-        if symbolizer is not None:
-            self._symbolizer = symbolizer
-        else:
-            self._symbolizer = LlvmSymbolizer()
-
-        for thread in self._threads:
-            proto_detokenizer.detokenize_fields(self._tokenizer_db, thread)
-
-    def active_thread(self) -> Optional[thread_pb2.Thread]:
-        """The thread that requested the snapshot capture."""
-        # First check if an interrupt handler was active.
-        for thread in self._threads:
-            if thread.state == thread_pb2.ThreadState.Enum.INTERRUPT_HANDLER:
-                return thread
-            if thread.active:  # The deprecated legacy way to report this.
-                return thread
-
-        # If not, search for a running thread.
-        for thread in self._threads:
-            if thread.state == thread_pb2.ThreadState.Enum.RUNNING:
-                return thread
-
-        return None
-
-    def __str__(self) -> str:
-        """outputs a pw.snapshot.Metadata proto as a multi-line string."""
-        output: List[str] = []
-        if not self._threads:
-            return ''
-
-        output.append('Thread State')
-        plural = '' if len(self._threads) == 1 else 's'
-        thread_state_overview = f'  {len(self._threads)} thread{plural} running'
-        requesting_thread = self.active_thread()
-        if not requesting_thread:
-            thread_state_overview += '.'
-            output.append(thread_state_overview)
-        else:
-            thread_state_overview += ', '
-            underline = (' ' * len(thread_state_overview) +
-                         '~' * len(requesting_thread.name.decode()))
-            thread_state_overview += (f'{requesting_thread.name.decode()}'
-                                      ' active at the time of capture.')
-            output.append(thread_state_overview)
-            output.append(underline)
-
-        output.append('')
-
-        # Put the active thread at the front.
-        requesting_thread = self.active_thread()
-        if requesting_thread is not None:
-            self._threads.remove(requesting_thread)
-            self._threads.insert(0, requesting_thread)
-
-        for thread in self._threads:
-            thread_name = thread.name.decode()
-            if not thread_name:
-                thread_name = '[unnamed thread]'
-            thread_headline = ('Thread '
-                               f'({THREAD_STATE_TO_STRING[thread.state]}): '
-                               f'{thread_name}')
-            if self.active_thread() == thread:
-                thread_headline += ' <-- [ACTIVE]'
-            output.append(thread_headline)
-            output.append(str(ThreadInfo(thread)))
-            if thread.raw_backtrace:
-                output.append(
-                    self._symbolizer.dump_stack_trace(thread.raw_backtrace))
-            # Blank line between threads for nicer formatting.
-            output.append('')
-
-        return '\n'.join(output)
diff --git a/pw_thread/py/thread_analyzer_test.py b/pw_thread/py/thread_analyzer_test.py
deleted file mode 100644
index 0a6844f..0000000
--- a/pw_thread/py/thread_analyzer_test.py
+++ /dev/null
@@ -1,259 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests for the thread analyzer."""
-
-import unittest
-from pw_thread.thread_analyzer import ThreadInfo, ThreadSnapshotAnalyzer
-from pw_thread_protos import thread_pb2
-
-
-class ThreadInfoTest(unittest.TestCase):
-    """Tests that the ThreadInfo class produces expected results."""
-    def test_empty_thread(self):
-        thread_info = ThreadInfo(thread_pb2.Thread())
-        expected = '\n'.join(
-            ('Est CPU usage: unknown', 'Stack info',
-             '  Current usage:   0x???????? - 0x???????? (size unknown)',
-             '  Est peak usage:  size unknown',
-             '  Stack limits:    0x???????? - 0x???????? (size unknown)'))
-        self.assertFalse(thread_info.has_stack_size_limit())
-        self.assertFalse(thread_info.has_stack_used())
-        self.assertEqual(expected, str(thread_info))
-
-    def test_thread_with_cpu_usage(self):
-        thread = thread_pb2.Thread()
-        thread.cpu_usage_hundredths = 1234
-        thread_info = ThreadInfo(thread)
-
-        expected = '\n'.join(
-            ('Est CPU usage: 12.34%', 'Stack info',
-             '  Current usage:   0x???????? - 0x???????? (size unknown)',
-             '  Est peak usage:  size unknown',
-             '  Stack limits:    0x???????? - 0x???????? (size unknown)'))
-        self.assertFalse(thread_info.has_stack_size_limit())
-        self.assertFalse(thread_info.has_stack_used())
-        self.assertEqual(expected, str(thread_info))
-
-    def test_thread_with_stack_pointer(self):
-        thread = thread_pb2.Thread()
-        thread.stack_pointer = 0x5AC6A86C
-        thread_info = ThreadInfo(thread)
-
-        expected = '\n'.join(
-            ('Est CPU usage: unknown', 'Stack info',
-             '  Current usage:   0x???????? - 0x5ac6a86c (size unknown)',
-             '  Est peak usage:  size unknown',
-             '  Stack limits:    0x???????? - 0x???????? (size unknown)'))
-        self.assertFalse(thread_info.has_stack_size_limit())
-        self.assertFalse(thread_info.has_stack_used())
-        self.assertEqual(expected, str(thread_info))
-
-    def test_thread_with_stack_usage(self):
-        thread = thread_pb2.Thread()
-        thread.stack_start_pointer = 0x5AC6B86C
-        thread.stack_pointer = 0x5AC6A86C
-        thread_info = ThreadInfo(thread)
-
-        expected = '\n'.join(
-            ('Est CPU usage: unknown', 'Stack info',
-             '  Current usage:   0x5ac6b86c - 0x5ac6a86c (4096 bytes)',
-             '  Est peak usage:  size unknown',
-             '  Stack limits:    0x5ac6b86c - 0x???????? (size unknown)'))
-        self.assertFalse(thread_info.has_stack_size_limit())
-        self.assertTrue(thread_info.has_stack_used())
-        self.assertEqual(expected, str(thread_info))
-
-    def test_thread_with_all_stack_info(self):
-        thread = thread_pb2.Thread()
-        thread.stack_start_pointer = 0x5AC6B86C
-        thread.stack_end_pointer = 0x5AC6986C
-        thread.stack_pointer = 0x5AC6A86C
-        thread_info = ThreadInfo(thread)
-
-        expected = '\n'.join(
-            ('Est CPU usage: unknown', 'Stack info',
-             '  Current usage:   0x5ac6b86c - 0x5ac6a86c (4096 bytes, 50.00%)',
-             '  Est peak usage:  size unknown',
-             '  Stack limits:    0x5ac6b86c - 0x5ac6986c (8192 bytes)'))
-        self.assertTrue(thread_info.has_stack_size_limit())
-        self.assertTrue(thread_info.has_stack_used())
-        self.assertEqual(expected, str(thread_info))
-
-
-class ThreadSnapshotAnalyzerTest(unittest.TestCase):
-    """Tests that the ThreadSnapshotAnalyzer class produces expected results."""
-    def test_no_threads(self):
-        analyzer = ThreadSnapshotAnalyzer(thread_pb2.SnapshotThreadInfo())
-        self.assertEqual('', str(analyzer))
-
-    def test_one_empty_thread(self):
-        snapshot = thread_pb2.SnapshotThreadInfo()
-        snapshot.threads.append(thread_pb2.Thread())
-        expected = '\n'.join((
-            'Thread State',
-            '  1 thread running.',
-            '',
-            'Thread (UNKNOWN): [unnamed thread]',
-            'Est CPU usage: unknown',
-            'Stack info',
-            '  Current usage:   0x???????? - 0x???????? (size unknown)',
-            '  Est peak usage:  size unknown',
-            '  Stack limits:    0x???????? - 0x???????? (size unknown)',
-            '',
-        ))
-        analyzer = ThreadSnapshotAnalyzer(snapshot)
-        self.assertEqual(analyzer.active_thread(), None)
-        self.assertEqual(str(ThreadSnapshotAnalyzer(snapshot)), expected)
-
-    def test_two_threads(self):
-        """Ensures multiple threads are printed correctly."""
-        snapshot = thread_pb2.SnapshotThreadInfo()
-
-        temp_thread = thread_pb2.Thread()
-        temp_thread.name = 'Idle'.encode()
-        temp_thread.state = thread_pb2.ThreadState.Enum.READY
-        temp_thread.stack_start_pointer = 0x2001ac00
-        temp_thread.stack_end_pointer = 0x2001aa00
-        temp_thread.stack_pointer = 0x2001ab0c
-        temp_thread.stack_pointer_est_peak = 0x2001aa00
-        snapshot.threads.append(temp_thread)
-
-        temp_thread = thread_pb2.Thread()
-        temp_thread.name = 'Alice'.encode()
-        temp_thread.stack_start_pointer = 0x2001b000
-        temp_thread.stack_pointer = 0x2001ae20
-        temp_thread.state = thread_pb2.ThreadState.Enum.BLOCKED
-        snapshot.threads.append(temp_thread)
-
-        expected = '\n'.join((
-            'Thread State',
-            '  2 threads running.',
-            '',
-            'Thread (READY): Idle',
-            'Est CPU usage: unknown',
-            'Stack info',
-            '  Current usage:   0x2001ac00 - 0x2001ab0c (244 bytes, 47.66%)',
-            '  Est peak usage:  512 bytes, 100.00%',
-            '  Stack limits:    0x2001ac00 - 0x2001aa00 (512 bytes)',
-            '',
-            'Thread (BLOCKED): Alice',
-            'Est CPU usage: unknown',
-            'Stack info',
-            '  Current usage:   0x2001b000 - 0x2001ae20 (480 bytes)',
-            '  Est peak usage:  size unknown',
-            '  Stack limits:    0x2001b000 - 0x???????? (size unknown)',
-            '',
-        ))
-        analyzer = ThreadSnapshotAnalyzer(snapshot)
-        self.assertEqual(analyzer.active_thread(), None)
-        self.assertEqual(str(ThreadSnapshotAnalyzer(snapshot)), expected)
-
-    def test_interrupts_with_thread(self):
-        """Ensures interrupts are properly reported as active."""
-        snapshot = thread_pb2.SnapshotThreadInfo()
-
-        temp_thread = thread_pb2.Thread()
-        temp_thread.name = 'Idle'.encode()
-        temp_thread.state = thread_pb2.ThreadState.Enum.READY
-        temp_thread.stack_start_pointer = 0x2001ac00
-        temp_thread.stack_end_pointer = 0x2001aa00
-        temp_thread.stack_pointer = 0x2001ab0c
-        temp_thread.stack_pointer_est_peak = 0x2001aa00
-        snapshot.threads.append(temp_thread)
-
-        temp_thread = thread_pb2.Thread()
-        temp_thread.name = 'Main/Handler'.encode()
-        temp_thread.stack_start_pointer = 0x2001b000
-        temp_thread.stack_pointer = 0x2001ae20
-        temp_thread.state = thread_pb2.ThreadState.Enum.INTERRUPT_HANDLER
-        snapshot.threads.append(temp_thread)
-
-        expected = '\n'.join((
-            'Thread State',
-            '  2 threads running, Main/Handler active at the time of capture.',
-            '                     ~~~~~~~~~~~~',
-            '',
-            # Ensure the active thread is moved to the top of the list.
-            'Thread (INTERRUPT_HANDLER): Main/Handler <-- [ACTIVE]',
-            'Est CPU usage: unknown',
-            'Stack info',
-            '  Current usage:   0x2001b000 - 0x2001ae20 (480 bytes)',
-            '  Est peak usage:  size unknown',
-            '  Stack limits:    0x2001b000 - 0x???????? (size unknown)',
-            '',
-            'Thread (READY): Idle',
-            'Est CPU usage: unknown',
-            'Stack info',
-            '  Current usage:   0x2001ac00 - 0x2001ab0c (244 bytes, 47.66%)',
-            '  Est peak usage:  512 bytes, 100.00%',
-            '  Stack limits:    0x2001ac00 - 0x2001aa00 (512 bytes)',
-            '',
-        ))
-        analyzer = ThreadSnapshotAnalyzer(snapshot)
-        self.assertEqual(analyzer.active_thread(), temp_thread)
-        self.assertEqual(str(ThreadSnapshotAnalyzer(snapshot)), expected)
-
-    def test_active_thread(self):
-        """Ensures the 'active' thread is highlighted."""
-        snapshot = thread_pb2.SnapshotThreadInfo()
-
-        temp_thread = thread_pb2.Thread()
-        temp_thread.name = 'Idle'.encode()
-        temp_thread.state = thread_pb2.ThreadState.Enum.READY
-        temp_thread.stack_start_pointer = 0x2001ac00
-        temp_thread.stack_end_pointer = 0x2001aa00
-        temp_thread.stack_pointer = 0x2001ab0c
-        temp_thread.stack_pointer_est_peak = 0x2001ac00 + 0x100
-        snapshot.threads.append(temp_thread)
-
-        temp_thread = thread_pb2.Thread()
-        temp_thread.name = 'Main/Handler'.encode()
-        temp_thread.active = True
-        temp_thread.stack_start_pointer = 0x2001b000
-        temp_thread.stack_pointer = 0x2001ae20
-        temp_thread.stack_pointer_est_peak = 0x2001b000 + 0x200
-        temp_thread.state = thread_pb2.ThreadState.Enum.INTERRUPT_HANDLER
-        snapshot.threads.append(temp_thread)
-
-        expected = '\n'.join((
-            'Thread State',
-            '  2 threads running, Main/Handler active at the time of capture.',
-            '                     ~~~~~~~~~~~~',
-            '',
-            # Ensure the active thread is moved to the top of the list.
-            'Thread (INTERRUPT_HANDLER): Main/Handler <-- [ACTIVE]',
-            'Est CPU usage: unknown',
-            'Stack info',
-            '  Current usage:   0x2001b000 - 0x2001ae20 (480 bytes)',
-            '  Est peak usage:  512 bytes',
-            '  Stack limits:    0x2001b000 - 0x???????? (size unknown)',
-            '',
-            'Thread (READY): Idle',
-            'Est CPU usage: unknown',
-            'Stack info',
-            '  Current usage:   0x2001ac00 - 0x2001ab0c (244 bytes, 47.66%)',
-            '  Est peak usage:  256 bytes, 50.00%',
-            '  Stack limits:    0x2001ac00 - 0x2001aa00 (512 bytes)',
-            '',
-        ))
-        analyzer = ThreadSnapshotAnalyzer(snapshot)
-
-        # Ensure the active thread is found.
-        self.assertEqual(analyzer.active_thread(), temp_thread)
-        self.assertEqual(str(ThreadSnapshotAnalyzer(snapshot)), expected)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_thread/sleep.cc b/pw_thread/sleep.cc
index c5d1ee8..403df3a 100644
--- a/pw_thread/sleep.cc
+++ b/pw_thread/sleep.cc
@@ -17,12 +17,12 @@
 using pw::chrono::SystemClock;
 
 extern "C" void pw_this_thread_SleepFor(
-    pw_chrono_SystemClock_Duration sleep_duration) {
-  pw::this_thread::sleep_for(SystemClock::duration(sleep_duration.ticks));
+    pw_chrono_SystemClock_Duration for_at_least) {
+  pw::this_thread::sleep_for(SystemClock::duration(for_at_least.ticks));
 }
 
 extern "C" void pw_this_thread_SleepUntil(
-    pw_chrono_SystemClock_TimePoint wakeup_time) {
+    pw_chrono_SystemClock_TimePoint until_at_least) {
   pw::this_thread::sleep_until(SystemClock::time_point(
-      SystemClock::duration(wakeup_time.duration_since_epoch.ticks)));
+      SystemClock::duration(until_at_least.duration_since_epoch.ticks)));
 }
diff --git a/pw_thread/sleep_facade_test.cc b/pw_thread/sleep_facade_test.cc
index ca83880..3112b63 100644
--- a/pw_thread/sleep_facade_test.cc
+++ b/pw_thread/sleep_facade_test.cc
@@ -28,8 +28,9 @@
 extern "C" {
 
 // Functions defined in sleep_facade_test_c.c which call the API from C.
-void pw_this_thread_CallSleepFor(pw_chrono_SystemClock_Duration sleep_duration);
-void pw_this_thread_CallSleepUntil(pw_chrono_SystemClock_TimePoint wakeup_time);
+void pw_this_thread_CallSleepFor(pw_chrono_SystemClock_Duration for_at_least);
+void pw_this_thread_CallSleepUntil(
+    pw_chrono_SystemClock_TimePoint until_at_least);
 
 }  // extern "C"
 
@@ -41,71 +42,27 @@
 constexpr pw_chrono_SystemClock_Duration kRoundedArbitraryDurationInC =
     PW_SYSTEM_CLOCK_MS(42);
 
-TEST(Sleep, SleepForPositiveDuration) {
+TEST(Sleep, SleepFor) {
   // Ensure we are in a thread context, meaning we are permitted to sleep.
   ASSERT_NE(get_id(), thread::Id());
 
-  SystemClock::time_point before = SystemClock::now();
+  const SystemClock::time_point before = SystemClock::now();
   sleep_for(kRoundedArbitraryDuration);
-  SystemClock::duration time_elapsed = SystemClock::now() - before;
+  const SystemClock::duration time_elapsed = SystemClock::now() - before;
   EXPECT_GE(time_elapsed, kRoundedArbitraryDuration);
 }
 
-TEST(Sleep, SleepForZeroLengthDuration) {
+TEST(Sleep, SleepUntil) {
   // Ensure we are in a thread context, meaning we are permitted to sleep.
   ASSERT_NE(get_id(), thread::Id());
 
-  // Ensure it doesn't sleep when a zero length duration is used.
-  SystemClock::time_point before = SystemClock::now();
-  sleep_for(SystemClock::duration::zero());
-  SystemClock::duration time_elapsed = SystemClock::now() - before;
-  EXPECT_LT(time_elapsed, kRoundedArbitraryDuration);
-}
-
-TEST(Sleep, SleepForNegativeDuration) {
-  // Ensure we are in a thread context, meaning we are permitted to sleep.
-  ASSERT_NE(get_id(), thread::Id());
-
-  // Ensure it doesn't sleep when a negative duration is used.
-  SystemClock::time_point before = SystemClock::now();
-  sleep_for(-kRoundedArbitraryDuration);
-  SystemClock::duration time_elapsed = SystemClock::now() - before;
-  EXPECT_LT(time_elapsed, kRoundedArbitraryDuration);
-}
-
-TEST(Sleep, SleepUntilFutureWakeupTime) {
-  // Ensure we are in a thread context, meaning we are permitted to sleep.
-  ASSERT_NE(get_id(), thread::Id());
-
-  SystemClock::time_point deadline =
+  const SystemClock::time_point deadline =
       SystemClock::now() + kRoundedArbitraryDuration;
   sleep_until(deadline);
   EXPECT_GE(SystemClock::now(), deadline);
 }
 
-TEST(Sleep, SleepUntilCurrentWakeupTime) {
-  // Ensure we are in a thread context, meaning we are permitted to sleep.
-  ASSERT_NE(get_id(), thread::Id());
-
-  // Ensure it doesn't sleep when now is used.
-  SystemClock::time_point deadline =
-      SystemClock::now() + kRoundedArbitraryDuration;
-  sleep_until(SystemClock::now());
-  EXPECT_LT(SystemClock::now(), deadline);
-}
-
-TEST(Sleep, SleepUntilPastWakeupTime) {
-  // Ensure we are in a thread context, meaning we are permitted to sleep.
-  ASSERT_NE(get_id(), thread::Id());
-
-  // Ensure it doesn't sleep when a timestamp in the past is used.
-  SystemClock::time_point deadline =
-      SystemClock::now() + kRoundedArbitraryDuration;
-  sleep_until(SystemClock::now() - kRoundedArbitraryDuration);
-  EXPECT_LT(SystemClock::now(), deadline);
-}
-
-TEST(Sleep, SleepForPositiveDurationInC) {
+TEST(Sleep, SleepForInC) {
   // Ensure we are in a thread context, meaning we are permitted to sleep.
   ASSERT_NE(get_id(), thread::Id());
 
@@ -116,32 +73,7 @@
   EXPECT_GE(time_elapsed.ticks, kRoundedArbitraryDurationInC.ticks);
 }
 
-TEST(Sleep, SleepForZeroLengthDurationInC) {
-  // Ensure we are in a thread context, meaning we are permitted to sleep.
-  ASSERT_NE(get_id(), thread::Id());
-
-  // Ensure it doesn't sleep when a zero length duration is used.
-  pw_chrono_SystemClock_TimePoint before = pw_chrono_SystemClock_Now();
-  pw_this_thread_SleepFor(PW_SYSTEM_CLOCK_MS(0));
-  pw_chrono_SystemClock_Duration time_elapsed =
-      pw_chrono_SystemClock_TimeElapsed(before, pw_chrono_SystemClock_Now());
-  EXPECT_LT(time_elapsed.ticks, kRoundedArbitraryDurationInC.ticks);
-}
-
-TEST(Sleep, SleepForNegativeDurationInC) {
-  // Ensure we are in a thread context, meaning we are permitted to sleep.
-  ASSERT_NE(get_id(), thread::Id());
-
-  // Ensure it doesn't sleep when a negative duration is used.
-  pw_chrono_SystemClock_TimePoint before = pw_chrono_SystemClock_Now();
-  pw_this_thread_SleepFor(
-      PW_SYSTEM_CLOCK_MS(-kRoundedArbitraryDurationInC.ticks));
-  pw_chrono_SystemClock_Duration time_elapsed =
-      pw_chrono_SystemClock_TimeElapsed(before, pw_chrono_SystemClock_Now());
-  EXPECT_LT(time_elapsed.ticks, kRoundedArbitraryDurationInC.ticks);
-}
-
-TEST(Sleep, SleepUntilFutureWakeupTimeInC) {
+TEST(Sleep, SleepUntilInC) {
   // Ensure we are in a thread context, meaning we are permitted to sleep.
   ASSERT_NE(get_id(), thread::Id());
 
@@ -154,37 +86,5 @@
             deadline.duration_since_epoch.ticks);
 }
 
-TEST(Sleep, SleepUntilCurrentWakeupTimeInC) {
-  // Ensure we are in a thread context, meaning we are permitted to sleep.
-  ASSERT_NE(get_id(), thread::Id());
-
-  // Ensure it doesn't sleep when now is used.
-  pw_chrono_SystemClock_TimePoint deadline;
-  deadline.duration_since_epoch.ticks =
-      pw_chrono_SystemClock_Now().duration_since_epoch.ticks +
-      kRoundedArbitraryDurationInC.ticks;
-  pw_this_thread_CallSleepUntil(pw_chrono_SystemClock_Now());
-  EXPECT_LT(pw_chrono_SystemClock_Now().duration_since_epoch.ticks,
-            deadline.duration_since_epoch.ticks);
-}
-
-TEST(Sleep, SleepUntilPastWakeupTimeInC) {
-  // Ensure we are in a thread context, meaning we are permitted to sleep.
-  ASSERT_NE(get_id(), thread::Id());
-
-  // Ensure it doesn't sleep when a timestamp in the past is used.
-  pw_chrono_SystemClock_TimePoint deadline;
-  deadline.duration_since_epoch.ticks =
-      pw_chrono_SystemClock_Now().duration_since_epoch.ticks +
-      kRoundedArbitraryDurationInC.ticks;
-  pw_chrono_SystemClock_TimePoint old_timestamp;
-  old_timestamp.duration_since_epoch.ticks =
-      pw_chrono_SystemClock_Now().duration_since_epoch.ticks -
-      kRoundedArbitraryDurationInC.ticks;
-  pw_this_thread_CallSleepUntil(old_timestamp);
-  EXPECT_LT(pw_chrono_SystemClock_Now().duration_since_epoch.ticks,
-            deadline.duration_since_epoch.ticks);
-}
-
 }  // namespace
 }  // namespace pw::this_thread
diff --git a/pw_thread/sleep_facade_test_c.c b/pw_thread/sleep_facade_test_c.c
index cd86271..450c387 100644
--- a/pw_thread/sleep_facade_test_c.c
+++ b/pw_thread/sleep_facade_test_c.c
@@ -17,12 +17,11 @@
 
 #include "pw_thread/sleep.h"
 
-void pw_this_thread_CallSleepFor(
-    pw_chrono_SystemClock_Duration sleep_duration) {
-  pw_this_thread_SleepFor(sleep_duration);
+void pw_this_thread_CallSleepFor(pw_chrono_SystemClock_Duration for_at_least) {
+  pw_this_thread_SleepFor(for_at_least);
 }
 
 void pw_this_thread_CallSleepUntil(
-    pw_chrono_SystemClock_TimePoint wakeup_time) {
-  pw_this_thread_SleepUntil(wakeup_time);
+    pw_chrono_SystemClock_TimePoint until_at_least) {
+  pw_this_thread_SleepUntil(until_at_least);
 }
diff --git a/pw_thread/snapshot.cc b/pw_thread/snapshot.cc
deleted file mode 100644
index 9a68338..0000000
--- a/pw_thread/snapshot.cc
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 PW_LOG_LEVEL PW_THREAD_CONFIG_LOG_LEVEL
-
-#include "pw_thread/snapshot.h"
-
-#include <string_view>
-
-#include "pw_bytes/span.h"
-#include "pw_function/function.h"
-#include "pw_log/log.h"
-#include "pw_protobuf/encoder.h"
-#include "pw_status/status.h"
-#include "pw_thread/config.h"
-#include "pw_thread_protos/thread.pwpb.h"
-
-namespace pw::thread {
-
-Status SnapshotStack(const StackContext& stack,
-                     Thread::StreamEncoder& encoder,
-                     const ProcessThreadStackCallback& thread_stack_callback) {
-  // TODO(pwbug/422): Add support for ascending stacks.
-  encoder.WriteStackStartPointer(stack.stack_high_addr);
-  encoder.WriteStackEndPointer(stack.stack_low_addr);
-  encoder.WriteStackPointer(stack.stack_pointer);
-  PW_LOG_DEBUG("Active stack: 0x%08x-0x%08x (%ld bytes)",
-               stack.stack_high_addr,
-               stack.stack_pointer,
-               static_cast<long>(stack.stack_high_addr) -
-                   static_cast<long>(stack.stack_pointer));
-  if (stack.stack_pointer_est_peak.has_value()) {
-    const uintptr_t stack_pointer_est_peak =
-        stack.stack_pointer_est_peak.value();
-    encoder.WriteStackPointerEstPeak(stack_pointer_est_peak);
-    PW_LOG_DEBUG("Est peak stack: 0x%08x-0x%08x (%ld bytes)",
-                 stack.stack_high_addr,
-                 stack_pointer_est_peak,
-                 static_cast<long>(stack.stack_high_addr) -
-                     static_cast<long>(stack_pointer_est_peak));
-  }
-  PW_LOG_DEBUG("Stack Limits: 0x%08x-0x%08x (%ld bytes)",
-               stack.stack_low_addr,
-               stack.stack_high_addr,
-               static_cast<long>(stack.stack_high_addr) -
-                   static_cast<long>(stack.stack_low_addr));
-
-  if (stack.stack_pointer > stack.stack_high_addr) {
-    PW_LOG_ERROR("%s's stack underflowed by %lu bytes",
-                 stack.thread_name.data(),
-                 static_cast<long unsigned>(stack.stack_pointer -
-                                            stack.stack_high_addr));
-    return Status::OutOfRange();
-  }
-
-  // Log an error, but don't prevent the capture.
-  if (stack.stack_pointer < stack.stack_low_addr) {
-    PW_LOG_ERROR(
-        "%s's stack overflowed by %lu bytes",
-        stack.thread_name.data(),
-        static_cast<long unsigned>(stack.stack_low_addr - stack.stack_pointer));
-  }
-
-  return thread_stack_callback(
-      encoder,
-      ConstByteSpan(reinterpret_cast<const std::byte*>(stack.stack_pointer),
-                    stack.stack_high_addr - stack.stack_pointer));
-}
-
-}  // namespace pw::thread
diff --git a/pw_thread_embos/BUILD b/pw_thread_embos/BUILD
new file mode 100644
index 0000000..dd35c30
--- /dev/null
+++ b/pw_thread_embos/BUILD
@@ -0,0 +1,99 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "id_headers",
+    hdrs = [
+        "public/pw_thread_embos/id_inline.h",
+        "public/pw_thread_embos/id_native.h",
+        "public_overrides/pw_thread_backend/id_inline.h",
+        "public_overrides/pw_thread_backend/id_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+)
+
+pw_cc_library(
+    name = "id",
+    deps = [
+        ":id_headers",
+        "//pw_thread:id_facade",
+    ],
+    # TODO(pwbug/317): This should depend on embOS but our third parties
+    # currently do not have Bazel support.
+)
+
+pw_cc_library(
+    name = "sleep_headers",
+    hdrs = [
+        "public/pw_thread_embos/sleep_inline.h",
+        "public_overrides/pw_thread_backend/sleep_inline.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        "//pw_chrono:system_clock",
+    ],
+)
+
+pw_cc_library(
+    name = "sleep",
+    srcs = [
+        "sleep.cc",
+    ],
+    deps = [
+        ":sleep_headers",
+        "//pw_chrono_embos:system_clock_headers",
+        "//pw_assert",
+        "//pw_chrono:system_clock",
+        "//pw_thread:sleep_facade",
+    ],
+    # TODO(pwbug/317): This should depend on embOS but our third parties
+    # currently do not have Bazel support.
+)
+
+pw_cc_library(
+    name = "yield_headers",
+    hdrs = [
+        "public/pw_thread_embos/yield_inline.h",
+        "public_overrides/pw_thread_backend/yield_inline.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    # TODO(pwbug/317): This should depend on embOS but our third parties
+    # currently do not have Bazel support.
+)
+
+pw_cc_library(
+    name = "yield",
+    deps = [
+        ":yield_headers",
+        "//pw_thread:yield_facade",
+    ],
+)
diff --git a/pw_thread_embos/BUILD.bazel b/pw_thread_embos/BUILD.bazel
deleted file mode 100644
index da4635a..0000000
--- a/pw_thread_embos/BUILD.bazel
+++ /dev/null
@@ -1,190 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "id_headers",
-    hdrs = [
-        "public/pw_thread_embos/id_inline.h",
-        "public/pw_thread_embos/id_native.h",
-        "public_overrides/pw_thread_backend/id_inline.h",
-        "public_overrides/pw_thread_backend/id_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-)
-
-pw_cc_library(
-    name = "id",
-    deps = [
-        ":id_headers",
-        "//pw_thread:id_facade",
-    ],
-    # TODO(pwbug/317): This should depend on embOS but our third parties
-    # currently do not have Bazel support.
-)
-
-pw_cc_library(
-    name = "sleep_headers",
-    hdrs = [
-        "public/pw_thread_embos/sleep_inline.h",
-        "public_overrides/pw_thread_backend/sleep_inline.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    deps = [
-        "//pw_chrono:system_clock",
-    ],
-)
-
-pw_cc_library(
-    name = "sleep",
-    srcs = [
-        "sleep.cc",
-    ],
-    deps = [
-        ":sleep_headers",
-        "//pw_assert",
-        "//pw_chrono:system_clock",
-        "//pw_chrono_embos:system_clock_headers",
-        "//pw_thread:sleep_facade",
-    ],
-    # TODO(pwbug/317): This should depend on embOS but our third parties
-    # currently do not have Bazel support.
-)
-
-# This target provides the embOS specific headers needs for thread creation.
-pw_cc_library(
-    name = "thread_headers",
-    hdrs = [
-        "public/pw_thread_embos/config.h",
-        "public/pw_thread_embos/context.h",
-        "public/pw_thread_embos/options.h",
-        "public/pw_thread_embos/thread_inline.h",
-        "public/pw_thread_embos/thread_native.h",
-        "public_overrides/pw_thread_backend/thread_inline.h",
-        "public_overrides/pw_thread_backend/thread_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    deps = [
-        ":id",
-        "//pw_assert",
-        "//pw_string",
-        "//pw_thread:thread_headers",
-    ],
-    # TODO(pwbug/317): This should depend on embOS but our third parties
-    # currently do not have Bazel support.
-)
-
-pw_cc_library(
-    name = "thread",
-    srcs = [
-        "thread.cc",
-    ],
-    deps = [
-        ":id",
-        ":thread_headers",
-        "//pw_assert",
-    ],
-    # TODO(pwbug/317): This should depend on embOS but our third parties
-    # currently do not have Bazel support.
-)
-
-pw_cc_library(
-    name = "test_threads",
-    srcs = [
-        "test_threads.cc",
-    ],
-    deps = [
-        "//pw_chrono:system_clock",
-        "//pw_thread:sleep",
-        "//pw_thread:test_threads_header",
-        "//pw_thread:thread_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "yield_headers",
-    hdrs = [
-        "public/pw_thread_embos/yield_inline.h",
-        "public_overrides/pw_thread_backend/yield_inline.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    # TODO(pwbug/317): This should depend on embOS but our third parties
-    # currently do not have Bazel support.
-)
-
-pw_cc_library(
-    name = "yield",
-    deps = [
-        ":yield_headers",
-        "//pw_thread:yield_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "util",
-    srcs = [
-        "util.cc",
-    ],
-    hdrs = [
-        "public/pw_thread_embos/util.h",
-    ],
-    deps = [
-        "//pw_function",
-        "//pw_status",
-    ],
-    # TODO(pwbug/317): This should depend on embOS but our third parties
-    # currently do not have Bazel support.
-)
-
-pw_cc_library(
-    name = "snapshot",
-    srcs = [
-        "snapshot.cc",
-    ],
-    hdrs = [
-        "public/pw_thread_embos/snapshot.h",
-    ],
-    deps = [
-        ":util",
-        "//pw_bytes",
-        "//pw_function",
-        "//pw_log",
-        "//pw_protobuf",
-        "//pw_status",
-        "//pw_thread:protos",
-        "//pw_thread:snapshot",
-    ],
-    # TODO(pwbug/317): This should depend on embOS but our third parties
-    # currently do not have Bazel support.
-)
diff --git a/pw_thread_embos/BUILD.gn b/pw_thread_embos/BUILD.gn
index 1b48750..4e76548 100644
--- a/pw_thread_embos/BUILD.gn
+++ b/pw_thread_embos/BUILD.gn
@@ -14,20 +14,10 @@
 
 import("//build_overrides/pigweed.gni")
 
-import("$dir_pw_build/error.gni")
-import("$dir_pw_build/module_config.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_chrono/backend.gni")
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_thread/backend.gni")
-import("$dir_pw_unit_test/test.gni")
-
-declare_args() {
-  # The build target that overrides the default configuration options for this
-  # module. This should point to a source set that provides defines through a
-  # public config (which may -include a file or add defines directly).
-  pw_thread_embos_CONFIG = pw_build_DEFAULT_MODULE_CONFIG
-}
 
 config("public_include_path") {
   include_dirs = [ "public" ]
@@ -39,15 +29,6 @@
   visibility = [ ":*" ]
 }
 
-pw_source_set("config") {
-  public = [ "public/pw_thread_embos/config.h" ]
-  public_configs = [ ":public_include_path" ]
-  public_deps = [
-    "$dir_pw_third_party/embos",
-    pw_thread_embos_CONFIG,
-  ]
-}
-
 # This target provides the backend for pw::thread::Id.
 pw_source_set("id") {
   public_configs = [
@@ -68,15 +49,6 @@
   deps = [ "$dir_pw_thread:id.facade" ]
 }
 
-pw_build_assert("check_system_clock_backend") {
-  condition =
-      pw_thread_OVERRIDE_SYSTEM_CLOCK_BACKEND_CHECK ||
-      pw_chrono_SYSTEM_CLOCK_BACKEND == "$dir_pw_chrono_embos:system_clock"
-  message = "The embOS pw::thread::sleep_{for,until} backend only works with " +
-            "the embOS pw::chrono::SystemClock backend."
-  visibility = [ ":*" ]
-}
-
 if (pw_chrono_SYSTEM_CLOCK_BACKEND != "" && pw_thread_SLEEP_BACKEND != "") {
   # This target provides the backend for pw::thread::sleep_{for,until}.
   pw_source_set("sleep") {
@@ -91,43 +63,20 @@
     public_deps = [ "$dir_pw_chrono:system_clock" ]
     sources = [ "sleep.cc" ]
     deps = [
-      ":check_system_clock_backend",
       "$dir_pw_assert",
       "$dir_pw_chrono_embos:system_clock",
       "$dir_pw_third_party/embos",
       "$dir_pw_thread:id",
       "$dir_pw_thread:sleep.facade",
     ]
+    assert(pw_thread_OVERRIDE_SYSTEM_CLOCK_BACKEND_CHECK ||
+               pw_chrono_SYSTEM_CLOCK_BACKEND ==
+                   "$dir_pw_chrono_embos:system_clock",
+           "The embOS pw::thread::sleep_{for,until} backend only works with " +
+               "the embOS pw::chrono::SystemClock backend.")
   }
 }
 
-# This target provides the backend for pw::thread::Thread and the headers needed
-# for thread creation.
-pw_source_set("thread") {
-  public_configs = [
-    ":public_include_path",
-    ":backend_config",
-  ]
-  public_deps = [
-    ":config",
-    "$dir_pw_assert",
-    "$dir_pw_string",
-    "$dir_pw_third_party/embos",
-    "$dir_pw_thread:id",
-    "$dir_pw_thread:thread.facade",
-  ]
-  public = [
-    "public/pw_thread_embos/context.h",
-    "public/pw_thread_embos/options.h",
-    "public/pw_thread_embos/thread_inline.h",
-    "public/pw_thread_embos/thread_native.h",
-    "public_overrides/pw_thread_backend/thread_inline.h",
-    "public_overrides/pw_thread_backend/thread_native.h",
-  ]
-  allow_circular_includes_from = [ "$dir_pw_thread:thread.facade" ]
-  sources = [ "thread.cc" ]
-}
-
 # This target provides the backend for pw::thread::yield.
 pw_source_set("yield") {
   public_configs = [
@@ -146,59 +95,6 @@
   deps = [ "$dir_pw_thread:yield.facade" ]
 }
 
-pw_source_set("util") {
-  public_configs = [ ":public_include_path" ]
-  public_deps = [
-    "$dir_pw_third_party/embos",
-    dir_pw_function,
-    dir_pw_status,
-  ]
-  public = [ "public/pw_thread_embos/util.h" ]
-  sources = [ "util.cc" ]
-}
-
-pw_source_set("snapshot") {
-  public_configs = [ ":public_include_path" ]
-  public_deps = [
-    ":config",
-    "$dir_pw_third_party/embos",
-    "$dir_pw_thread:protos.pwpb",
-    "$dir_pw_thread:snapshot",
-    dir_pw_bytes,
-    dir_pw_function,
-    dir_pw_log,
-    dir_pw_protobuf,
-    dir_pw_status,
-  ]
-  public = [ "public/pw_thread_embos/snapshot.h" ]
-  sources = [ "snapshot.cc" ]
-  deps = [ ":util" ]
-}
-
-pw_test_group("tests") {
-  tests = [ ":thread_backend_test" ]
-}
-
-pw_source_set("test_threads") {
-  public_deps = [ "$dir_pw_thread:test_threads" ]
-  sources = [ "test_threads.cc" ]
-  deps = [
-    "$dir_pw_chrono:system_clock",
-    "$dir_pw_thread:sleep",
-    "$dir_pw_thread:thread",
-    dir_pw_assert,
-    dir_pw_log,
-  ]
-}
-
-pw_test("thread_backend_test") {
-  enable_if = pw_thread_THREAD_BACKEND == "$dir_pw_thread_embos:thread"
-  deps = [
-    ":test_threads",
-    "$dir_pw_thread:thread_facade_test",
-  ]
-}
-
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
diff --git a/pw_thread_embos/OWNERS b/pw_thread_embos/OWNERS
deleted file mode 100644
index e9a6c58..0000000
--- a/pw_thread_embos/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-ewout@google.com
-hepler@google.com
diff --git a/pw_thread_embos/docs.rst b/pw_thread_embos/docs.rst
index 3bdbc4b..cad1908 100644
--- a/pw_thread_embos/docs.rst
+++ b/pw_thread_embos/docs.rst
@@ -1,238 +1,8 @@
 .. _module-pw_thread_embos:
 
-===============
+---------------
 pw_thread_embos
-===============
-This is a set of backends for pw_thread based on embOS v4.
+---------------
+This is a set of backends for pw_thread based on embOS v4. It is not ready for
+use, and is under construction.
 
-.. Warning::
-  This module is still under construction, the API is not yet stable.
-
------------------------
-Thread Creation Backend
------------------------
-A backend or ``pw::thread::Thread`` is offered using ``OS_CreateTaskEx()``.
-Optional joining support is enabled via an ``OS_EVENT`` in each thread's
-context.
-
-This backend permits users to start threads where contexts must be explicitly
-allocated and passed in as an option. As a quick example, a detached thread
-can be created as follows:
-
-.. code-block:: cpp
-
-  #include "pw_thread/detached_thread.h"
-  #include "pw_thread_embos/config.h"
-  #include "pw_thread_embos/context.h"
-  #include "pw_thread_embos/options.h"
-  #include "RTOS.h"  // For the embOS types.
-
-  constexpr OS_PRIO kFooPriority =
-      pw::thread::embos::config::kDefaultPriority;
-  constexpr OS_UINT kFooTimeSliceInterval =
-      pw::thread::embos::config::kDefaultTimeSliceInterval;
-  constexpr size_t kFooStackSizeWords =
-      pw::thread::embos::config::kDefaultStackSizeWords;
-
-  pw::thread::embos::ContextWithStack<kFooStackSizeWords>
-      example_thread_context;
-  void StartExampleThread() {
-    pw::thread::DetachedThread(
-        pw::thread::embos::Options()
-            .set_name("example_thread")
-            .set_priority(kFooPriority)
-            .set_time_slice_interval(kFooTimeSliceInterval)
-            .set_context(example_thread_context),
-        example_thread_function);
-  }
-
-
-Module Configuration Options
-============================
-The following configurations can be adjusted via compile-time configuration of
-this module, see the
-:ref:`module documentation <module-structure-compile-time-configuration>` for
-more details.
-
-.. c:macro:: PW_THREAD_EMBOS_CONFIG_JOINING_ENABLED
-
-  Whether thread joining is enabled. By default this is disabled.
-
-  We suggest only enabling this when thread joining is required to minimize
-  the RAM and ROM cost of threads.
-
-  Enabling this grows the RAM footprint of every pw::thread::Thread as it adds
-  an OS_EVENT to every thread's pw::thread::embos::Context. In addition, there
-  is a minute ROM cost to construct and destroy this added object.
-
-  PW_THREAD_JOINING_ENABLED gets set to this value.
-
-.. c:macro:: PW_THREAD_EMBOS_CONFIG_MINIMUM_STACK_SIZE_WORDS
-
-  The minimum stack size in words. By default this uses Segger's recommendation
-  of 68 bytes.
-
-.. c:macro:: PW_THREAD_EMBOS_CONFIG_DEFAULT_STACK_SIZE_WORDS
-
-  The default stack size in words. By default this uses Segger's recommendation
-  of 256 bytes to start.
-
-.. c:macro:: PW_THREAD_EMBOS_CONFIG_MAX_THREAD_NAME_LEN
-
-  The maximum length of a thread's name, not including null termination. By
-  default this is arbitrarily set to 15. This results in an array of characters
-  which is this length + 1 bytes in every ``pw::thread::Thread``'s context.
-
-.. c:macro:: PW_THREAD_EMBOS_CONFIG_MIN_PRIORITY
-
-  The minimum priority level, this is normally 1, since 0 is not a valid
-  priority level.
-
-.. c:macro:: PW_THREAD_EMBOS_CONFIG_DEFAULT_PRIORITY
-
-  The default priority level. By default this uses the minimal embOS priority.
-
-.. c:macro:: PW_THREAD_EMBOS_CONFIG_DEFAULT_TIME_SLICE_INTERVAL
-
-  The round robin time slice tick interval for threads at the same priority.
-  By default this is set to 2 ticks based on the embOS default.
-
-.. c:macro:: PW_THREAD_EMBOS_CONFIG_LOG_LEVEL
-
-  The log level to use for this module. Logs below this level are omitted.
-
-embOS Thread Options
-====================
-.. cpp:class:: pw::thread::embos::Options
-
-  .. cpp:function:: set_name(const char* name)
-
-    Sets the name for the embOS task, this is optional.
-    Note that this will be deep copied into the context and may be truncated
-    based on ``PW_THREAD_EMBOS_CONFIG_MAX_THREAD_NAME_LEN``.
-
-  .. cpp:function:: set_priority(OS_PRIO priority)
-
-    Sets the priority for the embOS task. Higher values are higher priority,
-    see embOS OS_CreateTaskEx for more detail.
-    Precondition: This must be >= ``PW_THREAD_EMBOS_CONFIG_MIN_PRIORITY``.
-
-  .. cpp:function:: set_time_slice_interval(OS_UINT time_slice_interval)
-
-    Sets the number of ticks this thread is allowed to run before other ready
-    threads of the same priority are given a chance to run.
-
-    A value of 0 disables time-slicing of this thread.
-
-    Precondition: This must be <= 255 ticks.
-
-  .. cpp:function:: set_context(pw::thread::embos::Context& context)
-
-    Set the pre-allocated context (all memory needed to run a thread). Note that
-    this is required for this thread creation backend! The ``Context`` can
-    either be constructed with an externally provided ``std::span<OS_UINT>``
-    stack or the templated form of ``ContextWithStack<kStackSizeWords>`` can
-    be used.
-
-
------------------------------
-Thread Identification Backend
------------------------------
-A backend for ``pw::thread::Id`` and ``pw::thread::get_id()`` is offerred using
-``OS_GetTaskID()``. It uses ``DASSERT`` to ensure that the scheduler has started
-via ``OS_IsRunning()``.
-
---------------------
-Thread Sleep Backend
---------------------
-A backend for ``pw::thread::sleep_for()`` and ``pw::thread::sleep_until()`` is
-offerred using ``OS_Delay()`` if the duration is at least one tick, else
-``OS_Yield()`` is used. It uses ``pw::this_thread::get_id() != thread::Id()`` to
-ensure it invoked only from a thread.
-
---------------------
-Thread Yield Backend
---------------------
-A backend for ``pw::thread::yield()`` is offered using via ``OS_Yield()``.
-It uses ``pw::this_thread::get_id() != thread::Id()`` to ensure it invoked only
-from a thread.
-
----------
-Utilities
----------
-``ForEachThread()``
-===================
-In cases where an operation must be performed for every thread,
-``ForEachThread()`` can be used to iterate over all the created thread TCBs.
-Note that it's only safe to use this while the scheduler is suspended, and this
-should only be used after ``OS_Start()`` has been called. Calling this before
-the scheduler has started is non-fatal, but will result in no action and a
-``FailedPrecondition`` error code.
-
-An ``Aborted`` error status is returned if the provided callback returns
-``false`` to request an early termination of thread iteration.
-
-*Return values*
-
-* ``FailedPrecondition``: Returned when ``ForEachThread()`` is run before the OS
-  has been initialized.
-* ``Aborted``: The callback requested an early-termination of thread iteration.
-* ``OkStatus``: The callback has been successfully run with every thread.
-
---------------------
-Snapshot Integration
---------------------
-This ``pw_thread`` backend provides helper functions that capture embOS thread
-info to a ``pw::thread::Thread`` proto.
-
-``SnapshotThreads()``
-=====================
-``SnapshotThread()`` captures the thread name, state, and stack information for
-the provided embOS TCB to a ``pw::thread::Thread`` protobuf encoder. To ensure
-the most up-to-date information is captured, the stack pointer for the currently
-running thread must be provided for cases where the running thread is being
-captured. For ARM Cortex-M CPUs, you can do something like this:
-
-.. Code:: cpp
-
-  // Capture PSP.
-  void* stack_ptr = 0;
-  asm volatile("mrs %0, psp\n" : "=r"(stack_ptr));
-  pw::thread::ProcessThreadStackCallback cb =
-      [](pw::thread::Thread::StreamEncoder& encoder,
-         pw::ConstByteSpan stack) -> pw::Status {
-    return encoder.WriteRawStack(stack);
-  };
-  pw::thread::embos::SnapshotThread(my_thread, stack_ptr,
-                                    snapshot_encoder, cb);
-
-``SnapshotThreads()`` wraps the singular thread capture to instead captures
-all created threads to a ``pw::thread::SnapshotThreadInfo`` message. This proto
-message overlays a snapshot, so it is safe to static cast a
-``pw::snapshot::Snapshot::StreamEncoder`` to a
-``pw::thread::SnapshotThreadInfo::StreamEncoder`` when calling this function.
-
-Thread Name Capture
--------------------
-In order to capture thread names when snapshotting a thread, embOS must have
-``OS_TRACKNAME`` enabled. If ``OS_TRACKNAME`` is disabled, no thread name
-is captured. Enabling this is strongly recommended for debugability.
-
-Thread State Capture
---------------------
-embOS thread state is not part of embOS's public API. Despite this, the
-snapshot integration captures thread state based on information on how the
-thread state is represented from
-`Segger's public forum <https://forum.segger.com/index.php/Thread/6548-ABANDONED-Task-state-values/?postID=23963#post23963>`_.
-This has been tested on embOS 4.22, and was initially
-reported for embOS 5.06. The logic Pigweed uses to interpret thread state may
-be incorrect for other versions of embOS.
-
-Thread Stack Capture
---------------------
-Full thread stack information capture is dependent on embOS tracking the stack
-bounds for each task. When either ``OS_SUPPORT_MPU`` or ``OS_CHECKSTACK`` are
-enabled, stack bounds are tracked and the callback for thread stack dumping
-will be called. If both of these options are disabled, ``stack_start_pointer``
-and ``stack_end_pointer`` will not be captured, and the
-``ProcessThreadStackCallback`` will not be called.
diff --git a/pw_thread_embos/public/pw_thread_embos/config.h b/pw_thread_embos/public/pw_thread_embos/config.h
deleted file mode 100644
index 5bb0a3e..0000000
--- a/pw_thread_embos/public/pw_thread_embos/config.h
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-// Configuration macros for the tokenizer module.
-#pragma once
-
-#include "RTOS.h"
-
-// Whether thread joining is enabled. By default this is disabled.
-//
-// We suggest only enabling this when thread joining is required to minimize
-// the RAM and ROM cost of threads.
-//
-// Enabling this grows the RAM footprint of every pw::thread::Thread as it adds
-// an OS_EVENT to every thread's pw::thread::embos::Context. In addition, there
-// is a minute ROM cost to construct and destroy this added object.
-//
-// PW_THREAD_JOINING_ENABLED gets set to this value.
-#ifndef PW_THREAD_EMBOS_CONFIG_JOINING_ENABLED
-#define PW_THREAD_EMBOS_CONFIG_JOINING_ENABLED 0
-#endif  // PW_THREAD_EMBOS_CONFIG_JOINING_ENABLED
-#define PW_THREAD_JOINING_ENABLED PW_THREAD_EMBOS_CONFIG_JOINING_ENABLED
-
-// The minimum stack size in words. By default this uses Segger's recommendation
-// of 68 bytes.
-#ifndef PW_THREAD_EMBOS_CONFIG_MINIMUM_STACK_SIZE_WORDS
-#define PW_THREAD_EMBOS_CONFIG_MINIMUM_STACK_SIZE_WORDS (68 / sizeof(OS_UINT))
-#endif  // PW_THREAD_EMBOS_CONFIG_MINIMUM_STACK_SIZE_WORDS
-
-// The default stack size in words. By default this uses Segger's recommendation
-// of 256 bytes to start.
-#ifndef PW_THREAD_EMBOS_CONFIG_DEFAULT_STACK_SIZE_WORDS
-#define PW_THREAD_EMBOS_CONFIG_DEFAULT_STACK_SIZE_WORDS (256 / sizeof(OS_UINT))
-#endif  // PW_THREAD_EMBOS_CONFIG_DEFAULT_STACK_SIZE_WORDS
-
-// The maximum length of a thread's name, not including null termination. By
-// default this is arbitrarily set to 15. This results in an array of characters
-// which is this length + 1 bytes in every pw::thread::Thread's context.
-#ifndef PW_THREAD_EMBOS_CONFIG_MAX_THREAD_NAME_LEN
-#define PW_THREAD_EMBOS_CONFIG_MAX_THREAD_NAME_LEN 15
-#endif  // PW_THREAD_EMBOS_CONFIG_MAX_THREAD_NAME_LEN
-
-// The minimum priority level, this is normally 1, since 0 is not a valid
-// priority level.
-#ifndef PW_THREAD_EMBOS_CONFIG_MIN_PRIORITY
-#define PW_THREAD_EMBOS_CONFIG_MIN_PRIORITY 1
-#endif  // PW_THREAD_EMBOS_CONFIG_MIN_PRIORITY
-
-// The default priority level. By default this uses the minimal embOS priority.
-#ifndef PW_THREAD_EMBOS_CONFIG_DEFAULT_PRIORITY
-#define PW_THREAD_EMBOS_CONFIG_DEFAULT_PRIORITY \
-  PW_THREAD_EMBOS_CONFIG_MIN_PRIORITY
-#endif  // PW_THREAD_EMBOS_CONFIG_DEFAULT_PRIORITY
-
-// The round robin time slice tick interval for threads at the same priority.
-// By default this is set to 2 ticks based on the embOS default.
-#ifndef PW_THREAD_EMBOS_CONFIG_DEFAULT_TIME_SLICE_INTERVAL
-#define PW_THREAD_EMBOS_CONFIG_DEFAULT_TIME_SLICE_INTERVAL 2
-#endif  // PW_THREAD_EMBOS_CONFIG_DEFAULT_TIME_SLICE_INTERVAL
-
-// The log level to use for this module. Logs below this level are omitted.
-#ifndef PW_THREAD_EMBOS_CONFIG_LOG_LEVEL
-#define PW_THREAD_EMBOS_CONFIG_LOG_LEVEL PW_LOG_LEVEL_DEBUG
-#endif  // PW_THREAD_EMBOS_CONFIG_LOG_LEVEL
-
-namespace pw::thread::embos::config {
-
-inline constexpr size_t kMaximumNameLength =
-    PW_THREAD_EMBOS_CONFIG_MAX_THREAD_NAME_LEN;
-inline constexpr size_t kMinimumStackSizeWords =
-    PW_THREAD_EMBOS_CONFIG_MINIMUM_STACK_SIZE_WORDS;
-inline constexpr size_t kDefaultStackSizeWords =
-    PW_THREAD_EMBOS_CONFIG_DEFAULT_STACK_SIZE_WORDS;
-inline constexpr OS_PRIO kMinimumPriority = PW_THREAD_EMBOS_CONFIG_MIN_PRIORITY;
-inline constexpr OS_PRIO kDefaultPriority =
-    PW_THREAD_EMBOS_CONFIG_DEFAULT_PRIORITY;
-inline constexpr OS_UINT kDefaultTimeSliceInterval =
-    PW_THREAD_EMBOS_CONFIG_DEFAULT_TIME_SLICE_INTERVAL;
-
-}  // namespace pw::thread::embos::config
diff --git a/pw_thread_embos/public/pw_thread_embos/context.h b/pw_thread_embos/public/pw_thread_embos/context.h
deleted file mode 100644
index 0b3ed5d..0000000
--- a/pw_thread_embos/public/pw_thread_embos/context.h
+++ /dev/null
@@ -1,130 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <span>
-
-#include "RTOS.h"
-#include "pw_string/util.h"
-#include "pw_thread_embos/config.h"
-
-namespace pw::thread {
-
-class Thread;  // Forward declare Thread which depends on Context.
-
-}  // namespace pw::thread
-
-namespace pw::thread::embos {
-
-// Static thread context allocation including the TCB, an event group for
-// joining if enabled, and an external statically allocated stack.
-//
-// Example usage:
-//
-//   std::array<ULONG, kFooStackSizeWords> example_thread_stack;
-//   pw::thread::embos::Context example_thread_context(example_thread_stack);
-//   void StartExampleThread() {
-//      pw::thread::DetachedThread(
-//        pw::thread::embos::Options()
-//            .set_name("static_example_thread")
-//            .set_priority(kFooPriority)
-//            .set_context(example_thread_context),
-//        example_thread_function);
-//   }
-class Context {
- public:
-  explicit Context(std::span<OS_UINT> stack_span)
-      : tcb_{}, stack_span_(stack_span) {}
-  Context(const Context&) = delete;
-  Context& operator=(const Context&) = delete;
-
-  // Intended for unit test & Thread use only.
-  OS_TASK& tcb() { return tcb_; }
-
- private:
-  friend Thread;
-
-  std::span<OS_UINT> stack() { return stack_span_; }
-
-  bool in_use() const { return in_use_; }
-  void set_in_use(bool in_use = true) { in_use_ = in_use; }
-
-  const char* name() const { return name_.data(); }
-  void set_name(const char* name) { string::Copy(name, name_); }
-
-  using ThreadRoutine = void (*)(void* arg);
-  void set_thread_routine(ThreadRoutine entry, void* arg) {
-    user_thread_entry_function_ = entry;
-    user_thread_entry_arg_ = arg;
-  }
-
-  bool detached() const { return detached_; }
-  void set_detached(bool value = true) { detached_ = value; }
-
-  bool thread_done() const { return thread_done_; }
-  void set_thread_done(bool value = true) { thread_done_ = value; }
-
-#if PW_THREAD_JOINING_ENABLED
-  OS_EVENT& join_event_object() { return event_object_; }
-#endif  // PW_THREAD_JOINING_ENABLED
-
-  static void ThreadEntryPoint(void* void_context_ptr);
-  static void TerminateThread(Context& context);
-
-  OS_TASK tcb_;
-  std::span<OS_UINT> stack_span_;
-
-  ThreadRoutine user_thread_entry_function_ = nullptr;
-  void* user_thread_entry_arg_ = nullptr;
-#if PW_THREAD_JOINING_ENABLED
-  // Note that the embOS life cycle of this event object is managed together
-  // with the thread life cycle, not this object's life cycle.
-  OS_EVENT event_object_;
-#endif  // PW_THREAD_JOINING_ENABLED
-  bool in_use_ = false;
-  bool detached_ = false;
-  bool thread_done_ = false;
-
-  // The TCB does not have storage for the name, ergo we provide storage for
-  // the thread's name which can be truncated down to just a null delimiter.
-  std::array<char, config::kMaximumNameLength + 1> name_;
-};
-
-// Static thread context allocation including the stack along with the Context.
-//
-// Example usage:
-//
-//   pw::thread::embos::ContextWithStack<kFooStackSizeWords>
-//       example_thread_context;
-//   void StartExampleThread() {
-//      pw::thread::DetachedThread(
-//        pw::thread::embos::Options()
-//            .set_name("static_example_thread")
-//            .set_priority(kFooPriority)
-//            .set_context(example_thread_context),
-//        example_thread_function);
-//   }
-template <size_t kStackSizeWords = config::kDefaultStackSizeWords>
-class ContextWithStack final : public Context {
- public:
-  constexpr ContextWithStack() : Context(stack_storage_) {
-    static_assert(kStackSizeWords >= config::kMinimumStackSizeWords);
-  }
-
- private:
-  std::array<OS_UINT, kStackSizeWords> stack_storage_;
-};
-
-}  // namespace pw::thread::embos
diff --git a/pw_thread_embos/public/pw_thread_embos/id_inline.h b/pw_thread_embos/public/pw_thread_embos/id_inline.h
index 068f997..b11078a 100644
--- a/pw_thread_embos/public/pw_thread_embos/id_inline.h
+++ b/pw_thread_embos/public/pw_thread_embos/id_inline.h
@@ -14,19 +14,13 @@
 #pragma once
 
 #include "RTOS.h"
-#include "pw_assert/assert.h"
-#include "pw_interrupt/context.h"
+#include "pw_assert/light.h"
 #include "pw_thread/id.h"
 
 namespace pw::this_thread {
 
-inline thread::Id get_id() noexcept {
-  // Ensure this is not being called by an interrupt.
-  PW_DASSERT(!interrupt::InInterruptContext());
-
-  // Ensure the kernel is running.
+inline thread::Id get_id() {
   PW_DASSERT(OS_IsRunning() != 0);
-
   return thread::Id(OS_GetTaskID());
 }
 
diff --git a/pw_thread_embos/public/pw_thread_embos/options.h b/pw_thread_embos/public/pw_thread_embos/options.h
deleted file mode 100644
index d024fde..0000000
--- a/pw_thread_embos/public/pw_thread_embos/options.h
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "RTOS.h"
-#include "pw_assert/assert.h"
-#include "pw_thread/thread.h"
-#include "pw_thread_embos/config.h"
-#include "pw_thread_embos/context.h"
-
-namespace pw::thread::embos {
-
-// pw::thread::Options for FreeRTOS.
-//
-// Example usage:
-//
-//   // Uses the default priority, but specifies a custom name and context.
-//   pw::thread::Thread example_thread(
-//     pw::thread::embos::Options()
-//         .set_name("example_thread"),
-//         .set_context(static_example_thread_context),
-//     example_thread_function);
-//
-//   // Provides the name, priority, and pre-allocated context.
-//   pw::thread::Thread static_example_thread(
-//     pw::thread::embos::Options()
-//         .set_name("static_example_thread")
-//         .set_priority(kFooPriority)
-//         .set_context(static_example_thread_context),
-//     example_thread_function);
-//
-class Options : public thread::Options {
- public:
-  constexpr Options() = default;
-  constexpr Options(const Options&) = default;
-  constexpr Options(Options&& other) = default;
-
-  // Sets the name for the embOS task, this is optional.
-  // Note that this will be deep copied into the context and may be truncated
-  // based on PW_THREAD_EMBOS_CONFIG_MAX_THREAD_NAME_LEN.
-  constexpr Options& set_name(const char* name) {
-    name_ = name;
-    return *this;
-  }
-
-  // Sets the priority for the embOS task. Higher values are higher priority,
-  // see embOS OS_CreateTaskEx for more detail.
-  //
-  // Precondition: This must be >= PW_THREAD_EMBOS_CONFIG_MIN_PRIORITY.
-  constexpr Options& set_priority(OS_PRIO priority) {
-    PW_DASSERT(priority >= config::kMinimumPriority);
-    priority_ = priority;
-    return *this;
-  }
-
-  // Sets the number of ticks this thread is allowed to run before other ready
-  // threads of the same priority are given a chance to run.
-  //
-  // A value of 0 disables time-slicing of this thread.
-  //
-  // Precondition: This must be <= 255 ticks.
-  constexpr Options& set_time_slice_interval(OS_UINT time_slice_interval) {
-    PW_DASSERT(time_slice_interval <= 255);
-    time_slice_interval_ = time_slice_interval;
-    return *this;
-  }
-
-  // Set the pre-allocated context (all memory needed to run a thread), see the
-  // pw::thread::embos::Context for more detail.
-  constexpr Options& set_context(Context& context) {
-    context_ = &context;
-    return *this;
-  }
-
- private:
-  friend thread::Thread;
-
-  const char* name() const { return name_; }
-  OS_PRIO priority() const { return priority_; }
-  OS_PRIO time_slice_interval() const { return time_slice_interval_; }
-  Context* context() const { return context_; }
-
-  const char* name_ = nullptr;
-  OS_PRIO priority_ = config::kDefaultPriority;
-  OS_PRIO time_slice_interval_ = config::kDefaultTimeSliceInterval;
-  Context* context_ = nullptr;
-};
-
-}  // namespace pw::thread::embos
diff --git a/pw_thread_embos/public/pw_thread_embos/sleep_inline.h b/pw_thread_embos/public/pw_thread_embos/sleep_inline.h
index 5bca8d9..5988e7e 100644
--- a/pw_thread_embos/public/pw_thread_embos/sleep_inline.h
+++ b/pw_thread_embos/public/pw_thread_embos/sleep_inline.h
@@ -17,10 +17,10 @@
 
 namespace pw::this_thread {
 
-inline void sleep_until(chrono::SystemClock::time_point wakeup_time) {
+inline void sleep_until(chrono::SystemClock::time_point until_at_least) {
   // Note that if this deadline is in the future, it will get rounded up by
   // one whole tick due to how sleep_for is implemented.
-  return sleep_for(wakeup_time - chrono::SystemClock::now());
+  return sleep_for(until_at_least - chrono::SystemClock::now());
 }
 
 }  // namespace pw::this_thread
diff --git a/pw_thread_embos/public/pw_thread_embos/snapshot.h b/pw_thread_embos/public/pw_thread_embos/snapshot.h
deleted file mode 100644
index 75b8adf..0000000
--- a/pw_thread_embos/public/pw_thread_embos/snapshot.h
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "RTOS.h"
-#include "pw_protobuf/encoder.h"
-#include "pw_status/status.h"
-#include "pw_thread/snapshot.h"
-#include "pw_thread_protos/thread.pwpb.h"
-
-namespace pw::thread::embos {
-
-// Captures all embos threads in a system as part of a snapshot.
-//
-// An updated running_thread_stack_pointer must be provided in order for the
-// running thread's context to reflect the running state. For ARM, you might do
-// something like this:
-//
-//    // Capture PSP.
-//    void* stack_ptr = 0;
-//    asm volatile("mrs %0, psp\n" : "=r"(stack_ptr));
-//    pw::thread::ProcessThreadStackCallback cb =
-//        [](pw::thread::Thread::StreamEncoder& encoder,
-//           pw::ConstByteSpan stack) -> pw::Status {
-//      return encoder.WriteRawStack(stack);
-//    };
-//    pw::thread::embos::SnapshotThread(stack_ptr, snapshot_encoder, cb);
-//
-// Warning: This is only safe to use when interrupts and the scheduler are
-// disabled!
-Status SnapshotThreads(void* running_thread_stack_pointer,
-                       SnapshotThreadInfo::StreamEncoder& encoder,
-                       ProcessThreadStackCallback& thread_stack_callback);
-
-// Captures only the provided thread handle as a pw::thread::Thread proto
-// message. After thread info capture, the ProcessThreadStackCallback is called
-// to capture either the raw_stack or raw_backtrace.
-//
-// An updated running_thread_stack_pointer must be provided in order for the
-// running thread's context to reflect the current state. If the thread being
-// captured is not the running thread, the value is ignored. Note that the
-// stack pointer in the thread handle is almost always stale on the running
-// thread.
-//
-// Captures the following proto fields:
-//   pw.thread.Thread:
-//     name (when OS_TRACKNAME is enabled)
-//     state
-//     stack_start_pointer (when OS_CHECKSTACK or OS_SUPPORT_MPU are enabled)
-//     stack_end_pointer (when OS_CHECKSTACK or OS_SUPPORT_MPU are enabled)
-//     stack_pointer
-//
-//
-//
-//
-// Warning: This is only safe to use when interrupts and the scheduler are
-// disabled!
-Status SnapshotThread(const OS_TASK& thread,
-                      void* running_thread_stack_pointer,
-                      Thread::StreamEncoder& encoder,
-                      ProcessThreadStackCallback& thread_stack_callback);
-
-}  // namespace pw::thread::embos
diff --git a/pw_thread_embos/public/pw_thread_embos/thread_inline.h b/pw_thread_embos/public/pw_thread_embos/thread_inline.h
deleted file mode 100644
index ed8cc4f..0000000
--- a/pw_thread_embos/public/pw_thread_embos/thread_inline.h
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <algorithm>
-
-#include "pw_assert/assert.h"
-#include "pw_thread/id.h"
-#include "pw_thread_embos/context.h"
-
-namespace pw::thread {
-
-inline Thread::Thread() : native_type_(nullptr) {}
-
-inline Thread& Thread::operator=(Thread&& other) {
-  native_type_ = other.native_type_;
-  other.native_type_ = nullptr;
-  return *this;
-}
-
-inline Thread::~Thread() { PW_DASSERT(native_type_ == nullptr); }
-
-inline Id Thread::get_id() const {
-  if (native_type_ == nullptr) {
-    return Id(nullptr);
-  }
-  return Id(&native_type_->tcb());
-}
-
-inline void Thread::swap(Thread& other) {
-  std::swap(native_type_, other.native_type_);
-}
-
-inline Thread::native_handle_type Thread::native_handle() {
-  return native_type_;
-}
-
-}  // namespace pw::thread
diff --git a/pw_thread_embos/public/pw_thread_embos/thread_native.h b/pw_thread_embos/public/pw_thread_embos/thread_native.h
deleted file mode 100644
index ba7a339..0000000
--- a/pw_thread_embos/public/pw_thread_embos/thread_native.h
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_thread_embos/context.h"
-
-namespace pw::thread::backend {
-
-// The native thread is a pointer to a thread's context.
-using NativeThread = pw::thread::embos::Context*;
-
-// The native thread handle is the same as the NativeThread.
-using NativeThreadHandle = pw::thread::embos::Context*;
-
-}  // namespace pw::thread::backend
diff --git a/pw_thread_embos/public/pw_thread_embos/util.h b/pw_thread_embos/public/pw_thread_embos/util.h
deleted file mode 100644
index ff1841d..0000000
--- a/pw_thread_embos/public/pw_thread_embos/util.h
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "RTOS.h"
-#include "pw_function/function.h"
-#include "pw_status/status.h"
-
-namespace pw::thread::embos {
-
-// A callback that is executed for each thread when using ForEachThread(). The
-// callback should return true if thread iteration should continue. When this
-// callback returns false, ForEachThread() will cease iteration of threads and
-// return an `Aborted` error code.
-using ThreadCallback = pw::Function<bool(const OS_TASK&)>;
-
-// Iterates through all threads that haven't been deleted, calling the provided
-// callback on each thread.
-//
-// Precondition:
-//   OS_Start() must be called prior to using this function.
-//
-// Returns:
-//   FailedPrecondition - The scheduler has not yet been initialized.
-//   Aborted - The callback requested an early-termination of thread iteration.
-//   OkStatus - Successfully iterated over all threads.
-//
-// Warning: This is only safe to use when the scheduler is disabled.
-Status ForEachThread(const ThreadCallback& cb);
-
-namespace internal {
-
-// This function is exposed for testing. Prefer
-// pw::thread::embos::ForEachThread.
-Status ForEachThread(const OS_TASK& starting_thread, const ThreadCallback& cb);
-
-}  // namespace internal
-}  // namespace pw::thread::embos
diff --git a/pw_thread_embos/public/pw_thread_embos/yield_inline.h b/pw_thread_embos/public/pw_thread_embos/yield_inline.h
index 8acf5e0..2ad21fb 100644
--- a/pw_thread_embos/public/pw_thread_embos/yield_inline.h
+++ b/pw_thread_embos/public/pw_thread_embos/yield_inline.h
@@ -14,13 +14,12 @@
 #pragma once
 
 #include "RTOS.h"
-#include "pw_assert/assert.h"
+#include "pw_assert/light.h"
 #include "pw_thread/id.h"
 
 namespace pw::this_thread {
 
-inline void yield() noexcept {
-  // Ensure this is being called by a thread.
+inline void yield() {
   PW_DASSERT(get_id() != thread::Id());
   OS_Yield();
 }
diff --git a/pw_thread_embos/public_overrides/pw_thread_backend/thread_inline.h b/pw_thread_embos/public_overrides/pw_thread_backend/thread_inline.h
deleted file mode 100644
index fd5df5c..0000000
--- a/pw_thread_embos/public_overrides/pw_thread_backend/thread_inline.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_thread_embos/thread_inline.h"
diff --git a/pw_thread_embos/public_overrides/pw_thread_backend/thread_native.h b/pw_thread_embos/public_overrides/pw_thread_backend/thread_native.h
deleted file mode 100644
index f8dd122..0000000
--- a/pw_thread_embos/public_overrides/pw_thread_backend/thread_native.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_thread_embos/thread_native.h"
diff --git a/pw_thread_embos/sleep.cc b/pw_thread_embos/sleep.cc
index 5ba9894..03ca25f 100644
--- a/pw_thread_embos/sleep.cc
+++ b/pw_thread_embos/sleep.cc
@@ -17,7 +17,7 @@
 #include <algorithm>
 
 #include "RTOS.h"
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_chrono/system_clock.h"
 #include "pw_chrono_embos/system_clock_constants.h"
 #include "pw_thread/id.h"
@@ -26,30 +26,24 @@
 
 namespace pw::this_thread {
 
-void sleep_for(chrono::SystemClock::duration sleep_duration) {
-  // Ensure we are invoking this from a thread.
+void sleep_for(chrono::SystemClock::duration for_at_least) {
   PW_DCHECK(get_id() != thread::Id());
 
   // Yield for negative and zero length durations.
-  if (sleep_duration <= SystemClock::duration::zero()) {
+  if (for_at_least <= SystemClock::duration::zero()) {
     OS_Yield();
     return;
   }
 
-  // In case the timeout is too long for us to express through the native
-  // embOS API, we repeatedly wait with shorter durations. Note that on a tick
-  // based kernel we cannot tell how far along we are on the current tick, ergo
-  // we add one whole tick to the final duration. However, this also means that
-  // the loop must ensure that timeout + 1 is less than the max timeout.
-  constexpr SystemClock::duration kMaxTimeoutMinusOne =
-      pw::chrono::embos::kMaxTimeout - SystemClock::duration(1);
-  while (sleep_duration > kMaxTimeoutMinusOne) {
-    OS_Delay(static_cast<OS_TIME>(kMaxTimeoutMinusOne.count()));
-    sleep_duration -= kMaxTimeoutMinusOne;
-  }
   // On a tick based kernel we cannot tell how far along we are on the current
   // tick, ergo we add one whole tick to the final duration.
-  OS_Delay(static_cast<OS_TIME>(sleep_duration.count()) + 1);
+  constexpr SystemClock::duration kMaxTimeoutMinusOne =
+      pw::chrono::embos::kMaxTimeout - SystemClock::duration(1);
+  while (for_at_least > kMaxTimeoutMinusOne) {
+    OS_Delay(static_cast<OS_TIME>(kMaxTimeoutMinusOne.count()));
+    for_at_least -= kMaxTimeoutMinusOne;
+  }
+  OS_Delay(static_cast<OS_TIME>(for_at_least.count()));
 }
 
 }  // namespace pw::this_thread
diff --git a/pw_thread_embos/snapshot.cc b/pw_thread_embos/snapshot.cc
deleted file mode 100644
index 1a9e17d..0000000
--- a/pw_thread_embos/snapshot.cc
+++ /dev/null
@@ -1,148 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 PW_LOG_LEVEL PW_THREAD_EMBOS_CONFIG_LOG_LEVEL
-
-#include "pw_thread_embos/snapshot.h"
-
-#include <string_view>
-
-#include "RTOS.h"
-#include "pw_function/function.h"
-#include "pw_log/log.h"
-#include "pw_protobuf/encoder.h"
-#include "pw_status/status.h"
-#include "pw_thread/snapshot.h"
-#include "pw_thread_embos/config.h"
-#include "pw_thread_embos/util.h"
-#include "pw_thread_protos/thread.pwpb.h"
-
-namespace pw::thread::embos {
-namespace {
-
-// TODO(amontanez): This might make unit testing codepaths that use this more
-// challenging.
-inline bool ThreadIsRunning(const OS_TASK& thread) {
-  return OS_GetpCurrentTask() == &thread;
-}
-
-void CaptureThreadState(const OS_TASK& thread, Thread::StreamEncoder& encoder) {
-  if (ThreadIsRunning(thread)) {
-    PW_LOG_DEBUG("Thread state: RUNNING");
-    encoder.WriteState(ThreadState::Enum::RUNNING);
-    return;
-  }
-
-  // One byte is reserved for task status.
-  //   - The lowest two bits are for a suspend counter.
-  //   - The third-lowest bit is reserved for a "timeout." (ignored here)
-  //   - The highest five bits indicate what the task is blocked on if non-zero.
-  //
-  // Note: embOS thread state is not part of the public API. This may not be
-  // correct for all versions. This has been tested on embOS 4.22, and was
-  // initially reported for embOS 5.06.
-  //
-  // Description of how `OS_TASK::Stat` is used by embOS:
-  //   https://forum.segger.com/index.php/Thread/6548-ABANDONED-Task-state-values/?postID=23963#post23963
-#if OS_VERSION_GENERIC < 42200 || OS_VERSION_GENERIC > 50600
-#warning embOS thread state interpretation logic is not verfied as working on this version of embOS
-#endif  // OS_VERSION_GENERIC < 42200 || OS_VERSION_GENERIC > 50600
-
-  if ((thread.Stat & 0x3) != 0) {
-    PW_LOG_DEBUG("Thread state: SUSPENDED");
-    encoder.WriteState(ThreadState::Enum::SUSPENDED);
-  } else if ((thread.Stat & 0xf8) == 0) {
-    PW_LOG_DEBUG("Thread state: READY");
-    encoder.WriteState(ThreadState::Enum::READY);
-  } else {
-    PW_LOG_DEBUG("Thread state: BLOCKED");
-    encoder.WriteState(ThreadState::Enum::BLOCKED);
-  }
-}
-
-}  // namespace
-
-Status SnapshotThreads(void* running_thread_stack_pointer,
-                       SnapshotThreadInfo::StreamEncoder& encoder,
-                       ProcessThreadStackCallback& stack_dumper) {
-  struct {
-    void* running_thread_stack_pointer;
-    SnapshotThreadInfo::StreamEncoder* encoder;
-    ProcessThreadStackCallback* stack_dumper;
-    Status thread_capture_status;
-  } ctx;
-  ctx.running_thread_stack_pointer = running_thread_stack_pointer;
-  ctx.encoder = &encoder;
-  ctx.stack_dumper = &stack_dumper;
-
-  ThreadCallback thread_capture_cb([&ctx](const OS_TASK& thread) -> bool {
-    Thread::StreamEncoder thread_encoder = ctx.encoder->GetThreadsEncoder();
-    ctx.thread_capture_status.Update(
-        SnapshotThread(thread,
-                       ctx.running_thread_stack_pointer,
-                       thread_encoder,
-                       *ctx.stack_dumper));
-    // Always iterate all threads.
-    return true;
-  });
-
-  if (Status status = ForEachThread(thread_capture_cb);
-      !status.ok() && !status.IsFailedPrecondition()) {
-    PW_LOG_ERROR("Failed to iterate threads during snapshot capture: %d",
-                 static_cast<int>(status.code()));
-  }
-
-  return ctx.thread_capture_status;
-}
-
-Status SnapshotThread(const OS_TASK& thread,
-                      void* running_thread_stack_pointer,
-                      Thread::StreamEncoder& encoder,
-                      ProcessThreadStackCallback& thread_stack_callback) {
-#if OS_TRACKNAME
-  PW_LOG_DEBUG("Capturing thread info for %s", thread.Name);
-  encoder.WriteName(std::as_bytes(std::span(std::string_view(thread.Name))));
-#else
-  PW_LOG_DEBUG("Capturing thread info for thread at 0x%08x", &thread);
-#endif  // OS_TRACKNAME
-
-  CaptureThreadState(thread, encoder);
-
-#if OS_CHECKSTACK || OS_SUPPORT_MPU
-  const StackContext thread_ctx = {
-      .thread_name = thread.Name,
-
-      .stack_low_addr = reinterpret_cast<uintptr_t>(thread.pStackBot),
-
-      .stack_high_addr =
-          reinterpret_cast<uintptr_t>(thread.pStackBot) + thread.StackSize,
-
-      // If the thread is active, the stack pointer in the TCB is stale.
-      .stack_pointer = reinterpret_cast<uintptr_t>(
-          ThreadIsRunning(thread) ? running_thread_stack_pointer
-                                  : thread.pStack),
-      .stack_pointer_est_peak = reinterpret_cast<uintptr_t>(thread.pStackBot) +
-                                thread.StackSize - OS_GetStackUsed(&thread),
-  };
-
-  return SnapshotStack(thread_ctx, encoder, thread_stack_callback);
-#else
-  PW_LOG_DEBUG("Stack pointer: 0x%08x", running_thread_stack_pointer);
-  encoder.WriteStackPointer(reinterpret_cast<uintptr_t>(
-      ThreadIsRunning(thread) ? running_thread_stack_pointer : thread.pStack));
-  return encoder.status();
-#endif  // OS_CHECKSTACK || OS_SUPPORT_MPU
-}
-
-}  // namespace pw::thread::embos
diff --git a/pw_thread_embos/test_threads.cc b/pw_thread_embos/test_threads.cc
deleted file mode 100644
index db95071..0000000
--- a/pw_thread_embos/test_threads.cc
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_thread/test_threads.h"
-
-#include <chrono>
-
-#include "RTOS.h"
-#include "pw_assert/check.h"
-#include "pw_chrono/system_clock.h"
-#include "pw_log/log.h"
-#include "pw_thread/sleep.h"
-#include "pw_thread_embos/context.h"
-#include "pw_thread_embos/options.h"
-
-namespace pw::thread::test {
-namespace {
-
-std::array<embos::ContextWithStack<>, 2> thread_contexts;
-
-}  // namespace
-
-const Options& TestOptionsThread0() {
-  static constexpr embos::Options thread_0_options =
-      embos::Options()
-          .set_name("pw::TestThread0")
-          .set_context(thread_contexts[0]);
-  return thread_0_options;
-}
-
-const Options& TestOptionsThread1() {
-  static constexpr embos::Options thread_1_options =
-      embos::Options()
-          .set_name("pw::TestThread1")
-          .set_context(thread_contexts[1]);
-  return thread_1_options;
-}
-
-void WaitUntilDetachedThreadsCleanedUp() {
-  // embOS does not permit us to invoke a callback after the TCB has been
-  // unregistered from the kernel, however it does provide an API to query this
-  // status.
-  for (auto& context : thread_contexts) {
-    while (OS_IsTask(&context.tcb())) {
-      this_thread::sleep_for(
-          chrono::SystemClock::for_at_least(std::chrono::milliseconds(1)));
-    }
-  }
-}
-
-}  // namespace pw::thread::test
diff --git a/pw_thread_embos/thread.cc b/pw_thread_embos/thread.cc
deleted file mode 100644
index d8f95d5..0000000
--- a/pw_thread_embos/thread.cc
+++ /dev/null
@@ -1,160 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_thread/thread.h"
-
-#include "pw_assert/check.h"
-#include "pw_thread/id.h"
-#include "pw_thread_embos/config.h"
-#include "pw_thread_embos/context.h"
-#include "pw_thread_embos/options.h"
-
-using pw::thread::embos::Context;
-
-namespace pw::thread {
-
-void Context::ThreadEntryPoint(void* void_context_ptr) {
-  Context& context = *reinterpret_cast<Context*>(void_context_ptr);
-
-  // Invoke the user's thread function. This may never return.
-  context.user_thread_entry_function_(context.user_thread_entry_arg_);
-
-  // Use a task only critical section to guard against join() and detach().
-  OS_SuspendAllTasks();
-  if (context.detached()) {
-    // There is no threadsafe way to re-use detached threads. Callbacks
-    // registered through OS_AddOnTerminateHook CANNOT be used for this as they
-    // are invoked before the kernel is done using the task's TCB!
-    // However to enable unit test coverage we go ahead and clear this.
-    context.set_in_use(false);
-
-#if PW_THREAD_JOINING_ENABLED
-    // If the thread handle was detached before the thread finished execution,
-    // i.e. got here, then we are responsible for cleaning up the join event
-    // object.
-    OS_EVENT_Delete(&context.join_event_object());
-#endif  // PW_THREAD_JOINING_ENABLED
-
-    // Re-enable the scheduler before we delete this execution. Note this name
-    // is a bit misleading as reference counting is used.
-    OS_ResumeAllSuspendedTasks();
-    OS_TerminateTask(nullptr);
-    PW_UNREACHABLE;
-  }
-
-  // Otherwise the task finished before the thread was detached or joined, defer
-  // cleanup to Thread's join() or detach().
-  context.set_thread_done();
-  OS_ResumeAllSuspendedTasks();
-
-#if PW_THREAD_JOINING_ENABLED
-  OS_EVENT_Set(&context.join_event_object());
-#endif  // PW_THREAD_JOINING_ENABLED
-
-  // Let the thread handle owner terminate this task when they detach or join.
-  OS_Suspend(nullptr);
-  PW_UNREACHABLE;
-}
-
-void Context::TerminateThread(Context& context) {
-  // Stop the other task first.
-  OS_TerminateTask(&context.tcb());
-
-  // Mark the context as unused for potential later re-use.
-  context.set_in_use(false);
-
-#if PW_THREAD_JOINING_ENABLED
-  // Just in case someone abused our API, ensure their use of the event group is
-  // properly handled by the kernel regardless.
-  OS_EVENT_Delete(&context.join_event_object());
-#endif  // PW_THREAD_JOINING_ENABLED
-}
-
-Thread::Thread(const thread::Options& facade_options,
-               ThreadRoutine entry,
-               void* arg)
-    : native_type_(nullptr) {
-  // Cast the generic facade options to the backend specific option of which
-  // only one type can exist at compile time.
-  auto options = static_cast<const embos::Options&>(facade_options);
-  PW_DCHECK_NOTNULL(options.context(), "The Context is not optional");
-  native_type_ = options.context();
-
-  // Can't use a context more than once.
-  PW_DCHECK(!native_type_->in_use());
-
-  // Reset the state of the static context in case it was re-used.
-  native_type_->set_in_use(true);
-  native_type_->set_detached(false);
-  native_type_->set_thread_done(false);
-#if PW_THREAD_JOINING_ENABLED
-  OS_EVENT_CreateEx(&options.context()->join_event_object(),
-                    OS_EVENT_RESET_MODE_AUTO);
-#endif  // PW_THREAD_JOINING_ENABLED
-
-  // Copy over the thread name.
-  native_type_->set_name(options.name());
-
-  // In order to support functions which return and joining, a delegate is
-  // deep copied into the context with a small wrapping function to actually
-  // invoke the task with its arg.
-  native_type_->set_thread_routine(entry, arg);
-
-  OS_CreateTaskEx(&options.context()->tcb(),
-                  native_type_->name(),
-                  options.priority(),
-                  Context::ThreadEntryPoint,
-                  options.context()->stack().data(),
-                  static_cast<OS_UINT>(options.context()->stack().size_bytes()),
-                  options.time_slice_interval(),
-                  options.context());
-}
-
-void Thread::detach() {
-  PW_CHECK(joinable());
-
-  OS_Suspend(&native_type_->tcb());
-  native_type_->set_detached();
-  const bool thread_done = native_type_->thread_done();
-  OS_Resume(&native_type_->tcb());
-
-  if (thread_done) {
-    // The task finished (hit end of Context::ThreadEntryPoint) before we
-    // invoked detach, clean up the thread.
-    Context::TerminateThread(*native_type_);
-  } else {
-    // We're detaching before the task finished, defer cleanup to the task at
-    // the end of Context::ThreadEntryPoint.
-  }
-
-  // Update to no longer represent a thread of execution.
-  native_type_ = nullptr;
-}
-
-#if PW_THREAD_JOINING_ENABLED
-void Thread::join() {
-  PW_CHECK(joinable());
-  PW_CHECK(this_thread::get_id() != get_id());
-
-  OS_EVENT_Wait(&native_type_->join_event_object());
-
-  // No need for a critical section here as the thread at this point is
-  // waiting to be deleted.
-  Context::TerminateThread(*native_type_);
-
-  // Update to no longer represent a thread of execution.
-  native_type_ = nullptr;
-}
-#endif  // PW_THREAD_JOINING_ENABLED
-
-}  // namespace pw::thread
diff --git a/pw_thread_embos/util.cc b/pw_thread_embos/util.cc
deleted file mode 100644
index d3b8902..0000000
--- a/pw_thread_embos/util.cc
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_thread_embos/util.h"
-
-#include "RTOS.h"
-#include "pw_function/function.h"
-#include "pw_status/status.h"
-
-namespace pw::thread::embos {
-
-namespace internal {
-
-// Iterates through all threads that haven't been deleted, calling the provided
-// callback.
-Status ForEachThread(const OS_TASK& starting_thread, const ThreadCallback& cb) {
-  if (!OS_IsRunning()) {
-    return Status::FailedPrecondition();
-  }
-
-  const OS_TASK* thread = &starting_thread;
-  while (thread != nullptr) {
-    if (!cb(*thread)) {
-      // Early-terminate iteration if requested by the callback.
-      return Status::Aborted();
-    }
-    thread = thread->pNext;
-  }
-
-  return OkStatus();
-}
-
-}  // namespace internal
-
-Status ForEachThread(const ThreadCallback& cb) {
-  return internal::ForEachThread(*OS_Global.pTask, cb);
-}
-
-}  // namespace pw::thread::embos
diff --git a/pw_thread_freertos/BUILD b/pw_thread_freertos/BUILD
new file mode 100644
index 0000000..ab6c845
--- /dev/null
+++ b/pw_thread_freertos/BUILD
@@ -0,0 +1,182 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "id_headers",
+    hdrs = [
+        "public/pw_thread_freertos/id_inline.h",
+        "public/pw_thread_freertos/id_native.h",
+        "public_overrides/pw_thread_backend/id_inline.h",
+        "public_overrides/pw_thread_backend/id_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+)
+
+pw_cc_library(
+    name = "id",
+    deps = [
+        ":id_headers",
+        "//pw_thread:id_facade",
+    ],
+    # TODO(pwbug/317): This should depend on FreeRTOS but our third parties
+    # currently do not have Bazel support.
+)
+
+pw_cc_library(
+    name = "sleep_headers",
+    hdrs = [
+        "public/pw_thread_freertos/sleep_inline.h",
+        "public_overrides/pw_thread_backend/sleep_inline.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        "//pw_chrono:system_clock",
+    ],
+)
+
+pw_cc_library(
+    name = "sleep",
+    srcs = [
+        "sleep.cc",
+    ],
+    deps = [
+        ":sleep_headers",
+        "//pw_chrono_freertos:system_clock_headers",
+        "//pw_assert",
+        "//pw_chrono:system_clock",
+        "//pw_thread:sleep_facade",
+    ],
+    # TODO(pwbug/317): This should depend on FreeRTOS but our third parties
+    # currently do not have Bazel support.
+)
+
+# This target provides the FreeRTOS specific headers needs for thread creation.
+pw_cc_library(
+    name = "thread_headers",
+    hdrs = [
+        "public/pw_thread_freertos/context.h",
+        "public/pw_thread_freertos/options.h",
+        "public/pw_thread_freertos/config.h",
+        "public/pw_thread_freertos/thread_inline.h",
+        "public/pw_thread_freertos/thread_native.h",
+        "public_overrides/pw_thread_backend/thread_inline.h",
+        "public_overrides/pw_thread_backend/thread_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        "//pw_assert",
+        ":id",
+        "//pw_thread:thread_headers",
+    ],
+    # TODO(pwbug/317): This should depend on FreeRTOS but our third parties
+    # currently do not have Bazel support.
+)
+
+pw_cc_library(
+    name = "thread",
+    srcs = [
+        "thread.cc",
+    ],
+    deps = [
+        "//pw_assert",
+        ":id",
+        ":thread_headers",
+    ],
+    # TODO(pwbug/317): This should depend on FreeRTOS but our third parties
+    # currently do not have Bazel support.
+)
+
+pw_cc_library(
+    name = "dynamic_test_threads",
+    deps = [
+        "//pw_thread:thread_facade",
+        "//pw_thread:test_threads_header",
+        "//pw_chrono:system_clock",
+        "//pw_thread:sleep",
+    ],
+    srcs = [
+        "dynamic_test_threads.cc",
+    ]
+)
+
+pw_cc_test(
+    name = "dynamic_thread_backend_test",
+    deps = [
+        "//pw_thread:thread_facade_test",
+        ":dynamic_test_threads",
+    ]
+)
+
+pw_cc_library(
+    name = "static_test_threads",
+    deps = [
+        "//pw_thread:thread_facade",
+        "//pw_thread:test_threads_header",
+        "//pw_chrono:system_clock",
+        "//pw_thread:sleep",
+    ],
+    srcs = [
+        "static_test_threads.cc",
+    ]
+)
+
+pw_cc_test(
+    name = "static_thread_backend_test",
+    deps = [
+        "//pw_thread:thread_facade_test",
+        ":static_test_threads",
+    ]
+)
+
+pw_cc_library(
+    name = "yield_headers",
+    hdrs = [
+        "public/pw_thread_freertos/yield_inline.h",
+        "public_overrides/pw_thread_backend/yield_inline.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    # TODO(pwbug/317): This should depend on FreeRTOS but our third parties
+    # currently do not have Bazel support.
+)
+
+pw_cc_library(
+    name = "yield",
+    deps = [
+        ":yield_headers",
+        "//pw_thread:yield_facade",
+    ],
+)
+
diff --git a/pw_thread_freertos/BUILD.bazel b/pw_thread_freertos/BUILD.bazel
deleted file mode 100644
index a03e530..0000000
--- a/pw_thread_freertos/BUILD.bazel
+++ /dev/null
@@ -1,239 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_facade",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "id_headers",
-    hdrs = [
-        "public/pw_thread_freertos/id_inline.h",
-        "public/pw_thread_freertos/id_native.h",
-        "public_overrides/pw_thread_backend/id_inline.h",
-        "public_overrides/pw_thread_backend/id_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-)
-
-pw_cc_library(
-    name = "id",
-    deps = [
-        ":id_headers",
-        "//pw_thread:id_facade",
-    ],
-    # TODO(pwbug/317): This should depend on FreeRTOS but our third parties
-    # currently do not have Bazel support.
-)
-
-pw_cc_library(
-    name = "sleep_headers",
-    hdrs = [
-        "public/pw_thread_freertos/sleep_inline.h",
-        "public_overrides/pw_thread_backend/sleep_inline.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    deps = [
-        "//pw_chrono:system_clock",
-    ],
-)
-
-pw_cc_library(
-    name = "sleep",
-    srcs = [
-        "sleep.cc",
-    ],
-    deps = [
-        ":sleep_headers",
-        "//pw_assert",
-        "//pw_chrono:system_clock",
-        "//pw_chrono_freertos:system_clock_headers",
-        "//pw_thread:sleep_facade",
-    ],
-    # TODO(pwbug/317): This should depend on FreeRTOS but our third parties
-    # currently do not have Bazel support.
-)
-
-# This target provides the FreeRTOS specific headers needs for thread creation.
-pw_cc_library(
-    name = "thread_headers",
-    hdrs = [
-        "public/pw_thread_freertos/config.h",
-        "public/pw_thread_freertos/context.h",
-        "public/pw_thread_freertos/options.h",
-        "public/pw_thread_freertos/thread_inline.h",
-        "public/pw_thread_freertos/thread_native.h",
-        "public_overrides/pw_thread_backend/thread_inline.h",
-        "public_overrides/pw_thread_backend/thread_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    deps = [
-        ":id",
-        "//pw_assert",
-        "//pw_string",
-        "//pw_sync:binary_semaphore",
-        "//pw_thread:thread_headers",
-    ],
-    # TODO(pwbug/317): This should depend on FreeRTOS but our third parties
-    # currently do not have Bazel support.
-)
-
-pw_cc_library(
-    name = "thread",
-    srcs = [
-        "thread.cc",
-    ],
-    deps = [
-        ":id",
-        ":thread_headers",
-        "//pw_assert",
-    ],
-    # TODO(pwbug/317): This should depend on FreeRTOS but our third parties
-    # currently do not have Bazel support.
-)
-
-pw_cc_library(
-    name = "dynamic_test_threads",
-    srcs = [
-        "dynamic_test_threads.cc",
-    ],
-    deps = [
-        "//pw_chrono:system_clock",
-        "//pw_thread:sleep",
-        "//pw_thread:test_threads_header",
-        "//pw_thread:thread_facade",
-    ],
-)
-
-pw_cc_test(
-    name = "dynamic_thread_backend_test",
-    deps = [
-        ":dynamic_test_threads",
-        "//pw_thread:thread_facade_test",
-    ],
-)
-
-pw_cc_library(
-    name = "static_test_threads",
-    srcs = [
-        "static_test_threads.cc",
-    ],
-    deps = [
-        "//pw_chrono:system_clock",
-        "//pw_thread:sleep",
-        "//pw_thread:test_threads_header",
-        "//pw_thread:thread_facade",
-    ],
-)
-
-pw_cc_test(
-    name = "static_thread_backend_test",
-    deps = [
-        ":static_test_threads",
-        "//pw_thread:thread_facade_test",
-    ],
-)
-
-pw_cc_library(
-    name = "yield_headers",
-    hdrs = [
-        "public/pw_thread_freertos/yield_inline.h",
-        "public_overrides/pw_thread_backend/yield_inline.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    # TODO(pwbug/317): This should depend on FreeRTOS but our third parties
-    # currently do not have Bazel support.
-)
-
-pw_cc_library(
-    name = "yield",
-    deps = [
-        ":yield_headers",
-        "//pw_thread:yield_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "util",
-    srcs = [
-        "util.cc",
-    ],
-    hdrs = [
-        "public/pw_thread_freertos/util.h",
-    ],
-    deps = [
-        "//pw_function",
-        "//pw_status",
-    ],
-    # TODO(pwbug/317): This should depend on FreeRTOS but our third parties
-    # currently do not have Bazel support.
-)
-
-pw_cc_library(
-    name = "snapshot",
-    srcs = [
-        "snapshot.cc",
-    ],
-    hdrs = [
-        "public/pw_thread_freertos/snapshot.h",
-    ],
-    deps = [
-        ":freertos_tasktcb",
-        ":util",
-        "//pw_function",
-        "//pw_log",
-        "//pw_protobuf",
-        "//pw_status",
-        "//pw_thread:protos",
-        "//pw_thread:snapshot",
-    ],
-    # TODO(pwbug/317): This should depend on FreeRTOS but our third parties
-    # currently do not have Bazel support.
-)
-
-pw_cc_facade(
-    name = "freertos_tasktcb_facade",
-    hdrs = [
-        "public/pw_thread_freertos/freertos_tsktcb.h",
-    ],
-    includes = ["public"],
-    # TODO(pwbug/317): This should depend on FreeRTOS but our third parties
-    # currently do not have Bazel support.
-)
-
-pw_cc_library(
-    name = "freertos_tasktcb",
-    deps = [
-        ":freertos_tasktcb_facade",
-    ],
-)
diff --git a/pw_thread_freertos/BUILD.gn b/pw_thread_freertos/BUILD.gn
index c1027cc..7071804 100644
--- a/pw_thread_freertos/BUILD.gn
+++ b/pw_thread_freertos/BUILD.gn
@@ -14,14 +14,11 @@
 
 import("//build_overrides/pigweed.gni")
 
-import("$dir_pw_build/error.gni")
-import("$dir_pw_build/facade.gni")
 import("$dir_pw_build/module_config.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_chrono/backend.gni")
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_thread/backend.gni")
-import("$dir_pw_thread_freertos/backend.gni")
 import("$dir_pw_unit_test/test.gni")
 
 declare_args() {
@@ -57,7 +54,6 @@
     ":backend_config",
   ]
   public_deps = [
-    "$dir_pw_assert",
     "$dir_pw_interrupt:context",
     "$dir_pw_third_party/freertos",
   ]
@@ -70,17 +66,6 @@
   deps = [ "$dir_pw_thread:id.facade" ]
 }
 
-pw_build_assert("check_system_clock_backend") {
-  condition =
-      pw_chrono_SYSTEM_CLOCK_BACKEND == "" ||
-      pw_chrono_SYSTEM_CLOCK_BACKEND == "$dir_pw_chrono_freertos:system_clock"
-  message = "This FreeRTOS backend only works with the FreeRTOS " +
-            "pw::chrono::SystemClock backend " +
-            "(pw_chrono_SYSTEM_CLOCK_BACKEND = " +
-            "\"$dir_pw_chrono_freertos:system_clock\")"
-  visibility = [ ":*" ]
-}
-
 # This target provides the backend for pw::this_thread::sleep_{for,until}.
 pw_source_set("sleep") {
   public_configs = [
@@ -94,13 +79,19 @@
   public_deps = [ "$dir_pw_chrono:system_clock" ]
   sources = [ "sleep.cc" ]
   deps = [
-    ":check_system_clock_backend",
     "$dir_pw_assert",
     "$dir_pw_chrono_freertos:system_clock",
     "$dir_pw_third_party/freertos",
     "$dir_pw_thread:id",
     "$dir_pw_thread:sleep.facade",
   ]
+  assert(pw_chrono_SYSTEM_CLOCK_BACKEND == "" ||
+             pw_chrono_SYSTEM_CLOCK_BACKEND ==
+                 "$dir_pw_chrono_freertos:system_clock",
+         "The FreeRTOS pw::this_thread::sleep_{for,until} backend only works " +
+             "with the FreeRTOS pw::chrono::SystemClock backend " +
+             "(pw_chrono_SYSTEM_CLOCK_BACKEND = " +
+             "\"$dir_pw_chrono_freertos:system_clock\")")
 }
 
 # This target provides the backend for pw::thread::Thread and the headers needed
@@ -113,7 +104,6 @@
   public_deps = [
     ":config",
     "$dir_pw_assert",
-    "$dir_pw_string",
     "$dir_pw_third_party/freertos",
     "$dir_pw_thread:id",
     "$dir_pw_thread:thread.facade",
@@ -148,46 +138,6 @@
   deps = [ "$dir_pw_thread:yield.facade" ]
 }
 
-pw_source_set("util") {
-  public_configs = [ ":public_include_path" ]
-  public_deps = [
-    "$dir_pw_third_party/freertos",
-    dir_pw_function,
-    dir_pw_status,
-  ]
-  public = [ "public/pw_thread_freertos/util.h" ]
-  deps = [ dir_pw_log ]
-  sources = [ "util.cc" ]
-}
-
-pw_facade("freertos_tsktcb") {
-  backend = pw_thread_freertos_FREERTOS_TSKTCB_BACKEND
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_thread_freertos/freertos_tsktcb.h" ]
-  public_deps = [ "$dir_pw_third_party/freertos" ]
-}
-
-pw_source_set("snapshot") {
-  public_configs = [ ":public_include_path" ]
-  public_deps = [
-    ":config",
-    "$dir_pw_third_party/freertos",
-    "$dir_pw_thread:protos.pwpb",
-    "$dir_pw_thread:snapshot",
-    dir_pw_function,
-    dir_pw_protobuf,
-    dir_pw_status,
-  ]
-  public = [ "public/pw_thread_freertos/snapshot.h" ]
-  sources = [ "snapshot.cc" ]
-  deps = [
-    ":freertos_tsktcb",
-    ":util",
-    dir_pw_function,
-    dir_pw_log,
-  ]
-}
-
 pw_test_group("tests") {
   tests = [
     ":dynamic_thread_backend_test",
diff --git a/pw_thread_freertos/CMakeLists.txt b/pw_thread_freertos/CMakeLists.txt
deleted file mode 100644
index 1e524f6..0000000
--- a/pw_thread_freertos/CMakeLists.txt
+++ /dev/null
@@ -1,155 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-
-pw_add_module_config(pw_thread_freertos_CONFIG)
-
-pw_add_module_library(pw_thread_freertos.config
-  HEADERS
-    public/pw_thread_freertos/config.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_third_party.freertos
-    ${pw_thread_freertos_CONFIG}
-)
-
-# This target provides the backend for pw::thread::Id & pw::this_thread::get_id.
-pw_add_module_library(pw_thread_freertos.id
-  IMPLEMENTS_FACADES
-    pw_thread.id
-  HEADERS
-    public/pw_thread_freertos/id_inline.h
-    public/pw_thread_freertos/id_native.h
-    public_overrides/pw_thread_backend/id_inline.h
-    public_overrides/pw_thread_backend/id_native.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides
-  PUBLIC_DEPS
-    pw_assert
-    pw_interrupt.context
-    pw_third_party.freertos
-)
-
-# This target provides the backend for pw::this_thread::sleep_{for,until}.
-pw_add_module_library(pw_thread_freertos.sleep
-  IMPLEMENTS_FACADES
-    pw_thread.sleep
-  HEADERS
-    public/pw_thread_freertos/sleep_inline.h
-    public_overrides/pw_thread_backend/sleep_inline.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides
-  PUBLIC_DEPS
-    pw_chrono.system_clock
-  SOURCES
-    sleep.cc
-  PRIVATE_DEPS
-    pw_assert
-    pw_chrono_freertos.system_clock
-    pw_third_party.freertos
-    pw_thread.id
-)
-
-# This target provides the backend for pw::thread::Thread and the headers needed
-# for thread creation.
-pw_add_module_library(pw_thread_freertos.thread
-  IMPLEMENTS_FACADES
-    pw_thread.thread
-  HEADERS
-    public/pw_thread_freertos/context.h
-    public/pw_thread_freertos/options.h
-    public/pw_thread_freertos/thread_inline.h
-    public/pw_thread_freertos/thread_native.h
-    public_overrides/pw_thread_backend/thread_inline.h
-    public_overrides/pw_thread_backend/thread_native.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides
-  PUBLIC_DEPS
-    pw_assert
-    pw_polyfill.span
-    pw_string
-    pw_third_party.freertos
-    pw_thread.id
-    pw_thread_freertos.config
-  SOURCES
-    thread.cc
-)
-
-# This target provides the backend for pw::this_thread::yield.
-pw_add_module_library(pw_thread_freertos.yield
-  IMPLEMENTS_FACADES
-    pw_thread.yield
-  HEADERS
-    public/pw_thread_freertos/yield_inline.h
-    public_overrides/pw_thread_backend/yield_inline.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides
-  PUBLIC_DEPS
-    pw_assert
-    pw_third_party.freertos
-    pw_thread.id
-)
-
-pw_add_module_library(pw_thread_freertos.util
-  HEADERS
-    public/pw_thread_freertos/util.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_third_party.freertos
-    pw_function
-    pw_polyfill.span
-    pw_status
-  SOURCES
-    util.cc
-  PRIVATE_DEPS
-    pw_log
-)
-
-pw_add_module_library(pw_thread_freertos.snapshot
-  HEADERS
-    public/pw_thread_freertos/snapshot.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_function
-    pw_polyfill.span
-    pw_protobuf
-    pw_status
-    pw_third_party.freertos
-    pw_thread.protos.pwpb
-    pw_thread.snapshot
-    pw_thread_freertos.config
-  SOURCES
-    snapshot.cc
-  PRIVATE_DEPS
-    pw_thread_freertos.freertos_tsktcb
-    pw_thread_freertos.util
-    pw_log
-)
-
-pw_add_facade(pw_thread_freertos.freertos_tsktcb
-  HEADERS
-    public/pw_thread_freertos/freertos_tsktcb.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_third_party.freertos
-)
diff --git a/pw_thread_freertos/OWNERS b/pw_thread_freertos/OWNERS
deleted file mode 100644
index e9a6c58..0000000
--- a/pw_thread_freertos/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-ewout@google.com
-hepler@google.com
diff --git a/pw_thread_freertos/backend.gni b/pw_thread_freertos/backend.gni
deleted file mode 100644
index f88b775..0000000
--- a/pw_thread_freertos/backend.gni
+++ /dev/null
@@ -1,25 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-declare_args() {
-  # Unfortunately FreeRTOS entirely hides the contents of the TCB inside of
-  # tasks.c but it's necessary for snapshot processing in order to access the
-  # stack limits. Set this to a pw_source_set which provides the tskTCB struct
-  # definition for snapshot to work with FreeRTOS.
-  #
-  # See the pw_thread_freertos docs for more details.
-  pw_thread_freertos_FREERTOS_TSKTCB_BACKEND = ""
-}
diff --git a/pw_thread_freertos/docs.rst b/pw_thread_freertos/docs.rst
index 4e50030..6628718 100644
--- a/pw_thread_freertos/docs.rst
+++ b/pw_thread_freertos/docs.rst
@@ -1,273 +1,8 @@
 .. _module-pw_thread_freertos:
 
-==================
+------------------
 pw_thread_freertos
-==================
-This is a set of backends for pw_thread based on FreeRTOS.
+------------------
+This is a set of backends for pw_thread based on FreeRTOS. It is not ready for
+use, and is under construction.
 
-.. Warning::
-  This module is still under construction, the API is not yet stable.
-
------------------------
-Thread Creation Backend
------------------------
-A backend for ``pw::thread::Thread`` is offered using ``xTaskCreateStatic()``.
-Optional dynamic allocation for threads is supported using ``xTaskCreate()``.
-Optional joining support is enabled via an ``StaticEventGroup_t`` in each
-thread's context.
-
-This backend always permits users to start threads where static contexts are
-passed in as an option. As a quick example, a detached thread can be created as
-follows:
-
-.. code-block:: cpp
-
-  #include "FreeRTOS.h"
-  #include "pw_thread/detached_thread.h"
-  #include "pw_thread_freertos/config.h"
-  #include "pw_thread_freertos/context.h"
-  #include "pw_thread_freertos/options.h"
-
-  constexpr UBaseType_t kFooPriority =
-      pw::thread::freertos::config::kDefaultPriority;
-  constexpr size_t kFooStackSizeWords =
-      pw::thread::freertos::config::kDefaultStackSizeWords;
-
-  pw::thread::freertos::StaticContextWithStack<kFooStackSizeWords>
-      example_thread_context;
-  void StartExampleThread() {
-    pw::thread::DetachedThread(
-        pw::thread::freertos::Options()
-            .set_name("static_example_thread")
-            .set_priority(kFooPriority)
-            .set_static_context(example_thread_context),
-        example_thread_function);
-  }
-
-Alternatively when ``PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED`` is
-enabled, dynamic thread allocation can be used. The above example could instead
-be done as follows:
-
-.. code-block:: cpp
-
-  #include "FreeRTOS.h"
-  #include "pw_thread/detached_thread.h"
-  #include "pw_thread_freertos/config.h"
-  #include "pw_thread_freertos/context.h"
-  #include "pw_thread_freertos/options.h"
-
-  constexpr UBaseType_t kFooPriority =
-      pw::thread::freertos::config::kDefaultPriority;
-  constexpr size_t kFooStackSizeWords =
-      pw::thread::freertos::config::kDefaultStackSizeWords;
-
-  void StartExampleThread() {
-    pw::thread::DetachedThread(
-        pw::thread::freertos::Options()
-            .set_name("dyanmic_example_thread")
-            .set_priority(kFooPriority)
-            .set_stack_size(kFooStackSizeWords),
-        example_thread_function)
-  }
-
-
-Module Configuration Options
-============================
-The following configurations can be adjusted via compile-time configuration of
-this module, see the
-:ref:`module documentation <module-structure-compile-time-configuration>` for
-more details.
-
-.. c:macro:: PW_THREAD_FREERTOS_CONFIG_JOINING_ENABLED
-
-  Whether thread joining is enabled. By default this is disabled.
-
-  We suggest only enabling this when thread joining is required to minimize
-  the RAM and ROM cost of threads.
-
-  Enabling this grows the RAM footprint of every ``pw::thread::Thread`` as it
-  adds a ``StaticEventGroup_t`` to every thread's
-  ``pw::thread::freertos::Context``. In addition, there is a minute ROM cost to
-  construct and destroy this added object.
-
-  ``PW_THREAD_JOINING_ENABLED`` gets set to this value.
-
-.. c:macro:: PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
-
-  Whether dynamic allocation for threads (stacks and contexts) is enabled. By
-  default this matches the FreeRTOS configuration on whether dynamic
-  allocations are enabled. Note that static contexts **must** be provided if
-  dynamic allocations are disabled.
-
-.. c:macro:: PW_THREAD_FREERTOS_CONFIG_DEFAULT_STACK_SIZE_WORDS
-
-   The default stack size in words. By default this uses the minimal FreeRTOS
-   stack size based on ``configMINIMAL_STACK_SIZE``.
-
-.. c:macro:: PW_THREAD_FREERTOS_CONFIG_DEFAULT_PRIORITY
-
-   The default thread priority. By default this uses the minimal FreeRTOS
-   priority level above the idle priority (``tskIDLE_PRIORITY + 1``).
-
-.. c:macro:: PW_THREAD_FREERTOS_CONFIG_MAXIMUM_PRIORITY
-
-  The maximum thread priority. By default this uses the value below the
-  number of priorities defined by the FreeRTOS configuration
-  (``configMAX_PRIORITIES - 1``).
-
-.. c:macro:: PW_THREAD_FREERTOS_CONFIG_LOG_LEVEL
-
-  The log level to use for this module. Logs below this level are omitted.
-
-FreeRTOS Thread Options
-=======================
-.. cpp:class:: pw::thread::freertos::Options
-
-  .. cpp:function:: set_name(const char* name)
-
-    Sets the name for the FreeRTOS task, note that this will be truncated
-    based on ``configMAX_TASK_NAME_LEN``. This is deep copied by FreeRTOS into
-    the task's task control block (TCB).
-
-  .. cpp:function:: set_priority(UBaseType_t priority)
-
-    Sets the priority for the FreeRTOS task. This must be a value between
-    ``0`` to ``PW_THREAD_FREERTOS_CONFIG_MAXIMUM_PRIORITY``. Higher priority
-    values have a higher priority.
-
-    Note that the idle task priority, ``tskIDLE_PRIORITY``, is fixed to ``0``.
-    See the `FreeRTOS documentation on the idle task
-    <https://www.freertos.org/RTOS-idle-task.html>`_ for more details.
-
-    Precondition: This must be <= PW_THREAD_FREERTOS_CONFIG_MAXIMUM_PRIORITY.
-
-  .. cpp:function:: set_stack_size(size_t size_words)
-
-    Set the stack size in words for a dynamically thread.
-
-    This is only available if
-    ``PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED`` is enabled.
-
-    Precondition: size_words must be >= ``configMINIMAL_STACK_SIZE``
-
-  .. cpp:function:: set_static_context(pw::thread::freertos::Context& context)
-
-    Set the pre-allocated context (all memory needed to run a thread). The
-    ``StaticContext`` can either be constructed with an externally provided
-    ``std::span<StackType_t>`` stack or the templated form of
-    ``StaticContextWithStack<kStackSizeWords>`` can be used.
-
-
------------------------------
-Thread Identification Backend
------------------------------
-A backend for ``pw::thread::Id`` and ``pw::thread::get_id()`` is offerred using
-``xTaskGetCurrentTaskHandle()``. It uses ``DASSERT`` to ensure that it is not
-invoked from interrupt context and if possible that the scheduler has started
-via ``xTaskGetSchedulerState()``.
-
---------------------
-Thread Sleep Backend
---------------------
-A backend for ``pw::thread::sleep_for()`` and ``pw::thread::sleep_until()`` is
-offerred using ``vTaskDelay()`` if the duration is at least one tick, else
-``taskYIELD()`` is used. It uses ``pw::this_thread::get_id() != thread::Id()``
-to ensure it invoked only from a thread.
-
---------------------
-Thread Yield Backend
---------------------
-A backend for ``pw::thread::yield()`` is offered using via ``taskYIELD()``.
-It uses ``pw::this_thread::get_id() != thread::Id()`` to ensure it invoked only
-from a thread.
-
----------
-Utilities
----------
-``ForEachThread()``
-===================
-In cases where an operation must be performed for every thread,
-``ForEachThread()`` can be used to iterate over all the created thread TCBs.
-Note that it's only safe to use this while the scheduler and interrupts are
-disabled.
-
-Calling this before the scheduler has started, via ``vTaskStartScheduler()``, is
-non-fatal but will result in no action and a ``FailedPrecondition`` error code.
-
-An ``Aborted`` error status is returned if the provided callback returns
-``false`` to request an early termination of thread iteration.
-
-*Return values*
-
-* ``FailedPrecondition``: Returned when ``ForEachThread()`` is run before the OS
-  has been initialized.
-* ``Aborted``: The callback requested an early-termination of thread iteration.
-* ``OkStatus``: The callback has been successfully run with every thread.
-
-.. Note:: This uses an unsupported method to iterate the threads in a more
-   efficient manner while also supporting interrupt contexts. This requires
-   linking against internal statics from the FreeRTOS kernel,
-   :ref:`pw_third_party_freertos_DISABLE_TASKS_STATICS <third_party-freertos_disable_task_statics>`
-   must be used.
-
---------------------
-Snapshot integration
---------------------
-This ``pw_thread`` backend provides helper functions that capture FreeRTOS
-thread state to a ``pw::thread::Thread`` proto.
-
-FreeRTOS tskTCB facade
-======================
-Unfortunately FreeRTOS entirely hides the contents of the TCB inside of
-``Source/tasks.c``, but it's necessary for snapshot processing in order to
-access the stack limits from interrupt contexts. For this reason, FreeRTOS
-snapshot integration relies on the ``pw_thread_freertos:freertos_tsktcb`` facade
-to provide the ``tskTCB`` definition.
-
-The selected backend is expected to provide the ``struct tskTCB`` definition
-through ``pw_thread_freertos_backend/freertos_tsktcb.h``. The facade asserts
-that this definition matches the size of FreeRTOS's ``StaticTask_T`` which is
-the public opaque TCB type.
-
-``SnapshotThreads()``
-=====================
-``SnapshotThread()`` captures the thread name, state, and stack information for
-the provided TCB to a ``pw::thread::Thread`` protobuf encoder. To ensure
-the most up-to-date information is captured, the stack pointer for the currently
-running thread must be provided for cases where the running thread is being
-captured. For ARM Cortex-M CPUs, you can do something like this:
-
-.. Code:: cpp
-
-  // Capture PSP.
-  void* stack_ptr = 0;
-  asm volatile("mrs %0, psp\n" : "=r"(stack_ptr));
-  pw::thread::ProcessThreadStackCallback cb =
-      [](pw::thread::Thread::StreamEncoder& encoder,
-         pw::ConstByteSpan stack) -> pw::Status {
-    return encoder.WriteRawStack(stack);
-  };
-  pw::thread::threadx::SnapshotThread(my_thread, thread_state, stack_ptr,
-                                      snapshot_encoder, cb);
-
-``SnapshotThreads()`` wraps the singular thread capture to instead captures
-all created threads to a ``pw::thread::SnapshotThreadInfo`` message which also
-captures the thread state for you. This proto
-message overlays a snapshot, so it is safe to static cast a
-``pw::snapshot::Snapshot::StreamEncoder`` to a
-``pw::thread::SnapshotThreadInfo::StreamEncoder`` when calling this function.
-
-.. Note:: ``SnapshotThreads()`` is only safe to use this while the scheduler and
-   interrupts are disabled as it relies on ``ForEachThread()``.
-
-Thread Stack Capture
---------------------
-Snapshot attempts to capture as much of the thread stack state as possible,
-however it can be limited by on the FreeRTOS configuration.
-
-The ``stack_start_ptr`` can only be provided if the ``portSTACK_GROWTH`` is < 0,
-i.e. the stack grows down, when ``configRECORD_STACK_HIGH_ADDRESS`` is enabled.
-
-The ``stack_pointer_est_peak`` can only be provided when
-``config_USE_TRACE_FACILITY`` and/or ``INCLUDE_uxTaskGetStackHighWaterMark`` are
-enabled and ``stack_start_ptr``'s requirements above are met.
diff --git a/pw_thread_freertos/public/pw_thread_freertos/config.h b/pw_thread_freertos/public/pw_thread_freertos/config.h
index deae58d..90c8c7a 100644
--- a/pw_thread_freertos/public/pw_thread_freertos/config.h
+++ b/pw_thread_freertos/public/pw_thread_freertos/config.h
@@ -18,16 +18,8 @@
 #include "task.h"
 
 // Whether thread joining is enabled. By default this is disabled.
-//
-// We suggest only enabling this when thread joining is required to minimize
-// the RAM and ROM cost of threads.
-//
-// Enabling this grows the RAM footprint of every pw::thread::Thread as it adds
-// a StaticEventGroup_t to every thread's pw::thread::freertos::Context. In
-// addition, there is a minute ROM cost to construct and destroy this added
-// object.
-//
-// PW_THREAD_JOINING_ENABLED gets set to this value.
+// When enabled this adds a StaticEventGroup_t & EventGroupHandle_t to
+// every pw::thread::Thread's context.
 #ifndef PW_THREAD_FREERTOS_CONFIG_JOINING_ENABLED
 #define PW_THREAD_FREERTOS_CONFIG_JOINING_ENABLED 0
 #endif  // PW_THREAD_FREERTOS_CONFIG_JOINING_ENABLED
@@ -52,22 +44,12 @@
   configMINIMAL_STACK_SIZE
 #endif  // PW_THREAD_FREERTOS_CONFIG_DEFAULT_STACK_SIZE_WORDS
 
-// The default thread priority. By default this uses the minimal FreeRTOS
+// The default stack size in words. By default this uses the minimal FreeRTOS
 // priority level above the idle priority.
 #ifndef PW_THREAD_FREERTOS_CONFIG_DEFAULT_PRIORITY
-#define PW_THREAD_FREERTOS_CONFIG_DEFAULT_PRIORITY (tskIDLE_PRIORITY + 1)
+#define PW_THREAD_FREERTOS_CONFIG_DEFAULT_PRIORITY tskIDLE_PRIORITY + 1
 #endif  // PW_THREAD_FREERTOS_CONFIG_DEFAULT_PRIORITY
 
-// The maximum thread priority defined by the FreeRTOS configuration.
-#ifndef PW_THREAD_FREERTOS_CONFIG_MAXIMUM_PRIORITY
-#define PW_THREAD_FREERTOS_CONFIG_MAXIMUM_PRIORITY (configMAX_PRIORITIES - 1)
-#endif  // PW_THREAD_FREERTOS_CONFIG_MAXIMUM_PRIORITY
-
-// The log level to use for this module. Logs below this level are omitted.
-#ifndef PW_THREAD_FREERTOS_CONFIG_LOG_LEVEL
-#define PW_THREAD_FREERTOS_CONFIG_LOG_LEVEL PW_LOG_LEVEL_DEBUG
-#endif  // PW_THREAD_FREERTOS_CONFIG_LOG_LEVEL
-
 namespace pw::thread::freertos::config {
 
 inline constexpr size_t kMinimumStackSizeWords = configMINIMAL_STACK_SIZE;
@@ -75,7 +57,5 @@
     PW_THREAD_FREERTOS_CONFIG_DEFAULT_STACK_SIZE_WORDS;
 inline constexpr UBaseType_t kDefaultPriority =
     PW_THREAD_FREERTOS_CONFIG_DEFAULT_PRIORITY;
-inline constexpr UBaseType_t kMaximumPriority =
-    PW_THREAD_FREERTOS_CONFIG_MAXIMUM_PRIORITY;
 
 }  // namespace pw::thread::freertos::config
diff --git a/pw_thread_freertos/public/pw_thread_freertos/context.h b/pw_thread_freertos/public/pw_thread_freertos/context.h
index 026bfdb..71bf1d5 100644
--- a/pw_thread_freertos/public/pw_thread_freertos/context.h
+++ b/pw_thread_freertos/public/pw_thread_freertos/context.h
@@ -56,8 +56,8 @@
 
   using ThreadRoutine = void (*)(void* arg);
   void set_thread_routine(ThreadRoutine entry, void* arg) {
-    user_thread_entry_function_ = entry;
-    user_thread_entry_arg_ = arg;
+    entry_ = entry;
+    arg_ = arg;
   }
 
   bool detached() const { return detached_; }
@@ -75,12 +75,12 @@
   StaticEventGroup_t& join_event_group() { return event_group_; }
 #endif  // PW_THREAD_JOINING_ENABLED
 
-  static void ThreadEntryPoint(void* void_context_ptr);
+  static void RunThread(void* void_context_ptr);
   static void TerminateThread(Context& context);
 
   TaskHandle_t task_handle_ = nullptr;
-  ThreadRoutine user_thread_entry_function_ = nullptr;
-  void* user_thread_entry_arg_ = nullptr;
+  ThreadRoutine entry_ = nullptr;
+  void* arg_ = nullptr;
 #if PW_THREAD_JOINING_ENABLED
   // Note that the FreeRTOS life cycle of this event group is managed together
   // with the task life cycle, not this object's life cycle.
@@ -96,7 +96,7 @@
 //
 // Example usage:
 //
-//   std::array<StackType_t, kFooStackSizeWords> example_thread_stack;
+//   std::array<StackType_t, 42> example_thread_stack;
 //   pw::thread::freertos::Context example_thread_context(example_thread_stack);
 //   void StartExampleThread() {
 //      pw::thread::Thread(
@@ -125,8 +125,7 @@
 //
 // Example usage:
 //
-//   pw::thread::freertos::ContextWithStack<kFooStackSizeWords>
-//       example_thread_context;
+//   pw::thread::freertos::ContextWithStack<42> example_thread_context;
 //   void StartExampleThread() {
 //      pw::thread::Thread(
 //        pw::thread::freertos::Options()
diff --git a/pw_thread_freertos/public/pw_thread_freertos/freertos_tsktcb.h b/pw_thread_freertos/public/pw_thread_freertos/freertos_tsktcb.h
deleted file mode 100644
index 2685018..0000000
--- a/pw_thread_freertos/public/pw_thread_freertos/freertos_tsktcb.h
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "FreeRTOS.h"
-#include "task.h"
-
-// The backend is expected to provide this header which defines the tskTCB
-// strut which matches FreeRTOS's definition of a task control block which is
-// opaquely presented via the public API as the StaticTask_t.
-#include "pw_thread_freertos_backend/freertos_tsktcb.h"
-
-static_assert(sizeof(tskTCB) == sizeof(StaticTask_t),
-              "The tskTCB mirror of the real task TCB doesn't match FreeRTOS");
diff --git a/pw_thread_freertos/public/pw_thread_freertos/id_inline.h b/pw_thread_freertos/public/pw_thread_freertos/id_inline.h
index fad5497..0c152e6 100644
--- a/pw_thread_freertos/public/pw_thread_freertos/id_inline.h
+++ b/pw_thread_freertos/public/pw_thread_freertos/id_inline.h
@@ -14,7 +14,7 @@
 #pragma once
 
 #include "FreeRTOS.h"
-#include "pw_assert/assert.h"
+#include "pw_assert/light.h"
 #include "pw_interrupt/context.h"
 #include "pw_thread/id.h"
 #include "task.h"
@@ -22,14 +22,12 @@
 namespace pw::this_thread {
 
 inline thread::Id get_id() noexcept {
-  // Ensure this is not being called by an interrupt.
   PW_DASSERT(!interrupt::InInterruptContext());
-
 #if INCLUDE_xTaskGetSchedulerState == 1 or configUSE_TIMERS == 1
-  // Ensure the kernel is running.
-  PW_DASSERT(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED);
+  if (xTaskGetSchedulerState() == taskSCHEDULER_NOT_STARTED) {
+    return thread::Id();
+  }
 #endif  // xTaskGetSchedulerState available.
-
   return thread::Id(xTaskGetCurrentTaskHandle());
 }
 
diff --git a/pw_thread_freertos/public/pw_thread_freertos/options.h b/pw_thread_freertos/public/pw_thread_freertos/options.h
index b87e2c2..921faa4 100644
--- a/pw_thread_freertos/public/pw_thread_freertos/options.h
+++ b/pw_thread_freertos/public/pw_thread_freertos/options.h
@@ -48,31 +48,22 @@
 
   // Sets the name for the FreeRTOS task, note that this will be truncated
   // based on configMAX_TASK_NAME_LEN.
-  // This is deep copied by FreeRTOS into the task's task control block (TCB).
-  constexpr Options& set_name(const char* name) {
+  constexpr Options set_name(const char* name) {
     name_ = name;
     return *this;
   }
 
-  // Sets the priority for the FreeRTOS task. This must be a value between
-  // 0 to PW_THREAD_FREERTOS_CONFIG_MAXIMUM_PRIORITY. Higher priority
-  // values have a higher priority.
-  //
-  // Note that the idle task priority, tskIDLE_PRIORITY, is fixed to 0.
-  // See the FreeRTOS documentation on the idle task for more details.
-  //
-  // Precondition: This must be <= PW_THREAD_FREERTOS_CONFIG_MAXIMUM_PRIORITY.
-  constexpr Options& set_priority(UBaseType_t priority) {
-    PW_DASSERT(priority <= config::kMaximumPriority);
+  // Sets the priority for the FreeRTOS task, see FreeRTOS xTaskCreate for more
+  // detail.
+  constexpr Options set_priority(UBaseType_t priority) {
     priority_ = priority;
     return *this;
   }
 
 #if PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
-  // Set the stack size of dynamic thread allocations.
-  //
-  // Precondition: size_words must be >= configMINIMAL_STACK_SIZE
-  constexpr Options& set_stack_size(size_t size_words) {
+  // Set the stack size for dynamic thread allocations, see FreeRTOS xTaskCreate
+  // for more detail.
+  constexpr Options set_stack_size(size_t size_words) {
     PW_DASSERT(size_words >= config::kMinimumStackSizeWords);
     stack_size_words_ = size_words;
     return *this;
@@ -81,7 +72,7 @@
 
   // Set the pre-allocated context (all memory needed to run a thread), see the
   // pw::thread::freertos::StaticContext for more detail.
-  constexpr Options& set_static_context(StaticContext& context) {
+  constexpr Options set_static_context(StaticContext& context) {
     context_ = &context;
     return *this;
   }
diff --git a/pw_thread_freertos/public/pw_thread_freertos/sleep_inline.h b/pw_thread_freertos/public/pw_thread_freertos/sleep_inline.h
index 44fe603..f23fdc9 100644
--- a/pw_thread_freertos/public/pw_thread_freertos/sleep_inline.h
+++ b/pw_thread_freertos/public/pw_thread_freertos/sleep_inline.h
@@ -18,10 +18,10 @@
 namespace pw::this_thread {
 
 // TODO(ewout): Consider optional vTaskDelayUntil support to minimize slop.
-inline void sleep_until(chrono::SystemClock::time_point wakeup_time) {
+inline void sleep_until(chrono::SystemClock::time_point until_at_least) {
   // Note that if this deadline is in the future, it will get rounded up by
   // one whole tick due to how sleep_for is implemented.
-  return sleep_for(wakeup_time - chrono::SystemClock::now());
+  return sleep_for(until_at_least - chrono::SystemClock::now());
 }
 
 }  // namespace pw::this_thread
diff --git a/pw_thread_freertos/public/pw_thread_freertos/snapshot.h b/pw_thread_freertos/public/pw_thread_freertos/snapshot.h
deleted file mode 100644
index ea5735f..0000000
--- a/pw_thread_freertos/public/pw_thread_freertos/snapshot.h
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <span>
-
-#include "FreeRTOS.h"
-#include "pw_protobuf/encoder.h"
-#include "pw_status/status.h"
-#include "pw_thread/snapshot.h"
-#include "pw_thread_protos/thread.pwpb.h"
-#include "task.h"
-
-namespace pw::thread::freertos {
-
-// Captures all FreeRTOS threads in a system as part of a snapshot.
-//
-// Note: this requires the pw_thread_freertos:freertos_tskcb backend to be
-// set in order to access the stack limits inside of tskTCB.
-//
-// An updated running_thread_stack_pointer must be provided in order for the
-// running thread's context to reflect the running state. In addition a task
-// status buffer must be provided which can fit uxTaskGetNumberOfTasks()
-// entries. For ARM, you might do something like this:
-//
-//    // Capture PSP.
-//    void* stack_ptr = 0;
-//    asm volatile("mrs %0, psp\n" : "=r"(stack_ptr));
-//    pw::thread::ProcessThreadStackCallback cb =
-//        [](pw::thread::Thread::StreamEncoder& encoder,
-//           pw::ConstByteSpan stack) -> pw::Status {
-//      return encoder.WriteRawStack(stack);
-//    };
-//    pw::thread::freertos::SnapshotThread(stack_ptr, snapshot_encoder, cb,
-//                                         task_status_buffer);
-//
-// Warning: This is only safe to use when the scheduler and interrupts are
-// disabled.
-Status SnapshotThreads(void* running_thread_stack_pointer,
-                       SnapshotThreadInfo::StreamEncoder& encoder,
-                       ProcessThreadStackCallback& thread_stack_callback);
-
-// Captures only the provided thread handle as a pw::thread::Thread proto
-// message.
-//
-// An updated running_thread_stack_pointer must be provided in order for the
-// running thread's context to reflect the current state. If the thread being
-// captured is not the running thread, the value is ignored. Note that the
-// stack pointer in the thread handle is almost always stale on the running
-// thread.
-//
-// Note: this requires the pw_thread_freertos:freertos_tskcb backend to be
-// set in order to access the stack limits inside of tskTCB.
-//
-// Captures the following proto fields:
-//
-//   pw.thread.Thread:
-//     name
-//     state
-//     stack_start_pointer
-//     stack_end_pointer (if (portSTACK_GROWTH > 0) ||
-//                           (configRECORD_STACK_HIGH_ADDRESS == 1))
-//     stack_pointer
-//
-Status SnapshotThread(TaskHandle_t thread,
-                      eTaskState thread_state,
-                      void* running_thread_stack_pointer,
-                      Thread::StreamEncoder& encoder,
-                      ProcessThreadStackCallback& thread_stack_callback);
-
-}  // namespace pw::thread::freertos
diff --git a/pw_thread_freertos/public/pw_thread_freertos/thread_inline.h b/pw_thread_freertos/public/pw_thread_freertos/thread_inline.h
index e692122..e0d38b3 100644
--- a/pw_thread_freertos/public/pw_thread_freertos/thread_inline.h
+++ b/pw_thread_freertos/public/pw_thread_freertos/thread_inline.h
@@ -16,7 +16,7 @@
 #include <algorithm>
 
 #include "FreeRTOS.h"
-#include "pw_assert/assert.h"
+#include "pw_assert/light.h"
 #include "pw_thread/id.h"
 #include "pw_thread_freertos/config.h"
 #include "pw_thread_freertos/options.h"
diff --git a/pw_thread_freertos/public/pw_thread_freertos/util.h b/pw_thread_freertos/public/pw_thread_freertos/util.h
deleted file mode 100644
index 55b39f2..0000000
--- a/pw_thread_freertos/public/pw_thread_freertos/util.h
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <span>
-
-#include "FreeRTOS.h"
-#include "pw_function/function.h"
-#include "pw_status/status.h"
-#include "task.h"
-
-namespace pw::thread::freertos {
-
-// A callback that is executed for each thread when using ForEachThread(). The
-// callback should return true if thread iteration should continue. When this
-// callback returns false, ForEachThread() will cease iteration of threads and
-// return an `Aborted` error code.
-using ThreadCallback = pw::Function<bool(TaskHandle_t, eTaskState)>;
-
-// Iterates through all threads that haven't been deleted, calling the provided
-// callback on each thread. If the callback fails on one thread, the iteration
-// stops.
-//
-// Note: this uses an unsupported method to iterate the threads in a more
-// efficient manner while also supporting interrupt contexts. This requires
-// linking against internal statics from theFreeRTOS kernel,
-// pw_third_party_freertos_DISABLE_TASKS_STATICS must be used.
-//
-// Precondition:
-//   vTaskStartScheduler() must be called prior to using this function.
-//
-// Returns:
-//   FailedPrecondition - The scheduler has not yet been initialized.
-//   Aborted - The callback requested an early-termination of thread iteration.
-//   OkStatus - Successfully iterated over all threads.
-//
-// Warning: This is only safe to use when the scheduler and interrupts are
-// disabled.
-Status ForEachThread(const ThreadCallback& cb);
-
-}  // namespace pw::thread::freertos
diff --git a/pw_thread_freertos/public/pw_thread_freertos/yield_inline.h b/pw_thread_freertos/public/pw_thread_freertos/yield_inline.h
index c2afaff..e428c38 100644
--- a/pw_thread_freertos/public/pw_thread_freertos/yield_inline.h
+++ b/pw_thread_freertos/public/pw_thread_freertos/yield_inline.h
@@ -16,14 +16,13 @@
 #include <algorithm>
 
 #include "FreeRTOS.h"
-#include "pw_assert/assert.h"
+#include "pw_assert/light.h"
 #include "pw_thread/id.h"
 #include "task.h"
 
 namespace pw::this_thread {
 
 inline void yield() noexcept {
-  // Ensure this is being called by a thread.
   PW_DASSERT(get_id() != thread::Id());
   taskYIELD();
 }
diff --git a/pw_thread_freertos/sleep.cc b/pw_thread_freertos/sleep.cc
index 1efa3df..0decfa0 100644
--- a/pw_thread_freertos/sleep.cc
+++ b/pw_thread_freertos/sleep.cc
@@ -17,7 +17,7 @@
 #include <algorithm>
 
 #include "FreeRTOS.h"
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_chrono/system_clock.h"
 #include "pw_chrono_freertos/system_clock_constants.h"
 #include "pw_thread/id.h"
@@ -27,30 +27,24 @@
 
 namespace pw::this_thread {
 
-void sleep_for(SystemClock::duration sleep_duration) {
-  // Ensure this is being called by a thread.
+void sleep_for(SystemClock::duration for_at_least) {
   PW_DCHECK(get_id() != thread::Id());
 
   // Yield for negative and zero length durations.
-  if (sleep_duration <= SystemClock::duration::zero()) {
+  if (for_at_least <= SystemClock::duration::zero()) {
     taskYIELD();
     return;
   }
 
-  // In case the timeout is too long for us to express through the native
-  // FreeRTOS API, we repeatedly wait with shorter durations. Note that on a
-  // tick based kernel we cannot tell how far along we are on the current tick,
-  // ergo we add one whole tick to the final duration. However, this also means
-  // that the loop must ensure that timeout + 1 is less than the max timeout.
-  constexpr SystemClock::duration kMaxTimeoutMinusOne =
-      pw::chrono::freertos::kMaxTimeout - SystemClock::duration(1);
-  while (sleep_duration > kMaxTimeoutMinusOne) {
-    vTaskDelay(static_cast<TickType_t>(kMaxTimeoutMinusOne.count()));
-    sleep_duration -= kMaxTimeoutMinusOne;
-  }
   // On a tick based kernel we cannot tell how far along we are on the current
   // tick, ergo we add one whole tick to the final duration.
-  vTaskDelay(static_cast<TickType_t>(sleep_duration.count() + 1));
+  constexpr SystemClock::duration kMaxTimeoutMinusOne =
+      pw::chrono::freertos::kMaxTimeout - SystemClock::duration(1);
+  while (for_at_least > kMaxTimeoutMinusOne) {
+    vTaskDelay(static_cast<TickType_t>(kMaxTimeoutMinusOne.count()));
+    for_at_least -= kMaxTimeoutMinusOne;
+  }
+  vTaskDelay(static_cast<TickType_t>(for_at_least.count() + 1));
 }
 
 }  // namespace pw::this_thread
diff --git a/pw_thread_freertos/snapshot.cc b/pw_thread_freertos/snapshot.cc
deleted file mode 100644
index 0d7b8a8..0000000
--- a/pw_thread_freertos/snapshot.cc
+++ /dev/null
@@ -1,174 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 PW_LOG_LEVEL PW_THREAD_FREERTOS_CONFIG_LOG_LEVEL
-
-#include "pw_thread_freertos/snapshot.h"
-
-#include <span>
-#include <string_view>
-
-#include "FreeRTOS.h"
-#include "pw_function/function.h"
-#include "pw_log/log.h"
-#include "pw_protobuf/encoder.h"
-#include "pw_status/status.h"
-#include "pw_thread/snapshot.h"
-#include "pw_thread_freertos/config.h"
-#include "pw_thread_freertos/freertos_tsktcb.h"
-#include "pw_thread_freertos/util.h"
-#include "pw_thread_protos/thread.pwpb.h"
-#include "task.h"
-
-namespace pw::thread::freertos {
-namespace {
-
-// The externed function is an internal FreeRTOS kernel function from
-// FreeRTOS/Source/tasks.c needed in order to calculate a thread's stack usage
-// from interrupts which the native APIs do not permit.
-#if ((configUSE_TRACE_FACILITY == 1) || \
-     (INCLUDE_uxTaskGetStackHighWaterMark == 1))
-extern "C" uint16_t prvTaskCheckFreeStackSpace(const uint8_t* pucStackByte);
-#endif  // ((configUSE_TRACE_FACILITY == 1) ||
-        // (INCLUDE_uxTaskGetStackHighWaterMark == 1))
-
-void CaptureThreadState(eTaskState thread_state,
-                        Thread::StreamEncoder& encoder) {
-  switch (thread_state) {
-    case eRunning:
-      PW_LOG_DEBUG("Thread state: RUNNING");
-      encoder.WriteState(ThreadState::Enum::RUNNING);
-      return;
-
-    case eReady:
-      PW_LOG_DEBUG("Thread state: READY");
-      encoder.WriteState(ThreadState::Enum::READY);
-      return;
-
-    case eBlocked:
-      PW_LOG_DEBUG("Thread state: BLOCKED");
-      encoder.WriteState(ThreadState::Enum::BLOCKED);
-      return;
-
-    case eSuspended:
-      PW_LOG_DEBUG("Thread state: SUSPENDED");
-      encoder.WriteState(ThreadState::Enum::SUSPENDED);
-      return;
-
-    case eDeleted:
-      PW_LOG_DEBUG("Thread state: INACTIVE");
-      encoder.WriteState(ThreadState::Enum::INACTIVE);
-      return;
-
-    case eInvalid:
-    default:
-      PW_LOG_DEBUG("Thread state: UNKNOWN");
-      encoder.WriteState(ThreadState::Enum::UNKNOWN);
-      return;
-  }
-}
-
-}  // namespace
-
-Status SnapshotThreads(void* running_thread_stack_pointer,
-                       SnapshotThreadInfo::StreamEncoder& encoder,
-                       ProcessThreadStackCallback& stack_dumper) {
-  struct {
-    void* running_thread_stack_pointer;
-    SnapshotThreadInfo::StreamEncoder* encoder;
-    ProcessThreadStackCallback* stack_dumper;
-    Status thread_capture_status;
-  } ctx;
-  ctx.running_thread_stack_pointer = running_thread_stack_pointer;
-  ctx.encoder = &encoder;
-  ctx.stack_dumper = &stack_dumper;
-  ctx.thread_capture_status = OkStatus();
-
-  ThreadCallback thread_capture_cb(
-      [&ctx](TaskHandle_t thread, eTaskState thread_state) -> bool {
-        Thread::StreamEncoder thread_encoder = ctx.encoder->GetThreadsEncoder();
-        ctx.thread_capture_status.Update(
-            SnapshotThread(thread,
-                           thread_state,
-                           ctx.running_thread_stack_pointer,
-                           thread_encoder,
-                           *ctx.stack_dumper));
-        return true;  // Iterate through all threads.
-      });
-  if (const Status status = ForEachThread(thread_capture_cb);
-      !status.ok() && !status.IsFailedPrecondition()) {
-    PW_LOG_ERROR("Failed to iterate threads during snapshot capture: %d",
-                 status.code());
-  }
-  return ctx.thread_capture_status;
-}
-
-Status SnapshotThread(TaskHandle_t thread,
-                      eTaskState thread_state,
-                      void* running_thread_stack_pointer,
-                      Thread::StreamEncoder& encoder,
-                      ProcessThreadStackCallback& thread_stack_callback) {
-  const tskTCB& tcb = *reinterpret_cast<tskTCB*>(thread);
-
-  PW_LOG_DEBUG("Capturing thread info for %s", tcb.pcTaskName);
-  encoder.WriteName(std::as_bytes(std::span(std::string_view(tcb.pcTaskName))));
-
-  CaptureThreadState(thread_state, encoder);
-
-  // TODO(pwbug/422): Update this once we add support for ascending stacks.
-  static_assert(portSTACK_GROWTH < 0, "Ascending stacks are not yet supported");
-
-  // If the thread is active, the stack pointer in the TCB is stale.
-  const uintptr_t stack_pointer = reinterpret_cast<uintptr_t>(
-      thread_state == eRunning ? running_thread_stack_pointer
-                               : tcb.pxTopOfStack);
-  const uintptr_t stack_low_addr = reinterpret_cast<uintptr_t>(tcb.pxStack);
-
-#if ((portSTACK_GROWTH > 0) || (configRECORD_STACK_HIGH_ADDRESS == 1))
-  const uintptr_t stack_high_addr =
-      reinterpret_cast<uintptr_t>(tcb.pxEndOfStack);
-  const StackContext thread_ctx = {
-    .thread_name = tcb.pcTaskName,
-    .stack_low_addr = stack_low_addr,
-    .stack_high_addr = stack_high_addr,
-    .stack_pointer = stack_pointer,
-#if ((configUSE_TRACE_FACILITY == 1) || \
-     (INCLUDE_uxTaskGetStackHighWaterMark == 1))
-#if (portSTACK_GROWTH > 0)
-    .stack_pointer_est_peak =
-        stack_high_addr -
-        (sizeof(StackType_t) *
-         prvTaskCheckFreeStackSpace(
-             reinterpret_cast<const uint8_t*>(stack_high_addr))),
-#else
-    .stack_pointer_est_peak =
-        stack_low_addr +
-        (sizeof(StackType_t) *
-         prvTaskCheckFreeStackSpace(
-             reinterpret_cast<const uint8_t*>(stack_low_addr))),
-#endif  // (portSTACK_GROWTH > 0)
-#else
-    .stack_pointer_est_peak = std::nullopt,
-#endif  // ((configUSE_TRACE_FACILITY == 1) ||
-        // (INCLUDE_uxTaskGetStackHighWaterMark == 1))
-  };
-  return SnapshotStack(thread_ctx, encoder, thread_stack_callback);
-#else
-  encoder.WriteStackEndPointer(stack_low_addr);
-  encoder.WriteStackPointer(stack_pointer);
-  return encoder.status();
-#endif  // ((portSTACK_GROWTH > 0) || (configRECORD_STACK_HIGH_ADDRESS == 1))
-}
-
-}  // namespace pw::thread::freertos
diff --git a/pw_thread_freertos/thread.cc b/pw_thread_freertos/thread.cc
index 865b2a3..b77df78 100644
--- a/pw_thread_freertos/thread.cc
+++ b/pw_thread_freertos/thread.cc
@@ -14,7 +14,7 @@
 #include "pw_thread/thread.h"
 
 #include "FreeRTOS.h"
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_preprocessor/compiler.h"
 #include "pw_thread/id.h"
 #include "pw_thread_freertos/config.h"
@@ -31,11 +31,9 @@
 #endif  // PW_THREAD_JOINING_ENABLED
 }  // namespace
 
-void Context::ThreadEntryPoint(void* void_context_ptr) {
+void Context::RunThread(void* void_context_ptr) {
   Context& context = *static_cast<Context*>(void_context_ptr);
-
-  // Invoke the user's thread function. This may never return.
-  context.user_thread_entry_function_(context.user_thread_entry_arg_);
+  context.entry_(context.arg_);
 
   // Use a task only critical section to guard against join() and detach().
   vTaskSuspendAll();
@@ -46,11 +44,9 @@
     context.set_task_handle(nullptr);
 
 #if PW_THREAD_JOINING_ENABLED
-    // If the thread handle was detached before the thread finished execution,
-    // i.e. got here, then we are responsible for cleaning up the join event
-    // group.
-    vEventGroupDelete(
-        reinterpret_cast<EventGroupHandle_t>(&context.join_event_group()));
+    // Just in case someone abused our API, ensure their use of the event group
+    // is properly handled by the kernel regardless.
+    vEventGroupDelete(&context.join_event_group());
 #endif  // PW_THREAD_JOINING_ENABLED
 
 #if PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
@@ -73,9 +69,7 @@
   xTaskResumeAll();
 
 #if PW_THREAD_JOINING_ENABLED
-  xEventGroupSetBits(
-      reinterpret_cast<EventGroupHandle_t>(&context.join_event_group()),
-      kThreadDoneBit);
+  xEventGroupSetBits(&context.join_event_group(), kThreadDoneBit);
 #endif  // PW_THREAD_JOINING_ENABLED
 
   while (true) {
@@ -100,8 +94,7 @@
 #if PW_THREAD_JOINING_ENABLED
   // Just in case someone abused our API, ensure their use of the event group is
   // properly handled by the kernel regardless.
-  vEventGroupDelete(
-      reinterpret_cast<EventGroupHandle_t>(&context.join_event_group()));
+  vEventGroupDelete(&context.join_event_group());
 #endif  // PW_THREAD_JOINING_ENABLED
 
 #if PW_THREAD_FREERTOS_CONFIG_DYNAMIC_ALLOCATION_ENABLED
@@ -140,7 +133,7 @@
     // invoke the task with its arg.
     native_type_->set_thread_routine(entry, arg);
     const TaskHandle_t task_handle =
-        xTaskCreateStatic(Context::ThreadEntryPoint,
+        xTaskCreateStatic(Context::RunThread,
                           options.name(),
                           options.static_context()->stack().size(),
                           native_type_,
@@ -171,7 +164,7 @@
     // invoke the task with its arg.
     native_type_->set_thread_routine(entry, arg);
     TaskHandle_t task_handle;
-    const BaseType_t result = xTaskCreate(Context::ThreadEntryPoint,
+    const BaseType_t result = xTaskCreate(Context::RunThread,
                                           options.name(),
                                           options.stack_size_words(),
                                           native_type_,
@@ -188,35 +181,28 @@
 void Thread::detach() {
   PW_CHECK(joinable());
 
-#if (INCLUDE_vTaskSuspend == 1) && (INCLUDE_xTaskGetSchedulerState == 1)
+#if INCLUDE_vTaskSuspend == 1
   // No need to suspend extra tasks.
-  if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) {
-    vTaskSuspend(native_type_->task_handle());
-  }
+  vTaskSuspend(native_type_->task_handle());
 #else
-  // Safe to suspend all tasks while scheduler is not running.
   vTaskSuspendAll();
 #endif  // INCLUDE_vTaskSuspend == 1
   native_type_->set_detached();
   const bool thread_done = native_type_->thread_done();
-#if (INCLUDE_vTaskSuspend == 1) && (INCLUDE_xTaskGetSchedulerState == 1)
-  // No need to suspend extra tasks, but only safe to call once scheduler is
-  // running.
-  if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) {
-    vTaskResume(native_type_->task_handle());
-  }
+#if INCLUDE_vTaskSuspend == 1
+  // No need to suspend extra tasks.
+  vTaskResume(native_type_->task_handle());
 #else
-  // Safe to resume all tasks while scheduler is not running.
-  xTaskResumeAll();
+  vTaskResumeAll();
 #endif  // INCLUDE_vTaskSuspend == 1
 
   if (thread_done) {
-    // The task finished (hit end of Context::ThreadEntryPoint) before we
-    // invoked detach, clean up the thread.
+    // The task finished (hit end of Context::RunThread) before we invoked
+    // detach, clean up the thread.
     Context::TerminateThread(*native_type_);
   } else {
     // We're detaching before the task finished, defer cleanup to the task at
-    // the end of Context::ThreadEntryPoint.
+    // the end of Context::RunThread.
   }
 
   // Update to no longer represent a thread of execution.
@@ -229,8 +215,7 @@
   PW_CHECK(this_thread::get_id() != get_id());
 
   // Wait indefinitely until kThreadDoneBit is set.
-  while (xEventGroupWaitBits(reinterpret_cast<EventGroupHandle_t>(
-                                 &native_type_->join_event_group()),
+  while (xEventGroupWaitBits(&native_type_->join_event_group(),
                              kThreadDoneBit,
                              pdTRUE,   // Clear the bits.
                              pdFALSE,  // Any bits is fine, N/A.
diff --git a/pw_thread_freertos/util.cc b/pw_thread_freertos/util.cc
deleted file mode 100644
index 1aeede5..0000000
--- a/pw_thread_freertos/util.cc
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_thread_freertos/util.h"
-
-#include "FreeRTOS.h"
-#include "list.h"
-#include "pw_function/function.h"
-#include "pw_log/log.h"
-#include "pw_status/status.h"
-#include "pw_status/try.h"
-#include "task.h"
-
-// The externed symbols below are all internal FreeRTOS kernel variables from
-// FreeRTOS/Source/tasks.c needed in order to iterate through all of the threads
-// from interrupts which the native APIs do not permit.
-
-extern "C" PRIVILEGED_DATA volatile BaseType_t xSchedulerRunning;
-
-extern "C" PRIVILEGED_DATA TaskHandle_t volatile pxCurrentTCB;
-
-// Prioritised ready tasks.
-extern "C" PRIVILEGED_DATA List_t pxReadyTasksLists[configMAX_PRIORITIES];
-
-// Points to the delayed task list currently being used.
-extern "C" PRIVILEGED_DATA List_t* volatile pxDelayedTaskList;
-
-// Points to the delayed task list currently being used to hold tasks that have
-// overflowed the current tick count.
-extern "C" PRIVILEGED_DATA List_t* volatile pxOverflowDelayedTaskList;
-
-#if INCLUDE_vTaskDelete == 1
-// Tasks that have been deleted - but their memory not yet freed.
-extern "C" PRIVILEGED_DATA List_t xTasksWaitingTermination;
-#endif  // INCLUDE_vTaskDelete == 1
-
-#if INCLUDE_vTaskSuspend == 1
-// Tasks that are currently suspended.
-extern "C" PRIVILEGED_DATA List_t xSuspendedTaskList;
-#endif  // INCLUDE_vTaskSuspend == 1
-
-namespace pw::thread::freertos {
-namespace {
-
-Status ForEachThreadInList(List_t* list,
-                           const eTaskState default_list_state,
-                           const ThreadCallback& cb) {
-  if (listCURRENT_LIST_LENGTH(list) == 0) {
-    return OkStatus();
-  }
-
-  Status status = OkStatus();
-  // Note that these are pointers to the thread control blocks, however the
-  // list macros from FreeRTOS do not cast the types and ergo we use void *.
-  void* current_thread;
-  void* first_thread_in_list;
-  listGET_OWNER_OF_NEXT_ENTRY(first_thread_in_list, list);
-  do {
-    listGET_OWNER_OF_NEXT_ENTRY(current_thread, list);
-    // We must finish the list iteration to restore the list state, but
-    // we want to stop invoking callbacks upon the first failure.
-    if (status.ok()) {
-      // Note that the lists do not contain the running state, so instead
-      // check for each thread whether it is currently running.
-      const TaskHandle_t current_thread_handle =
-          reinterpret_cast<TaskHandle_t>(current_thread);
-      if (!cb(current_thread_handle,
-              current_thread_handle == pxCurrentTCB ? eRunning
-                                                    : default_list_state)) {
-        status = Status::Aborted();
-      }
-    }
-  } while (current_thread != first_thread_in_list);
-  return status;
-}
-
-}  // namespace
-
-Status ForEachThread(const ThreadCallback& cb) {
-  if (xSchedulerRunning == pdFALSE) {
-    return Status::FailedPrecondition();
-  }
-
-  for (size_t i = 0; i < configMAX_PRIORITIES; ++i) {
-    PW_TRY(ForEachThreadInList(&pxReadyTasksLists[i], eReady, cb));
-  }
-  PW_TRY(ForEachThreadInList(pxDelayedTaskList, eBlocked, cb));
-  PW_TRY(ForEachThreadInList(pxOverflowDelayedTaskList, eBlocked, cb));
-#if INCLUDE_vTaskDelete == 1
-  PW_TRY(ForEachThreadInList(&xTasksWaitingTermination, eDeleted, cb));
-#endif  // INCLUDE_vTaskDelete == 1
-#if INCLUDE_vTaskSuspend == 1
-  PW_TRY(ForEachThreadInList(&xSuspendedTaskList, eSuspended, cb));
-#endif  // INCLUDE_vTaskSuspend == 1
-  return OkStatus();
-}
-
-}  // namespace pw::thread::freertos
diff --git a/pw_thread_stl/BUILD b/pw_thread_stl/BUILD
new file mode 100644
index 0000000..e7624a6
--- /dev/null
+++ b/pw_thread_stl/BUILD
@@ -0,0 +1,131 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "id_headers",
+    hdrs = [
+        "public/pw_thread_stl/id_inline.h",
+        "public/pw_thread_stl/id_native.h",
+        "public_overrides/pw_thread_backend/id_inline.h",
+        "public_overrides/pw_thread_backend/id_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+)
+
+pw_cc_library(
+    name = "id",
+    deps = [
+        ":id_headers",
+        "//pw_thread:id_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "sleep_headers",
+    hdrs = [
+        "public/pw_thread_stl/sleep_inline.h",
+        "public_overrides/pw_thread_backend/sleep_inline.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        "//pw_chrono:system_clock",
+    ],
+)
+
+pw_cc_library(
+    name = "sleep",
+    deps = [
+        ":sleep_headers",
+        "//pw_chrono:system_clock",
+        "//pw_thread:sleep_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "thread_headers",
+    hdrs = [
+        "public/pw_thread_stl/options.h",
+        "public/pw_thread_stl/thread_inline.h",
+        "public/pw_thread_stl/thread_native.h",
+        "public_overrides/pw_thread_backend/thread_inline.h",
+        "public_overrides/pw_thread_backend/thread_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+)
+
+pw_cc_library(
+    name = "thread",
+    deps = [
+        ":thread_headers",
+        "//pw_thread:thread_facade",
+    ],
+)
+
+pw_cc_library(
+    name = "test_threads",
+    deps = [
+        "//pw_thread:thread_facade",
+        "//pw_thread:test_threads_header",
+    ],
+    srcs = [
+        "test_threads.cc",
+    ]
+)
+
+pw_cc_test(
+    name = "thread_backend_test",
+    deps = [
+        "//pw_thread:thread_facade_test",
+        ":test_threads",
+    ]
+)
+
+pw_cc_library(
+    name = "yield_headers",
+    hdrs = [
+        "public/pw_thread_stl/yield_inline.h",
+        "public_overrides/pw_thread_backend/yield_inline.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+)
+
+pw_cc_library(
+    name = "yield",
+    deps = [
+        ":yield_headers",
+        "//pw_thread:yield_facade",
+    ],
+)
diff --git a/pw_thread_stl/BUILD.bazel b/pw_thread_stl/BUILD.bazel
deleted file mode 100644
index 91837a0..0000000
--- a/pw_thread_stl/BUILD.bazel
+++ /dev/null
@@ -1,145 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-load(
-    "//pw_build:selects.bzl",
-    "TARGET_COMPATIBLE_WITH_HOST_SELECT",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "id_headers",
-    hdrs = [
-        "public/pw_thread_stl/id_inline.h",
-        "public/pw_thread_stl/id_native.h",
-        "public_overrides/pw_thread_backend/id_inline.h",
-        "public_overrides/pw_thread_backend/id_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-)
-
-pw_cc_library(
-    name = "id",
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-    deps = [
-        ":id_headers",
-        "//pw_thread:id_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "sleep_headers",
-    hdrs = [
-        "public/pw_thread_stl/sleep_inline.h",
-        "public_overrides/pw_thread_backend/sleep_inline.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-    deps = [
-        "//pw_chrono:system_clock",
-    ],
-)
-
-pw_cc_library(
-    name = "sleep",
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-    deps = [
-        ":sleep_headers",
-        "//pw_chrono:system_clock",
-        "//pw_thread:sleep_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "thread_headers",
-    hdrs = [
-        "public/pw_thread_stl/options.h",
-        "public/pw_thread_stl/thread_inline.h",
-        "public/pw_thread_stl/thread_native.h",
-        "public_overrides/pw_thread_backend/thread_inline.h",
-        "public_overrides/pw_thread_backend/thread_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-)
-
-pw_cc_library(
-    name = "thread",
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-    deps = [
-        ":thread_headers",
-        "//pw_thread:thread_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "test_threads",
-    srcs = [
-        "test_threads.cc",
-    ],
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-    deps = [
-        "//pw_thread:test_threads_header",
-        "//pw_thread:thread_facade",
-    ],
-)
-
-pw_cc_test(
-    name = "thread_backend_test",
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-    deps = [
-        ":test_threads",
-        "//pw_thread:thread_facade_test",
-    ],
-)
-
-pw_cc_library(
-    name = "yield_headers",
-    hdrs = [
-        "public/pw_thread_stl/yield_inline.h",
-        "public_overrides/pw_thread_backend/yield_inline.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-)
-
-pw_cc_library(
-    name = "yield",
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-    deps = [
-        ":yield_headers",
-        "//pw_thread:yield_facade",
-    ],
-)
diff --git a/pw_thread_stl/BUILD.gn b/pw_thread_stl/BUILD.gn
index 0e4e6b6..37ccf9b 100644
--- a/pw_thread_stl/BUILD.gn
+++ b/pw_thread_stl/BUILD.gn
@@ -14,7 +14,6 @@
 
 import("//build_overrides/pigweed.gni")
 
-import("$dir_pw_build/error.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_chrono/backend.gni")
 import("$dir_pw_docgen/docs.gni")
@@ -64,17 +63,6 @@
   deps = [ "$dir_pw_thread:thread.facade" ]
 }
 
-pw_build_assert("check_system_clock_backend") {
-  condition =
-      pw_thread_SLEEP_BACKEND != "$dir_pw_thread_stl:sleep" ||
-      pw_chrono_SYSTEM_CLOCK_BACKEND == "" ||
-      pw_chrono_SYSTEM_CLOCK_BACKEND == "$dir_pw_chrono_stl:system_clock"
-  message = "The STL pw::this_thread::sleep_{for,until} backend only works " +
-            "with the STL pw::chrono::SystemClock backend " +
-            "(pw_chrono_SYSTEM_CLOCK_BACKEND = " +
-            "\"$dir_pw_chrono_stl:system_clock\")"
-}
-
 # This target provides the backend for pw::this_thread::sleep_{for,until}.
 pw_source_set("sleep") {
   public_configs = [
@@ -86,10 +74,16 @@
     "public_overrides/pw_thread_backend/sleep_inline.h",
   ]
   deps = [
-    ":check_system_clock_backend",
     "$dir_pw_chrono:system_clock",
     "$dir_pw_thread:sleep.facade",
   ]
+  assert(
+      pw_chrono_SYSTEM_CLOCK_BACKEND == "" ||
+          pw_chrono_SYSTEM_CLOCK_BACKEND == "$dir_pw_chrono_stl:system_clock",
+      "The STL pw::this_thread::sleep_{for,until} backend only works with " +
+          "the STL pw::chrono::SystemClock backend " +
+          "(pw_chrono_SYSTEM_CLOCK_BACKEND = " +
+          "\"$dir_pw_chrono_stl:system_clock\")")
 }
 
 # This target provides the backend for pw::this_thread::yield.
@@ -116,8 +110,7 @@
 }
 
 pw_test("thread_backend_test") {
-  enable_if = pw_thread_THREAD_BACKEND == "$dir_pw_thread_stl:thread" &&
-              pw_thread_SLEEP_BACKEND != ""
+  enable_if = pw_thread_THREAD_BACKEND == "$dir_pw_thread_stl:thread"
   deps = [
     ":test_threads",
     "$dir_pw_thread:thread_facade_test",
diff --git a/pw_thread_stl/CMakeLists.txt b/pw_thread_stl/CMakeLists.txt
deleted file mode 100644
index 86f916e..0000000
--- a/pw_thread_stl/CMakeLists.txt
+++ /dev/null
@@ -1,93 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-
-# This target provides the backend for pw::thread::Id & pw::this_thread::get_id.
-pw_add_module_library(pw_thread_stl.id
-  IMPLEMENTS_FACADES
-    pw_thread.id
-  HEADERS
-    public/pw_thread_stl/id_inline.h
-    public/pw_thread_stl/id_native.h
-    public_overrides/pw_thread_backend/id_inline.h
-    public_overrides/pw_thread_backend/id_native.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides
-)
-
-# This target provides the backend for pw::thread::Thread with joining
-# joining capability.
-pw_add_module_library(pw_thread_stl.thread
-  IMPLEMENTS_FACADES
-    pw_thread.thread
-  HEADERS
-    public/pw_thread_stl/options.h
-    public/pw_thread_stl/thread_inline.h
-    public/pw_thread_stl/thread_native.h
-    public_overrides/pw_thread_backend/thread_inline.h
-    public_overrides/pw_thread_backend/thread_native.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides
-)
-
-
-# This target provides the backend for pw::this_thread::sleep_{for,until}.
-pw_add_module_library(pw_thread_stl.sleep
-  IMPLEMENTS_FACADES
-    pw_thread.sleep
-  HEADERS
-    public/pw_thread_stl/sleep_inline.h
-    public_overrides/pw_thread_backend/sleep_inline.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides
-  PUBLIC_DEPS
-    pw_chrono.system_clock
-)
-
-# This target provides the backend for pw::this_thread::yield.
-pw_add_module_library(pw_thread_stl.yield
-  IMPLEMENTS_FACADES
-    pw_thread.yield
-  HEADERS
-    public/pw_thread_stl/yield_inline.h
-    public_overrides/pw_thread_backend/yield_inline.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides
-)
-
-pw_add_module_library(pw_thread_stl.test_threads
-  PUBLIC_DEPS
-    pw_thread.test_threads
-  SOURCES
-    test_threads.cc
-  PRIVATE_DEPS
-    pw_thread.thread
-)
-
-if(("${pw_thread.thread_BACKEND}" STREQUAL "pw_thread_stl.thread") AND
-   (NOT "${pw_thread.sleep_BACKEND}" STREQUAL "pw_thread.sleep.NO_BACKEND_SET"))
-  pw_add_test(pw_thread_stl.thread_backend_test
-    DEPS
-      pw_thread_stl.test_threads
-      pw_thread.thread_facade_test
-    GROUPS
-      modules
-      pw_thread_stl
-  )
-endif()
diff --git a/pw_thread_stl/OWNERS b/pw_thread_stl/OWNERS
deleted file mode 100644
index e9a6c58..0000000
--- a/pw_thread_stl/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-ewout@google.com
-hepler@google.com
diff --git a/pw_thread_stl/public/pw_thread_stl/sleep_inline.h b/pw_thread_stl/public/pw_thread_stl/sleep_inline.h
index d107871..2b0a914 100644
--- a/pw_thread_stl/public/pw_thread_stl/sleep_inline.h
+++ b/pw_thread_stl/public/pw_thread_stl/sleep_inline.h
@@ -20,31 +20,23 @@
 
 namespace pw::this_thread {
 
-inline void sleep_for(chrono::SystemClock::duration sleep_duration) {
-  sleep_duration =
-      std::max(sleep_duration, chrono::SystemClock::duration::zero());
+inline void sleep_for(chrono::SystemClock::duration for_at_least) {
+  for_at_least = std::max(for_at_least, chrono::SystemClock::duration::zero());
   // Although many implementations do yield with sleep_for(0), it is not
   // required, ergo we explicitly add handling.
-  if (sleep_duration == chrono::SystemClock::duration::zero()) {
+  if (for_at_least == chrono::SystemClock::duration::zero()) {
     return std::this_thread::yield();
   }
-#if defined(_WIN32)
-  // For some reason MinGW's implementation for sleep_for doesn't work
-  // correctly, however sleep_until does.
-  return std::this_thread::sleep_until(
-      chrono::SystemClock::TimePointAfterAtLeast(sleep_duration));
-#else
-  return std::this_thread::sleep_for(sleep_duration);
-#endif  // defined(_WIN32)
+  return std::this_thread::sleep_for(for_at_least);
 }
 
-inline void sleep_until(chrono::SystemClock::time_point wakeup_time) {
+inline void sleep_until(chrono::SystemClock::time_point until_at_least) {
   // Although many implementations do yield with deadlines in the past until
   // the current time, it is not required, ergo we explicitly add handling.
-  if (chrono::SystemClock::now() >= wakeup_time) {
+  if (chrono::SystemClock::now() >= until_at_least) {
     return std::this_thread::yield();
   }
-  return std::this_thread::sleep_until(wakeup_time);
+  return std::this_thread::sleep_until(until_at_least);
 }
 
 }  // namespace pw::this_thread
diff --git a/pw_thread_threadx/BUILD b/pw_thread_threadx/BUILD
new file mode 100644
index 0000000..427af06
--- /dev/null
+++ b/pw_thread_threadx/BUILD
@@ -0,0 +1,160 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "id_headers",
+    hdrs = [
+        "public/pw_thread_threadx/id_inline.h",
+        "public/pw_thread_threadx/id_native.h",
+        "public_overrides/pw_thread_backend/id_inline.h",
+        "public_overrides/pw_thread_backend/id_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+)
+
+pw_cc_library(
+    name = "id",
+    deps = [
+        ":id_headers",
+        "//pw_thread:id_facade",
+    ],
+    # TODO(pwbug/317): This should depend on ThreadX but our third parties
+		# currently do not have Bazel support.
+)
+
+# This target provides the ThreadX specific headers needs for thread creation.
+pw_cc_library(
+    name = "thread_headers",
+    hdrs = [
+        "public/pw_thread_threadx/context.h",
+        "public/pw_thread_threadx/options.h",
+        "public/pw_thread_threadx/config.h",
+        "public/pw_thread_threadx/thread_inline.h",
+        "public/pw_thread_threadx/thread_native.h",
+        "public_overrides/pw_thread_backend/thread_inline.h",
+        "public_overrides/pw_thread_backend/thread_native.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        "//pw_assert",
+        ":id",
+        "//pw_thread:thread_headers",
+    ],
+    # TODO(pwbug/317): This should depend on ThreadX but our third parties
+    # currently do not have Bazel support.
+)
+
+pw_cc_library(
+    name = "thread",
+    srcs = [
+        "thread.cc",
+    ],
+    deps = [
+        "//pw_assert",
+        ":id",
+        ":thread_headers",
+    ],
+    # TODO(pwbug/317): This should depend on ThreadX but our third parties
+    # currently do not have Bazel support.
+)
+
+pw_cc_library(
+    name = "test_threads",
+    deps = [
+        "//pw_thread:thread_facade",
+        "//pw_thread:test_threads_header",
+        "//pw_chrono:system_clock",
+        "//pw_thread:sleep",
+    ],
+    srcs = [
+        "test_threads.cc",
+    ]
+)
+
+pw_cc_test(
+    name = "thread_backend_test",
+    deps = [
+        "//pw_thread:thread_facade_test",
+        ":test_threads",
+    ]
+)
+
+pw_cc_library(
+    name = "sleep_headers",
+    hdrs = [
+        "public/pw_thread_threadx/sleep_inline.h",
+        "public_overrides/pw_thread_backend/sleep_inline.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        "//pw_chrono:system_clock",
+    ],
+)
+
+pw_cc_library(
+    name = "sleep",
+    srcs = [
+        "sleep.cc",
+    ],
+    deps = [
+        ":sleep_headers",
+        "//pw_chrono_threadx:system_clock_headers",
+        "//pw_assert",
+        "//pw_chrono:system_clock",
+        "//pw_thread:sleep_facade",
+    ],
+    # TODO(pwbug/317): This should depend on ThreadX but our third parties
+		# currently do not have Bazel support.
+)
+
+pw_cc_library(
+    name = "yield_headers",
+    hdrs = [
+        "public/pw_thread_threadx/yield_inline.h",
+        "public_overrides/pw_thread_backend/yield_inline.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    # TODO(pwbug/317): This should depend on ThreadX but our third parties
+		# currently do not have Bazel support.
+)
+
+pw_cc_library(
+    name = "yield",
+    deps = [
+        ":yield_headers",
+        "//pw_thread:yield_facade",
+    ],
+)
diff --git a/pw_thread_threadx/BUILD.bazel b/pw_thread_threadx/BUILD.bazel
deleted file mode 100644
index fc3718d..0000000
--- a/pw_thread_threadx/BUILD.bazel
+++ /dev/null
@@ -1,198 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "id_headers",
-    hdrs = [
-        "public/pw_thread_threadx/id_inline.h",
-        "public/pw_thread_threadx/id_native.h",
-        "public_overrides/pw_thread_backend/id_inline.h",
-        "public_overrides/pw_thread_backend/id_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-)
-
-pw_cc_library(
-    name = "id",
-    deps = [
-        ":id_headers",
-        "//pw_thread:id_facade",
-    ],
-    # TODO(pwbug/317): This should depend on ThreadX but our third parties
-    # currently do not have Bazel support.
-)
-
-# This target provides the ThreadX specific headers needs for thread creation.
-pw_cc_library(
-    name = "thread_headers",
-    hdrs = [
-        "public/pw_thread_threadx/config.h",
-        "public/pw_thread_threadx/context.h",
-        "public/pw_thread_threadx/options.h",
-        "public/pw_thread_threadx/thread_inline.h",
-        "public/pw_thread_threadx/thread_native.h",
-        "public_overrides/pw_thread_backend/thread_inline.h",
-        "public_overrides/pw_thread_backend/thread_native.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    deps = [
-        ":id",
-        "//pw_assert",
-        "//pw_string",
-        "//pw_thread:thread_headers",
-    ],
-    # TODO(pwbug/317): This should depend on ThreadX but our third parties
-    # currently do not have Bazel support.
-)
-
-pw_cc_library(
-    name = "thread",
-    srcs = [
-        "thread.cc",
-    ],
-    deps = [
-        ":id",
-        ":thread_headers",
-        "//pw_assert",
-    ],
-    # TODO(pwbug/317): This should depend on ThreadX but our third parties
-    # currently do not have Bazel support.
-)
-
-pw_cc_library(
-    name = "test_threads",
-    srcs = [
-        "test_threads.cc",
-    ],
-    deps = [
-        "//pw_chrono:system_clock",
-        "//pw_thread:sleep",
-        "//pw_thread:test_threads_header",
-        "//pw_thread:thread_facade",
-    ],
-)
-
-pw_cc_test(
-    name = "thread_backend_test",
-    deps = [
-        ":test_threads",
-        "//pw_thread:thread_facade_test",
-    ],
-)
-
-pw_cc_library(
-    name = "sleep_headers",
-    hdrs = [
-        "public/pw_thread_threadx/sleep_inline.h",
-        "public_overrides/pw_thread_backend/sleep_inline.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    deps = [
-        "//pw_chrono:system_clock",
-    ],
-)
-
-pw_cc_library(
-    name = "sleep",
-    srcs = [
-        "sleep.cc",
-    ],
-    deps = [
-        ":sleep_headers",
-        "//pw_assert",
-        "//pw_chrono:system_clock",
-        "//pw_chrono_threadx:system_clock_headers",
-        "//pw_thread:sleep_facade",
-    ],
-    # TODO(pwbug/317): This should depend on ThreadX but our third parties
-    # currently do not have Bazel support.
-)
-
-pw_cc_library(
-    name = "yield_headers",
-    hdrs = [
-        "public/pw_thread_threadx/yield_inline.h",
-        "public_overrides/pw_thread_backend/yield_inline.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    # TODO(pwbug/317): This should depend on ThreadX but our third parties
-    # currently do not have Bazel support.
-)
-
-pw_cc_library(
-    name = "yield",
-    deps = [
-        ":yield_headers",
-        "//pw_thread:yield_facade",
-    ],
-)
-
-pw_cc_library(
-    name = "util",
-    srcs = [
-        "util.cc",
-    ],
-    hdrs = [
-        "public/pw_thread_threadx/util.h",
-    ],
-    deps = [
-        "//pw_function",
-        "//pw_status",
-    ],
-    # TODO(pwbug/317): This should depend on ThreadX but our third parties
-    # currently do not have Bazel support.
-)
-
-pw_cc_library(
-    name = "snapshot",
-    srcs = [
-        "snapshot.cc",
-    ],
-    hdrs = [
-        "public/pw_thread_threadx/snapshot.h",
-    ],
-    deps = [
-        ":util",
-        "//pw_bytes",
-        "//pw_function",
-        "//pw_log",
-        "//pw_protobuf",
-        "//pw_status",
-        "//pw_thread:protos",
-    ],
-    # TODO(pwbug/317): This should depend on ThreadX but our third parties
-    # currently do not have Bazel support.
-)
diff --git a/pw_thread_threadx/BUILD.gn b/pw_thread_threadx/BUILD.gn
index 2917147..f7a090b 100644
--- a/pw_thread_threadx/BUILD.gn
+++ b/pw_thread_threadx/BUILD.gn
@@ -14,7 +14,6 @@
 
 import("//build_overrides/pigweed.gni")
 
-import("$dir_pw_build/error.gni")
 import("$dir_pw_build/module_config.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_chrono/backend.gni")
@@ -69,14 +68,6 @@
 }
 
 if (pw_chrono_SYSTEM_CLOCK_BACKEND != "" && pw_thread_SLEEP_BACKEND != "") {
-  pw_build_assert("check_system_clock_backend") {
-    condition =
-        pw_thread_OVERRIDE_SYSTEM_CLOCK_BACKEND_CHECK ||
-        pw_chrono_SYSTEM_CLOCK_BACKEND == "$dir_pw_chrono_threadx:system_clock"
-    message = "The ThreadX pw::this_thread::sleep_{for,until} backend only " +
-              "works with the ThreadX pw::chrono::SystemClock backend."
-  }
-
   # This target provides the backend for pw::this_thread::sleep_{for,until}.
   pw_source_set("sleep") {
     public_configs = [
@@ -90,12 +81,16 @@
     public_deps = [ "$dir_pw_chrono:system_clock" ]
     sources = [ "sleep.cc" ]
     deps = [
-      ":check_system_clock_backend",
       "$dir_pw_assert",
       "$dir_pw_chrono_threadx:system_clock",
       "$dir_pw_third_party/threadx",
       "$dir_pw_thread:id",
     ]
+    assert(
+        pw_thread_OVERRIDE_SYSTEM_CLOCK_BACKEND_CHECK ||
+            pw_chrono_SYSTEM_CLOCK_BACKEND ==
+                "$dir_pw_chrono_threadx:system_clock",
+        "The ThreadX pw::this_thread::sleep_{for,until} backend only works with " + "the ThreadX pw::chrono::SystemClock backend.")
   }
 }
 
@@ -109,7 +104,6 @@
   public_deps = [
     ":config",
     "$dir_pw_assert",
-    "$dir_pw_string",
     "$dir_pw_third_party/threadx",
     "$dir_pw_thread:id",
     "$dir_pw_thread:thread.facade",
@@ -144,38 +138,6 @@
   deps = [ "$dir_pw_thread:yield.facade" ]
 }
 
-pw_source_set("util") {
-  public_configs = [ ":public_include_path" ]
-  public_deps = [
-    "$dir_pw_third_party/threadx",
-    dir_pw_function,
-    dir_pw_status,
-  ]
-  public = [ "public/pw_thread_threadx/util.h" ]
-  sources = [ "util.cc" ]
-}
-
-pw_source_set("snapshot") {
-  public_configs = [ ":public_include_path" ]
-  public_deps = [
-    ":config",
-    "$dir_pw_third_party/threadx",
-    "$dir_pw_thread:protos.pwpb",
-    "$dir_pw_thread:snapshot",
-    dir_pw_bytes,
-    dir_pw_function,
-    dir_pw_log,
-    dir_pw_protobuf,
-    dir_pw_status,
-  ]
-  public = [ "public/pw_thread_threadx/snapshot.h" ]
-  sources = [ "snapshot.cc" ]
-  deps = [
-    ":util",
-    dir_pw_log,
-  ]
-}
-
 pw_test_group("tests") {
   tests = [ ":thread_backend_test" ]
 }
diff --git a/pw_thread_threadx/OWNERS b/pw_thread_threadx/OWNERS
deleted file mode 100644
index e9a6c58..0000000
--- a/pw_thread_threadx/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-ewout@google.com
-hepler@google.com
diff --git a/pw_thread_threadx/docs.rst b/pw_thread_threadx/docs.rst
index 4b73a40..527b487 100644
--- a/pw_thread_threadx/docs.rst
+++ b/pw_thread_threadx/docs.rst
@@ -8,44 +8,6 @@
 .. Warning::
   This module is still under construction, the API is not yet stable.
 
------------------------
-Thread Creation Backend
------------------------
-A backend for ``pw::thread::Thread`` is offered using ``tx_thread_create``.
-Optional joining support is enabled via an ``TX_EVENT_FLAGS_GROUP`` in each
-thread's context.
-
-This backend permits users to start threads where contexts must be explicitly
-allocated and passed in as an option. As a quick example, a detached thread can
-be created as follows:
-
-.. code-block:: cpp
-
-  #include "pw_thread/detached_thread.h"
-  #include "pw_thread_threadx/config.h"
-  #include "pw_thread_threadx/context.h"
-  #include "pw_thread_threadx/options.h"
-  #include "tx_api.h"
-
-  constexpr UINT kFooPriority =
-      pw::thread::threadx::config::kDefaultPriority;
-  constexpr ULONG kFooTimeSliceInterval =
-      pw::thread::threadx::config::kDefaultTimeSliceInterval;
-  constexpr size_t kFooStackSizeWords =
-      pw::thread::threadx::config::kDefaultStackSizeWords;
-
-  pw::thread::threadx::ContextWithStack<kFooStackSizeWords>
-      example_thread_context;
-  void StartExampleThread() {
-    pw::thread::DetachedThread(
-        pw::thread::threadx::Options()
-            .set_name("example_thread")
-            .set_priority(kFooPriority)
-            .set_time_slice_interval(kFooTimeSliceInterval)
-            .set_context(example_thread_context),
-        example_thread_function);
-  }
-
 .. list-table::
 
   * - :ref:`module-pw_thread` Facade
@@ -63,192 +25,3 @@
   * - ``pw_thread:thread``
     - ``pw_thread_threadx:thread``
     - Thread creation.
-
-Module Configuration Options
-============================
-The following configurations can be adjusted via compile-time configuration of
-this module, see the
-:ref:`module documentation <module-structure-compile-time-configuration>` for
-more details.
-
-.. c:macro:: PW_THREAD_THREADX_CONFIG_JOINING_ENABLED
-
-  Whether thread joining is enabled. By default this is disabled.
-
-  We suggest only enabling this when thread joining is required to minimize
-  the RAM and ROM cost of threads.
-
-  Enabling this grows the RAM footprint of every pw::thread::Thread as it adds
-  a TX_EVENT_FLAGS_GROUP to every thread's pw::thread::threadx::Context. In
-  addition, there is a minute ROM cost to construct and destroy this added
-  object.
-
-  PW_THREAD_JOINING_ENABLED gets set to this value.
-
-.. c:macro:: PW_THREAD_THREADX_CONFIG_DEFAULT_STACK_SIZE_WORDS
-
-  The default stack size in words. By default this uses the minimal ThreadX
-  stack size.
-
-.. c:macro:: PW_THREAD_THREADX_CONFIG_MAX_THREAD_NAME_LEN
-
-  The maximum length of a thread's name, not including null termination. By
-  default this is arbitrarily set to 15. This results in an array of characters
-  which is this length + 1 bytes in every pw::thread::Thread's context.
-
-.. c:macro:: PW_THREAD_THREADX_CONFIG_DEFAULT_TIME_SLICE_INTERVAL
-
-  The round robin time slice tick interval for threads at the same priority.
-  By default this is disabled as not all ports support this, using a value of 0
-  ticks.
-
-.. c:macro:: PW_THREAD_THREADX_CONFIG_MIN_PRIORITY
-
-  The minimum priority level, this is normally based on the number of priority
-  levels.
-
-.. c:macro:: PW_THREAD_THREADX_CONFIG_DEFAULT_PRIORITY
-
-  The default priority level. By default this uses the minimal ThreadX
-  priority level, given that 0 is the highest priority.
-
-.. c:macro:: PW_THREAD_THREADX_CONFIG_LOG_LEVEL
-
-  The log level to use for this module. Logs below this level are omitted.
-
-ThreadX Thread Options
-======================
-.. cpp:class:: pw::thread::threadx::Options
-
-  .. cpp:function:: set_name(const char* name)
-
-     Sets the name for the ThreadX thread, note that this will be deep copied
-     into the context and may be truncated based on
-     ``PW_THREAD_THREADX_CONFIG_MAX_THREAD_NAME_LEN``.
-
-  .. cpp:function:: set_priority(UINT priority)
-
-     Sets the priority for the ThreadX thread from 0 through 31, where a value
-     of 0 represents the highest priority, see ThreadX tx_thread_create for
-     more detail.
-
-     **Precondition**: priority <= ``PW_THREAD_THREADX_CONFIG_MIN_PRIORITY``.
-
-  .. cpp:function:: set_preemption_threshold(UINT preemption_threshold)
-
-     Optionally sets the preemption threshold for the ThreadX thread from 0
-     through 31.
-
-     Only priorities higher than this level (i.e. lower number) are allowed to
-     preempt this thread. In other words this allows the thread to specify the
-     priority ceiling for disabling preemption. Threads that have a higher
-     priority than the ceiling are still allowed to preempt while those with
-     less than the ceiling are not allowed to preempt.
-
-     Not setting the preemption threshold or explicitly specifying a value
-     equal to the priority disables preemption threshold.
-
-     Time slicing is disabled while the preemption threshold is enabled, i.e.
-     not equal to the priority, even if a time slice interval was specified.
-
-     The preemption threshold can be adjusted at run time, this only sets the
-     initial threshold.
-
-     **Precondition**: preemption_threshold <= priority
-
-  .. cpp:function:: set_time_slice_interval(UINT time_slice_interval)
-
-     Sets the number of ticks this thread is allowed to run before other ready
-     threads of the same priority are given a chance to run.
-
-     Time slicing is disabled while the preemption threshold is enabled, i.e.
-     not equal to the priority, even if a time slice interval was specified.
-
-     A value of ``TX_NO_TIME_SLICE`` (a value of 0) disables time-slicing of
-     this thread.
-
-     Using time slicing results in a slight amount of system overhead, threads
-     with a unique priority should consider ``TX_NO_TIME_SLICE``.
-
-
-  .. cpp:function:: set_context(pw::thread::embos::Context& context)
-
-     Set the pre-allocated context (all memory needed to run a thread). Note
-     that this is required for this thread creation backend! The Context can
-     either be constructed with an externally provided ``std::span<ULONG>``
-     stack or the templated form of ``ContextWihtStack<kStackSizeWords`` can be
-     used.
-
------------------------------
-Thread Identification Backend
------------------------------
-A backend for ``pw::thread::Id`` and ``pw::thread::get_id()`` is offerred using
-``tx_thread_identify()``. It uses ``DASSERT`` to ensure that a thread is
-executing via ``TX_THREAD_GET_SYSTEM_STATE()``.
-
---------------------
-Thread Sleep Backend
---------------------
-A backend for ``pw::thread::sleep_for()`` and ``pw::thread::sleep_until()`` is
-offerred using ``tx_thread_sleep()`` if the duration is at least one tick, else
-``tx_thread_relinquish()`` is used. It uses
-``pw::this_thread::get_id() != thread::Id()`` to ensure it invoked only from a
-thread.
-
---------------------
-Thread Yield Backend
---------------------
-A backend for ``pw::thread::yield()`` is offered using via
-``tx_thread_relinquish()``. It uses
-``pw::this_thread::get_id() != thread::Id()`` to ensure it invoked only from a
-thread.
-
----------
-Utilities
----------
-``ForEachThread()``
-===================
-In cases where an operation must be performed for every thread,
-``ForEachThread()`` can be used to iterate over all the created thread TCBs.
-Note that it's only safe to use this while the scheduler is disabled.
-
-An ``Aborted`` error status is returned if the provided callback returns
-``false`` to request an early termination of thread iteration.
-
-*Return values*
-
-* ``Aborted``: The callback requested an early-termination of thread iteration.
-* ``OkStatus``: The callback has been successfully run with every thread.
-
---------------------
-Snapshot integration
---------------------
-This ``pw_thread`` backend provides helper functions that capture ThreadX thread
-state to a ``pw::thread::Thread`` proto.
-
-``SnapshotThreads()``
-=====================
-``SnapshotThread()`` captures the thread name, state, and stack information for
-the provided ThreadX TCB to a ``pw::thread::Thread`` protobuf encoder. To ensure
-the most up-to-date information is captured, the stack pointer for the currently
-running thread must be provided for cases where the running thread is being
-captured. For ARM Cortex-M CPUs, you can do something like this:
-
-.. Code:: cpp
-
-  // Capture PSP.
-  void* stack_ptr = 0;
-  asm volatile("mrs %0, psp\n" : "=r"(stack_ptr));
-  pw::thread::ProcessThreadStackCallback cb =
-      [](pw::thread::Thread::StreamEncoder& encoder,
-         pw::ConstByteSpan stack) -> pw::Status {
-    return encoder.WriteRawStack(stack);
-  };
-  pw::thread::threadx::SnapshotThread(my_thread, stack_ptr,
-                                      snapshot_encoder, cb);
-
-``SnapshotThreads()`` wraps the singular thread capture to instead captures
-all created threads to a ``pw::thread::SnapshotThreadInfo`` message. This proto
-message overlays a snapshot, so it is safe to static cast a
-``pw::snapshot::Snapshot::StreamEncoder`` to a
-``pw::thread::SnapshotThreadInfo::StreamEncoder`` when calling this function.
diff --git a/pw_thread_threadx/public/pw_thread_threadx/config.h b/pw_thread_threadx/public/pw_thread_threadx/config.h
index 9de3901..0198f4b 100644
--- a/pw_thread_threadx/public/pw_thread_threadx/config.h
+++ b/pw_thread_threadx/public/pw_thread_threadx/config.h
@@ -17,16 +17,8 @@
 #include "tx_api.h"
 
 // Whether thread joining is enabled. By default this is disabled.
-//
-// We suggest only enabling this when thread joining is required to minimize
-// the RAM and ROM cost of threads.
-//
-// Enabling this grows the RAM footprint of every pw::thread::Thread as it adds
-// a TX_EVENT_FLAGS_GROUP to every thread's pw::thread::threadx::Context. In
-// addition, there is a minute ROM cost to construct and destroy this added
-// object.
-//
-// PW_THREAD_JOINING_ENABLED gets set to this value.
+// When enabled this adds a TX_EVENT_FLAGS_GROUP to every pw::thread::Thread's
+// context.
 #ifndef PW_THREAD_THREADX_CONFIG_JOINING_ENABLED
 #define PW_THREAD_THREADX_CONFIG_JOINING_ENABLED 0
 #endif  // PW_THREAD_THREADX_CONFIG_JOINING_ENABLED
@@ -36,7 +28,7 @@
 // stack size.
 #ifndef PW_THREAD_THREADX_CONFIG_DEFAULT_STACK_SIZE_WORDS
 #define PW_THREAD_THREADX_CONFIG_DEFAULT_STACK_SIZE_WORDS \
-  (TX_MINIMUM_STACK / sizeof(ULONG))
+  TX_MINIMUM_STACK / sizeof(ULONG)
 #endif  // PW_THREAD_THREADX_CONFIG_DEFAULT_STACK_SIZE_WORDS
 
 // The maximum length of a thread's name, not including null termination. By
@@ -56,25 +48,20 @@
 // The minimum priority level, this is normally based on the number of priority
 // levels.
 #ifndef PW_THREAD_THREADX_CONFIG_MIN_PRIORITY
-#define PW_THREAD_THREADX_CONFIG_MIN_PRIORITY (TX_MAX_PRIORITIES - 1)
+#define PW_THREAD_THREADX_CONFIG_MIN_PRIORITY TX_MAX_PRIORITIES - 1
 #endif  // PW_THREAD_THREADX_CONFIG_MIN_PRIORITY
 
-// The default priority level. By default this uses the minimal ThreadX
+// The default stack size in words. By default this uses the minimal ThreadX
 // priority level, given that 0 is the highest priority.
 #ifndef PW_THREAD_THREADX_CONFIG_DEFAULT_PRIORITY
 #define PW_THREAD_THREADX_CONFIG_DEFAULT_PRIORITY \
   PW_THREAD_THREADX_CONFIG_MIN_PRIORITY
 #endif  // PW_THREAD_THREADX_CONFIG_DEFAULT_PRIORITY
 
-// The log level to use for this module. Logs below this level are omitted.
-#ifndef PW_THREAD_THREADX_CONFIG_LOG_LEVEL
-#define PW_THREAD_THREADX_CONFIG_LOG_LEVEL PW_LOG_LEVEL_DEBUG
-#endif  // PW_THREAD_THREADX_CONFIG_LOG_LEVEL
-
 namespace pw::thread::threadx::config {
 
 inline constexpr size_t kMaximumNameLength =
-    PW_THREAD_THREADX_CONFIG_MAX_THREAD_NAME_LEN;
+    PW_THREAD_THREADX_CONFIG_MAX_THREAD_NAME_LEN + 1;
 inline constexpr size_t kMinimumStackSizeWords =
     TX_MINIMUM_STACK / sizeof(ULONG);
 inline constexpr size_t kDefaultStackSizeWords =
diff --git a/pw_thread_threadx/public/pw_thread_threadx/context.h b/pw_thread_threadx/public/pw_thread_threadx/context.h
index 8d5c87c..162e3b3 100644
--- a/pw_thread_threadx/public/pw_thread_threadx/context.h
+++ b/pw_thread_threadx/public/pw_thread_threadx/context.h
@@ -17,7 +17,6 @@
 #include <cstring>
 #include <span>
 
-#include "pw_string/util.h"
 #include "pw_thread_threadx/config.h"
 #include "tx_api.h"
 #include "tx_thread.h"
@@ -35,15 +34,15 @@
 //
 // Example usage:
 //
-//   std::array<ULONG, kFooStackSizeWords> example_thread_stack;
+//   std::array<ULONG, 42> example_thread_stack;
 //   pw::thread::threadx::Context example_thread_context(example_thread_stack);
 //   void StartExampleThread() {
-//      pw::thread::DetachedThread(
+//      pw::thread::Thread(
 //        pw::thread::threadx::Options()
-//            .set_name("example_thread")
+//            .set_name("static_example_thread")
 //            .set_priority(kFooPriority)
-//            .set_context(example_thread_context),
-//        example_thread_function);
+//            .set_static_context(example_thread_context),
+//        example_thread_function).detach();
 //   }
 class Context {
  public:
@@ -64,12 +63,17 @@
   void set_in_use(bool in_use = true) { in_use_ = in_use; }
 
   const char* name() const { return name_.data(); }
-  void set_name(const char* name) { string::Copy(name, name_); }
+  void set_name(const char* name) {
+    strncpy(name_.data(), name, name_.size() - 1);
+    // Forcefully NULL terminate as strncpy does not NULL terminate if the count
+    // limit is hit.
+    name_[name_.size() - 1] = '\0';
+  }
 
   using ThreadRoutine = void (*)(void* arg);
   void set_thread_routine(ThreadRoutine entry, void* arg) {
-    user_thread_entry_function_ = entry;
-    user_thread_entry_arg_ = arg;
+    entry_ = entry;
+    arg_ = arg;
   }
 
   bool detached() const { return detached_; }
@@ -82,14 +86,14 @@
   TX_EVENT_FLAGS_GROUP& join_event_group() { return event_group_; }
 #endif  // PW_THREAD_JOINING_ENABLED
 
-  static void ThreadEntryPoint(ULONG void_context_ptr);
+  static void RunThread(ULONG void_context_ptr);
   static void DeleteThread(Context& context);
 
   TX_THREAD tcb_;
   std::span<ULONG> stack_span_;
 
-  ThreadRoutine user_thread_entry_function_ = nullptr;
-  void* user_thread_entry_arg_ = nullptr;
+  ThreadRoutine entry_ = nullptr;
+  void* arg_ = nullptr;
 #if PW_THREAD_JOINING_ENABLED
   // Note that the ThreadX life cycle of this event group is managed together
   // with the thread life cycle, not this object's life cycle.
@@ -100,16 +104,15 @@
   bool thread_done_ = false;
 
   // The TCB does not have storage for the name, ergo we provide storage for
-  // the thread's name which can be truncated down to just a null delimiter.
-  std::array<char, config::kMaximumNameLength + 1> name_;
+  // the thread's name which can be truncated down to just a null delimeter.
+  std::array<char, config::kMaximumNameLength> name_;
 };
 
 // Static thread context allocation including the stack along with the Context.
 //
 // Example usage:
 //
-//   pw::thread::threadx::ContextWithStack<kFooStackSizeWords>
-//       example_thread_context;
+//   pw::thread::threadx::ContextWithStack<42> example_thread_context;
 //   void StartExampleThread() {
 //      pw::thread::Thread(
 //        pw::thread::threadx::Options()
diff --git a/pw_thread_threadx/public/pw_thread_threadx/id_inline.h b/pw_thread_threadx/public/pw_thread_threadx/id_inline.h
index 1ae08b8..7004996 100644
--- a/pw_thread_threadx/public/pw_thread_threadx/id_inline.h
+++ b/pw_thread_threadx/public/pw_thread_threadx/id_inline.h
@@ -13,7 +13,7 @@
 // the License.
 #pragma once
 
-#include "pw_assert/assert.h"
+#include "pw_assert/light.h"
 #include "pw_thread/id.h"
 #include "tx_api.h"
 // Prior to ThreadX 6.1, this contained TX_THREAD_GET_SYSTEM_STATE().
diff --git a/pw_thread_threadx/public/pw_thread_threadx/options.h b/pw_thread_threadx/public/pw_thread_threadx/options.h
index 079b587..cdd3414 100644
--- a/pw_thread_threadx/public/pw_thread_threadx/options.h
+++ b/pw_thread_threadx/public/pw_thread_threadx/options.h
@@ -31,7 +31,7 @@
 //   pw::thread::Thread example_thread(
 //     pw::thread::threadx::Options()
 //         .set_name("example_thread"),
-//         .set_context(example_thread_context),
+//         .set_context(static_example_thread_context),
 //     example_thread_function);
 //
 //   // Specifies the name, priority, time slice interval, and pre-allocated
@@ -41,7 +41,7 @@
 //         .set_name("static_example_thread")
 //         .set_priority(kFooPriority)
 //         .set_time_slice_interval(1)
-//         .set_context(example_thread_context),
+//         .set_context(static_example_thread_context),
 //     example_thread_function);
 //
 class Options : public thread::Options {
@@ -53,7 +53,7 @@
   // Sets the name for the ThreadX thread, note that this will be deep copied
   // into the context and may be truncated based on
   // PW_THREAD_THREADX_CONFIG_MAX_THREAD_NAME_LEN.
-  constexpr Options& set_name(const char* name) {
+  constexpr Options set_name(const char* name) {
     name_ = name;
     return *this;
   }
@@ -61,9 +61,7 @@
   // Sets the priority for the ThreadX thread from 0 through 31, where a value
   // of 0 represents the highest priority, see ThreadX tx_thread_create for
   // more detail.
-  //
-  // Precondition: priority <= PW_THREAD_THREADX_CONFIG_MIN_PRIORITY
-  constexpr Options& set_priority(UINT priority) {
+  constexpr Options set_priority(UINT priority) {
     PW_DASSERT(priority <= PW_THREAD_THREADX_CONFIG_MIN_PRIORITY);
     priority_ = priority;
     return *this;
@@ -88,7 +86,7 @@
   // initial threshold.
   //
   // Precondition: preemption_threshold <= priority
-  constexpr Options& set_preemption_threshold(UINT preemption_threshold) {
+  constexpr Options set_preemption_threshold(UINT preemption_threshold) {
     PW_DASSERT(preemption_threshold < PW_THREAD_THREADX_CONFIG_MIN_PRIORITY);
     possible_preemption_threshold_ = preemption_threshold;
     return *this;
@@ -105,16 +103,14 @@
   //
   // Using time slicing results in a slight amount of system overhead, threads
   // with a unique priority should consider TX_NO_TIME_SLICE.
-  constexpr Options& set_time_slice_interval(ULONG time_slice_interval) {
+  constexpr Options set_time_slice_interval(ULONG time_slice_interval) {
     time_slice_interval_ = time_slice_interval;
     return *this;
   }
 
-  // Set the pre-allocated context (all memory needed to run a thread). Note
-  // that this is required for this thread creation backend! The Context can
-  // either be constructed with an externally provided std::span<ULONG> stack
-  // or the templated form of ContextWihtStack<kStackSizeWords> can be used.
-  constexpr Options& set_context(Context& context) {
+  // Set the pre-allocated context (all memory needed to run a thread), see the
+  // pw::thread::threadx::Context for more detail.
+  constexpr Options set_context(Context& context) {
     context_ = &context;
     return *this;
   }
diff --git a/pw_thread_threadx/public/pw_thread_threadx/sleep_inline.h b/pw_thread_threadx/public/pw_thread_threadx/sleep_inline.h
index c2ca551..d03b744 100644
--- a/pw_thread_threadx/public/pw_thread_threadx/sleep_inline.h
+++ b/pw_thread_threadx/public/pw_thread_threadx/sleep_inline.h
@@ -17,10 +17,10 @@
 
 namespace pw::this_thread {
 
-inline void sleep_until(chrono::SystemClock::time_point wakeup_time) {
+inline void sleep_until(chrono::SystemClock::time_point until_at_least) {
   // Note that if this deadline is in the future, it will get rounded up by
   // one whole tick due to how sleep_for is implemented.
-  return sleep_for(wakeup_time - chrono::SystemClock::now());
+  return sleep_for(until_at_least - chrono::SystemClock::now());
 }
 
 }  // namespace pw::this_thread
diff --git a/pw_thread_threadx/public/pw_thread_threadx/snapshot.h b/pw_thread_threadx/public/pw_thread_threadx/snapshot.h
deleted file mode 100644
index 1845b53..0000000
--- a/pw_thread_threadx/public/pw_thread_threadx/snapshot.h
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_protobuf/encoder.h"
-#include "pw_status/status.h"
-#include "pw_thread/snapshot.h"
-#include "pw_thread_protos/thread.pwpb.h"
-#include "tx_api.h"
-
-namespace pw::thread::threadx {
-
-// Captures all threadx threads in a system as part of a snapshot.
-//
-// An updated running_thread_stack_pointer must be provided in order for the
-// running thread's context to reflect the running state. For ARM, you might do
-// something like this:
-//
-//    // Capture PSP.
-//    void* stack_ptr = 0;
-//    asm volatile("mrs %0, psp\n" : "=r"(stack_ptr));
-//    pw::thread::ProcessThreadStackCallback cb =
-//        [](pw::thread::Thread::StreamEncoder& encoder,
-//           pw::ConstByteSpan stack) -> pw::Status {
-//      return encoder.WriteRawStack(stack);
-//    };
-//    pw::thread::threadx::SnapshotThread(my_thread, stack_ptr,
-//                                        snapshot_encoder, cb);
-//
-// Warning: This is only safe to use when interrupts and the scheduler are
-// disabled!
-// Warning: SMP ports are not yet supported.
-Status SnapshotThreads(void* running_thread_stack_pointer,
-                       SnapshotThreadInfo::StreamEncoder& encoder,
-                       ProcessThreadStackCallback& thread_stack_callback);
-
-// Captures only the provided thread handle as a pw::thread::Thread proto
-// message. After thread info capture, the ProcessThreadStackCallback is called
-// to capture either the raw_stack or raw_backtrace.
-//
-// An updated running_thread_stack_pointer must be provided in order for the
-// running thread's context to reflect the current state. If the thread being
-// captured is not the running thread, the value is ignored. Note that the
-// stack pointer in the thread handle is almost always stale on the running
-// thread.
-//
-// Captures the following proto fields:
-//   pw.thread.Thread:
-//     name
-//     state
-//     stack_start_pointer
-//     stack_end_pointer
-//     stack_pointer
-//
-// Warning: This is only safe to use when interrupts and the scheduler are
-// disabled!
-// Warning: SMP ports are not yet supported.
-Status SnapshotThread(const TX_THREAD& thread,
-                      void* running_thread_stack_pointer,
-                      Thread::StreamEncoder& encoder,
-                      ProcessThreadStackCallback& thread_stack_callback);
-
-}  // namespace pw::thread::threadx
diff --git a/pw_thread_threadx/public/pw_thread_threadx/util.h b/pw_thread_threadx/public/pw_thread_threadx/util.h
deleted file mode 100644
index d68ff61..0000000
--- a/pw_thread_threadx/public/pw_thread_threadx/util.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_function/function.h"
-#include "pw_status/status.h"
-#include "tx_api.h"
-
-namespace pw::thread::threadx {
-
-// A callback that is executed for each thread when using ForEachThread(). The
-// callback should return true if thread iteration should continue. When this
-// callback returns false, ForEachThread() will cease iteration of threads and
-// return an `Aborted` error code.
-using ThreadCallback = pw::Function<bool(const TX_THREAD&)>;
-
-// Iterates through all threads that haven't been deleted, calling the provided
-// callback on each thread.
-//
-// Returns:
-//   Aborted - The callback requested an early-termination of thread iteration.
-//   OkStatus - Successfully iterated over all threads.
-//
-// Warning: This is only safe to use when the scheduler is disabled.
-Status ForEachThread(const ThreadCallback& cb);
-
-namespace internal {
-
-// This function is exposed for testing. Prefer
-// pw::thread::threadx::ForEachThread.
-Status ForEachThread(const TX_THREAD& starting_thread,
-                     const ThreadCallback& cb);
-
-}  // namespace internal
-}  // namespace pw::thread::threadx
diff --git a/pw_thread_threadx/public/pw_thread_threadx/yield_inline.h b/pw_thread_threadx/public/pw_thread_threadx/yield_inline.h
index a3002a0..32ce45c 100644
--- a/pw_thread_threadx/public/pw_thread_threadx/yield_inline.h
+++ b/pw_thread_threadx/public/pw_thread_threadx/yield_inline.h
@@ -13,14 +13,13 @@
 // the License.
 #pragma once
 
-#include "pw_assert/assert.h"
+#include "pw_assert/light.h"
 #include "pw_thread/id.h"
 #include "tx_api.h"
 
 namespace pw::this_thread {
 
 inline void yield() {
-  // Ensure this is being called by a thread.
   PW_DASSERT(get_id() != thread::Id());
   tx_thread_relinquish();
 }
diff --git a/pw_thread_threadx/sleep.cc b/pw_thread_threadx/sleep.cc
index 5bfb0ca..1cb5e5c 100644
--- a/pw_thread_threadx/sleep.cc
+++ b/pw_thread_threadx/sleep.cc
@@ -16,7 +16,7 @@
 
 #include <algorithm>
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_chrono/system_clock.h"
 #include "pw_chrono_threadx/system_clock_constants.h"
 #include "pw_thread/id.h"
@@ -26,33 +26,27 @@
 
 namespace pw::this_thread {
 
-void sleep_for(chrono::SystemClock::duration sleep_duration) {
-  // Ensure this is being called by a thread.
+void sleep_for(chrono::SystemClock::duration for_at_least) {
   PW_DCHECK(get_id() != thread::Id());
 
   // Yield for negative and zero length durations.
-  if (sleep_duration <= chrono::SystemClock::duration::zero()) {
+  if (for_at_least <= chrono::SystemClock::duration::zero()) {
     tx_thread_relinquish();
     return;
   }
 
-  // In case the timeout is too long for us to express through the native
-  // ThreadX API, we repeatedly wait with shorter durations. Note that on a tick
-  // based kernel we cannot tell how far along we are on the current tick, ergo
-  // we add one whole tick to the final duration. However, this also means that
-  // the loop must ensure that timeout + 1 is less than the max timeout.
+  // On a tick based kernel we cannot tell how far along we are on the current
+  // tick, ergo we add one whole tick to the final duration.
   constexpr SystemClock::duration kMaxTimeoutMinusOne =
       pw::chrono::threadx::kMaxTimeout - SystemClock::duration(1);
-  while (sleep_duration > kMaxTimeoutMinusOne) {
+  while (for_at_least > kMaxTimeoutMinusOne) {
     const UINT result =
         tx_thread_sleep(static_cast<ULONG>(kMaxTimeoutMinusOne.count()));
     PW_CHECK_UINT_EQ(TX_SUCCESS, result);
-    sleep_duration -= kMaxTimeoutMinusOne;
+    for_at_least -= kMaxTimeoutMinusOne;
   }
-  // On a tick based kernel we cannot tell how far along we are on the current
-  // tick, ergo we add one whole tick to the final duration.
   const UINT result =
-      tx_thread_sleep(static_cast<ULONG>(sleep_duration.count() + 1));
+      tx_thread_sleep(static_cast<ULONG>(for_at_least.count() + 1));
   PW_CHECK_UINT_EQ(TX_SUCCESS, result);
 }
 
diff --git a/pw_thread_threadx/snapshot.cc b/pw_thread_threadx/snapshot.cc
deleted file mode 100644
index f38e16d..0000000
--- a/pw_thread_threadx/snapshot.cc
+++ /dev/null
@@ -1,153 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 PW_LOG_LEVEL PW_THREAD_THREADX_CONFIG_LOG_LEVEL
-
-#include "pw_thread_threadx/snapshot.h"
-
-#include <string_view>
-
-#include "pw_function/function.h"
-#include "pw_log/log.h"
-#include "pw_protobuf/encoder.h"
-#include "pw_status/status.h"
-#include "pw_thread/snapshot.h"
-#include "pw_thread_protos/thread.pwpb.h"
-#include "pw_thread_threadx/config.h"
-#include "pw_thread_threadx/util.h"
-#include "tx_api.h"
-#include "tx_thread.h"
-
-namespace pw::thread::threadx {
-namespace {
-
-// TODO(amontanez): This might make unit testing codepaths that use this more
-// challenging.
-inline bool ThreadIsRunning(const TX_THREAD& thread) {
-  const TX_THREAD* running_thread;
-  TX_THREAD_GET_CURRENT(running_thread);
-  return running_thread == &thread;
-}
-
-void CaptureThreadState(const TX_THREAD& thread,
-                        Thread::StreamEncoder& encoder) {
-  if (ThreadIsRunning(thread)) {
-    PW_LOG_DEBUG("Thread state: RUNNING");
-    encoder.WriteState(ThreadState::Enum::RUNNING);
-    return;
-  }
-
-  switch (thread.tx_thread_state) {
-    case TX_READY:
-      PW_LOG_DEBUG("Thread state: READY");
-      encoder.WriteState(ThreadState::Enum::READY);
-      break;
-    case TX_COMPLETED:
-    case TX_TERMINATED:
-      PW_LOG_DEBUG("Thread state: INACTIVE");
-      encoder.WriteState(ThreadState::Enum::INACTIVE);
-      break;
-    case TX_SUSPENDED:
-    case TX_SLEEP:
-      PW_LOG_DEBUG("Thread state: SUSPENDED");
-      encoder.WriteState(ThreadState::Enum::SUSPENDED);
-      break;
-    case TX_QUEUE_SUSP:
-    case TX_SEMAPHORE_SUSP:
-    case TX_EVENT_FLAG:
-    case TX_BLOCK_MEMORY:
-    case TX_BYTE_MEMORY:
-    case TX_IO_DRIVER:
-    case TX_FILE:
-    case TX_TCP_IP:
-    case TX_MUTEX_SUSP:
-      PW_LOG_DEBUG("Thread state: BLOCKED");
-      encoder.WriteState(ThreadState::Enum::BLOCKED);
-      break;
-    default:
-      PW_LOG_DEBUG("Thread state: UNKNOWN");
-      encoder.WriteState(ThreadState::Enum::UNKNOWN);
-  }
-}
-
-}  // namespace
-
-Status SnapshotThreads(void* running_thread_stack_pointer,
-                       SnapshotThreadInfo::StreamEncoder& encoder,
-                       ProcessThreadStackCallback& stack_dumper) {
-  struct {
-    void* running_thread_stack_pointer;
-    SnapshotThreadInfo::StreamEncoder* encoder;
-    ProcessThreadStackCallback* stack_dumper;
-    Status thread_capture_status;
-  } ctx;
-  ctx.running_thread_stack_pointer = running_thread_stack_pointer;
-  ctx.encoder = &encoder;
-  ctx.stack_dumper = &stack_dumper;
-
-  ThreadCallback thread_capture_cb([&ctx](const TX_THREAD& thread) -> bool {
-    Thread::StreamEncoder thread_encoder = ctx.encoder->GetThreadsEncoder();
-    ctx.thread_capture_status.Update(
-        SnapshotThread(thread,
-                       ctx.running_thread_stack_pointer,
-                       thread_encoder,
-                       *ctx.stack_dumper));
-    // Always iterate all threads.
-    return true;
-  });
-
-  if (Status status = ForEachThread(thread_capture_cb); !status.ok()) {
-    PW_LOG_ERROR("Failed to iterate threads during snapshot capture: %d",
-                 static_cast<int>(status.code()));
-  }
-
-  return ctx.thread_capture_status;
-}
-
-Status SnapshotThread(const TX_THREAD& thread,
-                      void* running_thread_stack_pointer,
-                      Thread::StreamEncoder& encoder,
-                      ProcessThreadStackCallback& thread_stack_callback) {
-  PW_LOG_DEBUG("Capturing thread info for %s", thread.tx_thread_name);
-  encoder.WriteName(
-      std::as_bytes(std::span(std::string_view(thread.tx_thread_name))));
-
-  CaptureThreadState(thread, encoder);
-
-  const StackContext thread_ctx = {
-      .thread_name = thread.tx_thread_name,
-
-      // TODO(amontanez): When ThreadX is built with stack checking enabled, the
-      // lowest-addressed `unsigned long` is reserved for a watermark. This
-      // means in practice the stack pointer should never end up there. To be
-      // conservative, behave as though TX_THREAD_STACK_CHECK is always fully
-      // enabled.
-      .stack_low_addr =
-          reinterpret_cast<uintptr_t>(thread.tx_thread_stack_start) +
-          sizeof(ULONG),
-
-      .stack_high_addr =
-          reinterpret_cast<uintptr_t>(thread.tx_thread_stack_end),
-
-      // If the thread is active, the stack pointer in the TCB is stale.
-      .stack_pointer = reinterpret_cast<uintptr_t>(
-          ThreadIsRunning(thread) ? running_thread_stack_pointer
-                                  : thread.tx_thread_stack_ptr),
-      .stack_pointer_est_peak = std::nullopt,
-  };
-
-  return SnapshotStack(thread_ctx, encoder, thread_stack_callback);
-}
-
-}  // namespace pw::thread::threadx
diff --git a/pw_thread_threadx/thread.cc b/pw_thread_threadx/thread.cc
index 673a3ca..72befae 100644
--- a/pw_thread_threadx/thread.cc
+++ b/pw_thread_threadx/thread.cc
@@ -13,7 +13,7 @@
 // the License.
 #include "pw_thread/thread.h"
 
-#include "pw_assert/check.h"
+#include "pw_assert/assert.h"
 #include "pw_preprocessor/compiler.h"
 #include "pw_thread/id.h"
 #include "pw_thread_threadx/config.h"
@@ -30,11 +30,9 @@
 #endif  // PW_THREAD_JOINING_ENABLED
 }  // namespace
 
-void Context::ThreadEntryPoint(ULONG void_context_ptr) {
+void Context::RunThread(ULONG void_context_ptr) {
   Context& context = *reinterpret_cast<Context*>(void_context_ptr);
-
-  // Invoke the user's thread function. This may never return.
-  context.user_thread_entry_function_(context.user_thread_entry_arg_);
+  context.entry_(context.arg_);
 
   // Raise our preemption threshold as a thread only critical section to guard
   // against join() and detach().
@@ -52,9 +50,8 @@
     context.set_in_use(false);
 
 #if PW_THREAD_JOINING_ENABLED
-    // If the thread handle was detached before the thread finished execution,
-    // i.e. got here, then we are responsible for cleaning up the join event
-    // group.
+    // Just in case someone abused our API, ensure their use of the event group
+    // is properly handled by the kernel regardless.
     const UINT event_group_result =
         tx_event_flags_delete(&context.join_event_group());
     PW_DCHECK_UINT_EQ(TX_SUCCESS,
@@ -124,7 +121,7 @@
   PW_DCHECK(!native_type_->in_use());
 
   // Reset the state of the static context in case it was re-used.
-  native_type_->set_in_use(true);
+  native_type_->set_in_use(false);
   native_type_->set_detached(false);
   native_type_->set_thread_done(false);
 #if PW_THREAD_JOINING_ENABLED
@@ -147,7 +144,7 @@
   const UINT thread_result =
       tx_thread_create(&options.context()->tcb(),
                        const_cast<char*>(native_type_->name()),
-                       Context::ThreadEntryPoint,
+                       Context::RunThread,
                        reinterpret_cast<ULONG>(native_type_),
                        options.context()->stack().data(),
                        options.context()->stack().size_bytes(),
@@ -167,12 +164,12 @@
   tx_thread_resume(&native_type_->tcb());
 
   if (thread_done) {
-    // The task finished (hit end of Context::ThreadEntryPoint) before we
-    // invoked detach, clean up the thread.
+    // The task finished (hit end of Context::RunThread) before we invoked
+    // detach, clean up the thread.
     Context::DeleteThread(*native_type_);
   } else {
     // We're detaching before the task finished, defer cleanup to the task at
-    // the end of Context::ThreadEntryPoint.
+    // the end of Context::RunThread.
   }
 
   // Update to no longer represent a thread of execution.
diff --git a/pw_thread_threadx/util.cc b/pw_thread_threadx/util.cc
deleted file mode 100644
index eee57b7..0000000
--- a/pw_thread_threadx/util.cc
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_thread_threadx/util.h"
-
-#include "pw_function/function.h"
-#include "pw_status/status.h"
-#include "tx_api.h"
-#include "tx_thread.h"
-
-namespace pw::thread::threadx {
-
-namespace internal {
-
-// Iterates through all threads that haven't been deleted, calling the provided
-// callback.
-Status ForEachThread(const TX_THREAD& starting_thread,
-                     const ThreadCallback& cb) {
-  const TX_THREAD* thread = &starting_thread;
-  do {
-    if (!cb(*thread)) {
-      // Early-terminate iteration if requested by the callback.
-      return Status::Aborted();
-    }
-    thread = thread->tx_thread_created_next;
-  } while (thread != &starting_thread);
-
-  return OkStatus();
-}
-
-}  // namespace internal
-
-Status ForEachThread(const ThreadCallback& cb) {
-  return internal::ForEachThread(*_tx_thread_created_ptr, cb);
-}
-
-}  // namespace pw::thread::threadx
diff --git a/pw_tls_client/BUILD.bazel b/pw_tls_client/BUILD.bazel
deleted file mode 100644
index bae944d..0000000
--- a/pw_tls_client/BUILD.bazel
+++ /dev/null
@@ -1,102 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_facade",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_facade(
-    name = "pw_tls_client_facade",
-    hdrs = [
-        "public/pw_tls_client/options.h",
-        "public/pw_tls_client/session.h",
-        "public/pw_tls_client/status.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_assert",
-        "//pw_bytes",
-        "//pw_result",
-        "//pw_status",
-        "//pw_stream",
-    ],
-)
-
-pw_cc_library(
-    name = "pw_tls_client",
-    deps = [":pw_tls_client_facade"],
-)
-
-# TODO(zyecheng): Add a "backend_multiplexer" target once BoringSSL/MbedTLS is
-# ready.
-
-pw_cc_facade(
-    name = "entropy_facade",
-    hdrs = [
-        "public/pw_tls_client/entropy.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_bytes",
-        "//pw_status",
-    ],
-)
-
-pw_cc_library(
-    name = "fake_entropy",
-    srcs = ["fake_entropy.cc"],
-    deps = [
-        ":entropy_facade",
-    ],
-)
-
-# TODO(zyecheng): The target requires a build_time.h header that defines a
-# 'constexpr size_t kBuildTimeMicrosecondsUTC' variable for storing the build time.
-# In gn build, this is generated by a python action target. Need to figure out a
-# solution in bazel build.
-pw_cc_library(
-    name = "build_time",
-    srcs = [
-        "build_time.cc",
-    ],
-)
-
-pw_cc_library(
-    name = "crlset",
-    hdrs = ["public/pw_tls_client/crlset.h"],
-    includes = ["public"],
-    deps = [
-        "//pw_bytes",
-    ],
-)
-
-pw_cc_library(
-    name = "test_server",
-    srcs = ["test_server.cc"],
-    hdrs = ["public/pw_tls_client/test/test_server.h"],
-    includes = ["public"],
-)
-
-pw_cc_test(
-    name = "test_server_test",
-    srcs = ["test_server_test.cc"],
-    deps = [":test_server"],
-)
diff --git a/pw_tls_client/BUILD.gn b/pw_tls_client/BUILD.gn
deleted file mode 100644
index 8a89cf3..0000000
--- a/pw_tls_client/BUILD.gn
+++ /dev/null
@@ -1,168 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-import("$dir_pw_build/facade.gni")
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_third_party/boringssl/boringssl.gni")
-import("$dir_pw_tls_client/configs.gni")
-import("$dir_pw_unit_test/test.gni")
-
-config("public_includes") {
-  include_dirs = [ "public" ]
-}
-
-pw_facade("pw_tls_client") {
-  backend = pw_tls_client_BACKEND
-  public_configs = [ ":public_includes" ]
-  public = [
-    "public/pw_tls_client/options.h",
-    "public/pw_tls_client/session.h",
-    "public/pw_tls_client/status.h",
-  ]
-  public_deps = [
-    "$dir_pw_assert",
-    "$dir_pw_bytes",
-    "$dir_pw_result",
-    "$dir_pw_status",
-    "$dir_pw_stream",
-    "$dir_pw_string",
-  ]
-}
-
-pw_facade("tls_entropy") {
-  backend = pw_tls_client_ENTROPY_BACKEND
-  public_configs = [ ":public_includes" ]
-  public = [ "public/pw_tls_client/entropy.h" ]
-  public_deps = [
-    "$dir_pw_bytes",
-    "$dir_pw_status",
-  ]
-}
-
-# A fake entropy source that does nothing. It should only be used for
-# demo and test purpose only. Production code shall not use it.
-pw_source_set("fake_entropy") {
-  public_deps = [ ":tls_entropy.facade" ]
-  sources = [ "fake_entropy.cc" ]
-  deps = [ "$dir_pw_log" ]
-}
-
-# The ":time" target wraps the time() and gettimeofday(), which are
-# commonly used by TLS libraries for expiration check.
-config("time_wrap") {
-  # Link options that wrap C time calls.
-  ldflags = [
-    "-Wl,--wrap=time",
-    "-Wl,--wrap=gettimeofday",
-  ]
-}
-
-pw_facade("time") {
-  backend = pw_tls_client_TIME_BACKEND
-  public_configs = [ ":time_wrap" ]
-  public = []
-
-  # The target should only be used by TLS libraries to obtain date time
-  visibility = [
-    ":*",
-    "$dir_pw_third_party/boringssl",
-    "$dir_pw_third_party/mbedtls",
-  ]
-}
-
-# The build time is obtained with a python script and put in a generated header
-# file. The header file is included in build_time.cc
-pw_python_action("generate_buid_time_header") {
-  header_output = "$target_gen_dir/$target_name/build_time.h"
-  script = "generate_build_time_header.py"
-  outputs = [
-    header_output,
-
-    # A output file that is never generated so that this action is always
-    # re-run. This is to make sure that the build time in the header is always
-    # up-to-date.
-    "$target_gen_dir/non_exists",
-  ]
-  args = [ rebase_path(header_output) ]
-}
-
-# The target provides a backend to :time that returns build time.
-pw_source_set("build_time") {
-  time_injection_outputs = get_target_outputs(":generate_buid_time_header")
-  include_dirs = [ get_path_info(time_injection_outputs[0], "dir") ]
-  sources = [ "build_time.cc" ]
-  deps = [
-    ":generate_buid_time_header",
-    ":time.facade",
-  ]
-}
-
-# TODO(pwbug/396): Add a python target to generate source file from the
-# specified CRLSet file in `pw_tls_client_CRLSET_FILE`
-
-pw_source_set("crlset") {
-  public_configs = [ ":public_includes" ]
-  public = [ "public/pw_tls_client/crlset.h" ]
-
-  # TODO(pwbug/396): Add sources generated from a CRLSet file to build.
-}
-
-pw_source_set("test_server") {
-  sources = [ "test_server.cc" ]
-  public_configs = [ ":public_includes" ]
-  public = [ "public/pw_tls_client/test/test_server.h" ]
-  public_deps = [
-    "$dir_pw_bytes",
-    "$dir_pw_log",
-    "$dir_pw_stream",
-    "$dir_pw_third_party/boringssl",
-  ]
-}
-
-pw_test("test_server_test") {
-  enable_if = dir_pw_third_party_boringssl != ""
-  public_deps = [
-    ":test_data",
-    ":test_server",
-  ]
-  sources = [ "test_server_test.cc" ]
-}
-
-pw_python_action("generate_test_data") {
-  header_output = "$target_gen_dir/$target_name/test_certs_and_keys.h"
-  script = "py/pw_tls_client/generate_test_data.py"
-  python_deps = [ "py" ]
-  outputs = [ header_output ]
-  args = [ rebase_path(header_output) ]
-}
-
-config("test_data_includes") {
-  test_header_out = get_target_outputs(":generate_test_data")
-  include_dirs = [ get_path_info(test_header_out[0], "dir") ]
-}
-
-group("test_data") {
-  public_deps = [ ":generate_test_data" ]
-  public_configs = [ ":test_data_includes" ]
-}
-
-pw_test_group("tests") {
-  tests = [ ":test_server_test" ]
-}
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
diff --git a/pw_tls_client/OWNERS b/pw_tls_client/OWNERS
deleted file mode 100644
index 121c6f5..0000000
--- a/pw_tls_client/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-zyecheng@google.com
diff --git a/pw_tls_client/build_time.cc b/pw_tls_client/build_time.cc
deleted file mode 100644
index c59431a..0000000
--- a/pw_tls_client/build_time.cc
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "build_time.h"
-
-#include <sys/time.h>
-
-namespace {
-constexpr uint64_t kMicrosecondsPerSecond = 1'000'000;
-}
-
-#if __cplusplus
-extern "C" {
-#endif
-
-time_t __wrap_time(time_t* t) {
-  time_t ret =
-      static_cast<time_t>(kBuildTimeMicrosecondsUTC / kMicrosecondsPerSecond);
-  if (t) {
-    *t = ret;
-  }
-  return ret;
-}
-
-int __wrap_gettimeofday(struct timeval* tv, void* tz) {
-  // The use of the timezone structure is obsolete (see docs "man
-  // gettimeofday"). Thus we don't consider it.
-  (void)tz;
-  tv->tv_sec = kBuildTimeMicrosecondsUTC / kMicrosecondsPerSecond;
-  tv->tv_usec = kBuildTimeMicrosecondsUTC % kMicrosecondsPerSecond;
-  return 0;
-}
-
-#if __cplusplus
-}
-#endif
diff --git a/pw_tls_client/configs.gni b/pw_tls_client/configs.gni
deleted file mode 100644
index 70e04e5..0000000
--- a/pw_tls_client/configs.gni
+++ /dev/null
@@ -1,29 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-declare_args() {
-  # Backend for pw_tls_client
-  pw_tls_client_BACKEND = ""
-
-  # Backend for pw_tls_client:tls_entropy
-  pw_tls_client_ENTROPY_BACKEND = "$dir_pw_tls_client:fake_entropy"
-
-  # Backend for pw_tls_client:time
-  pw_tls_client_TIME_BACKEND = "$dir_pw_tls_client:build_time"
-
-  # The path to the CRLSet file.
-  pw_tls_client_CRLSET_FILE = ""
-}
diff --git a/pw_tls_client/docs.rst b/pw_tls_client/docs.rst
deleted file mode 100644
index 380018c..0000000
--- a/pw_tls_client/docs.rst
+++ /dev/null
@@ -1,232 +0,0 @@
-.. _module-pw_tls_client:
-
---------------
-pw_tls_client
---------------
-
-This module provides a facade that defines the public APIs for establishing TLS
-sessions over arbitrary transports. Two options of backends,
-pw_tls_client_mbedtls and pw_tls_client_boringssl, which are based on BoringSSL
-and MbedTLS libraries, are under construction.
-
-The facade provides a class ``pw::tls_client::Session`` with Open(), Read(),
-Write() and Close() methods for TLS communication. An instance is created by
-``pw::tls_client::Session::Create`` method. The method takes a
-``pw::tls_client::SessionOptions`` object, which is used to configure TLS
-connection options. The list of supported configurations currently include:
-
-1. Host name of the target server. This will be used as the Server Name
-Indication(SNI) extension during TLS handshake.
-
-2. User-implemented transport. The underlying transport for the TLS
-communication. It is an object that implements the interface of
-``pw::stream::ReaderWriter``.
-
-The module will also provide mechanisms/APIs for users to specify sources of
-trust anchors, time and entropy. These are under construction.
-
-.. warning::
-  This module is under construction, not ready for use, and the documentation
-  is incomplete.
-
-Prerequisites
-=============
-This module requires the following dependencies:
-
-1. Entropy
------------
-TLS requires an entropy source for generating random bytes. Users of this
-module should provide one by implementing a backend to the
-``pw_tls_client:entropy`` facade. The backend defaults to
-``pw_tls_client:fake_entropy`` that does nothing.
-
-2. Chromium Verifier
----------------------
-BoringSSL backend uses chromium verifier for certication verification. If the
-downstream project uses BoringSSL as the backend, the sources of the verifier,
-which is part of the chorimum sources, needs to be downloaded in order for
-``//third_party/chromium_verifier`` to build. It is recommended to use our
-support in pw_package for downloading compatible and tested version:
-
-.. code-block:: sh
-
-  pw package install chromium_verifier
-
-Then follow instruction for setting ``dir_pw_third_party_chromium_verifier`` to
-the path of the downloaded repo.
-
-3. Date time
--------------
-TLS needs a trust-worthy source of wall clock time in order to check
-expiration. Provisioning of time source for TLS communication is very specific
-to the TLS library in use. However, common TLS libraires, such as BoringSSL
-and MbedTLS, support the use of C APIs ``time()`` and ``getimtofday()`` for
-obtaining date time. To accomodate the use of these libraries, a facade target
-``pw_tls_client:time`` is added that wraps these APIs. For GN builds,
-specify the backend target with variable ``pw_tls_client_C_TIME_BACKEND``.
-``pw_tls_client_C_TIME_BACKEND`` defaults to the ``pw_tls_client::build_time``
-backend that returns build time.
-
-If downstream project chooses to use other TLS libraires that handle time source
-differently, then it needs to be investigated separately.
-
-4. CRLSet
------------
-The module supports CRLSet based revocation check for certificates. A CRLSet
-file specifies a list of X509 certificates that either need to be blocked, or
-have been revoked by the issuer. It is introduced by chromium and primarily
-used for certificate verification/revocation checks during TLS handshake. The
-format of a CRLSet file is available in
-https://chromium.googlesource.com/chromium/src/+/refs/heads/main/net/cert/crl_set.cc#24.
-
-Downstream projects need to provide a CRLSet file at build time. For GN builds,
-specify the path of the CRLSet file with the GN variable
-``pw_tls_client_CRLSET_FILE``. This module converts the CRLSet file into
-source code at build time and generates APIs for querying certificate
-block/revocation status. See ``pw_tls_client/crlset.h`` for more detail.
-
-Chromium maintains its own CRLSet that targets at the general Internet. To use it,
-run the following command to download the latest version:
-
-.. code-block:: sh
-
-  pw package install crlset --force
-
-The `--force` option forces CRLSet to be always re-downloaded so that it is
-up-to-date. Project that are concerned about up-to-date CRLSet should always
-run the above command before build.
-
-Toolings will be provided for generating custom CRLSet files from user-provided
-certificate files. The functionality is under construction.
-
-Setup
-=====
-This module requires the following setup:
-
-  1. Choose a ``pw_tls_client`` backend, or write one yourself.
-  2. If using GN build, Specify the ``pw_tls_client_BACKEND`` GN build arg to
-     point the library that provides a ``pw_tls_client`` backend. To use the
-     MbedTLS backend, set variable ``pw_tls_client_BACKEND`` to
-     ``//pw_tls_client_mbedtls``. To use the BoringSSL backend, set it to
-     ``//pw_tls_client_boringssl``.
-  3. Provide a `pw_tls_client:entropy` backend. If using GN build, specify the
-     backend with variable ``pw_tls_client_ENTROPY_BACKEND``.
-
-Module usage
-============
-For GN build, add ``//pw_tls_client`` to the dependency list.
-
-The following gives an example code for using the module on host platform.
-The example uses a Pigweed socket stream as the transport and performs TLS
-connection to www.google.com:
-
-.. code-block:: cpp
-
-  // Host domain name
-  constexpr char kHost[] = "www.google.com";
-
-  constexpr int kPort = 443;
-
-  // Server Name Indication.
-  constexpr const char* kServerNameIndication = kHost;
-
-  // An example message to send.
-  constexpr char kHTTPRequest[] = "GET / HTTP/1.1\r\n\r\n";
-
-  // pw::stream::SocketStream doesn't accept host domain name as input. Thus we
-  // introduce this helper function for getting the IP address
-  pw::Status GetIPAddrFromHostName(std::string_view host, std::span<char> ip) {
-    char null_terminated_host_name[256] = {0};
-    auto host_copy_status = pw::string::Copy(host, null_terminated_host_name);
-    if (!host_copy_status.ok()) {
-      return host_copy_status.status();
-    }
-
-    struct hostent* ent = gethostbyname(null_terminated_host_name);
-    if (ent == NULL) {
-      return PW_STATUS_INTERNAL;
-    }
-
-    in_addr** addr_list = reinterpret_cast<in_addr**>(ent->h_addr_list);
-    if (addr_list[0] == nullptr) {
-      return PW_STATUS_INTERNAL;
-    }
-
-    auto ip_copy_status = pw::string::Copy(inet_ntoa(*addr_list[0]), ip);
-    if (!ip_copy_status.ok()) {
-      return ip_copy_status.status();
-    }
-
-    return pw::OkStatus();
-  }
-
-  int main() {
-    // Get the IP address of the target host.
-    char ip_address[64] = {0};
-    auto get_ip_status = GetIPAddrFromHostName(kHost, ip_address);
-    if (!get_ip_status.ok()) {
-      return 1;
-    }
-
-    // Use a socket stream as the transport.
-    pw::stream::SocketStream socket_stream;
-
-    // Connect the socket to the remote host.
-    auto socket_connect_status = socket_stream.Connect(ip_address, kPort);
-    if (!socket_connect_status.ok()) {
-      return 1;
-    }
-
-    // Create a TLS session. Register the transport.
-    auto options = pw::tls_client::SessionOptions()
-            .set_server_name(kServerNameIndication)
-            .set_transport(socket_stream);
-    auto tls_conn = pw::tls_client::Session::Create(options);
-    if (!tls_conn.ok()) {
-      // Handle errors.
-      return 1;
-    }
-
-    auto open_status = tls_conn.value()->Open();
-    if (!open_status.ok()) {
-      // Inspect/handle error with open_status.code() and
-      // tls_conn.value()->GetLastTLSStatus().
-      return 1;
-    }
-
-    auto write_status = tls_conn.value()->Write(std::as_bytes(std::span{kHTTPRequest}));
-    if (!write_status.ok()) {
-      // Inspect/handle error with write_status.code() and
-      // tls_conn.value()->GetLastTLSStatus().
-      return 0;
-    }
-
-    // Listen for incoming data.
-    std::array<std::byte, 4096> buffer;
-    while (true) {
-      auto res = tls_conn.value()->Read(buffer);
-      if (!res.ok()) {
-        // Inspect/handle error with res.status().code() and
-        // tls_conn.value()->GetLastTLSStatus().
-        return 1;
-      }
-
-      // Process data in |buffer|. res.value() gives the span of read bytes.
-      // The following simply print to console.
-      if (res.value().size()) {
-        auto print_status = pw::sys_io::WriteBytes(res.value());
-        if (!print_status.ok()) {
-          return 1;
-        }
-      }
-
-    }
-  }
-
-A list of other demos will be provided in ``//pw_tls_client/examples/``
-
-Warning
-============
-
-Open()/Read() APIs are synchronous for now. Support for
-non-blocking/asynchronous usage will be added in the future.
diff --git a/pw_tls_client/fake_entropy.cc b/pw_tls_client/fake_entropy.cc
deleted file mode 100644
index 8205031..0000000
--- a/pw_tls_client/fake_entropy.cc
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_log/log.h"
-#include "pw_tls_client/entropy.h"
-
-namespace pw::tls_client {
-
-Status GetRandomBytes(ByteSpan) {
-  PW_LOG_INFO(
-      "Warning. The TLS client is using a fake enropy source that does "
-      "nothing. This should only be used for demo and test purpose."
-      "Production code shall not use it");
-  return OkStatus();
-}
-
-}  // namespace pw::tls_client
diff --git a/pw_tls_client/generate_build_time_header.py b/pw_tls_client/generate_build_time_header.py
deleted file mode 100644
index 824ac6e..0000000
--- a/pw_tls_client/generate_build_time_header.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Generate a header file for UTC time"""
-
-import argparse
-from datetime import datetime
-import sys
-
-HEADER = """// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <stdint.h>
-
-"""
-
-
-def parse_args() -> None:
-    """Setup argparse."""
-    parser = argparse.ArgumentParser()
-    parser.add_argument("out", help="path for output header file")
-    return parser.parse_args()
-
-
-def main() -> int:
-    """Main function"""
-    args = parse_args()
-    time_stamp = int(datetime.now().timestamp())
-    print(time_stamp)
-    with open(args.out, "w") as header:
-        print(args.out)
-        header.write(HEADER)
-
-        # Add a comment in the generated header to show readable build time
-        string_date = datetime.fromtimestamp(time_stamp).strftime(
-            "%m/%d/%Y %H:%M:%S")
-        header.write(f'// {string_date}\n')
-
-        # Write to the header.
-        header.write(''.join([
-            'constexpr uint64_t kBuildTimeMicrosecondsUTC = ',
-            f'{int(time_stamp * 1e6)};\n'
-        ]))
-    return 0
-
-
-if __name__ == "__main__":
-    sys.exit(main())
diff --git a/pw_tls_client/public/pw_tls_client/crlset.h b/pw_tls_client/public/pw_tls_client/crlset.h
deleted file mode 100644
index 727b5a5..0000000
--- a/pw_tls_client/public/pw_tls_client/crlset.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_bytes/span.h"
-
-namespace pw::tls_client::crlset {
-
-// The following APIs will be used to verify the chains of certificates the TLS
-// client builds. For example, for each certificate in the chain,
-// IsCertificateBlocked() is used to check if it needs to be blocked. Then
-// IsCertificateRevoked() is used to check if it is revoked by the issuer (the
-// certificate one level up the chain). If either of them returns true, the
-// chain should be rejected.
-
-// Query whether a certificate needs to be blocked according to the hardcoded
-// CRLSet. Callers need to provide the sha256 of the Subject Public Key Info
-// (SPKI) of the certificate.
-bool IsCertificateBlocked(ConstByteSpan certificate);
-
-// Query whether a certificate is revoked by its issuer according to the
-// hardcoded CRLSet. Callers need to provide the sha256 of the SPKI of the
-// issuer (in |issuer|) and the serial number (as byte sequences) of the
-// target certificate to query.
-bool IsCertificateRevoked(ConstByteSpan issuer, ConstByteSpan serial);
-
-}  // namespace pw::tls_client::crlset
diff --git a/pw_tls_client/public/pw_tls_client/entropy.h b/pw_tls_client/public/pw_tls_client/entropy.h
deleted file mode 100644
index 3001b7e..0000000
--- a/pw_tls_client/public/pw_tls_client/entropy.h
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_bytes/span.h"
-#include "pw_status/status.h"
-
-namespace pw::tls_client {
-// Fill |dest| with fresh, full-entropy random bytes upon each call.
-// |dest| must contain full entropy. Backend should take care of the possible
-// conditioning in its implementation.
-Status GetRandomBytes(ByteSpan dest);
-
-// An overloaded variant for accomodating C API interfaces, i.e. mbed TLS.
-inline Status GetRandomBytes(std::span<unsigned char> dest) {
-  return GetRandomBytes(std::as_writable_bytes(dest));
-}
-}  // namespace pw::tls_client
diff --git a/pw_tls_client/public/pw_tls_client/options.h b/pw_tls_client/public/pw_tls_client/options.h
deleted file mode 100644
index db087fe..0000000
--- a/pw_tls_client/public/pw_tls_client/options.h
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_assert/assert.h"
-#include "pw_assert/check.h"
-#include "pw_stream/stream.h"
-#include "pw_string/util.h"
-
-namespace pw::tls_client {
-
-class SessionOptions {
- public:
-  // Sets the TLS server name. This is typically a domain name (e.g.
-  // www.google.com) used to differentiate any other virtual domain names
-  // resident on the same physical server. The option is used as the Server
-  // Name Indication(SNI) extension during TLS handshake.
-  //
-  // Callers need to ensure that the memory backing |server_name| is valid until
-  // being passed to Session::Create(), where backend has a chance to load or
-  // make a copy.
-  constexpr SessionOptions& set_server_name(std::string_view server_name) {
-    server_name_ = server_name;
-    return *this;
-  }
-
-  // Set the underlying transport for the TLS connection. The transport is
-  // provided through an instance of stream::ReaderWriter. Callers should
-  // guarantee that the transport object outlives the Session instance to be
-  // built.
-  constexpr SessionOptions& set_transport(stream::ReaderWriter& transport) {
-    transport_ = &transport;
-    return *this;
-  }
-
-  constexpr pw::stream::ReaderWriter* transport() const { return transport_; }
-
-  constexpr std::string_view server_name() const { return server_name_; }
-
- private:
-  std::string_view server_name_;
-  pw::stream::ReaderWriter* transport_ = nullptr;
-
-  // TODO(zyecheng): Expand the list as necessary to cover aspects such as
-  // certificate verification/revocation check policies.
-};
-
-}  // namespace pw::tls_client
diff --git a/pw_tls_client/public/pw_tls_client/session.h b/pw_tls_client/public/pw_tls_client/session.h
deleted file mode 100644
index 3840453..0000000
--- a/pw_tls_client/public/pw_tls_client/session.h
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_stream/stream.h"
-#include "pw_tls_client/options.h"
-#include "pw_tls_client/status.h"
-
-// The backend shall provide the following header that implements a
-// backend::SessionImplementation type.
-#include "pw_tls_client_backends/backend_types.h"
-
-namespace pw::tls_client {
-
-// Session provides APIs for performing TLS communication.
-class Session : public stream::NonSeekableReaderWriter {
- public:
-  Session() = delete;
-
-  Session(const Session&) = delete;
-  Session& operator=(const Session&) = delete;
-
-  // Resources allocated during Session::Create() will be released in the
-  // destructor. For example, backend may choose to allocate Session from a pool
-  // during Session::Create() and returns it in the destructor.
-  //
-  // Close() will be called if the Session is open. Since Close() returns a
-  // pw::Status which cannot be forwarded from a destructor for callers to
-  // check. Backend shall assert check that it is OkStatus().
-  virtual ~Session();
-
-  // Starts a TLS connection. The backend performs TLS handshaking and
-  // certificate verification/revocation/expiration check.
-  //
-  // Calling Open() on an already opened Session returns error.
-  Status Open();
-
-  // Closes a TLS connection. The backend sends shutdown notification to the
-  // server. Note that the shutdown notification is part of the TLS protocol.
-  // Nothing is done to the underlying transport used to create this Session.
-  // The closing of the underlying transport is managed by the users of this
-  // class. Calling Close() on an unopened Session returns error.
-  Status Close();
-
-  // If Open()/Close()/Read()/Write() fails, the method returns a more detailed
-  // code indicating the last error. See definition of TLSStatus in status.h.
-  // The backend is responsible for mapping TLS library errors into the ones
-  // defined in TLSStatus.
-  TLSStatus GetLastTLSStatus();
-
-  // Factory method for creating an instance of Session according to the given
-  // options. Backend allocates and initializes library specific data structures
-  // (implmented in |session_impl_|) for operating as a TLS client.
-  //
-  // The client remains inactive until the call of Open().
-  static Result<Session*> Create(const SessionOptions& option);
-
- private:
-  // A Session instance should only be created from Create().
-  Session(const SessionOptions& option);
-
-  StatusWithSize DoRead(ByteSpan dest) override;
-  Status DoWrite(ConstByteSpan data) override;
-
-  // An opaque object up to the backend to implement.
-  backend::SessionImplementation session_impl_;
-};
-
-}  // namespace pw::tls_client
diff --git a/pw_tls_client/public/pw_tls_client/status.h b/pw_tls_client/public/pw_tls_client/status.h
deleted file mode 100644
index fd852e1..0000000
--- a/pw_tls_client/public/pw_tls_client/status.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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
-
-namespace pw::tls_client {
-
-// The list of TLS client status.
-enum class TLSStatus {
-  kOk,
-  kUnknownError,
-  kEntropySourceFailed,
-  // TODO(zyecheng): Expand the list as necessary when core TLS logic is being
-  // implemented. This may include certificate verification error, expiration
-  // check error, revocation check error etc.
-};
-
-}  // namespace pw::tls_client
diff --git a/pw_tls_client/public/pw_tls_client/test/test_server.h b/pw_tls_client/public/pw_tls_client/test/test_server.h
deleted file mode 100644
index 3724631..0000000
--- a/pw_tls_client/public/pw_tls_client/test/test_server.h
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <openssl/bio.h>
-#include <openssl/pem.h>
-#include <openssl/ssl.h>
-
-#include "pw_bytes/span.h"
-#include "pw_result/result.h"
-#include "pw_status/status.h"
-#include "pw_stream/stream.h"
-
-namespace pw::tls_client::test {
-
-class FixedSizeFIFOBuffer : public stream::NonSeekableReaderWriter {
- public:
-  FixedSizeFIFOBuffer() = delete;
-  FixedSizeFIFOBuffer(const FixedSizeFIFOBuffer&) = delete;
-  FixedSizeFIFOBuffer& operator=(const FixedSizeFIFOBuffer&) = delete;
-
-  FixedSizeFIFOBuffer(ByteSpan buffer) : buffer_(buffer) {}
-  void clear() { current_size_ = 0; }
-
- private:
-  StatusWithSize DoRead(ByteSpan dest) override;
-  Status DoWrite(ConstByteSpan data) override;
-  ByteSpan buffer_;
-  size_t current_size_ = 0;
-};
-
-// Writing to the server is equivalent to sending data to the server. Server
-// will be invoked to process the data when being written. The write does
-// not return until the server completes processing it.
-// Reading from the server is equivalent to receiving data from the server.
-//
-// The server accepts is only for one client and echo messages it sends.
-class InMemoryTestServer : public stream::NonSeekableReaderWriter {
- public:
-  InMemoryTestServer() = delete;
-  InMemoryTestServer(const InMemoryTestServer&) = delete;
-  InMemoryTestServer& operator=(const InMemoryTestServer&) = delete;
-
-  // `input_buffer` is for storing raw data sent from the client.
-  // `output_buffer` is for storing raw data server prepare and to be sent
-  // to the client.
-  //
-  // The required size of the buffer depends on the payload and needs to be
-  // determined by the users based on use cases.
-  InMemoryTestServer(ByteSpan input_buffer, ByteSpan output_buffer);
-
-  // Initialize a test server with a private key, server certificate, and
-  // CA chains (all DER format)
-  Status Initialize(ConstByteSpan key,
-                    ConstByteSpan cert,
-                    std::span<const ConstByteSpan> chains);
-
-  // Is handshake completed.
-  bool SessionEstablished() { return is_handshake_done_; }
-
-  // Returns whether a shutdown request has been received from the client.
-  bool ClientShutdownReceived();
-
-  Status GetLastBioStatus() { return last_bio_status_; }
-
- private:
-  bssl::UniquePtr<SSL_CTX> ctx_;
-  bssl::UniquePtr<SSL> ssl_;
-  bool is_handshake_done_ = false;
-
-  // Buffer for storing data sent from the client.
-  FixedSizeFIFOBuffer input_buffer_;
-
-  // Buffer for storing data prepared by the server to send to the client.
-  FixedSizeFIFOBuffer output_buffer_;
-
-  // Store the last status of BIO operation (in BioRead() and BioWrite());
-  Status last_bio_status_ = OkStatus();
-
-  // Process the data written to the server as much as possible.
-  // If server is in handshake process, it processes handshake and prepares data
-  // to send to the server . If server is in application data exchange
-  // phase, it decrypts data and echoes back to the client. Client can retrieve
-  // the message by reading from the server.
-  Status ProcessPackets();
-
-  // Methods for loading private key, certificate, and intermediate CA chain.
-  Status LoadPrivateKey(ConstByteSpan key);
-  Status LoadCertificate(ConstByteSpan cert);
-  Status LoadCAChain(std::span<const ConstByteSpan> chains);
-
-  // Methods for providing BIO interfaces.
-  static int BioRead(BIO* bio, char* out, int output_length);
-  static int BioWrite(BIO* bio, const char* in, int input_length);
-
-  StatusWithSize DoRead(ByteSpan dest) override;
-  Status DoWrite(ConstByteSpan data) override;
-};
-
-// A helper function to parse a DER format certificate.
-pw::Result<X509*> ParseDerCertificate(pw::ConstByteSpan cert);
-
-}  // namespace pw::tls_client::test
diff --git a/pw_tls_client/py/BUILD.gn b/pw_tls_client/py/BUILD.gn
deleted file mode 100644
index 6b59a08..0000000
--- a/pw_tls_client/py/BUILD.gn
+++ /dev/null
@@ -1,29 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-import("$dir_pw_build/python.gni")
-
-pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
-  sources = [
-    "pw_tls_client/__init__.py",
-    "pw_tls_client/generate_test_data.py",
-  ]
-  pylintrc = "$dir_pigweed/.pylintrc"
-}
diff --git a/pw_tls_client/py/pw_tls_client/__init__.py b/pw_tls_client/py/pw_tls_client/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/pw_tls_client/py/pw_tls_client/__init__.py
+++ /dev/null
diff --git a/pw_tls_client/py/pw_tls_client/generate_test_data.py b/pw_tls_client/py/pw_tls_client/generate_test_data.py
deleted file mode 100644
index 291278b..0000000
--- a/pw_tls_client/py/pw_tls_client/generate_test_data.py
+++ /dev/null
@@ -1,280 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Generate test data
-
-Generate data needed for unit tests, i.e. certificates, keys, and CRLSet.
-"""
-
-import argparse
-import subprocess
-import sys
-from datetime import datetime, timedelta
-from typing import List, Tuple
-
-from cryptography import x509
-from cryptography.hazmat.primitives import hashes
-from cryptography.hazmat.primitives import serialization
-from cryptography.hazmat.primitives.asymmetric import rsa
-from cryptography.x509.oid import NameOID
-
-CERTS_AND_KEYS_HEADER = """// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_bytes/span.h"
-
-"""
-
-
-class Subject:
-    """A subject wraps a name, private key and extensions for issuers
-    to issue its certificate"""
-    def __init__(self, name: str, extensions: List[Tuple[x509.ExtensionType,
-                                                         bool]]):
-        self._subject_name = x509.Name([
-            x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
-            x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"California"),
-            x509.NameAttribute(NameOID.LOCALITY_NAME, u"Mountain View"),
-            x509.NameAttribute(NameOID.ORGANIZATION_NAME, name),
-            x509.NameAttribute(NameOID.COMMON_NAME, u"Google-Pigweed"),
-        ])
-        self._private_key = rsa.generate_private_key(public_exponent=65537,
-                                                     key_size=2048)
-        self._extensions = extensions
-
-    def subject_name(self) -> x509.Name:
-        """Returns the subject name"""
-        return self._subject_name
-
-    def public_key(self) -> rsa.RSAPublicKey:
-        """Returns the public key of this subject"""
-        return self._private_key.public_key()
-
-    def private_key(self) -> rsa.RSAPrivateKey:
-        """Returns the private key of this subject"""
-        return self._private_key
-
-    def extensions(self) -> List[Tuple[x509.ExtensionType, bool]]:
-        """Returns the requested extensions for issuer"""
-        return self._extensions
-
-
-class CA(Subject):
-    """A CA/Sub-ca that issues certificates"""
-    def __init__(self, *args, **kwargs):
-        ext = [(x509.BasicConstraints(True, None), True),
-               (x509.KeyUsage(
-                   digital_signature=False,
-                   content_commitment=False,
-                   key_encipherment=False,
-                   data_encipherment=False,
-                   key_agreement=False,
-                   crl_sign=False,
-                   encipher_only=False,
-                   decipher_only=False,
-                   key_cert_sign=True,
-               ), True)]
-        super().__init__(*args, extensions=ext, **kwargs)
-
-    def sign(self, subject: Subject, not_before: datetime,
-             not_after: datetime) -> x509.Certificate:
-        """Issues a certificate for another CA/Sub-ca/Server"""
-        builder = x509.CertificateBuilder()
-
-        # Subject name is the target's subject name
-        builder = builder.subject_name(subject.subject_name())
-
-        # Issuer name is this CA/sub-ca's subject name
-        builder = builder.issuer_name(self._subject_name)
-
-        # Public key is the target's public key.
-        builder = builder.public_key(subject.public_key())
-
-        # Validity period.
-        builder = builder.not_valid_before(not_before).not_valid_after(
-            not_after)
-
-        # Uses a random serial number
-        builder = builder.serial_number(x509.random_serial_number())
-
-        # Add extensions
-        for extension, critical in subject.extensions():
-            builder = builder.add_extension(extension, critical)
-
-        # Sign and returns the certificate.
-        return builder.sign(self._private_key, hashes.SHA256())
-
-    def self_sign(self, not_before: datetime,
-                  not_after: datetime) -> x509.Certificate:
-        """Issues a self sign certificate"""
-        return self.sign(self, not_before, not_after)
-
-
-class Server(Subject):
-    """The end-entity server"""
-    def __init__(self, *args, **kwargs):
-        ext = [
-            (x509.BasicConstraints(False, None), True),
-            (x509.KeyUsage(
-                digital_signature=True,
-                content_commitment=False,
-                key_encipherment=False,
-                data_encipherment=False,
-                key_agreement=False,
-                crl_sign=False,
-                encipher_only=False,
-                decipher_only=False,
-                key_cert_sign=False,
-            ), True),
-            (x509.ExtendedKeyUsage([x509.ExtendedKeyUsageOID.SERVER_AUTH]),
-             True),
-        ]
-        super().__init__(*args, extensions=ext, **kwargs)
-
-
-def c_escaped_string(data: bytes):
-    """Generates a C byte string representation for a byte array
-
-    For example, given a byte sequence of [0x12, 0x34, 0x56]. The function
-    generates the following byte string code:
-
-            {"\x12\x34\x56", 3}
-    """
-    body = ''.join([f'\\x{b:02x}' for b in data])
-    return f'{{\"{body}\", {len(data)}}}'
-
-
-def byte_array_declaration(data: bytes, name: str) -> str:
-    """Generates a ConstByteSpan declaration for a byte array"""
-    type_name = '[[maybe_unused]] const pw::ConstByteSpan'
-    array_body = f'std::as_bytes(std::span{c_escaped_string(data)})'
-    return f'{type_name} {name} = {array_body};'
-
-
-class Codegen:
-    """Base helper class for code generation"""
-    def generate_code(self) -> str:
-        """Generates C++ code for this object"""
-
-
-class PrivateKeyGen(Codegen):
-    """Codegen class for a private key"""
-    def __init__(self, key: rsa.RSAPrivateKey, name: str):
-        self._key = key
-        self._name = name
-
-    def generate_code(self) -> str:
-        """Code generation"""
-        return byte_array_declaration(
-            self._key.private_bytes(
-                serialization.Encoding.DER,
-                serialization.PrivateFormat.TraditionalOpenSSL,
-                serialization.NoEncryption()), self._name)
-
-
-class CertificateGen(Codegen):
-    """Codegen class for a single certificate"""
-    def __init__(self, cert: x509.Certificate, name: str):
-        self._cert = cert
-        self._name = name
-
-    def generate_code(self) -> str:
-        """Code generation"""
-        return byte_array_declaration(
-            self._cert.public_bytes(serialization.Encoding.DER), self._name)
-
-
-def generate_test_data() -> str:
-    """Generates test data"""
-    subjects: List[Codegen] = []
-
-    # Working valid period.
-    # Start from yesterday, to make sure we are in the valid period.
-    not_before = datetime.utcnow() - timedelta(days=1)
-    # Valid for 1 year.
-    not_after = not_before + timedelta(days=365)
-
-    # Generate a root-A CA certificates
-    root_a = CA("root-A")
-    subjects.append(
-        CertificateGen(root_a.self_sign(not_before, not_after), "kRootACert"))
-
-    # Generate a sub CA certificate signed by root-A.
-    sub = CA("sub")
-    subjects.append(
-        CertificateGen(root_a.sign(sub, not_before, not_after), "kSubCACert"))
-
-    # Generate a valid server certificate signed by sub
-    server = Server("server")
-    subjects.append(
-        CertificateGen(sub.sign(server, not_before, not_after), "kServerCert"))
-    subjects.append(PrivateKeyGen(server.private_key(), "kServerKey"))
-
-    root_b = CA("root-B")
-    subjects.append(
-        CertificateGen(root_b.self_sign(not_before, not_after), "kRootBCert"))
-
-    code = 'namespace {\n\n'
-    for subject in subjects:
-        code += subject.generate_code() + '\n\n'
-    code += '}\n'
-
-    return code
-
-
-def clang_format(file):
-    subprocess.run([
-        "clang-format",
-        "-i",
-        file,
-    ], check=True)
-
-
-def parse_args():
-    """Setup argparse."""
-    parser = argparse.ArgumentParser()
-    parser.add_argument(
-        "certs_and_keys_header",
-        help="output header file for test certificates and keys")
-    return parser.parse_args()
-
-
-def main() -> int:
-    """Main"""
-    args = parse_args()
-
-    certs_and_keys = generate_test_data()
-
-    with open(args.certs_and_keys_header, 'w') as header:
-        header.write(CERTS_AND_KEYS_HEADER)
-        header.write(certs_and_keys)
-
-    clang_format(args.certs_and_keys_header)
-    return 0
-
-
-if __name__ == "__main__":
-    sys.exit(main())
diff --git a/pw_tls_client/py/pyproject.toml b/pw_tls_client/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/pw_tls_client/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/pw_tls_client/py/setup.cfg b/pw_tls_client/py/setup.cfg
deleted file mode 100644
index 6a52cae..0000000
--- a/pw_tls_client/py/setup.cfg
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pw_tls_client
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = pw_tls_client python package
-
-[options]
-packages = find:
-zip_safe = False
-install_requires = cryptography
-
-[options.package_data]
-pw_tls_client = py.typed
diff --git a/pw_tls_client/py/setup.py b/pw_tls_client/py/setup.py
deleted file mode 100644
index 15092a1..0000000
--- a/pw_tls_client/py/setup.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Python scripts for pw_tls_client"""
-
-import setuptools  # type: ignore
-
-setuptools.setup()  # Package definition in setup.cfg
diff --git a/pw_tls_client/test_server.cc b/pw_tls_client/test_server.cc
deleted file mode 100644
index ecbf489..0000000
--- a/pw_tls_client/test_server.cc
+++ /dev/null
@@ -1,296 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_tls_client/test/test_server.h"
-
-#include <openssl/bio.h>
-#include <openssl/pem.h>
-#include <openssl/ssl.h>
-
-#include <cstring>
-
-#include "pw_log/log.h"
-#include "pw_result/result.h"
-#include "pw_status/status.h"
-#include "pw_stream/stream.h"
-
-namespace {
-
-int TestBioNew(BIO* bio) {
-  bio->init = 1;
-  return 1;
-}
-
-long TestBioCtrl(BIO*, int, long, void*) { return 1; }
-
-int TestBioFree(BIO*) { return 1; }
-
-}  // namespace
-
-namespace pw::tls_client::test {
-
-Result<X509*> ParseDerCertificate(ConstByteSpan cert) {
-  BIO* bio = BIO_new_mem_buf(cert.data(), cert.size());
-  if (!bio) {
-    return Status::Internal();
-  }
-
-  X509* x509 = d2i_X509_bio(bio, nullptr);
-  if (x509 == nullptr) {
-    PW_LOG_DEBUG("Failed to parse x509");
-    BIO_free(bio);
-    return Status::Internal();
-  }
-
-  BIO_free(bio);
-  return x509;
-}
-
-StatusWithSize FixedSizeFIFOBuffer::DoRead(ByteSpan dest) {
-  size_t to_read = std::min(current_size_, dest.size());
-  memcpy(dest.data(), buffer_.data(), to_read);
-  // Push out the read data.
-  memmove(buffer_.data(), buffer_.data() + to_read, current_size_ - to_read);
-  current_size_ -= to_read;
-  return StatusWithSize(to_read);
-}
-
-Status FixedSizeFIFOBuffer::DoWrite(ConstByteSpan data) {
-  if (data.size() + current_size_ > buffer_.size()) {
-    PW_LOG_ERROR("Write overflow. Buffer is too small.");
-    return Status::ResourceExhausted();
-  }
-
-  // Append incoming data at the end.
-  memcpy(buffer_.begin() + current_size_, data.data(), data.size());
-  current_size_ += data.size();
-  return OkStatus();
-}
-
-InMemoryTestServer::InMemoryTestServer(ByteSpan input_buffer,
-                                       ByteSpan output_buffer)
-    : input_buffer_(input_buffer), output_buffer_(output_buffer) {}
-
-int InMemoryTestServer::BioRead(BIO* bio, char* out, int output_length) {
-  auto server = static_cast<InMemoryTestServer*>(bio->ptr);
-  auto read = server->input_buffer_.Read(std::as_writable_bytes(
-      std::span{out, static_cast<size_t>(output_length)}));
-  if (!read.ok()) {
-    server->last_bio_status_ = read.status();
-    return -1;
-  }
-  if (read.value().empty()) {
-    BIO_set_retry_read(bio);
-    return -1;
-  }
-  return static_cast<int>(read.value().size());
-}
-
-int InMemoryTestServer::BioWrite(BIO* bio,
-                                 const char* input,
-                                 int input_length) {
-  auto server = static_cast<InMemoryTestServer*>(bio->ptr);
-  if (auto status = server->output_buffer_.Write(
-          std::as_bytes(std::span{input, static_cast<size_t>(input_length)}));
-      !status.ok()) {
-    server->last_bio_status_ = status;
-    return -1;
-  }
-
-  return input_length;
-}
-
-Status InMemoryTestServer::Initialize(ConstByteSpan key,
-                                      ConstByteSpan cert,
-                                      std::span<const ConstByteSpan> chains) {
-  input_buffer_.clear();
-  output_buffer_.clear();
-  is_handshake_done_ = false;
-
-  ctx_ = bssl::UniquePtr<SSL_CTX>(SSL_CTX_new(TLS_method()));
-  if (!ctx_) {
-    return Status::Internal();
-  }
-
-  if (auto status = LoadPrivateKey(key); !status.ok()) {
-    return status;
-  }
-
-  if (auto status = LoadCertificate(cert); !status.ok()) {
-    return status;
-  }
-
-  if (auto status = LoadCAChain(chains); !status.ok()) {
-    return status;
-  }
-
-  ssl_ = bssl::UniquePtr<SSL>(SSL_new(ctx_.get()));
-  if (!ssl_) {
-    return Status::Internal();
-  }
-
-  static const BIO_METHOD bio_method = {
-      BIO_TYPE_MEM,
-      "bio boringssl test server",
-      InMemoryTestServer::BioWrite,
-      InMemoryTestServer::BioRead,
-      nullptr,      // puts
-      nullptr,      // gets
-      TestBioCtrl,  // ctrl
-      TestBioNew,
-      TestBioFree,  // free
-      nullptr       // callback_ctrl
-  };
-
-  BIO* bio = BIO_new(&bio_method);
-  if (!bio) {
-    return Status::Internal();
-  }
-
-  bio->ptr = this;
-
-  SSL_set_bio(ssl_.get(), bio, bio);
-  return OkStatus();
-}
-
-Status InMemoryTestServer::LoadPrivateKey(ConstByteSpan key) {
-  BIO* bio = BIO_new_mem_buf(key.data(), key.size());
-  if (!bio) {
-    return Status::Internal();
-  }
-
-  // Requires PEM format.
-  EVP_PKEY* pkey = d2i_PrivateKey_bio(bio, nullptr);
-  int ret = SSL_CTX_use_PrivateKey(ctx_.get(), pkey);
-  if (ret != 1) {
-    BIO_free(bio);
-    PW_LOG_DEBUG("Failed to load private key for test server");
-    return Status::Internal();
-  }
-
-  EVP_PKEY_free(pkey);
-  BIO_free(bio);
-  return OkStatus();
-}
-
-Status InMemoryTestServer::LoadCertificate(ConstByteSpan cert) {
-  auto res = ParseDerCertificate(cert);
-  if (!res.ok()) {
-    return res.status();
-  }
-
-  int ret = SSL_CTX_use_certificate(ctx_.get(), res.value());
-  if (ret != 1) {
-    X509_free(res.value());
-    PW_LOG_DEBUG("Failed to user server certificate %d", ret);
-    return Status::Internal();
-  }
-
-  X509_free(res.value());
-  return OkStatus();
-}
-
-Status InMemoryTestServer::LoadCAChain(std::span<const ConstByteSpan> chains) {
-  for (auto cert : chains) {
-    auto res = ParseDerCertificate(cert);
-    if (!res.ok()) {
-      return res.status();
-    }
-
-    int ret = SSL_CTX_add0_chain_cert(ctx_.get(), res.value());
-    if (ret != 1) {
-      X509_free(res.value());
-      PW_LOG_DEBUG("Failed to add certificate to chain %d", ret);
-      return Status::Internal();
-    }
-  }
-  return OkStatus();
-}
-
-bool InMemoryTestServer::ClientShutdownReceived() {
-  return SSL_get_shutdown(ssl_.get()) & SSL_RECEIVED_SHUTDOWN;
-}
-
-Status InMemoryTestServer::ProcessPackets() {
-  // Process handshake if it has not been completed.
-  if (!is_handshake_done_) {
-    int ret = SSL_accept(ssl_.get());
-    if (ret != 1) {
-      int ssl_err = SSL_get_error(ssl_.get(), ret);
-      if (ssl_err != SSL_ERROR_WANT_READ) {
-        PW_LOG_DEBUG("Error while server accepting, %d, %d", ssl_err, ret);
-        return Status::Internal();
-      }
-    } else {
-      // handshake complete.
-      is_handshake_done_ = true;
-    }
-  }
-
-  // Hanshake may be completed above and there may already be application data.
-  if (is_handshake_done_) {
-    static std::array<std::byte, 1024> buf;
-    while (true) {
-      int ssl_ret = SSL_read(ssl_.get(), buf.data(), buf.size());
-
-      if (ssl_ret == 0) {
-        // All input has been processed.
-        break;
-      }
-
-      if (ssl_ret < 0) {
-        // An error may have occured.
-        int ssl_err = SSL_get_error(ssl_.get(), ssl_ret);
-        if (ssl_err == SSL_ERROR_WANT_READ) {
-          // Need to wait for client to finish sending data. Non-blocking
-          // return.
-          break;
-        }
-
-        PW_LOG_DEBUG("Error while server reading");
-        return Status::Internal();
-      }
-
-      // Echo the message
-      int write_status = SSL_write(ssl_.get(), buf.data(), ssl_ret);
-      if (write_status <= 0) {
-        PW_LOG_DEBUG("Failed to write for test server");
-        return Status::Internal();
-      }
-    }
-  }
-
-  return OkStatus();
-}
-
-StatusWithSize InMemoryTestServer::DoRead(ByteSpan dest) {
-  auto res = output_buffer_.Read(dest);
-  if (!res.ok()) {
-    return StatusWithSize(res.status(), 0);
-  }
-
-  return StatusWithSize(res.value().size());
-}
-
-Status InMemoryTestServer::DoWrite(ConstByteSpan data) {
-  auto status = input_buffer_.Write(data);
-  if (!status.ok()) {
-    return status;
-  }
-
-  // Invoke the server to process the data.
-  return ProcessPackets();
-}
-
-}  // namespace pw::tls_client::test
diff --git a/pw_tls_client/test_server_test.cc b/pw_tls_client/test_server_test.cc
deleted file mode 100644
index 19b1efe..0000000
--- a/pw_tls_client/test_server_test.cc
+++ /dev/null
@@ -1,158 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_tls_client/test/test_server.h"
-
-#include <span>
-#include <string>
-
-#include "gtest/gtest.h"
-
-// The following header contains a set of test certificates and keys.
-// It is generated by
-// third_party/boringssl/py/boringssl/generate_test_data.py.
-#include "test_certs_and_keys.h"
-
-#define ASSERT_OK(expr) ASSERT_EQ(pw::OkStatus(), expr)
-
-namespace pw::tls_client::test {
-namespace {
-
-int TestClientBioRead(BIO* bio, char* out, int outl) {
-  auto read_writer = static_cast<stream::ReaderWriter*>(bio->ptr);
-  auto res = read_writer->Read(out, outl);
-  if (!res.ok()) {
-    return -1;
-  }
-  if (res.value().empty()) {
-    BIO_set_retry_read(bio);
-    return -1;
-  }
-  return res.value().size();
-}
-
-int TestClientBioWrite(BIO* bio, const char* in, int inl) {
-  auto read_writer = static_cast<stream::ReaderWriter*>(bio->ptr);
-  auto res = read_writer->Write(in, inl);
-  if (!res.ok()) {
-    return -1;
-  }
-  return inl;
-}
-
-int TestClientBioNew(BIO* bio) {
-  bio->init = 1;
-  return 1;
-}
-
-long TestClientBioCtrl(BIO*, int, long, void*) { return 1; }
-
-int TestClientBioFree(BIO*) { return 1; }
-
-const BIO_METHOD bio_method = {
-    BIO_TYPE_MEM,
-    "bio test server test",
-    TestClientBioWrite,
-    TestClientBioRead,
-    nullptr,
-    nullptr,
-    TestClientBioCtrl,
-    TestClientBioNew,
-    TestClientBioFree,
-    nullptr,
-};
-
-// Server needs to send certificate. Thus the send buffer needs to be bigger.
-std::array<std::byte, 4096> server_send_buffer;
-std::array<std::byte, 512> server_receive_buffer;
-
-// Create a raw BoringSSL client and load test trust anchors.
-void CreateSSLClient(bssl::UniquePtr<SSL_CTX>* ctx,
-                     bssl::UniquePtr<SSL>* client,
-                     stream::ReaderWriter* read_writer) {
-  *ctx = bssl::UniquePtr<SSL_CTX>(SSL_CTX_new(TLS_method()));
-  ASSERT_NE(*ctx, nullptr);
-  *client = bssl::UniquePtr<SSL>(SSL_new(ctx->get()));
-  ASSERT_NE(*client, nullptr);
-  BIO* bio = BIO_new(&bio_method);
-  ASSERT_NE(bio, nullptr);
-
-  // Load trust anchors to client
-  auto store = SSL_CTX_get_cert_store(ctx->get());
-  X509_VERIFY_PARAM_clear_flags(store->param, X509_V_FLAG_USE_CHECK_TIME);
-  const pw::ConstByteSpan kTrustAnchors[] = {kRootACert, kRootBCert};
-  for (auto cert : kTrustAnchors) {
-    auto res = ParseDerCertificate(cert);
-    ASSERT_OK(res.status());
-    ASSERT_EQ(X509_STORE_add_cert(store, res.value()), 1);
-    X509_free(res.value());
-  }
-  bio->ptr = read_writer;
-  SSL_set_bio(client->get(), bio, bio);
-}
-
-}  // namespace
-
-TEST(InMemoryTestServer, NormalConnectionSucceed) {
-  InMemoryTestServer server(server_receive_buffer, server_send_buffer);
-  const ConstByteSpan kIntermediates[] = {kSubCACert};
-  ASSERT_OK(server.Initialize(kServerKey, kServerCert, kIntermediates));
-
-  // Create a raw BoringSSL client
-  bssl::UniquePtr<SSL_CTX> client_ctx;
-  bssl::UniquePtr<SSL> ssl_client;
-  CreateSSLClient(&client_ctx, &ssl_client, &server);
-
-  // Handshake should be OK
-  ASSERT_EQ(SSL_connect(ssl_client.get()), 1);
-  ASSERT_TRUE(server.SessionEstablished());
-
-  // Client should pass certificate verification.
-  ASSERT_EQ(SSL_get_verify_result(ssl_client.get()), 0);
-
-  // Send some data to server
-  const char send_expected[] = "hello";
-  int send_len =
-      SSL_write(ssl_client.get(), send_expected, sizeof(send_expected));
-  ASSERT_EQ(static_cast<size_t>(send_len), sizeof(send_expected));
-
-  char receive_actual[sizeof(send_expected) + 1] = {0};
-  int read_ret =
-      SSL_read(ssl_client.get(), receive_actual, sizeof(receive_actual));
-  ASSERT_EQ(static_cast<size_t>(read_ret), sizeof(send_expected));
-  ASSERT_STREQ(send_expected, receive_actual);
-
-  // Shutdown
-  EXPECT_FALSE(server.ClientShutdownReceived());
-  ASSERT_NE(SSL_shutdown(ssl_client.get()), -1);
-  ASSERT_TRUE(server.ClientShutdownReceived());
-}
-
-TEST(InMemoryTestServer, BufferTooSmallErrorsOut) {
-  std::array<std::byte, 1> insufficient_buffer;
-  InMemoryTestServer server(server_receive_buffer, insufficient_buffer);
-  const ConstByteSpan kIntermediates[] = {kSubCACert};
-  ASSERT_OK(server.Initialize(kServerKey, kServerCert, kIntermediates));
-
-  // Create a raw BoringSSL client
-  bssl::UniquePtr<SSL_CTX> client_ctx;
-  bssl::UniquePtr<SSL> ssl_client;
-  CreateSSLClient(&client_ctx, &ssl_client, &server);
-
-  // Handshake should fail as server shouldn't have enough buffer
-  ASSERT_NE(SSL_connect(ssl_client.get()), 1);
-  ASSERT_EQ(server.GetLastBioStatus(), Status::ResourceExhausted());
-}
-
-}  // namespace pw::tls_client::test
diff --git a/pw_tls_client_boringssl/BUILD.bazel b/pw_tls_client_boringssl/BUILD.bazel
deleted file mode 100644
index 0e94f7d..0000000
--- a/pw_tls_client_boringssl/BUILD.bazel
+++ /dev/null
@@ -1,49 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_tls_client_boringssl",
-    srcs = ["tls_client_boringssl.cc"],
-    hdrs = [
-        "public/pw_tls_client_boringssl/backend_types.h",
-        "public_overrides/pw_tls_client_backends/backend_types.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    deps = [
-        "//pw_tls_client:pw_tls_client_facade",
-    ],
-)
-
-pw_cc_test(
-    name = "tls_boringssl_client_test",
-    srcs = [
-        "tls_client_boringssl_test.cc",
-    ],
-    deps = [
-        ":pw_tls_client_boringssl",
-    ],
-)
diff --git a/pw_tls_client_boringssl/BUILD.gn b/pw_tls_client_boringssl/BUILD.gn
deleted file mode 100644
index 7fb1624..0000000
--- a/pw_tls_client_boringssl/BUILD.gn
+++ /dev/null
@@ -1,59 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_third_party/boringssl/boringssl.gni")
-import("$dir_pw_third_party/chromium_verifier/chromium_verifier.gni")
-import("$dir_pw_tls_client/configs.gni")
-import("$dir_pw_unit_test/test.gni")
-
-config("backend_config") {
-  include_dirs = [
-    "public",
-    "public_overrides",
-  ]
-}
-
-pw_source_set("pw_tls_client_boringssl") {
-  public_configs = [ ":backend_config" ]
-  public = [
-    "public/pw_tls_client_boringssl/backend_types.h",
-    "public_overrides/pw_tls_client_backends/backend_types.h",
-  ]
-  sources = [ "tls_client_boringssl.cc" ]
-  public_deps = [
-    "$dir_pigweed/pw_tls_client:pw_tls_client.facade",
-    "$dir_pw_third_party/boringssl",
-  ]
-}
-
-pw_test("tls_client_boringssl_test") {
-  enable_if = dir_pw_third_party_boringssl != ""
-  deps = [
-    ":pw_tls_client_boringssl",
-    "$dir_pw_tls_client:test_data",
-  ]
-  sources = [ "tls_client_boringssl_test.cc" ]
-}
-
-pw_test_group("tests") {
-  tests = [ ":tls_client_boringssl_test" ]
-}
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
diff --git a/pw_tls_client_boringssl/OWNERS b/pw_tls_client_boringssl/OWNERS
deleted file mode 100644
index 121c6f5..0000000
--- a/pw_tls_client_boringssl/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-zyecheng@google.com
diff --git a/pw_tls_client_boringssl/docs.rst b/pw_tls_client_boringssl/docs.rst
deleted file mode 100644
index abb8dda..0000000
--- a/pw_tls_client_boringssl/docs.rst
+++ /dev/null
@@ -1,12 +0,0 @@
-.. _module-pw_tls_client_boringssl:
-
------------------------
-pw_tls_client_boringssl
------------------------
-
-This module implements the ``pw_tls_client`` facade with BoringSSL library.
-Please see docs of ``pw_tls_client`` for usage and setup.
-
-.. warning::
-  This module is under construction, not ready for use, and the documentation
-  is incomplete.
diff --git a/pw_tls_client_boringssl/public/pw_tls_client_boringssl/backend_types.h b/pw_tls_client_boringssl/public/pw_tls_client_boringssl/backend_types.h
deleted file mode 100644
index 23c102a..0000000
--- a/pw_tls_client_boringssl/public/pw_tls_client_boringssl/backend_types.h
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_tls_client/options.h"
-
-namespace pw::tls_client::backend {
-
-class SessionImplementation {
- public:
-  SessionImplementation(SessionOptions options);
-  ~SessionImplementation();
-};
-
-}  // namespace pw::tls_client::backend
diff --git a/pw_tls_client_boringssl/public_overrides/pw_tls_client_backends/backend_types.h b/pw_tls_client_boringssl/public_overrides/pw_tls_client_backends/backend_types.h
deleted file mode 100644
index 810e974..0000000
--- a/pw_tls_client_boringssl/public_overrides/pw_tls_client_backends/backend_types.h
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_tls_client_boringssl/backend_types.h"
diff --git a/pw_tls_client_boringssl/tls_client_boringssl.cc b/pw_tls_client_boringssl/tls_client_boringssl.cc
deleted file mode 100644
index 8d8ce80..0000000
--- a/pw_tls_client_boringssl/tls_client_boringssl.cc
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_tls_client/session.h"
-#include "pw_tls_client_boringssl/backend_types.h"
-
-namespace pw::tls_client {
-namespace backend {
-
-SessionImplementation::SessionImplementation(SessionOptions) {
-  // TODO(pwbug/421): To implement
-}
-
-SessionImplementation::~SessionImplementation() = default;
-
-}  // namespace backend
-
-Session::Session(const SessionOptions& options) : session_impl_(options) {
-  // TODO(pwbug/421): To implement
-}
-
-Session::~Session() = default;
-
-Result<Session*> Session::Create(const SessionOptions&) {
-  // TODO(pwbug/421): To implement
-  return PW_STATUS_UNIMPLEMENTED;
-}
-
-Status Session::Open() {
-  // TODO(pwbug/421): To implement
-  return PW_STATUS_UNIMPLEMENTED;
-}
-
-Status Session::Close() {
-  // TODO(pwbug/421): To implement
-  return PW_STATUS_UNIMPLEMENTED;
-}
-
-StatusWithSize Session::DoRead(ByteSpan) {
-  // TODO(pwbug/421): To implement
-  return StatusWithSize(PW_STATUS_UNIMPLEMENTED, 0);
-}
-
-Status Session::DoWrite(ConstByteSpan) {
-  // TODO(pwbug/421): To implement
-  return PW_STATUS_UNIMPLEMENTED;
-}
-
-TLSStatus Session::GetLastTLSStatus() { return TLSStatus::kUnknownError; }
-
-}  // namespace pw::tls_client
diff --git a/pw_tls_client_boringssl/tls_client_boringssl_test.cc b/pw_tls_client_boringssl/tls_client_boringssl_test.cc
deleted file mode 100644
index 0157d53..0000000
--- a/pw_tls_client_boringssl/tls_client_boringssl_test.cc
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "gtest/gtest.h"
-#include "pw_tls_client/session.h"
-
-// The following header contains a set of test certificates and keys.
-// It is generated by
-// third_party/boringssl/py/boringssl/generate_test_data.py.
-#include "test_certs_and_keys.h"
-
-namespace pw::tls_client {
-
-TEST(TLSClientBoringSSL, SessionCreationSucceeds) {
-  SessionOptions options;
-  auto res = Session::Create(options);
-  ASSERT_EQ(res.status(), PW_STATUS_UNIMPLEMENTED);
-}
-
-}  // namespace pw::tls_client
\ No newline at end of file
diff --git a/pw_tls_client_mbedtls/BUILD.bazel b/pw_tls_client_mbedtls/BUILD.bazel
deleted file mode 100644
index e376115..0000000
--- a/pw_tls_client_mbedtls/BUILD.bazel
+++ /dev/null
@@ -1,51 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-# TODO(pwbug/398): The recipe is under construction.
-
-pw_cc_library(
-    name = "pw_tls_client_mbedtls",
-    srcs = ["tls_client_mbedtls.cc"],
-    hdrs = [
-        "public/pw_tls_client_mbedtls/backend_types.h",
-        "public_overrides/pw_tls_client_backends/backend_types.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    deps = [
-        "//pw_tls_client:pw_tls_client_facade",
-    ],
-)
-
-pw_cc_test(
-    name = "tls_client_mbedtls_test",
-    srcs = [
-        "tls_client_mbedtls_test.cc",
-    ],
-    deps = [
-        ":pw_tls_client_mbedtls",
-    ],
-)
diff --git a/pw_tls_client_mbedtls/BUILD.gn b/pw_tls_client_mbedtls/BUILD.gn
deleted file mode 100644
index 261b85a..0000000
--- a/pw_tls_client_mbedtls/BUILD.gn
+++ /dev/null
@@ -1,63 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_third_party/mbedtls/mbedtls.gni")
-import("$dir_pw_tls_client/configs.gni")
-import("$dir_pw_unit_test/test.gni")
-
-config("public_includes") {
-  include_dirs = [ "public" ]
-}
-
-config("overrides") {
-  include_dirs = [ "public_overrides" ]
-}
-
-pw_source_set("pw_tls_client_mbedtls") {
-  public_configs = [
-    ":public_includes",
-    ":overrides",
-  ]
-  public = [
-    "public/pw_tls_client_mbedtls/backend_types.h",
-    "public_overrides/pw_tls_client_backends/backend_types.h",
-  ]
-  sources = [ "tls_client_mbedtls.cc" ]
-  public_deps = [
-    "$dir_pigweed/pw_tls_client:pw_tls_client.facade",
-    "$dir_pw_assert",
-    "$dir_pw_log",
-    "$dir_pw_stream",
-    "$dir_pw_third_party/mbedtls",
-    "$dir_pw_tls_client:tls_entropy",
-  ]
-}
-
-pw_test("tls_client_mbedtls_test") {
-  enable_if =
-      pw_tls_client_ENTROPY_BACKEND != "" && dir_pw_third_party_mbedtls != ""
-  deps = [ ":pw_tls_client_mbedtls" ]
-  sources = [ "tls_client_mbedtls_test.cc" ]
-}
-
-pw_test_group("tests") {
-  tests = [ ":tls_client_mbedtls_test" ]
-}
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
diff --git a/pw_tls_client_mbedtls/OWNERS b/pw_tls_client_mbedtls/OWNERS
deleted file mode 100644
index 121c6f5..0000000
--- a/pw_tls_client_mbedtls/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-zyecheng@google.com
diff --git a/pw_tls_client_mbedtls/docs.rst b/pw_tls_client_mbedtls/docs.rst
deleted file mode 100644
index 4dcabe4..0000000
--- a/pw_tls_client_mbedtls/docs.rst
+++ /dev/null
@@ -1,12 +0,0 @@
-.. _module-pw_tls_client_mbedtls:
-
-----------------------
-pw_tls_client_mbedtls
-----------------------
-
-This module implements the ``pw_tls_client`` facade with MbedTLS library.
-Please see docs of ``pw_tls_client`` for usage and setup.
-
-.. warning::
-  This module is under construction, not ready for use, and the documentation
-  is incomplete.
diff --git a/pw_tls_client_mbedtls/public/pw_tls_client_mbedtls/backend_types.h b/pw_tls_client_mbedtls/public/pw_tls_client_mbedtls/backend_types.h
deleted file mode 100644
index e3a2fd4..0000000
--- a/pw_tls_client_mbedtls/public/pw_tls_client_mbedtls/backend_types.h
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "mbedtls/certs.h"
-#include "mbedtls/ctr_drbg.h"
-#include "mbedtls/entropy.h"
-#include "mbedtls/error.h"
-#include "mbedtls/ssl.h"
-#include "pw_status/status.h"
-#include "pw_tls_client/options.h"
-
-namespace pw::tls_client::backend {
-class SessionImplementation {
- public:
-  SessionImplementation(SessionOptions options);
-  ~SessionImplementation();
-  Status Setup();
-  void SetTlsStatus(TLSStatus status) { tls_status_ = status; }
-  TLSStatus GetTlsStatus() { return tls_status_; }
-
-  // The method is for test only. When given a non-Ok status, it will override
-  // the status returned by entropy source pw::tls_client::GetRandomBytes();
-  static void SetEntropySourceStatus(Status status);
-
- private:
-  // mbedtls entropy
-  mbedtls_entropy_context entropy_ctx_;
-  mbedtls_ctr_drbg_context drbg_ctx_;
-
-  // SSL data structure
-  mbedtls_ssl_context ssl_ctx_;
-
-  // Configuration data structure
-  mbedtls_ssl_config ssl_config_;
-
-  // A copy of the option when creating the client.
-  SessionOptions session_options_;
-
-  TLSStatus tls_status_ = TLSStatus::kOk;
-
-  static int MbedTlsWrite(void* ctx, const uint8_t* buf, size_t len);
-  static int MbedTlsRead(void* ctx, unsigned char* buf, size_t len);
-  static int MbedTlsEntropySource(void* ctx,
-                                  unsigned char* out,
-                                  size_t len,
-                                  size_t* output_length);
-
-  static Status entropy_source_status_;
-};
-
-}  // namespace pw::tls_client::backend
diff --git a/pw_tls_client_mbedtls/public_overrides/pw_tls_client_backends/backend_types.h b/pw_tls_client_mbedtls/public_overrides/pw_tls_client_backends/backend_types.h
deleted file mode 100644
index 0481f1f..0000000
--- a/pw_tls_client_mbedtls/public_overrides/pw_tls_client_backends/backend_types.h
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_tls_client_mbedtls/backend_types.h"
diff --git a/pw_tls_client_mbedtls/tls_client_mbedtls.cc b/pw_tls_client_mbedtls/tls_client_mbedtls.cc
deleted file mode 100644
index dc01c22..0000000
--- a/pw_tls_client_mbedtls/tls_client_mbedtls.cc
+++ /dev/null
@@ -1,222 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "mbedtls/ssl.h"
-#include "pw_assert/check.h"
-#include "pw_log/log.h"
-#include "pw_tls_client/entropy.h"
-#include "pw_tls_client/session.h"
-#include "pw_tls_client_mbedtls/backend_types.h"
-
-namespace pw::tls_client {
-namespace backend {
-
-int SessionImplementation::MbedTlsWrite(void* ctx,
-                                        const uint8_t* buf,
-                                        size_t len) {
-  PW_CHECK_NOTNULL(ctx);
-  PW_CHECK_NOTNULL(buf);
-  auto writer =
-      static_cast<SessionImplementation*>(ctx)->session_options_.transport();
-  PW_CHECK_NOTNULL(writer);
-  return writer->Write(buf, len).ok() ? len : -1;
-}
-
-int SessionImplementation::MbedTlsRead(void* ctx,
-                                       unsigned char* buf,
-                                       size_t len) {
-  PW_CHECK_NOTNULL(ctx);
-  PW_CHECK_NOTNULL(buf);
-  auto reader =
-      static_cast<SessionImplementation*>(ctx)->session_options_.transport();
-  PW_CHECK_NOTNULL(reader);
-  auto res = reader->Read(buf, len);
-  if (!res.ok()) {
-    return -1;
-  }
-  return res.value().empty() ? MBEDTLS_ERR_SSL_WANT_READ : res.value().size();
-}
-
-Status SessionImplementation::entropy_source_status_ = OkStatus();
-
-void SessionImplementation::SetEntropySourceStatus(Status status) {
-  entropy_source_status_ = status;
-}
-
-// Entropy source callback
-int SessionImplementation::MbedTlsEntropySource(void* ctx,
-                                                unsigned char* out,
-                                                size_t len,
-                                                size_t* output_length) {
-  Status status;
-  if (entropy_source_status_ != OkStatus()) {
-    status = entropy_source_status_;
-  } else {
-    status = GetRandomBytes({out, len});
-  }
-
-  if (!status.ok()) {
-    PW_LOG_DEBUG("Failed to generate random bytes");
-    auto session_impl = static_cast<SessionImplementation*>(ctx);
-    session_impl->SetTlsStatus(pw::tls_client::TLSStatus::kEntropySourceFailed);
-    return MBEDTLS_ERR_ENTROPY_SOURCE_FAILED;
-  }
-  *output_length = len;
-  return 0;
-}
-
-SessionImplementation::SessionImplementation(SessionOptions options)
-    : session_options_(options) {
-  mbedtls_ssl_init(&ssl_ctx_);
-  mbedtls_ssl_config_init(&ssl_config_);
-  mbedtls_ctr_drbg_init(&drbg_ctx_);
-  mbedtls_entropy_init(&entropy_ctx_);
-}
-
-SessionImplementation::~SessionImplementation() {
-  mbedtls_ssl_free(&ssl_ctx_);
-  mbedtls_ssl_config_free(&ssl_config_);
-  mbedtls_ctr_drbg_free(&drbg_ctx_);
-  mbedtls_entropy_free(&entropy_ctx_);
-}
-
-Status SessionImplementation::Setup() {
-  int ret = 0;
-
-  // Set up default configuration.
-  ret = mbedtls_ssl_config_defaults(
-      &ssl_config_,
-      // Configured as client.
-      MBEDTLS_SSL_IS_CLIENT,
-      // Statndard TLS. The other option is MBEDTLS_SSL_TRANSPORT_DATAGRAM
-      // for DTLS, which we'll consider later.
-      MBEDTLS_SSL_TRANSPORT_STREAM,
-      // This option is used in all MbedTLS native examples.
-      // The other option is MBEDTLS_SSL_PRESET_SUITEB.
-      // However, there is no document/comment availalbe on what they do.
-      // Base on the source code, these options will restrict the version
-      // of TLS protocol. MBEDTLS_SSL_PRESET_SUITEB forces TLS 1.2.
-      // MBEDTLS_SSL_PRESET_DEFAULT is more relaxed. But since we
-      // define MBEDTLS_SSL_PROTO_TLS1_2 for all configs. There shouldn't be
-      // any difference.
-      MBEDTLS_SSL_PRESET_DEFAULT);
-  if (ret) {
-    return Status::Internal();
-  }
-
-  // Set up an entropy source.
-  ret = mbedtls_entropy_add_source(&entropy_ctx_,
-                                   MbedTlsEntropySource,
-                                   this,
-                                   1,
-                                   MBEDTLS_ENTROPY_SOURCE_STRONG);
-  if (ret) {
-    return Status::Internal();
-  }
-
-  // Set up drbg.
-  unsigned char personalized_bytes[] = "pw_tls_client";
-  ret = mbedtls_ctr_drbg_seed(&drbg_ctx_,
-                              mbedtls_entropy_func,
-                              &entropy_ctx_,
-                              personalized_bytes,
-                              sizeof(personalized_bytes));
-  if (ret) {
-    if (ret == MBEDTLS_ERR_CTR_DRBG_ENTROPY_SOURCE_FAILED) {
-      tls_status_ = TLSStatus::kEntropySourceFailed;
-    }
-    return Status::Internal();
-  }
-
-  // The API does not fail.
-  mbedtls_ssl_conf_rng(&ssl_config_, mbedtls_ctr_drbg_random, &drbg_ctx_);
-
-  // The API does not fail.
-  mbedtls_ssl_conf_authmode(&ssl_config_, MBEDTLS_SSL_VERIFY_REQUIRED);
-
-  // TODO(pwbug/398): Add logic for loading trust anchors.
-
-  // Load configuration to SSL.
-  ret = mbedtls_ssl_setup(&ssl_ctx_, &ssl_config_);
-  if (ret) {
-    return Status::Internal();
-  }
-
-  // Set up transport.
-  // The API does not fail.
-  mbedtls_ssl_set_bio(&ssl_ctx_, this, MbedTlsWrite, MbedTlsRead, nullptr);
-
-  ret = mbedtls_ssl_set_hostname(&ssl_ctx_,
-                                 session_options_.server_name().data());
-  if (ret) {
-    return Status::Internal();
-  }
-
-  return OkStatus();
-}
-
-}  // namespace backend
-
-Session::Session(const SessionOptions& options) : session_impl_(options) {}
-
-Session::~Session() = default;
-
-Result<Session*> Session::Create(const SessionOptions& options) {
-  if (!options.transport()) {
-    PW_LOG_DEBUG("Must provide a transport");
-    return Status::Internal();
-  }
-
-  auto sess = new Session(options);
-  if (!sess) {
-    return Status::ResourceExhausted();
-  }
-
-  // Set up the client.
-  auto setup_status = sess->session_impl_.Setup();
-  if (!setup_status.ok()) {
-    PW_LOG_DEBUG("Failed to setup");
-    // TODO(pwbug/398): `tls_status_` may be set, but the session object will
-    // be released. Map `tls_stauts_` to string and print out here so that
-    // the information can be catched.
-    delete sess;
-    return setup_status;
-  }
-
-  return sess;
-}
-
-Status Session::Open() {
-  // TODO(pwbug/398): To implement
-  return Status::Unimplemented();
-}
-
-Status Session::Close() {
-  // TODO(pwbug/398): To implement
-  return Status::Unimplemented();
-}
-
-StatusWithSize Session::DoRead(ByteSpan) {
-  // TODO(pwbug/398): To implement
-  return StatusWithSize(Status::Unimplemented(), 0);
-}
-
-Status Session::DoWrite(ConstByteSpan) {
-  // TODO(pwbug/398): To implement
-  return Status::Unimplemented();
-}
-
-TLSStatus Session::GetLastTLSStatus() { return session_impl_.GetTlsStatus(); }
-
-}  // namespace pw::tls_client
diff --git a/pw_tls_client_mbedtls/tls_client_mbedtls_test.cc b/pw_tls_client_mbedtls/tls_client_mbedtls_test.cc
deleted file mode 100644
index 66242e8..0000000
--- a/pw_tls_client_mbedtls/tls_client_mbedtls_test.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "gtest/gtest.h"
-#include "pw_stream/null_stream.h"
-#include "pw_tls_client/session.h"
-
-namespace pw::tls_client {
-
-TEST(TLSClientMbedTLS, CreateSucceed) {
-  auto options = SessionOptions().set_transport(stream::NullStream::Instance());
-  auto res = Session::Create(options);
-  ASSERT_EQ(res.status(), OkStatus());
-  ASSERT_NE(res.value(), nullptr);
-}
-
-TEST(TLSClientMbedTLS, CreateFailOnMissingTransport) {
-  auto options = SessionOptions();
-  auto res = Session::Create(options);
-  ASSERT_NE(res.status(), OkStatus());
-}
-
-TEST(TLSClientMbedTLS, EntropySourceFail) {
-  backend::SessionImplementation::SetEntropySourceStatus(Status::Internal());
-  auto options = SessionOptions().set_transport(stream::NullStream::Instance());
-  auto res = Session::Create(options);
-  ASSERT_NE(res.status(), OkStatus());
-}
-
-}  // namespace pw::tls_client
\ No newline at end of file
diff --git a/pw_tokenizer/BUILD b/pw_tokenizer/BUILD
new file mode 100644
index 0000000..4b288be
--- /dev/null
+++ b/pw_tokenizer/BUILD
@@ -0,0 +1,276 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_binary",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_tokenizer",
+    srcs = [
+        "encode_args.cc",
+        "hash.cc",
+        "public/pw_tokenizer/config.h",
+        "public/pw_tokenizer/internal/argument_types.h",
+        "public/pw_tokenizer/internal/argument_types_macro_4_byte.h",
+        "public/pw_tokenizer/internal/argument_types_macro_8_byte.h",
+        "public/pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_128_hash_macro.h",
+        "public/pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_256_hash_macro.h",
+        "public/pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_80_hash_macro.h",
+        "public/pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_96_hash_macro.h",
+        "public/pw_tokenizer/internal/tokenize_string.h",
+        "tokenize.cc",
+    ],
+    hdrs = [
+        "public/pw_tokenizer/encode_args.h",
+        "public/pw_tokenizer/hash.h",
+        "public/pw_tokenizer/tokenize.h",
+    ],
+    includes = ["public"],
+    deps = [
+        "//pw_polyfill",
+        "//pw_preprocessor",
+        "//pw_span",
+        "//pw_varint",
+    ],
+)
+
+# TODO(pwbug/101): Need to add support for facades/backends to Bazel.
+PW_TOKENIZER_GLOBAL_HANDLER_BACKEND = "//pw_tokenizer:test_backend"
+
+PW_TOKENIZER_GLOBAL_HANDLER_WITH_PAYLOAD_BACKEND = "//pw_tokenizer:test_backend"
+
+pw_cc_library(
+    name = "test_backend",
+    visibility = ["//visibility:private"],
+)
+
+pw_cc_library(
+    name = "global_handler",
+    srcs = ["tokenize_to_global_handler.cc"],
+    hdrs = ["public/pw_tokenizer/tokenize_to_global_handler.h"],
+    deps = [
+        ":pw_tokenizer",
+        PW_TOKENIZER_GLOBAL_HANDLER_BACKEND,
+    ],
+)
+
+pw_cc_library(
+    name = "global_handler_with_payload",
+    srcs = ["tokenize_to_global_handler_with_payload.cc"],
+    hdrs = ["public/pw_tokenizer/tokenize_to_global_handler_with_payload.h"],
+    deps = [
+        ":pw_tokenizer",
+        PW_TOKENIZER_GLOBAL_HANDLER_WITH_PAYLOAD_BACKEND,
+    ],
+)
+
+pw_cc_library(
+    name = "base64",
+    srcs = [
+        "base64.cc",
+    ],
+    hdrs = [
+        "public/pw_tokenizer/base64.h",
+    ],
+    includes = ["public"],
+    deps = [
+        "//pw_base64",
+        "//pw_preprocessor",
+        "//pw_span",
+    ],
+)
+
+pw_cc_library(
+    name = "decoder",
+    srcs = [
+        "decode.cc",
+        "detokenize.cc",
+        "token_database.cc",
+    ],
+    hdrs = [
+        "public/pw_tokenizer/detokenize.h",
+        "public/pw_tokenizer/internal/decode.h",
+        "public/pw_tokenizer/token_database.h",
+    ],
+    includes = ["public"],
+    deps = [
+        "//pw_span",
+        "//pw_varint",
+    ],
+)
+
+# Executable for generating test data for the C++ and Python detokenizers. This
+# target should only be built for the host.
+pw_cc_binary(
+    name = "generate_decoding_test_data",
+    srcs = [
+        "generate_decoding_test_data.cc",
+        "tokenize_test_fakes.cc",
+    ],
+    deps = [
+        ":decoder",
+        ":pw_tokenizer",
+        "//pw_preprocessor",
+        "//pw_span",
+        "//pw_varint",
+    ],
+)
+
+# Executable for generating a test ELF file for elf_reader_test.py. A host
+# version of this binary is checked in for use in elf_reader_test.py.
+cc_binary(
+    name = "elf_reader_test_binary",
+    srcs = [
+        "py/elf_reader_test_binary.c",
+    ],
+    linkopts = ["-Wl,--unresolved-symbols=ignore-all"],  # main is not defined
+    deps = [
+        ":pw_tokenizer",
+        "//pw_varint",
+    ],
+)
+
+pw_cc_test(
+    name = "argument_types_test",
+    srcs = [
+        "argument_types_test.cc",
+        "argument_types_test_c.c",
+        "pw_tokenizer_private/argument_types_test.h",
+        "tokenize_test_fakes.cc",
+    ],
+    deps = [
+        ":pw_tokenizer",
+        "//pw_preprocessor",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "base64_test",
+    srcs = [
+        "base64_test.cc",
+    ],
+    deps = [
+        ":base64",
+        "//pw_span",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "decode_test",
+    srcs = [
+        "decode_test.cc",
+        "pw_tokenizer_private/tokenized_string_decoding_test_data.h",
+        "pw_tokenizer_private/varint_decoding_test_data.h",
+    ],
+    deps = [
+        ":decoder",
+        "//pw_unit_test",
+        "//pw_varint",
+    ],
+)
+
+pw_cc_test(
+    name = "detokenize_test",
+    srcs = [
+        "detokenize_test.cc",
+    ],
+    deps = [
+        ":decoder",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "global_handlers_test",
+    srcs = [
+        "global_handlers_test.cc",
+        "global_handlers_test_c.c",
+        "pw_tokenizer_private/tokenize_test.h",
+    ],
+    deps = [
+        ":global_handler",
+        ":global_handler_with_payload",
+    ],
+)
+
+pw_cc_test(
+    name = "hash_test",
+    srcs = [
+        "hash_test.cc",
+        "pw_tokenizer_private/generated_hash_test_cases.h",
+        "tokenize_test_fakes.cc",
+    ],
+    deps = [
+        ":pw_tokenizer",
+        "//pw_preprocessor",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "simple_tokenize_test",
+    srcs = [
+        "simple_tokenize_test.cc",
+    ],
+    deps = [
+        ":pw_tokenizer",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "token_database_test",
+    srcs = [
+        "token_database_test.cc",
+    ],
+    deps = [
+        ":decoder",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "tokenize_test",
+    srcs = [
+        "pw_tokenizer_private/tokenize_test.h",
+        "tokenize_test.cc",
+        "tokenize_test_c.c",
+    ],
+    deps = [
+        ":pw_tokenizer",
+        "//pw_preprocessor",
+        "//pw_unit_test",
+        "//pw_varint",
+    ],
+)
+
+# Create a shared library for the tokenizer JNI wrapper. The include paths for
+# the JNI headers must be available in the system or provided with the
+# pw_java_native_interface_include_dirs variable.
+filegroup(
+    name = "detokenizer_jni",
+    srcs = [
+        "java/dev/pigweed/tokenizer/detokenizer.cc",
+    ],
+)
diff --git a/pw_tokenizer/BUILD.bazel b/pw_tokenizer/BUILD.bazel
deleted file mode 100644
index cafab2c..0000000
--- a/pw_tokenizer/BUILD.bazel
+++ /dev/null
@@ -1,304 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load("@rules_cc//cc:defs.bzl", "cc_binary")
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_binary",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-load("//pw_fuzzer:fuzzer.bzl", "pw_cc_fuzz_test")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_tokenizer",
-    srcs = [
-        "encode_args.cc",
-        "hash.cc",
-        "public/pw_tokenizer/config.h",
-        "public/pw_tokenizer/internal/argument_types.h",
-        "public/pw_tokenizer/internal/argument_types_macro_4_byte.h",
-        "public/pw_tokenizer/internal/argument_types_macro_8_byte.h",
-        "public/pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_128_hash_macro.h",
-        "public/pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_256_hash_macro.h",
-        "public/pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_80_hash_macro.h",
-        "public/pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_96_hash_macro.h",
-        "public/pw_tokenizer/internal/tokenize_string.h",
-        "tokenize.cc",
-    ],
-    hdrs = [
-        "public/pw_tokenizer/encode_args.h",
-        "public/pw_tokenizer/hash.h",
-        "public/pw_tokenizer/tokenize.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_containers:to_array",
-        "//pw_polyfill",
-        "//pw_preprocessor",
-        "//pw_span",
-        "//pw_varint",
-    ],
-)
-
-pw_cc_library(
-    name = "test_backend",
-    visibility = ["@pigweed_config//:__pkg__"],
-)
-
-pw_cc_library(
-    name = "global_handler",
-    srcs = ["tokenize_to_global_handler.cc"],
-    hdrs = ["public/pw_tokenizer/tokenize_to_global_handler.h"],
-    deps = [
-        ":pw_tokenizer",
-        "@pigweed_config//:pw_tokenizer_global_handler_backend",
-    ],
-)
-
-pw_cc_library(
-    name = "global_handler_with_payload",
-    srcs = ["tokenize_to_global_handler_with_payload.cc"],
-    hdrs = ["public/pw_tokenizer/tokenize_to_global_handler_with_payload.h"],
-    deps = [
-        ":pw_tokenizer",
-        "@pigweed_config//:pw_tokenizer_global_handler_with_payload_backend",
-    ],
-)
-
-pw_cc_library(
-    name = "base64",
-    srcs = [
-        "base64.cc",
-    ],
-    hdrs = [
-        "public/pw_tokenizer/base64.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":pw_tokenizer",
-        "//pw_base64",
-        "//pw_preprocessor",
-        "//pw_span",
-    ],
-)
-
-pw_cc_library(
-    name = "decoder",
-    srcs = [
-        "decode.cc",
-        "detokenize.cc",
-        "token_database.cc",
-    ],
-    hdrs = [
-        "public/pw_tokenizer/detokenize.h",
-        "public/pw_tokenizer/internal/decode.h",
-        "public/pw_tokenizer/token_database.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_span",
-        "//pw_varint",
-    ],
-)
-
-proto_library(
-    name = "tokenizer_proto",
-    srcs = [
-        "options.proto",
-    ],
-    import_prefix = "pw_tokenizer/proto",
-    strip_import_prefix = "//pw_tokenizer",
-    deps = [
-        "@com_google_protobuf//:descriptor_proto",
-    ],
-)
-
-# Executable for generating test data for the C++ and Python detokenizers. This
-# target should only be built for the host.
-pw_cc_binary(
-    name = "generate_decoding_test_data",
-    srcs = [
-        "generate_decoding_test_data.cc",
-    ],
-    target_compatible_with = select(
-        {
-            "@platforms//os:linux": [],
-            "@platforms//os:windows": [],
-            "@platforms//os:macos": [],
-            "//conditions:default": ["@platforms//:incompatible"],
-        },
-    ),
-    deps = [
-        ":decoder",
-        ":pw_tokenizer",
-        "//pw_preprocessor",
-        "//pw_span",
-        "//pw_varint",
-    ],
-)
-
-# Executable for generating a test ELF file for elf_reader_test.py. A host
-# version of this binary is checked in for use in elf_reader_test.py.
-cc_binary(
-    name = "elf_reader_test_binary",
-    srcs = [
-        "py/elf_reader_test_binary.c",
-    ],
-    linkopts = ["-Wl,--unresolved-symbols=ignore-all"],  # main is not defined
-    deps = [
-        ":pw_tokenizer",
-        "//pw_varint",
-    ],
-)
-
-pw_cc_test(
-    name = "argument_types_test",
-    srcs = [
-        "argument_types_test.cc",
-        "argument_types_test_c.c",
-        "pw_tokenizer_private/argument_types_test.h",
-    ],
-    deps = [
-        ":pw_tokenizer",
-        "//pw_preprocessor",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "base64_test",
-    srcs = [
-        "base64_test.cc",
-    ],
-    deps = [
-        ":base64",
-        "//pw_span",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "decode_test",
-    srcs = [
-        "decode_test.cc",
-        "pw_tokenizer_private/tokenized_string_decoding_test_data.h",
-        "pw_tokenizer_private/varint_decoding_test_data.h",
-    ],
-    deps = [
-        ":decoder",
-        "//pw_unit_test",
-        "//pw_varint",
-    ],
-)
-
-pw_cc_test(
-    name = "detokenize_test",
-    srcs = [
-        "detokenize_test.cc",
-    ],
-    deps = [
-        ":decoder",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_fuzz_test(
-    name = "detokenize_fuzzer",
-    srcs = ["detokenize_fuzzer.cc"],
-    deps = [
-        ":decoder",
-        ":pw_tokenizer",
-        "//pw_fuzzer",
-    ],
-)
-
-pw_cc_test(
-    name = "global_handlers_test",
-    srcs = [
-        "global_handlers_test.cc",
-        "global_handlers_test_c.c",
-        "pw_tokenizer_private/tokenize_test.h",
-    ],
-    deps = [
-        ":global_handler",
-        ":global_handler_with_payload",
-    ],
-)
-
-pw_cc_test(
-    name = "hash_test",
-    srcs = [
-        "hash_test.cc",
-        "pw_tokenizer_private/generated_hash_test_cases.h",
-    ],
-    deps = [
-        ":pw_tokenizer",
-        "//pw_preprocessor",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "simple_tokenize_test",
-    srcs = [
-        "simple_tokenize_test.cc",
-    ],
-    deps = [
-        ":global_handler",
-        ":global_handler_with_payload",
-        ":pw_tokenizer",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "token_database_test",
-    srcs = [
-        "token_database_test.cc",
-    ],
-    deps = [
-        ":decoder",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "tokenize_test",
-    srcs = [
-        "pw_tokenizer_private/tokenize_test.h",
-        "tokenize_test.cc",
-        "tokenize_test_c.c",
-    ],
-    deps = [
-        ":pw_tokenizer",
-        "//pw_preprocessor",
-        "//pw_unit_test",
-        "//pw_varint",
-    ],
-)
-
-# Create a shared library for the tokenizer JNI wrapper. The include paths for
-# the JNI headers must be available in the system or provided with the
-# pw_java_native_interface_include_dirs variable.
-filegroup(
-    name = "detokenizer_jni",
-    srcs = [
-        "java/dev/pigweed/tokenizer/detokenizer.cc",
-    ],
-)
diff --git a/pw_tokenizer/BUILD.gn b/pw_tokenizer/BUILD.gn
index 269419e..8a0305f 100644
--- a/pw_tokenizer/BUILD.gn
+++ b/pw_tokenizer/BUILD.gn
@@ -20,7 +20,6 @@
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_fuzzer/fuzzer.gni")
-import("$dir_pw_protobuf_compiler/proto.gni")
 import("$dir_pw_unit_test/test.gni")
 import("backend.gni")
 
@@ -41,10 +40,10 @@
 
   # Automatically add the tokenizer linker sections when cross-compiling or
   # building for Linux. macOS and Windows executables are not supported.
-  if (current_os == "" || current_os == "freertos") {
+  if (current_os == "") {
     ldflags = [
       "-T",
-      rebase_path("pw_tokenizer_linker_sections.ld", root_build_dir),
+      rebase_path("pw_tokenizer_linker_sections.ld"),
     ]
   } else if (current_os == "linux" && !pw_toolchain_OSS_FUZZ_ENABLED) {
     # When building for Linux, the linker provides a default linker script.
@@ -53,11 +52,10 @@
     # default linker script instead of overriding it.
     ldflags = [
       "-T",
-      rebase_path("add_tokenizer_sections_to_default_script.ld",
-                  root_build_dir),
+      rebase_path("add_tokenizer_sections_to_default_script.ld"),
+      "-L",
+      rebase_path("."),
     ]
-    lib_dirs = [ "." ]
-
     inputs += [ "add_tokenizer_sections_to_default_script.ld" ]
   }
   visibility = [ ":*" ]
@@ -74,7 +72,6 @@
   all_dependent_configs = [ ":linker_script" ]
   public_deps = [
     ":config",
-    "$dir_pw_containers:to_array",
     dir_pw_preprocessor,
   ]
   deps = [ dir_pw_varint ]
@@ -194,6 +191,7 @@
     ":detokenize_test",
     ":global_handlers_test",
     ":hash_test",
+    ":simple_tokenize_test_cpp11",
     ":simple_tokenize_test_cpp14",
     ":simple_tokenize_test_cpp17",
     ":token_database_fuzzer",
@@ -269,9 +267,9 @@
   deps = [ ":pw_tokenizer" ]
 }
 
-# Fully test C++14 compatibility by compiling all sources as C++14.
+# Fully test C++11 and C++14 compatibility by compiling all sources as C++11 or
+# C++14.
 _simple_tokenize_test_sources = [
-  "$dir_pw_containers/public/pw_containers/to_array.h",
   "$dir_pw_varint/public/pw_varint/varint.h",
   "$dir_pw_varint/varint.cc",
   "encode_args.cc",
@@ -296,9 +294,15 @@
 _simple_tokenize_test_configs = [
   ":public_include_path",
   "$dir_pw_varint:default_config",
-  "$dir_pw_containers:public_include_path",
 ]
 
+pw_test("simple_tokenize_test_cpp11") {
+  remove_configs = [ "$dir_pw_build:cpp17" ]
+  configs = [ "$dir_pw_build:cpp11" ] + _simple_tokenize_test_configs
+  sources = _simple_tokenize_test_sources
+  deps = [ dir_pw_preprocessor ]
+}
+
 pw_test("simple_tokenize_test_cpp14") {
   remove_configs = [ "$dir_pw_build:cpp17" ]
   configs = [ "$dir_pw_build:cpp14" ] + _simple_tokenize_test_configs
@@ -347,12 +351,6 @@
   ]
 }
 
-pw_proto_library("proto") {
-  sources = [ "options.proto" ]
-  prefix = "pw_tokenizer/proto"
-  python_package = "py"
-}
-
 declare_args() {
   # pw_JAVA_NATIVE_INTERFACE_INCLUDE_DIRS specifies the paths to use for
   # building Java Native Interface libraries. If no paths are provided, targets
@@ -382,9 +380,6 @@
 }
 
 pw_doc_group("docs") {
-  sources = [
-    "docs.rst",
-    "proto.rst",
-  ]
+  sources = [ "docs.rst" ]
   inputs = [ "py/pw_tokenizer/encode.py" ]
 }
diff --git a/pw_tokenizer/CMakeLists.txt b/pw_tokenizer/CMakeLists.txt
index 1ceecbc..a467ada 100644
--- a/pw_tokenizer/CMakeLists.txt
+++ b/pw_tokenizer/CMakeLists.txt
@@ -13,43 +13,16 @@
 # the License.
 
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-include($ENV{PW_ROOT}/pw_protobuf_compiler/proto.cmake)
-
-pw_add_module_config(pw_tokenizer_CONFIG)
-
-pw_add_module_library(pw_tokenizer.config
-  HEADERS
-    public/pw_tokenizer/config.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    ${pw_tokenizer_CONFIG}
-)
 
 pw_add_module_library(pw_tokenizer
-  HEADERS
-    public/pw_tokenizer/encode_args.h
-    public/pw_tokenizer/hash.h
-    public/pw_tokenizer/tokenize.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_containers
-    pw_polyfill.span
-    pw_preprocessor
-    pw_tokenizer.config
   SOURCES
     encode_args.cc
     hash.cc
-    public/pw_tokenizer/internal/argument_types.h
-    public/pw_tokenizer/internal/argument_types_macro_4_byte.h
-    public/pw_tokenizer/internal/argument_types_macro_8_byte.h
-    public/pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_128_hash_macro.h
-    public/pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_256_hash_macro.h
-    public/pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_80_hash_macro.h
-    public/pw_tokenizer/internal/pw_tokenizer_65599_fixed_length_96_hash_macro.h
-    public/pw_tokenizer/internal/tokenize_string.h
     tokenize.cc
+  PUBLIC_DEPS
+    pw_polyfill.overrides
+    pw_preprocessor
+    pw_span
   PRIVATE_DEPS
     pw_varint
 )
@@ -68,71 +41,44 @@
 endif()
 
 pw_add_module_library(pw_tokenizer.base64
-  HEADERS
-    public/pw_tokenizer/base64.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_base64
-    pw_polyfill.cstddef
-    pw_polyfill.span
-    pw_tokenizer
-    pw_tokenizer.config
   SOURCES
     base64.cc
+  PUBLIC_DEPS
+    pw_base64
+    pw_containers
+    pw_polyfill.overrides
+    pw_preprocessor
+    pw_span
 )
 
 pw_add_module_library(pw_tokenizer.decoder
-  HEADERS
-    public/pw_tokenizer/detokenize.h
-    public/pw_tokenizer/token_database.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_polyfill.span
-    pw_tokenizer
   SOURCES
     decode.cc
     detokenize.cc
-    public/pw_tokenizer/internal/decode.h
     token_database.cc
+  PUBLIC_DEPS
+    pw_span
+    pw_tokenizer
   PRIVATE_DEPS
     pw_varint
 )
 
 pw_add_facade(pw_tokenizer.global_handler
-  DEFAULT_BACKEND
-    pw_build.empty  # Default to an empty backend so the tests can run.
-  HEADERS
-    public/pw_tokenizer/tokenize_to_global_handler.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_preprocessor
-    pw_tokenizer
   SOURCES
     tokenize_to_global_handler.cc
+  PUBLIC_DEPS
+    pw_tokenizer
+  DEFAULT_BACKEND
+    pw_build.empty  # Default to an empty backend so the tests can run.
 )
 
 pw_add_facade(pw_tokenizer.global_handler_with_payload
-  DEFAULT_BACKEND
-    pw_build.empty  # Default to an empty backend so the tests can run.
-  HEADERS
-    public/pw_tokenizer/tokenize_to_global_handler_with_payload.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_preprocessor
-    pw_tokenizer
   SOURCES
     tokenize_to_global_handler_with_payload.cc
-)
-
-pw_proto_library(pw_tokenizer.proto
-  SOURCES
-    options.proto
-  PREFIX
-    pw_tokenizer/proto
+  PUBLIC_DEPS
+    pw_tokenizer
+  DEFAULT_BACKEND
+    pw_build.empty  # Default to an empty backend so the tests can run.
 )
 
 # Executable for generating test data for the C++ and Python detokenizers. This
diff --git a/pw_tokenizer/OWNERS b/pw_tokenizer/OWNERS
deleted file mode 100644
index d96cbc6..0000000
--- a/pw_tokenizer/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-hepler@google.com
diff --git a/pw_tokenizer/argument_types_test.cc b/pw_tokenizer/argument_types_test.cc
index 696886e..cf3b22b 100644
--- a/pw_tokenizer/argument_types_test.cc
+++ b/pw_tokenizer/argument_types_test.cc
@@ -23,7 +23,7 @@
 namespace pw::tokenizer {
 namespace {
 
-struct FakeType {};
+struct DummyType {};
 
 // Check each relevant type mapping.
 #define CHECK_TYPE(c_type, enum_type)                     \
@@ -61,7 +61,7 @@
 CHECK_TYPE(unsigned char*,    _PW_TOKENIZER_SELECT_INT_TYPE(void*));
 CHECK_TYPE(int*,              _PW_TOKENIZER_SELECT_INT_TYPE(void*));
 CHECK_TYPE(long long*,        _PW_TOKENIZER_SELECT_INT_TYPE(void*));
-CHECK_TYPE(FakeType*,         _PW_TOKENIZER_SELECT_INT_TYPE(void*));
+CHECK_TYPE(DummyType*,        _PW_TOKENIZER_SELECT_INT_TYPE(void*));
 
 // nullptr
 CHECK_TYPE(std::nullptr_t,    _PW_TOKENIZER_SELECT_INT_TYPE(void*));
diff --git a/pw_tokenizer/argument_types_test_c.c b/pw_tokenizer/argument_types_test_c.c
index 05257d7..849357d 100644
--- a/pw_tokenizer/argument_types_test_c.c
+++ b/pw_tokenizer/argument_types_test_c.c
@@ -24,7 +24,7 @@
 #error "This is a test of C code and must be compiled as C, not C++."
 #endif  // __cplusplus
 
-struct FakeType {};  // stand-in type for pointer argument type test
+struct DummyType {};  // stand-in type for pointer argument type test
 
 // Check each relevant type mapping using static_asserts.
 #define CHECK_TYPE(c_type, enum_type)                     \
@@ -62,7 +62,7 @@
 CHECK_TYPE(unsigned char*,    _PW_TOKENIZER_SELECT_INT_TYPE(void*));
 CHECK_TYPE(int*,              _PW_TOKENIZER_SELECT_INT_TYPE(void*));
 CHECK_TYPE(long long*,        _PW_TOKENIZER_SELECT_INT_TYPE(void*));
-CHECK_TYPE(struct FakeType*,  _PW_TOKENIZER_SELECT_INT_TYPE(void*));
+CHECK_TYPE(struct DummyType*, _PW_TOKENIZER_SELECT_INT_TYPE(void*));
 // clang-format on
 
 // null
diff --git a/pw_tokenizer/database.gni b/pw_tokenizer/database.gni
index 07d441f..81d6832 100644
--- a/pw_tokenizer/database.gni
+++ b/pw_tokenizer/database.gni
@@ -91,12 +91,15 @@
     not_needed([ "_domain" ])
   }
 
+  # Restrict parallelism for updating this database file to one thread. This
+  # makes it safe to update it from multiple toolchains.
+  pool("$target_name._pool") {
+    depth = 1
+  }
+
   pw_python_action(target_name) {
     script = "$dir_pw_tokenizer/py/pw_tokenizer/database.py"
-
-    # Restrict parallelism for updating this database file to one thread. This
-    # makes it safe to update it from multiple toolchains.
-    pool = "$dir_pw_tokenizer/pool:database($default_toolchain)"
+    pool = ":$target_name._pool"
 
     inputs = _input_databases
 
@@ -116,9 +119,9 @@
 
     args += [
       "--database",
-      rebase_path(_database, root_build_dir),
+      rebase_path(_database),
     ]
-    args += rebase_path(_input_databases, root_build_dir)
+    args += rebase_path(_input_databases)
 
     foreach(target, _targets) {
       args += [ "<TARGET_FILE($target)>$_domain" ]
@@ -131,8 +134,9 @@
     }
 
     if (defined(invoker.optional_paths)) {
-      _paths = rebase_path(invoker.optional_paths, root_build_dir)
-      assert(filter_include(_paths, [ "../*" ]) == [],
+      _paths = rebase_path(invoker.optional_paths)
+      _out_dir = rebase_path(root_build_dir)
+      assert(filter_include(_paths, [ "$_out_dir/*" ]) == _paths,
              "Paths in 'optional_paths' must be in the out directory. Use " +
                  "'input_databases' for files in the source tree.")
       args += _paths
diff --git a/pw_tokenizer/decode.cc b/pw_tokenizer/decode.cc
index ea18c11..f75da71 100644
--- a/pw_tokenizer/decode.cc
+++ b/pw_tokenizer/decode.cc
@@ -210,10 +210,10 @@
   const size_t bytes = varint::Decode(std::as_bytes(arguments), &value);
 
   if (bytes == 0u) {
-    return DecodedArg(ArgStatus::kDecodeError,
-                      text_,
-                      std::min(varint::kMaxVarint64SizeBytes,
-                               static_cast<size_t>(arguments.size())));
+    return DecodedArg(
+        ArgStatus::kDecodeError,
+        text_,
+        std::min(varint::kMaxVarint64SizeBytes, arguments.size()));
   }
 
   // Unsigned ints need to be masked to their bit width due to sign extension.
diff --git a/pw_tokenizer/docs.rst b/pw_tokenizer/docs.rst
index dc908ea..a95ee5a 100644
--- a/pw_tokenizer/docs.rst
+++ b/pw_tokenizer/docs.rst
@@ -410,23 +410,6 @@
 
 .. autofunction:: pw_tokenizer.encode.encode_token_and_args
 
-This function requires a string's token is already calculated. Typically these
-tokens are provided by a database, but they can be manually created using the
-tokenizer hash.
-
-.. autofunction:: pw_tokenizer.tokens.pw_tokenizer_65599_hash
-
-This is particularly useful for offline token database generation in cases where
-tokenized strings in a binary cannot be embedded as parsable pw_tokenizer
-entries.
-
-.. note::
-  In C, the hash length of a string has a fixed limit controlled by
-  ``PW_TOKENIZER_CFG_C_HASH_LENGTH``. To match tokens produced by C (as opposed
-  to C++) code, ``pw_tokenizer_65599_hash()`` should be called with a matching
-  hash length limit. When creating an offline database, it's a good idea to
-  generate tokens for both, and merge the databases.
-
 Encoding
 --------
 The token is a 32-bit hash calculated during compilation. The string is encoded
@@ -634,7 +617,7 @@
 of 8-byte entries. Each entry stores the token and the removal date, which is
 0xFFFFFFFF if there is none. The string literals are stored next in the same
 order as the entries. Strings are stored with null terminators. See
-`token_database.h <https://pigweed.googlesource.com/pigweed/pigweed/+/HEAD/pw_tokenizer/public/pw_tokenizer/token_database.h>`_
+`token_database.h <https://pigweed.googlesource.com/pigweed/pigweed/+/refs/heads/master/pw_tokenizer/public/pw_tokenizer/token_database.h>`_
 for full details.
 
 The binary form of the CSV database is shown below. It contains the same
@@ -662,15 +645,6 @@
   0x70: 25 75 20 25 64 00 54 68 65 20 61 6e 73 77 65 72  %u %d.The answer
   0x80: 20 69 73 3a 20 25 73 00 25 6c 6c 75 00            is: %s.%llu.
 
-
-JSON support
-------------
-While pw_tokenizer doesn't specify a JSON database format, a token database can
-be created from a JSON formatted array of strings. This is useful for side-band
-token database generation for strings that are not embedded as parsable tokens
-in compiled binaries. See :ref:`module-pw_tokenizer-database-creation` for
-instructions on generating a token database from a JSON file.
-
 Managing token databases
 ------------------------
 Token databases are managed with the ``database.py`` script. This script can be
@@ -681,23 +655,20 @@
 ``pw_tokenizer/py/example_binary_with_tokenized_strings.elf``. You can use that
 file to experiment with the ``database.py`` commands.
 
-.. _module-pw_tokenizer-database-creation:
-
 Create a database
 ^^^^^^^^^^^^^^^^^
 The ``create`` command makes a new token database from ELF files (.elf, .o, .so,
-etc.), archives (.a), existing token databases (CSV or binary), or a JSON file
-containing an array of strings.
+etc.), archives (.a), or existing token databases (CSV or binary).
 
 .. code-block:: sh
 
   ./database.py create --database DATABASE_NAME ELF_OR_DATABASE_FILE...
 
-Two database output formats are supported: CSV and binary. Provide
-``--type binary`` to ``create`` to generate a binary database instead of the
-default CSV. CSV databases are great for checking into a source control or for
-human review. Binary databases are more compact and simpler to parse. The C++
-detokenizer library only supports binary databases currently.
+Two database formats are supported: CSV and binary. Provide ``--type binary`` to
+``create`` to generate a binary database instead of the default CSV. CSV
+databases are great for checking into a source control or for human review.
+Binary databases are more compact and simpler to parse. The C++ detokenizer
+library only supports binary databases currently.
 
 Update a database
 ^^^^^^^^^^^^^^^^^
@@ -820,13 +791,6 @@
 monitors database files for changes and automatically reloads them when they
 change. This is helpful for long-running tools that use detokenization.
 
-For messages that are optionally tokenized and may be encoded as binary,
-Base64, or plaintext UTF-8, use
-:func:`pw_tokenizer.proto.decode_optionally_tokenized`. This will attempt to
-determine the correct method to detokenize and always provide a printable
-string. For more information on this feature, see
-:ref:`module-pw_tokenizer-proto`.
-
 C++
 ---
 The C++ detokenization libraries can be used in C++ or any language that can
@@ -870,16 +834,6 @@
     return Detokenizer(kDefaultDatabase);
   }
 
-Protocol buffers
-----------------
-``pw_tokenizer`` provides utilities for handling tokenized fields in protobufs.
-See :ref:`module-pw_tokenizer-proto` for details.
-
-.. toctree::
-  :hidden:
-
-  proto.rst
-
 Base64 format
 =============
 The tokenizer encodes messages to a compact binary representation. Applications
@@ -922,8 +876,8 @@
 
 Decoding
 --------
-The Python ``Detokenizer`` class supprts decoding and detokenizing prefixed
-Base64 messages with ``detokenize_base64`` and related methods.
+Base64 decoding and detokenizing is supported in the Python detokenizer through
+the ``detokenize_base64`` and related functions.
 
 .. tip::
   The Python detokenization tools support recursive detokenization for prefixed
@@ -954,44 +908,6 @@
     TransmitLogMessage(base64_buffer, base64_size);
   }
 
-Investigating undecoded messages
---------------------------------
-Tokenized messages cannot be decoded if the token is not recognized. The Python
-package includes the ``parse_message`` tool, which parses tokenized Base64
-messages without looking up the token in a database. This tool attempts to guess
-the types of the arguments and displays potential ways to decode them.
-
-This tool can be used to extract argument information from an otherwise unusable
-message. It could help identify which statement in the code produced the
-message. This tool is not particularly helpful for tokenized messages without
-arguments, since all it can do is show the value of the unknown token.
-
-The tool is executed by passing Base64 tokenized messages, with or without the
-``$`` prefix, to ``pw_tokenizer.parse_message``. Pass ``-h`` or ``--help`` to
-see full usage information.
-
-Example
-^^^^^^^
-.. code-block::
-
-  $ python -m pw_tokenizer.parse_message '$329JMwA=' koSl524TRkFJTEVEX1BSRUNPTkRJVElPTgJPSw== --specs %s %d
-
-  INF Decoding arguments for '$329JMwA='
-  INF Binary: b'\xdfoI3\x00' [df 6f 49 33 00] (5 bytes)
-  INF Token:  0x33496fdf
-  INF Args:   b'\x00' [00] (1 bytes)
-  INF Decoding with up to 8 %s or %d arguments
-  INF   Attempt 1: [%s]
-  INF   Attempt 2: [%d] 0
-
-  INF Decoding arguments for '$koSl524TRkFJTEVEX1BSRUNPTkRJVElPTgJPSw=='
-  INF Binary: b'\x92\x84\xa5\xe7n\x13FAILED_PRECONDITION\x02OK' [92 84 a5 e7 6e 13 46 41 49 4c 45 44 5f 50 52 45 43 4f 4e 44 49 54 49 4f 4e 02 4f 4b] (28 bytes)
-  INF Token:  0xe7a58492
-  INF Args:   b'n\x13FAILED_PRECONDITION\x02OK' [6e 13 46 41 49 4c 45 44 5f 50 52 45 43 4f 4e 44 49 54 49 4f 4e 02 4f 4b] (24 bytes)
-  INF Decoding with up to 8 %s or %d arguments
-  INF   Attempt 1: [%d %s %d %d %d] 55 FAILED_PRECONDITION 1 -40 -38
-  INF   Attempt 2: [%d %s %s] 55 FAILED_PRECONDITION OK
-
 Command line utilities
 ^^^^^^^^^^^^^^^^^^^^^^
 ``pw_tokenizer`` provides two standalone command line utilities for detokenizing
@@ -999,7 +915,7 @@
 
 * ``detokenize.py`` -- Detokenizes Base64-encoded strings in files or from
   stdin.
-* ``serial_detokenizer.py`` -- Detokenizes Base64-encoded strings from a
+* ``detokenize_serial.py`` -- Detokenizes Base64-encoded strings from a
   connected serial device.
 
 If the ``pw_tokenizer`` Python package is installed, these tools may be executed
@@ -1011,7 +927,7 @@
   python -m pw_tokenizer.detokenize -i input_file.txt
 
   # Detokenize Base64-encoded strings in output from a serial device
-  python -m pw_tokenizer.serial_detokenizer --device /dev/ttyACM0
+  python -m pw_tokenizer.detokenize_serial --device /dev/ttyACM0
 
 See the ``--help`` options for these tools for full usage information.
 
@@ -1059,9 +975,8 @@
 .. attention::
   Do not encode line numbers in tokenized strings. This results in a huge
   number of lines being added to the database, since every time code moves,
-  new strings are tokenized. If :ref:`module-pw_log_tokenized` is used, line
-  numbers are encoded in the log metadata. Line numbers may also be included by
-  by adding ``"%d"`` to the format string and passing ``__LINE__``.
+  new strings are tokenized. If line numbers are desired in a tokenized
+  string, add a ``"%d"`` to the string and pass ``__LINE__`` as an argument.
 
 Database management
 -------------------
@@ -1104,7 +1019,7 @@
   * Provide simple wrapper shell scripts that fill in arguments for the
     project. For example, point ``detokenize.py`` to the project's token
     databases.
-  * Use ``pw_tokenizer.AutoUpdatingDetokenizer`` to decode in
+  * Use ``pw_tokenizer.AutoReloadingDetokenizer`` to decode in
     continuously-running tools, so that users don't have to restart the tool
     when the token database updates.
   * Integrate detokenization everywhere it is needed. Integrating the tools
@@ -1211,7 +1126,7 @@
 Compatibility
 =============
   * C11
-  * C++14
+  * C++11
   * Python 3
 
 Dependencies
diff --git a/pw_tokenizer/encode_args.cc b/pw_tokenizer/encode_args.cc
index ccd5c61..93cd6c4 100644
--- a/pw_tokenizer/encode_args.cc
+++ b/pw_tokenizer/encode_args.cc
@@ -32,6 +32,12 @@
   kString = PW_TOKENIZER_ARG_TYPE_STRING,
 };
 
+// Just to be safe, make sure these values are what we expect them to be.
+static_assert(0b00u == static_cast<uint8_t>(ArgType::kInt));
+static_assert(0b01u == static_cast<uint8_t>(ArgType::kInt64));
+static_assert(0b10u == static_cast<uint8_t>(ArgType::kDouble));
+static_assert(0b11u == static_cast<uint8_t>(ArgType::kString));
+
 size_t EncodeInt(int value, const std::span<std::byte>& output) {
   return varint::Encode(value, std::as_writable_bytes(output));
 }
@@ -61,8 +67,7 @@
   }
 
   // Subtract 1 to save room for the status byte.
-  const size_t max_bytes =
-      std::min(static_cast<size_t>(output.size()), kMaxStringLength) - 1;
+  const size_t max_bytes = std::min(output.size(), kMaxStringLength) - 1;
 
   // Scan the string to find out how many bytes to copy.
   size_t bytes_to_copy = 0;
diff --git a/pw_tokenizer/generate_decoding_test_data.cc b/pw_tokenizer/generate_decoding_test_data.cc
index 83fda7f..9adfdfe 100644
--- a/pw_tokenizer/generate_decoding_test_data.cc
+++ b/pw_tokenizer/generate_decoding_test_data.cc
@@ -217,7 +217,7 @@
   std::mt19937 random(6006411);
   std::uniform_int_distribution<int64_t> big;
   std::uniform_int_distribution<int32_t> medium;
-  std::uniform_int_distribution<int32_t> small(' ', '~');
+  std::uniform_int_distribution<char> small(' ', '~');
   std::uniform_real_distribution<float> real;
 
   file->Section("Simple strings");
@@ -271,7 +271,7 @@
   MAKE_TEST_CASE("%ju", static_cast<uintmax_t>(99));
   MAKE_TEST_CASE("%jd", static_cast<intmax_t>(99));
   MAKE_TEST_CASE("%zu", sizeof(uint64_t));
-  MAKE_TEST_CASE("%zd", static_cast<ptrdiff_t>(123));
+  MAKE_TEST_CASE("%zd", static_cast<ssize_t>(123));
   MAKE_TEST_CASE("%td", static_cast<ptrdiff_t>(99));
 
   file->Section("Percent character");
@@ -348,7 +348,7 @@
   for (int i = 0; i < 100; ++i) {
     unsigned long long n1 = big(random);
     int n2 = medium(random);
-    char ch = static_cast<char>(small(random));
+    char ch = small(random);
     if (ch == '"' || ch == '\\') {
       ch = '\t';
     }
@@ -359,7 +359,7 @@
   for (int i = 0; i < 100; ++i) {
     const long long n1 = big(random);
     const unsigned n2 = medium(random);
-    const char ch = static_cast<char>(small(random));
+    const char ch = small(random);
 
     MAKE_TEST_CASE(
         "%s: %lld 0x%16u%08X %d", std::to_string(i).c_str(), n1, n2, n2, ch);
diff --git a/pw_tokenizer/options.proto b/pw_tokenizer/options.proto
deleted file mode 100644
index bc0b87a..0000000
--- a/pw_tokenizer/options.proto
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-syntax = "proto3";
-
-package pw.tokenizer;
-
-import "google/protobuf/descriptor.proto";
-
-enum Tokenization {
-  // The field may contain plain text or any type of tokenized data (binary or
-  // prefixed Base64).
-  TOKENIZATION_OPTIONAL = 0;
-}
-
-// Define the tokenized option, which indicates the data format for text in a
-// bytes field.
-extend google.protobuf.FieldOptions {
-  // The field number was randomly selected from the reserved, internal use
-  // field numbers (50000-99999).
-  // TODO(pwbug/393): Register with the Protobuf Global Extension Registry:
-  //     https://github.com/protocolbuffers/protobuf/blob/HEAD/docs/options.md
-  Tokenization format = 78576;
-}
diff --git a/pw_tokenizer/pool/BUILD.gn b/pw_tokenizer/pool/BUILD.gn
deleted file mode 100644
index cdb5bc3..0000000
--- a/pw_tokenizer/pool/BUILD.gn
+++ /dev/null
@@ -1,20 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-# Pools are used to limit concurrency; we don't want multiple instances.
-assert(current_toolchain == default_toolchain)
-
-pool("database") {
-  depth = 1
-}
diff --git a/pw_tokenizer/proto.rst b/pw_tokenizer/proto.rst
deleted file mode 100644
index 2479dd4..0000000
--- a/pw_tokenizer/proto.rst
+++ /dev/null
@@ -1,138 +0,0 @@
-.. _module-pw_tokenizer-proto:
-
-------------------------------------
-Tokenized fields in protocol buffers
-------------------------------------
-Text may be represented in a few different ways:
-
-- Plain ASCII or UTF-8 text (``This is plain text``)
-- Base64-encoded tokenized message (``$ibafcA==``)
-- Binary-encoded tokenized message (``89 b6 9f 70``)
-- Little-endian 32-bit integer token (``0x709fb689``)
-
-``pw_tokenizer`` provides tools for working with protobuf fields that may
-contain tokenized text.
-
-Tokenized field protobuf option
-===============================
-``pw_tokenizer`` provides the ``pw.tokenizer.format`` protobuf field option.
-This option may be applied to a protobuf field to indicate that it may contain a
-tokenized string. A string that is optionally tokenized is represented with a
-single ``bytes`` field annotated with ``(pw.tokenizer.format) =
-TOKENIZATION_OPTIONAL``.
-
-For example, the following protobuf has one field that may contain a tokenized
-string.
-
-.. code-block:: protobuf
-
-  message MessageWithOptionallyTokenizedField {
-    bytes just_bytes = 1;
-    bytes maybe_tokenized = 2 [(pw.tokenizer.format) = TOKENIZATION_OPTIONAL];
-    string just_text = 3;
-  }
-
-Decoding optionally tokenized strings
-=====================================
-The encoding used for an optionally tokenized field is not recorded in the
-protobuf. Despite this, the text can reliably be decoded. This is accomplished
-by attempting to decode the field as binary or Base64 tokenized data before
-treating it like plain text.
-
-The following diagram describes the decoding process for optionally tokenized
-fields in detail.
-
-.. mermaid::
-
-  flowchart TD
-     start([Received bytes]) --> binary
-
-     binary[Decode as<br>binary tokenized] --> binary_ok
-     binary_ok{Detokenizes<br>successfully?} -->|no| utf8
-     binary_ok -->|yes| done_binary([Display decoded binary])
-
-     utf8[Decode as UTF-8] --> utf8_ok
-     utf8_ok{Valid UTF-8?} -->|no| base64_encode
-     utf8_ok -->|yes| base64
-
-     base64_encode[Encode as<br>tokenized Base64] --> display
-     display([Display encoded Base64])
-
-     base64[Decode as<br>Base64 tokenized] --> base64_ok
-
-     base64_ok{Fully<br>or partially<br>detokenized?} -->|no| is_plain_text
-     base64_ok -->|yes| base64_results
-
-     is_plain_text{Text is<br>printable?} -->|no| base64_encode
-     is_plain_text-->|yes| plain_text
-
-     base64_results([Display decoded Base64])
-     plain_text([Display text])
-
-Potential decoding problems
----------------------------
-The decoding process for optionally tokenized fields will yield correct results
-in almost every situation. In rare circumstances, it is possible for it to fail,
-but these can be avoided with a low-overhead mitigation if desired.
-
-There are two ways in which the decoding process may fail.
-
-Accidentally interpreting plain text as tokenized binary
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-If a plain-text string happens to decode as a binary tokenized message, the
-incorrect message could be displayed. This is very unlikely to occur. While many
-tokens will incidentally end up being valid UTF-8 strings, it is highly unlikely
-that a device will happen to log one of these strings as plain text. The
-overwhelming majority of these strings will be nonsense.
-
-If an implementation wishes to guard against this extremely improbable
-situation, it is possible to prevent it. This situation is prevented by
-appending 0xFF (or another byte never valid in UTF-8) to binary tokenized data
-that happens to be valid UTF-8 (or all binary tokenized messages, if desired).
-When decoding, if there is an extra 0xFF byte, it is discarded.
-
-Displaying undecoded binary as plain text instead of Base64
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-If a message fails to decode as binary tokenized and it is not valid UTF-8, it
-is displayed as tokenized Base64. This makes it easily recognizable as a
-tokenized message and makes it simple to decode later from the text output (for
-example, with an updated token database).
-
-A binary message for which the token is not known may coincidentally be valid
-UTF-8 or ASCII. 6.25% of 4-byte sequences are composed only of ASCII characters.
-When decoding with an out-of-date token database, it is possible that some
-binary tokenized messages will be displayed as plain text rather than tokenized
-Base64.
-
-This situation is likely to occur, but should be infrequent. Even if it does
-happen, it is not a serious issue. A very small number of strings will be
-displayed incorrectly, but these strings cannot be decoded anyway. One nonsense
-string (e.g. ``a-D1``) would be displayed instead of another (``$YS1EMQ==``).
-Updating the token database would resolve the issue, though the non-Base64 logs
-would be difficult decode later from a log file.
-
-This situation can be avoided with the same approach described in
-`Accidentally interpreting plain text as tokenized binary`_. Appending
-an invalid UTF-8 character prevents the undecoded binary message from being
-interpreted as plain text.
-
-Python library
-==============
-The ``pw_tokenizer.proto`` module defines functions that may be used to
-detokenize protobuf objects in Python. The function
-:func:`pw_tokenizer.proto.detokenize_fields` detokenizes all fields annotated as
-tokenized, replacing them with their detokenized version. For example:
-
-.. code-block:: python
-
-  my_detokenizer = pw_tokenizer.Detokenizer(some_database)
-
-  my_message = SomeMessage(tokenized_field=b'$YS1EMQ==')
-  pw_tokenizer.proto.detokenize_fields(my_detokenizer, my_message)
-
-  assert my_message.tokenized_field == b'The detokenized string! Cool!'
-
-pw_tokenizer.proto
-------------------
-.. automodule:: pw_tokenizer.proto
-  :members:
diff --git a/pw_tokenizer/public/pw_tokenizer/internal/argument_types.h b/pw_tokenizer/public/pw_tokenizer/internal/argument_types.h
index 0fdf96b..79f631d 100644
--- a/pw_tokenizer/public/pw_tokenizer/internal/argument_types.h
+++ b/pw_tokenizer/public/pw_tokenizer/internal/argument_types.h
@@ -105,7 +105,7 @@
   }
 }
 
-#else  // C++14 version
+#else  // C++11 or C++14 version
 
 template <typename T,
           bool kIsDouble = std::is_floating_point<T>(),
diff --git a/pw_tokenizer/public/pw_tokenizer/internal/tokenize_string.h b/pw_tokenizer/public/pw_tokenizer/internal/tokenize_string.h
index 80df724..07591c0 100644
--- a/pw_tokenizer/public/pw_tokenizer/internal/tokenize_string.h
+++ b/pw_tokenizer/public/pw_tokenizer/internal/tokenize_string.h
@@ -31,8 +31,7 @@
 
 #ifdef __cplusplus
 
-#include "pw_containers/to_array.h"
-#include "pw_preprocessor/compiler.h"
+#include <array>
 
 namespace pw {
 namespace tokenizer {
@@ -51,12 +50,11 @@
         token_(token),
         domain_size_(kDomainSize),
         string_size_(kStringSize),
-        domain_(containers::to_array(domain)),
-        string_(containers::to_array(string)) {}
+        domain_(std::to_array(domain)),
+        string_(std::to_array(string)) {}
 
  private:
-  static_assert(kStringSize > 0u && kDomainSize > 0u,
-                "The string and domain must have at least a null terminator");
+  static_assert(kStringSize > 0u && kDomainSize > 0u);
 
   uint32_t magic_;
   uint32_t token_;
@@ -66,18 +64,6 @@
   std::array<char, kStringSize> string_;
 };
 
-// Use this MakeEntry function so that the type doesn't have to be specified in
-// the macro. Specifying the type causes problems when the tokenization macro is
-// used as an argument to another macro because it requires template arguments,
-// which the preprocessor misinterprets as macro arguments.
-template <uint32_t kDomainSize, uint32_t kStringSize>
-constexpr Entry<kDomainSize, kStringSize> MakeEntry(
-    uint32_t token,
-    const char (&domain)[kDomainSize],
-    const char (&string)[kStringSize]) {
-  return {token, domain, string};
-}
-
 }  // namespace internal
 }  // namespace tokenizer
 }  // namespace pw
@@ -144,7 +130,7 @@
 // be added to this file.
 #error "Unsupported value for PW_TOKENIZER_CFG_C_HASH_LENGTH"
 
-// Define a placeholder macro to give clearer compilation errors.
+// Define a dummy macro to give clearer compilation errors.
 #define PW_TOKENIZER_STRING_TOKEN(unused) 0u
 
 #endif  // PW_TOKENIZER_CFG_C_HASH_LENGTH
diff --git a/pw_tokenizer/public/pw_tokenizer/tokenize.h b/pw_tokenizer/public/pw_tokenizer/tokenize.h
index 8ad2b3a..cf21cd6 100644
--- a/pw_tokenizer/public/pw_tokenizer/tokenize.h
+++ b/pw_tokenizer/public/pw_tokenizer/tokenize.h
@@ -235,9 +235,12 @@
 #define _PW_TOKENIZER_CONST constexpr
 
 #define _PW_TOKENIZER_RECORD_ORIGINAL_STRING(token, domain, string)            \
-  alignas(1) static constexpr auto _PW_TOKENIZER_SECTION _PW_TOKENIZER_UNIQUE( \
-      _pw_tokenizer_string_entry_) =                                           \
-      ::pw::tokenizer::internal::MakeEntry(token, domain, string)
+  alignas(1) static constexpr ::pw::tokenizer::internal::Entry<sizeof(domain), \
+                                                               sizeof(string)> \
+      _PW_TOKENIZER_SECTION _PW_TOKENIZER_UNIQUE(                              \
+          _pw_tokenizer_string_entry_) {                                       \
+    token, domain, string                                                      \
+  }
 
 namespace pw {
 namespace tokenizer {
@@ -295,7 +298,7 @@
 //
 // pw_tokenizer is intended for use with ELF files only. Mach-O files (macOS
 // executables) do not support section names longer than 16 characters, so a
-// short, unused section name is used on macOS.
+// short, dummy section name is used on macOS.
 #ifdef __APPLE__
 #define _PW_TOKENIZER_SECTION \
   PW_KEEP_IN_SECTION(PW_STRINGIFY(_PW_TOKENIZER_UNIQUE(.pw.)))
diff --git a/pw_tokenizer/pw_tokenizer_private/generated_hash_test_cases.h b/pw_tokenizer/pw_tokenizer_private/generated_hash_test_cases.h
index 4f7f779..d7667fb 100644
--- a/pw_tokenizer/pw_tokenizer_private/generated_hash_test_cases.h
+++ b/pw_tokenizer/pw_tokenizer_private/generated_hash_test_cases.h
@@ -37,19 +37,19 @@
 } kHashTests[] = {
 
 {
-  std::string_view(),
+  std::string_view("", 0u),
   80u,  // fixed hash length
   UINT32_C(0),  // Python-calculated hash
   PW_TOKENIZER_65599_FIXED_LENGTH_80_HASH(""),  // macro-calculated hash
 },
 {
-  std::string_view(),
+  std::string_view("", 0u),
   96u,  // fixed hash length
   UINT32_C(0),  // Python-calculated hash
   PW_TOKENIZER_65599_FIXED_LENGTH_96_HASH(""),  // macro-calculated hash
 },
 {
-  std::string_view(),
+  std::string_view("", 0u),
   128u,  // fixed hash length
   UINT32_C(0),  // Python-calculated hash
   PW_TOKENIZER_65599_FIXED_LENGTH_128_HASH(""),  // macro-calculated hash
diff --git a/pw_tokenizer/py/BUILD.gn b/pw_tokenizer/py/BUILD.gn
index 2ac3044..e9e4468 100644
--- a/pw_tokenizer/py/BUILD.gn
+++ b/pw_tokenizer/py/BUILD.gn
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2020 The Pigweed Authors
 #
 # 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
@@ -15,20 +15,9 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_build/python.gni")
-import("$dir_pw_protobuf_compiler/proto.gni")
 
 pw_python_package("py") {
-  generate_setup = {
-    metadata = {
-      name = "pw_tokenizer"
-      version = "0.0.1"
-    }
-    options = {
-      extras_require = {
-        serial_detokenizer = [ "serial" ]
-      }
-    }
-  }
+  setup = [ "setup.py" ]
   sources = [
     "generate_argument_types_macro.py",
     "generate_hash_macro.py",
@@ -40,45 +29,22 @@
     "pw_tokenizer/detokenize.py",
     "pw_tokenizer/elf_reader.py",
     "pw_tokenizer/encode.py",
-    "pw_tokenizer/parse_message.py",
-    "pw_tokenizer/proto/__init__.py",
     "pw_tokenizer/serial_detokenizer.py",
     "pw_tokenizer/tokens.py",
+    "tokenized_string_decoding_test_data.py",
+    "varint_test_data.py",
   ]
   tests = [
     "database_test.py",
     "decode_test.py",
-    "detokenize_proto_test.py",
     "detokenize_test.py",
     "elf_reader_test.py",
     "encode_test.py",
-    "tokenized_string_decoding_test_data.py",
     "tokens_test.py",
-    "varint_test_data.py",
   ]
-  python_test_deps = [ ":test_proto.python" ]
   inputs = [
-    "elf_reader_test_binary.elf",
     "example_binary_with_tokenized_strings.elf",
     "example_legacy_binary_with_tokenized_strings.elf",
   ]
-  proto_library = "..:proto"
   pylintrc = "$dir_pigweed/.pylintrc"
 }
-
-# This setup.py may be used to install pw_tokenizer without GN. It does not
-# include the pw_tokenizer.proto subpackage, since it contains a generated
-# protobuf module.
-pw_python_script("setup") {
-  sources = [ "setup.py" ]
-  inputs = [
-    "setup.cfg",
-    "pyproject.toml",
-  ]
-}
-
-pw_proto_library("test_proto") {
-  sources = [ "detokenize_proto_test.proto" ]
-  deps = [ "..:proto" ]
-  prefix = "pw_tokenizer_tests"
-}
diff --git a/pw_tokenizer/py/database_test.py b/pw_tokenizer/py/database_test.py
index 74cf995..290c831 100755
--- a/pw_tokenizer/py/database_test.py
+++ b/pw_tokenizer/py/database_test.py
@@ -98,25 +98,6 @@
 e65aefef,          ,"Won't fit : %s%d"
 '''
 
-JSON_SOURCE_STRINGS = '''\
-[
-  "pigweed/pw_polyfill/standard_library_public/pw_polyfill/standard_library/assert.h",
-  "protocol_buffer/gen/pigweed/pw_protobuf/common_protos.proto_library/nanopb/pw_protobuf_protos/status.pb.h",
-  "pigweed/pw_rpc/client_server.cc",
-  "pigweed/pw_rpc/public/pw_rpc/client_server.h",
-  "This is a very long string that will produce two tokens; one for C++ and one for C. This is because this string exceeds the default C hash length."
-]
-'''
-
-CSV_STRINGS = '''\
-2cbf627a,          ,"pigweed/pw_rpc/client_server.cc"
-666562a1,          ,"protocol_buffer/gen/pigweed/pw_protobuf/common_protos.proto_library/nanopb/pw_protobuf_protos/status.pb.h"
-6c1e6eb3,          ,"pigweed/pw_rpc/public/pw_rpc/client_server.h"
-b25a9932,          ,"This is a very long string that will produce two tokens; one for C++ and one for C. This is because this string exceeds the default C hash length."
-eadf017f,          ,"pigweed/pw_polyfill/standard_library_public/pw_polyfill/standard_library/assert.h"
-f815dc5c,          ,"This is a very long string that will produce two tokens; one for C++ and one for C. This is because this string exceeds the default C hash length."
-'''
-
 EXPECTED_REPORT = {
     str(TOKENIZED_ENTRIES_ELF): {
         '': {
@@ -266,16 +247,6 @@
             CSV_DEFAULT_DOMAIN.replace('Jello', sub).replace('Hello', sub),
             self._csv.read_text())
 
-    def test_json_strings(self):
-        strings_file = self._dir / "strings.json"
-
-        with open(strings_file, 'w') as file:
-            file.write(JSON_SOURCE_STRINGS)
-
-        run_cli('create', '--force', '--database', self._csv, strings_file)
-        self.assertEqual(CSV_STRINGS.splitlines(),
-                         self._csv.read_text().splitlines())
-
 
 class LegacyDatabaseCommandLineTest(DatabaseCommandLineTest):
     """Test an ELF with the legacy plain string storage format."""
diff --git a/pw_tokenizer/py/decode_test.py b/pw_tokenizer/py/decode_test.py
index be08eb8..c0c4366 100755
--- a/pw_tokenizer/py/decode_test.py
+++ b/pw_tokenizer/py/decode_test.py
@@ -1,5 +1,5 @@
 #!/usr/bin/env python3
-# Copyright 2022 The Pigweed Authors
+# Copyright 2020 The Pigweed Authors
 #
 # 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
@@ -14,7 +14,6 @@
 # the License.
 """Tests the tokenized string decode module."""
 
-from datetime import datetime
 import unittest
 
 import tokenized_string_decoding_test_data as tokenized_string
@@ -22,7 +21,7 @@
 from pw_tokenizer import decode
 
 
-def error(msg, value=None) -> str:
+def error(msg, value=None):
     """Formats msg as the message for an argument that failed to parse."""
     if value is None:
         return '<[{}]>'.format(msg)
@@ -31,13 +30,13 @@
 
 class TestDecodeTokenized(unittest.TestCase):
     """Tests decoding tokenized strings with various arguments."""
-    def test_decode_generated_data(self) -> None:
+    def test_decode_generated_data(self):
         self.assertGreater(len(tokenized_string.TEST_DATA), 100)
 
         for fmt, decoded, encoded in tokenized_string.TEST_DATA:
             self.assertEqual(decode.decode(fmt, encoded, True), decoded)
 
-    def test_unicode_decode_errors(self) -> None:
+    def test_unicode_decode_errors(self):
         """Tests unicode errors, which do not occur in the C++ decoding code."""
         self.assertEqual(decode.decode('Why, %c', b'\x01', True),
                          'Why, ' + error('%c ERROR', -1))
@@ -56,12 +55,12 @@
         self.assertEqual(decode.decode('%c', b'\xff\xff\xff\xff\x0f', True),
                          error('%c ERROR', -2147483648))
 
-    def test_ignore_errors(self) -> None:
+    def test_ignore_errors(self):
         self.assertEqual(decode.decode('Why, %c', b'\x01'), 'Why, %c')
 
         self.assertEqual(decode.decode('%s %d', b'\x01!'), '! %d')
 
-    def test_pointer(self) -> None:
+    def test_pointer(self):
         """Tests pointer args, which are not natively supported in Python."""
         self.assertEqual(decode.decode('Hello: %p', b'\x00', True),
                          'Hello: 0x00000000')
@@ -70,8 +69,8 @@
 
 
 class TestIntegerDecoding(unittest.TestCase):
-    """Tests decoding variable-length integers."""
-    def test_decode_generated_data(self) -> None:
+    """Test decoding variable-length integers."""
+    def test_decode_generated_data(self):
         test_data = varint_test_data.TEST_DATA
         self.assertGreater(len(test_data), 100)
 
@@ -87,44 +86,5 @@
                     bytearray(encoded)).value)
 
 
-class TestFormattedString(unittest.TestCase):
-    """Tests scoring how successfully a formatted string decoded."""
-    def test_no_args(self) -> None:
-        result = decode.FormatString('string').format(b'')
-
-        self.assertTrue(result.ok())
-        self.assertEqual(result.score(), (True, True, 0, 0, datetime.max))
-
-    def test_one_arg(self) -> None:
-        result = decode.FormatString('%d').format(b'\0')
-
-        self.assertTrue(result.ok())
-        self.assertEqual(result.score(), (True, True, 0, 1, datetime.max))
-
-    def test_missing_args(self) -> None:
-        result = decode.FormatString('%p%d%d').format(b'\x02\x80')
-
-        self.assertFalse(result.ok())
-        self.assertEqual(result.score(), (False, True, -2, 3, datetime.max))
-        self.assertGreater(result.score(), result.score(datetime.now()))
-        self.assertGreater(result.score(datetime.now()),
-                           result.score(datetime.min))
-
-    def test_compare_score(self) -> None:
-        all_args_ok = decode.FormatString('%d%d%d').format(b'\0\0\0')
-        missing_one_arg = decode.FormatString('%d%d%d').format(b'\0\0')
-        missing_two_args = decode.FormatString('%d%d%d').format(b'\0')
-        all_args_extra_data = decode.FormatString('%d%d%d').format(b'\0\0\0\1')
-        missing_one_arg_extra_data = decode.FormatString('%d%d%d').format(
-            b'\0' + b'\x80' * 100)
-
-        self.assertGreater(all_args_ok.score(), missing_one_arg.score())
-        self.assertGreater(missing_one_arg.score(), missing_two_args.score())
-        self.assertGreater(missing_two_args.score(),
-                           all_args_extra_data.score())
-        self.assertGreater(all_args_extra_data.score(),
-                           missing_one_arg_extra_data.score())
-
-
 if __name__ == '__main__':
     unittest.main()
diff --git a/pw_tokenizer/py/detokenize_proto_test.proto b/pw_tokenizer/py/detokenize_proto_test.proto
deleted file mode 100644
index ff9439b..0000000
--- a/pw_tokenizer/py/detokenize_proto_test.proto
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-syntax = "proto3";
-
-package this_pigweed_test;
-
-import "pw_tokenizer/proto/options.proto";
-
-message TheMessage {
-  bytes just_bytes = 1;
-  bytes message = 2 [(pw.tokenizer.format) = TOKENIZATION_OPTIONAL];
-}
diff --git a/pw_tokenizer/py/detokenize_proto_test.py b/pw_tokenizer/py/detokenize_proto_test.py
deleted file mode 100644
index 1421dbf..0000000
--- a/pw_tokenizer/py/detokenize_proto_test.py
+++ /dev/null
@@ -1,131 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests decoding a proto with tokenized fields."""
-
-import base64
-import unittest
-
-from pw_tokenizer_tests.detokenize_proto_test_pb2 import TheMessage
-
-from pw_tokenizer import detokenize, encode, tokens
-from pw_tokenizer.proto import detokenize_fields, decode_optionally_tokenized
-
-_DATABASE = tokens.Database([
-    tokens.TokenizedStringEntry(0xAABBCCDD, "Luke, we're gonna have %s"),
-    tokens.TokenizedStringEntry(0x12345678, "This string has a $oeQAAA=="),
-    tokens.TokenizedStringEntry(0x0000e4a1, "recursive token"),
-])
-_DETOKENIZER = detokenize.Detokenizer(_DATABASE)
-
-
-class TestDetokenizeProtoFields(unittest.TestCase):
-    """Tests detokenizing optionally tokenized proto fields."""
-    def test_plain_text(self) -> None:
-        proto = TheMessage(message=b'boring conversation anyway!')
-        detokenize_fields(_DETOKENIZER, proto)
-        self.assertEqual(proto.message, b'boring conversation anyway!')
-
-    def test_binary(self) -> None:
-        proto = TheMessage(message=b'\xDD\xCC\xBB\xAA\x07company')
-        detokenize_fields(_DETOKENIZER, proto)
-        self.assertEqual(proto.message, b"Luke, we're gonna have company")
-
-    def test_binary_missing_arguments(self) -> None:
-        proto = TheMessage(message=b'\xDD\xCC\xBB\xAA')
-        detokenize_fields(_DETOKENIZER, proto)
-        self.assertEqual(proto.message, b"Luke, we're gonna have %s")
-
-    def test_recursive_binary(self) -> None:
-        proto = TheMessage(message=b'\x78\x56\x34\x12')
-        detokenize_fields(_DETOKENIZER, proto)
-        self.assertEqual(proto.message, b"This string has a recursive token")
-
-    def test_base64(self) -> None:
-        base64_msg = encode.prefixed_base64(b'\xDD\xCC\xBB\xAA\x07company')
-        proto = TheMessage(message=base64_msg.encode())
-        detokenize_fields(_DETOKENIZER, proto)
-        self.assertEqual(proto.message, b"Luke, we're gonna have company")
-
-    def test_recursive_base64(self) -> None:
-        base64_msg = encode.prefixed_base64(b'\x78\x56\x34\x12')
-        proto = TheMessage(message=base64_msg.encode())
-        detokenize_fields(_DETOKENIZER, proto)
-        self.assertEqual(proto.message, b"This string has a recursive token")
-
-    def test_plain_text_with_prefixed_base64(self) -> None:
-        base64_msg = encode.prefixed_base64(b'\xDD\xCC\xBB\xAA\x09pancakes!')
-        proto = TheMessage(message=f'Good morning, {base64_msg}'.encode())
-        detokenize_fields(_DETOKENIZER, proto)
-        self.assertEqual(proto.message,
-                         b"Good morning, Luke, we're gonna have pancakes!")
-
-    def test_unknown_token_not_utf8(self) -> None:
-        proto = TheMessage(message=b'\xFE\xED\xF0\x0D')
-        detokenize_fields(_DETOKENIZER, proto)
-        self.assertEqual(proto.message.decode(),
-                         encode.prefixed_base64(b'\xFE\xED\xF0\x0D'))
-
-    def test_only_control_characters(self) -> None:
-        proto = TheMessage(message=b'\1\2\3\4')
-        detokenize_fields(_DETOKENIZER, proto)
-        self.assertEqual(proto.message.decode(),
-                         encode.prefixed_base64(b'\1\2\3\4'))
-
-
-class TestDecodeOptionallyTokenized(unittest.TestCase):
-    """Tests optional detokenization directly."""
-    def setUp(self):
-        self.detok = detokenize.Detokenizer(
-            tokens.Database([
-                tokens.TokenizedStringEntry(0, 'cheese'),
-                tokens.TokenizedStringEntry(1, 'on pizza'),
-                tokens.TokenizedStringEntry(2, 'is quite good'),
-                tokens.TokenizedStringEntry(3, 'they say'),
-            ]))
-
-    def test_found_binary_token(self):
-        self.assertEqual(
-            'on pizza',
-            decode_optionally_tokenized(self.detok, b'\x01\x00\x00\x00'))
-
-    def test_missing_binary_token(self):
-        self.assertEqual(
-            '$' + base64.b64encode(b'\xD5\x8A\xF9\x2A\x8A').decode(),
-            decode_optionally_tokenized(self.detok, b'\xD5\x8A\xF9\x2A\x8A'))
-
-    def test_found_b64_token(self):
-        b64_bytes = b'$' + base64.b64encode(b'\x03\x00\x00\x00')
-        self.assertEqual('they say',
-                         decode_optionally_tokenized(self.detok, b64_bytes))
-
-    def test_missing_b64_token(self):
-        b64_bytes = b'$' + base64.b64encode(b'\xD5\x8A\xF9\x2A\x8A')
-        self.assertEqual(b64_bytes.decode(),
-                         decode_optionally_tokenized(self.detok, b64_bytes))
-
-    def test_found_alternate_prefix(self):
-        b64_bytes = b'~' + base64.b64encode(b'\x00\x00\x00\x00')
-        self.assertEqual(
-            'cheese', decode_optionally_tokenized(self.detok, b64_bytes, b'~'))
-
-    def test_missing_alternate_prefix(self):
-        b64_bytes = b'~' + base64.b64encode(b'\x02\x00\x00\x00')
-        self.assertEqual(
-            b64_bytes.decode(),
-            decode_optionally_tokenized(self.detok, b64_bytes, b'^'))
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_tokenizer/py/detokenize_test.py b/pw_tokenizer/py/detokenize_test.py
index 4b88305..1de6160 100755
--- a/pw_tokenizer/py/detokenize_test.py
+++ b/pw_tokenizer/py/detokenize_test.py
@@ -242,38 +242,34 @@
         self.assertEqual(str(detok.detokenize(b'\0\0\0\0')), '')
 
     def test_decode_from_elf_file(self):
-        """Test decoding from an elf file."""
         detok = detokenize.Detokenizer(io.BytesIO(ELF_WITH_TOKENIZER_SECTIONS))
         expected_tokens = frozenset(detok.database.token_to_entries.keys())
 
-        with tempfile.NamedTemporaryFile('wb', delete=False) as elf:
-            try:
-                elf.write(ELF_WITH_TOKENIZER_SECTIONS)
-                elf.close()
+        elf = tempfile.NamedTemporaryFile('wb', delete=False)
+        try:
+            elf.write(ELF_WITH_TOKENIZER_SECTIONS)
+            elf.close()
 
-                # Open ELF by file object
-                with open(elf.name, 'rb') as fd:
-                    detok = detokenize.Detokenizer(fd)
+            # Open ELF by file object
+            with open(elf.name, 'rb') as fd:
+                detok = detokenize.Detokenizer(fd)
 
-                self.assertEqual(
-                    expected_tokens,
-                    frozenset(detok.database.token_to_entries.keys()))
+            self.assertEqual(expected_tokens,
+                             frozenset(detok.database.token_to_entries.keys()))
 
-                # Open ELF by path
-                detok = detokenize.Detokenizer(elf.name)
-                self.assertEqual(
-                    expected_tokens,
-                    frozenset(detok.database.token_to_entries.keys()))
+            # Open ELF by path
+            detok = detokenize.Detokenizer(elf.name)
+            self.assertEqual(expected_tokens,
+                             frozenset(detok.database.token_to_entries.keys()))
 
-                # Open ELF by elf_reader.Elf
-                with open(elf.name, 'rb') as fd:
-                    detok = detokenize.Detokenizer(elf_reader.Elf(fd))
+            # Open ELF by elf_reader.Elf
+            with open(elf.name, 'rb') as fd:
+                detok = detokenize.Detokenizer(elf_reader.Elf(fd))
 
-                self.assertEqual(
-                    expected_tokens,
-                    frozenset(detok.database.token_to_entries.keys()))
-            finally:
-                os.unlink(elf.name)
+            self.assertEqual(expected_tokens,
+                             frozenset(detok.database.token_to_entries.keys()))
+        finally:
+            os.unlink(elf.name)
 
     def test_decode_from_csv_file(self):
         detok = detokenize.Detokenizer(io.BytesIO(ELF_WITH_TOKENIZER_SECTIONS))
@@ -282,26 +278,24 @@
         csv_database = str(detok.database)
         self.assertEqual(len(csv_database.splitlines()), TOKENS_IN_ELF)
 
-        with tempfile.NamedTemporaryFile('w', delete=False) as csv_file:
-            try:
-                csv_file.write(csv_database)
-                csv_file.close()
+        csv_file = tempfile.NamedTemporaryFile('w', delete=False)
+        try:
+            csv_file.write(csv_database)
+            csv_file.close()
 
-                # Open CSV by path
-                detok = detokenize.Detokenizer(csv_file.name)
-                self.assertEqual(
-                    expected_tokens,
-                    frozenset(detok.database.token_to_entries.keys()))
+            # Open CSV by path
+            detok = detokenize.Detokenizer(csv_file.name)
+            self.assertEqual(expected_tokens,
+                             frozenset(detok.database.token_to_entries.keys()))
 
-                # Open CSV by file object
-                with open(csv_file.name) as fd:
-                    detok = detokenize.Detokenizer(fd)
+            # Open CSV by file object
+            with open(csv_file.name) as fd:
+                detok = detokenize.Detokenizer(fd)
 
-                self.assertEqual(
-                    expected_tokens,
-                    frozenset(detok.database.token_to_entries.keys()))
-            finally:
-                os.unlink(csv_file.name)
+            self.assertEqual(expected_tokens,
+                             frozenset(detok.database.token_to_entries.keys()))
+        finally:
+            os.unlink(csv_file.name)
 
     def test_create_detokenizer_with_token_database(self):
         detok = detokenize.Detokenizer(io.BytesIO(ELF_WITH_TOKENIZER_SECTIONS))
@@ -409,20 +403,20 @@
 
         mock_getmtime.side_effect = move_back_time_if_file_exists
 
-        with tempfile.NamedTemporaryFile('wb', delete=False) as file:
-            try:
-                file.close()
+        file = tempfile.NamedTemporaryFile('wb', delete=False)
+        try:
+            file.close()
 
-                detok = detokenize.AutoUpdatingDetokenizer(file.name,
-                                                           min_poll_period_s=0)
-                self.assertFalse(detok.detokenize(JELLO_WORLD_TOKEN).ok())
+            detok = detokenize.AutoUpdatingDetokenizer(file.name,
+                                                       min_poll_period_s=0)
+            self.assertFalse(detok.detokenize(JELLO_WORLD_TOKEN).ok())
 
-                with open(file.name, 'wb') as fd:
-                    tokens.write_binary(db, fd)
+            with open(file.name, 'wb') as fd:
+                tokens.write_binary(db, fd)
 
-                self.assertTrue(detok.detokenize(JELLO_WORLD_TOKEN).ok())
-            finally:
-                os.unlink(file.name)
+            self.assertTrue(detok.detokenize(JELLO_WORLD_TOKEN).ok())
+        finally:
+            os.unlink(file.name)
 
         # The database stays around if the file is deleted.
         self.assertTrue(detok.detokenize(JELLO_WORLD_TOKEN).ok())
@@ -430,29 +424,29 @@
     def test_no_update_if_time_is_same(self, mock_getmtime):
         mock_getmtime.return_value = 100
 
-        with tempfile.NamedTemporaryFile('wb', delete=False) as file:
-            try:
-                tokens.write_csv(
-                    database.load_token_database(
-                        io.BytesIO(ELF_WITH_TOKENIZER_SECTIONS)), file)
-                file.close()
+        file = tempfile.NamedTemporaryFile('wb', delete=False)
+        try:
+            tokens.write_csv(
+                database.load_token_database(
+                    io.BytesIO(ELF_WITH_TOKENIZER_SECTIONS)), file)
+            file.close()
 
-                detok = detokenize.AutoUpdatingDetokenizer(file,
-                                                           min_poll_period_s=0)
-                self.assertTrue(detok.detokenize(JELLO_WORLD_TOKEN).ok())
+            detok = detokenize.AutoUpdatingDetokenizer(file,
+                                                       min_poll_period_s=0)
+            self.assertTrue(detok.detokenize(JELLO_WORLD_TOKEN).ok())
 
-                # Empty the database, but keep the mock modified time the same.
-                with open(file.name, 'wb'):
-                    pass
+            # Empty the database, but keep the mock modified time the same.
+            with open(file.name, 'wb'):
+                pass
 
-                self.assertTrue(detok.detokenize(JELLO_WORLD_TOKEN).ok())
-                self.assertTrue(detok.detokenize(JELLO_WORLD_TOKEN).ok())
+            self.assertTrue(detok.detokenize(JELLO_WORLD_TOKEN).ok())
+            self.assertTrue(detok.detokenize(JELLO_WORLD_TOKEN).ok())
 
-                # Move back time so the now-empty file is reloaded.
-                mock_getmtime.return_value = 50
-                self.assertFalse(detok.detokenize(JELLO_WORLD_TOKEN).ok())
-            finally:
-                os.unlink(file.name)
+            # Move back time so the now-empty file is reloaded.
+            mock_getmtime.return_value = 50
+            self.assertFalse(detok.detokenize(JELLO_WORLD_TOKEN).ok())
+        finally:
+            os.unlink(file.name)
 
 
 def _next_char(message: bytes) -> bytes:
@@ -532,26 +526,22 @@
     def test_detokenize_base64_live(self):
         for data, expected in self.TEST_CASES:
             output = io.BytesIO()
-            self.detok.detokenize_base64_live(io.BytesIO(data), output, '$')
+            detokenize.detokenize_base64_live(self.detok, io.BytesIO(data),
+                                              output, '$')
 
             self.assertEqual(expected, output.getvalue())
 
     def test_detokenize_base64_to_file(self):
         for data, expected in self.TEST_CASES:
             output = io.BytesIO()
-            self.detok.detokenize_base64_to_file(data, output, '$')
+            detokenize.detokenize_base64_to_file(self.detok, data, output, '$')
 
             self.assertEqual(expected, output.getvalue())
 
     def test_detokenize_base64(self):
         for data, expected in self.TEST_CASES:
-            self.assertEqual(expected,
-                             self.detok.detokenize_base64(data, b'$'))
-
-    def test_detokenize_base64_str(self):
-        for data, expected in self.TEST_CASES:
-            self.assertEqual(expected.decode(),
-                             self.detok.detokenize_base64(data.decode()))
+            self.assertEqual(
+                expected, detokenize.detokenize_base64(self.detok, data, b'$'))
 
 
 class DetokenizeBase64InfiniteRecursion(unittest.TestCase):
@@ -569,24 +559,28 @@
     def test_detokenize_self_recursion(self):
         for depth in range(5):
             self.assertEqual(
-                self.detok.detokenize_base64(b'This one is deep: $AAAAAA==',
+                detokenize.detokenize_base64(self.detok,
+                                             b'This one is deep: $AAAAAA==',
                                              recursion=depth),
                 b'This one is deep: $AAAAAA==')
 
     def test_detokenize_self_recursion_default(self):
         self.assertEqual(
-            self.detok.detokenize_base64(b'This one is deep: $AAAAAA=='),
+            detokenize.detokenize_base64(self.detok,
+                                         b'This one is deep: $AAAAAA=='),
             b'This one is deep: $AAAAAA==')
 
     def test_detokenize_cyclic_recursion_even(self):
         self.assertEqual(
-            self.detok.detokenize_base64(b'I said "$AQAAAA=="', recursion=2),
-            b'I said "$AgAAAA=="')
+            detokenize.detokenize_base64(self.detok,
+                                         b'I said "$AQAAAA=="',
+                                         recursion=2), b'I said "$AgAAAA=="')
 
     def test_detokenize_cyclic_recursion_odd(self):
         self.assertEqual(
-            self.detok.detokenize_base64(b'I said "$AQAAAA=="', recursion=3),
-            b'I said "$AwAAAA=="')
+            detokenize.detokenize_base64(self.detok,
+                                         b'I said "$AQAAAA=="',
+                                         recursion=3), b'I said "$AwAAAA=="')
 
 
 if __name__ == '__main__':
diff --git a/pw_tokenizer/py/generate_hash_test_data.py b/pw_tokenizer/py/generate_hash_test_data.py
index 373c82e..586617f 100755
--- a/pw_tokenizer/py/generate_hash_test_data.py
+++ b/pw_tokenizer/py/generate_hash_test_data.py
@@ -101,7 +101,7 @@
     return _TEST_CASE.format(str=escaped_str,
                              string_length=len(data),
                              hash_length=hash_length,
-                             hash=tokens.pw_tokenizer_65599_hash(
+                             hash=tokens.pw_tokenizer_65599_fixed_length_hash(
                                  data, hash_length),
                              macro=HASH_MACRO.format(hash_length))
 
diff --git a/pw_tokenizer/py/pw_tokenizer/database.py b/pw_tokenizer/py/pw_tokenizer/database.py
index 37351fa..39eb185 100755
--- a/pw_tokenizer/py/pw_tokenizer/database.py
+++ b/pw_tokenizer/py/pw_tokenizer/database.py
@@ -166,32 +166,8 @@
     return metadata
 
 
-def _database_from_strings(strings: List[str]) -> tokens.Database:
-    """Generates a C and C++ compatible database from untokenized strings."""
-    # Generate a C compatible database from the fixed length hash.
-    c_db = tokens.Database.from_strings(
-        strings,
-        tokenize=lambda string: tokens.pw_tokenizer_65599_hash(
-            string, tokens.DEFAULT_C_HASH_LENGTH))
-
-    # Generate a C++ compatible database by allowing the hash to follow the
-    # string length.
-    cpp_db = tokens.Database.from_strings(
-        strings, tokenize=tokens.pw_tokenizer_65599_hash)
-
-    # Use a union of the C and C++ compatible databases.
-    return tokens.Database.merged(c_db, cpp_db)
-
-
-def _database_from_json(fd) -> tokens.Database:
-    return _database_from_strings(json.load(fd))
-
-
 def _load_token_database(db, domain: Pattern[str]) -> tokens.Database:
-    """Loads a Database from supported database types.
-
-    Supports Database objects, JSONs, ELFs, CSVs, and binary databases.
-    """
+    """Loads a Database from a database object, ELF, CSV, or binary database."""
     if db is None:
         return tokens.Database()
 
@@ -201,7 +177,7 @@
     if isinstance(db, elf_reader.Elf):
         return _database_from_elf(db, domain)
 
-    # If it's a str, it might be a path. Check if it's an ELF, CSV, or JSON.
+    # If it's a str, it might be a path. Check if it's an ELF or CSV.
     if isinstance(db, (str, Path)):
         if not os.path.exists(db):
             raise FileNotFoundError(
@@ -212,11 +188,6 @@
             if elf_reader.compatible_file(fd):
                 return _database_from_elf(fd, domain)
 
-        # Generate a database from JSON.
-        if str(db).endswith('.json'):
-            with open(db, 'r') as json_fd:
-                return _database_from_json(json_fd)
-
         # Read the path as a packed binary or CSV file.
         return tokens.DatabaseFile(db)
 
@@ -224,12 +195,8 @@
     if elf_reader.compatible_file(db):
         return _database_from_elf(db, domain)
 
-    # Read the database as JSON, CSV, or packed binary from a file object's
-    # path.
+    # Read the database as CSV or packed binary from a file object's path.
     if hasattr(db, 'name') and os.path.exists(db.name):
-        if db.name.endswith('.json'):
-            return _database_from_json(db)
-
         return tokens.DatabaseFile(db.name)
 
     # Read CSV directly from the file object.
@@ -240,10 +207,7 @@
     *databases,
     domain: Union[str,
                   Pattern[str]] = tokens.DEFAULT_DOMAIN) -> tokens.Database:
-    """Loads a Database from supported database types.
-
-    Supports Database objects, JSONs, ELFs, CSVs, and binary databases.
-    """
+    """Loads a Database from database objects, ELFs, CSVs, or binary files."""
     domain = re.compile(domain)
     return tokens.Database.merged(*(_load_token_database(db, domain)
                                     for db in databases))
@@ -369,9 +333,8 @@
                 raise FileNotFoundError(f'{path_or_glob} is not a valid path')
 
             for path in paths:
-                # Resolve globs to JSON, CSV, or compatible binary files.
-                if elf_reader.compatible_file(path) or path.endswith(
-                    ('.csv', '.json')):
+                # Resolve globs to CSV or compatible binary files.
+                if elf_reader.compatible_file(path) or path.endswith('.csv'):
                     yield Path(path)
 
 
diff --git a/pw_tokenizer/py/pw_tokenizer/decode.py b/pw_tokenizer/py/pw_tokenizer/decode.py
index f6ca503..30d8771 100644
--- a/pw_tokenizer/py/pw_tokenizer/decode.py
+++ b/pw_tokenizer/py/pw_tokenizer/decode.py
@@ -20,7 +20,6 @@
 in the resulting string with an error message.
 """
 
-from datetime import datetime
 import re
 import struct
 from typing import Iterable, List, NamedTuple, Match, Sequence, Tuple
@@ -276,7 +275,7 @@
         return self.format()
 
     def __repr__(self) -> str:
-        return f'DecodedArg({self})'
+        return 'DecodedArg({!r})'.format(self)
 
 
 def parse_format_specifiers(format_string: str) -> Iterable[FormatSpec]:
@@ -289,33 +288,6 @@
     args: Sequence[DecodedArg]
     remaining: bytes
 
-    def ok(self) -> bool:
-        """Arg data decoded successfully and all expected args were found."""
-        return all(arg.ok() for arg in self.args) and not self.remaining
-
-    def score(self, date_removed: datetime = None) -> tuple:
-        """Returns a key for sorting by how successful a decode was.
-
-        Decoded strings are sorted by whether they
-
-          1. decoded all bytes for all arguments without errors,
-          2. decoded all data,
-          3. have the fewest decoding errors,
-          4. decoded the most arguments successfully, or
-          5. have the most recent removal date, if they were removed.
-
-        This must match the collision resolution logic in detokenize.cc.
-
-        To format a list of FormattedStrings from most to least successful,
-        use sort(key=FormattedString.score, reverse=True).
-        """
-        return (
-            self.ok(),  # decocoded all data and all expected args were found
-            not self.remaining,  # decoded all data
-            -sum(not arg.ok() for arg in self.args),  # fewest errors
-            len(self.args),  # decoded the most arguments
-            date_removed or datetime.max)  # most recently present
-
 
 class FormatString:
     """Represents a printf-style format string."""
diff --git a/pw_tokenizer/py/pw_tokenizer/detokenize.py b/pw_tokenizer/py/pw_tokenizer/detokenize.py
index 8f94fa0..770a4f0 100755
--- a/pw_tokenizer/py/pw_tokenizer/detokenize.py
+++ b/pw_tokenizer/py/pw_tokenizer/detokenize.py
@@ -34,6 +34,7 @@
 import argparse
 import base64
 import binascii
+from datetime import datetime
 import io
 import logging
 import os
@@ -43,9 +44,8 @@
 import struct
 import sys
 import time
-from typing import (AnyStr, BinaryIO, Callable, Dict, List, Iterable, IO,
-                    Iterator, Match, NamedTuple, Optional, Pattern, Tuple,
-                    Union)
+from typing import (BinaryIO, Callable, Dict, List, Iterable, Iterator, Match,
+                    NamedTuple, Optional, Pattern, Tuple, Union)
 
 try:
     from pw_tokenizer import database, decode, encode, tokens
@@ -56,11 +56,8 @@
         os.path.abspath(__file__))))
     from pw_tokenizer import database, decode, encode, tokens
 
-_LOG = logging.getLogger('pw_tokenizer')
-
 ENCODED_TOKEN = struct.Struct('<I')
-BASE64_PREFIX = encode.BASE64_PREFIX.encode()
-DEFAULT_RECURSION = 9
+_LOG = logging.getLogger('pw_tokenizer')
 
 
 class DetokenizedString:
@@ -82,7 +79,25 @@
         for entry, fmt in format_string_entries:
             result = fmt.format(encoded_message[ENCODED_TOKEN.size:],
                                 show_errors)
-            decode_attempts.append((result.score(entry.date_removed), result))
+
+            # Sort competing entries so the most likely matches appear first.
+            # Decoded strings are prioritized by whether they
+            #
+            #   1. decoded all bytes for all arguments without errors,
+            #   2. decoded all data,
+            #   3. have the fewest decoding errors,
+            #   4. decoded the most arguments successfully, or
+            #   5. have the most recent removal date, if they were removed.
+            #
+            # This must match the collision resolution logic in detokenize.cc.
+            score: Tuple = (
+                all(arg.ok() for arg in result.args) and not result.remaining,
+                not result.remaining,  # decoded all data
+                -sum(not arg.ok() for arg in result.args),  # fewest errors
+                len(result.args),  # decoded the most arguments
+                entry.date_removed or datetime.max)  # most recently present
+
+            decode_attempts.append((score, result))
 
         # Sort the attempts by the score so the most likely results are first.
         decode_attempts.sort(key=lambda value: value[0], reverse=True)
@@ -164,17 +179,12 @@
           show_errors: if True, an error message is used in place of the %
               conversion specifier when an argument fails to decode
         """
+        self.database = database.load_token_database(*token_database_or_elf)
         self.show_errors = show_errors
 
         # Cache FormatStrings for faster lookup & formatting.
         self._cache: Dict[int, List[_TokenizedFormatString]] = {}
 
-        self._initialize_database(token_database_or_elf)
-
-    def _initialize_database(self, token_sources: Iterable) -> None:
-        self.database = database.load_token_database(*token_sources)
-        self._cache.clear()
-
     def lookup(self, token: int) -> List[_TokenizedFormatString]:
         """Returns (TokenizedStringEntry, FormatString) list for matches."""
         try:
@@ -197,98 +207,12 @@
         return DetokenizedString(token, self.lookup(token), encoded_message,
                                  self.show_errors)
 
-    def detokenize_base64(self,
-                          data: AnyStr,
-                          prefix: Union[str, bytes] = BASE64_PREFIX,
-                          recursion: int = DEFAULT_RECURSION) -> AnyStr:
-        """Decodes and replaces prefixed Base64 messages in the provided data.
 
-        Args:
-          data: the binary data to decode
-          prefix: one-character byte string that signals the start of a message
-          recursion: how many levels to recursively decode
-
-        Returns:
-          copy of the data with all recognized tokens decoded
-        """
-        output = io.BytesIO()
-        self.detokenize_base64_to_file(data, output, prefix, recursion)
-        result = output.getvalue()
-        return result.decode() if isinstance(data, str) else result
-
-    def detokenize_base64_to_file(self,
-                                  data: Union[str, bytes],
-                                  output: BinaryIO,
-                                  prefix: Union[str, bytes] = BASE64_PREFIX,
-                                  recursion: int = DEFAULT_RECURSION) -> None:
-        """Decodes prefixed Base64 messages in data; decodes to output file."""
-        data = data.encode() if isinstance(data, str) else data
-        prefix = prefix.encode() if isinstance(prefix, str) else prefix
-
-        output.write(
-            _base64_message_regex(prefix).sub(
-                self._detokenize_prefixed_base64(prefix, recursion), data))
-
-    def detokenize_base64_live(self,
-                               input_file: BinaryIO,
-                               output: BinaryIO,
-                               prefix: Union[str, bytes] = BASE64_PREFIX,
-                               recursion: int = DEFAULT_RECURSION) -> None:
-        """Reads chars one-at-a-time, decoding messages; SLOW for big files."""
-        prefix_bytes = prefix.encode() if isinstance(prefix, str) else prefix
-
-        base64_message = _base64_message_regex(prefix_bytes)
-
-        def transform(data: bytes) -> bytes:
-            return base64_message.sub(
-                self._detokenize_prefixed_base64(prefix_bytes, recursion),
-                data)
-
-        for message in PrefixedMessageDecoder(
-                prefix,
-                string.ascii_letters + string.digits + '+/-_=').transform(
-                    input_file, transform):
-            output.write(message)
-
-            # Flush each line to prevent delays when piping between processes.
-            if b'\n' in message:
-                output.flush()
-
-    def _detokenize_prefixed_base64(
-            self, prefix: bytes,
-            recursion: int) -> Callable[[Match[bytes]], bytes]:
-        """Returns a function that decodes prefixed Base64."""
-        def decode_and_detokenize(match: Match[bytes]) -> bytes:
-            """Decodes prefixed base64 with this detokenizer."""
-            original = match.group(0)
-
-            try:
-                detokenized_string = self.detokenize(
-                    base64.b64decode(original[1:], validate=True))
-                if detokenized_string.matches():
-                    result = str(detokenized_string).encode()
-
-                    if recursion > 0 and original != result:
-                        result = self.detokenize_base64(
-                            result, prefix, recursion - 1)
-
-                    return result
-            except binascii.Error:
-                pass
-
-            return original
-
-        return decode_and_detokenize
-
-
-_PathOrFile = Union[IO, str, Path]
-
-
-class AutoUpdatingDetokenizer(Detokenizer):
+class AutoUpdatingDetokenizer:
     """Loads and updates a detokenizer from database paths."""
     class _DatabasePath:
         """Tracks the modified time of a path or file object."""
-        def __init__(self, path: _PathOrFile) -> None:
+        def __init__(self, path):
             self.path = path if isinstance(path, (str, Path)) else path.name
             self._modified_time: Optional[float] = self._last_modified_time()
 
@@ -314,24 +238,27 @@
                 return database.load_token_database()
 
     def __init__(self,
-                 *paths_or_files: _PathOrFile,
+                 *paths_or_files,
                  min_poll_period_s: float = 1.0) -> None:
         self.paths = tuple(self._DatabasePath(path) for path in paths_or_files)
         self.min_poll_period_s = min_poll_period_s
         self._last_checked_time: float = time.time()
-        super().__init__(*(path.load() for path in self.paths))
+        self._detokenizer = Detokenizer(*(path.load() for path in self.paths))
 
-    def _reload_if_changed(self) -> None:
+    def detokenize(self, data: bytes) -> DetokenizedString:
+        """Updates the token database if it has changed, then detokenizes."""
         if time.time() - self._last_checked_time >= self.min_poll_period_s:
             self._last_checked_time = time.time()
 
             if any(path.updated() for path in self.paths):
                 _LOG.info('Changes detected; reloading token database')
-                self._initialize_database(path.load() for path in self.paths)
+                self._detokenizer = Detokenizer(*(path.load()
+                                                  for path in self.paths))
 
-    def lookup(self, token: int) -> List[_TokenizedFormatString]:
-        self._reload_if_changed()
-        return super().lookup(token)
+        return self._detokenizer.detokenize(data)
+
+
+_Detokenizer = Union[Detokenizer, AutoUpdatingDetokenizer]
 
 
 class PrefixedMessageDecoder:
@@ -401,6 +328,37 @@
             yield transform(chunk) if is_message else chunk
 
 
+def _detokenize_prefixed_base64(
+        detokenizer: _Detokenizer, prefix: bytes,
+        recursion: int) -> Callable[[Match[bytes]], bytes]:
+    """Returns a function that decodes prefixed Base64 with the detokenizer."""
+    def decode_and_detokenize(match: Match[bytes]) -> bytes:
+        """Decodes prefixed base64 with the provided detokenizer."""
+        original = match.group(0)
+
+        try:
+            detokenized_string = detokenizer.detokenize(
+                base64.b64decode(original[1:], validate=True))
+            if detokenized_string.matches():
+                result = str(detokenized_string).encode()
+
+                if recursion > 0 and original != result:
+                    result = detokenize_base64(detokenizer, result, prefix,
+                                               recursion - 1)
+
+                return result
+        except binascii.Error:
+            pass
+
+        return original
+
+    return decode_and_detokenize
+
+
+BASE64_PREFIX = encode.BASE64_PREFIX.encode()
+DEFAULT_RECURSION = 9
+
+
 def _base64_message_regex(prefix: bytes) -> Pattern[bytes]:
     """Returns a regular expression for prefixed base64 tokenized strings."""
     return re.compile(
@@ -412,16 +370,64 @@
             br'(?:[A-Za-z0-9+/\-_]{3}=|[A-Za-z0-9+/\-_]{2}==)?'))
 
 
-# TODO(hepler): Remove this unnecessary function.
-def detokenize_base64(detokenizer: Detokenizer,
+def detokenize_base64_live(detokenizer: _Detokenizer,
+                           input_file: BinaryIO,
+                           output: BinaryIO,
+                           prefix: Union[str, bytes] = BASE64_PREFIX,
+                           recursion: int = DEFAULT_RECURSION) -> None:
+    """Reads chars one-at-a-time and decodes messages; SLOW for big files."""
+    prefix_bytes = prefix.encode() if isinstance(prefix, str) else prefix
+
+    base64_message = _base64_message_regex(prefix_bytes)
+
+    def transform(data: bytes) -> bytes:
+        return base64_message.sub(
+            _detokenize_prefixed_base64(detokenizer, prefix_bytes, recursion),
+            data)
+
+    for message in PrefixedMessageDecoder(
+            prefix, string.ascii_letters + string.digits + '+/-_=').transform(
+                input_file, transform):
+        output.write(message)
+
+        # Flush each line to prevent delays when piping between processes.
+        if b'\n' in message:
+            output.flush()
+
+
+def detokenize_base64_to_file(detokenizer: _Detokenizer,
+                              data: bytes,
+                              output: BinaryIO,
+                              prefix: Union[str, bytes] = BASE64_PREFIX,
+                              recursion: int = DEFAULT_RECURSION) -> None:
+    """Decodes prefixed Base64 messages in data; decodes to an output file."""
+    prefix = prefix.encode() if isinstance(prefix, str) else prefix
+    output.write(
+        _base64_message_regex(prefix).sub(
+            _detokenize_prefixed_base64(detokenizer, prefix, recursion), data))
+
+
+def detokenize_base64(detokenizer: _Detokenizer,
                       data: bytes,
                       prefix: Union[str, bytes] = BASE64_PREFIX,
                       recursion: int = DEFAULT_RECURSION) -> bytes:
-    """Alias for detokenizer.detokenize_base64 for backwards compatibility."""
-    return detokenizer.detokenize_base64(data, prefix, recursion)
+    """Decodes and replaces prefixed Base64 messages in the provided data.
+
+    Args:
+      detokenizer: the detokenizer with which to decode messages
+      data: the binary data to decode
+      prefix: one-character byte string that signals the start of a message
+      recursion: how many levels to recursively decode
+
+    Returns:
+      copy of the data with all recognized tokens decoded
+    """
+    output = io.BytesIO()
+    detokenize_base64_to_file(detokenizer, data, output, prefix, recursion)
+    return output.getvalue()
 
 
-def _follow_and_detokenize_file(detokenizer: Detokenizer,
+def _follow_and_detokenize_file(detokenizer: _Detokenizer,
                                 file: BinaryIO,
                                 output: BinaryIO,
                                 prefix: Union[str, bytes],
@@ -432,7 +438,7 @@
         while True:
             data = file.read()
             if data:
-                detokenizer.detokenize_base64_to_file(data, output, prefix)
+                detokenize_base64_to_file(detokenizer, data, output, prefix)
                 output.flush()
             else:
                 time.sleep(poll_period_s)
@@ -457,11 +463,11 @@
         _follow_and_detokenize_file(detokenizer, input_file, output, prefix)
     elif input_file.seekable():
         # Process seekable files all at once, which is MUCH faster.
-        detokenizer.detokenize_base64_to_file(input_file.read(), output,
-                                              prefix)
+        detokenize_base64_to_file(detokenizer, input_file.read(), output,
+                                  prefix)
     else:
         # For non-seekable inputs (e.g. pipes), read one character at a time.
-        detokenizer.detokenize_base64_live(input_file, output, prefix)
+        detokenize_base64_live(detokenizer, input_file, output, prefix)
 
 
 def _parse_args() -> argparse.Namespace:
diff --git a/pw_tokenizer/py/pw_tokenizer/parse_message.py b/pw_tokenizer/py/pw_tokenizer/parse_message.py
deleted file mode 100644
index f8655e1..0000000
--- a/pw_tokenizer/py/pw_tokenizer/parse_message.py
+++ /dev/null
@@ -1,182 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Parses the arguments in a Base64-encoded tokenized message.
-
-This is useful for attempting to decode tokenized messages with arguments for
-which the token is not recognized.
-"""
-
-import argparse
-import base64
-from dataclasses import dataclass
-import logging
-import sys
-from typing import Collection, Iterable, Iterator, Sequence
-
-import pw_cli.log
-from pw_tokenizer.decode import FormatString, FormattedString
-
-_LOG: logging.Logger = logging.getLogger('pw_tokenizer')
-
-DEFAULT_FORMAT_SPECS = (
-    '%s',
-    '%d',
-    '%f',
-)
-
-DEFAULT_MAX_ARGS = 8
-PREFIX = '$'
-
-
-def attempt_to_decode(
-        arg_data: bytes,
-        format_specs: Collection[str] = DEFAULT_FORMAT_SPECS,
-        max_args: int = DEFAULT_MAX_ARGS,
-        yield_failures: bool = False) -> Iterator[FormattedString]:
-    """Attemps to decode arguments using the provided format specifiers."""
-    format_strings = [(0, '')]  # (argument count, format string)
-
-    # Each argument requires at least 1 byte.
-    max_args = min(max_args, len(arg_data))
-
-    while format_strings:
-        arg_count, string = format_strings.pop(0)
-        decode_attempt = FormatString(string).format(arg_data)
-
-        if yield_failures or decode_attempt.ok():
-            yield decode_attempt
-
-        if arg_count < max_args:
-            format_strings.extend(
-                (arg_count + 1, string + spec) for spec in format_specs)
-
-
-@dataclass(frozen=True)
-class TokenizedMessage:
-    string: str
-    binary: bytes
-
-    @property
-    def token(self) -> int:
-        return int.from_bytes(self.binary[:4], 'little')
-
-    @property
-    def binary_args(self) -> bytes:
-        return self.binary[4:]
-
-    @classmethod
-    def parse(cls, message: str, prefix: str = '$') -> 'TokenizedMessage':
-        if not message.startswith(prefix):
-            raise ValueError(
-                f'{message} does not start wtih {prefix!r} as expected')
-
-        binary = base64.b64decode(message[1:])
-
-        if len(binary) < 4:
-            raise ValueError(f'{message} is only {len(binary)} bytes; '
-                             'tokenized messages must be at least 4 bytes')
-
-        return cls(message, binary)
-
-
-def _read_stdin():
-    try:
-        while True:
-            yield input()
-    except KeyboardInterrupt:
-        return
-
-
-def _text_list(items: Sequence, conjunction: str = 'or') -> str:
-    if len(items) == 1:
-        return str(items[0])
-
-    return f'{", ".join(str(i) for i in items[:-1])} {conjunction} {items[-1]}'
-
-
-def main(messages: Iterable[str], max_args: int, specs: Sequence[str],
-         show_failures: bool) -> int:
-    """Parses the arguments for a series of tokenized messages."""
-    exit_code = 0
-
-    for message in iter(messages) if messages else _read_stdin():
-        if not message:
-            continue
-
-        if not message.startswith(PREFIX):
-            message = PREFIX + message
-
-        _LOG.info('Decoding arguments for %r', message)
-        try:
-            parsed = TokenizedMessage.parse(message)
-        except ValueError as exc:
-            _LOG.error('%s', exc)
-            exit_code = 2
-            continue
-
-        _LOG.info('Binary: %r [%s] (%d bytes)', parsed.binary,
-                  parsed.binary.hex(' ', 1), len(parsed.binary))
-        _LOG.info('Token:  0x%08x', parsed.token)
-        _LOG.info('Args:   %r [%s] (%d bytes)', parsed.binary_args,
-                  parsed.binary_args.hex(' ', 1), len(parsed.binary_args))
-        _LOG.info('Decoding with up to %d %s arguments', max_args,
-                  _text_list(specs))
-
-        results = sorted(attempt_to_decode(parsed.binary_args, specs, max_args,
-                                           show_failures),
-                         key=FormattedString.score,
-                         reverse=True)
-
-        if not any(result.ok() for result in results):
-            _LOG.warning(
-                '  No combinations of up to %d %s arguments decoded '
-                'successfully', max_args, _text_list(specs))
-            exit_code = 1
-
-        for i, result in enumerate(results, 1):
-            _LOG.info(  # pylint: disable=logging-fstring-interpolation
-                f'  Attempt %{len(str(len(results)))}d: [%s] %s', i,
-                ' '.join(str(a.specifier) for a in result.args),
-                ' '.join(str(a) for a in result.args))
-        print()
-
-    return exit_code
-
-
-def _parse_args() -> argparse.Namespace:
-    parser = argparse.ArgumentParser(
-        description=__doc__,
-        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
-    parser.add_argument('--max-args',
-                        default=DEFAULT_MAX_ARGS,
-                        type=int,
-                        help='Maximum number of printf-style arguments')
-    parser.add_argument('--specs',
-                        nargs='*',
-                        default=DEFAULT_FORMAT_SPECS,
-                        help='Which printf-style format specifiers to check')
-    parser.add_argument('--show-failures',
-                        action='store_true',
-                        help='Show argument combintations that fail to decode')
-    parser.add_argument(
-        'messages',
-        nargs='*',
-        help=
-        'Base64-encoded tokenized messages to decode; omit to read from stdin')
-    return parser.parse_args()
-
-
-if __name__ == '__main__':
-    pw_cli.log.install()
-    sys.exit(main(**vars(_parse_args())))
diff --git a/pw_tokenizer/py/pw_tokenizer/proto/__init__.py b/pw_tokenizer/py/pw_tokenizer/proto/__init__.py
deleted file mode 100644
index 2dd7237..0000000
--- a/pw_tokenizer/py/pw_tokenizer/proto/__init__.py
+++ /dev/null
@@ -1,78 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Utilities for working with tokenized fields in protobufs."""
-
-from typing import Iterator
-
-from google.protobuf.descriptor import FieldDescriptor
-from google.protobuf.message import Message
-
-from pw_tokenizer.proto import options_pb2
-from pw_tokenizer import detokenize, encode
-
-
-def _tokenized_fields(proto: Message) -> Iterator[FieldDescriptor]:
-    for field in proto.DESCRIPTOR.fields:
-        extensions = field.GetOptions().Extensions
-        if options_pb2.format in extensions and extensions[
-                options_pb2.format] == options_pb2.TOKENIZATION_OPTIONAL:
-            yield field
-
-
-def decode_optionally_tokenized(detokenizer: detokenize.Detokenizer,
-                                data: bytes,
-                                prefix: str = encode.BASE64_PREFIX) -> str:
-    """Decodes data that may be plain text or binary / Base64 tokenized text."""
-    # Try detokenizing as binary.
-    result = detokenizer.detokenize(data)
-    if result.best_result() is not None:
-        # Rather than just returning the detokenized string, continue
-        # detokenization in case recursive Base64 detokenization is needed.
-        data = str(result).encode()
-
-    # Attempt to decode as UTF-8.
-    try:
-        text = data.decode()
-    except UnicodeDecodeError:
-        # Not UTF-8. Assume the token is unknown or the data is corrupt.
-        return encode.prefixed_base64(data, prefix)
-
-    # See if the string is prefixed Base64 or contains prefixed Base64.
-    detokenized = detokenize.detokenize_base64(detokenizer, data, prefix)
-    if detokenized != data:  # If anything detokenized successfully, use that.
-        return detokenized.decode()
-
-    # Attempt to determine whether this is an unknown token or plain text.
-    # Any string with only printable or whitespace characters is plain text.
-    if ''.join(text.split()).isprintable():
-        return text
-
-    # Assume this field is tokenized data that could not be decoded.
-    return encode.prefixed_base64(data, prefix)
-
-
-def detokenize_fields(detokenizer: detokenize.Detokenizer,
-                      proto: Message,
-                      prefix: str = encode.BASE64_PREFIX) -> None:
-    """Detokenizes fields annotated as tokenized in the given proto.
-
-    The fields are replaced with their detokenized version in the proto.
-    Tokenized fields are bytes fields, so the detokenized string is stored as
-    bytes. Call .decode() to convert the detokenized string from bytes to str.
-    """
-    for field in _tokenized_fields(proto):
-        decoded = decode_optionally_tokenized(detokenizer,
-                                              getattr(proto, field.name),
-                                              prefix)
-        setattr(proto, field.name, decoded.encode())
diff --git a/pw_tokenizer/py/pw_tokenizer/serial_detokenizer.py b/pw_tokenizer/py/pw_tokenizer/serial_detokenizer.py
index 234ca64..e010225 100644
--- a/pw_tokenizer/py/pw_tokenizer/serial_detokenizer.py
+++ b/pw_tokenizer/py/pw_tokenizer/serial_detokenizer.py
@@ -74,7 +74,8 @@
     serial_device = serial.Serial(port=device, baudrate=baudrate)
 
     try:
-        detokenizer.detokenize_base64_live(serial_device, output, prefix)
+        detokenize.detokenize_base64_live(detokenizer, serial_device, output,
+                                          prefix)
     except KeyboardInterrupt:
         output.flush()
 
diff --git a/pw_tokenizer/py/pw_tokenizer/tokens.py b/pw_tokenizer/py/pw_tokenizer/tokens.py
index f01c00a..663935e 100644
--- a/pw_tokenizer/py/pw_tokenizer/tokens.py
+++ b/pw_tokenizer/py/pw_tokenizer/tokens.py
@@ -45,8 +45,8 @@
     return char if isinstance(char, int) else ord(char)
 
 
-def pw_tokenizer_65599_hash(string: Union[str, bytes],
-                            hash_length: int = None) -> int:
+def pw_tokenizer_65599_fixed_length_hash(string: Union[str, bytes],
+                                         hash_length: int) -> int:
     """Hashes the provided string.
 
     This hash function is only used when adding tokens from legacy-style
@@ -63,7 +63,7 @@
 
 
 def default_hash(string: Union[str, bytes]) -> int:
-    return pw_tokenizer_65599_hash(string, DEFAULT_C_HASH_LENGTH)
+    return pw_tokenizer_65599_fixed_length_hash(string, DEFAULT_C_HASH_LENGTH)
 
 
 class _EntryKey(NamedTuple):
diff --git a/pw_tokenizer/py/pyproject.toml b/pw_tokenizer/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/pw_tokenizer/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/pw_tokenizer/py/setup.cfg b/pw_tokenizer/py/setup.cfg
deleted file mode 100644
index 99e075d..0000000
--- a/pw_tokenizer/py/setup.cfg
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pw_tokenizer
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Tools for working with tokenized strings
-
-[options]
-packages = pw_tokenizer
-zip_safe = False
-
-[options.package_data]
-pw_tokenizer = py.typed
diff --git a/pw_tokenizer/py/setup.py b/pw_tokenizer/py/setup.py
index fd5c1e6..04680f1 100644
--- a/pw_tokenizer/py/setup.py
+++ b/pw_tokenizer/py/setup.py
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2020 The Pigweed Authors
 #
 # 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
@@ -11,12 +11,19 @@
 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 # License for the specific language governing permissions and limitations under
 # the License.
-"""The pw_tokenizer package.
-
-Installing pw_tokenizer with this setup.py does not include the
-pw_tokenizer.proto package, since it contains a generated protobuf module. To
-access pw_tokenizer.proto, install pw_tokenizer from GN."""
+"""The pw_tokenizer package."""
 
 import setuptools  # type: ignore
 
-setuptools.setup()  # Package definition in setup.cfg
+setuptools.setup(
+    name='pw_tokenizer',
+    version='0.0.1',
+    author='Pigweed Authors',
+    author_email='pigweed-developers@googlegroups.com',
+    description='Tools for decoding tokenized strings',
+    packages=setuptools.find_packages(),
+    package_data={'pw_tokenizer': ['py.typed']},
+    zip_safe=False,
+    test_suite='setup.test_suite',
+    extra_requires=['serial'],
+)
diff --git a/pw_tokenizer/py/tokens_test.py b/pw_tokenizer/py/tokens_test.py
index c205762..1f67d5f 100755
--- a/pw_tokenizer/py/tokens_test.py
+++ b/pw_tokenizer/py/tokens_test.py
@@ -158,8 +158,8 @@
         self.assertEqual(answer.string, 'The answer: "%s"')
 
     def test_collisions(self):
-        hash_1 = tokens.pw_tokenizer_65599_hash('o000', 96)
-        hash_2 = tokens.pw_tokenizer_65599_hash('0Q1Q', 96)
+        hash_1 = tokens.pw_tokenizer_65599_fixed_length_hash('o000', 96)
+        hash_2 = tokens.pw_tokenizer_65599_fixed_length_hash('0Q1Q', 96)
         self.assertEqual(hash_1, hash_2)
 
         db = tokens.Database.from_strings(['o000', '0Q1Q'])
diff --git a/pw_tokenizer/simple_tokenize_test.cc b/pw_tokenizer/simple_tokenize_test.cc
index e4bd956..4112cef 100644
--- a/pw_tokenizer/simple_tokenize_test.cc
+++ b/pw_tokenizer/simple_tokenize_test.cc
@@ -51,24 +51,23 @@
 
 TEST(TokenizeStringLiteral, EmptyString_IsZero) {
   constexpr pw_tokenizer_Token token = PW_TOKENIZE_STRING("");
-  EXPECT_EQ(0u, token);
+  EXPECT_TRUE(0u == token);
 }
 
 TEST(TokenizeStringLiteral, String_MatchesHash) {
   constexpr uint32_t token = PW_TOKENIZE_STRING("[:-)");
-  EXPECT_EQ(TestHash("[:-)"), token);
+  EXPECT_TRUE(TestHash("[:-)") == token);
 }
 
 constexpr uint32_t kGlobalToken = PW_TOKENIZE_STRING(">:-[]");
 
 TEST(TokenizeStringLiteral, GlobalVariable_MatchesHash) {
-  EXPECT_EQ(TestHash(">:-[]"), kGlobalToken);
+  EXPECT_TRUE(TestHash(">:-[]") == kGlobalToken);
 }
 
 class TokenizeToBuffer : public ::testing::Test {
  public:
-  TokenizeToBuffer() : buffer_ {}
-  {}
+  TokenizeToBuffer() : buffer_{} {}
 
  protected:
   uint8_t buffer_[64];
@@ -81,7 +80,7 @@
 class GlobalMessage : public ::testing::Test {
  public:
   static void SetMessage(const uint8_t* message, size_t size) {
-    ASSERT_LE(size, sizeof(message_));
+    ASSERT_TRUE(size <= sizeof(message_));
     std::memcpy(message_, message, size);
     message_size_bytes_ = size;
   }
@@ -125,8 +124,8 @@
                    0x5C                     // char '.' (0x2E, zig-zag encoded)
                    >("%s there are %x (%.2f) of them%c");
   // clang-format on
-  ASSERT_EQ(expected.size(), message_size_bytes_);
-  EXPECT_EQ(std::memcmp(expected.data(), message_, expected.size()), 0);
+  ASSERT_TRUE(expected.size() == message_size_bytes_);
+  EXPECT_TRUE(std::memcmp(expected.data(), message_, expected.size()) == 0);
 }
 
 class TokenizeToGlobalHandler : public GlobalMessage<TokenizeToGlobalHandler> {
@@ -136,8 +135,8 @@
   PW_TOKENIZE_TO_GLOBAL_HANDLER("%x%lld%1.2f%s", 0, 0ll, -0.0, "");
   const auto expected =
       ExpectedData<0, 0, 0x00, 0x00, 0x00, 0x80, 0>("%x%lld%1.2f%s");
-  ASSERT_EQ(expected.size(), message_size_bytes_);
-  EXPECT_EQ(std::memcmp(expected.data(), message_, expected.size()), 0);
+  ASSERT_TRUE(expected.size() == message_size_bytes_);
+  EXPECT_TRUE(std::memcmp(expected.data(), message_, expected.size()) == 0);
 }
 
 extern "C" void pw_tokenizer_HandleEncodedMessage(
@@ -161,7 +160,7 @@
 intptr_t TokenizeToGlobalHandlerWithPayload::payload_;
 
 TEST_F(TokenizeToGlobalHandlerWithPayload, Variety) {
-  ASSERT_NE(payload_, 123);
+  ASSERT_TRUE(payload_ != 123);
 
   const auto expected =
       ExpectedData<0, 0, 0x00, 0x00, 0x00, 0x80, 0>("%x%lld%1.2f%s");
@@ -173,9 +172,9 @@
       0ll,
       -0.0,
       "");
-  ASSERT_EQ(expected.size(), message_size_bytes_);
-  EXPECT_EQ(std::memcmp(expected.data(), message_, expected.size()), 0);
-  EXPECT_EQ(payload_, 123);
+  ASSERT_TRUE(expected.size() == message_size_bytes_);
+  EXPECT_TRUE(std::memcmp(expected.data(), message_, expected.size()) == 0);
+  EXPECT_TRUE(payload_ == 123);
 
   PW_TOKENIZE_TO_GLOBAL_HANDLER_WITH_PAYLOAD(
       static_cast<pw_tokenizer_Payload>(-543),
@@ -184,9 +183,9 @@
       0ll,
       -0.0,
       "");
-  ASSERT_EQ(expected.size(), message_size_bytes_);
-  EXPECT_EQ(std::memcmp(expected.data(), message_, expected.size()), 0);
-  EXPECT_EQ(payload_, -543);
+  ASSERT_TRUE(expected.size() == message_size_bytes_);
+  EXPECT_TRUE(std::memcmp(expected.data(), message_, expected.size()) == 0);
+  EXPECT_TRUE(payload_ == -543);
 }
 
 extern "C" void pw_tokenizer_HandleEncodedMessageWithPayload(
diff --git a/pw_tokenizer/tokenize.cc b/pw_tokenizer/tokenize.cc
index 4d28010..d515f0c 100644
--- a/pw_tokenizer/tokenize.cc
+++ b/pw_tokenizer/tokenize.cc
@@ -42,7 +42,7 @@
   uint64_t value;  // value of the field
 };
 
-static_assert(sizeof(Metadata) == 32, "Metadata should be exactly 32 bytes");
+static_assert(sizeof(Metadata) == 32);
 
 // Store tokenization metadata in its own section. Mach-O files are not
 // supported by pw_tokenizer, but a short, Mach-O compatible section name is
diff --git a/pw_tokenizer/tokenize_test.cc b/pw_tokenizer/tokenize_test.cc
index f302e14..ff87f51 100644
--- a/pw_tokenizer/tokenize_test.cc
+++ b/pw_tokenizer/tokenize_test.cc
@@ -148,8 +148,7 @@
 
 class TokenizeToBuffer : public ::testing::Test {
  public:
-  TokenizeToBuffer() : buffer_ {}
-  {}
+  TokenizeToBuffer() : buffer_{} {}
 
  protected:
   uint8_t buffer_[64];
@@ -457,20 +456,11 @@
   }
 }
 
-#define MACRO_THAT_CALLS_ANOTHER_MACRO(action) ANOTHER_MACRO(action)
-
-#define ANOTHER_MACRO(action) action
-
-TEST_F(TokenizeToBuffer, AsArgumentToAnotherMacro) {
-  size_t message_size = sizeof(buffer_);
-  MACRO_THAT_CALLS_ANOTHER_MACRO(
-      PW_TOKENIZE_TO_BUFFER(buffer_, &message_size, __func__));
-  constexpr auto expected = ExpectedData(__func__);
-  ASSERT_EQ(expected.size(), message_size);
-  EXPECT_EQ(std::memcmp(expected.data(), buffer_, expected.size()), 0);
-}
-
-class TokenizeToCallback : public ::testing::Test {
+// Test fixture for callback and global handler. Both of these need a global
+// message buffer. To keep the message buffers separate, template this on the
+// derived class type.
+template <typename Impl>
+class GlobalMessage : public ::testing::Test {
  public:
   static void SetMessage(const uint8_t* message, size_t size) {
     ASSERT_LE(size, sizeof(message_));
@@ -479,7 +469,7 @@
   }
 
  protected:
-  TokenizeToCallback() {
+  GlobalMessage() {
     std::memset(message_, 0, sizeof(message_));
     message_size_bytes_ = 0;
   }
@@ -488,8 +478,12 @@
   static size_t message_size_bytes_;
 };
 
-uint8_t TokenizeToCallback::message_[256] = {};
-size_t TokenizeToCallback::message_size_bytes_ = 0;
+template <typename Impl>
+uint8_t GlobalMessage<Impl>::message_[256] = {};
+template <typename Impl>
+size_t GlobalMessage<Impl>::message_size_bytes_ = 0;
+
+class TokenizeToCallback : public GlobalMessage<TokenizeToCallback> {};
 
 TEST_F(TokenizeToCallback, Variety) {
   PW_TOKENIZE_TO_CALLBACK(
@@ -548,16 +542,6 @@
   EXPECT_EQ(std::memcmp(expected.data(), message_, expected.size()), 0);
 }
 
-TEST_F(TokenizeToCallback, AsArgumentToAnotherMacro) {
-  MACRO_THAT_CALLS_ANOTHER_MACRO(PW_TOKENIZE_TO_CALLBACK(SetMessage, __func__));
-  constexpr auto expected = ExpectedData(__func__);
-  ASSERT_EQ(expected.size(), message_size_bytes_);
-  EXPECT_EQ(std::memcmp(expected.data(), message_, expected.size()), 0);
-}
-
-#undef MACRO_THAT_CALLS_ANOTHER_MACRO
-#undef ANOTHER_MACRO
-
 // Hijack an internal macro to capture the tokenizer domain.
 #undef _PW_TOKENIZER_RECORD_ORIGINAL_STRING
 #define _PW_TOKENIZER_RECORD_ORIGINAL_STRING(token, domain, string) \
diff --git a/pw_tool/BUILD b/pw_tool/BUILD
new file mode 100644
index 0000000..304ec5b
--- /dev/null
+++ b/pw_tool/BUILD
@@ -0,0 +1,30 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_binary",
+)
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_binary(
+    name = "pw_tool",
+    srcs = [ "main.cc" ],
+    deps = [
+        "//pw_log",
+        "//pw_polyfill",
+    ]
+)
+
diff --git a/pw_tool/BUILD.bazel b/pw_tool/BUILD.bazel
deleted file mode 100644
index 2b6af38..0000000
--- a/pw_tool/BUILD.bazel
+++ /dev/null
@@ -1,29 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_binary",
-)
-
-licenses(["notice"])
-
-pw_cc_binary(
-    name = "pw_tool",
-    srcs = ["main.cc"],
-    deps = [
-        "//pw_log",
-        "//pw_polyfill",
-    ],
-)
diff --git a/pw_tool/OWNERS b/pw_tool/OWNERS
deleted file mode 100644
index 3afb926..0000000
--- a/pw_tool/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-frolv@google.com
diff --git a/pw_toolchain/BUILD b/pw_toolchain/BUILD
new file mode 100644
index 0000000..85a5ca0
--- /dev/null
+++ b/pw_toolchain/BUILD
@@ -0,0 +1,17 @@
+# Copyright 2019 The Pigweed Authors
+#
+# 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
+#
+#     https://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(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
diff --git a/pw_toolchain/BUILD.bazel b/pw_toolchain/BUILD.bazel
deleted file mode 100644
index 7bb1810..0000000
--- a/pw_toolchain/BUILD.bazel
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright 2019 The Pigweed Authors
-#
-# 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
-#
-#     https://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(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
diff --git a/pw_toolchain/OWNERS b/pw_toolchain/OWNERS
deleted file mode 100644
index 3afb926..0000000
--- a/pw_toolchain/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-frolv@google.com
diff --git a/pw_toolchain/arm_clang/BUILD.gn b/pw_toolchain/arm_clang/BUILD.gn
index 414b856..d4b73a9 100644
--- a/pw_toolchain/arm_clang/BUILD.gn
+++ b/pw_toolchain/arm_clang/BUILD.gn
@@ -36,14 +36,6 @@
   ldflags = [ "-Wl,-u_printf_float" ]
 }
 
-pw_clang_arm_config("cortex_m0plus") {
-  cflags = [ "-mcpu=cortex-m0plus" ]
-  cflags += cortex_m_common_flags
-  cflags += cortex_m_software_fpu_flags
-  asmflags = cflags
-  ldflags = cflags
-}
-
 pw_clang_arm_config("cortex_m3") {
   cflags = [ "-mcpu=cortex-m3" ]
   cflags += cortex_m_common_flags
diff --git a/pw_toolchain/arm_clang/clang_config.gni b/pw_toolchain/arm_clang/clang_config.gni
index de212b9..2c71a2f 100644
--- a/pw_toolchain/arm_clang/clang_config.gni
+++ b/pw_toolchain/arm_clang/clang_config.gni
@@ -12,8 +12,7 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
-_script_path =
-    get_path_info("../py/pw_toolchain/clang_arm_toolchain.py", "abspath")
+_script_path = rebase_path("../py/pw_toolchain/clang_arm_toolchain.py")
 
 # This template generates a config that can be used to target ARM cores using
 # a clang compiler.
diff --git a/pw_toolchain/arm_clang/toolchains.gni b/pw_toolchain/arm_clang/toolchains.gni
index 2171e67..91aea25 100644
--- a/pw_toolchain/arm_clang/toolchains.gni
+++ b/pw_toolchain/arm_clang/toolchains.gni
@@ -14,21 +14,18 @@
 
 import("//build_overrides/pigweed.gni")
 
-import("$dir_pw_toolchain/clang_tools.gni")
-
 # Specifies the tools used by host Clang toolchains.
 _arm_clang_toolchain = {
-  forward_variables_from(pw_toolchain_clang_tools, "*")
+  # Note: On macOS, there is no "llvm-ar", only "ar", which happens to be LLVM
+  # ar. This should get updated for linux systems.
+  ar = "ar"
+  cc = "clang"
+  cxx = "clang++"
 
   link_whole_archive = true
-
-  # Enable static analysis for arm clang based toolchains.
-  static_analysis = true
 }
 
 # Configs specific to different architectures.
-_cortex_m0plus = [ "$dir_pw_toolchain/arm_clang:cortex_m0plus" ]
-
 _cortex_m3 = [ "$dir_pw_toolchain/arm_clang:cortex_m3" ]
 
 _cortex_m4 = [ "$dir_pw_toolchain/arm_clang:cortex_m4" ]
@@ -37,27 +34,6 @@
 
 # Describes ARM clang toolchains for specific targets.
 pw_toolchain_arm_clang = {
-  cortex_m0plus_debug = {
-    name = "arm_clang_cortex_m0plus_debug"
-    forward_variables_from(_arm_clang_toolchain, "*")
-    defaults = {
-      default_configs = _cortex_m0plus + [ "$dir_pw_build:optimize_debugging" ]
-    }
-  }
-  cortex_m0plus_speed_optimized = {
-    name = "arm_clang_cortex_m0plus_speed_optimized"
-    forward_variables_from(_arm_clang_toolchain, "*")
-    defaults = {
-      default_configs = _cortex_m0plus + [ "$dir_pw_build:optimize_speed" ]
-    }
-  }
-  cortex_m0plus_size_optimized = {
-    name = "arm_clang_cortex_m0plus_size_optimized"
-    forward_variables_from(_arm_clang_toolchain, "*")
-    defaults = {
-      default_configs = _cortex_m0plus + [ "$dir_pw_build:optimize_size" ]
-    }
-  }
   cortex_m3_debug = {
     name = "arm_clang_cortex_m3_debug"
     forward_variables_from(_arm_clang_toolchain, "*")
@@ -127,9 +103,6 @@
 # it trivial to generate all the toolchains in this file via a
 # `generate_toolchains` target.
 pw_toolchain_arm_clang_list = [
-  pw_toolchain_arm_clang.cortex_m0plus_debug,
-  pw_toolchain_arm_clang.cortex_m0plus_speed_optimized,
-  pw_toolchain_arm_clang.cortex_m0plus_size_optimized,
   pw_toolchain_arm_clang.cortex_m3_debug,
   pw_toolchain_arm_clang.cortex_m3_speed_optimized,
   pw_toolchain_arm_clang.cortex_m3_size_optimized,
diff --git a/pw_toolchain/arm_gcc/BUILD.gn b/pw_toolchain/arm_gcc/BUILD.gn
index a5f8023..6d845b2 100644
--- a/pw_toolchain/arm_gcc/BUILD.gn
+++ b/pw_toolchain/arm_gcc/BUILD.gn
@@ -75,12 +75,6 @@
   ldflags = cflags
 }
 
-config("cortex_m33") {
-  cflags = [ "-mcpu=cortex-m33" ]
-  asmflags = cflags
-  ldflags = cflags
-}
-
 config("cortex_software_fpu") {
   cflags = [ "-mfloat-abi=soft" ]
   asmflags = cflags
@@ -106,13 +100,3 @@
   defines = [ "PW_ARMV7M_ENABLE_FPU=1" ]
   ldflags = cflags
 }
-
-config("cortex_hardware_fpu_v5_sp") {
-  cflags = [
-    "-mfloat-abi=hard",
-    "-mfpu=fpv5-sp-d16",
-  ]
-  asmflags = cflags
-  defines = [ "PW_ARMV7M_ENABLE_FPU=1" ]
-  ldflags = cflags
-}
diff --git a/pw_toolchain/arm_gcc/toolchains.gni b/pw_toolchain/arm_gcc/toolchains.gni
index 6595079..5d247e7 100644
--- a/pw_toolchain/arm_gcc/toolchains.gni
+++ b/pw_toolchain/arm_gcc/toolchains.gni
@@ -64,18 +64,6 @@
   "$dir_pw_toolchain/arm_gcc:cortex_hardware_fpu_v5",
 ]
 
-_cortex_m33 = [
-  "$dir_pw_toolchain/arm_gcc:cortex_common",
-  "$dir_pw_toolchain/arm_gcc:cortex_m33",
-  "$dir_pw_toolchain/arm_gcc:cortex_software_fpu",
-]
-
-_cortex_m33f = [
-  "$dir_pw_toolchain/arm_gcc:cortex_common",
-  "$dir_pw_toolchain/arm_gcc:cortex_m33",
-  "$dir_pw_toolchain/arm_gcc:cortex_hardware_fpu_v5_sp",
-]
-
 # Describes ARM GCC toolchains for specific targets.
 pw_toolchain_arm_gcc = {
   cortex_m0plus_debug = {
@@ -222,54 +210,6 @@
           _arm_gcc + _cortex_m7f + [ "$dir_pw_build:optimize_size" ]
     }
   }
-  cortex_m33_debug = {
-    name = "arm_gcc_cortex_m33_debug"
-    forward_variables_from(arm_gcc_toolchain_tools, "*")
-    defaults = {
-      default_configs =
-          _arm_gcc + _cortex_m33 + [ "$dir_pw_build:optimize_debugging" ]
-    }
-  }
-  cortex_m33_speed_optimized = {
-    name = "arm_gcc_cortex_m33_speed_optimized"
-    forward_variables_from(arm_gcc_toolchain_tools, "*")
-    defaults = {
-      default_configs =
-          _arm_gcc + _cortex_m33 + [ "$dir_pw_build:optimize_speed" ]
-    }
-  }
-  cortex_m33_size_optimized = {
-    name = "arm_gcc_cortex_m33_size_optimized"
-    forward_variables_from(arm_gcc_toolchain_tools, "*")
-    defaults = {
-      default_configs =
-          _arm_gcc + _cortex_m33 + [ "$dir_pw_build:optimize_size" ]
-    }
-  }
-  cortex_m33f_debug = {
-    name = "arm_gcc_cortex_m33f_debug"
-    forward_variables_from(arm_gcc_toolchain_tools, "*")
-    defaults = {
-      default_configs =
-          _arm_gcc + _cortex_m33f + [ "$dir_pw_build:optimize_debugging" ]
-    }
-  }
-  cortex_m33f_speed_optimized = {
-    name = "arm_gcc_cortex_m33f_speed_optimized"
-    forward_variables_from(arm_gcc_toolchain_tools, "*")
-    defaults = {
-      default_configs =
-          _arm_gcc + _cortex_m33f + [ "$dir_pw_build:optimize_speed" ]
-    }
-  }
-  cortex_m33f_size_optimized = {
-    name = "arm_gcc_cortex_m33f_size_optimized"
-    forward_variables_from(arm_gcc_toolchain_tools, "*")
-    defaults = {
-      default_configs =
-          _arm_gcc + _cortex_m33f + [ "$dir_pw_build:optimize_size" ]
-    }
-  }
 }
 
 # This list just contains the members of the above scope for convenience to make
@@ -294,10 +234,4 @@
   pw_toolchain_arm_gcc.cortex_m7f_debug,
   pw_toolchain_arm_gcc.cortex_m7f_speed_optimized,
   pw_toolchain_arm_gcc.cortex_m7f_size_optimized,
-  pw_toolchain_arm_gcc.cortex_m33_debug,
-  pw_toolchain_arm_gcc.cortex_m33_speed_optimized,
-  pw_toolchain_arm_gcc.cortex_m33_size_optimized,
-  pw_toolchain_arm_gcc.cortex_m33f_debug,
-  pw_toolchain_arm_gcc.cortex_m33f_speed_optimized,
-  pw_toolchain_arm_gcc.cortex_m33f_size_optimized,
 ]
diff --git a/pw_toolchain/clang_tools.gni b/pw_toolchain/clang_tools.gni
deleted file mode 100644
index 6ecd3b7..0000000
--- a/pw_toolchain/clang_tools.gni
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-declare_args() {
-  # This flag allows you to specify the root directory of the clang, clang++,
-  # and llvm-ar binaries to use when compiling with a clang-based toolchain.
-  # This is useful for debugging toolchain-related issues by building with an
-  # externally-provided toolchain.
-  pw_toolchain_CLANG_PREFIX = ""
-}
-
-pw_toolchain_clang_tools = {
-  if (pw_toolchain_CLANG_PREFIX != "") {
-    ar = pw_toolchain_CLANG_PREFIX + "/"
-    cc = pw_toolchain_CLANG_PREFIX + "/"
-    cxx = pw_toolchain_CLANG_PREFIX + "/"
-  } else {
-    ar = ""
-    cc = ""
-    cxx = ""
-  }
-
-  ar += "llvm-ar"
-  cc += "clang"
-  cxx += "clang++"
-}
diff --git a/pw_toolchain/default/BUILD.gn b/pw_toolchain/default/BUILD.gn
deleted file mode 100644
index e8ad524..0000000
--- a/pw_toolchain/default/BUILD.gn
+++ /dev/null
@@ -1,29 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_toolchain/non_c_toolchain.gni")
-
-# A toolchain that provides no C/C++ compiler for use as Pigweed's default
-# toolchain. This toolchain cannot compile C/C++, and trying to use it to do so
-# causes errors. The top-level BUILD.gn must explicitly specify the non-default
-# toolchains to use for each build.
-pw_non_c_toolchain("default") {
-  # If the user tries to build a target with the default toolchain, run a script
-  # printing out the error.
-  command = "python " +
-            rebase_path("$dir_pw_toolchain/py/pw_toolchain/bad_toolchain.py",
-                        root_build_dir)
-}
diff --git a/pw_toolchain/docs.rst b/pw_toolchain/docs.rst
index 81f6031..07e27fb 100644
--- a/pw_toolchain/docs.rst
+++ b/pw_toolchain/docs.rst
@@ -3,147 +3,25 @@
 ------------
 pw_toolchain
 ------------
-GN toolchains function both as a set of tools for compilation and as a workspace
-for evaluating build files. The same compilations and actions can be executed by
-different toolchains. Each toolchain maintains its own set of build args, and
-build steps from all toolchains can be executed in parallel.
+The ``pw_toolchain`` module enumerates GN toolchain definitions that may be used
+to build pigweed.
 
-Toolchains
-==========
-``pw_toolchain`` provides GN toolchains that may be used to build Pigweed. The
-following toolchains are defined:
+``pw_toolchain`` defines the following toolchains:
 
- - pw_toolchain_arm_clang.cortex_m0plus_debug
- - pw_toolchain_arm_clang.cortex_m0plus_speed_optimized
- - pw_toolchain_arm_clang.cortex_m0plus_size_optimized
- - pw_toolchain_arm_clang.cortex_m3_debug
- - pw_toolchain_arm_clang.cortex_m3_speed_optimized
- - pw_toolchain_arm_clang.cortex_m3_size_optimized
- - pw_toolchain_arm_clang.cortex_m4_debug
- - pw_toolchain_arm_clang.cortex_m4_speed_optimized
- - pw_toolchain_arm_clang.cortex_m4_size_optimized
- - pw_toolchain_arm_clang.cortex_m4f_debug
- - pw_toolchain_arm_clang.cortex_m4f_speed_optimized
- - pw_toolchain_arm_clang.cortex_m4f_size_optimized
- - pw_toolchain_arm_gcc.cortex_m0plus_debug
- - pw_toolchain_arm_gcc.cortex_m0plus_speed_optimized
- - pw_toolchain_arm_gcc.cortex_m0plus_size_optimized
- - pw_toolchain_arm_gcc.cortex_m3_debug
- - pw_toolchain_arm_gcc.cortex_m3_speed_optimized
- - pw_toolchain_arm_gcc.cortex_m3_size_optimized
- - pw_toolchain_arm_gcc.cortex_m4_debug
- - pw_toolchain_arm_gcc.cortex_m4_speed_optimized
- - pw_toolchain_arm_gcc.cortex_m4_size_optimized
- - pw_toolchain_arm_gcc.cortex_m4f_debug
- - pw_toolchain_arm_gcc.cortex_m4f_speed_optimized
- - pw_toolchain_arm_gcc.cortex_m4f_size_optimized
- - pw_toolchain_arm_gcc.cortex_m7_debug
- - pw_toolchain_arm_gcc.cortex_m7_speed_optimized
- - pw_toolchain_arm_gcc.cortex_m7_size_optimized
- - pw_toolchain_arm_gcc.cortex_m7f_debug
- - pw_toolchain_arm_gcc.cortex_m7f_speed_optimized
- - pw_toolchain_arm_gcc.cortex_m7f_size_optimized
- - pw_toolchain_arm_gcc.cortex_m33_debug
- - pw_toolchain_arm_gcc.cortex_m33_speed_optimized
- - pw_toolchain_arm_gcc.cortex_m33_size_optimized
- - pw_toolchain_arm_gcc.cortex_m33f_debug
- - pw_toolchain_arm_gcc.cortex_m33f_speed_optimized
- - pw_toolchain_arm_gcc.cortex_m33f_size_optimized
- - pw_toolchain_host_clang.debug
- - pw_toolchain_host_clang.speed_optimized
- - pw_toolchain_host_clang.size_optimized
- - pw_toolchain_host_clang.fuzz
- - pw_toolchain_host_gcc.debug
- - pw_toolchain_host_gcc.speed_optimized
- - pw_toolchain_host_gcc.size_optimized
+ - arm_gcc_cortex_m4_og
+ - arm_gcc_cortex_m4_o1
+ - arm_gcc_cortex_m4_o2
+ - arm_gcc_cortex_m4_os
+ - arm_gcc_cortex_m4f_og
+ - arm_gcc_cortex_m4f_o1
+ - arm_gcc_cortex_m4f_o2
+ - arm_gcc_cortex_m4f_os
+ - host_clang_og
+ - host_clang_o2
+ - host_clang_os
+ - host_gcc_og
+ - host_gcc_o2
+ - host_gcc_os
 
- .. note::
+.. note::
   The documentation for this module is currently incomplete.
-
-Non-C/C++ toolchains
-====================
-``pw_toolchain/non_c_toolchain.gni`` provides the ``pw_non_c_toolchain``
-template. This template creates toolchains that cannot compile C/C++ source
-code. These toolchains may only be used to execute GN actions or declare groups
-of targets in other toolchains. Attempting to compile C/C++ code with either of
-these toolchains results in errors.
-
-Non-C/C++ toolchains can be used to consolidate actions that should only occur
-once in a multi-toolchain build. Build targets from all toolchains can refer to
-these actions in a non-C/C++ toolchain so they only execute once instead of once
-per toolchain.
-
-For example, Pigweed runs protobuf compilation and Python package actions like
-installation and Pylint in toolchains created with ``pw_non_c_toolchain``. This
-allows all toolchains to cleanly share the same protobuf and Python declarations
-without any duplicated work.
-
-Testing other compiler versions
-===============================
-The clang-based toolchain provided by Pigweed can be substituted with another
-version by modifying the ``pw_toolchain_CLANG_PREFIX`` GN build argument to
-point to the directory that contains the desired clang, clang++, and llvm-ar
-binaries. This should only be used for debugging purposes. Pigweed does not
-officially support any compilers other than those provided by Pigweed.
-
-Running static analysis checks
-==============================
-``clang-tidy`` can be run as a compiler replacement, to analyze all sources
-built for a target. ``pw_toolchain/static_analysis_toolchain.gni`` provides
-the ``pw_static_analysis_toolchain`` template. This template creates toolchains
-that execute ``clang-tidy`` for C/C++ sources, and mock implementations of
-the ``link``, ``alink`` and ``solink`` tools.
-
-Additionally, ``generate_toolchain`` implements a boolean flag
-``static_analysis`` (default ``false``) which generates the derived
-toolchain ``${target_name}.static_analysis`` using
-``pw_generate_static_analysis_toolchain`` and the toolchain options.
-
-Excluding files from checks
----------------------------
-The build argument ``pw_toolchain_STATIC_ANALYSIS_SKIP_SOURCES_RES`` is used
-used to exclude source files from the analysis. The list must contain regular
-expressions matching individual files, rather than directories. For example,
-provide ``"the_path/.*"`` to exclude all files in all directories under
-``the_path``.
-
-The build argument ``pw_toolchain_STATIC_ANALYSIS_SKIP_INCLUDE_PATHS`` is used
-used to exclude header files from the analysis. This argument must be a list of
-POSIX-style path suffixes for include paths, or regular expressions. For
-example, passing ``the_path/include`` excludes all header files that are
-accessed from include paths ending in ``the_path/include``, while passing
-``.*/third_party/.*`` excludes all third-party header files.
-
-Provided toolchains
--------------------
-``pw_toolchain`` provides static analysis GN toolchains that may be used to
-test host targets:
-
- - pw_toolchain_host_clang.debug.static_analysis
- - pw_toolchain_host_clang.speed_optimized.static_analysis
- - pw_toolchain_host_clang.size_optimized.static_analysis
- - pw_toolchain_host_clang.fuzz.static_analysis
-   (if pw_toolchain_OSS_FUZZ_ENABLED is false)
- - pw_toolchain_arm_clang.debug.static_analysis
- - pw_toolchain_arm_clang.speed_optimized.static_analysis
- - pw_toolchain_arm_clang.size_optimized.static_analysis
-
- For example, to run ``clang-tidy`` on all source dependencies of the
- ``default`` target:
-
-.. code-block::
-
-  generate_toolchain("my_toolchain") {
-    ..
-    static_analysis = true
-  }
-
-  group("static_analysis") {
-    deps = [ ":default(my_toolchain.static_analysis)" ]
-  }
-
-.. warning::
-    The status of the static analysis checks might change when
-    any relevant .clang-tidy file is updated. You should
-    clean the output directory before invoking
-    ``clang-tidy``.
diff --git a/pw_toolchain/dummy/BUILD.gn b/pw_toolchain/dummy/BUILD.gn
new file mode 100644
index 0000000..ba0d738
--- /dev/null
+++ b/pw_toolchain/dummy/BUILD.gn
@@ -0,0 +1,68 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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("//build_overrides/pigweed.gni")
+
+import("$dir_pw_toolchain/universal_tools.gni")
+
+# A dummy toolchain which is set as the default for Pigweed. This is never used;
+# the top-level BUILD.gn enumerates the toolchains for each build.
+toolchain("dummy") {
+  tool("stamp") {
+    forward_variables_from(pw_universal_stamp, "*")
+  }
+
+  tool("copy") {
+    forward_variables_from(pw_universal_copy, "*")
+  }
+
+  # If the user tries to build a target with the default toolchain, run a script
+  # printing out the error.
+  _bad_toolchain_command =
+      "python " +
+      rebase_path("$dir_pw_toolchain/py/pw_toolchain/bad_toolchain.py")
+
+  tool("asm") {
+    command = _bad_toolchain_command
+    outputs =
+        [ "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o" ]
+  }
+
+  tool("cc") {
+    command = _bad_toolchain_command
+    outputs =
+        [ "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o" ]
+  }
+
+  tool("cxx") {
+    command = _bad_toolchain_command
+    outputs =
+        [ "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o" ]
+  }
+
+  tool("link") {
+    command = _bad_toolchain_command
+    outputs = [ "{{output_dir}}/{{target_output_name}}{{output_extension}}" ]
+  }
+
+  tool("alink") {
+    command = _bad_toolchain_command
+    outputs = [ "{{output_dir}}/{{target_output_name}}{{output_extension}}" ]
+  }
+
+  tool("solink") {
+    command = _bad_toolchain_command
+    outputs = [ "{{output_dir}}/{{target_output_name}}{{output_extension}}" ]
+  }
+}
diff --git a/pw_toolchain/generate_toolchain.gni b/pw_toolchain/generate_toolchain.gni
index db057c2..5be80eb 100644
--- a/pw_toolchain/generate_toolchain.gni
+++ b/pw_toolchain/generate_toolchain.gni
@@ -14,7 +14,6 @@
 
 import("//build_overrides/pigweed.gni")
 
-import("$dir_pw_toolchain/static_analysis_toolchain.gni")
 import("$dir_pw_toolchain/universal_tools.gni")
 
 declare_args() {
@@ -251,23 +250,26 @@
         _link_flags += [
           # Output a map file that shows symbols and their location.
           "-Wl,-map,$_link_mapfile",
+
+          # Delete unreferenced sections. Helpful with -ffunction-sections.
+          "-Wl,-dead_strip",
         ]
       } else {
         _link_flags += [
           # Output a map file that shows symbols and their location.
           "-Wl,-Map,$_link_mapfile",
+
+          # Delete unreferenced sections. Helpful with -ffunction-sections.
+          "-Wl,--gc-sections",
         ]
       }
 
-      _rsp_file = "$_link_outfile.rsp"
-      _rsp_contents = []
-
       _link_group = defined(invoker.link_group) && invoker.link_group
       if (_link_group) {
-        _rsp_contents += [ "-Wl,--start-group" ]
+        _link_flags += [ "-Wl,--start-group" ]
       }
-      _rsp_contents += [ "{{inputs}}" ]
-      _rsp_contents += [ "{{frameworks}}" ]
+      _link_flags += [ "{{inputs}}" ]
+      _link_flags += [ "{{frameworks}}" ]
 
       if (defined(invoker.link_whole_archive) && invoker.link_whole_archive) {
         # Load all object files from all libraries to resolve symbols.
@@ -278,29 +280,24 @@
         # Make sure you use this with --gc-sections, otherwise the
         # resulting binary will contain every symbol defined in every
         # input file and every static library. That could be quite a lot.
-        _rsp_contents += [
+        _link_flags += [
           "-Wl,--whole-archive",
           "{{libs}}",
           "-Wl,--no-whole-archive",
         ]
       } else {
-        _rsp_contents += [ "{{libs}}" ]
+        _link_flags += [ "{{libs}}" ]
       }
 
       if (_link_group) {
-        _rsp_contents += [ "-Wl,--end-group" ]
+        _link_flags += [ "-Wl,--end-group" ]
       }
-      _rsp_command = string_join(" ", _rsp_contents)
-
-      _link_flags += [ "@$_rsp_file" ]
       _link_flags += [ "-o $_link_outfile" ]
 
       _link_command = string_join(" ", _link_flags)
 
       tool("link") {
         command = _link_command
-        rspfile = _rsp_file
-        rspfile_content = _rsp_command
         description = "ld $_link_outfile"
         outputs = [
           _link_outfile,
@@ -319,8 +316,6 @@
 
       tool("solink") {
         command = _link_command + " -shared"
-        rspfile = _rsp_file
-        rspfile_content = _rsp_command
         description = "ld -shared $_link_outfile"
         outputs = [
           _link_outfile,
@@ -363,15 +358,6 @@
         forward_variables_from(invoker_toolchain_args, "*")
       }
     }
-
-    _generate_static_analysis_toolchain =
-        defined(invoker.static_analysis) && invoker.static_analysis
-
-    if (_generate_static_analysis_toolchain) {
-      pw_static_analysis_toolchain(target_name + ".static_analysis") {
-        forward_variables_from(invoker, "*")
-      }
-    }
   } else {
     not_needed(invoker, "*")
     group(target_name) {
diff --git a/pw_toolchain/host_clang/BUILD.gn b/pw_toolchain/host_clang/BUILD.gn
index bed41fb..fe30ba4 100644
--- a/pw_toolchain/host_clang/BUILD.gn
+++ b/pw_toolchain/host_clang/BUILD.gn
@@ -13,7 +13,6 @@
 # the License.
 
 import("//build_overrides/pigweed.gni")
-import("//build_overrides/pigweed_environment.gni")
 
 # See https://github.com/google/sanitizers
 config("sanitize_address") {
@@ -22,64 +21,12 @@
 }
 
 config("sanitize_memory") {
-  cflags = [
-    "-fsanitize=memory",
-
-    # Do not optimizes tail recursive calls to get better call stack.
-    "-fno-optimize-sibling-calls",
-
-    # Enable check after destruction detection.
-    "-fsanitize-memory-use-after-dtor",
-  ]
+  cflags = [ "-fsanitize=memory" ]
   ldflags = cflags
 }
 
 config("sanitize_undefined") {
-  cflags = [
-    "-fsanitize=undefined",
-
-    # Store the stack frame pointer in a register to get proper debug
-    # information.
-    "-fno-omit-frame-pointer",
-
-    # Exit the program on check failure. (The default is to continue execution,
-    # which prevents test frameworks from realizing the test has failed.)
-    "-fno-sanitize-recover=undefined",
-  ]
-  ldflags = cflags
-}
-
-# UBsan configuration that enables additional checks. These checks are
-# heuristic and may not correspond to undefined behavior.
-config("sanitize_undefined_heuristic") {
-  sanitizers = [
-    # Base checks for undefined behaviour.
-    "undefined",
-
-    # Checks for undefined or suspicious integer behavior.
-    "integer",
-
-    # Checks for floating point division by zero.
-    "float-divide-by-zero",
-
-    # Checks for suspicious behavior of implicit conversions.
-    "implicit-conversion",
-
-    # Checks for null as function arg, lvalue and return type.
-    "nullability",
-  ]
-  cflags = [
-    "-fsanitize=" + string_join(",", sanitizers),
-
-    # Store the stack frame pointer in a register to get proper debug
-    # information.
-    "-fno-omit-frame-pointer",
-  ]
-  ldflags = cflags
-}
-
-config("sanitize_thread") {
-  cflags = [ "-fsanitize=thread" ]
+  cflags = [ "-fsanitize=undefined" ]
   ldflags = cflags
 }
 
@@ -111,10 +58,10 @@
 # not on Linux) will fall back to the system libc++, which is
 # incompatible due to an ABI change.
 #
-# Pull the appropriate paths from our Pigweed env setup.
+# Pull the appropriate pathd from our Pigweed env setup.
 config("no_system_libcpp") {
   if (current_os == "mac") {
-    install_dir = dir_cipd_pigweed
+    install_dir = getenv("PW_PIGWEED_CIPD_INSTALL_DIR")
     assert(install_dir != "",
            "You forgot to activate the Pigweed environment; " +
                "did you source pw_env_setup/setup.sh?")
@@ -123,7 +70,7 @@
       "-nostdlib++",
 
       # Use the libc++ from CIPD.
-      dir_cipd_pigweed + "/lib/libc++.a",
+      getenv("PW_PIGWEED_CIPD_INSTALL_DIR") + "/lib/libc++.a",
     ]
   }
 }
diff --git a/pw_toolchain/host_clang/toolchain.cmake b/pw_toolchain/host_clang/toolchain.cmake
index ae34701..9f4dd6a 100644
--- a/pw_toolchain/host_clang/toolchain.cmake
+++ b/pw_toolchain/host_clang/toolchain.cmake
@@ -12,64 +12,14 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
-# Do not rely on the PW_ROOT environment variable being set through bootstrap.
-# Regardless of whether it's set or not the following include will ensure it is.
-include(${CMAKE_CURRENT_LIST_DIR}/../../pw_build/pigweed.cmake)
+include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
+
+pw_set_backend(pw_assert pw_assert_log)
+pw_set_backend(pw_chrono.system_clock pw_chrono_stl.system_clock)
+pw_set_backend(pw_log pw_log_basic)
+pw_set_backend(pw_rpc.system_server targets.host.system_rpc_server)
+pw_set_backend(pw_sync.mutex pw_sync_stl.mutex_backend)
+pw_set_backend(pw_sys_io pw_sys_io_stdio)
 
 set(CMAKE_C_COMPILER clang)
 set(CMAKE_CXX_COMPILER clang++)
-
-# TODO(pwbug/606): set up this facade in CMake
-# Use logging-based test output on host.
-# pw_set_backend(pw_unit_test.main pw_unit_test.logging_main)
-
-# Configure backend for assert facade.
-pw_set_backend(pw_assert pw_assert_basic)
-
-# Configure backend for logging facade.
-pw_set_backend(pw_log pw_log_basic)
-
-# Configure backends for pw_sync's facades.
-pw_set_backend(pw_sync.interrupt_spin_lock pw_sync_stl.interrupt_spin_lock)
-pw_set_backend(pw_sync.binary_semaphore pw_sync_stl.binary_semaphore_backend)
-pw_set_backend(pw_sync.counting_semaphore
-               pw_sync_stl.counting_semaphore_backend)
-pw_set_backend(pw_sync.mutex pw_sync_stl.mutex_backend)
-pw_set_backend(pw_sync.timed_mutex pw_sync_stl.timed_mutex_backend)
-pw_set_backend(pw_sync.thread_notification
-               pw_sync.binary_semaphore_thread_notification_backend)
-pw_set_backend(pw_sync.timed_thread_notification
-               pw_sync.binary_semaphore_timed_thread_notification_backend)
-
-# Configure backend for pw_sys_io facade.
-pw_set_backend(pw_sys_io pw_sys_io_stdio)
-
-# Configure backend for pw_rpc_system_server.
-pw_set_backend(pw_rpc.system_server targets.host.system_rpc_server)
-# TODO(hepler): set config to use global mutex
-
-# Configure backend for pw_chrono's facades.
-pw_set_backend(pw_chrono.system_clock pw_chrono_stl.system_clock)
-pw_set_backend(pw_chrono.system_timer pw_chrono_stl.system_timer)
-
-# Configure backends for pw_thread's facades.
-pw_set_backend(pw_thread.id pw_thread_stl.id)
-pw_set_backend(pw_thread.yield pw_thread_stl.yield)
-pw_set_backend(pw_thread.sleep pw_thread_stl.sleep)
-pw_set_backend(pw_thread.thread pw_thread_stl.thread)
-
-# TODO: Migrate this to match GN's tokenized trace setup.
-pw_set_backend(pw_trace pw_trace.null)
-
-# The CIPD provided Clang/LLVM toolchain must link against the matched
-# libc++ which is also from CIPD. However, by default, Clang on Mac (but
-# not on Linux) will fall back to the system libc++, which is
-# incompatible due to an ABI change.
-#
-# Pull the appropriate paths from our Pigweed env setup.
-if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
-    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -nostdlib++ $ENV{PW_PIGWEED_CIPD_INSTALL_DIR}/lib/libc++.a")
-endif()
-
-set(pw_build_WARNINGS pw_build.strict_warnings pw_build.extra_strict_warnings
-    CACHE STRING "" FORCE)
diff --git a/pw_toolchain/host_clang/toolchains.gni b/pw_toolchain/host_clang/toolchains.gni
index d9f9da7..b04c86a 100644
--- a/pw_toolchain/host_clang/toolchains.gni
+++ b/pw_toolchain/host_clang/toolchains.gni
@@ -14,11 +14,9 @@
 
 import("//build_overrides/pigweed.gni")
 
-import("$dir_pw_toolchain/clang_tools.gni")
-
 declare_args() {
-  # Sets the sanitizer to pass to clang. Valid values are "address", "memory",
-  # "thread", "undefined".
+  # Sets the sanitizer to pass to clang. Valid values are those for "-fsanitize"
+  # listed in https://clang.llvm.org/docs/UsersManual.html#id9.
   pw_toolchain_SANITIZERS = []
 
   # Indicates if this build is a part of OSS-Fuzz, which needs to be able to
@@ -29,27 +27,21 @@
 
 # Specifies the tools used by host Clang toolchains.
 _host_clang_toolchain = {
+  ar = "llvm-ar"
+
   if (pw_toolchain_OSS_FUZZ_ENABLED) {
-    # Just use the "llvm-ar" on the system path.
-    ar = "llvm-ar"
     cc = getenv("CC")
     cxx = getenv("CXX")
   } else {
-    forward_variables_from(pw_toolchain_clang_tools, "*")
+    cc = "clang"
+    cxx = "clang++"
   }
 
   is_host_toolchain = true
-
-  # Enable static analysis for host clang based toolchains,
-  # even with OSS-Fuzz enabled.
-  static_analysis = true
 }
 
 # Common default scope shared by all host Clang toolchains.
 _defaults = {
-  # TODO(pwbug/461) amend toolchain declaration process to
-  # remove this hack.
-  default_configs = []
   default_configs = [
     "$dir_pw_build:extra_debugging",
     "$dir_pw_toolchain/host_clang:no_system_libcpp",
@@ -64,10 +56,6 @@
     defaults = {
       forward_variables_from(_defaults, "*")
       default_configs += [ "$dir_pw_build:optimize_debugging" ]
-      foreach(sanitizer, pw_toolchain_SANITIZERS) {
-        default_configs +=
-            [ "$dir_pw_toolchain/host_clang:sanitize_$sanitizer" ]
-      }
     }
   }
 
@@ -77,10 +65,6 @@
     defaults = {
       forward_variables_from(_defaults, "*")
       default_configs += [ "$dir_pw_build:optimize_speed" ]
-      foreach(sanitizer, pw_toolchain_SANITIZERS) {
-        default_configs +=
-            [ "$dir_pw_toolchain/host_clang:sanitize_$sanitizer" ]
-      }
     }
   }
 
@@ -90,10 +74,6 @@
     defaults = {
       forward_variables_from(_defaults, "*")
       default_configs += [ "$dir_pw_build:optimize_size" ]
-      foreach(sanitizer, pw_toolchain_SANITIZERS) {
-        default_configs +=
-            [ "$dir_pw_toolchain/host_clang:sanitize_$sanitizer" ]
-      }
     }
   }
 
@@ -120,67 +100,6 @@
       }
     }
   }
-
-  asan = {
-    name = "host_clang_asan"
-    forward_variables_from(_host_clang_toolchain, "*")
-    defaults = {
-      forward_variables_from(_defaults, "*")
-
-      # Use debug mode to get proper debug information.
-      default_configs += [ "$dir_pw_build:optimize_debugging" ]
-      default_configs += [ "$dir_pw_toolchain/host_clang:sanitize_address" ]
-    }
-  }
-
-  ubsan = {
-    name = "host_clang_ubsan"
-    forward_variables_from(_host_clang_toolchain, "*")
-    defaults = {
-      forward_variables_from(_defaults, "*")
-
-      # Use debug mode to get proper debug information.
-      default_configs += [ "$dir_pw_build:optimize_debugging" ]
-      default_configs += [ "$dir_pw_toolchain/host_clang:sanitize_undefined" ]
-    }
-  }
-
-  ubsan_heuristic = {
-    name = "host_clang_ubsan_heuristic"
-    forward_variables_from(_host_clang_toolchain, "*")
-    defaults = {
-      forward_variables_from(_defaults, "*")
-
-      # Use debug mode to get proper debug information.
-      default_configs += [ "$dir_pw_build:optimize_debugging" ]
-      default_configs +=
-          [ "$dir_pw_toolchain/host_clang:sanitize_undefined_heuristic" ]
-    }
-  }
-
-  msan = {
-    name = "host_clang_msan"
-    forward_variables_from(_host_clang_toolchain, "*")
-    defaults = {
-      forward_variables_from(_defaults, "*")
-
-      # Use debug mode to get proper debug information.
-      default_configs += [ "$dir_pw_build:optimize_debugging" ]
-      default_configs += [ "$dir_pw_toolchain/host_clang:sanitize_memory" ]
-    }
-  }
-
-  tsan = {
-    name = "host_clang_tsan"
-    forward_variables_from(_host_clang_toolchain, "*")
-    defaults = {
-      forward_variables_from(_defaults, "*")
-
-      # Use debug mode to get proper debug information.
-      default_configs += [ "$dir_pw_build:optimize_debugging" ]
-      default_configs += [ "$dir_pw_toolchain/host_clang:sanitize_thread" ]
-    }
-  }
 }
 
 # Describes host clang toolchains.
@@ -189,9 +108,4 @@
   pw_toolchain_host_clang.speed_optimized,
   pw_toolchain_host_clang.size_optimized,
   pw_toolchain_host_clang.fuzz,
-  pw_toolchain_host_clang.asan,
-  pw_toolchain_host_clang.ubsan,
-  pw_toolchain_host_clang.ubsan_heuristic,
-  pw_toolchain_host_clang.msan,
-  pw_toolchain_host_clang.tsan,
 ]
diff --git a/pw_toolchain/host_gcc/toolchain.cmake b/pw_toolchain/host_gcc/toolchain.cmake
index 4529f69..cf87958 100644
--- a/pw_toolchain/host_gcc/toolchain.cmake
+++ b/pw_toolchain/host_gcc/toolchain.cmake
@@ -12,54 +12,14 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
-# Do not rely on the PW_ROOT environment variable being set through bootstrap.
-# Regardless of whether it's set or not the following include will ensure it is.
-include(${CMAKE_CURRENT_LIST_DIR}/../../pw_build/pigweed.cmake)
+include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
+
+pw_set_backend(pw_assert pw_assert_log)
+pw_set_backend(pw_chrono.system_clock pw_chrono_stl.system_clock)
+pw_set_backend(pw_log pw_log_basic)
+pw_set_backend(pw_rpc.system_server targets.host.system_rpc_server)
+pw_set_backend(pw_sync.mutex pw_sync_stl.mutex_backend)
+pw_set_backend(pw_sys_io pw_sys_io_stdio)
 
 set(CMAKE_C_COMPILER gcc)
 set(CMAKE_CXX_COMPILER g++)
-
-# TODO(pwbug/606): set up this facade in CMake
-# Use logging-based test output on host.
-# pw_set_backend(pw_unit_test.main pw_unit_test.logging_main)
-
-# Configure backend for assert facade.
-pw_set_backend(pw_assert pw_assert_basic)
-
-# Configure backend for logging facade.
-pw_set_backend(pw_log pw_log_basic)
-
-# Configure backends for pw_sync's facades.
-pw_set_backend(pw_sync.interrupt_spin_lock pw_sync_stl.interrupt_spin_lock)
-pw_set_backend(pw_sync.binary_semaphore pw_sync_stl.binary_semaphore_backend)
-pw_set_backend(pw_sync.counting_semaphore
-               pw_sync_stl.counting_semaphore_backend)
-pw_set_backend(pw_sync.mutex pw_sync_stl.mutex_backend)
-pw_set_backend(pw_sync.timed_mutex pw_sync_stl.timed_mutex_backend)
-pw_set_backend(pw_sync.thread_notification
-               pw_sync.binary_semaphore_thread_notification_backend)
-pw_set_backend(pw_sync.timed_thread_notification
-               pw_sync.binary_semaphore_timed_thread_notification_backend)
-
-# Configure backend for pw_sys_io facade.
-pw_set_backend(pw_sys_io pw_sys_io_stdio)
-
-# Configure backend for pw_rpc_system_server.
-pw_set_backend(pw_rpc.system_server targets.host.system_rpc_server)
-# TODO(hepler): set config to use global mutex
-
-# Configure backend for pw_chrono's facades.
-pw_set_backend(pw_chrono.system_clock pw_chrono_stl.system_clock)
-pw_set_backend(pw_chrono.system_timer pw_chrono_stl.system_timer)
-
-# Configure backends for pw_thread's facades.
-pw_set_backend(pw_thread.id pw_thread_stl.id)
-pw_set_backend(pw_thread.yield pw_thread_stl.yield)
-pw_set_backend(pw_thread.sleep pw_thread_stl.sleep)
-pw_set_backend(pw_thread.thread pw_thread_stl.thread)
-
-# TODO: Migrate this to match GN's tokenized trace setup.
-pw_set_backend(pw_trace pw_trace.null)
-
-set(pw_build_WARNINGS pw_build.strict_warnings pw_build.extra_strict_warnings
-    CACHE STRING "" FORCE)
diff --git a/pw_toolchain/non_c_toolchain.gni b/pw_toolchain/non_c_toolchain.gni
deleted file mode 100644
index 53f7bdd..0000000
--- a/pw_toolchain/non_c_toolchain.gni
+++ /dev/null
@@ -1,106 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-# Creates a toolchain that provides no C/C++ compiler. It can be used for
-# non-C/C++ languages or actions that should only happen once across all builds.
-# The toolchain cannot compile C/C++, and trying to use it to is an error.
-#
-# Args:
-#   command: Run this command if this toolchain is used to build C/C++ code.
-#
-template("pw_non_c_toolchain") {
-  # Import the universal stamp & copy tools.
-  import("$dir_pw_toolchain/universal_tools.gni")
-  _label = get_label_info(":$target_name", "label_no_toolchain")
-
-  # If the user tries to build a target with this toolchain, run a script that
-  # prints out an error.
-  _message =
-      "Attempted to use the $target_name toolchain to compile {{source}}.\n" +
-      "This toolchain cannot be used to compile C/C++ source code.\n\n" +
-      "This toolchain was either explicitly specified in a deps list with\n" +
-      "GN's :target($_label) syntax or was set as the\n" +
-      "default toolchain in the BUILDCONFIG.gn file.\n\n" +
-      "Ensure that no C/C++ GN targets are referred to with this toolchain,\n" +
-      "even transitively.\n\n" +
-      "See https://pigweed.dev/pw_toolchain for more information."
-
-  _command = string_join(" ",
-                         [
-                           "python",
-                           rebase_path("$dir_pw_build/py/pw_build/error.py",
-                                       root_build_dir),
-                           "--message \"$_message\"",
-                           "--target",
-                           _label,
-                           "--root",
-                           rebase_path("//", root_build_dir),
-                           "--out",
-                           ".",
-                         ])
-
-  if (defined(invoker.command)) {
-    _command = invoker.command
-  } else {
-    not_needed([ "invoker" ])
-  }
-
-  toolchain(target_name) {
-    tool("stamp") {
-      forward_variables_from(pw_universal_stamp, "*")
-    }
-
-    tool("copy") {
-      forward_variables_from(pw_universal_copy, "*")
-    }
-
-    tool("asm") {
-      command = _command
-      outputs =
-          [ "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o" ]
-    }
-
-    tool("cc") {
-      command = _command
-      outputs =
-          [ "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o" ]
-    }
-
-    tool("cxx") {
-      command = _command
-      outputs =
-          [ "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o" ]
-    }
-
-    # Can't use {{source}} for the linker, so replace it if it's in the command.
-    _command_no_source = string_replace(_command, "{{source}}", "C/C++ sources")
-
-    tool("link") {
-      command = _command_no_source
-      outputs = [ "{{output_dir}}/{{target_output_name}}{{output_extension}}" ]
-    }
-
-    tool("alink") {
-      command = _command_no_source
-      outputs = [ "{{output_dir}}/{{target_output_name}}{{output_extension}}" ]
-    }
-
-    tool("solink") {
-      command = _command_no_source
-      outputs = [ "{{output_dir}}/{{target_output_name}}{{output_extension}}" ]
-    }
-  }
-}
diff --git a/pw_toolchain/py/BUILD.gn b/pw_toolchain/py/BUILD.gn
index ca04dd6..ee7df0a 100644
--- a/pw_toolchain/py/BUILD.gn
+++ b/pw_toolchain/py/BUILD.gn
@@ -17,18 +17,12 @@
 import("$dir_pw_build/python.gni")
 
 pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
+  setup = [ "setup.py" ]
   sources = [
     "pw_toolchain/__init__.py",
     "pw_toolchain/bad_toolchain.py",
     "pw_toolchain/clang_arm_toolchain.py",
-    "pw_toolchain/clang_tidy.py",
     "pw_toolchain/copy_with_metadata.py",
   ]
-  tests = [ "clang_tidy_test.py" ]
   pylintrc = "$dir_pigweed/.pylintrc"
 }
diff --git a/pw_toolchain/py/clang_tidy_test.py b/pw_toolchain/py/clang_tidy_test.py
deleted file mode 100644
index c6ac615..0000000
--- a/pw_toolchain/py/clang_tidy_test.py
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests for clang_tidy."""
-
-import pathlib
-import unittest
-from unittest import mock
-
-from pw_toolchain import clang_tidy
-
-
-class ClangTidyTest(unittest.TestCase):
-    """Unit tests for the clang-tidy wrapper."""
-    @mock.patch('subprocess.run', autospec=True)
-    def test_source_exclude_filters(self, mock_run):
-        # Build the path using joinpath to use OS-appropriate separators on both
-        # Windows and Linux.
-        source_file = pathlib.Path('..').joinpath('third_party').joinpath(
-            'somefile.cc')
-        source_root = pathlib.Path('..')
-        source_exclude = ['third_party.*']
-        got = clang_tidy.main(False, 'clang-tidy', source_file, source_root,
-                              None, source_exclude, list(), list())
-
-        # Return code is zero.
-        self.assertEqual(got, 0)
-        # No calls to subprocess: we filtered out the source file.
-        self.assertEqual(len(mock_run.mock_calls), 0)
-
-    @mock.patch('subprocess.run', autospec=True)
-    def test_source_exclude_does_not_filter(self, mock_run):
-        mock_run.return_value.returncode = 0
-        source_file = pathlib.Path('..').joinpath('third_party').joinpath(
-            'somefile.cc')
-        source_root = pathlib.Path('..')
-        source_exclude = ['someotherdir.*']
-        got = clang_tidy.main(False, 'clang-tidy', source_file, source_root,
-                              None, source_exclude, list(), list())
-
-        # Return code is zero.
-        self.assertEqual(got, 0)
-        # One call to subprocess: we did not filter out the source file.
-        # There will be more than one mock call because accessing return value
-        # attributes also produces mock_calls.
-        self.assertGreater(len(mock_run.mock_calls), 0)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_toolchain/py/pw_toolchain/clang_arm_toolchain.py b/pw_toolchain/py/pw_toolchain/clang_arm_toolchain.py
index 5c0cf61..7494727 100644
--- a/pw_toolchain/py/pw_toolchain/clang_arm_toolchain.py
+++ b/pw_toolchain/py/pw_toolchain/clang_arm_toolchain.py
@@ -32,7 +32,6 @@
 
 import argparse
 import sys
-import os
 import subprocess
 
 from pathlib import Path
@@ -89,10 +88,8 @@
 
 def get_compiler_info(cflags: List[str]) -> Dict[str, str]:
     compiler_info: Dict[str, str] = {}
-    compiler_info['gcc_libs_dir'] = os.path.relpath(
-        str(get_gcc_lib_dir(cflags)), ".")
-    compiler_info['sysroot'] = os.path.relpath(
-        _compiler_info_command('-print-sysroot', cflags), ".")
+    compiler_info['gcc_libs_dir'] = str(get_gcc_lib_dir(cflags))
+    compiler_info['sysroot'] = _compiler_info_command('-print-sysroot', cflags)
     compiler_info['version'] = _compiler_info_command('-dumpversion', cflags)
     compiler_info['multi_dir'] = _compiler_info_command(
         '-print-multi-directory', cflags)
diff --git a/pw_toolchain/py/pw_toolchain/clang_tidy.py b/pw_toolchain/py/pw_toolchain/clang_tidy.py
deleted file mode 100644
index fd7c625..0000000
--- a/pw_toolchain/py/pw_toolchain/clang_tidy.py
+++ /dev/null
@@ -1,182 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Invoke clang-tidy.
-
-Implements additional features compared to directly calling
-clang-tidy:
-  - add option `--source-exclude` to exclude matching sources from the
-    clang-tidy analysis.
-  - inputs the full compile command, with the cc binary name
-  - TODO: infer platform options from the full compile command
-"""
-
-import argparse
-import logging
-from pathlib import Path
-import re
-import shlex
-import subprocess
-import sys
-from typing import Iterable, List, Optional, Union
-
-_LOG = logging.getLogger(__name__)
-
-
-def _parse_args() -> argparse.Namespace:
-    """Parses arguments for this script, splitting out the command to run."""
-
-    parser = argparse.ArgumentParser()
-    parser.add_argument('-v',
-                        '--verbose',
-                        action='store_true',
-                        help='Run clang_tidy with extra debug output.')
-
-    parser.add_argument('--clang-tidy',
-                        default='clang-tidy',
-                        help='Path to clang-tidy executable.')
-
-    parser.add_argument(
-        '--source-file',
-        required=True,
-        type=Path,
-        help='Path to the source file to analyze with clang-tidy.')
-    parser.add_argument(
-        '--source-root',
-        required=True,
-        type=Path,
-        help=('Path to the root source directory.'
-              ' The relative path from the root directory is matched'
-              ' against source filter rather than the absolute path.'))
-    parser.add_argument(
-        '--export-fixes',
-        required=False,
-        type=Path,
-        help=('YAML file to store suggested fixes in. The '
-              'stored fixes can be applied to the input source '
-              'code with clang-apply-replacements.'))
-
-    parser.add_argument('--source-exclude',
-                        default=[],
-                        action='append',
-                        type=str,
-                        help=('Regular expressions matching the paths of'
-                              ' source files to be excluded from the'
-                              ' analysis.'))
-
-    parser.add_argument(
-        '--skip-include-path',
-        default=[],
-        nargs='*',
-        help=('Exclude include paths ending in these paths from clang-tidy. '
-              'These paths are switched from -I to -isystem so clang-tidy '
-              'ignores them.'))
-
-    # Add a silent placeholder arg for everything that was left over.
-    parser.add_argument('extra_args',
-                        nargs=argparse.REMAINDER,
-                        help=argparse.SUPPRESS)
-
-    parsed_args = parser.parse_args()
-
-    if parsed_args.extra_args[0] != '--':
-        parser.error('arguments not correctly split')
-
-    parsed_args.extra_args = parsed_args.extra_args[1:]
-    return parsed_args
-
-
-def _filter_include_paths(args: Iterable[str],
-                          skip_include_paths: Iterable[str]) -> Iterable[str]:
-    filters = [f.rstrip('/') for f in skip_include_paths]
-
-    for arg in args:
-        if arg.startswith('-I'):
-            path = Path(arg[2:]).as_posix()
-            if any(
-                    path.endswith(f) or re.match(f, str(path))
-                    for f in filters):
-                yield '-isystem' + arg[2:]
-                continue
-
-        yield arg
-
-
-def run_clang_tidy(clang_tidy: str, verbose: bool, source_file: Path,
-                   export_fixes: Optional[Path], skip_include_path: List[str],
-                   extra_args: List[str]) -> int:
-    """Executes clang_tidy via subprocess. Returns true if no failures."""
-    command: List[Union[str, Path]] = [clang_tidy, source_file, '--use-color']
-
-    if not verbose:
-        command.append('--quiet')
-
-    if export_fixes is not None:
-        command.extend(['--export-fixes', export_fixes])
-
-    # Append extra compilation flags. extra_args[0] is skipped as it contains
-    # the compiler binary name.
-    command.append('--')
-    command.extend(_filter_include_paths(extra_args[1:], skip_include_path))
-
-    process = subprocess.run(
-        command,
-        stdout=subprocess.PIPE,
-        # clang-tidy prints regular information on
-        # stderr, even with the option --quiet.
-        stderr=subprocess.PIPE)
-
-    if process.returncode != 0:
-        _LOG.warning('%s', ' '.join(shlex.quote(str(arg)) for arg in command))
-
-    if process.stdout:
-        _LOG.warning(process.stdout.decode().strip())
-
-    if process.stderr and process.returncode != 0:
-        _LOG.error(process.stderr.decode().strip())
-
-    return process.returncode
-
-
-def main(
-    verbose: bool,
-    clang_tidy: str,
-    source_file: Path,
-    source_root: Path,
-    export_fixes: Optional[Path],
-    source_exclude: List[str],
-    skip_include_path: List[str],
-    extra_args: List[str],
-) -> int:
-    # Rebase the source file path on source_root.
-    # If source_file is not relative to source_root (which may be the case for
-    # generated files) stick with the original source_file.
-    try:
-        relative_source_file = source_file.relative_to(source_root)
-    except ValueError:
-        relative_source_file = source_file
-
-    for pattern in source_exclude:
-        if re.match(pattern, str(relative_source_file)):
-            return 0
-
-    source_file_path = source_file.resolve()
-    export_fixes_path = (export_fixes.resolve()
-                         if export_fixes is not None else None)
-    return run_clang_tidy(clang_tidy, verbose, source_file_path,
-                          export_fixes_path, skip_include_path, extra_args)
-
-
-if __name__ == '__main__':
-    sys.exit(main(**vars(_parse_args())))
diff --git a/pw_toolchain/py/pyproject.toml b/pw_toolchain/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/pw_toolchain/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/pw_toolchain/py/setup.cfg b/pw_toolchain/py/setup.cfg
deleted file mode 100644
index 6ddd198..0000000
--- a/pw_toolchain/py/setup.cfg
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pw_toolchain
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Pigweed toolchain wrapper
-
-[options]
-packages = find:
-zip_safe = False
-
-[options.package_data]
-pw_toolchain = py.typed
diff --git a/pw_toolchain/py/setup.py b/pw_toolchain/py/setup.py
index ecfac6e..088fe50 100644
--- a/pw_toolchain/py/setup.py
+++ b/pw_toolchain/py/setup.py
@@ -15,4 +15,11 @@
 
 import setuptools  # type: ignore
 
-setuptools.setup()  # Package definition in setup.cfg
+setuptools.setup(name='pw_toolchain',
+                 version='0.0.1',
+                 author='Pigweed Authors',
+                 author_email='pigweed-developers@googlegroups.com',
+                 description='Pigweed toolchain wrapper',
+                 packages=setuptools.find_packages(),
+                 package_data={'pw_toolchain': ['py.typed']},
+                 zip_safe=False)
diff --git a/pw_toolchain/static_analysis_toolchain.gni b/pw_toolchain/static_analysis_toolchain.gni
deleted file mode 100644
index 732f209..0000000
--- a/pw_toolchain/static_analysis_toolchain.gni
+++ /dev/null
@@ -1,230 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_toolchain/universal_tools.gni")
-
-declare_args() {
-  # Regular expressions matching the paths of the source files to be excluded
-  # from the analysis. clang-tidy provides no alternative option.
-  #
-  # For example, the following disables clang-tidy on all source files in the
-  # third_party directory:
-  #
-  #   pw_toolchain_STATIC_ANALYSIS_SKIP_SOURCES_RES = ["third_party/.*"]
-  #
-  pw_toolchain_STATIC_ANALYSIS_SKIP_SOURCES_RES = []
-
-  # Disable clang-tidy for specific include paths. In the clang-tidy command,
-  # include paths that end with one of these, or match as a regular expression,
-  # are switched from -I to -isystem, which causes clang-tidy to ignore them.
-  # Unfortunately, clang-tidy provides no other way to filter header files.
-  #
-  # For example, the following ignores header files in "mbedtls/include":
-  #
-  #   pw_toolchain_STATIC_ANALYSIS_SKIP_INCLUDE_PATHS = ["mbedtls/include"]
-  #
-  # While the following ignores all third-party header files:
-  #
-  #   pw_toolchain_STATIC_ANALYSIS_SKIP_INCLUDE_PATHS = [".*/third_party/.*"]
-  #
-  pw_toolchain_STATIC_ANALYSIS_SKIP_INCLUDE_PATHS = []
-}
-
-# Creates a toolchain target for static analysis.
-#
-# The generated toolchain runs clang-tidy on all source files that are not
-# excluded by pw_toolchain_STATIC_ANALYSIS_SKIP_SOURCES_RES or
-# pw_toolchain_STATIC_ANALYSIS_SKIP_INCLUDE_PATHS.
-#
-# Args:
-#   cc: (required) String indicating the C compiler to use.
-#   cxx: (required) String indicating the C++ compiler to use.
-template("pw_static_analysis_toolchain") {
-  invoker_toolchain_args = invoker.defaults
-
-  # Clang tidy is invoked by a wrapper script which implements the missing
-  # option --source-filter.
-  _clang_tidy_py_path =
-      rebase_path("$dir_pw_toolchain/py/pw_toolchain/clang_tidy.py",
-                  root_build_dir)
-  _clang_tidy_py = "${python_path} ${_clang_tidy_py_path}"
-  _source_root = rebase_path("//", root_build_dir)
-  _source_exclude = ""
-  foreach(pattern, pw_toolchain_STATIC_ANALYSIS_SKIP_SOURCES_RES) {
-    _source_exclude = _source_exclude + " --source-exclude '${pattern}'"
-  }
-
-  _skip_include_path =
-      "--skip-include-path " +
-      string_join(" ", pw_toolchain_STATIC_ANALYSIS_SKIP_INCLUDE_PATHS)
-
-  toolchain(target_name) {
-    # Uncomment this line to see which toolchains generate other toolchains.
-    # print("Generating toolchain: ${target_name} by ${current_toolchain}")
-
-    tool("asm") {
-      depfile = "{{output}}.d"
-      command = pw_universal_stamp.command
-      depsformat = "gcc"
-      description = "as {{output}}"
-      outputs = [
-        # Use {{source_file_part}}, which includes the extension, instead of
-        # {{source_name_part}} so that object files created from <file_name>.c
-        # and <file_name>.cc sources are unique.
-        "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o",
-      ]
-    }
-
-    assert(defined(invoker.cc), "toolchain is missing 'cc'")
-    tool("cc") {
-      depfile = "{{output}}.d"
-      command = string_join(" ",
-                            [
-                              _clang_tidy_py,
-                              _source_exclude,
-                              _skip_include_path,
-                              "--source-file {{source}}",
-                              "--source-root '${_source_root}'",
-                              "--export-fixes {{output}}.yaml",
-                              "--",
-                              invoker.cc,
-                              "-MMD -MF $depfile",  # Write out dependencies.
-                              "{{cflags}}",
-                              "{{cflags_c}}",  # Must come after {{cflags}}.
-                              "{{defines}}",
-                              "{{include_dirs}}",
-                              "-c {{source}}",
-                              "-o {{output}}",
-                            ]) + " && touch {{output}}"
-      depsformat = "gcc"
-      description = "clang-tidy {{source}}"
-      outputs =
-          [ "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o" ]
-    }
-
-    assert(defined(invoker.cxx), "toolchain is missing 'cxx'")
-    tool("cxx") {
-      depfile = "{{output}}.d"
-      command = string_join(" ",
-                            [
-                              _clang_tidy_py,
-                              _source_exclude,
-                              _skip_include_path,
-                              "--source-file {{source}}",
-                              "--source-root '${_source_root}'",
-                              "--export-fixes {{output}}.yaml",
-                              "--",
-                              invoker.cxx,
-                              "-MMD -MF $depfile",  # Write out dependencies.
-                              "{{cflags}}",
-                              "{{cflags_cc}}",  # Must come after {{cflags}}.
-                              "{{defines}}",
-                              "{{include_dirs}}",
-                              "-c {{source}}",
-                              "-o {{output}}",
-                            ]) + " && touch {{output}}"
-      depsformat = "gcc"
-      description = "clang-tidy {{source}}"
-      outputs =
-          [ "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o" ]
-    }
-
-    tool("objc") {
-      depfile = "{{output}}.d"
-      command = pw_universal_stamp.command
-      depsformat = "gcc"
-      description = "objc {{source}}"
-      outputs =
-          [ "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o" ]
-    }
-
-    tool("objcxx") {
-      depfile = "{{output}}.d"
-      command = pw_universal_stamp.command
-      depsformat = "gcc"
-      description = "objc++ {{output}}"
-      outputs =
-          [ "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o" ]
-    }
-
-    tool("alink") {
-      command = "rm -f {{output}} && touch {{output}}"
-      description = "ar {{target_output_name}}{{output_extension}}"
-      outputs = [ "{{output_dir}}/{{target_output_name}}{{output_extension}}" ]
-      default_output_extension = ".a"
-      default_output_dir = "{{target_out_dir}}/lib"
-    }
-
-    tool("link") {
-      if (host_os == "win") {
-        # Force the extension to '.bat', empty bat scripts are still
-        # executable and will not raise errors.
-        _output = "{{output_dir}}/{{target_output_name}}.bat"
-        command = pw_universal_stamp.command
-        default_output_extension = ".bat"
-      } else {
-        default_output_extension = ""
-        _output = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
-        command = "touch {{output}} && chmod +x {{output}}"
-      }
-      description = "ld $_output"
-      outputs = [ _output ]
-      default_output_dir = "{{target_out_dir}}/bin"
-    }
-
-    tool("solink") {
-      _output = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
-      command = pw_universal_stamp.command
-      description = "ld -shared $_output"
-      outputs = [ _output ]
-      default_output_dir = "{{target_out_dir}}/lib"
-      default_output_extension = ".so"
-    }
-
-    tool("stamp") {
-      # GN-ism: GN gets mad if you directly forward the contents of
-      # pw_universal_stamp.
-      _stamp = pw_universal_stamp
-      forward_variables_from(_stamp, "*")
-    }
-
-    tool("copy") {
-      # GN-ism: GN gets mad if you directly forward the contents of
-      # pw_universal_copy.
-      _copy = pw_universal_copy
-      forward_variables_from(_copy, "*")
-    }
-
-    # Build arguments to be overridden when compiling cross-toolchain:
-    #
-    #   pw_toolchain_defaults: A scope setting defaults to apply to GN targets
-    #     in this toolchain. It is analogous to $pw_target_defaults in
-    #     $dir_pigweed/pw_vars_default.gni.
-    #
-    #   pw_toolchain_SCOPE: A copy of the invoker scope that defines the
-    #     toolchain. Used for generating derivative toolchains.
-    #
-    toolchain_args = {
-      pw_toolchain_SCOPE = {
-      }
-      pw_toolchain_SCOPE = {
-        forward_variables_from(invoker, "*")
-        name = target_name
-      }
-      forward_variables_from(invoker_toolchain_args, "*")
-    }
-  }
-}
diff --git a/pw_toolchain/universal_tools.gni b/pw_toolchain/universal_tools.gni
index eafc0e8..820276d 100644
--- a/pw_toolchain/universal_tools.gni
+++ b/pw_toolchain/universal_tools.gni
@@ -19,8 +19,8 @@
     cp_command = "cp -af {{source}} {{output}}"
 
     # Use python script in absence of cp command.
-    copy_tool_path = rebase_path(dir_pw_toolchain, root_build_dir) +
-                     "/py/pw_toolchain/copy_with_metadata.py"
+    copy_tool_path =
+        rebase_path(dir_pw_toolchain) + "/py/pw_toolchain/copy_with_metadata.py"
     fallback_command = "python $copy_tool_path {{source}} {{output}}"
 
     command = "cmd /c \"($cp_command > NUL 2>&1) || ($fallback_command)\""
diff --git a/pw_trace/BUILD b/pw_trace/BUILD
new file mode 100644
index 0000000..24f5da2
--- /dev/null
+++ b/pw_trace/BUILD
@@ -0,0 +1,102 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_binary",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+# TODO(pwbug/101): Need to add support for facades/backends to Bazel.
+
+pw_cc_library(
+    name = "facade",
+    hdrs = [
+        "public/pw_trace/trace.h",
+        "public/pw_trace/internal/trace_internal.h",
+    ],
+    includes = ["public"],
+    deps = [
+        "//pw_preprocessor",
+    ],
+)
+
+pw_cc_library(
+    name = "pw_trace",
+    deps = [
+        ":facade",
+    ],
+)
+
+pw_cc_library(
+    name = "backend",
+    deps = [],
+)
+
+pw_cc_test(
+    name = "trace_backend_compile_test",
+    srcs = [
+        "trace_backend_compile_test.cc",
+        "trace_backend_compile_test_c.c",
+    ],
+    deps = [
+        ":backend",
+        ":facade",
+        ":pw_trace",
+        "//pw_preprocessor",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "trace_facade_test",
+    srcs = [
+        "trace_facade_test.cc",
+        "pw_trace_test/fake_backend.h",
+        "pw_trace_test/public_overrides/pw_trace_backend/trace_backend.h",
+    ],
+    includes = [
+        "pw_trace_test",
+        "pw_trace_test/public_overrides"
+    ],
+    deps = [
+        ":backend",
+        ":facade",
+        ":pw_trace",
+        "//pw_preprocessor",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_library(
+    name = "pw_trace_sample_app",
+    srcs = [ "example/sample_app.cc" ],
+    includes = [ "example/public" ],
+    deps = [ "//pw_trace" ],
+    hdrs = [ "example/public/pw_trace/example/sample_app.h" ]
+)
+
+pw_cc_binary(
+    name = "trace_example_basic",
+    deps = [
+        ":pw_trace_sample_app",
+        "//pw_log"
+    ],
+    srcs = [ "example/basic.cc" ]
+)
\ No newline at end of file
diff --git a/pw_trace/BUILD.bazel b/pw_trace/BUILD.bazel
deleted file mode 100644
index deeb15c..0000000
--- a/pw_trace/BUILD.bazel
+++ /dev/null
@@ -1,140 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_binary",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-# TODO(pwbug/101): Need to add support for facades/backends to Bazel.
-
-pw_cc_library(
-    name = "facade",
-    hdrs = [
-        "public/pw_trace/internal/trace_internal.h",
-        "public/pw_trace/trace.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_preprocessor",
-    ],
-)
-
-pw_cc_library(
-    name = "pw_trace",
-    deps = [
-        ":facade",
-    ],
-)
-
-pw_cc_library(
-    name = "null_headers",
-    hdrs = [
-        "public/pw_trace/internal/null.h",
-        "public_overrides/pw_trace_backend/trace_backend.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    deps = [
-        "//pw_preprocessor",
-    ],
-)
-
-pw_cc_library(
-    name = "null",
-    deps = [
-        "//pw_trace:facade",
-        "//pw_trace:null_headers",
-    ],
-)
-
-pw_cc_library(
-    name = "backend",
-    deps = [],
-)
-
-pw_cc_test(
-    name = "trace_backend_compile_test",
-    srcs = [
-        "trace_backend_compile_test.cc",
-        "trace_backend_compile_test_c.c",
-    ],
-    deps = [
-        ":backend",
-        ":facade",
-        ":pw_trace",
-        "//pw_preprocessor",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "trace_facade_test",
-    srcs = [
-        "pw_trace_test/fake_backend.h",
-        "pw_trace_test/public_overrides/pw_trace_backend/trace_backend.h",
-        "trace_facade_test.cc",
-    ],
-    includes = [
-        "pw_trace_test",
-        "pw_trace_test/public_overrides",
-    ],
-    deps = [
-        ":backend",
-        ":facade",
-        ":pw_trace",
-        "//pw_preprocessor",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_library(
-    name = "trace_null_test",
-    srcs = [
-        "trace_null_test.cc",
-        "trace_null_test_c.c",
-    ],
-    deps = [
-        ":facade",
-        ":null",
-        ":pw_trace",
-        "//pw_preprocessor",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_library(
-    name = "pw_trace_sample_app",
-    srcs = ["example/sample_app.cc"],
-    hdrs = ["example/public/pw_trace/example/sample_app.h"],
-    includes = ["example/public"],
-    deps = ["//pw_trace"],
-)
-
-pw_cc_binary(
-    name = "trace_example_basic",
-    srcs = ["example/basic.cc"],
-    deps = [
-        ":pw_trace_sample_app",
-        "//pw_log",
-    ],
-)
diff --git a/pw_trace/BUILD.gn b/pw_trace/BUILD.gn
index a62d1af..b0e455b 100644
--- a/pw_trace/BUILD.gn
+++ b/pw_trace/BUILD.gn
@@ -23,11 +23,6 @@
   include_dirs = [ "public" ]
 }
 
-config("null_config") {
-  include_dirs = [ "public_overrides" ]
-  visibility = [ ":*" ]
-}
-
 pw_facade("pw_trace") {
   backend = pw_trace_BACKEND
   public_configs = [ ":default_config" ]
@@ -35,25 +30,11 @@
     "public/pw_trace/internal/trace_internal.h",
     "public/pw_trace/trace.h",
   ]
-  public_deps = [ dir_pw_preprocessor ]
-}
-
-pw_source_set("null") {
-  public_configs = [
-    ":default_config",
-    ":null_config",
-  ]
-  public = [ "public_overrides/pw_trace_backend/trace_backend.h" ]
-  sources = [ "public/pw_trace/internal/null.h" ]
-  friend = [ ":*" ]
-  public_deps = [ dir_pw_preprocessor ]
+  deps = [ dir_pw_preprocessor ]
 }
 
 pw_test_group("tests") {
-  tests = [
-    ":trace_facade_test",
-    ":trace_null_test",
-  ]
+  tests = [ ":trace_facade_test" ]
   if (pw_trace_BACKEND != "") {
     tests += [ ":trace_backend_compile_test" ]
   }
@@ -88,14 +69,6 @@
   ]
 }
 
-pw_test("trace_null_test") {
-  sources = [
-    "trace_null_test.cc",
-    "trace_null_test_c.c",
-  ]
-  deps = [ ":null" ]
-}
-
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
diff --git a/pw_trace/CMakeLists.txt b/pw_trace/CMakeLists.txt
index c7df737..7a4fe84 100644
--- a/pw_trace/CMakeLists.txt
+++ b/pw_trace/CMakeLists.txt
@@ -15,48 +15,6 @@
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
 
 pw_add_facade(pw_trace
-  HEADERS
-    public/pw_trace/internal/trace_internal.h
-    public/pw_trace/trace.h
-  PUBLIC_INCLUDES
-    public
   PUBLIC_DEPS
     pw_preprocessor
 )
-
-pw_add_module_library(pw_trace.null
-  IMPLEMENTS_FACADES
-    pw_trace
-  HEADERS
-    public/pw_trace/internal/null.h
-    public_overrides/pw_trace_backend/trace_backend.h
-  PUBLIC_INCLUDES
-    public
-    public_overrides
-  PUBLIC_DEPS
-    pw_preprocessor
-)
-
-pw_add_test(pw_trace.pw_trace_null_test
-  SOURCES
-    trace_null_test.cc
-    trace_null_test_c.c
-  DEPS
-    pw_trace.null
-  GROUPS
-    modules
-    pw_trace
-)
-
-if(NOT "${pw_trace_BACKEND}" STREQUAL "pw_trace.NO_BACKEND_SET")
-  pw_add_test(pw_trace.trace_backend_compile_test
-   SOURCES
-    trace_backend_compile_test.cc
-    trace_backend_compile_test_c.c
-   DEPS
-    pw_trace
-  GROUPS
-    modules
-    pw_trace
-  )
-endif()
diff --git a/pw_trace/OWNERS b/pw_trace/OWNERS
deleted file mode 100644
index 8819b2c..0000000
--- a/pw_trace/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-keir@google.com
diff --git a/pw_trace/backend.gni b/pw_trace/backend.gni
index ddb706d..961dd57 100644
--- a/pw_trace/backend.gni
+++ b/pw_trace/backend.gni
@@ -12,9 +12,7 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
-import("//build_overrides/pigweed.gni")
-
 declare_args() {
-  # Default backend for the pw_trace module is pw_trace:null.
-  pw_trace_BACKEND = "$dir_pw_trace:null"
+  # Backend for the pw_trace module.
+  pw_trace_BACKEND = ""
 }
diff --git a/pw_trace/docs.rst b/pw_trace/docs.rst
index 51171ee..60aa359 100644
--- a/pw_trace/docs.rst
+++ b/pw_trace/docs.rst
@@ -38,24 +38,12 @@
 and are therefore useful at understanding the flow of events in the system over
 time.
 
-The default backend for pw_trace is pw_trace_null, which disables tracing.
-
 Compatibility
 -------------
 Most of the facade is compatible with C and C++, the only exception to this is
 the Scope and Function tracing macros which are convenience wrappers only
 available in C++.
 
-pw_trace:null
--------------
-``pw_trace_null`` is a ``pw_trace backend`` that ignores all ``pw_trace``
-statements. The backend implements ``pw_trace`` with empty inline functions.
-Using empty functions ensure that the arguments are evaluated and their types
-are correct. Since the functions are inline in the header, the compiler will
-optimize out the function call.
-
-This backend can be used to completely disable ``pw_trace``.
-
 Dependencies
 -------------
 ``pw_preprocessor``
@@ -292,7 +280,7 @@
 ----------------------
 Python Trace Generator
 ----------------------
-The Python tool is still in early development, but currently it supports
+The Python tool is still in early developments, but currently it supports
 generating a list of json lines from a list of trace events.
 
 To view the trace, these lines can be saved to a file and loaded into
diff --git a/pw_trace/example/sample_app.cc b/pw_trace/example/sample_app.cc
index aad1771..8edefa9 100644
--- a/pw_trace/example/sample_app.cc
+++ b/pw_trace/example/sample_app.cc
@@ -78,8 +78,7 @@
     // Buffer is used for the job queue.
     std::span<std::byte> buf_span = std::span<std::byte>(
         reinterpret_cast<std::byte*>(jobs_buffer_), sizeof(jobs_buffer_));
-    jobs_.SetBuffer(buf_span)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    jobs_.SetBuffer(buf_span);
   }
   const char* Name() const override { return "Processing Task"; }
   bool ShouldRun() override { return jobs_.EntryCount() > 0; }
@@ -91,9 +90,8 @@
     size_t entry_count = jobs_.EntryCount();
 
     // Get the next job from the queue.
-    jobs_.PeekFront(job_bytes.bytes, &bytes_read)
-        .IgnoreError();              // TODO(pwbug/387): Handle Status properly
-    jobs_.PopFront().IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    jobs_.PeekFront(job_bytes.bytes, &bytes_read);
+    jobs_.PopFront();
     Job& job = job_bytes.job;
 
     // Process the job
@@ -137,8 +135,7 @@
   }
   void AddJobInternal(uint32_t job_id, uint8_t value) {
     JobBytes job{.job = {.job_id = job_id, .value = value}};
-    jobs_.PushBack(job.bytes)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    jobs_.PushBack(job.bytes);
   }
 } processing_task;
 
diff --git a/pw_trace/public/pw_trace/internal/null.h b/pw_trace/public/pw_trace/internal/null.h
deleted file mode 100644
index 155b54a..0000000
--- a/pw_trace/public/pw_trace/internal/null.h
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <stddef.h>
-#include <stdint.h>
-
-#include "pw_preprocessor/compiler.h"
-#include "pw_preprocessor/util.h"
-
-// Enable traces
-#define PW_TRACE_TYPE_INSTANT 1
-#define PW_TRACE_TYPE_INSTANT_GROUP 1
-#define PW_TRACE_TYPE_DURATION_START 1
-#define PW_TRACE_TYPE_DURATION_END 1
-#define PW_TRACE_TYPE_DURATION_GROUP_START 1
-#define PW_TRACE_TYPE_DURATION_GROUP_END 1
-#define PW_TRACE_TYPE_ASYNC_START 1
-#define PW_TRACE_TYPE_ASYNC_INSTANT 1
-#define PW_TRACE_TYPE_ASYNC_END 1
-
-PW_EXTERN_C_START
-
-// Empty function for compiling out trace statements. Since the function is
-// empty and inline, it should be completely compiled out. This function
-// accomplishes following:
-//
-//   - Uses the arguments to PW_TRACE, which avoids "unused variable" warnings.
-//   - Executes expressions passed to PW_TRACE, so that the behavior is
-//     consistent between this null backend and an actual backend.
-//
-// These two functions are used in PW_TRACE and PW_TRACE_DATA.
-
-static inline void pw_trace_Ignored(int event_type,
-                                    uint8_t flags,
-                                    const char* label,
-                                    const char* group,
-                                    uint32_t trace_id) {
-  (void)event_type;
-  (void)flags;
-  (void)label;
-  (void)group;
-  (void)trace_id;
-}
-
-static inline void pw_trace_data_Ignored(int event_type,
-                                         uint8_t flags,
-                                         const char* label,
-                                         const char* group,
-                                         uint32_t trace_id,
-                                         const char* type,
-                                         const char* data,
-                                         size_t size) {
-  (void)event_type;
-  (void)flags;
-  (void)label;
-  (void)group;
-  (void)trace_id;
-  (void)type;
-  (void)data;
-  (void)size;
-}
-
-PW_EXTERN_C_END
-
-#define PW_TRACE(event_type, flags, label, group, trace_id) \
-  pw_trace_Ignored(event_type, flags, label, group, trace_id)
-
-#define PW_TRACE_DATA(                                           \
-    event_type, flags, label, group, trace_id, type, data, size) \
-  pw_trace_data_Ignored(                                         \
-      event_type, flags, label, group, trace_id, type, data, size)
diff --git a/pw_trace/public/pw_trace/internal/trace_internal.h b/pw_trace/public/pw_trace/internal/trace_internal.h
index 18e51a7..f3604db 100644
--- a/pw_trace/public/pw_trace/internal/trace_internal.h
+++ b/pw_trace/public/pw_trace/internal/trace_internal.h
@@ -17,7 +17,6 @@
 #pragma once
 
 #include "pw_preprocessor/arguments.h"
-#include "pw_preprocessor/concat.h"
 #include "pw_trace_backend/trace_backend.h"
 
 // Default: Flag value if none set
diff --git a/pw_trace/public_overrides/pw_trace_backend/trace_backend.h b/pw_trace/public_overrides/pw_trace_backend/trace_backend.h
deleted file mode 100644
index c31e7a9..0000000
--- a/pw_trace/public_overrides/pw_trace_backend/trace_backend.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_trace/internal/null.h"
diff --git a/pw_trace/pw_trace_test/fake_backend.h b/pw_trace/pw_trace_test/fake_backend.h
index f9dd3fc..c824e9a 100644
--- a/pw_trace/pw_trace_test/fake_backend.h
+++ b/pw_trace/pw_trace_test/fake_backend.h
@@ -95,8 +95,7 @@
            has_data_ == rhs.has_data_ &&                          //
            data_format_string_ == rhs.data_format_string_ &&      //
            data_size_ == rhs.data_size_ &&                        //
-           (data_size_ == 0 ||                                    //
-            (memcmp(data_, rhs.data_, data_size_) == 0));
+           (memcmp(data_, rhs.data_, data_size_) == 0);
   }
 
   bool IsEqualIgnoreLabel(const Event& rhs) const {
@@ -106,8 +105,7 @@
            has_data_ == rhs.has_data_ &&                          //
            data_format_string_ == rhs.data_format_string_ &&      //
            data_size_ == rhs.data_size_ &&                        //
-           (data_size_ == 0 ||                                    //
-            (memcmp(data_, rhs.data_, data_size_) == 0));
+           (memcmp(data_, rhs.data_, data_size_) == 0);
   }
 
  private:
@@ -142,4 +140,4 @@
   LastEvent::Instance().Set(                                     \
       Event(event_type, flags, label, group, trace_id, type, data, size));
 
-}  // namespace trace_fake_backend
+}  // namespace trace_fake_backend
\ No newline at end of file
diff --git a/pw_trace/py/BUILD.gn b/pw_trace/py/BUILD.gn
index a445dd9..736258c 100644
--- a/pw_trace/py/BUILD.gn
+++ b/pw_trace/py/BUILD.gn
@@ -17,11 +17,7 @@
 import("$dir_pw_build/python.gni")
 
 pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
+  setup = [ "setup.py" ]
   sources = [
     "pw_trace/__init__.py",
     "pw_trace/trace.py",
diff --git a/pw_trace/py/pw_trace/trace.py b/pw_trace/py/pw_trace/trace.py
index 03b00c2..c63d931 100755
--- a/pw_trace/py/pw_trace/trace.py
+++ b/pw_trace/py/pw_trace/trace.py
@@ -31,21 +31,16 @@
 
 
 class TraceType(Enum):
-    INVALID = 0
-    INSTANTANEOUS = 1
-    INSTANTANEOUS_GROUP = 2
-    ASYNC_START = 3
-    ASYNC_STEP = 4
-    ASYNC_END = 5
-    DURATION_START = 6
-    DURATION_END = 7
-    DURATION_GROUP_START = 8
-    DURATION_GROUP_END = 9
-
-    # TODO(hepler): Remove these aliases for the original style-incompliant
-    #     names when users have migrated.
-    DurationStart = 6  # pylint: disable=invalid-name
-    DurationEnd = 7  # pylint: disable=invalid-name
+    Invalid = 0
+    Instantaneous = 1
+    InstantaneousGroup = 2
+    AsyncStart = 3
+    AsyncStep = 4
+    AsyncEnd = 5
+    DurationStart = 6
+    DurationEnd = 7
+    DurationGroupStart = 8
+    DurationGroupEnd = 9
 
 
 class TraceEvent(NamedTuple):
@@ -83,40 +78,40 @@
             "name": (event.label),
             "ts": event.timestamp_us
         }
-        if event.event_type == TraceType.DURATION_START:
+        if event.event_type == TraceType.DurationStart:
             line["ph"] = "B"
             line["tid"] = event.label
-        elif event.event_type == TraceType.DURATION_END:
+        elif event.event_type == TraceType.DurationEnd:
             line["ph"] = "E"
             line["tid"] = event.label
-        elif event.event_type == TraceType.DURATION_GROUP_START:
+        elif event.event_type == TraceType.DurationGroupStart:
             line["ph"] = "B"
             line["tid"] = event.group
-        elif event.event_type == TraceType.DURATION_GROUP_END:
+        elif event.event_type == TraceType.DurationGroupEnd:
             line["ph"] = "E"
             line["tid"] = event.group
-        elif event.event_type == TraceType.INSTANTANEOUS:
+        elif event.event_type == TraceType.Instantaneous:
             line["ph"] = "I"
             line["s"] = "p"
-        elif event.event_type == TraceType.INSTANTANEOUS_GROUP:
+        elif event.event_type == TraceType.InstantaneousGroup:
             line["ph"] = "I"
             line["s"] = "t"
             line["tid"] = event.group
-        elif event.event_type == TraceType.ASYNC_START:
+        elif event.event_type == TraceType.AsyncStart:
             line["ph"] = "b"
             line["scope"] = event.group
             line["tid"] = event.group
             line["cat"] = event.module
             line["id"] = event.trace_id
             line["args"] = {"id": line["id"]}
-        elif event.event_type == TraceType.ASYNC_STEP:
+        elif event.event_type == TraceType.AsyncStep:
             line["ph"] = "n"
             line["scope"] = event.group
             line["tid"] = event.group
             line["cat"] = event.module
             line["id"] = event.trace_id
             line["args"] = {"id": line["id"]}
-        elif event.event_type == TraceType.ASYNC_END:
+        elif event.event_type == TraceType.AsyncEnd:
             line["ph"] = "e"
             line["scope"] = event.group
             line["tid"] = event.group
diff --git a/pw_trace/py/pyproject.toml b/pw_trace/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/pw_trace/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/pw_trace/py/setup.cfg b/pw_trace/py/setup.cfg
deleted file mode 100644
index f5dfe81..0000000
--- a/pw_trace/py/setup.cfg
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pw_trace
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Tools for dealing with trace data
-
-[options]
-packages = find:
-zip_safe = False
-
-[options.package_data]
-pw_trace = py.typed
diff --git a/pw_trace/py/setup.py b/pw_trace/py/setup.py
index 9e3cda1..9fa3379 100644
--- a/pw_trace/py/setup.py
+++ b/pw_trace/py/setup.py
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2020 The Pigweed Authors
 #
 # 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
@@ -11,8 +11,17 @@
 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 # License for the specific language governing permissions and limitations under
 # the License.
-"""pw_trace"""
+"""The pw_trace package."""
 
 import setuptools  # type: ignore
 
-setuptools.setup()  # Package definition in setup.cfg
+setuptools.setup(
+    name='pw_trace',
+    version='0.0.1',
+    author='Pigweed Authors',
+    author_email='pigweed-developers@googlegroups.com',
+    description='Tools for dealing with trace data',
+    packages=setuptools.find_packages(),
+    package_data={'pw_trace': ['py.typed']},
+    zip_safe=False,
+)
diff --git a/pw_trace/py/trace_test.py b/pw_trace/py/trace_test.py
index 6cd2bcc..889a4b1 100755
--- a/pw_trace/py/trace_test.py
+++ b/pw_trace/py/trace_test.py
@@ -21,16 +21,15 @@
 from pw_trace import trace
 
 test_events = [
-    trace.TraceEvent(trace.TraceType.INSTANTANEOUS, "m1", "L1", 1),
-    trace.TraceEvent(trace.TraceType.INSTANTANEOUS_GROUP, "m2", "L2", 2, "G2"),
-    trace.TraceEvent(trace.TraceType.ASYNC_STEP, "m3", "L3", 3, "G3", 103),
-    trace.TraceEvent(trace.TraceType.DURATION_START, "m4", "L4", 4),
-    trace.TraceEvent(trace.TraceType.DURATION_GROUP_START, "m5", "L5", 5,
-                     "G5"),
-    trace.TraceEvent(trace.TraceType.ASYNC_START, "m6", "L6", 6, "G6", 106),
-    trace.TraceEvent(trace.TraceType.DURATION_END, "m7", "L7", 7),
-    trace.TraceEvent(trace.TraceType.DURATION_GROUP_END, "m8", "L8", 8, "G8"),
-    trace.TraceEvent(trace.TraceType.ASYNC_END, "m9", "L9", 9, "G9", 109)
+    trace.TraceEvent(trace.TraceType.Instantaneous, "m1", "L1", 1),
+    trace.TraceEvent(trace.TraceType.InstantaneousGroup, "m2", "L2", 2, "G2"),
+    trace.TraceEvent(trace.TraceType.AsyncStep, "m3", "L3", 3, "G3", 103),
+    trace.TraceEvent(trace.TraceType.DurationStart, "m4", "L4", 4),
+    trace.TraceEvent(trace.TraceType.DurationGroupStart, "m5", "L5", 5, "G5"),
+    trace.TraceEvent(trace.TraceType.AsyncStart, "m6", "L6", 6, "G6", 106),
+    trace.TraceEvent(trace.TraceType.DurationEnd, "m7", "L7", 7),
+    trace.TraceEvent(trace.TraceType.DurationGroupEnd, "m8", "L8", 8, "G8"),
+    trace.TraceEvent(trace.TraceType.AsyncEnd, "m9", "L9", 9, "G9", 109)
 ]
 
 test_json = [
@@ -52,7 +51,7 @@
 class TestTraceGenerateJson(unittest.TestCase):
     """Tests generate json with various events."""
     def test_generate_single_json_event(self):
-        event = trace.TraceEvent(event_type=trace.TraceType.INSTANTANEOUS,
+        event = trace.TraceEvent(event_type=trace.TraceType.Instantaneous,
                                  module="module",
                                  label="label",
                                  timestamp_us=10)
@@ -74,7 +73,7 @@
 
     def test_generate_json_data_arg_label(self):
         event = trace.TraceEvent(
-            event_type=trace.TraceType.INSTANTANEOUS,
+            event_type=trace.TraceType.Instantaneous,
             module="module",
             label="",  # Is replaced by data string
             timestamp_us=10,
@@ -92,14 +91,13 @@
         })
 
     def test_generate_json_data_arg_group(self):
-        event = trace.TraceEvent(
-            event_type=trace.TraceType.INSTANTANEOUS_GROUP,
-            module="module",
-            label="label",
-            timestamp_us=10,
-            has_data=True,
-            data_fmt="@pw_arg_group",
-            data=bytes("arg", "utf-8"))
+        event = trace.TraceEvent(event_type=trace.TraceType.InstantaneousGroup,
+                                 module="module",
+                                 label="label",
+                                 timestamp_us=10,
+                                 has_data=True,
+                                 data_fmt="@pw_arg_group",
+                                 data=bytes("arg", "utf-8"))
         json_lines = trace.generate_trace_json([event])
         self.assertEqual(1, len(json_lines))
         self.assertEqual(
@@ -113,7 +111,7 @@
             })
 
     def test_generate_json_data_counter(self):
-        event = trace.TraceEvent(event_type=trace.TraceType.INSTANTANEOUS,
+        event = trace.TraceEvent(event_type=trace.TraceType.Instantaneous,
                                  module="module",
                                  label="counter",
                                  timestamp_us=10,
@@ -135,7 +133,7 @@
             })
 
     def test_generate_json_data_struct_fmt_single(self):
-        event = trace.TraceEvent(event_type=trace.TraceType.INSTANTANEOUS,
+        event = trace.TraceEvent(event_type=trace.TraceType.Instantaneous,
                                  module="module",
                                  label="counter",
                                  timestamp_us=10,
@@ -157,7 +155,7 @@
             })
 
     def test_generate_json_data_struct_fmt_multi(self):
-        event = trace.TraceEvent(event_type=trace.TraceType.INSTANTANEOUS,
+        event = trace.TraceEvent(event_type=trace.TraceType.Instantaneous,
                                  module="module",
                                  label="counter",
                                  timestamp_us=10,
diff --git a/pw_trace/trace_null_test.cc b/pw_trace/trace_null_test.cc
deleted file mode 100644
index d82714d..0000000
--- a/pw_trace/trace_null_test.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "gtest/gtest.h"
-#include "pw_trace/internal/null.h"
-
-#define PW_TRACE_MODULE_NAME "this test!"
-
-extern "C" bool CTest();
-
-namespace {
-
-TEST(TraceNull, PW_TRACE) { PW_TRACE(0, 1, "label", "group", 2); }
-
-TEST(TraceNull, PW_TRACE_DATA) {
-  PW_TRACE_DATA(0, 1, "label", "group", 2, "type", "data", 1);
-}
-
-}  // namespace
diff --git a/pw_trace/trace_null_test_c.c b/pw_trace/trace_null_test_c.c
deleted file mode 100644
index 5c4ebdd..0000000
--- a/pw_trace/trace_null_test_c.c
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <stdbool.h>
-#include <stddef.h>
-
-#include "pw_trace/internal/null.h"
-
-#define PW_TRACE_MODULE_NAME "c test!"
-
-bool CTest(void) {
-  PW_TRACE(0, 1, "label", "group", 2);
-  PW_TRACE_DATA(0, 1, "label", "group", 2, "type", "data", 1);
-  return true;
-}
diff --git a/pw_trace_tokenized/BUILD b/pw_trace_tokenized/BUILD
new file mode 100644
index 0000000..1944d71
--- /dev/null
+++ b/pw_trace_tokenized/BUILD
@@ -0,0 +1,239 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_binary",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+# TODO(pwbug/101): Need to add support for facades/backends to Bazel.
+
+pw_cc_library(
+    name = "headers",
+    hdrs = [
+        "public/pw_trace_tokenized/config.h",
+        "public/pw_trace_tokenized/internal/trace_tokenized_internal.h",
+        "public/pw_trace_tokenized/trace_callback.h",
+        "public/pw_trace_tokenized/trace_tokenized.h",
+        "public_overrides/pw_trace_backend/trace_backend.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        "//pw_preprocessor",
+        "//pw_tokenizer",
+    ],
+)
+
+pw_cc_library(
+    name = "pw_trace_tokenized",
+    srcs = [
+        "trace.cc",
+    ],
+    deps = [
+        ":headers",
+        "//pw_assert",
+        "//pw_log",
+        "//pw_status",
+        "//pw_tokenizer",
+        "//pw_trace:facade",
+        "//pw_varint",
+    ],
+)
+
+pw_cc_library(
+    name = "trace_rpc_service",
+    hdrs = [
+        "public/pw_trace_tokenized/trace_rpc_service_nanopb.h",
+    ],
+    includes = [
+        "public",
+    ],
+    srcs = [
+        "trace_rpc_service_nanopb.cc",
+    ],
+    deps = [
+        "//pw_log",
+        "//pw_trace",
+        "//pw_trace_tokenized_buffer",
+    ],
+)
+
+pw_cc_library(
+    name = "trace_buffer_headers",
+    hdrs = [
+        "public/pw_trace_tokenized/trace_buffer.h",
+    ],
+    includes = [
+        "public",
+    ],
+    deps = [
+        "//pw_ring_buffer",
+        "//pw_status",
+        "//pw_trace_tokenized",
+    ],
+)
+
+pw_cc_library(
+    name = "pw_trace_tokenized_buffer",
+    srcs = [
+        "trace_buffer.cc",
+    ],
+    deps = [
+        ":trace_buffer_headers",
+        "//pw_ring_buffer",
+        "//pw_status",
+    ],
+)
+
+pw_cc_library(
+    name = "pw_trace_tokenized_buffer_log",
+    hdrs = [
+        "public/pw_trace_tokenized/trace_buffer_log.h",
+    ],
+    srcs = [
+        "trace_buffer_log.cc",
+    ],
+    deps = [
+        ":trace_buffer_headers",
+	"//pw_base64",
+	"//pw_log",
+	"//pw_string",
+    ],
+)
+
+pw_cc_library(
+    name = "pw_trace_tokenized_fake_time",
+    srcs = [
+        "fake_trace_time.cc",
+    ],
+    deps = [
+        "//pw_trace",
+    ],
+)
+
+pw_cc_test(
+    name = "trace_tokenized_test",
+    srcs = [
+        "trace_test.cc",
+    ],
+    includes = [
+        "pw_trace_test",
+        "pw_trace_test/public_overrides"
+    ],
+    deps = [
+        ":backend",
+        ":facade",
+        ":pw_trace",
+        "//pw_preprocessor",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "trace_tokenized_buffer_test",
+    srcs = [
+        "trace_buffer_test.cc",
+    ],
+    deps = [
+        ":backend",
+        ":facade",
+        ":pw_trace",
+        "//pw_preprocessor",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "trace_tokenized_buffer_log_test",
+    srcs = [
+        "trace_buffer_log_test.cc",
+    ],
+    deps = [
+        ":backend",
+        ":facade",
+        ":pw_trace_log",
+        "//pw_preprocessor",
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_library(
+    name = "pw_trace_host_trace_time",
+    includes = [ "example/public" ],
+    deps = [ "//pw_trace" ],
+    srcs = [ "host_trace_time.cc" ]
+)
+
+pw_cc_library(
+    name = "pw_trace_example_to_file",
+    includes = [ "example/public" ],
+    deps = [ "//pw_trace" ],
+    hdrs = [ "example/public/pw_trace_tokenized/example/trace_to_file.h" ]
+)
+
+pw_cc_binary(
+    name = "trace_tokenized_example_basic",
+    deps = [
+        ":pw_trace_example_to_file",
+        "//pw_log",
+        "//dir_pw_trace",
+        "//dir_pw_trace:pw_trace_sample_app",
+    ],
+    srcs = [ "example/basic.cc" ]
+)
+
+pw_cc_binary(
+    name = "trace_tokenized_example_trigger",
+    deps = [
+        ":pw_trace_example_to_file",
+        "//pw_log",
+        "//dir_pw_trace",
+        "//dir_pw_trace:pw_trace_sample_app",
+    ],
+    srcs = [ "example/trigger.cc" ]
+)
+
+pw_cc_binary(
+    name = "trace_tokenized_example_filter",
+    deps = [
+        ":pw_trace_example_to_file",
+        "//pw_log",
+        "//dir_pw_trace",
+        "//dir_pw_trace:pw_trace_sample_app",
+    ],
+    srcs = [ "example/filter.cc" ]
+)
+
+pw_cc_library(
+    name = "trace_tokenized_example_rpc",
+    deps = [
+        ":pw_trace_rpc_service",
+        "//dir_pw_rpc:server",
+        "//dir_pw_rpc:system_server",
+        "//pw_log",
+        "//pw_hdlc",
+        "//dir_pw_trace",
+        "//dir_pw_trace:pw_trace_sample_app",
+    ],
+    srcs = [ "example/rpc.cc" ]
+)
\ No newline at end of file
diff --git a/pw_trace_tokenized/BUILD.bazel b/pw_trace_tokenized/BUILD.bazel
deleted file mode 100644
index 34e65ae..0000000
--- a/pw_trace_tokenized/BUILD.bazel
+++ /dev/null
@@ -1,247 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_binary",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-# TODO(pwbug/101): Need to add support for facades/backends to Bazel.
-
-pw_cc_library(
-    name = "headers",
-    hdrs = [
-        "public/pw_trace_tokenized/config.h",
-        "public/pw_trace_tokenized/internal/trace_tokenized_internal.h",
-        "public/pw_trace_tokenized/trace_callback.h",
-        "public/pw_trace_tokenized/trace_tokenized.h",
-        "public_overrides/pw_trace_backend/trace_backend.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    deps = [
-        "//pw_preprocessor",
-        "//pw_tokenizer",
-    ],
-)
-
-pw_cc_library(
-    name = "pw_trace_tokenized",
-    srcs = [
-        "trace.cc",
-    ],
-    deps = [
-        ":headers",
-        "//pw_assert",
-        "//pw_log",
-        "//pw_status",
-        "//pw_tokenizer",
-        "//pw_trace:facade",
-        "//pw_varint",
-    ],
-)
-
-pw_cc_library(
-    name = "trace_rpc_service",
-    srcs = [
-        "trace_rpc_service_nanopb.cc",
-    ],
-    hdrs = [
-        "public/pw_trace_tokenized/trace_rpc_service_nanopb.h",
-    ],
-    includes = [
-        "public",
-    ],
-    deps = [
-        "//pw_log",
-        "//pw_trace",
-        "//pw_trace_tokenized_buffer",
-    ],
-)
-
-pw_cc_library(
-    name = "trace_buffer_headers",
-    hdrs = [
-        "public/pw_trace_tokenized/trace_buffer.h",
-    ],
-    includes = [
-        "public",
-    ],
-    deps = [
-        "//pw_ring_buffer",
-        "//pw_status",
-        "//pw_trace_tokenized",
-    ],
-)
-
-pw_cc_library(
-    name = "pw_trace_tokenized_buffer",
-    srcs = [
-        "trace_buffer.cc",
-    ],
-    deps = [
-        ":trace_buffer_headers",
-        "//pw_ring_buffer",
-        "//pw_status",
-    ],
-)
-
-pw_cc_library(
-    name = "pw_trace_tokenized_buffer_log",
-    srcs = [
-        "trace_buffer_log.cc",
-    ],
-    hdrs = [
-        "public/pw_trace_tokenized/trace_buffer_log.h",
-    ],
-    deps = [
-        ":trace_buffer_headers",
-        "//pw_base64",
-        "//pw_log",
-        "//pw_string",
-    ],
-)
-
-proto_library(
-    name = "protos",
-    srcs = [
-        "pw_trace_protos/trace.proto",
-        "pw_trace_protos/trace_rpc.proto",
-    ],
-)
-
-pw_cc_library(
-    name = "pw_trace_tokenized_fake_time",
-    srcs = [
-        "fake_trace_time.cc",
-    ],
-    deps = [
-        "//pw_trace",
-    ],
-)
-
-pw_cc_test(
-    name = "trace_tokenized_test",
-    srcs = [
-        "trace_test.cc",
-    ],
-    includes = [
-        "pw_trace_test",
-        "pw_trace_test/public_overrides",
-    ],
-    deps = [
-        ":backend",
-        ":facade",
-        ":pw_trace",
-        "//pw_preprocessor",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "trace_tokenized_buffer_test",
-    srcs = [
-        "trace_buffer_test.cc",
-    ],
-    deps = [
-        ":backend",
-        ":facade",
-        ":pw_trace",
-        "//pw_preprocessor",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "trace_tokenized_buffer_log_test",
-    srcs = [
-        "trace_buffer_log_test.cc",
-    ],
-    deps = [
-        ":backend",
-        ":facade",
-        ":pw_trace_log",
-        "//pw_preprocessor",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_library(
-    name = "pw_trace_host_trace_time",
-    srcs = ["host_trace_time.cc"],
-    includes = ["example/public"],
-    deps = ["//pw_trace"],
-)
-
-pw_cc_library(
-    name = "pw_trace_example_to_file",
-    hdrs = ["example/public/pw_trace_tokenized/example/trace_to_file.h"],
-    includes = ["example/public"],
-    deps = ["//pw_trace"],
-)
-
-pw_cc_binary(
-    name = "trace_tokenized_example_basic",
-    srcs = ["example/basic.cc"],
-    deps = [
-        ":pw_trace_example_to_file",
-        "//dir_pw_trace",
-        "//dir_pw_trace:pw_trace_sample_app",
-        "//pw_log",
-    ],
-)
-
-pw_cc_binary(
-    name = "trace_tokenized_example_trigger",
-    srcs = ["example/trigger.cc"],
-    deps = [
-        ":pw_trace_example_to_file",
-        "//dir_pw_trace",
-        "//dir_pw_trace:pw_trace_sample_app",
-        "//pw_log",
-    ],
-)
-
-pw_cc_binary(
-    name = "trace_tokenized_example_filter",
-    srcs = ["example/filter.cc"],
-    deps = [
-        ":pw_trace_example_to_file",
-        "//dir_pw_trace",
-        "//dir_pw_trace:pw_trace_sample_app",
-        "//pw_log",
-    ],
-)
-
-pw_cc_library(
-    name = "trace_tokenized_example_rpc",
-    srcs = ["example/rpc.cc"],
-    deps = [
-        ":pw_trace_rpc_service",
-        "//dir_pw_rpc:server",
-        "//dir_pw_rpc:system_server",
-        "//dir_pw_trace",
-        "//dir_pw_trace:pw_trace_sample_app",
-        "//pw_hdlc",
-        "//pw_log",
-    ],
-)
diff --git a/pw_trace_tokenized/BUILD.gn b/pw_trace_tokenized/BUILD.gn
index e248c8e..6ed2a0b 100644
--- a/pw_trace_tokenized/BUILD.gn
+++ b/pw_trace_tokenized/BUILD.gn
@@ -75,17 +75,14 @@
   defines = [ "PW_TRACE_BUFFER_SIZE_BYTES=${pw_trace_tokenized_BUFFER_SIZE}" ]
 }
 
-pw_proto_library("protos") {
-  sources = [
-    "pw_trace_protos/trace.proto",
-    "pw_trace_protos/trace_rpc.proto",
-  ]
+pw_proto_library("trace_rpc_service_proto") {
+  sources = [ "pw_trace_protos/trace_rpc.proto" ]
   inputs = [ "pw_trace_protos/trace_rpc.options" ]
 }
 
 pw_source_set("trace_rpc_service") {
   public_configs = [ ":public_include_path" ]
-  public_deps = [ ":protos.nanopb_rpc" ]
+  public_deps = [ ":trace_rpc_service_proto.nanopb_rpc" ]
   deps = [
     ":core",
     ":tokenized_trace_buffer",
@@ -102,7 +99,6 @@
   deps = [ ":core" ]
   public_deps = [
     ":config",
-    "$dir_pw_bytes",
     "$dir_pw_ring_buffer",
     "$dir_pw_tokenizer",
     "$dir_pw_varint",
diff --git a/pw_trace_tokenized/CMakeLists.txt b/pw_trace_tokenized/CMakeLists.txt
index 3aa0786..e4bd46a 100644
--- a/pw_trace_tokenized/CMakeLists.txt
+++ b/pw_trace_tokenized/CMakeLists.txt
@@ -13,56 +13,16 @@
 # the License.
 
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-include($ENV{PW_ROOT}/pw_protobuf_compiler/proto.cmake)
 
-pw_add_module_library(pw_trace_tokenized
-  IMPLEMENTS_FACADES
+pw_auto_add_simple_module(pw_trace_tokenized
+  IMPLEMENTS_FACADE
     pw_trace
-  SOURCES
-    trace.cc
   PRIVATE_DEPS
-    pw_assert
     pw_log
     pw_ring_buffer
-    pw_varint
-  PUBLIC_DEPS
-    pw_status
-    pw_tokenizer
-)
-
-pw_add_module_library(pw_trace_tokenized.trace_buffer
-  SOURCES
-    trace_buffer.cc
-  PRIVATE_DEPS
     pw_assert
-    pw_bytes
-    pw_log
-  PUBLIC_DEPS
-    pw_ring_buffer
     pw_status
     pw_tokenizer
-    pw_trace_tokenized
+    pw_trace:facade
     pw_varint
 )
-
-pw_proto_library(pw_trace_tokenized.protos
-  SOURCES
-    pw_trace_protos/trace_rpc.proto
-  INPUTS
-    pw_trace_protos/trace_rpc.options
-)
-
-pw_add_module_library(pw_trace_tokenized.rpc_service
-  SOURCES
-    trace_rpc_service_nanopb.cc
-  PRIVATE_DEPS
-    pw_assert
-    pw_log
-    pw_ring_buffer
-    pw_trace_tokenized.trace_buffer
-    pw_trace_tokenized.protos.nanopb_rpc
-    pw_varint
-  PUBLIC_DEPS
-    pw_tokenizer
-    pw_status
-)
diff --git a/pw_trace_tokenized/OWNERS b/pw_trace_tokenized/OWNERS
deleted file mode 100644
index 8819b2c..0000000
--- a/pw_trace_tokenized/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-keir@google.com
diff --git a/pw_trace_tokenized/docs.rst b/pw_trace_tokenized/docs.rst
index ca537e5..c1c27ac 100644
--- a/pw_trace_tokenized/docs.rst
+++ b/pw_trace_tokenized/docs.rst
@@ -16,7 +16,7 @@
 This module is currently in development, and is therefore still undergoing
 significant changes.
 
-Future work will:
+Future work will add:
 
 1. Add a more complete API for how to retrieve data from ring_buffer.
 2. Add a Python library to decode the trace data.
@@ -60,7 +60,7 @@
 ---------
 Macro API
 ---------
-All code should use the trace API facade directly. This backend fully
+All code should use the trace API facade directly, this backend fully
 implements all features of the tracing facade.
 
 
@@ -139,7 +139,7 @@
 -----------
 Time source
 -----------
-Tracing requires the platform to provide the time source for tracing, this can
+Tracing rquires the platform to provide the time source for tracing, this can
 be done in one of a few ways.
 
 1. Create a file with the default time functions, and provide as build variable
@@ -159,7 +159,7 @@
 ------
 The optional trace buffer adds a ring buffer which contains the encoded trace
 data. This is still a work in progress, in particular better methods for
-retrieving the data still need to be added. Currently there is an accessor for
+retireving the data still needs to be added. Currently there is an accessor for
 the underlying ring buffer object, but this is a short term solution.
 
 .. cpp:function:: void ClearBuffer()
@@ -172,15 +172,6 @@
    Including the token, time, and any attached data. Any trace object larger
    then this will be dropped.
 
-.. cpp:function:: ConstByteSpan DeringAndViewRawBuffer()
-
-The DeringAndViewRawBuffer function can be used to get bulk access of the full
-deringed prefixed-ring-buffer data. This might be neccessary for large zero-copy
-bulk transfers. It is the caller's responsibility to disable tracing during
-access to the buffer. The data in the block is defined by the
-prefixed-ring-buffer format without any user-preamble.
-
-
 Added dependencies
 ------------------
 ``pw_ring_buffer``
@@ -212,17 +203,6 @@
 ``pw_tokenizer``
 ``pw_varint``
 
---------------
-Python decoder
---------------
-The python decoder can be used to convert the binary trace data into json data
-which can be viewed in chrome://tracing.
-
-``get_trace.py`` can be used for retrieveing trace data from devices which are
-using the trace_rpc_server.
-
-``trace_tokenized.py`` can be used to decode a binary file of trace data.
-
 --------
 Examples
 --------
@@ -239,10 +219,10 @@
 Trigger
 -------
 The trigger example demonstrates how a trace event can be used as a trigger to
-start and stop capturing a trace. The examples makes use of ``PW_TRACE_REF``
-and ``PW_TRACE_REF_DATA`` to specify a start and stop event for the capture.
-This can be useful if the trace buffer is small and you wish to capture a
-specific series of events.
+start and stop capturing a trace. The examples makes use of `PW_TRACE_REF` and
+`PW_TRACE_REF_DATA` to specify a start and stop event for the capture. This can
+be useful if the trace buffer is small and you wish to capture a specific
+series of events.
 
 Filter
 ------
@@ -252,14 +232,3 @@
 are not removed. This can be a useful feature while debugging as it limits the
 amount of events which get stored to the buffer, and only saves the events of
 interest.
-
---------------------
-Snapshot integration
---------------------
-Tokenized trace buffers can be captured to a ``pw.snapshot.Snapshot`` or
-``pw.trace.SnapshotTraceInfo`` proto in the ``trace_data`` field. The expected
-format is a de-ringed raw tokenized trace buffer, which can be retrieved via
-``pw::trace::DeringAndViewRawBuffer()``.
-
-``pw_trace_tokenized`` does not yet have Python tooling integration for
-interpretation of serialized snapshots with a populated ``trace_data`` field.
diff --git a/pw_trace_tokenized/example/basic.cc b/pw_trace_tokenized/example/basic.cc
index a3894e3..4daa894 100644
--- a/pw_trace_tokenized/example/basic.cc
+++ b/pw_trace_tokenized/example/basic.cc
@@ -12,17 +12,17 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 //==============================================================================
-// BUILD
+// BUID
 // ninja -C out
-// pw_strict_host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_basic
+// host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_basic
 //
 // RUN
-// ./out/pw_strict_host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_basic
+// .out/host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_basic
 // trace.bin
 //
 // DECODE
 // python pw_trace_tokenized/py/trace_tokenized.py -i trace.bin -o trace.json
-// ./out/pw_strict_host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_basic#trace
+// ./out/host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_basic
 //
 // VIEW
 // In chrome navigate to chrome://tracing, and load the trace.json file.
@@ -46,4 +46,4 @@
   PW_LOG_INFO("Running basic trace example...\n");
   RunTraceSampleApp();
   return 0;
-}
+}
\ No newline at end of file
diff --git a/pw_trace_tokenized/example/filter.cc b/pw_trace_tokenized/example/filter.cc
index 6d68fb9..573823a 100644
--- a/pw_trace_tokenized/example/filter.cc
+++ b/pw_trace_tokenized/example/filter.cc
@@ -12,17 +12,17 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 //==============================================================================
-// BUILD
+// BUID
 // ninja -C out
-// pw_strict_host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_filter
+// host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_filter
 //
 // RUN
-// ./out/pw_strict_host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_filter
+// .out/host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_filter
 // trace.bin
 //
 // DECODE
 // python pw_trace_tokenized/py/trace_tokenized.py -i trace.bin -o trace.json
-// ./out/pw_strict_host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_basic#trace
+// ./out/host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_basic
 //
 // VIEW
 // In chrome navigate to chrome://tracing, and load the trace.json file.
@@ -55,9 +55,7 @@
   }
 
   // Register filter callback
-  pw::trace::Callbacks::Instance()
-      .RegisterEventCallback(TraceEventCallback)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  pw::trace::Callbacks::Instance().RegisterEventCallback(TraceEventCallback);
 
   PW_TRACE_SET_ENABLED(true);  // Start with tracing enabled
 
diff --git a/pw_trace_tokenized/example/public/pw_trace_tokenized/example/trace_to_file.h b/pw_trace_tokenized/example/public/pw_trace_tokenized/example/trace_to_file.h
index 7527143..5c2d783 100644
--- a/pw_trace_tokenized/example/public/pw_trace_tokenized/example/trace_to_file.h
+++ b/pw_trace_tokenized/example/public/pw_trace_tokenized/example/trace_to_file.h
@@ -29,20 +29,16 @@
 class TraceToFile {
  public:
   TraceToFile(const char* file_name) {
-    Callbacks::Instance()
-        .RegisterSink(TraceSinkStartBlock,
-                      TraceSinkAddBytes,
-                      TraceSinkEndBlock,
-                      &out_,
-                      &sink_handle_)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    Callbacks::Instance().RegisterSink(TraceSinkStartBlock,
+                                       TraceSinkAddBytes,
+                                       TraceSinkEndBlock,
+                                       &out_,
+                                       &sink_handle_);
     out_.open(file_name, std::ios::out | std::ios::binary);
   }
 
   ~TraceToFile() {
-    Callbacks::Instance()
-        .UnregisterSink(sink_handle_)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    Callbacks::Instance().UnregisterSink(sink_handle_);
     out_.close();
   }
 
diff --git a/pw_trace_tokenized/example/rpc.cc b/pw_trace_tokenized/example/rpc.cc
index 5041bc3..8126d6a 100644
--- a/pw_trace_tokenized/example/rpc.cc
+++ b/pw_trace_tokenized/example/rpc.cc
@@ -13,27 +13,19 @@
 // the License.
 //==============================================================================
 /*
-
-NOTE
-To use this example you need to enable nanopb, one option is to set this in
-either your out/args.gn or the root .gn:
-default_args = {
-  dir_pw_third_party_nanopb = "<path to nanopb repo>"
-}
-
 BUILD
 ninja -C out
-pw_strict_host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_rpc
+host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_rpc
 
 RUN
-./out/pw_strict_host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_rpc
+.out/host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_rpc
 
 DECODE
 python pw_trace_tokenized/py/pw_trace_tokenized/get_trace.py
  -s localhost:33000
  -o trace.json
  -t
- out/pw_strict_host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_rpc
+ out/host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_rpc
  pw_trace_tokenized/pw_trace_protos/trace_rpc.proto
 
 VIEW
@@ -41,7 +33,6 @@
 */
 #include <thread>
 
-#include "pw_assert/check.h"
 #include "pw_log/log.h"
 #include "pw_rpc/server.h"
 #include "pw_rpc_system_server/rpc_server.h"
@@ -58,7 +49,7 @@
 
   // Set up the server and start processing data.
   pw::rpc::system_server::Server().RegisterService(trace_service);
-  PW_CHECK_OK(pw::rpc::system_server::Start());
+  pw::rpc::system_server::Start();
 }
 
 }  // namespace
@@ -72,4 +63,4 @@
   PW_LOG_INFO("Running basic trace example...\n");
   RunTraceSampleApp();
   return 0;
-}
+}
\ No newline at end of file
diff --git a/pw_trace_tokenized/example/trigger.cc b/pw_trace_tokenized/example/trigger.cc
index 1995aff..011ec5f 100644
--- a/pw_trace_tokenized/example/trigger.cc
+++ b/pw_trace_tokenized/example/trigger.cc
@@ -12,17 +12,17 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 //==============================================================================
-// BUILD
+// BUID
 // ninja -C out
-// pw_strict_host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_trigger
+// host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_trigger
 //
 // RUN
-// ./out/pw_strict_host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_trigger
+// .out/host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_trigger
 // trace.bin
 //
 // DECODE
 // python pw_trace_tokenized/py/trace_tokenized.py -i trace.bin -o trace.json
-// ./out/pw_strict_host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_basic#trace
+// ./out/host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_basic
 //
 // VIEW
 // In chrome navigate to chrome://tracing, and load the trace.json file.
@@ -77,10 +77,8 @@
   }
 
   // Register trigger callback
-  pw::trace::Callbacks::Instance()
-      .RegisterEventCallback(TraceEventCallback,
-                             pw::trace::CallbacksImpl::kCallOnEveryEvent)
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  pw::trace::Callbacks::Instance().RegisterEventCallback(
+      TraceEventCallback, pw::trace::CallbacksImpl::kCallOnEveryEvent);
 
   // Ensure tracing is off at start, the trigger will turn it on.
   PW_TRACE_SET_ENABLED(false);
diff --git a/pw_trace_tokenized/public/pw_trace_tokenized/config.h b/pw_trace_tokenized/public/pw_trace_tokenized/config.h
index 4167ace..b957610 100644
--- a/pw_trace_tokenized/public/pw_trace_tokenized/config.h
+++ b/pw_trace_tokenized/public/pw_trace_tokenized/config.h
@@ -102,7 +102,7 @@
 // could not be aquired.
 #ifndef PW_TRACE_TRY_LOCK
 #define PW_TRACE_TRY_LOCK() (true)  // Returns true if lock successful
-#endif                              // PW_TRACE_TRY_LOCK
+#endif  // PW_TRACE_TRY_LOCK
 
 #ifndef PW_TRACE_UNLOCK
 #define PW_TRACE_UNLOCK()
diff --git a/pw_trace_tokenized/public/pw_trace_tokenized/trace_buffer.h b/pw_trace_tokenized/public/pw_trace_tokenized/trace_buffer.h
index 5274e5e..66572f1 100644
--- a/pw_trace_tokenized/public/pw_trace_tokenized/trace_buffer.h
+++ b/pw_trace_tokenized/public/pw_trace_tokenized/trace_buffer.h
@@ -16,7 +16,6 @@
 // This file provides an optional trace buffer which can be used with the
 #pragma once
 
-#include "pw_bytes/span.h"
 #include "pw_ring_buffer/prefixed_entry_ring_buffer.h"
 #include "pw_trace_tokenized/config.h"
 #include "pw_trace_tokenized/trace_tokenized.h"
@@ -32,11 +31,5 @@
 // Get the ring buffer which contains the data.
 pw::ring_buffer::PrefixedEntryRingBuffer* GetBuffer();
 
-// View underlying buffer trace_tokenized provided ring_buffer at time of
-// construction. This allows for bulk access to the trace events buffer. Since
-// this also derings the underlying ring_buffer, ensure that tracing is disabled
-// when calling this function.
-ConstByteSpan DeringAndViewRawBuffer();
-
 }  // namespace trace
 }  // namespace pw
diff --git a/pw_trace_tokenized/public/pw_trace_tokenized/trace_callback.h b/pw_trace_tokenized/public/pw_trace_tokenized/trace_callback.h
index 6d9f23d..90d119d 100644
--- a/pw_trace_tokenized/public/pw_trace_tokenized/trace_callback.h
+++ b/pw_trace_tokenized/public/pw_trace_tokenized/trace_callback.h
@@ -212,20 +212,18 @@
       CallbacksImpl::CallOnEveryEvent called_on_every_event =
           CallbacksImpl::kCallOnlyWhenEnabled,
       void* user_data = nullptr) {
-    Callbacks::Instance()
-        .RegisterEventCallback(event_callback, called_on_every_event, user_data)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    Callbacks::Instance().RegisterEventCallback(
+        event_callback, called_on_every_event, user_data);
   }
   RegisterCallbackWhenCreated(CallbacksImpl::SinkStartBlock sink_start,
                               CallbacksImpl::SinkAddBytes sink_add_bytes,
                               CallbacksImpl::SinkEndBlock sink_end,
                               void* user_data = nullptr) {
-    Callbacks::Instance()
-        .RegisterSink(sink_start, sink_add_bytes, sink_end, user_data)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    Callbacks::Instance().RegisterSink(
+        sink_start, sink_add_bytes, sink_end, user_data);
   }
 };
 
 }  // namespace trace
 }  // namespace pw
-#endif  // __cplusplus
+#endif  // __cplusplus
\ No newline at end of file
diff --git a/pw_trace_tokenized/public/pw_trace_tokenized/trace_rpc_service_nanopb.h b/pw_trace_tokenized/public/pw_trace_tokenized/trace_rpc_service_nanopb.h
index 76aab91..cc326c1 100644
--- a/pw_trace_tokenized/public/pw_trace_tokenized/trace_rpc_service_nanopb.h
+++ b/pw_trace_tokenized/public/pw_trace_tokenized/trace_rpc_service_nanopb.h
@@ -17,16 +17,18 @@
 
 namespace pw::trace {
 
-class TraceService final
-    : public pw_rpc::nanopb::TraceService::Service<TraceService> {
+class TraceService final : public generated::TraceService<TraceService> {
  public:
-  pw::Status Enable(const pw_trace_TraceEnableMessage& request,
+  pw::Status Enable(ServerContext&,
+                    const pw_trace_TraceEnableMessage& request,
                     pw_trace_TraceEnableMessage& response);
 
-  pw::Status IsEnabled(const pw_trace_Empty& request,
+  pw::Status IsEnabled(ServerContext&,
+                       const pw_trace_Empty& request,
                        pw_trace_TraceEnableMessage& response);
 
-  void GetTraceData(const pw_trace_Empty& request,
+  void GetTraceData(ServerContext&,
+                    const pw_trace_Empty& request,
                     ServerWriter<pw_trace_TraceDataMessage>& writer);
 };
 
diff --git a/pw_trace_tokenized/public/pw_trace_tokenized/trace_tokenized.h b/pw_trace_tokenized/public/pw_trace_tokenized/trace_tokenized.h
index ec5b0ea..34d9456 100644
--- a/pw_trace_tokenized/public/pw_trace_tokenized/trace_tokenized.h
+++ b/pw_trace_tokenized/public/pw_trace_tokenized/trace_tokenized.h
@@ -65,9 +65,6 @@
     if (IsFull()) {
       return pw::Status::ResourceExhausted();
     }
-    if (data_size > PW_TRACE_BUFFER_MAX_DATA_SIZE_BYTES) {
-      return pw::Status::InvalidArgument();
-    }
     event_queue_[head_].trace_token = trace_token;
     event_queue_[head_].event_type = event_type;
     event_queue_[head_].module = module;
diff --git a/pw_trace_tokenized/pw_trace_protos/trace.proto b/pw_trace_tokenized/pw_trace_protos/trace.proto
deleted file mode 100644
index 66520c8..0000000
--- a/pw_trace_tokenized/pw_trace_protos/trace.proto
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-syntax = "proto3";
-
-package pw.trace;
-
-option java_package = "pw.trace.proto";
-option java_outer_classname = "Trace";
-
-// This message overlays the pw.snapshot.Snapshot proto. It's valid to encode
-// this message to the same sink that a Snapshot proto is being written to.
-message SnapshotTraceInfo {
-  // If this field is populated, it is expected to contain a de-ringed tokenized
-  // trace buffer.
-  bytes trace_data = 21;
-}
diff --git a/pw_trace_tokenized/py/BUILD.gn b/pw_trace_tokenized/py/BUILD.gn
index 4efae83..22ec840 100644
--- a/pw_trace_tokenized/py/BUILD.gn
+++ b/pw_trace_tokenized/py/BUILD.gn
@@ -17,11 +17,7 @@
 import("$dir_pw_build/python.gni")
 
 pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
+  setup = [ "setup.py" ]
   sources = [
     "pw_trace_tokenized/__init__.py",
     "pw_trace_tokenized/get_trace.py",
diff --git a/pw_trace_tokenized/py/pw_trace_tokenized/get_trace.py b/pw_trace_tokenized/py/pw_trace_tokenized/get_trace.py
index 1c3aa9b..988f62b 100755
--- a/pw_trace_tokenized/py/pw_trace_tokenized/get_trace.py
+++ b/pw_trace_tokenized/py/pw_trace_tokenized/get_trace.py
@@ -12,19 +12,16 @@
 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 # License for the specific language governing permissions and limitations under
 # the License.
-"""
+r"""
 Generates json trace files viewable using chrome://tracing using RPCs from a
 connected HdlcRpcClient.
 
 Example usage:
 python pw_trace_tokenized/py/pw_trace_tokenized/get_trace.py -s localhost:33000
   -o trace.json
-  -t
-  out/pw_strict_host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_rpc
+  -t out/host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_rpc
   pw_trace_tokenized/pw_trace_protos/trace_rpc.proto
-"""  # pylint: disable=line-too-long
-# pylint: enable=line-too-long
-
+"""
 import argparse
 import logging
 import glob
@@ -90,10 +87,10 @@
 
 
 def get_trace_data_from_device(client):
-    """Get the trace data using RPC from a Client"""
+    """ Get the trace data using RPC from a Client"""
     data = b''
-    service = client.client.channel(1).rpcs.pw.trace.TraceService
-    result = service.GetTraceData().responses
+    result = \
+        client.client.channel(1).rpcs.pw.trace.TraceService.GetTraceData().get()
     for streamed_data in result:
         data = data + bytes([len(streamed_data.data)])
         data = data + streamed_data.data
@@ -130,13 +127,7 @@
     parser.add_argument('proto_globs',
                         nargs='+',
                         help='glob pattern for .proto files')
-    parser.add_argument(
-        '-f',
-        '--ticks_per_second',
-        type=int,
-        dest='ticks_per_second',
-        default=1000,
-        help=('The clock rate of the trace events (Default 1000).'))
+
     return parser.parse_args()
 
 
@@ -146,8 +137,7 @@
     _LOG.info(database.database_summary(token_database))
     client = get_hdlc_rpc_client(**vars(args))
     data = get_trace_data_from_device(client)
-    events = trace_tokenized.get_trace_events([token_database], data,
-                                              args.ticks_per_second)
+    events = trace_tokenized.get_trace_events([token_database], data)
     json_lines = trace.generate_trace_json(events)
     trace_tokenized.save_trace_file(json_lines, args.trace_output_file)
 
diff --git a/pw_trace_tokenized/py/pw_trace_tokenized/trace_tokenized.py b/pw_trace_tokenized/py/pw_trace_tokenized/trace_tokenized.py
index 25108a5..fd16b3c 100755
--- a/pw_trace_tokenized/py/pw_trace_tokenized/trace_tokenized.py
+++ b/pw_trace_tokenized/py/pw_trace_tokenized/trace_tokenized.py
@@ -12,16 +12,14 @@
 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 # License for the specific language governing permissions and limitations under
 # the License.
-"""
+r"""
 Generates json trace files viewable using chrome://tracing from binary
 trace files.
 
 Example usage:
 python pw_trace_tokenized/py/trace_tokenized.py -i trace.bin -o trace.json
-out/pw_strict_host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_basic
-"""  # pylint: disable=line-too-long
-# pylint: enable=line-too-long
-
+./out/host_clang_debug/obj/pw_trace_tokenized/bin/trace_tokenized_example_basic
+"""
 from enum import IntEnum
 import argparse
 import logging
@@ -50,65 +48,65 @@
     return None
 
 
-# Token string: "event_type|flag|module|group|label|<optional DATA_FMT>"
+# Token string: "event_type|flag|module|group|label|<optional data_fmt>"
 class TokenIdx(IntEnum):
-    EVENT_TYPE = 0
-    FLAG = 1
-    MODULE = 2
-    GROUP = 3
-    LABEL = 4
-    DATA_FMT = 5  # optional
+    EventType = 0
+    Flag = 1
+    Module = 2
+    Group = 3
+    Label = 4
+    data_fmt = 5  # optional
 
 
 def get_trace_type(type_str):
     if type_str == "PW_TRACE_EVENT_TYPE_INSTANT":
-        return trace.TraceType.INSTANTANEOUS
+        return trace.TraceType.Instantaneous
     if type_str == "PW_TRACE_EVENT_TYPE_INSTANT_GROUP":
-        return trace.TraceType.INSTANTANEOUS_GROUP
+        return trace.TraceType.InstantaneousGroup
     if type_str == "PW_TRACE_EVENT_TYPE_ASYNC_START":
-        return trace.TraceType.ASYNC_START
+        return trace.TraceType.AsyncStart
     if type_str == "PW_TRACE_EVENT_TYPE_ASYNC_STEP":
-        return trace.TraceType.ASYNC_STEP
+        return trace.TraceType.AsyncStep
     if type_str == "PW_TRACE_EVENT_TYPE_ASYNC_END":
-        return trace.TraceType.ASYNC_END
+        return trace.TraceType.AsyncEnd
     if type_str == "PW_TRACE_EVENT_TYPE_DURATION_START":
-        return trace.TraceType.DURATION_START
+        return trace.TraceType.DurationStart
     if type_str == "PW_TRACE_EVENT_TYPE_DURATION_END":
-        return trace.TraceType.DURATION_END
+        return trace.TraceType.DurationEnd
     if type_str == "PW_TRACE_EVENT_TYPE_DURATION_GROUP_START":
-        return trace.TraceType.DURATION_GROUP_START
+        return trace.TraceType.DurationGroupStart
     if type_str == "PW_TRACE_EVENT_TYPE_DURATION_GROUP_END":
-        return trace.TraceType.DURATION_GROUP_END
-    return trace.TraceType.INVALID
+        return trace.TraceType.DurationGroupEnd
+    return trace.TraceType.Invalid
 
 
 def has_trace_id(token_string):
     token_values = token_string.split("|")
-    return trace.event_has_trace_id(token_values[TokenIdx.EVENT_TYPE])
+    return trace.event_has_trace_id(token_values[TokenIdx.EventType])
 
 
 def has_data(token_string):
     token_values = token_string.split("|")
-    return len(token_values) > TokenIdx.DATA_FMT
+    return len(token_values) > TokenIdx.data_fmt
 
 
 def create_trace_event(token_string, timestamp_us, trace_id, data):
     token_values = token_string.split("|")
     return trace.TraceEvent(event_type=get_trace_type(
-        token_values[TokenIdx.EVENT_TYPE]),
-                            module=token_values[TokenIdx.MODULE],
-                            label=token_values[TokenIdx.LABEL],
+        token_values[TokenIdx.EventType]),
+                            module=token_values[TokenIdx.Module],
+                            label=token_values[TokenIdx.Label],
                             timestamp_us=timestamp_us,
-                            group=token_values[TokenIdx.GROUP],
+                            group=token_values[TokenIdx.Group],
                             trace_id=trace_id,
-                            flags=token_values[TokenIdx.FLAG],
+                            flags=token_values[TokenIdx.Flag],
                             has_data=has_data(token_string),
-                            data_fmt=(token_values[TokenIdx.DATA_FMT]
+                            data_fmt=(token_values[TokenIdx.data_fmt]
                                       if has_data(token_string) else ""),
                             data=data if has_data(token_string) else b'')
 
 
-def parse_trace_event(buffer, db, last_time, ticks_per_second):
+def parse_trace_event(buffer, db, last_time, ticks_per_second=1000):
     """Parse a single trace event from bytes"""
     us_per_tick = 1000000 / ticks_per_second
     idx = 0
@@ -143,7 +141,7 @@
     return create_trace_event(token_string, timestamp_us, trace_id, data)
 
 
-def get_trace_events(databases, raw_trace_data, ticks_per_second):
+def get_trace_events(databases, raw_trace_data):
     """Handles the decoding traces."""
 
     db = tokens.Database.merged(*databases)
@@ -159,7 +157,7 @@
             break
 
         event = parse_trace_event(raw_trace_data[idx + 1:idx + 1 + size], db,
-                                  last_timestamp, ticks_per_second)
+                                  last_timestamp)
         if event:
             last_timestamp = event.timestamp_us
             events.append(event)
@@ -183,10 +181,10 @@
         output_file.write("{}]")
 
 
-def get_trace_events_from_file(databases, input_file_name, ticks_per_second):
+def get_trace_events_from_file(databases, input_file_name):
     """Get trace events from a file."""
     raw_trace_data = get_trace_data_from_file(input_file_name)
-    return get_trace_events(databases, raw_trace_data, ticks_per_second)
+    return get_trace_events(databases, raw_trace_data)
 
 
 def _parse_args():
@@ -209,20 +207,12 @@
                         '--output',
                         dest='output_file',
                         help=('The json file to which to write the output.'))
-    parser.add_argument(
-        '-t',
-        '--ticks_per_second',
-        type=int,
-        dest='ticks_per_second',
-        default=1000,
-        help=('The clock rate of the trace events (Default 1000).'))
 
     return parser.parse_args()
 
 
 def _main(args):
-    events = get_trace_events_from_file(args.databases, args.input_file,
-                                        args.ticks_per_second)
+    events = get_trace_events_from_file(args.databases, args.input_file)
     json_lines = trace.generate_trace_json(events)
     save_trace_file(json_lines, args.output_file)
 
diff --git a/pw_trace_tokenized/py/pyproject.toml b/pw_trace_tokenized/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/pw_trace_tokenized/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/pw_trace_tokenized/py/setup.cfg b/pw_trace_tokenized/py/setup.cfg
deleted file mode 100644
index 5a5cfb2..0000000
--- a/pw_trace_tokenized/py/setup.cfg
+++ /dev/null
@@ -1,30 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pw_trace_tokenized
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = pw_trace backend to tokenize trace events
-
-[options]
-packages = find:
-zip_safe = False
-install_requires =
-    pw_hdlc
-    pw_tokenizer
-    pw_trace
-
-[options.package_data]
-pw_trace_tokenized = py.typed
diff --git a/pw_trace_tokenized/py/setup.py b/pw_trace_tokenized/py/setup.py
index 7deec83..c20e9cf 100644
--- a/pw_trace_tokenized/py/setup.py
+++ b/pw_trace_tokenized/py/setup.py
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2020 The Pigweed Authors
 #
 # 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
@@ -11,8 +11,18 @@
 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 # License for the specific language governing permissions and limitations under
 # the License.
-"""pw_trace_tokenized"""
+"""The pw_trace_tokenized package."""
 
 import setuptools  # type: ignore
 
-setuptools.setup()  # Package definition in setup.cfg
+setuptools.setup(
+    name='pw_trace_tokenized',
+    version='0.0.1',
+    author='Pigweed Authors',
+    author_email='pigweed-developers@googlegroups.com',
+    description='pw_trace backend to tokenize trace events',
+    packages=setuptools.find_packages(),
+    package_data={'pw_trace_tokenized': ['py.typed']},
+    zip_safe=False,
+    install_requires=['pw_tokenizer'],
+)
diff --git a/pw_trace_tokenized/trace.cc b/pw_trace_tokenized/trace.cc
index ccc9ae5..3769ca2 100644
--- a/pw_trace_tokenized/trace.cc
+++ b/pw_trace_tokenized/trace.cc
@@ -180,7 +180,7 @@
     if (sink_callbacks_[sink_idx].add_bytes) {
       sink_callbacks_[sink_idx].add_bytes(
           user_data, header.data(), header.size());
-      if (!data.empty()) {
+      if (data.size() > 0) {
         sink_callbacks_[sink_idx].add_bytes(
             user_data, data.data(), data.size());
       }
@@ -229,8 +229,7 @@
 
 pw::Status CallbacksImpl::UnregisterAllSinks() {
   for (size_t sink_idx = 0; sink_idx < PW_TRACE_CONFIG_MAX_SINKS; sink_idx++) {
-    UnregisterSink(sink_idx)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    UnregisterSink(sink_idx);
   }
   return PW_STATUS_OK;
 }
@@ -282,8 +281,7 @@
 
 pw::Status CallbacksImpl::UnregisterAllEventCallbacks() {
   for (size_t i = 0; i < PW_TRACE_CONFIG_MAX_EVENT_CALLBACKS; i++) {
-    UnregisterEventCallback(i)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    UnregisterEventCallback(i);
   }
   return PW_STATUS_OK;
 }
diff --git a/pw_trace_tokenized/trace_buffer.cc b/pw_trace_tokenized/trace_buffer.cc
index 31314ec..2a5fc5b 100644
--- a/pw_trace_tokenized/trace_buffer.cc
+++ b/pw_trace_tokenized/trace_buffer.cc
@@ -27,12 +27,9 @@
 class TraceBuffer {
  public:
   TraceBuffer() {
-    ring_buffer_.SetBuffer(raw_buffer_)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    Callbacks::Instance()
-        .RegisterSink(
-            TraceSinkStartBlock, TraceSinkAddBytes, TraceSinkEndBlock, this)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    ring_buffer_.SetBuffer(raw_buffer_);
+    Callbacks::Instance().RegisterSink(
+        TraceSinkStartBlock, TraceSinkAddBytes, TraceSinkEndBlock, this);
   }
 
   static void TraceSinkStartBlock(void* user_data, size_t size) {
@@ -62,22 +59,14 @@
     if (buffer->block_idx_ != buffer->block_size_) {
       return;  // Block is too large, skipping.
     }
-    buffer->ring_buffer_
-        .PushBack(std::span<const std::byte>(&buffer->current_block_[0],
-                                             buffer->block_size_))
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    buffer->ring_buffer_.PushBack(std::span<const std::byte>(
+        &buffer->current_block_[0], buffer->block_size_));
   }
 
   pw::ring_buffer::PrefixedEntryRingBuffer& RingBuffer() {
     return ring_buffer_;
   };
 
-  ConstByteSpan DeringAndViewRawBuffer() {
-    ring_buffer_.Dering()
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    return ByteSpan(raw_buffer_, ring_buffer_.TotalUsedBytes());
-  }
-
  private:
   uint16_t block_size_ = 0;
   uint16_t block_idx_ = 0;
@@ -98,9 +87,5 @@
   return &trace_buffer_instance.RingBuffer();
 }
 
-ConstByteSpan DeringAndViewRawBuffer() {
-  return trace_buffer_instance.DeringAndViewRawBuffer();
-}
-
 }  // namespace trace
-}  // namespace pw
+}  // namespace pw
\ No newline at end of file
diff --git a/pw_trace_tokenized/trace_buffer_log.cc b/pw_trace_tokenized/trace_buffer_log.cc
index 11019ba..adaf17a 100644
--- a/pw_trace_tokenized/trace_buffer_log.cc
+++ b/pw_trace_tokenized/trace_buffer_log.cc
@@ -55,8 +55,7 @@
   PW_LOG_INFO("[TRACE] begin");
   while (trace_buffer->PeekFront(std::span(entry_buffer).subspan(1),
                                  &bytes_read) != pw::Status::OutOfRange()) {
-    trace_buffer->PopFront()
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    trace_buffer->PopFront();
     entry_buffer[0] = static_cast<std::byte>(bytes_read);
     // The entry buffer is formatted as (size, entry) with an extra byte as
     // a header to the entry. The calcuation of bytes_read + 1 represents
@@ -75,7 +74,7 @@
     }
     line_builder.append(entry_base64_buffer + written, to_write - written);
   }
-  if (!line_builder.empty()) {
+  if (line_builder.size() > 0) {
     PW_LOG_INFO("[TRACE] data: %s", line_builder.c_str());
   }
   PW_LOG_INFO("[TRACE] end");
diff --git a/pw_trace_tokenized/trace_buffer_log_test.cc b/pw_trace_tokenized/trace_buffer_log_test.cc
index 20c9366..e2327e2 100644
--- a/pw_trace_tokenized/trace_buffer_log_test.cc
+++ b/pw_trace_tokenized/trace_buffer_log_test.cc
@@ -27,8 +27,7 @@
   PW_TRACE_SET_ENABLED(true);
   PW_TRACE_INSTANT("test1");
   PW_TRACE_INSTANT("test2");
-  pw::trace::DumpTraceBufferToLog()
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  pw::trace::DumpTraceBufferToLog();
 }
 
 TEST(TokenizedTrace, DumpLargeBuffer) {
@@ -40,6 +39,5 @@
   for (int i = 0; i < 100; i++) {
     PW_TRACE_INSTANT("test");
   }
-  pw::trace::DumpTraceBufferToLog()
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  pw::trace::DumpTraceBufferToLog();
 }
diff --git a/pw_trace_tokenized/trace_buffer_test.cc b/pw_trace_tokenized/trace_buffer_test.cc
index c2f4b76..410db74 100644
--- a/pw_trace_tokenized/trace_buffer_test.cc
+++ b/pw_trace_tokenized/trace_buffer_test.cc
@@ -125,10 +125,8 @@
     EXPECT_EQ(buf->PeekFront(std::span<std::byte>(value), &bytes_read),
               pw::OkStatus());
     EXPECT_EQ(buf->PopFront(), pw::OkStatus());
-    size_t entry_count;
-    memcpy(
-        &entry_count, &value[bytes_read - sizeof(size_t)], sizeof(entry_count));
-    EXPECT_EQ(entry_count, expected_count);
+    EXPECT_EQ(*reinterpret_cast<size_t*>(&value[bytes_read - sizeof(size_t)]),
+              expected_count);
     expected_count++;
   }
 
@@ -142,24 +140,4 @@
   uint8_t data[PW_TRACE_BUFFER_MAX_BLOCK_SIZE_BYTES];
   PW_TRACE_INSTANT_DATA("Test", "data", data, sizeof(data));
   EXPECT_EQ(buf->EntryCount(), 0u);
-}
-
-TEST(TokenizedTrace, DeringAndViewRawBuffer) {
-  PW_TRACE_SET_ENABLED(true);
-  pw::trace::ClearBuffer();
-
-  // Should be empty span
-  pw::ConstByteSpan buf = pw::trace::DeringAndViewRawBuffer();
-  EXPECT_EQ(buf.size(), 0u);
-
-  // Should now have data
-  PW_TRACE_INSTANT("Test");
-  buf = pw::trace::DeringAndViewRawBuffer();
-  EXPECT_GT(buf.size(), 0u);
-
-  // Should now have more data
-  PW_TRACE_INSTANT("Test");
-  size_t size_start = buf.size();
-  buf = pw::trace::DeringAndViewRawBuffer();
-  EXPECT_GT(buf.size(), size_start);
-}
+}
\ No newline at end of file
diff --git a/pw_trace_tokenized/trace_rpc_service_nanopb.cc b/pw_trace_tokenized/trace_rpc_service_nanopb.cc
index 55b3f43..a6893da 100644
--- a/pw_trace_tokenized/trace_rpc_service_nanopb.cc
+++ b/pw_trace_tokenized/trace_rpc_service_nanopb.cc
@@ -22,21 +22,25 @@
 
 namespace pw::trace {
 
-pw::Status TraceService::Enable(const pw_trace_TraceEnableMessage& request,
+pw::Status TraceService::Enable(ServerContext&,
+                                const pw_trace_TraceEnableMessage& request,
                                 pw_trace_TraceEnableMessage& response) {
   TokenizedTrace::Instance().Enable(request.enable);
   response.enable = TokenizedTrace::Instance().IsEnabled();
   return PW_STATUS_OK;
 }
 
-pw::Status TraceService::IsEnabled(const pw_trace_Empty&,
+pw::Status TraceService::IsEnabled(ServerContext&,
+                                   const pw_trace_Empty&,
                                    pw_trace_TraceEnableMessage& response) {
   response.enable = TokenizedTrace::Instance().IsEnabled();
   return PW_STATUS_OK;
 }
 
 void TraceService::GetTraceData(
-    const pw_trace_Empty&, ServerWriter<pw_trace_TraceDataMessage>& writer) {
+    ServerContext&,
+    const pw_trace_Empty&,
+    ServerWriter<pw_trace_TraceDataMessage>& writer) {
   pw_trace_TraceDataMessage buffer = pw_trace_TraceDataMessage_init_default;
   size_t size = 0;
   pw::ring_buffer::PrefixedEntryRingBuffer* trace_buffer =
@@ -45,8 +49,7 @@
   while (trace_buffer->PeekFront(
              std::as_writable_bytes(std::span(buffer.data.bytes)), &size) !=
          pw::Status::OutOfRange()) {
-    trace_buffer->PopFront()
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    trace_buffer->PopFront();
     buffer.data.size = size;
     pw::Status status = writer.Write(buffer);
     if (!status.ok()) {
@@ -55,6 +58,6 @@
       break;
     }
   }
-  writer.Finish().IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  writer.Finish();
 }
-}  // namespace pw::trace
+}  // namespace pw::trace
\ No newline at end of file
diff --git a/pw_trace_tokenized/trace_test.cc b/pw_trace_tokenized/trace_test.cc
index d2dd316..616d908 100644
--- a/pw_trace_tokenized/trace_test.cc
+++ b/pw_trace_tokenized/trace_test.cc
@@ -56,27 +56,21 @@
 
   TraceTestInterface() {
     PW_TRACE_SET_ENABLED(true);
-    pw::trace::Callbacks::Instance()
-        .RegisterSink(TraceSinkStartBlock,
-                      TraceSinkAddBytes,
-                      TraceSinkEndBlock,
-                      this,
-                      &sink_handle_)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    pw::trace::Callbacks::Instance()
-        .RegisterEventCallback(TraceEventCallback,
-                               pw::trace::CallbacksImpl::kCallOnlyWhenEnabled,
-                               this,
-                               &event_callback_handle_)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    pw::trace::Callbacks::Instance().RegisterSink(TraceSinkStartBlock,
+                                                  TraceSinkAddBytes,
+                                                  TraceSinkEndBlock,
+                                                  this,
+                                                  &sink_handle_);
+    pw::trace::Callbacks::Instance().RegisterEventCallback(
+        TraceEventCallback,
+        pw::trace::CallbacksImpl::kCallOnlyWhenEnabled,
+        this,
+        &event_callback_handle_);
   }
   ~TraceTestInterface() {
-    pw::trace::Callbacks::Instance()
-        .UnregisterSink(sink_handle_)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    pw::trace::Callbacks::Instance()
-        .UnregisterEventCallback(event_callback_handle_)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    pw::trace::Callbacks::Instance().UnregisterSink(sink_handle_);
+    pw::trace::Callbacks::Instance().UnregisterEventCallback(
+        event_callback_handle_);
   }
   // ActionOnEvent will perform a specific action within the callback when an
   // event matches one of the characteristics of event_match_.
@@ -574,8 +568,7 @@
   constexpr size_t kQueueSize = 5;
   pw::trace::internal::TraceQueue<kQueueSize> queue;
   constexpr size_t kTestNum = 1;
-  queue.TryPushBack(QUEUE_TESTS_ARGS(kTestNum))
-      .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  queue.TryPushBack(QUEUE_TESTS_ARGS(kTestNum));
   EXPECT_FALSE(queue.IsEmpty());
   EXPECT_FALSE(queue.IsFull());
   EXPECT_TRUE(QUEUE_CHECK_RESULT(kQueueSize, queue.PeekFront(), kTestNum));
diff --git a/pw_transfer/BUILD.bazel b/pw_transfer/BUILD.bazel
deleted file mode 100644
index 3bae8c0..0000000
--- a/pw_transfer/BUILD.bazel
+++ /dev/null
@@ -1,236 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load("//pw_build:pigweed.bzl", "pw_cc_binary", "pw_cc_library", "pw_cc_test")
-load("//pw_protobuf_compiler:proto.bzl", "pw_proto_library")
-load("@com_google_protobuf//:protobuf.bzl", "py_proto_library")
-load("@rules_proto//proto:defs.bzl", "proto_library")
-load("@rules_proto_grpc//js:defs.bzl", "js_proto_library")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "config",
-    hdrs = ["public/pw_transfer/internal/config.h"],
-    includes = ["public"],
-    deps = [
-        "//pw_chrono:system_clock",
-    ],
-)
-
-pw_cc_library(
-    name = "core",
-    srcs = [
-        "chunk.cc",
-        "client_context.cc",
-        "context.cc",
-        "public/pw_transfer/internal/chunk.h",
-        "public/pw_transfer/internal/client_context.h",
-        "public/pw_transfer/internal/context.h",
-        "public/pw_transfer/internal/event.h",
-        "public/pw_transfer/internal/server_context.h",
-        "rate_estimate.cc",
-        "server_context.cc",
-        "transfer_thread.cc",
-    ],
-    hdrs = [
-        "public/pw_transfer/handler.h",
-        "public/pw_transfer/rate_estimate.h",
-        "public/pw_transfer/transfer_thread.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":config",
-        ":transfer_pwpb",
-        "//pw_bytes",
-        "//pw_chrono:system_clock",
-        "//pw_containers:intrusive_list",
-        "//pw_log",
-        "//pw_preprocessor",
-        "//pw_protobuf",
-        "//pw_result",
-        "//pw_rpc:client_server",
-        "//pw_rpc:internal_packet_cc.pwpb",
-        "//pw_rpc/raw:client_api",
-        "//pw_rpc/raw:server_api",
-        "//pw_status",
-        "//pw_stream",
-        "//pw_sync:binary_semaphore",
-        "//pw_sync:timed_thread_notification",
-        "//pw_thread:thread_core",
-        "//pw_varint",
-    ],
-)
-
-pw_cc_library(
-    name = "pw_transfer",
-    srcs = [
-        "transfer.cc",
-    ],
-    hdrs = [
-        "public/pw_transfer/transfer.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":core",
-        "//pw_assert",
-        "//pw_bytes",
-        "//pw_log",
-        "//pw_result",
-        "//pw_rpc:internal_packet_cc.pwpb",
-        "//pw_rpc/raw:server_api",
-        "//pw_status",
-        "//pw_stream",
-    ],
-)
-
-pw_cc_library(
-    name = "client",
-    srcs = [
-        "client.cc",
-    ],
-    hdrs = [
-        "public/pw_transfer/client.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":core",
-        "//pw_assert",
-        "//pw_function",
-        "//pw_log",
-        "//pw_stream",
-    ],
-)
-
-pw_cc_library(
-    name = "test_helpers",
-    srcs = [
-        "pw_transfer_private/chunk_testing.h",
-    ],
-    deps = [
-        ":core",
-        "//pw_containers",
-    ],
-)
-
-# TODO(pwbug/507): Add the client integration test to the build.
-filegroup(
-    name = "integration_test",
-    srcs = ["integration_test.cc"],
-)
-
-pw_cc_test(
-    name = "handler_test",
-    srcs = ["handler_test.cc"],
-    deps = [
-        ":pw_transfer",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "transfer_thread_test",
-    srcs = ["transfer_thread_test.cc"],
-    deps = [
-        ":pw_transfer",
-        ":test_helpers",
-        "//pw_rpc:thread_testing",
-        "//pw_rpc/raw:client_testing",
-        "//pw_rpc/raw:test_method_context",
-        "//pw_thread:thread",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "transfer_test",
-    srcs = ["transfer_test.cc"],
-    deps = [
-        ":pw_transfer",
-        ":test_helpers",
-        "//pw_rpc:thread_testing",
-        "//pw_rpc/raw:test_method_context",
-        "//pw_thread:thread",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "client_test",
-    srcs = ["client_test.cc"],
-    deps = [
-        ":client",
-        ":test_helpers",
-        "//pw_rpc:thread_testing",
-        "//pw_rpc/raw:client_testing",
-        "//pw_thread:sleep",
-        "//pw_thread:thread",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_binary(
-    name = "test_rpc_server",
-    srcs = ["test_rpc_server.cc"],
-    deps = [
-        ":pw_transfer",
-        ":test_server_pwpb",
-        "//pw_log",
-        "//pw_rpc/system_server",
-        "//pw_stream:std_file_stream",
-        "//pw_thread:thread",
-        "//pw_work_queue",
-    ],
-)
-
-proto_library(
-    name = "transfer_proto",
-    srcs = [
-        "transfer.proto",
-    ],
-)
-
-pw_proto_library(
-    name = "transfer_pwpb",
-    deps = [":transfer_proto"],
-)
-
-py_proto_library(
-    name = "transfer_proto_pb2",
-    srcs = ["transfer.proto"],
-)
-
-js_proto_library(
-    name = "transfer_proto_tspb",
-    protos = [":transfer_proto"],
-)
-
-proto_library(
-    name = "test_server",
-    srcs = [
-        "test_server.proto",
-    ],
-    import_prefix = "pw_transfer_test",
-    strip_import_prefix = "//pw_transfer",
-    deps = [
-        "//pw_protobuf:common_protos",
-    ],
-)
-
-pw_proto_library(
-    name = "test_server_pwpb",
-    deps = [":test_server"],
-)
diff --git a/pw_transfer/BUILD.gn b/pw_transfer/BUILD.gn
deleted file mode 100644
index cf47f33..0000000
--- a/pw_transfer/BUILD.gn
+++ /dev/null
@@ -1,262 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/module_config.gni")
-import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_protobuf_compiler/proto.gni")
-import("$dir_pw_rpc/internal/integration_test_ports.gni")
-import("$dir_pw_thread/backend.gni")
-import("$dir_pw_toolchain/generate_toolchain.gni")
-import("$dir_pw_unit_test/test.gni")
-
-declare_args() {
-  # The build target that overrides the default configuration options for this
-  # module. This should point to a source set that provides defines through a
-  # public config (which may -include a file or add defines directly).
-  pw_transfer_CONFIG = pw_build_DEFAULT_MODULE_CONFIG
-}
-
-config("public_include_path") {
-  include_dirs = [ "public" ]
-  visibility = [ ":*" ]
-}
-
-pw_source_set("config") {
-  public = [ "public/pw_transfer/internal/config.h" ]
-  public_configs = [ ":public_include_path" ]
-  public_deps = [
-    "$dir_pw_chrono:system_timer",
-    pw_transfer_CONFIG,
-  ]
-  visibility = [ ":*" ]
-}
-
-pw_source_set("pw_transfer") {
-  public_configs = [ ":public_include_path" ]
-  public_deps = [
-    ":core",
-    ":proto.raw_rpc",
-    dir_pw_assert,
-    dir_pw_bytes,
-    dir_pw_result,
-    dir_pw_status,
-    dir_pw_stream,
-  ]
-  deps = [ dir_pw_log ]
-  public = [ "public/pw_transfer/transfer.h" ]
-  sources = [ "transfer.cc" ]
-}
-
-pw_source_set("client") {
-  public_configs = [ ":public_include_path" ]
-  public_deps = [
-    ":core",
-    ":proto.raw_rpc",
-    dir_pw_assert,
-    dir_pw_function,
-    dir_pw_stream,
-  ]
-  deps = [ dir_pw_log ]
-  public = [ "public/pw_transfer/client.h" ]
-  sources = [ "client.cc" ]
-}
-
-pw_source_set("core") {
-  public_configs = [ ":public_include_path" ]
-  public_deps = [
-    ":config",
-    "$dir_pw_chrono:system_clock",
-    "$dir_pw_preprocessor",
-    "$dir_pw_rpc:client",
-    "$dir_pw_rpc/raw:client_api",
-    "$dir_pw_rpc/raw:server_api",
-    "$dir_pw_sync:binary_semaphore",
-    "$dir_pw_sync:timed_thread_notification",
-    "$dir_pw_thread:thread_core",
-    dir_pw_assert,
-    dir_pw_bytes,
-    dir_pw_result,
-    dir_pw_status,
-    dir_pw_stream,
-  ]
-  deps = [
-    ":proto.pwpb",
-    dir_pw_log,
-    dir_pw_protobuf,
-    dir_pw_varint,
-  ]
-  public = [
-    "public/pw_transfer/handler.h",
-    "public/pw_transfer/rate_estimate.h",
-    "public/pw_transfer/transfer_thread.h",
-  ]
-  sources = [
-    "chunk.cc",
-    "client_context.cc",
-    "context.cc",
-    "public/pw_transfer/internal/chunk.h",
-    "public/pw_transfer/internal/client_context.h",
-    "public/pw_transfer/internal/context.h",
-    "public/pw_transfer/internal/event.h",
-    "public/pw_transfer/internal/server_context.h",
-    "rate_estimate.cc",
-    "server_context.cc",
-    "transfer_thread.cc",
-  ]
-  friend = [ ":*" ]
-  visibility = [ ":*" ]
-}
-
-pw_source_set("test_helpers") {
-  public_deps = [
-    ":core",
-    dir_pw_containers,
-  ]
-  sources = [ "pw_transfer_private/chunk_testing.h" ]
-  friend = [ ":*" ]
-  visibility = [ ":*" ]
-}
-
-pw_proto_library("proto") {
-  sources = [ "transfer.proto" ]
-  python_package = "py"
-  prefix = "pw_transfer"
-}
-
-pw_test_group("tests") {
-  tests = []
-
-  # pw_transfer requires threading.
-  if (pw_thread_THREAD_BACKEND != "") {
-    tests = [
-      ":client_test",
-      ":transfer_thread_test",
-    ]
-
-    # TODO(pwbug/441): Fix transfer tests on Windows and non-host builds.
-    if (defined(pw_toolchain_SCOPE.is_host_toolchain) &&
-        pw_toolchain_SCOPE.is_host_toolchain && host_os != "win") {
-      tests += [
-        ":handler_test",
-        ":transfer_test",
-      ]
-    }
-  }
-}
-
-pw_test("handler_test") {
-  sources = [ "handler_test.cc" ]
-  deps = [ ":pw_transfer" ]
-}
-
-pw_test("transfer_test") {
-  sources = [ "transfer_test.cc" ]
-  deps = [
-    ":proto.pwpb",
-    ":pw_transfer",
-    ":test_helpers",
-    "$dir_pw_rpc:thread_testing",
-    "$dir_pw_rpc/raw:test_method_context",
-    "$dir_pw_thread:thread",
-  ]
-}
-
-pw_test("transfer_thread_test") {
-  sources = [ "transfer_thread_test.cc" ]
-  deps = [
-    ":core",
-    ":proto.raw_rpc",
-    ":pw_transfer",
-    ":test_helpers",
-    "$dir_pw_rpc:thread_testing",
-    "$dir_pw_rpc/raw:client_testing",
-    "$dir_pw_rpc/raw:test_method_context",
-    "$dir_pw_thread:thread",
-  ]
-}
-
-pw_test("client_test") {
-  sources = [ "client_test.cc" ]
-  deps = [
-    ":client",
-    ":test_helpers",
-    "$dir_pw_rpc:thread_testing",
-    "$dir_pw_rpc/raw:client_testing",
-    "$dir_pw_thread:sleep",
-    "$dir_pw_thread:thread",
-  ]
-}
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-  inputs = [
-    "transfer.proto",
-    "read.svg",
-    "write.svg",
-  ]
-}
-
-pw_proto_library("test_server_proto") {
-  sources = [ "test_server.proto" ]
-  prefix = "pw_transfer_test"
-  deps = [ "$dir_pw_protobuf:common_protos" ]
-}
-
-pw_executable("test_rpc_server") {
-  sources = [ "test_rpc_server.cc" ]
-  deps = [
-    ":pw_transfer",
-    ":test_server_proto.raw_rpc",
-    "$dir_pw_rpc/system_server",
-    "$dir_pw_rpc/system_server:socket",
-    "$dir_pw_stream:std_file_stream",
-    "$dir_pw_thread:thread",
-    dir_pw_log,
-  ]
-}
-
-pw_executable("integration_test") {
-  sources = [ "integration_test.cc" ]
-  deps = [
-    ":client",
-    ":test_server_proto.raw_rpc",
-    "$dir_pw_rpc:integration_testing",
-    "$dir_pw_sync:binary_semaphore",
-    "$dir_pw_thread:thread",
-    dir_pw_assert,
-    dir_pw_log,
-    dir_pw_unit_test,
-  ]
-}
-
-pw_python_action("cpp_client_integration_test") {
-  script = "$dir_pw_rpc/py/pw_rpc/testing.py"
-  args = [
-    "--server",
-    "<TARGET_FILE(:test_rpc_server)>",
-    "--client",
-    "<TARGET_FILE(:integration_test)>",
-    "--",
-    "$pw_transfer_CPP_CPP_TRANSFER_TEST_PORT",
-    "(pw_rpc:CREATE_TEMP_DIR)",
-  ]
-  deps = [
-    ":integration_test",
-    ":test_rpc_server",
-  ]
-
-  stamp = true
-}
diff --git a/pw_transfer/CMakeLists.txt b/pw_transfer/CMakeLists.txt
deleted file mode 100644
index 7f03af2..0000000
--- a/pw_transfer/CMakeLists.txt
+++ /dev/null
@@ -1,80 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-include($ENV{PW_ROOT}/pw_protobuf_compiler/proto.cmake)
-
-pw_add_module_config(pw_transfer_CONFIG)
-
-pw_add_module_library(pw_transfer.config
-  PUBLIC_DEPS
-    ${pw_transfer_CONFIG}
-  HEADERS
-    public/pw_transfer/internal/config.h
-)
-
-pw_add_module_library(pw_transfer
-  PUBLIC_DEPS
-    pw_assert
-    pw_result
-    pw_status
-    pw_stream
-    pw_transfer.core
-    pw_transfer.proto.raw_rpc
-  PRIVATE_DEPS
-    pw_log
-    pw_transfer.proto.pwpb
-  TEST_DEPS
-    pw_rpc.test_utils
-)
-
-pw_add_module_library(pw_transfer.client
-  PUBLIC_DEPS
-    pw_assert
-    pw_function
-    pw_stream
-    pw_sync.mutex
-    pw_transfer.core
-    pw_transfer.proto.raw_rpc
-  PRIVATE_DEPS
-    pw_log
-    pw_transfer.proto.pwpb
-  TEST_DEPS
-    pw_rpc.test_utils
-)
-
-pw_add_module_library(pw_transfer.core
-  PUBLIC_DEPS
-    pw_bytes
-    pw_chrono.system_clock
-    pw_containers.intrusive_list
-    pw_result
-    pw_rpc.client
-    pw_status
-    pw_stream
-    pw_sync.binary_semaphore
-    pw_thread.thread_core
-    pw_transfer.config
-  PRIVATE_DEPS
-    pw_protobuf
-    pw_transfer.proto.pwpb
-    pw_varint
-)
-
-pw_proto_library(pw_transfer.proto
-  SOURCES
-    transfer.proto
-  PREFIX
-    pw_transfer
-)
diff --git a/pw_transfer/OWNERS b/pw_transfer/OWNERS
deleted file mode 100644
index d96cbc6..0000000
--- a/pw_transfer/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-hepler@google.com
diff --git a/pw_transfer/chunk.cc b/pw_transfer/chunk.cc
deleted file mode 100644
index 7066e25..0000000
--- a/pw_transfer/chunk.cc
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_transfer/internal/chunk.h"
-
-#include "pw_protobuf/decoder.h"
-#include "pw_status/try.h"
-#include "pw_transfer/transfer.pwpb.h"
-
-namespace pw::transfer::internal {
-
-namespace ProtoChunk = transfer::Chunk;
-
-Result<uint32_t> ExtractTransferId(ConstByteSpan message) {
-  protobuf::Decoder decoder(message);
-
-  while (decoder.Next().ok()) {
-    ProtoChunk::Fields field =
-        static_cast<ProtoChunk::Fields>(decoder.FieldNumber());
-
-    switch (field) {
-      case ProtoChunk::Fields::TRANSFER_ID: {
-        uint32_t transfer_id;
-        PW_TRY(decoder.ReadUint32(&transfer_id));
-        return transfer_id;
-      }
-
-      default:
-        continue;
-    }
-  }
-
-  return Status::DataLoss();
-}
-
-Status DecodeChunk(ConstByteSpan message, Chunk& chunk) {
-  protobuf::Decoder decoder(message);
-  Status status;
-  uint32_t value;
-
-  chunk = {};
-
-  while ((status = decoder.Next()).ok()) {
-    ProtoChunk::Fields field =
-        static_cast<ProtoChunk::Fields>(decoder.FieldNumber());
-
-    switch (field) {
-      case ProtoChunk::Fields::TRANSFER_ID:
-        PW_TRY(decoder.ReadUint32(&chunk.transfer_id));
-        break;
-
-      case ProtoChunk::Fields::PENDING_BYTES:
-        PW_TRY(decoder.ReadUint32(&value));
-        chunk.pending_bytes = value;
-        break;
-
-      case ProtoChunk::Fields::MAX_CHUNK_SIZE_BYTES:
-        PW_TRY(decoder.ReadUint32(&value));
-        chunk.max_chunk_size_bytes = value;
-        break;
-
-      case ProtoChunk::Fields::MIN_DELAY_MICROSECONDS:
-        PW_TRY(decoder.ReadUint32(&value));
-        chunk.min_delay_microseconds = value;
-        break;
-
-      case ProtoChunk::Fields::OFFSET:
-        PW_TRY(decoder.ReadUint32(&chunk.offset));
-        break;
-
-      case ProtoChunk::Fields::DATA:
-        PW_TRY(decoder.ReadBytes(&chunk.data));
-        break;
-
-      case ProtoChunk::Fields::REMAINING_BYTES: {
-        uint64_t remaining;
-        PW_TRY(decoder.ReadUint64(&remaining));
-        chunk.remaining_bytes = remaining;
-        break;
-      }
-
-      case ProtoChunk::Fields::STATUS:
-        PW_TRY(decoder.ReadUint32(&value));
-        chunk.status = static_cast<Status::Code>(value);
-        break;
-
-      case ProtoChunk::Fields::WINDOW_END_OFFSET:
-        PW_TRY(decoder.ReadUint32(&chunk.window_end_offset));
-        break;
-
-      case ProtoChunk::Fields::TYPE: {
-        uint32_t type;
-        PW_TRY(decoder.ReadUint32(&type));
-        chunk.type = static_cast<Chunk::Type>(type);
-        break;
-      }
-    }
-  }
-
-  return status.IsOutOfRange() ? OkStatus() : status;
-}
-
-Result<ConstByteSpan> EncodeChunk(const Chunk& chunk, ByteSpan buffer) {
-  ProtoChunk::MemoryEncoder encoder(buffer);
-
-  encoder.WriteTransferId(chunk.transfer_id).IgnoreError();
-
-  if (chunk.window_end_offset != 0) {
-    encoder.WriteWindowEndOffset(chunk.window_end_offset).IgnoreError();
-  }
-
-  if (chunk.pending_bytes.has_value()) {
-    encoder.WritePendingBytes(chunk.pending_bytes.value()).IgnoreError();
-  }
-  if (chunk.max_chunk_size_bytes.has_value()) {
-    encoder.WriteMaxChunkSizeBytes(chunk.max_chunk_size_bytes.value())
-        .IgnoreError();
-  }
-  if (chunk.min_delay_microseconds.has_value()) {
-    encoder.WriteMinDelayMicroseconds(chunk.min_delay_microseconds.value())
-        .IgnoreError();
-  }
-  if (chunk.offset != 0) {
-    encoder.WriteOffset(chunk.offset).IgnoreError();
-  }
-  if (!chunk.data.empty()) {
-    encoder.WriteData(chunk.data).IgnoreError();
-  }
-  if (chunk.remaining_bytes.has_value()) {
-    encoder.WriteRemainingBytes(chunk.remaining_bytes.value()).IgnoreError();
-  }
-  if (chunk.status.has_value()) {
-    encoder.WriteStatus(chunk.status.value().code()).IgnoreError();
-  }
-
-  if (chunk.type.has_value()) {
-    encoder.WriteType(static_cast<ProtoChunk::Type>(chunk.type.value()))
-        .IgnoreError();
-  }
-
-  PW_TRY(encoder.status());
-  return ConstByteSpan(encoder);
-}
-
-}  // namespace pw::transfer::internal
diff --git a/pw_transfer/client.cc b/pw_transfer/client.cc
deleted file mode 100644
index 73822c2..0000000
--- a/pw_transfer/client.cc
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 PW_LOG_MODULE_NAME "TRN"
-
-#include "pw_transfer/client.h"
-
-#include "pw_log/log.h"
-
-namespace pw::transfer {
-
-Status Client::Read(uint32_t transfer_id,
-                    stream::Writer& output,
-                    CompletionFunc&& on_completion,
-                    chrono::SystemClock::duration timeout) {
-  if (on_completion == nullptr) {
-    return Status::InvalidArgument();
-  }
-
-  if (!has_read_stream_) {
-    rpc::RawClientReaderWriter read_stream = client_.Read(
-        [this](ConstByteSpan chunk) {
-          transfer_thread_.ProcessClientChunk(chunk);
-        },
-        [this](Status status) {
-          OnRpcError(status, internal::TransferType::kReceive);
-        });
-    transfer_thread_.SetClientReadStream(read_stream);
-    has_read_stream_ = true;
-  }
-
-  transfer_thread_.StartClientTransfer(internal::TransferType::kReceive,
-                                       transfer_id,
-                                       transfer_id,
-                                       &output,
-                                       max_parameters_,
-                                       std::move(on_completion),
-                                       timeout,
-                                       cfg::kDefaultMaxRetries);
-  return OkStatus();
-}
-
-Status Client::Write(uint32_t transfer_id,
-                     stream::Reader& input,
-                     CompletionFunc&& on_completion,
-                     chrono::SystemClock::duration timeout) {
-  if (on_completion == nullptr) {
-    return Status::InvalidArgument();
-  }
-
-  if (!has_write_stream_) {
-    rpc::RawClientReaderWriter write_stream = client_.Write(
-        [this](ConstByteSpan chunk) {
-          transfer_thread_.ProcessClientChunk(chunk);
-        },
-        [this](Status status) {
-          OnRpcError(status, internal::TransferType::kTransmit);
-        });
-    transfer_thread_.SetClientWriteStream(write_stream);
-    has_write_stream_ = true;
-  }
-
-  transfer_thread_.StartClientTransfer(internal::TransferType::kTransmit,
-                                       transfer_id,
-                                       transfer_id,
-                                       &input,
-                                       max_parameters_,
-                                       std::move(on_completion),
-                                       timeout,
-                                       cfg::kDefaultMaxRetries);
-
-  return OkStatus();
-}
-
-void Client::OnRpcError(Status status, internal::TransferType type) {
-  bool is_write_error = type == internal::TransferType::kTransmit;
-
-  PW_LOG_ERROR("Client %s stream terminated with status %d",
-               is_write_error ? "Write()" : "Read()",
-               status.code());
-
-  if (is_write_error) {
-    has_write_stream_ = false;
-  } else {
-    has_read_stream_ = false;
-  }
-}
-
-}  // namespace pw::transfer
diff --git a/pw_transfer/client_context.cc b/pw_transfer/client_context.cc
deleted file mode 100644
index 33c1aeb..0000000
--- a/pw_transfer/client_context.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_transfer/internal/client_context.h"
-
-#include "pw_assert/check.h"
-
-namespace pw::transfer::internal {
-
-Status ClientContext::FinalCleanup(Status status) {
-  PW_DASSERT(active());
-  if (on_completion_ != nullptr) {
-    on_completion_(status);
-  }
-  return OkStatus();
-}
-
-}  // namespace pw::transfer::internal
diff --git a/pw_transfer/client_test.cc b/pw_transfer/client_test.cc
deleted file mode 100644
index cbf82d7..0000000
--- a/pw_transfer/client_test.cc
+++ /dev/null
@@ -1,1467 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_transfer/client.h"
-
-#include <cstring>
-
-#include "gtest/gtest.h"
-#include "pw_assert/check.h"
-#include "pw_bytes/array.h"
-#include "pw_rpc/raw/client_testing.h"
-#include "pw_rpc/thread_testing.h"
-#include "pw_thread/sleep.h"
-#include "pw_thread/thread.h"
-#include "pw_thread_stl/options.h"
-#include "pw_transfer_private/chunk_testing.h"
-
-namespace pw::transfer::test {
-namespace {
-
-using internal::Chunk;
-using pw_rpc::raw::Transfer;
-
-using namespace std::chrono_literals;
-
-PW_MODIFY_DIAGNOSTICS_PUSH();
-PW_MODIFY_DIAGNOSTIC(ignored, "-Wmissing-field-initializers");
-
-thread::Options& TransferThreadOptions() {
-  static thread::stl::Options options;
-  return options;
-}
-
-class ReadTransfer : public ::testing::Test {
- protected:
-  ReadTransfer(size_t max_bytes_to_receive = 0)
-      : transfer_thread_(chunk_buffer_, encode_buffer_),
-        client_(context_.client(),
-                context_.channel().id(),
-                transfer_thread_,
-                max_bytes_to_receive),
-        system_thread_(TransferThreadOptions(), transfer_thread_) {}
-
-  ~ReadTransfer() {
-    transfer_thread_.Terminate();
-    system_thread_.join();
-  }
-
-  rpc::RawClientTestContext<> context_;
-
-  Thread<1, 1> transfer_thread_;
-  Client client_;
-
-  std::array<std::byte, 64> chunk_buffer_;
-  std::array<std::byte, 64> encode_buffer_;
-
-  thread::Thread system_thread_;
-};
-
-constexpr auto kData32 = bytes::Initialized<32>([](size_t i) { return i; });
-constexpr auto kData64 = bytes::Initialized<64>([](size_t i) { return i; });
-
-TEST_F(ReadTransfer, SingleChunk) {
-  stream::MemoryWriterBuffer<64> writer;
-  Status transfer_status = Status::Unknown();
-
-  ASSERT_EQ(OkStatus(),
-            client_.Read(3, writer, [&transfer_status](Status status) {
-              transfer_status = status;
-            }));
-
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // First transfer parameters chunk is sent.
-  rpc::PayloadsView payloads =
-      context_.output().payloads<Transfer::Read>(context_.channel().id());
-  ASSERT_EQ(payloads.size(), 1u);
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  Chunk c0 = DecodeChunk(payloads[0]);
-  EXPECT_EQ(c0.transfer_id, 3u);
-  EXPECT_EQ(c0.offset, 0u);
-  EXPECT_EQ(c0.pending_bytes.value(), 64u);
-
-  context_.server().SendServerStream<Transfer::Read>(EncodeChunk(
-      {.transfer_id = 3u, .offset = 0, .data = kData32, .remaining_bytes = 0}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(payloads.size(), 2u);
-
-  Chunk c1 = DecodeChunk(payloads[1]);
-  EXPECT_EQ(c1.transfer_id, 3u);
-  ASSERT_TRUE(c1.status.has_value());
-  EXPECT_EQ(c1.status.value(), OkStatus());
-
-  EXPECT_EQ(transfer_status, OkStatus());
-  EXPECT_EQ(std::memcmp(writer.data(), kData32.data(), writer.bytes_written()),
-            0);
-}
-
-TEST_F(ReadTransfer, MultiChunk) {
-  stream::MemoryWriterBuffer<64> writer;
-  Status transfer_status = Status::Unknown();
-
-  ASSERT_EQ(OkStatus(),
-            client_.Read(4, writer, [&transfer_status](Status status) {
-              transfer_status = status;
-            }));
-
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // First transfer parameters chunk is sent.
-  rpc::PayloadsView payloads =
-      context_.output().payloads<Transfer::Read>(context_.channel().id());
-  ASSERT_EQ(payloads.size(), 1u);
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  Chunk c0 = DecodeChunk(payloads[0]);
-  EXPECT_EQ(c0.transfer_id, 4u);
-  EXPECT_EQ(c0.offset, 0u);
-  EXPECT_EQ(c0.pending_bytes.value(), 64u);
-
-  constexpr ConstByteSpan data(kData32);
-  context_.server().SendServerStream<Transfer::Read>(
-      EncodeChunk({.transfer_id = 4u, .offset = 0, .data = data.first(16)}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(payloads.size(), 1u);
-
-  context_.server().SendServerStream<Transfer::Read>(
-      EncodeChunk({.transfer_id = 4u,
-                   .offset = 16,
-                   .data = data.subspan(16),
-                   .remaining_bytes = 0}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(payloads.size(), 2u);
-
-  Chunk c1 = DecodeChunk(payloads[1]);
-  EXPECT_EQ(c1.transfer_id, 4u);
-  ASSERT_TRUE(c1.status.has_value());
-  EXPECT_EQ(c1.status.value(), OkStatus());
-
-  EXPECT_EQ(transfer_status, OkStatus());
-  EXPECT_EQ(std::memcmp(writer.data(), kData32.data(), writer.bytes_written()),
-            0);
-}
-
-TEST_F(ReadTransfer, MultipleTransfers) {
-  stream::MemoryWriterBuffer<64> writer;
-  Status transfer_status = Status::Unknown();
-
-  ASSERT_EQ(OkStatus(),
-            client_.Read(3, writer, [&transfer_status](Status status) {
-              transfer_status = status;
-            }));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  context_.server().SendServerStream<Transfer::Read>(EncodeChunk(
-      {.transfer_id = 3u, .offset = 0, .data = kData32, .remaining_bytes = 0}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(transfer_status, OkStatus());
-  transfer_status = Status::Unknown();
-
-  ASSERT_EQ(OkStatus(),
-            client_.Read(3, writer, [&transfer_status](Status status) {
-              transfer_status = status;
-            }));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  context_.server().SendServerStream<Transfer::Read>(EncodeChunk(
-      {.transfer_id = 3u, .offset = 0, .data = kData32, .remaining_bytes = 0}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_EQ(transfer_status, OkStatus());
-}
-
-class ReadTransferMaxBytes32 : public ReadTransfer {
- protected:
-  ReadTransferMaxBytes32() : ReadTransfer(/*max_bytes_to_receive=*/32) {}
-};
-
-TEST_F(ReadTransferMaxBytes32, SetsPendingBytesFromConstructorArg) {
-  stream::MemoryWriterBuffer<64> writer;
-  EXPECT_EQ(OkStatus(), client_.Read(5, writer, [](Status) {}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // First transfer parameters chunk is sent.
-  rpc::PayloadsView payloads =
-      context_.output().payloads<Transfer::Read>(context_.channel().id());
-  ASSERT_EQ(payloads.size(), 1u);
-
-  Chunk c0 = DecodeChunk(payloads[0]);
-  EXPECT_EQ(c0.transfer_id, 5u);
-  EXPECT_EQ(c0.offset, 0u);
-  ASSERT_EQ(c0.pending_bytes.value(), 32u);
-}
-
-TEST_F(ReadTransferMaxBytes32, SetsPendingBytesFromWriterLimit) {
-  stream::MemoryWriterBuffer<16> small_writer;
-  EXPECT_EQ(OkStatus(), client_.Read(5, small_writer, [](Status) {}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // First transfer parameters chunk is sent.
-  rpc::PayloadsView payloads =
-      context_.output().payloads<Transfer::Read>(context_.channel().id());
-  ASSERT_EQ(payloads.size(), 1u);
-
-  Chunk c0 = DecodeChunk(payloads[0]);
-  EXPECT_EQ(c0.transfer_id, 5u);
-  EXPECT_EQ(c0.offset, 0u);
-  ASSERT_EQ(c0.pending_bytes.value(), 16u);
-}
-
-TEST_F(ReadTransferMaxBytes32, MultiParameters) {
-  stream::MemoryWriterBuffer<64> writer;
-  Status transfer_status = Status::Unknown();
-
-  ASSERT_EQ(OkStatus(),
-            client_.Read(6, writer, [&transfer_status](Status status) {
-              transfer_status = status;
-            }));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // First transfer parameters chunk is sent.
-  rpc::PayloadsView payloads =
-      context_.output().payloads<Transfer::Read>(context_.channel().id());
-  ASSERT_EQ(payloads.size(), 1u);
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  Chunk c0 = DecodeChunk(payloads[0]);
-  EXPECT_EQ(c0.transfer_id, 6u);
-  EXPECT_EQ(c0.offset, 0u);
-  ASSERT_EQ(c0.pending_bytes.value(), 32u);
-
-  constexpr ConstByteSpan data(kData64);
-  context_.server().SendServerStream<Transfer::Read>(
-      EncodeChunk({.transfer_id = 6u, .offset = 0, .data = data.first(32)}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(payloads.size(), 2u);
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  // Second parameters chunk.
-  Chunk c1 = DecodeChunk(payloads[1]);
-  EXPECT_EQ(c1.transfer_id, 6u);
-  EXPECT_EQ(c1.offset, 32u);
-  ASSERT_EQ(c1.pending_bytes.value(), 32u);
-
-  context_.server().SendServerStream<Transfer::Read>(
-      EncodeChunk({.transfer_id = 6u,
-                   .offset = 32,
-                   .data = data.subspan(32),
-                   .remaining_bytes = 0}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(payloads.size(), 3u);
-
-  Chunk c2 = DecodeChunk(payloads[2]);
-  EXPECT_EQ(c2.transfer_id, 6u);
-  ASSERT_TRUE(c2.status.has_value());
-  EXPECT_EQ(c2.status.value(), OkStatus());
-
-  EXPECT_EQ(transfer_status, OkStatus());
-  EXPECT_EQ(std::memcmp(writer.data(), data.data(), writer.bytes_written()), 0);
-}
-
-TEST_F(ReadTransfer, UnexpectedOffset) {
-  stream::MemoryWriterBuffer<64> writer;
-  Status transfer_status = Status::Unknown();
-
-  ASSERT_EQ(OkStatus(),
-            client_.Read(7, writer, [&transfer_status](Status status) {
-              transfer_status = status;
-            }));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // First transfer parameters chunk is sent.
-  rpc::PayloadsView payloads =
-      context_.output().payloads<Transfer::Read>(context_.channel().id());
-  ASSERT_EQ(payloads.size(), 1u);
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  Chunk c0 = DecodeChunk(payloads[0]);
-  EXPECT_EQ(c0.transfer_id, 7u);
-  EXPECT_EQ(c0.offset, 0u);
-  EXPECT_EQ(c0.pending_bytes.value(), 64u);
-
-  constexpr ConstByteSpan data(kData32);
-  context_.server().SendServerStream<Transfer::Read>(
-      EncodeChunk({.transfer_id = 7u, .offset = 0, .data = data.first(16)}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(payloads.size(), 1u);
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  // Send a chunk with an incorrect offset. The client should resend parameters.
-  context_.server().SendServerStream<Transfer::Read>(
-      EncodeChunk({.transfer_id = 7u,
-                   .offset = 8,  // wrong!
-                   .data = data.subspan(16),
-                   .remaining_bytes = 0}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(payloads.size(), 2u);
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  Chunk c1 = DecodeChunk(payloads[1]);
-  EXPECT_EQ(c1.transfer_id, 7u);
-  EXPECT_EQ(c1.offset, 16u);
-  EXPECT_EQ(c1.pending_bytes.value(), 48u);
-
-  // Send the correct chunk, completing the transfer.
-  context_.server().SendServerStream<Transfer::Read>(
-      EncodeChunk({.transfer_id = 7u,
-                   .offset = 16,
-                   .data = data.subspan(16),
-                   .remaining_bytes = 0}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(payloads.size(), 3u);
-
-  Chunk c2 = DecodeChunk(payloads[2]);
-  EXPECT_EQ(c2.transfer_id, 7u);
-  ASSERT_TRUE(c2.status.has_value());
-  EXPECT_EQ(c2.status.value(), OkStatus());
-
-  EXPECT_EQ(transfer_status, OkStatus());
-  EXPECT_EQ(std::memcmp(writer.data(), kData32.data(), writer.bytes_written()),
-            0);
-}
-
-TEST_F(ReadTransferMaxBytes32, TooMuchData) {
-  stream::MemoryWriterBuffer<32> writer;
-  Status transfer_status = Status::Unknown();
-
-  ASSERT_EQ(OkStatus(),
-            client_.Read(8, writer, [&transfer_status](Status status) {
-              transfer_status = status;
-            }));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // First transfer parameters chunk is sent.
-  rpc::PayloadsView payloads =
-      context_.output().payloads<Transfer::Read>(context_.channel().id());
-  ASSERT_EQ(payloads.size(), 1u);
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  Chunk c0 = DecodeChunk(payloads[0]);
-  EXPECT_EQ(c0.transfer_id, 8u);
-  EXPECT_EQ(c0.offset, 0u);
-  ASSERT_EQ(c0.pending_bytes.value(), 32u);
-
-  constexpr ConstByteSpan data(kData64);
-
-  // pending_bytes == 32
-  context_.server().SendServerStream<Transfer::Read>(
-      EncodeChunk({.transfer_id = 8u, .offset = 0, .data = data.first(16)}));
-
-  // pending_bytes == 16
-  context_.server().SendServerStream<Transfer::Read>(EncodeChunk(
-      {.transfer_id = 8u, .offset = 16, .data = data.subspan(16, 8)}));
-
-  // pending_bytes == 8, send 16 instead.
-  context_.server().SendServerStream<Transfer::Read>(EncodeChunk(
-      {.transfer_id = 8u, .offset = 24, .data = data.subspan(24, 16)}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(payloads.size(), 4u);
-
-  Chunk c1 = DecodeChunk(payloads[3]);
-  EXPECT_EQ(c1.transfer_id, 8u);
-  ASSERT_TRUE(c1.status.has_value());
-  EXPECT_EQ(c1.status.value(), Status::Internal());
-
-  EXPECT_EQ(transfer_status, Status::Internal());
-}
-
-TEST_F(ReadTransfer, ServerError) {
-  stream::MemoryWriterBuffer<64> writer;
-  Status transfer_status = Status::Unknown();
-
-  ASSERT_EQ(OkStatus(),
-            client_.Read(9, writer, [&transfer_status](Status status) {
-              transfer_status = status;
-            }));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // First transfer parameters chunk is sent.
-  rpc::PayloadsView payloads =
-      context_.output().payloads<Transfer::Read>(context_.channel().id());
-  ASSERT_EQ(payloads.size(), 1u);
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  Chunk c0 = DecodeChunk(payloads[0]);
-  EXPECT_EQ(c0.transfer_id, 9u);
-  EXPECT_EQ(c0.offset, 0u);
-  ASSERT_EQ(c0.pending_bytes.value(), 64u);
-
-  // Server sends an error. Client should not respond and terminate the
-  // transfer.
-  context_.server().SendServerStream<Transfer::Read>(
-      EncodeChunk({.transfer_id = 9u, .status = Status::NotFound()}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(payloads.size(), 1u);
-  EXPECT_EQ(transfer_status, Status::NotFound());
-}
-
-TEST_F(ReadTransfer, OnlySendsParametersOnceAfterDrop) {
-  stream::MemoryWriterBuffer<64> writer;
-  Status transfer_status = Status::Unknown();
-
-  ASSERT_EQ(OkStatus(),
-            client_.Read(10, writer, [&transfer_status](Status status) {
-              transfer_status = status;
-            }));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // First transfer parameters chunk is sent.
-  rpc::PayloadsView payloads =
-      context_.output().payloads<Transfer::Read>(context_.channel().id());
-  ASSERT_EQ(payloads.size(), 1u);
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  Chunk c0 = DecodeChunk(payloads[0]);
-  EXPECT_EQ(c0.transfer_id, 10u);
-  EXPECT_EQ(c0.offset, 0u);
-  ASSERT_EQ(c0.pending_bytes.value(), 64u);
-
-  constexpr ConstByteSpan data(kData64);
-
-  // Send the first 8 bytes of the transfer.
-  context_.server().SendServerStream<Transfer::Read>(
-      EncodeChunk({.transfer_id = 10u, .offset = 0, .data = data.first(8)}));
-
-  // Skip offset 8, send the rest starting from 16.
-  for (uint32_t offset = 16; offset < data.size(); offset += 8) {
-    context_.server().SendServerStream<Transfer::Read>(
-        EncodeChunk({.transfer_id = 10u,
-                     .offset = offset,
-                     .data = data.subspan(offset, 8)}));
-  }
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // Only one parameters update should be sent, with the offset of the initial
-  // dropped packet.
-  ASSERT_EQ(payloads.size(), 2u);
-
-  Chunk c1 = DecodeChunk(payloads[1]);
-  EXPECT_EQ(c1.transfer_id, 10u);
-  EXPECT_EQ(c1.offset, 8u);
-  ASSERT_EQ(c1.pending_bytes.value(), 56u);
-
-  // Send the remaining data to complete the transfer.
-  context_.server().SendServerStream<Transfer::Read>(
-      EncodeChunk({.transfer_id = 10u,
-                   .offset = 8,
-                   .data = data.subspan(8, 56),
-                   .remaining_bytes = 0}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(payloads.size(), 3u);
-
-  Chunk c2 = DecodeChunk(payloads[2]);
-  EXPECT_EQ(c2.transfer_id, 10u);
-  ASSERT_TRUE(c2.status.has_value());
-  EXPECT_EQ(c2.status.value(), OkStatus());
-
-  EXPECT_EQ(transfer_status, OkStatus());
-}
-
-TEST_F(ReadTransfer, ResendsParametersIfSentRepeatedChunkDuringRecovery) {
-  stream::MemoryWriterBuffer<64> writer;
-  Status transfer_status = Status::Unknown();
-
-  ASSERT_EQ(OkStatus(),
-            client_.Read(11, writer, [&transfer_status](Status status) {
-              transfer_status = status;
-            }));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // First transfer parameters chunk is sent.
-  rpc::PayloadsView payloads =
-      context_.output().payloads<Transfer::Read>(context_.channel().id());
-  ASSERT_EQ(payloads.size(), 1u);
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  Chunk c0 = DecodeChunk(payloads[0]);
-  EXPECT_EQ(c0.transfer_id, 11u);
-  EXPECT_EQ(c0.offset, 0u);
-  ASSERT_EQ(c0.pending_bytes.value(), 64u);
-
-  constexpr ConstByteSpan data(kData64);
-
-  // Send the first 8 bytes of the transfer.
-  context_.server().SendServerStream<Transfer::Read>(
-      EncodeChunk({.transfer_id = 11u, .offset = 0, .data = data.first(8)}));
-
-  // Skip offset 8, send the rest starting from 16.
-  for (uint32_t offset = 16; offset < data.size(); offset += 8) {
-    context_.server().SendServerStream<Transfer::Read>(
-        EncodeChunk({.transfer_id = 11u,
-                     .offset = offset,
-                     .data = data.subspan(offset, 8)}));
-  }
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // Only one parameters update should be sent, with the offset of the initial
-  // dropped packet.
-  ASSERT_EQ(payloads.size(), 2u);
-
-  const Chunk last_chunk = {
-      .transfer_id = 11u, .offset = 56, .data = data.subspan(56)};
-
-  // Re-send the final chunk of the block.
-  context_.server().SendServerStream<Transfer::Read>(EncodeChunk(last_chunk));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // The original drop parameters should be re-sent.
-  ASSERT_EQ(payloads.size(), 3u);
-  Chunk c2 = DecodeChunk(payloads[2]);
-  EXPECT_EQ(c2.transfer_id, 11u);
-  EXPECT_EQ(c2.offset, 8u);
-  ASSERT_EQ(c2.pending_bytes.value(), 56u);
-
-  // Do it again.
-  context_.server().SendServerStream<Transfer::Read>(EncodeChunk(last_chunk));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(payloads.size(), 4u);
-  Chunk c3 = DecodeChunk(payloads[3]);
-  EXPECT_EQ(c3.transfer_id, 11u);
-  EXPECT_EQ(c3.offset, 8u);
-  ASSERT_EQ(c3.pending_bytes.value(), 56u);
-
-  // Finish the transfer normally.
-  context_.server().SendServerStream<Transfer::Read>(
-      EncodeChunk({.transfer_id = 11u,
-                   .offset = 8,
-                   .data = data.subspan(8, 56),
-                   .remaining_bytes = 0}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(payloads.size(), 5u);
-
-  Chunk c4 = DecodeChunk(payloads[4]);
-  EXPECT_EQ(c4.transfer_id, 11u);
-  ASSERT_TRUE(c4.status.has_value());
-  EXPECT_EQ(c4.status.value(), OkStatus());
-
-  EXPECT_EQ(transfer_status, OkStatus());
-}
-
-constexpr chrono::SystemClock::duration kTestTimeout =
-    std::chrono::milliseconds(50);
-constexpr uint8_t kTestRetries = 3;
-
-TEST_F(ReadTransfer, Timeout_ResendsCurrentParameters) {
-  stream::MemoryWriterBuffer<64> writer;
-  Status transfer_status = Status::Unknown();
-
-  ASSERT_EQ(OkStatus(),
-            client_.Read(
-                12,
-                writer,
-                [&transfer_status](Status status) { transfer_status = status; },
-                kTestTimeout));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // First transfer parameters chunk is sent.
-  rpc::PayloadsView payloads =
-      context_.output().payloads<Transfer::Read>(context_.channel().id());
-  ASSERT_EQ(payloads.size(), 1u);
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  Chunk c0 = DecodeChunk(payloads.back());
-  EXPECT_EQ(c0.transfer_id, 12u);
-  EXPECT_EQ(c0.offset, 0u);
-  EXPECT_EQ(c0.pending_bytes.value(), 64u);
-
-  // Wait for the timeout to expire without doing anything. The client should
-  // resend its parameters chunk.
-  transfer_thread_.SimulateClientTimeout(12);
-  ASSERT_EQ(payloads.size(), 2u);
-
-  Chunk c = DecodeChunk(payloads.back());
-  EXPECT_EQ(c.transfer_id, 12u);
-  EXPECT_EQ(c.offset, 0u);
-  EXPECT_EQ(c.pending_bytes.value(), 64u);
-
-  // Transfer has not yet completed.
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  // Finish the transfer following the timeout.
-  context_.server().SendServerStream<Transfer::Read>(
-      EncodeChunk({.transfer_id = 12u,
-                   .offset = 0,
-                   .data = kData32,
-                   .remaining_bytes = 0}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(payloads.size(), 3u);
-
-  Chunk c4 = DecodeChunk(payloads.back());
-  EXPECT_EQ(c4.transfer_id, 12u);
-  ASSERT_TRUE(c4.status.has_value());
-  EXPECT_EQ(c4.status.value(), OkStatus());
-
-  EXPECT_EQ(transfer_status, OkStatus());
-}
-
-TEST_F(ReadTransfer, Timeout_ResendsUpdatedParameters) {
-  stream::MemoryWriterBuffer<64> writer;
-  Status transfer_status = Status::Unknown();
-
-  ASSERT_EQ(OkStatus(),
-            client_.Read(
-                13,
-                writer,
-                [&transfer_status](Status status) { transfer_status = status; },
-                kTestTimeout));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // First transfer parameters chunk is sent.
-  rpc::PayloadsView payloads =
-      context_.output().payloads<Transfer::Read>(context_.channel().id());
-  ASSERT_EQ(payloads.size(), 1u);
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  Chunk c0 = DecodeChunk(payloads.back());
-  EXPECT_EQ(c0.transfer_id, 13u);
-  EXPECT_EQ(c0.offset, 0u);
-  EXPECT_EQ(c0.pending_bytes.value(), 64u);
-
-  constexpr ConstByteSpan data(kData32);
-
-  // Send some data, but not everything.
-  context_.server().SendServerStream<Transfer::Read>(
-      EncodeChunk({.transfer_id = 13u, .offset = 0, .data = data.first(16)}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(payloads.size(), 1u);
-
-  // Wait for the timeout to expire without sending more data. The client should
-  // send an updated parameters chunk, accounting for the data already received.
-  transfer_thread_.SimulateClientTimeout(13);
-  ASSERT_EQ(payloads.size(), 2u);
-
-  Chunk c = DecodeChunk(payloads.back());
-  EXPECT_EQ(c.transfer_id, 13u);
-  EXPECT_EQ(c.offset, 16u);
-  EXPECT_EQ(c.pending_bytes.value(), 48u);
-
-  // Transfer has not yet completed.
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  // Send the rest of the data, finishing the transfer.
-  context_.server().SendServerStream<Transfer::Read>(
-      EncodeChunk({.transfer_id = 13u,
-                   .offset = 16,
-                   .data = data.subspan(16),
-                   .remaining_bytes = 0}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(payloads.size(), 3u);
-
-  Chunk c4 = DecodeChunk(payloads.back());
-  EXPECT_EQ(c4.transfer_id, 13u);
-  ASSERT_TRUE(c4.status.has_value());
-  EXPECT_EQ(c4.status.value(), OkStatus());
-
-  EXPECT_EQ(transfer_status, OkStatus());
-}
-
-TEST_F(ReadTransfer, Timeout_EndsTransferAfterMaxRetries) {
-  stream::MemoryWriterBuffer<64> writer;
-  Status transfer_status = Status::Unknown();
-
-  ASSERT_EQ(OkStatus(),
-            client_.Read(
-                14,
-                writer,
-                [&transfer_status](Status status) { transfer_status = status; },
-                kTestTimeout));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // First transfer parameters chunk is sent.
-  rpc::PayloadsView payloads =
-      context_.output().payloads<Transfer::Read>(context_.channel().id());
-  ASSERT_EQ(payloads.size(), 1u);
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  Chunk c0 = DecodeChunk(payloads.back());
-  EXPECT_EQ(c0.transfer_id, 14u);
-  EXPECT_EQ(c0.offset, 0u);
-  EXPECT_EQ(c0.pending_bytes.value(), 64u);
-
-  for (unsigned retry = 1; retry <= kTestRetries; ++retry) {
-    // Wait for the timeout to expire without doing anything. The client should
-    // resend its parameters chunk.
-    transfer_thread_.SimulateClientTimeout(14);
-    ASSERT_EQ(payloads.size(), retry + 1);
-
-    Chunk c = DecodeChunk(payloads.back());
-    EXPECT_EQ(c.transfer_id, 14u);
-    EXPECT_EQ(c.offset, 0u);
-    EXPECT_EQ(c.pending_bytes.value(), 64u);
-
-    // Transfer has not yet completed.
-    EXPECT_EQ(transfer_status, Status::Unknown());
-  }
-
-  // Sleep one more time after the final retry. The client should cancel the
-  // transfer at this point and send a DEADLINE_EXCEEDED chunk.
-  transfer_thread_.SimulateClientTimeout(14);
-  ASSERT_EQ(payloads.size(), 5u);
-
-  Chunk c4 = DecodeChunk(payloads.back());
-  EXPECT_EQ(c4.transfer_id, 14u);
-  ASSERT_TRUE(c4.status.has_value());
-  EXPECT_EQ(c4.status.value(), Status::DeadlineExceeded());
-
-  EXPECT_EQ(transfer_status, Status::DeadlineExceeded());
-
-  // After finishing the transfer, nothing else should be sent. Verify this by
-  // waiting for a bit.
-  this_thread::sleep_for(kTestTimeout * 4);
-  ASSERT_EQ(payloads.size(), 5u);
-}
-
-TEST_F(ReadTransfer, Timeout_ReceivingDataResetsRetryCount) {
-  stream::MemoryWriterBuffer<64> writer;
-  Status transfer_status = Status::Unknown();
-
-  constexpr ConstByteSpan data(kData32);
-
-  ASSERT_EQ(OkStatus(),
-            client_.Read(
-                14,
-                writer,
-                [&transfer_status](Status status) { transfer_status = status; },
-                kTestTimeout));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // First transfer parameters chunk is sent.
-  rpc::PayloadsView payloads =
-      context_.output().payloads<Transfer::Read>(context_.channel().id());
-  ASSERT_EQ(payloads.size(), 1u);
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  Chunk c0 = DecodeChunk(payloads.back());
-  EXPECT_EQ(c0.transfer_id, 14u);
-  EXPECT_EQ(c0.offset, 0u);
-  EXPECT_EQ(c0.window_end_offset, 64u);
-
-  // Simulate one less timeout than the maximum amount of retries.
-  for (unsigned retry = 1; retry <= kTestRetries - 1; ++retry) {
-    transfer_thread_.SimulateClientTimeout(14);
-    ASSERT_EQ(payloads.size(), retry + 1);
-
-    Chunk c = DecodeChunk(payloads.back());
-    EXPECT_EQ(c.transfer_id, 14u);
-    EXPECT_EQ(c.offset, 0u);
-    EXPECT_EQ(c.window_end_offset, 64u);
-
-    // Transfer has not yet completed.
-    EXPECT_EQ(transfer_status, Status::Unknown());
-  }
-
-  // Send some data.
-  context_.server().SendServerStream<Transfer::Read>(
-      EncodeChunk({.transfer_id = 14u, .offset = 0, .data = data.first(16)}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-  ASSERT_EQ(payloads.size(), 3u);
-
-  // Time out a couple more times. The context's retry count should have been
-  // reset, so it should go through the standard retry flow instead of
-  // terminating the transfer.
-  transfer_thread_.SimulateClientTimeout(14);
-  ASSERT_EQ(payloads.size(), 4u);
-
-  Chunk c = DecodeChunk(payloads.back());
-  EXPECT_FALSE(c.status.has_value());
-  EXPECT_EQ(c.transfer_id, 14u);
-  EXPECT_EQ(c.offset, 16u);
-  EXPECT_EQ(c.window_end_offset, 64u);
-
-  transfer_thread_.SimulateClientTimeout(14);
-  ASSERT_EQ(payloads.size(), 5u);
-
-  c = DecodeChunk(payloads.back());
-  EXPECT_FALSE(c.status.has_value());
-  EXPECT_EQ(c.transfer_id, 14u);
-  EXPECT_EQ(c.offset, 16u);
-  EXPECT_EQ(c.window_end_offset, 64u);
-}
-
-TEST_F(ReadTransfer, InitialPacketFails_OnCompletedCalledWithDataLoss) {
-  stream::MemoryWriterBuffer<64> writer;
-  Status transfer_status = Status::Unknown();
-
-  context_.output().set_send_status(Status::Unauthenticated());
-
-  ASSERT_EQ(OkStatus(),
-            client_.Read(
-                14,
-                writer,
-                [&transfer_status](Status status) {
-                  ASSERT_EQ(transfer_status,
-                            Status::Unknown());  // Must only call once
-                  transfer_status = status;
-                },
-                kTestTimeout));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_EQ(transfer_status, Status::Internal());
-}
-
-class WriteTransfer : public ::testing::Test {
- protected:
-  WriteTransfer()
-      : transfer_thread_(chunk_buffer_, encode_buffer_),
-        client_(context_.client(), context_.channel().id(), transfer_thread_),
-        system_thread_(TransferThreadOptions(), transfer_thread_) {}
-
-  ~WriteTransfer() {
-    transfer_thread_.Terminate();
-    system_thread_.join();
-  }
-
-  rpc::RawClientTestContext<> context_;
-
-  Thread<1, 1> transfer_thread_;
-  Client client_;
-
-  std::array<std::byte, 64> chunk_buffer_;
-  std::array<std::byte, 64> encode_buffer_;
-
-  thread::Thread system_thread_;
-};
-
-TEST_F(WriteTransfer, SingleChunk) {
-  stream::MemoryReader reader(kData32);
-  Status transfer_status = Status::Unknown();
-
-  ASSERT_EQ(OkStatus(),
-            client_.Write(3, reader, [&transfer_status](Status status) {
-              transfer_status = status;
-            }));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // The client begins by just sending the transfer ID.
-  rpc::PayloadsView payloads =
-      context_.output().payloads<Transfer::Write>(context_.channel().id());
-  ASSERT_EQ(payloads.size(), 1u);
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  Chunk c0 = DecodeChunk(payloads[0]);
-  EXPECT_EQ(c0.transfer_id, 3u);
-
-  // Send transfer parameters. Client should send a data chunk and the final
-  // chunk.
-  rpc::test::WaitForPackets(context_.output(), 2, [this] {
-    context_.server().SendServerStream<Transfer::Write>(
-        EncodeChunk({.transfer_id = 3,
-                     .pending_bytes = 64,
-                     .max_chunk_size_bytes = 32,
-                     .offset = 0}));
-  });
-
-  ASSERT_EQ(payloads.size(), 3u);
-
-  Chunk c1 = DecodeChunk(payloads[1]);
-  EXPECT_EQ(c1.transfer_id, 3u);
-  EXPECT_EQ(c1.offset, 0u);
-  EXPECT_EQ(std::memcmp(c1.data.data(), kData32.data(), c1.data.size()), 0);
-
-  Chunk c2 = DecodeChunk(payloads[2]);
-  EXPECT_EQ(c2.transfer_id, 3u);
-  ASSERT_TRUE(c2.remaining_bytes.has_value());
-  EXPECT_EQ(c2.remaining_bytes.value(), 0u);
-
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  // Send the final status chunk to complete the transfer.
-  context_.server().SendServerStream<Transfer::Write>(
-      EncodeChunk({.transfer_id = 3, .status = OkStatus()}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_EQ(payloads.size(), 3u);
-  EXPECT_EQ(transfer_status, OkStatus());
-}
-
-TEST_F(WriteTransfer, MultiChunk) {
-  stream::MemoryReader reader(kData32);
-  Status transfer_status = Status::Unknown();
-
-  ASSERT_EQ(OkStatus(),
-            client_.Write(4, reader, [&transfer_status](Status status) {
-              transfer_status = status;
-            }));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // The client begins by just sending the transfer ID.
-  rpc::PayloadsView payloads =
-      context_.output().payloads<Transfer::Write>(context_.channel().id());
-  ASSERT_EQ(payloads.size(), 1u);
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  Chunk c0 = DecodeChunk(payloads[0]);
-  EXPECT_EQ(c0.transfer_id, 4u);
-
-  // Send transfer parameters with a chunk size smaller than the data.
-
-  // Client should send two data chunks and the final chunk.
-  rpc::test::WaitForPackets(context_.output(), 3, [this] {
-    context_.server().SendServerStream<Transfer::Write>(
-        EncodeChunk({.transfer_id = 4,
-                     .pending_bytes = 64,
-                     .max_chunk_size_bytes = 16,
-                     .offset = 0}));
-  });
-
-  ASSERT_EQ(payloads.size(), 4u);
-
-  Chunk c1 = DecodeChunk(payloads[1]);
-  EXPECT_EQ(c1.transfer_id, 4u);
-  EXPECT_EQ(c1.offset, 0u);
-  EXPECT_EQ(std::memcmp(c1.data.data(), kData32.data(), c1.data.size()), 0);
-
-  Chunk c2 = DecodeChunk(payloads[2]);
-  EXPECT_EQ(c2.transfer_id, 4u);
-  EXPECT_EQ(c2.offset, 16u);
-  EXPECT_EQ(
-      std::memcmp(c2.data.data(), kData32.data() + c2.offset, c2.data.size()),
-      0);
-
-  Chunk c3 = DecodeChunk(payloads[3]);
-  EXPECT_EQ(c3.transfer_id, 4u);
-  ASSERT_TRUE(c3.remaining_bytes.has_value());
-  EXPECT_EQ(c3.remaining_bytes.value(), 0u);
-
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  // Send the final status chunk to complete the transfer.
-  context_.server().SendServerStream<Transfer::Write>(
-      EncodeChunk({.transfer_id = 4, .status = OkStatus()}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_EQ(payloads.size(), 4u);
-  EXPECT_EQ(transfer_status, OkStatus());
-}
-
-TEST_F(WriteTransfer, OutOfOrder_SeekSupported) {
-  stream::MemoryReader reader(kData32);
-  Status transfer_status = Status::Unknown();
-
-  ASSERT_EQ(OkStatus(),
-            client_.Write(5, reader, [&transfer_status](Status status) {
-              transfer_status = status;
-            }));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // The client begins by just sending the transfer ID.
-  rpc::PayloadsView payloads =
-      context_.output().payloads<Transfer::Write>(context_.channel().id());
-  ASSERT_EQ(payloads.size(), 1u);
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  Chunk c0 = DecodeChunk(payloads[0]);
-  EXPECT_EQ(c0.transfer_id, 5u);
-
-  // Send transfer parameters with a nonzero offset, requesting a seek.
-  // Client should send a data chunk and the final chunk.
-  rpc::test::WaitForPackets(context_.output(), 2, [this] {
-    context_.server().SendServerStream<Transfer::Write>(
-        EncodeChunk({.transfer_id = 5,
-                     .pending_bytes = 64,
-                     .max_chunk_size_bytes = 32,
-                     .offset = 16}));
-  });
-
-  ASSERT_EQ(payloads.size(), 3u);
-
-  Chunk c1 = DecodeChunk(payloads[1]);
-  EXPECT_EQ(c1.transfer_id, 5u);
-  EXPECT_EQ(c1.offset, 16u);
-  EXPECT_EQ(
-      std::memcmp(c1.data.data(), kData32.data() + c1.offset, c1.data.size()),
-      0);
-
-  Chunk c2 = DecodeChunk(payloads[2]);
-  EXPECT_EQ(c2.transfer_id, 5u);
-  ASSERT_TRUE(c2.remaining_bytes.has_value());
-  EXPECT_EQ(c2.remaining_bytes.value(), 0u);
-
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  // Send the final status chunk to complete the transfer.
-  context_.server().SendServerStream<Transfer::Write>(
-      EncodeChunk({.transfer_id = 5, .status = OkStatus()}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_EQ(payloads.size(), 3u);
-  EXPECT_EQ(transfer_status, OkStatus());
-}
-
-class FakeNonSeekableReader final : public stream::NonSeekableReader {
- public:
-  FakeNonSeekableReader(ConstByteSpan data) : data_(data), position_(0) {}
-
- private:
-  StatusWithSize DoRead(ByteSpan out) final {
-    if (position_ == data_.size()) {
-      return StatusWithSize::OutOfRange();
-    }
-
-    size_t to_copy = std::min(out.size(), data_.size() - position_);
-    std::memcpy(out.data(), data_.data() + position_, to_copy);
-    position_ += to_copy;
-
-    return StatusWithSize(to_copy);
-  }
-
-  ConstByteSpan data_;
-  size_t position_;
-};
-
-TEST_F(WriteTransfer, OutOfOrder_SeekNotSupported) {
-  FakeNonSeekableReader reader(kData32);
-  Status transfer_status = Status::Unknown();
-
-  ASSERT_EQ(OkStatus(),
-            client_.Write(6, reader, [&transfer_status](Status status) {
-              transfer_status = status;
-            }));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // The client begins by just sending the transfer ID.
-  rpc::PayloadsView payloads =
-      context_.output().payloads<Transfer::Write>(context_.channel().id());
-  ASSERT_EQ(payloads.size(), 1u);
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  Chunk c0 = DecodeChunk(payloads[0]);
-  EXPECT_EQ(c0.transfer_id, 6u);
-
-  // Send transfer parameters with a nonzero offset, requesting a seek.
-  context_.server().SendServerStream<Transfer::Write>(
-      EncodeChunk({.transfer_id = 6,
-                   .pending_bytes = 64,
-                   .max_chunk_size_bytes = 32,
-                   .offset = 16}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // Client should send a status chunk and end the transfer.
-  ASSERT_EQ(payloads.size(), 2u);
-
-  Chunk c1 = DecodeChunk(payloads[1]);
-  EXPECT_EQ(c1.transfer_id, 6u);
-  ASSERT_TRUE(c1.status.has_value());
-  EXPECT_EQ(c1.status.value(), Status::Unimplemented());
-
-  EXPECT_EQ(transfer_status, Status::Unimplemented());
-}
-
-TEST_F(WriteTransfer, ServerError) {
-  stream::MemoryReader reader(kData32);
-  Status transfer_status = Status::Unknown();
-
-  ASSERT_EQ(OkStatus(),
-            client_.Write(7, reader, [&transfer_status](Status status) {
-              transfer_status = status;
-            }));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // The client begins by just sending the transfer ID.
-  rpc::PayloadsView payloads =
-      context_.output().payloads<Transfer::Write>(context_.channel().id());
-  ASSERT_EQ(payloads.size(), 1u);
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  Chunk c0 = DecodeChunk(payloads[0]);
-  EXPECT_EQ(c0.transfer_id, 7u);
-
-  // Send an error from the server.
-  context_.server().SendServerStream<Transfer::Write>(
-      EncodeChunk({.transfer_id = 7, .status = Status::NotFound()}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // Client should not respond and terminate the transfer.
-  EXPECT_EQ(payloads.size(), 1u);
-  EXPECT_EQ(transfer_status, Status::NotFound());
-}
-
-TEST_F(WriteTransfer, MalformedParametersChunk) {
-  stream::MemoryReader reader(kData32);
-  Status transfer_status = Status::Unknown();
-
-  ASSERT_EQ(OkStatus(),
-            client_.Write(8, reader, [&transfer_status](Status status) {
-              transfer_status = status;
-            }));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // The client begins by just sending the transfer ID.
-  rpc::PayloadsView payloads =
-      context_.output().payloads<Transfer::Write>(context_.channel().id());
-  ASSERT_EQ(payloads.size(), 1u);
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  Chunk c0 = DecodeChunk(payloads[0]);
-  EXPECT_EQ(c0.transfer_id, 8u);
-
-  // Send an invalid transfer parameters chunk without pending_bytes.
-  context_.server().SendServerStream<Transfer::Write>(
-      EncodeChunk({.transfer_id = 8, .max_chunk_size_bytes = 32}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // Client should send a status chunk and end the transfer.
-  ASSERT_EQ(payloads.size(), 2u);
-
-  Chunk c1 = DecodeChunk(payloads[1]);
-  EXPECT_EQ(c1.transfer_id, 8u);
-  ASSERT_TRUE(c1.status.has_value());
-  EXPECT_EQ(c1.status.value(), Status::InvalidArgument());
-
-  EXPECT_EQ(transfer_status, Status::InvalidArgument());
-}
-
-TEST_F(WriteTransfer, AbortIfZeroBytesAreRequested) {
-  stream::MemoryReader reader(kData32);
-  Status transfer_status = Status::Unknown();
-
-  ASSERT_EQ(OkStatus(),
-            client_.Write(9, reader, [&transfer_status](Status status) {
-              transfer_status = status;
-            }));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // The client begins by just sending the transfer ID.
-  rpc::PayloadsView payloads =
-      context_.output().payloads<Transfer::Write>(context_.channel().id());
-  ASSERT_EQ(payloads.size(), 1u);
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  Chunk c0 = DecodeChunk(payloads[0]);
-  EXPECT_EQ(c0.transfer_id, 9u);
-
-  // Send an invalid transfer parameters chunk with 0 pending_bytes.
-  context_.server().SendServerStream<Transfer::Write>(EncodeChunk(
-      {.transfer_id = 9, .pending_bytes = 0, .max_chunk_size_bytes = 32}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // Client should send a status chunk and end the transfer.
-  ASSERT_EQ(payloads.size(), 2u);
-
-  Chunk c1 = DecodeChunk(payloads[1]);
-  EXPECT_EQ(c1.transfer_id, 9u);
-  ASSERT_TRUE(c1.status.has_value());
-  EXPECT_EQ(c1.status.value(), Status::ResourceExhausted());
-
-  EXPECT_EQ(transfer_status, Status::ResourceExhausted());
-}
-
-TEST_F(WriteTransfer, Timeout_RetriesWithInitialChunk) {
-  stream::MemoryReader reader(kData32);
-  Status transfer_status = Status::Unknown();
-
-  ASSERT_EQ(OkStatus(),
-            client_.Write(
-                10,
-                reader,
-                [&transfer_status](Status status) { transfer_status = status; },
-                kTestTimeout));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // The client begins by just sending the transfer ID.
-  rpc::PayloadsView payloads =
-      context_.output().payloads<Transfer::Write>(context_.channel().id());
-  ASSERT_EQ(payloads.size(), 1u);
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  Chunk c0 = DecodeChunk(payloads.back());
-  EXPECT_EQ(c0.transfer_id, 10u);
-
-  // Wait for the timeout to expire without doing anything. The client should
-  // resend the initial transmit chunk.
-  transfer_thread_.SimulateClientTimeout(10);
-  ASSERT_EQ(payloads.size(), 2u);
-
-  Chunk c = DecodeChunk(payloads.back());
-  EXPECT_EQ(c.transfer_id, 10u);
-
-  // Transfer has not yet completed.
-  EXPECT_EQ(transfer_status, Status::Unknown());
-}
-
-TEST_F(WriteTransfer, Timeout_RetriesWithMostRecentChunk) {
-  stream::MemoryReader reader(kData32);
-  Status transfer_status = Status::Unknown();
-
-  ASSERT_EQ(OkStatus(),
-            client_.Write(
-                11,
-                reader,
-                [&transfer_status](Status status) { transfer_status = status; },
-                kTestTimeout));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // The client begins by just sending the transfer ID.
-  rpc::PayloadsView payloads =
-      context_.output().payloads<Transfer::Write>(context_.channel().id());
-  ASSERT_EQ(payloads.size(), 1u);
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  Chunk c0 = DecodeChunk(payloads.back());
-  EXPECT_EQ(c0.transfer_id, 11u);
-
-  // Send the first parameters chunk.
-  rpc::test::WaitForPackets(context_.output(), 2, [this] {
-    context_.server().SendServerStream<Transfer::Write>(
-        EncodeChunk({.transfer_id = 11,
-                     .pending_bytes = 16,
-                     .max_chunk_size_bytes = 8,
-                     .offset = 0}));
-  });
-  ASSERT_EQ(payloads.size(), 3u);
-
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  Chunk c1 = DecodeChunk(payloads[1]);
-  EXPECT_EQ(c1.transfer_id, 11u);
-  EXPECT_EQ(c1.offset, 0u);
-  EXPECT_EQ(c1.data.size(), 8u);
-  EXPECT_EQ(std::memcmp(c1.data.data(), kData32.data(), c1.data.size()), 0);
-
-  Chunk c2 = DecodeChunk(payloads[2]);
-  EXPECT_EQ(c2.transfer_id, 11u);
-  EXPECT_EQ(c2.offset, 8u);
-  EXPECT_EQ(c2.data.size(), 8u);
-  EXPECT_EQ(
-      std::memcmp(c2.data.data(), kData32.data() + c2.offset, c1.data.size()),
-      0);
-
-  // Wait for the timeout to expire without doing anything. The client should
-  // resend the most recently sent chunk.
-  transfer_thread_.SimulateClientTimeout(11);
-  ASSERT_EQ(payloads.size(), 4u);
-
-  Chunk c3 = DecodeChunk(payloads[3]);
-  EXPECT_EQ(c3.transfer_id, c2.transfer_id);
-  EXPECT_EQ(c3.offset, c2.offset);
-  EXPECT_EQ(c3.data.size(), c2.data.size());
-  EXPECT_EQ(std::memcmp(c3.data.data(), c2.data.data(), c3.data.size()), 0);
-
-  // Transfer has not yet completed.
-  EXPECT_EQ(transfer_status, Status::Unknown());
-}
-
-TEST_F(WriteTransfer, Timeout_RetriesWithSingleChunkTransfer) {
-  stream::MemoryReader reader(kData32);
-  Status transfer_status = Status::Unknown();
-
-  ASSERT_EQ(OkStatus(),
-            client_.Write(
-                12,
-                reader,
-                [&transfer_status](Status status) { transfer_status = status; },
-                kTestTimeout));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // The client begins by just sending the transfer ID.
-  rpc::PayloadsView payloads =
-      context_.output().payloads<Transfer::Write>(context_.channel().id());
-  ASSERT_EQ(payloads.size(), 1u);
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  Chunk c0 = DecodeChunk(payloads.back());
-  EXPECT_EQ(c0.transfer_id, 12u);
-
-  // Send the first parameters chunk, requesting all the data. The client should
-  // respond with one data chunk and a remaining_bytes = 0 chunk.
-  rpc::test::WaitForPackets(context_.output(), 2, [this] {
-    context_.server().SendServerStream<Transfer::Write>(
-        EncodeChunk({.transfer_id = 12,
-                     .pending_bytes = 64,
-                     .max_chunk_size_bytes = 64,
-                     .offset = 0}));
-  });
-  ASSERT_EQ(payloads.size(), 3u);
-
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  Chunk c1 = DecodeChunk(payloads[1]);
-  EXPECT_EQ(c1.transfer_id, 12u);
-  EXPECT_EQ(c1.offset, 0u);
-  EXPECT_EQ(c1.data.size(), 32u);
-  EXPECT_EQ(std::memcmp(c1.data.data(), kData32.data(), c1.data.size()), 0);
-
-  Chunk c2 = DecodeChunk(payloads[2]);
-  EXPECT_EQ(c2.transfer_id, 12u);
-  ASSERT_TRUE(c2.remaining_bytes.has_value());
-  EXPECT_EQ(c2.remaining_bytes.value(), 0u);
-
-  // Wait for the timeout to expire without doing anything. The client should
-  // resend the data chunk.
-  transfer_thread_.SimulateClientTimeout(12);
-  ASSERT_EQ(payloads.size(), 4u);
-
-  Chunk c3 = DecodeChunk(payloads[3]);
-  EXPECT_EQ(c3.transfer_id, c1.transfer_id);
-  EXPECT_EQ(c3.offset, c1.offset);
-  EXPECT_EQ(c3.data.size(), c1.data.size());
-  EXPECT_EQ(std::memcmp(c3.data.data(), c1.data.data(), c3.data.size()), 0);
-
-  // The remaining_bytes = 0 chunk should be resent on the next parameters.
-  context_.server().SendServerStream<Transfer::Write>(
-      EncodeChunk({.transfer_id = 12,
-                   .pending_bytes = 64,
-                   .max_chunk_size_bytes = 64,
-                   .offset = 32}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(payloads.size(), 5u);
-
-  Chunk c4 = DecodeChunk(payloads[4]);
-  EXPECT_EQ(c4.transfer_id, 12u);
-  ASSERT_TRUE(c4.remaining_bytes.has_value());
-  EXPECT_EQ(c4.remaining_bytes.value(), 0u);
-
-  context_.server().SendServerStream<Transfer::Write>(
-      EncodeChunk({.transfer_id = 12, .status = OkStatus()}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_EQ(transfer_status, OkStatus());
-}
-
-TEST_F(WriteTransfer, Timeout_EndsTransferAfterMaxRetries) {
-  stream::MemoryReader reader(kData32);
-  Status transfer_status = Status::Unknown();
-
-  ASSERT_EQ(OkStatus(),
-            client_.Write(
-                13,
-                reader,
-                [&transfer_status](Status status) { transfer_status = status; },
-                kTestTimeout));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // The client begins by just sending the transfer ID.
-  rpc::PayloadsView payloads =
-      context_.output().payloads<Transfer::Write>(context_.channel().id());
-  ASSERT_EQ(payloads.size(), 1u);
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  Chunk c0 = DecodeChunk(payloads.back());
-  EXPECT_EQ(c0.transfer_id, 13u);
-
-  for (unsigned retry = 1; retry <= kTestRetries; ++retry) {
-    // Wait for the timeout to expire without doing anything. The client should
-    // resend the initial transmit chunk.
-    transfer_thread_.SimulateClientTimeout(13);
-    ASSERT_EQ(payloads.size(), retry + 1);
-
-    Chunk c = DecodeChunk(payloads.back());
-    EXPECT_EQ(c.transfer_id, 13u);
-
-    // Transfer has not yet completed.
-    EXPECT_EQ(transfer_status, Status::Unknown());
-  }
-
-  // Sleep one more time after the final retry. The client should cancel the
-  // transfer at this point and send a DEADLINE_EXCEEDED chunk.
-  transfer_thread_.SimulateClientTimeout(13);
-  ASSERT_EQ(payloads.size(), 5u);
-
-  Chunk c4 = DecodeChunk(payloads.back());
-  EXPECT_EQ(c4.transfer_id, 13u);
-  ASSERT_TRUE(c4.status.has_value());
-  EXPECT_EQ(c4.status.value(), Status::DeadlineExceeded());
-
-  EXPECT_EQ(transfer_status, Status::DeadlineExceeded());
-
-  // After finishing the transfer, nothing else should be sent. Verify this by
-  // waiting for a bit.
-  this_thread::sleep_for(kTestTimeout * 4);
-  ASSERT_EQ(payloads.size(), 5u);
-}
-
-TEST_F(WriteTransfer, Timeout_NonSeekableReaderEndsTransfer) {
-  FakeNonSeekableReader reader(kData32);
-  Status transfer_status = Status::Unknown();
-
-  ASSERT_EQ(OkStatus(),
-            client_.Write(
-                14,
-                reader,
-                [&transfer_status](Status status) { transfer_status = status; },
-                kTestTimeout));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // The client begins by just sending the transfer ID.
-  rpc::PayloadsView payloads =
-      context_.output().payloads<Transfer::Write>(context_.channel().id());
-  ASSERT_EQ(payloads.size(), 1u);
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  Chunk c0 = DecodeChunk(payloads.back());
-  EXPECT_EQ(c0.transfer_id, 14u);
-
-  // Send the first parameters chunk.
-  rpc::test::WaitForPackets(context_.output(), 2, [this] {
-    context_.server().SendServerStream<Transfer::Write>(
-        EncodeChunk({.transfer_id = 14,
-                     .pending_bytes = 16,
-                     .max_chunk_size_bytes = 8,
-                     .offset = 0}));
-  });
-  ASSERT_EQ(payloads.size(), 3u);
-
-  EXPECT_EQ(transfer_status, Status::Unknown());
-
-  Chunk c1 = DecodeChunk(payloads[1]);
-  EXPECT_EQ(c1.transfer_id, 14u);
-  EXPECT_EQ(c1.offset, 0u);
-  EXPECT_EQ(c1.data.size(), 8u);
-  EXPECT_EQ(std::memcmp(c1.data.data(), kData32.data(), c1.data.size()), 0);
-
-  Chunk c2 = DecodeChunk(payloads[2]);
-  EXPECT_EQ(c2.transfer_id, 14u);
-  EXPECT_EQ(c2.offset, 8u);
-  EXPECT_EQ(c2.data.size(), 8u);
-  EXPECT_EQ(
-      std::memcmp(c2.data.data(), kData32.data() + c2.offset, c1.data.size()),
-      0);
-
-  // Wait for the timeout to expire without doing anything. The client should
-  // fail to seek back and end the transfer.
-  transfer_thread_.SimulateClientTimeout(14);
-  ASSERT_EQ(payloads.size(), 4u);
-
-  Chunk c3 = DecodeChunk(payloads[3]);
-  EXPECT_EQ(c3.transfer_id, 14u);
-  ASSERT_TRUE(c3.status.has_value());
-  EXPECT_EQ(c3.status.value(), Status::DeadlineExceeded());
-
-  EXPECT_EQ(transfer_status, Status::DeadlineExceeded());
-}
-
-PW_MODIFY_DIAGNOSTICS_POP();
-
-}  // namespace
-}  // namespace pw::transfer::test
diff --git a/pw_transfer/context.cc b/pw_transfer/context.cc
deleted file mode 100644
index 0402703..0000000
--- a/pw_transfer/context.cc
+++ /dev/null
@@ -1,806 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 PW_LOG_MODULE_NAME "TRN"
-
-#include "pw_transfer/internal/context.h"
-
-#include <chrono>
-#include <mutex>
-
-#include "pw_assert/check.h"
-#include "pw_chrono/system_clock.h"
-#include "pw_log/log.h"
-#include "pw_status/try.h"
-#include "pw_transfer/transfer.pwpb.h"
-#include "pw_transfer/transfer_thread.h"
-#include "pw_varint/varint.h"
-
-PW_MODIFY_DIAGNOSTICS_PUSH();
-PW_MODIFY_DIAGNOSTIC(ignored, "-Wmissing-field-initializers");
-
-namespace pw::transfer::internal {
-
-void Context::HandleEvent(const Event& event) {
-  switch (event.type) {
-    case EventType::kNewClientTransfer:
-    case EventType::kNewServerTransfer: {
-      if (active()) {
-        Finish(Status::Aborted());
-      }
-
-      Initialize(event.new_transfer);
-
-      if (event.type == EventType::kNewClientTransfer) {
-        InitiateTransferAsClient();
-      } else {
-        StartTransferAsServer(event.new_transfer);
-      }
-      return;
-    }
-
-    case EventType::kClientChunk:
-    case EventType::kServerChunk:
-      PW_CHECK(initialized());
-      HandleChunkEvent(event.chunk);
-      return;
-
-    case EventType::kClientTimeout:
-    case EventType::kServerTimeout:
-      HandleTimeout();
-      return;
-
-    case EventType::kSendStatusChunk:
-    case EventType::kSetTransferStream:
-    case EventType::kAddTransferHandler:
-    case EventType::kRemoveTransferHandler:
-    case EventType::kTerminate:
-      // These events are intended for the transfer thread and should never be
-      // forwarded through to a context.
-      PW_CRASH("Transfer context received a transfer thread event");
-  }
-}
-
-void Context::InitiateTransferAsClient() {
-  PW_DCHECK(active());
-
-  SetTimeout(chunk_timeout_);
-
-  if (type() == TransferType::kReceive) {
-    // A receiver begins a new transfer with a parameters chunk telling the
-    // transmitter what to send.
-    UpdateAndSendTransferParameters(TransmitAction::kBegin);
-  } else {
-    SendInitialTransmitChunk();
-  }
-
-  // Don't send an error packet. If the transfer failed to start, then there's
-  // nothing to tell the server about.
-}
-
-void Context::StartTransferAsServer(const NewTransferEvent& new_transfer) {
-  PW_LOG_INFO("Starting transfer %u with handler %u",
-              static_cast<unsigned>(new_transfer.transfer_id),
-              static_cast<unsigned>(new_transfer.handler_id));
-
-  if (const Status status = new_transfer.handler->Prepare(new_transfer.type);
-      !status.ok()) {
-    PW_LOG_WARN("Transfer handler %u prepare failed with status %u",
-                static_cast<unsigned>(new_transfer.handler->id()),
-                status.code());
-    Finish(status.IsPermissionDenied() ? status : Status::DataLoss());
-    // Do not send the final status packet here! On the server, a start event
-    // will immediately be followed by the server chunk event. Sending the final
-    // chunk will be handled then.
-    return;
-  }
-
-  // Initialize doesn't set the handler since it's specific to server transfers.
-  static_cast<ServerContext&>(*this).set_handler(*new_transfer.handler);
-
-  // Server transfers use the stream provided by the handler rather than the
-  // stream included in the NewTransferEvent.
-  stream_ = &new_transfer.handler->stream();
-}
-
-void Context::SendInitialTransmitChunk() {
-  // A transmitter begins a transfer by just sending its ID.
-  internal::Chunk chunk = {};
-  chunk.transfer_id = transfer_id_;
-  chunk.type = Chunk::Type::kTransferStart;
-
-  EncodeAndSendChunk(chunk);
-}
-
-void Context::SendTransferParameters(TransmitAction action) {
-  internal::Chunk parameters = {
-      .transfer_id = transfer_id_,
-      .window_end_offset = window_end_offset_,
-      .pending_bytes = pending_bytes_,
-      .max_chunk_size_bytes = max_chunk_size_bytes_,
-      .min_delay_microseconds = kDefaultChunkDelayMicroseconds,
-      .offset = offset_,
-  };
-
-  switch (action) {
-    case TransmitAction::kBegin:
-      parameters.type = internal::Chunk::Type::kTransferStart;
-      break;
-    case TransmitAction::kRetransmit:
-      parameters.type = internal::Chunk::Type::kParametersRetransmit;
-      break;
-    case TransmitAction::kExtend:
-      parameters.type = internal::Chunk::Type::kParametersContinue;
-      break;
-  }
-
-  PW_LOG_DEBUG(
-      "Transfer %u sending transfer parameters: "
-      "offset=%u, window_end_offset=%u, pending_bytes=%u, chunk_size=%u",
-      static_cast<unsigned>(transfer_id_),
-      static_cast<unsigned>(offset_),
-      static_cast<unsigned>(window_end_offset_),
-      static_cast<unsigned>(pending_bytes_),
-      static_cast<unsigned>(max_chunk_size_bytes_));
-
-  EncodeAndSendChunk(parameters);
-}
-
-void Context::EncodeAndSendChunk(const Chunk& chunk) {
-  Result<ConstByteSpan> data =
-      internal::EncodeChunk(chunk, thread_->encode_buffer());
-  if (!data.ok()) {
-    PW_LOG_ERROR("Failed to encode chunk for transfer %u: %d",
-                 static_cast<unsigned>(chunk.transfer_id),
-                 data.status().code());
-    if (active()) {
-      Finish(Status::Internal());
-    }
-    return;
-  }
-
-  if (const Status status = rpc_writer_->Write(*data); !status.ok()) {
-    PW_LOG_ERROR("Failed to write chunk for transfer %u: %d",
-                 static_cast<unsigned>(chunk.transfer_id),
-                 status.code());
-    if (active()) {
-      Finish(Status::Internal());
-    }
-    return;
-  }
-}
-
-void Context::UpdateAndSendTransferParameters(TransmitAction action) {
-  size_t pending_bytes =
-      std::min(max_parameters_->pending_bytes(),
-               static_cast<uint32_t>(writer().ConservativeWriteLimit()));
-
-  window_size_ = pending_bytes;
-  window_end_offset_ = offset_ + pending_bytes;
-  pending_bytes_ = pending_bytes;
-
-  max_chunk_size_bytes_ = MaxWriteChunkSize(
-      max_parameters_->max_chunk_size_bytes(), rpc_writer_->channel_id());
-
-  PW_LOG_INFO("Transfer rate: %u B/s",
-              static_cast<unsigned>(transfer_rate_.GetRateBytesPerSecond()));
-
-  return SendTransferParameters(action);
-}
-
-void Context::Initialize(const NewTransferEvent& new_transfer) {
-  PW_DCHECK(!active());
-
-  transfer_id_ = new_transfer.transfer_id;
-  flags_ = static_cast<uint8_t>(new_transfer.type);
-  transfer_state_ = TransferState::kWaiting;
-  retries_ = 0;
-  max_retries_ = new_transfer.max_retries;
-
-  rpc_writer_ = new_transfer.rpc_writer;
-  stream_ = new_transfer.stream;
-
-  offset_ = 0;
-  window_size_ = 0;
-  window_end_offset_ = 0;
-  pending_bytes_ = 0;
-  max_chunk_size_bytes_ = new_transfer.max_parameters->max_chunk_size_bytes();
-
-  max_parameters_ = new_transfer.max_parameters;
-  thread_ = new_transfer.transfer_thread;
-
-  last_chunk_offset_ = 0;
-  chunk_timeout_ = new_transfer.timeout;
-  interchunk_delay_ = chrono::SystemClock::for_at_least(
-      std::chrono::microseconds(kDefaultChunkDelayMicroseconds));
-  next_timeout_ = kNoTimeout;
-
-  transfer_rate_.Reset();
-}
-
-void Context::HandleChunkEvent(const ChunkEvent& event) {
-  PW_DCHECK(event.transfer_id == transfer_id_);
-
-  Chunk chunk;
-  if (!DecodeChunk(ConstByteSpan(event.data, event.size), chunk).ok()) {
-    return;
-  }
-
-  // Received some data. Reset the retry counter.
-  retries_ = 0;
-
-  if (chunk.status.has_value()) {
-    if (active()) {
-      Finish(chunk.status.value());
-    } else {
-      PW_LOG_DEBUG("Got final status %d for completed transfer %d",
-                   static_cast<int>(chunk.status.value().code()),
-                   static_cast<int>(transfer_id_));
-    }
-    return;
-  }
-
-  if (type() == TransferType::kTransmit) {
-    HandleTransmitChunk(chunk);
-  } else {
-    HandleReceiveChunk(chunk);
-  }
-}
-
-void Context::HandleTransmitChunk(const Chunk& chunk) {
-  switch (transfer_state_) {
-    case TransferState::kInactive:
-    case TransferState::kRecovery:
-      PW_CRASH("Never should handle chunk while inactive");
-
-    case TransferState::kCompleted:
-      // If the transfer has already completed and another chunk is received,
-      // tell the other end that the transfer is over.
-      //
-      // TODO(frolv): Final status chunks should be ACKed by the other end. When
-      // that is added, this case should be updated to check if the received
-      // chunk is an ACK. If so, the transfer state can be reset to INACTIVE.
-      // Otherwise, the final status should be re-sent.
-      if (!chunk.IsInitialChunk()) {
-        status_ = Status::FailedPrecondition();
-      }
-      SendFinalStatusChunk();
-      return;
-
-    case TransferState::kWaiting:
-    case TransferState::kTransmitting:
-      HandleTransferParametersUpdate(chunk);
-      if (transfer_state_ == TransferState::kCompleted) {
-        SendFinalStatusChunk();
-      }
-      return;
-  }
-}
-
-void Context::HandleTransferParametersUpdate(const Chunk& chunk) {
-  if (!chunk.pending_bytes.has_value()) {
-    // Malformed chunk.
-    Finish(Status::InvalidArgument());
-    return;
-  }
-
-  bool retransmit = true;
-  if (chunk.type.has_value()) {
-    retransmit = chunk.type == Chunk::Type::kParametersRetransmit ||
-                 chunk.type == Chunk::Type::kTransferStart;
-  }
-
-  if (retransmit) {
-    // If the offsets don't match, attempt to seek on the reader. Not all
-    // readers support seeking; abort with UNIMPLEMENTED if this handler
-    // doesn't.
-    if (offset_ != chunk.offset) {
-      if (Status seek_status = reader().Seek(chunk.offset); !seek_status.ok()) {
-        PW_LOG_WARN("Transfer %u seek to %u failed with status %u",
-                    static_cast<unsigned>(transfer_id_),
-                    static_cast<unsigned>(chunk.offset),
-                    seek_status.code());
-
-        // Remap status codes to return one of the following:
-        //
-        //   INTERNAL: invalid seek, never should happen
-        //   DATA_LOSS: the reader is in a bad state
-        //   UNIMPLEMENTED: seeking is not supported
-        //
-        if (seek_status.IsOutOfRange()) {
-          seek_status = Status::Internal();
-        } else if (!seek_status.IsUnimplemented()) {
-          seek_status = Status::DataLoss();
-        }
-
-        Finish(seek_status);
-        return;
-      }
-    }
-
-    // Retransmit is the default behavior for older versions of the transfer
-    // protocol. The window_end_offset field is not guaranteed to be set in
-    // these versions, so it must be calculated.
-    offset_ = chunk.offset;
-    window_end_offset_ = offset_ + chunk.pending_bytes.value();
-    pending_bytes_ = chunk.pending_bytes.value();
-  } else {
-    window_end_offset_ = chunk.window_end_offset;
-  }
-
-  if (chunk.max_chunk_size_bytes.has_value()) {
-    max_chunk_size_bytes_ = std::min(chunk.max_chunk_size_bytes.value(),
-                                     max_parameters_->max_chunk_size_bytes());
-  }
-
-  if (chunk.min_delay_microseconds.has_value()) {
-    interchunk_delay_ = chrono::SystemClock::for_at_least(
-        std::chrono::microseconds(chunk.min_delay_microseconds.value()));
-  }
-
-  PW_LOG_DEBUG(
-      "Transfer %u received parameters type=%s offset=%u window_end_offset=%u",
-      static_cast<unsigned>(transfer_id_),
-      retransmit ? "RETRANSMIT" : "CONTINUE",
-      static_cast<unsigned>(chunk.offset),
-      static_cast<unsigned>(window_end_offset_));
-
-  // Parsed all of the parameters; start sending the window.
-  set_transfer_state(TransferState::kTransmitting);
-
-  TransmitNextChunk(retransmit);
-}
-
-void Context::TransmitNextChunk(bool retransmit_requested) {
-  ByteSpan buffer = thread_->encode_buffer();
-
-  // Begin by doing a partial encode of all the metadata fields, leaving the
-  // buffer with usable space for the chunk data at the end.
-  transfer::Chunk::MemoryEncoder encoder{buffer};
-  encoder.WriteTransferId(transfer_id_).IgnoreError();
-  encoder.WriteOffset(offset_).IgnoreError();
-
-  // TODO(frolv): Type field presence is currently meaningful, so this type must
-  // be serialized. Once all users of transfer always set chunk types, the field
-  // can be made non-optional and this write can be removed as TRANSFER_DATA has
-  // the default proto value of 0.
-  encoder.WriteType(transfer::Chunk::Type::TRANSFER_DATA).IgnoreError();
-
-  // Reserve space for the data proto field overhead and use the remainder of
-  // the buffer for the chunk data.
-  size_t reserved_size = encoder.size() + 1 /* data key */ + 5 /* data size */;
-
-  ByteSpan data_buffer = buffer.subspan(reserved_size);
-  size_t max_bytes_to_send =
-      std::min(window_end_offset_ - offset_, max_chunk_size_bytes_);
-
-  if (max_bytes_to_send < data_buffer.size()) {
-    data_buffer = data_buffer.first(max_bytes_to_send);
-  }
-
-  Result<ByteSpan> data = reader().Read(data_buffer);
-  if (data.status().IsOutOfRange()) {
-    // No more data to read.
-    encoder.WriteRemainingBytes(0).IgnoreError();
-    window_end_offset_ = offset_;
-    pending_bytes_ = 0;
-
-    PW_LOG_DEBUG("Transfer %u sending final chunk with remaining_bytes=0",
-                 static_cast<unsigned>(transfer_id_));
-  } else if (data.ok()) {
-    if (offset_ == window_end_offset_) {
-      if (retransmit_requested) {
-        PW_LOG_DEBUG(
-            "Transfer %u: received an empty retransmit request, but there is "
-            "still data to send; aborting with RESOURCE_EXHAUSTED",
-            id_for_log());
-        Finish(Status::ResourceExhausted());
-      } else {
-        PW_LOG_DEBUG(
-            "Transfer %u: ignoring continuation packet for transfer window "
-            "that has already been sent",
-            id_for_log());
-        SetTimeout(chunk_timeout_);
-      }
-      return;  // No data was requested, so there is nothing else to do.
-    }
-
-    PW_LOG_DEBUG("Transfer %u sending chunk offset=%u size=%u",
-                 static_cast<unsigned>(transfer_id_),
-                 static_cast<unsigned>(offset_),
-                 static_cast<unsigned>(data.value().size()));
-
-    encoder.WriteData(data.value()).IgnoreError();
-    last_chunk_offset_ = offset_;
-    offset_ += data.value().size();
-    pending_bytes_ -= data.value().size();
-  } else {
-    PW_LOG_ERROR("Transfer %u Read() failed with status %u",
-                 static_cast<unsigned>(transfer_id_),
-                 data.status().code());
-    Finish(Status::DataLoss());
-    return;
-  }
-
-  if (!encoder.status().ok()) {
-    PW_LOG_ERROR("Transfer %u failed to encode transmit chunk",
-                 static_cast<unsigned>(transfer_id_));
-    Finish(Status::Internal());
-    return;
-  }
-
-  if (const Status status = rpc_writer_->Write(encoder); !status.ok()) {
-    PW_LOG_ERROR("Transfer %u failed to send transmit chunk, status %u",
-                 static_cast<unsigned>(transfer_id_),
-                 status.code());
-    Finish(Status::DataLoss());
-    return;
-  }
-
-  flags_ |= kFlagsDataSent;
-
-  if (offset_ == window_end_offset_) {
-    // Sent all requested data. Must now wait for next parameters from the
-    // receiver.
-    set_transfer_state(TransferState::kWaiting);
-    SetTimeout(chunk_timeout_);
-  } else {
-    // More data is to be sent. Set a timeout to send the next chunk following
-    // the chunk delay.
-    SetTimeout(chrono::SystemClock::for_at_least(interchunk_delay_));
-  }
-}
-
-void Context::HandleReceiveChunk(const Chunk& chunk) {
-  switch (transfer_state_) {
-    case TransferState::kInactive:
-      PW_CRASH("Never should handle chunk while inactive");
-
-    case TransferState::kTransmitting:
-      PW_CRASH("Receive transfer somehow entered TRANSMITTING state");
-
-    case TransferState::kCompleted:
-      // If the transfer has already completed and another chunk is received,
-      // re-send the final status chunk.
-      //
-      // TODO(frolv): Final status chunks should be ACKed by the other end. When
-      // that is added, this case should be updated to check if the received
-      // chunk is an ACK. If so, the transfer state can be reset to INACTIVE.
-      // Otherwise, the final status should be re-sent.
-      SendFinalStatusChunk();
-      return;
-
-    case TransferState::kRecovery:
-      if (chunk.offset != offset_) {
-        if (last_chunk_offset_ == chunk.offset) {
-          PW_LOG_DEBUG(
-              "Transfer %u received repeated offset %u; retry detected, "
-              "resending transfer parameters",
-              static_cast<unsigned>(transfer_id_),
-              static_cast<unsigned>(chunk.offset));
-
-          UpdateAndSendTransferParameters(TransmitAction::kRetransmit);
-          if (transfer_state_ == TransferState::kCompleted) {
-            SendFinalStatusChunk();
-            return;
-          }
-          PW_LOG_DEBUG("Transfer %u waiting for offset %u, ignoring %u",
-                       static_cast<unsigned>(transfer_id_),
-                       static_cast<unsigned>(offset_),
-                       static_cast<unsigned>(chunk.offset));
-        }
-
-        last_chunk_offset_ = chunk.offset;
-        SetTimeout(chunk_timeout_);
-        return;
-      }
-
-      PW_LOG_DEBUG("Transfer %u received expected offset %u, resuming transfer",
-                   static_cast<unsigned>(transfer_id_),
-                   static_cast<unsigned>(offset_));
-      set_transfer_state(TransferState::kWaiting);
-
-      // The correct chunk was received; process it normally.
-      [[fallthrough]];
-    case TransferState::kWaiting:
-      HandleReceivedData(chunk);
-      if (transfer_state_ == TransferState::kCompleted) {
-        SendFinalStatusChunk();
-      }
-      return;
-  }
-}
-
-void Context::HandleReceivedData(const Chunk& chunk) {
-  if (chunk.data.size() > pending_bytes_) {
-    // End the transfer, as this indicates a bug with the client implementation
-    // where it doesn't respect pending_bytes. Trying to recover from here
-    // could potentially result in an infinite transfer loop.
-    PW_LOG_ERROR(
-        "Transfer %u received more data than what was requested (%u received "
-        "for %u pending); terminating transfer.",
-        id_for_log(),
-        static_cast<unsigned>(chunk.data.size()),
-        static_cast<unsigned>(pending_bytes_));
-    Finish(Status::Internal());
-    return;
-  }
-
-  if (chunk.offset != offset_) {
-    // Bad offset; reset pending_bytes to send another parameters chunk.
-    PW_LOG_DEBUG(
-        "Transfer %u expected offset %u, received %u; entering recovery state",
-        static_cast<unsigned>(transfer_id_),
-        static_cast<unsigned>(offset_),
-        static_cast<unsigned>(chunk.offset));
-
-    set_transfer_state(TransferState::kRecovery);
-    SetTimeout(chunk_timeout_);
-
-    UpdateAndSendTransferParameters(TransmitAction::kRetransmit);
-    return;
-  }
-
-  // Update the last offset seen so that retries can be detected.
-  last_chunk_offset_ = chunk.offset;
-
-  // Write staged data from the buffer to the stream.
-  if (!chunk.data.empty()) {
-    if (Status status = writer().Write(chunk.data); !status.ok()) {
-      PW_LOG_ERROR(
-          "Transfer %u write of %u B chunk failed with status %u; aborting "
-          "with DATA_LOSS",
-          static_cast<unsigned>(transfer_id_),
-          static_cast<unsigned>(chunk.data.size()),
-          status.code());
-      Finish(Status::DataLoss());
-      return;
-    }
-
-    transfer_rate_.Update(chunk.data.size());
-  }
-
-  // When the client sets remaining_bytes to 0, it indicates completion of the
-  // transfer. Acknowledge the completion through a status chunk and clean up.
-  if (chunk.IsFinalTransmitChunk()) {
-    Finish(OkStatus());
-    return;
-  }
-
-  // Update the transfer state.
-  offset_ += chunk.data.size();
-  pending_bytes_ -= chunk.data.size();
-
-  if (chunk.window_end_offset != 0) {
-    if (chunk.window_end_offset < offset_) {
-      PW_LOG_ERROR(
-          "Transfer %u got invalid end offset of %u (current offset %u)",
-          id_for_log(),
-          static_cast<unsigned>(chunk.window_end_offset),
-          static_cast<unsigned>(offset_));
-      Finish(Status::Internal());
-      return;
-    }
-
-    if (chunk.window_end_offset > window_end_offset_) {
-      // A transmitter should never send a larger end offset than what the
-      // receiver has advertised. If this occurs, there is a bug in the
-      // transmitter implementation. Terminate the transfer.
-      PW_LOG_ERROR(
-          "Transfer %u transmitter sent invalid end offset of %u, "
-          "greater than receiver offset %u",
-          id_for_log(),
-          static_cast<unsigned>(chunk.window_end_offset),
-          static_cast<unsigned>(window_end_offset_));
-      Finish(Status::Internal());
-      return;
-    }
-
-    window_end_offset_ = chunk.window_end_offset;
-    pending_bytes_ = chunk.window_end_offset - offset_;
-  }
-
-  SetTimeout(chunk_timeout_);
-
-  if (pending_bytes_ == 0u) {
-    // Received all pending data. Advance the transfer parameters.
-    UpdateAndSendTransferParameters(TransmitAction::kRetransmit);
-    return;
-  }
-
-  // Once the transmitter has sent a sufficient amount of data, try to extend
-  // the window to allow it to continue sending data without blocking.
-  uint32_t remaining_window_size = window_end_offset_ - offset_;
-  bool extend_window = remaining_window_size <=
-                       window_size_ / max_parameters_->extend_window_divisor();
-
-  if (extend_window) {
-    UpdateAndSendTransferParameters(TransmitAction::kExtend);
-    return;
-  }
-}
-
-void Context::SendFinalStatusChunk() {
-  PW_DCHECK(transfer_state_ == TransferState::kCompleted);
-
-  internal::Chunk chunk = {};
-  chunk.transfer_id = transfer_id_;
-  chunk.status = status_.code();
-  chunk.type = Chunk::Type::kTransferCompletion;
-
-  PW_LOG_DEBUG("Sending final chunk for transfer %u with status %u",
-               static_cast<unsigned>(transfer_id_),
-               status_.code());
-  EncodeAndSendChunk(chunk);
-}
-
-void Context::Finish(Status status) {
-  PW_DCHECK(active());
-
-  PW_LOG_INFO("Transfer %u completed with status %u",
-              static_cast<unsigned>(transfer_id_),
-              status.code());
-
-  status.Update(FinalCleanup(status));
-
-  set_transfer_state(TransferState::kCompleted);
-  SetTimeout(kFinalChunkAckTimeout);
-  status_ = status;
-}
-
-void Context::SetTimeout(chrono::SystemClock::duration timeout) {
-  next_timeout_ = chrono::SystemClock::TimePointAfterAtLeast(timeout);
-}
-
-void Context::HandleTimeout() {
-  ClearTimeout();
-
-  switch (transfer_state_) {
-    case TransferState::kCompleted:
-      // A timeout occurring in a completed state indicates that the other side
-      // never ACKed the final status packet. Reset the context to inactive.
-      set_transfer_state(TransferState::kInactive);
-      return;
-
-    case TransferState::kTransmitting:
-      // A timeout occurring in a TRANSMITTING state indicates that the transfer
-      // has waited for its inter-chunk delay and should transmit its next
-      // chunk.
-      TransmitNextChunk(/*retransmit_requested=*/false);
-      break;
-
-    case TransferState::kWaiting:
-    case TransferState::kRecovery:
-      // A timeout occurring in a WAITING or RECOVERY state indicates that no
-      // chunk has been received from the other side. The transfer should retry
-      // its previous operation.
-      SetTimeout(chunk_timeout_);  // Finish() clears the timeout if retry fails
-      Retry();
-      break;
-
-    case TransferState::kInactive:
-      PW_LOG_ERROR("Timeout occurred in INACTIVE state");
-      return;
-  }
-
-  if (transfer_state_ == TransferState::kCompleted) {
-    SendFinalStatusChunk();
-  }
-}
-
-void Context::Retry() {
-  if (retries_ == max_retries_) {
-    PW_LOG_ERROR("Transfer %u failed to receive a chunk after %u retries.",
-                 static_cast<unsigned>(transfer_id_),
-                 static_cast<unsigned>(retries_));
-    PW_LOG_ERROR("Canceling transfer.");
-    Finish(Status::DeadlineExceeded());
-    return;
-  }
-
-  ++retries_;
-
-  if (type() == TransferType::kReceive) {
-    // Resend the most recent transfer parameters.
-    PW_LOG_DEBUG(
-        "Receive transfer %u timed out waiting for chunk; resending parameters",
-        static_cast<unsigned>(transfer_id_));
-
-    SendTransferParameters(TransmitAction::kRetransmit);
-    return;
-  }
-
-  // In a transmit, if a data chunk has not yet been sent, the initial transfer
-  // parameters did not arrive from the receiver. Resend the initial chunk.
-  if ((flags_ & kFlagsDataSent) != kFlagsDataSent) {
-    PW_LOG_DEBUG(
-        "Transmit transfer %u timed out waiting for initial parameters",
-        static_cast<unsigned>(transfer_id_));
-    SendInitialTransmitChunk();
-    return;
-  }
-
-  // Otherwise, resend the most recent chunk. If the reader doesn't support
-  // seeking, this isn't possible, so just terminate the transfer immediately.
-  if (!reader().Seek(last_chunk_offset_).ok()) {
-    PW_LOG_ERROR("Transmit transfer %d timed out waiting for new parameters.",
-                 static_cast<unsigned>(transfer_id_));
-    PW_LOG_ERROR("Retrying requires a seekable reader. Alas, ours is not.");
-    Finish(Status::DeadlineExceeded());
-    return;
-  }
-
-  // Rewind the transfer position and resend the chunk.
-  size_t last_size_sent = offset_ - last_chunk_offset_;
-  offset_ = last_chunk_offset_;
-  pending_bytes_ += last_size_sent;
-
-  TransmitNextChunk(/*retransmit_requested=*/false);
-}
-
-uint32_t Context::MaxWriteChunkSize(uint32_t max_chunk_size_bytes,
-                                    uint32_t channel_id) const {
-  // Start with the user-provided maximum chunk size, which should be the usable
-  // payload length on the RPC ingress path after any transport overhead.
-  ptrdiff_t max_size = max_chunk_size_bytes;
-
-  // Subtract the RPC overhead (pw_rpc/internal/packet.proto).
-  //
-  //   type:       1 byte key, 1 byte value (CLIENT_STREAM)
-  //   channel_id: 1 byte key, varint value (calculate from stream)
-  //   service_id: 1 byte key, 4 byte value
-  //   method_id:  1 byte key, 4 byte value
-  //   payload:    1 byte key, varint length (remaining space)
-  //   status:     0 bytes (not set in stream packets)
-  //
-  //   TOTAL: 14 bytes + encoded channel_id size + encoded payload length
-  //
-  max_size -= 14;
-  max_size -= varint::EncodedSize(channel_id);
-  max_size -= varint::EncodedSize(max_size);
-
-  // TODO(frolv): Temporarily add 5 bytes for the new call_id change. The RPC
-  // overhead calculation will be moved into an RPC helper to avoid having
-  // pw_transfer depend on RPC internals.
-  max_size -= 5;
-
-  // Subtract the transfer service overhead for a client write chunk
-  // (pw_transfer/transfer.proto).
-  //
-  //   transfer_id: 1 byte key, varint value (calculate)
-  //   offset:      1 byte key, varint value (calculate)
-  //   data:        1 byte key, varint length (remaining space)
-  //
-  //   TOTAL: 3 + encoded transfer_id + encoded offset + encoded data length
-  //
-  max_size -= 3;
-  max_size -= varint::EncodedSize(transfer_id_);
-  max_size -= varint::EncodedSize(window_end_offset_);
-  max_size -= varint::EncodedSize(max_size);
-
-  // A resulting value of zero (or less) renders write transfers unusable, as
-  // there is no space to send any payload. This should be considered a
-  // programmer error in the transfer service setup.
-  PW_CHECK_INT_GT(
-      max_size,
-      0,
-      "Transfer service maximum chunk size is too small to fit a payload. "
-      "Increase max_chunk_size_bytes to support write transfers.");
-
-  return max_size;
-}
-
-}  // namespace pw::transfer::internal
-
-PW_MODIFY_DIAGNOSTICS_POP();
diff --git a/pw_transfer/docs.rst b/pw_transfer/docs.rst
deleted file mode 100644
index b0a068b..0000000
--- a/pw_transfer/docs.rst
+++ /dev/null
@@ -1,321 +0,0 @@
-.. _module-pw_transfer:
-
-===========
-pw_transfer
-===========
-
-.. attention::
-
-  ``pw_transfer`` is under construction and so is its documentation.
-
------
-Usage
------
-
-C++
-===
-The transfer service is defined and registered with an RPC server like any other
-RPC service.
-
-To know how to read data from or write data to device, a ``TransferHandler``
-interface is defined (``pw_transfer/public/pw_transfer/handler.h``). Transfer
-handlers wrap a stream reader and/or writer with initialization and completion
-code. Custom transfer handler implementations should derive from
-``ReadOnlyHandler``, ``WriteOnlyHandler``, or ``ReadWriteHandler`` as
-appropriate and override Prepare and Finalize methods if necessary.
-
-A transfer handler should be implemented and instantiated for each unique data
-transfer to or from a device. These handlers are then registered with the
-transfer service using their transfer IDs.
-
-**Example**
-
-.. code-block:: cpp
-
-  #include "pw_transfer/transfer.h"
-
-  namespace {
-
-  // Simple transfer handler which reads data from an in-memory buffer.
-  class SimpleBufferReadHandler : public pw::transfer::ReadOnlyHandler {
-   public:
-    SimpleReadTransfer(uint32_t transfer_id, pw::ConstByteSpan data)
-        : ReadOnlyHandler(transfer_id), reader_(data) {
-      set_reader(reader_);
-    }
-
-   private:
-    pw::stream::MemoryReader reader_;
-  };
-
-  // The maximum amount of data that can be sent in a single chunk, excluding
-  // transport layer overhead.
-  constexpr size_t kMaxChunkSizeBytes = 256;
-
-  // In a write transfer, the maximum number of bytes to receive at one time,
-  // (potentially across multiple chunks), unless specified otherwise by the
-  // transfer handler's stream::Writer.
-  constexpr size_t kDefaultMaxBytesToReceive = 1024;
-
-  // Instantiate a static transfer service.
-  // The service requires a work queue, and a buffer to store data from a chunk.
-  // The helper class TransferServiceBuffer comes with a builtin buffer.
-  pw::transfer::TransferServiceBuffer<kMaxChunkSizeBytes> transfer_service(
-      GetSystemWorkQueue(), kDefaultMaxBytesToReceive);
-
-  // Instantiate a handler for the data to be transferred.
-  constexpr uint32_t kBufferTransferId = 1;
-  char buffer_to_transfer[256] = { /* ... */ };
-  SimpleBufferReadHandler buffer_handler(kBufferTransferId, buffer_to_transfer);
-
-  }  // namespace
-
-  void InitTransfer() {
-    // Register the handler with the transfer service, then the transfer service
-    // with an RPC server.
-    transfer_service.RegisterHandler(buffer_handler);
-    GetSystemRpcServer().RegisterService(transfer_service);
-  }
-
-Module Configuration Options
-----------------------------
-The following configurations can be adjusted via compile-time configuration of
-this module, see the
-:ref:`module documentation <module-structure-compile-time-configuration>` for
-more details.
-
-.. c:macro:: PW_TRANSFER_DEFAULT_MAX_RETRIES
-
-  The default maximum number of times a transfer should retry sending a chunk
-  when no response is received. This can later be configured per-transfer.
-
-.. c:macro:: PW_TRANSFER_DEFAULT_TIMEOUT_MS
-
-  The default amount of time, in milliseconds, to wait for a chunk to arrive
-  before retrying. This can later be configured per-transfer.
-
-.. c:macro:: PW_TRANSFER_DEFAULT_EXTEND_WINDOW_DIVISOR
-
-  The fractional position within a window at which a receive transfer should
-  extend its window size to minimize the amount of time the transmitter
-  spends blocked.
-
-  For example, a divisor of 2 will extend the window when half of the
-  requested data has been received, a divisor of three will extend at a third
-  of the window, and so on.
-
-Python
-======
-.. automodule:: pw_transfer
-  :members: ProgressStats, Manager, Error
-
-**Example**
-
-.. code-block:: python
-
-  import pw_transfer
-
-  # Initialize a Pigweed RPC client; see pw_rpc docs for more info.
-  rpc_client = CustomRpcClient()
-  rpcs = rpc_client.channel(1).rpcs
-
-  transfer_service = rpcs.pw.transfer.Transfer
-  transfer_manager = pw_transfer.Manager(transfer_service)
-
-  try:
-    # Read transfer_id 3 from the server.
-    data = transfer_manager.read(3)
-  except pw_transfer.Error as err:
-    print('Failed to read:', err.status)
-
-  try:
-    # Send some data to the server. The transfer manager does not have to be
-    # reinitialized.
-    transfer_manager.write(2, b'hello, world')
-  except pw_transfer.Error as err:
-    print('Failed to write:', err.status)
-
-Typescript
-==========
-
-Provides a simple interface for transferring bulk data over pw_rpc.
-
-**Example**
-
-.. code-block:: typescript
-
-    import {Manager} from '@pigweed/pw_transfer'
-
-    const client = new CustomRpcClient();
-    service = client.channel()!.service('pw.transfer.Transfer')!;
-
-    const manager = new Manager(service, DEFAULT_TIMEOUT_S);
-
-    manager.read(3, (stats: ProgressStats) => {
-      console.log(`Progress Update: ${stats}`);
-    }).then((data: Uint8Array) => {
-      console.log(`Completed read: ${data}`);
-    }).catch(error => {
-      console.log(`Failed to read: ${error.status}`);
-    });
-
-    manager.write(2, textEncoder.encode('hello world'))
-      .catch(error => {
-        console.log(`Failed to read: ${error.status}`);
-      });
-
---------
-Protocol
---------
-
-Protocol buffer definition
-==========================
-.. literalinclude:: transfer.proto
-  :language: protobuf
-  :lines: 14-
-
-Server to client transfer (read)
-================================
-.. image:: read.svg
-
-Client to server transfer (write)
-=================================
-.. image:: write.svg
-
-Errors
-======
-
-Protocol errors
----------------
-At any point, either the client or server may terminate the transfer with a
-status code. The transfer chunk with the status is the final chunk of the
-transfer.
-
-The following table describes the meaning of each status code when sent by the
-sender or the receiver (see `Transfer roles`_).
-
-.. cpp:namespace-push:: pw::stream
-
-+-------------------------+-------------------------+-------------------------+
-| Status                  | Sent by sender          | Sent by receiver        |
-+=========================+=========================+=========================+
-| ``OK``                  | (not sent)              | All data was received   |
-|                         |                         | and handled             |
-|                         |                         | successfully.           |
-+-------------------------+-------------------------+-------------------------+
-| ``ABORTED``             | The service aborted the transfer because the      |
-|                         | client restarted it. This status is passed to the |
-|                         | transfer handler, but not sent to the client      |
-|                         | because it restarted the transfer.                |
-+-------------------------+---------------------------------------------------+
-| ``CANCELLED``           | The client cancelled the transfer.                |
-+-------------------------+-------------------------+-------------------------+
-| ``DATA_LOSS``           | Failed to read the data | Failed to write the     |
-|                         | to send. The            | received data. The      |
-|                         | :cpp:class:`Reader`     | :cpp:class:`Writer`     |
-|                         | returned an error.      | returned an error.      |
-+-------------------------+-------------------------+-------------------------+
-| ``FAILED_PRECONDITION`` | Received chunk for transfer that is not active.   |
-+-------------------------+-------------------------+-------------------------+
-| ``INVALID_ARGUMENT``    | Received a malformed packet.                      |
-+-------------------------+-------------------------+-------------------------+
-| ``INTERNAL``            | An assumption of the protocol was violated.       |
-|                         | Encountering ``INTERNAL`` indicates that there is |
-|                         | a bug in the service or client implementation.    |
-+-------------------------+-------------------------+-------------------------+
-| ``PERMISSION_DENIED``   | The transfer does not support the requested       |
-|                         | operation (either reading or writing).            |
-+-------------------------+-------------------------+-------------------------+
-| ``RESOURCE_EXHAUSTED``  | The receiver requested  | Storage is full.        |
-|                         | zero bytes, indicating  |                         |
-|                         | their storage is full,  |                         |
-|                         | but there is still data |                         |
-|                         | to send.                |                         |
-+-------------------------+-------------------------+-------------------------+
-| ``UNAVAILABLE``         | The service is busy with other transfers and      |
-|                         | cannot begin a new transfer at this time.         |
-+-------------------------+-------------------------+-------------------------+
-| ``UNIMPLEMENTED``       | Out-of-order chunk was  | (not sent)              |
-|                         | requested, but seeking  |                         |
-|                         | is not supported.       |                         |
-+-------------------------+-------------------------+-------------------------+
-
-.. cpp:namespace-pop::
-
-Client errors
--------------
-``pw_transfer`` clients may immediately return certain errors if they cannot
-start a transfer.
-
-.. list-table::
-
-  * - **Status**
-    - **Reason**
-  * - ``ALREADY_EXISTS``
-    - A transfer with the requested ID is already pending on this client.
-  * - ``DATA_LOSS``
-    - Sending the initial transfer chunk failed.
-  * - ``RESOURCE_EXHAUSTED``
-    - The client has insufficient resources to start an additional transfer at
-      this time.
-
-
-Transfer roles
-==============
-Every transfer has two participants: the sender and the receiver. The sender
-transmits data to the receiver. The receiver controls how the data is
-transferred and sends the final status when the transfer is complete.
-
-In read transfers, the client is the receiver and the service is the sender. In
-write transfers, the client is the sender and the service is the receiver.
-
-Sender flow
------------
-.. mermaid::
-
-  graph TD
-    start([Client initiates<br>transfer]) -->data_request
-    data_request[Receive transfer<br>parameters]-->send_chunk
-
-    send_chunk[Send chunk]-->sent_all
-
-    sent_all{Sent final<br>chunk?} -->|yes|wait
-    sent_all-->|no|sent_requested
-
-    sent_requested{Sent all<br>pending?}-->|yes|data_request
-    sent_requested-->|no|send_chunk
-
-    wait[Wait for receiver]-->is_done
-
-    is_done{Received<br>final chunk?}-->|yes|done
-    is_done-->|no|data_request
-
-    done([Transfer complete])
-
-Receiver flow
--------------
-.. mermaid::
-
-  graph TD
-    start([Client initiates<br>transfer]) -->request_bytes
-    request_bytes[Set transfer<br>parameters]-->wait
-
-    wait[Wait for chunk]-->received_chunk
-
-    received_chunk{Received<br>chunk by<br>deadline?}-->|no|request_bytes
-    received_chunk-->|yes|check_chunk
-
-    check_chunk{Correct<br>offset?} -->|yes|process_chunk
-    check_chunk --> |no|request_bytes
-
-    process_chunk[Process chunk]-->final_chunk
-
-    final_chunk{Final<br>chunk?}-->|yes|signal_completion
-    final_chunk{Final<br>chunk?}-->|no|received_requested
-
-    received_requested{Received all<br>pending?}-->|yes|request_bytes
-    received_requested-->|no|wait
-
-    signal_completion[Signal completion]-->done
-
-    done([Transfer complete])
diff --git a/pw_transfer/handler_test.cc b/pw_transfer/handler_test.cc
deleted file mode 100644
index 4a15910..0000000
--- a/pw_transfer/handler_test.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_transfer/handler.h"
-
-#include "gtest/gtest.h"
-
-namespace pw::transfer {
-namespace {
-
-TEST(Handlers, ReadOnly) {
-  ReadOnlyHandler handler(123);
-  EXPECT_EQ(OkStatus(), handler.PrepareRead());
-  EXPECT_EQ(Status::PermissionDenied(), handler.PrepareWrite());
-}
-
-TEST(Handlers, WriteOnly) {
-  WriteOnlyHandler handler(123);
-  EXPECT_EQ(Status::PermissionDenied(), handler.PrepareRead());
-  EXPECT_EQ(OkStatus(), handler.PrepareWrite());
-}
-
-TEST(Handlers, ReadWrite) {
-  ReadWriteHandler handler(123);
-  EXPECT_EQ(OkStatus(), handler.PrepareRead());
-  EXPECT_EQ(OkStatus(), handler.PrepareWrite());
-}
-
-}  // namespace
-}  // namespace pw::transfer
diff --git a/pw_transfer/integration_test.cc b/pw_transfer/integration_test.cc
deleted file mode 100644
index 3f6f829..0000000
--- a/pw_transfer/integration_test.cc
+++ /dev/null
@@ -1,258 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <algorithm>
-#include <chrono>
-#include <filesystem>
-#include <fstream>
-#include <string>
-#include <thread>
-
-#include "gtest/gtest.h"
-#include "pw_assert/check.h"
-#include "pw_bytes/array.h"
-#include "pw_log/log.h"
-#include "pw_rpc/integration_testing.h"
-#include "pw_status/status.h"
-#include "pw_sync/binary_semaphore.h"
-#include "pw_thread_stl/options.h"
-#include "pw_transfer/client.h"
-#include "pw_transfer_test/test_server.raw_rpc.pb.h"
-
-namespace pw::transfer {
-namespace {
-
-using namespace std::chrono_literals;
-
-constexpr int kIterations = 5;
-
-constexpr auto kData512 = bytes::Initialized<512>([](size_t i) { return i; });
-constexpr auto kData8192 = bytes::Initialized<8192>([](size_t i) { return i; });
-constexpr auto kDataHdlcEscape = bytes::Initialized<8192>(0x7e);
-
-std::filesystem::path directory;
-
-// Reads the file that represents the transfer with the specific ID.
-std::string GetContent(uint32_t transfer_id) {
-  std::ifstream stream(directory / std::to_string(transfer_id),
-                       std::ios::binary | std::ios::ate);
-  std::string contents(stream.tellg(), '\0');
-
-  stream.seekg(0, std::ios::beg);
-  PW_CHECK(stream.read(contents.data(), contents.size()));
-
-  return contents;
-}
-
-// Drops the null terminator from a string literal.
-template <size_t kLengthWithNull>
-ConstByteSpan AsByteSpan(const char (&data)[kLengthWithNull]) {
-  constexpr size_t kLength = kLengthWithNull - 1;
-  PW_CHECK_INT_EQ('\0', data[kLength], "Expecting null for last character");
-  return std::as_bytes(std::span(data, kLength));
-}
-
-constexpr ConstByteSpan AsByteSpan(ConstByteSpan data) { return data; }
-
-thread::Options& TransferThreadOptions() {
-  static thread::stl::Options options;
-  return options;
-}
-
-// Test fixture for pw_transfer tests. Clears the transfer files before and
-// after each test.
-class TransferIntegration : public ::testing::Test {
- protected:
-  TransferIntegration()
-      : transfer_thread_(chunk_buffer_, encode_buffer_),
-        system_thread_(TransferThreadOptions(), transfer_thread_),
-        client_(rpc::integration_test::client(),
-                rpc::integration_test::kChannelId,
-                transfer_thread_,
-                256),
-        test_server_client_(rpc::integration_test::client(),
-                            rpc::integration_test::kChannelId) {
-    ClearFiles();
-  }
-
-  ~TransferIntegration() {
-    ClearFiles();
-    transfer_thread_.Terminate();
-    system_thread_.join();
-  }
-
-  // Sets the content of a transfer ID and returns a MemoryReader for that data.
-  template <typename T>
-  void SetContent(uint32_t transfer_id, const T& content) {
-    const ConstByteSpan data = AsByteSpan(content);
-    std::ofstream stream(directory / std::to_string(transfer_id),
-                         std::ios::binary);
-    PW_CHECK(
-        stream.write(reinterpret_cast<const char*>(data.data()), data.size()));
-
-    sync::BinarySemaphore reload_complete;
-    rpc::RawUnaryReceiver call = test_server_client_.ReloadTransferFiles(
-        {}, [&reload_complete](ConstByteSpan, Status) {
-          reload_complete.release();
-        });
-    PW_CHECK(reload_complete.try_acquire_for(3s));
-  }
-
-  auto OnCompletion() {
-    return [this](Status status) {
-      last_status_ = status;
-      completed_.release();
-    };
-  }
-
-  // Checks that a read transfer succeeded and that the data matches the
-  // expected data.
-  void ExpectReadData(ConstByteSpan expected) {
-    ASSERT_EQ(WaitForCompletion(), OkStatus());
-    ASSERT_EQ(expected.size(), read_buffer_.size());
-
-    EXPECT_TRUE(std::equal(read_buffer_.begin(),
-                           read_buffer_.end(),
-                           std::as_bytes(std::span(expected)).begin()));
-  }
-
-  // Checks that a write transfer succeeded and that the written contents match.
-  void ExpectWriteData(uint32_t transfer_id, ConstByteSpan expected) {
-    ASSERT_EQ(WaitForCompletion(), OkStatus());
-
-    const std::string written = GetContent(transfer_id);
-    ASSERT_EQ(expected.size(), written.size());
-
-    ConstByteSpan bytes = std::as_bytes(std::span(written));
-    EXPECT_TRUE(std::equal(bytes.begin(), bytes.end(), expected.begin()));
-  }
-
-  // Waits for the transfer to complete and returns the status.
-  Status WaitForCompletion() {
-    PW_CHECK(completed_.try_acquire_for(3s));
-    return last_status_;
-  }
-
-  // Exact match the size of kData8192 to test filling the receiving buffer.
-  stream::MemoryWriterBuffer<kData8192.size()> read_buffer_;
-
-  Client& client() { return client_; }
-
- private:
-  static void ClearFiles() {
-    for (const auto& entry : std::filesystem::directory_iterator(directory)) {
-      if (!entry.is_regular_file()) {
-        continue;
-      }
-
-      if (const std::string name = entry.path().filename().string();
-          std::all_of(name.begin(), name.end(), [](char c) {
-            return std::isdigit(c);
-          })) {
-        PW_LOG_DEBUG("Clearing transfer file %s", name.c_str());
-        std::filesystem::remove(entry.path());
-      }
-    }
-  }
-
-  std::byte chunk_buffer_[512];
-  std::byte encode_buffer_[512];
-  transfer::Thread<2, 2> transfer_thread_;
-  thread::Thread system_thread_;
-
-  Client client_;
-
-  pw_rpc::raw::TestServer::Client test_server_client_;
-  Status last_status_ = Status::Unknown();
-  sync::BinarySemaphore completed_;
-};
-
-TEST_F(TransferIntegration, Read_UnknownId) {
-  SetContent(123, "hello");
-
-  ASSERT_EQ(OkStatus(), client().Read(456, read_buffer_, OnCompletion()));
-
-  EXPECT_EQ(Status::NotFound(), WaitForCompletion());
-}
-
-#define PW_TRANSFER_TEST_READ(name, content)                            \
-  TEST_F(TransferIntegration, Read_##name) {                            \
-    for (int i = 0; i < kIterations; ++i) {                             \
-      const ConstByteSpan data = AsByteSpan(content);                   \
-      SetContent(__LINE__, data);                                       \
-      ASSERT_EQ(OkStatus(),                                             \
-                client().Read(__LINE__, read_buffer_, OnCompletion())); \
-      ExpectReadData(data);                                             \
-      read_buffer_.clear();                                             \
-    }                                                                   \
-  }                                                                     \
-  static_assert(true, "Semicolons are required")
-
-PW_TRANSFER_TEST_READ(Empty, "");
-PW_TRANSFER_TEST_READ(SingleByte_1, "\0");
-PW_TRANSFER_TEST_READ(SingleByte_2, "?");
-PW_TRANSFER_TEST_READ(SmallData, "hunter2");
-PW_TRANSFER_TEST_READ(LargeData, kData512);
-PW_TRANSFER_TEST_READ(VeryLargeData, kData8192);
-
-TEST_F(TransferIntegration, Write_UnknownId) {
-  constexpr std::byte kData[] = {std::byte{0}, std::byte{1}, std::byte{2}};
-  stream::MemoryReader reader(kData);
-
-  ASSERT_EQ(OkStatus(), client().Write(99, reader, OnCompletion()));
-  EXPECT_EQ(Status::NotFound(), WaitForCompletion());
-
-  SetContent(99, "something");
-  ASSERT_EQ(OkStatus(), client().Write(100, reader, OnCompletion()));
-  EXPECT_EQ(Status::NotFound(), WaitForCompletion());
-}
-
-#define PW_TRANSFER_TEST_WRITE(name, content)                                  \
-  TEST_F(TransferIntegration, Write_##name) {                                  \
-    for (int i = 0; i < kIterations; ++i) {                                    \
-      SetContent(__LINE__, "This is junk data that should be overwritten!");   \
-      const ConstByteSpan data = AsByteSpan(content);                          \
-      stream::MemoryReader reader(data);                                       \
-      ASSERT_EQ(OkStatus(), client().Write(__LINE__, reader, OnCompletion())); \
-      ExpectWriteData(__LINE__, data);                                         \
-    }                                                                          \
-  }                                                                            \
-  static_assert(true, "Semicolons are required")
-
-PW_TRANSFER_TEST_WRITE(Empty, "");
-PW_TRANSFER_TEST_WRITE(SingleByte_1, "\0");
-PW_TRANSFER_TEST_WRITE(SingleByte_2, "?");
-PW_TRANSFER_TEST_WRITE(SmallData, "hunter2");
-PW_TRANSFER_TEST_WRITE(LargeData, kData512);
-PW_TRANSFER_TEST_WRITE(HdlcEscape, kDataHdlcEscape);
-PW_TRANSFER_TEST_WRITE(VeryLargeData, kData8192);
-
-}  // namespace
-}  // namespace pw::transfer
-
-int main(int argc, char* argv[]) {
-  if (!pw::rpc::integration_test::InitializeClient(argc, argv, "PORT DIRECTORY")
-           .ok()) {
-    return 1;
-  }
-
-  if (argc != 3) {
-    PW_LOG_INFO("Usage: %s PORT DIRECTORY", argv[0]);
-    return 1;
-  }
-
-  pw::transfer::directory = argv[2];
-
-  return RUN_ALL_TESTS();
-}
diff --git a/pw_transfer/public/pw_transfer/client.h b/pw_transfer/public/pw_transfer/client.h
deleted file mode 100644
index ae36223..0000000
--- a/pw_transfer/public/pw_transfer/client.h
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <array>
-
-#include "pw_function/function.h"
-#include "pw_status/status.h"
-#include "pw_stream/stream.h"
-#include "pw_transfer/internal/client_context.h"
-#include "pw_transfer/internal/config.h"
-#include "pw_transfer/transfer.raw_rpc.pb.h"
-#include "pw_transfer/transfer_thread.h"
-
-namespace pw::transfer {
-
-class Client {
- public:
-  using CompletionFunc = Function<void(Status)>;
-
-  // Initializes a transfer client on a specified RPC client and channel.
-  // Transfers are processed on a work queue so as not to block any RPC threads.
-  // The work queue does not have to be unique to the transfer client; it can be
-  // shared with other modules (including additional transfer clients).
-  //
-  // As data is processed within the work queue's context, the original RPC
-  // messages received by the transfer service are not available. Therefore,
-  // the transfer client requires an additional buffer where transfer data can
-  // stored during the context switch.
-  //
-  // The size of this buffer is the largest amount of bytes that can be sent
-  // within a single transfer chunk (read or write), excluding any transport
-  // layer overhead. Not all of this size is used to send data -- there is
-  // additional overhead in the pw_rpc and pw_transfer protocols (typically
-  // ~22B/chunk).
-  //
-  // An optional max_bytes_to_receive argument can be provided to set the
-  // default number of data bytes the client will request from the server at a
-  // time. If not provided, this defaults to the size of the data buffer. A
-  // larger value can make transfers more efficient as it minimizes the
-  // back-and-forth between client and server; however, it also increases the
-  // impact of packet loss, potentially requiring larger retransmissions to
-  // recover.
-  Client(rpc::Client& rpc_client,
-         uint32_t channel_id,
-         TransferThread& transfer_thread,
-         size_t max_bytes_to_receive = 0,
-         uint32_t extend_window_divisor = cfg::kDefaultExtendWindowDivisor)
-      : client_(rpc_client, channel_id),
-        transfer_thread_(transfer_thread),
-        max_parameters_(max_bytes_to_receive > 0
-                            ? max_bytes_to_receive
-                            : transfer_thread.max_chunk_size(),
-                        transfer_thread.max_chunk_size(),
-                        extend_window_divisor),
-        has_read_stream_(false),
-        has_write_stream_(false) {}
-
-  // Begins a new read transfer for the given transfer ID. The data read from
-  // the server is written to the provided writer. Returns OK if the transfer is
-  // successfully started. When the transfer finishes (successfully or not), the
-  // completion callback is invoked with the overall status.
-  Status Read(
-      uint32_t transfer_id,
-      stream::Writer& output,
-      CompletionFunc&& on_completion,
-      chrono::SystemClock::duration timeout = cfg::kDefaultChunkTimeout);
-
-  // Begins a new write transfer for the given transfer ID. Data from the
-  // provided reader is sent to the server. When the transfer finishes
-  // (successfully or not), the completion callback is invoked with the overall
-  // status.
-  Status Write(
-      uint32_t transfer_id,
-      stream::Reader& input,
-      CompletionFunc&& on_completion,
-      chrono::SystemClock::duration timeout = cfg::kDefaultChunkTimeout);
-
-  Status set_extend_window_divisor(uint32_t extend_window_divisor) {
-    if (extend_window_divisor <= 1) {
-      return Status::InvalidArgument();
-    }
-
-    max_parameters_.set_extend_window_divisor(extend_window_divisor);
-    return OkStatus();
-  }
-
- private:
-  using Transfer = pw_rpc::raw::Transfer;
-
-  void OnRpcError(Status status, internal::TransferType type);
-
-  Transfer::Client client_;
-  TransferThread& transfer_thread_;
-  internal::TransferParameters max_parameters_;
-
-  bool has_read_stream_;
-  bool has_write_stream_;
-};
-
-}  // namespace pw::transfer
diff --git a/pw_transfer/public/pw_transfer/handler.h b/pw_transfer/public/pw_transfer/handler.h
deleted file mode 100644
index 9680fb5..0000000
--- a/pw_transfer/public/pw_transfer/handler.h
+++ /dev/null
@@ -1,160 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_assert/assert.h"
-#include "pw_containers/intrusive_list.h"
-#include "pw_status/status.h"
-#include "pw_stream/stream.h"
-#include "pw_transfer/internal/event.h"
-
-namespace pw::transfer {
-namespace internal {
-
-// The internal::Handler class is the base class for the transfer handler
-// classes. Transfer handlers connect a transfer ID to the functions that do the
-// actual reads and/or writes.
-//
-// Handlers use a stream::Reader or stream::Writer to do the reads and writes.
-// They also provide optional Prepare and Finalize functions.
-class Handler : public IntrusiveList<Handler>::Item {
- public:
-  virtual ~Handler() = default;
-
-  constexpr uint32_t id() const { return transfer_id_; }
-
-  // Called at the beginning of a read transfer. The stream::Reader must be
-  // ready to read after a successful PrepareRead() call. Returning a non-OK
-  // status aborts the read.
-  //
-  // Status::Unimplemented() indicates that reads are not supported.
-  virtual Status PrepareRead() = 0;
-
-  // FinalizeRead() is called at the end of a read transfer. The status argument
-  // indicates whether the data transfer was successful or not.
-  virtual void FinalizeRead(Status) {}
-
-  // Called at the beginning of a write transfer. The stream::Writer must be
-  // ready to read after a successful PrepareRead() call. Returning a non-OK
-  // status aborts the write.
-  //
-  // Status::Unimplemented() indicates that writes are not supported.
-  virtual Status PrepareWrite() = 0;
-
-  // FinalizeWrite() is called at the end of a write transfer. The status
-  // argument indicates whether the data transfer was successful or not.
-  //
-  // Returning an error signals that the transfer failed, even if it had
-  // succeeded up to this point.
-  virtual Status FinalizeWrite(Status) { return OkStatus(); }
-
- protected:
-  constexpr Handler(uint32_t transfer_id, stream::Reader* reader)
-      : transfer_id_(transfer_id), reader_(reader) {}
-
-  constexpr Handler(uint32_t transfer_id, stream::Writer* writer)
-      : transfer_id_(transfer_id), writer_(writer) {}
-
-  void set_reader(stream::Reader& reader) { reader_ = &reader; }
-  void set_writer(stream::Writer& writer) { writer_ = &writer; }
-
- private:
-  friend class Context;
-
-  // Prepares for either a read or write transfer.
-  Status Prepare(internal::TransferType type) {
-    return type == internal::TransferType::kTransmit ? PrepareRead()
-                                                     : PrepareWrite();
-  }
-
-  // Only valid after a PrepareRead() or PrepareWrite() call that returns OK.
-  stream::Stream& stream() const {
-    PW_DASSERT(reader_ != nullptr);
-    return *reader_;
-  }
-
-  uint32_t transfer_id_;
-
-  // Use a union to support constexpr construction.
-  union {
-    stream::Reader* reader_;
-    stream::Writer* writer_;
-  };
-};
-
-}  // namespace internal
-
-class ReadOnlyHandler : public internal::Handler {
- public:
-  constexpr ReadOnlyHandler(uint32_t transfer_id)
-      : internal::Handler(transfer_id, static_cast<stream::Reader*>(nullptr)) {}
-
-  constexpr ReadOnlyHandler(uint32_t transfer_id, stream::Reader& reader)
-      : internal::Handler(transfer_id, &reader) {}
-
-  virtual ~ReadOnlyHandler() = default;
-
-  Status PrepareRead() override { return OkStatus(); }
-
-  // Writes are not supported.
-  Status PrepareWrite() final { return Status::PermissionDenied(); }
-
-  using internal::Handler::set_reader;
-
- private:
-  using internal::Handler::set_writer;
-};
-
-class WriteOnlyHandler : public internal::Handler {
- public:
-  constexpr WriteOnlyHandler(uint32_t transfer_id)
-      : internal::Handler(transfer_id, static_cast<stream::Writer*>(nullptr)) {}
-
-  constexpr WriteOnlyHandler(uint32_t transfer_id, stream::Writer& writer)
-      : internal::Handler(transfer_id, &writer) {}
-
-  virtual ~WriteOnlyHandler() = default;
-
-  // Reads are not supported.
-  Status PrepareRead() final { return Status::PermissionDenied(); }
-
-  Status PrepareWrite() override { return OkStatus(); }
-
-  using internal::Handler::set_writer;
-
- private:
-  using internal::Handler::set_reader;
-};
-
-class ReadWriteHandler : public internal::Handler {
- public:
-  constexpr ReadWriteHandler(uint32_t transfer_id)
-      : internal::Handler(transfer_id, static_cast<stream::Reader*>(nullptr)) {}
-  constexpr ReadWriteHandler(uint32_t transfer_id,
-                             stream::ReaderWriter& reader_writer)
-      : internal::Handler(transfer_id,
-                          &static_cast<stream::Reader&>(reader_writer)) {}
-
-  virtual ~ReadWriteHandler() = default;
-
-  // Both reads and writes are supported.
-  Status PrepareRead() override { return OkStatus(); }
-  Status PrepareWrite() override { return OkStatus(); }
-
-  void set_reader_writer(stream::ReaderWriter& reader_writer) {
-    set_reader(reader_writer);
-  }
-};
-
-}  // namespace pw::transfer
diff --git a/pw_transfer/public/pw_transfer/internal/chunk.h b/pw_transfer/public/pw_transfer/internal/chunk.h
deleted file mode 100644
index b20acf4..0000000
--- a/pw_transfer/public/pw_transfer/internal/chunk.h
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_bytes/span.h"
-#include "pw_result/result.h"
-
-namespace pw::transfer::internal {
-
-struct Chunk {
-  // TODO(frolv): This is copied from the proto enum. Ideally, this entire class
-  // would be generated by pw_protobuf.
-  enum Type {
-    kTransferData = 0,
-    kTransferStart = 1,
-    kParametersRetransmit = 2,
-    kParametersContinue = 3,
-    kTransferCompletion = 4,
-    kTransferCompletionAck = 5,  // Currently unused.
-  };
-
-  // The initial chunk always has an offset of 0 and no data or status.
-  //
-  // TODO(frolv): Going forward, all users of transfer should set a type for
-  // all chunks. This initial chunk assumption should be removed.
-  constexpr bool IsInitialChunk() const {
-    return type == Type::kTransferStart ||
-           (offset == 0 && data.empty() && !status.has_value());
-  }
-
-  // The final chunk from the transmitter sets remaining_bytes to 0 in both Read
-  // and Write transfers.
-  constexpr bool IsFinalTransmitChunk() const { return remaining_bytes == 0u; }
-
-  uint32_t transfer_id;
-  uint32_t window_end_offset;
-  std::optional<uint32_t> pending_bytes;
-  std::optional<uint32_t> max_chunk_size_bytes;
-  std::optional<uint32_t> min_delay_microseconds;
-  uint32_t offset;
-  ConstByteSpan data;
-  std::optional<uint64_t> remaining_bytes;
-  std::optional<Status> status;
-  std::optional<Type> type;
-};
-
-// Partially decodes a transfer chunk to find its transfer ID field.
-Result<uint32_t> ExtractTransferId(ConstByteSpan message);
-
-Status DecodeChunk(ConstByteSpan message, Chunk& chunk);
-Result<ConstByteSpan> EncodeChunk(const Chunk& chunk, ByteSpan buffer);
-
-}  // namespace pw::transfer::internal
diff --git a/pw_transfer/public/pw_transfer/internal/client_context.h b/pw_transfer/public/pw_transfer/internal/client_context.h
deleted file mode 100644
index a84af31..0000000
--- a/pw_transfer/public/pw_transfer/internal/client_context.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_function/function.h"
-#include "pw_rpc/raw/client_reader_writer.h"
-#include "pw_transfer/internal/context.h"
-
-namespace pw::transfer::internal {
-
-class ClientContext final : public Context {
- public:
-  constexpr ClientContext() : on_completion_(nullptr) {}
-
-  void set_on_completion(Function<void(Status)>&& on_completion) {
-    on_completion_ = std::move(on_completion);
-  }
-
- private:
-  Status FinalCleanup(Status status) override;
-
-  Function<void(Status)> on_completion_;
-};
-
-}  // namespace pw::transfer::internal
diff --git a/pw_transfer/public/pw_transfer/internal/config.h b/pw_transfer/public/pw_transfer/internal/config.h
deleted file mode 100644
index f144ac2..0000000
--- a/pw_transfer/public/pw_transfer/internal/config.h
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// Configuration macros for the transfer module.
-#pragma once
-
-#include <cinttypes>
-#include <limits>
-
-#include "pw_chrono/system_clock.h"
-
-// The default maximum number of times a transfer should retry sending a chunk
-// when no response is received. This can later be configured per-transfer.
-#ifndef PW_TRANSFER_DEFAULT_MAX_RETRIES
-#define PW_TRANSFER_DEFAULT_MAX_RETRIES 3
-#endif  // PW_TRANSFER_DEFAULT_MAX_RETRIES
-
-static_assert(PW_TRANSFER_DEFAULT_MAX_RETRIES > 0 &&
-              PW_TRANSFER_DEFAULT_MAX_RETRIES <=
-                  std::numeric_limits<uint8_t>::max());
-
-// The default amount of time, in milliseconds, to wait for a chunk to arrive
-// before retrying. This can later be configured per-transfer.
-#ifndef PW_TRANSFER_DEFAULT_TIMEOUT_MS
-#define PW_TRANSFER_DEFAULT_TIMEOUT_MS 2000
-#endif  // PW_TRANSFER_DEFAULT_TIMEOUT_MS
-
-static_assert(PW_TRANSFER_DEFAULT_TIMEOUT_MS > 0);
-
-// The fractional position within a window at which a receive transfer should
-// extend its window size to minimize the amount of time the transmitter
-// spends blocked.
-//
-// For example, a divisor of 2 will extend the window when half of the
-// requested data has been received, a divisor of three will extend at a third
-// of the window, and so on.
-#ifndef PW_TRANSFER_DEFAULT_EXTEND_WINDOW_DIVISOR
-#define PW_TRANSFER_DEFAULT_EXTEND_WINDOW_DIVISOR 2
-#endif  // PW_TRANSFER_DEFAULT_EXTEND_WINDOW_DIVISOR
-
-static_assert(PW_TRANSFER_DEFAULT_EXTEND_WINDOW_DIVISOR > 1);
-
-namespace pw::transfer::cfg {
-
-inline constexpr uint8_t kDefaultMaxRetries = PW_TRANSFER_DEFAULT_MAX_RETRIES;
-inline constexpr chrono::SystemClock::duration kDefaultChunkTimeout =
-    std::chrono::milliseconds(PW_TRANSFER_DEFAULT_TIMEOUT_MS);
-inline constexpr uint32_t kDefaultExtendWindowDivisor =
-    PW_TRANSFER_DEFAULT_EXTEND_WINDOW_DIVISOR;
-
-}  // namespace pw::transfer::cfg
diff --git a/pw_transfer/public/pw_transfer/internal/context.h b/pw_transfer/public/pw_transfer/internal/context.h
deleted file mode 100644
index f825419..0000000
--- a/pw_transfer/public/pw_transfer/internal/context.h
+++ /dev/null
@@ -1,322 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cinttypes>
-#include <cstddef>
-#include <limits>
-#include <optional>
-
-#include "pw_assert/assert.h"
-#include "pw_chrono/system_clock.h"
-#include "pw_rpc/writer.h"
-#include "pw_status/status.h"
-#include "pw_stream/stream.h"
-#include "pw_transfer/internal/chunk.h"
-#include "pw_transfer/internal/event.h"
-#include "pw_transfer/rate_estimate.h"
-
-namespace pw::transfer::internal {
-
-class TransferThread;
-
-class TransferParameters {
- public:
-  constexpr TransferParameters(uint32_t pending_bytes,
-                               uint32_t max_chunk_size_bytes,
-                               uint32_t extend_window_divisor)
-      : pending_bytes_(pending_bytes),
-        max_chunk_size_bytes_(max_chunk_size_bytes),
-        extend_window_divisor_(extend_window_divisor) {
-    PW_ASSERT(pending_bytes > 0);
-    PW_ASSERT(max_chunk_size_bytes > 0);
-    PW_ASSERT(extend_window_divisor > 1);
-  }
-
-  uint32_t pending_bytes() const { return pending_bytes_; }
-  void set_pending_bytes(uint32_t pending_bytes) {
-    pending_bytes_ = pending_bytes;
-  }
-
-  uint32_t max_chunk_size_bytes() const { return max_chunk_size_bytes_; }
-  void set_max_chunk_size_bytes(uint32_t max_chunk_size_bytes) {
-    max_chunk_size_bytes_ = max_chunk_size_bytes;
-  }
-
-  uint32_t extend_window_divisor() const { return extend_window_divisor_; }
-  void set_extend_window_divisor(uint32_t extend_window_divisor) {
-    PW_DASSERT(extend_window_divisor > 1);
-    extend_window_divisor_ = extend_window_divisor;
-  }
-
- private:
-  uint32_t pending_bytes_;
-  uint32_t max_chunk_size_bytes_;
-  uint32_t extend_window_divisor_;
-};
-
-// Information about a single transfer.
-class Context {
- public:
-  Context(const Context&) = delete;
-  Context(Context&&) = delete;
-  Context& operator=(const Context&) = delete;
-  Context& operator=(Context&&) = delete;
-
-  constexpr uint32_t transfer_id() const { return transfer_id_; }
-
-  // True if the context has been used for a transfer (it has an ID).
-  bool initialized() const {
-    return transfer_state_ != TransferState::kInactive;
-  }
-
-  // True if the transfer is active.
-  bool active() const { return transfer_state_ >= TransferState::kWaiting; }
-
-  std::optional<chrono::SystemClock::time_point> timeout() const {
-    return active() && next_timeout_ != kNoTimeout
-               ? std::optional(next_timeout_)
-               : std::nullopt;
-  }
-
-  // Returns true if the transfer's most recently set timeout has passed.
-  bool timed_out() const {
-    std::optional<chrono::SystemClock::time_point> next_timeout = timeout();
-    return next_timeout.has_value() &&
-           chrono::SystemClock::now() >= next_timeout.value();
-  }
-
-  // Processes an event for this transfer.
-  void HandleEvent(const Event& event);
-
- protected:
-  ~Context() = default;
-
-  constexpr Context()
-      : transfer_id_(0),
-        flags_(0),
-        transfer_state_(TransferState::kInactive),
-        retries_(0),
-        max_retries_(0),
-        stream_(nullptr),
-        rpc_writer_(nullptr),
-        offset_(0),
-        window_size_(0),
-        window_end_offset_(0),
-        pending_bytes_(0),
-        max_chunk_size_bytes_(std::numeric_limits<uint32_t>::max()),
-        max_parameters_(nullptr),
-        thread_(nullptr),
-        last_chunk_offset_(0),
-        chunk_timeout_(chrono::SystemClock::duration::zero()),
-        interchunk_delay_(chrono::SystemClock::for_at_least(
-            std::chrono::microseconds(kDefaultChunkDelayMicroseconds))),
-        next_timeout_(kNoTimeout) {}
-
-  constexpr TransferType type() const {
-    return static_cast<TransferType>(flags_ & kFlagsType);
-  }
-
- private:
-  enum class TransferState : uint8_t {
-    // This ServerContext has never been used for a transfer. It is available
-    // for use for a transfer.
-    kInactive,
-    // A transfer completed and the final status chunk was sent. The Context
-    // is
-    // available for use for a new transfer. A receive transfer uses this
-    // state
-    // to allow a transmitter to retry its last chunk if the final status
-    // chunk
-    // was dropped.
-    kCompleted,
-    // Waiting for the other end to send a chunk.
-    kWaiting,
-    // Transmitting a window of data to a receiver.
-    kTransmitting,
-    // Recovering after one or more chunks was dropped in an active transfer.
-    kRecovery,
-  };
-
-  enum class TransmitAction {
-    // Start of a new transfer.
-    kBegin,
-    // Extend the current window length.
-    kExtend,
-    // Retransmit from a specified offset.
-    kRetransmit,
-  };
-
-  void set_transfer_state(TransferState state) { transfer_state_ = state; }
-
-  // The transfer ID as unsigned instead of uint32_t so it can be used with %u.
-  unsigned id_for_log() const {
-    static_assert(sizeof(unsigned) >= sizeof(transfer_id_));
-    return static_cast<unsigned>(transfer_id_);
-  }
-
-  stream::Reader& reader() {
-    PW_DASSERT(active() && type() == TransferType::kTransmit);
-    return static_cast<stream::Reader&>(*stream_);
-  }
-
-  stream::Writer& writer() {
-    PW_DASSERT(active() && type() == TransferType::kReceive);
-    return static_cast<stream::Writer&>(*stream_);
-  }
-
-  // Calculates the maximum size of actual data that can be sent within a
-  // single client write transfer chunk, accounting for the overhead of the
-  // transfer protocol and RPC system.
-  //
-  // Note: This function relies on RPC protocol internals. This is generally a
-  // *bad* idea, but is necessary here due to limitations of the RPC system
-  // and its asymmetric ingress and egress paths.
-  //
-  // TODO(frolv): This should be investigated further and perhaps addressed
-  // within the RPC system, at the least through a helper function.
-  uint32_t MaxWriteChunkSize(uint32_t max_chunk_size_bytes,
-                             uint32_t channel_id) const;
-
-  // Initializes a new transfer using new_transfer. The provided stream
-  // argument is used in place of the NewTransferEvent's stream. Only
-  // initializes state; no packets are sent.
-  //
-  // Precondition: context is not active.
-  void Initialize(const NewTransferEvent& new_transfer);
-
-  // Starts a new transfer from an initialized context by sending the initial
-  // transfer chunk. This is only used by transfer clients, as the transfer
-  // service cannot initiate transfers.
-  //
-  // Calls Finish(), which calls the on_completion callback, if initiating a
-  // transfer fails.
-  void InitiateTransferAsClient();
-
-  // Starts a new transfer on the server after receiving a request from a
-  // client.
-  void StartTransferAsServer(const NewTransferEvent& new_transfer);
-
-  // Does final cleanup specific to the server or client. Returns whether the
-  // cleanup succeeded. An error in cleanup indicates that the transfer
-  // failed.
-  virtual Status FinalCleanup(Status status) = 0;
-
-  // Processes a chunk in either a transfer or receive transfer.
-  void HandleChunkEvent(const ChunkEvent& event);
-
-  // Processes a chunk in a transmit transfer.
-  void HandleTransmitChunk(const Chunk& chunk);
-
-  // Processes a transfer parameters update in a transmit transfer.
-  void HandleTransferParametersUpdate(const Chunk& chunk);
-
-  // Sends the next chunk in a transmit transfer, if any.
-  void TransmitNextChunk(bool retransmit_requested);
-
-  // Processes a chunk in a receive transfer.
-  void HandleReceiveChunk(const Chunk& chunk);
-
-  // Processes a data chunk in a received while in the kWaiting state.
-  void HandleReceivedData(const Chunk& chunk);
-
-  // Sends the first chunk in a transmit transfer.
-  void SendInitialTransmitChunk();
-
-  // In a receive transfer, sends a parameters chunk telling the transmitter
-  // how much data they can send.
-  void SendTransferParameters(TransmitAction action);
-
-  // Updates the current receive transfer parameters from the provided object,
-  // then sends them.
-  void UpdateAndSendTransferParameters(TransmitAction action);
-
-  // Sends a final status chunk of a completed transfer without updating the
-  // the transfer. Sends status_, which MUST have been set by a previous
-  // Finish() call.
-  void SendFinalStatusChunk();
-
-  // Marks the transfer as completed and calls FinalCleanup(). Sets status_ to
-  // the final status for this transfer. The transfer MUST be active when this
-  // is called.
-  void Finish(Status status);
-
-  // Encodes the specified chunk to the encode buffer and sends it with the
-  // rpc_writer_. Calls Finish() with an error if the operation fails.
-  void EncodeAndSendChunk(const Chunk& chunk);
-
-  void SetTimeout(chrono::SystemClock::duration timeout);
-  void ClearTimeout() { next_timeout_ = kNoTimeout; }
-
-  // Called when the transfer's timeout expires.
-  void HandleTimeout();
-
-  // Resends the last packet or aborts the transfer if the maximum retries has
-  // been exceeded.
-  void Retry();
-
-  static constexpr uint8_t kFlagsType = 1 << 0;
-  static constexpr uint8_t kFlagsDataSent = 1 << 1;
-
-  static constexpr uint32_t kDefaultChunkDelayMicroseconds = 2000;
-
-  // How long to wait for the other side to ACK a final transfer chunk before
-  // resetting the context so that it can be reused. During this time, the
-  // status chunk will be re-sent for every non-ACK chunk received,
-  // continually notifying the other end that the transfer is over.
-  static constexpr chrono::SystemClock::duration kFinalChunkAckTimeout =
-      std::chrono::milliseconds(5000);
-
-  static constexpr chrono::SystemClock::time_point kNoTimeout =
-      chrono::SystemClock::time_point(chrono::SystemClock::duration(0));
-
-  uint32_t transfer_id_;
-  uint8_t flags_;
-  TransferState transfer_state_;
-  uint8_t retries_;
-  uint8_t max_retries_;
-
-  // The stream from which to read or to which to write data.
-  stream::Stream* stream_;
-  rpc::Writer* rpc_writer_;
-
-  uint32_t offset_;
-  uint32_t window_size_;
-  uint32_t window_end_offset_;
-  // TODO(pwbug/584): Remove pending_bytes in favor of window_end_offset.
-  uint32_t pending_bytes_;
-  uint32_t max_chunk_size_bytes_;
-
-  const TransferParameters* max_parameters_;
-  TransferThread* thread_;
-
-  union {
-    Status status_;               // Used when state is kCompleted.
-    uint32_t last_chunk_offset_;  // Used in states kWaiting and kRecovery.
-  };
-
-  // How long to wait for a chunk from the other end.
-  chrono::SystemClock::duration chunk_timeout_;
-
-  // How long to delay between transmitting subsequent data chunks within a
-  // window.
-  chrono::SystemClock::duration interchunk_delay_;
-
-  // Timestamp at which the transfer will next time out, or kNoTimeout.
-  chrono::SystemClock::time_point next_timeout_;
-
-  RateEstimate transfer_rate_;
-};
-
-}  // namespace pw::transfer::internal
diff --git a/pw_transfer/public/pw_transfer/internal/event.h b/pw_transfer/public/pw_transfer/internal/event.h
deleted file mode 100644
index b4cc6ba..0000000
--- a/pw_transfer/public/pw_transfer/internal/event.h
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_chrono/system_clock.h"
-#include "pw_rpc/writer.h"
-#include "pw_stream/stream.h"
-
-namespace pw::transfer::internal {
-
-enum class TransferType : bool { kTransmit, kReceive };
-
-enum class TransferStream {
-  kClientRead,
-  kClientWrite,
-  kServerRead,
-  kServerWrite,
-};
-
-enum class EventType {
-  // Begins a new transfer in an available context.
-  kNewClientTransfer,
-  kNewServerTransfer,
-
-  // Processes an incoming chunk for a transfer.
-  kClientChunk,
-  kServerChunk,
-
-  // Runs the timeout handler for a transfer.
-  kClientTimeout,
-  kServerTimeout,
-
-  // Sends a status chunk to terminate a transfer. This does not call into the
-  // transfer context's completion handler; it is for out-of-band termination.
-  kSendStatusChunk,
-
-  // Updates one of the transfer thread's RPC streams.
-  kSetTransferStream,
-
-  // Manages the list of transfer handlers for a transfer service.
-  kAddTransferHandler,
-  kRemoveTransferHandler,
-
-  // For testing only: aborts the transfer thread.
-  kTerminate,
-};
-
-// Forward declarations required for events.
-class Handler;
-class TransferParameters;
-class TransferThread;
-
-struct NewTransferEvent {
-  TransferType type;
-  uint32_t transfer_id;
-  uint32_t handler_id;
-  rpc::Writer* rpc_writer;
-  const TransferParameters* max_parameters;
-  chrono::SystemClock::duration timeout;
-  uint32_t max_retries;
-  TransferThread* transfer_thread;
-
-  union {
-    stream::Stream* stream;  // In client-side transfers.
-    Handler* handler;        // In server-side transfers.
-  };
-};
-
-struct ChunkEvent {
-  uint32_t transfer_id;
-  const std::byte* data;
-  size_t size;
-};
-
-struct SendStatusChunkEvent {
-  uint32_t transfer_id;
-  Status::Code status;
-  TransferStream stream;
-};
-
-struct Event {
-  EventType type;
-
-  union {
-    NewTransferEvent new_transfer;
-    ChunkEvent chunk;
-    SendStatusChunkEvent send_status_chunk;
-    TransferStream set_transfer_stream;
-    Handler* add_transfer_handler;
-    Handler* remove_transfer_handler;
-  };
-};
-
-}  // namespace pw::transfer::internal
diff --git a/pw_transfer/public/pw_transfer/internal/server_context.h b/pw_transfer/public/pw_transfer/internal/server_context.h
deleted file mode 100644
index 038feec..0000000
--- a/pw_transfer/public/pw_transfer/internal/server_context.h
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_assert/assert.h"
-#include "pw_containers/intrusive_list.h"
-#include "pw_result/result.h"
-#include "pw_rpc/raw/server_reader_writer.h"
-#include "pw_transfer/handler.h"
-#include "pw_transfer/internal/context.h"
-
-namespace pw::transfer::internal {
-
-// Transfer context for use within the transfer service (server-side). Stores a
-// pointer to a transfer handler when active to stream the transfer data.
-class ServerContext final : public Context {
- public:
-  constexpr ServerContext() : handler_(nullptr) {}
-
-  // Sets the handler. The handler isn't set by Context::Initialize() since
-  // ClientContexts don't track it.
-  void set_handler(Handler& handler) { handler_ = &handler; }
-
- private:
-  // Ends the transfer with the given status, calling the handler's Finalize
-  // method. No chunks are sent.
-  //
-  // Returns DATA_LOSS if the finalize call fails.
-  //
-  // Precondition: Transfer context is active.
-  Status FinalCleanup(Status status) override;
-
-  Handler* handler_;
-};
-
-}  // namespace pw::transfer::internal
diff --git a/pw_transfer/public/pw_transfer/rate_estimate.h b/pw_transfer/public/pw_transfer/rate_estimate.h
deleted file mode 100644
index 9832397..0000000
--- a/pw_transfer/public/pw_transfer/rate_estimate.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <chrono>
-#include <cstdint>
-#include <optional>
-
-#include "pw_chrono/system_clock.h"
-
-namespace pw::transfer {
-
-class RateEstimate {
- public:
-  constexpr RateEstimate() : start_time_(std::nullopt), bytes_transferred_(0) {}
-
-  void Reset() {
-    start_time_ = chrono::SystemClock::now();
-    bytes_transferred_ = 0;
-  }
-
-  constexpr void Update(size_t new_bytes) { bytes_transferred_ += new_bytes; }
-
-  size_t GetRateBytesPerSecond() const;
-
- private:
-  std::optional<chrono::SystemClock::time_point> start_time_;
-  size_t bytes_transferred_;
-};
-
-}  // namespace pw::transfer
diff --git a/pw_transfer/public/pw_transfer/transfer.h b/pw_transfer/public/pw_transfer/transfer.h
deleted file mode 100644
index f6e71c3..0000000
--- a/pw_transfer/public/pw_transfer/transfer.h
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstddef>
-#include <cstdint>
-#include <limits>
-
-#include "pw_bytes/span.h"
-#include "pw_transfer/handler.h"
-#include "pw_transfer/internal/config.h"
-#include "pw_transfer/internal/server_context.h"
-#include "pw_transfer/transfer.raw_rpc.pb.h"
-#include "pw_transfer/transfer_thread.h"
-
-namespace pw::transfer {
-namespace internal {
-
-struct Chunk;
-
-}  // namespace internal
-
-class TransferService : public pw_rpc::raw::Transfer::Service<TransferService> {
- public:
-  // Initializes a TransferService that can be registered with an RPC server.
-  //
-  // The transfer service requires a work queue to perform deferred tasks, such
-  // as handling transfer timeouts and retries. This work queue does not need to
-  // be unique to the transfer service; it may be shared with other parts of the
-  // system.
-  //
-  // The provided buffer is used to stage data from transfer chunks before it is
-  // written out to the writer. The size of this buffer is the largest amount of
-  // data that can be sent in a single transfer chunk, excluding any transport
-  // layer overhead.
-  //
-  // max_pending_bytes is the maximum amount of data to ask for at a
-  // time during a write transfer, unless told a more restrictive amount by a
-  // transfer handler. This size can span multiple chunks. A larger value
-  // generally increases the efficiency of write transfers when sent over a
-  // reliable transport. However, if the underlying transport is unreliable,
-  // larger values could slow down a transfer in the event of repeated packet
-  // loss.
-  TransferService(
-      TransferThread& transfer_thread,
-      uint32_t max_pending_bytes,
-      chrono::SystemClock::duration chunk_timeout = cfg::kDefaultChunkTimeout,
-      uint8_t max_retries = cfg::kDefaultMaxRetries,
-      uint32_t extend_window_divisor = cfg::kDefaultExtendWindowDivisor)
-      : max_parameters_(max_pending_bytes,
-                        transfer_thread.max_chunk_size(),
-                        extend_window_divisor),
-        thread_(transfer_thread),
-        chunk_timeout_(chunk_timeout),
-        max_retries_(max_retries) {}
-
-  TransferService(const TransferService&) = delete;
-  TransferService(TransferService&&) = delete;
-
-  TransferService& operator=(const TransferService&) = delete;
-  TransferService& operator=(TransferService&&) = delete;
-
-  void Read(RawServerReaderWriter& reader_writer) {
-    reader_writer.set_on_next([this](ConstByteSpan message) {
-      HandleChunk(message, internal::TransferType::kTransmit);
-    });
-    thread_.SetServerReadStream(reader_writer);
-  }
-
-  void Write(RawServerReaderWriter& reader_writer) {
-    reader_writer.set_on_next([this](ConstByteSpan message) {
-      HandleChunk(message, internal::TransferType::kReceive);
-    });
-    thread_.SetServerWriteStream(reader_writer);
-  }
-
-  void RegisterHandler(internal::Handler& handler) {
-    thread_.AddTransferHandler(handler);
-  }
-
-  void set_max_pending_bytes(uint32_t max_pending_bytes) {
-    max_parameters_.set_pending_bytes(max_pending_bytes);
-  }
-
-  // Sets the maximum size for the data in a pw_transfer chunk. Note that the
-  // max chunk size must always fit within the transfer thread's chunk buffer.
-  void set_max_chunk_size_bytes(uint32_t max_chunk_size_bytes) {
-    max_parameters_.set_max_chunk_size_bytes(max_chunk_size_bytes);
-  }
-
-  void UnregisterHandler(internal::Handler& handler) {
-    thread_.RemoveTransferHandler(handler);
-  }
-
-  void set_chunk_timeout(chrono::SystemClock::duration chunk_timeout) {
-    chunk_timeout_ = chunk_timeout;
-  }
-
-  void set_max_retries(uint8_t max_retries) { max_retries_ = max_retries; }
-
-  Status set_extend_window_divisor(uint32_t extend_window_divisor) {
-    if (extend_window_divisor <= 1) {
-      return Status::InvalidArgument();
-    }
-
-    max_parameters_.set_extend_window_divisor(extend_window_divisor);
-    return OkStatus();
-  }
-
- private:
-  void HandleChunk(ConstByteSpan message, internal::TransferType type);
-
-  internal::TransferParameters max_parameters_;
-  TransferThread& thread_;
-
-  chrono::SystemClock::duration chunk_timeout_;
-  uint8_t max_retries_;
-};
-
-}  // namespace pw::transfer
diff --git a/pw_transfer/public/pw_transfer/transfer_thread.h b/pw_transfer/public/pw_transfer/transfer_thread.h
deleted file mode 100644
index 9e21bba..0000000
--- a/pw_transfer/public/pw_transfer/transfer_thread.h
+++ /dev/null
@@ -1,270 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <span>
-
-#include "pw_assert/assert.h"
-#include "pw_chrono/system_clock.h"
-#include "pw_function/function.h"
-#include "pw_preprocessor/compiler.h"
-#include "pw_rpc/raw/client_reader_writer.h"
-#include "pw_rpc/raw/server_reader_writer.h"
-#include "pw_sync/binary_semaphore.h"
-#include "pw_sync/timed_thread_notification.h"
-#include "pw_thread/thread_core.h"
-#include "pw_transfer/handler.h"
-#include "pw_transfer/internal/client_context.h"
-#include "pw_transfer/internal/context.h"
-#include "pw_transfer/internal/event.h"
-#include "pw_transfer/internal/server_context.h"
-
-namespace pw::transfer {
-namespace internal {
-
-class TransferThread : public thread::ThreadCore {
- public:
-  TransferThread(std::span<ClientContext> client_transfers,
-                 std::span<ServerContext> server_transfers,
-                 ByteSpan chunk_buffer,
-                 ByteSpan encode_buffer)
-      : client_transfers_(client_transfers),
-        server_transfers_(server_transfers),
-        chunk_buffer_(chunk_buffer),
-        encode_buffer_(encode_buffer) {}
-
-  void StartClientTransfer(TransferType type,
-                           uint32_t transfer_id,
-                           uint32_t handler_id,
-                           stream::Stream* stream,
-                           const TransferParameters& max_parameters,
-                           Function<void(Status)>&& on_completion,
-                           chrono::SystemClock::duration timeout,
-                           uint8_t max_retries) {
-    StartTransfer(type,
-                  transfer_id,
-                  handler_id,
-                  stream,
-                  max_parameters,
-                  std::move(on_completion),
-                  timeout,
-                  max_retries);
-  }
-
-  void StartServerTransfer(TransferType type,
-                           uint32_t transfer_id,
-                           uint32_t handler_id,
-                           const TransferParameters& max_parameters,
-                           chrono::SystemClock::duration timeout,
-                           uint8_t max_retries) {
-    StartTransfer(type,
-                  transfer_id,
-                  handler_id,
-                  /*stream=*/nullptr,
-                  max_parameters,
-                  /*on_completion=*/nullptr,
-                  timeout,
-                  max_retries);
-  }
-
-  void ProcessClientChunk(ConstByteSpan chunk) {
-    ProcessChunk(EventType::kClientChunk, chunk);
-  }
-
-  void ProcessServerChunk(ConstByteSpan chunk) {
-    ProcessChunk(EventType::kServerChunk, chunk);
-  }
-
-  void SetClientReadStream(rpc::RawClientReaderWriter& read_stream) {
-    SetClientStream(TransferStream::kClientRead, read_stream);
-  }
-
-  void SetClientWriteStream(rpc::RawClientReaderWriter& write_stream) {
-    SetClientStream(TransferStream::kClientWrite, write_stream);
-  }
-
-  void SetServerReadStream(rpc::RawServerReaderWriter& read_stream) {
-    SetServerStream(TransferStream::kServerRead, read_stream);
-  }
-
-  void SetServerWriteStream(rpc::RawServerReaderWriter& write_stream) {
-    SetServerStream(TransferStream::kServerWrite, write_stream);
-  }
-
-  void AddTransferHandler(Handler& handler) {
-    TransferHandlerEvent(EventType::kAddTransferHandler, handler);
-  }
-
-  void RemoveTransferHandler(Handler& handler) {
-    TransferHandlerEvent(EventType::kRemoveTransferHandler, handler);
-  }
-
-  size_t max_chunk_size() const { return chunk_buffer_.size(); }
-
-  // For testing only: terminates the transfer thread with a kTerminate event.
-  void Terminate();
-
-  // For testing only: blocks until the next event can be acquired, which means
-  // a previously enqueued event has been processed.
-  void WaitUntilEventIsProcessed() {
-    next_event_ownership_.acquire();
-    next_event_ownership_.release();
-  }
-
-  // For testing only: simulates a timeout event for a client transfer.
-  void SimulateClientTimeout(uint32_t transfer_id) {
-    SimulateTimeout(EventType::kClientTimeout, transfer_id);
-  }
-
-  // For testing only: simulates a timeout event for a server transfer.
-  void SimulateServerTimeout(uint32_t transfer_id) {
-    SimulateTimeout(EventType::kServerTimeout, transfer_id);
-  }
-
- private:
-  friend class Context;
-
-  // Maximum amount of time between transfer thread runs.
-  static constexpr chrono::SystemClock::duration kMaxTimeout =
-      std::chrono::seconds(2);
-
-  // Finds an active server or client transfer.
-  template <typename T>
-  static Context* FindActiveTransfer(const std::span<T>& transfers,
-                                     uint32_t transfer_id) {
-    auto transfer = std::find_if(
-        transfers.begin(), transfers.end(), [transfer_id](auto& c) {
-          return c.initialized() && c.transfer_id() == transfer_id;
-        });
-    return transfer != transfers.end() ? &*transfer : nullptr;
-  }
-
-  void SimulateTimeout(EventType type, uint32_t transfer_id);
-
-  // Finds an new server or client transfer.
-  template <typename T>
-  static Context* FindNewTransfer(const std::span<T>& transfers,
-                                  uint32_t transfer_id) {
-    Context* new_transfer = nullptr;
-
-    for (Context& context : transfers) {
-      if (context.active()) {
-        if (context.transfer_id() == transfer_id) {
-          // Restart an already active transfer.
-          return &context;
-        }
-      } else {
-        // Store the inactive context as an option, but keep checking for the
-        // restart case.
-        new_transfer = &context;
-      }
-    }
-
-    return new_transfer;
-  }
-
-  const ByteSpan& encode_buffer() const { return encode_buffer_; }
-
-  void Run() final;
-
-  void HandleTimeouts();
-
-  rpc::Writer& stream_for(TransferStream stream) {
-    switch (stream) {
-      case TransferStream::kClientRead:
-        return client_read_stream_;
-      case TransferStream::kClientWrite:
-        return client_write_stream_;
-      case TransferStream::kServerRead:
-        return server_read_stream_;
-      case TransferStream::kServerWrite:
-        return server_write_stream_;
-    }
-    // An unknown TransferStream value was passed, which means this function
-    // was passed an invalid enum value.
-    PW_ASSERT(false);
-  }
-
-  // Returns the earliest timeout among all active transfers, up to kMaxTimeout.
-  chrono::SystemClock::time_point GetNextTransferTimeout() const;
-
-  void StartTransfer(TransferType type,
-                     uint32_t transfer_id,
-                     uint32_t handler_id,
-                     stream::Stream* stream,
-                     const TransferParameters& max_parameters,
-                     Function<void(Status)>&& on_completion,
-                     chrono::SystemClock::duration timeout,
-                     uint8_t max_retries);
-
-  void ProcessChunk(EventType type, ConstByteSpan chunk);
-
-  void SetClientStream(TransferStream type, rpc::RawClientReaderWriter& stream);
-  void SetServerStream(TransferStream type, rpc::RawServerReaderWriter& stream);
-
-  void TransferHandlerEvent(EventType type, Handler& handler);
-
-  void HandleEvent(const Event& event);
-  Context* FindContextForEvent(const Event& event) const;
-
-  void SendStatusChunk(const SendStatusChunkEvent& event);
-
-  sync::TimedThreadNotification event_notification_;
-  sync::BinarySemaphore next_event_ownership_;
-
-  Event next_event_;
-  Function<void(Status)> staged_on_completion_;
-  rpc::RawClientReaderWriter staged_client_stream_;
-  rpc::RawServerReaderWriter staged_server_stream_;
-
-  rpc::RawClientReaderWriter client_read_stream_;
-  rpc::RawClientReaderWriter client_write_stream_;
-  rpc::RawServerReaderWriter server_read_stream_;
-  rpc::RawServerReaderWriter server_write_stream_;
-
-  std::span<ClientContext> client_transfers_;
-  std::span<ServerContext> server_transfers_;
-
-  // All registered transfer handlers.
-  IntrusiveList<Handler> handlers_;
-
-  // Buffer in which chunk data is staged for CHUNK events.
-  ByteSpan chunk_buffer_;
-
-  // Buffer into which responses are encoded. Only ever used from within the
-  // transfer thread, so no locking is required.
-  ByteSpan encode_buffer_;
-};
-
-}  // namespace internal
-
-using TransferThread = internal::TransferThread;
-
-template <size_t kMaxConcurrentClientTransfers,
-          size_t kMaxConcurrentServerTransfers>
-class Thread final : public internal::TransferThread {
- public:
-  Thread(ByteSpan chunk_buffer, ByteSpan encode_buffer)
-      : internal::TransferThread(
-            client_contexts_, server_contexts_, chunk_buffer, encode_buffer) {}
-
- private:
-  std::array<internal::ClientContext, kMaxConcurrentClientTransfers>
-      client_contexts_;
-  std::array<internal::ServerContext, kMaxConcurrentServerTransfers>
-      server_contexts_;
-};
-
-}  // namespace pw::transfer
diff --git a/pw_transfer/pw_transfer_private/chunk_testing.h b/pw_transfer/pw_transfer_private/chunk_testing.h
deleted file mode 100644
index b94d0d4..0000000
--- a/pw_transfer/pw_transfer_private/chunk_testing.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_bytes/span.h"
-#include "pw_containers/vector.h"
-#include "pw_transfer/internal/chunk.h"
-
-namespace pw::transfer::test {
-
-Vector<std::byte, 64> EncodeChunk(const internal::Chunk& chunk) {
-  Vector<std::byte, 64> buffer(64);
-  auto result = internal::EncodeChunk(chunk, buffer);
-  EXPECT_EQ(result.status(), OkStatus());
-  buffer.resize(result.value().size());
-  return buffer;
-}
-
-internal::Chunk DecodeChunk(ConstByteSpan buffer) {
-  internal::Chunk chunk = {};
-  EXPECT_EQ(internal::DecodeChunk(buffer, chunk), OkStatus());
-  return chunk;
-}
-
-}  // namespace pw::transfer::test
diff --git a/pw_transfer/py/BUILD.gn b/pw_transfer/py/BUILD.gn
deleted file mode 100644
index f21ec38..0000000
--- a/pw_transfer/py/BUILD.gn
+++ /dev/null
@@ -1,63 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/python.gni")
-import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_rpc/internal/integration_test_ports.gni")
-
-pw_python_package("py") {
-  generate_setup = {
-    metadata = {
-      name = "pw_transfer"
-      version = "0.0.1"
-    }
-  }
-  sources = [
-    "pw_transfer/__init__.py",
-    "pw_transfer/client.py",
-    "pw_transfer/transfer.py",
-  ]
-  tests = [ "tests/transfer_test.py" ]
-  python_deps = [
-    "$dir_pw_rpc/py",
-    "$dir_pw_status/py",
-  ]
-  python_test_deps = [ "$dir_pw_build/py" ]
-  pylintrc = "$dir_pigweed/.pylintrc"
-  proto_library = "..:proto"
-}
-
-pw_python_script("python_cpp_transfer_test") {
-  sources = [ "tests/python_cpp_transfer_test.py" ]
-  python_deps = [
-    ":py",
-    "$dir_pw_hdlc/py",
-    "$dir_pw_rpc/py",
-    "$dir_pw_status/py",
-    "..:test_server_proto.python",
-  ]
-  pylintrc = "$dir_pigweed/.pylintrc"
-
-  action = {
-    args = [
-      "--port=$pw_transfer_PYTHON_CPP_TRANSFER_TEST_PORT",
-      "--test-server-command",
-      "<TARGET_FILE(..:test_rpc_server)>",
-    ]
-    deps = [ "..:test_rpc_server" ]
-    stamp = true
-  }
-}
diff --git a/pw_transfer/py/pw_transfer/__init__.py b/pw_transfer/py/pw_transfer/__init__.py
deleted file mode 100644
index 1f54b3a..0000000
--- a/pw_transfer/py/pw_transfer/__init__.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Provides a simple interface for transferring bulk data over pw_rpc."""
-
-from pw_transfer.transfer import ProgressCallback, ProgressStats
-from pw_transfer.client import Error, Manager
diff --git a/pw_transfer/py/pw_transfer/client.py b/pw_transfer/py/pw_transfer/client.py
deleted file mode 100644
index b97f2ce..0000000
--- a/pw_transfer/py/pw_transfer/client.py
+++ /dev/null
@@ -1,353 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Client for the pw_transfer service, which transmits data over pw_rpc."""
-
-import asyncio
-import logging
-import threading
-from typing import Any, Dict, Optional, Union
-
-from pw_rpc.callback_client import BidirectionalStreamingCall
-from pw_status import Status
-
-from pw_transfer.transfer import (ProgressCallback, ReadTransfer, Transfer,
-                                  WriteTransfer)
-from pw_transfer.transfer_pb2 import Chunk
-
-_LOG = logging.getLogger(__package__)
-
-_TransferDict = Dict[int, Transfer]
-
-
-class Manager:  # pylint: disable=too-many-instance-attributes
-    """A manager for transmitting data through an RPC TransferService.
-
-    This should be initialized with an active Manager over an RPC channel. Only
-    one instance of this class should exist for a configured RPC TransferService
-    -- the Manager supports multiple simultaneous transfers.
-
-    When created, a Manager starts a separate thread in which transfer
-    communications and events are handled.
-    """
-    def __init__(self,
-                 rpc_transfer_service,
-                 *,
-                 default_response_timeout_s: float = 2.0,
-                 initial_response_timeout_s: float = 4.0,
-                 max_retries: int = 3):
-        """Initializes a Manager on top of a TransferService.
-
-        Args:
-          rpc_transfer_service: the pw_rpc transfer service client
-          default_response_timeout_s: max time to wait between receiving packets
-          initial_response_timeout_s: timeout for the first packet; may be
-              longer to account for transfer handler initialization
-          max_retires: number of times to retry after a timeout
-        """
-        self._service: Any = rpc_transfer_service
-        self._default_response_timeout_s = default_response_timeout_s
-        self._initial_response_timeout_s = initial_response_timeout_s
-        self.max_retries = max_retries
-
-        # Ongoing transfers in the service by ID.
-        self._read_transfers: _TransferDict = {}
-        self._write_transfers: _TransferDict = {}
-
-        # RPC streams for read and write transfers. These are shareable by
-        # multiple transfers of the same type.
-        self._read_stream: Optional[BidirectionalStreamingCall] = None
-        self._write_stream: Optional[BidirectionalStreamingCall] = None
-
-        self._loop = asyncio.new_event_loop()
-
-        # Queues are used for communication between the Manager context and the
-        # dedicated asyncio transfer thread.
-        self._new_transfer_queue: asyncio.Queue = asyncio.Queue()
-        self._read_chunk_queue: asyncio.Queue = asyncio.Queue()
-        self._write_chunk_queue: asyncio.Queue = asyncio.Queue()
-        self._quit_event = asyncio.Event()
-
-        self._thread = threading.Thread(target=self._start_event_loop_thread,
-                                        daemon=True)
-
-        self._thread.start()
-
-    def __del__(self):
-        # Notify the thread that the transfer manager is being destroyed and
-        # wait for it to exit.
-        if self._thread.is_alive():
-            self._loop.call_soon_threadsafe(self._quit_event.set)
-            self._thread.join()
-
-    def read(self,
-             transfer_id: int,
-             progress_callback: ProgressCallback = None) -> bytes:
-        """Receives ("downloads") data from the server.
-
-        Raises:
-          Error: the transfer failed to complete
-        """
-
-        if transfer_id in self._read_transfers:
-            raise ValueError(f'Read transfer {transfer_id} already exists')
-
-        transfer = ReadTransfer(transfer_id,
-                                self._send_read_chunk,
-                                self._end_read_transfer,
-                                self._default_response_timeout_s,
-                                self._initial_response_timeout_s,
-                                self.max_retries,
-                                progress_callback=progress_callback)
-        self._start_read_transfer(transfer)
-
-        transfer.done.wait()
-
-        if not transfer.status.ok():
-            raise Error(transfer.id, transfer.status)
-
-        return transfer.data
-
-    def write(self,
-              transfer_id: int,
-              data: Union[bytes, str],
-              progress_callback: ProgressCallback = None) -> None:
-        """Transmits ("uploads") data to the server.
-
-        Args:
-          transfer_id: ID of the write transfer
-          data: Data to send to the server.
-          progress_callback: Optional callback periodically invoked throughout
-              the transfer with the transfer state. Can be used to provide user-
-              facing status updates such as progress bars.
-
-        Raises:
-          Error: the transfer failed to complete
-        """
-
-        if isinstance(data, str):
-            data = data.encode()
-
-        if transfer_id in self._write_transfers:
-            raise ValueError(f'Write transfer {transfer_id} already exists')
-
-        transfer = WriteTransfer(transfer_id,
-                                 data,
-                                 self._send_write_chunk,
-                                 self._end_write_transfer,
-                                 self._default_response_timeout_s,
-                                 self._initial_response_timeout_s,
-                                 self.max_retries,
-                                 progress_callback=progress_callback)
-        self._start_write_transfer(transfer)
-
-        transfer.done.wait()
-
-        if not transfer.status.ok():
-            raise Error(transfer.id, transfer.status)
-
-    def _send_read_chunk(self, chunk: Chunk) -> None:
-        assert self._read_stream is not None
-        self._read_stream.send(chunk)
-
-    def _send_write_chunk(self, chunk: Chunk) -> None:
-        assert self._write_stream is not None
-        self._write_stream.send(chunk)
-
-    def _start_event_loop_thread(self):
-        """Entry point for event loop thread that starts an asyncio context."""
-        asyncio.set_event_loop(self._loop)
-
-        # Recreate the async communication channels in the context of the
-        # running event loop.
-        self._new_transfer_queue = asyncio.Queue()
-        self._read_chunk_queue = asyncio.Queue()
-        self._write_chunk_queue = asyncio.Queue()
-        self._quit_event = asyncio.Event()
-
-        self._loop.create_task(self._transfer_event_loop())
-        self._loop.run_forever()
-
-    async def _transfer_event_loop(self):
-        """Main async event loop."""
-        exit_thread = self._loop.create_task(self._quit_event.wait())
-        new_transfer = self._loop.create_task(self._new_transfer_queue.get())
-        read_chunk = self._loop.create_task(self._read_chunk_queue.get())
-        write_chunk = self._loop.create_task(self._write_chunk_queue.get())
-
-        while not self._quit_event.is_set():
-            # Perform a select(2)-like wait for one of several events to occur.
-            done, _ = await asyncio.wait(
-                (exit_thread, new_transfer, read_chunk, write_chunk),
-                return_when=asyncio.FIRST_COMPLETED)
-
-            if exit_thread in done:
-                break
-
-            if new_transfer in done:
-                await new_transfer.result().begin()
-                new_transfer = self._loop.create_task(
-                    self._new_transfer_queue.get())
-
-            if read_chunk in done:
-                self._loop.create_task(
-                    self._handle_chunk(self._read_transfers,
-                                       read_chunk.result()))
-                read_chunk = self._loop.create_task(
-                    self._read_chunk_queue.get())
-
-            if write_chunk in done:
-                self._loop.create_task(
-                    self._handle_chunk(self._write_transfers,
-                                       write_chunk.result()))
-                write_chunk = self._loop.create_task(
-                    self._write_chunk_queue.get())
-
-        self._loop.stop()
-
-    @staticmethod
-    async def _handle_chunk(transfers: _TransferDict, chunk: Chunk) -> None:
-        """Processes an incoming chunk from a stream.
-
-        The chunk is dispatched to an active transfer based on its ID. If the
-        transfer indicates that it is complete, the provided completion callback
-        is invoked.
-        """
-
-        try:
-            transfer = transfers[chunk.transfer_id]
-        except KeyError:
-            _LOG.error(
-                'TransferManager received chunk for unknown transfer %d',
-                chunk.transfer_id)
-            # TODO(frolv): What should be done here, if anything?
-            return
-
-        await transfer.handle_chunk(chunk)
-
-    def _open_read_stream(self) -> None:
-        self._read_stream = self._service.Read.invoke(
-            lambda _, chunk: self._loop.call_soon_threadsafe(
-                self._read_chunk_queue.put_nowait, chunk),
-            on_error=lambda _, status: self._on_read_error(status))
-
-    def _on_read_error(self, status: Status) -> None:
-        """Callback for an RPC error in the read stream."""
-
-        if status is Status.FAILED_PRECONDITION:
-            # FAILED_PRECONDITION indicates that the stream packet was not
-            # recognized as the stream is not open. This could occur if the
-            # server resets during an active transfer. Re-open the stream to
-            # allow pending transfers to continue.
-            self._open_read_stream()
-        else:
-            # Other errors are unrecoverable. Clear the stream and cancel any
-            # pending transfers with an INTERNAL status as this is a system
-            # error.
-            self._read_stream = None
-
-            for transfer in self._read_transfers.values():
-                transfer.finish(Status.INTERNAL, skip_callback=True)
-            self._read_transfers.clear()
-
-            _LOG.error('Read stream shut down: %s', status)
-
-    def _open_write_stream(self) -> None:
-        self._write_stream = self._service.Write.invoke(
-            lambda _, chunk: self._loop.call_soon_threadsafe(
-                self._write_chunk_queue.put_nowait, chunk),
-            on_error=lambda _, status: self._on_write_error(status))
-
-    def _on_write_error(self, status: Status) -> None:
-        """Callback for an RPC error in the write stream."""
-
-        if status is Status.FAILED_PRECONDITION:
-            # FAILED_PRECONDITION indicates that the stream packet was not
-            # recognized as the stream is not open. This could occur if the
-            # server resets during an active transfer. Re-open the stream to
-            # allow pending transfers to continue.
-            self._open_write_stream()
-        else:
-            # Other errors are unrecoverable. Clear the stream and cancel any
-            # pending transfers with an INTERNAL status as this is a system
-            # error.
-            self._write_stream = None
-
-            for transfer in self._write_transfers.values():
-                transfer.finish(Status.INTERNAL, skip_callback=True)
-            self._write_transfers.clear()
-
-            _LOG.error('Write stream shut down: %s', status)
-
-    def _start_read_transfer(self, transfer: Transfer) -> None:
-        """Begins a new read transfer, opening the stream if it isn't."""
-
-        self._read_transfers[transfer.id] = transfer
-
-        if not self._read_stream:
-            self._open_read_stream()
-
-        _LOG.debug('Starting new read transfer %d', transfer.id)
-        self._loop.call_soon_threadsafe(self._new_transfer_queue.put_nowait,
-                                        transfer)
-
-    def _end_read_transfer(self, transfer: Transfer) -> None:
-        """Completes a read transfer."""
-        del self._read_transfers[transfer.id]
-
-        if not transfer.status.ok():
-            _LOG.error('Read transfer %d terminated with status %s',
-                       transfer.id, transfer.status)
-
-        # TODO(frolv): This doesn't seem to work. Investigate why.
-        # If no more transfers are using the read stream, close it.
-        # if not self._read_transfers and self._read_stream:
-        #     self._read_stream.cancel()
-        #     self._read_stream = None
-
-    def _start_write_transfer(self, transfer: Transfer) -> None:
-        """Begins a new write transfer, opening the stream if it isn't."""
-
-        self._write_transfers[transfer.id] = transfer
-
-        if not self._write_stream:
-            self._open_write_stream()
-
-        _LOG.debug('Starting new write transfer %d', transfer.id)
-        self._loop.call_soon_threadsafe(self._new_transfer_queue.put_nowait,
-                                        transfer)
-
-    def _end_write_transfer(self, transfer: Transfer) -> None:
-        """Completes a write transfer."""
-        del self._write_transfers[transfer.id]
-
-        if not transfer.status.ok():
-            _LOG.error('Write transfer %d terminated with status %s',
-                       transfer.id, transfer.status)
-
-        # TODO(frolv): This doesn't seem to work. Investigate why.
-        # If no more transfers are using the write stream, close it.
-        # if not self._write_transfers and self._write_stream:
-        #     self._write_stream.cancel()
-        #     self._write_stream = None
-
-
-class Error(Exception):
-    """Exception raised when a transfer fails.
-
-    Stores the ID of the failed transfer and the error that occurred.
-    """
-    def __init__(self, transfer_id: int, status: Status):
-        super().__init__(f'Transfer {transfer_id} failed with status {status}')
-        self.transfer_id = transfer_id
-        self.status = status
diff --git a/pw_transfer/py/pw_transfer/transfer.py b/pw_transfer/py/pw_transfer/transfer.py
deleted file mode 100644
index 06574f4..0000000
--- a/pw_transfer/py/pw_transfer/transfer.py
+++ /dev/null
@@ -1,507 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Classes for read and write transfers."""
-
-import abc
-import asyncio
-from dataclasses import dataclass
-import logging
-import math
-import threading
-from typing import Any, Callable, Optional
-
-from pw_status import Status
-from pw_transfer.transfer_pb2 import Chunk
-
-_LOG = logging.getLogger(__package__)
-
-
-@dataclass(frozen=True)
-class ProgressStats:
-    bytes_sent: int
-    bytes_confirmed_received: int
-    total_size_bytes: Optional[int]
-
-    def percent_received(self) -> float:
-        if self.total_size_bytes is None:
-            return math.nan
-
-        return self.bytes_confirmed_received / self.total_size_bytes * 100
-
-    def __str__(self) -> str:
-        total = str(
-            self.total_size_bytes) if self.total_size_bytes else 'unknown'
-        return (f'{self.percent_received():5.1f}% ({self.bytes_sent} B sent, '
-                f'{self.bytes_confirmed_received} B received of {total} B)')
-
-
-ProgressCallback = Callable[[ProgressStats], Any]
-
-
-class _Timer:
-    """A timer which invokes a callback after a certain timeout."""
-    def __init__(self, timeout_s: float, callback: Callable[[], Any]):
-        self.timeout_s = timeout_s
-        self._callback = callback
-        self._task: Optional[asyncio.Task[Any]] = None
-
-    def start(self, timeout_s: float = None) -> None:
-        """Starts a new timer.
-
-        If a timer is already running, it is stopped and a new timer started.
-        This can be used to implement watchdog-like behavior, where a callback
-        is invoked after some time without a kick.
-        """
-        self.stop()
-        timeout_s = self.timeout_s if timeout_s is None else timeout_s
-        self._task = asyncio.create_task(self._run(timeout_s))
-
-    def stop(self) -> None:
-        """Terminates a running timer."""
-        if self._task is not None:
-            self._task.cancel()
-            self._task = None
-
-    async def _run(self, timeout_s: float) -> None:
-        await asyncio.sleep(timeout_s)
-        self._task = None
-        self._callback()
-
-
-class Transfer(abc.ABC):
-    """A client-side data transfer through a Manager.
-
-    Subclasses are responsible for implementing all of the logic for their type
-    of transfer, receiving messages from the server and sending the appropriate
-    messages in response.
-    """
-    def __init__(self,
-                 transfer_id: int,
-                 send_chunk: Callable[[Chunk], None],
-                 end_transfer: Callable[['Transfer'], None],
-                 response_timeout_s: float,
-                 initial_response_timeout_s: float,
-                 max_retries: int,
-                 progress_callback: ProgressCallback = None):
-        self.id = transfer_id
-        self.status = Status.OK
-        self.done = threading.Event()
-
-        self._send_chunk = send_chunk
-        self._end_transfer = end_transfer
-
-        self._retries = 0
-        self._max_retries = max_retries
-        self._response_timer = _Timer(response_timeout_s, self._on_timeout)
-        self._initial_response_timeout_s = initial_response_timeout_s
-
-        self._progress_callback = progress_callback
-
-    async def begin(self) -> None:
-        """Sends the initial chunk of the transfer."""
-        self._send_chunk(self._initial_chunk())
-        self._response_timer.start(self._initial_response_timeout_s)
-
-    @property
-    @abc.abstractmethod
-    def data(self) -> bytes:
-        """Returns the data read or written in this transfer."""
-
-    @abc.abstractmethod
-    def _initial_chunk(self) -> Chunk:
-        """Returns the initial chunk to notify the sever of the transfer."""
-
-    async def handle_chunk(self, chunk: Chunk) -> None:
-        """Processes an incoming chunk from the server.
-
-        Handles terminating chunks (i.e. those with a status) and forwards
-        non-terminating chunks to handle_data_chunk.
-        """
-        self._response_timer.stop()
-        self._retries = 0  # Received data from service, so reset the retries.
-
-        _LOG.debug('Received chunk\n%s', str(chunk).rstrip())
-
-        # Status chunks are only used to terminate a transfer. They do not
-        # contain any data that requires processing.
-        if chunk.HasField('status'):
-            self.finish(Status(chunk.status))
-            return
-
-        await self._handle_data_chunk(chunk)
-
-        # Start the timeout for the server to send a chunk in response.
-        self._response_timer.start()
-
-    @abc.abstractmethod
-    async def _handle_data_chunk(self, chunk: Chunk) -> None:
-        """Handles a chunk that contains or requests data."""
-
-    @abc.abstractmethod
-    def _retry_after_timeout(self) -> None:
-        """Retries after a timeout occurs."""
-
-    def _on_timeout(self) -> None:
-        """Handles a timeout while waiting for a chunk."""
-        if self.done.is_set():
-            return
-
-        self._retries += 1
-        if self._retries > self._max_retries:
-            self.finish(Status.DEADLINE_EXCEEDED)
-            return
-
-        _LOG.debug('Received no responses for %.3fs; retrying %d/%d',
-                   self._response_timer.timeout_s, self._retries,
-                   self._max_retries)
-        self._retry_after_timeout()
-        self._response_timer.start()
-
-    def finish(self, status: Status, skip_callback: bool = False) -> None:
-        """Ends the transfer with the specified status."""
-        self._response_timer.stop()
-        self.status = status
-
-        if status.ok():
-            total_size = len(self.data)
-            self._update_progress(total_size, total_size, total_size)
-
-        if not skip_callback:
-            self._end_transfer(self)
-
-        # Set done last so that the transfer has been fully cleaned up.
-        self.done.set()
-
-    def _update_progress(self, bytes_sent: int, bytes_confirmed_received: int,
-                         total_size_bytes: Optional[int]) -> None:
-        """Invokes the provided progress callback, if any, with the progress."""
-
-        stats = ProgressStats(bytes_sent, bytes_confirmed_received,
-                              total_size_bytes)
-        _LOG.debug('Transfer %d progress: %s', self.id, stats)
-
-        if self._progress_callback:
-            self._progress_callback(stats)
-
-    def _send_error(self, error: Status) -> None:
-        """Sends an error chunk to the server and finishes the transfer."""
-        self._send_chunk(
-            Chunk(transfer_id=self.id,
-                  status=error.value,
-                  type=Chunk.Type.TRANSFER_COMPLETION))
-        self.finish(error)
-
-
-class WriteTransfer(Transfer):
-    """A client -> server write transfer."""
-    def __init__(
-        self,
-        transfer_id: int,
-        data: bytes,
-        send_chunk: Callable[[Chunk], None],
-        end_transfer: Callable[[Transfer], None],
-        response_timeout_s: float,
-        initial_response_timeout_s: float,
-        max_retries: int,
-        progress_callback: ProgressCallback = None,
-    ):
-        super().__init__(transfer_id, send_chunk, end_transfer,
-                         response_timeout_s, initial_response_timeout_s,
-                         max_retries, progress_callback)
-        self._data = data
-
-        # Guard this class with a lock since a transfer parameters update might
-        # arrive while responding to a prior update.
-        self._lock = asyncio.Lock()
-        self._offset = 0
-        self._window_end_offset = 0
-        self._max_chunk_size = 0
-        self._chunk_delay_us: Optional[int] = None
-
-        # The window ID increments for each parameters update.
-        self._window_id = 0
-
-        self._last_chunk = self._initial_chunk()
-
-    @property
-    def data(self) -> bytes:
-        return self._data
-
-    def _initial_chunk(self) -> Chunk:
-        return Chunk(transfer_id=self.id, type=Chunk.Type.TRANSFER_START)
-
-    async def _handle_data_chunk(self, chunk: Chunk) -> None:
-        """Processes an incoming chunk from the server.
-
-        In a write transfer, the server only sends transfer parameter updates
-        to the client. When a message is received, update local parameters and
-        send data accordingly.
-        """
-
-        async with self._lock:
-            self._window_id += 1
-            window_id = self._window_id
-
-            if not self._handle_parameters_update(chunk):
-                return
-
-            bytes_acknowledged = chunk.offset
-
-        while True:
-            if self._chunk_delay_us:
-                await asyncio.sleep(self._chunk_delay_us / 1e6)
-
-            async with self._lock:
-                if self.done.is_set():
-                    return
-
-                if window_id != self._window_id:
-                    _LOG.debug('Transfer %d: Skipping stale window', self.id)
-                    return
-
-                write_chunk = self._next_chunk()
-                self._offset += len(write_chunk.data)
-                sent_requested_bytes = self._offset == self._window_end_offset
-
-            self._send_chunk(write_chunk)
-
-            self._update_progress(self._offset, bytes_acknowledged,
-                                  len(self.data))
-
-            if sent_requested_bytes:
-                break
-
-        self._last_chunk = write_chunk
-
-    def _handle_parameters_update(self, chunk: Chunk) -> bool:
-        """Updates transfer state based on a transfer parameters update."""
-
-        retransmit = True
-        if chunk.HasField('type'):
-            retransmit = (chunk.type == Chunk.Type.PARAMETERS_RETRANSMIT
-                          or chunk.type == Chunk.Type.TRANSFER_START)
-
-        if chunk.offset > len(self.data):
-            # Bad offset; terminate the transfer.
-            _LOG.error(
-                'Transfer %d: server requested invalid offset %d (size %d)',
-                self.id, chunk.offset, len(self.data))
-
-            self._send_error(Status.OUT_OF_RANGE)
-            return False
-
-        if chunk.pending_bytes == 0:
-            _LOG.error(
-                'Transfer %d: service requested 0 bytes (invalid); aborting',
-                self.id)
-            self._send_error(Status.INTERNAL)
-            return False
-
-        if retransmit:
-            # Check whether the client has sent a previous data offset, which
-            # indicates that some chunks were lost in transmission.
-            if chunk.offset < self._offset:
-                _LOG.debug('Write transfer %d rolling back: offset %d from %d',
-                           self.id, chunk.offset, self._offset)
-
-            self._offset = chunk.offset
-
-            # Retransmit is the default behavior for older versions of the
-            # transfer protocol. The window_end_offset field is not guaranteed
-            # to be set in these version, so it must be calculated.
-            max_bytes_to_send = min(chunk.pending_bytes,
-                                    len(self.data) - self._offset)
-            self._window_end_offset = self._offset + max_bytes_to_send
-        else:
-            assert chunk.type == Chunk.Type.PARAMETERS_CONTINUE
-
-            # Extend the window to the new end offset specified by the server.
-            self._window_end_offset = min(chunk.window_end_offset,
-                                          len(self.data))
-
-        if chunk.HasField('max_chunk_size_bytes'):
-            self._max_chunk_size = chunk.max_chunk_size_bytes
-
-        if chunk.HasField('min_delay_microseconds'):
-            self._chunk_delay_us = chunk.min_delay_microseconds
-
-        return True
-
-    def _retry_after_timeout(self) -> None:
-        self._send_chunk(self._last_chunk)
-
-    def _next_chunk(self) -> Chunk:
-        """Returns the next Chunk message to send in the data transfer."""
-        chunk = Chunk(transfer_id=self.id,
-                      offset=self._offset,
-                      type=Chunk.Type.TRANSFER_DATA)
-        max_bytes_in_chunk = min(self._max_chunk_size,
-                                 self._window_end_offset - self._offset)
-
-        chunk.data = self.data[self._offset:self._offset + max_bytes_in_chunk]
-
-        # Mark the final chunk of the transfer.
-        if len(self.data) - self._offset <= max_bytes_in_chunk:
-            chunk.remaining_bytes = 0
-
-        return chunk
-
-
-class ReadTransfer(Transfer):
-    """A client <- server read transfer.
-
-    Although Python can effectively handle an unlimited transfer window, this
-    client sets a conservative window and chunk size to avoid overloading the
-    device. These are configurable in the constructor.
-    """
-
-    # The fractional position within a window at which a receive transfer should
-    # extend its window size to minimize the amount of time the transmitter
-    # spends blocked.
-    #
-    # For example, a divisor of 2 will extend the window when half of the
-    # requested data has been received, a divisor of three will extend at a
-    # third of the window, and so on.
-    EXTEND_WINDOW_DIVISOR = 2
-
-    def __init__(  # pylint: disable=too-many-arguments
-            self,
-            transfer_id: int,
-            send_chunk: Callable[[Chunk], None],
-            end_transfer: Callable[[Transfer], None],
-            response_timeout_s: float,
-            initial_response_timeout_s: float,
-            max_retries: int,
-            max_bytes_to_receive: int = 8192,
-            max_chunk_size: int = 1024,
-            chunk_delay_us: int = None,
-            progress_callback: ProgressCallback = None):
-        super().__init__(transfer_id, send_chunk, end_transfer,
-                         response_timeout_s, initial_response_timeout_s,
-                         max_retries, progress_callback)
-        self._max_bytes_to_receive = max_bytes_to_receive
-        self._max_chunk_size = max_chunk_size
-        self._chunk_delay_us = chunk_delay_us
-
-        self._remaining_transfer_size: Optional[int] = None
-        self._data = bytearray()
-        self._offset = 0
-        self._pending_bytes = max_bytes_to_receive
-        self._window_end_offset = max_bytes_to_receive
-
-    @property
-    def data(self) -> bytes:
-        """Returns an immutable copy of the data that has been read."""
-        return bytes(self._data)
-
-    def _initial_chunk(self) -> Chunk:
-        return self._transfer_parameters(Chunk.Type.TRANSFER_START)
-
-    async def _handle_data_chunk(self, chunk: Chunk) -> None:
-        """Processes an incoming chunk from the server.
-
-        In a read transfer, the client receives data chunks from the server.
-        Once all pending data is received, the transfer parameters are updated.
-        """
-
-        if chunk.offset != self._offset:
-            # Initially, the transfer service only supports in-order transfers.
-            # If data is received out of order, request that the server
-            # retransmit from the previous offset.
-            self._send_chunk(
-                self._transfer_parameters(Chunk.Type.PARAMETERS_RETRANSMIT))
-            return
-
-        self._data += chunk.data
-        self._pending_bytes -= len(chunk.data)
-        self._offset += len(chunk.data)
-
-        if chunk.HasField('remaining_bytes'):
-            if chunk.remaining_bytes == 0:
-                # No more data to read. Acknowledge receipt and finish.
-                self._send_chunk(
-                    Chunk(transfer_id=self.id,
-                          status=Status.OK.value,
-                          type=Chunk.Type.TRANSFER_COMPLETION))
-                self.finish(Status.OK)
-                return
-
-            # The server may indicate if the amount of remaining data is known.
-            self._remaining_transfer_size = chunk.remaining_bytes
-        elif self._remaining_transfer_size is not None:
-            # Update the remaining transfer size, if it is known.
-            self._remaining_transfer_size -= len(chunk.data)
-
-            # If the transfer size drops to zero, the estimate was inaccurate.
-            if self._remaining_transfer_size <= 0:
-                self._remaining_transfer_size = None
-
-        total_size = None if self._remaining_transfer_size is None else (
-            self._remaining_transfer_size + self._offset)
-        self._update_progress(self._offset, self._offset, total_size)
-
-        if chunk.window_end_offset != 0:
-            if chunk.window_end_offset < self._offset:
-                _LOG.error(
-                    'Transfer %d: transmitter sent invalid earlier end offset '
-                    '%d (receiver offset %d)', self.id,
-                    chunk.window_end_offset, self._offset)
-                self._send_error(Status.INTERNAL)
-                return
-
-            if chunk.window_end_offset > self._window_end_offset:
-                _LOG.error(
-                    'Transfer %d: transmitter sent invalid later end offset '
-                    '%d (receiver end offset %d)', self.id,
-                    chunk.window_end_offset, self._window_end_offset)
-                self._send_error(Status.INTERNAL)
-                return
-
-            self._window_end_offset = chunk.window_end_offset
-            self._pending_bytes -= chunk.window_end_offset - self._offset
-
-        remaining_window_size = self._window_end_offset - self._offset
-        extend_window = (remaining_window_size <= self._max_bytes_to_receive /
-                         ReadTransfer.EXTEND_WINDOW_DIVISOR)
-
-        if self._pending_bytes == 0:
-            # All pending data was received. Send out a new parameters chunk for
-            # the next block.
-            self._send_chunk(
-                self._transfer_parameters(Chunk.Type.PARAMETERS_RETRANSMIT))
-        elif extend_window:
-            self._send_chunk(
-                self._transfer_parameters(Chunk.Type.PARAMETERS_CONTINUE))
-
-    def _retry_after_timeout(self) -> None:
-        self._send_chunk(
-            self._transfer_parameters(Chunk.Type.PARAMETERS_RETRANSMIT))
-
-    def _transfer_parameters(self, chunk_type: Any) -> Chunk:
-        """Sends an updated transfer parameters chunk to the server."""
-
-        self._pending_bytes = self._max_bytes_to_receive
-        self._window_end_offset = self._offset + self._max_bytes_to_receive
-
-        chunk = Chunk(transfer_id=self.id,
-                      pending_bytes=self._pending_bytes,
-                      window_end_offset=self._window_end_offset,
-                      max_chunk_size_bytes=self._max_chunk_size,
-                      offset=self._offset,
-                      type=chunk_type)
-
-        if self._chunk_delay_us:
-            chunk.min_delay_microseconds = self._chunk_delay_us
-
-        return chunk
diff --git a/pw_transfer/py/tests/python_cpp_transfer_test.py b/pw_transfer/py/tests/python_cpp_transfer_test.py
deleted file mode 100755
index 62e0d14..0000000
--- a/pw_transfer/py/tests/python_cpp_transfer_test.py
+++ /dev/null
@@ -1,212 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests transfers between the Python client and C++ service."""
-
-from pathlib import Path
-import random
-import tempfile
-from typing import List, Tuple, Union
-import unittest
-
-from pw_hdlc import rpc
-from pw_rpc import testing
-from pw_status import Status
-import pw_transfer
-from pw_transfer import transfer_pb2
-from pw_transfer_test import test_server_pb2
-
-ITERATIONS = 5
-TIMEOUT_S = 0.05
-
-_DATA_4096B = b'SPAM' * (4096 // len('SPAM'))
-
-
-class TransferServiceIntegrationTest(unittest.TestCase):
-    """Tests transfers between the Python client and C++ service."""
-    test_server_command: Tuple[str, ...] = ()
-    port: int
-
-    def setUp(self) -> None:
-        self._tempdir = tempfile.TemporaryDirectory(
-            prefix=f'pw_transfer_{self.id().rsplit(".", 1)[-1]}_')
-        self.directory = Path(self._tempdir.name)
-
-        command = (*self.test_server_command, str(self.directory))
-        self._outgoing_filter = rpc.PacketFilter('outgoing RPC')
-        self._incoming_filter = rpc.PacketFilter('incoming RPC')
-        self._context = rpc.HdlcRpcLocalServerAndClient(
-            command,
-            self.port, [transfer_pb2, test_server_pb2],
-            outgoing_processor=self._outgoing_filter,
-            incoming_processor=self._incoming_filter)
-
-        service = self._context.client.channel(1).rpcs.pw.transfer.Transfer
-        self.manager = pw_transfer.Manager(
-            service, default_response_timeout_s=TIMEOUT_S)
-
-        self._test_server = self._context.client.channel(
-            1).rpcs.pw.transfer.TestServer
-
-    def tearDown(self) -> None:
-        try:
-            self._tempdir.cleanup()
-        finally:
-            if hasattr(self, '_context'):
-                self._context.close()
-
-    def transfer_file_path(self, transfer_id: int) -> Path:
-        return self.directory / str(transfer_id)
-
-    def set_content(self, transfer_id: int, data: Union[bytes, str]) -> None:
-        self.transfer_file_path(transfer_id).write_bytes(
-            data.encode() if isinstance(data, str) else data)
-        self._test_server.ReloadTransferFiles()
-
-    def get_content(self, transfer_id: int) -> bytes:
-        return self.transfer_file_path(transfer_id).read_bytes()
-
-    def test_read_unknown_id(self) -> None:
-        with self.assertRaises(pw_transfer.Error) as ctx:
-            self.manager.read(99)
-        self.assertEqual(ctx.exception.status, Status.NOT_FOUND)
-
-    def test_read_empty(self) -> None:
-        for _ in range(ITERATIONS):
-            self.set_content(24, '')
-            self.assertEqual(self.manager.read(24), b'')
-
-    def test_read_single_byte(self) -> None:
-        for _ in range(ITERATIONS):
-            self.set_content(25, '0')
-            self.assertEqual(self.manager.read(25), b'0')
-
-    def test_read_small_amount_of_data(self) -> None:
-        for _ in range(ITERATIONS):
-            self.set_content(26, 'hunter2')
-            self.assertEqual(self.manager.read(26), b'hunter2')
-
-    def test_read_large_amount_of_data(self) -> None:
-        for _ in range(ITERATIONS):
-            size = 2**13  # TODO(hepler): Increase to 2**14 when it passes.
-            self.set_content(27, '~' * size)
-            self.assertEqual(self.manager.read(27), b'~' * size)
-
-    def test_write_unknown_id(self) -> None:
-        with self.assertRaises(pw_transfer.Error) as ctx:
-            self.manager.write(99, '')
-        self.assertEqual(ctx.exception.status, Status.NOT_FOUND)
-
-    def test_write_empty(self) -> None:
-        for _ in range(ITERATIONS):
-            self.set_content(28, 'junk')
-            self.manager.write(28, b'')
-            self.assertEqual(self.get_content(28), b'')
-
-    def test_write_single_byte(self) -> None:
-        for _ in range(ITERATIONS):
-            self.set_content(29, 'junk')
-            self.manager.write(29, b'$')
-            self.assertEqual(self.get_content(29), b'$')
-
-    def test_write_small_amount_of_data(self) -> None:
-        for _ in range(ITERATIONS):
-            self.set_content(30, 'junk')
-            self.manager.write(30, b'file transfer')
-            self.assertEqual(self.get_content(30), b'file transfer')
-
-    def test_write_large_amount_of_data(self) -> None:
-        for _ in range(ITERATIONS):
-            self.set_content(31, 'junk')
-            self.manager.write(31, b'*' * 512)
-            self.assertEqual(self.get_content(31), b'*' * 512)
-
-    def test_write_very_large_amount_of_data(self) -> None:
-        for _ in range(ITERATIONS):
-            self.set_content(32, 'junk')
-
-            # Larger than the transfer service's configured pending_bytes.
-            self.manager.write(32, _DATA_4096B)
-            self.assertEqual(self.get_content(32), _DATA_4096B)
-
-    def test_write_string(self) -> None:
-        for _ in range(ITERATIONS):
-            # Write a string instead of bytes.
-            self.set_content(33, 'junk')
-            self.manager.write(33, 'hello world')
-            self.assertEqual(self.get_content(33), b'hello world')
-
-    def test_write_drop_data_chunks_and_transfer_parameters(self) -> None:
-        self.set_content(34, 'junk')
-
-        # Allow the initial packet and first chunk, then drop the second chunk.
-        self._outgoing_filter.keep(2)
-        self._outgoing_filter.drop(1)
-
-        # Allow the initial transfer parameters updates, then drop the next two.
-        self._incoming_filter.keep(1)
-        self._incoming_filter.drop(2)
-
-        with self.assertLogs('pw_transfer', 'DEBUG') as logs:
-            self.manager.write(34, _DATA_4096B)
-
-        self.assertEqual(self.get_content(34), _DATA_4096B)
-
-        # Verify that the client retried twice.
-        messages = [r.getMessage() for r in logs.records]
-        retry = f'Received no responses for {TIMEOUT_S:.3f}s; retrying {{}}/3'
-        self.assertIn(retry.format(1), messages)
-        self.assertIn(retry.format(2), messages)
-
-    def test_write_regularly_drop_packets(self) -> None:
-        self.set_content(35, 'junk')
-
-        self._outgoing_filter.drop_every(5)  # drop one per window
-        self._incoming_filter.drop_every(3)
-
-        self.manager.write(35, _DATA_4096B)
-
-        self.assertEqual(self.get_content(35), _DATA_4096B)
-
-    def test_write_randomly_drop_packets(self) -> None:
-        # Allow lots of retries since there are lots of drops.
-        self.manager.max_retries = 9
-
-        for seed in [1, 5678, 600613]:
-            self.set_content(seed, 'junk')
-
-            rand = random.Random(seed)
-            self._incoming_filter.randomly_drop(3, rand)
-            self._outgoing_filter.randomly_drop(3, rand)
-
-            data = bytes(
-                rand.randrange(256) for _ in range(rand.randrange(16384)))
-            self.manager.write(seed, data)
-            self.assertEqual(self.get_content(seed), data)
-
-            self._incoming_filter.reset()
-            self._outgoing_filter.reset()
-
-
-def _main(test_server_command: List[str], port: int,
-          unittest_args: List[str]) -> None:
-    TransferServiceIntegrationTest.test_server_command = tuple(
-        test_server_command)
-    TransferServiceIntegrationTest.port = port
-
-    unittest.main(argv=unittest_args)
-
-
-if __name__ == '__main__':
-    _main(**vars(testing.parse_test_server_args()))
diff --git a/pw_transfer/py/tests/transfer_test.py b/pw_transfer/py/tests/transfer_test.py
deleted file mode 100644
index e73c09d..0000000
--- a/pw_transfer/py/tests/transfer_test.py
+++ /dev/null
@@ -1,562 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests for the transfer service client."""
-
-import enum
-import math
-import unittest
-from typing import Iterable, List
-
-from pw_status import Status
-from pw_rpc import callback_client, client, ids, packets
-from pw_rpc.internal import packet_pb2
-
-import pw_transfer
-from pw_transfer.transfer_pb2 import Chunk
-
-_TRANSFER_SERVICE_ID = ids.calculate('pw.transfer.Transfer')
-
-# If the default timeout is too short, some tests become flaky on Windows.
-DEFAULT_TIMEOUT_S = 0.3
-
-
-class _Method(enum.Enum):
-    READ = ids.calculate('Read')
-    WRITE = ids.calculate('Write')
-
-
-class TransferManagerTest(unittest.TestCase):
-    """Tests for the transfer manager."""
-    def setUp(self) -> None:
-        self._client = client.Client.from_modules(
-            callback_client.Impl(), [client.Channel(1, self._handle_request)],
-            (pw_transfer.transfer_pb2, ))
-        self._service = self._client.channel(1).rpcs.pw.transfer.Transfer
-
-        self._sent_chunks: List[Chunk] = []
-        self._packets_to_send: List[List[bytes]] = []
-
-    def _enqueue_server_responses(
-            self, method: _Method,
-            responses: Iterable[Iterable[Chunk]]) -> None:
-        for group in responses:
-            serialized_group = []
-            for response in group:
-                serialized_group.append(
-                    packet_pb2.RpcPacket(
-                        type=packet_pb2.PacketType.SERVER_STREAM,
-                        channel_id=1,
-                        service_id=_TRANSFER_SERVICE_ID,
-                        method_id=method.value,
-                        status=Status.OK.value,
-                        payload=response.SerializeToString()).
-                    SerializeToString())
-            self._packets_to_send.append(serialized_group)
-
-    def _enqueue_server_error(self, method: _Method, error: Status) -> None:
-        self._packets_to_send.append([
-            packet_pb2.RpcPacket(type=packet_pb2.PacketType.SERVER_ERROR,
-                                 channel_id=1,
-                                 service_id=_TRANSFER_SERVICE_ID,
-                                 method_id=method.value,
-                                 status=error.value).SerializeToString()
-        ])
-
-    def _handle_request(self, data: bytes) -> None:
-        packet = packets.decode(data)
-        if packet.type is not packet_pb2.PacketType.CLIENT_STREAM:
-            return
-
-        chunk = Chunk()
-        chunk.MergeFromString(packet.payload)
-        self._sent_chunks.append(chunk)
-
-        if self._packets_to_send:
-            responses = self._packets_to_send.pop(0)
-            for response in responses:
-                self._client.process_packet(response)
-
-    def _received_data(self) -> bytearray:
-        data = bytearray()
-        for chunk in self._sent_chunks:
-            data.extend(chunk.data)
-        return data
-
-    def test_read_transfer_basic(self):
-        manager = pw_transfer.Manager(
-            self._service, default_response_timeout_s=DEFAULT_TIMEOUT_S)
-
-        self._enqueue_server_responses(
-            _Method.READ,
-            ((Chunk(transfer_id=3, offset=0, data=b'abc',
-                    remaining_bytes=0), ), ),
-        )
-
-        data = manager.read(3)
-        self.assertEqual(data, b'abc')
-        self.assertEqual(len(self._sent_chunks), 2)
-        self.assertTrue(self._sent_chunks[-1].HasField('status'))
-        self.assertEqual(self._sent_chunks[-1].status, 0)
-
-    def test_read_transfer_multichunk(self) -> None:
-        manager = pw_transfer.Manager(
-            self._service, default_response_timeout_s=DEFAULT_TIMEOUT_S)
-
-        self._enqueue_server_responses(
-            _Method.READ,
-            ((
-                Chunk(transfer_id=3, offset=0, data=b'abc', remaining_bytes=3),
-                Chunk(transfer_id=3, offset=3, data=b'def', remaining_bytes=0),
-            ), ),
-        )
-
-        data = manager.read(3)
-        self.assertEqual(data, b'abcdef')
-        self.assertEqual(len(self._sent_chunks), 2)
-        self.assertTrue(self._sent_chunks[-1].HasField('status'))
-        self.assertEqual(self._sent_chunks[-1].status, 0)
-
-    def test_read_transfer_progress_callback(self) -> None:
-        manager = pw_transfer.Manager(
-            self._service, default_response_timeout_s=DEFAULT_TIMEOUT_S)
-
-        self._enqueue_server_responses(
-            _Method.READ,
-            ((
-                Chunk(transfer_id=3, offset=0, data=b'abc', remaining_bytes=3),
-                Chunk(transfer_id=3, offset=3, data=b'def', remaining_bytes=0),
-            ), ),
-        )
-
-        progress: List[pw_transfer.ProgressStats] = []
-
-        data = manager.read(3, progress.append)
-        self.assertEqual(data, b'abcdef')
-        self.assertEqual(len(self._sent_chunks), 2)
-        self.assertTrue(self._sent_chunks[-1].HasField('status'))
-        self.assertEqual(self._sent_chunks[-1].status, 0)
-        self.assertEqual(progress, [
-            pw_transfer.ProgressStats(3, 3, 6),
-            pw_transfer.ProgressStats(6, 6, 6),
-        ])
-
-    def test_read_transfer_retry_bad_offset(self) -> None:
-        """Server responds with an unexpected offset in a read transfer."""
-        manager = pw_transfer.Manager(
-            self._service, default_response_timeout_s=DEFAULT_TIMEOUT_S)
-
-        self._enqueue_server_responses(
-            _Method.READ,
-            (
-                (
-                    Chunk(transfer_id=3,
-                          offset=0,
-                          data=b'123',
-                          remaining_bytes=6),
-
-                    # Incorrect offset; expecting 3.
-                    Chunk(transfer_id=3,
-                          offset=1,
-                          data=b'456',
-                          remaining_bytes=3),
-                ),
-                (
-                    Chunk(transfer_id=3,
-                          offset=3,
-                          data=b'456',
-                          remaining_bytes=3),
-                    Chunk(transfer_id=3,
-                          offset=6,
-                          data=b'789',
-                          remaining_bytes=0),
-                ),
-            ))
-
-        data = manager.read(3)
-        self.assertEqual(data, b'123456789')
-
-        # Two transfer parameter requests should have been sent.
-        self.assertEqual(len(self._sent_chunks), 3)
-        self.assertTrue(self._sent_chunks[-1].HasField('status'))
-        self.assertEqual(self._sent_chunks[-1].status, 0)
-
-    def test_read_transfer_retry_timeout(self) -> None:
-        """Server doesn't respond to read transfer parameters."""
-        manager = pw_transfer.Manager(
-            self._service, default_response_timeout_s=DEFAULT_TIMEOUT_S)
-
-        self._enqueue_server_responses(
-            _Method.READ,
-            (
-                (),  # Send nothing in response to the initial parameters.
-                (Chunk(transfer_id=3, offset=0, data=b'xyz',
-                       remaining_bytes=0), ),
-            ))
-
-        data = manager.read(3)
-        self.assertEqual(data, b'xyz')
-
-        # Two transfer parameter requests should have been sent.
-        self.assertEqual(len(self._sent_chunks), 3)
-        self.assertTrue(self._sent_chunks[-1].HasField('status'))
-        self.assertEqual(self._sent_chunks[-1].status, 0)
-
-    def test_read_transfer_timeout(self) -> None:
-        manager = pw_transfer.Manager(
-            self._service, default_response_timeout_s=DEFAULT_TIMEOUT_S)
-
-        with self.assertRaises(pw_transfer.Error) as context:
-            manager.read(27)
-
-        exception = context.exception
-        self.assertEqual(exception.transfer_id, 27)
-        self.assertEqual(exception.status, Status.DEADLINE_EXCEEDED)
-
-        # The client should have sent four transfer parameters requests: one
-        # initial, and three retries.
-        self.assertEqual(len(self._sent_chunks), 4)
-
-    def test_read_transfer_error(self) -> None:
-        manager = pw_transfer.Manager(
-            self._service, default_response_timeout_s=DEFAULT_TIMEOUT_S)
-
-        self._enqueue_server_responses(
-            _Method.READ,
-            ((Chunk(transfer_id=31, status=Status.NOT_FOUND.value), ), ),
-        )
-
-        with self.assertRaises(pw_transfer.Error) as context:
-            manager.read(31)
-
-        exception = context.exception
-        self.assertEqual(exception.transfer_id, 31)
-        self.assertEqual(exception.status, Status.NOT_FOUND)
-
-    def test_read_transfer_server_error(self) -> None:
-        manager = pw_transfer.Manager(
-            self._service, default_response_timeout_s=DEFAULT_TIMEOUT_S)
-
-        self._enqueue_server_error(_Method.READ, Status.NOT_FOUND)
-
-        with self.assertRaises(pw_transfer.Error) as context:
-            manager.read(31)
-
-        exception = context.exception
-        self.assertEqual(exception.transfer_id, 31)
-        self.assertEqual(exception.status, Status.INTERNAL)
-
-    def test_write_transfer_basic(self) -> None:
-        manager = pw_transfer.Manager(
-            self._service, default_response_timeout_s=DEFAULT_TIMEOUT_S)
-
-        self._enqueue_server_responses(
-            _Method.WRITE,
-            (
-                (Chunk(transfer_id=4,
-                       offset=0,
-                       pending_bytes=32,
-                       max_chunk_size_bytes=8), ),
-                (Chunk(transfer_id=4, status=Status.OK.value), ),
-            ),
-        )
-
-        manager.write(4, b'hello')
-        self.assertEqual(len(self._sent_chunks), 2)
-        self.assertEqual(self._received_data(), b'hello')
-
-    def test_write_transfer_max_chunk_size(self) -> None:
-        manager = pw_transfer.Manager(
-            self._service, default_response_timeout_s=DEFAULT_TIMEOUT_S)
-
-        self._enqueue_server_responses(
-            _Method.WRITE,
-            (
-                (Chunk(transfer_id=4,
-                       offset=0,
-                       pending_bytes=32,
-                       max_chunk_size_bytes=8), ),
-                (),
-                (Chunk(transfer_id=4, status=Status.OK.value), ),
-            ),
-        )
-
-        manager.write(4, b'hello world')
-        self.assertEqual(len(self._sent_chunks), 3)
-        self.assertEqual(self._received_data(), b'hello world')
-        self.assertEqual(self._sent_chunks[1].data, b'hello wo')
-        self.assertEqual(self._sent_chunks[2].data, b'rld')
-
-    def test_write_transfer_multiple_parameters(self) -> None:
-        manager = pw_transfer.Manager(
-            self._service, default_response_timeout_s=DEFAULT_TIMEOUT_S)
-
-        self._enqueue_server_responses(
-            _Method.WRITE,
-            (
-                (Chunk(transfer_id=4,
-                       offset=0,
-                       pending_bytes=8,
-                       max_chunk_size_bytes=8), ),
-                (Chunk(transfer_id=4,
-                       offset=8,
-                       pending_bytes=8,
-                       max_chunk_size_bytes=8), ),
-                (Chunk(transfer_id=4, status=Status.OK.value), ),
-            ),
-        )
-
-        manager.write(4, b'data to write')
-        self.assertEqual(len(self._sent_chunks), 3)
-        self.assertEqual(self._received_data(), b'data to write')
-        self.assertEqual(self._sent_chunks[1].data, b'data to ')
-        self.assertEqual(self._sent_chunks[2].data, b'write')
-
-    def test_write_transfer_progress_callback(self) -> None:
-        manager = pw_transfer.Manager(
-            self._service, default_response_timeout_s=DEFAULT_TIMEOUT_S)
-
-        self._enqueue_server_responses(
-            _Method.WRITE,
-            (
-                (Chunk(transfer_id=4,
-                       offset=0,
-                       pending_bytes=8,
-                       max_chunk_size_bytes=8), ),
-                (Chunk(transfer_id=4,
-                       offset=8,
-                       pending_bytes=8,
-                       max_chunk_size_bytes=8), ),
-                (Chunk(transfer_id=4, status=Status.OK.value), ),
-            ),
-        )
-
-        progress: List[pw_transfer.ProgressStats] = []
-
-        manager.write(4, b'data to write', progress.append)
-        self.assertEqual(len(self._sent_chunks), 3)
-        self.assertEqual(self._received_data(), b'data to write')
-        self.assertEqual(self._sent_chunks[1].data, b'data to ')
-        self.assertEqual(self._sent_chunks[2].data, b'write')
-        self.assertEqual(progress, [
-            pw_transfer.ProgressStats(8, 0, 13),
-            pw_transfer.ProgressStats(13, 8, 13),
-            pw_transfer.ProgressStats(13, 13, 13)
-        ])
-
-    def test_write_transfer_rewind(self) -> None:
-        """Write transfer in which the server re-requests an earlier offset."""
-        manager = pw_transfer.Manager(
-            self._service, default_response_timeout_s=DEFAULT_TIMEOUT_S)
-
-        self._enqueue_server_responses(
-            _Method.WRITE,
-            (
-                (Chunk(transfer_id=4,
-                       offset=0,
-                       pending_bytes=8,
-                       max_chunk_size_bytes=8), ),
-                (Chunk(transfer_id=4,
-                       offset=8,
-                       pending_bytes=8,
-                       max_chunk_size_bytes=8), ),
-                (
-                    Chunk(
-                        transfer_id=4,
-                        offset=4,  # rewind
-                        pending_bytes=8,
-                        max_chunk_size_bytes=8), ),
-                (
-                    Chunk(
-                        transfer_id=4,
-                        offset=12,
-                        pending_bytes=16,  # update max size
-                        max_chunk_size_bytes=16), ),
-                (Chunk(transfer_id=4, status=Status.OK.value), ),
-            ),
-        )
-
-        manager.write(4, b'pigweed data transfer')
-        self.assertEqual(len(self._sent_chunks), 5)
-        self.assertEqual(self._sent_chunks[1].data, b'pigweed ')
-        self.assertEqual(self._sent_chunks[2].data, b'data tra')
-        self.assertEqual(self._sent_chunks[3].data, b'eed data')
-        self.assertEqual(self._sent_chunks[4].data, b' transfer')
-
-    def test_write_transfer_bad_offset(self) -> None:
-        manager = pw_transfer.Manager(
-            self._service, default_response_timeout_s=DEFAULT_TIMEOUT_S)
-
-        self._enqueue_server_responses(
-            _Method.WRITE,
-            (
-                (Chunk(transfer_id=4,
-                       offset=0,
-                       pending_bytes=8,
-                       max_chunk_size_bytes=8), ),
-                (
-                    Chunk(
-                        transfer_id=4,
-                        offset=100,  # larger offset than data
-                        pending_bytes=8,
-                        max_chunk_size_bytes=8), ),
-                (Chunk(transfer_id=4, status=Status.OK.value), ),
-            ),
-        )
-
-        with self.assertRaises(pw_transfer.Error) as context:
-            manager.write(4, b'small data')
-
-        exception = context.exception
-        self.assertEqual(exception.transfer_id, 4)
-        self.assertEqual(exception.status, Status.OUT_OF_RANGE)
-
-    def test_write_transfer_error(self) -> None:
-        manager = pw_transfer.Manager(
-            self._service, default_response_timeout_s=DEFAULT_TIMEOUT_S)
-
-        self._enqueue_server_responses(
-            _Method.WRITE,
-            ((Chunk(transfer_id=21, status=Status.UNAVAILABLE.value), ), ),
-        )
-
-        with self.assertRaises(pw_transfer.Error) as context:
-            manager.write(21, b'no write')
-
-        exception = context.exception
-        self.assertEqual(exception.transfer_id, 21)
-        self.assertEqual(exception.status, Status.UNAVAILABLE)
-
-    def test_write_transfer_server_error(self) -> None:
-        manager = pw_transfer.Manager(
-            self._service, default_response_timeout_s=DEFAULT_TIMEOUT_S)
-
-        self._enqueue_server_error(_Method.WRITE, Status.NOT_FOUND)
-
-        with self.assertRaises(pw_transfer.Error) as context:
-            manager.write(21, b'server error')
-
-        exception = context.exception
-        self.assertEqual(exception.transfer_id, 21)
-        self.assertEqual(exception.status, Status.INTERNAL)
-
-    def test_write_transfer_timeout_after_initial_chunk(self) -> None:
-        manager = pw_transfer.Manager(self._service,
-                                      default_response_timeout_s=0.001,
-                                      max_retries=2)
-
-        with self.assertRaises(pw_transfer.Error) as context:
-            manager.write(22, b'no server response!')
-
-        self.assertEqual(
-            self._sent_chunks,
-            [
-                Chunk(transfer_id=22,
-                      type=Chunk.Type.TRANSFER_START),  # initial chunk
-                Chunk(transfer_id=22,
-                      type=Chunk.Type.TRANSFER_START),  # retry 1
-                Chunk(transfer_id=22,
-                      type=Chunk.Type.TRANSFER_START),  # retry 2
-            ])
-
-        exception = context.exception
-        self.assertEqual(exception.transfer_id, 22)
-        self.assertEqual(exception.status, Status.DEADLINE_EXCEEDED)
-
-    def test_write_transfer_timeout_after_intermediate_chunk(self) -> None:
-        """Tests write transfers that timeout after the initial chunk."""
-        manager = pw_transfer.Manager(
-            self._service,
-            default_response_timeout_s=DEFAULT_TIMEOUT_S,
-            max_retries=2)
-
-        self._enqueue_server_responses(
-            _Method.WRITE,
-            [[Chunk(transfer_id=22, pending_bytes=10, max_chunk_size_bytes=5)]
-             ])
-
-        with self.assertRaises(pw_transfer.Error) as context:
-            manager.write(22, b'0123456789')
-
-        last_data_chunk = Chunk(transfer_id=22,
-                                data=b'56789',
-                                offset=5,
-                                remaining_bytes=0,
-                                type=Chunk.Type.TRANSFER_DATA)
-
-        self.assertEqual(
-            self._sent_chunks,
-            [
-                Chunk(transfer_id=22, type=Chunk.Type.TRANSFER_START),
-                Chunk(transfer_id=22,
-                      data=b'01234',
-                      type=Chunk.Type.TRANSFER_DATA),
-                last_data_chunk,  # last chunk
-                last_data_chunk,  # retry 1
-                last_data_chunk,  # retry 2
-            ])
-
-        exception = context.exception
-        self.assertEqual(exception.transfer_id, 22)
-        self.assertEqual(exception.status, Status.DEADLINE_EXCEEDED)
-
-    def test_write_zero_pending_bytes_is_internal_error(self) -> None:
-        manager = pw_transfer.Manager(
-            self._service, default_response_timeout_s=DEFAULT_TIMEOUT_S)
-
-        self._enqueue_server_responses(
-            _Method.WRITE,
-            ((Chunk(transfer_id=23, pending_bytes=0), ), ),
-        )
-
-        with self.assertRaises(pw_transfer.Error) as context:
-            manager.write(23, b'no write')
-
-        exception = context.exception
-        self.assertEqual(exception.transfer_id, 23)
-        self.assertEqual(exception.status, Status.INTERNAL)
-
-
-class ProgressStatsTest(unittest.TestCase):
-    def test_received_percent_known_total(self) -> None:
-        self.assertEqual(
-            pw_transfer.ProgressStats(75, 0, 100).percent_received(), 0.0)
-        self.assertEqual(
-            pw_transfer.ProgressStats(75, 50, 100).percent_received(), 50.0)
-        self.assertEqual(
-            pw_transfer.ProgressStats(100, 100, 100).percent_received(), 100.0)
-
-    def test_received_percent_unknown_total(self) -> None:
-        self.assertTrue(
-            math.isnan(
-                pw_transfer.ProgressStats(75, 50, None).percent_received()))
-        self.assertTrue(
-            math.isnan(
-                pw_transfer.ProgressStats(100, 100, None).percent_received()))
-
-    def test_str_known_total(self) -> None:
-        stats = str(pw_transfer.ProgressStats(75, 50, 100))
-        self.assertIn('75', stats)
-        self.assertIn('50', stats)
-        self.assertIn('100', stats)
-
-    def test_str_unknown_total(self) -> None:
-        stats = str(pw_transfer.ProgressStats(75, 50, None))
-        self.assertIn('75', stats)
-        self.assertIn('50', stats)
-        self.assertIn('unknown', stats)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/pw_transfer/rate_estimate.cc b/pw_transfer/rate_estimate.cc
deleted file mode 100644
index 09c26de..0000000
--- a/pw_transfer/rate_estimate.cc
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_transfer/rate_estimate.h"
-
-namespace pw::transfer {
-
-size_t RateEstimate::GetRateBytesPerSecond() const {
-  if (!start_time_.has_value()) {
-    return 0;
-  }
-
-  auto elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
-      chrono::SystemClock::now() - start_time_.value());
-  if (elapsed_ms.count() == 0) {
-    return 0;
-  }
-
-  constexpr unsigned int kMillsecondsPerSecond = 1000;
-  return (static_cast<uint64_t>(bytes_transferred_) * kMillsecondsPerSecond) /
-         elapsed_ms.count();
-}
-
-}  // namespace pw::transfer
diff --git a/pw_transfer/read.svg b/pw_transfer/read.svg
deleted file mode 100644
index f2a97ca..0000000
--- a/pw_transfer/read.svg
+++ /dev/null
@@ -1,116 +0,0 @@
-<!-- Created with blockdiag from the source below:
-
-  seqdiag {
-    default_note_color = aliceblue;
-
-    client -> server [
-        label = "set transfer parameters",
-        leftnote = "transfer_id\noffset\nwindow_end_offset\ntype=PARAMETERS_RETRANSMIT\nmax_chunk_size\nchunk_delay"
-    ];
-
-    client <-\- server [
-        noactivate,
-        label = "requested bytes\n(zero or more chunks)",
-        rightnote = "transfer_id\noffset\ndata\n(remaining_bytes)"
-    ];
-
-    client -\-> server [
-        noactivate,
-        label = "update transfer parameters\n(as needed)",
-        leftnote = "transfer_id\noffset\nwindow_end_offset\ntype=PARAMETERS_CONTINUE\n(max_chunk_size)\n(chunk_delay)"
-    ];
-
-    client <- server [
-        noactivate,
-        label = "final chunk",
-        rightnote = "transfer_id\noffset\ndata\nremaining_bytes=0"
-    ];
-
-    client -> server [
-        noactivate,
-        label = "acknowledge completion",
-        leftnote = "transfer_id\nstatus=OK"
-    ];
-  }
-
--->
-<svg height="652" viewBox="0 0 573 652" xmlns="http://www.w3.org/2000/svg" xmlns:inkspace="http://www.inkscape.org/namespaces/inkscape" xmlns:xlink="http://www.w3.org/1999/xlink" style="background-color:white">
-  <defs id="defs_block">
-    <filter height="1.504" id="filter_blur" inkspace:collect="always" width="1.1575" x="-0.07875" y="-0.252">
-      <feGaussianBlur id="feGaussianBlur3780" inkspace:collect="always" stdDeviation="4.2"></feGaussianBlur>
-    </filter>
-  </defs>
-  <title>pw_transfer Reads</title>
-  <desc></desc>
-  <rect fill="rgb(0,0,0)" height="464" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="8" x="221" y="166"></rect>
-  <rect fill="rgb(0,0,0)" height="464" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="8" x="413" y="166"></rect>
-  <polygon fill="rgb(0,0,0)" points="27,126 201,126 209,134 209,206 27,206 27,126" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></polygon>
-  <polygon fill="rgb(0,0,0)" points="433,246 552,246 560,254 560,300 433,300 433,246" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></polygon>
-  <polygon fill="rgb(0,0,0)" points="39,340 201,340 209,348 209,420 39,420 39,340" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></polygon>
-  <polygon fill="rgb(0,0,0)" points="433,460 552,460 560,468 560,514 433,514 433,460" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></polygon>
-  <polygon fill="rgb(0,0,0)" points="118,555 201,555 209,563 209,583 118,583 118,555" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></polygon>
-  <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="161" y="46"></rect>
-  <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="353" y="46"></rect>
-  <path d="M 222 80 L 222 640" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="8 4"></path>
-  <rect fill="moccasin" height="464" stroke="rgb(0,0,0)" width="8" x="218" y="160"></rect>
-  <path d="M 414 80 L 414 640" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="8 4"></path>
-  <rect fill="moccasin" height="464" stroke="rgb(0,0,0)" width="8" x="410" y="160"></rect>
-  <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="158" y="40"></rect>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="37" x="222" y="66">client</text>
-  <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="350" y="40"></rect>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="37" x="414" y="66">server</text>
-  <path d="M 230 160 L 406 160" fill="none" stroke="rgb(0,0,0)"></path>
-  <polygon fill="rgb(0,0,0)" points="398,156 406,160 398,164" stroke="rgb(0,0,0)"></polygon>
-  <polygon fill="rgb(240,248,255)" points="24,120 198,120 206,128 206,200 24,200 24,120" stroke="rgb(0,0,0)"></polygon>
-  <path d="M 198 120 L 198 128" fill="none" stroke="rgb(0,0,0)"></path>
-  <path d="M 198 128 L 206 128" fill="none" stroke="rgb(0,0,0)"></path>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="67" x="65" y="133">transfer_id</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="37" x="50" y="146">offset</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="103" x="83" y="159">window_end_offset</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="158" x="111" y="172">type=PARAMETERS_RETRANSMIT</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="85" x="74" y="185">max_chunk_size</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="67" x="65" y="198">chunk_delay</text>
-  <path d="M 230 267 L 406 267" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="4"></path>
-  <polygon fill="rgb(0,0,0)" points="238,263 230,267 238,271" stroke="rgb(0,0,0)"></polygon>
-  <polygon fill="rgb(240,248,255)" points="430,240 549,240 557,248 557,294 430,294 430,240" stroke="rgb(0,0,0)"></polygon>
-  <path d="M 549 240 L 549 248" fill="none" stroke="rgb(0,0,0)"></path>
-  <path d="M 549 248 L 557 248" fill="none" stroke="rgb(0,0,0)"></path>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="67" x="471" y="253">transfer_id</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="37" x="456" y="266">offset</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="25" x="450" y="279">data</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="103" x="489" y="292">(remaining_bytes)</text>
-  <path d="M 230 374 L 406 374" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="4"></path>
-  <polygon fill="rgb(0,0,0)" points="398,370 406,374 398,378" stroke="rgb(0,0,0)"></polygon>
-  <polygon fill="rgb(240,248,255)" points="36,334 198,334 206,342 206,414 36,414 36,334" stroke="rgb(0,0,0)"></polygon>
-  <path d="M 198 334 L 198 342" fill="none" stroke="rgb(0,0,0)"></path>
-  <path d="M 198 342 L 206 342" fill="none" stroke="rgb(0,0,0)"></path>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="67" x="77" y="347">transfer_id</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="37" x="62" y="360">offset</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="103" x="95" y="373">window_end_offset</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="146" x="117" y="386">type=PARAMETERS_CONTINUE</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="97" x="92" y="399">(max_chunk_size)</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="79" x="83" y="412">(chunk_delay)</text>
-  <path d="M 230 481 L 406 481" fill="none" stroke="rgb(0,0,0)"></path>
-  <polygon fill="rgb(0,0,0)" points="238,477 230,481 238,485" stroke="rgb(0,0,0)"></polygon>
-  <polygon fill="rgb(240,248,255)" points="430,454 549,454 557,462 557,508 430,508 430,454" stroke="rgb(0,0,0)"></polygon>
-  <path d="M 549 454 L 549 462" fill="none" stroke="rgb(0,0,0)"></path>
-  <path d="M 549 462 L 557 462" fill="none" stroke="rgb(0,0,0)"></path>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="67" x="471" y="467">transfer_id</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="37" x="456" y="480">offset</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="25" x="450" y="493">data</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="103" x="489" y="506">remaining_bytes=0</text>
-  <path d="M 230 563 L 406 563" fill="none" stroke="rgb(0,0,0)"></path>
-  <polygon fill="rgb(0,0,0)" points="398,559 406,563 398,567" stroke="rgb(0,0,0)"></polygon>
-  <polygon fill="rgb(240,248,255)" points="115,549 198,549 206,557 206,577 115,577 115,549" stroke="rgb(0,0,0)"></polygon>
-  <path d="M 198 549 L 198 557" fill="none" stroke="rgb(0,0,0)"></path>
-  <path d="M 198 557 L 206 557" fill="none" stroke="rgb(0,0,0)"></path>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="67" x="156" y="562">transfer_id</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="55" x="150" y="575">status=OK</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="140" x="304" y="158">set transfer parameters</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="91" x="360" y="252">requested bytes</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="128" x="342" y="265">(zero or more chunks)</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="158" x="313" y="359">update transfer parameters</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="67" x="267" y="372">(as needed)</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="67" x="372" y="479">final chunk</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="134" x="301" y="561">acknowledge completion</text>
-</svg>
diff --git a/pw_transfer/server_context.cc b/pw_transfer/server_context.cc
deleted file mode 100644
index 353258f..0000000
--- a/pw_transfer/server_context.cc
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 PW_LOG_MODULE_NAME "TRN"
-
-#include "pw_transfer/internal/server_context.h"
-
-#include "pw_assert/check.h"
-#include "pw_log/log.h"
-#include "pw_status/try.h"
-#include "pw_transfer/internal/chunk.h"
-#include "pw_transfer/transfer.pwpb.h"
-#include "pw_varint/varint.h"
-
-namespace pw::transfer::internal {
-
-Status ServerContext::FinalCleanup(const Status status) {
-  PW_DCHECK(active());
-
-  // If no handler is set, then the Prepare call failed. Nothing to do.
-  if (handler_ == nullptr) {
-    return OkStatus();
-  }
-
-  Handler& handler = *handler_;
-  handler_ = nullptr;
-
-  if (type() == TransferType::kTransmit) {
-    handler.FinalizeRead(status);
-    return OkStatus();
-  }
-
-  if (Status finalized = handler.FinalizeWrite(status); !finalized.ok()) {
-    PW_LOG_ERROR(
-        "FinalizeWrite() for transfer %u failed with status %u; aborting with "
-        "DATA_LOSS",
-        static_cast<unsigned>(handler.id()),
-        static_cast<int>(finalized.code()));
-    return Status::DataLoss();
-  }
-
-  return OkStatus();
-}
-
-}  // namespace pw::transfer::internal
diff --git a/pw_transfer/test_rpc_server.cc b/pw_transfer/test_rpc_server.cc
deleted file mode 100644
index e0f8e67..0000000
--- a/pw_transfer/test_rpc_server.cc
+++ /dev/null
@@ -1,151 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// Simple RPC server with the transfer service registered. Reads HDLC frames
-// with RPC packets through a socket. The transfer service reads and writes to
-// files within a given directory. The name of a file is its transfer ID.
-
-#include <cstddef>
-#include <filesystem>
-#include <string>
-#include <thread>
-#include <variant>
-#include <vector>
-
-#include "pw_assert/check.h"
-#include "pw_log/log.h"
-#include "pw_rpc_system_server/rpc_server.h"
-#include "pw_rpc_system_server/socket.h"
-#include "pw_stream/std_file_stream.h"
-#include "pw_thread/detached_thread.h"
-#include "pw_thread_stl/options.h"
-#include "pw_transfer/transfer.h"
-#include "pw_transfer_test/test_server.raw_rpc.pb.h"
-
-namespace pw::transfer {
-namespace {
-
-class FileTransferHandler final : public ReadWriteHandler {
- public:
-  FileTransferHandler(TransferService& service,
-                      uint32_t transfer_id,
-                      const char* path)
-      : ReadWriteHandler(transfer_id), service_(service), path_(path) {
-    service_.RegisterHandler(*this);
-  }
-
-  ~FileTransferHandler() { service_.UnregisterHandler(*this); }
-
-  Status PrepareRead() final {
-    PW_LOG_DEBUG("Preparing read for file %s", path_.c_str());
-    set_reader(stream_.emplace<stream::StdFileReader>(path_.c_str()));
-    return OkStatus();
-  }
-
-  void FinalizeRead(Status) final {
-    std::get<stream::StdFileReader>(stream_).Close();
-  }
-
-  Status PrepareWrite() final {
-    PW_LOG_DEBUG("Preparing write for file %s", path_.c_str());
-    set_writer(stream_.emplace<stream::StdFileWriter>(path_.c_str()));
-    return OkStatus();
-  }
-
-  Status FinalizeWrite(Status) final {
-    std::get<stream::StdFileWriter>(stream_).Close();
-    return OkStatus();
-  }
-
- private:
-  TransferService& service_;
-  std::string path_;
-  std::variant<std::monostate, stream::StdFileReader, stream::StdFileWriter>
-      stream_;
-};
-
-class TestServerService
-    : public pw_rpc::raw::TestServer::Service<TestServerService> {
- public:
-  TestServerService(TransferService& transfer_service)
-      : transfer_service_(transfer_service) {}
-
-  void set_directory(const char* directory) { directory_ = directory; }
-
-  void ReloadTransferFiles(ConstByteSpan, rpc::RawUnaryResponder&) {
-    LoadFileHandlers();
-  }
-
-  void LoadFileHandlers() {
-    PW_LOG_INFO("Reloading file handlers from %s", directory_.c_str());
-    file_transfer_handlers_.clear();
-
-    for (const auto& entry : std::filesystem::directory_iterator(directory_)) {
-      if (!entry.is_regular_file()) {
-        continue;
-      }
-
-      int transfer_id = std::atoi(entry.path().filename().c_str());
-      if (transfer_id > 0) {
-        PW_LOG_DEBUG("Found transfer file %d", transfer_id);
-        file_transfer_handlers_.emplace_back(
-            std::make_shared<FileTransferHandler>(
-                transfer_service_, transfer_id, entry.path().c_str()));
-      }
-    }
-  }
-
- private:
-  TransferService& transfer_service_;
-  std::string directory_;
-  std::vector<std::shared_ptr<FileTransferHandler>> file_transfer_handlers_;
-};
-
-constexpr size_t kChunkSizeBytes = 256;
-constexpr size_t kMaxReceiveSizeBytes = 1024;
-
-std::array<std::byte, kChunkSizeBytes> chunk_buffer;
-std::array<std::byte, kChunkSizeBytes> encode_buffer;
-transfer::Thread<4, 4> transfer_thread(chunk_buffer, encode_buffer);
-TransferService transfer_service(transfer_thread, kMaxReceiveSizeBytes);
-TestServerService test_server_service(transfer_service);
-
-void RunServer(int socket_port, const char* directory) {
-  rpc::system_server::set_socket_port(socket_port);
-
-  test_server_service.set_directory(directory);
-  test_server_service.LoadFileHandlers();
-
-  rpc::system_server::Init();
-  rpc::system_server::Server().RegisterService(test_server_service,
-                                               transfer_service);
-
-  thread::DetachedThread(thread::stl::Options(), transfer_thread);
-
-  PW_LOG_INFO("Starting pw_rpc server");
-  PW_CHECK_OK(rpc::system_server::Start());
-}
-
-}  // namespace
-}  // namespace pw::transfer
-
-int main(int argc, char* argv[]) {
-  if (argc != 3) {
-    PW_LOG_ERROR("Usage: %s PORT DIR", argv[0]);
-    return 1;
-  }
-
-  pw::transfer::RunServer(std::atoi(argv[1]), argv[2]);
-  return 0;
-}
diff --git a/pw_transfer/test_server.proto b/pw_transfer/test_server.proto
deleted file mode 100644
index c7b658e..0000000
--- a/pw_transfer/test_server.proto
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-syntax = "proto3";
-
-import 'pw_protobuf_protos/common.proto';
-
-package pw.transfer;
-
-// Manages the transfer service test RPC server (test_rpc_server.cc).
-service TestServer {
-  rpc ReloadTransferFiles(pw.protobuf.Empty) returns (pw.protobuf.Empty);
-}
diff --git a/pw_transfer/transfer.cc b/pw_transfer/transfer.cc
deleted file mode 100644
index 142ecb3..0000000
--- a/pw_transfer/transfer.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_transfer/transfer.h"
-
-#include "pw_assert/check.h"
-#include "pw_log/log.h"
-#include "pw_status/try.h"
-#include "pw_transfer/internal/chunk.h"
-
-namespace pw::transfer {
-
-void TransferService::HandleChunk(ConstByteSpan message,
-                                  internal::TransferType type) {
-  internal::Chunk chunk;
-  if (Status status = internal::DecodeChunk(message, chunk); !status.ok()) {
-    PW_LOG_ERROR("Failed to decode transfer chunk: %d", status.code());
-    return;
-  }
-
-  if (chunk.IsInitialChunk()) {
-    // TODO(frolv): Right now, transfer ID and handler ID are the same thing.
-    // The transfer ID should be made into a unique session ID instead.
-    thread_.StartServerTransfer(type,
-                                chunk.transfer_id,
-                                chunk.transfer_id,
-                                max_parameters_,
-                                chunk_timeout_,
-                                max_retries_);
-  }
-
-  thread_.ProcessServerChunk(message);
-}
-
-}  // namespace pw::transfer
diff --git a/pw_transfer/transfer.proto b/pw_transfer/transfer.proto
deleted file mode 100644
index 6f598a0..0000000
--- a/pw_transfer/transfer.proto
+++ /dev/null
@@ -1,172 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-syntax = "proto3";
-
-package pw.transfer;
-
-// The transfer RPC service is used to send data between the client and server.
-service Transfer {
-  // Transfer data from the server to the client; a "download" from the client's
-  // perspective.
-  rpc Read(stream Chunk) returns (stream Chunk);
-
-  // Transfer data from the client to the server; an "upload" from the client's
-  // perspective.
-  rpc Write(stream Chunk) returns (stream Chunk);
-}
-
-// Represents a chunk of data sent by the transfer service. Includes fields for
-// configuring the transfer parameters.
-//
-// Notation: (Read|Write) (→|←)
-//   X → Means client sending data to the server.
-//   X ← Means server sending data to the client.
-message Chunk {
-  // Represents the source or destination of the data. May be ephemeral or
-  // stable depending on the implementation. Sent in every request to identify
-  // the transfer target.
-  //
-  //  Read → ID of transfer
-  //  Read ← ID of transfer
-  // Write → ID of transfer
-  // Write ← ID of transfer
-  uint32 transfer_id = 1;
-
-  // Used by the receiver to indicate how many bytes it can accept. The
-  // transmitter sends this much data, divided into chunks no larger than
-  // max_chunk_size_bytes. The receiver then starts another window by sending
-  // request_bytes again with a new offset.
-  //
-  //  Read → The client requests this many bytes to be sent.
-  //  Read ← N/A
-  // Write → N/A
-  // Write ← The server requests this many bytes to be sent.
-  optional uint32 pending_bytes = 2;
-
-  // Maximum size of an individual chunk. The transmitter may send smaller
-  // chunks if required.
-  //
-  //  Read → Set maximum size for subsequent chunks.
-  //  Read ← N/A
-  // Write → N/A
-  // Write ← Set maximum size for subsequent chunks.
-  optional uint32 max_chunk_size_bytes = 3;
-
-  // Minimum required delay between chunks. The transmitter may delay longer if
-  // desired.
-  //
-  //  Read → Set minimum delay for subsequent chunks.
-  //  Read ← N/A
-  // Write → N/A
-  // Write ← Set minimum delay for subsequent chunks.
-  optional uint32 min_delay_microseconds = 4;
-
-  // On writes, the offset of the data. On reads, the offset at which to read.
-  //
-  //  Read → Read data starting at this offset.
-  //  Read ← Offset of the data.
-  // Write → Offset of the data.
-  // Write ← Write data starting at this offset.
-  uint64 offset = 5;
-
-  // The data that was read or the data to write.
-  //
-  //  Read → N/A
-  //  Read ← Data read
-  // Write → Data to write
-  // Write ← N/A
-  bytes data = 6;
-
-  // Estimated bytes remaining to read/write. Optional except for the last data
-  // chunk, for which remaining_bytes must be set to 0.
-  //
-  // The sender can set remaining_bytes at the beginning of a read/write so that
-  // the receiver can track progress or cancel the transaction if the value is
-  // too large.
-  //
-  //  Read → N/A
-  //  Read ← Remaining bytes to read, excluding any data in this chunk. Set to
-  //         0 for the last chunk.
-  // Write → Remaining bytes to write, excluding any data in is chunk. Set to
-  //         0 for the last chunk.
-  // Write ← N/A
-  optional uint64 remaining_bytes = 7;
-
-  // Pigweed status code indicating the completion of a transfer. This is only
-  // present in the final packet sent by either the transmitter or receiver.
-  //
-  // The possible status codes and their meanings are listed below:
-  //
-  //   OK: Transfer completed successfully.
-  //   DATA_LOSS: Transfer data could not be read/written (e.g. corruption).
-  //   INVALID_ARGUMENT: Received malformed chunk.
-  //   NOT_FOUND: The requested transfer ID is not registered (read/write).
-  //   OUT_OF_RANGE: The requested offset is larger than the data (read/write).
-  //   RESOURCE_EXHAUSTED: Concurrent transfer limit reached.
-  //   UNIMPLEMENTED: Transfer ID does not support requested operation (e.g.
-  //       trying to write to a read-only transfer).
-  //
-  //  Read → Transfer complete.
-  //  Read ← Transfer complete.
-  // Write → Transfer complete.
-  // Write ← Transfer complete.
-  optional uint32 status = 8;
-
-  // The offset up to which the transmitter can send data before waiting for the
-  // receiver to acknowledge.
-  //
-  //  Read → Offset up to which the server can send without blocking.
-  //  Read ← N/A
-  // Write → N/A
-  // Write ← Offset up to which the client can send without blocking.
-  //
-  // TODO(frolv): This will replace the pending_bytes field. Once all uses of
-  // transfer are migrated, that field should be removed.
-  uint32 window_end_offset = 9;
-
-  enum Type {
-    // Chunk containing transfer data.
-    TRANSFER_DATA = 0;
-
-    // First chunk of a transfer (only sent by the client).
-    TRANSFER_START = 1;
-
-    // Transfer parameters indicating that the transmitter should retransmit
-    // from the specified offset.
-    PARAMETERS_RETRANSMIT = 2;
-
-    // Transfer parameters telling the transmitter to continue sending up to
-    // index `offset + pending_bytes` of data. If the transmitter is already
-    // beyond `offset`, it does not have to rewind.
-    PARAMETERS_CONTINUE = 3;
-
-    // Sender of the chunk is terminating the transfer.
-    TRANSFER_COMPLETION = 4;
-
-    // Acknowledge the completion of a transfer. Currently unused.
-    // TODO(konkers): Implement this behavior.
-    TRANSFER_COMPLETION_ACK = 5;
-  };
-
-  // The type of this chunk. This field should only be processed when present.
-  // TODO(frolv): Update all users of pw_transfer and remove the optional
-  // semantics from this field.
-  //
-  //  Read → Chunk type (start/parameters).
-  //  Read ← Chunk type (data).
-  // Write → Chunk type (data).
-  // Write ← Chunk type (start/parameters).
-  optional Type type = 10;
-}
diff --git a/pw_transfer/transfer_test.cc b/pw_transfer/transfer_test.cc
deleted file mode 100644
index 76cfb01..0000000
--- a/pw_transfer/transfer_test.cc
+++ /dev/null
@@ -1,1547 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_transfer/transfer.h"
-
-#include "gtest/gtest.h"
-#include "pw_bytes/array.h"
-#include "pw_rpc/raw/test_method_context.h"
-#include "pw_rpc/thread_testing.h"
-#include "pw_thread/thread.h"
-#include "pw_thread_stl/options.h"
-#include "pw_transfer/transfer.pwpb.h"
-#include "pw_transfer_private/chunk_testing.h"
-
-namespace pw::transfer::test {
-namespace {
-
-using namespace std::chrono_literals;
-
-PW_MODIFY_DIAGNOSTICS_PUSH();
-PW_MODIFY_DIAGNOSTIC(ignored, "-Wmissing-field-initializers");
-
-// TODO(frolv): Have a generic way to obtain a thread for testing on any system.
-thread::Options& TransferThreadOptions() {
-  static thread::stl::Options options;
-  return options;
-}
-
-using internal::Chunk;
-
-class TestMemoryReader : public stream::SeekableReader {
- public:
-  constexpr TestMemoryReader(std::span<const std::byte> data)
-      : memory_reader_(data) {}
-
-  Status DoSeek(ptrdiff_t offset, Whence origin) override {
-    if (seek_status.ok()) {
-      return memory_reader_.Seek(offset, origin);
-    }
-    return seek_status;
-  }
-
-  StatusWithSize DoRead(ByteSpan dest) final {
-    if (!read_status.ok()) {
-      return StatusWithSize(read_status, 0);
-    }
-
-    auto result = memory_reader_.Read(dest);
-    return result.ok() ? StatusWithSize(result->size())
-                       : StatusWithSize(result.status(), 0);
-  }
-
-  Status seek_status;
-  Status read_status;
-
- private:
-  stream::MemoryReader memory_reader_;
-};
-
-class SimpleReadTransfer final : public ReadOnlyHandler {
- public:
-  SimpleReadTransfer(uint32_t transfer_id, ConstByteSpan data)
-      : ReadOnlyHandler(transfer_id),
-        prepare_read_called(false),
-        finalize_read_called(false),
-        finalize_read_status(Status::Unknown()),
-        reader_(data) {}
-
-  Status PrepareRead() final {
-    prepare_read_called = true;
-
-    if (!prepare_read_return_status.ok()) {
-      return prepare_read_return_status;
-    }
-
-    EXPECT_EQ(reader_.seek_status, reader_.Seek(0));
-    set_reader(reader_);
-    return OkStatus();
-  }
-
-  void FinalizeRead(Status status) final {
-    finalize_read_called = true;
-    finalize_read_status = status;
-  }
-
-  void set_seek_status(Status status) { reader_.seek_status = status; }
-  void set_read_status(Status status) { reader_.read_status = status; }
-
-  bool prepare_read_called;
-  bool finalize_read_called;
-  Status prepare_read_return_status;
-  Status finalize_read_status;
-
- private:
-  TestMemoryReader reader_;
-};
-
-constexpr auto kData = bytes::Initialized<32>([](size_t i) { return i; });
-
-class ReadTransfer : public ::testing::Test {
- protected:
-  ReadTransfer(size_t max_chunk_size_bytes = 64)
-      : handler_(3, kData),
-        transfer_thread_(std::span(data_buffer_).first(max_chunk_size_bytes),
-                         encode_buffer_),
-        ctx_(transfer_thread_, 64),
-        system_thread_(TransferThreadOptions(), transfer_thread_) {
-    ctx_.service().RegisterHandler(handler_);
-
-    ASSERT_FALSE(handler_.prepare_read_called);
-    ASSERT_FALSE(handler_.finalize_read_called);
-
-    ctx_.call();  // Open the read stream
-    transfer_thread_.WaitUntilEventIsProcessed();
-  }
-
-  ~ReadTransfer() {
-    transfer_thread_.Terminate();
-    system_thread_.join();
-  }
-
-  SimpleReadTransfer handler_;
-  Thread<1, 1> transfer_thread_;
-  PW_RAW_TEST_METHOD_CONTEXT(TransferService, Read) ctx_;
-  thread::Thread system_thread_;
-  std::array<std::byte, 64> data_buffer_;
-  std::array<std::byte, 64> encode_buffer_;
-};
-
-TEST_F(ReadTransfer, SingleChunk) {
-  rpc::test::WaitForPackets(ctx_.output(), 2, [this] {
-    ctx_.SendClientStream(EncodeChunk({.transfer_id = 3,
-                                       .window_end_offset = 64,
-                                       .pending_bytes = 64,
-                                       .offset = 0,
-                                       .type = Chunk::Type::kTransferStart}));
-
-    transfer_thread_.WaitUntilEventIsProcessed();
-  });
-
-  EXPECT_TRUE(handler_.prepare_read_called);
-  EXPECT_FALSE(handler_.finalize_read_called);
-
-  ASSERT_EQ(ctx_.total_responses(), 2u);
-  Chunk c0 = DecodeChunk(ctx_.responses()[0]);
-  Chunk c1 = DecodeChunk(ctx_.responses()[1]);
-
-  // First chunk should have all the read data.
-  EXPECT_EQ(c0.transfer_id, 3u);
-  EXPECT_EQ(c0.offset, 0u);
-  ASSERT_EQ(c0.data.size(), kData.size());
-  EXPECT_EQ(std::memcmp(c0.data.data(), kData.data(), c0.data.size()), 0);
-
-  // Second chunk should be empty and set remaining_bytes = 0.
-  EXPECT_EQ(c1.transfer_id, 3u);
-  EXPECT_EQ(c1.data.size(), 0u);
-  ASSERT_TRUE(c1.remaining_bytes.has_value());
-  EXPECT_EQ(c1.remaining_bytes.value(), 0u);
-
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 3, .status = OkStatus()}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_TRUE(handler_.finalize_read_called);
-  EXPECT_EQ(handler_.finalize_read_status, OkStatus());
-}
-
-TEST_F(ReadTransfer, PendingBytes_SingleChunk) {
-  rpc::test::WaitForPackets(ctx_.output(), 2, [this] {
-    ctx_.SendClientStream(EncodeChunk({.transfer_id = 3,
-                                       .pending_bytes = 64,
-                                       .offset = 0,
-                                       .type = Chunk::Type::kTransferStart}));
-
-    transfer_thread_.WaitUntilEventIsProcessed();
-  });
-
-  EXPECT_TRUE(handler_.prepare_read_called);
-  EXPECT_FALSE(handler_.finalize_read_called);
-
-  ASSERT_EQ(ctx_.total_responses(), 2u);
-  Chunk c0 = DecodeChunk(ctx_.responses()[0]);
-  Chunk c1 = DecodeChunk(ctx_.responses()[1]);
-
-  // First chunk should have all the read data.
-  EXPECT_EQ(c0.transfer_id, 3u);
-  EXPECT_EQ(c0.offset, 0u);
-  ASSERT_EQ(c0.data.size(), kData.size());
-  EXPECT_EQ(std::memcmp(c0.data.data(), kData.data(), c0.data.size()), 0);
-
-  // Second chunk should be empty and set remaining_bytes = 0.
-  EXPECT_EQ(c1.transfer_id, 3u);
-  EXPECT_EQ(c1.data.size(), 0u);
-  ASSERT_TRUE(c1.remaining_bytes.has_value());
-  EXPECT_EQ(c1.remaining_bytes.value(), 0u);
-
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 3, .status = OkStatus()}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_TRUE(handler_.finalize_read_called);
-  EXPECT_EQ(handler_.finalize_read_status, OkStatus());
-}
-
-TEST_F(ReadTransfer, MultiChunk) {
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 3,
-                                     .window_end_offset = 16,
-                                     .pending_bytes = 16,
-                                     .offset = 0,
-                                     .type = Chunk::Type::kTransferStart}));
-
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_TRUE(handler_.prepare_read_called);
-  EXPECT_FALSE(handler_.finalize_read_called);
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-  Chunk c0 = DecodeChunk(ctx_.responses()[0]);
-
-  EXPECT_EQ(c0.transfer_id, 3u);
-  EXPECT_EQ(c0.offset, 0u);
-  ASSERT_EQ(c0.data.size(), 16u);
-  EXPECT_EQ(std::memcmp(c0.data.data(), kData.data(), c0.data.size()), 0);
-
-  ctx_.SendClientStream(
-      EncodeChunk({.transfer_id = 3,
-                   .window_end_offset = 32,
-                   .pending_bytes = 16,
-                   .offset = 16,
-                   .type = Chunk::Type::kParametersContinue}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 2u);
-  Chunk c1 = DecodeChunk(ctx_.responses()[1]);
-
-  EXPECT_EQ(c1.transfer_id, 3u);
-  EXPECT_EQ(c1.offset, 16u);
-  ASSERT_EQ(c1.data.size(), 16u);
-  EXPECT_EQ(std::memcmp(c1.data.data(), kData.data() + 16, c1.data.size()), 0);
-
-  ctx_.SendClientStream(
-      EncodeChunk({.transfer_id = 3,
-                   .window_end_offset = 48,
-                   .pending_bytes = 16,
-                   .offset = 32,
-                   .type = Chunk::Type::kParametersContinue}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 3u);
-  Chunk c2 = DecodeChunk(ctx_.responses()[2]);
-
-  EXPECT_EQ(c2.transfer_id, 3u);
-  EXPECT_EQ(c2.data.size(), 0u);
-  ASSERT_TRUE(c2.remaining_bytes.has_value());
-  EXPECT_EQ(c2.remaining_bytes.value(), 0u);
-
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 3, .status = OkStatus()}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_TRUE(handler_.finalize_read_called);
-  EXPECT_EQ(handler_.finalize_read_status, OkStatus());
-}
-
-TEST_F(ReadTransfer, MultiChunk_RepeatedContinuePackets) {
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 3,
-                                     .window_end_offset = 16,
-                                     .pending_bytes = 16,
-                                     .offset = 0,
-                                     .type = Chunk::Type::kTransferStart}));
-
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  const auto continue_chunk =
-      EncodeChunk({.transfer_id = 3,
-                   .window_end_offset = 24,
-                   .pending_bytes = 8,
-                   .offset = 16,
-                   .type = Chunk::Type::kParametersContinue});
-  ctx_.SendClientStream(continue_chunk);
-
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // Resend the CONTINUE packets that don't actually advance the window.
-  for (int i = 0; i < 3; ++i) {
-    ctx_.SendClientStream(continue_chunk);
-    transfer_thread_.WaitUntilEventIsProcessed();
-  }
-
-  ASSERT_EQ(ctx_.total_responses(), 2u);  // Only sent one packet
-  Chunk c1 = DecodeChunk(ctx_.responses()[1]);
-
-  EXPECT_EQ(c1.transfer_id, 3u);
-  EXPECT_EQ(c1.offset, 16u);
-  ASSERT_EQ(c1.data.size(), 8u);
-  EXPECT_EQ(std::memcmp(c1.data.data(), kData.data() + 16, c1.data.size()), 0);
-}
-
-TEST_F(ReadTransfer, PendingBytes_MultiChunk) {
-  ctx_.SendClientStream(
-      EncodeChunk({.transfer_id = 3, .pending_bytes = 16, .offset = 0}));
-
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_TRUE(handler_.prepare_read_called);
-  EXPECT_FALSE(handler_.finalize_read_called);
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-  Chunk c0 = DecodeChunk(ctx_.responses()[0]);
-
-  EXPECT_EQ(c0.transfer_id, 3u);
-  EXPECT_EQ(c0.offset, 0u);
-  ASSERT_EQ(c0.data.size(), 16u);
-  EXPECT_EQ(std::memcmp(c0.data.data(), kData.data(), c0.data.size()), 0);
-
-  ctx_.SendClientStream(
-      EncodeChunk({.transfer_id = 3, .pending_bytes = 16, .offset = 16}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 2u);
-  Chunk c1 = DecodeChunk(ctx_.responses()[1]);
-
-  EXPECT_EQ(c1.transfer_id, 3u);
-  EXPECT_EQ(c1.offset, 16u);
-  ASSERT_EQ(c1.data.size(), 16u);
-  EXPECT_EQ(std::memcmp(c1.data.data(), kData.data() + 16, c1.data.size()), 0);
-
-  ctx_.SendClientStream(
-      EncodeChunk({.transfer_id = 3, .pending_bytes = 16, .offset = 32}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 3u);
-  Chunk c2 = DecodeChunk(ctx_.responses()[2]);
-
-  EXPECT_EQ(c2.transfer_id, 3u);
-  EXPECT_EQ(c2.data.size(), 0u);
-  ASSERT_TRUE(c2.remaining_bytes.has_value());
-  EXPECT_EQ(c2.remaining_bytes.value(), 0u);
-
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 3, .status = OkStatus()}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_TRUE(handler_.finalize_read_called);
-  EXPECT_EQ(handler_.finalize_read_status, OkStatus());
-}
-
-TEST_F(ReadTransfer, OutOfOrder_SeekingSupported) {
-  rpc::test::WaitForPackets(ctx_.output(), 4, [this] {
-    ctx_.SendClientStream(
-        EncodeChunk({.transfer_id = 3, .pending_bytes = 16, .offset = 0}));
-
-    transfer_thread_.WaitUntilEventIsProcessed();
-
-    Chunk chunk = DecodeChunk(ctx_.responses().back());
-    EXPECT_TRUE(std::equal(
-        &kData[0], &kData[16], chunk.data.begin(), chunk.data.end()));
-
-    ctx_.SendClientStream(
-        EncodeChunk({.transfer_id = 3, .pending_bytes = 8, .offset = 2}));
-
-    transfer_thread_.WaitUntilEventIsProcessed();
-
-    chunk = DecodeChunk(ctx_.responses().back());
-    EXPECT_TRUE(std::equal(
-        &kData[2], &kData[10], chunk.data.begin(), chunk.data.end()));
-
-    ctx_.SendClientStream(
-        EncodeChunk({.transfer_id = 3, .pending_bytes = 64, .offset = 17}));
-  });
-
-  ASSERT_EQ(ctx_.total_responses(), 4u);
-  Chunk chunk = DecodeChunk(ctx_.responses()[2]);
-  EXPECT_TRUE(std::equal(
-      &kData[17], kData.end(), chunk.data.begin(), chunk.data.end()));
-}
-
-TEST_F(ReadTransfer, OutOfOrder_SeekingNotSupported_EndsWithUnimplemented) {
-  handler_.set_seek_status(Status::Unimplemented());
-
-  ctx_.SendClientStream(
-      EncodeChunk({.transfer_id = 3, .pending_bytes = 16, .offset = 0}));
-  ctx_.SendClientStream(
-      EncodeChunk({.transfer_id = 3, .pending_bytes = 8, .offset = 2}));
-
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 2u);
-  Chunk chunk = DecodeChunk(ctx_.responses().back());
-  EXPECT_EQ(chunk.status, Status::Unimplemented());
-}
-
-TEST_F(ReadTransfer, MaxChunkSize_Client) {
-  rpc::test::WaitForPackets(ctx_.output(), 5, [this] {
-    ctx_.SendClientStream(EncodeChunk({.transfer_id = 3,
-                                       .pending_bytes = 64,
-                                       .max_chunk_size_bytes = 8,
-                                       .offset = 0,
-                                       .type = Chunk::Type::kTransferStart}));
-  });
-
-  EXPECT_TRUE(handler_.prepare_read_called);
-  EXPECT_FALSE(handler_.finalize_read_called);
-
-  ASSERT_EQ(ctx_.total_responses(), 5u);
-  Chunk c0 = DecodeChunk(ctx_.responses()[0]);
-  Chunk c1 = DecodeChunk(ctx_.responses()[1]);
-  Chunk c2 = DecodeChunk(ctx_.responses()[2]);
-  Chunk c3 = DecodeChunk(ctx_.responses()[3]);
-  Chunk c4 = DecodeChunk(ctx_.responses()[4]);
-
-  EXPECT_EQ(c0.transfer_id, 3u);
-  EXPECT_EQ(c0.offset, 0u);
-  ASSERT_EQ(c0.data.size(), 8u);
-  EXPECT_EQ(std::memcmp(c0.data.data(), kData.data(), c0.data.size()), 0);
-
-  EXPECT_EQ(c1.transfer_id, 3u);
-  EXPECT_EQ(c1.offset, 8u);
-  ASSERT_EQ(c1.data.size(), 8u);
-  EXPECT_EQ(std::memcmp(c1.data.data(), kData.data() + 8, c1.data.size()), 0);
-
-  EXPECT_EQ(c2.transfer_id, 3u);
-  EXPECT_EQ(c2.offset, 16u);
-  ASSERT_EQ(c2.data.size(), 8u);
-  EXPECT_EQ(std::memcmp(c2.data.data(), kData.data() + 16, c2.data.size()), 0);
-
-  EXPECT_EQ(c3.transfer_id, 3u);
-  EXPECT_EQ(c3.offset, 24u);
-  ASSERT_EQ(c3.data.size(), 8u);
-  EXPECT_EQ(std::memcmp(c3.data.data(), kData.data() + 24, c3.data.size()), 0);
-
-  EXPECT_EQ(c4.transfer_id, 3u);
-  EXPECT_EQ(c4.data.size(), 0u);
-  ASSERT_TRUE(c4.remaining_bytes.has_value());
-  EXPECT_EQ(c4.remaining_bytes.value(), 0u);
-
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 3, .status = OkStatus()}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_TRUE(handler_.finalize_read_called);
-  EXPECT_EQ(handler_.finalize_read_status, OkStatus());
-}
-
-TEST_F(ReadTransfer, HandlerIsClearedAfterTransfer) {
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 3,
-                                     .window_end_offset = 64,
-                                     .pending_bytes = 64,
-                                     .offset = 0,
-                                     .type = Chunk::Type::kTransferStart}));
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 3, .status = OkStatus()}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-  ASSERT_TRUE(handler_.prepare_read_called);
-  ASSERT_TRUE(handler_.finalize_read_called);
-  ASSERT_EQ(OkStatus(), handler_.finalize_read_status);
-
-  // Now, clear state and start a second transfer
-  handler_.prepare_read_return_status = Status::FailedPrecondition();
-  handler_.prepare_read_called = false;
-  handler_.finalize_read_called = false;
-
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 3,
-                                     .window_end_offset = 64,
-                                     .pending_bytes = 64,
-                                     .offset = 0,
-                                     .type = Chunk::Type::kTransferStart}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // Prepare failed, so the handler should not have been stored in the context,
-  // and finalize should not have been called.
-  ASSERT_TRUE(handler_.prepare_read_called);
-  ASSERT_FALSE(handler_.finalize_read_called);
-}
-
-class ReadTransferMaxChunkSize8 : public ReadTransfer {
- protected:
-  ReadTransferMaxChunkSize8() : ReadTransfer(/*max_chunk_size_bytes=*/8) {}
-};
-
-TEST_F(ReadTransferMaxChunkSize8, MaxChunkSize_Server) {
-  // Client asks for max 16-byte chunks, but service places a limit of 8 bytes.
-  rpc::test::WaitForPackets(ctx_.output(), 5, [this] {
-    ctx_.SendClientStream(EncodeChunk({.transfer_id = 3,
-                                       .pending_bytes = 64,
-                                       .max_chunk_size_bytes = 16,
-                                       .offset = 0,
-                                       .type = Chunk::Type::kTransferStart}));
-  });
-
-  EXPECT_TRUE(handler_.prepare_read_called);
-  EXPECT_FALSE(handler_.finalize_read_called);
-
-  ASSERT_EQ(ctx_.total_responses(), 5u);
-  Chunk c0 = DecodeChunk(ctx_.responses()[0]);
-  Chunk c1 = DecodeChunk(ctx_.responses()[1]);
-  Chunk c2 = DecodeChunk(ctx_.responses()[2]);
-  Chunk c3 = DecodeChunk(ctx_.responses()[3]);
-  Chunk c4 = DecodeChunk(ctx_.responses()[4]);
-
-  EXPECT_EQ(c0.transfer_id, 3u);
-  EXPECT_EQ(c0.offset, 0u);
-  ASSERT_EQ(c0.data.size(), 8u);
-  EXPECT_EQ(std::memcmp(c0.data.data(), kData.data(), c0.data.size()), 0);
-
-  EXPECT_EQ(c1.transfer_id, 3u);
-  EXPECT_EQ(c1.offset, 8u);
-  ASSERT_EQ(c1.data.size(), 8u);
-  EXPECT_EQ(std::memcmp(c1.data.data(), kData.data() + 8, c1.data.size()), 0);
-
-  EXPECT_EQ(c2.transfer_id, 3u);
-  EXPECT_EQ(c2.offset, 16u);
-  ASSERT_EQ(c2.data.size(), 8u);
-  EXPECT_EQ(std::memcmp(c2.data.data(), kData.data() + 16, c2.data.size()), 0);
-
-  EXPECT_EQ(c3.transfer_id, 3u);
-  EXPECT_EQ(c3.offset, 24u);
-  ASSERT_EQ(c3.data.size(), 8u);
-  EXPECT_EQ(std::memcmp(c3.data.data(), kData.data() + 24, c3.data.size()), 0);
-
-  EXPECT_EQ(c4.transfer_id, 3u);
-  EXPECT_EQ(c4.data.size(), 0u);
-  ASSERT_TRUE(c4.remaining_bytes.has_value());
-  EXPECT_EQ(c4.remaining_bytes.value(), 0u);
-
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 3, .status = OkStatus()}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_TRUE(handler_.finalize_read_called);
-  EXPECT_EQ(handler_.finalize_read_status, OkStatus());
-}
-
-TEST_F(ReadTransfer, ClientError) {
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 3,
-                                     .pending_bytes = 16,
-                                     .offset = 0,
-                                     .type = Chunk::Type::kTransferStart}));
-
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_TRUE(handler_.prepare_read_called);
-  EXPECT_FALSE(handler_.finalize_read_called);
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-
-  // Send client error.
-  ctx_.SendClientStream(
-      EncodeChunk({.transfer_id = 3, .status = Status::OutOfRange()}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-  EXPECT_TRUE(handler_.finalize_read_called);
-  EXPECT_EQ(handler_.finalize_read_status, Status::OutOfRange());
-}
-
-TEST_F(ReadTransfer, MalformedParametersChunk) {
-  // pending_bytes is required in a parameters chunk.
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 3}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_TRUE(handler_.prepare_read_called);
-  EXPECT_TRUE(handler_.finalize_read_called);
-  EXPECT_EQ(handler_.finalize_read_status, Status::InvalidArgument());
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-  Chunk chunk = DecodeChunk(ctx_.responses()[0]);
-  EXPECT_EQ(chunk.transfer_id, 3u);
-  ASSERT_TRUE(chunk.status.has_value());
-  EXPECT_EQ(chunk.status.value(), Status::InvalidArgument());
-}
-
-TEST_F(ReadTransfer, UnregisteredHandler) {
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 11,
-                                     .pending_bytes = 32,
-                                     .offset = 0,
-                                     .type = Chunk::Type::kTransferStart}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-  Chunk chunk = DecodeChunk(ctx_.responses()[0]);
-  EXPECT_EQ(chunk.transfer_id, 11u);
-  ASSERT_TRUE(chunk.status.has_value());
-  EXPECT_EQ(chunk.status.value(), Status::NotFound());
-}
-
-TEST_F(ReadTransfer, IgnoresNonPendingTransfers) {
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 3, .offset = 3}));
-  ctx_.SendClientStream(EncodeChunk(
-      {.transfer_id = 3, .offset = 0, .data = std::span(kData).first(10)}));
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 3, .status = OkStatus()}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // Only start transfer for initial packet.
-  EXPECT_FALSE(handler_.prepare_read_called);
-  EXPECT_FALSE(handler_.finalize_read_called);
-}
-
-TEST_F(ReadTransfer, AbortAndRestartIfInitialPacketIsReceived) {
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 3,
-                                     .pending_bytes = 16,
-                                     .offset = 0,
-                                     .type = Chunk::Type::kTransferStart}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-
-  EXPECT_TRUE(handler_.prepare_read_called);
-  EXPECT_FALSE(handler_.finalize_read_called);
-  handler_.prepare_read_called = false;  // Reset so can check if called again.
-
-  ctx_.SendClientStream(  // Resend starting chunk
-      EncodeChunk({.transfer_id = 3,
-                   .pending_bytes = 16,
-                   .offset = 0,
-                   .type = Chunk::Type::kTransferStart}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 2u);
-
-  EXPECT_TRUE(handler_.prepare_read_called);
-  EXPECT_TRUE(handler_.finalize_read_called);
-  EXPECT_EQ(handler_.finalize_read_status, Status::Aborted());
-  handler_.finalize_read_called = false;  // Reset so can check later
-
-  ctx_.SendClientStream(
-      EncodeChunk({.transfer_id = 3, .pending_bytes = 16, .offset = 16}));
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 3, .status = OkStatus()}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 3u);
-  EXPECT_TRUE(handler_.finalize_read_called);
-  EXPECT_EQ(handler_.finalize_read_status, OkStatus());
-}
-
-TEST_F(ReadTransfer, ZeroPendingBytesWithRemainingData_Aborts) {
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 3,
-                                     .pending_bytes = 0,
-                                     .type = Chunk::Type::kTransferStart}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-  ASSERT_TRUE(handler_.finalize_read_called);
-  EXPECT_EQ(handler_.finalize_read_status, Status::ResourceExhausted());
-
-  Chunk chunk = DecodeChunk(ctx_.responses().back());
-  EXPECT_EQ(chunk.status, Status::ResourceExhausted());
-}
-
-TEST_F(ReadTransfer, ZeroPendingBytesNoRemainingData_Completes) {
-  // Make the next read appear to be the end of the stream.
-  handler_.set_read_status(Status::OutOfRange());
-
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 3,
-                                     .pending_bytes = 0,
-                                     .type = Chunk::Type::kTransferStart}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  Chunk chunk = DecodeChunk(ctx_.responses().back());
-  EXPECT_EQ(chunk.transfer_id, 3u);
-  EXPECT_EQ(chunk.remaining_bytes, 0u);
-
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 3, .status = OkStatus()}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-  ASSERT_TRUE(handler_.finalize_read_called);
-  EXPECT_EQ(handler_.finalize_read_status, OkStatus());
-}
-
-TEST_F(ReadTransfer, SendsErrorIfChunkIsReceivedInCompletedState) {
-  rpc::test::WaitForPackets(ctx_.output(), 2, [this] {
-    ctx_.SendClientStream(EncodeChunk({.transfer_id = 3,
-                                       .pending_bytes = 64,
-                                       .offset = 0,
-                                       .type = Chunk::Type::kTransferStart}));
-  });
-
-  EXPECT_TRUE(handler_.prepare_read_called);
-  EXPECT_FALSE(handler_.finalize_read_called);
-
-  ASSERT_EQ(ctx_.total_responses(), 2u);
-  Chunk c0 = DecodeChunk(ctx_.responses()[0]);
-  Chunk c1 = DecodeChunk(ctx_.responses()[1]);
-
-  // First chunk should have all the read data.
-  EXPECT_EQ(c0.transfer_id, 3u);
-  EXPECT_EQ(c0.offset, 0u);
-  ASSERT_EQ(c0.data.size(), kData.size());
-  EXPECT_EQ(std::memcmp(c0.data.data(), kData.data(), c0.data.size()), 0);
-
-  // Second chunk should be empty and set remaining_bytes = 0.
-  EXPECT_EQ(c1.transfer_id, 3u);
-  EXPECT_EQ(c1.data.size(), 0u);
-  ASSERT_TRUE(c1.remaining_bytes.has_value());
-  EXPECT_EQ(c1.remaining_bytes.value(), 0u);
-
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 3, .status = OkStatus()}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_TRUE(handler_.finalize_read_called);
-  EXPECT_EQ(handler_.finalize_read_status, OkStatus());
-
-  // At this point the transfer should be in a completed state. Send a
-  // non-initial chunk as a continuation of the transfer.
-  handler_.finalize_read_called = false;
-
-  ctx_.SendClientStream(
-      EncodeChunk({.transfer_id = 3, .pending_bytes = 48, .offset = 16}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 3u);
-
-  Chunk c2 = DecodeChunk(ctx_.responses()[2]);
-  ASSERT_TRUE(c2.status.has_value());
-  EXPECT_EQ(c2.status.value(), Status::FailedPrecondition());
-
-  // FinalizeRead should not be called again.
-  EXPECT_FALSE(handler_.finalize_read_called);
-}
-
-class SimpleWriteTransfer final : public WriteOnlyHandler {
- public:
-  SimpleWriteTransfer(uint32_t transfer_id, ByteSpan data)
-      : WriteOnlyHandler(transfer_id),
-        prepare_write_called(false),
-        finalize_write_called(false),
-        finalize_write_status(Status::Unknown()),
-        writer_(data) {}
-
-  Status PrepareWrite() final {
-    EXPECT_EQ(OkStatus(), writer_.Seek(0));
-    set_writer(writer_);
-    prepare_write_called = true;
-    return OkStatus();
-  }
-
-  Status FinalizeWrite(Status status) final {
-    finalize_write_called = true;
-    finalize_write_status = status;
-    return finalize_write_return_status_;
-  }
-
-  void set_finalize_write_return(Status status) {
-    finalize_write_return_status_ = status;
-  }
-
-  bool prepare_write_called;
-  bool finalize_write_called;
-  Status finalize_write_status;
-
- private:
-  Status finalize_write_return_status_;
-  stream::MemoryWriter writer_;
-};
-
-class WriteTransfer : public ::testing::Test {
- protected:
-  WriteTransfer(size_t max_bytes_to_receive = 64)
-      : buffer{},
-        handler_(7, buffer),
-        transfer_thread_(data_buffer_, encode_buffer_),
-        system_thread_(TransferThreadOptions(), transfer_thread_),
-        ctx_(transfer_thread_,
-             max_bytes_to_receive,
-             // Use a long timeout to avoid accidentally triggering timeouts.
-             std::chrono::minutes(1)) {
-    ctx_.service().RegisterHandler(handler_);
-
-    ASSERT_FALSE(handler_.prepare_write_called);
-    ASSERT_FALSE(handler_.finalize_write_called);
-
-    ctx_.call();  // Open the write stream
-    transfer_thread_.WaitUntilEventIsProcessed();
-  }
-
-  ~WriteTransfer() {
-    transfer_thread_.Terminate();
-    system_thread_.join();
-  }
-
-  std::array<std::byte, kData.size()> buffer;
-  SimpleWriteTransfer handler_;
-
-  Thread<1, 1> transfer_thread_;
-  thread::Thread system_thread_;
-  std::array<std::byte, 64> data_buffer_;
-  std::array<std::byte, 64> encode_buffer_;
-  PW_RAW_TEST_METHOD_CONTEXT(TransferService, Write) ctx_;
-};
-
-TEST_F(WriteTransfer, SingleChunk) {
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 7}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_TRUE(handler_.prepare_write_called);
-  EXPECT_FALSE(handler_.finalize_write_called);
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-  Chunk chunk = DecodeChunk(ctx_.responses()[0]);
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  ASSERT_TRUE(chunk.pending_bytes.has_value());
-  EXPECT_EQ(chunk.pending_bytes.value(), 32u);
-  ASSERT_TRUE(chunk.max_chunk_size_bytes.has_value());
-  EXPECT_EQ(chunk.max_chunk_size_bytes.value(), 37u);
-
-  ctx_.SendClientStream<64>(EncodeChunk({.transfer_id = 7,
-                                         .offset = 0,
-                                         .data = std::span(kData),
-                                         .remaining_bytes = 0}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 2u);
-  chunk = DecodeChunk(ctx_.responses()[1]);
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  ASSERT_TRUE(chunk.status.has_value());
-  EXPECT_EQ(chunk.status.value(), OkStatus());
-
-  EXPECT_TRUE(handler_.finalize_write_called);
-  EXPECT_EQ(handler_.finalize_write_status, OkStatus());
-  EXPECT_EQ(std::memcmp(buffer.data(), kData.data(), kData.size()), 0);
-}
-
-TEST_F(WriteTransfer, FinalizeFails) {
-  // Return an error when FinalizeWrite is called.
-  handler_.set_finalize_write_return(Status::FailedPrecondition());
-
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 7}));
-  ctx_.SendClientStream<64>(EncodeChunk({.transfer_id = 7,
-                                         .offset = 0,
-                                         .data = std::span(kData),
-                                         .remaining_bytes = 0}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 2u);
-  Chunk chunk = DecodeChunk(ctx_.responses()[1]);
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  ASSERT_TRUE(chunk.status.has_value());
-  EXPECT_EQ(chunk.status.value(), Status::DataLoss());
-
-  EXPECT_TRUE(handler_.finalize_write_called);
-  EXPECT_EQ(handler_.finalize_write_status, OkStatus());
-}
-
-TEST_F(WriteTransfer, SendingFinalPacketFails) {
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 7}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ctx_.output().set_send_status(Status::Unknown());
-
-  ctx_.SendClientStream<64>(EncodeChunk({.transfer_id = 7,
-                                         .offset = 0,
-                                         .data = std::span(kData),
-                                         .remaining_bytes = 0}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // Should only have sent the transfer parameters.
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-  Chunk chunk = DecodeChunk(ctx_.responses()[0]);
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  ASSERT_TRUE(chunk.pending_bytes.has_value());
-  EXPECT_EQ(chunk.pending_bytes.value(), 32u);
-  ASSERT_TRUE(chunk.max_chunk_size_bytes.has_value());
-  EXPECT_EQ(chunk.max_chunk_size_bytes.value(), 37u);
-
-  // When FinalizeWrite() was called, the transfer was considered successful.
-  EXPECT_TRUE(handler_.finalize_write_called);
-  EXPECT_EQ(handler_.finalize_write_status, OkStatus());
-}
-
-TEST_F(WriteTransfer, MultiChunk) {
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 7}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_TRUE(handler_.prepare_write_called);
-  EXPECT_FALSE(handler_.finalize_write_called);
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-  Chunk chunk = DecodeChunk(ctx_.responses()[0]);
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  ASSERT_TRUE(chunk.pending_bytes.has_value());
-  EXPECT_EQ(chunk.pending_bytes.value(), 32u);
-
-  ctx_.SendClientStream<64>(EncodeChunk(
-      {.transfer_id = 7, .offset = 0, .data = std::span(kData).first(8)}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-
-  ctx_.SendClientStream<64>(EncodeChunk({.transfer_id = 7,
-                                         .offset = 8,
-                                         .data = std::span(kData).subspan(8),
-                                         .remaining_bytes = 0}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 2u);
-  chunk = DecodeChunk(ctx_.responses()[1]);
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  ASSERT_TRUE(chunk.status.has_value());
-  EXPECT_EQ(chunk.status.value(), OkStatus());
-
-  EXPECT_TRUE(handler_.finalize_write_called);
-  EXPECT_EQ(handler_.finalize_write_status, OkStatus());
-  EXPECT_EQ(std::memcmp(buffer.data(), kData.data(), kData.size()), 0);
-}
-
-TEST_F(WriteTransfer, WriteFailsOnRetry) {
-  // Skip one packet to fail on a retry.
-  ctx_.output().set_send_status(Status::FailedPrecondition(), 1);
-
-  // Wait for 3 packets: initial params, retry attempt, final error
-  rpc::test::WaitForPackets(ctx_.output(), 3, [this] {
-    // Send only one client packet so the service times out.
-    ctx_.SendClientStream(EncodeChunk({.transfer_id = 7}));
-    transfer_thread_.SimulateServerTimeout(7);  // Time out to trigger retry
-  });
-
-  // Attempted to send 3 packets, but the 2nd packet was dropped.
-  // Check that the last packet is an INTERNAL error from the RPC write failure.
-  ASSERT_EQ(ctx_.total_responses(), 2u);
-  Chunk chunk = DecodeChunk(ctx_.responses()[1]);
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  ASSERT_TRUE(chunk.status.has_value());
-  EXPECT_EQ(chunk.status.value(), Status::Internal());
-}
-
-TEST_F(WriteTransfer, TimeoutInRecoveryState) {
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 7}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-  Chunk chunk = DecodeChunk(ctx_.responses().back());
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  EXPECT_EQ(chunk.offset, 0u);
-  ASSERT_TRUE(chunk.pending_bytes.has_value());
-  EXPECT_EQ(chunk.pending_bytes.value(), 32u);
-
-  constexpr std::span data(kData);
-
-  ctx_.SendClientStream<64>(
-      EncodeChunk({.transfer_id = 7, .offset = 0, .data = data.first(8)}));
-
-  // Skip offset 8 to enter a recovery state.
-  ctx_.SendClientStream<64>(EncodeChunk(
-      {.transfer_id = 7, .offset = 12, .data = data.subspan(12, 4)}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // Recovery parameters should be sent for offset 8.
-  ASSERT_EQ(ctx_.total_responses(), 2u);
-  chunk = DecodeChunk(ctx_.responses().back());
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  EXPECT_EQ(chunk.offset, 8u);
-  ASSERT_TRUE(chunk.pending_bytes.has_value());
-  EXPECT_EQ(chunk.pending_bytes.value(), 24u);
-
-  // Timeout while in the recovery state.
-  transfer_thread_.SimulateServerTimeout(7);
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // Same recovery parameters should be re-sent.
-  ASSERT_EQ(ctx_.total_responses(), 3u);
-  chunk = DecodeChunk(ctx_.responses().back());
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  EXPECT_EQ(chunk.offset, 8u);
-  ASSERT_TRUE(chunk.pending_bytes.has_value());
-  EXPECT_EQ(chunk.pending_bytes.value(), 24u);
-}
-
-TEST_F(WriteTransfer, ExtendWindow) {
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 7}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_TRUE(handler_.prepare_write_called);
-  EXPECT_FALSE(handler_.finalize_write_called);
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-  Chunk chunk = DecodeChunk(ctx_.responses()[0]);
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  EXPECT_EQ(chunk.window_end_offset, 32u);
-  ASSERT_TRUE(chunk.pending_bytes.has_value());
-  EXPECT_EQ(chunk.pending_bytes.value(), 32u);
-
-  // Window starts at 32 bytes and should extend when half of that is sent.
-  ctx_.SendClientStream<64>(EncodeChunk(
-      {.transfer_id = 7, .offset = 0, .data = std::span(kData).first(4)}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-
-  ctx_.SendClientStream<64>(EncodeChunk(
-      {.transfer_id = 7, .offset = 4, .data = std::span(kData).subspan(4, 4)}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-
-  ctx_.SendClientStream<64>(EncodeChunk(
-      {.transfer_id = 7, .offset = 8, .data = std::span(kData).subspan(8, 4)}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-
-  ctx_.SendClientStream<64>(
-      EncodeChunk({.transfer_id = 7,
-                   .offset = 12,
-                   .data = std::span(kData).subspan(12, 4)}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-  ASSERT_EQ(ctx_.total_responses(), 2u);
-
-  // Extend parameters chunk.
-  chunk = DecodeChunk(ctx_.responses()[1]);
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  EXPECT_EQ(chunk.window_end_offset, 32u);
-  EXPECT_EQ(chunk.type, Chunk::Type::kParametersContinue);
-  ASSERT_TRUE(chunk.pending_bytes.has_value());
-  EXPECT_EQ(chunk.pending_bytes.value(), 16u);
-
-  ctx_.SendClientStream<64>(EncodeChunk({.transfer_id = 7,
-                                         .offset = 16,
-                                         .data = std::span(kData).subspan(16),
-                                         .remaining_bytes = 0}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 3u);
-  chunk = DecodeChunk(ctx_.responses()[2]);
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  ASSERT_TRUE(chunk.status.has_value());
-  EXPECT_EQ(chunk.status.value(), OkStatus());
-
-  EXPECT_TRUE(handler_.finalize_write_called);
-  EXPECT_EQ(handler_.finalize_write_status, OkStatus());
-  EXPECT_EQ(std::memcmp(buffer.data(), kData.data(), kData.size()), 0);
-}
-
-class WriteTransferMaxBytes16 : public WriteTransfer {
- protected:
-  WriteTransferMaxBytes16() : WriteTransfer(/*max_bytes_to_receive=*/16) {}
-};
-
-TEST_F(WriteTransfer, TransmitterReducesWindow) {
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 7}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_TRUE(handler_.prepare_write_called);
-  EXPECT_FALSE(handler_.finalize_write_called);
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-  Chunk chunk = DecodeChunk(ctx_.responses().back());
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  EXPECT_EQ(chunk.window_end_offset, 32u);
-
-  // Send only 12 bytes and set that as the new end offset.
-  ctx_.SendClientStream<64>(EncodeChunk({.transfer_id = 7,
-                                         .window_end_offset = 12,
-                                         .offset = 0,
-                                         .data = std::span(kData).first(12)}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-  ASSERT_EQ(ctx_.total_responses(), 2u);
-
-  // Receiver should respond immediately with a retransmit chunk as the end of
-  // the window has been reached.
-  chunk = DecodeChunk(ctx_.responses().back());
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  EXPECT_EQ(chunk.offset, 12u);
-  EXPECT_EQ(chunk.window_end_offset, 32u);
-  EXPECT_EQ(chunk.type, Chunk::Type::kParametersRetransmit);
-}
-
-TEST_F(WriteTransfer, TransmitterExtendsWindow_TerminatesWithInvalid) {
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 7}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_TRUE(handler_.prepare_write_called);
-  EXPECT_FALSE(handler_.finalize_write_called);
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-  Chunk chunk = DecodeChunk(ctx_.responses().back());
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  EXPECT_EQ(chunk.window_end_offset, 32u);
-
-  // Send only 12 bytes and set that as the new end offset.
-  ctx_.SendClientStream<64>(
-      EncodeChunk({.transfer_id = 7,
-                   // Larger window end offset than the receiver's.
-                   .window_end_offset = 48,
-                   .offset = 0,
-                   .data = std::span(kData).first(16)}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-  ASSERT_EQ(ctx_.total_responses(), 2u);
-
-  chunk = DecodeChunk(ctx_.responses().back());
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  ASSERT_TRUE(chunk.status.has_value());
-  EXPECT_EQ(chunk.status.value(), Status::Internal());
-}
-
-TEST_F(WriteTransferMaxBytes16, MultipleParameters) {
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 7}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_TRUE(handler_.prepare_write_called);
-  EXPECT_FALSE(handler_.finalize_write_called);
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-  Chunk chunk = DecodeChunk(ctx_.responses()[0]);
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  ASSERT_TRUE(chunk.pending_bytes.has_value());
-  EXPECT_EQ(chunk.pending_bytes.value(), 16u);
-
-  ctx_.SendClientStream<64>(EncodeChunk(
-      {.transfer_id = 7, .offset = 0, .data = std::span(kData).first(8)}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 2u);
-  chunk = DecodeChunk(ctx_.responses()[1]);
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  EXPECT_EQ(chunk.offset, 8u);
-  EXPECT_EQ(chunk.window_end_offset, 24u);
-  ASSERT_TRUE(chunk.pending_bytes.has_value());
-  EXPECT_EQ(chunk.pending_bytes.value(), 16u);
-
-  ctx_.SendClientStream<64>(EncodeChunk(
-      {.transfer_id = 7, .offset = 8, .data = std::span(kData).subspan(8, 8)}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 3u);
-  chunk = DecodeChunk(ctx_.responses()[2]);
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  EXPECT_EQ(chunk.offset, 16u);
-  EXPECT_EQ(chunk.window_end_offset, 32u);
-  ASSERT_TRUE(chunk.pending_bytes.has_value());
-  EXPECT_EQ(chunk.pending_bytes.value(), 16u);
-
-  ctx_.SendClientStream<64>(
-      EncodeChunk({.transfer_id = 7,
-                   .offset = 16,
-                   .data = std::span(kData).subspan(16, 8)}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 4u);
-  chunk = DecodeChunk(ctx_.responses()[3]);
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  EXPECT_EQ(chunk.offset, 24u);
-  EXPECT_EQ(chunk.window_end_offset, 32u);
-  ASSERT_TRUE(chunk.pending_bytes.has_value());
-  EXPECT_EQ(chunk.pending_bytes.value(), 8u);
-
-  ctx_.SendClientStream<64>(EncodeChunk({.transfer_id = 7,
-                                         .offset = 24,
-                                         .data = std::span(kData).subspan(24),
-                                         .remaining_bytes = 0}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 5u);
-  chunk = DecodeChunk(ctx_.responses()[4]);
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  ASSERT_TRUE(chunk.status.has_value());
-  EXPECT_EQ(chunk.status.value(), OkStatus());
-
-  EXPECT_TRUE(handler_.finalize_write_called);
-  EXPECT_EQ(handler_.finalize_write_status, OkStatus());
-  EXPECT_EQ(std::memcmp(buffer.data(), kData.data(), kData.size()), 0);
-}
-
-TEST_F(WriteTransferMaxBytes16, SetsDefaultPendingBytes) {
-  // Default max bytes is smaller than buffer.
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 7}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-  Chunk chunk = DecodeChunk(ctx_.responses()[0]);
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  EXPECT_EQ(chunk.pending_bytes.value(), 16u);
-}
-
-TEST_F(WriteTransfer, SetsWriterPendingBytes) {
-  // Buffer is smaller than constructor's default max bytes.
-  std::array<std::byte, 8> small_buffer = {};
-
-  SimpleWriteTransfer handler_(987, small_buffer);
-  ctx_.service().RegisterHandler(handler_);
-
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 987}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-  Chunk chunk = DecodeChunk(ctx_.responses()[0]);
-  EXPECT_EQ(chunk.transfer_id, 987u);
-  EXPECT_EQ(chunk.pending_bytes.value(), 8u);
-}
-
-TEST_F(WriteTransfer, UnexpectedOffset) {
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 7}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_TRUE(handler_.prepare_write_called);
-  EXPECT_FALSE(handler_.finalize_write_called);
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-  Chunk chunk = DecodeChunk(ctx_.responses()[0]);
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  EXPECT_EQ(chunk.offset, 0u);
-  ASSERT_TRUE(chunk.pending_bytes.has_value());
-  EXPECT_EQ(chunk.pending_bytes.value(), 32u);
-
-  ctx_.SendClientStream<64>(EncodeChunk(
-      {.transfer_id = 7, .offset = 0, .data = std::span(kData).first(8)}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-
-  ctx_.SendClientStream<64>(EncodeChunk({.transfer_id = 7,
-                                         .offset = 4,  // incorrect
-                                         .data = std::span(kData).subspan(16),
-                                         .remaining_bytes = 0}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 2u);
-  chunk = DecodeChunk(ctx_.responses()[1]);
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  EXPECT_EQ(chunk.offset, 8u);
-  ASSERT_TRUE(chunk.pending_bytes.has_value());
-  EXPECT_EQ(chunk.pending_bytes.value(), 24u);
-
-  ctx_.SendClientStream<64>(EncodeChunk({.transfer_id = 7,
-                                         .offset = 8,  // correct
-                                         .data = std::span(kData).subspan(8),
-                                         .remaining_bytes = 0}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 3u);
-  chunk = DecodeChunk(ctx_.responses()[2]);
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  ASSERT_TRUE(chunk.status.has_value());
-  EXPECT_EQ(chunk.status.value(), OkStatus());
-
-  EXPECT_TRUE(handler_.finalize_write_called);
-  EXPECT_EQ(handler_.finalize_write_status, OkStatus());
-  EXPECT_EQ(std::memcmp(buffer.data(), kData.data(), kData.size()), 0);
-}
-
-TEST_F(WriteTransferMaxBytes16, TooMuchData) {
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 7}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_TRUE(handler_.prepare_write_called);
-  EXPECT_FALSE(handler_.finalize_write_called);
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-  Chunk chunk = DecodeChunk(ctx_.responses()[0]);
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  ASSERT_TRUE(chunk.pending_bytes.has_value());
-  EXPECT_EQ(chunk.pending_bytes.value(), 16u);
-
-  // pending_bytes = 16 but send 24
-  ctx_.SendClientStream<64>(EncodeChunk(
-      {.transfer_id = 7, .offset = 0, .data = std::span(kData).first(24)}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 2u);
-  chunk = DecodeChunk(ctx_.responses()[1]);
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  ASSERT_TRUE(chunk.status.has_value());
-  EXPECT_EQ(chunk.status.value(), Status::Internal());
-}
-
-TEST_F(WriteTransfer, UnregisteredHandler) {
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 999}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-  Chunk chunk = DecodeChunk(ctx_.responses()[0]);
-  EXPECT_EQ(chunk.transfer_id, 999u);
-  ASSERT_TRUE(chunk.status.has_value());
-  EXPECT_EQ(chunk.status.value(), Status::NotFound());
-}
-
-TEST_F(WriteTransfer, ClientError) {
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 7}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_TRUE(handler_.prepare_write_called);
-  EXPECT_FALSE(handler_.finalize_write_called);
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-  Chunk chunk = DecodeChunk(ctx_.responses()[0]);
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  ASSERT_TRUE(chunk.pending_bytes.has_value());
-  EXPECT_EQ(chunk.pending_bytes.value(), 32u);
-
-  ctx_.SendClientStream<64>(
-      EncodeChunk({.transfer_id = 7, .status = Status::DataLoss()}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_EQ(ctx_.total_responses(), 1u);
-
-  EXPECT_TRUE(handler_.finalize_write_called);
-  EXPECT_EQ(handler_.finalize_write_status, Status::DataLoss());
-}
-
-TEST_F(WriteTransfer, OnlySendParametersUpdateOnceAfterDrop) {
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 7}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-
-  constexpr std::span data(kData);
-  ctx_.SendClientStream<64>(
-      EncodeChunk({.transfer_id = 7, .offset = 0, .data = data.first(1)}));
-
-  // Drop offset 1, then send the rest of the data.
-  for (uint32_t i = 2; i < kData.size(); ++i) {
-    ctx_.SendClientStream<64>(EncodeChunk(
-        {.transfer_id = 7, .offset = i, .data = data.subspan(i, 1)}));
-  }
-
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 2u);
-  Chunk chunk = DecodeChunk(ctx_.responses().back());
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  EXPECT_EQ(chunk.offset, 1u);
-
-  // Send the remaining data and the final status.
-  ctx_.SendClientStream<64>(EncodeChunk({.transfer_id = 7,
-                                         .offset = 1,
-                                         .data = data.subspan(1, 31),
-                                         .status = OkStatus()}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_TRUE(handler_.finalize_write_called);
-  EXPECT_EQ(handler_.finalize_write_status, OkStatus());
-}
-
-TEST_F(WriteTransfer, ResendParametersIfSentRepeatedChunkDuringRecovery) {
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 7}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-
-  constexpr std::span data(kData);
-
-  // Skip offset 0, then send the rest of the data.
-  for (uint32_t i = 1; i < kData.size(); ++i) {
-    ctx_.SendClientStream<64>(EncodeChunk(
-        {.transfer_id = 7, .offset = i, .data = data.subspan(i, 1)}));
-  }
-
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 2u);  // Resent transfer parameters once.
-
-  const auto last_chunk = EncodeChunk(
-      {.transfer_id = 7, .offset = kData.size() - 1, .data = data.last(1)});
-  ctx_.SendClientStream<64>(last_chunk);
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // Resent transfer parameters since the packet is repeated
-  ASSERT_EQ(ctx_.total_responses(), 3u);
-
-  ctx_.SendClientStream<64>(last_chunk);
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 4u);
-
-  Chunk chunk = DecodeChunk(ctx_.responses().back());
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  EXPECT_EQ(chunk.offset, 0u);
-  EXPECT_TRUE(chunk.pending_bytes.has_value());
-
-  // Resumes normal operation when correct offset is sent.
-  ctx_.SendClientStream<64>(EncodeChunk(
-      {.transfer_id = 7, .offset = 0, .data = kData, .status = OkStatus()}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_TRUE(handler_.finalize_write_called);
-  EXPECT_EQ(handler_.finalize_write_status, OkStatus());
-}
-
-TEST_F(WriteTransfer, ResendsStatusIfClientRetriesAfterStatusChunk) {
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 7}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-
-  ctx_.SendClientStream<64>(EncodeChunk({.transfer_id = 7,
-                                         .offset = 0,
-                                         .data = std::span(kData),
-                                         .remaining_bytes = 0}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 2u);
-  Chunk chunk = DecodeChunk(ctx_.responses().back());
-  ASSERT_TRUE(chunk.status.has_value());
-  EXPECT_EQ(chunk.status.value(), OkStatus());
-
-  ctx_.SendClientStream<64>(EncodeChunk({.transfer_id = 7,
-                                         .offset = 0,
-                                         .data = std::span(kData),
-                                         .remaining_bytes = 0}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 3u);
-  chunk = DecodeChunk(ctx_.responses().back());
-  ASSERT_TRUE(chunk.status.has_value());
-  EXPECT_EQ(chunk.status.value(), OkStatus());
-}
-
-TEST_F(WriteTransfer, IgnoresNonPendingTransfers) {
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 7, .offset = 3}));
-  ctx_.SendClientStream(EncodeChunk(
-      {.transfer_id = 7, .offset = 0, .data = std::span(kData).first(10)}));
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 7, .status = OkStatus()}));
-
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // Only start transfer for initial packet.
-  EXPECT_FALSE(handler_.prepare_write_called);
-  EXPECT_FALSE(handler_.finalize_write_called);
-}
-
-TEST_F(WriteTransfer, AbortAndRestartIfInitialPacketIsReceived) {
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 7}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-
-  ctx_.SendClientStream<64>(EncodeChunk(
-      {.transfer_id = 7, .offset = 0, .data = std::span(kData).first(8)}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-
-  ASSERT_TRUE(handler_.prepare_write_called);
-  ASSERT_FALSE(handler_.finalize_write_called);
-  handler_.prepare_write_called = false;  // Reset to check it's called again.
-
-  // Simulate client disappearing then restarting the transfer.
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 7}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_TRUE(handler_.prepare_write_called);
-  EXPECT_TRUE(handler_.finalize_write_called);
-  EXPECT_EQ(handler_.finalize_write_status, Status::Aborted());
-
-  handler_.finalize_write_called = false;  // Reset to check it's called again.
-
-  ASSERT_EQ(ctx_.total_responses(), 2u);
-
-  ctx_.SendClientStream<64>(EncodeChunk({.transfer_id = 7,
-                                         .offset = 0,
-                                         .data = std::span(kData),
-                                         .remaining_bytes = 0}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 3u);
-
-  EXPECT_TRUE(handler_.finalize_write_called);
-  EXPECT_EQ(handler_.finalize_write_status, OkStatus());
-  EXPECT_EQ(std::memcmp(buffer.data(), kData.data(), kData.size()), 0);
-}
-
-class SometimesUnavailableReadHandler final : public ReadOnlyHandler {
- public:
-  SometimesUnavailableReadHandler(uint32_t transfer_id, ConstByteSpan data)
-      : ReadOnlyHandler(transfer_id), reader_(data), call_count_(0) {}
-
-  Status PrepareRead() final {
-    if ((call_count_++ % 2) == 0) {
-      return Status::Unavailable();
-    }
-
-    set_reader(reader_);
-    return OkStatus();
-  }
-
- private:
-  stream::MemoryReader reader_;
-  int call_count_;
-};
-
-TEST_F(ReadTransfer, PrepareError) {
-  SometimesUnavailableReadHandler unavailable_handler(88, kData);
-  ctx_.service().RegisterHandler(unavailable_handler);
-
-  ctx_.SendClientStream(
-      EncodeChunk({.transfer_id = 88, .pending_bytes = 128, .offset = 0}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-  Chunk chunk = DecodeChunk(ctx_.responses()[0]);
-  EXPECT_EQ(chunk.transfer_id, 88u);
-  ASSERT_TRUE(chunk.status.has_value());
-  EXPECT_EQ(chunk.status.value(), Status::DataLoss());
-
-  // Try starting the transfer again. It should work this time.
-  // TODO(frolv): This won't work until completion ACKs are supported.
-  if (false) {
-    ctx_.SendClientStream(
-        EncodeChunk({.transfer_id = 88, .pending_bytes = 128, .offset = 0}));
-    transfer_thread_.WaitUntilEventIsProcessed();
-
-    ASSERT_EQ(ctx_.total_responses(), 2u);
-    chunk = DecodeChunk(ctx_.responses()[1]);
-    EXPECT_EQ(chunk.transfer_id, 88u);
-    ASSERT_EQ(chunk.data.size(), kData.size());
-    EXPECT_EQ(std::memcmp(chunk.data.data(), kData.data(), chunk.data.size()),
-              0);
-  }
-}
-
-TEST_F(WriteTransferMaxBytes16, Service_SetMaxPendingBytes) {
-  ctx_.SendClientStream(EncodeChunk({.transfer_id = 7}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_TRUE(handler_.prepare_write_called);
-  EXPECT_FALSE(handler_.finalize_write_called);
-
-  // First parameters chunk has default pending bytes of 16.
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-  Chunk chunk = DecodeChunk(ctx_.responses()[0]);
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  ASSERT_TRUE(chunk.pending_bytes.has_value());
-  EXPECT_EQ(chunk.pending_bytes.value(), 16u);
-
-  // Update the pending bytes value.
-  ctx_.service().set_max_pending_bytes(12);
-
-  ctx_.SendClientStream<64>(EncodeChunk(
-      {.transfer_id = 7, .offset = 0, .data = std::span(kData).first(8)}));
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  // Second parameters chunk should use the new max pending bytes.
-  ASSERT_EQ(ctx_.total_responses(), 2u);
-  chunk = DecodeChunk(ctx_.responses()[1]);
-  EXPECT_EQ(chunk.transfer_id, 7u);
-  EXPECT_EQ(chunk.offset, 8u);
-  EXPECT_EQ(chunk.window_end_offset, 20u);
-  ASSERT_TRUE(chunk.pending_bytes.has_value());
-  EXPECT_EQ(chunk.pending_bytes.value(), 12u);
-}
-
-PW_MODIFY_DIAGNOSTICS_POP();
-
-}  // namespace
-}  // namespace pw::transfer::test
diff --git a/pw_transfer/transfer_thread.cc b/pw_transfer/transfer_thread.cc
deleted file mode 100644
index aac5cad..0000000
--- a/pw_transfer/transfer_thread.cc
+++ /dev/null
@@ -1,320 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 PW_LOG_MODULE_NAME "TRN"
-
-#include "pw_transfer/transfer_thread.h"
-
-#include "pw_assert/check.h"
-#include "pw_log/log.h"
-#include "pw_transfer/internal/chunk.h"
-
-PW_MODIFY_DIAGNOSTICS_PUSH();
-PW_MODIFY_DIAGNOSTIC(ignored, "-Wmissing-field-initializers");
-
-namespace pw::transfer::internal {
-
-void TransferThread::Terminate() {
-  next_event_ownership_.acquire();
-  next_event_.type = EventType::kTerminate;
-  event_notification_.release();
-}
-
-void TransferThread::SimulateTimeout(EventType type, uint32_t transfer_id) {
-  next_event_ownership_.acquire();
-
-  next_event_.type = type;
-  next_event_.chunk = {};
-  next_event_.chunk.transfer_id = transfer_id;
-
-  event_notification_.release();
-
-  WaitUntilEventIsProcessed();
-}
-
-void TransferThread::Run() {
-  // Next event starts freed.
-  next_event_ownership_.release();
-
-  while (true) {
-    if (event_notification_.try_acquire_until(GetNextTransferTimeout())) {
-      if (next_event_.type == EventType::kTerminate) {
-        return;
-      }
-
-      HandleEvent(next_event_);
-
-      // Finished processing the event. Allow the next_event struct to be
-      // overwritten.
-      next_event_ownership_.release();
-    }
-
-    // Regardless of whether an event was received or not, check for any
-    // transfers which have timed out and process them if so.
-    for (Context& context : client_transfers_) {
-      if (context.timed_out()) {
-        context.HandleEvent({.type = EventType::kClientTimeout});
-      }
-    }
-    for (Context& context : server_transfers_) {
-      if (context.timed_out()) {
-        context.HandleEvent({.type = EventType::kServerTimeout});
-      }
-    }
-  }
-}
-
-chrono::SystemClock::time_point TransferThread::GetNextTransferTimeout() const {
-  chrono::SystemClock::time_point timeout =
-      chrono::SystemClock::TimePointAfterAtLeast(kMaxTimeout);
-
-  for (Context& context : client_transfers_) {
-    auto ctx_timeout = context.timeout();
-    if (ctx_timeout.has_value() && ctx_timeout.value() < timeout) {
-      timeout = ctx_timeout.value();
-    }
-  }
-  for (Context& context : server_transfers_) {
-    auto ctx_timeout = context.timeout();
-    if (ctx_timeout.has_value() && ctx_timeout.value() < timeout) {
-      timeout = ctx_timeout.value();
-    }
-  }
-
-  return timeout;
-}
-
-void TransferThread::StartTransfer(TransferType type,
-                                   uint32_t transfer_id,
-                                   uint32_t handler_id,
-                                   stream::Stream* stream,
-                                   const TransferParameters& max_parameters,
-                                   Function<void(Status)>&& on_completion,
-                                   chrono::SystemClock::duration timeout,
-                                   uint8_t max_retries) {
-  // Block until the last event has been processed.
-  next_event_ownership_.acquire();
-
-  bool is_client_transfer = stream != nullptr;
-
-  next_event_.type = is_client_transfer ? EventType::kNewClientTransfer
-                                        : EventType::kNewServerTransfer;
-  next_event_.new_transfer = {
-      .type = type,
-      .transfer_id = transfer_id,
-      .handler_id = handler_id,
-      .max_parameters = &max_parameters,
-      .timeout = timeout,
-      .max_retries = max_retries,
-      .transfer_thread = this,
-  };
-
-  staged_on_completion_ = std::move(on_completion);
-
-  // The transfer is initialized with either a stream (client-side) or a handler
-  // (server-side). If no stream is provided, try to find a registered handler
-  // with the specified ID.
-  if (is_client_transfer) {
-    next_event_.new_transfer.stream = stream;
-    next_event_.new_transfer.rpc_writer = &static_cast<rpc::Writer&>(
-        type == TransferType::kTransmit ? client_write_stream_
-                                        : client_read_stream_);
-  } else {
-    auto handler = std::find_if(handlers_.begin(),
-                                handlers_.end(),
-                                [&](auto& h) { return h.id() == handler_id; });
-    if (handler != handlers_.end()) {
-      next_event_.new_transfer.handler = &*handler;
-      next_event_.new_transfer.rpc_writer = &static_cast<rpc::Writer&>(
-          type == TransferType::kTransmit ? server_read_stream_
-                                          : server_write_stream_);
-    } else {
-      // No handler exists for the transfer: return a NOT_FOUND.
-      next_event_.type = EventType::kSendStatusChunk;
-      next_event_.send_status_chunk = {
-          .transfer_id = transfer_id,
-          .status = Status::NotFound().code(),
-          .stream = type == TransferType::kTransmit
-                        ? TransferStream::kServerRead
-                        : TransferStream::kServerWrite,
-      };
-    }
-  }
-
-  event_notification_.release();
-}
-
-void TransferThread::ProcessChunk(EventType type, ConstByteSpan chunk) {
-  // If this assert is hit, there is a bug in the transfer implementation.
-  // Contexts' max_chunk_size_bytes fields should be set based on the size of
-  // chunk_buffer_.
-  PW_CHECK(chunk.size() <= chunk_buffer_.size(),
-           "Transfer received a larger chunk than it can handle.");
-
-  Result<uint32_t> transfer_id = ExtractTransferId(chunk);
-  if (!transfer_id.ok()) {
-    PW_LOG_ERROR("Received a malformed chunk without a transfer ID");
-    return;
-  }
-
-  // Block until the last event has been processed.
-  next_event_ownership_.acquire();
-
-  std::memcpy(chunk_buffer_.data(), chunk.data(), chunk.size());
-
-  next_event_.type = type;
-  next_event_.chunk = {
-      .transfer_id = *transfer_id,
-      .data = chunk_buffer_.data(),
-      .size = chunk.size(),
-  };
-
-  event_notification_.release();
-}
-
-void TransferThread::SetClientStream(TransferStream type,
-                                     rpc::RawClientReaderWriter& stream) {
-  // Block until the last event has been processed.
-  next_event_ownership_.acquire();
-
-  next_event_.type = EventType::kSetTransferStream;
-  next_event_.set_transfer_stream = type;
-  staged_client_stream_ = std::move(stream);
-
-  event_notification_.release();
-}
-
-void TransferThread::SetServerStream(TransferStream type,
-                                     rpc::RawServerReaderWriter& stream) {
-  // Block until the last event has been processed.
-  next_event_ownership_.acquire();
-
-  next_event_.type = EventType::kSetTransferStream;
-  next_event_.set_transfer_stream = type;
-  staged_server_stream_ = std::move(stream);
-
-  event_notification_.release();
-}
-
-void TransferThread::TransferHandlerEvent(EventType type,
-                                          internal::Handler& handler) {
-  // Block until the last event has been processed.
-  next_event_ownership_.acquire();
-
-  next_event_.type = type;
-  if (type == EventType::kAddTransferHandler) {
-    next_event_.add_transfer_handler = &handler;
-  } else {
-    next_event_.remove_transfer_handler = &handler;
-  }
-
-  event_notification_.release();
-}
-
-void TransferThread::HandleEvent(const internal::Event& event) {
-  switch (event.type) {
-    case EventType::kSendStatusChunk:
-      SendStatusChunk(event.send_status_chunk);
-      break;
-
-    case EventType::kSetTransferStream:
-      switch (event.set_transfer_stream) {
-        case TransferStream::kClientRead:
-          client_read_stream_ = std::move(staged_client_stream_);
-          break;
-
-        case TransferStream::kClientWrite:
-          client_write_stream_ = std::move(staged_client_stream_);
-          break;
-
-        case TransferStream::kServerRead:
-          server_read_stream_ = std::move(staged_server_stream_);
-          break;
-
-        case TransferStream::kServerWrite:
-          server_write_stream_ = std::move(staged_server_stream_);
-          break;
-      }
-      return;
-
-    case EventType::kAddTransferHandler:
-      handlers_.push_front(*event.add_transfer_handler);
-      return;
-
-    case EventType::kRemoveTransferHandler:
-      handlers_.remove(*event.remove_transfer_handler);
-      return;
-
-    default:
-      // Other events are handled by individual transfer contexts.
-      break;
-  }
-
-  if (Context* ctx = FindContextForEvent(event); ctx != nullptr) {
-    if (event.type == EventType::kNewClientTransfer) {
-      // TODO(frolv): This is terrible.
-      static_cast<ClientContext*>(ctx)->set_on_completion(
-          std::move(staged_on_completion_));
-    }
-
-    ctx->HandleEvent(event);
-  }
-}
-
-Context* TransferThread::FindContextForEvent(
-    const internal::Event& event) const {
-  switch (event.type) {
-    case EventType::kNewClientTransfer:
-      return FindNewTransfer(client_transfers_, event.new_transfer.transfer_id);
-    case EventType::kNewServerTransfer:
-      return FindNewTransfer(server_transfers_, event.new_transfer.transfer_id);
-    case EventType::kClientChunk:
-      return FindActiveTransfer(client_transfers_, event.chunk.transfer_id);
-    case EventType::kServerChunk:
-      return FindActiveTransfer(server_transfers_, event.chunk.transfer_id);
-    case EventType::kClientTimeout:  // Manually triggered client timeout
-      return FindActiveTransfer(client_transfers_, event.chunk.transfer_id);
-    case EventType::kServerTimeout:  // Manually triggered server timeout
-      return FindActiveTransfer(server_transfers_, event.chunk.transfer_id);
-    default:
-      return nullptr;
-  }
-}
-
-void TransferThread::SendStatusChunk(
-    const internal::SendStatusChunkEvent& event) {
-  rpc::Writer& destination = stream_for(event.stream);
-
-  internal::Chunk chunk = {};
-  chunk.transfer_id = event.transfer_id;
-  chunk.status = event.status;
-
-  Result<ConstByteSpan> result = internal::EncodeChunk(chunk, chunk_buffer_);
-
-  if (!result.ok()) {
-    PW_LOG_ERROR("Failed to encode final chunk for transfer %u",
-                 static_cast<unsigned>(event.transfer_id));
-    return;
-  }
-
-  if (!destination.Write(result.value()).ok()) {
-    PW_LOG_ERROR("Failed to send final chunk for transfer %u",
-                 static_cast<unsigned>(event.transfer_id));
-    return;
-  }
-}
-
-}  // namespace pw::transfer::internal
-
-PW_MODIFY_DIAGNOSTICS_POP();
diff --git a/pw_transfer/transfer_thread_test.cc b/pw_transfer/transfer_thread_test.cc
deleted file mode 100644
index 7890041..0000000
--- a/pw_transfer/transfer_thread_test.cc
+++ /dev/null
@@ -1,218 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_transfer/transfer_thread.h"
-
-#include "gtest/gtest.h"
-#include "pw_assert/check.h"
-#include "pw_bytes/array.h"
-#include "pw_rpc/raw/client_testing.h"
-#include "pw_rpc/raw/test_method_context.h"
-#include "pw_rpc/thread_testing.h"
-#include "pw_thread/thread.h"
-#include "pw_thread_stl/options.h"
-#include "pw_transfer/handler.h"
-#include "pw_transfer/transfer.h"
-#include "pw_transfer/transfer.raw_rpc.pb.h"
-#include "pw_transfer_private/chunk_testing.h"
-
-namespace pw::transfer::test {
-namespace {
-
-using internal::Chunk;
-
-PW_MODIFY_DIAGNOSTICS_PUSH();
-PW_MODIFY_DIAGNOSTIC(ignored, "-Wmissing-field-initializers");
-
-// TODO(frolv): Have a generic way to obtain a thread for testing on any system.
-thread::Options& TransferThreadOptions() {
-  static thread::stl::Options options;
-  return options;
-}
-
-class TransferThreadTest : public ::testing::Test {
- public:
-  TransferThreadTest()
-      : ctx_(transfer_thread_, 512),
-        max_parameters_(chunk_buffer_.size(),
-                        chunk_buffer_.size(),
-                        cfg::kDefaultExtendWindowDivisor),
-        transfer_thread_(chunk_buffer_, encode_buffer_),
-        system_thread_(TransferThreadOptions(), transfer_thread_) {}
-
-  ~TransferThreadTest() {
-    transfer_thread_.Terminate();
-    system_thread_.join();
-  }
-
- protected:
-  PW_RAW_TEST_METHOD_CONTEXT(TransferService, Read) ctx_;
-
-  std::array<std::byte, 64> chunk_buffer_;
-  std::array<std::byte, 64> encode_buffer_;
-
-  rpc::RawClientTestContext<> rpc_client_context_;
-  internal::TransferParameters max_parameters_;
-
-  transfer::Thread<1, 1> transfer_thread_;
-
-  thread::Thread system_thread_;
-};
-
-class SimpleReadTransfer final : public ReadOnlyHandler {
- public:
-  SimpleReadTransfer(uint32_t transfer_id, ConstByteSpan data)
-      : ReadOnlyHandler(transfer_id),
-        prepare_read_called(false),
-        finalize_read_called(false),
-        finalize_read_status(Status::Unknown()),
-        reader_(data) {}
-
-  Status PrepareRead() final {
-    PW_CHECK_OK(reader_.Seek(0));
-    set_reader(reader_);
-    prepare_read_called = true;
-    return OkStatus();
-  }
-
-  void FinalizeRead(Status status) final {
-    finalize_read_called = true;
-    finalize_read_status = status;
-  }
-
-  bool prepare_read_called;
-  bool finalize_read_called;
-  Status finalize_read_status;
-
- private:
-  stream::MemoryReader reader_;
-};
-
-constexpr auto kData = bytes::Initialized<32>([](size_t i) { return i; });
-
-TEST_F(TransferThreadTest, AddTransferHandler) {
-  auto reader_writer = ctx_.reader_writer();
-  transfer_thread_.SetServerReadStream(reader_writer);
-
-  SimpleReadTransfer handler(3, kData);
-  transfer_thread_.AddTransferHandler(handler);
-
-  transfer_thread_.StartServerTransfer(internal::TransferType::kTransmit,
-                                       3,
-                                       3,
-                                       max_parameters_,
-                                       std::chrono::seconds(2),
-                                       0);
-
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_TRUE(handler.prepare_read_called);
-}
-
-TEST_F(TransferThreadTest, RemoveTransferHandler) {
-  auto reader_writer = ctx_.reader_writer();
-  transfer_thread_.SetServerReadStream(reader_writer);
-
-  SimpleReadTransfer handler(3, kData);
-  transfer_thread_.AddTransferHandler(handler);
-  transfer_thread_.RemoveTransferHandler(handler);
-
-  transfer_thread_.StartServerTransfer(internal::TransferType::kTransmit,
-                                       3,
-                                       3,
-                                       max_parameters_,
-                                       std::chrono::seconds(2),
-                                       0);
-
-  transfer_thread_.WaitUntilEventIsProcessed();
-
-  EXPECT_FALSE(handler.prepare_read_called);
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-  auto chunk = DecodeChunk(ctx_.response());
-  EXPECT_EQ(chunk.transfer_id, 3u);
-  ASSERT_TRUE(chunk.status.has_value());
-  EXPECT_EQ(chunk.status.value(), Status::NotFound());
-}
-
-TEST_F(TransferThreadTest, ProcessChunk_SendsWindow) {
-  auto reader_writer = ctx_.reader_writer();
-  transfer_thread_.SetServerReadStream(reader_writer);
-
-  SimpleReadTransfer handler(3, kData);
-  transfer_thread_.AddTransferHandler(handler);
-
-  transfer_thread_.StartServerTransfer(internal::TransferType::kTransmit,
-                                       3,
-                                       3,
-                                       max_parameters_,
-                                       std::chrono::seconds(2),
-                                       0);
-
-  rpc::test::WaitForPackets(ctx_.output(), 2, [this] {
-    // Malformed transfer parameters chunk without a pending_bytes field.
-    transfer_thread_.ProcessServerChunk(
-        EncodeChunk({.transfer_id = 3,
-                     .window_end_offset = 16,
-                     .pending_bytes = 16,
-                     .max_chunk_size_bytes = 8,
-                     .offset = 0,
-                     .type = Chunk::Type::kParametersRetransmit}));
-  });
-
-  ASSERT_EQ(ctx_.total_responses(), 2u);
-  auto chunk = DecodeChunk(ctx_.responses()[0]);
-  EXPECT_EQ(chunk.transfer_id, 3u);
-  EXPECT_EQ(chunk.offset, 0u);
-  EXPECT_EQ(chunk.data.size(), 8u);
-  EXPECT_EQ(std::memcmp(chunk.data.data(), kData.data(), chunk.data.size()), 0);
-
-  chunk = DecodeChunk(ctx_.responses()[1]);
-  EXPECT_EQ(chunk.transfer_id, 3u);
-  EXPECT_EQ(chunk.offset, 8u);
-  EXPECT_EQ(chunk.data.size(), 8u);
-  EXPECT_EQ(std::memcmp(chunk.data.data(), kData.data() + 8, chunk.data.size()),
-            0);
-}
-
-TEST_F(TransferThreadTest, ProcessChunk_Malformed) {
-  auto reader_writer = ctx_.reader_writer();
-  transfer_thread_.SetServerReadStream(reader_writer);
-
-  SimpleReadTransfer handler(3, kData);
-  transfer_thread_.AddTransferHandler(handler);
-
-  rpc::test::WaitForPackets(ctx_.output(), 1, [this] {
-    transfer_thread_.StartServerTransfer(internal::TransferType::kTransmit,
-                                         3,
-                                         3,
-                                         max_parameters_,
-                                         std::chrono::seconds(2),
-                                         0);
-
-    // Malformed transfer parameters chunk without a pending_bytes field.
-    transfer_thread_.ProcessServerChunk(EncodeChunk({.transfer_id = 3}));
-  });
-
-  ASSERT_EQ(ctx_.total_responses(), 1u);
-  auto chunk = DecodeChunk(ctx_.response());
-  EXPECT_EQ(chunk.transfer_id, 3u);
-  ASSERT_TRUE(chunk.status.has_value());
-  EXPECT_EQ(chunk.status.value(), Status::InvalidArgument());
-}
-
-PW_MODIFY_DIAGNOSTICS_POP();
-
-}  // namespace
-}  // namespace pw::transfer::test
diff --git a/pw_transfer/ts/BUILD.bazel b/pw_transfer/ts/BUILD.bazel
deleted file mode 100644
index ca639ae..0000000
--- a/pw_transfer/ts/BUILD.bazel
+++ /dev/null
@@ -1,74 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
-load("@npm//@bazel/typescript:index.bzl", "ts_library", "ts_project")
-load("@npm//@bazel/jasmine:index.bzl", "jasmine_node_test")
-load("//pw_protobuf_compiler/ts:ts_proto_collection.bzl", "ts_proto_collection")
-
-package(default_visibility = ["//visibility:public"])
-
-ts_proto_collection(
-    name = "transfer_proto_collection",
-    js_proto_library = "@//pw_transfer:transfer_proto_tspb",
-    proto_library = "@//pw_transfer:transfer_proto",
-)
-
-ts_project(
-    name = "lib",
-    srcs = [
-        "client.ts",
-        "transfer.ts",
-    ],
-    declaration = True,
-    source_map = True,
-    deps = [
-        "//pw_rpc/ts:pw_rpc",
-        "//pw_status/ts:pw_status",
-        "//pw_transfer:transfer_proto_tspb",
-        "@npm//:node_modules",  # can't use fine-grained deps
-    ],
-)
-
-js_library(
-    name = "pw_transfer",
-    package_name = "@pigweed/pw_transfer",
-    srcs = ["package.json"],
-    deps = [
-        ":lib",
-    ],
-)
-
-ts_library(
-    name = "test_lib",
-    srcs = [
-        "transfer_test.ts",
-    ],
-    deps = [
-        ":lib",
-        ":transfer_proto_collection",
-        "//pw_rpc/ts:lib",
-        "//pw_rpc/ts:packet_proto_tspb",
-        "//pw_status/ts:pw_status",
-        "//pw_transfer:transfer_proto_tspb",
-        "@npm//@types/jasmine",
-    ],
-)
-
-jasmine_node_test(
-    name = "test",
-    srcs = [
-        ":test_lib",
-    ],
-)
diff --git a/pw_transfer/ts/BUILD.gn b/pw_transfer/ts/BUILD.gn
deleted file mode 100644
index e69de29..0000000
--- a/pw_transfer/ts/BUILD.gn
+++ /dev/null
diff --git a/pw_transfer/ts/client.ts b/pw_transfer/ts/client.ts
deleted file mode 100644
index db1f807..0000000
--- a/pw_transfer/ts/client.ts
+++ /dev/null
@@ -1,278 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-/** Client for the pw_transfer service, which transmits data over pw_rpc. */
-
-import {
-  BidirectionalStreamingCall,
-  BidirectionalStreamingMethodStub,
-  ServiceClient,
-} from '@pigweed/pw_rpc';
-import {Status} from '@pigweed/pw_status';
-import {Chunk} from 'transfer_proto_tspb/transfer_proto_tspb_pb/pw_transfer/transfer_pb';
-
-import {
-  ReadTransfer,
-  ProgressCallback,
-  Transfer,
-  WriteTransfer,
-} from './transfer';
-
-type TransferDict = {
-  [key: number]: Transfer;
-};
-
-const DEFAULT_MAX_RETRIES = 3;
-const DEFAULT_RESPONSE_TIMEOUT_S = 2;
-const DEFAULT_INITIAL_RESPONSE_TIMEOUT = 4;
-
-/**
- *  A manager for transmitting data through an RPC TransferService.
- *
- *  This should be initialized with an active Manager over an RPC channel. Only
- *  one instance of this class should exist for a configured RPC TransferService
- *  -- the Manager supports multiple simultaneous transfers.
- *
- *  When created, a Manager starts a separate thread in which transfer
- *  communications and events are handled.
- */
-export class Manager {
-  // Ongoing transfers in the service by ID
-  private readTransfers: TransferDict = {};
-  private writeTransfers: TransferDict = {};
-
-  // RPC streams for read and write transfers. These are shareable by
-  // multiple transfers of the same type.
-  private readStream?: BidirectionalStreamingCall;
-  private writeStream?: BidirectionalStreamingCall;
-
-  /**
-   * Initializes a Manager on top of a TransferService.
-   *
-   * Args:
-   * @param{ServiceClient} service: the pw_rpc transfer service
-   * client
-   * @param{number} defaultResponseTimeoutS: max time to wait between receiving
-   * packets
-   * @param{number} initialResponseTimeoutS: timeout for the first packet; may
-   * be longer to account for transfer handler initialization
-   * @param{number} maxRetries: number of times to retry after a timeout
-   */
-  constructor(
-    private service: ServiceClient,
-    private defaultResponseTimeoutS = DEFAULT_RESPONSE_TIMEOUT_S,
-    private initialResponseTimeoutS = DEFAULT_INITIAL_RESPONSE_TIMEOUT,
-    private maxRetries = DEFAULT_MAX_RETRIES
-  ) {}
-
-  /**
-   * Receives ("downloads") data from the server.
-   *
-   * @throws Throws an error when the transfer fails to complete.
-   */
-  async read(
-    transferId: number,
-    progressCallback?: ProgressCallback
-  ): Promise<Uint8Array> {
-    if (transferId in this.readTransfers) {
-      throw new Error(`Read transfer ${transferId} already exists`);
-    }
-    const transfer = new ReadTransfer(
-      transferId,
-      this.sendReadChunkCallback,
-      this.defaultResponseTimeoutS,
-      this.maxRetries,
-      progressCallback
-    );
-
-    this.startReadTransfer(transfer);
-
-    const status = await transfer.done;
-
-    delete this.readTransfers[transfer.id];
-    if (status !== Status.OK) {
-      throw new TransferError(transfer.id, transfer.status);
-    }
-    return transfer.data;
-  }
-
-  /** Begins a new read transfer, opening the stream if it isn't. */
-  startReadTransfer(transfer: Transfer): void {
-    this.readTransfers[transfer.id] = transfer;
-
-    if (this.readStream === undefined) {
-      this.openReadStream();
-    }
-    console.debug(`Starting new read transfer ${transfer.id}`);
-    transfer.begin();
-  }
-
-  /**
-  Transmits (uploads) data to the server.
-   *
-   * @param{number} transferId: ID of the write transfer
-   * @param{Uint8Array} data: Data to send to the server.
-   */
-  async write(
-    transferId: number,
-    data: Uint8Array,
-    progressCallback?: ProgressCallback
-  ): Promise<void> {
-    const transfer = new WriteTransfer(
-      transferId,
-      data,
-      this.sendWriteChunkCallback,
-      this.defaultResponseTimeoutS,
-      this.initialResponseTimeoutS,
-      this.maxRetries,
-      progressCallback
-    );
-    this.startWriteTransfer(transfer);
-
-    const status = await transfer.done;
-
-    delete this.writeTransfers[transfer.id];
-    if (transfer.status !== Status.OK) {
-      throw new TransferError(transfer.id, transfer.status);
-    }
-  }
-
-  sendReadChunkCallback = (chunk: Chunk) => {
-    this.readStream!.send(chunk);
-  };
-
-  sendWriteChunkCallback = (chunk: Chunk) => {
-    this.writeStream!.send(chunk);
-  };
-
-  /** Begins a new write transfer, opening the stream if it isn't */
-  startWriteTransfer(transfer: Transfer): void {
-    this.writeTransfers[transfer.id] = transfer;
-
-    if (!this.writeStream) {
-      this.openWriteStream();
-    }
-
-    console.debug(`Starting new write transfer ${transfer.id}`);
-    transfer.begin();
-  }
-
-  private openReadStream(): void {
-    const readRpc = this.service.method(
-      'Read'
-    )! as BidirectionalStreamingMethodStub;
-    this.readStream = readRpc.invoke(
-      (chunk: Chunk) => {
-        this.handleChunk(this.readTransfers, chunk);
-      },
-      () => {},
-      this.onReadError
-    );
-  }
-
-  private openWriteStream(): void {
-    const writeRpc = this.service.method(
-      'Write'
-    )! as BidirectionalStreamingMethodStub;
-    this.writeStream = writeRpc.invoke(
-      (chunk: Chunk) => {
-        this.handleChunk(this.writeTransfers, chunk);
-      },
-      () => {},
-      this.onWriteError
-    );
-  }
-
-  /**
-   * Callback for an RPC error in the read stream.
-   */
-  private onReadError = (status: Status) => {
-    if (status === Status.FAILED_PRECONDITION) {
-      // FAILED_PRECONDITION indicates that the stream packet was not
-      // recognized as the stream is not open. This could occur if the
-      // server resets during an active transfer. Re-open the stream to
-      // allow pending transfers to continue.
-      this.openReadStream();
-      return;
-    }
-
-    // Other errors are unrecoverable. Clear the stream and cancel any
-    // pending transfers with an INTERNAL status as this is a system
-    // error.
-    this.readStream = undefined;
-
-    for (const key in this.readTransfers) {
-      const transfer = this.readTransfers[key];
-      transfer.finish(Status.INTERNAL);
-    }
-    this.readTransfers = {};
-    console.error(`Read stream shut down ${Status[status]}`);
-  };
-
-  private onWriteError = (status: Status) => {
-    if (status === Status.FAILED_PRECONDITION) {
-      // FAILED_PRECONDITION indicates that the stream packet was not
-      // recognized as the stream is not open. This could occur if the
-      // server resets during an active transfer. Re-open the stream to
-      // allow pending transfers to continue.
-      this.openWriteStream();
-    } else {
-      // Other errors are unrecoverable. Clear the stream and cancel any
-      // pending transfers with an INTERNAL status as this is a system
-      // error.
-      this.writeStream = undefined;
-
-      for (const key in this.writeTransfers) {
-        const transfer = this.writeTransfers[key];
-        transfer.finish(Status.INTERNAL);
-      }
-      this.writeTransfers = {};
-      console.error(`Write stream shut down: ${Status[status]}`);
-    }
-  };
-
-  /**
-   * Processes an incoming chunk from a stream.
-   *
-   * The chunk is dispatched to an active transfer based on its ID. If the
-   * transfer indicates that it is complete, the provided completion callback
-   * is invoked.
-   */
-  private async handleChunk(transfers: TransferDict, chunk: Chunk) {
-    const transfer = transfers[chunk.getTransferId()];
-    if (transfer === undefined) {
-      console.error(
-        `TransferManager received chunk for unknown transfer ${chunk.getTransferId()}`
-      );
-      return;
-    }
-    transfer.handleChunk(chunk);
-  }
-}
-
-/**
- * Exception raised when a transfer fails.
- *
- * Stores the ID of the failed transfer and the error that occured.
- */
-class TransferError extends Error {
-  id: number;
-  status: Status;
-
-  constructor(id: number, status: Status) {
-    super(`Transfer ${id} failed with status ${Status[status]}`);
-    this.status = status;
-    this.id = id;
-  }
-}
diff --git a/pw_transfer/ts/index.ts b/pw_transfer/ts/index.ts
deleted file mode 100644
index 5ba7e83..0000000
--- a/pw_transfer/ts/index.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-export {Manager} from './client';
diff --git a/pw_transfer/ts/package.json b/pw_transfer/ts/package.json
deleted file mode 100644
index 81ddd21..0000000
--- a/pw_transfer/ts/package.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-  "name": "@pigweed/pw_transfer",
-  "version": "1.0.0",
-  "main": "index.js",
-  "license": "Apache-2.0",
-  "dependencies": {
-    "@bazel/jasmine": "^4.1.0",
-    "@pigweed/pw_rpc": "link:../../pw_status/pw_rpc",
-    "@pigweed/pw_status": "link:../../pw_status/ts",
-    "@types/jasmine": "^3.9.0",
-    "jasmine": "^3.9.0",
-    "jasmine-core": "^3.9.0"
-  }
-}
diff --git a/pw_transfer/ts/transfer.ts b/pw_transfer/ts/transfer.ts
deleted file mode 100644
index 708f5cf..0000000
--- a/pw_transfer/ts/transfer.ts
+++ /dev/null
@@ -1,537 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 {
-  BidirectionalStreamingCall,
-  BidirectionalStreamingMethodStub,
-  ServiceClient,
-} from '@pigweed/pw_rpc';
-import {Status} from '@pigweed/pw_status';
-import {Chunk} from 'transfer_proto_tspb/transfer_proto_tspb_pb/pw_transfer/transfer_pb';
-
-export class ProgressStats {
-  constructor(
-    readonly bytesSent: number,
-    readonly bytesConfirmedReceived: number,
-    readonly totalSizeBytes?: number
-  ) {}
-
-  get percentReceived(): number {
-    if (this.totalSizeBytes === undefined) {
-      return NaN;
-    }
-    return (this.bytesConfirmedReceived / this.totalSizeBytes) * 100;
-  }
-
-  toString(): string {
-    const total =
-      this.totalSizeBytes === undefined
-        ? 'undefined'
-        : this.totalSizeBytes.toString();
-    const percent = this.percentReceived.toFixed(2);
-    return (
-      `${percent}% (${this.bytesSent} B sent, ` +
-      `${this.bytesConfirmedReceived} B received of ${total} B)`
-    );
-  }
-}
-
-export type ProgressCallback = (stats: ProgressStats) => void;
-
-/** A Timer which invokes a callback after a certain timeout. */
-class Timer {
-  private task?: ReturnType<typeof setTimeout>;
-
-  constructor(
-    readonly timeoutS: number,
-    private readonly callback: () => any
-  ) {}
-
-  /**
-   * Starts a new timer.
-   *
-   * If a timer is already running, it is stopped and a new timer started.
-   * This can be used to implement watchdog-like behavior, where a callback
-   * is invoked after some time without a kick.
-   */
-  start() {
-    this.stop();
-    this.task = setTimeout(this.callback, this.timeoutS * 1000);
-  }
-
-  /** Terminates a running timer. */
-  stop() {
-    if (this.task !== undefined) {
-      clearTimeout(this.task);
-      this.task = undefined;
-    }
-  }
-}
-
-/**
- * A client-side data transfer through a Manager.
- *
- * Subclasses are responsible for implementing all of the logic for their type
- * of transfer, receiving messages from the server and sending the appropriate
- * messages in response.
- */
-export abstract class Transfer {
-  status: Status = Status.OK;
-  done: Promise<Status>;
-  protected data = new Uint8Array();
-
-  private retries = 0;
-  private responseTimer?: Timer;
-  private resolve?: (value: Status | PromiseLike<Status>) => void;
-
-  constructor(
-    public id: number,
-    protected sendChunk: (chunk: Chunk) => void,
-    responseTimeoutS: number,
-    private maxRetries: number,
-    private progressCallback?: ProgressCallback
-  ) {
-    this.responseTimer = new Timer(responseTimeoutS, this.onTimeout);
-    this.done = new Promise<Status>(resolve => {
-      this.resolve = resolve!;
-    });
-  }
-
-  /** Returns the initial chunk to notify the server of the transfer. */
-  protected abstract get initialChunk(): Chunk;
-
-  /** Handles a chunk that contains or requests data. */
-  protected abstract handleDataChunk(chunk: Chunk): void;
-
-  /** Retries after a timeout occurs. */
-  protected abstract retryAfterTimeout(): void;
-
-  /** Handles a timeout while waiting for a chunk. */
-  private onTimeout = () => {
-    this.retries += 1;
-    if (this.retries > this.maxRetries) {
-      this.finish(Status.DEADLINE_EXCEEDED);
-      return;
-    }
-
-    console.debug(
-      `Received no responses for ${this.responseTimer?.timeoutS}; retrying ${this.retries}/${this.maxRetries}`
-    );
-
-    this.retryAfterTimeout();
-    this.responseTimer?.start();
-  };
-
-  /** Sends an error chunk to the server and finishes the transfer. */
-  protected sendError(error: Status): void {
-    const chunk = new Chunk();
-    chunk.setStatus(error);
-    chunk.setTransferId(this.id);
-    chunk.setType(Chunk.Type.TRANSFER_COMPLETION);
-    this.sendChunk(chunk);
-    this.finish(error);
-  }
-
-  /** Sends the initial chunk of the transfer. */
-  begin(): void {
-    this.sendChunk(this.initialChunk);
-    this.responseTimer?.start();
-  }
-
-  /** Ends the transfer with the specified status. */
-  finish(status: Status): void {
-    this.responseTimer?.stop();
-    this.responseTimer = undefined;
-    this.status = status;
-
-    if (status === Status.OK) {
-      const totalSize = this.data.length;
-      this.updateProgress(totalSize, totalSize, totalSize);
-    }
-
-    this.resolve!(this.status);
-  }
-
-  /** Invokes the provided progress callback, if any, with the progress */
-  updateProgress(
-    bytesSent: number,
-    bytesConfirmedReceived: number,
-    totalSizeBytes?: number
-  ): void {
-    const stats = new ProgressStats(
-      bytesSent,
-      bytesConfirmedReceived,
-      totalSizeBytes
-    );
-    console.debug(`Transfer ${this.id} progress: ${stats}`);
-
-    if (this.progressCallback !== undefined) {
-      this.progressCallback(stats);
-    }
-  }
-
-  /**
-   *  Processes an incoming chunk from the server.
-   *
-   *  Handles terminating chunks (i.e. those with a status) and forwards
-   *  non-terminating chunks to handle_data_chunk.
-   */
-  handleChunk(chunk: Chunk): void {
-    this.responseTimer?.stop();
-    this.retries = 0; // Received data from service, so reset the retries.
-
-    console.debug(`Received chunk:(${chunk})`);
-
-    // Status chunks are only used to terminate a transfer. They do not
-    // contain any data that requires processing.
-    if (chunk.hasStatus()) {
-      this.finish(chunk.getStatus());
-      return;
-    }
-
-    this.handleDataChunk(chunk);
-
-    // Start the timeout for the server to send a chunk in response.
-    this.responseTimer?.start();
-  }
-}
-
-/**
- * A client <= server read transfer.
- *
- * Although typescript can effectively handle an unlimited transfer window, this
- * client sets a conservative window and chunk size to avoid overloading the
- * device. These are configurable in the constructor.
- */
-export class ReadTransfer extends Transfer {
-  private maxBytesToReceive: number;
-  private maxChunkSize: number;
-  private chunkDelayMicroS?: number; // Microseconds
-  private remainingTransferSize?: number;
-  private offset = 0;
-  private pendingBytes: number;
-  private windowEndOffset: number;
-
-  // The fractional position within a window at which a receive transfer should
-  // extend its window size to minimize the amount of time the transmitter
-  // spends blocked.
-  //
-  // For example, a divisor of 2 will extend the window when half of the
-  // requested data has been received, a divisor of three will extend at a third
-  // of the window, and so on.
-  private static EXTEND_WINDOW_DIVISOR = 2;
-
-  data = new Uint8Array();
-
-  constructor(
-    id: number,
-    sendChunk: (chunk: Chunk) => void,
-    responseTimeoutS: number,
-    maxRetries: number,
-    progressCallback?: ProgressCallback,
-    maxBytesToReceive = 8192,
-    maxChunkSize = 1024,
-    chunkDelayMicroS?: number
-  ) {
-    super(id, sendChunk, responseTimeoutS, maxRetries, progressCallback);
-    this.maxBytesToReceive = maxBytesToReceive;
-    this.maxChunkSize = maxChunkSize;
-    this.chunkDelayMicroS = chunkDelayMicroS;
-    this.pendingBytes = maxBytesToReceive;
-    this.windowEndOffset = maxBytesToReceive;
-  }
-
-  protected get initialChunk(): Chunk {
-    return this.transferParameters(Chunk.Type.TRANSFER_START);
-  }
-
-  /** Builds an updated transfer parameters chunk to send the server. */
-  private transferParameters(type: Chunk.TypeMap[keyof Chunk.TypeMap]): Chunk {
-    this.pendingBytes = this.maxBytesToReceive;
-    this.windowEndOffset = this.offset + this.maxBytesToReceive;
-
-    const chunk = new Chunk();
-    chunk.setTransferId(this.id);
-    chunk.setPendingBytes(this.pendingBytes);
-    chunk.setMaxChunkSizeBytes(this.maxChunkSize);
-    chunk.setOffset(this.offset);
-    chunk.setWindowEndOffset(this.windowEndOffset);
-    chunk.setType(type);
-
-    if (this.chunkDelayMicroS !== 0) {
-      chunk.setMinDelayMicroseconds(this.chunkDelayMicroS!);
-    }
-    return chunk;
-  }
-
-  /**
-   * Processes an incoming chunk from the server.
-   *
-   * In a read transfer, the client receives data chunks from the server.
-   * Once all pending data is received, the transfer parameters are updated.
-   */
-  protected handleDataChunk(chunk: Chunk): void {
-    if (chunk.getOffset() != this.offset) {
-      // Initially, the transfer service only supports in-order transfers.
-      // If data is received out of order, request that the server
-      // retransmit from the previous offset.
-      this.sendChunk(this.transferParameters(Chunk.Type.PARAMETERS_RETRANSMIT));
-      return;
-    }
-
-    const oldData = this.data;
-    const chunkData = chunk.getData() as Uint8Array;
-    this.data = new Uint8Array(chunkData.length + oldData.length);
-    this.data.set(oldData);
-    this.data.set(chunkData, oldData.length);
-
-    this.pendingBytes -= chunk.getData().length;
-    this.offset += chunk.getData().length;
-
-    if (chunk.hasRemainingBytes()) {
-      if (chunk.getRemainingBytes() === 0) {
-        // No more data to read. Acknowledge receipt and finish.
-        const endChunk = new Chunk();
-        endChunk.setTransferId(this.id);
-        endChunk.setStatus(Status.OK);
-        endChunk.setType(Chunk.Type.TRANSFER_COMPLETION);
-        this.sendChunk(endChunk);
-        this.finish(Status.OK);
-        return;
-      }
-
-      this.remainingTransferSize = chunk.getRemainingBytes();
-    } else if (this.remainingTransferSize !== undefined) {
-      // Update the remaining transfer size, if it is known.
-      this.remainingTransferSize -= chunk.getData().length;
-
-      if (this.remainingTransferSize <= 0) {
-        this.remainingTransferSize = undefined;
-      }
-    }
-
-    if (chunk.getWindowEndOffset() !== 0) {
-      if (chunk.getWindowEndOffset() < this.offset) {
-        console.error(
-          `Transfer ${
-            this.id
-          }: transmitter sent invalid earlier end offset ${chunk.getWindowEndOffset()} (receiver offset ${
-            this.offset
-          })`
-        );
-        this.sendError(Status.INTERNAL);
-        return;
-      }
-
-      if (chunk.getWindowEndOffset() < this.offset) {
-        console.error(
-          `Transfer ${
-            this.id
-          }: transmitter sent invalid later end offset ${chunk.getWindowEndOffset()} (receiver end offset ${
-            this.windowEndOffset
-          })`
-        );
-        this.sendError(Status.INTERNAL);
-        return;
-      }
-
-      this.windowEndOffset = chunk.getWindowEndOffset();
-      this.pendingBytes -= chunk.getWindowEndOffset() - this.offset;
-    }
-
-    const remainingWindowSize = this.windowEndOffset - this.offset;
-    const extendWindow =
-      remainingWindowSize <=
-      this.maxBytesToReceive / ReadTransfer.EXTEND_WINDOW_DIVISOR;
-
-    const totalSize =
-      this.remainingTransferSize === undefined
-        ? undefined
-        : this.remainingTransferSize + this.offset;
-    this.updateProgress(this.offset, this.offset, totalSize);
-
-    if (this.pendingBytes === 0) {
-      // All pending data was received. Send out a new parameters chunk
-      // for the next block.
-      this.sendChunk(this.transferParameters(Chunk.Type.PARAMETERS_RETRANSMIT));
-    } else if (extendWindow) {
-      this.sendChunk(this.transferParameters(Chunk.Type.PARAMETERS_CONTINUE));
-    }
-  }
-
-  protected retryAfterTimeout(): void {
-    this.sendChunk(this.transferParameters(Chunk.Type.PARAMETERS_RETRANSMIT));
-  }
-}
-
-/**
- * A client => server write transfer.
- */
-export class WriteTransfer extends Transfer {
-  readonly data: Uint8Array;
-  private windowId = 0;
-  offset = 0;
-  maxChunkSize = 0;
-  chunkDelayMicroS?: number;
-  windowEndOffset = 0;
-  lastChunk: Chunk;
-
-  constructor(
-    id: number,
-    data: Uint8Array,
-    sendChunk: (chunk: Chunk) => void,
-    responseTimeoutS: number,
-    initialResponseTimeoutS: number,
-    maxRetries: number,
-    progressCallback?: ProgressCallback
-  ) {
-    super(id, sendChunk, responseTimeoutS, maxRetries, progressCallback);
-    this.data = data;
-    this.lastChunk = this.initialChunk;
-  }
-
-  protected get initialChunk(): Chunk {
-    const chunk = new Chunk();
-    chunk.setTransferId(this.id);
-    chunk.setType(Chunk.Type.TRANSFER_START);
-    return chunk;
-  }
-
-  /**
-   * Processes an incoming chunk from the server.
-   *
-   * In a write transfer, the server only sends transfer parameter updates
-   * to the client. When a message is received, update local parameters and
-   * send data accordingly.
-   */
-  protected handleDataChunk(chunk: Chunk): void {
-    this.windowId += 1;
-    const initialWindowId = this.windowId;
-
-    if (!this.handleParametersUpdate(chunk)) {
-      return;
-    }
-
-    const bytesAknowledged = chunk.getOffset();
-
-    let writeChunk: Chunk;
-    while (true) {
-      writeChunk = this.nextChunk();
-      this.offset += writeChunk.getData().length;
-      const sentRequestedBytes = this.offset === this.windowEndOffset;
-
-      this.updateProgress(this.offset, bytesAknowledged, this.data.length);
-      this.sendChunk(writeChunk);
-
-      if (sentRequestedBytes) {
-        break;
-      }
-    }
-
-    this.lastChunk = writeChunk;
-  }
-
-  /** Updates transfer state base on a transfer parameters update. */
-  private handleParametersUpdate(chunk: Chunk): boolean {
-    let retransmit = true;
-    if (chunk.hasType()) {
-      retransmit = chunk.getType() === Chunk.Type.PARAMETERS_RETRANSMIT;
-    }
-
-    if (chunk.getOffset() > this.data.length) {
-      // Bad offset; terminate the transfer.
-      console.error(
-        `Transfer ${
-          this.id
-        }: server requested invalid offset ${chunk.getOffset()} (size ${
-          this.data.length
-        })`
-      );
-
-      this.sendError(Status.OUT_OF_RANGE);
-      return false;
-    }
-
-    if (chunk.getPendingBytes() === 0) {
-      console.error(
-        `Transfer ${this.id}: service requested 0 bytes (invalid); aborting`
-      );
-      this.sendError(Status.INTERNAL);
-      return false;
-    }
-
-    if (retransmit) {
-      // Check whether the client has sent a previous data offset, which
-      // indicates that some chunks were lost in transmission.
-      if (chunk.getOffset() < this.offset) {
-        console.debug(
-          `Write transfer ${
-            this.id
-          } rolling back to offset ${chunk.getOffset()} from ${this.offset}`
-        );
-      }
-
-      this.offset = chunk.getOffset();
-
-      // Retransmit is the default behavior for older versions of the
-      // transfer protocol. The window_end_offset field is not guaranteed
-      // to be set in these version, so it must be calculated.
-      const maxBytesToSend = Math.min(
-        chunk.getPendingBytes(),
-        this.data.length - this.offset
-      );
-      this.windowEndOffset = this.offset + maxBytesToSend;
-    } else {
-      // Extend the window to the new end offset specified by the server.
-      this.windowEndOffset = Math.min(
-        chunk.getWindowEndOffset(),
-        this.data.length
-      );
-    }
-
-    if (chunk.hasMaxChunkSizeBytes()) {
-      this.maxChunkSize = chunk.getMaxChunkSizeBytes();
-    }
-
-    if (chunk.hasMinDelayMicroseconds()) {
-      this.chunkDelayMicroS = chunk.getMinDelayMicroseconds();
-    }
-    return true;
-  }
-
-  /** Returns the next Chunk message to send in the data transfer. */
-  private nextChunk(): Chunk {
-    const chunk = new Chunk();
-    chunk.setTransferId(this.id);
-    chunk.setOffset(this.offset);
-    chunk.setType(Chunk.Type.TRANSFER_DATA);
-
-    const maxBytesInChunk = Math.min(
-      this.maxChunkSize,
-      this.windowEndOffset - this.offset
-    );
-
-    chunk.setData(this.data.slice(this.offset, this.offset + maxBytesInChunk));
-
-    // Mark the final chunk of the transfer.
-    if (this.data.length - this.offset <= maxBytesInChunk) {
-      chunk.setRemainingBytes(0);
-    }
-    return chunk;
-  }
-
-  protected retryAfterTimeout(): void {
-    this.sendChunk(this.lastChunk);
-  }
-}
diff --git a/pw_transfer/ts/transfer_test.ts b/pw_transfer/ts/transfer_test.ts
deleted file mode 100644
index 1707a82..0000000
--- a/pw_transfer/ts/transfer_test.ts
+++ /dev/null
@@ -1,662 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-/* eslint-env browser, jasmine */
-import 'jasmine';
-
-import {
-  Channel,
-  Client,
-  decode,
-  MethodStub,
-  ServiceClient,
-} from '@pigweed/pw_rpc';
-import {Status} from '@pigweed/pw_status';
-import {
-  PacketType,
-  RpcPacket,
-} from 'packet_proto_tspb/packet_proto_tspb_pb/pw_rpc/internal/packet_pb';
-import {ProtoCollection} from 'transfer_proto_collection/generated/ts_proto_collection';
-import {Chunk} from 'transfer_proto_tspb/transfer_proto_tspb_pb/pw_transfer/transfer_pb';
-
-import {Manager} from './client';
-import {ProgressStats} from './transfer';
-
-const DEFAULT_TIMEOUT_S = 0.3;
-
-describe('Encoder', () => {
-  const textEncoder = new TextEncoder();
-  const textDecoder = new TextDecoder();
-  let client: Client;
-  let service: ServiceClient;
-  let sentChunks: Chunk[];
-  let packetsToSend: Uint8Array[][];
-
-  beforeEach(() => {
-    const lib = new ProtoCollection();
-    const channels: Channel[] = [new Channel(1, handleRequest)];
-    client = Client.fromProtoSet(channels, lib);
-    service = client.channel(1)!.service('pw.transfer.Transfer')!;
-
-    sentChunks = [];
-    packetsToSend = [];
-  });
-
-  function handleRequest(data: Uint8Array): void {
-    const packet = decode(data);
-    if (packet.getType() !== PacketType.CLIENT_STREAM) {
-      return;
-    }
-
-    const chunk = Chunk.deserializeBinary(packet.getPayload_asU8());
-    sentChunks.push(chunk);
-
-    if (packetsToSend.length > 0) {
-      const responses = packetsToSend.shift()!;
-      for (const response of responses) {
-        client.processPacket(response);
-      }
-    }
-  }
-
-  function receivedData(): Uint8Array {
-    let length = 0;
-    sentChunks.forEach((chunk: Chunk) => {
-      length += chunk.getData().length;
-    });
-    const data = new Uint8Array(length);
-    let offset = 0;
-    sentChunks.forEach((chunk: Chunk) => {
-      data.set(chunk.getData() as Uint8Array, offset);
-      offset += chunk.getData().length;
-    });
-    return data;
-  }
-
-  function enqueueServerError(method: MethodStub, error: Status): void {
-    const packet = new RpcPacket();
-    packet.setType(PacketType.SERVER_ERROR);
-    packet.setChannelId(1);
-    packet.setServiceId(service.id);
-    packet.setMethodId(method.id);
-    packet.setStatus(error);
-    packetsToSend.push([packet.serializeBinary()]);
-  }
-
-  function enqueueServerResponses(method: MethodStub, responses: Chunk[][]) {
-    for (const responseGroup of responses) {
-      const serializedGroup = [];
-      for (const response of responseGroup) {
-        const packet = new RpcPacket();
-        packet.setType(PacketType.SERVER_STREAM);
-        packet.setChannelId(1);
-        packet.setServiceId(service.id);
-        packet.setMethodId(method.id);
-        packet.setStatus(Status.OK);
-        packet.setPayload(response.serializeBinary());
-        serializedGroup.push(packet.serializeBinary());
-      }
-      packetsToSend.push(serializedGroup);
-    }
-  }
-
-  function buildChunk(
-    transferId: number,
-    offset: number,
-    data: string,
-    remainingBytes: number
-  ): Chunk {
-    const chunk = new Chunk();
-    chunk.setTransferId(transferId);
-    chunk.setOffset(offset);
-    chunk.setData(textEncoder.encode(data));
-    chunk.setRemainingBytes(remainingBytes);
-    return chunk;
-  }
-
-  it('read transfer basic', async () => {
-    const manager = new Manager(service, DEFAULT_TIMEOUT_S);
-
-    const chunk1 = buildChunk(3, 0, 'abc', 0);
-    enqueueServerResponses(service.method('Read')!, [[chunk1]]);
-
-    const data = await manager.read(3);
-    expect(textDecoder.decode(data)).toEqual('abc');
-    expect(sentChunks).toHaveSize(2);
-    expect(sentChunks[sentChunks.length - 1].hasStatus()).toBeTrue();
-    expect(sentChunks[sentChunks.length - 1].getStatus()).toEqual(Status.OK);
-  });
-
-  it('read transfer multichunk', async () => {
-    const manager = new Manager(service, DEFAULT_TIMEOUT_S);
-
-    const chunk1 = buildChunk(3, 0, 'abc', 3);
-    const chunk2 = buildChunk(3, 3, 'def', 0);
-    enqueueServerResponses(service.method('Read')!, [[chunk1, chunk2]]);
-
-    const data = await manager.read(3);
-    expect(data).toEqual(textEncoder.encode('abcdef'));
-    expect(sentChunks).toHaveSize(2);
-    expect(sentChunks[sentChunks.length - 1].hasStatus()).toBeTrue();
-    expect(sentChunks[sentChunks.length - 1].getStatus()).toEqual(Status.OK);
-  });
-
-  it('read transfer progress callback', async () => {
-    const manager = new Manager(service, DEFAULT_TIMEOUT_S);
-
-    const chunk1 = buildChunk(3, 0, 'abc', 3);
-    const chunk2 = buildChunk(3, 3, 'def', 0);
-    enqueueServerResponses(service.method('Read')!, [[chunk1, chunk2]]);
-
-    const progress: Array<ProgressStats> = [];
-
-    const data = await manager.read(3, (stats: ProgressStats) => {
-      progress.push(stats);
-    });
-    expect(textDecoder.decode(data)).toEqual('abcdef');
-    expect(sentChunks).toHaveSize(2);
-    expect(sentChunks[sentChunks.length - 1].hasStatus()).toBeTrue();
-    expect(sentChunks[sentChunks.length - 1].getStatus()).toEqual(Status.OK);
-
-    expect(progress).toEqual([
-      new ProgressStats(3, 3, 6),
-      new ProgressStats(6, 6, 6),
-    ]);
-  });
-
-  it('read transfer retry bad offset', async () => {
-    const manager = new Manager(service, DEFAULT_TIMEOUT_S);
-
-    const chunk1 = buildChunk(3, 0, '123', 6);
-    const chunk2 = buildChunk(3, 1, '456', 3); // Incorrect offset; expecting 3
-    const chunk3 = buildChunk(3, 3, '456', 3);
-    const chunk4 = buildChunk(3, 6, '789', 0);
-
-    enqueueServerResponses(service.method('Read')!, [
-      [chunk1, chunk2],
-      [chunk3, chunk4],
-    ]);
-
-    const data = await manager.read(3);
-    expect(data).toEqual(textEncoder.encode('123456789'));
-    expect(sentChunks).toHaveSize(3);
-    expect(sentChunks[sentChunks.length - 1].hasStatus()).toBeTrue();
-    expect(sentChunks[sentChunks.length - 1].getStatus()).toEqual(Status.OK);
-  });
-
-  it('read transfer retry timeout', async () => {
-    const manager = new Manager(service, DEFAULT_TIMEOUT_S);
-
-    const chunk = buildChunk(3, 0, 'xyz', 0);
-    enqueueServerResponses(service.method('Read')!, [[], [chunk]]);
-
-    const data = await manager.read(3);
-    expect(textDecoder.decode(data)).toEqual('xyz');
-
-    // Two transfer parameter requests should have been sent.
-    expect(sentChunks).toHaveSize(3);
-    expect(sentChunks[sentChunks.length - 1].hasStatus()).toBeTrue();
-    expect(sentChunks[sentChunks.length - 1].getStatus()).toEqual(Status.OK);
-  });
-
-  it('read transfer timeout', async () => {
-    const manager = new Manager(service, DEFAULT_TIMEOUT_S);
-
-    await manager
-      .read(27)
-      .then(() => {
-        fail('Unexpected completed promise');
-      })
-      .catch(error => {
-        expect(error.id).toEqual(27);
-        expect(Status[error.status]).toEqual(Status[Status.DEADLINE_EXCEEDED]);
-        expect(sentChunks).toHaveSize(4);
-      });
-  });
-
-  it('read transfer error', async () => {
-    const manager = new Manager(service, DEFAULT_TIMEOUT_S);
-
-    const chunk = new Chunk();
-    chunk.setStatus(Status.NOT_FOUND);
-    chunk.setTransferId(31);
-    enqueueServerResponses(service.method('Read')!, [[chunk]]);
-
-    await manager
-      .read(31)
-      .then(() => {
-        fail('Unexpected completed promise');
-      })
-      .catch(error => {
-        expect(error.id).toEqual(31);
-        expect(Status[error.status]).toEqual(Status[Status.NOT_FOUND]);
-      });
-  });
-
-  it('read transfer server error', async () => {
-    const manager = new Manager(service, DEFAULT_TIMEOUT_S);
-
-    enqueueServerError(service.method('Read')!, Status.NOT_FOUND);
-    await manager
-      .read(31)
-      .then(data => {
-        fail('Unexpected completed promise');
-      })
-      .catch(error => {
-        expect(error.id).toEqual(31);
-        expect(Status[error.status]).toEqual(Status[Status.INTERNAL]);
-      });
-  });
-
-  it('write transfer basic', async () => {
-    const manager = new Manager(service, DEFAULT_TIMEOUT_S);
-
-    const chunk = new Chunk();
-    chunk.setTransferId(4);
-    chunk.setOffset(0);
-    chunk.setPendingBytes(32);
-    chunk.setMaxChunkSizeBytes(8);
-
-    const completeChunk = new Chunk();
-    completeChunk.setTransferId(4);
-    completeChunk.setStatus(Status.OK);
-
-    enqueueServerResponses(service.method('Write')!, [
-      [chunk],
-      [completeChunk],
-    ]);
-
-    await manager.write(4, textEncoder.encode('hello'));
-    expect(sentChunks).toHaveSize(2);
-    expect(receivedData()).toEqual(textEncoder.encode('hello'));
-  });
-
-  it('write transfer max chunk size', async () => {
-    const manager = new Manager(service, DEFAULT_TIMEOUT_S);
-
-    const chunk = new Chunk();
-    chunk.setTransferId(4);
-    chunk.setOffset(0);
-    chunk.setPendingBytes(32);
-    chunk.setMaxChunkSizeBytes(8);
-
-    const completeChunk = new Chunk();
-    completeChunk.setTransferId(4);
-    completeChunk.setStatus(Status.OK);
-
-    enqueueServerResponses(service.method('Write')!, [
-      [chunk],
-      [completeChunk],
-    ]);
-
-    await manager.write(4, textEncoder.encode('hello world'));
-    expect(sentChunks).toHaveSize(3);
-    expect(receivedData()).toEqual(textEncoder.encode('hello world'));
-    expect(sentChunks[1].getData()).toEqual(textEncoder.encode('hello wo'));
-    expect(sentChunks[2].getData()).toEqual(textEncoder.encode('rld'));
-  });
-
-  it('write transfer multiple parameters', async () => {
-    const manager = new Manager(service, DEFAULT_TIMEOUT_S);
-
-    const chunk = new Chunk();
-    chunk.setTransferId(4);
-    chunk.setOffset(0);
-    chunk.setPendingBytes(8);
-    chunk.setMaxChunkSizeBytes(8);
-
-    const chunk2 = new Chunk();
-    chunk2.setTransferId(4);
-    chunk2.setOffset(8);
-    chunk2.setPendingBytes(8);
-    chunk2.setMaxChunkSizeBytes(8);
-
-    const completeChunk = new Chunk();
-    completeChunk.setTransferId(4);
-    completeChunk.setStatus(Status.OK);
-
-    enqueueServerResponses(service.method('Write')!, [
-      [chunk],
-      [chunk2],
-      [completeChunk],
-    ]);
-
-    await manager.write(4, textEncoder.encode('data to write'));
-    expect(sentChunks).toHaveSize(3);
-    expect(receivedData()).toEqual(textEncoder.encode('data to write'));
-    expect(sentChunks[1].getData()).toEqual(textEncoder.encode('data to '));
-    expect(sentChunks[2].getData()).toEqual(textEncoder.encode('write'));
-  });
-
-  it('write transfer parameters update', async () => {
-    const manager = new Manager(service, DEFAULT_TIMEOUT_S);
-
-    const chunk = new Chunk();
-    chunk.setTransferId(4);
-    chunk.setOffset(0);
-    chunk.setPendingBytes(8);
-    chunk.setMaxChunkSizeBytes(4);
-    chunk.setType(Chunk.Type.PARAMETERS_RETRANSMIT);
-    chunk.setWindowEndOffset(8);
-
-    const chunk2 = new Chunk();
-    chunk2.setTransferId(4);
-    chunk2.setOffset(4);
-    chunk2.setPendingBytes(8);
-    chunk2.setType(Chunk.Type.PARAMETERS_CONTINUE);
-    chunk2.setWindowEndOffset(12);
-
-    const chunk3 = new Chunk();
-    chunk3.setTransferId(4);
-    chunk3.setOffset(8);
-    chunk3.setPendingBytes(8);
-    chunk3.setType(Chunk.Type.PARAMETERS_CONTINUE);
-    chunk3.setWindowEndOffset(16);
-
-    const chunk4 = new Chunk();
-    chunk4.setTransferId(4);
-    chunk4.setOffset(12);
-    chunk4.setPendingBytes(8);
-    chunk4.setType(Chunk.Type.PARAMETERS_CONTINUE);
-    chunk4.setWindowEndOffset(20);
-
-    const chunk5 = new Chunk();
-    chunk5.setTransferId(4);
-    chunk5.setOffset(16);
-    chunk5.setPendingBytes(8);
-    chunk5.setType(Chunk.Type.PARAMETERS_CONTINUE);
-    chunk5.setWindowEndOffset(24);
-
-    const chunk6 = new Chunk();
-    chunk6.setTransferId(4);
-    chunk6.setOffset(20);
-    chunk6.setPendingBytes(8);
-    chunk6.setType(Chunk.Type.PARAMETERS_CONTINUE);
-    chunk6.setWindowEndOffset(28);
-
-    const completeChunk = new Chunk();
-    completeChunk.setTransferId(4);
-    completeChunk.setStatus(Status.OK);
-
-    enqueueServerResponses(service.method('Write')!, [
-      [chunk],
-      [chunk2],
-      [chunk3],
-      [chunk4],
-      [chunk5],
-      [chunk6],
-      [completeChunk],
-    ]);
-
-    await manager.write(4, textEncoder.encode('hello this is a message'));
-    expect(receivedData()).toEqual(
-      textEncoder.encode('hello this is a message')
-    );
-    expect(sentChunks[1].getData()).toEqual(textEncoder.encode('hell'));
-    expect(sentChunks[2].getData()).toEqual(textEncoder.encode('o th'));
-    expect(sentChunks[3].getData()).toEqual(textEncoder.encode('is i'));
-    expect(sentChunks[4].getData()).toEqual(textEncoder.encode('s a '));
-    expect(sentChunks[5].getData()).toEqual(textEncoder.encode('mess'));
-    expect(sentChunks[6].getData()).toEqual(textEncoder.encode('age'));
-  });
-
-  it('write transfer progress callback', async () => {
-    const manager = new Manager(service, DEFAULT_TIMEOUT_S);
-
-    const chunk = new Chunk();
-    chunk.setTransferId(4);
-    chunk.setOffset(0);
-    chunk.setPendingBytes(8);
-    chunk.setMaxChunkSizeBytes(8);
-
-    const chunk2 = new Chunk();
-    chunk2.setTransferId(4);
-    chunk2.setOffset(8);
-    chunk2.setPendingBytes(8);
-    chunk2.setMaxChunkSizeBytes(8);
-
-    const completeChunk = new Chunk();
-    completeChunk.setTransferId(4);
-    completeChunk.setStatus(Status.OK);
-
-    enqueueServerResponses(service.method('Write')!, [
-      [chunk],
-      [chunk2],
-      [completeChunk],
-    ]);
-
-    const progress: Array<ProgressStats> = [];
-    await manager.write(
-      4,
-      textEncoder.encode('data to write'),
-      (stats: ProgressStats) => {
-        progress.push(stats);
-      }
-    );
-    expect(sentChunks).toHaveSize(3);
-    expect(receivedData()).toEqual(textEncoder.encode('data to write'));
-    expect(sentChunks[1].getData()).toEqual(textEncoder.encode('data to '));
-    expect(sentChunks[2].getData()).toEqual(textEncoder.encode('write'));
-
-    console.log(progress);
-    expect(progress).toEqual([
-      new ProgressStats(8, 0, 13),
-      new ProgressStats(13, 8, 13),
-      new ProgressStats(13, 13, 13),
-    ]);
-  });
-
-  it('write transfer rewind', async () => {
-    const manager = new Manager(service, DEFAULT_TIMEOUT_S);
-
-    const chunk1 = new Chunk();
-    chunk1.setTransferId(4);
-    chunk1.setOffset(0);
-    chunk1.setPendingBytes(8);
-    chunk1.setMaxChunkSizeBytes(8);
-
-    const chunk2 = new Chunk();
-    chunk2.setTransferId(4);
-    chunk2.setOffset(8);
-    chunk2.setPendingBytes(8);
-    chunk2.setMaxChunkSizeBytes(8);
-
-    const chunk3 = new Chunk();
-    chunk3.setTransferId(4);
-    chunk3.setOffset(4); // Rewind
-    chunk3.setPendingBytes(8);
-    chunk3.setMaxChunkSizeBytes(8);
-
-    const chunk4 = new Chunk();
-    chunk4.setTransferId(4);
-    chunk4.setOffset(12); // Rewind
-    chunk4.setPendingBytes(16);
-    chunk4.setMaxChunkSizeBytes(16);
-
-    const completeChunk = new Chunk();
-    completeChunk.setTransferId(4);
-    completeChunk.setStatus(Status.OK);
-
-    enqueueServerResponses(service.method('Write')!, [
-      [chunk1],
-      [chunk2],
-      [chunk3],
-      [chunk4],
-      [completeChunk],
-    ]);
-
-    await manager.write(4, textEncoder.encode('pigweed data transfer'));
-    expect(sentChunks).toHaveSize(5);
-    expect(sentChunks[1].getData()).toEqual(textEncoder.encode('pigweed '));
-    expect(sentChunks[2].getData()).toEqual(textEncoder.encode('data tra'));
-    expect(sentChunks[3].getData()).toEqual(textEncoder.encode('eed data'));
-    expect(sentChunks[4].getData()).toEqual(textEncoder.encode(' transfer'));
-  });
-
-  it('write transfer bad offset', async () => {
-    const manager = new Manager(service, DEFAULT_TIMEOUT_S);
-
-    const chunk1 = new Chunk();
-    chunk1.setTransferId(4);
-    chunk1.setOffset(0);
-    chunk1.setPendingBytes(8);
-    chunk1.setMaxChunkSizeBytes(8);
-
-    const chunk2 = new Chunk();
-    chunk2.setTransferId(4);
-    chunk2.setOffset(100); // larger offset than data
-    chunk2.setPendingBytes(8);
-    chunk2.setMaxChunkSizeBytes(8);
-
-    const completeChunk = new Chunk();
-    completeChunk.setTransferId(4);
-    completeChunk.setStatus(Status.OK);
-
-    enqueueServerResponses(service.method('Write')!, [
-      [chunk1],
-      [chunk2],
-      [completeChunk],
-    ]);
-
-    await manager
-      .write(4, textEncoder.encode('small data'))
-      .then(() => {
-        fail('Unexpected succesful promise');
-      })
-      .catch(error => {
-        expect(error.id).toEqual(4);
-        expect(Status[error.status]).toEqual(Status[Status.OUT_OF_RANGE]);
-      });
-  });
-
-  it('write transfer error', async () => {
-    const manager = new Manager(service, DEFAULT_TIMEOUT_S);
-
-    const chunk = new Chunk();
-    chunk.setTransferId(21);
-    chunk.setStatus(Status.UNAVAILABLE);
-
-    enqueueServerResponses(service.method('Write')!, [[chunk]]);
-
-    await manager
-      .write(21, textEncoder.encode('no write'))
-      .then(() => {
-        fail('Unexpected succesful promise');
-      })
-      .catch(error => {
-        expect(error.id).toEqual(21);
-        expect(Status[error.status]).toEqual(Status[Status.UNAVAILABLE]);
-      });
-  });
-
-  it('write transfer server error', async () => {
-    const manager = new Manager(service, DEFAULT_TIMEOUT_S);
-
-    const chunk = new Chunk();
-    chunk.setTransferId(21);
-    chunk.setStatus(Status.NOT_FOUND);
-
-    enqueueServerError(service.method('Write')!, Status.NOT_FOUND);
-
-    await manager
-      .write(21, textEncoder.encode('server error'))
-      .then(() => {
-        fail('Unexpected succesful promise');
-      })
-      .catch(error => {
-        expect(error.id).toEqual(21);
-        expect(Status[error.status]).toEqual(Status[Status.INTERNAL]);
-      });
-  });
-
-  it('write transfer timeout after initial chunk', async () => {
-    const manager = new Manager(service, 0.001, 4, 2);
-
-    await manager
-      .write(22, textEncoder.encode('no server response!'))
-      .then(() => {
-        fail('unexpected succesful write');
-      })
-      .catch(error => {
-        expect(sentChunks).toHaveSize(3); // Initial chunk + two retries.
-        expect(error.id).toEqual(22);
-        expect(Status[error.status]).toEqual(Status[Status.DEADLINE_EXCEEDED]);
-      });
-  });
-
-  it('write transfer timeout after intermediate chunk', async () => {
-    const manager = new Manager(service, DEFAULT_TIMEOUT_S, 4, 2);
-
-    const chunk = new Chunk();
-    chunk.setTransferId(22);
-    chunk.setPendingBytes(10);
-    chunk.setMaxChunkSizeBytes(5);
-
-    enqueueServerResponses(service.method('Write')!, [[chunk]]);
-
-    await manager
-      .write(22, textEncoder.encode('0123456789'))
-      .then(() => {
-        fail('unexpected succesful write');
-      })
-      .catch(error => {
-        const expectedChunk1 = new Chunk();
-        expectedChunk1.setTransferId(22);
-        expectedChunk1.setType(Chunk.Type.TRANSFER_START);
-        const expectedChunk2 = new Chunk();
-        expectedChunk2.setTransferId(22);
-        expectedChunk2.setData(textEncoder.encode('01234'));
-        expectedChunk2.setType(Chunk.Type.TRANSFER_DATA);
-        const lastChunk = new Chunk();
-        lastChunk.setTransferId(22);
-        lastChunk.setData(textEncoder.encode('56789'));
-        lastChunk.setOffset(5);
-        lastChunk.setRemainingBytes(0);
-        lastChunk.setType(Chunk.Type.TRANSFER_DATA);
-
-        const expectedChunks = [
-          expectedChunk1,
-          expectedChunk2,
-          lastChunk,
-          lastChunk, // retry 1
-          lastChunk, // retry 2
-        ];
-
-        expect(sentChunks).toEqual(expectedChunks);
-
-        expect(error.id).toEqual(22);
-        expect(Status[error.status]).toEqual(Status[Status.DEADLINE_EXCEEDED]);
-      });
-  });
-
-  it('write zero pending bytes is internal error', async () => {
-    const manager = new Manager(service, DEFAULT_TIMEOUT_S);
-
-    const chunk = new Chunk();
-    chunk.setTransferId(23);
-    chunk.setPendingBytes(0);
-
-    enqueueServerResponses(service.method('Write')!, [[chunk]]);
-
-    await manager
-      .write(23, textEncoder.encode('no write'))
-      .then(() => {
-        fail('Unexpected succesful promise');
-      })
-      .catch(error => {
-        expect(error.id).toEqual(23);
-        expect(Status[error.status]).toEqual(Status[Status.INTERNAL]);
-      });
-  });
-});
diff --git a/pw_transfer/ts/tsconfig.json b/pw_transfer/ts/tsconfig.json
deleted file mode 100644
index 0fab6a4..0000000
--- a/pw_transfer/ts/tsconfig.json
+++ /dev/null
@@ -1,35 +0,0 @@
-{
-  "compilerOptions": {
-    "allowUnreachableCode": false,
-    "allowUnusedLabels": false,
-    "declaration": true,
-    "forceConsistentCasingInFileNames": true,
-    "lib": [
-      "es2018",
-      "dom",
-      "dom.iterable",
-      "esnext"
-    ],
-    "module": "commonjs",
-    "noEmitOnError": true,
-    "noFallthroughCasesInSwitch": true,
-    "noImplicitReturns": true,
-    "pretty": true,
-    "sourceMap": true,
-    "strict": true,
-    "target": "es2018",
-    "jsx": "react",
-    "plugins": [
-      {
-        "name": "@bazel/tsetse",
-        "disabledRules": [
-          "must-use-promises"
-        ]
-      }
-    ]
-  },
-  "exclude": [
-    "node_modules"
-  ]
-}
-
diff --git a/pw_transfer/write.svg b/pw_transfer/write.svg
deleted file mode 100644
index fba4e98..0000000
--- a/pw_transfer/write.svg
+++ /dev/null
@@ -1,131 +0,0 @@
-<!-- Created with blockdiag from the source below:
-
-  seqdiag {
-    default_note_color = aliceblue;
-
-    client -> server [
-        label = "start",
-        leftnote = "transfer_id\ntype=TRANSFER_START"
-    ];
-
-    client <- server [
-        noactivate,
-        label = "set transfer parameters",
-        rightnote = "transfer_id\noffset\nwindow_end_offset\ntype=PARAMETERS_RETRANSMIT\nmax_chunk_size\nchunk_delay"
-    ];
-
-    client -\-> server [
-        noactivate,
-        label = "requested bytes\n(zero or more chunks)",
-        leftnote = "transfer_id\noffset\ndata\n(remaining_bytes)"
-    ];
-
-    client <-\- server [
-        noactivate,
-        label = "update transfer parameters\n(as needed)",
-        rightnote = "transfer_id\noffset\nwindow_end_offset\ntype=PARAMETERS_CONTINUE\n(max_chunk_size)\n(chunk_delay)"
-    ];
-
-    client -> server [
-        noactivate,
-        label = "final chunk",
-        leftnote = "transfer_id\noffset\ndata\nremaining_bytes=0"
-    ];
-
-    client <- server [
-        noactivate,
-        label = "acknowledge completion",
-        rightnote = "transfer_id\nstatus=OK"
-    ];
-  }
-
--->
-<svg height="720" viewBox="0 0 585 720" xmlns="http://www.w3.org/2000/svg" xmlns:inkspace="http://www.inkscape.org/namespaces/inkscape" xmlns:xlink="http://www.w3.org/1999/xlink" style="background-color:white">
-  <defs id="defs_block">
-    <filter height="1.504" id="filter_blur" inkspace:collect="always" width="1.1575" x="-0.07875" y="-0.252">
-      <feGaussianBlur id="feGaussianBlur3780" inkspace:collect="always" stdDeviation="4.2"></feGaussianBlur>
-    </filter>
-  </defs>
-  <title>blockdiag</title>
-  <desc></desc>
-  <rect fill="rgb(0,0,0)" height="558" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="8" x="178" y="140"></rect>
-  <rect fill="rgb(0,0,0)" height="558" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="8" x="370" y="140"></rect>
-  <polygon fill="rgb(0,0,0)" points="27,126 158,126 166,134 166,154 27,154 27,126" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></polygon>
-  <polygon fill="rgb(0,0,0)" points="390,194 564,194 572,202 572,274 390,274 390,194" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></polygon>
-  <polygon fill="rgb(0,0,0)" points="39,314 158,314 166,322 166,368 39,368 39,314" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></polygon>
-  <polygon fill="rgb(0,0,0)" points="390,408 552,408 560,416 560,488 390,488 390,408" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></polygon>
-  <polygon fill="rgb(0,0,0)" points="39,528 158,528 166,536 166,582 39,582 39,528" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></polygon>
-  <polygon fill="rgb(0,0,0)" points="390,623 473,623 481,631 481,651 390,651 390,623" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1"></polygon>
-  <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="118" y="46"></rect>
-  <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="310" y="46"></rect>
-  <path d="M 179 80 L 179 708" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="8 4"></path>
-  <rect fill="moccasin" height="558" stroke="rgb(0,0,0)" width="8" x="175" y="134"></rect>
-  <path d="M 371 80 L 371 708" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="8 4"></path>
-  <rect fill="moccasin" height="558" stroke="rgb(0,0,0)" width="8" x="367" y="134"></rect>
-  <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="115" y="40"></rect>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="37" x="179" y="66">client</text>
-  <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="307" y="40"></rect>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="37" x="371" y="66">server</text>
-  <path d="M 187 134 L 363 134" fill="none" stroke="rgb(0,0,0)"></path>
-  <polygon fill="rgb(0,0,0)" points="355,130 363,134 355,138" stroke="rgb(0,0,0)"></polygon>
-  <polygon fill="rgb(240,248,255)" points="24,120 155,120 163,128 163,148 24,148 24,120" stroke="rgb(0,0,0)"></polygon>
-  <path d="M 155 120 L 155 128" fill="none" stroke="rgb(0,0,0)"></path>
-  <path d="M 155 128 L 163 128" fill="none" stroke="rgb(0,0,0)"></path>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="67" x="65" y="133">transfer_id</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="115" x="89" y="146">type=TRANSFER_START</text>
-  <path d="M 187 228 L 363 228" fill="none" stroke="rgb(0,0,0)"></path>
-  <polygon fill="rgb(0,0,0)" points="195,224 187,228 195,232" stroke="rgb(0,0,0)"></polygon>
-  <polygon fill="rgb(240,248,255)" points="387,188 561,188 569,196 569,268 387,268 387,188" stroke="rgb(0,0,0)"></polygon>
-  <path d="M 561 188 L 561 196" fill="none" stroke="rgb(0,0,0)"></path>
-  <path d="M 561 196 L 569 196" fill="none" stroke="rgb(0,0,0)"></path>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="67" x="428" y="201">transfer_id</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="37" x="413" y="214">offset</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="103" x="446" y="227">window_end_offset</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="158" x="474" y="240">type=PARAMETERS_RETRANSMIT</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="85" x="437" y="253">max_chunk_size</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="67" x="428" y="266">chunk_delay</text>
-  <path d="M 187 335 L 363 335" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="4"></path>
-  <polygon fill="rgb(0,0,0)" points="355,331 363,335 355,339" stroke="rgb(0,0,0)"></polygon>
-  <polygon fill="rgb(240,248,255)" points="36,308 155,308 163,316 163,362 36,362 36,308" stroke="rgb(0,0,0)"></polygon>
-  <path d="M 155 308 L 155 316" fill="none" stroke="rgb(0,0,0)"></path>
-  <path d="M 155 316 L 163 316" fill="none" stroke="rgb(0,0,0)"></path>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="67" x="77" y="321">transfer_id</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="37" x="62" y="334">offset</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="25" x="56" y="347">data</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="103" x="95" y="360">(remaining_bytes)</text>
-  <path d="M 187 442 L 363 442" fill="none" stroke="rgb(0,0,0)" stroke-dasharray="4"></path>
-  <polygon fill="rgb(0,0,0)" points="195,438 187,442 195,446" stroke="rgb(0,0,0)"></polygon>
-  <polygon fill="rgb(240,248,255)" points="387,402 549,402 557,410 557,482 387,482 387,402" stroke="rgb(0,0,0)"></polygon>
-  <path d="M 549 402 L 549 410" fill="none" stroke="rgb(0,0,0)"></path>
-  <path d="M 549 410 L 557 410" fill="none" stroke="rgb(0,0,0)"></path>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="67" x="428" y="415">transfer_id</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="37" x="413" y="428">offset</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="103" x="446" y="441">window_end_offset</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="146" x="468" y="454">type=PARAMETERS_CONTINUE</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="97" x="443" y="467">(max_chunk_size)</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="79" x="434" y="480">(chunk_delay)</text>
-  <path d="M 187 549 L 363 549" fill="none" stroke="rgb(0,0,0)"></path>
-  <polygon fill="rgb(0,0,0)" points="355,545 363,549 355,553" stroke="rgb(0,0,0)"></polygon>
-  <polygon fill="rgb(240,248,255)" points="36,522 155,522 163,530 163,576 36,576 36,522" stroke="rgb(0,0,0)"></polygon>
-  <path d="M 155 522 L 155 530" fill="none" stroke="rgb(0,0,0)"></path>
-  <path d="M 155 530 L 163 530" fill="none" stroke="rgb(0,0,0)"></path>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="67" x="77" y="535">transfer_id</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="37" x="62" y="548">offset</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="25" x="56" y="561">data</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="103" x="95" y="574">remaining_bytes=0</text>
-  <path d="M 187 631 L 363 631" fill="none" stroke="rgb(0,0,0)"></path>
-  <polygon fill="rgb(0,0,0)" points="195,627 187,631 195,635" stroke="rgb(0,0,0)"></polygon>
-  <polygon fill="rgb(240,248,255)" points="387,617 470,617 478,625 478,645 387,645 387,617" stroke="rgb(0,0,0)"></polygon>
-  <path d="M 470 617 L 470 625" fill="none" stroke="rgb(0,0,0)"></path>
-  <path d="M 470 625 L 478 625" fill="none" stroke="rgb(0,0,0)"></path>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="67" x="428" y="630">transfer_id</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="55" x="422" y="643">status=OK</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="31" x="206" y="132">start</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="140" x="293" y="226">set transfer parameters</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="91" x="236" y="320">requested bytes</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="128" x="255" y="333">(zero or more chunks)</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="158" x="284" y="427">update transfer parameters</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="67" x="329" y="440">(as needed)</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="67" x="224" y="547">final chunk</text>
-  <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" textLength="134" x="296" y="629">acknowledge completion</text>
-</svg>
diff --git a/pw_unit_test/BUILD b/pw_unit_test/BUILD
new file mode 100644
index 0000000..7d94c19
--- /dev/null
+++ b/pw_unit_test/BUILD
@@ -0,0 +1,125 @@
+# Copyright 2019 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_unit_test",
+    srcs = [
+        "framework.cc",
+    ],
+    hdrs = [
+        "public/pw_unit_test/event_handler.h",
+        "public/pw_unit_test/framework.h",
+        "public_overrides/gtest/gtest.h",
+    ],
+    includes = [
+        "public",
+        "public_overrides",
+    ],
+    deps = [
+        "//pw_polyfill",
+        "//pw_preprocessor",
+        "//pw_string",
+    ],
+)
+
+pw_cc_library(
+    name = "simple_printing_event_handler",
+    srcs = ["simple_printing_event_handler.cc"],
+    hdrs = [
+        "public/pw_unit_test/simple_printing_event_handler.h",
+    ],
+    includes = [
+        "public",
+    ],
+    deps = [
+        "//pw_preprocessor",
+        "//pw_unit_test",
+    ],
+)
+
+filegroup(
+    name = "logging_event_handler",
+    srcs = [
+        "logging_event_handler.cc",
+        "public/pw_unit_test/logging_event_handler.h",
+    ],
+)
+
+filegroup(
+    name = "logging_main",
+    srcs = [
+        "logging_main.cc",
+    ],
+)
+
+pw_cc_library(
+    name = "main",
+    srcs = [
+        "simple_printing_main.cc",
+    ],
+    deps = [
+        ":pw_unit_test",
+        ":simple_printing_event_handler",
+        "//pw_span",
+        "//pw_sys_io",
+    ],
+)
+
+pw_cc_library(
+    name = "rpc_service",
+    hdrs = [
+        "public/pw_unit_test/internal/rpc_event_handler.h",
+        "public/pw_unit_test/unit_test_service.h",
+    ],
+    srcs = [
+        "rpc_event_handler.cc",
+        "unit_test_service.cc",
+    ],
+    deps = [
+        ":pw_unit_test",
+        "//pw_log",
+    ],
+)
+
+pw_cc_library(
+    name = "rpc_main",
+    srcs = [
+        "rpc_main.cc",
+    ],
+    deps = [
+        ":pw_unit_test",
+        ":rpc_service",
+        "//pw_hdlc:pw_rpc",
+        "//pw_log",
+        "//pw_rpc:server",
+    ],
+)
+
+pw_cc_test(
+    name = "framework_test",
+    srcs = ["framework_test.cc"],
+    deps = [
+        ":pw_unit_test",
+    ],
+)
diff --git a/pw_unit_test/BUILD.bazel b/pw_unit_test/BUILD.bazel
deleted file mode 100644
index 7ef41c7..0000000
--- a/pw_unit_test/BUILD.bazel
+++ /dev/null
@@ -1,177 +0,0 @@
-# Copyright 2019 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_binary",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-load("//pw_protobuf_compiler:proto.bzl", "pw_proto_library")
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "config",
-    hdrs = ["public/pw_unit_test/config.h"],
-    includes = ["public"],
-    deps = [
-        "//pw_polyfill",
-    ],
-)
-
-pw_cc_library(
-    name = "pw_unit_test",
-    srcs = [
-        "framework.cc",
-    ],
-    hdrs = [
-        "public/pw_unit_test/event_handler.h",
-        "public/pw_unit_test/framework.h",
-        "public_overrides/gtest/gtest.h",
-    ],
-    includes = [
-        "public",
-        "public_overrides",
-    ],
-    deps = [
-        ":config",
-        "//pw_polyfill",
-        "//pw_preprocessor",
-        "//pw_span",
-        "//pw_string",
-    ],
-)
-
-pw_cc_library(
-    name = "simple_printing_event_handler",
-    srcs = ["simple_printing_event_handler.cc"],
-    hdrs = [
-        "public/pw_unit_test/simple_printing_event_handler.h",
-    ],
-    includes = [
-        "public",
-    ],
-    deps = [
-        "//pw_preprocessor",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_library(
-    name = "logging_event_handler",
-    srcs = [
-        "logging_event_handler.cc",
-    ],
-    hdrs = [
-        "public/pw_unit_test/logging_event_handler.h",
-    ],
-    includes = [
-        "public",
-    ],
-    deps = [
-        "//pw_log",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_binary(
-    name = "logging_main",
-    srcs = [
-        "logging_main.cc",
-    ],
-    deps = [
-        ":logging_event_handler",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_library(
-    name = "main",
-    srcs = [
-        "simple_printing_main.cc",
-    ],
-    deps = [
-        ":pw_unit_test",
-        ":simple_printing_event_handler",
-        "//pw_span",
-        "//pw_sys_io",
-    ],
-)
-
-proto_library(
-    name = "unit_test_proto",
-    srcs = ["pw_unit_test_proto/unit_test.proto"],
-    strip_import_prefix = "//pw_unit_test",
-)
-
-pw_proto_library(
-    name = "unit_test_cc",
-    deps = [":unit_test_proto"],
-)
-
-pw_cc_library(
-    name = "rpc_service",
-    srcs = [
-        "rpc_event_handler.cc",
-        "unit_test_service.cc",
-    ],
-    hdrs = [
-        "public/pw_unit_test/internal/rpc_event_handler.h",
-        "public/pw_unit_test/unit_test_service.h",
-    ],
-    deps = [
-        ":pw_unit_test",
-        ":unit_test_cc.pwpb",
-        ":unit_test_cc.raw_rpc",
-        "//pw_log",
-    ],
-)
-
-pw_cc_library(
-    name = "rpc_main",
-    srcs = [
-        "rpc_main.cc",
-    ],
-    deps = [
-        ":pw_unit_test",
-        ":rpc_service",
-        "//pw_hdlc:pw_rpc",
-        "//pw_log",
-        "//pw_rpc",
-        "//pw_rpc/system_server",
-    ],
-)
-
-pw_cc_test(
-    name = "framework_test",
-    srcs = ["framework_test.cc"],
-    deps = [
-        ":pw_unit_test",
-    ],
-)
-
-# TODO(hepler): Build this as a cc_binary and use it in integration tests.
-filegroup(
-    name = "test_rpc_server",
-    srcs = ["test_rpc_server.cc"],
-    # deps = [
-    #     ":pw_unit_test",
-    #     ":rpc_service",
-    #     "//pw_log",
-    #     "//pw_rpc/system_server",
-    # ],
-)
diff --git a/pw_unit_test/BUILD.gn b/pw_unit_test/BUILD.gn
index f4c113f..aa80fb9 100644
--- a/pw_unit_test/BUILD.gn
+++ b/pw_unit_test/BUILD.gn
@@ -14,28 +14,11 @@
 
 import("//build_overrides/pigweed.gni")
 
-import("$dir_pw_build/module_config.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_protobuf_compiler/proto.gni")
 import("$dir_pw_unit_test/test.gni")
 
-declare_args() {
-  # The build target that overrides the default configuration options for this
-  # module. This should point to a source set that provides defines through a
-  # public config (which may -include a file or add defines directly).
-  pw_unit_test_CONFIG = pw_build_DEFAULT_MODULE_CONFIG
-}
-
-# This pool limits the maximum number of unit tests that may run in parallel.
-# Despite the fact that this is a single GN "target", each toolchain owns its
-# own version of this pool, meaning pw_unit_test_POOL_DEPTH may be set
-# differently across targets in a single build, and build steps in one toolchain
-# will not consume pool resources of steps from another toolchain.
-pool("unit_test_pool") {
-  depth = pw_unit_test_POOL_DEPTH
-}
-
 config("default_config") {
   include_dirs = [
     "public",
@@ -43,21 +26,10 @@
   ]
 }
 
-pw_source_set("config") {
-  public = [ "public/pw_unit_test/config.h" ]
-  public_configs = [ ":default_config" ]
-  public_deps = [
-    dir_pw_polyfill,
-    pw_unit_test_CONFIG,
-  ]
-  visibility = [ ":*" ]
-}
-
 # pw_unit_test core library.
 pw_source_set("pw_unit_test") {
   public_configs = [ ":default_config" ]
   public_deps = [
-    ":config",
     dir_pw_polyfill,
     dir_pw_preprocessor,
     dir_pw_string,
@@ -141,17 +113,6 @@
   sources = [ "rpc_main.cc" ]
 }
 
-pw_executable("test_rpc_server") {
-  sources = [ "test_rpc_server.cc" ]
-  deps = [
-    ":pw_unit_test",
-    ":rpc_service",
-    "$dir_pw_rpc/system_server",
-    "$dir_pw_rpc/system_server:socket",
-    dir_pw_log,
-  ]
-}
-
 pw_proto_library("unit_test_proto") {
   sources = [ "pw_unit_test_proto/unit_test.proto" ]
 }
diff --git a/pw_unit_test/CMakeLists.txt b/pw_unit_test/CMakeLists.txt
index 5adaebe..983fe30 100644
--- a/pw_unit_test/CMakeLists.txt
+++ b/pw_unit_test/CMakeLists.txt
@@ -14,16 +14,6 @@
 
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
 
-pw_add_module_config(pw_unit_test_CONFIG)
-
-pw_add_module_library(pw_unit_test.config
-  PUBLIC_DEPS
-    ${pw_unit_test_CONFIG}
-    pw_polyfill
-  HEADERS
-    public/pw_unit_test/config.h
-)
-
 pw_add_module_library(pw_unit_test
   SOURCES
     framework.cc
@@ -31,7 +21,6 @@
     pw_polyfill
     pw_preprocessor
     pw_string
-    pw_unit_test.config
 )
 
 # pw_unit_test overrides the gtest/gtest.h header.
diff --git a/pw_unit_test/OWNERS b/pw_unit_test/OWNERS
deleted file mode 100644
index 3afb926..0000000
--- a/pw_unit_test/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-frolv@google.com
diff --git a/pw_unit_test/README.md b/pw_unit_test/README.md
index 9ce966e..713fad1 100644
--- a/pw_unit_test/README.md
+++ b/pw_unit_test/README.md
@@ -1,6 +1,6 @@
 # pw\_unit\_test: Lightweight C++ unit testing framework
 
 The pw\_unit\_test module contains the code for *Pigweed Test*, a
-[Googletest](https://github.com/google/googletest/blob/HEAD/docs/primer.md)-compatible
+[Googletest](https://github.com/google/googletest/blob/master/googletest/docs/primer.md)-compatible
 unit testing framework that runs on anything from bare-metal microcontrollers
 to large desktop operating systems.
diff --git a/pw_unit_test/docs.rst b/pw_unit_test/docs.rst
index f96c56a..5459484 100644
--- a/pw_unit_test/docs.rst
+++ b/pw_unit_test/docs.rst
@@ -1,15 +1,15 @@
 .. _module-pw_unit_test:
 
-============
+------------
 pw_unit_test
-============
+------------
 ``pw_unit_test`` unit testing library with a `Google Test`_-compatible API,
 built on top of embedded-friendly primitives.
 
-.. _Google Test: https://github.com/google/googletest/blob/HEAD/docs/primer.md
+.. _Google Test: https://github.com/google/googletest/blob/master/googletest/docs/primer.md
 
 ``pw_unit_test`` is a portable library which can run on almost any system from
-bare metal to a full-fledged desktop OS. It does this by offloading the
+from bare metal to a full-fledged desktop OS. It does this by offloading the
 responsibility of test reporting and output to the underlying system,
 communicating its results through a common interface. Unit tests can be written
 once and run under many different environments, empowering developers to write
@@ -23,35 +23,23 @@
 
   This documentation is currently incomplete.
 
-------------------
 Writing unit tests
-------------------
+==================
+
 ``pw_unit_test``'s interface is largely compatible with `Google Test`_. Refer to
 the Google Test documentation for examples of to define unit test cases.
 
 .. note::
 
-  Many of Google Test's more advanced features are not yet implemented. Missing
-  features include:
-
-  * Any GoogleMock features (e.g. :c:macro:`EXPECT_THAT`)
-  * Floating point comparison macros (e.g. :c:macro:`EXPECT_FLOAT_EQ`)
-  * Death tests (e.g. :c:macro:`EXPECT_DEATH`); ``EXPECT_DEATH_IF_SUPPORTED``
-    does nothing but silently passes
-  * Value-parameterized tests
-
-  To request a feature addition, please
+  A lot of Google Test's more advanced features are not yet implemented. To
+  request a feature addition, please
   `let us know <mailto:pigweed@googlegroups.com>`_.
 
-  See `Using upstream Googletest and Googlemock` below for information
-  about using upstream Googletest instead.
-
-------------------------
 Using the test framework
-------------------------
+========================
 
 The EventHandler interface
-==========================
+^^^^^^^^^^^^^^^^^^^^^^^^^^
 The ``EventHandler`` class in ``public/pw_unit_test/event_handler.h`` defines
 the interface through which ``pw_unit_test`` communicates the results of its
 test runs. A platform using ``pw_unit_test`` must register an event handler with
@@ -99,7 +87,7 @@
 .. _running-tests:
 
 Running tests
-=============
+^^^^^^^^^^^^^
 To run unit tests, link the tests into a single binary with the unit testing
 framework, register an event handler, and call the ``RUN_ALL_TESTS`` macro.
 
@@ -122,7 +110,7 @@
   }
 
 Test filtering
-==============
+^^^^^^^^^^^^^^
 If using C++17, filters can be set on the test framework to run only a subset of
 the registered unit tests. This is useful when many tests are bundled into a
 single application image.
@@ -134,7 +122,7 @@
   Test filtering is only supported in C++17.
 
 Build system integration
-========================
+^^^^^^^^^^^^^^^^^^^^^^^^
 ``pw_unit_test`` integrates directly into Pigweed's GN build system. To define
 simple unit tests, set the ``pw_unit_test_MAIN`` build variable to a target
 which configures the test framework as described in the :ref:`running-tests`
@@ -150,7 +138,7 @@
 
 The ``pw_unit_test`` module provides a few optional libraries to simplify setup:
 
- - ``simple_printing_event_handler``: When running tests, output test results
+ - ``simple_printing_event_handler```: When running tests, output test results
    as plain text over ``pw_sys_io``.
  - ``simple_printing_main``: Implements a ``main()`` function that simply runs
    tests using the ``simple_printing_event_handler``.
@@ -191,6 +179,7 @@
     enable_if = device_has_1m_flash
   }
 
+
 pw_test_group template
 ----------------------
 ``pw_test_group`` defines a collection of tests or other test groups. It creates
@@ -252,81 +241,6 @@
    careful when running a facade test on a system that heavily depends on the
    facade being tested.
 
-Build arguments
----------------
-
-.. option:: pw_unit_test_AUTOMATIC_RUNNER <executable>
-
-  Path to a test runner to automatically run unit tests after they are built.
-
-  If set, a ``pw_test`` target's ``<target_name>.run`` action will invoke the
-  test runner specified by this argument, passing the path to the unit test to
-  run. If this is unset, the ``pw_test`` target's ``<target_name>.run`` step
-  will do nothing.
-
-  Targets that don't support parallelized execution of tests (e.g. a on-device
-  test runner that must flash a device and run the test in serial) should
-  set pw_unit_test_POOL_DEPTH to 1.
-
-  Type: string (name of an executable on the PATH, or path to an executable)
-  Usage: toolchain-controlled only
-
-.. option:: pw_unit_test_AUTOMATIC_RUNNER_ARGS <args>
-
-  An optional list of strings to pass as args to the test runner specified
-  by pw_unit_test_AUTOMATIC_RUNNER.
-
-  Type: list of strings (args to pass to pw_unit_test_AUTOMATIC_RUNNER)
-  Usage: toolchain-controlled only
-
-.. option:: pw_unit_test_AUTOMATIC_RUNNER_TIMEOUT <timeout_seconds>
-
-  An optional timeout to apply when running the automatic runner. Timeout is
-  in seconds. Defaults to empty which means no timeout.
-
-  Type: string (number of seconds to wait before killing test runner)
-  Usage: toolchain-controlled only
-
-.. option:: pw_unit_test_PUBLIC_DEPS <dependencies>
-
-  Additional dependencies required by all unit test targets. (For example, if
-  using a different test library like Googletest.)
-
-  Type: list of strings (list of dependencies as GN paths)
-  Usage: toolchain-controlled only
-
-.. option:: pw_unit_test_MAIN <source_set>
-
-  Implementation of a main function for ``pw_test`` unit test binaries.
-
-  Type: string (GN path to a source set)
-  Usage: toolchain-controlled only
-
-.. option:: pw_unit_test_POOL_DEPTH <pool_depth>
-
-  The maximum number of unit tests that may be run concurrently for the
-  current toolchain. Setting this to 0 disables usage of a pool, allowing
-  unlimited parallelization.
-
-  Note: A single target with two toolchain configurations (e.g. release/debug)
-        will use two separate test runner pools by default. Set
-        pw_unit_test_POOL_TOOLCHAIN to the same toolchain for both targets to
-        merge the pools and force serialization.
-
-  Type: integer
-  Usage: toolchain-controlled only
-
-.. option:: pw_unit_test_POOL_TOOLCHAIN <toolchain>
-
-  The toolchain to use when referring to the pw_unit_test runner pool. When
-  this is disabled, the current toolchain is used. This means that every
-  toolchain will use its own pool definition. If two toolchains should share
-  the same pool, this argument should be by one of the toolchains to the GN
-  path of the other toolchain.
-
-  Type: string (GN path to a toolchain)
-  Usage: toolchain-controlled only
-
 RPC service
 ===========
 ``pw_unit_test`` provides an RPC service which runs unit tests on demand and
@@ -371,39 +285,6 @@
   run_tests(client.rpcs())
 
 pw_unit_test.rpc
-----------------
+^^^^^^^^^^^^^^^^
 .. automodule:: pw_unit_test.rpc
   :members: EventHandler, run_tests
-
-Module Configuration Options
-============================
-The following configurations can be adjusted via compile-time configuration of
-this module.
-
-.. c:macro:: PW_UNIT_TEST_CONFIG_EVENT_BUFFER_SIZE
-
-  The size of the event buffer that the UnitTestService contains.
-  This buffer is used to encode events.  By default this is set to
-  128 bytes.
-
-.. c:macro:: PW_UNIT_TEST_CONFIG_MEMORY_POOL_SIZE
-
-  The size of the memory pool to use for test fixture instances. By default this
-  is set to 16K.
-
-Using upstream Googletest and Googlemock
-========================================
-
-If you want to use the full upstream Googletest/Googlemock, you must do the
-following:
-
-* Set the GN var ``dir_pw_third_party_googletest`` to the location of the
-  googletest source. You can use ``pw package install googletest`` to fetch the
-  source if desired.
-* Set the GN var ``pw_unit_test_MAIN = "//third_party/googletest:gmock_main"``
-* Set the GN var ``pw_unit_test_PUBLIC_DEPS = [ "//third_party/googletest" ]``
-
-.. note::
-
-  Not all unit tests build properly with upstream Googletest yet. This is a
-  work in progress.
diff --git a/pw_unit_test/facade_test.gni b/pw_unit_test/facade_test.gni
index 04f7bfd..4912888 100644
--- a/pw_unit_test/facade_test.gni
+++ b/pw_unit_test/facade_test.gni
@@ -96,7 +96,7 @@
       }
     }
   } else {
-    # No-op target for non-pigweed toolchains.
+    # Dummy target for non-pigweed toolchains.
     not_needed(invoker, "*")
     pw_test_group(target_name) {
       enable_if = false
diff --git a/pw_unit_test/framework.cc b/pw_unit_test/framework.cc
index 46d913b..70b36d3 100644
--- a/pw_unit_test/framework.cc
+++ b/pw_unit_test/framework.cc
@@ -33,7 +33,7 @@
 // populated using static initialization.
 TestInfo* Framework::tests_ = nullptr;
 
-void Framework::RegisterTest(TestInfo* new_test) const {
+void Framework::RegisterTest(TestInfo* new_test) {
   // If the test list is empty, set new_test as the first test.
   if (tests_ == nullptr) {
     tests_ = new_test;
@@ -92,9 +92,6 @@
     case TestResult::kFailure:
       run_tests_summary_.failed_tests++;
       break;
-    case TestResult::kSkipped:
-      run_tests_summary_.skipped_tests++;
-      break;
   }
 
   if (event_handler_ != nullptr) {
@@ -104,18 +101,10 @@
   current_test_ = nullptr;
 }
 
-void Framework::CurrentTestSkip(int line) {
-  if (current_result_ == TestResult::kSuccess) {
-    current_result_ = TestResult::kSkipped;
-  }
-  return CurrentTestExpectSimple(
-      "(test skipped)", "(test skipped)", line, true);
-}
-
-void Framework::CurrentTestExpectSimple(const char* expression,
-                                        const char* evaluated_expression,
-                                        int line,
-                                        bool success) {
+void Framework::ExpectationResult(const char* expression,
+                                  const char* evaluated_expression,
+                                  int line,
+                                  bool success) {
   if (!success) {
     current_result_ = TestResult::kFailure;
     exit_status_ = 1;
@@ -135,7 +124,7 @@
   event_handler_->TestCaseExpect(current_test_->test_case(), expectation);
 }
 
-bool Framework::ShouldRunTest(const TestInfo& test_info) const {
+bool Framework::ShouldRunTest(const TestInfo& test_info) {
 #if PW_CXX_STANDARD_IS_SUPPORTED(17)
   // Test suite filtering is only supported if using C++17.
   if (!test_suites_to_run_.empty()) {
diff --git a/pw_unit_test/framework_test.cc b/pw_unit_test/framework_test.cc
index d287859..1c0a1ce 100644
--- a/pw_unit_test/framework_test.cc
+++ b/pw_unit_test/framework_test.cc
@@ -78,22 +78,6 @@
   }
 }
 
-TEST(PigweedTest, SkipMacro) {
-  GTEST_SKIP();
-  // This code should not run.
-  EXPECT_TRUE(false);
-}
-
-class SkipOnSetUpTest : public ::testing::Test {
- public:
-  void SetUp() override { GTEST_SKIP(); }
-};
-
-TEST_F(SkipOnSetUpTest, FailTest) {
-  // This code should not run because the test was skipped in SetUp().
-  EXPECT_TRUE(false);
-}
-
 class NonCopyable {
  public:
   NonCopyable(int value) : value_(value) {}
diff --git a/pw_unit_test/logging_event_handler.cc b/pw_unit_test/logging_event_handler.cc
index 7e80c32..e1ad080 100644
--- a/pw_unit_test/logging_event_handler.cc
+++ b/pw_unit_test/logging_event_handler.cc
@@ -31,9 +31,6 @@
     const RunTestsSummary& run_tests_summary) {
   PW_LOG_INFO("[==========] Done running all tests.");
   PW_LOG_INFO("[  PASSED  ] %d test(s).", run_tests_summary.passed_tests);
-  if (run_tests_summary.skipped_tests) {
-    PW_LOG_WARN("[  SKIPPED ] %d test(s).", run_tests_summary.skipped_tests);
-  }
   if (run_tests_summary.failed_tests) {
     PW_LOG_ERROR("[  FAILED  ] %d test(s).", run_tests_summary.failed_tests);
   }
@@ -55,10 +52,6 @@
       PW_LOG_ERROR(
           "[  FAILED  ] %s.%s", test_case.suite_name, test_case.test_name);
       break;
-    case TestResult::kSkipped:
-      PW_LOG_WARN(
-          "[  SKIPPED ] %s.%s", test_case.suite_name, test_case.test_name);
-      break;
   }
 }
 
@@ -71,14 +64,17 @@
   const char* result = expectation.success ? "Success" : "Failure";
   uint32_t level = expectation.success ? PW_LOG_LEVEL_INFO : PW_LOG_LEVEL_ERROR;
   PW_LOG(level,
-         PW_LOG_FLAGS,
+         PW_LOG_DEFAULT_FLAGS,
          "%s:%d: %s",
          test_case.file_name,
          expectation.line_number,
          result);
-  PW_LOG(level, PW_LOG_FLAGS, "      Expected: %s", expectation.expression);
   PW_LOG(level,
-         PW_LOG_FLAGS,
+         PW_LOG_DEFAULT_FLAGS,
+         "      Expected: %s",
+         expectation.expression);
+  PW_LOG(level,
+         PW_LOG_DEFAULT_FLAGS,
          "        Actual: %s",
          expectation.evaluated_expression);
 }
diff --git a/pw_unit_test/public/pw_unit_test/config.h b/pw_unit_test/public/pw_unit_test/config.h
deleted file mode 100644
index 155d0f9..0000000
--- a/pw_unit_test/public/pw_unit_test/config.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// Configuration macros for the unit test module.
-#pragma once
-
-#include <cstddef>
-
-#include "pw_polyfill/language_feature_macros.h"
-
-#ifndef PW_UNIT_TEST_CONFIG_EVENT_BUFFER_SIZE
-#define PW_UNIT_TEST_CONFIG_EVENT_BUFFER_SIZE 128
-#endif  // PW_UNIT_TEST_CONFIG_EVENT_BUFFER_SIZE
-
-#ifndef PW_UNIT_TEST_CONFIG_MEMORY_POOL_SIZE
-#define PW_UNIT_TEST_CONFIG_MEMORY_POOL_SIZE 16384
-#endif  // PW_UNIT_TEST_CONFIG_MEMORY_POOL_SIZE
-
-namespace pw {
-namespace unit_test {
-namespace config {
-
-PW_INLINE_VARIABLE constexpr size_t kEventBufferSize =
-    PW_UNIT_TEST_CONFIG_EVENT_BUFFER_SIZE;
-
-PW_INLINE_VARIABLE constexpr size_t kMemoryPoolSize =
-    PW_UNIT_TEST_CONFIG_MEMORY_POOL_SIZE;
-
-}  // namespace config
-}  // namespace unit_test
-}  // namespace pw
diff --git a/pw_unit_test/public/pw_unit_test/event_handler.h b/pw_unit_test/public/pw_unit_test/event_handler.h
index ea029ac..3a5a710 100644
--- a/pw_unit_test/public/pw_unit_test/event_handler.h
+++ b/pw_unit_test/public/pw_unit_test/event_handler.h
@@ -51,8 +51,6 @@
 enum class TestResult {
   kSuccess = 0,
   kFailure = 1,
-  // Test skipped at runtime. This is neither a success nor a failure.
-  kSkipped = 2,
 };
 
 struct TestCase {
diff --git a/pw_unit_test/public/pw_unit_test/framework.h b/pw_unit_test/public/pw_unit_test/framework.h
index ab35948..959cef8 100644
--- a/pw_unit_test/public/pw_unit_test/framework.h
+++ b/pw_unit_test/public/pw_unit_test/framework.h
@@ -24,8 +24,8 @@
 #include <span>
 
 #include "pw_polyfill/standard.h"
+#include "pw_preprocessor/concat.h"
 #include "pw_preprocessor/util.h"
-#include "pw_unit_test/config.h"
 #include "pw_unit_test/event_handler.h"
 
 #if PW_CXX_STANDARD_IS_SUPPORTED(17)
@@ -35,7 +35,7 @@
 #endif  // PW_CXX_STANDARD_IS_SUPPORTED(17)
 
 #define PW_TEST(test_suite_name, test_name) \
-  _PW_TEST(test_suite_name, test_name, ::pw::unit_test::internal::Test)
+  _PW_TEST(test_suite_name, test_name, ::pw::unit_test::Test)
 
 // TEST() is a pretty generic macro name which could conflict with other code.
 // If GTEST_DONT_DEFINE_TEST is set, don't alias PW_TEST to TEST.
@@ -46,41 +46,35 @@
 #define TEST_F(test_fixture, test_name) \
   _PW_TEST(test_fixture, test_name, test_fixture)
 
-#define EXPECT_TRUE(expr) static_cast<void>(_PW_TEST_BOOL(expr, true))
-#define EXPECT_FALSE(expr) static_cast<void>(_PW_TEST_BOOL(expr, false))
-#define EXPECT_EQ(lhs, rhs) static_cast<void>(_PW_TEST_OP(lhs, rhs, ==))
-#define EXPECT_NE(lhs, rhs) static_cast<void>(_PW_TEST_OP(lhs, rhs, !=))
-#define EXPECT_GT(lhs, rhs) static_cast<void>(_PW_TEST_OP(lhs, rhs, >))
-#define EXPECT_GE(lhs, rhs) static_cast<void>(_PW_TEST_OP(lhs, rhs, >=))
-#define EXPECT_LT(lhs, rhs) static_cast<void>(_PW_TEST_OP(lhs, rhs, <))
-#define EXPECT_LE(lhs, rhs) static_cast<void>(_PW_TEST_OP(lhs, rhs, <=))
-#define EXPECT_STREQ(lhs, rhs) static_cast<void>(_PW_TEST_C_STR(lhs, rhs, ==))
-#define EXPECT_STRNE(lhs, rhs) static_cast<void>(_PW_TEST_C_STR(lhs, rhs, !=))
+#define EXPECT_TRUE(expr) _PW_EXPECT_BOOL(expr, true)
+#define EXPECT_FALSE(expr) _PW_EXPECT_BOOL(expr, false)
+#define EXPECT_EQ(lhs, rhs) _PW_TEST_OP(_PW_TEST_EXPECT, lhs, rhs, ==)
+#define EXPECT_NE(lhs, rhs) _PW_TEST_OP(_PW_TEST_EXPECT, lhs, rhs, !=)
+#define EXPECT_GT(lhs, rhs) _PW_TEST_OP(_PW_TEST_EXPECT, lhs, rhs, >)
+#define EXPECT_GE(lhs, rhs) _PW_TEST_OP(_PW_TEST_EXPECT, lhs, rhs, >=)
+#define EXPECT_LT(lhs, rhs) _PW_TEST_OP(_PW_TEST_EXPECT, lhs, rhs, <)
+#define EXPECT_LE(lhs, rhs) _PW_TEST_OP(_PW_TEST_EXPECT, lhs, rhs, <=)
+#define EXPECT_STREQ(lhs, rhs) _PW_TEST_STREQ(_PW_TEST_EXPECT, lhs, rhs)
+#define EXPECT_STRNE(lhs, rhs) _PW_TEST_STRNE(_PW_TEST_EXPECT, lhs, rhs)
 
-#define ASSERT_TRUE(expr) _PW_TEST_ASSERT(_PW_TEST_BOOL(expr, true))
-#define ASSERT_FALSE(expr) _PW_TEST_ASSERT(_PW_TEST_BOOL(expr, false))
-#define ASSERT_EQ(lhs, rhs) _PW_TEST_ASSERT(_PW_TEST_OP(lhs, rhs, ==))
-#define ASSERT_NE(lhs, rhs) _PW_TEST_ASSERT(_PW_TEST_OP(lhs, rhs, !=))
-#define ASSERT_GT(lhs, rhs) _PW_TEST_ASSERT(_PW_TEST_OP(lhs, rhs, >))
-#define ASSERT_GE(lhs, rhs) _PW_TEST_ASSERT(_PW_TEST_OP(lhs, rhs, >=))
-#define ASSERT_LT(lhs, rhs) _PW_TEST_ASSERT(_PW_TEST_OP(lhs, rhs, <))
-#define ASSERT_LE(lhs, rhs) _PW_TEST_ASSERT(_PW_TEST_OP(lhs, rhs, <=))
-#define ASSERT_STREQ(lhs, rhs) _PW_TEST_ASSERT(_PW_TEST_C_STR(lhs, rhs, ==))
-#define ASSERT_STRNE(lhs, rhs) _PW_TEST_ASSERT(_PW_TEST_C_STR(lhs, rhs, !=))
+#define ASSERT_TRUE(expr) _PW_ASSERT_BOOL(expr, true)
+#define ASSERT_FALSE(expr) _PW_ASSERT_BOOL(expr, false)
+#define ASSERT_EQ(lhs, rhs) _PW_TEST_OP(_PW_TEST_ASSERT, lhs, rhs, ==)
+#define ASSERT_NE(lhs, rhs) _PW_TEST_OP(_PW_TEST_ASSERT, lhs, rhs, !=)
+#define ASSERT_GT(lhs, rhs) _PW_TEST_OP(_PW_TEST_ASSERT, lhs, rhs, >)
+#define ASSERT_GE(lhs, rhs) _PW_TEST_OP(_PW_TEST_ASSERT, lhs, rhs, >=)
+#define ASSERT_LT(lhs, rhs) _PW_TEST_OP(_PW_TEST_ASSERT, lhs, rhs, <)
+#define ASSERT_LE(lhs, rhs) _PW_TEST_OP(_PW_TEST_ASSERT, lhs, rhs, <=)
+#define ASSERT_STREQ(lhs, rhs) _PW_TEST_STREQ(_PW_TEST_ASSERT, lhs, rhs)
+#define ASSERT_STRNE(lhs, rhs) _PW_TEST_STRNE(_PW_TEST_ASSERT, lhs, rhs)
 
 // Generates a non-fatal failure with a generic message.
-#define ADD_FAILURE()                                                  \
-  ::pw::unit_test::internal::Framework::Get().CurrentTestExpectSimple( \
-      "(line is not executed)", "(line was executed)", __LINE__, false)
+#define ADD_FAILURE() \
+  _PW_TEST_MESSAGE("(line is not executed)", "(line was executed)", false)
 
 // Generates a fatal failure with a generic message.
 #define GTEST_FAIL() return ADD_FAILURE()
 
-// Skips test at runtime, which is neither successful nor failed. Skip aborts
-// current function.
-#define GTEST_SKIP() \
-  return ::pw::unit_test::internal::Framework::Get().CurrentTestSkip(__LINE__)
-
 // Define either macro to 1 to omit the definition of FAIL(), which is a
 // generic name and clashes with some other libraries.
 #if !(defined(GTEST_DONT_DEFINE_FAIL) && GTEST_DONT_DEFINE_FAIL)
@@ -88,9 +82,7 @@
 #endif  // !GTEST_DONT_DEFINE_FAIL
 
 // Generates a success with a generic message.
-#define GTEST_SUCCEED()                                                \
-  ::pw::unit_test::internal::Framework::Get().CurrentTestExpectSimple( \
-      "(success)", "(success)", __LINE__, true)
+#define GTEST_SUCCEED() _PW_TEST_MESSAGE("(success)", "(success)", true)
 
 // Define either macro to 1 to omit the definition of SUCCEED(), which
 // is a generic name and clashes with some other libraries.
@@ -115,19 +107,6 @@
 #define RUN_ALL_TESTS() \
   ::pw::unit_test::internal::Framework::Get().RunAllTests()
 
-// Death tests are not supported. The *_DEATH_IF_SUPPORTED macros do nothing.
-#define GTEST_HAS_DEATH_TEST 0
-
-#define EXPECT_DEATH_IF_SUPPORTED(statement, regex) \
-  if (0) {                                          \
-    static_cast<void>(statement);                   \
-    static_cast<void>(regex);                       \
-  }                                                 \
-  static_assert(true, "Macros must be termianted with a semicolon")
-
-#define ASSERT_DEATH_IF_SUPPORTED(statement, regex) \
-  EXPECT_DEATH_IF_SUPPORTED(statement, regex)
-
 namespace pw {
 
 #if PW_CXX_STANDARD_IS_SUPPORTED(17)
@@ -163,16 +142,12 @@
 #endif  // PW_CXX_STANDARD_IS_SUPPORTED(17)
 
 namespace unit_test {
-namespace internal {
 
 class Test;
-class TestInfo;
 
-// Used to tag arguments to EXPECT_STREQ/EXPECT_STRNE so they are treated like C
-// strings rather than pointers.
-struct CStringArg {
-  const char* const c_str;
-};
+namespace internal {
+
+class TestInfo;
 
 // Singleton test framework class responsible for managing and running test
 // cases. This implementation is internal to Pigweed test; free functions
@@ -194,7 +169,7 @@
 
   // Registers a single test case with the framework. The framework owns the
   // registered unit test. Called during static initialization.
-  void RegisterTest(TestInfo* test) const;
+  void RegisterTest(TestInfo* test);
 
   // Sets the handler to which the framework dispatches test events. During a
   // test run, the framework owns the event handler.
@@ -216,10 +191,7 @@
   }
 #endif  // PW_CXX_STANDARD_IS_SUPPORTED(17)
 
-  bool ShouldRunTest(const TestInfo& test_info) const;
-
-  // Whether the current test is skipped.
-  bool IsSkipped() const { return current_result_ == TestResult::kSkipped; }
+  bool ShouldRunTest(const TestInfo& test_info);
 
   // Constructs an instance of a unit test class and runs the test.
   //
@@ -262,59 +234,40 @@
   bool CurrentTestExpect(Expectation expectation,
                          const Lhs& lhs,
                          const Rhs& rhs,
-                         [[maybe_unused]] const char* expectation_string,
+                         const char* expectation_string,
                          const char* expression,
                          int line) {
     // Size of the buffer into which to write the string with the evaluated
     // version of the arguments. This buffer is allocated on the unit test's
     // stack, so it shouldn't be too large.
     // TODO(hepler): Make this configurable.
-    [[maybe_unused]] constexpr size_t kExpectationBufferSizeBytes = 128;
+    constexpr size_t kExpectationBufferSizeBytes = 128;
 
-    const bool success = expectation(lhs, rhs);
-    CurrentTestExpectSimple(
-        expression,
+    bool result = expectation(lhs, rhs);
+    ExpectationResult(expression,
 #if PW_CXX_STANDARD_IS_SUPPORTED(17)
-        MakeString<kExpectationBufferSizeBytes>(ConvertForPrint(lhs),
-                                                ' ',
-                                                expectation_string,
-                                                ' ',
-                                                ConvertForPrint(rhs))
-            .c_str(),
+                      MakeString<kExpectationBufferSizeBytes>(
+                          lhs, ' ', expectation_string, ' ', rhs)
+                          .c_str(),
 #else
-        "(evaluation requires C++17)",
+                      "(evaluation requires C++17)",
 #endif  // PW_CXX_STANDARD_IS_SUPPORTED(17)
-        line,
-        success);
-    return success;
-  }
+                      line,
+                      result);
 
-  // Skips the current test and dispatches an event for it.
-  void CurrentTestSkip(int line);
+    static_cast<void>(expectation_string);
+    static_cast<void>(kExpectationBufferSizeBytes);
+
+    return result;
+  }
 
   // Dispatches an event indicating the result of an expectation.
-  void CurrentTestExpectSimple(const char* expression,
-                               const char* evaluated_expression,
-                               int line,
-                               bool success);
+  void ExpectationResult(const char* expression,
+                         const char* evaluated_expression,
+                         int line,
+                         bool success);
 
  private:
-  // Convert char* to void* so that they are printed as pointers instead of
-  // strings in EXPECT_EQ and other macros. EXPECT_STREQ wraps its pointers in a
-  // CStringArg so its pointers are treated like C strings.
-  static constexpr const void* ConvertForPrint(const char* str) { return str; }
-
-  static constexpr const void* ConvertForPrint(char* str) { return str; }
-
-  static constexpr const char* ConvertForPrint(CStringArg value) {
-    return value.c_str;
-  }
-
-  template <typename T>
-  static constexpr T ConvertForPrint(T&& value) {
-    return std::forward<T>(value);
-  }
-
   // Sets current_test_ and dispatches an event indicating that a test started.
   void StartTest(const TestInfo& test);
 
@@ -331,7 +284,7 @@
   // The current test case which is running.
   const TestInfo* current_test_;
 
-  // Overall result of the current test case (pass/fail/skip).
+  // Overall result of the current test case (pass/fail).
   TestResult current_result_;
 
   // Overall result of the ongoing test run, which covers multiple tests.
@@ -345,11 +298,12 @@
 
 #if PW_CXX_STANDARD_IS_SUPPORTED(17)
   std::span<std::string_view> test_suites_to_run_;
-#else
-  std::span<const char*> test_suites_to_run_;  // Always empty in C++14.
 #endif  // PW_CXX_STANDARD_IS_SUPPORTED(17)
 
-  std::aligned_storage_t<config::kMemoryPoolSize, alignof(std::max_align_t)>
+  // Memory region in which to construct test case classes as they are run.
+  // TODO(frolv): Make the memory pool size configurable.
+  static constexpr size_t kTestMemoryPoolSizeBytes = 16384;
+  std::aligned_storage_t<kTestMemoryPoolSizeBytes, alignof(std::max_align_t)>
       memory_pool_;
 };
 
@@ -393,6 +347,8 @@
   TestInfo* next_ = nullptr;
 };
 
+}  // namespace internal
+
 // Base class for all test cases or custom test fixtures.
 // Every unit test created using the TEST or TEST_F macro defines a class that
 // inherits from this (or a subclass of this).
@@ -416,10 +372,7 @@
   // Runs the unit test.
   void PigweedTestRun() {
     SetUp();
-    // TODO(deymo): Skip the test body if there's a fatal error in SetUp().
-    if (!Framework::Get().IsSkipped()) {
-      PigweedTestBody();
-    }
+    PigweedTestBody();
     TearDown();
   }
 
@@ -443,25 +396,6 @@
   virtual void PigweedTestBody() = 0;
 };
 
-// Checks that a test suite name is valid.
-constexpr bool HasNoUnderscores(const char* suite) {
-  const char* disabled_prefix = "DISABLED_";
-
-  for (; *suite != '\0'; ++suite) {
-    if (*suite == *disabled_prefix) {
-      disabled_prefix += 1;
-    } else {
-      disabled_prefix = "";
-      if (*suite == '_') {
-        return false;
-      }
-    }
-  }
-  return true;
-}
-
-}  // namespace internal
-
 #if PW_CXX_STANDARD_IS_SUPPORTED(17)
 inline void SetTestSuitesToRun(std::span<std::string_view> test_suites) {
   internal::Framework::Get().SetTestSuitesToRun(test_suites);
@@ -471,43 +405,63 @@
 }  // namespace unit_test
 }  // namespace pw
 
-#define _PW_TEST(test_suite_name, test_name, parent_class)                     \
-  static_assert(sizeof(#test_suite_name) > 1,                                  \
-                "The test suite name must not be empty");                      \
-  static_assert(::pw::unit_test::internal::HasNoUnderscores(#test_suite_name), \
-                "The test suite name (" #test_suite_name                       \
-                ") cannot contain underscores");                               \
-  static_assert(sizeof(#test_name) > 1, "The test name must not be empty");    \
-                                                                               \
-  _PW_TEST_CLASS(test_suite_name,                                              \
-                 test_name,                                                    \
-                 test_suite_name##_##test_name##_Test,                         \
-                 parent_class)
+#define _PW_TEST_CLASS_NAME(test_suite_name, test_name) \
+  PW_CONCAT(test_suite_name, _, test_name, _Test)
 
-#define _PW_TEST_CLASS(suite, name, class_name, parent_class)              \
-  class class_name final : public parent_class {                           \
-   private:                                                                \
-    void PigweedTestBody() override;                                       \
-                                                                           \
-    static ::pw::unit_test::internal::TestInfo test_info_;                 \
-  };                                                                       \
-                                                                           \
-  ::pw::unit_test::internal::TestInfo class_name::test_info_(              \
-      #suite,                                                              \
-      #name,                                                               \
-      __FILE__,                                                            \
-      ::pw::unit_test::internal::Framework::CreateAndRunTest<class_name>); \
-                                                                           \
-  void class_name::PigweedTestBody()
+#define _PW_TEST(test_suite_name, test_name, parent_class)              \
+  static_assert(sizeof(#test_suite_name) > 1,                           \
+                "test_suite_name must not be empty");                   \
+  static_assert(sizeof(#test_name) > 1, "test_name must not be empty"); \
+                                                                        \
+  class _PW_TEST_CLASS_NAME(test_suite_name, test_name) final           \
+      : public parent_class {                                           \
+   private:                                                             \
+    void PigweedTestBody() override;                                    \
+                                                                        \
+    static ::pw::unit_test::internal::TestInfo test_info_;              \
+  };                                                                    \
+                                                                        \
+  ::pw::unit_test::internal::TestInfo                                   \
+      _PW_TEST_CLASS_NAME(test_suite_name, test_name)::test_info_(      \
+          #test_suite_name,                                             \
+          #test_name,                                                   \
+          __FILE__,                                                     \
+          ::pw::unit_test::internal::Framework::CreateAndRunTest<       \
+              _PW_TEST_CLASS_NAME(test_suite_name, test_name)>);        \
+                                                                        \
+  void _PW_TEST_CLASS_NAME(test_suite_name, test_name)::PigweedTestBody()
 
-#define _PW_TEST_ASSERT(expectation) \
-  do {                               \
-    if (!(expectation)) {            \
-      return;                        \
-    }                                \
+#define _PW_TEST_EXPECT(lhs, rhs, expectation, expectation_string) \
+  ::pw::unit_test::internal::Framework::Get().CurrentTestExpect(   \
+      expectation,                                                 \
+      (lhs),                                                       \
+      (rhs),                                                       \
+      expectation_string,                                          \
+      #lhs " " expectation_string " " #rhs,                        \
+      __LINE__)
+
+#define _PW_TEST_ASSERT(lhs, rhs, expectation, expectation_string)     \
+  do {                                                                 \
+    if (!_PW_TEST_EXPECT(lhs, rhs, expectation, expectation_string)) { \
+      return;                                                          \
+    }                                                                  \
   } while (0)
 
-#define _PW_TEST_BOOL(expr, value)                               \
+#define _PW_TEST_MESSAGE(expected, actual, success)              \
+  ::pw::unit_test::internal::Framework::Get().ExpectationResult( \
+      expected, actual, __LINE__, success)
+
+#define _PW_TEST_OP(expect_or_assert, lhs, rhs, op)  \
+  expect_or_assert(                                  \
+      lhs,                                           \
+      rhs,                                           \
+      [](const auto& _pw_lhs, const auto& _pw_rhs) { \
+        return _pw_lhs op _pw_rhs;                   \
+      },                                             \
+      #op)
+
+// Implement boolean expectations in a C++11-compatible way.
+#define _PW_EXPECT_BOOL(expr, value)                             \
   ::pw::unit_test::internal::Framework::Get().CurrentTestExpect( \
       [](bool lhs, bool rhs) { return lhs == rhs; },             \
       static_cast<bool>(expr),                                   \
@@ -516,31 +470,32 @@
       #expr " is " #value,                                       \
       __LINE__)
 
-#define _PW_TEST_OP(lhs, rhs, op)                                \
-  ::pw::unit_test::internal::Framework::Get().CurrentTestExpect( \
-      [](const auto& _pw_lhs, const auto& _pw_rhs) {             \
-        return _pw_lhs op _pw_rhs;                               \
-      },                                                         \
-      (lhs),                                                     \
-      (rhs),                                                     \
-      #op,                                                       \
-      #lhs " " #op " " #rhs,                                     \
-      __LINE__)
+#define _PW_ASSERT_BOOL(expr, value)     \
+  do {                                   \
+    if (!_PW_EXPECT_BOOL(expr, value)) { \
+      return;                            \
+    }                                    \
+  } while (0)
 
-#define _PW_TEST_C_STR(lhs, rhs, op)                             \
-  ::pw::unit_test::internal::Framework::Get().CurrentTestExpect( \
-      [](const auto& _pw_lhs, const auto& _pw_rhs) {             \
-        return std::strcmp(_pw_lhs.c_str, _pw_rhs.c_str) op 0;   \
-      },                                                         \
-      ::pw::unit_test::internal::CStringArg{lhs},                \
-      ::pw::unit_test::internal::CStringArg{rhs},                \
-      #op,                                                       \
-      #lhs " " #op " " #rhs,                                     \
-      __LINE__)
+#define _PW_TEST_STREQ(expect_or_assert, lhs, rhs)   \
+  expect_or_assert(                                  \
+      lhs,                                           \
+      rhs,                                           \
+      [](const auto& _pw_lhs, const auto& _pw_rhs) { \
+        return std::strcmp(_pw_lhs, _pw_rhs) == 0;   \
+      },                                             \
+      "equals")
+
+#define _PW_TEST_STRNE(expect_or_assert, lhs, rhs)   \
+  expect_or_assert(                                  \
+      lhs,                                           \
+      rhs,                                           \
+      [](const auto& _pw_lhs, const auto& _pw_rhs) { \
+        return std::strcmp(_pw_lhs, _pw_rhs) != 0;   \
+      },                                             \
+      "does not equal")
 
 // Alias Test as ::testing::Test for Googletest compatibility.
 namespace testing {
-
-using Test = ::pw::unit_test::internal::Test;
-
+using Test = ::pw::unit_test::Test;
 }  // namespace testing
diff --git a/pw_unit_test/public/pw_unit_test/unit_test_service.h b/pw_unit_test/public/pw_unit_test/unit_test_service.h
index a7269ee..f5c7a79 100644
--- a/pw_unit_test/public/pw_unit_test/unit_test_service.h
+++ b/pw_unit_test/public/pw_unit_test/unit_test_service.h
@@ -14,19 +14,17 @@
 #pragma once
 
 #include "pw_log/log.h"
-#include "pw_unit_test/config.h"
 #include "pw_unit_test/internal/rpc_event_handler.h"
 #include "pw_unit_test_proto/unit_test.pwpb.h"
 #include "pw_unit_test_proto/unit_test.raw_rpc.pb.h"
 
 namespace pw::unit_test {
 
-class UnitTestService final
-    : public pw_rpc::raw::UnitTest::Service<UnitTestService> {
+class UnitTestService final : public generated::UnitTest<UnitTestService> {
  public:
   UnitTestService() : handler_(*this), verbose_(false) {}
 
-  void Run(ConstByteSpan request, RawServerWriter& writer);
+  void Run(ServerContext& ctx, ConstByteSpan request, RawServerWriter& writer);
 
  private:
   friend class internal::RpcEventHandler;
@@ -36,11 +34,11 @@
   // migrated to it.
   template <typename WriteFunction>
   void WriteEvent(WriteFunction event_writer) {
-    Event::MemoryEncoder event(encoding_buffer_);
+    protobuf::NestedEncoder<2, 3> encoder(writer_.PayloadBuffer());
+    Event::Encoder event(&encoder);
     event_writer(event);
-    if (event.status().ok()) {
-      writer_.Write(event)
-          .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    if (Result<ConstByteSpan> result = encoder.Encode(); result.ok()) {
+      writer_.Write(result.value());
     }
   }
 
@@ -54,7 +52,6 @@
   internal::RpcEventHandler handler_;
   RawServerWriter writer_;
   bool verbose_;
-  std::array<std::byte, config::kEventBufferSize> encoding_buffer_ = {};
 };
 
 }  // namespace pw::unit_test
diff --git a/pw_unit_test/py/BUILD.gn b/pw_unit_test/py/BUILD.gn
index 57bf6a2..468af15 100644
--- a/pw_unit_test/py/BUILD.gn
+++ b/pw_unit_test/py/BUILD.gn
@@ -15,14 +15,9 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_build/python.gni")
-import("$dir_pw_rpc/internal/integration_test_ports.gni")
 
 pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
+  setup = [ "setup.py" ]
   sources = [
     "pw_unit_test/__init__.py",
     "pw_unit_test/rpc.py",
@@ -35,24 +30,3 @@
   ]
   pylintrc = "$dir_pigweed/.pylintrc"
 }
-
-pw_python_script("rpc_service_test") {
-  sources = [ "rpc_service_test.py" ]
-  python_deps = [
-    ":py",
-    "$dir_pw_hdlc/py",
-    "$dir_pw_rpc/py",
-    "$dir_pw_status/py",
-  ]
-  pylintrc = "$dir_pigweed/.pylintrc"
-
-  action = {
-    args = [
-      "--port=$pw_unit_test_RPC_SERVICE_TEST_PORT",
-      "--test-server-command",
-      "<TARGET_FILE(..:test_rpc_server)>",
-    ]
-    deps = [ "..:test_rpc_server" ]
-    stamp = true
-  }
-}
diff --git a/pw_unit_test/py/pw_unit_test/__init__.py b/pw_unit_test/py/pw_unit_test/__init__.py
index b1a7814..e69de29 100644
--- a/pw_unit_test/py/pw_unit_test/__init__.py
+++ b/pw_unit_test/py/pw_unit_test/__init__.py
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Utilities for running unit tests over Pigweed RPC."""
-
-from pw_unit_test.rpc import run_tests, EventHandler, TestCase
diff --git a/pw_unit_test/py/pw_unit_test/rpc.py b/pw_unit_test/py/pw_unit_test/rpc.py
index 5a9e9f9..9fc8280 100644
--- a/pw_unit_test/py/pw_unit_test/rpc.py
+++ b/pw_unit_test/py/pw_unit_test/rpc.py
@@ -22,7 +22,7 @@
 from pw_rpc.callback_client import OptionalTimeout, UseDefault
 from pw_unit_test_proto import unit_test_pb2
 
-_LOG = logging.getLogger(__package__)
+_LOG = logging.getLogger(__name__)
 
 
 @dataclass(frozen=True)
@@ -38,11 +38,6 @@
         return f'TestCase({str(self)})'
 
 
-def _test_case(raw_test_case: unit_test_pb2.TestCaseDescriptor) -> TestCase:
-    return TestCase(raw_test_case.suite_name, raw_test_case.test_name,
-                    raw_test_case.file_name)
-
-
 @dataclass(frozen=True)
 class TestExpectation:
     expression: str
@@ -128,21 +123,15 @@
     True if all tests pass.
     """
     unit_test_service = rpcs.pw.unit_test.UnitTest  # type: ignore[attr-defined]
-    request = unit_test_service.Run.request(
-        report_passed_expectations=report_passed_expectations,
-        test_suite=test_suites)
-    call = unit_test_service.Run.invoke(request, timeout_s=timeout_s)
-    test_responses = iter(call)
+
+    test_responses = iter(
+        unit_test_service.Run(
+            report_passed_expectations=report_passed_expectations,
+            test_suite=test_suites,
+            pw_rpc_timeout_s=timeout_s))
 
     # Read the first response, which must be a test_run_start message.
-    try:
-        first_response = next(test_responses)
-    except StopIteration:
-        _LOG.error(
-            'The "test_run_start" message was dropped! UnitTest.Run '
-            'concluded with %s.', call.status)
-        raise
-
+    first_response = next(test_responses)
     if not first_response.HasField('test_run_start'):
         raise ValueError(
             'Expected a "test_run_start" response from pw.unit_test.Run, '
@@ -157,7 +146,9 @@
     for response in test_responses:
         if response.HasField('test_case_start'):
             raw_test_case = response.test_case_start
-            current_test_case = _test_case(raw_test_case)
+            current_test_case = TestCase(raw_test_case.suite_name,
+                                         raw_test_case.test_name,
+                                         raw_test_case.file_name)
 
         for event_handler in event_handlers:
             if response.HasField('test_run_start'):
@@ -173,8 +164,7 @@
                 event_handler.test_case_end(current_test_case,
                                             response.test_case_end)
             elif response.HasField('test_case_disabled'):
-                event_handler.test_case_disabled(
-                    _test_case(response.test_case_disabled))
+                event_handler.test_case_disabled(current_test_case)
             elif response.HasField('test_case_expectation'):
                 raw_expectation = response.test_case_expectation
                 expectation = TestExpectation(
diff --git a/pw_unit_test/py/pw_unit_test/test_runner.py b/pw_unit_test/py/pw_unit_test/test_runner.py
index 420c9a8..0d395ae 100644
--- a/pw_unit_test/py/pw_unit_test/test_runner.py
+++ b/pw_unit_test/py/pw_unit_test/test_runner.py
@@ -19,10 +19,10 @@
 import json
 import logging
 import os
+import shlex
 import subprocess
 import sys
 
-from pathlib import Path
 from typing import Dict, Iterable, List, Optional, Sequence, Set, Tuple
 
 import pw_cli.log
@@ -44,12 +44,8 @@
                         type=str,
                         required=True,
                         help='Executable which runs a test on the target')
-    parser.add_argument('-m',
-                        '--timeout',
-                        type=float,
-                        help='Timeout for test runner in seconds')
     parser.add_argument('runner_args',
-                        nargs="*",
+                        nargs=argparse.REMAINDER,
                         help='Arguments to forward to the test runner')
 
     # The runner script can either run binaries directly or groups.
@@ -126,15 +122,11 @@
 
 class TestRunner:
     """Runs unit tests by calling out to a runner script."""
-    def __init__(self,
-                 executable: str,
-                 args: Sequence[str],
-                 tests: Iterable[Test],
-                 timeout: Optional[float] = None):
+    def __init__(self, executable: str, args: Sequence[str],
+                 tests: Iterable[Test]):
         self._executable: str = executable
         self._args: Sequence[str] = args
         self._tests: List[Test] = list(tests)
-        self._timeout = timeout
 
     async def run_tests(self) -> None:
         """Runs all registered unit tests through the runner script."""
@@ -144,20 +136,13 @@
             test_counter = f'Test {idx:{len(total)}}/{total}'
 
             _LOG.info('%s: [ RUN] %s', test_counter, test.name)
-
-            # Convert POSIX to native directory seperators as GN produces '/'
-            # but the Windows test runner needs '\\'.
-            command = [
-                str(Path(self._executable)),
-                str(Path(test.file_path)), *self._args
-            ]
+            command = [self._executable, test.file_path, *self._args]
 
             if self._executable.endswith('.py'):
                 command.insert(0, sys.executable)
 
             try:
-                process = await pw_cli.process.run_async(*command,
-                                                         timeout=self._timeout)
+                process = await pw_cli.process.run_async(*command)
                 if process.returncode == 0:
                     test.status = TestResult.SUCCESS
                     test_result = 'PASS'
@@ -327,22 +312,40 @@
     return tests
 
 
-async def find_and_run_tests(
-    root: str,
-    runner: str,
-    timeout: Optional[float],
-    runner_args: Sequence[str] = (),
-    group: Optional[Sequence[str]] = None,
-    test: Optional[Sequence[str]] = None,
-) -> int:
+# TODO(frolv): Try to figure out a better solution for passing through the
+# corrected sys.argv across all pw commands.
+async def find_and_run_tests(argv_copy: List[str],
+                             root: str,
+                             runner: str,
+                             runner_args: Sequence[str] = (),
+                             group: Optional[Sequence[str]] = None,
+                             test: Optional[Sequence[str]] = None) -> int:
     """Runs some unit tests."""
 
+    if runner_args:
+        if runner_args[0] != '--':
+            _LOG.error('Unrecognized argument: %s', runner_args[0])
+            _LOG.info('')
+            _LOG.info('Did you mean to pass this argument to the runner?')
+            _LOG.info('Insert a -- in front of it to forward it through:')
+            _LOG.info('')
+
+            index = argv_copy.index(runner_args[0])
+            fixed_cmd = [*argv_copy[:index], '--', *argv_copy[index:]]
+
+            _LOG.info('  %s', ' '.join(shlex.quote(arg) for arg in fixed_cmd))
+            _LOG.info('')
+
+            return 1
+
+        runner_args = runner_args[1:]
+
     if test:
         tests = tests_from_paths(test)
     else:
         tests = tests_from_groups(group, root)
 
-    test_runner = TestRunner(runner, runner_args, tests, timeout)
+    test_runner = TestRunner(runner, runner_args, tests)
     await test_runner.run_tests()
 
     return 0 if test_runner.all_passed() else 1
@@ -360,7 +363,7 @@
 
     args_as_dict = dict(vars(parser.parse_args()))
     del args_as_dict['verbose']
-    return asyncio.run(find_and_run_tests(**args_as_dict))
+    return asyncio.run(find_and_run_tests(sys.argv, **args_as_dict))
 
 
 if __name__ == '__main__':
diff --git a/pw_unit_test/py/pyproject.toml b/pw_unit_test/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/pw_unit_test/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/pw_unit_test/py/rpc_service_test.py b/pw_unit_test/py/rpc_service_test.py
deleted file mode 100755
index 66c4f4c..0000000
--- a/pw_unit_test/py/rpc_service_test.py
+++ /dev/null
@@ -1,128 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Tests using the callback client for pw_rpc."""
-
-import logging
-from pathlib import Path
-from typing import List, Tuple
-import unittest
-from unittest import mock
-
-from pw_hdlc import rpc
-from pw_rpc import testing
-from pw_unit_test_proto import unit_test_pb2
-from pw_unit_test import run_tests, EventHandler, TestCase
-from pw_status import Status
-
-# The three suites (Passing, Failing, and DISABLED_Disabled) have these cases.
-_CASES = ('Zero', 'One', 'Two', 'DISABLED_Disabled')
-_FILE = 'pw_unit_test/test_rpc_server.cc'
-
-PASSING = tuple(TestCase('Passing', case, _FILE) for case in _CASES[:-1])
-FAILING = tuple(TestCase('Failing', case, _FILE) for case in _CASES[:-1])
-EXECUTED_TESTS = PASSING + FAILING
-
-DISABLED_SUITE = tuple(
-    TestCase('DISABLED_Disabled', case, _FILE) for case in _CASES)
-
-ALL_DISABLED_TESTS = (
-    TestCase('Passing', 'DISABLED_Disabled', _FILE),
-    TestCase('Failing', 'DISABLED_Disabled', _FILE),
-    *DISABLED_SUITE,
-)
-
-
-class RpcIntegrationTest(unittest.TestCase):
-    """Calls RPCs on an RPC server through a socket."""
-    test_server_command: Tuple[str, ...] = ()
-    port: int
-
-    def setUp(self) -> None:
-        self._context = rpc.HdlcRpcLocalServerAndClient(
-            self.test_server_command, self.port, [unit_test_pb2])
-        self.rpcs = self._context.client.channel(1).rpcs
-        self.handler = mock.NonCallableMagicMock(spec=EventHandler)
-
-    def tearDown(self) -> None:
-        self._context.close()
-
-    def test_run_tests_default_handler(self) -> None:
-        with self.assertLogs(logging.getLogger('pw_unit_test'),
-                             'INFO') as logs:
-            self.assertFalse(run_tests(self.rpcs))
-
-        for test in EXECUTED_TESTS:
-            self.assertTrue(any(str(test) in log for log in logs.output), test)
-
-    def test_run_tests_calls_test_case_start(self) -> None:
-        self.assertFalse(run_tests(self.rpcs, event_handlers=[self.handler]))
-
-        self.handler.test_case_start.assert_has_calls(
-            [mock.call(case) for case in EXECUTED_TESTS], any_order=True)
-
-    def test_run_tests_calls_test_case_end(self) -> None:
-        self.assertFalse(run_tests(self.rpcs, event_handlers=[self.handler]))
-
-        calls = [
-            mock.call(
-                case, unit_test_pb2.SUCCESS
-                if case.suite_name == 'Passing' else unit_test_pb2.FAILURE)
-            for case in EXECUTED_TESTS
-        ]
-        self.handler.test_case_end.assert_has_calls(calls, any_order=True)
-
-    def test_run_tests_calls_test_case_disabled(self) -> None:
-        self.assertFalse(run_tests(self.rpcs, event_handlers=[self.handler]))
-
-        self.handler.test_case_disabled.assert_has_calls(
-            [mock.call(case) for case in ALL_DISABLED_TESTS], any_order=True)
-
-    def test_passing_tests_only(self) -> None:
-        self.assertTrue(
-            run_tests(self.rpcs,
-                      test_suites=['Passing'],
-                      event_handlers=[self.handler]))
-        calls = [mock.call(case, unit_test_pb2.SUCCESS) for case in PASSING]
-        self.handler.test_case_end.assert_has_calls(calls, any_order=True)
-
-    def test_disabled_tests_only(self) -> None:
-        self.assertTrue(
-            run_tests(self.rpcs,
-                      test_suites=['DISABLED_Disabled'],
-                      event_handlers=[self.handler]))
-
-        self.handler.test_case_start.assert_not_called()
-        self.handler.test_case_end.assert_not_called()
-        self.handler.test_case_disabled.assert_has_calls(
-            [mock.call(case) for case in DISABLED_SUITE], any_order=True)
-
-    def test_failing_tests(self) -> None:
-        self.assertFalse(
-            run_tests(self.rpcs,
-                      test_suites=['Failing'],
-                      event_handlers=[self.handler]))
-        calls = [mock.call(case, unit_test_pb2.FAILURE) for case in FAILING]
-        self.handler.test_case_end.assert_has_calls(calls, any_order=True)
-
-
-def _main(test_server_command: List[str], port: int,
-          unittest_args: List[str]) -> None:
-    RpcIntegrationTest.test_server_command = tuple(test_server_command)
-    RpcIntegrationTest.port = port
-    unittest.main(argv=unittest_args)
-
-
-if __name__ == '__main__':
-    _main(**vars(testing.parse_test_server_args()))
diff --git a/pw_unit_test/py/setup.cfg b/pw_unit_test/py/setup.cfg
deleted file mode 100644
index c73ce69..0000000
--- a/pw_unit_test/py/setup.cfg
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pw_unit_test
-version = 0.1.0
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Unit tests for Pigweed projects
-
-[options]
-packages = find:
-zip_safe = False
-install_requires = pw_cli; pw_rpc; pw_unit_test_proto
-
-[options.package_data]
-pw_unit_test = py.typed
diff --git a/pw_unit_test/py/setup.py b/pw_unit_test/py/setup.py
index a3101bf..7ae8df1 100644
--- a/pw_unit_test/py/setup.py
+++ b/pw_unit_test/py/setup.py
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2019 The Pigweed Authors
 #
 # 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
@@ -15,4 +15,18 @@
 
 import setuptools  # type: ignore
 
-setuptools.setup()  # Package definition in setup.cfg
+setuptools.setup(
+    name='pw_unit_test',
+    version='0.1.0',
+    author='Pigweed Authors',
+    author_email='pigweed-developers@googlegroups.com',
+    description='Unit tests for Pigweed projects',
+    packages=setuptools.find_packages(),
+    package_data={'pw_unit_test': ['py.typed']},
+    zip_safe=False,
+    install_requires=[
+        'pw_cli',
+        'pw_rpc',
+        'pw_unit_test_proto',
+    ],
+)
diff --git a/pw_unit_test/simple_printing_event_handler.cc b/pw_unit_test/simple_printing_event_handler.cc
index 5a84d57..ab5cd74 100644
--- a/pw_unit_test/simple_printing_event_handler.cc
+++ b/pw_unit_test/simple_printing_event_handler.cc
@@ -28,9 +28,6 @@
     const RunTestsSummary& run_tests_summary) {
   WriteLine("[==========] Done running all tests.");
   WriteLine("[  PASSED  ] %d test(s).", run_tests_summary.passed_tests);
-  if (run_tests_summary.skipped_tests) {
-    WriteLine("[  SKIPPED ] %d test(s).", run_tests_summary.skipped_tests);
-  }
   if (run_tests_summary.failed_tests) {
     WriteLine("[  FAILED  ] %d test(s).", run_tests_summary.failed_tests);
   }
@@ -52,10 +49,6 @@
       WriteLine(
           "[  FAILED  ] %s.%s", test_case.suite_name, test_case.test_name);
       break;
-    case TestResult::kSkipped:
-      WriteLine(
-          "[  SKIPPED ] %s.%s", test_case.suite_name, test_case.test_name);
-      break;
   }
 }
 
diff --git a/pw_unit_test/test.gni b/pw_unit_test/test.gni
index 0169194..1fdb428 100644
--- a/pw_unit_test/test.gni
+++ b/pw_unit_test/test.gni
@@ -20,64 +20,20 @@
 declare_args() {
   # Path to a test runner to automatically run unit tests after they are built.
   #
-  # If set, a ``pw_test`` target's ``<target_name>.run`` action will invoke the
-  # test runner specified by this argument, passing the path to the unit test to
-  # run. If this is unset, the ``pw_test`` target's ``<target_name>.run`` step
-  # will do nothing.
+  # If set, the pw_test() template creates an action that invokes the test runner
+  # on each test executable. If unset, the pw_test() template only creates a test
+  # executable target.
   #
-  # Targets that don't support parallelized execution of tests (e.g. a on-device
-  # test runner that must flash a device and run the test in serial) should
-  # set pw_unit_test_POOL_DEPTH to 1.
-  #
-  # Type: string (name of an executable on the PATH, or path to an executable)
-  # Usage: toolchain-controlled only
+  # This should only be enabled for targets which support parallelized running of
+  # unit tests, such as desktops with multiple cores.
   pw_unit_test_AUTOMATIC_RUNNER = ""
 
-  # Optional list of arguments to forward to the automatic runner.
-  #
-  # Type: list of strings (args to pass to pw_unit_test_AUTOMATIC_RUNNER)
-  # Usage: toolchain-controlled only
-  pw_unit_test_AUTOMATIC_RUNNER_ARGS = []
-
-  # Optional timeout to apply when running tests via the automatic runner.
-  # Timeout is in seconds. Defaults to empty which means no timeout.
-  pw_unit_test_AUTOMATIC_RUNNER_TIMEOUT = ""
-
   # Additional dependencies required by all unit test targets. (For example, if
   # using a different test library like Googletest.)
-  #
-  # Type: list of strings (list of dependencies as GN paths)
-  # Usage: toolchain-controlled only
   pw_unit_test_PUBLIC_DEPS = []
 
-  # Implementation of a main function for ``pw_test`` unit test binaries.
-  #
-  # Type: string (GN path to a source set)
-  # Usage: toolchain-controlled only
+  # Implementation of a main function for "pw_test" unit test binaries.
   pw_unit_test_MAIN = "$dir_pw_unit_test:simple_printing_main"
-
-  # The maximum number of unit tests that may be run concurrently for the
-  # current toolchain. Setting this to 0 disables usage of a pool, allowing
-  # unlimited parallelization.
-  #
-  # Note: A single target with two toolchain configurations (e.g. release/debug)
-  #       will use two separate test runner pools by default. Set
-  #       pw_unit_test_POOL_TOOLCHAIN to the same toolchain for both targets to
-  #       merge the pools and force serialization.
-  #
-  # Type: integer
-  # Usage: toolchain-controlled only
-  pw_unit_test_POOL_DEPTH = 0
-
-  # The toolchain to use when referring to the pw_unit_test runner pool. When
-  # this is disabled, the current toolchain is used. This means that every
-  # toolchain will use its own pool definition. If two toolchains should share
-  # the same pool, this argument should be by one of the toolchains to the GN
-  # path of the other toolchain.
-  #
-  # Type: string (GN path to a toolchain)
-  # Usage: toolchain-controlled only
-  pw_unit_test_POOL_TOOLCHAIN = ""
 }
 
 # Defines a target if enable_if is true. Otherwise, it defines that target as
@@ -216,41 +172,23 @@
       # Create a run target for the .DISABLED version of the test.
       _test_to_run = _test_target_name + ".DISABLED"
 
-      # Create a placeholder _run target for the regular version of the test.
+      # Create a dummy _run target for the regular version of the test.
       group(_test_target_name + ".run") {
         deps = [ ":$_test_target_name" ]
       }
     }
 
     pw_python_action(_test_to_run + ".run") {
-      # Optionally limit max test runner concurrency.
-      if (pw_unit_test_POOL_DEPTH != 0) {
-        _pool_toolchain = current_toolchain
-        if (pw_unit_test_POOL_TOOLCHAIN != "") {
-          _pool_toolchain = pw_unit_test_POOL_TOOLCHAIN
-        }
-        pool = "$dir_pw_unit_test:unit_test_pool($_pool_toolchain)"
-      }
-
       deps = [ ":$_test_target_name" ]
       inputs = [ pw_unit_test_AUTOMATIC_RUNNER ]
-      module = "pw_unit_test.test_runner"
-      python_deps = [ "$dir_pw_unit_test/py" ]
+      script = "$dir_pw_unit_test/py/pw_unit_test/test_runner.py"
+      python_deps = [ "$dir_pw_cli/py" ]
       args = [
         "--runner",
-        rebase_path(pw_unit_test_AUTOMATIC_RUNNER, root_build_dir),
+        rebase_path(pw_unit_test_AUTOMATIC_RUNNER),
         "--test",
         "<TARGET_FILE(:$_test_to_run)>",
       ]
-      if (pw_unit_test_AUTOMATIC_RUNNER_TIMEOUT != "") {
-        args += [
-          "--timeout",
-          pw_unit_test_AUTOMATIC_RUNNER_TIMEOUT,
-        ]
-      }
-      if (pw_unit_test_AUTOMATIC_RUNNER_ARGS != []) {
-        args += [ "--" ] + pw_unit_test_AUTOMATIC_RUNNER_ARGS
-      }
       stamp = true
     }
 
diff --git a/pw_unit_test/test_rpc_server.cc b/pw_unit_test/test_rpc_server.cc
deleted file mode 100644
index 795dc1b..0000000
--- a/pw_unit_test/test_rpc_server.cc
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "gtest/gtest.h"
-#include "pw_assert/check.h"
-#include "pw_log/log.h"
-#include "pw_rpc_system_server/rpc_server.h"
-#include "pw_rpc_system_server/socket.h"
-#include "pw_unit_test/unit_test_service.h"
-
-namespace {
-
-pw::unit_test::UnitTestService unit_test_service;
-
-TEST(Passing, Zero) {}
-
-TEST(Passing, One) { EXPECT_TRUE(true); }
-
-TEST(Passing, Two) {
-  EXPECT_FALSE(0);
-  EXPECT_STREQ("Yes!", "Yes!\0extra stuff!");
-}
-
-TEST(Passing, DISABLED_Disabled) {
-  EXPECT_FALSE(0);
-  EXPECT_STREQ("Yes!", "Yes!\0extra stuff!");
-}
-
-TEST(Failing, Zero) { FAIL(); }
-
-TEST(Failing, One) { EXPECT_TRUE(false); }
-
-TEST(Failing, Two) {
-  EXPECT_FALSE(1);
-  EXPECT_STREQ("No!", "No?");
-}
-
-TEST(Failing, DISABLED_Disabled) {
-  EXPECT_FALSE(1);
-  EXPECT_STREQ("No!", "No?");
-}
-
-TEST(DISABLED_Disabled, Zero) { FAIL(); }
-
-TEST(DISABLED_Disabled, One) { EXPECT_TRUE(false); }
-
-TEST(DISABLED_Disabled, Two) {
-  EXPECT_FALSE(1);
-  EXPECT_STREQ("No!", "No?");
-}
-
-TEST(DISABLED_Disabled, DISABLED_Disabled) {
-  EXPECT_FALSE(1);
-  EXPECT_STREQ("No!", "No?");
-}
-
-}  // namespace
-
-int main(int argc, char* argv[]) {
-  if (argc != 2) {
-    PW_LOG_ERROR("Usage: %s PORT", argv[0]);
-    return 1;
-  }
-  pw::rpc::system_server::set_socket_port(std::atoi(argv[1]));
-  pw::rpc::system_server::Init();
-  pw::rpc::system_server::Server().RegisterService(unit_test_service);
-
-  PW_LOG_INFO("Starting pw_rpc server");
-  PW_CHECK_OK(pw::rpc::system_server::Start());
-
-  return 0;
-}
diff --git a/pw_unit_test/unit_test_service.cc b/pw_unit_test/unit_test_service.cc
index 5965755..b43537f 100644
--- a/pw_unit_test/unit_test_service.cc
+++ b/pw_unit_test/unit_test_service.cc
@@ -21,7 +21,9 @@
 
 namespace pw::unit_test {
 
-void UnitTestService::Run(ConstByteSpan request, RawServerWriter& writer) {
+void UnitTestService::Run(ServerContext&,
+                          ConstByteSpan request,
+                          RawServerWriter& writer) {
   writer_ = std::move(writer);
   verbose_ = false;
 
@@ -36,8 +38,7 @@
   while ((status = decoder.Next()).ok()) {
     switch (static_cast<TestRunRequest::Fields>(decoder.FieldNumber())) {
       case TestRunRequest::Fields::REPORT_PASSED_EXPECTATIONS:
-        decoder.ReadBool(&verbose_)
-            .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+        decoder.ReadBool(&verbose_);
         break;
 
       case TestRunRequest::Fields::TEST_SUITE: {
@@ -49,10 +50,9 @@
         if (!suites_to_run.full()) {
           suites_to_run.push_back(suite_name);
         } else {
-          PW_LOG_ERROR("Maximum of %u test suite filters supported",
-                       static_cast<unsigned>(suites_to_run.max_size()));
-          writer_.Finish(Status::InvalidArgument())
-              .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+          PW_LOG_ERROR("Maximum of %d test suite filters supported",
+                       suites_to_run.max_size());
+          writer_.Finish(Status::InvalidArgument());
           return;
         }
 
@@ -62,8 +62,7 @@
   }
 
   if (status != Status::OutOfRange()) {
-    writer_.Finish(status)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    writer_.Finish(status);
     return;
   }
 
@@ -81,59 +80,45 @@
 
   PW_LOG_INFO("Unit test run complete");
 
-  writer_.Finish().IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  writer_.Finish();
 }
 
 void UnitTestService::WriteTestRunStart() {
   // Write out the key for the start field (even though the message is empty).
-  WriteEvent(
-      [&](Event::StreamEncoder& event) { event.GetTestRunStartEncoder(); });
+  WriteEvent([&](Event::Encoder& event) { event.GetTestRunStartEncoder(); });
 }
 
 void UnitTestService::WriteTestRunEnd(const RunTestsSummary& summary) {
-  WriteEvent([&](Event::StreamEncoder& event) {
-    TestRunEnd::StreamEncoder test_run_end = event.GetTestRunEndEncoder();
-    test_run_end.WritePassed(summary.passed_tests)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    test_run_end.WriteFailed(summary.failed_tests)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    test_run_end.WriteSkipped(summary.skipped_tests)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    test_run_end.WriteDisabled(summary.disabled_tests)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  WriteEvent([&](Event::Encoder& event) {
+    TestRunEnd::Encoder test_run_end = event.GetTestRunEndEncoder();
+    test_run_end.WritePassed(summary.passed_tests);
+    test_run_end.WriteFailed(summary.failed_tests);
+    test_run_end.WriteSkipped(summary.skipped_tests);
+    test_run_end.WriteDisabled(summary.disabled_tests);
   });
 }
 
 void UnitTestService::WriteTestCaseStart(const TestCase& test_case) {
-  WriteEvent([&](Event::StreamEncoder& event) {
-    TestCaseDescriptor::StreamEncoder descriptor =
-        event.GetTestCaseStartEncoder();
-    descriptor.WriteSuiteName(test_case.suite_name)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    descriptor.WriteTestName(test_case.test_name)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    descriptor.WriteFileName(test_case.file_name)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  WriteEvent([&](Event::Encoder& event) {
+    TestCaseDescriptor::Encoder descriptor = event.GetTestCaseStartEncoder();
+    descriptor.WriteSuiteName(test_case.suite_name);
+    descriptor.WriteTestName(test_case.test_name);
+    descriptor.WriteFileName(test_case.file_name);
   });
 }
 
 void UnitTestService::WriteTestCaseEnd(TestResult result) {
-  WriteEvent([&](Event::StreamEncoder& event) {
-    event.WriteTestCaseEnd(static_cast<TestCaseResult>(result))
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  WriteEvent([&](Event::Encoder& event) {
+    event.WriteTestCaseEnd(static_cast<TestCaseResult>(result));
   });
 }
 
 void UnitTestService::WriteTestCaseDisabled(const TestCase& test_case) {
-  WriteEvent([&](Event::StreamEncoder& event) {
-    TestCaseDescriptor::StreamEncoder descriptor =
-        event.GetTestCaseDisabledEncoder();
-    descriptor.WriteSuiteName(test_case.suite_name)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    descriptor.WriteTestName(test_case.test_name)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    descriptor.WriteFileName(test_case.file_name)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+  WriteEvent([&](Event::Encoder& event) {
+    TestCaseDescriptor::Encoder descriptor = event.GetTestCaseDisabledEncoder();
+    descriptor.WriteSuiteName(test_case.suite_name);
+    descriptor.WriteTestName(test_case.test_name);
+    descriptor.WriteFileName(test_case.file_name);
   });
 }
 
@@ -143,18 +128,14 @@
     return;
   }
 
-  WriteEvent([&](Event::StreamEncoder& event) {
-    TestCaseExpectation::StreamEncoder test_case_expectation =
+  WriteEvent([&](Event::Encoder& event) {
+    TestCaseExpectation::Encoder test_case_expectation =
         event.GetTestCaseExpectationEncoder();
-    test_case_expectation.WriteExpression(expectation.expression)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    test_case_expectation
-        .WriteEvaluatedExpression(expectation.evaluated_expression)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    test_case_expectation.WriteLineNumber(expectation.line_number)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    test_case_expectation.WriteSuccess(expectation.success)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    test_case_expectation.WriteExpression(expectation.expression);
+    test_case_expectation.WriteEvaluatedExpression(
+        expectation.evaluated_expression);
+    test_case_expectation.WriteLineNumber(expectation.line_number);
+    test_case_expectation.WriteSuccess(expectation.success);
   });
 }
 
diff --git a/pw_varint/BUILD b/pw_varint/BUILD
new file mode 100644
index 0000000..1104fdb
--- /dev/null
+++ b/pw_varint/BUILD
@@ -0,0 +1,51 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pw_varint",
+    srcs = [
+        "varint.cc",
+    ],
+    hdrs = [
+        "public/pw_varint/varint.h",
+    ],
+    includes = ["public"],
+    deps = [
+        "//pw_polyfill",
+        "//pw_preprocessor",
+        "//pw_span",
+    ],
+)
+
+pw_cc_test(
+    name = "varint_test",
+    srcs = [
+        "varint_test_c.c",
+        "varint_test.cc",
+    ],
+    deps = [
+        ":pw_varint",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_varint/BUILD.bazel b/pw_varint/BUILD.bazel
deleted file mode 100644
index 5b51629..0000000
--- a/pw_varint/BUILD.bazel
+++ /dev/null
@@ -1,79 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_varint",
-    srcs = [
-        "varint.cc",
-    ],
-    hdrs = [
-        "public/pw_varint/varint.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_polyfill",
-        "//pw_preprocessor",
-        "//pw_span",
-    ],
-)
-
-pw_cc_library(
-    name = "stream",
-    srcs = [
-        "stream.cc",
-    ],
-    hdrs = [
-        "public/pw_varint/stream.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":pw_varint",
-        "//pw_span",
-        "//pw_status",
-        "//pw_stream",
-    ],
-)
-
-pw_cc_test(
-    name = "varint_test",
-    srcs = [
-        "varint_test.cc",
-        "varint_test_c.c",
-    ],
-    deps = [
-        ":pw_varint",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_test(
-    name = "stream_test",
-    srcs = [
-        "stream_test.cc",
-    ],
-    deps = [
-        ":stream",
-        "//pw_unit_test",
-    ],
-)
diff --git a/pw_varint/BUILD.gn b/pw_varint/BUILD.gn
index 7edbe7e..c5c7980 100644
--- a/pw_varint/BUILD.gn
+++ b/pw_varint/BUILD.gn
@@ -25,26 +25,15 @@
 pw_source_set("pw_varint") {
   public_configs = [ ":default_config" ]
   public_deps = [ dir_pw_preprocessor ]
-  sources = [ "varint.cc" ]
+  sources = [
+    "public/pw_varint/varint.h",
+    "varint.cc",
+  ]
   public = [ "public/pw_varint/varint.h" ]
 }
 
-pw_source_set("stream") {
-  public_configs = [ ":default_config" ]
-  public_deps = [
-    dir_pw_status,
-    dir_pw_stream,
-  ]
-  public = [ "public/pw_varint/stream.h" ]
-  sources = [ "stream.cc" ]
-  deps = [ ":pw_varint" ]
-}
-
 pw_test_group("tests") {
-  tests = [
-    ":stream_test",
-    ":varint_test",
-  ]
+  tests = [ ":varint_test" ]
 }
 
 pw_test("varint_test") {
@@ -55,14 +44,6 @@
   ]
 }
 
-pw_test("stream_test") {
-  deps = [
-    ":stream",
-    dir_pw_stream,
-  ]
-  sources = [ "stream_test.cc" ]
-}
-
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
diff --git a/pw_varint/CMakeLists.txt b/pw_varint/CMakeLists.txt
index 2a88f62..84da984 100644
--- a/pw_varint/CMakeLists.txt
+++ b/pw_varint/CMakeLists.txt
@@ -14,54 +14,8 @@
 
 include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
 
-pw_add_module_library(pw_varint
-  HEADERS
-    public/pw_varint/varint.h
-  PUBLIC_INCLUDES
-    public
+pw_auto_add_simple_module(pw_varint
   PUBLIC_DEPS
-    pw_polyfill.cstddef
-    pw_polyfill.span
     pw_preprocessor
-  SOURCES
-    varint.cc
-)
-if(Zephyr_FOUND AND CONFIG_PIGWEED_VARINT)
-  zephyr_link_libraries(pw_varint)
-endif()
-
-pw_add_module_library(pw_varint.stream
-  HEADERS
-    public/pw_varint/stream.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_status
-    pw_stream
-  SOURCES
-    stream.cc
-  PRIVATE_DEPS
-    pw_varint
-)
-
-pw_add_test(pw_varint.varint_test
-  SOURCES
-    varint_test.cc
-    varint_test_c.c
-  DEPS
-    pw_varint
-  GROUPS
-    modules
-    pw_varint
-)
-
-pw_add_test(pw_varint.stream_test
-  SOURCES
-    stream_test.cc
-  DEPS
-    pw_stream
-    pw_varint.stream
-  GROUPS
-    modules
-    pw_varint
+    pw_span
 )
diff --git a/pw_varint/Kconfig b/pw_varint/Kconfig
deleted file mode 100644
index b43114d..0000000
--- a/pw_varint/Kconfig
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-config PIGWEED_VARINT
-    bool "Enable Pigweed variable length int library (pw_varint)"
-    select PIGWEED_PREPROCESSOR
-    select PIGWEED_SPAN
diff --git a/pw_varint/OWNERS b/pw_varint/OWNERS
deleted file mode 100644
index 3afb926..0000000
--- a/pw_varint/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-frolv@google.com
diff --git a/pw_varint/docs.rst b/pw_varint/docs.rst
index 1d87fe6..58c90f9 100644
--- a/pw_varint/docs.rst
+++ b/pw_varint/docs.rst
@@ -14,7 +14,7 @@
 Compatibility
 =============
 * C
-* C++14 (with :doc:`../pw_polyfill/docs`)
+* C++11 (with :doc:`../pw_polyfill/docs`)
 
 API
 ===
@@ -33,27 +33,6 @@
 Returns the maximum integer value that can be encoded as a varint into the
 specified number of bytes.
 
-
-Stream API
-----------
-
-.. cpp:function:: StatusWithSize Read(stream::Reader& reader, int64_t* output)
-.. cpp:function:: StatusWithSize Read(stream::Reader& reader, uint64_t* output)
-
-Decoders a varint from the current position of a stream. If reading into a
-signed integer, the value is ZigZag decoded.
-
-Returns the number of bytes read from the stream, places the value in `output`,
-if successful. Returns `OutOfRange` if the varint does not fit in to the type,
-or if the input is exhausted before the number terminates.
-
-Reads a maximum of 10 bytes.
-
 Dependencies
 ============
 * ``pw_span``
-
-Zephyr
-======
-To enable ``pw_varint`` for Zephyr add ``CONFIG_PIGWEED_VARINT=y`` to the
-project's configuration.
diff --git a/pw_varint/public/pw_varint/stream.h b/pw_varint/public/pw_varint/stream.h
deleted file mode 100644
index 7ae3564..0000000
--- a/pw_varint/public/pw_varint/stream.h
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_status/status_with_size.h"
-#include "pw_stream/stream.h"
-
-namespace pw {
-namespace varint {
-
-// Reads a varint-encoded value from a pw::stream. If reading into a signed
-// integer, the value is ZigZag decoded.
-//
-// Returns the number of bytes read from the stream if successful, OutOfRange
-// if the varint does not fit in a int64_t / uint64_t or if the input is
-// exhausted before the number terminates. Reads a maximum of 10 bytes.
-StatusWithSize Read(stream::Reader& reader, int64_t* output);
-StatusWithSize Read(stream::Reader& reader, uint64_t* output);
-
-}  // namespace varint
-}  // namespace pw
diff --git a/pw_varint/stream.cc b/pw_varint/stream.cc
deleted file mode 100644
index 00fe1fb..0000000
--- a/pw_varint/stream.cc
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_varint/stream.h"
-
-#include <cstddef>
-#include <cstdint>
-#include <span>
-
-#include "pw_status/status_with_size.h"
-#include "pw_stream/stream.h"
-#include "pw_varint/varint.h"
-
-namespace pw {
-namespace varint {
-
-StatusWithSize Read(stream::Reader& reader, int64_t* output) {
-  uint64_t value = 0;
-  StatusWithSize count = Read(reader, &value);
-  if (!count.ok()) {
-    return count;
-  }
-
-  *output = ZigZagDecode(value);
-  return count;
-}
-
-StatusWithSize Read(stream::Reader& reader, uint64_t* output) {
-  uint64_t value = 0;
-  size_t count = 0;
-
-  while (true) {
-    if (count >= varint::kMaxVarint64SizeBytes) {
-      // Varint can't fit a uint64_t.
-      return StatusWithSize::OutOfRange();
-    }
-
-    std::byte b;
-    if (auto result = reader.Read(std::span(&b, 1)); !result.ok()) {
-      return StatusWithSize(result.status(), 0);
-    }
-
-    value |= static_cast<uint64_t>(b & std::byte(0b01111111)) << (7 * count);
-    ++count;
-
-    // MSB == 0 indicates last byte of the varint.
-    if ((b & std::byte(0b10000000)) == std::byte(0)) {
-      break;
-    }
-  }
-
-  *output = value;
-  return StatusWithSize(count);
-}
-
-}  // namespace varint
-}  // namespace pw
diff --git a/pw_varint/stream_test.cc b/pw_varint/stream_test.cc
deleted file mode 100644
index 6a42f58..0000000
--- a/pw_varint/stream_test.cc
+++ /dev/null
@@ -1,244 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_varint/stream.h"
-
-#include <array>
-#include <cstddef>
-#include <cstdint>
-#include <cstring>
-#include <limits>
-
-#include "gtest/gtest.h"
-#include "pw_stream/memory_stream.h"
-
-namespace pw::varint {
-namespace {
-template <size_t kStringSize>
-auto MakeBuffer(const char (&data)[kStringSize]) {
-  constexpr size_t kSizeBytes = kStringSize - 1;
-  static_assert(kSizeBytes <= 10, "Varint arrays never need be larger than 10");
-
-  std::array<std::byte, kSizeBytes> array;
-  std::memcpy(array.data(), data, kSizeBytes);
-  return array;
-}
-}  // namespace
-
-TEST(VarintRead, Signed64_SingleByte) {
-  int64_t value = -1234;
-
-  {
-    const auto buffer = MakeBuffer("\x00");
-    stream::MemoryReader reader(buffer);
-    const auto sws = Read(reader, &value);
-    EXPECT_TRUE(sws.ok());
-    EXPECT_EQ(sws.size(), 1u);
-    EXPECT_EQ(value, 0);
-  }
-
-  {
-    const auto buffer = MakeBuffer("\x01");
-    stream::MemoryReader reader(buffer);
-    const auto sws = Read(reader, &value);
-    EXPECT_TRUE(sws.ok());
-    EXPECT_EQ(sws.size(), 1u);
-    EXPECT_EQ(value, -1);
-  }
-
-  {
-    const auto buffer = MakeBuffer("\x02");
-    stream::MemoryReader reader(buffer);
-    const auto sws = Read(reader, &value);
-    EXPECT_TRUE(sws.ok());
-    EXPECT_EQ(sws.size(), 1u);
-    EXPECT_EQ(value, 1);
-  }
-
-  {
-    const auto buffer = MakeBuffer("\x03");
-    stream::MemoryReader reader(buffer);
-    const auto sws = Read(reader, &value);
-    EXPECT_TRUE(sws.ok());
-    EXPECT_EQ(sws.size(), 1u);
-    EXPECT_EQ(value, -2);
-  }
-
-  {
-    const auto buffer = MakeBuffer("\x04");
-    stream::MemoryReader reader(buffer);
-    const auto sws = Read(reader, &value);
-    EXPECT_TRUE(sws.ok());
-    EXPECT_EQ(sws.size(), 1u);
-    EXPECT_EQ(value, 2);
-  }
-}
-
-TEST(VarintRead, Signed64_MultiByte) {
-  int64_t value = -1234;
-
-  {
-    const auto buffer = MakeBuffer("\x80\x01");
-    stream::MemoryReader reader(buffer);
-    const auto sws = Read(reader, &value);
-    EXPECT_TRUE(sws.ok());
-    EXPECT_EQ(sws.size(), 2u);
-    EXPECT_EQ(value, 64);
-  }
-
-  {
-    const auto buffer = MakeBuffer("\x81\x01");
-    stream::MemoryReader reader(buffer);
-    const auto sws = Read(reader, &value);
-    EXPECT_TRUE(sws.ok());
-    EXPECT_EQ(sws.size(), 2u);
-    EXPECT_EQ(value, -65);
-  }
-
-  {
-    const auto buffer = MakeBuffer("\x82\x01");
-    stream::MemoryReader reader(buffer);
-    const auto sws = Read(reader, &value);
-    EXPECT_TRUE(sws.ok());
-    EXPECT_EQ(sws.size(), 2u);
-    EXPECT_EQ(value, 65);
-  }
-
-  {
-    const auto buffer = MakeBuffer("\xff\xff\xff\xff\x0f");
-    stream::MemoryReader reader(buffer);
-    const auto sws = Read(reader, &value);
-    EXPECT_TRUE(sws.ok());
-    EXPECT_EQ(sws.size(), 5u);
-    EXPECT_EQ(value, std::numeric_limits<int32_t>::min());
-  }
-
-  {
-    const auto buffer = MakeBuffer("\xfe\xff\xff\xff\x0f");
-    stream::MemoryReader reader(buffer);
-    const auto sws = Read(reader, &value);
-    EXPECT_TRUE(sws.ok());
-    EXPECT_EQ(sws.size(), 5u);
-    EXPECT_EQ(value, std::numeric_limits<int32_t>::max());
-  }
-
-  {
-    const auto buffer = MakeBuffer("\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01");
-    stream::MemoryReader reader(buffer);
-    const auto sws = Read(reader, &value);
-    EXPECT_TRUE(sws.ok());
-    EXPECT_EQ(sws.size(), 10u);
-    EXPECT_EQ(value, std::numeric_limits<int64_t>::min());
-  }
-
-  {
-    const auto buffer = MakeBuffer("\xfe\xff\xff\xff\xff\xff\xff\xff\xff\x01");
-    stream::MemoryReader reader(buffer);
-    const auto sws = Read(reader, &value);
-    EXPECT_TRUE(sws.ok());
-    EXPECT_EQ(sws.size(), 10u);
-    EXPECT_EQ(value, std::numeric_limits<int64_t>::max());
-  }
-}
-
-TEST(VarintRead, Unsigned64_SingleByte) {
-  uint64_t value = 1234;
-
-  {
-    const auto buffer = MakeBuffer("\x00");
-    stream::MemoryReader reader(buffer);
-    const auto sws = Read(reader, &value);
-    EXPECT_TRUE(sws.ok());
-    EXPECT_EQ(sws.size(), 1u);
-    EXPECT_EQ(value, 0u);
-  }
-
-  {
-    const auto buffer = MakeBuffer("\x04");
-    stream::MemoryReader reader(buffer);
-    const auto sws = Read(reader, &value);
-    EXPECT_TRUE(sws.ok());
-    EXPECT_EQ(sws.size(), 1u);
-    EXPECT_EQ(value, 4u);
-  }
-
-  {
-    const auto buffer = MakeBuffer("\x41");
-    stream::MemoryReader reader(buffer);
-    const auto sws = Read(reader, &value);
-    EXPECT_TRUE(sws.ok());
-    EXPECT_EQ(sws.size(), 1u);
-    EXPECT_EQ(value, 65u);
-  }
-}
-
-TEST(VarintRead, Unsigned64_MultiByte) {
-  uint64_t value = -1234;
-
-  {
-    const auto buffer = MakeBuffer("\x80\x01");
-    stream::MemoryReader reader(buffer);
-    const auto sws = Read(reader, &value);
-    EXPECT_TRUE(sws.ok());
-    EXPECT_EQ(sws.size(), 2u);
-    EXPECT_EQ(value, 128u);
-  }
-
-  {
-    const auto buffer = MakeBuffer("\x81\x01");
-    stream::MemoryReader reader(buffer);
-    const auto sws = Read(reader, &value);
-    EXPECT_TRUE(sws.ok());
-    EXPECT_EQ(sws.size(), 2u);
-    EXPECT_EQ(value, 129u);
-  }
-
-  {
-    const auto buffer = MakeBuffer("\xfe\xff\xff\xff\x0f");
-    stream::MemoryReader reader(buffer);
-    const auto sws = Read(reader, &value);
-    EXPECT_TRUE(sws.ok());
-    EXPECT_EQ(sws.size(), 5u);
-    EXPECT_EQ(value, std::numeric_limits<uint32_t>::max() - 1);
-  }
-
-  {
-    const auto buffer = MakeBuffer("\xff\xff\xff\xff\x0f");
-    stream::MemoryReader reader(buffer);
-    const auto sws = Read(reader, &value);
-    EXPECT_TRUE(sws.ok());
-    EXPECT_EQ(sws.size(), 5u);
-    EXPECT_EQ(value, std::numeric_limits<uint32_t>::max());
-  }
-
-  {
-    const auto buffer = MakeBuffer("\xfe\xff\xff\xff\xff\xff\xff\xff\xff\x01");
-    stream::MemoryReader reader(buffer);
-    const auto sws = Read(reader, &value);
-    EXPECT_TRUE(sws.ok());
-    EXPECT_EQ(sws.size(), 10u);
-    EXPECT_EQ(value, std::numeric_limits<uint64_t>::max() - 1);
-  }
-
-  {
-    const auto buffer = MakeBuffer("\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01");
-    stream::MemoryReader reader(buffer);
-    const auto sws = Read(reader, &value);
-    EXPECT_TRUE(sws.ok());
-    EXPECT_EQ(sws.size(), 10u);
-    EXPECT_EQ(value, std::numeric_limits<uint64_t>::max());
-  }
-}
-
-}  // namespace pw::varint
diff --git a/pw_varint/varint.cc b/pw_varint/varint.cc
index 0eadab3..fcef43d 100644
--- a/pw_varint/varint.cc
+++ b/pw_varint/varint.cc
@@ -30,7 +30,7 @@
 
 }  // namespace
 
-extern "C" size_t pw_varint_EncodeCustom(uint64_t integer,
+extern "C" size_t pw_varint_EncodeCustom(uint64_t input,
                                          void* output,
                                          size_t output_size,
                                          pw_varint_Format format) {
@@ -54,10 +54,10 @@
       return 0;
     }
 
-    bool last_byte = (integer >> 7) == 0u;
+    bool last_byte = (input >> 7) == 0u;
 
     // Grab 7 bits and set the eighth according to the continuation bit.
-    std::byte value = (static_cast<std::byte>(integer) & std::byte(0x7f))
+    std::byte value = (static_cast<std::byte>(input) & std::byte(0x7f))
                       << value_shift;
 
     if (last_byte) {
@@ -67,8 +67,8 @@
     }
 
     buffer[written++] = value;
-    integer >>= 7;
-  } while (integer != 0u);
+    input >>= 7;
+  } while (input != 0u);
 
   return written;
 }
diff --git a/pw_varint/varint_test.cc b/pw_varint/varint_test.cc
index a359f84..27282c4 100644
--- a/pw_varint/varint_test.cc
+++ b/pw_varint/varint_test.cc
@@ -40,14 +40,17 @@
 
 class Varint : public ::testing::Test {
  protected:
-  Varint() : buffer_ {
-    std::byte{'a'}, std::byte{'b'}, std::byte{'c'}, std::byte{'d'},
-        std::byte{'e'}, std::byte{'f'}, std::byte{'g'}, std::byte{'h'},
-        std::byte{'i'}, std::byte {
-      'j'
-    }
-  }
-  {}
+  Varint()
+      : buffer_{std::byte{'a'},
+                std::byte{'b'},
+                std::byte{'c'},
+                std::byte{'d'},
+                std::byte{'e'},
+                std::byte{'f'},
+                std::byte{'g'},
+                std::byte{'h'},
+                std::byte{'i'},
+                std::byte{'j'}} {}
   std::byte buffer_[10];
 };
 
diff --git a/pw_watch/OWNERS b/pw_watch/OWNERS
deleted file mode 100644
index 4462823..0000000
--- a/pw_watch/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-hepler@google.com
-keir@google.com
diff --git a/pw_watch/docs.rst b/pw_watch/docs.rst
index 306bec2..ad4def6 100644
--- a/pw_watch/docs.rst
+++ b/pw_watch/docs.rst
@@ -19,15 +19,16 @@
 ============
 The simplest way to get started with ``pw_watch`` is to launch it from a shell
 using the Pigweed environment as ``pw watch``. By default, ``pw_watch`` watches
-for repository changes and triggers the default Ninja build target at out/. To
-override this behavior, provide the ``-C`` argument to ``pw watch``.
+for repository changes and triggers the default Ninja build target for an
+automatically located build directory (typically ``$PW_ROOT/out``). To override
+this behavior, provide the ``-C`` argument to ``pw watch``.
 
 .. code:: sh
 
-  # Use ./out/ as the build directory and build the default target
+  # Find a build directory and build the default target
   pw watch
 
-  # Use ./out/ as the build directory and build the stm32f429i target
+  # Find a build directory and build the stm32f429i target
   pw watch python.lint stm32f429i
 
   # Build pw_run_tests.modules in the out/cmake directory
@@ -36,21 +37,9 @@
   # Build the default target in out/ and pw_apps in out/cmake
   pw watch -C out -C out/cmake pw_apps
 
-  # Build python.tests in out/ and build pw_apps in out/cmake
+  # Find a directory and build python.tests, and build pw_apps in out/cmake
   pw watch python.tests -C out/cmake pw_apps
 
-  # Build the default target, but only run up to 8 jobs in parallel.
-  pw watch -j8
-
-  # Build the default target and start a docs server on http://127.0.0.1:8000
-  pw watch --serve-docs
-
-  # Build the default target and start a docs server on http://127.0.0.1:5555
-  pw watch --serve-docs --serve-docs-port=5555
-
-  # Build with a full screen terminal user interface similar to pw_console.
-  pw watch --fullscreen
-
 ``pw watch`` only rebuilds when a file that is not ignored by Git changes.
 Adding exclusions to a ``.gitignore`` causes watch to ignore them, even if the
 files were forcibly added to a repo. By default, only files matching certain
@@ -65,8 +54,6 @@
 change. This can be disabled with the ``--no-restart`` option. While running
 ``pw watch``, you may also press enter to immediately restart a build.
 
-See ``pw watch -h`` for the full list of command line arguments.
-
 Unit Test Integration
 =====================
 Thanks to GN's understanding of the full dependency tree, only the tests
diff --git a/pw_watch/py/BUILD.gn b/pw_watch/py/BUILD.gn
index 2197983..e14d675 100644
--- a/pw_watch/py/BUILD.gn
+++ b/pw_watch/py/BUILD.gn
@@ -17,16 +17,11 @@
 import("$dir_pw_build/python.gni")
 
 pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
+  setup = [ "setup.py" ]
   sources = [
     "pw_watch/__init__.py",
     "pw_watch/debounce.py",
     "pw_watch/watch.py",
-    "pw_watch/watch_app.py",
   ]
   tests = [ "watch_test.py" ]
   pylintrc = "$dir_pigweed/.pylintrc"
diff --git a/pw_watch/py/pw_watch/debounce.py b/pw_watch/py/pw_watch/debounce.py
index 3417b1e..0384219 100644
--- a/pw_watch/py/pw_watch/debounce.py
+++ b/pw_watch/py/pw_watch/debounce.py
@@ -18,7 +18,7 @@
 import threading
 from abc import ABC, abstractmethod
 
-_LOG = logging.getLogger('pw_watch')
+_LOG = logging.getLogger(__name__)
 
 
 class DebouncedFunction(ABC):
@@ -97,12 +97,12 @@
             # re-try running afterwards.
 
             # Push an empty line to flush ongoing I/O in subprocess.
-            _LOG.error('')
+            print()
 
             # Surround the error message with newlines to make it stand out.
-            _LOG.error('')
+            print()
             _LOG.error('Event while running: %s', event_description)
-            _LOG.error('')
+            print()
 
             self.function.cancel()
             self._transition(State.INTERRUPTED)
diff --git a/pw_watch/py/pw_watch/watch.py b/pw_watch/py/pw_watch/watch.py
index fe495ef..c7269e7 100755
--- a/pw_watch/py/pw_watch/watch.py
+++ b/pw_watch/py/pw_watch/watch.py
@@ -37,48 +37,28 @@
 
 import argparse
 from dataclasses import dataclass
-import errno
-from itertools import zip_longest
 import logging
 import os
 from pathlib import Path
-import re
 import shlex
 import subprocess
 import sys
 import threading
-from threading import Thread
-from typing import (
-    Iterable,
-    List,
-    NamedTuple,
-    NoReturn,
-    Optional,
-    Sequence,
-    Tuple,
-)
-
-import httpwatcher  # type: ignore
+from typing import (Iterable, List, NamedTuple, NoReturn, Optional, Sequence,
+                    Tuple)
 
 from watchdog.events import FileSystemEventHandler  # type: ignore[import]
 from watchdog.observers import Observer  # type: ignore[import]
 
-from prompt_toolkit.formatted_text.base import OneStyleAndTextTuple
-from prompt_toolkit.formatted_text import StyleAndTextTuples
-
 import pw_cli.branding
 import pw_cli.color
 import pw_cli.env
-import pw_cli.log
 import pw_cli.plugins
-import pw_console.python_logging
 
-from pw_watch.watch_app import WatchApp
 from pw_watch.debounce import DebouncedFunction, Debouncer
 
 _COLOR = pw_cli.color.colors()
-_LOG = logging.getLogger('pw_watch')
-_NINJA_LOG = logging.getLogger('pw_watch_ninja_output')
+_LOG = logging.getLogger(__name__)
 _ERRNO_INOTIFY_LIMIT_REACHED = 28
 
 # Suppress events under 'fsevents', generated by watchdog on every file
@@ -110,8 +90,6 @@
                  ░  ░ ░       ░  ░
 """
 
-_FULLSCREEN_STATUS_COLUMN_WIDTH = 10
-
 
 # TODO(keir): Figure out a better strategy for exiting. The problem with the
 # watcher is that doing a "clean exit" is slow. However, by directly exiting,
@@ -147,67 +125,34 @@
 
     Returns true for ignored files that were manually added to a repo.
     """
-    file = file.resolve()
-    directory = file.parent
-
-    # Run the Git command from file's parent so that the correct repo is used.
-    while True:
-        try:
-            returncode = subprocess.run(
-                ['git', 'check-ignore', '--quiet', '--no-index', file],
-                stdout=subprocess.DEVNULL,
-                stderr=subprocess.DEVNULL,
-                cwd=directory).returncode
-            return returncode in (0, 128)
-        except FileNotFoundError:
-            # If the directory no longer exists, try parent directories until
-            # an existing directory is found or all directories have been
-            # checked. This approach makes it possible to check if a deleted
-            # path is ignored in the repo it was originally created in.
-            if directory == directory.parent:
-                return False
-
-            directory = directory.parent
+    returncode = subprocess.run(
+        ['git', 'check-ignore', '--quiet', '--no-index', file],
+        stdout=subprocess.DEVNULL,
+        stderr=subprocess.DEVNULL,
+        cwd=file.parent).returncode
+    return returncode in (0, 128)
 
 
 class PigweedBuildWatcher(FileSystemEventHandler, DebouncedFunction):
     """Process filesystem events and launch builds if necessary."""
-    # pylint: disable=too-many-instance-attributes
-    NINJA_BUILD_STEP = re.compile(
-        r'^\[(?P<step>[0-9]+)/(?P<total_steps>[0-9]+)\] (?P<action>.*)$')
-
     def __init__(
         self,
-        build_commands: Sequence[BuildCommand],
         patterns: Sequence[str] = (),
         ignore_patterns: Sequence[str] = (),
+        build_commands: Sequence[BuildCommand] = (),
         charset: WatchCharset = _ASCII_CHARSET,
         restart: bool = True,
-        jobs: int = None,
-        fullscreen: bool = False,
-        banners: bool = True,
     ):
         super().__init__()
 
-        self.banners = banners
-        self.status_message: Optional[OneStyleAndTextTuple] = None
-        self.result_message: Optional[StyleAndTextTuples] = None
-        self.current_stdout = ''
-        self.current_build_step = ''
-        self.current_build_percent = 0.0
-        self.current_build_errors = 0
         self.patterns = patterns
         self.ignore_patterns = ignore_patterns
         self.build_commands = build_commands
         self.charset: WatchCharset = charset
 
         self.restart_on_changes = restart
-        self.fullscreen_enabled = fullscreen
-        self.watch_app: Optional[WatchApp] = None
         self._current_build: subprocess.Popen
 
-        self._extra_ninja_args = [] if jobs is None else [f'-j{jobs}']
-
         self.debouncer = Debouncer(self)
 
         # Track state of a build. These need to be members instead of locals
@@ -215,23 +160,15 @@
         self.matching_path: Optional[Path] = None
         self.builds_succeeded: List[bool] = []
 
-        if not self.fullscreen_enabled:
-            self.wait_for_keypress_thread = threading.Thread(
-                None, self._wait_for_enter)
-            self.wait_for_keypress_thread.start()
-
-    def rebuild(self):
-        """ Rebuild command triggered from watch app."""
-        self._current_build.terminate()
-        self._current_build.wait()
-        self.debouncer.press('Manual build requested')
+        self.wait_for_keypress_thread = threading.Thread(
+            None, self._wait_for_enter)
+        self.wait_for_keypress_thread.start()
 
     def _wait_for_enter(self) -> NoReturn:
         try:
             while True:
                 _ = input()
-                self._current_build.terminate()
-                self._current_build.wait()
+                self._current_build.kill()
 
                 self.debouncer.press('Manual build requested...')
         # Ctrl-C on Unix generates KeyboardInterrupt
@@ -270,18 +207,8 @@
         if self.matching_path is None:
             self.matching_path = matching_path
 
-        log_message = f'File change detected: {os.path.relpath(matching_path)}'
-        if self.restart_on_changes:
-            if self.fullscreen_enabled and self.watch_app:
-                self.watch_app.rebuild_on_filechange()
-            self.debouncer.press(f'{log_message} Triggering build...')
-        else:
-            _LOG.info('%s ; not rebuilding', log_message)
-
-    def _clear_screen(self) -> None:
-        if not self.fullscreen_enabled:
-            print('\033c', end='')  # TODO(pwbug/38): Not Windows compatible.
-            sys.stdout.flush()
+        self.debouncer.press(
+            f'File change detected: {os.path.relpath(matching_path)}')
 
     # Implementation of DebouncedFunction.run()
     #
@@ -292,25 +219,14 @@
         """Run all the builds in serial and capture pass/fail for each."""
 
         # Clear the screen and show a banner indicating the build is starting.
-        self._clear_screen()
-
-        if self.fullscreen_enabled:
-            self.create_result_message()
-            _LOG.info(
-                _COLOR.green(
-                    'Watching for changes. Ctrl-d to exit; enter to rebuild'))
-        else:
-            for line in pw_cli.branding.banner().splitlines():
-                _LOG.info(line)
-            _LOG.info(
-                _COLOR.green(
-                    '  Watching for changes. Ctrl-C to exit; enter to rebuild')
-            )
-        _LOG.info('')
+        print('\033c', end='')  # TODO(pwbug/38): Not Windows compatible.
+        print(pw_cli.branding.banner())
+        print(
+            _COLOR.green(
+                '  Watching for changes. Ctrl-C to exit; enter to rebuild'))
+        print()
         _LOG.info('Change detected: %s', self.matching_path)
 
-        self._clear_screen()
-
         self.builds_succeeded = []
         num_builds = len(self.build_commands)
         _LOG.info('Starting build with %d directories', num_builds)
@@ -318,146 +234,32 @@
         env = os.environ.copy()
         # Force colors in Pigweed subcommands run through the watcher.
         env['PW_USE_COLOR'] = '1'
-        # Force Ninja to output ANSI colors
-        env['CLICOLOR_FORCE'] = '1'
 
         for i, cmd in enumerate(self.build_commands, 1):
-            index = f'[{i}/{num_builds}]'
-            self.builds_succeeded.append(self._run_build(index, cmd, env))
-            if self.builds_succeeded[-1]:
+            _LOG.info('[%d/%d] Starting build: %s', i, num_builds, cmd)
+
+            # Run the build. Put a blank before/after for visual separation.
+            print()
+            self._current_build = subprocess.Popen(
+                ['ninja', '-C', *cmd.args()], env=env)
+            returncode = self._current_build.wait()
+            print()
+
+            build_ok = (returncode == 0)
+            if build_ok:
                 level = logging.INFO
                 tag = '(OK)'
             else:
                 level = logging.ERROR
                 tag = '(FAIL)'
-
-            _LOG.log(level, '%s Finished build: %s %s', index, cmd, tag)
-            self.create_result_message()
-
-    def create_result_message(self):
-        if not self.fullscreen_enabled:
-            return
-
-        self.result_message = []
-        first_building_target_found = False
-        for (succeeded, command) in zip_longest(self.builds_succeeded,
-                                                self.build_commands):
-            if succeeded:
-                self.result_message.append(
-                    ('class:theme-fg-green',
-                     'OK'.rjust(_FULLSCREEN_STATUS_COLUMN_WIDTH)))
-            elif succeeded is None and not first_building_target_found:
-                first_building_target_found = True
-                self.result_message.append(
-                    ('class:theme-fg-yellow',
-                     'Building'.rjust(_FULLSCREEN_STATUS_COLUMN_WIDTH)))
-            elif first_building_target_found:
-                self.result_message.append(
-                    ('', ''.rjust(_FULLSCREEN_STATUS_COLUMN_WIDTH)))
-            else:
-                self.result_message.append(
-                    ('class:theme-fg-red',
-                     'Failed'.rjust(_FULLSCREEN_STATUS_COLUMN_WIDTH)))
-            self.result_message.append(('', f'  {command}\n'))
-
-    def _run_build(self, index: str, cmd: BuildCommand, env: dict) -> bool:
-        # Make sure there is a build.ninja file for Ninja to use.
-        build_ninja = cmd.build_dir / 'build.ninja'
-        if not build_ninja.exists():
-            # If this is a CMake directory, prompt the user to re-run CMake.
-            if cmd.build_dir.joinpath('CMakeCache.txt').exists():
-                _LOG.error('%s %s does not exist; re-run CMake to generate it',
-                           index, build_ninja)
-                return False
-
-            _LOG.warning('%s %s does not exist; running gn gen %s', index,
-                         build_ninja, cmd.build_dir)
-            if not self._execute_command(['gn', 'gen', cmd.build_dir], env):
-                return False
-
-        command = ['ninja', *self._extra_ninja_args, '-C', *cmd.args()]
-        _LOG.info('%s Starting build: %s', index,
-                  ' '.join(shlex.quote(arg) for arg in command))
-
-        return self._execute_command(command, env)
-
-    def _execute_command(self, command: list, env: dict) -> bool:
-        """Runs a command with a blank before/after for visual separation."""
-        self.current_build_errors = 0
-        self.status_message = (
-            'class:theme-fg-yellow',
-            'Building'.rjust(_FULLSCREEN_STATUS_COLUMN_WIDTH))
-        if self.fullscreen_enabled:
-            return self._execute_command_watch_app(command, env)
-        print()
-        self._current_build = subprocess.Popen(command, env=env)
-        returncode = self._current_build.wait()
-        print()
-        return returncode == 0
-
-    def _execute_command_watch_app(self, command: list, env: dict) -> bool:
-        """Runs a command with and outputs the logs."""
-        if not self.watch_app:
-            return False
-        self.current_stdout = ''
-        returncode = None
-        with subprocess.Popen(command,
-                              env=env,
-                              stdout=subprocess.PIPE,
-                              stderr=subprocess.STDOUT,
-                              errors='replace') as proc:
-            self._current_build = proc
-
-            # Empty line at the start.
-            _NINJA_LOG.info('')
-            while returncode is None:
-                if not proc.stdout:
-                    continue
-
-                output = proc.stdout.readline()
-                self.current_stdout += output
-
-                line_match_result = self.NINJA_BUILD_STEP.match(output)
-                if line_match_result:
-                    matches = line_match_result.groupdict()
-                    self.current_build_step = line_match_result.group(0)
-                    self.current_build_percent = float(
-                        int(matches.get('step', 0)) /
-                        int(matches.get('total_steps', 1)))
-
-                elif output.startswith(WatchApp.NINJA_FAILURE_TEXT):
-                    _NINJA_LOG.critical(output.strip())
-                    self.current_build_errors += 1
-
-                else:
-                    # Mypy output mixes character encoding in its colored output
-                    # due to it's use of the curses module retrieving the 'sgr0'
-                    # (or exit_attribute_mode) capability from the host
-                    # machine's terminfo database.
-                    #
-                    # This can result in this sequence ending up in STDOUT as
-                    # b'\x1b(B\x1b[m'. (B tells terminals to interpret text as
-                    # USASCII encoding but will appear in prompt_toolkit as a B
-                    # character.
-                    #
-                    # The following replace calls will strip out those
-                    # instances.
-                    _NINJA_LOG.info(
-                        output.replace('\x1b(B\x1b[m',
-                                       '').replace('\x1b[1m', '').strip())
-                self.watch_app.redraw_ui()
-
-                returncode = proc.poll()
-            # Empty line at the end.
-            _NINJA_LOG.info('')
-
-        return returncode == 0
+            _LOG.log(level, '[%d/%d] Finished build: %s %s', i, num_builds,
+                     cmd, tag)
+            self.builds_succeeded.append(build_ok)
 
     # Implementation of DebouncedFunction.cancel()
     def cancel(self) -> bool:
         if self.restart_on_changes:
-            self._current_build.terminate()
-            self._current_build.wait()
+            self._current_build.kill()
             return True
 
         return False
@@ -466,59 +268,41 @@
     def on_complete(self, cancelled: bool = False) -> None:
         # First, use the standard logging facilities to report build status.
         if cancelled:
-            self.status_message = (
-                '', 'Cancelled'.rjust(_FULLSCREEN_STATUS_COLUMN_WIDTH))
             _LOG.error('Finished; build was interrupted')
         elif all(self.builds_succeeded):
-            self.status_message = (
-                'class:theme-fg-green',
-                'Succeeded'.rjust(_FULLSCREEN_STATUS_COLUMN_WIDTH))
             _LOG.info('Finished; all successful')
         else:
-            self.status_message = (
-                'class:theme-fg-red',
-                'Failed'.rjust(_FULLSCREEN_STATUS_COLUMN_WIDTH))
             _LOG.info('Finished; some builds failed')
 
-        # Show individual build results for fullscreen app
-        if self.fullscreen_enabled:
-            self.create_result_message()
-        # For non-fullscreen pw watch
+        # Then, show a more distinct colored banner.
+        if not cancelled:
+            # Write out build summary table so you can tell which builds passed
+            # and which builds failed.
+            print()
+            print(' .------------------------------------')
+            print(' |')
+            for (succeeded, cmd) in zip(self.builds_succeeded,
+                                        self.build_commands):
+                slug = (self.charset.slug_ok
+                        if succeeded else self.charset.slug_fail)
+                print(f' |   {slug}  {cmd}')
+            print(' |')
+            print(" '------------------------------------")
         else:
-            # Show a more distinct colored banner.
-            if not cancelled:
-                # Write out build summary table so you can tell which builds
-                # passed and which builds failed.
-                _LOG.info('')
-                _LOG.info(' .------------------------------------')
-                _LOG.info(' |')
-                for (succeeded, cmd) in zip(self.builds_succeeded,
-                                            self.build_commands):
-                    slug = (self.charset.slug_ok
-                            if succeeded else self.charset.slug_fail)
-                    _LOG.info(' |   %s  %s', slug, cmd)
-                _LOG.info(' |')
-                _LOG.info(" '------------------------------------")
-            else:
-                # Build was interrupted.
-                _LOG.info('')
-                _LOG.info(' .------------------------------------')
-                _LOG.info(' |')
-                _LOG.info(' |  %s- interrupted', self.charset.slug_fail)
-                _LOG.info(' |')
-                _LOG.info(" '------------------------------------")
+            # Build was interrupted.
+            print()
+            print(' .------------------------------------')
+            print(' |')
+            print(' |  ', self.charset.slug_fail, '- interrupted')
+            print(' |')
+            print(" '------------------------------------")
 
-            # Show a large color banner for the overall result.
-            if self.banners:
-                if all(self.builds_succeeded) and not cancelled:
-                    for line in _PASS_MESSAGE.splitlines():
-                        _LOG.info(_COLOR.green(line))
-                else:
-                    for line in _FAIL_MESSAGE.splitlines():
-                        _LOG.info(_COLOR.red(line))
+        # Show a large color banner so it is obvious what the overall result is.
+        if all(self.builds_succeeded) and not cancelled:
+            print(_COLOR.green(_PASS_MESSAGE))
+        else:
+            print(_COLOR.red(_FAIL_MESSAGE))
 
-        if self.watch_app:
-            self.watch_app.redraw_ui()
         self.matching_path = None
 
     # Implementation of DebouncedFunction.on_keyboard_interrupt()
@@ -531,7 +315,6 @@
     '*.bloaty',
     '*.c',
     '*.cc',
-    '*.css',
     '*.cpp',
     '*.cmake',
     'CMakeLists.txt',
@@ -546,8 +329,6 @@
     '*.proto',
     '*.py',
     '*.rst',
-    '*.s',
-    '*.S',
 )
 
 
@@ -580,9 +361,9 @@
         default=[],
         help=('Automatically locate a build directory and build these '
               'targets. For example, `host docs` searches for a Ninja '
-              'build directory at out/ and builds the `host` and `docs` '
-              'targets. To specify one or more directories, ust the '
-              '-C / --build_directory option.'))
+              'build directory (starting with out/) and builds the '
+              '`host` and `docs` targets. To specify one or more '
+              'directories, ust the -C / --build_directory option.'))
     parser.add_argument(
         '-C',
         '--build_directory',
@@ -594,45 +375,6 @@
         help=('Specify a build directory and optionally targets to '
               'build. `pw watch -C out tgt` is equivalent to `ninja '
               '-C out tgt`'))
-    parser.add_argument(
-        '--serve-docs',
-        dest='serve_docs',
-        action='store_true',
-        default=False,
-        help='Start a webserver for docs on localhost. The port for this '
-        ' webserver can be set with the --serve-docs-port option. '
-        ' Defaults to http://127.0.0.1:8000')
-    parser.add_argument(
-        '--serve-docs-port',
-        dest='serve_docs_port',
-        type=int,
-        default=8000,
-        help='Set the port for the docs webserver. Default to 8000.')
-
-    parser.add_argument(
-        '--serve-docs-path',
-        dest='serve_docs_path',
-        type=Path,
-        default="docs/gen/docs",
-        help='Set the path for the docs to serve. Default to docs/gen/docs'
-        ' in the build directory.')
-    parser.add_argument(
-        '-j',
-        '--jobs',
-        type=int,
-        help="Number of cores to use; defaults to Ninja's default")
-    parser.add_argument('-f',
-                        '--fullscreen',
-                        action='store_true',
-                        default=False,
-                        help='Use a fullscreen interface.')
-    parser.add_argument('--debug-logging',
-                        action='store_true',
-                        help='Enable debug logging.')
-    parser.add_argument('--no-banners',
-                        dest='banners',
-                        action='store_false',
-                        help='Hide pass/fail banners.')
 
 
 def _exit(code: int) -> NoReturn:
@@ -648,31 +390,21 @@
 def _exit_due_to_interrupt() -> NoReturn:
     # To keep the log lines aligned with each other in the presence of
     # a '^C' from the keyboard interrupt, add a newline before the log.
+    print()
+    print()
     _LOG.info('Got Ctrl-C; exiting...')
     _exit(0)
 
 
-def _exit_due_to_inotify_watch_limit():
+def _exit_due_to_inotify_limit():
     # Show information and suggested commands in OSError: inotify limit reached.
-    _LOG.error('Inotify watch limit reached: run this in your terminal if '
-               'you are in Linux to temporarily increase inotify limit.  \n')
-    _LOG.info(
+    _LOG.error('Inotify limit reached: run this in your terminal if you '
+               'are in Linux to temporarily increase inotify limit.  \n')
+    print(
         _COLOR.green('        sudo sysctl fs.inotify.max_user_watches='
                      '$NEW_LIMIT$\n'))
-    _LOG.info('  Change $NEW_LIMIT$ with an integer number, '
-              'e.g., 20000 should be enough.')
-    _exit(0)
-
-
-def _exit_due_to_inotify_instance_limit():
-    # Show information and suggested commands in OSError: inotify limit reached.
-    _LOG.error('Inotify instance limit reached: run this in your terminal if '
-               'you are in Linux to temporarily increase inotify limit.  \n')
-    _LOG.info(
-        _COLOR.green('        sudo sysctl fs.inotify.max_user_instances='
-                     '$NEW_LIMIT$\n'))
-    _LOG.info('  Change $NEW_LIMIT$ with an integer number, '
-              'e.g., 20000 should be enough.')
+    print('  Change $NEW_LIMIT$ with an integer number, '
+          'e.g., 1000 should be enough.')
     _exit(0)
 
 
@@ -780,21 +512,24 @@
     return exclude_list
 
 
-def watch_setup(
-    default_build_targets: List[str],
-    build_directories: List[str],
-    patterns: str,
-    ignore_patterns_string: str,
-    exclude_list: List[Path],
-    restart: bool,
-    jobs: Optional[int],
-    serve_docs: bool,
-    serve_docs_port: int,
-    serve_docs_path: Path,
-    fullscreen: bool,
-    banners: bool,
-    # pylint: disable=too-many-arguments
-) -> Tuple[str, PigweedBuildWatcher, List[Path]]:
+def _find_build_dir(default_build_dir: Path = Path('out')) -> Optional[Path]:
+    """Searches for a build directory, returning the first it finds."""
+    # Give priority to out/, then something under out/.
+    if default_build_dir.joinpath('build.ninja').exists():
+        return default_build_dir
+
+    for path in default_build_dir.glob('**/build.ninja'):
+        return path.parent
+
+    for path in Path.cwd().glob('**/build.ninja'):
+        return path.parent
+
+    return None
+
+
+def watch(default_build_targets: List[str], build_directories: List[str],
+          patterns: str, ignore_patterns_string: str, exclude_list: List[Path],
+          restart: bool):
     """Watches files and runs Ninja commands when they change."""
     _LOG.info('Starting Pigweed build watcher')
 
@@ -807,23 +542,22 @@
 
     # Preset exclude list for pigweed directory.
     exclude_list += get_common_excludes()
-    # Add build directories to the exclude list.
-    exclude_list.extend(
-        Path(build_dir[0]).resolve() for build_dir in build_directories)
 
     build_commands = [
         BuildCommand(Path(build_dir[0]), tuple(build_dir[1:]))
         for build_dir in build_directories
     ]
 
-    # If no build directory was specified, check for out/build.ninja.
+    # If no build directory was specified, search the tree for a build.ninja.
     if default_build_targets or not build_directories:
+        build_dir = _find_build_dir()
+
         # Make sure we found something; if not, bail.
-        if not Path('out').exists():
+        if build_dir is None:
             _die("No build dirs found. Did you forget to run 'gn gen out'?")
 
         build_commands.append(
-            BuildCommand(Path('out'), tuple(default_build_targets)))
+            BuildCommand(build_dir, tuple(default_build_targets)))
 
     # Verify that the build output directories exist.
     for i, build_target in enumerate(build_commands, 1):
@@ -835,22 +569,6 @@
 
     _LOG.debug('Patterns: %s', patterns)
 
-    if serve_docs:
-
-        def _serve_docs():
-            # Disable logs from httpwatcher and deps
-            logging.getLogger('httpwatcher').setLevel(logging.CRITICAL)
-            logging.getLogger('tornado').setLevel(logging.CRITICAL)
-
-            docs_path = build_commands[0].build_dir.joinpath(
-                serve_docs_path.joinpath('html'))
-            httpwatcher.watch(docs_path,
-                              host="127.0.0.1",
-                              port=serve_docs_port)
-
-        # Spin up an httpwatcher in a new thread since it blocks
-        threading.Thread(None, _serve_docs, "httpwatcher").start()
-
     # Try to make a short display path for the watched directory that has
     # "$HOME" instead of the full home directory. This is nice for users
     # who have deeply nested home directories.
@@ -867,21 +585,13 @@
         charset = _ASCII_CHARSET
 
     event_handler = PigweedBuildWatcher(
-        build_commands=build_commands,
         patterns=patterns.split(_WATCH_PATTERN_DELIMITER),
         ignore_patterns=ignore_patterns,
+        build_commands=build_commands,
         charset=charset,
         restart=restart,
-        jobs=jobs,
-        fullscreen=fullscreen,
-        banners=banners,
     )
-    return path_to_log, event_handler, exclude_list
 
-
-def watch(path_to_log: Path, event_handler: PigweedBuildWatcher,
-          exclude_list: List[Path]):
-    """Watches files and runs Ninja commands when they change."""
     try:
         # It can take awhile to configure the filesystem watcher, so have the
         # message reflect that with the "...". Run inside the try: to
@@ -914,10 +624,9 @@
         _exit_due_to_interrupt()
     except OSError as err:
         if err.args[0] == _ERRNO_INOTIFY_LIMIT_REACHED:
-            _exit_due_to_inotify_watch_limit()
-        if err.errno == errno.EMFILE:
-            _exit_due_to_inotify_instance_limit()
-        raise err
+            _exit_due_to_inotify_limit()
+        else:
+            raise err
 
     _LOG.critical('Should never get here')
     observer.join()
@@ -929,52 +638,7 @@
         description=__doc__,
         formatter_class=argparse.RawDescriptionHelpFormatter)
     add_parser_arguments(parser)
-    args = parser.parse_args()
-
-    path_to_log, event_handler, exclude_list = watch_setup(
-        default_build_targets=args.default_build_targets,
-        build_directories=args.build_directories,
-        patterns=args.patterns,
-        ignore_patterns_string=args.ignore_patterns_string,
-        exclude_list=args.exclude_list,
-        restart=args.restart,
-        jobs=args.jobs,
-        serve_docs=args.serve_docs,
-        serve_docs_port=args.serve_docs_port,
-        serve_docs_path=args.serve_docs_path,
-        fullscreen=args.fullscreen,
-        banners=args.banners,
-    )
-
-    if args.fullscreen:
-        watch_logfile = (pw_console.python_logging.create_temp_log_file(
-            prefix=__package__))
-        pw_cli.log.install(
-            level=logging.DEBUG,
-            use_color=True,
-            hide_timestamp=False,
-            log_file=watch_logfile,
-        )
-        pw_console.python_logging.setup_python_logging(
-            last_resort_filename=watch_logfile)
-
-        watch_thread = Thread(target=watch,
-                              args=(path_to_log, event_handler, exclude_list),
-                              daemon=True)
-        watch_thread.start()
-        watch_app = WatchApp(event_handler=event_handler,
-                             debug_logging=args.debug_logging,
-                             log_file_name=watch_logfile)
-
-        event_handler.watch_app = watch_app
-        watch_app.run()
-    else:
-        pw_cli.log.install(
-            level=logging.DEBUG if args.debug_logging else logging.INFO,
-            use_color=True,
-            hide_timestamp=False,
-        )
-        watch(Path(path_to_log), event_handler, exclude_list)
+    watch(**vars(parser.parse_args()))
 
 
 if __name__ == '__main__':
diff --git a/pw_watch/py/pw_watch/watch_app.py b/pw_watch/py/pw_watch/watch_app.py
deleted file mode 100644
index 61f7fd9..0000000
--- a/pw_watch/py/pw_watch/watch_app.py
+++ /dev/null
@@ -1,372 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-""" Prompt toolkit application for pw watch. """
-
-import asyncio
-import logging
-from pathlib import Path
-import re
-import sys
-from typing import List, NoReturn, Optional
-
-from prompt_toolkit.application import Application
-from prompt_toolkit.clipboard.pyperclip import PyperclipClipboard
-from prompt_toolkit.filters import Condition
-from prompt_toolkit.history import (
-    FileHistory,
-    History,
-    ThreadedHistory,
-)
-from prompt_toolkit.key_binding import (
-    KeyBindings,
-    KeyBindingsBase,
-    merge_key_bindings,
-)
-from prompt_toolkit.layout import (
-    Dimension,
-    DynamicContainer,
-    Float,
-    FloatContainer,
-    FormattedTextControl,
-    HSplit,
-    Layout,
-    Window,
-)
-from prompt_toolkit.layout.controls import BufferControl
-from prompt_toolkit.styles import DynamicStyle, merge_styles, Style
-from prompt_toolkit.formatted_text import StyleAndTextTuples
-
-from pw_console.console_app import get_default_colordepth
-from pw_console.console_prefs import ConsolePrefs
-from pw_console.get_pw_console_app import PW_CONSOLE_APP_CONTEXTVAR
-from pw_console.log_pane import LogPane
-from pw_console.plugin_mixin import PluginMixin
-from pw_console.quit_dialog import QuitDialog
-import pw_console.style
-import pw_console.widgets.border
-from pw_console.window_manager import WindowManager
-
-_NINJA_LOG = logging.getLogger('pw_watch_ninja_output')
-_LOG = logging.getLogger('pw_watch')
-
-
-class WatchWindowManager(WindowManager):
-    def update_root_container_body(self):
-        self.application.window_manager_container = (
-            self.create_root_container())
-
-
-class WatchApp(PluginMixin):
-    """Pigweed Watch main window application."""
-    # pylint: disable=too-many-instance-attributes
-
-    NINJA_FAILURE_TEXT = '\033[31mFAILED: '
-
-    NINJA_BUILD_STEP = re.compile(
-        r"^\[(?P<step>[0-9]+)/(?P<total_steps>[0-9]+)\] (?P<action>.*)$")
-
-    def __init__(self,
-                 event_handler,
-                 debug_logging: bool = False,
-                 log_file_name: Optional[str] = None):
-
-        self.event_handler = event_handler
-
-        self.external_logfile: Optional[Path] = (Path(log_file_name)
-                                                 if log_file_name else None)
-        self.color_depth = get_default_colordepth()
-
-        # Necessary for some of pw_console's window manager features to work
-        # such as mouse drag resizing.
-        PW_CONSOLE_APP_CONTEXTVAR.set(self)  # type: ignore
-
-        self.prefs = ConsolePrefs()
-
-        self.quit_dialog = QuitDialog(self, self.exit)  # type: ignore
-
-        self.search_history_filename = self.prefs.search_history
-        # History instance for search toolbars.
-        self.search_history: History = ThreadedHistory(
-            FileHistory(str(self.search_history_filename)))
-
-        self.window_manager = WatchWindowManager(self)
-
-        pw_console.python_logging.setup_python_logging()
-
-        self._build_error_count = 0
-        self._errors_in_output = False
-
-        self.ninja_log_pane = LogPane(application=self,
-                                      pane_title='Pigweed Watch')
-        self.ninja_log_pane.add_log_handler(_NINJA_LOG, level_name='INFO')
-        self.ninja_log_pane.add_log_handler(
-            _LOG, level_name=('DEBUG' if debug_logging else 'INFO'))
-        # Set python log format to just the message itself.
-        self.ninja_log_pane.log_view.log_store.formatter = logging.Formatter(
-            '%(message)s')
-        self.ninja_log_pane.table_view = False
-        # Enable line wrapping
-        self.ninja_log_pane.toggle_wrap_lines()
-        # Blank right side toolbar text
-        self.ninja_log_pane._pane_subtitle = ' '
-        self.ninja_log_view = self.ninja_log_pane.log_view
-
-        # Make tab and shift-tab search for next and previous error
-        next_error_bindings = KeyBindings()
-
-        @next_error_bindings.add('s-tab')
-        def _previous_error(_event):
-            self.jump_to_error(backwards=True)
-
-        @next_error_bindings.add('tab')
-        def _next_error(_event):
-            self.jump_to_error()
-
-        existing_log_bindings: Optional[KeyBindingsBase] = (
-            self.ninja_log_pane.log_content_control.key_bindings)
-
-        key_binding_list: List[KeyBindingsBase] = []
-        if existing_log_bindings:
-            key_binding_list.append(existing_log_bindings)
-        key_binding_list.append(next_error_bindings)
-        self.ninja_log_pane.log_content_control.key_bindings = (
-            merge_key_bindings(key_binding_list))
-
-        self.window_manager.add_pane(self.ninja_log_pane)
-
-        self.window_manager_container = (
-            self.window_manager.create_root_container())
-
-        self.status_bar_border_style = 'class:command-runner-border'
-
-        self.root_container = FloatContainer(
-            HSplit([
-                pw_console.widgets.border.create_border(
-                    HSplit([
-                        # The top toolbar.
-                        Window(
-                            content=FormattedTextControl(
-                                self.get_statusbar_text),
-                            height=Dimension.exact(1),
-                            style='class:toolbar_inactive',
-                        ),
-                        # Result Toolbar.
-                        Window(
-                            content=FormattedTextControl(
-                                self.get_resultbar_text),
-                            height=lambda: len(self.event_handler.
-                                               build_commands),
-                            style='class:toolbar_inactive',
-                        ),
-                    ]),
-                    border_style=lambda: self.status_bar_border_style,
-                    base_style='class:toolbar_inactive',
-                    left_margin_columns=1,
-                    right_margin_columns=1,
-                ),
-                # The main content.
-                DynamicContainer(lambda: self.window_manager_container),
-            ]),
-            floats=[
-                Float(
-                    content=self.quit_dialog,
-                    top=2,
-                    left=2,
-                ),
-            ],
-        )
-
-        key_bindings = KeyBindings()
-
-        @key_bindings.add('enter', filter=self.input_box_not_focused())
-        def _run_build(_event):
-            "Rebuild."
-            self.run_build()
-
-        register = self.prefs.register_keybinding
-
-        @register('global.exit-no-confirmation', key_bindings)
-        def _quit_no_confirm(_event):
-            """Quit without confirmation."""
-            _LOG.info('Got quit signal; exiting...')
-            self.exit(0)
-
-        @register('global.exit-with-confirmation', key_bindings)
-        def _quit_with_confirm(_event):
-            """Quit with confirmation dialog."""
-            self.quit_dialog.open_dialog()
-
-        self.key_bindings = merge_key_bindings([
-            self.window_manager.key_bindings,
-            key_bindings,
-        ])
-
-        self.current_theme = pw_console.style.generate_styles(
-            self.prefs.ui_theme)
-        self.current_theme = merge_styles([
-            self.current_theme,
-            Style.from_dict({'search': 'bg:ansired ansiblack'}),
-        ])
-
-        self.layout = Layout(self.root_container,
-                             focused_element=self.ninja_log_pane)
-
-        self.application: Application = Application(
-            layout=self.layout,
-            key_bindings=self.key_bindings,
-            mouse_support=True,
-            color_depth=self.color_depth,
-            clipboard=PyperclipClipboard(),
-            style=DynamicStyle(lambda: merge_styles([
-                self.current_theme,
-            ])),
-            full_screen=True,
-        )
-
-        self.plugin_init(
-            plugin_callback=self.check_build_status,
-            plugin_callback_frequency=0.5,
-            plugin_logger_name='pw_watch_stdout_checker',
-        )
-
-    def jump_to_error(self, backwards: bool = False) -> None:
-        if not self.ninja_log_pane.log_view.search_text:
-            self.ninja_log_pane.log_view.set_search_regex(
-                '^FAILED: ', False, None)
-        if backwards:
-            self.ninja_log_pane.log_view.search_backwards()
-        else:
-            self.ninja_log_pane.log_view.search_forwards()
-        self.ninja_log_pane.log_view.log_screen.reset_logs(
-            log_index=self.ninja_log_pane.log_view.log_index)
-
-        self.ninja_log_pane.log_view.move_selected_line_to_top()
-
-    def update_menu_items(self):
-        """Required by the Window Manager Class."""
-
-    def redraw_ui(self):
-        """Redraw the prompt_toolkit UI."""
-        if hasattr(self, 'application'):
-            self.application.invalidate()
-
-    def focus_on_container(self, pane):
-        """Set application focus to a specific container."""
-        self.application.layout.focus(pane)
-
-    def focused_window(self):
-        """Return the currently focused window."""
-        return self.application.layout.current_window
-
-    def command_runner_is_open(self) -> bool:
-        # pylint: disable=no-self-use
-        return False
-
-    def clear_ninja_log(self) -> None:
-        self.ninja_log_view.log_store.clear_logs()
-        self.ninja_log_view._restart_filtering()  # pylint: disable=protected-access
-        self.ninja_log_view.view_mode_changed()
-
-    def run_build(self):
-        """Manually trigger a rebuild."""
-        self.clear_ninja_log()
-        self.event_handler.rebuild()
-
-    def rebuild_on_filechange(self):
-        self.ninja_log_view.log_store.clear_logs()
-        self.ninja_log_view.view_mode_changed()
-
-    def get_statusbar_text(self):
-        status = self.event_handler.status_message
-        fragments = [('class:logo', 'Pigweed Watch')]
-        is_building = False
-        if status:
-            fragments = [status]
-            is_building = status[1].endswith('Building')
-        separator = ('', '  ')
-        self.status_bar_border_style = 'class:theme-fg-green'
-
-        if is_building:
-            percent = self.event_handler.current_build_percent
-            percent *= 100
-            fragments.append(separator)
-            fragments.append(('ansicyan', '{:.0f}%'.format(percent)))
-            self.status_bar_border_style = 'class:theme-fg-yellow'
-
-        if self.event_handler.current_build_errors > 0:
-            fragments.append(separator)
-            fragments.append(('', 'Errors:'))
-            fragments.append(
-                ('ansired', str(self.event_handler.current_build_errors)))
-            self.status_bar_border_style = 'class:theme-fg-red'
-
-        if is_building:
-            fragments.append(separator)
-            fragments.append(('', self.event_handler.current_build_step))
-
-        return fragments
-
-    def get_resultbar_text(self) -> StyleAndTextTuples:
-        result = self.event_handler.result_message
-        if not result:
-            result = [('', 'Loading...')]
-        return result
-
-    def exit(self, exit_code: int = 0) -> None:
-        log_file = self.external_logfile
-
-        def _really_exit(future: asyncio.Future) -> NoReturn:
-            if log_file:
-                # Print a message showing where logs were saved to.
-                print('Logs saved to: {}'.format(log_file.resolve()))
-            sys.exit(future.result())
-
-        if self.application.future:
-            self.application.future.add_done_callback(_really_exit)
-        self.application.exit(result=exit_code)
-
-    def check_build_status(self) -> bool:
-        if not self.event_handler.current_stdout:
-            return False
-
-        if self._errors_in_output:
-            return True
-
-        if self.event_handler.current_build_errors > self._build_error_count:
-            self._errors_in_output = True
-            self.jump_to_error()
-
-        return True
-
-    def run(self):
-        self.plugin_start()
-        # Run the prompt_toolkit application
-        self.application.run(set_exception_handler=True)
-
-    def input_box_not_focused(self) -> Condition:
-        """Condition checking the focused control is not a text input field."""
-        @Condition
-        def _test() -> bool:
-            """Check if the currently focused control is an input buffer.
-
-            Returns:
-                bool: True if the currently focused control is not a text input
-                    box. For example if the user presses enter when typing in
-                    the search box, return False.
-            """
-            return not isinstance(self.application.layout.current_control,
-                                  BufferControl)
-
-        return _test
diff --git a/pw_watch/py/pyproject.toml b/pw_watch/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/pw_watch/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/pw_watch/py/setup.cfg b/pw_watch/py/setup.cfg
deleted file mode 100644
index 504489c..0000000
--- a/pw_watch/py/setup.cfg
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = pw_watch
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Pigweed automatic builder
-
-[options]
-packages = find:
-zip_safe = False
-install_requires = httpwatcher; pw_cli; watchdog>=2.1.0
-
-[options.package_data]
-pw_watch = py.typed
diff --git a/pw_watch/py/setup.py b/pw_watch/py/setup.py
index e73a002..82f4c17 100644
--- a/pw_watch/py/setup.py
+++ b/pw_watch/py/setup.py
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2019 The Pigweed Authors
 #
 # 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
@@ -15,4 +15,22 @@
 
 import setuptools  # type: ignore
 
-setuptools.setup()  # Package definition in setup.cfg
+setuptools.setup(
+    name='pw_watch',
+    version='0.0.1',
+    author='Pigweed Authors',
+    author_email='pigweed-developers@googlegroups.com',
+    description='Pigweed automatic builder',
+    packages=setuptools.find_packages(),
+    package_data={'pw_watch': ['py.typed']},
+    zip_safe=False,
+    install_requires=[
+        'pw_cli',
+        # Fixes the watchdog version to 0.10.3, released 2020-06-25
+        # as versions later than this ignore the 'recursive' argument
+        # on MacOS. This was causing us to trigger on any file within
+        # the source tree, even those that should have been ignored.
+        # See https://github.com/gorakhargosh/watchdog/issues/771.
+        'watchdog==0.10.3',
+    ],
+)
diff --git a/pw_web_ui/BUILD b/pw_web_ui/BUILD
new file mode 100644
index 0000000..118fba4
--- /dev/null
+++ b/pw_web_ui/BUILD
@@ -0,0 +1,29 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+exports_files(["rollup.config.js"], visibility = ["//:__subpackages__"])
+
+alias(
+    name = "devserver",
+    actual = "//pw_web_ui/src/frontend:devserver",
+)
+
+alias(
+    name = "prodserver",
+    actual = "//pw_web_ui/src/frontend:prodserver",
+)
+
+alias(
+    name = "app_bundle",
+    actual = "//pw_web_ui/src/frontend:app_bundle",
+)
diff --git a/pw_web_ui/BUILD.bazel b/pw_web_ui/BUILD.bazel
deleted file mode 100644
index 1fe99a2..0000000
--- a/pw_web_ui/BUILD.bazel
+++ /dev/null
@@ -1,120 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
-load("@npm//@bazel/concatjs:index.bzl", "karma_web_test")
-load("@npm//@bazel/esbuild:index.bzl", "esbuild")
-load("@npm//@bazel/typescript:index.bzl", "ts_library", "ts_project")
-load("@npm//http-server:index.bzl", "http_server")
-load("//pw_protobuf_compiler/ts:ts_proto_collection.bzl", "ts_proto_collection")
-load("@rules_proto_grpc//js:defs.bzl", "js_proto_library")
-
-package(default_visibility = ["//visibility:public"])
-
-js_proto_library(
-    name = "rpc_protos_tspb",
-    protos = [
-        "//pw_rpc:echo_proto",
-    ],
-)
-
-ts_proto_collection(
-    name = "web_proto_collection",
-    js_proto_library = "@//pw_web_ui:rpc_protos_tspb",
-    proto_library = "@//pw_rpc:echo_proto",
-)
-
-ts_project(
-    name = "lib",
-    srcs = [
-        "index.ts",
-        "src/frontend/app.tsx",
-        "src/frontend/index.tsx",
-        "src/frontend/log.tsx",
-        "src/frontend/serial_log.tsx",
-        "src/transport/device_transport.ts",
-        "src/transport/serial_mock.ts",
-        "src/transport/web_serial_transport.ts",
-        "types/serial.d.ts",
-    ],
-    declaration = True,
-    source_map = True,
-    deps = [
-        ":web_proto_collection",
-        "//pw_hdlc/ts:pw_hdlc",
-        "//pw_rpc/ts:pw_rpc",
-        "//pw_status/ts:pw_status",
-        "@npm//:node_modules",
-    ],  # can't use fine-grained deps
-)
-
-js_library(
-    name = "pw_web_ui",
-    package_name = "@pigweed/pw_web_ui",
-    srcs = ["package.json"],
-    deps = [":lib"],
-)
-
-ts_library(
-    name = "web_ui_test_lib",
-    srcs = [
-        "src/transport/web_serial_transport_test.ts",
-    ],
-    deps = [
-        ":lib",
-        "@npm//rxjs",
-    ],
-)
-
-esbuild(
-    name = "web_ui_test_bundle",
-    entry_point = "src/transport/web_serial_transport_test.ts",
-    deps = [":web_ui_test_lib"],
-)
-
-esbuild(
-    name = "app_bundle",
-    args = {
-        "resolveExtensions": [
-            ".mjs",
-            ".js",
-        ],
-    },
-    entry_point = "src/frontend/index.tsx",
-    target = "es2021",
-    deps = [":lib"],
-)
-
-karma_web_test(
-    name = "web_ui_test",
-    srcs = [
-        ":web_ui_test_bundle",
-    ],
-)
-
-http_server(
-    name = "devserver",
-    args = ["pw_web_ui/"],
-    data = [
-        "index.html",
-        ":app_bundle",
-    ],
-)
-
-# needed for embedding into downstream projects
-filegroup(name = "pw_web_ui__contents")
-
-filegroup(name = "pw_web_ui__files")
-
-filegroup(name = "pw_web_ui__nested_node_modules")
diff --git a/pw_web_ui/OWNERS b/pw_web_ui/OWNERS
deleted file mode 100644
index e69de29..0000000
--- a/pw_web_ui/OWNERS
+++ /dev/null
diff --git a/pw_web_ui/index.html b/pw_web_ui/index.html
deleted file mode 100644
index e46873b..0000000
--- a/pw_web_ui/index.html
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
-Copyright 2020 The Pigweed Authors
-
-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
-
-    https://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.
--->
-<html>
-  <head>
-    <title>Pigweed Web UI</title>
-  </head>
-  <body style="background-color:#1a1c1e"></body>
-    <div id="react-root"></div>
-    <script src="app_bundle.js"></script>
-  </body>
-</html>
diff --git a/pw_web_ui/index.ts b/pw_web_ui/index.ts
deleted file mode 100644
index 8a34d2a..0000000
--- a/pw_web_ui/index.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-export {default as DeviceTransport} from './src/transport/device_transport';
-export * from './src/transport/web_serial_transport';
diff --git a/pw_web_ui/package.json b/pw_web_ui/package.json
deleted file mode 100644
index 7510557..0000000
--- a/pw_web_ui/package.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-  "name": "@pigweed/pw_web_ui",
-  "version": "1.0.0",
-  "main": "index.js",
-  "license": "MIT",
-  "dependencies": {
-    "@types/jasmine": "^3.9.0",
-    "@types/react-dom": "^17.0.9",
-    "http-server": "^13.0.2",
-    "jasmine": "^3.9.0",
-    "react-dom": "^17.0.2",
-    "rxjs": "^7.3.0"
-  }
-}
diff --git a/pw_web_ui/src/frontend/BUILD b/pw_web_ui/src/frontend/BUILD
new file mode 100644
index 0000000..03da0b0
--- /dev/null
+++ b/pw_web_ui/src/frontend/BUILD
@@ -0,0 +1,62 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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(default_visibility = ["//visibility:public"])
+
+load("@npm//@bazel/typescript:index.bzl", "ts_library", "ts_devserver")
+load("//pw_web_ui:web_bundle.bzl", "web_bundle")
+
+ts_library(
+    name = "app_lib",
+    srcs = [
+        "app.tsx",
+        "index.tsx",
+    ],
+    deps = [
+        "@npm//@types/react",
+        "@npm//react",
+        "@npm//@types/react-dom",
+        "@npm//react-dom",
+        "@npm//@material-ui/core",
+        "//pw_web_ui/src/transport:web_serial_transport_lib",
+    ],
+)
+
+web_bundle(
+    name = "app_bundle",
+    deps = [
+        ":app_lib",
+    ],
+    entry_point = "index.tsx",
+)
+
+ts_devserver(
+    # TODO(msoulanille): Use the devserver's bundler
+    # instead of serving the production bundle.
+    name = "devserver",
+    static_files = [
+        "index.html",
+        ":app_bundle",
+    ]
+)
+
+ts_devserver(
+    # Bundles and serves the production bundle for testing.
+    # Should NOT be used for serving in production.
+    name = "prodserver",
+    static_files = [
+        "index.html",
+        ":app_bundle",
+    ]
+)
diff --git a/pw_web_ui/src/frontend/app.tsx b/pw_web_ui/src/frontend/app.tsx
index 0c7ebc6..82b9d65 100644
--- a/pw_web_ui/src/frontend/app.tsx
+++ b/pw_web_ui/src/frontend/app.tsx
@@ -1,4 +1,4 @@
-// Copyright 2021 The Pigweed Authors
+// Copyright 2020 The Pigweed Authors
 //
 // 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
@@ -13,178 +13,29 @@
 // the License.
 
 /* eslint-env browser */
-import {
-  Button,
-  TextField,
-  makeStyles,
-  Typography,
-  createTheme,
-  ThemeProvider,
-} from '@material-ui/core';
-import {ToggleButtonGroup, ToggleButton} from '@material-ui/lab';
-import {WebSerialTransport} from '../transport/web_serial_transport';
-import {Decoder, Frame, Encoder} from '@pigweed/pw_hdlc';
-import {SerialLog} from './serial_log';
-import {Log} from './log';
+import Button from '@material-ui/core/Button';
 import * as React from 'react';
-import {useState, useRef} from 'react';
-import {Channel, Client, UnaryMethodStub} from '@pigweed/pw_rpc';
-import {Status} from '@pigweed/pw_status';
-import {ProtoCollection} from 'web_proto_collection/generated/ts_proto_collection';
-
-const RPC_ADDRESS = 82;
-
-const darkTheme = createTheme({
-  palette: {
-    type: 'dark',
-    primary: {
-      main: '#42a5f5',
-    },
-    secondary: {
-      main: 'rgb(232, 21, 165)',
-    },
-  },
-});
-
-const useStyles = makeStyles(() => ({
-  root: {
-    display: 'flex',
-    'flex-direction': 'column',
-    'align-items': 'flex-start',
-    color: 'rgba(255, 255, 255, 0.8);',
-    overflow: 'hidden',
-    width: '1000px',
-  },
-  connect: {
-    'margin-top': '16px',
-    'margin-bottom': '20px',
-  },
-  rpc: {
-    margin: '10px',
-  },
-}));
+import {WebSerialTransport} from '../transport/web_serial_transport';
 
 export function App() {
-  const [connected, setConnected] = useState<boolean>(false);
-  const [frames, setFrames] = useState<Frame[]>([]);
-  const [logLines, setLogLines] = useState<string[]>([]);
-  const [echoText, setEchoText] = useState<string>('Hello World');
-  const [logViewer, setLogViewer] = useState<string>('log');
+  const transport = new WebSerialTransport();
 
-  const classes = useStyles();
-
-  const transportRef = useRef(new WebSerialTransport());
-  const decoderRef = useRef(new Decoder());
-  const encoderRef = useRef(new Encoder());
-  const protoCollectionRef = useRef(new ProtoCollection());
-  const channelsRef = useRef([
-    new Channel(1, (bytes: Uint8Array) => {
-      sendPacket(transportRef.current!, bytes);
-    }),
-  ]);
-  const clientRef = useRef<Client>(
-    Client.fromProtoSet(channelsRef.current!, protoCollectionRef.current!)
-  );
-  const echoService = clientRef
-    .current!.channel()!
-    .methodStub('pw.rpc.EchoService.Echo') as UnaryMethodStub;
-
-  function onConnected() {
-    setConnected(true);
-    transportRef.current!.chunks.subscribe((item: Uint8Array) => {
-      const decoded = decoderRef.current!.process(item);
-      for (const frame of decoded) {
-        setFrames(old => [...old, frame]);
-        if (frame.address === RPC_ADDRESS) {
-          const status = clientRef.current!.processPacket(frame.data);
-        }
-        if (frame.address === 1) {
-          const decodedLine = new TextDecoder().decode(frame.data);
-          const date = new Date();
-          const logLine = `[${date.toLocaleTimeString()}] ${decodedLine}`;
-          setLogLines(old => [...old, logLine]);
-        }
-      }
-    });
-  }
-
-  function sendPacket(
-    transport: WebSerialTransport,
-    packetBytes: Uint8Array
-  ): void {
-    const hdlcBytes = encoderRef.current.uiFrame(RPC_ADDRESS, packetBytes);
-    transport.sendChunk(hdlcBytes);
-  }
-
-  function echo(text: string) {
-    const request = new echoService.method.responseType();
-    request.setMsg(text);
-    echoService
-      .call(request)
-      .then(([status, response]) => {
-        console.log(response.toObject());
-      })
-      .catch(() => {});
-  }
+  transport.chunks.subscribe(item => {
+    console.log(item);
+  });
 
   return (
-    <div className={classes.root}>
-      <ThemeProvider theme={darkTheme}>
-        <Typography variant="h3">Pigweb Demo</Typography>
-        <Button
-          className={classes.connect}
-          disabled={connected}
-          variant="contained"
-          color="primary"
-          onClick={() => {
-            transportRef.current
-              .connect()
-              .then(onConnected)
-              .catch(error => {
-                setConnected(false);
-                console.log(error);
-              });
-          }}
-        >
-          {connected ? 'Connected' : 'Connect'}
-        </Button>
-        <ToggleButtonGroup
-          value={logViewer}
-          onChange={(event, selected) => {
-            setLogViewer(selected);
-          }}
-          exclusive
-        >
-          <ToggleButton value="log">Log Viewer</ToggleButton>
-          <ToggleButton value="serial">Serial Debug</ToggleButton>
-        </ToggleButtonGroup>
-        {logViewer === 'log' ? (
-          <Log lines={logLines} />
-        ) : (
-          <SerialLog frames={frames} />
-        )}
-        <span className={classes.rpc}>
-          <TextField
-            id="echo-text"
-            label="Echo Text"
-            disabled={!connected}
-            value={echoText}
-            onChange={event => {
-              setEchoText(event.target.value);
-            }}
-          ></TextField>
-          <Button
-            disabled={!connected}
-            variant="contained"
-            color="primary"
-            onClick={() => {
-              echo(echoText);
-            }}
-          >
-            Send Echo RPC
-          </Button>
-        </span>
-      </ThemeProvider>
+    <div className="app">
+      <h1>Example Page</h1>
+      <Button
+        variant="contained"
+        color="primary"
+        onClick={() => {
+          transport.connect();
+        }}
+      >
+        Connect
+      </Button>
     </div>
   );
 }
diff --git a/pw_web_ui/src/frontend/index.html b/pw_web_ui/src/frontend/index.html
new file mode 100644
index 0000000..21ab25d
--- /dev/null
+++ b/pw_web_ui/src/frontend/index.html
@@ -0,0 +1,24 @@
+<!--
+Copyright 2020 The Pigweed Authors
+
+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
+
+    https://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.
+-->
+<html>
+  <head>
+    <title>Pigweed Web UI</title>
+  </head>
+  <body>
+    <div id="react-root"></div>
+    <script src="app_bundle.js"></script>
+  </body>
+</html>
diff --git a/pw_web_ui/src/frontend/log.tsx b/pw_web_ui/src/frontend/log.tsx
deleted file mode 100644
index 669bc2b..0000000
--- a/pw_web_ui/src/frontend/log.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-/* eslint-env browser */
-
-import {makeStyles, Paper, Box} from '@material-ui/core';
-import * as React from 'react';
-import {default as AnsiUp} from 'ansi_up';
-import * as Parser from 'html-react-parser';
-
-type Props = {
-  lines: string[];
-};
-
-const useStyles = makeStyles(() => ({
-  root: {
-    padding: '8px',
-    'background-color': '#131416',
-    height: '500px',
-    'overflow-y': 'auto',
-    width: '100%',
-    color: 'white',
-  },
-}));
-
-export function Log(props: Props) {
-  const classes = useStyles();
-  const xtermRef = React.useRef(null);
-  const ansiUp = new AnsiUp();
-
-  function row(text: string, index: number) {
-    const textHtml = ansiUp.ansi_to_html(text);
-    return (
-      <Box key={index} sx={{fontFamily: 'Monospace'}}>
-        {Parser.default(textHtml)}
-      </Box>
-    );
-  }
-
-  return <div className={classes.root}>{props.lines.map(row)}</div>;
-}
diff --git a/pw_web_ui/src/frontend/rpc.tsx b/pw_web_ui/src/frontend/rpc.tsx
deleted file mode 100644
index f7defc2..0000000
--- a/pw_web_ui/src/frontend/rpc.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-/* eslint-env browser */
-import {makeStyles, Box} from '@material-ui/core';
-import * as React from 'react';
-import {Status} from '@pigweed/pw_status';
-import {Message} from 'google-protobuf';
-import {Call} from '@pigweed/pw_rpc';
-
-type Props = {
-  calls: Call[];
-};
-
-const useStyles = makeStyles(() => ({
-  root: {
-    padding: '8px',
-    'background-color': '131416',
-    height: '300px',
-    'overflow-y': 'auto',
-    width: '100%',
-    color: 'white',
-  },
-}));
-
-export function RpcPane(props: Props) {
-  const classes = useStyles();
-
-  function row(call: Call, index: number) {
-    return (
-      <Box key={index} sx={{fontFamily: 'Monospace'}}>
-        {call.rpc.service.name}.{call.rpc.method.name}
-        \n---
-        {call.completed ? Status[call.status!] : 'In progress...'}
-      </Box>
-    );
-  }
-
-  return <div className={classes.root}>{props.calls.map(row)}</div>;
-}
diff --git a/pw_web_ui/src/frontend/serial_log.tsx b/pw_web_ui/src/frontend/serial_log.tsx
deleted file mode 100644
index 9a71927..0000000
--- a/pw_web_ui/src/frontend/serial_log.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-/* eslint-env browser */
-import {makeStyles, Paper, Box} from '@material-ui/core';
-import * as React from 'react';
-import {FrameStatus, Frame} from '@pigweed/pw_hdlc';
-
-type Props = {
-  frames: Frame[];
-};
-
-const useStyles = makeStyles(() => ({
-  root: {
-    padding: '8px',
-    'background-color': '#131416',
-    height: '500px',
-    'overflow-y': 'auto',
-    width: '100%',
-    color: 'white',
-  },
-}));
-
-export function SerialLog(props: Props) {
-  const classes = useStyles();
-  const decoder = new TextDecoder();
-
-  // TODO(b/199515206): Display HDLC packets in user friendly manner.
-  //
-  // See the python console serial debug window for reference.
-  function row(frame: Frame, index: number) {
-    let rowText = '';
-    if (frame.status === FrameStatus.OK) {
-      rowText = decoder.decode(frame.data);
-    } else {
-      rowText = `[${frame.rawDecoded}]`;
-    }
-
-    return (
-      <div key={index}>
-        {frame.status}: {rowText}
-      </div>
-    );
-  }
-
-  return (
-    <Paper className={classes.root}>
-      <Box sx={{fontFamily: 'Monospace'}}>
-        {props.frames.map((frame: Frame, index: number) => row(frame, index))}
-      </Box>
-    </Paper>
-  );
-}
diff --git a/pw_web_ui/src/transport/BUILD b/pw_web_ui/src/transport/BUILD
new file mode 100644
index 0000000..14959a1
--- /dev/null
+++ b/pw_web_ui/src/transport/BUILD
@@ -0,0 +1,85 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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(default_visibility = ["//visibility:public"])
+
+load("@npm//@bazel/typescript:index.bzl", "ts_library")
+load("@npm//@bazel/jasmine:index.bzl", "jasmine_node_test")
+load("//pw_web_ui:web_bundle.bzl", "web_bundle")
+load("@npm//@bazel/karma:index.bzl", "karma_web_test")
+
+ts_library(
+    name = "device_transport_lib",
+    srcs = [
+        "device_transport.ts",
+    ],
+    deps = [
+        "@npm//rxjs",
+    ],
+)
+
+ts_library(
+    name = "serial_mock_lib",
+    srcs = [
+        "serial_mock.ts",
+    ],
+    deps = [
+        "//pw_web_ui/types:serial_lib",
+        "@npm//jasmine",
+        "@npm//@types/jasmine",
+        "@npm//rxjs",
+    ],
+)
+
+ts_library(
+    name = "web_serial_transport_lib",
+    srcs = [
+        "web_serial_transport.ts",
+    ],
+    deps = [
+        ":device_transport_lib",
+        "//pw_web_ui/types:serial_lib",
+        "@npm//rxjs",
+    ],
+)
+
+ts_library(
+    name = "web_serial_transport_lib_test",
+    srcs = [
+        "web_serial_transport_test.ts",
+    ],
+    deps = [
+        ":serial_mock_lib",
+        ":web_serial_transport_lib",
+        "@npm//jasmine",
+        "@npm//@types/jasmine",
+        "@npm//rxjs",
+    ],
+)
+
+web_bundle(
+    name = "web_serial_transport_lib_test_bundle",
+    deps = [
+        ":web_serial_transport_lib_test",
+    ],
+    entry_point = "web_serial_transport_test.ts",
+)
+
+
+karma_web_test(
+    name = "web_test",
+    srcs = [
+        ":web_serial_transport_lib_test_bundle"
+    ],
+)
diff --git a/pw_web_ui/src/transport/web_serial_transport.ts b/pw_web_ui/src/transport/web_serial_transport.ts
index 70dd9fe..b45ee8e 100644
--- a/pw_web_ui/src/transport/web_serial_transport.ts
+++ b/pw_web_ui/src/transport/web_serial_transport.ts
@@ -13,15 +13,14 @@
 // the License.
 
 /* eslint-env browser */
-import {BehaviorSubject, Observable, Subject, Subscription} from 'rxjs';
-
+import {BehaviorSubject, Subject, Subscription, Observable} from 'rxjs';
 import DeviceTransport from './device_transport';
 
 const DEFAULT_SERIAL_OPTIONS: SerialOptions & {baudRate: number} = {
   // Some versions of chrome use `baudrate` (linux)
-  baudrate: 115200,
+  baudrate: 921600,
   // Some versions use `baudRate` (chromebook)
-  baudRate: 115200,
+  baudRate: 921600,
   databits: 8,
   parity: 'none',
   stopbits: 1,
@@ -109,11 +108,11 @@
 
     this.rxSubscriptions.push(
       this.activePortConnectionConnection.chunks.subscribe(
-        (chunk: any) => {
+        chunk => {
           this.chunks.next(chunk);
         },
-        (err: any) => {
-          throw new Error(`Chunks observable had an unexpected error ${err}`);
+        err => {
+          throw new Error(`Chunks observable had an unexpeted error ${err}`);
         },
         () => {
           this.connected.next(false);
@@ -125,7 +124,7 @@
     );
 
     this.rxSubscriptions.push(
-      this.activePortConnectionConnection.errors.subscribe((error: any) => {
+      this.activePortConnectionConnection.errors.subscribe(error => {
         this.errors.next(error);
         if (error instanceof DeviceLostError) {
           // The device has been lost
diff --git a/pw_web_ui/src/transport/web_serial_transport_test.ts b/pw_web_ui/src/transport/web_serial_transport_test.ts
index 689e8fe..ee57058 100644
--- a/pw_web_ui/src/transport/web_serial_transport_test.ts
+++ b/pw_web_ui/src/transport/web_serial_transport_test.ts
@@ -14,9 +14,8 @@
 
 /* eslint-env browser, jasmine */
 import {last, take} from 'rxjs/operators';
-
 import {SerialMock} from './serial_mock';
-import {DeviceLockedError, WebSerialTransport} from './web_serial_transport';
+import {WebSerialTransport, DeviceLockedError} from './web_serial_transport';
 
 describe('WebSerialTransport', () => {
   let serialMock: SerialMock;
diff --git a/pw_web_ui/tsconfig.json b/pw_web_ui/tsconfig.json
deleted file mode 100644
index eafd51c..0000000
--- a/pw_web_ui/tsconfig.json
+++ /dev/null
@@ -1,34 +0,0 @@
-{
-  "compilerOptions": {
-    "allowUnreachableCode": false,
-    "allowUnusedLabels": false,
-    "declaration": true,
-    "forceConsistentCasingInFileNames": true,
-    "lib": [
-      "es2021",
-      "dom",
-      "dom.iterable",
-      "esnext"
-    ],
-    "module": "commonjs",
-    "noEmitOnError": true,
-    "noFallthroughCasesInSwitch": true,
-    "noImplicitReturns": true,
-    "pretty": true,
-    "sourceMap": true,
-    "strict": true,
-    "target": "es2018",
-    "jsx": "react",
-    "plugins": [
-      {
-        "name": "@bazel/tsetse",
-        "disabledRules": [
-          "must-use-promises"
-        ]
-      }
-    ]
-  },
-  "exclude": [
-    "node_modules"
-  ]
-}
diff --git a/pw_web_ui/types/BUILD b/pw_web_ui/types/BUILD
new file mode 100644
index 0000000..886610a
--- /dev/null
+++ b/pw_web_ui/types/BUILD
@@ -0,0 +1,24 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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(default_visibility = ["//visibility:public"])
+
+load("@npm//@bazel/typescript:index.bzl", "ts_library")
+
+ts_library(
+    name = "serial_lib",
+    srcs = [
+        "serial.d.ts",
+    ],
+)
diff --git a/pw_web_ui/types/serial.d.ts b/pw_web_ui/types/serial.d.ts
index 46bbcc7..70557a2 100644
--- a/pw_web_ui/types/serial.d.ts
+++ b/pw_web_ui/types/serial.d.ts
@@ -38,7 +38,7 @@
 }
 
 /**
- * @see https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/serial/serial_port_filter.idl
+ * @see https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/modules/serial/serial_port_filter.idl
  */
 interface SerialPortFilter {
   usbVendorId?: number;
@@ -46,21 +46,21 @@
 }
 
 /**
- * @see https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/serial/serial_port_request_options.idl
+ * @see https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/modules/serial/serial_port_request_options.idl
  */
 interface SerialPortRequestOptions {
   filters?: SerialPortFilter[];
 }
 
 /**
- * @see https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/serial/serial_connection_event_init.idl
+ * @see https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/modules/serial/serial_connection_event_init.idl
  */
 interface SerialConnectionEventInit extends EventInit {
   port: SerialPort;
 }
 
 /**
- * @see https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/serial/serial_connection_event.idl
+ * @see https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/modules/serial/serial_connection_event.idl
  */
 declare class SerialConnectionEvent extends Event {
   constructor(type: string, eventInitDict: SerialConnectionEventInit);
diff --git a/pw_web_ui/web_bundle.bzl b/pw_web_ui/web_bundle.bzl
index d785bdb..0ac17ea 100644
--- a/pw_web_ui/web_bundle.bzl
+++ b/pw_web_ui/web_bundle.bzl
@@ -11,22 +11,21 @@
 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 # License for the specific language governing permissions and limitations under
 # the License.
-"""Create web bundle."""
 
 load("@npm//@bazel/rollup:index.bzl", "rollup_bundle")
 
 def web_bundle(name, deps, entry_point):
-    rollup_bundle(
-        name = name,
-        deps = deps + [
-            "@npm//@rollup/plugin-node-resolve",
-            "@npm//@rollup/plugin-commonjs",
-            "@npm//rollup-plugin-node-builtins",
-            "@npm//rollup-plugin-node-globals",
-            "@npm//rollup-plugin-sourcemaps",
-        ],
-        entry_point = entry_point,
-        config_file = "//pw_web_ui:rollup.config.js",
-        sourcemap = "inline",
-        format = "cjs",
-    )
+  rollup_bundle(
+      name = name,
+      deps = deps + [
+        "@npm//@rollup/plugin-node-resolve",
+        "@npm//@rollup/plugin-commonjs",
+        "@npm//rollup-plugin-node-builtins",
+        "@npm//rollup-plugin-node-globals",
+        "@npm//rollup-plugin-sourcemaps",
+      ],
+      entry_point = entry_point,
+      config_file = "//pw_web_ui:rollup.config.js",
+      sourcemap = "inline",
+      format = "cjs"
+  )
diff --git a/pw_web_ui/yarn.lock b/pw_web_ui/yarn.lock
deleted file mode 100644
index 680739f..0000000
--- a/pw_web_ui/yarn.lock
+++ /dev/null
@@ -1,359 +0,0 @@
-# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
-# yarn lockfile v1
-
-
-"@types/jasmine@^3.9.0":
-  version "3.9.1"
-  resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.9.1.tgz#94c65ee8bf9d24d9e1d84abaed57b6e0da8b49de"
-  integrity sha512-PVpjh8S8lqKFKurWSKdFATlfBHGPzgy0PoDdzQ+rr78jTQ0aacyh9YndzZcAUPxhk4kRujItFFGQdUJ7flHumw==
-
-"@types/prop-types@*":
-  version "15.7.4"
-  resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11"
-  integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==
-
-"@types/react-dom@^17.0.9":
-  version "17.0.9"
-  resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.9.tgz#441a981da9d7be117042e1a6fd3dac4b30f55add"
-  integrity sha512-wIvGxLfgpVDSAMH5utdL9Ngm5Owu0VsGmldro3ORLXV8CShrL8awVj06NuEXFQ5xyaYfdca7Sgbk/50Ri1GdPg==
-  dependencies:
-    "@types/react" "*"
-
-"@types/react@*":
-  version "17.0.27"
-  resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.27.tgz#6498ed9b3ad117e818deb5525fa1946c09f2e0e6"
-  integrity sha512-zgiJwtsggVGtr53MndV7jfiUESTqrbxOcBvwfe6KS/9bzaVPCTDieTWnFNecVNx6EAaapg5xsLLWFfHHR437AA==
-  dependencies:
-    "@types/prop-types" "*"
-    "@types/scheduler" "*"
-    csstype "^3.0.2"
-
-"@types/scheduler@*":
-  version "0.16.2"
-  resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"
-  integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==
-
-async@^2.6.2:
-  version "2.6.3"
-  resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff"
-  integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==
-  dependencies:
-    lodash "^4.17.14"
-
-balanced-match@^1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
-  integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
-
-basic-auth@^1.0.3:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-1.1.0.tgz#45221ee429f7ee1e5035be3f51533f1cdfd29884"
-  integrity sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=
-
-brace-expansion@^1.1.7:
-  version "1.1.11"
-  resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
-  integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
-  dependencies:
-    balanced-match "^1.0.0"
-    concat-map "0.0.1"
-
-call-bind@^1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
-  integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
-  dependencies:
-    function-bind "^1.1.1"
-    get-intrinsic "^1.0.2"
-
-colors@^1.4.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
-  integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==
-
-concat-map@0.0.1:
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
-  integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
-
-corser@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/corser/-/corser-2.0.1.tgz#8eda252ecaab5840dcd975ceb90d9370c819ff87"
-  integrity sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=
-
-csstype@^3.0.2:
-  version "3.0.9"
-  resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.9.tgz#6410af31b26bd0520933d02cbc64fce9ce3fbf0b"
-  integrity sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==
-
-debug@^3.1.1:
-  version "3.2.7"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
-  integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
-  dependencies:
-    ms "^2.1.1"
-
-eventemitter3@^4.0.0:
-  version "4.0.7"
-  resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
-  integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
-
-follow-redirects@^1.0.0:
-  version "1.14.4"
-  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379"
-  integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==
-
-fs.realpath@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
-  integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
-
-function-bind@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
-  integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
-
-get-intrinsic@^1.0.2:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6"
-  integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==
-  dependencies:
-    function-bind "^1.1.1"
-    has "^1.0.3"
-    has-symbols "^1.0.1"
-
-glob@^7.1.6:
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
-  integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
-  dependencies:
-    fs.realpath "^1.0.0"
-    inflight "^1.0.4"
-    inherits "2"
-    minimatch "^3.0.4"
-    once "^1.3.0"
-    path-is-absolute "^1.0.0"
-
-has-symbols@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
-  integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==
-
-has@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
-  integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
-  dependencies:
-    function-bind "^1.1.1"
-
-he@^1.1.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
-  integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
-
-http-proxy@^1.18.0:
-  version "1.18.1"
-  resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549"
-  integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==
-  dependencies:
-    eventemitter3 "^4.0.0"
-    follow-redirects "^1.0.0"
-    requires-port "^1.0.0"
-
-http-server@^13.0.2:
-  version "13.0.2"
-  resolved "https://registry.yarnpkg.com/http-server/-/http-server-13.0.2.tgz#36f8a8ae0e1b78e7bf30a4dfb01ae89b904904ef"
-  integrity sha512-R8kvPT7qp11AMJWLZsRShvm6heIXdlR/+tL5oAWNG/86A/X+IAFX6q0F9SA2G+dR5aH/759+9PLH0V34Q6j4rg==
-  dependencies:
-    basic-auth "^1.0.3"
-    colors "^1.4.0"
-    corser "^2.0.1"
-    he "^1.1.0"
-    http-proxy "^1.18.0"
-    mime "^1.6.0"
-    minimist "^1.2.5"
-    opener "^1.5.1"
-    portfinder "^1.0.25"
-    secure-compare "3.0.1"
-    union "~0.5.0"
-    url-join "^2.0.5"
-
-inflight@^1.0.4:
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
-  integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
-  dependencies:
-    once "^1.3.0"
-    wrappy "1"
-
-inherits@2:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
-  integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
-
-jasmine-core@~3.9.0:
-  version "3.9.0"
-  resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.9.0.tgz#09a3c8169fe98ec69440476d04a0e4cb4d59e452"
-  integrity sha512-Tv3kVbPCGVrjsnHBZ38NsPU3sDOtNa0XmbG2baiyJqdb5/SPpDO6GVwJYtUryl6KB4q1Ssckwg612ES9Z0dreQ==
-
-jasmine@^3.9.0:
-  version "3.9.0"
-  resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-3.9.0.tgz#286c4f9f88b69defc24acf3989af5533d5c6a0e6"
-  integrity sha512-JgtzteG7xnqZZ51fg7N2/wiQmXon09szkALcRMTgCMX4u/m17gVJFjObnvw5FXkZOWuweHPaPRVB6DI2uN0wVA==
-  dependencies:
-    glob "^7.1.6"
-    jasmine-core "~3.9.0"
-
-"js-tokens@^3.0.0 || ^4.0.0":
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
-  integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
-
-lodash@^4.17.14:
-  version "4.17.21"
-  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
-  integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
-
-loose-envify@^1.1.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
-  integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
-  dependencies:
-    js-tokens "^3.0.0 || ^4.0.0"
-
-mime@^1.6.0:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
-  integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
-
-minimatch@^3.0.4:
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
-  integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
-  dependencies:
-    brace-expansion "^1.1.7"
-
-minimist@^1.2.5:
-  version "1.2.5"
-  resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
-  integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
-
-mkdirp@^0.5.5:
-  version "0.5.5"
-  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
-  integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
-  dependencies:
-    minimist "^1.2.5"
-
-ms@^2.1.1:
-  version "2.1.3"
-  resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
-  integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
-
-object-assign@^4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
-  integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
-
-object-inspect@^1.9.0:
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1"
-  integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==
-
-once@^1.3.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
-  integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
-  dependencies:
-    wrappy "1"
-
-opener@^1.5.1:
-  version "1.5.2"
-  resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598"
-  integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==
-
-path-is-absolute@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
-  integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
-
-portfinder@^1.0.25:
-  version "1.0.28"
-  resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778"
-  integrity sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==
-  dependencies:
-    async "^2.6.2"
-    debug "^3.1.1"
-    mkdirp "^0.5.5"
-
-qs@^6.4.0:
-  version "6.10.1"
-  resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a"
-  integrity sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==
-  dependencies:
-    side-channel "^1.0.4"
-
-react-dom@^17.0.2:
-  version "17.0.2"
-  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
-  integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
-  dependencies:
-    loose-envify "^1.1.0"
-    object-assign "^4.1.1"
-    scheduler "^0.20.2"
-
-requires-port@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
-  integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
-
-rxjs@^7.3.0:
-  version "7.3.1"
-  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.3.1.tgz#cc375521f9e238b474fe552b0b9fd1be33d08099"
-  integrity sha512-vNenx7gqjPyeKpRnM6S5Ksm/oFTRijWWzYlRON04KaehZ3YjDwEmVjGUGo0TKWVjeNXOujVRlh0K1drUbcdPkw==
-  dependencies:
-    tslib "~2.1.0"
-
-scheduler@^0.20.2:
-  version "0.20.2"
-  resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91"
-  integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==
-  dependencies:
-    loose-envify "^1.1.0"
-    object-assign "^4.1.1"
-
-secure-compare@3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/secure-compare/-/secure-compare-3.0.1.tgz#f1a0329b308b221fae37b9974f3d578d0ca999e3"
-  integrity sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=
-
-side-channel@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
-  integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
-  dependencies:
-    call-bind "^1.0.0"
-    get-intrinsic "^1.0.2"
-    object-inspect "^1.9.0"
-
-tslib@~2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
-  integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
-
-union@~0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/union/-/union-0.5.0.tgz#b2c11be84f60538537b846edb9ba266ba0090075"
-  integrity sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==
-  dependencies:
-    qs "^6.4.0"
-
-url-join@^2.0.5:
-  version "2.0.5"
-  resolved "https://registry.yarnpkg.com/url-join/-/url-join-2.0.5.tgz#5af22f18c052a000a48d7b82c5e9c2e2feeda728"
-  integrity sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=
-
-wrappy@1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
-  integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
diff --git a/pw_work_queue/BUILD.bazel b/pw_work_queue/BUILD.bazel
deleted file mode 100644
index 3f29327..0000000
--- a/pw_work_queue/BUILD.bazel
+++ /dev/null
@@ -1,87 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-    "pw_cc_test",
-)
-load(
-    "//pw_build:selects.bzl",
-    "TARGET_COMPATIBLE_WITH_HOST_SELECT",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pw_work_queue",
-    srcs = ["work_queue.cc"],
-    hdrs = [
-        "public/pw_work_queue/internal/circular_buffer.h",
-        "public/pw_work_queue/work_queue.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_function",
-        "//pw_metric:metric",
-        "//pw_status",
-        "//pw_sync:interrupt_spin_lock",
-        "//pw_sync:lock_annotations",
-        "//pw_sync:thread_notification",
-        "//pw_thread:thread",
-    ],
-)
-
-pw_cc_library(
-    name = "test_thread_header",
-    hdrs = ["public/pw_work_queue/test_thread.h"],
-    includes = ["public"],
-)
-
-pw_cc_library(
-    name = "work_queue_test",
-    srcs = [
-        "work_queue_test.cc",
-    ],
-    deps = [
-        ":pw_work_queue",
-        ":test_thread",
-        "//pw_log",
-        "//pw_unit_test",
-    ],
-)
-
-pw_cc_library(
-    name = "stl_test_thread",
-    srcs = [
-        "stl_test_thread.cc",
-    ],
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-    deps = [
-        "//pw_thread:test_thread_header",
-        "//pw_thread:thread",
-        "//pw_thread_stl:thread",
-    ],
-)
-
-pw_cc_test(
-    name = "stl_work_queue_test",
-    target_compatible_with = select(TARGET_COMPATIBLE_WITH_HOST_SELECT),
-    deps = [
-        ":stl_test_thread",
-        ":work_queue_test",
-    ],
-)
diff --git a/pw_work_queue/BUILD.gn b/pw_work_queue/BUILD.gn
deleted file mode 100644
index 08fd679..0000000
--- a/pw_work_queue/BUILD.gn
+++ /dev/null
@@ -1,89 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/facade.gni")
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_thread/backend.gni")
-import("$dir_pw_unit_test/test.gni")
-
-config("public_include_path") {
-  include_dirs = [ "public" ]
-  visibility = [ ":*" ]
-}
-
-pw_source_set("pw_work_queue") {
-  public_configs = [ ":public_include_path" ]
-  public = [
-    "public/pw_work_queue/internal/circular_buffer.h",
-    "public/pw_work_queue/work_queue.h",
-  ]
-  public_deps = [
-    "$dir_pw_sync:interrupt_spin_lock",
-    "$dir_pw_sync:lock_annotations",
-    "$dir_pw_sync:thread_notification",
-    "$dir_pw_thread:thread",
-    dir_pw_function,
-    dir_pw_metric,
-    dir_pw_status,
-  ]
-  sources = [ "work_queue.cc" ]
-}
-
-pw_source_set("test_thread") {
-  public_configs = [ ":public_include_path" ]
-  public = [ "public/pw_work_queue/test_thread.h" ]
-  public_deps = [ "$dir_pw_thread:thread" ]
-}
-
-# To instantiate this test based on a selected thread backend to provide
-# test_thread you can create a pw_test target which depends on this
-# pw_source_set and a pw_source_set which provides the implementation of
-# test_thread. See ":stl_work_queue_test" as an example.
-pw_source_set("work_queue_test") {
-  sources = [ "work_queue_test.cc" ]
-  deps = [
-    ":pw_work_queue",
-    ":test_thread",
-    dir_pw_log,
-    dir_pw_unit_test,
-  ]
-}
-
-pw_test_group("tests") {
-  tests = [ ":stl_work_queue_test" ]
-}
-
-pw_source_set("stl_test_thread") {
-  sources = [ "stl_test_thread.cc" ]
-  deps = [
-    ":test_thread",
-    "$dir_pw_thread:thread",
-    "$dir_pw_thread_stl:thread",
-  ]
-}
-
-pw_test("stl_work_queue_test") {
-  enable_if = pw_thread_THREAD_BACKEND == "$dir_pw_thread_stl:thread"
-  deps = [
-    ":stl_test_thread",
-    ":work_queue_test",
-  ]
-}
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
diff --git a/pw_work_queue/OWNERS b/pw_work_queue/OWNERS
deleted file mode 100644
index 21d24bc..0000000
--- a/pw_work_queue/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-ewout@google.com
diff --git a/pw_work_queue/docs.rst b/pw_work_queue/docs.rst
deleted file mode 100644
index 7ae6efa..0000000
--- a/pw_work_queue/docs.rst
+++ /dev/null
@@ -1,104 +0,0 @@
-.. _module-pw_work_queue:
-
-=============
-pw_work_queue
-=============
-The ``pw_work_queue`` module contains utilities for deferring work to be
-executed by another thread.
-
-.. Warning::
-  This module is still under construction, the API is not yet stable.
-
----------
-WorkQueue
----------
-The ``pw::work_queue::WorkQueue`` class enables threads and interrupts to
-enqueue work as a ``pw::work_queue::WorkItem`` for execution by the work queue.
-
-The entire API is thread and interrupt safe.
-
-Queue Sizing
-============
-The number of outstanding work requests is limited based on the
-``pw::work_queue::WorkQueue``'s internal queue size. This must be set
-appropriately for the application by the user.
-
-The queue size is set trough either through the size of the ``queue_storage``
-buffer passed into the constructor or by using the templated
-``pw::work_queue::WorkQueueWithBuffer`` helper.
-
-.. Note:: While the queue is full, the queue will not accept further work.
-
-Cooperative Thread Cancellation
-===============================
-The class is a ``pw::thread::ThreadCore``, meaning it should be executed as a
-single thread. In order to facilitate clean shutdown, it provides a
-``RequestStop()`` API for cooperative cancellation which should be invoked
-before joining the thread.
-
-.. Note:: Once stop has been requested the queue will no longer accept further
-          work.
-
-C++
-===
-.. cpp:class:: pw::work_queue::WorkQueue
-
-  .. cpp:function:: Status PushWork(WorkItem work_item)
-
-     Enqueues a work_item for execution by the work queue thread.
-
-     Returns:
-
-     * **Ok** - Success, entry was enqueued for execution.
-     * **FailedPrecondition** - the work queue is shutting down, entries are no
-       longer permitted.
-     * **ResourceExhausted** - internal work queue is full, entry was not
-       enqueued.
-
-  .. cpp:function:: void CheckPushWork(WorkItem work_item)
-
-     Queue work for execution. Crash if the work cannot be queued due to a
-     full queue or a stopped worker thread.
-
-     This call is recommended where possible since it saves error handling code
-     at the callsite; and in many practical cases, it is a bug if the work
-     queue is full (and so a crash is useful to detect the problem).
-
-     **Precondition:** The queue must not overflow, i.e. be full.
-
-     **Precondition:** The queue must not have been requested to stop, i.e. it
-     must not be in the process of shutting down.
-
-  .. cpp:function:: void RequestStop()
-
-     Locks the queue to prevent further work enqueing, finishes outstanding
-     work, then shuts down the worker thread.
-
-     The WorkQueue cannot be resumed after stopping as the ThreadCore thread
-     returns and may be joined. It must be reconstructed for re-use after
-     the thread has been joined.
-
-Example
--------
-
-.. code-block:: cpp
-
-  #include "pw_thread/detached_thread.h"
-  #include "pw_work_queue/work_queue.h"
-
-  pw::work_queue::WorkQueueWithBuffer<10> work_queue;
-
-  pw::thread::Options& WorkQueueThreadOptions();
-  void SomeLongRunningProcessing();
-
-  void SomeInterruptHandler() {
-    // Instead of executing the long running processing task in the interrupt,
-    // the work_queue executes it on the interrupt's behalf.
-    work_queue.CheckPushWork(SomeLongRunningProcessing);
-  }
-
-  int main() {
-    // Start up the work_queue as a detached thread which runs forever.
-    pw::thread::DetachedThread(WorkQueueThreadOptions(), work_queue);
-  }
-
diff --git a/pw_work_queue/public/pw_work_queue/internal/circular_buffer.h b/pw_work_queue/public/pw_work_queue/internal/circular_buffer.h
deleted file mode 100644
index 55295d0..0000000
--- a/pw_work_queue/public/pw_work_queue/internal/circular_buffer.h
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <optional>
-#include <span>
-
-namespace pw::work_queue::internal {
-
-// TODO(hepler): Replace this with a std::deque like container.
-template <typename T>
-class CircularBuffer {
- public:
-  explicit constexpr CircularBuffer(std::span<T> buffer)
-      : buffer_(buffer), head_(0), tail_(0), count_(0) {}
-
-  bool empty() const { return count_ == 0; }
-  bool full() const { return count_ == buffer_.size(); }
-  size_t size() const { return count_; }
-  size_t capacity() const { return buffer_.size(); }
-
-  template <typename Ty>
-  bool Push(Ty&& value) {
-    PW_DASSERT(tail_ < buffer_.size());
-
-    if (full()) {
-      return false;
-    }
-
-    buffer_[tail_] = std::forward<Ty>(value);
-    IncrementWithWrap(tail_);
-    ++count_;
-    return true;
-  }
-
-  std::optional<T> Pop() {
-    PW_DASSERT(head_ < buffer_.size());
-
-    if (empty()) {
-      return std::nullopt;
-    }
-
-    T entry = std::move(buffer_[head_]);
-    IncrementWithWrap(head_);
-    --count_;
-    return entry;
-  }
-
- private:
-  void IncrementWithWrap(size_t& index) const {
-    index++;
-    // Note: branch is faster than mod (%) on common embedded architectures.
-    if (index == buffer_.size()) {
-      index = 0;
-    }
-  }
-
-  std::span<T> buffer_;
-
-  size_t head_;
-  size_t tail_;
-  size_t count_;
-};
-
-}  // namespace pw::work_queue::internal
diff --git a/pw_work_queue/public/pw_work_queue/test_thread.h b/pw_work_queue/public/pw_work_queue/test_thread.h
deleted file mode 100644
index a84fca1..0000000
--- a/pw_work_queue/public/pw_work_queue/test_thread.h
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_thread/thread.h"
-
-namespace pw::work_queue::test {
-
-// Test thread used to verify the work queue.
-const thread::Options& WorkQueueThreadOptions();
-
-}  // namespace pw::work_queue::test
diff --git a/pw_work_queue/public/pw_work_queue/work_queue.h b/pw_work_queue/public/pw_work_queue/work_queue.h
deleted file mode 100644
index 9b592b8..0000000
--- a/pw_work_queue/public/pw_work_queue/work_queue.h
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <array>
-#include <cstdint>
-#include <span>
-
-#include "pw_function/function.h"
-#include "pw_metric/metric.h"
-#include "pw_status/status.h"
-#include "pw_sync/interrupt_spin_lock.h"
-#include "pw_sync/lock_annotations.h"
-#include "pw_sync/thread_notification.h"
-#include "pw_thread/thread_core.h"
-#include "pw_work_queue/internal/circular_buffer.h"
-
-namespace pw::work_queue {
-
-using WorkItem = Function<void()>;
-
-// The WorkQueue class enables threads and interrupts to enqueue work as a
-// pw::work_queue::WorkItem for execution by the work queue.
-//
-// The entire API is thread and interrupt safe.
-class WorkQueue : public thread::ThreadCore {
- public:
-  // Note: the ThreadNotification prevents this from being constexpr.
-  explicit WorkQueue(std::span<WorkItem> queue_storage)
-      : stop_requested_(false), circular_buffer_(queue_storage) {}
-
-  // Enqueues a work_item for execution by the work queue thread.
-  //
-  // Returns:
-  // Ok - Success, entry was enqueued for execution.
-  // FailedPrecondition - the work queue is shutting down, entries are no
-  //     longer permitted.
-  // ResourceExhausted - internal work queue is full, entry was not enqueued.
-  Status PushWork(WorkItem&& work_item) PW_LOCKS_EXCLUDED(lock_) {
-    return InternalPushWork(std::move(work_item));
-  }
-
-  // Queue work for execution. Crash if the work cannot be queued due to a
-  // full queue or a stopped worker thread.
-  //
-  // This call is recommended where possible since it saves error handling code
-  // at the callsite; and in many practical cases, it is a bug if the work
-  // queue is full (and so a crash is useful to detect the problem).
-  //
-  // Precondition: The queue must not overflow, i.e. be full.
-  // Precondition: The queue must not have been requested to stop, i.e. it must
-  //     not be in the process of shutting down.
-  void CheckPushWork(WorkItem&& work_item) PW_LOCKS_EXCLUDED(lock_);
-
-  // Locks the queue to prevent further work enqueing, finishes outstanding
-  // work, then shuts down the worker thread.
-  //
-  // The WorkQueue cannot be resumed after stopping as the ThreadCore thread
-  // returns and may be joined. It must be reconstructed for re-use after
-  // the thread has been joined.
-  void RequestStop() PW_LOCKS_EXCLUDED(lock_);
-
- private:
-  void Run() override PW_LOCKS_EXCLUDED(lock_);
-  Status InternalPushWork(WorkItem&& work_item) PW_LOCKS_EXCLUDED(lock_);
-
-  sync::InterruptSpinLock lock_;
-  bool stop_requested_ PW_GUARDED_BY(lock_);
-  internal::CircularBuffer<WorkItem> circular_buffer_ PW_GUARDED_BY(lock_);
-  sync::ThreadNotification work_notification_;
-
-  // TODO(ewout): The group and/or its name token should be passed as a ctor
-  // arg instead. Depending on the approach here the group should be exposed
-  // While doing this evaluate whether perhaps we should instead construct
-  // TypedMetric<uint32_t>s directly, avoiding the macro usage given the
-  // min_queue_remaining_ initial value requires dependency injection.
-  // And lastly when the restructure is finalized add unit tests to ensure these
-  // metrics work as intended.
-  PW_METRIC_GROUP(metrics_, "pw::work_queue::WorkQueue");
-  PW_METRIC(metrics_, max_queue_used_, "max_queue_used", 0u);
-  PW_METRIC(metrics_,
-            min_queue_remaining_,
-            "min_queue_remaining",
-            static_cast<uint32_t>(circular_buffer_.capacity()));
-};
-
-template <size_t kWorkQueueEntries>
-class WorkQueueWithBuffer : public WorkQueue {
- public:
-  constexpr WorkQueueWithBuffer() : WorkQueue(queue_storage_) {}
-
- private:
-  std::array<WorkItem, kWorkQueueEntries> queue_storage_;
-};
-
-}  // namespace pw::work_queue
diff --git a/pw_work_queue/stl_test_thread.cc b/pw_work_queue/stl_test_thread.cc
deleted file mode 100644
index 6d33940..0000000
--- a/pw_work_queue/stl_test_thread.cc
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_thread_stl/options.h"
-#include "pw_work_queue/test_thread.h"
-
-namespace pw::work_queue::test {
-
-// The STL doesn't offer any options so the default constructed options are used
-// directly.
-
-const thread::Options& WorkQueueThreadOptions() {
-  static constexpr thread::stl::Options thread_options;
-  return thread_options;
-}
-
-}  // namespace pw::work_queue::test
diff --git a/pw_work_queue/work_queue.cc b/pw_work_queue/work_queue.cc
deleted file mode 100644
index 62be123..0000000
--- a/pw_work_queue/work_queue.cc
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_work_queue/work_queue.h"
-
-#include <mutex>
-
-#include "pw_assert/check.h"
-
-namespace pw::work_queue {
-
-void WorkQueue::RequestStop() {
-  std::lock_guard lock(lock_);
-  stop_requested_ = true;
-  work_notification_.release();
-}
-
-void WorkQueue::Run() {
-  while (true) {
-    work_notification_.acquire();
-
-    // Drain the work queue.
-    bool stop_requested;
-    bool work_remaining;
-    do {
-      std::optional<WorkItem> possible_work_item;
-      {
-        std::lock_guard lock(lock_);
-        possible_work_item = circular_buffer_.Pop();
-        work_remaining = !circular_buffer_.empty();
-        stop_requested = stop_requested_;
-      }
-      if (!possible_work_item.has_value()) {
-        continue;  // No work item to process.
-      }
-      WorkItem& work_item = possible_work_item.value();
-      PW_CHECK(work_item != nullptr);
-      work_item();
-    } while (work_remaining);
-
-    // Queue was drained, return if we've been requested to stop.
-    if (stop_requested) {
-      return;
-    }
-  }
-}
-
-void WorkQueue::CheckPushWork(WorkItem&& work_item) {
-  PW_CHECK_OK(InternalPushWork(std::move(work_item)),
-              "Failed to push work item into the work queue");
-}
-
-Status WorkQueue::InternalPushWork(WorkItem&& work_item) {
-  std::lock_guard lock(lock_);
-
-  if (stop_requested_) {
-    // Entries are not permitted to be enqueued once stop has been requested.
-    return Status::FailedPrecondition();
-  }
-
-  if (circular_buffer_.full()) {
-    return Status::ResourceExhausted();
-  }
-
-  circular_buffer_.Push(std::move(work_item));
-
-  // Update the watermarks for the queue.
-  const uint32_t queue_entries = circular_buffer_.size();
-  if (queue_entries > max_queue_used_.value()) {
-    max_queue_used_.Set(queue_entries);
-  }
-  const uint32_t queue_remaining = circular_buffer_.capacity() - queue_entries;
-  if (queue_remaining < min_queue_remaining_.value()) {
-    min_queue_remaining_.Set(queue_entries);
-  }
-
-  work_notification_.release();
-  return OkStatus();
-}
-
-}  // namespace pw::work_queue
diff --git a/pw_work_queue/work_queue_test.cc b/pw_work_queue/work_queue_test.cc
deleted file mode 100644
index 6b793e0..0000000
--- a/pw_work_queue/work_queue_test.cc
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_work_queue/work_queue.h"
-
-#include "gtest/gtest.h"
-#include "pw_function/function.h"
-#include "pw_log/log.h"
-#include "pw_sync/thread_notification.h"
-#include "pw_thread/thread.h"
-#include "pw_work_queue/test_thread.h"
-
-namespace pw::work_queue {
-namespace {
-
-TEST(WorkQueue, PingPongOneRequestType) {
-  struct {
-    int counter = 0;
-    sync::ThreadNotification worker_ping;
-  } context;
-
-  WorkQueueWithBuffer<10> work_queue;
-
-  // Start the worker thread.
-  thread::Thread work_thread(test::WorkQueueThreadOptions(), work_queue);
-
-  // Pick a number bigger than the circular buffer to ensure we loop around.
-  const int kPingPongs = 300;
-
-  for (int i = 0; i < kPingPongs; ++i) {
-    // Ping: throw work at the queue that will increment our counter.
-    work_queue
-        .PushWork([&context] {
-          context.counter++;
-          PW_LOG_INFO("Send pong...");
-          context.worker_ping.release();
-        })
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-
-    // Throw a distraction in the queue.
-    work_queue
-        .PushWork([] {
-          PW_LOG_INFO(
-              "I'm a random task in the work queue; nothing to see here!");
-        })
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-
-    // Pong: wait for the callback to notify us from the worker thread.
-    context.worker_ping.acquire();
-  }
-
-  // Wait for the worker thread to terminate.
-  work_queue.RequestStop();
-  work_thread.join();
-
-  EXPECT_EQ(context.counter, kPingPongs);
-}
-
-TEST(WorkQueue, PingPongTwoRequestTypesWithExtraRequests) {
-  struct {
-    int counter = 0;
-    sync::ThreadNotification worker_ping;
-  } context_a, context_b;
-
-  WorkQueueWithBuffer<10> work_queue;
-
-  // Start the worker thread.
-  thread::Thread work_thread(test::WorkQueueThreadOptions(), work_queue);
-
-  // Pick a number bigger than the circular buffer to ensure we loop around.
-  const int kPingPongs = 300;
-
-  // Run a bunch of work items in the queue.
-  for (int i = 0; i < kPingPongs; ++i) {
-    // Other requests...
-    work_queue.PushWork([] { PW_LOG_INFO("Chopping onions"); })
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-
-    // Ping A: throw work at the queue that will increment our counter.
-    work_queue
-        .PushWork([&context_a] {
-          context_a.counter++;
-          context_a.worker_ping.release();
-        })
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-
-    // Other requests...
-    work_queue.PushWork([] { PW_LOG_INFO("Dicing carrots"); })
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-    work_queue.PushWork([] { PW_LOG_INFO("Blanching spinach"); })
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-
-    // Ping B: throw work at the queue that will increment our counter.
-    work_queue
-        .PushWork([&context_b] {
-          context_b.counter++;
-          context_b.worker_ping.release();
-        })
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-
-    // Other requests...
-    work_queue.PushWork([] { PW_LOG_INFO("Peeling potatoes"); })
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
-
-    // Pong A & B: wait for the callbacks to notify us from the worker thread.
-    context_a.worker_ping.acquire();
-    context_b.worker_ping.acquire();
-  }
-
-  // Wait for the worker thread to terminate.
-  work_queue.RequestStop();
-  work_thread.join();
-
-  EXPECT_EQ(context_a.counter, kPingPongs);
-  EXPECT_EQ(context_b.counter, kPingPongs);
-}
-
-// TODO(ewout): Add unit tests for the metrics once they have been restructured.
-
-}  // namespace
-}  // namespace pw::work_queue
diff --git a/targets/BUILD.bazel b/targets/BUILD.bazel
deleted file mode 100644
index 486d1ed..0000000
--- a/targets/BUILD.bazel
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
diff --git a/targets/android/BUILD.gn b/targets/android/BUILD.gn
deleted file mode 100644
index 74571e9..0000000
--- a/targets/android/BUILD.gn
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_android_toolchain/android.gni")
-import("$dir_pw_docgen/docs.gni")
-
-if (pw_android_toolchain_NDK_PATH != "") {
-  import("$dir_pw_android_toolchain/generate_toolchain.gni")
-  import("target_toolchains.gni")
-
-  pw_generate_android_toolchains("target_toolchains") {
-    toolchains = pw_target_toolchain_android_list
-  }
-}
-
-pw_doc_group("target_docs") {
-  sources = [ "target_docs.rst" ]
-}
diff --git a/targets/android/OWNERS b/targets/android/OWNERS
deleted file mode 100644
index c224618..0000000
--- a/targets/android/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-keybuk@google.com
diff --git a/targets/android/target_docs.rst b/targets/android/target_docs.rst
deleted file mode 100644
index b8a92a7..0000000
--- a/targets/android/target_docs.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-.. _target-android:
-
--------
-Android
--------
-The Android target supports building Pigweed on devices using an Android
-Native Development Kit (NDK).
-
-.. warning::
-  This target is under construction, not ready for use, and the documentation
-  is incomplete.
diff --git a/targets/android/target_toolchains.gni b/targets/android/target_toolchains.gni
deleted file mode 100644
index ef1a6f7..0000000
--- a/targets/android/target_toolchains.gni
+++ /dev/null
@@ -1,97 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_android_toolchain/android.gni")
-import("$dir_pw_log/backend.gni")
-import("$dir_pw_sys_io/backend.gni")
-
-if (pw_android_toolchain_NDK_PATH != "") {
-  import("$dir_pw_android_toolchain/generate_toolchain.gni")
-  import("$dir_pw_android_toolchain/toolchains.gni")
-
-  _target_config = {
-    # Configuration options for Pigweed executable targets.
-    pw_build_EXECUTABLE_TARGET_TYPE = "executable"
-
-    # Facade backends
-    pw_assert_BACKEND = dir_pw_assert_basic
-    pw_log_BACKEND = dir_pw_log_basic
-    pw_sys_io_BACKEND = dir_pw_sys_io_stdio
-
-    pw_build_LINK_DEPS = []
-    pw_build_LINK_DEPS += [
-      "$dir_pw_assert:impl",
-      "$dir_pw_log:impl",
-    ]
-  }
-
-  _toolchain_properties = {
-  }
-
-  _target_default_configs = []
-
-  pw_target_toolchain_android = {
-    _excluded_members = [
-      "defaults",
-      "name",
-    ]
-
-    debug = {
-      name = "android_debug"
-      _toolchain_base = pw_toolchain_android.debug
-      forward_variables_from(_toolchain_base, "*", _excluded_members)
-      forward_variables_from(_toolchain_properties, "*")
-      defaults = {
-        forward_variables_from(_toolchain_base.defaults, "*")
-        forward_variables_from(_target_config, "*")
-        default_configs += _target_default_configs
-      }
-    }
-
-    speed_optimized = {
-      name = "android_speed_optimized"
-      _toolchain_base = pw_toolchain_android.speed_optimized
-      forward_variables_from(_toolchain_base, "*", _excluded_members)
-      forward_variables_from(_toolchain_properties, "*")
-      defaults = {
-        forward_variables_from(_toolchain_base.defaults, "*")
-        forward_variables_from(_target_config, "*")
-        default_configs += _target_default_configs
-      }
-    }
-
-    size_optimized = {
-      name = "android_size_optimized"
-      _toolchain_base = pw_toolchain_android.size_optimized
-      forward_variables_from(_toolchain_base, "*", _excluded_members)
-      forward_variables_from(_toolchain_properties, "*")
-      defaults = {
-        forward_variables_from(_toolchain_base.defaults, "*")
-        forward_variables_from(_target_config, "*")
-        default_configs += _target_default_configs
-      }
-    }
-  }
-
-  # This list just contains the members of the above scope for convenience to make
-  # it trivial to generate all the toolchains in this file via a
-  # `pw_generate_android_toolchains` target.
-  pw_target_toolchain_android_list = [
-    pw_target_toolchain_android.debug,
-    pw_target_toolchain_android.speed_optimized,
-    pw_target_toolchain_android.size_optimized,
-  ]
-}
diff --git a/targets/arduino/BUILD b/targets/arduino/BUILD
new file mode 100644
index 0000000..7514235
--- /dev/null
+++ b/targets/arduino/BUILD
@@ -0,0 +1,42 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pre_init",
+    srcs = [
+        "init.cc",
+    ],
+    deps = [
+        "//pw_preprocessor",
+        "//pw_sys_io_arduino",
+    ],
+)
+
+pw_cc_library(
+    name = "system_rpc_server",
+    srcs = ["system_rpc_server.cc"],
+    deps = [
+        "//pw_rpc/system_server:facade",
+        "//pw_hdlc:pw_rpc",
+    ],
+)
\ No newline at end of file
diff --git a/targets/arduino/BUILD.bazel b/targets/arduino/BUILD.bazel
deleted file mode 100644
index fcfb660..0000000
--- a/targets/arduino/BUILD.bazel
+++ /dev/null
@@ -1,42 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pre_init",
-    srcs = [
-        "init.cc",
-    ],
-    deps = [
-        "//pw_preprocessor",
-        "//pw_sys_io_arduino",
-    ],
-)
-
-pw_cc_library(
-    name = "system_rpc_server",
-    srcs = ["system_rpc_server.cc"],
-    deps = [
-        "//pw_hdlc:pw_rpc",
-        "//pw_rpc/system_server:facade",
-    ],
-)
diff --git a/targets/arduino/BUILD.gn b/targets/arduino/BUILD.gn
index 0032273..7217dff 100644
--- a/targets/arduino/BUILD.gn
+++ b/targets/arduino/BUILD.gn
@@ -32,7 +32,7 @@
   if (current_toolchain != default_toolchain) {
     config("arduino_build") {
       # Debug: Print out arduinobuilder.py args
-      # print(string_join(" ", [rebase_path(arduino_builder_script, root_build_dir)] + arduino_show_command_args))
+      # print(string_join(" ", [rebase_path(arduino_builder_script)] + arduino_show_command_args))
 
       # Run prebuilds
       # TODO(tonymd) This only needs to be run once but it's happening multiple times.
diff --git a/targets/arduino/OWNERS b/targets/arduino/OWNERS
deleted file mode 100644
index ca011e8..0000000
--- a/targets/arduino/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-tonymd@google.com
diff --git a/targets/arduino/init.cc b/targets/arduino/init.cc
index 41a1676..8ea12c9 100644
--- a/targets/arduino/init.cc
+++ b/targets/arduino/init.cc
@@ -19,4 +19,4 @@
 // Arduino target specific init. For Pigweed, this calls pw_sys_io's init. User
 // projects may chose to provide something different if they need more pre-main
 // init functionality.
-extern "C" void pw_arduino_Init() { pw_sys_io_arduino_Init(); }
+extern "C" void pw_arduino_Init() { pw_sys_io_Init(); }
diff --git a/targets/arduino/system_rpc_server.cc b/targets/arduino/system_rpc_server.cc
index 52be514..00b9bf9 100644
--- a/targets/arduino/system_rpc_server.cc
+++ b/targets/arduino/system_rpc_server.cc
@@ -30,9 +30,8 @@
 stream::SysIoReader reader;
 
 // Set up the output channel for the pw_rpc server to use.
-hdlc::RpcChannelOutput hdlc_channel_output(writer,
-                                           pw::hdlc::kDefaultRpcAddress,
-                                           "HDLC channel");
+hdlc::RpcChannelOutputBuffer<kMaxTransmissionUnit> hdlc_channel_output(
+    writer, pw::hdlc::kDefaultRpcAddress, "HDLC channel");
 Channel channels[] = {pw::rpc::Channel::Create<1>(&hdlc_channel_output)};
 rpc::Server server(channels);
 
diff --git a/targets/arduino/target_docs.rst b/targets/arduino/target_docs.rst
index 379da1b..a6f6b0b 100644
--- a/targets/arduino/target_docs.rst
+++ b/targets/arduino/target_docs.rst
@@ -217,7 +217,7 @@
 
   _library_args = [
     "--library-path",
-    rebase_path(arduino_core_library_path, root_build_dir),
+    rebase_path(arduino_core_library_path),
     "--library-names",
     "Time",
     "Wire",
diff --git a/targets/arduino/target_toolchains.gni b/targets/arduino/target_toolchains.gni
index dfc9b83..469a95f 100644
--- a/targets/arduino/target_toolchains.gni
+++ b/targets/arduino/target_toolchains.gni
@@ -32,7 +32,7 @@
       get_path_info("arduino_executable.gni", "abspath")
 
   # Path to the bloaty config file for the output binaries.
-  pw_bloat_BLOATY_CONFIG = "$dir_pw_boot_cortex_m/bloaty_config.bloaty"
+  pw_bloat_BLOATY_CONFIG = "$dir_pw_boot_armv7m/bloaty_config.bloaty"
 
   if (pw_arduino_use_test_server) {
     _test_runner_script =
@@ -46,17 +46,11 @@
   pw_log_BACKEND = dir_pw_log_basic
   pw_sync_INTERRUPT_SPIN_LOCK_BACKEND =
       "$dir_pw_sync_baremetal:interrupt_spin_lock"
-  pw_sync_MUTEX_BACKEND = "$dir_pw_sync_baremetal:mutex"
   pw_sys_io_BACKEND = dir_pw_sys_io_arduino
   pw_rpc_system_server_BACKEND =
       "$dir_pigweed/targets/arduino:system_rpc_server"
   pw_arduino_build_INIT_BACKEND = "$dir_pigweed/targets/arduino:pre_init"
 
-  pw_build_LINK_DEPS = [
-    "$dir_pw_assert:impl",
-    "$dir_pw_log:impl",
-  ]
-
   current_cpu = "arm"
   current_os = ""
 }
diff --git a/targets/default_config.BUILD b/targets/default_config.BUILD
deleted file mode 100644
index 2ed1d01..0000000
--- a/targets/default_config.BUILD
+++ /dev/null
@@ -1,135 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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(default_visibility = ["//visibility:public"])
-
-label_flag(
-    name = "pw_log_backend",
-    build_setting_default = "@pigweed//pw_log:backend_multiplexer",
-)
-
-label_flag(
-    name = "pw_log_string_handler_backend",
-    build_setting_default = "@pigweed//pw_log_string:handler_backend_multiplexer",
-)
-
-label_flag(
-    name = "pw_assert_backend",
-    build_setting_default = "@pigweed//pw_assert:backend_multiplexer",
-)
-
-label_flag(
-    name = "pw_boot_backend",
-    build_setting_default = "@pigweed//pw_boot:backend_multiplexer",
-)
-
-label_flag(
-    name = "pw_chrono_system_clock_backend",
-    build_setting_default = "@pigweed//pw_chrono:system_clock_backend_multiplexer",
-)
-
-label_flag(
-    name = "pw_chrono_system_timer_backend",
-    build_setting_default = "@pigweed//pw_chrono:system_timer_backend_multiplexer",
-)
-
-label_flag(
-    name = "pw_rpc_system_server_backend",
-    build_setting_default = "@pigweed//pw_rpc/system_server:system_server_backend_multiplexer",
-)
-
-label_flag(
-    name = "pw_sync_binary_semaphore_backend",
-    build_setting_default = "@pigweed//pw_sync:binary_semaphore_backend_multiplexer",
-)
-
-label_flag(
-    name = "pw_sync_counting_semaphore_backend",
-    build_setting_default = "@pigweed//pw_sync:counting_semaphore_backend_multiplexer",
-)
-
-label_flag(
-    name = "pw_sync_mutex_backend",
-    build_setting_default = "@pigweed//pw_sync:mutex_backend_multiplexer",
-)
-
-label_flag(
-    name = "pw_sync_timed_mutex_backend",
-    build_setting_default = "@pigweed//pw_sync:timed_mutex_backend_multiplexer",
-)
-
-label_flag(
-    name = "pw_sync_interrupt_spin_lock_backend",
-    build_setting_default = "@pigweed//pw_sync:interrupt_spin_lock_backend_multiplexer",
-)
-
-label_flag(
-    name = "pw_sync_thread_notification_backend",
-    build_setting_default = "@pigweed//pw_sync:thread_notification_backend_multiplexer",
-)
-
-label_flag(
-    name = "pw_sync_timed_thread_notification_backend",
-    build_setting_default = "@pigweed//pw_sync:timed_thread_notification_backend_multiplexer",
-)
-
-label_flag(
-    name = "pw_interrupt_backend",
-    build_setting_default = "@pigweed//pw_interrupt:backend_multiplexer",
-)
-
-label_flag(
-    name = "pw_malloc_backend",
-    build_setting_default = "@pigweed//pw_malloc:backend_multiplexer",
-)
-
-label_flag(
-    name = "pw_thread_id_backend",
-    build_setting_default = "@pigweed//pw_thread:id_backend_multiplexer",
-)
-
-label_flag(
-    name = "pw_thread_sleep_backend",
-    build_setting_default = "@pigweed//pw_thread:sleep_backend_multiplexer",
-)
-
-label_flag(
-    name = "pw_thread_thread_backend",
-    build_setting_default = "@pigweed//pw_thread:thread_backend_multiplexer",
-)
-
-label_flag(
-    name = "pw_thread_yield_backend",
-    build_setting_default = "@pigweed//pw_thread:yield_backend_multiplexer",
-)
-
-label_flag(
-    name = "pw_tokenizer_global_handler_backend",
-    build_setting_default = "@pigweed//pw_tokenizer:test_backend",
-)
-
-label_flag(
-    name = "pw_tokenizer_global_handler_with_payload_backend",
-    build_setting_default = "@pigweed//pw_tokenizer:test_backend",
-)
-
-label_flag(
-    name = "pw_sys_io_backend",
-    build_setting_default = "@pigweed//pw_sys_io:backend_multiplexer",
-)
-
-label_flag(
-    name = "target_rtos",
-    build_setting_default = "@pigweed//pw_build/constraints/rtos:none",
-)
diff --git a/targets/docs/BUILD.gn b/targets/docs/BUILD.gn
index 8f41247..73b1606 100644
--- a/targets/docs/BUILD.gn
+++ b/targets/docs/BUILD.gn
@@ -14,7 +14,7 @@
 
 import("//build_overrides/pigweed.gni")
 
-import("$dir_pigweed/targets/stm32f429i_disc1/target_toolchains.gni")
+import("$dir_pigweed/targets/stm32f429i-disc1/target_toolchains.gni")
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_protobuf_compiler/proto.gni")
 import("$dir_pw_third_party/nanopb/nanopb.gni")
diff --git a/targets/docs/OWNERS b/targets/docs/OWNERS
deleted file mode 100644
index d96cbc6..0000000
--- a/targets/docs/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-hepler@google.com
diff --git a/targets/emcraft_sf2_som/BUILD.bazel b/targets/emcraft_sf2_som/BUILD.bazel
deleted file mode 100644
index 26d0426..0000000
--- a/targets/emcraft_sf2_som/BUILD.bazel
+++ /dev/null
@@ -1,58 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_binary",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pre_init",
-    srcs = [
-        "boot.cc",
-        "vector_table.c",
-    ],
-    hdrs = [
-        "config/FreeRTOSConfig.h",
-        "config/sf2_mss_hal_conf.h",
-    ],
-    deps = [
-        "//pw_boot",
-        "//pw_boot_cortex_m",
-        "//pw_malloc",
-        "//pw_preprocessor",
-        "//pw_string",
-        "//pw_sys_io_emcraft_sf2",
-        "//third_party/freertos",
-        "//third_party/smartfusion_mss",
-    ],
-)
-
-pw_cc_binary(
-    name = "demo",
-    srcs = [
-        "main.cc",
-    ],
-    deps = [
-        "//pw_thread:thread",
-        "//pw_thread:thread_core",
-        "//pw_thread_freertos:thread",
-        "//third_party/freertos",
-    ],
-)
diff --git a/targets/emcraft_sf2_som/BUILD.gn b/targets/emcraft_sf2_som/BUILD.gn
deleted file mode 100644
index 9d77869..0000000
--- a/targets/emcraft_sf2_som/BUILD.gn
+++ /dev/null
@@ -1,149 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_malloc/backend.gni")
-import("$dir_pw_system/system_target.gni")
-import("$dir_pw_third_party/smartfusion_mss/mss.gni")
-import("$dir_pw_tokenizer/backend.gni")
-import("$dir_pw_toolchain/generate_toolchain.gni")
-
-config("pw_malloc_active") {
-  if (pw_malloc_BACKEND != "") {
-    defines = [ "PW_MALLOC_ACTIVE=1" ]
-  }
-}
-
-if (current_toolchain != default_toolchain) {
-  pw_source_set("pre_init") {
-    configs = [ ":pw_malloc_active" ]
-    deps = [
-      "$dir_pw_boot",
-      "$dir_pw_boot_cortex_m",
-      "$dir_pw_malloc",
-      "$dir_pw_preprocessor",
-      "$dir_pw_string",
-      "$dir_pw_sys_io_emcraft_sf2",
-      "$dir_pw_system",
-      "$dir_pw_third_party/freertos",
-    ]
-    sources = [
-      "boot.cc",
-      "vector_table.c",
-    ]
-  }
-
-  config("config_includes") {
-    include_dirs = [ "config" ]
-  }
-
-  pw_source_set("sf2_mss_hal_config") {
-    public_configs = [ ":config_includes" ]
-    public =
-        [ "config/sf2_mss_hal_conf.h" ]  # SKEYS likely want to put the MDDR
-                                         # config by cortex etc stuff here
-  }
-
-  pw_source_set("sf2_freertos_config") {
-    public_configs = [ ":config_includes" ]
-    public_deps = [ "$dir_pw_third_party/freertos:config_assert" ]
-    public = [ "config/FreeRTOSConfig.h" ]
-  }
-}
-
-# Configured for use with a first stage boot loader to configure DDR and
-# perform memory remapping.
-pw_system_target("emcraft_sf2_som") {
-  cpu = PW_SYSTEM_CPU.CORTEX_M3
-  scheduler = PW_SYSTEM_SCHEDULER.FREERTOS
-  link_deps = [ "$dir_pigweed/targets/emcraft_sf2_som:pre_init" ]
-
-  build_args = {
-    pw_log_BACKEND = dir_pw_log_tokenized
-    pw_tokenizer_GLOBAL_HANDLER_WITH_PAYLOAD_BACKEND =
-        "$dir_pw_system:log_backend.impl"
-    pw_third_party_freertos_CONFIG =
-        "$dir_pigweed/targets/emcraft_sf2_som:sf2_freertos_config"
-    pw_third_party_freertos_PORT = "$dir_pw_third_party/freertos:arm_cm3"
-    pw_sys_io_BACKEND = dir_pw_sys_io_emcraft_sf2
-
-    # Non-debug build for use with the boot loader.
-    pw_boot_cortex_m_LINK_CONFIG_DEFINES = [
-      "PW_BOOT_FLASH_BEGIN=0x00000200",  # After vector table.
-
-      # TODO(skeys) Bootloader is capable of loading 16M of uncompressed code
-      # from SPI flash to external RAM. For now use the allocated eNVM flash
-      # (256K - Bootloader - InSystemProgrammer = 192K)
-      "PW_BOOT_FLASH_SIZE=0x30000",
-
-      # TODO(pwbug/219): Currently "pw_tokenizer/detokenize_test" requires at
-      # least 6K bytes in heap when using pw_malloc_freelist. The heap size
-      # required for tests should be investigated.
-      "PW_BOOT_HEAP_SIZE=4M",
-
-      # With external RAM remapped, we use the entire internal ram for the
-      # stack (64K).
-      "PW_BOOT_MIN_STACK_SIZE=1024K",
-
-      # Using external DDR RAM, we just need to make sure we go past our ROM
-      # sections.
-      "PW_BOOT_RAM_BEGIN=0xA1000000",
-
-      # We assume that the bootloader loaded all 16M of text.
-      "PW_BOOT_RAM_SIZE=48M",
-      "PW_BOOT_VECTOR_TABLE_BEGIN=0x00000000",
-      "PW_BOOT_VECTOR_TABLE_SIZE=512",
-    ]
-  }
-}
-
-# Debug target configured to work with MSS linker script and startup code.
-# TODO(skeys) Add linker script and config for debug builds using SoftConsole.
-pw_system_target("emcraft_sf2_som_debug") {
-  cpu = PW_SYSTEM_CPU.CORTEX_M3
-  scheduler = PW_SYSTEM_SCHEDULER.FREERTOS
-  link_deps = [ "$dir_pigweed/targets/emcraft_sf2_som:pre_init" ]
-
-  build_args = {
-    pw_log_BACKEND = dir_pw_log_tokenized
-    pw_tokenizer_GLOBAL_HANDLER_WITH_PAYLOAD_BACKEND =
-        "$dir_pw_system:log_backend.impl"
-    pw_third_party_freertos_CONFIG =
-        "$dir_pigweed/targets/emcraft_sf2_som:sf2_freertos_config"
-    pw_third_party_freertos_PORT = "$dir_pw_third_party/freertos:arm_cm3"
-    pw_sys_io_BACKEND = dir_pw_sys_io_emcraft_sf2
-
-    pw_boot_cortex_m_LINK_CONFIG_DEFINES = [
-      "PW_BOOT_FLASH_BEGIN=0x00000200",
-      "PW_BOOT_FLASH_SIZE=200K",
-
-      # TODO(pwbug/219): Currently "pw_tokenizer/detokenize_test" requires at
-      # least 6K bytes in heap when using pw_malloc_freelist. The heap size
-      # required for tests should be investigated.
-      "PW_BOOT_HEAP_SIZE=7K",
-      "PW_BOOT_MIN_STACK_SIZE=1K",
-      "PW_BOOT_RAM_BEGIN=0x20000000",
-      "PW_BOOT_RAM_SIZE=64K",
-      "PW_BOOT_VECTOR_TABLE_BEGIN=0x00000000",
-      "PW_BOOT_VECTOR_TABLE_SIZE=512",
-    ]
-  }
-}
-
-pw_doc_group("docs") {
-  sources = [ "target_docs.rst" ]
-}
diff --git a/targets/emcraft_sf2_som/boot.cc b/targets/emcraft_sf2_som/boot.cc
deleted file mode 100644
index bb786c8..0000000
--- a/targets/emcraft_sf2_som/boot.cc
+++ /dev/null
@@ -1,195 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_boot/boot.h"
-
-#include <array>
-
-#include "FreeRTOS.h"
-#include "config/sf2_mss_hal_conf.h"
-#include "m2sxxx.h"
-#include "pw_boot_cortex_m/boot.h"
-#include "pw_malloc/malloc.h"
-#include "pw_preprocessor/compiler.h"
-#include "pw_string/util.h"
-#include "pw_sys_io_emcraft_sf2/init.h"
-#include "pw_system/init.h"
-#include "system_m2sxxx.h"
-#include "task.h"
-
-#include liberosoc_CONFIG_FILE
-
-namespace {
-
-std::array<StackType_t, configMINIMAL_STACK_SIZE> freertos_idle_stack;
-StaticTask_t freertos_idle_tcb;
-
-std::array<StackType_t, configTIMER_TASK_STACK_DEPTH> freertos_timer_stack;
-StaticTask_t freertos_timer_tcb;
-
-std::array<char, configMAX_TASK_NAME_LEN> temp_thread_name_buffer;
-
-}  // namespace
-
-// Functions needed when configGENERATE_RUN_TIME_STATS is on.
-extern "C" void configureTimerForRunTimeStats(void) {}
-extern "C" unsigned long getRunTimeCounterValue(void) { return 10 /* FIXME */; }
-// uwTick is an uint32_t incremented each Systick interrupt 1ms. uwTick is used
-// to execute HAL_Delay function.
-
-// Required for configCHECK_FOR_STACK_OVERFLOW.
-extern "C" void vApplicationStackOverflowHook(TaskHandle_t, char* pcTaskName) {
-  pw::string::Copy(pcTaskName, temp_thread_name_buffer);
-  PW_CRASH("Stack OVF for task %s", temp_thread_name_buffer.data());
-}
-
-// Required for configUSE_TIMERS.
-extern "C" void vApplicationGetTimerTaskMemory(
-    StaticTask_t** ppxIdleTaskTCBBuffer,
-    StackType_t** ppxIdleTaskStackBuffer,
-    uint32_t* pulIdleTaskStackSize) {
-  *ppxIdleTaskTCBBuffer = &freertos_idle_tcb;
-  *ppxIdleTaskStackBuffer = freertos_idle_stack.data();
-  *pulIdleTaskStackSize = freertos_idle_stack.size();
-}
-
-extern "C" void vApplicationGetIdleTaskMemory(
-    StaticTask_t** ppxIdleTaskTCBBuffer,
-    StackType_t** ppxIdleTaskStackBuffer,
-    uint32_t* pulIdleTaskStackSize) {
-  *ppxIdleTaskTCBBuffer = &freertos_timer_tcb;
-  *ppxIdleTaskStackBuffer = freertos_timer_stack.data();
-  *pulIdleTaskStackSize = freertos_timer_stack.size();
-}
-
-extern "C" void pw_boot_PreStaticMemoryInit() {
-#if SF2_MSS_NO_BOOTLOADER
-  SystemInit();
-  // Initialize DDR
-  // inclusive-language: disable
-  MDDR->core.ddrc.DYN_SOFT_RESET_CR = 0x0000;
-  MDDR->core.ddrc.DYN_REFRESH_1_CR = 0x27de;
-  MDDR->core.ddrc.DYN_REFRESH_2_CR = 0x030f;
-  MDDR->core.ddrc.DYN_POWERDOWN_CR = 0x0002;
-  MDDR->core.ddrc.DYN_DEBUG_CR = 0x0000;
-  MDDR->core.ddrc.MODE_CR = 0x00C1;
-  MDDR->core.ddrc.ADDR_MAP_BANK_CR = 0x099f;
-  MDDR->core.ddrc.ECC_DATA_MASK_CR = 0x0000;
-  MDDR->core.ddrc.ADDR_MAP_COL_1_CR = 0x3333;
-  MDDR->core.ddrc.ADDR_MAP_COL_2_CR = 0xffff;
-  MDDR->core.ddrc.ADDR_MAP_ROW_1_CR = 0x7777;
-  MDDR->core.ddrc.ADDR_MAP_ROW_2_CR = 0x0fff;
-  MDDR->core.ddrc.INIT_1_CR = 0x0001;
-  MDDR->core.ddrc.CKE_RSTN_CYCLES_CR[0] = 0x4242;
-  MDDR->core.ddrc.CKE_RSTN_CYCLES_CR[1] = 0x0008;
-  MDDR->core.ddrc.INIT_MR_CR = 0x0033;
-  MDDR->core.ddrc.INIT_EMR_CR = 0x0020;
-  MDDR->core.ddrc.INIT_EMR2_CR = 0x0000;
-  MDDR->core.ddrc.INIT_EMR3_CR = 0x0000;
-  MDDR->core.ddrc.DRAM_BANK_TIMING_PARAM_CR = 0x00c0;
-  MDDR->core.ddrc.DRAM_RD_WR_LATENCY_CR = 0x0023;
-  MDDR->core.ddrc.DRAM_RD_WR_PRE_CR = 0x0235;
-  MDDR->core.ddrc.DRAM_MR_TIMING_PARAM_CR = 0x0064;
-  MDDR->core.ddrc.DRAM_RAS_TIMING_CR = 0x0108;
-  MDDR->core.ddrc.DRAM_RD_WR_TRNARND_TIME_CR = 0x0178;
-  MDDR->core.ddrc.DRAM_T_PD_CR = 0x0033;
-  MDDR->core.ddrc.DRAM_BANK_ACT_TIMING_CR = 0x1947;
-  MDDR->core.ddrc.ODT_PARAM_1_CR = 0x0010;
-  MDDR->core.ddrc.ODT_PARAM_2_CR = 0x0000;
-  MDDR->core.ddrc.ADDR_MAP_COL_3_CR = 0x3300;
-  MDDR->core.ddrc.MODE_REG_RD_WR_CR = 0x0000;
-  MDDR->core.ddrc.MODE_REG_DATA_CR = 0x0000;
-  MDDR->core.ddrc.PWR_SAVE_1_CR = 0x0514;
-  MDDR->core.ddrc.PWR_SAVE_2_CR = 0x0000;
-  MDDR->core.ddrc.ZQ_LONG_TIME_CR = 0x0200;
-  MDDR->core.ddrc.ZQ_SHORT_TIME_CR = 0x0040;
-  MDDR->core.ddrc.ZQ_SHORT_INT_REFRESH_MARGIN_CR[0] = 0x0012;
-  MDDR->core.ddrc.ZQ_SHORT_INT_REFRESH_MARGIN_CR[1] = 0x0002;
-  MDDR->core.ddrc.PERF_PARAM_1_CR = 0x4000;
-  MDDR->core.ddrc.HPR_QUEUE_PARAM_CR[0] = 0x80f8;
-  MDDR->core.ddrc.HPR_QUEUE_PARAM_CR[1] = 0x0007;
-  MDDR->core.ddrc.LPR_QUEUE_PARAM_CR[0] = 0x80f8;
-  MDDR->core.ddrc.LPR_QUEUE_PARAM_CR[1] = 0x0007;
-  MDDR->core.ddrc.WR_QUEUE_PARAM_CR = 0x0200;
-  MDDR->core.ddrc.PERF_PARAM_2_CR = 0x0001;
-  MDDR->core.ddrc.PERF_PARAM_3_CR = 0x0000;
-  MDDR->core.ddrc.DFI_RDDATA_EN_CR = 0x0003;
-  MDDR->core.ddrc.DFI_MIN_CTRLUPD_TIMING_CR = 0x0003;
-  MDDR->core.ddrc.DFI_MAX_CTRLUPD_TIMING_CR = 0x0040;
-  MDDR->core.ddrc.DFI_WR_LVL_CONTROL_CR[0] = 0x0000;
-  MDDR->core.ddrc.DFI_WR_LVL_CONTROL_CR[1] = 0x0000;
-  MDDR->core.ddrc.DFI_RD_LVL_CONTROL_CR[0] = 0x0000;
-  MDDR->core.ddrc.DFI_RD_LVL_CONTROL_CR[1] = 0x0000;
-  MDDR->core.ddrc.DFI_CTRLUPD_TIME_INTERVAL_CR = 0x0309;
-  MDDR->core.ddrc.AXI_FABRIC_PRI_ID_CR = 0x0000;
-  MDDR->core.ddrc.ECC_INT_CLR_REG = 0x0000;
-
-  MDDR->core.phy.LOOPBACK_TEST_CR = 0x0000;
-  MDDR->core.phy.CTRL_SLAVE_RATIO_CR = 0x0080;
-  MDDR->core.phy.DATA_SLICE_IN_USE_CR = 0x0003;
-  MDDR->core.phy.DQ_OFFSET_CR[0] = 0x00000000;
-  MDDR->core.phy.DQ_OFFSET_CR[2] = 0x0000;
-  MDDR->core.phy.DLL_LOCK_DIFF_CR = 0x000B;
-  MDDR->core.phy.FIFO_WE_SLAVE_RATIO_CR[0] = 0x0040;
-  MDDR->core.phy.FIFO_WE_SLAVE_RATIO_CR[1] = 0x0401;
-  MDDR->core.phy.FIFO_WE_SLAVE_RATIO_CR[2] = 0x4010;
-  MDDR->core.phy.FIFO_WE_SLAVE_RATIO_CR[3] = 0x0000;
-  MDDR->core.phy.LOCAL_ODT_CR = 0x0001;
-  MDDR->core.phy.RD_DQS_SLAVE_RATIO_CR[0] = 0x0040;
-  MDDR->core.phy.RD_DQS_SLAVE_RATIO_CR[1] = 0x0401;
-  MDDR->core.phy.RD_DQS_SLAVE_RATIO_CR[2] = 0x4010;
-  MDDR->core.phy.WR_DATA_SLAVE_RATIO_CR[0] = 0x0040;
-  MDDR->core.phy.WR_DATA_SLAVE_RATIO_CR[1] = 0x0401;
-  MDDR->core.phy.WR_DATA_SLAVE_RATIO_CR[2] = 0x4010;
-  MDDR->core.phy.WR_RD_RL_CR = 0x0021;
-  MDDR->core.phy.RDC_WE_TO_RE_DELAY_CR = 0x0003;
-  MDDR->core.phy.USE_FIXED_RE_CR = 0x0001;
-  MDDR->core.phy.USE_RANK0_DELAYS_CR = 0x0001;
-  MDDR->core.phy.CONFIG_CR = 0x0009;
-  MDDR->core.phy.DYN_RESET_CR = 0x01;
-  MDDR->core.ddrc.DYN_SOFT_RESET_CR = 0x01;
-  // inclusive-language: enable
-  // Wait for config
-  while ((MDDR->core.ddrc.DDRC_SR) == 0x0000) {
-  }
-#endif
-}
-
-extern "C" void pw_boot_PreStaticConstructorInit() {
-  // TODO(skeys) add "#if no_bootLoader" and the functions needed for init.
-
-#if PW_MALLOC_ACTIVE
-  pw_MallocInit(&pw_boot_heap_low_addr, &pw_boot_heap_high_addr);
-#endif  // PW_MALLOC_ACTIVE
-  pw_sys_io_Init();
-}
-
-// TODO(amontanez): pw_boot_PreMainInit() should get renamed to
-// pw_boot_FinalizeBoot or similar when main() is removed.
-extern "C" void pw_boot_PreMainInit() {
-  pw::system::Init();
-  vTaskStartScheduler();
-  PW_UNREACHABLE;
-}
-
-// This `main()` stub prevents another main function from being linked since
-// this target deliberately doesn't run `main()`.
-extern "C" int main() {}
-
-extern "C" PW_NO_RETURN void pw_boot_PostMain() {
-  // In case main() returns, just sit here until the device is reset.
-  while (true) {
-  }
-  PW_UNREACHABLE;
-}
diff --git a/targets/emcraft_sf2_som/config/FreeRTOSConfig.h b/targets/emcraft_sf2_som/config/FreeRTOSConfig.h
deleted file mode 100644
index c024e3e..0000000
--- a/targets/emcraft_sf2_som/config/FreeRTOSConfig.h
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <stdint.h>
-
-// Externally defined variables that must be forward-declared for FreeRTOS to
-// use them.
-extern uint32_t SystemCoreClock;
-extern void configureTimerForRunTimeStats(void);
-extern unsigned long getRunTimeCounterValue(void);
-
-#define configSUPPORT_DYNAMIC_ALLOCATION 0
-#define configSUPPORT_STATIC_ALLOCATION 1
-
-#define configUSE_16_BIT_TICKS 0
-#define configUSE_CO_ROUTINES 0
-#define configUSE_IDLE_HOOK 0
-#define configUSE_MUTEXES 1
-#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1
-#define configUSE_PREEMPTION 1
-#define configUSE_TICK_HOOK 0
-#define configUSE_TIMERS 1
-#define configUSE_TRACE_FACILITY 1
-
-#define configGENERATE_RUN_TIME_STATS 1
-#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS configureTimerForRunTimeStats
-#define portGET_RUN_TIME_COUNTER_VALUE getRunTimeCounterValue
-
-#define configCHECK_FOR_STACK_OVERFLOW 2
-#define configCPU_CLOCK_HZ (SystemCoreClock)
-#define configENABLE_BACKWARD_COMPATIBILITY 0
-#define configMAX_CO_ROUTINE_PRIORITIES (2)
-#define configMAX_PRIORITIES (7)
-#define configMAX_TASK_NAME_LEN (16)
-#define configMESSAGE_BUFFER_LENGTH_TYPE size_t
-#define configMINIMAL_STACK_SIZE ((uint16_t)128)
-#define configQUEUE_REGISTRY_SIZE 8
-#define configRECORD_STACK_HIGH_ADDRESS 1
-#define configTICK_RATE_HZ ((TickType_t)1000)
-#define configTIMER_QUEUE_LENGTH 10
-#define configTIMER_TASK_PRIORITY (6)
-#define configTIMER_TASK_STACK_DEPTH 512
-
-/* __NVIC_PRIO_BITS in CMSIS */
-#define configPRIO_BITS 4
-
-#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15
-#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
-#define configKERNEL_INTERRUPT_PRIORITY \
-  (configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))
-#define configMAX_SYSCALL_INTERRUPT_PRIORITY \
-  (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))
-
-#define INCLUDE_uxTaskPriorityGet 1
-#define INCLUDE_vTaskCleanUpResources 0
-#define INCLUDE_vTaskDelay 1
-#define INCLUDE_vTaskDelayUntil 0
-#define INCLUDE_vTaskDelete 1
-#define INCLUDE_vTaskPrioritySet 1
-#define INCLUDE_vTaskSuspend 1
-#define INCLUDE_xTaskGetSchedulerState 1
-
-// Instead of defining configASSERT(), include a header that provides a
-// definition that redirects to pw_assert.
-#include "pw_third_party/freertos/config_assert.h"
-
-#define vPortSVCHandler SVC_Handler
-#define xPortPendSVHandler PendSV_Handler
-#define xPortSysTickHandler SysTick_Handler
diff --git a/targets/emcraft_sf2_som/config/sf2_mss_hal_conf.h b/targets/emcraft_sf2_som/config/sf2_mss_hal_conf.h
deleted file mode 100644
index 0e82d68..0000000
--- a/targets/emcraft_sf2_som/config/sf2_mss_hal_conf.h
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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
-
-#if (MSS_SYS_MDDR_CONFIG_BY_CORTEX == 1)
-#error "Please turn off DDR initialization! See the comment in this file above."
-#endif
-
-#define HAL_GPIO_MODULE_ENABLED
-#include "mss_gpio/mss_gpio.h"
-
-#define HAL_UART_MODULE_ENABLED
-#include "mss_uart/mss_uart.h"
diff --git a/targets/emcraft_sf2_som/target_docs.rst b/targets/emcraft_sf2_som/target_docs.rst
deleted file mode 100644
index adcc077..0000000
--- a/targets/emcraft_sf2_som/target_docs.rst
+++ /dev/null
@@ -1,35 +0,0 @@
-.. _target-emcraft-sf2-som:
-
--------------------------------------
-_target-emcraft-sf2-som: SmartFusion2
--------------------------------------
-The Emcraft SmartFusion2 system-on-module target configuration
-uses FreeRTOS and the Microchip MSS HAL rather than a from-the-ground-up
-baremetal approach.
-
------
-Setup
------
-To use this target, pigweed must be set up to use FreeRTOS and the Microchip
-MSS HAL for the SmartFusion series. The supported repositories can be
-downloaded via ``pw package``, and then the build must be manually configured
-to point to the locations the repositories were downloaded to.
-
-.. code:: sh
-
-  pw package install freertos
-  pw package install smartfusion_mss
-  pw package install nanopb
-
-  gn args out
-    # Add these lines, replacing ${PW_ROOT} with the path to the location that
-    # Pigweed is checked out at.
-    dir_pw_third_party_freertos = "${PW_ROOT}/.environment/packages/freertos"
-    dir_pw_third_party_smartfusion_mss =
-      "${PW_ROOT}/.environment/packages/smartfusion_mss"
-    dir_pw_third_party_nanopb = "${PW_ROOT}/.environment/packages/nanopb"
-
-Building and running the demo
-=============================
-This target does not yet build as part of Pigweed, but will later be
-available though the pw_system_demo build target.
diff --git a/targets/emcraft_sf2_som/vector_table.c b/targets/emcraft_sf2_som/vector_table.c
deleted file mode 100644
index a4f5f7f..0000000
--- a/targets/emcraft_sf2_som/vector_table.c
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <stdbool.h>
-
-#include "pw_boot/boot.h"
-#include "pw_boot_cortex_m/boot.h"
-#include "pw_preprocessor/compiler.h"
-
-// Default handler to insert into the ARMv7-M vector table (below).
-// This function exists for convenience. If a device isn't doing what you
-// expect, it might have hit a fault and ended up here.
-static void DefaultFaultHandler(void) {
-  while (true) {
-    // Wait for debugger to attach.
-  }
-}
-
-// This is the device's interrupt vector table. It's not referenced in any
-// code because the platform (SmartFusion) expects this table to be present at
-// the beginning of flash. The exact address is specified in the pw_boot_armv7m
-// configuration as part of the target config.
-//
-// For more information, see ARMv7-M Architecture Reference Manual DDI 0403E.b
-// section B1.5.3.
-
-// This typedef is for convenience when building the vector table. With the
-// exception of SP_main (0th entry in the vector table), all the entries of the
-// vector table are function pointers.
-typedef void (*InterruptHandler)(void);
-
-// Interrupt handlers critical for OS operation.
-void SVC_Handler(void);
-void PendSV_Handler(void);
-void SysTick_Handler(void);
-
-PW_KEEP_IN_SECTION(".vector_table")
-const InterruptHandler vector_table[] = {
-    // The starting location of the stack pointer.
-    // This address is NOT an interrupt handler/function pointer, it is simply
-    // the address that the main stack pointer should be initialized to. The
-    // value is reinterpret casted because it needs to be in the vector table.
-    [0] = (InterruptHandler)(&pw_boot_stack_high_addr),
-
-    // Reset handler, dictates how to handle reset interrupt. This is the
-    // address that the Program Counter (PC) is initialized to at boot.
-    [1] = pw_boot_Entry,
-
-    // NMI handler.
-    [2] = DefaultFaultHandler,
-    // HardFault handler.
-    [3] = DefaultFaultHandler,
-    // 4-6: Specialized fault handlers.
-    // 7-10: Reserved.
-    // SVCall handler.
-    [11] = SVC_Handler,
-    // DebugMon handler.
-    [12] = DefaultFaultHandler,
-    // 13: Reserved.
-    // PendSV handler.
-    [14] = PendSV_Handler,
-    // SysTick handler.
-    [15] = SysTick_Handler,
-};
diff --git a/targets/host/BUILD b/targets/host/BUILD
new file mode 100644
index 0000000..20fd666
--- /dev/null
+++ b/targets/host/BUILD
@@ -0,0 +1,32 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "system_rpc_server",
+    srcs = ["system_rpc_server.cc"],
+    deps = [
+        "//pw_rpc/system_server:facade",
+        "//pw_hdlc:pw_rpc",
+    ],
+)
+
diff --git a/targets/host/BUILD.bazel b/targets/host/BUILD.bazel
deleted file mode 100644
index 1ea37dc..0000000
--- a/targets/host/BUILD.bazel
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "system_rpc_server",
-    srcs = ["system_rpc_server.cc"],
-    deps = [
-        "//pw_hdlc:pw_rpc",
-        "//pw_hdlc:rpc_channel_output",
-        "//pw_rpc/system_server:facade",
-        "//pw_stream:socket_stream",
-    ],
-)
diff --git a/targets/host/BUILD.gn b/targets/host/BUILD.gn
index b740e62..65f1f43 100644
--- a/targets/host/BUILD.gn
+++ b/targets/host/BUILD.gn
@@ -31,9 +31,9 @@
     deps = [
       "$dir_pw_hdlc:pw_rpc",
       "$dir_pw_hdlc:rpc_channel_output",
+      "$dir_pw_rpc:synchronized_channel_output",
       "$dir_pw_rpc/system_server:facade",
       "$dir_pw_stream:socket_stream",
-      dir_pw_assert,
       dir_pw_log,
     ]
     sources = [ "system_rpc_server.cc" ]
diff --git a/targets/host/CMakeLists.txt b/targets/host/CMakeLists.txt
index dc7d487..dfacc25 100644
--- a/targets/host/CMakeLists.txt
+++ b/targets/host/CMakeLists.txt
@@ -22,5 +22,6 @@
   PRIVATE_DEPS
     pw_hdlc
     pw_rpc.server
+    pw_rpc.synchronized_channel_output
     pw_stream.socket_stream
 )
diff --git a/targets/host/OWNERS b/targets/host/OWNERS
deleted file mode 100644
index d96cbc6..0000000
--- a/targets/host/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-hepler@google.com
diff --git a/targets/host/macos.gni b/targets/host/macos.gni
index 62c9380..5624f26 100644
--- a/targets/host/macos.gni
+++ b/targets/host/macos.gni
@@ -14,6 +14,11 @@
 
 import("host_common.gni")
 
+declare_args() {
+  # Specifies the toolchain to use for this build.
+  pw_target_toolchain = "$dir_pw_toolchain:host_clang_og"
+}
+
 pw_executable_config.bloaty_config_file =
     get_path_info("macos.bloaty", "abspath")
 
diff --git a/targets/host/pigweed_internal/BUILD.gn b/targets/host/pigweed_internal/BUILD.gn
deleted file mode 100644
index 295536e..0000000
--- a/targets/host/pigweed_internal/BUILD.gn
+++ /dev/null
@@ -1,28 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pigweed/targets/host/target_toolchains.gni")
-import("$dir_pw_toolchain/generate_toolchain.gni")
-
-assert(
-    "//targets/host/pigweed_internal/" == get_path_info("./", "abspath"),
-    "The host toolchains in targets/host/pigweed_internal may only be used " +
-        "for building upstream Pigweed. Use the host toolchains in " +
-        "targets/host for downstream code.")
-
-generate_toolchains("internal_host_toolchains") {
-  toolchains = pw_internal_host_toolchains
-}
diff --git a/targets/host/system_rpc_server.cc b/targets/host/system_rpc_server.cc
index 79e00ca..fd38732 100644
--- a/targets/host/system_rpc_server.cc
+++ b/targets/host/system_rpc_server.cc
@@ -14,44 +14,39 @@
 
 #include <cstddef>
 #include <cstdint>
-#include <cstdio>
 
-#include "pw_assert/check.h"
 #include "pw_hdlc/rpc_channel.h"
 #include "pw_hdlc/rpc_packets.h"
 #include "pw_log/log.h"
+#include "pw_rpc/synchronized_channel_output.h"
 #include "pw_rpc_system_server/rpc_server.h"
 #include "pw_stream/socket_stream.h"
 
 namespace pw::rpc::system_server {
 namespace {
 
-constexpr size_t kMaxTransmissionUnit = 512;
-uint16_t socket_port = 33000;
+constexpr size_t kMaxTransmissionUnit = 256;
+constexpr uint16_t kSocketPort = 33000;
 
 stream::SocketStream socket_stream;
-
-hdlc::RpcChannelOutput hdlc_channel_output(socket_stream,
-                                           hdlc::kDefaultRpcAddress,
-                                           "HDLC channel");
+sync::Mutex channel_output_mutex;
+rpc::SynchronizedChannelOutput<
+    hdlc::RpcChannelOutputBuffer<kMaxTransmissionUnit>>
+    hdlc_channel_output(channel_output_mutex,
+                        socket_stream,
+                        hdlc::kDefaultRpcAddress,
+                        "HDLC channel");
 Channel channels[] = {rpc::Channel::Create<1>(&hdlc_channel_output)};
 rpc::Server server(channels);
 
 }  // namespace
 
-void set_socket_port(uint16_t new_socket_port) {
-  socket_port = new_socket_port;
-}
-
 void Init() {
   log_basic::SetOutput([](std::string_view log) {
-    std::fprintf(stderr, "%.*s\n", static_cast<int>(log.size()), log.data());
-    hdlc::WriteUIFrame(1, std::as_bytes(std::span(log)), socket_stream)
-        .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+    hdlc::WriteUIFrame(1, std::as_bytes(std::span(log)), socket_stream);
   });
 
-  PW_LOG_INFO("Starting pw_rpc server on port %d", socket_port);
-  PW_CHECK_OK(socket_stream.Serve(socket_port));
+  socket_stream.Serve(kSocketPort);
 }
 
 rpc::Server& Server() { return server; }
@@ -69,8 +64,7 @@
         if (auto result = decoder.Process(byte); result.ok()) {
           hdlc::Frame& frame = result.value();
           if (frame.address() == hdlc::kDefaultRpcAddress) {
-            server.ProcessPacket(frame.data(), hdlc_channel_output)
-                .IgnoreError();  // TODO(pwbug/387): Handle Status properly
+            server.ProcessPacket(frame.data(), hdlc_channel_output);
           }
         }
       }
diff --git a/targets/host/target_docs.rst b/targets/host/target_docs.rst
index cadbb9b..7d20d17 100644
--- a/targets/host/target_docs.rst
+++ b/targets/host/target_docs.rst
@@ -1,83 +1,49 @@
 .. _target-host:
 
-====
+----
 host
-====
+----
 The Pigweed host target is used for unit testing and some host side tooling.
 
-----------
-Toolchains
-----------
-Pigweed several toolchains preconfigured for compiling for the host.
-
-.. list-table::
-
-  * - Toolchain name
-    - GN path
-    - Compiler
-    - Optimization
-  * - ``host_clang_debug``
-    - ``//targets/host:host_clang_debug``
-    - Clang
-    - ``-Og``
-  * - ``host_clang_size_optimized``
-    - ``//targets/host:host_clang_size_optimized``
-    - Clang
-    - ``-Os``
-  * - ``host_clang_speed_optimized``
-    - ``//targets/host:host_clang_speed_optimized``
-    - Clang
-    - ``-O2``
-  * - ``host_gcc_debug``
-    - ``//targets/host:host_gcc_debug``
-    - GCC
-    - ``-Og``
-  * - ``host_gcc_size_optimized``
-    - ``//targets/host:host_gcc_size_optimized``
-    - GCC
-    - ``-Os``
-  * - ``host_gcc_speed_optimized``
-    - ``//targets/host:host_gcc_speed_optimized``
-    - GCC
-    - ``-O2``
-
-These toolchains may be used directly by downstream projects if desired. For
-upstream builds, Pigweed uses internal-only variants of these toolchains. The
-upstream toolchains are defined in ``//targets/host/pigweed_internal`` and are
-prefixed with ``pw_strict_``. The upstream toolchains may not be used by
-downstream projects.
-
---------
 Building
---------
-To build for the host with a default configuration, invoke Ninja with the
-top-level ``host`` group as the target to build.
+========
+To build for this target, invoke ninja with the top-level "host" group as the
+target to build.
 
-.. code-block:: sh
+.. code:: sh
 
   $ ninja -C out host
 
+There are two host toolchains, and both of them can be manually invoked by
+replacing `host` with `host_clang` or `host_gcc`. Not all toolchains are
+supported on all platforms. Unless working specifically on one toolchain, it is
+recommended to leave this to the default.
 
-``host`` may be replaced with with ``host_clang``, ``host_gcc``,
-``host_clang_debug``, etc. to build with a more specific host toolchain. Not all
-toolchains are supported on all platforms. Unless working specifically on one
-toolchain, it is recommended to use the default.
-
--------------
 Running Tests
--------------
+=============
 Tests are automatically run as part of the host build, but if you desire to
 manually run tests, you may invoke them from a shell directly.
 
 Example:
 
-.. code-block:: sh
+... code:: sh
 
   $ ./out/host_[compiler]_debug/obj/pw_status/status_test
 
-----------
 RPC server
-----------
+==========
 The host target implements a system RPC server that runs over a local socket,
 defaulting to port 33000. To communicate with a process running the host RPC
 server, use ``pw rpc -s localhost:33000 <protos>``.
+
+Configuration
+=============
+The host target exposes a few options that may be used to change the host build
+behavior.
+
+pw_build_HOST_TOOLS
+-------------------
+Pigweed includes a number of host-only tooling that may be built as part of the
+host build. These tools are included as part of the bootstrap, so it's only
+necessary to enable this setting when modifying host tooling. This is
+disabled by default.
diff --git a/targets/host/target_toolchains.gni b/targets/host/target_toolchains.gni
index 15c08ac..07b769a 100644
--- a/targets/host/target_toolchains.gni
+++ b/targets/host/target_toolchains.gni
@@ -1,4 +1,4 @@
-# Copyright 2022 The Pigweed Authors
+# Copyright 2020 The Pigweed Authors
 #
 # 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
@@ -19,7 +19,6 @@
 import("$dir_pw_rpc/system_server/backend.gni")
 import("$dir_pw_sync/backend.gni")
 import("$dir_pw_sys_io/backend.gni")
-import("$dir_pw_system/backend.gni")
 import("$dir_pw_third_party/nanopb/nanopb.gni")
 import("$dir_pw_thread/backend.gni")
 import("$dir_pw_toolchain/host_clang/toolchains.gni")
@@ -33,29 +32,18 @@
 
   # Configure backend for assert facade.
   pw_assert_BACKEND = "$dir_pw_assert_basic"
-  pw_assert_LITE_BACKEND = "$dir_pw_assert:print_and_abort"
 
   # Configure backend for logging facade.
   pw_log_BACKEND = "$dir_pw_log_basic"
 
   # Configure backends for pw_sync's facades.
   pw_sync_INTERRUPT_SPIN_LOCK_BACKEND = "$dir_pw_sync_stl:interrupt_spin_lock"
-  pw_sync_BINARY_SEMAPHORE_BACKEND = "$dir_pw_sync_stl:binary_semaphore_backend"
-  pw_sync_COUNTING_SEMAPHORE_BACKEND =
-      "$dir_pw_sync_stl:counting_semaphore_backend"
-  pw_sync_MUTEX_BACKEND = "$dir_pw_sync_stl:mutex_backend"
-  pw_sync_TIMED_MUTEX_BACKEND = "$dir_pw_sync_stl:timed_mutex_backend"
-  pw_sync_THREAD_NOTIFICATION_BACKEND =
-      "$dir_pw_sync:binary_semaphore_thread_notification_backend"
-  pw_sync_TIMED_THREAD_NOTIFICATION_BACKEND =
-      "$dir_pw_sync:binary_semaphore_timed_thread_notification_backend"
 
   # Configure backend for pw_sys_io facade.
   pw_sys_io_BACKEND = "$dir_pw_sys_io_stdio"
 
   # Configure backend for pw_rpc_system_server.
   pw_rpc_system_server_BACKEND = "$dir_pigweed/targets/host:system_rpc_server"
-  pw_rpc_CONFIG = "$dir_pw_rpc:use_global_mutex"
 
   # Configure backend for trace facade.
   pw_trace_BACKEND = "$dir_pw_trace_tokenized"
@@ -63,21 +51,8 @@
   # Tokenizer trace time.
   pw_trace_tokenizer_time = "$dir_pw_trace_tokenized:host_trace_time"
 
-  # Configure backend for pw_chrono's facades.
+  # Configure backend for pw_chrono's system_clock facade.
   pw_chrono_SYSTEM_CLOCK_BACKEND = "$dir_pw_chrono_stl:system_clock"
-  pw_chrono_SYSTEM_TIMER_BACKEND = "$dir_pw_chrono_stl:system_timer"
-
-  # Configure backends for pw_thread's facades.
-  pw_thread_ID_BACKEND = "$dir_pw_thread_stl:id"
-  pw_thread_YIELD_BACKEND = "$dir_pw_thread_stl:yield"
-  pw_thread_SLEEP_BACKEND = "$dir_pw_thread_stl:sleep"
-  pw_thread_THREAD_BACKEND = "$dir_pw_thread_stl:thread"
-
-  pw_build_LINK_DEPS = []  # Explicit list overwrite required by GN
-  pw_build_LINK_DEPS = [
-    "$dir_pw_assert:impl",
-    "$dir_pw_log:impl",
-  ]
 
   # Specify builtin GN variables.
   current_os = host_os
@@ -104,11 +79,30 @@
   pw_unit_test_AUTOMATIC_RUNNER = get_path_info("run_test.bat", "abspath")
 }
 
+# TODO(amontanez): figure out why std::mutex doesn't work on Windows.
+# These current target configurations do not work on windows.
+_win_incompatible_config = {
+  # Configure backends for pw_sync's facades.
+  pw_sync_BINARY_SEMAPHORE_BACKEND = "$dir_pw_sync_stl:binary_semaphore_backend"
+  pw_sync_COUNTING_SEMAPHORE_BACKEND =
+      "$dir_pw_sync_stl:counting_semaphore_backend"
+  pw_sync_MUTEX_BACKEND = "$dir_pw_sync_stl:mutex_backend"
+  pw_sync_TIMED_MUTEX_BACKEND = "$dir_pw_sync_stl:timed_mutex_backend"
+
+  # Configure backends for pw_thread's facades.
+  pw_thread_ID_BACKEND = "$dir_pw_thread_stl:id"
+  pw_thread_SLEEP_BACKEND = "$dir_pw_thread_stl:sleep"
+  pw_thread_YIELD_BACKEND = "$dir_pw_thread_stl:yield"
+  pw_thread_THREAD_BACKEND = "$dir_pw_thread_stl:thread"
+}
+
 _os_specific_config = {
   if (host_os == "linux") {
     forward_variables_from(_linux_config, "*")
+    forward_variables_from(_win_incompatible_config, "*")
   } else if (host_os == "mac") {
     forward_variables_from(_mac_config, "*")
+    forward_variables_from(_win_incompatible_config, "*")
   } else if (host_os == "win") {
     forward_variables_from(_win_config, "*")
   }
@@ -123,12 +117,12 @@
   "$dir_pw_toolchain/host_gcc:threading_support",
 ]
 
-_excluded_members = [
-  "defaults",
-  "name",
-]
-
 pw_target_toolchain_host = {
+  _excluded_members = [
+    "defaults",
+    "name",
+  ]
+
   clang_debug = {
     name = "host_clang_debug"
     _toolchain_base = pw_toolchain_host_clang.debug
@@ -177,66 +171,6 @@
     }
   }
 
-  clang_asan = {
-    name = "host_clang_asan"
-    _toolchain_base = pw_toolchain_host_clang.asan
-    forward_variables_from(_toolchain_base, "*", _excluded_members)
-    defaults = {
-      forward_variables_from(_toolchain_base.defaults, "*")
-      forward_variables_from(_host_common, "*")
-      forward_variables_from(_os_specific_config, "*")
-      default_configs += _clang_default_configs
-    }
-  }
-
-  clang_ubsan = {
-    name = "host_clang_ubsan"
-    _toolchain_base = pw_toolchain_host_clang.ubsan
-    forward_variables_from(_toolchain_base, "*", _excluded_members)
-    defaults = {
-      forward_variables_from(_toolchain_base.defaults, "*")
-      forward_variables_from(_host_common, "*")
-      forward_variables_from(_os_specific_config, "*")
-      default_configs += _clang_default_configs
-    }
-  }
-
-  clang_ubsan_heuristic = {
-    name = "host_clang_ubsan_heuristic"
-    _toolchain_base = pw_toolchain_host_clang.ubsan_heuristic
-    forward_variables_from(_toolchain_base, "*", _excluded_members)
-    defaults = {
-      forward_variables_from(_toolchain_base.defaults, "*")
-      forward_variables_from(_host_common, "*")
-      forward_variables_from(_os_specific_config, "*")
-      default_configs += _clang_default_configs
-    }
-  }
-
-  clang_msan = {
-    name = "host_clang_msan"
-    _toolchain_base = pw_toolchain_host_clang.msan
-    forward_variables_from(_toolchain_base, "*", _excluded_members)
-    defaults = {
-      forward_variables_from(_toolchain_base.defaults, "*")
-      forward_variables_from(_host_common, "*")
-      forward_variables_from(_os_specific_config, "*")
-      default_configs += _clang_default_configs
-    }
-  }
-
-  clang_tsan = {
-    name = "host_clang_tsan"
-    _toolchain_base = pw_toolchain_host_clang.tsan
-    forward_variables_from(_toolchain_base, "*", _excluded_members)
-    defaults = {
-      forward_variables_from(_toolchain_base.defaults, "*")
-      forward_variables_from(_host_common, "*")
-      forward_variables_from(_os_specific_config, "*")
-      default_configs += _clang_default_configs
-    }
-  }
-
   gcc_debug = {
     name = "host_gcc_debug"
     _toolchain_base = pw_toolchain_host_gcc.debug
@@ -282,95 +216,7 @@
   pw_target_toolchain_host.clang_speed_optimized,
   pw_target_toolchain_host.clang_size_optimized,
   pw_target_toolchain_host.clang_fuzz,
-  pw_target_toolchain_host.clang_asan,
-  pw_target_toolchain_host.clang_ubsan,
-  pw_target_toolchain_host.clang_ubsan_heuristic,
-  pw_target_toolchain_host.clang_msan,
-  pw_target_toolchain_host.clang_tsan,
   pw_target_toolchain_host.gcc_debug,
   pw_target_toolchain_host.gcc_speed_optimized,
   pw_target_toolchain_host.gcc_size_optimized,
 ]
-
-# Additional configuration intended only for upstream Pigweed use.
-_pigweed_internal = {
-  pw_status_CONFIG = "$dir_pw_status:check_if_used"
-}
-
-# Host toolchains exclusively for upstream Pigweed use. To give upstream Pigweed
-# flexibility in how it compiles code, these toolchains may not be used by
-# downstream projects.
-pw_internal_host_toolchains = [
-  {
-    name = "pw_strict_host_clang_debug"
-    _toolchain_base = pw_toolchain_host_clang.debug
-    forward_variables_from(_toolchain_base, "*", _excluded_members)
-    defaults = {
-      forward_variables_from(_toolchain_base.defaults, "*")
-      forward_variables_from(_host_common, "*")
-      forward_variables_from(_pigweed_internal, "*")
-      forward_variables_from(_os_specific_config, "*")
-      default_configs += _clang_default_configs
-    }
-  },
-  {
-    name = "pw_strict_host_clang_speed_optimized"
-    _toolchain_base = pw_toolchain_host_clang.speed_optimized
-    forward_variables_from(_toolchain_base, "*", _excluded_members)
-    defaults = {
-      forward_variables_from(_toolchain_base.defaults, "*")
-      forward_variables_from(_host_common, "*")
-      forward_variables_from(_pigweed_internal, "*")
-      forward_variables_from(_os_specific_config, "*")
-      default_configs += _clang_default_configs
-    }
-  },
-  {
-    name = "pw_strict_host_clang_size_optimized"
-    _toolchain_base = pw_toolchain_host_clang.size_optimized
-    forward_variables_from(_toolchain_base, "*", _excluded_members)
-    defaults = {
-      forward_variables_from(_toolchain_base.defaults, "*")
-      forward_variables_from(_host_common, "*")
-      forward_variables_from(_pigweed_internal, "*")
-      forward_variables_from(_os_specific_config, "*")
-      default_configs += _clang_default_configs
-    }
-  },
-  {
-    name = "pw_strict_host_gcc_debug"
-    _toolchain_base = pw_toolchain_host_gcc.debug
-    forward_variables_from(_toolchain_base, "*", _excluded_members)
-    defaults = {
-      forward_variables_from(_toolchain_base.defaults, "*")
-      forward_variables_from(_host_common, "*")
-      forward_variables_from(_pigweed_internal, "*")
-      forward_variables_from(_os_specific_config, "*")
-      default_configs += _gcc_default_configs
-    }
-  },
-  {
-    name = "pw_strict_host_gcc_speed_optimized"
-    _toolchain_base = pw_toolchain_host_gcc.speed_optimized
-    forward_variables_from(_toolchain_base, "*", _excluded_members)
-    defaults = {
-      forward_variables_from(_toolchain_base.defaults, "*")
-      forward_variables_from(_host_common, "*")
-      forward_variables_from(_pigweed_internal, "*")
-      forward_variables_from(_os_specific_config, "*")
-      default_configs += _gcc_default_configs
-    }
-  },
-  {
-    name = "pw_strict_host_gcc_size_optimized"
-    _toolchain_base = pw_toolchain_host_gcc.size_optimized
-    forward_variables_from(_toolchain_base, "*", _excluded_members)
-    defaults = {
-      forward_variables_from(_toolchain_base.defaults, "*")
-      forward_variables_from(_host_common, "*")
-      forward_variables_from(_pigweed_internal, "*")
-      forward_variables_from(_os_specific_config, "*")
-      default_configs += _gcc_default_configs
-    }
-  },
-]
diff --git a/targets/host_device_simulator/BUILD.gn b/targets/host_device_simulator/BUILD.gn
deleted file mode 100644
index 68b58fc..0000000
--- a/targets/host_device_simulator/BUILD.gn
+++ /dev/null
@@ -1,28 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_system/system_target.gni")
-
-pw_system_target("host_device_simulator") {
-  cpu = PW_SYSTEM_CPU.NATIVE
-  scheduler = PW_SYSTEM_SCHEDULER.NATIVE
-}
-
-pw_doc_group("target_docs") {
-  sources = [ "target_docs.rst" ]
-}
diff --git a/targets/host_device_simulator/target_docs.rst b/targets/host_device_simulator/target_docs.rst
deleted file mode 100644
index 494a4b9..0000000
--- a/targets/host_device_simulator/target_docs.rst
+++ /dev/null
@@ -1,41 +0,0 @@
-.. _target-host-device-simulator:
-
-=====================
-Host Device Simulator
-=====================
-This Pigweed target simulates the behavior of an embedded device, spawning
-threads for facilities like RPC and logging. Executables build by this target
-will perpetually run until they crash or are explicitly terminated. All
-communications with the process are over the RPC server hosted on a local
-socket rather than by directly interacting with the terminal via standard I/O.
-
------
-Setup
------
-To use this target, Pigweed must be set up to use nanopb. The required source
-repository can be downloaded via ``pw package``, and then the build must be
-manually configured to point to the location the repository was downloaded to.
-
-.. code:: sh
-
-  pw package install nanopb
-
-  gn args out
-    # Add this line, replacing ${PW_ROOT} with the path to the location that
-    # Pigweed is checked out at.
-    dir_pw_third_party_nanopb = "${PW_ROOT}/.environment/packages/nanopb"
-
------------------------------
-Building and running the demo
------------------------------
-This target has an associated demo application that can be built and then
-run with the following commands:
-
-.. code:: sh
-
-  ninja -C out pw_system_demo
-
-  ./out/host_device_simulator.speed_optimized/obj/pw_system/bin/system_example
-
-To communicate with the launched process, use
-``pw-system-console -s localhost:33000 --proto-globs pw_rpc/echo.proto``.
diff --git a/targets/lm3s6965evb-qemu/BUILD b/targets/lm3s6965evb-qemu/BUILD
new file mode 100644
index 0000000..bb15e06
--- /dev/null
+++ b/targets/lm3s6965evb-qemu/BUILD
@@ -0,0 +1,35 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pre_init",
+    srcs = [
+        "boot.cc",
+        "vector_table.c"
+    ],
+    deps = [
+        "//pw_boot_armv7m",
+        "//pw_preprocessor",
+        "//pw_sys_io_baremetal_lm3s6965evb",
+    ],
+)
diff --git a/targets/lm3s6965evb-qemu/BUILD.gn b/targets/lm3s6965evb-qemu/BUILD.gn
new file mode 100644
index 0000000..ebfa30f
--- /dev/null
+++ b/targets/lm3s6965evb-qemu/BUILD.gn
@@ -0,0 +1,42 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/target_types.gni")
+import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_toolchain/generate_toolchain.gni")
+import("target_toolchains.gni")
+
+generate_toolchains("target_toolchains") {
+  toolchains = pw_target_toolchain_lm3s6965evb_qemu_list
+}
+
+if (current_toolchain != default_toolchain) {
+  pw_source_set("pre_init") {
+    public_deps = [
+      "$dir_pw_boot_armv7m",
+      "$dir_pw_sys_io_baremetal_lm3s6965evb",
+    ]
+    deps = [ "$dir_pw_preprocessor" ]
+    sources = [
+      "boot.cc",
+      "vector_table.c",
+    ]
+  }
+}
+
+pw_doc_group("target_docs") {
+  sources = [ "target_docs.rst" ]
+}
diff --git a/targets/lm3s6965evb-qemu/boot.cc b/targets/lm3s6965evb-qemu/boot.cc
new file mode 100644
index 0000000..f4a537c
--- /dev/null
+++ b/targets/lm3s6965evb-qemu/boot.cc
@@ -0,0 +1,48 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_boot_armv7m/boot.h"
+
+#include "pw_preprocessor/compiler.h"
+#include "pw_sys_io_baremetal_lm3s6965evb/init.h"
+
+// Note that constexpr is used inside of this function instead of using a static
+// constexpr or declaring it outside of this function in an anonymous namespace,
+// because constexpr makes it available for the compiler to evaluate during
+// copmile time but does NOT require it to be evaluated at compile team and we
+// have to be incredibly careful that this does not end up in the .data section.
+void pw_boot_PreStaticMemoryInit() {
+  // Force RCC to be at default at boot.
+  constexpr uint32_t kRccDefault = 0x078E3AD1U;
+  volatile uint32_t& rcc = *reinterpret_cast<volatile uint32_t*>(0x400FE070U);
+  rcc = kRccDefault;
+  constexpr uint32_t kRcc2Default = 0x07802810U;
+  volatile uint32_t& rcc2 = *reinterpret_cast<volatile uint32_t*>(0x400FE070U);
+  rcc2 = kRcc2Default;
+}
+
+void pw_boot_PreStaticConstructorInit() {}
+
+void pw_boot_PreMainInit() { pw_sys_io_Init(); }
+
+PW_NO_RETURN void pw_boot_PostMain() {
+  // QEMU requires a special command to tell the VM to shut down.
+  volatile uint32_t* aircr = (uint32_t*)(0xE000ED0CU);
+  *aircr = 0x5fa0004;
+
+  // In case main() returns, just sit here until the device is reset.
+  while (true) {
+  }
+  PW_UNREACHABLE;
+}
diff --git a/targets/lm3s6965evb-qemu/lm3s6965evb_executable.gni b/targets/lm3s6965evb-qemu/lm3s6965evb_executable.gni
new file mode 100644
index 0000000..70a87cb
--- /dev/null
+++ b/targets/lm3s6965evb-qemu/lm3s6965evb_executable.gni
@@ -0,0 +1,26 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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("//build_overrides/pigweed.gni")
+
+# Executable wrapper that includes some baremetal startup code.
+template("lm3s6965evb_executable") {
+  target("executable", target_name) {
+    forward_variables_from(invoker, "*")
+    if (!defined(deps)) {
+      deps = []
+    }
+    deps += [ "$dir_pigweed/targets/lm3s6965evb-qemu:pre_init" ]
+  }
+}
diff --git a/targets/lm3s6965evb-qemu/py/BUILD.gn b/targets/lm3s6965evb-qemu/py/BUILD.gn
new file mode 100644
index 0000000..36373ff
--- /dev/null
+++ b/targets/lm3s6965evb-qemu/py/BUILD.gn
@@ -0,0 +1,26 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/python.gni")
+
+pw_python_package("py") {
+  setup = [ "setup.py" ]
+  sources = [
+    "lm3s6965evb_qemu_utils/__init__.py",
+    "lm3s6965evb_qemu_utils/unit_test_runner.py",
+  ]
+  pylintrc = "$dir_pigweed/.pylintrc"
+}
diff --git a/targets/lm3s6965evb_qemu/py/lm3s6965evb_qemu_utils/__init__.py b/targets/lm3s6965evb-qemu/py/lm3s6965evb_qemu_utils/__init__.py
similarity index 100%
rename from targets/lm3s6965evb_qemu/py/lm3s6965evb_qemu_utils/__init__.py
rename to targets/lm3s6965evb-qemu/py/lm3s6965evb_qemu_utils/__init__.py
diff --git a/targets/lm3s6965evb_qemu/py/lm3s6965evb_qemu_utils/py.typed b/targets/lm3s6965evb-qemu/py/lm3s6965evb_qemu_utils/py.typed
similarity index 100%
rename from targets/lm3s6965evb_qemu/py/lm3s6965evb_qemu_utils/py.typed
rename to targets/lm3s6965evb-qemu/py/lm3s6965evb_qemu_utils/py.typed
diff --git a/targets/lm3s6965evb_qemu/py/lm3s6965evb_qemu_utils/unit_test_runner.py b/targets/lm3s6965evb-qemu/py/lm3s6965evb_qemu_utils/unit_test_runner.py
similarity index 100%
rename from targets/lm3s6965evb_qemu/py/lm3s6965evb_qemu_utils/unit_test_runner.py
rename to targets/lm3s6965evb-qemu/py/lm3s6965evb_qemu_utils/unit_test_runner.py
diff --git a/targets/lm3s6965evb-qemu/py/setup.py b/targets/lm3s6965evb-qemu/py/setup.py
new file mode 100644
index 0000000..4223685
--- /dev/null
+++ b/targets/lm3s6965evb-qemu/py/setup.py
@@ -0,0 +1,35 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+"""lm3s6965evb_qemu_utils"""
+
+import setuptools  # type: ignore
+
+setuptools.setup(
+    name='lm3s6965evb_qemu_utils',
+    version='0.0.1',
+    author='Pigweed Authors',
+    author_email='pigweed-developers@googlegroups.com',
+    description=
+    'Target-specific python scripts for the lm3s6965evb-qemu target',
+    packages=setuptools.find_packages(),
+    package_data={'lm3s6965evb_qemu_utils': ['py.typed']},
+    zip_safe=False,
+    entry_points={
+        'console_scripts': [
+            'lm3s6965evb_qemu_unit_test_runner = '
+            '    lm3s6965evb_qemu_utils.unit_test_runner:main',
+        ]
+    },
+    install_requires=['coloredlogs'],
+)
diff --git a/targets/lm3s6965evb_qemu/target_docs.rst b/targets/lm3s6965evb-qemu/target_docs.rst
similarity index 100%
rename from targets/lm3s6965evb_qemu/target_docs.rst
rename to targets/lm3s6965evb-qemu/target_docs.rst
diff --git a/targets/lm3s6965evb-qemu/target_toolchains.gni b/targets/lm3s6965evb-qemu/target_toolchains.gni
new file mode 100644
index 0000000..c3fba37
--- /dev/null
+++ b/targets/lm3s6965evb-qemu/target_toolchains.gni
@@ -0,0 +1,162 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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("//build_overrides/pigweed.gni")
+
+import("$dir_pw_sys_io/backend.gni")
+import("$dir_pw_toolchain/arm_clang/toolchains.gni")
+import("$dir_pw_toolchain/arm_gcc/toolchains.gni")
+
+_test_runner_script = "py/lm3s6965evb_qemu_utils/unit_test_runner.py"
+
+_target_config = {
+  # Use the logging main.
+  pw_unit_test_MAIN = "$dir_pw_unit_test:logging_main"
+
+  # Configuration options for Pigweed executable targets.
+  pw_build_EXECUTABLE_TARGET_TYPE = "lm3s6965evb_executable"
+
+  pw_build_EXECUTABLE_TARGET_TYPE_FILE =
+      get_path_info("lm3s6965evb_executable.gni", "abspath")
+
+  # Path to the bloaty config file for the output binaries.
+  pw_bloat_BLOATY_CONFIG = "$dir_pw_boot_armv7m/bloaty_config.bloaty"
+
+  pw_unit_test_AUTOMATIC_RUNNER = get_path_info(_test_runner_script, "abspath")
+
+  # Facade backends
+  pw_assert_BACKEND = dir_pw_assert_basic
+  pw_boot_BACKEND = dir_pw_boot_armv7m
+  pw_log_BACKEND = dir_pw_log_basic
+  pw_sys_io_BACKEND = dir_pw_sys_io_baremetal_lm3s6965evb
+  pw_sync_INTERRUPT_SPIN_LOCK_BACKEND =
+      "$dir_pw_sync_baremetal:interrupt_spin_lock"
+
+  # pw_cpu_exception_armv7m tests do not work as expected in QEMU. It does not
+  # appear the divide-by-zero traps as expected when enabled, which prevents the
+  # module from triggering a recoverable exception. Since pw_cpu_exception is
+  # not fully set up on this target, disable it for now.
+  # pw_cpu_exception_ENTRY_BACKEND =
+  #     "$dir_pw_cpu_exception_cortex_m:cpu_exception_armv7m
+
+  pw_boot_armv7m_LINK_CONFIG_DEFINES = [
+    "PW_BOOT_FLASH_BEGIN=0x00000200",
+    "PW_BOOT_FLASH_SIZE=255K",
+    "PW_BOOT_HEAP_SIZE=0",
+    "PW_BOOT_MIN_STACK_SIZE=1K",
+    "PW_BOOT_RAM_BEGIN=0x20000000",
+    "PW_BOOT_RAM_SIZE=64K",
+    "PW_BOOT_VECTOR_TABLE_BEGIN=0x00000000",
+    "PW_BOOT_VECTOR_TABLE_SIZE=512",
+  ]
+
+  current_cpu = "arm"
+  current_os = ""
+}
+
+_gcc_target_default_configs = [
+  "$dir_pw_build:extra_strict_warnings",
+  "$dir_pw_toolchain/arm_gcc:enable_float_printf",
+]
+
+_clang_target_default_configs = [
+  "$dir_pw_build:clang_thread_safety_warnings",
+  "$dir_pw_build:extra_strict_warnings",
+  "$dir_pw_toolchain/arm_clang:enable_float_printf",
+]
+
+pw_target_toolchain_lm3s6965evb_qemu = {
+  _excluded_members = [
+    "defaults",
+    "name",
+  ]
+
+  debug = {
+    name = "lm3s6965evb_qemu_gcc_debug"
+    _toolchain_base = pw_toolchain_arm_gcc.cortex_m3_debug
+    forward_variables_from(_toolchain_base, "*", _excluded_members)
+    defaults = {
+      forward_variables_from(_toolchain_base.defaults, "*")
+      forward_variables_from(_target_config, "*")
+      default_configs += _gcc_target_default_configs
+    }
+  }
+
+  speed_optimized = {
+    name = "lm3s6965evb_qemu_gcc_speed_optimized"
+    _toolchain_base = pw_toolchain_arm_gcc.cortex_m3_speed_optimized
+    forward_variables_from(_toolchain_base, "*", _excluded_members)
+    defaults = {
+      forward_variables_from(_toolchain_base.defaults, "*")
+      forward_variables_from(_target_config, "*")
+      default_configs += _gcc_target_default_configs
+    }
+  }
+
+  size_optimized = {
+    name = "lm3s6965evb_qemu_gcc_size_optimized"
+    _toolchain_base = pw_toolchain_arm_gcc.cortex_m3_size_optimized
+    forward_variables_from(_toolchain_base, "*", _excluded_members)
+    defaults = {
+      forward_variables_from(_toolchain_base.defaults, "*")
+      forward_variables_from(_target_config, "*")
+      default_configs += _gcc_target_default_configs
+    }
+  }
+
+  debug_clang = {
+    name = "lm3s6965evb_qemu_clang_debug"
+    _toolchain_base = pw_toolchain_arm_clang.cortex_m3_debug
+    forward_variables_from(_toolchain_base, "*", _excluded_members)
+    defaults = {
+      forward_variables_from(_toolchain_base.defaults, "*")
+      forward_variables_from(_target_config, "*")
+      default_configs += _clang_target_default_configs
+    }
+  }
+
+  speed_optimized_clang = {
+    name = "lm3s6965evb_qemu_clang_speed_optimized"
+    _toolchain_base = pw_toolchain_arm_clang.cortex_m3_speed_optimized
+    forward_variables_from(_toolchain_base, "*", _excluded_members)
+    defaults = {
+      forward_variables_from(_toolchain_base.defaults, "*")
+      forward_variables_from(_target_config, "*")
+      default_configs += _clang_target_default_configs
+    }
+  }
+
+  size_optimized_clang = {
+    name = "lm3s6965evb_qemu_clang_size_optimized"
+    _toolchain_base = pw_toolchain_arm_clang.cortex_m3_size_optimized
+    forward_variables_from(_toolchain_base, "*", _excluded_members)
+    defaults = {
+      forward_variables_from(_toolchain_base.defaults, "*")
+      forward_variables_from(_target_config, "*")
+      default_configs += _clang_target_default_configs
+    }
+  }
+}
+
+# This list just contains the members of the above scope for convenience to make
+# it trivial to generate all the toolchains in this file via a
+# `generate_toolchains` target.
+pw_target_toolchain_lm3s6965evb_qemu_list = [
+  pw_target_toolchain_lm3s6965evb_qemu.debug,
+  pw_target_toolchain_lm3s6965evb_qemu.speed_optimized,
+  pw_target_toolchain_lm3s6965evb_qemu.size_optimized,
+  pw_target_toolchain_lm3s6965evb_qemu.debug_clang,
+  pw_target_toolchain_lm3s6965evb_qemu.speed_optimized_clang,
+  pw_target_toolchain_lm3s6965evb_qemu.size_optimized_clang,
+]
diff --git a/targets/lm3s6965evb-qemu/vector_table.c b/targets/lm3s6965evb-qemu/vector_table.c
new file mode 100644
index 0000000..a9228c5
--- /dev/null
+++ b/targets/lm3s6965evb-qemu/vector_table.c
@@ -0,0 +1,57 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 <stdbool.h>
+
+#include "pw_boot_armv7m/boot.h"
+
+// Default handler to insert into the ARMv7-M vector table (below).
+// This function exists for convenience. If a device isn't doing what you
+// expect, it might have hit a fault and ended up here.
+static void DefaultFaultHandler(void) {
+  while (true) {
+    // Wait for debugger to attach.
+  }
+}
+
+// This is the device's interrupt vector table. It's not referenced in any
+// code because the platform (STM32F4xx) expects this table to be present at the
+// beginning of flash. The exact address is specified in the pw_boot_armv7m
+// configuration as part of the target config.
+//
+// For more information, see ARMv7-M Architecture Reference Manual DDI 0403E.b
+// section B1.5.3.
+
+// This typedef is for convenience when building the vector table. With the
+// exception of SP_main (0th entry in the vector table), all the entries of the
+// vector table are function pointers.
+typedef void (*InterruptHandler)(void);
+
+PW_KEEP_IN_SECTION(".vector_table")
+const InterruptHandler vector_table[] = {
+    // The starting location of the stack pointer.
+    // This address is NOT an interrupt handler/function pointer, it is simply
+    // the address that the main stack pointer should be initialized to. The
+    // value is reinterpret casted because it needs to be in the vector table.
+    [0] = (InterruptHandler)(&pw_boot_stack_high_addr),
+
+    // Reset handler, dictates how to handle reset interrupt. This is the
+    // address that the Program Counter (PC) is initialized to at boot.
+    [1] = pw_boot_Entry,
+
+    // NMI handler.
+    [2] = DefaultFaultHandler,
+    // HardFault handler.
+    [3] = DefaultFaultHandler,
+};
diff --git a/targets/lm3s6965evb_qemu/BUILD.bazel b/targets/lm3s6965evb_qemu/BUILD.bazel
deleted file mode 100644
index 533460f..0000000
--- a/targets/lm3s6965evb_qemu/BUILD.bazel
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pre_init",
-    srcs = [
-        "boot.cc",
-        "vector_table.c",
-    ],
-    deps = [
-        "//pw_boot",
-        "//pw_boot_cortex_m",
-        "//pw_preprocessor",
-        "//pw_sys_io_baremetal_lm3s6965evb",
-    ],
-)
diff --git a/targets/lm3s6965evb_qemu/BUILD.gn b/targets/lm3s6965evb_qemu/BUILD.gn
deleted file mode 100644
index 230221c..0000000
--- a/targets/lm3s6965evb_qemu/BUILD.gn
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_toolchain/generate_toolchain.gni")
-import("target_toolchains.gni")
-
-generate_toolchains("target_toolchains") {
-  toolchains = pw_target_toolchain_lm3s6965evb_qemu_list
-}
-
-if (current_toolchain != default_toolchain) {
-  pw_source_set("pre_init") {
-    public_deps = [
-      "$dir_pw_boot",
-      "$dir_pw_boot_cortex_m",
-      "$dir_pw_sys_io_baremetal_lm3s6965evb",
-    ]
-    deps = [ "$dir_pw_preprocessor" ]
-    sources = [
-      "boot.cc",
-      "vector_table.c",
-    ]
-  }
-}
-
-pw_doc_group("target_docs") {
-  sources = [ "target_docs.rst" ]
-}
diff --git a/targets/lm3s6965evb_qemu/OWNERS b/targets/lm3s6965evb_qemu/OWNERS
deleted file mode 100644
index 307b1de..0000000
--- a/targets/lm3s6965evb_qemu/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-amontanez@google.com
diff --git a/targets/lm3s6965evb_qemu/boot.cc b/targets/lm3s6965evb_qemu/boot.cc
deleted file mode 100644
index d596364..0000000
--- a/targets/lm3s6965evb_qemu/boot.cc
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_boot/boot.h"
-
-#include "pw_preprocessor/compiler.h"
-#include "pw_sys_io_baremetal_lm3s6965evb/init.h"
-
-// Note that constexpr is used inside of this function instead of using a static
-// constexpr or declaring it outside of this function in an anonymous namespace,
-// because constexpr makes it available for the compiler to evaluate during
-// compile time but does NOT require it to be evaluated at compile time and we
-// have to be incredibly careful that this does not end up in the .data section.
-void pw_boot_PreStaticMemoryInit() {
-  // Force RCC to be at default at boot.
-  constexpr uint32_t kRccDefault = 0x078E3AD1U;
-  volatile uint32_t& rcc = *reinterpret_cast<volatile uint32_t*>(0x400FE070U);
-  rcc = kRccDefault;
-  constexpr uint32_t kRcc2Default = 0x07802810U;
-  volatile uint32_t& rcc2 = *reinterpret_cast<volatile uint32_t*>(0x400FE070U);
-  rcc2 = kRcc2Default;
-}
-
-void pw_boot_PreStaticConstructorInit() {}
-
-void pw_boot_PreMainInit() { pw_sys_io_lm3s6965evb_Init(); }
-
-PW_NO_RETURN void pw_boot_PostMain() {
-  // QEMU requires a special command to tell the VM to shut down.
-  volatile uint32_t* aircr = (uint32_t*)(0xE000ED0CU);
-  *aircr = 0x5fa0004;
-
-  // In case main() returns, just sit here until the device is reset.
-  while (true) {
-  }
-  PW_UNREACHABLE;
-}
diff --git a/targets/lm3s6965evb_qemu/lm3s6965evb_executable.gni b/targets/lm3s6965evb_qemu/lm3s6965evb_executable.gni
deleted file mode 100644
index bd54e8a..0000000
--- a/targets/lm3s6965evb_qemu/lm3s6965evb_executable.gni
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-# Executable wrapper that includes some baremetal startup code.
-template("lm3s6965evb_executable") {
-  target("executable", target_name) {
-    forward_variables_from(invoker, "*")
-    if (!defined(deps)) {
-      deps = []
-    }
-    deps += [ "$dir_pigweed/targets/lm3s6965evb_qemu:pre_init" ]
-  }
-}
diff --git a/targets/lm3s6965evb_qemu/py/BUILD.gn b/targets/lm3s6965evb_qemu/py/BUILD.gn
deleted file mode 100644
index 38d0add..0000000
--- a/targets/lm3s6965evb_qemu/py/BUILD.gn
+++ /dev/null
@@ -1,30 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/python.gni")
-
-pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
-  sources = [
-    "lm3s6965evb_qemu_utils/__init__.py",
-    "lm3s6965evb_qemu_utils/unit_test_runner.py",
-  ]
-  pylintrc = "$dir_pigweed/.pylintrc"
-}
diff --git a/targets/lm3s6965evb_qemu/py/pyproject.toml b/targets/lm3s6965evb_qemu/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/targets/lm3s6965evb_qemu/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/targets/lm3s6965evb_qemu/py/setup.cfg b/targets/lm3s6965evb_qemu/py/setup.cfg
deleted file mode 100644
index f380275..0000000
--- a/targets/lm3s6965evb_qemu/py/setup.cfg
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = lm3s6965evb_qemu_utils
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Target-specific python scripts for the lm3s6965evb-qemu target
-
-[options]
-packages = find:
-zip_safe = False
-install_requires = coloredlogs
-
-[options.entry_points]
-console_scripts =
-    lm3s6965evb_qemu_unit_test_runner = lm3s6965evb_qemu_utils.unit_test_runner:main
-
-[options.package_data]
-lm3s6965evb_qemu_utils = py.typed
diff --git a/targets/lm3s6965evb_qemu/py/setup.py b/targets/lm3s6965evb_qemu/py/setup.py
deleted file mode 100644
index 7842ebd..0000000
--- a/targets/lm3s6965evb_qemu/py/setup.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""lm3s6965evb_qemu_utils"""
-
-import setuptools  # type: ignore
-
-setuptools.setup()  # Package definition in setup.cfg
diff --git a/targets/lm3s6965evb_qemu/target_toolchains.gni b/targets/lm3s6965evb_qemu/target_toolchains.gni
deleted file mode 100644
index 3a271b7..0000000
--- a/targets/lm3s6965evb_qemu/target_toolchains.gni
+++ /dev/null
@@ -1,168 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_sys_io/backend.gni")
-import("$dir_pw_toolchain/arm_clang/toolchains.gni")
-import("$dir_pw_toolchain/arm_gcc/toolchains.gni")
-
-_test_runner_script = "py/lm3s6965evb_qemu_utils/unit_test_runner.py"
-
-_target_config = {
-  # Use the logging main.
-  pw_unit_test_MAIN = "$dir_pw_unit_test:logging_main"
-
-  # Configuration options for Pigweed executable targets.
-  pw_build_EXECUTABLE_TARGET_TYPE = "lm3s6965evb_executable"
-
-  pw_build_EXECUTABLE_TARGET_TYPE_FILE =
-      get_path_info("lm3s6965evb_executable.gni", "abspath")
-
-  # Path to the bloaty config file for the output binaries.
-  pw_bloat_BLOATY_CONFIG = "$dir_pw_boot_cortex_m/bloaty_config.bloaty"
-
-  pw_unit_test_AUTOMATIC_RUNNER = get_path_info(_test_runner_script, "abspath")
-
-  # Facade backends
-  pw_assert_BACKEND = dir_pw_assert_basic
-  pw_boot_BACKEND = "$dir_pw_boot_cortex_m"
-  pw_log_BACKEND = dir_pw_log_basic
-  pw_sys_io_BACKEND = dir_pw_sys_io_baremetal_lm3s6965evb
-  pw_sync_INTERRUPT_SPIN_LOCK_BACKEND =
-      "$dir_pw_sync_baremetal:interrupt_spin_lock"
-  pw_sync_MUTEX_BACKEND = "$dir_pw_sync_baremetal:mutex"
-
-  # pw_cpu_exception_armv7m tests do not work as expected in QEMU. It does not
-  # appear the divide-by-zero traps as expected when enabled, which prevents the
-  # module from triggering a recoverable exception. Since pw_cpu_exception is
-  # not fully set up on this target, disable it for now.
-  # pw_cpu_exception_ENTRY_BACKEND =
-  #     "$dir_pw_cpu_exception_cortex_m:cpu_exception_armv7m
-
-  pw_boot_cortex_m_LINK_CONFIG_DEFINES = [
-    "PW_BOOT_FLASH_BEGIN=0x00000200",
-    "PW_BOOT_FLASH_SIZE=255K",
-    "PW_BOOT_HEAP_SIZE=0",
-    "PW_BOOT_MIN_STACK_SIZE=1K",
-    "PW_BOOT_RAM_BEGIN=0x20000000",
-    "PW_BOOT_RAM_SIZE=64K",
-    "PW_BOOT_VECTOR_TABLE_BEGIN=0x00000000",
-    "PW_BOOT_VECTOR_TABLE_SIZE=512",
-  ]
-
-  pw_build_LINK_DEPS = [
-    "$dir_pw_assert:impl",
-    "$dir_pw_log:impl",
-  ]
-
-  current_cpu = "arm"
-  current_os = ""
-}
-
-_gcc_target_default_configs = [
-  "$dir_pw_build:extra_strict_warnings",
-  "$dir_pw_toolchain/arm_gcc:enable_float_printf",
-]
-
-_clang_target_default_configs = [
-  "$dir_pw_build:clang_thread_safety_warnings",
-  "$dir_pw_build:extra_strict_warnings",
-  "$dir_pw_toolchain/arm_clang:enable_float_printf",
-]
-
-pw_target_toolchain_lm3s6965evb_qemu = {
-  _excluded_members = [
-    "defaults",
-    "name",
-  ]
-
-  debug = {
-    name = "lm3s6965evb_qemu_gcc_debug"
-    _toolchain_base = pw_toolchain_arm_gcc.cortex_m3_debug
-    forward_variables_from(_toolchain_base, "*", _excluded_members)
-    defaults = {
-      forward_variables_from(_toolchain_base.defaults, "*")
-      forward_variables_from(_target_config, "*")
-      default_configs += _gcc_target_default_configs
-    }
-  }
-
-  speed_optimized = {
-    name = "lm3s6965evb_qemu_gcc_speed_optimized"
-    _toolchain_base = pw_toolchain_arm_gcc.cortex_m3_speed_optimized
-    forward_variables_from(_toolchain_base, "*", _excluded_members)
-    defaults = {
-      forward_variables_from(_toolchain_base.defaults, "*")
-      forward_variables_from(_target_config, "*")
-      default_configs += _gcc_target_default_configs
-    }
-  }
-
-  size_optimized = {
-    name = "lm3s6965evb_qemu_gcc_size_optimized"
-    _toolchain_base = pw_toolchain_arm_gcc.cortex_m3_size_optimized
-    forward_variables_from(_toolchain_base, "*", _excluded_members)
-    defaults = {
-      forward_variables_from(_toolchain_base.defaults, "*")
-      forward_variables_from(_target_config, "*")
-      default_configs += _gcc_target_default_configs
-    }
-  }
-
-  debug_clang = {
-    name = "lm3s6965evb_qemu_clang_debug"
-    _toolchain_base = pw_toolchain_arm_clang.cortex_m3_debug
-    forward_variables_from(_toolchain_base, "*", _excluded_members)
-    defaults = {
-      forward_variables_from(_toolchain_base.defaults, "*")
-      forward_variables_from(_target_config, "*")
-      default_configs += _clang_target_default_configs
-    }
-  }
-
-  speed_optimized_clang = {
-    name = "lm3s6965evb_qemu_clang_speed_optimized"
-    _toolchain_base = pw_toolchain_arm_clang.cortex_m3_speed_optimized
-    forward_variables_from(_toolchain_base, "*", _excluded_members)
-    defaults = {
-      forward_variables_from(_toolchain_base.defaults, "*")
-      forward_variables_from(_target_config, "*")
-      default_configs += _clang_target_default_configs
-    }
-  }
-
-  size_optimized_clang = {
-    name = "lm3s6965evb_qemu_clang_size_optimized"
-    _toolchain_base = pw_toolchain_arm_clang.cortex_m3_size_optimized
-    forward_variables_from(_toolchain_base, "*", _excluded_members)
-    defaults = {
-      forward_variables_from(_toolchain_base.defaults, "*")
-      forward_variables_from(_target_config, "*")
-      default_configs += _clang_target_default_configs
-    }
-  }
-}
-
-# This list just contains the members of the above scope for convenience to make
-# it trivial to generate all the toolchains in this file via a
-# `generate_toolchains` target.
-pw_target_toolchain_lm3s6965evb_qemu_list = [
-  pw_target_toolchain_lm3s6965evb_qemu.debug,
-  pw_target_toolchain_lm3s6965evb_qemu.speed_optimized,
-  pw_target_toolchain_lm3s6965evb_qemu.size_optimized,
-  pw_target_toolchain_lm3s6965evb_qemu.debug_clang,
-  pw_target_toolchain_lm3s6965evb_qemu.speed_optimized_clang,
-  pw_target_toolchain_lm3s6965evb_qemu.size_optimized_clang,
-]
diff --git a/targets/lm3s6965evb_qemu/vector_table.c b/targets/lm3s6965evb_qemu/vector_table.c
deleted file mode 100644
index 653b649..0000000
--- a/targets/lm3s6965evb_qemu/vector_table.c
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <stdbool.h>
-
-#include "pw_boot/boot.h"
-#include "pw_boot_cortex_m/boot.h"
-
-// Default handler to insert into the ARMv7-M vector table (below).
-// This function exists for convenience. If a device isn't doing what you
-// expect, it might have hit a fault and ended up here.
-static void DefaultFaultHandler(void) {
-  while (true) {
-    // Wait for debugger to attach.
-  }
-}
-
-// This is the device's interrupt vector table. It's not referenced in any
-// code because the platform (STM32F4xx) expects this table to be present at the
-// beginning of flash. The exact address is specified in the pw_boot_cortex_m
-// configuration as part of the target config.
-//
-// For more information, see ARMv7-M Architecture Reference Manual DDI 0403E.b
-// section B1.5.3.
-
-// This typedef is for convenience when building the vector table. With the
-// exception of SP_main (0th entry in the vector table), all the entries of the
-// vector table are function pointers.
-typedef void (*InterruptHandler)(void);
-
-PW_KEEP_IN_SECTION(".vector_table")
-const InterruptHandler vector_table[] = {
-    // The starting location of the stack pointer.
-    // This address is NOT an interrupt handler/function pointer, it is simply
-    // the address that the main stack pointer should be initialized to. The
-    // value is reinterpret casted because it needs to be in the vector table.
-    [0] = (InterruptHandler)(&pw_boot_stack_high_addr),
-
-    // Reset handler, dictates how to handle reset interrupt. This is the
-    // address that the Program Counter (PC) is initialized to at boot.
-    [1] = pw_boot_Entry,
-
-    // NMI handler.
-    [2] = DefaultFaultHandler,
-    // HardFault handler.
-    [3] = DefaultFaultHandler,
-};
diff --git a/targets/mimxrt595_evk/BUILD.bazel b/targets/mimxrt595_evk/BUILD.bazel
deleted file mode 100644
index 6f70ddd..0000000
--- a/targets/mimxrt595_evk/BUILD.bazel
+++ /dev/null
@@ -1,47 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-# TODO(pwbug/545): These just list sources files to pass presubmit and aren't
-# expected to actually work.
-pw_cc_library(
-    name = "boot",
-    srcs = [
-        "boot.cc",
-        "vector_table.c",
-    ],
-    deps = [
-        "//pw_boot",
-        "//pw_boot_cortex_m",
-        "//pw_preprocessor",
-        "//pw_sys_io_mcuxpresso",
-    ],
-)
-
-pw_cc_library(
-    name = "system_rpc_server",
-    srcs = ["system_rpc_server.cc"],
-    deps = [
-        "//pw_hdlc:pw_rpc",
-        "//pw_rpc/system_server:facade",
-    ],
-)
diff --git a/targets/mimxrt595_evk/BUILD.gn b/targets/mimxrt595_evk/BUILD.gn
deleted file mode 100644
index 9d60034..0000000
--- a/targets/mimxrt595_evk/BUILD.gn
+++ /dev/null
@@ -1,137 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/linker_script.gni")
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_malloc/backend.gni")
-import("$dir_pw_third_party/mcuxpresso/mcuxpresso.gni")
-import("$dir_pw_toolchain/generate_toolchain.gni")
-import("target_toolchains.gni")
-
-generate_toolchains("target_toolchains") {
-  toolchains = pw_target_toolchain_mimxrt595_evk_list
-}
-
-declare_args() {
-  # When compiling with an MCUXpresso SDK, this variable is set to the path of
-  # the manifest file within the SDK installation. When set, a pw_source_set
-  # for a sample project SDK is created at
-  # "//targets/mimxrt595_evk/sample_sdk".
-  pw_target_mimxrt595_evk_MANIFEST = ""
-
-  # This list should contain the necessary defines for setting linker script
-  # memory regions. While we don't directly use the pw_boot_cortex_m linker
-  # script, these are deliberately matching to make being able to later easier.
-  pw_target_mimxrt595_evk_LINK_CONFIG_DEFINES = []
-}
-
-config("pw_malloc_active") {
-  if (pw_malloc_BACKEND != "") {
-    defines = [ "PW_MALLOC_ACTIVE=1" ]
-  }
-}
-
-config("disable_warnings") {
-  cflags = [
-    "-Wno-cast-qual",
-    "-Wno-redundant-decls",
-    "-Wno-undef",
-    "-Wno-unused-parameter",
-    "-Wno-unused-variable",
-  ]
-  visibility = [ ":*" ]
-}
-
-config("freestanding") {
-  cflags = [
-    "-ffreestanding",
-    "-fno-builtin",
-  ]
-  asmflags = cflags
-  ldflags = cflags
-  visibility = [ ":*" ]
-}
-
-config("sdk_defines") {
-  defines = [
-    "CPU_MIMXRT595SFFOC_cm33",
-    "DEBUG_CONSOLE_TRANSFER_NON_BLOCKING",
-    "SDK_DEBUGCONSOLE=1",
-  ]
-  visibility = [ ":*" ]
-}
-
-if (current_toolchain != default_toolchain) {
-  pw_linker_script("flash_linker_script") {
-    defines = pw_target_mimxrt595_evk_LINK_CONFIG_DEFINES
-    linker_script = "mimxrt595_flash.ld"
-  }
-
-  pw_source_set("system_rpc_server") {
-    deps = [
-      "$dir_pw_hdlc:pw_rpc",
-      "$dir_pw_hdlc:rpc_channel_output",
-      "$dir_pw_rpc/system_server:facade",
-      "$dir_pw_stream:sys_io_stream",
-      dir_pw_log,
-    ]
-    sources = [ "system_rpc_server.cc" ]
-  }
-}
-
-if (pw_third_party_mcuxpresso_SDK != "") {
-  # Startup and vector table for NXP MIMXRT595-EVK.
-  pw_source_set("boot") {
-    public_configs = [ ":pw_malloc_active" ]
-    deps = [
-      "$dir_pw_boot",
-      "$dir_pw_boot_cortex_m",
-      "$dir_pw_preprocessor",
-      "$dir_pw_sys_io_mcuxpresso",
-      pw_third_party_mcuxpresso_SDK,
-    ]
-    if (pw_malloc_BACKEND != "") {
-      deps += [ "$dir_pw_malloc" ]
-    }
-    sources = [
-      "boot.cc",
-      "vector_table.c",
-    ]
-  }
-}
-
-if (pw_third_party_mcuxpresso_SDK == "//targets/mimxrt595_evk:sample_sdk") {
-  pw_mcuxpresso_sdk("sample_sdk") {
-    manifest = pw_target_mimxrt595_evk_MANIFEST
-    include = [
-      "component.serial_manager_uart.MIMXRT595S",
-      "project_template.evkmimxrt595.MIMXRT595S",
-      "utility.debug_console.MIMXRT595S",
-    ]
-    exclude = [ "device.MIMXRT595S_startup.MIMXRT595S" ]
-
-    public_configs = [
-      ":disable_warnings",
-      ":freestanding",
-      ":sdk_defines",
-    ]
-  }
-}
-
-pw_doc_group("target_docs") {
-  sources = [ "target_docs.rst" ]
-}
diff --git a/targets/mimxrt595_evk/boot.cc b/targets/mimxrt595_evk/boot.cc
deleted file mode 100644
index 914453f..0000000
--- a/targets/mimxrt595_evk/boot.cc
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_boot/boot.h"
-
-#include "board.h"
-#include "clock_config.h"
-#include "peripherals.h"
-#include "pin_mux.h"
-#include "pw_boot_cortex_m/boot.h"
-#include "pw_preprocessor/compiler.h"
-#include "pw_sys_io_mcuxpresso/init.h"
-
-#if PW_MALLOC_ACTIVE
-#include "pw_malloc/malloc.h"
-#endif  // PW_MALLOC_ACTIVE
-
-void pw_boot_PreStaticMemoryInit() {
-  // Call CMSIS SystemInit code.
-  SystemInit();
-}
-
-void pw_boot_PreStaticConstructorInit() {
-#if PW_MALLOC_ACTIVE
-  pw_MallocInit(&pw_boot_heap_low_addr, &pw_boot_heap_high_addr);
-#endif  // PW_MALLOC_ACTIVE
-}
-
-void pw_boot_PreMainInit() {
-  BOARD_InitPins();
-  BOARD_InitBootClocks();
-  BOARD_InitPeripherals();
-
-  pw_sys_io_mcuxpresso_Init();
-}
-
-PW_NO_RETURN void pw_boot_PostMain() {
-  // In case main() returns, just sit here until the device is reset.
-  while (true) {
-  }
-  PW_UNREACHABLE;
-}
diff --git a/targets/mimxrt595_evk/mimxrt595_executable.gni b/targets/mimxrt595_evk/mimxrt595_executable.gni
deleted file mode 100644
index ba1a708..0000000
--- a/targets/mimxrt595_evk/mimxrt595_executable.gni
+++ /dev/null
@@ -1,64 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/exec.gni")
-import("$dir_pw_malloc/backend.gni")
-
-# Executable wrapper that includes some baremetal startup code.
-template("mimxrt595_executable") {
-  group(target_name) {
-    deps = [
-      ":${target_name}__binary",
-      ":${target_name}__elf",
-    ]
-  }
-
-  # .elf binary created by the standard toolchain.
-  _base_target_name = target_name
-  executable("${target_name}__elf") {
-    output_name = "${_base_target_name}"
-    forward_variables_from(invoker, "*")
-    if (!defined(deps)) {
-      deps = []
-    }
-    deps += [ "//targets/mimxrt595_evk:boot" ]
-    if (pw_malloc_BACKEND != "") {
-      if (!defined(configs)) {
-        configs = []
-      }
-      configs += [ "$dir_pw_malloc:pw_malloc_wrapper_config" ]
-    }
-  }
-
-  # .bin binary created by extracting from the toolchain output.
-  pw_exec("${target_name}__binary") {
-    if (defined(invoker.output_dir)) {
-      _output_dir = invoker.output_dir
-    } else {
-      _output_dir = target_out_dir
-    }
-
-    outputs = [ "${_output_dir}/${_base_target_name}.bin" ]
-    deps = [ ":${_base_target_name}__elf" ]
-
-    program = "arm-none-eabi-objcopy"
-    args = [
-      "-Obinary",
-      "<TARGET_FILE(:${_base_target_name}__elf)>",
-      rebase_path(outputs[0], root_build_dir),
-    ]
-  }
-}
diff --git a/targets/mimxrt595_evk/mimxrt595_flash.ld b/targets/mimxrt595_evk/mimxrt595_flash.ld
deleted file mode 100644
index 46e79ff..0000000
--- a/targets/mimxrt595_evk/mimxrt595_flash.ld
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * Copyright 2021 The Pigweed Authors
- *
- * 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
- *
- *     https://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.
- */
-
-/* This linker script is derived from pw_boot_cortex_m/basic_cortex_m.ld for use
- * with the NXP MIMXRT595-EVK, booting from FLASH.
- */
-
-/* Provide useful error messages when required configurations are not set. */
-#ifndef PW_BOOT_VECTOR_TABLE_BEGIN
-#error "PW_BOOT_VECTOR_TABLE_BEGIN is not defined, and is required to use pw_boot_cortex_m"
-#endif  // PW_BOOT_VECTOR_TABLE_BEGIN
-
-#ifndef PW_BOOT_VECTOR_TABLE_SIZE
-#error "PW_BOOT_VECTOR_TABLE_SIZE is not defined, and is required to use pw_boot_cortex_m"
-#endif  // PW_BOOT_VECTOR_TABLE_SIZE
-
-#ifndef PW_BOOT_FLASH_BEGIN
-#error "PW_BOOT_FLASH_BEGIN is not defined, and is required to use pw_boot_cortex_m"
-#endif  // PW_BOOT_FLASH_BEGIN
-
-#ifndef PW_BOOT_FLASH_SIZE
-#error "PW_BOOT_FLASH_SIZE is not defined, and is required to use pw_boot_cortex_m"
-#endif  // PW_BOOT_FLASH_SIZE
-
-#ifndef PW_BOOT_RAM_BEGIN
-#error "PW_BOOT_RAM_BEGIN is not defined, and is required to use pw_boot_cortex_m"
-#endif  // PW_BOOT_RAM_BEGIN
-
-#ifndef PW_BOOT_RAM_SIZE
-#error "PW_BOOT_RAM_SIZE is not defined, and is required to use pw_boot_cortex_m"
-#endif  // PW_BOOT_RAM_SIZE
-
-#ifndef PW_BOOT_HEAP_SIZE
-#error "PW_BOOT_HEAP_SIZE is not defined, and is required to use pw_boot_cortex_m"
-#endif  // PW_BOOT_HEAP_SIZE
-
-#ifndef PW_BOOT_MIN_STACK_SIZE
-#error "PW_BOOT_MIN_STACK_SIZE is not defined, and is required to use pw_boot_cortex_m"
-#endif  // PW_BOOT_MIN_STACK_SIZE
-
-
-/* Note: This technically doesn't set the firmware's entry point. Setting the
- *       firmware entry point is done by setting vector_table[1]
- *       (Reset_Handler). However, this DOES tell the compiler how to optimize
- *       when --gc-sections is enabled.
- */
-ENTRY(pw_boot_Entry)
-
-MEMORY
-{
-  /* Flash Config for bootloader */
-  FLASH_CONFIG(rx) : \
-    ORIGIN = 0x08000400, \
-    LENGTH = 0x00000200
-  /* Vector Table (typically in flash) */
-  VECTOR_TABLE(rx) : \
-    ORIGIN = PW_BOOT_VECTOR_TABLE_BEGIN, \
-    LENGTH = PW_BOOT_VECTOR_TABLE_SIZE
-  /* Internal Flash */
-  FLASH(rx) : \
-    ORIGIN = PW_BOOT_FLASH_BEGIN, \
-    LENGTH = PW_BOOT_FLASH_SIZE
-  /* Internal SRAM */
-  RAM(rwx) : \
-    ORIGIN = PW_BOOT_RAM_BEGIN, \
-    LENGTH = PW_BOOT_RAM_SIZE
-  /* USB SRAM */
-  USB_SRAM(rw) : \
-    ORIGIN = 0x40140000, \
-    LENGTH = 0x00004000
-}
-
-SECTIONS
-{
-  .flash_config :
-  {
-    . = ALIGN(4);
-    KEEP(*(.flash_conf))
-  } >FLASH_CONFIG
-
-  /* This is the link-time vector table. If used, the VTOR (Vector Table Offset
-   * Register) MUST point to this memory location in order to be used. This can
-   * be done by ensuring this section exists at the default location of the VTOR
-   * so it's used on reset, or by explicitly setting the VTOR in a bootloader
-   * manually to point to &pw_boot_vector_table_addr before interrupts are enabled.
-   */
-  .vector_table : ALIGN(512)
-  {
-    pw_boot_vector_table_addr = .;
-    KEEP(*(.vector_table))
-  } >VECTOR_TABLE
-
-  /* Main executable code. */
-  .code : ALIGN(8)
-  {
-    . = ALIGN(8);
-    /* Application code. */
-    *(.text)
-    *(.text*)
-    KEEP(*(.init))
-    KEEP(*(.fini))
-
-    . = ALIGN(8);
-    /* Constants.*/
-    *(.rodata)
-    *(.rodata*)
-
-    /* Glue ARM to Thumb code, and vice-versa */
-    *(.glue_7)
-    *(.glue_7t)
-
-    /* Exception handling frame */
-    *(.eh_frame)
-
-    /* .preinit_array, .init_array, .fini_array are used by libc.
-     * Each section is a list of function pointers that are called pre-main and
-     * post-exit for object initialization and tear-down.
-     * Since the region isn't explicitly referenced, specify KEEP to prevent
-     * link-time garbage collection. SORT is used for sections that have strict
-     * init/de-init ordering requirements. */
-    . = ALIGN(8);
-    PROVIDE_HIDDEN(__preinit_array_start = .);
-    KEEP(*(.preinit_array*))
-    PROVIDE_HIDDEN(__preinit_array_end = .);
-
-    PROVIDE_HIDDEN(__init_array_start = .);
-    KEEP(*(SORT(.init_array.*)))
-    KEEP(*(.init_array*))
-    PROVIDE_HIDDEN(__init_array_end = .);
-
-    PROVIDE_HIDDEN(__fini_array_start = .);
-    KEEP(*(SORT(.fini_array.*)))
-    KEEP(*(.fini_array*))
-    PROVIDE_HIDDEN(__fini_array_end = .);
-  } >FLASH
-
-  /* Used by unwind-arm/ */
-  .ARM : ALIGN(8) {
-    __exidx_start = .;
-    *(.ARM.exidx*)
-    __exidx_end = .;
-  } >FLASH
-
-  /* Explicitly initialized global and static data. (.data)*/
-  .static_init_ram : ALIGN(8)
-  {
-    *(CodeQuickAccess)
-    *(DataQuickAccess)
-    *(.data)
-    *(.data*)
-    . = ALIGN(8);
-  } >RAM AT> FLASH
-
-  /* Zero initialized global/static data. (.bss)
-   * This section is zero initialized in pw_boot_Entry(). */
-  .zero_init_ram : ALIGN(8)
-  {
-    *(.bss)
-    *(.bss*)
-    *(COMMON)
-    . = ALIGN(8);
-  } >RAM
-
-  .heap : ALIGN(8)
-  {
-    pw_boot_heap_low_addr = .;
-    . = . + PW_BOOT_HEAP_SIZE;
-    . = ALIGN(8);
-    pw_boot_heap_high_addr = .;
-  } >RAM
-
-  /* Link-time check for stack overlaps. */
-  .stack (NOLOAD) : ALIGN(8)
-  {
-    /* Set the address that the main stack pointer should be initialized to. */
-    pw_boot_stack_low_addr = .;
-    HIDDEN(_stack_size = ORIGIN(RAM) + LENGTH(RAM) - .);
-    /* Align the stack to a lower address to ensure it isn't out of range. */
-    HIDDEN(_stack_high = (. + _stack_size) & ~0x7);
-    ASSERT(_stack_high - . >= PW_BOOT_MIN_STACK_SIZE,
-           "Error: Not enough RAM for desired minimum stack size.");
-    . = _stack_high;
-    pw_boot_stack_high_addr = .;
-  } >RAM
-
-  m_usb_bdt (NOLOAD) :
-  {
-    . = ALIGN(512);
-    *(m_usb_bdt)
-  } >USB_SRAM
-
-  m_usb_global (NOLOAD) :
-  {
-    *(m_usb_global)
-  } >USB_SRAM
-
-  /* Discard unwind info. */
-  .ARM.extab 0x0 (INFO) :
-  {
-    KEEP(*(.ARM.extab*))
-  }
-
-  .ARM.attributes 0 : { *(.ARM.attributes) }
-}
-
-/* Symbols used by core_init.c: */
-/* Start of .static_init_ram in FLASH. */
-_pw_static_init_flash_start = LOADADDR(.static_init_ram);
-
-/* Region of .static_init_ram in RAM. */
-_pw_static_init_ram_start = ADDR(.static_init_ram);
-_pw_static_init_ram_end = _pw_static_init_ram_start + SIZEOF(.static_init_ram);
-
-/* Region of .zero_init_ram. */
-_pw_zero_init_ram_start = ADDR(.zero_init_ram);
-_pw_zero_init_ram_end = _pw_zero_init_ram_start + SIZEOF(.zero_init_ram);
-
-/* Size of image for bootloader header. */
-_pw_image_size = _pw_static_init_flash_start + (_pw_static_init_ram_end - _pw_static_init_ram_start) - pw_boot_vector_table_addr;
-
-/* arm-none-eabi expects `end` symbol to point to start of heap for sbrk. */
-PROVIDE(end = _pw_zero_init_ram_end);
diff --git a/targets/mimxrt595_evk/system_rpc_server.cc b/targets/mimxrt595_evk/system_rpc_server.cc
deleted file mode 100644
index 52be514..0000000
--- a/targets/mimxrt595_evk/system_rpc_server.cc
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstddef>
-
-#include "pw_hdlc/rpc_channel.h"
-#include "pw_hdlc/rpc_packets.h"
-#include "pw_log/log.h"
-#include "pw_rpc_system_server/rpc_server.h"
-#include "pw_stream/sys_io_stream.h"
-
-namespace pw::rpc::system_server {
-namespace {
-
-constexpr size_t kMaxTransmissionUnit = 256;
-
-// Used to write HDLC data to pw::sys_io.
-stream::SysIoWriter writer;
-stream::SysIoReader reader;
-
-// Set up the output channel for the pw_rpc server to use.
-hdlc::RpcChannelOutput hdlc_channel_output(writer,
-                                           pw::hdlc::kDefaultRpcAddress,
-                                           "HDLC channel");
-Channel channels[] = {pw::rpc::Channel::Create<1>(&hdlc_channel_output)};
-rpc::Server server(channels);
-
-}  // namespace
-
-void Init() {
-  // Send log messages to HDLC address 1. This prevents logs from interfering
-  // with pw_rpc communications.
-  pw::log_basic::SetOutput([](std::string_view log) {
-    pw::hdlc::WriteUIFrame(1, std::as_bytes(std::span(log)), writer);
-  });
-}
-
-rpc::Server& Server() { return server; }
-
-Status Start() {
-  // Declare a buffer for decoding incoming HDLC frames.
-  std::array<std::byte, kMaxTransmissionUnit> input_buffer;
-  hdlc::Decoder decoder(input_buffer);
-
-  while (true) {
-    std::byte byte;
-    Status ret_val = pw::sys_io::ReadByte(&byte);
-    if (!ret_val.ok()) {
-      return ret_val;
-    }
-    if (auto result = decoder.Process(byte); result.ok()) {
-      hdlc::Frame& frame = result.value();
-      if (frame.address() == hdlc::kDefaultRpcAddress) {
-        server.ProcessPacket(frame.data(), hdlc_channel_output);
-      }
-    }
-  }
-}
-
-}  // namespace pw::rpc::system_server
diff --git a/targets/mimxrt595_evk/target_docs.rst b/targets/mimxrt595_evk/target_docs.rst
deleted file mode 100644
index 7b6b3c8..0000000
--- a/targets/mimxrt595_evk/target_docs.rst
+++ /dev/null
@@ -1,142 +0,0 @@
-.. _target-mimxrt595-evk:
-
--------------
-mimxrt595-evk
--------------
-The NXP MIMXRT595-EVK_ evaluation board is a demonstration target for on-device
-Pigweed development
-
-.. _MIMXRT595-EVK: https://www.nxp.com/design/development-boards/i-mx-evaluation-and-development-boards/i-mx-rt595-evaluation-kit:MIMXRT595-EVK
-
-Configuring
-===========
-Step 1: Download SDK
---------------------
-To configure this Pigweed target you will first need to download an NXP
-`MCUXpresso SDK`_ for your device and unpack it within your project source tree.
-
-.. _MCUXpresso SDK: https://mcuxpresso.nxp.com/en/welcome
-
-Step 2: Create SDK source set
------------------------------
-You'll next need to create a source set based on the downloaded SDK, and set
-the ``pw_third_party_mcuxpresso_SDK`` build arg to the name of the source set
-you create. See :ref:`module-pw_build_mcuxpresso` for more details.
-
-Alternatively to get started you can start with the basic project template by
-setting the ``pw_target_mimxrt595_evk_MANIFEST`` build arg to the location of
-the manifest file within the unpacked SDK, and then setting the
-``pw_third_party_mcuxpresso_SDK`` to the ``sample_sdk`` source set within the
-Pigweed target directory.
-
-.. code:: sh
-
-  $ gn args out
-  # Modify and save the args file to use the sample SDK.
-  pw_target_mimxrt595_evk_MANIFEST = "//third_party/mcuxpresso/sdk/EVK-MIMXRT595_manifest_v3_8.xml"
-  pw_third_party_mcuxpresso_SDK = "//targets/mimxrt595_evk:sample_sdk"
-
-Building
-========
-Once configured, to build for this Pigweed target, simply build the top-level
-"mimxrt595" Ninja target.
-
-.. code:: sh
-
-  $ ninja -C out mimxrt595
-
-Running and Debugging
-=====================
-First Time Setup
-----------------
-The MIMXRT595-EVK comes with an integrated Link2 debug probe that can operate in
-either CMSIS-DAP or SEGGER J-Link mode. CMSIS-DAP is how the board will likely
-come by default, but J-Link is the mode that is currently known to work, so
-you'll need to flash the Link2 with the correct firmware.
-
-1. Download and install the LPCScrypt_ utility from the NXP website.
-
-2. Place a jumper over **JP1** (not **J1**). If you're having trouble locating
-   this, it's in the top-right of the board in a block of four jumpers closest
-   to the USB ports.
-
-3. Connect a USB cable into the top-right USB port (**J40**) and your computer.
-
-4. Run ``scripts/boot_lpcscrypt`` from the LPCScrypt installation.
-
-5. Run ``scripts/program_JLINK`` from the LPCScrypt installation, press the
-   *SPACE* key to update the firmware.
-
-6. Unplug the USB cable and remove the **JP1** jumper.
-
-Now is also a good time to download and install the J-Link_ package from the
-SEGGER website.
-
-.. _LPCScrypt: https://www.nxp.com/design/microcontrollers-developer-resources/lpcscrypt-v2-1-2:LPCSCRYPT
-.. _J-Link: https://www.segger.com/downloads/jlink/
-
-General Setup
--------------
-Each time you prepare the MIMXRT595-EVK for use, you'll need to do a few steps.
-You don't need to repeat these if you leave everything setup and don't
-disconnect or reboot.
-
-1. Ensure the **SW7** DIP switches are set to Off-Off-On (boot from QSPI Flash).
-
-2. Connect a USB cable into the top-right USB port (**J40**) and your computer.
-
-3. Start the J-Link GDB Server and leave this running:
-
-   .. code-block:: sh
-
-     JLinkGDBServer -select USB -device MIMXRT595S -endian little -if SWD -speed 4000 -noir
-
-On Linux, you may need to install the `libncurses5` library to use the tools:
-
-.. code-block:: sh
-
-  sudo apt install libncurses5
-
-Running and Debugging
----------------------
-Use ``arm-none-eabi-gdb`` to load an executable into the target, debug, and run
-it.
-
-.. code-block::
-  :emphasize-lines: 1,6,10,12,20
-
-  (gdb) target remote :2331
-  Remote debugging using :2331
-  warning: No executable has been specified and target does not support
-  determining executable automatically.  Try using the "file" command.
-  0x08000000 in ?? ()
-  (gdb) file out/mimxrt595_evk_debug/obj/pw_status/test/status_test.elf
-  A program is being debugged already.
-  Are you sure you want to change the file? (y or n) y
-  Reading symbols from out/mimxrt595_evk_debug/obj/pw_status/test/status_test.elf...
-  (gdb) monitor reset
-  Resetting target
-  (gdb) load
-  Loading section .flash_config, size 0x200 lma 0x8000400
-  Loading section .vector_table, size 0x168 lma 0x8001000
-  Loading section .code, size 0xb34c lma 0x8001180
-  Loading section .ARM, size 0x8 lma 0x800c4d0
-  Loading section .static_init_ram, size 0x3c8 lma 0x800c4d8
-  Start address 0x080048d0, load size 47748
-  Transfer rate: 15542 KB/sec, 6821 bytes/write.
-  (gdb) monitor reset
-  Resetting target
-
-You can now set any breakpoints you wish, and ``continue`` to run the
-executable.
-
-To reset the target use ``monitor reset``.
-
-To load an updated version of the same file, after resetting the target,
-use ``load`` and a second ``monitor reset`` as shown above.
-
-To debug a new file, use ``file`` before ``load``.
-
-Debug console is available on the USB serial port, e.g. ``/dev/ttyACM0``
-(Linux) or ``/dev/tty.usbmodem*`` (Mac).
-
diff --git a/targets/mimxrt595_evk/target_toolchains.gni b/targets/mimxrt595_evk/target_toolchains.gni
deleted file mode 100644
index ff34402..0000000
--- a/targets/mimxrt595_evk/target_toolchains.gni
+++ /dev/null
@@ -1,127 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_boot/backend.gni")
-import("$dir_pw_log/backend.gni")
-import("$dir_pw_rpc/system_server/backend.gni")
-import("$dir_pw_sync/backend.gni")
-import("$dir_pw_sys_io/backend.gni")
-import("$dir_pw_toolchain/arm_gcc/toolchains.gni")
-
-_target_config = {
-  # Use the logging main.
-  pw_unit_test_MAIN = "$dir_pw_unit_test:logging_main"
-
-  # Configuration options for Pigweed executable targets.
-  pw_build_EXECUTABLE_TARGET_TYPE = "mimxrt595_executable"
-
-  pw_build_EXECUTABLE_TARGET_TYPE_FILE =
-      get_path_info("mimxrt595_executable.gni", "abspath")
-
-  # Facade backends
-  pw_assert_BACKEND = dir_pw_assert_basic
-  pw_boot_BACKEND = "$dir_pw_boot_cortex_m"
-  pw_sync_INTERRUPT_SPIN_LOCK_BACKEND =
-      "$dir_pw_sync_baremetal:interrupt_spin_lock"
-  pw_sync_MUTEX_BACKEND = "$dir_pw_sync_baremetal:mutex"
-  pw_log_BACKEND = dir_pw_log_basic
-  pw_sys_io_BACKEND = "$dir_pw_sys_io_mcuxpresso"
-  pw_rpc_system_server_BACKEND =
-      "$dir_pigweed/targets/mimxrt595_evk:system_rpc_server"
-
-  # Override the default pw_boot_cortex_m linker script and set the memory
-  # regions for the target.
-  pw_boot_cortex_m_LINKER_SCRIPT = "//targets/mimxrt595_evk:flash_linker_script"
-  pw_target_mimxrt595_evk_LINK_CONFIG_DEFINES = [
-    "PW_BOOT_FLASH_BEGIN=0x08001180",
-    "PW_BOOT_FLASH_SIZE=0x001FEE80",
-    "PW_BOOT_HEAP_SIZE=200K",
-    "PW_BOOT_MIN_STACK_SIZE=1K",
-    "PW_BOOT_RAM_BEGIN=0x20080000",
-    "PW_BOOT_RAM_SIZE=0x00280000",
-    "PW_BOOT_VECTOR_TABLE_BEGIN=0x08001000",
-    "PW_BOOT_VECTOR_TABLE_SIZE=0x00000180",
-  ]
-
-  pw_build_LINK_DEPS = [
-    "$dir_pw_assert:impl",
-    "$dir_pw_log:impl",
-  ]
-
-  current_cpu = "arm"
-  current_os = ""
-}
-
-_toolchain_properties = {
-  final_binary_extension = ".elf"
-}
-
-_target_default_configs = [
-  "$dir_pw_build:extra_strict_warnings",
-  "$dir_pw_toolchain/arm_gcc:enable_float_printf",
-]
-
-pw_target_toolchain_mimxrt595_evk = {
-  _excluded_members = [
-    "defaults",
-    "name",
-  ]
-
-  debug = {
-    name = "mimxrt595_evk_debug"
-    _toolchain_base = pw_toolchain_arm_gcc.cortex_m33f_debug
-    forward_variables_from(_toolchain_base, "*", _excluded_members)
-    forward_variables_from(_toolchain_properties, "*")
-    defaults = {
-      forward_variables_from(_toolchain_base.defaults, "*")
-      forward_variables_from(_target_config, "*")
-      default_configs += _target_default_configs
-    }
-  }
-
-  speed_optimized = {
-    name = "mimxrt595_evk_speed_optimized"
-    _toolchain_base = pw_toolchain_arm_gcc.cortex_m33f_speed_optimized
-    forward_variables_from(_toolchain_base, "*", _excluded_members)
-    forward_variables_from(_toolchain_properties, "*")
-    defaults = {
-      forward_variables_from(_toolchain_base.defaults, "*")
-      forward_variables_from(_target_config, "*")
-      default_configs += _target_default_configs
-    }
-  }
-
-  size_optimized = {
-    name = "mimxrt595_evk_size_optimized"
-    _toolchain_base = pw_toolchain_arm_gcc.cortex_m33f_size_optimized
-    forward_variables_from(_toolchain_base, "*", _excluded_members)
-    forward_variables_from(_toolchain_properties, "*")
-    defaults = {
-      forward_variables_from(_toolchain_base.defaults, "*")
-      forward_variables_from(_target_config, "*")
-      default_configs += _target_default_configs
-    }
-  }
-}
-
-# This list just contains the members of the above scope for convenience to make
-# it trivial to generate all the toolchains in this file via a
-# `generate_toolchains` target.
-pw_target_toolchain_mimxrt595_evk_list = [
-  pw_target_toolchain_mimxrt595_evk.debug,
-  pw_target_toolchain_mimxrt595_evk.speed_optimized,
-  pw_target_toolchain_mimxrt595_evk.size_optimized,
-]
diff --git a/targets/mimxrt595_evk/vector_table.c b/targets/mimxrt595_evk/vector_table.c
deleted file mode 100644
index 98aac95..0000000
--- a/targets/mimxrt595_evk/vector_table.c
+++ /dev/null
@@ -1,414 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <stdbool.h>
-
-#include "pw_boot/boot.h"
-#include "pw_boot_cortex_m/boot.h"
-#include "pw_preprocessor/compiler.h"
-
-// Extern symbols provided by linker script.
-// This symbol contains the size of the image.
-extern uint8_t _pw_image_size;
-
-// Default handler to insert into the ARMv8-M vector table (below).
-// This function exists for convenience. If a device isn't doing what you
-// expect, it might have hit a fault and ended up here.
-static void DefaultFaultHandler(void) {
-  while (true) {
-    // Wait for debugger to attach.
-  }
-}
-
-// Default interrupt handler that entries in the ARMv8-M vector table (below)
-// are aliased to, allowing them to be replaced at link time with OS or SDK
-// implementations. If a device isn't doing what you expect, it might have
-// raised an interrupt and ended up here.
-static void DefaultInterruptHandler(void) {
-  while (true) {
-    // Wait for debugger to attach.
-  }
-}
-
-// Default handlers to insert into the ARMv8-M vector table (below) that
-// are likely to be replaced by OS implementations.
-void SVC_Handler(void) PW_ALIAS(DefaultInterruptHandler);
-void DebugMon_Handler(void) PW_ALIAS(DefaultInterruptHandler);
-void PendSV_Handler(void) PW_ALIAS(DefaultInterruptHandler);
-void SysTick_Handler(void) PW_ALIAS(DefaultInterruptHandler);
-
-// Default handlers to insert into the ARMv8-M vector table (below) that
-// are call a driver implementation that may be provided by the SDK.
-static void WDT0_IRQHandler(void);
-static void DMA0_IRQHandler(void);
-static void GPIO_INTA_IRQHandler(void);
-static void GPIO_INTB_IRQHandler(void);
-static void PIN_INT0_IRQHandler(void);
-static void PIN_INT1_IRQHandler(void);
-static void PIN_INT2_IRQHandler(void);
-static void PIN_INT3_IRQHandler(void);
-static void UTICK0_IRQHandler(void);
-static void MRT0_IRQHandler(void);
-static void CTIMER0_IRQHandler(void);
-static void CTIMER1_IRQHandler(void);
-static void SCT0_IRQHandler(void);
-static void CTIMER3_IRQHandler(void);
-static void FLEXCOMM0_IRQHandler(void);
-static void FLEXCOMM1_IRQHandler(void);
-static void FLEXCOMM2_IRQHandler(void);
-static void FLEXCOMM3_IRQHandler(void);
-static void FLEXCOMM4_IRQHandler(void);
-static void FLEXCOMM5_IRQHandler(void);
-static void FLEXCOMM14_IRQHandler(void);
-static void FLEXCOMM15_IRQHandler(void);
-static void ADC0_IRQHandler(void);
-static void Reserved39_IRQHandler(void);
-static void ACMP_IRQHandler(void);
-static void DMIC0_IRQHandler(void);
-static void Reserved42_IRQHandler(void);
-static void HYPERVISOR_IRQHandler(void);
-static void SECURE_VIOLATION_IRQHandler(void);
-static void HWVAD0_IRQHandler(void);
-static void Reserved46_IRQHandler(void);
-static void RNG_IRQHandler(void);
-static void RTC_IRQHandler(void);
-static void DSP_TIE_EXPSTATE1_IRQHandler(void);
-static void MU_A_IRQHandler(void);
-static void PIN_INT4_IRQHandler(void);
-static void PIN_INT5_IRQHandler(void);
-static void PIN_INT6_IRQHandler(void);
-static void PIN_INT7_IRQHandler(void);
-static void CTIMER2_IRQHandler(void);
-static void CTIMER4_IRQHandler(void);
-static void OS_EVENT_IRQHandler(void);
-static void FLEXSPI0_FLEXSPI1_IRQHandler(void);
-static void FLEXCOMM6_IRQHandler(void);
-static void FLEXCOMM7_IRQHandler(void);
-static void USDHC0_IRQHandler(void);
-static void USDHC1_IRQHandler(void);
-static void SGPIO_INTA_IRQHandler(void);
-static void SGPIO_INTB_IRQHandler(void);
-static void I3C0_IRQHandler(void);
-static void USB0_IRQHandler(void);
-static void USB0_NEEDCLK_IRQHandler(void);
-static void WDT1_IRQHandler(void);
-static void USB_PHYDCD_IRQHandler(void);
-static void DMA1_IRQHandler(void);
-static void PUF_IRQHandler(void);
-static void POWERQUAD_IRQHandler(void);
-static void CASPER_IRQHandler(void);
-static void PMU_PMIC_IRQHandler(void);
-static void HASHCRYPT_IRQHandler(void);
-static void FLEXCOMM8_IRQHandler(void);
-static void FLEXCOMM9_IRQHandler(void);
-static void FLEXCOMM10_IRQHandler(void);
-static void FLEXCOMM11_IRQHandler(void);
-static void FLEXCOMM12_IRQHandler(void);
-static void FLEXCOMM13_IRQHandler(void);
-static void FLEXCOMM16_IRQHandler(void);
-static void I3C1_IRQHandler(void);
-static void FLEXIO_IRQHandler(void);
-static void LCDIF_IRQHandler(void);
-static void GPU_IRQHandler(void);
-static void MIPI_IRQHandler(void);
-static void Reserved88_IRQHandler(void);
-static void SDMA_IRQHandler(void);
-
-// This is the device's interrupt vector table. It's not referenced in any
-// code because the platform (EVKMIMXRT595) expects this table to be present
-// at the beginning of flash. The exact address is specified in the pw_boot
-// configuration as part of the target config.
-
-// This typedef is for convenience when building the vector table. With the
-// exception of SP_main (0th entry in the vector table), image length (8th),
-// type (9th), reserved 10th entry, and image load address (13th entry), all
-// the entries of the vector table are function pointers.
-typedef void (*InterruptHandler)(void);
-
-PW_KEEP_IN_SECTION(".vector_table")
-const InterruptHandler vector_table[] = {
-    // Core Level - CM33
-
-    // The starting location of the stack pointer.
-    // This address is NOT an interrupt handler/function pointer, it is simply
-    // the address that the main stack pointer should be initialized to. The
-    // value is reinterpret casted because it needs to be in the vector table.
-    [0] = (InterruptHandler)(&pw_boot_stack_high_addr),
-
-    // Reset handler, dictates how to handle reset interrupt. This is the
-    // address that the Program Counter (PC) is initialized to at boot.
-    [1] = pw_boot_Entry,
-
-    // NMI handler.
-    [2] = DefaultFaultHandler,
-    // HardFault handler.
-    [3] = DefaultFaultHandler,
-    // MemManage (MPU Fault) handler.
-    [4] = DefaultFaultHandler,
-    // BusFault handler.
-    [5] = DefaultFaultHandler,
-    // UsageFault handler.
-    [6] = DefaultFaultHandler,
-    // SecureFault handler.
-    [7] = DefaultFaultHandler,
-    // Image Length.
-    [8] = (InterruptHandler)(&_pw_image_size),
-    // Image Type.
-    [9] = 0,
-    // Reserved.
-    [10] = 0,
-    // SVCall handler.
-    [11] = SVC_Handler,
-    // DebugMon handler.
-    [12] = DebugMon_Handler,
-    // Image Load Address.
-    [13] = (InterruptHandler)(&pw_boot_vector_table_addr),
-    // PendSV handler.
-    [14] = PendSV_Handler,
-    // SysTick handler.
-    [15] = SysTick_Handler,
-
-    // Chip Level - MIMXRT595S_cm33
-
-    // Watchdog timer interrupt.
-    [16] = WDT0_IRQHandler,
-    // DMA interrupt.
-    [17] = DMA0_IRQHandler,
-    // GPIO Interrupt A.
-    [18] = GPIO_INTA_IRQHandler,
-    // GPIO Interrupt B.
-    [19] = GPIO_INTB_IRQHandler,
-    // General Purpose Input/Output interrupt 0.
-    [20] = PIN_INT0_IRQHandler,
-    // General Purpose Input/Output interrupt 1.
-    [21] = PIN_INT1_IRQHandler,
-    // General Purpose Input/Output interrupt 2.
-    [22] = PIN_INT2_IRQHandler,
-    // General Purpose Input/Output interrupt 3.
-    [23] = PIN_INT3_IRQHandler,
-    // Micro-tick Timer.
-    [24] = UTICK0_IRQHandler,
-    // Multi-Rate Timer.
-    [25] = MRT0_IRQHandler,
-    // Standard counter/timer CTIMER0.
-    [26] = CTIMER0_IRQHandler,
-    // Standard counter/timer CTIMER1.
-    [27] = CTIMER1_IRQHandler,
-    // SCTimer/PWM.
-    [28] = SCT0_IRQHandler,
-    // Standard counter/timer CTIMER3.
-    [29] = CTIMER3_IRQHandler,
-    // FlexComm interrupt.
-    [30] = FLEXCOMM0_IRQHandler,
-    // FlexComm interrupt.
-    [31] = FLEXCOMM1_IRQHandler,
-    // FlexComm interrupt.
-    [32] = FLEXCOMM2_IRQHandler,
-    // FlexComm interrupt.
-    [33] = FLEXCOMM3_IRQHandler,
-    // FlexComm interrupt.
-    [34] = FLEXCOMM4_IRQHandler,
-    // FlexComm interrupt.
-    [35] = FLEXCOMM5_IRQHandler,
-    // FlexComm interrupt. Standalone SPI.
-    [36] = FLEXCOMM14_IRQHandler,
-    // FlexComm interrupt. Standalone I2C.
-    [37] = FLEXCOMM15_IRQHandler,
-    // Analog-to-Digital Converter interrupt.
-    [38] = ADC0_IRQHandler,
-    // Reserved interrupt.
-    [39] = Reserved39_IRQHandler,
-    // Analog comparator Interrupts.
-    [40] = ACMP_IRQHandler,
-    // Digital Microphone Interface interrupt.
-    [41] = DMIC0_IRQHandler,
-    // Reserved interrupt.
-    [42] = Reserved42_IRQHandler,
-    // Hypervisor interrupt.
-    [43] = HYPERVISOR_IRQHandler,
-    // Secure violation interrupt.
-    [44] = SECURE_VIOLATION_IRQHandler,
-    // Hardware Voice Activity Detector interrupt.
-    [45] = HWVAD0_IRQHandler,
-    // Reserved interrupt.
-    [46] = Reserved46_IRQHandler,
-    // Random Number Generator interrupt.
-    [47] = RNG_IRQHandler,
-    // Real Time Clock Alarm interrupt OR Wakeup timer interrupt.
-    [48] = RTC_IRQHandler,
-    // DSP interrupt.
-    [49] = DSP_TIE_EXPSTATE1_IRQHandler,
-    // Messaging Unit - Side A.
-    [50] = MU_A_IRQHandler,
-    // General Purpose Input/Output interrupt 4.
-    [51] = PIN_INT4_IRQHandler,
-    // General Purpose Input/Output interrupt 5.
-    [52] = PIN_INT5_IRQHandler,
-    // General Purpose Input/Output interrupt 6.
-    [53] = PIN_INT6_IRQHandler,
-    // General Purpose Input/Output interrupt 7.
-    [54] = PIN_INT7_IRQHandler,
-    // Standard counter/timer CTIMER2.
-    [55] = CTIMER2_IRQHandler,
-    // Standard counter/timer CTIMER4.
-    [56] = CTIMER4_IRQHandler,
-    // Event timer M33 Wakeup/interrupt.
-    [57] = OS_EVENT_IRQHandler,
-    // FlexSPI0_IRQ OR FlexSPI1_IRQ.
-    [58] = FLEXSPI0_FLEXSPI1_IRQHandler,
-    // FlexComm interrupt.
-    [59] = FLEXCOMM6_IRQHandler,
-    // FlexComm interrupt.
-    [60] = FLEXCOMM7_IRQHandler,
-    // USDHC interrupt.
-    [61] = USDHC0_IRQHandler,
-    // USDHC interrupt.
-    [62] = USDHC1_IRQHandler,
-    // Secure GPIO HS interrupt 0.
-    [63] = SGPIO_INTA_IRQHandler,
-    // Secure GPIO HS interrupt 1.
-    [64] = SGPIO_INTB_IRQHandler,
-    // Improved Inter Integrated Circuit 0 interrupt.
-    [65] = I3C0_IRQHandler,
-    // USB device.
-    [66] = USB0_IRQHandler,
-    // USB Activity Wake-up Interrupt.
-    [67] = USB0_NEEDCLK_IRQHandler,
-    // Watchdog timer 1 interrupt.
-    [68] = WDT1_IRQHandler,
-    // USBPHY DCD interrupt.
-    [69] = USB_PHYDCD_IRQHandler,
-    // DMA interrupt.
-    [70] = DMA1_IRQHandler,
-    // QuidKey interrupt.
-    [71] = PUF_IRQHandler,
-    // Powerquad interrupt.
-    [72] = POWERQUAD_IRQHandler,
-    // Caspar interrupt.
-    [73] = CASPER_IRQHandler,
-    // Power Management Control interrupt.
-    [74] = PMU_PMIC_IRQHandler,
-    // SHA interrupt.
-    [75] = HASHCRYPT_IRQHandler,
-    // FlexComm interrupt.
-    [76] = FLEXCOMM8_IRQHandler,
-    // FlexComm interrupt.
-    [77] = FLEXCOMM9_IRQHandler,
-    // FlexComm interrupt.
-    [78] = FLEXCOMM10_IRQHandler,
-    // FlexComm interrupt.
-    [79] = FLEXCOMM11_IRQHandler,
-    // FlexComm interrupt.
-    [80] = FLEXCOMM12_IRQHandler,
-    // FlexComm interrupt.
-    [81] = FLEXCOMM13_IRQHandler,
-    // FlexComm interrupt.
-    [82] = FLEXCOMM16_IRQHandler,
-    // Improved Inter Integrated Circuit 1 interrupt.
-    [83] = I3C1_IRQHandler,
-    // Flexible I/O interrupt.
-    [84] = FLEXIO_IRQHandler,
-    // Liquid Crystal Display interface interrupt.
-    [85] = LCDIF_IRQHandler,
-    // Graphics Processor Unit interrupt.
-    [86] = GPU_IRQHandler,
-    // MIPI interrupt.
-    [87] = MIPI_IRQHandler,
-    // Reserved interrupt.
-    [88] = Reserved88_IRQHandler,
-    // Smart DMA Engine Controller interrupt.
-    [89] = SDMA_IRQHandler,
-};
-
-// Define handlers that call out to a driver handler provided by the SDK.
-#define DRIVER_HANDLER(_IRQHandler, _DriverIRQHandler)            \
-  void _DriverIRQHandler(void) PW_ALIAS(DefaultInterruptHandler); \
-  static void _IRQHandler(void) { _DriverIRQHandler(); }
-
-DRIVER_HANDLER(WDT0_IRQHandler, WDT0_DriverIRQHandler);
-DRIVER_HANDLER(DMA0_IRQHandler, DMA0_DriverIRQHandler);
-DRIVER_HANDLER(GPIO_INTA_IRQHandler, GPIO_INTA_DriverIRQHandler);
-DRIVER_HANDLER(GPIO_INTB_IRQHandler, GPIO_INTB_DriverIRQHandler);
-DRIVER_HANDLER(PIN_INT0_IRQHandler, PIN_INT0_DriverIRQHandler);
-DRIVER_HANDLER(PIN_INT1_IRQHandler, PIN_INT1_DriverIRQHandler);
-DRIVER_HANDLER(PIN_INT2_IRQHandler, PIN_INT2_DriverIRQHandler);
-DRIVER_HANDLER(PIN_INT3_IRQHandler, PIN_INT3_DriverIRQHandler);
-DRIVER_HANDLER(UTICK0_IRQHandler, UTICK0_DriverIRQHandler);
-DRIVER_HANDLER(MRT0_IRQHandler, MRT0_DriverIRQHandler);
-DRIVER_HANDLER(CTIMER0_IRQHandler, CTIMER0_DriverIRQHandler);
-DRIVER_HANDLER(CTIMER1_IRQHandler, CTIMER1_DriverIRQHandler);
-DRIVER_HANDLER(SCT0_IRQHandler, SCT0_DriverIRQHandler);
-DRIVER_HANDLER(CTIMER3_IRQHandler, CTIMER3_DriverIRQHandler);
-DRIVER_HANDLER(FLEXCOMM0_IRQHandler, FLEXCOMM0_DriverIRQHandler);
-DRIVER_HANDLER(FLEXCOMM1_IRQHandler, FLEXCOMM1_DriverIRQHandler);
-DRIVER_HANDLER(FLEXCOMM2_IRQHandler, FLEXCOMM2_DriverIRQHandler);
-DRIVER_HANDLER(FLEXCOMM3_IRQHandler, FLEXCOMM3_DriverIRQHandler);
-DRIVER_HANDLER(FLEXCOMM4_IRQHandler, FLEXCOMM4_DriverIRQHandler);
-DRIVER_HANDLER(FLEXCOMM5_IRQHandler, FLEXCOMM5_DriverIRQHandler);
-DRIVER_HANDLER(FLEXCOMM14_IRQHandler, FLEXCOMM14_DriverIRQHandler);
-DRIVER_HANDLER(FLEXCOMM15_IRQHandler, FLEXCOMM15_DriverIRQHandler);
-DRIVER_HANDLER(ADC0_IRQHandler, ADC0_DriverIRQHandler);
-DRIVER_HANDLER(Reserved39_IRQHandler, Reserved39_DriverIRQHandler);
-DRIVER_HANDLER(ACMP_IRQHandler, ACMP_DriverIRQHandler);
-DRIVER_HANDLER(DMIC0_IRQHandler, DMIC0_DriverIRQHandler);
-DRIVER_HANDLER(Reserved42_IRQHandler, Reserved42_DriverIRQHandler);
-DRIVER_HANDLER(HYPERVISOR_IRQHandler, HYPERVISOR_DriverIRQHandler);
-DRIVER_HANDLER(SECURE_VIOLATION_IRQHandler, SECURE_VIOLATION_DriverIRQHandler);
-DRIVER_HANDLER(HWVAD0_IRQHandler, HWVAD0_DriverIRQHandler);
-DRIVER_HANDLER(Reserved46_IRQHandler, Reserved46_DriverIRQHandler);
-DRIVER_HANDLER(RNG_IRQHandler, RNG_DriverIRQHandler);
-DRIVER_HANDLER(RTC_IRQHandler, RTC_DriverIRQHandler);
-DRIVER_HANDLER(DSP_TIE_EXPSTATE1_IRQHandler,
-               DSP_TIE_EXPSTATE1_DriverIRQHandler);
-DRIVER_HANDLER(MU_A_IRQHandler, MU_A_DriverIRQHandler);
-DRIVER_HANDLER(PIN_INT4_IRQHandler, PIN_INT4_DriverIRQHandler);
-DRIVER_HANDLER(PIN_INT5_IRQHandler, PIN_INT5_DriverIRQHandler);
-DRIVER_HANDLER(PIN_INT6_IRQHandler, PIN_INT6_DriverIRQHandler);
-DRIVER_HANDLER(PIN_INT7_IRQHandler, PIN_INT7_DriverIRQHandler);
-DRIVER_HANDLER(CTIMER2_IRQHandler, CTIMER2_DriverIRQHandler);
-DRIVER_HANDLER(CTIMER4_IRQHandler, CTIMER4_DriverIRQHandler);
-DRIVER_HANDLER(OS_EVENT_IRQHandler, OS_EVENT_DriverIRQHandler);
-DRIVER_HANDLER(FLEXSPI0_FLEXSPI1_IRQHandler,
-               FLEXSPI0_FLEXSPI1_DriverIRQHandler);
-DRIVER_HANDLER(FLEXCOMM6_IRQHandler, FLEXCOMM6_DriverIRQHandler);
-DRIVER_HANDLER(FLEXCOMM7_IRQHandler, FLEXCOMM7_DriverIRQHandler);
-DRIVER_HANDLER(USDHC0_IRQHandler, USDHC0_DriverIRQHandler);
-DRIVER_HANDLER(USDHC1_IRQHandler, USDHC1_DriverIRQHandler);
-DRIVER_HANDLER(SGPIO_INTA_IRQHandler, SGPIO_INTA_DriverIRQHandler);
-DRIVER_HANDLER(SGPIO_INTB_IRQHandler, SGPIO_INTB_DriverIRQHandler);
-DRIVER_HANDLER(I3C0_IRQHandler, I3C0_DriverIRQHandler);
-DRIVER_HANDLER(USB0_IRQHandler, USB0_DriverIRQHandler);
-DRIVER_HANDLER(USB0_NEEDCLK_IRQHandler, USB0_NEEDCLK_DriverIRQHandler);
-DRIVER_HANDLER(WDT1_IRQHandler, WDT1_DriverIRQHandler);
-DRIVER_HANDLER(USB_PHYDCD_IRQHandler, USB_PHYDCD_DriverIRQHandler);
-DRIVER_HANDLER(DMA1_IRQHandler, DMA1_DriverIRQHandler);
-DRIVER_HANDLER(PUF_IRQHandler, PUF_DriverIRQHandler);
-DRIVER_HANDLER(POWERQUAD_IRQHandler, POWERQUAD_DriverIRQHandler);
-DRIVER_HANDLER(CASPER_IRQHandler, CASPER_DriverIRQHandler);
-DRIVER_HANDLER(PMU_PMIC_IRQHandler, PMU_PMIC_DriverIRQHandler);
-DRIVER_HANDLER(HASHCRYPT_IRQHandler, HASHCRYPT_DriverIRQHandler);
-DRIVER_HANDLER(FLEXCOMM8_IRQHandler, FLEXCOMM8_DriverIRQHandler);
-DRIVER_HANDLER(FLEXCOMM9_IRQHandler, FLEXCOMM9_DriverIRQHandler);
-DRIVER_HANDLER(FLEXCOMM10_IRQHandler, FLEXCOMM10_DriverIRQHandler);
-DRIVER_HANDLER(FLEXCOMM11_IRQHandler, FLEXCOMM11_DriverIRQHandler);
-DRIVER_HANDLER(FLEXCOMM12_IRQHandler, FLEXCOMM12_DriverIRQHandler);
-DRIVER_HANDLER(FLEXCOMM13_IRQHandler, FLEXCOMM13_DriverIRQHandler);
-DRIVER_HANDLER(FLEXCOMM16_IRQHandler, FLEXCOMM16_DriverIRQHandler);
-DRIVER_HANDLER(I3C1_IRQHandler, I3C1_DriverIRQHandler);
-DRIVER_HANDLER(FLEXIO_IRQHandler, FLEXIO_DriverIRQHandler);
-DRIVER_HANDLER(LCDIF_IRQHandler, LCDIF_DriverIRQHandler);
-DRIVER_HANDLER(GPU_IRQHandler, GPU_DriverIRQHandler);
-DRIVER_HANDLER(MIPI_IRQHandler, MIPI_DriverIRQHandler);
-DRIVER_HANDLER(Reserved88_IRQHandler, Reserved88_DriverIRQHandler);
-DRIVER_HANDLER(SDMA_IRQHandler, SDMA_DriverIRQHandler);
diff --git a/targets/rp2040/BUILD.bazel b/targets/rp2040/BUILD.bazel
deleted file mode 100644
index 76bd682..0000000
--- a/targets/rp2040/BUILD.bazel
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-# This is just a stub to silence warnings saying that pico_logging_test_main.cc
-# is missing from the bazel build. There's no plans yet to do a Bazel build for
-# the Pi Pico.
-pw_cc_library(
-    name = "pico_logging_test_main",
-    srcs = [
-        "pico_logging_test_main.cc",
-    ],
-    deps = [
-        "//pw_unit_test",
-        "//pw_unit_test:logging_event_handler",
-    ],
-)
diff --git a/targets/rp2040/BUILD.gn b/targets/rp2040/BUILD.gn
deleted file mode 100644
index 9f0bbdb..0000000
--- a/targets/rp2040/BUILD.gn
+++ /dev/null
@@ -1,79 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_toolchain/arm_gcc/toolchains.gni")
-import("$dir_pw_toolchain/generate_toolchain.gni")
-
-if (current_toolchain != default_toolchain) {
-  pw_source_set("pico_logging_test_main") {
-    # Required because the pico SDK can't properly propagate -Wno-undef and
-    # -Wno-unused-function because of Pigweed's very unusual default_configs
-    # behavior.
-    remove_configs = [ "$dir_pw_build:strict_warnings" ]
-    deps = [
-      "$PICO_ROOT/src/common/pico_base",
-      "$PICO_ROOT/src/common/pico_stdlib",
-      "$dir_pw_unit_test:logging_event_handler",
-      "$dir_pw_unit_test:pw_unit_test",
-    ]
-    sources = [ "pico_logging_test_main.cc" ]
-  }
-}
-
-generate_toolchain("rp2040") {
-  _excluded_members = [
-    "defaults",
-    "name",
-  ]
-  _toolchain_base = pw_toolchain_arm_gcc.cortex_m0plus_size_optimized
-  forward_variables_from(_toolchain_base, "*", _excluded_members)
-  final_binary_extension = ".elf"
-
-  # For now, no Pigweed configurations set up.
-  defaults = {
-    forward_variables_from(_toolchain_base.defaults, "*")
-
-    pw_build_EXECUTABLE_TARGET_TYPE = "pico_executable"
-    pw_build_EXECUTABLE_TARGET_TYPE_FILE =
-        get_path_info("pico_executable.gni", "abspath")
-    pw_unit_test_MAIN = "$dir_pigweed/targets/rp2040:pico_logging_test_main"
-    pw_assert_BACKEND = dir_pw_assert_basic
-    pw_log_BACKEND = dir_pw_log_basic
-    pw_sys_io_BACKEND = "$dir_pw_sys_io_stdio"
-
-    pw_sync_INTERRUPT_SPIN_LOCK_BACKEND =
-        "$dir_pw_sync_baremetal:interrupt_spin_lock"
-    pw_sync_MUTEX_BACKEND = "$dir_pw_sync_baremetal:mutex"
-
-    # Silence GN variable overwrite warning.
-    pw_build_LINK_DEPS = []
-
-    pw_build_LINK_DEPS = [
-      "$dir_pw_assert:impl",
-      "$dir_pw_log:impl",
-    ]
-
-    current_cpu = "arm"
-    current_os = ""
-  }
-}
-
-pw_doc_group("target_docs") {
-  sources = [ "target_docs.rst" ]
-}
diff --git a/targets/rp2040/pico_executable.gni b/targets/rp2040/pico_executable.gni
deleted file mode 100644
index f8afcf1..0000000
--- a/targets/rp2040/pico_executable.gni
+++ /dev/null
@@ -1,25 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-# Executable wrapper that allows the 2nd stage bootloader to strip link deps.
-template("pico_executable") {
-  target("executable", target_name) {
-    forward_variables_from(invoker, "*")
-    if (defined(no_link_deps) && no_link_deps) {
-      public_deps -= [ "$dir_pw_build:link_deps" ]
-    }
-  }
-}
diff --git a/targets/rp2040/pico_logging_test_main.cc b/targets/rp2040/pico_logging_test_main.cc
deleted file mode 100644
index 1c5c92c..0000000
--- a/targets/rp2040/pico_logging_test_main.cc
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pico/stdlib.h"
-#include "pw_unit_test/framework.h"
-#include "pw_unit_test/logging_event_handler.h"
-
-int main() {
-  setup_default_uart();
-  pw::unit_test::LoggingEventHandler handler;
-  pw::unit_test::RegisterEventHandler(&handler);
-  return RUN_ALL_TESTS();
-}
diff --git a/targets/rp2040/target_docs.rst b/targets/rp2040/target_docs.rst
deleted file mode 100644
index 0546f72..0000000
--- a/targets/rp2040/target_docs.rst
+++ /dev/null
@@ -1,63 +0,0 @@
-.. _target-raspberry-pi-pico:
-
------------------
-Raspberry Pi Pico
------------------
-.. warning::
-  This target is in an early state and is under active development. Usability
-  is not very polished, and many features/configuration options that work in
-  upstream Pi Pico CMake build have not yet been ported to the GN build.
-
------
-Setup
------
-To use this target, Pigweed must be set up to build against the Raspberry Pi
-Pico SDK. This can be downloaded via ``pw package``, and then the build must be
-manually configured to point to the location of the downloaded SDK.
-
-.. code:: sh
-
-  pw package install pico_sdk
-
-  gn args out
-    # Add these lines, replacing ${PW_ROOT} with the path to the location that
-    # Pigweed is checked out at.
-    PICO_SRC_DIR = "${PW_ROOT}/.environment/packages/pico_sdk"
-
------
-Usage
------
-The Pi Pico is currently configured to output logs and test results over UART
-via GPIO 1 and 2 (TX and RX, respectively) at a baud rate of 115200. Because
-of this, you'll need a USB TTL adapter to communicate with the Pi Pico.
-
-Once the pico SDK is configured, the Pi Pico will build as part of the default
-GN build:
-
-.. code:: sh
-
-  ninja -C out
-
-Pigweed's build will produce ELF files for each unit test built for the Pi Pico.
-While ELF files can be flashed to a Pi Pico via SWD, it's slightly easier to
-use the Pi Pico's bootloader to flash the firmware as a UF2 file.
-
-Pigweed currently does not yet build/provide the elf2uf2 utility used to convert
-ELF files to UF2 files. This tool can be built from within the Pi Pico SDK with
-the following command:
-
-.. code:: sh
-
-  mkdir build && cd build && cmake -G Ninja ../ && ninja
-  # Copy the tool so it's visible in your PATH.
-  cp elf2uf2/elf2uf2 $HOME/bin/elf2uf2
-
-Flashing
-========
-Flashing the Pi Pico is as easy as 1-2-3:
-
-#. Create a UF2 file from an ELF file using ``elf2uf2``.
-#. While holding the button on the Pi Pico, connect the Pico to your computer
-   via the micro USB port.
-#. Copy the UF2 to the RPI-RP2 volume that enumerated when you connected the
-   Pico.
diff --git a/targets/stm32f429i-disc1/BUILD b/targets/stm32f429i-disc1/BUILD
new file mode 100644
index 0000000..b9cffcc
--- /dev/null
+++ b/targets/stm32f429i-disc1/BUILD
@@ -0,0 +1,45 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "pre_init",
+    srcs = [
+        "boot.cc",
+        "vector_table.c"
+    ],
+    deps = [
+        "//pw_boot_armv7m",
+        "//pw_malloc",
+        "//pw_preprocessor",
+        "//pw_sys_io_baremetal_stm32f429",
+    ],
+)
+
+pw_cc_library(
+    name = "system_rpc_server",
+    srcs = ["system_rpc_server.cc"],
+    deps = [
+        "//pw_rpc/system_server:facade",
+        "//pw_hdlc:pw_rpc",
+    ],
+)
diff --git a/targets/stm32f429i-disc1/BUILD.gn b/targets/stm32f429i-disc1/BUILD.gn
new file mode 100644
index 0000000..5527e65
--- /dev/null
+++ b/targets/stm32f429i-disc1/BUILD.gn
@@ -0,0 +1,64 @@
+# Copyright 2019 The Pigweed Authors
+#
+# 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
+#
+#     https://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("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/target_types.gni")
+import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_malloc/backend.gni")
+import("$dir_pw_toolchain/generate_toolchain.gni")
+import("target_toolchains.gni")
+
+generate_toolchains("target_toolchains") {
+  toolchains = pw_target_toolchain_stm32f429i_disc1_list
+}
+
+config("pw_malloc_active") {
+  if (pw_malloc_BACKEND != "") {
+    defines = [ "PW_MALLOC_ACTIVE=1" ]
+  }
+}
+
+if (current_toolchain != default_toolchain) {
+  pw_source_set("pre_init") {
+    configs = [ ":pw_malloc_active" ]
+    public_deps = [
+      "$dir_pw_boot_armv7m",
+      "$dir_pw_sys_io_baremetal_stm32f429",
+    ]
+    deps = [
+      "$dir_pw_malloc",
+      "$dir_pw_preprocessor",
+    ]
+    sources = [
+      "boot.cc",
+      "vector_table.c",
+    ]
+  }
+
+  pw_source_set("system_rpc_server") {
+    deps = [
+      "$dir_pw_hdlc:pw_rpc",
+      "$dir_pw_hdlc:rpc_channel_output",
+      "$dir_pw_rpc/system_server:facade",
+      "$dir_pw_stream:sys_io_stream",
+      dir_pw_log,
+    ]
+    sources = [ "system_rpc_server.cc" ]
+  }
+}
+
+pw_doc_group("target_docs") {
+  sources = [ "target_docs.rst" ]
+}
diff --git a/targets/stm32f429i-disc1/boot.cc b/targets/stm32f429i-disc1/boot.cc
new file mode 100644
index 0000000..a0d0fc2
--- /dev/null
+++ b/targets/stm32f429i-disc1/boot.cc
@@ -0,0 +1,67 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 "pw_boot_armv7m/boot.h"
+
+#include "pw_malloc/malloc.h"
+#include "pw_preprocessor/compiler.h"
+#include "pw_sys_io_baremetal_stm32f429/init.h"
+
+// Note that constexpr is used inside of this function instead of using a static
+// constexpr or declaring it outside of this function in an anonymous namespace,
+// because constexpr makes it available for the compiler to evaluate during
+// copmile time but does NOT require it to be evaluated at compile team and we
+// have to be incredibly careful that this does not end up in the .data section.
+void pw_boot_PreStaticMemoryInit() {
+  // TODO(pwbug/17): Optionally enable Replace when Pigweed config system is
+  // added.
+#if PW_ARMV7M_ENABLE_FPU
+  // Enable FPU if built using hardware FPU instructions.
+  // CPCAR mask that enables FPU. (ARMv7-M Section B3.2.20)
+  constexpr uint32_t kFpuEnableMask = (0xFu << 20);
+
+  // Memory mapped register to enable FPU. (ARMv7-M Section B3.2.2, Table B3-4)
+  volatile uint32_t& arm_v7m_cpacr =
+      *reinterpret_cast<volatile uint32_t*>(0xE000ED88u);
+  arm_v7m_cpacr |= kFpuEnableMask;
+
+  // Ensure the FPU configuration is committed and enabled before continuing and
+  // potentially executing any FPU instructions, however rare that may be during
+  // startup.
+  asm volatile(
+      " dsb \n"
+      " isb \n"
+      // clang-format off
+      : /*output=*/
+      : /*input=*/
+      : /*clobbers=*/"memory"
+      // clang-format on
+  );
+#endif  // PW_ARMV7M_ENABLE_FPU
+}
+
+void pw_boot_PreStaticConstructorInit() {
+#if PW_MALLOC_ACTIVE
+  pw_MallocInit();
+#endif  // PW_MALLOC_ACTIVE
+}
+
+void pw_boot_PreMainInit() { pw_sys_io_Init(); }
+
+PW_NO_RETURN void pw_boot_PostMain() {
+  // In case main() returns, just sit here until the device is reset.
+  while (true) {
+  }
+  PW_UNREACHABLE;
+}
diff --git a/targets/stm32f429i-disc1/py/BUILD.gn b/targets/stm32f429i-disc1/py/BUILD.gn
new file mode 100644
index 0000000..85dd255
--- /dev/null
+++ b/targets/stm32f429i-disc1/py/BUILD.gn
@@ -0,0 +1,30 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/python.gni")
+
+pw_python_package("py") {
+  setup = [ "setup.py" ]
+  sources = [
+    "stm32f429i_disc1_utils/__init__.py",
+    "stm32f429i_disc1_utils/stm32f429i_detector.py",
+    "stm32f429i_disc1_utils/unit_test_client.py",
+    "stm32f429i_disc1_utils/unit_test_runner.py",
+    "stm32f429i_disc1_utils/unit_test_server.py",
+  ]
+  pylintrc = "$dir_pigweed/.pylintrc"
+  python_deps = [ "$dir_pw_cli/py" ]
+}
diff --git a/targets/stm32f429i-disc1/py/setup.py b/targets/stm32f429i-disc1/py/setup.py
new file mode 100644
index 0000000..f8ab0a2
--- /dev/null
+++ b/targets/stm32f429i-disc1/py/setup.py
@@ -0,0 +1,45 @@
+# Copyright 2019 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+"""stm32f429i_disc1_utils"""
+
+import setuptools  # type: ignore
+
+setuptools.setup(
+    name='stm32f429i_disc1_utils',
+    version='0.0.1',
+    author='Pigweed Authors',
+    author_email='pigweed-developers@googlegroups.com',
+    description=
+    'Target-specific python scripts for the stm32f429i-disc1 target',
+    packages=setuptools.find_packages(),
+    package_data={'stm32f429i_disc1_utils': ['py.typed']},
+    zip_safe=False,
+    entry_points={
+        'console_scripts': [
+            'stm32f429i_disc1_unit_test_runner = '
+            '    stm32f429i_disc1_utils.unit_test_runner:main',
+            'stm32f429i_disc1_detector = '
+            '    stm32f429i_disc1_utils.stm32f429i_detector:main',
+            'stm32f429i_disc1_test_server = '
+            '    stm32f429i_disc1_utils.unit_test_server:main',
+            'stm32f429i_disc1_test_client = '
+            '    stm32f429i_disc1_utils.unit_test_client:main',
+        ]
+    },
+    install_requires=[
+        'pyserial>=3.5,<4.0',
+        'coloredlogs',
+        'pw_cli',
+    ],
+)
diff --git a/targets/stm32f429i_disc1/py/stm32f429i_disc1_utils/__init__.py b/targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/__init__.py
similarity index 100%
rename from targets/stm32f429i_disc1/py/stm32f429i_disc1_utils/__init__.py
rename to targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/__init__.py
diff --git a/targets/stm32f429i_disc1/py/stm32f429i_disc1_utils/openocd_stm32f4xx.cfg b/targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/openocd_stm32f4xx.cfg
similarity index 100%
rename from targets/stm32f429i_disc1/py/stm32f429i_disc1_utils/openocd_stm32f4xx.cfg
rename to targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/openocd_stm32f4xx.cfg
diff --git a/targets/stm32f429i_disc1/py/stm32f429i_disc1_utils/py.typed b/targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/py.typed
similarity index 100%
rename from targets/stm32f429i_disc1/py/stm32f429i_disc1_utils/py.typed
rename to targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/py.typed
diff --git a/targets/stm32f429i_disc1/py/stm32f429i_disc1_utils/stm32f429i_detector.py b/targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/stm32f429i_detector.py
similarity index 100%
rename from targets/stm32f429i_disc1/py/stm32f429i_disc1_utils/stm32f429i_detector.py
rename to targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/stm32f429i_detector.py
diff --git a/targets/stm32f429i_disc1/py/stm32f429i_disc1_utils/unit_test_client.py b/targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/unit_test_client.py
similarity index 100%
rename from targets/stm32f429i_disc1/py/stm32f429i_disc1_utils/unit_test_client.py
rename to targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/unit_test_client.py
diff --git a/targets/stm32f429i_disc1/py/stm32f429i_disc1_utils/unit_test_runner.py b/targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/unit_test_runner.py
similarity index 100%
rename from targets/stm32f429i_disc1/py/stm32f429i_disc1_utils/unit_test_runner.py
rename to targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/unit_test_runner.py
diff --git a/targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/unit_test_server.py b/targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/unit_test_server.py
new file mode 100644
index 0000000..fec6ee2
--- /dev/null
+++ b/targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/unit_test_server.py
@@ -0,0 +1,116 @@
+#!/usr/bin/env python3
+# Copyright 2019 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+"""Launch a pw_test_server server to use for multi-device testing."""
+
+import argparse
+import logging
+import sys
+import tempfile
+from typing import IO, List, Optional
+
+import pw_cli.process
+import pw_cli.log
+
+from stm32f429i_disc1_utils import stm32f429i_detector
+
+_LOG = logging.getLogger('unit_test_server')
+
+_TEST_RUNNER_COMMAND = 'stm32f429i_disc1_unit_test_runner'
+
+_TEST_SERVER_COMMAND = 'pw_target_runner_server'
+
+
+def parse_args():
+    """Parses command-line arguments."""
+
+    parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument('--server-port',
+                        type=int,
+                        default=8080,
+                        help='Port to launch the pw_target_runner_server on')
+    parser.add_argument('--server-config',
+                        type=argparse.FileType('r'),
+                        help='Path to server config file')
+    parser.add_argument('--verbose',
+                        '-v',
+                        dest='verbose',
+                        action="store_true",
+                        help='Output additional logs as the script runs')
+
+    return parser.parse_args()
+
+
+def generate_runner(command: str, arguments: List[str]) -> str:
+    """Generates a text-proto style pw_target_runner_server configuration."""
+    # TODO(amontanez): Use a real proto library to generate this when we have
+    # one set up.
+    for i, arg in enumerate(arguments):
+        arguments[i] = f'  args: "{arg}"'
+    runner = ['runner {', f'  command:"{command}"']
+    runner.extend(arguments)
+    runner.append('}\n')
+    return '\n'.join(runner)
+
+
+def generate_server_config() -> IO[bytes]:
+    """Returns a temporary generated file for use as the server config."""
+    boards = stm32f429i_detector.detect_boards()
+    if not boards:
+        _LOG.critical('No attached boards detected')
+        sys.exit(1)
+    config_file = tempfile.NamedTemporaryFile()
+    _LOG.debug('Generating test server config at %s', config_file.name)
+    _LOG.debug('Found %d attached devices', len(boards))
+    for board in boards:
+        test_runner_args = [
+            '--stlink-serial', board.serial_number, '--port', board.dev_name
+        ]
+        config_file.write(
+            generate_runner(_TEST_RUNNER_COMMAND,
+                            test_runner_args).encode('utf-8'))
+    config_file.flush()
+    return config_file
+
+
+def launch_server(server_config: Optional[IO[bytes]],
+                  server_port: Optional[int]) -> int:
+    """Launch a device test server with the provided arguments."""
+    if server_config is None:
+        # Auto-detect attached boards if no config is provided.
+        server_config = generate_server_config()
+
+    cmd = [_TEST_SERVER_COMMAND, '-config', server_config.name]
+
+    if server_port is not None:
+        cmd.extend(['-port', str(server_port)])
+
+    return pw_cli.process.run(*cmd, log_output=True).returncode
+
+
+def main():
+    """Launch a device test server with the provided arguments."""
+    args = parse_args()
+
+    # Try to use pw_cli logs, else default to something reasonable.
+    pw_cli.log.install()
+    if args.verbose:
+        _LOG.setLevel(logging.DEBUG)
+
+    exit_code = launch_server(args.server_config, args.server_port)
+    sys.exit(exit_code)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/targets/stm32f429i-disc1/stm32f429i_executable.gni b/targets/stm32f429i-disc1/stm32f429i_executable.gni
new file mode 100644
index 0000000..99190cc
--- /dev/null
+++ b/targets/stm32f429i-disc1/stm32f429i_executable.gni
@@ -0,0 +1,33 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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("//build_overrides/pigweed.gni")
+import("$dir_pw_malloc/backend.gni")
+
+# Executable wrapper that includes some baremetal startup code.
+template("stm32f429i_executable") {
+  target("executable", target_name) {
+    forward_variables_from(invoker, "*")
+    if (!defined(deps)) {
+      deps = []
+    }
+    deps += [ "$dir_pigweed/targets/stm32f429i-disc1:pre_init" ]
+    if (pw_malloc_BACKEND != "") {
+      if (!defined(configs)) {
+        configs = []
+      }
+      configs += [ "$dir_pw_malloc:pw_malloc_wrapper_config" ]
+    }
+  }
+}
diff --git a/targets/stm32f429i-disc1/system_rpc_server.cc b/targets/stm32f429i-disc1/system_rpc_server.cc
new file mode 100644
index 0000000..00b9bf9
--- /dev/null
+++ b/targets/stm32f429i-disc1/system_rpc_server.cc
@@ -0,0 +1,70 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 <cstddef>
+
+#include "pw_hdlc/rpc_channel.h"
+#include "pw_hdlc/rpc_packets.h"
+#include "pw_log/log.h"
+#include "pw_rpc_system_server/rpc_server.h"
+#include "pw_stream/sys_io_stream.h"
+
+namespace pw::rpc::system_server {
+namespace {
+
+constexpr size_t kMaxTransmissionUnit = 256;
+
+// Used to write HDLC data to pw::sys_io.
+stream::SysIoWriter writer;
+stream::SysIoReader reader;
+
+// Set up the output channel for the pw_rpc server to use.
+hdlc::RpcChannelOutputBuffer<kMaxTransmissionUnit> hdlc_channel_output(
+    writer, pw::hdlc::kDefaultRpcAddress, "HDLC channel");
+Channel channels[] = {pw::rpc::Channel::Create<1>(&hdlc_channel_output)};
+rpc::Server server(channels);
+
+}  // namespace
+
+void Init() {
+  // Send log messages to HDLC address 1. This prevents logs from interfering
+  // with pw_rpc communications.
+  pw::log_basic::SetOutput([](std::string_view log) {
+    pw::hdlc::WriteUIFrame(1, std::as_bytes(std::span(log)), writer);
+  });
+}
+
+rpc::Server& Server() { return server; }
+
+Status Start() {
+  // Declare a buffer for decoding incoming HDLC frames.
+  std::array<std::byte, kMaxTransmissionUnit> input_buffer;
+  hdlc::Decoder decoder(input_buffer);
+
+  while (true) {
+    std::byte byte;
+    Status ret_val = pw::sys_io::ReadByte(&byte);
+    if (!ret_val.ok()) {
+      return ret_val;
+    }
+    if (auto result = decoder.Process(byte); result.ok()) {
+      hdlc::Frame& frame = result.value();
+      if (frame.address() == hdlc::kDefaultRpcAddress) {
+        server.ProcessPacket(frame.data(), hdlc_channel_output);
+      }
+    }
+  }
+}
+
+}  // namespace pw::rpc::system_server
diff --git a/targets/stm32f429i-disc1/target_docs.rst b/targets/stm32f429i-disc1/target_docs.rst
new file mode 100644
index 0000000..2e4fcbd
--- /dev/null
+++ b/targets/stm32f429i-disc1/target_docs.rst
@@ -0,0 +1,246 @@
+.. _target-stm32f429i-disc1:
+
+----------------
+stm32f429i-disc1
+----------------
+The STMicroelectronics STM32F429I-DISC1 development board is currently Pigweed's
+primary target for on-device testing and development.
+
+Building
+========
+To build for this Pigweed target, simply build the top-level "stm32f429i" Ninja
+target.
+
+.. code:: sh
+
+  $ ninja -C out stm32f429i
+
+Testing
+=======
+When working in upstream Pigweed, building this target will build all Pigweed modules' unit tests.
+These tests can be run on-device in a few different ways.
+
+Run a unit test
+---------------
+If using ``out`` as a build directory, tests will be located in
+``out/stm32f429i_disc1_debug/obj/[module name]/[test_name].elf``. To run these
+on device, the stm32f429i-disc1 target provides a helper script that flashes the
+test to a device and then runs it.
+
+.. code:: sh
+
+  # Setup pigweed environment.
+  $ source activate.sh
+  # Run test.
+  $ stm32f429i_disc1_unit_test_runner /path/to/binary
+
+Run multiple tests
+------------------
+Running all tests one-by-one is rather tedious. To make running multiple
+tests easier, use Pigweed's ``pw test`` command and pass it a path to the build
+directory and the name of the test runner. By default, ``pw test`` will run all
+tests, but it can be restricted it to specific ``pw_test_group`` targets using
+the ``--group`` argument. Alternatively, individual test binaries can be
+specified with the ``--test`` option.
+
+.. code:: sh
+
+  # Setup Pigweed environment.
+  $ source activate.sh
+  # Run test.
+  $ pw test --root out/stm32f429i_disc_debug/  \
+        --runner stm32f429i_disc1_unit_test_runner
+
+Run tests affected by code changes
+----------------------------------
+When writing code that will impact multiple modules, it's helpful to only run
+all tests that are affected by a given code change. Thanks to the GN/Ninja
+build, this is possible! This is done by using a ``pw_target_runner_server``
+that Ninja can send the tests to as it rebuilds affected targets.
+
+Additionally, this method enables distributed testing. If multiple devices are
+connected, the tests will be run across all attached devices to further speed up
+testing.
+
+Step 1: Start test server
+^^^^^^^^^^^^^^^^^^^^^^^^^
+To allow Ninja to properly serialize tests to run on an arbitrary number of
+devices, Ninja will send test requests to a server running in the background.
+The first step is to launch this server. By default, the script will attempt
+to automatically detect all attached STM32f429I-DISC1 boards and use them for
+testing. To override this behavior, provide a custom server configuration file
+with ``--server-config``.
+
+.. tip::
+
+  If you unplug or plug in any boards, you'll need to restart the test server
+  for hardware changes to properly be detected.
+
+.. code:: sh
+
+  $ stm32f429i_disc1_test_server
+
+Step 2: Configure GN
+^^^^^^^^^^^^^^^^^^^^
+By default, this hardware target has incremental testing via
+``pw_target_runner`` disabled. Enabling the ``pw_use_test_server`` build arg
+tells GN to send requests to a running ``stm32f429i_disc1_test_server``.
+
+.. code:: sh
+
+  $ gn args out
+  # Modify and save the args file to use pw_target_runner.
+  pw_use_test_server = true
+
+Step 3: Build changes
+^^^^^^^^^^^^^^^^^^^^^
+Whenever you run ``ninja -C out stm32f429i``, affected tests will be built and
+run on the attached device(s). Alternatively, you may use ``pw watch`` to set up
+Pigweed to build/test whenever it sees changes to source files.
+
+RPC server
+==========
+The stm32f429i target implements a system RPC server that over a simple UART
+driver. To communicate with a device running the RPC server, run
+``pw rpc -d <device> -b 115200 <protos>``.
+
+Debugging
+=========
+There are multiple ways to debug the device, including using commercial tools
+like SEGGER's J-Link. However, the Discovery board has an on-board STLink
+debugger, which is supported by the open source OpenOCD debugger. To debug with
+OpenOCD requires a few steps. Summary version of the steps:
+
+#. Connect OpenOCD to the device in terminal A. Leave this running
+
+   .. code:: sh
+
+     $ openocd -f targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/openocd_stm32f4xx.cfg
+
+#. Connect GDB to the running OpenOCD instance in terminal B
+
+   .. code:: sh
+
+     $ arm-none-eabi-gdb -ex "target remote :3333" \
+       out/stm32f429i_disc1_debug/obj/pw_assert/test/assert_facade_test.elf
+
+#. Flash (``load``), run (``mon reset init; continue``), and debug
+
+   .. code:: none
+
+     (gdb) set print pretty on
+     (gdb) load
+     (gdb) mon reset init
+     (gdb) continue
+
+#. You can re-flash the device after compiling by running ``load``.
+
+
+Step 1: Start an OpenOCD server and connect to the device
+---------------------------------------------------------
+OpenOCD is a persistent server that you run and leave running to bridge between
+GDB and the device. To run it for the Discovery board:
+
+.. code:: sh
+
+  $ openocd -f targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/openocd_stm32f4xx.cfg
+
+Typical output:
+
+.. code:: none
+
+  Open On-Chip Debugger 0.10.0+dev-01243-ge41c0f49-dirty (2020-05-21-10:27)
+  Licensed under GNU GPL v2
+  For bug reports, read
+          http://openocd.org/doc/doxygen/bugs.html
+  DEPRECATED! use 'adapter driver' not 'interface'
+  Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
+  srst_only separate srst_nogate srst_open_drain connect_deassert_srst
+
+  Info : Listening on port 6666 for tcl connections
+  Info : Listening on port 4444 for telnet connections
+  Info : clock speed 2000 kHz
+  Info : STLINK V2J25M14 (API v2) VID:PID 0483:374B
+  Info : Target voltage: 2.871879
+  Info : stm32f4x.cpu: hardware has 6 breakpoints, 4 watchpoints
+  Info : starting gdb server for stm32f4x.cpu on 3333
+  Info : Listening on port 3333 for gdb connections
+
+Step 2: Start GDB and connect to the OpenOCD server
+---------------------------------------------------
+Start GDB pointing to the correct .elf file, and tell it to connect to the
+OpenOCD server (running on port 333 by default).
+
+.. code:: sh
+
+  $ arm-none-eabi-gdb -ex "target remote :3333" \
+    out/stm32f429i_disc1_debug/obj/pw_assert/test/assert_facade_test.elf
+
+In this case the assert facade test is debugged, but substitute your own ELF
+file. This should produce output similar to the following:
+
+.. code:: none
+
+  GNU gdb (GNU Arm Embedded Toolchain 9-2020-q2-update) 8.3.1.20191211-git
+  Copyright (C) 2019 Free Software Foundation, Inc.
+  License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
+  This is free software: you are free to change and redistribute it.
+  There is NO WARRANTY, to the extent permitted by law.
+  Type "show copying" and "show warranty" for details.
+  This GDB was configured as "--host=x86_64-apple-darwin10 --target=arm-none-eabi".
+  Type "show configuration" for configuration details.
+  For bug reporting instructions, please see:
+  <http://www.gnu.org/software/gdb/bugs/>.
+  Find the GDB manual and other documentation resources online at:
+      <http://www.gnu.org/software/gdb/documentation/>.
+
+  For help, type "help".
+  Type "apropos word" to search for commands related to "word"...
+  Reading symbols from out/stm32f429i_disc1_debug/obj/pw_assert//test/assert_facade_test.elf...
+  Remote debugging using :3333
+  pw_BootEntry () at ../pw_boot_armv7m/core_init.c:117
+  117	  }
+
+Step 3: Flash, run, and debug
+-----------------------------
+Now that the GDB instance is connected to the device, you can flash, run, and debug.
+
+To flash
+
+.. code:: none
+
+  (gdb) load
+
+This will produce output similar to:
+
+.. code:: none
+
+  (gdb) load
+  Loading section .vector_table, size 0x10 lma 0x8000000
+  Loading section .code, size 0xdb8c lma 0x8000200
+  Loading section .ARM, size 0x8 lma 0x800dd90
+  Loading section .static_init_ram, size 0x1d0 lma 0x800dd98
+  Start address 0x8007c48, load size 56692
+  Transfer rate: 25 KB/sec, 8098 bytes/write.
+
+To reset the device and halt on the first instruction (before main):
+
+.. code:: none
+
+  (gdb) mon reset init
+
+
+This will produce output similar to:
+
+.. code:: none
+
+  (gdb) mon reset init
+  Unable to match requested speed 2000 kHz, using 1800 kHz
+  Unable to match requested speed 2000 kHz, using 1800 kHz
+  target halted due to debug-request, current mode: Thread
+  xPSR: 0x01000000 pc: 0x08007930 msp: 0x20030000
+  Unable to match requested speed 8000 kHz, using 4000 kHz
+  Unable to match requested speed 8000 kHz, using 4000 kHz
+
+The device is now ready for debugging. You can place breakpoints and start the
+device with ``continue``.
diff --git a/targets/stm32f429i-disc1/target_toolchains.gni b/targets/stm32f429i-disc1/target_toolchains.gni
new file mode 100644
index 0000000..e87245e
--- /dev/null
+++ b/targets/stm32f429i-disc1/target_toolchains.gni
@@ -0,0 +1,139 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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("//build_overrides/pigweed.gni")
+
+import("$dir_pw_rpc/system_server/backend.gni")
+import("$dir_pw_sys_io/backend.gni")
+import("$dir_pw_toolchain/arm_gcc/toolchains.gni")
+
+declare_args() {
+  # Enable the pw_target_runner for on-device testing.
+  pw_use_test_server = false
+}
+
+_target_config = {
+  # Use the logging main.
+  pw_unit_test_MAIN = "$dir_pw_unit_test:logging_main"
+
+  # Configuration options for Pigweed executable targets.
+  pw_build_EXECUTABLE_TARGET_TYPE = "stm32f429i_executable"
+
+  pw_build_EXECUTABLE_TARGET_TYPE_FILE =
+      get_path_info("stm32f429i_executable.gni", "abspath")
+
+  # Path to the bloaty config file for the output binaries.
+  pw_bloat_BLOATY_CONFIG = "$dir_pw_boot_armv7m/bloaty_config.bloaty"
+
+  if (pw_use_test_server) {
+    _test_runner_script = "py/stm32f429i_disc1_utils/unit_test_client.py"
+    pw_unit_test_AUTOMATIC_RUNNER =
+        get_path_info(_test_runner_script, "abspath")
+  }
+
+  # Facade backends
+  pw_assert_BACKEND = dir_pw_assert_basic
+  pw_boot_BACKEND = dir_pw_boot_armv7m
+  pw_cpu_exception_ENTRY_BACKEND =
+      "$dir_pw_cpu_exception_cortex_m:cpu_exception_armv7m"
+  pw_cpu_exception_HANDLER_BACKEND = "$dir_pw_cpu_exception:basic_handler"
+  pw_cpu_exception_SUPPORT_BACKEND =
+      "$dir_pw_cpu_exception_cortex_m:support_armv7m"
+  pw_sync_INTERRUPT_SPIN_LOCK_BACKEND =
+      "$dir_pw_sync_baremetal:interrupt_spin_lock"
+  pw_log_BACKEND = dir_pw_log_basic
+  pw_sys_io_BACKEND = dir_pw_sys_io_baremetal_stm32f429
+  pw_rpc_system_server_BACKEND =
+      "$dir_pigweed/targets/stm32f429i-disc1:system_rpc_server"
+  pw_malloc_BACKEND = dir_pw_malloc_freelist
+
+  pw_boot_armv7m_LINK_CONFIG_DEFINES = [
+    "PW_BOOT_FLASH_BEGIN=0x08000200",
+    "PW_BOOT_FLASH_SIZE=512K",
+
+    # TODO(pwbug/219): Currently "pw_tokenizer/detokenize_test" requires at
+    # least 6K bytes in heap when using pw_malloc_freelist. The heap size
+    # required for tests should be investigated.
+    "PW_BOOT_HEAP_SIZE=7K",
+    "PW_BOOT_MIN_STACK_SIZE=1K",
+    "PW_BOOT_RAM_BEGIN=0x20000000",
+    "PW_BOOT_RAM_SIZE=192K",
+    "PW_BOOT_VECTOR_TABLE_BEGIN=0x08000000",
+    "PW_BOOT_VECTOR_TABLE_SIZE=512",
+  ]
+
+  current_cpu = "arm"
+  current_os = ""
+}
+
+_toolchain_properties = {
+  final_binary_extension = ".elf"
+}
+
+_target_default_configs = [
+  "$dir_pw_build:extra_strict_warnings",
+  "$dir_pw_toolchain/arm_gcc:enable_float_printf",
+]
+
+pw_target_toolchain_stm32f429i_disc1 = {
+  _excluded_members = [
+    "defaults",
+    "name",
+  ]
+
+  debug = {
+    name = "stm32f429i_disc1_debug"
+    _toolchain_base = pw_toolchain_arm_gcc.cortex_m4f_debug
+    forward_variables_from(_toolchain_base, "*", _excluded_members)
+    forward_variables_from(_toolchain_properties, "*")
+    defaults = {
+      forward_variables_from(_toolchain_base.defaults, "*")
+      forward_variables_from(_target_config, "*")
+      default_configs += _target_default_configs
+    }
+  }
+
+  speed_optimized = {
+    name = "stm32f429i_disc1_speed_optimized"
+    _toolchain_base = pw_toolchain_arm_gcc.cortex_m4f_speed_optimized
+    forward_variables_from(_toolchain_base, "*", _excluded_members)
+    forward_variables_from(_toolchain_properties, "*")
+    defaults = {
+      forward_variables_from(_toolchain_base.defaults, "*")
+      forward_variables_from(_target_config, "*")
+      default_configs += _target_default_configs
+    }
+  }
+
+  size_optimized = {
+    name = "stm32f429i_disc1_size_optimized"
+    _toolchain_base = pw_toolchain_arm_gcc.cortex_m4f_size_optimized
+    forward_variables_from(_toolchain_base, "*", _excluded_members)
+    forward_variables_from(_toolchain_properties, "*")
+    defaults = {
+      forward_variables_from(_toolchain_base.defaults, "*")
+      forward_variables_from(_target_config, "*")
+      default_configs += _target_default_configs
+    }
+  }
+}
+
+# This list just contains the members of the above scope for convenience to make
+# it trivial to generate all the toolchains in this file via a
+# `generate_toolchains` target.
+pw_target_toolchain_stm32f429i_disc1_list = [
+  pw_target_toolchain_stm32f429i_disc1.debug,
+  pw_target_toolchain_stm32f429i_disc1.speed_optimized,
+  pw_target_toolchain_stm32f429i_disc1.size_optimized,
+]
diff --git a/targets/stm32f429i-disc1/vector_table.c b/targets/stm32f429i-disc1/vector_table.c
new file mode 100644
index 0000000..a9228c5
--- /dev/null
+++ b/targets/stm32f429i-disc1/vector_table.c
@@ -0,0 +1,57 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 <stdbool.h>
+
+#include "pw_boot_armv7m/boot.h"
+
+// Default handler to insert into the ARMv7-M vector table (below).
+// This function exists for convenience. If a device isn't doing what you
+// expect, it might have hit a fault and ended up here.
+static void DefaultFaultHandler(void) {
+  while (true) {
+    // Wait for debugger to attach.
+  }
+}
+
+// This is the device's interrupt vector table. It's not referenced in any
+// code because the platform (STM32F4xx) expects this table to be present at the
+// beginning of flash. The exact address is specified in the pw_boot_armv7m
+// configuration as part of the target config.
+//
+// For more information, see ARMv7-M Architecture Reference Manual DDI 0403E.b
+// section B1.5.3.
+
+// This typedef is for convenience when building the vector table. With the
+// exception of SP_main (0th entry in the vector table), all the entries of the
+// vector table are function pointers.
+typedef void (*InterruptHandler)(void);
+
+PW_KEEP_IN_SECTION(".vector_table")
+const InterruptHandler vector_table[] = {
+    // The starting location of the stack pointer.
+    // This address is NOT an interrupt handler/function pointer, it is simply
+    // the address that the main stack pointer should be initialized to. The
+    // value is reinterpret casted because it needs to be in the vector table.
+    [0] = (InterruptHandler)(&pw_boot_stack_high_addr),
+
+    // Reset handler, dictates how to handle reset interrupt. This is the
+    // address that the Program Counter (PC) is initialized to at boot.
+    [1] = pw_boot_Entry,
+
+    // NMI handler.
+    [2] = DefaultFaultHandler,
+    // HardFault handler.
+    [3] = DefaultFaultHandler,
+};
diff --git a/targets/stm32f429i_disc1/BUILD.bazel b/targets/stm32f429i_disc1/BUILD.bazel
deleted file mode 100644
index f43dd19..0000000
--- a/targets/stm32f429i_disc1/BUILD.bazel
+++ /dev/null
@@ -1,46 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pre_init",
-    srcs = [
-        "boot.cc",
-        "vector_table.c",
-    ],
-    deps = [
-        "//pw_boot",
-        "//pw_boot_cortex_m",
-        "//pw_malloc",
-        "//pw_preprocessor",
-        "//pw_sys_io_baremetal_stm32f429",
-    ],
-)
-
-pw_cc_library(
-    name = "system_rpc_server",
-    srcs = ["system_rpc_server.cc"],
-    deps = [
-        "//pw_hdlc:pw_rpc",
-        "//pw_rpc/system_server:facade",
-    ],
-)
diff --git a/targets/stm32f429i_disc1/BUILD.gn b/targets/stm32f429i_disc1/BUILD.gn
deleted file mode 100644
index 8bfec70..0000000
--- a/targets/stm32f429i_disc1/BUILD.gn
+++ /dev/null
@@ -1,65 +0,0 @@
-# Copyright 2019 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_malloc/backend.gni")
-import("$dir_pw_toolchain/generate_toolchain.gni")
-import("target_toolchains.gni")
-
-generate_toolchains("target_toolchains") {
-  toolchains = pw_target_toolchain_stm32f429i_disc1_list
-}
-
-config("pw_malloc_active") {
-  if (pw_malloc_BACKEND != "") {
-    defines = [ "PW_MALLOC_ACTIVE=1" ]
-  }
-}
-
-if (current_toolchain != default_toolchain) {
-  pw_source_set("pre_init") {
-    configs = [ ":pw_malloc_active" ]
-    public_deps = [
-      "$dir_pw_boot",
-      "$dir_pw_boot_cortex_m",
-      "$dir_pw_sys_io_baremetal_stm32f429",
-    ]
-    deps = [
-      "$dir_pw_malloc",
-      "$dir_pw_preprocessor",
-    ]
-    sources = [
-      "boot.cc",
-      "vector_table.c",
-    ]
-  }
-
-  pw_source_set("system_rpc_server") {
-    deps = [
-      "$dir_pw_hdlc:pw_rpc",
-      "$dir_pw_hdlc:rpc_channel_output",
-      "$dir_pw_rpc/system_server:facade",
-      "$dir_pw_stream:sys_io_stream",
-      dir_pw_log,
-    ]
-    sources = [ "system_rpc_server.cc" ]
-  }
-}
-
-pw_doc_group("target_docs") {
-  sources = [ "target_docs.rst" ]
-}
diff --git a/targets/stm32f429i_disc1/OWNERS b/targets/stm32f429i_disc1/OWNERS
deleted file mode 100644
index 307b1de..0000000
--- a/targets/stm32f429i_disc1/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-amontanez@google.com
diff --git a/targets/stm32f429i_disc1/boot.cc b/targets/stm32f429i_disc1/boot.cc
deleted file mode 100644
index aec48df..0000000
--- a/targets/stm32f429i_disc1/boot.cc
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_boot/boot.h"
-
-#include "pw_boot_cortex_m/boot.h"
-#include "pw_malloc/malloc.h"
-#include "pw_preprocessor/compiler.h"
-#include "pw_sys_io_baremetal_stm32f429/init.h"
-
-// Note that constexpr is used inside of this function instead of using a static
-// constexpr or declaring it outside of this function in an anonymous namespace,
-// because constexpr makes it available for the compiler to evaluate during
-// compile time but does NOT require it to be evaluated at compile time and we
-// have to be incredibly careful that this does not end up in the .data section.
-void pw_boot_PreStaticMemoryInit() {
-  // TODO(pwbug/17): Optionally enable Replace when Pigweed config system is
-  // added.
-#if PW_ARMV7M_ENABLE_FPU
-  // Enable FPU if built using hardware FPU instructions.
-  // CPCAR mask that enables FPU. (ARMv7-M Section B3.2.20)
-  constexpr uint32_t kFpuEnableMask = (0xFu << 20);
-
-  // Memory mapped register to enable FPU. (ARMv7-M Section B3.2.2, Table B3-4)
-  volatile uint32_t& arm_v7m_cpacr =
-      *reinterpret_cast<volatile uint32_t*>(0xE000ED88u);
-  arm_v7m_cpacr |= kFpuEnableMask;
-
-  // Ensure the FPU configuration is committed and enabled before continuing and
-  // potentially executing any FPU instructions, however rare that may be during
-  // startup.
-  asm volatile(
-      " dsb \n"
-      " isb \n"
-      // clang-format off
-      : /*output=*/
-      : /*input=*/
-      : /*clobbers=*/"memory"
-      // clang-format on
-  );
-#endif  // PW_ARMV7M_ENABLE_FPU
-}
-
-void pw_boot_PreStaticConstructorInit() {
-#if PW_MALLOC_ACTIVE
-  pw_MallocInit(&pw_boot_heap_low_addr, &pw_boot_heap_high_addr);
-#endif  // PW_MALLOC_ACTIVE
-}
-
-void pw_boot_PreMainInit() { pw_sys_io_stm32f429_Init(); }
-
-PW_NO_RETURN void pw_boot_PostMain() {
-  // In case main() returns, just sit here until the device is reset.
-  while (true) {
-  }
-  PW_UNREACHABLE;
-}
diff --git a/targets/stm32f429i_disc1/py/BUILD.gn b/targets/stm32f429i_disc1/py/BUILD.gn
deleted file mode 100644
index ea35c60..0000000
--- a/targets/stm32f429i_disc1/py/BUILD.gn
+++ /dev/null
@@ -1,34 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/python.gni")
-
-pw_python_package("py") {
-  setup = [
-    "pyproject.toml",
-    "setup.cfg",
-    "setup.py",
-  ]
-  sources = [
-    "stm32f429i_disc1_utils/__init__.py",
-    "stm32f429i_disc1_utils/stm32f429i_detector.py",
-    "stm32f429i_disc1_utils/unit_test_client.py",
-    "stm32f429i_disc1_utils/unit_test_runner.py",
-    "stm32f429i_disc1_utils/unit_test_server.py",
-  ]
-  pylintrc = "$dir_pigweed/.pylintrc"
-  python_deps = [ "$dir_pw_cli/py" ]
-}
diff --git a/targets/stm32f429i_disc1/py/pyproject.toml b/targets/stm32f429i_disc1/py/pyproject.toml
deleted file mode 100644
index 798b747..0000000
--- a/targets/stm32f429i_disc1/py/pyproject.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[build-system]
-requires = ['setuptools', 'wheel']
-build-backend = 'setuptools.build_meta'
diff --git a/targets/stm32f429i_disc1/py/setup.cfg b/targets/stm32f429i_disc1/py/setup.cfg
deleted file mode 100644
index 42a74b8..0000000
--- a/targets/stm32f429i_disc1/py/setup.cfg
+++ /dev/null
@@ -1,34 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-[metadata]
-name = stm32f429i_disc1_utils
-version = 0.0.1
-author = Pigweed Authors
-author_email = pigweed-developers@googlegroups.com
-description = Target-specific python scripts for the stm32f429i-disc1 target
-
-[options]
-packages = find:
-zip_safe = False
-install_requires = pyserial>=3.5,<4.0; coloredlogs; pw_cli
-
-[options.entry_points]
-console_scripts =
-    stm32f429i_disc1_unit_test_runner = stm32f429i_disc1_utils.unit_test_runner:main
-    stm32f429i_disc1_detector = stm32f429i_disc1_utils.stm32f429i_detector:main
-    stm32f429i_disc1_test_server = stm32f429i_disc1_utils.unit_test_server:main
-    stm32f429i_disc1_test_client = stm32f429i_disc1_utils.unit_test_client:main
-
-[options.package_data]
-stm32f429i_disc1_utils = py.typed
diff --git a/targets/stm32f429i_disc1/py/setup.py b/targets/stm32f429i_disc1/py/setup.py
deleted file mode 100644
index 9ce2559..0000000
--- a/targets/stm32f429i_disc1/py/setup.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""stm32f429i_disc1_utils"""
-
-import setuptools  # type: ignore
-
-setuptools.setup()  # Package definition in setup.cfg
diff --git a/targets/stm32f429i_disc1/py/stm32f429i_disc1_utils/unit_test_server.py b/targets/stm32f429i_disc1/py/stm32f429i_disc1_utils/unit_test_server.py
deleted file mode 100644
index 302be22..0000000
--- a/targets/stm32f429i_disc1/py/stm32f429i_disc1_utils/unit_test_server.py
+++ /dev/null
@@ -1,116 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2019 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Launch a pw_target_runner server to use for multi-device testing."""
-
-import argparse
-import logging
-import sys
-import tempfile
-from typing import IO, List, Optional
-
-import pw_cli.process
-import pw_cli.log
-
-from stm32f429i_disc1_utils import stm32f429i_detector
-
-_LOG = logging.getLogger('unit_test_server')
-
-_TEST_RUNNER_COMMAND = 'stm32f429i_disc1_unit_test_runner'
-
-_TEST_SERVER_COMMAND = 'pw_target_runner_server'
-
-
-def parse_args():
-    """Parses command-line arguments."""
-
-    parser = argparse.ArgumentParser(description=__doc__)
-    parser.add_argument('--server-port',
-                        type=int,
-                        default=8080,
-                        help='Port to launch the pw_target_runner_server on')
-    parser.add_argument('--server-config',
-                        type=argparse.FileType('r'),
-                        help='Path to server config file')
-    parser.add_argument('--verbose',
-                        '-v',
-                        dest='verbose',
-                        action="store_true",
-                        help='Output additional logs as the script runs')
-
-    return parser.parse_args()
-
-
-def generate_runner(command: str, arguments: List[str]) -> str:
-    """Generates a text-proto style pw_target_runner_server configuration."""
-    # TODO(amontanez): Use a real proto library to generate this when we have
-    # one set up.
-    for i, arg in enumerate(arguments):
-        arguments[i] = f'  args: "{arg}"'
-    runner = ['runner {', f'  command:"{command}"']
-    runner.extend(arguments)
-    runner.append('}\n')
-    return '\n'.join(runner)
-
-
-def generate_server_config() -> IO[bytes]:
-    """Returns a temporary generated file for use as the server config."""
-    boards = stm32f429i_detector.detect_boards()
-    if not boards:
-        _LOG.critical('No attached boards detected')
-        sys.exit(1)
-    config_file = tempfile.NamedTemporaryFile()
-    _LOG.debug('Generating test server config at %s', config_file.name)
-    _LOG.debug('Found %d attached devices', len(boards))
-    for board in boards:
-        test_runner_args = [
-            '--stlink-serial', board.serial_number, '--port', board.dev_name
-        ]
-        config_file.write(
-            generate_runner(_TEST_RUNNER_COMMAND,
-                            test_runner_args).encode('utf-8'))
-    config_file.flush()
-    return config_file
-
-
-def launch_server(server_config: Optional[IO[bytes]],
-                  server_port: Optional[int]) -> int:
-    """Launch a device test server with the provided arguments."""
-    if server_config is None:
-        # Auto-detect attached boards if no config is provided.
-        server_config = generate_server_config()
-
-    cmd = [_TEST_SERVER_COMMAND, '-config', server_config.name]
-
-    if server_port is not None:
-        cmd.extend(['-port', str(server_port)])
-
-    return pw_cli.process.run(*cmd, log_output=True).returncode
-
-
-def main():
-    """Launch a device test server with the provided arguments."""
-    args = parse_args()
-
-    # Try to use pw_cli logs, else default to something reasonable.
-    pw_cli.log.install()
-    if args.verbose:
-        _LOG.setLevel(logging.DEBUG)
-
-    exit_code = launch_server(args.server_config, args.server_port)
-    sys.exit(exit_code)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/targets/stm32f429i_disc1/stm32f429i_executable.gni b/targets/stm32f429i_disc1/stm32f429i_executable.gni
deleted file mode 100644
index 4dafe8a..0000000
--- a/targets/stm32f429i_disc1/stm32f429i_executable.gni
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-import("$dir_pw_malloc/backend.gni")
-
-# Executable wrapper that includes some baremetal startup code.
-template("stm32f429i_executable") {
-  target("executable", target_name) {
-    forward_variables_from(invoker, "*")
-    if (!defined(deps)) {
-      deps = []
-    }
-    deps += [ "$dir_pigweed/targets/stm32f429i_disc1:pre_init" ]
-    if (pw_malloc_BACKEND != "") {
-      if (!defined(configs)) {
-        configs = []
-      }
-      configs += [ "$dir_pw_malloc:pw_malloc_wrapper_config" ]
-    }
-  }
-}
diff --git a/targets/stm32f429i_disc1/system_rpc_server.cc b/targets/stm32f429i_disc1/system_rpc_server.cc
deleted file mode 100644
index 52be514..0000000
--- a/targets/stm32f429i_disc1/system_rpc_server.cc
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <cstddef>
-
-#include "pw_hdlc/rpc_channel.h"
-#include "pw_hdlc/rpc_packets.h"
-#include "pw_log/log.h"
-#include "pw_rpc_system_server/rpc_server.h"
-#include "pw_stream/sys_io_stream.h"
-
-namespace pw::rpc::system_server {
-namespace {
-
-constexpr size_t kMaxTransmissionUnit = 256;
-
-// Used to write HDLC data to pw::sys_io.
-stream::SysIoWriter writer;
-stream::SysIoReader reader;
-
-// Set up the output channel for the pw_rpc server to use.
-hdlc::RpcChannelOutput hdlc_channel_output(writer,
-                                           pw::hdlc::kDefaultRpcAddress,
-                                           "HDLC channel");
-Channel channels[] = {pw::rpc::Channel::Create<1>(&hdlc_channel_output)};
-rpc::Server server(channels);
-
-}  // namespace
-
-void Init() {
-  // Send log messages to HDLC address 1. This prevents logs from interfering
-  // with pw_rpc communications.
-  pw::log_basic::SetOutput([](std::string_view log) {
-    pw::hdlc::WriteUIFrame(1, std::as_bytes(std::span(log)), writer);
-  });
-}
-
-rpc::Server& Server() { return server; }
-
-Status Start() {
-  // Declare a buffer for decoding incoming HDLC frames.
-  std::array<std::byte, kMaxTransmissionUnit> input_buffer;
-  hdlc::Decoder decoder(input_buffer);
-
-  while (true) {
-    std::byte byte;
-    Status ret_val = pw::sys_io::ReadByte(&byte);
-    if (!ret_val.ok()) {
-      return ret_val;
-    }
-    if (auto result = decoder.Process(byte); result.ok()) {
-      hdlc::Frame& frame = result.value();
-      if (frame.address() == hdlc::kDefaultRpcAddress) {
-        server.ProcessPacket(frame.data(), hdlc_channel_output);
-      }
-    }
-  }
-}
-
-}  // namespace pw::rpc::system_server
diff --git a/targets/stm32f429i_disc1/target_docs.rst b/targets/stm32f429i_disc1/target_docs.rst
deleted file mode 100644
index fcf6d73..0000000
--- a/targets/stm32f429i_disc1/target_docs.rst
+++ /dev/null
@@ -1,246 +0,0 @@
-.. _target-stm32f429i-disc1:
-
-----------------
-stm32f429i-disc1
-----------------
-The STMicroelectronics STM32F429I-DISC1 development board is currently Pigweed's
-primary target for on-device testing and development.
-
-Building
-========
-To build for this Pigweed target, simply build the top-level "stm32f429i" Ninja
-target.
-
-.. code:: sh
-
-  $ ninja -C out stm32f429i
-
-Testing
-=======
-When working in upstream Pigweed, building this target will build all Pigweed modules' unit tests.
-These tests can be run on-device in a few different ways.
-
-Run a unit test
----------------
-If using ``out`` as a build directory, tests will be located in
-``out/stm32f429i_disc1_debug/obj/[module name]/[test_name].elf``. To run these
-on device, the stm32f429i-disc1 target provides a helper script that flashes the
-test to a device and then runs it.
-
-.. code:: sh
-
-  # Setup pigweed environment.
-  $ source activate.sh
-  # Run test.
-  $ stm32f429i_disc1_unit_test_runner /path/to/binary
-
-Run multiple tests
-------------------
-Running all tests one-by-one is rather tedious. To make running multiple
-tests easier, use Pigweed's ``pw test`` command and pass it a path to the build
-directory and the name of the test runner. By default, ``pw test`` will run all
-tests, but it can be restricted it to specific ``pw_test_group`` targets using
-the ``--group`` argument. Alternatively, individual test binaries can be
-specified with the ``--test`` option.
-
-.. code:: sh
-
-  # Setup Pigweed environment.
-  $ source activate.sh
-  # Run test.
-  $ pw test --root out/stm32f429i_disc_debug/  \
-        --runner stm32f429i_disc1_unit_test_runner
-
-Run tests affected by code changes
-----------------------------------
-When writing code that will impact multiple modules, it's helpful to only run
-all tests that are affected by a given code change. Thanks to the GN/Ninja
-build, this is possible! This is done by using a ``pw_target_runner_server``
-that Ninja can send the tests to as it rebuilds affected targets.
-
-Additionally, this method enables distributed testing. If multiple devices are
-connected, the tests will be run across all attached devices to further speed up
-testing.
-
-Step 1: Start test server
-^^^^^^^^^^^^^^^^^^^^^^^^^
-To allow Ninja to properly serialize tests to run on an arbitrary number of
-devices, Ninja will send test requests to a server running in the background.
-The first step is to launch this server. By default, the script will attempt
-to automatically detect all attached STM32f429I-DISC1 boards and use them for
-testing. To override this behavior, provide a custom server configuration file
-with ``--server-config``.
-
-.. tip::
-
-  If you unplug or plug in any boards, you'll need to restart the test server
-  for hardware changes to properly be detected.
-
-.. code:: sh
-
-  $ stm32f429i_disc1_test_server
-
-Step 2: Configure GN
-^^^^^^^^^^^^^^^^^^^^
-By default, this hardware target has incremental testing via
-``pw_target_runner`` disabled. Enabling the ``pw_use_test_server`` build arg
-tells GN to send requests to a running ``stm32f429i_disc1_test_server``.
-
-.. code:: sh
-
-  $ gn args out
-  # Modify and save the args file to use pw_target_runner.
-  pw_use_test_server = true
-
-Step 3: Build changes
-^^^^^^^^^^^^^^^^^^^^^
-Whenever you run ``ninja -C out stm32f429i``, affected tests will be built and
-run on the attached device(s). Alternatively, you may use ``pw watch`` to set up
-Pigweed to build/test whenever it sees changes to source files.
-
-RPC server
-==========
-The stm32f429i target implements a system RPC server that over a simple UART
-driver. To communicate with a device running the RPC server, run
-``pw rpc -d <device> -b 115200 <protos>``.
-
-Debugging
-=========
-There are multiple ways to debug the device, including using commercial tools
-like SEGGER's J-Link. However, the Discovery board has an on-board STLink
-debugger, which is supported by the open source OpenOCD debugger. To debug with
-OpenOCD requires a few steps. Summary version of the steps:
-
-#. Connect OpenOCD to the device in terminal A. Leave this running
-
-   .. code:: sh
-
-     $ openocd -f targets/stm32f429i_disc1/py/stm32f429i_disc1_utils/openocd_stm32f4xx.cfg
-
-#. Connect GDB to the running OpenOCD instance in terminal B
-
-   .. code:: sh
-
-     $ arm-none-eabi-gdb -ex "target remote :3333" \
-       out/stm32f429i_disc1_debug/obj/pw_assert/test/assert_facade_test.elf
-
-#. Flash (``load``), run (``mon reset run; continue``), and debug
-
-   .. code:: none
-
-     (gdb) set print pretty on
-     (gdb) load
-     (gdb) mon reset run
-     (gdb) continue
-
-#. You can re-flash the device after compiling by running ``load``.
-
-
-Step 1: Start an OpenOCD server and connect to the device
----------------------------------------------------------
-OpenOCD is a persistent server that you run and leave running to bridge between
-GDB and the device. To run it for the Discovery board:
-
-.. code:: sh
-
-  $ openocd -f targets/stm32f429i_disc1/py/stm32f429i_disc1_utils/openocd_stm32f4xx.cfg
-
-Typical output:
-
-.. code:: none
-
-  Open On-Chip Debugger 0.10.0+dev-01243-ge41c0f49-dirty (2020-05-21-10:27)
-  Licensed under GNU GPL v2
-  For bug reports, read
-          http://openocd.org/doc/doxygen/bugs.html
-  DEPRECATED! use 'adapter driver' not 'interface'
-  Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
-  srst_only separate srst_nogate srst_open_drain connect_deassert_srst
-
-  Info : Listening on port 6666 for tcl connections
-  Info : Listening on port 4444 for telnet connections
-  Info : clock speed 2000 kHz
-  Info : STLINK V2J25M14 (API v2) VID:PID 0483:374B
-  Info : Target voltage: 2.871879
-  Info : stm32f4x.cpu: hardware has 6 breakpoints, 4 watchpoints
-  Info : starting gdb server for stm32f4x.cpu on 3333
-  Info : Listening on port 3333 for gdb connections
-
-Step 2: Start GDB and connect to the OpenOCD server
----------------------------------------------------
-Start GDB pointing to the correct .elf file, and tell it to connect to the
-OpenOCD server (running on port 333 by default).
-
-.. code:: sh
-
-  $ arm-none-eabi-gdb -ex "target remote :3333" \
-    out/stm32f429i_disc1_debug/obj/pw_assert/test/assert_facade_test.elf
-
-In this case the assert facade test is debugged, but substitute your own ELF
-file. This should produce output similar to the following:
-
-.. code:: none
-
-  GNU gdb (GNU Arm Embedded Toolchain 9-2020-q2-update) 8.3.1.20191211-git
-  Copyright (C) 2019 Free Software Foundation, Inc.
-  License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
-  This is free software: you are free to change and redistribute it.
-  There is NO WARRANTY, to the extent permitted by law.
-  Type "show copying" and "show warranty" for details.
-  This GDB was configured as "--host=x86_64-apple-darwin10 --target=arm-none-eabi".
-  Type "show configuration" for configuration details.
-  For bug reporting instructions, please see:
-  <http://www.gnu.org/software/gdb/bugs/>.
-  Find the GDB manual and other documentation resources online at:
-      <http://www.gnu.org/software/gdb/documentation/>.
-
-  For help, type "help".
-  Type "apropos word" to search for commands related to "word"...
-  Reading symbols from out/stm32f429i_disc1_debug/obj/pw_assert//test/assert_facade_test.elf...
-  Remote debugging using :3333
-  pw_BootEntry () at ../pw_boot_cortex_m/core_init.c:117
-  117	  }
-
-Step 3: Flash, run, and debug
------------------------------
-Now that the GDB instance is connected to the device, you can flash, run, and debug.
-
-To flash
-
-.. code:: none
-
-  (gdb) load
-
-This will produce output similar to:
-
-.. code:: none
-
-  (gdb) load
-  Loading section .vector_table, size 0x10 lma 0x8000000
-  Loading section .code, size 0xdb8c lma 0x8000200
-  Loading section .ARM, size 0x8 lma 0x800dd90
-  Loading section .static_init_ram, size 0x1d0 lma 0x800dd98
-  Start address 0x8007c48, load size 56692
-  Transfer rate: 25 KB/sec, 8098 bytes/write.
-
-To reset the device and halt on the first instruction (before main):
-
-.. code:: none
-
-  (gdb) mon reset run
-
-
-This will produce output similar to:
-
-.. code:: none
-
-  (gdb) mon reset run
-  Unable to match requested speed 2000 kHz, using 1800 kHz
-  Unable to match requested speed 2000 kHz, using 1800 kHz
-  target halted due to debug-request, current mode: Thread
-  xPSR: 0x01000000 pc: 0x08007930 msp: 0x20030000
-  Unable to match requested speed 8000 kHz, using 4000 kHz
-  Unable to match requested speed 8000 kHz, using 4000 kHz
-
-The device is now ready for debugging. You can place breakpoints and start the
-device with ``continue``.
diff --git a/targets/stm32f429i_disc1/target_toolchains.gni b/targets/stm32f429i_disc1/target_toolchains.gni
deleted file mode 100644
index 61012a7..0000000
--- a/targets/stm32f429i_disc1/target_toolchains.gni
+++ /dev/null
@@ -1,150 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_rpc/system_server/backend.gni")
-import("$dir_pw_sys_io/backend.gni")
-import("$dir_pw_toolchain/arm_gcc/toolchains.gni")
-
-declare_args() {
-  # Enable the pw_target_runner for on-device testing.
-  pw_use_test_server = false
-}
-
-_target_config = {
-  # Use the logging main.
-  pw_unit_test_MAIN = "$dir_pw_unit_test:logging_main"
-
-  # Configuration options for Pigweed executable targets.
-  pw_build_EXECUTABLE_TARGET_TYPE = "stm32f429i_executable"
-
-  pw_build_EXECUTABLE_TARGET_TYPE_FILE =
-      get_path_info("stm32f429i_executable.gni", "abspath")
-
-  # Path to the bloaty config file for the output binaries.
-  pw_bloat_BLOATY_CONFIG = "$dir_pw_boot_cortex_m/bloaty_config.bloaty"
-
-  if (pw_use_test_server) {
-    _test_runner_script = "py/stm32f429i_disc1_utils/unit_test_client.py"
-    pw_unit_test_AUTOMATIC_RUNNER =
-        get_path_info(_test_runner_script, "abspath")
-  }
-
-  # Facade backends
-  pw_assert_BACKEND = dir_pw_assert_basic
-  pw_boot_BACKEND = "$dir_pw_boot_cortex_m"
-  pw_cpu_exception_ENTRY_BACKEND =
-      "$dir_pw_cpu_exception_cortex_m:cpu_exception"
-  pw_cpu_exception_HANDLER_BACKEND = "$dir_pw_cpu_exception:basic_handler"
-  pw_cpu_exception_SUPPORT_BACKEND = "$dir_pw_cpu_exception_cortex_m:support"
-  pw_sync_INTERRUPT_SPIN_LOCK_BACKEND =
-      "$dir_pw_sync_baremetal:interrupt_spin_lock"
-  pw_sync_MUTEX_BACKEND = "$dir_pw_sync_baremetal:mutex"
-  pw_log_BACKEND = dir_pw_log_basic
-  pw_sys_io_BACKEND = dir_pw_sys_io_baremetal_stm32f429
-  pw_rpc_system_server_BACKEND =
-      "$dir_pigweed/targets/stm32f429i_disc1:system_rpc_server"
-  pw_malloc_BACKEND = dir_pw_malloc_freelist
-
-  pw_boot_cortex_m_LINK_CONFIG_DEFINES = [
-    "PW_BOOT_FLASH_BEGIN=0x08000200",
-    "PW_BOOT_FLASH_SIZE=1024K",
-
-    # TODO(pwbug/219): Currently "pw_tokenizer/detokenize_test" requires at
-    # least 6K bytes in heap when using pw_malloc_freelist. The heap size
-    # required for tests should be investigated.
-    #
-    # TLS realted tests such as $dir_pw_third_party/boringssl:tests require
-    # much larger heap for dynamic allocation. The current number is an
-    # estimated requirement. The acutal required size will be further investigated
-    # when all TLS tests are in place.
-    "PW_BOOT_HEAP_SIZE=112K",
-    "PW_BOOT_MIN_STACK_SIZE=1K",
-    "PW_BOOT_RAM_BEGIN=0x20000000",
-    "PW_BOOT_RAM_SIZE=192K",
-    "PW_BOOT_VECTOR_TABLE_BEGIN=0x08000000",
-    "PW_BOOT_VECTOR_TABLE_SIZE=512",
-  ]
-
-  pw_build_LINK_DEPS = [
-    "$dir_pw_assert:impl",
-    "$dir_pw_cpu_exception:entry_impl",
-    "$dir_pw_log:impl",
-  ]
-
-  current_cpu = "arm"
-  current_os = ""
-}
-
-_toolchain_properties = {
-  final_binary_extension = ".elf"
-}
-
-_target_default_configs = [
-  "$dir_pw_build:extra_strict_warnings",
-  "$dir_pw_toolchain/arm_gcc:enable_float_printf",
-]
-
-pw_target_toolchain_stm32f429i_disc1 = {
-  _excluded_members = [
-    "defaults",
-    "name",
-  ]
-
-  debug = {
-    name = "stm32f429i_disc1_debug"
-    _toolchain_base = pw_toolchain_arm_gcc.cortex_m4f_debug
-    forward_variables_from(_toolchain_base, "*", _excluded_members)
-    forward_variables_from(_toolchain_properties, "*")
-    defaults = {
-      forward_variables_from(_toolchain_base.defaults, "*")
-      forward_variables_from(_target_config, "*")
-      default_configs += _target_default_configs
-    }
-  }
-
-  speed_optimized = {
-    name = "stm32f429i_disc1_speed_optimized"
-    _toolchain_base = pw_toolchain_arm_gcc.cortex_m4f_speed_optimized
-    forward_variables_from(_toolchain_base, "*", _excluded_members)
-    forward_variables_from(_toolchain_properties, "*")
-    defaults = {
-      forward_variables_from(_toolchain_base.defaults, "*")
-      forward_variables_from(_target_config, "*")
-      default_configs += _target_default_configs
-    }
-  }
-
-  size_optimized = {
-    name = "stm32f429i_disc1_size_optimized"
-    _toolchain_base = pw_toolchain_arm_gcc.cortex_m4f_size_optimized
-    forward_variables_from(_toolchain_base, "*", _excluded_members)
-    forward_variables_from(_toolchain_properties, "*")
-    defaults = {
-      forward_variables_from(_toolchain_base.defaults, "*")
-      forward_variables_from(_target_config, "*")
-      default_configs += _target_default_configs
-    }
-  }
-}
-
-# This list just contains the members of the above scope for convenience to make
-# it trivial to generate all the toolchains in this file via a
-# `generate_toolchains` target.
-pw_target_toolchain_stm32f429i_disc1_list = [
-  pw_target_toolchain_stm32f429i_disc1.debug,
-  pw_target_toolchain_stm32f429i_disc1.speed_optimized,
-  pw_target_toolchain_stm32f429i_disc1.size_optimized,
-]
diff --git a/targets/stm32f429i_disc1/vector_table.c b/targets/stm32f429i_disc1/vector_table.c
deleted file mode 100644
index 653b649..0000000
--- a/targets/stm32f429i_disc1/vector_table.c
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2020 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <stdbool.h>
-
-#include "pw_boot/boot.h"
-#include "pw_boot_cortex_m/boot.h"
-
-// Default handler to insert into the ARMv7-M vector table (below).
-// This function exists for convenience. If a device isn't doing what you
-// expect, it might have hit a fault and ended up here.
-static void DefaultFaultHandler(void) {
-  while (true) {
-    // Wait for debugger to attach.
-  }
-}
-
-// This is the device's interrupt vector table. It's not referenced in any
-// code because the platform (STM32F4xx) expects this table to be present at the
-// beginning of flash. The exact address is specified in the pw_boot_cortex_m
-// configuration as part of the target config.
-//
-// For more information, see ARMv7-M Architecture Reference Manual DDI 0403E.b
-// section B1.5.3.
-
-// This typedef is for convenience when building the vector table. With the
-// exception of SP_main (0th entry in the vector table), all the entries of the
-// vector table are function pointers.
-typedef void (*InterruptHandler)(void);
-
-PW_KEEP_IN_SECTION(".vector_table")
-const InterruptHandler vector_table[] = {
-    // The starting location of the stack pointer.
-    // This address is NOT an interrupt handler/function pointer, it is simply
-    // the address that the main stack pointer should be initialized to. The
-    // value is reinterpret casted because it needs to be in the vector table.
-    [0] = (InterruptHandler)(&pw_boot_stack_high_addr),
-
-    // Reset handler, dictates how to handle reset interrupt. This is the
-    // address that the Program Counter (PC) is initialized to at boot.
-    [1] = pw_boot_Entry,
-
-    // NMI handler.
-    [2] = DefaultFaultHandler,
-    // HardFault handler.
-    [3] = DefaultFaultHandler,
-};
diff --git a/targets/stm32f429i_disc1_stm32cube/BUILD.bazel b/targets/stm32f429i_disc1_stm32cube/BUILD.bazel
deleted file mode 100644
index 891d890..0000000
--- a/targets/stm32f429i_disc1_stm32cube/BUILD.bazel
+++ /dev/null
@@ -1,58 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_binary",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "pre_init",
-    srcs = [
-        "boot.cc",
-        "vector_table.c",
-    ],
-    hdrs = [
-        "config/FreeRTOSConfig.h",
-        "config/stm32f4xx_hal_conf.h",
-    ],
-    deps = [
-        "//pw_boot",
-        "//pw_boot_cortex_m",
-        "//pw_malloc",
-        "//pw_preprocessor",
-        "//pw_string",
-        "//pw_sys_io_stm32cube",
-        "//third_party/freertos",
-        "//third_party/stm32cube",
-    ],
-)
-
-pw_cc_binary(
-    name = "demo",
-    srcs = [
-        "main.cc",
-    ],
-    deps = [
-        "//pw_thread:thread",
-        "//pw_thread:thread_core",
-        "//pw_thread_freertos:thread",
-        "//third_party/freertos",
-    ],
-)
diff --git a/targets/stm32f429i_disc1_stm32cube/BUILD.gn b/targets/stm32f429i_disc1_stm32cube/BUILD.gn
deleted file mode 100644
index 8186928..0000000
--- a/targets/stm32f429i_disc1_stm32cube/BUILD.gn
+++ /dev/null
@@ -1,103 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_docgen/docs.gni")
-import("$dir_pw_malloc/backend.gni")
-import("$dir_pw_system/system_target.gni")
-import("$dir_pw_third_party/stm32cube/stm32cube.gni")
-import("$dir_pw_tokenizer/backend.gni")
-import("$dir_pw_toolchain/generate_toolchain.gni")
-
-config("pw_malloc_active") {
-  if (pw_malloc_BACKEND != "") {
-    defines = [ "PW_MALLOC_ACTIVE=1" ]
-  }
-}
-
-if (current_toolchain != default_toolchain) {
-  pw_source_set("pre_init") {
-    configs = [ ":pw_malloc_active" ]
-    deps = [
-      "$dir_pw_boot",
-      "$dir_pw_boot_cortex_m",
-      "$dir_pw_malloc",
-      "$dir_pw_preprocessor",
-      "$dir_pw_string",
-      "$dir_pw_sys_io_stm32cube",
-      "$dir_pw_system",
-      "$dir_pw_third_party/freertos",
-      "$dir_pw_third_party/stm32cube",
-    ]
-    sources = [
-      "boot.cc",
-      "vector_table.c",
-    ]
-  }
-
-  config("config_includes") {
-    include_dirs = [ "config" ]
-  }
-
-  pw_source_set("stm32f4xx_hal_config") {
-    public_configs = [ ":config_includes" ]
-    public = [ "config/stm32f4xx_hal_conf.h" ]
-  }
-
-  pw_source_set("stm32f4xx_freertos_config") {
-    public_configs = [ ":config_includes" ]
-    public_deps = [ "$dir_pw_third_party/freertos:config_assert" ]
-    public = [ "config/FreeRTOSConfig.h" ]
-  }
-}
-
-pw_system_target("stm32f429i_disc1_stm32cube") {
-  cpu = PW_SYSTEM_CPU.CORTEX_M4F
-  scheduler = PW_SYSTEM_SCHEDULER.FREERTOS
-
-  link_deps = [ "$dir_pigweed/targets/stm32f429i_disc1_stm32cube:pre_init" ]
-  build_args = {
-    pw_log_BACKEND = dir_pw_log_tokenized
-    pw_tokenizer_GLOBAL_HANDLER_WITH_PAYLOAD_BACKEND =
-        "$dir_pw_system:log_backend.impl"
-    pw_third_party_freertos_CONFIG = "$dir_pigweed/targets/stm32f429i_disc1_stm32cube:stm32f4xx_freertos_config"
-    pw_third_party_freertos_PORT = "$dir_pw_third_party/freertos:arm_cm4f"
-    pw_sys_io_BACKEND = dir_pw_sys_io_stm32cube
-    dir_pw_third_party_stm32cube = dir_pw_third_party_stm32cube_f4
-    pw_third_party_stm32cube_PRODUCT = "STM32F429xx"
-    pw_third_party_stm32cube_CONFIG =
-        "$dir_pigweed/targets/stm32f429i_disc1_stm32cube:stm32f4xx_hal_config"
-    pw_third_party_stm32cube_CORE_INIT = ""
-    pw_boot_cortex_m_LINK_CONFIG_DEFINES = [
-      "PW_BOOT_FLASH_BEGIN=0x08000200",
-      "PW_BOOT_FLASH_SIZE=2048K",
-
-      # TODO(pwbug/219): Currently "pw_tokenizer/detokenize_test" requires at
-      # least 6K bytes in heap when using pw_malloc_freelist. The heap size
-      # required for tests should be investigated.
-      "PW_BOOT_HEAP_SIZE=7K",
-      "PW_BOOT_MIN_STACK_SIZE=1K",
-      "PW_BOOT_RAM_BEGIN=0x20000000",
-      "PW_BOOT_RAM_SIZE=192K",
-      "PW_BOOT_VECTOR_TABLE_BEGIN=0x08000000",
-      "PW_BOOT_VECTOR_TABLE_SIZE=512",
-    ]
-  }
-}
-
-pw_doc_group("target_docs") {
-  sources = [ "target_docs.rst" ]
-}
diff --git a/targets/stm32f429i_disc1_stm32cube/boot.cc b/targets/stm32f429i_disc1_stm32cube/boot.cc
deleted file mode 100644
index 8f00947..0000000
--- a/targets/stm32f429i_disc1_stm32cube/boot.cc
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_boot/boot.h"
-
-#include <array>
-
-#include "FreeRTOS.h"
-#include "pw_boot_cortex_m/boot.h"
-#include "pw_malloc/malloc.h"
-#include "pw_preprocessor/compiler.h"
-#include "pw_string/util.h"
-#include "pw_sys_io_stm32cube/init.h"
-#include "pw_system/init.h"
-#include "stm32f4xx.h"
-#include "task.h"
-
-namespace {
-
-std::array<StackType_t, configMINIMAL_STACK_SIZE> freertos_idle_stack;
-StaticTask_t freertos_idle_tcb;
-
-std::array<StackType_t, configTIMER_TASK_STACK_DEPTH> freertos_timer_stack;
-StaticTask_t freertos_timer_tcb;
-
-std::array<char, configMAX_TASK_NAME_LEN> temp_thread_name_buffer;
-
-}  // namespace
-
-// Initializes clock to its max, 180Mhz. Note that this naming follows CubeMX's
-// naming out of convention. It's not required that this target provides a
-// symbol named SystemClock_Config. This function shares the same purpose as
-// the symbol of the same name that is generated by CubeMX.
-extern "C" void SystemClock_Config() {
-  RCC_OscInitTypeDef RCC_OscInitStruct = {};
-  RCC_ClkInitTypeDef RCC_ClkInitStruct = {};
-
-  __HAL_RCC_PWR_CLK_ENABLE();
-  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
-
-  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
-  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
-  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
-  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
-  RCC_OscInitStruct.PLL.PLLM = 4;
-  RCC_OscInitStruct.PLL.PLLN = 180;
-  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
-  RCC_OscInitStruct.PLL.PLLQ = 8;
-  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
-    pw_boot_PostMain();
-  }
-
-  // OverDrive required for operation > 168Mhz
-  if (HAL_PWREx_EnableOverDrive() != HAL_OK) {
-    pw_boot_PostMain();
-  }
-
-  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK |
-                                RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
-  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
-  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
-  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
-  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
-
-  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK) {
-    pw_boot_PostMain();
-  }
-}
-
-// Functions needed when configGENERATE_RUN_TIME_STATS is on.
-extern "C" void configureTimerForRunTimeStats(void) {}
-extern "C" unsigned long getRunTimeCounterValue(void) { return uwTick; }
-
-// Required for configCHECK_FOR_STACK_OVERFLOW.
-extern "C" void vApplicationStackOverflowHook(TaskHandle_t, char* pcTaskName) {
-  pw::string::Copy(pcTaskName, temp_thread_name_buffer);
-  PW_CRASH("Stack OVF for task %s", temp_thread_name_buffer.data());
-}
-
-// Required for configUSE_TIMERS.
-extern "C" void vApplicationGetTimerTaskMemory(
-    StaticTask_t** ppxTimerTaskTCBBuffer,
-    StackType_t** ppxTimerTaskStackBuffer,
-    uint32_t* pulTimerTaskStackSize) {
-  *ppxTimerTaskTCBBuffer = &freertos_timer_tcb;
-  *ppxTimerTaskStackBuffer = freertos_timer_stack.data();
-  *pulTimerTaskStackSize = freertos_timer_stack.size();
-}
-
-extern "C" void vApplicationGetIdleTaskMemory(
-    StaticTask_t** ppxIdleTaskTCBBuffer,
-    StackType_t** ppxIdleTaskStackBuffer,
-    uint32_t* pulIdleTaskStackSize) {
-  *ppxIdleTaskTCBBuffer = &freertos_idle_tcb;
-  *ppxIdleTaskStackBuffer = freertos_idle_stack.data();
-  *pulIdleTaskStackSize = freertos_idle_stack.size();
-}
-
-extern "C" void pw_boot_PreStaticMemoryInit() {}
-
-extern "C" void pw_boot_PreStaticConstructorInit() {
-  // Provided by STMicroelectronics SDK. Can be configured to be provided
-  // elsewhere by changing pw_third_party_stm32cube_CMSIS_INIT.
-  SystemInit();
-
-  // Provided by the STMicroelectronics SDK.
-  HAL_Init();
-
-  // Typically provided by CubeMX codegen, SystemClock_Config() is instead
-  // provided as part of this target.
-  SystemClock_Config();
-
-#if PW_MALLOC_ACTIVE
-  pw_MallocInit(&pw_boot_heap_low_addr, &pw_boot_heap_high_addr);
-#endif  // PW_MALLOC_ACTIVE
-  pw_sys_io_Init();
-}
-
-// TODO(amontanez): pw_boot_PreMainInit() should get renamed to
-// pw_boot_FinalizeBoot or similar when main() is removed.
-extern "C" void pw_boot_PreMainInit() {
-  pw::system::Init();
-  vTaskStartScheduler();
-  PW_UNREACHABLE;
-}
-
-// This `main()` stub prevents another main function from being linked since
-// this target deliberately doesn't run `main()`.
-extern "C" int main() {}
-
-extern "C" PW_NO_RETURN void pw_boot_PostMain() {
-  // In case main() returns, just sit here until the device is reset.
-  while (true) {
-  }
-  PW_UNREACHABLE;
-}
diff --git a/targets/stm32f429i_disc1_stm32cube/config/FreeRTOSConfig.h b/targets/stm32f429i_disc1_stm32cube/config/FreeRTOSConfig.h
deleted file mode 100644
index e32e244..0000000
--- a/targets/stm32f429i_disc1_stm32cube/config/FreeRTOSConfig.h
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <stdint.h>
-
-// Externally defined variables that must be forward-declared for FreeRTOS to
-// use them.
-extern uint32_t SystemCoreClock;
-extern void configureTimerForRunTimeStats(void);
-extern unsigned long getRunTimeCounterValue(void);
-
-#define configSUPPORT_DYNAMIC_ALLOCATION 0
-#define configSUPPORT_STATIC_ALLOCATION 1
-
-#define configUSE_16_BIT_TICKS 0
-#define configUSE_CO_ROUTINES 0
-#define configUSE_IDLE_HOOK 0
-#define configUSE_MUTEXES 1
-#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1
-#define configUSE_PREEMPTION 1
-#define configUSE_TICK_HOOK 0
-#define configUSE_TIMERS 1
-#define configUSE_TRACE_FACILITY 1
-
-#define configGENERATE_RUN_TIME_STATS 1
-#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS configureTimerForRunTimeStats
-#define portGET_RUN_TIME_COUNTER_VALUE getRunTimeCounterValue
-
-#define configCHECK_FOR_STACK_OVERFLOW 2
-#define configCPU_CLOCK_HZ (SystemCoreClock)
-#define configENABLE_BACKWARD_COMPATIBILITY 0
-#define configMAX_CO_ROUTINE_PRIORITIES (2)
-#define configMAX_PRIORITIES (7)
-#define configMAX_TASK_NAME_LEN (16)
-#define configMESSAGE_BUFFER_LENGTH_TYPE size_t
-#define configMINIMAL_STACK_SIZE ((uint16_t)128)
-#define configQUEUE_REGISTRY_SIZE 8
-#define configRECORD_STACK_HIGH_ADDRESS 1
-#define configTICK_RATE_HZ ((TickType_t)1000)
-#define configTIMER_QUEUE_LENGTH 10
-#define configTIMER_TASK_PRIORITY (6)
-#define configTIMER_TASK_STACK_DEPTH 512
-
-/* __NVIC_PRIO_BITS in CMSIS */
-#define configPRIO_BITS 4
-
-#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15
-#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
-#define configKERNEL_INTERRUPT_PRIORITY \
-  (configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))
-#define configMAX_SYSCALL_INTERRUPT_PRIORITY \
-  (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))
-
-#define INCLUDE_uxTaskPriorityGet 1
-#define INCLUDE_vTaskCleanUpResources 0
-#define INCLUDE_vTaskDelay 1
-#define INCLUDE_vTaskDelayUntil 0
-#define INCLUDE_vTaskDelete 1
-#define INCLUDE_vTaskPrioritySet 1
-#define INCLUDE_vTaskSuspend 1
-#define INCLUDE_xTaskGetSchedulerState 1
-
-// Instead of defining configASSERT(), include a header that provides a
-// definition that redirects to pw_assert.
-#include "pw_third_party/freertos/config_assert.h"
-
-#define vPortSVCHandler SVC_Handler
-#define xPortPendSVHandler PendSV_Handler
-#define xPortSysTickHandler SysTick_Handler
diff --git a/targets/stm32f429i_disc1_stm32cube/config/stm32f4xx_hal_conf.h b/targets/stm32f429i_disc1_stm32cube/config/stm32f4xx_hal_conf.h
deleted file mode 100644
index a3f2e8f..0000000
--- a/targets/stm32f429i_disc1_stm32cube/config/stm32f4xx_hal_conf.h
+++ /dev/null
@@ -1,278 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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
-
-/* Clock setup */
-#define HSI_VALUE 16000000U
-#define LSI_VALUE 32000U
-
-// The F429-disc has an 8Mhz external crystal
-#define HSE_VALUE 8000000U
-#define HSE_STARTUP_TIMEOUT 100U
-
-// The F429-disc has no LSE
-#define LSE_VALUE 0U
-#define LSE_STARTUP_TIMEOUT 5000U
-
-#define EXTERNAL_CLOCK_VALUE 0U
-
-/* HAL Config */
-#define TICK_INT_PRIORITY 0x0FU
-#define USE_RTOS 0U
-#define PREFETCH_ENABLE 1U
-#define INSTRUCTION_CACHE_ENABLE 1U
-#define DATA_CACHE_ENABLE 1U
-
-#define assert_param(expr) ((void)0U)
-
-/* Ethernet driver buffers size + count
- * (also used by FreeRTOS_Plus_TCP's stm32 driver) */
-#define ETH_RX_BUF_SIZE ETH_MAX_PACKET_SIZE
-#define ETH_TX_BUF_SIZE ETH_MAX_PACKET_SIZE
-#define ETH_RXBUFNB 4U
-#define ETH_TXBUFNB 4U
-
-/* Ethernet PHY Defines (unused by FreeRTOS_Plus_TCP's driver) */
-#define PHY_RESET_DELAY 0x000000FFU
-#define PHY_CONFIG_DELAY 0x00000FFFU
-
-#define PHY_READ_TO 0x0000FFFFU
-#define PHY_WRITE_TO 0x0000FFFFU
-
-/* Common PHY Registers */
-#define PHY_BCR ((uint16_t)0x0000)
-#define PHY_BSR ((uint16_t)0x0001)
-
-#define PHY_RESET ((uint16_t)0x8000)
-#define PHY_LOOPBACK ((uint16_t)0x4000)
-#define PHY_FULLDUPLEX_100M ((uint16_t)0x2100)
-#define PHY_HALFDUPLEX_100M ((uint16_t)0x2000)
-#define PHY_FULLDUPLEX_10M ((uint16_t)0x0100)
-#define PHY_HALFDUPLEX_10M ((uint16_t)0x0000)
-#define PHY_AUTONEGOTIATION ((uint16_t)0x1000)
-#define PHY_RESTART_AUTONEGOTIATION ((uint16_t)0x0200)
-#define PHY_POWERDOWN ((uint16_t)0x0800)
-#define PHY_ISOLATE ((uint16_t)0x0400)
-
-#define PHY_AUTONEGO_COMPLETE ((uint16_t)0x0020)
-#define PHY_LINKED_STATUS ((uint16_t)0x0004)
-#define PHY_JABBER_DETECTION ((uint16_t)0x0002)
-
-/* Extended PHY Registers */
-#define PHY_SR ((uint16_t)0x0010)
-#define PHY_MICR ((uint16_t)0x0011)
-#define PHY_MISR ((uint16_t)0x0012)
-
-#define PHY_LINK_STATUS ((uint16_t)0x0001)
-#define PHY_SPEED_STATUS ((uint16_t)0x0002)
-#define PHY_DUPLEX_STATUS ((uint16_t)0x0004)
-
-#define PHY_MICR_INT_EN ((uint16_t)0x0002)
-#define PHY_MICR_INT_OE ((uint16_t)0x0001)
-
-#define PHY_MISR_LINK_INT_EN ((uint16_t)0x0020)
-#define PHY_LINK_INTERRUPT ((uint16_t)0x2000)
-
-// SPI config
-#define USE_SPI_CRC 1U
-
-/** HAL Headers: comment out defines + include to remove **/
-/* primary HAL headers */
-#define HAL_CORTEX_MODULE_ENABLED
-#include "stm32f4xx_hal_cortex.h"
-
-#define HAL_DMA_MODULE_ENABLED
-#include "stm32f4xx_hal_dma.h"
-
-#define HAL_EXTI_MODULE_ENABLED
-#include "stm32f4xx_hal_exti.h"
-
-#define HAL_GPIO_MODULE_ENABLED
-#include "stm32f4xx_hal_gpio.h"
-
-#define HAL_RCC_MODULE_ENABLED
-#include "stm32f4xx_hal_rcc.h"
-
-/* remaining headers (can be commented out if desired) */
-#define HAL_ADC_MODULE_ENABLED
-#define USE_HAL_ADC_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_adc.h"
-
-#define HAL_CAN_MODULE_ENABLED
-#define USE_HAL_CAN_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_can.h"
-
-// #define HAL_CAN_LEGACY_MODULE_ENABLED
-// #include "stm32f4xx_hal_can_legacy.h"
-
-#define HAL_CEC_MODULE_ENABLED
-#define USE_HAL_CEC_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_cec.h"
-
-#define HAL_CRC_MODULE_ENABLED
-#include "stm32f4xx_hal_crc.h"
-
-#define HAL_CRYP_MODULE_ENABLED
-#define USE_HAL_CRYP_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_cryp.h"
-
-#define HAL_DAC_MODULE_ENABLED
-#define USE_HAL_DAC_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_dac.h"
-
-#define HAL_DCMI_MODULE_ENABLED
-#define USE_HAL_DCMI_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_dcmi.h"
-
-#define HAL_DMA2D_MODULE_ENABLED
-#define USE_HAL_DMA2D_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_dma2d.h"
-
-#define HAL_DFSDM_MODULE_ENABLED
-#define USE_HAL_DFSDM_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_dfsdm.h"
-
-#define HAL_DSI_MODULE_ENABLED
-#define USE_HAL_DSI_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_dsi.h"
-
-#define HAL_ETH_MODULE_ENABLED
-#define USE_HAL_ETH_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_eth.h"
-
-#define HAL_FLASH_MODULE_ENABLED
-#include "stm32f4xx_hal_flash.h"
-
-#define HAL_FMPI2C_MODULE_ENABLED
-#define USE_HAL_FMPI2C_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_fmpi2c.h"
-
-#define HAL_FMPSMBUS_MODULE_ENABLED
-#define USE_HAL_FMPSMBUS_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_fmpsmbus.h"
-
-#define HAL_HASH_MODULE_ENABLED
-#define USE_HAL_HASH_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_hash.h"
-
-#define HAL_HCD_MODULE_ENABLED
-#define USE_HAL_HCD_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_hcd.h"
-
-#define HAL_I2C_MODULE_ENABLED
-#define USE_HAL_I2C_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_i2c.h"
-
-#define HAL_I2S_MODULE_ENABLED
-#define USE_HAL_I2S_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_i2s.h"
-
-#define HAL_IRDA_MODULE_ENABLED
-#define USE_HAL_IRDA_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_irda.h"
-
-#define HAL_IWDG_MODULE_ENABLED
-#include "stm32f4xx_hal_iwdg.h"
-
-#define HAL_LPTIM_MODULE_ENABLED
-#define USE_HAL_LPTIM_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_lptim.h"
-
-#define HAL_LTDC_MODULE_ENABLED
-#define USE_HAL_LTDC_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_ltdc.h"
-
-#define HAL_MMC_MODULE_ENABLED
-#define USE_HAL_MMC_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_mmc.h"
-
-#define HAL_NAND_MODULE_ENABLED
-#define USE_HAL_NAND_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_nand.h"
-
-#define HAL_NOR_MODULE_ENABLED
-#define USE_HAL_NOR_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_nor.h"
-
-#define HAL_PCCARD_MODULE_ENABLED
-#define USE_HAL_PCCARD_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_pccard.h"
-
-#define HAL_PCD_MODULE_ENABLED
-#define USE_HAL_PCD_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_pcd.h"
-
-#define HAL_PWR_MODULE_ENABLED
-#include "stm32f4xx_hal_pwr.h"
-
-#define HAL_QSPI_MODULE_ENABLED
-#define USE_HAL_QSPI_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_qspi.h"
-
-#define HAL_RNG_MODULE_ENABLED
-#define USE_HAL_RNG_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_rng.h"
-
-#define HAL_RTC_MODULE_ENABLED
-#define USE_HAL_RTC_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_rtc.h"
-
-#define HAL_SAI_MODULE_ENABLED
-#define USE_HAL_SAI_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_sai.h"
-
-#define HAL_SD_MODULE_ENABLED
-#define USE_HAL_SD_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_sd.h"
-
-#define HAL_SDRAM_MODULE_ENABLED
-#define USE_HAL_SDRAM_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_sdram.h"
-
-#define HAL_SMARTCARD_MODULE_ENABLED
-#define USE_HAL_SMARTCARD_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_smartcard.h"
-
-#define HAL_SMBUS_MODULE_ENABLED
-#define USE_HAL_SMBUS_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_smbus.h"
-
-#define HAL_SPDIFRX_MODULE_ENABLED
-#define USE_HAL_SPDIFRX_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_spdifrx.h"
-
-#define HAL_SPI_MODULE_ENABLED
-#define USE_HAL_SPI_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_spi.h"
-
-#define HAL_SRAM_MODULE_ENABLED
-#define USE_HAL_SRAM_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_sram.h"
-
-#define HAL_TIM_MODULE_ENABLED
-#define USE_HAL_TIM_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_tim.h"
-
-#define HAL_UART_MODULE_ENABLED
-#define USE_HAL_UART_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_uart.h"
-
-#define HAL_USART_MODULE_ENABLED
-#define USE_HAL_USART_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_usart.h"
-
-#define HAL_WWDG_MODULE_ENABLED
-#define USE_HAL_WWDG_REGISTER_CALLBACKS 0U
-#include "stm32f4xx_hal_wwdg.h"
diff --git a/targets/stm32f429i_disc1_stm32cube/target_docs.rst b/targets/stm32f429i_disc1_stm32cube/target_docs.rst
deleted file mode 100644
index e7e4622..0000000
--- a/targets/stm32f429i_disc1_stm32cube/target_docs.rst
+++ /dev/null
@@ -1,68 +0,0 @@
-.. _target-stm32f429i-disc1-stm32cube:
-
----------------------------
-stm32f429i-disc1: STM32Cube
----------------------------
-.. warning::
-  This target is in a very preliminary state and is under active development.
-  This demo gives a preview of the direction we are heading with
-  :ref:`pw_system<module-pw_system>`, but it is not yet ready for production
-  use.
-
-
-The STMicroelectronics STM32F429I-DISC1 development board is currently Pigweed's
-primary target for on-device testing and development. This target configuration
-uses :ref:`pw_system<module-pw_system>` on top of FreeRTOS and the STM32Cube HAL
-rather than a from-the-ground-up baremetal approach.
-
------
-Setup
------
-To use this target, Pigweed must be set up to use FreeRTOS and the STM32Cube HAL
-for the STM32F4 series. The supported repositories can be downloaded via
-``pw package``, and then the build must be manually configured to point to the
-locations the repositories were downloaded to.
-
-.. code:: sh
-
-  pw package install freertos
-  pw package install stm32cube_f4
-  pw package install nanopb
-
-  gn args out
-    # Add these lines, replacing ${PW_ROOT} with the path to the location that
-    # Pigweed is checked out at.
-    dir_pw_third_party_freertos = "${PW_ROOT}/.environment/packages/freertos"
-    dir_pw_third_party_stm32cube_f4 = "${PW_ROOT}/.environment/packages/stm32cube_f4"
-    dir_pw_third_party_nanopb = "${PW_ROOT}/.environment/packages/nanopb"
-
-Building and running the demo
-=============================
-This target has an associated demo application that can be built and then
-flashed to a device with the following commands:
-
-.. code:: sh
-
-  ninja -C out pw_system_demo
-
-  openocd -f targets/stm32f429i_disc1/py/stm32f429i_disc1_utils/openocd_stm32f4xx.cfg -c "program out/stm32f429i_disc1_stm32cube.size_optimized/obj/pw_system/bin/system_example.elf reset exit"
-
-Once the board has been flashed, you can connect to it and send RPC commands
-via the Pigweed console:
-
-.. code:: sh
-
-  pw-system-console -d /dev/{ttyX} -b 115200 --proto-globs pw_rpc/echo.proto --token-databases out/stm32f429i_disc1_stm32cube.size_optimized/obj/pw_system/bin/system_example.elf
-
-Replace ``{ttyX}`` with the appropriate device on your machine. On Linux this
-may look like ``ttyACM0``, and on a Mac it may look like ``cu.usbmodem***``.
-
-When the console opens, try sending an Echo RPC request. You should get back
-the same message you sent to the device.
-
-.. code:: sh
-
-  >>> device.rpcs.pw.rpc.EchoService.Echo(msg="Hello, Pigweed!")
-  (Status.OK, pw.rpc.EchoMessage(msg='Hello, Pigweed!'))
-
-You are now up and running!
diff --git a/targets/stm32f429i_disc1_stm32cube/vector_table.c b/targets/stm32f429i_disc1_stm32cube/vector_table.c
deleted file mode 100644
index bf6e886..0000000
--- a/targets/stm32f429i_disc1_stm32cube/vector_table.c
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <stdbool.h>
-
-#include "pw_boot/boot.h"
-#include "pw_boot_cortex_m/boot.h"
-#include "pw_preprocessor/compiler.h"
-#include "stm32f4xx.h"
-
-// Default handler to insert into the ARMv7-M vector table (below).
-// This function exists for convenience. If a device isn't doing what you
-// expect, it might have hit a fault and ended up here.
-static void DefaultFaultHandler(void) {
-  while (true) {
-    // Wait for debugger to attach.
-  }
-}
-
-// This is the device's interrupt vector table. It's not referenced in any
-// code because the platform (STM32F4xx) expects this table to be present at the
-// beginning of flash. The exact address is specified in the pw_boot_armv7m
-// configuration as part of the target config.
-//
-// For more information, see ARMv7-M Architecture Reference Manual DDI 0403E.b
-// section B1.5.3.
-
-// This typedef is for convenience when building the vector table. With the
-// exception of SP_main (0th entry in the vector table), all the entries of the
-// vector table are function pointers.
-typedef void (*InterruptHandler)(void);
-
-// This is the timer interrupt handler implemented by the stm32cubef4 timer
-// template.
-void TIM6_DAC_IRQHandler(void);
-
-// Interrupt handlers critical for OS operation.
-void SVC_Handler(void);
-void PendSV_Handler(void);
-void SysTick_Handler(void);
-
-PW_KEEP_IN_SECTION(".vector_table")
-const InterruptHandler vector_table[] = {
-    // The starting location of the stack pointer.
-    // This address is NOT an interrupt handler/function pointer, it is simply
-    // the address that the main stack pointer should be initialized to. The
-    // value is reinterpret casted because it needs to be in the vector table.
-    [0] = (InterruptHandler)(&pw_boot_stack_high_addr),
-
-    // Reset handler, dictates how to handle reset interrupt. This is the
-    // address that the Program Counter (PC) is initialized to at boot.
-    [1] = pw_boot_Entry,
-
-    // NMI handler.
-    [2] = DefaultFaultHandler,
-    // HardFault handler.
-    [3] = DefaultFaultHandler,
-    // 4-6: Specialized fault handlers.
-    // 7-10: Reserved.
-    // SVCall handler.
-    [11] = SVC_Handler,
-    // DebugMon handler.
-    [12] = DefaultFaultHandler,
-    // 13: Reserved.
-    // PendSV handler.
-    [14] = PendSV_Handler,
-    // SysTick handler.
-    [15] = SysTick_Handler,
-    // stm32f4xx_hal sys-tick handler.
-    [TIM6_DAC_IRQn + 16] = TIM6_DAC_IRQHandler,
-};
diff --git a/third_party/arduino/BUILD b/third_party/arduino/BUILD
new file mode 100644
index 0000000..f7a092c
--- /dev/null
+++ b/third_party/arduino/BUILD
@@ -0,0 +1,22 @@
+# Copyright 2020 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
diff --git a/third_party/arduino/BUILD.bazel b/third_party/arduino/BUILD.bazel
deleted file mode 100644
index 75de8d2..0000000
--- a/third_party/arduino/BUILD.bazel
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
diff --git a/third_party/arduino/OWNERS b/third_party/arduino/OWNERS
deleted file mode 100644
index ca011e8..0000000
--- a/third_party/arduino/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-tonymd@google.com
diff --git a/third_party/boringssl/BUILD.bazel b/third_party/boringssl/BUILD.bazel
deleted file mode 100644
index e0051e3..0000000
--- a/third_party/boringssl/BUILD.bazel
+++ /dev/null
@@ -1,28 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-#
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-pw_cc_library(
-    name = "sysdeps",
-    srcs = ["crypto_sysrand.cc"],
-    hdrs = ["sysdeps/sys/socket.h"],
-    includes = ["sysdeps"],
-)
-
-# TODO(zyecheng): Add build recipes for BoringSSL
diff --git a/third_party/boringssl/BUILD.gn b/third_party/boringssl/BUILD.gn
deleted file mode 100644
index af41f28..0000000
--- a/third_party/boringssl/BUILD.gn
+++ /dev/null
@@ -1,93 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_third_party/boringssl/boringssl.gni")
-import("$dir_pw_unit_test/test.gni")
-
-if (dir_pw_third_party_boringssl != "") {
-  import("$dir_pw_third_party_boringssl/BUILD.generated.gni")
-
-  config("boringssl_public_config") {
-    include_dirs = [
-      "$dir_pw_third_party_boringssl/src/include",
-      "public",
-    ]
-    cflags = [
-      "-Wno-cast-qual",
-      "-Wno-ignored-qualifiers",
-      "-w",
-    ]
-
-    # This can be removed once boringssl threading primitives are implemented,
-    # i.e. using pw_sync, and when we have a posix style socket layer.
-    defines =
-        [ "OPENSSL_NO_THREADS_CORRUPT_MEMORY_AND_LEAK_SECRETS_IF_THREADED" ]
-  }
-
-  config("boringssl_internal_config") {
-    defines = [
-      # Enable virtual desctructor and compile-time check of pure virtual base class
-      "BORINGSSL_ALLOW_CXX_RUNTIME",
-
-      # Code size optimiaztion
-      "OPENSSL_SMALL",
-
-      # The ARM assembly code is only for cortex-A.
-      "OPENSSL_NO_ASM",
-
-      # Disable assert, which may additionally link in unwanted binaries via
-      # argument evaluation.
-      "NDEBUG",
-    ]
-    cflags = [
-      "-Wno-unused-function",
-      "-Wno-conversion",
-      "-Wno-unused-parameter",
-      "-Wno-char-subscripts",
-    ]
-    cflags_cc = [
-      "-fpermissive",
-      "-Wno-error",  # To get through the -Werror=permissive error
-    ]
-    include_dirs = [ "$dir_pw_third_party_boringssl" ]
-  }
-
-  # Remove sources that require file system and posix socket support
-  excluded_sources = [
-    "src/crypto/bio/connect.c",
-    "src/crypto/bio/fd.c",
-    "src/crypto/bio/socket.c",
-    "src/crypto/bio/socket_helper.c",
-  ]
-
-  pw_source_set("boringssl") {
-    sources = [ "crypto_sysrand.cc" ]
-    foreach(source, crypto_sources - excluded_sources + ssl_sources) {
-      sources += [ "$dir_pw_third_party_boringssl/$source" ]
-    }
-    public_configs = [ ":boringssl_public_config" ]
-    configs = [ ":boringssl_internal_config" ]
-
-    # Contains a faked "sysdeps/sys/socket.h"
-    # Can be removed once posix socket layer in Pigweed is supported.
-    include_dirs = [ "sysdeps" ]
-
-    public_deps = [ "$dir_pw_tls_client:time" ]
-  }
-} else {
-  group("boringssl") {
-  }
-}
diff --git a/third_party/boringssl/OWNERS b/third_party/boringssl/OWNERS
deleted file mode 100644
index 121c6f5..0000000
--- a/third_party/boringssl/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-zyecheng@google.com
diff --git a/third_party/boringssl/README.md b/third_party/boringssl/README.md
deleted file mode 100644
index 0d3e06d..0000000
--- a/third_party/boringssl/README.md
+++ /dev/null
@@ -1,10 +0,0 @@
-# BoringSSL Library
-
-The folder provides build scripts for building the BoringSSL library. The
-source code needs to be downloaded by the user. It is recommended to download
-via "pw package install boringssl". This ensures that necessary build files
-are generated. It als downloads the chromium verifier library, which will be
-used as the default certificate verifier for boringssl in pw_tls_client.
-For gn build, set `dir_pw_third_party_boringssl` to point to the
-path of the source code. For applications using BoringSSL, add
-`$dir_pw_third_party/boringssl` to the dependency list.
diff --git a/third_party/boringssl/boringssl.gni b/third_party/boringssl/boringssl.gni
deleted file mode 100644
index 12f7f6b..0000000
--- a/third_party/boringssl/boringssl.gni
+++ /dev/null
@@ -1,20 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-declare_args() {
-  # If compiling backends with boringssl, this variable is set to the path to the
-  # boringssl source code. When set, a pw_source_set for the boringssl library is
-  # created at "$dir_pw_third_party/boringssl".
-  dir_pw_third_party_boringssl = ""
-}
diff --git a/third_party/boringssl/crypto_sysrand.cc b/third_party/boringssl/crypto_sysrand.cc
deleted file mode 100644
index 34fe1bb..0000000
--- a/third_party/boringssl/crypto_sysrand.cc
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "src/crypto/fipsmodule/rand/internal.h"
-
-extern "C" {
-// OPENSSL_URANDOM is defined automatically based on platform flags.
-// See crypto/fipsmodule/rand/internal.h
-#ifdef OPENSSL_URANDOM
-// When OPENSSL_URANDOM is defined, boringssl assumes linux and
-// reads from "dev/urandom" for generating randoms bytes.
-// We mock the required file io functions to accomodate it for now.
-// TODO(zyecheng): Ask BoringSSL team if there are ways to disable
-// OPENSSL_URANDOM, potentially by adding a OPENSSL_PIGWEED flag in
-// crypto/fipsmodule/rand/internal.h. If not, we need to keep these
-// mockings.
-
-#define URANDOM_FILE_FD 123
-int open(const char* file, int, ...) {
-  if (strcmp(file, "/dev/urandom") == 0) {
-    return URANDOM_FILE_FD;
-  }
-  return -1;
-}
-
-ssize_t read(int fd, void*, size_t len) {
-  if (fd == URANDOM_FILE_FD) {
-    // TODO(zyecheng): Add code to generate random bytes.
-  }
-  return static_cast<ssize_t>(len);
-}
-
-#else
-// When OPENSSL_URANDOM is not defined, BoringSSL expects an implementation of
-// the following function for generating random bytes.
-void CRYPTO_sysrand(uint8_t*, size_t) {
-  // TODO(zyecheng): Add code to generate random bytes.
-}
-#endif
-}
diff --git a/third_party/boringssl/sysdeps/sys/socket.h b/third_party/boringssl/sysdeps/sys/socket.h
deleted file mode 100644
index 9ba1f9f..0000000
--- a/third_party/boringssl/sysdeps/sys/socket.h
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// Nothing. For place-holder only.
-
-#pragma once
diff --git a/third_party/chromium_verifier/BUILD.bazel b/third_party/chromium_verifier/BUILD.bazel
deleted file mode 100644
index c644d3c..0000000
--- a/third_party/chromium_verifier/BUILD.bazel
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-pw_cc_library(
-    name = "pthread",
-    hdrs = [
-        "public/chromium_verifier/pthread/pthread.h",
-    ],
-    includes = ["public/chromium_verifier/pthread"],
-)
diff --git a/third_party/chromium_verifier/BUILD.gn b/third_party/chromium_verifier/BUILD.gn
deleted file mode 100644
index a89f3b7..0000000
--- a/third_party/chromium_verifier/BUILD.gn
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-import("$dir_pw_build/target_types.gni")
-import("chromium_verifier.gni")
-
-if (dir_pw_third_party_chromium_verifier != "") {
-  # A pthread definition layer for systems without POSIX thread implementation.
-  config("pthread_config") {
-    include_dirs = [ "public/chromium_verifier/pthread" ]
-  }
-
-  pw_source_set("pthread") {
-    public_configs = [ ":pthread_config" ]
-    public = [ "public/chromium_verifier/pthread/pthread.h" ]
-  }
-
-  # TODO(pwbug/394): Add build support for chromium verifier.
-  # TODO(pwbug/394): Port related chromium native unittests. Then add instruction
-  # for rolling.
-} else {
-  group("chromium_verifier") {
-  }
-}
diff --git a/third_party/chromium_verifier/OWNERS b/third_party/chromium_verifier/OWNERS
deleted file mode 100644
index 121c6f5..0000000
--- a/third_party/chromium_verifier/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-zyecheng@google.com
diff --git a/third_party/chromium_verifier/README.md b/third_party/chromium_verifier/README.md
deleted file mode 100644
index 1f67ee9..0000000
--- a/third_party/chromium_verifier/README.md
+++ /dev/null
@@ -1,30 +0,0 @@
-# Chrome Certificate Verifier Library
-
-The folder provides targets for building the certificate verifier used by
-chromium. The sources live in the chromium source repo. It is recommended
-to download the repo via `pw package install chromium_verifier`, which
-performs a sparse checkout instead of checking out the who repo. For gn build,
-set `dir_pw_third_party_chromium_verifier` to point to the repo path. The
-library requires `third_party/boringssl` and need to be setup first. See
-`third_party/boringssl/README.md` for instruction. The library will primarily
-be used by pw_tls_client when using boringssl backend.
-
-The verifier we build for embedded target excludes the chromium metric feature.
-Specifically, for the current port, we use a noop implementation for function
-`UmaHistogramCounts10000()`. The function is originally used to generate
-histograms that record iteration count. For the verifier, the iteration count
-is only used in unittest. Compiling the feature requires to bring in a
-significant amount of additional sources and also many system dependencies
-including threading, file system, memory mapping management (sys/mman.h) etc.
-It's too complicated to accomodate for embedded target.
-
-However we do build a full version including the metric feature on Linux host
-platform for running native unittest, as a criterion for rolling.
-
-Certain chromium sources include header `pthread.h` and use data type and
-functions such as `pthread_t`, `pthread_mutex_lock` etc. Although the code
-the verifier executes has no reference to them, they are still needed for
-compilation. If the target platform does not have a native POSIX thread
-implementation, we provide a `pthread.h` that declares the needed data types
-and functions for build. For GN builds, simply set
-`pw_third_party_chromium_verifier_HAS_NATIVE_PTHREAD` to false.
diff --git a/third_party/chromium_verifier/chromium_verifier.gni b/third_party/chromium_verifier/chromium_verifier.gni
deleted file mode 100644
index 477bd58..0000000
--- a/third_party/chromium_verifier/chromium_verifier.gni
+++ /dev/null
@@ -1,28 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-declare_args() {
-  # The path to chromium source. Repo must be available at
-  # "$dir_pw_third_party_chromium_verifier/src". It is recommended to install
-  # the package via "pw package install chromium_verifier"
-  dir_pw_third_party_chromium_verifier = ""
-
-  # Specify whether the target platform has native POSIX thread implementation.
-  # Our current porting of chromium verifier doesn't require to link in any of the
-  # pthread APIs, but some declarations and type definitions, such as pthread_t,
-  # pthread_mutex_t and pthread_mutex_lock, are still needed for compilation.
-  # If this is set to false, the module will provide a pthread.h header that
-  # defines the needed types.
-  pw_third_party_chromium_verifier_HAS_NATIVE_PTHREAD = true
-}
diff --git a/third_party/chromium_verifier/public/chromium_verifier/pthread/pthread.h b/third_party/chromium_verifier/public/chromium_verifier/pthread/pthread.h
deleted file mode 100644
index e43b7a1..0000000
--- a/third_party/chromium_verifier/public/chromium_verifier/pthread/pthread.h
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <stdint.h>
-
-// The following declares a number of pthread APIs and data types needed to
-// compile chromium verifier. But our port doesn't have any referrence to
-// them. Thus just declarations are sufficient. The header will only be
-// used for targets without a native POSIX thread implementation.
-
-typedef unsigned long int pthread_t;
-typedef uint32_t pthread_key_t;
-typedef uint32_t pthread_mutex_t;
-
-#if __cplusplus
-extern "C" {
-#endif
-
-int pthread_mutex_lock(pthread_mutex_t* __mutex);
-int pthread_mutex_trylock(pthread_mutex_t*);
-int pthread_mutex_unlock(pthread_mutex_t*);
-
-#if __cplusplus
-}
-#endif
diff --git a/third_party/chromium_verifier/sources.gni b/third_party/chromium_verifier/sources.gni
deleted file mode 100644
index 850262b..0000000
--- a/third_party/chromium_verifier/sources.gni
+++ /dev/null
@@ -1,149 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-#
-# The file is auto-generated when chromium verifier is installed from pw_package.
-# See //pw_package/py/pw_package/packages/boringssl.py for more detail.
-
-# The list only includes sources that implement core verifier logic.
-chromium_verifier_sources_minimal = [
-  "base/location.cc",
-  "base/memory/ref_counted.cc",
-  "base/strings/string_number_conversions.cc",
-  "base/strings/string_split.cc",
-  "base/strings/string_util.cc",
-  "base/strings/stringprintf.cc",
-  "base/strings/utf_string_conversion_utils.cc",
-  "base/strings/utf_string_conversions.cc",
-  "base/supports_user_data.cc",
-  "base/time/time.cc",
-  "crypto/openssl_util.cc",
-  "net/base/ip_address.cc",
-  "net/cert/internal/cert_error_id.cc",
-  "net/cert/internal/cert_error_params.cc",
-  "net/cert/internal/cert_errors.cc",
-  "net/cert/internal/cert_issuer_source_static.cc",
-  "net/cert/internal/certificate_policies.cc",
-  "net/cert/internal/common_cert_errors.cc",
-  "net/cert/internal/extended_key_usage.cc",
-  "net/cert/internal/general_names.cc",
-  "net/cert/internal/name_constraints.cc",
-  "net/cert/internal/parse_certificate.cc",
-  "net/cert/internal/parse_name.cc",
-  "net/cert/internal/parsed_certificate.cc",
-  "net/cert/internal/path_builder.cc",
-  "net/cert/internal/signature_algorithm.cc",
-  "net/cert/internal/simple_path_builder_delegate.cc",
-  "net/cert/internal/trust_store.cc",
-  "net/cert/internal/trust_store_collection.cc",
-  "net/cert/internal/trust_store_in_memory.cc",
-  "net/cert/internal/verify_certificate_chain.cc",
-  "net/cert/internal/verify_name_match.cc",
-  "net/cert/internal/verify_signed_data.cc",
-  "net/der/encode_values.cc",
-  "net/der/input.cc",
-  "net/der/parse_values.cc",
-  "net/der/parser.cc",
-  "net/der/tag.cc",
-]
-
-# Chromium verifier has some metric feature that records iteration count.
-# It is only used for test. However, compiling the feature requires to bring in
-# a significant amount of additional sources and also many system dependencies
-# including threading, file system, memory mapping management (sys/mman.h) etc. It's
-# too complicated to accomodate for embedded target. Thus for now, we'll only build
-# it on host for running native unittests, as a criterion for rolling. The following
-# is the list of sources that need to be built for running the test in
-# "net/cert/internal/path_builder_unittest.cc"
-chromium_verifier_sources_extended = [
-  "base/at_exit.cc",
-  "base/base64.cc",
-  "base/base_paths.cc",
-  "base/base_paths_posix.cc",
-  "base/base_switches.cc",
-  "base/callback_internal.cc",
-  "base/command_line.cc",
-  "base/debug/activity_tracker.cc",
-  "base/debug/alias.cc",
-  "base/debug/stack_trace.cc",
-  "base/debug/stack_trace_posix.cc",
-  "base/deferred_sequenced_task_runner.cc",
-  "base/environment.cc",
-  "base/files/file.cc",
-  "base/files/file_path.cc",
-  "base/files/file_path_constants.cc",
-  "base/files/file_posix.cc",
-  "base/files/file_tracing.cc",
-  "base/files/file_util.cc",
-  "base/files/file_util_posix.cc",
-  "base/files/memory_mapped_file.cc",
-  "base/files/memory_mapped_file_posix.cc",
-  "base/files/scoped_file.cc",
-  "base/files/scoped_file_linux.cc",
-  "base/hash/md5_boringssl.cc",
-  "base/lazy_instance_helpers.cc",
-  "base/memory/page_size_posix.cc",
-  "base/memory/shared_memory_mapping.cc",
-  "base/memory/shared_memory_security_policy.cc",
-  "base/memory/shared_memory_tracker.cc",
-  "base/metrics/bucket_ranges.cc",
-  "base/metrics/crc32.cc",
-  "base/metrics/dummy_histogram.cc",  # inclusive-language: disable
-  "base/metrics/histogram.cc",
-  "base/metrics/histogram_base.cc",
-  "base/metrics/histogram_functions.cc",
-  "base/metrics/histogram_samples.cc",
-  "base/metrics/metrics_hashes.cc",
-  "base/metrics/persistent_histogram_allocator.cc",
-  "base/metrics/persistent_memory_allocator.cc",
-  "base/metrics/persistent_sample_map.cc",
-  "base/metrics/sample_map.cc",
-  "base/metrics/sample_vector.cc",
-  "base/metrics/sparse_histogram.cc",
-  "base/metrics/statistics_recorder.cc",
-  "base/nix/xdg_util.cc",
-  "base/observer_list_threadsafe.cc",
-  "base/path_service.cc",
-  "base/pickle.cc",
-  "base/posix/can_lower_nice_to.cc",
-  "base/process/process_handle_posix.cc",
-  "base/process/process_metrics_linux.cc",
-  "base/strings/strcat.cc",
-  "base/strings/string_piece.cc",
-  "base/strings/string_util_constants.cc",
-  "base/synchronization/lock_impl_posix.cc",
-  "base/task/scoped_set_task_priority_for_current_thread.cc",
-  "base/task/thread_pool.cc",
-  "base/task/thread_pool/environment_config.cc",
-  "base/task/thread_pool/thread_pool_instance.cc",
-  "base/task_runner.cc",
-  "base/test/metrics/histogram_tester.cc",
-  "base/third_party/xdg_user_dirs/xdg_user_dir_lookup.cc",
-  "base/threading/platform_thread_internal_posix.cc",
-  "base/threading/platform_thread_linux.cc",
-  "base/threading/platform_thread_posix.cc",
-  "base/threading/scoped_blocking_call.cc",
-  "base/threading/scoped_blocking_call_internal.cc",
-  "base/threading/thread_id_name_manager.cc",
-  "base/threading/thread_local_storage.cc",
-  "base/threading/thread_local_storage_posix.cc",
-  "base/trace_event/trace_event_stub.cc",
-  "base/values.cc",
-  "net/cert/internal/test_helpers.cc",
-  "net/cert/pem.cc",
-  "third_party/modp_b64/modp_b64.cc",
-]
-
-# Unittest sources.
-chromium_verifier_unittest_sources =
-    [ "net/cert/internal/path_builder_unittest.cc" ]
diff --git a/third_party/embos/OWNERS b/third_party/embos/OWNERS
deleted file mode 100644
index 21d24bc..0000000
--- a/third_party/embos/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-ewout@google.com
diff --git a/third_party/freertos/BUILD.bazel b/third_party/freertos/BUILD.bazel
deleted file mode 100644
index f635a0f..0000000
--- a/third_party/freertos/BUILD.bazel
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "config_assert",
-    hdrs = [
-        "public/pw_third_party/freertos/config_assert.h",
-    ],
-    includes = ["public"],
-    deps = [
-        "//pw_assert",
-    ],
-)
diff --git a/third_party/freertos/BUILD.gn b/third_party/freertos/BUILD.gn
index 48749cc..9b30c06 100644
--- a/third_party/freertos/BUILD.gn
+++ b/third_party/freertos/BUILD.gn
@@ -14,177 +14,62 @@
 
 import("//build_overrides/pigweed.gni")
 
-import("$dir_pw_build/error.gni")
 import("$dir_pw_build/target_types.gni")
-import("$dir_pw_docgen/docs.gni")
 import("freertos.gni")
 
 # This file defines a GN source_set for an external installation of freertos.
 # To use, checkout the freertos source into a directory, then set the build arg
 # dir_pw_third_party_freertos to point to that directory. The freertos library
 # will be available in GN at "$dir_pw_third_party/freertos".
-if (dir_pw_third_party_freertos == "") {
-  group("freertos") {
-  }
-} else if (pw_third_party_freertos_PORT == "") {
-  pw_error("freertos") {
-    message_lines = [
-      "FreeRTOS is being used by $current_toolchain, but pw_third_party_freertos_PORT is not set.",
-      "If this toolchain is intentionally using FreeRTOS, ensure your toolchain configuration for this target sets pw_third_party_freertos_PORT.",
-    ]
-  }
-} else if (pw_third_party_freertos_CONFIG == "") {
-  pw_error("freertos") {
-    message_lines = [
-      "FreeRTOS is being used by $current_toolchain, but pw_third_party_freertos_CONFIG is not set.",
-      "If this toolchain is intentionally using FreeRTOS, ensure your toolchain configuration for this target sets pw_third_party_freertos_CONFIG.",
-    ]
-  }
-} else {
+if (dir_pw_third_party_freertos != "") {
   config("disable_warnings") {
-    cflags = [
-      "-Wno-unused-parameter",
-      "-Wno-cast-qual",
-    ]
+    cflags = [ "-Wno-error=unused-parameter" ]
     visibility = [ ":*" ]
   }
 
   config("public_includes") {
-    include_dirs = [ "$dir_pw_third_party_freertos/include" ]
+    include_dirs = [ "Source/include" ]
     visibility = [ ":*" ]
   }
 
   pw_source_set("freertos") {
     public_configs = [ ":public_includes" ]
-    allow_circular_includes_from = [
-      pw_third_party_freertos_PORT,
-      ":freertos_tasks",
-    ]
+    allow_circular_includes_from = [ pw_third_party_freertos_PORT ]
     public_deps = [
       pw_third_party_freertos_CONFIG,
       pw_third_party_freertos_PORT,
     ]
     public = [
-      "$dir_pw_third_party_freertos/include/FreeRTOS.h",
-      "$dir_pw_third_party_freertos/include/StackMacros.h",
-      "$dir_pw_third_party_freertos/include/croutine.h",
-      "$dir_pw_third_party_freertos/include/deprecated_definitions.h",
-      "$dir_pw_third_party_freertos/include/event_groups.h",
-      "$dir_pw_third_party_freertos/include/list.h",
-      "$dir_pw_third_party_freertos/include/message_buffer.h",
-      "$dir_pw_third_party_freertos/include/mpu_prototypes.h",
-      "$dir_pw_third_party_freertos/include/mpu_wrappers.h",
-      "$dir_pw_third_party_freertos/include/portable.h",
-      "$dir_pw_third_party_freertos/include/projdefs.h",
-      "$dir_pw_third_party_freertos/include/queue.h",
-      "$dir_pw_third_party_freertos/include/semphr.h",
-      "$dir_pw_third_party_freertos/include/stack_macros.h",
-      "$dir_pw_third_party_freertos/include/stream_buffer.h",
-      "$dir_pw_third_party_freertos/include/task.h",
-      "$dir_pw_third_party_freertos/include/timers.h",
+      "$dir_pw_third_party_freertos/Source/include/FreeRTOS.h",
+      "$dir_pw_third_party_freertos/Source/include/StackMacros.h",
+      "$dir_pw_third_party_freertos/Source/include/croutine.h",
+      "$dir_pw_third_party_freertos/Source/include/deprecated_definitions.h",
+      "$dir_pw_third_party_freertos/Source/include/event_groups.h",
+      "$dir_pw_third_party_freertos/Source/include/list.h",
+      "$dir_pw_third_party_freertos/Source/include/message_buffer.h",
+      "$dir_pw_third_party_freertos/Source/include/mpu_prototypes.h",
+      "$dir_pw_third_party_freertos/Source/include/mpu_wrappers.h",
+      "$dir_pw_third_party_freertos/Source/include/portable.h",
+      "$dir_pw_third_party_freertos/Source/include/projdefs.h",
+      "$dir_pw_third_party_freertos/Source/include/queue.h",
+      "$dir_pw_third_party_freertos/Source/include/semphr.h",
+      "$dir_pw_third_party_freertos/Source/include/stack_macros.h",
+      "$dir_pw_third_party_freertos/Source/include/stream_buffer.h",
+      "$dir_pw_third_party_freertos/Source/include/task.h",
+      "$dir_pw_third_party_freertos/Source/include/timers.h",
     ]
     configs = [ ":disable_warnings" ]
     sources = [
-      "$dir_pw_third_party_freertos/croutine.c",
-      "$dir_pw_third_party_freertos/event_groups.c",
-      "$dir_pw_third_party_freertos/list.c",
-      "$dir_pw_third_party_freertos/queue.c",
-      "$dir_pw_third_party_freertos/stream_buffer.c",
-      "$dir_pw_third_party_freertos/timers.c",
-    ]
-    deps = [ ":freertos_tasks" ]
-  }
-
-  # In order to link against internal kernel data structures through the use of
-  # extern "C", statics can be optionally disabled for the tasks.c source file
-  # to enable use of things like pw_thread_freertos/util.h's ForEachThread.
-  config("disable_statics") {
-    cflags = [
-      "-Dstatic=",
-      "-DPW_THIRD_PARTY_FREERTOS_NO_STATICS=1",
-    ]
-    visibility = [ ":*" ]
-  }
-
-  pw_source_set("freertos_tasks") {
-    public_configs = [ ":public_includes" ]
-    configs = [ ":disable_warnings" ]
-    if (pw_third_party_freertos_DISABLE_TASKS_STATICS) {
-      configs += [ ":disable_statics" ]
-    }
-    sources = [ "$dir_pw_third_party_freertos/tasks.c" ]
-    deps = [
-      pw_third_party_freertos_CONFIG,
-      pw_third_party_freertos_PORT,
+      "$dir_pw_third_party_freertos/Source/croutine.c",
+      "$dir_pw_third_party_freertos/Source/event_groups.c",
+      "$dir_pw_third_party_freertos/Source/list.c",
+      "$dir_pw_third_party_freertos/Source/queue.c",
+      "$dir_pw_third_party_freertos/Source/stream_buffer.c",
+      "$dir_pw_third_party_freertos/Source/tasks.c",
+      "$dir_pw_third_party_freertos/Source/timers.c",
     ]
   }
-
-  # ARM CM7 port of FreeRTOS
-  config("arm_cm7_includes") {
-    include_dirs = [ "$dir_pw_third_party_freertos/portable/GCC/ARM_CM7/r0p1" ]
-    visibility = [ ":arm_cm7" ]
+} else {
+  group("freertos") {
   }
-
-  pw_source_set("arm_cm7") {
-    public_configs = [
-      ":arm_cm7_includes",
-      ":public_includes",
-    ]
-    public_deps = [ pw_third_party_freertos_CONFIG ]
-    public =
-        [ "$dir_pw_third_party_freertos/portable/GCC/ARM_CM7/r0p1/portmacro.h" ]
-    sources =
-        [ "$dir_pw_third_party_freertos/portable/GCC/ARM_CM7/r0p1/port.c" ]
-    configs = [ ":disable_warnings" ]
-  }
-
-  # ARM CM4F port of FreeRTOS.
-  config("arm_cm4f_includes") {
-    include_dirs = [ "$dir_pw_third_party_freertos/portable/GCC/ARM_CM4F" ]
-    visibility = [ ":arm_cm4f" ]
-  }
-
-  pw_source_set("arm_cm4f") {
-    public_configs = [
-      ":arm_cm4f_includes",
-      ":public_includes",
-    ]
-    public_deps = [ pw_third_party_freertos_CONFIG ]
-    public =
-        [ "$dir_pw_third_party_freertos/portable/GCC/ARM_CM4F/portmacro.h" ]
-    sources = [ "$dir_pw_third_party_freertos/portable/GCC/ARM_CM4F/port.c" ]
-    configs = [ ":disable_warnings" ]
-  }
-
-  # ARM CM3 port of FreeRTOS.
-  config("arm_cm3_includes") {
-    include_dirs = [ "$dir_pw_third_party_freertos/portable/GCC/ARM_CM3" ]
-    visibility = [ ":arm_cm3" ]
-  }
-
-  pw_source_set("arm_cm3") {
-    public_configs = [
-      ":arm_cm3_includes",
-      ":public_includes",
-    ]
-    public_deps = [ pw_third_party_freertos_CONFIG ]
-    public = [ "$dir_pw_third_party_freertos/portable/GCC/ARM_CM3/portmacro.h" ]
-    sources = [ "$dir_pw_third_party_freertos/portable/GCC/ARM_CM3/port.c" ]
-    configs = [ ":disable_warnings" ]
-  }
-}
-
-config("public_include_path") {
-  include_dirs = [ "public" ]
-  visibility = [ ":*" ]
-}
-
-pw_source_set("config_assert") {
-  public_configs = [ ":public_include_path" ]
-  public_deps = [ dir_pw_assert ]
-  public = [ "public/pw_third_party/freertos/config_assert.h" ]
-}
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
 }
diff --git a/third_party/freertos/CMakeLists.txt b/third_party/freertos/CMakeLists.txt
deleted file mode 100644
index 3208003..0000000
--- a/third_party/freertos/CMakeLists.txt
+++ /dev/null
@@ -1,164 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-
-set(dir_pw_third_party_freertos "" CACHE PATH
-    "Path to the FreeRTOS installation's Source directory. \
-     If set, pw_third_party.freertos is provided")
-set(pw_third_party_freertos_CONFIG "" CACHE STRING
-    "The CMake target which provides the FreeRTOS config header")
-set(pw_third_party_freertos_PORT "" CACHE STRING
-    "The CMake target which provides the port specific includes and sources")
-option(pw_third_party_freertos_DISABLE_TASKS_STATICS
-       "Whether to disable statics inside of tasks.c")
-
-pw_add_module_library(pw_third_party.freertos.disable_warnings
-  PUBLIC_COMPILE_OPTIONS
-    -Wno-unused-parameter
-    -Wno-cast-qual
-)
-
-# If FreeRTOS is not configured, a script that displays an error message is used
-# instead. If the build rule is used in the build it fails with this error.
-if(NOT dir_pw_third_party_freertos)
-  add_custom_target(pw_third_party.freertos._not_configured
-    COMMAND
-      "${CMAKE_COMMAND}" -E echo
-        "ERROR: Attempted to build the pw_third_party.freertos without"
-        "configuring it via dir_pw_third_party_freertos."
-        "See https://pigweed.dev/third_party/freertos."
-    COMMAND
-      "${CMAKE_COMMAND}" -E false
-  )
-  add_library(pw_third_party.freertos INTERFACE)
-  add_dependencies(pw_third_party.freertos
-    pw_third_party.freertos._not_configured)
-  return()
-else(dir_pw_third_party_freertos)
-  if(NOT pw_third_party_freertos_PORT)
-    message(FATAL_ERROR "FreeRTOS is being used, but "
-            "pw_third_party_freertos_PORT is not set.")
-  endif()
-  if(NOT pw_third_party_freertos_CONFIG)
-    message(FATAL_ERROR "FreeRTOS is being used, but "
-            "pw_third_party_freertos_CONFIG is not set.")
-  endif()
-
-  pw_add_module_library(pw_third_party.freertos
-    HEADERS
-      ${dir_pw_third_party_freertos}/include/FreeRTOS.h
-      ${dir_pw_third_party_freertos}/include/StackMacros.h
-      ${dir_pw_third_party_freertos}/include/croutine.h
-      ${dir_pw_third_party_freertos}/include/deprecated_definitions.h
-      ${dir_pw_third_party_freertos}/include/event_groups.h
-      ${dir_pw_third_party_freertos}/include/list.h
-      ${dir_pw_third_party_freertos}/include/message_buffer.h
-      ${dir_pw_third_party_freertos}/include/mpu_prototypes.h
-      ${dir_pw_third_party_freertos}/include/mpu_wrappers.h
-      ${dir_pw_third_party_freertos}/include/portable.h
-      ${dir_pw_third_party_freertos}/include/projdefs.h
-      ${dir_pw_third_party_freertos}/include/queue.h
-      ${dir_pw_third_party_freertos}/include/semphr.h
-      ${dir_pw_third_party_freertos}/include/stack_macros.h
-      ${dir_pw_third_party_freertos}/include/stream_buffer.h
-      ${dir_pw_third_party_freertos}/include/task.h
-      ${dir_pw_third_party_freertos}/include/timers.h
-    PUBLIC_INCLUDES
-      ${dir_pw_third_party_freertos}/include
-    PUBLIC_DEPS
-      ${pw_third_party_freertos_CONFIG}
-      ${pw_third_party_freertos_PORT}
-    SOURCES
-      ${dir_pw_third_party_freertos}/croutine.c
-      ${dir_pw_third_party_freertos}/event_groups.c
-      ${dir_pw_third_party_freertos}/list.c
-      ${dir_pw_third_party_freertos}/queue.c
-      ${dir_pw_third_party_freertos}/stream_buffer.c
-      ${dir_pw_third_party_freertos}/timers.c
-    PRIVATE_DEPS
-      pw_third_party.freertos.freertos_tasks
-      pw_third_party.freertos.disable_warnings
-  )
-endif()
-
-if(pw_third_party_freertos_DISABLE_TASKS_STATICS)
-  set(disable_tasks_statics "static=" "PW_THIRD_PARTY_FREERTOS_NO_STATICS=1")
-endif()
-pw_add_module_library(pw_third_party.freertos.freertos_tasks
-  SOURCES
-    ${dir_pw_third_party_freertos}/tasks.c
-  PRIVATE_DEPS
-    ${pw_third_party_freertos_CONFIG}
-    ${pw_third_party_freertos_PORT}
-    pw_third_party.freertos.disable_warnings
-  PRIVATE_INCLUDES
-    ${dir_pw_third_party_freertos}/include
-  PRIVATE_DEFINES
-    ${disable_tasks_statics}
-)
-
-# ARM CM7 port of FreeRTOS.
-pw_add_module_library(pw_third_party.freertos.arm_cm7
-  HEADERS
-    ${dir_pw_third_party_freertos}/portable/GCC/ARM_CM7/r0p1/portmacro.h
-  PUBLIC_DEPS
-    ${pw_third_party_freertos_CONFIG}
-  PUBLIC_INCLUDES
-    ${dir_pw_third_party_freertos}/include
-    ${dir_pw_third_party_freertos}/portable/GCC/ARM_CM7/r0p1
-  SOURCES
-    ${dir_pw_third_party_freertos}/portable/GCC/ARM_CM7/r0p1/port.c
-  PRIVATE_DEPS
-    pw_third_party.freertos.disable_warnings
-)
-
-# ARM CM4F port of FreeRTOS.
-pw_add_module_library(pw_third_party.freertos.arm_cm4f
-  HEADERS
-    ${dir_pw_third_party_freertos}/portable/GCC/ARM_CM4F/portmacro.h
-  PUBLIC_DEPS
-    ${pw_third_party_freertos_CONFIG}
-  PUBLIC_INCLUDES
-    ${dir_pw_third_party_freertos}/include
-    ${dir_pw_third_party_freertos}/portable/GCC/ARM_CM4F
-  SOURCES
-    ${dir_pw_third_party_freertos}/portable/GCC/ARM_CM4F/port.c
-  PRIVATE_DEPS
-    pw_third_party.freertos.disable_warnings
-)
-
-# ARM CM33F port of FreeRTOS.
-pw_add_module_library(pw_third_party.freertos.arm_cm33f
-  HEADERS
-    ${dir_pw_third_party_freertos}/portable/GCC/ARM_CM33F/portmacro.h
-  PUBLIC_DEPS
-    ${pw_third_party_freertos_CONFIG}
-  PUBLIC_INCLUDES
-    ${dir_pw_third_party_freertos}/include
-    ${dir_pw_third_party_freertos}/portable/GCC/ARM_CM33F
-  SOURCES
-    ${dir_pw_third_party_freertos}/portable/GCC/ARM_CM33F/port.c
-  PRIVATE_DEPS
-    pw_third_party.freertos.disable_warnings
-)
-
-pw_add_module_library(pw_third_party.freertos.config_assert
-  HEADERS
-    public/pw_third_party/freertos/config_assert.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_assert
-)
diff --git a/third_party/freertos/OWNERS b/third_party/freertos/OWNERS
deleted file mode 100644
index 21d24bc..0000000
--- a/third_party/freertos/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-ewout@google.com
diff --git a/third_party/freertos/docs.rst b/third_party/freertos/docs.rst
deleted file mode 100644
index e7d9050..0000000
--- a/third_party/freertos/docs.rst
+++ /dev/null
@@ -1,95 +0,0 @@
-.. _module-pw_third_party_freertos:
-
-========
-FreeRTOS
-========
-
-The ``$dir_pw_third_party/freertos/`` module contains various helpers to use
-FreeRTOS, including Pigweed backend modules which depend on FreeRTOS.
-
--------------
-Build Support
--------------
-This module provides support to compile FreeRTOS with GN and CMake. This is
-required when compiling backends modules for FreeRTOS.
-
-GN
-==
-In order to use this you are expected to configure the following variables from
-``$dir_pw_third_party/freertos:freertos.gni``:
-
-#. Set the GN ``dir_pw_third_party_freertos`` to the path of the FreeRTOS
-   installation.
-#. Set ``pw_third_party_freertos_CONFIG`` to a ``pw_source_set`` which provides
-   the FreeRTOS config header.
-#. Set ``pw_third_party_freertos_PORT`` to a ``pw_source_set`` which provides
-   the FreeRTOS port specific includes and sources.
-
-After this is done a ``pw_source_set`` for the FreeRTOS library is created at
-``$dir_pw_third_party/freertos``.
-
-CMake
-=====
-In order to use this you are expected to set the following variables from
-``third_party/freertos/CMakeLists.txt``:
-
-#. Set the GN ``dir_pw_third_party_freertos`` to the path of the FreeRTOS
-   installation.
-#. Set ``pw_third_party_freertos_CONFIG`` to a library target which provides
-   the FreeRTOS config header.
-#. Set ``pw_third_party_freertos_PORT`` to a library target which provides
-   the FreeRTOS port specific includes and sources.
-
-
-.. _third_party-freertos_disable_task_statics:
-
-Linking against FreeRTOS kernel's static internals
-==================================================
-In order to link against internal kernel data structures through the use of
-extern "C", statics can be optionally disabled for the tasks.c source file
-to enable use of things like pw_thread_freertos/util.h's ``ForEachThread``.
-
-To facilitate this, Pigweed offers an opt-in option which can be enabled by
-configuring GN through
-``pw_third_party_freertos_DISABLE_TASKS_STATICS = true`` or CMake through
-``set(pw_third_party_freertos_DISABLE_TASKS_STATICS ON CACHE BOOL "" FORCE)``.
-This redefines ``static`` to nothing for the ``Source/tasks.c`` FreeRTOS source
-file when building through ``$dir_pw_third_party/freertos`` in GN and through
-``pw_third_party.freertos`` in CMake.
-
-.. attention:: If you use this, make sure that your FreeRTOSConfig.h and port
-  does not rely on any statics inside of tasks.c. For example, you cannot use
-  ``PW_CHECK`` for ``configASSERT`` when this is enabled.
-
-As a helper ``PW_THIRD_PARTY_FREERTOS_NO_STATICS=1`` is defined when statics are
-disabled to help manage conditional configuration.
-
-We highly recommend
-:ref:`our configASSERT wrapper <third_party-freertos_config_assert>` when  using
-this configuration, which correctly sets ``configASSERT`` to use ``PW_CHECK` and
-``PW_ASSERT`` for you.
-
------------------------------
-OS Abstraction Layers Support
------------------------------
-Support for Pigweed's :ref:`docs-os_abstraction_layers` are provided for
-FreeRTOS via the following modules:
-
-* :ref:`module-pw_chrono_freertos`
-* :ref:`module-pw_sync_freertos`
-* :ref:`module-pw_thread_freertos`
-
-.. _third_party-freertos_config_assert:
-
---------------------------
-configASSERT and pw_assert
---------------------------
-To make it easier to use :ref:`module-pw_assert` with FreeRTOS a helper header
-is provided under ``pw_third_party/freertos/config_assert.h`` which defines
-``configASSERT`` for you using Pigweed's assert system for your
-``FreeRTOSConfig.h`` if you chose to use it.
-
-.. code-block:: cpp
-
-  // Instead of defining configASSERT, simply include this header in its place.
-  #include "pw_third_party/freertos/config_assert.h"
diff --git a/third_party/freertos/freertos.gni b/third_party/freertos/freertos.gni
index 81308d6..c3f5147 100644
--- a/third_party/freertos/freertos.gni
+++ b/third_party/freertos/freertos.gni
@@ -23,18 +23,4 @@
 
   # The pw_source_set which provides the port specific includes and sources.
   pw_third_party_freertos_PORT = ""
-
-  # In order to link against internal kernel data structures through the use of
-  # extern "C", statics can be optionally disabled for the tasks.c source file
-  # to enable use of things like pw_thread_freertos/util.h's ForEachThread.
-  #
-  # WARNING: If you use this, make sure that your FreeRTOSConfig.h and port
-  # does not rely on any statics inside of tasks.c. For example, you cannot use
-  # PW_CHECK for configASSERT when this is enabled.
-  #
-  # As a helper PW_THIRD_PARTY_FREERTOS_NO_STATICS=1 is defined when statics are
-  # disabled to help manage conditional configuration. In addition a helper
-  # pw_third_party_freertos/assert_config.h header is provided which correctly
-  # sets configASSERT to use PW_CHECK or PW_ASSERT for you.
-  pw_third_party_freertos_DISABLE_TASKS_STATICS = false
 }
diff --git a/third_party/freertos/public/pw_third_party/freertos/config_assert.h b/third_party/freertos/public/pw_third_party/freertos/config_assert.h
deleted file mode 100644
index 11d8048..0000000
--- a/third_party/freertos/public/pw_third_party/freertos/config_assert.h
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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
-
-// Because FreeRTOS.h includes FreeRTOSConfig.h inside of an extern "C" we must
-// wrap the include of check in an extern "C++" for things to work.
-#ifdef __cplusplus
-extern "C++" {
-#endif  // __cplusplus
-
-#if defined(PW_THIRD_PARTY_FREERTOS_NO_STATICS) && \
-    PW_THIRD_PARTY_FREERTOS_NO_STATICS == 1
-#include "pw_assert/assert.h"
-#define configASSERT PW_ASSERT
-#else
-#include "pw_assert/check.h"
-#define configASSERT PW_CHECK
-#endif  // PW_THIRD_PARTY_FREERTOS_NO_STATICS == 1
-
-#ifdef __cplusplus
-}  // extern "C++"
-#endif  // __cplusplus
diff --git a/third_party/google_auto/BUILD.bazel b/third_party/google_auto/BUILD.bazel
deleted file mode 100644
index ed020bf..0000000
--- a/third_party/google_auto/BUILD.bazel
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-java_plugin(
-    name = "value_plugin",
-    processor_class = "com.google.auto.value.processor.AutoValueProcessor",
-    visibility = ["//visibility:private"],
-    deps = [
-        "@maven//:com_google_auto_value_auto_value",
-    ],
-)
-
-java_library(
-    name = "value",
-    exported_plugins = [
-        ":value_plugin",
-    ],
-    neverlink = 1,
-    visibility = ["//visibility:public"],
-    exports = [
-        "@maven//:com_google_auto_value_auto_value",
-        "@maven//:com_google_auto_value_auto_value_annotations",
-    ],
-)
diff --git a/third_party/googletest/BUILD.gn b/third_party/googletest/BUILD.gn
index f50a339..cdf272d 100644
--- a/third_party/googletest/BUILD.gn
+++ b/third_party/googletest/BUILD.gn
@@ -44,14 +44,12 @@
       "$dir_pw_third_party_googletest/googlemock/include/gmock/gmock-actions.h",
       "$dir_pw_third_party_googletest/googlemock/include/gmock/gmock-cardinalities.h",
       "$dir_pw_third_party_googletest/googlemock/include/gmock/gmock-function-mocker.h",
+      "$dir_pw_third_party_googletest/googlemock/include/gmock/gmock-generated-actions.h",
       "$dir_pw_third_party_googletest/googlemock/include/gmock/gmock-matchers.h",
       "$dir_pw_third_party_googletest/googlemock/include/gmock/gmock-more-actions.h",
       "$dir_pw_third_party_googletest/googlemock/include/gmock/gmock-more-matchers.h",
       "$dir_pw_third_party_googletest/googlemock/include/gmock/gmock-nice-strict.h",
       "$dir_pw_third_party_googletest/googlemock/include/gmock/gmock-spec-builders.h",
-      "$dir_pw_third_party_googletest/googlemock/include/gmock/internal/custom/gmock-generated-actions.h",
-      "$dir_pw_third_party_googletest/googlemock/include/gmock/internal/custom/gmock-matchers.h",
-      "$dir_pw_third_party_googletest/googlemock/include/gmock/internal/custom/gmock-port.h",
       "$dir_pw_third_party_googletest/googlemock/include/gmock/internal/gmock-internal-utils.h",
       "$dir_pw_third_party_googletest/googlemock/include/gmock/internal/gmock-port.h",
       "$dir_pw_third_party_googletest/googlemock/include/gmock/internal/gmock-pp.h",
@@ -70,9 +68,6 @@
       "$dir_pw_third_party_googletest/googletest/include/gtest/gtest-typed-test.h",
       "$dir_pw_third_party_googletest/googletest/include/gtest/gtest_pred_impl.h",
       "$dir_pw_third_party_googletest/googletest/include/gtest/gtest_prod.h",
-      "$dir_pw_third_party_googletest/googletest/include/gtest/internal/custom/gtest-port.h",
-      "$dir_pw_third_party_googletest/googletest/include/gtest/internal/custom/gtest-printers.h",
-      "$dir_pw_third_party_googletest/googletest/include/gtest/internal/custom/gtest.h",
       "$dir_pw_third_party_googletest/googletest/include/gtest/internal/gtest-death-test-internal.h",
       "$dir_pw_third_party_googletest/googletest/include/gtest/internal/gtest-filepath.h",
       "$dir_pw_third_party_googletest/googletest/include/gtest/internal/gtest-internal.h",
@@ -95,7 +90,7 @@
 
   pw_source_set("gtest_main") {
     public_deps = [ ":googletest" ]
-    sources = [ "$dir_pw_third_party_googletest/googletest/src/gtest_main.cc" ]
+    sources = [ "$dir_pw_third_party_googletest/googlemock/src/gtest_main.cc" ]
   }
 
   pw_source_set("gmock_main") {
diff --git a/third_party/mbedtls/BUILD.bazel b/third_party/mbedtls/BUILD.bazel
deleted file mode 100644
index 7d007c5..0000000
--- a/third_party/mbedtls/BUILD.bazel
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright 2020 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-# Ready-made configurations
-mbedtls_configs = [
-    ("default", "configs/config_default.h"),
-]
-
-# Config targets.
-[
-    pw_cc_library(
-        name = "%s_config" % config_name,
-        hdrs = [
-            config_header,
-            "configs/config_pigweed_common.h",
-        ],
-        copts = ["-DMBEDTLS_CONFIG_FILE=\"%s\"" % config_header],
-        includes = ["."],
-    )
-    for config_name, config_header in mbedtls_configs
-]
-
-# TODO(zyecheng): Add build recipe for the library.
diff --git a/third_party/mbedtls/BUILD.gn b/third_party/mbedtls/BUILD.gn
deleted file mode 100644
index 27bc89d..0000000
--- a/third_party/mbedtls/BUILD.gn
+++ /dev/null
@@ -1,143 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-import("$dir_pw_build/target_types.gni")
-import("mbedtls.gni")
-
-if (dir_pw_third_party_mbedtls != "") {
-  # The list currently includes all source files for build.
-  mbedtls_sources = [
-    "library/aes.c",
-    "library/aesni.c",
-    "library/arc4.c",
-    "library/aria.c",
-    "library/asn1parse.c",
-    "library/asn1write.c",
-    "library/base64.c",
-    "library/bignum.c",
-    "library/blowfish.c",
-    "library/camellia.c",
-    "library/ccm.c",
-    "library/certs.c",
-    "library/chacha20.c",
-    "library/chachapoly.c",
-    "library/cipher.c",
-    "library/cipher_wrap.c",
-    "library/cmac.c",
-    "library/ctr_drbg.c",
-    "library/debug.c",
-    "library/des.c",
-    "library/dhm.c",
-    "library/ecdh.c",
-    "library/ecdsa.c",
-    "library/ecjpake.c",
-    "library/ecp.c",
-    "library/ecp_curves.c",
-    "library/entropy.c",
-    "library/entropy_poll.c",
-    "library/error.c",
-    "library/gcm.c",
-    "library/havege.c",
-    "library/hkdf.c",
-    "library/hmac_drbg.c",
-    "library/md.c",
-    "library/md2.c",
-    "library/md4.c",
-    "library/md5.c",
-    "library/memory_buffer_alloc.c",
-    "library/net_sockets.c",
-    "library/nist_kw.c",
-    "library/oid.c",
-    "library/padlock.c",
-    "library/pem.c",
-    "library/pk.c",
-    "library/pk_wrap.c",
-    "library/pkcs11.c",
-    "library/pkcs12.c",
-    "library/pkcs5.c",
-    "library/pkparse.c",
-    "library/pkwrite.c",
-    "library/platform.c",
-    "library/platform_util.c",
-    "library/poly1305.c",
-    "library/psa_crypto.c",
-    "library/psa_crypto_driver_wrappers.c",
-    "library/psa_crypto_se.c",
-    "library/psa_crypto_slot_management.c",
-    "library/psa_crypto_storage.c",
-    "library/psa_its_file.c",
-    "library/ripemd160.c",
-    "library/rsa.c",
-    "library/rsa_internal.c",
-    "library/sha1.c",
-    "library/sha256.c",
-    "library/sha512.c",
-    "library/ssl_cache.c",
-    "library/ssl_ciphersuites.c",
-    "library/ssl_cli.c",
-    "library/ssl_cookie.c",
-    "library/ssl_msg.c",
-    "library/ssl_srv.c",
-    "library/ssl_ticket.c",
-    "library/ssl_tls.c",
-    "library/ssl_tls13_keys.c",
-    "library/threading.c",
-    "library/timing.c",
-    "library/version.c",
-    "library/version_features.c",
-    "library/x509.c",
-    "library/x509_create.c",
-    "library/x509_crl.c",
-    "library/x509_crt.c",
-    "library/x509_csr.c",
-    "library/x509write_crt.c",
-    "library/x509write_csr.c",
-    "library/xtea.c",
-  ]
-
-  config("mbedtls_config") {
-    include_dirs = [
-      "$dir_pw_third_party_mbedtls",
-      "$dir_pw_third_party_mbedtls/include",
-      "$dir_pw_third_party/mbedtls",
-    ]
-    cflags = [
-      "-Wno-error=cast-qual",
-      "-Wno-error=redundant-decls",
-      "-w",
-    ]
-
-    config_header_file = rebase_path(pw_third_party_mbedtls_CONFIG_HEADER)
-    defines = [ "MBEDTLS_CONFIG_FILE=\"$config_header_file\"" ]
-  }
-
-  pw_source_set("mbedtls") {
-    sources = []
-    foreach(source, mbedtls_sources) {
-      sources += [ "$dir_pw_third_party_mbedtls/" + source ]
-    }
-
-    public = [
-      "configs/config_default.h",
-      "configs/config_pigweed_common.h",
-    ]
-
-    public_deps = [ "$dir_pw_tls_client:time" ]
-    public_configs = [ ":mbedtls_config" ]
-  }
-} else {
-  group("mbedtls") {
-  }
-}
diff --git a/third_party/mbedtls/OWNERS b/third_party/mbedtls/OWNERS
deleted file mode 100644
index 121c6f5..0000000
--- a/third_party/mbedtls/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-zyecheng@google.com
diff --git a/third_party/mbedtls/README.md b/third_party/mbedtls/README.md
deleted file mode 100644
index 4d11f66..0000000
--- a/third_party/mbedtls/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# MbedTLS Library
-
-The folder provides build scripts and configuration recipes for building
-the MbedTLS library. The source code needs to be downloaded by the user, or
-via the support in pw_package "pw package install mbedtls". For gn build,
-set `dir_pw_third_party_mbedtls` to point to the path of the source code.
-For applications using MbedTLS, add `$dir_pw_third_party/mbedtls` to the
-dependency list. The config header can be set using gn variable
-`pw_third_party_mbedtls_CONFIG_HEADER`.
diff --git a/third_party/mbedtls/configs/config_default.h b/third_party/mbedtls/configs/config_default.h
deleted file mode 100644
index 6518fbb..0000000
--- a/third_party/mbedtls/configs/config_default.h
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 <mbedtls/config.h>
-
-// override some flags needed by pigweed
-#include "configs/config_pigweed_common.h"
diff --git a/third_party/mbedtls/configs/config_pigweed_common.h b/third_party/mbedtls/configs/config_pigweed_common.h
deleted file mode 100644
index 94bc08d..0000000
--- a/third_party/mbedtls/configs/config_pigweed_common.h
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// Some common configs for using mbedtls in Pigweed. These include disabling of
-// file system, socket and linux/windows specific features. See
-// include/mbedtls/config.h for a detail explanation of these configurations.
-
-#pragma once
-
-// No file system support.
-#undef MBEDTLS_FS_IO
-// No posix socket support
-#undef MBEDTLS_NET_C
-// This feature requires file system support.
-#undef MBEDTLS_PSA_ITS_FILE_C
-// The following two require MBEDTLS_PSA_ITS_FILE_C
-#undef MBEDTLS_PSA_CRYPTO_C
-#undef MBEDTLS_PSA_CRYPTO_STORAGE_C
-// This feature only works on Unix/Windows
-#undef MBEDTLS_TIMING_C
-// Use a custom entropy generator
-#define MBEDTLS_NO_PLATFORM_ENTROPY
-// Error string support for debugging
-#define MBEDTLS_ERROR_C
-
-#include "mbedtls/check_config.h"
diff --git a/third_party/mbedtls/mbedtls.gni b/third_party/mbedtls/mbedtls.gni
deleted file mode 100644
index e94ac8e..0000000
--- a/third_party/mbedtls/mbedtls.gni
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-declare_args() {
-  # If compiling backends with mbedtls, this variable is set to the path to the
-  # mbedtls source code. When set, a pw_source_set for the mbedtls library is
-  # created at "$dir_pw_third_party/mbedtls".
-  dir_pw_third_party_mbedtls = ""
-
-  # Path to the config header.
-  pw_third_party_mbedtls_CONFIG_HEADER =
-      "$dir_pigweed/third_party/mbedtls/configs/config_default.h"
-}
diff --git a/third_party/mcuxpresso/BUILD.gn b/third_party/mcuxpresso/BUILD.gn
deleted file mode 100644
index 515c3b7..0000000
--- a/third_party/mcuxpresso/BUILD.gn
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("mcuxpresso.gni")
diff --git a/third_party/mcuxpresso/OWNERS b/third_party/mcuxpresso/OWNERS
deleted file mode 100644
index c224618..0000000
--- a/third_party/mcuxpresso/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-keybuk@google.com
diff --git a/third_party/mcuxpresso/mcuxpresso.gni b/third_party/mcuxpresso/mcuxpresso.gni
deleted file mode 100644
index 27e597e..0000000
--- a/third_party/mcuxpresso/mcuxpresso.gni
+++ /dev/null
@@ -1,128 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-declare_args() {
-  # If compiling a project against an MCUXpresso SDK, this variable can be set
-  # to the name of the pw_source_set you create using `pw_mcuxpresso_sdk` to
-  # enable additional Pigweed support.
-  pw_third_party_mcuxpresso_SDK = ""
-}
-
-# Creates a source set for an MCUXpresso SDK.
-#
-# In addition to the named source set, two configs are created; one named
-# `${target_name}__defines` contains pre-processor definitions for the SDK
-# project and the other named `${target_name}__includes` defines the include
-# paths. These may be used to break circular dependencies.
-#
-# Args:
-#  manifest: The MCUXpresso SDK manifest XML file describing the components of
-#    the SDK.
-#
-#  sdk_dir: Optional path to directory containing the SDK. When ommitted the
-#    parent of `manifest` is used, which is usually the correct definition.
-#
-#  include: list of SDK components to include in the project.
-#
-#  exclude: Optional list of SDK components to exclude from the project.
-#
-#  allow_circular_includes_from, configs, deps, public_configs, public_deps:
-#    Optional extra properties for the source set.
-#
-# Example:
-#
-#   pw_mcuxpresso_sdk("sample_project_sdk") {
-#     manifest = "$dir_pw_third_party/mcuxpresso/EVK-MIMXRT595_manifest_v3_8.xml"
-#     include = [ "project_template.evkmimxrt595.MIMXRT595S" ]
-#   }
-#
-#   pw_executable("hello_world") {
-#     sources = [ "hello_world.cc "]
-#     deps = [ ":sample_project_sdk" ]
-#   }
-#
-template("pw_mcuxpresso_sdk") {
-  assert(defined(invoker.manifest), "pw_mcuxpresso_sdk requires a manifest")
-
-  if (defined(invoker.sdk_dir)) {
-    _sdk_dir = invoker.sdk_dir
-  } else {
-    _sdk_dir = get_path_info(invoker.manifest, "dir")
-  }
-
-  _script_args = [
-    "project",
-    rebase_path(invoker.manifest),
-    "--prefix=$_sdk_dir",
-  ]
-
-  if (defined(invoker.include)) {
-    foreach(dependency, invoker.include) {
-      _script_args += [
-        "--include",
-        dependency,
-      ]
-    }
-  }
-
-  if (defined(invoker.exclude)) {
-    foreach(dependency, invoker.exclude) {
-      _script_args += [
-        "--exclude",
-        dependency,
-      ]
-    }
-  }
-
-  # This script finds the components distributed with the SDK.
-  _script = "$dir_pw_build_mcuxpresso/py/pw_build_mcuxpresso/__main__.py"
-  _project = exec_script(_script, _script_args, "scope", [ invoker.manifest ])
-
-  config("${target_name}__defines") {
-    forward_variables_from(_project, [ "defines" ])
-  }
-
-  config("${target_name}__includes") {
-    forward_variables_from(_project, [ "include_dirs" ])
-  }
-
-  pw_source_set(target_name) {
-    forward_variables_from(_project,
-                           [
-                             "libs",
-                             "public",
-                             "sources",
-                           ])
-
-    public_configs = [
-      ":${target_name}__defines",
-      ":${target_name}__includes",
-    ]
-    if (defined(invoker.public_configs)) {
-      public_configs += invoker.public_configs
-    }
-
-    forward_variables_from(invoker,
-                           [
-                             "configs",
-                             "deps",
-                             "public_deps",
-                             "allow_circular_includes_from",
-                           ])
-  }
-}
diff --git a/third_party/micro_ecc/BUILD.gn b/third_party/micro_ecc/BUILD.gn
deleted file mode 100644
index e0de9fa..0000000
--- a/third_party/micro_ecc/BUILD.gn
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-import("$dir_pw_build/target_types.gni")
-import("micro_ecc.gni")
-
-if (dir_pw_third_party_micro_ecc != "") {
-  config("default") {
-    # Suppress all upstream introduced warnings.
-    cflags = [ "-w" ]
-
-    include_dirs = [ "$dir_pw_third_party_micro_ecc/" ]
-
-    # Disabling point compression saves 200 bytes.
-    defines = [ "uECC_SUPPORT_COMPRESSED_POINT=0" ]
-  }
-
-  pw_source_set("micro_ecc") {
-    public_configs = [ ":default" ]
-    sources = [ "$dir_pw_third_party_micro_ecc/uECC.c" ]
-  }
-} else {
-  group("micro_ecc") {
-  }
-}
diff --git a/third_party/micro_ecc/OWNERS b/third_party/micro_ecc/OWNERS
deleted file mode 100644
index b01d16c..0000000
--- a/third_party/micro_ecc/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-alizhang@google.com
diff --git a/third_party/micro_ecc/micro_ecc.gni b/third_party/micro_ecc/micro_ecc.gni
deleted file mode 100644
index 8b523c7..0000000
--- a/third_party/micro_ecc/micro_ecc.gni
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-declare_args() {
-  # Points to where the upstream code resides.
-  dir_pw_third_party_micro_ecc = ""
-}
diff --git a/third_party/nanopb/BUILD.gn b/third_party/nanopb/BUILD.gn
index 8e43b73..77e2453 100644
--- a/third_party/nanopb/BUILD.gn
+++ b/third_party/nanopb/BUILD.gn
@@ -1,4 +1,4 @@
-# Copyright 2021 The Pigweed Authors
+# Copyright 2020 The Pigweed Authors
 #
 # 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
@@ -14,16 +14,10 @@
 
 import("//build_overrides/pigweed.gni")
 
-import("$dir_pw_build/python.gni")
 import("$dir_pw_build/target_types.gni")
-import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_protobuf_compiler/proto.gni")
 import("nanopb.gni")
 
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
-
 # This file defines a GN source_set for an external installation of nanopb.
 # To use, checkout the nanopb source into a directory, then set the build arg
 # dir_pw_third_party_nanopb to point to that directory. The nanopb library
@@ -41,7 +35,6 @@
       "$dir_pw_third_party_nanopb/pb_decode.h",
       "$dir_pw_third_party_nanopb/pb_encode.h",
     ]
-    public_deps = [ pw_third_party_nanopb_CONFIG ]
     sources = [
       "$dir_pw_third_party_nanopb/pb_common.c",
       "$dir_pw_third_party_nanopb/pb_decode.c",
@@ -54,27 +47,7 @@
     sources = [ "$dir_pw_third_party_nanopb/generator/proto/nanopb.proto" ]
     python_module_as_package = "nanopb_pb2"
   }
-
-  # Generates nanopb_pb2.py, which is needed to compile protobufs with Nanopb.
-  pw_python_script("generate_nanopb_proto") {
-    sources = [ "generate_nanopb_proto.py" ]
-    pylintrc = "$dir_pigweed/.pylintrc"
-    action = {
-      args = [ rebase_path(dir_pw_third_party_nanopb, root_build_dir) ]
-      stamp = true
-    }
-  }
 } else {
   group("nanopb") {
   }
-  pw_python_group("generate_nanopb_proto") {
-  }
-}
-
-config("disable_error_messages_config") {
-  defines = [ "PB_NO_ERRMSG=1" ]
-}
-
-pw_source_set("disable_error_messages") {
-  public_configs = [ ":disable_error_messages_config" ]
 }
diff --git a/third_party/nanopb/CMakeLists.txt b/third_party/nanopb/CMakeLists.txt
index 11cde7f..7b5745e 100644
--- a/third_party/nanopb/CMakeLists.txt
+++ b/third_party/nanopb/CMakeLists.txt
@@ -12,9 +12,6 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
-include($ENV{PW_ROOT}/pw_build/pigweed.cmake)
-include($ENV{PW_ROOT}/pw_protobuf_compiler/proto.cmake)
-
 set(dir_pw_third_party_nanopb "" CACHE PATH "Path to the Nanopb installation.")
 option(pw_third_party_nanopb_ADD_SUBDIRECTORY
     "Whether to add the dir_pw_third_party_nanopb subdirectory" OFF)
@@ -34,22 +31,3 @@
   INTERFACE
     "${dir_pw_third_party_nanopb}"
 )
-
-pw_proto_library(pw_third_party.nanopb.proto
-  SOURCES
-    "${dir_pw_third_party_nanopb}/generator/proto/nanopb.proto"
-  STRIP_PREFIX
-    "${dir_pw_third_party_nanopb}/generator/proto"
-)
-
-# Generates nanopb_pb2.py, which is needed to compile protobufs with Nanopb.
-add_custom_command(
-  COMMAND
-    python3 "${CMAKE_CURRENT_LIST_DIR}/generate_nanopb_proto.py" "${dir_pw_third_party_nanopb}"
-  OUTPUT
-    "${dir_pw_third_party_nanopb}/generator/proto/nanopb_pb2.py"
-)
-add_custom_target(pw_third_party.nanopb.generate_proto
-  DEPENDS
-    "${dir_pw_third_party_nanopb}/generator/proto/nanopb_pb2.py"
-)
diff --git a/third_party/nanopb/OWNERS b/third_party/nanopb/OWNERS
deleted file mode 100644
index 3afb926..0000000
--- a/third_party/nanopb/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-frolv@google.com
diff --git a/third_party/nanopb/docs.rst b/third_party/nanopb/docs.rst
deleted file mode 100644
index 1af7211..0000000
--- a/third_party/nanopb/docs.rst
+++ /dev/null
@@ -1,29 +0,0 @@
-.. _module-pw_third_party_nanopb:
-
-======
-Nanopb
-======
-
-The ``$dir_pw_third_party/nanopb/`` module contains Nanopb, a tiny protobuf
-library. It is used by :ref:`module-pw_protobuf_compiler`.
-
-----------------
-GN Build Support
-----------------
-This module provides support to compile Nanopb with GN.
-
-Follow the documentation on :ref:`module-pw_protobuf_compiler` for general
-help on how to use this.
-
-Enabling ``PB_NO_ERRMSG=1``
----------------------------
-
-In your toolchain configuration, you can use the following:
-
-.. code-block::
-
-  pw_third_party_nanopb_CONFIG = "$dir_pw_third_party/nanopb:disable_error_messages"
-
-
-This will add ``-DPB_NO_ERRMSG=1`` to the build, which disables error messages
-as strings and may save some code space at the expense of ease of debugging.
diff --git a/third_party/nanopb/generate_nanopb_proto.py b/third_party/nanopb/generate_nanopb_proto.py
deleted file mode 100644
index 166db85..0000000
--- a/third_party/nanopb/generate_nanopb_proto.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-"""Generates nanopb_pb2.py by importing the Nanopb proto module.
-
-The Nanopb repository generates nanopb_pb2.py dynamically when its Python
-package is imported if it does not exist. If multiple processes try to use
-Nanopb to compile simultaneously on a clean build, they can interfere with each
-other. One process might rewrite nanopb_pb2.py as another process is trying to
-access it, resulting in import errors.
-
-This script imports the Nanopb module so that nanopb_pb2.py is generated if it
-doesn't exist. All Nanopb proto compilation targets depend on this script so
-that nanopb_pb2.py is guaranteed to exist before they need it.
-"""
-
-import argparse
-import importlib.util
-from pathlib import Path
-import sys
-
-
-def generate_nanopb_proto(root: Path) -> None:
-    sys.path.append(str(root / 'generator'))
-
-    spec = importlib.util.spec_from_file_location(
-        'proto', root / 'generator' / 'proto' / '__init__.py')
-    assert spec is not None
-    proto_module = importlib.util.module_from_spec(spec)
-    spec.loader.exec_module(proto_module)  # type: ignore[union-attr]
-
-
-def _parse_args() -> argparse.Namespace:
-    parser = argparse.ArgumentParser(description=__doc__)
-    parser.add_argument('root', type=Path, help='Nanopb root')
-    return parser.parse_args()
-
-
-if __name__ == '__main__':
-    generate_nanopb_proto(**vars(_parse_args()))
diff --git a/third_party/nanopb/nanopb.gni b/third_party/nanopb/nanopb.gni
index 110deba..7cda7c0 100644
--- a/third_party/nanopb/nanopb.gni
+++ b/third_party/nanopb/nanopb.gni
@@ -11,18 +11,10 @@
 # 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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/module_config.gni")
 
 declare_args() {
   # If compiling protos for nanopb, this variable is set to the path to the
   # nanopb installation. When set, a pw_source_set for the nanopb library is
   # created at "$dir_pw_third_party/nanopb".
   dir_pw_third_party_nanopb = ""
-
-  # The build target that overrides the default configuration options for this
-  # module. This should point to a source set that provides defines through a
-  # public config (which may -include a file or add defines directly).
-  pw_third_party_nanopb_CONFIG = pw_build_DEFAULT_MODULE_CONFIG
 }
diff --git a/third_party/pico_sdk/gn/BUILD.gn b/third_party/pico_sdk/gn/BUILD.gn
deleted file mode 100644
index f411b80..0000000
--- a/third_party/pico_sdk/gn/BUILD.gn
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-# These warnings need to be disabled when using strict warnings.
-#
-# TODO(amontanez): Just applying these flags to Pi Pico source sets does not
-# work because of Pigweed's default_configs notion and how it orders flags.
-# Removing Pigweed's strict  warnings config is the only working solution for
-# now.
-config("disable_warnings") {
-  cflags = [
-    "-Wno-undef",
-    "-Wno-unused-function",
-  ]
-  asmflags = cflags
-}
diff --git a/third_party/pico_sdk/gn/generate_config_header.gni b/third_party/pico_sdk/gn/generate_config_header.gni
deleted file mode 100644
index 0cbc4e0..0000000
--- a/third_party/pico_sdk/gn/generate_config_header.gni
+++ /dev/null
@@ -1,66 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# Generates a pico/config_autogen.h file as part of a source set that provides
-# the required include directory as a public config.
-#
-# Example contents:
-#
-#   // AUTO GENERATED BY A GN generate_config_header TARGET
-#   #include "boards/pico.h"
-#   #include "cmsis/rename_exceptions.h"
-#
-# Arguments:
-#   config_header_files (required): Includes that should be written to the
-#     generated header file.
-template("generate_config_header") {
-  assert(defined(invoker.config_header_files), "No headers provided")
-
-  _generated_header_dir = "${target_gen_dir}/${target_name}_include"
-  _generated_header_path = "${_generated_header_dir}/pico/config_autogen.h"
-
-  # Provide the include path so the header is exposed when targets depend on
-  # the generate_config_header target.
-  config("${target_name}.public_include_dirs") {
-    include_dirs = [ "${_generated_header_dir}" ]
-  }
-
-  # Actually generate config_autogen.h.
-  generated_file("${target_name}.generated_header") {
-    outputs = [ "${_generated_header_path}" ]
-    _lines = [ "// AUTO GENERATED BY A GN generate_config_header TARGET" ]
-    foreach(_header, invoker.config_header_files) {
-      _lines += [ "#include \"${_header}\"" ]
-    }
-
-    # Join with newline.
-    _NEWLINE_CHAR = "$0x0A"
-    contents = string_join(_NEWLINE_CHAR, _lines)
-  }
-
-  # This source set bundles up the generated header such that depending on
-  # this template will allow targets to include "pico/config_autogen.h".
-  pw_source_set("${target_name}") {
-    remove_configs = [ "$dir_pw_build:strict_warnings" ]
-    public_configs = [ ":${target_name}.public_include_dirs" ]
-    deps = [ ":${target_name}.generated_header" ]
-    public = [ "${_generated_header_path}" ]
-    forward_variables_from(invoker, "*", [ "config_header_files" ])
-  }
-}
diff --git a/third_party/pico_sdk/pi_pico.gni b/third_party/pico_sdk/pi_pico.gni
deleted file mode 100644
index dde153f..0000000
--- a/third_party/pico_sdk/pi_pico.gni
+++ /dev/null
@@ -1,34 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-declare_args() {
-  # PIGWEED ONLY: Since Pigweed doesn't host 3p code, this points to the actual
-  # location of the Pi Pico source. If the GN build is ever upstreamed, this
-  # variable would not be needed.
-  PICO_SRC_DIR = ""
-}
-
-# Actual Pi Pico build configuration options.
-declare_args() {
-  PICO_BARE_METAL = false
-  PICO_BOARD = "\"rp2040\""
-  PICO_BOARD_HEADER_DIR = get_path_info("src/boards", "abspath")
-
-  # TODO(amontanez): This needs to be thought through fully.
-  PICO_GENERATED_CONFIG = get_path_info("src/rp2040:rp2040_config", "abspath")
-
-  # TODO(amontanez): This needs to be thought through fully, but can wait until
-  # a Pi Pico successor that requires it.
-  PICO_PLATFORM_DIR = get_path_info("src/rp2040", "abspath")
-}
diff --git a/third_party/pico_sdk/src/BUILD.gn b/third_party/pico_sdk/src/BUILD.gn
deleted file mode 100644
index 62b3952..0000000
--- a/third_party/pico_sdk/src/BUILD.gn
+++ /dev/null
@@ -1,23 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-# TODO(amontanez): If a successor to the RP2040 comes out, this might need to
-# be a little smarter about what code is pulled in.
-group("pico_sdk") {
-  public_deps = [
-    "common",
-    "rp2040",
-    "rp2_common",
-  ]
-}
diff --git a/third_party/pico_sdk/src/boards/BUILD.gn b/third_party/pico_sdk/src/boards/BUILD.gn
deleted file mode 100644
index d92764e..0000000
--- a/third_party/pico_sdk/src/boards/BUILD.gn
+++ /dev/null
@@ -1,58 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/boards"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("boards") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public = [
-    "${_CWD}/include/boards/adafruit_feather_rp2040.h",
-    "${_CWD}/include/boards/adafruit_itsybitsy_rp2040.h",
-    "${_CWD}/include/boards/adafruit_qtpy_rp2040.h",
-    "${_CWD}/include/boards/adafruit_trinkey_qt2040.h",
-    "${_CWD}/include/boards/arduino_nano_rp2040_connect.h",
-    "${_CWD}/include/boards/melopero_shake_rp2040.h",
-    "${_CWD}/include/boards/none.h",
-    "${_CWD}/include/boards/pico.h",
-    "${_CWD}/include/boards/pimoroni_interstate75.h",
-    "${_CWD}/include/boards/pimoroni_keybow2040.h",
-    "${_CWD}/include/boards/pimoroni_pga2040.h",
-    "${_CWD}/include/boards/pimoroni_picolipo_16mb.h",
-    "${_CWD}/include/boards/pimoroni_picolipo_4mb.h",
-    "${_CWD}/include/boards/pimoroni_picosystem.h",
-    "${_CWD}/include/boards/pimoroni_plasma2040.h",
-    "${_CWD}/include/boards/pimoroni_tiny2040.h",
-    "${_CWD}/include/boards/pybstick26_rp2040.h",
-    "${_CWD}/include/boards/sparkfun_micromod.h",
-    "${_CWD}/include/boards/sparkfun_promicro.h",
-    "${_CWD}/include/boards/sparkfun_thingplus.h",
-    "${_CWD}/include/boards/vgaboard.h",
-    "${_CWD}/include/boards/waveshare_rp2040_lcd_0.96.h",
-    "${_CWD}/include/boards/waveshare_rp2040_plus_16mb.h",
-    "${_CWD}/include/boards/waveshare_rp2040_plus_4mb.h",
-    "${_CWD}/include/boards/waveshare_rp2040_zero.h",
-  ]
-}
diff --git a/third_party/pico_sdk/src/common/BUILD.gn b/third_party/pico_sdk/src/common/BUILD.gn
deleted file mode 100644
index 3dda4de..0000000
--- a/third_party/pico_sdk/src/common/BUILD.gn
+++ /dev/null
@@ -1,34 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-group("common") {
-  public_deps = [
-    "boot_picoboot",
-    "boot_uf2",
-    "pico_base",
-    "pico_usb_reset_interface",
-  ]
-
-  if (!PICO_BARE_METAL) {
-    public_deps += [
-      "pico_binary_info",
-      "pico_bit_ops",
-      "pico_divider",
-      "pico_stdlib",
-      "pico_sync",
-      "pico_time",
-      "pico_util",
-    ]
-  }
-}
diff --git a/third_party/pico_sdk/src/common/boot_picoboot/BUILD.gn b/third_party/pico_sdk/src/common/boot_picoboot/BUILD.gn
deleted file mode 100644
index 2e141d4..0000000
--- a/third_party/pico_sdk/src/common/boot_picoboot/BUILD.gn
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/common/boot_picoboot"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("boot_picoboot") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-
-  # Optionally requires a dep on "pico/platform.h"
-
-  public = [ "${_CWD}/include/boot/picoboot.h" ]
-}
diff --git a/third_party/pico_sdk/src/common/boot_uf2/BUILD.gn b/third_party/pico_sdk/src/common/boot_uf2/BUILD.gn
deleted file mode 100644
index 2e6468b..0000000
--- a/third_party/pico_sdk/src/common/boot_uf2/BUILD.gn
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/common/boot_uf2"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("boot_uf2") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public = [ "${_CWD}/include/boot/uf2.h" ]
-}
diff --git a/third_party/pico_sdk/src/common/pico_base/BUILD.gn b/third_party/pico_sdk/src/common/pico_base/BUILD.gn
deleted file mode 100644
index 73e176d..0000000
--- a/third_party/pico_sdk/src/common/pico_base/BUILD.gn
+++ /dev/null
@@ -1,66 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/common/pico_base"
-
-import("generate_version_header.gni")
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-config("board_define") {
-  defines = [
-    "PICO_BOARD=${PICO_BOARD}",
-    "PICO_ON_DEVICE=1",
-    "PICO_NO_HARDWARE=0",
-    "PICO_BUILD=1",
-  ]
-}
-
-generate_version_header("version") {
-  version_major = PICO_SDK_VERSION_MAJOR
-  version_minor = PICO_SDK_VERSION_MINOR
-  version_revision = PICO_SDK_VERSION_REVISION
-  version_string = PICO_SDK_VERSION_STRING
-}
-
-pw_source_set("pico_base") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [
-    ":board_define",
-    ":public_include_dirs",
-  ]
-  public = [
-    "${_CWD}/include/pico.h",
-    "${_CWD}/include/pico/assert.h",
-    "${_CWD}/include/pico/config.h",
-    "${_CWD}/include/pico/error.h",
-    "${_CWD}/include/pico/types.h",
-  ]
-  public_deps = [
-    ":version",
-    "${PICO_GENERATED_CONFIG}",
-    "${PICO_ROOT}/src/rp2_common/pico_platform:headers",
-  ]
-  allow_circular_includes_from =
-      [ "${PICO_ROOT}/src/rp2_common/pico_platform:headers" ]
-}
diff --git a/third_party/pico_sdk/src/common/pico_base/generate_version_header.gni b/third_party/pico_sdk/src/common/pico_base/generate_version_header.gni
deleted file mode 100644
index 189bf7e..0000000
--- a/third_party/pico_sdk/src/common/pico_base/generate_version_header.gni
+++ /dev/null
@@ -1,80 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-declare_args() {
-  PICO_SDK_VERSION_MAJOR = 1
-  PICO_SDK_VERSION_MINOR = 3
-  PICO_SDK_VERSION_REVISION = 0
-}
-
-# GN-ism: To reference earlier args, this needs to be in a separate block.
-declare_args() {
-  PICO_SDK_VERSION_STRING = "${PICO_SDK_VERSION_MAJOR}.${PICO_SDK_VERSION_MINOR}.${PICO_SDK_VERSION_REVISION}"
-}
-
-template("generate_version_header") {
-  assert(defined(invoker.version_major))
-  assert(defined(invoker.version_minor))
-  assert(defined(invoker.version_revision))
-  assert(defined(invoker.version_string))
-
-  _generated_header_dir = "${target_gen_dir}/${target_name}_include"
-  _generated_header_path = "${_generated_header_dir}/pico/version.h"
-
-  config("${target_name}.public_include_dirs") {
-    include_dirs = [ "${_generated_header_dir}" ]
-  }
-
-  generated_file("${target_name}.generated_header") {
-    outputs = [ "${_generated_header_path}" ]
-    _lines = [
-      "// ---------------------------------------",
-      "// THIS FILE IS AUTOGENERATED; DO NOT EDIT",
-      "// ---------------------------------------",
-      "",
-      "#ifndef _PICO_VERSION_H",
-      "#define _PICO_VERSION_H",
-      "",
-      "#define PICO_SDK_VERSION_MAJOR    ${invoker.version_major}",
-      "#define PICO_SDK_VERSION_MINOR    ${invoker.version_minor}",
-      "#define PICO_SDK_VERSION_REVISION ${invoker.version_revision}",
-      "#define PICO_SDK_VERSION_STRING   \"${invoker.version_string}\"",
-      "",
-      "#endif",
-    ]
-
-    # Join with newline.
-    _NEWLINE_CHAR = "$0x0A"
-    contents = string_join(_NEWLINE_CHAR, _lines)
-  }
-
-  pw_source_set("${target_name}") {
-    remove_configs = [ "$dir_pw_build:strict_warnings" ]
-    public_configs = [ ":${target_name}.public_include_dirs" ]
-    deps = [ ":${target_name}.generated_header" ]
-    public = [ "${_generated_header_path}" ]
-    forward_variables_from(invoker,
-                           "*",
-                           [
-                             "version_major",
-                             "version_minor",
-                             "version_revision",
-                             "version_string",
-                           ])
-  }
-}
diff --git a/third_party/pico_sdk/src/common/pico_binary_info/BUILD.gn b/third_party/pico_sdk/src/common/pico_binary_info/BUILD.gn
deleted file mode 100644
index 86fbbb5..0000000
--- a/third_party/pico_sdk/src/common/pico_binary_info/BUILD.gn
+++ /dev/null
@@ -1,38 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/common/pico_binary_info"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("pico_binary_info") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [ "${PICO_ROOT}/src/common/pico_base" ]
-  public = [
-    "${_CWD}/include/pico/binary_info.h",
-    "${_CWD}/include/pico/binary_info/code.h",
-    "${_CWD}/include/pico/binary_info/defs.h",
-    "${_CWD}/include/pico/binary_info/structure.h",
-  ]
-}
diff --git a/third_party/pico_sdk/src/common/pico_bit_ops/BUILD.gn b/third_party/pico_sdk/src/common/pico_bit_ops/BUILD.gn
deleted file mode 100644
index 7a55fb1..0000000
--- a/third_party/pico_sdk/src/common/pico_bit_ops/BUILD.gn
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/common/pico_bit_ops"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("pico_bit_ops") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [ "${PICO_ROOT}/src/common/pico_base" ]
-  public = [ "${_CWD}/include/pico/bit_ops.h" ]
-}
diff --git a/third_party/pico_sdk/src/common/pico_divider/BUILD.gn b/third_party/pico_sdk/src/common/pico_divider/BUILD.gn
deleted file mode 100644
index dea76d4..0000000
--- a/third_party/pico_sdk/src/common/pico_divider/BUILD.gn
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/common/pico_divider"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("pico_divider") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/rp2_common/hardware_divider",
-  ]
-  public = [ "${_CWD}/include/pico/divider.h" ]
-}
diff --git a/third_party/pico_sdk/src/common/pico_stdlib/BUILD.gn b/third_party/pico_sdk/src/common/pico_stdlib/BUILD.gn
deleted file mode 100644
index 721254d..0000000
--- a/third_party/pico_sdk/src/common/pico_stdlib/BUILD.gn
+++ /dev/null
@@ -1,59 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/common/pico_stdlib"
-
-import("pico_stdio.gni")
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-  defines = [ "${PICO_STDIO}=1" ]
-}
-
-pw_source_set("headers") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/common/pico_time",
-    "${PICO_ROOT}/src/rp2_common/hardware_gpio",
-    "${PICO_ROOT}/src/rp2_common/hardware_uart",
-    "${PICO_ROOT}/src/rp2_common/pico_stdio",
-  ]
-
-  if (PICO_STDIO == ENUM_LIB_PICO_STDIO.UART) {
-    public_deps += [ "${PICO_ROOT}/src/rp2_common/pico_stdio_uart" ]
-  } else if (PICO_STDIO == ENUM_LIB_PICO_STDIO.USB) {
-    public_deps += [ "${PICO_ROOT}/src/rp2_common/pico_stdio_usb" ]
-  } else if (PICO_STDIO == ENUM_LIB_PICO_STDIO.SEMIHOSTING) {
-    public_deps += [ "${PICO_ROOT}/src/rp2_common/pico_stdio_semihosting" ]
-  }
-
-  public = [ "include/pico/stdlib.h" ]
-}
-
-pw_source_set("pico_stdlib") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_deps = [ ":headers" ]
-
-  # Ensure the pico stdlib implementation is linked in.
-  deps = [ "${PICO_ROOT}/src/rp2_common/pico_stdlib" ]
-}
diff --git a/third_party/pico_sdk/src/common/pico_stdlib/pico_stdio.gni b/third_party/pico_sdk/src/common/pico_stdlib/pico_stdio.gni
deleted file mode 100644
index 8d1f27b..0000000
--- a/third_party/pico_sdk/src/common/pico_stdlib/pico_stdio.gni
+++ /dev/null
@@ -1,24 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-ENUM_LIB_PICO_STDIO = {
-  UART = "LIB_PICO_STDIO_UART"
-  USB = "LIB_PICO_STDIO_USB"
-  SEMIHOSTING = "LIB_PICO_STDIO_SEMIHOSTING"
-}
-
-# TODO(amontanez): This looks like a facade. Rethink?
-declare_args() {
-  PICO_STDIO = ENUM_LIB_PICO_STDIO.UART
-}
diff --git a/third_party/pico_sdk/src/common/pico_sync/BUILD.gn b/third_party/pico_sdk/src/common/pico_sync/BUILD.gn
deleted file mode 100644
index 4fe1f46..0000000
--- a/third_party/pico_sdk/src/common/pico_sync/BUILD.gn
+++ /dev/null
@@ -1,49 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/common/pico_sync"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("pico_sync") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/common/pico_time:headers",
-    "${PICO_ROOT}/src/rp2_common/hardware_sync",
-  ]
-  public = [
-    "${_CWD}/include/pico/critical_section.h",
-    "${_CWD}/include/pico/lock_core.h",
-    "${_CWD}/include/pico/mutex.h",
-    "${_CWD}/include/pico/sem.h",
-    "${_CWD}/include/pico/sync.h",
-  ]
-  sources = [
-    "${_CWD}/critical_section.c",
-    "${_CWD}/lock_core.c",
-    "${_CWD}/mutex.c",
-    "${_CWD}/sem.c",
-  ]
-}
diff --git a/third_party/pico_sdk/src/common/pico_time/BUILD.gn b/third_party/pico_sdk/src/common/pico_time/BUILD.gn
deleted file mode 100644
index ca0dff6..0000000
--- a/third_party/pico_sdk/src/common/pico_time/BUILD.gn
+++ /dev/null
@@ -1,49 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/common/pico_time"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("headers") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/rp2_common/hardware_timer",
-  ]
-  public = [
-    "${_CWD}/include/pico/time.h",
-    "${_CWD}/include/pico/timeout_helper.h",
-  ]
-}
-
-pw_source_set("pico_time") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_deps = [ ":headers" ]
-  deps = [ "${PICO_ROOT}/src/common/pico_util" ]
-  sources = [
-    "${_CWD}/time.c",
-    "${_CWD}/timeout_helper.c",
-  ]
-}
diff --git a/third_party/pico_sdk/src/common/pico_usb_reset_interface/BUILD.gn b/third_party/pico_sdk/src/common/pico_usb_reset_interface/BUILD.gn
deleted file mode 100644
index 2c3abd8..0000000
--- a/third_party/pico_sdk/src/common/pico_usb_reset_interface/BUILD.gn
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/common/pico_usb_reset_interface"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("pico_usb_reset_interface") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public = [ "${_CWD}/include/pico/usb_reset_interface.h" ]
-}
diff --git a/third_party/pico_sdk/src/common/pico_util/BUILD.gn b/third_party/pico_sdk/src/common/pico_util/BUILD.gn
deleted file mode 100644
index eed47bd..0000000
--- a/third_party/pico_sdk/src/common/pico_util/BUILD.gn
+++ /dev/null
@@ -1,47 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/common/pico_util"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("pico_util") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/common/pico_sync",
-    "${PICO_ROOT}/src/rp2_common/hardware_sync",
-  ]
-  public = [
-    "${_CWD}/include/pico/util/datetime.h",
-    "${_CWD}/include/pico/util/pheap.h",
-    "${_CWD}/include/pico/util/queue.h",
-  ]
-  sources = [
-    "${_CWD}/datetime.c",
-    "${_CWD}/doc.h",
-    "${_CWD}/pheap.c",
-    "${_CWD}/queue.c",
-  ]
-}
diff --git a/third_party/pico_sdk/src/rp2040/BUILD.gn b/third_party/pico_sdk/src/rp2040/BUILD.gn
deleted file mode 100644
index c330097..0000000
--- a/third_party/pico_sdk/src/rp2040/BUILD.gn
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-import("${PICO_ROOT}/gn/generate_config_header.gni")
-group("rp2040") {
-  public_deps = [
-    "hardware_regs",
-    "hardware_structs",
-  ]
-}
-
-generate_config_header("rp2040_config") {
-  public_deps = [
-    "${PICO_ROOT}/src/boards",
-    "${PICO_ROOT}/src/rp2_common/cmsis:rename_exceptions",
-  ]
-  config_header_files = [
-    "boards/pico.h",
-    "cmsis/rename_exceptions.h",
-  ]
-}
diff --git a/third_party/pico_sdk/src/rp2040/hardware_regs/BUILD.gn b/third_party/pico_sdk/src/rp2040/hardware_regs/BUILD.gn
deleted file mode 100644
index 739d4bb..0000000
--- a/third_party/pico_sdk/src/rp2040/hardware_regs/BUILD.gn
+++ /dev/null
@@ -1,77 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2040/hardware_regs"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("platform_defs") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public = [ "${_CWD}/include/hardware/platform_defs.h" ]
-}
-
-pw_source_set("hardware_regs") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    ":platform_defs",
-    "${PICO_ROOT}/src/rp2_common/pico_platform:headers",
-  ]
-  public = [
-    "${_CWD}/include/hardware/regs/adc.h",
-    "${_CWD}/include/hardware/regs/addressmap.h",
-    "${_CWD}/include/hardware/regs/busctrl.h",
-    "${_CWD}/include/hardware/regs/clocks.h",
-    "${_CWD}/include/hardware/regs/dma.h",
-    "${_CWD}/include/hardware/regs/dreq.h",
-    "${_CWD}/include/hardware/regs/i2c.h",
-    "${_CWD}/include/hardware/regs/intctrl.h",
-    "${_CWD}/include/hardware/regs/io_bank0.h",
-    "${_CWD}/include/hardware/regs/io_qspi.h",
-    "${_CWD}/include/hardware/regs/m0plus.h",
-    "${_CWD}/include/hardware/regs/pads_bank0.h",
-    "${_CWD}/include/hardware/regs/pads_qspi.h",
-    "${_CWD}/include/hardware/regs/pio.h",
-    "${_CWD}/include/hardware/regs/pll.h",
-    "${_CWD}/include/hardware/regs/psm.h",
-    "${_CWD}/include/hardware/regs/pwm.h",
-    "${_CWD}/include/hardware/regs/resets.h",
-    "${_CWD}/include/hardware/regs/rosc.h",
-    "${_CWD}/include/hardware/regs/rtc.h",
-    "${_CWD}/include/hardware/regs/sio.h",
-    "${_CWD}/include/hardware/regs/spi.h",
-    "${_CWD}/include/hardware/regs/ssi.h",
-    "${_CWD}/include/hardware/regs/syscfg.h",
-    "${_CWD}/include/hardware/regs/sysinfo.h",
-    "${_CWD}/include/hardware/regs/tbman.h",
-    "${_CWD}/include/hardware/regs/timer.h",
-    "${_CWD}/include/hardware/regs/uart.h",
-    "${_CWD}/include/hardware/regs/usb.h",
-    "${_CWD}/include/hardware/regs/usb_device_dpram.h",
-    "${_CWD}/include/hardware/regs/vreg_and_chip_reset.h",
-    "${_CWD}/include/hardware/regs/watchdog.h",
-    "${_CWD}/include/hardware/regs/xip.h",
-    "${_CWD}/include/hardware/regs/xosc.h",
-  ]
-}
diff --git a/third_party/pico_sdk/src/rp2040/hardware_structs/BUILD.gn b/third_party/pico_sdk/src/rp2040/hardware_structs/BUILD.gn
deleted file mode 100644
index b472f5e..0000000
--- a/third_party/pico_sdk/src/rp2040/hardware_structs/BUILD.gn
+++ /dev/null
@@ -1,69 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2040/hardware_structs"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("hardware_structs") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/rp2040/hardware_regs",
-    "${PICO_ROOT}/src/rp2040/hardware_regs:platform_defs",
-    "${PICO_ROOT}/src/rp2_common/hardware_base",
-  ]
-  public = [
-    "${_CWD}/include/hardware/structs/adc.h",
-    "${_CWD}/include/hardware/structs/bus_ctrl.h",
-    "${_CWD}/include/hardware/structs/clocks.h",
-    "${_CWD}/include/hardware/structs/dma.h",
-    "${_CWD}/include/hardware/structs/i2c.h",
-    "${_CWD}/include/hardware/structs/interp.h",
-    "${_CWD}/include/hardware/structs/iobank0.h",
-    "${_CWD}/include/hardware/structs/ioqspi.h",
-    "${_CWD}/include/hardware/structs/mpu.h",
-    "${_CWD}/include/hardware/structs/pads_qspi.h",
-    "${_CWD}/include/hardware/structs/padsbank0.h",
-    "${_CWD}/include/hardware/structs/pio.h",
-    "${_CWD}/include/hardware/structs/pll.h",
-    "${_CWD}/include/hardware/structs/psm.h",
-    "${_CWD}/include/hardware/structs/pwm.h",
-    "${_CWD}/include/hardware/structs/resets.h",
-    "${_CWD}/include/hardware/structs/rosc.h",
-    "${_CWD}/include/hardware/structs/rtc.h",
-    "${_CWD}/include/hardware/structs/scb.h",
-    "${_CWD}/include/hardware/structs/sio.h",
-    "${_CWD}/include/hardware/structs/spi.h",
-    "${_CWD}/include/hardware/structs/ssi.h",
-    "${_CWD}/include/hardware/structs/syscfg.h",
-    "${_CWD}/include/hardware/structs/systick.h",
-    "${_CWD}/include/hardware/structs/timer.h",
-    "${_CWD}/include/hardware/structs/uart.h",
-    "${_CWD}/include/hardware/structs/usb.h",
-    "${_CWD}/include/hardware/structs/vreg_and_chip_reset.h",
-    "${_CWD}/include/hardware/structs/watchdog.h",
-    "${_CWD}/include/hardware/structs/xip_ctrl.h",
-    "${_CWD}/include/hardware/structs/xosc.h",
-  ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/BUILD.gn b/third_party/pico_sdk/src/rp2_common/BUILD.gn
deleted file mode 100644
index 494af4a..0000000
--- a/third_party/pico_sdk/src/rp2_common/BUILD.gn
+++ /dev/null
@@ -1,73 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-group("rp2_common") {
-  public_deps = [
-    "hardware_adc",
-    "hardware_base",
-    "hardware_claim",
-    "hardware_clocks",
-    "hardware_divider",
-    "hardware_dma",
-    "hardware_exception",
-    "hardware_flash",
-    "hardware_gpio",
-    "hardware_i2c",
-    "hardware_interp",
-    "hardware_irq",
-    "hardware_pio",
-    "hardware_pll",
-    "hardware_pwm",
-    "hardware_resets",
-    "hardware_rtc",
-    "hardware_spi",
-    "hardware_sync",
-    "hardware_timer",
-    "hardware_uart",
-    "hardware_vreg",
-    "hardware_watchdog",
-    "hardware_xosc",
-    "pico_bootrom",
-    "pico_platform",
-  ]
-
-  if (!PICO_BARE_METAL) {
-    public_deps += [
-      "boot_stage2",
-      "cmsis",
-      "pico_bit_ops",
-      "pico_bootsel_via_double_reset",
-      "pico_divider",
-      "pico_double",
-      "pico_fix",
-      "pico_float",
-      "pico_int64_ops",
-      "pico_malloc",
-      "pico_mem_ops",
-      "pico_multicore",
-      "pico_printf",
-      "pico_runtime",
-      "pico_standard_link",
-      "pico_stdio",
-      "pico_stdio_semihosting",
-      "pico_stdio_uart",
-      "pico_stdio_usb",
-      "pico_stdlib",
-      "pico_unique_id",
-      "tinyusb",
-    ]
-    # Not a real library:
-    #   pico_cxx_options
-  }
-}
diff --git a/third_party/pico_sdk/src/rp2_common/boot_stage2/BUILD.gn b/third_party/pico_sdk/src/rp2_common/boot_stage2/BUILD.gn
deleted file mode 100644
index d23802a..0000000
--- a/third_party/pico_sdk/src/rp2_common/boot_stage2/BUILD.gn
+++ /dev/null
@@ -1,114 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/exec.gni")
-import("$dir_pw_build/python_action.gni")
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/boot_stage2"
-
-config("public_include_dirs") {
-  include_dirs = [
-    "${_CWD}/include",
-    "${_CWD}/asminclude",
-  ]
-}
-
-pw_executable("boot_stage2_elf") {
-  _linker_script_path = rebase_path("${_CWD}/boot_stage2.ld", root_build_dir)
-
-  # Compile as position-independent.
-  cflags = [ "-fPIC" ]
-  asmflags = cflags
-
-  ldflags = cflags
-  ldflags += [
-    "-T${_linker_script_path}",
-    "-nostartfiles",
-
-    # Unfortunately, this is not properly applied to compiler flags thanks to
-    # `default_configs`.
-    "-Wl,--no-gc-sections",
-  ]
-
-  public_configs = [ ":public_include_dirs" ]
-
-  # The upstream boot_stage2.ld doesn't specify the binary entry point or
-  # mark the required sections as KEEP(), so they're optimized out with
-  # Pigweed's aggressive default optimizations.
-  remove_configs = [
-    "$dir_pw_build:reduced_size",
-    "$dir_pw_build:strict_warnings",
-  ]
-  no_link_deps = true
-
-  public = [ "${_CWD}/include/boot_stage2/config.h" ]
-
-  deps = [ "${PICO_ROOT}/src/common/pico_base" ]
-
-  # The correct assembly file is pulled in by compile_time_choice.S.
-  inputs = [
-    "${_CWD}/boot_stage2.ld",
-    "${_CWD}/boot2_at25sf128a.S",
-    "${_CWD}/boot2_generic_03h.S",
-    "${_CWD}/boot2_is25lp080.S",
-    "${_CWD}/boot2_usb_blinky.S",
-    "${_CWD}/boot2_w25q080.S",
-    "${_CWD}/boot2_w25x10cl.S",
-  ]
-  sources = [ "${_CWD}/compile_time_choice.S" ]
-}
-
-pw_exec("boot_stage2_bin") {
-  _out_bin = "${target_out_dir}/boot_stage2.bin"
-  program = "arm-none-eabi-objcopy"
-  args = [
-    "-Obinary",
-    "<TARGET_FILE(:boot_stage2_elf)>",
-    rebase_path(_out_bin, root_build_dir),
-  ]
-  outputs = [ _out_bin ]
-  deps = [ ":boot_stage2_elf" ]
-}
-
-pw_python_action("boot_stage2_padded") {
-  _src_bin = get_target_outputs(":boot_stage2_bin")
-  _out_asm = "${target_out_dir}/boot_stage2.S"
-  script = "${_CWD}/pad_checksum"
-  args = [
-    "-s",
-    "0xffffffff",
-    rebase_path(_src_bin[0], root_build_dir),
-    rebase_path(_out_asm, root_build_dir),
-  ]
-  outputs = [ _out_asm ]
-  deps = [ ":boot_stage2_bin" ]
-}
-
-pw_source_set("boot_stage2_asm") {
-  deps = [ ":boot_stage2_padded" ]
-  sources = get_target_outputs(":boot_stage2_padded")
-}
-
-group("boot_stage2") {
-  public_deps = [
-    ":boot_stage2_asm",
-    ":boot_stage2_elf",
-  ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/cmsis/BUILD.gn b/third_party/pico_sdk/src/rp2_common/cmsis/BUILD.gn
deleted file mode 100644
index 94c8f41..0000000
--- a/third_party/pico_sdk/src/rp2_common/cmsis/BUILD.gn
+++ /dev/null
@@ -1,59 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/cmsis"
-
-config("public_include_dirs") {
-  include_dirs = [
-    "${_CWD}/include",
-    "${_CWD}/stub/CMSIS/Core/Include",
-    "${_CWD}/stub/CMSIS/Device/RaspberryPi/RP2040/Include",
-  ]
-}
-
-pw_source_set("rename_exceptions") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public = [ "include/cmsis/rename_exceptions.h" ]
-}
-
-# TODO(amontanez): The CMSIS stub should probably be more configurable to match
-# CMake.
-pw_source_set("cmsis") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public = [
-    "${_CWD}/stub/CMSIS/Core/Include/cmsis_armcc.h",
-    "${_CWD}/stub/CMSIS/Core/Include/cmsis_armclang.h",
-    "${_CWD}/stub/CMSIS/Core/Include/cmsis_armclang_ltm.h",
-    "${_CWD}/stub/CMSIS/Core/Include/cmsis_compiler.h",
-    "${_CWD}/stub/CMSIS/Core/Include/cmsis_gcc.h",
-    "${_CWD}/stub/CMSIS/Core/Include/cmsis_iccarm.h",
-    "${_CWD}/stub/CMSIS/Core/Include/cmsis_version.h",
-    "${_CWD}/stub/CMSIS/Core/Include/core_cm0plus.h",
-    "${_CWD}/stub/CMSIS/Core/Include/mpu_armv7.h",
-    "${_CWD}/stub/CMSIS/Device/RaspberryPi/RP2040/Include/RP2040.h",
-    "${_CWD}/stub/CMSIS/Device/RaspberryPi/RP2040/Include/system_RP2040.h",
-  ]
-  deps = [ "${PICO_ROOT}/src/rp2_common/hardware_clocks" ]
-  sources =
-      [ "${_CWD}/stub/CMSIS/Device/RaspberryPi/RP2040/Source/system_RP2040.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/hardware_adc/BUILD.gn b/third_party/pico_sdk/src/rp2_common/hardware_adc/BUILD.gn
deleted file mode 100644
index 7a93646..0000000
--- a/third_party/pico_sdk/src/rp2_common/hardware_adc/BUILD.gn
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/hardware_adc"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("hardware_adc") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/rp2040/hardware_structs",
-    "${PICO_ROOT}/src/rp2_common/hardware_gpio",
-  ]
-  deps = [ "${PICO_ROOT}/src/rp2_common/hardware_resets" ]
-  public = [ "${_CWD}/include/hardware/adc.h" ]
-  sources = [ "${_CWD}/adc.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/hardware_base/BUILD.gn b/third_party/pico_sdk/src/rp2_common/hardware_base/BUILD.gn
deleted file mode 100644
index 00909d4..0000000
--- a/third_party/pico_sdk/src/rp2_common/hardware_base/BUILD.gn
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/hardware_base"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("hardware_base") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/rp2040/hardware_regs",
-  ]
-  public = [ "${_CWD}/include/hardware/address_mapped.h" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/hardware_claim/BUILD.gn b/third_party/pico_sdk/src/rp2_common/hardware_claim/BUILD.gn
deleted file mode 100644
index 9ced82c..0000000
--- a/third_party/pico_sdk/src/rp2_common/hardware_claim/BUILD.gn
+++ /dev/null
@@ -1,41 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/hardware_claim"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("hardware_claim") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/rp2_common/hardware_sync",
-  ]
-
-  # hardware_claim and hardware_sync circularly depend on each other.
-  allow_circular_includes_from = [ "${PICO_ROOT}/src/rp2_common/hardware_sync" ]
-
-  public = [ "${_CWD}/include/hardware/claim.h" ]
-  sources = [ "${_CWD}/claim.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/hardware_clocks/BUILD.gn b/third_party/pico_sdk/src/rp2_common/hardware_clocks/BUILD.gn
deleted file mode 100644
index dd4eb47..0000000
--- a/third_party/pico_sdk/src/rp2_common/hardware_clocks/BUILD.gn
+++ /dev/null
@@ -1,54 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/hardware_clocks"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("hardware_clocks") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public = [ "${_CWD}/include/hardware/clocks.h" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/rp2040/hardware_structs",
-  ]
-  deps = [
-    "${PICO_ROOT}/src/rp2040/hardware_regs",
-    "${PICO_ROOT}/src/rp2040/hardware_regs:platform_defs",
-    "${PICO_ROOT}/src/rp2_common/hardware_gpio",
-    "${PICO_ROOT}/src/rp2_common/hardware_irq",
-    "${PICO_ROOT}/src/rp2_common/hardware_pll",
-    "${PICO_ROOT}/src/rp2_common/hardware_watchdog",
-    "${PICO_ROOT}/src/rp2_common/hardware_xosc",
-  ]
-
-  # hardware_pll and hardware_clocks circularly depend on each other.
-  # hardware_xosc and hardware_clocks circularly depend on each other.
-  allow_circular_includes_from = [
-    "${PICO_ROOT}/src/rp2_common/hardware_pll",
-    "${PICO_ROOT}/src/rp2_common/hardware_xosc",
-  ]
-
-  sources = [ "${_CWD}/clocks.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/hardware_divider/BUILD.gn b/third_party/pico_sdk/src/rp2_common/hardware_divider/BUILD.gn
deleted file mode 100644
index 25e1a5f..0000000
--- a/third_party/pico_sdk/src/rp2_common/hardware_divider/BUILD.gn
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/hardware_divider"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("hardware_divider") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/rp2040/hardware_structs",
-  ]
-  deps = [
-    "${PICO_ROOT}/src/rp2040/hardware_regs",
-    "${PICO_ROOT}/src/rp2_common/pico_platform",
-  ]
-  public = [
-    "${_CWD}/include/hardware/divider.h",
-    "${_CWD}/include/hardware/divider_helper.S",
-  ]
-  sources = [ "${_CWD}/divider.S" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/hardware_dma/BUILD.gn b/third_party/pico_sdk/src/rp2_common/hardware_dma/BUILD.gn
deleted file mode 100644
index 33ca25c..0000000
--- a/third_party/pico_sdk/src/rp2_common/hardware_dma/BUILD.gn
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/hardware_dma"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("hardware_dma") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/rp2040/hardware_regs",
-    "${PICO_ROOT}/src/rp2040/hardware_structs",
-  ]
-  deps = [ "${PICO_ROOT}/src/rp2_common/hardware_claim" ]
-  public = [ "${_CWD}/include/hardware/dma.h" ]
-  sources = [ "${_CWD}/dma.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/hardware_exception/BUILD.gn b/third_party/pico_sdk/src/rp2_common/hardware_exception/BUILD.gn
deleted file mode 100644
index cb8e2de..0000000
--- a/third_party/pico_sdk/src/rp2_common/hardware_exception/BUILD.gn
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/hardware_exception"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("hardware_exception") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/rp2040/hardware_regs",
-    "${PICO_ROOT}/src/rp2_common/hardware_base",
-  ]
-  deps = [
-    "${PICO_ROOT}/src/common/pico_sync",
-    "${PICO_ROOT}/src/rp2040/hardware_regs:platform_defs",
-    "${PICO_ROOT}/src/rp2040/hardware_structs",
-  ]
-  public = [ "${_CWD}/include/hardware/exception.h" ]
-  sources = [ "${_CWD}/exception.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/hardware_flash/BUILD.gn b/third_party/pico_sdk/src/rp2_common/hardware_flash/BUILD.gn
deleted file mode 100644
index 6e3e190..0000000
--- a/third_party/pico_sdk/src/rp2_common/hardware_flash/BUILD.gn
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/hardware_flash"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("hardware_flash") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [ "${PICO_ROOT}/src/common/pico_base" ]
-  deps = [
-    "${PICO_ROOT}/src/common/pico_sync",
-    "${PICO_ROOT}/src/rp2040/hardware_structs",
-    "${PICO_ROOT}/src/rp2_common/pico_bootrom",
-  ]
-  public = [ "${_CWD}/include/hardware/flash.h" ]
-  sources = [ "${_CWD}/flash.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/hardware_gpio/BUILD.gn b/third_party/pico_sdk/src/rp2_common/hardware_gpio/BUILD.gn
deleted file mode 100644
index 193f323..0000000
--- a/third_party/pico_sdk/src/rp2_common/hardware_gpio/BUILD.gn
+++ /dev/null
@@ -1,45 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/hardware_gpio"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("hardware_gpio") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/rp2040/hardware_structs",
-  ]
-  deps = [
-    # TODO(amontanez): This is off by default, properly control with
-    # configuration.
-    # "${PICO_ROOT}/src/common/pico_binary_info",
-
-    "${PICO_ROOT}/src/rp2_common/hardware_irq",
-    "${PICO_ROOT}/src/rp2_common/hardware_sync",
-  ]
-  public = [ "${_CWD}/include/hardware/gpio.h" ]
-  sources = [ "${_CWD}/gpio.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/hardware_i2c/BUILD.gn b/third_party/pico_sdk/src/rp2_common/hardware_i2c/BUILD.gn
deleted file mode 100644
index 1c3f717..0000000
--- a/third_party/pico_sdk/src/rp2_common/hardware_i2c/BUILD.gn
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/hardware_i2c"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("hardware_i2c") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/common/pico_time",
-    "${PICO_ROOT}/src/rp2040/hardware_structs",
-  ]
-  deps = [
-    "${PICO_ROOT}/src/common/pico_time",
-    "${PICO_ROOT}/src/rp2_common/hardware_clocks",
-    "${PICO_ROOT}/src/rp2_common/hardware_resets",
-  ]
-  public = [ "${_CWD}/include/hardware/i2c.h" ]
-  sources = [ "${_CWD}/i2c.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/hardware_interp/BUILD.gn b/third_party/pico_sdk/src/rp2_common/hardware_interp/BUILD.gn
deleted file mode 100644
index 6a2dec8..0000000
--- a/third_party/pico_sdk/src/rp2_common/hardware_interp/BUILD.gn
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/hardware_interp"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("hardware_interp") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/rp2040/hardware_regs",
-    "${PICO_ROOT}/src/rp2040/hardware_structs",
-  ]
-  deps = [ "${PICO_ROOT}/src/rp2_common/hardware_claim" ]
-  public = [ "${_CWD}/include/hardware/interp.h" ]
-  sources = [ "${_CWD}/interp.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/hardware_irq/BUILD.gn b/third_party/pico_sdk/src/rp2_common/hardware_irq/BUILD.gn
deleted file mode 100644
index a21900e..0000000
--- a/third_party/pico_sdk/src/rp2_common/hardware_irq/BUILD.gn
+++ /dev/null
@@ -1,53 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/hardware_irq"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("hardware_irq") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/rp2040/hardware_regs",
-    "${PICO_ROOT}/src/rp2_common/hardware_base",
-  ]
-  deps = [
-    "${PICO_ROOT}/src/common/pico_sync",
-    "${PICO_ROOT}/src/rp2040/hardware_regs:platform_defs",
-    "${PICO_ROOT}/src/rp2040/hardware_structs",
-  ]
-
-  # There's a dependency cycle with:
-  #   hardware_irq->pico_sync->pico_time->hardware_timer->hardware_irq
-  deps += [ "${PICO_ROOT}/src/rp2_common/hardware_timer" ]
-  allow_circular_includes_from =
-      [ "${PICO_ROOT}/src/rp2_common/hardware_timer" ]
-
-  public = [ "${_CWD}/include/hardware/irq.h" ]
-  sources = [
-    "${_CWD}/irq.c",
-    "${_CWD}/irq_handler_chain.S",
-  ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/hardware_pio/BUILD.gn b/third_party/pico_sdk/src/rp2_common/hardware_pio/BUILD.gn
deleted file mode 100644
index f1fc257..0000000
--- a/third_party/pico_sdk/src/rp2_common/hardware_pio/BUILD.gn
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/hardware_pio"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("hardware_pio") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/rp2040/hardware_regs",
-    "${PICO_ROOT}/src/rp2040/hardware_structs",
-    "${PICO_ROOT}/src/rp2_common/hardware_base",
-    "${PICO_ROOT}/src/rp2_common/hardware_gpio",
-  ]
-  deps = [ "${PICO_ROOT}/src/rp2_common/hardware_claim" ]
-  public = [
-    "${_CWD}/include/hardware/pio.h",
-    "${_CWD}/include/hardware/pio_instructions.h",
-  ]
-  sources = [ "${_CWD}/pio.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/hardware_pll/BUILD.gn b/third_party/pico_sdk/src/rp2_common/hardware_pll/BUILD.gn
deleted file mode 100644
index 837611d..0000000
--- a/third_party/pico_sdk/src/rp2_common/hardware_pll/BUILD.gn
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/hardware_pll"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("hardware_pll") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/rp2040/hardware_structs",
-  ]
-  deps = [ "${PICO_ROOT}/src/rp2_common/hardware_resets" ]
-
-  # hardware_pll and hardware_clocks circularly depend on each other.
-  configs =
-      [ "${PICO_ROOT}/src/rp2_common/hardware_clocks:public_include_dirs" ]
-
-  public = [ "${_CWD}/include/hardware/pll.h" ]
-  sources = [ "${_CWD}/pll.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/hardware_pwm/BUILD.gn b/third_party/pico_sdk/src/rp2_common/hardware_pwm/BUILD.gn
deleted file mode 100644
index 968ef3e..0000000
--- a/third_party/pico_sdk/src/rp2_common/hardware_pwm/BUILD.gn
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/hardware_pwm"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("hardware_pwm") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/rp2040/hardware_regs",
-    "${PICO_ROOT}/src/rp2040/hardware_structs",
-  ]
-  public = [ "${_CWD}/include/hardware/pwm.h" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/hardware_resets/BUILD.gn b/third_party/pico_sdk/src/rp2_common/hardware_resets/BUILD.gn
deleted file mode 100644
index 92beff0..0000000
--- a/third_party/pico_sdk/src/rp2_common/hardware_resets/BUILD.gn
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/hardware_resets"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("hardware_resets") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/rp2040/hardware_structs",
-  ]
-  public = [ "${_CWD}/include/hardware/resets.h" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/hardware_rtc/BUILD.gn b/third_party/pico_sdk/src/rp2_common/hardware_rtc/BUILD.gn
deleted file mode 100644
index b59a0f2..0000000
--- a/third_party/pico_sdk/src/rp2_common/hardware_rtc/BUILD.gn
+++ /dev/null
@@ -1,42 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/hardware_rtc"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("hardware_rtc") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/rp2040/hardware_structs",
-  ]
-  deps = [
-    "${PICO_ROOT}/src/rp2_common/hardware_clocks",
-    "${PICO_ROOT}/src/rp2_common/hardware_irq",
-    "${PICO_ROOT}/src/rp2_common/hardware_resets",
-  ]
-  public = [ "${_CWD}/include/hardware/rtc.h" ]
-  sources = [ "${_CWD}/rtc.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/hardware_spi/BUILD.gn b/third_party/pico_sdk/src/rp2_common/hardware_spi/BUILD.gn
deleted file mode 100644
index 42f5597..0000000
--- a/third_party/pico_sdk/src/rp2_common/hardware_spi/BUILD.gn
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/hardware_spi"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("hardware_spi") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/common/pico_time",
-    "${PICO_ROOT}/src/rp2040/hardware_regs",
-    "${PICO_ROOT}/src/rp2040/hardware_structs",
-  ]
-  deps = [
-    "${PICO_ROOT}/src/rp2_common/hardware_clocks",
-    "${PICO_ROOT}/src/rp2_common/hardware_resets",
-  ]
-  public = [ "${_CWD}/include/hardware/spi.h" ]
-  sources = [ "${_CWD}/spi.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/hardware_sync/BUILD.gn b/third_party/pico_sdk/src/rp2_common/hardware_sync/BUILD.gn
deleted file mode 100644
index 442aab3..0000000
--- a/third_party/pico_sdk/src/rp2_common/hardware_sync/BUILD.gn
+++ /dev/null
@@ -1,42 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/hardware_sync"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("hardware_sync") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/rp2040/hardware_regs",
-    "${PICO_ROOT}/src/rp2_common/hardware_base",
-  ]
-
-  # hardware_claim and hardware_sync circularly depend on each other.
-  configs = [ "${PICO_ROOT}/src/rp2_common/hardware_claim:public_include_dirs" ]
-
-  public = [ "${_CWD}/include/hardware/sync.h" ]
-  sources = [ "${_CWD}/sync.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/hardware_timer/BUILD.gn b/third_party/pico_sdk/src/rp2_common/hardware_timer/BUILD.gn
deleted file mode 100644
index 6abf5ac..0000000
--- a/third_party/pico_sdk/src/rp2_common/hardware_timer/BUILD.gn
+++ /dev/null
@@ -1,45 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/hardware_timer"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("hardware_timer") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/rp2040/hardware_structs",
-  ]
-  deps = [
-    "${PICO_ROOT}/src/rp2_common/hardware_claim",
-    "${PICO_ROOT}/src/rp2_common/hardware_sync",
-  ]
-
-  # There's a dependency cycle with:
-  #   hardware_irq->pico_sync->pico_time->hardware_timer->hardware_irq
-  configs = [ "${PICO_ROOT}/src/rp2_common/hardware_irq:public_include_dirs" ]
-  public = [ "${_CWD}/include/hardware/timer.h" ]
-  sources = [ "${_CWD}/timer.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/hardware_uart/BUILD.gn b/third_party/pico_sdk/src/rp2_common/hardware_uart/BUILD.gn
deleted file mode 100644
index 0c71aae..0000000
--- a/third_party/pico_sdk/src/rp2_common/hardware_uart/BUILD.gn
+++ /dev/null
@@ -1,45 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/hardware_uart"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("hardware_uart") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/rp2040/hardware_regs",
-    "${PICO_ROOT}/src/rp2040/hardware_structs",
-  ]
-  deps = [
-    "${PICO_ROOT}/src/rp2040/hardware_regs:platform_defs",
-    "${PICO_ROOT}/src/rp2_common/hardware_base",
-    "${PICO_ROOT}/src/rp2_common/hardware_clocks",
-    "${PICO_ROOT}/src/rp2_common/hardware_resets",
-    "${PICO_ROOT}/src/rp2_common/hardware_timer",
-  ]
-  public = [ "${_CWD}/include/hardware/uart.h" ]
-  sources = [ "${_CWD}/uart.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/hardware_vreg/BUILD.gn b/third_party/pico_sdk/src/rp2_common/hardware_vreg/BUILD.gn
deleted file mode 100644
index 1815fc0..0000000
--- a/third_party/pico_sdk/src/rp2_common/hardware_vreg/BUILD.gn
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/hardware_vreg"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("hardware_vreg") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/rp2040/hardware_structs",
-  ]
-  public = [ "${_CWD}/include/hardware/vreg.h" ]
-  sources = [ "${_CWD}/vreg.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/hardware_watchdog/BUILD.gn b/third_party/pico_sdk/src/rp2_common/hardware_watchdog/BUILD.gn
deleted file mode 100644
index 644f722..0000000
--- a/third_party/pico_sdk/src/rp2_common/hardware_watchdog/BUILD.gn
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/hardware_watchdog"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("hardware_watchdog") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/rp2040/hardware_structs",
-  ]
-  public = [ "${_CWD}/include/hardware/watchdog.h" ]
-  sources = [ "${_CWD}/watchdog.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/hardware_xosc/BUILD.gn b/third_party/pico_sdk/src/rp2_common/hardware_xosc/BUILD.gn
deleted file mode 100644
index a2d77d8..0000000
--- a/third_party/pico_sdk/src/rp2_common/hardware_xosc/BUILD.gn
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/hardware_xosc"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("hardware_xosc") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/rp2040/hardware_structs",
-  ]
-  deps = [ "${PICO_ROOT}/src/rp2040/hardware_regs:platform_defs" ]
-
-  # hardware_pll and hardware_clocks circularly depend on each other.
-  configs =
-      [ "${PICO_ROOT}/src/rp2_common/hardware_clocks:public_include_dirs" ]
-
-  public = [ "${_CWD}/include/hardware/xosc.h" ]
-  sources = [ "${_CWD}/xosc.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/pico_bit_ops/BUILD.gn b/third_party/pico_sdk/src/rp2_common/pico_bit_ops/BUILD.gn
deleted file mode 100644
index f799a72..0000000
--- a/third_party/pico_sdk/src/rp2_common/pico_bit_ops/BUILD.gn
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/pico_bit_ops"
-
-pw_source_set("pico_bit_ops") {
-  deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/rp2_common/pico_bootrom",
-    "${PICO_ROOT}/src/rp2_common/pico_platform",
-  ]
-  sources = [ "${_CWD}/bit_ops_eabi.S" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/pico_bootrom/BUILD.gn b/third_party/pico_sdk/src/rp2_common/pico_bootrom/BUILD.gn
deleted file mode 100644
index d1d28a2..0000000
--- a/third_party/pico_sdk/src/rp2_common/pico_bootrom/BUILD.gn
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/pico_bootrom"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("pico_bootrom") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [ "${PICO_ROOT}/src/common/pico_base" ]
-  public = [
-    "${_CWD}/include/pico/bootrom.h",
-    "${_CWD}/include/pico/bootrom/sf_table.h",
-  ]
-  sources = [ "${_CWD}/bootrom.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/pico_bootsel_via_double_reset/BUILD.gn b/third_party/pico_sdk/src/rp2_common/pico_bootsel_via_double_reset/BUILD.gn
deleted file mode 100644
index 5f61527..0000000
--- a/third_party/pico_sdk/src/rp2_common/pico_bootsel_via_double_reset/BUILD.gn
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/pico_bootsel_via_double_reset"
-
-pw_source_set("pico_bootsel_via_double_reset") {
-  deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/common/pico_binary_info",
-    "${PICO_ROOT}/src/rp2_common/pico_bootrom",
-    "${PICO_ROOT}/src/rp2_common/pico_time",
-  ]
-  sources = [ "${_CWD}/pico_bootsel_via_double_reset.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/pico_divider/BUILD.gn b/third_party/pico_sdk/src/rp2_common/pico_divider/BUILD.gn
deleted file mode 100644
index e231a82..0000000
--- a/third_party/pico_sdk/src/rp2_common/pico_divider/BUILD.gn
+++ /dev/null
@@ -1,30 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/pico_divider"
-
-pw_source_set("pico_divider") {
-  deps = [
-    "${PICO_ROOT}/src/rp2040/hardware_regs",
-    "${PICO_ROOT}/src/rp2_common/hardware_divider",
-  ]
-  sources = [ "${_CWD}/divider.S" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/pico_double/BUILD.gn b/third_party/pico_sdk/src/rp2_common/pico_double/BUILD.gn
deleted file mode 100644
index ef7feb7..0000000
--- a/third_party/pico_sdk/src/rp2_common/pico_double/BUILD.gn
+++ /dev/null
@@ -1,47 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/pico_double"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("pico_double") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/rp2_common/pico_bootrom",
-  ]
-  deps = [
-    "${PICO_ROOT}/src/rp2_common/hardware_divider",
-    "${PICO_ROOT}/src/rp2_common/pico_platform",
-  ]
-  public = [ "${_CWD}/include/pico/double.h" ]
-  sources = [
-    "${_CWD}/double_aeabi.S",
-    "${_CWD}/double_init_rom.c",
-    "${_CWD}/double_math.c",
-    "${_CWD}/double_none.S",
-    "${_CWD}/double_v1_rom_shim.S",
-  ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/pico_fix/BUILD.gn b/third_party/pico_sdk/src/rp2_common/pico_fix/BUILD.gn
deleted file mode 100644
index f9397b1..0000000
--- a/third_party/pico_sdk/src/rp2_common/pico_fix/BUILD.gn
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-group("pico_fix") {
-  public_deps = [ "rp2040_usb_device_enumeration" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/pico_fix/rp2040_usb_device_enumeration/BUILD.gn b/third_party/pico_sdk/src/rp2_common/pico_fix/rp2040_usb_device_enumeration/BUILD.gn
deleted file mode 100644
index 99d8cd4..0000000
--- a/third_party/pico_sdk/src/rp2_common/pico_fix/rp2040_usb_device_enumeration/BUILD.gn
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/pico_fix/rp2040_usb_device_enumeration"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("rp2040_usb_device_enumeration") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/rp2040/hardware_structs",
-    "${PICO_ROOT}/src/rp2_common/hardware_gpio",
-    "${PICO_ROOT}/src/rp2_common/pico_time",
-  ]
-  public = [ "${_CWD}/include/pico/fix/rp2040_usb_device_enumeration.h" ]
-  sources = [ "${_CWD}/rp2040_usb_device_enumberation.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/pico_float/BUILD.gn b/third_party/pico_sdk/src/rp2_common/pico_float/BUILD.gn
deleted file mode 100644
index 04c0fdf..0000000
--- a/third_party/pico_sdk/src/rp2_common/pico_float/BUILD.gn
+++ /dev/null
@@ -1,47 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/pico_float"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("pico_float") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/rp2_common/pico_bootrom",
-  ]
-  deps = [
-    "${PICO_ROOT}/src/rp2_common/hardware_divider",
-    "${PICO_ROOT}/src/rp2_common/pico_platform",
-  ]
-  public = [ "${_CWD}/include/pico/float.h" ]
-  sources = [
-    "${_CWD}/float_aeabi.S",
-    "${_CWD}/float_init_rom.c",
-    "${_CWD}/float_math.c",
-    "${_CWD}/float_none.S",
-    "${_CWD}/float_v1_rom_shim.S",
-  ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/pico_int64_ops/BUILD.gn b/third_party/pico_sdk/src/rp2_common/pico_int64_ops/BUILD.gn
deleted file mode 100644
index 6458280..0000000
--- a/third_party/pico_sdk/src/rp2_common/pico_int64_ops/BUILD.gn
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/pico_int64_ops"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("pico_int64_ops") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [ "${PICO_ROOT}/src/common/pico_base" ]
-  deps = [ "${PICO_ROOT}/src/rp2_common/pico_platform" ]
-  public = [ "${_CWD}/include/pico/int64_ops.h" ]
-  sources = [ "${_CWD}/pico_int64_ops_aeabi.S" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/pico_malloc/BUILD.gn b/third_party/pico_sdk/src/rp2_common/pico_malloc/BUILD.gn
deleted file mode 100644
index ad1afbe..0000000
--- a/third_party/pico_sdk/src/rp2_common/pico_malloc/BUILD.gn
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/pico_malloc"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("pico_malloc") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/common/pico_sync",
-  ]
-  public = [ "${_CWD}/include/pico/malloc.h" ]
-  sources = [ "${_CWD}/pico_malloc.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/pico_mem_ops/BUILD.gn b/third_party/pico_sdk/src/rp2_common/pico_mem_ops/BUILD.gn
deleted file mode 100644
index df40c7e..0000000
--- a/third_party/pico_sdk/src/rp2_common/pico_mem_ops/BUILD.gn
+++ /dev/null
@@ -1,41 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/pico_mem_ops"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("pico_mem_ops") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [ "${PICO_ROOT}/src/common/pico_base" ]
-  deps = [
-    "${PICO_ROOT}/src/rp2_common/pico_bootrom",
-    "${PICO_ROOT}/src/rp2_common/pico_platform",
-  ]
-  public = [ "${_CWD}/include/pico/mem_ops.h" ]
-  sources = [
-    "${_CWD}/mem_ops.c",
-    "${_CWD}/mem_ops_aeabi.S",
-  ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/pico_multicore/BUILD.gn b/third_party/pico_sdk/src/rp2_common/pico_multicore/BUILD.gn
deleted file mode 100644
index d154954..0000000
--- a/third_party/pico_sdk/src/rp2_common/pico_multicore/BUILD.gn
+++ /dev/null
@@ -1,45 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/pico_multicore"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("pico_multicore") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/common/pico_sync",
-    "${PICO_ROOT}/src/rp2040/hardware_structs",
-  ]
-  deps = [
-    "${PICO_ROOT}/src/rp2040/hardware_regs",
-    "${PICO_ROOT}/src/rp2_common/hardware_claim",
-    "${PICO_ROOT}/src/rp2_common/hardware_irq",
-    "${PICO_ROOT}/src/rp2_common/hardware_sync",
-    "${PICO_ROOT}/src/rp2_common/pico_runtime",
-  ]
-  public = [ "${_CWD}/include/pico/multicore.h" ]
-  sources = [ "${_CWD}/pico_multicore.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/pico_platform/BUILD.gn b/third_party/pico_sdk/src/rp2_common/pico_platform/BUILD.gn
deleted file mode 100644
index 593196d..0000000
--- a/third_party/pico_sdk/src/rp2_common/pico_platform/BUILD.gn
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/pico_platform"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("headers") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [ "${PICO_ROOT}/src/rp2040/hardware_regs:platform_defs" ]
-  configs = [ "${PICO_ROOT}/src/rp2_common/pico_platform:public_include_dirs" ]
-  public = [
-    "${_CWD}/include/pico/asm_helper.S",
-    "${_CWD}/include/pico/platform.h",
-  ]
-}
-
-pw_source_set("pico_platform") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_deps = [ ":headers" ]
-  deps = [ "${PICO_ROOT}/src/rp2_common/hardware_base" ]
-  sources = [ "${_CWD}/platform.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/pico_printf/BUILD.gn b/third_party/pico_sdk/src/rp2_common/pico_printf/BUILD.gn
deleted file mode 100644
index 00453c4..0000000
--- a/third_party/pico_sdk/src/rp2_common/pico_printf/BUILD.gn
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/pico_printf"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("pico_printf_none") {
-  deps = [
-    "${PICO_ROOT}/src/rp2_common/pico_bootrom",
-    "${PICO_ROOT}/src/rp2_common/pico_platform",
-  ]
-  sources = [ "${_CWD}/printf_none.S" ]
-}
-
-pw_source_set("pico_printf") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [ "${PICO_ROOT}/src/common/pico_base" ]
-  deps = [ "${PICO_ROOT}/src/rp2_common/pico_platform" ]
-  public = [ "${_CWD}/include/pico/printf.h" ]
-  sources = [ "${_CWD}/printf.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/pico_runtime/BUILD.gn b/third_party/pico_sdk/src/rp2_common/pico_runtime/BUILD.gn
deleted file mode 100644
index f0c42c6..0000000
--- a/third_party/pico_sdk/src/rp2_common/pico_runtime/BUILD.gn
+++ /dev/null
@@ -1,46 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/pico_runtime"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("pico_runtime") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-
-  deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/common/pico_sync",
-    "${PICO_ROOT}/src/common/pico_time",
-    "${PICO_ROOT}/src/rp2040/hardware_regs",
-    "${PICO_ROOT}/src/rp2040/hardware_structs",
-    "${PICO_ROOT}/src/rp2_common/hardware_clocks",
-    "${PICO_ROOT}/src/rp2_common/hardware_irq",
-    "${PICO_ROOT}/src/rp2_common/hardware_resets",
-    "${PICO_ROOT}/src/rp2_common/pico_bootrom",
-    "${PICO_ROOT}/src/rp2_common/pico_printf",
-  ]
-  public = [ "${_CWD}/include/pico/runtime.h" ]
-  sources = [ "${_CWD}/runtime.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/pico_standard_link/BUILD.gn b/third_party/pico_sdk/src/rp2_common/pico_standard_link/BUILD.gn
deleted file mode 100644
index 457f93d..0000000
--- a/third_party/pico_sdk/src/rp2_common/pico_standard_link/BUILD.gn
+++ /dev/null
@@ -1,46 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/pico_standard_link"
-
-# TODO(amontanez): Not all linker script configurations are supported yet.
-
-config("linker_script") {
-  _linker_script_path = rebase_path("${_CWD}/memmap_default.ld", root_build_dir)
-  ldflags = [ "-T${_linker_script_path}" ]
-}
-
-pw_source_set("pico_standard_link") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  all_dependent_configs = [ ":linker_script" ]
-  inputs = [ "${_CWD}/memmap_default.ld" ]
-  deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/common/pico_binary_info",
-    "${PICO_ROOT}/src/rp2040/hardware_regs",
-    "${PICO_ROOT}/src/rp2_common/boot_stage2",
-  ]
-  sources = [
-    "${_CWD}/binary_info.c",
-    "${_CWD}/crt0.S",
-    "${_CWD}/new_delete.cpp",
-  ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/pico_stdio/BUILD.gn b/third_party/pico_sdk/src/rp2_common/pico_stdio/BUILD.gn
deleted file mode 100644
index 38e2c69..0000000
--- a/third_party/pico_sdk/src/rp2_common/pico_stdio/BUILD.gn
+++ /dev/null
@@ -1,73 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/pico_stdio"
-
-import("${PICO_ROOT}/src/common/pico_stdlib/pico_stdio.gni")
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-config("printf_wrappers") {
-  ldflags = [
-    "-Wl,--wrap=printf",
-    "-Wl,--wrap=vprintf",
-    "-Wl,--wrap=puts",
-    "-Wl,--wrap=putchar",
-    "-Wl,--wrap=getchar",
-  ]
-}
-
-# TODO(amontanez): This is definitely a facade. For now, just have header and
-# header+impl build targets to simulate.
-pw_source_set("headers") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_base",
-    "${PICO_ROOT}/src/rp2_common/pico_platform",
-  ]
-
-  public = [
-    "${_CWD}/include/pico/stdio.h",
-    "${_CWD}/include/pico/stdio/driver.h",
-  ]
-}
-
-pw_source_set("pico_stdio") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  all_dependent_configs = [ ":printf_wrappers" ]
-  public_deps = [ ":headers" ]
-  deps = [
-    "${PICO_ROOT}/src/common/pico_sync",
-    "${PICO_ROOT}/src/common/pico_time",
-    "${PICO_ROOT}/src/rp2_common/pico_printf",
-  ]
-  if (PICO_STDIO == ENUM_LIB_PICO_STDIO.UART) {
-    deps += [ "${PICO_ROOT}/src/rp2_common/pico_stdio_uart" ]
-  } else if (PICO_STDIO == ENUM_LIB_PICO_STDIO.USB) {
-    deps += [ "${PICO_ROOT}/src/rp2_common/pico_stdio_usb" ]
-  } else if (PICO_STDIO == ENUM_LIB_PICO_STDIO.SEMIHOSTING) {
-    deps += [ "${PICO_ROOT}/src/rp2_common/pico_stdio_semihosting" ]
-  }
-  sources = [ "${_CWD}/stdio.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/pico_stdio_semihosting/BUILD.gn b/third_party/pico_sdk/src/rp2_common/pico_stdio_semihosting/BUILD.gn
deleted file mode 100644
index 5590ad2..0000000
--- a/third_party/pico_sdk/src/rp2_common/pico_stdio_semihosting/BUILD.gn
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/pico_stdio_semihosting"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("pico_stdio_semihosting") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [ "${PICO_ROOT}/src/rp2_common/pico_stdio:headers" ]
-  deps = [ "${PICO_ROOT}/src/common/pico_binary_info" ]
-  public = [ "${_CWD}/include/pico/stdio_semihosting.h" ]
-  sources = [ "${_CWD}/pico_stdio/semihosting.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/pico_stdio_uart/BUILD.gn b/third_party/pico_sdk/src/rp2_common/pico_stdio_uart/BUILD.gn
deleted file mode 100644
index 788a0de..0000000
--- a/third_party/pico_sdk/src/rp2_common/pico_stdio_uart/BUILD.gn
+++ /dev/null
@@ -1,41 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/pico_stdio_uart"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("pico_stdio_uart") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/rp2_common/hardware_uart",
-    "${PICO_ROOT}/src/rp2_common/pico_stdio:headers",
-  ]
-  deps = [
-    "${PICO_ROOT}/src/common/pico_binary_info",
-    "${PICO_ROOT}/src/rp2_common/hardware_gpio",
-  ]
-  public = [ "${_CWD}/include/pico/stdio_uart.h" ]
-  sources = [ "${_CWD}/stdio_uart.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/pico_stdio_usb/BUILD.gn b/third_party/pico_sdk/src/rp2_common/pico_stdio_usb/BUILD.gn
deleted file mode 100644
index 30a3434..0000000
--- a/third_party/pico_sdk/src/rp2_common/pico_stdio_usb/BUILD.gn
+++ /dev/null
@@ -1,52 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/pico_stdio_usb"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("pico_stdio_usb") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [
-    "${PICO_ROOT}/src/common/pico_usb_reset_interface",
-    "${PICO_ROOT}/src/rp2_common/pico_stdio:headers",
-  ]
-  deps = [
-    "${PICO_ROOT}/src/common/pico_sync",
-    "${PICO_ROOT}/src/common/pico_time",
-    "${PICO_ROOT}/src/rp2_common/hardware_irq",
-  ]
-
-  # TODO(amontanez): Still needs a dependency on tinyusb.
-  public = [
-    "${_CWD}/include/pico/stdio_usb.h",
-    "${_CWD}/include/pico/stdio_usb/reset_interface.h",
-    "${_CWD}/include/tusb_config.h",
-  ]
-  sources = [
-    "${_CWD}/reset_interface.c",
-    "${_CWD}/stdio_usb.c",
-    "${_CWD}/stdio_usb_descriptors.c",
-  ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/pico_stdlib/BUILD.gn b/third_party/pico_sdk/src/rp2_common/pico_stdlib/BUILD.gn
deleted file mode 100644
index 448d1d7..0000000
--- a/third_party/pico_sdk/src/rp2_common/pico_stdlib/BUILD.gn
+++ /dev/null
@@ -1,51 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/pico_stdlib"
-
-import("${PICO_ROOT}/src/common/pico_stdlib/pico_stdio.gni")
-
-pw_source_set("pico_stdlib") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  deps = [
-    "${PICO_ROOT}/src/common/pico_stdlib:headers",
-    "${PICO_ROOT}/src/rp2_common/hardware_clocks",
-    "${PICO_ROOT}/src/rp2_common/hardware_pll",
-  ]
-
-  # These libraries must be linked in for this to work, even though this does
-  # not #include anything from these:
-  deps += [
-    "${PICO_ROOT}/src/common/pico_time",
-    "${PICO_ROOT}/src/rp2_common/pico_platform",
-    "${PICO_ROOT}/src/rp2_common/pico_runtime",
-    "${PICO_ROOT}/src/rp2_common/pico_standard_link",
-    "${PICO_ROOT}/src/rp2_common/pico_stdio",
-  ]
-
-  if (PICO_STDIO == ENUM_LIB_PICO_STDIO.UART) {
-    deps += [ "${PICO_ROOT}/src/rp2_common/pico_stdio_uart" ]
-  } else {
-    deps += [ "${PICO_ROOT}/src/common/pico_binary_info" ]
-  }
-
-  sources = [ "${_CWD}/stdlib.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/pico_unique_id/BUILD.gn b/third_party/pico_sdk/src/rp2_common/pico_unique_id/BUILD.gn
deleted file mode 100644
index a3f7b57..0000000
--- a/third_party/pico_sdk/src/rp2_common/pico_unique_id/BUILD.gn
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pi_pico.gni")
-import("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/target_types.gni")
-
-# TODO(amontanez): This can go away if the GN build can be upstreamed to the
-# Pi Pico repo.
-_CWD = "${PICO_SRC_DIR}/src/rp2_common/pico_unique_id"
-
-config("public_include_dirs") {
-  include_dirs = [ "${_CWD}/include" ]
-}
-
-pw_source_set("pico_unique_id") {
-  remove_configs = [ "$dir_pw_build:strict_warnings" ]
-  public_configs = [ ":public_include_dirs" ]
-  public_deps = [ "${PICO_ROOT}/src/common/pico_base" ]
-  deps = [ "${PICO_ROOT}/src/rp2_common/hardware_flash" ]
-  public = [ "${_CWD}/include/pico/unique_id.h" ]
-  sources = [ "${_CWD}/unique_id.c" ]
-}
diff --git a/third_party/pico_sdk/src/rp2_common/tinyusb/BUILD.gn b/third_party/pico_sdk/src/rp2_common/tinyusb/BUILD.gn
deleted file mode 100644
index 32680d5..0000000
--- a/third_party/pico_sdk/src/rp2_common/tinyusb/BUILD.gn
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-# TODO(amontanez): Build shim for TinyUSB
diff --git a/third_party/protobuf/BUILD.gn b/third_party/protobuf/BUILD.gn
index d33c588..8e42cf8 100644
--- a/third_party/protobuf/BUILD.gn
+++ b/third_party/protobuf/BUILD.gn
@@ -15,7 +15,6 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_build/target_types.gni")
-import("$dir_pw_protobuf_compiler/proto.gni")
 import("protobuf.gni")
 
 # This file defines a GN source_set for an external installation of protobuf.
@@ -147,23 +146,6 @@
     public_deps = [ ":libprotobuf_lite" ]
     configs = [ ":defines" ]
   }
-
-  pw_proto_library("wellknown_types") {
-    sources = [
-      "$dir_pw_third_party_protobuf/src/google/protobuf/any.proto",
-      "$dir_pw_third_party_protobuf/src/google/protobuf/api.proto",
-      "$dir_pw_third_party_protobuf/src/google/protobuf/descriptor.proto",
-      "$dir_pw_third_party_protobuf/src/google/protobuf/duration.proto",
-      "$dir_pw_third_party_protobuf/src/google/protobuf/empty.proto",
-      "$dir_pw_third_party_protobuf/src/google/protobuf/field_mask.proto",
-      "$dir_pw_third_party_protobuf/src/google/protobuf/source_context.proto",
-      "$dir_pw_third_party_protobuf/src/google/protobuf/struct.proto",
-      "$dir_pw_third_party_protobuf/src/google/protobuf/timestamp.proto",
-      "$dir_pw_third_party_protobuf/src/google/protobuf/type.proto",
-      "$dir_pw_third_party_protobuf/src/google/protobuf/wrappers.proto",
-    ]
-    strip_prefix = "$dir_pw_third_party_protobuf/src/"
-  }
 } else {
   # As mentioned above, these targets are effectively disabled if the build
   # argument pointing to the protobuf source directory is not set.
diff --git a/third_party/protobuf/OWNERS b/third_party/protobuf/OWNERS
deleted file mode 100644
index 3afb926..0000000
--- a/third_party/protobuf/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-frolv@google.com
diff --git a/third_party/rules_proto_grpc/BUILD.bazel b/third_party/rules_proto_grpc/BUILD.bazel
deleted file mode 100644
index a524793..0000000
--- a/third_party/rules_proto_grpc/BUILD.bazel
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-exports_files(["internal_proto.bzl"])
diff --git a/third_party/rules_proto_grpc/internal_proto.bzl b/third_party/rules_proto_grpc/internal_proto.bzl
deleted file mode 100644
index cab19d4..0000000
--- a/third_party/rules_proto_grpc/internal_proto.bzl
+++ /dev/null
@@ -1,237 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-"""Backend implementation for 'pw_protobuf_compiler/proto.bzl'"""
-
-# Apache License, Version 2.0, January 2004, http://www.apache.org/licenses/
-# Adapted from: https://github.com/rules-proto-grpc/rules_proto_grpc/
-# Files adapted:
-#  - rules_proto_grpc/cpp/cpp_grpc_library.bzl
-#  - rules_proto_grpc/cpp/cpp_grpc_compile.bzl
-# These two files have been adapted for use in Pigweed and combined into this
-# file.
-
-load("@rules_proto//proto:defs.bzl", "ProtoInfo")
-load(
-    "@rules_proto_grpc//:defs.bzl",
-    "ProtoLibraryAspectNodeInfo",
-    "ProtoPluginInfo",
-    "proto_compile_aspect_attrs",
-    "proto_compile_aspect_impl",
-    "proto_compile_attrs",
-    "proto_compile_impl",
-)
-load("@rules_proto_grpc//internal:filter_files.bzl", "filter_files")
-
-# Create compile rule
-def _proto_compiler_aspect(plugin_group, prefix):
-    return aspect(
-        implementation = proto_compile_aspect_impl,
-        provides = [ProtoLibraryAspectNodeInfo],
-        attr_aspects = ["deps"],
-        attrs = dict(
-            proto_compile_aspect_attrs,
-            _plugins = attr.label_list(
-                doc = "List of protoc plugins to apply",
-                providers = [ProtoPluginInfo],
-                default = plugin_group,
-            ),
-            _prefix = attr.string(
-                doc = "String used to disambiguate aspects when generating \
-outputs",
-                default = prefix,
-            ),
-        ),
-        toolchains = [str(Label("@rules_proto_grpc//protobuf:toolchain_type"))],
-    )
-
-def _proto_compiler_rule(plugin_group, aspect):
-    return rule(
-        implementation = proto_compile_impl,
-        attrs = dict(
-            proto_compile_attrs,
-            _plugins = attr.label_list(
-                doc = "List of protoc plugins to apply",
-                providers = [ProtoPluginInfo],
-                default = plugin_group,
-            ),
-            protos = attr.label_list(
-                providers = [ProtoInfo],
-                doc = "List of proto_library targets.",
-            ),
-            deps = attr.label_list(
-                doc = "List of proto_library targets. Prefer protos.",
-                aspects = [aspect],
-            ),
-        ),
-        toolchains = [str(Label("@rules_proto_grpc//protobuf:toolchain_type"))],
-    )
-
-nanopb_compile_aspect = _proto_compiler_aspect(
-    [Label("//pw_rpc:nanopb_plugin")],
-    "nanopb_proto_compile_aspect",
-)
-nanopb_compile = _proto_compiler_rule(
-    [Label("//pw_rpc:nanopb_plugin")],
-    nanopb_compile_aspect,
-)
-
-pwpb_compile_aspect = _proto_compiler_aspect(
-    [Label("@pigweed//pw_protobuf:pw_cc_plugin")],
-    "pwpb_proto_compile_aspect",
-)
-pwpb_compile = _proto_compiler_rule(
-    [Label("@pigweed//pw_protobuf:pw_cc_plugin")],
-    pwpb_compile_aspect,
-)
-
-raw_rpc_compile_aspect = _proto_compiler_aspect(
-    [Label("@pigweed//pw_rpc:pw_cc_plugin_raw")],
-    "raw_rpc_proto_compile_aspect",
-)
-raw_rpc_compile = _proto_compiler_rule(
-    [Label("@pigweed//pw_rpc:pw_cc_plugin_raw")],
-    raw_rpc_compile_aspect,
-)
-
-nanopb_rpc_compile_aspect = _proto_compiler_aspect(
-    [
-        Label("@pigweed//pw_rpc:pw_cc_plugin_nanopb_rpc"),
-        Label("//pw_rpc:nanopb_plugin"),
-    ],
-    "nanopb_rpc_proto_compile_aspect",
-)
-nanopb_rpc_compile = _proto_compiler_rule(
-    [
-        Label("@pigweed//pw_rpc:pw_cc_plugin_nanopb_rpc"),
-        Label("//pw_rpc:nanopb_plugin"),
-    ],
-    nanopb_rpc_compile_aspect,
-)
-
-PLUGIN_INFO = {
-    "nanopb": {
-        "compiler": nanopb_compile,
-        "deps": ["@com_github_nanopb_nanopb//:nanopb"],
-        "has_srcs": True,
-        # TODO: Find a way to get Nanopb to generate nested structs.
-        # Otherwise add the manual tag to the resulting library,
-        # preventing it from being built unless directly depended on.
-        # e.g. The 'Pigweed' message in
-        # pw_protobuf/pw_protobuf_test_protos/full_test.proto will fail to
-        # compile as it has a self referring nested message. According to
-        # the docs
-        # https://jpa.kapsi.fi/nanopb/docs/reference.html#proto-file-options
-        # and https://github.com/nanopb/nanopb/issues/433 it seams like it
-        # should be possible to configure nanopb to generate nested structs.
-        "additional_tags": ["manual"],
-    },
-    "nanopb_rpc": {
-        "compiler": nanopb_rpc_compile,
-        "deps": [
-            "@com_github_nanopb_nanopb//:nanopb",
-            "@pigweed//pw_rpc/nanopb:server_api",
-            "@pigweed//pw_rpc/nanopb:client_api",
-            "@pigweed//pw_rpc",
-        ],
-        "has_srcs": True,
-        # See above todo.
-        "additional_tags": ["manual"],
-    },
-    "pwpb": {
-        "compiler": pwpb_compile,
-        "deps": ["@pigweed//pw_protobuf"],
-        "has_srcs": False,
-        "additional_tags": [],
-    },
-    "raw_rpc": {
-        "compiler": raw_rpc_compile,
-        "deps": [
-            "@pigweed//pw_rpc",
-            "@pigweed//pw_rpc/raw:server_api",
-            "@pigweed//pw_rpc/raw:client_api",
-        ],
-        "has_srcs": False,
-        "additional_tags": [],
-    },
-}
-
-def pw_proto_library(name, **kwargs):  # buildifier: disable=function-docstring
-    for plugin_name, info in PLUGIN_INFO.items():
-        name_pb = name + "_pb" + "." + plugin_name
-        additional_tags = [
-            tag
-            for tag in info["additional_tags"]
-            if tag not in kwargs.get("tags", [])
-        ]
-        info["compiler"](
-            name = name_pb,
-            tags = additional_tags,
-            # Forward deps and verbose tags to implementation
-            verbose = kwargs.get("verbose", 0),
-            deps = kwargs.get("deps", []),
-            protos = kwargs.get("protos", []),
-        )
-
-        # Filter files to sources and headers
-        filter_files(
-            name = name_pb + "_srcs",
-            target = name_pb,
-            extensions = ["c", "cc", "cpp", "cxx"],
-            tags = additional_tags,
-        )
-
-        filter_files(
-            name = name_pb + "_hdrs",
-            target = name_pb,
-            extensions = ["h"],
-            tags = additional_tags,
-        )
-
-        # Cannot use pw_cc_library here as it will add cxxopts.
-        # Note that the srcs attribute here is passed in as a DefaultInfo
-        # object, which is not supported by pw_cc_library.
-        native.cc_library(
-            name = name + "." + plugin_name,
-            hdrs = [name_pb + "_hdrs"],
-            includes = [name_pb],
-            alwayslink = kwargs.get("alwayslink"),
-            copts = kwargs.get("copts", []),
-            defines = kwargs.get("defines", []),
-            srcs = [name_pb + "_srcs"] if info["has_srcs"] else [],
-            deps = info["deps"],
-            include_prefix = kwargs.get("include_prefix", ""),
-            linkopts = kwargs.get("linkopts", []),
-            linkstatic = kwargs.get("linkstatic", True),
-            local_defines = kwargs.get("local_defines", []),
-            nocopts = kwargs.get("nocopts", ""),
-            visibility = kwargs.get("visibility"),
-            tags = kwargs.get("tags", []) + additional_tags,
-        )
-
-    if "manual" in kwargs.get("tags", []):
-        additional_tags = []
-    else:
-        additional_tags = ["manual"]
-
-    # Combine all plugins into a single library.
-    native.cc_library(
-        name = name,
-        deps = [
-            name + "." + plugin_name
-            for plugin_name in PLUGIN_INFO.keys()
-        ],
-        tags = kwargs.get("tags", []) + additional_tags,
-        **{k: v for k, v in kwargs.items() if k not in ["deps", "protos"]}
-    )
diff --git a/third_party/smartfusion_mss/BUILD.bazel b/third_party/smartfusion_mss/BUILD.bazel
deleted file mode 100644
index bf4dfa0..0000000
--- a/third_party/smartfusion_mss/BUILD.bazel
+++ /dev/null
@@ -1,40 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-# Ready-made configurations
-liberosoc_configs = [
-    ("default", "configs/config_default.h"),
-    ("debug", "configs/config_debug.h"),
-]
-
-# Config targets.
-[
-    pw_cc_library(
-        name = "%s_config" % config_name,
-        hdrs = [
-            config_header,
-            "configs/config_pigweed_common.h",
-        ],
-        copts = ["-Dmss_CONFIG_FILE=\"%s\"" % config_header],
-        includes = ["."],
-    )
-    for config_name, config_header in liberosoc_configs
-]
-
-# TODO(skeys): Add build recipe for the library.
diff --git a/third_party/smartfusion_mss/BUILD.gn b/third_party/smartfusion_mss/BUILD.gn
deleted file mode 100644
index 2e3c108..0000000
--- a/third_party/smartfusion_mss/BUILD.gn
+++ /dev/null
@@ -1,112 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-import("$dir_pw_build/linker_script.gni")
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_third_party/smartfusion_mss/mss.gni")
-
-declare_args() {
-  pw_target_smartfusion2_LINK_CONFIG_DEFINES = []
-}
-
-if (dir_pw_third_party_smartfusion_mss != "") {
-  # The list currently includes all source files for build.
-  smartfusion_mss_sources = [
-    "exported_firmware/CMSIS/startup_gcc/startup_m2sxxx.S",
-    "exported_firmware/CMSIS/system_m2sxxx.c",
-    "exported_firmware/drivers/mss_can/mss_can.c",
-    "exported_firmware/drivers/mss_ethernet_mac/m88e1340_phy.c",
-    "exported_firmware/drivers/mss_ethernet_mac/mss_ethernet_mac.c",
-    "exported_firmware/drivers/mss_gpio/mss_gpio.c",
-    "exported_firmware/drivers/mss_hpdma/mss_hpdma.c",
-    "exported_firmware/drivers/mss_i2c/mss_i2c.c",
-    "exported_firmware/drivers/mss_nvm/mss_nvm.c",
-    "exported_firmware/drivers/mss_rtc/mss_rtc.c",
-    "exported_firmware/drivers/mss_spi/mss_spi.c",
-    "exported_firmware/drivers/mss_sys_services/mss_comblk.c",
-    "exported_firmware/drivers/mss_sys_services/mss_sys_services.c",
-    "exported_firmware/drivers/mss_uart/mss_uart.c",
-    "exported_firmware/drivers/mss_usb/mss_usb_common_cif.c",
-    "exported_firmware/drivers/mss_usb/mss_usb_device.c",
-    "exported_firmware/drivers/mss_usb/mss_usb_device_cdc.c",
-    "exported_firmware/drivers/mss_usb/mss_usb_device_cif.c",
-    "exported_firmware/drivers/mss_usb/mss_usb_device_hid.c",
-    "exported_firmware/drivers/mss_usb/mss_usb_device_msd.c",
-    "exported_firmware/drivers/mss_usb/mss_usb_device_printer.c",
-    "exported_firmware/drivers/mss_usb/mss_usb_device_rndis.c",
-    "exported_firmware/drivers/mss_usb/mss_usb_device_vendor.c",
-    "exported_firmware/drivers/mss_usb/mss_usb_host.c",
-    "exported_firmware/drivers/mss_usb/mss_usb_host_cif.c",
-    "exported_firmware/drivers/mss_usb/mss_usb_host_msc.c",
-    "exported_firmware/drivers_config/sys_config/sys_config.c",
-  ]
-
-  liberosoc_configs = [
-    {
-      name = "default"
-      config_header = "configs/config_default.h"
-    },
-    {
-      name = "debug"
-      config_header = "configs/config_debug.h"
-    },
-  ]
-
-  foreach(ele, liberosoc_configs) {
-    config_name = ele.name + "_config"
-    config(config_name) {
-      # Custom config file is specified by macro liberosoc_CONFIG_FILE
-      # for liberosoc
-      defines = [ "liberosoc_CONFIG_FILE=\"${ele.config_header}\"" ]
-    }
-
-    srcset_name = ele.name + "_config_srcset"
-    pw_source_set(srcset_name) {
-      public = [
-        "configs/config_pigweed_common.h",
-        ele.config_header,
-      ]
-      public_configs = [
-        ":${config_name}",
-        ":smartfusion_mss_common_config",
-      ]
-    }
-  }
-
-  config("smartfusion_mss_common_config") {
-    include_dirs = [
-      "$dir_pw_third_party_smartfusion_mss/exported_firmware/CMSIS/V4.5/Include",
-      "$dir_pw_third_party_smartfusion_mss/exported_firmware/drivers",
-      "$dir_pw_third_party_smartfusion_mss/exported_firmware/CMSIS",
-      "$dir_pw_third_party/smartfusion_mss",
-    ]
-    cflags = [
-      "-Wno-error=cast-qual",
-      "-Wno-error=redundant-decls",
-      "-w",
-    ]
-  }
-
-  pw_source_set("smartfusion_mss") {
-    sources = []
-    foreach(source, smartfusion_mss_sources) {
-      sources += [ "$dir_pw_third_party_smartfusion_mss/" + source ]
-    }
-    public_deps = [ ":${pw_third_party_smartfusion_mss_CONFIG}_config_srcset" ]
-  }
-} else {
-  group("smartfusion_mss") {
-  }
-}
diff --git a/third_party/smartfusion_mss/README.md b/third_party/smartfusion_mss/README.md
deleted file mode 100644
index 7532545..0000000
--- a/third_party/smartfusion_mss/README.md
+++ /dev/null
@@ -1,6 +0,0 @@
-# LiberoSoC Library
-
-The folder provides build scripts and configuration recipes for building
-the SmartFusion2 Microcontroller Subsystem library. The source code needs to be downloaded by the user, or
-via the support in pw_package "pw package install sf2mss". For gn build,
-set `dir_pw_third_party_smartfusion_mss` to point to the path of the source code.
diff --git a/third_party/smartfusion_mss/configs/config_debug.h b/third_party/smartfusion_mss/configs/config_debug.h
deleted file mode 100644
index 7eb6960..0000000
--- a/third_party/smartfusion_mss/configs/config_debug.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "configs/config_pigweed_common.h"
-
-#define SF2_MSS_NO_BOOTLOADER 1
diff --git a/third_party/smartfusion_mss/configs/config_default.h b/third_party/smartfusion_mss/configs/config_default.h
deleted file mode 100644
index 30aee59..0000000
--- a/third_party/smartfusion_mss/configs/config_default.h
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "configs/config_pigweed_common.h"
diff --git a/third_party/smartfusion_mss/configs/config_pigweed_common.h b/third_party/smartfusion_mss/configs/config_pigweed_common.h
deleted file mode 100644
index 3f4f6ea..0000000
--- a/third_party/smartfusion_mss/configs/config_pigweed_common.h
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2022 The Pigweed Authors
-//
-// 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
-//
-//     https://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.
-
-// Some common configs for using mbedtls in Pigweed. These include disabling of
-// file system, socket and linux/windows specific features.
-// See include/mbedtls/config.h for a detail explanation of these
-// configurations.
-
-#pragma once
diff --git a/third_party/smartfusion_mss/mss.gni b/third_party/smartfusion_mss/mss.gni
deleted file mode 100644
index dd97178..0000000
--- a/third_party/smartfusion_mss/mss.gni
+++ /dev/null
@@ -1,24 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-declare_args() {
-  # If compiling backends with mbedtls, this variable is set to the path to the
-  # mbedtls source code. When set, a pw_source_set for the mbedtls library is
-  # created at "$dir_pw_third_party/mbedtls".
-  dir_pw_third_party_smartfusion_mss = ""
-
-  # configuration for mbedtls. Can be one of `mbedtls_configs` in the BUILD.gn
-  # file
-  pw_third_party_smartfusion_mss_CONFIG = "default"
-}
diff --git a/third_party/stm32cube/BUILD.bazel b/third_party/stm32cube/BUILD.bazel
deleted file mode 100644
index 81a3ff7..0000000
--- a/third_party/stm32cube/BUILD.bazel
+++ /dev/null
@@ -1,30 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-load(
-    "//pw_build:pigweed.bzl",
-    "pw_cc_library",
-)
-
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-pw_cc_library(
-    name = "stm32cube",
-    hdrs = [
-        "public/stm32cube/init.h",
-        "public/stm32cube/stm32cube.h",
-    ],
-)
diff --git a/third_party/stm32cube/BUILD.gn b/third_party/stm32cube/BUILD.gn
deleted file mode 100644
index c541fc8..0000000
--- a/third_party/stm32cube/BUILD.gn
+++ /dev/null
@@ -1,175 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/linker_script.gni")
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_third_party/stm32cube/stm32cube.gni")
-
-if (dir_pw_third_party_stm32cube == "") {
-  group("linker_script_template") {
-  }
-  group("core_init_template") {
-  }
-  group("cmsis_init_template") {
-  }
-  group("hal_config_template") {
-  }
-  group("stm32cube_headers") {
-  }
-  group("stm32cube") {
-  }
-} else {
-  stm32cube_builder_script =
-      "$dir_pw_stm32cube_build/py/pw_stm32cube_build/__main__.py"
-
-  rebased_dir_pw_third_party_stm32cube =
-      rebase_path(dir_pw_third_party_stm32cube)
-
-  find_files_args = [
-    "find_files",
-    rebased_dir_pw_third_party_stm32cube,
-    pw_third_party_stm32cube_PRODUCT,
-  ]
-  if (pw_third_party_stm32cube_CORE_INIT ==
-      "$dir_pw_third_party/stm32cube:core_init_template") {
-    find_files_args += [ "--init" ]
-  }
-
-  # This script finds the files relavent for the current product.
-  files = exec_script(stm32cube_builder_script,
-                      find_files_args,
-                      "scope",
-                      [ "$rebased_dir_pw_third_party_stm32cube/files.txt" ])
-
-  if (pw_third_party_stm32cube_CORE_INIT ==
-      "$dir_pw_third_party/stm32cube:core_init_template") {
-    assert(files.gcc_linker != "" || files.iar_linker != "",
-           "No linker file found")
-
-    gcc_linker = files.gcc_linker
-    if (gcc_linker == "") {
-      gcc_linker = "$target_gen_dir/linker.ld"
-      gcc_linker_str = exec_script(stm32cube_builder_script,
-                                   [
-                                     "icf_to_ld",
-                                     files.iar_linker,
-                                   ],
-                                   "string",
-                                   [ files.iar_linker ])
-      write_file(gcc_linker, gcc_linker_str)
-    }
-
-    startup_file = "$target_gen_dir/startup.s"
-    startup_file_str = exec_script(stm32cube_builder_script,
-                                   [
-                                     "inject_init",
-                                     files.startup,
-                                   ],
-                                   "string",
-                                   [ files.startup ])
-    write_file(startup_file, startup_file_str)
-
-    pw_linker_script("linker_script_template") {
-      linker_script = gcc_linker
-    }
-
-    pw_source_set("core_init_template") {
-      deps = [ ":linker_script_template" ]
-      sources = [ startup_file ]
-    }
-  }
-
-  pw_source_set("hal_timebase_template") {
-    deps = [ ":stm32cube_headers" ]
-    sources = [ "$dir_pw_third_party_stm32cube/hal_driver/Src/${files.family}_hal_timebase_tim_template.c" ]
-  }
-
-  pw_source_set("cmsis_init_template") {
-    deps = [ ":stm32cube_headers" ]
-    sources = [ "$dir_pw_third_party_stm32cube/cmsis_device/Source/Templates/system_${files.family}.c" ]
-  }
-
-  # Generate a stub config header that points to the correct template.
-  write_file("$target_gen_dir/template_config/${files.family}_hal_conf.h",
-             "#include \"${files.family}_hal_conf_template.h\"")
-  config("hal_config_template_includes") {
-    include_dirs = [ "$target_gen_dir/template_config" ]
-  }
-  pw_source_set("hal_config_template") {
-    public_configs = [ ":hal_config_template_includes" ]
-
-    # This is to make sure GN properly detects changes to these files. The
-    # generated file shouldn't change, but the file it redirects to might.
-    public = [ "$target_gen_dir/template_config/${files.family}_hal_conf.h" ]
-    inputs = [ "$dir_pw_third_party_stm32cube/hal_driver/Inc/${files.family}_hal_conf_template.h" ]
-  }
-
-  config("flags") {
-    cflags = [ "-Wno-unused-parameter" ]
-    cflags_c = [
-      "-Wno-redundant-decls",
-      "-Wno-sign-compare",
-      "-Wno-old-style-declaration",
-      "-Wno-maybe-uninitialized",
-      "-Wno-undef",
-      "-Wno-implicit-function-declaration",
-    ]
-    defines = [
-      "USE_HAL_DRIVER",
-      files.product_define,
-      "STM32CUBE_HEADER=\"${files.family}.h\"",
-      "__ARMCC_VERSION=0",  # workaround for bug at stm32l552xx.h:1303
-    ]
-    visibility = [ ":*" ]
-  }
-
-  config("public_include_paths") {
-    include_dirs = files.include_dirs
-    include_dirs += [ "public" ]
-    visibility = [ ":*" ]
-  }
-
-  # Only libraries that implement parts of the stm32cube hal should depend on
-  # this. If you just want to depend on the hal, depend on stm32cube directly.
-  pw_source_set("stm32cube_headers") {
-    public_configs = [
-      ":flags",
-      ":public_include_paths",
-    ]
-    public = [
-      "public/stm32cube/init.h",
-      "public/stm32cube/stm32cube.h",
-    ]
-    public += files.headers
-    public_deps = [ pw_third_party_stm32cube_CONFIG ]
-    visibility = [ ":*" ]
-    if (pw_third_party_stm32cube_CORE_INIT != "") {
-      visibility += [ pw_third_party_stm32cube_CORE_INIT ]
-    }
-  }
-
-  pw_source_set("stm32cube") {
-    public_deps = [ ":stm32cube_headers" ]
-    sources = files.sources
-    deps = [
-      pw_third_party_stm32cube_CMSIS_INIT,
-      pw_third_party_stm32cube_TIMEBASE,
-    ]
-    if (pw_third_party_stm32cube_CORE_INIT != "") {
-      deps += [ pw_third_party_stm32cube_CORE_INIT ]
-    }
-  }
-}
diff --git a/third_party/stm32cube/OWNERS b/third_party/stm32cube/OWNERS
deleted file mode 100644
index fb4f20a..0000000
--- a/third_party/stm32cube/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-vars@google.com
diff --git a/third_party/stm32cube/public/stm32cube/init.h b/third_party/stm32cube/public/stm32cube/init.h
deleted file mode 100644
index 2e79f61..0000000
--- a/third_party/stm32cube/public/stm32cube/init.h
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 "pw_preprocessor/util.h"
-
-PW_EXTERN_C_START
-
-void pw_stm32cube_Init(void);
-
-PW_EXTERN_C_END
\ No newline at end of file
diff --git a/third_party/stm32cube/public/stm32cube/stm32cube.h b/third_party/stm32cube/public/stm32cube/stm32cube.h
deleted file mode 100644
index 2cd24f1..0000000
--- a/third_party/stm32cube/public/stm32cube/stm32cube.h
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2021 The Pigweed Authors
-//
-// 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
-//
-//     https://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 STM32CUBE_HEADER
diff --git a/third_party/stm32cube/stm32cube.gni b/third_party/stm32cube/stm32cube.gni
deleted file mode 100644
index f1848df..0000000
--- a/third_party/stm32cube/stm32cube.gni
+++ /dev/null
@@ -1,66 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-declare_args() {
-  # pw_package/stm32cube_xx install directories
-  dir_pw_third_party_stm32cube_f0 = ""
-  dir_pw_third_party_stm32cube_f1 = ""
-  dir_pw_third_party_stm32cube_f2 = ""
-  dir_pw_third_party_stm32cube_f3 = ""
-  dir_pw_third_party_stm32cube_f4 = ""
-  dir_pw_third_party_stm32cube_f7 = ""
-  dir_pw_third_party_stm32cube_g0 = ""
-  dir_pw_third_party_stm32cube_g4 = ""
-  dir_pw_third_party_stm32cube_h7 = ""
-  dir_pw_third_party_stm32cube_l0 = ""
-  dir_pw_third_party_stm32cube_l1 = ""
-  dir_pw_third_party_stm32cube_l4 = ""
-  dir_pw_third_party_stm32cube_l5 = ""
-  dir_pw_third_party_stm32cube_wb = ""
-  dir_pw_third_party_stm32cube_wl = ""
-
-  # The currently selected stm32cube_xx package
-  # This can be selected by the target by doing something like:
-  #  dir_pw_third_party_stm32cube = dir_pw_third_party_stm32cube_f4
-  dir_pw_third_party_stm32cube = ""
-
-  # The Product specified in as much detail as possible.
-  # i.e. "stm32f429zit", "stm32l552ze", "stm32f207zg", etc.
-  pw_third_party_stm32cube_PRODUCT = ""
-
-  # pw_source_set with `stm32{family}xx_hal_conf.h`
-  # The default uses the in-tree `stm32{family}xx_hal_conf_template.h`.
-  pw_third_party_stm32cube_CONFIG =
-      "$dir_pw_third_party/stm32cube:hal_config_template"
-
-  # pw_source_set containing timebase
-  # The default uses the in-tree `stm32{family}xx_hal_timebase_tim_template.c`
-  pw_third_party_stm32cube_TIMEBASE =
-      "$dir_pw_third_party/stm32cube:hal_timebase_template"
-
-  # pw_source_set containing cmsis init logic
-  # The default uses the in-tree `system_stm32{family}xx.c`
-  pw_third_party_stm32cube_CMSIS_INIT =
-      "$dir_pw_third_party/stm32cube:cmsis_init_template"
-
-  # pw_source_set containing the core initization logic. This normally includes
-  # a `startup_stm32{...}.s` + a dependent `pw_linker_script`. The default
-  # `core_init_template` uses the upstream startup and linker script matching
-  # $pw_third_party_stm32cube_PRODUCT. If set to "", you must provide your own
-  # linker/startup logic somewhere else in the build.
-  pw_third_party_stm32cube_CORE_INIT =
-      "$dir_pw_third_party/stm32cube:core_init_template"
-}
diff --git a/third_party/threadx/OWNERS b/third_party/threadx/OWNERS
deleted file mode 100644
index 21d24bc..0000000
--- a/third_party/threadx/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-ewout@google.com
diff --git a/third_party/tinyusb/BUILD.gn b/third_party/tinyusb/BUILD.gn
deleted file mode 100644
index 3d92e4c..0000000
--- a/third_party/tinyusb/BUILD.gn
+++ /dev/null
@@ -1,322 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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("//build_overrides/pigweed.gni")
-
-import("$dir_pw_build/error.gni")
-import("$dir_pw_build/target_types.gni")
-import("$dir_pw_docgen/docs.gni")
-import("tinyusb.gni")
-
-# List of targets defined by this module.
-_tinyusb_targets = [
-  "device",
-  "device_audio",
-  "device_bth",
-  "device_cdc",
-  "device_dfu",
-  "device_hid",
-  "device_midi",
-  "device_msc",
-  "device_net",
-  "device_usbtmc",
-  "device_vendor",
-  "device_video",
-  "host",
-  "host_cdc",
-  "host_hid",
-  "host_msc",
-  "host_vendor",
-]
-
-# This file defines multiple GN source_set for using tinyusb and its different
-# parts. See docs.rst for the configuration details.
-if (pw_third_party_tinyusb_SOURCE == "") {
-  group("tinyusb") {
-  }
-  foreach(tinyusb_target, _tinyusb_targets) {
-    group("${tinyusb_target}") {
-    }
-  }
-} else if (pw_third_party_tinyusb_PORT == "") {
-  pw_error("tinyusb") {
-    message_lines = [
-      "tinyusb is being used by $current_toolchain, but pw_third_party_tinyusb_PORT is not set.",
-      "If this toolchain is intentionally using tinyusb, ensure your toolchain configuration for this target sets pw_third_party_tinyusb_PORT.",
-    ]
-  }
-  foreach(tinyusb_target, _tinyusb_targets) {
-    group("${tinyusb_target}") {
-      deps = [ ":tinyusb" ]
-    }
-  }
-} else if (pw_third_party_tinyusb_CONFIG == "") {
-  pw_error("tinyusb") {
-    message_lines = [
-      "tinyusb is being used by $current_toolchain, but pw_third_party_tinyusb_CONFIG is not set.",
-      "If this toolchain is intentionally using tinyusb, ensure your toolchain configuration for this target sets pw_third_party_tinyusb_CONFIG.",
-    ]
-  }
-  foreach(tinyusb_target, _tinyusb_targets) {
-    group("${tinyusb_target}") {
-      deps = [ ":tinyusb" ]
-    }
-  }
-} else {
-  not_needed([ "_tinyusb_targets" ])
-
-  config("public_includes") {
-    include_dirs = [ "$pw_third_party_tinyusb_SOURCE/src" ]
-    visibility = [ ":*" ]
-  }
-
-  pw_source_set("tinyusb") {
-    public_configs = [ ":public_includes" ]
-
-    # tinyusb depends on headers provided by the other modules in this file
-    # conditional to the features enabled by the config, so ignore those include
-    # checks.
-    check_includes = false
-    allow_circular_includes_from = [ pw_third_party_tinyusb_PORT ]
-    public_deps = [
-      pw_third_party_tinyusb_CONFIG,
-      pw_third_party_tinyusb_PORT,
-    ]
-    public = [
-      "$pw_third_party_tinyusb_SOURCE/src/common/tusb_common.h",
-      "$pw_third_party_tinyusb_SOURCE/src/common/tusb_compiler.h",
-      "$pw_third_party_tinyusb_SOURCE/src/common/tusb_error.h",
-      "$pw_third_party_tinyusb_SOURCE/src/common/tusb_fifo.h",
-      "$pw_third_party_tinyusb_SOURCE/src/common/tusb_timeout.h",
-      "$pw_third_party_tinyusb_SOURCE/src/common/tusb_types.h",
-      "$pw_third_party_tinyusb_SOURCE/src/common/tusb_verify.h",
-      "$pw_third_party_tinyusb_SOURCE/src/tusb.h",
-      "$pw_third_party_tinyusb_SOURCE/src/tusb_option.h",
-    ]
-    sources = [
-      "$pw_third_party_tinyusb_SOURCE/src/common/tusb_fifo.c",
-      "$pw_third_party_tinyusb_SOURCE/src/tusb.c",
-    ]
-  }
-
-  ##############################################################################
-  # Device side.
-  pw_source_set("device") {
-    public_configs = [ ":public_includes" ]
-    public_deps = [
-      ":tinyusb",
-      pw_third_party_tinyusb_PORT,
-    ]
-
-    # Ports may need to include the device headers.
-    allow_circular_includes_from = [ pw_third_party_tinyusb_PORT ]
-    sources = [
-      "$pw_third_party_tinyusb_SOURCE/src/device/usbd.c",
-      "$pw_third_party_tinyusb_SOURCE/src/device/usbd_control.c",
-    ]
-    public = [
-      "$pw_third_party_tinyusb_SOURCE/src/device/dcd.h",
-      "$pw_third_party_tinyusb_SOURCE/src/device/usbd.h",
-      "$pw_third_party_tinyusb_SOURCE/src/device/usbd_pvt.h",
-    ]
-  }
-
-  pw_source_set("device_audio") {
-    public_configs = [ ":public_includes" ]
-    public_deps = [ ":device" ]
-    sources =
-        [ "$pw_third_party_tinyusb_SOURCE/src/class/audio/audio_device.c" ]
-    public = [
-      "$pw_third_party_tinyusb_SOURCE/src/class/audio/audio.h",
-      "$pw_third_party_tinyusb_SOURCE/src/class/audio/audio_device.h",
-    ]
-  }
-
-  pw_source_set("device_bth") {
-    public_configs = [ ":public_includes" ]
-    public_deps = [ ":device" ]
-    sources = [ "$pw_third_party_tinyusb_SOURCE/src/class/bth/bth_device.c" ]
-    public = [ "$pw_third_party_tinyusb_SOURCE/src/class/bth/bth_device.h" ]
-  }
-
-  pw_source_set("device_cdc") {
-    public_configs = [ ":public_includes" ]
-    public_deps = [ ":device" ]
-    sources = [ "$pw_third_party_tinyusb_SOURCE/src/class/cdc/cdc_device.c" ]
-    public = [
-      "$pw_third_party_tinyusb_SOURCE/src/class/cdc/cdc.h",
-      "$pw_third_party_tinyusb_SOURCE/src/class/cdc/cdc_device.h",
-    ]
-  }
-
-  pw_source_set("device_dfu") {
-    public_configs = [ ":public_includes" ]
-    public_deps = [ ":device" ]
-    sources = [
-      "$pw_third_party_tinyusb_SOURCE/src/class/dfu/dfu_device.c",
-      "$pw_third_party_tinyusb_SOURCE/src/class/dfu/dfu_rt_device.c",
-    ]
-    public = [
-      "$pw_third_party_tinyusb_SOURCE/src/class/dfu/dfu.h",
-      "$pw_third_party_tinyusb_SOURCE/src/class/dfu/dfu_device.h",
-      "$pw_third_party_tinyusb_SOURCE/src/class/dfu/dfu_rt_device.h",
-    ]
-  }
-
-  pw_source_set("device_hid") {
-    public_configs = [ ":public_includes" ]
-    public_deps = [ ":device" ]
-    sources = [ "$pw_third_party_tinyusb_SOURCE/src/class/hid/hid_device.c" ]
-    public = [
-      "$pw_third_party_tinyusb_SOURCE/src/class/hid/hid.h",
-      "$pw_third_party_tinyusb_SOURCE/src/class/hid/hid_device.h",
-    ]
-  }
-
-  pw_source_set("device_midi") {
-    public_configs = [ ":public_includes" ]
-    public_deps = [ ":device" ]
-    sources = [ "$pw_third_party_tinyusb_SOURCE/src/class/midi/midi_device.c" ]
-    public = [
-      "$pw_third_party_tinyusb_SOURCE/src/class/midi/midi.h",
-      "$pw_third_party_tinyusb_SOURCE/src/class/midi/midi_device.h",
-    ]
-  }
-
-  pw_source_set("device_msc") {
-    public_configs = [ ":public_includes" ]
-    public_deps = [ ":device" ]
-    sources = [ "$pw_third_party_tinyusb_SOURCE/src/class/msc/msc_device.c" ]
-    public = [
-      "$pw_third_party_tinyusb_SOURCE/src/class/msc/msc.h",
-      "$pw_third_party_tinyusb_SOURCE/src/class/msc/msc_device.h",
-    ]
-  }
-
-  pw_source_set("device_net") {
-    public_configs = [ ":public_includes" ]
-    public_deps = [ ":device" ]
-    sources = [
-      "$pw_third_party_tinyusb_SOURCE/src/class/net/ecm_rndis_device.c",
-      "$pw_third_party_tinyusb_SOURCE/src/class/net/ncm_device.c",
-    ]
-    public = [
-      "$pw_third_party_tinyusb_SOURCE/src/class/net/ncm.h",
-      "$pw_third_party_tinyusb_SOURCE/src/class/net/net_device.h",
-    ]
-  }
-
-  pw_source_set("device_usbtmc") {
-    public_configs = [ ":public_includes" ]
-    public_deps = [ ":device" ]
-    sources =
-        [ "$pw_third_party_tinyusb_SOURCE/src/class/usbtmc/usbtmc_device.c" ]
-    public = [
-      "$pw_third_party_tinyusb_SOURCE/src/class/usbtmc/usbtmc.h",
-      "$pw_third_party_tinyusb_SOURCE/src/class/usbtmc/usbtmc_device.h",
-    ]
-  }
-
-  pw_source_set("device_vendor") {
-    public_configs = [ ":public_includes" ]
-    public_deps = [ ":device" ]
-    sources =
-        [ "$pw_third_party_tinyusb_SOURCE/src/class/vendor/vendor_device.c" ]
-    public =
-        [ "$pw_third_party_tinyusb_SOURCE/src/class/vendor/vendor_device.h" ]
-  }
-
-  pw_source_set("device_video") {
-    public_configs = [ ":public_includes" ]
-    public_deps = [ ":common" ]
-    sources =
-        [ "$pw_third_party_tinyusb_SOURCE/src/class/video/video_device.c" ]
-    public = [
-      "$pw_third_party_tinyusb_SOURCE/src/class/video/video.h",
-      "$pw_third_party_tinyusb_SOURCE/src/class/video/video_device.h",
-    ]
-  }
-
-  ##############################################################################
-  # Host side.
-  pw_source_set("host") {
-    public_configs = [ ":public_includes" ]
-    public_deps = [
-      ":tinyusb",
-      pw_third_party_tinyusb_PORT,
-    ]
-
-    # Ports may need to include the host headers.
-    allow_circular_includes_from = [ pw_third_party_tinyusb_PORT ]
-    sources = [
-      "$pw_third_party_tinyusb_SOURCE/src/host/hub.c",
-      "$pw_third_party_tinyusb_SOURCE/src/host/usbh.c",
-      "$pw_third_party_tinyusb_SOURCE/src/host/usbh_control.c",
-    ]
-    public = [
-      "$pw_third_party_tinyusb_SOURCE/src/host/hcd.h",
-      "$pw_third_party_tinyusb_SOURCE/src/host/hcd_attr.h",
-      "$pw_third_party_tinyusb_SOURCE/src/host/hub.h",
-      "$pw_third_party_tinyusb_SOURCE/src/host/usbh.h",
-      "$pw_third_party_tinyusb_SOURCE/src/host/usbh_classdriver.h",
-    ]
-  }
-
-  pw_source_set("host_cdc") {
-    public_configs = [ ":public_includes" ]
-    public_deps = [ ":host" ]
-    sources = [
-      "$pw_third_party_tinyusb_SOURCE/src/class/cdc/cdc_host.c",
-      "$pw_third_party_tinyusb_SOURCE/src/class/cdc/cdc_rndis_host.c",
-    ]
-    public = [
-      "$pw_third_party_tinyusb_SOURCE/src/class/cdc/cdc.h",
-      "$pw_third_party_tinyusb_SOURCE/src/class/cdc/cdc_host.h",
-      "$pw_third_party_tinyusb_SOURCE/src/class/cdc/cdc_rndis.h",
-      "$pw_third_party_tinyusb_SOURCE/src/class/cdc/cdc_rndis_host.h",
-    ]
-  }
-
-  pw_source_set("host_hid") {
-    public_configs = [ ":public_includes" ]
-    public_deps = [ ":host" ]
-    sources = [ "$pw_third_party_tinyusb_SOURCE/src/class/hid/hid_host.c" ]
-    public = [
-      "$pw_third_party_tinyusb_SOURCE/src/class/hid/hid.h",
-      "$pw_third_party_tinyusb_SOURCE/src/class/hid/hid_host.h",
-    ]
-  }
-
-  pw_source_set("host_msc") {
-    public_configs = [ ":public_includes" ]
-    public_deps = [ ":host" ]
-    sources = [ "$pw_third_party_tinyusb_SOURCE/src/class/msc/msc_host.c" ]
-    public = [
-      "$pw_third_party_tinyusb_SOURCE/src/class/msc/msc.h",
-      "$pw_third_party_tinyusb_SOURCE/src/class/msc/msc_host.h",
-    ]
-  }
-
-  pw_source_set("host_vendor") {
-    public_configs = [ ":public_includes" ]
-    public_deps = [ ":host" ]
-    sources =
-        [ "$pw_third_party_tinyusb_SOURCE/src/class/vendor/vendor_host.c" ]
-    public = [ "$pw_third_party_tinyusb_SOURCE/src/class/vendor/vendor_host.h" ]
-  }
-}
-
-pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
-}
diff --git a/third_party/tinyusb/OWNERS b/third_party/tinyusb/OWNERS
deleted file mode 100644
index a39ed44..0000000
--- a/third_party/tinyusb/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-deymo@google.com
diff --git a/third_party/tinyusb/docs.rst b/third_party/tinyusb/docs.rst
deleted file mode 100644
index eb93553..0000000
--- a/third_party/tinyusb/docs.rst
+++ /dev/null
@@ -1,42 +0,0 @@
-.. _module-pw_third_party_tinyusb:
-
-=======
-TinyUSB
-=======
-
-The ``$dir_pw_third_party/tinyusb/`` module contains the build files needed to
-integrate TinyUSB into a Pigweed build.
-
-----------------
-GN Build Support
-----------------
-This module provides support to compile TinyUSB with GN, however it doesn't
-include the source code of the `tinyusb <https://github.com/hathach/tinyusb>`_
-project.
-
-In order to use this you are expected to configure the following variables from
-``$dir_pw_third_party/tinyusb:tinyusb.gni``:
-
-#. Set the GN ``pw_third_party_tinyusb_SOURCE`` to the path of the TinyUSB
-   source code directory. This is the directory that contains the ``src/``
-   sub-directory.
-#. Set ``pw_third_party_tinyusb_CONFIG`` to a ``pw_source_set`` which provides
-   the TinyUSB ``tusb_config.h`` config header. While it is possible to provide
-   public definitions in this ``pw_source_set`` the configuration header file is
-   still required by TinyUSB.
-#. Set ``pw_third_party_tinyusb_PORT`` to a ``pw_source_set`` which provides
-   the TinyUSB port sources for the specific MCU. Several MCUs are supported by
-   the upstream TinyUSB project, check the ``src/portable/`` path for your MCU.
-
-After this is done multiple ``pw_source_set`` entries for the different parts of
-TinyUSB library are created at ``$dir_pw_third_party/tinyusb``.
-
-.. _third_party-tinyusb_classes:
-
-Adding TinyUSB dependency
-=========================
-TinyUSB library is split into a device side and host side, with a few common
-sources between the two. It is possible to depend on both at compile time, but
-initialize only one side. Device and host both provide classes on top such as
-Communications Device Class (CDC) or Mass Storage Class (MSC) which can be
-selectively included in the build by depending on the respective GN targets.
diff --git a/third_party/tinyusb/tinyusb.gni b/third_party/tinyusb/tinyusb.gni
deleted file mode 100644
index 22b070c..0000000
--- a/third_party/tinyusb/tinyusb.gni
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright 2022 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-declare_args() {
-  # If compiling backends with tinyusb, this variable is set to the path to the
-  # tinyusb source code. When set, a pw_source_set for the tinyusb library is
-  # created at "$dir_pw_third_party/tinyusb".
-  pw_third_party_tinyusb_SOURCE = ""
-
-  # The pw_source_set which provides the port specific includes and sources for
-  # the mcu. This should set a public config with at least CFG_TUSB_MCU defined
-  # for the mcu port.
-  pw_third_party_tinyusb_PORT = ""
-
-  # The pw_source_set which provides the public config providing the include
-  # path to tusb_config.h and its dependencies. A tusb_config.h in the include
-  # path is required, but macros can be passed as defines in a public config.
-  # tinyusb source dependencies vary according to this config, so if for example
-  # the config file sets CFG_TUSB_OS to OPT_OS_FREERTOS a public dependency on
-  # the FreeRTOS pw_source_set is also required.
-  pw_third_party_tinyusb_CONFIG = ""
-}
diff --git a/tsconfig.json b/tsconfig.json
index a398e19..b4204d6 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,5 +1,19 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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.
+
 {
-  "extends": "./node_modules/gts/tsconfig-google.json",
+ "extends": "./node_modules/gts/tsconfig-google.json",
   "compilerOptions": {
     "lib": [
       "dom",
diff --git a/yarn.lock b/yarn.lock
index bafa4de..8ff6708 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,92 +2,71 @@
 # yarn lockfile v1
 
 
-"@babel/code-frame@7.12.11":
-  version "7.12.11"
-  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f"
-  integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==
-  dependencies:
-    "@babel/highlight" "^7.10.4"
-
 "@babel/code-frame@^7.0.0":
-  version "7.15.8"
-  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.15.8.tgz#45990c47adadb00c03677baa89221f7cc23d2503"
-  integrity sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==
+  version "7.10.1"
+  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.1.tgz#d5481c5095daa1c57e16e54c6f9198443afb49ff"
+  integrity sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==
   dependencies:
-    "@babel/highlight" "^7.14.5"
+    "@babel/highlight" "^7.10.1"
 
-"@babel/helper-validator-identifier@^7.14.5":
-  version "7.15.7"
-  resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389"
-  integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==
+"@babel/helper-validator-identifier@^7.10.1":
+  version "7.10.1"
+  resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz#5770b0c1a826c4f53f5ede5e153163e0318e94b5"
+  integrity sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==
 
-"@babel/highlight@^7.10.4", "@babel/highlight@^7.14.5":
-  version "7.14.5"
-  resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9"
-  integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==
+"@babel/highlight@^7.10.1":
+  version "7.10.1"
+  resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.1.tgz#841d098ba613ba1a427a2b383d79e35552c38ae0"
+  integrity sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==
   dependencies:
-    "@babel/helper-validator-identifier" "^7.14.5"
+    "@babel/helper-validator-identifier" "^7.10.1"
     chalk "^2.0.0"
     js-tokens "^4.0.0"
 
+"@babel/runtime-corejs3@^7.8.3":
+  version "7.10.2"
+  resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.10.2.tgz#3511797ddf9a3d6f3ce46b99cc835184817eaa4e"
+  integrity sha512-+a2M/u7r15o3dV1NEizr9bRi+KUVnrs/qYxF0Z06DAPx/4VCWaz1WA7EcbE+uqGgt39lp5akWGmHsTseIkHkHg==
+  dependencies:
+    core-js-pure "^3.0.0"
+    regenerator-runtime "^0.13.4"
+
 "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.7":
-  version "7.15.4"
-  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a"
-  integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==
+  version "7.10.2"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.2.tgz#d103f21f2602497d38348a32e008637d506db839"
+  integrity sha512-6sF3uQw2ivImfVIl62RZ7MXhO2tap69WeWK57vAaimT6AZbE4FbqjdEJIN1UqoD6wI6B+1n9UiagafH1sxjOtg==
   dependencies:
     regenerator-runtime "^0.13.4"
 
-"@bazel/concatjs@4.1.0":
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/@bazel/concatjs/-/concatjs-4.1.0.tgz#73be7f4a6ab94ba3531a39d3a31e15c50a139800"
-  integrity sha512-r9ZM3TxOHPM1OUAxa5NAfAe/bHg9jn7Q0J92HJBv4m+/MGnAJ1ZZPTzxu7w79aUxha/Cr/oqF2DxKAdDbA7Lkg==
+"@bazel/jasmine@^2.2.0":
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/@bazel/jasmine/-/jasmine-2.2.0.tgz#78fc4171362113d993e2473b0168ee8c255e05c9"
+  integrity sha512-NmrgbHBSWUaBXT9rFqeC+bYYWfHtPphnfgchusy7LOQMs/aWVePEo9tsbggNBt1pf6NYv+Y/cHLgtlL/EdQ6Dg==
   dependencies:
-    protobufjs "6.8.8"
-    source-map-support "0.5.9"
-    tsutils "3.21.0"
+    c8 "~7.1.0"
+    jasmine-reporters "~2.3.2"
 
-"@bazel/esbuild@4.1.0":
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/@bazel/esbuild/-/esbuild-4.1.0.tgz#419def12e7d56e095b9d3eb40a66574d7f9b8114"
-  integrity sha512-mN1PfDMJlkgxcpMMTKbD6cpalL8SkRo9om7m6bg1olYSwsrAW6QHNa7ohK4xVtnxgQaWDu4KxioJVMlEmebEqw==
-
-"@bazel/ibazel@^0.15.10":
-  version "0.15.10"
-  resolved "https://registry.yarnpkg.com/@bazel/ibazel/-/ibazel-0.15.10.tgz#cf0cff1aec6d8e7bb23e1fc618d09fbd39b7a13f"
-  integrity sha512-0v+OwCQ6fsGFa50r6MXWbUkSGuWOoZ22K4pMSdtWiL5LKFIE4kfmMmtQS+M7/ICNwk2EIYob+NRreyi/DGUz5A==
-
-"@bazel/jasmine@4.1.0":
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/@bazel/jasmine/-/jasmine-4.1.0.tgz#ec9fc5179af265de47aba8bb40a094e9b062aab2"
-  integrity sha512-AUKzBZ12qKkcI5apXzL/2VKfsF4tHkdLPNsF/p6gEnIW4/aYb6M9wZOFsUh1MLYds+kqx1zN90EGfiZKa6wbOw==
+"@bazel/karma@^2.2.0":
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/@bazel/karma/-/karma-2.2.0.tgz#9bf6f6f1aa5f12b25468b1cad5e4404138436600"
+  integrity sha512-qyVE7vZ/qaibmpmcRdjS0rlorLGR0zZtlUSImVVTcPTSXqt364fp8TWBWe7oOneJ1SOVyUmTOAzyE86ArxZ/AA==
   dependencies:
-    c8 "~7.5.0"
-    jasmine-reporters "~2.4.0"
+    tmp "0.1.0"
 
-"@bazel/rollup@4.1.0":
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/@bazel/rollup/-/rollup-4.1.0.tgz#63c1ae1043a0245e8f21cf9d59030093db3e9721"
-  integrity sha512-EYCbhH8gxGnGT0b/sgjcL5/9MQ2rVkUA6mg9sRJsJPuyyQ9mP0Cbvm0Xy9pqoeu5bFb8aTudQR973Xpcr4LaqQ==
-  dependencies:
-    "@bazel/worker" "4.1.0"
+"@bazel/rollup@^2.2.0":
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/@bazel/rollup/-/rollup-2.2.0.tgz#15651d545114e08db056f10a1eeaa4e76fc4df56"
+  integrity sha512-N4SyrvFkdAVc24CqFNhDtrR6P3XJTdPGziCuF7QM/BGihnsGlxF6+Dt2n5BTLJnObiB1St8vtRwCtAY8faxYWQ==
 
-"@bazel/typescript@4.1.0":
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/@bazel/typescript/-/typescript-4.1.0.tgz#883e793c37df70c4ebc44bc4139f6008b3eb92fa"
-  integrity sha512-EfjGIa70IGwkBqOeirrbCRgvC/jue91L5r13c6NErb6JiSAzbKuxyKvZp4isGNPqv5W/LqpLGim5/yUQAmKXww==
+"@bazel/typescript@^2.2.0":
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/@bazel/typescript/-/typescript-2.2.0.tgz#f2d3dce8715d574fe3146f19fdb8479abcc4d608"
+  integrity sha512-Thf8pXntBzE3EvJtyiTBNsfIf1QnYmGPQmUSGLcKUuuFoplUVYShMRHaxBoPZmYsnD/x+BFLgUKIzlXiEQpGqQ==
   dependencies:
-    "@bazel/worker" "4.1.0"
     protobufjs "6.8.8"
     semver "5.6.0"
     source-map-support "0.5.9"
-    tsutils "3.21.0"
-
-"@bazel/worker@4.1.0":
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/@bazel/worker/-/worker-4.1.0.tgz#f63a5c821ce01731a4bc1b38f7dff16332fe331c"
-  integrity sha512-xO8dMC2GR+MwZrRa+FTG9yr5yzUCFvs7MiVYEJ25L708XhOqs34at6BFH1Xa3/ZYQt7iDBFpwY6QzbhhIIQrXA==
-  dependencies:
-    google-protobuf "^3.6.1"
+    tsutils "2.27.2"
 
 "@bcoe/v8-coverage@^0.2.3":
   version "0.2.3"
@@ -99,169 +78,74 @@
   resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413"
   integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==
 
-"@eslint/eslintrc@^0.4.3":
-  version "0.4.3"
-  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c"
-  integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==
-  dependencies:
-    ajv "^6.12.4"
-    debug "^4.1.1"
-    espree "^7.3.0"
-    globals "^13.9.0"
-    ignore "^4.0.6"
-    import-fresh "^3.2.1"
-    js-yaml "^3.13.1"
-    minimatch "^3.0.4"
-    strip-json-comments "^3.1.1"
-
-"@grpc/grpc-js@^1.3.7":
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.4.1.tgz#799063a4ff7395987d4fceb2aab133629b003840"
-  integrity sha512-/chkA48TdAvATHA7RXJPeHQLdfFhpu51974s8htjO/XTDHA41j5+SkR5Io+lr9XsLmkZD6HxLyRAFGmA9wjO2w==
-  dependencies:
-    "@grpc/proto-loader" "^0.6.4"
-    "@types/node" ">=12.12.47"
-
-"@grpc/proto-loader@^0.6.4":
-  version "0.6.6"
-  resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.6.6.tgz#d8e51ea808ec5fa54d9defbecbf859336fb2da71"
-  integrity sha512-cdMaPZ8AiFz6ua6PUbP+LKbhwJbFXnrQ/mlnKGUyzDUZ3wp7vPLksnmLCBX6SHgSmjX7CbNVNLFYD5GmmjO4GQ==
-  dependencies:
-    "@types/long" "^4.0.1"
-    lodash.camelcase "^4.3.0"
-    long "^4.0.0"
-    protobufjs "^6.10.0"
-    yargs "^16.1.1"
-
-"@humanwhocodes/config-array@^0.5.0":
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9"
-  integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==
-  dependencies:
-    "@humanwhocodes/object-schema" "^1.2.0"
-    debug "^4.1.1"
-    minimatch "^3.0.4"
-
-"@humanwhocodes/object-schema@^1.2.0":
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz#87de7af9c231826fdd68ac7258f77c429e0e5fcf"
-  integrity sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==
-
 "@istanbuljs/schema@^0.1.2":
-  version "0.1.3"
-  resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98"
-  integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd"
+  integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==
 
-"@mapbox/node-pre-gyp@^1.0.5":
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.6.tgz#f859d601a210537e27530f363028cde56e0cf962"
-  integrity sha512-qK1ECws8UxuPqOA8F5LFD90vyVU33W7N3hGfgsOVfrJaRVc8McC3JClTDHpeSbL9CBrOHly/4GsNPAvIgNZE+g==
-  dependencies:
-    detect-libc "^1.0.3"
-    https-proxy-agent "^5.0.0"
-    make-dir "^3.1.0"
-    node-fetch "^2.6.5"
-    nopt "^5.0.0"
-    npmlog "^5.0.1"
-    rimraf "^3.0.2"
-    semver "^7.3.5"
-    tar "^6.1.11"
-
-"@material-ui/core@^4.12.1":
-  version "4.12.3"
-  resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.12.3.tgz#80d665caf0f1f034e52355c5450c0e38b099d3ca"
-  integrity sha512-sdpgI/PL56QVsEJldwEe4FFaFTLUqN+rd7sSZiRCdx2E/C7z5yK0y/khAWVBH24tXwto7I1hCzNWfJGZIYJKnw==
+"@material-ui/core@^4.10.2":
+  version "4.10.2"
+  resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.10.2.tgz#0ef78572132fcef1a25f6969bce0d34652d42e31"
+  integrity sha512-Uf4iDLi9sW6HKbVQDyDZDr1nMR4RUAE7w/RIIJZGNVZResC0xwmpLRZMtaUdSO43N0R0yJehfxTi4Z461Cd49A==
   dependencies:
     "@babel/runtime" "^7.4.4"
-    "@material-ui/styles" "^4.11.4"
-    "@material-ui/system" "^4.12.1"
-    "@material-ui/types" "5.1.0"
-    "@material-ui/utils" "^4.11.2"
+    "@material-ui/styles" "^4.10.0"
+    "@material-ui/system" "^4.9.14"
+    "@material-ui/types" "^5.1.0"
+    "@material-ui/utils" "^4.10.2"
     "@types/react-transition-group" "^4.2.0"
     clsx "^1.0.4"
     hoist-non-react-statics "^3.3.2"
     popper.js "1.16.1-lts"
     prop-types "^15.7.2"
-    react-is "^16.8.0 || ^17.0.0"
+    react-is "^16.8.0"
     react-transition-group "^4.4.0"
 
-"@material-ui/lab@^4.0.0-alpha.60":
-  version "4.0.0-alpha.60"
-  resolved "https://registry.yarnpkg.com/@material-ui/lab/-/lab-4.0.0-alpha.60.tgz#5ad203aed5a8569b0f1753945a21a05efa2234d2"
-  integrity sha512-fadlYsPJF+0fx2lRuyqAuJj7hAS1tLDdIEEdov5jlrpb5pp4b+mRDUqQTUxi4inRZHS1bEXpU8QWUhO6xX88aA==
-  dependencies:
-    "@babel/runtime" "^7.4.4"
-    "@material-ui/utils" "^4.11.2"
-    clsx "^1.0.4"
-    prop-types "^15.7.2"
-    react-is "^16.8.0 || ^17.0.0"
-
-"@material-ui/styles@^4.11.4":
-  version "4.11.4"
-  resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.11.4.tgz#eb9dfccfcc2d208243d986457dff025497afa00d"
-  integrity sha512-KNTIZcnj/zprG5LW0Sao7zw+yG3O35pviHzejMdcSGCdWbiO8qzRgOYL8JAxAsWBKOKYwVZxXtHWaB5T2Kvxew==
+"@material-ui/styles@^4.10.0":
+  version "4.10.0"
+  resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.10.0.tgz#2406dc23aa358217aa8cc772e6237bd7f0544071"
+  integrity sha512-XPwiVTpd3rlnbfrgtEJ1eJJdFCXZkHxy8TrdieaTvwxNYj42VnnCyFzxYeNW9Lhj4V1oD8YtQ6S5Gie7bZDf7Q==
   dependencies:
     "@babel/runtime" "^7.4.4"
     "@emotion/hash" "^0.8.0"
-    "@material-ui/types" "5.1.0"
-    "@material-ui/utils" "^4.11.2"
+    "@material-ui/types" "^5.1.0"
+    "@material-ui/utils" "^4.9.6"
     clsx "^1.0.4"
     csstype "^2.5.2"
     hoist-non-react-statics "^3.3.2"
-    jss "^10.5.1"
-    jss-plugin-camel-case "^10.5.1"
-    jss-plugin-default-unit "^10.5.1"
-    jss-plugin-global "^10.5.1"
-    jss-plugin-nested "^10.5.1"
-    jss-plugin-props-sort "^10.5.1"
-    jss-plugin-rule-value-function "^10.5.1"
-    jss-plugin-vendor-prefixer "^10.5.1"
+    jss "^10.0.3"
+    jss-plugin-camel-case "^10.0.3"
+    jss-plugin-default-unit "^10.0.3"
+    jss-plugin-global "^10.0.3"
+    jss-plugin-nested "^10.0.3"
+    jss-plugin-props-sort "^10.0.3"
+    jss-plugin-rule-value-function "^10.0.3"
+    jss-plugin-vendor-prefixer "^10.0.3"
     prop-types "^15.7.2"
 
-"@material-ui/system@^4.12.1":
-  version "4.12.1"
-  resolved "https://registry.yarnpkg.com/@material-ui/system/-/system-4.12.1.tgz#2dd96c243f8c0a331b2bb6d46efd7771a399707c"
-  integrity sha512-lUdzs4q9kEXZGhbN7BptyiS1rLNHe6kG9o8Y307HCvF4sQxbCgpL2qi+gUk+yI8a2DNk48gISEQxoxpgph0xIw==
+"@material-ui/system@^4.9.14":
+  version "4.9.14"
+  resolved "https://registry.yarnpkg.com/@material-ui/system/-/system-4.9.14.tgz#4b00c48b569340cefb2036d0596b93ac6c587a5f"
+  integrity sha512-oQbaqfSnNlEkXEziDcJDDIy8pbvwUmZXWNqlmIwDqr/ZdCK8FuV3f4nxikUh7hvClKV2gnQ9djh5CZFTHkZj3w==
   dependencies:
     "@babel/runtime" "^7.4.4"
-    "@material-ui/utils" "^4.11.2"
+    "@material-ui/utils" "^4.9.6"
     csstype "^2.5.2"
     prop-types "^15.7.2"
 
-"@material-ui/types@5.1.0":
+"@material-ui/types@^5.1.0":
   version "5.1.0"
   resolved "https://registry.yarnpkg.com/@material-ui/types/-/types-5.1.0.tgz#efa1c7a0b0eaa4c7c87ac0390445f0f88b0d88f2"
   integrity sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==
 
-"@material-ui/utils@^4.11.2":
-  version "4.11.2"
-  resolved "https://registry.yarnpkg.com/@material-ui/utils/-/utils-4.11.2.tgz#f1aefa7e7dff2ebcb97d31de51aecab1bb57540a"
-  integrity sha512-Uul8w38u+PICe2Fg2pDKCaIG7kOyhowZ9vjiC1FsVwPABTW8vPPKfF6OvxRq3IiBaI1faOJmgdvMG7rMJARBhA==
+"@material-ui/utils@^4.10.2", "@material-ui/utils@^4.9.6":
+  version "4.10.2"
+  resolved "https://registry.yarnpkg.com/@material-ui/utils/-/utils-4.10.2.tgz#3fd5470ca61b7341f1e0468ac8f29a70bf6df321"
+  integrity sha512-eg29v74P7W5r6a4tWWDAAfZldXIzfyO1am2fIsC39hdUUHm/33k6pGOKPbgDjg/U/4ifmgAePy/1OjkKN6rFRw==
   dependencies:
     "@babel/runtime" "^7.4.4"
     prop-types "^15.7.2"
-    react-is "^16.8.0 || ^17.0.0"
-
-"@nodelib/fs.scandir@2.1.5":
-  version "2.1.5"
-  resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
-  integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
-  dependencies:
-    "@nodelib/fs.stat" "2.0.5"
-    run-parallel "^1.1.9"
-
-"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
-  version "2.0.5"
-  resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
-  integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
-
-"@nodelib/fs.walk@^1.2.3":
-  version "1.2.8"
-  resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
-  integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
-  dependencies:
-    "@nodelib/fs.scandir" "2.1.5"
-    fastq "^1.6.0"
+    react-is "^16.8.0"
 
 "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
   version "1.1.2"
@@ -316,32 +200,33 @@
   resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
   integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=
 
-"@rollup/plugin-commonjs@^19.0.0":
-  version "19.0.2"
-  resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-19.0.2.tgz#1ccc3d63878d1bc9846f8969f09dd3b3e4ecc244"
-  integrity sha512-gBjarfqlC7qs0AutpRW/hrFNm+cd2/QKxhwyFa+srbg1oX7rDsEU3l+W7LAUhsAp9mPJMAkXDhLbQaVwEaE8bA==
+"@rollup/plugin-commonjs@^13.0.0":
+  version "13.0.0"
+  resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-13.0.0.tgz#8a1d684ba6848afe8b9e3d85649d4b2f6f7217ec"
+  integrity sha512-Anxc3qgkAi7peAyesTqGYidG5GRim9jtg8xhmykNaZkImtvjA7Wsqep08D2mYsqw1IF7rA3lYfciLgzUSgRoqw==
   dependencies:
-    "@rollup/pluginutils" "^3.1.0"
+    "@rollup/pluginutils" "^3.0.8"
     commondir "^1.0.1"
-    estree-walker "^2.0.1"
-    glob "^7.1.6"
-    is-reference "^1.2.1"
-    magic-string "^0.25.7"
-    resolve "^1.17.0"
+    estree-walker "^1.0.1"
+    glob "^7.1.2"
+    is-reference "^1.1.2"
+    magic-string "^0.25.2"
+    resolve "^1.11.0"
 
-"@rollup/plugin-node-resolve@^13.0.0":
-  version "13.0.6"
-  resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.0.6.tgz#29629070bb767567be8157f575cfa8f2b8e9ef77"
-  integrity sha512-sFsPDMPd4gMqnh2gS0uIxELnoRUp5kBl5knxD2EO0778G1oOJv4G1vyT2cpWz75OU2jDVcXhjVUuTAczGyFNKA==
+"@rollup/plugin-node-resolve@^8.0.1":
+  version "8.0.1"
+  resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-8.0.1.tgz#364b5938808ee6b5164dea5ef7291be3f7395199"
+  integrity sha512-KIeAmueDDaYMqMBnUngLVVZhURwxA12nq/YB6nGm5/JpVyOMwI1fCVU3oL/dAnnLBG7oiPXntO5LHOiMrfNXCA==
   dependencies:
-    "@rollup/pluginutils" "^3.1.0"
-    "@types/resolve" "1.17.1"
+    "@rollup/pluginutils" "^3.0.8"
+    "@types/resolve" "0.0.8"
     builtin-modules "^3.1.0"
+    deep-freeze "^0.0.1"
     deepmerge "^4.2.2"
     is-module "^1.0.0"
-    resolve "^1.19.0"
+    resolve "^1.14.2"
 
-"@rollup/pluginutils@^3.0.9", "@rollup/pluginutils@^3.1.0":
+"@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.0.9":
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b"
   integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==
@@ -362,47 +247,25 @@
   dependencies:
     defer-to-connect "^1.0.1"
 
-"@types/argparse@^2.0.10":
-  version "2.0.10"
-  resolved "https://registry.yarnpkg.com/@types/argparse/-/argparse-2.0.10.tgz#664e84808accd1987548d888b9d21b3e9c996a6c"
-  integrity sha512-C4wahC3gz3vQtvPazrJ5ONwmK1zSDllQboiWvpMM/iOswCYfBREFnjFbq/iWKIVOCl8+m5Pk6eva6/ZSsDuIGA==
+"@types/color-name@^1.1.1":
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
+  integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
 
-"@types/component-emitter@^1.2.10":
-  version "1.2.11"
-  resolved "https://registry.yarnpkg.com/@types/component-emitter/-/component-emitter-1.2.11.tgz#50d47d42b347253817a39709fef03ce66a108506"
-  integrity sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==
-
-"@types/cookie@^0.4.0":
-  version "0.4.1"
-  resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d"
-  integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==
-
-"@types/cors@^2.8.8":
-  version "2.8.12"
-  resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080"
-  integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==
-
-"@types/crc@^3.4.0":
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/@types/crc/-/crc-3.4.0.tgz#2366beb4399cd734b33e42c7ac809576e617d48a"
-  integrity sha1-I2a+tDmc1zSzPkLHrICVduYX1Io=
-  dependencies:
-    "@types/node" "*"
-
-"@types/estree@*":
-  version "0.0.50"
-  resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83"
-  integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==
+"@types/eslint-visitor-keys@^1.0.0":
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
+  integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==
 
 "@types/estree@0.0.39":
   version "0.0.39"
   resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
   integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
 
-"@types/google-protobuf@^3.15.5":
-  version "3.15.5"
-  resolved "https://registry.yarnpkg.com/@types/google-protobuf/-/google-protobuf-3.15.5.tgz#644b2be0f5613b1f822c70c73c6b0e0b5b5fa2ad"
-  integrity sha512-6bgv24B+A2bo9AfzReeg5StdiijKzwwnRflA8RLd1V4Yv995LeTmo0z69/MPbBDFSiZWdZHQygLo/ccXhMEDgw==
+"@types/estree@0.0.44":
+  version "0.0.44"
+  resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.44.tgz#980cc5a29a3ef3bea6ff1f7d021047d7ea575e21"
+  integrity sha512-iaIVzr+w2ZJ5HkidlZ3EJM8VTZb2MJLCjw3V+505yVts0gRC4UMvjw0d1HPtGqI/HQC/KdsYtayfzl+AXY2R8g==
 
 "@types/is-windows@^1.0.0":
   version "1.0.0"
@@ -414,155 +277,122 @@
   resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
   integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==
 
-"@types/jasmine@^3.7.8":
-  version "3.10.1"
-  resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.10.1.tgz#9a7ceab2e539503f70a7e6a7028c70171aca2754"
-  integrity sha512-So26woGjM6F9b2julbJlXdcPdyhwteZzEX2EbFmreuJBamPVVdp6w4djywUG9TmcwjiC+ECAe+RSSBgYEOgEqQ==
+"@types/jasmine@^3.5.10":
+  version "3.5.10"
+  resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.5.10.tgz#a1a41012012b5da9d4b205ba9eba58f6cce2ab7b"
+  integrity sha512-3F8qpwBAiVc5+HPJeXJpbrl+XjawGmciN5LgiO7Gv1pl1RHtjoMNqZpqEksaPJW05ViKe8snYInRs6xB25Xdew==
 
-"@types/json-schema@^7.0.7":
-  version "7.0.9"
-  resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
-  integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==
+"@types/json-schema@^7.0.3":
+  version "7.0.5"
+  resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd"
+  integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==
 
-"@types/long@^4.0.0", "@types/long@^4.0.1":
+"@types/long@^4.0.0":
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
   integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==
 
 "@types/minimist@^1.2.0":
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c"
-  integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.0.tgz#69a23a3ad29caf0097f06eda59b361ee2f0639f6"
+  integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=
 
-"@types/node@*", "@types/node@>=10.0.0", "@types/node@>=12.12.47", "@types/node@>=13.7.0", "@types/node@^16.0.1":
-  version "16.11.4"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.4.tgz#90771124822d6663814f7c1c9b45a6654d8fd964"
-  integrity sha512-TMgXmy0v2xWyuCSCJM6NCna2snndD8yvQF67J29ipdzMcsPa9u+o0tjF5+EQNdhcuZplYuouYqpc4zcd5I6amQ==
+"@types/node@*":
+  version "14.0.13"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.13.tgz#ee1128e881b874c371374c1f72201893616417c9"
+  integrity sha512-rouEWBImiRaSJsVA+ITTFM6ZxibuAlTuNOCyxVbwreu6k6+ujs7DfnU9o+PShFhET78pMBl3eH+AGSI5eOTkPA==
 
 "@types/node@^10.1.0":
-  version "10.17.60"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b"
-  integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==
+  version "10.17.31"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.31.tgz#fd3578fed25e5946372b06dab43eae49248367fa"
+  integrity sha512-AiazLSnsm7GfTxr08GrqeqMxygR/yV78RDk5gaw+S7pOP70BIqUbTFl9vZRyUC/XubcwIqkiiHxbJNFAGvSoOw==
+
+"@types/node@^13.11.1":
+  version "13.13.12"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.12.tgz#9c72e865380a7dc99999ea0ef20fc9635b503d20"
+  integrity sha512-zWz/8NEPxoXNT9YyF2osqyA9WjssZukYpgI4UYZpOjcyqwIUqWGkcCionaEb9Ki+FULyPyvNFpg/329Kd2/pbw==
 
 "@types/normalize-package-data@^2.4.0":
-  version "2.4.1"
-  resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301"
-  integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
+  integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==
 
 "@types/prop-types@*":
-  version "15.7.4"
-  resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11"
-  integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==
+  version "15.7.3"
+  resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
+  integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
 
-"@types/react-dom@^17.0.9":
-  version "17.0.10"
-  resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.10.tgz#d6972ec018d23cf22b99597f1289343d99ea9d9d"
-  integrity sha512-8oz3NAUId2z/zQdFI09IMhQPNgIbiP8Lslhv39DIDamr846/0spjZK0vnrMak0iB8EKb9QFTTIdg2Wj2zH5a3g==
+"@types/react-dom@^16.9.8":
+  version "16.9.8"
+  resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.8.tgz#fe4c1e11dfc67155733dfa6aa65108b4971cb423"
+  integrity sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==
   dependencies:
     "@types/react" "*"
 
 "@types/react-transition-group@^4.2.0":
-  version "4.4.4"
-  resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.4.tgz#acd4cceaa2be6b757db61ed7b432e103242d163e"
-  integrity sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug==
+  version "4.4.0"
+  resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.0.tgz#882839db465df1320e4753e6e9f70ca7e9b4d46d"
+  integrity sha512-/QfLHGpu+2fQOqQaXh8MG9q03bFENooTb/it4jr5kKaZlDQfWvjqWZg48AwzPVMBHlRuTRAY7hRHCEOXz5kV6w==
   dependencies:
     "@types/react" "*"
 
-"@types/react@*", "@types/react@^17.0.14":
-  version "17.0.31"
-  resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.31.tgz#fe05ebf91ff3ae35bb6b13f6c1b461db8089dff8"
-  integrity sha512-MQSR5EL4JZtdWRvqDgz9kXhSDDoy2zMTYyg7UhP+FZ5ttUOocWyxiqFJiI57sUG0BtaEX7WDXYQlkCYkb3X9vQ==
+"@types/react@*", "@types/react@^16.9.36":
+  version "16.9.36"
+  resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.36.tgz#ade589ff51e2a903e34ee4669e05dbfa0c1ce849"
+  integrity sha512-mGgUb/Rk/vGx4NCvquRuSH0GHBQKb1OqpGS9cT9lFxlTLHZgkksgI60TuIxubmn7JuCb+sENHhQciqa0npm0AQ==
   dependencies:
     "@types/prop-types" "*"
-    "@types/scheduler" "*"
-    csstype "^3.0.2"
+    csstype "^2.2.0"
 
-"@types/resolve@1.17.1":
-  version "1.17.1"
-  resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
-  integrity sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==
+"@types/resolve@0.0.8":
+  version "0.0.8"
+  resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194"
+  integrity sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==
   dependencies:
     "@types/node" "*"
 
-"@types/scheduler@*":
-  version "0.16.2"
-  resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"
-  integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==
-
-"@typescript-eslint/eslint-plugin@^4.2.0":
-  version "4.33.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz#c24dc7c8069c7706bc40d99f6fa87edcb2005276"
-  integrity sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg==
+"@typescript-eslint/eslint-plugin@2.31.0":
+  version "2.31.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.31.0.tgz#942c921fec5e200b79593c71fafb1e3f57aa2e36"
+  integrity sha512-iIC0Pb8qDaoit+m80Ln/aaeu9zKQdOLF4SHcGLarSeY1gurW6aU4JsOPMjKQwXlw70MvWKZQc6S2NamA8SJ/gg==
   dependencies:
-    "@typescript-eslint/experimental-utils" "4.33.0"
-    "@typescript-eslint/scope-manager" "4.33.0"
-    debug "^4.3.1"
+    "@typescript-eslint/experimental-utils" "2.31.0"
     functional-red-black-tree "^1.0.1"
-    ignore "^5.1.8"
-    regexpp "^3.1.0"
-    semver "^7.3.5"
-    tsutils "^3.21.0"
+    regexpp "^3.0.0"
+    tsutils "^3.17.1"
 
-"@typescript-eslint/experimental-utils@4.33.0":
-  version "4.33.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz#6f2a786a4209fa2222989e9380b5331b2810f7fd"
-  integrity sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q==
+"@typescript-eslint/experimental-utils@2.31.0":
+  version "2.31.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.31.0.tgz#a9ec514bf7fd5e5e82bc10dcb6a86d58baae9508"
+  integrity sha512-MI6IWkutLYQYTQgZ48IVnRXmLR/0Q6oAyJgiOror74arUMh7EWjJkADfirZhRsUMHeLJ85U2iySDwHTSnNi9vA==
   dependencies:
-    "@types/json-schema" "^7.0.7"
-    "@typescript-eslint/scope-manager" "4.33.0"
-    "@typescript-eslint/types" "4.33.0"
-    "@typescript-eslint/typescript-estree" "4.33.0"
-    eslint-scope "^5.1.1"
-    eslint-utils "^3.0.0"
+    "@types/json-schema" "^7.0.3"
+    "@typescript-eslint/typescript-estree" "2.31.0"
+    eslint-scope "^5.0.0"
+    eslint-utils "^2.0.0"
 
-"@typescript-eslint/parser@^4.2.0":
-  version "4.33.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.33.0.tgz#dfe797570d9694e560528d18eecad86c8c744899"
-  integrity sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==
+"@typescript-eslint/parser@2.31.0":
+  version "2.31.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.31.0.tgz#beddd4e8efe64995108b229b2862cd5752d40d6f"
+  integrity sha512-uph+w6xUOlyV2DLSC6o+fBDzZ5i7+3/TxAsH4h3eC64tlga57oMb96vVlXoMwjR/nN+xyWlsnxtbDkB46M2EPQ==
   dependencies:
-    "@typescript-eslint/scope-manager" "4.33.0"
-    "@typescript-eslint/types" "4.33.0"
-    "@typescript-eslint/typescript-estree" "4.33.0"
-    debug "^4.3.1"
+    "@types/eslint-visitor-keys" "^1.0.0"
+    "@typescript-eslint/experimental-utils" "2.31.0"
+    "@typescript-eslint/typescript-estree" "2.31.0"
+    eslint-visitor-keys "^1.1.0"
 
-"@typescript-eslint/scope-manager@4.33.0":
-  version "4.33.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz#d38e49280d983e8772e29121cf8c6e9221f280a3"
-  integrity sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ==
+"@typescript-eslint/typescript-estree@2.31.0":
+  version "2.31.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.31.0.tgz#ac536c2d46672aa1f27ba0ec2140d53670635cfd"
+  integrity sha512-vxW149bXFXXuBrAak0eKHOzbcu9cvi6iNcJDzEtOkRwGHxJG15chiAQAwhLOsk+86p9GTr/TziYvw+H9kMaIgA==
   dependencies:
-    "@typescript-eslint/types" "4.33.0"
-    "@typescript-eslint/visitor-keys" "4.33.0"
-
-"@typescript-eslint/types@4.33.0":
-  version "4.33.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72"
-  integrity sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==
-
-"@typescript-eslint/typescript-estree@4.33.0":
-  version "4.33.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz#0dfb51c2908f68c5c08d82aefeaf166a17c24609"
-  integrity sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==
-  dependencies:
-    "@typescript-eslint/types" "4.33.0"
-    "@typescript-eslint/visitor-keys" "4.33.0"
-    debug "^4.3.1"
-    globby "^11.0.3"
+    debug "^4.1.1"
+    eslint-visitor-keys "^1.1.0"
+    glob "^7.1.6"
     is-glob "^4.0.1"
-    semver "^7.3.5"
-    tsutils "^3.21.0"
-
-"@typescript-eslint/visitor-keys@4.33.0":
-  version "4.33.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz#2a22f77a41604289b7a186586e9ec48ca92ef1dd"
-  integrity sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg==
-  dependencies:
-    "@typescript-eslint/types" "4.33.0"
-    eslint-visitor-keys "^2.0.0"
-
-abbrev@1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
-  integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
+    lodash "^4.17.15"
+    semver "^6.3.0"
+    tsutils "^3.17.1"
 
 abstract-leveldown@~0.12.0, abstract-leveldown@~0.12.1:
   version "0.12.4"
@@ -579,78 +409,61 @@
     mime-types "~2.1.24"
     negotiator "0.6.2"
 
-acorn-jsx@^5.3.1:
-  version "5.3.2"
-  resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
-  integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
+acorn-jsx@^5.2.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe"
+  integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==
 
 acorn@^5.7.3:
   version "5.7.4"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e"
   integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==
 
-acorn@^7.4.0:
-  version "7.4.1"
-  resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
-  integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
+acorn@^7.1.1:
+  version "7.3.1"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.3.1.tgz#85010754db53c3fbaf3b9ea3e083aa5c5d147ffd"
+  integrity sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==
 
-agent-base@6:
-  version "6.0.2"
-  resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
-  integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
-  dependencies:
-    debug "4"
+after@0.8.2:
+  version "0.8.2"
+  resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
+  integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=
 
-ajv@^6.10.0, ajv@^6.12.4:
-  version "6.12.6"
-  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
-  integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
+ajv@^6.10.0, ajv@^6.10.2:
+  version "6.12.2"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd"
+  integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==
   dependencies:
     fast-deep-equal "^3.1.1"
     fast-json-stable-stringify "^2.0.0"
     json-schema-traverse "^0.4.1"
     uri-js "^4.2.2"
 
-ajv@^8.0.1:
-  version "8.6.3"
-  resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.6.3.tgz#11a66527761dc3e9a3845ea775d2d3c0414e8764"
-  integrity sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==
-  dependencies:
-    fast-deep-equal "^3.1.1"
-    json-schema-traverse "^1.0.0"
-    require-from-string "^2.0.2"
-    uri-js "^4.2.2"
-
 ansi-align@^3.0.0:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59"
-  integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb"
+  integrity sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==
   dependencies:
-    string-width "^4.1.0"
-
-ansi-colors@^4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
-  integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
+    string-width "^3.0.0"
 
 ansi-escapes@^4.2.1:
-  version "4.3.2"
-  resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
-  integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==
+  version "4.3.1"
+  resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61"
+  integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==
   dependencies:
-    type-fest "^0.21.3"
+    type-fest "^0.11.0"
 
-ansi-regex@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
-  integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
+ansi-regex@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
+  integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
 
-ansi-regex@^5.0.1:
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
-  integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
+ansi-regex@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
+  integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
 
-ansi-styles@^3.2.1:
+ansi-styles@^3.2.0, ansi-styles@^3.2.1:
   version "3.2.1"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
   integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
@@ -658,38 +471,21 @@
     color-convert "^1.9.0"
 
 ansi-styles@^4.0.0, ansi-styles@^4.1.0:
-  version "4.3.0"
-  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
-  integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359"
+  integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==
   dependencies:
+    "@types/color-name" "^1.1.1"
     color-convert "^2.0.1"
 
-ansi_up@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/ansi_up/-/ansi_up-5.1.0.tgz#9cf10e6d359bb434bdcfab5ae4c3abfe1617b6db"
-  integrity sha512-3wwu+nJCKBVBwOCurm0uv91lMoVkhFB+3qZQz3U11AmAdDJ4tkw1sNPWJQcVxMVYwe0pGEALOjSBOxdxNc+pNQ==
-
-anymatch@~3.1.2:
-  version "3.1.2"
-  resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
-  integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
+anymatch@~3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142"
+  integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==
   dependencies:
     normalize-path "^3.0.0"
     picomatch "^2.0.4"
 
-"aproba@^1.0.3 || ^2.0.0":
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc"
-  integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==
-
-are-we-there-yet@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c"
-  integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==
-  dependencies:
-    delegates "^1.0.0"
-    readable-stream "^3.6.0"
-
 argparse@^1.0.7:
   version "1.0.10"
   resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
@@ -697,55 +493,48 @@
   dependencies:
     sprintf-js "~1.0.2"
 
-argparse@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
-  integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
-
-array-includes@^3.1.3:
-  version "3.1.4"
-  resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.4.tgz#f5b493162c760f3539631f005ba2bb46acb45ba9"
-  integrity sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==
+array-includes@^3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348"
+  integrity sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==
   dependencies:
-    call-bind "^1.0.2"
     define-properties "^1.1.3"
-    es-abstract "^1.19.1"
-    get-intrinsic "^1.1.1"
-    is-string "^1.0.7"
+    es-abstract "^1.17.0"
+    is-string "^1.0.5"
 
-array-union@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
-  integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
-
-array.prototype.flatmap@^1.2.4:
-  version "1.2.5"
-  resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.5.tgz#908dc82d8a406930fdf38598d51e7411d18d4446"
-  integrity sha512-08u6rVyi1Lj7oqWbS9nUxliETrtIROT4XGTA4D/LWGten6E3ocm7cy9SIrmNHOL5XVbVuckUp3X6Xyg8/zpvHA==
-  dependencies:
-    call-bind "^1.0.0"
-    define-properties "^1.1.3"
-    es-abstract "^1.19.0"
+arraybuffer.slice@~0.0.7:
+  version "0.0.7"
+  resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675"
+  integrity sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==
 
 arrify@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
   integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=
 
-asn1.js@^5.2.0:
-  version "5.4.1"
-  resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07"
-  integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==
+arrify@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa"
+  integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==
+
+asn1.js@^4.0.0:
+  version "4.10.1"
+  resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0"
+  integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==
   dependencies:
     bn.js "^4.0.0"
     inherits "^2.0.1"
     minimalistic-assert "^1.0.0"
-    safer-buffer "^2.1.0"
 
-astral-regex@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
-  integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
+astral-regex@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9"
+  integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==
+
+async-limiter@~1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
+  integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
 
 async@^2.6.2:
   version "2.6.3"
@@ -759,35 +548,37 @@
   resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
   integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
 
-balanced-match@^1.0.0:
+backo2@1.0.2:
   version "1.0.2"
-  resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
-  integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+  resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"
+  integrity sha1-MasayLEpNjRj41s+u2n038+6eUc=
 
-base64-arraybuffer@0.1.4:
-  version "0.1.4"
-  resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812"
-  integrity sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=
+balanced-match@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
+  integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
 
-base64-js@^1.3.1, base64-js@^1.5.1:
-  version "1.5.1"
-  resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
-  integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
+base64-arraybuffer@0.1.5:
+  version "0.1.5"
+  resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8"
+  integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg=
 
-base64id@2.0.0, base64id@~2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6"
-  integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==
+base64id@1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/base64id/-/base64id-1.0.0.tgz#47688cb99bb6804f0e06d3e763b1c32e57d8e6b6"
+  integrity sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=
 
-basic-auth@^1.0.3:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-1.1.0.tgz#45221ee429f7ee1e5035be3f51533f1cdfd29884"
-  integrity sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=
+better-assert@~1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522"
+  integrity sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=
+  dependencies:
+    callsite "1.0.0"
 
 binary-extensions@^2.0.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
-  integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c"
+  integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==
 
 bl@~0.8.1:
   version "0.8.2"
@@ -796,17 +587,22 @@
   dependencies:
     readable-stream "~1.0.26"
 
-bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9:
-  version "4.12.0"
-  resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
-  integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==
+blob@0.0.5:
+  version "0.0.5"
+  resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683"
+  integrity sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==
 
-bn.js@^5.0.0, bn.js@^5.1.1:
-  version "5.2.0"
-  resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002"
-  integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==
+bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.4.0:
+  version "4.11.9"
+  resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828"
+  integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==
 
-body-parser@^1.19.0:
+bn.js@^5.1.1:
+  version "5.1.2"
+  resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.2.tgz#c9686902d3c9a27729f43ab10f9d79c2004da7b0"
+  integrity sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA==
+
+body-parser@^1.16.1:
   version "1.19.0"
   resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
   integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
@@ -822,19 +618,19 @@
     raw-body "2.4.0"
     type-is "~1.6.17"
 
-boxen@^5.0.0:
-  version "5.1.2"
-  resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50"
-  integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==
+boxen@^4.2.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64"
+  integrity sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==
   dependencies:
     ansi-align "^3.0.0"
-    camelcase "^6.2.0"
-    chalk "^4.1.0"
-    cli-boxes "^2.2.1"
-    string-width "^4.2.2"
-    type-fest "^0.20.2"
+    camelcase "^5.3.1"
+    chalk "^3.0.0"
+    cli-boxes "^2.2.0"
+    string-width "^4.1.0"
+    term-size "^2.1.0"
+    type-fest "^0.8.1"
     widest-line "^3.1.0"
-    wrap-ansi "^7.0.0"
 
 brace-expansion@^1.1.7:
   version "1.1.11"
@@ -844,14 +640,14 @@
     balanced-match "^1.0.0"
     concat-map "0.0.1"
 
-braces@^3.0.1, braces@^3.0.2, braces@~3.0.2:
+braces@^3.0.2, braces@~3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
   integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
   dependencies:
     fill-range "^7.0.1"
 
-brorand@^1.0.1, brorand@^1.1.0:
+brorand@^1.0.1:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
   integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=
@@ -897,23 +693,23 @@
     levelup "^0.18.2"
 
 browserify-rsa@^4.0.0, browserify-rsa@^4.0.1:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d"
-  integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524"
+  integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=
   dependencies:
-    bn.js "^5.0.0"
+    bn.js "^4.1.0"
     randombytes "^2.0.1"
 
 browserify-sign@^4.0.0:
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3"
-  integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.0.tgz#545d0b1b07e6b2c99211082bf1b12cce7a0b0e11"
+  integrity sha512-hEZC1KEeYuoHRqhGhTy6gWrpJA3ZDjFWv0DE61643ZnOXAKJb3u7yWcrU0mMc9SwAqK1n7myPGndkp0dFG7NFA==
   dependencies:
     bn.js "^5.1.1"
     browserify-rsa "^4.0.1"
     create-hash "^1.2.0"
     create-hmac "^1.1.7"
-    elliptic "^6.5.3"
+    elliptic "^6.5.2"
     inherits "^2.0.4"
     parse-asn1 "^5.1.5"
     readable-stream "^3.6.0"
@@ -925,49 +721,33 @@
   integrity sha1-8mNHuC33b9N+GLy1KIxJcM/VxAQ=
 
 buffer-from@^1.0.0:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
-  integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
+  integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
 
 buffer-xor@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
   integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=
 
-buffer@^5.1.0:
-  version "5.7.1"
-  resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
-  integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
-  dependencies:
-    base64-js "^1.3.1"
-    ieee754 "^1.1.13"
-
-buffer@^6.0.3:
-  version "6.0.3"
-  resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
-  integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
-  dependencies:
-    base64-js "^1.3.1"
-    ieee754 "^1.2.1"
-
 builtin-modules@^3.1.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887"
-  integrity sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484"
+  integrity sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==
 
 bytes@3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
   integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
 
-c8@~7.5.0:
-  version "7.5.0"
-  resolved "https://registry.yarnpkg.com/c8/-/c8-7.5.0.tgz#a69439ab82848f344a74bb25dc5dd4e867764481"
-  integrity sha512-GSkLsbvDr+FIwjNSJ8OwzWAyuznEYGTAd1pzb/Kr0FMLuV4vqYJTyjboDTwmlUNAG6jAU3PFWzqIdKrOt1D8tw==
+c8@~7.1.0:
+  version "7.1.2"
+  resolved "https://registry.yarnpkg.com/c8/-/c8-7.1.2.tgz#3fd785e8d264175ceffe92c74607f5cfb12f018d"
+  integrity sha512-lCEwL9lbvWOQLxoLw8RF7PM8Cdj+rKxRp/PyWC9S8xASvYHRwXQ2gxzsNTgLhQM1Utc1YDAjzQYPQIxVEyelGg==
   dependencies:
     "@bcoe/v8-coverage" "^0.2.3"
     "@istanbuljs/schema" "^0.1.2"
-    find-up "^5.0.0"
+    find-up "^4.0.0"
     foreground-child "^2.0.0"
     furi "^2.0.0"
     istanbul-lib-coverage "^3.0.0"
@@ -975,9 +755,9 @@
     istanbul-reports "^3.0.2"
     rimraf "^3.0.0"
     test-exclude "^6.0.0"
-    v8-to-istanbul "^7.1.0"
-    yargs "^16.0.0"
-    yargs-parser "^20.0.0"
+    v8-to-istanbul "^4.1.2"
+    yargs "^15.0.0"
+    yargs-parser "^18.0.0"
 
 cacheable-request@^6.0.0:
   version "6.1.0"
@@ -992,13 +772,10 @@
     normalize-url "^4.1.0"
     responselike "^1.0.2"
 
-call-bind@^1.0.0, call-bind@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
-  integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
-  dependencies:
-    function-bind "^1.1.1"
-    get-intrinsic "^1.0.2"
+callsite@1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20"
+  integrity sha1-KAOY5dZkvXQDi28JBRU+borxvCA=
 
 callsites@^3.0.0:
   version "3.1.0"
@@ -1014,17 +791,17 @@
     map-obj "^4.0.0"
     quick-lru "^4.0.1"
 
-camelcase@^5.3.1:
+camelcase@^5.0.0, camelcase@^5.3.1:
   version "5.3.1"
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
   integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
 
-camelcase@^6.2.0:
-  version "6.2.0"
-  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809"
-  integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
+camelcase@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.0.0.tgz#5259f7c30e35e278f1bdc2a4d91230b37cad981e"
+  integrity sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==
 
-chalk@^2.0.0:
+chalk@^2.0.0, chalk@^2.1.0:
   version "2.4.2"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
   integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@@ -1033,10 +810,18 @@
     escape-string-regexp "^1.0.5"
     supports-color "^5.3.0"
 
-chalk@^4.0.0, chalk@^4.1.0:
-  version "4.1.2"
-  resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
-  integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
+chalk@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
+  integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
+  dependencies:
+    ansi-styles "^4.1.0"
+    supports-color "^7.1.0"
+
+chalk@^4.0.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
+  integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==
   dependencies:
     ansi-styles "^4.1.0"
     supports-color "^7.1.0"
@@ -1046,25 +831,20 @@
   resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
   integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
 
-chokidar@^3.5.1:
-  version "3.5.2"
-  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75"
-  integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==
+chokidar@^3.0.0:
+  version "3.4.0"
+  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.0.tgz#b30611423ce376357c765b9b8f904b9fba3c0be8"
+  integrity sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==
   dependencies:
-    anymatch "~3.1.2"
+    anymatch "~3.1.1"
     braces "~3.0.2"
-    glob-parent "~5.1.2"
+    glob-parent "~5.1.0"
     is-binary-path "~2.1.0"
     is-glob "~4.0.1"
     normalize-path "~3.0.0"
-    readdirp "~3.6.0"
+    readdirp "~3.4.0"
   optionalDependencies:
-    fsevents "~2.3.2"
-
-chownr@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
-  integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
+    fsevents "~2.1.2"
 
 ci-info@^2.0.0:
   version "2.0.0"
@@ -1079,10 +859,10 @@
     inherits "^2.0.1"
     safe-buffer "^5.0.1"
 
-cli-boxes@^2.2.1:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f"
-  integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==
+cli-boxes@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.0.tgz#538ecae8f9c6ca508e3c3c95b453fe93cb4c168d"
+  integrity sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w==
 
 cli-cursor@^3.1.0:
   version "3.1.0"
@@ -1091,19 +871,19 @@
   dependencies:
     restore-cursor "^3.1.0"
 
-cli-width@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6"
-  integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==
+cli-width@^2.0.0:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48"
+  integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==
 
-cliui@^7.0.2:
-  version "7.0.4"
-  resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
-  integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
+cliui@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1"
+  integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==
   dependencies:
     string-width "^4.2.0"
     strip-ansi "^6.0.0"
-    wrap-ansi "^7.0.0"
+    wrap-ansi "^6.2.0"
 
 clone-response@^1.0.2:
   version "1.0.2"
@@ -1146,12 +926,7 @@
   resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
   integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
 
-color-support@^1.1.2:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
-  integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==
-
-colors@^1.4.0:
+colors@^1.1.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
   integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==
@@ -1161,10 +936,20 @@
   resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
   integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
 
-component-emitter@~1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
-  integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
+component-bind@1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1"
+  integrity sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=
+
+component-emitter@1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
+  integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=
+
+component-inherit@0.0.3:
+  version "0.0.3"
+  resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143"
+  integrity sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=
 
 concat-map@0.0.1:
   version "0.0.1"
@@ -1193,7 +978,7 @@
     write-file-atomic "^3.0.0"
     xdg-basedir "^4.0.0"
 
-connect@^3.7.0:
+connect@^3.6.0:
   version "3.7.0"
   resolved "https://registry.yarnpkg.com/connect/-/connect-3.7.0.tgz#5d49348910caa5e07a01800b030d0c35f20484f8"
   integrity sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==
@@ -1203,60 +988,40 @@
     parseurl "~1.3.3"
     utils-merge "1.0.1"
 
-console-control-strings@^1.0.0, console-control-strings@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
-  integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
-
 content-type@~1.0.4:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
   integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
 
 convert-source-map@^1.6.0:
-  version "1.8.0"
-  resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369"
-  integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442"
+  integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==
   dependencies:
     safe-buffer "~5.1.1"
 
-cookie@~0.4.1:
-  version "0.4.1"
-  resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1"
-  integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==
+cookie@0.3.1:
+  version "0.3.1"
+  resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
+  integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=
+
+core-js-pure@^3.0.0:
+  version "3.6.5"
+  resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813"
+  integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==
 
 core-util-is@~1.0.0:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
-  integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
-
-cors@~2.8.5:
-  version "2.8.5"
-  resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
-  integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
-  dependencies:
-    object-assign "^4"
-    vary "^1"
-
-corser@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/corser/-/corser-2.0.1.tgz#8eda252ecaab5840dcd975ceb90d9370c819ff87"
-  integrity sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=
-
-crc@^3.8.0:
-  version "3.8.0"
-  resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6"
-  integrity sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==
-  dependencies:
-    buffer "^5.1.0"
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
+  integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
 
 create-ecdh@^4.0.0:
-  version "4.0.4"
-  resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e"
-  integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff"
+  integrity sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==
   dependencies:
     bn.js "^4.1.0"
-    elliptic "^6.5.3"
+    elliptic "^6.0.0"
 
 create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0:
   version "1.2.0"
@@ -1281,7 +1046,18 @@
     safe-buffer "^5.0.1"
     sha.js "^2.4.8"
 
-cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
+cross-spawn@^6.0.5:
+  version "6.0.5"
+  resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
+  integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
+  dependencies:
+    nice-try "^1.0.4"
+    path-key "^2.0.1"
+    semver "^5.5.0"
+    shebang-command "^1.2.0"
+    which "^1.2.9"
+
+cross-spawn@^7.0.0:
   version "7.0.3"
   resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
   integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
@@ -1320,31 +1096,21 @@
     "@babel/runtime" "^7.8.3"
     is-in-browser "^1.0.2"
 
-csstype@^2.5.2:
-  version "2.6.18"
-  resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.18.tgz#980a8b53085f34af313410af064f2bd241784218"
-  integrity sha512-RSU6Hyeg14am3Ah4VZEmeX8H7kLwEEirXe6aU2IPfKNvhXwTflK5HQRDNI0ypQXoqmm+QPyG2IaPuQE5zMwSIQ==
-
-csstype@^3.0.2:
-  version "3.0.9"
-  resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.9.tgz#6410af31b26bd0520933d02cbc64fce9ce3fbf0b"
-  integrity sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==
+csstype@^2.2.0, csstype@^2.5.2, csstype@^2.6.5, csstype@^2.6.7:
+  version "2.6.10"
+  resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.10.tgz#e63af50e66d7c266edb6b32909cfd0aabe03928b"
+  integrity sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w==
 
 custom-event@~1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425"
   integrity sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=
 
-date-format@^2.1.0:
+date-format@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/date-format/-/date-format-2.1.0.tgz#31d5b5ea211cf5fd764cd38baf9d033df7e125cf"
   integrity sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==
 
-date-format@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/date-format/-/date-format-3.0.0.tgz#eb8780365c7d2b1511078fb491e6479780f3ad95"
-  integrity sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w==
-
 debug@2.6.9:
   version "2.6.9"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@@ -1352,20 +1118,27 @@
   dependencies:
     ms "2.0.0"
 
-debug@4, debug@^4.0.1, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@~4.3.1:
-  version "4.3.2"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
-  integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
-  dependencies:
-    ms "2.1.2"
-
-debug@^3.1.1:
-  version "3.2.7"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
-  integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
+debug@^3.2.6:
+  version "3.2.6"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
+  integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
   dependencies:
     ms "^2.1.1"
 
+debug@^4.0.1, debug@^4.1.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
+  integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
+  dependencies:
+    ms "^2.1.1"
+
+debug@~3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
+  integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
+  dependencies:
+    ms "2.0.0"
+
 decamelize-keys@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9"
@@ -1396,10 +1169,15 @@
   resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
   integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
 
-deep-is@^0.1.3:
-  version "0.1.4"
-  resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
-  integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
+deep-freeze@^0.0.1:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/deep-freeze/-/deep-freeze-0.0.1.tgz#3a0b0005de18672819dfd38cd31f91179c893e84"
+  integrity sha1-OgsABd4YZygZ39OM0x+RF5yJPoQ=
+
+deep-is@~0.1.3:
+  version "0.1.3"
+  resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
+  integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=
 
 deepmerge@^4.2.2:
   version "4.2.2"
@@ -1418,18 +1196,13 @@
   dependencies:
     abstract-leveldown "~0.12.1"
 
-define-properties@^1.1.3:
+define-properties@^1.1.2, define-properties@^1.1.3:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
   integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
   dependencies:
     object-keys "^1.0.12"
 
-delegates@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
-  integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
-
 depd@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
@@ -1443,11 +1216,6 @@
     inherits "^2.0.1"
     minimalistic-assert "^1.0.0"
 
-detect-libc@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
-  integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
-
 di@^0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c"
@@ -1462,13 +1230,6 @@
     miller-rabin "^4.0.0"
     randombytes "^2.0.0"
 
-dir-glob@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
-  integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==
-  dependencies:
-    path-type "^4.0.0"
-
 doctrine@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
@@ -1484,14 +1245,14 @@
     esutils "^2.0.2"
 
 dom-helpers@^5.0.1:
-  version "5.2.1"
-  resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
-  integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==
+  version "5.1.4"
+  resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.1.4.tgz#4609680ab5c79a45f2531441f1949b79d6587f4b"
+  integrity sha512-TjMyeVUvNEnOnhzs6uAn9Ya47GmMo3qq7m+Lr/3ON0Rs5kHvb8I+SQYjLUSYn7qhEm0QjW0yrBkvz9yOrwwz1A==
   dependencies:
     "@babel/runtime" "^7.8.7"
-    csstype "^3.0.2"
+    csstype "^2.6.7"
 
-dom-serialize@^2.2.1:
+dom-serialize@^2.2.0:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b"
   integrity sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=
@@ -1501,40 +1262,10 @@
     extend "^3.0.0"
     void-elements "^2.0.0"
 
-dom-serializer@^1.0.1:
-  version "1.3.2"
-  resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91"
-  integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==
-  dependencies:
-    domelementtype "^2.0.1"
-    domhandler "^4.2.0"
-    entities "^2.0.0"
-
-domelementtype@^2.0.1, domelementtype@^2.2.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57"
-  integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==
-
-domhandler@4.2.2, domhandler@^4.0.0, domhandler@^4.2.0:
-  version "4.2.2"
-  resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.2.tgz#e825d721d19a86b8c201a35264e226c678ee755f"
-  integrity sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w==
-  dependencies:
-    domelementtype "^2.2.0"
-
-domutils@^2.5.2:
-  version "2.8.0"
-  resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
-  integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==
-  dependencies:
-    dom-serializer "^1.0.1"
-    domelementtype "^2.2.0"
-    domhandler "^4.2.0"
-
 dot-prop@^5.2.0:
-  version "5.3.0"
-  resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88"
-  integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb"
+  integrity sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==
   dependencies:
     is-obj "^2.0.0"
 
@@ -1548,18 +1279,23 @@
   resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
   integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
 
-elliptic@^6.5.3:
-  version "6.5.4"
-  resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
-  integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==
+elliptic@^6.0.0, elliptic@^6.5.2:
+  version "6.5.2"
+  resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762"
+  integrity sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==
   dependencies:
-    bn.js "^4.11.9"
-    brorand "^1.1.0"
+    bn.js "^4.4.0"
+    brorand "^1.0.1"
     hash.js "^1.0.0"
-    hmac-drbg "^1.0.1"
-    inherits "^2.0.4"
-    minimalistic-assert "^1.0.1"
-    minimalistic-crypto-utils "^1.0.1"
+    hmac-drbg "^1.0.0"
+    inherits "^2.0.1"
+    minimalistic-assert "^1.0.0"
+    minimalistic-crypto-utils "^1.0.0"
+
+emoji-regex@^7.0.1:
+  version "7.0.3"
+  resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
+  integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==
 
 emoji-regex@^8.0.0:
   version "8.0.0"
@@ -1578,47 +1314,55 @@
   dependencies:
     once "^1.4.0"
 
-engine.io-parser@~4.0.0:
-  version "4.0.3"
-  resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-4.0.3.tgz#83d3a17acfd4226f19e721bb22a1ee8f7662d2f6"
-  integrity sha512-xEAAY0msNnESNPc00e19y5heTPX4y/TJ36gr8t1voOaNmTojP9b3oK3BbJLFufW2XFPQaaijpFewm2g2Um3uqA==
+engine.io-client@~3.2.0:
+  version "3.2.1"
+  resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.2.1.tgz#6f54c0475de487158a1a7c77d10178708b6add36"
+  integrity sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==
   dependencies:
-    base64-arraybuffer "0.1.4"
+    component-emitter "1.2.1"
+    component-inherit "0.0.3"
+    debug "~3.1.0"
+    engine.io-parser "~2.1.1"
+    has-cors "1.1.0"
+    indexof "0.0.1"
+    parseqs "0.0.5"
+    parseuri "0.0.5"
+    ws "~3.3.1"
+    xmlhttprequest-ssl "~1.5.4"
+    yeast "0.1.2"
 
-engine.io@~4.1.0:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-4.1.1.tgz#9a8f8a5ac5a5ea316183c489bf7f5b6cf91ace5b"
-  integrity sha512-t2E9wLlssQjGw0nluF6aYyfX8LwYU8Jj0xct+pAhfWfv/YrBn6TSNtEYsgxHIfaMqfrLx07czcMg9bMN6di+3w==
+engine.io-parser@~2.1.0, engine.io-parser@~2.1.1:
+  version "2.1.3"
+  resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.1.3.tgz#757ab970fbf2dfb32c7b74b033216d5739ef79a6"
+  integrity sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==
+  dependencies:
+    after "0.8.2"
+    arraybuffer.slice "~0.0.7"
+    base64-arraybuffer "0.1.5"
+    blob "0.0.5"
+    has-binary2 "~1.0.2"
+
+engine.io@~3.2.0:
+  version "3.2.1"
+  resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.2.1.tgz#b60281c35484a70ee0351ea0ebff83ec8c9522a2"
+  integrity sha512-+VlKzHzMhaU+GsCIg4AoXF1UdDFjHHwMmMKqMJNDNLlUlejz58FCy4LBqB2YVJskHGYl06BatYWKP2TVdVXE5w==
   dependencies:
     accepts "~1.3.4"
-    base64id "2.0.0"
-    cookie "~0.4.1"
-    cors "~2.8.5"
-    debug "~4.3.1"
-    engine.io-parser "~4.0.0"
-    ws "~7.4.2"
-
-enquirer@^2.3.5:
-  version "2.3.6"
-  resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
-  integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==
-  dependencies:
-    ansi-colors "^4.1.1"
+    base64id "1.0.0"
+    cookie "0.3.1"
+    debug "~3.1.0"
+    engine.io-parser "~2.1.0"
+    ws "~3.3.1"
 
 ent@~2.2.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d"
   integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0=
 
-entities@^2.0.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
-  integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
-
 errno@^0.1.1, errno@~0.1.1:
-  version "0.1.8"
-  resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f"
-  integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==
+  version "0.1.7"
+  resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618"
+  integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==
   dependencies:
     prr "~1.0.1"
 
@@ -1629,31 +1373,22 @@
   dependencies:
     is-arrayish "^0.2.1"
 
-es-abstract@^1.19.0, es-abstract@^1.19.1:
-  version "1.19.1"
-  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3"
-  integrity sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==
+es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.5:
+  version "1.17.6"
+  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a"
+  integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==
   dependencies:
-    call-bind "^1.0.2"
     es-to-primitive "^1.2.1"
     function-bind "^1.1.1"
-    get-intrinsic "^1.1.1"
-    get-symbol-description "^1.0.0"
     has "^1.0.3"
-    has-symbols "^1.0.2"
-    internal-slot "^1.0.3"
-    is-callable "^1.2.4"
-    is-negative-zero "^2.0.1"
-    is-regex "^1.1.4"
-    is-shared-array-buffer "^1.0.1"
-    is-string "^1.0.7"
-    is-weakref "^1.0.1"
-    object-inspect "^1.11.0"
+    has-symbols "^1.0.1"
+    is-callable "^1.2.0"
+    is-regex "^1.1.0"
+    object-inspect "^1.7.0"
     object-keys "^1.1.1"
-    object.assign "^4.1.2"
-    string.prototype.trimend "^1.0.4"
-    string.prototype.trimstart "^1.0.4"
-    unbox-primitive "^1.0.1"
+    object.assign "^4.1.0"
+    string.prototype.trimend "^1.0.1"
+    string.prototype.trimstart "^1.0.1"
 
 es-to-primitive@^1.2.1:
   version "1.2.1"
@@ -1664,11 +1399,6 @@
     is-date-object "^1.0.1"
     is-symbol "^1.0.2"
 
-escalade@^3.1.1:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
-  integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
-
 escape-goat@^2.0.0:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675"
@@ -1684,15 +1414,12 @@
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
   integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
 
-escape-string-regexp@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
-  integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
-
-eslint-config-prettier@^7.0.0:
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-7.2.0.tgz#f4a4bd2832e810e8cc7c1411ec85b3e85c0c53f9"
-  integrity sha512-rV4Qu0C3nfJKPOAhFujFxB7RMP+URFyQqqOZW9DMRD7ZDTFyjaIlETU3xzHELt++4ugC0+Jm084HQYkkJe+Ivg==
+eslint-config-prettier@^6.10.1:
+  version "6.11.0"
+  resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.11.0.tgz#f6d2238c1290d01c859a8b5c1f7d352a0b0da8b1"
+  integrity sha512-oB8cpLWSAjOVFEJhhyMZh6NOEOtBVziaqdDQ86+qhDHFbZXoRTM7pNSvFRfW/W/L/LrQ38C99J5CGuRBBzBsdA==
+  dependencies:
+    get-stdin "^6.0.0"
 
 eslint-plugin-es@^3.0.0:
   version "3.0.1"
@@ -1714,148 +1441,137 @@
     resolve "^1.10.1"
     semver "^6.1.0"
 
-eslint-plugin-prettier@^3.1.4:
-  version "3.4.1"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz#e9ddb200efb6f3d05ffe83b1665a716af4a387e5"
-  integrity sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==
+eslint-plugin-prettier@^3.1.2:
+  version "3.1.4"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.4.tgz#168ab43154e2ea57db992a2cd097c828171f75c2"
+  integrity sha512-jZDa8z76klRqo+TdGDTFJSavwbnWK2ZpqGKNZ+VvweMW516pDUMmQ2koXvxEE4JhzNvTv+radye/bWGBmA6jmg==
   dependencies:
     prettier-linter-helpers "^1.0.0"
 
-eslint-plugin-react@^7.24.0:
-  version "7.26.1"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.26.1.tgz#41bcfe3e39e6a5ac040971c1af94437c80daa40e"
-  integrity sha512-Lug0+NOFXeOE+ORZ5pbsh6mSKjBKXDXItUD2sQoT+5Yl0eoT82DqnXeTMfUare4QVCn9QwXbfzO/dBLjLXwVjQ==
+eslint-plugin-react@^7.20.0:
+  version "7.20.0"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.20.0.tgz#f98712f0a5e57dfd3e5542ef0604b8739cd47be3"
+  integrity sha512-rqe1abd0vxMjmbPngo4NaYxTcR3Y4Hrmc/jg4T+sYz63yqlmJRknpEQfmWY+eDWPuMmix6iUIK+mv0zExjeLgA==
   dependencies:
-    array-includes "^3.1.3"
-    array.prototype.flatmap "^1.2.4"
+    array-includes "^3.1.1"
     doctrine "^2.1.0"
-    estraverse "^5.2.0"
-    jsx-ast-utils "^2.4.1 || ^3.0.0"
-    minimatch "^3.0.4"
-    object.entries "^1.1.4"
-    object.fromentries "^2.0.4"
-    object.hasown "^1.0.0"
-    object.values "^1.1.4"
+    has "^1.0.3"
+    jsx-ast-utils "^2.2.3"
+    object.entries "^1.1.1"
+    object.fromentries "^2.0.2"
+    object.values "^1.1.1"
     prop-types "^15.7.2"
-    resolve "^2.0.0-next.3"
-    semver "^6.3.0"
-    string.prototype.matchall "^4.0.5"
+    resolve "^1.15.1"
+    string.prototype.matchall "^4.0.2"
+    xregexp "^4.3.0"
 
-eslint-scope@^5.1.1:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
-  integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
+eslint-scope@^5.0.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.0.tgz#d0f971dfe59c69e0cada684b23d49dbf82600ce5"
+  integrity sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==
   dependencies:
-    esrecurse "^4.3.0"
+    esrecurse "^4.1.0"
     estraverse "^4.1.1"
 
-eslint-utils@^2.0.0, eslint-utils@^2.1.0:
+eslint-utils@^1.4.3:
+  version "1.4.3"
+  resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f"
+  integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==
+  dependencies:
+    eslint-visitor-keys "^1.1.0"
+
+eslint-utils@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27"
   integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==
   dependencies:
     eslint-visitor-keys "^1.1.0"
 
-eslint-utils@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672"
-  integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==
+eslint-visitor-keys@^1.1.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz#74415ac884874495f78ec2a97349525344c981fa"
+  integrity sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ==
+
+eslint@^6.8.0:
+  version "6.8.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb"
+  integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==
   dependencies:
-    eslint-visitor-keys "^2.0.0"
-
-eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
-  integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==
-
-eslint-visitor-keys@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303"
-  integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==
-
-eslint@^7.10.0, eslint@^7.30.0:
-  version "7.32.0"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d"
-  integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==
-  dependencies:
-    "@babel/code-frame" "7.12.11"
-    "@eslint/eslintrc" "^0.4.3"
-    "@humanwhocodes/config-array" "^0.5.0"
+    "@babel/code-frame" "^7.0.0"
     ajv "^6.10.0"
-    chalk "^4.0.0"
-    cross-spawn "^7.0.2"
+    chalk "^2.1.0"
+    cross-spawn "^6.0.5"
     debug "^4.0.1"
     doctrine "^3.0.0"
-    enquirer "^2.3.5"
-    escape-string-regexp "^4.0.0"
-    eslint-scope "^5.1.1"
-    eslint-utils "^2.1.0"
-    eslint-visitor-keys "^2.0.0"
-    espree "^7.3.1"
-    esquery "^1.4.0"
+    eslint-scope "^5.0.0"
+    eslint-utils "^1.4.3"
+    eslint-visitor-keys "^1.1.0"
+    espree "^6.1.2"
+    esquery "^1.0.1"
     esutils "^2.0.2"
-    fast-deep-equal "^3.1.3"
-    file-entry-cache "^6.0.1"
+    file-entry-cache "^5.0.1"
     functional-red-black-tree "^1.0.1"
-    glob-parent "^5.1.2"
-    globals "^13.6.0"
+    glob-parent "^5.0.0"
+    globals "^12.1.0"
     ignore "^4.0.6"
     import-fresh "^3.0.0"
     imurmurhash "^0.1.4"
+    inquirer "^7.0.0"
     is-glob "^4.0.0"
     js-yaml "^3.13.1"
     json-stable-stringify-without-jsonify "^1.0.1"
-    levn "^0.4.1"
-    lodash.merge "^4.6.2"
+    levn "^0.3.0"
+    lodash "^4.17.14"
     minimatch "^3.0.4"
+    mkdirp "^0.5.1"
     natural-compare "^1.4.0"
-    optionator "^0.9.1"
+    optionator "^0.8.3"
     progress "^2.0.0"
-    regexpp "^3.1.0"
-    semver "^7.2.1"
-    strip-ansi "^6.0.0"
-    strip-json-comments "^3.1.0"
-    table "^6.0.9"
+    regexpp "^2.0.1"
+    semver "^6.1.2"
+    strip-ansi "^5.2.0"
+    strip-json-comments "^3.0.1"
+    table "^5.2.3"
     text-table "^0.2.0"
     v8-compile-cache "^2.0.3"
 
-espree@^7.3.0, espree@^7.3.1:
-  version "7.3.1"
-  resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6"
-  integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==
+espree@^6.1.2:
+  version "6.2.1"
+  resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a"
+  integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==
   dependencies:
-    acorn "^7.4.0"
-    acorn-jsx "^5.3.1"
-    eslint-visitor-keys "^1.3.0"
+    acorn "^7.1.1"
+    acorn-jsx "^5.2.0"
+    eslint-visitor-keys "^1.1.0"
 
 esprima@^4.0.0:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
   integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
 
-esquery@^1.4.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5"
-  integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==
+esquery@^1.0.1:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57"
+  integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==
   dependencies:
     estraverse "^5.1.0"
 
-esrecurse@^4.3.0:
-  version "4.3.0"
-  resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
-  integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
+esrecurse@^4.1.0:
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf"
+  integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==
   dependencies:
-    estraverse "^5.2.0"
+    estraverse "^4.1.0"
 
-estraverse@^4.1.1:
+estraverse@^4.1.0, estraverse@^4.1.1:
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
   integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
 
-estraverse@^5.1.0, estraverse@^5.2.0:
-  version "5.2.0"
-  resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880"
-  integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==
+estraverse@^5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.1.0.tgz#374309d39fd935ae500e7b92e8a6b4c720e59642"
+  integrity sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==
 
 estree-walker@^0.5.2:
   version "0.5.2"
@@ -1872,20 +1588,15 @@
   resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700"
   integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==
 
-estree-walker@^2.0.1:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
-  integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
-
 esutils@^2.0.2:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
   integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
 
 eventemitter3@^4.0.0:
-  version "4.0.7"
-  resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
-  integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
+  version "4.0.4"
+  resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384"
+  integrity sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==
 
 evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
   version "1.0.3"
@@ -1895,28 +1606,21 @@
     md5.js "^1.3.4"
     safe-buffer "^5.1.1"
 
-execa@^5.0.0:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
-  integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==
+execa@^4.0.0:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/execa/-/execa-4.0.2.tgz#ad87fb7b2d9d564f70d2b62d511bee41d5cbb240"
+  integrity sha512-QI2zLa6CjGWdiQsmSkZoGtDx2N+cQIGb3yNolGTdjSQzydzLgYYf8LRuagp7S7fPimjcrzUDSUFd/MgzELMi4Q==
   dependencies:
-    cross-spawn "^7.0.3"
-    get-stream "^6.0.0"
-    human-signals "^2.1.0"
+    cross-spawn "^7.0.0"
+    get-stream "^5.0.0"
+    human-signals "^1.1.1"
     is-stream "^2.0.0"
     merge-stream "^2.0.0"
-    npm-run-path "^4.0.1"
-    onetime "^5.1.2"
-    signal-exit "^3.0.3"
+    npm-run-path "^4.0.0"
+    onetime "^5.1.0"
+    signal-exit "^3.0.2"
     strip-final-newline "^2.0.0"
 
-executioner@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/executioner/-/executioner-2.0.1.tgz#add328e03bc45dd598f358fbb529fc0be0ec6fcd"
-  integrity sha1-rdMo4DvEXdWY81j7tSn8C+Dsb80=
-  dependencies:
-    mixly "^1.0.0"
-
 extend@^3.0.0:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
@@ -1931,7 +1635,7 @@
     iconv-lite "^0.4.24"
     tmp "^0.0.33"
 
-fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
+fast-deep-equal@^3.1.1:
   version "3.1.3"
   resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
   integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
@@ -1941,34 +1645,16 @@
   resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
   integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
 
-fast-glob@^3.1.1:
-  version "3.2.7"
-  resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1"
-  integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==
-  dependencies:
-    "@nodelib/fs.stat" "^2.0.2"
-    "@nodelib/fs.walk" "^1.2.3"
-    glob-parent "^5.1.2"
-    merge2 "^1.3.0"
-    micromatch "^4.0.4"
-
 fast-json-stable-stringify@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
   integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
 
-fast-levenshtein@^2.0.6:
+fast-levenshtein@~2.0.6:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
   integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
 
-fastq@^1.6.0:
-  version "1.13.0"
-  resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c"
-  integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==
-  dependencies:
-    reusify "^1.0.4"
-
 figures@^3.0.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
@@ -1976,12 +1662,12 @@
   dependencies:
     escape-string-regexp "^1.0.5"
 
-file-entry-cache@^6.0.1:
-  version "6.0.1"
-  resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
-  integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==
+file-entry-cache@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c"
+  integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==
   dependencies:
-    flat-cache "^3.0.4"
+    flat-cache "^2.0.1"
 
 fill-range@^7.0.1:
   version "7.0.1"
@@ -2003,7 +1689,7 @@
     statuses "~1.5.0"
     unpipe "~1.0.0"
 
-find-up@^4.1.0:
+find-up@^4.0.0, find-up@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
   integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
@@ -2011,36 +1697,24 @@
     locate-path "^5.0.0"
     path-exists "^4.0.0"
 
-find-up@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
-  integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
+flat-cache@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0"
+  integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==
   dependencies:
-    locate-path "^6.0.0"
-    path-exists "^4.0.0"
+    flatted "^2.0.0"
+    rimraf "2.6.3"
+    write "1.0.3"
 
-flat-cache@^3.0.4:
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11"
-  integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==
-  dependencies:
-    flatted "^3.1.0"
-    rimraf "^3.0.2"
-
-flatted@^2.0.1:
+flatted@^2.0.0:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138"
   integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==
 
-flatted@^3.1.0:
-  version "3.2.2"
-  resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.2.tgz#64bfed5cb68fe3ca78b3eb214ad97b63bedce561"
-  integrity sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==
-
 follow-redirects@^1.0.0:
-  version "1.14.4"
-  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379"
-  integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==
+  version "1.12.0"
+  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.12.0.tgz#ff0ccf85cf2c867c481957683b5f91b75b25e240"
+  integrity sha512-JgawlbfBQKjbKegPn8vUsvJqplE7KHJuhGO4yPcb+ZOIYKSr+xobMVlfRBToZwZUUxy7lFiKBdFNloz9ui368Q==
 
 foreach@~2.0.1:
   version "2.0.5"
@@ -2055,36 +1729,24 @@
     cross-spawn "^7.0.0"
     signal-exit "^3.0.2"
 
-fs-extra@^8.1.0:
-  version "8.1.0"
-  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
-  integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
+fs-extra@^7.0.1:
+  version "7.0.1"
+  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
+  integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==
   dependencies:
-    graceful-fs "^4.2.0"
+    graceful-fs "^4.1.2"
     jsonfile "^4.0.0"
     universalify "^0.1.0"
 
-fs-minipass@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
-  integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==
-  dependencies:
-    minipass "^3.0.0"
-
 fs.realpath@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
   integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
 
-fsevents@~2.3.2:
-  version "2.3.2"
-  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
-  integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
-
-fulcon@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/fulcon/-/fulcon-1.0.2.tgz#8a4dfda4c73fcd9cc62a79d5045c392b45547320"
-  integrity sha1-ik39pMc/zZzGKnnVBFw5K0VUcyA=
+fsevents@~2.1.2:
+  version "2.1.3"
+  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e"
+  integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==
 
 function-bind@^1.1.1:
   version "1.1.1"
@@ -2111,34 +1773,15 @@
   dependencies:
     readable-stream "~1.0.26-4"
 
-gauge@^3.0.0:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.1.tgz#4bea07bcde3782f06dced8950e51307aa0f4a346"
-  integrity sha512-6STz6KdQgxO4S/ko+AbjlFGGdGcknluoqU+79GOFCDqqyYj5OanQf9AjxwN0jCidtT+ziPMmPSt9E4hfQ0CwIQ==
-  dependencies:
-    aproba "^1.0.3 || ^2.0.0"
-    color-support "^1.1.2"
-    console-control-strings "^1.0.0"
-    has-unicode "^2.0.1"
-    object-assign "^4.1.1"
-    signal-exit "^3.0.0"
-    string-width "^1.0.1 || ^2.0.0"
-    strip-ansi "^3.0.1 || ^4.0.0"
-    wide-align "^1.1.2"
-
-get-caller-file@^2.0.5:
+get-caller-file@^2.0.1:
   version "2.0.5"
   resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
   integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
 
-get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6"
-  integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==
-  dependencies:
-    function-bind "^1.1.1"
-    has "^1.0.3"
-    has-symbols "^1.0.1"
+get-stdin@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b"
+  integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==
 
 get-stream@^4.1.0:
   version "4.1.0"
@@ -2147,37 +1790,24 @@
   dependencies:
     pump "^3.0.0"
 
-get-stream@^5.1.0:
-  version "5.2.0"
-  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3"
-  integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==
+get-stream@^5.0.0, get-stream@^5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9"
+  integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==
   dependencies:
     pump "^3.0.0"
 
-get-stream@^6.0.0:
-  version "6.0.1"
-  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
-  integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
-
-get-symbol-description@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6"
-  integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==
-  dependencies:
-    call-bind "^1.0.2"
-    get-intrinsic "^1.1.1"
-
-glob-parent@^5.1.2, glob-parent@~5.1.2:
-  version "5.1.2"
-  resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
-  integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
+glob-parent@^5.0.0, glob-parent@~5.1.0:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
+  integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==
   dependencies:
     is-glob "^4.0.1"
 
-glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.1.7:
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
-  integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
+glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
+  version "7.1.6"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
+  integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
   dependencies:
     fs.realpath "^1.0.0"
     inflight "^1.0.4"
@@ -2186,36 +1816,19 @@
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
-global-dirs@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.0.tgz#70a76fe84ea315ab37b1f5576cbde7d48ef72686"
-  integrity sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==
+global-dirs@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.0.1.tgz#acdf3bb6685bcd55cb35e8a052266569e9469201"
+  integrity sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A==
   dependencies:
-    ini "2.0.0"
+    ini "^1.3.5"
 
-globals@^13.6.0, globals@^13.9.0:
-  version "13.11.0"
-  resolved "https://registry.yarnpkg.com/globals/-/globals-13.11.0.tgz#40ef678da117fe7bd2e28f1fab24951bd0255be7"
-  integrity sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==
+globals@^12.1.0:
+  version "12.4.0"
+  resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8"
+  integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==
   dependencies:
-    type-fest "^0.20.2"
-
-globby@^11.0.3:
-  version "11.0.4"
-  resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5"
-  integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==
-  dependencies:
-    array-union "^2.1.0"
-    dir-glob "^3.0.1"
-    fast-glob "^3.1.1"
-    ignore "^5.1.4"
-    merge2 "^1.3.0"
-    slash "^3.0.0"
-
-google-protobuf@^3.15.5, google-protobuf@^3.17.3, google-protobuf@^3.6.1:
-  version "3.19.0"
-  resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.19.0.tgz#97f474323c92f19fd6737af1bb792e396991e0b8"
-  integrity sha512-qXGAiv3OOlaJXJNeKOBKxbBAwjsxzhx+12ZdKOkZTsqsRkyiQRmr/nBkAkqnuQ8cmA9X5NVXvObQTpHVnXE2DQ==
+    type-fest "^0.8.1"
 
 got@^9.6.0:
   version "9.6.0"
@@ -2234,43 +1847,30 @@
     to-readable-stream "^1.0.0"
     url-parse-lax "^3.0.0"
 
-graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.6:
-  version "4.2.8"
-  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
-  integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
+graceful-fs@^4.1.2, graceful-fs@^4.1.6:
+  version "4.2.4"
+  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
+  integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
 
-grpc-tools@^1.11.2:
-  version "1.11.2"
-  resolved "https://registry.yarnpkg.com/grpc-tools/-/grpc-tools-1.11.2.tgz#22d802d40012510ccc6591d11f9c94109ac07aab"
-  integrity sha512-4+EgpnnkJraamY++oyBCw5Hp9huRYfgakjNVKbiE3PgO9Tv5ydVlRo7ZyGJ0C0SEiA7HhbVc1sNNtIyK7FiEtg==
+gts@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/gts/-/gts-2.0.2.tgz#b8b28de99361b5c5c24db30a375a0f546bbc04a4"
+  integrity sha512-SLytzl2IqKXf6kGULwr07XQ9lVsvjrzFD3OAA7DEfIQYuD+lKBPt/cZ/RYGxaWerY4PTfmnXT7KdxEr9Ec8uHQ==
   dependencies:
-    "@mapbox/node-pre-gyp" "^1.0.5"
-
-grpc-web@^1.2.1:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/grpc-web/-/grpc-web-1.3.0.tgz#4c36d97e7a7b6102a7df463e7822cd86d4f65ed8"
-  integrity sha512-nePImtnrnkZLErFq00Sr1H6AqaRrRptOJEhjUnlTB6RiJgs8ULYvRI9cX2hDwMvyYgakmO3H/wThYvS+Ibdreg==
-
-gts@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/gts/-/gts-3.1.0.tgz#b27ce914191ed6ad34781968d0c77e0ed3042388"
-  integrity sha512-Pbj3ob1VR1IRlEVEBNtKoQ1wHOa8cZz62KEojK8Fn/qeS2ClWI4gLNfhek3lD68aZSmUEg8TFb6AHXIwUMgyqQ==
-  dependencies:
-    "@typescript-eslint/eslint-plugin" "^4.2.0"
-    "@typescript-eslint/parser" "^4.2.0"
-    chalk "^4.1.0"
-    eslint "^7.10.0"
-    eslint-config-prettier "^7.0.0"
+    "@typescript-eslint/eslint-plugin" "2.31.0"
+    "@typescript-eslint/parser" "2.31.0"
+    chalk "^4.0.0"
+    eslint "^6.8.0"
+    eslint-config-prettier "^6.10.1"
     eslint-plugin-node "^11.1.0"
-    eslint-plugin-prettier "^3.1.4"
-    execa "^5.0.0"
-    inquirer "^7.3.3"
-    json5 "^2.1.3"
-    meow "^9.0.0"
+    eslint-plugin-prettier "^3.1.2"
+    execa "^4.0.0"
+    inquirer "^7.1.0"
+    meow "^7.0.0"
     ncp "^2.0.0"
-    prettier "^2.1.2"
+    prettier "^2.0.4"
     rimraf "^3.0.2"
-    update-notifier "^5.0.0"
+    update-notifier "^4.1.0"
     write-file-atomic "^3.0.3"
 
 hard-rejection@^2.1.0:
@@ -2278,10 +1878,17 @@
   resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883"
   integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==
 
-has-bigints@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113"
-  integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==
+has-binary2@~1.0.2:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d"
+  integrity sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==
+  dependencies:
+    isarray "2.0.1"
+
+has-cors@1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39"
+  integrity sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=
 
 has-flag@^3.0.0:
   version "3.0.0"
@@ -2293,22 +1900,10 @@
   resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
   integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
 
-has-symbols@^1.0.1, has-symbols@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
-  integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==
-
-has-tostringtag@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25"
-  integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==
-  dependencies:
-    has-symbols "^1.0.2"
-
-has-unicode@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
-  integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=
+has-symbols@^1.0.0, has-symbols@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
+  integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==
 
 has-yarn@^2.1.0:
   version "2.1.0"
@@ -2339,12 +1934,7 @@
     inherits "^2.0.3"
     minimalistic-assert "^1.0.1"
 
-he@^1.1.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
-  integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
-
-hmac-drbg@^1.0.1:
+hmac-drbg@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
   integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=
@@ -2361,50 +1951,15 @@
     react-is "^16.7.0"
 
 hosted-git-info@^2.1.4:
-  version "2.8.9"
-  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
-  integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
-
-hosted-git-info@^4.0.1:
-  version "4.0.2"
-  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.0.2.tgz#5e425507eede4fea846b7262f0838456c4209961"
-  integrity sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg==
-  dependencies:
-    lru-cache "^6.0.0"
-
-html-dom-parser@1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/html-dom-parser/-/html-dom-parser-1.0.2.tgz#bb5ff844f214657d899aa4fb7b0a9e7d15607e96"
-  integrity sha512-Jq4oVkVSn+10ut3fyc2P/Fs1jqTo0l45cP6Q8d2ef/9jfkYwulO0QXmyLI0VUiZrXF4czpGgMEJRa52CQ6Fk8Q==
-  dependencies:
-    domhandler "4.2.2"
-    htmlparser2 "6.1.0"
+  version "2.8.8"
+  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
+  integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
 
 html-escaper@^2.0.0:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
   integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
 
-html-react-parser@^1.4.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/html-react-parser/-/html-react-parser-1.4.0.tgz#bf264f38b9fdf4d94e2120f6a39586c15cb81bd0"
-  integrity sha512-v8Kxy+7L90ZFSM690oJWBNRzZWZOQquYPpQt6kDQPzQyZptXgOJ69kHSi7xdqNdm1mOfsDPwF4K9Bo/dS5gRTQ==
-  dependencies:
-    domhandler "4.2.2"
-    html-dom-parser "1.0.2"
-    react-property "2.0.0"
-    style-to-js "1.1.0"
-
-htmlparser2@6.1.0:
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7"
-  integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==
-  dependencies:
-    domelementtype "^2.0.1"
-    domhandler "^4.0.0"
-    domutils "^2.5.2"
-    entities "^2.0.0"
-
 http-cache-semantics@^4.0.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
@@ -2421,7 +1976,7 @@
     statuses ">= 1.5.0 < 2"
     toidentifier "1.0.0"
 
-http-proxy@^1.18.0, http-proxy@^1.18.1:
+http-proxy@^1.13.0:
   version "1.18.1"
   resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549"
   integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==
@@ -2430,41 +1985,15 @@
     follow-redirects "^1.0.0"
     requires-port "^1.0.0"
 
-http-server@^13.0.2:
-  version "13.0.2"
-  resolved "https://registry.yarnpkg.com/http-server/-/http-server-13.0.2.tgz#36f8a8ae0e1b78e7bf30a4dfb01ae89b904904ef"
-  integrity sha512-R8kvPT7qp11AMJWLZsRShvm6heIXdlR/+tL5oAWNG/86A/X+IAFX6q0F9SA2G+dR5aH/759+9PLH0V34Q6j4rg==
-  dependencies:
-    basic-auth "^1.0.3"
-    colors "^1.4.0"
-    corser "^2.0.1"
-    he "^1.1.0"
-    http-proxy "^1.18.0"
-    mime "^1.6.0"
-    minimist "^1.2.5"
-    opener "^1.5.1"
-    portfinder "^1.0.25"
-    secure-compare "3.0.1"
-    union "~0.5.0"
-    url-join "^2.0.5"
-
-https-proxy-agent@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
-  integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==
-  dependencies:
-    agent-base "6"
-    debug "4"
-
-human-signals@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
-  integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
+human-signals@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
+  integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==
 
 hyphenate-style-name@^1.0.3:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d"
-  integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz#097bb7fa0b8f1a9cf0bd5c734cf95899981a9b48"
+  integrity sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ==
 
 iconv-lite@0.4.24, iconv-lite@^0.4.24:
   version "0.4.24"
@@ -2478,25 +2007,20 @@
   resolved "https://registry.yarnpkg.com/idb-wrapper/-/idb-wrapper-1.7.2.tgz#8251afd5e77fe95568b1c16152eb44b396767ea2"
   integrity sha512-zfNREywMuf0NzDo9mVsL0yegjsirJxHpKHvWcyRozIqQy89g0a3U+oBPOCN4cc0oCiOuYgZHimzaW/R46G1Mpg==
 
-ieee754@^1.1.13, ieee754@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
-  integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
-
 ignore@^4.0.6:
   version "4.0.6"
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
   integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
 
-ignore@^5.1.1, ignore@^5.1.4, ignore@^5.1.8:
+ignore@^5.1.1:
   version "5.1.8"
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57"
   integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==
 
-import-fresh@^3.0.0, import-fresh@^3.2.1:
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
-  integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
+import-fresh@^3.0.0:
+  version "3.2.1"
+  resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66"
+  integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==
   dependencies:
     parent-module "^1.0.0"
     resolve-from "^4.0.0"
@@ -2516,7 +2040,7 @@
   resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
   integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
 
-indexof@~0.0.1:
+indexof@0.0.1, indexof@~0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
   integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=
@@ -2539,68 +2063,44 @@
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
   integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
 
-ini@2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5"
-  integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==
+ini@^1.3.5, ini@~1.3.0:
+  version "1.3.5"
+  resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
+  integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
 
-ini@~1.3.0:
-  version "1.3.8"
-  resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
-  integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
-
-inline-style-parser@0.1.1:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1"
-  integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==
-
-inquirer@^7.3.3:
-  version "7.3.3"
-  resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003"
-  integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==
+inquirer@^7.0.0, inquirer@^7.1.0:
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.2.0.tgz#63ce99d823090de7eb420e4bb05e6f3449aa389a"
+  integrity sha512-E0c4rPwr9ByePfNlTIB8z51kK1s2n6jrHuJeEHENl/sbq2G/S1auvibgEwNR4uSyiU+PiYHqSwsgGiXjG8p5ZQ==
   dependencies:
     ansi-escapes "^4.2.1"
-    chalk "^4.1.0"
+    chalk "^3.0.0"
     cli-cursor "^3.1.0"
-    cli-width "^3.0.0"
+    cli-width "^2.0.0"
     external-editor "^3.0.3"
     figures "^3.0.0"
-    lodash "^4.17.19"
+    lodash "^4.17.15"
     mute-stream "0.0.8"
     run-async "^2.4.0"
-    rxjs "^6.6.0"
+    rxjs "^6.5.3"
     string-width "^4.1.0"
     strip-ansi "^6.0.0"
     through "^2.3.6"
 
-install-peers@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/install-peers/-/install-peers-1.0.3.tgz#6348f8f67e6bc23c19ee78adb819c43f8d1dd7d7"
-  integrity sha512-MAlSHlrn4p+g3fhx8ZVxQZXX+MkeinKLu/ThfAmrVnN5c2L8Vof7myb0UsgowJEiGcFNHYnTvo37r3uap5asYA==
+internal-slot@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.2.tgz#9c2e9fb3cd8e5e4256c6f45fe310067fcfa378a3"
+  integrity sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g==
   dependencies:
-    executioner "^2.0.1"
-
-internal-slot@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c"
-  integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==
-  dependencies:
-    get-intrinsic "^1.1.0"
+    es-abstract "^1.17.0-next.1"
     has "^1.0.3"
-    side-channel "^1.0.4"
+    side-channel "^1.0.2"
 
 is-arrayish@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
   integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
 
-is-bigint@^1.0.1:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3"
-  integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==
-  dependencies:
-    has-bigints "^1.0.1"
-
 is-binary-path@~2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
@@ -2608,18 +2108,10 @@
   dependencies:
     binary-extensions "^2.0.0"
 
-is-boolean-object@^1.1.0:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719"
-  integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==
-  dependencies:
-    call-bind "^1.0.2"
-    has-tostringtag "^1.0.0"
-
-is-callable@^1.1.4, is-callable@^1.2.4:
-  version "1.2.4"
-  resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945"
-  integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==
+is-callable@^1.1.4, is-callable@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb"
+  integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==
 
 is-ci@^2.0.0:
   version "2.0.0"
@@ -2628,24 +2120,15 @@
   dependencies:
     ci-info "^2.0.0"
 
-is-core-module@^2.2.0, is-core-module@^2.5.0:
-  version "2.8.0"
-  resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548"
-  integrity sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==
-  dependencies:
-    has "^1.0.3"
-
 is-date-object@^1.0.1:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f"
-  integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==
-  dependencies:
-    has-tostringtag "^1.0.0"
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e"
+  integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==
 
 is-docker@^2.0.0:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
-  integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.0.0.tgz#2cb0df0e75e2d064fe1864c37cdeacb7b2dcf25b"
+  integrity sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==
 
 is-extglob@^2.1.1:
   version "2.1.1"
@@ -2663,9 +2146,9 @@
   integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
 
 is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1:
-  version "4.0.3"
-  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
-  integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
+  integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
   dependencies:
     is-extglob "^2.1.1"
 
@@ -2674,35 +2157,23 @@
   resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835"
   integrity sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=
 
-is-installed-globally@^0.4.0:
-  version "0.4.0"
-  resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520"
-  integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==
+is-installed-globally@^0.3.1:
+  version "0.3.2"
+  resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141"
+  integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==
   dependencies:
-    global-dirs "^3.0.0"
-    is-path-inside "^3.0.2"
+    global-dirs "^2.0.1"
+    is-path-inside "^3.0.1"
 
 is-module@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
   integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=
 
-is-negative-zero@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24"
-  integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==
-
-is-npm@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-5.0.0.tgz#43e8d65cc56e1b67f8d47262cf667099193f45a8"
-  integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==
-
-is-number-object@^1.0.4:
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0"
-  integrity sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==
-  dependencies:
-    has-tostringtag "^1.0.0"
+is-npm@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d"
+  integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==
 
 is-number@^7.0.0:
   version "7.0.0"
@@ -2719,73 +2190,58 @@
   resolved "https://registry.yarnpkg.com/is-object/-/is-object-0.1.2.tgz#00efbc08816c33cfc4ac8251d132e10dc65098d7"
   integrity sha1-AO+8CIFsM8/ErIJR0TLhDcZQmNc=
 
-is-path-inside@^3.0.2:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
-  integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
+is-path-inside@^3.0.1:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017"
+  integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==
 
 is-plain-obj@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
   integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4=
 
-is-reference@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7"
-  integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==
+is-reference@^1.1.2:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.0.tgz#d938b0cf85a0df09849417b274f02fb509293599"
+  integrity sha512-ZVxq+5TkOx6GQdnoMm2aRdCKADdcrOWXLGzGT+vIA8DMpqEJaRk5AL1bS80zJ2bjHunVmjdzfCt0e4BymIEqKQ==
   dependencies:
-    "@types/estree" "*"
+    "@types/estree" "0.0.44"
 
-is-regex@^1.1.4:
-  version "1.1.4"
-  resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
-  integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==
+is-regex@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.0.tgz#ece38e389e490df0dc21caea2bd596f987f767ff"
+  integrity sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==
   dependencies:
-    call-bind "^1.0.2"
-    has-tostringtag "^1.0.0"
-
-is-shared-array-buffer@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz#97b0c85fbdacb59c9c446fe653b82cf2b5b7cfe6"
-  integrity sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==
+    has-symbols "^1.0.1"
 
 is-stream@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
-  integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3"
+  integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==
 
-is-string@^1.0.5, is-string@^1.0.7:
-  version "1.0.7"
-  resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd"
-  integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==
-  dependencies:
-    has-tostringtag "^1.0.0"
+is-string@^1.0.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6"
+  integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==
 
-is-symbol@^1.0.2, is-symbol@^1.0.3:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c"
-  integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==
+is-symbol@^1.0.2:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937"
+  integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==
   dependencies:
-    has-symbols "^1.0.2"
+    has-symbols "^1.0.1"
 
 is-typedarray@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
   integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
 
-is-weakref@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.1.tgz#842dba4ec17fa9ac9850df2d6efbc1737274f2a2"
-  integrity sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ==
-  dependencies:
-    call-bind "^1.0.0"
-
 is-windows@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
   integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
 
-is-wsl@^2.2.0:
+is-wsl@^2.1.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
   integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
@@ -2807,15 +2263,20 @@
   resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
   integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
 
+isarray@2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e"
+  integrity sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=
+
 isarray@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
   integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
 
-isbinaryfile@^4.0.8:
-  version "4.0.8"
-  resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.8.tgz#5d34b94865bd4946633ecc78a026fc76c5b11fcf"
-  integrity sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w==
+isbinaryfile@^4.0.2:
+  version "4.0.6"
+  resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.6.tgz#edcb62b224e2b4710830b67498c8e4e5a4d2610b"
+  integrity sha512-ORrEy+SNVqUhrCaal4hA4fBzhggQQ+BaLntyPOdoEiwlKZW9BZiJXjg3RMiruE4tPEI3pyVPpySHQF/dKWperg==
 
 isbuffer@~0.0.0:
   version "0.0.0"
@@ -2828,9 +2289,9 @@
   integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
 
 istanbul-lib-coverage@^3.0.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3"
-  integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec"
+  integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==
 
 istanbul-lib-report@^3.0.0:
   version "3.0.0"
@@ -2842,33 +2303,33 @@
     supports-color "^7.1.0"
 
 istanbul-reports@^3.0.2:
-  version "3.0.5"
-  resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.5.tgz#a2580107e71279ea6d661ddede929ffc6d693384"
-  integrity sha512-5+19PlhnGabNWB7kOFnuxT8H3T/iIyQzIbQMxXsURmmvKg86P2sbkrGOT77VnHw0Qr0gc2XzRaRfMZYYbSQCJQ==
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.2.tgz#d593210e5000683750cb09fc0644e4b6e27fd53b"
+  integrity sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==
   dependencies:
     html-escaper "^2.0.0"
     istanbul-lib-report "^3.0.0"
 
-jasmine-core@^3.6.0, jasmine-core@^3.8.0, jasmine-core@~3.10.0:
-  version "3.10.1"
-  resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.10.1.tgz#7aa6fa2b834a522315c651a128d940eca553989a"
-  integrity sha512-ooZWSDVAdh79Rrj4/nnfklL3NQVra0BcuhcuWoAwwi+znLDoUeH87AFfeX8s+YeYi6xlv5nveRyaA1v7CintfA==
+jasmine-core@^3.5.0, jasmine-core@~3.5.0:
+  version "3.5.0"
+  resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.5.0.tgz#132c23e645af96d85c8bca13c8758b18429fc1e4"
+  integrity sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA==
 
-jasmine-reporters@~2.4.0:
-  version "2.4.0"
-  resolved "https://registry.yarnpkg.com/jasmine-reporters/-/jasmine-reporters-2.4.0.tgz#708c17ae70ba6671e3a930bb1b202aab80a31409"
-  integrity sha512-jxONSrBLN1vz/8zCx5YNWQSS8iyDAlXQ5yk1LuqITe4C6iXCDx5u6Q0jfNtkKhL4qLZPe69fL+AWvXFt9/x38w==
+jasmine-reporters@~2.3.2:
+  version "2.3.2"
+  resolved "https://registry.yarnpkg.com/jasmine-reporters/-/jasmine-reporters-2.3.2.tgz#898818ffc234eb8b3f635d693de4586f95548d43"
+  integrity sha512-u/7AT9SkuZsUfFBLLzbErohTGNsEUCKaQbsVYnLFW1gEuL2DzmBL4n8v90uZsqIqlWvWUgian8J6yOt5Fyk/+A==
   dependencies:
     mkdirp "^0.5.1"
-    xmldom "^0.5.0"
+    xmldom "^0.1.22"
 
-jasmine@^3.8.0:
-  version "3.10.0"
-  resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-3.10.0.tgz#acd3cd560a9d20d8fdad6bd2dd05867d188503f3"
-  integrity sha512-2Y42VsC+3CQCTzTwJezOvji4qLORmKIE0kwowWC+934Krn6ZXNQYljiwK5st9V3PVx96BSiDYXSB60VVah3IlQ==
+jasmine@^3.5.0:
+  version "3.5.0"
+  resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-3.5.0.tgz#7101eabfd043a1fc82ac24e0ab6ec56081357f9e"
+  integrity sha512-DYypSryORqzsGoMazemIHUfMkXM7I7easFaxAvNM3Mr6Xz3Fy36TupTrAOxZWN8MVKEU5xECv22J4tUQf3uBzQ==
   dependencies:
-    glob "^7.1.6"
-    jasmine-core "~3.10.0"
+    glob "^7.1.4"
+    jasmine-core "~3.5.0"
 
 "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
   version "4.0.0"
@@ -2876,9 +2337,9 @@
   integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
 
 js-yaml@^3.13.1:
-  version "3.14.1"
-  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
-  integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
+  version "3.14.0"
+  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482"
+  integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==
   dependencies:
     argparse "^1.0.7"
     esprima "^4.0.0"
@@ -2888,33 +2349,21 @@
   resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
   integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=
 
-json-parse-even-better-errors@^2.3.0:
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
-  integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
+json-parse-better-errors@^1.0.1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
+  integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
 
 json-schema-traverse@^0.4.1:
   version "0.4.1"
   resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
   integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
 
-json-schema-traverse@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
-  integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
-
 json-stable-stringify-without-jsonify@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
   integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
 
-json5@^2.1.3:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3"
-  integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
-  dependencies:
-    minimist "^1.2.5"
-
 jsonfile@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
@@ -2922,83 +2371,83 @@
   optionalDependencies:
     graceful-fs "^4.1.6"
 
-jss-plugin-camel-case@^10.5.1:
-  version "10.8.1"
-  resolved "https://registry.yarnpkg.com/jss-plugin-camel-case/-/jss-plugin-camel-case-10.8.1.tgz#342fd406c2e8781ecdc4ac298a78b892f75581ab"
-  integrity sha512-nOYKsvX9qh/AcUWSSRZHKyUj4RwqnhUSq4EKNFA1nHsNw0VJYwtF1yqtOPvztWEP3LTlNhcwoPINsb/eKVmYqA==
+jss-plugin-camel-case@^10.0.3:
+  version "10.3.0"
+  resolved "https://registry.yarnpkg.com/jss-plugin-camel-case/-/jss-plugin-camel-case-10.3.0.tgz#ae4da53b39a6e3ea94b70a20fc41c11f0b87386a"
+  integrity sha512-tadWRi/SLWqLK3EUZEdDNJL71F3ST93Zrl9JYMjV0QDqKPAl0Liue81q7m/nFUpnSTXczbKDy4wq8rI8o7WFqA==
   dependencies:
     "@babel/runtime" "^7.3.1"
     hyphenate-style-name "^1.0.3"
-    jss "10.8.1"
+    jss "^10.3.0"
 
-jss-plugin-default-unit@^10.5.1:
-  version "10.8.1"
-  resolved "https://registry.yarnpkg.com/jss-plugin-default-unit/-/jss-plugin-default-unit-10.8.1.tgz#1c35b89cd70ca5b0e01c21d89d908e75c1ea80ad"
-  integrity sha512-W/uwVJNrFtUrVyAPfH/3ZngFYUVilMxgNbuWHYslqv3c5gnBKM6iXeoDzOnB+wtQJoSCTLzD3q77H7OeNK3oxg==
+jss-plugin-default-unit@^10.0.3:
+  version "10.3.0"
+  resolved "https://registry.yarnpkg.com/jss-plugin-default-unit/-/jss-plugin-default-unit-10.3.0.tgz#cd74cf5088542620a82591f76c62c6b43a7e50a6"
+  integrity sha512-tT5KkIXAsZOSS9WDSe8m8lEHIjoEOj4Pr0WrG0WZZsMXZ1mVLFCSsD2jdWarQWDaRNyMj/I4d7czRRObhOxSuw==
   dependencies:
     "@babel/runtime" "^7.3.1"
-    jss "10.8.1"
+    jss "^10.3.0"
 
-jss-plugin-global@^10.5.1:
-  version "10.8.1"
-  resolved "https://registry.yarnpkg.com/jss-plugin-global/-/jss-plugin-global-10.8.1.tgz#992a14210c567178eb4cd385edcd267ae8cc6f28"
-  integrity sha512-ERYLzD+L/v3yQL2mM5/PE+3xU/GCXcfXuGIL1kVkiEIpXnWtND/Mphf2iHQaqedx59uhiVHFZaMtv6qv+iNsDw==
+jss-plugin-global@^10.0.3:
+  version "10.3.0"
+  resolved "https://registry.yarnpkg.com/jss-plugin-global/-/jss-plugin-global-10.3.0.tgz#6b883e74900bb71f65ac2b19bea78f7d1e85af3f"
+  integrity sha512-etYTG/y3qIR/vxZnKY+J3wXwObyBDNhBiB3l/EW9/pE3WHE//BZdK8LFvQcrCO48sZW1Z6paHo6klxUPP7WbzA==
   dependencies:
     "@babel/runtime" "^7.3.1"
-    jss "10.8.1"
+    jss "^10.3.0"
 
-jss-plugin-nested@^10.5.1:
-  version "10.8.1"
-  resolved "https://registry.yarnpkg.com/jss-plugin-nested/-/jss-plugin-nested-10.8.1.tgz#ac9750f8185725a0fd6ea484860767c53ec3d3dc"
-  integrity sha512-Z15G23Fb8/br23EclH9CAq2UGdi29XgpSWXFTBusMJbWjitFdDCdYMzk7bSUJ6P7L5+WpaIDNxIJ9WrdMRqdXw==
+jss-plugin-nested@^10.0.3:
+  version "10.3.0"
+  resolved "https://registry.yarnpkg.com/jss-plugin-nested/-/jss-plugin-nested-10.3.0.tgz#ae8aceac95e09c3d40c991ea32403fb647d9e0a8"
+  integrity sha512-qWiEkoXNEkkZ+FZrWmUGpf+zBsnEOmKXhkjNX85/ZfWhH9dfGxUCKuJFuOWFM+rjQfxV4csfesq4hY0jk8Qt0w==
   dependencies:
     "@babel/runtime" "^7.3.1"
-    jss "10.8.1"
+    jss "^10.3.0"
     tiny-warning "^1.0.2"
 
-jss-plugin-props-sort@^10.5.1:
-  version "10.8.1"
-  resolved "https://registry.yarnpkg.com/jss-plugin-props-sort/-/jss-plugin-props-sort-10.8.1.tgz#ee07bebf8ebeab01f8d9369973c64891cca53af9"
-  integrity sha512-BNbKYuh4IawWr7cticlnbI+kBx01o39DNHkjAkc2CGKWVboUb2EpktDqonqVN/BjyzDgZXKOmwz36ZFkLQB45g==
+jss-plugin-props-sort@^10.0.3:
+  version "10.3.0"
+  resolved "https://registry.yarnpkg.com/jss-plugin-props-sort/-/jss-plugin-props-sort-10.3.0.tgz#5b0625f87b6431a7969c56b0d8c696525969bfe4"
+  integrity sha512-boetORqL/lfd7BWeFD3K+IyPqyIC+l3CRrdZr+NPq7Noqp+xyg/0MR7QisgzpxCEulk+j2CRcEUoZsvgPC4nTg==
   dependencies:
     "@babel/runtime" "^7.3.1"
-    jss "10.8.1"
+    jss "^10.3.0"
 
-jss-plugin-rule-value-function@^10.5.1:
-  version "10.8.1"
-  resolved "https://registry.yarnpkg.com/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.8.1.tgz#40b19406f3cc027d9001de9026750e726c322993"
-  integrity sha512-XrvM4bokyU1xPXr+gVEIlT9WylLQZcdC+1JDxriXDEWmKEjJgtH+w6ZicchTydLqq1qtA4fEevhdMvm4QvgIKw==
+jss-plugin-rule-value-function@^10.0.3:
+  version "10.3.0"
+  resolved "https://registry.yarnpkg.com/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.3.0.tgz#498b0e2bae16cb316a6bdb73fd783cf9604ba747"
+  integrity sha512-7WiMrKIHH3rwxTuJki9+7nY11r1UXqaUZRhHvqTD4/ZE+SVhvtD5Tx21ivNxotwUSleucA/8boX+NF21oXzr5Q==
   dependencies:
     "@babel/runtime" "^7.3.1"
-    jss "10.8.1"
+    jss "^10.3.0"
     tiny-warning "^1.0.2"
 
-jss-plugin-vendor-prefixer@^10.5.1:
-  version "10.8.1"
-  resolved "https://registry.yarnpkg.com/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.8.1.tgz#362146c2b641aae1d29f279307aec0e2167c7ee2"
-  integrity sha512-77b/iEFmA669s+USru2Y5eg9Hs1C1N0zE/4EaJm/fqKScCTNawHXZv5l5w6j81A9CNa63Ar7jekAIfBkoKFmLw==
+jss-plugin-vendor-prefixer@^10.0.3:
+  version "10.3.0"
+  resolved "https://registry.yarnpkg.com/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.3.0.tgz#b09c13a4d05a055429d8a24e19cc01ce049f0ed4"
+  integrity sha512-sZQbrcZyP5V0ADjCLwUA1spVWoaZvM7XZ+2fSeieZFBj31cRsnV7X70FFDerMHeiHAXKWzYek+67nMDjhrZAVQ==
   dependencies:
     "@babel/runtime" "^7.3.1"
     css-vendor "^2.0.8"
-    jss "10.8.1"
+    jss "^10.3.0"
 
-jss@10.8.1, jss@^10.5.1:
-  version "10.8.1"
-  resolved "https://registry.yarnpkg.com/jss/-/jss-10.8.1.tgz#375797c259ffce417e56ae1a7fe703acde8de9ee"
-  integrity sha512-P4wKxU+2m5ReGl0Mmbf9XYgVjFIVZJOZ9ylXBxdpanX+HHgj5XVaAIgYzYpKbBLPCdkAUsI/Iq1fhQPsMNu0YA==
+jss@^10.0.3, jss@^10.3.0:
+  version "10.3.0"
+  resolved "https://registry.yarnpkg.com/jss/-/jss-10.3.0.tgz#2cf7be265f72b59c1764d816fdabff1c5dd18326"
+  integrity sha512-B5sTRW9B6uHaUVzSo9YiMEOEp3UX8lWevU0Fsv+xtRnsShmgCfIYX44bTH8bPJe6LQKqEXku3ulKuHLbxBS97Q==
   dependencies:
     "@babel/runtime" "^7.3.1"
-    csstype "^3.0.2"
+    csstype "^2.6.5"
     is-in-browser "^1.1.3"
     tiny-warning "^1.0.2"
 
-"jsx-ast-utils@^2.4.1 || ^3.0.0":
-  version "3.2.1"
-  resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz#720b97bfe7d901b927d87c3773637ae8ea48781b"
-  integrity sha512-uP5vu8xfy2F9A6LGC22KO7e2/vGTS1MhP+18f++ZNlf0Ohaxbc9nIEwHAsejlJKyzfZzU5UIhe5ItYkitcZnZA==
+jsx-ast-utils@^2.2.3:
+  version "2.4.1"
+  resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz#1114a4c1209481db06c690c2b4f488cc665f657e"
+  integrity sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w==
   dependencies:
-    array-includes "^3.1.3"
-    object.assign "^4.1.2"
+    array-includes "^3.1.1"
+    object.assign "^4.1.0"
 
 karma-chrome-launcher@^3.1.0:
   version "3.1.0"
@@ -3007,69 +2456,61 @@
   dependencies:
     which "^1.2.1"
 
-karma-firefox-launcher@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/karma-firefox-launcher/-/karma-firefox-launcher-2.1.1.tgz#6457226f8e4f091b664cef79bb5d39bf1e008765"
-  integrity sha512-VzDMgPseXak9DtfyE1O5bB2BwsMy1zzO1kUxVW1rP0yhC4tDNJ0p3JoFdzvrK4QqVzdqUMa9Rx9YzkdFp8hz3Q==
+karma-firefox-launcher@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/karma-firefox-launcher/-/karma-firefox-launcher-1.3.0.tgz#ebcbb1d1ddfada6be900eb8fae25bcf2dcdc8171"
+  integrity sha512-Fi7xPhwrRgr+94BnHX0F5dCl1miIW4RHnzjIGxF8GaIEp7rNqX7LSi7ok63VXs3PS/5MQaQMhGxw+bvD+pibBQ==
   dependencies:
-    is-wsl "^2.2.0"
-    which "^2.0.1"
+    is-wsl "^2.1.0"
 
-karma-jasmine@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/karma-jasmine/-/karma-jasmine-4.0.1.tgz#b99e073b6d99a5196fc4bffc121b89313b0abd82"
-  integrity sha512-h8XDAhTiZjJKzfkoO1laMH+zfNlra+dEQHUAjpn5JV1zCPtOIVWGQjLBrqhnzQa/hrU2XrZwSyBa6XjEBzfXzw==
+karma-jasmine@^3.3.1:
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/karma-jasmine/-/karma-jasmine-3.3.1.tgz#c01b1a2ec973e1531c1f6535e1d7d66b8e4275c2"
+  integrity sha512-Nxh7eX9mOQMyK0VSsMxdod+bcqrR/ikrmEiWj5M6fwuQ7oI+YEF1FckaDsWfs6TIpULm9f0fTKMjF7XcrvWyqQ==
   dependencies:
-    jasmine-core "^3.6.0"
-
-karma-junit-reporter@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/karma-junit-reporter/-/karma-junit-reporter-2.0.1.tgz#d34eef7f0b2fd064e0896954e8851a90cf14c8f3"
-  integrity sha512-VtcGfE0JE4OE1wn0LK8xxDKaTP7slN8DO3I+4xg6gAi1IoAHAXOJ1V9G/y45Xg6sxdxPOR3THCFtDlAfBo9Afw==
-  dependencies:
-    path-is-absolute "^1.0.0"
-    xmlbuilder "12.0.0"
+    jasmine-core "^3.5.0"
 
 karma-requirejs@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/karma-requirejs/-/karma-requirejs-1.1.0.tgz#fddae2cb87d7ebc16fb0222893564d7fee578798"
   integrity sha1-/driy4fX68FvsCIok1ZNf+5Xh5g=
 
-karma-sourcemap-loader@^0.3.8:
-  version "0.3.8"
-  resolved "https://registry.yarnpkg.com/karma-sourcemap-loader/-/karma-sourcemap-loader-0.3.8.tgz#d4bae72fb7a8397328a62b75013d2df937bdcf9c"
-  integrity sha512-zorxyAakYZuBcHRJE+vbrK2o2JXLFWK8VVjiT/6P+ltLBUGUvqTEkUiQ119MGdOrK7mrmxXHZF1/pfT6GgIZ6g==
+karma-sourcemap-loader@^0.3.7:
+  version "0.3.7"
+  resolved "https://registry.yarnpkg.com/karma-sourcemap-loader/-/karma-sourcemap-loader-0.3.7.tgz#91322c77f8f13d46fed062b042e1009d4c4505d8"
+  integrity sha1-kTIsd/jxPUb+0GKwQuEAnUxFBdg=
   dependencies:
     graceful-fs "^4.1.2"
 
-karma@6.3.4:
-  version "6.3.4"
-  resolved "https://registry.yarnpkg.com/karma/-/karma-6.3.4.tgz#359899d3aab3d6b918ea0f57046fd2a6b68565e6"
-  integrity sha512-hbhRogUYIulfkBTZT7xoPrCYhRBnBoqbbL4fszWD0ReFGUxU+LYBr3dwKdAluaDQ/ynT9/7C+Lf7pPNW4gSx4Q==
+karma@5.0.2:
+  version "5.0.2"
+  resolved "https://registry.yarnpkg.com/karma/-/karma-5.0.2.tgz#e404373dac6e3fa08409ae4d9eda7d83adb43ee5"
+  integrity sha512-RpUuCuGJfN3WnjYPGIH+VBF8023Lfm3TQH6D1kcNL+FxtEPc2UUz/nVjbVAGXH4Pm+Q7FVOAQjdAeFUpXpQ3IA==
   dependencies:
-    body-parser "^1.19.0"
+    body-parser "^1.16.1"
     braces "^3.0.2"
-    chokidar "^3.5.1"
-    colors "^1.4.0"
-    connect "^3.7.0"
+    chokidar "^3.0.0"
+    colors "^1.1.0"
+    connect "^3.6.0"
     di "^0.0.1"
-    dom-serialize "^2.2.1"
-    glob "^7.1.7"
-    graceful-fs "^4.2.6"
-    http-proxy "^1.18.1"
-    isbinaryfile "^4.0.8"
-    lodash "^4.17.21"
-    log4js "^6.3.0"
-    mime "^2.5.2"
-    minimatch "^3.0.4"
-    qjobs "^1.2.0"
-    range-parser "^1.2.1"
-    rimraf "^3.0.2"
-    socket.io "^3.1.0"
+    dom-serialize "^2.2.0"
+    flatted "^2.0.0"
+    glob "^7.1.1"
+    graceful-fs "^4.1.2"
+    http-proxy "^1.13.0"
+    isbinaryfile "^4.0.2"
+    lodash "^4.17.14"
+    log4js "^4.0.0"
+    mime "^2.3.1"
+    minimatch "^3.0.2"
+    qjobs "^1.1.4"
+    range-parser "^1.2.0"
+    rimraf "^2.6.0"
+    socket.io "2.1.1"
     source-map "^0.6.1"
-    tmp "^0.2.1"
-    ua-parser-js "^0.7.28"
-    yargs "^16.1.1"
+    tmp "0.0.33"
+    ua-parser-js "0.7.21"
+    yargs "^15.3.1"
 
 keyv@^3.0.0:
   version "3.1.0"
@@ -3083,7 +2524,7 @@
   resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
   integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
 
-latest-version@^5.1.0:
+latest-version@^5.0.0:
   version "5.1.0"
   resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face"
   integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==
@@ -3175,13 +2616,13 @@
     semver "~2.3.1"
     xtend "~3.0.0"
 
-levn@^0.4.1:
-  version "0.4.1"
-  resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
-  integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==
+levn@^0.3.0, levn@~0.3.0:
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
+  integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=
   dependencies:
-    prelude-ls "^1.2.1"
-    type-check "~0.4.0"
+    prelude-ls "~1.1.2"
+    type-check "~0.3.2"
 
 lines-and-columns@^1.1.6:
   version "1.1.6"
@@ -3195,48 +2636,21 @@
   dependencies:
     p-locate "^4.1.0"
 
-locate-path@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
-  integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
+lodash@^4.17.14, lodash@^4.17.15:
+  version "4.17.15"
+  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
+  integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
+
+log4js@^4.0.0:
+  version "4.5.1"
+  resolved "https://registry.yarnpkg.com/log4js/-/log4js-4.5.1.tgz#e543625e97d9e6f3e6e7c9fc196dd6ab2cae30b5"
+  integrity sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw==
   dependencies:
-    p-locate "^5.0.0"
-
-lodash.camelcase@^4.3.0:
-  version "4.3.0"
-  resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
-  integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY=
-
-lodash.clonedeep@^4.5.0:
-  version "4.5.0"
-  resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
-  integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
-
-lodash.merge@^4.6.2:
-  version "4.6.2"
-  resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
-  integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
-
-lodash.truncate@^4.4.2:
-  version "4.4.2"
-  resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
-  integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=
-
-lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.21:
-  version "4.17.21"
-  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
-  integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
-
-log4js@^6.3.0:
-  version "6.3.0"
-  resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.3.0.tgz#10dfafbb434351a3e30277a00b9879446f715bcb"
-  integrity sha512-Mc8jNuSFImQUIateBFwdOQcmC6Q5maU0VVvdC2R6XMb66/VnT+7WS4D/0EeNMZu1YODmJe5NIn2XftCzEocUgw==
-  dependencies:
-    date-format "^3.0.0"
+    date-format "^2.0.0"
     debug "^4.1.1"
-    flatted "^2.0.1"
+    flatted "^2.0.0"
     rfdc "^1.1.4"
-    streamroller "^2.2.4"
+    streamroller "^1.0.6"
 
 long@^4.0.0:
   version "4.0.0"
@@ -3260,13 +2674,6 @@
   resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479"
   integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==
 
-lru-cache@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
-  integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
-  dependencies:
-    yallist "^4.0.0"
-
 ltgt@^2.1.2:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/ltgt/-/ltgt-2.2.1.tgz#f35ca91c493f7b73da0e07495304f17b31f87ee5"
@@ -3279,14 +2686,14 @@
   dependencies:
     vlq "^0.2.2"
 
-magic-string@^0.25.7:
+magic-string@^0.25.2:
   version "0.25.7"
   resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051"
   integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==
   dependencies:
     sourcemap-codec "^1.4.4"
 
-make-dir@^3.0.0, make-dir@^3.1.0:
+make-dir@^3.0.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
   integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
@@ -3299,9 +2706,9 @@
   integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=
 
 map-obj@^4.0.0:
-  version "4.3.0"
-  resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a"
-  integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.1.0.tgz#b91221b542734b9f14256c0132c897c5d7256fd5"
+  integrity sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==
 
 md5.js@^1.3.4:
   version "1.3.5"
@@ -3317,42 +2724,30 @@
   resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
   integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
 
-meow@^9.0.0:
-  version "9.0.0"
-  resolved "https://registry.yarnpkg.com/meow/-/meow-9.0.0.tgz#cd9510bc5cac9dee7d03c73ee1f9ad959f4ea364"
-  integrity sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==
+meow@^7.0.0:
+  version "7.0.1"
+  resolved "https://registry.yarnpkg.com/meow/-/meow-7.0.1.tgz#1ed4a0a50b3844b451369c48362eb0515f04c1dc"
+  integrity sha512-tBKIQqVrAHqwit0vfuFPY3LlzJYkEOFyKa3bPgxzNl6q/RtN8KQ+ALYEASYuFayzSAsjlhXj/JZ10rH85Q6TUw==
   dependencies:
     "@types/minimist" "^1.2.0"
+    arrify "^2.0.1"
+    camelcase "^6.0.0"
     camelcase-keys "^6.2.2"
-    decamelize "^1.2.0"
     decamelize-keys "^1.1.0"
     hard-rejection "^2.1.0"
-    minimist-options "4.1.0"
-    normalize-package-data "^3.0.0"
+    minimist-options "^4.0.2"
+    normalize-package-data "^2.5.0"
     read-pkg-up "^7.0.1"
     redent "^3.0.0"
     trim-newlines "^3.0.0"
-    type-fest "^0.18.0"
-    yargs-parser "^20.2.3"
+    type-fest "^0.13.1"
+    yargs-parser "^18.1.3"
 
 merge-stream@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
   integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
 
-merge2@^1.3.0:
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
-  integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
-
-micromatch@^4.0.4:
-  version "4.0.4"
-  resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9"
-  integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==
-  dependencies:
-    braces "^3.0.1"
-    picomatch "^2.2.3"
-
 miller-rabin@^4.0.0:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
@@ -3361,27 +2756,22 @@
     bn.js "^4.0.0"
     brorand "^1.0.1"
 
-mime-db@1.50.0:
-  version "1.50.0"
-  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.50.0.tgz#abd4ac94e98d3c0e185016c67ab45d5fde40c11f"
-  integrity sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==
+mime-db@1.44.0:
+  version "1.44.0"
+  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92"
+  integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==
 
 mime-types@~2.1.24:
-  version "2.1.33"
-  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.33.tgz#1fa12a904472fafd068e48d9e8401f74d3f70edb"
-  integrity sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==
+  version "2.1.27"
+  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f"
+  integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==
   dependencies:
-    mime-db "1.50.0"
+    mime-db "1.44.0"
 
-mime@^1.6.0:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
-  integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
-
-mime@^2.5.2:
-  version "2.5.2"
-  resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe"
-  integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==
+mime@^2.3.1:
+  version "2.4.6"
+  resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1"
+  integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==
 
 mimic-fn@^2.1.0:
   version "2.1.0"
@@ -3403,19 +2793,19 @@
   resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
   integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
 
-minimalistic-crypto-utils@^1.0.1:
+minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
   integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
 
-minimatch@^3.0.4:
+minimatch@^3.0.2, minimatch@^3.0.4:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
   integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
   dependencies:
     brace-expansion "^1.1.7"
 
-minimist-options@4.1.0:
+minimist-options@^4.0.2:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619"
   integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==
@@ -3429,55 +2819,23 @@
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
   integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
 
-minipass@^3.0.0:
-  version "3.1.5"
-  resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.5.tgz#71f6251b0a33a49c01b3cf97ff77eda030dff732"
-  integrity sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw==
-  dependencies:
-    yallist "^4.0.0"
-
-minizlib@^2.1.1:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
-  integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
-  dependencies:
-    minipass "^3.0.0"
-    yallist "^4.0.0"
-
-mixly@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/mixly/-/mixly-1.0.0.tgz#9b5a2e1f63e6dfba0d30e6797ffae62ab1dc24ef"
-  integrity sha1-m1ouH2Pm37oNMOZ5f/rmKrHcJO8=
-  dependencies:
-    fulcon "^1.0.1"
-
-mkdirp@^0.5.1, mkdirp@^0.5.5:
+mkdirp@^0.5.1:
   version "0.5.5"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
   integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
   dependencies:
     minimist "^1.2.5"
 
-mkdirp@^1.0.3:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
-  integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
-
 ms@2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
   integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
 
-ms@2.1.2:
+ms@^2.1.1:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
   integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
 
-ms@^2.1.1:
-  version "2.1.3"
-  resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
-  integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
-
 mute-stream@0.0.8:
   version "0.0.8"
   resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
@@ -3498,19 +2856,10 @@
   resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
   integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
 
-node-fetch@^2.6.5:
-  version "2.6.5"
-  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.5.tgz#42735537d7f080a7e5f78b6c549b7146be1742fd"
-  integrity sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==
-  dependencies:
-    whatwg-url "^5.0.0"
-
-nopt@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88"
-  integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==
-  dependencies:
-    abbrev "1"
+nice-try@^1.0.4:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
+  integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
 
 normalize-package-data@^2.5.0:
   version "2.5.0"
@@ -3522,54 +2871,39 @@
     semver "2 || 3 || 4 || 5"
     validate-npm-package-license "^3.0.1"
 
-normalize-package-data@^3.0.0:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e"
-  integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==
-  dependencies:
-    hosted-git-info "^4.0.1"
-    is-core-module "^2.5.0"
-    semver "^7.3.4"
-    validate-npm-package-license "^3.0.1"
-
 normalize-path@^3.0.0, normalize-path@~3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
   integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
 
 normalize-url@^4.1.0:
-  version "4.5.1"
-  resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a"
-  integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==
+  version "4.5.0"
+  resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129"
+  integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==
 
-npm-run-path@^4.0.1:
+npm-run-path@^4.0.0:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
   integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==
   dependencies:
     path-key "^3.0.0"
 
-npmlog@^5.0.1:
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0"
-  integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==
-  dependencies:
-    are-we-there-yet "^2.0.0"
-    console-control-strings "^1.1.0"
-    gauge "^3.0.0"
-    set-blocking "^2.0.0"
-
-object-assign@^4, object-assign@^4.1.1:
+object-assign@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
   integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
 
-object-inspect@^1.11.0, object-inspect@^1.9.0:
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1"
-  integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==
+object-component@0.0.3:
+  version "0.0.3"
+  resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291"
+  integrity sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=
 
-object-keys@^1.0.12, object-keys@^1.1.1:
+object-inspect@^1.7.0:
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67"
+  integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==
+
+object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
   integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
@@ -3588,50 +2922,44 @@
   resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.4.0.tgz#28a6aae7428dd2c3a92f3d95f21335dd204e0336"
   integrity sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=
 
-object.assign@^4.1.2:
-  version "4.1.2"
-  resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940"
-  integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==
+object.assign@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da"
+  integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==
   dependencies:
-    call-bind "^1.0.0"
-    define-properties "^1.1.3"
-    has-symbols "^1.0.1"
-    object-keys "^1.1.1"
+    define-properties "^1.1.2"
+    function-bind "^1.1.1"
+    has-symbols "^1.0.0"
+    object-keys "^1.0.11"
 
-object.entries@^1.1.4:
-  version "1.1.5"
-  resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.5.tgz#e1acdd17c4de2cd96d5a08487cfb9db84d881861"
-  integrity sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==
+object.entries@^1.1.1:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.2.tgz#bc73f00acb6b6bb16c203434b10f9a7e797d3add"
+  integrity sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA==
   dependencies:
-    call-bind "^1.0.2"
     define-properties "^1.1.3"
-    es-abstract "^1.19.1"
+    es-abstract "^1.17.5"
+    has "^1.0.3"
 
-object.fromentries@^2.0.4:
-  version "2.0.5"
-  resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.5.tgz#7b37b205109c21e741e605727fe8b0ad5fa08251"
-  integrity sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==
+object.fromentries@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.2.tgz#4a09c9b9bb3843dd0f89acdb517a794d4f355ac9"
+  integrity sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==
   dependencies:
-    call-bind "^1.0.2"
     define-properties "^1.1.3"
-    es-abstract "^1.19.1"
+    es-abstract "^1.17.0-next.1"
+    function-bind "^1.1.1"
+    has "^1.0.3"
 
-object.hasown@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.0.tgz#7232ed266f34d197d15cac5880232f7a4790afe5"
-  integrity sha512-MhjYRfj3GBlhSkDHo6QmvgjRLXQ2zndabdf3nX0yTyZK9rPfxb6uRpAac8HXNLy1GpqWtZ81Qh4v3uOls2sRAg==
+object.values@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e"
+  integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==
   dependencies:
     define-properties "^1.1.3"
-    es-abstract "^1.19.1"
-
-object.values@^1.1.4:
-  version "1.1.5"
-  resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac"
-  integrity sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==
-  dependencies:
-    call-bind "^1.0.2"
-    define-properties "^1.1.3"
-    es-abstract "^1.19.1"
+    es-abstract "^1.17.0-next.1"
+    function-bind "^1.1.1"
+    has "^1.0.3"
 
 octal@^1.0.0:
   version "1.0.0"
@@ -3652,29 +2980,24 @@
   dependencies:
     wrappy "1"
 
-onetime@^5.1.0, onetime@^5.1.2:
-  version "5.1.2"
-  resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
-  integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
+onetime@^5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.0.tgz#fff0f3c91617fe62bb50189636e99ac8a6df7be5"
+  integrity sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==
   dependencies:
     mimic-fn "^2.1.0"
 
-opener@^1.5.1:
-  version "1.5.2"
-  resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598"
-  integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==
-
-optionator@^0.9.1:
-  version "0.9.1"
-  resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
-  integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==
+optionator@^0.8.3:
+  version "0.8.3"
+  resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"
+  integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==
   dependencies:
-    deep-is "^0.1.3"
-    fast-levenshtein "^2.0.6"
-    levn "^0.4.1"
-    prelude-ls "^1.2.1"
-    type-check "^0.4.0"
-    word-wrap "^1.2.3"
+    deep-is "~0.1.3"
+    fast-levenshtein "~2.0.6"
+    levn "~0.3.0"
+    prelude-ls "~1.1.2"
+    type-check "~0.3.2"
+    word-wrap "~1.2.3"
 
 os-tmpdir@~1.0.2:
   version "1.0.2"
@@ -3693,13 +3016,6 @@
   dependencies:
     p-try "^2.0.0"
 
-p-limit@^3.0.2:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
-  integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
-  dependencies:
-    yocto-queue "^0.1.0"
-
 p-locate@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
@@ -3707,13 +3023,6 @@
   dependencies:
     p-limit "^2.2.0"
 
-p-locate@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
-  integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
-  dependencies:
-    p-limit "^3.0.2"
-
 p-try@^2.0.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
@@ -3737,26 +3046,41 @@
     callsites "^3.0.0"
 
 parse-asn1@^5.0.0, parse-asn1@^5.1.5:
-  version "5.1.6"
-  resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4"
-  integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==
+  version "5.1.5"
+  resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e"
+  integrity sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==
   dependencies:
-    asn1.js "^5.2.0"
+    asn1.js "^4.0.0"
     browserify-aes "^1.0.0"
+    create-hash "^1.1.0"
     evp_bytestokey "^1.0.0"
     pbkdf2 "^3.0.3"
     safe-buffer "^5.1.1"
 
 parse-json@^5.0.0:
-  version "5.2.0"
-  resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
-  integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.0.0.tgz#73e5114c986d143efa3712d4ea24db9a4266f60f"
+  integrity sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==
   dependencies:
     "@babel/code-frame" "^7.0.0"
     error-ex "^1.3.1"
-    json-parse-even-better-errors "^2.3.0"
+    json-parse-better-errors "^1.0.1"
     lines-and-columns "^1.1.6"
 
+parseqs@0.0.5:
+  version "0.0.5"
+  resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d"
+  integrity sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=
+  dependencies:
+    better-assert "~1.0.0"
+
+parseuri@0.0.5:
+  version "0.0.5"
+  resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a"
+  integrity sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=
+  dependencies:
+    better-assert "~1.0.0"
+
 parseurl@~1.3.3:
   version "1.3.3"
   resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
@@ -3772,25 +3096,25 @@
   resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
   integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
 
+path-key@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
+  integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
+
 path-key@^3.0.0, path-key@^3.1.0:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
   integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
 
 path-parse@^1.0.6:
-  version "1.0.7"
-  resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
-  integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
-
-path-type@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
-  integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
+  integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
 
 pbkdf2@^3.0.3:
-  version "3.1.2"
-  resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075"
-  integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.1.tgz#cb8724b0fada984596856d1a6ebafd3584654b94"
+  integrity sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==
   dependencies:
     create-hash "^1.1.2"
     create-hmac "^1.1.4"
@@ -3798,29 +3122,20 @@
     safe-buffer "^5.0.1"
     sha.js "^2.4.8"
 
-picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
-  integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==
+picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2:
+  version "2.2.2"
+  resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
+  integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
 
 popper.js@1.16.1-lts:
   version "1.16.1-lts"
   resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1-lts.tgz#cf6847b807da3799d80ee3d6d2f90df8a3f50b05"
   integrity sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA==
 
-portfinder@^1.0.25:
-  version "1.0.28"
-  resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778"
-  integrity sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==
-  dependencies:
-    async "^2.6.2"
-    debug "^3.1.1"
-    mkdirp "^0.5.5"
-
-prelude-ls@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
-  integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
+prelude-ls@~1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
+  integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
 
 prepend-http@^2.0.0:
   version "2.0.0"
@@ -3834,10 +3149,10 @@
   dependencies:
     fast-diff "^1.1.2"
 
-prettier@^2.1.2:
-  version "2.4.1"
-  resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.4.1.tgz#671e11c89c14a4cfc876ce564106c4a6726c9f5c"
-  integrity sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==
+prettier@^2.0.4:
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4"
+  integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==
 
 process-es6@^0.11.2, process-es6@^0.11.6:
   version "0.11.6"
@@ -3882,25 +3197,6 @@
     "@types/node" "^10.1.0"
     long "^4.0.0"
 
-protobufjs@^6.10.0:
-  version "6.11.2"
-  resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.2.tgz#de39fabd4ed32beaa08e9bb1e30d08544c1edf8b"
-  integrity sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==
-  dependencies:
-    "@protobufjs/aspromise" "^1.1.2"
-    "@protobufjs/base64" "^1.1.2"
-    "@protobufjs/codegen" "^2.0.4"
-    "@protobufjs/eventemitter" "^1.1.0"
-    "@protobufjs/fetch" "^1.1.0"
-    "@protobufjs/float" "^1.0.2"
-    "@protobufjs/inquire" "^1.1.0"
-    "@protobufjs/path" "^1.1.2"
-    "@protobufjs/pool" "^1.1.0"
-    "@protobufjs/utf8" "^1.1.0"
-    "@types/long" "^4.0.1"
-    "@types/node" ">=13.7.0"
-    long "^4.0.0"
-
 prr@~0.0.0:
   version "0.0.0"
   resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a"
@@ -3936,14 +3232,14 @@
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
   integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
 
-pupa@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62"
-  integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==
+pupa@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.0.1.tgz#dbdc9ff48ffbea4a26a069b6f9f7abb051008726"
+  integrity sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA==
   dependencies:
     escape-goat "^2.0.0"
 
-qjobs@^1.2.0:
+qjobs@^1.1.4:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071"
   integrity sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==
@@ -3953,18 +3249,6 @@
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
   integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
 
-qs@^6.4.0:
-  version "6.10.1"
-  resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a"
-  integrity sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==
-  dependencies:
-    side-channel "^1.0.4"
-
-queue-microtask@^1.2.2:
-  version "1.2.3"
-  resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
-  integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
-
 quick-lru@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
@@ -3985,7 +3269,7 @@
     randombytes "^2.0.5"
     safe-buffer "^5.1.0"
 
-range-parser@^1.2.1:
+range-parser@^1.2.0:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
   integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
@@ -4010,47 +3294,39 @@
     minimist "^1.2.0"
     strip-json-comments "~2.0.1"
 
-react-dom@^17.0.2:
-  version "17.0.2"
-  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
-  integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
+react-dom@^16.13.1:
+  version "16.13.1"
+  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.1.tgz#c1bd37331a0486c078ee54c4740720993b2e0e7f"
+  integrity sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==
   dependencies:
     loose-envify "^1.1.0"
     object-assign "^4.1.1"
-    scheduler "^0.20.2"
+    prop-types "^15.6.2"
+    scheduler "^0.19.1"
 
-react-is@^16.7.0, react-is@^16.8.1:
+react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1:
   version "16.13.1"
   resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
   integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
 
-"react-is@^16.8.0 || ^17.0.0":
-  version "17.0.2"
-  resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
-  integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
-
-react-property@2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/react-property/-/react-property-2.0.0.tgz#2156ba9d85fa4741faf1918b38efc1eae3c6a136"
-  integrity sha512-kzmNjIgU32mO4mmH5+iUyrqlpFQhF8K2k7eZ4fdLSOPFrD1XgEuSBv9LDEgxRXTMBqMd8ppT0x6TIzqE5pdGdw==
-
 react-transition-group@^4.4.0:
-  version "4.4.2"
-  resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.2.tgz#8b59a56f09ced7b55cbd53c36768b922890d5470"
-  integrity sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==
+  version "4.4.1"
+  resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9"
+  integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==
   dependencies:
     "@babel/runtime" "^7.5.5"
     dom-helpers "^5.0.1"
     loose-envify "^1.4.0"
     prop-types "^15.6.2"
 
-react@^17.0.2:
-  version "17.0.2"
-  resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
-  integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
+react@^16.13.1:
+  version "16.13.1"
+  resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
+  integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==
   dependencies:
     loose-envify "^1.1.0"
     object-assign "^4.1.1"
+    prop-types "^15.6.2"
 
 read-pkg-up@^7.0.1:
   version "7.0.1"
@@ -4113,10 +3389,10 @@
     isarray "0.0.1"
     string_decoder "~0.10.x"
 
-readdirp@~3.6.0:
-  version "3.6.0"
-  resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
-  integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
+readdirp@~3.4.0:
+  version "3.4.0"
+  resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada"
+  integrity sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==
   dependencies:
     picomatch "^2.2.1"
 
@@ -4129,27 +3405,32 @@
     strip-indent "^3.0.0"
 
 regenerator-runtime@^0.13.4:
-  version "0.13.9"
-  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
-  integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
+  version "0.13.5"
+  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697"
+  integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==
 
-regexp.prototype.flags@^1.3.1:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26"
-  integrity sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==
+regexp.prototype.flags@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75"
+  integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==
   dependencies:
-    call-bind "^1.0.2"
     define-properties "^1.1.3"
+    es-abstract "^1.17.0-next.1"
 
-regexpp@^3.0.0, regexpp@^3.1.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2"
-  integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==
+regexpp@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f"
+  integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==
+
+regexpp@^3.0.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2"
+  integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==
 
 registry-auth-token@^4.0.0:
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250"
-  integrity sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.1.1.tgz#40a33be1e82539460f94328b0f7f0f84c16d9479"
+  integrity sha512-9bKS7nTl9+/A1s7tnPeGrUpRcVY+LUh7bfFgzpndALdPfXQBfQV77rQVtqgUV3ti4vc/Ik81Ex8UJDWDQ12zQA==
   dependencies:
     rc "^1.2.8"
 
@@ -4165,10 +3446,10 @@
   resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
   integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
 
-require-from-string@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
-  integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
+require-main-filename@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
+  integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
 
 requirejs@^2.3.6:
   version "2.3.6"
@@ -4185,20 +3466,11 @@
   resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
   integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
 
-resolve@^1.10.0, resolve@^1.10.1, resolve@^1.17.0, resolve@^1.19.0:
-  version "1.20.0"
-  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
-  integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
+resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.0, resolve@^1.14.2, resolve@^1.15.1:
+  version "1.17.0"
+  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444"
+  integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==
   dependencies:
-    is-core-module "^2.2.0"
-    path-parse "^1.0.6"
-
-resolve@^2.0.0-next.3:
-  version "2.0.0-next.3"
-  resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.3.tgz#d41016293d4a8586a39ca5d9b5f15cbea1f55e46"
-  integrity sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==
-  dependencies:
-    is-core-module "^2.2.0"
     path-parse "^1.0.6"
 
 responselike@^1.0.2:
@@ -4216,15 +3488,24 @@
     onetime "^5.1.0"
     signal-exit "^3.0.2"
 
-reusify@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
-  integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
-
 rfdc@^1.1.4:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b"
-  integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.4.tgz#ba72cc1367a0ccd9cf81a870b3b58bd3ad07f8c2"
+  integrity sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==
+
+rimraf@2.6.3:
+  version "2.6.3"
+  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
+  integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
+  dependencies:
+    glob "^7.1.3"
+
+rimraf@^2.6.0, rimraf@^2.6.3:
+  version "2.7.1"
+  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
+  integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
+  dependencies:
+    glob "^7.1.3"
 
 rimraf@^3.0.0, rimraf@^3.0.2:
   version "3.0.2"
@@ -4263,10 +3544,10 @@
     process-es6 "^0.11.6"
     rollup-pluginutils "^2.3.1"
 
-rollup-plugin-sourcemaps@^0.6.3:
-  version "0.6.3"
-  resolved "https://registry.yarnpkg.com/rollup-plugin-sourcemaps/-/rollup-plugin-sourcemaps-0.6.3.tgz#bf93913ffe056e414419607f1d02780d7ece84ed"
-  integrity sha512-paFu+nT1xvuO1tPFYXGe+XnQvg4Hjqv/eIhG8i5EspfYYPBKL57X7iVbfv55aNVASg3dzWvES9dmWsL2KhfByw==
+rollup-plugin-sourcemaps@^0.6.2:
+  version "0.6.2"
+  resolved "https://registry.yarnpkg.com/rollup-plugin-sourcemaps/-/rollup-plugin-sourcemaps-0.6.2.tgz#1eed5a3e07b833dc14c4cdb1e63b300d340f4a74"
+  integrity sha512-9AwTKg3yRykwzemfLt71ySe0LvrAci+bpsOL1LaTYFk5BX4HF6X7DQfpHa74ANfSja3hyjiQkXCR8goSOnW//Q==
   dependencies:
     "@rollup/pluginutils" "^3.0.9"
     source-map-resolve "^0.6.0"
@@ -4278,39 +3559,25 @@
   dependencies:
     estree-walker "^0.6.1"
 
-rollup@^2.52.8:
-  version "2.58.0"
-  resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.58.0.tgz#a643983365e7bf7f5b7c62a8331b983b7c4c67fb"
-  integrity sha512-NOXpusKnaRpbS7ZVSzcEXqxcLDOagN6iFS8p45RkoiMqPHDLwJm758UF05KlMoCRbLBTZsPOIa887gZJ1AiXvw==
+rollup@^2.15.0:
+  version "2.16.1"
+  resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.16.1.tgz#97805e88071e2c6727bd0b64904976d14495c873"
+  integrity sha512-UYupMcbFtoWLB6ZtL4hPZNUTlkXjJfGT33Mmhz3hYLNmRj/cOvX2B26ZxDQuEpwtLdcyyyraBGQ7EfzmMJnXXg==
   optionalDependencies:
-    fsevents "~2.3.2"
+    fsevents "~2.1.2"
 
 run-async@^2.4.0:
   version "2.4.1"
   resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455"
   integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==
 
-run-parallel@^1.1.9:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
-  integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
-  dependencies:
-    queue-microtask "^1.2.2"
-
-rxjs@^6.6.0:
-  version "6.6.7"
-  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"
-  integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==
+rxjs@^6.5.3, rxjs@^6.5.5:
+  version "6.5.5"
+  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec"
+  integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==
   dependencies:
     tslib "^1.9.0"
 
-rxjs@^7.2.0:
-  version "7.4.0"
-  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.4.0.tgz#a12a44d7eebf016f5ff2441b87f28c9a51cebc68"
-  integrity sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w==
-  dependencies:
-    tslib "~2.1.0"
-
 safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0:
   version "5.2.1"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
@@ -4321,24 +3588,19 @@
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
   integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
 
-"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.1.0:
+"safer-buffer@>= 2.1.2 < 3":
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
   integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
 
-scheduler@^0.20.2:
-  version "0.20.2"
-  resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91"
-  integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==
+scheduler@^0.19.1:
+  version "0.19.1"
+  resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196"
+  integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==
   dependencies:
     loose-envify "^1.1.0"
     object-assign "^4.1.1"
 
-secure-compare@3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/secure-compare/-/secure-compare-3.0.1.tgz#f1a0329b308b221fae37b9974f3d578d0ca999e3"
-  integrity sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=
-
 semver-diff@^3.1.1:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b"
@@ -4346,7 +3608,7 @@
   dependencies:
     semver "^6.3.0"
 
-"semver@2 || 3 || 4 || 5":
+"semver@2 || 3 || 4 || 5", semver@^5.5.0:
   version "5.7.1"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
   integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
@@ -4356,18 +3618,11 @@
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
   integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
 
-semver@^6.0.0, semver@^6.1.0, semver@^6.2.0, semver@^6.3.0:
+semver@^6.0.0, semver@^6.1.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0:
   version "6.3.0"
   resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
   integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
 
-semver@^7.2.1, semver@^7.3.4, semver@^7.3.5:
-  version "7.3.5"
-  resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
-  integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
-  dependencies:
-    lru-cache "^6.0.0"
-
 semver@~2.3.1:
   version "2.3.2"
   resolved "https://registry.yarnpkg.com/semver/-/semver-2.3.2.tgz#b9848f25d6cf36333073ec9ef8856d42f1233e52"
@@ -4391,6 +3646,13 @@
     inherits "^2.0.1"
     safe-buffer "^5.0.1"
 
+shebang-command@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
+  integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=
+  dependencies:
+    shebang-regex "^1.0.0"
+
 shebang-command@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
@@ -4398,67 +3660,83 @@
   dependencies:
     shebang-regex "^3.0.0"
 
+shebang-regex@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
+  integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=
+
 shebang-regex@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
   integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
 
-side-channel@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
-  integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
+side-channel@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.2.tgz#df5d1abadb4e4bf4af1cd8852bf132d2f7876947"
+  integrity sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA==
   dependencies:
-    call-bind "^1.0.0"
-    get-intrinsic "^1.0.2"
-    object-inspect "^1.9.0"
+    es-abstract "^1.17.0-next.1"
+    object-inspect "^1.7.0"
 
-signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3:
-  version "3.0.5"
-  resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.5.tgz#9e3e8cc0c75a99472b44321033a7702e7738252f"
-  integrity sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==
+signal-exit@^3.0.2:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
+  integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==
 
-slash@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
-  integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
-
-slice-ansi@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b"
-  integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==
-  dependencies:
-    ansi-styles "^4.0.0"
-    astral-regex "^2.0.0"
-    is-fullwidth-code-point "^3.0.0"
-
-socket.io-adapter@~2.1.0:
+slice-ansi@^2.1.0:
   version "2.1.0"
-  resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.1.0.tgz#edc5dc36602f2985918d631c1399215e97a1b527"
-  integrity sha512-+vDov/aTsLjViYTwS9fPy5pEtTkrbEKsw2M+oVSoFGw6OD1IpvlV1VPhUzNbofCQ8oyMbdYJqDtGdmHQK6TdPg==
-
-socket.io-parser@~4.0.3:
-  version "4.0.4"
-  resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.0.4.tgz#9ea21b0d61508d18196ef04a2c6b9ab630f4c2b0"
-  integrity sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==
+  resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636"
+  integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==
   dependencies:
-    "@types/component-emitter" "^1.2.10"
-    component-emitter "~1.3.0"
-    debug "~4.3.1"
+    ansi-styles "^3.2.0"
+    astral-regex "^1.0.0"
+    is-fullwidth-code-point "^2.0.0"
 
-socket.io@^3.1.0:
-  version "3.1.2"
-  resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-3.1.2.tgz#06e27caa1c4fc9617547acfbb5da9bc1747da39a"
-  integrity sha512-JubKZnTQ4Z8G4IZWtaAZSiRP3I/inpy8c/Bsx2jrwGrTbKeVU5xd6qkKMHpChYeM3dWZSO0QACiGK+obhBNwYw==
+socket.io-adapter@~1.1.0:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz#ab3f0d6f66b8fc7fca3959ab5991f82221789be9"
+  integrity sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==
+
+socket.io-client@2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.1.1.tgz#dcb38103436ab4578ddb026638ae2f21b623671f"
+  integrity sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ==
   dependencies:
-    "@types/cookie" "^0.4.0"
-    "@types/cors" "^2.8.8"
-    "@types/node" ">=10.0.0"
-    accepts "~1.3.4"
-    base64id "~2.0.0"
-    debug "~4.3.1"
-    engine.io "~4.1.0"
-    socket.io-adapter "~2.1.0"
-    socket.io-parser "~4.0.3"
+    backo2 "1.0.2"
+    base64-arraybuffer "0.1.5"
+    component-bind "1.0.0"
+    component-emitter "1.2.1"
+    debug "~3.1.0"
+    engine.io-client "~3.2.0"
+    has-binary2 "~1.0.2"
+    has-cors "1.1.0"
+    indexof "0.0.1"
+    object-component "0.0.3"
+    parseqs "0.0.5"
+    parseuri "0.0.5"
+    socket.io-parser "~3.2.0"
+    to-array "0.1.4"
+
+socket.io-parser@~3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.2.0.tgz#e7c6228b6aa1f814e6148aea325b51aa9499e077"
+  integrity sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==
+  dependencies:
+    component-emitter "1.2.1"
+    debug "~3.1.0"
+    isarray "2.0.1"
+
+socket.io@2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.1.1.tgz#a069c5feabee3e6b214a75b40ce0652e1cfb9980"
+  integrity sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==
+  dependencies:
+    debug "~3.1.0"
+    engine.io "~3.2.0"
+    has-binary2 "~1.0.2"
+    socket.io-adapter "~1.1.0"
+    socket.io-client "2.1.1"
+    socket.io-parser "~3.2.0"
 
 source-map-resolve@^0.6.0:
   version "0.6.0"
@@ -4513,9 +3791,9 @@
     spdx-license-ids "^3.0.0"
 
 spdx-license-ids@^3.0.0:
-  version "3.0.10"
-  resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz#0d9becccde7003d6c658d487dd48a32f0bf3014b"
-  integrity sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==
+  version "3.0.5"
+  resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654"
+  integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==
 
 sprintf-js@~1.0.2:
   version "1.0.3"
@@ -4527,66 +3805,67 @@
   resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
   integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
 
-streamroller@^2.2.4:
-  version "2.2.4"
-  resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-2.2.4.tgz#c198ced42db94086a6193608187ce80a5f2b0e53"
-  integrity sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ==
+streamroller@^1.0.6:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-1.0.6.tgz#8167d8496ed9f19f05ee4b158d9611321b8cacd9"
+  integrity sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg==
   dependencies:
-    date-format "^2.1.0"
-    debug "^4.1.1"
-    fs-extra "^8.1.0"
+    async "^2.6.2"
+    date-format "^2.0.0"
+    debug "^3.2.6"
+    fs-extra "^7.0.1"
+    lodash "^4.17.14"
 
 string-range@~1.2, string-range@~1.2.1:
   version "1.2.2"
   resolved "https://registry.yarnpkg.com/string-range/-/string-range-1.2.2.tgz#a893ed347e72299bc83befbbf2a692a8d239d5dd"
   integrity sha1-qJPtNH5yKZvIO++78qaSqNI51d0=
 
-"string-width@^1.0.1 || ^2.0.0":
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
-  integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
+string-width@^3.0.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
+  integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==
   dependencies:
+    emoji-regex "^7.0.1"
     is-fullwidth-code-point "^2.0.0"
-    strip-ansi "^4.0.0"
+    strip-ansi "^5.1.0"
 
-"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
-  version "4.2.3"
-  resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
-  integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5"
+  integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==
   dependencies:
     emoji-regex "^8.0.0"
     is-fullwidth-code-point "^3.0.0"
-    strip-ansi "^6.0.1"
+    strip-ansi "^6.0.0"
 
-string.prototype.matchall@^4.0.5:
-  version "4.0.6"
-  resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.6.tgz#5abb5dabc94c7b0ea2380f65ba610b3a544b15fa"
-  integrity sha512-6WgDX8HmQqvEd7J+G6VtAahhsQIssiZ8zl7zKh1VDMFyL3hRTJP4FTNA3RbIp2TOQ9AYNDcc7e3fH0Qbup+DBg==
+string.prototype.matchall@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.2.tgz#48bb510326fb9fdeb6a33ceaa81a6ea04ef7648e"
+  integrity sha512-N/jp6O5fMf9os0JU3E72Qhf590RSRZU/ungsL/qJUYVTNv7hTG0P/dbPjxINVN9jpscu3nzYwKESU3P3RY5tOg==
   dependencies:
-    call-bind "^1.0.2"
     define-properties "^1.1.3"
-    es-abstract "^1.19.1"
-    get-intrinsic "^1.1.1"
-    has-symbols "^1.0.2"
-    internal-slot "^1.0.3"
-    regexp.prototype.flags "^1.3.1"
-    side-channel "^1.0.4"
+    es-abstract "^1.17.0"
+    has-symbols "^1.0.1"
+    internal-slot "^1.0.2"
+    regexp.prototype.flags "^1.3.0"
+    side-channel "^1.0.2"
 
-string.prototype.trimend@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80"
-  integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==
+string.prototype.trimend@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913"
+  integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==
   dependencies:
-    call-bind "^1.0.2"
     define-properties "^1.1.3"
+    es-abstract "^1.17.5"
 
-string.prototype.trimstart@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed"
-  integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==
+string.prototype.trimstart@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54"
+  integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==
   dependencies:
-    call-bind "^1.0.2"
     define-properties "^1.1.3"
+    es-abstract "^1.17.5"
 
 string_decoder@^1.1.1:
   version "1.3.0"
@@ -4607,19 +3886,19 @@
   dependencies:
     safe-buffer "~5.1.0"
 
-"strip-ansi@^3.0.1 || ^4.0.0", strip-ansi@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
-  integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8=
+strip-ansi@^5.1.0, strip-ansi@^5.2.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
+  integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
   dependencies:
-    ansi-regex "^3.0.0"
+    ansi-regex "^4.1.0"
 
-strip-ansi@^6.0.0, strip-ansi@^6.0.1:
-  version "6.0.1"
-  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
-  integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+strip-ansi@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532"
+  integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==
   dependencies:
-    ansi-regex "^5.0.1"
+    ansi-regex "^5.0.0"
 
 strip-final-newline@^2.0.0:
   version "2.0.0"
@@ -4633,30 +3912,16 @@
   dependencies:
     min-indent "^1.0.0"
 
-strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
-  integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
+strip-json-comments@^3.0.1:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180"
+  integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==
 
 strip-json-comments@~2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
   integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
 
-style-to-js@1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/style-to-js/-/style-to-js-1.1.0.tgz#631cbb20fce204019b3aa1fcb5b69d951ceac4ac"
-  integrity sha512-1OqefPDxGrlMwcbfpsTVRyzwdhr4W0uxYQzeA2F1CBc8WG04udg2+ybRnvh3XYL4TdHQrCahLtax2jc8xaE6rA==
-  dependencies:
-    style-to-object "0.3.0"
-
-style-to-object@0.3.0:
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.3.0.tgz#b1b790d205991cc783801967214979ee19a76e46"
-  integrity sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==
-  dependencies:
-    inline-style-parser "0.1.1"
-
 supports-color@^5.3.0:
   version "5.5.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
@@ -4665,35 +3930,26 @@
     has-flag "^3.0.0"
 
 supports-color@^7.1.0:
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
-  integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
+  version "7.1.0"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1"
+  integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==
   dependencies:
     has-flag "^4.0.0"
 
-table@^6.0.9:
-  version "6.7.2"
-  resolved "https://registry.yarnpkg.com/table/-/table-6.7.2.tgz#a8d39b9f5966693ca8b0feba270a78722cbaf3b0"
-  integrity sha512-UFZK67uvyNivLeQbVtkiUs8Uuuxv24aSL4/Vil2PJVtMgU8Lx0CYkP12uCGa3kjyQzOSgV1+z9Wkb82fCGsO0g==
+table@^5.2.3:
+  version "5.4.6"
+  resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e"
+  integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==
   dependencies:
-    ajv "^8.0.1"
-    lodash.clonedeep "^4.5.0"
-    lodash.truncate "^4.4.2"
-    slice-ansi "^4.0.0"
-    string-width "^4.2.3"
-    strip-ansi "^6.0.1"
+    ajv "^6.10.2"
+    lodash "^4.17.14"
+    slice-ansi "^2.1.0"
+    string-width "^3.0.0"
 
-tar@^6.1.11:
-  version "6.1.11"
-  resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621"
-  integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==
-  dependencies:
-    chownr "^2.0.0"
-    fs-minipass "^2.0.0"
-    minipass "^3.0.0"
-    minizlib "^2.1.1"
-    mkdirp "^1.0.3"
-    yallist "^4.0.0"
+term-size@^2.1.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.0.tgz#1f16adedfe9bdc18800e1776821734086fcc6753"
+  integrity sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw==
 
 test-exclude@^6.0.0:
   version "6.0.0"
@@ -4719,20 +3975,25 @@
   resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
   integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
 
-tmp@0.2.1, tmp@^0.2.1:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14"
-  integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==
-  dependencies:
-    rimraf "^3.0.0"
-
-tmp@^0.0.33:
+tmp@0.0.33, tmp@^0.0.33:
   version "0.0.33"
   resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
   integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==
   dependencies:
     os-tmpdir "~1.0.2"
 
+tmp@0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.1.0.tgz#ee434a4e22543082e294ba6201dcc6eafefa2877"
+  integrity sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==
+  dependencies:
+    rimraf "^2.6.3"
+
+to-array@0.1.4:
+  version "0.1.4"
+  resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890"
+  integrity sha1-F+bBH3PdTz10zaek/zI46a2b+JA=
+
 to-readable-stream@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771"
@@ -4750,61 +4011,46 @@
   resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
   integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
 
-tr46@~0.0.3:
-  version "0.0.3"
-  resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
-  integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=
-
 trim-newlines@^3.0.0:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144"
-  integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==
-
-ts-protoc-gen@^0.15.0:
-  version "0.15.0"
-  resolved "https://registry.yarnpkg.com/ts-protoc-gen/-/ts-protoc-gen-0.15.0.tgz#2fec5930b46def7dcc9fa73c060d770b7b076b7b"
-  integrity sha512-TycnzEyrdVDlATJ3bWFTtra3SCiEP0W0vySXReAuEygXCUr1j2uaVyL0DhzjwuUdQoW5oXPwk6oZWeA0955V+g==
-  dependencies:
-    google-protobuf "^3.15.5"
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.0.tgz#79726304a6a898aa8373427298d54c2ee8b1cb30"
+  integrity sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==
 
 tslib@^1.8.1, tslib@^1.9.0:
-  version "1.14.1"
-  resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
-  integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
+  version "1.13.0"
+  resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
+  integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==
 
-tslib@~2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
-  integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
-
-tsutils@3.21.0, tsutils@^3.21.0:
-  version "3.21.0"
-  resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
-  integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==
+tsutils@2.27.2:
+  version "2.27.2"
+  resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.27.2.tgz#60ba88a23d6f785ec4b89c6e8179cac9b431f1c7"
+  integrity sha512-qf6rmT84TFMuxAKez2pIfR8UCai49iQsfB7YWVjV1bKpy/d0PWT5rEOSM6La9PiHZ0k1RRZQiwVdVJfQ3BPHgg==
   dependencies:
     tslib "^1.8.1"
 
-type-check@^0.4.0, type-check@~0.4.0:
-  version "0.4.0"
-  resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
-  integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==
+tsutils@^3.17.1:
+  version "3.17.1"
+  resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759"
+  integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==
   dependencies:
-    prelude-ls "^1.2.1"
+    tslib "^1.8.1"
 
-type-fest@^0.18.0:
-  version "0.18.1"
-  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f"
-  integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==
+type-check@~0.3.2:
+  version "0.3.2"
+  resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
+  integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=
+  dependencies:
+    prelude-ls "~1.1.2"
 
-type-fest@^0.20.2:
-  version "0.20.2"
-  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
-  integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
+type-fest@^0.11.0:
+  version "0.11.0"
+  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1"
+  integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==
 
-type-fest@^0.21.3:
-  version "0.21.3"
-  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"
-  integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
+type-fest@^0.13.1:
+  version "0.13.1"
+  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934"
+  integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==
 
 type-fest@^0.6.0:
   version "0.6.0"
@@ -4841,32 +4087,20 @@
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
   integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
 
-typescript@^4.3.5:
-  version "4.4.4"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c"
-  integrity sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==
+typescript@^3.9.3:
+  version "3.9.5"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36"
+  integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==
 
-ua-parser-js@^0.7.28:
-  version "0.7.30"
-  resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.30.tgz#4cf5170e8b55ac553fe8b38df3a82f0669671f0b"
-  integrity sha512-uXEtSresNUlXQ1QL4/3dQORcGv7+J2ookOG2ybA/ga9+HYEXueT2o+8dUJQkpedsyTyCJ6jCCirRcKtdtx1kbg==
+ua-parser-js@0.7.21:
+  version "0.7.21"
+  resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777"
+  integrity sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==
 
-unbox-primitive@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471"
-  integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==
-  dependencies:
-    function-bind "^1.1.1"
-    has-bigints "^1.0.1"
-    has-symbols "^1.0.2"
-    which-boxed-primitive "^1.0.2"
-
-union@~0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/union/-/union-0.5.0.tgz#b2c11be84f60538537b846edb9ba266ba0090075"
-  integrity sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==
-  dependencies:
-    qs "^6.4.0"
+ultron@~1.1.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c"
+  integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==
 
 unique-string@^2.0.0:
   version "2.0.0"
@@ -4885,38 +4119,32 @@
   resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
   integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
 
-update-notifier@^5.0.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-5.1.0.tgz#4ab0d7c7f36a231dd7316cf7729313f0214d9ad9"
-  integrity sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==
+update-notifier@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.0.tgz#4866b98c3bc5b5473c020b1250583628f9a328f3"
+  integrity sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew==
   dependencies:
-    boxen "^5.0.0"
-    chalk "^4.1.0"
+    boxen "^4.2.0"
+    chalk "^3.0.0"
     configstore "^5.0.1"
     has-yarn "^2.1.0"
     import-lazy "^2.1.0"
     is-ci "^2.0.0"
-    is-installed-globally "^0.4.0"
-    is-npm "^5.0.0"
+    is-installed-globally "^0.3.1"
+    is-npm "^4.0.0"
     is-yarn-global "^0.3.0"
-    latest-version "^5.1.0"
-    pupa "^2.1.1"
-    semver "^7.3.4"
+    latest-version "^5.0.0"
+    pupa "^2.0.1"
     semver-diff "^3.1.1"
     xdg-basedir "^4.0.0"
 
 uri-js@^4.2.2:
-  version "4.4.1"
-  resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
-  integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
+  version "4.2.2"
+  resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
+  integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==
   dependencies:
     punycode "^2.1.0"
 
-url-join@^2.0.5:
-  version "2.0.5"
-  resolved "https://registry.yarnpkg.com/url-join/-/url-join-2.0.5.tgz#5af22f18c052a000a48d7b82c5e9c2e2feeda728"
-  integrity sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=
-
 url-parse-lax@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c"
@@ -4935,14 +4163,14 @@
   integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
 
 v8-compile-cache@^2.0.3:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
-  integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745"
+  integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==
 
-v8-to-istanbul@^7.1.0:
-  version "7.1.2"
-  resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz#30898d1a7fa0c84d225a2c1434fb958f290883c1"
-  integrity sha512-TxNb7YEUwkLXCQYeudi6lgQ/SZrzNO4kMdlqVxaZPUIUjCv6iSSypUQX70kNBSERpQ8fk48+d61FXk+tgqcWow==
+v8-to-istanbul@^4.1.2:
+  version "4.1.4"
+  resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-4.1.4.tgz#b97936f21c0e2d9996d4985e5c5156e9d4e49cd6"
+  integrity sha512-Rw6vJHj1mbdK8edjR7+zuJrpDtKIgNdAvTSAcpYfgMIw+u2dPDntD3dgN4XQFLU2/fvFQdzj+EeSGfd/jnY5fQ==
   dependencies:
     "@types/istanbul-lib-coverage" "^2.0.1"
     convert-source-map "^1.6.0"
@@ -4956,11 +4184,6 @@
     spdx-correct "^3.0.0"
     spdx-expression-parse "^3.0.0"
 
-vary@^1:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
-  integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
-
 vlq@^0.2.2:
   version "0.2.3"
   resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26"
@@ -4971,36 +4194,12 @@
   resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
   integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=
 
-wait-queue@^1.1.4:
-  version "1.1.4"
-  resolved "https://registry.yarnpkg.com/wait-queue/-/wait-queue-1.1.4.tgz#344f9bdd6e011ddc0bb1e3252eeb41234f7a8a85"
-  integrity sha512-/VdMghiBDG/Ch43ZRp3d8OSd8A0dx8hfkBO7AfWCDzMn2blHquMf+3gqHHhYcggSBpKf7VTzA939bb0DevYKBA==
+which-module@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
+  integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
 
-webidl-conversions@^3.0.0:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
-  integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=
-
-whatwg-url@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
-  integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0=
-  dependencies:
-    tr46 "~0.0.3"
-    webidl-conversions "^3.0.0"
-
-which-boxed-primitive@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
-  integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==
-  dependencies:
-    is-bigint "^1.0.1"
-    is-boolean-object "^1.1.0"
-    is-number-object "^1.0.4"
-    is-string "^1.0.5"
-    is-symbol "^1.0.3"
-
-which@^1.2.1:
+which@^1.2.1, which@^1.2.9:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
   integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
@@ -5014,13 +4213,6 @@
   dependencies:
     isexe "^2.0.0"
 
-wide-align@^1.1.2:
-  version "1.1.5"
-  resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3"
-  integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==
-  dependencies:
-    string-width "^1.0.2 || 2 || 3 || 4"
-
 widest-line@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca"
@@ -5028,15 +4220,15 @@
   dependencies:
     string-width "^4.0.0"
 
-word-wrap@^1.2.3:
+word-wrap@~1.2.3:
   version "1.2.3"
   resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
   integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
 
-wrap-ansi@^7.0.0:
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
-  integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+wrap-ansi@^6.2.0:
+  version "6.2.0"
+  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
+  integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
   dependencies:
     ansi-styles "^4.0.0"
     string-width "^4.1.0"
@@ -5057,25 +4249,43 @@
     signal-exit "^3.0.2"
     typedarray-to-buffer "^3.1.5"
 
-ws@~7.4.2:
-  version "7.4.6"
-  resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c"
-  integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==
+write@1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3"
+  integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==
+  dependencies:
+    mkdirp "^0.5.1"
+
+ws@~3.3.1:
+  version "3.3.3"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2"
+  integrity sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==
+  dependencies:
+    async-limiter "~1.0.0"
+    safe-buffer "~5.1.0"
+    ultron "~1.1.0"
 
 xdg-basedir@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
   integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==
 
-xmlbuilder@12.0.0:
-  version "12.0.0"
-  resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-12.0.0.tgz#e2ed675e06834a089ddfb84db96e2c2b03f78c1a"
-  integrity sha512-lMo8DJ8u6JRWp0/Y4XLa/atVDr75H9litKlb2E5j3V3MesoL50EBgZDWoLT3F/LztVnG67GjPXLZpqcky/UMnQ==
+xmldom@^0.1.22:
+  version "0.1.31"
+  resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.31.tgz#b76c9a1bd9f0a9737e5a72dc37231cf38375e2ff"
+  integrity sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==
 
-xmldom@^0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.5.0.tgz#193cb96b84aa3486127ea6272c4596354cb4962e"
-  integrity sha512-Foaj5FXVzgn7xFzsKeNIde9g6aFBxTPi37iwsno8QvApmtg7KYrr+OPyRHcJF7dud2a5nGRBXK3n0dL62Gf7PA==
+xmlhttprequest-ssl@~1.5.4:
+  version "1.5.5"
+  resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e"
+  integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=
+
+xregexp@^4.3.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.3.0.tgz#7e92e73d9174a99a59743f67a4ce879a04b5ae50"
+  integrity sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g==
+  dependencies:
+    "@babel/runtime-corejs3" "^7.8.3"
 
 xtend@^2.2.0:
   version "2.2.0"
@@ -5102,35 +4312,54 @@
   resolved "https://registry.yarnpkg.com/xtend/-/xtend-3.0.0.tgz#5cce7407baf642cba7becda568111c493f59665a"
   integrity sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=
 
-y18n@^5.0.5:
-  version "5.0.8"
-  resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
-  integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
-
-yallist@^4.0.0:
+y18n@^4.0.0:
   version "4.0.0"
-  resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
-  integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
+  resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
+  integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
 
-yargs-parser@^20.0.0, yargs-parser@^20.2.2, yargs-parser@^20.2.3:
-  version "20.2.9"
-  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
-  integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
-
-yargs@^16.0.0, yargs@^16.1.1:
-  version "16.2.0"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
-  integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
+yargs-parser@^18.0.0, yargs-parser@^18.1.1, yargs-parser@^18.1.2, yargs-parser@^18.1.3:
+  version "18.1.3"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
+  integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
   dependencies:
-    cliui "^7.0.2"
-    escalade "^3.1.1"
-    get-caller-file "^2.0.5"
-    require-directory "^2.1.1"
-    string-width "^4.2.0"
-    y18n "^5.0.5"
-    yargs-parser "^20.2.2"
+    camelcase "^5.0.0"
+    decamelize "^1.2.0"
 
-yocto-queue@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
-  integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
+yargs@^15.0.0:
+  version "15.4.1"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
+  integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
+  dependencies:
+    cliui "^6.0.0"
+    decamelize "^1.2.0"
+    find-up "^4.1.0"
+    get-caller-file "^2.0.1"
+    require-directory "^2.1.1"
+    require-main-filename "^2.0.0"
+    set-blocking "^2.0.0"
+    string-width "^4.2.0"
+    which-module "^2.0.0"
+    y18n "^4.0.0"
+    yargs-parser "^18.1.2"
+
+yargs@^15.3.1:
+  version "15.3.1"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.1.tgz#9505b472763963e54afe60148ad27a330818e98b"
+  integrity sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==
+  dependencies:
+    cliui "^6.0.0"
+    decamelize "^1.2.0"
+    find-up "^4.1.0"
+    get-caller-file "^2.0.1"
+    require-directory "^2.1.1"
+    require-main-filename "^2.0.0"
+    set-blocking "^2.0.0"
+    string-width "^4.2.0"
+    which-module "^2.0.0"
+    y18n "^4.0.0"
+    yargs-parser "^18.1.1"
+
+yeast@0.1.2:
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"
+  integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk=
diff --git a/zephyr/OWNERS b/zephyr/OWNERS
deleted file mode 100644
index 2230ec2..0000000
--- a/zephyr/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-peress@google.com
diff --git a/zephyr/module.yml b/zephyr/module.yml
deleted file mode 100644
index a9f5744..0000000
--- a/zephyr/module.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright 2021 The Pigweed Authors
-#
-# 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
-#
-#     https://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.
-
-name: pigweed
-build:
-  cmake: .
-  kconfig: Kconfig.zephyr