diff --git a/src/datamodel/serializer/Uuid.cpp b/src/datamodel/serializer/Uuid.cpp index 65fb53d8d..35aa7b712 100644 --- a/src/datamodel/serializer/Uuid.cpp +++ b/src/datamodel/serializer/Uuid.cpp @@ -10,3 +10,154 @@ // SPDX-License-Identifier: Apache-2.0 #include "up-cpp/datamodel/serializer/Uuid.h" + +#include + +#include +#include +#include + +namespace { +constexpr uint64_t TIMESTAMP_MASK = 0xFFFFFFFFFFFF; +constexpr uint64_t TIMESTAMP_SHIFT = 16; +constexpr uint64_t VERSION_MASK = 0xF; +constexpr uint64_t VERSION_SHIFT = 12; +constexpr uint64_t COUNTER_MASK = 0xFFF; +constexpr uint64_t VARIANT_MASK = 0x3; +constexpr uint64_t VARIANT_SHIFT = 62; +constexpr uint64_t RANDOM_MASK = 0x3FFFFFFFFFFFFFFF; +constexpr uint64_t RANDOM_SHIFT = 48; +constexpr size_t UUID_BYTE_SIZE = 16; +constexpr size_t UUID_PART_SIZE = 4; +constexpr uint32_t HEX_BASE = 16; +constexpr uint64_t MASK_32_BITS = 0xFFFFFFFF; +constexpr uint64_t MASK_16_BITS = 0xFFFF; +constexpr uint64_t MASK_14_BITS = 0x3FFF; +constexpr size_t MSB_HIGH_ = 0; +constexpr size_t MSB_LOW__ = 4; +constexpr size_t LSB_HIGH_ = 8; +constexpr size_t LSB_LOW__ = 12; +} // namespace + +namespace uprotocol::datamodel::serializer::uuid { + +std::string AsString::serialize(const uprotocol::v1::UUID uuid) { + // Extracting the parts of the UUIDv8 + uint64_t unix_ts_ms = (uuid.msb() >> TIMESTAMP_SHIFT) & TIMESTAMP_MASK; + uint8_t ver = (uuid.msb() >> VERSION_SHIFT) & VERSION_MASK; + uint16_t counter = uuid.msb() & COUNTER_MASK; + uint8_t var = (uuid.lsb() >> VARIANT_SHIFT) & VARIANT_MASK; + uint64_t rand_b = uuid.lsb() & RANDOM_MASK; + + // Formatting the UUIDv8 in the traditional format + std::stringstream ss; + ss << std::hex << std::setfill('0') << std::setw(8) + << ((unix_ts_ms >> 16) & MASK_32_BITS) // First 32 bits of timestamp + << "-" << std::setw(4) + << ((unix_ts_ms)&MASK_16_BITS) // Next 16 bits of timestamp i.e. last 16 + // bit of ts + << "-" << std::setw(4) + << (((ver & VERSION_MASK) << 12) | + (counter & COUNTER_MASK)) // Last 16 bits of timestamp and version + << "-" << std::setw(4) + << (((var & VARIANT_MASK) << 14) | + ((rand_b >> RANDOM_SHIFT) & MASK_14_BITS)) // Variant and randb + << "-" << std::setw(12) << (rand_b & TIMESTAMP_MASK); // Random number + + return std::move(ss).str(); +} + +uprotocol::v1::UUID AsString::deserialize(const std::string& str) { + // Check if the UUID string is in the correct format + // Format : 12345678-1234-1234-1234-123456789012 + // Index : 01234567890123456789012345678901234 + // Layout : ***msb**-lsb*-vcnt-varr-***RAND***** + // msb - timestamp most significant bits (32 bits) + // lsb - timestamp least significant bits (16 bits) + // v - version (4 bits) + // cnt - counter (12 bits) + // var - variant (2 bits) + // RAND - random (62 bits) + // Please check UP-spec for UUID formatting: + // https://github.com/eclipse-uprotocol/up-spec/blob/main/basics/uuid.adoc + + if (str.length() != 36 || str[8] != '-' || str[13] != '-' || + str[18] != '-' || str[23] != '-') { + throw std::invalid_argument("Invalid UUID string format"); + } + + uprotocol::v1::UUID uuid; + uint64_t unix_ts_ms = 0; + uint8_t ver = 0; + uint16_t counter = 0; + uint8_t var = 0; + uint64_t rand_b = 0; + + try { + // Extract the parts from the UUID string + unix_ts_ms = std::stoull(str.substr(0, 8), nullptr, HEX_BASE) << 16; + unix_ts_ms |= std::stoull(str.substr(9, 4), nullptr, HEX_BASE); + + uint16_t ver_counter = std::stoul(str.substr(14, 4), nullptr, HEX_BASE); + ver = (ver_counter >> 12) & VERSION_MASK; + counter = ver_counter & COUNTER_MASK; + + uint16_t var_randb = std::stoul(str.substr(19, 4), nullptr, HEX_BASE); + var = (var_randb >> 14) & VARIANT_MASK; + rand_b = static_cast(var_randb & MASK_14_BITS) + << RANDOM_SHIFT; + rand_b |= std::stoull(str.substr(24), nullptr, HEX_BASE); + } catch (const std::exception& e) { + throw std::invalid_argument("Invalid UUID string format"); + } + + // Reconstruct the UUID + uuid.set_msb((unix_ts_ms << TIMESTAMP_SHIFT) | (ver << VERSION_SHIFT) | + counter); + uuid.set_lsb((static_cast(var) << VARIANT_SHIFT) | rand_b); + + return uuid; +} + +// Serialization function +std::vector AsBytes::serialize(const v1::UUID uuid) { + std::vector bytes(UUID_BYTE_SIZE); + + uint32_t msb_high = htonl(static_cast(uuid.msb() >> 32)); + uint32_t msb_low = htonl(static_cast(uuid.msb() & MASK_32_BITS)); + uint32_t lsb_high = htonl(static_cast(uuid.lsb() >> 32)); + uint32_t lsb_low = htonl(static_cast(uuid.lsb() & MASK_32_BITS)); + + std::memcpy(&bytes[MSB_HIGH_], &msb_high, UUID_PART_SIZE); + std::memcpy(&bytes[MSB_LOW__], &msb_low, UUID_PART_SIZE); + std::memcpy(&bytes[LSB_HIGH_], &lsb_high, UUID_PART_SIZE); + std::memcpy(&bytes[LSB_LOW__], &lsb_low, UUID_PART_SIZE); + + return bytes; +} + +v1::UUID AsBytes::deserialize(const std::vector& bytes) { + if (bytes.size() != UUID_BYTE_SIZE) { + throw std::invalid_argument("Invalid UUID byte array size"); + } + + uint32_t msb_high, msb_low, lsb_high, lsb_low; + + std::memcpy(&msb_high, &bytes[MSB_HIGH_], UUID_PART_SIZE); + std::memcpy(&msb_low, &bytes[MSB_LOW__], UUID_PART_SIZE); + std::memcpy(&lsb_high, &bytes[LSB_HIGH_], UUID_PART_SIZE); + std::memcpy(&lsb_low, &bytes[LSB_LOW__], UUID_PART_SIZE); + + uint64_t msb = + (static_cast(ntohl(msb_high)) << 32) | ntohl(msb_low); + uint64_t lsb = + (static_cast(ntohl(lsb_high)) << 32) | ntohl(lsb_low); + + v1::UUID uuid; + uuid.set_msb(msb); + uuid.set_lsb(lsb); + + return uuid; +} + +} // namespace uprotocol::datamodel::serializer::uuid diff --git a/test/coverage/datamodel/UuidSerializerTest.cpp b/test/coverage/datamodel/UuidSerializerTest.cpp index 676a35d88..152f29fe5 100644 --- a/test/coverage/datamodel/UuidSerializerTest.cpp +++ b/test/coverage/datamodel/UuidSerializerTest.cpp @@ -11,10 +11,14 @@ #include #include +#include + +#include +#include namespace { -class TestFixture : public testing::Test { +class TestUuidSerializer : public testing::Test { protected: // Run once per TEST_F. // Used to set up clean environments per test. @@ -23,8 +27,8 @@ class TestFixture : public testing::Test { // Run once per execution of the test application. // Used for setup of all tests. Has access to this instance. - TestFixture() = default; - ~TestFixture() = default; + TestUuidSerializer() = default; + ~TestUuidSerializer() = default; // Run once per execution of the test application. // Used only for global setup outside of tests. @@ -32,7 +36,254 @@ class TestFixture : public testing::Test { static void TearDownTestSuite() {} }; -// TODO replace -TEST_F(TestFixture, SomeTestName) {} +// Test string serialization +TEST_F(TestUuidSerializer, SerializeToString) { + uprotocol::v1::UUID uuid; + uuid.set_msb(0x1234567890ABCDEF); + uuid.set_lsb(0xFEDCBA0987654321); + + std::string serialized_uuid = + uprotocol::datamodel::serializer::uuid::AsString::serialize(uuid); + // Assert the serialized UUID matches the expected value + EXPECT_EQ(serialized_uuid, "12345678-90ab-cdef-fedc-ba0987654321"); +} + +// Test serialization with leading zeros in each segment +TEST_F(TestUuidSerializer, SerializeWithLeadingZeros) { + uprotocol::v1::UUID uuid; + uuid.set_msb(0x00001234007800AB); + uuid.set_lsb(0x00FE00BA09876543); + + std::string serialized_uuid = + uprotocol::datamodel::serializer::uuid::AsString::serialize(uuid); + // Assert the serialized UUID matches the expected value + EXPECT_EQ(serialized_uuid, "00001234-0078-00ab-00fe-00ba09876543"); +} + +// Test serialization with mixed case letters +TEST_F(TestUuidSerializer, SerializeWithMixedCaseLetters) { + uprotocol::v1::UUID uuid; + uuid.set_msb(0x1234567890ABcDEF); + uuid.set_lsb(0x00dcbA0987654321); + + std::string serialized_uuid = + uprotocol::datamodel::serializer::uuid::AsString::serialize(uuid); + // Assert the serialized UUID matches the expected value in lowercase + EXPECT_EQ(serialized_uuid, "12345678-90ab-cdef-00dc-ba0987654321"); +} + +// Test serialization with leading zeros and mixed case letters +TEST_F(TestUuidSerializer, SerializeWithLeadingZerosAndMixedCaseLetters) { + uprotocol::v1::UUID uuid; + uuid.set_msb(0x00001234567890AB); + uuid.set_lsb(0xFedcba0987654982); + + std::string serialized_uuid = + uprotocol::datamodel::serializer::uuid::AsString::serialize(uuid); + // Assert the serialized UUID matches the expected value + EXPECT_EQ(serialized_uuid, "00001234-5678-90ab-fedc-ba0987654982"); +} + +// Test serialization with leading/trailing zeros and mixed case letters +TEST_F(TestUuidSerializer, + SerializeWithLeadingZerosAndTrailingZerosAndMixedCaseLetters) { + uprotocol::v1::UUID uuid; + uuid.set_msb(0x00001234567890AB); + uuid.set_lsb(0xFedcba0987600000); + + std::string serialized_uuid = + uprotocol::datamodel::serializer::uuid::AsString::serialize(uuid); + // Assert the serialized UUID matches the expected value + EXPECT_EQ(serialized_uuid, "00001234-5678-90ab-fedc-ba0987600000"); +} + +// Test string deserialization +TEST(DeserializerTest, DeserializeUUID) { + // Define a UUID string in the traditional format + std::string uuid_str = "12345678-9abc-def0-fedc-ba9876543210"; + // Deserialize the UUID string + uprotocol::v1::UUID deserialized_uuid = + uprotocol::datamodel::serializer::uuid::AsString::deserialize(uuid_str); + // Assert the deserialized UUID matches the expected values + EXPECT_EQ(deserialized_uuid.msb(), 0x123456789ABCDEF0); + EXPECT_EQ(deserialized_uuid.lsb(), 0xFEDCBA9876543210); +} + +// Test deserialization with leading/trailing zeros and mixed case letters +TEST_F(TestUuidSerializer, + DeserializeWithLeadingZerosAndTrailingZerosAndMixedCaseLetters) { + std::string uuid_str = "00001234-5678-90ab-feDc-ba0987600000"; + + uprotocol::v1::UUID deserialized_uuid = + uprotocol::datamodel::serializer::uuid::AsString::deserialize(uuid_str); + + // Assert the deserialized UUID matches the expected values + EXPECT_EQ(deserialized_uuid.msb(), 0x00001234567890aB); + EXPECT_EQ(deserialized_uuid.lsb(), 0xFedcBA0987600000); +} + +// Test invalid string deserialization +TEST(DeserializerTest, InvalidUUIDFormat) { + // Define an invalid UUID string (missing dashes) + std::string invalid_uuid_str = "123456789abcdef0123456789abcdef0"; + // Assert that deserialization throws an invalid argument exception + EXPECT_THROW(uprotocol::datamodel::serializer::uuid::AsString::deserialize( + invalid_uuid_str), + std::invalid_argument); +} + +// Test deserialization with correct length but incorrect placement of dashes +TEST(DeserializerTest, DeserializeWithMissingOneCharacter) { + std::string invalid_uuid = + "12345678-1234-5678-1234-56781234567"; // Missing one character + EXPECT_THROW(uprotocol::datamodel::serializer::uuid::AsString::deserialize( + invalid_uuid), + std::invalid_argument); +} + +// Test deserialization with UUIDs that have an extra character +TEST(DeserializerTest, DeserializeWithExtraCharacter) { + std::string invalid_uuid1 = + "12345678-1234-5678-1234-1234567890123"; // Extra character at the end + EXPECT_THROW(uprotocol::datamodel::serializer::uuid::AsString::deserialize( + invalid_uuid1), + std::invalid_argument); +} + +TEST(DeserializerTest, DeserializeWithIncorrectDashPlacement) { + std::string invalid_uuid1 = + "123456781-2345-6781-2345-67812345678"; // First Dash placement + + EXPECT_THROW(uprotocol::datamodel::serializer::uuid::AsString::deserialize( + invalid_uuid1), + std::invalid_argument); + std::string invalid_uuid2 = + "12345678-12345-6781-2345-67812345678"; // Second Dash placement + + EXPECT_THROW(uprotocol::datamodel::serializer::uuid::AsString::deserialize( + invalid_uuid2), + std::invalid_argument); + std::string invalid_uuid3 = + "12345678-1234-56781-2345-67812345678"; // Third Dash placement + + EXPECT_THROW(uprotocol::datamodel::serializer::uuid::AsString::deserialize( + invalid_uuid3), + std::invalid_argument); + std::string invalid_uuid4 = + "12345678-1234-5678-12345-67812345678"; // Fourth Dash placement + + EXPECT_THROW(uprotocol::datamodel::serializer::uuid::AsString::deserialize( + invalid_uuid4), + std::invalid_argument); +} + +// Test deserialization with a zero-length string +TEST(DeserializerTest, DeserializeEmptyString) { + // Define an empty UUID string + std::string empty_uuid_str = ""; + // Deserialize the empty UUID string + EXPECT_THROW(uprotocol::datamodel::serializer::uuid::AsString::deserialize( + empty_uuid_str), + std::invalid_argument); +} + +// Test deserialization with an invalid character in the UUID string +TEST(DeserializerTest, DeserializeInvalidCharacter) { + // Define a UUID string with an invalid character ('x' instead of valid hex) + std::string invalid_uuid_str = "1234567890ab-cdef-1234-5678-90abcdefxabc"; + EXPECT_THROW(uprotocol::datamodel::serializer::uuid::AsString::deserialize( + invalid_uuid_str), + std::invalid_argument); +} + +// Test byte serialization +TEST_F(TestUuidSerializer, SerializeToBytes) { + uprotocol::v1::UUID uuid; + uuid.set_msb(0x1234567890ABCDEF); + uuid.set_lsb(0xFEDCBA0987654321); + + std::vector uuid_bytes = + uprotocol::datamodel::serializer::uuid::AsBytes::serialize(uuid); + std::vector expected_bytes = {0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, + 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x09, + 0x87, 0x65, 0x43, 0x21}; + + EXPECT_EQ(uuid_bytes, expected_bytes); +} + +// Test byte deserialization +TEST_F(TestUuidSerializer, DeserializeFromBytes) { + std::vector uuid_bytes = {0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, + 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x09, + 0x87, 0x65, 0x43, 0x21}; + uprotocol::v1::UUID uuid = + uprotocol::datamodel::serializer::uuid::AsBytes::deserialize( + uuid_bytes); + + EXPECT_EQ(uuid.msb(), 0x1234567890ABCDEF); + EXPECT_EQ(uuid.lsb(), 0xFEDCBA0987654321); +} + +// Test invalid byte deserialization +TEST_F(TestUuidSerializer, DeserializeInvalidBytes) { + std::vector invalid_bytes = {0x12, 0x34, 0x56}; + EXPECT_THROW(uprotocol::datamodel::serializer::uuid::AsBytes::deserialize( + invalid_bytes), + std::invalid_argument); +} + +// Test edge case: minimum values for msb and lsb +TEST_F(TestUuidSerializer, SerializeDeserializeMinValues) { + uprotocol::v1::UUID uuid; + uuid.set_msb(0x0000000000000000); + uuid.set_lsb(0x0000000000000000); + + std::string uuid_str = + uprotocol::datamodel::serializer::uuid::AsString::serialize(uuid); + EXPECT_EQ(uuid_str, "00000000-0000-0000-0000-000000000000"); + + uprotocol::v1::UUID deserialized_uuid = + uprotocol::datamodel::serializer::uuid::AsString::deserialize(uuid_str); + EXPECT_EQ(deserialized_uuid.msb(), 0x0000000000000000); + EXPECT_EQ(deserialized_uuid.lsb(), 0x0000000000000000); + + std::vector uuid_bytes = + uprotocol::datamodel::serializer::uuid::AsBytes::serialize(uuid); + std::vector expected_bytes(16, 0x00); + EXPECT_EQ(uuid_bytes, expected_bytes); + + deserialized_uuid = + uprotocol::datamodel::serializer::uuid::AsBytes::deserialize( + uuid_bytes); + EXPECT_EQ(deserialized_uuid.msb(), 0x0000000000000000); + EXPECT_EQ(deserialized_uuid.lsb(), 0x0000000000000000); +} + +// Test edge case: maximum values for msb and lsb +TEST_F(TestUuidSerializer, SerializeDeserializeMaxValues) { + uprotocol::v1::UUID uuid; + uuid.set_msb(0xFFFFFFFFFFFFFFFF); + uuid.set_lsb(0xFFFFFFFFFFFFFFFF); + + std::string uuid_str = + uprotocol::datamodel::serializer::uuid::AsString::serialize(uuid); + EXPECT_EQ(uuid_str, "ffffffff-ffff-ffff-ffff-ffffffffffff"); + + uprotocol::v1::UUID deserialized_uuid = + uprotocol::datamodel::serializer::uuid::AsString::deserialize(uuid_str); + EXPECT_EQ(deserialized_uuid.msb(), 0xFFFFFFFFFFFFFFFF); + EXPECT_EQ(deserialized_uuid.lsb(), 0xFFFFFFFFFFFFFFFF); + + std::vector uuid_bytes = + uprotocol::datamodel::serializer::uuid::AsBytes::serialize(uuid); + std::vector expected_bytes(16, 0xFF); + EXPECT_EQ(uuid_bytes, expected_bytes); + + deserialized_uuid = + uprotocol::datamodel::serializer::uuid::AsBytes::deserialize( + uuid_bytes); + EXPECT_EQ(deserialized_uuid.msb(), 0xFFFFFFFFFFFFFFFF); + EXPECT_EQ(deserialized_uuid.lsb(), 0xFFFFFFFFFFFFFFFF); +} } // namespace