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/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