pw_ring_buffer: Add TryPushBack()

This adds a PushBack variant that will only push if there is space in
the buffer, and will not evict items from the front.

Change-Id: I393d7f5703c1cf5853d2192cb390836839d565e4
diff --git a/pw_ring_buffer/BUILD.gn b/pw_ring_buffer/BUILD.gn
index f51a094..0c3bedb 100644
--- a/pw_ring_buffer/BUILD.gn
+++ b/pw_ring_buffer/BUILD.gn
@@ -36,7 +36,10 @@
 }
 
 pw_test("prefixed_entry_ring_buffer_test") {
-  deps = [ ":pw_ring_buffer" ]
+  deps = [
+    ":pw_ring_buffer",
+    "$dir_pw_assert:pw_assert",
+  ]
   sources = [ "prefixed_entry_ring_buffer_test.cc" ]
 }
 
diff --git a/pw_ring_buffer/prefixed_entry_ring_buffer.cc b/pw_ring_buffer/prefixed_entry_ring_buffer.cc
index dd475ff..fac8e6e 100644
--- a/pw_ring_buffer/prefixed_entry_ring_buffer.cc
+++ b/pw_ring_buffer/prefixed_entry_ring_buffer.cc
@@ -43,8 +43,9 @@
   return Status::OK;
 }
 
-Status PrefixedEntryRingBuffer::PushBack(span<const byte> data,
-                                         byte user_preamble_data) {
+Status PrefixedEntryRingBuffer::InternalPushBack(span<const byte> data,
+                                                 byte user_preamble_data,
+                                                 bool drop_elements_if_needed) {
   if (buffer_ == nullptr) {
     return Status::FAILED_PRECONDITION;
   }
@@ -61,9 +62,15 @@
     return Status::OUT_OF_RANGE;
   }
 
-  // Drop old entries until we have space for the new entry.
-  while (RawAvailableBytes() < total_write_bytes) {
-    PopFront();
+  if (drop_elements_if_needed) {
+    // PushBack() case: evict items as needed.
+    // Drop old entries until we have space for the new entry.
+    while (RawAvailableBytes() < total_write_bytes) {
+      PopFront();
+    }
+  } else if (RawAvailableBytes() < total_write_bytes) {
+    // TryPushBack() case: don't evict items.
+    return Status::RESOURCE_EXHAUSTED;
   }
 
   // Write the new entry into the ring buffer.
diff --git a/pw_ring_buffer/prefixed_entry_ring_buffer_test.cc b/pw_ring_buffer/prefixed_entry_ring_buffer_test.cc
index db5c14c..aad8fac 100644
--- a/pw_ring_buffer/prefixed_entry_ring_buffer_test.cc
+++ b/pw_ring_buffer/prefixed_entry_ring_buffer_test.cc
@@ -17,6 +17,7 @@
 #include <cstddef>
 #include <cstdint>
 
+#include "pw_assert/assert.h"
 #include "pw_containers/vector.h"
 #include "pw_unit_test/framework.h"
 
@@ -362,6 +363,70 @@
 TEST(PrefixedEntryRingBuffer, Dering) { DeringTest(true); }
 TEST(PrefixedEntryRingBuffer, DeringNoPreload) { DeringTest(false); }
 
+template <typename T>
+Status PushBack(PrefixedEntryRingBuffer& ring, T element) {
+  union {
+    std::array<byte, sizeof(element)> buffer;
+    T item;
+  } aliased;
+  aliased.item = element;
+  return ring.PushBack(span(aliased.buffer));
+}
+
+template <typename T>
+Status TryPushBack(PrefixedEntryRingBuffer& ring, T element) {
+  union {
+    std::array<byte, sizeof(element)> buffer;
+    T item;
+  } aliased;
+  aliased.item = element;
+  return ring.TryPushBack(span(aliased.buffer));
+}
+
+template <typename T>
+T PeekFront(PrefixedEntryRingBuffer& ring) {
+  union {
+    std::array<byte, sizeof(T)> buffer;
+    T item;
+  } aliased;
+  size_t bytes_read = 0;
+  PW_CHECK_INT_EQ(ring.PeekFront(span(aliased.buffer), &bytes_read),
+                  Status::OK);
+  PW_CHECK_INT_EQ(bytes_read, sizeof(T));
+  return aliased.item;
+}
+
+TEST(PrefixedEntryRingBuffer, TryPushBack) {
+  PrefixedEntryRingBuffer ring;
+  byte test_buffer[kTestBufferSize];
+  EXPECT_EQ(ring.SetBuffer(test_buffer), Status::OK);
+
+  // Fill up the ring buffer with a constant.
+  int total_items = 0;
+  while (true) {
+    Status status = TryPushBack<int>(ring, 5);
+    if (status.ok()) {
+      total_items++;
+    } else {
+      EXPECT_EQ(status, Status::RESOURCE_EXHAUSTED);
+      break;
+    }
+  }
+  EXPECT_EQ(PeekFront<int>(ring), 5);
+
+  // Should be unable to push more items.
+  for (int i = 0; i < total_items; ++i) {
+    EXPECT_EQ(TryPushBack<int>(ring, 100), Status::RESOURCE_EXHAUSTED);
+    EXPECT_EQ(PeekFront<int>(ring), 5);
+  }
+
+  // Fill up the ring buffer with a constant.
+  for (int i = 0; i < total_items; ++i) {
+    EXPECT_EQ(PushBack<int>(ring, 100), Status::OK);
+  }
+  EXPECT_EQ(PeekFront<int>(ring), 100);
+}
+
 }  // namespace
 }  // namespace ring_buffer
 }  // namespace pw
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 902a310..f87c44f 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
@@ -23,9 +23,9 @@
 
 // A circular ring buffer for arbitrary length data entries. Each PushBack()
 // produces a buffer entry. Each entry consists of a preamble followed by an
