From 5deb077fe55ab11c2b9986664f2cb71ea9451160 Mon Sep 17 00:00:00 2001 From: Martchus Date: Thu, 31 May 2018 23:26:38 +0200 Subject: [PATCH] Extend Binary{Reader,Writer} to ease binary (de)serialization To implement reflection-enabled binary (de)serialization in https://github.com/Martchus/reflective-rapidjson. --- CMakeLists.txt | 4 +- io/binaryreader.cpp | 25 +++++--- io/binaryreader.h | 138 +++++++++++++++++++++++++++++++++++++++++ io/binarywriter.cpp | 43 ++++++++++--- io/binarywriter.h | 147 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 336 insertions(+), 21 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4160b5c..8a90573 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -136,8 +136,8 @@ set(META_APP_URL "https://github.com/${META_APP_AUTHOR}/${META_PROJECT_NAME}") set(META_APP_DESCRIPTION "Useful C++ classes and routines such as argument parser, IO and conversion utilities") set(META_FEATURES_FOR_COMPILER_DETECTION_HEADER cxx_thread_local) set(META_VERSION_MAJOR 4) -set(META_VERSION_MINOR 14) -set(META_VERSION_PATCH 2) +set(META_VERSION_MINOR 15) +set(META_VERSION_PATCH 0) # find required 3rd party libraries include(3rdParty) diff --git a/io/binaryreader.cpp b/io/binaryreader.cpp index 7873b76..3252cc1 100644 --- a/io/binaryreader.cpp +++ b/io/binaryreader.cpp @@ -19,6 +19,7 @@ using namespace ConversionUtilities; * \class IoUtilities::BinaryReader * \brief Reads primitive data types from a std::istream. * \remarks Supports both, little endian and big endian. + * \sa For automatic serialization of structs, see https://github.com/Martchus/reflective-rapidjson. */ /*! @@ -92,13 +93,7 @@ istream::pos_type BinaryReader::readStreamsize() return streamsize; } -/*! - * \brief Reads a length prefixed string from the current stream. - * - * \remarks Reads the length prefix from the stream and then a string of the denoted length. - * Advances the current position of the stream by the denoted length of the string plus the prefix length. - */ -string BinaryReader::readLengthPrefixedString() +void BinaryReader::bufferVariableLengthInteger() { static constexpr int maxPrefixLength = 8; int prefixLength = 1; @@ -109,12 +104,22 @@ string BinaryReader::readLengthPrefixedString() mask >>= 1; } if (prefixLength > maxPrefixLength) { - throw ConversionException("Length denotation of length-prefixed string exceeds maximum."); + throw ConversionException("Length denotation of variable length unsigned integer exceeds maximum."); } memset(m_buffer, 0, maxPrefixLength); m_stream->read(m_buffer + (maxPrefixLength - prefixLength), prefixLength); *(m_buffer + (maxPrefixLength - prefixLength)) ^= mask; - return readString(BE::toUInt64(m_buffer)); +} + +/*! + * \brief Reads a length prefixed string from the current stream. + * \remarks Reads the length prefix from the stream and then a string of the denoted length. + * Advances the current position of the stream by the denoted length of the string plus the prefix length. + * \todo Make inline in v5. + */ +string BinaryReader::readLengthPrefixedString() +{ + return readString(readVariableLengthUIntBE()); } /*! @@ -142,7 +147,7 @@ string BinaryReader::readTerminatedString(byte termination) { stringstream ss(ios_base::in | ios_base::out | ios_base::binary); ss.exceptions(ios_base::badbit | ios_base::failbit); - m_stream->get(*ss.rdbuf(), termination); // delim byte is not extracted from the stream + m_stream->get(*ss.rdbuf(), static_cast(termination)); // delim byte is not extracted from the stream m_stream->seekg(1, ios_base::cur); // "extract" delim byte manually return ss.str(); } diff --git a/io/binaryreader.h b/io/binaryreader.h index 00b7033..655e213 100644 --- a/io/binaryreader.h +++ b/io/binaryreader.h @@ -41,6 +41,7 @@ public: uint64 readUInt56BE(); int64 readInt64BE(); uint64 readUInt64BE(); + uint64 readVariableLengthUIntBE(); float32 readFloat32BE(); float64 readFloat64BE(); int16 readInt16LE(); @@ -55,6 +56,7 @@ public: uint64 readUInt56LE(); int64 readInt64LE(); uint64 readUInt64LE(); + uint64 readVariableLengthUIntLE(); float32 readFloat32LE(); float64 readFloat64LE(); char readChar(); @@ -78,7 +80,23 @@ public: static uint32 computeCrc32(const char *buffer, std::size_t length); static const uint32 crc32Table[]; + // declare further overloads for read() to ease use of BinaryReader in templates + void read(char &oneCharacter); + void read(byte &oneByte); + void read(bool &oneBool); + void read(std::string &lengthPrefixedString); + void read(int16 &one16BitInt); + void read(uint16 &one16BitUInt); + void read(int32 &one32BitInt); + void read(uint32 &one32BitUInt); + void read(int64 &one64BitInt); + void read(uint64 &one64BitUInt); + void read(float32 &one32BitFloat); + void read(float64 &one64BitFloat); + private: + void bufferVariableLengthInteger(); + std::istream *m_stream; bool m_ownership; char m_buffer[8]; @@ -317,6 +335,16 @@ inline uint64 BinaryReader::readUInt64BE() return ConversionUtilities::BE::toUInt64(m_buffer); } +/*! + * \brief Reads an up to 8 byte long big endian unsigned integer from the current stream and advances the current position of the stream by one to eight byte. + * \throws Throws ConversionException if the size of the integer exceeds the maximum. + */ +inline uint64 BinaryReader::readVariableLengthUIntBE() +{ + bufferVariableLengthInteger(); + return ConversionUtilities::BE::toUInt64(m_buffer); +} + /*! * \brief Reads a 32-bit big endian floating point value from the current stream and advances the current position of the stream by four bytes. */ @@ -461,6 +489,16 @@ inline uint64 BinaryReader::readUInt64LE() return ConversionUtilities::LE::toUInt64(m_buffer); } +/*! + * \brief Reads an up to 8 byte long little endian unsigned integer from the current stream and advances the current position of the stream by one to eight byte. + * \throws Throws ConversionException if the size of the integer exceeds the maximum. + */ +inline uint64 BinaryReader::readVariableLengthUIntLE() +{ + bufferVariableLengthInteger(); + return ConversionUtilities::LE::toUInt64(m_buffer); +} + /*! * \brief Reads a 32-bit little endian floating point value from the current stream and advances the current position of the stream by four bytes. */ @@ -557,6 +595,106 @@ inline float32 BinaryReader::readFixed16LE() { return ConversionUtilities::toFloat32(readUInt32LE()); } + +/*! + * \brief Reads a single character from the current stream and advances the current position of the stream by one byte. + */ +inline void BinaryReader::read(char &oneCharacter) +{ + oneCharacter = readChar(); +} + +/*! + * \brief Reads a single byte/unsigned character from the current stream and advances the current position of the stream by one byte. + */ +inline void BinaryReader::read(byte &oneByte) +{ + oneByte = readByte(); +} + +/*! + * \brief Reads a boolean value from the current stream and advances the current position of the stream by one byte. + * \sa IoUtilities::BitReader + */ +inline void BinaryReader::read(bool &oneBool) +{ + oneBool = readBool(); +} + +/*! + * \brief Reads a length prefixed string from the current stream. + * + * \remarks Reads the length prefix from the stream and then a string of the denoted length. + * Advances the current position of the stream by the denoted length of the string plus the prefix length. + */ +inline void BinaryReader::read(std::string &lengthPrefixedString) +{ + lengthPrefixedString = readLengthPrefixedString(); +} + +/*! + * \brief Reads a 16-bit big endian signed integer from the current stream and advances the current position of the stream by two bytes. + */ +inline void BinaryReader::read(int16 &one16BitInt) +{ + one16BitInt = readInt16BE(); +} + +/*! + * \brief Reads a 16-bit big endian unsigned integer from the current stream and advances the current position of the stream by two bytes. + */ +inline void BinaryReader::read(uint16 &one16BitUInt) +{ + one16BitUInt = readUInt16BE(); +} + +/*! + * \brief Reads a 16-bit big endian signed integer from the current stream and advances the current position of the stream by two bytes. + */ +inline void BinaryReader::read(int32 &one32BitInt) +{ + one32BitInt = readInt16BE(); +} + +/*! + * \brief Reads a 32-bit big endian unsigned integer from the current stream and advances the current position of the stream by four bytes. + */ +inline void BinaryReader::read(uint32 &one32BitUInt) +{ + one32BitUInt = readUInt32BE(); +} + +/*! + * \brief Reads a 64-bit big endian signed integer from the current stream and advances the current position of the stream by eight bytes. + */ +inline void BinaryReader::read(int64 &one64BitInt) +{ + one64BitInt = readInt64BE(); +} + +/*! + * \brief Reads a 64-bit big endian unsigned integer from the current stream and advances the current position of the stream by eight bytes. + */ +inline void BinaryReader::read(uint64 &one64BitUInt) +{ + one64BitUInt = readUInt64BE(); +} + +/*! + * \brief Reads a 32-bit big endian floating point value from the current stream and advances the current position of the stream by four bytes. + */ +inline void BinaryReader::read(float32 &one32BitFloat) +{ + one32BitFloat = readFloat32BE(); +} + +/*! + * \brief Reads a 64-bit big endian floating point value from the current stream and advances the current position of the stream by eight bytes. + */ +inline void BinaryReader::read(float64 &one64BitFloat) +{ + one64BitFloat = readFloat64BE(); +} } // namespace IoUtilities #endif // IOUTILITIES_BINERYREADER_H diff --git a/io/binarywriter.cpp b/io/binarywriter.cpp index 277c7d0..0c0268a 100644 --- a/io/binarywriter.cpp +++ b/io/binarywriter.cpp @@ -13,6 +13,7 @@ using namespace ConversionUtilities; * \class IoUtilities::BinaryWriter * \brief Writes primitive data types to a std::ostream. * \remarks Supports both, little endian and big endian. + * \sa For automatic deserialization of structs, see https://github.com/Martchus/reflective-rapidjson. */ /*! @@ -71,24 +72,48 @@ void BinaryWriter::setStream(ostream *stream, bool giveOwnership) } /*! - * \brief Writes the length of a string and the string itself to the current stream. - * - * Advances the current position of the stream by the length of the string plus the size of the length prefix. + * \brief Writes the specified integer \a value. Conversion to bytes is done using the specified function. */ -void BinaryWriter::writeLengthPrefixedString(const string &value) +void BinaryWriter::writeVariableLengthInteger(uint64 value, void (*getBytes)(uint64, char *)) { - const uint64 size = value.size(); uint64 boundCheck = 0x80; byte prefixLength = 1; for (; boundCheck != 0x8000000000000000; boundCheck <<= 7, ++prefixLength) { - if (size < boundCheck) { - BE::getBytes(size | boundCheck, m_buffer); + if (value < boundCheck) { + getBytes(value | boundCheck, m_buffer); break; } } if (prefixLength == 9) { - throw ConversionException("The size of the string exceeds the maximum."); + throw ConversionException("The variable-length integer to be written exceeds the maximum."); } m_stream->write(m_buffer + 8 - prefixLength, prefixLength); - m_stream->write(value.data(), static_cast(size)); +} + +/*! + * \brief Writes the length of a string and the string itself to the current stream. + * + * Advances the current position of the stream by the length of the string plus the size of the length prefix. + * + * \throws Throws ConversionException if the string size exceeds the maximum. + * \todo Make inline in v5. + */ +void BinaryWriter::writeLengthPrefixedString(const string &value) +{ + writeVariableLengthUIntBE(value.size()); + m_stream->write(value.data(), static_cast(value.size())); +} + +/*! + * \brief Writes the length of a string and the string itself to the current stream. + * + * Advances the current position of the stream by the length of the string plus the size of the length prefix. + * + * \throws Throws ConversionException if the string size exceeds the maximum. + * \todo Make inline in v5. + */ +void BinaryWriter::writeLengthPrefixedCString(const char *value, size_t size) +{ + writeVariableLengthUIntBE(size); + m_stream->write(value, static_cast(size)); } diff --git a/io/binarywriter.h b/io/binarywriter.h index cd6d631..155458b 100644 --- a/io/binarywriter.h +++ b/io/binarywriter.h @@ -4,6 +4,7 @@ #include "../conversion/binaryconversion.h" #include "../conversion/types.h" +#include #include #include #include @@ -41,6 +42,7 @@ public: void writeUInt56BE(uint64 value); void writeInt64BE(int64 value); void writeUInt64BE(uint64 value); + void writeVariableLengthUIntBE(uint64 value); void writeFloat32BE(float32 value); void writeFloat64BE(float64 value); void writeInt16LE(int16 value); @@ -55,11 +57,13 @@ public: void writeUInt56LE(uint64 value); void writeInt64LE(int64 value); void writeUInt64LE(uint64 value); + void writeVariableLengthUIntLE(uint64 value); void writeFloat32LE(float32 value); void writeFloat64LE(float64 value); void writeString(const std::string &value); void writeTerminatedString(const std::string &value); void writeLengthPrefixedString(const std::string &value); + void writeLengthPrefixedCString(const char *value, std::size_t size); void writeBool(bool value); void writeSynchsafeUInt32BE(uint32 valueToConvertAndWrite); void writeFixed8BE(float32 valueToConvertAndWrite); @@ -68,7 +72,24 @@ public: void writeFixed8LE(float32 valueToConvertAndWrite); void writeFixed16LE(float32 valueToConvertAndWrite); + // declare further overloads for write() to ease use of BinaryWriter in templates + void write(char oneChar); + void write(byte oneByte); + void write(bool oneBool); + void write(const std::string &lengthPrefixedString); + void write(const char *lengthPrefixedString); + void write(int16 one16BitInt); + void write(uint16 one16BitUint); + void write(int32 one32BitInt); + void write(uint32 one32BitUint); + void write(int64 one64BitInt); + void write(uint64 one64BitUint); + void write(float32 one32BitFloat); + void write(float64 one64BitFloat); + private: + void writeVariableLengthInteger(uint64 size, void (*getBytes)(uint64, char *)); + std::ostream *m_stream; bool m_ownership; char m_buffer[8]; @@ -306,6 +327,15 @@ inline void BinaryWriter::writeUInt64BE(uint64 value) m_stream->write(m_buffer, sizeof(uint64)); } +/*! + * \brief Writes an up to 8 byte long big endian unsigned integer to the current stream and advances the current position of the stream by one to eight bytes. + * \throws Throws ConversionException if \a value exceeds the maximum. + */ +inline void BinaryWriter::writeVariableLengthUIntBE(uint64 value) +{ + writeVariableLengthInteger(value, static_cast(&ConversionUtilities::BE::getBytes)); +} + /*! * \brief Writes a 32-bit big endian floating point \a value to the current stream and advances the current position of the stream by four bytes. */ @@ -440,6 +470,15 @@ inline void BinaryWriter::writeUInt64LE(uint64 value) m_stream->write(m_buffer, sizeof(uint64)); } +/*! + * \brief Writes an up to 8 byte long little endian unsigned integer to the current stream and advances the current position of the stream by one to eight bytes. + * \throws Throws ConversionException if \a value exceeds the maximum. + */ +inline void BinaryWriter::writeVariableLengthUIntLE(uint64 value) +{ + writeVariableLengthInteger(value, static_cast(&ConversionUtilities::LE::getBytes)); +} + /*! * \brief Writes a 32-bit little endian floating point \a value to the current stream and advances the current position of the stream by four bytes. */ @@ -525,6 +564,114 @@ inline void BinaryWriter::writeFixed16LE(float32 valueToConvertAndWrite) { writeUInt32LE(ConversionUtilities::toFixed16(valueToConvertAndWrite)); } + +/*! + * \brief Writes a single character to the current stream and advances the current position of the stream by one byte. + */ +inline void BinaryWriter::write(char oneChar) +{ + writeChar(oneChar); +} + +/*! + * \brief Writes a single byte to the current stream and advances the current position of the stream by one byte. + */ +inline void BinaryWriter::write(byte oneByte) +{ + writeByte(oneByte); +} + +/*! + * \brief Writes a boolean value to the current stream and advances the current position of the stream by one byte. + */ +inline void BinaryWriter::write(bool oneBool) +{ + writeBool(oneBool); +} + +/*! + * \brief Writes the length of a string and the string itself to the current stream. + * + * Advances the current position of the stream by the length of the string plus the size of the length prefix. + */ +inline void BinaryWriter::write(const std::string &lengthPrefixedString) +{ + writeLengthPrefixedCString(lengthPrefixedString.data(), lengthPrefixedString.size()); +} + +/*! + * \brief Writes the length of a string and the string itself to the current stream. + * + * Advances the current position of the stream by the length of the string plus the size of the length prefix. + */ +inline void BinaryWriter::write(const char *lengthPrefixedString) +{ + writeLengthPrefixedCString(lengthPrefixedString, std::strlen(lengthPrefixedString)); +} + +/*! + * \brief Writes a 16-bit big endian signed integer to the current stream and advances the current position of the stream by two bytes. + */ +inline void BinaryWriter::write(int16 one16BitInt) +{ + writeInt16BE(one16BitInt); +} + +/*! + * \brief Writes a 16-bit big endian unsigned integer to the current stream and advances the current position of the stream by two bytes. + */ +inline void BinaryWriter::write(uint16 one16BitUint) +{ + writeUInt16BE(one16BitUint); +} + +/*! + * \brief Writes a 32-bit big endian signed integer to the current stream and advances the current position of the stream by four bytes. + */ +inline void BinaryWriter::write(int32 one32BitInt) +{ + writeInt32BE(one32BitInt); +} + +/*! + * \brief Writes a 32-bit big endian unsigned integer to the current stream and advances the current position of the stream by four bytes. + */ +inline void BinaryWriter::write(uint32 one32BitUint) +{ + writeUInt32BE(one32BitUint); +} + +/*! + * \brief Writes a 64-bit big endian signed integer to the current stream and advances the current position of the stream by eight bytes. + */ +inline void BinaryWriter::write(int64 one64BitInt) +{ + writeInt64BE(one64BitInt); +} + +/*! + * \brief Writes a 64-bit big endian unsigned integer to the current stream and advances the current position of the stream by eight bytes. + */ +inline void BinaryWriter::write(uint64 one64BitUint) +{ + writeUInt64BE(one64BitUint); +} + +/*! + * \brief Writes a 32-bit big endian floating point \a value to the current stream and advances the current position of the stream by four bytes. + */ +inline void BinaryWriter::write(float32 one32BitFloat) +{ + writeFloat32BE(one32BitFloat); +} + +/*! + * \brief Writes a 64-bit big endian floating point \a value to the current stream and advances the current position of the stream by eight bytes. + */ +inline void BinaryWriter::write(float64 one64BitFloat) +{ + writeFloat64BE(one64BitFloat); +} } // namespace IoUtilities #endif // IO_UTILITIES_BINARYWRITER_H