According to the IETF spec uuid variant bits should be set to '10'. All others are either invalid or reserved. The patch change the code to follow the spec. Closes scylladb/scylladb#27073
305 lines
12 KiB
C++
305 lines
12 KiB
C++
/*
|
|
* Copyright (C) 2014-present ScyllaDB
|
|
*/
|
|
|
|
/*
|
|
* SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
|
|
*/
|
|
|
|
#define BOOST_TEST_MODULE core
|
|
|
|
#include <boost/test/unit_test.hpp>
|
|
#include <utility>
|
|
#include "utils/UUID_gen.hh"
|
|
#include "marshal_exception.hh"
|
|
|
|
BOOST_AUTO_TEST_CASE(test_generation_of_name_based_UUID) {
|
|
auto uuid = utils::UUID_gen::get_name_UUID("systembatchlog");
|
|
BOOST_REQUIRE_EQUAL(fmt::to_string(uuid), "0290003c-977e-397c-ac3e-fdfdc01d626b");
|
|
}
|
|
|
|
using utils::UUID;
|
|
|
|
BOOST_AUTO_TEST_CASE(test_UUID_comparison) {
|
|
static const std::initializer_list<std::pair<UUID, UUID>> uuid_pairs = {
|
|
|
|
{ UUID("ffeeddcc-aa99-8877-6655-443322110000"), UUID("00000000-0000-0000-0000-000000000000") },
|
|
{ UUID("0feeddcc-aa99-8877-6655-443322110000"), UUID("00000000-0000-0000-0000-000000000000") },
|
|
{ UUID("00000000-0000-0000-0000-000000000001"), UUID("00000000-0000-0000-0000-000000000000") },
|
|
|
|
{ UUID("ffeeddcc-aa99-8877-6655-443322110001"), UUID("ffeeddcc-aa99-8877-6655-443322110000") },
|
|
{ UUID("0feeddcc-aa99-8877-6655-443322110000"), UUID("0eeeddcc-aa99-8877-6655-443322110000") },
|
|
{ UUID("0290003c-987e-397c-ac3e-fdfdc01d626b"), UUID("0290003c-977e-397c-ac3e-fdfdc01d626b") },
|
|
|
|
};
|
|
|
|
for (auto& p : uuid_pairs) {
|
|
BOOST_REQUIRE_GT(p.first, p.second);
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_from_string) {
|
|
auto check = [] (std::string_view sv) {
|
|
auto uuid = UUID(sv);
|
|
BOOST_CHECK_EQUAL(uuid.version(), 4);
|
|
BOOST_CHECK_EQUAL(fmt::to_string(uuid), sv);
|
|
BOOST_CHECK_EQUAL((uuid.get_least_significant_bits() >> 62) & 0x3, 2);
|
|
};
|
|
|
|
check("b1415756-49c3-4fa8-9b72-d1b867b032af");
|
|
check("85859d5c-fcf3-4b0b-9089-197b8b06735c");
|
|
check("e596c2f2-d29d-44a0-bb89-0a90ff928490");
|
|
check("f28f86f5-cbc2-4526-ba25-db90c226ec6a");
|
|
check("ce84997b-6ea2-4468-9f02-8a65abf4141a");
|
|
|
|
// shorter than 16 bytes
|
|
BOOST_CHECK_THROW(UUID(""), marshal_exception);
|
|
BOOST_CHECK_THROW(UUID("dead-beef"), marshal_exception);
|
|
// longer than 16 bytes
|
|
BOOST_CHECK_THROW(UUID("ce84997b-6ea2-4468-9f02-8a65abf4141-long-long-ago"), marshal_exception);
|
|
// unconvertible string
|
|
BOOST_CHECK_THROW(UUID("hellowol-dea2-4468-9f02-8a65abf4141a"), marshal_exception);
|
|
// trailing garbage in msb
|
|
BOOST_CHECK_THROW(UUID("ce84997b-6ea2-wxyz-9f02-8a65abf4141a"), marshal_exception);
|
|
// trailing garbage in lsb
|
|
BOOST_CHECK_THROW(UUID("ce84997b-6ea2-4468-9f02-8a65abf4wxyz"), marshal_exception);
|
|
// spaces at the beginning
|
|
BOOST_CHECK_THROW(UUID(" 4997b-6ea2-wxyz-9f02-8a65abf4141a"), marshal_exception);
|
|
// spaces at the end
|
|
BOOST_CHECK_THROW(UUID("ce84997b-6ea2-4468-9f02-8a65abf4 "), marshal_exception);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_make_random_uuid) {
|
|
std::vector<UUID> uuids;
|
|
for (auto i = 0; i < 100; i++) {
|
|
auto uuid = utils::make_random_uuid();
|
|
BOOST_CHECK_EQUAL(uuid.version(), 4);
|
|
BOOST_CHECK_EQUAL((uuid.get_least_significant_bits() >> 62) & 0x3, 2);
|
|
uuids.emplace_back(uuid);
|
|
}
|
|
std::sort(uuids.begin(), uuids.end());
|
|
BOOST_CHECK(std::unique(uuids.begin(), uuids.end()) == uuids.end());
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_get_time_uuid) {
|
|
using namespace std::chrono;
|
|
|
|
auto uuid = utils::UUID_gen::get_time_UUID();
|
|
BOOST_CHECK(uuid.is_timestamp());
|
|
|
|
auto tp = system_clock::now();
|
|
uuid = utils::UUID_gen::get_time_UUID(tp);
|
|
BOOST_CHECK(uuid.is_timestamp());
|
|
|
|
auto millis = duration_cast<milliseconds>(tp.time_since_epoch());
|
|
uuid = utils::UUID_gen::get_time_UUID(millis);
|
|
BOOST_CHECK(uuid.is_timestamp());
|
|
|
|
auto unix_timestamp = utils::UUID_gen::unix_timestamp(uuid);
|
|
BOOST_CHECK(unix_timestamp == millis);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_uuid_to_uint32) {
|
|
// (gdb) p/x 0x3123223d ^ 0x17300 ^ 0x31e31215 ^ 0x98312
|
|
// $2 = 0xc8c03a
|
|
uint64_t x = 0x173003123223d;
|
|
uint64_t y = 0x9831231e31215;
|
|
uint32_t expected_id = 0xc8c03a;
|
|
auto uuid = utils::UUID(x, y);
|
|
uint32_t id = uuid_xor_to_uint32(uuid);
|
|
BOOST_CHECK(id == expected_id);
|
|
}
|
|
|
|
std::strong_ordering timeuuid_legacy_tri_compare(bytes_view o1, bytes_view o2) {
|
|
auto compare_pos = [&] (unsigned pos, int mask, std::strong_ordering ifequal) {
|
|
auto d = (o1[pos] & mask) <=> (o2[pos] & mask);
|
|
return d != 0 ? d : ifequal;
|
|
};
|
|
auto res = compare_pos(6, 0xf,
|
|
compare_pos(7, 0xff,
|
|
compare_pos(4, 0xff,
|
|
compare_pos(5, 0xff,
|
|
compare_pos(0, 0xff,
|
|
compare_pos(1, 0xff,
|
|
compare_pos(2, 0xff,
|
|
compare_pos(3, 0xff, std::strong_ordering::equal))))))));
|
|
if (res == 0) {
|
|
res = std::lexicographical_compare_three_way(o1.begin(), o1.end(), o2.begin(), o2.end(),
|
|
[] (const int8_t& a, const int8_t& b) { return a <=> b; });
|
|
}
|
|
return res;
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_timeuuid_msb_is_monotonic) {
|
|
using utils::UUID, utils::UUID_gen;
|
|
auto uuid = UUID_gen::get_time_UUID();
|
|
auto first = uuid.serialize();
|
|
int64_t scale_list[] = { 1, 10, 10000, 10000000, 0 };
|
|
auto str = [&scale_list](int64_t *scale) {
|
|
static std::string name_list[] = { " 100ns", "mc", "ms", "s" };
|
|
return name_list[scale - scale_list];
|
|
};
|
|
|
|
for (int64_t *scale = scale_list; *scale; scale++) {
|
|
int step = 1; /* + (random() % 169) ; */
|
|
auto prev = first;
|
|
for (int64_t i = 1; i < 3697; i += step) {
|
|
auto next = UUID(UUID_gen::create_time(UUID_gen::decimicroseconds{uuid.timestamp() + (i * *scale)}), 0).serialize();
|
|
bool t1 = utils::timeuuid_tri_compare(next, prev) > 0;
|
|
bool t2 = utils::timeuuid_tri_compare(next, first) > 0;
|
|
if (!t1 || !t2) {
|
|
BOOST_CHECK_MESSAGE(t1 && t2, seastar::format("a UUID {}{} later is not great than at test start: {} {}", i, str(scale), t1, t2));
|
|
}
|
|
prev = next;
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_timeuuid_tri_compare_legacy) {
|
|
using utils::UUID, utils::UUID_gen;
|
|
auto uuid = UUID_gen::get_time_UUID();
|
|
auto first = uuid.serialize();
|
|
int64_t scale_list[] = { 1, 10, 10000, 10000000, 0 };
|
|
auto str = [&scale_list](int64_t *scale) {
|
|
static std::string name_list[] = { " 100ns", "mc", "ms", "s" };
|
|
return name_list[scale - scale_list];
|
|
};
|
|
|
|
|
|
for (int64_t *scale = scale_list; *scale; scale++) {
|
|
int step = 1; /* + (random() % 169) ; */
|
|
auto prev = first;
|
|
for (int64_t i = 1; i < 3697; i += step) {
|
|
auto next = UUID(UUID_gen::create_time(UUID_gen::decimicroseconds{uuid.timestamp() + (i * *scale)}), 0).serialize();
|
|
bool t1 = utils::timeuuid_tri_compare(next, prev) == timeuuid_legacy_tri_compare(next, prev);
|
|
bool t2 = utils::timeuuid_tri_compare(next, first) == timeuuid_legacy_tri_compare(next, first);
|
|
if (!t1 || !t2) {
|
|
BOOST_CHECK_MESSAGE(t1 && t2, seastar::format("a UUID {}{} later violates compare order", i, str(scale)));
|
|
}
|
|
prev = next;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
BOOST_AUTO_TEST_CASE(test_timeuuid_submicro_is_monotonic) {
|
|
const int64_t PAD6 = 0xFFFF'FFFF'FFFF;
|
|
using namespace std::chrono;
|
|
using utils::UUID, utils::UUID_gen;
|
|
UUID current_timeuuid = UUID_gen::get_time_UUID();
|
|
// Node identifier must be set to avoid collisions
|
|
BOOST_CHECK((current_timeuuid.get_least_significant_bits() & PAD6) != 0);
|
|
int64_t micros = UUID_gen::micros_timestamp(current_timeuuid);
|
|
int maxsubmicro = (1 << 17) - 1;
|
|
int step = 1 + (random() % 169);
|
|
auto prev = UUID_gen::get_time_UUID_bytes_from_micros_and_submicros(std::chrono::microseconds{micros}, 0);
|
|
auto prev_timeuuid = UUID_gen::get_UUID(prev.data());
|
|
// Check prev_timeuuid node identifier is set. It uses
|
|
// a spoof MAC address, not the same as a standard UUID.
|
|
BOOST_CHECK((prev_timeuuid.get_least_significant_bits() & PAD6) != 0);
|
|
auto check_is_valid_timeuuid = [&](UUID uuid) {
|
|
// UUID is version 1
|
|
BOOST_CHECK(uuid.is_timestamp());
|
|
// UUID preserves the original microsecond time
|
|
BOOST_CHECK(UUID_gen::micros_timestamp(uuid) == micros);
|
|
// UUID 100nsec time grows monotonically
|
|
BOOST_CHECK(uuid.timestamp() >= prev_timeuuid.timestamp());
|
|
// UUID node is the same for all generated UUIDs
|
|
BOOST_CHECK((prev_timeuuid.get_least_significant_bits() & PAD6) ==
|
|
(uuid.get_least_significant_bits() & PAD6));
|
|
};
|
|
for (int i = 1; i <= maxsubmicro; i += step) {
|
|
auto uuid = UUID_gen::get_time_UUID_bytes_from_micros_and_submicros(
|
|
std::chrono::microseconds{micros}, i);
|
|
check_is_valid_timeuuid(UUID_gen::get_UUID(uuid.data()));
|
|
// UUID submicro part grows monotonically
|
|
BOOST_CHECK(utils::timeuuid_tri_compare({uuid.data(), 16}, {prev.data(), 16}) > 0);
|
|
prev = uuid;
|
|
}
|
|
BOOST_CHECK_EXCEPTION(UUID_gen::get_time_UUID_bytes_from_micros_and_submicros(std::chrono::microseconds{micros}, 2 * maxsubmicro),
|
|
utils::timeuuid_submicro_out_of_range, [](auto& x) -> bool { return true; });
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_min_time_uuid) {
|
|
using namespace std::chrono;
|
|
|
|
auto tp = system_clock::now();
|
|
auto millis = duration_cast<milliseconds>(tp.time_since_epoch());
|
|
auto uuid = utils::UUID_gen::min_time_UUID(millis);
|
|
BOOST_CHECK(uuid.is_timestamp());
|
|
|
|
auto unix_timestamp = utils::UUID_gen::unix_timestamp(uuid);
|
|
BOOST_CHECK(unix_timestamp == millis);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_max_time_uuid) {
|
|
using namespace std::chrono;
|
|
|
|
auto tp = system_clock::now();
|
|
auto millis = duration_cast<milliseconds>(tp.time_since_epoch());
|
|
auto uuid = utils::UUID_gen::max_time_UUID(millis);
|
|
BOOST_CHECK(uuid.is_timestamp());
|
|
|
|
auto unix_timestamp = utils::UUID_gen::unix_timestamp(uuid);
|
|
BOOST_CHECK(unix_timestamp == millis);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_negate) {
|
|
using namespace utils;
|
|
|
|
auto original_uuid = UUID_gen::get_time_UUID();
|
|
BOOST_TEST_MESSAGE(fmt::format("original_uuid: {}", original_uuid));
|
|
|
|
auto negated_uuid = UUID_gen::negate(original_uuid);
|
|
BOOST_TEST_MESSAGE(fmt::format("negated_uuid: {}", negated_uuid));
|
|
|
|
BOOST_REQUIRE(original_uuid != negated_uuid);
|
|
|
|
auto re_negated_uuid = UUID_gen::negate(negated_uuid);
|
|
BOOST_TEST_MESSAGE(fmt::format("re_negated_uuid: {}", re_negated_uuid));
|
|
|
|
BOOST_REQUIRE(original_uuid == re_negated_uuid);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_null_uuid) {
|
|
// Verify that the default-constructed UUID is null
|
|
utils::UUID uuid;
|
|
BOOST_CHECK(uuid.is_null());
|
|
BOOST_CHECK(!uuid);
|
|
|
|
// Verify that the null_uuid is indeed null
|
|
uuid = utils::null_uuid();
|
|
BOOST_CHECK(uuid.is_null());
|
|
BOOST_CHECK(!uuid);
|
|
|
|
// Verify that the default constructed
|
|
// UUID and the null_uuid are equal.
|
|
BOOST_REQUIRE_EQUAL(UUID(), utils::null_uuid());
|
|
|
|
// Verify that a random uuid is not null
|
|
uuid = utils::make_random_uuid();
|
|
BOOST_CHECK(!uuid.is_null());
|
|
BOOST_CHECK(uuid);
|
|
|
|
// Verify that a time uuid is not null
|
|
uuid = utils::UUID_gen::get_time_UUID();
|
|
BOOST_CHECK(!uuid.is_null());
|
|
BOOST_CHECK(uuid);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(check_get_random_time_UUID_from_micros_variant) {
|
|
using namespace std::chrono;
|
|
|
|
for (int i = 0; i < 100; i++) {
|
|
auto tp = system_clock::now();
|
|
auto micros = duration_cast<microseconds>(tp.time_since_epoch());
|
|
|
|
auto uuid = utils::UUID_gen::get_random_time_UUID_from_micros(micros);
|
|
// check that generated UUID is timeuuid
|
|
BOOST_CHECK(uuid.is_timestamp());
|
|
// check that generated UUID preserves the original microsecond time
|
|
BOOST_CHECK_EQUAL(utils::UUID_gen::micros_timestamp(uuid), micros.count());
|
|
// check that variant is RFC 4122 (IETF)
|
|
BOOST_CHECK_EQUAL((uuid.get_least_significant_bits() >> 62) & 0x3, 2);
|
|
}
|
|
} |