/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include export module nihil.uuid; namespace nihil { template [[nodiscard]] constexpr auto hex2char(TChar const ch) noexcept -> unsigned char { if (ch >= static_cast('0') && ch <= static_cast('9')) return static_cast( ch - static_cast('0')); if (ch >= static_cast('a') && ch <= static_cast('f')) return static_cast( 10 + ch - static_cast('a')); if (ch >= static_cast('A') && ch <= static_cast('F')) return static_cast( 10 + ch - static_cast('A')); return 0; } template [[nodiscard]] constexpr auto is_hex(TChar const ch) noexcept -> bool { return (ch >= static_cast('0') && ch <= static_cast('9')) || (ch >= static_cast('a') && ch <= static_cast('f')) || (ch >= static_cast('A') && ch <= static_cast('F')); } template [[nodiscard]] constexpr auto to_string_view(TChar const *str) noexcept -> std::basic_string_view { if (str) return str; return {}; } template [[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; using digest8_t = std::array; 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(start); auto *last = static_cast(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(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( (bit_count >> 24) & 0xFF)); self.process_byte(static_cast( (bit_count >> 16) & 0xFF)); self.process_byte(static_cast( (bit_count >> 8) & 0xFF)); self.process_byte(static_cast( (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(d32[0] >> 24); digest[di++] = static_cast(d32[0] >> 16); digest[di++] = static_cast(d32[0] >> 8); digest[di++] = static_cast(d32[0] >> 0); digest[di++] = static_cast(d32[1] >> 24); digest[di++] = static_cast(d32[1] >> 16); digest[di++] = static_cast(d32[1] >> 8); digest[di++] = static_cast(d32[1] >> 0); digest[di++] = static_cast(d32[2] >> 24); digest[di++] = static_cast(d32[2] >> 16); digest[di++] = static_cast(d32[2] >> 8); digest[di++] = static_cast(d32[2] >> 0); digest[di++] = static_cast(d32[3] >> 24); digest[di++] = static_cast(d32[3] >> 16); digest[di++] = static_cast(d32[3] >> 8); digest[di++] = static_cast(d32[3] >> 0); digest[di++] = static_cast(d32[4] >> 24); digest[di++] = static_cast(d32[4] >> 16); digest[di++] = static_cast(d32[4] >> 8); digest[di++] = static_cast(d32[4] >> 0); return digest; } private: auto process_block(this sha1 &self) -> void { auto w = std::array(); for (std::size_t i = 0; i < 16; i++) { w[i] = static_cast( self.m_block[i * 4 + 0]) << 24; w[i] |= static_cast( self.m_block[i * 4 + 1]) << 16; w[i] |= static_cast( self.m_block[i * 4 + 2]) << 8; w[i] |= static_cast( 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 m_block; std::size_t m_blockByteIndex; std::size_t m_byteCount; }; template inline constexpr std::string_view empty_guid = "00000000-0000-0000-0000-000000000000"; template <> inline constexpr std::wstring_view empty_guid = L"00000000-0000-0000-0000-000000000000"; template inline constexpr std::string_view guid_encoder = "0123456789abcdef"; template <> inline constexpr std::wstring_view guid_encoder = 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 Allocator = std::allocator> auto to_string(uuid const &id) -> std::basic_string; // -------------------------------------------------------------------------------------------------------------------------- // 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 const &arr) noexcept : data{arr} { } explicit uuid(std::span bytes) { std::ranges::copy(bytes, std::ranges::begin(data)); } explicit uuid(std::span bytes) { if (bytes.size() != 16) throw std::logic_error("wrong size for uuid"); std::ranges::copy(bytes, std::ranges::begin(data)); } template 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 { return std::span( reinterpret_cast(data.data()), 16); } template [[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 [[nodiscard]] constexpr static auto from_string(StringType const & in_str) noexcept -> std::optional { auto str = to_string_view(in_str); bool firstDigit = true; size_t hasBraces = 0; size_t index = 0; std::array 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(hex2char(str[i]) << 4); firstDigit = false; } else { data[index] = static_cast(data[index] | hex2char(str[i])); index++; firstDigit = true; } } if (index < 16) { return {}; } return uuid{data}; } private: std::array data{ { 0 } }; friend auto operator==(uuid const &, uuid const &) noexcept -> bool; friend auto operator<(uuid const &, uuid const &) noexcept -> bool; template friend auto operator<<(std::basic_ostream &s, uuid const &id) -> std::basic_ostream &; template friend auto to_string(uuid const &id) -> std::basic_string; friend std::hash; }; // -------------------------------------------------------------------------------------------------------------------------- // 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 [[nodiscard]] auto to_string(uuid const &id) -> std::basic_string { auto uustr = std::basic_string( std::from_range, empty_guid); 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[id.data[index] >> 4 & 0x0f]; uustr[++i] = guid_encoder[id.data[index] & 0x0f]; index++; } return uustr; } export template auto operator<<(std::basic_ostream &s, uuid const &id) -> std::basic_ostream & { 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 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{}; for (int i = 0; i < 16; i += 4) *reinterpret_cast(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 distribution; std::shared_ptr generator; }; export using uuid_random_generator = basic_uuid_random_generator; export struct uuid_name_generator { explicit uuid_name_generator(uuid const &namespace_uuid) noexcept : nsuuid(namespace_uuid) { } template [[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::ranges::copy(nsbytes, std::ranges::begin(bytes)); hasher.process_bytes(bytes.data(), bytes.size()); } template auto process_characters( std::basic_string_view const str) -> void { for (std::uint32_t c : str) { hasher.process_byte(static_cast(c & 0xFF)); if constexpr (!std::is_same_v) { hasher.process_byte( static_cast((c >> 8) & 0xFF)); hasher.process_byte( static_cast((c >> 16) & 0xFF)); hasher.process_byte( static_cast((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 {}; 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 { 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(uuid.data[0]) << 56 | static_cast(uuid.data[1]) << 48 | static_cast(uuid.data[2]) << 40 | static_cast(uuid.data[3]) << 32 | static_cast(uuid.data[4]) << 24 | static_cast(uuid.data[5]) << 16 | static_cast(uuid.data[6]) << 8 | static_cast(uuid.data[7]); std::uint64_t h = static_cast(uuid.data[8]) << 56 | static_cast(uuid.data[9]) << 48 | static_cast(uuid.data[10]) << 40 | static_cast(uuid.data[11]) << 32 | static_cast(uuid.data[12]) << 24 | static_cast(uuid.data[13]) << 16 | static_cast(uuid.data[14]) << 8 | static_cast(uuid.data[15]); return std::hash{}(l ^ h); } }; } // namespace std