-// arbitrary length data chunk/blob. The preamble is comprised of an optional
-// user preamble byte and an always present varint. The varint encodes the
-// number of bytes in the data chunk/blob.
+// arbitrary length data chunk. The preamble is comprised of an optional user
+// preamble byte and an always present varint. The varint encodes the number of
+// bytes in the data chunk.
 //
 // The ring buffer holds the most recent entries stored in the buffer. Once
 // filled to capacity, incoming entries bump out the oldest entries to make
@@ -52,8 +52,8 @@
   // Removes all data from the ring buffer.
   void Clear();
 
-  // Write a chunk/blob of data to the ring buffer. If available space is less
-  // than size of data chunk to be written then silently pop and discard oldest
+  // Write a chunk of data to the ring buffer. If available space is less than
+  // size of data chunk to be written then silently pop and discard oldest
   // stored data chunks until space is available.
   //
   // Preamble argument is a caller-provided value prepended to the front of the
@@ -66,9 +66,29 @@
   // FAILED_PRECONDITION - Buffer not initialized.
   // OUT_OF_RANGE - Size of data is greater than buffer size.
   Status PushBack(span<const std::byte> data,
-                  std::byte user_preamble_data = std::byte(0));
+                  std::byte user_preamble_data = std::byte(0)) {
+    return InternalPushBack(data, user_preamble_data, true);
+  }
 
-  // Read the oldest stored data chunk/blob of data from the ring buffer to
+  // 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.
+  //
+  // Return values:
+  // OK - Data successfully written to the ring buffer.
+  // INVALID_ARGUMENT - Size of data to write is zero bytes
+  // FAILED_PRECONDITION - Buffer not initialized.
+  // OUT_OF_RANGE - Size of data is greater than buffer size.
+  // RESOURCE_EXHAUSTED - The ring buffer doesn't have space for the data
+  // without popping off existing elements.
+  Status TryPushBack(span<const std::byte> data,
+                     std::byte user_preamble_data = std::byte(0)) {
+    return InternalPushBack(data, user_preamble_data, false);
+  }
+
+  // Read the oldest stored data chunk of data from the ring buffer to
   // the provided destination span. The number of bytes read is written to
   // bytes_read
   //
@@ -89,8 +109,7 @@
 
   Status PeekFrontWithPreamble(ReadOutput output);
 
-  // Pop and discard the oldest stored data chunk/blob of data from the ring
-  // buffer.
+  // Pop and discard the oldest stored data chunk of data from the ring buffer.
   //
   // Return values:
   // OK - Data successfully read from the ring buffer.
@@ -114,15 +133,15 @@
   size_t EntryCount() { return entry_count_; }
 
   // Get the size in bytes of all the current entries in the ring buffer,
-  // including preamble and data chunk/blob.
+  // including preamble and data chunk.
   size_t TotalUsedBytes() { return buffer_bytes_ - RawAvailableBytes(); }
 
-  // Get the size in bytes of the next data chunk/blob, not including
-  // preamble, to be read.
+  // Get the size in bytes of the next chunk, not including preamble, to be
+  // read.
   size_t FrontEntryDataSizeBytes();
 
-  // Get the size in bytes of the next entry, including preamble and data
-  // chunk/blob, to be read.
+  // Get the size in bytes of the next chunk, including preamble and data
+  // chunk, to be read.
   size_t FrontEntryTotalSizeBytes();
 
  private:
@@ -136,8 +155,14 @@
   template <typename T>
   Status InternalRead(T read_output, bool get_preamble);
 
-  // Get info struct with the size of the preamble and data chunk/blob for the
-  // next entry to be read.
+  // Push back implementation, which optionally discards front elements to fit
+  // the incoming element.
+  Status InternalPushBack(span<const std::byte> data,
+                          std::byte user_preamble_data,
+                          bool pop_front_if_needed);
+
+  // Get info struct with the size of the preamble and data chunk for the next
+  // entry to be read.
   EntryInfo FrontEntryInfo();
 
   // Get the raw number of available bytes free in the ring buffer. This is