blob: 1f8a387a4c2911d579dc78111360f7696fd11b3d [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 <algorithm>
#include <array>
#include <cstddef>
#include <cstring>
#include "pw_bytes/bit.h"
#include "pw_bytes/endian.h"
#include "pw_bytes/span.h"
#include "pw_containers/iterator.h"
#include "pw_preprocessor/compiler.h"
#include "pw_status/status.h"
#include "pw_status/status_with_size.h"
namespace pw {
// ByteBuilder facilitates building bytes in a fixed-size buffer.
// BytesBuilders never overflow. Status is tracked for each operation and
// an overall status is maintained, which reflects the most recent error.
//
// A ByteBuilder does not own the buffer it writes to. It can be used to write
// bytes to any buffer. The ByteBuffer template class, defined below,
// allocates a buffer alongside a ByteBuilder.
class ByteBuilder {
public:
// iterator class will allow users of ByteBuilder and ByteBuffer to access
// the data stored in the buffer. It has the functionality of C++'s
// random access iterator.
class iterator {
public:
using difference_type = ptrdiff_t;
using value_type = std::byte;
using element_type = const std::byte;
using pointer = const std::byte*;
using reference = const std::byte&;
using iterator_category = containers::contiguous_iterator_tag;
explicit constexpr iterator(const std::byte* byte_ptr = nullptr)
: byte_(byte_ptr) {}
constexpr iterator& operator++() {
byte_ += 1;
return *this;
}
constexpr iterator operator++(int) {
iterator previous(byte_);
operator++();
return previous;
}
constexpr iterator& operator--() {
byte_ -= 1;
return *this;
}
constexpr iterator operator--(int) {
iterator previous(byte_);
operator--();
return previous;
}
constexpr iterator& operator+=(int n) {
byte_ += n;
return *this;
}
constexpr iterator operator+(int n) const { return iterator(byte_ + n); }
constexpr iterator& operator-=(int n) { return operator+=(-n); }
constexpr iterator operator-(int n) const { return iterator(byte_ - n); }
constexpr difference_type operator-(const iterator& rhs) const {
return byte_ - rhs.byte_;
}
constexpr reference operator*() const { return *byte_; }
constexpr pointer operator->() const { return byte_; }
constexpr reference operator[](int index) const { return byte_[index]; }
constexpr bool operator==(const iterator& rhs) const {
return byte_ == rhs.byte_;
}
constexpr bool operator!=(const iterator& rhs) const {
return byte_ != rhs.byte_;
}
constexpr bool operator<(const iterator& rhs) const {
return byte_ < rhs.byte_;
}
constexpr bool operator>(const iterator& rhs) const {
return byte_ > rhs.byte_;
}
constexpr bool operator<=(const iterator& rhs) const {
return !operator>(rhs);
}
constexpr bool operator>=(const iterator& rhs) const {
return !operator<(rhs);
}
// The Peek methods will retreive ordered (Little/Big Endian) values
// located at the iterator position without moving the iterator forward.
int8_t PeekInt8() const { return static_cast<int8_t>(PeekUint8()); }
uint8_t PeekUint8() const {
return bytes::ReadInOrder<uint8_t>(endian::little, byte_);
}
int16_t PeekInt16(endian order = endian::little) const {
return static_cast<int16_t>(PeekUint16(order));
}
uint16_t PeekUint16(endian order = endian::little) const {
return bytes::ReadInOrder<uint16_t>(order, byte_);
}
int32_t PeekInt32(endian order = endian::little) const {
return static_cast<int32_t>(PeekUint32(order));
}
uint32_t PeekUint32(endian order = endian::little) const {
return bytes::ReadInOrder<uint32_t>(order, byte_);
}
int64_t PeekInt64(endian order = endian::little) const {
return static_cast<int64_t>(PeekUint64(order));
}
uint64_t PeekUint64(endian order = endian::little) const {
return bytes::ReadInOrder<uint64_t>(order, byte_);
}
// The Read methods will retreive ordered (Little/Big Endian) values
// located at the iterator position and move the iterator forward by
// sizeof(value) positions forward.
int8_t ReadInt8() { return static_cast<int8_t>(ReadUint8()); }
uint8_t ReadUint8() {
uint8_t value = bytes::ReadInOrder<uint8_t>(endian::little, byte_);
byte_ += 1;
return value;
}
int16_t ReadInt16(endian order = endian::little) {
return static_cast<int16_t>(ReadUint16(order));
}
uint16_t ReadUint16(endian order = endian::little) {
uint16_t value = bytes::ReadInOrder<uint16_t>(order, byte_);
byte_ += 2;
return value;
}
int32_t ReadInt32(endian order = endian::little) {
return static_cast<int32_t>(ReadUint32(order));
}
uint32_t ReadUint32(endian order = endian::little) {
uint32_t value = bytes::ReadInOrder<uint32_t>(order, byte_);
byte_ += 4;
return value;
}
int64_t ReadInt64(endian order = endian::little) {
return static_cast<int64_t>(ReadUint64(order));
}
uint64_t ReadUint64(endian order = endian::little) {
int64_t value = bytes::ReadInOrder<int64_t>(order, byte_);
byte_ += 8;
return value;
}
private:
const std::byte* byte_;
};
using element_type = const std::byte;
using value_type = std::byte;
using pointer = std::byte*;
using reference = std::byte&;
using iterator = iterator;
using const_iterator = iterator;
// Creates an empty ByteBuilder.
constexpr ByteBuilder(ByteSpan buffer) : buffer_(buffer), size_(0) {}
// Disallow copy/assign to avoid confusion about where the bytes is actually
// stored. ByteBuffers may be copied into one another.
ByteBuilder(const ByteBuilder&) = delete;
ByteBuilder& operator=(const ByteBuilder&) = delete;
// Returns the contents of the bytes buffer.
const std::byte* data() const { return buffer_.data(); }
// Returns the ByteBuilder's status, which reflects the most recent error
// that occurred while updating the bytes. After an update fails, the status
// remains non-OK until it is cleared with clear() or clear_status(). Returns:
//
// OK if no errors have occurred
// RESOURCE_EXHAUSTED if output to the ByteBuilder was truncated
// INVALID_ARGUMENT if printf-style formatting failed
// OUT_OF_RANGE if an operation outside the buffer was attempted
//
Status status() const { return status_; }
// Returns status() and size() as a StatusWithSize.
StatusWithSize status_with_size() const {
return StatusWithSize(status_, size_);
}
// True if status() is OkStatus().
bool ok() const { return status_.ok(); }
// True if the bytes builder is empty.
bool empty() const { return size() == 0u; }
// Returns the current length of the bytes.
size_t size() const { return size_; }
// Returns the maximum length of the bytes.
size_t max_size() const { return buffer_.size(); }
// Clears the bytes and resets its error state.
void clear() {
size_ = 0;
status_ = OkStatus();
}
// Sets the statuses to OkStatus();
void clear_status() { status_ = OkStatus(); }
// Appends a single byte. Sets the status to RESOURCE_EXHAUSTED if the
// byte cannot be added because the buffer is full.
void push_back(std::byte b) { append(1, b); }
// Removes the last byte. Sets the status to OUT_OF_RANGE if the buffer
// is empty (in which case the unsigned overflow is intentional).
void pop_back() PW_NO_SANITIZE("unsigned-integer-overflow") {
resize(size() - 1);
}
// Root of bytebuffer wrapped in iterator type
const_iterator begin() const { return iterator(data()); }
const_iterator cbegin() const { return begin(); }
// End of bytebuffer wrapped in iterator type
const_iterator end() const { return iterator(data() + size()); }
const_iterator cend() const { return end(); }
// Front and Back C++ container functions
const std::byte& front() const { return buffer_[0]; }
const std::byte& back() const { return buffer_[size() - 1]; }
// Appends the provided byte count times.
ByteBuilder& append(size_t count, std::byte b);
// Appends count bytes from 'bytes' to the end of the ByteBuilder. If count
// exceeds the remaining space in the ByteBuffer, no bytes will be appended
// and the status is set to RESOURCE_EXHAUSTED.
ByteBuilder& append(const void* bytes, size_t count);
// Appends bytes from a byte span that calls the pointer/length version.
ByteBuilder& append(ConstByteSpan bytes) {
return append(bytes.data(), bytes.size());
}
// Sets the ByteBuilder's size. This function only truncates; if
// new_size > size(), it sets status to OUT_OF_RANGE and does nothing.
void resize(size_t new_size);
// Put methods for inserting different 8-bit ints
ByteBuilder& PutUint8(uint8_t val) { return WriteInOrder(val); }
ByteBuilder& PutInt8(int8_t val) { return WriteInOrder(val); }
// Put methods for inserting different 16-bit ints
ByteBuilder& PutUint16(uint16_t value, endian order = endian::little) {
return WriteInOrder(bytes::ConvertOrderTo(order, value));
}
ByteBuilder& PutInt16(int16_t value, endian order = endian::little) {
return PutUint16(static_cast<uint16_t>(value), order);
}
// Put methods for inserting different 32-bit ints
ByteBuilder& PutUint32(uint32_t value, endian order = endian::little) {
return WriteInOrder(bytes::ConvertOrderTo(order, value));
}
ByteBuilder& PutInt32(int32_t value, endian order = endian::little) {
return PutUint32(static_cast<uint32_t>(value), order);
}
// Put methods for inserting different 64-bit ints
ByteBuilder& PutUint64(uint64_t value, endian order = endian::little) {
return WriteInOrder(bytes::ConvertOrderTo(order, value));
}
ByteBuilder& PutInt64(int64_t value, endian order = endian::little) {
return PutUint64(static_cast<uint64_t>(value), order);
}
protected:
// Functions to support ByteBuffer copies.
constexpr ByteBuilder(const ByteSpan& buffer, const ByteBuilder& other)
: buffer_(buffer), size_(other.size_), status_(other.status_) {}
void CopySizeAndStatus(const ByteBuilder& other) {
size_ = other.size_;
status_ = other.status_;
}
private:
template <typename T>
ByteBuilder& WriteInOrder(T value) {
return append(&value, sizeof(value));
}
size_t ResizeForAppend(size_t bytes_to_append);
const ByteSpan buffer_;
size_t size_;
Status status_;
};
// ByteBuffers declare a buffer along with a ByteBuilder.
template <size_t kSizeBytes>
class ByteBuffer : public ByteBuilder {
public:
ByteBuffer() : ByteBuilder(buffer_) {}
// ByteBuffers of the same size may be copied and assigned into one another.
ByteBuffer(const ByteBuffer& other) : ByteBuilder(buffer_, other) {
CopyContents(other);
}
// A smaller ByteBuffer may be copied or assigned into a larger one.
template <size_t kOtherSizeBytes>
ByteBuffer(const ByteBuffer<kOtherSizeBytes>& other)
: ByteBuilder(buffer_, other) {
static_assert(ByteBuffer<kOtherSizeBytes>::max_size() <= max_size(),
"A ByteBuffer cannot be copied into a smaller buffer");
CopyContents(other);
}
template <size_t kOtherSizeBytes>
ByteBuffer& operator=(const ByteBuffer<kOtherSizeBytes>& other) {
assign<kOtherSizeBytes>(other);
return *this;
}
ByteBuffer& operator=(const ByteBuffer& other) {
assign<kSizeBytes>(other);
return *this;
}
template <size_t kOtherSizeBytes>
ByteBuffer& assign(const ByteBuffer<kOtherSizeBytes>& other) {
static_assert(ByteBuffer<kOtherSizeBytes>::max_size() <= max_size(),
"A ByteBuffer cannot be copied into a smaller buffer");
CopySizeAndStatus(other);
CopyContents(other);
return *this;
}
// Returns the maximum length of the bytes that can be inserted in the bytes
// buffer.
static constexpr size_t max_size() { return kSizeBytes; }
// Returns a ByteBuffer<kSizeBytes>& instead of a generic ByteBuilder& for
// append calls.
template <typename... Args>
ByteBuffer& append(Args&&... args) {
ByteBuilder::append(std::forward<Args>(args)...);
return *this;
}
private:
template <size_t kOtherSize>
void CopyContents(const ByteBuffer<kOtherSize>& other) {
std::memcpy(buffer_.data(), other.data(), other.size());
}
std::array<std::byte, kSizeBytes> buffer_;
};
constexpr ByteBuilder::iterator operator+(int n, ByteBuilder::iterator it) {
return it + n;
}
} // namespace pw