• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #pragma once
15 
16 #include <cstddef>
17 #include <span>
18 
19 #include "pw_containers/intrusive_list.h"
20 #include "pw_status/status.h"
21 
22 namespace pw {
23 namespace ring_buffer {
24 
25 // A circular ring buffer for arbitrary length data entries. Each PushBack()
26 // produces a buffer entry. Each entry consists of a preamble followed by an
27 // arbitrary length data chunk. The preamble is comprised of an optional user
28 // preamble byte and an always present varint. The varint encodes the number of
29 // bytes in the data chunk. This is a FIFO queue, with the oldest entries at
30 // the 'front' (to be processed by readers) and the newest entries at the 'back'
31 // (where the writer pushes to).
32 //
33 // The ring buffer supports multiple readers, which can be attached/detached
34 // from the buffer. Each reader has its own read pointer and can peek and pop
35 // the entry at the head. Entries are not bumped out from the buffer until all
36 // readers have moved past that entry, or if the buffer is at capacity and space
37 // is needed to push a new entry. When making space, the buffer will push slow
38 // readers forward to the new oldest entry. Entries are internally wrapped
39 // around as needed.
40 class PrefixedEntryRingBufferMulti {
41  public:
42   typedef Status (*ReadOutput)(std::span<const std::byte>);
43 
44   // A reader that provides a single-reader interface into the multi-reader ring
45   // buffer it has been attached to via AttachReader(). Readers maintain their
46   // read position in the ring buffer as well as the remaining count of entries
47   // from that position. Readers are only able to consume entries that were
48   // pushed after the attach operation.
49   //
50   // Readers can peek and pop entries similar to the single-reader interface.
51   // When popping entries, although the reader moves forward and drops the
52   // entry, the entry is not removed from the ring buffer until all other
53   // attached readers have moved past that entry.
54   //
55   // When the attached ring buffer needs to make space, it may push the reader
56   // index forward. Users of this class should consider the possibility of data
57   // loss if they read slower than the writer.
58   class Reader : public IntrusiveList<Reader>::Item {
59    public:
Reader()60     constexpr Reader() : buffer(nullptr), read_idx(0), entry_count(0) {}
61 
62     // TODO(pwbug/344): Add locking to the internal functions. Who owns the
63     // lock? This class? Does this class need a lock if it's not a multi-reader?
64     // (One doesn't exist today but presumably nothing prevents push + pop
65     // operations from happening on two different threads).
66 
67     // Read the oldest stored data chunk of data from the ring buffer to
68     // the provided destination std::span. The number of bytes read is written
69     // to bytes_read
70     //
71     // Return values:
72     // OK - Data successfully read from the ring buffer.
73     // FAILED_PRECONDITION - Buffer not initialized.
74     // OUT_OF_RANGE - No entries in ring buffer to read.
75     // RESOURCE_EXHAUSTED - Destination data std::span was smaller number of
76     // bytes than the data size of the data chunk being read.  Available
77     // destination bytes were filled, remaining bytes of the data chunk were
78     // ignored.
PeekFront(std::span<std::byte> data,size_t * bytes_read_out)79     Status PeekFront(std::span<std::byte> data, size_t* bytes_read_out) {
80       return buffer->InternalPeekFront(*this, data, bytes_read_out);
81     }
82 
PeekFront(ReadOutput output)83     Status PeekFront(ReadOutput output) {
84       return buffer->InternalPeekFront(*this, output);
85     }
86 
87     // Same as PeekFront but includes the entry's preamble of optional user
88     // value and the varint of the data size.
89     // TODO(pwbug/341): Move all other APIs to passing bytes_read by reference,
90     // as it is required to determine the length populated in the span.
91     Status PeekFrontWithPreamble(std::span<std::byte> data,
92                                  uint32_t& user_preamble_out,
93                                  size_t& entry_bytes_read_out);
94 
PeekFrontWithPreamble(std::span<std::byte> data,size_t * bytes_read_out)95     Status PeekFrontWithPreamble(std::span<std::byte> data,
96                                  size_t* bytes_read_out) {
97       return buffer->InternalPeekFrontWithPreamble(*this, data, bytes_read_out);
98     }
99 
PeekFrontWithPreamble(ReadOutput output)100     Status PeekFrontWithPreamble(ReadOutput output) {
101       return buffer->InternalPeekFrontWithPreamble(*this, output);
102     }
103 
104     // Pop and discard the oldest stored data chunk of data from the ring
105     // buffer.
106     //
107     // Return values:
108     // OK - Data successfully read from the ring buffer.
109     // FAILED_PRECONDITION - Buffer not initialized.
110     // OUT_OF_RANGE - No entries in ring buffer to pop.
PopFront()111     Status PopFront() { return buffer->InternalPopFront(*this); }
112 
113     // Get the size in bytes of the next chunk, not including preamble, to be
114     // read.
FrontEntryDataSizeBytes()115     size_t FrontEntryDataSizeBytes() {
116       return buffer->InternalFrontEntryDataSizeBytes(*this);
117     }
118 
119     // Get the size in bytes of the next chunk, including preamble and data
120     // chunk, to be read.
FrontEntryTotalSizeBytes()121     size_t FrontEntryTotalSizeBytes() {
122       return buffer->InternalFrontEntryTotalSizeBytes(*this);
123     }
124 
125     // Get the number of variable-length entries currently in the ring buffer.
126     //
127     // Return value:
128     // Entry count.
EntryCount()129     size_t EntryCount() { return entry_count; }
130 
131    protected:
132     friend PrefixedEntryRingBufferMulti;
133 
134     PrefixedEntryRingBufferMulti* buffer;
135     size_t read_idx;
136     size_t entry_count;
137   };
138 
139   // TODO(pwbug/340): Consider changing bool to an enum, to explicitly enumerate
140   // what this variable means in clients.
141   PrefixedEntryRingBufferMulti(bool user_preamble = false)
buffer_(nullptr)142       : buffer_(nullptr),
143         buffer_bytes_(0),
144         write_idx_(0),
145         user_preamble_(user_preamble) {}
146 
147   // Set the raw buffer to be used by the ring buffer.
148   //
149   // Return values:
150   // OK - successfully set the raw buffer.
151   // INVALID_ARGUMENT - Argument was nullptr, size zero, or too large.
152   Status SetBuffer(std::span<std::byte> buffer);
153 
154   // Attach reader to the ring buffer. Readers can only be attached to one
155   // ring buffer at a time.
156   //
157   // Return values:
158   // OK - Successfully configured reader for ring buffer.
159   // INVALID_ARGUMENT - Argument was already attached to another ring buffer.
160   Status AttachReader(Reader& reader);
161 
162   // Detach reader from the ring buffer. Readers can only be detached if they
163   // were previously attached.
164   //
165   // Return values:
166   // OK - Successfully removed reader for ring buffer.
167   // INVALID_ARGUMENT - Argument was not previously attached to this ring
168   // buffer.
169   Status DetachReader(Reader& reader);
170 
171   // Removes all data from the ring buffer.
172   void Clear();
173 
174   // Write a chunk of data to the ring buffer. If available space is less than
175   // size of data chunk to be written then silently pop and discard oldest
176   // stored data chunks until space is available.
177   //
178   // Preamble argument is a caller-provided value prepended to the front of the
179   // entry. It is only used if user_preamble was set at class construction
180   // time. It is varint-encoded before insertion into the buffer.
181   //
182   // Return values:
183   // OK - Data successfully written to the ring buffer.
184   // INVALID_ARGUMENT - Size of data to write is zero bytes
185   // FAILED_PRECONDITION - Buffer not initialized.
186   // OUT_OF_RANGE - Size of data is greater than buffer size.
187   Status PushBack(std::span<const std::byte> data,
188                   uint32_t user_preamble_data = 0) {
189     return InternalPushBack(data, user_preamble_data, true);
190   }
191 
192   // [Deprecated] An implementation of PushBack that accepts a single-byte as
193   // preamble data. Clients should migrate to passing uint32_t preamble data.
PushBack(std::span<const std::byte> data,std::byte user_preamble_data)194   Status PushBack(std::span<const std::byte> data,
195                   std::byte user_preamble_data) {
196     return PushBack(data, static_cast<uint32_t>(user_preamble_data));
197   }
198 
199   // Write a chunk of data to the ring buffer if there is space available.
200   //
201   // Preamble argument is a caller-provided value prepended to the front of the
202   // entry. It is only used if user_preamble was set at class construction
203   // time. It is varint-encoded before insertion into the buffer.
204   //
205   // Return values:
206   // OK - Data successfully written to the ring buffer.
207   // INVALID_ARGUMENT - Size of data to write is zero bytes
208   // FAILED_PRECONDITION - Buffer not initialized.
209   // OUT_OF_RANGE - Size of data is greater than buffer size.
210   // RESOURCE_EXHAUSTED - The ring buffer doesn't have space for the data
211   // without popping off existing elements.
212   Status TryPushBack(std::span<const std::byte> data,
213                      uint32_t user_preamble_data = 0) {
214     return InternalPushBack(data, user_preamble_data, false);
215   }
216 
217   // [Deprecated] An implementation of TryPushBack that accepts a single-byte as
218   // preamble data. Clients should migrate to passing uint32_t preamble data.
TryPushBack(std::span<const std::byte> data,std::byte user_preamble_data)219   Status TryPushBack(std::span<const std::byte> data,
220                      std::byte user_preamble_data) {
221     return TryPushBack(data, static_cast<uint32_t>(user_preamble_data));
222   }
223 
224   // Get the size in bytes of all the current entries in the ring buffer,
225   // including preamble and data chunk.
TotalUsedBytes()226   size_t TotalUsedBytes() { return buffer_bytes_ - RawAvailableBytes(); }
227 
228   // Dering the buffer by reordering entries internally in the buffer by
229   // rotating to have the oldest entry is at the lowest address/index with
230   // newest entry at the highest address.
231   //
232   // Return values:
233   // OK - Buffer data successfully deringed.
234   // FAILED_PRECONDITION - Buffer not initialized, or no readers attached.
235   Status Dering();
236 
237  protected:
238   // Read the oldest stored data chunk of data from the ring buffer to
239   // the provided destination std::span. The number of bytes read is written to
240   // `bytes_read_out`.
241   //
242   // Return values:
243   // OK - Data successfully read from the ring buffer.
244   // FAILED_PRECONDITION - Buffer not initialized.
245   // OUT_OF_RANGE - No entries in ring buffer to read.
246   // RESOURCE_EXHAUSTED - Destination data std::span was smaller number of bytes
247   // than the data size of the data chunk being read.  Available destination
248   // bytes were filled, remaining bytes of the data chunk were ignored.
249   Status InternalPeekFront(Reader& reader,
250                            std::span<std::byte> data,
251                            size_t* bytes_read_out);
252   Status InternalPeekFront(Reader& reader, ReadOutput output);
253 
254   // Same as Read but includes the entry's preamble of optional user value and
255   // the varint of the data size
256   Status InternalPeekFrontWithPreamble(Reader& reader,
257                                        std::span<std::byte> data,
258                                        size_t* bytes_read_out);
259   Status InternalPeekFrontWithPreamble(Reader& reader, ReadOutput output);
260 
261   // Pop and discard the oldest stored data chunk of data from the ring buffer.
262   //
263   // Return values:
264   // OK - Data successfully read from the ring buffer.
265   // FAILED_PRECONDITION - Buffer not initialized.
266   // OUT_OF_RANGE - No entries in ring buffer to pop.
267   Status InternalPopFront(Reader& reader);
268 
269   // Get the size in bytes of the next chunk, not including preamble, to be
270   // read.
271   size_t InternalFrontEntryDataSizeBytes(Reader& reader);
272 
273   // Get the size in bytes of the next chunk, including preamble and data
274   // chunk, to be read.
275   size_t InternalFrontEntryTotalSizeBytes(Reader& reader);
276 
277   // Internal version of Read used by all the public interface versions. T
278   // should be of type ReadOutput.
279   template <typename T>
280   Status InternalRead(Reader& reader,
281                       T read_output,
282                       bool include_preamble_in_output,
283                       uint32_t* user_preamble_out = nullptr);
284 
285  private:
286   struct EntryInfo {
287     size_t preamble_bytes;
288     uint32_t user_preamble;
289     size_t data_bytes;
290   };
291 
292   // Push back implementation, which optionally discards front elements to fit
293   // the incoming element.
294   Status InternalPushBack(std::span<const std::byte> data,
295                           uint32_t user_preamble_data,
296                           bool pop_front_if_needed);
297 
298   // Internal function to pop all of the slowest readers. This function may pop
299   // multiple readers if multiple are slow.
300   //
301   // Precondition: This function requires that at least one reader is attached
302   // and has at least one entry to pop.
303   void InternalPopFrontAll();
304 
305   // Returns the slowest reader in the list.
306   //
307   // Precondition: This function requires that at least one reader is attached.
308   Reader& GetSlowestReader();
309 
310   // Get info struct with the size of the preamble and data chunk for the next
311   // entry to be read.
312   EntryInfo FrontEntryInfo(Reader& reader);
313 
314   // Get the raw number of available bytes free in the ring buffer. This is
315   // not available bytes for data, since there is a variable size preamble for
316   // each entry.
317   size_t RawAvailableBytes();
318 
319   // Do the basic write of the specified number of bytes starting at the last
320   // write index of the ring buffer to the destination, handing any wrap-around
321   // of the ring buffer. This is basic, raw operation with no safety checks.
322   void RawWrite(std::span<const std::byte> source);
323 
324   // Do the basic read of the specified number of bytes starting at the given
325   // index of the ring buffer to the destination, handing any wrap-around of
326   // the ring buffer. This is basic, raw operation with no safety checks.
327   void RawRead(std::byte* destination, size_t source_idx, size_t length_bytes);
328 
329   size_t IncrementIndex(size_t index, size_t count);
330 
331   std::byte* buffer_;
332   size_t buffer_bytes_;
333 
334   size_t write_idx_;
335   const bool user_preamble_;
336 
337   // List of attached readers.
338   IntrusiveList<Reader> readers_;
339 
340   // Maximum bufer size allowed. Restricted to this to allow index aliasing to
341   // not overflow.
342   static constexpr size_t kMaxBufferBytes =
343       std::numeric_limits<size_t>::max() / 2;
344 };
345 
346 class PrefixedEntryRingBuffer : public PrefixedEntryRingBufferMulti,
347                                 public PrefixedEntryRingBufferMulti::Reader {
348  public:
349   PrefixedEntryRingBuffer(bool user_preamble = false)
PrefixedEntryRingBufferMulti(user_preamble)350       : PrefixedEntryRingBufferMulti(user_preamble) {
351     AttachReader(*this);
352   }
353 };
354 
355 }  // namespace ring_buffer
356 }  // namespace pw
357