mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-09 14:06:33 +00:00
[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 <rsesek@chromium.org> Commit-Queue: Ben Hamilton <benhamilton@google.com>
This commit is contained in:
parent
f7b5e00268
commit
1a7918b716
@ -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",
|
||||
|
545
client/length_delimited_ring_buffer.h
Normal file
545
client/length_delimited_ring_buffer.h
Normal file
@ -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 <algorithm>
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
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 <uint32_t RingBufferCapacity>
|
||||
using RingBufferArray = std::array<uint8_t, RingBufferCapacity>;
|
||||
|
||||
//! \return The size of the `RingBufferArray` as a `uint32_t`.
|
||||
template <size_t RingBufferCapacity>
|
||||
constexpr uint32_t RingBufferArraySize(
|
||||
const RingBufferArray<RingBufferCapacity>& ring_buffer_data) {
|
||||
static_assert(RingBufferCapacity <= std::numeric_limits<uint32_t>::max());
|
||||
return static_cast<uint32_t>(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 <typename RingBufferArrayType>
|
||||
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 <typename RingBufferArrayType>
|
||||
std::optional<int> 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<uint32_t>(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 <typename RingBufferArrayType>
|
||||
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 <typename RingBufferArrayType>
|
||||
std::optional<int> 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<uint8_t>(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<uint8_t>(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 <uint32_t RingBufferCapacity>
|
||||
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<const Header*>(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<const uint8_t*>(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<RingBufferCapacity> data;
|
||||
|
||||
static_assert(sizeof(Header) == 16);
|
||||
static_assert(RingBufferCapacity <= std::numeric_limits<uint32_t>::max());
|
||||
};
|
||||
|
||||
// Ensure the ring buffer is packed correctly at its default capacity.
|
||||
static_assert(sizeof(RingBufferData<internal::kDefaultRingBufferCapacity>) ==
|
||||
16 + internal::kDefaultRingBufferCapacity);
|
||||
|
||||
// Allow just `RingBufferData foo;` to be declared without template arguments
|
||||
// using CTAD.
|
||||
template <uint32_t Capacity = internal::kDefaultRingBufferCapacity>
|
||||
RingBufferData() -> RingBufferData<Capacity>;
|
||||
|
||||
//! \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 <typename RingBufferDataType>
|
||||
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<uint8_t>& 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<uint8_t>& 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 <typename RingBufferDataType>
|
||||
LengthDelimitedRingBufferReader(RingBufferDataType&)
|
||||
-> LengthDelimitedRingBufferReader<RingBufferDataType>;
|
||||
|
||||
//! \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 <typename RingBufferDataType>
|
||||
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<int> 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<const uint8_t* const>(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 <typename RingBufferDataType>
|
||||
LengthDelimitedRingBufferWriter(RingBufferDataType&)
|
||||
-> LengthDelimitedRingBufferWriter<RingBufferDataType>;
|
||||
|
||||
} // namespace crashpad
|
||||
|
||||
#endif // CRASHPAD_CLIENT_LENGTH_DELIMITED_RING_BUFFER_H_
|
284
client/length_delimited_ring_buffer_test.cc
Normal file
284
client/length_delimited_ring_buffer_test.cc
Normal file
@ -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 <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#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<const void*>(&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<uint8_t> 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<uint8_t> result;
|
||||
EXPECT_THAT(reader.Pop(result), IsTrue());
|
||||
const std::vector<uint8_t> 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<uint8_t> first;
|
||||
EXPECT_THAT(reader.Pop(first), IsTrue());
|
||||
const std::vector<uint8_t> expected_first = {0x41};
|
||||
EXPECT_THAT(first, Eq(expected_first));
|
||||
|
||||
std::vector<uint8_t> second;
|
||||
EXPECT_THAT(reader.Pop(second), IsTrue());
|
||||
const std::vector<uint8_t> expected_second = {0x42};
|
||||
EXPECT_THAT(second, Eq(expected_second));
|
||||
|
||||
std::vector<uint8_t> 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<uint8_t> first;
|
||||
EXPECT_THAT(reader.Pop(first), IsTrue());
|
||||
const std::vector<uint8_t> expected_first = {0x41, 0x42};
|
||||
EXPECT_THAT(first, Eq(expected_first));
|
||||
|
||||
std::vector<uint8_t> second;
|
||||
EXPECT_THAT(reader.Pop(second), IsTrue());
|
||||
const std::vector<uint8_t> expected_second = {0x43};
|
||||
EXPECT_THAT(second, Eq(expected_second));
|
||||
|
||||
std::vector<uint8_t> 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<uint8_t> first;
|
||||
EXPECT_THAT(reader.Pop(first), IsTrue());
|
||||
const std::vector<uint8_t> expected_first = {uint8_t{0x42}};
|
||||
EXPECT_THAT(first, Eq(expected_first));
|
||||
|
||||
std::vector<uint8_t> second;
|
||||
EXPECT_THAT(reader.Pop(second), IsTrue());
|
||||
const std::vector<uint8_t> 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<uint8_t> first;
|
||||
EXPECT_THAT(reader.Pop(first), IsTrue());
|
||||
const std::vector<uint8_t> expected_first = {0x43, 0x44};
|
||||
EXPECT_THAT(first, Eq(expected_first));
|
||||
|
||||
std::vector<uint8_t> 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<const uint8_t*>(s.c_str()),
|
||||
static_cast<uint32_t>(s.length())),
|
||||
IsTrue());
|
||||
|
||||
LengthDelimitedRingBufferReader reader(ring_buffer);
|
||||
std::vector<uint8_t> first;
|
||||
EXPECT_THAT(reader.Pop(first), IsTrue());
|
||||
std::string result(reinterpret_cast<const char*>(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<const uint8_t*>(kValidBufferSize3),
|
||||
kValidBufferSize3Len),
|
||||
IsFalse());
|
||||
}
|
||||
|
||||
TEST(LengthDelimitedRingBufferDataTest,
|
||||
DeserializeFromInvalidVersionShouldFail) {
|
||||
RingBufferData<3> ring_buffer;
|
||||
EXPECT_THAT(ring_buffer.DeserializeFromBuffer(
|
||||
reinterpret_cast<const uint8_t*>(kInvalidVersionBuffer),
|
||||
kInvalidVersionBufferLen),
|
||||
IsFalse());
|
||||
}
|
||||
|
||||
TEST(LengthDelimitedRingBufferDataTest,
|
||||
DeserializeFromFullBufferShouldSucceed) {
|
||||
RingBufferData<3> ring_buffer;
|
||||
EXPECT_THAT(ring_buffer.DeserializeFromBuffer(
|
||||
reinterpret_cast<const uint8_t*>(kValidBufferSize3),
|
||||
kValidBufferSize3Len),
|
||||
IsTrue());
|
||||
LengthDelimitedRingBufferReader reader(ring_buffer);
|
||||
std::vector<uint8_t> data;
|
||||
EXPECT_THAT(reader.Pop(data), IsTrue());
|
||||
const std::vector<uint8_t> expected = {0x42, 0x23};
|
||||
EXPECT_THAT(data, Eq(expected));
|
||||
}
|
||||
|
||||
TEST(LengthDelimitedRingBufferDataTest,
|
||||
DeserializeFromMidCrashBufferShouldSucceedButSubsequentPopShouldFail) {
|
||||
RingBufferData ring_buffer;
|
||||
EXPECT_THAT(ring_buffer.DeserializeFromBuffer(
|
||||
reinterpret_cast<const uint8_t*>(kMidCrashBuffer),
|
||||
kMidCrashBufferLen),
|
||||
IsTrue());
|
||||
LengthDelimitedRingBufferReader reader(ring_buffer);
|
||||
// Pop should fail since the length was written to be 0.
|
||||
std::vector<uint8_t> data;
|
||||
EXPECT_THAT(reader.Pop(data), IsFalse());
|
||||
}
|
||||
|
||||
} // namespace
|
Loading…
x
Reference in New Issue
Block a user