1 /* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */
2 
3 #ifndef MPT_UUID_UUID_HPP
4 #define MPT_UUID_UUID_HPP
5 
6 
7 
8 #include "mpt/base/constexpr_throw.hpp"
9 #include "mpt/base/macros.hpp"
10 #include "mpt/base/integer.hpp"
11 #include "mpt/base/memory.hpp"
12 #include "mpt/base/namespace.hpp"
13 #include "mpt/endian/integer.hpp"
14 #include "mpt/format/default_formatter.hpp"
15 #include "mpt/format/simple.hpp"
16 #include "mpt/parse/parse.hpp"
17 #include "mpt/random/random.hpp"
18 #include "mpt/string/types.hpp"
19 #include "mpt/string/utility.hpp"
20 
21 #if MPT_OS_WINDOWS
22 #if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0600)
23 #include <guiddef.h>
24 #endif // _WIN32_WINNT
25 #include <objbase.h>
26 #include <rpc.h>
27 #endif // MPT_OS_WINDOWS
28 
29 
30 
31 namespace mpt {
32 inline namespace MPT_INLINE_NS {
33 
34 
35 // Microsoft on-disk layout
36 struct GUIDms {
37 	uint32le Data1;
38 	uint16le Data2;
39 	uint16le Data3;
40 	uint64be Data4; // yes, big endian here
41 };
declare_binary_safe(const GUIDms &)42 constexpr bool declare_binary_safe(const GUIDms &) {
43 	return true;
44 }
45 static_assert(mpt::check_binary_size<GUIDms>(16));
46 
47 // RFC binary format
48 struct UUIDbin {
49 	uint32be Data1;
50 	uint16be Data2;
51 	uint16be Data3;
52 	uint64be Data4;
53 };
declare_binary_safe(const UUIDbin &)54 constexpr bool declare_binary_safe(const UUIDbin &) {
55 	return true;
56 }
57 static_assert(mpt::check_binary_size<UUIDbin>(16));
58 
59 
60 
61 struct UUID {
62 private:
63 	uint32 Data1;
64 	uint16 Data2;
65 	uint16 Data3;
66 	uint64 Data4;
67 
68 public:
GetData1mpt::MPT_INLINE_NS::UUID69 	MPT_CONSTEXPRINLINE uint32 GetData1() const noexcept {
70 		return Data1;
71 	}
GetData2mpt::MPT_INLINE_NS::UUID72 	MPT_CONSTEXPRINLINE uint16 GetData2() const noexcept {
73 		return Data2;
74 	}
GetData3mpt::MPT_INLINE_NS::UUID75 	MPT_CONSTEXPRINLINE uint16 GetData3() const noexcept {
76 		return Data3;
77 	}
GetData4mpt::MPT_INLINE_NS::UUID78 	MPT_CONSTEXPRINLINE uint64 GetData4() const noexcept {
79 		return Data4;
80 	}
81 
82 public:
GetData64_1mpt::MPT_INLINE_NS::UUID83 	MPT_CONSTEXPRINLINE uint64 GetData64_1() const noexcept {
84 		return (static_cast<uint64>(Data1) << 32) | (static_cast<uint64>(Data2) << 16) | (static_cast<uint64>(Data3) << 0);
85 	}
GetData64_2mpt::MPT_INLINE_NS::UUID86 	MPT_CONSTEXPRINLINE uint64 GetData64_2() const noexcept {
87 		return Data4;
88 	}
89 
90 public:
91 	// xxxxxxxx-xxxx-Mmxx-Nnxx-xxxxxxxxxxxx
92 	// <--32-->-<16>-<16>-<-------64------>
IsNilmpt::MPT_INLINE_NS::UUID93 	MPT_CONSTEXPRINLINE bool IsNil() const noexcept {
94 		return (Data1 == 0) && (Data2 == 0) && (Data3 == 0) && (Data4 == 0);
95 	}
IsValidmpt::MPT_INLINE_NS::UUID96 	MPT_CONSTEXPRINLINE bool IsValid() const noexcept {
97 		return (Data1 != 0) || (Data2 != 0) || (Data3 != 0) || (Data4 != 0);
98 	}
Variantmpt::MPT_INLINE_NS::UUID99 	MPT_CONSTEXPRINLINE uint8 Variant() const noexcept {
100 		return Nn() >> 4u;
101 	}
Versionmpt::MPT_INLINE_NS::UUID102 	MPT_CONSTEXPRINLINE uint8 Version() const noexcept {
103 		return Mm() >> 4u;
104 	}
IsRFC4122mpt::MPT_INLINE_NS::UUID105 	MPT_CONSTEXPRINLINE bool IsRFC4122() const noexcept {
106 		return (Variant() & 0xcu) == 0x8u;
107 	}
108 
109 private:
Mmmpt::MPT_INLINE_NS::UUID110 	MPT_CONSTEXPRINLINE uint8 Mm() const noexcept {
111 		return static_cast<uint8>((Data3 >> 8) & 0xffu);
112 	}
Nnmpt::MPT_INLINE_NS::UUID113 	MPT_CONSTEXPRINLINE uint8 Nn() const noexcept {
114 		return static_cast<uint8>((Data4 >> 56) & 0xffu);
115 	}
MakeRFC4122mpt::MPT_INLINE_NS::UUID116 	void MakeRFC4122(uint8 version) noexcept {
117 		// variant
118 		uint8 Nn = static_cast<uint8>((Data4 >> 56) & 0xffu);
119 		Data4 &= 0x00ffffffffffffffull;
120 		Nn &= ~(0xc0u);
121 		Nn |= 0x80u;
122 		Data4 |= static_cast<uint64>(Nn) << 56;
123 		// version
124 		version &= 0x0fu;
125 		uint8 Mm = static_cast<uint8>((Data3 >> 8) & 0xffu);
126 		Data3 &= 0x00ffu;
127 		Mm &= ~(0xf0u);
128 		Mm |= (version << 4u);
129 		Data3 |= static_cast<uint16>(Mm) << 8;
130 	}
131 #if MPT_OS_WINDOWS
132 private:
UUIDFromWin32mpt::MPT_INLINE_NS::UUID133 	static mpt::UUID UUIDFromWin32(::UUID uuid) {
134 		return mpt::UUID(uuid.Data1, uuid.Data2, uuid.Data3, (static_cast<uint64>(0) | (static_cast<uint64>(uuid.Data4[0]) << 56) | (static_cast<uint64>(uuid.Data4[1]) << 48) | (static_cast<uint64>(uuid.Data4[2]) << 40) | (static_cast<uint64>(uuid.Data4[3]) << 32) | (static_cast<uint64>(uuid.Data4[4]) << 24) | (static_cast<uint64>(uuid.Data4[5]) << 16) | (static_cast<uint64>(uuid.Data4[6]) << 8) | (static_cast<uint64>(uuid.Data4[7]) << 0)));
135 	}
UUIDToWin32mpt::MPT_INLINE_NS::UUID136 	static ::UUID UUIDToWin32(mpt::UUID uuid) {
137 		::UUID result = ::UUID();
138 		result.Data1 = uuid.GetData1();
139 		result.Data2 = uuid.GetData2();
140 		result.Data3 = uuid.GetData3();
141 		result.Data4[0] = static_cast<uint8>(uuid.GetData4() >> 56);
142 		result.Data4[1] = static_cast<uint8>(uuid.GetData4() >> 48);
143 		result.Data4[2] = static_cast<uint8>(uuid.GetData4() >> 40);
144 		result.Data4[3] = static_cast<uint8>(uuid.GetData4() >> 32);
145 		result.Data4[4] = static_cast<uint8>(uuid.GetData4() >> 24);
146 		result.Data4[5] = static_cast<uint8>(uuid.GetData4() >> 16);
147 		result.Data4[6] = static_cast<uint8>(uuid.GetData4() >> 8);
148 		result.Data4[7] = static_cast<uint8>(uuid.GetData4() >> 0);
149 		return result;
150 	}
151 
152 public:
UUIDmpt::MPT_INLINE_NS::UUID153 	explicit UUID(::UUID uuid) {
154 		*this = UUIDFromWin32(uuid);
155 	}
operator ::UUIDmpt::MPT_INLINE_NS::UUID156 	operator ::UUID() const {
157 		return UUIDToWin32(*this);
158 	}
159 #endif // MPT_OS_WINDOWS
160 private:
NibbleFromCharmpt::MPT_INLINE_NS::UUID161 	static MPT_CONSTEXPRINLINE uint8 NibbleFromChar(char x) {
162 		return ('0' <= x && x <= '9') ? static_cast<uint8>(x - '0' + 0) : ('a' <= x && x <= 'z') ? static_cast<uint8>(x - 'a' + 10)
163 			: ('A' <= x && x <= 'Z')                                                             ? static_cast<uint8>(x - 'A' + 10)
164 																								 : mpt::constexpr_throw<uint8>(std::domain_error(""));
165 	}
ByteFromHexmpt::MPT_INLINE_NS::UUID166 	static MPT_CONSTEXPRINLINE uint8 ByteFromHex(char x, char y) {
167 		return static_cast<uint8>(uint8(0) | (NibbleFromChar(x) << 4) | (NibbleFromChar(y) << 0));
168 	}
ParseHex16mpt::MPT_INLINE_NS::UUID169 	static MPT_CONSTEXPRINLINE uint16 ParseHex16(const char * str) {
170 		return static_cast<uint16>(uint16(0) | (static_cast<uint16>(ByteFromHex(str[0], str[1])) << 8) | (static_cast<uint16>(ByteFromHex(str[2], str[3])) << 0));
171 	}
ParseHex32mpt::MPT_INLINE_NS::UUID172 	static MPT_CONSTEXPRINLINE uint32 ParseHex32(const char * str) {
173 		return static_cast<uint32>(uint32(0) | (static_cast<uint32>(ByteFromHex(str[0], str[1])) << 24) | (static_cast<uint32>(ByteFromHex(str[2], str[3])) << 16) | (static_cast<uint32>(ByteFromHex(str[4], str[5])) << 8) | (static_cast<uint32>(ByteFromHex(str[6], str[7])) << 0));
174 	}
175 
176 public:
ParseLiteralmpt::MPT_INLINE_NS::UUID177 	static MPT_CONSTEXPRINLINE UUID ParseLiteral(const char * str, std::size_t len) {
178 		return (len == 36 && str[8] == '-' && str[13] == '-' && str[18] == '-' && str[23] == '-') ? mpt::UUID(
179 				   ParseHex32(str + 0),
180 				   ParseHex16(str + 9),
181 				   ParseHex16(str + 14),
182 				   uint64(0)
183 					   | (static_cast<uint64>(ParseHex16(str + 19)) << 48)
184 					   | (static_cast<uint64>(ParseHex16(str + 24)) << 32)
185 					   | (static_cast<uint64>(ParseHex32(str + 28)) << 0))
186 																								  : mpt::constexpr_throw<mpt::UUID>(std::domain_error(""));
187 	}
188 
189 public:
UUIDmpt::MPT_INLINE_NS::UUID190 	MPT_CONSTEXPRINLINE UUID() noexcept
191 		: Data1(0)
192 		, Data2(0)
193 		, Data3(0)
194 		, Data4(0) {
195 		return;
196 	}
UUIDmpt::MPT_INLINE_NS::UUID197 	MPT_CONSTEXPRINLINE explicit UUID(uint32 Data1, uint16 Data2, uint16 Data3, uint64 Data4) noexcept
198 		: Data1(Data1)
199 		, Data2(Data2)
200 		, Data3(Data3)
201 		, Data4(Data4) {
202 		return;
203 	}
UUIDmpt::MPT_INLINE_NS::UUID204 	explicit UUID(UUIDbin uuid) {
205 		Data1 = uuid.Data1.get();
206 		Data2 = uuid.Data2.get();
207 		Data3 = uuid.Data3.get();
208 		Data4 = uuid.Data4.get();
209 	}
UUIDmpt::MPT_INLINE_NS::UUID210 	explicit UUID(GUIDms guid) {
211 		Data1 = guid.Data1.get();
212 		Data2 = guid.Data2.get();
213 		Data3 = guid.Data3.get();
214 		Data4 = guid.Data4.get();
215 	}
operator UUIDbinmpt::MPT_INLINE_NS::UUID216 	operator UUIDbin() const {
217 		UUIDbin result{};
218 		result.Data1 = GetData1();
219 		result.Data2 = GetData2();
220 		result.Data3 = GetData3();
221 		result.Data4 = GetData4();
222 		return result;
223 	}
operator GUIDmsmpt::MPT_INLINE_NS::UUID224 	operator GUIDms() const {
225 		GUIDms result{};
226 		result.Data1 = GetData1();
227 		result.Data2 = GetData2();
228 		result.Data3 = GetData3();
229 		result.Data4 = GetData4();
230 		return result;
231 	}
232 
233 public:
234 	// Create a UUID
235 	template <typename Trng>
Generatempt::MPT_INLINE_NS::UUID236 	static UUID Generate(Trng & rng) {
237 #if MPT_OS_WINDOWS && MPT_OS_WINDOWS_WINRT
238 #if (_WIN32_WINNT >= 0x0602)
239 		::GUID guid = ::GUID();
240 		HRESULT result = CoCreateGuid(&guid);
241 		if (result != S_OK) {
242 			return mpt::UUID::RFC4122Random(rng);
243 		}
244 		return mpt::UUID::UUIDFromWin32(guid);
245 #else
246 		return mpt::UUID::RFC4122Random(rng);
247 #endif
248 #elif MPT_OS_WINDOWS && !MPT_OS_WINDOWS_WINRT
249 		::UUID uuid = ::UUID();
250 		RPC_STATUS status = ::UuidCreate(&uuid);
251 		if (status != RPC_S_OK && status != RPC_S_UUID_LOCAL_ONLY) {
252 			return mpt::UUID::RFC4122Random(rng);
253 		}
254 		status = RPC_S_OK;
255 		if (UuidIsNil(&uuid, &status) != FALSE) {
256 			return mpt::UUID::RFC4122Random(rng);
257 		}
258 		if (status != RPC_S_OK) {
259 			return mpt::UUID::RFC4122Random(rng);
260 		}
261 		return mpt::UUID::UUIDFromWin32(uuid);
262 #else
263 		return RFC4122Random(rng);
264 #endif
265 	}
266 	// Create a UUID that contains local, traceable information.
267 	// Safe for local use. May be faster.
268 	template <typename Trng>
GenerateLocalUseOnlympt::MPT_INLINE_NS::UUID269 	static UUID GenerateLocalUseOnly(Trng & rng) {
270 #if MPT_OS_WINDOWS && MPT_OS_WINDOWS_WINRT
271 #if (_WIN32_WINNT >= 0x0602)
272 		::GUID guid = ::GUID();
273 		HRESULT result = CoCreateGuid(&guid);
274 		if (result != S_OK) {
275 			return mpt::UUID::RFC4122Random(rng);
276 		}
277 		return mpt::UUID::UUIDFromWin32(guid);
278 #else
279 		return mpt::UUID::RFC4122Random(rng);
280 #endif
281 #elif MPT_OS_WINDOWS && !MPT_OS_WINDOWS_WINRT
282 #if _WIN32_WINNT >= 0x0501
283 		// Available since Win2000, but we check for WinXP in order to not use this
284 		// function in Win32old builds. It is not available on some non-fully
285 		// patched Win98SE installs in the wild.
286 		::UUID uuid = ::UUID();
287 		RPC_STATUS status = ::UuidCreateSequential(&uuid);
288 		if (status != RPC_S_OK && status != RPC_S_UUID_LOCAL_ONLY) {
289 			return Generate(rng);
290 		}
291 		status = RPC_S_OK;
292 		if (UuidIsNil(&uuid, &status) != FALSE) {
293 			return mpt::UUID::RFC4122Random(rng);
294 		}
295 		if (status != RPC_S_OK) {
296 			return mpt::UUID::RFC4122Random(rng);
297 		}
298 		return mpt::UUID::UUIDFromWin32(uuid);
299 #else
300 		// Fallback to ::UuidCreate is safe as ::UuidCreateSequential is only a
301 		// tiny performance optimization.
302 		return Generate(rng);
303 #endif
304 #else
305 		return RFC4122Random(rng);
306 #endif
307 	}
308 	// Create a RFC4122 Random UUID.
309 	template <typename Trng>
RFC4122Randommpt::MPT_INLINE_NS::UUID310 	static UUID RFC4122Random(Trng & prng) {
311 		UUID result;
312 		result.Data1 = mpt::random<uint32>(prng);
313 		result.Data2 = mpt::random<uint16>(prng);
314 		result.Data3 = mpt::random<uint16>(prng);
315 		result.Data4 = mpt::random<uint64>(prng);
316 		result.MakeRFC4122(4);
317 		return result;
318 	}
319 	friend UUID UUIDRFC4122NamespaceV3(const UUID & ns, const mpt::ustring & name);
320 	friend UUID UUIDRFC4122NamespaceV5(const UUID & ns, const mpt::ustring & name);
321 
322 public:
323 	// General UUID<->string conversion.
324 	// The string must/will be in standard UUID format: 4f9a455d-e7ef-4367-b2f0-0c83a38a5c72
FromStringmpt::MPT_INLINE_NS::UUID325 	static UUID FromString(const mpt::ustring & str) {
326 		std::vector<mpt::ustring> segments = mpt::split<mpt::ustring>(str, MPT_ULITERAL("-"));
327 		if (segments.size() != 5) {
328 			return UUID();
329 		}
330 		if (segments[0].length() != 8) {
331 			return UUID();
332 		}
333 		if (segments[1].length() != 4) {
334 			return UUID();
335 		}
336 		if (segments[2].length() != 4) {
337 			return UUID();
338 		}
339 		if (segments[3].length() != 4) {
340 			return UUID();
341 		}
342 		if (segments[4].length() != 12) {
343 			return UUID();
344 		}
345 		UUID result;
346 		result.Data1 = mpt::ConvertHexStringTo<uint32>(segments[0]);
347 		result.Data2 = mpt::ConvertHexStringTo<uint16>(segments[1]);
348 		result.Data3 = mpt::ConvertHexStringTo<uint16>(segments[2]);
349 		result.Data4 = mpt::ConvertHexStringTo<uint64>(segments[3] + segments[4]);
350 		return result;
351 	}
ToAStringmpt::MPT_INLINE_NS::UUID352 	std::string ToAString() const {
353 		return std::string()
354 			+ mpt::format<std::string>::hex0<8>(GetData1())
355 			+ std::string("-")
356 			+ mpt::format<std::string>::hex0<4>(GetData2())
357 			+ std::string("-")
358 			+ mpt::format<std::string>::hex0<4>(GetData3())
359 			+ std::string("-")
360 			+ mpt::format<std::string>::hex0<4>(static_cast<uint16>(GetData4() >> 48))
361 			+ std::string("-")
362 			+ mpt::format<std::string>::hex0<4>(static_cast<uint16>(GetData4() >> 32))
363 			+ mpt::format<std::string>::hex0<8>(static_cast<uint32>(GetData4() >> 0));
364 	}
ToUStringmpt::MPT_INLINE_NS::UUID365 	mpt::ustring ToUString() const {
366 		return mpt::ustring()
367 			+ mpt::format<mpt::ustring>::hex0<8>(GetData1())
368 			+ MPT_USTRING("-")
369 			+ mpt::format<mpt::ustring>::hex0<4>(GetData2())
370 			+ MPT_USTRING("-")
371 			+ mpt::format<mpt::ustring>::hex0<4>(GetData3())
372 			+ MPT_USTRING("-")
373 			+ mpt::format<mpt::ustring>::hex0<4>(static_cast<uint16>(GetData4() >> 48))
374 			+ MPT_USTRING("-")
375 			+ mpt::format<mpt::ustring>::hex0<4>(static_cast<uint16>(GetData4() >> 32))
376 			+ mpt::format<mpt::ustring>::hex0<8>(static_cast<uint32>(GetData4() >> 0));
377 	}
378 };
379 
operator ==(const mpt::UUID & a,const mpt::UUID & b)380 MPT_CONSTEXPRINLINE bool operator==(const mpt::UUID & a, const mpt::UUID & b) noexcept {
381 	return (a.GetData1() == b.GetData1()) && (a.GetData2() == b.GetData2()) && (a.GetData3() == b.GetData3()) && (a.GetData4() == b.GetData4());
382 }
383 
operator !=(const mpt::UUID & a,const mpt::UUID & b)384 MPT_CONSTEXPRINLINE bool operator!=(const mpt::UUID & a, const mpt::UUID & b) noexcept {
385 	return (a.GetData1() != b.GetData1()) || (a.GetData2() != b.GetData2()) || (a.GetData3() != b.GetData3()) || (a.GetData4() != b.GetData4());
386 }
387 
388 
389 namespace uuid_literals {
390 
operator ""_uuid(const char * str,std::size_t len)391 MPT_CONSTEXPRINLINE mpt::UUID operator"" _uuid(const char * str, std::size_t len) {
392 	return mpt::UUID::ParseLiteral(str, len);
393 }
394 
395 } // namespace uuid_literals
396 
397 
398 template <typename Tstring>
uuid_to_string(mpt::UUID uuid)399 inline Tstring uuid_to_string(mpt::UUID uuid) {
400 	return mpt::transcode<Tstring>(uuid.ToUString());
401 }
402 
403 template <>
uuid_to_string(mpt::UUID uuid)404 inline std::string uuid_to_string<std::string>(mpt::UUID uuid) {
405 	return uuid.ToAString();
406 }
407 
408 template <typename Tstring, typename T, std::enable_if_t<std::is_same<T, mpt::UUID>::value, bool> = true>
format_value_default(const T & x)409 inline Tstring format_value_default(const T & x) {
410 	return mpt::transcode<Tstring>(mpt::uuid_to_string<typename mpt::select_format_string_type<Tstring>::type>(x));
411 }
412 
413 
414 } // namespace MPT_INLINE_NS
415 } // namespace mpt
416 
417 
418 
419 #endif // MPT_UUID_UUID_HPP
420