Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
15 changes: 13 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ add_library(dbps_common_lib STATIC
src/common/json_request.cpp
src/common/enum_utils.cpp
src/common/value_encryption_utils.cpp
src/common/typed_list_values.cpp
)
target_include_directories(dbps_common_lib PUBLIC
src/common
Expand All @@ -145,6 +146,7 @@ add_library(dbps_server_lib STATIC
target_link_libraries(dbps_server_lib PUBLIC dbps_common_lib snappy)
target_include_directories(dbps_server_lib PUBLIC
src/server
src/common
${CMAKE_BINARY_DIR}/_deps/cppcodec-src
${CMAKE_BINARY_DIR}/_deps/jwt-cpp-src/include
${CMAKE_BINARY_DIR}/_deps/nlohmann_json-src/include
Expand Down Expand Up @@ -261,6 +263,13 @@ if(BUILD_TESTS)
gtest_main
)

# Typed list values tests
add_executable(typed_list_values_test src/common/typed_list_values_test.cpp)
target_link_libraries(typed_list_values_test
dbps_common_lib
gtest_main
)

# Encryption sequencer tests
add_executable(encryption_sequencer_test src/server/encryption_sequencer_test.cpp)
target_link_libraries(encryption_sequencer_test
Expand All @@ -279,13 +288,13 @@ if(BUILD_TESTS)
target_include_directories(decoding_utils_test PRIVATE src/server)

# Bytes utils tests
add_executable(bytes_utils_test src/server/bytes_utils_test.cpp)
add_executable(bytes_utils_test src/common/bytes_utils_test.cpp)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you :)

target_link_libraries(bytes_utils_test
dbps_server_lib
dbps_common_lib
gtest_main
)
target_include_directories(bytes_utils_test PRIVATE src/server)
target_include_directories(bytes_utils_test PRIVATE src/server src/common)

# Compression utils tests
add_executable(compression_utils_test src/server/compression_utils_test.cpp)
Expand Down Expand Up @@ -473,6 +482,7 @@ if(BUILD_TESTS)
json_request_test
enum_utils_test
value_encryption_utils_test
typed_list_values_test
encryption_sequencer_test
decoding_utils_test
bytes_utils_test
Expand All @@ -493,6 +503,7 @@ if(BUILD_TESTS)
gtest_discover_tests(json_request_test)
gtest_discover_tests(enum_utils_test)
gtest_discover_tests(value_encryption_utils_test)
gtest_discover_tests(typed_list_values_test)
gtest_discover_tests(encryption_sequencer_test)
gtest_discover_tests(decoding_utils_test)
gtest_discover_tests(bytes_utils_test)
Expand Down
92 changes: 49 additions & 43 deletions src/server/bytes_utils.h → src/common/bytes_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,37 +20,68 @@
#include <vector>
#include <cstdint>
#include <limits>
#include <cstring>
#include "exceptions.h"

// Little-endian helpers reused across modules
inline void append_u32_le(std::vector<uint8_t>& out, uint32_t v) {
out.push_back(static_cast<uint8_t>(v & 0xFF));
out.push_back(static_cast<uint8_t>((v >> 8) & 0xFF));
out.push_back(static_cast<uint8_t>((v >> 16) & 0xFF));
out.push_back(static_cast<uint8_t>((v >> 24) & 0xFF));
}

inline void append_i32_le(std::vector<uint8_t>& out, int32_t v) {
append_u32_le(out, static_cast<uint32_t>(v));
}

inline void append_u64_le(std::vector<uint8_t>& out, uint64_t v) {
out.push_back(static_cast<uint8_t>(v & 0xFF));
out.push_back(static_cast<uint8_t>((v >> 8) & 0xFF));
out.push_back(static_cast<uint8_t>((v >> 16) & 0xFF));
out.push_back(static_cast<uint8_t>((v >> 24) & 0xFF));
out.push_back(static_cast<uint8_t>((v >> 32) & 0xFF));
out.push_back(static_cast<uint8_t>((v >> 40) & 0xFF));
out.push_back(static_cast<uint8_t>((v >> 48) & 0xFF));
out.push_back(static_cast<uint8_t>((v >> 56) & 0xFF));
}

inline void append_i64_le(std::vector<uint8_t>& out, int64_t v) {
append_u64_le(out, static_cast<uint64_t>(v));
}

inline void append_f32_le(std::vector<uint8_t>& out, float v) {
uint32_t bits = 0;
std::memcpy(&bits, &v, sizeof(bits));
append_u32_le(out, bits);
}

inline void append_f64_le(std::vector<uint8_t>& out, double v) {
uint64_t bits = 0;
std::memcpy(&bits, &v, sizeof(bits));
append_u64_le(out, bits);
}

inline uint32_t read_u32_le(const std::vector<uint8_t>& in, size_t offset) {
return static_cast<uint32_t>(in[offset]) |
(static_cast<uint32_t>(in[offset + 1]) << 8) |
(static_cast<uint32_t>(in[offset + 2]) << 16) |
(static_cast<uint32_t>(in[offset + 3]) << 24);
}

struct SplitBytesPair {
std::vector<uint8_t> leading;
std::vector<uint8_t> trailing;
};

/**
* Join two byte vectors into a single vector.
* This is the converse operation of Split - concatenates leading and trailing bytes.
*
* @param leading The first part of the bytes
* @param trailing The second part of the bytes
* @return Combined bytes vector with leading followed by trailing
*/
inline std::vector<uint8_t> Join(const std::vector<uint8_t>& leading, const std::vector<uint8_t>& trailing) {
inline std::vector<uint8_t> Join(const std::vector<uint8_t>& leading, const std::vector<uint8_t>& trailing) {
std::vector<uint8_t> result;
result.reserve(leading.size() + trailing.size());
result.insert(result.end(), leading.begin(), leading.end());
result.insert(result.end(), trailing.begin(), trailing.end());
return result;
}

/**
* Split the input bytes in two parts, determined by the given index.
*
* @param bytes The bytes to split
* @param index The index at which to split (bytes before index go to leading, bytes from index go to trailing)
* @return SplitBytesPair structure with split bytes
* @throws InvalidInputException if index is invalid
*/
inline SplitBytesPair Split(const std::vector<uint8_t>& bytes, int index) {
SplitBytesPair result;

Expand All @@ -63,16 +94,6 @@ inline SplitBytesPair Split(const std::vector<uint8_t>& bytes, int index) {
return result;
}

/**
* Join two byte vectors with length prefix, making it self-contained and parseable.
* The output format is: [4-byte length of leading][leading bytes][trailing bytes]
* This allows the split point to be recovered when parsing later.
*
* @param leading The first part of the bytes
* @param trailing The second part of the bytes
* @return Combined bytes vector with length prefix, leading, then trailing
* @throws InvalidInputException if leading size exceeds maximum representable value (2^32 - 1)
*/
inline std::vector<uint8_t> JoinWithLengthPrefix(const std::vector<uint8_t>& leading, const std::vector<uint8_t>& trailing) {
if (leading.size() > std::numeric_limits<uint32_t>::max()) {
throw InvalidInputException("Leading bytes size exceeds maximum representable value");
Expand All @@ -82,35 +103,22 @@ inline std::vector<uint8_t> JoinWithLengthPrefix(const std::vector<uint8_t>& lea
std::vector<uint8_t> result;
result.reserve(4 + leading.size() + trailing.size());

// Prepend 4-byte length (little-endian)
result.push_back(leading_length & 0xFF);
result.push_back((leading_length >> 8) & 0xFF);
result.push_back((leading_length >> 16) & 0xFF);
result.push_back((leading_length >> 24) & 0xFF);

// Append leading bytes
result.insert(result.end(), leading.begin(), leading.end());

// Append trailing bytes
result.insert(result.end(), trailing.begin(), trailing.end());

return result;
}

/**
* Parse a self-contained byte vector that was created with JoinWithLengthPrefix.
* Extracts the leading and trailing parts based on the embedded length prefix.
*
* @param bytes The combined bytes with length prefix
* @return SplitBytesPair structure with leading and trailing bytes
* @throws InvalidInputException if the data is invalid or malformed
*/
inline SplitBytesPair SplitWithLengthPrefix(const std::vector<uint8_t>& bytes) {
if (bytes.size() < 4) {
throw InvalidInputException("Invalid length-prefixed data: insufficient bytes for length prefix");
}

// Read 4-byte length (little-endian)
uint32_t leading_length = static_cast<uint32_t>(bytes[0]) |
(static_cast<uint32_t>(bytes[1]) << 8) |
(static_cast<uint32_t>(bytes[2]) << 16) |
Expand All @@ -123,11 +131,9 @@ inline SplitBytesPair SplitWithLengthPrefix(const std::vector<uint8_t>& bytes) {

SplitBytesPair result;

// Extract leading bytes (skip the 4-byte length prefix)
result.leading = std::vector<uint8_t>(bytes.begin() + 4, bytes.begin() + 4 + leading_length);

// Extract trailing bytes (everything after leading)
result.trailing = std::vector<uint8_t>(bytes.begin() + 4 + leading_length, bytes.end());

return result;
}

56 changes: 29 additions & 27 deletions src/server/bytes_utils_test.cpp → src/common/bytes_utils_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include "bytes_utils.h"
#include "exceptions.h"

#include <vector>
#include <gtest/gtest.h>

Expand Down Expand Up @@ -125,8 +126,8 @@ TEST(BytesUtils, JoinWithLengthPrefix_EmptyLeading) {
std::vector<uint8_t> trailing = {0x04, 0x05, 0x06};
std::vector<uint8_t> result = JoinWithLengthPrefix(leading, trailing);

EXPECT_EQ(7, result.size()); // 4 bytes length + 0 bytes leading + 3 bytes trailing
EXPECT_EQ(0x00, result[0]); // length = 0
EXPECT_EQ(7, result.size()); // 4 bytes length + 0 leading + 3 trailing
EXPECT_EQ(0x00, result[0]);
EXPECT_EQ(0x00, result[1]);
EXPECT_EQ(0x00, result[2]);
EXPECT_EQ(0x00, result[3]);
Expand All @@ -140,45 +141,46 @@ TEST(BytesUtils, JoinWithLengthPrefix_EmptyTrailing) {
std::vector<uint8_t> trailing;
std::vector<uint8_t> result = JoinWithLengthPrefix(leading, trailing);

EXPECT_EQ(7, result.size()); // 4 bytes length + 3 bytes leading + 0 bytes trailing
EXPECT_EQ(0x03, result[0]); // length = 3
EXPECT_EQ(7, result.size()); // 4 bytes length + 3 leading + 0 trailing
EXPECT_EQ(0x03, result[0]);
EXPECT_EQ(0x00, result[1]);
EXPECT_EQ(0x00, result[2]);
EXPECT_EQ(0x00, result[3]);
EXPECT_EQ(0x01, result[4]);
EXPECT_EQ(0x02, result[5]);
EXPECT_EQ(0x03, result[6]);
}

TEST(BytesUtils, JoinWithLengthPrefix_BothEmpty) {
std::vector<uint8_t> leading;
std::vector<uint8_t> trailing;
std::vector<uint8_t> result = JoinWithLengthPrefix(leading, trailing);

EXPECT_EQ(4, result.size()); // only the 4-byte length prefix
EXPECT_EQ(0x00, result[0]);
EXPECT_EQ(0x00, result[1]);
EXPECT_EQ(0x00, result[2]);
EXPECT_EQ(0x00, result[3]);
}

TEST(BytesUtils, SplitWithLengthPrefix_Normal) {
// Create data with JoinWithLengthPrefix
std::vector<uint8_t> leading = {0x01, 0x02, 0x03};
std::vector<uint8_t> trailing = {0x04, 0x05, 0x06};
std::vector<uint8_t> joined = JoinWithLengthPrefix(leading, trailing);

// Parse it back
SplitBytesPair result = SplitWithLengthPrefix(joined);
std::vector<uint8_t> combined = JoinWithLengthPrefix(leading, trailing);
SplitBytesPair result = SplitWithLengthPrefix(combined);

EXPECT_EQ(leading, result.leading);
EXPECT_EQ(trailing, result.trailing);
}

TEST(BytesUtils, SplitWithLengthPrefix_InvalidTooShort) {
std::vector<uint8_t> invalid = {0x01, 0x02}; // Less than 4 bytes
EXPECT_THROW(SplitWithLengthPrefix(invalid), InvalidInputException);
TEST(BytesUtils, SplitWithLengthPrefix_InvalidData_Short) {
std::vector<uint8_t> bytes = {0x01, 0x02, 0x03}; // too short for length prefix
EXPECT_THROW(SplitWithLengthPrefix(bytes), InvalidInputException);
}

TEST(BytesUtils, SplitWithLengthPrefix_InvalidInsufficientData) {
// Length prefix says 10 bytes, but we only have 4 bytes total
std::vector<uint8_t> invalid = {0x0A, 0x00, 0x00, 0x00}; // length = 10, but only 4 bytes total
EXPECT_THROW(SplitWithLengthPrefix(invalid), InvalidInputException);
}

TEST(BytesUtils, JoinWithLengthPrefixAndSplit_RoundTrip) {
std::vector<uint8_t> leading = {0x01, 0x02, 0x03, 0x04, 0x05};
std::vector<uint8_t> trailing = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60};

std::vector<uint8_t> joined = JoinWithLengthPrefix(leading, trailing);
SplitBytesPair parsed = SplitWithLengthPrefix(joined);

EXPECT_EQ(leading, parsed.leading);
EXPECT_EQ(trailing, parsed.trailing);
TEST(BytesUtils, SplitWithLengthPrefix_InvalidData_TruncatedLeading) {
// length=5 but only 4 bytes provided after prefix
std::vector<uint8_t> bytes = {0x05, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04};
EXPECT_THROW(SplitWithLengthPrefix(bytes), InvalidInputException);
}

File renamed without changes.
Loading