Skip to content

Commit

Permalink
Restructure code
Browse files Browse the repository at this point in the history
  • Loading branch information
dktapps committed Apr 9, 2024
1 parent 8a962e7 commit 85f3daa
Show file tree
Hide file tree
Showing 9 changed files with 746 additions and 682 deletions.
239 changes: 239 additions & 0 deletions Serializers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
#ifndef SERIALIZERS_H
#define SERIALIZERS_H

extern "C" {
#include "Zend/zend_string.h"
#include "Zend/zend_exceptions.h"
}
#include "classes/DataDecodeException.h"
#include <algorithm>
#include <type_traits>

enum class ByteOrder {
BigEndian,
LittleEndian,
#ifdef WORDS_BIGENDIAN
Native = BigEndian
#else
Native = LittleEndian
#endif
};

template<typename TValue>
union Flipper {
char bytes[sizeof(TValue)];
TValue value;
};

template<typename TValue>
static inline bool readByte(unsigned char* buffer, size_t used, size_t& offset, TValue& result) {
const auto SIZE = sizeof(TValue);
if (used - offset < SIZE) {
zend_throw_exception_ex(data_decode_exception_ce, 0, "Need at least %zu bytes, but only have %zu bytes", SIZE, used - offset);
return false;
}

result = *(reinterpret_cast<TValue*>(&buffer[offset]));

offset += SIZE;
return true;
}
template<typename TValue, ByteOrder byteOrder>
static inline bool readFixedSizeType(unsigned char* bytes, size_t used, size_t& offset, TValue& result) {

const auto SIZE = sizeof(TValue);
if (used - offset < SIZE) {
zend_throw_exception_ex(data_decode_exception_ce, 0, "Need at least %zu bytes, but only have %zu bytes", SIZE, used - offset);
return false;
}

Flipper<TValue> flipper;

memcpy(flipper.bytes, &bytes[offset], sizeof(flipper.bytes));
if (byteOrder != ByteOrder::Native) {
std::reverse(std::begin(flipper.bytes), std::end(flipper.bytes));
}

result = flipper.value;

offset += SIZE;
return true;
}

template<typename TValue, ByteOrder byteOrder>
static inline bool readInt24(unsigned char* bytes, size_t used, size_t& offset, TValue& result) {
const size_t SIZE = 3;

if (used - offset < SIZE) {
zend_throw_exception_ex(data_decode_exception_ce, 0, "Need at least %zu bytes, but only have %zu bytes", SIZE, used - offset);
return false;
}

result = 0;
if (byteOrder == ByteOrder::LittleEndian) {
result |= bytes[offset];
result |= bytes[offset + 1] << 8;
result |= bytes[offset + 2] << 16;
}
else {
result |= bytes[offset + 2];
result |= bytes[offset + 1] << 8;
result |= bytes[offset] << 16;
}

const size_t SIGNED_SHIFT = std::is_signed<TValue>::value ? (sizeof(TValue) - SIZE) * CHAR_BIT : 0;
if (SIGNED_SHIFT > 0) {
result = (result << SIGNED_SHIFT) >> SIGNED_SHIFT;
}

offset += SIZE;
return true;
}

struct VarIntConstants {
static const unsigned char BITS_PER_BYTE = 7u;

template<size_t TYPE_BITS>
static const unsigned char MAX_BYTES = TYPE_BITS / BITS_PER_BYTE + ((TYPE_BITS % BITS_PER_BYTE) > 0);

static const unsigned char VALUE_MASK = static_cast<unsigned char>(~(1u << BITS_PER_BYTE));
static const unsigned char MSB_MASK = static_cast<unsigned char>(1u << BITS_PER_BYTE);
};

template<typename TValue>
static inline bool readUnsignedVarInt(unsigned char* bytes, size_t used, size_t& offset, TValue& result) {
const auto TYPE_BITS = sizeof(TValue) * CHAR_BIT;
result = 0;
for (unsigned int shift = 0; shift < TYPE_BITS; shift += VarIntConstants::BITS_PER_BYTE) {
if (offset >= used) {
zend_throw_exception(data_decode_exception_ce, "No bytes left in buffer", 0);
return false;
}

auto byte = bytes[offset++];
result |= (byte & VarIntConstants::VALUE_MASK) << shift;
if ((byte & VarIntConstants::MSB_MASK) == 0) {
return true;
}
}

zend_throw_exception_ex(data_decode_exception_ce, 0, "VarInt did not terminate after %u bytes!", VarIntConstants::MAX_BYTES<TYPE_BITS>);
return false;
}

