| // 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) { |
| uint64_t value = bytes::ReadInOrder<uint64_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 @rst |
| /// |
| /// .. pw-status-codes:: |
| /// |
| /// OK: No errors have occurred. |
| /// |
| /// RESOURCE_EXHAUSTED: Output to the ``ByteBuilder`` was truncated. |
| /// |
| /// INVALID_ARGUMENT: ``printf``-style formatting failed. |
| /// |
| /// OUT_OF_RANGE: An operation outside the buffer was attempted. |
| /// |
| /// @endrst |
| 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_) {} |
| |
| // Implicit copy constructors are not provided in order to prevent |
| // accidental copies of data when passing around ByteByffers. |
| // |
| // Copy assignment, however, is provided, as it requires the user to |
| // explicitly declare a separate local. |
| ByteBuffer(ByteBuffer& other) = delete; |
| |
| 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; |
| } |
| |
| /// ByteBuffers are not movable: the underlying data must be copied. |
| ByteBuffer(ByteBuffer&& other) = delete; |
| |
| /// ByteBuffers are not movable: the underlying data must be copied. |
| ByteBuffer& operator=(ByteBuffer&& other) = delete; |
| |
| /// 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 |