Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
build/*
.vscode/*
.DS_Store
.DS_Store
.cache/*
2 changes: 2 additions & 0 deletions include/core/dary_heap.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#ifndef LIB_DARY_HEAP_H
#define LIB_DARY_HEAP_H

#include "core/serializer.h"
#include <vector>
#include <unordered_map>
#include <functional>
Expand Down Expand Up @@ -152,6 +153,7 @@ class dary_heap {
throw std::out_of_range("heap is empty");
return heap_.front().value;
}

};

} // namespace core
Expand Down
170 changes: 170 additions & 0 deletions include/core/serializer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
#ifndef LIB_SERIALIZER_H
#define LIB_SERIALIZER_H


#include <cstddef> // for size_t
#include <cstring>
#include <stdexcept>
#include <string>
#include <vector>


namespace core {


template<typename T>
class AbstractSerializer {
public:
virtual ~AbstractSerializer() = default;
// create a buffer and serialize object to it however you want
virtual char* serialize(const T& obj) const = 0;

// deserialize from some T that was AbstractSerializer::serialize into that buffer
virtual T deserialize(char* buffer, size_t size) const = 0;

// how many bytes we need to serialize object T
virtual size_t bytesRequired() const = 0;
};
}; // namespace core


class OBufferArchive {
public:
static constexpr bool isLoading = false;

OBufferArchive() = default;

// Return a const reference to the underlying buffer
const std::vector<char>& buffer() const { return buffer_; }

// Alternatively, a pointer + size if needed
const char* data() const { return buffer_.data(); }
std::size_t size() const { return buffer_.size(); }

// Clears the internal buffer
void clear() { buffer_.clear(); }

template<typename T>
OBufferArchive& operator&(const T& value) {
static_assert(std::is_arithmetic<T>::value,
"OBufferArchive only supports arithmetic by default here. "
"Use custom serialize(...) for complex types.");
writeBytes(&value, sizeof(T));
return *this;
}

// In OBufferArchive:
OBufferArchive& operator&(const char* cstr) {
// 1) Determine length (0 if cstr == nullptr)
std::size_t length = 0;
if (cstr) {
length = std::strlen(cstr);
}

// 2) Write length as an arithmetic type
*this& length; // calls the arithmetic overload

// 3) Write the raw characters if length > 0
if (length > 0) {
writeBytes(cstr, length);
}

return *this;
}

OBufferArchive& operator&(const std::string& str) {
// 1) write length
std::size_t length = str.size();

*this& length; // calls the arithmetic overload

// 2) write the raw characters
if (length > 0) {
writeBytes(str.data(), length);
}
return *this;
}

private:
std::vector<char> buffer_;

void writeBytes(const void* ptr, std::size_t size) {
const char* cptr = static_cast<const char*>(ptr);
buffer_.insert(buffer_.end(), cptr, cptr + size);
}
};

class IBufferArchive {
public:
static constexpr bool isLoading = true;

// The constructor takes a pointer to an existing buffer and its length.
IBufferArchive(const char* data, std::size_t size) : data_(data), size_(size), pos_(0) {}

// No copying to avoid confusion, but you can allow it if you like
IBufferArchive(const IBufferArchive&) = delete;
IBufferArchive& operator=(const IBufferArchive&) = delete;

// -----------------------------
// operator& for arithmetic T
// -----------------------------
template<typename T>
IBufferArchive& operator&(T& value) {
static_assert(std::is_arithmetic<T>::value,
"IBufferArchive only supports arithmetic by default. "
"Use custom serialize(...) for non-trivial types.");
readBytes(&value, sizeof(T));
return *this;
}

// -----------------------------
// operator& for std::string
// -----------------------------
IBufferArchive& operator&(std::string& str) {
// 1) read length
std::size_t length = 0;
*this& length;
// 2) read the characters
str.clear();
if (length > 0) {
str.resize(length);
readBytes(&str[0], length);
}
return *this;
}

IBufferArchive& operator&(const char*& str) {
// 1) read the length
std::size_t length = 0;
*this& length; // calls the arithmetic overload

// 2) if there's data, allocate + read
if (length > 0) {
// allocate a buffer, read the bytes
char* temp = new char[length + 1];
readBytes(temp, length);
temp[length] = '\0'; // null-terminate
str = temp; // store the pointer in 'str'
} else {
// no data means make str null
str = nullptr;
}
return *this;
}

private:
const char* data_{nullptr};
std::size_t size_{0};
std::size_t pos_{0};

void readBytes(void* dst, std::size_t len) {
if (pos_ + len > size_) {
throw std::runtime_error("IBufferArchive: out of range read");
}
std::memcpy(dst, data_ + pos_, len);
pos_ += len;
}
};


#endif
File renamed without changes.
201 changes: 201 additions & 0 deletions tests/serialize_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
// d-ary heap tests

#include <core/dary_heap.h>
#include <core/serializer.h>
#include <cstddef>
#include <cstring>
#include <gtest/gtest.h>

using namespace core;

struct VariableSizeStruct {
size_t hash;
size_t random_number;
char* data;
};

class RandomSerializer {
public:
// Round up to the nearest multiple of 'boundary' (for alignment)
size_t RoundUp(size_t length, size_t boundary) const {
static const size_t oneless = boundary - 1;
return (length + oneless) & ~oneless;
}

// Compute how many bytes are needed to serialize the struct properly
size_t bytesRequired(size_t data_size) const {
return RoundUp(sizeof(size_t) * 3 + data_size + 1, sizeof(size_t)); // +1 for null terminator
}

// Serialize the object into a contiguous byte buffer
char* serialize(const VariableSizeStruct& obj) const {
if (obj.data == nullptr) {
std::cerr << "Serializing Variable Object with Nullptr" << std::endl;
exit(1);
}

size_t data_length = strlen(obj.data) + 1; // Include null terminator
size_t buffer_size = bytesRequired(data_length);

char* buffer = new char[buffer_size]; // Allocate a buffer
char* ptr = buffer;

// Store the data length at the start
std::memcpy(ptr, &data_length, sizeof(size_t));
ptr += sizeof(size_t);

// Copy hash
std::memcpy(ptr, &obj.hash, sizeof(size_t));
ptr += sizeof(size_t);

// Copy random_number
std::memcpy(ptr, &obj.random_number, sizeof(size_t));
ptr += sizeof(size_t);

// Copy data (null-terminated string)
std::memcpy(ptr, obj.data, data_length);
ptr += RoundUp(data_length, sizeof(size_t)); // Ensure alignment

return buffer; // Caller must free the buffer
}

// Deserialize from a byte buffer into a VariableSizeStruct object
VariableSizeStruct deserialize(const char* buffer, size_t size) const {
VariableSizeStruct obj;
const char* ptr = buffer;

// Read data length
size_t data_length;
std::memcpy(&data_length, ptr, sizeof(size_t));
ptr += sizeof(size_t);

// Read hash
std::memcpy(&obj.hash, ptr, sizeof(size_t));
ptr += sizeof(size_t);

// Read random_number
std::memcpy(&obj.random_number, ptr, sizeof(size_t));
ptr += sizeof(size_t);

// Read data (allocate memory for it)
obj.data = static_cast<char*>(malloc(data_length));
if (!obj.data) {
std::cerr << "Memory allocation failed for data string" << std::endl;
exit(1);
}
std::memcpy(obj.data, ptr, data_length);

return obj; // Return the deserialized struct
}
};


// ✅ Test Serialization of a Valid Struct
TEST(RandomSerializerTest, SerializeDeserialize) {
RandomSerializer serializer;
VariableSizeStruct obj{12345, 67890, "Hello, World!"};

char* buffer = serializer.serialize(obj);
size_t buffer_size = serializer.bytesRequired(strlen(obj.data));

VariableSizeStruct deserialized = serializer.deserialize(buffer, buffer_size);

EXPECT_EQ(deserialized.hash, obj.hash);
EXPECT_EQ(deserialized.random_number, obj.random_number);
EXPECT_STREQ(deserialized.data, obj.data);

delete[] buffer;
free((void*)deserialized.data);
}

// ✅ Test if `bytesRequired` Computes Correctly
TEST(RandomSerializerTest, BytesRequired) {
RandomSerializer serializer;

size_t expected_size = serializer.RoundUp((sizeof(size_t) * 3) + 14, sizeof(size_t)); // "Hello, World!" + null
EXPECT_EQ(serializer.bytesRequired(13), expected_size);
}

// ✅ Test Empty String Serialization
TEST(RandomSerializerTest, SerializeEmptyString) {
RandomSerializer serializer;
VariableSizeStruct obj{98765, 43210, ""};

char* buffer = serializer.serialize(obj);
size_t buffer_size = serializer.bytesRequired(strlen(obj.data));

VariableSizeStruct deserialized = serializer.deserialize(buffer, buffer_size);

EXPECT_EQ(deserialized.hash, obj.hash);
EXPECT_EQ(deserialized.random_number, obj.random_number);
EXPECT_STREQ(deserialized.data, obj.data);

delete[] buffer;
free((void*)deserialized.data);
}

// ✅ Test Long String Serialization
TEST(RandomSerializerTest, SerializeLongString) {
RandomSerializer serializer;
std::string long_string(1000, 'A');
VariableSizeStruct obj{11111, 22222, (char*)long_string.c_str()};

char* buffer = serializer.serialize(obj);
size_t buffer_size = serializer.bytesRequired(long_string.size());

VariableSizeStruct deserialized = serializer.deserialize(buffer, buffer_size);

EXPECT_EQ(deserialized.hash, obj.hash);
EXPECT_EQ(deserialized.random_number, obj.random_number);
EXPECT_STREQ(deserialized.data, obj.data);

delete[] buffer;
free((void*)deserialized.data);
}

// ✅ Test Null Pointer Case (shouldn't crash)
TEST(RandomSerializerTest, NullDataHandling) {
RandomSerializer serializer;
VariableSizeStruct obj{55555, 66666, nullptr};

ASSERT_EXIT(serializer.serialize(obj), ::testing::ExitedWithCode(1), ".*");
}
// ✅ Test Alignment (Ensuring RoundUp Works Properly)
TEST(RandomSerializerTest, Alignment) {
RandomSerializer serializer;

EXPECT_EQ(serializer.RoundUp(10, 8), 16);
EXPECT_EQ(serializer.RoundUp(17, 8), 24);
EXPECT_EQ(serializer.RoundUp(32, 8), 32);
}

// ✅ Test Serialization Buffer Allocation
TEST(RandomSerializerTest, BufferAllocation) {
RandomSerializer serializer;
VariableSizeStruct obj{123, 456, "Test String"};

char* buffer = serializer.serialize(obj);
EXPECT_NE(buffer, nullptr);

delete[] buffer;
}

// ✅ Test Deserialization from Invalid Buffer
// TEST(RandomSerializerTest, DeserializeInvalidBuffer) {
// RandomSerializer serializer;
// char buffer[32] = {};

// EXPECT_THROW(serializer.deserialize(buffer, sizeof(buffer)), std::runtime_error);
// }

// ✅ Test Deserializing a Struct with Uninitialized Data
// TEST(RandomSerializerTest, DeserializeUninitializedData) {
// RandomSerializer serializer;
// char raw_data[32] = {};

// VariableSizeStruct obj = serializer.deserialize(raw_data, sizeof(raw_data));

// EXPECT_EQ(obj.hash, 0);
// EXPECT_EQ(obj.random_number, 0);
// EXPECT_STREQ(obj.data, "");
// }