template<typename TUnsignedValue, typename TSignedValue>
static inline bool readSignedVarInt(unsigned char* bytes, size_t used, size_t& offset, TSignedValue& result) {
TUnsignedValue unsignedResult;
if (!readUnsignedVarInt<TUnsignedValue>(bytes, used, offset, unsignedResult)) {
return false;
}

TUnsignedValue mask = 0;
if (unsignedResult & 1) {
//we don't know the type of TUnsignedValue here so we can't just use ~0
mask = ~mask;
}

result = static_cast<TSignedValue>((unsignedResult >> 1) ^ mask);
return true;
}


static inline void extendBuffer(zend_string*& buffer, size_t offset, size_t usedBytes) {
size_t requiredSize = offset + usedBytes;
if (ZSTR_LEN(buffer) < requiredSize) {
size_t doubleSize = ZSTR_LEN(buffer) * 2;
buffer = zend_string_realloc(buffer, doubleSize > requiredSize ? doubleSize : requiredSize, 0);
ZSTR_VAL(buffer)[ZSTR_LEN(buffer)] = '\0'; //make sure null terminator is always set, to stop sprintf reading out-of-bounds
}
else {
buffer = zend_string_separate(buffer, 0);
}
}

template<typename TValue>
static void writeByte(zend_string*& buffer, size_t& offset, TValue value) {
extendBuffer(buffer, offset, sizeof(TValue));

ZSTR_VAL(buffer)[offset] = *reinterpret_cast<char*>(&value);

offset += sizeof(TValue);
}

template<typename TValue, ByteOrder byteOrder>
static void writeFixedSizeType(zend_string*& buffer, size_t& offset, TValue value) {
extendBuffer(buffer, offset, sizeof(TValue));

Flipper<TValue> flipper;
flipper.value = value;

if (byteOrder != ByteOrder::Native) {
std::reverse(std::begin(flipper.bytes), std::end(flipper.bytes));
}

memcpy(&ZSTR_VAL(buffer)[offset], flipper.bytes, sizeof(flipper.bytes));

offset += sizeof(TValue);
}

template<typename TValue, ByteOrder byteOrder>
static void writeInt24(zend_string*& buffer, size_t& offset, TValue value) {
const size_t SIZE = 3;
extendBuffer(buffer, offset, SIZE);

if (byteOrder == ByteOrder::LittleEndian) {
ZSTR_VAL(buffer)[offset] = value & 0xff;
ZSTR_VAL(buffer)[offset + 1] = (value >> 8) & 0xff;
ZSTR_VAL(buffer)[offset + 2] = (value >> 16) & 0xff;
}
else {
ZSTR_VAL(buffer)[offset] = (value >> 16) & 0xff;
ZSTR_VAL(buffer)[offset + 1] = (value >> 8) & 0xff;
ZSTR_VAL(buffer)[offset + 2] = value & 0xff;
}

offset += SIZE;
}

template<typename TValue>
static inline void writeUnsignedVarInt(zend_string*& buffer, size_t& offset, TValue value) {
const auto TYPE_BITS = sizeof(TValue) * CHAR_BIT;
char result[VarIntConstants::MAX_BYTES<TYPE_BITS>];

TValue remaining = value;

for (auto i = 0; i < VarIntConstants::MAX_BYTES<TYPE_BITS>; i++) {
unsigned char nextByte = remaining & VarIntConstants::VALUE_MASK;

TValue nextRemaining = remaining >> VarIntConstants::BITS_PER_BYTE;

if (nextRemaining == 0) {
result[i] = nextByte;

auto usedBytes = i + 1;
extendBuffer(buffer, offset, usedBytes);
memcpy(&ZSTR_VAL(buffer)[offset], &result[0], usedBytes);
offset += usedBytes;

return;
}

result[i] = nextByte | VarIntConstants::MSB_MASK;
remaining = nextRemaining;
}

zend_value_error("Value too large to be encoded as a VarInt");
}

template<typename TUnsignedType, typename TSignedType>
static inline void writeSignedVarInt(zend_string*& buffer, size_t& offset, TSignedType value) {
TUnsignedType mask = 0;
if (value < 0) {
//we don't know the type of TUnsignedType here, can't use ~0 directly (the compiler will optimise this anyway)
mask = ~mask;
}

writeUnsignedVarInt<TUnsignedType>(buffer, offset, (static_cast<TUnsignedType>(value) << 1) ^ mask);
}

#endif
Loading

0 comments on commit 85f3daa

Please sign in to comment.