#ifndef STDUUID_H #define STDUUID_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __cplusplus #if (__cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) #define LIBUUID_CPP20_OR_GREATER #endif #endif #ifdef LIBUUID_CPP20_OR_GREATER #include #else #include #endif #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #ifndef NOMINMAX #define NOMINMAX #endif #ifdef UUID_SYSTEM_GENERATOR #include #endif #ifdef UUID_TIME_GENERATOR #include #pragma comment(lib, "IPHLPAPI.lib") #endif #elif defined(__linux__) || defined(__unix__) #ifdef UUID_SYSTEM_GENERATOR #include #endif #elif defined(__APPLE__) #ifdef UUID_SYSTEM_GENERATOR #include #endif #endif namespace uuids { #ifdef __cpp_lib_span template using span = std::span; #else template using span = gsl::span; #endif namespace detail { template [[nodiscard]] constexpr inline unsigned char hex2char(TChar const ch) noexcept { 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 inline bool is_hex(TChar const ch) noexcept { 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 std::basic_string_view to_string_view(TChar const *str) noexcept { if (str) return str; return {}; } template [[nodiscard]] constexpr std::basic_string_view to_string_view(StringType const &str) noexcept { return str; } class sha1 { public: using digest32_t = uint32_t[5]; using digest8_t = uint8_t[20]; static constexpr unsigned int block_bytes = 64; [[nodiscard]] inline static uint32_t left_rotate(uint32_t value, size_t const count) noexcept { return (value << count) ^ (value >> (32 - count)); } sha1() { reset(); } void reset() noexcept { m_digest[0] = 0x67452301; m_digest[1] = 0xEFCDAB89; m_digest[2] = 0x98BADCFE; m_digest[3] = 0x10325476; m_digest[4] = 0xC3D2E1F0; m_blockByteIndex = 0; m_byteCount = 0; } void process_byte(uint8_t octet) { this->m_block[this->m_blockByteIndex++] = octet; ++this->m_byteCount; if (m_blockByteIndex == block_bytes) { this->m_blockByteIndex = 0; process_block(); } } void process_block(void const *const start, void const *const end) { const uint8_t *begin = static_cast(start); const uint8_t *finish = static_cast(end); while (begin != finish) { process_byte(*begin); begin++; } } void process_bytes(void const *const data, size_t const len) { const uint8_t *block = static_cast(data); process_block(block, block + len); } uint32_t const *get_digest(digest32_t digest) { size_t const bitCount = this->m_byteCount * 8; process_byte(0x80); if (this->m_blockByteIndex > 56) { while (m_blockByteIndex != 0) { process_byte(0); } while (m_blockByteIndex < 56) { process_byte(0); } } else { while (m_blockByteIndex < 56) { process_byte(0); } } process_byte(0); process_byte(0); process_byte(0); process_byte(0); process_byte(static_cast((bitCount >> 24) & 0xFF)); process_byte(static_cast((bitCount >> 16) & 0xFF)); process_byte(static_cast((bitCount >> 8) & 0xFF)); process_byte(static_cast((bitCount)&0xFF)); memcpy(digest, m_digest, 5 * sizeof(uint32_t)); return digest; } uint8_t const *get_digest_bytes(digest8_t digest) { digest32_t d32; get_digest(d32); size_t di = 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: void process_block() { uint32_t w[80]; for (size_t i = 0; i < 16; i++) { w[i] = static_cast(m_block[i * 4 + 0] << 24); w[i] |= static_cast(m_block[i * 4 + 1] << 16); w[i] |= static_cast(m_block[i * 4 + 2] << 8); w[i] |= static_cast(m_block[i * 4 + 3]); } for (size_t i = 16; i < 80; i++) { w[i] = left_rotate((w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]), 1); } uint32_t a = m_digest[0]; uint32_t b = m_digest[1]; uint32_t c = m_digest[2]; uint32_t d = m_digest[3]; uint32_t e = m_digest[4]; for (std::size_t i = 0; i < 80; ++i) { uint32_t f = 0; uint32_t k = 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; } uint32_t temp = left_rotate(a, 5) + f + e + k + w[i]; e = d; d = c; c = left_rotate(b, 30); b = a; a = temp; } m_digest[0] += a; m_digest[1] += b; m_digest[2] += c; m_digest[3] += d; m_digest[4] += e; } private: digest32_t m_digest; uint8_t m_block[64]; size_t m_blockByteIndex; size_t m_byteCount; }; template inline constexpr CharT empty_guid[37] = "00000000-0000-0000-0000-000000000000"; template <> inline constexpr wchar_t empty_guid[37] = L"00000000-0000-0000-0000-000000000000"; template inline constexpr CharT guid_encoder[17] = "0123456789abcdef"; template <> inline constexpr wchar_t guid_encoder[17] = L"0123456789abcdef"; } // namespace detail // -------------------------------------------------------------------------------------------------------------------------- // UUID format https://tools.ietf.org/html/rfc4122 // -------------------------------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------------------------------- // Field NDR Data Type Octet # 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 enum class 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 enum class uuid_version { none = 0, // only possible for nil or invalid uuids time_based = 1, // The time-based version specified in RFC 4122 dce_security = 2, // DCE Security version, with embedded POSIX UIDs. name_based_md5 = 3, // The name-based version specified in RFS 4122 with MD5 hashing random_number_based = 4, // The randomly or pseudo-randomly generated version // specified in RFS 4122 name_based_sha1 = 5 // The name-based version specified in RFS 4122 with SHA1 hashing }; // Forward declare uuid & to_string so that we can declare to_string as a friend // later. class uuid; template , class Allocator = std::allocator> std::basic_string to_string(uuid const &id); // -------------------------------------------------------------------------------------------------------------------------- // uuid class // -------------------------------------------------------------------------------------------------------------------------- class uuid { public: using value_type = uint8_t; constexpr uuid() noexcept = default; uuid(value_type (&arr)[16]) noexcept { std::copy(std::cbegin(arr), std::cend(arr), std::begin(data)); } constexpr uuid(std::array const &arr) noexcept : data{arr} {} explicit uuid(span bytes) { std::copy(std::cbegin(bytes), std::cend(bytes), std::begin(data)); } template explicit uuid(ForwardIterator first, ForwardIterator last) { if (std::distance(first, last) == 16) std::copy(first, last, std::begin(data)); } [[nodiscard]] constexpr uuid_variant variant() const noexcept { 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 uuid_version version() const noexcept { 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 bool is_nil() const noexcept { for (size_t i = 0; i < data.size(); ++i) if (data[i] != 0) return false; return true; } void swap(uuid &other) noexcept { data.swap(other.data); } [[nodiscard]] inline span as_bytes() const { return span( reinterpret_cast(data.data()), 16); } template [[nodiscard]] constexpr static bool is_valid_uuid(StringType const &in_str) noexcept { auto str = detail::to_string_view(in_str); bool firstDigit = true; size_t hasBraces = 0; size_t index = 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 || !detail::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 std::optional from_string(StringType const &in_str) noexcept { auto str = detail::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 || !detail::is_hex(str[i])) { return {}; } if (firstDigit) { data[index] = static_cast(detail::hex2char(str[i]) << 4); firstDigit = false; } else { data[index] = static_cast(data[index] | detail::hex2char(str[i])); index++; firstDigit = true; } } if (index < 16) { return {}; } return uuid{data}; } private: std::array data{{0}}; friend bool operator==(uuid const &lhs, uuid const &rhs) noexcept; friend bool operator<(uuid const &lhs, uuid const &rhs) noexcept; template friend std::basic_ostream & operator<<(std::basic_ostream &s, uuid const &id); template friend std::basic_string to_string(uuid const &id); friend std::hash; }; // -------------------------------------------------------------------------------------------------------------------------- // operators and non-member functions // -------------------------------------------------------------------------------------------------------------------------- [[nodiscard]] inline bool operator==(uuid const &lhs, uuid const &rhs) noexcept { return lhs.data == rhs.data; } [[nodiscard]] inline bool operator!=(uuid const &lhs, uuid const &rhs) noexcept { return !(lhs == rhs); } [[nodiscard]] inline bool operator<(uuid const &lhs, uuid const &rhs) noexcept { return lhs.data < rhs.data; } template [[nodiscard]] inline std::basic_string to_string(uuid const &id) { std::basic_string uustr{detail::empty_guid}; for (size_t i = 0, index = 0; i < 36; ++i) { if (i == 8 || i == 13 || i == 18 || i == 23) { continue; } uustr[i] = detail::guid_encoder[id.data[index] >> 4 & 0x0f]; uustr[++i] = detail::guid_encoder[id.data[index] & 0x0f]; index++; } return uustr; } template std::basic_ostream & operator<<(std::basic_ostream &s, uuid const &id) { s << to_string(id); return s; } inline void swap(uuids::uuid &lhs, uuids::uuid &rhs) noexcept { lhs.swap(rhs); } // -------------------------------------------------------------------------------------------------------------------------- // namespace IDs that could be used for generating name-based uuids // -------------------------------------------------------------------------------------------------------------------------- // Name string is a fully-qualified domain name static 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 static 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) static 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) static uuid uuid_namespace_x500{{0x6b, 0xa7, 0xb8, 0x14, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8}}; // -------------------------------------------------------------------------------------------------------------------------- // uuid generators // -------------------------------------------------------------------------------------------------------------------------- #ifdef UUID_SYSTEM_GENERATOR class uuid_system_generator { public: using result_type = uuid; uuid operator()() { #ifdef _WIN32 GUID newId; HRESULT hr = ::CoCreateGuid(&newId); if (FAILED(hr)) { throw std::system_error(hr, std::system_category(), "CoCreateGuid failed"); } std::array bytes = { {static_cast((newId.Data1 >> 24) & 0xFF), static_cast((newId.Data1 >> 16) & 0xFF), static_cast((newId.Data1 >> 8) & 0xFF), static_cast((newId.Data1) & 0xFF), (unsigned char)((newId.Data2 >> 8) & 0xFF), (unsigned char)((newId.Data2) & 0xFF), (unsigned char)((newId.Data3 >> 8) & 0xFF), (unsigned char)((newId.Data3) & 0xFF), newId.Data4[0], newId.Data4[1], newId.Data4[2], newId.Data4[3], newId.Data4[4], newId.Data4[5], newId.Data4[6], newId.Data4[7]}}; return uuid{std::begin(bytes), std::end(bytes)}; #elif defined(__linux__) || defined(__unix__) uuid_t id; uuid_generate(id); std::array bytes = {{id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], id[8], id[9], id[10], id[11], id[12], id[13], id[14], id[15]}}; return uuid{std::begin(bytes), std::end(bytes)}; #elif defined(__APPLE__) auto newId = CFUUIDCreate(NULL); auto bytes = CFUUIDGetUUIDBytes(newId); CFRelease(newId); std::array arrbytes = { {bytes.byte0, bytes.byte1, bytes.byte2, bytes.byte3, bytes.byte4, bytes.byte5, bytes.byte6, bytes.byte7, bytes.byte8, bytes.byte9, bytes.byte10, bytes.byte11, bytes.byte12, bytes.byte13, bytes.byte14, bytes.byte15}}; return uuid{std::begin(arrbytes), std::end(arrbytes)}; #else return uuid{}; #endif } }; #endif template class basic_uuid_random_generator { public: 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]] uuid operator()() { alignas(uint32_t) uint8_t bytes[16]; for (int i = 0; i < 16; i += 4) *reinterpret_cast(bytes + 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; }; using uuid_random_generator = basic_uuid_random_generator; class uuid_name_generator { public: explicit uuid_name_generator(uuid const &namespace_uuid) noexcept : nsuuid(namespace_uuid) {} template [[nodiscard]] uuid operator()(StringType const &name) { reset(); process_characters(detail::to_string_view(name)); return make_uuid(); } private: void reset() { hasher.reset(); std::byte bytes[16]; auto nsbytes = nsuuid.as_bytes(); std::copy(std::cbegin(nsbytes), std::cend(nsbytes), bytes); hasher.process_bytes(bytes, 16); } template void process_characters(std::basic_string_view const str) { for (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]] uuid make_uuid() { detail::sha1::digest8_t digest; hasher.get_digest_bytes(digest); // variant must be 0b10xxxxxx digest[8] &= 0xBF; digest[8] |= 0x80; // version must be 0b0101xxxx digest[6] &= 0x5F; digest[6] |= 0x50; return uuid{digest, digest + 16}; } private: uuid nsuuid; detail::sha1 hasher; }; #ifdef UUID_TIME_GENERATOR // !!! DO NOT USE THIS IN PRODUCTION // this implementation is unreliable for good uuids class uuid_time_generator { using mac_address = std::array; std::optional device_address; [[nodiscard]] bool get_mac_address() { if (device_address.has_value()) { return true; } #ifdef _WIN32 DWORD len = 0; auto ret = GetAdaptersInfo(nullptr, &len); if (ret != ERROR_BUFFER_OVERFLOW) return false; std::vector buf(len); auto pips = reinterpret_cast(&buf.front()); ret = GetAdaptersInfo(pips, &len); if (ret != ERROR_SUCCESS) return false; mac_address addr; std::copy(pips->Address, pips->Address + 6, std::begin(addr)); device_address = addr; #endif return device_address.has_value(); } [[nodiscard]] long long get_time_intervals() { auto start = std::chrono::system_clock::from_time_t(time_t(-12219292800)); auto diff = std::chrono::system_clock::now() - start; auto ns = std::chrono::duration_cast(diff).count(); return ns / 100; } [[nodiscard]] static unsigned short get_clock_sequence() { static std::mt19937 clock_gen(std::random_device{}()); static std::uniform_int_distribution clock_dis; static std::atomic_ushort clock_sequence = clock_dis(clock_gen); return clock_sequence++; } public: [[nodiscard]] uuid operator()() { if (get_mac_address()) { std::array data; auto tm = get_time_intervals(); auto clock_seq = get_clock_sequence(); auto ptm = reinterpret_cast(&tm); memcpy(&data[0], ptm + 4, 4); memcpy(&data[4], ptm + 2, 2); memcpy(&data[6], ptm, 2); memcpy(&data[8], &clock_seq, 2); // variant must be 0b10xxxxxx data[8] &= 0xBF; data[8] |= 0x80; // version must be 0b0001xxxx data[6] &= 0x1F; data[6] |= 0x10; memcpy(&data[10], &device_address.value()[0], 6); return uuids::uuid{std::cbegin(data), std::cend(data)}; } return {}; } }; #endif } // namespace uuids namespace std { template <> struct hash { using argument_type = uuids::uuid; using result_type = std::size_t; [[nodiscard]] result_type operator()(argument_type const &uuid) const { #ifdef UUID_HASH_STRING_BASED std::hash hasher; return static_cast(hasher(uuids::to_string(uuid))); #else 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]); 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]); if constexpr (sizeof(result_type) > 4) { return result_type(l ^ h); } else { uint64_t hash64 = l ^ h; return result_type(uint32_t(hash64 >> 32) ^ uint32_t(hash64)); } #endif } }; } // namespace std #endif /* STDUUID_H */