From 1a7918b7160e55afbec9d642b97538b7d1dae578 Mon Sep 17 00:00:00 2001 From: Ben Hamilton Date: Thu, 15 Dec 2022 10:09:30 -0700 Subject: [PATCH] [client] New class LengthDelimitedRingBuffer This CL implements LengthDelimitedRingBuffer, a general-purpose ringbuffer suitable for use as a Crashpad Annotation. This ringbuffer supports writing variably-sized data delimited by a Base 128 varint-encoded length separator. LengthDelimitedRingBuffer is backed by a std::array, so it has a fixed maximum size. It supports reading via RingBufferReader as well as writing via RingBufferWriter. Change-Id: I23ecb4a85ee8e846e1efc6937a5cb089a494d50a Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/4023618 Reviewed-by: Robert Sesek Commit-Queue: Ben Hamilton --- client/BUILD.gn | 2 + client/length_delimited_ring_buffer.h | 545 ++++++++++++++++++++ client/length_delimited_ring_buffer_test.cc | 284 ++++++++++ 3 files changed, 831 insertions(+) create mode 100644 client/length_delimited_ring_buffer.h create mode 100644 client/length_delimited_ring_buffer_test.cc diff --git a/client/BUILD.gn b/client/BUILD.gn index 001a719c..b05aed5f 100644 --- a/client/BUILD.gn +++ b/client/BUILD.gn @@ -119,6 +119,7 @@ static_library("common") { "crash_report_database.h", "crashpad_info.cc", "crashpad_info.h", + "length_delimited_ring_buffer.h", "settings.cc", "settings.h", "simple_address_range_bag.h", @@ -154,6 +155,7 @@ source_set("client_test") { "annotation_list_test.cc", "annotation_test.cc", "crash_report_database_test.cc", + "length_delimited_ring_buffer_test.cc", "prune_crash_reports_test.cc", "settings_test.cc", "simple_address_range_bag_test.cc", diff --git a/client/length_delimited_ring_buffer.h b/client/length_delimited_ring_buffer.h new file mode 100644 index 00000000..3ee77501 --- /dev/null +++ b/client/length_delimited_ring_buffer.h @@ -0,0 +1,545 @@ +// Copyright 2022 The Crashpad Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_CLIENT_LENGTH_DELIMITED_RING_BUFFER_H_ +#define CRASHPAD_CLIENT_LENGTH_DELIMITED_RING_BUFFER_H_ + +#include +#include +#include +#include +#include + +#include +#include + +namespace crashpad { + +namespace internal { + +//! \brief Default capacity of `RingBufferData`, in bytes. +inline constexpr uint32_t kDefaultRingBufferCapacity = 8192; + +//! \brief A tuple holding the current range of bytes which can be read from or +//! have been written to. +struct Range final { + uint32_t offset; + uint32_t length; +}; + +static_assert(sizeof(Range) == 8, + "struct Range is not packed on this platform"); + +//! \!brief Calculates the length in bytes of `value` encoded using +//! little-endian Base 128 varint encoding. +//! \sa https://developers.google.com/protocol-buffers/docs/encoding#varints +//! +//! `LengthDelimitedRingBufferWriter` uses varint-encoded delimiters to enable +//! zero-copy deserialization of the ringbuffer's contents when storing +//! protobufs inside the ringbuffer, e.g. via +//! `google::protobuf::util::ParseDelimitedFromZeroCopyStream()` or similar. +//! +//! \sa +//! https://github.com/protocolbuffers/protobuf/blob/3202b9da88ceb75b65bbabaf4033c95e872f828d/src/google/protobuf/util/delimited_message_util.h#L85 +//! \sa +//! https://github.com/protocolbuffers/protobuf/blob/8bd49dea5e167a389d94b71d24c981d8f9fa0c99/src/google/protobuf/io/zero_copy_stream_impl_lite.h#L68 +//! \sa +//! https://github.com/protocolbuffers/protobuf/blob/8bd49dea5e167a389d94b71d24c981d8f9fa0c99/src/google/protobuf/io/coded_stream.h#L171 +//! +//! \!param[in] value Value to be encoded in Base 128 varint encoding. +//! \!return The length in bytes of `value` in Base 128 varint encoding. +constexpr uint32_t Base128VarintUint32EncodedLength(uint32_t value) { + uint32_t size = 1; + while (value >= 0x80) { + value >>= 7; + size++; + } + return size; +} + +template +using RingBufferArray = std::array; + +//! \return The size of the `RingBufferArray` as a `uint32_t`. +template +constexpr uint32_t RingBufferArraySize( + const RingBufferArray& ring_buffer_data) { + static_assert(RingBufferCapacity <= std::numeric_limits::max()); + return static_cast(ring_buffer_data.size()); +} + +//! \brief Reads data from the ring buffer into a target buffer. +//! \param[in] ring_buffer_data The ring buffer to read. +//! \param[in,out] ring_buffer_read_range The range of the data available +//! to read. Upon return, set to the remaining range of data available +//! to read, if any. +//! \param[in] target_buffer Buffer into which data will be written. +//! \param[in] target_buffer_length Number of bytes to write into +//! `target_buffer`. +//! +//! \return `true` if the read succeeded, `false` otherwise. On success, updates +//! `ring_buffer_read_range` to reflect the bytes consumed. +//! +//! The bytes can wrap around the end of the ring buffer, in which case the read +//! continues at the beginning of the ring buffer (if the ring buffer is long +//! enough). +template +bool ReadBytesFromRingBuffer(const RingBufferArrayType& ring_buffer_data, + internal::Range& ring_buffer_read_range, + uint8_t* target_buffer, + uint32_t target_buffer_length) { + if (target_buffer_length > ring_buffer_read_range.length) { + return false; + } + if (target_buffer_length == 0) { + return true; + } + const uint32_t initial_read_length = std::min( + target_buffer_length, + RingBufferArraySize(ring_buffer_data) - ring_buffer_read_range.offset); + memcpy(target_buffer, + &ring_buffer_data[ring_buffer_read_range.offset], + initial_read_length); + if (initial_read_length < target_buffer_length) { + const uint32_t remaining_read_length = + target_buffer_length - initial_read_length; + memcpy(target_buffer + initial_read_length, + &ring_buffer_data[0], + remaining_read_length); + } + ring_buffer_read_range.offset = + (ring_buffer_read_range.offset + target_buffer_length) % + RingBufferArraySize(ring_buffer_data); + ring_buffer_read_range.length -= target_buffer_length; + return true; +} + +//! \brief Reads a single little-endian Base 128 varint-encoded uint32 from the +//! ring buffer. +//! \param[in] ring_buffer_data The ring buffer to read. +//! \param[in,out] ring_buffer_read_range The range of the data available +//! to read. Upon return, set to the remaining range of data available +//! to read, if any. +//! \param[out] result Upon success, set to the decoded value read from the +//! buffer. +//! +//! \return The length in bytes of the varint if the read succeeded, +//! `std::nullopt` otherwise. On success, updates `ring_buffer_read_range` +//! to reflect the bytes available to read. +//! +//! The varint can wrap around the end of the ring buffer, in which case the +//! read continues at the beginning of the ring buffer (if the ring buffer is +//! long enough). +template +std::optional ReadBase128VarintUint32FromRingBuffer( + const RingBufferArrayType& ring_buffer_data, + internal::Range& ring_buffer_read_range, + uint32_t& result) { + result = 0; + uint8_t cur_varint_byte = 0; + constexpr uint8_t kValueMask = 0x7f; + constexpr uint8_t kContinuationMask = 0x80; + int length = 0; + do { + if (!ReadBytesFromRingBuffer( + ring_buffer_data, ring_buffer_read_range, &cur_varint_byte, 1)) { + return std::nullopt; + } + result |= static_cast(cur_varint_byte & kValueMask) + << (length * 7); + ++length; + } while ((cur_varint_byte & kContinuationMask) == kContinuationMask); + return length; +} + +//! \brief Writes data from the source buffer into the ring buffer. +//! \param[in] source_buffer Buffer from which data will be read. +//! \param[in] source_buffer_length The length in bytes of `source_buffer`. +//! \param[in] ring_buffer_data The ring buffer into which data will be read. +//! \param[in,out] ring_buffer_write_range The range of the data available +//! to write. Upon return, set to the remaining range of data available +//! to write, if any. +//! +//! \return `true` if write read succeeded, `false` otherwise. On success, +//! updates +//! `ring_buffer_write_range` to reflect the bytes written. +//! +//! The bytes can wrap around the end of the ring buffer, in which case the +//! write continues at the beginning of the ring buffer (if the ring buffer is +//! long enough). +template +bool WriteBytesToRingBuffer(const uint8_t* const source_buffer, + uint32_t source_buffer_length, + RingBufferArrayType& ring_buffer_data, + internal::Range& ring_buffer_write_range) { + const uint32_t ring_buffer_bytes_remaining = + RingBufferArraySize(ring_buffer_data) - ring_buffer_write_range.length; + if (source_buffer_length > ring_buffer_bytes_remaining) { + return false; + } + const uint32_t initial_write_length = std::min( + source_buffer_length, + RingBufferArraySize(ring_buffer_data) - ring_buffer_write_range.offset); + memcpy(&ring_buffer_data[ring_buffer_write_range.offset], + source_buffer, + initial_write_length); + if (initial_write_length < source_buffer_length) { + const uint32_t remaining_write_length = + source_buffer_length - initial_write_length; + memcpy(&ring_buffer_data[0], + source_buffer + initial_write_length, + remaining_write_length); + } + ring_buffer_write_range.offset = + (ring_buffer_write_range.offset + source_buffer_length) % + RingBufferArraySize(ring_buffer_data); + ring_buffer_write_range.length -= source_buffer_length; + return true; +} + +//! \brief Writes a single Base 128 varint-encoded little-endian uint32 into the +//! ring buffer. +//! \param[in] value The value to encode and write into the ring buffer. +//! \param[in] ring_buffer_data The ring buffer into which to write. +//! \param[in,out] ring_buffer_write_range The range of the data available +//! to write. Upon return, set to the remaining range of data available +//! to write, if any. +//! +//! \return The length in bytes of the varint if the write succeeded, +//! `std::nullopt` otherwise. On success, updates `write_buffer_read_range` +//! to reflect the range available to write, if any. +//! +//! The varint can wrap around the end of the ring buffer, in which case the +//! write continues at the beginning of the ring buffer (if the ring buffer is +//! long enough). +template +std::optional WriteBase128VarintUint32ToRingBuffer( + uint32_t value, + RingBufferArrayType& ring_buffer_data, + internal::Range& ring_buffer_write_range) { + uint8_t cur_varint_byte; + constexpr uint8_t kValueMask = 0x7f; + constexpr uint8_t kContinuationMask = 0x80; + // Every varint encodes to at least 1 byte of data. + int length = 1; + while (value > kValueMask) { + cur_varint_byte = + (static_cast(value) & kValueMask) | kContinuationMask; + if (!WriteBytesToRingBuffer( + &cur_varint_byte, 1, ring_buffer_data, ring_buffer_write_range)) { + return std::nullopt; + } + value >>= 7; + ++length; + } + cur_varint_byte = static_cast(value); + if (!WriteBytesToRingBuffer( + &cur_varint_byte, 1, ring_buffer_data, ring_buffer_write_range)) { + return std::nullopt; + } + return length; +} + +} // namespace internal + +//! \brief Storage for a ring buffer which can hold up to `RingBufferCapacity` +//! bytes of Base 128-varint delimited variable-length items. +//! +//! This struct contains a header immediately followed by the ring buffer +//! data. The current read offset and length are stored in `header.data_range`. +//! +//! The structure of this object is: +//! +//! `|magic|version|data_offset|data_length|ring_buffer_data|` +//! +//! To write data to this object, see `LengthDelimitedRingBufferWriter`. +//! To read data from this object, see `LengthDelimitedRingBufferReader`. +//! +//! The bytes of this structure are suitable for direct serialization from +//! memory to disk, e.g. as a crashpad::Annotation. +template +struct RingBufferData final { + RingBufferData() = default; + RingBufferData(RingBufferData&) = delete; + RingBufferData& operator=(RingBufferData&) = delete; + + //! \brief Attempts to overwrite the contents of this object by deserializing + //! the buffer into this object. + //! \param[in] buffer The bytes to deserialize into this object. + //! \param[in] length The length in bytes of `buffer`. + //! + //! \return `true` if the buffer was a valid RingBufferData and this object + //! has enough capacity to store its bytes, `false` otherwise. + bool DeserializeFromBuffer(const void* buffer, uint32_t length) { + if (length < sizeof(header) || length > sizeof(header) + sizeof(data)) { + return false; + } + const Header* other_header = reinterpret_cast(buffer); + if (other_header->magic != kMagic || other_header->version != kVersion) { + return false; + } + header.data_range = other_header->data_range; + const uint8_t* other_ring_buffer_bytes = reinterpret_cast(buffer) + sizeof(*other_header); + const uint32_t other_ring_buffer_len = length - sizeof(*other_header); + memcpy(&data[0], other_ring_buffer_bytes, other_ring_buffer_len); + return true; + } + + //! \return The current length in bytes of the data written to the ring + //! buffer. + uint32_t GetRingBufferLength() const { + internal::Range data_range = header.data_range; + return sizeof(header) + std::min(internal::RingBufferArraySize(data), + data_range.offset + data_range.length); + } + + //! \brief Resets the state of the ring buffer (e.g., for testing). + void ResetForTesting() { header.data_range = {0, 0}; } + + //! \brief The magic signature of the ring buffer. + static constexpr uint32_t kMagic = 0xcab00d1e; + //! \brief The version of the ring buffer. + static constexpr uint32_t kVersion = 1; + + //! \brief A header containing metadata preceding the ring buffer data. + struct Header final { + Header() : magic(kMagic), version(kVersion), data_range({0, 0}) {} + + //! \brief The fixed magic value identifying this as a ring buffer. + const uint32_t magic; + + //! \brief The version of this ring buffer data. + const uint32_t version; + + //! \brief The range of readable data in the ring buffer. + internal::Range data_range; + }; + + //! \brief The header containing ring buffer metadata. + Header header; + + //! \brief The bytes of the ring buffer data. + internal::RingBufferArray data; + + static_assert(sizeof(Header) == 16); + static_assert(RingBufferCapacity <= std::numeric_limits::max()); +}; + +// Ensure the ring buffer is packed correctly at its default capacity. +static_assert(sizeof(RingBufferData) == + 16 + internal::kDefaultRingBufferCapacity); + +// Allow just `RingBufferData foo;` to be declared without template arguments +// using CTAD. +template +RingBufferData() -> RingBufferData; + +//! \brief Reads variable-length data buffers from a `RingBufferData`, +//! delimited by Base128 varint-encoded length delimiters. +//! +//! Holds a reference to a `RingBufferData` with the capacity to hold +//! `RingBufferDataType::size()` bytes of variable-length buffers each +//! preceded by its length (encoded as a Base128 length varint). +//! +//! Provides reading capabilities via `Pop()`. +template +class LengthDelimitedRingBufferReader final { + public: + //! \brief Constructs a reader which holds a reference to `ring_buffer`. + //! \param[in] ring_buffer The ring buffer from which data will be read. + //! This object must outlive the lifetime of `ring_buffer`. + constexpr explicit LengthDelimitedRingBufferReader( + RingBufferDataType& ring_buffer) + : ring_buffer_(ring_buffer), + data_range_(ring_buffer_.header.data_range) {} + + LengthDelimitedRingBufferReader(const LengthDelimitedRingBufferReader&) = + delete; + LengthDelimitedRingBufferReader& operator=( + const LengthDelimitedRingBufferReader&) = delete; + + //! \brief Pops off the next buffer from the front of the ring buffer. + //! + //! \param[in] target_buffer On success, the buffer into which data will + //! be read. + //! \return On success, returns `true` and advances `ring_buffer.data_range` + //! past the end of the buffer read. Otherwise, returns `false`. + bool Pop(std::vector& target_buffer) { + return PopWithRange(target_buffer, data_range_); + } + + //! \brief Resets the state of the reader (e.g., for testing). + void ResetForTesting() { data_range_ = {0, 0}; } + + private: + //! \brief Pops off the next buffer from the front of the ring buffer. + //! \param[in] target_buffer On success, the buffer into which data will + //! be read. + //! \param[in,out] data_range The range of data available to read. + //! On success, updated to the remaining range avilable to read. + //! \return On success, returns `true` and advances `ring_buffer.data_range` + //! past the end of the buffer read. Otherwise, returns `false`. + bool PopWithRange(std::vector& target_buffer, + internal::Range& data_range) { + uint32_t buffer_length; + if (!ReadBase128VarintUint32FromRingBuffer( + ring_buffer_.data, data_range, buffer_length)) { + return false; + } + if (buffer_length == 0) { + // A zero-length buffer means the buffer was truncated in the middle of a + // Push(). + return false; + } + const auto previous_target_buffer_size = target_buffer.size(); + target_buffer.resize(previous_target_buffer_size + buffer_length); + if (!ReadBytesFromRingBuffer(ring_buffer_.data, + data_range, + &target_buffer[previous_target_buffer_size], + buffer_length)) { + return false; + } + return true; + } + + //! \brief Reference to the ring buffer from which data is read. + const RingBufferDataType& ring_buffer_; + //! \brief Range of data currently available to read. + internal::Range data_range_; +}; + +// Allow just `LengthDelimitedRingBufferReader reader(foo);` to be declared +// without template arguments using CTAD. +template +LengthDelimitedRingBufferReader(RingBufferDataType&) + -> LengthDelimitedRingBufferReader; + +//! \brief Writes variable-length data buffers to a `RingBufferData`, +//! delimited by Base128 varint-encoded length delimiters. +//! +//! Holds a reference to a `RingBufferData` with the capacity to hold +//! `RingBufferDataType::size()` bytes of variable-length buffers each +//! preceded by its length (encoded as a Base128 length varint). +//! +//! Provides writing capabilities via `Push()`. +template +class LengthDelimitedRingBufferWriter final { + public: + //! \brief Constructs a writer which holds a reference to `ring_buffer`. + //! \param[in] ring_buffer The ring buffer into which data will be written. + //! This object must outlive the lifetime of `ring_buffer`. + constexpr explicit LengthDelimitedRingBufferWriter( + RingBufferDataType& ring_buffer) + : ring_buffer_(ring_buffer), ring_buffer_write_offset_(0) {} + + LengthDelimitedRingBufferWriter(const LengthDelimitedRingBufferWriter&) = + delete; + LengthDelimitedRingBufferWriter& operator=( + const LengthDelimitedRingBufferWriter&) = delete; + + //! \brief Writes data to the ring buffer. + //! + //! If there is not enough room remaining in the ring buffer to store the new + //! data, old data will be removed from the ring buffer in FIFO order until + //! there is room for the new data. + //! + //! \param[in] buffer The data to be written. + //! \param[in] buffer_length The lengh of `buffer`, in bytes. + //! \return On success, returns `true`, updates `ring_buffer.data_range` + //! to reflect the remaining data available to read, and updates + //! `ring_buffer_write_offset_` to reflec the current write positionl. + //! Otherwise, returns `false`. + bool Push(const void* const buffer, uint32_t buffer_length) { + if (buffer_length == 0) { + // Pushing a zero-length buffer is not allowed + // (`LengthDelimitedRingBufferWriter` reserves that to represent a + // temporarily truncated item below). + return false; + } + const uint32_t buffer_varint_encoded_length = + internal::Base128VarintUint32EncodedLength(buffer_length); + const uint32_t bytes_needed = buffer_varint_encoded_length + buffer_length; + if (bytes_needed > ring_buffer_.data.size()) { + return false; + } + // If needed, move the readable region forward one buffer at a time to make + // room for `buffer_length` bytes of new data. + auto readable_data_range = ring_buffer_.header.data_range; + uint32_t bytes_available = + internal::RingBufferArraySize(ring_buffer_.data) - + readable_data_range.length; + while (bytes_available < bytes_needed) { + uint32_t bytes_to_skip; + std::optional varint_length = ReadBase128VarintUint32FromRingBuffer( + ring_buffer_.data, readable_data_range, bytes_to_skip); + if (!varint_length.has_value()) { + return false; + } + // Skip past the next entry including its prepended varint length. + readable_data_range.offset = + (readable_data_range.offset + bytes_to_skip) % + internal::RingBufferArraySize(ring_buffer_.data); + readable_data_range.length -= bytes_to_skip; + bytes_available += varint_length.value() + bytes_to_skip; + } + // Write the varint containing `buffer_length` to the current write + // position. + internal::Range write_range = { + ring_buffer_write_offset_, + bytes_needed, + }; + + internal::WriteBase128VarintUint32ToRingBuffer( + buffer_length, ring_buffer_.data, write_range); + // Next, write the bytes from `buffer`. + internal::WriteBytesToRingBuffer( + reinterpret_cast(buffer), + buffer_length, + ring_buffer_.data, + write_range); + // Finally, update the write position and read data range taking into + // account any items skipped to make room plus the new buffer's varint + // length and the new buffer's length. + ring_buffer_write_offset_ = write_range.offset; + const internal::Range final_data_range = { + readable_data_range.offset, + readable_data_range.length + bytes_needed, + }; + ring_buffer_.header.data_range = final_data_range; + return true; + } + + //! \brief Resets the state of the ring buffer and writer (e.g., for testing). + void ResetForTesting() { + ring_buffer_.ResetForTesting(); + ring_buffer_write_offset_ = 0; + } + + private: + //! \brief Reference to the ring buffer from which data is written. + RingBufferDataType& ring_buffer_; + + // \brief Current write position next time `Push()` is invoked. + uint32_t ring_buffer_write_offset_; +}; + +// Allow just `LengthDelimitedRingBufferWriter writer(foo);` to be declared +// without template arguments using CTAD. +template +LengthDelimitedRingBufferWriter(RingBufferDataType&) + -> LengthDelimitedRingBufferWriter; + +} // namespace crashpad + +#endif // CRASHPAD_CLIENT_LENGTH_DELIMITED_RING_BUFFER_H_ diff --git a/client/length_delimited_ring_buffer_test.cc b/client/length_delimited_ring_buffer_test.cc new file mode 100644 index 00000000..e38dbfd1 --- /dev/null +++ b/client/length_delimited_ring_buffer_test.cc @@ -0,0 +1,284 @@ +// Copyright 2022 The Crashpad Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "client/length_delimited_ring_buffer.h" + +#include +#include +#include + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace { + +using ::crashpad::LengthDelimitedRingBufferReader; +using ::crashpad::LengthDelimitedRingBufferWriter; +using ::crashpad::RingBufferData; + +using ::testing::Eq; +using ::testing::IsFalse; +using ::testing::IsTrue; + +// Buffer with magic 0xcab00d1e, version 1, read_pos 0, length 3, and 3 bytes of +// data (1 varint length, 2 bytes data) +constexpr char kValidBufferSize3[] = + "\x1e\x0d\xb0\xca\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x02\x42" + "\x23"; +constexpr size_t kValidBufferSize3Len = + sizeof(kValidBufferSize3) - 1; // Remove trailing NUL. + +// Buffer with magic 0xcab00d1e, version 8, read_pos 0, length 3, and 3 bytes of +// data (1 varint length, 2 bytes data). +constexpr char kInvalidVersionBuffer[] = + "\x1e\x0d\xb0\xca\x08\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x02\xAB" + "\xCD"; +constexpr size_t kInvalidVersionBufferLen = + sizeof(kInvalidVersionBuffer) - 1; // Remove trailing NUL. + +// Buffer representing process which crashed while in the middle of a Push() +// operation, with a previously-Push()ed buffer whose length was zeroed out at +// the start. +constexpr char kMidCrashBuffer[] = + "\x1e\x0d\xb0\xca\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x42" + "\x23"; +constexpr size_t kMidCrashBufferLen = + sizeof(kMidCrashBuffer) - 1; // Remove trailing NUL. + +constexpr uint8_t kHello[] = {0x68, 0x65, 0x6c, 0x6c, 0x6f}; + +TEST(LengthDelimitedRingBufferTest, + RingBufferDataShouldStartWithMagicAndVersion) { + RingBufferData ring_buffer; + const void* ring_buffer_bytes = static_cast(&ring_buffer); + EXPECT_THAT(memcmp(ring_buffer_bytes, "\x1e\x0d\xb0\xca\x01\x00\x00\x00", 8), + Eq(0)); +} + +TEST(LengthDelimitedRingBufferTest, + EmptyBufferSizeShouldIncludeHeaderInRingBufferLength) { + RingBufferData ring_buffer; + EXPECT_THAT(ring_buffer.GetRingBufferLength(), + Eq(16U)); // 4 for uint32 magic, 4 for uint32 version, 4 for + // uint32 read_pos, 4 for uint32 length +} + +TEST(LengthDelimitedRingBufferTest, + NonEmptyBufferSizeShouldIncludeHeaderAndData) { + RingBufferData ring_buffer; + LengthDelimitedRingBufferWriter writer(ring_buffer); + ASSERT_THAT(writer.Push(kHello, sizeof(kHello)), IsTrue()); + EXPECT_THAT(ring_buffer.GetRingBufferLength(), + Eq(22U)); // 16 for header, 1 for varint length, 5 for data +} + +TEST(LengthDelimitedRingBufferTest, PopOnEmptyBufferShouldFail) { + RingBufferData ring_buffer; + LengthDelimitedRingBufferReader reader(ring_buffer); + std::vector result; + EXPECT_THAT(reader.Pop(result), IsFalse()); +} + +TEST(LengthDelimitedRingBufferTest, PushZeroLengthShouldFail) { + RingBufferData ring_buffer; + LengthDelimitedRingBufferWriter writer(ring_buffer); + ASSERT_THAT(writer.Push(nullptr, 0), IsFalse()); +} + +TEST(LengthDelimitedRingBufferTest, PushExactlyBufferSizeThenPopShouldSucceed) { + RingBufferData ring_buffer; + LengthDelimitedRingBufferWriter writer(ring_buffer); + ASSERT_THAT(writer.Push(kHello, sizeof(kHello)), IsTrue()); + + LengthDelimitedRingBufferReader reader(ring_buffer); + std::vector result; + EXPECT_THAT(reader.Pop(result), IsTrue()); + const std::vector expected_first = {0x68, 0x65, 0x6c, 0x6c, 0x6f}; + EXPECT_THAT(result, Eq(expected_first)); +} + +TEST(LengthDelimitedRingBufferTest, PushLargerThanBufferSizeShouldFail) { + RingBufferData<4> ring_buffer; + LengthDelimitedRingBufferWriter writer(ring_buffer); + EXPECT_THAT(writer.Push(kHello, sizeof(kHello)), IsFalse()); +} + +TEST(LengthDelimitedRingBufferTest, + PushUntilFullThenPopUntilEmptyShouldReturnInFIFOOrder) { + RingBufferData<4> ring_buffer; + LengthDelimitedRingBufferWriter writer(ring_buffer); + constexpr uint8_t a = 0x41; + EXPECT_THAT(writer.Push(&a, sizeof(a)), + IsTrue()); // Writes 2 bytes (1 for length) + constexpr uint8_t b = 0x42; + EXPECT_THAT(writer.Push(&b, sizeof(b)), + IsTrue()); // Writes 2 bytes (1 for length) + + LengthDelimitedRingBufferReader reader(ring_buffer); + std::vector first; + EXPECT_THAT(reader.Pop(first), IsTrue()); + const std::vector expected_first = {0x41}; + EXPECT_THAT(first, Eq(expected_first)); + + std::vector second; + EXPECT_THAT(reader.Pop(second), IsTrue()); + const std::vector expected_second = {0x42}; + EXPECT_THAT(second, Eq(expected_second)); + + std::vector empty; + EXPECT_THAT(reader.Pop(empty), IsFalse()); +} + +TEST(LengthDelimitedRingBufferTest, + PushThenPopBuffersOfDifferingLengthsShouldReturnBuffers) { + RingBufferData<5> ring_buffer; + LengthDelimitedRingBufferWriter writer(ring_buffer); + constexpr uint8_t ab[2] = {0x41, 0x42}; + EXPECT_THAT(writer.Push(ab, sizeof(ab)), + IsTrue()); // Writes 3 bytes (1 for length) + constexpr uint8_t c = 0x43; + EXPECT_THAT(writer.Push(&c, sizeof(c)), + IsTrue()); // Writes 2 bytes (1 for length) + + LengthDelimitedRingBufferReader reader(ring_buffer); + std::vector first; + EXPECT_THAT(reader.Pop(first), IsTrue()); + const std::vector expected_first = {0x41, 0x42}; + EXPECT_THAT(first, Eq(expected_first)); + + std::vector second; + EXPECT_THAT(reader.Pop(second), IsTrue()); + const std::vector expected_second = {0x43}; + EXPECT_THAT(second, Eq(expected_second)); + + std::vector empty; + EXPECT_THAT(reader.Pop(empty), IsFalse()); +} + +TEST(LengthDelimitedRingBufferDataTest, PushOnFullBufferShouldOverwriteOldest) { + RingBufferData<4> ring_buffer; + LengthDelimitedRingBufferWriter writer(ring_buffer); + constexpr uint8_t a = 0x41; + EXPECT_THAT(writer.Push(&a, sizeof(a)), + IsTrue()); // Writes 2 bytes (1 for length) + constexpr uint8_t b = 0x42; + EXPECT_THAT(writer.Push(&b, sizeof(b)), + IsTrue()); // Writes 2 bytes (1 for length) + constexpr uint8_t c = 0x43; + EXPECT_THAT(writer.Push(&c, sizeof(c)), IsTrue()); // Should overwrite "A" + + LengthDelimitedRingBufferReader reader(ring_buffer); + std::vector first; + EXPECT_THAT(reader.Pop(first), IsTrue()); + const std::vector expected_first = {uint8_t{0x42}}; + EXPECT_THAT(first, Eq(expected_first)); + + std::vector second; + EXPECT_THAT(reader.Pop(second), IsTrue()); + const std::vector expected_second = {uint8_t{0x43}}; + EXPECT_THAT(second, Eq(expected_second)); +} + +TEST(LengthDelimitedRingBufferDataTest, + PushOnFullBufferShouldOverwriteMultipleOldest) { + RingBufferData<4> ring_buffer; + LengthDelimitedRingBufferWriter writer(ring_buffer); + constexpr uint8_t a = 0x41; + EXPECT_THAT(writer.Push(&a, sizeof(a)), + IsTrue()); // Writes 2 bytes (1 for length) + constexpr uint8_t b = 0x42; + EXPECT_THAT(writer.Push(&b, sizeof(b)), + IsTrue()); // Writes 2 bytes (1 for length) + constexpr uint8_t cd[] = {0x43, 0x44}; + EXPECT_THAT(writer.Push(cd, sizeof(cd)), + IsTrue()); // Needs 3 bytes; should overwrite "A" and "B" + + LengthDelimitedRingBufferReader reader(ring_buffer); + std::vector first; + EXPECT_THAT(reader.Pop(first), IsTrue()); + const std::vector expected_first = {0x43, 0x44}; + EXPECT_THAT(first, Eq(expected_first)); + + std::vector empty; + EXPECT_THAT(reader.Pop(empty), IsFalse()); +} + +TEST(LengthDelimitedRingBufferDataTest, PushThenPopWithLengthVarintTwoBytes) { + RingBufferData ring_buffer; + std::string s(150, 'X'); + LengthDelimitedRingBufferWriter writer(ring_buffer); + ASSERT_THAT(writer.Push(reinterpret_cast(s.c_str()), + static_cast(s.length())), + IsTrue()); + + LengthDelimitedRingBufferReader reader(ring_buffer); + std::vector first; + EXPECT_THAT(reader.Pop(first), IsTrue()); + std::string result(reinterpret_cast(first.data()), first.size()); + EXPECT_THAT(result, Eq(s)); +} + +TEST(LengthDelimitedRingBufferDataTest, DeserializeFromTooShortShouldFail) { + RingBufferData<1> ring_buffer; + EXPECT_THAT(ring_buffer.DeserializeFromBuffer(nullptr, 0), IsFalse()); +} + +TEST(LengthDelimitedRingBufferDataTest, DeserializeFromTooLongShouldFail) { + RingBufferData<1> ring_buffer; + // This buffer is size 3; it won't fit in the template arg (size 1). + EXPECT_THAT(ring_buffer.DeserializeFromBuffer( + reinterpret_cast(kValidBufferSize3), + kValidBufferSize3Len), + IsFalse()); +} + +TEST(LengthDelimitedRingBufferDataTest, + DeserializeFromInvalidVersionShouldFail) { + RingBufferData<3> ring_buffer; + EXPECT_THAT(ring_buffer.DeserializeFromBuffer( + reinterpret_cast(kInvalidVersionBuffer), + kInvalidVersionBufferLen), + IsFalse()); +} + +TEST(LengthDelimitedRingBufferDataTest, + DeserializeFromFullBufferShouldSucceed) { + RingBufferData<3> ring_buffer; + EXPECT_THAT(ring_buffer.DeserializeFromBuffer( + reinterpret_cast(kValidBufferSize3), + kValidBufferSize3Len), + IsTrue()); + LengthDelimitedRingBufferReader reader(ring_buffer); + std::vector data; + EXPECT_THAT(reader.Pop(data), IsTrue()); + const std::vector expected = {0x42, 0x23}; + EXPECT_THAT(data, Eq(expected)); +} + +TEST(LengthDelimitedRingBufferDataTest, + DeserializeFromMidCrashBufferShouldSucceedButSubsequentPopShouldFail) { + RingBufferData ring_buffer; + EXPECT_THAT(ring_buffer.DeserializeFromBuffer( + reinterpret_cast(kMidCrashBuffer), + kMidCrashBufferLen), + IsTrue()); + LengthDelimitedRingBufferReader reader(ring_buffer); + // Pop should fail since the length was written to be 0. + std::vector data; + EXPECT_THAT(reader.Pop(data), IsFalse()); +} + +} // namespace