pw_bytes: Module for bytes manipulation.

Added a ByteBuffer module which contains a collection of utilities for
manipulating binary data.

Change-Id: Ia071c3e68a1ada63232fcb56f3d2d8171bac0778
diff --git a/BUILD.gn b/BUILD.gn
index 1d86400..3fecc7a 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -131,6 +131,7 @@
       "$dir_pigweed/docs",
       "$dir_pw_allocator",
       "$dir_pw_base64",
+      "$dir_pw_bytes",
       "$dir_pw_checksum",
       "$dir_pw_polyfill",
       "$dir_pw_preprocessor",
@@ -163,6 +164,7 @@
       "$dir_pw_allocator:tests",
       "$dir_pw_assert:tests",
       "$dir_pw_base64:tests",
+      "$dir_pw_bytes:tests",
       "$dir_pw_checksum:tests",
       "$dir_pw_containers:tests",
       "$dir_pw_fuzzer:tests",
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e1c6392..a5ff121 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -21,6 +21,7 @@
 add_subdirectory(pw_assert)
 add_subdirectory(pw_assert_basic)
 add_subdirectory(pw_base64)
+add_subdirectory(pw_bytes)
 add_subdirectory(pw_checksum)
 add_subdirectory(pw_containers)
 add_subdirectory(pw_cpu_exception)
diff --git a/docs/BUILD.gn b/docs/BUILD.gn
index c700ecd..9bbfa88 100644
--- a/docs/BUILD.gn
+++ b/docs/BUILD.gn
@@ -65,6 +65,7 @@
     "$dir_pw_bloat:docs",
     "$dir_pw_boot_armv7m:docs",
     "$dir_pw_build:docs",
+    "$dir_pw_bytes:docs",
     "$dir_pw_checksum:docs",
     "$dir_pw_cli:docs",
     "$dir_pw_containers:docs",
