From 4e3b3de391c7ea0872bb6891d1eba863fb223181 Mon Sep 17 00:00:00 2001 From: Joshua Scott Date: Sun, 18 Nov 2018 17:10:21 +0000 Subject: [PATCH] util: Implement a common interface for bit streams --- include/ki/pclass/Property.h | 12 +- include/ki/pclass/StaticProperty.h | 16 +- include/ki/pclass/TypeSystem.h | 31 +++- include/ki/pclass/VectorProperty.h | 8 +- include/ki/pclass/types/ClassType.h | 4 +- .../pclass/types/FloatingPointPrimitiveType.h | 4 +- .../ki/pclass/types/IntegralPrimitiveType.h | 4 +- include/ki/pclass/types/PrimitiveType.h | 8 +- include/ki/pclass/types/StringPrimitiveType.h | 4 +- include/ki/pclass/types/Type.h | 4 +- include/ki/serialization/SerializerBinary.h | 16 +- include/ki/util/BitStream.h | 157 +++++++++--------- src/pclass/Property.cpp | 4 +- src/pclass/Type.cpp | 4 +- src/serialization/SerializerBinary.cpp | 16 +- src/util/BitStream.cpp | 124 +++++++++++--- test/src/unit-bitstream.cpp | 6 + 17 files changed, 260 insertions(+), 162 deletions(-) diff --git a/include/ki/pclass/Property.h b/include/ki/pclass/Property.h index dc6d123..89959fd 100644 --- a/include/ki/pclass/Property.h +++ b/include/ki/pclass/Property.h @@ -28,8 +28,8 @@ namespace pclass virtual Value get_value() const = 0; virtual const PropertyClass *get_object() const = 0; - virtual void write_value_to(BitStream &stream) const = 0; - virtual void read_value_from(BitStream &stream) = 0; + virtual void write_value_to(BitStreamBase &stream) const = 0; + virtual void read_value_from(BitStreamBase &stream) = 0; private: std::string m_name; @@ -53,13 +53,13 @@ namespace pclass Value get_value() const final override; const PropertyClass *get_object() const final override; - void write_value_to(BitStream &stream) const final override; - void read_value_from(BitStream &stream) final override; + void write_value_to(BitStreamBase &stream) const final override; + void read_value_from(BitStreamBase &stream) final override; virtual Value get_value(int index) const = 0; virtual const PropertyClass *get_object(int index) const = 0; - virtual void write_value_to(BitStream &stream, int index) const = 0; - virtual void read_value_from(BitStream &stream, int index) = 0; + virtual void write_value_to(BitStreamBase &stream, int index) const = 0; + virtual void read_value_from(BitStreamBase &stream, int index) = 0; }; } } diff --git a/include/ki/pclass/StaticProperty.h b/include/ki/pclass/StaticProperty.h index 09926da..d12daee 100644 --- a/include/ki/pclass/StaticProperty.h +++ b/include/ki/pclass/StaticProperty.h @@ -161,12 +161,12 @@ namespace pclass > struct value_rw_helper { - static void write(const StaticProperty &prop, BitStream &stream) + static void write(const StaticProperty &prop, BitStreamBase &stream) { prop.get_type().write_to(stream, prop.m_value); } - static void read(StaticProperty &prop, BitStream &stream) + static void read(StaticProperty &prop, BitStreamBase &stream) { prop.get_type().read_from(stream, Value(prop.m_value)); } @@ -187,12 +187,12 @@ namespace pclass >::type > { - static void write(const StaticProperty &prop, BitStream &stream) + static void write(const StaticProperty &prop, BitStreamBase &stream) { prop.get_type().write_to(stream, *prop.m_value); } - static void read(StaticProperty &prop, BitStream &stream) + static void read(StaticProperty &prop, BitStreamBase &stream) { prop.get_type().read_from(stream, Value(*prop.m_value)); } @@ -215,12 +215,12 @@ namespace pclass return value_object_helper::get_object(prop); } - static void write(const StaticProperty &prop, BitStream &stream) + static void write(const StaticProperty &prop, BitStreamBase &stream) { value_rw_helper::write(prop, stream); } - static void read(StaticProperty &prop, BitStream &stream) + static void read(StaticProperty &prop, BitStreamBase &stream) { value_rw_helper::read(prop, stream); } @@ -260,12 +260,12 @@ namespace pclass return std::is_pointer::value; } - void write_value_to(BitStream &stream) const override + void write_value_to(BitStreamBase &stream) const override { value_helper::write(*this, stream); } - void read_value_from(BitStream &stream) override + void read_value_from(BitStreamBase &stream) override { value_helper::read(*this, stream); } diff --git a/include/ki/pclass/TypeSystem.h b/include/ki/pclass/TypeSystem.h index eb77a57..c5bb227 100644 --- a/include/ki/pclass/TypeSystem.h +++ b/include/ki/pclass/TypeSystem.h @@ -38,17 +38,16 @@ namespace pclass } template - ClassType &define_class( - const std::string &name, const Type *base_class = nullptr) + ClassType &define_class(const std::string &name) { - // If the caller does not specify a base class, automatically make - // ki::pclass::PropertyClass the base class (if it has been defined) - if (base_class == nullptr && has_type("class PropertyClass")) - base_class = &get_type("class PropertyClass"); + return define_class(name, nullptr); + } - auto *type = new ClassType(name, base_class, *this); - define_type(type); - return *type; + template + ClassType &define_class( + const std::string &name, const Type &base_class) + { + return define_class(name, &base_class); } template @@ -71,6 +70,20 @@ namespace pclass protected: void define_type(Type *type); + template + ClassType &define_class( + const std::string &name, const Type *base_class) + { + // If the caller does not specify a base class, automatically make + // ki::pclass::PropertyClass the base class (if it has been defined) + if (base_class == nullptr && has_type("class PropertyClass")) + base_class = &get_type("class PropertyClass"); + + auto *type = new ClassType(name, base_class, *this); + define_type(type); + return *type; + } + private: TypeList m_types; TypeNameMap m_type_name_lookup; diff --git a/include/ki/pclass/VectorProperty.h b/include/ki/pclass/VectorProperty.h index df21ade..a675991 100644 --- a/include/ki/pclass/VectorProperty.h +++ b/include/ki/pclass/VectorProperty.h @@ -40,14 +40,14 @@ namespace pclass return false; } - void write_value_to(BitStream &stream, const int index) const override + void write_value_to(BitStreamBase &stream, const int index) const override { if (index < 0 || index >= this->size()) throw runtime_error("Index out of bounds."); this->get_type().write_to(stream, this->at(index)); } - void read_value_from(BitStream &stream, const int index) override + void read_value_from(BitStreamBase &stream, const int index) override { if (index < 0 || index >= this->size()) throw runtime_error("Index out of bounds."); @@ -73,14 +73,14 @@ namespace pclass return true; } - void write_value_to(BitStream &stream, const int index) const override + void write_value_to(BitStreamBase &stream, const int index) const override { if (index < 0 || index >= this->size()) throw runtime_error("Index out of bounds."); this->get_type().write_to(stream, *this->at(index)); } - void read_value_from(BitStream &stream, const int index) override + void read_value_from(BitStreamBase &stream, const int index) override { if (index < 0 || index >= this->size()) throw runtime_error("Index out of bounds."); diff --git a/include/ki/pclass/types/ClassType.h b/include/ki/pclass/types/ClassType.h index fb50821..8989096 100644 --- a/include/ki/pclass/types/ClassType.h +++ b/include/ki/pclass/types/ClassType.h @@ -45,7 +45,7 @@ namespace pclass return new ClassT(*this, get_type_system()); } - void write_to(BitStream &stream, const Value &value) const override + void write_to(BitStreamBase &stream, const Value &value) const override { const auto &object = dynamic_cast(value.get()); const auto &properties = object.get_properties(); @@ -53,7 +53,7 @@ namespace pclass (*it)->write_value_to(stream); } - void read_from(BitStream &stream, Value &value) const override + void read_from(BitStreamBase &stream, Value &value) const override { auto &object = dynamic_cast(value.get()); auto &properties = object.get_properties(); diff --git a/include/ki/pclass/types/FloatingPointPrimitiveType.h b/include/ki/pclass/types/FloatingPointPrimitiveType.h index 41fa848..26fe484 100644 --- a/include/ki/pclass/types/FloatingPointPrimitiveType.h +++ b/include/ki/pclass/types/FloatingPointPrimitiveType.h @@ -12,7 +12,7 @@ namespace pclass typename std::enable_if::value>::type > { - static void write_to(BitStream &stream, const ValueT &value) + static void write_to(BitStreamBase &stream, const ValueT &value) { // Reinterpret the reference as a reference to an integer const uint_type &v = *( @@ -35,7 +35,7 @@ namespace pclass typename std::enable_if::value>::type > { - static void read_from(BitStream &stream, ValueT &value) + static void read_from(BitStreamBase &stream, ValueT &value) { // Reinterpret the reference as a reference to an integer uint_type &v = *( diff --git a/include/ki/pclass/types/IntegralPrimitiveType.h b/include/ki/pclass/types/IntegralPrimitiveType.h index 7fb7302..e4c75f6 100644 --- a/include/ki/pclass/types/IntegralPrimitiveType.h +++ b/include/ki/pclass/types/IntegralPrimitiveType.h @@ -12,7 +12,7 @@ namespace pclass typename std::enable_if::value>::type > { - static void write_to(BitStream &stream, const ValueT &value) + static void write_to(BitStreamBase &stream, const ValueT &value) { stream.write(value); } @@ -24,7 +24,7 @@ namespace pclass typename std::enable_if::value>::type > { - static void read_from(BitStream &stream, ValueT &value) + static void read_from(BitStreamBase &stream, ValueT &value) { value = stream.read(); } diff --git a/include/ki/pclass/types/PrimitiveType.h b/include/ki/pclass/types/PrimitiveType.h index 55d3b88..921d620 100644 --- a/include/ki/pclass/types/PrimitiveType.h +++ b/include/ki/pclass/types/PrimitiveType.h @@ -11,7 +11,7 @@ namespace pclass template struct PrimitiveTypeWriter { - static void write_to(BitStream &stream, const ValueT &value) + static void write_to(BitStreamBase &stream, const ValueT &value) { // Provide a compiler error if this is not specialized static_assert( @@ -27,7 +27,7 @@ namespace pclass template struct PrimitiveTypeReader { - static void read_from(BitStream &stream, ValueT &value) + static void read_from(BitStreamBase &stream, ValueT &value) { // Provide a compiler error if this is not specialized static_assert( @@ -50,14 +50,14 @@ namespace pclass m_kind = kind::PRIMITIVE; } - void write_to(BitStream &stream, const Value &value) const override + void write_to(BitStreamBase &stream, const Value &value) const override { if (!value.is()) throw std::runtime_error("Invalid call to Type::write_to -- value type does not match ValueT."); PrimitiveTypeWriter::write_to(stream, value.get()); } - void read_from(BitStream &stream, Value &value) const override + void read_from(BitStreamBase &stream, Value &value) const override { if (!value.is()) throw std::runtime_error("Invalid call to Type::read_from -- value type does not match ValueT."); diff --git a/include/ki/pclass/types/StringPrimitiveType.h b/include/ki/pclass/types/StringPrimitiveType.h index 4af6870..c9e5f4d 100644 --- a/include/ki/pclass/types/StringPrimitiveType.h +++ b/include/ki/pclass/types/StringPrimitiveType.h @@ -16,7 +16,7 @@ namespace pclass using type = std::basic_string<_Elem, _Traits, _Alloc>; public: - static void write_to(BitStream &stream, const type &value) + static void write_to(BitStreamBase &stream, const type &value) { // Write the length as an unsigned short stream.write(value.length()); @@ -38,7 +38,7 @@ namespace pclass using type = std::basic_string<_Elem, _Traits, _Alloc>; public: - static void read_from(BitStream &stream, type &value) + static void read_from(BitStreamBase &stream, type &value) { // Read the length and create a new string with the correct capacity auto length = stream.read(); diff --git a/include/ki/pclass/types/Type.h b/include/ki/pclass/types/Type.h index eaf3529..caf1160 100644 --- a/include/ki/pclass/types/Type.h +++ b/include/ki/pclass/types/Type.h @@ -50,8 +50,8 @@ namespace pclass const TypeSystem &get_type_system() const; virtual PropertyClass *instantiate() const; - virtual void write_to(BitStream &stream, const Value &value) const; - virtual void read_from(BitStream &stream, Value &value) const; + virtual void write_to(BitStreamBase &stream, const Value &value) const; + virtual void read_from(BitStreamBase &stream, Value &value) const; protected: kind m_kind; diff --git a/include/ki/serialization/SerializerBinary.h b/include/ki/serialization/SerializerBinary.h index 0532100..beb2336 100644 --- a/include/ki/serialization/SerializerBinary.h +++ b/include/ki/serialization/SerializerBinary.h @@ -45,17 +45,17 @@ namespace serialization bool is_file, flags flags); virtual ~SerializerBinary() {} - void save(const pclass::PropertyClass *object, BitStream &stream); - void load(pclass::PropertyClass *&dest, BitStream &stream); + void save(const pclass::PropertyClass *object, BitStreamBase &stream); + void load(pclass::PropertyClass *&dest, BitStreamBase &stream); protected: - virtual void presave_object(const pclass::PropertyClass *object, BitStream &stream) const; - void save_object(const pclass::PropertyClass *object, BitStream &stream) const; - void save_property(const pclass::PropertyBase *prop, BitStream &stream) const; + virtual void presave_object(const pclass::PropertyClass *object, BitStreamBase &stream) const; + void save_object(const pclass::PropertyClass *object, BitStreamBase &stream) const; + void save_property(const pclass::PropertyBase *prop, BitStreamBase &stream) const; - virtual void preload_object(pclass::PropertyClass *&dest, BitStream &stream) const; - void load_object(pclass::PropertyClass *&dest, BitStream &stream) const; - void load_property(pclass::PropertyBase *prop, BitStream &stream) const; + virtual void preload_object(pclass::PropertyClass *&dest, BitStreamBase &stream) const; + void load_object(pclass::PropertyClass *&dest, BitStreamBase &stream) const; + void load_property(pclass::PropertyBase *prop, BitStreamBase &stream) const; private: const pclass::TypeSystem *m_type_system; diff --git a/include/ki/util/BitStream.h b/include/ki/util/BitStream.h index 3b26a94..4af7193 100644 --- a/include/ki/util/BitStream.h +++ b/include/ki/util/BitStream.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include "ki/util/BitTypes.h" #define KI_BITSTREAM_DEFAULT_BUFFER_SIZE 0x2000 @@ -8,9 +9,10 @@ namespace ki { /** - * A readable/writeable stream of bits. + * An abstract base class that provides a common interface for + * writing to, reading from, and querying bit streams. */ - class BitStream + class BitStreamBase { public: /** @@ -45,33 +47,33 @@ namespace ki void set_bit(int bit); }; - explicit BitStream(std::size_t buffer_size = KI_BITSTREAM_DEFAULT_BUFFER_SIZE); - ~BitStream(); + virtual ~BitStreamBase() {} /** - * @return The stream's current position. + * @returns The stream's current position. */ - stream_pos tell() const; + virtual stream_pos tell() const = 0; /** * Sets the position of the stream. * @param position The new position of the stream. */ - void seek(stream_pos position); + virtual void seek(stream_pos position) = 0; /** - * @return The current size of the internal buffer. + * @returns The current size of the internal buffer. */ - std::size_t capacity() const; + virtual std::size_t capacity() const = 0; /** - * @return A pointer to the start of the internal buffer. + * @returns A pointer to the start of the internal buffer. */ - const uint8_t *data() const; + virtual const uint8_t *data() const = 0; /** * Reads a value from the buffer given a defined number of bits. - * @param bits The number of bits to read. + * @param[in] bits The number of bits to read. Defaults to the bitsize of IntegerT. + * @returns The value read from the buffer. */ template < typename IntegerT, @@ -79,42 +81,14 @@ namespace ki > IntegerT read(const uint8_t bits = bitsizeof::value) { - IntegerT value = 0; - - // Iterate until we've read all of the bits - auto unread_bits = bits; - while (unread_bits > 0) - { - // Calculate how many bits to read from the current byte based on how many bits - // are left and how many bits we still need to read - const uint8_t bits_available = (8 - m_position.get_bit()); - const auto bit_count = unread_bits < bits_available ? unread_bits : bits_available; - - // Find the bit-mask based on how many bits are being read - const uint8_t bit_mask = ((1 << bit_count) - 1) << m_position.get_bit(); - - // Read the bits from the current byte and position them on the least-signficant bit - const uint8_t bits_value = (m_buffer[m_position.get_byte()] & bit_mask) >> m_position.get_bit(); - - // Position the value of the bits we just read based on how many bits of the value - // we've already read - const uint8_t read_bits = bits - unread_bits; - value |= (IntegerT)bits_value << read_bits; - - // Remove the bits we just read from the count of unread bits - unread_bits -= bit_count; - - // Move forward the number of bits we just read - seek(tell() + bit_count); - } - - return value; + return static_cast(read(bits)); } /** * Writes a value to the buffer that occupies a defined number of bits. - * @param value The value to write. - * @param bits The number of bits to use. + * @tparam IntegerT The type of value (must be an integral type). + * @param[in] value The value to write. + * @param[in] bits The number of bits to use. Defaults to the bitsize of IntegerT. */ template < typename IntegerT, @@ -122,53 +96,88 @@ namespace ki > void write(IntegerT value, const uint8_t bits = bitsizeof::value) { - // Iterate until we've written all of the bits - auto unwritten_bits = bits; - while (unwritten_bits > 0) - { - // Calculate how many bits to write based on how many bits are left in the current byte - // and how many bits from the value we still need to write - const uint8_t bits_available = (8 - m_position.get_bit()); - const auto bit_count = unwritten_bits < bits_available ? unwritten_bits : bits_available; - - // Find the bit-mask based on how many bits are being written, and how many bits we've - // already written - const uint8_t written_bits = bits - unwritten_bits; - IntegerT bit_mask = (IntegerT)((1 << bit_count) - 1) << written_bits; - - // Get the bits from the value and position them at the current bit position - uint8_t value_byte = ((value & bit_mask) >> written_bits) & 0xFF; - value_byte <<= m_position.get_bit(); - - // Write the bits into the byte we're currently at - m_buffer[m_position.get_byte()] |= value_byte; - unwritten_bits -= bit_count; - - // Move forward the number of bits we just wrote - seek(tell() + bit_count); - } + write(static_cast(value), bits); } /** * Copy memory from an external buffer into the bitstream's buffer from the current position. - * @param src The buffer to copy data from. - * @param bitsize The number of bits to copy from the src buffer. + * @param[in] src The buffer to copy data from. + * @param[in] bitsize The number of bits to copy from the src buffer. */ void write_copy(uint8_t *src, std::size_t bitsize); /** * Copy memory from the bitstream's buffer into an external buffer. - * @param dst The destination buffer to copy data to. - * @param bitsize The number of bits to copy into the dst buffer. + * @param[out] dst The destination buffer to copy data to. + * @param[in] bitsize The number of bits to copy into the dst buffer. */ void read_copy(uint8_t *dst, std::size_t bitsize); + protected: + virtual uint64_t read(uint8_t bits) = 0; + virtual void write(uint64_t value, uint8_t bits) = 0; + }; + + /** + * A read/write-able stream of bits. + */ + class BitStream : public BitStreamBase + { + public: + explicit BitStream(std::size_t buffer_size = KI_BITSTREAM_DEFAULT_BUFFER_SIZE); + virtual ~BitStream(); + + stream_pos tell() const override; + void seek(stream_pos position) override; + std::size_t capacity() const override; + const uint8_t *data() const override; + + /** + * @copydoc BitStreamBase::read(uint8_t) + * @throws ki::runtime_error Not enough data available to read the specified number of bits. + */ + template < + typename IntegerT, + typename = std::enable_if::value> + > + IntegerT read(const uint8_t bits = bitsizeof::value) + { + return BitStreamBase::read(bits); + } + + template < + typename IntegerT, + typename = std::enable_if::value> + > + void write(IntegerT value, const uint8_t bits = bitsizeof::value) + { + BitStreamBase::write(value, bits); + } + + protected: + uint64_t read(uint8_t bits) override; + void write(uint64_t value, uint8_t bits) override; + private: - uint8_t *m_buffer; + uint8_t * m_buffer; std::size_t m_buffer_size; stream_pos m_position; void expand_buffer(); void validate_buffer(); }; + + /** + * TODO: Documentation + */ + class BitStreamSection : public BitStreamBase + { + public: + explicit BitStreamSection(BitStreamBase &stream, std::size_t size); + + private: + BitStreamBase &m_stream; + stream_pos m_position; + std::size_t m_size; + }; } diff --git a/src/pclass/Property.cpp b/src/pclass/Property.cpp index a849b18..9759867 100644 --- a/src/pclass/Property.cpp +++ b/src/pclass/Property.cpp @@ -73,13 +73,13 @@ namespace pclass throw runtime_error("Called get_object() on a dynamic property. Use get_object(index) instead."); } - void DynamicPropertyBase::write_value_to(BitStream &stream) const + void DynamicPropertyBase::write_value_to(BitStreamBase &stream) const { // The caller must specify an index throw runtime_error("Called write_value_to() on a dynamic property. Use write_value_to(index) instead."); } - void DynamicPropertyBase::read_value_from(BitStream &stream) + void DynamicPropertyBase::read_value_from(BitStreamBase &stream) { // The caller must specify an index throw runtime_error("Called read_value_from() on a dynamic property. Use read_value_from(index) instead."); diff --git a/src/pclass/Type.cpp b/src/pclass/Type.cpp index 97cabad..e9d096e 100644 --- a/src/pclass/Type.cpp +++ b/src/pclass/Type.cpp @@ -38,14 +38,14 @@ namespace pclass return m_type_system; } - void Type::write_to(BitStream& stream, const Value& value) const + void Type::write_to(BitStreamBase &stream, const Value& value) const { std::ostringstream oss; oss << "Type '" << m_name << "' does not implement Type::write_to."; throw runtime_error(oss.str()); } - void Type::read_from(BitStream& stream, Value& value) const + void Type::read_from(BitStreamBase &stream, Value& value) const { std::ostringstream oss; oss << "Type '" << m_name << "' does not implement Type::read_from."; diff --git a/src/serialization/SerializerBinary.cpp b/src/serialization/SerializerBinary.cpp index a4fef18..b37e80c 100644 --- a/src/serialization/SerializerBinary.cpp +++ b/src/serialization/SerializerBinary.cpp @@ -15,7 +15,7 @@ namespace serialization m_root_object = nullptr; } - void SerializerBinary::save(const pclass::PropertyClass* object, BitStream& stream) + void SerializerBinary::save(const pclass::PropertyClass* object, BitStreamBase &stream) { // Write the serializer flags if (FLAG_IS_SET(m_flags, flags::WRITE_SERIALIZER_FLAGS)) @@ -109,7 +109,7 @@ namespace serialization } } - void SerializerBinary::presave_object(const pclass::PropertyClass *object, BitStream& stream) const + void SerializerBinary::presave_object(const pclass::PropertyClass *object, BitStreamBase &stream) const { // If we have an object, write the type hash, otherwise, write NULL (0). if (object) @@ -118,7 +118,7 @@ namespace serialization stream.write(NULL); } - void SerializerBinary::save_object(const pclass::PropertyClass *object, BitStream& stream) const + void SerializerBinary::save_object(const pclass::PropertyClass *object, BitStreamBase &stream) const { // Write any object headers presave_object(object, stream); @@ -156,7 +156,7 @@ namespace serialization } } - void SerializerBinary::save_property(const pclass::PropertyBase *prop, BitStream& stream) const + void SerializerBinary::save_property(const pclass::PropertyBase *prop, BitStreamBase &stream) const { // Remember where we started writing the property data const auto start_pos = stream.tell(); @@ -227,7 +227,7 @@ namespace serialization } } - void SerializerBinary::load(pclass::PropertyClass*& dest, BitStream& stream) + void SerializerBinary::load(pclass::PropertyClass*& dest, BitStreamBase &stream) { // Read the serializer flags if (FLAG_IS_SET(m_flags, flags::WRITE_SERIALIZER_FLAGS)) @@ -240,17 +240,17 @@ namespace serialization } } - void SerializerBinary::preload_object(pclass::PropertyClass*& dest, BitStream& stream) const + void SerializerBinary::preload_object(pclass::PropertyClass*& dest, BitStreamBase &stream) const { } - void SerializerBinary::load_object(pclass::PropertyClass*& dest, BitStream& stream) const + void SerializerBinary::load_object(pclass::PropertyClass*& dest, BitStreamBase &stream) const { } - void SerializerBinary::load_property(pclass::PropertyBase* prop, BitStream& stream) const + void SerializerBinary::load_property(pclass::PropertyBase* prop, BitStreamBase &stream) const { } diff --git a/src/util/BitStream.cpp b/src/util/BitStream.cpp index 597b3e7..7e08c49 100644 --- a/src/util/BitStream.cpp +++ b/src/util/BitStream.cpp @@ -1,8 +1,7 @@ #include "ki/util/BitStream.h" +#include "ki/util/exception.h" #include -#include #include -#include #include namespace ki @@ -137,6 +136,40 @@ namespace ki return copy; } + void BitStreamBase::write_copy(uint8_t *src, const std::size_t bitsize) + { + // Copy all whole bytes + const auto bytes = bitsize / 8; + auto written_bytes = 0; + while (written_bytes < bytes) + { + write(src[written_bytes]); + written_bytes++; + } + + // Copy left over bits + const auto bits = bitsize % 8; + if (bits > 0) + write(src[bytes + 1], bits); + } + + void BitStreamBase::read_copy(uint8_t *dst, const std::size_t bitsize) + { + // Copy all whole bytes + const auto bytes = bitsize / 8; + auto read_bytes = 0; + while (read_bytes < bytes) + { + dst[read_bytes] = read(); + read_bytes++; + } + + // Copy left over bits + const auto bits = bitsize % 8; + if (bits > 0) + dst[bytes + 1] = read(bits); + } + BitStream::BitStream(const size_t buffer_size) { m_buffer = new uint8_t[buffer_size] { 0 }; @@ -170,38 +203,75 @@ namespace ki return m_buffer; } - void BitStream::write_copy(uint8_t *src, const std::size_t bitsize) + uint64_t BitStream::read(const uint8_t bits) { - // Copy all whole bytes - const auto bytes = bitsize / 8; - auto written_bytes = 0; - while (written_bytes < bytes) + // Do we have these bits available to read? + if ((m_position + bits).as_bits() > m_buffer_size * 8) { - write(src[written_bytes]); - written_bytes++; + std::ostringstream oss; + oss << "Not enough data in buffer to read " + << static_cast(bits) << "bits."; + throw runtime_error(oss.str()); } - // Copy left over bits - const auto bits = bitsize % 8; - if (bits > 0) - write(src[bytes + 1], bits); + // Iterate until we've read all of the bits + uint64_t value = 0; + auto unread_bits = bits; + while (unread_bits > 0) + { + // Calculate how many bits to read from the current byte based on how many bits + // are left and how many bits we still need to read + const uint8_t bits_available = (8 - m_position.get_bit()); + const auto bit_count = unread_bits < bits_available ? unread_bits : bits_available; + + // Find the bit-mask based on how many bits are being read + const uint8_t bit_mask = (1 << bit_count) - 1 << m_position.get_bit(); + + // Read the bits from the current byte and position them on the least-signficant bit + const uint8_t bits_value = (m_buffer[m_position.get_byte()] & bit_mask) >> m_position.get_bit(); + + // Position the value of the bits we just read based on how many bits of the value + // we've already read + const uint8_t read_bits = bits - unread_bits; + value |= static_cast(bits_value) << read_bits; + + // Remove the bits we just read from the count of unread bits + unread_bits -= bit_count; + + // Move forward the number of bits we just read + seek(tell() + bit_count); + } + + return value; } - void BitStream::read_copy(uint8_t *dst, const std::size_t bitsize) + void BitStream::write(const uint64_t value, const uint8_t bits) { - // Copy all whole bytes - const auto bytes = bitsize / 8; - auto read_bytes = 0; - while (read_bytes < bytes) + // Iterate until we've written all of the bits + auto unwritten_bits = bits; + while (unwritten_bits > 0) { - dst[read_bytes] = read(); - read_bytes++; - } + // Calculate how many bits to write based on how many bits are left in the current byte + // and how many bits from the value we still need to write + const uint8_t bits_available = (8 - m_position.get_bit()); + const auto bit_count = unwritten_bits < bits_available ? unwritten_bits : bits_available; - // Copy left over bits - const auto bits = bitsize % 8; - if (bits > 0) - dst[bytes + 1] = read(bits); + // Find the bit-mask based on how many bits are being written, and how many bits we've + // already written + const uint8_t written_bits = bits - unwritten_bits; + const auto bit_mask = static_cast((1 << bit_count) - 1) << written_bits; + + // Get the bits from the value and position them at the current bit position + uint8_t value_byte = ((value & bit_mask) >> written_bits) & 0xFF; + value_byte <<= m_position.get_bit(); + + // Write the bits into the byte we're currently at + m_buffer[m_position.get_byte()] |= value_byte; + unwritten_bits -= bit_count; + + // Move forward the number of bits we just wrote + seek(tell() + bit_count); + } } void BitStream::expand_buffer() @@ -216,7 +286,7 @@ namespace ki // Has the buffer reached maximum size? if (new_size == m_buffer_size) - throw std::runtime_error("Buffer cannot be expanded as it has reached maximum size."); + throw runtime_error("Buffer cannot be expanded as it has reached maximum size."); // Allocate a new buffer, copy everything over, and then delete the old buffer auto *new_buffer = new uint8_t[new_size] { 0 }; @@ -230,7 +300,7 @@ namespace ki { // Make sure we haven't underflowed if (m_position.get_byte() < 0) - throw std::runtime_error("Position of buffer is less than 0!"); + throw runtime_error("Position of buffer is less than 0!"); // Expand the buffer if we've overflowed if (m_position.get_byte() >= m_buffer_size) diff --git a/test/src/unit-bitstream.cpp b/test/src/unit-bitstream.cpp index a84c243..371a69b 100644 --- a/test/src/unit-bitstream.cpp +++ b/test/src/unit-bitstream.cpp @@ -254,3 +254,9 @@ TEST_CASE("BitStream buffer expansion", "[bit-stream]") delete bit_stream; SUCCEED(); } + +/** +* TODO: Test reading outside of buffer (should throw) +* TODO: Test read/write copy on BitStream +* TODO: Test BitStreamSection +*/