// 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 #include #include #include #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(PeekUint8()); } uint8_t PeekUint8() const { return bytes::ReadInOrder(endian::little, byte_); } int16_t PeekInt16(endian order = endian::little) const { return static_cast(PeekUint16(order)); } uint16_t PeekUint16(endian order = endian::little) const { return bytes::ReadInOrder(order, byte_); } int32_t PeekInt32(endian order = endian::little) const { return static_cast(PeekUint32(order)); } uint32_t PeekUint32(endian order = endian::little) const { return bytes::ReadInOrder(order, byte_); } int64_t PeekInt64(endian order = endian::little) const { return static_cast(PeekUint64(order)); } uint64_t PeekUint64(endian order = endian::little) const { return bytes::ReadInOrder(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(ReadUint8()); } uint8_t ReadUint8() { uint8_t value = bytes::ReadInOrder(endian::little, byte_); byte_ += 1; return value; } int16_t ReadInt16(endian order = endian::little) { return static_cast(ReadUint16(order)); } uint16_t ReadUint16(endian order = endian::little) { uint16_t value = bytes::ReadInOrder(order, byte_); byte_ += 2; return value; } int32_t ReadInt32(endian order = endian::little) { return static_cast(ReadUint32(order)); } uint32_t ReadUint32(endian order = endian::little) { uint32_t value = bytes::ReadInOrder(order, byte_); byte_ += 4; return value; } int64_t ReadInt64(endian order = endian::little) { return static_cast(ReadUint64(order)); } uint64_t ReadUint64(endian order = endian::little) { uint64_t value = bytes::ReadInOrder(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(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(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(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 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 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 ByteBuffer& operator=(const ByteBuffer& other) { assign(other); return *this; } ByteBuffer& operator=(const ByteBuffer& other) { assign(other); return *this; } template ByteBuffer& assign(const ByteBuffer& other) { static_assert(ByteBuffer::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& instead of a generic ByteBuilder& for /// append calls. template ByteBuffer& append(Args&&... args) { ByteBuilder::append(std::forward(args)...); return *this; } private: template void CopyContents(const ByteBuffer& other) { std::memcpy(buffer_.data(), other.data(), other.size()); } std::array buffer_; }; constexpr ByteBuilder::iterator operator+(int n, ByteBuilder::iterator it) { return it + n; } } // namespace pw