diff --git a/modules.gni b/modules.gni
index 338d5fe..eb659b1 100644
--- a/modules.gni
+++ b/modules.gni
@@ -23,6 +23,7 @@
   dir_pw_bloat = get_path_info("pw_bloat", "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_cli = get_path_info("pw_cli", "abspath")
   dir_pw_containers = get_path_info("pw_containers", "abspath")
diff --git a/pw_bytes/BUILD b/pw_bytes/BUILD
new file mode 100644
index 0000000..8bfa841
--- /dev/null
+++ b/pw_bytes/BUILD
@@ -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.
+
+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/byte_builder.h",
+    ],
+    includes = ["public"],
+    deps = [
+        "//pw_preprocessor",
+        "//pw_span",
+        "//pw_status",
+    ],
+)
+
+pw_cc_test(
+    name = "byte_builder_test",
+    srcs = ["byte_builder_test.cc"],
+    deps = [
+        ":pw_bytes",
+        "//pw_unit_test",
+    ],
+)
\ No newline at end of file
diff --git a/pw_bytes/BUILD.gn b/pw_bytes/BUILD.gn
new file mode 100644
index 0000000..b7aa5ab
--- /dev/null
+++ b/pw_bytes/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.
+
+# gn-format disable
+import("//build_overrides/pigweed.gni")
+
+import("$dir_pw_bloat/bloat.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_source_set("pw_bytes") {
+  public_configs = [ ":default_config" ]
+  public = [ "public/pw_bytes/byte_builder.h" ]
+  sources = [ "byte_builder.cc" ]
+  public_deps = [
+    "$dir_pw_preprocessor",
+    "$dir_pw_span",
+    "$dir_pw_status",
+  ]
+}
+
+pw_test_group("tests") {
+  tests = [ ":byte_builder_test" ]
+  group_deps = [
+    "$dir_pw_preprocessor:tests",
+    "$dir_pw_span:tests",
+    "$dir_pw_status:tests",
+  ]
+}
+
+pw_test("byte_builder_test") {
+  deps = [ ":pw_bytes" ]
+  sources = [ "byte_builder_test.cc" ]
+}
+
+pw_doc_group("docs") {
+  sources = [ "docs.rst" ]
+}
diff --git a/pw_bytes/CMakeLists.txt b/pw_bytes/CMakeLists.txt
new file mode 100644
index 0000000..f6706f8
--- /dev/null
+++ b/pw_bytes/CMakeLists.txt
@@ -0,0 +1,20 @@
+# Copyright 2020 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# 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_auto_add_simple_module(pw_bytes
+  PUBLIC_DEPS
+    pw_preprocessor
+    pw_span
+    pw_status
+)
diff --git a/pw_bytes/README.md b/pw_bytes/README.md
new file mode 100644
index 0000000..9a89aca
--- /dev/null
+++ b/pw_bytes/README.md
@@ -0,0 +1 @@
+# pw\_bytes: Embedded-friendly C++ bytes manipulation primitives
diff --git a/pw_bytes/byte_builder.cc b/pw_bytes/byte_builder.cc
new file mode 100644
index 0000000..80e74d2
--- /dev/null
+++ b/pw_bytes/byte_builder.cc
@@ -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.
+
+#include "pw_bytes/byte_builder.h"
+
+namespace pw {
+
+void ByteBuilder::clear() {
+  size_ = 0;
+  status_ = Status::OK;
+  last_status_ = Status::OK;
+}
+
+ByteBuilder& ByteBuilder::append(size_t count, std::byte 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_[size_];
+  std::memcpy(append_destination, bytes, ResizeForAppend(count));
+  return *this;
+}
+
+size_t ByteBuilder::ResizeForAppend(size_t bytes_to_append) {
+  const size_t copied = std::min(bytes_to_append, max_size() - size());
+  size_ += copied;
+
+  if (buffer_.empty() || bytes_to_append != copied) {
+    SetErrorStatus(Status::RESOURCE_EXHAUSTED);
+  } else {
+    last_status_ = Status::OK;
+  }
+
+  return copied;
+}
+
+void ByteBuilder::resize(size_t new_size) {
+  if (new_size <= size_) {
+    size_ = new_size;
+    last_status_ = Status::OK;
+  } else {
+    SetErrorStatus(Status::OUT_OF_RANGE);
+  }
+}
+
+void ByteBuilder::CopySizeAndStatus(const ByteBuilder& other) {
+  size_ = other.size_;
+  status_ = other.status_;
+  last_status_ = other.last_status_;
+}
+
+void ByteBuilder::SetErrorStatus(Status status) {
+  last_status_ = status;
+  status_ = status;
+}
+
+}  // namespace pw
diff --git a/pw_bytes/byte_builder_test.cc b/pw_bytes/byte_builder_test.cc
new file mode 100644
index 0000000..f39c422
--- /dev/null
+++ b/pw_bytes/byte_builder_test.cc
@@ -0,0 +1,364 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES 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//byte_builder.h"
+
+#include <array>
+#include <cstddef>
+
+#include "gtest/gtest.h"
+
+using std::byte;
+
+template <typename... Args>
+constexpr std::array<byte, sizeof...(Args)> MakeBytes(Args... args) noexcept {
+  return {static_cast<byte>(args)...};
+}
+
+namespace pw {
+namespace {
+
+TEST(ByteBuilder, EmptyBuffer_SizeAndMaxSizeAreCorrect) {
+  ByteBuilder bb(span<byte>{});
+
+  EXPECT_TRUE(bb.empty());
+  EXPECT_EQ(0u, bb.size());
+  EXPECT_EQ(0u, bb.max_size());
+}
+
+TEST(ByteBuilder, NonEmptyBufferOfSize0_SizeAndMaxSizeAreCorrect) {
+  std::array<byte, 3> buffer = MakeBytes(0x01, 0x02, 0x03);
+  ByteBuilder bb(buffer);
+
+  EXPECT_TRUE(bb.empty());
+  EXPECT_EQ(0u, bb.size());
+  EXPECT_EQ(3u, bb.max_size());
+}
+
+TEST(ByteBuilder, Constructor_InsertsEmptyBuffer) {
+  std::array<byte, 3> buffer = MakeBytes(0x01, 0x02, 0x03);
+  ByteBuilder bb(buffer);
+
+  EXPECT_TRUE(bb.empty());
+}
+
+TEST(ByteBuilder, EmptyBuffer_Append) {
+  ByteBuilder bb(span<byte>{});
+  EXPECT_TRUE(bb.empty());
+
+  auto bytesTestLiteral = MakeBytes(0x04, 0x05);
+
+  EXPECT_FALSE(bb.append(bytesTestLiteral.data(), 2).ok());
+  EXPECT_EQ(0u, bb.size());
+  EXPECT_EQ(0u, bb.max_size());
+}
+
+TEST(ByteBuilder, NonEmptyBufferOfSize0_Append) {
+  std::array<byte, 3> buffer = MakeBytes(0x01, 0x02, 0x03);
+  ByteBuilder bb(buffer);
+  EXPECT_TRUE(bb.empty());
+
+  auto bytesTestLiteral = MakeBytes(0x04, 0x05);
+
+  EXPECT_TRUE(bb.append(bytesTestLiteral.data(), 2).ok());
+  EXPECT_EQ(byte{0x04}, bb.data()[0]);
+  EXPECT_EQ(byte{0x05}, bb.data()[1]);
+}
+
+TEST(ByteBuilder, NonEmptyBufferOfSize0_Append_Partial_NotResourceExhausted) {
+  std::array<byte, 3> buffer = MakeBytes(0x01, 0x02, 0x03);
+  ByteBuilder bb(buffer);
+
+  EXPECT_TRUE(bb.empty());
+
+  auto bytesTestLiteral = MakeBytes(0x04, 0x05, 0x06, 0x07);
+
+  EXPECT_TRUE(bb.append(bytesTestLiteral.data(), 3).ok());
+  EXPECT_EQ(byte{0x04}, bb.data()[0]);
+  EXPECT_EQ(byte{0x05}, bb.data()[1]);
+  EXPECT_EQ(byte{0x06}, bb.data()[2]);
+}
+
+TEST(ByteBuilder, NonEmptyBufferOfSize0_Append_Partial_ResourceExhausted) {
+  std::array<byte, 3> buffer = MakeBytes(0x01, 0x02, 0x03);
+  ByteBuilder bb(buffer);
+
+  EXPECT_TRUE(bb.empty());
+
+  auto bytesTestLiteral = MakeBytes(0x04, 0x05, 0x06, 0x07);
+
+  EXPECT_FALSE(bb.append(bytesTestLiteral.data(), 4).ok());
+  EXPECT_EQ(Status::RESOURCE_EXHAUSTED, bb.last_status());
+
+  EXPECT_EQ(byte{0x04}, bb.data()[0]);
+  EXPECT_EQ(byte{0x05}, bb.data()[1]);
+  EXPECT_EQ(byte{0x06}, bb.data()[2]);
+
+  EXPECT_EQ(bb.size(), bb.max_size());
+  EXPECT_EQ(3u, bb.size());
+}
+
+TEST(ByteBuilder, Append_RepeatedBytes) {
+  ByteBuffer<8> bb;
+  EXPECT_TRUE(bb.empty());
+
+  EXPECT_TRUE(bb.append(7, byte{0x04}).ok());
+
+  for (size_t i = 0; i < 7; i++) {
+    EXPECT_EQ(byte{0x04}, bb.data()[i]);
+  }
+}
+
+TEST(ByteBuilder, Append_Bytes_Full) {
+  ByteBuffer<8> bb;
+
+  EXPECT_EQ(8u, bb.max_size() - bb.size());
+
+  EXPECT_TRUE(bb.append(8, byte{0x04}).ok());
+
+  for (size_t i = 0; i < 8; i++) {
+    EXPECT_EQ(byte{0x04}, bb.data()[i]);
+  }
+}
+
+TEST(ByteBuilder, Append_Bytes_Exhausted) {
+  ByteBuffer<8> bb;
+
+  EXPECT_EQ(Status::RESOURCE_EXHAUSTED, bb.append(9, byte{0x04}).status());
+
+  for (size_t i = 0; i < 8; i++) {
+    EXPECT_EQ(byte{0x04}, bb.data()[i]);
+  }
+}
+
+TEST(ByteBuilder, Append_Partial) {
+  std::array<byte, 3> buffer = MakeBytes(0x01, 0x02, 0x03);
+  ByteBuffer<12> bb;
+
+  EXPECT_TRUE(bb.append(buffer.data(), 2).ok());
+  EXPECT_EQ(2u, bb.size());
+  EXPECT_EQ(byte{0x01}, bb.data()[0]);
+  EXPECT_EQ(byte{0x02}, bb.data()[1]);
+}
+
+TEST(ByteBuilder, EmptyBuffer_Resize_WritesNothing) {
+  std::array<byte, 3> buffer = MakeBytes(0x01, 0x02, 0x03);
+  ByteBuilder bb(buffer);
+
+  bb.resize(0);
+  EXPECT_TRUE(bb.ok());
+}
+
+TEST(ByteBuilder, EmptyBuffer_Resize_Larger_Fails) {
+  std::array<byte, 3> buffer = MakeBytes(0x01, 0x02, 0x03);
+  ByteBuilder bb(buffer);
+
+  bb.resize(1);
+
+  EXPECT_EQ(Status::RESOURCE_EXHAUSTED, bb.append(9, byte{0x04}).status());
+}
+
+TEST(ByteBuilder, Resize_Smaller) {
+  std::array<byte, 3> buffer = MakeBytes(0x01, 0x02, 0x03);
+  ByteBuffer<8> bb;
+
+  EXPECT_TRUE(bb.append(buffer.data(), 3).ok());
+
+  bb.resize(1);
+  EXPECT_TRUE(bb.ok());
+  EXPECT_EQ(1u, bb.size());
+  EXPECT_EQ(byte{0x01}, bb.data()[0]);
+}
+
+TEST(ByteBuilder, Resize_Clear) {
+  std::array<byte, 3> buffer = MakeBytes(0x01, 0x02, 0x03);
+  ByteBuffer<8> bb;
+
+  EXPECT_TRUE(bb.append(buffer.data(), 3).ok());
+
+  bb.resize(0);
+  EXPECT_TRUE(bb.ok());
+  EXPECT_EQ(0u, bb.size());
+  EXPECT_TRUE(bb.empty());
+}
+
+TEST(ByteBuilder, Resize_Larger_Fails) {
+  std::array<byte, 3> buffer = MakeBytes(0x01, 0x02, 0x03);
+  ByteBuffer<8> bb;
+
+  EXPECT_TRUE(bb.append(buffer.data(), 3).ok());
+
+  EXPECT_EQ(3u, bb.size());
+  bb.resize(5);
+  EXPECT_EQ(3u, bb.size());
+  EXPECT_EQ(bb.status(), Status::OUT_OF_RANGE);
+}
+
+TEST(ByteBuilder, Status_StartsOk) {
+  ByteBuffer<16> bb;
+  EXPECT_EQ(Status::OK, bb.status());
+  EXPECT_EQ(Status::OK, bb.last_status());
+}
+
+TEST(ByteBuilder, Status_StatusAndLastStatusUpdate) {
+  std::array<byte, 3> buffer = MakeBytes(0x01, 0x02, 0x03);
+  ByteBuffer<2> bb;
+
+  EXPECT_FALSE(bb.append(buffer.data(), 3).ok());
+  EXPECT_EQ(Status::RESOURCE_EXHAUSTED, bb.status());
+  EXPECT_EQ(Status::RESOURCE_EXHAUSTED, bb.last_status());
+
+  bb.resize(4);
+  EXPECT_EQ(Status::OUT_OF_RANGE, bb.status());
+  EXPECT_EQ(Status::OUT_OF_RANGE, bb.last_status());
+
+  EXPECT_FALSE(bb.append(buffer.data(), 0).ok());
+  EXPECT_EQ(Status::OUT_OF_RANGE, bb.status());
+  EXPECT_EQ(Status::OK, bb.last_status());
+}
+
+TEST(ByteBuilder, Status_ClearStatus_SetsStatuesToOk) {
+  std::array<byte, 3> buffer = MakeBytes(0x01, 0x02, 0x03);
+  ByteBuffer<2> bb;
+
+  EXPECT_FALSE(bb.append(buffer.data(), 3).ok());
+  EXPECT_EQ(Status::RESOURCE_EXHAUSTED, bb.status());
+  EXPECT_EQ(Status::RESOURCE_EXHAUSTED, bb.last_status());
+
+  bb.clear_status();
+  EXPECT_EQ(Status::OK, bb.status());
+  EXPECT_EQ(Status::OK, bb.last_status());
+}
+
+TEST(ByteBuilder, PushBack) {
+  ByteBuffer<12> bb;
+  bb.push_back(byte{0x01});
+  EXPECT_EQ(Status::OK, bb.last_status());
+  EXPECT_EQ(1u, bb.size());
+  EXPECT_EQ(byte{0x01}, bb.data()[0]);
+}
+
+TEST(ByteBuilder, PushBack_Full) {
+  ByteBuffer<1> bb;
+  bb.push_back(byte{0x01});
+  EXPECT_EQ(Status::OK, bb.last_status());
+  EXPECT_EQ(1u, bb.size());
+}
+
+TEST(ByteBuilder, PushBack_Full_ResourceExhausted) {
+  ByteBuffer<1> bb;
+  bb.push_back(byte{0x01});
+  bb.push_back(byte{0x01});
+
+  EXPECT_EQ(Status::RESOURCE_EXHAUSTED, bb.last_status());
+  EXPECT_EQ(1u, bb.size());
+}
+
+TEST(ByteBuilder, PopBack) {
+  std::array<byte, 3> buffer = MakeBytes(0x01, 0x02, 0x03);
+  ByteBuffer<3> bb;
+
+  bb.append(buffer.data(), 3);
+
+  bb.pop_back();
+  EXPECT_EQ(Status::OK, bb.last_status());
+  EXPECT_EQ(2u, bb.size());
+  EXPECT_EQ(byte{0x01}, bb.data()[0]);
+  EXPECT_EQ(byte{0x02}, bb.data()[1]);
+}
+
+TEST(ByteBuilder, PopBack_Empty) {
+  std::array<byte, 3> buffer = MakeBytes(0x01, 0x02, 0x03);
+  ByteBuffer<3> bb;
+  bb.append(buffer.data(), 3);
+
+  bb.pop_back();
+  bb.pop_back();
+  bb.pop_back();
+  EXPECT_EQ(Status::OK, bb.last_status());
+  EXPECT_EQ(0u, bb.size());
+  EXPECT_TRUE(bb.empty());
+}
+
+TEST(ByteBuffer, Assign) {
+  std::array<byte, 3> buffer = MakeBytes(0x01, 0x02, 0x03);
+  ByteBuffer<10> one;
+  ByteBuffer<10> two;
+
+  one.append(buffer.data(), 3);
+  EXPECT_EQ(byte{0x01}, one.data()[0]);
+  EXPECT_EQ(byte{0x02}, one.data()[1]);
+  EXPECT_EQ(byte{0x03}, one.data()[2]);
+
+  two = one;
+  EXPECT_EQ(byte{0x01}, two.data()[0]);
+  EXPECT_EQ(byte{0x02}, two.data()[1]);
+  EXPECT_EQ(byte{0x03}, two.data()[2]);
+
+  auto bytesTestLiteral = MakeBytes(0x04, 0x05, 0x06, 0x07);
+  one.append(bytesTestLiteral.data(), 2);
+  two.append(bytesTestLiteral.data(), 4);
+  EXPECT_EQ(5u, one.size());
+  EXPECT_EQ(7u, two.size());
+  EXPECT_EQ(byte{0x04}, one.data()[3]);
+  EXPECT_EQ(byte{0x05}, one.data()[4]);
+  EXPECT_EQ(byte{0x04}, two.data()[3]);
+  EXPECT_EQ(byte{0x05}, two.data()[4]);
+  EXPECT_EQ(byte{0x06}, two.data()[5]);
+  EXPECT_EQ(byte{0x07}, two.data()[6]);
+
+  two.push_back(byte{0x01});
+  two.push_back(byte{0x01});
+  two.push_back(byte{0x01});
+  two.push_back(byte{0x01});
+  ASSERT_EQ(Status::RESOURCE_EXHAUSTED, two.status());
+  ASSERT_EQ(Status::RESOURCE_EXHAUSTED, two.last_status());
+
+  one = two;
+  EXPECT_EQ(byte{0x01}, two.data()[7]);
+  EXPECT_EQ(byte{0x01}, two.data()[8]);
+  EXPECT_EQ(byte{0x01}, two.data()[9]);
+  EXPECT_EQ(Status::RESOURCE_EXHAUSTED, one.status());
+  EXPECT_EQ(Status::RESOURCE_EXHAUSTED, one.last_status());
+}
+
+TEST(ByteBuffer, CopyConstructFromSameSize) {
+  ByteBuffer<10> one;
+  std::array<byte, 3> buffer = MakeBytes(0x01, 0x02, 0x03);
+
+  one.append(buffer.data(), 3);
+  EXPECT_EQ(byte{0x01}, one.data()[0]);
+  EXPECT_EQ(byte{0x02}, one.data()[1]);
+  EXPECT_EQ(byte{0x03}, one.data()[2]);
+
+  ByteBuffer<10> two(one);
+  EXPECT_EQ(byte{0x01}, two.data()[0]);
+  EXPECT_EQ(byte{0x02}, two.data()[1]);
+  EXPECT_EQ(byte{0x03}, two.data()[2]);
+}
+
+TEST(ByteBuffer, CopyConstructFromSmaller) {
+  std::array<byte, 3> buffer = MakeBytes(0x01, 0x02, 0x03);
+  ByteBuffer<2> one;
+  one.append(buffer.data(), 2);
+  ByteBuffer<3> two(one);
+
+  EXPECT_EQ(byte{0x01}, two.data()[0]);
+  EXPECT_EQ(byte{0x02}, two.data()[1]);
+
+  EXPECT_EQ(Status::OK, two.status());
+}
+
+}  // namespace
+}  // namespace pw
diff --git a/pw_bytes/docs.rst b/pw_bytes/docs.rst
new file mode 100644
index 0000000..83e9f1b
--- /dev/null
+++ b/pw_bytes/docs.rst
@@ -0,0 +1,32 @@
+.. _chapter-pw-bytes:
+
+.. default-domain:: cpp
+
+.. highlight:: sh
+
+---------
+pw_bytes
+---------
+pw_bytes is a collection of utilities for manipulating binary data.
+
+Compatibility
+=============
+C++17
+
+Dependencies
+============
+* ``pw_preprocessor``
+* ``pw_status``
+* ``pw_span``
+
+Features
+========
+
+pw::ByteBuilder
+-----------------
+ByteBuilder is a utility class which facilitates the creation and
+building of formatted bytes in a fixed-size buffer.
+
+Future work
+^^^^^^^^^^^
+* Adding Endianness
diff --git a/pw_bytes/public/pw_bytes/byte_builder.h b/pw_bytes/public/pw_bytes/byte_builder.h
new file mode 100644
index 0000000..923f3db
--- /dev/null
+++ b/pw_bytes/public/pw_bytes/byte_builder.h
@@ -0,0 +1,200 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS 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 <cstddef>
+#include <cstring>
+
+#include "pw_preprocessor/compiler.h"
+#include "pw_span/span.h"
+#include "pw_status/status.h"
+#include "pw_status/status_with_size.h"
+
+namespace pw {
+
+// ByteBuilder facilitates building bytes in a fixed-size buffer.
+// BytesBuilders never overflow. Status is tracked for each operation and
+// an overall status is maintained, which reflects the most recent error.
+//
+// A ByteBuilder does not own the buffer it writes to. It can be used to write
+// bytes to any buffer. The ByteBuffer template class, defined below,
+// allocates a buffer alongside a ByteBuilder.
+
+class ByteBuilder {
+ public:
+  // Creates an empty ByteBuilder.
+  constexpr ByteBuilder(span<std::byte> buffer) : buffer_(buffer), size_(0) {}
+
+  // Disallow copy/assign to avoid confusion about where the bytes is actually
+  // stored. ByteBuffers may be copied into one another.
+  ByteBuilder(const ByteBuilder&) = delete;
+
+  ByteBuilder& operator=(const ByteBuilder&) = delete;
+
+  // 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
+  // that occurred while updating the bytes. After an update fails, the status
+  // remains non-OK until it is cleared with clear() or clear_status(). Returns:
+  //
+  //     OK if no errors have occurred
+  //     RESOURCE_EXHAUSTED if output to the ByteBuilder was truncated
+  //     INVALID_ARGUMENT if printf-style formatting failed
+  //     OUT_OF_RANGE if an operation outside the buffer was attempted
+  //
+  Status status() const { return status_; }
+
+  // Returns status() and size() as a StatusWithSize.
+  StatusWithSize status_with_size() const {
+    return StatusWithSize(status_, size_);
+  }
+
+  // The status from the last operation. May be OK while status() is not OK.
+  Status last_status() const { return last_status_; }
+
+  // True if status() is Status::OK.
+  bool ok() const { return status_.ok(); }
+
+  // True if the bytes builder is empty.
+  bool empty() const { return size() == 0u; }
+
+  // Returns the current length of the bytes, excluding the null terminator.
+  size_t size() const { return size_; }
+
+  // Returns the maximum length of the bytes, excluding the null terminator.
+  size_t max_size() const { return buffer_.size(); }
+
+  // Clears the bytes and resets its error state.
+  void clear();
+
+  // Sets the statuses to Status::OK;
+  void clear_status() {
+    status_ = Status::OK;
+    last_status_ = Status::OK;
+  }
+
+  // Appends a single byte. Stets the status to RESOURCE_EXHAUSTED if the
+  // byte cannot be added because the buffer is full.
+  void push_back(std::byte b) { append(1, b); }
+
+  // Removes the last byte. Sets the status to OUT_OF_RANGE if the buffer
+  // is empty (in which case the unsigned overflow is intentional).
+  void pop_back() PW_NO_SANITIZE("unsigned-integer-overflow") {
+    resize(size() - 1);
+  }
+
+  // Appends the provided byte count times.
+  ByteBuilder& append(size_t count, std::byte b);
+
+  // Appends count bytes from 'bytes' to the end of the ByteBuilder. If count
+  // exceeds the remaining space in the ByteBuffer, max_size() - size()
+  // bytes are appended and the status is set to RESOURCE_EXHAUSTED.
+  ByteBuilder& append(const void* bytes, size_t count);
+
+  // Appends bytes from a byte span that calls the pointer/length version.
+  ByteBuilder& append(span<std::byte> bytes) {
+    return append(bytes.data(), bytes.size());
+  }
+
+  // Sets the ByteBuilder's size. This function only truncates; if
+  // new_size > size(), it sets status to OUT_OF_RANGE and does nothing.
+  void resize(size_t new_size);
+
+ protected:
+  // Functions to support ByteBuffer copies.
+  constexpr ByteBuilder(const span<std::byte>& buffer, const ByteBuilder& other)
+      : buffer_(buffer),
+        size_(other.size_),
+        status_(other.status_),
+        last_status_(other.last_status_) {}
+
+  void CopySizeAndStatus(const ByteBuilder& other);
+
+ private:
+  size_t ResizeForAppend(size_t bytes_to_append);
+
+  void SetErrorStatus(Status status);
+
+  const span<std::byte> buffer_;
+
+  size_t size_;
+  Status status_;
+  Status last_status_;
+};
+
+// ByteBuffers declare a buffer along with a ByteBuilder.
+template <size_t size_bytes>
+class ByteBuffer : public ByteBuilder {
+ public:
+  ByteBuffer() : ByteBuilder(buffer_) {}
+
+  // ByteBuffers of the same size may be copied and assigned into one another.
+  ByteBuffer(const ByteBuffer& other) : ByteBuilder(buffer_, other) {
+    CopyContents(other);
+  }
+
+  // A smaller ByteBuffer may be copied or assigned into a larger one.
+  template <size_t other_size_bytes>
+  ByteBuffer(const ByteBuffer<other_size_bytes>& other)
+      : ByteBuilder(buffer_, other) {
+    static_assert(ByteBuffer<other_size_bytes>::max_size() <= max_size(),
+                  "A ByteBuffer cannot be copied into a smaller buffer");
+    CopyContents(other);
+  }
+
+  template <size_t other_size_bytes>
+  ByteBuffer& operator=(const ByteBuffer<other_size_bytes>& other) {
+    assign<other_size_bytes>(other);
+    return *this;
+  }
+
+  ByteBuffer& operator=(const ByteBuffer& other) {
+    assign<size_bytes>(other);
+    return *this;
+  }
+
+  template <size_t other_size_bytes>
+  ByteBuffer& assign(const ByteBuffer<other_size_bytes>& other) {
+    static_assert(ByteBuffer<other_size_bytes>::max_size() <= max_size(),
+                  "A ByteBuffer cannot be copied into a smaller buffer");
+    CopySizeAndStatus(other);
+    CopyContents(other);
+    return *this;
+  }
+
+  // Returns the maximum length of the bytes that can be inserted in the bytes
+  // buffer.
+  static constexpr size_t max_size() { return size_bytes; }
+
+  // Returns a ByteBuffer<size_bytes>& instead of a generic ByteBuilder& for
+  // append calls.
+  template <typename... Args>
+  ByteBuffer& append(Args&&... args) {
+    ByteBuilder::append(std::forward<Args>(args)...);
+    return *this;
+  }
+
+ private:
+  template <size_t kOtherSize>
+  void CopyContents(const ByteBuffer<kOtherSize>& other) {
+    std::memcpy(buffer_.data(), other.data(), other.size());
+  }
+
+  std::array<std::byte, size_bytes> buffer_;
+};
+
+}  // namespace pw