#pragma once #include #include #include #include #include "esphome/core/helpers.h" namespace esphome { namespace bytebuffer { enum Endian { LITTLE, BIG }; /** * A class modelled on the Java ByteBuffer class. It wraps a vector of bytes and permits putting and getting * items of various sizes, with an automatically incremented position. * * There are three variables maintained pointing into the buffer: * * capacity: the maximum amount of data that can be stored - set on construction and cannot be changed * limit: the limit of the data currently available to get or put * position: the current insert or extract position * * 0 <= position <= limit <= capacity * * In addition a mark can be set to the current position with mark(). A subsequent call to reset() will restore * the position to the mark. * * The buffer can be marked to be little-endian (default) or big-endian. All subsequent operations will use that order. * * The flip() operation will reset the position to 0 and limit to the current position. This is useful for reading * data from a buffer after it has been written. * * The code is defined here in the header file rather than in a .cpp file, so that it does not get compiled if not used. * The templated functions ensure that only those typed functions actually used are compiled. The functions * are implicitly inline-able which will aid performance. */ class ByteBuffer { public: // Default constructor (compatibility with TEMPLATABLE_VALUE) // Creates a zero-length ByteBuffer which is little use to anybody. ByteBuffer() : ByteBuffer(std::vector()) {} /** * Create a new Bytebuffer with the given capacity */ ByteBuffer(size_t capacity, Endian endianness = LITTLE) : data_(std::vector(capacity)), endianness_(endianness), limit_(capacity){}; // templated functions to implement putting and getting data of various types. There are two flavours of all // functions - one that uses the position as the offset, and updates the position accordingly, and one that // takes an explicit offset and does not update the position. // Separate temnplates are provided for types that fit into 32 bits and those that are bigger. These delegate // the actual put/get to common code based around those sizes. // This reduces the code size and execution time for smaller types. A similar structure for e.g. 16 bits is unlikely // to provide any further benefit given that all target platforms are native 32 bit. template T get(typename std::enable_if::value, T>::type * = 0, typename std::enable_if<(sizeof(T) <= sizeof(uint32_t)), T>::type * = 0) { // integral types that fit into 32 bit return static_cast(this->get_uint32_(sizeof(T))); } template T get(size_t offset, typename std::enable_if::value, T>::type * = 0, typename std::enable_if<(sizeof(T) <= sizeof(uint32_t)), T>::type * = 0) { return static_cast(this->get_uint32_(offset, sizeof(T))); } template void put(const T &value, typename std::enable_if::value, T>::type * = 0, typename std::enable_if<(sizeof(T) <= sizeof(uint32_t)), T>::type * = 0) { this->put_uint32_(static_cast(value), sizeof(T)); } template void put(const T &value, size_t offset, typename std::enable_if::value, T>::type * = 0, typename std::enable_if<(sizeof(T) <= sizeof(uint32_t)), T>::type * = 0) { this->put_uint32_(static_cast(value), offset, sizeof(T)); } // integral types that do not fit into 32 bit (basically only 64 bit types) template T get(typename std::enable_if::value, T>::type * = 0, typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) { return static_cast(this->get_uint64_(sizeof(T))); } template T get(size_t offset, typename std::enable_if::value, T>::type * = 0, typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) { return static_cast(this->get_uint64_(offset, sizeof(T))); } template void put(const T &value, typename std::enable_if::value, T>::type * = 0, typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) { this->put_uint64_(value, sizeof(T)); } template void put(const T &value, size_t offset, typename std::enable_if::value, T>::type * = 0, typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) { this->put_uint64_(static_cast(value), offset, sizeof(T)); } // floating point types. Caters for 32 and 64 bit floating point. template T get(typename std::enable_if::value, T>::type * = 0, typename std::enable_if<(sizeof(T) == sizeof(uint32_t)), T>::type * = 0) { return bit_cast(this->get_uint32_(sizeof(T))); } template T get(typename std::enable_if::value, T>::type * = 0, typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) { return bit_cast(this->get_uint64_(sizeof(T))); } template T get(size_t offset, typename std::enable_if::value, T>::type * = 0, typename std::enable_if<(sizeof(T) == sizeof(uint32_t)), T>::type * = 0) { return bit_cast(this->get_uint32_(offset, sizeof(T))); } template T get(size_t offset, typename std::enable_if::value, T>::type * = 0, typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) { return bit_cast(this->get_uint64_(offset, sizeof(T))); } template void put(const T &value, typename std::enable_if::value, T>::type * = 0, typename std::enable_if<(sizeof(T) <= sizeof(uint32_t)), T>::type * = 0) { this->put_uint32_(bit_cast(value), sizeof(T)); } template void put(const T &value, typename std::enable_if::value, T>::type * = 0, typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) { this->put_uint64_(bit_cast(value), sizeof(T)); } template void put(const T &value, size_t offset, typename std::enable_if::value, T>::type * = 0, typename std::enable_if<(sizeof(T) <= sizeof(uint32_t)), T>::type * = 0) { this->put_uint32_(bit_cast(value), offset, sizeof(T)); } template void put(const T &value, size_t offset, typename std::enable_if::value, T>::type * = 0, typename std::enable_if<(sizeof(T) == sizeof(uint64_t)), T>::type * = 0) { this->put_uint64_(bit_cast(value), offset, sizeof(T)); } template static ByteBuffer wrap(T value, Endian endianness = LITTLE) { ByteBuffer buffer = ByteBuffer(sizeof(T), endianness); buffer.put(value); buffer.flip(); return buffer; } static ByteBuffer wrap(std::vector const &data, Endian endianness = LITTLE) { ByteBuffer buffer = {data}; buffer.endianness_ = endianness; return buffer; } static ByteBuffer wrap(const uint8_t *ptr, size_t len, Endian endianness = LITTLE) { return wrap(std::vector(ptr, ptr + len), endianness); } // convenience functions with explicit types named.. void put_float(float value) { this->put(value); } void put_double(double value) { this->put(value); } uint8_t get_uint8() { return this->data_[this->position_++]; } // Get a 16 bit unsigned value, increment by 2 uint16_t get_uint16() { return this->get(); } // Get a 24 bit unsigned value, increment by 3 uint32_t get_uint24() { return this->get_uint32_(3); }; // Get a 32 bit unsigned value, increment by 4 uint32_t get_uint32() { return this->get(); }; // Get a 64 bit unsigned value, increment by 8 uint64_t get_uint64() { return this->get(); }; // Signed versions of the get functions uint8_t get_int8() { return static_cast(this->get_uint8()); }; int16_t get_int16() { return this->get(); } int32_t get_int32() { return this->get(); } int64_t get_int64() { return this->get(); } // Get a float value, increment by 4 float get_float() { return this->get(); } // Get a double value, increment by 8 double get_double() { return this->get(); } // Get a bool value, increment by 1 bool get_bool() { return static_cast(this->get_uint8()); } uint32_t get_int24(size_t offset) { auto value = this->get_uint24(offset); uint32_t mask = (~static_cast(0)) << 23; if ((value & mask) != 0) value |= mask; return value; } uint32_t get_int24() { auto value = this->get_uint24(); uint32_t mask = (~static_cast(0)) << 23; if ((value & mask) != 0) value |= mask; return value; } std::vector get_vector(size_t length, size_t offset) { auto start = this->data_.begin() + offset; return {start, start + length}; } std::vector get_vector(size_t length) { auto result = this->get_vector(length, this->position_); this->position_ += length; return result; } // Convenience named functions void put_uint8(uint8_t value) { this->data_[this->position_++] = value; } void put_uint16(uint16_t value) { this->put(value); } void put_uint24(uint32_t value) { this->put_uint32_(value, 3); } void put_uint32(uint32_t value) { this->put(value); } void put_uint64(uint64_t value) { this->put(value); } // Signed versions of the put functions void put_int8(int8_t value) { this->put_uint8(static_cast(value)); } void put_int16(int16_t value) { this->put(value); } void put_int24(int32_t value) { this->put_uint32_(value, 3); } void put_int32(int32_t value) { this->put(value); } void put_int64(int64_t value) { this->put(value); } // Extra put functions void put_bool(bool value) { this->put_uint8(value); } // versions of the above with an offset, these do not update the position uint64_t get_uint64(size_t offset) { return this->get(offset); } uint32_t get_uint24(size_t offset) { return this->get_uint32_(offset, 3); }; double get_double(size_t offset) { return get(offset); } // Get one byte from the buffer, increment position by 1 uint8_t get_uint8(size_t offset) { return this->data_[offset]; } // Get a 16 bit unsigned value, increment by 2 uint16_t get_uint16(size_t offset) { return get(offset); } // Get a 24 bit unsigned value, increment by 3 uint32_t get_uint32(size_t offset) { return this->get(offset); }; // Get a 64 bit unsigned value, increment by 8 uint8_t get_int8(size_t offset) { return get(offset); } int16_t get_int16(size_t offset) { return get(offset); } int32_t get_int32(size_t offset) { return get(offset); } int64_t get_int64(size_t offset) { return get(offset); } // Get a float value, increment by 4 float get_float(size_t offset) { return get(offset); } // Get a double value, increment by 8 // Get a bool value, increment by 1 bool get_bool(size_t offset) { return this->get_uint8(offset); } void put_uint8(uint8_t value, size_t offset) { this->data_[offset] = value; } void put_uint16(uint16_t value, size_t offset) { this->put(value, offset); } void put_uint24(uint32_t value, size_t offset) { this->put(value, offset); } void put_uint32(uint32_t value, size_t offset) { this->put(value, offset); } void put_uint64(uint64_t value, size_t offset) { this->put(value, offset); } // Signed versions of the put functions void put_int8(int8_t value, size_t offset) { this->put_uint8(static_cast(value), offset); } void put_int16(int16_t value, size_t offset) { this->put(value, offset); } void put_int24(int32_t value, size_t offset) { this->put_uint32_(value, offset, 3); } void put_int32(int32_t value, size_t offset) { this->put(value, offset); } void put_int64(int64_t value, size_t offset) { this->put(value, offset); } // Extra put functions void put_float(float value, size_t offset) { this->put(value, offset); } void put_double(double value, size_t offset) { this->put(value, offset); } void put_bool(bool value, size_t offset) { this->put_uint8(value, offset); } void put(const std::vector &value, size_t offset) { std::copy(value.begin(), value.end(), this->data_.begin() + offset); } void put_vector(const std::vector &value, size_t offset) { this->put(value, offset); } void put(const std::vector &value) { this->put_vector(value, this->position_); this->position_ += value.size(); } void put_vector(const std::vector &value) { this->put(value); } // Getters inline size_t get_capacity() const { return this->data_.size(); } inline size_t get_position() const { return this->position_; } inline size_t get_limit() const { return this->limit_; } inline size_t get_remaining() const { return this->get_limit() - this->get_position(); } inline Endian get_endianness() const { return this->endianness_; } inline void mark() { this->mark_ = this->position_; } inline void big_endian() { this->endianness_ = BIG; } inline void little_endian() { this->endianness_ = LITTLE; } // retrieve a pointer to the underlying data. std::vector get_data() { return this->data_; }; void get_bytes(void *dest, size_t length) { std::copy(this->data_.begin() + this->position_, this->data_.begin() + this->position_ + length, (uint8_t *) dest); this->position_ += length; } void get_bytes(void *dest, size_t length, size_t offset) { std::copy(this->data_.begin() + offset, this->data_.begin() + offset + length, (uint8_t *) dest); } void rewind() { this->position_ = 0; } void reset() { this->position_ = this->mark_; } void set_limit(size_t limit) { this->limit_ = limit; } void set_position(size_t position) { this->position_ = position; } void clear() { this->limit_ = this->get_capacity(); this->position_ = 0; } void flip() { this->limit_ = this->position_; this->position_ = 0; } protected: uint64_t get_uint64_(size_t offset, size_t length) const { uint64_t value = 0; if (this->endianness_ == LITTLE) { offset += length; while (length-- != 0) { value <<= 8; value |= this->data_[--offset]; } } else { while (length-- != 0) { value <<= 8; value |= this->data_[offset++]; } } return value; } uint64_t get_uint64_(size_t length) { auto result = this->get_uint64_(this->position_, length); this->position_ += length; return result; } uint32_t get_uint32_(size_t offset, size_t length) const { uint32_t value = 0; if (this->endianness_ == LITTLE) { offset += length; while (length-- != 0) { value <<= 8; value |= this->data_[--offset]; } } else { while (length-- != 0) { value <<= 8; value |= this->data_[offset++]; } } return value; } uint32_t get_uint32_(size_t length) { auto result = this->get_uint32_(this->position_, length); this->position_ += length; return result; } /// Putters void put_uint64_(uint64_t value, size_t length) { this->put_uint64_(value, this->position_, length); this->position_ += length; } void put_uint32_(uint32_t value, size_t length) { this->put_uint32_(value, this->position_, length); this->position_ += length; } void put_uint64_(uint64_t value, size_t offset, size_t length) { if (this->endianness_ == LITTLE) { while (length-- != 0) { this->data_[offset++] = static_cast(value); value >>= 8; } } else { offset += length; while (length-- != 0) { this->data_[--offset] = static_cast(value); value >>= 8; } } } void put_uint32_(uint32_t value, size_t offset, size_t length) { if (this->endianness_ == LITTLE) { while (length-- != 0) { this->data_[offset++] = static_cast(value); value >>= 8; } } else { offset += length; while (length-- != 0) { this->data_[--offset] = static_cast(value); value >>= 8; } } } ByteBuffer(std::vector const &data) : data_(data), limit_(data.size()) {} std::vector data_; Endian endianness_{LITTLE}; size_t position_{0}; size_t mark_{0}; size_t limit_{0}; }; } // namespace bytebuffer } // namespace esphome