pw_ring_buffer: Varint encode preamble byte
Preamble bytes act as field keys to permit ring buffers to be
proto-compliant after a dering operation. Allow passing in field keys
larger than a single byte and varint-encode them before writing.
Change-Id: I4def047c035fcaa94312e2c156f32694aa6bf9c8
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/35340
Reviewed-by: Ewout van Bekkum <ewout@google.com>
Commit-Queue: Prashanth Swaminathan <prashanthsw@google.com>
diff --git a/pw_log_multisink/log_queue.cc b/pw_log_multisink/log_queue.cc
index 6a528ea..8fbc42c 100644
--- a/pw_log_multisink/log_queue.cc
+++ b/pw_log_multisink/log_queue.cc
@@ -23,9 +23,9 @@
namespace {
using pw::protobuf::WireType;
-constexpr std::byte kLogKey = static_cast<std::byte>(pw::protobuf::MakeKey(
+constexpr uint32_t kLogKey = pw::protobuf::MakeKey(
static_cast<uint32_t>(pw::log::LogEntries::Fields::ENTRIES),
- WireType::kDelimited));
+ WireType::kDelimited);
} // namespace
@@ -66,7 +66,7 @@
status = PW_STATUS_INTERNAL;
} else {
// Try to push back the encoded log entry.
- status = ring_buffer_.TryPushBack(log_entry, std::byte(kLogKey));
+ status = ring_buffer_.TryPushBack(log_entry, kLogKey);
}
if (!status.ok()) {
diff --git a/pw_ring_buffer/prefixed_entry_ring_buffer.cc b/pw_ring_buffer/prefixed_entry_ring_buffer.cc
index 8758240..1f49678 100644
--- a/pw_ring_buffer/prefixed_entry_ring_buffer.cc
+++ b/pw_ring_buffer/prefixed_entry_ring_buffer.cc
@@ -75,7 +75,7 @@
Status PrefixedEntryRingBufferMulti::InternalPushBack(
std::span<const byte> data,
- byte user_preamble_data,
+ uint32_t user_preamble_data,
bool drop_elements_if_needed) {
if (buffer_ == nullptr) {
return Status::FailedPrecondition();
@@ -85,10 +85,17 @@
}
// Prepare the preamble, and ensure we can fit the preamble and entry.
- byte varint_buf[kMaxEntryPreambleBytes];
- size_t varint_bytes = varint::Encode<size_t>(data.size_bytes(), varint_buf);
+ byte preamble_buf[varint::kMaxVarint32SizeBytes];
+ byte length_buf[varint::kMaxVarint32SizeBytes];
+
+ size_t user_preamble_bytes = 0;
+ if (user_preamble_) {
+ user_preamble_bytes =
+ varint::Encode<uint32_t>(user_preamble_data, preamble_buf);
+ }
+ size_t length_bytes = varint::Encode<uint32_t>(data.size_bytes(), length_buf);
size_t total_write_bytes =
- (user_preamble_ ? 1 : 0) + varint_bytes + data.size_bytes();
+ user_preamble_bytes + length_bytes + data.size_bytes();
if (buffer_bytes_ < total_write_bytes) {
return Status::OutOfRange();
}
@@ -105,10 +112,12 @@
}
// Write the new entry into the ring buffer.
+ // TODO(pwbug/337): This could be more efficient, the preamble and length
+ // data could be written in a single raw write call.
if (user_preamble_) {
- RawWrite(std::span(&user_preamble_data, sizeof(user_preamble_data)));
+ RawWrite(std::span(preamble_buf, user_preamble_bytes));
}
- RawWrite(std::span(varint_buf, varint_bytes));
+ RawWrite(std::span(length_buf, length_bytes));
RawWrite(data);
// Update all readers of the new count.
@@ -315,17 +324,27 @@
PrefixedEntryRingBufferMulti::FrontEntryInfo(Reader& reader) {
// Entry headers consists of: (optional prefix byte, varint size, data...)
- // Read the entry header; extract the varint and it's size in bytes.
- byte varint_buf[kMaxEntryPreambleBytes];
+ // If a preamble exists, extract the varint and it's bytes in bytes.
+ size_t user_preamble_bytes = 0;
+ byte varint_buf[varint::kMaxVarint32SizeBytes];
+ if (user_preamble_) {
+ RawRead(varint_buf, reader.read_idx, varint::kMaxVarint32SizeBytes);
+ uint64_t user_preamble_data;
+ user_preamble_bytes = varint::Decode(varint_buf, &user_preamble_data);
+ PW_DASSERT(user_preamble_bytes != 0u);
+ }
+
+ // Read the entry header; extract the varint and it's bytes in bytes.
RawRead(varint_buf,
- IncrementIndex(reader.read_idx, user_preamble_ ? 1 : 0),
- kMaxEntryPreambleBytes);
- uint64_t entry_size;
- size_t varint_size = varint::Decode(varint_buf, &entry_size);
+ IncrementIndex(reader.read_idx, user_preamble_bytes),
+ varint::kMaxVarint32SizeBytes);
+ uint64_t entry_bytes;
+ size_t length_bytes = varint::Decode(varint_buf, &entry_bytes);
+ PW_DASSERT(length_bytes != 0u);
EntryInfo info = {};
- info.preamble_bytes = (user_preamble_ ? 1 : 0) + varint_size;
- info.data_bytes = entry_size;
+ info.preamble_bytes = user_preamble_bytes + length_bytes;
+ info.data_bytes = entry_bytes;
return info;
}
diff --git a/pw_ring_buffer/prefixed_entry_ring_buffer_test.cc b/pw_ring_buffer/prefixed_entry_ring_buffer_test.cc
index 9b3f15f..1275801 100644
--- a/pw_ring_buffer/prefixed_entry_ring_buffer_test.cc
+++ b/pw_ring_buffer/prefixed_entry_ring_buffer_test.cc
@@ -114,8 +114,13 @@
ASSERT_EQ(ring.FrontEntryDataSizeBytes(), 0u);
ASSERT_EQ(ring.FrontEntryTotalSizeBytes(), 0u);
- ASSERT_EQ(ring.PushBack(std::span(single_entry_data, data_size), byte(i)),
- OkStatus());
+ // Limit the value of the preamble to a single byte, to ensure that we
+ // retain a static `single_entry_buffer_size` during the test. Single
+ // bytes are varint-encoded to the same value.
+ uint32_t preamble_byte = i % 128;
+ ASSERT_EQ(
+ ring.PushBack(std::span(single_entry_data, data_size), preamble_byte),
+ OkStatus());
ASSERT_EQ(ring.FrontEntryDataSizeBytes(), data_size);
ASSERT_EQ(ring.FrontEntryTotalSizeBytes(), single_entry_total_size);
@@ -136,7 +141,7 @@
ASSERT_EQ(ring.PopFront(), OkStatus());
if (user_data) {
- expect_buffer[0] = byte(i);
+ expect_buffer[0] = byte(preamble_byte);
}
// ASSERT_THAT(std::span(expect_buffer),
@@ -242,8 +247,13 @@
ASSERT_EQ(ring.FrontEntryDataSizeBytes(), 0u);
ASSERT_EQ(ring.FrontEntryTotalSizeBytes(), 0u);
- ASSERT_EQ(ring.PushBack(std::span(single_entry_data, data_size), byte(i)),
- OkStatus());
+ // Limit the value of the preamble to a single byte, to ensure that we
+ // retain a static `single_entry_buffer_size` during the test. Single
+ // bytes are varint-encoded to the same value.
+ uint32_t preamble_byte = i % 128;
+ ASSERT_EQ(
+ ring.PushBack(std::span(single_entry_data, data_size), preamble_byte),
+ OkStatus());
ASSERT_EQ(ring.FrontEntryDataSizeBytes(), data_size);
ASSERT_EQ(ring.FrontEntryTotalSizeBytes(), single_entry_total_size);
@@ -262,7 +272,7 @@
ASSERT_EQ(ring.PopFront(), OkStatus());
if (user_data) {
- expect_buffer[0] = byte(i);
+ expect_buffer[0] = byte(preamble_byte);
}
ASSERT_EQ(
diff --git a/pw_ring_buffer/public/pw_ring_buffer/prefixed_entry_ring_buffer.h b/pw_ring_buffer/public/pw_ring_buffer/prefixed_entry_ring_buffer.h
index ee6b393..c9d8e66 100644
--- a/pw_ring_buffer/public/pw_ring_buffer/prefixed_entry_ring_buffer.h
+++ b/pw_ring_buffer/public/pw_ring_buffer/prefixed_entry_ring_buffer.h
@@ -169,7 +169,7 @@
//
// Preamble argument is a caller-provided value prepended to the front of the
// entry. It is only used if user_preamble was set at class construction
- // time.
+ // time. It is varint-encoded before insertion into the buffer.
//
// Return values:
// OK - Data successfully written to the ring buffer.
@@ -177,15 +177,22 @@
// FAILED_PRECONDITION - Buffer not initialized.
// OUT_OF_RANGE - Size of data is greater than buffer size.
Status PushBack(std::span<const std::byte> data,
- std::byte user_preamble_data = std::byte(0)) {
+ uint32_t user_preamble_data = 0) {
return InternalPushBack(data, user_preamble_data, true);
}
+ // [Deprecated] An implementation of PushBack that accepts a single-byte as
+ // preamble data. Clients should migrate to passing uint32_t preamble data.
+ Status PushBack(std::span<const std::byte> data,
+ std::byte user_preamble_data) {
+ return PushBack(data, static_cast<uint32_t>(user_preamble_data));
+ }
+
// Write a chunk of data to the ring buffer if there is space available.
//
// Preamble argument is a caller-provided value prepended to the front of the
// entry. It is only used if user_preamble was set at class construction
- // time.
+ // time. It is varint-encoded before insertion into the buffer.
//
// Return values:
// OK - Data successfully written to the ring buffer.
@@ -195,10 +202,17 @@
// RESOURCE_EXHAUSTED - The ring buffer doesn't have space for the data
// without popping off existing elements.
Status TryPushBack(std::span<const std::byte> data,
- std::byte user_preamble_data = std::byte(0)) {
+ uint32_t user_preamble_data = 0) {
return InternalPushBack(data, user_preamble_data, false);
}
+ // [Deprecated] An implementation of TryPushBack that accepts a single-byte as
+ // preamble data. Clients should migrate to passing uint32_t preamble data.
+ Status TryPushBack(std::span<const std::byte> data,
+ std::byte user_preamble_data) {
+ return TryPushBack(data, static_cast<uint32_t>(user_preamble_data));
+ }
+
// Get the size in bytes of all the current entries in the ring buffer,
// including preamble and data chunk.
size_t TotalUsedBytes() { return buffer_bytes_ - RawAvailableBytes(); }
@@ -266,7 +280,7 @@
// Push back implementation, which optionally discards front elements to fit
// the incoming element.
Status InternalPushBack(std::span<const std::byte> data,
- std::byte user_preamble_data,
+ uint32_t user_preamble_data,
bool pop_front_if_needed);
// Internal function to pop all of the slowest readers. This function may pop
@@ -311,10 +325,6 @@
// List of attached readers.
IntrusiveList<Reader> readers_;
- // Worst case size for the variable-sized preable that is prepended to
- // each entry.
- static constexpr size_t kMaxEntryPreambleBytes = sizeof(size_t) + 1;
-
// Maximum bufer size allowed. Restricted to this to allow index aliasing to
// not overflow.
static constexpr size_t kMaxBufferBytes =