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