blob: b7b4706318468713972f5f584ff61048496cc885 [file] [log] [blame]
// Copyright 2020 The Pigweed 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
//
// https://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.
#pragma once
#include <cstddef>
#include <span>
#include "pw_containers/intrusive_list.h"
#include "pw_status/status.h"
namespace pw {
namespace ring_buffer {
// 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. 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. This is a FIFO queue, with the oldest entries at
// the 'front' (to be processed by readers) and the newest entries at the 'back'
// (where the writer pushes to).
//
// The ring buffer supports multiple readers, which can be attached/detached
// from the buffer. Each reader has its own read pointer and can peek and pop
// the entry at the head. Entries are not bumped out from the buffer until all
// readers have moved past that entry, or if the buffer is at capacity and space
// is needed to push a new entry. When making space, the buffer will push slow
// readers forward to the new oldest entry. Entries are internally wrapped
// around as needed.
class PrefixedEntryRingBufferMulti {
public:
typedef Status (*ReadOutput)(std::span<const std::byte>);
// A reader that provides a single-reader interface into the multi-reader ring
// buffer it has been attached to via AttachReader(). Readers maintain their
// read position in the ring buffer as well as the remaining count of entries
// from that position. Readers are only able to consume entries that were
// pushed after the attach operation.
//
// Readers can peek and pop entries similar to the single-reader interface.
// When popping entries, although the reader moves forward and drops the
// entry, the entry is not removed from the ring buffer until all other
// attached readers have moved past that entry.
//
// When the attached ring buffer needs to make space, it may push the reader
// index forward. Users of this class should consider the possibility of data
// loss if they read slower than the writer.
class Reader : public IntrusiveList<Reader>::Item {
public:
constexpr Reader() : buffer(nullptr), read_idx(0), entry_count(0) {}
// TODO(pwbug/344): Add locking to the internal functions. Who owns the
// lock? This class? Does this class need a lock if it's not a multi-reader?
// (One doesn't exist today but presumably nothing prevents push + pop
// operations from happening on two different threads).
// Read the oldest stored data chunk of data from the ring buffer to
// the provided destination std::span. The number of bytes read is written
// to bytes_read
//
// Return values:
// OK - Data successfully read from the ring buffer.
// FAILED_PRECONDITION - Buffer not initialized.
// OUT_OF_RANGE - No entries in ring buffer to read.
// RESOURCE_EXHAUSTED - Destination data std::span was smaller number of
// bytes than the data size of the data chunk being read. Available
// destination bytes were filled, remaining bytes of the data chunk were
// ignored.
Status PeekFront(std::span<std::byte> data, size_t* bytes_read_out) {
return buffer->InternalPeekFront(*this, data, bytes_read_out);
}
Status PeekFront(ReadOutput output) {
return buffer->InternalPeekFront(*this, output);
}
// Same as PeekFront but includes the entry's preamble of optional user
// value and the varint of the data size.
// TODO(pwbug/341): Move all other APIs to passing bytes_read by reference,
// as it is required to determine the length populated in the span.
Status PeekFrontWithPreamble(std::span<std::byte> data,
uint32_t& user_preamble_out,
size_t& entry_bytes_read_out);
Status PeekFrontWithPreamble(std::span<std::byte> data,
size_t* bytes_read_out) {
return buffer->InternalPeekFrontWithPreamble(*this, data, bytes_read_out);
}
Status PeekFrontWithPreamble(ReadOutput output) {
return buffer->InternalPeekFrontWithPreamble(*this, output);
}
// Pop and discard the oldest stored data chunk of data from the ring
// buffer.
//
// Return values:
// OK - Data successfully read from the ring buffer.
// FAILED_PRECONDITION - Buffer not initialized.
// OUT_OF_RANGE - No entries in ring buffer to pop.
Status PopFront() { return buffer->InternalPopFront(*this); }
// Get the size in bytes of the next chunk, not including preamble, to be
// read.
size_t FrontEntryDataSizeBytes() {
return buffer->InternalFrontEntryDataSizeBytes(*this);
}
// Get the size in bytes of the next chunk, including preamble and data
// chunk, to be read.
size_t FrontEntryTotalSizeBytes() {
return buffer->InternalFrontEntryTotalSizeBytes(*this);
}
// Get the number of variable-length entries currently in the ring buffer.
//
// Return value:
// Entry count.
size_t EntryCount() { return entry_count; }
protected:
friend PrefixedEntryRingBufferMulti;
PrefixedEntryRingBufferMulti* buffer;
size_t read_idx;
size_t entry_count;
};
// TODO(pwbug/340): Consider changing bool to an enum, to explicitly enumerate
// what this variable means in clients.
PrefixedEntryRingBufferMulti(bool user_preamble = false)
: buffer_(nullptr),
buffer_bytes_(0),
write_idx_(0),
user_preamble_(user_preamble) {}
// Set the raw buffer to be used by the ring buffer.
//
// Return values:
// OK - successfully set the raw buffer.
// INVALID_ARGUMENT - Argument was nullptr, size zero, or too large.
Status SetBuffer(std::span<std::byte> buffer);
// Attach reader to the ring buffer. Readers can only be attached to one
// ring buffer at a time.
//
// Return values:
// OK - Successfully configured reader for ring buffer.
// INVALID_ARGUMENT - Argument was already attached to another ring buffer.
Status AttachReader(Reader& reader);
// Detach reader from the ring buffer. Readers can only be detached if they
// were previously attached.
//
// Return values:
// OK - Successfully removed reader for ring buffer.
// INVALID_ARGUMENT - Argument was not previously attached to this ring
// buffer.
Status DetachReader(Reader& reader);
// Removes all data from the ring buffer.
void Clear();
// 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
// entry. It is only used if user_preamble was set at class construction
// time. It is varint-encoded before insertion into the buffer.
//
// 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.
Status PushBack(std::span<const std::byte> data,
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. It is varint-encoded before insertion into the buffer.
//
// 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(std::span<const std::byte> data,
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(); }
// Dering the buffer by reordering entries internally in the buffer by
// rotating to have the oldest entry is at the lowest address/index with
// newest entry at the highest address.
//
// Return values:
// OK - Buffer data successfully deringed.
// FAILED_PRECONDITION - Buffer not initialized, or no readers attached.
Status Dering();
protected:
// Read the oldest stored data chunk of data from the ring buffer to
// the provided destination std::span. The number of bytes read is written to
// `bytes_read_out`.
//
// Return values:
// OK - Data successfully read from the ring buffer.
// FAILED_PRECONDITION - Buffer not initialized.
// OUT_OF_RANGE - No entries in ring buffer to read.
// RESOURCE_EXHAUSTED - Destination data std::span was smaller number of bytes
// than the data size of the data chunk being read. Available destination
// bytes were filled, remaining bytes of the data chunk were ignored.
Status InternalPeekFront(Reader& reader,
std::span<std::byte> data,
size_t* bytes_read_out);
Status InternalPeekFront(Reader& reader, ReadOutput output);
// Same as Read but includes the entry's preamble of optional user value and
// the varint of the data size
Status InternalPeekFrontWithPreamble(Reader& reader,
std::span<std::byte> data,
size_t* bytes_read_out);
Status InternalPeekFrontWithPreamble(Reader& reader, ReadOutput output);
// Pop and discard the oldest stored data chunk of data from the ring buffer.
//
// Return values:
// OK - Data successfully read from the ring buffer.
// FAILED_PRECONDITION - Buffer not initialized.
// OUT_OF_RANGE - No entries in ring buffer to pop.
Status InternalPopFront(Reader& reader);
// Get the size in bytes of the next chunk, not including preamble, to be
// read.
size_t InternalFrontEntryDataSizeBytes(Reader& reader);
// Get the size in bytes of the next chunk, including preamble and data
// chunk, to be read.
size_t InternalFrontEntryTotalSizeBytes(Reader& reader);
// Internal version of Read used by all the public interface versions. T
// should be of type ReadOutput.
template <typename T>
Status InternalRead(Reader& reader,
T read_output,
bool include_preamble_in_output,
uint32_t* user_preamble_out = nullptr);
private:
struct EntryInfo {
size_t preamble_bytes;
uint32_t user_preamble;
size_t data_bytes;
};
// Push back implementation, which optionally discards front elements to fit
// the incoming element.
Status InternalPushBack(std::span<const std::byte> data,
uint32_t user_preamble_data,
bool pop_front_if_needed);
// Internal function to pop all of the slowest readers. This function may pop
// multiple readers if multiple are slow.
//
// Precondition: This function requires that at least one reader is attached
// and has at least one entry to pop.
void InternalPopFrontAll();
// Returns the slowest reader in the list.
//
// Precondition: This function requires that at least one reader is attached.
Reader& GetSlowestReader();
// Get info struct with the size of the preamble and data chunk for the next
// entry to be read.
EntryInfo FrontEntryInfo(Reader& reader);
// Get the raw number of available bytes free in the ring buffer. This is
// not available bytes for data, since there is a variable size preamble for
// each entry.
size_t RawAvailableBytes();
// Do the basic write of the specified number of bytes starting at the last
// write index of the ring buffer to the destination, handing any wrap-around
// of the ring buffer. This is basic, raw operation with no safety checks.
void RawWrite(std::span<const std::byte> source);
// Do the basic read of the specified number of bytes starting at the given
// index of the ring buffer to the destination, handing any wrap-around of
// the ring buffer. This is basic, raw operation with no safety checks.
void RawRead(std::byte* destination, size_t source_idx, size_t length_bytes);
size_t IncrementIndex(size_t index, size_t count);
std::byte* buffer_;
size_t buffer_bytes_;
size_t write_idx_;
const bool user_preamble_;
// List of attached readers.
IntrusiveList<Reader> readers_;
// Maximum bufer size allowed. Restricted to this to allow index aliasing to
// not overflow.
static constexpr size_t kMaxBufferBytes =
std::numeric_limits<size_t>::max() / 2;
};
class PrefixedEntryRingBuffer : public PrefixedEntryRingBufferMulti,
public PrefixedEntryRingBufferMulti::Reader {
public:
PrefixedEntryRingBuffer(bool user_preamble = false)
: PrefixedEntryRingBufferMulti(user_preamble) {
AttachReader(*this);
}
};
} // namespace ring_buffer
} // namespace pw