diff --git a/src/common/bitstream.cpp b/src/common/bitstream.cpp new file mode 100644 index 0000000..06ec438 --- /dev/null +++ b/src/common/bitstream.cpp @@ -0,0 +1,382 @@ +#include +#include "bitstream.h" +#include "util.h" + +#define BITS_TO_BYTES(bits) (((bits) + 7) / 8) + + +BitStream::BitStream(std::vector& exisitingBuf) : + buf(exisitingBuf), + streamBitPos(0), + lastError(Error::NONE) { + +} + +size_t BitStream::getSizeBits() const { + return buf.size() * 8; +} + +size_t BitStream::getRemainingBits() const { + size_t sizeBits = getSizeBits(); + if (streamBitPos >= sizeBits) { + return 0; + } else { + return sizeBits - streamBitPos; + } +} + +size_t BitStream::getRemainingBytes() const { + size_t usedBytes = BITS_TO_BYTES(streamBitPos); + if (usedBytes >= buf.size()) { + return 0; + } else { + return buf.size() - usedBytes; + } +} + +uint8_t* BitStream::getHeadBytePtr() const { + return buf.data() + (streamBitPos / 8); +} + +std::vector::iterator BitStream::getHeadIterator() const { + return buf.begin() + (streamBitPos / 8); +} + +uint8_t* BitStream::getPosBytePtr(size_t bitPos) const { + return buf.data() + (bitPos / 8); +} + +size_t BitStream::getPos() const { + return streamBitPos; +} + +void BitStream::setPos(size_t pos) { + if (pos > buf.size() * 8) { + lastError = Error::INVALID_STREAM_POS; + return; + } + + streamBitPos = pos; +} + +void BitStream::deltaPos(int32_t delta) { + // TODO: Signedness is funky here. Figure out a better way. + int32_t newPos = (int32_t)streamBitPos + delta; + + if (newPos < 0 || newPos > buf.size() * 8) { + lastError = Error::INVALID_STREAM_POS; + return; + } + + streamBitPos = newPos; +} + +void BitStream::alignPos() { + size_t bitsIn = (streamBitPos % 8); + if (bitsIn != 0) { + deltaPos(8 - bitsIn); + } +} + +BitStream::Error BitStream::getLastError() { + return lastError; +} + +void BitStream::readBytes(uint8_t* outBuf, size_t numBytes, bool peek) { + if (numBytes == 0) { + return; + } + + // If the stream position is not aligned on a byte boundary, need to do bit reading + if ((streamBitPos & 0x7) != 0) { + readBits(outBuf, numBytes * 8, peek); + return; + } + + size_t remainingBytes = getRemainingBytes(); + if (remainingBytes < numBytes) { + lastError = Error::READ_TOO_MUCH; + return; + } + + memcpy(outBuf, getHeadBytePtr(), numBytes); + + if (!peek) { + streamBitPos += numBytes * 8; + } +} + +bool BitStream::readBit(bool peek) { + size_t remainingBits = getRemainingBits(); + if (remainingBits < 1) { + lastError = Error::READ_TOO_MUCH; + return false; + } + + bool result = (*getHeadBytePtr() & (0x80 >> (streamBitPos & 0x7))) != 0; + + streamBitPos++; + + return result; +} + +void BitStream::readBits(uint8_t* outBuf, size_t numBits, bool peek) { + //NOTE: WILL CLOBBER any existing data in the bytes that get written to + if (numBits == 0) { + return; + } + + // If the stream position is aligned on a byte boundary and we are reading a quantity of bits divisble by 8, we can use faster byte reading + if ((streamBitPos & 0x7) == 0 && (numBits & 0x7) == 0) { + readBytes(outBuf, numBits / 8, peek); + return; + } + + size_t remainingBits = getRemainingBits(); + if (remainingBits < numBits) { + lastError = Error::READ_TOO_MUCH; + return; + } + + size_t prevStreamBitPos = streamBitPos; + + unsigned char* srcPtr = buf.data(); + + size_t bitsToRead = numBits; + + while (true) { + size_t byteOffset = streamBitPos / 8; + size_t bitOffset = (streamBitPos & 0x7); + size_t bitsLeft = 8 - bitOffset; + + if (bitsLeft >= bitsToRead) { + // We have enough bits remaining in the current source byte to finish the read, so read them all into the current destination byte + // Shift the remaining bits right to be flush with the start of the current destination byte, and mask out the bits to the left + size_t bitGap = (bitsLeft - bitsToRead); + *outBuf = ((srcPtr[byteOffset] >> bitGap) & ((1 << bitsToRead) - 1)); + streamBitPos += bitsToRead; + break; + } else { + // We don't have enough bits remaining in the current byte to finish the read, so read as much as we need into the current destination byte + // Shift the current source byte left to reserve a number of bits on the right to read from the next byte, and mask out the bits to the left + size_t bitsToWriteToSrc = std::min(bitsToRead, (size_t)8); + size_t bitsToReserve = bitsToWriteToSrc - bitsLeft; + + *outBuf = ((srcPtr[byteOffset] & ((1 << bitsLeft) - 1)) << bitsToReserve); + + // Read the rest of the bits we reserved room for in the destination byte from the next source byte + *outBuf |= (srcPtr[byteOffset + 1] >> (8 - bitsToReserve)); + + streamBitPos += bitsToWriteToSrc; + bitsToRead -= bitsToWriteToSrc; + + if (bitsToRead == 0) { + break; + } + + outBuf++; + } + } + + if (peek) { + streamBitPos = prevStreamBitPos; + } +} + +void BitStream::writeBytes(const uint8_t* data, size_t numBytes) { + if (numBytes == 0) { + return; + } + + // If the stream position is not aligned on a byte boundary, need to do bit writing + if ((streamBitPos & 0x7) != 0) { + writeBits(data, numBytes * 8); + return; + } + + // Reserve space for the number of bytes we're going to write + size_t remainingBytes = getRemainingBytes(); + if (remainingBytes < numBytes) { + buf.resize(buf.size() + numBytes - remainingBytes); + } + + memcpy(getHeadBytePtr(), data, numBytes); + + streamBitPos += numBytes * 8; +} + +void BitStream::writeBit(bool value) { + size_t remainingBits = getRemainingBits(); + if (remainingBits < 1) { + buf.resize(buf.size() + 1); + } + + if (value) { + *getHeadBytePtr() |= (0x80 >> (streamBitPos & 0x7)); + } else { + *getHeadBytePtr() &= ~(0x80 >> (streamBitPos & 0x7)); + } + + streamBitPos++; +} + +void BitStream::writeBits(const uint8_t* data, size_t numBits) { + if (numBits == 0) { + return; + } + + // If the stream position is aligned on a byte boundary and we are writing a quantity of bits divisble by 8, we can use faster byte writing + if ((streamBitPos & 0x7) == 0 && (numBits & 0x7) == 0) { + writeBytes(data, numBits / 8); + return; + } + + // Reserve space for the number of bits we're going to write + size_t remainingBits = getRemainingBits(); + if (remainingBits < numBits) { + buf.resize(BITS_TO_BYTES(buf.size() * 8 + numBits - remainingBits)); + } + + unsigned char* dstPtr = buf.data(); + + size_t bitsToWrite = numBits; + + while (true) { + size_t byteOffset = streamBitPos / 8; + size_t bitOffset = (streamBitPos & 0x7); + size_t bitsLeft = 8 - bitOffset; + + if (bitsLeft >= bitsToWrite) { + // We have enough room in the current byte to fit all of the rest of the bits, so write them all from the current source byte + // Shift the remaining bits left to close the gap and be flush with the end of the stream + size_t bitGap = (bitsLeft - bitsToWrite); + dstPtr[byteOffset] |= (*data << bitGap); + streamBitPos += bitsToWrite; + break; + } else { + // We don't have enough room for all of the remaining bits, so write as much as we need to from the current source byte + // Shift the current byte right to un-overlap the bits and be flush with the end of the stream + size_t bitsToWriteFromSrc = std::min(bitsToWrite, (size_t)8); + size_t bitsOverlapped = bitsToWriteFromSrc - bitsLeft; + + dstPtr[byteOffset] |= (*data >> bitsOverlapped); + + // Now write the rest of the bits remaining in the current source byte to the next destination byte + dstPtr[byteOffset + 1] |= (*data << (8 - bitsOverlapped)); + + streamBitPos += bitsToWriteFromSrc; + bitsToWrite -= bitsToWriteFromSrc; + + if (bitsToWrite == 0) { + break; + } + + data++; + } + } +} + +uint16_t BitStream::readStringLength() { + uint16_t strLen = 0; + readBits((uint8_t*)&strLen, 8); + if ((strLen & 0x80) != 0) { + strLen &= 0x7F; + } else { + // NOTE: Swapped byte ordering + strLen = (strLen << 8); + readBits((uint8_t*)&strLen, 8); + } + + return strLen; +} + +void BitStream::read(std::string& str) { + uint16_t strLen = readStringLength(); + + alignPos(); + + str.resize(strLen); + readBytes((uint8_t*)str.data(), strLen); +} + +void BitStream::read(std::wstring& str) { + uint16_t strLen = readStringLength(); + + alignPos(); + + str.resize(strLen); + readBytes((uint8_t*)str.data(), strLen * 2); +} + +void BitStream::readQuantitizedDouble(double& data, size_t numBits, float max, float min, double epsilon) { + double range = static_cast(max - min); + if (range + epsilon < 0.0) { + throw std::invalid_argument("quantitized double range - min value is greater than max value"); + } + else if (numBits == 0) { + throw std::invalid_argument("quantitized double size - no bits, no value"); + } + uint64_t outBuf = 0L; + readBits((uint8_t*)&outBuf, numBits); + if (range < epsilon || outBuf == 0L) { + data = min; + } else { + uint64_t fieldMax = (1 << numBits) - 1; + data = outBuf * range / fieldMax; + if (data + epsilon < 0.0) { + data = 0.0; + } else if (data + epsilon > range) { + data = range; + } + data += min; + } +} + +void BitStream::writeStringLength(uint16_t length) { + if (length < 128) { + uint8_t strLenWithFlag = 0x80 | length; + writeBytes((uint8_t*)&strLenWithFlag, 1); + } else { + // NOTE: Swapped byte ordering + writeBytes(((uint8_t*)&length) + 1, 1); + writeBytes((uint8_t*)&length, 1); + } +} + +void BitStream::write(const std::string& str) { + const uint16_t strLen = (uint16_t)str.length(); + writeStringLength(strLen); + + alignPos(); + + writeBytes((uint8_t*)str.data(), strLen); +} + +void BitStream::write(const std::wstring& str) { + const uint16_t strLen = (uint16_t)str.length(); + writeStringLength(strLen); + + alignPos(); + + writeBytes((uint8_t*)str.data(), strLen * 2); +} + +void BitStream::writeQuantitizedDouble(const double& data, size_t numBits, float max, float min, double epsilon) { + double range = static_cast(max - min); + uint64_t inBuf; + if (range + epsilon < 0.0) { + throw std::invalid_argument("quantitized double range - min value is greater than max value"); + } else if (numBits == 0) { + throw std::invalid_argument("quantitized double size - no bits, no value"); + } else + if (std::fabs(range) < epsilon || data + epsilon <= min) { + inBuf = 0L; + } else { + uint64_t fieldMax = (1 << numBits) - 1; + inBuf = static_cast(((data - min) * fieldMax / range)); + if (inBuf > fieldMax) { + inBuf = fieldMax; + } + } + writeBits((uint8_t*)&inBuf, numBits); +} diff --git a/src/common/bitstream.h b/src/common/bitstream.h index 3f16708..116e640 100644 --- a/src/common/bitstream.h +++ b/src/common/bitstream.h @@ -1,11 +1,7 @@ #pragma once -#include -#include #include -#define BITS_TO_BYTES(bits) (((bits) + 7) / 8) - /** * A stream serializer that reads/writes to an external buffer. * @@ -23,331 +19,97 @@ class BitStream { READ_TOO_MUCH }; - BitStream(std::vector& exisitingBuf) : - buf(exisitingBuf), - streamBitPos(0), - lastError(Error::NONE) { - - } + BitStream(std::vector& exisitingBuf); /** * @return The total number of bits in the buffer */ - size_t getSizeBits() const { - return buf.size() * 8; - } + size_t getSizeBits() const; /** * @return The number of unread bits after the stream pos */ - size_t getRemainingBits() const { - size_t sizeBits = getSizeBits(); - if (streamBitPos >= sizeBits) { - return 0; - } else { - return sizeBits - streamBitPos; - } - } + size_t getRemainingBits() const; /** - * @return The number of COMPLETELY unread bytes after the stream pos - */ - size_t getRemainingBytes() const { - size_t usedBytes = BITS_TO_BYTES(streamBitPos); - if (usedBytes >= buf.size()) { - return 0; - } else { - return buf.size() - usedBytes; - } - } + * @return The number of COMPLETELY unread bytes after the stream pos + */ + size_t getRemainingBytes() const; /** * @return A pointer to the byte holding the stream pos */ - uint8_t* getHeadBytePtr() const { - return buf.data() + (streamBitPos / 8); - } + uint8_t* getHeadBytePtr() const; /** * @return An iterator to the byte holding the stream pos */ - std::vector::iterator getHeadIterator() const { - return buf.begin() + (streamBitPos / 8); - } + std::vector::iterator getHeadIterator() const; /** * @return A pointer to the byte holding the given bit pos */ - uint8_t* getPosBytePtr(size_t bitPos) const { - return buf.data() + (bitPos / 8); - } + uint8_t* getPosBytePtr(size_t) const; /** * @return The current stream pos */ - size_t getPos() const { - return streamBitPos; - } + size_t getPos() const; /** * Sets the current stream pos. */ - void setPos(size_t pos) { - if (pos > buf.size() * 8) { - lastError = Error::INVALID_STREAM_POS; - return; - } - - streamBitPos = pos; - } + void setPos(size_t pos); /** * Moves the stream pos by some delta. */ - void deltaPos(int32_t delta) { - // TODO: Signedness is funky here. Figure out a better way. - int32_t newPos = (int32_t)streamBitPos + delta; - - if (newPos < 0 || newPos > buf.size() * 8) { - lastError = Error::INVALID_STREAM_POS; - return; - } - - streamBitPos = newPos; - } + void deltaPos(int32_t delta); /** * Aligns the stream pos to the next highest byte boundary if necessary. */ - void alignPos() { - size_t bitsIn = (streamBitPos % 8); - if (bitsIn != 0) { - deltaPos(8 - bitsIn); - } - } + void alignPos(); /** * @return The error type if the stream has encountered a serialization error. */ - Error getLastError() { - return lastError; - } + Error getLastError(); /** * Reads a number of bytes. */ - void readBytes(uint8_t* outBuf, size_t numBytes, bool peek = false) { - if (numBytes == 0) { - return; - } - - // If the stream position is not aligned on a byte boundary, need to do bit reading - if ((streamBitPos & 0x7) != 0) { - readBits(outBuf, numBytes * 8, peek); - return; - } - - size_t remainingBytes = getRemainingBytes(); - if (remainingBytes < numBytes) { - lastError = Error::READ_TOO_MUCH; - return; - } - - memcpy(outBuf, getHeadBytePtr(), numBytes); - - if (!peek) { - streamBitPos += numBytes * 8; - } - } + void readBytes(uint8_t* outBuf, size_t numBytes, bool peek = false); /** * @return The boolean value of the next bit in the stream */ - bool readBit(bool peek = false) { - size_t remainingBits = getRemainingBits(); - if (remainingBits < 1) { - lastError = Error::READ_TOO_MUCH; - return false; - } - - bool result = (*getHeadBytePtr() & (0x80 >> (streamBitPos & 0x7))) != 0; - - streamBitPos++; - - return result; - } + bool readBit(bool peek = false); /** * Reads a number of bits. * NOTE: WILL clobber any existing data in the bytes that get written to */ - void readBits(uint8_t* outBuf, size_t numBits, bool peek = false) { - if (numBits == 0) { - return; - } - - // If the stream position is aligned on a byte boundary and we are reading a quantity of bits divisble by 8, we can use faster byte reading - if ((streamBitPos & 0x7) == 0 && (numBits & 0x7) == 0) { - readBytes(outBuf, numBits / 8, peek); - return; - } - - size_t remainingBits = getRemainingBits(); - if (remainingBits < numBits) { - lastError = Error::READ_TOO_MUCH; - return; - } - - size_t prevStreamBitPos = streamBitPos; - - unsigned char* srcPtr = buf.data(); - - size_t bitsToRead = numBits; - - while (true) { - size_t byteOffset = streamBitPos / 8; - size_t bitOffset = (streamBitPos & 0x7); - size_t bitsLeft = 8 - bitOffset; - - if (bitsLeft >= bitsToRead) { - // We have enough bits remaining in the current source byte to finish the read, so read them all into the current destination byte - // Shift the remaining bits right to be flush with the start of the current destination byte, and mask out the bits to the left - size_t bitGap = (bitsLeft - bitsToRead); - *outBuf = ((srcPtr[byteOffset] >> bitGap) & ((1 << bitsToRead) - 1)); - streamBitPos += bitsToRead; - break; - } else { - // We don't have enough bits remaining in the current byte to finish the read, so read as much as we need into the current destination byte - // Shift the current source byte left to reserve a number of bits on the right to read from the next byte, and mask out the bits to the left - size_t bitsToWriteToSrc = std::min(bitsToRead, (size_t)8); - size_t bitsToReserve = bitsToWriteToSrc - bitsLeft; - - *outBuf = ((srcPtr[byteOffset] & ((1 << bitsLeft) - 1)) << bitsToReserve); - - // Read the rest of the bits we reserved room for in the destination byte from the next source byte - *outBuf |= (srcPtr[byteOffset + 1] >> (8 - bitsToReserve)); - - streamBitPos += bitsToWriteToSrc; - bitsToRead -= bitsToWriteToSrc; - - if (bitsToRead == 0) { - break; - } - - outBuf++; - } - } - - if (peek) { - streamBitPos = prevStreamBitPos; - } - } + void readBits(uint8_t* outBuf, size_t numBits, bool peek = false); /** * Writes a number of bytes. */ - void writeBytes(const uint8_t* data, size_t numBytes) { - if (numBytes == 0) { - return; - } - - // If the stream position is not aligned on a byte boundary, need to do bit writing - if ((streamBitPos & 0x7) != 0) { - writeBits(data, numBytes * 8); - return; - } - - // Reserve space for the number of bytes we're going to write - size_t remainingBytes = getRemainingBytes(); - if (remainingBytes < numBytes) { - buf.resize(buf.size() + numBytes - remainingBytes); - } - - memcpy(getHeadBytePtr(), data, numBytes); - - streamBitPos += numBytes * 8; - } + void writeBytes(const uint8_t* data, size_t numBytes); /** * Writes a boolean value as a single bit. */ - void writeBit(bool value) { - size_t remainingBits = getRemainingBits(); - if (remainingBits < 1) { - buf.resize(buf.size() + 1); - } - - if (value) { - *getHeadBytePtr() |= (0x80 >> (streamBitPos & 0x7)); - } else { - *getHeadBytePtr() &= ~(0x80 >> (streamBitPos & 0x7)); - } - - streamBitPos++; - } + void writeBit(bool value); /** * Writes a number of bits. */ - void writeBits(const uint8_t* data, size_t numBits) { - if (numBits == 0) { - return; - } - - // If the stream position is aligned on a byte boundary and we are writing a quantity of bits divisble by 8, we can use faster byte writing - if ((streamBitPos & 0x7) == 0 && (numBits & 0x7) == 0) { - writeBytes(data, numBits / 8); - return; - } - - // Reserve space for the number of bits we're going to write - size_t remainingBits = getRemainingBits(); - if (remainingBits < numBits) { - buf.resize(BITS_TO_BYTES(buf.size() * 8 + numBits - remainingBits)); - } - - unsigned char* dstPtr = buf.data(); - - size_t bitsToWrite = numBits; - - while (true) { - size_t byteOffset = streamBitPos / 8; - size_t bitOffset = (streamBitPos & 0x7); - size_t bitsLeft = 8 - bitOffset; - - if (bitsLeft >= bitsToWrite) { - // We have enough room in the current byte to fit all of the rest of the bits, so write them all from the current source byte - // Shift the remaining bits left to close the gap and be flush with the end of the stream - size_t bitGap = (bitsLeft - bitsToWrite); - dstPtr[byteOffset] |= (*data << bitGap); - streamBitPos += bitsToWrite; - break; - } else { - // We don't have enough room for all of the remaining bits, so write as much as we need to from the current source byte - // Shift the current byte right to un-overlap the bits and be flush with the end of the stream - size_t bitsToWriteFromSrc = std::min(bitsToWrite, (size_t)8); - size_t bitsOverlapped = bitsToWriteFromSrc - bitsLeft; - - dstPtr[byteOffset] |= (*data >> bitsOverlapped); - - // Now write the rest of the bits remaining in the current source byte to the next destination byte - dstPtr[byteOffset + 1] |= (*data << (8 - bitsOverlapped)); - - streamBitPos += bitsToWriteFromSrc; - bitsToWrite -= bitsToWriteFromSrc; - - if (bitsToWrite == 0) { - break; - } - - data++; - } - } - } + void writeBits(const uint8_t* data, size_t numBits); /** - * Templated read functions. - */ + * Templated read functions. + */ template void read(std::array& outBuf, bool peek = false) { static_assert(!std::is_pointer::value, "Object type must not be a pointer."); @@ -362,37 +124,23 @@ class BitStream { readBytes((uint8_t*)&object, sizeof(T), peek); } - uint16_t readStringLength() { - uint16_t strLen = 0; - readBits((uint8_t*)&strLen, 8); - if ((strLen & 0x80) != 0) { - strLen &= 0x7F; - } else { - // NOTE: Swapped byte ordering - strLen = (strLen << 8); - readBits((uint8_t*)&strLen, 8); - } - - return strLen; - } - - void read(std::string& str) { - uint16_t strLen = readStringLength(); + uint16_t readStringLength(); - alignPos(); - - str.resize(strLen); - readBytes((uint8_t*)str.data(), strLen); - } + void read(std::string& str); - void read(std::wstring& str) { - uint16_t strLen = readStringLength(); + void read(std::wstring& str); - alignPos(); - - str.resize(strLen); - readBytes((uint8_t*)str.data(), strLen * 2); - } + /** + Read from buffer a double value within a min-max float range as a value in a 0-[max] integer range. + @param data the double value to write to + @param numBits the number of bits to be occupied in the buffer, the max converted integer value + @param max the maximum float value + @param min the minimum float value, defaulting to 0.0f + @param epsilon a comparable tolerable difference between decimal numbers, defaulting to 0.001 + @throw invalid_argument if min is greater than max + @throw invalid_argument if the number of bits is zero + */ + void readQuantitizedDouble(double& data, size_t numBits, float max, float min = 0.0f, double epsilon = 0.001); /** * Templated write functions. @@ -418,34 +166,23 @@ class BitStream { writeBytes((const uint8_t*)&object, sizeof(T)); } - void writeStringLength(uint16_t length) { - if (length < 128) { - uint8_t strLenWithFlag = 0x80 | length; - writeBytes((uint8_t*)&strLenWithFlag, 1); - } else { - // NOTE: Swapped byte ordering - writeBytes(((uint8_t*)&length) + 1, 1); - writeBytes((uint8_t*)&length, 1); - } - } - - void write(const std::string& str) { - const uint16_t strLen = (uint16_t)str.length(); - writeStringLength(strLen); + void writeStringLength(uint16_t length); - alignPos(); + void write(const std::string& str); - writeBytes((uint8_t*)str.data(), strLen); - } - - void write(const std::wstring& str) { - const uint16_t strLen = (uint16_t)str.length(); - writeStringLength(strLen); - - alignPos(); + void write(const std::wstring& str); - writeBytes((uint8_t*)str.data(), strLen * 2); - } + /** + Write to buffer a double value within a min-max float range as a value in a 0-[max] integer range. + @param data the double to write to buffer + @param numBits the number of bits to be occupied in the buffer, the max converted integer value + @param max the maximum float value + @param min the minimum float value, defaulting to 0.0f + @param epsilon a comparable tolerable difference between decimal numbers, defaulting to 0.001 + @throw invalid_argument if min is greater than max + @throw invalid_argument if the number of bits is zero + */ + void writeQuantitizedDouble(const double& data, size_t numBits, float max, float min = 0.0f, double epsilon = 0.001); std::vector& buf; diff --git a/src/common/bitstream_test.cpp b/src/common/bitstream_test.cpp index 011f807..522d09e 100644 --- a/src/common/bitstream_test.cpp +++ b/src/common/bitstream_test.cpp @@ -1,6 +1,8 @@ +#include #include "bitstream.h" #include "log.h" #include "test.h" +#include "util.h" class PlanetsideBitstream { public: @@ -119,6 +121,117 @@ void testBitstreamReadBits() { } } +void testBitstreamReadQuantitizedFloat() { + static std::vector expectedBuf = std::vector({ + 0x6C, 0x2D, 0x76, 0x55, 0x35, 0xCA, 0x16 //6C2D7 65535 CA16 + }); + BitStream bitstream(expectedBuf); + + double x; + double y; + double z; + bitstream.readQuantitizedDouble(x, 20, 8192.0); + bitstream.readQuantitizedDouble(y, 20, 8192.0); + bitstream.readQuantitizedDouble(z, 16, 1024.0); + assertEqual(compareDecimals(x, 3674.85, 0.01), 0); + assertEqual(compareDecimals(y, 2726.7917, 0.01), 0); + assertEqual(compareDecimals(z, 91.1581, 0.01), 0); +} + +void testBitstreamWriteQuantitizedFloat() { + static std::vector expectedBuf = std::vector({ + 0x6C, 0x2D, 0x76, 0x55, 0x35, 0xCA, 0x16 //6C2D7 65535 CA16 + }); + BitStream expected_bitstream(expectedBuf); + + double a = 3674.85; + double b = 2726.7917; + double c = 91.1581; + std::vector bitstreamBuf; + BitStream test_bitstream(bitstreamBuf); + test_bitstream.writeQuantitizedDouble(a, 20, 8192.0); + test_bitstream.writeQuantitizedDouble(b, 20, 8192.0); + test_bitstream.writeQuantitizedDouble(c, 16, 1024.0); + assertBuffersEqual(bitstreamBuf, expectedBuf); +} + +void testBitstreamReadQuantitizedFloatLimits() { + static std::vector expectedBuf = std::vector({ + 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF //FFFF 0000 FFFF + }); + BitStream bitstream(expectedBuf); + + double a; + double b; + double c; + bitstream.readQuantitizedDouble(a, 16, 256.0, -256.0); + bitstream.readQuantitizedDouble(b, 16, 256.0, -256.0); + bitstream.readQuantitizedDouble(c, 16, 256.0, -256.0); + assertEqual(compareDecimals(a, 256.0, 0.01), 0); + assertEqual(compareDecimals(b, -256.0, 0.01), 0); + assertEqual(compareDecimals(c, 256.0, 0.01), 0); +} + +void testBitstreamWriteQuantitizedFloatLimits() { + static std::vector expectedBuf = std::vector({ + 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF //FFFF 0000 FFFF + }); + BitStream test_bitstream(expectedBuf); + + double a = 260.5; //too high for 256.0 + double b = -260.5; //too low for -256.0 + std::vector bitstreamBuf; + BitStream bitstream(bitstreamBuf); + bitstream.writeQuantitizedDouble(a, 16, 256.0, -256.0); + bitstream.writeQuantitizedDouble(b, 16, 256.0, -256.0); + bitstream.writeQuantitizedDouble(a, 16, 256.0, -256.0); + assertBuffersEqual(bitstreamBuf, expectedBuf); //confirms limiting +} + +void testBitStreamWriteAndReadBackQuantitizedFloats() { + double a = 3674.85; + double b = 2726.79; + double c = 91.1421; + + std::vector bitstreamBuf; + BitStream bitstream(bitstreamBuf); + bitstream.writeQuantitizedDouble(a, 20, 8192.0); + bitstream.writeQuantitizedDouble(b, 20, 8192.0); + bitstream.writeQuantitizedDouble(c, 16, 1024.0); + + bitstream.setPos(0); //rewind the stream + double ax; + double bx; + double cx; + bitstream.readQuantitizedDouble(ax, 20, 8192.0); + bitstream.readQuantitizedDouble(bx, 20, 8192.0); + bitstream.readQuantitizedDouble(cx, 16, 1024.0); + assertEqual(compareDecimals(ax, a, 0.01), 0); + assertEqual(compareDecimals(bx, b, 0.01), 0); + assertEqual(compareDecimals(cx, c, 0.01), 0); +} + +void testBitStreamReadAndWriteQuantitizedFloats() { + static std::vector expectedBuf = std::vector({ + 0x6C, 0x2D, 0x76, 0x55, 0x35, 0xCA, 0x16 //6C2D7 65535 CA16 + }); + BitStream test_bitstream(expectedBuf); + + double a; + double b; + double c; + test_bitstream.readQuantitizedDouble(a, 20, 8192.0); + test_bitstream.readQuantitizedDouble(b, 20, 8192.0); + test_bitstream.readQuantitizedDouble(c, 16, 1024.0); + + std::vector bitstreamBuf; + BitStream bitstream(bitstreamBuf); + bitstream.writeQuantitizedDouble(a, 20, 8192.0); + bitstream.writeQuantitizedDouble(b, 20, 8192.0); + bitstream.writeQuantitizedDouble(c, 16, 1024.0); + assertBuffersEqual(bitstreamBuf, expectedBuf); +} + void testBitstream() { testBitstreamWriteBitsBasic(); testBitstreamWriteBits(); @@ -128,4 +241,11 @@ void testBitstream() { // TODO: Test mixed bits and bytes write/read // TODO: Test primitive write/read // TODO: Test string write/read + + testBitstreamReadQuantitizedFloat(); + testBitstreamWriteQuantitizedFloat(); + testBitstreamReadQuantitizedFloatLimits(); + testBitstreamWriteQuantitizedFloatLimits(); + testBitStreamWriteAndReadBackQuantitizedFloats(); + testBitStreamReadAndWriteQuantitizedFloats(); } diff --git a/src/common/packet/pkt_test_control.cpp b/src/common/packet/pkt_test_control.cpp index 6ed136f..033509e 100644 --- a/src/common/packet/pkt_test_control.cpp +++ b/src/common/packet/pkt_test_control.cpp @@ -1,3 +1,4 @@ +#include #include #include "pkt_all.h" #include "pkt_test.h" diff --git a/src/common/packet/pkt_test_crypto.cpp b/src/common/packet/pkt_test_crypto.cpp index 1a23400..4766447 100644 --- a/src/common/packet/pkt_test_crypto.cpp +++ b/src/common/packet/pkt_test_crypto.cpp @@ -1,3 +1,4 @@ +#include #include #include "pkt_all.h" #include "pkt_test.h" diff --git a/src/common/packet/pkt_test_game.cpp b/src/common/packet/pkt_test_game.cpp index 8e7422d..3a8626a 100644 --- a/src/common/packet/pkt_test_game.cpp +++ b/src/common/packet/pkt_test_game.cpp @@ -1,3 +1,4 @@ +#include #include #include "pkt_all.h" #include "pkt_test.h" diff --git a/src/common/test.cpp b/src/common/test.cpp index 467d6f2..28227ba 100644 --- a/src/common/test.cpp +++ b/src/common/test.cpp @@ -1,3 +1,4 @@ +#include #include "test.h" void printAssertMessage() { diff --git a/src/common/test.h b/src/common/test.h index aee0873..8781879 100644 --- a/src/common/test.h +++ b/src/common/test.h @@ -1,7 +1,5 @@ #pragma once -#include "util.h" - #define assertBuffersEqual(actual, expected, ...) do {\ if (actual != expected) {\ std::cout << "Assert failed! File \"" << __FILE__ << "\" line " << __LINE__ << std::endl;\ diff --git a/src/common/util.cpp b/src/common/util.cpp index 921c308..95668f1 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -1,8 +1,5 @@ -#include #include -#include #include -#include "bitstream.h" #include "util.h" uint32_t randomUnsignedInt() { @@ -79,3 +76,23 @@ std::vector hexToBytes(std::string hexStr) { return bytes; } + +/** +Basic double comparison test using std::fabs. +May be repurposed for other numeric data types cautiously. +Models return values after Java Comparator's compare(T o1, T o2). +@param a the first number +@param b the second number +@param epsilon the minimal tolerable difference between a and b before they are no longer considered equal +@return a negative integer, zero, or a positive integer as the first argument is <, ==, or > the second +*/ +int compareDecimals(double a, double b, double epsilon) { + double epsilonFabs = std::fabs(epsilon); + if (std::fabs(a - b) < epsilonFabs) { + return 0; + } else if (a + epsilonFabs < b) { + return -1; + } else { + return 1; + } +} diff --git a/src/common/util.h b/src/common/util.h index de58c7b..c8a5bca 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -1,9 +1,5 @@ #pragma once -#include -#include -#include "bitstream.h" - uint32_t randomUnsignedInt(); uint8_t randomUnsignedChar(); std::size_t getTimeSeconds(); @@ -13,3 +9,5 @@ std::size_t getTimeNanoseconds(); void utilSleep(size_t ms); std::vector hexToBytes(std::string hexStr); + +int compareDecimals(double a, double b, double epsilon);