diff --git a/CMakeLists.txt b/CMakeLists.txt index 0481d01..86b3817 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 @@ -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 @@ -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 @@ -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) 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) @@ -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 @@ -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) diff --git a/src/server/bytes_utils.h b/src/common/bytes_utils.h similarity index 69% rename from src/server/bytes_utils.h rename to src/common/bytes_utils.h index b3b6706..4641d9a 100644 --- a/src/server/bytes_utils.h +++ b/src/common/bytes_utils.h @@ -20,8 +20,55 @@ #include #include #include +#include #include "exceptions.h" +// Little-endian helpers reused across modules +inline void append_u32_le(std::vector& out, uint32_t v) { + out.push_back(static_cast(v & 0xFF)); + out.push_back(static_cast((v >> 8) & 0xFF)); + out.push_back(static_cast((v >> 16) & 0xFF)); + out.push_back(static_cast((v >> 24) & 0xFF)); +} + +inline void append_i32_le(std::vector& out, int32_t v) { + append_u32_le(out, static_cast(v)); +} + +inline void append_u64_le(std::vector& out, uint64_t v) { + out.push_back(static_cast(v & 0xFF)); + out.push_back(static_cast((v >> 8) & 0xFF)); + out.push_back(static_cast((v >> 16) & 0xFF)); + out.push_back(static_cast((v >> 24) & 0xFF)); + out.push_back(static_cast((v >> 32) & 0xFF)); + out.push_back(static_cast((v >> 40) & 0xFF)); + out.push_back(static_cast((v >> 48) & 0xFF)); + out.push_back(static_cast((v >> 56) & 0xFF)); +} + +inline void append_i64_le(std::vector& out, int64_t v) { + append_u64_le(out, static_cast(v)); +} + +inline void append_f32_le(std::vector& 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& 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& in, size_t offset) { + return static_cast(in[offset]) | + (static_cast(in[offset + 1]) << 8) | + (static_cast(in[offset + 2]) << 16) | + (static_cast(in[offset + 3]) << 24); +} + struct SplitBytesPair { std::vector leading; std::vector trailing; @@ -35,7 +82,7 @@ struct SplitBytesPair { * @param trailing The second part of the bytes * @return Combined bytes vector with leading followed by trailing */ - inline std::vector Join(const std::vector& leading, const std::vector& trailing) { +inline std::vector Join(const std::vector& leading, const std::vector& trailing) { std::vector result; result.reserve(leading.size() + trailing.size()); result.insert(result.end(), leading.begin(), leading.end()); @@ -78,20 +125,16 @@ inline std::vector JoinWithLengthPrefix(const std::vector& lea throw InvalidInputException("Leading bytes size exceeds maximum representable value"); } + // Calculate the length of the leading bytes uint32_t leading_length = static_cast(leading.size()); std::vector 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); + // Prepend 4-byte length + append_u32_le(result, leading_length); - // Append leading bytes + // Append leading and trailing bytes result.insert(result.end(), leading.begin(), leading.end()); - - // Append trailing bytes result.insert(result.end(), trailing.begin(), trailing.end()); return result; @@ -110,11 +153,8 @@ inline SplitBytesPair SplitWithLengthPrefix(const std::vector& bytes) { throw InvalidInputException("Invalid length-prefixed data: insufficient bytes for length prefix"); } - // Read 4-byte length (little-endian) - uint32_t leading_length = static_cast(bytes[0]) | - (static_cast(bytes[1]) << 8) | - (static_cast(bytes[2]) << 16) | - (static_cast(bytes[3]) << 24); + // Read 4-byte length + uint32_t leading_length = read_u32_le(bytes, 0); if (bytes.size() < 4 + leading_length) { throw InvalidInputException("Invalid length-prefixed data: insufficient bytes for leading data (expected " + @@ -125,7 +165,7 @@ inline SplitBytesPair SplitWithLengthPrefix(const std::vector& bytes) { // Extract leading bytes (skip the 4-byte length prefix) result.leading = std::vector(bytes.begin() + 4, bytes.begin() + 4 + leading_length); - + // Extract trailing bytes (everything after leading) result.trailing = std::vector(bytes.begin() + 4 + leading_length, bytes.end()); diff --git a/src/server/bytes_utils_test.cpp b/src/common/bytes_utils_test.cpp similarity index 81% rename from src/server/bytes_utils_test.cpp rename to src/common/bytes_utils_test.cpp index ea68cd3..3779a54 100644 --- a/src/server/bytes_utils_test.cpp +++ b/src/common/bytes_utils_test.cpp @@ -17,6 +17,7 @@ #include "bytes_utils.h" #include "exceptions.h" + #include #include @@ -125,8 +126,8 @@ TEST(BytesUtils, JoinWithLengthPrefix_EmptyLeading) { std::vector trailing = {0x04, 0x05, 0x06}; std::vector 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]); @@ -140,29 +141,47 @@ TEST(BytesUtils, JoinWithLengthPrefix_EmptyTrailing) { std::vector trailing; std::vector 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 leading; + std::vector trailing; + std::vector 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 leading = {0x01, 0x02, 0x03}; std::vector trailing = {0x04, 0x05, 0x06}; - std::vector joined = JoinWithLengthPrefix(leading, trailing); - - // Parse it back - SplitBytesPair result = SplitWithLengthPrefix(joined); + std::vector combined = JoinWithLengthPrefix(leading, trailing); + SplitBytesPair result = SplitWithLengthPrefix(combined); EXPECT_EQ(leading, result.leading); EXPECT_EQ(trailing, result.trailing); } -TEST(BytesUtils, SplitWithLengthPrefix_InvalidTooShort) { - std::vector invalid = {0x01, 0x02}; // Less than 4 bytes - EXPECT_THROW(SplitWithLengthPrefix(invalid), InvalidInputException); +TEST(BytesUtils, SplitWithLengthPrefix_InvalidData_Short) { + std::vector bytes = {0x01, 0x02, 0x03}; // too short for length prefix + EXPECT_THROW(SplitWithLengthPrefix(bytes), InvalidInputException); +} + +TEST(BytesUtils, SplitWithLengthPrefix_InvalidData_TruncatedLeading) { + // length=5 but only 4 bytes provided after prefix + std::vector bytes = {0x05, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04}; + EXPECT_THROW(SplitWithLengthPrefix(bytes), InvalidInputException); } TEST(BytesUtils, SplitWithLengthPrefix_InvalidInsufficientData) { @@ -180,5 +199,4 @@ TEST(BytesUtils, JoinWithLengthPrefixAndSplit_RoundTrip) { EXPECT_EQ(leading, parsed.leading); EXPECT_EQ(trailing, parsed.trailing); -} - +} \ No newline at end of file diff --git a/src/server/exceptions.h b/src/common/exceptions.h similarity index 100% rename from src/server/exceptions.h rename to src/common/exceptions.h diff --git a/src/common/typed_list_values.cpp b/src/common/typed_list_values.cpp new file mode 100644 index 0000000..8c4acb6 --- /dev/null +++ b/src/common/typed_list_values.cpp @@ -0,0 +1,270 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#include "typed_list_values.h" + +#include +#include +#include +#include +#include +#include "bytes_utils.h" + +// Convert decrypted byte-vectors into a TypedListValues according to datatype +TypedListValues BuildTypedListFromRawBytes( + Type::type datatype, + const std::vector& elements_bytes) { + + switch (datatype) { + case Type::INT32: { + std::vector out; + for (size_t i = 0; i < elements_bytes.size(); ++i) { + const std::vector& bytes = elements_bytes[i]; + if (bytes.size() != 4) { + throw std::runtime_error("DecryptTypedListValues: invalid INT32 element size"); + } + int32_t v = static_cast(bytes[0]) | + (static_cast(bytes[1]) << 8) | + (static_cast(bytes[2]) << 16) | + (static_cast(bytes[3]) << 24); + out.push_back(v); + } + return out; + } + case Type::INT64: { + std::vector out; + for (size_t i = 0; i < elements_bytes.size(); ++i) { + const std::vector& bytes = elements_bytes[i]; + if (bytes.size() != 8) { + throw std::runtime_error("DecryptTypedListValues: invalid INT64 element size"); + } + uint64_t u = static_cast(bytes[0]) | + (static_cast(bytes[1]) << 8) | + (static_cast(bytes[2]) << 16) | + (static_cast(bytes[3]) << 24) | + (static_cast(bytes[4]) << 32) | + (static_cast(bytes[5]) << 40) | + (static_cast(bytes[6]) << 48) | + (static_cast(bytes[7]) << 56); + out.push_back(static_cast(u)); + } + return out; + } + case Type::FLOAT: { + std::vector out; + for (size_t i = 0; i < elements_bytes.size(); ++i) { + const std::vector& bytes = elements_bytes[i]; + if (bytes.size() != 4) { + throw std::runtime_error("DecryptTypedListValues: invalid FLOAT element size"); + } + uint32_t bits = static_cast(bytes[0]) | + (static_cast(bytes[1]) << 8) | + (static_cast(bytes[2]) << 16) | + (static_cast(bytes[3]) << 24); + float v; + std::memcpy(&v, &bits, sizeof(v)); + out.push_back(v); + } + return out; + } + case Type::DOUBLE: { + std::vector out; + for (size_t i = 0; i < elements_bytes.size(); ++i) { + const std::vector& bytes = elements_bytes[i]; + if (bytes.size() != 8) { + throw std::runtime_error("DecryptTypedListValues: invalid DOUBLE element size"); + } + uint64_t bits = static_cast(bytes[0]) | + (static_cast(bytes[1]) << 8) | + (static_cast(bytes[2]) << 16) | + (static_cast(bytes[3]) << 24) | + (static_cast(bytes[4]) << 32) | + (static_cast(bytes[5]) << 40) | + (static_cast(bytes[6]) << 48) | + (static_cast(bytes[7]) << 56); + double v; + std::memcpy(&v, &bits, sizeof(v)); + out.push_back(v); + } + return out; + } + case Type::INT96: { + std::vector > out; + for (size_t i = 0; i < elements_bytes.size(); ++i) { + const std::vector& bytes = elements_bytes[i]; + if (bytes.size() != 12) { + throw std::runtime_error("DecryptTypedListValues: invalid INT96 element size"); + } + std::array a; + a[0] = static_cast(bytes[0]) | + (static_cast(bytes[1]) << 8) | + (static_cast(bytes[2]) << 16) | + (static_cast(bytes[3]) << 24); + a[1] = static_cast(bytes[4]) | + (static_cast(bytes[5]) << 8) | + (static_cast(bytes[6]) << 16) | + (static_cast(bytes[7]) << 24); + a[2] = static_cast(bytes[8]) | + (static_cast(bytes[9]) << 8) | + (static_cast(bytes[10]) << 16) | + (static_cast(bytes[11]) << 24); + out.push_back(a); + } + return out; + } + case Type::BYTE_ARRAY: + case Type::FIXED_LEN_BYTE_ARRAY: { + std::vector out; + for (size_t i = 0; i < elements_bytes.size(); ++i) { + const std::vector& bytes = elements_bytes[i]; + out.emplace_back(reinterpret_cast(bytes.data()), bytes.size()); + } + return out; + } + case Type::UNDEFINED: { + std::vector out; + for (size_t i = 0; i < elements_bytes.size(); ++i) { + const std::vector& bytes = elements_bytes[i]; + if (bytes.size() != 1) { + throw std::runtime_error("DecryptTypedListValues: invalid UNDEFINED element size"); + } + out.push_back(bytes[0]); + } + return out; + } + default: + throw std::runtime_error("DecryptTypedListValues: unsupported datatype"); + } +} + +// helper function to build list of raw bytes from a TypedListValues +std::vector BuildRawBytesFromTypedListValues(const TypedListValues& elements) { + std::vector raw_values; + std::visit([&](const auto& vec) { + typedef std::decay_t VecT; + typedef typename VecT::value_type ElemT; + + auto serialize = [&](const ElemT& elem) -> std::vector { + std::vector serialized; + if constexpr (std::is_same::value) { + append_i32_le(serialized, elem); + } else if constexpr (std::is_same::value) { + append_i64_le(serialized, elem); + } else if constexpr (std::is_same::value) { + append_f32_le(serialized, elem); + } else if constexpr (std::is_same::value) { + append_f64_le(serialized, elem); + } else if constexpr (std::is_same >::value) { + append_u32_le(serialized, elem[0]); + append_u32_le(serialized, elem[1]); + append_u32_le(serialized, elem[2]); + } else if constexpr (std::is_same::value) { + serialized.insert(serialized.end(), elem.begin(), elem.end()); + } else if constexpr (std::is_same::value) { + serialized.push_back(elem); + } else { + static_assert(sizeof(ElemT) == 0, "Unsupported element type in TypedListValues"); + } + return serialized; + }; + + raw_values.reserve(vec.size()); + for (size_t i = 0; i < vec.size(); ++i) { + const ElemT& elem = vec[i]; + std::vector bytes = serialize(elem); + raw_values.push_back(std::move(bytes)); + } + }, elements); + return raw_values; +} + +namespace { + +template +const char* GetTypeName() { + if constexpr (std::is_same_v>) return "INT32"; + else if constexpr (std::is_same_v>) return "INT64"; + else if constexpr (std::is_same_v>) return "FLOAT"; + else if constexpr (std::is_same_v>) return "DOUBLE"; + else if constexpr (std::is_same_v>>) return "INT96"; + else if constexpr (std::is_same_v>) + return "string (BYTE_ARRAY/FIXED_LEN_BYTE_ARRAY)"; + else if constexpr (std::is_same_v>) return "UNDEFINED (raw bytes)"; + else if constexpr (std::is_same_v) return "empty/error"; + else return "unknown"; +} + +} // namespace + +std::string TypedListToString(const TypedListValues& list) { + std::ostringstream out; + + std::visit([&out](auto&& values) { + using T = std::decay_t; + + if constexpr (std::is_same_v) { + out << "Empty/error state\n"; + } + else if constexpr (std::is_same_v>>) { + // Special case for INT96 - [lo, mid, hi] format + out << "Decoded INT96 values ([lo, mid, hi] 32-bit words):\n"; + for (size_t i = 0; i < values.size(); ++i) { + out << " [" << i << "] [" << values[i][0] << ", " + << values[i][1] << ", " << values[i][2] << "]\n"; + } + } + else if constexpr (std::is_same_v>) { + // Special case for UNDEFINED - raw bytes as hex + out << "Decoded UNDEFINED type (raw bytes):\n"; + out << " Hex: "; + for (size_t i = 0; i < values.size(); ++i) { + out << std::hex << std::setw(2) << std::setfill('0') + << static_cast(values[i]); + if (i < values.size() - 1) out << " "; + } + out << std::dec << "\n"; // Reset to decimal + + // Also show as string if printable + out << " String: \""; + for (uint8_t byte : values) { + if (byte >= 32 && byte < 127) { + out << static_cast(byte); + } else { + out << "."; + } + } + out << "\"\n"; + } + else if constexpr (std::is_same_v>) { + // String values with quotes and the length of the string. + out << "Decoded " << GetTypeName() << " values:\n"; + for (size_t i = 0; i < values.size(); ++i) { + out << " [" << i << "] \"" << values[i] << "\" (length: " << values[i].size() << ")\n"; + } + } + else { + // Generic case for numeric types (int32, int64, float, double) + out << "Decoded " << GetTypeName() << " values:\n"; + for (size_t i = 0; i < values.size(); ++i) { + out << " [" << i << "] " << values[i] << "\n"; + } + } + }, list); + + return out.str(); +} + diff --git a/src/common/typed_list_values.h b/src/common/typed_list_values.h new file mode 100644 index 0000000..108007b --- /dev/null +++ b/src/common/typed_list_values.h @@ -0,0 +1,72 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#pragma once + +#include +#include +#include +#include +#include +#include "enums.h" + +using namespace dbps::external; + +using TypedListValues = std::variant< + std::vector, + std::vector, + std::vector, + std::vector, + std::vector>, // For INT96 + std::vector, // For BYTE_ARRAY and FIXED_LEN_BYTE_ARRAY + std::vector // For UNDEFINED, a plain untyped byte sequence. +>; + +/** + * Type alias for raw value bytes. + */ +using RawValueBytes = std::vector; + +/** + * Convert a vector of raw value bytes into a typed list based on the provided datatype. + * The numeric representations are expected to be little-endian. + * + * @throws std::runtime_error if element sizes are inconsistent with the datatype + * or if the datatype is unsupported. + */ +TypedListValues BuildTypedListFromRawBytes( + Type::type datatype, + const std::vector& elements_bytes); + +/** + * Convert a typed list of values into their raw little-endian byte representation. + * Produces one RawValueBytes per input element. + * + * @throws at compile-time if an unsupported variant alternative is provided + * (static_assert in implementation). + */ +std::vector BuildRawBytesFromTypedListValues( + const TypedListValues& elements); + +/** + * Print a typed list in a human-readable format. + * + * @param list The typed list to print + * @return String representation of the typed list + */ +std::string TypedListToString(const TypedListValues& list); + diff --git a/src/common/typed_list_values_test.cpp b/src/common/typed_list_values_test.cpp new file mode 100644 index 0000000..8d9434b --- /dev/null +++ b/src/common/typed_list_values_test.cpp @@ -0,0 +1,185 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#include "typed_list_values.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace dbps::external; + +TEST(TypedListValuesTest, BuildRawBytesFromTypedListValues_BuildTypedListFromRawBytes_RoundTrip_INT32) { + TypedListValues input = std::vector{0, 1, -1, 256, 123456789}; + std::vector raw = BuildRawBytesFromTypedListValues(input); + ASSERT_EQ(raw.size(), 5u); + for (const auto& r : raw) { + EXPECT_EQ(r.size(), 4u); + } + auto out = BuildTypedListFromRawBytes(Type::INT32, raw); + const auto& out_vec = std::get>(out); + const auto& in_vec = std::get>(input); + ASSERT_EQ(out_vec, in_vec); +} + +TEST(TypedListValuesTest, BuildRawBytesFromTypedListValues_BuildTypedListFromRawBytes_RoundTrip_INT64) { + TypedListValues input = std::vector{0, 1, -1, 256, 0x1122334455667788LL}; + std::vector raw = BuildRawBytesFromTypedListValues(input); + ASSERT_EQ(raw.size(), 5u); + for (const auto& r : raw) { + EXPECT_EQ(r.size(), 8u); + } + auto out = BuildTypedListFromRawBytes(Type::INT64, raw); + const auto& out_vec = std::get>(out); + const auto& in_vec = std::get>(input); + ASSERT_EQ(out_vec, in_vec); +} + +TEST(TypedListValuesTest, BuildRawBytesFromTypedListValues_BuildTypedListFromRawBytes_RoundTrip_FLOAT) { + TypedListValues input = std::vector{0.0f, 1.5f, -2.25f, 12345.0f}; + std::vector raw = BuildRawBytesFromTypedListValues(input); + ASSERT_EQ(raw.size(), 4u); + for (const auto& r : raw) { + EXPECT_EQ(r.size(), 4u); + } + auto out = BuildTypedListFromRawBytes(Type::FLOAT, raw); + const auto& out_vec = std::get>(out); + const auto& in_vec = std::get>(input); + ASSERT_EQ(out_vec.size(), in_vec.size()); + for (size_t i = 0; i < in_vec.size(); ++i) { + EXPECT_FLOAT_EQ(out_vec[i], in_vec[i]); + } +} + +TEST(TypedListValuesTest, BuildRawBytesFromTypedListValues_BuildTypedListFromRawBytes_RoundTrip_DOUBLE) { + TypedListValues input = std::vector{0.0, 1.5, -2.25, 12345.0}; + std::vector raw = BuildRawBytesFromTypedListValues(input); + ASSERT_EQ(raw.size(), 4u); + for (const auto& r : raw) { + EXPECT_EQ(r.size(), 8u); + } + auto out = BuildTypedListFromRawBytes(Type::DOUBLE, raw); + const auto& out_vec = std::get>(out); + const auto& in_vec = std::get>(input); + ASSERT_EQ(out_vec.size(), in_vec.size()); + for (size_t i = 0; i < in_vec.size(); ++i) { + EXPECT_DOUBLE_EQ(out_vec[i], in_vec[i]); + } +} + +TEST(TypedListValuesTest, BuildRawBytesFromTypedListValues_BuildTypedListFromRawBytes_RoundTrip_INT96) { + std::vector> vals; + vals.push_back({0u, 0u, 0u}); + vals.push_back({1u, 2u, 3u}); + vals.push_back({0x11223344u, 0x55667788u, 0xAABBCCDDu}); + TypedListValues input = vals; + std::vector raw = BuildRawBytesFromTypedListValues(input); + ASSERT_EQ(raw.size(), vals.size()); + for (const auto& r : raw) { + EXPECT_EQ(r.size(), 12u); + } + auto out = BuildTypedListFromRawBytes(Type::INT96, raw); + const auto& out_vec = std::get>>(out); + ASSERT_EQ(out_vec, vals); +} + +TEST(TypedListValuesTest, BuildRawBytesFromTypedListValues_BuildTypedListFromRawBytes_RoundTrip_BYTE_ARRAY_and_FIXED) { + std::vector strs = {"", "a", "hello", std::string("\x00\x01\x02", 3)}; + { + TypedListValues input = strs; + auto raw = BuildRawBytesFromTypedListValues(input); + ASSERT_EQ(raw.size(), strs.size()); + for (size_t i = 0; i < strs.size(); ++i) { + EXPECT_EQ(std::string(reinterpret_cast(raw[i].data()), raw[i].size()), strs[i]); + } + auto out = BuildTypedListFromRawBytes(Type::BYTE_ARRAY, raw); + const auto& out_vec = std::get>(out); + ASSERT_EQ(out_vec, strs); + } + { + // Same raw used with FIXED_LEN_BYTE_ARRAY path (treated equivalently here) + TypedListValues input = strs; + auto raw = BuildRawBytesFromTypedListValues(input); + auto out = BuildTypedListFromRawBytes(Type::FIXED_LEN_BYTE_ARRAY, raw); + const auto& out_vec = std::get>(out); + ASSERT_EQ(out_vec, strs); + } +} + +TEST(TypedListValuesTest, BuildRawBytesFromTypedListValues_BuildTypedListFromRawBytes_RoundTrip_UNDEFINED) { + TypedListValues input = std::vector{0u, 255u, 42u}; + std::vector raw = BuildRawBytesFromTypedListValues(input); + ASSERT_EQ(raw.size(), 3u); + for (const auto& r : raw) { + EXPECT_EQ(r.size(), 1u); + } + auto out = BuildTypedListFromRawBytes(Type::UNDEFINED, raw); + const auto& out_vec = std::get>(out); + const auto& in_vec = std::get>(input); + ASSERT_EQ(out_vec, in_vec); +} + +TEST(TypedListValuesTest, BuildTypedListFromRawBytes_InvalidElementSizes_Throws) { + // INT32 wrong size + { + RawValueBytes r = {0x01, 0x02}; + std::vector v{r}; + EXPECT_THROW((void)BuildTypedListFromRawBytes(Type::INT32, v), std::runtime_error); + } + // INT64 wrong size + { + RawValueBytes r = {0x01, 0x02, 0x03}; + std::vector v{r}; + EXPECT_THROW((void)BuildTypedListFromRawBytes(Type::INT64, v), std::runtime_error); + } + // FLOAT wrong size + { + RawValueBytes r = {0x00, 0x00, 0x80}; + std::vector v{r}; + EXPECT_THROW((void)BuildTypedListFromRawBytes(Type::FLOAT, v), std::runtime_error); + } + // DOUBLE wrong size + { + RawValueBytes r = {0x00, 0x00, 0x00, 0x00}; + std::vector v{r}; + EXPECT_THROW((void)BuildTypedListFromRawBytes(Type::DOUBLE, v), std::runtime_error); + } + // INT96 wrong size + { + RawValueBytes r = RawValueBytes(11, 0); + std::vector v{r}; + EXPECT_THROW((void)BuildTypedListFromRawBytes(Type::INT96, v), std::runtime_error); + } + // UNDEFINED wrong size (expects exactly 1) + { + RawValueBytes r = {0xAA, 0xBB}; + std::vector v{r}; + EXPECT_THROW((void)BuildTypedListFromRawBytes(Type::UNDEFINED, v), std::runtime_error); + } +} + +TEST(TypedListValuesTest, BuildTypedListFromRawBytes_UnsupportedType_Throws) { + std::vector raw; // empty ok; type unsupported should still throw + EXPECT_THROW({ + (void)BuildTypedListFromRawBytes(Type::BOOLEAN, raw); + }, std::runtime_error); +} + diff --git a/src/common/value_encryption_utils.cpp b/src/common/value_encryption_utils.cpp index 5d3f978..ead10b4 100644 --- a/src/common/value_encryption_utils.cpp +++ b/src/common/value_encryption_utils.cpp @@ -24,245 +24,22 @@ #include #include #include - -namespace { //file-local helper functions - inline void append_u32_le(std::vector& out, uint32_t v) { - out.push_back(static_cast(v & 0xFF)); - out.push_back(static_cast((v >> 8) & 0xFF)); - out.push_back(static_cast((v >> 16) & 0xFF)); - out.push_back(static_cast((v >> 24) & 0xFF)); - } - - inline uint32_t read_u32_le(const std::vector& in, size_t offset) { - return static_cast(in[offset]) | - (static_cast(in[offset + 1]) << 8) | - (static_cast(in[offset + 2]) << 16) | - (static_cast(in[offset + 3]) << 24); - } - - inline void append_i32_le(std::vector& out, int32_t v) { - append_u32_le(out, static_cast(v)); - } - - inline void append_u64_le(std::vector& out, uint64_t v) { - out.push_back(static_cast(v & 0xFF)); - out.push_back(static_cast((v >> 8) & 0xFF)); - out.push_back(static_cast((v >> 16) & 0xFF)); - out.push_back(static_cast((v >> 24) & 0xFF)); - out.push_back(static_cast((v >> 32) & 0xFF)); - out.push_back(static_cast((v >> 40) & 0xFF)); - out.push_back(static_cast((v >> 48) & 0xFF)); - out.push_back(static_cast((v >> 56) & 0xFF)); - } - - inline void append_i64_le(std::vector& out, int64_t v) { - append_u64_le(out, static_cast(v)); - } - - inline void append_f32_le(std::vector& 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& out, double v) { - uint64_t bits = 0; - std::memcpy(&bits, &v, sizeof(bits)); - append_u64_le(out, bits); - } -} // namespace (anon) +#include "bytes_utils.h" namespace dbps::value_encryption_utils { -// Helper: Convert decrypted byte-vectors into a TypedListValues according to datatype -TypedListValues BuildTypedListFromRawBytes( - Type::type datatype, - const std::vector& elements_bytes) { - - switch (datatype) { - case Type::INT32: { - std::vector out; - for (size_t i = 0; i < elements_bytes.size(); ++i) { - const auto& elem = elements_bytes[i]; - const std::vector& bytes = elem.bytes; - if (bytes.size() != 4) { - throw std::runtime_error("DecryptTypedListValues: invalid INT32 element size"); - } - int32_t v = static_cast(bytes[0]) | - (static_cast(bytes[1]) << 8) | - (static_cast(bytes[2]) << 16) | - (static_cast(bytes[3]) << 24); - out.push_back(v); - } - return out; - } - case Type::INT64: { - std::vector out; - for (size_t i = 0; i < elements_bytes.size(); ++i) { - const auto& elem = elements_bytes[i]; - const std::vector& bytes = elem.bytes; - if (bytes.size() != 8) { - throw std::runtime_error("DecryptTypedListValues: invalid INT64 element size"); - } - uint64_t u = static_cast(bytes[0]) | - (static_cast(bytes[1]) << 8) | - (static_cast(bytes[2]) << 16) | - (static_cast(bytes[3]) << 24) | - (static_cast(bytes[4]) << 32) | - (static_cast(bytes[5]) << 40) | - (static_cast(bytes[6]) << 48) | - (static_cast(bytes[7]) << 56); - out.push_back(static_cast(u)); - } - return out; - } - case Type::FLOAT: { - std::vector out; - for (size_t i = 0; i < elements_bytes.size(); ++i) { - const auto& elem = elements_bytes[i]; - const std::vector& bytes = elem.bytes; - if (bytes.size() != 4) { - throw std::runtime_error("DecryptTypedListValues: invalid FLOAT element size"); - } - uint32_t bits = static_cast(bytes[0]) | - (static_cast(bytes[1]) << 8) | - (static_cast(bytes[2]) << 16) | - (static_cast(bytes[3]) << 24); - float v; - std::memcpy(&v, &bits, sizeof(v)); - out.push_back(v); - } - return out; - } - case Type::DOUBLE: { - std::vector out; - for (size_t i = 0; i < elements_bytes.size(); ++i) { - const auto& elem = elements_bytes[i]; - const std::vector& bytes = elem.bytes; - if (bytes.size() != 8) { - throw std::runtime_error("DecryptTypedListValues: invalid DOUBLE element size"); - } - uint64_t bits = static_cast(bytes[0]) | - (static_cast(bytes[1]) << 8) | - (static_cast(bytes[2]) << 16) | - (static_cast(bytes[3]) << 24) | - (static_cast(bytes[4]) << 32) | - (static_cast(bytes[5]) << 40) | - (static_cast(bytes[6]) << 48) | - (static_cast(bytes[7]) << 56); - double v; - std::memcpy(&v, &bits, sizeof(v)); - out.push_back(v); - } - return out; - } - case Type::INT96: { - std::vector > out; - for (size_t i = 0; i < elements_bytes.size(); ++i) { - const auto& elem = elements_bytes[i]; - const std::vector& bytes = elem.bytes; - if (bytes.size() != 12) { - throw std::runtime_error("DecryptTypedListValues: invalid INT96 element size"); - } - std::array a; - a[0] = static_cast(bytes[0]) | - (static_cast(bytes[1]) << 8) | - (static_cast(bytes[2]) << 16) | - (static_cast(bytes[3]) << 24); - a[1] = static_cast(bytes[4]) | - (static_cast(bytes[5]) << 8) | - (static_cast(bytes[6]) << 16) | - (static_cast(bytes[7]) << 24); - a[2] = static_cast(bytes[8]) | - (static_cast(bytes[9]) << 8) | - (static_cast(bytes[10]) << 16) | - (static_cast(bytes[11]) << 24); - out.push_back(a); - } - return out; - } - case Type::BYTE_ARRAY: - case Type::FIXED_LEN_BYTE_ARRAY: { - std::vector out; - for (size_t i = 0; i < elements_bytes.size(); ++i) { - const auto& elem = elements_bytes[i]; - const std::vector& bytes = elem.bytes; - out.emplace_back(reinterpret_cast(bytes.data()), bytes.size()); - } - return out; - } - case Type::UNDEFINED: { - std::vector out; - for (size_t i = 0; i < elements_bytes.size(); ++i) { - const auto& elem = elements_bytes[i]; - const std::vector& bytes = elem.bytes; - if (bytes.size() != 1) { - throw std::runtime_error("DecryptTypedListValues: invalid UNDEFINED element size"); - } - out.push_back(bytes[0]); - } - return out; - } - default: - throw std::runtime_error("DecryptTypedListValues: unsupported datatype"); - } -} - -// helper function to build list of raw bytes from a TypedListValues -std::vector BuildRawBytesFromTypedListValues(const TypedListValues& elements) { - std::vector raw_values; - std::visit([&](const auto& vec) { - typedef std::decay_t VecT; - typedef typename VecT::value_type ElemT; - - auto serialize = [&](const ElemT& elem) -> std::vector { - std::vector serialized; - if constexpr (std::is_same::value) { - append_i32_le(serialized, elem); - } else if constexpr (std::is_same::value) { - append_i64_le(serialized, elem); - } else if constexpr (std::is_same::value) { - append_f32_le(serialized, elem); - } else if constexpr (std::is_same::value) { - append_f64_le(serialized, elem); - } else if constexpr (std::is_same >::value) { - append_u32_le(serialized, elem[0]); - append_u32_le(serialized, elem[1]); - append_u32_le(serialized, elem[2]); - } else if constexpr (std::is_same::value) { - serialized.insert(serialized.end(), elem.begin(), elem.end()); - } else if constexpr (std::is_same::value) { - serialized.push_back(elem); - } else { - static_assert(sizeof(ElemT) == 0, "Unsupported element type in TypedListValues"); - } - return serialized; - }; - - raw_values.reserve(vec.size()); - for (size_t i = 0; i < vec.size(); ++i) { - const ElemT& elem = vec[i]; - std::vector bytes = serialize(elem); - RawValueBytes raw; - raw.bytes = std::move(bytes); - raw_values.push_back(std::move(raw)); - } - }, elements); - return raw_values; -} - std::vector ConcatenateEncryptedValues(const std::vector& values) { if (values.size() > static_cast(std::numeric_limits::max())) { - throw std::overflow_error("Too many elements to serialize into uint32 count"); + throw InvalidInputException("Too many elements to serialize into uint32 count"); } // Precompute capacity: 4 bytes for count + for each element (4 bytes size + payload) size_t total_capacity = 4; for (size_t i = 0; i < values.size(); ++i) { const EncryptedValue& ev = values[i]; - const size_t payload_size = ev.payload.size(); + const size_t payload_size = ev.size(); if (payload_size > static_cast(std::numeric_limits::max())) { - throw std::overflow_error("Element size exceeds uint32 capacity"); + throw InvalidInputException("Element size exceeds uint32 capacity"); } total_capacity += 4 + payload_size; } @@ -274,9 +51,9 @@ std::vector ConcatenateEncryptedValues(const std::vector(ev.payload.size())); + append_u32_le(out, static_cast(ev.size())); // Append the entire payload (ciphertext) - out.insert(out.end(), ev.payload.begin(), ev.payload.end()); + out.insert(out.end(), ev.begin(), ev.end()); } return out; @@ -285,7 +62,7 @@ std::vector ConcatenateEncryptedValues(const std::vector ParseConcatenatedEncryptedValues(const std::vector& blob) { size_t offset = 0; if (blob.size() < 4) { - throw std::runtime_error("Malformed input: missing element count"); + throw InvalidInputException("Malformed input: missing element count"); } uint32_t count = read_u32_le(blob, offset); offset += 4; @@ -295,25 +72,25 @@ std::vector ParseConcatenatedEncryptedValues(const std::vector(sz)) { - throw std::runtime_error("Malformed input: truncated payload bytes"); + throw InvalidInputException("Malformed input: truncated payload bytes"); } EncryptedValue ev; - ev.payload.assign(blob.begin() + static_cast(offset), - blob.begin() + static_cast(offset + sz)); + ev.assign(blob.begin() + static_cast(offset), + blob.begin() + static_cast(offset + sz)); offset += static_cast(sz); result.push_back(std::move(ev)); } // ensure no trailing bytes remain after parsing if (offset != blob.size()) { - throw std::runtime_error("Malformed input: trailing bytes after parsing EncryptedValue entries"); + throw InvalidInputException("Malformed input: trailing bytes after parsing EncryptedValue entries"); } return result; } @@ -327,10 +104,8 @@ std::vector EncryptTypedListValues( for (size_t i = 0; i < raw_values.size(); ++i) { const RawValueBytes& raw = raw_values[i]; - std::vector payload = fn_encrypt_byte_array(raw.bytes); - EncryptedValue ev; - ev.payload = std::move(payload); - encrypted_elements.push_back(std::move(ev)); + std::vector payload = fn_encrypt_byte_array(raw); + encrypted_elements.push_back(std::move(payload)); } return encrypted_elements; } @@ -345,12 +120,8 @@ TypedListValues DecryptTypedListValues( decrypted_values.reserve(encrypted_values.size()); for (size_t i = 0; i < encrypted_values.size(); ++i) { const EncryptedValue& ev = encrypted_values[i]; - std::vector decrypted = fn_decrypt_byte_array(ev.payload); - - RawValueBytes raw; - raw.bytes = std::move(decrypted); - - decrypted_values.push_back(std::move(raw)); + RawValueBytes decrypted = fn_decrypt_byte_array(ev); + decrypted_values.push_back(std::move(decrypted)); } // 2) Convert all decrypted bytes to a TypedListValues according to datatype diff --git a/src/common/value_encryption_utils.h b/src/common/value_encryption_utils.h index 0841326..791b61c 100644 --- a/src/common/value_encryption_utils.h +++ b/src/common/value_encryption_utils.h @@ -23,45 +23,17 @@ #include #include -// For TypedListValues type -#include "../server/decoding_utils.h" +#include "typed_list_values.h" +#include "enums.h" namespace dbps::value_encryption_utils { -/** - * Simple container for an encrypted payload and its size. - */ -struct EncryptedValue { - std::vector payload; -}; - -/** - * Simple container for the raw bytes of a value. -*/ -struct RawValueBytes { - std::vector bytes; -}; +using namespace dbps::external; /** - * Convert a vector of raw value bytes into a typed list based on the provided datatype. - * The numeric representations are expected to be little-endian. - * - * @throws std::runtime_error if element sizes are inconsistent with the datatype - * or if the datatype is unsupported. - */ -TypedListValues BuildTypedListFromRawBytes( - Type::type datatype, - const std::vector& elements_bytes); - -/** - * Convert a typed list of values into their raw little-endian byte representation. - * Produces one RawValueBytes per input element. - * - * @throws at compile-time if an unsupported variant alternative is provided - * (static_assert in implementation). + * Type alias for encrypted value bytes. */ -std::vector BuildRawBytesFromTypedListValues( - const TypedListValues& elements); +using EncryptedValue = std::vector; /** * Concatenate a list of EncryptedValue into a single binary blob: diff --git a/src/common/value_encryption_utils_test.cpp b/src/common/value_encryption_utils_test.cpp index 226cd7f..5d52162 100644 --- a/src/common/value_encryption_utils_test.cpp +++ b/src/common/value_encryption_utils_test.cpp @@ -26,18 +26,8 @@ #include #include -namespace { - using namespace dbps::value_encryption_utils; -EncryptedValue make_ev(const std::vector& bytes) { - EncryptedValue ev; - ev.payload = bytes; - return ev; -} - -} // namespace - TEST(ValueEncryptionUtilsTest, ConcatenateEncryptedValues_ParseConcatenatedEncryptedValues_RoundTrip) { std::vector input; { @@ -45,16 +35,16 @@ TEST(ValueEncryptionUtilsTest, ConcatenateEncryptedValues_ParseConcatenatedEncry v1.push_back(1); v1.push_back(2); v1.push_back(3); - input.push_back(make_ev(v1)); + input.push_back(v1); } { std::vector v2; // empty - input.push_back(make_ev(v2)); + input.push_back(v2); } { std::vector v3; v3.push_back(0xFF); - input.push_back(make_ev(v3)); + input.push_back(v3); } std::vector blob = ConcatenateEncryptedValues(input); @@ -62,7 +52,7 @@ TEST(ValueEncryptionUtilsTest, ConcatenateEncryptedValues_ParseConcatenatedEncry ASSERT_EQ(output.size(), input.size()); for (size_t i = 0; i < input.size(); ++i) { - EXPECT_EQ(output[i].payload, input[i].payload); + EXPECT_EQ(output[i], input[i]); } } @@ -122,155 +112,6 @@ static std::vector serialize_i32_le(int32_t v) { return out; } -TEST(ValueEncryptionUtilsTest, BuildRawBytesFromTypedListValues_BuildTypedListFromRawBytes_RoundTrip_INT32) { - TypedListValues input = std::vector{0, 1, -1, 256, 123456789}; - std::vector raw = BuildRawBytesFromTypedListValues(input); - ASSERT_EQ(raw.size(), 5u); - for (const auto& r : raw) { - EXPECT_EQ(r.bytes.size(), 4u); - } - auto out = BuildTypedListFromRawBytes(Type::INT32, raw); - const auto& out_vec = std::get>(out); - const auto& in_vec = std::get>(input); - ASSERT_EQ(out_vec, in_vec); -} - -TEST(ValueEncryptionUtilsTest, BuildRawBytesFromTypedListValues_BuildTypedListFromRawBytes_RoundTrip_INT64) { - TypedListValues input = std::vector{0, 1, -1, 256, 0x1122334455667788LL}; - std::vector raw = BuildRawBytesFromTypedListValues(input); - ASSERT_EQ(raw.size(), 5u); - for (const auto& r : raw) { - EXPECT_EQ(r.bytes.size(), 8u); - } - auto out = BuildTypedListFromRawBytes(Type::INT64, raw); - const auto& out_vec = std::get>(out); - const auto& in_vec = std::get>(input); - ASSERT_EQ(out_vec, in_vec); -} - -TEST(ValueEncryptionUtilsTest, BuildRawBytesFromTypedListValues_BuildTypedListFromRawBytes_RoundTrip_FLOAT) { - TypedListValues input = std::vector{0.0f, 1.5f, -2.25f, 12345.0f}; - std::vector raw = BuildRawBytesFromTypedListValues(input); - ASSERT_EQ(raw.size(), 4u); - for (const auto& r : raw) { - EXPECT_EQ(r.bytes.size(), 4u); - } - auto out = BuildTypedListFromRawBytes(Type::FLOAT, raw); - const auto& out_vec = std::get>(out); - const auto& in_vec = std::get>(input); - ASSERT_EQ(out_vec.size(), in_vec.size()); - for (size_t i = 0; i < in_vec.size(); ++i) { - EXPECT_FLOAT_EQ(out_vec[i], in_vec[i]); - } -} - -TEST(ValueEncryptionUtilsTest, BuildRawBytesFromTypedListValues_BuildTypedListFromRawBytes_RoundTrip_DOUBLE) { - TypedListValues input = std::vector{0.0, 1.5, -2.25, 12345.0}; - std::vector raw = BuildRawBytesFromTypedListValues(input); - ASSERT_EQ(raw.size(), 4u); - for (const auto& r : raw) { - EXPECT_EQ(r.bytes.size(), 8u); - } - auto out = BuildTypedListFromRawBytes(Type::DOUBLE, raw); - const auto& out_vec = std::get>(out); - const auto& in_vec = std::get>(input); - ASSERT_EQ(out_vec.size(), in_vec.size()); - for (size_t i = 0; i < in_vec.size(); ++i) { - EXPECT_DOUBLE_EQ(out_vec[i], in_vec[i]); - } -} - -TEST(ValueEncryptionUtilsTest, BuildRawBytesFromTypedListValues_BuildTypedListFromRawBytes_RoundTrip_INT96) { - std::vector> vals; - vals.push_back({0u, 0u, 0u}); - vals.push_back({1u, 2u, 3u}); - vals.push_back({0x11223344u, 0x55667788u, 0xAABBCCDDu}); - TypedListValues input = vals; - std::vector raw = BuildRawBytesFromTypedListValues(input); - ASSERT_EQ(raw.size(), vals.size()); - for (const auto& r : raw) { - EXPECT_EQ(r.bytes.size(), 12u); - } - auto out = BuildTypedListFromRawBytes(Type::INT96, raw); - const auto& out_vec = std::get>>(out); - ASSERT_EQ(out_vec, vals); -} - -TEST(ValueEncryptionUtilsTest, BuildRawBytesFromTypedListValues_BuildTypedListFromRawBytes_RoundTrip_BYTE_ARRAY_and_FIXED) { - std::vector strs = {"", "a", "hello", std::string("\x00\x01\x02", 3)}; - { - TypedListValues input = strs; - auto raw = BuildRawBytesFromTypedListValues(input); - ASSERT_EQ(raw.size(), strs.size()); - for (size_t i = 0; i < strs.size(); ++i) { - EXPECT_EQ(std::string(reinterpret_cast(raw[i].bytes.data()), raw[i].bytes.size()), strs[i]); - } - auto out = BuildTypedListFromRawBytes(Type::BYTE_ARRAY, raw); - const auto& out_vec = std::get>(out); - ASSERT_EQ(out_vec, strs); - } - { - // Same raw used with FIXED_LEN_BYTE_ARRAY path (treated equivalently here) - TypedListValues input = strs; - auto raw = BuildRawBytesFromTypedListValues(input); - auto out = BuildTypedListFromRawBytes(Type::FIXED_LEN_BYTE_ARRAY, raw); - const auto& out_vec = std::get>(out); - ASSERT_EQ(out_vec, strs); - } -} - -TEST(ValueEncryptionUtilsTest, BuildRawBytesFromTypedListValues_BuildTypedListFromRawBytes_RoundTrip_UNDEFINED) { - TypedListValues input = std::vector{0u, 255u, 42u}; - std::vector raw = BuildRawBytesFromTypedListValues(input); - ASSERT_EQ(raw.size(), 3u); - for (const auto& r : raw) { - EXPECT_EQ(r.bytes.size(), 1u); - } - auto out = BuildTypedListFromRawBytes(Type::UNDEFINED, raw); - const auto& out_vec = std::get>(out); - const auto& in_vec = std::get>(input); - ASSERT_EQ(out_vec, in_vec); -} - -TEST(ValueEncryptionUtilsTest, BuildTypedListFromRawBytes_InvalidElementSizes_Throws) { - // INT32 wrong size - { - RawValueBytes r; r.bytes = {0x01, 0x02}; - std::vector v{r}; - EXPECT_THROW((void)BuildTypedListFromRawBytes(Type::INT32, v), std::runtime_error); - } - // INT64 wrong size - { - RawValueBytes r; r.bytes = {0x01, 0x02, 0x03}; - std::vector v{r}; - EXPECT_THROW((void)BuildTypedListFromRawBytes(Type::INT64, v), std::runtime_error); - } - // FLOAT wrong size - { - RawValueBytes r; r.bytes = {0x00, 0x00, 0x80}; - std::vector v{r}; - EXPECT_THROW((void)BuildTypedListFromRawBytes(Type::FLOAT, v), std::runtime_error); - } - // DOUBLE wrong size - { - RawValueBytes r; r.bytes = {0x00, 0x00, 0x00, 0x00}; - std::vector v{r}; - EXPECT_THROW((void)BuildTypedListFromRawBytes(Type::DOUBLE, v), std::runtime_error); - } - // INT96 wrong size - { - RawValueBytes r; r.bytes = std::vector(11, 0); - std::vector v{r}; - EXPECT_THROW((void)BuildTypedListFromRawBytes(Type::INT96, v), std::runtime_error); - } - // UNDEFINED wrong size (expects exactly 1) - { - RawValueBytes r; r.bytes = {0xAA, 0xBB}; - std::vector v{r}; - EXPECT_THROW((void)BuildTypedListFromRawBytes(Type::UNDEFINED, v), std::runtime_error); - } -} - TEST(ValueEncryptionUtilsTest, EncryptTypedListValues_DecryptTypedListValues_RoundTrip_INT32) { // Identity "encryption/decryption" functions auto enc = [](const std::vector& b) { return b; }; @@ -280,7 +121,7 @@ TEST(ValueEncryptionUtilsTest, EncryptTypedListValues_DecryptTypedListValues_Rou // Ensure declared sizes match the raw element sizes (4 per int32) ASSERT_EQ(encrypted.size(), 3u); for (const auto& ev : encrypted) { - EXPECT_EQ(ev.payload.size(), 4u); + EXPECT_EQ(ev.size(), 4u); } auto decrypted = DecryptTypedListValues(encrypted, Type::INT32, dec); const auto& out_vec = std::get>(decrypted); @@ -316,7 +157,7 @@ TEST(ValueEncryptionUtilsTest, EncryptTypedListValues_DecryptTypedListValues_Rou auto encrypted = EncryptTypedListValues(input, enc); ASSERT_EQ(encrypted.size(), 3u); for (const auto& ev : encrypted) { - EXPECT_EQ(ev.payload.size(), 4u); + EXPECT_EQ(ev.size(), 4u); } auto decrypted = DecryptTypedListValues(encrypted, Type::FLOAT, dec); const auto& out_vec = std::get>(decrypted); @@ -334,7 +175,7 @@ TEST(ValueEncryptionUtilsTest, EncryptTypedListValues_DecryptTypedListValues_Rou auto encrypted = EncryptTypedListValues(input, enc); ASSERT_EQ(encrypted.size(), 3u); for (const auto& ev : encrypted) { - EXPECT_EQ(ev.payload.size(), 8u); + EXPECT_EQ(ev.size(), 8u); } auto decrypted = DecryptTypedListValues(encrypted, Type::INT64, dec); const auto& out_vec = std::get>(decrypted); @@ -363,7 +204,7 @@ TEST(ValueEncryptionUtilsTest, EncryptTypedListValues_DecryptTypedListValues_Rou auto encrypted = EncryptTypedListValues(input, enc); ASSERT_EQ(encrypted.size(), vals.size()); for (const auto& ev : encrypted) { - EXPECT_EQ(ev.payload.size(), 12u); + EXPECT_EQ(ev.size(), 12u); } auto decrypted = DecryptTypedListValues(encrypted, Type::INT96, dec); const auto& out_vec = std::get>>(decrypted); @@ -377,24 +218,16 @@ TEST(ValueEncryptionUtilsTest, EncryptTypedListValues_DecryptTypedListValues_Rou auto encrypted = EncryptTypedListValues(input, enc); ASSERT_EQ(encrypted.size(), 3u); for (const auto& ev : encrypted) { - EXPECT_EQ(ev.payload.size(), 1u); + EXPECT_EQ(ev.size(), 1u); } auto decrypted = DecryptTypedListValues(encrypted, Type::UNDEFINED, dec); const auto& out_vec = std::get>(decrypted); ASSERT_EQ(out_vec, std::get>(input)); } -TEST(ValueEncryptionUtilsTest, BuildTypedListFromRawBytes_UnsupportedType_Throws) { - std::vector raw; // empty ok; type unsupported should still throw - EXPECT_THROW({ - (void)BuildTypedListFromRawBytes(Type::BOOLEAN, raw); - }, std::runtime_error); -} - TEST(ValueEncryptionUtilsTest, DecryptTypedListValues_InvalidDecryptedSize_Throws) { // Provide an encrypted value whose decrypted bytes are of incorrect size for FLOAT (expect 4, give 3) - EncryptedValue ev; - ev.payload = std::vector{0x00, 0x00, 0x80}; // 3 bytes + EncryptedValue ev = std::vector{0x00, 0x00, 0x80}; // 3 bytes std::vector values{ev}; auto dec = [](const std::vector& b) { return b; }; // identity EXPECT_THROW({ @@ -475,7 +308,7 @@ TEST(ValueEncryptionUtilsTest, EncryptTypedListValues_DecryptTypedListValues_Var // Ensure ciphertext length is exactly double the plaintext bytes length for (size_t i = 0; i < strs.size(); ++i) { const auto& ev = encrypted[i]; - EXPECT_EQ(ev.payload.size(), std::get>(input)[i].size() * 2); + EXPECT_EQ(ev.size(), std::get>(input)[i].size() * 2); } auto decrypted = DecryptTypedListValues(encrypted, Type::BYTE_ARRAY, dec); diff --git a/src/server/compression_utils.h b/src/server/compression_utils.h index f735916..88bb23b 100644 --- a/src/server/compression_utils.h +++ b/src/server/compression_utils.h @@ -21,7 +21,7 @@ #include #include "enums.h" #include "enum_utils.h" -#include "exceptions.h" +#include "../common/exceptions.h" using namespace dbps::external; using namespace dbps::enum_utils; diff --git a/src/server/compression_utils_test.cpp b/src/server/compression_utils_test.cpp index 0def963..bdf07c6 100644 --- a/src/server/compression_utils_test.cpp +++ b/src/server/compression_utils_test.cpp @@ -16,7 +16,7 @@ // under the License. #include "compression_utils.h" -#include "exceptions.h" +#include "../common/exceptions.h" #include #include #include diff --git a/src/server/decoding_utils.cpp b/src/server/decoding_utils.cpp index d6a7a5a..b790b0b 100644 --- a/src/server/decoding_utils.cpp +++ b/src/server/decoding_utils.cpp @@ -17,11 +17,8 @@ #include "decoding_utils.h" #include "enum_utils.h" -#include #include -#include -#include -#include +#include using namespace dbps::external; using namespace dbps::enum_utils; @@ -57,7 +54,9 @@ int CalculateLevelBytesLength(const std::vector& raw, int32_t def_level_length = std::get(encoding_attribs.at("page_v2_definition_levels_byte_length")); int32_t rep_level_length = std::get(encoding_attribs.at("page_v2_repetition_levels_byte_length")); total_level_bytes = def_level_length + rep_level_length; - std::cout << "CalculateLevelBytesLength DATA_PAGE_V2: total_level_bytes=" << total_level_bytes << std::endl; + // TODO(Issue 183): Remove unnecessary printouts in this function. + std::cout << "CalculateLevelBytesLength DATA_PAGE_V2: total_level_bytes=" + << total_level_bytes << std::endl; } else if (page_type == "DATA_PAGE_V1") { // Check that encoding types are RLE (instead of BIT_PACKED which is deprecated) @@ -76,12 +75,14 @@ int CalculateLevelBytesLength(const std::vector& raw, if (max_rep_level > 0) { int bytes_skipped = SkipV1RLELevel(offset); total_level_bytes += bytes_skipped; - std::cout << "CalculateLevelBytesLength DATA_PAGE_V1: repetition level bytes skipped=" << bytes_skipped << std::endl; + std::cout << "CalculateLevelBytesLength DATA_PAGE_V1: repetition level bytes skipped=" + << bytes_skipped << std::endl; } if (max_def_level > 0) { int bytes_skipped = SkipV1RLELevel(offset); total_level_bytes += bytes_skipped; - std::cout << "CalculateLevelBytesLength DATA_PAGE_V1: definition level bytes skipped=" << bytes_skipped << std::endl; + std::cout << "CalculateLevelBytesLength DATA_PAGE_V1: definition level bytes skipped=" + << bytes_skipped << std::endl; } } else if (page_type == "DICTIONARY_PAGE") { @@ -106,19 +107,35 @@ int CalculateLevelBytesLength(const std::vector& raw, return total_level_bytes; } -template -std::vector DecodeFixedSizeType(const uint8_t* raw_data, size_t raw_size, const char* name) { - if ((raw_size % sizeof(T)) != 0) { - throw InvalidInputException(std::string("Invalid data size for ") + name + " decoding"); +inline static size_t GetFixedElemSizeOrThrow(Type::type datatype, const std::optional& datatype_length) { + switch (datatype) { + case Type::INT32: + case Type::FLOAT: + return 4; + case Type::INT64: + case Type::DOUBLE: + return 8; + case Type::INT96: + // INT96 is three little-endian uint32 words laid out consecutively (lo, mid, hi). + return 12; + case Type::FIXED_LEN_BYTE_ARRAY: { + if (!datatype_length.has_value() || datatype_length.value() <= 0) { + throw InvalidInputException( + "FIXED_LEN_BYTE_ARRAY requires a positive datatype_length"); + } + return static_cast(datatype_length.value()); + } + case Type::UNDEFINED: + return 1; + case Type::BYTE_ARRAY: + throw DBPSUnsupportedException("BYTE_ARRAY is variable-length; not fixed-size"); + default: + throw DBPSUnsupportedException( + "Unsupported datatype: " + std::string(to_string(datatype))); } - std::vector result; - const T* v = reinterpret_cast(raw_data); - size_t count = raw_size / sizeof(T); - result.assign(v, v + count); - return result; } -TypedListValues ParseValueBytesIntoTypedList( +std::vector SliceValueBytesIntoRawBytes( const std::vector& bytes, Type::type datatype, const std::optional& datatype_length, @@ -126,166 +143,103 @@ TypedListValues ParseValueBytesIntoTypedList( if (format != Format::PLAIN) { throw DBPSUnsupportedException("Unsupported format: " + std::string(to_string(format))); } - switch (datatype) { - case Type::INT32: { - return DecodeFixedSizeType(bytes.data(), bytes.size(), "INT32"); - } - case Type::INT64: { - return DecodeFixedSizeType(bytes.data(), bytes.size(), "INT64"); - } - case Type::FLOAT: { - return DecodeFixedSizeType(bytes.data(), bytes.size(), "FLOAT"); - } - case Type::DOUBLE: { - return DecodeFixedSizeType(bytes.data(), bytes.size(), "DOUBLE"); - } - case Type::INT96: { - if ((bytes.size() % 12) != 0) { - throw InvalidInputException("Invalid data size for INT96 decoding"); - } - std::vector> result; - const uint8_t* p = bytes.data(); - const uint8_t* last_byte = bytes.data() + bytes.size(); - while (p + 12 <= last_byte) { - std::array value; - memcpy(&value[0], p + 0, 4); - memcpy(&value[1], p + 4, 4); - memcpy(&value[2], p + 8, 4); - result.push_back(value); - p += 12; - } - return result; - } - case Type::BYTE_ARRAY: { - std::vector result; - const uint8_t* p = bytes.data(); - const uint8_t* last_byte = bytes.data() + bytes.size(); - while (p + 4 <= last_byte) { - uint32_t len; - memcpy(&len, p, sizeof(len)); - p += 4; - if (p + len > last_byte) { - throw InvalidInputException( - "Invalid BYTE_ARRAY encoding: length exceeds data bounds"); - } - const char* s = reinterpret_cast(p); - result.emplace_back(s, len); - p += len; - } - if (p != last_byte) { - throw InvalidInputException( - "Invalid BYTE_ARRAY encoding: unexpected trailing bytes"); - } - return result; - } - case Type::FIXED_LEN_BYTE_ARRAY: { - if (!datatype_length.has_value() || datatype_length.value() <= 0) { - throw InvalidInputException( - "FIXED_LEN_BYTE_ARRAY requires positive datatype_length"); - } - int fixed_length = datatype_length.value(); - if ((bytes.size() % fixed_length) != 0) { + + // Variable-length BYTE_ARRAY: parse [4-byte len][bytes...] elements in order. + // This is the Parquet specific encoding for BYTE_ARRAY. + if (datatype == Type::BYTE_ARRAY) { + std::vector out; + const uint8_t* p = bytes.data(); + const uint8_t* last = bytes.data() + bytes.size(); + while (p + 4 <= last) { + uint32_t len = 0; + std::memcpy(&len, p, sizeof(len)); // little-endian length + p += 4; + if (p + len > last) { throw InvalidInputException( - "Invalid data size for FIXED_LEN_BYTE_ARRAY decoding"); + "Invalid BYTE_ARRAY encoding: length exceeds data bounds"); } - std::vector result; - size_t element_count = bytes.size() / fixed_length; - for (size_t i = 0; i < element_count; ++i) { - const char* element_start = reinterpret_cast( - bytes.data() + i * fixed_length); - result.emplace_back(element_start, fixed_length); - } - return result; + out.emplace_back(p, p + len); + p += len; } - case Type::UNDEFINED: { - std::vector result; - const char* s = reinterpret_cast(bytes.data()); - result.assign(s, s + bytes.size()); - return result; - } - default: { - throw DBPSUnsupportedException( - "Unsupported datatype: " + std::string(to_string(datatype))); + if (p != last) { + throw InvalidInputException("Invalid BYTE_ARRAY encoding: trailing bytes remain"); } + return out; + } + + // Fixed-size types: slice into raw value bytes in order. + const size_t elem_size = GetFixedElemSizeOrThrow(datatype, datatype_length); + + // Validate that the input size is divisible by the element size + if ((bytes.size() % elem_size) != 0) { + throw InvalidInputException("Input size not divisible by element width"); + } + + // Slice the input bytes into raw value bytes + const size_t count = bytes.size() / elem_size; + std::vector out; + out.reserve(count); + for (size_t i = 0; i < count; ++i) { + const auto begin = bytes.begin() + static_cast(i * elem_size); + out.emplace_back(begin, begin + static_cast(elem_size)); } + return out; } -std::vector GetTypedListAsValueBytes( - const TypedListValues& list, +std::vector CombineRawBytesIntoValueBytes( + const std::vector& elements, Type::type datatype, const std::optional& datatype_length, Format::type format) { - throw DBPSUnsupportedException("GetTypedListAsValueBytes not implemented"); + if (format != Format::PLAIN) { + throw DBPSUnsupportedException("Unsupported format: " + std::string(to_string(format))); + } + + if (datatype == Type::BYTE_ARRAY) { + std::vector out; + size_t total = 0; + for (const auto& v : elements) { + total += 4 + v.size(); + } + out.reserve(total); + for (const auto& v : elements) { + append_u32_le(out, static_cast(v.size())); + out.insert(out.end(), v.begin(), v.end()); + } + return out; + } + + const size_t elem_size = GetFixedElemSizeOrThrow(datatype, datatype_length); + + for (size_t i = 0; i < elements.size(); ++i) { + if (elements[i].size() != elem_size) { + throw InvalidInputException("Element size mismatch for fixed-size datatype"); + } + } + + std::vector out; + out.reserve(elem_size * elements.size()); + for (const auto& v : elements) { + out.insert(out.end(), v.begin(), v.end()); + } + return out; } -template -const char* GetTypeName() { - if constexpr (std::is_same_v>) return "INT32"; - else if constexpr (std::is_same_v>) return "INT64"; - else if constexpr (std::is_same_v>) return "FLOAT"; - else if constexpr (std::is_same_v>) return "DOUBLE"; - else if constexpr (std::is_same_v>>) return "INT96"; - else if constexpr (std::is_same_v>) - return "string (BYTE_ARRAY/FIXED_LEN_BYTE_ARRAY)"; - else if constexpr (std::is_same_v>) return "UNDEFINED (raw bytes)"; - else if constexpr (std::is_same_v) return "empty/error"; - else return "unknown"; +TypedListValues ParseValueBytesIntoTypedList( + const std::vector& bytes, + Type::type datatype, + const std::optional& datatype_length, + Format::type format) { + std::vector raw_values = + SliceValueBytesIntoRawBytes(bytes, datatype, datatype_length, format); + return BuildTypedListFromRawBytes(datatype, raw_values); } -std::string PrintTypedList(const TypedListValues& list) { - std::ostringstream out; - - std::visit([&out](auto&& values) { - using T = std::decay_t; - - if constexpr (std::is_same_v) { - out << "Empty/error state\n"; - } - else if constexpr (std::is_same_v>>) { - // Special case for INT96 - [lo, mid, hi] format - out << "Decoded INT96 values ([lo, mid, hi] 32-bit words):\n"; - for (size_t i = 0; i < values.size(); ++i) { - out << " [" << i << "] [" << values[i][0] << ", " - << values[i][1] << ", " << values[i][2] << "]\n"; - } - } - else if constexpr (std::is_same_v>) { - // Special case for UNDEFINED - raw bytes as hex - out << "Decoded UNDEFINED type (raw bytes):\n"; - out << " Hex: "; - for (size_t i = 0; i < values.size(); ++i) { - out << std::hex << std::setw(2) << std::setfill('0') - << static_cast(values[i]); - if (i < values.size() - 1) out << " "; - } - out << std::dec << "\n"; // Reset to decimal - - // Also show as string if printable - out << " String: \""; - for (uint8_t byte : values) { - if (byte >= 32 && byte < 127) { - out << static_cast(byte); - } else { - out << "."; - } - } - out << "\"\n"; - } - else if constexpr (std::is_same_v>) { - // String values with quotes and the length of the string. - out << "Decoded " << GetTypeName() << " values:\n"; - for (size_t i = 0; i < values.size(); ++i) { - out << " [" << i << "] \"" << values[i] << "\" (length: " << values[i].size() << ")\n"; - } - } - else { - // Generic case for numeric types (int32, int64, float, double) - out << "Decoded " << GetTypeName() << " values:\n"; - for (size_t i = 0; i < values.size(); ++i) { - out << " [" << i << "] " << values[i] << "\n"; - } - } - }, list); - - return out.str(); +std::vector GetTypedListAsValueBytes( + const TypedListValues& list, + Type::type datatype, + const std::optional& datatype_length, + Format::type format) { + std::vector raw_values = BuildRawBytesFromTypedListValues(list); + return CombineRawBytesIntoValueBytes(raw_values, datatype, datatype_length, format); } diff --git a/src/server/decoding_utils.h b/src/server/decoding_utils.h index ebd7da5..70012a9 100644 --- a/src/server/decoding_utils.h +++ b/src/server/decoding_utils.h @@ -23,26 +23,17 @@ #include #include #include -#include #include "enums.h" -#include "exceptions.h" +#include "../common/exceptions.h" #include "enum_utils.h" +#include "../common/typed_list_values.h" +#include "../common/bytes_utils.h" struct LevelAndValueBytes { std::vector level_bytes; std::vector value_bytes; }; -using TypedListValues = std::variant< - std::vector, - std::vector, - std::vector, - std::vector, - std::vector>, // For INT96 - std::vector, // For BYTE_ARRAY and FIXED_LEN_BYTE_ARRAY - std::vector // For UNDEFINED, a plain untyped byte sequence. ->; - using namespace dbps::external; /** @@ -56,6 +47,25 @@ using namespace dbps::external; int CalculateLevelBytesLength(const std::vector& raw, const std::map>& encoding_attribs); +/** + * Slice a flat byte buffer into RawValueBytes elements according to datatype/format. + * This follows the Parquet specific encoding. + */ +std::vector SliceValueBytesIntoRawBytes( + const std::vector& bytes, + Type::type datatype, + const std::optional& datatype_length, + Format::type format); + +/** + * Combine RawValueBytes elements back into a flat value-bytes buffer. + */ +std::vector CombineRawBytesIntoValueBytes( + const std::vector& elements, + Type::type datatype, + const std::optional& datatype_length, + Format::type format); + /** * Parse the value bytes into a typed list based on the data type and format. * @@ -90,11 +100,3 @@ std::vector GetTypedListAsValueBytes( Type::type datatype, const std::optional& datatype_length, Format::type format); - -/** - * Print a typed list in a human-readable format. - * - * @param list The typed list to print - * @return String representation of the typed list - */ -std::string PrintTypedList(const TypedListValues& list); diff --git a/src/server/decoding_utils_test.cpp b/src/server/decoding_utils_test.cpp index a315680..fe098b5 100644 --- a/src/server/decoding_utils_test.cpp +++ b/src/server/decoding_utils_test.cpp @@ -16,7 +16,7 @@ // under the License. #include "decoding_utils.h" -#include "exceptions.h" +#include "../common/exceptions.h" #include #include #include @@ -25,6 +25,7 @@ #include #include #include +#include "../common/bytes_utils.h" using namespace dbps::external; @@ -224,3 +225,108 @@ TEST(DecodingUtils, ParseValueBytesIntoTypedList_InvalidDataSize) { EXPECT_THROW(ParseValueBytesIntoTypedList(bytes, Type::INT32, std::nullopt, Format::PLAIN), InvalidInputException); } + +TEST(DecodingUtils, SliceValueBytesIntoRawBytes_INT32) { + std::vector bytes = {0x04,0x03,0x02,0x01, 0x0D,0x0C,0x0B,0x0A}; + auto out = SliceValueBytesIntoRawBytes(bytes, Type::INT32, std::nullopt, Format::PLAIN); + ASSERT_EQ(out.size(), 2u); + EXPECT_EQ(out[0], (std::vector{0x04,0x03,0x02,0x01})); + EXPECT_EQ(out[1], (std::vector{0x0D,0x0C,0x0B,0x0A})); +} + +TEST(DecodingUtils, SliceValueBytesIntoRawBytes_INT96) { + std::vector bytes = { + 0x01,0x02,0x03,0x04, + 0x05,0x06,0x07,0x08, + 0x09,0x0A,0x0B,0x0C + }; + auto out = SliceValueBytesIntoRawBytes(bytes, Type::INT96, std::nullopt, Format::PLAIN); + ASSERT_EQ(out.size(), 1u); + EXPECT_EQ(out[0], bytes); +} + +TEST(DecodingUtils, SliceValueBytesIntoRawBytes_BYTE_ARRAY) { + std::vector bytes; + append_u32_le(bytes, 2); + bytes.insert(bytes.end(), {'h','i'}); + append_u32_le(bytes, 3); + bytes.insert(bytes.end(), {'x','y','z'}); + + auto out = SliceValueBytesIntoRawBytes(bytes, Type::BYTE_ARRAY, std::nullopt, Format::PLAIN); + ASSERT_EQ(out.size(), 2u); + EXPECT_EQ(out[0], (std::vector{'h','i'})); + EXPECT_EQ(out[1], (std::vector{'x','y','z'})); +} + +TEST(DecodingUtils, SliceValueBytesIntoRawBytes_BYTE_ARRAY_Truncated) { + std::vector bytes = {0x04,0x00,0x00,0x00, 'a','b','c'}; + EXPECT_THROW( + SliceValueBytesIntoRawBytes(bytes, Type::BYTE_ARRAY, std::nullopt, Format::PLAIN), + InvalidInputException); +} + +TEST(DecodingUtils, SliceValueBytesIntoRawBytes_FixedSizeMisaligned) { + std::vector bytes = {0x00,0x01,0x02}; // not divisible by 8 for INT64 + EXPECT_THROW( + SliceValueBytesIntoRawBytes(bytes, Type::INT64, std::nullopt, Format::PLAIN), + InvalidInputException); +} + +TEST(DecodingUtils, CombineRawBytesIntoValueBytes_INT32) { + std::vector elems = { + {0x04,0x03,0x02,0x01}, + {0x0D,0x0C,0x0B,0x0A} + }; + auto out = CombineRawBytesIntoValueBytes(elems, Type::INT32, std::nullopt, Format::PLAIN); + EXPECT_EQ(out, (std::vector{0x04,0x03,0x02,0x01, 0x0D,0x0C,0x0B,0x0A})); +} + +TEST(DecodingUtils, CombineRawBytesIntoValueBytes_BYTE_ARRAY) { + std::vector elems = { + {'h','i'}, + {'x','y','z'} + }; + auto out = CombineRawBytesIntoValueBytes(elems, Type::BYTE_ARRAY, std::nullopt, Format::PLAIN); + // Expect [len=2][hi][len=3][xyz] + std::vector expected; + append_u32_le(expected, 2); + expected.insert(expected.end(), {'h','i'}); + append_u32_le(expected, 3); + expected.insert(expected.end(), {'x','y','z'}); + EXPECT_EQ(out, expected); +} + +TEST(DecodingUtils, CombineRawBytesIntoValueBytes_FIXED_LEN_BYTE_ARRAY_SizeMismatch) { + // Expect length 3, but provide a 2-byte element -> should throw + std::vector elems = { + {'a','b'}, + {'x','y','z'} + }; + EXPECT_THROW( + CombineRawBytesIntoValueBytes(elems, Type::FIXED_LEN_BYTE_ARRAY, 3, Format::PLAIN), + InvalidInputException); +} + +TEST(DecodingUtils, SliceAndCombine_RoundTrip_INT64) { + // Two int64 values: little-endian bytes + std::vector bytes = { + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + auto sliced = SliceValueBytesIntoRawBytes(bytes, Type::INT64, std::nullopt, Format::PLAIN); + auto combined = CombineRawBytesIntoValueBytes(sliced, Type::INT64, std::nullopt, Format::PLAIN); + EXPECT_EQ(bytes, combined); +} + +TEST(DecodingUtils, SliceAndCombine_RoundTrip_BYTE_ARRAY) { + std::vector bytes; + append_u32_le(bytes, 3); + bytes.insert(bytes.end(), {'f','o','o'}); + append_u32_le(bytes, 0); // empty string + append_u32_le(bytes, 4); + bytes.insert(bytes.end(), {'b','a','r','!'}); + + auto sliced = SliceValueBytesIntoRawBytes(bytes, Type::BYTE_ARRAY, std::nullopt, Format::PLAIN); + auto combined = CombineRawBytesIntoValueBytes(sliced, Type::BYTE_ARRAY, std::nullopt, Format::PLAIN); + EXPECT_EQ(bytes, combined); +} diff --git a/src/server/encryption_sequencer.cpp b/src/server/encryption_sequencer.cpp index cbb7189..96ced07 100644 --- a/src/server/encryption_sequencer.cpp +++ b/src/server/encryption_sequencer.cpp @@ -18,9 +18,9 @@ #include "encryption_sequencer.h" #include "enum_utils.h" #include "decoding_utils.h" -#include "bytes_utils.h" +#include "../common/bytes_utils.h" #include "compression_utils.h" -#include "exceptions.h" +#include "../common/exceptions.h" #include "encryptors/basic_encryptor.h" #include #include diff --git a/src/server/encryptors/basic_encryptor.cpp b/src/server/encryptors/basic_encryptor.cpp index e20a2dd..0cef9b0 100644 --- a/src/server/encryptors/basic_encryptor.cpp +++ b/src/server/encryptors/basic_encryptor.cpp @@ -17,7 +17,7 @@ #include "basic_encryptor.h" #include "../decoding_utils.h" -#include "../exceptions.h" +#include "../../common/exceptions.h" #include "../../common/enum_utils.h" #include #include @@ -68,7 +68,7 @@ std::vector BasicEncryptor::EncryptValueList( const TypedListValues& typed_list) { // Printout the typed list. - auto print_result = PrintTypedList(typed_list); + auto print_result = TypedListToString(typed_list); if (print_result.length() > 1000) { std::cout << "Encrypt value - Decoded plaintext data (first 1000 chars):\n" << print_result.substr(0, 1000) << "..."; diff --git a/src/server/encryptors/basic_encryptor_test.cpp b/src/server/encryptors/basic_encryptor_test.cpp index 41b574c..7d62d17 100644 --- a/src/server/encryptors/basic_encryptor_test.cpp +++ b/src/server/encryptors/basic_encryptor_test.cpp @@ -16,9 +16,9 @@ // under the License. #include "basic_encryptor.h" -#include "../exceptions.h" +#include "../../common/enums.h" +#include "../../common/exceptions.h" #include "../decoding_utils.h" -#include "../common/enums.h" #include #include diff --git a/src/server/encryptors/dbps_encryptor.h b/src/server/encryptors/dbps_encryptor.h index d37922c..0fe27b2 100644 --- a/src/server/encryptors/dbps_encryptor.h +++ b/src/server/encryptors/dbps_encryptor.h @@ -21,9 +21,9 @@ #include #include #include -#include "../exceptions.h" -#include "../decoding_utils.h" -#include "../common/enums.h" +#include "../../common/exceptions.h" +#include "../../common/typed_list_values.h" +#include "../../common/enums.h" #ifndef DBPS_EXPORT #define DBPS_EXPORT