diff options
Diffstat (limited to 'nihil.uuid')
| -rw-r--r-- | nihil.uuid/CMakeLists.txt | 23 | ||||
| -rw-r--r-- | nihil.uuid/test.cc | 1001 | ||||
| -rw-r--r-- | nihil.uuid/uuid.ccm | 842 |
3 files changed, 1866 insertions, 0 deletions
diff --git a/nihil.uuid/CMakeLists.txt b/nihil.uuid/CMakeLists.txt new file mode 100644 index 0000000..fe037e7 --- /dev/null +++ b/nihil.uuid/CMakeLists.txt @@ -0,0 +1,23 @@ +# This source code is released into the public domain. + +add_library(nihil.uuid STATIC) +target_sources(nihil.uuid + PUBLIC FILE_SET modules TYPE CXX_MODULES FILES + uuid.ccm +) + +if(NIHIL_TESTS) + enable_testing() + + add_executable(nihil.uuid.test test.cc) + target_link_libraries(nihil.uuid.test PRIVATE + nihil.uuid + Catch2::Catch2WithMain + ) + + find_package(Catch2 REQUIRED) + + include(CTest) + include(Catch) + catch_discover_tests(nihil.uuid.test) +endif() diff --git a/nihil.uuid/test.cc b/nihil.uuid/test.cc new file mode 100644 index 0000000..0f21298 --- /dev/null +++ b/nihil.uuid/test.cc @@ -0,0 +1,1001 @@ +/* + * From https://github.com/mariusbancila/stduuid + * + * Copyright (c) 2017 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <algorithm> +#include <random> +#include <set> +#include <unordered_set> + +#include <catch2/catch_test_macros.hpp> + +//NOLINTBEGIN(bugprone-unchecked-optional-access) + +namespace +{ + +// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0205r0.html +template <typename EngineT, std::size_t StateSize = EngineT::state_size> +void seed_rng(EngineT& engine) +{ + using engine_type = typename EngineT::result_type; + using device_type = std::random_device::result_type; + using seedseq_type = std::seed_seq::result_type; + + constexpr auto bytes_needed = StateSize * sizeof(engine_type); + constexpr auto numbers_needed = + (sizeof(device_type) < sizeof(seedseq_type)) + ? (bytes_needed / sizeof(device_type)) + : (bytes_needed / sizeof(seedseq_type)); + + auto numbers = std::array<device_type, numbers_needed>{}; + auto rnddev = std::random_device{}; + std::ranges::generate(numbers, std::ref(rnddev)); + + auto seedseq = std::seed_seq(std::cbegin(numbers), + std::cend(numbers)); + engine.seed(seedseq); +} + +} // anonymous namespace + +import nihil.uuid; + +using namespace nihil; + +TEST_CASE("uuid: Test multiple default generators", "[uuid]") +{ + uuid id1; + uuid id2; + + { + std::random_device rd; + auto seed_data = std::array<int, std::mt19937::state_size> {}; + std::ranges::generate(seed_data, std::ref(rd)); + std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); + std::mt19937 generator(seq); + + id1 = uuid_random_generator{ generator }(); + REQUIRE(!id1.is_nil()); + REQUIRE(id1.version() == uuid_version::random_number_based); + REQUIRE(id1.variant() == uuid_variant::rfc); + } + + { + std::random_device rd; + auto seed_data = std::array<int, std::mt19937::state_size> {}; + std::ranges::generate(seed_data, std::ref(rd)); + std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); + std::mt19937 generator(seq); + + id2 = uuid_random_generator{ generator }(); + REQUIRE(!id2.is_nil()); + REQUIRE(id2.version() == uuid_version::random_number_based); + REQUIRE(id2.variant() == uuid_variant::rfc); + } + + REQUIRE(id1 != id2); +} + +TEST_CASE("uuid: Test default generator", "[uuid]") +{ + std::random_device rd; + auto seed_data = std::array<int, std::mt19937::state_size> {}; + std::ranges::generate(seed_data, std::ref(rd)); + std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); + std::mt19937 generator(seq); + + uuid const guid = uuid_random_generator{generator}(); + REQUIRE(!guid.is_nil()); + REQUIRE(guid.version() == uuid_version::random_number_based); + REQUIRE(guid.variant() == uuid_variant::rfc); +} + +TEST_CASE("uuid: Test random generator (conversion ctor w/ smart ptr)", + "[uuid]") +{ + std::random_device rd; + auto seed_data = std::array<int, std::mt19937::state_size> {}; + std::ranges::generate(seed_data, std::ref(rd)); + std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); + std::mt19937 generator(seq); + + uuid_random_generator dgen(&generator); + auto id1 = dgen(); + REQUIRE(!id1.is_nil()); + REQUIRE(id1.version() == uuid_version::random_number_based); + REQUIRE(id1.variant() == uuid_variant::rfc); + + auto id2 = dgen(); + REQUIRE(!id2.is_nil()); + REQUIRE(id2.version() == uuid_version::random_number_based); + REQUIRE(id2.variant() == uuid_variant::rfc); + + REQUIRE(id1 != id2); +} + +TEST_CASE("uuid: Test random generator (conversion ctor w/ ptr)", "[uuid]") +{ + std::random_device rd; + auto seed_data = std::array<int, std::mt19937::state_size> {}; + std::ranges::generate(seed_data, std::ref(rd)); + std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); + auto generator = std::make_unique<std::mt19937>(seq); + + uuid_random_generator dgen(generator.get()); + auto id1 = dgen(); + REQUIRE(!id1.is_nil()); + REQUIRE(id1.version() == uuid_version::random_number_based); + REQUIRE(id1.variant() == uuid_variant::rfc); + + auto id2 = dgen(); + REQUIRE(!id1.is_nil()); + REQUIRE(id2.version() == uuid_version::random_number_based); + REQUIRE(id2.variant() == uuid_variant::rfc); + + REQUIRE(id1 != id2); +} + +TEST_CASE("uuid: Test random generator (conversion ctor w/ ref)", "[uuid]") +{ + std::random_device rd; + auto seed_data = std::array<int, std::mt19937::state_size> {}; + std::ranges::generate(seed_data, std::ref(rd)); + std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); + std::mt19937 generator(seq); + + uuid_random_generator dgen(generator); + auto id1 = dgen(); + REQUIRE(!id1.is_nil()); + REQUIRE(id1.version() == uuid_version::random_number_based); + REQUIRE(id1.variant() == uuid_variant::rfc); + + auto id2 = dgen(); + REQUIRE(!id2.is_nil()); + REQUIRE(id2.version() == uuid_version::random_number_based); + REQUIRE(id2.variant() == uuid_variant::rfc); + + REQUIRE(id1 != id2); +} + +TEST_CASE("uuid: Test basic random generator (conversion ctor w/ ptr) " + "w/ ranlux48_base", "[uuid]") +{ + std::random_device rd; + auto seed_data = std::array<int, 6> {}; + std::ranges::generate(seed_data, std::ref(rd)); + std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); + std::ranlux48_base generator(seq); + + basic_uuid_random_generator<std::ranlux48_base> dgen(&generator); + auto id1 = dgen(); + REQUIRE(!id1.is_nil()); + REQUIRE(id1.version() == uuid_version::random_number_based); + REQUIRE(id1.variant() == uuid_variant::rfc); + + auto id2 = dgen(); + REQUIRE(!id2.is_nil()); + REQUIRE(id2.version() == uuid_version::random_number_based); + REQUIRE(id2.variant() == uuid_variant::rfc); + + REQUIRE(id1 != id2); +} + +TEST_CASE("uuid: Test basic random generator (conversion ctor w/ smart ptr) " + "w/ ranlux48_base", "[uuid]") +{ + std::random_device rd; + auto seed_data = std::array<int, 6> {}; + std::ranges::generate(seed_data, std::ref(rd)); + std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); + auto generator = std::make_unique<std::ranlux48_base>(seq); + + basic_uuid_random_generator<std::ranlux48_base> dgen(generator.get()); + auto id1 = dgen(); + REQUIRE(!id1.is_nil()); + REQUIRE(id1.version() == uuid_version::random_number_based); + REQUIRE(id1.variant() == uuid_variant::rfc); + + auto id2 = dgen(); + REQUIRE(!id2.is_nil()); + REQUIRE(id2.version() == uuid_version::random_number_based); + REQUIRE(id2.variant() == uuid_variant::rfc); + + REQUIRE(id1 != id2); +} + +TEST_CASE("uuid: Test basic random generator (conversion ctor w/ ref) " + "w/ ranlux48_base", "[uuid]") +{ + std::random_device rd; + auto seed_data = std::array<int, 6> {}; + std::ranges::generate(seed_data, std::ref(rd)); + std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); + std::ranlux48_base generator(seq); + + basic_uuid_random_generator<std::ranlux48_base> dgen(generator); + auto id1 = dgen(); + REQUIRE(!id1.is_nil()); + REQUIRE(id1.version() == uuid_version::random_number_based); + REQUIRE(id1.variant() == uuid_variant::rfc); + + auto id2 = dgen(); + REQUIRE(!id2.is_nil()); + REQUIRE(id2.version() == uuid_version::random_number_based); + REQUIRE(id2.variant() == uuid_variant::rfc); + + REQUIRE(id1 != id2); +} + +TEST_CASE("uuid: Test namespaces", "[uuid]") +{ + REQUIRE(uuid_namespace_dns == uuid::from_string("6ba7b810-9dad-11d1-80b4-00c04fd430c8")); + REQUIRE(uuid_namespace_url == uuid::from_string("6ba7b811-9dad-11d1-80b4-00c04fd430c8")); + REQUIRE(uuid_namespace_oid == uuid::from_string("6ba7b812-9dad-11d1-80b4-00c04fd430c8")); + REQUIRE(uuid_namespace_x500 == uuid::from_string("6ba7b814-9dad-11d1-80b4-00c04fd430c8")); +} + +TEST_CASE("uuid: Test name generator (char*)", "[uuid]") +{ + uuid_name_generator dgen(uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43").value()); + + auto id1 = dgen("john"); + REQUIRE(!id1.is_nil()); + REQUIRE(id1.version() == uuid_version::name_based_sha1); + REQUIRE(id1.variant() == uuid_variant::rfc); + + auto id2 = dgen("jane"); + REQUIRE(!id2.is_nil()); + REQUIRE(id2.version() == uuid_version::name_based_sha1); + REQUIRE(id2.variant() == uuid_variant::rfc); + + auto id3 = dgen("jane"); + REQUIRE(!id3.is_nil()); + REQUIRE(id3.version() == uuid_version::name_based_sha1); + REQUIRE(id3.variant() == uuid_variant::rfc); + + auto id4 = dgen(L"jane"); + REQUIRE(!id4.is_nil()); + REQUIRE(id4.version() == uuid_version::name_based_sha1); + REQUIRE(id4.variant() == uuid_variant::rfc); + + REQUIRE(id1 != id2); + REQUIRE(id2 == id3); + REQUIRE(id3 != id4); +} + +TEST_CASE("uuid: Test name generator (std::string)", "[uuid]") +{ + using namespace std::string_literals; + + uuid_name_generator dgen(uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43").value()); + auto id1 = dgen("john"s); + REQUIRE(!id1.is_nil()); + REQUIRE(id1.version() == uuid_version::name_based_sha1); + REQUIRE(id1.variant() == uuid_variant::rfc); + + auto id2 = dgen("jane"s); + REQUIRE(!id2.is_nil()); + REQUIRE(id2.version() == uuid_version::name_based_sha1); + REQUIRE(id2.variant() == uuid_variant::rfc); + + auto id3 = dgen("jane"s); + REQUIRE(!id3.is_nil()); + REQUIRE(id3.version() == uuid_version::name_based_sha1); + REQUIRE(id3.variant() == uuid_variant::rfc); + + auto id4 = dgen(L"jane"s); + REQUIRE(!id4.is_nil()); + REQUIRE(id4.version() == uuid_version::name_based_sha1); + REQUIRE(id4.variant() == uuid_variant::rfc); + + REQUIRE(id1 != id2); + REQUIRE(id2 == id3); + REQUIRE(id3 != id4); +} + +TEST_CASE("uuid: Test name generator (std::string_view)", "[uuid]") +{ + using namespace std::string_view_literals; + + uuid_name_generator dgen(uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43").value()); + auto id1 = dgen("john"sv); + REQUIRE(!id1.is_nil()); + REQUIRE(id1.version() == uuid_version::name_based_sha1); + REQUIRE(id1.variant() == uuid_variant::rfc); + + auto id2 = dgen("jane"sv); + REQUIRE(!id2.is_nil()); + REQUIRE(id2.version() == uuid_version::name_based_sha1); + REQUIRE(id2.variant() == uuid_variant::rfc); + + auto id3 = dgen("jane"sv); + REQUIRE(!id3.is_nil()); + REQUIRE(id3.version() == uuid_version::name_based_sha1); + REQUIRE(id3.variant() == uuid_variant::rfc); + + auto id4 = dgen(L"jane"sv); + REQUIRE(!id4.is_nil()); + REQUIRE(id4.version() == uuid_version::name_based_sha1); + REQUIRE(id4.variant() == uuid_variant::rfc); + + REQUIRE(id1 != id2); + REQUIRE(id2 == id3); + REQUIRE(id3 != id4); +} + +TEST_CASE("uuid: Test name generator equality (char const*, std::string, " + "std::string_view)", "[uuid]") +{ + using namespace std::literals; + + auto dgen = uuid_name_generator(uuid::from_string( + "47183823-2574-4bfd-b411-99ed177d3e43").value()); + auto id1 = dgen("john"); + auto id2 = dgen("john"s); + auto id3 = dgen("john"sv); + + REQUIRE(id1 == id2); + REQUIRE(id2 == id3); +} + +TEST_CASE("uuid: Test default constructor", "[uuid]") +{ + auto empty = uuid(); + REQUIRE(empty.is_nil()); +} + +TEST_CASE("uuid: Test string conversion", "[uuid]") +{ + auto empty = uuid(); + REQUIRE(to_string(empty) == + "00000000-0000-0000-0000-000000000000"); + REQUIRE(to_string<wchar_t>(empty) == + L"00000000-0000-0000-0000-000000000000"); +} + +TEST_CASE("uuid: Test is_valid_uuid(char*)", "[uuid]") +{ + REQUIRE(uuid::is_valid_uuid( + "47183823-2574-4bfd-b411-99ed177d3e43")); + REQUIRE(uuid::is_valid_uuid( + "{47183823-2574-4bfd-b411-99ed177d3e43}")); + REQUIRE(uuid::is_valid_uuid( + L"47183823-2574-4bfd-b411-99ed177d3e43")); + REQUIRE(uuid::is_valid_uuid( + L"{47183823-2574-4bfd-b411-99ed177d3e43}")); + REQUIRE(uuid::is_valid_uuid( + "00000000-0000-0000-0000-000000000000")); + REQUIRE(uuid::is_valid_uuid( + "{00000000-0000-0000-0000-000000000000}")); + REQUIRE(uuid::is_valid_uuid( + L"00000000-0000-0000-0000-000000000000")); + REQUIRE(uuid::is_valid_uuid( + L"{00000000-0000-0000-0000-000000000000}")); +} + +TEST_CASE("uuid: Test is_valid_uuid(basic_string)", "[uuid]") +{ + using namespace std::string_literals; + + { + auto str = "47183823-2574-4bfd-b411-99ed177d3e43"s; + REQUIRE(uuid::is_valid_uuid(str)); + } + + { + auto str = "{47183823-2574-4bfd-b411-99ed177d3e43}"s; + REQUIRE(uuid::is_valid_uuid(str)); + } + + { + auto str = L"47183823-2574-4bfd-b411-99ed177d3e43"s; + REQUIRE(uuid::is_valid_uuid(str)); + } + + { + auto str = L"{47183823-2574-4bfd-b411-99ed177d3e43}"s; + REQUIRE(uuid::is_valid_uuid(str)); + } + + { + auto str = "00000000-0000-0000-0000-000000000000"s; + REQUIRE(uuid::is_valid_uuid(str)); + } + + { + auto str = "{00000000-0000-0000-0000-000000000000}"s; + REQUIRE(uuid::is_valid_uuid(str)); + } + + { + auto str = L"00000000-0000-0000-0000-000000000000"s; + REQUIRE(uuid::is_valid_uuid(str)); + } + + { + auto str = L"{00000000-0000-0000-0000-000000000000}"s; + REQUIRE(uuid::is_valid_uuid(str)); + } +} + +TEST_CASE("uuid: Test is_valid_uuid(basic_string_view)", "[uuid]") +{ + using namespace std::string_view_literals; + + REQUIRE(uuid::is_valid_uuid( + "47183823-2574-4bfd-b411-99ed177d3e43"sv)); + REQUIRE(uuid::is_valid_uuid( + "{47183823-2574-4bfd-b411-99ed177d3e43}"sv)); + REQUIRE(uuid::is_valid_uuid( + L"47183823-2574-4bfd-b411-99ed177d3e43"sv)); + REQUIRE(uuid::is_valid_uuid( + L"{47183823-2574-4bfd-b411-99ed177d3e43}"sv)); + REQUIRE(uuid::is_valid_uuid( + "00000000-0000-0000-0000-000000000000"sv)); + REQUIRE(uuid::is_valid_uuid( + "{00000000-0000-0000-0000-000000000000}"sv)); + REQUIRE(uuid::is_valid_uuid( + L"00000000-0000-0000-0000-000000000000"sv)); + REQUIRE(uuid::is_valid_uuid( + L"{00000000-0000-0000-0000-000000000000}"sv)); +} + +TEST_CASE("uuid: Test is_valid_uuid(char*) invalid format", "[uuid]") +{ + REQUIRE(!uuid::is_valid_uuid("")); + REQUIRE(!uuid::is_valid_uuid("{}")); + REQUIRE(!uuid::is_valid_uuid( + "47183823-2574-4bfd-b411-99ed177d3e4")); + REQUIRE(!uuid::is_valid_uuid( + "47183823-2574-4bfd-b411-99ed177d3e430")); + REQUIRE(!uuid::is_valid_uuid( + "{47183823-2574-4bfd-b411-99ed177d3e43")); + REQUIRE(!uuid::is_valid_uuid( + "47183823-2574-4bfd-b411-99ed177d3e43}")); +} + +TEST_CASE("uuid: Test is_valid_uuid(basic_string) invalid format", "[uuid]") +{ + using namespace std::string_literals; + + { + auto str = ""s; + REQUIRE(!uuid::is_valid_uuid(str)); + } + + { + auto str = "{}"s; + REQUIRE(!uuid::is_valid_uuid(str)); + } + + { + auto str = "47183823-2574-4bfd-b411-99ed177d3e4"s; + REQUIRE(!uuid::is_valid_uuid(str)); + } + + { + auto str = "47183823-2574-4bfd-b411-99ed177d3e430"s; + REQUIRE(!uuid::is_valid_uuid(str)); + } + + { + auto str = "{47183823-2574-4bfd-b411-99ed177d3e43"s; + REQUIRE(!uuid::is_valid_uuid(str)); + } + + { + auto str = "47183823-2574-4bfd-b411-99ed177d3e43}"s; + REQUIRE(!uuid::is_valid_uuid(str)); + } +} + +TEST_CASE("uuid: Test is_valid_uuid(basic_string_view) invalid format", + "[uuid]") +{ + using namespace std::string_view_literals; + + REQUIRE(!uuid::is_valid_uuid(""sv)); + REQUIRE(!uuid::is_valid_uuid("{}"sv)); + REQUIRE(!uuid::is_valid_uuid( + "47183823-2574-4bfd-b411-99ed177d3e4"sv)); + REQUIRE(!uuid::is_valid_uuid( + "47183823-2574-4bfd-b411-99ed177d3e430"sv)); + REQUIRE(!uuid::is_valid_uuid( + "{47183823-2574-4bfd-b411-99ed177d3e43"sv)); + REQUIRE(!uuid::is_valid_uuid( + "47183823-2574-4bfd-b411-99ed177d3e43}"sv)); +} + +TEST_CASE("uuid: Test from_string(char*)", "[uuid]") +{ + { + auto str = "47183823-2574-4bfd-b411-99ed177d3e43"; + auto guid = uuid::from_string(str).value(); + REQUIRE(to_string(guid) == str); + } + + { + auto str = "{47183823-2574-4bfd-b411-99ed177d3e43}"; + auto guid = uuid::from_string(str).value(); + REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"); + } + + { + auto guid = uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43").value(); + REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"); + REQUIRE(to_string<wchar_t>(guid) == L"47183823-2574-4bfd-b411-99ed177d3e43"); + } + + { + auto str = L"47183823-2574-4bfd-b411-99ed177d3e43"; + auto guid = uuid::from_string(str).value(); + REQUIRE(to_string<wchar_t>(guid) == str); + } + + { + auto str = "4718382325744bfdb41199ed177d3e43"; + REQUIRE_NOTHROW(uuid::from_string(str)); + REQUIRE(uuid::from_string(str).has_value()); + } + + { + auto str = "00000000-0000-0000-0000-000000000000"; + auto guid = uuid::from_string(str).value(); + REQUIRE(guid.is_nil()); + } + + { + auto str = "{00000000-0000-0000-0000-000000000000}"; + auto guid = uuid::from_string(str).value(); + REQUIRE(guid.is_nil()); + } + + { + auto str = L"00000000-0000-0000-0000-000000000000"; + auto guid = uuid::from_string(str).value(); + REQUIRE(guid.is_nil()); + } + + { + auto str = L"{00000000-0000-0000-0000-000000000000}"; + auto guid = uuid::from_string(str).value(); + REQUIRE(guid.is_nil()); + } +} + +TEST_CASE("uuid: Test from_string(basic_string)", "[uuid]") +{ + using namespace std::string_literals; + + { + auto str = "47183823-2574-4bfd-b411-99ed177d3e43"s; + auto guid = uuid::from_string(str).value(); + REQUIRE(to_string(guid) == str); + } + + { + auto str = "{47183823-2574-4bfd-b411-99ed177d3e43}"s; + auto guid = uuid::from_string(str).value(); + REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"); + } + + { + auto guid = uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43"s).value(); + REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"); + REQUIRE(to_string<wchar_t>(guid) == L"47183823-2574-4bfd-b411-99ed177d3e43"); + } + + { + auto str = L"47183823-2574-4bfd-b411-99ed177d3e43"s; + auto guid = uuid::from_string(str).value(); + REQUIRE(to_string<wchar_t>(guid) == str); + } + + { + auto str = "4718382325744bfdb41199ed177d3e43"s; + REQUIRE_NOTHROW(uuid::from_string(str)); + REQUIRE(uuid::from_string(str).has_value()); + } + + { + auto str = "00000000-0000-0000-0000-000000000000"s; + auto guid = uuid::from_string(str).value(); + REQUIRE(guid.is_nil()); + } + + { + auto str = "{00000000-0000-0000-0000-000000000000}"s; + auto guid = uuid::from_string(str).value(); + REQUIRE(guid.is_nil()); + } + + { + auto str = L"00000000-0000-0000-0000-000000000000"s; + auto guid = uuid::from_string(str).value(); + REQUIRE(guid.is_nil()); + } + + { + auto str = L"{00000000-0000-0000-0000-000000000000}"s; + auto guid = uuid::from_string(str).value(); + REQUIRE(guid.is_nil()); + } +} + +TEST_CASE("uuid: Test from_string(basic_string_view)", "[uuid]") +{ + using namespace std::string_view_literals; + + { + auto str = "47183823-2574-4bfd-b411-99ed177d3e43"sv; + auto guid = uuid::from_string(str).value(); + REQUIRE(to_string(guid) == str); + } + + { + auto str = "{47183823-2574-4bfd-b411-99ed177d3e43}"sv; + auto guid = uuid::from_string(str).value(); + REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"); + } + + { + auto guid = uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43"sv).value(); + REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"); + REQUIRE(to_string<wchar_t>(guid) == L"47183823-2574-4bfd-b411-99ed177d3e43"); + } + + { + auto str = L"47183823-2574-4bfd-b411-99ed177d3e43"sv; + auto guid = uuid::from_string(str).value(); + REQUIRE(to_string<wchar_t>(guid) == str); + } + + { + auto str = "4718382325744bfdb41199ed177d3e43"sv; + REQUIRE_NOTHROW(uuid::from_string(str)); + REQUIRE(uuid::from_string(str).has_value()); + } + + { + auto str = "00000000-0000-0000-0000-000000000000"sv; + auto guid = uuid::from_string(str).value(); + REQUIRE(guid.is_nil()); + } + + { + auto str = "{00000000-0000-0000-0000-000000000000}"sv; + auto guid = uuid::from_string(str).value(); + REQUIRE(guid.is_nil()); + } + + { + auto str = L"00000000-0000-0000-0000-000000000000"sv; + auto guid = uuid::from_string(str).value(); + REQUIRE(guid.is_nil()); + } + + { + auto str = L"{00000000-0000-0000-0000-000000000000}"sv; + auto guid = uuid::from_string(str).value(); + REQUIRE(guid.is_nil()); + } +} + +TEST_CASE("uuid: Test constexpr from_string", "[uuid]") +{ + constexpr uuid value = uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43").value(); + static_assert(!value.is_nil()); + static_assert(value.variant() == uuid_variant::rfc); + static_assert(value.version() != uuid_version::none); +} + +TEST_CASE("uuid: Test from_string(char*) invalid format", "[uuid]") +{ + REQUIRE(!uuid::from_string("").has_value()); + REQUIRE(!uuid::from_string("{}").has_value()); + REQUIRE(!uuid::from_string( + "47183823-2574-4bfd-b411-99ed177d3e4").has_value()); + REQUIRE(!uuid::from_string( + "47183823-2574-4bfd-b411-99ed177d3e430").has_value()); + REQUIRE(!uuid::from_string( + "{47183823-2574-4bfd-b411-99ed177d3e43").has_value()); + REQUIRE(!uuid::from_string( + "47183823-2574-4bfd-b411-99ed177d3e43}").has_value()); +} + +TEST_CASE("uuid: Test from_string(basic_string) invalid format", "[uuid]") +{ + using namespace std::string_literals; + + { + auto str = ""s; + REQUIRE(!uuid::from_string(str).has_value()); + } + + { + auto str = "{}"s; + REQUIRE(!uuid::from_string(str).has_value()); + } + + { + auto str = "47183823-2574-4bfd-b411-99ed177d3e4"s; + REQUIRE(!uuid::from_string(str).has_value()); + } + + { + auto str = "47183823-2574-4bfd-b411-99ed177d3e430"s; + REQUIRE(!uuid::from_string(str).has_value()); + } + + { + auto str = "{47183823-2574-4bfd-b411-99ed177d3e43"s; + REQUIRE(!uuid::from_string(str).has_value()); + } + + { + auto str = "47183823-2574-4bfd-b411-99ed177d3e43}"s; + REQUIRE(!uuid::from_string(str).has_value()); + } +} + +TEST_CASE("uuid: Test from_string(basic_string_view) invalid format", "[uuid]") +{ + using namespace std::string_view_literals; + + REQUIRE(!uuid::from_string(""sv).has_value()); + REQUIRE(!uuid::from_string("{}"sv).has_value()); + REQUIRE(!uuid::from_string( + "47183823-2574-4bfd-b411-99ed177d3e4"sv).has_value()); + REQUIRE(!uuid::from_string( + "47183823-2574-4bfd-b411-99ed177d3e430"sv).has_value()); + REQUIRE(!uuid::from_string( + "{47183823-2574-4bfd-b411-99ed177d3e43"sv).has_value()); + REQUIRE(!uuid::from_string( + "47183823-2574-4bfd-b411-99ed177d3e43}"sv).has_value()); +} + +TEST_CASE("uuid: Test iterators constructor", "[uuid]") +{ + using namespace std::string_literals; + + { + std::array<uuid::value_type, 16> arr{{ + 0x47, 0x18, 0x38, 0x23, + 0x25, 0x74, + 0x4b, 0xfd, + 0xb4, 0x11, + 0x99, 0xed, 0x17, 0x7d, 0x3e, 0x43 + }}; + + uuid guid(std::begin(arr), std::end(arr)); + REQUIRE(to_string(guid) == + "47183823-2574-4bfd-b411-99ed177d3e43"s); + } + + { + uuid::value_type arr[16] = { // NOLINT + 0x47, 0x18, 0x38, 0x23, + 0x25, 0x74, + 0x4b, 0xfd, + 0xb4, 0x11, + 0x99, 0xed, 0x17, 0x7d, 0x3e, 0x43 + }; + + uuid guid(std::begin(arr), std::end(arr)); + REQUIRE(to_string(guid) == + "47183823-2574-4bfd-b411-99ed177d3e43"s); + } +} + +TEST_CASE("uuid: Test array constructors", "[uuid]") +{ + using namespace std::string_literals; + + { + uuid guid{{ + 0x47, 0x18, 0x38, 0x23, + 0x25, 0x74, + 0x4b, 0xfd, + 0xb4, 0x11, + 0x99, 0xed, 0x17, 0x7d, 0x3e, 0x43 + }}; + + REQUIRE(to_string(guid) == + "47183823-2574-4bfd-b411-99ed177d3e43"s); + } + + { + std::array<uuid::value_type, 16> arr{{ + 0x47, 0x18, 0x38, 0x23, + 0x25, 0x74, + 0x4b, 0xfd, + 0xb4, 0x11, + 0x99, 0xed, 0x17, 0x7d, 0x3e, 0x43 + }}; + + uuid guid(arr); + REQUIRE(to_string(guid) == + "47183823-2574-4bfd-b411-99ed177d3e43"s); + } + + { + uuid::value_type arr[16] { //NOLINT + 0x47, 0x18, 0x38, 0x23, + 0x25, 0x74, + 0x4b, 0xfd, + 0xb4, 0x11, + 0x99, 0xed, 0x17, 0x7d, 0x3e, 0x43 + }; + + uuid guid(arr); + REQUIRE(to_string(guid) == + "47183823-2574-4bfd-b411-99ed177d3e43"s); + } +} + +TEST_CASE("uuid: Test equality", "[uuid]") +{ + uuid empty; + + auto engine = uuid_random_generator::engine_type{}; + seed_rng(engine); + uuid guid = uuid_random_generator{engine}(); + + REQUIRE(empty == empty); + REQUIRE(guid == guid); + REQUIRE(empty != guid); +} + +TEST_CASE("Test comparison", "[uuid]") +{ + auto empty = uuid{}; + + auto engine = uuid_random_generator::engine_type{}; + seed_rng(engine); + + uuid_random_generator gen{engine}; + auto id = gen(); + + REQUIRE(empty < id); + + std::set<uuid> ids{ + uuid{}, + gen(), + gen(), + gen(), + gen() + }; + + REQUIRE(ids.size() == 5); + REQUIRE(ids.find(uuid{}) != ids.end()); +} + +TEST_CASE("uuid: Test hashing", "[uuid]") +{ + using namespace std::string_literals; + + auto str = "47183823-2574-4bfd-b411-99ed177d3e43"s; + auto guid = uuid::from_string(str).value(); + + auto h1 = std::hash<std::string>{}; + auto h2 = std::hash<uuid>{}; + REQUIRE(h1(str) != h2(guid)); + + auto engine = uuid_random_generator::engine_type{}; + seed_rng(engine); + uuid_random_generator gen{ engine }; + + std::unordered_set<uuid> ids{ + uuid{}, + gen(), + gen(), + gen(), + gen() + }; + + REQUIRE(ids.size() == 5); + REQUIRE(ids.find(uuid{}) != ids.end()); +} + +TEST_CASE("uuid: Test swap", "[uuid]") +{ + uuid empty; + + auto engine = uuid_random_generator::engine_type{}; + seed_rng(engine); + uuid guid = uuid_random_generator{engine}(); + + REQUIRE(empty.is_nil()); + REQUIRE(!guid.is_nil()); + + std::swap(empty, guid); + + REQUIRE(!empty.is_nil()); + REQUIRE(guid.is_nil()); + + empty.swap(guid); + + REQUIRE(empty.is_nil()); + REQUIRE(!guid.is_nil()); +} + +TEST_CASE("uuid: Test constexpr", "[uuid]") +{ + constexpr uuid empty; + static_assert(empty.is_nil()); + static_assert(empty.variant() == uuid_variant::ncs); + static_assert(empty.version() == uuid_version::none); +} + +TEST_CASE("uuid: Test size", "[uuid]") +{ + REQUIRE(sizeof(uuid) == 16); +} + +TEST_CASE("uuid: Test assignment", "[uuid]") +{ + auto id1 = uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43").value(); + auto id2 = id1; + REQUIRE(id1 == id2); + + id1 = uuid::from_string("{fea43102-064f-4444-adc2-02cec42623f8}").value(); + REQUIRE(id1 != id2); + + auto id3 = std::move(id2); + REQUIRE(to_string(id3) == "47183823-2574-4bfd-b411-99ed177d3e43"); +} + +TEST_CASE("uuid: Test trivial", "[uuid]") +{ + REQUIRE(std::is_trivially_copyable_v<uuid>); +} + +TEST_CASE("uuid: Test as_bytes", "[uuid]") +{ + std::array<uuid::value_type, 16> arr{{ + 0x47, 0x18, 0x38, 0x23, + 0x25, 0x74, + 0x4b, 0xfd, + 0xb4, 0x11, + 0x99, 0xed, 0x17, 0x7d, 0x3e, 0x43 + }}; + + { + uuid id{ arr }; + REQUIRE(!id.is_nil()); + + auto view = id.as_bytes(); + REQUIRE(memcmp(view.data(), arr.data(), arr.size()) == 0); + } + + { + const uuid id{ arr }; + REQUIRE(!id.is_nil()); + + auto view = id.as_bytes(); + REQUIRE(memcmp(view.data(), arr.data(), arr.size()) == 0); + } +} + +//NOLINTEND(bugprone-unchecked-optional-access) diff --git a/nihil.uuid/uuid.ccm b/nihil.uuid/uuid.ccm new file mode 100644 index 0000000..4aa424e --- /dev/null +++ b/nihil.uuid/uuid.ccm @@ -0,0 +1,842 @@ +/* + * From https://github.com/mariusbancila/stduuid + * + * Copyright (c) 2017 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +module; + +#include <algorithm> +#include <array> +#include <atomic> +#include <chrono> +#include <cstring> +#include <functional> +#include <iomanip> +#include <iterator> +#include <memory> +#include <numeric> +#include <optional> +#include <random> +#include <ranges> +#include <span> +#include <string> +#include <sstream> +#include <string_view> +#include <type_traits> + +export module nihil.uuid; + +namespace nihil { + +template <typename TChar> +[[nodiscard]] constexpr auto hex2char(TChar const ch) noexcept -> unsigned char +{ + if (ch >= static_cast<TChar>('0') && ch <= static_cast<TChar>('9')) + return static_cast<unsigned char>( + ch - static_cast<TChar>('0')); + + if (ch >= static_cast<TChar>('a') && ch <= static_cast<TChar>('f')) + return static_cast<unsigned char>( + 10 + ch - static_cast<TChar>('a')); + + if (ch >= static_cast<TChar>('A') && ch <= static_cast<TChar>('F')) + return static_cast<unsigned char>( + 10 + ch - static_cast<TChar>('A')); + + return 0; +} + +template <typename TChar> +[[nodiscard]] constexpr auto is_hex(TChar const ch) noexcept -> bool +{ + return (ch >= static_cast<TChar>('0') && + ch <= static_cast<TChar>('9')) || + (ch >= static_cast<TChar>('a') && + ch <= static_cast<TChar>('f')) || + (ch >= static_cast<TChar>('A') && + ch <= static_cast<TChar>('F')); +} + +template <typename TChar> +[[nodiscard]] constexpr auto to_string_view(TChar const *str) noexcept + -> std::basic_string_view<TChar> +{ + if (str) + return str; + return {}; +} + +template <typename StringType> +[[nodiscard]] constexpr auto to_string_view(StringType const &str) noexcept + -> std::basic_string_view< + typename StringType::value_type, + typename StringType::traits_type> +{ + return str; +} + +struct sha1 +{ + using digest32_t = std::array<std::uint32_t, 5>; + using digest8_t = std::array<std::uint8_t, 20>; + + static constexpr unsigned int block_bytes = 64; + + sha1() + { + reset(); + } + + [[nodiscard]] inline static auto + left_rotate(std::uint32_t value, std::size_t const count) noexcept + -> std::uint32_t + { + return (value << count) ^ (value >> (32 - count)); + } + + auto reset(this sha1 &self) noexcept -> void + { + self.m_digest[0] = 0x67452301; + self.m_digest[1] = 0xEFCDAB89; + self.m_digest[2] = 0x98BADCFE; + self.m_digest[3] = 0x10325476; + self.m_digest[4] = 0xC3D2E1F0; + self.m_blockByteIndex = 0; + self.m_byteCount = 0; + } + + auto process_byte(this sha1 &self, std::uint8_t octet) -> void + { + self.m_block[self.m_blockByteIndex++] = octet; + ++self.m_byteCount; + + if (self.m_blockByteIndex == block_bytes) { + self.m_blockByteIndex = 0; + self.process_block(); + } + } + + auto process_block(this sha1 &self, + void const *const start, + void const *const end) + -> void + { + auto *first = static_cast<uint8_t const *>(start); + auto *last = static_cast<uint8_t const *>(end); + + while (first != last) { + self.process_byte(*first); + first++; + } + } + + auto process_bytes(this sha1 &self, + void const *const data, + size_t const len) + -> void + { + auto *block = static_cast<uint8_t const *>(data); + self.process_block(block, block + len); + } + + auto get_digest(this sha1 &self) -> digest32_t + { + auto const bit_count = self.m_byteCount * 8; + + self.process_byte(0x80); + if (self.m_blockByteIndex > 56) { + while (self.m_blockByteIndex != 0) + self.process_byte(0); + + while (self.m_blockByteIndex < 56) + self.process_byte(0); + } else { + while (self.m_blockByteIndex < 56) + self.process_byte(0); + } + + self.process_byte(0); + self.process_byte(0); + self.process_byte(0); + self.process_byte(0); + self.process_byte(static_cast<unsigned char>( + (bit_count >> 24) & 0xFF)); + self.process_byte(static_cast<unsigned char>( + (bit_count >> 16) & 0xFF)); + self.process_byte(static_cast<unsigned char>( + (bit_count >> 8) & 0xFF)); + self.process_byte(static_cast<unsigned char>( + (bit_count) & 0xFF)); + + return self.m_digest; + } + + auto get_digest_bytes(this sha1 &self) -> digest8_t + { + auto d32 = self.get_digest(); + auto digest = digest8_t{}; + + auto di = std::size_t{0}; + + digest[di++] = static_cast<uint8_t>(d32[0] >> 24); + digest[di++] = static_cast<uint8_t>(d32[0] >> 16); + digest[di++] = static_cast<uint8_t>(d32[0] >> 8); + digest[di++] = static_cast<uint8_t>(d32[0] >> 0); + + digest[di++] = static_cast<uint8_t>(d32[1] >> 24); + digest[di++] = static_cast<uint8_t>(d32[1] >> 16); + digest[di++] = static_cast<uint8_t>(d32[1] >> 8); + digest[di++] = static_cast<uint8_t>(d32[1] >> 0); + + digest[di++] = static_cast<uint8_t>(d32[2] >> 24); + digest[di++] = static_cast<uint8_t>(d32[2] >> 16); + digest[di++] = static_cast<uint8_t>(d32[2] >> 8); + digest[di++] = static_cast<uint8_t>(d32[2] >> 0); + + digest[di++] = static_cast<uint8_t>(d32[3] >> 24); + digest[di++] = static_cast<uint8_t>(d32[3] >> 16); + digest[di++] = static_cast<uint8_t>(d32[3] >> 8); + digest[di++] = static_cast<uint8_t>(d32[3] >> 0); + + digest[di++] = static_cast<uint8_t>(d32[4] >> 24); + digest[di++] = static_cast<uint8_t>(d32[4] >> 16); + digest[di++] = static_cast<uint8_t>(d32[4] >> 8); + digest[di++] = static_cast<uint8_t>(d32[4] >> 0); + + return digest; + } + +private: + auto process_block(this sha1 &self) -> void + { + auto w = std::array<std::uint32_t, 80>(); + + for (std::size_t i = 0; i < 16; i++) { + w[i] = static_cast<std::uint32_t>( + self.m_block[i * 4 + 0]) << 24; + w[i] |= static_cast<std::uint32_t>( + self.m_block[i * 4 + 1]) << 16; + w[i] |= static_cast<std::uint32_t>( + self.m_block[i * 4 + 2]) << 8; + w[i] |= static_cast<std::uint32_t>( + self.m_block[i * 4 + 3]); + } + + for (std::size_t i = 16; i < 80; i++) { + w[i] = left_rotate((w[i - 3] ^ w[i - 8] ^ + w[i - 14] ^ w[i - 16]), 1); + } + + auto a = self.m_digest[0]; + auto b = self.m_digest[1]; + auto c = self.m_digest[2]; + auto d = self.m_digest[3]; + auto e = self.m_digest[4]; + + for (std::size_t i = 0; i < 80; ++i) { + auto f = std::uint32_t{0}; + auto k = std::uint32_t{0}; + + if (i < 20) { + f = (b & c) | (~b & d); + k = 0x5A827999; + } else if (i < 40) { + f = b ^ c ^ d; + k = 0x6ED9EBA1; + } else if (i < 60) { + f = (b & c) | (b & d) | (c & d); + k = 0x8F1BBCDC; + } else { + f = b ^ c ^ d; + k = 0xCA62C1D6; + } + + auto temp = std::uint32_t{left_rotate(a, 5) + + f + e + k + w[i]}; + e = d; + d = c; + c = left_rotate(b, 30); + b = a; + a = temp; + } + + self.m_digest[0] += a; + self.m_digest[1] += b; + self.m_digest[2] += c; + self.m_digest[3] += d; + self.m_digest[4] += e; + } + + digest32_t m_digest; + std::array<std::uint8_t, 64> m_block; + std::size_t m_blockByteIndex; + std::size_t m_byteCount; +}; + +template <typename CharT> +inline constexpr std::string_view empty_guid = + "00000000-0000-0000-0000-000000000000"; + +template <> +inline constexpr std::wstring_view empty_guid<wchar_t> + = L"00000000-0000-0000-0000-000000000000"; + +template <typename CharT> +inline constexpr std::string_view guid_encoder = "0123456789abcdef"; + +template <> +inline constexpr std::wstring_view guid_encoder<wchar_t> = L"0123456789abcdef"; + +// --------------------------------------------------------------------- +// UUID format https://tools.ietf.org/html/rfc4122 +// --------------------------------------------------------------------- + +// --------------------------------------------------------------------- +// Field NDR Data Type Octet # Note +// Note +// --------------------------------------------------------------------- +// time_low unsigned long 0 - 3 +// The low field of the timestamp. +// time_mid unsigned short 4 - 5 +// The middle field of the timestamp. +// time_hi_and_version unsigned short 6 - 7 +// The high field of the timestamp multiplexed with the version number. +// clock_seq_hi_and_reserved unsigned small 8 +// The high field of the clock sequence multiplexed with the variant. +// clock_seq_low unsigned small 9 +// The low field of the clock sequence. +// node character 10 - 15 +// The spatially unique node identifier. +// --------------------------------------------------------------------- +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | time_low | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | time_mid | time_hi_and_version | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |clk_seq_hi_res | clk_seq_low | node (0-1) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | node (2-5) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +// --------------------------------------------------------------------- +// enumerations +// --------------------------------------------------------------------- + +// indicated by a bit pattern in octet 8, marked with N in +// xxxxxxxx-xxxx-xxxx-Nxxx-xxxxxxxxxxxx +export enum struct uuid_variant { + // NCS backward compatibility (with the obsolete Apollo Network + // Computing System 1.5 UUID format). + // N bit pattern: 0xxx + // > the first 6 octets of the UUID are a 48-bit timestamp (the number + // of 4 microsecond units of time since 1 Jan 1980 UTC); + // > the next 2 octets are reserved; + // > the next octet is the "address family"; + // > the final 7 octets are a 56-bit host ID in the form specified by + // the address family + ncs, + + // RFC 4122/DCE 1.1 + // N bit pattern: 10xx + // > big-endian byte order + rfc, + + // Microsoft Corporation backward compatibility + // N bit pattern: 110x + // > little endian byte order + // > formely used in the Component Object Model (COM) library + microsoft, + + // reserved for possible future definition + // N bit pattern: 111x + reserved +}; + +// indicated by a bit pattern in octet 6, marked with M in +// xxxxxxxx-xxxx-Mxxx-xxxx-xxxxxxxxxxxx +export enum struct uuid_version { + // only possible for nil or invalid uuids + none = 0, + // The time-based version specified in RFC 4122 + time_based = 1, + // DCE Security version, with embedded POSIX UIDs. + dce_security = 2, + // The name-based version specified in RFS 4122 with MD5 hashing + name_based_md5 = 3, + // The randomly or pseudo-randomly generated version specified in RFS 4122 + random_number_based = 4, + // The name-based version specified in RFS 4122 with SHA1 hashing + name_based_sha1 = 5 +}; + +// Forward declare uuid and to_string so that we can declare to_string as a +// friend later. +export struct uuid; +export template <typename CharT = char, + typename Traits = std::char_traits<CharT>, + typename Allocator = std::allocator<CharT>> +auto to_string(uuid const &id) -> std::basic_string<CharT, Traits, Allocator>; + +// -------------------------------------------------------------------------------------------------------------------------- +// uuid class +// -------------------------------------------------------------------------------------------------------------------------- +export struct uuid { + using value_type = std::uint8_t; + + constexpr uuid() noexcept = default; + + uuid(value_type(&arr)[16]) noexcept // NOLINT + { + std::ranges::copy(arr, std::ranges::begin(data)); + } + + constexpr uuid(std::array<value_type, 16> const &arr) noexcept + : data{arr} + { + } + + explicit uuid(std::span<value_type, 16> bytes) + { + std::ranges::copy(bytes, std::ranges::begin(data)); + } + + explicit uuid(std::span<value_type> bytes) + { + if (bytes.size() != 16) + throw std::logic_error("wrong size for uuid"); + std::ranges::copy(bytes, std::ranges::begin(data)); + } + + template<typename ForwardIterator> + explicit uuid(ForwardIterator first, ForwardIterator last) + { + if (std::distance(first, last) != 16) + throw std::logic_error("wrong size for uuid"); + + std::copy(first, last, std::begin(data)); + } + + [[nodiscard]] constexpr auto variant() const noexcept -> uuid_variant + { + if ((data[8] & 0x80) == 0x00) + return uuid_variant::ncs; + else if ((data[8] & 0xC0) == 0x80) + return uuid_variant::rfc; + else if ((data[8] & 0xE0) == 0xC0) + return uuid_variant::microsoft; + else + return uuid_variant::reserved; + } + + [[nodiscard]] constexpr auto version() const noexcept -> uuid_version + { + if ((data[6] & 0xF0) == 0x10) + return uuid_version::time_based; + else if ((data[6] & 0xF0) == 0x20) + return uuid_version::dce_security; + else if ((data[6] & 0xF0) == 0x30) + return uuid_version::name_based_md5; + else if ((data[6] & 0xF0) == 0x40) + return uuid_version::random_number_based; + else if ((data[6] & 0xF0) == 0x50) + return uuid_version::name_based_sha1; + else + return uuid_version::none; + } + + [[nodiscard]] constexpr auto is_nil() const noexcept -> bool + { + for (auto i : data) + if (i != 0) + return false; + return true; + } + + auto swap(uuid &other) noexcept -> void + { + data.swap(other.data); + } + + [[nodiscard]] inline auto as_bytes() const + -> std::span<std::byte const, 16> + { + return std::span<std::byte const, 16>( + reinterpret_cast<std::byte const*>(data.data()), + 16); + } + + template <typename StringType> + [[nodiscard]] constexpr static auto + is_valid_uuid(StringType const &in_str) noexcept + -> bool + { + auto str = to_string_view(in_str); + auto firstDigit = true; + auto hasBraces = std::size_t{0}; + auto index = std::size_t{0}; + + if (str.empty()) + return false; + + if (str.front() == '{') + hasBraces = 1; + + if (hasBraces && str.back() != '}') + return false; + + for (size_t i = hasBraces; i < str.size() - hasBraces; ++i) { + if (str[i] == '-') + continue; + + if (index >= 16 || !is_hex(str[i])) + return false; + + if (firstDigit) { + firstDigit = false; + } else { + index++; + firstDigit = true; + } + } + + if (index < 16) + return false; + + return true; + } + + template <typename StringType> + [[nodiscard]] constexpr static auto + from_string(StringType const & in_str) noexcept + -> std::optional<uuid> + { + auto str = to_string_view(in_str); + bool firstDigit = true; + size_t hasBraces = 0; + size_t index = 0; + + std::array<uint8_t, 16> data{ { 0 } }; + + if (str.empty()) + return {}; + + if (str.front() == '{') + hasBraces = 1; + if (hasBraces && str.back() != '}') + return {}; + + for (size_t i = hasBraces; i < str.size() - hasBraces; ++i) { + if (str[i] == '-') + continue; + + if (index >= 16 || !is_hex(str[i])) { + return {}; + } + + if (firstDigit) { + data[index] = static_cast<uint8_t>(hex2char(str[i]) << 4); + firstDigit = false; + } else { + data[index] = static_cast<uint8_t>(data[index] | hex2char(str[i])); + index++; + firstDigit = true; + } + } + + if (index < 16) { + return {}; + } + + return uuid{data}; + } + +private: + std::array<value_type, 16> data{ { 0 } }; + + friend auto operator==(uuid const &, uuid const &) noexcept -> bool; + friend auto operator<(uuid const &, uuid const &) noexcept -> bool; + + template <class Elem, class Traits> + friend auto operator<<(std::basic_ostream<Elem, Traits> &s, + uuid const &id) + -> std::basic_ostream<Elem, Traits> &; + + template<class CharT, class Traits, class Allocator> + friend auto to_string(uuid const &id) + -> std::basic_string<CharT, Traits, Allocator>; + + friend std::hash<uuid>; +}; + +// -------------------------------------------------------------------------------------------------------------------------- +// operators and non-member functions +// -------------------------------------------------------------------------------------------------------------------------- + +export [[nodiscard]] +auto operator== (uuid const &lhs, uuid const &rhs) noexcept -> bool +{ + return lhs.data == rhs.data; +} + +export [[nodiscard]] +auto operator!= (uuid const &lhs, uuid const &rhs) noexcept -> bool +{ + return !(lhs == rhs); +} + +export [[nodiscard]] +auto operator< (uuid const &lhs, uuid const &rhs) noexcept -> bool +{ + return lhs.data < rhs.data; +} + +export template <typename CharT, typename Traits, typename Allocator> +[[nodiscard]] auto to_string(uuid const &id) + -> std::basic_string<CharT, Traits, Allocator> +{ + auto uustr = std::basic_string<CharT, Traits, Allocator>( + std::from_range, empty_guid<CharT>); + + for (std::size_t i = 0, index = 0; i < 36; ++i) { + if (i == 8 || i == 13 || i == 18 || i == 23) + continue; + + uustr[i] = guid_encoder<CharT>[id.data[index] >> 4 & 0x0f]; + uustr[++i] = guid_encoder<CharT>[id.data[index] & 0x0f]; + index++; + } + + return uustr; +} + +export template <class Elem, class Traits> +auto operator<<(std::basic_ostream<Elem, Traits> &s, uuid const &id) + -> std::basic_ostream<Elem, Traits> & +{ + s << to_string(id); + return s; +} + +export auto swap(uuid &lhs, uuid &rhs) noexcept -> void +{ + lhs.swap(rhs); +} + +/*********************************************************************** + * namespace IDs that could be used for generating name-based uuids + */ + +// Name string is a fully-qualified domain name +export uuid uuid_namespace_dns{{ + 0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, + 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8 +}}; + +// Name string is a URL +export uuid uuid_namespace_url{{ + 0x6b, 0xa7, 0xb8, 0x11, 0x9d, 0xad, 0x11, 0xd1, + 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8 +}}; + +// Name string is an ISO OID (See https://oidref.com/, +// https://en.wikipedia.org/wiki/Object_identifier) +export uuid uuid_namespace_oid{{ + 0x6b, 0xa7, 0xb8, 0x12, 0x9d, 0xad, 0x11, 0xd1, + 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8 +}}; + +// Name string is an X.500 DN, in DER or a text output format (See +// https://en.wikipedia.org/wiki/X.500, +// https://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One) +export uuid uuid_namespace_x500{{ + 0x6b, 0xa7, 0xb8, 0x14, 0x9d, 0xad, 0x11, 0xd1, + 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8 +}}; + +/*********************************************************************** + * uuid generators + */ + +export template <typename UniformRandomNumberGenerator> +struct basic_uuid_random_generator +{ + using engine_type = UniformRandomNumberGenerator; + + explicit basic_uuid_random_generator(engine_type &gen) + : generator(&gen, [](auto) {}) + { + } + + explicit basic_uuid_random_generator(engine_type *gen) + : generator(gen, [](auto) {}) + { + } + + [[nodiscard]] auto operator()() -> uuid + { + alignas(std::uint32_t) + auto bytes = std::array<std::uint8_t, 16>{}; + + for (int i = 0; i < 16; i += 4) + *reinterpret_cast<std::uint32_t *>(bytes.data() + i) = + distribution(*generator); + + // variant must be 10xxxxxx + bytes[8] &= 0xBF; + bytes[8] |= 0x80; + + // version must be 0100xxxx + bytes[6] &= 0x4F; + bytes[6] |= 0x40; + + return uuid{std::begin(bytes), std::end(bytes)}; + } + +private: + std::uniform_int_distribution<std::uint32_t> distribution; + std::shared_ptr<UniformRandomNumberGenerator> generator; +}; + +export using uuid_random_generator = basic_uuid_random_generator<std::mt19937>; + +export struct uuid_name_generator +{ + explicit uuid_name_generator(uuid const &namespace_uuid) noexcept + : nsuuid(namespace_uuid) + { + } + + template <typename StringType> + [[nodiscard]] auto operator()(StringType const &name) -> uuid + { + reset(); + process_characters(to_string_view(name)); + return make_uuid(); + } + +private: + auto reset() -> void + { + hasher.reset(); + + auto nsbytes = nsuuid.as_bytes(); + + auto bytes = std::array<std::byte, 16>(); + std::ranges::copy(nsbytes, std::ranges::begin(bytes)); + + hasher.process_bytes(bytes.data(), bytes.size()); + } + + template <typename CharT, typename Traits> + auto process_characters( + std::basic_string_view<CharT, Traits> const str) -> void + { + for (std::uint32_t c : str) { + hasher.process_byte(static_cast<std::uint8_t>(c & 0xFF)); + if constexpr (!std::is_same_v<CharT, char>) { + hasher.process_byte( + static_cast<uint8_t>((c >> 8) & 0xFF)); + hasher.process_byte( + static_cast<uint8_t>((c >> 16) & 0xFF)); + hasher.process_byte( + static_cast<uint8_t>((c >> 24) & 0xFF)); + } + } + } + + [[nodiscard]] auto make_uuid() -> uuid + { + auto digest = hasher.get_digest_bytes(); + + // variant must be 0b10xxxxxx + digest[8] &= 0xBF; + digest[8] |= 0x80; + + // version must be 0b0101xxxx + digest[6] &= 0x5F; + digest[6] |= 0x50; + + return uuid(std::span(digest).subspan(0, 16)); + } + +private: + uuid nsuuid; + sha1 hasher; +}; + +/* + * Create a random UUID. + */ +export auto random_uuid() -> uuid +{ + auto rd = std::random_device(); + auto seed_data = std::array<int, std::mt19937::state_size> {}; + std::ranges::generate(seed_data, std::ref(rd)); + + auto seq = std::seed_seq(std::ranges::begin(seed_data), + std::ranges::end(seed_data)); + auto generator = std::mt19937(seq); + auto gen = uuid_random_generator{generator}; + + return gen(); +} + +} // namespace nihil + +namespace std { + +export template <> +struct hash<nihil::uuid> +{ + using argument_type = nihil::uuid; + using result_type = std::size_t; + + [[nodiscard]] auto operator()(argument_type const &uuid) const + -> result_type + { + std::uint64_t l = + static_cast<uint64_t>(uuid.data[0]) << 56 | + static_cast<uint64_t>(uuid.data[1]) << 48 | + static_cast<uint64_t>(uuid.data[2]) << 40 | + static_cast<uint64_t>(uuid.data[3]) << 32 | + static_cast<uint64_t>(uuid.data[4]) << 24 | + static_cast<uint64_t>(uuid.data[5]) << 16 | + static_cast<uint64_t>(uuid.data[6]) << 8 | + static_cast<uint64_t>(uuid.data[7]); + + std::uint64_t h = + static_cast<uint64_t>(uuid.data[8]) << 56 | + static_cast<uint64_t>(uuid.data[9]) << 48 | + static_cast<uint64_t>(uuid.data[10]) << 40 | + static_cast<uint64_t>(uuid.data[11]) << 32 | + static_cast<uint64_t>(uuid.data[12]) << 24 | + static_cast<uint64_t>(uuid.data[13]) << 16 | + static_cast<uint64_t>(uuid.data[14]) << 8 | + static_cast<uint64_t>(uuid.data[15]); + + return std::hash<std::uint64_t>{}(l ^ h); + } +}; + +} // namespace std |
